fix(ai): preserve CCA schemas with annotation conflicts
This commit is contained in:
@@ -8,6 +8,9 @@
|
||||
|
||||
- Fixed an issue in the Responses API where empty tool results were incorrectly serialized with a "(see attached image)" placeholder, causing models to look for non-existent attachments.
|
||||
- Fixed OpenAI Responses server non-streaming envelopes to always include the required "incomplete_details" field, using null for completed responses.
|
||||
### Fixed
|
||||
|
||||
- Preserved Cloud Code Assist tool schemas when mixed-type unions carry branch-local validation descriptions.
|
||||
|
||||
## [16.4.2] - 2026-07-10
|
||||
|
||||
|
||||
@@ -547,7 +547,11 @@ function collapseMixedTypeCombinerVariants(schema: JsonObject, combiner: "anyOf"
|
||||
|
||||
const existingValue = mergedVariantFields[key];
|
||||
if (existingValue !== undefined && !areJsonValuesEqual(existingValue, variantValue)) {
|
||||
return schema;
|
||||
if (key !== "description") return schema;
|
||||
// Descriptions are annotations, so merge branch-local spill text instead of
|
||||
// treating it as a structural incompatibility.
|
||||
mergedVariantFields[key] = mergeSchemaDescriptions(existingValue, variantValue);
|
||||
continue;
|
||||
}
|
||||
mergedVariantFields[key] = variantValue;
|
||||
}
|
||||
@@ -588,7 +592,9 @@ function collapseMixedTypeCombinerVariants(schema: JsonObject, combiner: "anyOf"
|
||||
const value = mergedVariantFields[key];
|
||||
const existingValue = nextSchema[key];
|
||||
if (existingValue !== undefined && !areJsonValuesEqual(existingValue, value)) {
|
||||
return schema;
|
||||
if (key !== "description") return schema;
|
||||
nextSchema[key] = mergeSchemaDescriptions(existingValue, value);
|
||||
continue;
|
||||
}
|
||||
if (existingValue === undefined) {
|
||||
nextSchema[key] = value;
|
||||
@@ -597,6 +603,13 @@ function collapseMixedTypeCombinerVariants(schema: JsonObject, combiner: "anyOf"
|
||||
return nextSchema;
|
||||
}
|
||||
|
||||
function mergeSchemaDescriptions(existing: unknown, incoming: unknown): string {
|
||||
if (typeof existing !== "string") return typeof incoming === "string" ? incoming : "";
|
||||
if (typeof incoming !== "string" || incoming.length === 0 || existing === incoming) return existing;
|
||||
if (existing.length === 0) return incoming;
|
||||
return `${existing}\n\n${incoming}`;
|
||||
}
|
||||
|
||||
function collapseSameTypeCombinerVariants(schema: JsonObject, combiner: "anyOf" | "oneOf"): JsonObject {
|
||||
const variantsRaw = schema[combiner];
|
||||
if (!Array.isArray(variantsRaw) || variantsRaw.length === 0) return schema;
|
||||
|
||||
@@ -1021,6 +1021,17 @@ describe("normalizeSchemaForCCA", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps mixed unions when branch validation spill differs from the parent description", () => {
|
||||
const normalized = normalizeSchemaForCCA({
|
||||
anyOf: [{ type: "string" }, { type: "array", minItems: 1, items: { type: "string" } }],
|
||||
description: "Optional result type",
|
||||
}) as Record<string, unknown>;
|
||||
|
||||
expect(normalized.type).toBe("string");
|
||||
expect(normalized.anyOf).toBeUndefined();
|
||||
expect(normalized.description).toBe("Optional result type\n\n{minItems: 1}");
|
||||
});
|
||||
|
||||
it("strips sibling type-specific keys copied from parent when mixed-type collapse picks opposing type", () => {
|
||||
// Edge case: parent has a sibling `items` outside the anyOf,
|
||||
// and the chosen type is string. The sibling must be stripped.
|
||||
|
||||
@@ -138,6 +138,22 @@ describe("builtin tool schemas provider compatibility", () => {
|
||||
expect(failures).toEqual([]);
|
||||
});
|
||||
|
||||
it("preserves the yield result schema for Cloud Code Assist", async () => {
|
||||
const toolSchemas = await collectToolSchemas();
|
||||
const yieldEntry = toolSchemas.find(tool => tool.name === "yield");
|
||||
expect(yieldEntry).toBeDefined();
|
||||
if (!yieldEntry) return;
|
||||
|
||||
const normalized = asSchemaObject(normalizeSchemaForCCA(yieldEntry.schema));
|
||||
const properties = asSchemaObject(normalized?.properties);
|
||||
const typeSchema = asSchemaObject(properties?.type);
|
||||
|
||||
expect(normalized?.type).toBe("object");
|
||||
expect(properties?.result).toBeDefined();
|
||||
expect(typeSchema?.type).toBe("string");
|
||||
expect(typeSchema?.anyOf).toBeUndefined();
|
||||
});
|
||||
|
||||
it("asserts that browser tool schema root has 'type: \"object\"' for Codex and OpenAI Responses compatibility", async () => {
|
||||
const toolSchemas = await collectToolSchemas();
|
||||
const browserEntry = toolSchemas.find(tool => tool.name === "browser");
|
||||
|
||||
Reference in New Issue
Block a user