Merge branch 'main' into fix/antigravity-usage-display
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
- Fixed Antigravity usage reports missing `email` and `accountId` in metadata, so the `/usage` display and the deduplicator can associate reports with their credentials.
|
||||
- Fixed usage-report dedup ignoring `projectId` for Google Cloud providers, preventing duplicate credential entries from being recognized as the same account.
|
||||
|
||||
- Fixed Cloud Code Assist (Antigravity / Gemini CLI) rejecting the `github` tool with HTTP 400 when the `pr` parameter schema contained `anyOf: [string, array]`. The CCA mixed-type combiner collapse picked the first non-null type (`string`) but indiscriminately copied type-specific keys from variant branches — `items` from the array variant leaked onto the string-typed result, producing `{type: "string", items: {...}}` which Google's API rejects as invalid. The collapse now filters merged variant fields against the winning type's allowed key set. ([#2002](https://github.com/can1357/oh-my-pi/pull/2002))
|
||||
## [15.9.67] - 2026-06-06
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -154,6 +154,22 @@ export const CLOUD_CODE_ASSIST_TYPE_SPECIFIC_KEYS: Record<string, Record<string,
|
||||
null: {},
|
||||
};
|
||||
|
||||
/**
|
||||
* Flat set of every type-specific key across all CCA types.
|
||||
* Used to identify sibling keys that need filtering during mixed-type collapse.
|
||||
*/
|
||||
export const ALL_CCA_TYPE_SPECIFIC_KEYS: Record<string, true> = buildAllCcaTypeSpecificKeys();
|
||||
|
||||
function buildAllCcaTypeSpecificKeys(): Record<string, true> {
|
||||
const all: Record<string, true> = {};
|
||||
for (const typeKeys of Object.values(CLOUD_CODE_ASSIST_TYPE_SPECIFIC_KEYS)) {
|
||||
for (const key in typeKeys) {
|
||||
all[key] = true;
|
||||
}
|
||||
}
|
||||
return all;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cloud Code Assist shared schema keys allowed on any type.
|
||||
* Used alongside CLOUD_CODE_ASSIST_TYPE_SPECIFIC_KEYS for CCA combiner collapsing.
|
||||
|
||||
@@ -11,6 +11,7 @@ import { dereferenceJsonSchema } from "./dereference";
|
||||
import { upgradeJsonSchemaTo202012 } from "./draft";
|
||||
import { areJsonValuesEqual, mergePropertySchemas } from "./equality";
|
||||
import {
|
||||
ALL_CCA_TYPE_SPECIFIC_KEYS,
|
||||
CLOUD_CODE_ASSIST_SHARED_SCHEMA_KEYS,
|
||||
CLOUD_CODE_ASSIST_TYPE_SPECIFIC_KEYS,
|
||||
COMBINATOR_KEYS,
|
||||
@@ -501,12 +502,32 @@ function collapseMixedTypeCombinerVariants(schema: JsonObject, combiner: "anyOf"
|
||||
if (variantTypes.length < 2 || variantTypes.every(type => type === "object")) {
|
||||
return schema;
|
||||
}
|
||||
|
||||
const nextSchema = copySchemaWithout(schema, combiner);
|
||||
const nonNullTypes = variantTypes.filter(t => t !== "null");
|
||||
nextSchema.type = nonNullTypes[0] ?? variantTypes[0];
|
||||
const chosenType: string = nonNullTypes[0] ?? variantTypes[0];
|
||||
nextSchema.type = chosenType;
|
||||
const chosenTypeAllowedKeys = CLOUD_CODE_ASSIST_TYPE_SPECIFIC_KEYS[chosenType] ?? {};
|
||||
|
||||
// Strip sibling keys that were copied from the parent and belong to a
|
||||
// different type (e.g. `items` sibling on a now-string-typed schema).
|
||||
for (const key in nextSchema) {
|
||||
if (!Object.hasOwn(nextSchema, key)) continue;
|
||||
if (key === "type") continue;
|
||||
if (
|
||||
Object.hasOwn(ALL_CCA_TYPE_SPECIFIC_KEYS, key) &&
|
||||
!Object.hasOwn(chosenTypeAllowedKeys, key) &&
|
||||
!Object.hasOwn(CLOUD_CODE_ASSIST_SHARED_SCHEMA_KEYS, key)
|
||||
) {
|
||||
delete nextSchema[key];
|
||||
}
|
||||
}
|
||||
|
||||
for (const key in mergedVariantFields) {
|
||||
if (!Object.hasOwn(mergedVariantFields, key)) continue;
|
||||
// Drop type-specific keys that don't belong to the chosen type
|
||||
if (!Object.hasOwn(chosenTypeAllowedKeys, key) && !Object.hasOwn(CLOUD_CODE_ASSIST_SHARED_SCHEMA_KEYS, key)) {
|
||||
continue;
|
||||
}
|
||||
const value = mergedVariantFields[key];
|
||||
const existingValue = nextSchema[key];
|
||||
if (existingValue !== undefined && !areJsonValuesEqual(existingValue, value)) {
|
||||
|
||||
@@ -728,6 +728,18 @@ describe("stripResidualCombiners", () => {
|
||||
expect(normalized.anyOf).toBeUndefined();
|
||||
expect(normalized.oneOf).toBeUndefined();
|
||||
});
|
||||
|
||||
it("drops array-only keys when mixed-type collapse picks string from anyOf fixpoint", () => {
|
||||
const stripped = stripResidualCombiners({
|
||||
anyOf: [{ type: "string" }, { type: "array", items: { type: "string" } }],
|
||||
description: "pr number, url, or branch",
|
||||
}) as Record<string, unknown>;
|
||||
|
||||
expect(stripped.type).toBe("string");
|
||||
expect(stripped.items).toBeUndefined();
|
||||
expect(stripped.anyOf).toBeUndefined();
|
||||
expect(stripped.description).toBe("pr number, url, or branch");
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -952,6 +964,36 @@ describe("normalizeSchemaForCCA", () => {
|
||||
properties: {},
|
||||
});
|
||||
});
|
||||
|
||||
it("strips array-only keys when mixed-type collapse picks a non-array type", () => {
|
||||
// Regression: anyOf [{type:"string"}, {type:"array", items:{type:"string"}}]
|
||||
// collapsed to {type:"string", items:{type:"string"}} which is invalid.
|
||||
// The fix filters mergedVariantFields against the chosen type's allowed keys.
|
||||
const normalized = normalizeSchemaForCCA({
|
||||
anyOf: [{ type: "string" }, { type: "array", items: { type: "string" } }],
|
||||
description: "pr number, url, or branch",
|
||||
});
|
||||
|
||||
expect(normalized).toEqual({
|
||||
type: "string",
|
||||
description: "pr number, url, or branch",
|
||||
});
|
||||
});
|
||||
|
||||
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.
|
||||
const normalized = normalizeSchemaForCCA({
|
||||
anyOf: [{ type: "string" }, { type: "array", items: { type: "number" } }],
|
||||
items: { type: "string" },
|
||||
description: "pr number, url, or branch",
|
||||
});
|
||||
|
||||
expect(normalized).toEqual({
|
||||
type: "string",
|
||||
description: "pr number, url, or branch",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user