codex-multi-auth 2.16.0 → 2.17.2
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/.codex-plugin/plugin.json +9 -9
- package/LICENSE +21 -21
- package/README.md +487 -463
- package/assets/codex-multi-auth-icon.svg +39 -39
- package/assets/readme-hero.svg +31 -31
- package/config/README.md +31 -31
- package/config/codex-legacy.json +1221 -1221
- package/config/codex-modern.json +423 -423
- package/config/minimal-codex.json +15 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -3
- package/dist/lib/account-policy.d.ts +4 -0
- package/dist/lib/account-policy.d.ts.map +1 -1
- package/dist/lib/account-policy.js +8 -0
- package/dist/lib/accounts/rate-limits.d.ts.map +1 -1
- package/dist/lib/accounts/rate-limits.js +2 -0
- package/dist/lib/accounts.d.ts +15 -3
- package/dist/lib/accounts.d.ts.map +1 -1
- package/dist/lib/accounts.js +231 -30
- package/dist/lib/api-route-store.d.ts +24 -0
- package/dist/lib/api-route-store.d.ts.map +1 -0
- package/dist/lib/api-route-store.js +145 -0
- package/dist/lib/auth/account-access.d.ts +115 -0
- package/dist/lib/auth/account-access.d.ts.map +1 -0
- package/dist/lib/auth/account-access.js +214 -0
- package/dist/lib/auth/token-utils.d.ts +54 -1
- package/dist/lib/auth/token-utils.d.ts.map +1 -1
- package/dist/lib/auth/token-utils.js +92 -7
- package/dist/lib/cli.d.ts +1 -1
- package/dist/lib/cli.d.ts.map +1 -1
- package/dist/lib/cli.js +4 -0
- package/dist/lib/codex-cli/state.d.ts +7 -0
- package/dist/lib/codex-cli/state.d.ts.map +1 -1
- package/dist/lib/codex-cli/state.js +18 -0
- package/dist/lib/codex-cli/writer.d.ts.map +1 -1
- package/dist/lib/codex-cli/writer.js +60 -8
- package/dist/lib/codex-manager/account-manager-commands.d.ts.map +1 -1
- package/dist/lib/codex-manager/account-manager-commands.js +1 -0
- package/dist/lib/codex-manager/account-pool-write.d.ts +3 -1
- package/dist/lib/codex-manager/account-pool-write.d.ts.map +1 -1
- package/dist/lib/codex-manager/account-pool-write.js +4 -0
- package/dist/lib/codex-manager/api-login-menu.d.ts +14 -0
- package/dist/lib/codex-manager/api-login-menu.d.ts.map +1 -0
- package/dist/lib/codex-manager/api-login-menu.js +224 -0
- package/dist/lib/codex-manager/commands/account.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/account.js +27 -0
- package/dist/lib/codex-manager/commands/best.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/best.js +6 -2
- package/dist/lib/codex-manager/commands/check.d.ts +7 -1
- package/dist/lib/codex-manager/commands/check.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/check.js +24 -3
- package/dist/lib/codex-manager/commands/forecast.d.ts +1 -1
- package/dist/lib/codex-manager/commands/forecast.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/forecast.js +7 -3
- package/dist/lib/codex-manager/commands/report.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/report.js +5 -1
- package/dist/lib/codex-manager/commands/resets.d.ts +2 -0
- package/dist/lib/codex-manager/commands/resets.d.ts.map +1 -0
- package/dist/lib/codex-manager/commands/resets.js +67 -0
- package/dist/lib/codex-manager/commands/rotation.d.ts +2 -2
- package/dist/lib/codex-manager/commands/rotation.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/rotation.js +63 -12
- package/dist/lib/codex-manager/commands/status.d.ts +12 -1
- package/dist/lib/codex-manager/commands/status.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/status.js +166 -22
- package/dist/lib/codex-manager/commands/uninstall.js +1 -1
- package/dist/lib/codex-manager/formatters/quota-formatters.d.ts.map +1 -1
- package/dist/lib/codex-manager/formatters/quota-formatters.js +3 -2
- package/dist/lib/codex-manager/health-check.d.ts +3 -0
- package/dist/lib/codex-manager/health-check.d.ts.map +1 -1
- package/dist/lib/codex-manager/health-check.js +57 -7
- package/dist/lib/codex-manager/help.d.ts.map +1 -1
- package/dist/lib/codex-manager/help.js +4 -2
- package/dist/lib/codex-manager/login-flow.d.ts.map +1 -1
- package/dist/lib/codex-manager/login-flow.js +69 -7
- package/dist/lib/codex-manager/login-menu-data.d.ts.map +1 -1
- package/dist/lib/codex-manager/login-menu-data.js +21 -8
- package/dist/lib/codex-manager/login-oauth.d.ts +3 -0
- package/dist/lib/codex-manager/login-oauth.d.ts.map +1 -1
- package/dist/lib/codex-manager/login-oauth.js +6 -1
- package/dist/lib/codex-manager/login-workspace-choice.d.ts +11 -0
- package/dist/lib/codex-manager/login-workspace-choice.d.ts.map +1 -0
- package/dist/lib/codex-manager/login-workspace-choice.js +37 -0
- package/dist/lib/codex-manager/persist-selected-account.d.ts.map +1 -1
- package/dist/lib/codex-manager/persist-selected-account.js +2 -1
- package/dist/lib/codex-manager/quota-cache-helpers.d.ts +7 -1
- package/dist/lib/codex-manager/quota-cache-helpers.d.ts.map +1 -1
- package/dist/lib/codex-manager/quota-cache-helpers.js +34 -19
- package/dist/lib/codex-manager/repair-commands.d.ts +13 -0
- package/dist/lib/codex-manager/repair-commands.d.ts.map +1 -1
- package/dist/lib/codex-manager/repair-commands.js +144 -9
- package/dist/lib/codex-manager.d.ts.map +1 -1
- package/dist/lib/codex-manager.js +25 -6
- package/dist/lib/concurrency.d.ts +3 -0
- package/dist/lib/concurrency.d.ts.map +1 -0
- package/dist/lib/concurrency.js +31 -0
- package/dist/lib/context-budget-response.js +8 -8
- package/dist/lib/context-overflow.js +10 -10
- package/dist/lib/model-route-policy.d.ts +52 -0
- package/dist/lib/model-route-policy.d.ts.map +1 -0
- package/dist/lib/model-route-policy.js +159 -0
- package/dist/lib/oauth-success.html +125 -125
- package/dist/lib/policy/runtime-policy.d.ts +1 -0
- package/dist/lib/policy/runtime-policy.d.ts.map +1 -1
- package/dist/lib/policy/runtime-policy.js +3 -0
- package/dist/lib/preemptive-quota-scheduler.d.ts +2 -0
- package/dist/lib/preemptive-quota-scheduler.d.ts.map +1 -1
- package/dist/lib/preemptive-quota-scheduler.js +4 -0
- package/dist/lib/prompts/codex-host-bridge.js +138 -138
- package/dist/lib/prompts/codex.js +79 -79
- package/dist/lib/quota-cache.d.ts +7 -3
- package/dist/lib/quota-cache.d.ts.map +1 -1
- package/dist/lib/quota-cache.js +91 -59
- package/dist/lib/quota-probe.d.ts +5 -0
- package/dist/lib/quota-probe.d.ts.map +1 -1
- package/dist/lib/quota-probe.js +26 -2
- package/dist/lib/quota-readiness.d.ts +7 -2
- package/dist/lib/quota-readiness.d.ts.map +1 -1
- package/dist/lib/quota-readiness.js +25 -4
- package/dist/lib/request/client-cancellation.d.ts +5 -0
- package/dist/lib/request/client-cancellation.d.ts.map +1 -0
- package/dist/lib/request/client-cancellation.js +7 -0
- package/dist/lib/request/response-outcome.d.ts +25 -0
- package/dist/lib/request/response-outcome.d.ts.map +1 -0
- package/dist/lib/request/response-outcome.js +49 -0
- package/dist/lib/request/stream-failover-runtime.d.ts +2 -1
- package/dist/lib/request/stream-failover-runtime.d.ts.map +1 -1
- package/dist/lib/request/stream-failover-runtime.js +29 -10
- package/dist/lib/runtime/account-check.d.ts.map +1 -1
- package/dist/lib/runtime/account-check.js +2 -8
- package/dist/lib/runtime/account-model-catalog.d.ts +64 -0
- package/dist/lib/runtime/account-model-catalog.d.ts.map +1 -0
- package/dist/lib/runtime/account-model-catalog.js +154 -0
- package/dist/lib/runtime/account-pool.d.ts +3 -1
- package/dist/lib/runtime/account-pool.d.ts.map +1 -1
- package/dist/lib/runtime/account-pool.js +4 -0
- package/dist/lib/runtime/account-reset-credits.d.ts +42 -0
- package/dist/lib/runtime/account-reset-credits.d.ts.map +1 -0
- package/dist/lib/runtime/account-reset-credits.js +82 -0
- package/dist/lib/runtime/api-model-capabilities.d.ts +30 -0
- package/dist/lib/runtime/api-model-capabilities.d.ts.map +1 -0
- package/dist/lib/runtime/api-model-capabilities.js +434 -0
- package/dist/lib/runtime/api-model-runtime.d.ts +42 -0
- package/dist/lib/runtime/api-model-runtime.d.ts.map +1 -0
- package/dist/lib/runtime/api-model-runtime.js +358 -0
- package/dist/lib/runtime/app-bind.d.ts +19 -1
- package/dist/lib/runtime/app-bind.d.ts.map +1 -1
- package/dist/lib/runtime/app-bind.js +89 -11
- package/dist/lib/runtime/automatic-account-checks.d.ts +35 -0
- package/dist/lib/runtime/automatic-account-checks.d.ts.map +1 -0
- package/dist/lib/runtime/automatic-account-checks.js +140 -0
- package/dist/lib/runtime/automatic-subscription-checks.d.ts +7 -0
- package/dist/lib/runtime/automatic-subscription-checks.d.ts.map +1 -0
- package/dist/lib/runtime/automatic-subscription-checks.js +54 -0
- package/dist/lib/runtime/catalog-capabilities.d.ts +8 -0
- package/dist/lib/runtime/catalog-capabilities.d.ts.map +1 -0
- package/dist/lib/runtime/catalog-capabilities.js +61 -0
- package/dist/lib/runtime/inference-activity.d.ts +15 -0
- package/dist/lib/runtime/inference-activity.d.ts.map +1 -0
- package/dist/lib/runtime/inference-activity.js +95 -0
- package/dist/lib/runtime/model-discovery-status.d.ts +100 -0
- package/dist/lib/runtime/model-discovery-status.d.ts.map +1 -0
- package/dist/lib/runtime/model-discovery-status.js +387 -0
- package/dist/lib/runtime/native-account-storage.d.ts +11 -0
- package/dist/lib/runtime/native-account-storage.d.ts.map +1 -0
- package/dist/lib/runtime/native-account-storage.js +50 -0
- package/dist/lib/runtime/native-account-sync.d.ts +19 -0
- package/dist/lib/runtime/native-account-sync.d.ts.map +1 -0
- package/dist/lib/runtime/native-account-sync.js +62 -0
- package/dist/lib/runtime/native-binding-lock.d.ts +3 -0
- package/dist/lib/runtime/native-binding-lock.d.ts.map +1 -0
- package/dist/lib/runtime/native-binding-lock.js +5 -0
- package/dist/lib/runtime/native-client-auth.d.ts +3 -0
- package/dist/lib/runtime/native-client-auth.d.ts.map +1 -0
- package/dist/lib/runtime/native-client-auth.js +42 -0
- package/dist/lib/runtime/native-provider-config.d.ts +4 -0
- package/dist/lib/runtime/native-provider-config.d.ts.map +1 -0
- package/dist/lib/runtime/native-provider-config.js +52 -0
- package/dist/lib/runtime/native-rate-limits.d.ts +18 -0
- package/dist/lib/runtime/native-rate-limits.d.ts.map +1 -0
- package/dist/lib/runtime/native-rate-limits.js +135 -0
- package/dist/lib/runtime/reset-credit-routing.d.ts +41 -0
- package/dist/lib/runtime/reset-credit-routing.d.ts.map +1 -0
- package/dist/lib/runtime/reset-credit-routing.js +66 -0
- package/dist/lib/runtime/reset-credits.d.ts +101 -0
- package/dist/lib/runtime/reset-credits.d.ts.map +1 -0
- package/dist/lib/runtime/reset-credits.js +162 -0
- package/dist/lib/runtime/response-output-history.d.ts +17 -0
- package/dist/lib/runtime/response-output-history.d.ts.map +1 -0
- package/dist/lib/runtime/response-output-history.js +85 -0
- package/dist/lib/runtime/responses-websocket.d.ts +31 -0
- package/dist/lib/runtime/responses-websocket.d.ts.map +1 -0
- package/dist/lib/runtime/responses-websocket.js +636 -0
- package/dist/lib/runtime/resume-picker.d.ts +41 -0
- package/dist/lib/runtime/resume-picker.d.ts.map +1 -0
- package/dist/lib/runtime/resume-picker.js +169 -0
- package/dist/lib/runtime/rotation-account-selection.d.ts +6 -0
- package/dist/lib/runtime/rotation-account-selection.d.ts.map +1 -1
- package/dist/lib/runtime/rotation-account-selection.js +63 -0
- package/dist/lib/runtime/rotation-proxy-state.d.ts +33 -0
- package/dist/lib/runtime/rotation-proxy-state.d.ts.map +1 -1
- package/dist/lib/runtime/rotation-server-types.d.ts +18 -0
- package/dist/lib/runtime/rotation-server-types.d.ts.map +1 -1
- package/dist/lib/runtime/runtime-capability-failures.d.ts +14 -0
- package/dist/lib/runtime/runtime-capability-failures.d.ts.map +1 -0
- package/dist/lib/runtime/runtime-capability-failures.js +76 -0
- package/dist/lib/runtime/runtime-observability.d.ts +7 -0
- package/dist/lib/runtime/runtime-observability.d.ts.map +1 -1
- package/dist/lib/runtime/runtime-observability.js +42 -2
- package/dist/lib/runtime/subscription-first-use.d.ts +10 -0
- package/dist/lib/runtime/subscription-first-use.d.ts.map +1 -0
- package/dist/lib/runtime/subscription-first-use.js +64 -0
- package/dist/lib/runtime/subscription-quota-event.d.ts +6 -0
- package/dist/lib/runtime/subscription-quota-event.d.ts.map +1 -0
- package/dist/lib/runtime/subscription-quota-event.js +31 -0
- package/dist/lib/runtime/subscription-quota-order.d.ts +17 -0
- package/dist/lib/runtime/subscription-quota-order.d.ts.map +1 -0
- package/dist/lib/runtime/subscription-quota-order.js +31 -0
- package/dist/lib/runtime/workspace-model-scopes.d.ts +15 -0
- package/dist/lib/runtime/workspace-model-scopes.d.ts.map +1 -0
- package/dist/lib/runtime/workspace-model-scopes.js +34 -0
- package/dist/lib/runtime-constants.d.ts +3 -0
- package/dist/lib/runtime-constants.d.ts.map +1 -1
- package/dist/lib/runtime-constants.js +5 -0
- package/dist/lib/runtime-paths.d.ts.map +1 -1
- package/dist/lib/runtime-paths.js +4 -1
- package/dist/lib/runtime-rotation-proxy.d.ts.map +1 -1
- package/dist/lib/runtime-rotation-proxy.js +852 -42
- package/dist/lib/schemas.d.ts +20 -0
- package/dist/lib/schemas.d.ts.map +1 -1
- package/dist/lib/schemas.js +3 -0
- package/dist/lib/storage/credential-sidecars.d.ts +13 -0
- package/dist/lib/storage/credential-sidecars.d.ts.map +1 -0
- package/dist/lib/storage/credential-sidecars.js +52 -0
- package/dist/lib/storage/file-lock.d.ts +9 -0
- package/dist/lib/storage/file-lock.d.ts.map +1 -0
- package/dist/lib/storage/file-lock.js +186 -0
- package/dist/lib/storage/flagged-storage.d.ts.map +1 -1
- package/dist/lib/storage/flagged-storage.js +12 -0
- package/dist/lib/storage/path-state.d.ts +2 -0
- package/dist/lib/storage/path-state.d.ts.map +1 -1
- package/dist/lib/storage/path-state.js +9 -0
- package/dist/lib/storage/paths.js +4 -4
- package/dist/lib/storage/pending-auth.d.ts +18 -0
- package/dist/lib/storage/pending-auth.d.ts.map +1 -0
- package/dist/lib/storage/pending-auth.js +155 -0
- package/dist/lib/storage/public-types.d.ts +13 -0
- package/dist/lib/storage/public-types.d.ts.map +1 -1
- package/dist/lib/storage/record-identity.d.ts +16 -0
- package/dist/lib/storage/record-identity.d.ts.map +1 -0
- package/dist/lib/storage/record-identity.js +16 -0
- package/dist/lib/storage/snapshot-merge.d.ts +16 -0
- package/dist/lib/storage/snapshot-merge.d.ts.map +1 -0
- package/dist/lib/storage/snapshot-merge.js +206 -0
- package/dist/lib/storage/transactions.d.ts.map +1 -1
- package/dist/lib/storage/transactions.js +5 -4
- package/dist/lib/storage.d.ts +6 -0
- package/dist/lib/storage.d.ts.map +1 -1
- package/dist/lib/storage.js +77 -7
- package/dist/lib/ui/auth-menu-builder.d.ts +2 -0
- package/dist/lib/ui/auth-menu-builder.d.ts.map +1 -1
- package/dist/lib/ui/auth-menu-builder.js +1 -0
- package/dist/lib/ui/auth-menu.d.ts.map +1 -1
- package/dist/lib/ui/auth-menu.js +1 -0
- package/dist/lib/ui/check-progress.d.ts +8 -0
- package/dist/lib/ui/check-progress.d.ts.map +1 -0
- package/dist/lib/ui/check-progress.js +30 -0
- package/dist/lib/ui/format.d.ts +3 -1
- package/dist/lib/ui/format.d.ts.map +1 -1
- package/dist/lib/ui/format.js +8 -0
- package/dist/lib/ui/ui-copy.d.ts +2 -2
- package/dist/lib/ui/ui-copy.js +2 -2
- package/dist/lib/usage/pricing.js +2 -2
- package/dist/lib/usage/usage-extraction.d.ts +3 -1
- package/dist/lib/usage/usage-extraction.d.ts.map +1 -1
- package/dist/lib/usage/usage-extraction.js +46 -9
- package/node_modules/@codex-ai/plugin/dist/index.d.ts +2 -2
- package/node_modules/@codex-ai/plugin/dist/index.js +2 -2
- package/node_modules/@codex-ai/plugin/dist/tool.d.ts +42 -42
- package/node_modules/@codex-ai/plugin/dist/tool.js +29 -29
- package/node_modules/@codex-ai/plugin/package.json +9 -9
- package/node_modules/@codex-ai/sdk/dist/index.d.ts +4 -4
- package/node_modules/@codex-ai/sdk/dist/index.js +2 -2
- package/node_modules/@codex-ai/sdk/package.json +8 -8
- package/package.json +200 -197
- package/scripts/codex-app-launcher.js +653 -653
- package/scripts/codex-app-router.js +373 -366
- package/scripts/codex-bin-resolver.js +313 -281
- package/scripts/codex-multi-auth.js +59 -59
- package/scripts/codex-routing.js +62 -61
- package/scripts/codex.js +6642 -6564
- package/scripts/install-codex-auth-utils.js +213 -213
- package/scripts/mcodex.js +367 -367
- package/scripts/postinstall.js +114 -114
- package/scripts/preuninstall.js +265 -265
- package/vendor/codex-ai-plugin/dist/index.d.ts +2 -2
- package/vendor/codex-ai-plugin/dist/index.js +2 -2
- package/vendor/codex-ai-plugin/dist/tool.d.ts +42 -42
- package/vendor/codex-ai-plugin/dist/tool.js +29 -29
- package/vendor/codex-ai-plugin/package.json +9 -9
- package/vendor/codex-ai-sdk/dist/index.d.ts +4 -4
- package/vendor/codex-ai-sdk/dist/index.js +2 -2
- package/vendor/codex-ai-sdk/package.json +8 -8
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { ClientCancellationError } from "../request/client-cancellation.js";
|
|
2
|
+
import { mapWithConcurrency } from "../concurrency.js";
|
|
3
|
+
import { RuntimeCapabilityFailures, classifyCapabilityFailure, } from "./runtime-capability-failures.js";
|
|
4
|
+
import { readErrorBody } from "../request/stream-failover-runtime.js";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import { resolveModelRoute, parseModelRoute, modelEntitlements, } from "../model-route-policy.js";
|
|
7
|
+
import { isRecord } from "../utils.js";
|
|
8
|
+
/** Endpoint-family exclusions, not a fixed allowlist of text model releases.
|
|
9
|
+
* Model ID discovery alone does not prove Responses/tool compatibility. */
|
|
10
|
+
export function isNonResponsesModel(id) {
|
|
11
|
+
return (/(?:^|[-_])(?:tts|whisper|embedding|moderation|transcribe|transcription|realtime|audio|image|sora)(?:[-_]|$)/i.test(id) ||
|
|
12
|
+
/^(?:dall-e|babbage|davinci)(?:-|$)/i.test(id) ||
|
|
13
|
+
/(?:^|-)live(?:-|$)/i.test(id));
|
|
14
|
+
}
|
|
15
|
+
/** Conservative native-picker metadata: discovery proves an ID, not optional tool capabilities. */
|
|
16
|
+
export function apiPickerModel(id) {
|
|
17
|
+
return {
|
|
18
|
+
slug: id,
|
|
19
|
+
display_name: id,
|
|
20
|
+
description: "API model; optional capabilities are not advertised by model discovery.",
|
|
21
|
+
supported_reasoning_levels: [],
|
|
22
|
+
default_reasoning_level: null,
|
|
23
|
+
shell_type: "shell_command",
|
|
24
|
+
visibility: "list",
|
|
25
|
+
supported_in_api: true,
|
|
26
|
+
priority: 0,
|
|
27
|
+
availability_nux: null,
|
|
28
|
+
upgrade: null,
|
|
29
|
+
model_messages: {
|
|
30
|
+
instructions_template: "You are a coding assistant. Follow the user instructions and supplied tool definitions.",
|
|
31
|
+
},
|
|
32
|
+
support_verbosity: false,
|
|
33
|
+
default_verbosity: null,
|
|
34
|
+
apply_patch_tool_type: null,
|
|
35
|
+
truncation_policy: { mode: "bytes", limit: 10000 },
|
|
36
|
+
experimental_supported_tools: [],
|
|
37
|
+
input_modalities: ["text"],
|
|
38
|
+
supports_reasoning_summary_parameter: false,
|
|
39
|
+
use_responses_lite: false,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** Apply current credential policy, never metadata inherited from another credential or old config. */
|
|
43
|
+
function withAccessPrograms(model, route) {
|
|
44
|
+
const result = { ...model };
|
|
45
|
+
delete result.available_access_programs;
|
|
46
|
+
const override = route.modelAccessPrograms && Object.hasOwn(route.modelAccessPrograms, model.slug)
|
|
47
|
+
? route.modelAccessPrograms[model.slug] : undefined;
|
|
48
|
+
const programs = override ?? route.accessPrograms;
|
|
49
|
+
if (programs)
|
|
50
|
+
result.available_access_programs = structuredClone(programs);
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
export async function boundedJson(response, max = 8 * 1024 * 1024) {
|
|
54
|
+
const reader = response.body?.getReader();
|
|
55
|
+
if (!reader)
|
|
56
|
+
throw Error("Empty model catalog");
|
|
57
|
+
const chunks = [];
|
|
58
|
+
let size = 0;
|
|
59
|
+
try {
|
|
60
|
+
for (;;) {
|
|
61
|
+
const { done, value } = await reader.read();
|
|
62
|
+
if (done)
|
|
63
|
+
break;
|
|
64
|
+
size += value.byteLength;
|
|
65
|
+
if (size > max)
|
|
66
|
+
throw Error("Model catalog too large");
|
|
67
|
+
chunks.push(value);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
await reader.cancel();
|
|
72
|
+
}
|
|
73
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
74
|
+
}
|
|
75
|
+
export class ApiModelRuntime {
|
|
76
|
+
fetchImpl;
|
|
77
|
+
now;
|
|
78
|
+
updateDiscovery;
|
|
79
|
+
enrichModels;
|
|
80
|
+
onCatalogUpdated;
|
|
81
|
+
discoveryUpdates = new Map();
|
|
82
|
+
lastPersistenceError = null;
|
|
83
|
+
failures = new RuntimeCapabilityFailures(() => this.now());
|
|
84
|
+
visibility = new Map();
|
|
85
|
+
snapshots = new Map();
|
|
86
|
+
activeKeys = new Set();
|
|
87
|
+
cachedCatalogs() {
|
|
88
|
+
return [...this.snapshots]
|
|
89
|
+
.filter(([key]) => this.activeKeys.has(key))
|
|
90
|
+
.map(([, catalog]) => catalog);
|
|
91
|
+
}
|
|
92
|
+
cache = new Map();
|
|
93
|
+
constructor(fetchImpl = fetch, now = Date.now, updateDiscovery, enrichModels, onCatalogUpdated) {
|
|
94
|
+
this.fetchImpl = fetchImpl;
|
|
95
|
+
this.now = now;
|
|
96
|
+
this.updateDiscovery = updateDiscovery;
|
|
97
|
+
this.enrichModels = enrichModels;
|
|
98
|
+
this.onCatalogUpdated = onCatalogUpdated;
|
|
99
|
+
}
|
|
100
|
+
key(route) {
|
|
101
|
+
return createHash("sha256")
|
|
102
|
+
.update(route.id)
|
|
103
|
+
.update("\0")
|
|
104
|
+
.update(route.apiKey)
|
|
105
|
+
.digest("hex");
|
|
106
|
+
}
|
|
107
|
+
async catalogs(routes, refresh = false, forceProbes = false, waitForCapabilities = true, waitForPersistence = true, requestKind) {
|
|
108
|
+
this.activeKeys = new Set(routes.map((route) => this.key(route)));
|
|
109
|
+
return mapWithConcurrency(requestKind ? routes.filter(route => route.kind === requestKind) : routes, 3, async (route) => {
|
|
110
|
+
const key = this.key(route), cached = this.cache.get(key);
|
|
111
|
+
if (route.enabled &&
|
|
112
|
+
(refresh ||
|
|
113
|
+
!cached ||
|
|
114
|
+
cached.at + (cached.error ? 5000 : 5 * 60_000) <= this.now())) {
|
|
115
|
+
let models = [];
|
|
116
|
+
let error = false;
|
|
117
|
+
try {
|
|
118
|
+
const response = await this.fetchImpl("https://api.openai.com/v1/models", {
|
|
119
|
+
method: "GET",
|
|
120
|
+
headers: { authorization: `Bearer ${route.apiKey}` },
|
|
121
|
+
redirect: "error",
|
|
122
|
+
signal: AbortSignal.timeout(15000),
|
|
123
|
+
});
|
|
124
|
+
if (!response.ok) {
|
|
125
|
+
await response.body?.cancel();
|
|
126
|
+
throw Error("API catalog unavailable");
|
|
127
|
+
}
|
|
128
|
+
const value = await boundedJson(response);
|
|
129
|
+
if (!isRecord(value) ||
|
|
130
|
+
!Array.isArray(value.data) ||
|
|
131
|
+
value.data.length > 10000)
|
|
132
|
+
throw Error("Invalid API catalog");
|
|
133
|
+
models = value.data.flatMap((m) => isRecord(m) &&
|
|
134
|
+
typeof m.id === "string" &&
|
|
135
|
+
/^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$/.test(m.id) &&
|
|
136
|
+
!isNonResponsesModel(m.id)
|
|
137
|
+
? [
|
|
138
|
+
{
|
|
139
|
+
...apiPickerModel(m.id),
|
|
140
|
+
...cached?.models.find((old) => old.slug === m.id),
|
|
141
|
+
},
|
|
142
|
+
]
|
|
143
|
+
: []);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
error = true;
|
|
147
|
+
}
|
|
148
|
+
if (this.cache.size >= 100)
|
|
149
|
+
this.cache.delete(this.cache.keys().next().value ?? "");
|
|
150
|
+
this.cache.set(key, { at: this.now(), models, error });
|
|
151
|
+
}
|
|
152
|
+
const entry = this.cache.get(key);
|
|
153
|
+
if (route.enabled && entry && !entry.error && this.updateDiscovery) {
|
|
154
|
+
let pending = this.discoveryUpdates.get(key);
|
|
155
|
+
if (!pending) {
|
|
156
|
+
pending = this.updateDiscovery(route, entry.models.map(m => m.slug));
|
|
157
|
+
this.discoveryUpdates.set(key, pending);
|
|
158
|
+
void pending.then(() => { this.lastPersistenceError = null; }, () => { this.lastPersistenceError = "api_catalog_persistence_failed"; }).finally(() => { this.discoveryUpdates.delete(key); });
|
|
159
|
+
}
|
|
160
|
+
if (waitForPersistence) {
|
|
161
|
+
try {
|
|
162
|
+
route = await pending;
|
|
163
|
+
}
|
|
164
|
+
catch { /* Keep the explicit policy read for this request; discovery cannot expand it. */ }
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
this.visibility.set(key, route.visibleModels);
|
|
168
|
+
const snapshot = {
|
|
169
|
+
id: route.id,
|
|
170
|
+
kind: route.kind,
|
|
171
|
+
enabled: route.enabled,
|
|
172
|
+
priority: route.priority,
|
|
173
|
+
models: (entry?.models ?? []).map(model => withAccessPrograms(model, route)),
|
|
174
|
+
visibleModels: route.visibleModels,
|
|
175
|
+
};
|
|
176
|
+
this.snapshots.set(key, snapshot);
|
|
177
|
+
if (route.enabled && entry && !entry.error && this.enrichModels && (!requestKind || !cached || cached.error)) {
|
|
178
|
+
const pending = this.enrichModels(entry.models.filter((m) => route.visibleModels.includes(m.slug)), refresh, requestKind ? { ...route, probeCapabilities: false } : route, forceProbes).then((enriched) => {
|
|
179
|
+
const byId = new Map(enriched.map((m) => [m.slug, m]));
|
|
180
|
+
snapshot.models = entry.models.map((m) => withAccessPrograms(byId.get(m.slug) ?? m, route));
|
|
181
|
+
// The caller receives its own completed result; only the newest refresh may replace the shared cache.
|
|
182
|
+
if (this.cache.get(key) !== entry ||
|
|
183
|
+
this.snapshots.get(key) !== snapshot ||
|
|
184
|
+
!this.activeKeys.has(key))
|
|
185
|
+
return;
|
|
186
|
+
entry.models = snapshot.models;
|
|
187
|
+
this.onCatalogUpdated?.();
|
|
188
|
+
});
|
|
189
|
+
if (waitForCapabilities)
|
|
190
|
+
await pending;
|
|
191
|
+
else
|
|
192
|
+
void pending.catch(() => {
|
|
193
|
+
entry.error = true;
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
return snapshot;
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
statuses(routes) {
|
|
200
|
+
return routes.map((r) => {
|
|
201
|
+
const c = this.cache.get(this.key(r));
|
|
202
|
+
return {
|
|
203
|
+
id: r.id,
|
|
204
|
+
kind: r.kind,
|
|
205
|
+
label: r.label,
|
|
206
|
+
priority: r.priority,
|
|
207
|
+
checkedAt: c?.at ?? 0,
|
|
208
|
+
error: c?.error ?? true,
|
|
209
|
+
availableModels: c?.models.map((m) => m.slug) ?? [],
|
|
210
|
+
entitlements: (c?.models ?? [])
|
|
211
|
+
.filter((m) => (this.visibility.get(this.key(r)) ?? r.visibleModels).includes(m.slug))
|
|
212
|
+
.map(model => modelEntitlements(withAccessPrograms(model, r))),
|
|
213
|
+
visibleModels: (this.visibility.get(this.key(r)) ?? r.visibleModels).filter((id) => c?.models.some((m) => m.slug === id)),
|
|
214
|
+
};
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
async request(alias, body, routes, signal, onAttempt) {
|
|
218
|
+
let route;
|
|
219
|
+
try {
|
|
220
|
+
if (isNonResponsesModel(parseModelRoute(alias).upstreamModel))
|
|
221
|
+
return this.failure(400, "model_requires_other_endpoint", "This model uses a media, audio, embedding, or legacy endpoint and cannot serve a Codex Responses turn. Select a text/coding model.");
|
|
222
|
+
route = resolveModelRoute(alias, await this.catalogs(routes, false, false, true, false, parseModelRoute(alias).kind), {
|
|
223
|
+
serviceTier: typeof body.service_tier === "string" ? body.service_tier : undefined,
|
|
224
|
+
reasoningEffort: isRecord(body.reasoning) && typeof body.reasoning.effort === "string"
|
|
225
|
+
? body.reasoning.effort
|
|
226
|
+
: undefined,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return this.failure(400, "invalid_model_route");
|
|
231
|
+
}
|
|
232
|
+
if (route.kind === "oauth")
|
|
233
|
+
return this.failure(400, "explicit_api_model_required");
|
|
234
|
+
// Until connection-affinity support exists, never guess the owner of server-side response state.
|
|
235
|
+
if (body.background === true || body.conversation)
|
|
236
|
+
return this.failure(400, "stateful_api_route_unsupported");
|
|
237
|
+
if (body.previous_response_id)
|
|
238
|
+
return this.failure(400, "previous_response_not_found");
|
|
239
|
+
const payload = {
|
|
240
|
+
...body,
|
|
241
|
+
model: route.upstreamModel,
|
|
242
|
+
store: false,
|
|
243
|
+
};
|
|
244
|
+
if (route.serviceTier)
|
|
245
|
+
payload.service_tier = route.serviceTier;
|
|
246
|
+
delete payload.client_metadata;
|
|
247
|
+
let lastStatus = 503;
|
|
248
|
+
let lastRejection;
|
|
249
|
+
for (const candidate of route.candidates) {
|
|
250
|
+
const credential = routes.find((r) => r.id === candidate.id);
|
|
251
|
+
if (!credential)
|
|
252
|
+
continue;
|
|
253
|
+
const effort = isRecord(body.reasoning) && typeof body.reasoning.effort === "string"
|
|
254
|
+
? body.reasoning.effort
|
|
255
|
+
: undefined;
|
|
256
|
+
if (!this.failures.supports(this.key(credential), route.upstreamModel, effort, route.serviceTier))
|
|
257
|
+
continue;
|
|
258
|
+
if (signal?.aborted)
|
|
259
|
+
throw new ClientCancellationError();
|
|
260
|
+
const controller = new AbortController();
|
|
261
|
+
const abort = () => controller.abort();
|
|
262
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
263
|
+
const timer = setTimeout(abort, 60000);
|
|
264
|
+
try {
|
|
265
|
+
onAttempt?.(routes.indexOf(credential));
|
|
266
|
+
const response = await this.fetchImpl("https://api.openai.com/v1/responses", {
|
|
267
|
+
method: "POST",
|
|
268
|
+
headers: {
|
|
269
|
+
authorization: `Bearer ${credential.apiKey}`,
|
|
270
|
+
"content-type": "application/json",
|
|
271
|
+
},
|
|
272
|
+
body: JSON.stringify(payload),
|
|
273
|
+
redirect: "error",
|
|
274
|
+
signal: controller.signal,
|
|
275
|
+
});
|
|
276
|
+
lastStatus = response.status;
|
|
277
|
+
lastRejection = undefined;
|
|
278
|
+
if (response.ok) {
|
|
279
|
+
const headers = new Headers();
|
|
280
|
+
for (const name of [
|
|
281
|
+
"content-type",
|
|
282
|
+
"cache-control",
|
|
283
|
+
"x-request-id",
|
|
284
|
+
"retry-after",
|
|
285
|
+
]) {
|
|
286
|
+
const value = response.headers.get(name);
|
|
287
|
+
if (value)
|
|
288
|
+
headers.set(name, value);
|
|
289
|
+
}
|
|
290
|
+
return new Response(response.body, {
|
|
291
|
+
status: response.status,
|
|
292
|
+
headers,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
const errorBody = await readErrorBody(response, 5000, 65536);
|
|
296
|
+
let data;
|
|
297
|
+
try {
|
|
298
|
+
data = JSON.parse(errorBody);
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
data = null;
|
|
302
|
+
}
|
|
303
|
+
const rejection = classifyCapabilityFailure(response.status, data);
|
|
304
|
+
const source = isRecord(data) && isRecord(data.error) ? data.error : {};
|
|
305
|
+
const sanitized = { message: "Upstream rejected the API request.", code: "api_request_rejected" };
|
|
306
|
+
for (const field of ["code", "type", "param"]) {
|
|
307
|
+
const value = source[field];
|
|
308
|
+
if (typeof value === "string" && /^[A-Za-z0-9_.]{1,80}$/.test(value))
|
|
309
|
+
sanitized[field] = value;
|
|
310
|
+
}
|
|
311
|
+
if (rejection) {
|
|
312
|
+
lastRejection = Response.json({ error: sanitized }, { status: response.status });
|
|
313
|
+
this.failures.record(this.key(credential), route.upstreamModel, rejection, effort, route.serviceTier);
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
if (![401, 403, 429, 500, 502, 503, 504].includes(response.status)) {
|
|
317
|
+
return Response.json({ error: sanitized }, { status: response.status });
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
if (error instanceof ClientCancellationError)
|
|
322
|
+
throw error;
|
|
323
|
+
if (signal?.aborted)
|
|
324
|
+
throw new ClientCancellationError();
|
|
325
|
+
lastStatus = 502;
|
|
326
|
+
lastRejection = undefined;
|
|
327
|
+
}
|
|
328
|
+
finally {
|
|
329
|
+
clearTimeout(timer);
|
|
330
|
+
signal?.removeEventListener("abort", abort);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (lastRejection)
|
|
334
|
+
return lastRejection;
|
|
335
|
+
// A 401/403 here belongs to the API key, not the desktop login; the native
|
|
336
|
+
// client must not read an exhausted credential pool as a bad login.
|
|
337
|
+
return this.failure(lastStatus === 401 || lastStatus === 403 ? 503 : lastStatus, "model_route_pool_unavailable");
|
|
338
|
+
}
|
|
339
|
+
recordStreamFailure(credential, alias, body, error) {
|
|
340
|
+
const route = parseModelRoute(alias);
|
|
341
|
+
if (route.kind !== credential.kind)
|
|
342
|
+
return;
|
|
343
|
+
const rejection = classifyCapabilityFailure(400, error);
|
|
344
|
+
if (!rejection)
|
|
345
|
+
return;
|
|
346
|
+
const effort = isRecord(body.reasoning) && typeof body.reasoning.effort === "string" ? body.reasoning.effort : undefined;
|
|
347
|
+
const tier = route.serviceTier ?? (typeof body.service_tier === "string" ? body.service_tier : undefined);
|
|
348
|
+
this.failures.record(this.key(credential), route.upstreamModel, rejection, effort, tier);
|
|
349
|
+
}
|
|
350
|
+
failure(status, code, message = "No eligible credential could serve the selected API model route. No other pool was used.") {
|
|
351
|
+
return Response.json({
|
|
352
|
+
error: {
|
|
353
|
+
code,
|
|
354
|
+
message,
|
|
355
|
+
},
|
|
356
|
+
}, { status });
|
|
357
|
+
}
|
|
358
|
+
}
|
|
@@ -11,6 +11,11 @@ export interface AppBindPaths {
|
|
|
11
11
|
launchAgentPath: string | null;
|
|
12
12
|
}
|
|
13
13
|
export interface AppBindState {
|
|
14
|
+
nativeOpenai?: boolean;
|
|
15
|
+
catalogAccount?: {
|
|
16
|
+
email: string;
|
|
17
|
+
accountId: string;
|
|
18
|
+
};
|
|
14
19
|
version: 1;
|
|
15
20
|
platform: NodeJS.Platform;
|
|
16
21
|
host: string;
|
|
@@ -70,6 +75,11 @@ export interface AppBindResult {
|
|
|
70
75
|
}
|
|
71
76
|
export type ProcessIdentityVerifier = (pid: number, startedAt: number, platform: NodeJS.Platform, identityToken?: string) => boolean | Promise<boolean>;
|
|
72
77
|
export interface AppBindOptions {
|
|
78
|
+
nativeOpenai?: boolean;
|
|
79
|
+
catalogAccount?: {
|
|
80
|
+
email: string;
|
|
81
|
+
accountId: string;
|
|
82
|
+
};
|
|
73
83
|
env?: NodeJS.ProcessEnv;
|
|
74
84
|
platform?: NodeJS.Platform;
|
|
75
85
|
home?: string;
|
|
@@ -93,6 +103,8 @@ export interface DetachedProcessStopOptions {
|
|
|
93
103
|
/** Expected per-process nonce when verifying a persisted PID. */
|
|
94
104
|
identityToken?: string;
|
|
95
105
|
verifyProcessIdentity?: ProcessIdentityVerifier;
|
|
106
|
+
/** Called once the recorded PID is verified as this router, before it is stopped. */
|
|
107
|
+
onOwnershipVerified?: () => void;
|
|
96
108
|
}
|
|
97
109
|
export interface RuntimeRotationAppHelperStatus {
|
|
98
110
|
state: string | null;
|
|
@@ -104,9 +116,15 @@ export interface RuntimeRotationAppHelperStatus {
|
|
|
104
116
|
/** Per-process nonce echoed by the helper command line. */
|
|
105
117
|
identityToken?: string | null;
|
|
106
118
|
}
|
|
107
|
-
export declare function rewriteConfigTomlForAppBind(rawConfig: string, baseUrl: string, clientApiKey?: string): string;
|
|
119
|
+
export declare function rewriteConfigTomlForAppBind(rawConfig: string, baseUrl: string, clientApiKey?: string, nativeOpenai?: boolean): string;
|
|
108
120
|
export declare function restoreConfigTomlFromAppBind(currentConfig: string, originalConfig: string): string;
|
|
109
121
|
export declare function resolveAppBindPaths(options?: AppBindOptions): AppBindPaths;
|
|
122
|
+
/**
|
|
123
|
+
* Whether anything still accepts connections at a recorded router address.
|
|
124
|
+
* Only "refused" is proof that no router serves it; a timeout or an unusable
|
|
125
|
+
* address is "unknown".
|
|
126
|
+
*/
|
|
127
|
+
export declare function probeRouterAddress(baseUrl: string | null | undefined, platform?: NodeJS.Platform, timeoutMs?: number): Promise<"refused" | "listening" | "unknown">;
|
|
110
128
|
export declare function stopRuntimeRotationRouterProcess(router: Pick<AppBindRouterStatus, "state" | "pid" | "startedAt" | "updatedAt" | "statusPath" | "routerScriptPath" | "identityToken"> | null, platform: NodeJS.Platform, routerScriptPath: string, options?: DetachedProcessStopOptions): Promise<boolean>;
|
|
111
129
|
/** Parse the documented local-time format emitted by `ps -o lstart=`. */
|
|
112
130
|
export declare function parsePosixProcessStartTime(value: string): number | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-bind.d.ts","sourceRoot":"","sources":["../../../lib/runtime/app-bind.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"app-bind.d.ts","sourceRoot":"","sources":["../../../lib/runtime/app-bind.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import { withNativeBindingLock } from "./native-binding-lock.js";
|
|
2
|
+
import { isRecord } from "../utils.js";
|
|
3
|
+
import { hasNativeProviderConfig, rewriteNativeProviderConfig, restoreNativeProviderConfig } from "./native-provider-config.js";
|
|
1
4
|
import { spawn } from "node:child_process";
|
|
2
5
|
import { createHash, randomBytes } from "node:crypto";
|
|
3
6
|
import { closeSync, existsSync, mkdirSync, openSync } from "node:fs";
|
|
4
7
|
import { mkdir, open, readFile, readdir, rename, rm, unlink } from "node:fs/promises";
|
|
8
|
+
import { connect } from "node:net";
|
|
5
9
|
import { homedir } from "node:os";
|
|
6
10
|
import { basename, dirname, join } from "node:path";
|
|
7
11
|
import process from "node:process";
|
|
@@ -51,11 +55,13 @@ async function withAppBindLock(key, operation) {
|
|
|
51
55
|
}
|
|
52
56
|
}
|
|
53
57
|
}
|
|
54
|
-
export function rewriteConfigTomlForAppBind(rawConfig, baseUrl, clientApiKey = "") {
|
|
58
|
+
export function rewriteConfigTomlForAppBind(rawConfig, baseUrl, clientApiKey = "", nativeOpenai = false) {
|
|
59
|
+
if (nativeOpenai)
|
|
60
|
+
return rewriteNativeProviderConfig(rawConfig, baseUrl);
|
|
55
61
|
return rewriteConfigTomlForRuntimeRotationProvider(rawConfig, baseUrl, clientApiKey);
|
|
56
62
|
}
|
|
57
63
|
export function restoreConfigTomlFromAppBind(currentConfig, originalConfig) {
|
|
58
|
-
return restoreConfigTomlFromRuntimeRotationProvider(currentConfig, originalConfig);
|
|
64
|
+
return restoreConfigTomlFromRuntimeRotationProvider(restoreNativeProviderConfig(currentConfig, originalConfig), originalConfig);
|
|
59
65
|
}
|
|
60
66
|
function sha256(value) {
|
|
61
67
|
return createHash("sha256").update(value).digest("hex");
|
|
@@ -183,6 +189,8 @@ function readAppBindStateRecord(record) {
|
|
|
183
189
|
nodePath,
|
|
184
190
|
routerScriptPath,
|
|
185
191
|
clientApiKey,
|
|
192
|
+
nativeOpenai: record.nativeOpenai === true,
|
|
193
|
+
catalogAccount: isRecord(record.catalogAccount) && typeof record.catalogAccount.email === "string" && typeof record.catalogAccount.accountId === "string" ? { email: record.catalogAccount.email, accountId: record.catalogAccount.accountId } : undefined,
|
|
186
194
|
identityToken: identityToken ?? undefined,
|
|
187
195
|
startupPath: readString(record, "startupPath"),
|
|
188
196
|
launchAgentPath: readString(record, "launchAgentPath"),
|
|
@@ -421,6 +429,30 @@ function spawnRouter(state) {
|
|
|
421
429
|
closeSync(logFd);
|
|
422
430
|
}
|
|
423
431
|
}
|
|
432
|
+
/**
|
|
433
|
+
* Whether anything still accepts connections at a recorded router address.
|
|
434
|
+
* Only "refused" is proof that no router serves it; a timeout or an unusable
|
|
435
|
+
* address is "unknown".
|
|
436
|
+
*/
|
|
437
|
+
export async function probeRouterAddress(baseUrl, platform = process.platform, timeoutMs = platform === "win32" ? WINDOWS_PROCESS_IDENTITY_PROBE_TIMEOUT_MS : 1000) {
|
|
438
|
+
let url;
|
|
439
|
+
try {
|
|
440
|
+
url = new URL(baseUrl ?? "");
|
|
441
|
+
}
|
|
442
|
+
catch {
|
|
443
|
+
return "unknown";
|
|
444
|
+
}
|
|
445
|
+
const port = Number(url.port);
|
|
446
|
+
if (!Number.isInteger(port) || port <= 0)
|
|
447
|
+
return "unknown";
|
|
448
|
+
return new Promise((resolve) => {
|
|
449
|
+
const socket = connect({ host: url.hostname.replace(/^\[|\]$/g, ""), port });
|
|
450
|
+
const done = (result) => { clearTimeout(timer); socket.destroy(); resolve(result); };
|
|
451
|
+
const timer = setTimeout(() => done("unknown"), timeoutMs);
|
|
452
|
+
socket.once("connect", () => done("listening"));
|
|
453
|
+
socket.once("error", (error) => done(error.code === "ECONNREFUSED" ? "refused" : "unknown"));
|
|
454
|
+
});
|
|
455
|
+
}
|
|
424
456
|
async function maybeStartRouter(state, options) {
|
|
425
457
|
if (options.spawnDetached === false)
|
|
426
458
|
return false;
|
|
@@ -497,6 +529,7 @@ export async function stopRuntimeRotationRouterProcess(router, platform, routerS
|
|
|
497
529
|
if (!verified) {
|
|
498
530
|
return false;
|
|
499
531
|
}
|
|
532
|
+
options.onOwnershipVerified?.();
|
|
500
533
|
return stopDetachedProcess(router.pid, platform, options);
|
|
501
534
|
}
|
|
502
535
|
async function stopRouter(router, platform, routerScriptPath, options = {}) {
|
|
@@ -537,6 +570,13 @@ async function runWindowsTaskkill(pid) {
|
|
|
537
570
|
}
|
|
538
571
|
});
|
|
539
572
|
}
|
|
573
|
+
// `ps -o lstart=` is locale-sensitive: under a non-English LC_TIME it prints
|
|
574
|
+
// localized day/month names that parsePosixProcessStartTime cannot read, so
|
|
575
|
+
// every POSIX identity probe would fail and unbind would leak live routers.
|
|
576
|
+
const POSIX_IDENTITY_PROBE_ENV = {
|
|
577
|
+
...process.env,
|
|
578
|
+
LC_ALL: "C",
|
|
579
|
+
};
|
|
540
580
|
async function runProcessIdentityProbe(command, args, options = {}) {
|
|
541
581
|
return new Promise((resolve) => {
|
|
542
582
|
let output = "";
|
|
@@ -568,6 +608,7 @@ async function runProcessIdentityProbe(command, args, options = {}) {
|
|
|
568
608
|
child = spawn(command, args, {
|
|
569
609
|
stdio: ["ignore", "pipe", "ignore"],
|
|
570
610
|
windowsHide: true,
|
|
611
|
+
...(options.env ? { env: options.env } : {}),
|
|
571
612
|
});
|
|
572
613
|
child.stdout?.setEncoding("utf8");
|
|
573
614
|
child.stdout?.on("data", (chunk) => {
|
|
@@ -616,8 +657,8 @@ async function readProcessIdentity(pid, platform, log) {
|
|
|
616
657
|
};
|
|
617
658
|
}
|
|
618
659
|
const [startedAtRaw, commandLineRaw] = await Promise.all([
|
|
619
|
-
runProcessIdentityProbe("ps", ["-p", String(pid), "-o", "lstart="], { log }),
|
|
620
|
-
runProcessIdentityProbe("ps", ["-p", String(pid), "-o", "command="], { log }),
|
|
660
|
+
runProcessIdentityProbe("ps", ["-p", String(pid), "-o", "lstart="], { env: POSIX_IDENTITY_PROBE_ENV, log }),
|
|
661
|
+
runProcessIdentityProbe("ps", ["-p", String(pid), "-o", "command="], { env: POSIX_IDENTITY_PROBE_ENV, log }),
|
|
621
662
|
]);
|
|
622
663
|
if (!startedAtRaw || !commandLineRaw) {
|
|
623
664
|
log?.(`Process identity probe returned no POSIX record for PID ${pid}`);
|
|
@@ -960,7 +1001,7 @@ export async function getAppBindStatus(options = {}) {
|
|
|
960
1001
|
if (state === null) {
|
|
961
1002
|
const current = await readConfigIfExists(paths.configPath);
|
|
962
1003
|
unmanagedBind =
|
|
963
|
-
current.existed && configHasRuntimeRotationProvider(current.content);
|
|
1004
|
+
current.existed && (configHasRuntimeRotationProvider(current.content) || hasNativeProviderConfig(current.content));
|
|
964
1005
|
}
|
|
965
1006
|
return {
|
|
966
1007
|
bound: state !== null || unmanagedBind,
|
|
@@ -973,12 +1014,42 @@ export async function getAppBindStatus(options = {}) {
|
|
|
973
1014
|
}
|
|
974
1015
|
export async function bindCodexAppRuntimeRotation(options = {}) {
|
|
975
1016
|
const paths = resolveAppBindPaths(options);
|
|
976
|
-
return withAppBindLock(paths.bindDir, () => bindCodexAppRuntimeRotationLocked(options, paths));
|
|
1017
|
+
return withAppBindLock(paths.bindDir, () => withNativeBindingLock(paths.configPath, () => bindCodexAppRuntimeRotationLocked(options, paths)));
|
|
977
1018
|
}
|
|
978
1019
|
async function bindCodexAppRuntimeRotationLocked(options, paths) {
|
|
979
1020
|
const platform = options.platform ?? process.platform;
|
|
980
1021
|
const now = options.now?.() ?? Date.now();
|
|
981
1022
|
const existingState = await readAppBindState(paths.statePath);
|
|
1023
|
+
const nativeOpenai = options.nativeOpenai ?? existingState?.nativeOpenai ??
|
|
1024
|
+
((options.env ?? process.env).CODEX_MULTI_AUTH_NATIVE_OPENAI === "1");
|
|
1025
|
+
const catalogAccount = options.nativeOpenai === false ? undefined : options.catalogAccount ?? existingState?.catalogAccount;
|
|
1026
|
+
if (existingState && (!!existingState.nativeOpenai !== nativeOpenai || JSON.stringify(catalogAccount) !== JSON.stringify(existingState.catalogAccount))) {
|
|
1027
|
+
const router = await readRouterStatus(paths.statusPath);
|
|
1028
|
+
let ownRouter = false;
|
|
1029
|
+
await stopRouter(router, platform, existingState.routerScriptPath, {
|
|
1030
|
+
log: options.log, identityToken: existingState.identityToken, verifyProcessIdentity: options.verifyProcessIdentity,
|
|
1031
|
+
onOwnershipVerified: () => { ownRouter = true; },
|
|
1032
|
+
});
|
|
1033
|
+
// Refuse only when the recorded PID is verifiably our router and it survived
|
|
1034
|
+
// the stop. A recycled PID now owned by another process must not block
|
|
1035
|
+
// mode changes forever (unbind treats the same case as a warning).
|
|
1036
|
+
if (router?.pid && isProcessAlive(router.pid)) {
|
|
1037
|
+
if (ownRouter)
|
|
1038
|
+
throw new Error("Stop the existing app router before changing provider mode");
|
|
1039
|
+
// A failed identity check is not proof: a slow or failing process probe
|
|
1040
|
+
// (common on Windows) also returns false for a live router. Treat the pid
|
|
1041
|
+
// as recycled only when nothing answers at the recorded router address.
|
|
1042
|
+
const address = await probeRouterAddress(router.baseUrl ?? existingState.baseUrl, options.platform ?? process.platform);
|
|
1043
|
+
if (address !== "refused") {
|
|
1044
|
+
throw new Error(`Could not confirm that the app router recorded as pid ${router.pid} has stopped (its address ${address === "listening" ? "still accepts connections" : "could not be checked"}). Stop it before changing provider mode.`);
|
|
1045
|
+
}
|
|
1046
|
+
options.log?.(`Warning: recorded router pid ${router.pid} is not the app router; continuing`);
|
|
1047
|
+
// The record describes a router that no longer exists. Drop it so
|
|
1048
|
+
// maybeStartRouter starts a replacement instead of trusting the live
|
|
1049
|
+
// (recycled) pid and leaving the new mode pointed at a dead address.
|
|
1050
|
+
await unlinkIfExists(paths.statusPath);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
982
1053
|
const host = existingState?.host ?? "127.0.0.1";
|
|
983
1054
|
let port = existingState && existingState.port > 0 ? existingState.port : 0;
|
|
984
1055
|
let baseUrl = existingState?.baseUrl ?? formatBaseUrl(host, port);
|
|
@@ -993,8 +1064,13 @@ async function bindCodexAppRuntimeRotationLocked(options, paths) {
|
|
|
993
1064
|
content,
|
|
994
1065
|
createdAt: now,
|
|
995
1066
|
};
|
|
996
|
-
|
|
1067
|
+
const bindingContent = nativeOpenai
|
|
1068
|
+
? restoreConfigTomlFromAppBind(content, backup.content)
|
|
1069
|
+
: restoreNativeProviderConfig(content, backup.content);
|
|
1070
|
+
let boundConfig = rewriteConfigTomlForAppBind(bindingContent, baseUrl, clientApiKey, nativeOpenai);
|
|
997
1071
|
let state = {
|
|
1072
|
+
nativeOpenai,
|
|
1073
|
+
catalogAccount,
|
|
998
1074
|
version: 1,
|
|
999
1075
|
platform,
|
|
1000
1076
|
host,
|
|
@@ -1056,7 +1132,7 @@ async function bindCodexAppRuntimeRotationLocked(options, paths) {
|
|
|
1056
1132
|
}
|
|
1057
1133
|
throw new Error("Codex app bind could not resolve a runtime router port; refusing to write config.toml with port=0.");
|
|
1058
1134
|
}
|
|
1059
|
-
boundConfig = rewriteConfigTomlForAppBind(
|
|
1135
|
+
boundConfig = rewriteConfigTomlForAppBind(bindingContent, baseUrl, clientApiKey, nativeOpenai);
|
|
1060
1136
|
state = {
|
|
1061
1137
|
...state,
|
|
1062
1138
|
port,
|
|
@@ -1078,7 +1154,7 @@ async function bindCodexAppRuntimeRotationLocked(options, paths) {
|
|
|
1078
1154
|
}
|
|
1079
1155
|
export async function unbindCodexAppRuntimeRotation(options = {}) {
|
|
1080
1156
|
const paths = resolveAppBindPaths(options);
|
|
1081
|
-
return withAppBindLock(paths.bindDir, () => unbindCodexAppRuntimeRotationLocked(options, paths));
|
|
1157
|
+
return withAppBindLock(paths.bindDir, () => withNativeBindingLock(paths.configPath, () => unbindCodexAppRuntimeRotationLocked(options, paths)));
|
|
1082
1158
|
}
|
|
1083
1159
|
async function unbindCodexAppRuntimeRotationLocked(options, paths) {
|
|
1084
1160
|
const state = await readAppBindState(paths.statePath);
|
|
@@ -1262,8 +1338,8 @@ async function unbindCodexAppRuntimeRotationLocked(options, paths) {
|
|
|
1262
1338
|
// above can't see this, so consult the config directly and self-heal it
|
|
1263
1339
|
// back to a working provider when it is bound.
|
|
1264
1340
|
const current = await readConfigIfExists(paths.configPath);
|
|
1265
|
-
if (current.existed && configHasRuntimeRotationProvider(current.content)) {
|
|
1266
|
-
await atomicWriteFile(paths.configPath, restoreConfigTomlFromRuntimeRotationProviderWithoutBackup(current.content));
|
|
1341
|
+
if (current.existed && (configHasRuntimeRotationProvider(current.content) || hasNativeProviderConfig(current.content))) {
|
|
1342
|
+
await atomicWriteFile(paths.configPath, restoreConfigTomlFromRuntimeRotationProviderWithoutBackup(restoreNativeProviderConfig(current.content, "")));
|
|
1267
1343
|
selfHealed = true;
|
|
1268
1344
|
}
|
|
1269
1345
|
}
|
|
@@ -1328,6 +1404,8 @@ export function formatAppBindStatus(status) {
|
|
|
1328
1404
|
else if (status.router?.lastAccountIndex !== null && status.router?.lastAccountIndex !== undefined) {
|
|
1329
1405
|
parts.push(`lastAccount=Account ${status.router.lastAccountIndex + 1}`);
|
|
1330
1406
|
}
|
|
1407
|
+
if (status.state.nativeOpenai)
|
|
1408
|
+
return `Codex app bind: ${parts.join(", ")}, native OpenAI, live account catalogs`;
|
|
1331
1409
|
return [
|
|
1332
1410
|
`Codex app bind: ${parts.join(", ")}`,
|
|
1333
1411
|
[
|