@yansigit/opencodex 2.32.0 → 2.33.0
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 +2 -2
- package/gui/dist/assets/index-DKLr4LTE.js +102 -0
- package/gui/dist/assets/index-DrSQdTRd.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +6 -5
- package/src/adapters/anthropic.ts +20 -6
- package/src/adapters/azure.ts +20 -4
- package/src/adapters/base.ts +3 -1
- package/src/adapters/command-code.ts +40 -7
- package/src/adapters/cursor/live-transport.ts +1 -1
- package/src/adapters/cursor/protobuf-events.ts +158 -7
- package/src/adapters/cursor/protobuf-request.ts +33 -15
- package/src/adapters/cursor/request-builder.ts +4 -3
- package/src/adapters/cursor/tool-definitions.ts +27 -1
- package/src/adapters/cursor/types.ts +4 -3
- package/src/adapters/cursor.ts +9 -0
- package/src/adapters/google-antigravity-replay.ts +2 -2
- package/src/adapters/google-antigravity-wire.ts +7 -0
- package/src/adapters/google-errors.ts +6 -2
- package/src/adapters/google-http.ts +30 -7
- package/src/adapters/google-truncation.ts +5 -0
- package/src/adapters/google-wire-compiler.ts +38 -6
- package/src/adapters/google.ts +148 -26
- package/src/adapters/kiro-tools.ts +20 -9
- package/src/adapters/openai-chat.ts +9 -0
- package/src/adapters/openai-responses.ts +1 -1
- package/src/bridge.ts +112 -9
- package/src/claude/context-windows.ts +16 -9
- package/src/cli/doctor.ts +2 -2
- package/src/cli/index.ts +9 -2
- package/src/cli/provider.ts +6 -0
- package/src/cli/status.ts +23 -0
- package/src/codex/auth-api.ts +4 -2
- package/src/codex/autostart-health.ts +16 -0
- package/src/codex/catalog/aggregation.ts +12 -12
- package/src/codex/catalog/effort.ts +18 -3
- package/src/codex/catalog/metadata.ts +27 -1
- package/src/codex/catalog/model-metadata.ts +39 -12
- package/src/codex/catalog/parsing.ts +38 -27
- package/src/codex/catalog/provider-fetch.ts +198 -133
- package/src/codex/catalog/sync.ts +1 -1
- package/src/codex/convergence.ts +5 -0
- package/src/codex/shim.ts +56 -3
- package/src/config/provider-validation.ts +37 -0
- package/src/config.ts +78 -2
- package/src/generated/compatibility-version.json +120 -96
- package/src/images/loop.ts +37 -6
- package/src/lib/azure-identity.ts +154 -0
- package/src/lib/debug.ts +42 -0
- package/src/lib/errors.ts +14 -0
- package/src/lib/provider-outbound.ts +45 -33
- package/src/lib/provider-tls-profile.ts +309 -0
- package/src/lib/proxy-env.ts +49 -0
- package/src/lib/redact.ts +10 -1
- package/src/oauth/antigravity-routing.ts +282 -236
- package/src/oauth/callback-server.ts +22 -2
- package/src/oauth/command-code.ts +5 -16
- package/src/oauth/google-antigravity.ts +42 -5
- package/src/oauth/index.ts +15 -3
- package/src/oauth/kimi.ts +9 -1
- package/src/oauth/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +6 -0
- package/src/providers/antigravity-quota.ts +3 -1
- package/src/providers/api-keys.ts +2 -1
- package/src/providers/auto-compact-budget.ts +65 -0
- package/src/providers/derive.ts +4 -0
- package/src/providers/key-failover.ts +5 -1
- package/src/providers/openai-tiers.ts +5 -0
- package/src/providers/provider-id-rewrite.ts +1 -0
- package/src/providers/quota.ts +59 -13
- package/src/providers/registry.ts +3 -1
- package/src/providers/request-pacing.ts +33 -6
- package/src/providers/xai-transport.ts +21 -0
- package/src/responses/google-provider-options.ts +36 -0
- package/src/responses/namespace-tool-compat.ts +84 -4
- package/src/responses/parser.ts +11 -0
- package/src/responses/provider-opaque-metadata.ts +3 -3
- package/src/responses/schema.ts +37 -0
- package/src/responses/state.ts +94 -4
- package/src/router.ts +8 -2
- package/src/server/auth-cors.ts +28 -0
- package/src/server/images.ts +19 -35
- package/src/server/management/agent-settings-routes.ts +205 -15
- package/src/server/management/combo-routes.ts +6 -0
- package/src/server/management/config-routes.ts +31 -5
- package/src/server/management/model-rows.ts +4 -0
- package/src/server/management/oauth-account-routes.ts +25 -4
- package/src/server/management/provider-routes.ts +113 -15
- package/src/server/management/routing-profile-routes.ts +3 -0
- package/src/server/request-log.ts +21 -0
- package/src/server/responses/agent-task-recovery.ts +1 -1
- package/src/server/responses/compact.ts +30 -1
- package/src/server/responses/core.ts +359 -153
- package/src/server/responses/empty-completion-guard.ts +35 -6
- package/src/server/responses/fetch-helpers.ts +18 -5
- package/src/server/responses/v2-native-parent-override.ts +59 -0
- package/src/server/responses/ws-upstream.ts +75 -2
- package/src/server/responses-undeclared-tool-guard.ts +90 -8
- package/src/service.ts +1 -1
- package/src/types/config.ts +16 -1
- package/src/types/provider.ts +16 -0
- package/src/types/request.ts +28 -0
- package/src/types/tools.ts +27 -0
- package/src/types.ts +6 -0
- package/src/web-search/gemini-executor.ts +6 -4
- package/src/web-search/loop.ts +42 -6
- package/gui/dist/assets/index-BG43zwVe.js +0 -102
- package/gui/dist/assets/index-CiSI-jrP.css +0 -1
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from "./google-errors";
|
|
8
8
|
import { isGoogleMixedBuiltinToolError, repairGoogleInvalidRequestBody, stripGoogleBuiltinToolsFromWireBody } from "./google-wire-compiler";
|
|
9
9
|
import { normalizeUpstreamHttpErrorResponse, readDisplaySafeErrorPayloadText } from "./upstream-http-error";
|
|
10
|
-
import { recordAntigravityCooldown } from "../oauth/antigravity-routing";
|
|
10
|
+
import { recordAntigravityCooldown, recordAntigravitySyntheticFailure } from "../oauth/antigravity-routing";
|
|
11
11
|
import {
|
|
12
12
|
antigravityHostCandidates,
|
|
13
13
|
canonicalAntigravityHttpsHost,
|
|
@@ -178,6 +178,7 @@ function responseWithBufferedBody(
|
|
|
178
178
|
async function prepareCcaSseResponse(
|
|
179
179
|
response: Response,
|
|
180
180
|
fetchPeer: (() => Promise<Response>) | undefined,
|
|
181
|
+
accountId?: string,
|
|
181
182
|
): Promise<Response> {
|
|
182
183
|
if (!response.body) return fetchPeer ? fetchPeer() : response;
|
|
183
184
|
const reader = response.body.getReader();
|
|
@@ -209,6 +210,13 @@ async function prepareCcaSseResponse(
|
|
|
209
210
|
return failoverOrPassthrough();
|
|
210
211
|
}
|
|
211
212
|
if (probe === "quota_exhausted" || probe === "geo_blocked") {
|
|
213
|
+
if (accountId) {
|
|
214
|
+
recordAntigravitySyntheticFailure(accountId, {
|
|
215
|
+
code: probe === "quota_exhausted" ? 429 : 403,
|
|
216
|
+
status: probe === "quota_exhausted" ? "RESOURCE_EXHAUSTED" : "PERMISSION_DENIED",
|
|
217
|
+
message: probe === "quota_exhausted" ? "quota exceeded" : "user location is not supported",
|
|
218
|
+
});
|
|
219
|
+
}
|
|
212
220
|
const status = probe === "quota_exhausted" ? 429 : 403;
|
|
213
221
|
return passthrough(undefined, status);
|
|
214
222
|
}
|
|
@@ -237,6 +245,13 @@ async function prepareCcaSseResponse(
|
|
|
237
245
|
return failoverOrPassthrough();
|
|
238
246
|
}
|
|
239
247
|
if (probe === "quota_exhausted" || probe === "geo_blocked") {
|
|
248
|
+
if (accountId) {
|
|
249
|
+
recordAntigravitySyntheticFailure(accountId, {
|
|
250
|
+
code: probe === "quota_exhausted" ? 429 : 403,
|
|
251
|
+
status: probe === "quota_exhausted" ? "RESOURCE_EXHAUSTED" : "PERMISSION_DENIED",
|
|
252
|
+
message: probe === "quota_exhausted" ? "quota exceeded" : "user location is not supported",
|
|
253
|
+
});
|
|
254
|
+
}
|
|
240
255
|
const status = probe === "quota_exhausted" ? 429 : 403;
|
|
241
256
|
return passthrough(overflow, status);
|
|
242
257
|
}
|
|
@@ -262,6 +277,8 @@ function isUnavailableResponse(response: Response): boolean {
|
|
|
262
277
|
export interface GoogleRetryOptions {
|
|
263
278
|
/** Repair-and-replay structurally invalid 400 bodies (Vertex/Antigravity behavior). */
|
|
264
279
|
repairInvalid400?: boolean;
|
|
280
|
+
/** Let the Responses layer own Antigravity's single same-account 429 replay. */
|
|
281
|
+
retry429?: boolean;
|
|
265
282
|
}
|
|
266
283
|
|
|
267
284
|
async function normalizeFinalGoogleError(label: string, res: Response, signal?: AbortSignal): Promise<Response> {
|
|
@@ -291,13 +308,15 @@ async function recordAntigravityHttpCooldown(
|
|
|
291
308
|
if (response.status === 429) {
|
|
292
309
|
recordAntigravityCooldown(
|
|
293
310
|
accountId,
|
|
294
|
-
|
|
295
|
-
|
|
311
|
+
response.headers.get("retry-after"),
|
|
312
|
+
Date.now(),
|
|
313
|
+
isQuotaExhaustedBody(payloadText) ? "quota" : "rate-limit",
|
|
314
|
+
"synthetic",
|
|
296
315
|
);
|
|
297
316
|
return true;
|
|
298
317
|
}
|
|
299
|
-
if (isAntigravityGeoBlockedBody(payloadText)) {
|
|
300
|
-
recordAntigravityCooldown(accountId, "
|
|
318
|
+
if (response.status === 403 || isAntigravityGeoBlockedBody(payloadText)) {
|
|
319
|
+
recordAntigravityCooldown(accountId, response.headers.get("retry-after"), Date.now(), "geoblock", "synthetic");
|
|
301
320
|
return true;
|
|
302
321
|
}
|
|
303
322
|
return false;
|
|
@@ -318,6 +337,7 @@ async function fetchGoogleWithRetryInternal(
|
|
|
318
337
|
opts: GoogleRetryOptions = {},
|
|
319
338
|
): Promise<Response> {
|
|
320
339
|
const repairInvalid400 = opts.repairInvalid400 ?? true;
|
|
340
|
+
const retry429 = opts.retry429 ?? true;
|
|
321
341
|
const timeoutMs = ctx.timeoutMs ?? 200_000;
|
|
322
342
|
const executor = ctx.executor ?? globalThis.fetch;
|
|
323
343
|
let activeRequest = request;
|
|
@@ -369,7 +389,7 @@ async function fetchGoogleWithRetryInternal(
|
|
|
369
389
|
opts,
|
|
370
390
|
)
|
|
371
391
|
: undefined;
|
|
372
|
-
return prepareCcaSseResponse(res, fetchPeer);
|
|
392
|
+
return prepareCcaSseResponse(res, fetchPeer, ctx.accountId);
|
|
373
393
|
}
|
|
374
394
|
if (res.status === 400 && repairInvalid400 && !compatibilityReplayUsed) {
|
|
375
395
|
let payloadText = "";
|
|
@@ -397,6 +417,9 @@ async function fetchGoogleWithRetryInternal(
|
|
|
397
417
|
continue;
|
|
398
418
|
}
|
|
399
419
|
}
|
|
420
|
+
if (res.status === 429 && !retry429) {
|
|
421
|
+
return ctx.returnRawErrors ? res : normalizeFinalGoogleError(label, res, ctx.abortSignal);
|
|
422
|
+
}
|
|
400
423
|
if (!retryableGoogleStatus(res.status) || attempt === GOOGLE_RETRY_ATTEMPTS - 1) {
|
|
401
424
|
return ctx.returnRawErrors ? res : normalizeFinalGoogleError(label, res, ctx.abortSignal);
|
|
402
425
|
}
|
|
@@ -466,5 +489,5 @@ export function fetchVertexWithRetry(request: AdapterRequest, ctx: AdapterFetchC
|
|
|
466
489
|
|
|
467
490
|
/** Antigravity (Cloud Code Assist) retry wrapper. */
|
|
468
491
|
export function fetchAntigravityWithRetry(request: AdapterRequest, ctx: AdapterFetchContext = {}): Promise<Response> {
|
|
469
|
-
return fetchGoogleWithRetry("Antigravity", request, ctx);
|
|
492
|
+
return fetchGoogleWithRetry("Antigravity", request, ctx, { retry429: false });
|
|
470
493
|
}
|
|
@@ -12,6 +12,11 @@ export function vertexTruncationErrorMessage(reason?: string): string {
|
|
|
12
12
|
return `Vertex AI response truncated upstream before the turn completed${suffix}`;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
export function googleTruncationErrorMessage(reason?: string): string {
|
|
16
|
+
const suffix = reason ? ` (${redactSecretString(reason).slice(0, 160)})` : "";
|
|
17
|
+
return `Google AI Studio response truncated upstream before the turn completed${suffix}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
15
20
|
/**
|
|
16
21
|
* Whether a finished turn must fail closed. A truncation reason arriving mid tool call always
|
|
17
22
|
* does. MALFORMED_FUNCTION_CALL fails closed even with zero started calls: the malformed call
|
|
@@ -5,6 +5,15 @@ type JsonObject = Record<string, unknown>;
|
|
|
5
5
|
|
|
6
6
|
const GOOGLE_TOOL_NAME = /^[A-Za-z_][A-Za-z0-9_-]{0,63}$/;
|
|
7
7
|
const GOOGLE_THINKING_LEVELS = new Set(["minimal", "low", "medium", "high"]);
|
|
8
|
+
const GOOGLE_SAFETY_CATEGORIES = new Set([
|
|
9
|
+
"HARM_CATEGORY_HATE_SPEECH", "HARM_CATEGORY_SEXUALLY_EXPLICIT", "HARM_CATEGORY_DANGEROUS_CONTENT",
|
|
10
|
+
"HARM_CATEGORY_HARASSMENT", "HARM_CATEGORY_CIVIC_INTEGRITY", "HARM_CATEGORY_JAILBREAK",
|
|
11
|
+
]);
|
|
12
|
+
const GOOGLE_SAFETY_THRESHOLDS = new Set([
|
|
13
|
+
"HARM_BLOCK_THRESHOLD_UNSPECIFIED", "BLOCK_LOW_AND_ABOVE", "BLOCK_MEDIUM_AND_ABOVE",
|
|
14
|
+
"BLOCK_ONLY_HIGH", "BLOCK_NONE", "OFF",
|
|
15
|
+
]);
|
|
16
|
+
const GOOGLE_CACHED_CONTENT = /^(?:cachedContents\/[^/?#\s]+|projects\/[^/?#\s]+\/locations\/[^/?#\s]+\/cachedContents\/[^/?#\s]+)$/;
|
|
8
17
|
|
|
9
18
|
function isObject(value: unknown): value is JsonObject {
|
|
10
19
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
@@ -158,12 +167,18 @@ function compileGenerationConfig(value: unknown): JsonObject | undefined {
|
|
|
158
167
|
))].slice(0, 5);
|
|
159
168
|
if (stopSequences.length > 0) out.stopSequences = stopSequences;
|
|
160
169
|
}
|
|
161
|
-
if (isObject(value.thinkingConfig)
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if (
|
|
170
|
+
if (isObject(value.thinkingConfig)) {
|
|
171
|
+
const config: JsonObject = {};
|
|
172
|
+
if (typeof value.thinkingConfig.thinkingBudget === "number"
|
|
173
|
+
&& Number.isSafeInteger(value.thinkingConfig.thinkingBudget)
|
|
174
|
+
&& value.thinkingConfig.thinkingBudget >= -1) config.thinkingBudget = value.thinkingConfig.thinkingBudget;
|
|
175
|
+
if (typeof value.thinkingConfig.includeThoughts === "boolean") config.includeThoughts = value.thinkingConfig.includeThoughts;
|
|
176
|
+
if (typeof value.thinkingConfig.thinkingLevel === "string") {
|
|
177
|
+
const raw = value.thinkingConfig.thinkingLevel.toLowerCase();
|
|
178
|
+
const thinkingLevel = GOOGLE_THINKING_LEVELS.has(raw) ? raw : (["xhigh", "max", "ultra"].includes(raw) ? "high" : undefined);
|
|
179
|
+
if (thinkingLevel && config.thinkingBudget === undefined) config.thinkingLevel = thinkingLevel;
|
|
180
|
+
}
|
|
181
|
+
if (Object.keys(config).length > 0) out.thinkingConfig = config;
|
|
167
182
|
}
|
|
168
183
|
if (Array.isArray(value.responseModalities)) {
|
|
169
184
|
const valid = value.responseModalities.filter((m): m is string => typeof m === "string" && ["TEXT", "IMAGE", "AUDIO"].includes(m));
|
|
@@ -180,6 +195,20 @@ function compileGenerationConfig(value: unknown): JsonObject | undefined {
|
|
|
180
195
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
181
196
|
}
|
|
182
197
|
|
|
198
|
+
function compileSafetySettings(value: unknown): unknown[] | undefined {
|
|
199
|
+
if (!Array.isArray(value) || value.length > 16) return undefined;
|
|
200
|
+
const seen = new Set<string>();
|
|
201
|
+
const out: JsonObject[] = [];
|
|
202
|
+
for (const setting of value) {
|
|
203
|
+
if (!isObject(setting) || typeof setting.category !== "string" || !GOOGLE_SAFETY_CATEGORIES.has(setting.category)
|
|
204
|
+
|| typeof setting.threshold !== "string" || !GOOGLE_SAFETY_THRESHOLDS.has(setting.threshold)
|
|
205
|
+
|| seen.has(setting.category)) return undefined;
|
|
206
|
+
seen.add(setting.category);
|
|
207
|
+
out.push({ category: setting.category, threshold: setting.threshold });
|
|
208
|
+
}
|
|
209
|
+
return out.length > 0 ? out : undefined;
|
|
210
|
+
}
|
|
211
|
+
|
|
183
212
|
function compileToolConfig(value: unknown, toWireName: (name: string) => string): JsonObject | undefined {
|
|
184
213
|
if (!isObject(value) || !isObject(value.functionCallingConfig)) return undefined;
|
|
185
214
|
const raw = value.functionCallingConfig;
|
|
@@ -216,6 +245,9 @@ export function compileGoogleWireBody(input: unknown): {
|
|
|
216
245
|
if (generationConfig) body.generationConfig = generationConfig;
|
|
217
246
|
const toolConfig = compileToolConfig(source.toolConfig, names.toWire);
|
|
218
247
|
if (toolConfig) body.toolConfig = toolConfig;
|
|
248
|
+
const safetySettings = compileSafetySettings(source.safetySettings);
|
|
249
|
+
if (safetySettings) body.safetySettings = safetySettings;
|
|
250
|
+
if (typeof source.cachedContent === "string" && GOOGLE_CACHED_CONTENT.test(source.cachedContent)) body.cachedContent = source.cachedContent;
|
|
219
251
|
if (typeof source.sessionId === "string" && source.sessionId.length > 0) body.sessionId = source.sessionId;
|
|
220
252
|
return { body, restoreToolName: names.fromWire };
|
|
221
253
|
}
|
package/src/adapters/google.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { AdapterFetchContext, AdapterRequest, ProviderAdapter } from "./base";
|
|
1
|
+
import type { AdapterFetchContext, AdapterRequest, IncomingMeta, ProviderAdapter } from "./base";
|
|
2
2
|
import { debugDroppedFrame } from "../lib/debug";
|
|
3
3
|
import { createToolCallIdAllocator } from "./tool-call-id";
|
|
4
4
|
import { createImageBudget, materializeInlineImage, MAX_ENCODED_BYTES_PER_IMAGE, artifactHttpUrl } from "../images/artifacts";
|
|
5
|
+
import { OcxRequestValidationError } from "../lib/errors";
|
|
5
6
|
import type {
|
|
6
7
|
AdapterEvent,
|
|
7
8
|
OcxAssistantMessage,
|
|
@@ -19,8 +20,9 @@ import { contentPartsToText, parseDataUrl } from "./image";
|
|
|
19
20
|
import { getVertexAccessToken } from "../lib/gcp-adc";
|
|
20
21
|
import { fetchAntigravityWithRetry, fetchVertexWithRetry } from "./google-http";
|
|
21
22
|
import { safeAntigravityHttpErrorMessage, safeVertexHttpErrorMessage } from "./google-errors";
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
23
|
+
import { sanitizeUpstreamErrorText } from "./upstream-http-error";
|
|
24
|
+
import { googleTruncationErrorMessage, isVertexTruncatedTurn, vertexTruncationErrorMessage } from "./google-truncation";
|
|
25
|
+
import { ANTIGRAVITY_REQUEST_UA, ANTIGRAVITY_SIGNATURE_BYPASS_SENTINEL, antigravitySessionId, isLikelyRealThoughtSignature, sanitizeAntigravityClaudeSignatures } from "./google-antigravity-wire";
|
|
24
26
|
import { repairGoogleToolPairs, stripTrailingClaudePrefill } from "./google-antigravity-tools";
|
|
25
27
|
import { canonicalAntigravityHttpsHost, isAntigravityHttpsHost } from "./google-antigravity-hosts";
|
|
26
28
|
import { compileGoogleWireBody } from "./google-wire-compiler";
|
|
@@ -45,6 +47,16 @@ import {
|
|
|
45
47
|
} from "../lib/translator-budget";
|
|
46
48
|
import { buildNonOpenAIToolCatalogNudgeForTools } from "./tool-catalog-nudge";
|
|
47
49
|
import { configuredReasoningEfforts, mapReasoningEffort } from "../reasoning-effort";
|
|
50
|
+
import { normalizeAntigravityProviderError } from "../oauth/antigravity-routing";
|
|
51
|
+
|
|
52
|
+
const INLINE_ERROR_URL_USERINFO = /https?:\/\/[^\s"'<>]*@/gi;
|
|
53
|
+
|
|
54
|
+
function safeAntigravityInlineErrorMessage(value: unknown): string | undefined {
|
|
55
|
+
if (typeof value !== "string") return undefined;
|
|
56
|
+
return sanitizeUpstreamErrorText(value)
|
|
57
|
+
.replace(INLINE_ERROR_URL_USERINFO, "[REDACTED_URL]")
|
|
58
|
+
.slice(0, 500);
|
|
59
|
+
}
|
|
48
60
|
|
|
49
61
|
// Google-family models (Gemini/Vertex/Antigravity) tend to emit long running commentary between
|
|
50
62
|
// tool calls. This steers them to keep the BETWEEN-STEP text to one line and reason internally
|
|
@@ -139,6 +151,19 @@ const GEMINI_EMPTY_PLACEHOLDER = "(empty)";
|
|
|
139
151
|
const GEMINI_EMPTY_TOOL_OUTPUT_PLACEHOLDER = "(empty tool output)";
|
|
140
152
|
const GEMINI_MISSING_TOOL_RESULT = "[missing tool_result for this tool_use in history]";
|
|
141
153
|
|
|
154
|
+
function appendGeminiContent(
|
|
155
|
+
contents: unknown[],
|
|
156
|
+
next: { role: string; parts: unknown[] },
|
|
157
|
+
mergeAdjacentUsers = true,
|
|
158
|
+
): void {
|
|
159
|
+
const previous = contents.at(-1) as { role?: unknown; parts?: unknown[] } | undefined;
|
|
160
|
+
if (mergeAdjacentUsers && previous?.role === "user" && next.role === "user" && Array.isArray(previous.parts)) {
|
|
161
|
+
previous.parts.push(...next.parts);
|
|
162
|
+
} else {
|
|
163
|
+
contents.push(next);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
142
167
|
/** A Gemini text part, or undefined when the value cannot form a valid non-empty text block. */
|
|
143
168
|
function geminiTextPart(text: unknown): { text: string } | undefined {
|
|
144
169
|
return typeof text === "string" && text.length > 0 ? { text } : undefined;
|
|
@@ -208,6 +233,11 @@ function messagesToGeminiFormat(
|
|
|
208
233
|
const systemInstruction = { parts: [{ text: systemText }] };
|
|
209
234
|
|
|
210
235
|
const contents: unknown[] = [];
|
|
236
|
+
let userMergeBarrier = false;
|
|
237
|
+
const appendContent = (next: { role: string; parts: unknown[] }): void => {
|
|
238
|
+
appendGeminiContent(contents, next, !userMergeBarrier);
|
|
239
|
+
userMergeBarrier = false;
|
|
240
|
+
};
|
|
211
241
|
const messages = repairGoogleToolPairs(parsed.context.messages, { dropUnmatchedCalls: repairToolPairs });
|
|
212
242
|
|
|
213
243
|
const callIds = createToolCallIdAllocator();
|
|
@@ -226,7 +256,7 @@ function messagesToGeminiFormat(
|
|
|
226
256
|
case "user":
|
|
227
257
|
case "developer": {
|
|
228
258
|
if (typeof msg.content === "string") {
|
|
229
|
-
|
|
259
|
+
appendContent({ role: "user", parts: [{ text: msg.content || GEMINI_EMPTY_PLACEHOLDER }] });
|
|
230
260
|
} else {
|
|
231
261
|
const parts: unknown[] = [];
|
|
232
262
|
for (const p of msg.content as OcxContentPart[]) {
|
|
@@ -241,7 +271,7 @@ function messagesToGeminiFormat(
|
|
|
241
271
|
const textPart = geminiTextPart(p.text);
|
|
242
272
|
if (textPart) parts.push(textPart);
|
|
243
273
|
}
|
|
244
|
-
|
|
274
|
+
appendContent({ role: "user", parts: parts.length > 0 ? parts : [{ text: GEMINI_EMPTY_PLACEHOLDER }] });
|
|
245
275
|
}
|
|
246
276
|
break;
|
|
247
277
|
}
|
|
@@ -291,8 +321,11 @@ function messagesToGeminiFormat(
|
|
|
291
321
|
// A turn with nothing Gemini can represent (e.g. thinking-only) would serialize as
|
|
292
322
|
// `parts: []`, which the Anthropic translation rejects. Skip it, as the Anthropic
|
|
293
323
|
// adapter does for its own empty assistant content.
|
|
294
|
-
if (parts.length === 0)
|
|
295
|
-
|
|
324
|
+
if (parts.length === 0) {
|
|
325
|
+
userMergeBarrier = true;
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
appendContent({ role: "model", parts });
|
|
296
329
|
if (toolCalls.length > 0) {
|
|
297
330
|
// Gemini/Claude-on-Antigravity requires one adjacent response batch for the whole
|
|
298
331
|
// function-call turn. Replayed histories can be interrupted, reversed, duplicated, or
|
|
@@ -321,7 +354,7 @@ function messagesToGeminiFormat(
|
|
|
321
354
|
for (const orphan of orphanResults) {
|
|
322
355
|
responseParts.push(...geminiOrphanToolResultParts(orphan));
|
|
323
356
|
}
|
|
324
|
-
|
|
357
|
+
appendContent({ role: "user", parts: responseParts });
|
|
325
358
|
i = j - 1;
|
|
326
359
|
}
|
|
327
360
|
break;
|
|
@@ -332,7 +365,7 @@ function messagesToGeminiFormat(
|
|
|
332
365
|
// batch never reach here (the assistant branch consumes them). Standalone or
|
|
333
366
|
// barrier-delayed results still have to stay visible — especially image-bearing
|
|
334
367
|
// screenshots — as explicit user text rather than vanishing or 400ing CCA.
|
|
335
|
-
|
|
368
|
+
appendContent({ role: "user", parts: geminiOrphanToolResultParts(msg as OcxToolResultMessage) });
|
|
336
369
|
break;
|
|
337
370
|
}
|
|
338
371
|
}
|
|
@@ -395,6 +428,13 @@ function usageFromGemini(usage: Record<string, number> | undefined): OcxUsage |
|
|
|
395
428
|
};
|
|
396
429
|
}
|
|
397
430
|
|
|
431
|
+
function googlePromptFeedbackError(root: Record<string, unknown>): Extract<AdapterEvent, { type: "error" }> | undefined {
|
|
432
|
+
const feedback = root.promptFeedback;
|
|
433
|
+
if (!isGoogleRecord(feedback) || typeof feedback.blockReason !== "string" || !feedback.blockReason.trim()) return undefined;
|
|
434
|
+
const reason = sanitizeUpstreamErrorText(feedback.blockReason).slice(0, 160);
|
|
435
|
+
return { type: "error", message: `google response blocked by prompt feedback: ${reason}` };
|
|
436
|
+
}
|
|
437
|
+
|
|
398
438
|
/**
|
|
399
439
|
* Cap on the buffered non-streaming response body (100 MiB), matching
|
|
400
440
|
* IMAGES_RESPONSE_MAX_BYTES in src/server/images.ts. Enforced by streaming the
|
|
@@ -672,6 +712,7 @@ function invalidGoogleShapeEvent(
|
|
|
672
712
|
export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapter {
|
|
673
713
|
// Per-request closure: resolveAdapter builds a fresh adapter per request (server.ts), so buildRequest
|
|
674
714
|
// can stash the CCA model/session for parseStream's reasoning-replay observation.
|
|
715
|
+
let observeProviderError: IncomingMeta["onProviderError"];
|
|
675
716
|
let antigravityModel: string | undefined;
|
|
676
717
|
let antigravitySession: string | undefined;
|
|
677
718
|
// Vertex returns the same opaque Gemini thought signatures as CCA, but its replay namespace
|
|
@@ -681,8 +722,23 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
681
722
|
let vertexReplaySession: string | undefined;
|
|
682
723
|
let restoreGoogleToolName = (name: string): string => name;
|
|
683
724
|
const emitInTurnGroundingSourcesQueue: boolean[] = [];
|
|
725
|
+
const truncationErrorMessage = provider.googleMode === "vertex" || provider.googleMode === "cloud-code-assist"
|
|
726
|
+
? vertexTruncationErrorMessage
|
|
727
|
+
: googleTruncationErrorMessage;
|
|
684
728
|
return {
|
|
685
729
|
name: "google",
|
|
730
|
+
validateRequest(parsed: OcxParsedRequest) {
|
|
731
|
+
if (provider.googleMode === "cloud-code-assist" && parsed.options.providerOptions?.google) {
|
|
732
|
+
throw new OcxRequestValidationError("provider_options.google is not supported on Google Cloud Code Assist routes");
|
|
733
|
+
}
|
|
734
|
+
const googleOptions = parsed.options.providerOptions?.google;
|
|
735
|
+
if (isImageCapableModel(parsed.modelId)
|
|
736
|
+
&& (googleOptions?.thinkingBudget !== undefined || googleOptions?.includeThoughts !== undefined)) {
|
|
737
|
+
throw new OcxRequestValidationError(
|
|
738
|
+
"provider_options.google thinking_budget and include_thoughts are not supported for image-capable Gemini models",
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
},
|
|
686
742
|
|
|
687
743
|
// Vertex + Antigravity get Kiro-style retry/timeout + classified, redacted errors.
|
|
688
744
|
// Direct AI-Studio uses the canonical server transport (fetchWithTransientRetry), which
|
|
@@ -697,7 +753,8 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
697
753
|
}
|
|
698
754
|
: {}),
|
|
699
755
|
|
|
700
|
-
async buildRequest(parsed: OcxParsedRequest) {
|
|
756
|
+
async buildRequest(parsed: OcxParsedRequest, options?: IncomingMeta) {
|
|
757
|
+
observeProviderError = options?.onProviderError;
|
|
701
758
|
const routedModelId = provider.googleMode === "cloud-code-assist"
|
|
702
759
|
? resolveAntigravityEffortWireModel(
|
|
703
760
|
parsed.modelId,
|
|
@@ -723,6 +780,9 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
723
780
|
// catalog is a guaranteed upstream 400.
|
|
724
781
|
const toolConfig = tools ? toolChoiceToGeminiToolConfig(parsed) : undefined;
|
|
725
782
|
if (toolConfig) body.toolConfig = toolConfig;
|
|
783
|
+
const googleOptions = parsed.options.providerOptions?.google;
|
|
784
|
+
if (googleOptions?.safetySettings) body.safetySettings = googleOptions.safetySettings;
|
|
785
|
+
if (googleOptions?.cachedContent) body.cachedContent = googleOptions.cachedContent;
|
|
726
786
|
|
|
727
787
|
const generationConfig: Record<string, unknown> = {};
|
|
728
788
|
if (parsed.options.maxOutputTokens) generationConfig.maxOutputTokens = parsed.options.maxOutputTokens;
|
|
@@ -744,7 +804,13 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
744
804
|
const thinkingLevel = thinkingEligible
|
|
745
805
|
? mapReasoningEffort(provider, parsed.modelId, parsed.options.reasoning)
|
|
746
806
|
: undefined;
|
|
747
|
-
if (
|
|
807
|
+
if (!isImageCapableModel(parsed.modelId)) {
|
|
808
|
+
const thinkingConfig: Record<string, unknown> = {};
|
|
809
|
+
if (googleOptions?.thinkingBudget !== undefined) thinkingConfig.thinkingBudget = googleOptions.thinkingBudget;
|
|
810
|
+
else if (thinkingLevel) thinkingConfig.thinkingLevel = thinkingLevel;
|
|
811
|
+
if (googleOptions?.includeThoughts !== undefined) thinkingConfig.includeThoughts = googleOptions.includeThoughts;
|
|
812
|
+
if (Object.keys(thinkingConfig).length > 0) generationConfig.thinkingConfig = thinkingConfig;
|
|
813
|
+
}
|
|
748
814
|
if (!generationConfig.thinkingConfig && isImageCapableModel(parsed.modelId)) {
|
|
749
815
|
generationConfig.responseModalities = ["TEXT", "IMAGE"];
|
|
750
816
|
}
|
|
@@ -816,6 +882,19 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
816
882
|
const strippedModelTail = /claude/i.test(wireModelId) ? stripTrailingClaudePrefill(contents) : false;
|
|
817
883
|
if (antigravityUsesReplayCache(wireModelId)) {
|
|
818
884
|
applyAntigravityReplay(wireModelId, sessionId, contents);
|
|
885
|
+
// If any functionCall still lacks a thoughtSignature on Gemini Antigravity,
|
|
886
|
+
// supply the bypass sentinel so Antigravity does not reject the turn with HTTP 400.
|
|
887
|
+
for (const c of contents as { role?: string; parts?: unknown[] }[]) {
|
|
888
|
+
if (c?.role !== "model" || !Array.isArray(c.parts)) continue;
|
|
889
|
+
for (const p of c.parts) {
|
|
890
|
+
if (p && typeof p === "object") {
|
|
891
|
+
const partObj = p as Record<string, unknown>;
|
|
892
|
+
if (partObj.functionCall && !partObj.thoughtSignature && !partObj.thought_signature) {
|
|
893
|
+
partObj.thoughtSignature = ANTIGRAVITY_SIGNATURE_BYPASS_SENTINEL;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
819
898
|
} else {
|
|
820
899
|
sanitizeAntigravityClaudeSignatures(contents);
|
|
821
900
|
}
|
|
@@ -970,17 +1049,31 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
970
1049
|
|
|
971
1050
|
// Inline provider error inside a 200 stream → terminal error (see openai-chat.ts).
|
|
972
1051
|
if (chunk.error) {
|
|
973
|
-
const
|
|
1052
|
+
const rawError = chunk.error as { code?: unknown; status?: unknown; message?: unknown };
|
|
1053
|
+
const safeMessage = safeAntigravityInlineErrorMessage(rawError.message);
|
|
1054
|
+
const error = normalizeAntigravityProviderError({
|
|
1055
|
+
code: rawError.code,
|
|
1056
|
+
status: rawError.status,
|
|
1057
|
+
message: safeMessage,
|
|
1058
|
+
});
|
|
1059
|
+
if (provider.googleMode === "cloud-code-assist" && error) observeProviderError?.(error);
|
|
1060
|
+
const err = { ...(error ?? {}), message: error?.message ?? safeMessage ?? "upstream error" };
|
|
974
1061
|
// Clear-on-invalid: a signature rejection means our replayed thoughtSignatures are stale.
|
|
975
1062
|
// Drop the cache entry so the next turn starts clean instead of re-injecting a bad sig.
|
|
976
1063
|
const replayModel = provider.googleMode === "cloud-code-assist" ? antigravityModel : vertexReplayModel;
|
|
977
1064
|
const replaySession = provider.googleMode === "cloud-code-assist" ? antigravitySession : vertexReplaySession;
|
|
978
1065
|
if ((provider.googleMode === "cloud-code-assist" || provider.googleMode === "vertex")
|
|
979
1066
|
&& replayModel && replaySession
|
|
1067
|
+
&& !/missing.*thought_signature/i.test(err?.message ?? "")
|
|
980
1068
|
&& /signature|invalid_argument|invalid argument/i.test(err?.message ?? "")) {
|
|
981
1069
|
clearAntigravityReplay(replayModel, replaySession);
|
|
982
1070
|
}
|
|
983
|
-
yield {
|
|
1071
|
+
yield {
|
|
1072
|
+
type: "error",
|
|
1073
|
+
...(error?.status !== undefined ? { status: error.status } : {}),
|
|
1074
|
+
...(error?.code ? { code: error.code } : {}),
|
|
1075
|
+
message: err.message ?? "upstream error",
|
|
1076
|
+
};
|
|
984
1077
|
return "terminate";
|
|
985
1078
|
}
|
|
986
1079
|
|
|
@@ -1010,7 +1103,14 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1010
1103
|
// absent key and an empty array are already skipped here; `null` joins them. A non-null
|
|
1011
1104
|
// non-array container is still claimed structure the parser cannot read, and stays
|
|
1012
1105
|
// terminal.
|
|
1013
|
-
if (rawCandidates === undefined || rawCandidates === null)
|
|
1106
|
+
if (rawCandidates === undefined || rawCandidates === null) {
|
|
1107
|
+
const feedbackError = googlePromptFeedbackError(root);
|
|
1108
|
+
if (feedbackError) {
|
|
1109
|
+
yield feedbackError;
|
|
1110
|
+
return "terminate";
|
|
1111
|
+
}
|
|
1112
|
+
return "continue";
|
|
1113
|
+
}
|
|
1014
1114
|
if (!Array.isArray(rawCandidates)) {
|
|
1015
1115
|
yield invalidGoogleShapeEvent({
|
|
1016
1116
|
reason: "candidates_not_array",
|
|
@@ -1018,7 +1118,14 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1018
1118
|
});
|
|
1019
1119
|
return "terminate";
|
|
1020
1120
|
}
|
|
1021
|
-
if (rawCandidates.length === 0)
|
|
1121
|
+
if (rawCandidates.length === 0) {
|
|
1122
|
+
const feedbackError = googlePromptFeedbackError(root);
|
|
1123
|
+
if (feedbackError) {
|
|
1124
|
+
yield feedbackError;
|
|
1125
|
+
return "terminate";
|
|
1126
|
+
}
|
|
1127
|
+
return "continue";
|
|
1128
|
+
}
|
|
1022
1129
|
const rawCandidate = rawCandidates[0];
|
|
1023
1130
|
if (!isGoogleRecord(rawCandidate)) {
|
|
1024
1131
|
// Unlike a root `data: null` keepalive, this is a claimed response candidate. Treat it
|
|
@@ -1086,7 +1193,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1086
1193
|
if (parts) {
|
|
1087
1194
|
for (const part of parts) {
|
|
1088
1195
|
const sig = googlePartThoughtSignature(part);
|
|
1089
|
-
if (part.thought === true
|
|
1196
|
+
if (sig && isLikelyRealThoughtSignature(sig) && (part.thought === true || !part.functionCall)) {
|
|
1090
1197
|
pendingStreamThoughtSig = sig;
|
|
1091
1198
|
}
|
|
1092
1199
|
const textEvent = googlePartTextEvent(part, filterCcaSearchSuggestionHtml);
|
|
@@ -1191,9 +1298,8 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1191
1298
|
}
|
|
1192
1299
|
// Fail-closed: a turn cut off mid tool call (MAX_TOKENS / MALFORMED_FUNCTION_CALL) surfaces
|
|
1193
1300
|
// an error instead of a silently-incomplete done. Mirrors kiro-truncation.
|
|
1194
|
-
if ((
|
|
1195
|
-
|
|
1196
|
-
yield { type: "error", message: vertexTruncationErrorMessage(lastFinishReason) };
|
|
1301
|
+
if (isVertexTruncatedTurn(lastFinishReason, toolCallsStarted)) {
|
|
1302
|
+
yield { type: "error", message: truncationErrorMessage(lastFinishReason) };
|
|
1197
1303
|
return;
|
|
1198
1304
|
}
|
|
1199
1305
|
if (!sawAnyFrame || !sawTerminalSignal) {
|
|
@@ -1237,7 +1343,8 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1237
1343
|
// Cloud Code Assist exposes only the SSE transport. Unary callers still use this
|
|
1238
1344
|
// buffered adapter entry point, so collect the exact same events parseStream emits
|
|
1239
1345
|
// instead of maintaining a second CCA JSON parser.
|
|
1240
|
-
|
|
1346
|
+
const isSse = response.headers.get("content-type")?.includes("text/event-stream") ?? false;
|
|
1347
|
+
if (provider.googleMode === "cloud-code-assist" && isSse) {
|
|
1241
1348
|
const events: AdapterEvent[] = [];
|
|
1242
1349
|
let previousTail: AdapterEvent | undefined;
|
|
1243
1350
|
try {
|
|
@@ -1326,10 +1433,24 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1326
1433
|
return events;
|
|
1327
1434
|
};
|
|
1328
1435
|
if (raw.error) {
|
|
1329
|
-
const
|
|
1330
|
-
|
|
1436
|
+
const rawError = raw.error as { code?: unknown; status?: unknown; message?: unknown };
|
|
1437
|
+
const safeMessage = safeAntigravityInlineErrorMessage(rawError.message);
|
|
1438
|
+
const error = normalizeAntigravityProviderError({
|
|
1439
|
+
code: rawError.code,
|
|
1440
|
+
status: rawError.status,
|
|
1441
|
+
message: safeMessage,
|
|
1442
|
+
});
|
|
1443
|
+
if (error) observeProviderError?.(error);
|
|
1444
|
+
return finish([{
|
|
1445
|
+
type: "error",
|
|
1446
|
+
...(error?.status !== undefined ? { status: error.status } : {}),
|
|
1447
|
+
...(error?.code ? { code: error.code } : {}),
|
|
1448
|
+
message: error?.message ?? safeMessage ?? "upstream error",
|
|
1449
|
+
}]);
|
|
1331
1450
|
}
|
|
1332
|
-
const json = raw
|
|
1451
|
+
const json = (provider.googleMode === "cloud-code-assist" && raw.response && typeof raw.response === "object" && !Array.isArray(raw.response))
|
|
1452
|
+
? (raw.response as Record<string, unknown>)
|
|
1453
|
+
: raw;
|
|
1333
1454
|
const events: AdapterEvent[] = [];
|
|
1334
1455
|
|
|
1335
1456
|
const rawCandidates: unknown = json.candidates;
|
|
@@ -1345,6 +1466,8 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1345
1466
|
}
|
|
1346
1467
|
const candidates = rawCandidates as { finishReason?: string }[] | undefined;
|
|
1347
1468
|
if (!candidates?.length) {
|
|
1469
|
+
const feedbackError = googlePromptFeedbackError(json);
|
|
1470
|
+
if (feedbackError) return finish([feedbackError]);
|
|
1348
1471
|
return finish([{ type: "error", message: "google response contained no candidates" }]);
|
|
1349
1472
|
}
|
|
1350
1473
|
const rawCandidate: unknown = candidates[0];
|
|
@@ -1417,9 +1540,8 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1417
1540
|
|
|
1418
1541
|
// Fail-closed truncation, same as the stream path: a non-stream turn cut off mid tool call
|
|
1419
1542
|
// (MAX_TOKENS / MALFORMED_FUNCTION_CALL) surfaces an error instead of a silent done.
|
|
1420
|
-
if (
|
|
1421
|
-
|
|
1422
|
-
return finish([{ type: "error", message: vertexTruncationErrorMessage(candidate.finishReason) }]);
|
|
1543
|
+
if (isVertexTruncatedTurn(candidate.finishReason, toolCallsStarted)) {
|
|
1544
|
+
return finish([{ type: "error", message: truncationErrorMessage(candidate.finishReason) }]);
|
|
1423
1545
|
}
|
|
1424
1546
|
|
|
1425
1547
|
const usage = json.usageMetadata as Record<string, number> | undefined;
|
|
@@ -172,6 +172,12 @@ function omittedToolCatalogNotice(kept: number, omitted: readonly OcxTool[], reg
|
|
|
172
172
|
return `[opencodex] Kiro's outbound catalog budget allows ${kept} of ${kept + omitted.length} client tools this turn. Omitted and unavailable this turn: ${summary}.`;
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
+
function boundedCatalogPriority(tool: OcxTool): number {
|
|
176
|
+
if (tool.loadedFromToolSearch) return 0;
|
|
177
|
+
if (tool.toolSearch) return 1;
|
|
178
|
+
return 2;
|
|
179
|
+
}
|
|
180
|
+
|
|
175
181
|
export function convertKiroToolContext(
|
|
176
182
|
parsed: OcxParsedRequest,
|
|
177
183
|
registry: KiroToolNameRegistry = createKiroToolNameRegistry(),
|
|
@@ -181,9 +187,7 @@ export function convertKiroToolContext(
|
|
|
181
187
|
// Validate every listed name even when tool_choice:none emulates a tool-free turn.
|
|
182
188
|
for (const tool of tools) registry.alias(namespacedToolName(tool.namespace, tool.name));
|
|
183
189
|
const effectiveTools = parsed.options.toolChoice === "none" ? [] : tools;
|
|
184
|
-
const
|
|
185
|
-
let omittedAt = effectiveTools.length;
|
|
186
|
-
for (const [index, tool] of effectiveTools.entries()) {
|
|
190
|
+
const convertedEntries = effectiveTools.map((tool, index) => {
|
|
187
191
|
const description = tool.description || `Tool: ${tool.name}`;
|
|
188
192
|
// Send the full namespaced wire name (e.g. mcp__chrome-devtools__navigate_page) so Kiro echoes
|
|
189
193
|
// it back; the bridge's toolNsMap is keyed by this name and restores the MCP namespace Codex
|
|
@@ -198,19 +202,26 @@ export function convertKiroToolContext(
|
|
|
198
202
|
inputSchema: { json: ensureRootObjectType(sanitizeKiroSchema(tool.parameters ?? {})) },
|
|
199
203
|
},
|
|
200
204
|
};
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
205
|
+
return { tool, index, converted };
|
|
206
|
+
});
|
|
207
|
+
const exceedsBudget = convertedEntries.length > MAX_KIRO_TOOL_COUNT
|
|
208
|
+
|| serializedToolCatalogBytes(convertedEntries.map(entry => entry.converted)) > MAX_KIRO_TOOL_CATALOG_BYTES;
|
|
209
|
+
const candidates = exceedsBudget
|
|
210
|
+
? convertedEntries.toSorted((a, b) => boundedCatalogPriority(a.tool) - boundedCatalogPriority(b.tool) || a.index - b.index)
|
|
211
|
+
: convertedEntries;
|
|
212
|
+
const convertedTools: unknown[] = [];
|
|
213
|
+
let omittedAt = candidates.length;
|
|
214
|
+
for (const [index, entry] of candidates.entries()) {
|
|
204
215
|
if (
|
|
205
216
|
convertedTools.length >= MAX_KIRO_TOOL_COUNT
|
|
206
|
-
|| serializedToolCatalogBytes([...convertedTools, converted]) > MAX_KIRO_TOOL_CATALOG_BYTES
|
|
217
|
+
|| serializedToolCatalogBytes([...convertedTools, entry.converted]) > MAX_KIRO_TOOL_CATALOG_BYTES
|
|
207
218
|
) {
|
|
208
219
|
omittedAt = index;
|
|
209
220
|
break;
|
|
210
221
|
}
|
|
211
|
-
convertedTools.push(converted);
|
|
222
|
+
convertedTools.push(entry.converted);
|
|
212
223
|
}
|
|
213
|
-
const omittedTools =
|
|
224
|
+
const omittedTools = candidates.slice(omittedAt).map(entry => entry.tool);
|
|
214
225
|
return {
|
|
215
226
|
tools: convertedTools,
|
|
216
227
|
systemAdditions: omittedTools.length > 0 ? [omittedToolCatalogNotice(convertedTools.length, omittedTools, registry)] : [],
|
|
@@ -1244,6 +1244,15 @@ function toolsToChatFormat(parsed: OcxParsedRequest, provider: OcxProviderConfig
|
|
|
1244
1244
|
},
|
|
1245
1245
|
}];
|
|
1246
1246
|
});
|
|
1247
|
+
if (xaiTarget) {
|
|
1248
|
+
const omitted = tools.length - formatted.length;
|
|
1249
|
+
debugProviderDiagnostic("openai-chat", "tool-catalog", {
|
|
1250
|
+
declared: tools.length,
|
|
1251
|
+
emitted: formatted.length,
|
|
1252
|
+
omitted,
|
|
1253
|
+
...(omitted > 0 ? { omissionCause: "xai_schema_not_lossless" } : {}),
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1247
1256
|
return formatted.length > 0 ? formatted : undefined;
|
|
1248
1257
|
}
|
|
1249
1258
|
|
|
@@ -1686,7 +1686,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
1686
1686
|
let convertedRoutedCustomToolNames: Set<string> | undefined;
|
|
1687
1687
|
let routedCustomToolRepairNames: Set<string> | undefined;
|
|
1688
1688
|
let convertedRoutedToolSearchNames: Set<string> | undefined;
|
|
1689
|
-
let convertedRoutedNamespaceToolAliases: Map<string, { namespace: string; name: string }> | undefined;
|
|
1689
|
+
let convertedRoutedNamespaceToolAliases: Map<string, { namespace: string; name: string; kind: "function" | "custom" }> | undefined;
|
|
1690
1690
|
const unexpandedMiss = !!parsed.previousResponseId && parsed._previousResponseInputExpanded !== true;
|
|
1691
1691
|
let outBody = stripPreviousResponseId(
|
|
1692
1692
|
parsed._rawBody,
|