@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,5 +1,6 @@
|
|
|
1
1
|
import { collectResponsesToolGroups } from "../responses/tool-groups";
|
|
2
2
|
import { relaySseWithPayloadRewrite, type SsePayloadRewrite } from "./sse-payload-rewrite";
|
|
3
|
+
import type { TranslatorBudget } from "../lib/translator-budget";
|
|
3
4
|
|
|
4
5
|
interface NamespacedTool {
|
|
5
6
|
namespace: string;
|
|
@@ -22,12 +23,24 @@ function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
|
22
23
|
export function imageGenToolCallAliases(
|
|
23
24
|
toolNsMap: ReadonlyMap<string, NamespacedTool>,
|
|
24
25
|
requestBody?: unknown,
|
|
26
|
+
translatorBudget?: TranslatorBudget,
|
|
25
27
|
): Map<string, NamespacedTool> {
|
|
26
28
|
const aliases = new Map<string, NamespacedTool>();
|
|
29
|
+
const retainAlias = (key: string, tool: NamespacedTool) => {
|
|
30
|
+
if (aliases.has(key)) {
|
|
31
|
+
aliases.set(key, tool);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
translatorBudget?.chargeRetained(
|
|
35
|
+
Buffer.byteLength(key) + Buffer.byteLength(tool.namespace) + Buffer.byteLength(tool.name),
|
|
36
|
+
{ kind: "request_copies" },
|
|
37
|
+
);
|
|
38
|
+
aliases.set(key, tool);
|
|
39
|
+
};
|
|
27
40
|
for (const [wireName, tool] of toolNsMap) {
|
|
28
41
|
if (tool.namespace !== IMAGE_GEN_NAMESPACE) continue;
|
|
29
|
-
|
|
30
|
-
|
|
42
|
+
retainAlias(wireName, tool);
|
|
43
|
+
retainAlias(`${tool.namespace}.${tool.name}`, tool);
|
|
31
44
|
}
|
|
32
45
|
for (const group of collectResponsesToolGroups(requestBody)) {
|
|
33
46
|
for (const tool of group) {
|
|
@@ -40,8 +53,8 @@ export function imageGenToolCallAliases(
|
|
|
40
53
|
) continue;
|
|
41
54
|
const localName = tool.name.slice(IMAGE_GEN_DOTTED_PREFIX.length);
|
|
42
55
|
const target = { namespace: IMAGE_GEN_NAMESPACE, name: localName };
|
|
43
|
-
|
|
44
|
-
|
|
56
|
+
retainAlias(`${IMAGE_GEN_NAMESPACE}__${localName}`, target);
|
|
57
|
+
retainAlias(tool.name, target);
|
|
45
58
|
}
|
|
46
59
|
}
|
|
47
60
|
return aliases;
|
|
@@ -112,7 +125,8 @@ export function createImageGenCallRestoreRewrite(
|
|
|
112
125
|
export function relaySseWithImageGenCallRestore(
|
|
113
126
|
body: ReadableStream<Uint8Array>,
|
|
114
127
|
aliases: ReadonlyMap<string, NamespacedTool>,
|
|
128
|
+
translatorBudget: TranslatorBudget,
|
|
115
129
|
): ReadableStream<Uint8Array> {
|
|
116
130
|
const rewrite = createImageGenCallRestoreRewrite(aliases);
|
|
117
|
-
return rewrite ? relaySseWithPayloadRewrite(body, rewrite) : body;
|
|
131
|
+
return rewrite ? relaySseWithPayloadRewrite(body, rewrite, translatorBudget) : body;
|
|
118
132
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import type { ResponsesItemIdRepairConfig } from "../types";
|
|
3
3
|
import { relaySseWithPayloadRewrite, type SsePayloadRewrite } from "./sse-payload-rewrite";
|
|
4
|
+
import type { TranslatorBudget } from "../lib/translator-budget";
|
|
4
5
|
|
|
5
6
|
type RepairableItemType = "message" | "reasoning";
|
|
6
7
|
|
|
@@ -9,6 +10,7 @@ interface ResponsesItemIdRepairState {
|
|
|
9
10
|
readonly placeholders: Record<RepairableItemType, ReadonlySet<string>>;
|
|
10
11
|
readonly outputIds: Record<RepairableItemType, Map<number, string>>;
|
|
11
12
|
readonly scope: string;
|
|
13
|
+
readonly budget?: TranslatorBudget;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
const REPAIRABLE_PREFIXES: Record<RepairableItemType, string> = {
|
|
@@ -48,8 +50,8 @@ function mintCanonicalId(type: RepairableItemType, scope: string, outputIndex: n
|
|
|
48
50
|
return `${REPAIRABLE_PREFIXES[type]}ocx_${scope}_${outputIndex}`;
|
|
49
51
|
}
|
|
50
52
|
|
|
51
|
-
function createRepairState(config: ResponsesItemIdRepairConfig): ResponsesItemIdRepairState {
|
|
52
|
-
|
|
53
|
+
function createRepairState(config: ResponsesItemIdRepairConfig, budget?: TranslatorBudget): ResponsesItemIdRepairState {
|
|
54
|
+
const state = {
|
|
53
55
|
repairMissingTerminalIds: config.repairMissingTerminalIds === true,
|
|
54
56
|
placeholders: {
|
|
55
57
|
message: new Set(config.message ?? []),
|
|
@@ -60,7 +62,14 @@ function createRepairState(config: ResponsesItemIdRepairConfig): ResponsesItemId
|
|
|
60
62
|
reasoning: new Map<number, string>(),
|
|
61
63
|
},
|
|
62
64
|
scope: randomUUID().replace(/-/g, ""),
|
|
65
|
+
budget,
|
|
63
66
|
};
|
|
67
|
+
budget?.chargeRetained(new TextEncoder().encode(JSON.stringify({
|
|
68
|
+
message: [...state.placeholders.message],
|
|
69
|
+
reasoning: [...state.placeholders.reasoning],
|
|
70
|
+
scope: state.scope,
|
|
71
|
+
})).byteLength, { kind: "item_ids" });
|
|
72
|
+
return state;
|
|
64
73
|
}
|
|
65
74
|
|
|
66
75
|
function rememberMappedId(
|
|
@@ -80,6 +89,7 @@ function rememberMappedId(
|
|
|
80
89
|
? rawId
|
|
81
90
|
: null;
|
|
82
91
|
if (!mapped) return null;
|
|
92
|
+
state.budget?.chargeRetained(new TextEncoder().encode(JSON.stringify([outputIndex, rawId, mapped])).byteLength, { kind: "item_ids" });
|
|
83
93
|
state.outputIds[type].set(outputIndex, mapped);
|
|
84
94
|
return mapped;
|
|
85
95
|
}
|
|
@@ -163,7 +173,13 @@ function repairEventPayload(
|
|
|
163
173
|
changed = true;
|
|
164
174
|
}
|
|
165
175
|
}
|
|
166
|
-
|
|
176
|
+
if (!changed) return payload;
|
|
177
|
+
const rewritten = JSON.stringify(nextEvent);
|
|
178
|
+
const bytes = new TextEncoder().encode(rewritten).byteLength;
|
|
179
|
+
const reservation = state.budget?.reserveTransient(bytes, { kind: "item_ids" });
|
|
180
|
+
reservation?.commitRetained();
|
|
181
|
+
if (state.budget) queueMicrotask(() => state.budget?.releaseRetained(bytes, { kind: "item_ids" }));
|
|
182
|
+
return rewritten;
|
|
167
183
|
}
|
|
168
184
|
|
|
169
185
|
/**
|
|
@@ -187,16 +203,18 @@ function repairEventPayload(
|
|
|
187
203
|
/** Stateful payload rewrite for composition with other client-facing SSE transforms. */
|
|
188
204
|
export function createResponsesItemIdPayloadRewrite(
|
|
189
205
|
config: ResponsesItemIdRepairConfig,
|
|
206
|
+
budget?: TranslatorBudget,
|
|
190
207
|
): SsePayloadRewrite {
|
|
191
|
-
const state = createRepairState(config);
|
|
208
|
+
const state = createRepairState(config, budget);
|
|
192
209
|
return (payload) => repairEventPayload(payload, state);
|
|
193
210
|
}
|
|
194
211
|
|
|
195
212
|
export function relaySseWithResponsesItemIdRepair(
|
|
196
213
|
body: ReadableStream<Uint8Array>,
|
|
197
214
|
config: ResponsesItemIdRepairConfig,
|
|
215
|
+
budget: TranslatorBudget,
|
|
198
216
|
): ReadableStream<Uint8Array> {
|
|
199
|
-
return relaySseWithPayloadRewrite(body, createResponsesItemIdPayloadRewrite(config));
|
|
217
|
+
return relaySseWithPayloadRewrite(body, createResponsesItemIdPayloadRewrite(config, budget), budget);
|
|
200
218
|
}
|
|
201
219
|
|
|
202
220
|
export function hasResponsesItemIdRepair(config: ResponsesItemIdRepairConfig | undefined): boolean {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { TranslatorBudget } from "../lib/translator-budget";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Shared client-facing SSE payload rewrite shell.
|
|
3
5
|
*
|
|
@@ -66,11 +68,60 @@ export function composeSsePayloadRewrites(...rewrites: SsePayloadRewrite[]): Sse
|
|
|
66
68
|
export function relaySseWithPayloadRewrite(
|
|
67
69
|
body: ReadableStream<Uint8Array>,
|
|
68
70
|
rewrite: SsePayloadRewrite,
|
|
71
|
+
translatorBudget: TranslatorBudget,
|
|
69
72
|
): ReadableStream<Uint8Array> {
|
|
70
73
|
const reader = body.getReader();
|
|
71
74
|
const decoder = new TextDecoder();
|
|
72
75
|
const encoder = new TextEncoder();
|
|
73
76
|
let buffer = "";
|
|
77
|
+
let bufferBytes = 0;
|
|
78
|
+
|
|
79
|
+
const appendBuffer = (fragment: string): void => {
|
|
80
|
+
if (!fragment) return;
|
|
81
|
+
const nextBytes = bufferBytes + encoder.encode(fragment).byteLength;
|
|
82
|
+
const reservation = translatorBudget.reserveTransient(nextBytes, { kind: "live_transient" });
|
|
83
|
+
try {
|
|
84
|
+
buffer += fragment;
|
|
85
|
+
reservation.commitRetained();
|
|
86
|
+
translatorBudget.releaseRetained(bufferBytes, { kind: "live_transient" });
|
|
87
|
+
bufferBytes = nextBytes;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
reservation.release();
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const replaceBuffer = (next: string): void => {
|
|
95
|
+
const nextBytes = encoder.encode(next).byteLength;
|
|
96
|
+
const reservation = translatorBudget.reserveTransient(nextBytes, { kind: "live_transient" });
|
|
97
|
+
reservation.commitRetained();
|
|
98
|
+
buffer = next;
|
|
99
|
+
translatorBudget.releaseRetained(bufferBytes, { kind: "live_transient" });
|
|
100
|
+
bufferBytes = nextBytes;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const enqueueText = (
|
|
104
|
+
controller: ReadableStreamDefaultController<Uint8Array>,
|
|
105
|
+
text: string,
|
|
106
|
+
): void => {
|
|
107
|
+
const bytes = encoder.encode(text).byteLength;
|
|
108
|
+
const reservation = translatorBudget.reserveTransient(bytes, { kind: "live_transient" });
|
|
109
|
+
try {
|
|
110
|
+
const encoded = encoder.encode(text);
|
|
111
|
+
reservation.commitRetained();
|
|
112
|
+
controller.enqueue(encoded);
|
|
113
|
+
translatorBudget.releaseRetained(bytes, { kind: "live_transient" });
|
|
114
|
+
} catch (error) {
|
|
115
|
+
reservation.release();
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const releaseBuffer = (): void => {
|
|
121
|
+
translatorBudget.releaseRetained(bufferBytes, { kind: "live_transient" });
|
|
122
|
+
buffer = "";
|
|
123
|
+
bufferBytes = 0;
|
|
124
|
+
};
|
|
74
125
|
|
|
75
126
|
const emitProcessedBlocks = (
|
|
76
127
|
controller: ReadableStreamDefaultController<Uint8Array>,
|
|
@@ -78,13 +129,13 @@ export function relaySseWithPayloadRewrite(
|
|
|
78
129
|
): void => {
|
|
79
130
|
let next: { block: string; delimiter: string; rest: string } | null;
|
|
80
131
|
while ((next = nextSseBlock(buffer))) {
|
|
81
|
-
|
|
132
|
+
replaceBuffer(next.rest);
|
|
82
133
|
const payload = sseDataPayload(next.block);
|
|
83
134
|
const rewrittenPayload = payload ? rewrite(payload) : undefined;
|
|
84
135
|
const block = payload && rewrittenPayload !== undefined && rewrittenPayload !== payload
|
|
85
136
|
? replaceSseDataPayload(next.block, rewrittenPayload)
|
|
86
137
|
: next.block;
|
|
87
|
-
controller
|
|
138
|
+
enqueueText(controller, block + next.delimiter);
|
|
88
139
|
}
|
|
89
140
|
if (flushFinal && buffer.length > 0) {
|
|
90
141
|
const payload = sseDataPayload(buffer);
|
|
@@ -92,24 +143,32 @@ export function relaySseWithPayloadRewrite(
|
|
|
92
143
|
const block = payload && rewrittenPayload !== undefined && rewrittenPayload !== payload
|
|
93
144
|
? replaceSseDataPayload(buffer, rewrittenPayload)
|
|
94
145
|
: buffer;
|
|
95
|
-
controller
|
|
96
|
-
|
|
146
|
+
enqueueText(controller, block);
|
|
147
|
+
releaseBuffer();
|
|
97
148
|
}
|
|
98
149
|
};
|
|
99
150
|
|
|
100
151
|
return new ReadableStream<Uint8Array>({
|
|
101
152
|
async pull(controller) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
153
|
+
try {
|
|
154
|
+
const { done, value } = await reader.read();
|
|
155
|
+
if (done) {
|
|
156
|
+
appendBuffer(decoder.decode());
|
|
157
|
+
emitProcessedBlocks(controller, true);
|
|
158
|
+
releaseBuffer();
|
|
159
|
+
controller.close();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
appendBuffer(decoder.decode(value, { stream: true }));
|
|
163
|
+
emitProcessedBlocks(controller);
|
|
164
|
+
} catch (error) {
|
|
165
|
+
releaseBuffer();
|
|
166
|
+
try { await reader.cancel(error); } catch { /* already closed */ }
|
|
167
|
+
controller.error(error);
|
|
108
168
|
}
|
|
109
|
-
buffer += decoder.decode(value, { stream: true });
|
|
110
|
-
emitProcessedBlocks(controller);
|
|
111
169
|
},
|
|
112
170
|
cancel(reason) {
|
|
171
|
+
releaseBuffer();
|
|
113
172
|
reader.cancel(reason).catch(() => {});
|
|
114
173
|
},
|
|
115
174
|
});
|
|
@@ -6,9 +6,11 @@ import { getCodexRoutingKind } from "../codex/inject";
|
|
|
6
6
|
import { diagnoseCodexShim } from "../codex/shim";
|
|
7
7
|
import { durableBunPath } from "../lib/bun-runtime";
|
|
8
8
|
import type { OcxConfig } from "../types";
|
|
9
|
+
import { truncateRetainedUtf8 } from "../lib/admission";
|
|
9
10
|
|
|
10
11
|
const CACHE_TTL_MS = 30_000;
|
|
11
12
|
const PROBE_TIMEOUT_MS = 5_000;
|
|
13
|
+
const MAX_DIAGNOSTIC_VALUE_BYTES = 8 * 1024;
|
|
12
14
|
let cached: { timestamp: number; value: StartupHealth } | null = null;
|
|
13
15
|
let inflight: Promise<StartupHealth> | null = null;
|
|
14
16
|
let generation = 0;
|
|
@@ -63,7 +65,18 @@ function runProbe(config: Pick<OcxConfig, "codexAutoStart">): Promise<StartupHea
|
|
|
63
65
|
try {
|
|
64
66
|
const parsed = JSON.parse(lines[index]) as StartupHealth;
|
|
65
67
|
if (["native", "protected", "at-risk"].includes(parsed.status) && typeof parsed.rebootSafe === "boolean") {
|
|
66
|
-
resolve({
|
|
68
|
+
resolve({
|
|
69
|
+
...parsed,
|
|
70
|
+
recommendedCommand: parsed.recommendedCommand === null
|
|
71
|
+
? null
|
|
72
|
+
: truncateRetainedUtf8(parsed.recommendedCommand, MAX_DIAGNOSTIC_VALUE_BYTES),
|
|
73
|
+
commands: {
|
|
74
|
+
installService: truncateRetainedUtf8(parsed.commands.installService, MAX_DIAGNOSTIC_VALUE_BYTES),
|
|
75
|
+
installShim: truncateRetainedUtf8(parsed.commands.installShim, MAX_DIAGNOSTIC_VALUE_BYTES),
|
|
76
|
+
restoreNative: truncateRetainedUtf8(parsed.commands.restoreNative, MAX_DIAGNOSTIC_VALUE_BYTES),
|
|
77
|
+
},
|
|
78
|
+
diagnosticStale: false,
|
|
79
|
+
});
|
|
67
80
|
return;
|
|
68
81
|
}
|
|
69
82
|
} catch { /* scan earlier output; config repair messages may precede JSON */ }
|
package/src/server/system-env.ts
CHANGED
|
@@ -236,7 +236,14 @@ async function computeEffectiveModelEnv(config: OcxConfig, auto?: AutoContextMod
|
|
|
236
236
|
const { boundedContextWindows, buildClaudeContextWindows, effectiveModelEnv } = await import("../claude/context-windows");
|
|
237
237
|
const windows = await boundedContextWindows(async () => {
|
|
238
238
|
const { gatherRoutedModels, visibleNativeSlugs } = await import("../codex/catalog");
|
|
239
|
-
|
|
239
|
+
try {
|
|
240
|
+
return buildClaudeContextWindows([...visibleNativeSlugs(config)], await gatherRoutedModels(config));
|
|
241
|
+
} catch (error) {
|
|
242
|
+
if (error && typeof error === "object" && (error as { code?: unknown }).code === "catalog_busy") {
|
|
243
|
+
return buildClaudeContextWindows([...visibleNativeSlugs(config)], []);
|
|
244
|
+
}
|
|
245
|
+
throw error;
|
|
246
|
+
}
|
|
240
247
|
});
|
|
241
248
|
return { modelEnv: effectiveModelEnv(config.claudeCode, windows ?? {}, auto), windows: windows ?? {} };
|
|
242
249
|
}
|
|
@@ -79,6 +79,12 @@ export type WindowsTcpDropResult = {
|
|
|
79
79
|
dropped: number;
|
|
80
80
|
/** IPv6 (or unparseable) rows skipped — never coerced into IPv4 wildcards. */
|
|
81
81
|
skippedIpv6: number;
|
|
82
|
+
/**
|
|
83
|
+
* SetTcpEntry returned ERROR_ACCESS_DENIED (5) or the UAC-non-elevated code (317).
|
|
84
|
+
* On a normal (non-admin) update worker this is expected — ghost LISTEN rows must
|
|
85
|
+
* clear by waiting for the OS, not by SetTcpEntry.
|
|
86
|
+
*/
|
|
87
|
+
accessDenied: number;
|
|
82
88
|
};
|
|
83
89
|
|
|
84
90
|
function htons(port: number): number {
|
|
@@ -133,21 +139,22 @@ function readNetstatAno(): string {
|
|
|
133
139
|
*/
|
|
134
140
|
export function dropWindowsTcpRowsForLocalPort(port: number): WindowsTcpDropResult {
|
|
135
141
|
if (process.platform !== "win32" || !Number.isFinite(port) || port <= 0) {
|
|
136
|
-
return { dropped: 0, skippedIpv6: 0 };
|
|
142
|
+
return { dropped: 0, skippedIpv6: 0, accessDenied: 0 };
|
|
137
143
|
}
|
|
138
144
|
const setTcpEntry = loadSetTcpEntry();
|
|
139
|
-
if (!setTcpEntry) return { dropped: 0, skippedIpv6: 0 };
|
|
145
|
+
if (!setTcpEntry) return { dropped: 0, skippedIpv6: 0, accessDenied: 0 };
|
|
140
146
|
|
|
141
147
|
let output = "";
|
|
142
148
|
try {
|
|
143
149
|
output = readNetstatAno();
|
|
144
150
|
} catch {
|
|
145
|
-
return { dropped: 0, skippedIpv6: 0 };
|
|
151
|
+
return { dropped: 0, skippedIpv6: 0, accessDenied: 0 };
|
|
146
152
|
}
|
|
147
153
|
|
|
148
154
|
const rows = parseTcpQuadsForLocalPort(output, Math.trunc(port));
|
|
149
155
|
let dropped = 0;
|
|
150
156
|
let skippedIpv6 = 0;
|
|
157
|
+
let accessDenied = 0;
|
|
151
158
|
for (const row of rows) {
|
|
152
159
|
const localDw = ipv4ToWinUint32(row.localAddr);
|
|
153
160
|
const remoteDw = ipv4ToWinUint32(row.remoteAddr);
|
|
@@ -165,10 +172,13 @@ export function dropWindowsTcpRowsForLocalPort(port: number): WindowsTcpDropResu
|
|
|
165
172
|
view.setUint32(12, remoteDw, true);
|
|
166
173
|
view.setUint32(16, htons(row.remotePort), true);
|
|
167
174
|
try {
|
|
168
|
-
|
|
175
|
+
const rc = setTcpEntry(ptr(buf));
|
|
176
|
+
if (rc === 0) dropped += 1;
|
|
177
|
+
// 5 = ERROR_ACCESS_DENIED, 317 = non-elevated SetTcpEntry (MSDN).
|
|
178
|
+
else if (rc === 5 || rc === 317) accessDenied += 1;
|
|
169
179
|
} catch {
|
|
170
180
|
/* keep going */
|
|
171
181
|
}
|
|
172
182
|
}
|
|
173
|
-
return { dropped, skippedIpv6 };
|
|
183
|
+
return { dropped, skippedIpv6, accessDenied };
|
|
174
184
|
}
|
package/src/server/ws-bridge.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { FORWARD_HEADERS } from "../adapters/openai-responses";
|
|
|
3
3
|
import type { CodexAuthContext } from "../codex/auth-context";
|
|
4
4
|
import { headersForCodexAuthContext } from "../codex/auth-context";
|
|
5
5
|
import type { ResponsesTerminalStatus } from "../bridge";
|
|
6
|
+
import type { DataPlaneAdmission } from "./auth-cors";
|
|
7
|
+
import type { AdmissionReservation } from "../lib/admission";
|
|
6
8
|
|
|
7
9
|
const OPEN = 1;
|
|
8
10
|
type ResponsesTerminalReporter = (status: ResponsesTerminalStatus) => void;
|
|
@@ -19,6 +21,14 @@ const SAFE_RESPONSE_HEADER_EXACT = new Set([
|
|
|
19
21
|
|
|
20
22
|
export interface WsData {
|
|
21
23
|
headers?: Headers; // base inbound forward headers only; per-turn auth refresh injects current pool tokens
|
|
24
|
+
/**
|
|
25
|
+
* Resolved once at the handshake. Auth is handshake-time only on this path, so
|
|
26
|
+
* the per-frame log contexts have no request headers left to re-resolve from.
|
|
27
|
+
* Optional like every other member here: a socket object can exist before the
|
|
28
|
+
* handshake fills it, and an unattributed frame is preferable to a fabricated
|
|
29
|
+
* attribution.
|
|
30
|
+
*/
|
|
31
|
+
admission?: DataPlaneAdmission;
|
|
22
32
|
authContext?: CodexAuthContext; // last resolved account decision for observability/registry cleanup
|
|
23
33
|
cancel?: () => void; // cancels the in-flight stream reader/fetch
|
|
24
34
|
turnId?: number; // monotonically increasing per socket; prevents stale frames after replacement turns
|
|
@@ -29,6 +39,21 @@ export interface WsData {
|
|
|
29
39
|
liveUpstreamHeaders?: Record<string, string>;
|
|
30
40
|
livePending?: Array<string | Buffer>;
|
|
31
41
|
liveOpened?: boolean;
|
|
42
|
+
admissionLease?: AdmissionReservation<ServerWebSocket<WsData>>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Build the Responses WebSocket upgrade payload.
|
|
47
|
+
*
|
|
48
|
+
* Extracted so the handshake's contract is testable: `server.upgrade` hands its
|
|
49
|
+
* `data` straight to the socket, and a client has no way to read `ws.data` back.
|
|
50
|
+
* A test that only asserts "the socket opened" would still pass if the admission
|
|
51
|
+
* were dropped from the payload, so the payload itself is what gets asserted.
|
|
52
|
+
*/
|
|
53
|
+
export function buildResponsesWsData(headers: Headers, admission: DataPlaneAdmission, admissionLease?: AdmissionReservation<ServerWebSocket<WsData>>): WsData {
|
|
54
|
+
// Auth is handshake-time only on this path: the per-frame contexts have no
|
|
55
|
+
// request headers left to re-resolve from, so the decision rides along here.
|
|
56
|
+
return { headers, admission, ...(admissionLease ? { admissionLease } : {}) };
|
|
32
57
|
}
|
|
33
58
|
|
|
34
59
|
export class WsSendDroppedError extends Error {
|