@bitkyc08/opencodex 2.8.0 → 2.9.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 +24 -0
- package/bin/ocx.mjs +32 -4
- package/gui/dist/assets/index-CHwf3tTD.css +1 -0
- package/gui/dist/assets/index-u5eFOv2y.js +67 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic-image-normalize.ts +114 -20
- package/src/adapters/anthropic.ts +126 -10
- package/src/adapters/azure.ts +3 -3
- package/src/adapters/base.ts +7 -3
- package/src/adapters/cursor/discovery.ts +14 -4
- package/src/adapters/cursor/effort-map.ts +18 -6
- package/src/adapters/cursor/framing.ts +102 -27
- package/src/adapters/cursor/kv-store.ts +30 -3
- package/src/adapters/cursor/live-models.ts +22 -2
- package/src/adapters/cursor/live-transport.ts +245 -49
- package/src/adapters/cursor/mcp-manager.ts +105 -8
- package/src/adapters/cursor/native-exec-mcp.ts +5 -3
- package/src/adapters/cursor/native-exec-shell.ts +296 -14
- package/src/adapters/cursor/native-exec.ts +381 -33
- package/src/adapters/cursor/protobuf-events.ts +28 -1
- package/src/adapters/cursor/protobuf-request.ts +71 -39
- package/src/adapters/cursor/request-builder.ts +2 -2
- package/src/adapters/cursor/transport.ts +2 -0
- package/src/adapters/cursor.ts +13 -2
- package/src/adapters/google-antigravity-replay.ts +184 -17
- package/src/adapters/google.ts +58 -8
- package/src/adapters/kiro-thinking.ts +23 -9
- package/src/adapters/kiro-tools.ts +49 -18
- package/src/adapters/kiro.ts +377 -133
- package/src/adapters/mimo-free.ts +36 -4
- package/src/adapters/openai-chat.ts +143 -17
- package/src/adapters/openai-responses.ts +130 -14
- package/src/adapters/run-turn-queue.ts +7 -1
- package/src/bridge.ts +466 -69
- package/src/chat/outbound.ts +144 -38
- package/src/claude/inbound-debug.ts +53 -8
- package/src/claude/outbound.ts +224 -38
- package/src/cli/agent-driven.ts +34 -1
- package/src/cli/catalog-prewarm.ts +5 -2
- package/src/cli/claude-desktop.ts +2 -2
- package/src/cli/doctor.ts +12 -0
- package/src/cli/export-command.ts +187 -0
- package/src/cli/help.ts +11 -0
- package/src/cli/index.ts +13 -3
- package/src/cli/init.ts +129 -102
- package/src/cli/opencode.ts +36 -151
- package/src/cli/star-prompt.ts +13 -4
- package/src/cli/status-oauth.ts +12 -2
- package/src/clients/config-export.ts +377 -0
- package/src/codex/account-runtime-state.ts +19 -1
- package/src/codex/account-store.ts +162 -82
- package/src/codex/auth-api.ts +467 -159
- package/src/codex/auth-context.ts +15 -2
- package/src/codex/catalog/aggregation.ts +15 -0
- package/src/codex/catalog/effort.ts +16 -6
- package/src/codex/catalog/metadata.ts +6 -0
- package/src/codex/catalog/parsing.ts +3 -1
- package/src/codex/catalog/provider-fetch.ts +29 -0
- package/src/codex/catalog/sync.ts +64 -7
- package/src/codex/catalog.ts +2 -2
- package/src/codex/inject.ts +5 -5
- package/src/codex/main-account-cache.ts +8 -1
- package/src/codex/model-cache.ts +81 -2
- package/src/codex/pool-rotation.ts +39 -0
- package/src/codex/project-config-warnings.ts +12 -1
- package/src/codex/quota.ts +35 -3
- package/src/codex/routing.ts +46 -1
- package/src/codex/shim.ts +10 -4
- package/src/codex/subagent-model-fallback.ts +12 -0
- package/src/codex/websocket-registry.ts +27 -0
- package/src/combos/failover.ts +31 -1
- package/src/combos/request.ts +9 -0
- package/src/combos/resolve.ts +60 -4
- package/src/combos/types.ts +12 -0
- package/src/config.ts +510 -55
- package/src/github/star-state.ts +13 -1
- package/src/images/fulfill.ts +39 -1
- package/src/images/loop.ts +52 -12
- package/src/lib/admission.ts +83 -0
- package/src/lib/app-owned-memory-stores.ts +173 -0
- package/src/lib/app-owned-memory.ts +265 -0
- package/src/lib/bun-stream-caps.ts +31 -7
- package/src/lib/config-ownership.ts +33 -0
- package/src/lib/crash-guard.ts +65 -5
- package/src/lib/debug-log-buffer.ts +47 -6
- package/src/lib/destination-policy.ts +12 -1
- package/src/lib/errors.ts +3 -0
- package/src/lib/gcp-adc.ts +40 -2
- package/src/lib/injection-debug-log.ts +26 -2
- package/src/lib/provider-outbound.ts +3 -0
- package/src/lib/sidecar-tracker.ts +5 -2
- package/src/lib/sse-decoder.ts +257 -37
- package/src/lib/state-store-registrations.ts +109 -0
- package/src/lib/state-store-sweeper.ts +184 -0
- package/src/lib/translator-budget.ts +356 -0
- package/src/lib/windows-secret-acl.ts +33 -12
- package/src/lib/winsw.ts +14 -1
- package/src/oauth/anthropic-routing.ts +31 -7
- package/src/oauth/google-antigravity.ts +2 -1
- package/src/oauth/health.ts +30 -12
- package/src/oauth/index.ts +127 -23
- package/src/oauth/kiro-credentials.ts +72 -1
- package/src/oauth/kiro.ts +23 -4
- package/src/oauth/store.ts +165 -18
- package/src/oauth/token-guardian.ts +43 -4
- package/src/oauth/types.ts +2 -1
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/derive.ts +12 -0
- package/src/providers/free-directory.ts +4 -1
- package/src/providers/key-failover.ts +12 -0
- package/src/providers/openai-sidecar.ts +4 -1
- package/src/providers/quota.ts +68 -7
- package/src/providers/registry.ts +279 -3
- package/src/responses/parser.ts +5 -1
- package/src/responses/spill-store.ts +394 -0
- package/src/responses/state.ts +520 -102
- package/src/router.ts +18 -1
- package/src/server/adapter-resolve.ts +20 -3
- package/src/server/auth-cors.ts +121 -28
- package/src/server/chat-completions.ts +57 -12
- package/src/server/claude-messages.ts +85 -13
- package/src/server/index.ts +242 -100
- package/src/server/lifecycle.ts +155 -25
- package/src/server/management/agent-settings-routes.ts +79 -36
- package/src/server/management/api-key-usage.ts +167 -0
- package/src/server/management/body.ts +35 -0
- package/src/server/management/combo-routes.ts +5 -1
- package/src/server/management/config-routes.ts +42 -12
- package/src/server/management/logs-usage-routes.ts +41 -21
- package/src/server/management/model-routes.ts +188 -54
- package/src/server/management/oauth-account-routes.ts +115 -26
- package/src/server/management/provider-routes.ts +56 -6
- package/src/server/management/shared.ts +16 -3
- package/src/server/management/sidebar-routes.ts +50 -1
- package/src/server/management/system-restart.ts +13 -6
- package/src/server/management/system-routes.ts +15 -3
- package/src/server/management/usage-summary-cache.ts +86 -0
- package/src/server/management-api.ts +39 -5
- package/src/server/management-auth.ts +65 -14
- package/src/server/port-reclaim.ts +58 -12
- package/src/server/ports.ts +2 -0
- package/src/server/proxy-liveness.ts +60 -14
- package/src/server/relay-eager.ts +20 -4
- package/src/server/relay.ts +548 -154
- package/src/server/request-decompress.ts +51 -4
- package/src/server/request-log.ts +134 -15
- package/src/server/responses/collaboration.ts +15 -4
- package/src/server/responses/compact.ts +3 -0
- package/src/server/responses/core.ts +241 -66
- package/src/server/responses-image-gen-repair.ts +19 -5
- package/src/server/responses-item-id-repair.ts +23 -5
- package/src/server/sse-payload-rewrite.ts +71 -12
- package/src/server/startup-health-cache.ts +14 -1
- package/src/server/system-env.ts +8 -1
- package/src/server/windows-tcp-drop.ts +15 -5
- package/src/server/ws-bridge.ts +25 -0
- package/src/service.ts +179 -13
- package/src/storage/policy-job.ts +93 -23
- package/src/storage/policy-worker.ts +6 -0
- package/src/storage/restore-job.ts +62 -16
- package/src/storage/restore-worker.ts +6 -0
- package/src/storage/storage-mutation-coordinator.ts +36 -6
- package/src/storage/worker-lifecycle.ts +181 -47
- package/src/tray/windows.ts +97 -25
- package/src/types.ts +39 -6
- package/src/update/index.ts +24 -5
- package/src/update/job.ts +598 -73
- package/src/usage/log.ts +115 -17
- package/src/usage/summary.ts +67 -2
- package/src/vision/index.ts +112 -22
- package/src/web-search/loop.ts +38 -6
- package/src/web-search/progress-stream.ts +14 -3
- package/gui/dist/assets/index-BDjpkcRN.js +0 -67
- package/gui/dist/assets/index-BHsKRFh9.css +0 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { enforceAppOwnedMemoryBudget } from "../lib/app-owned-memory";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Antigravity (Cloud Code Assist) thoughtSignature reasoning-replay cache.
|
|
3
5
|
*
|
|
@@ -10,18 +12,47 @@
|
|
|
10
12
|
* Claude-on-Antigravity uses inline signature sanitization instead (see google-antigravity-wire).
|
|
11
13
|
*/
|
|
12
14
|
|
|
15
|
+
interface ReplayCall {
|
|
16
|
+
signature: string;
|
|
17
|
+
sizeBytes: number;
|
|
18
|
+
touchedAtMs: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
13
21
|
interface ReplayEntry {
|
|
14
22
|
/** thoughtSignature keyed by functionCall identity (name + canonical args). */
|
|
15
|
-
byCall: Map<string,
|
|
23
|
+
byCall: Map<string, ReplayCall>;
|
|
24
|
+
bytes: number;
|
|
16
25
|
expiresAtMs: number;
|
|
26
|
+
oldestAtMs: number | null;
|
|
17
27
|
}
|
|
18
28
|
|
|
19
29
|
const MIN_SIGNATURE_LEN = 16;
|
|
20
30
|
const REPLAY_TTL_MS = 60 * 60 * 1000; // 1h
|
|
21
|
-
const
|
|
31
|
+
export const ANTIGRAVITY_REPLAY_MAX_ENTRIES = 10_240;
|
|
22
32
|
const REPLAY_EVICT_BATCH = 128;
|
|
33
|
+
const REPLAY_MAX_CALLS_PER_SESSION = 256;
|
|
34
|
+
export const ANTIGRAVITY_REPLAY_MAX_BYTES_PER_SESSION = 2 * 1024 * 1024;
|
|
35
|
+
export const ANTIGRAVITY_REPLAY_MAX_TOTAL_BYTES = 64 * 1024 * 1024;
|
|
36
|
+
const REPLAY_MAX_SIGNATURE_BYTES = 64 * 1024;
|
|
37
|
+
|
|
38
|
+
interface ReplayLimits {
|
|
39
|
+
maxCallsPerSession: number;
|
|
40
|
+
maxBytesPerSession: number;
|
|
41
|
+
maxSignatureBytes: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const DEFAULT_REPLAY_LIMITS: ReplayLimits = {
|
|
45
|
+
maxCallsPerSession: REPLAY_MAX_CALLS_PER_SESSION,
|
|
46
|
+
maxBytesPerSession: ANTIGRAVITY_REPLAY_MAX_BYTES_PER_SESSION,
|
|
47
|
+
maxSignatureBytes: REPLAY_MAX_SIGNATURE_BYTES,
|
|
48
|
+
};
|
|
23
49
|
|
|
24
50
|
const replayCache = new Map<string, ReplayEntry>();
|
|
51
|
+
const utf8 = new TextEncoder();
|
|
52
|
+
let replayLimits = { ...DEFAULT_REPLAY_LIMITS };
|
|
53
|
+
let replayBytes = 0;
|
|
54
|
+
let replayOldestSessionKey: string | undefined;
|
|
55
|
+
let replayOldestAt: number | null = null;
|
|
25
56
|
|
|
26
57
|
function replayKey(model: string, sessionId: string): string {
|
|
27
58
|
return `${model}::session:${sessionId}`;
|
|
@@ -57,12 +88,80 @@ function extractSignature(part: Record<string, unknown>): string | undefined {
|
|
|
57
88
|
return undefined;
|
|
58
89
|
}
|
|
59
90
|
|
|
91
|
+
function deleteReplaySession(key: string): number {
|
|
92
|
+
const entry = replayCache.get(key);
|
|
93
|
+
if (!entry) return 0;
|
|
94
|
+
replayCache.delete(key);
|
|
95
|
+
replayBytes -= entry.bytes;
|
|
96
|
+
if (replayOldestSessionKey === key) recomputeReplayOldestCandidate();
|
|
97
|
+
return entry.bytes;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function recomputeReplayOldestCandidate(): void {
|
|
101
|
+
replayOldestSessionKey = undefined;
|
|
102
|
+
replayOldestAt = null;
|
|
103
|
+
for (const [key, entry] of replayCache) {
|
|
104
|
+
if (entry.oldestAtMs === null || (replayOldestAt !== null && entry.oldestAtMs >= replayOldestAt)) continue;
|
|
105
|
+
replayOldestSessionKey = key;
|
|
106
|
+
replayOldestAt = entry.oldestAtMs;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function refreshReplaySessionCandidate(key: string, entry: ReplayEntry): void {
|
|
111
|
+
entry.oldestAtMs = entry.byCall.values().next().value?.touchedAtMs ?? null;
|
|
112
|
+
if (replayOldestSessionKey === key) {
|
|
113
|
+
recomputeReplayOldestCandidate();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (entry.oldestAtMs !== null && (replayOldestAt === null || entry.oldestAtMs < replayOldestAt)) {
|
|
117
|
+
replayOldestSessionKey = key;
|
|
118
|
+
replayOldestAt = entry.oldestAtMs;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function deleteExpiredReplaySessions(now: number): void {
|
|
123
|
+
for (const [key, entry] of replayCache) if (entry.expiresAtMs <= now) deleteReplaySession(key);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function sweepExpiredAntigravityReplay(now = Date.now()): number {
|
|
127
|
+
const before = replayCache.size;
|
|
128
|
+
deleteExpiredReplaySessions(now);
|
|
129
|
+
return before - replayCache.size;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function deleteReplayCall(entry: ReplayEntry, callKey: string): number {
|
|
133
|
+
const call = entry.byCall.get(callKey);
|
|
134
|
+
if (!call) return 0;
|
|
135
|
+
entry.byCall.delete(callKey);
|
|
136
|
+
entry.bytes -= call.sizeBytes;
|
|
137
|
+
replayBytes -= call.sizeBytes;
|
|
138
|
+
return call.sizeBytes;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function evictInnerCalls(entry: ReplayEntry): void {
|
|
142
|
+
while (
|
|
143
|
+
entry.byCall.size > replayLimits.maxCallsPerSession
|
|
144
|
+
|| entry.bytes > replayLimits.maxBytesPerSession
|
|
145
|
+
) {
|
|
146
|
+
const oldest = entry.byCall.keys().next().value;
|
|
147
|
+
if (oldest === undefined) break;
|
|
148
|
+
deleteReplayCall(entry, oldest);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
60
152
|
function evictIfNeeded(): void {
|
|
61
|
-
if (replayCache.size
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
153
|
+
if (replayCache.size > ANTIGRAVITY_REPLAY_MAX_ENTRIES) {
|
|
154
|
+
const oldest = [...replayCache.entries()]
|
|
155
|
+
.sort((a, b) => a[1].expiresAtMs - b[1].expiresAtMs)
|
|
156
|
+
.slice(0, REPLAY_EVICT_BATCH);
|
|
157
|
+
for (const [key] of oldest) deleteReplaySession(key);
|
|
158
|
+
}
|
|
159
|
+
while (replayBytes > ANTIGRAVITY_REPLAY_MAX_TOTAL_BYTES) {
|
|
160
|
+
const oldestKey = [...replayCache.entries()]
|
|
161
|
+
.sort((a, b) => a[1].expiresAtMs - b[1].expiresAtMs)[0]?.[0];
|
|
162
|
+
if (oldestKey === undefined) return;
|
|
163
|
+
deleteReplaySession(oldestKey);
|
|
164
|
+
}
|
|
66
165
|
}
|
|
67
166
|
|
|
68
167
|
/** Gemini/Flash/Agent use the replay cache; Claude does not (inline sanitization instead). */
|
|
@@ -78,9 +177,16 @@ export function antigravityUsesReplayCache(model: string): boolean {
|
|
|
78
177
|
*/
|
|
79
178
|
export function observeAntigravityReplay(model: string, sessionId: string, parts: unknown[]): void {
|
|
80
179
|
if (!antigravityUsesReplayCache(model) || !Array.isArray(parts) || parts.length === 0) return;
|
|
180
|
+
const now = Date.now();
|
|
181
|
+
deleteExpiredReplaySessions(now);
|
|
81
182
|
const key = replayKey(model, sessionId);
|
|
82
|
-
const entry = replayCache.get(key) ?? {
|
|
83
|
-
|
|
183
|
+
const entry = replayCache.get(key) ?? {
|
|
184
|
+
byCall: new Map<string, ReplayCall>(),
|
|
185
|
+
bytes: 0,
|
|
186
|
+
expiresAtMs: 0,
|
|
187
|
+
oldestAtMs: null,
|
|
188
|
+
};
|
|
189
|
+
let inserted = false;
|
|
84
190
|
for (const raw of parts) {
|
|
85
191
|
if (!raw || typeof raw !== "object") continue;
|
|
86
192
|
const part = raw as Record<string, unknown>;
|
|
@@ -89,12 +195,22 @@ export function observeAntigravityReplay(model: string, sessionId: string, parts
|
|
|
89
195
|
const fc = part.functionCall as { name?: unknown; args?: unknown } | undefined;
|
|
90
196
|
const ck = fc ? functionCallKey(fc.name, fc.args) : undefined;
|
|
91
197
|
if (!ck) continue; // only function-call signatures are replayable by identity
|
|
92
|
-
|
|
198
|
+
const signatureBytes = utf8.encode(sig).byteLength;
|
|
199
|
+
const sizeBytes = utf8.encode(ck).byteLength + signatureBytes;
|
|
200
|
+
if (signatureBytes > replayLimits.maxSignatureBytes || sizeBytes > replayLimits.maxBytesPerSession) continue;
|
|
201
|
+
deleteReplayCall(entry, ck);
|
|
202
|
+
entry.byCall.set(ck, { signature: sig, sizeBytes, touchedAtMs: now });
|
|
203
|
+
entry.bytes += sizeBytes;
|
|
204
|
+
replayBytes += sizeBytes;
|
|
205
|
+
inserted = true;
|
|
93
206
|
}
|
|
94
|
-
if (!
|
|
95
|
-
entry
|
|
207
|
+
if (!inserted) return;
|
|
208
|
+
evictInnerCalls(entry);
|
|
209
|
+
entry.expiresAtMs = now + REPLAY_TTL_MS;
|
|
96
210
|
replayCache.set(key, entry);
|
|
211
|
+
refreshReplaySessionCandidate(key, entry);
|
|
97
212
|
evictIfNeeded();
|
|
213
|
+
enforceAppOwnedMemoryBudget();
|
|
98
214
|
}
|
|
99
215
|
|
|
100
216
|
/**
|
|
@@ -104,11 +220,13 @@ export function observeAntigravityReplay(model: string, sessionId: string, parts
|
|
|
104
220
|
*/
|
|
105
221
|
export function applyAntigravityReplay(model: string, sessionId: string, contents: unknown[]): unknown[] {
|
|
106
222
|
if (!antigravityUsesReplayCache(model) || !Array.isArray(contents)) return contents;
|
|
223
|
+
const now = Date.now();
|
|
224
|
+
deleteExpiredReplaySessions(now);
|
|
107
225
|
const entry = replayCache.get(replayKey(model, sessionId));
|
|
108
|
-
if (!entry
|
|
109
|
-
if (entry) replayCache.delete(replayKey(model, sessionId));
|
|
226
|
+
if (!entry) {
|
|
110
227
|
return contents;
|
|
111
228
|
}
|
|
229
|
+
let touched = false;
|
|
112
230
|
for (const c of contents as { role?: string; parts?: unknown[] }[]) {
|
|
113
231
|
if (!c || typeof c !== "object" || c.role !== "model" || !Array.isArray(c.parts)) continue;
|
|
114
232
|
for (const raw of c.parts) {
|
|
@@ -118,19 +236,68 @@ export function applyAntigravityReplay(model: string, sessionId: string, content
|
|
|
118
236
|
if (!fc) continue;
|
|
119
237
|
if (part.thoughtSignature !== undefined || part.thought_signature !== undefined) continue;
|
|
120
238
|
const ck = functionCallKey(fc.name, fc.args);
|
|
121
|
-
const
|
|
122
|
-
if (
|
|
239
|
+
const call = ck ? entry.byCall.get(ck) : undefined;
|
|
240
|
+
if (call && ck) {
|
|
241
|
+
part.thoughtSignature = call.signature;
|
|
242
|
+
entry.byCall.delete(ck);
|
|
243
|
+
entry.byCall.set(ck, { ...call, touchedAtMs: now });
|
|
244
|
+
touched = true;
|
|
245
|
+
}
|
|
123
246
|
}
|
|
124
247
|
}
|
|
248
|
+
if (touched) refreshReplaySessionCandidate(replayKey(model, sessionId), entry);
|
|
125
249
|
return contents;
|
|
126
250
|
}
|
|
127
251
|
|
|
128
252
|
/** Drop the cache entry when upstream rejects a signature (clear-on-invalid). */
|
|
129
253
|
export function clearAntigravityReplay(model: string, sessionId: string): void {
|
|
130
|
-
|
|
254
|
+
deleteReplaySession(replayKey(model, sessionId));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function antigravityReplayMetrics(): {
|
|
258
|
+
sessions: number;
|
|
259
|
+
calls: number;
|
|
260
|
+
totalBytes: number;
|
|
261
|
+
largestSessionBytes: number;
|
|
262
|
+
} {
|
|
263
|
+
let calls = 0;
|
|
264
|
+
let largestSessionBytes = 0;
|
|
265
|
+
for (const entry of replayCache.values()) {
|
|
266
|
+
calls += entry.byCall.size;
|
|
267
|
+
largestSessionBytes = Math.max(largestSessionBytes, entry.bytes);
|
|
268
|
+
}
|
|
269
|
+
return { sessions: replayCache.size, calls, totalBytes: replayBytes, largestSessionBytes };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function antigravityReplayRetainedStoreSnapshot(): {
|
|
273
|
+
count: number;
|
|
274
|
+
bytes: number;
|
|
275
|
+
evictableBytes: number;
|
|
276
|
+
pinnedBytes: number;
|
|
277
|
+
oldestAt: number | null;
|
|
278
|
+
} {
|
|
279
|
+
return {
|
|
280
|
+
count: replayCache.size,
|
|
281
|
+
bytes: replayBytes,
|
|
282
|
+
evictableBytes: replayBytes,
|
|
283
|
+
pinnedBytes: 0,
|
|
284
|
+
oldestAt: replayOldestAt,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function evictOldestAntigravityReplayForBudget(): number {
|
|
289
|
+
return replayOldestSessionKey === undefined ? 0 : deleteReplaySession(replayOldestSessionKey);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function setAntigravityReplayLimitsForTests(limits?: Partial<ReplayLimits>): void {
|
|
293
|
+
__resetAntigravityReplayCache();
|
|
294
|
+
replayLimits = limits ? { ...DEFAULT_REPLAY_LIMITS, ...limits } : { ...DEFAULT_REPLAY_LIMITS };
|
|
131
295
|
}
|
|
132
296
|
|
|
133
297
|
/** Test seam. */
|
|
134
298
|
export function __resetAntigravityReplayCache(): void {
|
|
135
299
|
replayCache.clear();
|
|
300
|
+
replayBytes = 0;
|
|
301
|
+
replayOldestSessionKey = undefined;
|
|
302
|
+
replayOldestAt = null;
|
|
136
303
|
}
|
package/src/adapters/google.ts
CHANGED
|
@@ -23,6 +23,11 @@ import { compileGoogleWireBody } from "./google-wire-compiler";
|
|
|
23
23
|
import { neutralizeIdentity } from "./identity";
|
|
24
24
|
import { antigravityUsesReplayCache, applyAntigravityReplay, clearAntigravityReplay, observeAntigravityReplay } from "./google-antigravity-replay";
|
|
25
25
|
import { resolveAntigravityEffortWireModel } from "../providers/antigravity-models";
|
|
26
|
+
import {
|
|
27
|
+
isTranslatorBudgetExceededError,
|
|
28
|
+
retainTranslatedEventBatch,
|
|
29
|
+
type TranslatorBudget,
|
|
30
|
+
} from "../lib/translator-budget";
|
|
26
31
|
import { buildNonOpenAIToolCatalogNudgeForTools } from "./tool-catalog-nudge";
|
|
27
32
|
import { mapReasoningEffort } from "../reasoning-effort";
|
|
28
33
|
|
|
@@ -408,7 +413,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
408
413
|
return { url, method: "POST", headers, body: JSON.stringify(compiled.body) };
|
|
409
414
|
},
|
|
410
415
|
|
|
411
|
-
async *parseStream(response: Response): AsyncGenerator<AdapterEvent> {
|
|
416
|
+
async *parseStream(response: Response, budget: TranslatorBudget): AsyncGenerator<AdapterEvent> {
|
|
412
417
|
if (!response.body) {
|
|
413
418
|
yield { type: "error", message: "No response body" };
|
|
414
419
|
return;
|
|
@@ -420,7 +425,9 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
420
425
|
|
|
421
426
|
const reader = response.body.getReader();
|
|
422
427
|
const decoder = new TextDecoder();
|
|
428
|
+
const budgetEncoder = new TextEncoder();
|
|
423
429
|
let buffer = "";
|
|
430
|
+
let bufferBytes = 0;
|
|
424
431
|
let pendingUsage: OcxUsage | undefined;
|
|
425
432
|
let toolCallsStarted = 0;
|
|
426
433
|
let lastFinishReason: string | undefined;
|
|
@@ -529,7 +536,13 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
529
536
|
while (true) {
|
|
530
537
|
const { done, value } = await reader.read();
|
|
531
538
|
if (done) break;
|
|
532
|
-
buffer
|
|
539
|
+
const nextBuffer = buffer + decoder.decode(value, { stream: true });
|
|
540
|
+
const nextBufferBytes = budgetEncoder.encode(nextBuffer).byteLength;
|
|
541
|
+
const appendReservation = budget.reserveTransient(nextBufferBytes, { kind: "live_transient" });
|
|
542
|
+
buffer = nextBuffer;
|
|
543
|
+
appendReservation.commitRetained();
|
|
544
|
+
budget.releaseRetained(bufferBytes, { kind: "live_transient" });
|
|
545
|
+
bufferBytes = nextBufferBytes;
|
|
533
546
|
// Cap incomplete frames before waiting for a newline — otherwise a single
|
|
534
547
|
// unterminated data: payload can grow without bound.
|
|
535
548
|
if (buffer.length > MAX_SSE_FRAME_BYTES) {
|
|
@@ -540,6 +553,11 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
540
553
|
|
|
541
554
|
const lines = buffer.split("\n");
|
|
542
555
|
buffer = lines.pop() ?? "";
|
|
556
|
+
const residualBytes = budgetEncoder.encode(buffer).byteLength;
|
|
557
|
+
const residualReservation = budget.reserveTransient(residualBytes, { kind: "live_transient" });
|
|
558
|
+
residualReservation.commitRetained();
|
|
559
|
+
budget.releaseRetained(bufferBytes, { kind: "live_transient" });
|
|
560
|
+
bufferBytes = residualBytes;
|
|
543
561
|
|
|
544
562
|
let sawLiveness = false;
|
|
545
563
|
let sawContentEvent = false;
|
|
@@ -587,12 +605,23 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
587
605
|
usage: pendingUsage,
|
|
588
606
|
...(stopReason ? { stopReason } : {}),
|
|
589
607
|
};
|
|
608
|
+
} catch (error) {
|
|
609
|
+
if (!isTranslatorBudgetExceededError(error)) throw error;
|
|
610
|
+
try { await reader.cancel(error); } catch { /* already closed */ }
|
|
611
|
+
yield {
|
|
612
|
+
type: "error",
|
|
613
|
+
status: 502,
|
|
614
|
+
errorType: "upstream_error",
|
|
615
|
+
code: "translation_buffer_limit",
|
|
616
|
+
message: "upstream translation buffer exceeded the safe limit",
|
|
617
|
+
};
|
|
590
618
|
} finally {
|
|
619
|
+
budget.releaseRetained(bufferBytes, { kind: "live_transient" });
|
|
591
620
|
reader.releaseLock();
|
|
592
621
|
}
|
|
593
622
|
},
|
|
594
623
|
|
|
595
|
-
async parseResponse(response: Response): Promise<AdapterEvent[]> {
|
|
624
|
+
async parseResponse(response: Response, budget: TranslatorBudget): Promise<AdapterEvent[]> {
|
|
596
625
|
// Reject oversized responses before JSON parse. Prefer Content-Length when
|
|
597
626
|
// present and truthful; always stream-read with a hard byte cap so a missing
|
|
598
627
|
// or lying Content-Length cannot force a full in-memory buffer + parse.
|
|
@@ -602,6 +631,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
602
631
|
return [{ type: "error", message: `google response too large (content-length ${contentLength} exceeds ${MAX_RESPONSE_BYTES} bytes)` }];
|
|
603
632
|
}
|
|
604
633
|
let rawText: string;
|
|
634
|
+
let rawTextBytes = 0;
|
|
605
635
|
try {
|
|
606
636
|
const reader = response.body?.getReader();
|
|
607
637
|
if (!reader) return [{ type: "error", message: "google response had no body" }];
|
|
@@ -616,38 +646,58 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
616
646
|
await reader.cancel().catch(() => {});
|
|
617
647
|
return [{ type: "error", message: `google response too large (exceeded ${MAX_RESPONSE_BYTES} bytes)` }];
|
|
618
648
|
}
|
|
649
|
+
budget.chargeRetained(value.byteLength, { kind: "retained_collectors" });
|
|
619
650
|
chunks.push(value);
|
|
620
651
|
}
|
|
621
652
|
} finally {
|
|
622
653
|
try { await reader.cancel(); } catch { /* ignore */ }
|
|
623
654
|
reader.releaseLock();
|
|
624
655
|
}
|
|
656
|
+
const bytesReservation = budget.reserveTransient(total, { kind: "retained_collectors" });
|
|
625
657
|
const bytes = new Uint8Array(total);
|
|
626
658
|
let offset = 0;
|
|
627
659
|
for (const chunk of chunks) {
|
|
628
660
|
bytes.set(chunk, offset);
|
|
629
661
|
offset += chunk.byteLength;
|
|
630
662
|
}
|
|
663
|
+
bytesReservation.commitRetained();
|
|
664
|
+
budget.releaseRetained(total, { kind: "retained_collectors" });
|
|
631
665
|
rawText = new TextDecoder().decode(bytes);
|
|
666
|
+
rawTextBytes = new TextEncoder().encode(rawText).byteLength;
|
|
667
|
+
const textReservation = budget.reserveTransient(rawTextBytes, { kind: "retained_collectors" });
|
|
668
|
+
textReservation.commitRetained();
|
|
669
|
+
budget.releaseRetained(total, { kind: "retained_collectors" });
|
|
632
670
|
} catch (err) {
|
|
633
671
|
return [{ type: "error", message: err instanceof Error ? err.message : "failed to read google response body" }];
|
|
634
672
|
}
|
|
635
673
|
let raw: Record<string, unknown>;
|
|
674
|
+
let rawBytes = 0;
|
|
636
675
|
try {
|
|
637
676
|
raw = JSON.parse(rawText) as Record<string, unknown>;
|
|
677
|
+
rawBytes = new TextEncoder().encode(JSON.stringify(raw)).byteLength;
|
|
678
|
+
const rawReservation = budget.reserveTransient(rawBytes, { kind: "retained_collectors" });
|
|
679
|
+
rawReservation.commitRetained();
|
|
680
|
+
budget.releaseRetained(rawTextBytes, { kind: "retained_collectors" });
|
|
638
681
|
} catch {
|
|
682
|
+
budget.releaseRetained(rawTextBytes, { kind: "retained_collectors" });
|
|
639
683
|
return [{ type: "error", message: "google response was not valid JSON" }];
|
|
640
684
|
}
|
|
685
|
+
const finish = (events: AdapterEvent[]): AdapterEvent[] => {
|
|
686
|
+
retainTranslatedEventBatch(events, budget);
|
|
687
|
+
budget.releaseRetained(rawBytes, { kind: "retained_collectors" });
|
|
688
|
+
rawBytes = 0;
|
|
689
|
+
return events;
|
|
690
|
+
};
|
|
641
691
|
if (raw.error) {
|
|
642
692
|
const err = raw.error as { message?: string };
|
|
643
|
-
return [{ type: "error", message: err.message ?? "upstream error" }];
|
|
693
|
+
return finish([{ type: "error", message: err.message ?? "upstream error" }]);
|
|
644
694
|
}
|
|
645
695
|
// Antigravity (CCA) nests the standard Gemini payload under `response`; unwrap it.
|
|
646
696
|
let json = raw;
|
|
647
697
|
if (provider.googleMode === "cloud-code-assist") {
|
|
648
698
|
const wrapped = raw.response;
|
|
649
699
|
if (!wrapped || typeof wrapped !== "object" || Array.isArray(wrapped)) {
|
|
650
|
-
return [{ type: "error", message: "google-antigravity response missing response wrapper" }];
|
|
700
|
+
return finish([{ type: "error", message: "google-antigravity response missing response wrapper" }]);
|
|
651
701
|
}
|
|
652
702
|
json = wrapped as Record<string, unknown>;
|
|
653
703
|
}
|
|
@@ -655,7 +705,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
655
705
|
|
|
656
706
|
const candidates = json.candidates as { content?: { parts?: { text?: string; functionCall?: { name: string; args: unknown } }[] }; finishReason?: string }[] | undefined;
|
|
657
707
|
if (!candidates?.length) {
|
|
658
|
-
return [{ type: "error", message: "google response contained no candidates" }];
|
|
708
|
+
return finish([{ type: "error", message: "google response contained no candidates" }]);
|
|
659
709
|
}
|
|
660
710
|
let toolCallsStarted = 0;
|
|
661
711
|
const imageBudget = createImageBudget();
|
|
@@ -694,7 +744,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
694
744
|
// (MAX_TOKENS / MALFORMED_FUNCTION_CALL) surfaces an error instead of a silent done.
|
|
695
745
|
if ((provider.googleMode === "vertex" || provider.googleMode === "cloud-code-assist")
|
|
696
746
|
&& toolCallsStarted > 0 && isVertexTruncationReason(candidates?.[0]?.finishReason)) {
|
|
697
|
-
return [{ type: "error", message: vertexTruncationErrorMessage(candidates?.[0]?.finishReason) }];
|
|
747
|
+
return finish([{ type: "error", message: vertexTruncationErrorMessage(candidates?.[0]?.finishReason) }]);
|
|
698
748
|
}
|
|
699
749
|
|
|
700
750
|
const usage = json.usageMetadata as Record<string, number> | undefined;
|
|
@@ -702,7 +752,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
702
752
|
type: "done",
|
|
703
753
|
usage: usageFromGemini(usage),
|
|
704
754
|
});
|
|
705
|
-
return events;
|
|
755
|
+
return finish(events);
|
|
706
756
|
},
|
|
707
757
|
};
|
|
708
758
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AdapterEvent } from "../types";
|
|
2
|
+
import type { TranslatorBudget } from "../lib/translator-budget";
|
|
2
3
|
|
|
3
4
|
type ThinkingTag = "<thinking>" | "<think>" | "<reasoning>";
|
|
4
5
|
type ParserState = "pre" | "thinking" | "streaming";
|
|
@@ -21,40 +22,53 @@ export class KiroThinkingParser {
|
|
|
21
22
|
private thinkingBuffer = "";
|
|
22
23
|
private closeTag = "";
|
|
23
24
|
|
|
25
|
+
constructor(private readonly budget?: TranslatorBudget) {}
|
|
26
|
+
|
|
27
|
+
private replaceCarry(field: "preBuffer" | "thinkingBuffer", next: string): void {
|
|
28
|
+
const previous = this[field];
|
|
29
|
+
if (previous === next) return;
|
|
30
|
+
const previousBytes = Buffer.byteLength(previous);
|
|
31
|
+
const nextBytes = Buffer.byteLength(next);
|
|
32
|
+
const reservation = this.budget?.reserveTransient(nextBytes, { kind: "reasoning" });
|
|
33
|
+
this[field] = next;
|
|
34
|
+
reservation?.commitRetained();
|
|
35
|
+
this.budget?.releaseRetained(previousBytes, { kind: "reasoning" });
|
|
36
|
+
}
|
|
37
|
+
|
|
24
38
|
feed(text: string): AdapterEvent[] {
|
|
25
39
|
if (!text) return [];
|
|
26
40
|
if (this.state === "streaming") return [{ type: "text_delta", text }];
|
|
27
41
|
if (this.state === "thinking") {
|
|
28
|
-
this.thinkingBuffer
|
|
42
|
+
this.replaceCarry("thinkingBuffer", this.thinkingBuffer + text);
|
|
29
43
|
return this.drainThinking();
|
|
30
44
|
}
|
|
31
|
-
this.preBuffer
|
|
45
|
+
this.replaceCarry("preBuffer", this.preBuffer + text);
|
|
32
46
|
const stripped = this.preBuffer.trimStart();
|
|
33
47
|
const openTag = OPEN_TAGS.find(tag => stripped.startsWith(tag));
|
|
34
48
|
if (openTag) {
|
|
35
49
|
this.state = "thinking";
|
|
36
50
|
this.closeTag = closeTagFor(openTag);
|
|
37
|
-
this.thinkingBuffer
|
|
38
|
-
this.preBuffer
|
|
51
|
+
this.replaceCarry("thinkingBuffer", stripped.slice(openTag.length));
|
|
52
|
+
this.replaceCarry("preBuffer", "");
|
|
39
53
|
return this.drainThinking();
|
|
40
54
|
}
|
|
41
55
|
if (stripped.length <= MAX_OPEN_TAG && isPossibleOpenTagPrefix(stripped)) return [];
|
|
42
56
|
this.state = "streaming";
|
|
43
57
|
const out = this.preBuffer;
|
|
44
|
-
this.preBuffer
|
|
58
|
+
this.replaceCarry("preBuffer", "");
|
|
45
59
|
return out ? [{ type: "text_delta", text: out }] : [];
|
|
46
60
|
}
|
|
47
61
|
|
|
48
62
|
flush(): AdapterEvent[] {
|
|
49
63
|
if (this.state === "thinking") {
|
|
50
64
|
const out = this.thinkingBuffer;
|
|
51
|
-
this.thinkingBuffer
|
|
65
|
+
this.replaceCarry("thinkingBuffer", "");
|
|
52
66
|
this.state = "streaming";
|
|
53
67
|
return out ? [{ type: "reasoning_raw_delta", text: out }] : [];
|
|
54
68
|
}
|
|
55
69
|
if (this.preBuffer) {
|
|
56
70
|
const out = this.preBuffer;
|
|
57
|
-
this.preBuffer
|
|
71
|
+
this.replaceCarry("preBuffer", "");
|
|
58
72
|
this.state = "streaming";
|
|
59
73
|
return [{ type: "text_delta", text: out }];
|
|
60
74
|
}
|
|
@@ -67,7 +81,7 @@ export class KiroThinkingParser {
|
|
|
67
81
|
if (idx >= 0) {
|
|
68
82
|
const thinking = this.thinkingBuffer.slice(0, idx);
|
|
69
83
|
const after = this.thinkingBuffer.slice(idx + close.length).trimStart();
|
|
70
|
-
this.thinkingBuffer
|
|
84
|
+
this.replaceCarry("thinkingBuffer", "");
|
|
71
85
|
this.state = "streaming";
|
|
72
86
|
const events: AdapterEvent[] = [];
|
|
73
87
|
if (thinking) events.push({ type: "reasoning_raw_delta", text: thinking });
|
|
@@ -76,7 +90,7 @@ export class KiroThinkingParser {
|
|
|
76
90
|
}
|
|
77
91
|
if (this.thinkingBuffer.length <= MAX_CLOSE_TAG) return [];
|
|
78
92
|
const send = this.thinkingBuffer.slice(0, -MAX_CLOSE_TAG);
|
|
79
|
-
this.thinkingBuffer
|
|
93
|
+
this.replaceCarry("thinkingBuffer", this.thinkingBuffer.slice(-MAX_CLOSE_TAG));
|
|
80
94
|
return send ? [{ type: "reasoning_raw_delta", text: send }] : [];
|
|
81
95
|
}
|
|
82
96
|
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import type { OcxParsedRequest } from "../types";
|
|
1
|
+
import type { OcxParsedRequest, OcxTool } from "../types";
|
|
2
2
|
import { namespacedToolName } from "../types";
|
|
3
3
|
import { normalizeKiroModelId } from "../providers/kiro-models";
|
|
4
4
|
import { createKiroToolNameRegistry, type KiroToolNameRegistry } from "./kiro-wire";
|
|
5
5
|
|
|
6
6
|
const MAX_KIRO_TOOL_DESCRIPTION_UNVERIFIED = 1024;
|
|
7
7
|
const MAX_KIRO_TOOL_DESCRIPTION_GPT_56_SOL = 9_216;
|
|
8
|
+
// Issue #719's successful Kiro probe used 49 outbound tools at about 108 KiB. Leave one of those
|
|
9
|
+
// slots for Kiro's private completion tool and headroom for the enclosing request/message fields.
|
|
10
|
+
export const MAX_KIRO_TOOL_COUNT = 48;
|
|
11
|
+
export const MAX_KIRO_TOOL_CATALOG_BYTES = 96_000;
|
|
12
|
+
const textEncoder = new TextEncoder();
|
|
8
13
|
|
|
9
14
|
// JSON Schema validation/annotation keywords that Kiro's runtimeservice tool-spec validator
|
|
10
15
|
// rejects ("ValidationException: Invalid tool use format."). Codex's built-in tools omit these,
|
|
@@ -147,6 +152,17 @@ function truncateDescription(description: string, limit: number): string {
|
|
|
147
152
|
return `${description.slice(0, limit - 1)}…`;
|
|
148
153
|
}
|
|
149
154
|
|
|
155
|
+
function serializedToolCatalogBytes(tools: readonly unknown[]): number {
|
|
156
|
+
return textEncoder.encode(JSON.stringify(tools)).byteLength;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function omittedToolCatalogNotice(kept: number, omitted: readonly OcxTool[], registry: KiroToolNameRegistry): string {
|
|
160
|
+
const names = omitted.slice(0, 12).map(tool => registry.alias(namespacedToolName(tool.namespace, tool.name)));
|
|
161
|
+
const remainder = omitted.length - names.length;
|
|
162
|
+
const summary = `${names.join(", ")}${remainder > 0 ? `, and ${remainder} more` : ""}`;
|
|
163
|
+
return `[opencodex] Kiro's outbound catalog budget allows ${kept} of ${kept + omitted.length} client tools this turn. Omitted and unavailable this turn: ${summary}.`;
|
|
164
|
+
}
|
|
165
|
+
|
|
150
166
|
export function convertKiroToolContext(
|
|
151
167
|
parsed: OcxParsedRequest,
|
|
152
168
|
registry: KiroToolNameRegistry = createKiroToolNameRegistry(),
|
|
@@ -156,24 +172,39 @@ export function convertKiroToolContext(
|
|
|
156
172
|
// Validate every listed name even when tool_choice:none emulates a tool-free turn.
|
|
157
173
|
for (const tool of tools) registry.alias(namespacedToolName(tool.namespace, tool.name));
|
|
158
174
|
const effectiveTools = parsed.options.toolChoice === "none" ? [] : tools;
|
|
175
|
+
const convertedTools: unknown[] = [];
|
|
176
|
+
let omittedAt = effectiveTools.length;
|
|
177
|
+
for (const [index, tool] of effectiveTools.entries()) {
|
|
178
|
+
const description = tool.description || `Tool: ${tool.name}`;
|
|
179
|
+
// Send the full namespaced wire name (e.g. mcp__chrome-devtools__navigate_page) so Kiro echoes
|
|
180
|
+
// it back; the bridge's toolNsMap is keyed by this name and restores the MCP namespace Codex
|
|
181
|
+
// routes by. Kiro's runtimeservice rejects names with spaces or >64 chars, so normalize to a
|
|
182
|
+
// safe form and remember the mapping; the response parser restores the original wire name.
|
|
183
|
+
const wireName = namespacedToolName(tool.namespace, tool.name);
|
|
184
|
+
const toolName = registry.alias(wireName);
|
|
185
|
+
const converted = {
|
|
186
|
+
toolSpecification: {
|
|
187
|
+
name: toolName,
|
|
188
|
+
description: truncateDescription(description, descriptionLimit),
|
|
189
|
+
inputSchema: { json: ensureRootObjectType(sanitizeKiroSchema(tool.parameters ?? {})) },
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
// Preserve declaration order and only omit a suffix. Ranking tools would make a catalog change
|
|
193
|
+
// silently alter which capability disappears; this deterministic policy is paired with a
|
|
194
|
+
// model-visible omission notice so unavailable tools are explicit rather than assumed absent.
|
|
195
|
+
if (
|
|
196
|
+
convertedTools.length >= MAX_KIRO_TOOL_COUNT
|
|
197
|
+
|| serializedToolCatalogBytes([...convertedTools, converted]) > MAX_KIRO_TOOL_CATALOG_BYTES
|
|
198
|
+
) {
|
|
199
|
+
omittedAt = index;
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
convertedTools.push(converted);
|
|
203
|
+
}
|
|
204
|
+
const omittedTools = effectiveTools.slice(omittedAt);
|
|
159
205
|
return {
|
|
160
|
-
tools:
|
|
161
|
-
|
|
162
|
-
// Send the full namespaced wire name (e.g. mcp__chrome-devtools__navigate_page) so Kiro echoes
|
|
163
|
-
// it back; the bridge's toolNsMap is keyed by this name and restores the MCP namespace Codex
|
|
164
|
-
// routes by. Kiro's runtimeservice rejects names with spaces or >64 chars, so normalize to a
|
|
165
|
-
// safe form and remember the mapping; the response parser restores the original wire name.
|
|
166
|
-
const wireName = namespacedToolName(t.namespace, t.name);
|
|
167
|
-
const toolName = registry.alias(wireName);
|
|
168
|
-
return {
|
|
169
|
-
toolSpecification: {
|
|
170
|
-
name: toolName,
|
|
171
|
-
description: truncateDescription(description, descriptionLimit),
|
|
172
|
-
inputSchema: { json: ensureRootObjectType(sanitizeKiroSchema(t.parameters ?? {})) },
|
|
173
|
-
},
|
|
174
|
-
};
|
|
175
|
-
}),
|
|
176
|
-
systemAdditions: [],
|
|
206
|
+
tools: convertedTools,
|
|
207
|
+
systemAdditions: omittedTools.length > 0 ? [omittedToolCatalogNotice(convertedTools.length, omittedTools, registry)] : [],
|
|
177
208
|
nameMap: registry.nameMap,
|
|
178
209
|
registry,
|
|
179
210
|
};
|