@bitkyc08/opencodex 2.88.0 → 2.90.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -1
- package/bin/ocx.mjs +15 -4
- package/gui/dist/assets/App-DgxkE6Md.js +58 -0
- package/gui/dist/assets/App-dnkJ85te.css +1 -0
- package/gui/dist/assets/{Tray-B5bC7AWL.js → Tray-DbTNc_88.js} +1 -1
- package/gui/dist/assets/{index-B5bsvxor.js → index-TmaHa54u.js} +15 -15
- package/gui/dist/assets/quota-summary-B16x5DZZ.js +1 -0
- package/gui/dist/index.html +1 -1
- package/package.json +2 -2
- package/src/adapters/anthropic/cache-retention.ts +117 -0
- package/src/adapters/anthropic/passthrough.ts +7 -4
- package/src/adapters/anthropic.ts +10 -3
- package/src/adapters/base.ts +4 -0
- package/src/adapters/claude-cli/adapter.ts +13 -7
- package/src/adapters/claude-cli/usage-admission.ts +14 -2
- package/src/adapters/coding-agent/protocol.ts +6 -0
- package/src/adapters/coding-agent/turn.ts +14 -4
- package/src/adapters/cursor/protobuf-request.ts +3 -0
- package/src/adapters/google-http.ts +3 -2
- package/src/adapters/google-progress-history.ts +19 -1
- package/src/adapters/google-vertex-endpoint.ts +67 -0
- package/src/adapters/google.ts +62 -37
- package/src/adapters/image.ts +5 -3
- package/src/adapters/input-media-guard.ts +5 -2
- package/src/adapters/kiro-refusal.ts +41 -1
- package/src/adapters/ollama-native.ts +1 -0
- package/src/adapters/openai-chat/messages.ts +34 -4
- package/src/adapters/openai-chat/passthrough.ts +37 -4
- package/src/adapters/openai-chat/reasoning-replay.ts +98 -13
- package/src/adapters/openai-chat/serialized-tool-call-content.ts +95 -134
- package/src/adapters/openai-chat/serialized-tool-call-markup.ts +108 -0
- package/src/adapters/openai-chat/standalone-bare-tool-calls.ts +136 -0
- package/src/adapters/openai-chat.ts +7 -6
- package/src/adapters/openai-responses/compaction-search-history.ts +27 -0
- package/src/adapters/openai-responses/opengateway-input.ts +139 -0
- package/src/adapters/openai-responses/passthrough.ts +22 -13
- package/src/adapters/openai-responses/reasoning.ts +68 -1
- package/src/adapters/openai-responses/transport.ts +13 -1
- package/src/adapters/registry.ts +1 -1
- package/src/adapters/upstream-http-error.ts +3 -2
- package/src/adapters/zed.ts +7 -4
- package/src/bridge/internal.ts +11 -1
- package/src/bridge/response-json.ts +2 -2
- package/src/bridge/sse.ts +10 -4
- package/src/claude/desktop-3p-library.ts +43 -3
- package/src/claude/desktop-3p.ts +20 -10
- package/src/claude/desktop-picker-profile.ts +18 -9
- package/src/claude/desktop-remote-store-io.ts +2 -2
- package/src/claude/inbound.ts +12 -26
- package/src/claude/outbound.ts +5 -0
- package/src/cli/account-api.ts +13 -1
- package/src/cli/account-extended.ts +17 -5
- package/src/cli/account-key-quota.ts +4 -1
- package/src/cli/account.ts +1 -1
- package/src/cli/capabilities-agents-routing.ts +45 -8
- package/src/cli/capabilities-base.ts +5 -5
- package/src/cli/capabilities-provider-models.ts +5 -4
- package/src/cli/combo-result.ts +8 -2
- package/src/cli/combo.ts +17 -0
- package/src/cli/ensure-desktop-cold-start.ts +2 -2
- package/src/cli/help.ts +1 -1
- package/src/cli/message-command.ts +4 -1
- package/src/cli/message-remote-args.ts +69 -0
- package/src/cli/message-remote-command.ts +77 -0
- package/src/cli/provider-runtime.ts +69 -1
- package/src/cli/registry.ts +3 -3
- package/src/codex/account-lifecycle.ts +19 -6
- package/src/codex/account-pause.ts +6 -1
- package/src/codex/account-runtime-state.ts +5 -0
- package/src/codex/account-store.ts +55 -9
- package/src/codex/account-usability.ts +4 -1
- package/src/codex/auth-api/account-list.ts +13 -5
- package/src/codex/auth-api/account-pause-group.ts +2 -22
- package/src/codex/auth-api/login-collision.ts +59 -0
- package/src/codex/auth-api/login-flow.ts +12 -7
- package/src/codex/auth-api/main-account-probe.ts +3 -2
- package/src/codex/auth-api/pool-mode-gate.ts +81 -13
- package/src/codex/auth-collision.ts +31 -4
- package/src/codex/auth-context.ts +57 -10
- package/src/codex/catalog/aggregation.ts +15 -0
- package/src/codex/catalog/auto-review.ts +2 -1
- package/src/codex/catalog/build-entries.ts +17 -7
- package/src/codex/catalog/derive-entry.ts +3 -1
- package/src/codex/catalog/metadata.ts +2 -0
- package/src/codex/catalog/model-visibility.ts +0 -2
- package/src/codex/catalog/ownership.ts +71 -0
- package/src/codex/catalog/parsing.ts +12 -3
- package/src/codex/catalog/retained-sync.ts +2 -1
- package/src/codex/catalog/routed-removal.ts +4 -4
- package/src/codex/cli-update-plan.ts +18 -3
- package/src/codex/collision-identity.ts +78 -0
- package/src/codex/convergence.ts +2 -1
- package/src/codex/low-quota-desktop-notifier.ts +125 -0
- package/src/codex/low-quota-protection.ts +57 -31
- package/src/codex/main-account-cache.ts +77 -3
- package/src/codex/main-account-hard-lock.ts +14 -0
- package/src/codex/main-account.ts +6 -2
- package/src/codex/native-residue.ts +2 -3
- package/src/codex/quota.ts +26 -2
- package/src/codex/routing/cooldown-math.ts +5 -0
- package/src/codex/routing/failure-window.ts +61 -9
- package/src/codex/routing.ts +4 -3
- package/src/codex/shim-diagnostics.ts +2 -1
- package/src/codex/shim-fingerprint.ts +11 -2
- package/src/codex/shim-templates.ts +16 -5
- package/src/codex/shim-windows-refresh.ts +224 -0
- package/src/codex/shim.ts +45 -21
- package/src/combos/failover.ts +10 -2
- package/src/combos/jev-model-backend.ts +30 -7
- package/src/combos/jev-model-request.ts +20 -0
- package/src/combos/jev-quota-activation.ts +153 -0
- package/src/combos/jev-quota-config.ts +62 -0
- package/src/combos/jev-quota-route.ts +26 -0
- package/src/combos/jev-quota.ts +88 -0
- package/src/combos/jev.ts +25 -12
- package/src/combos/types.ts +27 -0
- package/src/compatibility/index.ts +3 -0
- package/src/compatibility/openai-responses.ts +8 -1
- package/src/compatibility/opengateway-responses.ts +61 -0
- package/src/config/account-auto-pause-revisions.ts +136 -0
- package/src/config/diagnostics.ts +13 -0
- package/src/config/live-reconcile.ts +9 -0
- package/src/config/persist-unlocked.ts +10 -1
- package/src/config/provider-validation.ts +7 -2
- package/src/config/removed-opencode-free.ts +135 -0
- package/src/config/schema/account-auto-pause.ts +14 -0
- package/src/config/schema/config-schema.ts +15 -0
- package/src/config/schema/leaf-validators.ts +7 -0
- package/src/config.ts +10 -3
- package/src/generated/compatibility-version.json +476 -216
- package/src/generated/model-metadata.ts +1 -1
- package/src/images/loop.ts +34 -11
- package/src/lib/account-auto-pause-response.ts +16 -0
- package/src/lib/bun-runtime.ts +18 -1
- package/src/lib/bun-transpiler-cache-scope.d.mts +8 -0
- package/src/lib/bun-transpiler-cache-scope.mjs +82 -0
- package/src/lib/decision-quota-sink.ts +76 -0
- package/src/lib/provider-admission-wait.ts +14 -0
- package/src/lib/service-secrets.ts +37 -2
- package/src/lib/state-store-registrations.ts +25 -0
- package/src/lib/translator-budget.ts +1 -1
- package/src/lib/upstream-retry.ts +15 -0
- package/src/link/ssh-argv.ts +3 -0
- package/src/messaging/envelope.ts +6 -3
- package/src/messaging/remote-auth.ts +83 -0
- package/src/messaging/remote-bridge.ts +270 -0
- package/src/messaging/remote-contract.ts +108 -0
- package/src/messaging/remote-enrollment-recovery.ts +59 -0
- package/src/messaging/remote-enrollment.ts +244 -0
- package/src/messaging/remote-envelope.ts +40 -0
- package/src/messaging/remote-files.ts +117 -0
- package/src/messaging/remote-owner.ts +169 -0
- package/src/messaging/remote-ports.ts +56 -0
- package/src/messaging/remote-process.ts +131 -0
- package/src/messaging/remote-replies.ts +98 -0
- package/src/messaging/remote-rpc-admission.ts +32 -0
- package/src/messaging/remote-send.ts +72 -0
- package/src/messaging/remote-store.ts +111 -0
- package/src/messaging/remote-tunnels.ts +65 -0
- package/src/messaging/rpc.ts +13 -1
- package/src/oauth/account-auto-pause-policy.ts +66 -0
- package/src/oauth/account-auto-pause.ts +171 -0
- package/src/oauth/account-import/google-antigravity-adapter.ts +38 -2
- package/src/oauth/account-import/parser.ts +15 -2
- package/src/oauth/account-import/types.ts +7 -0
- package/src/oauth/account-quota-rank.ts +2 -1
- package/src/oauth/anthropic-account-refusal.ts +6 -0
- package/src/oauth/anthropic-model-family.ts +8 -0
- package/src/oauth/anthropic-model-quota.ts +2 -6
- package/src/oauth/api-key-pool-preserve.ts +37 -0
- package/src/oauth/chatgpt-shared.ts +10 -1
- package/src/oauth/dsh.ts +45 -2
- package/src/oauth/index.ts +5 -35
- package/src/oauth/kiro.ts +44 -1
- package/src/oauth/login-flow-state.ts +11 -2
- package/src/oauth/store.ts +35 -8
- package/src/oauth/types.ts +2 -0
- package/src/plugins/upstream-hooks.ts +14 -1
- package/src/providers/api-key-pause.ts +32 -0
- package/src/providers/api-key-resolve.ts +10 -4
- package/src/providers/api-key-selection.ts +6 -1
- package/src/providers/api-keys.ts +8 -4
- package/src/providers/claude-cli-usage.ts +25 -5
- package/src/providers/initial-model-selection.ts +2 -2
- package/src/providers/key-failover.ts +21 -13
- package/src/providers/key-store.ts +8 -3
- package/src/providers/mistral-transport.ts +207 -0
- package/src/providers/model-rename-fields.ts +1 -0
- package/src/providers/openai-sidecar.ts +5 -0
- package/src/providers/opencode-zen-rate-limit.ts +6 -72
- package/src/providers/quota/account-cache.ts +15 -1
- package/src/providers/quota/anthropic-account-quota.ts +20 -7
- package/src/providers/quota/anthropic-cooldown-recovery.ts +22 -4
- package/src/providers/quota/anthropic-decision-windows.ts +47 -0
- package/src/providers/quota/antigravity.ts +45 -8
- package/src/providers/quota/failure-codes.ts +18 -0
- package/src/providers/quota/report-cache.ts +14 -0
- package/src/providers/quota/vendor-probes-oauth.ts +15 -6
- package/src/providers/quota-decision-publication.ts +77 -0
- package/src/providers/quota-decision-snapshot.ts +145 -0
- package/src/providers/quota-routing-cache.ts +4 -1
- package/src/providers/quota-types.ts +6 -1
- package/src/providers/quota.ts +8 -3
- package/src/providers/registry/entries-extended.ts +14 -53
- package/src/providers/registry/model-seeds.ts +1 -1
- package/src/providers/registry.ts +1 -2
- package/src/providers/routed-config.ts +1 -1
- package/src/providers/stale-vision-classification-migration.ts +1 -1
- package/src/providers/xai-transport.ts +2 -1
- package/src/responses/audio.ts +46 -0
- package/src/responses/input-media.ts +37 -15
- package/src/responses/parser-content.ts +22 -27
- package/src/responses/parser.ts +18 -10
- package/src/responses/reasoning-retention.ts +218 -11
- package/src/responses/schema.ts +3 -4
- package/src/responses/web-search-replay.ts +147 -0
- package/src/routing/compatibility/behavior.ts +4 -1
- package/src/server/auth-cors.ts +7 -0
- package/src/server/background-lifecycle.ts +2 -1
- package/src/server/chat-native.ts +36 -20
- package/src/server/claude-messages.ts +2 -1
- package/src/server/index/optional-listeners.ts +6 -0
- package/src/server/index/serve-options.ts +8 -2
- package/src/server/index/websocket-handler.ts +10 -0
- package/src/server/lifecycle.ts +17 -1
- package/src/server/management/combo-routes.ts +17 -2
- package/src/server/management/oauth-account-routes.ts +12 -2
- package/src/server/management/provider-completion-test.ts +289 -0
- package/src/server/management/provider-routes.ts +9 -1
- package/src/server/management/route-registry.ts +2 -0
- package/src/server/messages-native.ts +29 -4
- package/src/server/messages-shared.ts +4 -1
- package/src/server/request-log.ts +23 -9
- package/src/server/responses/account-auto-pause-recovery.ts +26 -0
- package/src/server/responses/adapter-continuation.ts +2 -2
- package/src/server/responses/adapter-dispatch.ts +79 -30
- package/src/server/responses/codex-ws-exchange.ts +3 -0
- package/src/server/responses/codex-ws-metadata.ts +44 -0
- package/src/server/responses/codex-ws-session.ts +21 -1
- package/src/server/responses/combo-session-recall.ts +25 -0
- package/src/server/responses/combo-stream-preflight.ts +10 -3
- package/src/server/responses/combo-tool-loop-pin.ts +70 -0
- package/src/server/responses/compact.ts +10 -5
- package/src/server/responses/context-overflow.ts +141 -2
- package/src/server/responses/core-auth.ts +20 -0
- package/src/server/responses/core-codex-account.ts +28 -15
- package/src/server/responses/core-combo.ts +34 -5
- package/src/server/responses/core-opaque-recovery.ts +20 -11
- package/src/server/responses/core.ts +12 -9
- package/src/server/responses/deepseek-image-files.ts +121 -0
- package/src/server/responses/empty-completion-guard.ts +10 -0
- package/src/server/responses/fetch-helpers.ts +65 -14
- package/src/server/responses/input-admission.ts +6 -2
- package/src/server/responses/jev-model-invoke.ts +13 -14
- package/src/server/responses/main-hard-lock-routing.ts +33 -8
- package/src/server/responses/native-steering-log.ts +2 -2
- package/src/server/responses/opengateway-completion-guard.ts +232 -0
- package/src/server/responses/passthrough-completion.ts +41 -0
- package/src/server/responses/passthrough-delivery.ts +5 -2
- package/src/server/responses/passthrough-dispatch.ts +42 -45
- package/src/server/responses/passthrough-key-rotation.ts +40 -0
- package/src/server/responses/passthrough-observations.ts +38 -0
- package/src/server/responses/reasoning-archive-error.ts +21 -0
- package/src/server/responses/request-compression.ts +98 -0
- package/src/server/responses/request-prepare.ts +11 -1
- package/src/server/responses/request-transport.ts +92 -5
- package/src/server/responses/run-turn-execution.ts +2 -0
- package/src/server/responses/sidecar-execution.ts +16 -3
- package/src/server/responses/terminal-guard.ts +2 -0
- package/src/server/startup-action-control.ts +2 -2
- package/src/server/startup-health-cache.ts +2 -2
- package/src/server/windows-tray-control.ts +2 -2
- package/src/service/definition.ts +9 -1
- package/src/service/orchestration.ts +17 -0
- package/src/types/account-auto-pause.ts +15 -0
- package/src/types/config.ts +23 -1
- package/src/types/provider.ts +11 -5
- package/src/types/request.ts +36 -3
- package/src/types.ts +2 -0
- package/src/usage/cost.ts +17 -1
- package/src/usage/expected-prices.ts +3 -1
- package/src/usage/jev-stats.ts +4 -0
- package/src/usage/log.ts +2 -0
- package/src/usage/summary.ts +7 -0
- package/src/web-search/loop.ts +39 -13
- package/src/web-search/progress-stream.ts +4 -1
- package/src/web-search/run-turn-loop.ts +9 -3
- package/src/web-search/sidecar-search-replay.ts +96 -0
- package/gui/dist/assets/App-CHC89EXQ.js +0 -58
- package/gui/dist/assets/App-wT3mXqFz.css +0 -1
- package/gui/dist/assets/quota-summary-D8y2wTsB.js +0 -1
package/src/claude/desktop-3p.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { canonicalDirectory } from "./desktop-remote-store-io";
|
|
|
6
6
|
import {
|
|
7
7
|
resolveDesktop3pConfigLibraryPath, parseMetadata, SAFE_DESKTOP_PROFILE_ID, isRecord,
|
|
8
8
|
isOwnedDesktopEntry, isOwnedDesktopGatewayEntry, profilePath, readDesktopProfileSnapshot, readDesktopLibraryText, readDesktopLibraryBytes,
|
|
9
|
-
readOptionalDesktopLibraryBytes,
|
|
9
|
+
readOptionalDesktopLibraryBytes, assertDesktopLibraryTextSize, serializeDesktopLibraryJson,
|
|
10
10
|
type Desktop3pConfigLibraryOptions, type Desktop3pMetadata, type Desktop3pMetadataEntry,
|
|
11
11
|
} from "./desktop-3p-library";
|
|
12
12
|
export { resolveDesktop3pConfigLibraryPath, type Desktop3pConfigLibraryOptions } from "./desktop-3p-library";
|
|
@@ -642,13 +642,24 @@ function removeDesktop3pStandardPivotLocal(
|
|
|
642
642
|
if (targetIds.length === 0) return { ok: true, changed: false, kind: "noop", libraryPath: inspected.libraryPath };
|
|
643
643
|
|
|
644
644
|
let metadataAfterPivot = metadata;
|
|
645
|
+
let standardPath: string | undefined;
|
|
646
|
+
let pivotMetadataJson: string | undefined;
|
|
645
647
|
if (selectedOwnedGatewayActive) {
|
|
646
648
|
const standardId = randomUUID();
|
|
647
|
-
|
|
648
|
-
atomicWriteFile(standardPath, "{}\n");
|
|
649
|
+
standardPath = profilePath(inspected.libraryPath, standardId);
|
|
649
650
|
const standardEntry: Desktop3pMetadataEntry = { id: standardId, name: "opencodex-standard" };
|
|
650
651
|
metadataAfterPivot = { ...metadata, appliedId: standardId, entries: [...metadata.entries, standardEntry] };
|
|
651
|
-
|
|
652
|
+
pivotMetadataJson = serializeDesktopLibraryJson(metadataAfterPivot);
|
|
653
|
+
}
|
|
654
|
+
// Bound the final metadata before any unlink so a size refusal cannot strand
|
|
655
|
+
// a row pointing at an already-deleted profile.
|
|
656
|
+
const removalMetadataJson = serializeDesktopLibraryJson({
|
|
657
|
+
...metadataAfterPivot,
|
|
658
|
+
entries: metadataAfterPivot.entries.filter(entry => !targetIds.includes(entry.id)),
|
|
659
|
+
});
|
|
660
|
+
if (standardPath !== undefined && pivotMetadataJson !== undefined) {
|
|
661
|
+
atomicWriteFile(standardPath, "{}\n");
|
|
662
|
+
atomicWriteFile(metadataPath, pivotMetadataJson);
|
|
652
663
|
}
|
|
653
664
|
|
|
654
665
|
const residualPaths: string[] = [];
|
|
@@ -674,10 +685,7 @@ function removeDesktop3pStandardPivotLocal(
|
|
|
674
685
|
}
|
|
675
686
|
// Do not leave a metadata row pointing at a deleted profile. For a foreign
|
|
676
687
|
// selection this only removes proven opencodex residues; appliedId is kept.
|
|
677
|
-
atomicWriteFile(
|
|
678
|
-
metadataPath,
|
|
679
|
-
JSON.stringify({ ...metadataAfterPivot, entries: metadataAfterPivot.entries.filter(entry => !targetIds.includes(entry.id)) }, null, 2) + "\n",
|
|
680
|
-
);
|
|
688
|
+
atomicWriteFile(metadataPath, removalMetadataJson);
|
|
681
689
|
return { ok: true, changed: true, kind: "removed", libraryPath: inspected.libraryPath };
|
|
682
690
|
} catch (error) {
|
|
683
691
|
return { ok: false, changed: false, kind: "write_failed", libraryPath: inspected.libraryPath, reason: desktopMutationFailureReason(error) };
|
|
@@ -831,7 +839,7 @@ function writeDesktop3pConfigWithGenerator(
|
|
|
831
839
|
|
|
832
840
|
const generated = generate();
|
|
833
841
|
const previous = readDesktopProfileSnapshot(configPath);
|
|
834
|
-
const configJson =
|
|
842
|
+
const configJson = serializeDesktopLibraryJson({ ...previous.foreign, ...generated });
|
|
835
843
|
const fingerprint = createHash("sha256").update(configJson).digest("hex").slice(0, 16);
|
|
836
844
|
if (refreshOnly) {
|
|
837
845
|
atomicWriteFileNoFollow(configPath, configJson, undefined, {
|
|
@@ -849,9 +857,10 @@ function writeDesktop3pConfigWithGenerator(
|
|
|
849
857
|
});
|
|
850
858
|
return { written: true, path: configPath, fingerprint };
|
|
851
859
|
}
|
|
860
|
+
const metadataJson = serializeDesktopLibraryJson({ ...metadata, appliedId: id, entries });
|
|
852
861
|
atomicReplaceDesktopConfig(configPath, configJson, previous.bytes);
|
|
853
862
|
try {
|
|
854
|
-
atomicWriteFile(metadataPath,
|
|
863
|
+
atomicWriteFile(metadataPath, metadataJson);
|
|
855
864
|
} catch (metaError) {
|
|
856
865
|
// Roll back from the verified bytes in memory: reopening the backup could meet a swapped
|
|
857
866
|
// entry and leave the new profile beside the old metadata. A profile this call created is
|
|
@@ -895,6 +904,7 @@ export function atomicReplaceDesktopConfig(
|
|
|
895
904
|
previous: Buffer | undefined,
|
|
896
905
|
writer: typeof atomicWriteFileNoFollow = atomicWriteFileNoFollow,
|
|
897
906
|
): { backupPath?: string } {
|
|
907
|
+
assertDesktopLibraryTextSize(content);
|
|
898
908
|
const validateBeforeRename = () => assertDesktopProfileUnchanged(path, previous);
|
|
899
909
|
validateBeforeRename();
|
|
900
910
|
const backupPath = `${path}.bak`;
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
readDesktopLibraryText,
|
|
20
20
|
resolveDesktop3pConfigLibraryPath,
|
|
21
21
|
SAFE_DESKTOP_PROFILE_ID,
|
|
22
|
+
serializeDesktopLibraryJson,
|
|
22
23
|
type Desktop3pConfigLibraryOptions,
|
|
23
24
|
type Desktop3pMetadata,
|
|
24
25
|
type Desktop3pMetadataEntry,
|
|
@@ -48,7 +49,7 @@ function metadataPath(libraryPath: string): string {
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
function metadataJson(metadata: Desktop3pMetadata): string {
|
|
51
|
-
return
|
|
52
|
+
return serializeDesktopLibraryJson(metadata);
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
function isValidMetadata(metadata: Desktop3pMetadata): boolean {
|
|
@@ -190,6 +191,10 @@ function applyDesktopPickerProfileLocked(options: { proxyPort: number } & Deskto
|
|
|
190
191
|
const changed = !alreadySelected || !oldProfile.exists || oldProfile.content !== target;
|
|
191
192
|
if (!changed) return { ok: true, changed: false, path: profile };
|
|
192
193
|
|
|
194
|
+
const entries = existing
|
|
195
|
+
? metadata.entries.map(entry => entry.id === entryId ? { ...entry, name: DESKTOP_PICKER_ENTRY_NAME } : entry)
|
|
196
|
+
: [...metadata.entries, { id: entryId, name: DESKTOP_PICKER_ENTRY_NAME }];
|
|
197
|
+
const nextMetadataJson = metadataJson({ ...metadata, appliedId: entryId, entries });
|
|
193
198
|
mkdirSync(join(configDir, PICKER_DIRECTORY), { recursive: true, mode: 0o700 });
|
|
194
199
|
try {
|
|
195
200
|
// Keep the same backup convention as the existing Desktop writer. It is removed with the
|
|
@@ -199,10 +204,7 @@ function applyDesktopPickerProfileLocked(options: { proxyPort: number } & Deskto
|
|
|
199
204
|
if (!alreadySelected) {
|
|
200
205
|
atomicWriteFile(previousStatePath, JSON.stringify({ entryId, previousAppliedId }) + "\n");
|
|
201
206
|
}
|
|
202
|
-
|
|
203
|
-
? metadata.entries.map(entry => entry.id === entryId ? { ...entry, name: DESKTOP_PICKER_ENTRY_NAME } : entry)
|
|
204
|
-
: [...metadata.entries, { id: entryId, name: DESKTOP_PICKER_ENTRY_NAME }];
|
|
205
|
-
atomicWriteFile(metaPath, metadataJson({ ...metadata, appliedId: entryId, entries }));
|
|
207
|
+
atomicWriteFile(metaPath, nextMetadataJson);
|
|
206
208
|
} catch {
|
|
207
209
|
try {
|
|
208
210
|
restoreFile(metaPath, oldMeta);
|
|
@@ -246,6 +248,8 @@ function removeDesktopPickerProfileLocked(options: DesktopPickerProfileOptions):
|
|
|
246
248
|
try { state = readPickerState(statePath); } catch { return { ok: false, reason: "profile_state_unreadable" }; }
|
|
247
249
|
|
|
248
250
|
let metadataAfterPivot = metadata;
|
|
251
|
+
let standardPath: string | undefined;
|
|
252
|
+
let pivotMetadataJson: string | undefined;
|
|
249
253
|
if (selected) {
|
|
250
254
|
const previous = state?.entryId === picker.id ? state.previousAppliedId : null;
|
|
251
255
|
const previousExists = previous !== null && previous !== picker.id && metadata.entries.some(entry => entry.id === previous);
|
|
@@ -253,16 +257,22 @@ function removeDesktopPickerProfileLocked(options: DesktopPickerProfileOptions):
|
|
|
253
257
|
metadataAfterPivot = { ...metadata, appliedId: previous };
|
|
254
258
|
} else {
|
|
255
259
|
const standardId = randomUUID();
|
|
256
|
-
|
|
260
|
+
standardPath = profilePath(libraryPath, standardId);
|
|
257
261
|
metadataAfterPivot = {
|
|
258
262
|
...metadata,
|
|
259
263
|
appliedId: standardId,
|
|
260
264
|
entries: [...metadata.entries, { id: standardId, name: "opencodex-standard" }],
|
|
261
265
|
};
|
|
262
266
|
}
|
|
263
|
-
|
|
267
|
+
pivotMetadataJson = metadataJson(metadataAfterPivot);
|
|
264
268
|
}
|
|
265
269
|
|
|
270
|
+
const entries = metadataAfterPivot.entries.filter(entry => entry.id !== picker.id);
|
|
271
|
+
const finalMetadataJson = metadataJson({ ...metadataAfterPivot, entries });
|
|
272
|
+
|
|
273
|
+
if (standardPath !== undefined) atomicWriteFile(standardPath, "{}\n");
|
|
274
|
+
if (pivotMetadataJson !== undefined) atomicWriteFile(metaPath, pivotMetadataJson);
|
|
275
|
+
|
|
266
276
|
const residualPaths: string[] = [];
|
|
267
277
|
for (const path of [profilePath(libraryPath, picker.id), `${profilePath(libraryPath, picker.id)}.bak`]) {
|
|
268
278
|
try { unlinkIfPresent(path); } catch { /* report the path without exposing file contents */ }
|
|
@@ -272,9 +282,8 @@ function removeDesktopPickerProfileLocked(options: DesktopPickerProfileOptions):
|
|
|
272
282
|
return { ok: false, reason: "cleanup_incomplete", residualPaths };
|
|
273
283
|
}
|
|
274
284
|
|
|
275
|
-
const entries = metadataAfterPivot.entries.filter(entry => entry.id !== picker.id);
|
|
276
285
|
try {
|
|
277
|
-
atomicWriteFile(metaPath,
|
|
286
|
+
atomicWriteFile(metaPath, finalMetadataJson);
|
|
278
287
|
try { unlinkIfPresent(statePath); } catch { /* residual is reported below */ }
|
|
279
288
|
if (existsSync(statePath)) return { ok: false, reason: "cleanup_incomplete", residualPaths: [statePath] };
|
|
280
289
|
} catch {
|
|
@@ -2,7 +2,7 @@ import { chmodSync, lstatSync, mkdirSync, realpathSync, unlinkSync } from "node:
|
|
|
2
2
|
import { basename, dirname, join, resolve } from "node:path";
|
|
3
3
|
import { atomicWriteFile, getConfigDir } from "../config";
|
|
4
4
|
import { hardenSecretDir } from "../lib/windows-secret-acl";
|
|
5
|
-
import { isRecord, readDesktopLibraryBytes, resolveDesktop3pConfigLibraryPath, SAFE_DESKTOP_PROFILE_ID, type Desktop3pMetadata } from "./desktop-3p-library";
|
|
5
|
+
import { isRecord, MAX_DESKTOP_PROFILE_FILE_BYTES, readDesktopLibraryBytes, resolveDesktop3pConfigLibraryPath, SAFE_DESKTOP_PROFILE_ID, type Desktop3pMetadata } from "./desktop-3p-library";
|
|
6
6
|
import { DesktopStoreError, digest } from "./desktop-remote-store-state";
|
|
7
7
|
|
|
8
8
|
export const MAX_DESKTOP_METADATA_ENTRIES = 256;
|
|
@@ -45,7 +45,7 @@ export class StoreIO {
|
|
|
45
45
|
try {
|
|
46
46
|
// The shared verified regular-file read keeps a swapped FIFO from parking this
|
|
47
47
|
// open before the post-read identity recheck below can run.
|
|
48
|
-
const bytes = readDesktopLibraryBytes(path);
|
|
48
|
+
const bytes = readDesktopLibraryBytes(path, Math.min(maxBytes, MAX_DESKTOP_PROFILE_FILE_BYTES));
|
|
49
49
|
if (bytes.byteLength > maxBytes || identity(lstatSync(path)) !== identity(stat)) throw new DesktopStoreError("conflict");
|
|
50
50
|
const value: unknown = JSON.parse(bytes.toString("utf8"));
|
|
51
51
|
if (!isRecord(value)) throw new DesktopStoreError("unsafe");
|
package/src/claude/inbound.ts
CHANGED
|
@@ -21,6 +21,7 @@ import { stabilizeClaudeInstructionsForPromptCache } from "./inbound-cache-stabi
|
|
|
21
21
|
import { decodeReasoningEnvelope, encodeReasoningEnvelope, OCX_REASONING_PREFIX } from "../responses/reasoning-envelope";
|
|
22
22
|
import { inlineDocumentMarker } from "../responses/inline-document";
|
|
23
23
|
import { createTranslatorBudget, type TranslatorBudget } from "../lib/translator-budget";
|
|
24
|
+
import { collectAnthropicCacheTtls } from "../adapters/anthropic/cache-retention";
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
|
|
@@ -390,34 +391,19 @@ export type ClaudeCacheKeySource = "metadata" | "system" | null;
|
|
|
390
391
|
/**
|
|
391
392
|
* The translated lane places its own prompt-cache breakpoints, so the caller's are dropped;
|
|
392
393
|
* their TTL is the one thing worth keeping. When every caller breakpoint asks for `ttl: "1h"`
|
|
393
|
-
* (Claude Code on a subscription marks them so) this maps to `cacheRetention: "long"`. A
|
|
394
|
-
*
|
|
395
|
-
* breakpoint 1h
|
|
396
|
-
*
|
|
394
|
+
* (Claude Code on a subscription marks them so) this maps to `cacheRetention: "long"`. A caller
|
|
395
|
+
* that pins any breakpoint to an explicit non-1h TTL ("5m") maps to "short": "long" would also
|
|
396
|
+
* make the moving per-turn breakpoint 1h and bill writes the caller asked to keep at 5m at the
|
|
397
|
+
* 1h rate. Breakpoints without a TTL express no preference, so they fall through to the
|
|
398
|
+
* destination default (1h on first-party Anthropic). Read out of body: the Responses body has
|
|
399
|
+
* no field for it.
|
|
397
400
|
*/
|
|
398
|
-
export function callerCacheRetention(raw: unknown): "long" | undefined {
|
|
399
|
-
if (!isRec(raw)) return undefined;
|
|
401
|
+
export function callerCacheRetention(raw: unknown): "long" | "short" | undefined {
|
|
400
402
|
// Top-level automatic caching is a breakpoint too, and content blocks nest (tool_result,
|
|
401
|
-
// search_result, document sources), so every block container is walked.
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
if (depth > 16) return;
|
|
406
|
-
if (Array.isArray(node)) { for (const item of node) visit(item, depth + 1); return; }
|
|
407
|
-
if (!isRec(node)) return;
|
|
408
|
-
if (isRec(node.cache_control)) ttls.push(node.cache_control.ttl);
|
|
409
|
-
for (const [key, value] of Object.entries(node)) {
|
|
410
|
-
if (key !== "cache_control" && key !== "input" && key !== "input_schema"
|
|
411
|
-
&& (Array.isArray(value) || isRec(value))) visit(value, depth + 1);
|
|
412
|
-
}
|
|
413
|
-
};
|
|
414
|
-
if (isRec(raw.cache_control)) ttls.push(raw.cache_control.ttl);
|
|
415
|
-
visit(raw.system, 0);
|
|
416
|
-
visit(raw.tools, 0);
|
|
417
|
-
visit(raw.messages, 0);
|
|
418
|
-
return ttls.length > 0 && ttls.every(ttl => ttl === "1h")
|
|
419
|
-
? "long"
|
|
420
|
-
: undefined;
|
|
403
|
+
// search_result, document sources), so every block container is walked.
|
|
404
|
+
const ttls = collectAnthropicCacheTtls(raw);
|
|
405
|
+
if (ttls.some(ttl => ttl !== undefined && ttl !== "1h")) return "short";
|
|
406
|
+
return ttls.length > 0 && ttls.every(ttl => ttl === "1h") ? "long" : undefined;
|
|
421
407
|
}
|
|
422
408
|
|
|
423
409
|
export interface ClaudeInboundTranslation {
|
package/src/claude/outbound.ts
CHANGED
|
@@ -156,6 +156,8 @@ export function anthropicUsage(usage: unknown, webSearchRequests = 0): Rec {
|
|
|
156
156
|
const details = isRec(u.input_tokens_details) ? u.input_tokens_details : {};
|
|
157
157
|
const cached = typeof details.cached_tokens === "number" ? details.cached_tokens : 0;
|
|
158
158
|
const cacheWrite = typeof details.cache_write_tokens === "number" ? details.cache_write_tokens : 0;
|
|
159
|
+
const cacheWrite1h = typeof details.cache_write_1h_tokens === "number"
|
|
160
|
+
? Math.max(0, Math.min(details.cache_write_1h_tokens, cacheWrite)) : undefined;
|
|
159
161
|
const input = typeof u.input_tokens === "number" ? u.input_tokens : 0;
|
|
160
162
|
const output = typeof u.output_tokens === "number" ? u.output_tokens : 0;
|
|
161
163
|
return {
|
|
@@ -163,6 +165,9 @@ export function anthropicUsage(usage: unknown, webSearchRequests = 0): Rec {
|
|
|
163
165
|
output_tokens: output,
|
|
164
166
|
cache_read_input_tokens: cached,
|
|
165
167
|
cache_creation_input_tokens: cacheWrite,
|
|
168
|
+
...(cacheWrite1h !== undefined
|
|
169
|
+
? { cache_creation: { ephemeral_5m_input_tokens: cacheWrite - cacheWrite1h, ephemeral_1h_input_tokens: cacheWrite1h } }
|
|
170
|
+
: {}),
|
|
166
171
|
// Only successful searches are billed/counted (Anthropic contract; Claude Code cost accounting).
|
|
167
172
|
...(webSearchRequests > 0 ? { server_tool_use: { web_search_requests: webSearchRequests } } : {}),
|
|
168
173
|
};
|
package/src/cli/account-api.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { projectApiKeyQuotaRows } from "./account-key-quota";
|
|
|
16
16
|
import { projectAccountHealth } from "./account-next-actions";
|
|
17
17
|
import type { OAuthHealthLabel } from "../oauth/health";
|
|
18
18
|
|
|
19
|
+
import { accountPauseDetailsSchema } from "../config/schema/account-auto-pause";
|
|
19
20
|
export type AccountType = "codex" | "oauth" | "api-key";
|
|
20
21
|
|
|
21
22
|
export interface AccountRow {
|
|
@@ -61,6 +62,7 @@ export interface AccountRow {
|
|
|
61
62
|
* human listing (#2703).
|
|
62
63
|
*/
|
|
63
64
|
paused?: boolean;
|
|
65
|
+
pauseDetails?: import("../types/account-auto-pause").AccountPauseDetails;
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
export type ClassifyResult = { type: AccountType } | { error: string };
|
|
@@ -342,6 +344,7 @@ interface CodexAccountDto {
|
|
|
342
344
|
quota?: CodexQuotaDto | null;
|
|
343
345
|
quotaRefresh?: unknown;
|
|
344
346
|
paused?: boolean;
|
|
347
|
+
pauseDetails?: import("../types/account-auto-pause").AccountPauseDetails;
|
|
345
348
|
}
|
|
346
349
|
|
|
347
350
|
function projectQuota(quota: CodexQuotaDto | null | undefined): CodexQuotaDto | null {
|
|
@@ -415,6 +418,8 @@ export async function fetchCodexRows(
|
|
|
415
418
|
? a.autoSwitchThresholdOverride
|
|
416
419
|
: null,
|
|
417
420
|
paused: a.paused === true,
|
|
421
|
+
// Unknown display details are dropped defensively; the pause state above is retained.
|
|
422
|
+
pauseDetails: accountPauseDetailsSchema.safeParse(a.pauseDetails).data,
|
|
418
423
|
...(includeQuota ? {
|
|
419
424
|
quota: projectQuota(a.quota),
|
|
420
425
|
quotaRefresh: projectCodexQuotaRefreshOutcome(a.quotaRefresh),
|
|
@@ -432,6 +437,7 @@ interface OAuthAccountDto {
|
|
|
432
437
|
healthLabel?: unknown;
|
|
433
438
|
/** Present only for providers that support operator pause (generic OAuth pools). */
|
|
434
439
|
paused?: boolean;
|
|
440
|
+
pauseDetails?: import("../types/account-auto-pause").AccountPauseDetails;
|
|
435
441
|
autoSwitchThresholdOverride?: number | null;
|
|
436
442
|
needsReauthReason?: "verify_account";
|
|
437
443
|
autoSelectable?: boolean;
|
|
@@ -475,7 +481,8 @@ async function fetchOAuthRows(
|
|
|
475
481
|
active: a.active ?? a.id === activeId,
|
|
476
482
|
needsReauth: a.needsReauth,
|
|
477
483
|
...projectAccountHealth(a, name, a.id),
|
|
478
|
-
|
|
484
|
+
// Unknown display details are dropped defensively; paused remains authoritative.
|
|
485
|
+
...(a.paused === true ? { paused: true, pauseDetails: accountPauseDetailsSchema.safeParse(a.pauseDetails).data } : {}),
|
|
479
486
|
...(isAnthropicOAuthInstance(name) && Object.hasOwn(a, "autoSwitchThresholdOverride") ? { autoSwitchThresholdOverride: a.autoSwitchThresholdOverride } : {}),
|
|
480
487
|
...(a.needsReauthReason === "verify_account" ? { needsReauthReason: a.needsReauthReason } : {}),
|
|
481
488
|
...(name === "kiro" && typeof a.autoSelectable === "boolean"
|
|
@@ -495,6 +502,8 @@ async function fetchOAuthRows(
|
|
|
495
502
|
}
|
|
496
503
|
|
|
497
504
|
interface ApiKeyDto {
|
|
505
|
+
disabled?: boolean;
|
|
506
|
+
pauseDetails?: import("../types/account-auto-pause").AccountPauseDetails;
|
|
498
507
|
id: string;
|
|
499
508
|
label?: string;
|
|
500
509
|
masked?: string;
|
|
@@ -523,6 +532,9 @@ async function fetchKeyRows(deps: AccountDeps, baseUrl: string, name: string, qu
|
|
|
523
532
|
label: k.label ?? k.masked,
|
|
524
533
|
masked: k.masked,
|
|
525
534
|
active: k.active ?? k.id === activeId,
|
|
535
|
+
paused: k.disabled === true,
|
|
536
|
+
// Unknown display details are dropped defensively; disabled still keeps the key paused.
|
|
537
|
+
pauseDetails: accountPauseDetailsSchema.safeParse(k.pauseDetails).data,
|
|
526
538
|
}));
|
|
527
539
|
return { rows, activeId, status: 200 };
|
|
528
540
|
}
|
|
@@ -260,7 +260,7 @@ function resetIso(value: number | undefined): string | null {
|
|
|
260
260
|
|
|
261
261
|
function refreshLine(row: FamilyRows["rows"][number]): string {
|
|
262
262
|
const parts = [row.id === MAIN_ID ? "main" : row.id, row.email, row.plan];
|
|
263
|
-
if (row.paused) parts.push("paused");
|
|
263
|
+
if (row.paused) parts.push(row.pauseDetails ? `paused(${row.pauseDetails.reason})` : "paused");
|
|
264
264
|
// Was a second quota dialect: it gated the whole block on weekly/monthly, so an account
|
|
265
265
|
// reporting only a 5h window printed `quota: unknown` while `quotaParts` five lines below
|
|
266
266
|
// rendered the same data correctly for the provider path (#2703). Two halves of one file
|
|
@@ -786,7 +786,7 @@ export async function cmdPriority(args: string[], deps: AccountDeps): Promise<nu
|
|
|
786
786
|
return 0;
|
|
787
787
|
}
|
|
788
788
|
|
|
789
|
-
function resolveGenericOAuthAccountTarget(accounts: unknown[], requested: string): { id: string } | { error: string } {
|
|
789
|
+
function resolveGenericOAuthAccountTarget(accounts: unknown[], requested: string, kind: "OAuth" | "API key" = "OAuth"): { id: string } | { error: string } {
|
|
790
790
|
const rows = accounts.filter((value): value is { id: string; alias?: unknown } =>
|
|
791
791
|
typeof value === "object" && value !== null && typeof (value as { id?: unknown }).id === "string",
|
|
792
792
|
);
|
|
@@ -797,10 +797,10 @@ function resolveGenericOAuthAccountTarget(accounts: unknown[], requested: string
|
|
|
797
797
|
: rows.filter(account => typeof account.alias === "string" && account.alias.toLowerCase() === requested.toLowerCase());
|
|
798
798
|
if (matches.length === 1) return { id: matches[0]!.id };
|
|
799
799
|
if (matches.length > 1) return { error: `alias "${requested}" names ${matches.length} accounts; use the account id` };
|
|
800
|
-
return { error: `Account not found: no
|
|
800
|
+
return { error: `Account not found: no ${kind} account has the id or alias "${requested}"` };
|
|
801
801
|
}
|
|
802
802
|
|
|
803
|
-
/** Pause or resume a Codex or
|
|
803
|
+
/** Pause or resume a Codex, OAuth, or API-key pool account. */
|
|
804
804
|
export async function cmdPause(args: string[], deps: AccountDeps, paused: boolean): Promise<number> {
|
|
805
805
|
const wantsJson = flag(args, "--json");
|
|
806
806
|
const name = args.shift();
|
|
@@ -839,8 +839,20 @@ export async function cmdPause(args: string[], deps: AccountDeps, paused: boolea
|
|
|
839
839
|
return 0;
|
|
840
840
|
}
|
|
841
841
|
|
|
842
|
+
if (classified.type === "api-key") {
|
|
843
|
+
const rows = await fetchRows(deps, baseUrl, name, "api-key");
|
|
844
|
+
if (rows.status === 0) return proxyUnreachable(rows.transportError);
|
|
845
|
+
if (rows.status !== 200) return apiError(rows.errorJson ?? {}, "failed to list API key accounts", rows.status);
|
|
846
|
+
const target = resolveGenericOAuthAccountTarget(rows.rows.map(row => ({ id: row.id, alias: row.label })), requestedId, "API key");
|
|
847
|
+
if ("error" in target) return usage(`Error: ${target.error}`);
|
|
848
|
+
const result = await apiJson(deps, baseUrl, "PUT", "/api/providers/keys/pause", { name, id: target.id, paused });
|
|
849
|
+
if (result.status === 0) return proxyUnreachable(result.transportError);
|
|
850
|
+
if (result.status !== 200) return apiError(result.json, `failed to ${verb} account`, result.status);
|
|
851
|
+
console.log(wantsJson ? JSON.stringify({ ok: true, provider: name, id: target.id, paused }, null, 2) : `${name}: account ${paused ? "paused" : "resumed"}`);
|
|
852
|
+
return 0;
|
|
853
|
+
}
|
|
842
854
|
if (classified.type !== "codex") {
|
|
843
|
-
return usage(`Error: ${verb} applies to the openai Codex account pool
|
|
855
|
+
return usage(`Error: ${verb} applies to the openai Codex account pool, a generic OAuth provider, or an API-key pool`);
|
|
844
856
|
}
|
|
845
857
|
const target = await resolveCodexAccountTarget(deps, baseUrl, requestedId);
|
|
846
858
|
if ("networkDown" in target) return proxyUnreachable(target.transportError);
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { parseQuotaFailureCode, type AccountQuotaFields, type ProviderQuota, type ProviderQuotaWindow, type ProviderQuotaCreditsUsd } from "../providers/quota-types";
|
|
3
3
|
import { terminalSafeText } from "./runtime-api";
|
|
4
4
|
import type { AccountRow, FamilyRows } from "./account-api";
|
|
5
|
+
import { accountPauseDetailsSchema } from "../config/schema/account-auto-pause";
|
|
5
6
|
|
|
6
7
|
function record(value: unknown): Record<string, unknown> {
|
|
7
8
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Malformed API-key quota response.");
|
|
@@ -88,7 +89,9 @@ export function projectApiKeyQuotaRows(value: unknown, provider: string): Family
|
|
|
88
89
|
}
|
|
89
90
|
const masked = optionalString(row, "masked");
|
|
90
91
|
return { provider, type: "api-key", id, label: optionalString(row, "label") ?? masked,
|
|
91
|
-
masked, active: optionalBoolean(row, "active") ?? id === activeId,
|
|
92
|
+
masked, active: optionalBoolean(row, "active") ?? id === activeId,
|
|
93
|
+
// Same projection as the plain key listing: disabled keeps the key paused, unknown details drop.
|
|
94
|
+
paused: row.disabled === true, pauseDetails: accountPauseDetailsSchema.safeParse(row.pauseDetails).data, ...fields };
|
|
92
95
|
});
|
|
93
96
|
return { rows, activeId, status: 200 };
|
|
94
97
|
}
|
package/src/cli/account.ts
CHANGED
|
@@ -119,7 +119,7 @@ function statusText(row: AccountRow): string {
|
|
|
119
119
|
// `paused` leads, and does NOT replace `selected`. A paused-but-selected account is the
|
|
120
120
|
// state an operator most needs named -- requests route to it while the pool believes it is
|
|
121
121
|
// held out -- so printing only one of the two would hide exactly the confusing case (#2703).
|
|
122
|
-
if (row.paused) parts.push("paused");
|
|
122
|
+
if (row.paused) parts.push(row.pauseDetails ? `paused(${row.pauseDetails.reason})` : "paused");
|
|
123
123
|
if (row.active) parts.push(row.type === "codex" ? "selected" : "active");
|
|
124
124
|
if (row.needsReauth && !(row.provider === "kiro" && row.skipReason === "needs_reauth")) {
|
|
125
125
|
parts.push(row.needsReauthReason === "verify_account" ? "needs-reauth(verify)" : "needs-reauth");
|
|
@@ -3,15 +3,15 @@ import type { Capability } from "./capability-types";
|
|
|
3
3
|
export const AGENT_ROUTING_CAPABILITIES: readonly Capability[] = [
|
|
4
4
|
{
|
|
5
5
|
command: ["message", "sessions"],
|
|
6
|
-
summary: "Discover already-loaded
|
|
7
|
-
routes: [], flags: [{ name: "--json", value: "boolean", summary: "Emit a versioned
|
|
6
|
+
summary: "Discover already-loaded Codex sessions locally or on an explicitly enrolled peer.",
|
|
7
|
+
routes: [], flags: [{ name: "--json", value: "boolean", summary: "Emit a versioned session directory." }, { name: "--host", value: "string", summary: "Exact enrolled peer alias or machine UUID; requires a live foreground owner." }],
|
|
8
8
|
mutates: false, json: "envelope",
|
|
9
|
-
bannerLines: ["ocx message <sub>
|
|
10
|
-
details: ["Linux/macOS
|
|
9
|
+
bannerLines: ["ocx message <sub> Codex queued messages, explicit SSH peers and foreground ownership"],
|
|
10
|
+
details: ["Linux/macOS only. Local discovery uses the effective CODEX_HOME Unix socket; --host uses the live foreground owner's authenticated loopback route. Starts no daemon and returns no partial directory."],
|
|
11
11
|
},
|
|
12
12
|
{
|
|
13
13
|
command: ["message", "send"],
|
|
14
|
-
summary: "Submit one correlated peer message to an exact loaded
|
|
14
|
+
summary: "Submit one correlated peer message to an exact loaded Codex destination.",
|
|
15
15
|
routes: [], mutates: true, json: "envelope",
|
|
16
16
|
flags: [
|
|
17
17
|
{ name: "--thread", value: "string", summary: "Exact loaded UUID; choose this or --name." },
|
|
@@ -20,10 +20,11 @@ export const AGENT_ROUTING_CAPABILITIES: readonly Capability[] = [
|
|
|
20
20
|
{ name: "--kind", value: "string", summary: "request (default), response or notification." },
|
|
21
21
|
{ name: "--in-reply-to", value: "string", summary: "Request message UUID; required only for a response." },
|
|
22
22
|
{ name: "--json", value: "boolean", summary: "Emit a receipt with not_sent, queued or unknown status." },
|
|
23
|
+
{ name: "--host", value: "string", summary: "Exact enrolled peer alias or machine UUID; omit for the unchanged local path." },
|
|
23
24
|
],
|
|
24
25
|
details: ["Requires daemon support for experimental thread/queue/add. Sender context comes from CODEX_THREAD_ID, not an authentication claim.",
|
|
25
26
|
"queued means submitted, not processed. unknown must not be replayed; no automatic retry, daemon start or thread resume.",
|
|
26
|
-
"Exit 0: queued; 1: not sent; 3: unknown; 64: invalid usage. No
|
|
27
|
+
"Exit 0: queued; 1: not sent; 3: unknown; 64: invalid usage. No Claude transport, native lifecycle mutation or skill installation."],
|
|
27
28
|
},
|
|
28
29
|
{
|
|
29
30
|
command: ["advisor"],
|
|
@@ -46,6 +47,42 @@ export const AGENT_ROUTING_CAPABILITIES: readonly Capability[] = [
|
|
|
46
47
|
],
|
|
47
48
|
},
|
|
48
49
|
|
|
50
|
+
{
|
|
51
|
+
command: ["message", "enable"], summary: "Explicitly enable private remote messaging state, without starting a listener.", routes: [],
|
|
52
|
+
flags: [{ name: "--port", value: "number", summary: "Unprivileged IPv4-loopback port; defaults to 39176 on first enable." },
|
|
53
|
+
{ name: "--json", value: "boolean", summary: "Emit configuration without credentials." }], mutates: true, json: "envelope",
|
|
54
|
+
},
|
|
55
|
+
{ command: ["message", "disable"], summary: "Disable remote admission and retire the foreground owner generation.", routes: [],
|
|
56
|
+
flags: [{ name: "--json", value: "boolean", summary: "Emit safe configuration." }], mutates: true, json: "envelope" },
|
|
57
|
+
{ command: ["message", "status"], summary: "Read enabled, running and leased/initiated route states without activation.", routes: [],
|
|
58
|
+
flags: [{ name: "--json", value: "boolean", summary: "Emit safe configuration and owner observations." }], mutates: false, json: "envelope" },
|
|
59
|
+
{ command: ["message", "serve"], summary: "Own a foreground listener and optional duplex SSH peer routes; no automatic reconnect.", routes: [],
|
|
60
|
+
flags: [{ name: "--host", value: "string", summary: "Exact initiating peer; repeat for at most four distinct peers." },
|
|
61
|
+
{ name: "--json", value: "boolean", summary: "Emit the startup observation without credentials." }], mutates: true, json: "envelope" },
|
|
62
|
+
{ command: ["message", "hosts", "probe"], summary: "Offer an SSH host fingerprint without enrollment or persistent changes.", routes: [],
|
|
63
|
+
flags: [{ name: "--ssh", value: "string", required: true, summary: "OpenSSH destination/alias." },
|
|
64
|
+
{ name: "--json", value: "boolean", summary: "Emit the offered fingerprint." }], mutates: false, json: "envelope" },
|
|
65
|
+
{ command: ["message", "hosts", "add"], usage: "ocx message hosts add <alias> --ssh <destination> --fingerprint <SHA256> [--json]",
|
|
66
|
+
summary: "Enroll outbound delivery and correlated return replies after SSH fingerprint confirmation.", routes: [],
|
|
67
|
+
flags: [{ name: "--ssh", value: "string", required: true, summary: "OpenSSH destination/alias." },
|
|
68
|
+
{ name: "--fingerprint", value: "string", required: true, summary: "Explicitly confirmed offered SSH host-key fingerprint." },
|
|
69
|
+
{ name: "--json", value: "boolean", summary: "Emit enrollment metadata, never credentials." }], mutates: true, json: "envelope" },
|
|
70
|
+
{ command: ["message", "hosts", "allow-inbound"], usage: "ocx message hosts allow-inbound <alias-or-machine-uuid> [--json]",
|
|
71
|
+
summary: "Explicitly allow this enrolled peer to send any message kind to local loaded sessions.", routes: [],
|
|
72
|
+
flags: [{ name: "--json", value: "boolean", summary: "Emit the local inbound grant, without credentials." }], mutates: true, json: "envelope" },
|
|
73
|
+
{ command: ["message", "hosts", "deny-inbound"], usage: "ocx message hosts deny-inbound <alias-or-machine-uuid> [--json]",
|
|
74
|
+
summary: "Restrict this peer to correlated replies on locally opened reply routes.", routes: [],
|
|
75
|
+
flags: [{ name: "--json", value: "boolean", summary: "Emit the narrowed local inbound grant." }], mutates: true, json: "envelope" },
|
|
76
|
+
{ command: ["message", "hosts", "list"], summary: "List exact peer identities without credentials or listener activation.", routes: [],
|
|
77
|
+
flags: [{ name: "--json", value: "boolean", summary: "Emit safe peer metadata." }], mutates: false, json: "envelope" },
|
|
78
|
+
{ command: ["message", "hosts", "remove"], usage: "ocx message hosts remove <alias-or-machine-uuid> [--transaction <uuid>] [--json]",
|
|
79
|
+
summary: "Revoke local admission first and report any unconfirmed remote cleanup explicitly.", routes: [],
|
|
80
|
+
flags: [{ name: "--transaction", value: "string", summary: "Remove only this exact enrollment; refuse a replacement transaction." },
|
|
81
|
+
{ name: "--json", value: "boolean", summary: "Emit local and remote revocation outcomes." }], mutates: true, json: "envelope" },
|
|
82
|
+
{ command: ["message", "hosts", "abandon"], usage: "ocx message hosts abandon --transaction <uuid> [--json]",
|
|
83
|
+
summary: "Abandon an exact pending local enrollment; remote cleanup remains unconfirmed (exit 3).", routes: [],
|
|
84
|
+
flags: [{ name: "--transaction", value: "string", required: true, summary: "Exact pending UUID from hosts list or status; never an alias." },
|
|
85
|
+
{ name: "--json", value: "boolean", summary: "Emit local abandonment and explicit remote-cleanup guidance." }], mutates: true, json: "envelope" },
|
|
49
86
|
{
|
|
50
87
|
command: ["agent", "status"],
|
|
51
88
|
usage: "ocx agent status [--json]",
|
|
@@ -298,10 +335,10 @@ export const AGENT_ROUTING_CAPABILITIES: readonly Capability[] = [
|
|
|
298
335
|
},
|
|
299
336
|
{
|
|
300
337
|
command: ["combo","set"],
|
|
301
|
-
usage: "ocx combo set <id> [--targets <provider/model[:weight],...> | --targets-file <FILE|->] [--strategy <failover|round-robin|random|least-used|reset-window|jev>] [--sticky <1-100|->] [--effort <low|medium|high|xhigh|max|ultra|->] [--effort-mode <fallback|force|->] [--alias <name|->] [--native-alias [on|off]] [--allow-metered-targets <on|off>] [--display-name <label|->] [--decision-provider <provider|-> | --decision-model <route|->] [--decision-timeout <ms|->] [--rename-from <id>] [--image-input <auto|disabled>] [--reasoning-effort-mode <strict|adaptive>] [--json]",
|
|
338
|
+
usage: "ocx combo set <id> [--targets <provider/model[:weight],...> | --targets-file <FILE|->] [--strategy <failover|round-robin|random|least-used|reset-window|jev>] [--sticky <1-100|->] [--effort <low|medium|high|xhigh|max|ultra|->] [--effort-mode <fallback|force|->] [--alias <name|->] [--native-alias [on|off]] [--allow-metered-targets <on|off>] [--display-name <label|->] [--description <text|->] [--decision-provider <provider|-> | --decision-model <route|->] [--decision-timeout <ms|->] [--decision-quota-signals <on|off|->] [--decision-quota-tiers <JSON|->] [--rename-from <id>] [--image-input <auto|disabled>] [--reasoning-effort-mode <strict|adaptive>] [--json]",
|
|
302
339
|
summary: "Create, update or rename a combo.",
|
|
303
340
|
routes: [{"method":"GET","path":"/api/combos"},{"method":"PUT","path":"/api/combos"}],
|
|
304
|
-
flags: [{"name":"--allow-metered-targets","value":"string","summary":"on grants operator consent to metered targets only on a loopback-only proxy; off clears it."},{"name":"--targets","value":"string","summary":"Ordered provider/model[:weight] list; required for creation."},{"name":"--strategy","value":"string","summary":"Routing strategy."},{"name":"--sticky","value":"string","summary":"Round-robin limit 1-100; - resets."},{"name":"--effort","value":"string","summary":"Default effort; - clears."},{"name":"--effort-mode","value":"string","summary":"fallback or force; - resets to fallback."},{"name":"--alias","value":"string","summary":"Alias; - clears."},{"name":"--display-name","value":"string","summary":"Label; - clears."},{"name":"--decision-provider","value":"string","summary":"JEV provider; - clears."},{"name":"--decision-model","value":"string","summary":"JEV route; - clears; exclusive with provider."},{"name":"--decision-timeout","value":"string","summary":"JEV deadline 1000-120000 ms; - clears."},{"name":"--rename-from","value":"string","summary":"Existing combo ID to rename."},{"name":"--json","value":"boolean","summary":"Emit structured JSON."},{"name":"--native-alias","value":"string","summary":"Bare legacy flag means on; explicit on/off preserves presence, including false."},{"name":"--targets-file","value":"string","summary":"Bounded ordered target JSON array, exclusive with --targets; preserves effort/profile/last-resort metadata."},{"name":"--image-input","value":"string","summary":"auto or disabled; explicit auto clears the previous disabled choice."},{"name":"--reasoning-effort-mode","value":"string","summary":"strict or adaptive, distinct from --effort-mode fallback/force."}],
|
|
341
|
+
flags: [{"name":"--allow-metered-targets","value":"string","summary":"on grants operator consent to metered targets only on a loopback-only proxy; off clears it."},{"name":"--targets","value":"string","summary":"Ordered provider/model[:weight] list; required for creation."},{"name":"--strategy","value":"string","summary":"Routing strategy."},{"name":"--sticky","value":"string","summary":"Round-robin limit 1-100; - resets."},{"name":"--effort","value":"string","summary":"Default effort; - clears."},{"name":"--effort-mode","value":"string","summary":"fallback or force; - resets to fallback."},{"name":"--alias","value":"string","summary":"Alias; - clears."},{"name":"--display-name","value":"string","summary":"Label; - clears."},{"name":"--description","value":"string","summary":"Catalog row description; - clears."},{"name":"--decision-provider","value":"string","summary":"JEV provider; - clears."},{"name":"--decision-model","value":"string","summary":"JEV route; - clears; exclusive with provider."},{"name":"--decision-timeout","value":"string","summary":"JEV deadline 1000-120000 ms; - clears."},{"name":"--decision-quota-signals","value":"string","summary":"JEV advisory quota on/off; - clears."},{"name":"--decision-quota-tiers","value":"string","summary":"Partial JSON thresholds; - clears."},{"name":"--rename-from","value":"string","summary":"Existing combo ID to rename."},{"name":"--json","value":"boolean","summary":"Emit structured JSON."},{"name":"--native-alias","value":"string","summary":"Bare legacy flag means on; explicit on/off preserves presence, including false."},{"name":"--targets-file","value":"string","summary":"Bounded ordered target JSON array, exclusive with --targets; preserves effort/profile/last-resort metadata."},{"name":"--image-input","value":"string","summary":"auto or disabled; explicit auto clears the previous disabled choice."},{"name":"--reasoning-effort-mode","value":"string","summary":"strict or adaptive, distinct from --effort-mode fallback/force."}],
|
|
305
342
|
mutates: true,
|
|
306
343
|
json: "payload",
|
|
307
344
|
details: ["create and update are aliases of set. Existing fields are read before writing; --targets replaces targets.","force overrides valid client effort and may increase cost and latency.","Only the implemented target grammar is supported; full GUI target metadata and routing-profile writes are separate tasks.","New file/flag conflicts refuse before transport. Partial edits preserve target metadata; explicit false/default values override server carry. Turning a native alias off may also require clearing its incompatible alias.","Read/merge/write uses one target and remains an upsert without revision CAS. Saved-but-incomplete catalog outcomes return nonzero with their receipt."],
|
|
@@ -340,18 +340,18 @@ export const CAPABILITIES: readonly Capability[] = [
|
|
|
340
340
|
},
|
|
341
341
|
{
|
|
342
342
|
command: ["account","pause"],
|
|
343
|
-
summary: "Exclude one account in a Codex, Anthropic
|
|
344
|
-
routes: [{"method":"GET","path":"/api/codex-auth/accounts"},{"method":"PUT","path":"/api/codex-auth/accounts/pause"},{"method":"GET","path":"/api/oauth/accounts"},{"method":"PUT","path":"/api/oauth/accounts/pause"}],
|
|
343
|
+
summary: "Exclude one account in a Codex, Anthropic, generic OAuth or API-key pool from automatic selection.",
|
|
344
|
+
routes: [{"method":"GET","path":"/api/codex-auth/accounts"},{"method":"PUT","path":"/api/codex-auth/accounts/pause"},{"method":"GET","path":"/api/oauth/accounts"},{"method":"PUT","path":"/api/oauth/accounts/pause"},{"method":"GET","path":"/api/providers/keys"},{"method":"PUT","path":"/api/providers/keys/pause"}],
|
|
345
345
|
flags: [{"name":"--json","value":"boolean","summary":"Emit the pause result as JSON."}],
|
|
346
346
|
mutates: true,
|
|
347
347
|
json: "envelope",
|
|
348
|
-
details: ["Codex pause unbinds pinned threads and selects a fallback when possible; with no fallback,
|
|
348
|
+
details: ["Codex pause unbinds pinned threads and selects a fallback when possible; with no fallback, the paused account cannot dispatch until resumed. Anthropic and generic OAuth pause exclude the account from new requests, failover and refresh, and an all-paused pool answers 403. Credentials and health are preserved; already-sent turns are not cancelled."],
|
|
349
349
|
usage: "ocx account pause <provider> <account-id|alias|main> [--json]",
|
|
350
350
|
},
|
|
351
351
|
{
|
|
352
352
|
command: ["account","resume"],
|
|
353
|
-
summary: "Return a paused account to a Codex, Anthropic
|
|
354
|
-
routes: [{"method":"GET","path":"/api/codex-auth/accounts"},{"method":"PUT","path":"/api/codex-auth/accounts/pause"},{"method":"GET","path":"/api/oauth/accounts"},{"method":"PUT","path":"/api/oauth/accounts/pause"}],
|
|
353
|
+
summary: "Return a paused account to a Codex, Anthropic, generic OAuth or API-key pool.",
|
|
354
|
+
routes: [{"method":"GET","path":"/api/codex-auth/accounts"},{"method":"PUT","path":"/api/codex-auth/accounts/pause"},{"method":"GET","path":"/api/oauth/accounts"},{"method":"PUT","path":"/api/oauth/accounts/pause"},{"method":"GET","path":"/api/providers/keys"},{"method":"PUT","path":"/api/providers/keys/pause"}],
|
|
355
355
|
flags: [{"name":"--json","value":"boolean","summary":"Emit the resume result as JSON."}],
|
|
356
356
|
mutates: true,
|
|
357
357
|
json: "envelope",
|
|
@@ -49,10 +49,11 @@ export const PROVIDER_MODEL_CAPABILITIES: readonly Capability[] = [
|
|
|
49
49
|
details: ["At least one edit is required; only supplied fields are patched. update is an existing alias. Use provider show only for local state, not as proof of the live write.","Management errors use safe fixed provider diagnostics; catalog failures retain the saved receipt and a nonzero exit."],
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
|
-
command: ["provider", "test"], summary: "Probe
|
|
53
|
-
usage: "ocx provider test <name> [--
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
command: ["provider", "test"], summary: "Probe discovery connectivity or explicitly test one model completion.",
|
|
53
|
+
usage: "ocx provider test <name> [--model <id> [--yes]] [--json]",
|
|
54
|
+
routes: [{ method: "POST", path: "/api/providers/test" }, { method: "POST", path: "/api/providers/test-completion" }], mutates: true, json: "payload",
|
|
55
|
+
flags: [{ name: "--model", value: "string", summary: "Run one bounded completion for this model instead of discovery." }, { name: "--yes", value: "boolean", summary: "Confirm completion quota/cost; requires --model. Mandatory with --json or non-interactive input." }, { name: "--json", value: "boolean", summary: "Emit the probe result; completion warnings go to stderr." }],
|
|
56
|
+
details: ["Without --model, discovery behavior is unchanged; static catalogs can return applicable:false. Completion tests print a quota/cost warning and require a default-No terminal confirmation or --yes; cancellation exits 1 without a request.", "Completion output includes TTFT, total latency, upstream/wire model metadata and comparison. Metadata is not proof of model identity; mismatch still exits 0. Failed tests exit 1; non-applicable results exit 0."],
|
|
56
57
|
},
|
|
57
58
|
{
|
|
58
59
|
command: ["provider", "quota"], summary: "Read provider quota reports, optionally refreshing them.",
|
package/src/cli/combo-result.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeJevQuotaTiers } from "../combos/jev-quota-config";
|
|
1
2
|
import { parseComboTargets } from "./combo-input";
|
|
2
3
|
import { printCatalogResult } from "./catalog-command-result";
|
|
3
4
|
|
|
@@ -7,7 +8,7 @@ const ENUMS: Readonly<Record<string, readonly (string | null)[]>> = {
|
|
|
7
8
|
defaultEffortMode: ["fallback", "force"], reasoningEffortMode: ["strict", "adaptive"],
|
|
8
9
|
imageInput: ["auto", "disabled"], cooldownWaitPolicy: ["before-last-resort"],
|
|
9
10
|
};
|
|
10
|
-
const STRINGS = new Set(["alias", "displayName", "decisionProvider", "decisionModel"]);
|
|
11
|
+
const STRINGS = new Set(["alias", "displayName", "description", "decisionProvider", "decisionModel"]);
|
|
11
12
|
const RANGES: Readonly<Record<string, readonly [number, number]>> = {
|
|
12
13
|
stickyLimit: [1, 100], cooldownMs: [1, 600_000], waitForCooldownMs: [0, 600_000],
|
|
13
14
|
decisionTimeoutMs: [1_000, 120_000],
|
|
@@ -33,7 +34,12 @@ function publicCombo(value: unknown): Record<string, unknown> {
|
|
|
33
34
|
} else if (Object.hasOwn(RANGES, key)) {
|
|
34
35
|
const [min, max] = RANGES[key]!;
|
|
35
36
|
if (typeof child !== "number" || !Number.isSafeInteger(child) || child < min || child > max) invalid();
|
|
36
|
-
} else if (key === "
|
|
37
|
+
} else if (key === "decisionQuotaTiers") {
|
|
38
|
+
const tiers = normalizeJevQuotaTiers(child);
|
|
39
|
+
if (!tiers) invalid();
|
|
40
|
+
result[key] = tiers;
|
|
41
|
+
continue;
|
|
42
|
+
} else if (key === "nativeAlias" || key === "allowMeteredTargets" || key === "decisionQuotaSignals") {
|
|
37
43
|
if (typeof child !== "boolean") invalid();
|
|
38
44
|
} else invalid();
|
|
39
45
|
result[key] = child;
|