@bitkyc08/opencodex 2.65.0 → 2.66.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 +14 -0
- package/gui/dist/assets/App-DyHlFeNB.js +51 -0
- package/gui/dist/assets/App-gkoJIEOV.css +1 -0
- package/gui/dist/assets/{Tray-D4bA1vff.js → Tray-CI8cVKWu.js} +1 -1
- package/gui/dist/assets/index-BiwnVAUm.js +86 -0
- package/gui/dist/assets/index-ComVsdSk.css +1 -0
- package/gui/dist/assets/{tray-data-BZXhB3qz.js → tray-data-_WyYzbMt.js} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +1 -2
- package/src/adapters/anthropic/beta-allowlist.ts +80 -0
- package/src/adapters/anthropic/passthrough.ts +221 -0
- package/src/adapters/anthropic-image-codec.ts +56 -19
- package/src/adapters/anthropic-image-normalize.ts +20 -10
- package/src/adapters/anthropic.ts +55 -24
- package/src/adapters/coding-agent/protocol.ts +29 -19
- package/src/adapters/cursor/live-transport.ts +18 -3
- package/src/adapters/cursor/native-exec-shell.ts +18 -63
- package/src/adapters/cursor/native-exec.ts +5 -2
- package/src/adapters/cursor/native-foreground-shell.ts +44 -0
- package/src/adapters/devin/cloud-direct/chat.ts +7 -1
- package/src/adapters/exec-tool-result-normalize.ts +4 -1
- package/src/adapters/google.ts +12 -0
- package/src/adapters/ollama-native.ts +32 -1
- package/src/adapters/openai-chat/serialized-tool-call-content.ts +131 -34
- package/src/adapters/openai-chat.ts +6 -4
- package/src/adapters/openai-responses/passthrough.ts +1 -0
- package/src/adapters/openai-responses/reasoning.ts +45 -1
- package/src/bridge/response-json.ts +10 -2
- package/src/chat/outbound.ts +104 -52
- package/src/claude/claude-code-block.ts +16 -0
- package/src/claude/context-windows.ts +52 -4
- package/src/claude/desktop-first-party.ts +25 -8
- package/src/claude/inbound.ts +22 -0
- package/src/claude/intercept/connect-proxy.ts +37 -4
- package/src/claude/intercept/proxy-auth.ts +166 -0
- package/src/claude/intercept/runtime.ts +21 -0
- package/src/claude/intercept/settings.ts +33 -12
- package/src/claude/outbound.ts +45 -27
- package/src/cli/account-extended.ts +47 -15
- package/src/cli/account-target.ts +115 -0
- package/src/cli/account.ts +20 -28
- package/src/cli/agent.ts +10 -1
- package/src/cli/api-protocols.ts +206 -0
- package/src/cli/capabilities.ts +86 -0
- package/src/cli/combo.ts +2 -2
- package/src/cli/connect.ts +127 -3
- package/src/cli/dispatch.ts +8 -0
- package/src/cli/doctor.ts +33 -0
- package/src/cli/help.ts +2 -0
- package/src/cli/link.ts +288 -0
- package/src/cli/registry.ts +23 -0
- package/src/cli/runtime-api.ts +79 -0
- package/src/cli/system-command.ts +21 -2
- package/src/client/connect.ts +125 -23
- package/src/client/hub-relay.ts +12 -8
- package/src/client/link-join.ts +323 -0
- package/src/client/link-relay.ts +241 -0
- package/src/client/link-state.ts +110 -0
- package/src/client/link-teardown.ts +63 -0
- package/src/client/link-tunnel.ts +465 -0
- package/src/client/machine-listener.ts +14 -5
- package/src/client/runtime.ts +91 -42
- package/src/client/state.ts +4 -0
- package/src/codex/catalog/build-entries.ts +4 -0
- package/src/codex/catalog/derive-entry.ts +5 -0
- package/src/codex/catalog/parsing.ts +4 -0
- package/src/codex/catalog/retained-sync.ts +4 -1
- package/src/codex/desktop-switches.ts +88 -8
- package/src/codex/inject/plan.ts +16 -3
- package/src/codex/inject.ts +3 -0
- package/src/codex/internal/catalog-writer.ts +13 -2
- package/src/combos/failover.ts +3 -2
- package/src/combos/index.ts +12 -0
- package/src/combos/jev.ts +646 -0
- package/src/combos/request.ts +5 -0
- package/src/combos/resolve.ts +17 -15
- package/src/combos/types.ts +47 -3
- package/src/config/atomic-write.ts +3 -0
- package/src/config/live-reconcile.ts +20 -4
- package/src/config/persist-unlocked.ts +20 -7
- package/src/config/schema/config-schema.ts +16 -0
- package/src/config/schema/leaf-validators.ts +38 -1
- package/src/generated/compatibility-version.json +416 -128
- package/src/integrations/omp-yaml-source.ts +109 -4
- package/src/lab/automation/orchestrator.ts +60 -4
- package/src/lab/events/limits.ts +1 -1
- package/src/lib/bounded-body.ts +55 -38
- package/src/lib/config-ownership.ts +11 -22
- package/src/lib/local-desktop-snapshot-capability.ts +56 -0
- package/src/lib/local-management-capability.ts +25 -3
- package/src/lib/optional-shutdown-hooks.ts +17 -0
- package/src/lib/state-store-registrations.ts +2 -2
- package/src/lib/windows-elevation.ts +62 -25
- package/src/lib/windows-secret-acl.ts +114 -23
- package/src/link/admission-wait.ts +73 -0
- package/src/link/compensation.ts +100 -0
- package/src/link/fingerprint.ts +21 -0
- package/src/link/paths.ts +19 -0
- package/src/link/ports.ts +12 -0
- package/src/link/routes.ts +32 -0
- package/src/link/ssh-argv.ts +170 -0
- package/src/link/ssh-config.ts +152 -0
- package/src/link/ssh-runner.ts +161 -0
- package/src/link/status-projection.ts +96 -0
- package/src/link/store.ts +139 -0
- package/src/link/supervisor.ts +404 -0
- package/src/link/tunnel-state.ts +91 -0
- package/src/protocols/baseline.ts +88 -0
- package/src/protocols/codecs/chat.ts +17 -0
- package/src/protocols/codecs/messages.ts +18 -0
- package/src/protocols/codecs/responses.ts +17 -0
- package/src/protocols/contract.ts +185 -0
- package/src/protocols/dto.ts +267 -0
- package/src/protocols/encoders/adapter-events.ts +876 -0
- package/src/protocols/encoders/chat.ts +243 -0
- package/src/protocols/encoders/messages.ts +462 -0
- package/src/protocols/envelope.ts +43 -0
- package/src/protocols/features.ts +295 -0
- package/src/protocols/guard.ts +33 -0
- package/src/protocols/opaque-state.ts +132 -0
- package/src/protocols/path.ts +39 -0
- package/src/protocols/plan-snapshot.ts +226 -0
- package/src/protocols/plan.ts +192 -0
- package/src/protocols/provider-summary.ts +101 -0
- package/src/protocols/settings.ts +118 -0
- package/src/protocols/shadow-plan.ts +36 -0
- package/src/protocols/shadow.ts +62 -0
- package/src/protocols/trace.ts +335 -0
- package/src/providers/model-rename-migration.ts +3 -3
- package/src/providers/registry/entries-core.ts +6 -1
- package/src/providers/registry/entries-extended.ts +15 -0
- package/src/providers/registry/model-ids.ts +1 -0
- package/src/providers/registry/types.ts +6 -0
- package/src/providers/registry.ts +1 -1
- package/src/remote-control/workspace-hub.ts +5 -4
- package/src/responses/apply-patch-envelope.ts +6 -1
- package/src/responses/code-mode-shell-input.ts +4 -2
- package/src/responses/freeform-wrapper-scan.ts +51 -24
- package/src/responses/progressive-freeform-input.ts +14 -20
- package/src/responses/spill-store.ts +187 -33
- package/src/responses/state/snapshot-select.ts +49 -0
- package/src/responses/state/spill-inspect.ts +46 -0
- package/src/responses/state/spill-queue.ts +16 -0
- package/src/responses/state.ts +51 -27
- package/src/server/audio-client.ts +8 -3
- package/src/server/audio-upstream.ts +9 -4
- package/src/server/auth-cors.ts +25 -8
- package/src/server/chat-completions.ts +71 -11
- package/src/server/chat-native-eligibility.ts +74 -0
- package/src/server/chat-native.ts +139 -74
- package/src/server/claude-messages.ts +140 -22
- package/src/server/hub-usage.ts +4 -3
- package/src/server/index/link-listener.ts +195 -0
- package/src/server/index/optional-listeners.ts +115 -0
- package/src/server/index/serve-options.ts +44 -10
- package/src/server/index.ts +10 -9
- package/src/server/inference/attempt.ts +51 -0
- package/src/server/inference/client-encoder-delivery.ts +235 -0
- package/src/server/inference/client-wire-log.ts +49 -0
- package/src/server/inference/client-wire.ts +69 -0
- package/src/server/inference/context.ts +15 -0
- package/src/server/inference/final-log.ts +37 -0
- package/src/server/local-desktop-snapshot-auth.ts +57 -0
- package/src/server/management/agent-settings-routes.ts +15 -13
- package/src/server/management/api-access.ts +11 -1
- package/src/server/management/config-routes.ts +3 -6
- package/src/server/management/context.ts +25 -0
- package/src/server/management/link-routes.ts +535 -0
- package/src/server/management/logs-usage-routes.ts +31 -1
- package/src/server/management/native-integration-routes.ts +6 -11
- package/src/server/management/oauth-account-routes.ts +42 -16
- package/src/server/management/protocol-routes.ts +159 -0
- package/src/server/management/protocol-settings-patch.ts +145 -0
- package/src/server/management/provider-patch-transaction.ts +61 -0
- package/src/server/management/provider-routes.ts +39 -18
- package/src/server/management/route-registry.ts +12 -0
- package/src/server/management/sidebar-routes.ts +8 -3
- package/src/server/management/system-routes.ts +18 -1
- package/src/server/management/usage-aggregate-cache.ts +206 -6
- package/src/server/management-api.ts +26 -1
- package/src/server/management-auth.ts +19 -5
- package/src/server/messages-native-eligibility.ts +161 -0
- package/src/server/messages-native-oauth.ts +149 -0
- package/src/server/messages-native.ts +790 -0
- package/src/server/relay.ts +9 -0
- package/src/server/request-log-filter.ts +92 -0
- package/src/server/request-log.ts +31 -70
- package/src/server/responses/adapter-delivery.ts +43 -16
- package/src/server/responses/agent-task-recovery.ts +90 -22
- package/src/server/responses/codex-ws-exchange.ts +4 -0
- package/src/server/responses/codex-ws-wire.ts +10 -1
- package/src/server/responses/core-combo-native.ts +323 -0
- package/src/server/responses/core-combo.ts +224 -13
- package/src/server/responses/core-options.ts +22 -0
- package/src/server/responses/core.ts +14 -26
- package/src/server/responses/request-sidecar-auth.ts +8 -3
- package/src/server/responses/run-turn-execution.ts +257 -63
- package/src/server/responses/sidecar-execution.ts +7 -4
- package/src/server/system-env.ts +7 -0
- package/src/service/diagnostics.ts +7 -1
- package/src/service/launchd.ts +26 -37
- package/src/service/windows-ops.ts +24 -20
- package/src/types/config.ts +58 -1
- package/src/types.ts +2 -0
- package/src/update/transactional-install.d.mts +0 -1
- package/src/update/transactional-install.mjs +17 -17
- package/src/usage/jev-stats.ts +495 -0
- package/src/usage/log.ts +24 -3
- package/src/web-search/run-turn-loop.ts +568 -0
- package/gui/dist/assets/App-8NMiZxT0.css +0 -1
- package/gui/dist/assets/App-DGLte6IR.js +0 -50
- package/gui/dist/assets/index--EWgGQvZ.css +0 -1
- package/gui/dist/assets/index-Bi1K37Wl.js +0 -86
package/src/chat/outbound.ts
CHANGED
|
@@ -31,7 +31,7 @@ function uuid(): string {
|
|
|
31
31
|
return crypto.randomUUID().replace(/-/g, "");
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
function completionId(): string {
|
|
34
|
+
export function completionId(): string {
|
|
35
35
|
return `chatcmpl-${uuid().slice(0, 24)}`;
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -133,12 +133,12 @@ function streamErrorStatus(message: string): number {
|
|
|
133
133
|
return 502;
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
function dataFrame(payload: Rec | "[DONE]"): string {
|
|
136
|
+
export function dataFrame(payload: Rec | "[DONE]"): string {
|
|
137
137
|
if (payload === "[DONE]") return "data: [DONE]\n\n";
|
|
138
138
|
return `data: ${JSON.stringify(payload)}\n\n`;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
function chunkBase(id: string, model: string, created: number): Rec {
|
|
141
|
+
export function chunkBase(id: string, model: string, created: number): Rec {
|
|
142
142
|
return {
|
|
143
143
|
id,
|
|
144
144
|
object: "chat.completion.chunk",
|
|
@@ -161,6 +161,90 @@ function appendedUtf8Bytes(previous: string, previousBytes: number, fragment: st
|
|
|
161
161
|
return nextBytes;
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
/** Details a streamed Chat failure carries into its error frame. */
|
|
165
|
+
export interface ChatCompletionsStreamFailure {
|
|
166
|
+
code?: string | null;
|
|
167
|
+
type?: string;
|
|
168
|
+
status?: number;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* The `data: {error}` payload a streamed Chat failure ends with, and whether it is one of the
|
|
173
|
+
* fixed, bounded failures that must be delivered even when the translation budget is exhausted.
|
|
174
|
+
* Provider text is redacted; the two fixed failures carry no provider text at all.
|
|
175
|
+
*/
|
|
176
|
+
export function chatCompletionsStreamErrorPayload(
|
|
177
|
+
message: string,
|
|
178
|
+
details?: ChatCompletionsStreamFailure,
|
|
179
|
+
): { payload: Rec; bounded: boolean } {
|
|
180
|
+
const translatorOverflow = details?.code === "translation_buffer_limit";
|
|
181
|
+
const safeMessage = translatorOverflow ? "upstream translation buffer exceeded the safe limit"
|
|
182
|
+
: details?.code === "invalid_refusal" ? "upstream refusal representations are inconsistent"
|
|
183
|
+
: redactSecretString(message);
|
|
184
|
+
const statusHint = details?.status ?? streamErrorStatus(safeMessage);
|
|
185
|
+
const classified = classifyError(statusHint, details?.type ?? "upstream_error", safeMessage);
|
|
186
|
+
if (translatorOverflow) {
|
|
187
|
+
classified.code = "translation_buffer_limit";
|
|
188
|
+
// Provider-controlled overflow is an upstream failure on every path:
|
|
189
|
+
// streaming frame, collector, and defensive JSON agree on 502.
|
|
190
|
+
classified.type = "upstream_error";
|
|
191
|
+
} else if (details?.code === "invalid_refusal") {
|
|
192
|
+
classified.code = details.code;
|
|
193
|
+
classified.type = "upstream_error";
|
|
194
|
+
} else if (isCyberPolicyCode(details?.code) || classified.code === CYBER_POLICY_ERROR_CODE) {
|
|
195
|
+
classified.code = CYBER_POLICY_ERROR_CODE;
|
|
196
|
+
classified.type = cyberPolicyErrorType(details?.type);
|
|
197
|
+
} else if (details?.code !== undefined && details.code !== null && !classified.code) {
|
|
198
|
+
classified.code = details.code;
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
payload: {
|
|
202
|
+
error: {
|
|
203
|
+
message: classified.message,
|
|
204
|
+
type: classified.type,
|
|
205
|
+
param: null,
|
|
206
|
+
code: classified.code,
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
bounded: translatorOverflow || details?.code === "invalid_refusal",
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** How a Responses `response.failed` error object becomes a streamed Chat failure. */
|
|
214
|
+
export function chatCompletionsFailedResponse(error: Rec): { message: string; details: ChatCompletionsStreamFailure } {
|
|
215
|
+
const message = typeof error.message === "string" ? error.message : "upstream request failed";
|
|
216
|
+
const code = typeof error.code === "string" ? error.code : null;
|
|
217
|
+
const type = typeof error.type === "string" ? error.type : undefined;
|
|
218
|
+
return {
|
|
219
|
+
message,
|
|
220
|
+
details: {
|
|
221
|
+
code,
|
|
222
|
+
...(code === "translation_buffer_limit"
|
|
223
|
+
? { status: 502, type: "upstream_error" }
|
|
224
|
+
: { type, ...(code === CYBER_POLICY_ERROR_CODE ? { status: 400 } : {}) }),
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* A Responses incomplete reason as the Chat client sees it: a truthful early finish for an
|
|
231
|
+
* output cap or a content filter, and a failure for every other reason (stall, adapter EOF,
|
|
232
|
+
* proxy-synthesized incompletes), which must never look like a clean stop.
|
|
233
|
+
*/
|
|
234
|
+
export function chatCompletionsIncompleteOutcome(
|
|
235
|
+
reason: unknown,
|
|
236
|
+
message: unknown,
|
|
237
|
+
): { finishReason: "length" | "content_filter" } | { failMessage: string } {
|
|
238
|
+
if (reason === "max_output_tokens") return { finishReason: "length" };
|
|
239
|
+
if (reason === "content_filter") return { finishReason: "content_filter" };
|
|
240
|
+
const why = typeof reason === "string" ? reason : "unknown";
|
|
241
|
+
return {
|
|
242
|
+
failMessage: typeof message === "string" && message.length > 0
|
|
243
|
+
? message
|
|
244
|
+
: `upstream stream ended early (${why})`,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
164
248
|
function refusalTranslationError(): ChatCompletionsStreamError {
|
|
165
249
|
// Never include provider-controlled refusal text or correlation IDs in diagnostics.
|
|
166
250
|
return new ChatCompletionsStreamError("upstream refusal representations are inconsistent", {
|
|
@@ -506,38 +590,12 @@ export function responsesSseToChatCompletionsSse(
|
|
|
506
590
|
// Deliver the error frame then close the stream abnormally (no [DONE]).
|
|
507
591
|
// Do not controller.error() — that can drop already-enqueued bytes from consumers
|
|
508
592
|
// like response.text().
|
|
509
|
-
const
|
|
510
|
-
const safeMessage = translatorOverflow ? "upstream translation buffer exceeded the safe limit"
|
|
511
|
-
: details?.code === "invalid_refusal" ? "upstream refusal representations are inconsistent"
|
|
512
|
-
: redactSecretString(message);
|
|
513
|
-
const statusHint = details?.status ?? streamErrorStatus(safeMessage);
|
|
514
|
-
const classified = classifyError(statusHint, details?.type ?? "upstream_error", safeMessage);
|
|
515
|
-
if (translatorOverflow) {
|
|
516
|
-
classified.code = "translation_buffer_limit";
|
|
517
|
-
// Provider-controlled overflow is an upstream failure on every path:
|
|
518
|
-
// streaming frame, collector, and defensive JSON agree on 502.
|
|
519
|
-
classified.type = "upstream_error";
|
|
520
|
-
} else if (details?.code === "invalid_refusal") {
|
|
521
|
-
classified.code = details.code;
|
|
522
|
-
classified.type = "upstream_error";
|
|
523
|
-
} else if (isCyberPolicyCode(details?.code) || classified.code === CYBER_POLICY_ERROR_CODE) {
|
|
524
|
-
classified.code = CYBER_POLICY_ERROR_CODE;
|
|
525
|
-
classified.type = cyberPolicyErrorType(details?.type);
|
|
526
|
-
} else if (details?.code !== undefined && details.code !== null && !classified.code) {
|
|
527
|
-
classified.code = details.code;
|
|
528
|
-
}
|
|
593
|
+
const { payload, bounded } = chatCompletionsStreamErrorPayload(message, details);
|
|
529
594
|
try {
|
|
530
|
-
const frame = encoder.encode(dataFrame(
|
|
531
|
-
error: {
|
|
532
|
-
message: classified.message,
|
|
533
|
-
type: classified.type,
|
|
534
|
-
param: null,
|
|
535
|
-
code: classified.code,
|
|
536
|
-
},
|
|
537
|
-
}));
|
|
595
|
+
const frame = encoder.encode(dataFrame(payload));
|
|
538
596
|
// These fixed, bounded failures must survive even when decoder-owned input
|
|
539
597
|
// still fills the budget. They contain no provider text or IDs.
|
|
540
|
-
if (
|
|
598
|
+
if (bounded) controller.enqueue(frame);
|
|
541
599
|
else enqueueLiveFrame(frame);
|
|
542
600
|
emittedFrames++;
|
|
543
601
|
} catch {
|
|
@@ -552,8 +610,16 @@ export function responsesSseToChatCompletionsSse(
|
|
|
552
610
|
}
|
|
553
611
|
switch (eventName) {
|
|
554
612
|
case "response.created":
|
|
613
|
+
ensureRole();
|
|
614
|
+
break;
|
|
555
615
|
case "response.heartbeat":
|
|
556
616
|
ensureRole();
|
|
617
|
+
// A typed Responses heartbeat carries transport liveness, not Chat content. Preserve
|
|
618
|
+
// that signal as an SSE comment so idle-sensitive Chat clients receive bytes without
|
|
619
|
+
// inventing a semantic chunk that parsers, usage counters, or progress watchdogs could
|
|
620
|
+
// mistake for model output.
|
|
621
|
+
enqueueLiveFrame(encoder.encode(": opencodex heartbeat\n\n"));
|
|
622
|
+
emittedFrames++;
|
|
557
623
|
break;
|
|
558
624
|
case "response.output_text.delta": {
|
|
559
625
|
if (typeof data.delta === "string") emitContent(data.delta);
|
|
@@ -680,37 +746,23 @@ export function responsesSseToChatCompletionsSse(
|
|
|
680
746
|
case "response.incomplete": {
|
|
681
747
|
const response = isRec(data.response) ? data.response : {};
|
|
682
748
|
const details = isRec(response.incomplete_details) ? response.incomplete_details : {};
|
|
683
|
-
const
|
|
684
|
-
|
|
685
|
-
: undefined;
|
|
686
|
-
if (reason !== undefined) {
|
|
749
|
+
const outcome = chatCompletionsIncompleteOutcome(details.reason, details.message);
|
|
750
|
+
if ("finishReason" in outcome) {
|
|
687
751
|
// Truthful OpenAI-compatible finish reasons: the turn ended, just early.
|
|
688
752
|
snapshotRefusals(response);
|
|
689
|
-
finish(
|
|
753
|
+
finish(outcome.finishReason, response.usage);
|
|
690
754
|
} else {
|
|
691
755
|
// upstream_stall_timeout / adapter_eof / proxy-synthesized incompletes are
|
|
692
756
|
// failures, not early finishes: emit an error frame and close WITHOUT
|
|
693
757
|
// [DONE] instead of a success-looking stop/tool_calls + [DONE].
|
|
694
|
-
|
|
695
|
-
const message = typeof details.message === "string" && details.message.length > 0
|
|
696
|
-
? details.message
|
|
697
|
-
: `upstream stream ended early (${why})`;
|
|
698
|
-
fail(message);
|
|
758
|
+
fail(outcome.failMessage);
|
|
699
759
|
}
|
|
700
760
|
break;
|
|
701
761
|
}
|
|
702
762
|
case "response.failed": {
|
|
703
763
|
const response = isRec(data.response) ? data.response : {};
|
|
704
|
-
const
|
|
705
|
-
|
|
706
|
-
const code = typeof error.code === "string" ? error.code : null;
|
|
707
|
-
const type = typeof error.type === "string" ? error.type : undefined;
|
|
708
|
-
fail(message, {
|
|
709
|
-
code,
|
|
710
|
-
...(code === "translation_buffer_limit"
|
|
711
|
-
? { status: 502, type: "upstream_error" }
|
|
712
|
-
: { type, ...(code === CYBER_POLICY_ERROR_CODE ? { status: 400 } : {}) }),
|
|
713
|
-
});
|
|
764
|
+
const failure = chatCompletionsFailedResponse(isRec(response.error) ? response.error : {});
|
|
765
|
+
fail(failure.message, failure.details);
|
|
714
766
|
break;
|
|
715
767
|
}
|
|
716
768
|
default:
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one way a management route installs a new `claudeCode` block on the live config.
|
|
3
|
+
*
|
|
4
|
+
* Every writer stamps the auth-mode migration sentinel. `runClaudeAuthModeMigration` reads a
|
|
5
|
+
* block with no `authMode` and no sentinel as a pre-upgrade subscriber and pins it to literal
|
|
6
|
+
* subscription, so a route that CREATES the block (a bare `{ enabled }` toggle) without the
|
|
7
|
+
* sentinel silently converts an Auto user on the next start. Persisting stays with the caller,
|
|
8
|
+
* through `saveConfigPreservingClaudeCode`, whose baseline guard keeps a concurrent hand edit
|
|
9
|
+
* of the block unless this process changed it too.
|
|
10
|
+
*/
|
|
11
|
+
import type { OcxClaudeCodeConfig, OcxConfig } from "../types";
|
|
12
|
+
|
|
13
|
+
export function commitClaudeCodeBlock(config: OcxConfig, next: OcxClaudeCodeConfig, now = new Date()): void {
|
|
14
|
+
if (!next.authModeMigratedAt) next.authModeMigratedAt = now.toISOString();
|
|
15
|
+
config.claudeCode = next;
|
|
16
|
+
}
|
|
@@ -7,14 +7,26 @@
|
|
|
7
7
|
* the legacy claude-ocx-* spelling a saved selector may still carry —
|
|
8
8
|
* with first-wins dedupe (mirrors the desktop3p registry collision policy).
|
|
9
9
|
* Values are authoritative context windows only (native override table /
|
|
10
|
-
* adapter-reported CatalogModel.contextWindow
|
|
10
|
+
* adapter-reported CatalogModel.contextWindow / the Anthropic registry for passthrough ids);
|
|
11
|
+
* nothing is guessed.
|
|
11
12
|
*/
|
|
12
13
|
import { aliasForNative, aliasForRoute, currentClaudeAliasSpelling, legacyAliasForNative, legacyAliasForRoute } from "./alias";
|
|
13
14
|
import { desktop3pAlias } from "./desktop-3p";
|
|
14
15
|
import { nativeOpenAiContextWindow, type CatalogModel, type NativeContextLimitsInput } from "../codex/catalog";
|
|
16
|
+
import { ANTHROPIC_MODEL_CONTEXT_WINDOWS } from "../providers/registry/model-seeds";
|
|
17
|
+
import type { OcxClaudeCodeConfig } from "../types";
|
|
15
18
|
|
|
16
19
|
const ONE_MILLION = 1_000_000;
|
|
17
20
|
|
|
21
|
+
/**
|
|
22
|
+
* The native id each Claude Code tier alias resolves to (2.1.282: `--model opus` sends
|
|
23
|
+
* `claude-opus-5-5`, `sonnet` sends `claude-sonnet-5`, `fable` sends `claude-fable-5-1`).
|
|
24
|
+
* Behind a gateway Claude Code accounts an unmarked id at 200k, so an unset tier slot is filled
|
|
25
|
+
* with this id when the map marks it (#5755). A marked alias cannot stand in: Claude Code sends
|
|
26
|
+
* `sonnet[1m]` upstream unresolved. Haiku has no entry; its 200k window never marks.
|
|
27
|
+
*/
|
|
28
|
+
const CLAUDE_CODE_NATIVE_TIERS = { opus: "claude-opus-5-5", sonnet: "claude-sonnet-5", fable: "claude-fable-5-1" } as const;
|
|
29
|
+
|
|
18
30
|
/**
|
|
19
31
|
* Auto-context defaults (devlog 260712 020, user-approved).
|
|
20
32
|
*
|
|
@@ -124,6 +136,27 @@ export function shouldMarkOneMillion(window: number | undefined, auto: AutoConte
|
|
|
124
136
|
return auto.enabled && window > AUTO_CONTEXT_FLOOR && window >= auto.compactWindow;
|
|
125
137
|
}
|
|
126
138
|
|
|
139
|
+
/** Present when bare Claude ids take the native passthrough on this launch (#5755). */
|
|
140
|
+
export interface NativeClaudePassthrough {
|
|
141
|
+
/** `claudeCode.modelMap`: a mapped id is routed, not passed through. */
|
|
142
|
+
modelMap?: Readonly<Record<string, string>>;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* On a local subscription launch with `nativePassthrough` on, a bare `claude-*` id goes straight
|
|
147
|
+
* to Anthropic under Claude Code's own login and never reaches the router
|
|
148
|
+
* (server/claude-messages.ts `wantsNativePassthrough`). Undefined when the router decides:
|
|
149
|
+
* proxy auth, passthrough off, or a connected client, which authenticates with an admission token.
|
|
150
|
+
*/
|
|
151
|
+
export function nativeClaudePassthroughFor(
|
|
152
|
+
claudeCode: OcxClaudeCodeConfig | undefined,
|
|
153
|
+
markerMode: "proxy" | "subscription",
|
|
154
|
+
): NativeClaudePassthrough | undefined {
|
|
155
|
+
return markerMode === "subscription" && claudeCode?.nativePassthrough !== false
|
|
156
|
+
? { modelMap: claudeCode?.modelMap }
|
|
157
|
+
: undefined;
|
|
158
|
+
}
|
|
159
|
+
|
|
127
160
|
export function buildClaudeContextWindows(
|
|
128
161
|
nativeSlugs: readonly string[],
|
|
129
162
|
routedModels: readonly CatalogModel[],
|
|
@@ -131,6 +164,7 @@ export function buildClaudeContextWindows(
|
|
|
131
164
|
// it the Claude surface keeps advertising the uncapped authoritative window while the
|
|
132
165
|
// Codex catalog advertises the capped one, and the two disagree about the same model.
|
|
133
166
|
nativeContextCap?: NativeContextLimitsInput,
|
|
167
|
+
nativeClaude?: NativeClaudePassthrough,
|
|
134
168
|
): Record<string, number> {
|
|
135
169
|
const out: Record<string, number> = {};
|
|
136
170
|
const put = (key: string | null, value: number) => {
|
|
@@ -145,6 +179,16 @@ export function buildClaudeContextWindows(
|
|
|
145
179
|
put(aliasForNative(slug), window);
|
|
146
180
|
put(legacyAliasForNative(slug), window);
|
|
147
181
|
}
|
|
182
|
+
// Passthrough ids never reach the router, so the registry speaks for them ahead of any routed
|
|
183
|
+
// row that shares the bare id (#5755). An anthropic row capped under 1M still keeps its id
|
|
184
|
+
// unmarked, and a modelMap entry routes its id elsewhere.
|
|
185
|
+
if (nativeClaude) {
|
|
186
|
+
const capped = new Set(routedModels.flatMap(m =>
|
|
187
|
+
m.provider === "anthropic" && typeof m.contextWindow === "number" && m.contextWindow < ONE_MILLION ? [m.id] : []));
|
|
188
|
+
for (const [id, window] of Object.entries(ANTHROPIC_MODEL_CONTEXT_WINDOWS)) {
|
|
189
|
+
if (window >= ONE_MILLION && !capped.has(id) && !nativeClaude.modelMap?.[id]) put(id, window);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
148
192
|
// Anthropic passthrough guard (audit 021 #3): canonical claude ids ride the
|
|
149
193
|
// subscription passthrough — marking a sub-1M one would strap [1m]/1M-beta onto
|
|
150
194
|
// a model that cannot host it. Register anthropic rows only at >=1M.
|
|
@@ -217,10 +261,14 @@ export function effectiveModelEnv(
|
|
|
217
261
|
const marked = withOneMillionMarker(value === undefined ? undefined : currentClaudeAliasSpelling(value), windows, auto);
|
|
218
262
|
if (marked) out[name] = marked;
|
|
219
263
|
};
|
|
264
|
+
// An unset tier slot is filled only when its native id marks; otherwise Claude Code keeps
|
|
265
|
+
// choosing, as before.
|
|
266
|
+
const tier = (configured: string | undefined, native: string) =>
|
|
267
|
+
configured ?? (shouldMarkOneMillion(windows[native], auto) ? native : undefined);
|
|
220
268
|
set("ANTHROPIC_MODEL", claudeCode?.model);
|
|
221
|
-
set("ANTHROPIC_DEFAULT_OPUS_MODEL", claudeCode?.tierModels?.opus);
|
|
222
|
-
set("ANTHROPIC_DEFAULT_SONNET_MODEL", claudeCode?.tierModels?.sonnet);
|
|
223
|
-
set("ANTHROPIC_DEFAULT_FABLE_MODEL", claudeCode?.tierModels?.fable);
|
|
269
|
+
set("ANTHROPIC_DEFAULT_OPUS_MODEL", tier(claudeCode?.tierModels?.opus, CLAUDE_CODE_NATIVE_TIERS.opus));
|
|
270
|
+
set("ANTHROPIC_DEFAULT_SONNET_MODEL", tier(claudeCode?.tierModels?.sonnet, CLAUDE_CODE_NATIVE_TIERS.sonnet));
|
|
271
|
+
set("ANTHROPIC_DEFAULT_FABLE_MODEL", tier(claudeCode?.tierModels?.fable, CLAUDE_CODE_NATIVE_TIERS.fable));
|
|
224
272
|
const effectiveHaiku = claudeCode?.tierModels?.haiku ?? claudeCode?.smallFastModel;
|
|
225
273
|
set("ANTHROPIC_DEFAULT_HAIKU_MODEL", effectiveHaiku);
|
|
226
274
|
set("ANTHROPIC_SMALL_FAST_MODEL", effectiveHaiku);
|
|
@@ -24,6 +24,7 @@ import type { OcxConfig } from "../types";
|
|
|
24
24
|
import { inspectDesktop3pConfigLibrary } from "./desktop-3p";
|
|
25
25
|
import { claudeInterceptCaCertPath, ensureLocalInterceptCa } from "./intercept/local-ca";
|
|
26
26
|
import { claudeInterceptEnabled, claudeInterceptProxyPort } from "./intercept/runtime";
|
|
27
|
+
import { ensureClaudeInterceptProxyToken, readClaudeInterceptProxyToken } from "./intercept/proxy-auth";
|
|
27
28
|
import {
|
|
28
29
|
applyClaudeInterceptSettings,
|
|
29
30
|
buildClaudeInterceptEnv,
|
|
@@ -115,14 +116,22 @@ export interface DesktopFirstPartyTarget {
|
|
|
115
116
|
env: ClaudeInterceptEnv;
|
|
116
117
|
}
|
|
117
118
|
|
|
118
|
-
|
|
119
|
-
export function desktopFirstPartyTarget(
|
|
119
|
+
function firstPartyTarget(
|
|
120
120
|
config: Pick<OcxConfig, "claudeCode" | "port">,
|
|
121
|
-
opencodexConfigDir
|
|
121
|
+
opencodexConfigDir: string,
|
|
122
|
+
authToken: string,
|
|
122
123
|
): DesktopFirstPartyTarget {
|
|
123
124
|
const proxyPort = claudeInterceptProxyPort(config, config.port ?? 10100);
|
|
124
125
|
const caCertPath = claudeInterceptCaCertPath(opencodexConfigDir);
|
|
125
|
-
return { proxyPort, caCertPath, env: buildClaudeInterceptEnv(proxyPort, caCertPath) };
|
|
126
|
+
return { proxyPort, caCertPath, env: buildClaudeInterceptEnv(proxyPort, caCertPath, authToken) };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** The settings env a first-party apply on this machine writes (CA and token are created on demand). */
|
|
130
|
+
export function desktopFirstPartyTarget(
|
|
131
|
+
config: Pick<OcxConfig, "claudeCode" | "port">,
|
|
132
|
+
opencodexConfigDir = getConfigDir(),
|
|
133
|
+
): DesktopFirstPartyTarget {
|
|
134
|
+
return firstPartyTarget(config, opencodexConfigDir, ensureClaudeInterceptProxyToken(opencodexConfigDir));
|
|
126
135
|
}
|
|
127
136
|
|
|
128
137
|
export interface DesktopFirstPartyInspection {
|
|
@@ -147,8 +156,11 @@ export function captureDesktopFirstPartyRollback(
|
|
|
147
156
|
config: Pick<OcxConfig, "claudeCode" | "port">,
|
|
148
157
|
options: DesktopFirstPartyOptions = {},
|
|
149
158
|
): () => boolean {
|
|
159
|
+
// The expected env resolves at rollback time: the apply mints the proxy token, so capturing it
|
|
160
|
+
// here would both mint on a path that may never apply and throw when the intercept directory is
|
|
161
|
+
// unavailable — a failure the apply already reports as ca_unavailable.
|
|
150
162
|
return captureClaudeInterceptSettingsRollback(
|
|
151
|
-
desktopFirstPartyTarget(config, options.opencodexConfigDir).env, options.claudeConfigDir,
|
|
163
|
+
() => desktopFirstPartyTarget(config, options.opencodexConfigDir).env, options.claudeConfigDir,
|
|
152
164
|
);
|
|
153
165
|
}
|
|
154
166
|
|
|
@@ -156,7 +168,11 @@ export function inspectDesktopFirstParty(
|
|
|
156
168
|
config: Pick<OcxConfig, "claudeCode" | "port" | "runtimeRole">,
|
|
157
169
|
options: DesktopFirstPartyOptions = {},
|
|
158
170
|
): DesktopFirstPartyInspection {
|
|
159
|
-
const
|
|
171
|
+
const opencodexConfigDir = options.opencodexConfigDir ?? getConfigDir();
|
|
172
|
+
// Inspection is read-only: a missing token means no apply or runtime start produced one,
|
|
173
|
+
// so an owned env can never match the empty credential — it classifies stale, and a real
|
|
174
|
+
// apply is what refreshes it.
|
|
175
|
+
const target = firstPartyTarget(config, opencodexConfigDir, readClaudeInterceptProxyToken(opencodexConfigDir) ?? "");
|
|
160
176
|
const settings = inspectClaudeInterceptSettings(target.env, options.claudeConfigDir);
|
|
161
177
|
return {
|
|
162
178
|
interceptEnabled: claudeInterceptEnabled(config),
|
|
@@ -205,12 +221,13 @@ export function applyDesktopFirstParty(
|
|
|
205
221
|
options: DesktopFirstPartyOptions = {},
|
|
206
222
|
): DesktopFirstPartyApplyResult {
|
|
207
223
|
const opencodexConfigDir = options.opencodexConfigDir ?? getConfigDir();
|
|
208
|
-
const target = desktopFirstPartyTarget(config, opencodexConfigDir);
|
|
209
224
|
if (!claudeInterceptEnabled(config)) return { ok: false, reason: "intercept_disabled", path: "" };
|
|
225
|
+
let target: DesktopFirstPartyTarget;
|
|
210
226
|
try {
|
|
211
227
|
ensureLocalInterceptCa(opencodexConfigDir);
|
|
228
|
+
target = desktopFirstPartyTarget(config, opencodexConfigDir);
|
|
212
229
|
} catch {
|
|
213
|
-
return { ok: false, reason: "ca_unavailable", path:
|
|
230
|
+
return { ok: false, reason: "ca_unavailable", path: claudeInterceptCaCertPath(opencodexConfigDir) };
|
|
214
231
|
}
|
|
215
232
|
const written = applyClaudeInterceptSettings(target.env, options.claudeConfigDir);
|
|
216
233
|
if (!written.ok) return { ok: false, reason: written.reason, path: written.path };
|
package/src/claude/inbound.ts
CHANGED
|
@@ -177,6 +177,28 @@ function blockedSkillCallIds(messages: readonly unknown[], blocked: readonly str
|
|
|
177
177
|
return ids;
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Whether translating this Messages body would elide a blocked skill bundle: a user text block
|
|
182
|
+
* `maybeElideSkillText` would stub, or a tool_result answering a blocked Skill call. Pure; the
|
|
183
|
+
* managed native Messages lane asks it so a request whose bundle the operator blocked keeps the
|
|
184
|
+
* translated path that applies the block.
|
|
185
|
+
*/
|
|
186
|
+
export function anthropicBodyElidesBlockedSkill(body: unknown, cc?: Pick<OcxClaudeCodeConfig, "blockedSkills">): boolean {
|
|
187
|
+
if (!isRec(body) || !Array.isArray(body.messages)) return false;
|
|
188
|
+
const names = effectiveBlockedSkillNames(cc);
|
|
189
|
+
if (names.length === 0) return false;
|
|
190
|
+
const callIds = blockedSkillCallIds(body.messages, names);
|
|
191
|
+
for (const msg of body.messages) {
|
|
192
|
+
if (!isRec(msg) || msg.role !== "user" || !Array.isArray(msg.content)) continue;
|
|
193
|
+
for (const block of msg.content) {
|
|
194
|
+
if (!isRec(block)) continue;
|
|
195
|
+
if (block.type === "text" && typeof block.text === "string" && maybeElideSkillText(block.text, names) !== block.text) return true;
|
|
196
|
+
if (block.type === "tool_result" && typeof block.tool_use_id === "string" && callIds.has(block.tool_use_id)) return true;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
|
|
180
202
|
/**
|
|
181
203
|
* Claude Code (observed 2026-07-11, real CLI smoke) sends `role:"system"` entries in
|
|
182
204
|
* `messages` despite the published API having no system role. They are emitted as
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { timingSafeEqual } from "node:crypto";
|
|
1
2
|
import { BlockList, createServer, connect, isIP, type Server, type Socket } from "node:net";
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -23,6 +24,12 @@ const UPSTREAM_CONNECT_TIMEOUT_MS = 15_000;
|
|
|
23
24
|
export interface ConnectProxyOptions {
|
|
24
25
|
/** Loopback port of the TLS listener that terminates intercepted tunnels. */
|
|
25
26
|
interceptPort: number;
|
|
27
|
+
/**
|
|
28
|
+
* Per-install bearer carried as HTTP Basic proxy credentials. Omit only for listeners whose
|
|
29
|
+
* clients cannot present proxy credentials at all; an unauthenticated CONNECT proxy stays an
|
|
30
|
+
* open loopback relay, so every consumer that can carry the credential must set this.
|
|
31
|
+
*/
|
|
32
|
+
authToken?: string | (() => string | null);
|
|
26
33
|
/** Hostnames (lowercase) whose 443 tunnels are spliced onto `interceptPort`. */
|
|
27
34
|
interceptHosts?: readonly string[];
|
|
28
35
|
/** Per-connection override, consulted before interceptHosts; null keeps the default. */
|
|
@@ -60,7 +67,7 @@ function connectRequestInfo(head: string): ConnectRequestInfo {
|
|
|
60
67
|
}
|
|
61
68
|
|
|
62
69
|
type ResolvedConnectProxyOptions = Required<Pick<ConnectProxyOptions, "interceptPort" | "interceptHosts" | "dialUpstream">>
|
|
63
|
-
& Pick<ConnectProxyOptions, "selectTunnel">;
|
|
70
|
+
& Pick<ConnectProxyOptions, "selectTunnel" | "authToken">;
|
|
64
71
|
|
|
65
72
|
export interface ConnectProxyHandle {
|
|
66
73
|
port: number;
|
|
@@ -103,9 +110,10 @@ export function isLoopbackTarget(host: string): boolean {
|
|
|
103
110
|
return /^(0x[0-9a-f]+|\d+)(\.(0x[0-9a-f]+|\d+))*$/.test(host);
|
|
104
111
|
}
|
|
105
112
|
|
|
106
|
-
function respond(socket: Socket, status: number, reason: string): void {
|
|
113
|
+
function respond(socket: Socket, status: number, reason: string, headers: Readonly<Record<string, string>> = {}): void {
|
|
107
114
|
if (socket.destroyed) return;
|
|
108
|
-
|
|
115
|
+
const extra = Object.entries(headers).map(([name, value]) => `${name}: ${value}\r\n`).join("");
|
|
116
|
+
socket.end(`HTTP/1.1 ${status} ${reason}\r\n${extra}Connection: close\r\nContent-Length: 0\r\n\r\n`);
|
|
109
117
|
}
|
|
110
118
|
|
|
111
119
|
function splice(client: Socket, upstream: Socket, pending: Uint8Array): void {
|
|
@@ -125,6 +133,20 @@ function splice(client: Socket, upstream: Socket, pending: Uint8Array): void {
|
|
|
125
133
|
upstream.pipe(client);
|
|
126
134
|
}
|
|
127
135
|
|
|
136
|
+
function proxyAuthorized(head: string, token: string): boolean {
|
|
137
|
+
const header = head.split("\r\n").find(line => /^proxy-authorization:/i.test(line));
|
|
138
|
+
const supplied = header?.slice(header.indexOf(":") + 1).trim();
|
|
139
|
+
const expected = `Basic ${Buffer.from(`opencodex:${token}`).toString("base64")}`;
|
|
140
|
+
if (!supplied) return false;
|
|
141
|
+
// Compare byte lengths, not string lengths: the head decodes latin1 and Buffer.from
|
|
142
|
+
// re-encodes utf-8, so a non-ASCII header can match in characters while differing in
|
|
143
|
+
// bytes — and timingSafeEqual throws on a length mismatch instead of returning false.
|
|
144
|
+
const suppliedBytes = Buffer.from(supplied);
|
|
145
|
+
const expectedBytes = Buffer.from(expected);
|
|
146
|
+
if (suppliedBytes.length !== expectedBytes.length) return false;
|
|
147
|
+
return timingSafeEqual(suppliedBytes, expectedBytes);
|
|
148
|
+
}
|
|
149
|
+
|
|
128
150
|
function handleConnection(socket: Socket, options: ResolvedConnectProxyOptions): void {
|
|
129
151
|
let head: Buffer = Buffer.alloc(0);
|
|
130
152
|
socket.on("error", () => socket.destroy());
|
|
@@ -143,13 +165,23 @@ function handleConnection(socket: Socket, options: ResolvedConnectProxyOptions):
|
|
|
143
165
|
}
|
|
144
166
|
socket.off("data", onData);
|
|
145
167
|
socket.pause();
|
|
146
|
-
const
|
|
168
|
+
const requestHead = head.subarray(0, end).toString("latin1");
|
|
169
|
+
const target = parseConnectRequestLine(requestHead);
|
|
147
170
|
// Bytes after the head belong to the tunnel (a client may pipeline its TLS ClientHello).
|
|
148
171
|
let pending = head.subarray(end + 4);
|
|
149
172
|
if (!target) {
|
|
150
173
|
respond(socket, 405, "Method Not Allowed");
|
|
151
174
|
return;
|
|
152
175
|
}
|
|
176
|
+
if (options.authToken !== undefined) {
|
|
177
|
+
let token: string | null = null;
|
|
178
|
+
try { token = typeof options.authToken === "function" ? options.authToken() : options.authToken; }
|
|
179
|
+
catch { /* unavailable credential must never fall back to an unauthenticated proxy */ }
|
|
180
|
+
if (!token || !proxyAuthorized(requestHead, token)) {
|
|
181
|
+
respond(socket, 407, "Proxy Authentication Required", { "Proxy-Authenticate": 'Basic realm="OpenCodex"' });
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
153
185
|
if (isLoopbackTarget(target.host)) {
|
|
154
186
|
respond(socket, 403, "Forbidden");
|
|
155
187
|
return;
|
|
@@ -228,6 +260,7 @@ function handleConnection(socket: Socket, options: ResolvedConnectProxyOptions):
|
|
|
228
260
|
export function startConnectProxy(port: number, options: ConnectProxyOptions): Promise<ConnectProxyHandle> {
|
|
229
261
|
const resolved: ResolvedConnectProxyOptions = {
|
|
230
262
|
interceptPort: options.interceptPort,
|
|
263
|
+
authToken: options.authToken,
|
|
231
264
|
interceptHosts: options.interceptHosts ?? CLAUDE_INTERCEPT_HOSTS,
|
|
232
265
|
selectTunnel: options.selectTunnel,
|
|
233
266
|
dialUpstream: options.dialUpstream ?? ((host: string, targetPort: number) => connect({ host, port: targetPort })),
|