Skip to content

Commit b87b332

Browse files
wtfsayoclaude
andcommitted
fix(devin): keep both constraints when a Gemini type union and anyOf are disjoint
When no existing anyOf branch shared a type with the type array, the fallback kept the anyOf and dropped the type union, so the branches admitted types the node never allowed. Keep both under allOf, the same form the conflict path uses; live, gemini-3-8-flash-medium accepts it. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
1 parent 3232330 commit b87b332

2 files changed

Lines changed: 15 additions & 2 deletions

File tree

‎src/adapters/devin/cloud-direct/tool-schema.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,9 @@ function splitTypeArray(node: Schema, types: unknown[]): Schema {
7474
}
7575
}
7676
if (nullReachable) branches.push({ type: 'null' });
77-
// Nothing satisfies both unions; keep the existing anyOf rather than emit an empty one.
78-
if (branches.length === 0) return { ...annotations, ...rest, anyOf: existing };
77+
// The two unions are disjoint. Keep both constraints rather than drop the type union,
78+
// which would let the anyOf branches admit types the node never allowed.
79+
if (branches.length === 0) return { ...annotations, allOf: [splitTypeArray({ ...rest, type: types }, types), { anyOf: existing }] };
7980
if (branches.length === 1 && isSchema(branches[0])) return { ...annotations, ...branches[0] };
8081
return { ...annotations, anyOf: branches };
8182
}

‎tests/providers/devin-chat-wire-fixes.test.ts‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,18 @@ describe("Gemini tool schema type arrays", () => {
149149
});
150150
});
151151

152+
test("a type union disjoint from the existing anyOf keeps both constraints", () => {
153+
const out = normalizeDevinToolParameters("gemini-x", {
154+
type: ["string", "null"], minLength: 2, anyOf: [{ type: "integer" }],
155+
}) as any;
156+
expect(out).toEqual({
157+
allOf: [
158+
{ anyOf: [{ minLength: 2, type: "string" }, { type: "null" }] },
159+
{ anyOf: [{ type: "integer" }] },
160+
],
161+
});
162+
});
163+
152164
test("an existing anyOf that agrees with the outer keywords is folded in, not nested under allOf", () => {
153165
const out = normalizeDevinToolParameters("MODEL_GOOGLE_GEMINI_2_5_PRO", {
154166
type: ["object", "null"], anyOf: [{ required: ["a"] }, { required: ["b"] }],

0 commit comments

Comments
 (0)