@bitkyc08/opencodex 2.7.34 → 2.7.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/gui/dist/assets/index-BunUANVE.js +52 -0
- package/gui/dist/index.html +1 -1
- package/package.json +5 -2
- package/src/adapters/anthropic.ts +22 -37
- package/src/adapters/cursor/cursor-errors.ts +11 -0
- package/src/adapters/cursor/discovery.ts +25 -0
- package/src/adapters/cursor/live-transport.ts +5 -0
- package/src/adapters/cursor/protobuf-events.ts +14 -0
- package/src/adapters/cursor/protobuf-request.ts +11 -1
- package/src/adapters/cursor/request-builder.ts +20 -3
- package/src/adapters/cursor.ts +69 -22
- package/src/adapters/google-http.ts +23 -4
- package/src/adapters/google-tool-schema.ts +143 -68
- package/src/adapters/google-wire-compiler.ts +228 -0
- package/src/adapters/google.ts +27 -18
- package/src/adapters/kiro.ts +25 -1
- package/src/chat/inbound.ts +295 -0
- package/src/chat/outbound.ts +565 -0
- package/src/claude/agents-inject.ts +4 -2
- package/src/codex/catalog.ts +31 -2
- package/src/codex/home.ts +1 -1
- package/src/codex/quota.ts +43 -13
- package/src/index.ts +1 -0
- package/src/lib/sse-decoder.ts +84 -0
- package/src/providers/antigravity-models.ts +9 -3
- package/src/server/chat-completions.ts +258 -0
- package/src/server/gui-static.ts +1 -0
- package/src/server/index.ts +24 -0
- package/src/server/management-api.ts +9 -0
- package/src/server/responses.ts +28 -2
- package/src/types.ts +6 -3
- package/gui/dist/assets/index-BkmJJgg6.js +0 -52
|
@@ -1,98 +1,173 @@
|
|
|
1
1
|
type Schema = Record<string, unknown>;
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"uniqueItems", "additionalItems", "unevaluatedProperties", "unevaluatedItems",
|
|
15
|
-
"dependentRequired", "dependentSchemas", "propertyNames", "contains",
|
|
16
|
-
"encrypted",
|
|
17
|
-
]);
|
|
18
|
-
|
|
19
|
-
const MAX_DEREF_DEPTH = 64;
|
|
3
|
+
// Google documents this function-schema subset: type, nullable, required, format, description,
|
|
4
|
+
// properties, items, enum, anyOf, $ref, and $defs. We inline local refs and normalize anyOf, so
|
|
5
|
+
// only the eight scalar/container keywords below are ever emitted. Building from an allowlist
|
|
6
|
+
// prevents new MCP/JSON-Schema annotations from turning into provider-wide 400 responses.
|
|
7
|
+
const ALLOWED_TYPES = new Set(["string", "integer", "number", "boolean", "array", "object"]);
|
|
8
|
+
const MAX_SCHEMA_DEPTH = 24; // Google's documented nesting limit is 32; leave headroom for CCA.
|
|
9
|
+
const MAX_DEREF_DEPTH = 16;
|
|
10
|
+
|
|
11
|
+
function isRecord(value: unknown): value is Schema {
|
|
12
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
13
|
+
}
|
|
20
14
|
|
|
21
15
|
function resolveRef(ref: string, defs: Map<string, unknown>): unknown {
|
|
22
|
-
// Only local pointers into the schema's own $defs/definitions are
|
|
23
|
-
// "#/$defs/Foo"). Anything else cannot be inlined, so it collapses to an unconstrained object.
|
|
16
|
+
// Only local pointers into the schema's own $defs/definitions are safe to inline.
|
|
24
17
|
const match = /^#\/(?:\$defs|definitions)\/(.+)$/.exec(ref);
|
|
25
18
|
if (!match) return undefined;
|
|
26
|
-
|
|
19
|
+
try {
|
|
20
|
+
return defs.get(decodeURIComponent(match[1].replace(/~1/g, "/").replace(/~0/g, "~")));
|
|
21
|
+
} catch {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
function collectDefs(root: unknown, defs: Map<string, unknown>): void {
|
|
30
|
-
if (!root
|
|
27
|
+
if (!isRecord(root)) return;
|
|
31
28
|
for (const bag of ["$defs", "definitions"] as const) {
|
|
32
|
-
const group =
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
29
|
+
const group = root[bag];
|
|
30
|
+
if (!isRecord(group)) continue;
|
|
31
|
+
for (const [name, value] of Object.entries(group)) {
|
|
32
|
+
if (!defs.has(name)) defs.set(name, value);
|
|
37
33
|
}
|
|
38
34
|
}
|
|
39
35
|
}
|
|
40
36
|
|
|
41
|
-
function normalizeType(value: unknown, out: Schema): void {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
37
|
+
function normalizeType(value: unknown, out: Schema, preserveNullType: boolean): void {
|
|
38
|
+
const candidates = Array.isArray(value) ? value : [value];
|
|
39
|
+
let sawNull = false;
|
|
40
|
+
|
|
41
|
+
for (const candidate of candidates) {
|
|
42
|
+
if (typeof candidate !== "string") continue;
|
|
43
|
+
const type = candidate.toLowerCase();
|
|
44
|
+
if (type === "null") {
|
|
45
|
+
sawNull = true;
|
|
46
|
+
} else if (out.type === undefined && ALLOWED_TYPES.has(type)) {
|
|
47
|
+
out.type = type;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!sawNull) return;
|
|
52
|
+
if (out.type !== undefined) out.nullable = true;
|
|
53
|
+
else if (preserveNullType) out.type = "null";
|
|
54
|
+
else out.nullable = true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function sanitizeEnum(value: unknown): string[] | undefined {
|
|
58
|
+
if (!Array.isArray(value)) return undefined;
|
|
59
|
+
const values = [...new Set(value.filter((item): item is string => typeof item === "string"))];
|
|
60
|
+
return values.length > 0 ? values : undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function normalizeAnyOf(
|
|
64
|
+
value: unknown,
|
|
65
|
+
defs: Map<string, unknown>,
|
|
66
|
+
depth: number,
|
|
67
|
+
refDepth: number,
|
|
68
|
+
): Schema {
|
|
69
|
+
if (!Array.isArray(value) || value.length === 0) return {};
|
|
70
|
+
const schemas = value.map(item => sanitizeSchema(item, defs, depth + 1, refDepth, true));
|
|
71
|
+
|
|
72
|
+
const nonNullSchemas = schemas.filter(schema => schema.type !== "null");
|
|
73
|
+
const nullSchemas = schemas.filter(schema => schema.type === "null");
|
|
74
|
+
if (
|
|
75
|
+
nonNullSchemas.length === 1
|
|
76
|
+
&& nullSchemas.length > 0
|
|
77
|
+
&& nullSchemas.every(schema => Object.keys(schema).every(key => key === "type"))
|
|
78
|
+
) {
|
|
79
|
+
return { ...nonNullSchemas[0], nullable: true };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const type = schemas[0]?.type;
|
|
83
|
+
const sameType = schemas.length > 0 && schemas.every(schema => schema.type === type);
|
|
84
|
+
const enumOnly = schemas.every(schema => {
|
|
85
|
+
const allowedKeys = type === undefined ? new Set(["enum"]) : new Set(["type", "enum"]);
|
|
86
|
+
return Array.isArray(schema.enum) && Object.keys(schema).every(key => allowedKeys.has(key));
|
|
87
|
+
});
|
|
88
|
+
if (sameType && enumOnly && type !== "null") {
|
|
89
|
+
const values = sanitizeEnum(schemas.flatMap(schema => schema.enum as unknown[]));
|
|
90
|
+
if (values) return { ...(typeof type === "string" ? { type } : {}), enum: values };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// CCA's Claude bridge turns typed anyOf branches into an invalid input_schema. Widen only this
|
|
94
|
+
// node when a union cannot be collapsed losslessly; parent annotations and structure survive.
|
|
95
|
+
return {};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function sanitizeProperties(
|
|
99
|
+
value: unknown,
|
|
100
|
+
defs: Map<string, unknown>,
|
|
101
|
+
depth: number,
|
|
102
|
+
refDepth: number,
|
|
103
|
+
): Record<string, Schema> | undefined {
|
|
104
|
+
if (!isRecord(value)) return undefined;
|
|
105
|
+
const properties: Record<string, Schema> = Object.create(null) as Record<string, Schema>;
|
|
106
|
+
for (const [name, schema] of Object.entries(value)) {
|
|
107
|
+
// Property names form a name bag and must never be interpreted as schema keywords.
|
|
108
|
+
properties[name] = sanitizeSchema(schema, defs, depth + 1, refDepth, false);
|
|
47
109
|
}
|
|
48
|
-
|
|
49
|
-
if (value.includes("null")) out.nullable = true;
|
|
50
|
-
if (nonNull.length > 0) out.type = nonNull[0];
|
|
110
|
+
return properties;
|
|
51
111
|
}
|
|
52
112
|
|
|
53
|
-
function
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
113
|
+
function sanitizeSchema(
|
|
114
|
+
node: unknown,
|
|
115
|
+
defs: Map<string, unknown>,
|
|
116
|
+
depth: number,
|
|
117
|
+
refDepth: number,
|
|
118
|
+
preserveNullType: boolean,
|
|
119
|
+
): Schema {
|
|
120
|
+
if (depth >= MAX_SCHEMA_DEPTH || !isRecord(node)) return {};
|
|
57
121
|
|
|
58
|
-
if (typeof
|
|
59
|
-
const target = resolveRef(
|
|
60
|
-
if (target
|
|
61
|
-
const merged: Schema = { ...
|
|
62
|
-
for (const [key, value] of Object.entries(
|
|
122
|
+
if (typeof node.$ref === "string" && refDepth < MAX_DEREF_DEPTH) {
|
|
123
|
+
const target = resolveRef(node.$ref, defs);
|
|
124
|
+
if (isRecord(target)) {
|
|
125
|
+
const merged: Schema = { ...target };
|
|
126
|
+
for (const [key, value] of Object.entries(node)) {
|
|
63
127
|
if (key !== "$ref") merged[key] = value;
|
|
64
128
|
}
|
|
65
|
-
return
|
|
129
|
+
return sanitizeSchema(merged, defs, depth, refDepth + 1, preserveNullType);
|
|
66
130
|
}
|
|
67
131
|
}
|
|
68
132
|
|
|
69
133
|
const out: Schema = {};
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
134
|
+
normalizeType(node.type, out, preserveNullType);
|
|
135
|
+
|
|
136
|
+
if (typeof node.nullable === "boolean") out.nullable = node.nullable;
|
|
137
|
+
if (typeof node.description === "string") out.description = node.description;
|
|
138
|
+
if (typeof node.format === "string") out.format = node.format;
|
|
139
|
+
|
|
140
|
+
const enumValues = sanitizeEnum(node.enum ?? (typeof node.const === "string" ? [node.const] : undefined));
|
|
141
|
+
if (enumValues) out.enum = enumValues;
|
|
142
|
+
|
|
143
|
+
const properties = sanitizeProperties(node.properties, defs, depth, refDepth);
|
|
144
|
+
if (properties) out.properties = properties;
|
|
145
|
+
|
|
146
|
+
if (isRecord(node.items)) {
|
|
147
|
+
out.items = sanitizeSchema(node.items, defs, depth + 1, refDepth, false);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (Array.isArray(node.required)) {
|
|
151
|
+
out.required = [...new Set(node.required.filter((item): item is string => typeof item === "string"))];
|
|
87
152
|
}
|
|
153
|
+
|
|
154
|
+
if (node.anyOf !== undefined) Object.assign(out, normalizeAnyOf(node.anyOf, defs, depth, refDepth));
|
|
88
155
|
return out;
|
|
89
156
|
}
|
|
90
157
|
|
|
91
158
|
export function sanitizeGeminiToolParameters(parameters: unknown): Record<string, unknown> {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
159
|
+
try {
|
|
160
|
+
const defs = new Map<string, unknown>();
|
|
161
|
+
collectDefs(parameters, defs);
|
|
162
|
+
const root = sanitizeSchema(parameters, defs, 0, 0, false);
|
|
163
|
+
|
|
164
|
+
// Function arguments are always an object. Claude additionally rejects root composition and a
|
|
165
|
+
// missing root type even when those forms are valid general-purpose JSON Schema.
|
|
166
|
+
root.type = "object";
|
|
167
|
+
if (!isRecord(root.properties)) root.properties = {};
|
|
168
|
+
return root;
|
|
169
|
+
} catch {
|
|
170
|
+
// Last-resort containment: no third-party schema may break every tool in the request.
|
|
171
|
+
return { type: "object", properties: {} };
|
|
172
|
+
}
|
|
98
173
|
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { sanitizeGeminiToolParameters } from "./google-tool-schema";
|
|
3
|
+
|
|
4
|
+
type JsonObject = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
const GOOGLE_TOOL_NAME = /^[A-Za-z_][A-Za-z0-9_-]{0,63}$/;
|
|
7
|
+
const GOOGLE_THINKING_LEVELS = new Set(["minimal", "low", "medium", "high"]);
|
|
8
|
+
|
|
9
|
+
function isObject(value: unknown): value is JsonObject {
|
|
10
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function toolNameCodec(names: readonly string[]): {
|
|
14
|
+
toWire: (name: string) => string;
|
|
15
|
+
fromWire: (name: string) => string;
|
|
16
|
+
} {
|
|
17
|
+
const toWire = new Map<string, string>();
|
|
18
|
+
const fromWire = new Map<string, string>();
|
|
19
|
+
const used = new Set<string>();
|
|
20
|
+
|
|
21
|
+
for (const name of names) {
|
|
22
|
+
if (toWire.has(name)) continue;
|
|
23
|
+
if (GOOGLE_TOOL_NAME.test(name) && !used.has(name)) {
|
|
24
|
+
toWire.set(name, name);
|
|
25
|
+
fromWire.set(name, name);
|
|
26
|
+
used.add(name);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let cleaned = name.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
31
|
+
if (!/^[A-Za-z_]/.test(cleaned)) cleaned = `_${cleaned}`;
|
|
32
|
+
const prefix = (cleaned || "tool").slice(0, 55);
|
|
33
|
+
for (let salt = 0; ; salt++) {
|
|
34
|
+
const hashInput = salt === 0 ? name : `${name}#${salt}`;
|
|
35
|
+
const suffix = createHash("sha256").update(hashInput).digest("hex").slice(0, 8);
|
|
36
|
+
const candidate = `${prefix}_${suffix}`;
|
|
37
|
+
if (used.has(candidate)) continue;
|
|
38
|
+
toWire.set(name, candidate);
|
|
39
|
+
fromWire.set(candidate, name);
|
|
40
|
+
used.add(candidate);
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
toWire: name => toWire.get(name) ?? name,
|
|
47
|
+
fromWire: name => fromWire.get(name) ?? name,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function collectToolNames(body: JsonObject): string[] {
|
|
52
|
+
const names: string[] = [];
|
|
53
|
+
if (Array.isArray(body.tools)) {
|
|
54
|
+
for (const rawTool of body.tools) {
|
|
55
|
+
if (!isObject(rawTool) || !Array.isArray(rawTool.functionDeclarations)) continue;
|
|
56
|
+
for (const rawDeclaration of rawTool.functionDeclarations) {
|
|
57
|
+
if (isObject(rawDeclaration) && typeof rawDeclaration.name === "string") names.push(rawDeclaration.name);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (Array.isArray(body.contents)) {
|
|
62
|
+
for (const rawContent of body.contents) {
|
|
63
|
+
if (!isObject(rawContent) || !Array.isArray(rawContent.parts)) continue;
|
|
64
|
+
for (const rawPart of rawContent.parts) {
|
|
65
|
+
if (!isObject(rawPart)) continue;
|
|
66
|
+
for (const key of ["functionCall", "functionResponse"]) {
|
|
67
|
+
const call = rawPart[key];
|
|
68
|
+
if (isObject(call) && typeof call.name === "string") names.push(call.name);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return names;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function compileContents(value: unknown, toWireName: (name: string) => string): unknown[] | undefined {
|
|
77
|
+
if (!Array.isArray(value)) return undefined;
|
|
78
|
+
return value.map(rawContent => {
|
|
79
|
+
if (!isObject(rawContent)) return {};
|
|
80
|
+
const content = { ...rawContent };
|
|
81
|
+
if (!Array.isArray(rawContent.parts)) return content;
|
|
82
|
+
content.parts = rawContent.parts.map(rawPart => {
|
|
83
|
+
if (!isObject(rawPart)) return {};
|
|
84
|
+
const part = { ...rawPart };
|
|
85
|
+
for (const key of ["functionCall", "functionResponse"]) {
|
|
86
|
+
const call = rawPart[key];
|
|
87
|
+
if (isObject(call) && typeof call.name === "string") {
|
|
88
|
+
part[key] = { ...call, name: toWireName(call.name) };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return part;
|
|
92
|
+
});
|
|
93
|
+
return content;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function compileTools(value: unknown, toWireName: (name: string) => string): unknown[] | undefined {
|
|
98
|
+
if (!Array.isArray(value)) return undefined;
|
|
99
|
+
const tools = value.flatMap(rawTool => {
|
|
100
|
+
if (!isObject(rawTool) || !Array.isArray(rawTool.functionDeclarations)) return [];
|
|
101
|
+
const functionDeclarations = rawTool.functionDeclarations.flatMap(rawDeclaration => {
|
|
102
|
+
if (!isObject(rawDeclaration) || typeof rawDeclaration.name !== "string") return [];
|
|
103
|
+
return [{
|
|
104
|
+
name: toWireName(rawDeclaration.name),
|
|
105
|
+
...(typeof rawDeclaration.description === "string" ? { description: rawDeclaration.description } : {}),
|
|
106
|
+
parameters: sanitizeGeminiToolParameters(rawDeclaration.parameters),
|
|
107
|
+
}];
|
|
108
|
+
});
|
|
109
|
+
return functionDeclarations.length > 0 ? [{ functionDeclarations }] : [];
|
|
110
|
+
});
|
|
111
|
+
return tools.length > 0 ? tools : undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function finiteNumber(value: unknown): number | undefined {
|
|
115
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function compileGenerationConfig(value: unknown): JsonObject | undefined {
|
|
119
|
+
if (!isObject(value)) return undefined;
|
|
120
|
+
const out: JsonObject = {};
|
|
121
|
+
const maxOutputTokens = finiteNumber(value.maxOutputTokens);
|
|
122
|
+
if (maxOutputTokens !== undefined && maxOutputTokens > 0) out.maxOutputTokens = Math.floor(maxOutputTokens);
|
|
123
|
+
const temperature = finiteNumber(value.temperature);
|
|
124
|
+
if (temperature !== undefined && temperature >= 0) out.temperature = Math.min(2, temperature);
|
|
125
|
+
const topP = finiteNumber(value.topP);
|
|
126
|
+
if (topP !== undefined && topP >= 0) out.topP = Math.min(1, topP);
|
|
127
|
+
if (Array.isArray(value.stopSequences)) {
|
|
128
|
+
const stopSequences = [...new Set(value.stopSequences.filter(
|
|
129
|
+
(item): item is string => typeof item === "string" && item.length > 0,
|
|
130
|
+
))].slice(0, 5);
|
|
131
|
+
if (stopSequences.length > 0) out.stopSequences = stopSequences;
|
|
132
|
+
}
|
|
133
|
+
if (isObject(value.thinkingConfig) && typeof value.thinkingConfig.thinkingLevel === "string") {
|
|
134
|
+
const raw = value.thinkingConfig.thinkingLevel.toLowerCase();
|
|
135
|
+
const thinkingLevel = GOOGLE_THINKING_LEVELS.has(raw)
|
|
136
|
+
? raw
|
|
137
|
+
: (["xhigh", "max", "ultra"].includes(raw) ? "high" : undefined);
|
|
138
|
+
if (thinkingLevel) out.thinkingConfig = { thinkingLevel };
|
|
139
|
+
}
|
|
140
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function compileToolConfig(value: unknown, toWireName: (name: string) => string): JsonObject | undefined {
|
|
144
|
+
if (!isObject(value) || !isObject(value.functionCallingConfig)) return undefined;
|
|
145
|
+
const raw = value.functionCallingConfig;
|
|
146
|
+
const out: JsonObject = {};
|
|
147
|
+
if (typeof raw.mode === "string" && ["AUTO", "ANY", "NONE", "VALIDATED"].includes(raw.mode.toUpperCase())) {
|
|
148
|
+
out.mode = raw.mode.toUpperCase();
|
|
149
|
+
}
|
|
150
|
+
if (Array.isArray(raw.allowedFunctionNames)) {
|
|
151
|
+
const names = raw.allowedFunctionNames
|
|
152
|
+
.filter((name): name is string => typeof name === "string")
|
|
153
|
+
.map(toWireName);
|
|
154
|
+
if (names.length > 0) out.allowedFunctionNames = names;
|
|
155
|
+
}
|
|
156
|
+
return Object.keys(out).length > 0 ? { functionCallingConfig: out } : undefined;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Final trust boundary for every Google-family request. The adapter may build a convenient
|
|
161
|
+
* Gemini-shaped object; only this compiler is allowed to decide what reaches the wire.
|
|
162
|
+
*/
|
|
163
|
+
export function compileGoogleWireBody(input: unknown): {
|
|
164
|
+
body: JsonObject;
|
|
165
|
+
restoreToolName: (name: string) => string;
|
|
166
|
+
} {
|
|
167
|
+
const source = isObject(input) ? input : {};
|
|
168
|
+
const names = toolNameCodec(collectToolNames(source));
|
|
169
|
+
const body: JsonObject = {};
|
|
170
|
+
const contents = compileContents(source.contents, names.toWire);
|
|
171
|
+
if (contents) body.contents = contents;
|
|
172
|
+
if (isObject(source.systemInstruction)) body.systemInstruction = source.systemInstruction;
|
|
173
|
+
const tools = compileTools(source.tools, names.toWire);
|
|
174
|
+
if (tools) body.tools = tools;
|
|
175
|
+
const generationConfig = compileGenerationConfig(source.generationConfig);
|
|
176
|
+
if (generationConfig) body.generationConfig = generationConfig;
|
|
177
|
+
const toolConfig = compileToolConfig(source.toolConfig, names.toWire);
|
|
178
|
+
if (toolConfig) body.toolConfig = toolConfig;
|
|
179
|
+
if (typeof source.sessionId === "string" && source.sessionId.length > 0) body.sessionId = source.sessionId;
|
|
180
|
+
return { body, restoreToolName: names.fromWire };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function functionDeclarations(root: JsonObject): JsonObject[] {
|
|
184
|
+
if (!Array.isArray(root.tools)) return [];
|
|
185
|
+
return root.tools.flatMap(rawTool => {
|
|
186
|
+
if (!isObject(rawTool) || !Array.isArray(rawTool.functionDeclarations)) return [];
|
|
187
|
+
return rawTool.functionDeclarations.filter(isObject);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Build a changed request for one known-safe replay of an INVALID_ARGUMENT response. */
|
|
192
|
+
export function repairGoogleInvalidRequestBody(body: string, errorPayload: string): string | undefined {
|
|
193
|
+
const schemaError = /(?:input[_ ]schema|json schema|function[_ ]declarations?|x-mcp-header)/i.test(errorPayload);
|
|
194
|
+
const thinkingError = /thinking[_ ]?(?:config|level)/i.test(errorPayload);
|
|
195
|
+
if (!schemaError && !thinkingError) return undefined;
|
|
196
|
+
let parsed: unknown;
|
|
197
|
+
try {
|
|
198
|
+
parsed = JSON.parse(body) as unknown;
|
|
199
|
+
} catch {
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
if (!isObject(parsed)) return undefined;
|
|
203
|
+
const root = isObject(parsed.request) ? parsed.request : parsed;
|
|
204
|
+
let changed = false;
|
|
205
|
+
|
|
206
|
+
if (thinkingError && isObject(root.generationConfig) && "thinkingConfig" in root.generationConfig) {
|
|
207
|
+
delete root.generationConfig.thinkingConfig;
|
|
208
|
+
if (Object.keys(root.generationConfig).length === 0) delete root.generationConfig;
|
|
209
|
+
changed = true;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (schemaError) {
|
|
213
|
+
const declarations = functionDeclarations(root);
|
|
214
|
+
if (declarations.length > 0) {
|
|
215
|
+
const indexed = /tools(?:\.|\[)(\d+)(?:\])?\.custom\.input_schema/i.exec(errorPayload)?.[1]
|
|
216
|
+
?? /function[_]?declarations(?:\.|\[)(\d+)/i.exec(errorPayload)?.[1];
|
|
217
|
+
const index = indexed === undefined ? -1 : Number.parseInt(indexed, 10);
|
|
218
|
+
const rejected = declarations[index];
|
|
219
|
+
const targets = rejected ? [rejected] : declarations;
|
|
220
|
+
for (const declaration of targets) {
|
|
221
|
+
declaration.parameters = { type: "object", properties: {} };
|
|
222
|
+
delete declaration.parametersJsonSchema;
|
|
223
|
+
}
|
|
224
|
+
changed = true;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return changed ? JSON.stringify(parsed) : undefined;
|
|
228
|
+
}
|
package/src/adapters/google.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { fetchAntigravityWithRetry, fetchVertexWithRetry } from "./google-http";
|
|
|
18
18
|
import { safeAntigravityHttpErrorMessage, safeVertexHttpErrorMessage } from "./google-errors";
|
|
19
19
|
import { isVertexTruncationReason, vertexTruncationErrorMessage } from "./google-truncation";
|
|
20
20
|
import { ANTIGRAVITY_REQUEST_UA, antigravitySessionId, isLikelyRealThoughtSignature, sanitizeAntigravityClaudeSignatures } from "./google-antigravity-wire";
|
|
21
|
-
import {
|
|
21
|
+
import { compileGoogleWireBody } from "./google-wire-compiler";
|
|
22
22
|
import { neutralizeIdentity } from "./identity";
|
|
23
23
|
import { antigravityUsesReplayCache, applyAntigravityReplay, clearAntigravityReplay, observeAntigravityReplay } from "./google-antigravity-replay";
|
|
24
24
|
import { resolveAntigravityEffortWireModel } from "../providers/antigravity-models";
|
|
@@ -179,7 +179,7 @@ function toolsToGeminiFormat(parsed: OcxParsedRequest): unknown[] | undefined {
|
|
|
179
179
|
functionDeclarations: tools.map(t => ({
|
|
180
180
|
name: namespacedToolName(t.namespace, t.name),
|
|
181
181
|
description: t.description,
|
|
182
|
-
parameters:
|
|
182
|
+
parameters: t.parameters,
|
|
183
183
|
})),
|
|
184
184
|
}];
|
|
185
185
|
}
|
|
@@ -199,6 +199,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
199
199
|
// can stash the CCA model/session for parseStream's reasoning-replay observation.
|
|
200
200
|
let antigravityModel: string | undefined;
|
|
201
201
|
let antigravitySession: string | undefined;
|
|
202
|
+
let restoreGoogleToolName = (name: string): string => name;
|
|
202
203
|
return {
|
|
203
204
|
name: "google",
|
|
204
205
|
|
|
@@ -262,23 +263,27 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
262
263
|
}
|
|
263
264
|
// Reasoning continuity: Gemini models re-inject cached thoughtSignatures; Claude-on-Antigravity
|
|
264
265
|
// sanitizes signatures inline (no cache). Both guard against the upstream 400 on bad signatures.
|
|
265
|
-
if (Array.isArray((body as { contents?: unknown[] }).contents)) {
|
|
266
|
-
const contents = (body as { contents: unknown[] }).contents;
|
|
267
|
-
if (antigravityUsesReplayCache(wireModelId)) {
|
|
268
|
-
applyAntigravityReplay(wireModelId, sessionId, contents);
|
|
269
|
-
} else {
|
|
270
|
-
sanitizeAntigravityClaudeSignatures(contents);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
266
|
// The real Antigravity client puts the session id ONLY at `request.sessionId` (camelCase,
|
|
274
267
|
// nested) — matching CLIProxyAPI `generateStableSessionID`. An extra top-level/snake_case
|
|
275
268
|
// spelling is a non-first-party key, so we send the single canonical location.
|
|
276
|
-
const
|
|
269
|
+
const draftRequest: Record<string, unknown> = { ...body, sessionId };
|
|
277
270
|
// Claude-on-Antigravity forces VALIDATED function calling (the real client always sets it).
|
|
278
271
|
if (/claude/i.test(wireModelId)) {
|
|
279
|
-
const existing = (
|
|
272
|
+
const existing = (draftRequest.toolConfig ?? {}) as Record<string, unknown>;
|
|
280
273
|
const fcc = (existing.functionCallingConfig ?? {}) as Record<string, unknown>;
|
|
281
|
-
|
|
274
|
+
draftRequest.toolConfig = { ...existing, functionCallingConfig: { ...fcc, mode: "VALIDATED" } };
|
|
275
|
+
}
|
|
276
|
+
const compiled = compileGoogleWireBody(draftRequest);
|
|
277
|
+
const request = compiled.body;
|
|
278
|
+
restoreGoogleToolName = compiled.restoreToolName;
|
|
279
|
+
// Compile names before replay: signatures are keyed by the exact provider-visible name.
|
|
280
|
+
if (Array.isArray((request as { contents?: unknown[] }).contents)) {
|
|
281
|
+
const contents = (request as { contents: unknown[] }).contents;
|
|
282
|
+
if (antigravityUsesReplayCache(wireModelId)) {
|
|
283
|
+
applyAntigravityReplay(wireModelId, sessionId, contents);
|
|
284
|
+
} else {
|
|
285
|
+
sanitizeAntigravityClaudeSignatures(contents);
|
|
286
|
+
}
|
|
282
287
|
}
|
|
283
288
|
const envelope = {
|
|
284
289
|
model: wireModelId,
|
|
@@ -297,12 +302,14 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
297
302
|
}
|
|
298
303
|
|
|
299
304
|
if (provider.googleMode === "vertex") {
|
|
305
|
+
const compiled = compileGoogleWireBody(body);
|
|
306
|
+
restoreGoogleToolName = compiled.restoreToolName;
|
|
300
307
|
// Vertex AI: project/location endpoint with GCP ADC, or x-goog-api-key fast path.
|
|
301
308
|
const apiKey = resolveVertexApiKey(provider.apiKey);
|
|
302
309
|
if (apiKey) {
|
|
303
310
|
const url = `https://aiplatform.googleapis.com/v1/publishers/google/models/${parsed.modelId}:${method}${streamParam}`;
|
|
304
311
|
headers["x-goog-api-key"] = apiKey;
|
|
305
|
-
return { url, method: "POST", headers, body: JSON.stringify(body) };
|
|
312
|
+
return { url, method: "POST", headers, body: JSON.stringify(compiled.body) };
|
|
306
313
|
}
|
|
307
314
|
const project = provider.project || process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT;
|
|
308
315
|
if (!project) throw new Error("Vertex AI requires a project id (provider.project or GOOGLE_CLOUD_PROJECT/GCLOUD_PROJECT).");
|
|
@@ -312,7 +319,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
312
319
|
const url = `https://${host}/v1/projects/${project}/locations/${location}/publishers/google/models/${parsed.modelId}:${method}${streamParam}`;
|
|
313
320
|
const token = await getVertexAccessToken();
|
|
314
321
|
headers["Authorization"] = `Bearer ${token}`;
|
|
315
|
-
return { url, method: "POST", headers, body: JSON.stringify(body) };
|
|
322
|
+
return { url, method: "POST", headers, body: JSON.stringify(compiled.body) };
|
|
316
323
|
}
|
|
317
324
|
|
|
318
325
|
// ai-studio (default): Generative Language API + x-goog-api-key.
|
|
@@ -321,7 +328,9 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
321
328
|
if (!apiKey) throw new Error("google (AI Studio) requires a non-empty API key");
|
|
322
329
|
headers["x-goog-api-key"] = apiKey;
|
|
323
330
|
|
|
324
|
-
|
|
331
|
+
const compiled = compileGoogleWireBody(body);
|
|
332
|
+
restoreGoogleToolName = compiled.restoreToolName;
|
|
333
|
+
return { url, method: "POST", headers, body: JSON.stringify(compiled.body) };
|
|
325
334
|
},
|
|
326
335
|
|
|
327
336
|
async *parseStream(response: Response): AsyncGenerator<AdapterEvent> {
|
|
@@ -403,7 +412,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
403
412
|
if (part.functionCall) {
|
|
404
413
|
const id = `call_${crypto.randomUUID().slice(0, 8)}`;
|
|
405
414
|
toolCallsStarted++;
|
|
406
|
-
yield { type: "tool_call_start", id, name: part.functionCall.name };
|
|
415
|
+
yield { type: "tool_call_start", id, name: restoreGoogleToolName(part.functionCall.name) };
|
|
407
416
|
yield { type: "tool_call_delta", arguments: JSON.stringify(part.functionCall.args ?? {}) };
|
|
408
417
|
yield { type: "tool_call_end" };
|
|
409
418
|
}
|
|
@@ -456,7 +465,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
456
465
|
if (part.functionCall) {
|
|
457
466
|
const id = `call_${crypto.randomUUID().slice(0, 8)}`;
|
|
458
467
|
toolCallsStarted++;
|
|
459
|
-
events.push({ type: "tool_call_start", id, name: part.functionCall.name });
|
|
468
|
+
events.push({ type: "tool_call_start", id, name: restoreGoogleToolName(part.functionCall.name) });
|
|
460
469
|
events.push({ type: "tool_call_delta", arguments: JSON.stringify(part.functionCall.args ?? {}) });
|
|
461
470
|
events.push({ type: "tool_call_end" });
|
|
462
471
|
}
|
package/src/adapters/kiro.ts
CHANGED
|
@@ -846,6 +846,14 @@ async function* parseKiroAttempt(
|
|
|
846
846
|
...(contextWindow ? { configuredContextWindow: contextWindow } : {}),
|
|
847
847
|
});
|
|
848
848
|
}
|
|
849
|
+
debugProviderDiagnostic("kiro", "attempt_complete", {
|
|
850
|
+
mode,
|
|
851
|
+
sawText,
|
|
852
|
+
sawReasoning,
|
|
853
|
+
sawRealTool,
|
|
854
|
+
completionCalls,
|
|
855
|
+
assistantChars: assistantText.length,
|
|
856
|
+
});
|
|
849
857
|
|
|
850
858
|
if (mode === "text_fallback") {
|
|
851
859
|
if (completionAnswer !== undefined) {
|
|
@@ -909,7 +917,23 @@ async function* parseKiroAttempt(
|
|
|
909
917
|
terminal: { type: "done", usage: finalUsage, endTurn: false, ...(finalProviderState ? { providerState: finalProviderState } : {}) },
|
|
910
918
|
};
|
|
911
919
|
}
|
|
912
|
-
|
|
920
|
+
// A clean Smithy EOF after ordinary assistant text is a complete answer even when the
|
|
921
|
+
// private completion tool was advertised. Replaying that answer through a second Kiro
|
|
922
|
+
// request adds latency and can hang an otherwise-finished Codex turn if the retry stalls.
|
|
923
|
+
// Reasoning-only output still needs the bounded fallback because it has no user-facing text.
|
|
924
|
+
if (mode === "required" && sawText) {
|
|
925
|
+
return {
|
|
926
|
+
assistantText,
|
|
927
|
+
sawReasoning,
|
|
928
|
+
terminal: {
|
|
929
|
+
type: "done",
|
|
930
|
+
usage: finalUsage,
|
|
931
|
+
endTurn: true,
|
|
932
|
+
...(finalProviderState ? { providerState: finalProviderState } : {}),
|
|
933
|
+
},
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
if (mode === "required" && sawReasoning) {
|
|
913
937
|
return { assistantText, sawReasoning, needsFallback: true, usage: finalUsage, providerState: finalProviderState };
|
|
914
938
|
}
|
|
915
939
|
if (!sawText && !sawReasoning) {
|