@yansigit/opencodex 2.31.3 → 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/bin/ocx.mjs +99 -70
- 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 +25 -13
- package/src/adapters/azure.ts +20 -4
- package/src/adapters/base.ts +5 -1
- package/src/adapters/command-code.ts +42 -10
- package/src/adapters/cursor/live-models.ts +8 -0
- package/src/adapters/cursor/live-transport.ts +1 -1
- package/src/adapters/cursor/native-exec-desktop.ts +16 -0
- 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 +154 -33
- package/src/adapters/kiro-tools.ts +20 -9
- package/src/adapters/kiro.ts +0 -3
- package/src/adapters/openai-chat.ts +9 -0
- package/src/adapters/openai-responses.ts +4 -1
- package/src/adapters/tool-catalog-nudge.ts +1 -1
- package/src/adapters/xai-web-search.ts +7 -2
- package/src/bridge.ts +133 -24
- package/src/claude/context-windows.ts +16 -9
- package/src/cli/dispatch.ts +50 -2
- package/src/cli/doctor.ts +26 -13
- package/src/cli/help.ts +4 -3
- package/src/cli/index.ts +20 -6
- package/src/cli/models.ts +13 -3
- package/src/cli/observe.ts +20 -5
- package/src/cli/provider.ts +8 -1
- package/src/cli/registry.ts +7 -5
- package/src/cli/status.ts +25 -1
- package/src/cli/system-restart-client.ts +1 -1
- package/src/cli/usage-report.ts +134 -0
- package/src/codex/app-server-processes.ts +3 -1
- package/src/codex/auth-api.ts +4 -2
- package/src/codex/autostart-health.ts +16 -0
- package/src/codex/catalog/aggregation.ts +12 -0
- package/src/codex/catalog/effort.ts +42 -12
- package/src/codex/catalog/metadata.ts +27 -1
- package/src/codex/catalog/model-metadata.ts +593 -0
- package/src/codex/catalog/parsing.ts +71 -27
- package/src/codex/catalog/provider-fetch.ts +189 -33
- package/src/codex/catalog/sync.ts +6 -5
- package/src/codex/convergence.ts +5 -0
- package/src/codex/desktop-app-restart.ts +342 -0
- package/src/codex/history-job.ts +32 -3
- package/src/codex/history-manifest.ts +112 -0
- package/src/codex/history-migration-guardian.ts +5 -5
- package/src/codex/history-provider.ts +825 -247
- package/src/codex/history-worker.ts +8 -5
- package/src/codex/inject.ts +49 -21
- package/src/codex/injected-marker.ts +1 -1
- package/src/codex/internal/history-writer.ts +4 -3
- package/src/codex/native-profile-startup.ts +157 -27
- package/src/codex/native-residue.ts +26 -33
- package/src/codex/shim.ts +56 -3
- package/src/combos/failover.ts +27 -0
- package/src/compatibility/index.ts +26 -0
- package/src/compatibility/manifest.ts +253 -0
- package/src/compatibility/openai-responses.ts +81 -0
- package/src/config/atomic-write.ts +219 -0
- package/src/config/paths.ts +40 -0
- package/src/config/process-state.ts +308 -0
- package/src/config/provider-validation.ts +214 -0
- package/src/config.ts +153 -814
- package/src/generated/compatibility-version.json +232 -148
- package/src/images/loop.ts +37 -6
- package/src/images/plan.ts +5 -4
- package/src/integrations/ownership-policy.ts +141 -0
- package/src/integrations/ownership.ts +10 -0
- package/src/integrations/state.ts +44 -5
- package/src/integrations/writer.ts +6 -0
- package/src/lib/azure-identity.ts +154 -0
- package/src/lib/bounded-body.ts +14 -2
- package/src/lib/debug.ts +42 -0
- package/src/lib/errors.ts +14 -0
- package/src/lib/process-control.ts +2 -1
- 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/lib/state-store-registrations.ts +2 -0
- package/src/lib/tool-argument-integers.ts +56 -5
- 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/health.ts +1 -1
- 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 +4 -2
- package/src/providers/request-pacing.ts +33 -6
- package/src/providers/xai-transport.ts +21 -0
- package/src/reasoning-effort.ts +19 -2
- package/src/responses/apply-patch-envelope.ts +63 -0
- package/src/responses/custom-tool-compat.ts +132 -38
- package/src/responses/google-provider-options.ts +36 -0
- package/src/responses/namespace-tool-compat.ts +84 -4
- package/src/responses/parser.ts +14 -2
- package/src/responses/provider-opaque-metadata.ts +3 -3
- package/src/responses/reasoning-replay-cache.ts +81 -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 +37 -7
- package/src/server/images.ts +19 -35
- package/src/server/index.ts +102 -21
- package/src/server/local-management-read-client.ts +1 -1
- package/src/server/local-provider-reload-client.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +206 -16
- package/src/server/management/combo-routes.ts +6 -0
- package/src/server/management/config-routes.ts +35 -6
- package/src/server/management/context.ts +1 -1
- package/src/server/management/logs-usage-routes.ts +27 -6
- package/src/server/management/model-routes.ts +8 -4
- package/src/server/management/model-rows.ts +4 -0
- package/src/server/management/native-integration-routes.ts +2 -1
- package/src/server/management/oauth-account-routes.ts +25 -4
- package/src/server/management/provider-capability-config.ts +1 -1
- package/src/server/management/provider-routes.ts +113 -15
- package/src/server/management/routing-profile-routes.ts +3 -0
- package/src/server/management/system-restart.ts +1 -1
- package/src/server/port-reclaim.ts +1 -1
- package/src/server/proxy-liveness.ts +2 -1
- package/src/server/request-log-conversation.ts +30 -0
- package/src/server/request-log.ts +21 -0
- package/src/server/responses/agent-task-recovery.ts +1 -1
- package/src/server/responses/codex-auth-error.ts +55 -0
- package/src/server/responses/combo-stream-preflight.ts +171 -0
- package/src/server/responses/compact.ts +36 -22
- package/src/server/responses/core.ts +584 -247
- package/src/server/responses/empty-completion-guard.ts +35 -6
- package/src/server/responses/fetch-helpers.ts +20 -102
- package/src/server/responses/v2-native-parent-override.ts +59 -0
- package/src/server/responses/ws-upstream.ts +75 -2
- package/src/server/responses-custom-tool-repair.ts +41 -5
- package/src/server/responses-undeclared-tool-guard.ts +241 -18
- package/src/service.ts +9 -5
- package/src/types/config.ts +16 -1
- package/src/types/provider.ts +16 -0
- package/src/types/request.ts +34 -1
- package/src/types/tools.ts +114 -11
- package/src/types.ts +7 -1
- package/src/update/index.ts +5 -4
- package/src/update/job.ts +3 -1
- package/src/update/transactional-install.mjs +8 -1
- package/src/usage/log.ts +16 -8
- package/src/usage/summary.ts +201 -8
- package/src/vision/describe.ts +18 -13
- package/src/web-search/executor.ts +10 -3
- package/src/web-search/gemini-executor.ts +6 -4
- package/src/web-search/loop.ts +42 -6
- package/gui/dist/assets/index-CGoDO3uO.css +0 -1
- package/gui/dist/assets/index-Cxt5fZMP.js +0 -102
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,
|
|
@@ -14,13 +15,14 @@ import type {
|
|
|
14
15
|
OcxToolResultMessage,
|
|
15
16
|
OcxUsage,
|
|
16
17
|
} from "../types";
|
|
17
|
-
import { isAllowedToolChoice, namespacedToolName, resolveToolChoiceWireName,
|
|
18
|
+
import { isAllowedToolChoice, namespacedToolName, resolveToolChoiceWireName, toolChoiceToolPredicate } from "../types";
|
|
18
19
|
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
|
}
|
|
@@ -346,11 +379,8 @@ function toolsToGeminiFormat(
|
|
|
346
379
|
wireModelId: string,
|
|
347
380
|
): unknown[] | undefined {
|
|
348
381
|
const grounding = parsed._ccaInTurnGrounding;
|
|
349
|
-
const
|
|
350
|
-
?
|
|
351
|
-
: undefined;
|
|
352
|
-
const tools = allowed
|
|
353
|
-
? parsed.context.tools?.filter(t => toolAllowedByChoice(t, allowed, parsed.context.tools)) ?? []
|
|
382
|
+
const tools = isAllowedToolChoice(parsed.options.toolChoice)
|
|
383
|
+
? parsed.context.tools?.filter(toolChoiceToolPredicate(parsed.options.toolChoice, parsed.context.tools)) ?? []
|
|
354
384
|
: parsed.context.tools ?? [];
|
|
355
385
|
const functionDeclarations = tools.map(t => ({
|
|
356
386
|
name: namespacedToolName(t.namespace, t.name),
|
|
@@ -398,6 +428,13 @@ function usageFromGemini(usage: Record<string, number> | undefined): OcxUsage |
|
|
|
398
428
|
};
|
|
399
429
|
}
|
|
400
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
|
+
|
|
401
438
|
/**
|
|
402
439
|
* Cap on the buffered non-streaming response body (100 MiB), matching
|
|
403
440
|
* IMAGES_RESPONSE_MAX_BYTES in src/server/images.ts. Enforced by streaming the
|
|
@@ -675,6 +712,7 @@ function invalidGoogleShapeEvent(
|
|
|
675
712
|
export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapter {
|
|
676
713
|
// Per-request closure: resolveAdapter builds a fresh adapter per request (server.ts), so buildRequest
|
|
677
714
|
// can stash the CCA model/session for parseStream's reasoning-replay observation.
|
|
715
|
+
let observeProviderError: IncomingMeta["onProviderError"];
|
|
678
716
|
let antigravityModel: string | undefined;
|
|
679
717
|
let antigravitySession: string | undefined;
|
|
680
718
|
// Vertex returns the same opaque Gemini thought signatures as CCA, but its replay namespace
|
|
@@ -684,8 +722,23 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
684
722
|
let vertexReplaySession: string | undefined;
|
|
685
723
|
let restoreGoogleToolName = (name: string): string => name;
|
|
686
724
|
const emitInTurnGroundingSourcesQueue: boolean[] = [];
|
|
725
|
+
const truncationErrorMessage = provider.googleMode === "vertex" || provider.googleMode === "cloud-code-assist"
|
|
726
|
+
? vertexTruncationErrorMessage
|
|
727
|
+
: googleTruncationErrorMessage;
|
|
687
728
|
return {
|
|
688
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
|
+
},
|
|
689
742
|
|
|
690
743
|
// Vertex + Antigravity get Kiro-style retry/timeout + classified, redacted errors.
|
|
691
744
|
// Direct AI-Studio uses the canonical server transport (fetchWithTransientRetry), which
|
|
@@ -700,7 +753,8 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
700
753
|
}
|
|
701
754
|
: {}),
|
|
702
755
|
|
|
703
|
-
async buildRequest(parsed: OcxParsedRequest) {
|
|
756
|
+
async buildRequest(parsed: OcxParsedRequest, options?: IncomingMeta) {
|
|
757
|
+
observeProviderError = options?.onProviderError;
|
|
704
758
|
const routedModelId = provider.googleMode === "cloud-code-assist"
|
|
705
759
|
? resolveAntigravityEffortWireModel(
|
|
706
760
|
parsed.modelId,
|
|
@@ -726,6 +780,9 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
726
780
|
// catalog is a guaranteed upstream 400.
|
|
727
781
|
const toolConfig = tools ? toolChoiceToGeminiToolConfig(parsed) : undefined;
|
|
728
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;
|
|
729
786
|
|
|
730
787
|
const generationConfig: Record<string, unknown> = {};
|
|
731
788
|
if (parsed.options.maxOutputTokens) generationConfig.maxOutputTokens = parsed.options.maxOutputTokens;
|
|
@@ -747,7 +804,13 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
747
804
|
const thinkingLevel = thinkingEligible
|
|
748
805
|
? mapReasoningEffort(provider, parsed.modelId, parsed.options.reasoning)
|
|
749
806
|
: undefined;
|
|
750
|
-
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
|
+
}
|
|
751
814
|
if (!generationConfig.thinkingConfig && isImageCapableModel(parsed.modelId)) {
|
|
752
815
|
generationConfig.responseModalities = ["TEXT", "IMAGE"];
|
|
753
816
|
}
|
|
@@ -819,6 +882,19 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
819
882
|
const strippedModelTail = /claude/i.test(wireModelId) ? stripTrailingClaudePrefill(contents) : false;
|
|
820
883
|
if (antigravityUsesReplayCache(wireModelId)) {
|
|
821
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
|
+
}
|
|
822
898
|
} else {
|
|
823
899
|
sanitizeAntigravityClaudeSignatures(contents);
|
|
824
900
|
}
|
|
@@ -973,17 +1049,31 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
973
1049
|
|
|
974
1050
|
// Inline provider error inside a 200 stream → terminal error (see openai-chat.ts).
|
|
975
1051
|
if (chunk.error) {
|
|
976
|
-
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" };
|
|
977
1061
|
// Clear-on-invalid: a signature rejection means our replayed thoughtSignatures are stale.
|
|
978
1062
|
// Drop the cache entry so the next turn starts clean instead of re-injecting a bad sig.
|
|
979
1063
|
const replayModel = provider.googleMode === "cloud-code-assist" ? antigravityModel : vertexReplayModel;
|
|
980
1064
|
const replaySession = provider.googleMode === "cloud-code-assist" ? antigravitySession : vertexReplaySession;
|
|
981
1065
|
if ((provider.googleMode === "cloud-code-assist" || provider.googleMode === "vertex")
|
|
982
1066
|
&& replayModel && replaySession
|
|
1067
|
+
&& !/missing.*thought_signature/i.test(err?.message ?? "")
|
|
983
1068
|
&& /signature|invalid_argument|invalid argument/i.test(err?.message ?? "")) {
|
|
984
1069
|
clearAntigravityReplay(replayModel, replaySession);
|
|
985
1070
|
}
|
|
986
|
-
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
|
+
};
|
|
987
1077
|
return "terminate";
|
|
988
1078
|
}
|
|
989
1079
|
|
|
@@ -1013,7 +1103,14 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1013
1103
|
// absent key and an empty array are already skipped here; `null` joins them. A non-null
|
|
1014
1104
|
// non-array container is still claimed structure the parser cannot read, and stays
|
|
1015
1105
|
// terminal.
|
|
1016
|
-
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
|
+
}
|
|
1017
1114
|
if (!Array.isArray(rawCandidates)) {
|
|
1018
1115
|
yield invalidGoogleShapeEvent({
|
|
1019
1116
|
reason: "candidates_not_array",
|
|
@@ -1021,7 +1118,14 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1021
1118
|
});
|
|
1022
1119
|
return "terminate";
|
|
1023
1120
|
}
|
|
1024
|
-
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
|
+
}
|
|
1025
1129
|
const rawCandidate = rawCandidates[0];
|
|
1026
1130
|
if (!isGoogleRecord(rawCandidate)) {
|
|
1027
1131
|
// Unlike a root `data: null` keepalive, this is a claimed response candidate. Treat it
|
|
@@ -1077,7 +1181,9 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1077
1181
|
const replaySession = provider.googleMode === "cloud-code-assist" ? antigravitySession : vertexReplaySession;
|
|
1078
1182
|
if ((provider.googleMode === "cloud-code-assist" || provider.googleMode === "vertex")
|
|
1079
1183
|
&& parts && replayModel && replaySession) {
|
|
1080
|
-
|
|
1184
|
+
// Observation may scan the whole frame, so use it only for replay-cache side effects.
|
|
1185
|
+
// The source-order loop below exclusively owns stream carry and cannot pair backwards.
|
|
1186
|
+
observeAntigravityReplay(
|
|
1081
1187
|
replayModel,
|
|
1082
1188
|
replaySession,
|
|
1083
1189
|
parts as unknown[],
|
|
@@ -1087,7 +1193,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1087
1193
|
if (parts) {
|
|
1088
1194
|
for (const part of parts) {
|
|
1089
1195
|
const sig = googlePartThoughtSignature(part);
|
|
1090
|
-
if (part.thought === true
|
|
1196
|
+
if (sig && isLikelyRealThoughtSignature(sig) && (part.thought === true || !part.functionCall)) {
|
|
1091
1197
|
pendingStreamThoughtSig = sig;
|
|
1092
1198
|
}
|
|
1093
1199
|
const textEvent = googlePartTextEvent(part, filterCcaSearchSuggestionHtml);
|
|
@@ -1192,9 +1298,8 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1192
1298
|
}
|
|
1193
1299
|
// Fail-closed: a turn cut off mid tool call (MAX_TOKENS / MALFORMED_FUNCTION_CALL) surfaces
|
|
1194
1300
|
// an error instead of a silently-incomplete done. Mirrors kiro-truncation.
|
|
1195
|
-
if ((
|
|
1196
|
-
|
|
1197
|
-
yield { type: "error", message: vertexTruncationErrorMessage(lastFinishReason) };
|
|
1301
|
+
if (isVertexTruncatedTurn(lastFinishReason, toolCallsStarted)) {
|
|
1302
|
+
yield { type: "error", message: truncationErrorMessage(lastFinishReason) };
|
|
1198
1303
|
return;
|
|
1199
1304
|
}
|
|
1200
1305
|
if (!sawAnyFrame || !sawTerminalSignal) {
|
|
@@ -1238,7 +1343,8 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1238
1343
|
// Cloud Code Assist exposes only the SSE transport. Unary callers still use this
|
|
1239
1344
|
// buffered adapter entry point, so collect the exact same events parseStream emits
|
|
1240
1345
|
// instead of maintaining a second CCA JSON parser.
|
|
1241
|
-
|
|
1346
|
+
const isSse = response.headers.get("content-type")?.includes("text/event-stream") ?? false;
|
|
1347
|
+
if (provider.googleMode === "cloud-code-assist" && isSse) {
|
|
1242
1348
|
const events: AdapterEvent[] = [];
|
|
1243
1349
|
let previousTail: AdapterEvent | undefined;
|
|
1244
1350
|
try {
|
|
@@ -1327,10 +1433,24 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1327
1433
|
return events;
|
|
1328
1434
|
};
|
|
1329
1435
|
if (raw.error) {
|
|
1330
|
-
const
|
|
1331
|
-
|
|
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
|
+
}]);
|
|
1332
1450
|
}
|
|
1333
|
-
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;
|
|
1334
1454
|
const events: AdapterEvent[] = [];
|
|
1335
1455
|
|
|
1336
1456
|
const rawCandidates: unknown = json.candidates;
|
|
@@ -1346,6 +1466,8 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1346
1466
|
}
|
|
1347
1467
|
const candidates = rawCandidates as { finishReason?: string }[] | undefined;
|
|
1348
1468
|
if (!candidates?.length) {
|
|
1469
|
+
const feedbackError = googlePromptFeedbackError(json);
|
|
1470
|
+
if (feedbackError) return finish([feedbackError]);
|
|
1349
1471
|
return finish([{ type: "error", message: "google response contained no candidates" }]);
|
|
1350
1472
|
}
|
|
1351
1473
|
const rawCandidate: unknown = candidates[0];
|
|
@@ -1418,9 +1540,8 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1418
1540
|
|
|
1419
1541
|
// Fail-closed truncation, same as the stream path: a non-stream turn cut off mid tool call
|
|
1420
1542
|
// (MAX_TOKENS / MALFORMED_FUNCTION_CALL) surfaces an error instead of a silent done.
|
|
1421
|
-
if (
|
|
1422
|
-
|
|
1423
|
-
return finish([{ type: "error", message: vertexTruncationErrorMessage(candidate.finishReason) }]);
|
|
1543
|
+
if (isVertexTruncatedTurn(candidate.finishReason, toolCallsStarted)) {
|
|
1544
|
+
return finish([{ type: "error", message: truncationErrorMessage(candidate.finishReason) }]);
|
|
1424
1545
|
}
|
|
1425
1546
|
|
|
1426
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)] : [],
|
package/src/adapters/kiro.ts
CHANGED
|
@@ -318,9 +318,6 @@ function validateKiroCapabilities(parsed: OcxParsedRequest): void {
|
|
|
318
318
|
if (choice !== undefined && choice !== "auto" && choice !== "none") {
|
|
319
319
|
throw new Error("Kiro supports only automatic tool choice or tool_choice:none");
|
|
320
320
|
}
|
|
321
|
-
if (parsed.options.parallelToolCalls === true) {
|
|
322
|
-
throw new Error("Kiro does not support parallel tool calls");
|
|
323
|
-
}
|
|
324
321
|
if (parsed.options.serviceTier !== undefined) {
|
|
325
322
|
throw new Error("Kiro does not support service tiers");
|
|
326
323
|
}
|
|
@@ -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
|
|
|
@@ -1684,8 +1684,9 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
1684
1684
|
|
|
1685
1685
|
const forward = provider.authMode === "forward";
|
|
1686
1686
|
let convertedRoutedCustomToolNames: Set<string> | undefined;
|
|
1687
|
+
let routedCustomToolRepairNames: Set<string> | undefined;
|
|
1687
1688
|
let convertedRoutedToolSearchNames: Set<string> | undefined;
|
|
1688
|
-
let convertedRoutedNamespaceToolAliases: Map<string, { namespace: string; name: string }> | undefined;
|
|
1689
|
+
let convertedRoutedNamespaceToolAliases: Map<string, { namespace: string; name: string; kind: "function" | "custom" }> | undefined;
|
|
1689
1690
|
const unexpandedMiss = !!parsed.previousResponseId && parsed._previousResponseInputExpanded !== true;
|
|
1690
1691
|
let outBody = stripPreviousResponseId(
|
|
1691
1692
|
parsed._rawBody,
|
|
@@ -1741,6 +1742,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
1741
1742
|
);
|
|
1742
1743
|
outBody = rewritten.body;
|
|
1743
1744
|
convertedRoutedCustomToolNames = rewritten.names;
|
|
1745
|
+
routedCustomToolRepairNames = rewritten.repairNames;
|
|
1744
1746
|
}
|
|
1745
1747
|
if (!isCanonicalOpenAiForwardProvider(provider)) {
|
|
1746
1748
|
// Run after custom-tool lowering so the search compatibility layer can choose a
|
|
@@ -1811,6 +1813,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
1811
1813
|
body,
|
|
1812
1814
|
releaseBodyObservation,
|
|
1813
1815
|
...(convertedRoutedCustomToolNames ? { convertedRoutedCustomToolNames } : {}),
|
|
1816
|
+
...(routedCustomToolRepairNames ? { routedCustomToolRepairNames } : {}),
|
|
1814
1817
|
...(convertedRoutedToolSearchNames ? { convertedRoutedToolSearchNames } : {}),
|
|
1815
1818
|
...(convertedRoutedNamespaceToolAliases ? { convertedRoutedNamespaceToolAliases } : {}),
|
|
1816
1819
|
...(tierLog ? { tierLog } : {}),
|
|
@@ -120,7 +120,7 @@ export function buildNonOpenAIToolCatalogNudgeFromNames(
|
|
|
120
120
|
"Call only listed names with their listed argument keys; do not invent, translate, or rename tools.",
|
|
121
121
|
"Names mentioned only in instructions, tool descriptions, argument descriptions, or nested helper APIs are not additional top-level tools.",
|
|
122
122
|
verifiedCodeModeExecName
|
|
123
|
-
? "`" + verifiedCodeModeExecName + "` is Codex code mode: its body is JavaScript evaluated in a V8 isolate. Nested helpers are called INSIDE that body as `await tools.<name>(...)`, for example `await tools.exec_command({cmd: \"ls\"})` or `await tools.codex_app__list_threads({})`. Absence from the top-level catalog or from `" + verifiedCodeModeExecName + "`'s description is not absence: deferred helpers stay callable on `tools.<name>`. Discover them from the isolate global `ALL_TOOLS`, not `tools.ALL_TOOLS`. Do not skip an available nested helper because it is omitted from the listed top-level names."
|
|
123
|
+
? "`" + verifiedCodeModeExecName + "` is Codex code mode: its body is JavaScript evaluated in a V8 isolate. Nested helpers are called INSIDE that body as `await tools.<name>(...)`, for example `await tools.exec_command({cmd: \"ls\"})` or `await tools.codex_app__list_threads({})`. Absence from the top-level catalog or from `" + verifiedCodeModeExecName + "`'s description is not absence: deferred helpers stay callable on `tools.<name>`. Discover them from the isolate global `ALL_TOOLS`, not `tools.ALL_TOOLS`. Do not skip an available nested helper because it is omitted from the listed top-level names. Nested `tools.apply_patch(input)` is host-executed: the string must begin exactly with `*** Begin Patch` and end with `*** End Patch` (no trailing `***` on those lines). OpenCodex does not rewrite JavaScript inside exec, so a decorated `*** Begin Patch ***` envelope is rejected by Codex before the file is touched."
|
|
124
124
|
: "If a listed tool exposes nested helpers such as a tools.* API, call the listed parent tool and use those helpers only inside that tool's input.",
|
|
125
125
|
unavailableNeighborNames.length > 0
|
|
126
126
|
? "Do not use neighboring-agent tool names " + quoteNames(unavailableNeighborNames) + " unless this turn's catalog lists those exact names."
|
|
@@ -60,10 +60,15 @@ function normalizeToolGroup(tools: unknown[]): ToolGroupRewrite {
|
|
|
60
60
|
: undefined;
|
|
61
61
|
const enableImageSearch = searchContentTypes?.includes("image") === true;
|
|
62
62
|
const next: Record<string, unknown> = { ...tool, type: CODEX_WEB_SEARCH_TOOL };
|
|
63
|
+
// Only the two fields xAI actually refuses are removed. Probed 2026-08-22, one field per
|
|
64
|
+
// request, against BOTH xAI destinations (api.x.ai and cli-chat-proxy.grok.com): they behave
|
|
65
|
+
// identically — `external_web_access` 400s on every value including `true`, and
|
|
66
|
+
// `search_context_size` 400s, while `user_location`, `search_content_types`, `filters` and
|
|
67
|
+
// `enable_image_search` are all accepted. Deleting the accepted ones was a silent capability
|
|
68
|
+
// loss, and it contradicted the sibling layer, whose own probe note already records
|
|
69
|
+
// user_location/filters as accepted (tests/responses-routed-web-search-fields.test.ts).
|
|
63
70
|
delete next.external_web_access;
|
|
64
71
|
delete next.search_context_size;
|
|
65
|
-
delete next.search_content_types;
|
|
66
|
-
delete next.user_location;
|
|
67
72
|
if (enableImageSearch && !Object.hasOwn(next, "enable_image_search")) {
|
|
68
73
|
next.enable_image_search = true;
|
|
69
74
|
}
|