@bitkyc08/opencodex 2.52.0-preview.20260912 → 2.53.0-preview.20260913
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/gui/dist/assets/index-BBOZWGB6.css +1 -0
- package/gui/dist/assets/index-D7ynYo2K.js +128 -0
- package/gui/dist/index.html +2 -2
- package/native/remote-workspace-helper/Cargo.lock +130 -0
- package/native/remote-workspace-helper/Cargo.toml +24 -0
- package/native/remote-workspace-helper/src/main.rs +49 -0
- package/native/remote-workspace-helper/src/protocol.rs +246 -0
- package/native/remote-workspace-helper/src/sandbox/macos.rs +19 -0
- package/native/remote-workspace-helper/src/sandbox/mod.rs +77 -0
- package/native/remote-workspace-helper/src/sandbox/windows.rs +15 -0
- package/package.json +6 -1
- package/src/adapters/anthropic-image-normalize.ts +30 -2
- package/src/adapters/anthropic.ts +1 -1
- package/src/adapters/base.ts +8 -2
- package/src/adapters/cursor/cursor-errors.ts +12 -0
- package/src/adapters/cursor/thread-continuity.ts +93 -0
- package/src/adapters/cursor.ts +104 -73
- package/src/adapters/devin/cloud-direct/chat.ts +312 -23
- package/src/adapters/devin/cloud-direct/metadata.ts +31 -2
- package/src/adapters/devin/live-models.ts +70 -3
- package/src/adapters/devin.ts +281 -21
- package/src/adapters/google-wire-compiler.ts +14 -6
- package/src/adapters/google.ts +22 -8
- package/src/adapters/kiro/adapter.ts +316 -0
- package/src/adapters/kiro/conversation.ts +136 -0
- package/src/adapters/kiro/payload.ts +432 -0
- package/src/adapters/kiro/reasoning.ts +56 -0
- package/src/adapters/kiro/stream.ts +1153 -0
- package/src/adapters/kiro/usage.ts +223 -0
- package/src/adapters/kiro/wire.ts +76 -0
- package/src/adapters/kiro.ts +8 -2319
- package/src/adapters/mimo-free.ts +1 -1
- package/src/adapters/openai-chat-images.ts +101 -0
- package/src/adapters/openai-chat.ts +201 -181
- package/src/adapters/openai-responses.ts +92 -224
- package/src/adapters/registry.ts +0 -7
- package/src/adapters/run-turn-queue.ts +13 -6
- package/src/bridge.ts +14 -15
- package/src/chat/inbound.ts +29 -4
- package/src/chat/outbound.ts +145 -107
- package/src/claude/desktop-profile.ts +4 -6
- package/src/cli/account-api.ts +14 -0
- package/src/cli/account-extended.ts +1 -1
- package/src/cli/account-history.ts +60 -0
- package/src/cli/account-main.ts +80 -0
- package/src/cli/account.ts +11 -3
- package/src/cli/capabilities.ts +113 -0
- package/src/cli/catalog.ts +109 -0
- package/src/cli/dispatch.ts +9 -0
- package/src/cli/help.ts +2 -0
- package/src/cli/index.ts +2 -2
- package/src/cli/observe.ts +28 -1
- package/src/cli/opencode.ts +42 -8
- package/src/cli/provider-runtime.ts +11 -1
- package/src/cli/provider.ts +22 -2
- package/src/cli/registry.ts +21 -0
- package/src/cli/remote-workspace.ts +154 -0
- package/src/cli/status.ts +39 -7
- package/src/cli/usage-report.ts +14 -2
- package/src/client/hub-client.ts +34 -0
- package/src/client/hub-state.ts +9 -1
- package/src/codex/account-store.ts +78 -0
- package/src/codex/auth-api.ts +81 -54
- package/src/codex/auth-context.ts +45 -16
- package/src/codex/catalog/effort.ts +1 -1
- package/src/codex/catalog/metadata.ts +3 -6
- package/src/codex/catalog/native-models.ts +4 -4
- package/src/codex/catalog/parsing.ts +2 -20
- package/src/codex/catalog/provider-fetch.ts +10 -1
- package/src/codex/catalog/remote.ts +233 -0
- package/src/codex/catalog/sync.ts +403 -35
- package/src/codex/convergence.ts +1 -1
- package/src/codex/history-manifest.ts +36 -0
- package/src/codex/history-provider.ts +32 -5
- package/src/codex/inject.ts +9 -0
- package/src/codex/main-account.ts +113 -0
- package/src/codex/main-device-reauth-api.ts +89 -0
- package/src/codex/main-device-reauth.ts +217 -0
- package/src/codex/native-residue.ts +9 -2
- package/src/codex/quota-auto-refresh.ts +3 -2
- package/src/codex/quota-capacity.ts +98 -0
- package/src/codex/quota-history.ts +160 -0
- package/src/codex/quota-types.ts +8 -0
- package/src/codex/quota.ts +118 -91
- package/src/codex/refresh.ts +2 -1
- package/src/codex/routing.ts +90 -17
- package/src/codex/sync.ts +33 -4
- package/src/combos/request.ts +19 -1
- package/src/config/multi-agent-surface.ts +61 -0
- package/src/config/provider-validation.ts +176 -0
- package/src/config.ts +213 -11
- package/src/generated/compatibility-version.json +436 -168
- package/src/images/loop.ts +119 -36
- package/src/lib/admission.ts +12 -6
- package/src/lib/redact.ts +7 -0
- package/src/lib/translator-budget.ts +4 -3
- package/src/lib/windows-atomic-replace.ts +1 -0
- package/src/lib/windows-elevation.ts +1 -1
- package/src/oauth/chatgpt-device.ts +62 -5
- package/src/oauth/devin/cli-import.ts +130 -0
- package/src/oauth/devin.ts +63 -8
- package/src/oauth/index.ts +29 -14
- package/src/oauth/kiro.ts +18 -6
- package/src/oauth/login-cli.ts +9 -1
- package/src/oauth/meta-muse-device.ts +464 -0
- package/src/oauth/meta-muse.ts +123 -32
- package/src/oauth/pool-kernel.ts +9 -0
- package/src/oauth/pool-settings-capability.ts +2 -2
- package/src/oauth/store.ts +57 -0
- package/src/oauth/types.ts +31 -0
- package/src/providers/derive.ts +13 -3
- package/src/providers/devin-cli-authmode-migration.ts +57 -35
- package/src/providers/devin-provider-merge-migration.ts +240 -0
- package/src/providers/muse-key-quota.ts +117 -0
- package/src/providers/muse-subscription-usage.ts +14 -2
- package/src/providers/openai-sidecar.ts +25 -3
- package/src/providers/opencode-zen-rate-limit.ts +58 -0
- package/src/providers/provider-id-rewrite.ts +20 -5
- package/src/providers/quota-types.ts +12 -0
- package/src/providers/quota.ts +143 -102
- package/src/providers/reasoning-metadata.ts +543 -0
- package/src/providers/registry.ts +80 -49
- package/src/reasoning-effort.ts +26 -2
- package/src/remote/hub-usage.ts +32 -0
- package/src/remote-control/index.ts +192 -41
- package/src/remote-control/workspace-activation.ts +9 -0
- package/src/remote-control/workspace-agent-connection.ts +366 -0
- package/src/remote-control/workspace-claude-runtime.ts +243 -0
- package/src/remote-control/workspace-codex-runtime.ts +531 -0
- package/src/remote-control/workspace-codex-sandbox.ts +115 -0
- package/src/remote-control/workspace-command-runner.ts +748 -0
- package/src/remote-control/workspace-coordinator.ts +231 -0
- package/src/remote-control/workspace-device.ts +585 -0
- package/src/remote-control/workspace-executable.ts +43 -0
- package/src/remote-control/workspace-executor.ts +397 -0
- package/src/remote-control/workspace-hub.ts +519 -0
- package/src/remote-control/workspace-pi-runtime.ts +382 -0
- package/src/remote-control/workspace-process.ts +129 -0
- package/src/remote-control/workspace-rpc.ts +304 -0
- package/src/remote-control/workspace-runtime.ts +60 -0
- package/src/remote-control/workspace-secret-store.ts +39 -0
- package/src/remote-control/workspace-sessions.ts +799 -0
- package/src/remote-control/workspace-tool-bridge.ts +192 -0
- package/src/responses/code-mode-helper-compat.ts +22 -3
- package/src/responses/hosted-tool-policy.ts +0 -1
- package/src/responses/muse-tool-name-alias.ts +379 -0
- package/src/responses/plaintext-v2-agent-messages.ts +902 -0
- package/src/router.ts +7 -0
- package/src/routing/compatibility/behavior.ts +0 -1
- package/src/server/audio-client.ts +64 -0
- package/src/server/audio-dictation.ts +91 -0
- package/src/server/audio-live.ts +185 -0
- package/src/server/audio-transcriptions.ts +183 -0
- package/src/server/audio-upstream.ts +153 -0
- package/src/server/auth-cors.ts +61 -2
- package/src/server/chat-completions.ts +1 -1
- package/src/server/chat-native-sse.ts +92 -48
- package/src/server/chat-native.ts +37 -15
- package/src/server/hub-usage.ts +57 -0
- package/src/server/images.ts +4 -0
- package/src/server/index.ts +722 -57
- package/src/server/lifecycle.ts +5 -6
- package/src/server/live-call-bindings.ts +60 -0
- package/src/server/live.ts +12 -1
- package/src/server/management/agent-settings-routes.ts +25 -4
- package/src/server/management/api-access.ts +37 -0
- package/src/server/management/api-key-usage.ts +7 -2
- package/src/server/management/config-routes.ts +1 -18
- package/src/server/management/context.ts +15 -0
- package/src/server/management/logs-usage-routes.ts +2 -0
- package/src/server/management/oauth-account-routes.ts +39 -12
- package/src/server/management/provider-routes.ts +125 -2
- package/src/server/management/remote-workspace-routes.ts +140 -0
- package/src/server/management/route-registry.ts +15 -0
- package/src/server/management/usage-aggregate-cache.ts +14 -15
- package/src/server/management/usage-summary-cache.ts +2 -0
- package/src/server/management-api.ts +23 -0
- package/src/server/ports.ts +17 -0
- package/src/server/relay-eager.ts +4 -1
- package/src/server/relay.ts +70 -10
- package/src/server/request-decompress.ts +6 -3
- package/src/server/responses/agent-task-recovery.ts +25 -32
- package/src/server/responses/codex-auth-error.ts +11 -0
- package/src/server/responses/codex-ws-exchange.ts +52 -3
- package/src/server/responses/codex-ws-wire.ts +55 -0
- package/src/server/responses/compact.ts +9 -1
- package/src/server/responses/core.ts +337 -73
- package/src/server/responses/encrypted-payload.ts +45 -2
- package/src/server/responses/ws-upstream.ts +4 -1
- package/src/server/responses-self-named-namespace-scrub.ts +1 -3
- package/src/server/responses-undeclared-tool-guard.ts +1 -1
- package/src/server/search.ts +3 -0
- package/src/server/sse-payload-rewrite.ts +136 -51
- package/src/server/ws-bridge.ts +35 -1
- package/src/service/cli.ts +372 -0
- package/src/service/diagnostics.ts +340 -0
- package/src/service/guards.ts +303 -0
- package/src/service/health.ts +222 -0
- package/src/service/launchd.ts +853 -0
- package/src/service/orchestration.ts +617 -0
- package/src/service/repair.ts +334 -0
- package/src/service/state.ts +363 -0
- package/src/service/systemd.ts +229 -0
- package/src/service/windows-ops.ts +690 -0
- package/src/service/windows-scheduler.ts +769 -0
- package/src/service/windows-taskxml.ts +613 -0
- package/src/service.ts +22 -5550
- package/src/storage/cleanup/db.ts +258 -0
- package/src/storage/cleanup/execute.ts +358 -0
- package/src/storage/cleanup/paths.ts +189 -0
- package/src/storage/cleanup/pending.ts +140 -0
- package/src/storage/cleanup/preview.ts +292 -0
- package/src/storage/cleanup/reconcile.ts +347 -0
- package/src/storage/cleanup/restore.ts +932 -0
- package/src/storage/cleanup/satellite.ts +474 -0
- package/src/storage/cleanup/staging.ts +129 -0
- package/src/storage/cleanup/types.ts +98 -0
- package/src/storage/cleanup.ts +49 -3127
- package/src/types/accounts.ts +2 -0
- package/src/types/config.ts +13 -12
- package/src/types/provider.ts +37 -0
- package/src/types/request.ts +2 -0
- package/src/types/tools.ts +17 -5
- package/src/types.ts +1 -0
- package/src/usage/expected-prices.ts +127 -0
- package/src/usage/log.ts +58 -1
- package/src/vision/eligibility.ts +13 -2
- package/src/web-search/loop.ts +56 -3
- package/gui/dist/assets/index-D_t6sCWs.js +0 -115
- package/gui/dist/assets/index-EdoPnm9_.css +0 -1
- package/src/adapters/devin-cli/acp.ts +0 -204
- package/src/adapters/devin-cli/adapter.ts +0 -345
- package/src/adapters/devin-cli/binary.ts +0 -69
- package/src/adapters/devin-cli/models.ts +0 -57
- package/src/oauth/devin-cli.ts +0 -149
- package/src/server/responses-reasoning-summary-rewrite.ts +0 -178
package/src/chat/outbound.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
type Rec = Record<string, unknown>;
|
|
9
9
|
|
|
10
|
-
import { decodeServerSentEvents
|
|
10
|
+
import { decodeServerSentEvents } from "../lib/sse-decoder";
|
|
11
11
|
import {
|
|
12
12
|
isTranslatorBudgetExceededError,
|
|
13
13
|
type TranslatorBudget,
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
isCyberPolicyMessage,
|
|
22
22
|
} from "../lib/errors";
|
|
23
23
|
import { redactSecretString } from "../lib/redact";
|
|
24
|
+
import { createSseBlockBuffer, sseDataPayload } from "../server/sse-payload-rewrite";
|
|
24
25
|
|
|
25
26
|
function isRec(v: unknown): v is Rec {
|
|
26
27
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
@@ -182,6 +183,10 @@ export function responsesSseToChatCompletionsSse(
|
|
|
182
183
|
let cancelled = false;
|
|
183
184
|
let started = false;
|
|
184
185
|
let sawToolUse = false;
|
|
186
|
+
// The upstream response-level service-tier echo (xAI Priority Processing, OpenAI
|
|
187
|
+
// fast tier), captured from any response event and stamped on every emitted chunk,
|
|
188
|
+
// matching how the source annotates its own streamed chat chunks.
|
|
189
|
+
let serviceTier: string | undefined;
|
|
185
190
|
const id = completionId();
|
|
186
191
|
const created = Math.floor(Date.now() / 1000);
|
|
187
192
|
// tool call_id -> streaming index (OpenAI requires stable indices per tool call)
|
|
@@ -298,9 +303,18 @@ export function responsesSseToChatCompletionsSse(
|
|
|
298
303
|
if (existing && existing.parts.size > 0 && item.type !== undefined) throw refusalTranslationError();
|
|
299
304
|
return;
|
|
300
305
|
}
|
|
301
|
-
//
|
|
302
|
-
|
|
303
|
-
|
|
306
|
+
// Only an item that actually carries refusal content belongs in the refusal ledger.
|
|
307
|
+
// The exit used to be the position guard alone, so every message that had an index
|
|
308
|
+
// was enrolled — ordinary assistant text included — and the id/index consistency
|
|
309
|
+
// checks inside refusalItem() then governed streams containing no refusal at all.
|
|
310
|
+
// One index legitimately carrying two item ids was then fatal: a bridge that flushes
|
|
311
|
+
// a hidden reasoning envelope at the index of a still-open message failed a healthy
|
|
312
|
+
// turn with invalid_refusal. `existing` keeps a ledger already opened by real refusal
|
|
313
|
+
// evidence under the same scrutiny as before. The non-streaming collector below has
|
|
314
|
+
// always scoped itself this way; this is the streaming path catching up.
|
|
315
|
+
const hasRefusalPart = Array.isArray(item.content)
|
|
316
|
+
&& item.content.some(part => isRec(part) && part.type === "refusal");
|
|
317
|
+
if (!existing && !hasRefusalPart) return;
|
|
304
318
|
const known = refusalItem(outputIndex, item, "id");
|
|
305
319
|
if (!Array.isArray(item.content)) return;
|
|
306
320
|
item.content.forEach((part: unknown, contentIndex: number) => {
|
|
@@ -366,6 +380,9 @@ export function responsesSseToChatCompletionsSse(
|
|
|
366
380
|
};
|
|
367
381
|
const emit = (payload: Rec | "[DONE]") => {
|
|
368
382
|
if (failed) return;
|
|
383
|
+
if (serviceTier !== undefined && isRec(payload) && Array.isArray(payload.choices)) {
|
|
384
|
+
payload.service_tier = serviceTier;
|
|
385
|
+
}
|
|
369
386
|
if (terminalBatch) {
|
|
370
387
|
const serialized = dataFrame(payload);
|
|
371
388
|
const stringReservation = translatorBudget.reserveTransient(Buffer.byteLength(serialized), { kind: "live_transient" });
|
|
@@ -530,6 +547,9 @@ export function responsesSseToChatCompletionsSse(
|
|
|
530
547
|
};
|
|
531
548
|
|
|
532
549
|
const handleFrame = (eventName: string, data: Rec) => {
|
|
550
|
+
if (isRec(data.response) && typeof data.response.service_tier === "string") {
|
|
551
|
+
serviceTier = data.response.service_tier;
|
|
552
|
+
}
|
|
533
553
|
switch (eventName) {
|
|
534
554
|
case "response.created":
|
|
535
555
|
case "response.heartbeat":
|
|
@@ -565,7 +585,15 @@ export function responsesSseToChatCompletionsSse(
|
|
|
565
585
|
const item = isRec(data.item) ? data.item : null;
|
|
566
586
|
if (item?.type === "message") {
|
|
567
587
|
snapshotRefusalItem(data.output_index, item);
|
|
568
|
-
|
|
588
|
+
// Bind the event-level id only for an index the refusal ledger already
|
|
589
|
+
// tracks. Testing membership directly rather than through position()
|
|
590
|
+
// matters: position() throws on a malformed index, which would add a
|
|
591
|
+
// failure to refusal-free streams instead of removing one.
|
|
592
|
+
if (Object.hasOwn(data, "item_id")
|
|
593
|
+
&& typeof data.output_index === "number"
|
|
594
|
+
&& refusalItems.has(data.output_index)) {
|
|
595
|
+
refusalItem(data.output_index, data, "item_id");
|
|
596
|
+
}
|
|
569
597
|
}
|
|
570
598
|
if (!item || item.type !== "function_call") break;
|
|
571
599
|
ensureRole();
|
|
@@ -611,7 +639,11 @@ export function responsesSseToChatCompletionsSse(
|
|
|
611
639
|
const item = isRec(data.item) ? data.item : null;
|
|
612
640
|
if (!item) break;
|
|
613
641
|
snapshotRefusalItem(data.output_index, item);
|
|
614
|
-
if (item.type === "message" && Object.hasOwn(data, "item_id")
|
|
642
|
+
if (item.type === "message" && Object.hasOwn(data, "item_id")
|
|
643
|
+
&& typeof data.output_index === "number"
|
|
644
|
+
&& refusalItems.has(data.output_index)) {
|
|
645
|
+
refusalItem(data.output_index, data, "item_id");
|
|
646
|
+
}
|
|
615
647
|
if (item.type === "function_call") {
|
|
616
648
|
sawToolUse = true;
|
|
617
649
|
const callId = typeof item.call_id === "string" ? item.call_id : "";
|
|
@@ -871,6 +903,10 @@ export function responsesJsonToChatCompletion(json: unknown, model: string, tran
|
|
|
871
903
|
logprobs: null,
|
|
872
904
|
}],
|
|
873
905
|
usage: chatCompletionsUsage(body.usage),
|
|
906
|
+
// Relay the upstream service-tier echo (xAI Priority Processing, OpenAI fast tier)
|
|
907
|
+
// so a Chat Completions caller can confirm the tier the turn actually used, the
|
|
908
|
+
// same field the Responses lane already relays for responses-wire upstreams.
|
|
909
|
+
...(typeof body.service_tier === "string" ? { service_tier: body.service_tier } : {}),
|
|
874
910
|
};
|
|
875
911
|
}
|
|
876
912
|
|
|
@@ -880,129 +916,125 @@ export async function collectChatCompletion(
|
|
|
880
916
|
model: string,
|
|
881
917
|
translatorBudget: TranslatorBudget,
|
|
882
918
|
): Promise<Rec> {
|
|
919
|
+
const reader = stream.getReader();
|
|
883
920
|
const decoder = new TextDecoder();
|
|
884
|
-
|
|
921
|
+
const buffer = createSseBlockBuffer(translatorBudget);
|
|
885
922
|
let content = "";
|
|
886
923
|
let refusal: string | null = null;
|
|
887
924
|
let reasoning = "";
|
|
925
|
+
const retainedBytes = { content: 0, refusal: 0, reasoning: 0 };
|
|
888
926
|
const toolCalls = new Map<number, { id: string; name: string; arguments: string; argumentBytes: number }>();
|
|
889
927
|
// Per-call budget scopes (2 MiB/call enforced by the budget): the map key is the
|
|
890
928
|
// wire index, which is stable across deltas and present before the call id.
|
|
891
929
|
const callScope = (index: number) => `chat_collect_${index}`;
|
|
892
930
|
let finishReason = "stop";
|
|
893
931
|
let usage: unknown;
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
932
|
+
let serviceTier: unknown;
|
|
933
|
+
const appendRetained = (key: keyof typeof retainedBytes, previous: string, fragment: string): string => {
|
|
934
|
+
const previousBytes = retainedBytes[key];
|
|
935
|
+
const nextBytes = appendedUtf8Bytes(previous, previousBytes, fragment);
|
|
936
|
+
const reservation = translatorBudget.reserveTransient(nextBytes, { kind: "retained_collectors" });
|
|
937
|
+
try {
|
|
938
|
+
const next = previous + fragment;
|
|
939
|
+
reservation.commitRetained();
|
|
940
|
+
translatorBudget.releaseRetained(previousBytes, { kind: "retained_collectors" });
|
|
941
|
+
retainedBytes[key] = nextBytes;
|
|
942
|
+
return next;
|
|
943
|
+
} catch (error) {
|
|
944
|
+
reservation.release();
|
|
945
|
+
throw error;
|
|
946
|
+
}
|
|
947
|
+
};
|
|
948
|
+
const releaseCollectors = () => {
|
|
949
|
+
translatorBudget.releaseRetained(retainedBytes.content + retainedBytes.refusal + retainedBytes.reasoning,
|
|
950
|
+
{ kind: "retained_collectors" });
|
|
899
951
|
};
|
|
900
|
-
const reader = stream.getReader();
|
|
901
952
|
try {
|
|
953
|
+
// Share native relay framing, while retaining the collector's existing admission
|
|
954
|
+
// order: release each consumed input frame before accumulating its output fields.
|
|
955
|
+
// CRLF and multiline data obey the same contract as the streaming response.
|
|
902
956
|
for (;;) {
|
|
903
|
-
|
|
904
|
-
let value: Uint8Array | undefined;
|
|
905
|
-
try {
|
|
906
|
-
({ done, value } = await reader.read());
|
|
907
|
-
} catch (err) {
|
|
908
|
-
if (isChatCompletionsStreamError(err)) throw err;
|
|
909
|
-
if (isTranslatorBudgetExceededError(err)) {
|
|
910
|
-
// Provider-controlled overflow is an upstream failure, not a client
|
|
911
|
-
// request error: match the adapter/bridge contract (502 upstream_error).
|
|
912
|
-
throw new ChatCompletionsStreamError(err.message, {
|
|
913
|
-
status: 502,
|
|
914
|
-
type: "upstream_error",
|
|
915
|
-
code: err.code,
|
|
916
|
-
});
|
|
917
|
-
}
|
|
918
|
-
throw new ChatCompletionsStreamError(err instanceof Error ? err.message : String(err));
|
|
919
|
-
}
|
|
957
|
+
const { done, value } = await reader.read();
|
|
920
958
|
if (done) break;
|
|
921
959
|
if (!value) continue;
|
|
922
|
-
buffer
|
|
923
|
-
let
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
const
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
if (typeof
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
const
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
translatorBudget.releaseRetained(current.argumentBytes, { kind: "tool_args", callId: callScope(index) });
|
|
990
|
-
current.argumentBytes = nextBytes;
|
|
991
|
-
} catch (error) {
|
|
992
|
-
reservation.release();
|
|
993
|
-
throw error;
|
|
994
|
-
}
|
|
960
|
+
buffer.append(decoder.decode(value, { stream: true }));
|
|
961
|
+
for (let frame = buffer.next(); frame; frame = buffer.next()) {
|
|
962
|
+
const data = sseDataPayload(frame.block)?.trim();
|
|
963
|
+
if (!data || data === "[DONE]") continue;
|
|
964
|
+
let parsed: unknown;
|
|
965
|
+
try { parsed = JSON.parse(data); } catch { continue; }
|
|
966
|
+
if (!isRec(parsed)) continue;
|
|
967
|
+
if (isRec(parsed.error)) {
|
|
968
|
+
const message = typeof parsed.error.message === "string"
|
|
969
|
+
? parsed.error.message
|
|
970
|
+
: "upstream request failed";
|
|
971
|
+
const type = typeof parsed.error.type === "string" ? parsed.error.type : "server_error";
|
|
972
|
+
const code = typeof parsed.error.code === "string" ? parsed.error.code : null;
|
|
973
|
+
const status = code === "translation_buffer_limit"
|
|
974
|
+
? 502
|
|
975
|
+
: code === CYBER_POLICY_ERROR_CODE || isCyberPolicyMessage(message)
|
|
976
|
+
? 400
|
|
977
|
+
: streamErrorStatus(message);
|
|
978
|
+
const streamError = new ChatCompletionsStreamError(message, {
|
|
979
|
+
status,
|
|
980
|
+
type: code === "translation_buffer_limit" ? "upstream_error" : type,
|
|
981
|
+
code,
|
|
982
|
+
});
|
|
983
|
+
throw streamError;
|
|
984
|
+
}
|
|
985
|
+
if (parsed.usage) usage = parsed.usage;
|
|
986
|
+
if (typeof parsed.service_tier === "string") serviceTier = parsed.service_tier;
|
|
987
|
+
const choices = Array.isArray(parsed.choices) ? parsed.choices : [];
|
|
988
|
+
const choice = isRec(choices[0]) ? choices[0] : null;
|
|
989
|
+
if (!choice) continue;
|
|
990
|
+
if (typeof choice.finish_reason === "string") finishReason = choice.finish_reason;
|
|
991
|
+
const delta = isRec(choice.delta) ? choice.delta : null;
|
|
992
|
+
if (!delta) continue;
|
|
993
|
+
if (typeof delta.content === "string") content = appendRetained("content", content, delta.content);
|
|
994
|
+
if (delta.refusal !== undefined && delta.refusal !== null) {
|
|
995
|
+
if (typeof delta.refusal !== "string") throw refusalTranslationError();
|
|
996
|
+
refusal = appendRetained("refusal", refusal ?? "", delta.refusal);
|
|
997
|
+
}
|
|
998
|
+
if (typeof delta.reasoning_content === "string") reasoning = appendRetained("reasoning", reasoning, delta.reasoning_content);
|
|
999
|
+
if (Array.isArray(delta.tool_calls)) {
|
|
1000
|
+
for (const tc of delta.tool_calls) {
|
|
1001
|
+
if (!isRec(tc)) continue;
|
|
1002
|
+
const index = typeof tc.index === "number" ? tc.index : 0;
|
|
1003
|
+
let current = toolCalls.get(index);
|
|
1004
|
+
if (!current) {
|
|
1005
|
+
current = { id: "", name: "", arguments: "", argumentBytes: 0 };
|
|
1006
|
+
toolCalls.set(index, current);
|
|
1007
|
+
translatorBudget.openCall(callScope(index));
|
|
1008
|
+
}
|
|
1009
|
+
if (typeof tc.id === "string") current.id = tc.id;
|
|
1010
|
+
const fn = isRec(tc.function) ? tc.function : {};
|
|
1011
|
+
// Done-frame final arguments are authoritative last-write-wins snapshots.
|
|
1012
|
+
if (typeof fn.name === "string" && fn.name.length > 0) current.name = fn.name;
|
|
1013
|
+
if (typeof fn.arguments === "string") {
|
|
1014
|
+
const replace = fn.arguments.startsWith("{") || fn.arguments.startsWith("[") || current.arguments.length === 0;
|
|
1015
|
+
const nextBytes = replace
|
|
1016
|
+
? Buffer.byteLength(fn.arguments)
|
|
1017
|
+
: appendedUtf8Bytes(current.arguments, current.argumentBytes, fn.arguments);
|
|
1018
|
+
const reservation = translatorBudget.reserveTransient(nextBytes, { kind: "tool_args", callId: callScope(index) });
|
|
1019
|
+
try {
|
|
1020
|
+
current.arguments = replace ? fn.arguments : current.arguments + fn.arguments;
|
|
1021
|
+
reservation.commitRetained();
|
|
1022
|
+
translatorBudget.releaseRetained(current.argumentBytes, { kind: "tool_args", callId: callScope(index) });
|
|
1023
|
+
current.argumentBytes = nextBytes;
|
|
1024
|
+
} catch (error) {
|
|
1025
|
+
reservation.release();
|
|
1026
|
+
throw error;
|
|
995
1027
|
}
|
|
996
1028
|
}
|
|
997
1029
|
}
|
|
998
1030
|
}
|
|
999
1031
|
}
|
|
1032
|
+
buffer.compact();
|
|
1000
1033
|
}
|
|
1001
1034
|
} catch (error) {
|
|
1002
|
-
//
|
|
1003
|
-
// upstream translator releases its maps and stops any pending provider read.
|
|
1035
|
+
// Cancel while we still own the reader so a failed collection releases its upstream.
|
|
1004
1036
|
try { await reader.cancel(error); } catch { /* preserve the original failure */ }
|
|
1005
|
-
|
|
1037
|
+
releaseCollectors();
|
|
1006
1038
|
// Never leak an open call scope on the error path; the turn budget's
|
|
1007
1039
|
// dispose is a backstop, not the owner of this transfer.
|
|
1008
1040
|
for (const index of toolCalls.keys()) translatorBudget.closeCall(callScope(index));
|
|
@@ -1015,8 +1047,12 @@ export async function collectChatCompletion(
|
|
|
1015
1047
|
code: error.code,
|
|
1016
1048
|
});
|
|
1017
1049
|
}
|
|
1018
|
-
throw error;
|
|
1050
|
+
if (isChatCompletionsStreamError(error)) throw error;
|
|
1051
|
+
throw new ChatCompletionsStreamError(error instanceof Error ? error.message : String(error));
|
|
1019
1052
|
} finally {
|
|
1053
|
+
// Preserve the previous EOF contract: only delimiter-terminated events are collected.
|
|
1054
|
+
// A partial final frame is discarded, with its retained input ownership released.
|
|
1055
|
+
buffer.clear();
|
|
1020
1056
|
reader.releaseLock();
|
|
1021
1057
|
}
|
|
1022
1058
|
|
|
@@ -1048,6 +1084,7 @@ export async function collectChatCompletion(
|
|
|
1048
1084
|
return copy;
|
|
1049
1085
|
});
|
|
1050
1086
|
} catch (error) {
|
|
1087
|
+
releaseCollectors();
|
|
1051
1088
|
for (const copyBytes of chargedCopies) {
|
|
1052
1089
|
translatorBudget.releaseRetained(copyBytes, { kind: "retained_collectors" });
|
|
1053
1090
|
}
|
|
@@ -1076,5 +1113,6 @@ export async function collectChatCompletion(
|
|
|
1076
1113
|
logprobs: null,
|
|
1077
1114
|
}],
|
|
1078
1115
|
usage: usage && isRec(usage) ? usage : chatCompletionsUsage(undefined),
|
|
1116
|
+
...(typeof serviceTier === "string" ? { service_tier: serviceTier } : {}),
|
|
1079
1117
|
};
|
|
1080
1118
|
}
|
|
@@ -103,12 +103,10 @@ export function parseDesktopProfile(value: unknown): DesktopProfile {
|
|
|
103
103
|
if (!isPlainObject(value.assignments)) throw new DesktopProfileError("must be an object", "profile.assignments");
|
|
104
104
|
if (!isPlainObject(value.defaults)) throw new DesktopProfileError("must be an object", "profile.defaults");
|
|
105
105
|
assertExactKeys(value.defaults, DESKTOP_FAMILIES, "profile.defaults");
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
throw new DesktopProfileError("must be a string", "profile.appliedAt");
|
|
111
|
-
}
|
|
106
|
+
// JSON null (and any other non-string) is unset, not fatal. Older builds and several
|
|
107
|
+
// writers persisted appliedFingerprint/appliedAt as JSON null; treating those as a
|
|
108
|
+
// document-level parse failure made loadConfig replace the whole operator config with
|
|
109
|
+
// defaults (#4430). appliedMarkers() already drops non-strings when copying.
|
|
112
110
|
|
|
113
111
|
const assignments: Record<string, OcxClaudeDesktopAssignment> = {};
|
|
114
112
|
const aliases = new Set<string>();
|
package/src/cli/account-api.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseQuotaFailureCode, type QuotaFailureCode } from "../providers/quota-types";
|
|
1
2
|
/**
|
|
2
3
|
* Data-access layer for `ocx account` (issue #180) — live-proxy HTTP client and
|
|
3
4
|
* per-family account readers. Kept separate from account.ts (command handlers)
|
|
@@ -26,12 +27,16 @@ export interface AccountRow {
|
|
|
26
27
|
masked?: string;
|
|
27
28
|
active: boolean;
|
|
28
29
|
needsReauth?: boolean;
|
|
30
|
+
selectionExcludedReason?: "plan_excluded";
|
|
31
|
+
selectionExcludedPlan?: string;
|
|
29
32
|
/** Registered credential that is still excluded from routing until validation completes. */
|
|
30
33
|
validationPending?: boolean;
|
|
31
34
|
/** Codex pool selection order, higher used earlier. Absent where ordering does not apply. */
|
|
32
35
|
priority?: number;
|
|
33
36
|
quota?: CodexQuotaDto | null;
|
|
34
37
|
quotaRefresh?: CodexQuotaRefreshOutcome;
|
|
38
|
+
quotaUnavailable?: boolean;
|
|
39
|
+
quotaFailure?: QuotaFailureCode;
|
|
35
40
|
/**
|
|
36
41
|
* Whether the pool is holding this account out of rotation.
|
|
37
42
|
*
|
|
@@ -243,6 +248,8 @@ interface CodexAccountDto {
|
|
|
243
248
|
plan?: string;
|
|
244
249
|
isMain?: boolean;
|
|
245
250
|
needsReauth?: boolean;
|
|
251
|
+
selectionExcludedReason?: "plan_excluded";
|
|
252
|
+
selectionExcludedPlan?: string;
|
|
246
253
|
health?: { reason?: string };
|
|
247
254
|
priority?: number;
|
|
248
255
|
quota?: CodexQuotaDto | null;
|
|
@@ -309,6 +316,10 @@ export async function fetchCodexRows(
|
|
|
309
316
|
plan: a.plan,
|
|
310
317
|
active: a.id === activeId,
|
|
311
318
|
needsReauth: a.needsReauth,
|
|
319
|
+
...(a.selectionExcludedReason === "plan_excluded" ? {
|
|
320
|
+
selectionExcludedReason: "plan_excluded" as const,
|
|
321
|
+
...(typeof a.selectionExcludedPlan === "string" ? { selectionExcludedPlan: a.selectionExcludedPlan } : {}),
|
|
322
|
+
} : {}),
|
|
312
323
|
...(a.health?.reason === "validation_pending" ? { validationPending: true } : {}),
|
|
313
324
|
priority: typeof a.priority === "number" ? a.priority : 0,
|
|
314
325
|
paused: a.paused === true,
|
|
@@ -330,6 +341,7 @@ interface OAuthAccountDto {
|
|
|
330
341
|
plan?: string | null;
|
|
331
342
|
quota?: CodexQuotaDto | null;
|
|
332
343
|
quotaUnavailable?: boolean;
|
|
344
|
+
quotaFailure?: unknown;
|
|
333
345
|
}
|
|
334
346
|
|
|
335
347
|
async function fetchOAuthRows(
|
|
@@ -363,6 +375,8 @@ async function fetchOAuthRows(
|
|
|
363
375
|
plan: a.plan ?? null,
|
|
364
376
|
...(a.quota !== undefined ? { quota: a.quota } : {}),
|
|
365
377
|
...(a.quotaUnavailable !== undefined ? { quotaUnavailable: a.quotaUnavailable } : {}),
|
|
378
|
+
...(a.quotaUnavailable === true && parseQuotaFailureCode(a.quotaFailure)
|
|
379
|
+
? { quotaFailure: parseQuotaFailureCode(a.quotaFailure) } : {}),
|
|
366
380
|
}));
|
|
367
381
|
return { rows, activeId, status: 200 };
|
|
368
382
|
}
|
|
@@ -44,7 +44,7 @@ const EXTENDED_USAGE = `Usage:
|
|
|
44
44
|
ocx account pause <provider> <id|main> [--json]
|
|
45
45
|
ocx account resume <provider> <id|main> [--json]
|
|
46
46
|
ocx account pause-exhausted <provider> [--json]
|
|
47
|
-
ocx account strategy <provider> [<quota|round-robin|fill-first>] [--json]
|
|
47
|
+
ocx account strategy <provider> [<quota|round-robin|fill-first|reset-first>] [--json]
|
|
48
48
|
ocx account sticky <provider> [<1-100>] [--json]
|
|
49
49
|
ocx account remove <provider> <id|main> --yes [--json]
|
|
50
50
|
ocx account clear-cooldown <provider> <id|main> [--json]
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { parseCapacityReason } from "../codex/quota-capacity";
|
|
2
|
+
import { isValidCodexAccountId } from "../codex/account-id";
|
|
3
|
+
import { apiError, apiJson, proxyUnreachable, resolveBaseUrl, type AccountDeps } from "./account-api";
|
|
4
|
+
|
|
5
|
+
function historyDate(value: unknown): string {
|
|
6
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return "unknown";
|
|
7
|
+
const date = new Date(value);
|
|
8
|
+
return Number.isFinite(date.getTime()) ? date.toISOString() : "unknown";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Read cached pool observations without refreshing credentials or spending quota. */
|
|
12
|
+
export async function cmdAccountHistory(args: string[], deps: AccountDeps): Promise<number> {
|
|
13
|
+
const [provider, accountId, ...flags] = args;
|
|
14
|
+
let json = false;
|
|
15
|
+
let limit = 200;
|
|
16
|
+
let hasLimit = false;
|
|
17
|
+
let valid = provider === "openai" && isValidCodexAccountId(accountId);
|
|
18
|
+
for (let index = 0; index < flags.length; index++) {
|
|
19
|
+
if (flags[index] === "--json" && !json) json = true;
|
|
20
|
+
else if (flags[index] === "--limit" && !hasLimit && /^(?:[1-9]|[1-9][0-9]|1[0-9]{2}|200)$/.test(flags[index + 1] ?? "")) {
|
|
21
|
+
limit = Number(flags[++index]); hasLimit = true;
|
|
22
|
+
} else valid = false;
|
|
23
|
+
}
|
|
24
|
+
if (!valid) {
|
|
25
|
+
console.error("Usage: ocx account history openai <pool-account-id> [--limit <1-200>] [--json]");
|
|
26
|
+
return 1;
|
|
27
|
+
}
|
|
28
|
+
const baseUrl = await resolveBaseUrl(deps);
|
|
29
|
+
if (!baseUrl) return proxyUnreachable();
|
|
30
|
+
const result = await apiJson(deps, baseUrl, "GET", `/api/codex-auth/quota/history?accountId=${encodeURIComponent(accountId)}&limit=${limit}`);
|
|
31
|
+
if (result.status === 0) return proxyUnreachable(result.transportError);
|
|
32
|
+
if (result.status !== 200) return apiError(result.json, "Quota history unavailable", result.status);
|
|
33
|
+
if (json) { console.log(JSON.stringify(result.json, null, 2)); return 0; }
|
|
34
|
+
const observations = result.json.observations;
|
|
35
|
+
if (!Array.isArray(observations)) return apiError({}, "Invalid quota history response", 502);
|
|
36
|
+
console.log("OBSERVED\tSOURCE\tWINDOW\tUSED\tRESET");
|
|
37
|
+
if (!observations.length) console.log("No quota observations for this credential publication.");
|
|
38
|
+
for (const observation of observations) {
|
|
39
|
+
if (!observation || typeof observation !== "object" || !Array.isArray(observation.windows)
|
|
40
|
+
|| !Number.isFinite(observation.observedAt)) return apiError({}, "Invalid quota history response", 502);
|
|
41
|
+
for (const window of observation.windows) {
|
|
42
|
+
console.log(`${historyDate(observation.observedAt)}\t${observation.source}\t${window.family}/${window.window}\t${window.usedPercent}%\t${historyDate(window.resetAtMs)}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const capacity = result.json.capacity;
|
|
46
|
+
if (capacity && typeof capacity === "object" && "status" in capacity && capacity.status === "estimated"
|
|
47
|
+
&& "estimates" in capacity && Array.isArray(capacity.estimates)) {
|
|
48
|
+
console.log("Effective capacity estimate (low confidence; not a provider token limit):");
|
|
49
|
+
for (const estimate of capacity.estimates) {
|
|
50
|
+
if (estimate && Number.isFinite(estimate.estimatedTokens) && Number.isSafeInteger(estimate.sampleCount)) {
|
|
51
|
+
console.log(`${estimate.window}\t~${estimate.estimatedTokens} reported tokens / 100%\t${estimate.sampleCount} samples`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (capacity && typeof capacity === "object" && "status" in capacity && capacity.status === "insufficient-evidence") {
|
|
56
|
+
const reason = "reason" in capacity ? parseCapacityReason(capacity.reason) : undefined;
|
|
57
|
+
console.log(`Effective capacity: insufficient evidence${reason ? ` (${reason})` : ""}.`);
|
|
58
|
+
}
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
package/src/cli/account-main.ts
CHANGED
|
@@ -17,6 +17,9 @@ const USAGE = `Usage:
|
|
|
17
17
|
ocx account main list [--json]
|
|
18
18
|
ocx account main register <label> [--json]
|
|
19
19
|
ocx account main add <label>
|
|
20
|
+
ocx account main reauth --device [--no-wait] [--json]
|
|
21
|
+
ocx account main reauth status --flow <id> [--json]
|
|
22
|
+
ocx account main reauth cancel --flow <id> [--json]
|
|
20
23
|
ocx account main switch <profile-id-or-label> --yes [--json]
|
|
21
24
|
ocx account main recover [--rollback --yes] [--json]
|
|
22
25
|
|
|
@@ -186,6 +189,83 @@ export async function cmdNativeMainAccount(args: string[], deps: AccountDeps): P
|
|
|
186
189
|
const baseUrl = await resolveBaseUrl(deps);
|
|
187
190
|
if (!baseUrl) return proxyUnreachable();
|
|
188
191
|
|
|
192
|
+
if (sub === "reauth") {
|
|
193
|
+
// #3898: same-identity device reauth of the native __main__ slot via the
|
|
194
|
+
// dedicated management namespace; /api/codex-auth/login stays pool-only.
|
|
195
|
+
if (confirmed || rollback) return reject(args);
|
|
196
|
+
const noWait = flag(args, "--no-wait");
|
|
197
|
+
const device = flag(args, "--device");
|
|
198
|
+
const flowIndex = args.indexOf("--flow");
|
|
199
|
+
const flowId = flowIndex >= 0 ? args.splice(flowIndex, 2)[1] : undefined;
|
|
200
|
+
const action = args.shift();
|
|
201
|
+
if (args.length > 0) return reject(args);
|
|
202
|
+
|
|
203
|
+
const printStatus = (status: Record<string, unknown>): void => {
|
|
204
|
+
if (wantsJson) { console.log(JSON.stringify(status, null, 2)); return; }
|
|
205
|
+
console.log("status: " + String(status.status ?? "unknown"));
|
|
206
|
+
if (typeof status.verificationUrl === "string" && status.verificationUrl) {
|
|
207
|
+
console.log("url: " + status.verificationUrl);
|
|
208
|
+
}
|
|
209
|
+
if (typeof status.deviceCode === "string" && status.deviceCode) {
|
|
210
|
+
console.log("deviceCode: " + status.deviceCode);
|
|
211
|
+
}
|
|
212
|
+
if (typeof status.code === "string" && status.code) {
|
|
213
|
+
console.log("failure: " + status.code);
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
if (action === "status" || action === "cancel") {
|
|
218
|
+
if (!flowId || device || noWait) return reject(args);
|
|
219
|
+
const query = "/api/codex-auth/main/reauth-device?flowId=" + encodeURIComponent(flowId);
|
|
220
|
+
const result = action === "status"
|
|
221
|
+
? await apiJson(deps, baseUrl, "GET", query)
|
|
222
|
+
: await apiJson(deps, baseUrl, "DELETE", query);
|
|
223
|
+
if (result.status === 0) return proxyUnreachable(result.transportError);
|
|
224
|
+
if (result.status !== 200) return apiError(result.json, "failed to " + action + " the native main reauth", result.status);
|
|
225
|
+
printStatus(result.json);
|
|
226
|
+
return 0;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (action !== undefined || !device) return reject(args);
|
|
230
|
+
const started = await apiJson(deps, baseUrl, "POST", "/api/codex-auth/main/reauth-device");
|
|
231
|
+
if (started.status === 0) return proxyUnreachable(started.transportError);
|
|
232
|
+
if (started.status !== 200) return apiError(started.json, "failed to start the native main device reauth", started.status);
|
|
233
|
+
const startFlowId = typeof started.json.flowId === "string" ? started.json.flowId : "";
|
|
234
|
+
if (!startFlowId) {
|
|
235
|
+
console.error("The proxy returned an invalid reauth flow.");
|
|
236
|
+
return 1;
|
|
237
|
+
}
|
|
238
|
+
// The URL and human code arrive with the usercode response, a beat after start.
|
|
239
|
+
let pending = started.json;
|
|
240
|
+
const statusQuery = "/api/codex-auth/main/reauth-device?flowId=" + encodeURIComponent(startFlowId);
|
|
241
|
+
for (let attempt = 0; attempt < 50 && !pending.deviceCode; attempt += 1) {
|
|
242
|
+
await Bun.sleep(200);
|
|
243
|
+
const polled = await apiJson(deps, baseUrl, "GET", statusQuery);
|
|
244
|
+
if (polled.status === 200) pending = polled.json;
|
|
245
|
+
}
|
|
246
|
+
if (noWait) {
|
|
247
|
+
printStatus({ flowId: startFlowId, ...pending });
|
|
248
|
+
console.log("follow up: ocx account main reauth status --flow " + startFlowId);
|
|
249
|
+
return 0;
|
|
250
|
+
}
|
|
251
|
+
// Blocking wait bounded by the service flow expiry (15-minute grant + margin).
|
|
252
|
+
const deadline = Date.now() + 16 * 60_000;
|
|
253
|
+
for (;;) {
|
|
254
|
+
if (pending.status !== "pending" && pending.status !== "committing") break;
|
|
255
|
+
if (Date.now() >= deadline) {
|
|
256
|
+
console.error("The reauth flow did not finish within the device grant window; check status with --flow " + startFlowId + ".");
|
|
257
|
+
return 1;
|
|
258
|
+
}
|
|
259
|
+
await Bun.sleep(2_000);
|
|
260
|
+
const polled = await apiJson(deps, baseUrl, "GET", statusQuery);
|
|
261
|
+
if (polled.status === 0) return proxyUnreachable(polled.transportError);
|
|
262
|
+
if (polled.status !== 200) return apiError(polled.json, "failed to poll the native main reauth", polled.status);
|
|
263
|
+
pending = polled.json;
|
|
264
|
+
}
|
|
265
|
+
printStatus({ flowId: startFlowId, ...pending });
|
|
266
|
+
return pending.status === "succeeded" ? 0 : 1;
|
|
267
|
+
}
|
|
268
|
+
|
|
189
269
|
if (sub === "doctor" || sub === "list") {
|
|
190
270
|
if (args.length > 0 || confirmed || rollback) return reject(args);
|
|
191
271
|
const path = sub === "doctor" ? "/api/native-main-profiles/doctor" : "/api/native-main-profiles";
|