@yansigit/opencodex 2.33.1 → 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 -3
- package/src/adapters/anthropic.ts +79 -2
- package/src/adapters/command-code.ts +40 -3
- 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 +9 -9
- package/src/adapters/cursor/live-models.ts +7 -2
- package/src/adapters/cursor/live-transport.ts +14 -0
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-shell.ts +103 -6
- package/src/adapters/cursor/native-exec.ts +20 -0
- package/src/adapters/cursor/protobuf-request.ts +74 -39
- package/src/adapters/cursor/request-builder.ts +32 -10
- package/src/adapters/cursor/tool-definitions.ts +13 -10
- package/src/adapters/cursor/tool-result-normalize.ts +35 -3
- package/src/adapters/cursor/types.ts +16 -4
- package/src/adapters/cursor.ts +97 -70
- 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 +156 -65
- 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 +334 -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 +14 -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 +5 -0
- 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 -37
- 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 +229 -153
- 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 +54 -0
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/index.ts +57 -5
- package/src/oauth/key-providers.ts +10 -1
- package/src/oauth/kiro.ts +45 -0
- package/src/oauth/login-cli.ts +19 -20
- 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 +28 -0
- 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 +73 -14
- package/src/providers/registry.ts +160 -68
- 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 +40 -2
- package/src/routing/analytics.ts +1 -0
- package/src/routing/quota.ts +10 -0
- package/src/server/auth-cors.ts +23 -0
- package/src/server/chat-completions.ts +24 -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 +152 -80
- 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 +1 -1
- package/src/server/management/model-routes.ts +266 -0
- 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/relay-eager.ts +147 -20
- package/src/server/relay.ts +251 -19
- package/src/server/request-log-conversation.ts +21 -0
- package/src/server/request-log.ts +46 -20
- package/src/server/responses/collaboration.ts +42 -5
- package/src/server/responses/combo-stream-preflight.ts +10 -3
- package/src/server/responses/core.ts +571 -137
- 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/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 +15 -4
- 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 +77 -0
- package/src/types/request.ts +16 -10
- package/src/types/tools.ts +20 -25
- package/src/types.ts +1 -0
- package/src/usage/expected-prices.ts +43 -24
- package/src/usage/log.ts +17 -7
- package/src/web-search/exa-executor.ts +40 -9
- package/src/web-search/loop.ts +5 -4
- package/gui/dist/assets/index-CIDo4y4k.js +0 -102
- package/gui/dist/assets/index-DrSQdTRd.css +0 -1
- package/src/server/aistudio-ws-hub.ts +0 -295
|
@@ -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,
|
|
@@ -49,9 +49,8 @@ import { buildNonOpenAIToolCatalogNudgeForTools } from "./tool-catalog-nudge";
|
|
|
49
49
|
import { configuredReasoningEfforts, mapReasoningEffort } from "../reasoning-effort";
|
|
50
50
|
import { normalizeAntigravityProviderError } from "../oauth/antigravity-routing";
|
|
51
51
|
import { buildAiStudioHeaders, parseGoogleCookieJar } from "../oauth/google-aistudio-auth";
|
|
52
|
-
import {
|
|
52
|
+
import { resolveAiStudioCredentials } from "../oauth/aistudio-credentials";
|
|
53
53
|
import { parseMakerSuiteChunk } from "./google-aistudio-parser";
|
|
54
|
-
import { globalAiStudioRelayHub } from "../server/aistudio-ws-hub";
|
|
55
54
|
|
|
56
55
|
const INLINE_ERROR_URL_USERINFO = /https?:\/\/[^\s"'<>]*@/gi;
|
|
57
56
|
|
|
@@ -75,6 +74,43 @@ const GOOGLE_BREVITY_INSTRUCTION = [
|
|
|
75
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.",
|
|
76
75
|
].join("\n");
|
|
77
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
|
+
|
|
78
114
|
/**
|
|
79
115
|
* Some Google direct deployments expose current Gemini Flash generations with a `-tiered`
|
|
80
116
|
* wire suffix (`gemini-3.7-flash` -> `gemini-3.7-flash-tiered`). Keep the picker-visible id
|
|
@@ -181,7 +217,7 @@ function geminiTextPart(text: unknown): { text: string } | undefined {
|
|
|
181
217
|
*/
|
|
182
218
|
function geminiToolResultText(content: string | OcxContentPart[]): string {
|
|
183
219
|
if (typeof content === "string") return content || GEMINI_EMPTY_TOOL_OUTPUT_PLACEHOLDER;
|
|
184
|
-
const hasContent = content.some(p => p.type
|
|
220
|
+
const hasContent = content.some(p => p.type !== "text" || p.text.length > 0);
|
|
185
221
|
return hasContent ? contentPartsToText(content) : GEMINI_EMPTY_TOOL_OUTPUT_PLACEHOLDER;
|
|
186
222
|
}
|
|
187
223
|
|
|
@@ -225,7 +261,7 @@ function messagesToGeminiFormat(
|
|
|
225
261
|
parsed: OcxParsedRequest,
|
|
226
262
|
identityModelId: string,
|
|
227
263
|
repairToolPairs: boolean,
|
|
228
|
-
): { systemInstruction?: unknown; contents: unknown[] } {
|
|
264
|
+
): { systemInstruction?: unknown; contents: unknown[]; replayedCallIds: string[] } {
|
|
229
265
|
// Neutralize Codex's GPT-5 identity line (Gemini/Antigravity share this path) so a routed model
|
|
230
266
|
// never misreports as GPT-5/OpenAI, and never leaks the proxy identity upstream.
|
|
231
267
|
const toolCatalogNudge = buildNonOpenAIToolCatalogNudgeForTools(parsed.context.tools, parsed.options.toolChoice);
|
|
@@ -237,6 +273,7 @@ function messagesToGeminiFormat(
|
|
|
237
273
|
const systemInstruction = { parts: [{ text: systemText }] };
|
|
238
274
|
|
|
239
275
|
const contents: unknown[] = [];
|
|
276
|
+
const replayedCallIds: string[] = [];
|
|
240
277
|
let userMergeBarrier = false;
|
|
241
278
|
const appendContent = (next: { role: string; parts: unknown[] }): void => {
|
|
242
279
|
appendGeminiContent(contents, next, !userMergeBarrier);
|
|
@@ -271,6 +308,13 @@ function messagesToGeminiFormat(
|
|
|
271
308
|
parts.push(data ? { inline_data: { mime_type: data.mediaType, data: data.base64 } } : { text: `[image: ${p.imageUrl}]` });
|
|
272
309
|
continue;
|
|
273
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
|
+
}
|
|
274
318
|
// Drop empty/malformed text instead of emitting `{ text: "" }` or a bare `{}` part.
|
|
275
319
|
const textPart = geminiTextPart(p.text);
|
|
276
320
|
if (textPart) parts.push(textPart);
|
|
@@ -318,7 +362,10 @@ function messagesToGeminiFormat(
|
|
|
318
362
|
const signature = tc.providerMetadata?.google?.thoughtSignature
|
|
319
363
|
?? tc.thoughtSignature
|
|
320
364
|
?? lookupReplayThoughtSignature(tc.id, parsed._reasoningReplayScope);
|
|
321
|
-
if (isLikelyRealThoughtSignature(signature))
|
|
365
|
+
if (isLikelyRealThoughtSignature(signature)) {
|
|
366
|
+
part.thoughtSignature = signature;
|
|
367
|
+
replayedCallIds.push(tc.id);
|
|
368
|
+
}
|
|
322
369
|
parts.push(part);
|
|
323
370
|
}
|
|
324
371
|
}
|
|
@@ -375,7 +422,7 @@ function messagesToGeminiFormat(
|
|
|
375
422
|
}
|
|
376
423
|
}
|
|
377
424
|
|
|
378
|
-
return { systemInstruction, contents };
|
|
425
|
+
return { systemInstruction, contents, replayedCallIds };
|
|
379
426
|
}
|
|
380
427
|
|
|
381
428
|
function toolsToGeminiFormat(
|
|
@@ -744,6 +791,47 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
744
791
|
const truncationErrorMessage = provider.googleMode === "vertex" || provider.googleMode === "cloud-code-assist"
|
|
745
792
|
? vertexTruncationErrorMessage
|
|
746
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
|
+
|
|
747
835
|
return {
|
|
748
836
|
name: "google",
|
|
749
837
|
validateRequest(parsed: OcxParsedRequest) {
|
|
@@ -763,46 +851,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
763
851
|
// Direct AI-Studio uses the canonical server transport (fetchWithTransientRetry), which
|
|
764
852
|
// retries transient 5xx responses through providerFetch while preserving multi-key pool
|
|
765
853
|
// 429 rotation and raw error formatting.
|
|
766
|
-
...(provider.googleMode === "
|
|
767
|
-
? {
|
|
768
|
-
fetchResponse: async (request: AdapterRequest, ctx?: AdapterFetchContext): Promise<Response> => {
|
|
769
|
-
if (globalAiStudioRelayHub.hasActiveSessions()) {
|
|
770
|
-
const streamRes = await globalAiStudioRelayHub.dispatchStream(
|
|
771
|
-
{
|
|
772
|
-
url: request.url,
|
|
773
|
-
method: request.method,
|
|
774
|
-
headers: request.headers,
|
|
775
|
-
body: request.body,
|
|
776
|
-
},
|
|
777
|
-
ctx?.abortSignal,
|
|
778
|
-
);
|
|
779
|
-
const encoder = new TextEncoder();
|
|
780
|
-
const bodyStream = new ReadableStream({
|
|
781
|
-
async start(controller) {
|
|
782
|
-
try {
|
|
783
|
-
for await (const chunk of streamRes.chunks) {
|
|
784
|
-
controller.enqueue(encoder.encode(chunk));
|
|
785
|
-
}
|
|
786
|
-
controller.close();
|
|
787
|
-
} catch (err) {
|
|
788
|
-
controller.error(err);
|
|
789
|
-
}
|
|
790
|
-
},
|
|
791
|
-
});
|
|
792
|
-
return new Response(bodyStream, {
|
|
793
|
-
status: 200,
|
|
794
|
-
headers: { "Content-Type": "text/event-stream" },
|
|
795
|
-
});
|
|
796
|
-
}
|
|
797
|
-
return fetch(request.url, {
|
|
798
|
-
method: request.method,
|
|
799
|
-
headers: request.headers,
|
|
800
|
-
body: request.body,
|
|
801
|
-
signal: ctx?.abortSignal,
|
|
802
|
-
});
|
|
803
|
-
},
|
|
804
|
-
}
|
|
805
|
-
: provider.googleMode === "vertex" || provider.googleMode === "cloud-code-assist"
|
|
854
|
+
...(provider.googleMode === "vertex" || provider.googleMode === "cloud-code-assist"
|
|
806
855
|
? {
|
|
807
856
|
fetchResponse: (request: AdapterRequest, ctx?: AdapterFetchContext): Promise<Response> =>
|
|
808
857
|
(provider.googleMode === "cloud-code-assist" ? fetchAntigravityWithRetry : fetchVertexWithRetry)(request, ctx),
|
|
@@ -824,11 +873,13 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
824
873
|
: resolveDirectGeminiWireModelId(parsed.modelId, provider.directGeminiWireRenames !== false);
|
|
825
874
|
// AI Studio's `-tiered` spelling is wire-only; CCA aliases may migrate to another generation.
|
|
826
875
|
const identityModelId = provider.googleMode === "cloud-code-assist" ? routedModelId : parsed.modelId;
|
|
827
|
-
const { systemInstruction, contents } = messagesToGeminiFormat(
|
|
876
|
+
const { systemInstruction, contents, replayedCallIds } = messagesToGeminiFormat(
|
|
828
877
|
parsed,
|
|
829
878
|
identityModelId,
|
|
830
879
|
provider.googleMode === "cloud-code-assist",
|
|
831
880
|
);
|
|
881
|
+
lastInjectedCallIds = [...replayedCallIds];
|
|
882
|
+
lastReasoningReplayScope = parsed._reasoningReplayScope;
|
|
832
883
|
const tools = toolsToGeminiFormat(parsed, routedModelId);
|
|
833
884
|
|
|
834
885
|
const body: Record<string, unknown> = { contents };
|
|
@@ -843,7 +894,8 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
843
894
|
if (googleOptions?.cachedContent) body.cachedContent = googleOptions.cachedContent;
|
|
844
895
|
|
|
845
896
|
const generationConfig: Record<string, unknown> = {};
|
|
846
|
-
|
|
897
|
+
const clampedMaxOutputTokens = clampGoogleMaxOutputTokens(identityModelId, parsed.options.maxOutputTokens);
|
|
898
|
+
if (clampedMaxOutputTokens !== undefined) generationConfig.maxOutputTokens = clampedMaxOutputTokens;
|
|
847
899
|
if (parsed.options.temperature !== undefined) generationConfig.temperature = parsed.options.temperature;
|
|
848
900
|
if (parsed.options.topP !== undefined) generationConfig.topP = parsed.options.topP;
|
|
849
901
|
if (parsed.options.stopSequences) generationConfig.stopSequences = parsed.options.stopSequences;
|
|
@@ -1019,8 +1071,9 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1019
1071
|
if (provider.googleMode === "ai-studio-web") {
|
|
1020
1072
|
const base = (provider.baseUrl || "https://alkalimakersuite-pa.clients6.google.com").replace(/\/+$/, "");
|
|
1021
1073
|
const url = `${base}/v1internal:${method}${streamParam}`;
|
|
1022
|
-
const
|
|
1023
|
-
|
|
1074
|
+
const credentials = resolveAiStudioCredentials(provider);
|
|
1075
|
+
if (credentials.kind !== "ready") throw new Error(credentials.reason);
|
|
1076
|
+
const jar = parseGoogleCookieJar(credentials.cookieHeader);
|
|
1024
1077
|
const aiStudioHeaders = await buildAiStudioHeaders(jar, "https://aistudio.google.com");
|
|
1025
1078
|
Object.assign(headers, aiStudioHeaders);
|
|
1026
1079
|
const compiled = compileGoogleWireBody({ ...body, model: routedModelId });
|
|
@@ -1051,6 +1104,11 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1051
1104
|
const emitInTurnGroundingSources = emitInTurnGroundingSourcesQueue.shift() ?? false;
|
|
1052
1105
|
const filterCcaSearchSuggestionHtml =
|
|
1053
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
|
+
}
|
|
1054
1112
|
if (!response.body) {
|
|
1055
1113
|
yield { type: "error", message: "No response body" };
|
|
1056
1114
|
return;
|
|
@@ -1063,6 +1121,17 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1063
1121
|
const reader = response.body.getReader();
|
|
1064
1122
|
const decoder = new TextDecoder();
|
|
1065
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
|
+
}
|
|
1066
1135
|
let buffer = "";
|
|
1067
1136
|
let bufferBytes = 0;
|
|
1068
1137
|
// Raw unterminated-line bytes, independent of TextDecoder's pending UTF-8
|
|
@@ -1124,15 +1193,9 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1124
1193
|
if (provider.googleMode === "cloud-code-assist" && error) observeProviderError?.(error);
|
|
1125
1194
|
const err = { ...(error ?? {}), message: error?.message ?? safeMessage ?? "upstream error" };
|
|
1126
1195
|
// Clear-on-invalid: a signature rejection means our replayed thoughtSignatures are stale.
|
|
1127
|
-
// Drop the cache entry
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
if ((provider.googleMode === "cloud-code-assist" || provider.googleMode === "vertex")
|
|
1131
|
-
&& replayModel && replaySession
|
|
1132
|
-
&& !/missing.*thought_signature/i.test(err?.message ?? "")
|
|
1133
|
-
&& /signature|invalid_argument|invalid argument/i.test(err?.message ?? "")) {
|
|
1134
|
-
clearAntigravityReplay(replayModel, replaySession);
|
|
1135
|
-
}
|
|
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);
|
|
1136
1199
|
yield {
|
|
1137
1200
|
type: "error",
|
|
1138
1201
|
...(error?.status !== undefined ? { status: error.status } : {}),
|
|
@@ -1321,6 +1384,11 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1321
1384
|
}
|
|
1322
1385
|
incompleteLineBytes = lineScan.residual;
|
|
1323
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
|
+
}
|
|
1324
1392
|
const nextBufferBytes = budgetEncoder.encode(nextBuffer).byteLength;
|
|
1325
1393
|
const appendReservation = budget.reserveTransient(nextBufferBytes, { kind: "live_transient" });
|
|
1326
1394
|
buffer = nextBuffer;
|
|
@@ -1384,6 +1452,10 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1384
1452
|
return;
|
|
1385
1453
|
}
|
|
1386
1454
|
}
|
|
1455
|
+
if (isHtmlContentType || isHtmlRedirect(residual)) {
|
|
1456
|
+
yield { type: "error", message: reauthError };
|
|
1457
|
+
return;
|
|
1458
|
+
}
|
|
1387
1459
|
yield { type: "error", message: `upstream non-SSE response: ${residual.slice(0, 300)}` };
|
|
1388
1460
|
return;
|
|
1389
1461
|
} else if ((yield* handleDataLine(residual)) === "terminate") return;
|
|
@@ -1509,7 +1581,18 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1509
1581
|
let raw: Record<string, unknown>;
|
|
1510
1582
|
let rawBytes = 0;
|
|
1511
1583
|
try {
|
|
1512
|
-
|
|
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;
|
|
1513
1596
|
rawBytes = new TextEncoder().encode(JSON.stringify(raw)).byteLength;
|
|
1514
1597
|
const rawReservation = budget.reserveTransient(rawBytes, { kind: "retained_collectors" });
|
|
1515
1598
|
rawReservation.commitRetained();
|
|
@@ -1533,16 +1616,24 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1533
1616
|
message: safeMessage,
|
|
1534
1617
|
});
|
|
1535
1618
|
if (error) observeProviderError?.(error);
|
|
1619
|
+
const message = error?.message ?? safeMessage ?? "upstream error";
|
|
1620
|
+
handleSignatureRejection(message);
|
|
1536
1621
|
return finish([{
|
|
1537
1622
|
type: "error",
|
|
1538
1623
|
...(error?.status !== undefined ? { status: error.status } : {}),
|
|
1539
1624
|
...(error?.code ? { code: error.code } : {}),
|
|
1540
|
-
message
|
|
1625
|
+
message,
|
|
1541
1626
|
}]);
|
|
1542
1627
|
}
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
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
|
+
}
|
|
1546
1637
|
const events: AdapterEvent[] = [];
|
|
1547
1638
|
|
|
1548
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)] : [],
|