@yansigit/opencodex 2.33.0 → 2.35.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 +3 -3
- package/gui/dist/assets/index-BjCaHxdz.js +112 -0
- package/gui/dist/assets/index-DLkXOXLC.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic.ts +79 -2
- package/src/adapters/command-code.ts +141 -23
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/checkpoint-store.ts +15 -10
- package/src/adapters/cursor/discovery.ts +60 -2
- package/src/adapters/cursor/effort-map.ts +79 -1
- package/src/adapters/cursor/envelope-echo.ts +162 -0
- package/src/adapters/cursor/live-models.ts +7 -2
- package/src/adapters/cursor/live-transport.ts +17 -1
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-fs.ts +13 -12
- package/src/adapters/cursor/native-exec-network.ts +3 -5
- package/src/adapters/cursor/native-exec-policy.ts +47 -0
- package/src/adapters/cursor/native-exec-shell.ts +116 -31
- package/src/adapters/cursor/native-exec.ts +38 -10
- package/src/adapters/cursor/protobuf-events.ts +28 -2
- package/src/adapters/cursor/protobuf-request.ts +93 -41
- package/src/adapters/cursor/request-builder.ts +39 -10
- package/src/adapters/cursor/tool-definitions.ts +27 -3
- package/src/adapters/cursor/tool-result-normalize.ts +51 -6
- package/src/adapters/cursor/types.ts +23 -4
- package/src/adapters/cursor.ts +170 -29
- package/src/adapters/google-aistudio-parser.ts +49 -0
- package/src/adapters/google-antigravity-replay.ts +105 -25
- package/src/adapters/google-antigravity-wire.ts +5 -0
- package/src/adapters/google-errors.ts +41 -12
- package/src/adapters/google-http.ts +12 -11
- package/src/adapters/google.ts +219 -36
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +15 -0
- package/src/adapters/kiro-tools.ts +43 -15
- package/src/adapters/kiro.ts +54 -9
- package/src/adapters/openai-chat.ts +286 -242
- package/src/adapters/openai-responses.ts +335 -24
- package/src/adapters/run-turn-queue.ts +36 -1
- package/src/adapters/tool-catalog-nudge.ts +2 -2
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/bridge.ts +67 -26
- package/src/chat/inbound.ts +29 -1
- package/src/chat/outbound.ts +15 -7
- package/src/claude/agents-inject.ts +8 -1
- package/src/claude/outbound.ts +10 -8
- package/src/cli/account-api.ts +27 -7
- package/src/cli/account-extended.ts +10 -3
- package/src/cli/account.ts +29 -5
- package/src/cli/alias.ts +66 -0
- package/src/cli/claude.ts +26 -1
- package/src/cli/dispatch.ts +13 -1
- package/src/cli/help.ts +1 -0
- package/src/cli/index.ts +6 -1
- package/src/cli/init.ts +1 -0
- package/src/cli/models-runtime.ts +95 -0
- package/src/cli/models.ts +13 -7
- package/src/cli/provider-runtime.ts +16 -2
- package/src/cli/registry.ts +6 -1
- package/src/cli/telemetry-commands.ts +25 -0
- package/src/cli/v2.ts +34 -10
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +3 -2
- package/src/codex/app-server-processes.ts +80 -6
- package/src/codex/auth-api.ts +48 -8
- package/src/codex/auth-context.ts +21 -18
- package/src/codex/catalog/aggregation.ts +6 -0
- package/src/codex/catalog/model-metadata.ts +13 -1
- package/src/codex/catalog/native-models.ts +5 -2
- package/src/codex/catalog/parsing.ts +16 -0
- package/src/codex/catalog/provider-fetch.ts +20 -3
- package/src/codex/catalog/sync.ts +127 -2
- package/src/codex/catalog.ts +1 -1
- package/src/codex/codex-write-lock.ts +3 -1
- package/src/codex/convergence-types.ts +1 -1
- package/src/codex/convergence.ts +22 -2
- package/src/codex/desired-state.ts +2 -2
- package/src/codex/desktop-app-restart.ts +18 -5
- package/src/codex/inject-coordination.ts +83 -0
- package/src/codex/inject.ts +14 -1
- package/src/codex/log-guard/inspect.ts +22 -4
- package/src/codex/model-entitlements.ts +9 -2
- package/src/codex/prompt-layers.ts +371 -25
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota.ts +123 -18
- package/src/codex/routing.ts +9 -0
- package/src/codex/subagent-model-fallback.ts +198 -27
- package/src/codex/transition-state.ts +107 -8
- package/src/combos/types.ts +10 -0
- package/src/compatibility/openai-responses.ts +33 -1
- package/src/config/autonomous-remediation.ts +21 -0
- package/src/config/provider-validation.ts +14 -0
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +191 -17
- package/src/generated/compatibility-version.json +279 -159
- package/src/generated/model-metadata.ts +3 -0
- package/src/images/loop.ts +5 -4
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +1 -1
- package/src/lab/fabric/producer-child.ts +1 -1
- package/src/lib/config-ownership.ts +20 -0
- package/src/lib/errors.ts +11 -2
- package/src/lib/package-tree-integrity.ts +101 -0
- package/src/oauth/aistudio-credentials.ts +65 -0
- package/src/oauth/aistudio-native-daemon.ts +116 -0
- package/src/oauth/aistudio-session-sync.ts +95 -0
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/google-aistudio-auth.ts +98 -0
- package/src/oauth/index.ts +57 -5
- package/src/oauth/key-providers.ts +18 -1
- package/src/oauth/kiro.ts +45 -0
- package/src/oauth/login-cli.ts +65 -1
- package/src/oauth/types.ts +15 -0
- package/src/providers/codex-capacity.ts +5 -2
- package/src/providers/command-code-efforts.ts +38 -6
- package/src/providers/context-cap.ts +4 -3
- package/src/providers/default-aliases.ts +65 -0
- package/src/providers/derive.ts +29 -1
- package/src/providers/fastwire.ts +7 -1
- package/src/providers/model-presets.ts +119 -0
- package/src/providers/new-model-policy.ts +146 -0
- package/src/providers/provider-id-rewrite.ts +2 -1
- package/src/providers/quota.ts +157 -46
- package/src/providers/registry.ts +184 -71
- package/src/providers/slug-codec.ts +52 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/custom-tool-compat.ts +34 -10
- package/src/responses/parser.ts +4 -0
- package/src/responses/schema.ts +5 -1
- package/src/responses/thought-signature-replay.ts +17 -0
- package/src/router.ts +43 -2
- package/src/routing/account-pool/cooldown.ts +8 -0
- package/src/routing/account-pool/index.ts +1 -0
- package/src/routing/analytics.ts +1 -0
- package/src/routing/quota.ts +10 -0
- package/src/server/auth-cors.ts +24 -0
- package/src/server/chat-completions.ts +26 -16
- package/src/server/chat-native-sse.ts +3 -3
- package/src/server/chat-native.ts +30 -11
- package/src/server/claude-messages.ts +1 -1
- package/src/server/effort-policy.ts +16 -0
- package/src/server/index.ts +180 -14
- package/src/server/lifecycle.ts +52 -1
- package/src/server/management/agent-settings-routes.ts +31 -15
- package/src/server/management/codex-prompt-routes.ts +570 -0
- package/src/server/management/combo-routes.ts +2 -1
- package/src/server/management/config-routes.ts +27 -9
- package/src/server/management/context.ts +9 -0
- package/src/server/management/logs-usage-routes.ts +11 -5
- package/src/server/management/model-routes.ts +266 -0
- package/src/server/management/oauth-account-routes.ts +13 -3
- package/src/server/management/provider-routes.ts +137 -3
- package/src/server/management/routing-profile-routes.ts +2 -2
- package/src/server/management-api.ts +2 -0
- package/src/server/port-reclaim.ts +19 -1
- package/src/server/relay-eager.ts +147 -20
- package/src/server/relay.ts +251 -19
- package/src/server/request-log-conversation.ts +33 -0
- package/src/server/request-log.ts +48 -21
- package/src/server/responses/collaboration.ts +42 -5
- package/src/server/responses/combo-stream-preflight.ts +10 -3
- package/src/server/responses/core.ts +575 -140
- package/src/server/responses/empty-completion-guard.ts +35 -0
- package/src/server/responses/fetch-helpers.ts +14 -6
- package/src/server/responses/input-admission.ts +3 -1
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/policy-fallback.ts +1 -1
- package/src/server/responses/responses-field-backfill.ts +105 -13
- package/src/server/responses/ws-upstream.ts +35 -5
- package/src/server/responses-custom-tool-repair.ts +52 -7
- package/src/server/responses-terminal-repair.ts +25 -4
- package/src/server/sse-frame-buffer.ts +31 -4
- package/src/server/ws-bridge.ts +14 -2
- package/src/smoke/fingerprint-cache.ts +133 -0
- package/src/smoke/live-scenarios.ts +33 -0
- package/src/smoke/runner.ts +119 -0
- package/src/telemetry/dispatcher.ts +44 -0
- package/src/telemetry/fingerprint.ts +24 -0
- package/src/telemetry/hook.ts +43 -0
- package/src/telemetry/ledger.ts +54 -0
- package/src/telemetry/types.ts +23 -0
- package/src/types/config.ts +66 -14
- package/src/types/provider.ts +79 -1
- package/src/types/request.ts +18 -10
- package/src/types/tools.ts +30 -11
- package/src/types.ts +1 -0
- package/src/usage/command-code-manifest.ts +116 -0
- package/src/usage/cost.ts +2 -2
- package/src/usage/expected-prices.ts +126 -24
- package/src/usage/log.ts +18 -8
- package/src/usage/summary.ts +34 -12
- package/src/web-search/exa-executor.ts +40 -9
- package/src/web-search/index.ts +16 -8
- package/src/web-search/loop.ts +5 -4
- package/gui/dist/assets/index-DKLr4LTE.js +0 -102
- package/gui/dist/assets/index-DrSQdTRd.css +0 -1
|
@@ -39,7 +39,7 @@ function requestForHost(request: AdapterRequest, host: string): AdapterRequest {
|
|
|
39
39
|
return { ...request, url: target.toString() };
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
type CcaSseProbe = "empty" | "candidate" | "unavailable" | "quota_exhausted" | "geo_blocked" | "terminal";
|
|
42
|
+
type CcaSseProbe = "empty" | "candidate" | "unavailable" | "quota_exhausted" | "rate_limit" | "geo_blocked" | "terminal";
|
|
43
43
|
|
|
44
44
|
function probeCcaSseEvent(bytes: Uint8Array): CcaSseProbe {
|
|
45
45
|
const text = new TextDecoder().decode(bytes);
|
|
@@ -67,6 +67,7 @@ function probeCcaSseEvent(bytes: Uint8Array): CcaSseProbe {
|
|
|
67
67
|
}
|
|
68
68
|
const serialized = JSON.stringify(frame);
|
|
69
69
|
if (isQuotaExhaustedBody(serialized)) return "quota_exhausted";
|
|
70
|
+
if (/rate[- ]limit|too many requests|per[- ]minute|requests per minute|concurrent request/i.test(serialized)) return "rate_limit";
|
|
70
71
|
if (isAntigravityGeoBlockedBody(serialized)) return "geo_blocked";
|
|
71
72
|
return "terminal";
|
|
72
73
|
}
|
|
@@ -209,15 +210,15 @@ async function prepareCcaSseResponse(
|
|
|
209
210
|
if (probe === "unavailable") {
|
|
210
211
|
return failoverOrPassthrough();
|
|
211
212
|
}
|
|
212
|
-
if (probe === "quota_exhausted" || probe === "geo_blocked") {
|
|
213
|
+
if (probe === "quota_exhausted" || probe === "rate_limit" || probe === "geo_blocked") {
|
|
213
214
|
if (accountId) {
|
|
214
215
|
recordAntigravitySyntheticFailure(accountId, {
|
|
215
|
-
code: probe === "
|
|
216
|
-
status: probe === "
|
|
217
|
-
message: probe === "quota_exhausted" ? "quota exceeded" : "user location is not supported",
|
|
216
|
+
code: probe === "geo_blocked" ? 403 : 429,
|
|
217
|
+
status: probe === "geo_blocked" ? "PERMISSION_DENIED" : "RESOURCE_EXHAUSTED",
|
|
218
|
+
message: probe === "quota_exhausted" ? "quota exceeded" : probe === "rate_limit" ? "rate limit exceeded" : "user location is not supported",
|
|
218
219
|
});
|
|
219
220
|
}
|
|
220
|
-
const status = probe === "
|
|
221
|
+
const status = probe === "geo_blocked" ? 403 : 429;
|
|
221
222
|
return passthrough(undefined, status);
|
|
222
223
|
}
|
|
223
224
|
}
|
|
@@ -244,15 +245,15 @@ async function prepareCcaSseResponse(
|
|
|
244
245
|
}
|
|
245
246
|
return failoverOrPassthrough();
|
|
246
247
|
}
|
|
247
|
-
if (probe === "quota_exhausted" || probe === "geo_blocked") {
|
|
248
|
+
if (probe === "quota_exhausted" || probe === "rate_limit" || probe === "geo_blocked") {
|
|
248
249
|
if (accountId) {
|
|
249
250
|
recordAntigravitySyntheticFailure(accountId, {
|
|
250
|
-
code: probe === "
|
|
251
|
-
status: probe === "
|
|
252
|
-
message: probe === "quota_exhausted" ? "quota exceeded" : "user location is not supported",
|
|
251
|
+
code: probe === "geo_blocked" ? 403 : 429,
|
|
252
|
+
status: probe === "geo_blocked" ? "PERMISSION_DENIED" : "RESOURCE_EXHAUSTED",
|
|
253
|
+
message: probe === "quota_exhausted" ? "quota exceeded" : probe === "rate_limit" ? "rate limit exceeded" : "user location is not supported",
|
|
253
254
|
});
|
|
254
255
|
}
|
|
255
|
-
const status = probe === "
|
|
256
|
+
const status = probe === "geo_blocked" ? 403 : 429;
|
|
256
257
|
return passthrough(overflow, status);
|
|
257
258
|
}
|
|
258
259
|
if (probe === "terminal") return passthrough(overflow);
|
package/src/adapters/google.ts
CHANGED
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
} from "../web-search/gemini-executor";
|
|
38
38
|
import type { WebSearchSource } from "../web-search/parse";
|
|
39
39
|
import { googleVertexLocationConfigError } from "../providers/google-vertex-location";
|
|
40
|
-
import { lookupReplayThoughtSignature } from "../responses/thought-signature-replay";
|
|
40
|
+
import { forgetThoughtSignatureForReplay, lookupReplayThoughtSignature } from "../responses/thought-signature-replay";
|
|
41
41
|
import {
|
|
42
42
|
isTranslatorBudgetExceededError,
|
|
43
43
|
releaseTranslatedEvent,
|
|
@@ -48,6 +48,9 @@ import {
|
|
|
48
48
|
import { buildNonOpenAIToolCatalogNudgeForTools } from "./tool-catalog-nudge";
|
|
49
49
|
import { configuredReasoningEfforts, mapReasoningEffort } from "../reasoning-effort";
|
|
50
50
|
import { normalizeAntigravityProviderError } from "../oauth/antigravity-routing";
|
|
51
|
+
import { buildAiStudioHeaders, parseGoogleCookieJar } from "../oauth/google-aistudio-auth";
|
|
52
|
+
import { resolveAiStudioCredentials } from "../oauth/aistudio-credentials";
|
|
53
|
+
import { parseMakerSuiteChunk } from "./google-aistudio-parser";
|
|
51
54
|
|
|
52
55
|
const INLINE_ERROR_URL_USERINFO = /https?:\/\/[^\s"'<>]*@/gi;
|
|
53
56
|
|
|
@@ -71,6 +74,43 @@ const GOOGLE_BREVITY_INSTRUCTION = [
|
|
|
71
74
|
"- This applies only to intermediate progress text. Your final answer after the work is done is exempt: write it in full and at whatever length the task requires.",
|
|
72
75
|
].join("\n");
|
|
73
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Documented output ceiling for a Google-surface model, or `undefined` when the id is not
|
|
79
|
+
* recognized.
|
|
80
|
+
*
|
|
81
|
+
* Unknown ids return `undefined` deliberately. An earlier revision returned a 16,384 floor for
|
|
82
|
+
* anything unmatched, which silently truncated aliases, gateway ids, and any model added after
|
|
83
|
+
* this table was written — the operator asked for N tokens and got 16,384 with no signal. A cap
|
|
84
|
+
* we cannot justify is worse than no cap: `structure/02_config-and-codex-home.md` is explicit
|
|
85
|
+
* that an explicit request value wins, so an unrecognized model passes through untouched and the
|
|
86
|
+
* upstream remains the authority on its own limit.
|
|
87
|
+
*
|
|
88
|
+
* Matching is prefix/family based rather than substring based for the same reason: `includes("pro")`
|
|
89
|
+
* matched any id containing "pro" (`my-prototype-model`), and `includes("oss")` matched any id
|
|
90
|
+
* containing "oss" (`crossover-v2`).
|
|
91
|
+
*/
|
|
92
|
+
export function maxOutputTokensForGoogleModel(modelId: string): number | undefined {
|
|
93
|
+
const lower = modelId.toLowerCase().trim();
|
|
94
|
+
if (lower.startsWith("gemini")) {
|
|
95
|
+
// Pro tops out one token below the flash/other Gemini ceiling; both are documented values.
|
|
96
|
+
return /(^|[-.])pro([-.]|$)/.test(lower) ? 65535 : 65536;
|
|
97
|
+
}
|
|
98
|
+
if (lower.startsWith("claude")) return 64000;
|
|
99
|
+
if (lower.startsWith("gpt-oss")) return 32768;
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function clampGoogleMaxOutputTokens(
|
|
104
|
+
modelId: string,
|
|
105
|
+
requestedTokens?: number,
|
|
106
|
+
): number | undefined {
|
|
107
|
+
if (requestedTokens === undefined || requestedTokens <= 0) return undefined;
|
|
108
|
+
const modelMax = maxOutputTokensForGoogleModel(modelId);
|
|
109
|
+
// Unknown model: honour the request as-is rather than inventing a ceiling for it.
|
|
110
|
+
if (modelMax === undefined) return requestedTokens;
|
|
111
|
+
return Math.min(requestedTokens, modelMax);
|
|
112
|
+
}
|
|
113
|
+
|
|
74
114
|
/**
|
|
75
115
|
* Some Google direct deployments expose current Gemini Flash generations with a `-tiered`
|
|
76
116
|
* wire suffix (`gemini-3.7-flash` -> `gemini-3.7-flash-tiered`). Keep the picker-visible id
|
|
@@ -177,7 +217,7 @@ function geminiTextPart(text: unknown): { text: string } | undefined {
|
|
|
177
217
|
*/
|
|
178
218
|
function geminiToolResultText(content: string | OcxContentPart[]): string {
|
|
179
219
|
if (typeof content === "string") return content || GEMINI_EMPTY_TOOL_OUTPUT_PLACEHOLDER;
|
|
180
|
-
const hasContent = content.some(p => p.type
|
|
220
|
+
const hasContent = content.some(p => p.type !== "text" || p.text.length > 0);
|
|
181
221
|
return hasContent ? contentPartsToText(content) : GEMINI_EMPTY_TOOL_OUTPUT_PLACEHOLDER;
|
|
182
222
|
}
|
|
183
223
|
|
|
@@ -221,7 +261,7 @@ function messagesToGeminiFormat(
|
|
|
221
261
|
parsed: OcxParsedRequest,
|
|
222
262
|
identityModelId: string,
|
|
223
263
|
repairToolPairs: boolean,
|
|
224
|
-
): { systemInstruction?: unknown; contents: unknown[] } {
|
|
264
|
+
): { systemInstruction?: unknown; contents: unknown[]; replayedCallIds: string[] } {
|
|
225
265
|
// Neutralize Codex's GPT-5 identity line (Gemini/Antigravity share this path) so a routed model
|
|
226
266
|
// never misreports as GPT-5/OpenAI, and never leaks the proxy identity upstream.
|
|
227
267
|
const toolCatalogNudge = buildNonOpenAIToolCatalogNudgeForTools(parsed.context.tools, parsed.options.toolChoice);
|
|
@@ -233,6 +273,7 @@ function messagesToGeminiFormat(
|
|
|
233
273
|
const systemInstruction = { parts: [{ text: systemText }] };
|
|
234
274
|
|
|
235
275
|
const contents: unknown[] = [];
|
|
276
|
+
const replayedCallIds: string[] = [];
|
|
236
277
|
let userMergeBarrier = false;
|
|
237
278
|
const appendContent = (next: { role: string; parts: unknown[] }): void => {
|
|
238
279
|
appendGeminiContent(contents, next, !userMergeBarrier);
|
|
@@ -267,6 +308,13 @@ function messagesToGeminiFormat(
|
|
|
267
308
|
parts.push(data ? { inline_data: { mime_type: data.mediaType, data: data.base64 } } : { text: `[image: ${p.imageUrl}]` });
|
|
268
309
|
continue;
|
|
269
310
|
}
|
|
311
|
+
if (p.type === "video") {
|
|
312
|
+
const data = parseDataUrl(p.videoUrl);
|
|
313
|
+
// Gemini accepts inline video bytes in the same Part union as images. Arbitrary
|
|
314
|
+
// remote URLs are not valid fileData references, so retain only a short marker.
|
|
315
|
+
parts.push(data ? { inline_data: { mime_type: data.mediaType, data: data.base64 } } : { text: `[video: ${p.videoUrl}]` });
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
270
318
|
// Drop empty/malformed text instead of emitting `{ text: "" }` or a bare `{}` part.
|
|
271
319
|
const textPart = geminiTextPart(p.text);
|
|
272
320
|
if (textPart) parts.push(textPart);
|
|
@@ -314,7 +362,10 @@ function messagesToGeminiFormat(
|
|
|
314
362
|
const signature = tc.providerMetadata?.google?.thoughtSignature
|
|
315
363
|
?? tc.thoughtSignature
|
|
316
364
|
?? lookupReplayThoughtSignature(tc.id, parsed._reasoningReplayScope);
|
|
317
|
-
if (isLikelyRealThoughtSignature(signature))
|
|
365
|
+
if (isLikelyRealThoughtSignature(signature)) {
|
|
366
|
+
part.thoughtSignature = signature;
|
|
367
|
+
replayedCallIds.push(tc.id);
|
|
368
|
+
}
|
|
318
369
|
parts.push(part);
|
|
319
370
|
}
|
|
320
371
|
}
|
|
@@ -371,7 +422,7 @@ function messagesToGeminiFormat(
|
|
|
371
422
|
}
|
|
372
423
|
}
|
|
373
424
|
|
|
374
|
-
return { systemInstruction, contents };
|
|
425
|
+
return { systemInstruction, contents, replayedCallIds };
|
|
375
426
|
}
|
|
376
427
|
|
|
377
428
|
function toolsToGeminiFormat(
|
|
@@ -540,6 +591,21 @@ function googlePartThoughtSignature(part: GoogleResponsePart): string | undefine
|
|
|
540
591
|
return typeof nested === "string" && nested.length > 0 ? nested : undefined;
|
|
541
592
|
}
|
|
542
593
|
|
|
594
|
+
function ensureThoughtSignatureBypassSentinel(contents: unknown[], modelId?: string): void {
|
|
595
|
+
if (modelId && !/gemini-(?:3.7|2.5)|thinking/i.test(modelId)) return;
|
|
596
|
+
for (const c of contents as { role?: string; parts?: unknown[] }[]) {
|
|
597
|
+
if (c?.role !== "model" || !Array.isArray(c.parts)) continue;
|
|
598
|
+
for (const p of c.parts) {
|
|
599
|
+
if (p && typeof p === "object") {
|
|
600
|
+
const partObj = p as Record<string, unknown>;
|
|
601
|
+
if (partObj.functionCall && !partObj.thoughtSignature && !partObj.thought_signature) {
|
|
602
|
+
partObj.thoughtSignature = ANTIGRAVITY_SIGNATURE_BYPASS_SENTINEL;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
543
609
|
/**
|
|
544
610
|
* Carry a Gemini thought signature with the exact function-call part that produced it. Google
|
|
545
611
|
* validates the signature against that specific part, so it must ride the individual tool call
|
|
@@ -725,6 +791,47 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
725
791
|
const truncationErrorMessage = provider.googleMode === "vertex" || provider.googleMode === "cloud-code-assist"
|
|
726
792
|
? vertexTruncationErrorMessage
|
|
727
793
|
: googleTruncationErrorMessage;
|
|
794
|
+
let lastInjectedCallIds: string[] = [];
|
|
795
|
+
let lastReasoningReplayScope: OcxParsedRequest["_reasoningReplayScope"];
|
|
796
|
+
|
|
797
|
+
// Conservative batch invalidation: upstream Gemini/Antigravity errors (e.g.
|
|
798
|
+
// "Function call is missing a thought_signature in functionCall parts") do not specify which
|
|
799
|
+
// specific call_id was rejected. When a request containing replayed signatures is rejected,
|
|
800
|
+
// we evict all callIds injected in that turn (lastInjectedCallIds) from the durable store
|
|
801
|
+
// and clear the session replay cache, preventing poisoned-signature loops while allowing
|
|
802
|
+
// subsequent turns to re-accumulate valid signatures. Unrelated calls from other turns remain intact.
|
|
803
|
+
//
|
|
804
|
+
// Memory-cache clearing stays broad (any invalid-argument/signature error can poison the
|
|
805
|
+
// session replay cache), but durable-store eviction is intentionally narrower: it only runs
|
|
806
|
+
// when the error text explicitly mentions a signature, so a generic tool-schema
|
|
807
|
+
// INVALID_ARGUMENT does not destroy valid durable signatures.
|
|
808
|
+
function handleSignatureRejection(errorMessage?: string) {
|
|
809
|
+
const replayModel = provider.googleMode === "cloud-code-assist" ? antigravityModel : vertexReplayModel;
|
|
810
|
+
const replaySession = provider.googleMode === "cloud-code-assist" ? antigravitySession : vertexReplaySession;
|
|
811
|
+
const text = errorMessage ?? "";
|
|
812
|
+
const isInvalidArgument = /invalid_argument|invalid argument/i.test(text);
|
|
813
|
+
const isSignatureError = /signature|thought_signature|thoughtSignature/i.test(text);
|
|
814
|
+
// The in-memory Antigravity replay cache only exists for CCA/Vertex, so clearing it stays
|
|
815
|
+
// scoped to those modes (replayModel/replaySession are undefined elsewhere anyway).
|
|
816
|
+
if (
|
|
817
|
+
(provider.googleMode === "cloud-code-assist" || provider.googleMode === "vertex")
|
|
818
|
+
&& replayModel && replaySession && (isInvalidArgument || isSignatureError)
|
|
819
|
+
) {
|
|
820
|
+
clearAntigravityReplay(replayModel, replaySession);
|
|
821
|
+
}
|
|
822
|
+
// The DURABLE store is not mode-scoped: signatures are remembered through
|
|
823
|
+
// rememberAndSerializeExtraContent and read back by lookupReplayThoughtSignature on every
|
|
824
|
+
// Google mode, including AI Studio. Gating eviction on CCA/Vertex therefore left AI Studio
|
|
825
|
+
// with rejected signatures cached forever, replaying them into every subsequent turn — the
|
|
826
|
+
// store poisons itself and the request keeps failing. Eviction follows the same scope the
|
|
827
|
+
// write does.
|
|
828
|
+
if (isSignatureError) {
|
|
829
|
+
for (const callId of lastInjectedCallIds) {
|
|
830
|
+
forgetThoughtSignatureForReplay(callId, lastReasoningReplayScope);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
728
835
|
return {
|
|
729
836
|
name: "google",
|
|
730
837
|
validateRequest(parsed: OcxParsedRequest) {
|
|
@@ -766,11 +873,13 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
766
873
|
: resolveDirectGeminiWireModelId(parsed.modelId, provider.directGeminiWireRenames !== false);
|
|
767
874
|
// AI Studio's `-tiered` spelling is wire-only; CCA aliases may migrate to another generation.
|
|
768
875
|
const identityModelId = provider.googleMode === "cloud-code-assist" ? routedModelId : parsed.modelId;
|
|
769
|
-
const { systemInstruction, contents } = messagesToGeminiFormat(
|
|
876
|
+
const { systemInstruction, contents, replayedCallIds } = messagesToGeminiFormat(
|
|
770
877
|
parsed,
|
|
771
878
|
identityModelId,
|
|
772
879
|
provider.googleMode === "cloud-code-assist",
|
|
773
880
|
);
|
|
881
|
+
lastInjectedCallIds = [...replayedCallIds];
|
|
882
|
+
lastReasoningReplayScope = parsed._reasoningReplayScope;
|
|
774
883
|
const tools = toolsToGeminiFormat(parsed, routedModelId);
|
|
775
884
|
|
|
776
885
|
const body: Record<string, unknown> = { contents };
|
|
@@ -785,7 +894,8 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
785
894
|
if (googleOptions?.cachedContent) body.cachedContent = googleOptions.cachedContent;
|
|
786
895
|
|
|
787
896
|
const generationConfig: Record<string, unknown> = {};
|
|
788
|
-
|
|
897
|
+
const clampedMaxOutputTokens = clampGoogleMaxOutputTokens(identityModelId, parsed.options.maxOutputTokens);
|
|
898
|
+
if (clampedMaxOutputTokens !== undefined) generationConfig.maxOutputTokens = clampedMaxOutputTokens;
|
|
789
899
|
if (parsed.options.temperature !== undefined) generationConfig.temperature = parsed.options.temperature;
|
|
790
900
|
if (parsed.options.topP !== undefined) generationConfig.topP = parsed.options.topP;
|
|
791
901
|
if (parsed.options.stopSequences) generationConfig.stopSequences = parsed.options.stopSequences;
|
|
@@ -882,19 +992,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
882
992
|
const strippedModelTail = /claude/i.test(wireModelId) ? stripTrailingClaudePrefill(contents) : false;
|
|
883
993
|
if (antigravityUsesReplayCache(wireModelId)) {
|
|
884
994
|
applyAntigravityReplay(wireModelId, sessionId, contents);
|
|
885
|
-
|
|
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
|
-
}
|
|
995
|
+
ensureThoughtSignatureBypassSentinel(contents);
|
|
898
996
|
} else {
|
|
899
997
|
sanitizeAntigravityClaudeSignatures(contents);
|
|
900
998
|
}
|
|
@@ -970,6 +1068,23 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
970
1068
|
return { url, method: "POST", headers, body: JSON.stringify(compiled.body) };
|
|
971
1069
|
}
|
|
972
1070
|
|
|
1071
|
+
if (provider.googleMode === "ai-studio-web") {
|
|
1072
|
+
const base = (provider.baseUrl || "https://alkalimakersuite-pa.clients6.google.com").replace(/\/+$/, "");
|
|
1073
|
+
const url = `${base}/v1internal:${method}${streamParam}`;
|
|
1074
|
+
const credentials = resolveAiStudioCredentials(provider);
|
|
1075
|
+
if (credentials.kind !== "ready") throw new Error(credentials.reason);
|
|
1076
|
+
const jar = parseGoogleCookieJar(credentials.cookieHeader);
|
|
1077
|
+
const aiStudioHeaders = await buildAiStudioHeaders(jar, "https://aistudio.google.com");
|
|
1078
|
+
Object.assign(headers, aiStudioHeaders);
|
|
1079
|
+
const compiled = compileGoogleWireBody({ ...body, model: routedModelId });
|
|
1080
|
+
restoreGoogleToolName = compiled.restoreToolName;
|
|
1081
|
+
if (Array.isArray((compiled.body as { contents?: unknown[] }).contents)) {
|
|
1082
|
+
ensureThoughtSignatureBypassSentinel((compiled.body as { contents: unknown[] }).contents, routedModelId);
|
|
1083
|
+
}
|
|
1084
|
+
emitInTurnGroundingSourcesQueue.push(!!parsed._ccaInTurnGrounding);
|
|
1085
|
+
return { url, method: "POST", headers, body: JSON.stringify(compiled.body) };
|
|
1086
|
+
}
|
|
1087
|
+
|
|
973
1088
|
// ai-studio (default): Generative Language API + x-goog-api-key.
|
|
974
1089
|
const url = `${provider.baseUrl}/v1beta/models/${routedModelId}:${method}${streamParam}`;
|
|
975
1090
|
const apiKey = provider.apiKey?.trim();
|
|
@@ -978,6 +1093,9 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
978
1093
|
|
|
979
1094
|
const compiled = compileGoogleWireBody(body);
|
|
980
1095
|
restoreGoogleToolName = compiled.restoreToolName;
|
|
1096
|
+
if (Array.isArray((compiled.body as { contents?: unknown[] }).contents)) {
|
|
1097
|
+
ensureThoughtSignatureBypassSentinel((compiled.body as { contents: unknown[] }).contents, routedModelId);
|
|
1098
|
+
}
|
|
981
1099
|
emitInTurnGroundingSourcesQueue.push(!!parsed._ccaInTurnGrounding);
|
|
982
1100
|
return { url, method: "POST", headers, body: JSON.stringify(compiled.body) };
|
|
983
1101
|
},
|
|
@@ -986,6 +1104,11 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
986
1104
|
const emitInTurnGroundingSources = emitInTurnGroundingSourcesQueue.shift() ?? false;
|
|
987
1105
|
const filterCcaSearchSuggestionHtml =
|
|
988
1106
|
provider.googleMode === "cloud-code-assist" && emitInTurnGroundingSources;
|
|
1107
|
+
if (provider.googleMode === "ai-studio-web" && (response.status === 401 || response.status === 403 || (response.status >= 300 && response.status < 400))) {
|
|
1108
|
+
try { await response.body?.cancel(); } catch { /* ignore */ }
|
|
1109
|
+
yield { type: "error", message: "Google AI Studio session expired — re-authentication required" };
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
989
1112
|
if (!response.body) {
|
|
990
1113
|
yield { type: "error", message: "No response body" };
|
|
991
1114
|
return;
|
|
@@ -998,6 +1121,17 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
998
1121
|
const reader = response.body.getReader();
|
|
999
1122
|
const decoder = new TextDecoder();
|
|
1000
1123
|
const budgetEncoder = new TextEncoder();
|
|
1124
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
1125
|
+
const isHtmlContentType = contentType.toLowerCase().includes("text/html");
|
|
1126
|
+
const isHtmlRedirect = (text: string) => {
|
|
1127
|
+
const lower = text.trim().toLowerCase();
|
|
1128
|
+
return lower.startsWith("<!doctype") || lower.includes("accounts.google.com/v3/signin");
|
|
1129
|
+
};
|
|
1130
|
+
const reauthError = "Google AI Studio session expired — re-authentication required";
|
|
1131
|
+
if (isHtmlContentType) {
|
|
1132
|
+
yield { type: "error", message: reauthError };
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1001
1135
|
let buffer = "";
|
|
1002
1136
|
let bufferBytes = 0;
|
|
1003
1137
|
// Raw unterminated-line bytes, independent of TextDecoder's pending UTF-8
|
|
@@ -1059,15 +1193,9 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1059
1193
|
if (provider.googleMode === "cloud-code-assist" && error) observeProviderError?.(error);
|
|
1060
1194
|
const err = { ...(error ?? {}), message: error?.message ?? safeMessage ?? "upstream error" };
|
|
1061
1195
|
// Clear-on-invalid: a signature rejection means our replayed thoughtSignatures are stale.
|
|
1062
|
-
// Drop the cache entry
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
if ((provider.googleMode === "cloud-code-assist" || provider.googleMode === "vertex")
|
|
1066
|
-
&& replayModel && replaySession
|
|
1067
|
-
&& !/missing.*thought_signature/i.test(err?.message ?? "")
|
|
1068
|
-
&& /signature|invalid_argument|invalid argument/i.test(err?.message ?? "")) {
|
|
1069
|
-
clearAntigravityReplay(replayModel, replaySession);
|
|
1070
|
-
}
|
|
1196
|
+
// Drop the cache entry and durable store entry for rejected calls so the next turn
|
|
1197
|
+
// starts clean instead of re-injecting a bad sig.
|
|
1198
|
+
handleSignatureRejection(err?.message);
|
|
1071
1199
|
yield {
|
|
1072
1200
|
type: "error",
|
|
1073
1201
|
...(error?.status !== undefined ? { status: error.status } : {}),
|
|
@@ -1256,6 +1384,11 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1256
1384
|
}
|
|
1257
1385
|
incompleteLineBytes = lineScan.residual;
|
|
1258
1386
|
const nextBuffer = buffer + decoder.decode(value, { stream: true });
|
|
1387
|
+
if (isHtmlRedirect(nextBuffer)) {
|
|
1388
|
+
yield { type: "error", message: reauthError };
|
|
1389
|
+
try { await reader.cancel(); } catch { /* ignore */ }
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1259
1392
|
const nextBufferBytes = budgetEncoder.encode(nextBuffer).byteLength;
|
|
1260
1393
|
const appendReservation = budget.reserveTransient(nextBufferBytes, { kind: "live_transient" });
|
|
1261
1394
|
buffer = nextBuffer;
|
|
@@ -1280,6 +1413,16 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1280
1413
|
if (result === "content") sawContentEvent = true;
|
|
1281
1414
|
continue;
|
|
1282
1415
|
}
|
|
1416
|
+
if (provider.googleMode === "ai-studio-web") {
|
|
1417
|
+
const parsed = parseMakerSuiteChunk(line);
|
|
1418
|
+
if (parsed.text) {
|
|
1419
|
+
sawAnyFrame = true;
|
|
1420
|
+
sawTerminalSignal = true;
|
|
1421
|
+
sawContentEvent = true;
|
|
1422
|
+
yield { type: "text_delta", text: parsed.text };
|
|
1423
|
+
continue;
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1283
1426
|
sawLiveness = true;
|
|
1284
1427
|
if (line.startsWith(":") || !line.trim()) continue;
|
|
1285
1428
|
debugDroppedFrame("google", line);
|
|
@@ -1292,7 +1435,28 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1292
1435
|
if (residual.startsWith(":")) {
|
|
1293
1436
|
yield { type: "heartbeat" };
|
|
1294
1437
|
} else if (!residual.startsWith("data:")) {
|
|
1295
|
-
|
|
1438
|
+
try {
|
|
1439
|
+
const parsedErr = JSON.parse(residual);
|
|
1440
|
+
if (parsedErr.error?.message) {
|
|
1441
|
+
yield { type: "error", message: parsedErr.error.message };
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
} catch (err) {
|
|
1445
|
+
void err;
|
|
1446
|
+
}
|
|
1447
|
+
if (provider.googleMode === "ai-studio-web") {
|
|
1448
|
+
const parsed = parseMakerSuiteChunk(residual);
|
|
1449
|
+
if (parsed.text) {
|
|
1450
|
+
yield { type: "text_delta", text: parsed.text };
|
|
1451
|
+
yield { type: "done" };
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
if (isHtmlContentType || isHtmlRedirect(residual)) {
|
|
1456
|
+
yield { type: "error", message: reauthError };
|
|
1457
|
+
return;
|
|
1458
|
+
}
|
|
1459
|
+
yield { type: "error", message: `upstream non-SSE response: ${residual.slice(0, 300)}` };
|
|
1296
1460
|
return;
|
|
1297
1461
|
} else if ((yield* handleDataLine(residual)) === "terminate") return;
|
|
1298
1462
|
}
|
|
@@ -1344,7 +1508,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1344
1508
|
// buffered adapter entry point, so collect the exact same events parseStream emits
|
|
1345
1509
|
// instead of maintaining a second CCA JSON parser.
|
|
1346
1510
|
const isSse = response.headers.get("content-type")?.includes("text/event-stream") ?? false;
|
|
1347
|
-
if (provider.googleMode === "cloud-code-assist" && isSse) {
|
|
1511
|
+
if ((provider.googleMode === "cloud-code-assist" && isSse) || provider.googleMode === "ai-studio-web") {
|
|
1348
1512
|
const events: AdapterEvent[] = [];
|
|
1349
1513
|
let previousTail: AdapterEvent | undefined;
|
|
1350
1514
|
try {
|
|
@@ -1417,7 +1581,18 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1417
1581
|
let raw: Record<string, unknown>;
|
|
1418
1582
|
let rawBytes = 0;
|
|
1419
1583
|
try {
|
|
1420
|
-
|
|
1584
|
+
const parsedRaw: unknown = JSON.parse(rawText);
|
|
1585
|
+
// `JSON.parse("null")` returns null instead of throwing, so the catch below cannot see it
|
|
1586
|
+
// and the `raw.error` read crashed the turn — #1219 at the buffered body root, which #1240
|
|
1587
|
+
// never reached because that audit swept SSE frame parsers only. There is no next frame to
|
|
1588
|
+
// recover into here, so unlike a stream frame this fails closed, matching the
|
|
1589
|
+
// unparseable-body branch just below and the buffered candidate guards added in #2232.
|
|
1590
|
+
if (!isGoogleRecord(parsedRaw)) {
|
|
1591
|
+
budget.releaseRetained(rawTextBytes, { kind: "retained_collectors" });
|
|
1592
|
+
const valueType = googleStructuralValueType(parsedRaw);
|
|
1593
|
+
return [{ type: "error", message: `google response was not a JSON object (${valueType})` }];
|
|
1594
|
+
}
|
|
1595
|
+
raw = parsedRaw;
|
|
1421
1596
|
rawBytes = new TextEncoder().encode(JSON.stringify(raw)).byteLength;
|
|
1422
1597
|
const rawReservation = budget.reserveTransient(rawBytes, { kind: "retained_collectors" });
|
|
1423
1598
|
rawReservation.commitRetained();
|
|
@@ -1441,16 +1616,24 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1441
1616
|
message: safeMessage,
|
|
1442
1617
|
});
|
|
1443
1618
|
if (error) observeProviderError?.(error);
|
|
1619
|
+
const message = error?.message ?? safeMessage ?? "upstream error";
|
|
1620
|
+
handleSignatureRejection(message);
|
|
1444
1621
|
return finish([{
|
|
1445
1622
|
type: "error",
|
|
1446
1623
|
...(error?.status !== undefined ? { status: error.status } : {}),
|
|
1447
1624
|
...(error?.code ? { code: error.code } : {}),
|
|
1448
|
-
message
|
|
1625
|
+
message,
|
|
1449
1626
|
}]);
|
|
1450
1627
|
}
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1628
|
+
// Antigravity (CCA) nests the standard Gemini payload under `response`; unwrap it.
|
|
1629
|
+
let json = raw;
|
|
1630
|
+
if (provider.googleMode === "cloud-code-assist") {
|
|
1631
|
+
const wrapped = raw.response;
|
|
1632
|
+
if (!wrapped || typeof wrapped !== "object" || Array.isArray(wrapped)) {
|
|
1633
|
+
return finish([{ type: "error", message: "google-antigravity response missing response wrapper" }]);
|
|
1634
|
+
}
|
|
1635
|
+
json = wrapped as Record<string, unknown>;
|
|
1636
|
+
}
|
|
1454
1637
|
const events: AdapterEvent[] = [];
|
|
1455
1638
|
|
|
1456
1639
|
const rawCandidates: unknown = json.candidates;
|
package/src/adapters/image.ts
CHANGED
|
@@ -18,6 +18,6 @@ export function parseDataUrl(url: string): { mediaType: string; base64: string }
|
|
|
18
18
|
*/
|
|
19
19
|
export function contentPartsToText(content: string | OcxContentPart[]): string {
|
|
20
20
|
if (typeof content === "string") return content;
|
|
21
|
-
const text = content.map(p =>
|
|
21
|
+
const text = content.map(p => p.type === "text" ? p.text : p.type === "image" ? "[image]" : "[video]").join("");
|
|
22
22
|
return text || "[image]";
|
|
23
23
|
}
|
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
export const KIRO_COMPLETION_TOOL_NAME = "codex_kiro_final_answer";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Request-scoped CodeWhisperer service profile for AWS Builder ID accounts.
|
|
5
|
+
*
|
|
6
|
+
* Builder ID is a personal identity with no AWS account behind it, so AWS never mints an
|
|
7
|
+
* account-scoped `profile/<id>` ARN for it. The Kiro CLI resolves this the same way: it carries
|
|
8
|
+
* this fixed service profile on Builder ID requests. The embedded account id is Amazon's own, not
|
|
9
|
+
* the user's, which is why sending it is not the same as synthesizing an account identity.
|
|
10
|
+
*
|
|
11
|
+
* Request-scoped is load-bearing. This value must never be persisted into `KiroOAuthMetadata`,
|
|
12
|
+
* never seed region inference (it is `us-east-1` and would pin every Builder ID account there),
|
|
13
|
+
* and never participate in account matching.
|
|
14
|
+
*/
|
|
15
|
+
export const KIRO_BUILDER_ID_SERVICE_PROFILE_ARN =
|
|
16
|
+
"arn:aws:codewhisperer:us-east-1:638616132270:profile/AAAACCCCXXXX";
|
|
2
17
|
export const KIRO_CONTINUATION_MESSAGE =
|
|
3
18
|
"Continue from the prior conversation. Do not quote or mention this instruction.";
|
|
4
19
|
export const KIRO_COMPLETION_RETRY_MESSAGE =
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { OcxParsedRequest, OcxTool } from "../types";
|
|
2
2
|
import { namespacedToolName } from "../types";
|
|
3
3
|
import { normalizeKiroModelId } from "../providers/kiro-models";
|
|
4
|
+
import { isCodexCodeModeExecTool } from "./tool-catalog-nudge";
|
|
4
5
|
import { createKiroToolNameRegistry, type KiroToolNameRegistry } from "./kiro-wire";
|
|
5
6
|
|
|
6
7
|
const MAX_KIRO_TOOL_DESCRIPTION_UNVERIFIED = 1024;
|
|
@@ -103,7 +104,7 @@ function ensureRootObjectType(schema: unknown): Record<string, unknown> {
|
|
|
103
104
|
// Seed with the root's own properties/required so a schema like
|
|
104
105
|
// { type:"object", properties:{path}, required:["path"], oneOf:[...] } keeps them.
|
|
105
106
|
if (obj.properties && typeof obj.properties === "object") {
|
|
106
|
-
Object.assign(props,
|
|
107
|
+
Object.assign(props, sanitizeSchemaMap(obj.properties) as Record<string, unknown>);
|
|
107
108
|
}
|
|
108
109
|
if (Array.isArray(obj.required)) {
|
|
109
110
|
for (const r of obj.required) if (typeof r === "string") required.add(r);
|
|
@@ -118,7 +119,7 @@ function ensureRootObjectType(schema: unknown): Record<string, unknown> {
|
|
|
118
119
|
if (!variant || typeof variant !== "object" || Array.isArray(variant)) continue;
|
|
119
120
|
const v = variant as Record<string, unknown>;
|
|
120
121
|
if (v.properties && typeof v.properties === "object") {
|
|
121
|
-
Object.assign(props,
|
|
122
|
+
Object.assign(props, sanitizeSchemaMap(v.properties) as Record<string, unknown>);
|
|
122
123
|
}
|
|
123
124
|
if (mergeRequired && Array.isArray(v.required)) {
|
|
124
125
|
for (const r of v.required) if (typeof r === "string") required.add(r);
|
|
@@ -174,8 +175,14 @@ function omittedToolCatalogNotice(kept: number, omitted: readonly OcxTool[], reg
|
|
|
174
175
|
|
|
175
176
|
function boundedCatalogPriority(tool: OcxTool): number {
|
|
176
177
|
if (tool.loadedFromToolSearch) return 0;
|
|
177
|
-
|
|
178
|
-
|
|
178
|
+
// Codex code mode reaches shell, file edits, apply_patch and every MCP helper ONLY as nested
|
|
179
|
+
// `tools.<name>(...)` calls inside this one tool. Dropping it does not shrink the catalog, it
|
|
180
|
+
// makes the rest of the catalog uncallable -- so it outranks the search gateway and filler.
|
|
181
|
+
// It stays BEHIND `loadedFromToolSearch` because those are tools the model asked for by name
|
|
182
|
+
// this turn (#2475). Cursor pins its execution path the same way (request-builder.ts, #399).
|
|
183
|
+
if (isCodexCodeModeExecTool(tool)) return 1;
|
|
184
|
+
if (tool.toolSearch) return 2;
|
|
185
|
+
return 3;
|
|
179
186
|
}
|
|
180
187
|
|
|
181
188
|
export function convertKiroToolContext(
|
|
@@ -209,19 +216,40 @@ export function convertKiroToolContext(
|
|
|
209
216
|
const candidates = exceedsBudget
|
|
210
217
|
? convertedEntries.toSorted((a, b) => boundedCatalogPriority(a.tool) - boundedCatalogPriority(b.tool) || a.index - b.index)
|
|
211
218
|
: convertedEntries;
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
219
|
+
// Reserve a seat for the code-mode execution path before filling the rest.
|
|
220
|
+
//
|
|
221
|
+
// Priority alone cannot save it. `loadedFromToolSearch` tools outrank it and arrive unbounded
|
|
222
|
+
// (the Responses parser pushes every `tool_search_output` spec), so a session that accumulated
|
|
223
|
+
// MAX_KIRO_TOOL_COUNT loaded tools would exhaust the budget before reaching tier 1 and drop the
|
|
224
|
+
// one tool through which all of them are actually callable.
|
|
225
|
+
//
|
|
226
|
+
// Reservation rather than eviction: this lowers the room the fill loop sees, so it admits one
|
|
227
|
+
// fewer tool. It never removes a tool that already fit, which is what keeps #2475's loaded-result
|
|
228
|
+
// guarantee intact -- Cursor's `evictNonExecutionPath` exempts only the execution path and could
|
|
229
|
+
// evict a loaded tool instead.
|
|
230
|
+
const reserved = candidates.find(entry => isCodexCodeModeExecTool(entry.tool));
|
|
231
|
+
const admitted = new Set<number>();
|
|
232
|
+
const filled: unknown[] = [];
|
|
233
|
+
for (const entry of candidates) {
|
|
234
|
+
if (entry === reserved) continue;
|
|
235
|
+
// Measure the projected FINAL array: the byte budget is computed over the serialized array, so
|
|
236
|
+
// subtracting a standalone size would misjudge it by the separators JSON adds between entries.
|
|
237
|
+
const projected = reserved ? [...filled, entry.converted, reserved.converted] : [...filled, entry.converted];
|
|
215
238
|
if (
|
|
216
|
-
|
|
217
|
-
|| serializedToolCatalogBytes(
|
|
218
|
-
)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
convertedTools.push(entry.converted);
|
|
239
|
+
projected.length > MAX_KIRO_TOOL_COUNT
|
|
240
|
+
|| serializedToolCatalogBytes(projected) > MAX_KIRO_TOOL_CATALOG_BYTES
|
|
241
|
+
) break;
|
|
242
|
+
filled.push(entry.converted);
|
|
243
|
+
admitted.add(entry.index);
|
|
223
244
|
}
|
|
224
|
-
|
|
245
|
+
if (reserved) admitted.add(reserved.index);
|
|
246
|
+
// Rebuild in sorted-candidate order so the wire order stays loaded -> exec -> gateway -> filler.
|
|
247
|
+
// Pushing the reserved entry after the loop would place it last instead.
|
|
248
|
+
const convertedTools = candidates.filter(entry => admitted.has(entry.index)).map(entry => entry.converted);
|
|
249
|
+
// Derive omissions by set difference. The old `candidates.slice(omittedAt)` assumed every
|
|
250
|
+
// candidate after the first rejection was omitted, which stops being true once one of them was
|
|
251
|
+
// reserved and admitted: the notice would name `exec` unavailable while it is on the wire.
|
|
252
|
+
const omittedTools = candidates.filter(entry => !admitted.has(entry.index)).map(entry => entry.tool);
|
|
225
253
|
return {
|
|
226
254
|
tools: convertedTools,
|
|
227
255
|
systemAdditions: omittedTools.length > 0 ? [omittedToolCatalogNotice(convertedTools.length, omittedTools, registry)] : [],
|