@bitkyc08/opencodex 2.58.0 → 2.60.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 +28 -10
- package/gui/dist/assets/index-BTuCbqQd.css +1 -0
- package/gui/dist/assets/index-DoBVdPHP.js +134 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/crusoe.svg +1 -0
- package/gui/dist/provider-icons/opper.svg +3 -0
- package/package.json +4 -1
- package/src/adapters/anthropic-image-codec.ts +16 -2
- package/src/adapters/anthropic-image-normalize.ts +49 -2
- package/src/adapters/anthropic.ts +4 -1
- package/src/adapters/base.ts +34 -1
- package/src/adapters/coding-agent/turn.ts +22 -2
- package/src/adapters/command-code.ts +50 -3
- package/src/adapters/cursor/catalog.ts +11 -0
- package/src/adapters/cursor/checkpoint-store.ts +3 -0
- package/src/adapters/cursor/discovery.ts +11 -8
- package/src/adapters/cursor/effort-map.ts +16 -2
- package/src/adapters/cursor/envelope-echo.ts +55 -2
- package/src/adapters/cursor/live-transport.ts +26 -9
- package/src/adapters/cursor/message-mapper.ts +3 -2
- package/src/adapters/cursor/protobuf-request.ts +8 -5
- package/src/adapters/cursor/request-builder.ts +21 -4
- package/src/adapters/cursor/thread-continuity.ts +105 -31
- package/src/adapters/cursor/tool-guidance.ts +5 -4
- package/src/adapters/cursor/transport.ts +19 -0
- package/src/adapters/cursor.ts +45 -2
- package/src/adapters/devin/cloud-direct/chat.ts +14 -3
- package/src/adapters/devin/cloud-direct/index.ts +7 -0
- package/src/adapters/devin/cloud-direct/stated-reset-retry.ts +140 -0
- package/src/adapters/devin.ts +125 -26
- package/src/adapters/google-antigravity-replay.ts +1 -1
- package/src/adapters/google-antigravity-wire.ts +55 -4
- package/src/adapters/google-http.ts +57 -11
- package/src/adapters/google-tool-schema.ts +595 -31
- package/src/adapters/google-wire-compiler.ts +93 -10
- package/src/adapters/google-wire-shape.ts +461 -0
- package/src/adapters/google.ts +60 -10
- package/src/adapters/openai-chat/response-events.ts +61 -0
- package/src/adapters/openai-chat-images.ts +3 -1
- package/src/adapters/openai-chat.ts +10 -11
- package/src/adapters/openai-responses/image-gen.ts +8 -6
- package/src/adapters/openai-responses/passthrough.ts +25 -4
- package/src/adapters/openai-responses/reasoning.ts +7 -0
- package/src/adapters/openai-responses/tool-output-recovery.ts +75 -0
- package/src/adapters/openai-responses/tool-schema.ts +19 -7
- package/src/adapters/opencode-go-additional-tools.ts +12 -2
- package/src/adapters/responses-tool-schema.ts +76 -46
- package/src/adapters/run-turn-queue.ts +17 -4
- package/src/bridge/response-json.ts +1 -1
- package/src/bridge/sse.ts +28 -35
- package/src/claude/context-windows.ts +22 -0
- package/src/claude/outbound.ts +35 -4
- package/src/cli/account-api.ts +4 -3
- package/src/cli/account-extended.ts +26 -6
- package/src/cli/account-orca-import.ts +63 -0
- package/src/cli/account.ts +32 -4
- package/src/cli/capabilities.ts +40 -0
- package/src/cli/claude.ts +29 -1
- package/src/cli/codex-cli-update.ts +97 -2
- package/src/cli/dispatch.ts +57 -3
- package/src/cli/doctor.ts +218 -4
- package/src/cli/help.ts +4 -1
- package/src/cli/hub.ts +3 -2
- package/src/cli/index.ts +95 -22
- package/src/cli/models-runtime.ts +33 -4
- package/src/cli/opencode.ts +2 -2
- package/src/cli/provider.ts +13 -1
- package/src/cli/registry.ts +11 -1
- package/src/cli/runtime-api.ts +44 -0
- package/src/cli/start-args.ts +94 -0
- package/src/cli/system-command.ts +2 -0
- package/src/client/machine-api.ts +6 -5
- package/src/client/machine-listener.ts +16 -3
- package/src/client/runtime.ts +26 -2
- package/src/clients/config-export/constants.ts +2 -3
- package/src/clients/config-export.ts +5 -5
- package/src/codex/account-store.ts +146 -5
- package/src/codex/auth-api/account-list.ts +19 -11
- package/src/codex/auth-api/pool-quota-probe.ts +44 -10
- package/src/codex/auth-api/routes.ts +17 -2
- package/src/codex/auth-context.ts +16 -12
- package/src/codex/catalog/build-entries.ts +25 -4
- package/src/codex/catalog/derive-entry.ts +8 -1
- package/src/codex/catalog/effort.ts +10 -6
- package/src/codex/catalog/gather-capture.ts +22 -2
- package/src/codex/catalog/model-hints.ts +66 -33
- package/src/codex/catalog/parsing.ts +90 -5
- package/src/codex/catalog/provider-models.ts +19 -2
- package/src/codex/catalog/reserve-warn.ts +96 -0
- package/src/codex/catalog/retained-sync.ts +41 -26
- package/src/codex/catalog/routed-gather.ts +61 -3
- package/src/codex/cli-installation-identity.ts +210 -0
- package/src/codex/cli-installation-targets.ts +158 -0
- package/src/codex/context-compat.ts +5 -2
- package/src/codex/convergence.ts +5 -0
- package/src/codex/desired-state.ts +4 -1
- package/src/codex/history-job.ts +6 -6
- package/src/codex/history-provider.ts +24 -167
- package/src/codex/history-rollout-read.ts +174 -0
- package/src/codex/history-state-open.ts +105 -0
- package/src/codex/inject/config-toml.ts +44 -2
- package/src/codex/inject.ts +3 -2
- package/src/codex/internal/catalog-writer.ts +33 -1
- package/src/codex/lineage.ts +83 -32
- package/src/codex/loopback-target.ts +31 -0
- package/src/codex/main-account-hard-lock.ts +2 -1
- package/src/codex/main-account.ts +10 -3
- package/src/codex/main-device-reauth.ts +17 -9
- package/src/codex/model-cache.ts +47 -0
- package/src/codex/model-entitlements.ts +80 -2
- package/src/codex/observed-model-denials.ts +230 -0
- package/src/codex/orca-auth-source.ts +94 -0
- package/src/codex/orca-import.ts +219 -0
- package/src/codex/prompt-text-probe.ts +289 -16
- package/src/codex/quota-401-recovery.ts +12 -0
- package/src/codex/quota-types.ts +65 -0
- package/src/codex/quota.ts +24 -19
- package/src/codex/routing/cooldown-math.ts +8 -47
- package/src/codex/routing/pin-drain.ts +57 -0
- package/src/codex/routing.ts +20 -16
- package/src/codex/shim.ts +1 -1
- package/src/codex/subagent-model-fallback.ts +114 -2
- package/src/codex/windows-installation-files.ts +224 -0
- package/src/combos/failover.ts +125 -5
- package/src/config/admitted-identity.ts +222 -0
- package/src/config/diagnostics.ts +43 -1
- package/src/config/feature-flags.ts +5 -0
- package/src/config/load-degrade.ts +33 -0
- package/src/config/pending-teardown.ts +8 -0
- package/src/config/process-state.ts +36 -3
- package/src/config/provider-relative-send-path.ts +16 -0
- package/src/config/proxy-env.ts +31 -7
- package/src/config/schema/compaction-triggers.ts +11 -0
- package/src/config/schema/config-schema.ts +25 -0
- package/src/config/schema/leaf-validators.ts +76 -17
- package/src/config.ts +2 -2
- package/src/generated/compatibility-version.json +410 -258
- package/src/generated/model-metadata.ts +1 -1
- package/src/grok/reset-coupons.ts +38 -19
- package/src/images/loop.ts +6 -1
- package/src/integrations/aside-profile-context.ts +37 -3
- package/src/integrations/aside-profile-journal.ts +68 -3
- package/src/integrations/aside-profiles.ts +128 -3
- package/src/integrations/mutation-plan.ts +815 -0
- package/src/integrations/writer.ts +85 -99
- package/src/lab/live/transport.ts +4 -0
- package/src/lab/live/types.ts +5 -0
- package/src/lab/subject/behavior-fingerprint.ts +1 -1
- package/src/lib/admin-secrets.ts +9 -1
- package/src/lib/bounded-body.ts +4 -2
- package/src/lib/debug-log-buffer.ts +6 -1
- package/src/lib/debug.ts +23 -0
- package/src/lib/destination-policy.ts +48 -6
- package/src/lib/errors.ts +82 -15
- package/src/lib/http-response-semantics.ts +57 -0
- package/src/lib/lab-live-pinned-sender.ts +26 -12
- package/src/lib/local-destinations.ts +32 -5
- package/src/lib/pinned-http.ts +142 -2
- package/src/lib/plain-data.ts +103 -0
- package/src/lib/process-control.ts +13 -5
- package/src/lib/provider-outbound.ts +53 -5
- package/src/lib/proxy-env.ts +70 -3
- package/src/lib/request-execution-budget.ts +11 -3
- package/src/lib/response-body-inactivity.ts +193 -0
- package/src/lib/retry-delay.ts +69 -0
- package/src/lib/socks5-fetch.ts +741 -0
- package/src/lib/spend-ledger-owner.ts +364 -0
- package/src/lib/spend-reservation-ledger.ts +332 -35
- package/src/lib/windows-system-proxy.ts +16 -11
- package/src/lib/workflow-budget.ts +145 -8
- package/src/oauth/account-quota-rank.ts +72 -15
- package/src/oauth/callback-server.ts +4 -3
- package/src/oauth/generic-account-failover.ts +41 -27
- package/src/oauth/health.ts +12 -1
- package/src/oauth/index.ts +3 -107
- package/src/oauth/login-flow-state.ts +127 -0
- package/src/oauth/orcarouter.ts +15 -2
- package/src/oauth/store.ts +8 -0
- package/src/providers/codex-capacity.ts +9 -0
- package/src/providers/derive.ts +34 -17
- package/src/providers/devin-cli-authmode-migration.ts +14 -10
- package/src/providers/devin-provider-merge-migration.ts +33 -12
- package/src/providers/free-directory.ts +20 -2
- package/src/providers/key-failover.ts +327 -25
- package/src/providers/model-rename-migration.ts +56 -1
- package/src/providers/model-rename-startup.ts +7 -5
- package/src/providers/openai-sidecar.ts +4 -0
- package/src/providers/openai-virtual-models.ts +42 -2
- package/src/providers/opencode-go-transport.ts +14 -5
- package/src/providers/quota/antigravity.ts +22 -2
- package/src/providers/quota/report-cache.ts +3 -0
- package/src/providers/quota/vendor-probes-key.ts +1 -1
- package/src/providers/registry/entries-core.ts +39 -17
- package/src/providers/registry/entries-extended.ts +131 -1
- package/src/providers/registry/model-ids.ts +168 -0
- package/src/providers/registry/model-seeds.ts +87 -21
- package/src/providers/registry/types.ts +2 -0
- package/src/providers/resolved-model-policy-merge.ts +167 -0
- package/src/providers/resolved-model-policy.ts +406 -0
- package/src/providers/stale-vision-classification-migration.ts +137 -0
- package/src/responses/apply-patch-envelope.ts +32 -11
- package/src/responses/bridge-search-replay-cache.ts +152 -0
- package/src/responses/code-mode-helper-compat.ts +26 -16
- package/src/responses/custom-tool-compat.ts +1 -1
- package/src/responses/freeform-wrapper-scan.ts +279 -0
- package/src/responses/hosted-tool-policy.ts +85 -2
- package/src/responses/legacy-dotted-tool-name-repair.ts +134 -0
- package/src/responses/progressive-freeform-input.ts +130 -0
- package/src/responses/reasoning-envelope.ts +30 -0
- package/src/responses/schema.ts +9 -2
- package/src/responses/state.ts +5 -12
- package/src/responses/tool-name-aliases.ts +15 -1
- package/src/router.ts +91 -115
- package/src/routing/compatibility/behavior.ts +9 -0
- package/src/routing/compatibility/subject.ts +16 -1
- package/src/server/adapter-resolve.ts +9 -0
- package/src/server/auth-cors.ts +29 -0
- package/src/server/chat-completions.ts +13 -5
- package/src/server/chat-native-sse.ts +26 -9
- package/src/server/chat-native.ts +10 -4
- package/src/server/claude-messages.ts +30 -5
- package/src/server/effort-row.ts +11 -3
- package/src/server/grok-responses-control-frame.ts +160 -1
- package/src/server/gui-static.ts +36 -2
- package/src/server/inbound-body-admission.ts +187 -0
- package/src/server/index/serve-options.ts +86 -29
- package/src/server/index/spend-ledger-lifecycle.ts +66 -0
- package/src/server/index/websocket-handler.ts +6 -1
- package/src/server/index.ts +24 -25
- package/src/server/management/api-access.ts +3 -4
- package/src/server/management/aside-profile-routes.ts +266 -7
- package/src/server/management/config-routes.ts +48 -7
- package/src/server/management/context.ts +3 -0
- package/src/server/management/integration-routes.ts +287 -5
- package/src/server/management/metrics-routes.ts +20 -0
- package/src/server/management/model-rows.ts +224 -12
- package/src/server/management/provider-capability-config.ts +35 -7
- package/src/server/management/provider-routes.ts +70 -18
- package/src/server/management/route-registry.ts +14 -0
- package/src/server/management/shared.ts +10 -3
- package/src/server/management/system-restart.ts +7 -2
- package/src/server/management/system-routes.ts +2 -0
- package/src/server/management/usage-aggregate-cache.ts +4 -0
- package/src/server/management-api.ts +2 -0
- package/src/server/management-auth.ts +15 -1
- package/src/server/proxy-liveness.ts +97 -2
- package/src/server/readiness.ts +29 -10
- package/src/server/relay-eager.ts +24 -2
- package/src/server/relay.ts +136 -34
- package/src/server/request-log.ts +80 -3
- package/src/server/request-metrics.ts +236 -0
- package/src/server/responses/adapter-continuation.ts +74 -30
- package/src/server/responses/adapter-delivery.ts +39 -8
- package/src/server/responses/adapter-dispatch.ts +63 -30
- package/src/server/responses/compact.ts +89 -18
- package/src/server/responses/compaction-routing.ts +111 -0
- package/src/server/responses/core-codex-account.ts +90 -24
- package/src/server/responses/core-combo.ts +7 -7
- package/src/server/responses/core-normalize.ts +16 -15
- package/src/server/responses/core-opaque-recovery.ts +1 -0
- package/src/server/responses/core-options.ts +4 -0
- package/src/server/responses/encrypted-payload.ts +20 -2
- package/src/server/responses/fetch-helpers.ts +68 -2
- package/src/server/responses/passthrough-delivery.ts +41 -15
- package/src/server/responses/passthrough-dispatch.ts +145 -53
- package/src/server/responses/passthrough-execution.ts +11 -1
- package/src/server/responses/policy-fallback.ts +5 -13
- package/src/server/responses/request-prepare.ts +96 -17
- package/src/server/responses/request-send-budget.ts +89 -8
- package/src/server/responses/request-sidecar-auth.ts +17 -9
- package/src/server/responses/request-spend.ts +38 -9
- package/src/server/responses/request-transport.ts +15 -12
- package/src/server/responses/run-turn-execution.ts +45 -8
- package/src/server/responses/sidecar-execution.ts +19 -2
- package/src/server/responses/ws-upstream.ts +16 -28
- package/src/server/responses-custom-tool-repair.ts +29 -56
- package/src/server/responses-undeclared-tool-guard.ts +31 -1
- package/src/server/sse-frame-buffer.ts +12 -10
- package/src/server/sse-payload-rewrite.ts +37 -10
- package/src/server/system-env-shell.ts +5 -1
- package/src/server/system-env.ts +7 -1
- package/src/server/workflow-refusal.ts +56 -2
- package/src/service/cli.ts +16 -6
- package/src/service/guards.ts +10 -0
- package/src/service/health.ts +43 -0
- package/src/service/state.ts +7 -2
- package/src/tray/windows-tray.ps1 +155 -3
- package/src/types/accounts.ts +4 -0
- package/src/types/config.ts +111 -6
- package/src/types/provider.ts +37 -0
- package/src/types/request.ts +9 -1
- package/src/types/tools.ts +14 -0
- package/src/types/wire.ts +9 -1
- package/src/types.ts +1 -0
- package/src/usage/expected-prices.ts +28 -0
- package/src/usage/log.ts +87 -4
- package/src/vision/eligibility.ts +88 -9
- package/src/vision/plan.ts +34 -10
- package/src/web-search/executor.ts +41 -2
- package/src/web-search/loop.ts +6 -1
- package/src/web-search/passthrough-bridge.ts +39 -5
- package/gui/dist/assets/index-BbrHOIY0.js +0 -128
- package/gui/dist/assets/index-C5-RdDmD.css +0 -1
|
@@ -107,66 +107,96 @@ function usesUnicodePropertyEscape(pattern: string): boolean {
|
|
|
107
107
|
* preserved: loosening a nested constraint can instead reject an input in those contexts.
|
|
108
108
|
* Unsupported patterns there remain the destination's validation responsibility.
|
|
109
109
|
*
|
|
110
|
-
* Returns `node` itself when nothing was dropped.
|
|
111
|
-
*
|
|
110
|
+
* Returns `node` itself when nothing was dropped. Traversal keeps only the active path and clones
|
|
111
|
+
* only ancestors of a removed constraint, so a broad no-op schema does not create an output tree
|
|
112
|
+
* or one pending closure per sibling. The explicit stack still handles caller-controlled nesting
|
|
113
|
+
* depth; the separate Responses-only encrypted-marker normalization is unchanged.
|
|
112
114
|
*/
|
|
113
115
|
export function stripUnicodePropertyPatterns(node: unknown, inNameBag = false): unknown {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
116
|
+
interface Frame {
|
|
117
|
+
node: unknown[] | Record<string, unknown>;
|
|
118
|
+
inNameBag: boolean;
|
|
119
|
+
parent?: Frame;
|
|
120
|
+
parentKey?: string | number;
|
|
121
|
+
output?: unknown[] | Record<string, unknown>;
|
|
122
|
+
index?: number;
|
|
123
|
+
entries?: IterableIterator<[string, unknown]>;
|
|
124
|
+
}
|
|
120
125
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const
|
|
126
|
+
function * ownEntries(value: Record<string, unknown>): IterableIterator<[string, unknown]> {
|
|
127
|
+
// Unlike Object.entries(), this does not materialize every key/value pair before traversal.
|
|
128
|
+
for (const key in value) {
|
|
129
|
+
if (Object.prototype.hasOwnProperty.call(value, key)) yield [key, value[key]];
|
|
130
|
+
}
|
|
131
|
+
}
|
|
124
132
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
stack.push({ node: current[i], inNameBag: false, assign: value => { out[i] = value; } });
|
|
131
|
-
}
|
|
132
|
-
continue;
|
|
133
|
+
function cloneContainer(frame: Frame): unknown[] | Record<string, unknown> {
|
|
134
|
+
if (frame.output) return frame.output;
|
|
135
|
+
if (Array.isArray(frame.node)) {
|
|
136
|
+
frame.output = frame.node.slice();
|
|
137
|
+
return frame.output;
|
|
133
138
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
139
|
+
const output: Record<string, unknown> = Object.create(null) as Record<string, unknown>;
|
|
140
|
+
for (const key in frame.node) {
|
|
141
|
+
if (Object.prototype.hasOwnProperty.call(frame.node, key)) output[key] = frame.node[key];
|
|
137
142
|
}
|
|
143
|
+
frame.output = output;
|
|
144
|
+
return output;
|
|
145
|
+
}
|
|
138
146
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
frame.
|
|
147
|
+
function finish(frame: Frame): void {
|
|
148
|
+
if (!frame.output || !frame.parent) return;
|
|
149
|
+
const parent = cloneContainer(frame.parent);
|
|
150
|
+
if (Array.isArray(parent)) parent[frame.parentKey as number] = frame.output;
|
|
151
|
+
else parent[frame.parentKey as string] = frame.output;
|
|
152
|
+
}
|
|
142
153
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
dropped++;
|
|
154
|
+
if (!node || typeof node !== "object") return node;
|
|
155
|
+
const root: Frame = { node: node as unknown[] | Record<string, unknown>, inNameBag };
|
|
156
|
+
const stack: Frame[] = [root];
|
|
157
|
+
|
|
158
|
+
while (stack.length > 0) {
|
|
159
|
+
const frame = stack[stack.length - 1]!;
|
|
160
|
+
|
|
161
|
+
if (Array.isArray(frame.node)) {
|
|
162
|
+
const index = frame.index ?? 0;
|
|
163
|
+
if (index >= frame.node.length) {
|
|
164
|
+
stack.pop();
|
|
165
|
+
finish(frame);
|
|
156
166
|
continue;
|
|
157
167
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
168
|
+
frame.index = index + 1;
|
|
169
|
+
const child = frame.node[index];
|
|
170
|
+
if (child && typeof child === "object") {
|
|
171
|
+
stack.push({ node: child as unknown[] | Record<string, unknown>, inNameBag: false, parent: frame, parentKey: index });
|
|
162
172
|
}
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
frame.entries ??= ownEntries(frame.node);
|
|
177
|
+
const next = frame.entries.next();
|
|
178
|
+
if (next.done) {
|
|
179
|
+
stack.pop();
|
|
180
|
+
finish(frame);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const [key, value] = next.value;
|
|
184
|
+
if (!frame.inNameBag && key === "pattern" && typeof value === "string" && usesUnicodePropertyEscape(value)) {
|
|
185
|
+
delete (cloneContainer(frame) as Record<string, unknown>)[key];
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (!frame.inNameBag && (PRESERVED_PATTERN_SUBTREES.has(key) || SCHEMA_LITERAL_VALUE_KEYS.has(key))) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (value && typeof value === "object") {
|
|
163
192
|
stack.push({
|
|
164
|
-
node: value,
|
|
165
|
-
inNameBag: SCHEMA_NAME_BAG_KEYS.has(key),
|
|
166
|
-
|
|
193
|
+
node: value as unknown[] | Record<string, unknown>,
|
|
194
|
+
inNameBag: !frame.inNameBag && SCHEMA_NAME_BAG_KEYS.has(key),
|
|
195
|
+
parent: frame,
|
|
196
|
+
parentKey: key,
|
|
167
197
|
});
|
|
168
198
|
}
|
|
169
199
|
}
|
|
170
200
|
|
|
171
|
-
return
|
|
201
|
+
return root.output ?? node;
|
|
172
202
|
}
|
|
@@ -28,6 +28,7 @@ export interface AdapterEventPreflight {
|
|
|
28
28
|
stream: AsyncIterable<AdapterEvent>;
|
|
29
29
|
error?: Extract<AdapterEvent, { type: "error" }>;
|
|
30
30
|
empty: boolean;
|
|
31
|
+
replayUnsafe: boolean;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
async function* replay(
|
|
@@ -51,10 +52,12 @@ export async function preflightAdapterEvents(
|
|
|
51
52
|
): Promise<AdapterEventPreflight> {
|
|
52
53
|
const iterator = source[Symbol.asyncIterator]();
|
|
53
54
|
const buffered: AdapterEvent[] = [];
|
|
55
|
+
let replayUnsafe = false;
|
|
54
56
|
while (true) {
|
|
55
57
|
const next = await iterator.next();
|
|
56
|
-
if (next.done) return { stream: replay(buffered, iterator), empty: true };
|
|
58
|
+
if (next.done) return { stream: replay(buffered, iterator), empty: true, replayUnsafe };
|
|
57
59
|
if (next.value.type === "heartbeat") {
|
|
60
|
+
replayUnsafe ||= next.value.replayUnsafe === true;
|
|
58
61
|
buffered.push(next.value);
|
|
59
62
|
if (buffered.length > PREFLIGHT_HEARTBEAT_RETAIN_LIMIT) buffered.shift();
|
|
60
63
|
continue;
|
|
@@ -62,9 +65,9 @@ export async function preflightAdapterEvents(
|
|
|
62
65
|
buffered.push(next.value);
|
|
63
66
|
if (next.value.type === "error") {
|
|
64
67
|
await iterator.return?.();
|
|
65
|
-
return { stream: replay(buffered, iterator), error: next.value, empty: false };
|
|
68
|
+
return { stream: replay(buffered, iterator), error: next.value, empty: false, replayUnsafe };
|
|
66
69
|
}
|
|
67
|
-
return { stream: replay(buffered, iterator), empty: false };
|
|
70
|
+
return { stream: replay(buffered, iterator), empty: false, replayUnsafe };
|
|
68
71
|
}
|
|
69
72
|
}
|
|
70
73
|
|
|
@@ -90,7 +93,17 @@ export function createAdapterEventQueue(opts?: {
|
|
|
90
93
|
const coalesceIntoTail = (event: AdapterEvent): boolean => {
|
|
91
94
|
const tail = queued[queued.length - 1];
|
|
92
95
|
if (!tail) return false;
|
|
93
|
-
if (event.type === "heartbeat")
|
|
96
|
+
if (event.type === "heartbeat") {
|
|
97
|
+
if (tail.type !== "heartbeat") return false;
|
|
98
|
+
// Heartbeats carry no ordering between themselves, but the replay-unsafe
|
|
99
|
+
// marker is not ordering — it is a latch. Dropping the incoming event
|
|
100
|
+
// would discard the only record that Cursor already performed a local
|
|
101
|
+
// side effect, and preflight would then permit an OAuth replay of it.
|
|
102
|
+
if (event.replayUnsafe === true && tail.replayUnsafe !== true) {
|
|
103
|
+
queued[queued.length - 1] = { type: "heartbeat", replayUnsafe: true };
|
|
104
|
+
}
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
94
107
|
if (event.type === "text_delta" && tail.type === "text_delta" && tail.phase === event.phase) {
|
|
95
108
|
if (tail.text.length + event.text.length > COALESCE_MAX_CHUNK_LENGTH) return false;
|
|
96
109
|
queued[queued.length - 1] = { type: "text_delta", text: tail.text + event.text, phase: tail.phase };
|
|
@@ -203,7 +203,7 @@ function buildResponseJSONWithBudget(
|
|
|
203
203
|
): string => {
|
|
204
204
|
const helper = resolveCodeModeHelperName(codeModeHelperName, toolName, args, namespace, options?.declaredToolNames);
|
|
205
205
|
return helper
|
|
206
|
-
? compileCodeModeHelperInput(args, helper)
|
|
206
|
+
? compileCodeModeHelperInput(args, helper, codeModeHelperName ?? toolName)
|
|
207
207
|
: repairFreeformToolInput(args, toolName, namespace);
|
|
208
208
|
};
|
|
209
209
|
const parseArgsObj = (args: string): Record<string, unknown> => {
|
package/src/bridge/sse.ts
CHANGED
|
@@ -16,7 +16,11 @@ import {
|
|
|
16
16
|
type OcxErrorPayload,
|
|
17
17
|
} from "../lib/errors";
|
|
18
18
|
import { redactSecretString } from "../lib/redact";
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
mayBecomePatchEnvelope,
|
|
21
|
+
repairFreeformToolInput,
|
|
22
|
+
} from "../responses/apply-patch-envelope";
|
|
23
|
+
import { progressiveFreeformInput } from "../responses/progressive-freeform-input";
|
|
20
24
|
import { encodeCompactionSummary } from "../responses/compaction";
|
|
21
25
|
import { compileCodeModeHelperInput, resolveCodeModeHelperName } from "../responses/code-mode-helper-compat";
|
|
22
26
|
import { isTruncatedStopReason, truncationReasonFor } from "../responses/truncated-stop-reason";
|
|
@@ -145,37 +149,9 @@ export function bridgeToResponsesSSE(
|
|
|
145
149
|
): string => {
|
|
146
150
|
const helper = resolveCodeModeHelperName(codeModeHelperName, toolName, args, namespace, options?.declaredToolNames);
|
|
147
151
|
return helper
|
|
148
|
-
? compileCodeModeHelperInput(args, helper)
|
|
152
|
+
? compileCodeModeHelperInput(args, helper, codeModeHelperName ?? toolName)
|
|
149
153
|
: repairFreeformToolInput(args, toolName, namespace);
|
|
150
154
|
};
|
|
151
|
-
// Best-effort unwrap of a PARTIAL freeform arg buffer for live input streaming
|
|
152
|
-
// (`response.custom_tool_call_input.delta` — codex-rs uses it for UI preview only;
|
|
153
|
-
// the completed custom_tool_call item stays authoritative). Compact `{"input":"...`
|
|
154
|
-
// buffers get their string value progressively unescaped; anything else streams raw.
|
|
155
|
-
const FREEFORM_WRAP_PREFIX = '{"input":"';
|
|
156
|
-
const freeformPartialInput = (args: string): string => {
|
|
157
|
-
if (!args.startsWith(FREEFORM_WRAP_PREFIX)) return args;
|
|
158
|
-
const body = args.slice(FREEFORM_WRAP_PREFIX.length);
|
|
159
|
-
let out = "";
|
|
160
|
-
for (let i = 0; i < body.length; i++) {
|
|
161
|
-
const c = body[i];
|
|
162
|
-
if (c === '"') break; // unescaped closing quote: value complete
|
|
163
|
-
if (c === "\\") {
|
|
164
|
-
const n = body[i + 1];
|
|
165
|
-
if (n === undefined) break; // escape split across chunks: wait for more
|
|
166
|
-
i++;
|
|
167
|
-
if (n === "n") out += "\n";
|
|
168
|
-
else if (n === "t") out += "\t";
|
|
169
|
-
else if (n === "r") out += "\r";
|
|
170
|
-
else if (n === "u") {
|
|
171
|
-
const hex = body.slice(i + 1, i + 5);
|
|
172
|
-
if (hex.length === 4 && /^[0-9a-fA-F]{4}$/.test(hex)) { out += String.fromCharCode(parseInt(hex, 16)); i += 4; }
|
|
173
|
-
else break; // incomplete \uXXXX: wait for more
|
|
174
|
-
} else out += n; // \" \\ \/ etc.
|
|
175
|
-
} else out += c;
|
|
176
|
-
}
|
|
177
|
-
return out;
|
|
178
|
-
};
|
|
179
155
|
// tool_search_call carries arguments as a JSON object ({query, limit}); parse the model's arg string.
|
|
180
156
|
const parseArgsObj = (args: string): Record<string, unknown> => {
|
|
181
157
|
try { const o = JSON.parse(args); return o && typeof o === "object" ? o : {}; } catch { return {}; }
|
|
@@ -1075,10 +1051,21 @@ export function bridgeToResponsesSSE(
|
|
|
1075
1051
|
});
|
|
1076
1052
|
}
|
|
1077
1053
|
if (currentToolCall.freeform && !currentToolCall.codeModeHelperName) {
|
|
1078
|
-
//
|
|
1079
|
-
//
|
|
1080
|
-
|
|
1081
|
-
|
|
1054
|
+
// `progressiveFreeformInput` holds while the buffer is still an ambiguous prefix
|
|
1055
|
+
// of a JSON wrapper; otherwise stream only the unwrapped input suffix, never
|
|
1056
|
+
// rewinding on a mode flip.
|
|
1057
|
+
//
|
|
1058
|
+
// The name is dropped for a namespaced tool that does not own the apply-patch
|
|
1059
|
+
// grammar, because `repairFreeformToolInput` drops it at completion for the
|
|
1060
|
+
// same reason. Streaming under a vocabulary the completed item does not use
|
|
1061
|
+
// is the same disagreement in the other direction.
|
|
1062
|
+
const ownsFreeformGrammar = currentToolCall.namespace === undefined
|
|
1063
|
+
|| currentToolCall.namespace === "functions";
|
|
1064
|
+
const full = progressiveFreeformInput(
|
|
1065
|
+
currentToolCall.args,
|
|
1066
|
+
ownsFreeformGrammar ? currentToolCall.name : "",
|
|
1067
|
+
);
|
|
1068
|
+
if (full !== null) {
|
|
1082
1069
|
const emitted = currentToolCall.inputEmitted ?? "";
|
|
1083
1070
|
// Also hold a buffer that could still become a complete patch envelope:
|
|
1084
1071
|
// at completion such a body is recompiled into an apply_patch helper call,
|
|
@@ -1086,7 +1073,13 @@ export function bridgeToResponsesSSE(
|
|
|
1086
1073
|
const mayCompile = declaresCodeModeExec(options?.declaredToolNames)
|
|
1087
1074
|
&& !currentToolCall.namespace
|
|
1088
1075
|
&& currentToolCall.name === "exec";
|
|
1089
|
-
|
|
1076
|
+
// `apply_patch` holds for a different reason with the same shape:
|
|
1077
|
+
// `normalizeApplyPatchDelimiters` rewrites a decorated `*** Begin Patch ***`
|
|
1078
|
+
// envelope at completion, so streaming the decorated markers would be
|
|
1079
|
+
// replaced by the normalized ones.
|
|
1080
|
+
const mayNormalize = ownsFreeformGrammar && currentToolCall.name === "apply_patch";
|
|
1081
|
+
if (!((mayCompile || mayNormalize) && mayBecomePatchEnvelope(full))
|
|
1082
|
+
&& full.startsWith(emitted) && full.length > emitted.length) {
|
|
1090
1083
|
emit("response.custom_tool_call_input.delta", {
|
|
1091
1084
|
item_id: currentToolCall.itemId, output_index: currentToolCall.outputIndex,
|
|
1092
1085
|
delta: full.slice(emitted.length),
|
|
@@ -86,6 +86,28 @@ export function resolveAutoContext(claudeCode: AutoContextConfigSlice | undefine
|
|
|
86
86
|
return { enabled: true, compactWindow };
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Config value -> ENABLE_TOOL_SEARCH wire value (#4838).
|
|
91
|
+
*
|
|
92
|
+
* Claude Code parses this variable itself and accepts more than a boolean:
|
|
93
|
+
* "true", "auto", "auto:N" (N is a percentage floor for non-deferred tools; its
|
|
94
|
+
* parser rejects anything under 100) and "force", which also overrides an
|
|
95
|
+
* OS-level managed-settings suppression. Passing a string through verbatim keeps
|
|
96
|
+
* that whole vocabulary reachable instead of flattening it to a boolean that
|
|
97
|
+
* would have to be re-expanded here later.
|
|
98
|
+
*
|
|
99
|
+
* `false`, absent and blank inject nothing rather than injecting "false". Every
|
|
100
|
+
* caller injects with user-wins semantics, so an operator's own export survives
|
|
101
|
+
* either way, and writing "false" would be the one path that could quietly
|
|
102
|
+
* disagree with it.
|
|
103
|
+
*/
|
|
104
|
+
export function claudeToolSearchEnv(value: boolean | string | undefined): string | undefined {
|
|
105
|
+
if (value === true) return "true";
|
|
106
|
+
if (typeof value !== "string") return undefined;
|
|
107
|
+
const trimmed = value.trim();
|
|
108
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
109
|
+
}
|
|
110
|
+
|
|
89
111
|
/**
|
|
90
112
|
* [1m]-marking predicate. Windows >= 1M always mark (CLI accounts exactly 1M).
|
|
91
113
|
* Auto-context additionally marks windows > 200k that can safely host the compact
|
package/src/claude/outbound.ts
CHANGED
|
@@ -189,7 +189,27 @@ function webSearchPairFromItem(item: Rec): { id: string; input: Rec; resultConte
|
|
|
189
189
|
return { id, input, resultContent, completed };
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
|
|
192
|
+
/**
|
|
193
|
+
* `inputTokenFloor` is this proxy's own count of the prompt it forwarded, used only when the
|
|
194
|
+
* upstream sent no confirmed usage before the first frame.
|
|
195
|
+
*
|
|
196
|
+
* Real Anthropic fills `message_start.message.usage.input_tokens` with the turn's prompt size,
|
|
197
|
+
* and third-party clients read it there — Paseo's context meter takes input from this frame and
|
|
198
|
+
* output from `message_delta`, so a hardcoded zero showed a nearly-empty ring for a turn whose
|
|
199
|
+
* `/context` reported ~97k (#4857). #4891 fixed the destinations that report usage up front;
|
|
200
|
+
* the internal bridge attaches `usage: null` to its lifecycle frames, so those paths had
|
|
201
|
+
* nothing to report and kept sending zero.
|
|
202
|
+
*
|
|
203
|
+
* A floor is a measurement, which is what makes it publishable here: it counts the prompt this
|
|
204
|
+
* proxy actually sent, the same estimate `claude-messages.ts` already trusts as a log floor. It
|
|
205
|
+
* is not a claim about upstream's tokenizer, and it is not final — `message_delta` carries the
|
|
206
|
+
* authoritative count for every reader that waits for it, exactly as before.
|
|
207
|
+
*/
|
|
208
|
+
function messageSnapshot(model: string, confirmedUsage?: Rec, inputTokenFloor?: number): Rec {
|
|
209
|
+
const usage = confirmedUsage
|
|
210
|
+
?? (typeof inputTokenFloor === "number" && Number.isFinite(inputTokenFloor) && inputTokenFloor > 0
|
|
211
|
+
? { input_tokens: Math.trunc(inputTokenFloor), output_tokens: 0 }
|
|
212
|
+
: { input_tokens: 0, output_tokens: 0 });
|
|
193
213
|
return {
|
|
194
214
|
id: `msg_${uuid()}`,
|
|
195
215
|
type: "message",
|
|
@@ -198,7 +218,7 @@ function messageSnapshot(model: string, confirmedUsage?: Rec): Rec {
|
|
|
198
218
|
model,
|
|
199
219
|
stop_reason: null,
|
|
200
220
|
stop_sequence: null,
|
|
201
|
-
usage
|
|
221
|
+
usage,
|
|
202
222
|
};
|
|
203
223
|
}
|
|
204
224
|
|
|
@@ -228,7 +248,15 @@ interface OpenBlock {
|
|
|
228
248
|
export function responsesSseToAnthropicSse(
|
|
229
249
|
upstream: ReadableStream<Uint8Array>,
|
|
230
250
|
model: string,
|
|
231
|
-
opts: {
|
|
251
|
+
opts: {
|
|
252
|
+
pingIntervalMs?: number;
|
|
253
|
+
translatorBudget: TranslatorBudget;
|
|
254
|
+
/**
|
|
255
|
+
* This proxy's count of the prompt it forwarded, published on `message_start` only when the
|
|
256
|
+
* upstream sent no confirmed usage before the first frame. See `messageSnapshot` (#4857).
|
|
257
|
+
*/
|
|
258
|
+
inputTokenFloor?: number;
|
|
259
|
+
},
|
|
232
260
|
): ReadableStream<Uint8Array> {
|
|
233
261
|
const translatorBudget = opts.translatorBudget;
|
|
234
262
|
const pingIntervalMs = opts?.pingIntervalMs ?? 20_000;
|
|
@@ -283,7 +311,10 @@ export function responsesSseToAnthropicSse(
|
|
|
283
311
|
const ensureStarted = () => {
|
|
284
312
|
if (started) return;
|
|
285
313
|
started = true;
|
|
286
|
-
emit("message_start", {
|
|
314
|
+
emit("message_start", {
|
|
315
|
+
type: "message_start",
|
|
316
|
+
message: messageSnapshot(model, earlyAnthropicUsage, opts.inputTokenFloor),
|
|
317
|
+
});
|
|
287
318
|
emit("ping", { type: "ping" });
|
|
288
319
|
};
|
|
289
320
|
// Keepalive pings protect remote deployments behind LB/NAT idle timeouts even
|
package/src/cli/account-api.ts
CHANGED
|
@@ -370,9 +370,10 @@ async function fetchOAuthRows(
|
|
|
370
370
|
email: a.email,
|
|
371
371
|
active: a.active ?? a.id === activeId,
|
|
372
372
|
needsReauth: a.needsReauth,
|
|
373
|
-
// Forward the server's answer verbatim
|
|
374
|
-
//
|
|
375
|
-
|
|
373
|
+
// Forward the server's answer verbatim. An absent key means the proxy predates tier
|
|
374
|
+
// reporting while `null` means it checked and found no tier — collapsing either
|
|
375
|
+
// direction would destroy the one distinction this field exists to make.
|
|
376
|
+
...(Object.hasOwn(a, "plan") ? { plan: a.plan } : {}),
|
|
376
377
|
...(a.quota !== undefined ? { quota: a.quota } : {}),
|
|
377
378
|
...(a.quotaUnavailable !== undefined ? { quotaUnavailable: a.quotaUnavailable } : {}),
|
|
378
379
|
...(a.quotaUnavailable === true && parseQuotaFailureCode(a.quotaFailure)
|
|
@@ -403,16 +403,16 @@ export async function cmdAutoSwitch(args: string[], deps: AccountDeps): Promise<
|
|
|
403
403
|
// shared kernel, and absent is a server that does not speak this field at all. Collapsing
|
|
404
404
|
// false into absent would render the live feature as an unknown capability.
|
|
405
405
|
const inert = typeof settings.inert === "boolean" ? settings.inert : null;
|
|
406
|
-
// A stored threshold only steers selection once the pool consumes it, which is
|
|
407
|
-
// what `inert: false` reports.
|
|
408
|
-
const enabled = inert === false && storedThreshold !== null;
|
|
406
|
+
// A positive stored threshold only steers selection once the pool consumes it, which is
|
|
407
|
+
// exactly what `inert: false` reports. Zero remains the explicit disabled value.
|
|
408
|
+
const enabled = inert === false && storedThreshold !== null && storedThreshold > 0;
|
|
409
409
|
if (wantsJson) {
|
|
410
410
|
console.log(JSON.stringify({ provider: name, autoSwitchThreshold: storedThreshold, enabled, poolEnabled, inert }, null, 2));
|
|
411
411
|
} else {
|
|
412
412
|
const value = storedThreshold === null ? "unset" : `${storedThreshold}%`;
|
|
413
413
|
const state = inert === false ? (enabled ? "on" : "off") : inert === true ? "inactive" : "unavailable";
|
|
414
414
|
const why = inert === false
|
|
415
|
-
? (enabled ? "applied by this pool" : "no threshold stored")
|
|
415
|
+
? (enabled ? "applied by this pool" : storedThreshold === 0 ? "usage-based switching disabled" : "no threshold stored")
|
|
416
416
|
: inert === true ? "not applied by this pool" : "threshold support is unknown";
|
|
417
417
|
console.log(`auto-switch: ${state} (stored threshold ${value}; ${why})`);
|
|
418
418
|
}
|
|
@@ -925,12 +925,32 @@ async function poolSetting(
|
|
|
925
925
|
if (response.status !== 200) return apiError(response.json, `failed to read ${label}`, response.status);
|
|
926
926
|
const strategy = response.json[transport.strategyKey];
|
|
927
927
|
const sticky = response.json[transport.stickyKey];
|
|
928
|
+
const autoSwitchThreshold = typeof response.json.autoSwitchThreshold === "number"
|
|
929
|
+
? response.json.autoSwitchThreshold
|
|
930
|
+
: undefined;
|
|
928
931
|
if (wantsJson) {
|
|
929
932
|
// Pool-neutral key names: the two routes spell the same two settings differently, and a
|
|
930
933
|
// `--json` consumer should not have to branch on which pool answered.
|
|
931
|
-
|
|
934
|
+
const payload: Record<string, unknown> = { ok: true, provider: name, strategy, stickyLimit: sticky };
|
|
935
|
+
if (autoSwitchThreshold !== undefined) {
|
|
936
|
+
payload.autoSwitchThreshold = autoSwitchThreshold;
|
|
937
|
+
}
|
|
938
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
932
939
|
} else {
|
|
933
|
-
|
|
940
|
+
if (field === "strategy" && autoSwitchThreshold !== undefined) {
|
|
941
|
+
const thresholdSummary = strategy === "round-robin"
|
|
942
|
+
? "threshold not used"
|
|
943
|
+
: autoSwitchThreshold > 0
|
|
944
|
+
? (strategy === "fill-first"
|
|
945
|
+
? `drain at ${autoSwitchThreshold}%`
|
|
946
|
+
: strategy === "reset-first"
|
|
947
|
+
? `nearest reset below ${autoSwitchThreshold}%`
|
|
948
|
+
: `switch at ${autoSwitchThreshold}%`)
|
|
949
|
+
: "proactive switching off";
|
|
950
|
+
console.log(`${name}: ${label} is ${String(strategy)} (${thresholdSummary})`);
|
|
951
|
+
} else {
|
|
952
|
+
console.log(`${name}: ${label} is ${String(field === "strategy" ? strategy : sticky)}`);
|
|
953
|
+
}
|
|
934
954
|
}
|
|
935
955
|
return 0;
|
|
936
956
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { importOrcaAccounts } from "../codex/orca-import";
|
|
2
|
+
|
|
3
|
+
const USAGE = "Usage: ocx account import-orca --source <orca-data-directory> --registry <orca-data.json> [--apply] [--json]";
|
|
4
|
+
|
|
5
|
+
export interface OrcaImportCommandDeps {
|
|
6
|
+
importAccounts?: typeof importOrcaAccounts;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Local-only: never sends source paths or credentials to a management listener. */
|
|
10
|
+
export async function cmdOrcaImport(args: string[], deps: OrcaImportCommandDeps = {}): Promise<number> {
|
|
11
|
+
let sourceDir: string | undefined;
|
|
12
|
+
let registryPath: string | undefined;
|
|
13
|
+
let apply = false;
|
|
14
|
+
const wantsJson = args.includes("--json");
|
|
15
|
+
const seen = new Set<string>();
|
|
16
|
+
for (let index = 0; index < args.length; index++) {
|
|
17
|
+
const arg = args[index]!;
|
|
18
|
+
if (seen.has(arg) || !["--source", "--registry", "--apply", "--json"].includes(arg)) {
|
|
19
|
+
console.error(USAGE);
|
|
20
|
+
return 1;
|
|
21
|
+
}
|
|
22
|
+
seen.add(arg);
|
|
23
|
+
if (arg === "--source" || arg === "--registry") {
|
|
24
|
+
const value = args[++index];
|
|
25
|
+
if (!value || value.startsWith("--")) {
|
|
26
|
+
console.error(USAGE);
|
|
27
|
+
return 1;
|
|
28
|
+
}
|
|
29
|
+
if (arg === "--source") sourceDir = value;
|
|
30
|
+
else registryPath = value;
|
|
31
|
+
} else if (arg === "--apply") apply = true;
|
|
32
|
+
}
|
|
33
|
+
if (!sourceDir || !registryPath) {
|
|
34
|
+
console.error(USAGE);
|
|
35
|
+
return 1;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const run = deps.importAccounts ?? (await import("../codex/orca-import")).importOrcaAccounts;
|
|
39
|
+
const result = await run({ sourceDir, registryPath, apply });
|
|
40
|
+
if (wantsJson) console.log(JSON.stringify({
|
|
41
|
+
mode: result.mode, discovered: result.discovered, eligible: result.eligible,
|
|
42
|
+
imported: result.imported, duplicates: result.duplicates, invalid: result.invalid,
|
|
43
|
+
invalidReasons: result.invalidReasons,
|
|
44
|
+
}));
|
|
45
|
+
else {
|
|
46
|
+
console.log(`Orca: ${result.discovered} discovered, ${result.eligible} eligible, ${result.imported} imported, ${result.duplicates} duplicates, ${result.invalid} invalid.`);
|
|
47
|
+
if (result.invalid > 0) {
|
|
48
|
+
const reasons = Object.entries(result.invalidReasons).map(([reason, count]) => `${reason}=${count}`).join(", ");
|
|
49
|
+
console.log(`Invalid reasons: ${reasons}`);
|
|
50
|
+
}
|
|
51
|
+
console.log(apply
|
|
52
|
+
? "Start the proxy and use Refresh quotas in Codex Auth to validate new accounts. Orca retains refresh ownership."
|
|
53
|
+
: "Preview only. Stop the proxy before repeating with --apply. Orca authentication files stay read-only.");
|
|
54
|
+
}
|
|
55
|
+
return result.invalid > 0 && result.eligible === 0 ? 1 : 0;
|
|
56
|
+
} catch {
|
|
57
|
+
// Filesystem/JSON exceptions may contain source paths or token fragments.
|
|
58
|
+
const error = "Orca import failed. Check the source, destination config, and stopped proxy; no successful completion is confirmed.";
|
|
59
|
+
if (wantsJson) console.log(JSON.stringify({ error: "orca_import_failed" }));
|
|
60
|
+
else console.error(error);
|
|
61
|
+
return 1;
|
|
62
|
+
}
|
|
63
|
+
}
|
package/src/cli/account.ts
CHANGED
|
@@ -57,6 +57,7 @@ const ACCOUNT_USAGE = `Usage:
|
|
|
57
57
|
ocx account clear-cooldown <provider> <account-id|main> [--json]
|
|
58
58
|
ocx account add-key <provider> [--label <label>] [--json]
|
|
59
59
|
ocx account import <provider> --format <format> (--file <path>|--stdin) [--json]
|
|
60
|
+
ocx account import-orca --source <orca-data-directory> --registry <orca-data.json> [--apply] [--json]
|
|
60
61
|
ocx account login <provider> [--id <account-id>] [--reauth] [--code -] [--no-wait] [--json]
|
|
61
62
|
ocx account code <provider> [--flow <flow-id>] [--json] (reads the code from stdin)
|
|
62
63
|
ocx account cancel <provider> [--flow <flow-id>] [--json]
|
|
@@ -323,13 +324,36 @@ async function cmdUse(rest: string[], deps: AccountDeps): Promise<number> {
|
|
|
323
324
|
if (res.status === 0) return proxyUnreachable(res.transportError);
|
|
324
325
|
if (res.status !== 200) return apiError(res.json, `failed to switch ${name}`, res.status);
|
|
325
326
|
|
|
326
|
-
|
|
327
|
-
|
|
327
|
+
// The route reports this only when routing would drop the pin it just recorded, so an
|
|
328
|
+
// absent field means the pin survives (#4521).
|
|
329
|
+
const pinDrainReason = typeof res.json.pinDrainReason === "string" ? res.json.pinDrainReason : undefined;
|
|
330
|
+
if (wantsJson) {
|
|
331
|
+
console.log(JSON.stringify({
|
|
332
|
+
ok: true,
|
|
333
|
+
provider: name,
|
|
334
|
+
type: c.type,
|
|
335
|
+
activeId,
|
|
336
|
+
...(pinDrainReason !== undefined ? { pinDrained: true, pinDrainReason } : {}),
|
|
337
|
+
}, null, 2));
|
|
338
|
+
} else {
|
|
339
|
+
console.log(`${name}: active ${c.type === "api-key" ? "key" : "account"} is now ${displayId(activeId)}`);
|
|
340
|
+
}
|
|
328
341
|
if (c.type === "codex") {
|
|
329
342
|
console.error("Takes effect immediately; running threads move on their next request, and in-flight requests keep the account they captured.");
|
|
330
343
|
const active = await apiJson(deps, baseUrl, "GET", "/api/codex-auth/active");
|
|
331
|
-
|
|
332
|
-
|
|
344
|
+
const threshold = active.status === 200 && typeof active.json.autoSwitchThreshold === "number"
|
|
345
|
+
? active.json.autoSwitchThreshold
|
|
346
|
+
: undefined;
|
|
347
|
+
if (pinDrainReason !== undefined) {
|
|
348
|
+
// "may override" is the right caveat for a pin that is currently fine and could be
|
|
349
|
+
// overtaken later. It is the wrong sentence for one the next request will discard, and
|
|
350
|
+
// printing only that is what left the operator believing the account was pinned.
|
|
351
|
+
const because = pinDrainReason === "quota_threshold"
|
|
352
|
+
? `is at or above the auto-switch threshold${threshold !== undefined ? ` (${threshold}%)` : ""}`
|
|
353
|
+
: `cannot currently be selected (${pinDrainReason})`;
|
|
354
|
+
console.error(`Note: ${displayId(activeId)} ${because}, so routing releases this pin on its next request.`);
|
|
355
|
+
} else if (threshold !== undefined && threshold > 0) {
|
|
356
|
+
console.error(`Note: auto-switch (threshold ${threshold}%) may override this pin.`);
|
|
333
357
|
}
|
|
334
358
|
}
|
|
335
359
|
return 0;
|
|
@@ -360,6 +384,10 @@ export async function cmdAccount(args: string[], deps: AccountDeps = {}): Promis
|
|
|
360
384
|
if (sub === "clear-cooldown") return await cmdClearCooldown(rest, deps);
|
|
361
385
|
if (sub === "add-key") return await cmdAddKey(rest, deps);
|
|
362
386
|
if (sub === "import") return await cmdImport(rest, deps);
|
|
387
|
+
if (sub === "import-orca") {
|
|
388
|
+
const { cmdOrcaImport } = await import("./account-orca-import");
|
|
389
|
+
return await cmdOrcaImport(rest);
|
|
390
|
+
}
|
|
363
391
|
if (sub === "main") {
|
|
364
392
|
const { cmdNativeMainAccount } = await import("./account-main");
|
|
365
393
|
return await cmdNativeMainAccount(rest, deps);
|