@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
package/src/claude/outbound.ts
CHANGED
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
* - errors: {type:"error", error:{type,message}}; may arrive mid-stream after HTTP 200.
|
|
11
11
|
*/
|
|
12
12
|
import { isTransientUpstreamStatus } from "../lib/upstream-retry";
|
|
13
|
+
import {
|
|
14
|
+
isTranslatorBudgetExceededError,
|
|
15
|
+
TRANSLATOR_MAX_TURN_BYTES,
|
|
16
|
+
TranslatorBudgetExceededError,
|
|
17
|
+
type TranslatorBudget,
|
|
18
|
+
} from "../lib/translator-budget";
|
|
13
19
|
|
|
14
20
|
type Rec = Record<string, unknown>;
|
|
15
21
|
|
|
@@ -38,12 +44,12 @@ export function anthropicErrorType(status: number): string {
|
|
|
38
44
|
}
|
|
39
45
|
}
|
|
40
46
|
|
|
41
|
-
export function anthropicErrorBody(status: number, message: string, type?: string): Rec {
|
|
42
|
-
return { type: "error", error: { type: type ?? anthropicErrorType(status), message } };
|
|
47
|
+
export function anthropicErrorBody(status: number, message: string, type?: string, code?: string): Rec {
|
|
48
|
+
return { type: "error", error: { type: type ?? anthropicErrorType(status), message, ...(code ? { code } : {}) } };
|
|
43
49
|
}
|
|
44
50
|
|
|
45
|
-
export function anthropicErrorResponse(status: number, message: string, type?: string): Response {
|
|
46
|
-
return new Response(JSON.stringify(anthropicErrorBody(status, message, type)), {
|
|
51
|
+
export function anthropicErrorResponse(status: number, message: string, type?: string, code?: string): Response {
|
|
52
|
+
return new Response(JSON.stringify(anthropicErrorBody(status, message, type, code)), {
|
|
47
53
|
status,
|
|
48
54
|
headers: { "Content-Type": "application/json" },
|
|
49
55
|
});
|
|
@@ -76,6 +82,17 @@ function sseFrame(name: string, data: Rec): string {
|
|
|
76
82
|
return `event: ${name}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
77
83
|
}
|
|
78
84
|
|
|
85
|
+
function appendedUtf8Bytes(previous: string, previousBytes: number, fragment: string): number {
|
|
86
|
+
let nextBytes = previousBytes + Buffer.byteLength(fragment);
|
|
87
|
+
const previousLast = previous.charCodeAt(previous.length - 1);
|
|
88
|
+
const fragmentFirst = fragment.charCodeAt(0);
|
|
89
|
+
if (previousLast >= 0xd800 && previousLast <= 0xdbff
|
|
90
|
+
&& fragmentFirst >= 0xdc00 && fragmentFirst <= 0xdfff) {
|
|
91
|
+
nextBytes -= 2;
|
|
92
|
+
}
|
|
93
|
+
return nextBytes;
|
|
94
|
+
}
|
|
95
|
+
|
|
79
96
|
/**
|
|
80
97
|
* Claude Code / Anthropic WebSearch domain filters are optional and mutually exclusive.
|
|
81
98
|
* Empty arrays are rejected ("ambiguous"); both fields together are rejected. Routed models
|
|
@@ -167,19 +184,23 @@ interface OpenBlock {
|
|
|
167
184
|
/** Buffer WebSearch args and emit one sanitized input_json_delta on close (#381). */
|
|
168
185
|
bufferWebSearchArgs?: boolean;
|
|
169
186
|
argsBuf?: string;
|
|
187
|
+
argsBufBytes?: number;
|
|
170
188
|
webSearchArgsEmitted?: boolean;
|
|
189
|
+
callId?: string;
|
|
171
190
|
}
|
|
172
191
|
|
|
173
192
|
/** Streaming: Responses SSE bytes -> Anthropic Messages SSE bytes. */
|
|
174
193
|
export function responsesSseToAnthropicSse(
|
|
175
194
|
upstream: ReadableStream<Uint8Array>,
|
|
176
195
|
model: string,
|
|
177
|
-
opts
|
|
196
|
+
opts: { pingIntervalMs?: number; translatorBudget: TranslatorBudget },
|
|
178
197
|
): ReadableStream<Uint8Array> {
|
|
198
|
+
const translatorBudget = opts.translatorBudget;
|
|
179
199
|
const pingIntervalMs = opts?.pingIntervalMs ?? 20_000;
|
|
180
200
|
const decoder = new TextDecoder();
|
|
181
201
|
const encoder = new TextEncoder();
|
|
182
202
|
let buffer = "";
|
|
203
|
+
let bufferBytes = 0;
|
|
183
204
|
let started = false;
|
|
184
205
|
let terminated = false;
|
|
185
206
|
let cancelled = false;
|
|
@@ -189,10 +210,32 @@ export function responsesSseToAnthropicSse(
|
|
|
189
210
|
let webSearchRequests = 0;
|
|
190
211
|
let pingTimer: ReturnType<typeof setInterval> | undefined;
|
|
191
212
|
let reader: ReadableStreamDefaultReader<Uint8Array> | undefined;
|
|
213
|
+
const utf8SliceBytes = (value: string, start: number, end: number): number => {
|
|
214
|
+
let bytes = 0;
|
|
215
|
+
for (let index = start; index < end; index++) {
|
|
216
|
+
const codePoint = value.codePointAt(index)!;
|
|
217
|
+
if (codePoint <= 0x7f) bytes += 1;
|
|
218
|
+
else if (codePoint <= 0x7ff) bytes += 2;
|
|
219
|
+
else if (codePoint <= 0xffff) bytes += 3;
|
|
220
|
+
else { bytes += 4; index += 1; }
|
|
221
|
+
}
|
|
222
|
+
return bytes;
|
|
223
|
+
};
|
|
224
|
+
const queuedLiveFrameBytes: number[] = [];
|
|
225
|
+
const releaseDeliveredFrame = () => {
|
|
226
|
+
const bytes = queuedLiveFrameBytes.shift();
|
|
227
|
+
if (bytes !== undefined) translatorBudget.releaseRetained(bytes, { kind: "live_transient" });
|
|
228
|
+
};
|
|
192
229
|
|
|
193
230
|
return new ReadableStream<Uint8Array>({
|
|
194
231
|
start(controller) {
|
|
195
|
-
const emit = (name: string, data: Rec) =>
|
|
232
|
+
const emit = (name: string, data: Rec) => {
|
|
233
|
+
const frame = encoder.encode(sseFrame(name, data));
|
|
234
|
+
const reservation = translatorBudget.reserveTransient(frame.byteLength, { kind: "live_transient" });
|
|
235
|
+
controller.enqueue(frame);
|
|
236
|
+
reservation.commitRetained();
|
|
237
|
+
queuedLiveFrameBytes.push(frame.byteLength);
|
|
238
|
+
};
|
|
196
239
|
const ensureStarted = () => {
|
|
197
240
|
if (started) return;
|
|
198
241
|
started = true;
|
|
@@ -234,6 +277,7 @@ export function responsesSseToAnthropicSse(
|
|
|
234
277
|
});
|
|
235
278
|
}
|
|
236
279
|
emit("content_block_stop", { type: "content_block_stop", index: open.index });
|
|
280
|
+
if (open.callId) translatorBudget.closeCall(open.callId);
|
|
237
281
|
open = null;
|
|
238
282
|
};
|
|
239
283
|
const ensureBlock = (kind: "text" | "thinking") => {
|
|
@@ -265,13 +309,26 @@ export function responsesSseToAnthropicSse(
|
|
|
265
309
|
// (devlog/_plan/260716_claudecode_hardening/020). On win32 mid-stream socket
|
|
266
310
|
// resets reach the reader catch (no failed-tail relay) and stay api_error —
|
|
267
311
|
// same as today, deliberate residual.
|
|
268
|
-
const fail = (status: number, message: string, upstreamDerived = false) => {
|
|
312
|
+
const fail = (status: number, message: string, upstreamDerived = false, code?: string) => {
|
|
269
313
|
if (terminated) return;
|
|
270
314
|
terminated = true;
|
|
315
|
+
if (code === "translation_buffer_limit") {
|
|
316
|
+
if (open?.callId) translatorBudget.closeCall(open.callId);
|
|
317
|
+
open = null;
|
|
318
|
+
// No normal close frames are valid after overflow. Emit exactly one bounded
|
|
319
|
+
// typed terminal without consulting the exhausted budget.
|
|
320
|
+
controller.enqueue(encoder.encode(sseFrame("error", anthropicErrorBody(
|
|
321
|
+
413,
|
|
322
|
+
message,
|
|
323
|
+
"request_too_large",
|
|
324
|
+
"translation_buffer_limit",
|
|
325
|
+
))));
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
271
328
|
ensureStarted();
|
|
272
329
|
closeOpenBlock();
|
|
273
330
|
const type = upstreamDerived && isTransientUpstreamStatus(status) ? "overloaded_error" : undefined;
|
|
274
|
-
emit("error", anthropicErrorBody(status, message, type));
|
|
331
|
+
emit("error", anthropicErrorBody(status, message, type, code));
|
|
275
332
|
};
|
|
276
333
|
|
|
277
334
|
const handleFrame = (eventName: string, data: Rec) => {
|
|
@@ -311,21 +368,25 @@ export function responsesSseToAnthropicSse(
|
|
|
311
368
|
const index = blockIndex++;
|
|
312
369
|
const name = typeof item.name === "string" ? item.name : "";
|
|
313
370
|
const bufferWebSearchArgs = isClaudeWebSearchToolName(name);
|
|
371
|
+
const callId = typeof item.call_id === "string" ? item.call_id : `toolu_${uuid()}`;
|
|
314
372
|
emit("content_block_start", {
|
|
315
373
|
type: "content_block_start", index,
|
|
316
374
|
content_block: {
|
|
317
375
|
type: "tool_use",
|
|
318
|
-
id:
|
|
376
|
+
id: callId,
|
|
319
377
|
name,
|
|
320
378
|
input: {},
|
|
321
379
|
},
|
|
322
380
|
});
|
|
381
|
+
translatorBudget.openCall(callId);
|
|
323
382
|
open = {
|
|
324
383
|
kind: "tool_use",
|
|
325
384
|
index,
|
|
385
|
+
callId,
|
|
326
386
|
itemId: typeof item.id === "string" ? item.id : undefined,
|
|
327
387
|
bufferWebSearchArgs,
|
|
328
388
|
argsBuf: "",
|
|
389
|
+
argsBufBytes: 0,
|
|
329
390
|
webSearchArgsEmitted: false,
|
|
330
391
|
};
|
|
331
392
|
break;
|
|
@@ -334,7 +395,23 @@ export function responsesSseToAnthropicSse(
|
|
|
334
395
|
if (typeof data.delta !== "string" || data.delta.length === 0) break;
|
|
335
396
|
if (!open || open.kind !== "tool_use") break;
|
|
336
397
|
if (open.bufferWebSearchArgs) {
|
|
337
|
-
|
|
398
|
+
const previous = open.argsBuf ?? "";
|
|
399
|
+
const previousBytes = open.argsBufBytes ?? 0;
|
|
400
|
+
const nextBytes = appendedUtf8Bytes(previous, previousBytes, data.delta);
|
|
401
|
+
const scope = {
|
|
402
|
+
kind: "tool_args",
|
|
403
|
+
...(open.callId ? { callId: open.callId } : {}),
|
|
404
|
+
} as const;
|
|
405
|
+
const reservation = translatorBudget.reserveTransient(nextBytes, scope);
|
|
406
|
+
try {
|
|
407
|
+
open.argsBuf = previous + data.delta;
|
|
408
|
+
open.argsBufBytes = nextBytes;
|
|
409
|
+
reservation.commitRetained();
|
|
410
|
+
translatorBudget.releaseRetained(previousBytes, scope);
|
|
411
|
+
} catch (error) {
|
|
412
|
+
reservation.release();
|
|
413
|
+
throw error;
|
|
414
|
+
}
|
|
338
415
|
break;
|
|
339
416
|
}
|
|
340
417
|
emit("content_block_delta", {
|
|
@@ -424,11 +501,21 @@ export function responsesSseToAnthropicSse(
|
|
|
424
501
|
const response = isRec(data.response) ? data.response : {};
|
|
425
502
|
const error = isRec(response.error) ? response.error : {};
|
|
426
503
|
const message = typeof error.message === "string" ? error.message : "upstream request failed";
|
|
427
|
-
const
|
|
504
|
+
const code = typeof error.code === "string" ? error.code : undefined;
|
|
505
|
+
if (code === "translation_buffer_limit") {
|
|
506
|
+
throw new TranslatorBudgetExceededError("live_transient", TRANSLATOR_MAX_TURN_BYTES);
|
|
507
|
+
}
|
|
508
|
+
const status = code === "translation_buffer_limit"
|
|
509
|
+
? 413
|
|
510
|
+
: typeof error.status === "number" ? error.status : 500;
|
|
428
511
|
// status-absent response.failed (relaySseWithFailedTail synthetic tail) defaults
|
|
429
512
|
// to 500, which is in the transient set — the mid-stream reset shape maps to
|
|
430
513
|
// overloaded_error by design.
|
|
431
|
-
fail(
|
|
514
|
+
fail(
|
|
515
|
+
status,
|
|
516
|
+
message,
|
|
517
|
+
true,
|
|
518
|
+
);
|
|
432
519
|
break;
|
|
433
520
|
}
|
|
434
521
|
default:
|
|
@@ -442,26 +529,103 @@ export function responsesSseToAnthropicSse(
|
|
|
442
529
|
for (;;) {
|
|
443
530
|
const { done, value } = await reader.read();
|
|
444
531
|
if (done) break;
|
|
445
|
-
|
|
446
|
-
let
|
|
447
|
-
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
532
|
+
const decodedReservation = translatorBudget.reserveTransient(value.byteLength, { kind: "live_transient" });
|
|
533
|
+
let decodedCommitted = false;
|
|
534
|
+
try {
|
|
535
|
+
const fragment = decoder.decode(value, { stream: true });
|
|
536
|
+
decodedReservation.commitRetained();
|
|
537
|
+
decodedCommitted = true;
|
|
538
|
+
const nextBufferBytes = appendedUtf8Bytes(buffer, bufferBytes, fragment);
|
|
539
|
+
const appendReservation = translatorBudget.reserveTransient(nextBufferBytes, { kind: "live_transient" });
|
|
540
|
+
try {
|
|
541
|
+
buffer += fragment;
|
|
542
|
+
appendReservation.commitRetained();
|
|
543
|
+
translatorBudget.releaseRetained(bufferBytes, { kind: "live_transient" });
|
|
544
|
+
bufferBytes = nextBufferBytes;
|
|
545
|
+
} catch (error) {
|
|
546
|
+
appendReservation.release();
|
|
547
|
+
throw error;
|
|
548
|
+
}
|
|
549
|
+
let sep: number;
|
|
550
|
+
while ((sep = buffer.indexOf("\n\n")) !== -1) {
|
|
551
|
+
const rawFrameBytes = utf8SliceBytes(buffer, 0, sep);
|
|
552
|
+
const residualBytes = bufferBytes - rawFrameBytes - 2;
|
|
553
|
+
const rawReservation = translatorBudget.reserveTransient(rawFrameBytes, { kind: "live_transient" });
|
|
554
|
+
let residualReservation: ReturnType<TranslatorBudget["reserveTransient"]> | undefined;
|
|
555
|
+
try {
|
|
556
|
+
residualReservation = translatorBudget.reserveTransient(residualBytes, { kind: "live_transient" });
|
|
557
|
+
const rawFrame = buffer.slice(0, sep);
|
|
558
|
+
const residual = buffer.slice(sep + 2);
|
|
559
|
+
rawReservation.commitRetained();
|
|
560
|
+
residualReservation.commitRetained();
|
|
561
|
+
residualReservation = undefined;
|
|
562
|
+
buffer = residual;
|
|
563
|
+
translatorBudget.releaseRetained(bufferBytes, { kind: "live_transient" });
|
|
564
|
+
bufferBytes = residualBytes;
|
|
565
|
+
try {
|
|
566
|
+
let eventName = "";
|
|
567
|
+
let dataLine = "";
|
|
568
|
+
let dataLineBytes = 0;
|
|
569
|
+
try {
|
|
570
|
+
let lineStart = 0;
|
|
571
|
+
while (lineStart <= rawFrame.length) {
|
|
572
|
+
const newline = rawFrame.indexOf("\n", lineStart);
|
|
573
|
+
const lineEnd = newline === -1 ? rawFrame.length : newline;
|
|
574
|
+
if (rawFrame.startsWith("event: ", lineStart)) {
|
|
575
|
+
eventName = rawFrame.slice(lineStart + 7, lineEnd).trim();
|
|
576
|
+
} else if (rawFrame.startsWith("data: ", lineStart)) {
|
|
577
|
+
const fragmentStart = lineStart + 6;
|
|
578
|
+
const fragmentBytes = utf8SliceBytes(rawFrame, fragmentStart, lineEnd);
|
|
579
|
+
const fragmentReservation = translatorBudget.reserveTransient(fragmentBytes, { kind: "live_transient" });
|
|
580
|
+
let fragmentCommitted = false;
|
|
581
|
+
try {
|
|
582
|
+
const fragment = rawFrame.slice(fragmentStart, lineEnd);
|
|
583
|
+
fragmentReservation.commitRetained();
|
|
584
|
+
fragmentCommitted = true;
|
|
585
|
+
const nextDataLineBytes = appendedUtf8Bytes(dataLine, dataLineBytes, fragment);
|
|
586
|
+
const dataReservation = translatorBudget.reserveTransient(nextDataLineBytes, { kind: "live_transient" });
|
|
587
|
+
try {
|
|
588
|
+
dataLine += fragment;
|
|
589
|
+
dataReservation.commitRetained();
|
|
590
|
+
translatorBudget.releaseRetained(dataLineBytes, { kind: "live_transient" });
|
|
591
|
+
dataLineBytes = nextDataLineBytes;
|
|
592
|
+
} catch (error) {
|
|
593
|
+
dataReservation.release();
|
|
594
|
+
throw error;
|
|
595
|
+
}
|
|
596
|
+
} finally {
|
|
597
|
+
if (fragmentCommitted) {
|
|
598
|
+
translatorBudget.releaseRetained(fragmentBytes, { kind: "live_transient" });
|
|
599
|
+
} else fragmentReservation.release();
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
if (newline === -1) break;
|
|
603
|
+
lineStart = newline + 1;
|
|
604
|
+
}
|
|
605
|
+
if (!dataLine) continue;
|
|
606
|
+
let data: unknown;
|
|
607
|
+
try { data = JSON.parse(dataLine); } catch { continue; }
|
|
608
|
+
if (!isRec(data)) continue;
|
|
609
|
+
// Responses-compatible gateways may omit the optional SSE event field
|
|
610
|
+
// while retaining the event name in the JSON payload's required type.
|
|
611
|
+
const resolvedEventName = eventName || (typeof data.type === "string" ? data.type : "");
|
|
612
|
+
if (!resolvedEventName || terminated) continue;
|
|
613
|
+
handleFrame(resolvedEventName, data);
|
|
614
|
+
} finally {
|
|
615
|
+
translatorBudget.releaseRetained(dataLineBytes, { kind: "live_transient" });
|
|
616
|
+
}
|
|
617
|
+
} finally {
|
|
618
|
+
translatorBudget.releaseRetained(rawFrameBytes, { kind: "live_transient" });
|
|
619
|
+
}
|
|
620
|
+
} catch (error) {
|
|
621
|
+
rawReservation.release();
|
|
622
|
+
residualReservation?.release();
|
|
623
|
+
throw error;
|
|
624
|
+
}
|
|
455
625
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
if (!isRec(data)) continue;
|
|
460
|
-
// Responses-compatible gateways may omit the optional SSE event field
|
|
461
|
-
// while retaining the event name in the JSON payload's required type.
|
|
462
|
-
const resolvedEventName = eventName || (typeof data.type === "string" ? data.type : "");
|
|
463
|
-
if (!resolvedEventName || terminated) continue;
|
|
464
|
-
handleFrame(resolvedEventName, data);
|
|
626
|
+
} finally {
|
|
627
|
+
if (decodedCommitted) translatorBudget.releaseRetained(value.byteLength, { kind: "live_transient" });
|
|
628
|
+
else decodedReservation.release();
|
|
465
629
|
}
|
|
466
630
|
}
|
|
467
631
|
// EOF without a terminal frame is a TRUNCATION, not success (devlog 100:
|
|
@@ -470,16 +634,25 @@ export function responsesSseToAnthropicSse(
|
|
|
470
634
|
// with a mid-stream Anthropic error event so the client can retry.
|
|
471
635
|
if (!cancelled) fail(502, "upstream stream ended before a terminal frame (truncated response)", true);
|
|
472
636
|
} catch (err) {
|
|
473
|
-
|
|
637
|
+
if (isTranslatorBudgetExceededError(err)) {
|
|
638
|
+
try { await reader.cancel(err); } catch { /* already closed */ }
|
|
639
|
+
fail(413, "upstream translation buffer exceeded the safe limit", false, "translation_buffer_limit");
|
|
640
|
+
} else fail(500, err instanceof Error ? err.message : String(err));
|
|
474
641
|
} finally {
|
|
642
|
+
translatorBudget.releaseRetained(bufferBytes, { kind: "live_transient" });
|
|
475
643
|
if (pingTimer !== undefined) clearInterval(pingTimer);
|
|
476
644
|
reader.releaseLock();
|
|
477
645
|
if (!cancelled) controller.close();
|
|
478
646
|
}
|
|
479
647
|
})();
|
|
480
648
|
},
|
|
649
|
+
pull() {
|
|
650
|
+
releaseDeliveredFrame();
|
|
651
|
+
},
|
|
481
652
|
cancel(reason) {
|
|
482
653
|
cancelled = true;
|
|
654
|
+
while (queuedLiveFrameBytes.length > 0) releaseDeliveredFrame();
|
|
655
|
+
if (open?.callId) translatorBudget.closeCall(open.callId);
|
|
483
656
|
if (pingTimer !== undefined) clearInterval(pingTimer);
|
|
484
657
|
return reader?.cancel(reason);
|
|
485
658
|
},
|
|
@@ -587,7 +760,11 @@ export function responsesJsonToAnthropicMessage(json: unknown, model: string): R
|
|
|
587
760
|
* (routed adapters do not support non-stream turns), so the translated stream is
|
|
588
761
|
* aggregated here instead of translating a JSON body.
|
|
589
762
|
*/
|
|
590
|
-
export async function collectAnthropicMessage(
|
|
763
|
+
export async function collectAnthropicMessage(
|
|
764
|
+
stream: ReadableStream<Uint8Array>,
|
|
765
|
+
model: string,
|
|
766
|
+
translatorBudget: TranslatorBudget,
|
|
767
|
+
): Promise<Rec> {
|
|
591
768
|
const decoder = new TextDecoder();
|
|
592
769
|
const reader = stream.getReader();
|
|
593
770
|
let buffer = "";
|
|
@@ -597,6 +774,12 @@ export async function collectAnthropicMessage(stream: ReadableStream<Uint8Array>
|
|
|
597
774
|
let stopReason: string | null = "end_turn";
|
|
598
775
|
let usage: Rec = anthropicUsage(undefined);
|
|
599
776
|
let error: Rec | null = null;
|
|
777
|
+
const replaceRetained = (previous: string, next: string, kind: "live_transient" | "retained_collectors") => {
|
|
778
|
+
const reservation = translatorBudget.reserveTransient(Buffer.byteLength(next), { kind });
|
|
779
|
+
reservation.commitRetained();
|
|
780
|
+
translatorBudget.releaseRetained(Buffer.byteLength(previous), { kind });
|
|
781
|
+
return next;
|
|
782
|
+
};
|
|
600
783
|
|
|
601
784
|
const closeBlock = () => {
|
|
602
785
|
if (!openBlock) return;
|
|
@@ -604,6 +787,7 @@ export async function collectAnthropicMessage(stream: ReadableStream<Uint8Array>
|
|
|
604
787
|
if (openBlock.type === "tool_use" || openBlock.type === "server_tool_use") {
|
|
605
788
|
try { openBlock.input = toolJson.length > 0 ? JSON.parse(toolJson) : {}; } catch { openBlock.input = {}; }
|
|
606
789
|
}
|
|
790
|
+
translatorBudget.chargeRetained(Buffer.byteLength(JSON.stringify(openBlock)), { kind: "retained_collectors" });
|
|
607
791
|
content.push(openBlock);
|
|
608
792
|
openBlock = null;
|
|
609
793
|
toolJson = "";
|
|
@@ -619,13 +803,15 @@ export async function collectAnthropicMessage(stream: ReadableStream<Uint8Array>
|
|
|
619
803
|
const delta = isRec(data.delta) ? data.delta : {};
|
|
620
804
|
if (!openBlock) break;
|
|
621
805
|
if (delta.type === "text_delta" && typeof delta.text === "string") {
|
|
622
|
-
openBlock.text
|
|
806
|
+
const previous = typeof openBlock.text === "string" ? openBlock.text : "";
|
|
807
|
+
openBlock.text = replaceRetained(previous, previous + delta.text, "retained_collectors");
|
|
623
808
|
} else if (delta.type === "thinking_delta" && typeof delta.thinking === "string") {
|
|
624
|
-
openBlock.thinking
|
|
809
|
+
const previous = typeof openBlock.thinking === "string" ? openBlock.thinking : "";
|
|
810
|
+
openBlock.thinking = replaceRetained(previous, previous + delta.thinking, "retained_collectors");
|
|
625
811
|
} else if (delta.type === "signature_delta" && typeof delta.signature === "string") {
|
|
626
812
|
openBlock.signature = delta.signature;
|
|
627
813
|
} else if (delta.type === "input_json_delta" && typeof delta.partial_json === "string") {
|
|
628
|
-
toolJson
|
|
814
|
+
toolJson = replaceRetained(toolJson, toolJson + delta.partial_json, "retained_collectors");
|
|
629
815
|
}
|
|
630
816
|
break;
|
|
631
817
|
}
|
|
@@ -650,11 +836,11 @@ export async function collectAnthropicMessage(stream: ReadableStream<Uint8Array>
|
|
|
650
836
|
for (;;) {
|
|
651
837
|
const { done, value } = await reader.read();
|
|
652
838
|
if (done) break;
|
|
653
|
-
buffer
|
|
839
|
+
buffer = replaceRetained(buffer, buffer + decoder.decode(value, { stream: true }), "live_transient");
|
|
654
840
|
let sep: number;
|
|
655
841
|
while ((sep = buffer.indexOf("\n\n")) !== -1) {
|
|
656
842
|
const rawFrame = buffer.slice(0, sep);
|
|
657
|
-
buffer = buffer.slice(sep + 2);
|
|
843
|
+
buffer = replaceRetained(buffer, buffer.slice(sep + 2), "live_transient");
|
|
658
844
|
let eventName = "";
|
|
659
845
|
let dataLine = "";
|
|
660
846
|
for (const line of rawFrame.split("\n")) {
|
package/src/cli/agent-driven.ts
CHANGED
|
@@ -13,19 +13,44 @@
|
|
|
13
13
|
* the user.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* Env vars set by agent harnesses and CI runners inside the shell they spawn.
|
|
18
|
+
*
|
|
19
|
+
* The list errs wide on purpose. A harness that is missed here is a harness
|
|
20
|
+
* whose model answers a consent question with the user's GitHub identity, which
|
|
21
|
+
* is the failure this module exists to prevent; a harness matched by mistake
|
|
22
|
+
* only postpones a prompt to the user's next hand-typed run.
|
|
23
|
+
*/
|
|
17
24
|
const AGENT_ENV_VARS = [
|
|
25
|
+
// Claude Code
|
|
18
26
|
"CLAUDECODE",
|
|
19
27
|
"CLAUDE_CODE_ENTRYPOINT",
|
|
28
|
+
"CLAUDE_CODE_SSE_PORT",
|
|
29
|
+
// Codex CLI / App / SDK. `CODEX_HOME` is deliberately NOT here: users export it
|
|
30
|
+
// from their own shell profile, so matching it would suppress the prompt for a
|
|
31
|
+
// person who is sitting right there.
|
|
20
32
|
"CODEX_THREAD_ID",
|
|
21
33
|
"CODEX_SHELL",
|
|
22
34
|
"CODEX_CI",
|
|
35
|
+
"CODEX_SANDBOX",
|
|
36
|
+
"CODEX_SANDBOX_NETWORK_DISABLED",
|
|
37
|
+
// Cursor
|
|
23
38
|
"CURSOR_TRACE_ID",
|
|
24
39
|
"CURSOR_SESSION_TOKEN",
|
|
40
|
+
"CURSOR_AGENT",
|
|
41
|
+
// Other coding agents
|
|
25
42
|
"AIDER_CHAT",
|
|
43
|
+
"OPENCODE_BIN_PATH",
|
|
44
|
+
"GEMINI_CLI",
|
|
45
|
+
// Generic runners and hosted environments that are never a person at a keyboard
|
|
26
46
|
"REPL_ID",
|
|
27
47
|
"CI",
|
|
28
48
|
"GITHUB_ACTIONS",
|
|
49
|
+
"GITLAB_CI",
|
|
50
|
+
"BUILDKITE",
|
|
51
|
+
"JENKINS_URL",
|
|
52
|
+
"TEAMCITY_VERSION",
|
|
53
|
+
"CODESPACES",
|
|
29
54
|
] as const;
|
|
30
55
|
|
|
31
56
|
/**
|
|
@@ -35,3 +60,11 @@ const AGENT_ENV_VARS = [
|
|
|
35
60
|
export function isAgentDriven(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
36
61
|
return AGENT_ENV_VARS.some(name => (env[name] ?? "").trim() !== "");
|
|
37
62
|
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The env-var names that mark this process as agent-driven. Empty for a real
|
|
66
|
+
* hand-typed run. Callers that refuse an action use it to name the reason.
|
|
67
|
+
*/
|
|
68
|
+
export function agentDrivenMarkers(env: NodeJS.ProcessEnv = process.env): string[] {
|
|
69
|
+
return AGENT_ENV_VARS.filter(name => (env[name] ?? "").trim() !== "");
|
|
70
|
+
}
|
|
@@ -19,6 +19,9 @@ export function scheduleCatalogPrewarm(deps: CatalogPrewarmDeps = {}): void {
|
|
|
19
19
|
const { gatherRoutedModels } = await (deps.importCatalog?.() ?? import("../codex/catalog"));
|
|
20
20
|
return gatherRoutedModels(load());
|
|
21
21
|
})
|
|
22
|
-
.catch(
|
|
22
|
+
.catch(error => {
|
|
23
|
+
if (error && typeof error === "object" && (error as { code?: unknown }).code === "catalog_busy") {
|
|
24
|
+
console.warn("[catalog] startup discovery skipped: catalog gather is busy");
|
|
25
|
+
}
|
|
26
|
+
});
|
|
23
27
|
}
|
|
24
|
-
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
type DesktopProfile,
|
|
11
11
|
} from "../claude/desktop-profile";
|
|
12
12
|
import { writeDesktop3pConfig, type Desktop3pConfigMode, parseDesktop3pModeArgs } from "../claude/desktop-3p";
|
|
13
|
-
import { filterCatalogVisibleModels,
|
|
13
|
+
import { filterCatalogVisibleModels, desktopVisibleNativeSlugs } from "../codex/catalog";
|
|
14
14
|
import { buildClaudeDesktopState, fetchAllModels } from "../server/management-api";
|
|
15
15
|
import { findLiveProxy } from "../server/proxy-liveness";
|
|
16
16
|
|
|
@@ -42,7 +42,7 @@ async function applyProfile(profile: DesktopProfile, mode: Desktop3pConfigMode):
|
|
|
42
42
|
}));
|
|
43
43
|
const result = writeDesktop3pConfig(
|
|
44
44
|
live?.port ?? config.port ?? 10100,
|
|
45
|
-
[...
|
|
45
|
+
[...desktopVisibleNativeSlugs(config)],
|
|
46
46
|
routed,
|
|
47
47
|
config.apiKeys?.[0]?.key,
|
|
48
48
|
mode,
|
package/src/cli/doctor.ts
CHANGED
|
@@ -149,6 +149,18 @@ export async function collectOAuthDoctorChecks(
|
|
|
149
149
|
message:
|
|
150
150
|
"Codex account health unavailable (proxy not running). Action: start the proxy and re-run `ocx doctor` to inspect live cooldown/reauth",
|
|
151
151
|
});
|
|
152
|
+
} else if (report.codexHealthSource === "management-auth-failed") {
|
|
153
|
+
checks.push({
|
|
154
|
+
level: "WARN",
|
|
155
|
+
message:
|
|
156
|
+
"Codex account health unavailable (proxy running; management authentication failed). Action: verify the admin token configuration, restart the proxy, and re-run `ocx doctor`",
|
|
157
|
+
});
|
|
158
|
+
} else if (report.codexHealthSource === "management-api-unavailable") {
|
|
159
|
+
checks.push({
|
|
160
|
+
level: "WARN",
|
|
161
|
+
message:
|
|
162
|
+
"Codex account health unavailable (proxy running; management API response failed). Action: inspect the proxy service log, restart the proxy if needed, and re-run `ocx doctor`",
|
|
163
|
+
});
|
|
152
164
|
}
|
|
153
165
|
for (const entry of report.entries) {
|
|
154
166
|
if (entry.health.status === "healthy") continue;
|