@bitkyc08/opencodex 2.76.0 → 2.78.0-preview.20261005
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/bin/ocx.mjs +11 -10
- package/gui/dist/assets/App-CppMz-6w.js +54 -0
- package/gui/dist/assets/App-lCIslZ1L.css +1 -0
- package/gui/dist/assets/{Tray-DjNwwrWS.js → Tray-CJWQqtVv.js} +1 -1
- package/gui/dist/assets/index-DQt9srNi.js +94 -0
- package/gui/dist/assets/index-DmQB8D_R.css +1 -0
- package/gui/dist/assets/quota-summary-BMsyoZH2.js +1 -0
- package/gui/dist/index.html +4 -3
- package/package.json +1 -1
- package/src/adapters/anthropic/beta-allowlist.ts +19 -2
- package/src/adapters/anthropic/client-identity.ts +13 -4
- package/src/adapters/anthropic/native-client-preamble.ts +31 -0
- package/src/adapters/anthropic/passthrough.ts +103 -25
- package/src/adapters/antigravity-refusal.ts +28 -0
- package/src/adapters/cursor/protobuf-request.ts +6 -0
- package/src/adapters/cursor/request-builder.ts +3 -0
- package/src/adapters/cursor/structured-output.ts +13 -0
- package/src/adapters/cursor/types.ts +2 -0
- package/src/adapters/devin/cloud-direct/chat.ts +7 -2
- package/src/adapters/devin/trajectory.ts +49 -0
- package/src/adapters/devin.ts +13 -0
- package/src/adapters/ollama-native.ts +62 -19
- package/src/adapters/openai-responses/passthrough.ts +133 -103
- package/src/adapters/openai-responses/request-strips.ts +10 -4
- package/src/adapters/zed.ts +26 -4
- package/src/bridge/response-json.ts +14 -14
- package/src/bridge/sse.ts +15 -5
- package/src/chatgpt/app-server-shim/bundle-trust.ts +40 -10
- package/src/chatgpt/app-server-shim/gate-rewrite.ts +3 -0
- package/src/claude/agents-inject.ts +4 -43
- package/src/claude/gateway-cache.ts +16 -13
- package/src/claude/inbound-model-options.ts +9 -2
- package/src/claude/intercept/cli-catalog.ts +27 -16
- package/src/claude/intercept/client-hello.ts +103 -0
- package/src/claude/intercept/model-bindings.ts +3 -2
- package/src/claude/intercept/picker-bootstrap.ts +37 -16
- package/src/claude/intercept/picker-budget.ts +41 -0
- package/src/claude/intercept/picker-listener.ts +279 -66
- package/src/claude/outbound.ts +2 -0
- package/src/claude/subagent-force-status.ts +53 -0
- package/src/claude/subagent-model.ts +93 -0
- package/src/cli/access-audio-input.ts +84 -0
- package/src/cli/access-audio-live.ts +99 -0
- package/src/cli/access-audio.ts +123 -0
- package/src/cli/access-data-client.ts +127 -0
- package/src/cli/access-data-plane.ts +104 -0
- package/src/cli/access-data-response.ts +76 -0
- package/src/cli/access.ts +65 -2
- package/src/cli/account-api.ts +33 -6
- package/src/cli/account-auth.ts +111 -42
- package/src/cli/account-key-quota.ts +118 -0
- package/src/cli/account-main.ts +11 -4
- package/src/cli/account-next-actions.ts +63 -0
- package/src/cli/account-policy-dto.ts +68 -0
- package/src/cli/account-policy.ts +167 -0
- package/src/cli/account-target.ts +8 -0
- package/src/cli/account.ts +37 -9
- package/src/cli/agent-runtime-settings.ts +130 -0
- package/src/cli/agent-settings.ts +101 -0
- package/src/cli/agent.ts +29 -2
- package/src/cli/alias.ts +7 -2
- package/src/cli/aside-profiles.ts +2 -2
- package/src/cli/capabilities-access-remote.ts +147 -0
- package/src/cli/capabilities-accounts.ts +195 -0
- package/src/cli/capabilities-agents-routing.ts +414 -0
- package/src/cli/capabilities-base.ts +783 -0
- package/src/cli/capabilities-command.ts +1 -0
- package/src/cli/capabilities-integrations.ts +264 -0
- package/src/cli/capabilities-lab.ts +214 -0
- package/src/cli/capabilities-observe-system.ts +662 -0
- package/src/cli/capabilities-provider-models.ts +302 -0
- package/src/cli/capabilities.ts +30 -1190
- package/src/cli/capability-types.ts +54 -0
- package/src/cli/catalog-command-result.ts +57 -0
- package/src/cli/chatgpt-command.ts +35 -9
- package/src/cli/claude-agent-startup-sync.ts +17 -0
- package/src/cli/claude-desktop-profile.ts +100 -0
- package/src/cli/claude-desktop.ts +39 -6
- package/src/cli/claude.ts +22 -6
- package/src/cli/codex-shim-autorestore.ts +2 -1
- package/src/cli/codex-shim-readiness.ts +109 -0
- package/src/cli/combo-input.ts +88 -0
- package/src/cli/combo-result.ts +52 -0
- package/src/cli/combo-stats.ts +123 -0
- package/src/cli/combo.ts +37 -14
- package/src/cli/companion-timeline.ts +142 -0
- package/src/cli/companion-usage.ts +146 -0
- package/src/cli/companion.ts +12 -3
- package/src/cli/config-command.ts +24 -8
- package/src/cli/connect.ts +19 -0
- package/src/cli/dispatch.ts +55 -82
- package/src/cli/doctor.ts +22 -4
- package/src/cli/export-command.ts +20 -9
- package/src/cli/gui.ts +8 -3
- package/src/cli/help-catalog.ts +101 -0
- package/src/cli/help-models-context.ts +17 -0
- package/src/cli/help-navigation.ts +50 -0
- package/src/cli/help-recovery.ts +66 -0
- package/src/cli/help.ts +69 -11
- package/src/cli/index.ts +38 -27
- package/src/cli/injection-follow.ts +35 -0
- package/src/cli/inspect.ts +5 -0
- package/src/cli/integration-aside-sync.ts +69 -0
- package/src/cli/integration-cursor.ts +106 -0
- package/src/cli/integration-input.ts +60 -0
- package/src/cli/integration-journal.ts +49 -0
- package/src/cli/integration-plan-dto.ts +156 -0
- package/src/cli/integration-preview.ts +215 -0
- package/src/cli/integrations.ts +24 -18
- package/src/cli/json-input.ts +134 -0
- package/src/cli/link.ts +32 -4
- package/src/cli/local-sync-result.ts +41 -0
- package/src/cli/log-follow.ts +84 -0
- package/src/cli/log-view-filter.ts +146 -0
- package/src/cli/logout-command.ts +44 -0
- package/src/cli/model-picker-ordering.ts +137 -0
- package/src/cli/models-custom-input.ts +107 -0
- package/src/cli/models-custom-runtime.ts +112 -0
- package/src/cli/models-order.ts +194 -0
- package/src/cli/models-runtime-subcommands.ts +2 -0
- package/src/cli/models-runtime.ts +9 -1
- package/src/cli/models.ts +66 -81
- package/src/cli/observe-stream.ts +142 -0
- package/src/cli/observe.ts +75 -34
- package/src/cli/opencode.ts +54 -10
- package/src/cli/provider-batch.ts +77 -0
- package/src/cli/provider-lifecycle-runtime.ts +170 -0
- package/src/cli/provider-result.ts +124 -0
- package/src/cli/provider-runtime.ts +15 -12
- package/src/cli/provider-settings.ts +143 -0
- package/src/cli/provider.ts +116 -64
- package/src/cli/registry.ts +90 -24
- package/src/cli/remote-workspace-hub.ts +83 -0
- package/src/cli/remote-workspace.ts +6 -1
- package/src/cli/restart-failure.ts +27 -0
- package/src/cli/root.ts +42 -9
- package/src/cli/route-policy-write.ts +137 -0
- package/src/cli/route-policy.ts +8 -0
- package/src/cli/runtime-api.ts +57 -80
- package/src/cli/secret-args.ts +149 -0
- package/src/cli/settings-result.ts +30 -0
- package/src/cli/storage.ts +77 -7
- package/src/cli/system-command.ts +60 -2
- package/src/cli/system-restart-client.ts +20 -3
- package/src/cli/system-settings-parity.ts +83 -0
- package/src/cli/tray-proxy.ts +3 -2
- package/src/cli/update-restart-candidate.ts +21 -0
- package/src/cli/update-restart-child.ts +81 -0
- package/src/cli/update-restart-home.ts +38 -0
- package/src/cli/update-restart-transport.ts +193 -0
- package/src/cli/update-restart.ts +260 -0
- package/src/cli/usage-model-search.ts +58 -0
- package/src/cli/usage-report.ts +13 -5
- package/src/cli/v2-input.ts +66 -0
- package/src/cli/v2-local-output.ts +37 -0
- package/src/cli/v2-runtime.ts +90 -0
- package/src/cli/v2.ts +102 -144
- package/src/clients/config-export/contracts.ts +100 -0
- package/src/clients/config-export/droid.ts +54 -2
- package/src/clients/config-export/dsh.ts +21 -0
- package/src/clients/config-export/kilo.ts +12 -2
- package/src/clients/config-export/model-metadata.ts +10 -4
- package/src/clients/config-export/reasoning-metadata.ts +47 -0
- package/src/clients/config-export.ts +53 -67
- package/src/clients/omo-role-models.ts +17 -3
- package/src/codex/account-credit-use.ts +4 -2
- package/src/codex/account-runtime-state.ts +21 -0
- package/src/codex/account-store.ts +11 -47
- package/src/codex/account-usability.ts +11 -8
- package/src/codex/auth-api/account-list.ts +73 -63
- package/src/codex/auth-api/main-account-probe.ts +25 -12
- package/src/codex/auth-api/pool-mode-gate.ts +6 -9
- package/src/codex/auth-api/pool-quota-probe.ts +2 -4
- package/src/codex/auth-api/reset-credit-service.ts +3 -2
- package/src/codex/auth-context.ts +30 -11
- package/src/codex/catalog/aggregation.ts +3 -0
- package/src/codex/catalog/build-entries.ts +1 -1
- package/src/codex/catalog/bundled.ts +9 -2
- package/src/codex/catalog/heal-observation.ts +66 -0
- package/src/codex/catalog/model-hints.ts +2 -1
- package/src/codex/catalog/model-visibility.ts +6 -3
- package/src/codex/catalog/parsing.ts +2 -0
- package/src/codex/catalog/provider-models.ts +13 -4
- package/src/codex/catalog/publication-observer.ts +19 -0
- package/src/codex/catalog/remote.ts +13 -15
- package/src/codex/catalog/restore.ts +19 -6
- package/src/codex/catalog/retained-sync.ts +60 -11
- package/src/codex/catalog/routed-removal.ts +116 -0
- package/src/codex/catalog/write-audit-contract.ts +21 -0
- package/src/codex/catalog/write-audit.ts +171 -0
- package/src/codex/catalog-self-heal.ts +284 -0
- package/src/codex/catalog-write-serialization.ts +71 -7
- package/src/codex/chatgpt-refresh-failure.ts +56 -0
- package/src/codex/codex-home-owner.ts +124 -0
- package/src/codex/convergence.ts +57 -16
- package/src/codex/features.ts +8 -1
- package/src/codex/inject/restore.ts +46 -4
- package/src/codex/inject.ts +11 -1
- package/src/codex/internal/catalog-writer.ts +120 -13
- package/src/codex/journal.ts +49 -13
- package/src/codex/main-account.ts +144 -11
- package/src/codex/management-convergence.ts +3 -0
- package/src/codex/quota-refresh-outcome.ts +17 -2
- package/src/codex/quota-types.ts +36 -1
- package/src/codex/quota.ts +67 -16
- package/src/codex/refresh.ts +20 -9
- package/src/codex/routing/cooldown-math.ts +10 -4
- package/src/codex/routing/selection.ts +11 -4
- package/src/codex/routing.ts +9 -9
- package/src/codex/runtime.ts +7 -1
- package/src/codex/shim-diagnostics.ts +80 -0
- package/src/codex/shim-fingerprint.ts +7 -2
- package/src/codex/shim-migration.ts +153 -0
- package/src/codex/shim-overlay.ts +509 -0
- package/src/codex/shim-path-resolution.ts +145 -0
- package/src/codex/shim-probe.ts +4 -3
- package/src/codex/shim-state-file.ts +93 -7
- package/src/codex/shim.ts +54 -107
- package/src/codex/subagent-model-fallback.ts +2 -1
- package/src/codex/sync.ts +24 -4
- package/src/combos/failover.ts +101 -1
- package/src/combos/index.ts +1 -0
- package/src/combos/jev.ts +13 -3
- package/src/combos/resolve.ts +12 -0
- package/src/config/atomic-write.ts +13 -8
- package/src/config/diagnostics.ts +23 -1
- package/src/config/load-degrade.ts +7 -0
- package/src/config/provider-validation.ts +3 -0
- package/src/config/schema/chatgpt-desktop.ts +22 -0
- package/src/config/schema/compaction-triggers.ts +8 -0
- package/src/config/schema/config-schema.ts +43 -14
- package/src/config/schema/leaf-validators.ts +8 -3
- package/src/config/subagent-models.ts +12 -0
- package/src/generated/compatibility-version.json +645 -237
- package/src/grok/session-identity.ts +33 -0
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/merge.ts +13 -5
- package/src/integrations/mutation-plan.ts +50 -5
- package/src/integrations/omp-yaml-source.ts +181 -1
- package/src/integrations/ownership-policy.ts +66 -0
- package/src/integrations/registry.ts +50 -1
- package/src/integrations/state.ts +106 -19
- package/src/integrations/target.ts +25 -2
- package/src/integrations/writer.ts +107 -35
- package/src/lib/gui-pair-capability.ts +17 -0
- package/src/lib/package-tree-integrity.ts +42 -1
- package/src/lib/provider-client-headers.ts +62 -0
- package/src/lib/proxy-env.ts +3 -0
- package/src/lib/test-home-guard.ts +2 -1
- package/src/oauth/anthropic-account-refusal.ts +19 -8
- package/src/oauth/anthropic-continuity.ts +65 -0
- package/src/oauth/anthropic-identity.ts +75 -0
- package/src/oauth/anthropic-routing.ts +20 -15
- package/src/oauth/anthropic.ts +34 -3
- package/src/oauth/generic-account-failover.ts +21 -0
- package/src/oauth/health.ts +32 -5
- package/src/oauth/index.ts +42 -35
- package/src/oauth/local-token-detect.ts +7 -0
- package/src/oauth/pool-settings-capability.ts +9 -2
- package/src/oauth/refresh-policy.ts +2 -0
- package/src/oauth/store.ts +55 -6
- package/src/oauth/types.ts +9 -0
- package/src/protocols/encoders/messages.ts +4 -3
- package/src/protocols/plan-snapshot.ts +1 -1
- package/src/protocols/settings.ts +28 -4
- package/src/providers/antigravity-effort-families.ts +75 -0
- package/src/providers/antigravity-models.ts +54 -16
- package/src/providers/antigravity-wire-snapshot.ts +66 -0
- package/src/providers/derive.ts +2 -0
- package/src/providers/model-rename-fields.ts +3 -0
- package/src/providers/new-model-policy.ts +48 -10
- package/src/providers/registry/entries-extended.ts +3 -0
- package/src/providers/registry/model-ids.ts +2 -0
- package/src/providers/registry/model-seeds.ts +5 -0
- package/src/providers/registry/types.ts +4 -0
- package/src/responses/compaction.ts +19 -0
- package/src/router.ts +6 -0
- package/src/server/auth-cors.ts +9 -0
- package/src/server/caller-session-identity.ts +32 -0
- package/src/server/chat-completions.ts +10 -0
- package/src/server/claude-messages.ts +29 -15
- package/src/server/droid-reasoning-default.ts +50 -0
- package/src/server/github-copilot-responses-repair.ts +3 -6
- package/src/server/grok-responses-control-frame.ts +2 -2
- package/src/server/grok-responses-snapshot-repair.ts +4 -5
- package/src/server/gui-session.ts +33 -11
- package/src/server/index/claude-intercept-lifecycle.ts +4 -0
- package/src/server/index/serve-options.ts +13 -7
- package/src/server/index.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +12 -134
- package/src/server/management/config-routes.ts +1 -1
- package/src/server/management/context.ts +8 -1
- package/src/server/management/cursor-integration-routes.ts +3 -3
- package/src/server/management/integration-routes.ts +69 -11
- package/src/server/management/link-routes.ts +20 -13
- package/src/server/management/model-routes.ts +3 -3
- package/src/server/management/model-row-export-metadata.ts +150 -0
- package/src/server/management/model-rows.ts +148 -9
- package/src/server/management/native-integration-routes.ts +2 -2
- package/src/server/management/oauth-account-routes.ts +96 -30
- package/src/server/management/route-registry.ts +29 -27
- package/src/server/management/shared.ts +19 -5
- package/src/server/management/subagent-model-routes.ts +214 -0
- package/src/server/messages-native-eligibility.ts +3 -6
- package/src/server/messages-native-oauth.ts +82 -35
- package/src/server/messages-native.ts +78 -16
- package/src/server/relay.ts +14 -44
- package/src/server/request-log-generation-window.ts +38 -0
- package/src/server/request-log.ts +13 -0
- package/src/server/responses/adapter-dispatch.ts +152 -28
- package/src/server/responses/codex-auth-error.ts +10 -2
- package/src/server/responses/combo-stream-preflight.ts +29 -3
- package/src/server/responses/compaction-routing.ts +10 -1
- package/src/server/responses/core-auth.ts +67 -30
- package/src/server/responses/core-codex-account.ts +6 -3
- package/src/server/responses/core-combo-failure.ts +5 -1
- package/src/server/responses/core-combo-native.ts +7 -1
- package/src/server/responses/core-combo.ts +64 -7
- package/src/server/responses/core-normalize.ts +7 -0
- package/src/server/responses/core-options.ts +5 -0
- package/src/server/responses/fetch-helpers.ts +9 -0
- package/src/server/responses/passthrough-delivery.ts +1 -1
- package/src/server/responses/policy-fallback.ts +1 -1
- package/src/server/responses/request-prepare.ts +5 -0
- package/src/server/responses/request-spend.ts +30 -30
- package/src/server/responses-custom-tool-repair.ts +2 -2
- package/src/server/responses-hosted-image-display.ts +70 -9
- package/src/server/responses-terminal-repair.ts +15 -2
- package/src/server/sse-frame-buffer.ts +38 -36
- package/src/server/sse-payload-rewrite.ts +78 -49
- package/src/server/ws-bridge.ts +1 -11
- package/src/service/cli.ts +6 -1
- package/src/service/ownership-mutation-lease.d.mts +26 -0
- package/src/service/ownership-mutation-lease.mjs +76 -1
- package/src/service/windows-taskxml.ts +1 -2
- package/src/service-manager-probe.ts +36 -2
- package/src/types/config.ts +6 -0
- package/src/types/provider.ts +28 -10
- package/src/update/index.ts +2 -1
- package/src/update/npm-invocation.mjs +35 -3
- package/src/update/proxy-liveness-probe.mjs +38 -8
- package/src/update/update-failure-guidance.d.mts +6 -0
- package/src/update/update-failure-guidance.mjs +30 -5
- package/src/usage/expected-prices.ts +9 -0
- package/src/usage/log.ts +7 -0
- package/src/usage/summary.ts +82 -1
- package/src/web-search/loop.ts +11 -8
- package/src/web-search/run-turn-loop.ts +10 -7
- package/gui/dist/assets/App-CddorHdA.js +0 -51
- package/gui/dist/assets/App-DMudtISW.css +0 -1
- package/gui/dist/assets/index-Btze1I3T.css +0 -1
- package/gui/dist/assets/index-CvnY7E6i.js +0 -86
- package/gui/dist/assets/quota-summary-B_AWpHfP.js +0 -1
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/** Bounded, serial observation of fixed management read surfaces. */
|
|
2
|
+
import { readBoundedResponseBytes } from "../lib/bounded-body";
|
|
3
|
+
import { runningProxyUpdateHeaders } from "../oauth/login-cli";
|
|
4
|
+
import { findLiveProxy, type LiveProxy } from "../server/proxy-liveness";
|
|
5
|
+
import { runtimeBaseUrl, type RuntimeApiDeps } from "./runtime-api";
|
|
6
|
+
|
|
7
|
+
const POLL_MS = 1_000;
|
|
8
|
+
const REQUEST_MS = 10_000;
|
|
9
|
+
const RESPONSE_BYTES = 32 * 1024 * 1024;
|
|
10
|
+
type ObservationPath = "/api/logs" | "/api/debug/injection-logs" | "/api/companion/settings" | "/api/usage";
|
|
11
|
+
|
|
12
|
+
export interface ObserveStreamDeps extends RuntimeApiDeps {
|
|
13
|
+
signal?: AbortSignal;
|
|
14
|
+
/** Reduced timings for isolated tests only; never command-line options. */
|
|
15
|
+
pollIntervalMs?: number;
|
|
16
|
+
requestTimeoutMs?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class ObservationError extends Error {}
|
|
20
|
+
|
|
21
|
+
function reduced(value: number | undefined, ceiling: number): number {
|
|
22
|
+
return value !== undefined && Number.isFinite(value) && value > 0 && value <= ceiling ? value : ceiling;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function interrupted<T>(work: Promise<T>, signal: AbortSignal): Promise<T> {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const abort = () => { cleanup(); reject(signal.reason); };
|
|
28
|
+
const cleanup = () => signal.removeEventListener("abort", abort);
|
|
29
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
30
|
+
work.then(value => { cleanup(); resolve(value); }, error => { cleanup(); reject(error); });
|
|
31
|
+
if (signal.aborted) abort();
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function waitForPoll(ms: number, signal: AbortSignal): Promise<void> {
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
const cleanup = () => { clearTimeout(timer); signal.removeEventListener("abort", abort); };
|
|
38
|
+
const abort = () => { cleanup(); reject(signal.reason); };
|
|
39
|
+
const timer = setTimeout(() => { cleanup(); resolve(); }, ms);
|
|
40
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
41
|
+
if (signal.aborted) abort();
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function identity(live: LiveProxy | undefined): string {
|
|
46
|
+
return JSON.stringify(live ? [live.pid, live.port, live.hostname, live.role] : null);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface ObserveStream {
|
|
50
|
+
signal: AbortSignal;
|
|
51
|
+
get(path: ObservationPath, query: URLSearchParams): Promise<unknown>;
|
|
52
|
+
wait(): Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface ObservationContext {
|
|
56
|
+
kind: "follow" | "snapshot";
|
|
57
|
+
limitOption?: "--limit" | "--scan-limit";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Each invocation owns its signals, in-flight request and pinned runtime identity. */
|
|
61
|
+
export async function withObserveStream(
|
|
62
|
+
deps: ObserveStreamDeps,
|
|
63
|
+
run: (stream: ObserveStream) => Promise<void | number>,
|
|
64
|
+
context: ObservationContext = { kind: "follow", limitOption: "--limit" },
|
|
65
|
+
): Promise<number> {
|
|
66
|
+
const retry = context.kind === "follow" ? "restart follow" : "retry the command";
|
|
67
|
+
const controller = new AbortController();
|
|
68
|
+
let exit = 0;
|
|
69
|
+
const stop = (code: number) => {
|
|
70
|
+
if (controller.signal.aborted) return;
|
|
71
|
+
exit = code;
|
|
72
|
+
controller.abort();
|
|
73
|
+
};
|
|
74
|
+
const onInt = () => stop(130);
|
|
75
|
+
const onTerm = () => stop(143);
|
|
76
|
+
process.on("SIGINT", onInt);
|
|
77
|
+
process.on("SIGTERM", onTerm);
|
|
78
|
+
deps.signal?.addEventListener("abort", onInt, { once: true });
|
|
79
|
+
if (deps.signal?.aborted) onInt();
|
|
80
|
+
let pinned: { origin: string; identity: string } | undefined;
|
|
81
|
+
const signal = controller.signal;
|
|
82
|
+
const timeoutMs = reduced(deps.requestTimeoutMs, REQUEST_MS);
|
|
83
|
+
const get = async (path: ObservationPath, query: URLSearchParams): Promise<unknown> => {
|
|
84
|
+
signal.throwIfAborted();
|
|
85
|
+
let live: LiveProxy | undefined;
|
|
86
|
+
const origin = await interrupted(runtimeBaseUrl({ ...deps, findLiveProxy: async () => {
|
|
87
|
+
const found = await (deps.findLiveProxy ?? findLiveProxy)();
|
|
88
|
+
live = found ? { ...found } : undefined;
|
|
89
|
+
// Snapshot recovery comes from our observed target, never arbitrary transport text.
|
|
90
|
+
if (context.kind === "snapshot") {
|
|
91
|
+
if (!found) throw new ObservationError("Proxy is not running. Start it with: ocx start");
|
|
92
|
+
if (found.role === "client") throw new ObservationError("This listener runs in the client role and has no management API. Run this command on its serving hub, or open the hub dashboard.");
|
|
93
|
+
}
|
|
94
|
+
return found;
|
|
95
|
+
} }), signal);
|
|
96
|
+
signal.throwIfAborted();
|
|
97
|
+
const current = { origin, identity: identity(live) };
|
|
98
|
+
if (pinned && (pinned.origin !== current.origin || pinned.identity !== current.identity)) {
|
|
99
|
+
throw new ObservationError(`The runtime changed. ${context.kind === "follow" ? "Restart follow" : "Retry the command"} to observe the current runtime.`);
|
|
100
|
+
}
|
|
101
|
+
pinned = current;
|
|
102
|
+
const request = new AbortController();
|
|
103
|
+
const abort = () => request.abort();
|
|
104
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
105
|
+
const timer = setTimeout(abort, timeoutMs);
|
|
106
|
+
try {
|
|
107
|
+
signal.throwIfAborted();
|
|
108
|
+
const responsePromise = (deps.fetchImpl ?? fetch)(`${origin}${path}?${query}`, {
|
|
109
|
+
method: "GET", headers: runningProxyUpdateHeaders(), credentials: "omit", redirect: "error", signal: request.signal,
|
|
110
|
+
});
|
|
111
|
+
// A test transport or late fetch may settle after abort. Release its body too.
|
|
112
|
+
void responsePromise.then(response => {
|
|
113
|
+
if (request.signal.aborted) void response.body?.cancel().catch(() => {});
|
|
114
|
+
}, () => {});
|
|
115
|
+
const response = await interrupted(responsePromise, request.signal);
|
|
116
|
+
const result = await readBoundedResponseBytes(response, { maxBytes: RESPONSE_BYTES, signal: request.signal });
|
|
117
|
+
signal.throwIfAborted();
|
|
118
|
+
request.signal.throwIfAborted();
|
|
119
|
+
if (result.oversized) throw new ObservationError(`The observation window exceeds 32 MiB. ${context.limitOption ? `Reduce ${context.limitOption} and ${retry}` : "Check the runtime response size and retry the command"}.`);
|
|
120
|
+
if (!response.ok) throw new ObservationError(`The observation request was refused. Check runtime access and ${retry}.`);
|
|
121
|
+
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(result.bytes));
|
|
122
|
+
} finally {
|
|
123
|
+
clearTimeout(timer);
|
|
124
|
+
signal.removeEventListener("abort", abort);
|
|
125
|
+
request.abort();
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
try {
|
|
129
|
+
signal.throwIfAborted();
|
|
130
|
+
const result = await run({ signal, get, wait: () => waitForPoll(reduced(deps.pollIntervalMs, POLL_MS), signal) });
|
|
131
|
+
return exit || result || 0;
|
|
132
|
+
} catch (error) {
|
|
133
|
+
if (exit) return exit;
|
|
134
|
+
console.error(`Error: ${error instanceof ObservationError ? error.message : `Observation failed or timed out. Check the runtime and ${retry}.`}`);
|
|
135
|
+
return 1;
|
|
136
|
+
} finally {
|
|
137
|
+
controller.abort();
|
|
138
|
+
process.removeListener("SIGINT", onInt);
|
|
139
|
+
process.removeListener("SIGTERM", onTerm);
|
|
140
|
+
deps.signal?.removeEventListener("abort", onInt);
|
|
141
|
+
}
|
|
142
|
+
}
|
package/src/cli/observe.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CliUsageError,
|
|
3
|
+
RuntimeApiError,
|
|
3
4
|
printData,
|
|
4
5
|
rejectArgs,
|
|
5
6
|
runCliAction,
|
|
@@ -8,10 +9,16 @@ import {
|
|
|
8
9
|
takeFlag,
|
|
9
10
|
takeIntegerOption,
|
|
10
11
|
takeOption,
|
|
12
|
+
takeOptionWithSyntax,
|
|
11
13
|
type RuntimeApiDeps,
|
|
12
14
|
} from "./runtime-api";
|
|
15
|
+
import { followLogs } from "./log-follow";
|
|
16
|
+
import { handleLogFilterCommand } from "./log-view-filter";
|
|
17
|
+
import { followInjection } from "./injection-follow";
|
|
18
|
+
import type { ObserveStreamDeps } from "./observe-stream";
|
|
13
19
|
import { formatUsageReport } from "./usage-report";
|
|
14
|
-
import {
|
|
20
|
+
import { selectUsageModelView, takeUsageSearchOption } from "./usage-model-search";
|
|
21
|
+
import { USAGE_RANGES, USAGE_SURFACES, type UsageSummary, type UsageFilterEcho } from "../usage/summary";
|
|
15
22
|
import { parseUsageTimeWindow, type UsageTimeWindow } from "../usage/time-range";
|
|
16
23
|
import { redactSecretString } from "../lib/redact";
|
|
17
24
|
import { readClientConnectionState, sameClientConnectionOwner } from "../client/state";
|
|
@@ -21,18 +28,19 @@ import type { HubUsageReport } from "../remote/hub-usage";
|
|
|
21
28
|
|
|
22
29
|
const USAGE = `Usage:
|
|
23
30
|
ocx observe logs [--provider <name>] [--model <id>] [--status <code>]
|
|
24
|
-
[--conversation <id>] [--account <label>] [--limit <n>] [--follow] [--json|--jsonl]
|
|
31
|
+
[--conversation <id>] [--account <label>] [--limit <n>] [--follow] [--json|--jsonl] [--events]
|
|
25
32
|
ocx logs explain <request-id> [--json]
|
|
33
|
+
ocx logs filter [selectors] [--scan-limit <1..2000>] [--limit <1..2000>] [--json|--jsonl]
|
|
26
34
|
ocx logs rebuild-index
|
|
27
35
|
ocx logs index-status
|
|
28
36
|
ocx observe usage [--range <today|1d|7d|30d|all>] [--surface <all|codex|claude|grok>]
|
|
29
37
|
[--since <epoch-ms|ISO-datetime>] [--until <epoch-ms|ISO-datetime>]
|
|
30
|
-
[--provider <name>] [--model <id>] [--json]
|
|
38
|
+
[--provider <name>] [--model <id>] [--api-key-id <id>] [--search <text>] [--json]
|
|
31
39
|
ocx observe storage [codex-logs [status|protect|unprotect|repair|compact] [--mode <compat|quiet>]] [--json]
|
|
32
40
|
ocx observe memory [--json]
|
|
33
41
|
ocx observe debug [--json]
|
|
34
42
|
ocx observe claude-inbound [--limit <n>] [--json]
|
|
35
|
-
ocx observe injection [--limit <n>] [--json]`;
|
|
43
|
+
ocx observe injection [--limit <n>] [--follow [--jsonl]] [--json]`;
|
|
36
44
|
|
|
37
45
|
type LogEntry = Record<string, unknown> & { id?: string | number; timestamp?: string; provider?: string; model?: string; status?: number };
|
|
38
46
|
|
|
@@ -77,10 +85,11 @@ function formatLog(row: LogEntry): string {
|
|
|
77
85
|
}
|
|
78
86
|
|
|
79
87
|
/** Read or follow request logs while preserving raw JSON and JSONL output. */
|
|
80
|
-
async function logs(argv: string[], deps:
|
|
88
|
+
async function logs(argv: string[], deps: ObserveStreamDeps): Promise<number> {
|
|
81
89
|
const args = [...argv];
|
|
82
90
|
const wantsJson = takeFlag(args, "--json");
|
|
83
|
-
const
|
|
91
|
+
const wantsEvents = takeFlag(args, "--events");
|
|
92
|
+
const wantsJsonl = takeFlag(args, "--jsonl") || wantsEvents;
|
|
84
93
|
const follow = takeFlag(args, "--follow") || takeFlag(args, "-f");
|
|
85
94
|
const provider = takeOption(args, "--provider");
|
|
86
95
|
const model = takeOption(args, "--model");
|
|
@@ -97,24 +106,19 @@ async function logs(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
97
106
|
if (follow && wantsJson) {
|
|
98
107
|
throw new CliUsageError("--follow cannot be combined with --json; use --jsonl for streaming JSONL", USAGE);
|
|
99
108
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
if (!follow) return;
|
|
115
|
-
if (seen.size > 5_000) seen = new Set([...seen].slice(-2_500));
|
|
116
|
-
await Bun.sleep(1_000);
|
|
117
|
-
} while (true);
|
|
109
|
+
if (wantsEvents && !follow) throw new CliUsageError("--events requires --follow", USAGE);
|
|
110
|
+
const params = { provider, model, status, conversationId, account, limit };
|
|
111
|
+
if (follow) {
|
|
112
|
+
if (limit > 2000) throw new CliUsageError("--limit must be between 1 and 2000 for follow", USAGE);
|
|
113
|
+
return followLogs({ query: new URLSearchParams(query(params)), limit, jsonl: wantsJsonl, events: wantsEvents, formatRow: formatLog }, deps);
|
|
114
|
+
}
|
|
115
|
+
const data = await runtimeRequest(`/api/logs${query(params)}`, {}, deps);
|
|
116
|
+
if (wantsJson) printData(data, true);
|
|
117
|
+
else for (const row of logRows(data)) {
|
|
118
|
+
if (wantsJsonl) console.log(JSON.stringify(row));
|
|
119
|
+
else console.log(formatLog(row));
|
|
120
|
+
}
|
|
121
|
+
return 0;
|
|
118
122
|
}
|
|
119
123
|
|
|
120
124
|
async function explain(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
@@ -163,6 +167,7 @@ async function indexStatus(argv: string[], deps: RuntimeApiDeps): Promise<void>
|
|
|
163
167
|
|
|
164
168
|
async function usage(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
165
169
|
const args = [...argv];
|
|
170
|
+
const search = takeUsageSearchOption(args);
|
|
166
171
|
const wantsJson = takeFlag(args, "--json");
|
|
167
172
|
const range = takeOption(args, "--range") ?? "30d";
|
|
168
173
|
const surface = takeOption(args, "--surface") ?? "all";
|
|
@@ -170,6 +175,9 @@ async function usage(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
170
175
|
const model = takeOption(args, "--model");
|
|
171
176
|
const since = takeOption(args, "--since");
|
|
172
177
|
const until = takeOption(args, "--until");
|
|
178
|
+
const rawKeyId = takeOptionWithSyntax(args, "--api-key-id")?.value;
|
|
179
|
+
const apiKeyId = rawKeyId?.trim();
|
|
180
|
+
if (apiKeyId !== undefined && !apiKeyId) throw new CliUsageError("--api-key-id must not be blank", USAGE);
|
|
173
181
|
let window: UsageTimeWindow | undefined;
|
|
174
182
|
try {
|
|
175
183
|
window = parseUsageTimeWindow(since, until);
|
|
@@ -184,13 +192,14 @@ async function usage(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
184
192
|
throw new CliUsageError(`--surface must be one of ${USAGE_SURFACES.join(", ")}`, USAGE);
|
|
185
193
|
}
|
|
186
194
|
rejectArgs(args.map(redactSecretString), USAGE);
|
|
187
|
-
const suffix = query({ range, surface, provider, model, since: window?.since, until: window?.until });
|
|
195
|
+
const suffix = query({ range, surface, provider, model, apiKeyId, since: window?.since, until: window?.until });
|
|
188
196
|
const connection = readClientConnectionState();
|
|
189
|
-
let result: UsageSummary | HubUsageReport;
|
|
197
|
+
let result: (UsageSummary & { filter?: UsageFilterEcho }) | HubUsageReport;
|
|
190
198
|
if (connection.kind === "invalid" || connection.kind === "mismatched") {
|
|
191
199
|
throw new Error(`Client usage unavailable: ${connection.reason}`);
|
|
192
200
|
}
|
|
193
201
|
if (connection.kind === "connected") {
|
|
202
|
+
if (apiKeyId !== undefined) throw new CliUsageError("--api-key-id is unavailable on connected clients; run on the hub for a selected key, or omit it for this client's usage", USAGE);
|
|
194
203
|
const token = readServiceApiTokenState();
|
|
195
204
|
if (token.kind !== "present" || token.fingerprint !== connection.value.tokenFingerprint) {
|
|
196
205
|
throw new Error("Client usage unavailable: the enrolled data key is missing or changed; repair the client connection");
|
|
@@ -206,7 +215,18 @@ async function usage(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
206
215
|
throw new Error("Client connection changed while reading usage; retry for the current connection");
|
|
207
216
|
}
|
|
208
217
|
} else {
|
|
209
|
-
result = await runtimeRequest<UsageSummary>(`/api/usage${suffix}`, {}, deps);
|
|
218
|
+
if (apiKeyId === undefined) result = await runtimeRequest<UsageSummary>(`/api/usage${suffix}`, {}, deps);
|
|
219
|
+
else {
|
|
220
|
+
try {
|
|
221
|
+
result = await runtimeRequest<UsageSummary & { filter?: UsageFilterEcho }>(`/api/usage${suffix}`, { redirect: "error", credentials: "omit" }, deps);
|
|
222
|
+
} catch (error) {
|
|
223
|
+
if (error instanceof RuntimeApiError && error.code === "proxy_not_running") throw error;
|
|
224
|
+
throw new Error("Key-scoped usage could not be read. Check runtime access and retry.");
|
|
225
|
+
}
|
|
226
|
+
if (result?.filter?.apiKeyId !== apiKeyId) {
|
|
227
|
+
throw new Error("The server did not confirm the requested API key scope. Upgrade and restart the proxy, then retry.");
|
|
228
|
+
}
|
|
229
|
+
}
|
|
210
230
|
}
|
|
211
231
|
// Older daemons ignore custom bounds and return successful preset reports.
|
|
212
232
|
if (window && (result?.customWindow !== true || result.since !== window.since || result.until !== window.until)) {
|
|
@@ -216,8 +236,10 @@ async function usage(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
216
236
|
// the call, so passing formatUsageReport(...) inline would run the human
|
|
217
237
|
// renderer during --json and let its assumptions affect a path that is meant
|
|
218
238
|
// to bypass it entirely.
|
|
219
|
-
|
|
220
|
-
|
|
239
|
+
const view = search === undefined ? undefined : selectUsageModelView(result, search);
|
|
240
|
+
const displayed = view ?? result;
|
|
241
|
+
if (wantsJson) printData(displayed, true);
|
|
242
|
+
else printData(displayed, false, formatUsageReport(displayed as Parameters<typeof formatUsageReport>[0], view ? { modelView: view.modelView } : undefined));
|
|
221
243
|
}
|
|
222
244
|
|
|
223
245
|
async function simple(path: string, argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
@@ -265,24 +287,43 @@ async function storage(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
265
287
|
printData(result, wantsJson, summaryLines(result));
|
|
266
288
|
}
|
|
267
289
|
|
|
268
|
-
|
|
269
|
-
|
|
290
|
+
async function injection(argv: string[], deps: ObserveStreamDeps): Promise<number> {
|
|
291
|
+
const args = [...argv];
|
|
292
|
+
const follow = takeFlag(args, "--follow");
|
|
293
|
+
const jsonl = takeFlag(args, "--jsonl");
|
|
294
|
+
if (!follow) {
|
|
295
|
+
if (jsonl) throw new CliUsageError("--jsonl requires --follow for injection logs", USAGE);
|
|
296
|
+
await simple("/api/debug/injection-logs", args, deps);
|
|
297
|
+
return 0;
|
|
298
|
+
}
|
|
299
|
+
if (takeFlag(args, "--json")) throw new CliUsageError("--follow cannot be combined with --json; use --jsonl", USAGE);
|
|
300
|
+
const limit = takeIntegerOption(args, "--limit", { min: 1 }) ?? 500;
|
|
301
|
+
if (limit > 2000) throw new CliUsageError("--limit must be between 1 and 2000 for follow", USAGE);
|
|
302
|
+
rejectArgs(args, USAGE);
|
|
303
|
+
return followInjection(limit, jsonl, deps);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export async function handleObserveCommand(argv: string[], deps: ObserveStreamDeps = {}): Promise<number> {
|
|
307
|
+
let streamExit = 0;
|
|
308
|
+
const exit = await runCliAction(async () => {
|
|
270
309
|
const [sub = "logs", ...rest] = argv;
|
|
271
310
|
if (sub === "logs") {
|
|
272
311
|
const action = rest[0];
|
|
273
|
-
if (action === "
|
|
312
|
+
if (action === "filter") streamExit = await handleLogFilterCommand(rest.slice(1), deps, formatLog);
|
|
313
|
+
else if (action === "explain") await explain(rest.slice(1), deps);
|
|
274
314
|
else if (action === "rebuild-index") await rebuildIndex(rest.slice(1), deps);
|
|
275
315
|
else if (action === "index-status") await indexStatus(rest.slice(1), deps);
|
|
276
|
-
else await logs(rest, deps);
|
|
316
|
+
else streamExit = await logs(rest, deps);
|
|
277
317
|
}
|
|
278
318
|
else if (sub === "usage") await usage(rest, deps);
|
|
279
319
|
else if (sub === "storage") await storage(rest, deps);
|
|
280
320
|
else if (sub === "memory") await simple("/api/system/memory", rest, deps);
|
|
281
321
|
else if (sub === "debug") await simple("/api/debug", rest, deps);
|
|
282
322
|
else if (sub === "claude-inbound") await simple("/api/claude/inbound-debug", rest, deps);
|
|
283
|
-
else if (sub === "injection") await
|
|
323
|
+
else if (sub === "injection") streamExit = await injection(rest, deps);
|
|
284
324
|
else throw new CliUsageError(`unknown observe command ${sub}`, USAGE);
|
|
285
325
|
});
|
|
326
|
+
return exit || streamExit;
|
|
286
327
|
}
|
|
287
328
|
|
|
288
329
|
export const OBSERVE_USAGE = USAGE;
|
package/src/cli/opencode.ts
CHANGED
|
@@ -39,6 +39,7 @@ import type {
|
|
|
39
39
|
OpencodeProviderBlocks,
|
|
40
40
|
OpencodeV2ProviderBlock,
|
|
41
41
|
} from "../clients/config-export";
|
|
42
|
+
import type { EffectiveModelExportMetadata } from "../clients/config-export/contracts";
|
|
42
43
|
import { filterCatalogVisibleModels, visibleNativeSlugs } from "../codex/catalog";
|
|
43
44
|
import { commandInvocation } from "../lib/win-exec";
|
|
44
45
|
import { configuredAdminToken, opencodeCatalogToken } from "../lib/admin-secrets";
|
|
@@ -106,12 +107,30 @@ export interface OpencodeProxyModelRow {
|
|
|
106
107
|
contextWindow?: number;
|
|
107
108
|
/** Authoritative output limit (CatalogModel.maxOutputTokens); optional. */
|
|
108
109
|
maxOutputTokens?: number;
|
|
110
|
+
/** Authoritative input ceiling (CatalogModel.maxInputTokens); optional. */
|
|
111
|
+
maxInputTokens?: number;
|
|
112
|
+
/**
|
|
113
|
+
* Derived on the hub from known evidence; absent means unknown, never a negative. An
|
|
114
|
+
* explicit `false` from the hub is evidence too and is carried through unchanged.
|
|
115
|
+
*/
|
|
116
|
+
supportsTools?: boolean;
|
|
117
|
+
supportsReasoning?: boolean;
|
|
118
|
+
supportsReasoningSummaries?: boolean;
|
|
109
119
|
/** Declared input modalities from `/api/models`; carried into opencode model capabilities. */
|
|
110
120
|
inputModalities?: string[];
|
|
111
121
|
/** Declared effort ladder from `/api/models`; carried into opencode model variants. */
|
|
112
122
|
reasoningEfforts?: string[];
|
|
113
123
|
/** Declared default effort from `/api/models`. */
|
|
114
124
|
defaultReasoningEffort?: string;
|
|
125
|
+
/**
|
|
126
|
+
* Effective export metadata `/api/models` attaches to every row it produces. Custom row
|
|
127
|
+
* fields are the operator's stored overrides, so this object is the authoritative
|
|
128
|
+
* projection a client consumes; routed and native rows carry their declared default in it
|
|
129
|
+
* because the row-level default is the picker's preference order. When present, an absent
|
|
130
|
+
* field inside means "no declared value" — never "fall back to the row" — and every field
|
|
131
|
+
* below still works for rows without it (older hubs, hand-built rows).
|
|
132
|
+
*/
|
|
133
|
+
exportMetadata?: EffectiveModelExportMetadata;
|
|
115
134
|
}
|
|
116
135
|
|
|
117
136
|
const PROJECT_CONFIG_FILENAMES = ["opencode.json", "opencode.jsonc"] as const;
|
|
@@ -324,6 +343,12 @@ export async function fetchOpencodeProxyModels(
|
|
|
324
343
|
/**
|
|
325
344
|
* Visible OpenCode catalog entries from proxy /api/models rows. Disabled rows are omitted;
|
|
326
345
|
* native rows are omitted in Codex Direct mode.
|
|
346
|
+
*
|
|
347
|
+
* Custom rows carry their effective projection in `exportMetadata`; when it is present it
|
|
348
|
+
* wins over the row's own fields, which for a custom row are the operator's stored overrides
|
|
349
|
+
* rather than the resolved capabilities. Routed/native projections also distinguish declared
|
|
350
|
+
* defaults from picker preferences; older hubs without the field retain their row values. The empty-vs-undefined ladder distinction
|
|
351
|
+
* survives: a row declaring `reasoningEfforts: []` exports `[]`, not an absent key.
|
|
327
352
|
*/
|
|
328
353
|
export function opencodeCatalogFromProxyRows(
|
|
329
354
|
rows: readonly OpencodeProxyModelRow[],
|
|
@@ -343,24 +368,43 @@ export function opencodeCatalogFromProxyRows(
|
|
|
343
368
|
&& !visibleRouted.has(row)) continue;
|
|
344
369
|
if (seen.has(namespaced)) continue;
|
|
345
370
|
seen.add(namespaced);
|
|
371
|
+
const effective = row.exportMetadata;
|
|
372
|
+
const maxTokens = effective?.maxTokens ?? row.maxOutputTokens;
|
|
373
|
+
const maxInputTokens = effective?.maxInputTokens ?? row.maxInputTokens;
|
|
374
|
+
const inputModalities = effective?.inputModalities ?? row.inputModalities;
|
|
375
|
+
const reasoningEfforts = effective?.reasoningEfforts ?? row.reasoningEfforts;
|
|
376
|
+
// The projection is the authority on the default: when it exists, an absent default means
|
|
377
|
+
// NO declared default, and falling back to the row would reintroduce the picker's
|
|
378
|
+
// synthetic medium/low value. Rows without one (older hubs, hand-built rows) keep the
|
|
379
|
+
// row's own value.
|
|
380
|
+
const defaultReasoningEffort = effective !== undefined
|
|
381
|
+
? effective.defaultReasoningEffort
|
|
382
|
+
: row.defaultReasoningEffort;
|
|
383
|
+
const supportsTools = effective?.supportsTools ?? row.supportsTools;
|
|
384
|
+
const supportsReasoning = effective?.supportsReasoning ?? row.supportsReasoning;
|
|
385
|
+
const supportsReasoningSummaries = effective?.supportsReasoningSummaries ?? row.supportsReasoningSummaries;
|
|
346
386
|
catalog.push({
|
|
347
387
|
namespaced,
|
|
348
388
|
native: row.native === true,
|
|
349
389
|
provider: row.provider,
|
|
350
390
|
id: row.id,
|
|
351
|
-
contextWindow: row.contextWindow,
|
|
352
|
-
...(typeof
|
|
391
|
+
contextWindow: effective?.contextWindow ?? row.contextWindow,
|
|
392
|
+
...(typeof maxTokens === "number" ? { maxTokens } : {}),
|
|
393
|
+
...(typeof maxInputTokens === "number" ? { maxInputTokens } : {}),
|
|
353
394
|
displayName: row.displayNameSource === "fallback" ? undefined : row.displayName,
|
|
354
|
-
...(Array.isArray(
|
|
355
|
-
? { inputModalities: [...
|
|
395
|
+
...(Array.isArray(inputModalities) && inputModalities.length > 0
|
|
396
|
+
? { inputModalities: [...inputModalities] }
|
|
356
397
|
: {}),
|
|
357
398
|
...(typeof row.fastRowAvailable === "boolean" ? { fastRowAvailable: row.fastRowAvailable } : {}),
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
? { defaultReasoningEffort: row.defaultReasoningEffort }
|
|
399
|
+
// `[]` is a declaration, not an absence: an explicit no-rungs ladder stays empty here.
|
|
400
|
+
...(Array.isArray(reasoningEfforts) ? { reasoningEfforts: [...reasoningEfforts] } : {}),
|
|
401
|
+
...(typeof defaultReasoningEffort === "string" && defaultReasoningEffort.length > 0
|
|
402
|
+
? { defaultReasoningEffort }
|
|
363
403
|
: {}),
|
|
404
|
+
// An explicit false survives the hop; only derivation is true-or-nothing.
|
|
405
|
+
...(typeof supportsTools === "boolean" ? { supportsTools } : {}),
|
|
406
|
+
...(typeof supportsReasoning === "boolean" ? { supportsReasoning } : {}),
|
|
407
|
+
...(typeof supportsReasoningSummaries === "boolean" ? { supportsReasoningSummaries } : {}),
|
|
364
408
|
});
|
|
365
409
|
}
|
|
366
410
|
return catalog;
|
|
@@ -377,7 +421,7 @@ export function isOpencodeRuntimeConfigError(
|
|
|
377
421
|
|
|
378
422
|
/**
|
|
379
423
|
* Merge inherited `OPENCODE_CONFIG_CONTENT` and override only our own blocks:
|
|
380
|
-
* `provider.opencodex` (V1) and `providers.opencodex` (V2
|
|
424
|
+
* `provider.opencodex` (V1 maps) and `providers.opencodex` (native V2 arrays).
|
|
381
425
|
* When no inline layer is present, emit the minimal runtime object for this launcher.
|
|
382
426
|
*/
|
|
383
427
|
export function mergeOpencodeRuntimeConfig(
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { parseProviderEditorConfigDTO } from "../server/auth-cors";
|
|
2
|
+
import { readJsonInput, serializeManagementJson } from "./json-input";
|
|
3
|
+
import { printProviderReceipt, runProviderAction } from "./provider-result";
|
|
4
|
+
import {
|
|
5
|
+
CliUsageError, printData, runtimeBaseUrl, runtimeRequest, takeFlag,
|
|
6
|
+
takeOptionWithSyntax, type RuntimeApiDeps,
|
|
7
|
+
} from "./runtime-api";
|
|
8
|
+
|
|
9
|
+
function record(value: unknown): value is Record<string, unknown> {
|
|
10
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
11
|
+
const prototype = Object.getPrototypeOf(value);
|
|
12
|
+
return prototype === Object.prototype || prototype === null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function editorInput(value: unknown) {
|
|
16
|
+
const parsed = parseProviderEditorConfigDTO(value);
|
|
17
|
+
if (!parsed.ok) throw new CliUsageError("Provider batch input must be an editable provider snapshot without secret, derived or unknown fields.");
|
|
18
|
+
return parsed.value;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function snapshot(wantsJson: boolean, deps: RuntimeApiDeps): Promise<number> {
|
|
22
|
+
const baseUrl = await runtimeBaseUrl(deps);
|
|
23
|
+
const fetched = await runtimeRequest("/api/config", { method: "GET", redirect: "error" }, { ...deps, baseUrl });
|
|
24
|
+
if (!record(fetched) || !record(fetched.providers)) throw new Error("Invalid provider snapshot response.");
|
|
25
|
+
const entries = Object.entries(fetched.providers).map(([name, provider]) => {
|
|
26
|
+
if (!record(provider)) throw new Error("Invalid provider snapshot response.");
|
|
27
|
+
// These are the four dashboard decorations, not editor fields. Any other
|
|
28
|
+
// unexpected field must reach the canonical parser and fail closed.
|
|
29
|
+
const { hasApiKey, hasHeaders, xaiResponsesOptInState, initialModelSelection, ...editable } = provider;
|
|
30
|
+
return [name, editable] as const;
|
|
31
|
+
});
|
|
32
|
+
const parsed = parseProviderEditorConfigDTO({
|
|
33
|
+
defaultProvider: fetched.defaultProvider,
|
|
34
|
+
providers: Object.fromEntries(entries),
|
|
35
|
+
});
|
|
36
|
+
if (!parsed.ok) throw new Error("Invalid provider snapshot response.");
|
|
37
|
+
printData(parsed.value, wantsJson);
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function apply(args: string[], wantsJson: boolean, deps: RuntimeApiDeps): Promise<number> {
|
|
42
|
+
const baselinePath = takeOptionWithSyntax(args, "--baseline")?.value;
|
|
43
|
+
const nextPath = takeOptionWithSyntax(args, "--file")?.value;
|
|
44
|
+
const confirmed = takeFlag(args, "--yes");
|
|
45
|
+
if (args.length || !baselinePath?.trim() || !nextPath?.trim()) {
|
|
46
|
+
throw new CliUsageError("Provider apply requires --baseline FILE and --file FILE, with optional --yes and --json.");
|
|
47
|
+
}
|
|
48
|
+
if (baselinePath === "-" && nextPath === "-") {
|
|
49
|
+
throw new CliUsageError("Provider apply accepts at most one stdin source.");
|
|
50
|
+
}
|
|
51
|
+
// Parse both documents as supplied: normalizing a public baseline would
|
|
52
|
+
// change the server's CAS comparison and can discard unseen private data.
|
|
53
|
+
const baseline = editorInput(await readJsonInput(baselinePath, deps, "Provider baseline"));
|
|
54
|
+
const next = editorInput(await readJsonInput(nextPath, deps, "Provider next snapshot"));
|
|
55
|
+
if (!confirmed && Object.keys(baseline.providers).some(name => !Object.hasOwn(next.providers, name))) {
|
|
56
|
+
throw new CliUsageError("Provider removals or renames require --yes. Batch apply does not perform single-provider DELETE OAuth account cleanup.");
|
|
57
|
+
}
|
|
58
|
+
const body = serializeManagementJson({ baseline, next });
|
|
59
|
+
const baseUrl = await runtimeBaseUrl(deps);
|
|
60
|
+
const result = await runtimeRequest("/api/providers", {
|
|
61
|
+
method: "PUT", redirect: "error", headers: { "content-type": "application/json" }, body,
|
|
62
|
+
}, { ...deps, baseUrl });
|
|
63
|
+
return printProviderReceipt(result, wantsJson, "Provider batch");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Public editor snapshots and one server-authoritative compare-and-swap. */
|
|
67
|
+
export async function handleProviderBatchCommand(
|
|
68
|
+
sub: "snapshot" | "apply", args: string[], deps: RuntimeApiDeps = {},
|
|
69
|
+
): Promise<number> {
|
|
70
|
+
return runProviderAction(async () => {
|
|
71
|
+
const argv = [...args];
|
|
72
|
+
const wantsJson = takeFlag(argv, "--json");
|
|
73
|
+
if (sub === "apply") return apply(argv, wantsJson, deps);
|
|
74
|
+
if (argv.length) throw new CliUsageError("Provider snapshot accepts only --json.");
|
|
75
|
+
return snapshot(wantsJson, deps);
|
|
76
|
+
});
|
|
77
|
+
}
|