@alan-ai-hq/agent-manager 0.1.113 → 0.1.115
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/LICENSE +88 -0
- package/dist/__tests__/binding-switch-git.test.d.ts +2 -0
- package/dist/__tests__/binding-switch-git.test.d.ts.map +1 -0
- package/dist/__tests__/binding-switch-git.test.js +555 -0
- package/dist/__tests__/binding-switch-git.test.js.map +1 -0
- package/dist/__tests__/cli-ensure-install.test.d.ts +2 -0
- package/dist/__tests__/cli-ensure-install.test.d.ts.map +1 -0
- package/dist/__tests__/cli-ensure-install.test.js +177 -0
- package/dist/__tests__/cli-ensure-install.test.js.map +1 -0
- package/dist/__tests__/cli-executable.test.d.ts +2 -0
- package/dist/__tests__/cli-executable.test.d.ts.map +1 -0
- package/dist/__tests__/cli-executable.test.js +155 -0
- package/dist/__tests__/cli-executable.test.js.map +1 -0
- package/dist/__tests__/cli-executable.windows.test.d.ts +2 -0
- package/dist/__tests__/cli-executable.windows.test.d.ts.map +1 -0
- package/dist/__tests__/cli-executable.windows.test.js +123 -0
- package/dist/__tests__/cli-executable.windows.test.js.map +1 -0
- package/dist/__tests__/cli-help.test.d.ts +2 -0
- package/dist/__tests__/cli-help.test.d.ts.map +1 -0
- package/dist/__tests__/cli-help.test.js +47 -0
- package/dist/__tests__/cli-help.test.js.map +1 -0
- package/dist/__tests__/computer-readiness.test.d.ts +2 -0
- package/dist/__tests__/computer-readiness.test.d.ts.map +1 -0
- package/dist/__tests__/computer-readiness.test.js +63 -0
- package/dist/__tests__/computer-readiness.test.js.map +1 -0
- package/dist/__tests__/core-agent-provider-auth-env.test.d.ts +2 -0
- package/dist/__tests__/core-agent-provider-auth-env.test.d.ts.map +1 -0
- package/dist/__tests__/core-agent-provider-auth-env.test.js +93 -0
- package/dist/__tests__/core-agent-provider-auth-env.test.js.map +1 -0
- package/dist/__tests__/core-agent-workflow-context.test.d.ts +2 -0
- package/dist/__tests__/core-agent-workflow-context.test.d.ts.map +1 -0
- package/dist/__tests__/core-agent-workflow-context.test.js +139 -0
- package/dist/__tests__/core-agent-workflow-context.test.js.map +1 -0
- package/dist/__tests__/daemon-native-hooks.test.d.ts +2 -0
- package/dist/__tests__/daemon-native-hooks.test.d.ts.map +1 -0
- package/dist/__tests__/daemon-native-hooks.test.js +94 -0
- package/dist/__tests__/daemon-native-hooks.test.js.map +1 -0
- package/dist/__tests__/daemon-reconcile-ask.test.d.ts +2 -0
- package/dist/__tests__/daemon-reconcile-ask.test.d.ts.map +1 -0
- package/dist/__tests__/daemon-reconcile-ask.test.js +100 -0
- package/dist/__tests__/daemon-reconcile-ask.test.js.map +1 -0
- package/dist/__tests__/daemon-runner-status.test.d.ts +2 -0
- package/dist/__tests__/daemon-runner-status.test.d.ts.map +1 -0
- package/dist/__tests__/daemon-runner-status.test.js +102 -0
- package/dist/__tests__/daemon-runner-status.test.js.map +1 -0
- package/dist/__tests__/daemon-socket.integration.test.d.ts +2 -0
- package/dist/__tests__/daemon-socket.integration.test.d.ts.map +1 -0
- package/dist/__tests__/daemon-socket.integration.test.js +435 -0
- package/dist/__tests__/daemon-socket.integration.test.js.map +1 -0
- package/dist/__tests__/daemon.test.d.ts +2 -0
- package/dist/__tests__/daemon.test.d.ts.map +1 -0
- package/dist/__tests__/daemon.test.js +4884 -0
- package/dist/__tests__/daemon.test.js.map +1 -0
- package/dist/__tests__/ensure-shared-checkout-branch.test.d.ts +2 -0
- package/dist/__tests__/ensure-shared-checkout-branch.test.d.ts.map +1 -0
- package/dist/__tests__/ensure-shared-checkout-branch.test.js +129 -0
- package/dist/__tests__/ensure-shared-checkout-branch.test.js.map +1 -0
- package/dist/__tests__/event-journal-stress.test.d.ts +2 -0
- package/dist/__tests__/event-journal-stress.test.d.ts.map +1 -0
- package/dist/__tests__/event-journal-stress.test.js +124 -0
- package/dist/__tests__/event-journal-stress.test.js.map +1 -0
- package/dist/__tests__/event-journal.test.d.ts +2 -0
- package/dist/__tests__/event-journal.test.d.ts.map +1 -0
- package/dist/__tests__/event-journal.test.js +264 -0
- package/dist/__tests__/event-journal.test.js.map +1 -0
- package/dist/__tests__/event-outbox-persist-failure.test.d.ts +2 -0
- package/dist/__tests__/event-outbox-persist-failure.test.d.ts.map +1 -0
- package/dist/__tests__/event-outbox-persist-failure.test.js +143 -0
- package/dist/__tests__/event-outbox-persist-failure.test.js.map +1 -0
- package/dist/__tests__/event-outbox.test.d.ts +2 -0
- package/dist/__tests__/event-outbox.test.d.ts.map +1 -0
- package/dist/__tests__/event-outbox.test.js +602 -0
- package/dist/__tests__/event-outbox.test.js.map +1 -0
- package/dist/__tests__/execution-activity-tracker.test.d.ts +2 -0
- package/dist/__tests__/execution-activity-tracker.test.d.ts.map +1 -0
- package/dist/__tests__/execution-activity-tracker.test.js +64 -0
- package/dist/__tests__/execution-activity-tracker.test.js.map +1 -0
- package/dist/__tests__/execution-lease-client.test.d.ts +2 -0
- package/dist/__tests__/execution-lease-client.test.d.ts.map +1 -0
- package/dist/__tests__/execution-lease-client.test.js +274 -0
- package/dist/__tests__/execution-lease-client.test.js.map +1 -0
- package/dist/__tests__/fake-provider-cli.test.d.ts +2 -0
- package/dist/__tests__/fake-provider-cli.test.d.ts.map +1 -0
- package/dist/__tests__/fake-provider-cli.test.js +46 -0
- package/dist/__tests__/fake-provider-cli.test.js.map +1 -0
- package/dist/__tests__/generated-images.test.d.ts +2 -0
- package/dist/__tests__/generated-images.test.d.ts.map +1 -0
- package/dist/__tests__/generated-images.test.js +132 -0
- package/dist/__tests__/generated-images.test.js.map +1 -0
- package/dist/__tests__/helpers/sandbox-outbox.d.ts +3 -0
- package/dist/__tests__/helpers/sandbox-outbox.d.ts.map +1 -0
- package/dist/__tests__/helpers/sandbox-outbox.js +23 -0
- package/dist/__tests__/helpers/sandbox-outbox.js.map +1 -0
- package/dist/__tests__/install-command.test.d.ts +2 -0
- package/dist/__tests__/install-command.test.d.ts.map +1 -0
- package/dist/__tests__/install-command.test.js +185 -0
- package/dist/__tests__/install-command.test.js.map +1 -0
- package/dist/__tests__/integration-cli.test.d.ts +2 -0
- package/dist/__tests__/integration-cli.test.d.ts.map +1 -0
- package/dist/__tests__/integration-cli.test.js +150 -0
- package/dist/__tests__/integration-cli.test.js.map +1 -0
- package/dist/__tests__/lifecycle-contracts.test.d.ts +2 -0
- package/dist/__tests__/lifecycle-contracts.test.d.ts.map +1 -0
- package/dist/__tests__/lifecycle-contracts.test.js +66 -0
- package/dist/__tests__/lifecycle-contracts.test.js.map +1 -0
- package/dist/__tests__/lifecycle-logger.test.d.ts +2 -0
- package/dist/__tests__/lifecycle-logger.test.d.ts.map +1 -0
- package/dist/__tests__/lifecycle-logger.test.js +49 -0
- package/dist/__tests__/lifecycle-logger.test.js.map +1 -0
- package/dist/__tests__/local-daemon-lifecycle.test.d.ts +2 -0
- package/dist/__tests__/local-daemon-lifecycle.test.d.ts.map +1 -0
- package/dist/__tests__/local-daemon-lifecycle.test.js +594 -0
- package/dist/__tests__/local-daemon-lifecycle.test.js.map +1 -0
- package/dist/__tests__/local-git-status.test.d.ts +2 -0
- package/dist/__tests__/local-git-status.test.d.ts.map +1 -0
- package/dist/__tests__/local-git-status.test.js +31 -0
- package/dist/__tests__/local-git-status.test.js.map +1 -0
- package/dist/__tests__/mcp-config-adapters-files.test.d.ts +2 -0
- package/dist/__tests__/mcp-config-adapters-files.test.d.ts.map +1 -0
- package/dist/__tests__/mcp-config-adapters-files.test.js +486 -0
- package/dist/__tests__/mcp-config-adapters-files.test.js.map +1 -0
- package/dist/__tests__/mcp-config-adapters.test.d.ts +2 -0
- package/dist/__tests__/mcp-config-adapters.test.d.ts.map +1 -0
- package/dist/__tests__/mcp-config-adapters.test.js +492 -0
- package/dist/__tests__/mcp-config-adapters.test.js.map +1 -0
- package/dist/__tests__/mcp-sync.test.d.ts +2 -0
- package/dist/__tests__/mcp-sync.test.d.ts.map +1 -0
- package/dist/__tests__/mcp-sync.test.js +21 -0
- package/dist/__tests__/mcp-sync.test.js.map +1 -0
- package/dist/__tests__/model-scan-runner.test.d.ts +2 -0
- package/dist/__tests__/model-scan-runner.test.d.ts.map +1 -0
- package/dist/__tests__/model-scan-runner.test.js +80 -0
- package/dist/__tests__/model-scan-runner.test.js.map +1 -0
- package/dist/__tests__/native-hook-command.test.d.ts +2 -0
- package/dist/__tests__/native-hook-command.test.d.ts.map +1 -0
- package/dist/__tests__/native-hook-command.test.js +386 -0
- package/dist/__tests__/native-hook-command.test.js.map +1 -0
- package/dist/__tests__/native-hook-config.test.d.ts +2 -0
- package/dist/__tests__/native-hook-config.test.d.ts.map +1 -0
- package/dist/__tests__/native-hook-config.test.js +374 -0
- package/dist/__tests__/native-hook-config.test.js.map +1 -0
- package/dist/__tests__/native-hook-drain.test.d.ts +2 -0
- package/dist/__tests__/native-hook-drain.test.d.ts.map +1 -0
- package/dist/__tests__/native-hook-drain.test.js +197 -0
- package/dist/__tests__/native-hook-drain.test.js.map +1 -0
- package/dist/__tests__/native-hook-inbox.test.d.ts +2 -0
- package/dist/__tests__/native-hook-inbox.test.d.ts.map +1 -0
- package/dist/__tests__/native-hook-inbox.test.js +139 -0
- package/dist/__tests__/native-hook-inbox.test.js.map +1 -0
- package/dist/__tests__/native-session.test.d.ts +2 -0
- package/dist/__tests__/native-session.test.d.ts.map +1 -0
- package/dist/__tests__/native-session.test.js +41 -0
- package/dist/__tests__/native-session.test.js.map +1 -0
- package/dist/__tests__/native-stream-outbox.test.d.ts +2 -0
- package/dist/__tests__/native-stream-outbox.test.d.ts.map +1 -0
- package/dist/__tests__/native-stream-outbox.test.js +231 -0
- package/dist/__tests__/native-stream-outbox.test.js.map +1 -0
- package/dist/__tests__/native-stream-platform.test.d.ts +2 -0
- package/dist/__tests__/native-stream-platform.test.d.ts.map +1 -0
- package/dist/__tests__/native-stream-platform.test.js +20 -0
- package/dist/__tests__/native-stream-platform.test.js.map +1 -0
- package/dist/__tests__/native-transcript-cursor.test.d.ts +2 -0
- package/dist/__tests__/native-transcript-cursor.test.d.ts.map +1 -0
- package/dist/__tests__/native-transcript-cursor.test.js +60 -0
- package/dist/__tests__/native-transcript-cursor.test.js.map +1 -0
- package/dist/__tests__/native-transcript-stream.test.d.ts +2 -0
- package/dist/__tests__/native-transcript-stream.test.d.ts.map +1 -0
- package/dist/__tests__/native-transcript-stream.test.js +378 -0
- package/dist/__tests__/native-transcript-stream.test.js.map +1 -0
- package/dist/__tests__/network-diagnostics.test.d.ts +2 -0
- package/dist/__tests__/network-diagnostics.test.d.ts.map +1 -0
- package/dist/__tests__/network-diagnostics.test.js +26 -0
- package/dist/__tests__/network-diagnostics.test.js.map +1 -0
- package/dist/__tests__/observable-map.test.d.ts +2 -0
- package/dist/__tests__/observable-map.test.d.ts.map +1 -0
- package/dist/__tests__/observable-map.test.js +54 -0
- package/dist/__tests__/observable-map.test.js.map +1 -0
- package/dist/__tests__/outbox-baseline.bench.test.d.ts +2 -0
- package/dist/__tests__/outbox-baseline.bench.test.d.ts.map +1 -0
- package/dist/__tests__/outbox-baseline.bench.test.js +108 -0
- package/dist/__tests__/outbox-baseline.bench.test.js.map +1 -0
- package/dist/__tests__/provider-auth-preflight.test.d.ts +2 -0
- package/dist/__tests__/provider-auth-preflight.test.d.ts.map +1 -0
- package/dist/__tests__/provider-auth-preflight.test.js +123 -0
- package/dist/__tests__/provider-auth-preflight.test.js.map +1 -0
- package/dist/__tests__/provider-model-discovery.test.d.ts +2 -0
- package/dist/__tests__/provider-model-discovery.test.d.ts.map +1 -0
- package/dist/__tests__/provider-model-discovery.test.js +1071 -0
- package/dist/__tests__/provider-model-discovery.test.js.map +1 -0
- package/dist/__tests__/resource-diagnostics.test.d.ts +2 -0
- package/dist/__tests__/resource-diagnostics.test.d.ts.map +1 -0
- package/dist/__tests__/resource-diagnostics.test.js +138 -0
- package/dist/__tests__/resource-diagnostics.test.js.map +1 -0
- package/dist/__tests__/resume-fallback-env.test.d.ts +2 -0
- package/dist/__tests__/resume-fallback-env.test.d.ts.map +1 -0
- package/dist/__tests__/resume-fallback-env.test.js +28 -0
- package/dist/__tests__/resume-fallback-env.test.js.map +1 -0
- package/dist/__tests__/run-journal.test.d.ts +2 -0
- package/dist/__tests__/run-journal.test.d.ts.map +1 -0
- package/dist/__tests__/run-journal.test.js +121 -0
- package/dist/__tests__/run-journal.test.js.map +1 -0
- package/dist/__tests__/run-start-gate.test.d.ts +2 -0
- package/dist/__tests__/run-start-gate.test.d.ts.map +1 -0
- package/dist/__tests__/run-start-gate.test.js +18 -0
- package/dist/__tests__/run-start-gate.test.js.map +1 -0
- package/dist/__tests__/runtime-capabilities-sync.test.d.ts +2 -0
- package/dist/__tests__/runtime-capabilities-sync.test.d.ts.map +1 -0
- package/dist/__tests__/runtime-capabilities-sync.test.js +66 -0
- package/dist/__tests__/runtime-capabilities-sync.test.js.map +1 -0
- package/dist/__tests__/runtime-config.test.d.ts +2 -0
- package/dist/__tests__/runtime-config.test.d.ts.map +1 -0
- package/dist/__tests__/runtime-config.test.js +137 -0
- package/dist/__tests__/runtime-config.test.js.map +1 -0
- package/dist/__tests__/runtime-presenter-heartbeat.test.d.ts +2 -0
- package/dist/__tests__/runtime-presenter-heartbeat.test.d.ts.map +1 -0
- package/dist/__tests__/runtime-presenter-heartbeat.test.js +253 -0
- package/dist/__tests__/runtime-presenter-heartbeat.test.js.map +1 -0
- package/dist/__tests__/runtime-registration.test.d.ts +2 -0
- package/dist/__tests__/runtime-registration.test.d.ts.map +1 -0
- package/dist/__tests__/runtime-registration.test.js +109 -0
- package/dist/__tests__/runtime-registration.test.js.map +1 -0
- package/dist/__tests__/sandbox-outbox-storage.test.d.ts +2 -0
- package/dist/__tests__/sandbox-outbox-storage.test.d.ts.map +1 -0
- package/dist/__tests__/sandbox-outbox-storage.test.js +81 -0
- package/dist/__tests__/sandbox-outbox-storage.test.js.map +1 -0
- package/dist/__tests__/sandbox-outbox.test.d.ts +2 -0
- package/dist/__tests__/sandbox-outbox.test.d.ts.map +1 -0
- package/dist/__tests__/sandbox-outbox.test.js +300 -0
- package/dist/__tests__/sandbox-outbox.test.js.map +1 -0
- package/dist/__tests__/sandbox-workflow-context.test.d.ts +2 -0
- package/dist/__tests__/sandbox-workflow-context.test.d.ts.map +1 -0
- package/dist/__tests__/sandbox-workflow-context.test.js +600 -0
- package/dist/__tests__/sandbox-workflow-context.test.js.map +1 -0
- package/dist/__tests__/service-manager-deadline.test.d.ts +2 -0
- package/dist/__tests__/service-manager-deadline.test.d.ts.map +1 -0
- package/dist/__tests__/service-manager-deadline.test.js +36 -0
- package/dist/__tests__/service-manager-deadline.test.js.map +1 -0
- package/dist/__tests__/service-manager.test.d.ts +2 -0
- package/dist/__tests__/service-manager.test.d.ts.map +1 -0
- package/dist/__tests__/service-manager.test.js +769 -0
- package/dist/__tests__/service-manager.test.js.map +1 -0
- package/dist/__tests__/service-stop-ownership.test.d.ts +2 -0
- package/dist/__tests__/service-stop-ownership.test.d.ts.map +1 -0
- package/dist/__tests__/service-stop-ownership.test.js +89 -0
- package/dist/__tests__/service-stop-ownership.test.js.map +1 -0
- package/dist/__tests__/skill-invocation.test.d.ts +2 -0
- package/dist/__tests__/skill-invocation.test.d.ts.map +1 -0
- package/dist/__tests__/skill-invocation.test.js +40 -0
- package/dist/__tests__/skill-invocation.test.js.map +1 -0
- package/dist/__tests__/sleep-inhibitor-real.test.d.ts +2 -0
- package/dist/__tests__/sleep-inhibitor-real.test.d.ts.map +1 -0
- package/dist/__tests__/sleep-inhibitor-real.test.js +68 -0
- package/dist/__tests__/sleep-inhibitor-real.test.js.map +1 -0
- package/dist/__tests__/sleep-inhibitor.test.d.ts +2 -0
- package/dist/__tests__/sleep-inhibitor.test.d.ts.map +1 -0
- package/dist/__tests__/sleep-inhibitor.test.js +236 -0
- package/dist/__tests__/sleep-inhibitor.test.js.map +1 -0
- package/dist/__tests__/status-local-workspace.test.d.ts +2 -0
- package/dist/__tests__/status-local-workspace.test.d.ts.map +1 -0
- package/dist/__tests__/status-local-workspace.test.js +121 -0
- package/dist/__tests__/status-local-workspace.test.js.map +1 -0
- package/dist/__tests__/suspension-detector.test.d.ts +2 -0
- package/dist/__tests__/suspension-detector.test.d.ts.map +1 -0
- package/dist/__tests__/suspension-detector.test.js +112 -0
- package/dist/__tests__/suspension-detector.test.js.map +1 -0
- package/dist/__tests__/web-presenter-parent-events.test.d.ts +2 -0
- package/dist/__tests__/web-presenter-parent-events.test.d.ts.map +1 -0
- package/dist/__tests__/web-presenter-parent-events.test.js +168 -0
- package/dist/__tests__/web-presenter-parent-events.test.js.map +1 -0
- package/dist/__tests__/workspace-probe-concurrency.test.d.ts +2 -0
- package/dist/__tests__/workspace-probe-concurrency.test.d.ts.map +1 -0
- package/dist/__tests__/workspace-probe-concurrency.test.js +54 -0
- package/dist/__tests__/workspace-probe-concurrency.test.js.map +1 -0
- package/dist/__tests__/workspace-relocation.test.d.ts +2 -0
- package/dist/__tests__/workspace-relocation.test.d.ts.map +1 -0
- package/dist/__tests__/workspace-relocation.test.js +66 -0
- package/dist/__tests__/workspace-relocation.test.js.map +1 -0
- package/dist/__tests__/workspace-rpc.test.d.ts +2 -0
- package/dist/__tests__/workspace-rpc.test.d.ts.map +1 -0
- package/dist/__tests__/workspace-rpc.test.js +90 -0
- package/dist/__tests__/workspace-rpc.test.js.map +1 -0
- package/dist/__tests__/workspace-run-lease.test.d.ts +2 -0
- package/dist/__tests__/workspace-run-lease.test.d.ts.map +1 -0
- package/dist/__tests__/workspace-run-lease.test.js +331 -0
- package/dist/__tests__/workspace-run-lease.test.js.map +1 -0
- package/dist/__tests__/ws-client-connection.test.d.ts +2 -0
- package/dist/__tests__/ws-client-connection.test.d.ts.map +1 -0
- package/dist/__tests__/ws-client-connection.test.js +295 -0
- package/dist/__tests__/ws-client-connection.test.js.map +1 -0
- package/dist/__tests__/ws-client-heartbeat-volatile.test.d.ts +2 -0
- package/dist/__tests__/ws-client-heartbeat-volatile.test.d.ts.map +1 -0
- package/dist/__tests__/ws-client-heartbeat-volatile.test.js +63 -0
- package/dist/__tests__/ws-client-heartbeat-volatile.test.js.map +1 -0
- package/dist/__tests__/ws-client-heartbeat.integration.test.d.ts +2 -0
- package/dist/__tests__/ws-client-heartbeat.integration.test.d.ts.map +1 -0
- package/dist/__tests__/ws-client-heartbeat.integration.test.js +236 -0
- package/dist/__tests__/ws-client-heartbeat.integration.test.js.map +1 -0
- package/dist/__tests__/ws-client-user-message-ack.integration.test.d.ts +2 -0
- package/dist/__tests__/ws-client-user-message-ack.integration.test.d.ts.map +1 -0
- package/dist/__tests__/ws-client-user-message-ack.integration.test.js +211 -0
- package/dist/__tests__/ws-client-user-message-ack.integration.test.js.map +1 -0
- package/dist/antigravity-api-key-config.d.ts +2 -0
- package/dist/antigravity-api-key-config.d.ts.map +1 -0
- package/dist/antigravity-api-key-config.js +79 -0
- package/dist/antigravity-api-key-config.js.map +1 -0
- package/dist/binding-switch-git.d.ts +26 -0
- package/dist/binding-switch-git.d.ts.map +1 -0
- package/dist/binding-switch-git.js +871 -0
- package/dist/binding-switch-git.js.map +1 -0
- package/dist/cli-ensure-install.d.ts +34 -0
- package/dist/cli-ensure-install.d.ts.map +1 -0
- package/dist/cli-ensure-install.js +154 -0
- package/dist/cli-ensure-install.js.map +1 -0
- package/dist/cli-executable.d.ts +44 -0
- package/dist/cli-executable.d.ts.map +1 -0
- package/dist/cli-executable.js +435 -0
- package/dist/cli-executable.js.map +1 -0
- package/dist/cli-help.d.ts +11 -0
- package/dist/cli-help.d.ts.map +1 -0
- package/dist/cli-help.js +65 -0
- package/dist/cli-help.js.map +1 -0
- package/dist/computer-readiness.d.ts +41 -0
- package/dist/computer-readiness.d.ts.map +1 -0
- package/dist/computer-readiness.js +191 -0
- package/dist/computer-readiness.js.map +1 -0
- package/dist/core-agent.d.ts +32 -0
- package/dist/core-agent.d.ts.map +1 -0
- package/dist/core-agent.js +331 -0
- package/dist/core-agent.js.map +1 -0
- package/dist/daemon-execute.d.ts +42 -0
- package/dist/daemon-execute.d.ts.map +1 -0
- package/dist/daemon-execute.js +666 -0
- package/dist/daemon-execute.js.map +1 -0
- package/dist/daemon-helpers.d.ts +150 -0
- package/dist/daemon-helpers.d.ts.map +1 -0
- package/dist/daemon-helpers.js +157 -0
- package/dist/daemon-helpers.js.map +1 -0
- package/dist/daemon-local-control.d.ts +208 -0
- package/dist/daemon-local-control.d.ts.map +1 -0
- package/dist/daemon-local-control.js +826 -0
- package/dist/daemon-local-control.js.map +1 -0
- package/dist/daemon-local-http.d.ts +57 -0
- package/dist/daemon-local-http.d.ts.map +1 -0
- package/dist/daemon-local-http.js +375 -0
- package/dist/daemon-local-http.js.map +1 -0
- package/dist/daemon-native-hooks.d.ts +17 -0
- package/dist/daemon-native-hooks.d.ts.map +1 -0
- package/dist/daemon-native-hooks.js +54 -0
- package/dist/daemon-native-hooks.js.map +1 -0
- package/dist/daemon-provider-scan.d.ts +33 -0
- package/dist/daemon-provider-scan.d.ts.map +1 -0
- package/dist/daemon-provider-scan.js +187 -0
- package/dist/daemon-provider-scan.js.map +1 -0
- package/dist/daemon-runner-status.d.ts +164 -0
- package/dist/daemon-runner-status.d.ts.map +1 -0
- package/dist/daemon-runner-status.js +424 -0
- package/dist/daemon-runner-status.js.map +1 -0
- package/dist/daemon-start.d.ts +5 -0
- package/dist/daemon-start.d.ts.map +1 -0
- package/dist/daemon-start.js +1313 -0
- package/dist/daemon-start.js.map +1 -0
- package/dist/daemon-workspace.d.ts +36 -0
- package/dist/daemon-workspace.d.ts.map +1 -0
- package/dist/daemon-workspace.js +154 -0
- package/dist/daemon-workspace.js.map +1 -0
- package/dist/daemon.d.ts +59 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +681 -0
- package/dist/daemon.js.map +1 -0
- package/dist/event-journal-crypto.d.ts +14 -0
- package/dist/event-journal-crypto.d.ts.map +1 -0
- package/dist/event-journal-crypto.js +89 -0
- package/dist/event-journal-crypto.js.map +1 -0
- package/dist/event-journal-paths.d.ts +10 -0
- package/dist/event-journal-paths.d.ts.map +1 -0
- package/dist/event-journal-paths.js +29 -0
- package/dist/event-journal-paths.js.map +1 -0
- package/dist/event-journal-recovery.d.ts +83 -0
- package/dist/event-journal-recovery.d.ts.map +1 -0
- package/dist/event-journal-recovery.js +333 -0
- package/dist/event-journal-recovery.js.map +1 -0
- package/dist/event-journal-types.d.ts +54 -0
- package/dist/event-journal-types.d.ts.map +1 -0
- package/dist/event-journal-types.js +10 -0
- package/dist/event-journal-types.js.map +1 -0
- package/dist/event-journal-writer.d.ts +44 -0
- package/dist/event-journal-writer.d.ts.map +1 -0
- package/dist/event-journal-writer.js +182 -0
- package/dist/event-journal-writer.js.map +1 -0
- package/dist/event-outbox.d.ts +113 -0
- package/dist/event-outbox.d.ts.map +1 -0
- package/dist/event-outbox.js +707 -0
- package/dist/event-outbox.js.map +1 -0
- package/dist/execution-activity-tracker.d.ts +43 -0
- package/dist/execution-activity-tracker.d.ts.map +1 -0
- package/dist/execution-activity-tracker.js +116 -0
- package/dist/execution-activity-tracker.js.map +1 -0
- package/dist/execution-lease-client.d.ts +62 -0
- package/dist/execution-lease-client.d.ts.map +1 -0
- package/dist/execution-lease-client.js +271 -0
- package/dist/execution-lease-client.js.map +1 -0
- package/dist/fake-provider-cli.d.ts +13 -0
- package/dist/fake-provider-cli.d.ts.map +1 -0
- package/dist/fake-provider-cli.js +53 -0
- package/dist/fake-provider-cli.js.map +1 -0
- package/dist/generated-images.d.ts +2 -0
- package/dist/generated-images.d.ts.map +1 -0
- package/dist/generated-images.js +2 -0
- package/dist/generated-images.js.map +1 -0
- package/dist/index.cjs +47033 -45514
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +224 -0
- package/dist/index.js.map +1 -0
- package/dist/install-command.d.ts +2 -0
- package/dist/install-command.d.ts.map +1 -0
- package/dist/install-command.js +96 -0
- package/dist/install-command.js.map +1 -0
- package/dist/integration-cli.d.ts +14 -0
- package/dist/integration-cli.d.ts.map +1 -0
- package/dist/integration-cli.js +80 -0
- package/dist/integration-cli.js.map +1 -0
- package/dist/lifecycle-logger.d.ts +25 -0
- package/dist/lifecycle-logger.d.ts.map +1 -0
- package/dist/lifecycle-logger.js +73 -0
- package/dist/lifecycle-logger.js.map +1 -0
- package/dist/local-daemon-lifecycle.d.ts +112 -0
- package/dist/local-daemon-lifecycle.d.ts.map +1 -0
- package/dist/local-daemon-lifecycle.js +433 -0
- package/dist/local-daemon-lifecycle.js.map +1 -0
- package/dist/local-git-status.d.ts +14 -0
- package/dist/local-git-status.d.ts.map +1 -0
- package/dist/local-git-status.js +43 -0
- package/dist/local-git-status.js.map +1 -0
- package/dist/managed-mcp.d.ts +7 -0
- package/dist/managed-mcp.d.ts.map +1 -0
- package/dist/managed-mcp.js +16 -0
- package/dist/managed-mcp.js.map +1 -0
- package/dist/mcp-config-adapters.d.ts +57 -0
- package/dist/mcp-config-adapters.d.ts.map +1 -0
- package/dist/mcp-config-adapters.js +929 -0
- package/dist/mcp-config-adapters.js.map +1 -0
- package/dist/mcp-sync.d.ts +44 -0
- package/dist/mcp-sync.d.ts.map +1 -0
- package/dist/mcp-sync.js +68 -0
- package/dist/mcp-sync.js.map +1 -0
- package/dist/model-scan-runner.d.ts +15 -0
- package/dist/model-scan-runner.d.ts.map +1 -0
- package/dist/model-scan-runner.js +56 -0
- package/dist/model-scan-runner.js.map +1 -0
- package/dist/native-hook-command.d.ts +31 -0
- package/dist/native-hook-command.d.ts.map +1 -0
- package/dist/native-hook-command.js +289 -0
- package/dist/native-hook-command.js.map +1 -0
- package/dist/native-hook-config.d.ts +43 -0
- package/dist/native-hook-config.d.ts.map +1 -0
- package/dist/native-hook-config.js +601 -0
- package/dist/native-hook-config.js.map +1 -0
- package/dist/native-hook-drain.d.ts +34 -0
- package/dist/native-hook-drain.d.ts.map +1 -0
- package/dist/native-hook-drain.js +83 -0
- package/dist/native-hook-drain.js.map +1 -0
- package/dist/native-hook-inbox.d.ts +40 -0
- package/dist/native-hook-inbox.d.ts.map +1 -0
- package/dist/native-hook-inbox.js +217 -0
- package/dist/native-hook-inbox.js.map +1 -0
- package/dist/native-project-config.d.ts +11 -0
- package/dist/native-project-config.d.ts.map +1 -0
- package/dist/native-project-config.js +49 -0
- package/dist/native-project-config.js.map +1 -0
- package/dist/native-session.d.ts +20 -0
- package/dist/native-session.d.ts.map +1 -0
- package/dist/native-session.js +213 -0
- package/dist/native-session.js.map +1 -0
- package/dist/native-stream-journal.d.ts +22 -0
- package/dist/native-stream-journal.d.ts.map +1 -0
- package/dist/native-stream-journal.js +98 -0
- package/dist/native-stream-journal.js.map +1 -0
- package/dist/native-stream-outbox.d.ts +40 -0
- package/dist/native-stream-outbox.d.ts.map +1 -0
- package/dist/native-stream-outbox.js +358 -0
- package/dist/native-stream-outbox.js.map +1 -0
- package/dist/native-stream-platform.d.ts +4 -0
- package/dist/native-stream-platform.d.ts.map +1 -0
- package/dist/native-stream-platform.js +11 -0
- package/dist/native-stream-platform.js.map +1 -0
- package/dist/native-transcript-cursor.d.ts +33 -0
- package/dist/native-transcript-cursor.d.ts.map +1 -0
- package/dist/native-transcript-cursor.js +108 -0
- package/dist/native-transcript-cursor.js.map +1 -0
- package/dist/native-transcript-stream.d.ts +20 -0
- package/dist/native-transcript-stream.d.ts.map +1 -0
- package/dist/native-transcript-stream.js +438 -0
- package/dist/native-transcript-stream.js.map +1 -0
- package/dist/network-diagnostics.d.ts +8 -0
- package/dist/network-diagnostics.d.ts.map +1 -0
- package/dist/network-diagnostics.js +56 -0
- package/dist/network-diagnostics.js.map +1 -0
- package/dist/observable-map.d.ts +13 -0
- package/dist/observable-map.d.ts.map +1 -0
- package/dist/observable-map.js +30 -0
- package/dist/observable-map.js.map +1 -0
- package/dist/provider-auth-preflight.d.ts +19 -0
- package/dist/provider-auth-preflight.d.ts.map +1 -0
- package/dist/provider-auth-preflight.js +107 -0
- package/dist/provider-auth-preflight.js.map +1 -0
- package/dist/provider-model-discovery.d.ts +33 -0
- package/dist/provider-model-discovery.d.ts.map +1 -0
- package/dist/provider-model-discovery.js +731 -0
- package/dist/provider-model-discovery.js.map +1 -0
- package/dist/resource-diagnostics.d.ts +21 -0
- package/dist/resource-diagnostics.d.ts.map +1 -0
- package/dist/resource-diagnostics.js +161 -0
- package/dist/resource-diagnostics.js.map +1 -0
- package/dist/resume-fallback-env.d.ts +3 -0
- package/dist/resume-fallback-env.d.ts.map +1 -0
- package/dist/resume-fallback-env.js +14 -0
- package/dist/resume-fallback-env.js.map +1 -0
- package/dist/run-journal.d.ts +51 -0
- package/dist/run-journal.d.ts.map +1 -0
- package/dist/run-journal.js +193 -0
- package/dist/run-journal.js.map +1 -0
- package/dist/runtime-backends.d.ts +2 -0
- package/dist/runtime-backends.d.ts.map +1 -0
- package/dist/runtime-backends.js +2 -0
- package/dist/runtime-backends.js.map +1 -0
- package/dist/runtime-capabilities-sync.d.ts +40 -0
- package/dist/runtime-capabilities-sync.d.ts.map +1 -0
- package/dist/runtime-capabilities-sync.js +56 -0
- package/dist/runtime-capabilities-sync.js.map +1 -0
- package/dist/runtime-config.d.ts +26 -0
- package/dist/runtime-config.d.ts.map +1 -0
- package/dist/runtime-config.js +170 -0
- package/dist/runtime-config.js.map +1 -0
- package/dist/runtime-presenter.d.ts +81 -0
- package/dist/runtime-presenter.d.ts.map +1 -0
- package/dist/runtime-presenter.js +422 -0
- package/dist/runtime-presenter.js.map +1 -0
- package/dist/runtime-registration.d.ts +40 -0
- package/dist/runtime-registration.d.ts.map +1 -0
- package/dist/runtime-registration.js +143 -0
- package/dist/runtime-registration.js.map +1 -0
- package/dist/sandbox-outbox-storage.d.ts +13 -0
- package/dist/sandbox-outbox-storage.d.ts.map +1 -0
- package/dist/sandbox-outbox-storage.js +98 -0
- package/dist/sandbox-outbox-storage.js.map +1 -0
- package/dist/sandbox-outbox.d.ts +115 -0
- package/dist/sandbox-outbox.d.ts.map +1 -0
- package/dist/sandbox-outbox.js +213 -0
- package/dist/sandbox-outbox.js.map +1 -0
- package/dist/sandbox.d.ts +34 -0
- package/dist/sandbox.d.ts.map +1 -0
- package/dist/sandbox.js +469 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/service-manager.d.ts +113 -0
- package/dist/service-manager.d.ts.map +1 -0
- package/dist/service-manager.js +1065 -0
- package/dist/service-manager.js.map +1 -0
- package/dist/skills/__tests__/local-skill-source.test.d.ts +2 -0
- package/dist/skills/__tests__/local-skill-source.test.d.ts.map +1 -0
- package/dist/skills/__tests__/local-skill-source.test.js +373 -0
- package/dist/skills/__tests__/local-skill-source.test.js.map +1 -0
- package/dist/skills/__tests__/managed-skill-store.test.d.ts +2 -0
- package/dist/skills/__tests__/managed-skill-store.test.d.ts.map +1 -0
- package/dist/skills/__tests__/managed-skill-store.test.js +114 -0
- package/dist/skills/__tests__/managed-skill-store.test.js.map +1 -0
- package/dist/skills/__tests__/runtime-skill-coordinator.test.d.ts +2 -0
- package/dist/skills/__tests__/runtime-skill-coordinator.test.d.ts.map +1 -0
- package/dist/skills/__tests__/runtime-skill-coordinator.test.js +251 -0
- package/dist/skills/__tests__/runtime-skill-coordinator.test.js.map +1 -0
- package/dist/skills/__tests__/runtime-skill-manager.test.d.ts +2 -0
- package/dist/skills/__tests__/runtime-skill-manager.test.d.ts.map +1 -0
- package/dist/skills/__tests__/runtime-skill-manager.test.js +664 -0
- package/dist/skills/__tests__/runtime-skill-manager.test.js.map +1 -0
- package/dist/skills/__tests__/skill-bundle.test.d.ts +2 -0
- package/dist/skills/__tests__/skill-bundle.test.d.ts.map +1 -0
- package/dist/skills/__tests__/skill-bundle.test.js +42 -0
- package/dist/skills/__tests__/skill-bundle.test.js.map +1 -0
- package/dist/skills/__tests__/skill-control-client.test.d.ts +2 -0
- package/dist/skills/__tests__/skill-control-client.test.d.ts.map +1 -0
- package/dist/skills/__tests__/skill-control-client.test.js +49 -0
- package/dist/skills/__tests__/skill-control-client.test.js.map +1 -0
- package/dist/skills/__tests__/skill-source-config.test.d.ts +2 -0
- package/dist/skills/__tests__/skill-source-config.test.d.ts.map +1 -0
- package/dist/skills/__tests__/skill-source-config.test.js +295 -0
- package/dist/skills/__tests__/skill-source-config.test.js.map +1 -0
- package/dist/skills/local-skill-source.d.ts +77 -0
- package/dist/skills/local-skill-source.d.ts.map +1 -0
- package/dist/skills/local-skill-source.js +660 -0
- package/dist/skills/local-skill-source.js.map +1 -0
- package/dist/skills/managed-skill-store.d.ts +44 -0
- package/dist/skills/managed-skill-store.d.ts.map +1 -0
- package/dist/skills/managed-skill-store.js +160 -0
- package/dist/skills/managed-skill-store.js.map +1 -0
- package/dist/skills/runtime-skill-coordinator.d.ts +35 -0
- package/dist/skills/runtime-skill-coordinator.d.ts.map +1 -0
- package/dist/skills/runtime-skill-coordinator.js +166 -0
- package/dist/skills/runtime-skill-coordinator.js.map +1 -0
- package/dist/skills/runtime-skill-manager.d.ts +89 -0
- package/dist/skills/runtime-skill-manager.d.ts.map +1 -0
- package/dist/skills/runtime-skill-manager.js +934 -0
- package/dist/skills/runtime-skill-manager.js.map +1 -0
- package/dist/skills/skill-bundle.d.ts +2 -0
- package/dist/skills/skill-bundle.d.ts.map +1 -0
- package/dist/skills/skill-bundle.js +2 -0
- package/dist/skills/skill-bundle.js.map +1 -0
- package/dist/skills/skill-cli.d.ts +2 -0
- package/dist/skills/skill-cli.d.ts.map +1 -0
- package/dist/skills/skill-cli.js +81 -0
- package/dist/skills/skill-cli.js.map +1 -0
- package/dist/skills/skill-control-client.d.ts +20 -0
- package/dist/skills/skill-control-client.d.ts.map +1 -0
- package/dist/skills/skill-control-client.js +56 -0
- package/dist/skills/skill-control-client.js.map +1 -0
- package/dist/skills/skill-invocation.d.ts +10 -0
- package/dist/skills/skill-invocation.d.ts.map +1 -0
- package/dist/skills/skill-invocation.js +24 -0
- package/dist/skills/skill-invocation.js.map +1 -0
- package/dist/sleep-inhibitor.d.ts +32 -0
- package/dist/sleep-inhibitor.d.ts.map +1 -0
- package/dist/sleep-inhibitor.js +297 -0
- package/dist/sleep-inhibitor.js.map +1 -0
- package/dist/suspension-detector.d.ts +24 -0
- package/dist/suspension-detector.d.ts.map +1 -0
- package/dist/suspension-detector.js +59 -0
- package/dist/suspension-detector.js.map +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/dist/web-presenter.d.ts +52 -0
- package/dist/web-presenter.d.ts.map +1 -0
- package/dist/web-presenter.js +264 -0
- package/dist/web-presenter.js.map +1 -0
- package/dist/workspace-relocation.d.ts +22 -0
- package/dist/workspace-relocation.d.ts.map +1 -0
- package/dist/workspace-relocation.js +90 -0
- package/dist/workspace-relocation.js.map +1 -0
- package/dist/workspace-rpc.d.ts +2 -0
- package/dist/workspace-rpc.d.ts.map +1 -0
- package/dist/workspace-rpc.js +15 -0
- package/dist/workspace-rpc.js.map +1 -0
- package/dist/workspace-run-lease.d.ts +116 -0
- package/dist/workspace-run-lease.d.ts.map +1 -0
- package/dist/workspace-run-lease.js +320 -0
- package/dist/workspace-run-lease.js.map +1 -0
- package/dist/ws-client.d.ts +168 -0
- package/dist/ws-client.d.ts.map +1 -0
- package/dist/ws-client.js +440 -0
- package/dist/ws-client.js.map +1 -0
- package/package.json +4 -3
|
@@ -0,0 +1,1071 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdirSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { collapseCursorDiscoveredModelIds } from "@alan-ai/shared/runtime-discovered-models";
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
7
|
+
import * as cli from "../cli-executable.js";
|
|
8
|
+
import { applyZenEntitlement, discoverProviderModelDetails, discoverProviderModels, extractClaudeModelIdsFromExecutable, extractCodexDiscoveredModelIdsFromDebugJson, extractCodexModelSlugsFromDebugJson, extractGrokModelIdsFromModelsOutput, extractModelIdsFromJson, extractModelIdsFromLabeledList, extractOpenCodeDiscoveredModelIdsFromVerboseOutput, extractOpenCodeModelBillingFromVerboseOutput, extractOpenCodeModelCapabilitiesFromVerboseOutput, filterClaudeDatedModelIds, filterClaudePrefixModelIds, getCatalogModelIds, isValidClaudeModelId, readConfiguredOpenCodeProviders, } from "../provider-model-discovery.js";
|
|
9
|
+
// Captured from a live `grok models` run against grok 1.0.3 (2026-08-14).
|
|
10
|
+
const GROK_MODELS_FIXTURE = `You are logged in with grok.com.
|
|
11
|
+
|
|
12
|
+
Default model: grok-4.6
|
|
13
|
+
|
|
14
|
+
Available models:
|
|
15
|
+
* grok-4.6 (default)
|
|
16
|
+
- grok-4.5
|
|
17
|
+
`;
|
|
18
|
+
// big-pickle and deepseek-v4-flash-free blocks are trimmed but otherwise verbatim
|
|
19
|
+
// captures from a real `opencode models --verbose` (opencode 1.17.18) run — see
|
|
20
|
+
// notes/opencode-cli-reverse-engineering.md. some-non-reasoning-model is synthetic
|
|
21
|
+
// (no reasoning:false model was visible in the live capture, only free zen models
|
|
22
|
+
// were) but follows the exact same schema, to cover the "skip entirely" path.
|
|
23
|
+
const OPENCODE_VERBOSE_FIXTURE = `opencode/big-pickle
|
|
24
|
+
{
|
|
25
|
+
"id": "big-pickle",
|
|
26
|
+
"providerID": "opencode",
|
|
27
|
+
"name": "Big Pickle",
|
|
28
|
+
"capabilities": {
|
|
29
|
+
"temperature": true,
|
|
30
|
+
"reasoning": true,
|
|
31
|
+
"attachment": false,
|
|
32
|
+
"toolcall": true
|
|
33
|
+
},
|
|
34
|
+
"release_date": "2025-10-17",
|
|
35
|
+
"variants": {}
|
|
36
|
+
}
|
|
37
|
+
opencode/deepseek-v4-flash-free
|
|
38
|
+
{
|
|
39
|
+
"id": "deepseek-v4-flash-free",
|
|
40
|
+
"providerID": "opencode",
|
|
41
|
+
"name": "DeepSeek V4 Flash Free",
|
|
42
|
+
"capabilities": {
|
|
43
|
+
"temperature": true,
|
|
44
|
+
"reasoning": true,
|
|
45
|
+
"attachment": false,
|
|
46
|
+
"toolcall": true
|
|
47
|
+
},
|
|
48
|
+
"release_date": "2026-04-24",
|
|
49
|
+
"variants": {
|
|
50
|
+
"low": { "reasoningEffort": "low" },
|
|
51
|
+
"medium": { "reasoningEffort": "medium" },
|
|
52
|
+
"high": { "reasoningEffort": "high" },
|
|
53
|
+
"max": { "reasoningEffort": "max" }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
opencode/some-non-reasoning-model
|
|
57
|
+
{
|
|
58
|
+
"id": "some-non-reasoning-model",
|
|
59
|
+
"providerID": "opencode",
|
|
60
|
+
"name": "Some Non Reasoning Model",
|
|
61
|
+
"capabilities": {
|
|
62
|
+
"temperature": true,
|
|
63
|
+
"reasoning": false,
|
|
64
|
+
"attachment": false,
|
|
65
|
+
"toolcall": true
|
|
66
|
+
},
|
|
67
|
+
"release_date": "2026-01-01",
|
|
68
|
+
"variants": {}
|
|
69
|
+
}
|
|
70
|
+
`;
|
|
71
|
+
/**
|
|
72
|
+
* Discovery also hydrates missing metadata from `$XDG_CACHE_HOME/opencode/models.json`.
|
|
73
|
+
* Passing an empty env would fall back to `~/.cache`, so on a machine that has actually
|
|
74
|
+
* run opencode these tests would read the developer's real 94-model catalogue and their
|
|
75
|
+
* exact-equality assertions would depend on whoever ran them. Point every case at a
|
|
76
|
+
* directory that does not exist.
|
|
77
|
+
*/
|
|
78
|
+
const NO_CACHE_ENV = { XDG_CACHE_HOME: join(tmpdir(), `opencode-no-cache-${randomUUID()}`) };
|
|
79
|
+
// OpenCode discovery also asks Zen which models the credential can dispatch. Default
|
|
80
|
+
// every test to offline so the CLI-scan cases assert the raw scan and no test can
|
|
81
|
+
// silently depend on opencode.ai; the entitlement cases stub `fetch` explicitly.
|
|
82
|
+
beforeEach(() => {
|
|
83
|
+
vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("offline"));
|
|
84
|
+
});
|
|
85
|
+
afterEach(() => {
|
|
86
|
+
vi.restoreAllMocks();
|
|
87
|
+
});
|
|
88
|
+
describe("provider-model-discovery", () => {
|
|
89
|
+
it("extracts model ids from JSON arrays and envelopes", () => {
|
|
90
|
+
expect(extractModelIdsFromJson([{ id: "gpt-5.5" }, { model: "claude-sonnet-5" }])).toEqual([
|
|
91
|
+
"gpt-5.5",
|
|
92
|
+
"claude-sonnet-5",
|
|
93
|
+
]);
|
|
94
|
+
expect(extractModelIdsFromJson({ models: [{ id: "opencode/big-pickle" }] })).toEqual([
|
|
95
|
+
"opencode/big-pickle",
|
|
96
|
+
]);
|
|
97
|
+
});
|
|
98
|
+
it("keeps OpenCode model ids separate from their native variants", () => {
|
|
99
|
+
expect(extractOpenCodeDiscoveredModelIdsFromVerboseOutput(OPENCODE_VERBOSE_FIXTURE)).toEqual([
|
|
100
|
+
"opencode/big-pickle",
|
|
101
|
+
"opencode/deepseek-v4-flash-free",
|
|
102
|
+
"opencode/some-non-reasoning-model",
|
|
103
|
+
]);
|
|
104
|
+
});
|
|
105
|
+
it("reads OpenCode billing from live cost data, not model names", () => {
|
|
106
|
+
const text = [
|
|
107
|
+
"opencode/big-pickle",
|
|
108
|
+
"{",
|
|
109
|
+
' "cost": { "input": 0, "output": 0 },',
|
|
110
|
+
' "capabilities": { "reasoning": true },',
|
|
111
|
+
' "variants": {}',
|
|
112
|
+
"}",
|
|
113
|
+
"opencode/future-model-without-a-free-suffix",
|
|
114
|
+
"{",
|
|
115
|
+
' "cost": { "input": 0.25, "output": 0.5 },',
|
|
116
|
+
' "capabilities": { "reasoning": false },',
|
|
117
|
+
' "variants": {}',
|
|
118
|
+
"}",
|
|
119
|
+
].join("\n");
|
|
120
|
+
expect(extractOpenCodeModelBillingFromVerboseOutput(text)).toEqual({
|
|
121
|
+
"opencode/big-pickle": "free",
|
|
122
|
+
"opencode/future-model-without-a-free-suffix": "paid",
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
it("carries verbose OpenCode billing alongside the discovered ids", async () => {
|
|
126
|
+
const probe = vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
|
|
127
|
+
status: 0,
|
|
128
|
+
stdout: [
|
|
129
|
+
"opencode/big-pickle",
|
|
130
|
+
"{",
|
|
131
|
+
' "cost": { "input": 0, "output": 0 },',
|
|
132
|
+
' "capabilities": { "reasoning": true },',
|
|
133
|
+
' "variants": { "none": {}, "extended-thinking": {} }',
|
|
134
|
+
"}",
|
|
135
|
+
].join("\n"),
|
|
136
|
+
stderr: "",
|
|
137
|
+
error: undefined,
|
|
138
|
+
});
|
|
139
|
+
try {
|
|
140
|
+
await expect(discoverProviderModelDetails("opencode_cli", "/bin/opencode", NO_CACHE_ENV)).resolves.toEqual({
|
|
141
|
+
models: ["opencode/big-pickle"],
|
|
142
|
+
modelVariants: { "opencode/big-pickle": ["none", "extended-thinking"] },
|
|
143
|
+
modelBilling: { "opencode/big-pickle": "free" },
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
probe.mockRestore();
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
it("is not confused by a brace inside a JSON string value", () => {
|
|
151
|
+
const text = [
|
|
152
|
+
"opencode/weird-name",
|
|
153
|
+
"{",
|
|
154
|
+
' "id": "weird-name",',
|
|
155
|
+
' "name": "Uses a { brace } in its name",',
|
|
156
|
+
' "capabilities": { "reasoning": true },',
|
|
157
|
+
' "variants": { "high": { "reasoningEffort": "high" } }',
|
|
158
|
+
"}",
|
|
159
|
+
].join("\n");
|
|
160
|
+
expect(extractOpenCodeDiscoveredModelIdsFromVerboseOutput(text)).toEqual([
|
|
161
|
+
"opencode/weird-name",
|
|
162
|
+
]);
|
|
163
|
+
expect(extractOpenCodeModelCapabilitiesFromVerboseOutput(text)).toEqual([
|
|
164
|
+
{ id: "opencode/weird-name", variants: ["high"] },
|
|
165
|
+
]);
|
|
166
|
+
});
|
|
167
|
+
it("retains a provider-defined variant without corrupting the base id", () => {
|
|
168
|
+
const text = [
|
|
169
|
+
"opencode/future-model",
|
|
170
|
+
"{",
|
|
171
|
+
' "capabilities": { "reasoning": true },',
|
|
172
|
+
' "variants": { "extreme": { "reasoningEffort": "extreme" } }',
|
|
173
|
+
"}",
|
|
174
|
+
].join("\n");
|
|
175
|
+
expect(extractOpenCodeDiscoveredModelIdsFromVerboseOutput(text)).toEqual([
|
|
176
|
+
"opencode/future-model",
|
|
177
|
+
]);
|
|
178
|
+
expect(extractOpenCodeModelCapabilitiesFromVerboseOutput(text)).toEqual([
|
|
179
|
+
{ id: "opencode/future-model", variants: ["extreme"] },
|
|
180
|
+
]);
|
|
181
|
+
});
|
|
182
|
+
it("parses codex debug models JSON slugs", () => {
|
|
183
|
+
expect(extractCodexModelSlugsFromDebugJson({
|
|
184
|
+
models: [
|
|
185
|
+
{ slug: "gpt-5.5", visibility: "list" },
|
|
186
|
+
{ slug: "codex-auto-review", visibility: "hidden" },
|
|
187
|
+
{ slug: "gpt-5.4-mini", visibility: "hide" },
|
|
188
|
+
],
|
|
189
|
+
})).toEqual(["gpt-5.5"]);
|
|
190
|
+
});
|
|
191
|
+
it("expands codex supported reasoning levels into effort-suffixed scan ids", () => {
|
|
192
|
+
expect(extractCodexDiscoveredModelIdsFromDebugJson({
|
|
193
|
+
models: [
|
|
194
|
+
{
|
|
195
|
+
slug: "gpt-5.6-sol",
|
|
196
|
+
visibility: "list",
|
|
197
|
+
supported_reasoning_levels: [
|
|
198
|
+
{ effort: "low" },
|
|
199
|
+
{ effort: "medium" },
|
|
200
|
+
{ effort: "high" },
|
|
201
|
+
{ effort: "xhigh" },
|
|
202
|
+
{ effort: "max" },
|
|
203
|
+
{ effort: "ultra" },
|
|
204
|
+
],
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
slug: "gpt-5.6-luna",
|
|
208
|
+
visibility: "list",
|
|
209
|
+
supported_reasoning_levels: [{ effort: "low" }, { effort: "high" }],
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
})).toEqual([
|
|
213
|
+
"gpt-5.6-sol-low",
|
|
214
|
+
"gpt-5.6-sol-medium",
|
|
215
|
+
"gpt-5.6-sol-high",
|
|
216
|
+
"gpt-5.6-sol-xhigh",
|
|
217
|
+
"gpt-5.6-sol-max",
|
|
218
|
+
"gpt-5.6-sol-ultra",
|
|
219
|
+
"gpt-5.6-luna-low",
|
|
220
|
+
"gpt-5.6-luna-high",
|
|
221
|
+
]);
|
|
222
|
+
});
|
|
223
|
+
it("parses cursor-agent style labeled model lists", () => {
|
|
224
|
+
const stdout = [
|
|
225
|
+
"Available models",
|
|
226
|
+
"",
|
|
227
|
+
"auto - Auto (current, default)",
|
|
228
|
+
"gpt-5.3-codex - Codex 5.3",
|
|
229
|
+
"composer-2.5-fast - Composer 2.5 Fast",
|
|
230
|
+
"Tip: use --model <id> to switch.",
|
|
231
|
+
].join("\n");
|
|
232
|
+
expect(extractModelIdsFromLabeledList(stdout)).toEqual([
|
|
233
|
+
"auto",
|
|
234
|
+
"gpt-5.3-codex",
|
|
235
|
+
"composer-2.5-fast",
|
|
236
|
+
]);
|
|
237
|
+
});
|
|
238
|
+
it("parses grok models text without treating the login banner as a model id", () => {
|
|
239
|
+
expect(extractGrokModelIdsFromModelsOutput(GROK_MODELS_FIXTURE)).toEqual([
|
|
240
|
+
"grok-4.6",
|
|
241
|
+
"grok-4.5",
|
|
242
|
+
]);
|
|
243
|
+
});
|
|
244
|
+
it("falls back to Default model when the bullet list is missing", () => {
|
|
245
|
+
expect(extractGrokModelIdsFromModelsOutput("You are logged in with grok.com.\n\nDefault model: grok-4.6\n")).toEqual(["grok-4.6"]);
|
|
246
|
+
});
|
|
247
|
+
it("parses tab-separated agy model rows without keeping display labels", () => {
|
|
248
|
+
const stdout = [
|
|
249
|
+
"Fetching available models...",
|
|
250
|
+
"gemini-3.6-flash-high\tGemini 3.6 Flash (High)",
|
|
251
|
+
"claude-sonnet-4-6\tClaude Sonnet 4.6 (Thinking)",
|
|
252
|
+
].join("\n");
|
|
253
|
+
expect(extractModelIdsFromLabeledList(stdout)).toEqual([
|
|
254
|
+
"gemini-3.6-flash-high",
|
|
255
|
+
"claude-sonnet-4-6",
|
|
256
|
+
]);
|
|
257
|
+
});
|
|
258
|
+
it("drops short claude id prefixes when a longer sibling exists", () => {
|
|
259
|
+
expect(filterClaudePrefixModelIds(["claude-opus-4", "claude-opus-4-6", "claude-sonnet-4-6"])).toEqual(["claude-opus-4-6", "claude-sonnet-4-6"]);
|
|
260
|
+
});
|
|
261
|
+
it("rejects invalid claude scrape tokens", () => {
|
|
262
|
+
expect(isValidClaudeModelId("claude-fable-5.md")).toBe(false);
|
|
263
|
+
expect(isValidClaudeModelId("claude-sonnet-4.6")).toBe(false);
|
|
264
|
+
expect(isValidClaudeModelId("claude-opus-4-6-20251101")).toBe(false);
|
|
265
|
+
expect(isValidClaudeModelId("claude-opus-4-8")).toBe(true);
|
|
266
|
+
expect(isValidClaudeModelId("sonnet")).toBe(true);
|
|
267
|
+
});
|
|
268
|
+
it("rejects internal binary codenames scraped across hyphens", () => {
|
|
269
|
+
// The greedy scrape regex ran into an internal string in the Claude binary and
|
|
270
|
+
// produced "claude-fable-5-mythos-5", which then evicted the real "claude-fable-5".
|
|
271
|
+
expect(isValidClaudeModelId("claude-fable-5-mythos-5")).toBe(false);
|
|
272
|
+
// Known non-numeric qualifiers stay valid.
|
|
273
|
+
expect(isValidClaudeModelId("claude-opus-4-6-fast")).toBe(true);
|
|
274
|
+
expect(isValidClaudeModelId("claude-sonnet-5-thinking")).toBe(true);
|
|
275
|
+
// A real base model survives.
|
|
276
|
+
expect(isValidClaudeModelId("claude-fable-5")).toBe(true);
|
|
277
|
+
});
|
|
278
|
+
it("never evicts a curated catalogue id via the prefix filter", () => {
|
|
279
|
+
// Even if a bogus longer sibling slips through, a shipped catalogue model must remain.
|
|
280
|
+
const result = filterClaudePrefixModelIds(["claude-fable-5", "claude-fable-5-mythos-5"]);
|
|
281
|
+
expect(result).toContain("claude-fable-5");
|
|
282
|
+
});
|
|
283
|
+
it("drops dated claude ids when an undated sibling exists", () => {
|
|
284
|
+
expect(filterClaudeDatedModelIds([
|
|
285
|
+
"claude-opus-4-6",
|
|
286
|
+
"claude-opus-4-6-20251101",
|
|
287
|
+
"claude-sonnet-4-5-20250929",
|
|
288
|
+
])).toEqual(["claude-opus-4-6", "claude-sonnet-4-5-20250929"]);
|
|
289
|
+
});
|
|
290
|
+
it("extracts claude model ids embedded in the executable", () => {
|
|
291
|
+
const tmp = join(tmpdir(), `claude-exe-${randomUUID()}`);
|
|
292
|
+
writeFileSync(tmp, Buffer.from("prefix claude-sonnet-4-6 middle claude-opus-4 claude-opus-4-8 claude-fable-5.md suffix", "latin1"));
|
|
293
|
+
try {
|
|
294
|
+
expect(extractClaudeModelIdsFromExecutable(tmp)).toEqual(expect.arrayContaining([
|
|
295
|
+
"claude-sonnet-4-6",
|
|
296
|
+
"claude-opus-4-8",
|
|
297
|
+
"sonnet",
|
|
298
|
+
"opus",
|
|
299
|
+
"haiku",
|
|
300
|
+
"fable",
|
|
301
|
+
]));
|
|
302
|
+
expect(extractClaudeModelIdsFromExecutable(tmp)).not.toEqual(expect.arrayContaining(["claude-fable-5.md", "claude-opus-4"]));
|
|
303
|
+
}
|
|
304
|
+
finally {
|
|
305
|
+
unlinkSync(tmp);
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
it("returns seed catalog ids for known providers", () => {
|
|
309
|
+
expect(getCatalogModelIds("claude_cli")).toEqual([""]);
|
|
310
|
+
expect(getCatalogModelIds("codex_app_server")).toEqual([]);
|
|
311
|
+
expect(getCatalogModelIds("antigravity_cli")).toEqual([""]);
|
|
312
|
+
expect(getCatalogModelIds("droid_cli").length).toBeGreaterThan(0);
|
|
313
|
+
expect(getCatalogModelIds("copilot_cli").length).toBeGreaterThan(0);
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
describe("discoverProviderModels droid", () => {
|
|
317
|
+
afterEach(() => {
|
|
318
|
+
vi.restoreAllMocks();
|
|
319
|
+
});
|
|
320
|
+
it("uses catalog models without probing the interactive TUI", async () => {
|
|
321
|
+
const probe = vi.spyOn(cli, "runCliProbeAsync");
|
|
322
|
+
const models = await discoverProviderModels("droid_cli", "/bin/droid", {});
|
|
323
|
+
expect(models).toEqual(getCatalogModelIds("droid_cli"));
|
|
324
|
+
expect(probe).not.toHaveBeenCalled();
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
describe("discoverProviderModels copilot", () => {
|
|
328
|
+
afterEach(() => {
|
|
329
|
+
vi.restoreAllMocks();
|
|
330
|
+
});
|
|
331
|
+
it("uses catalog models without probing — Copilot CLI 1.0.75 has no non-interactive model list", async () => {
|
|
332
|
+
const probe = vi.spyOn(cli, "runCliProbeAsync");
|
|
333
|
+
const models = await discoverProviderModels("copilot_cli", "/opt/homebrew/bin/copilot", {});
|
|
334
|
+
expect(models).toEqual(getCatalogModelIds("copilot_cli"));
|
|
335
|
+
expect(probe).not.toHaveBeenCalled();
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
describe("discoverProviderModels", () => {
|
|
339
|
+
afterEach(() => {
|
|
340
|
+
vi.restoreAllMocks();
|
|
341
|
+
});
|
|
342
|
+
it("probes codex debug models and expands reasoning levels for effort merge", async () => {
|
|
343
|
+
vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
|
|
344
|
+
status: 0,
|
|
345
|
+
stdout: JSON.stringify({
|
|
346
|
+
models: [
|
|
347
|
+
{
|
|
348
|
+
slug: "gpt-5.6-sol",
|
|
349
|
+
visibility: "list",
|
|
350
|
+
supported_reasoning_levels: [{ effort: "low" }, { effort: "high" }],
|
|
351
|
+
},
|
|
352
|
+
{ slug: "gpt-5.3-codex-spark", visibility: "list" },
|
|
353
|
+
],
|
|
354
|
+
}),
|
|
355
|
+
stderr: "",
|
|
356
|
+
error: undefined,
|
|
357
|
+
});
|
|
358
|
+
const models = await discoverProviderModels("codex_app_server", "/bin/codex", {});
|
|
359
|
+
expect(models).toEqual(["gpt-5.6-sol-low", "gpt-5.6-sol-high", "gpt-5.3-codex-spark"]);
|
|
360
|
+
expect(cli.runCliProbeAsync).toHaveBeenCalledWith("/bin/codex", expect.objectContaining({ TERM: "xterm-256color" }), ["debug", "models"], 8_000, { maxOutputChars: 1024 * 1024 });
|
|
361
|
+
});
|
|
362
|
+
it("does not offer a truncated Codex JSON document as a model", async () => {
|
|
363
|
+
vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
|
|
364
|
+
status: 0,
|
|
365
|
+
stdout: `{"models":[{"slug":"gpt-5.6-sol","description":"${"x".repeat(200)}`,
|
|
366
|
+
stderr: "",
|
|
367
|
+
error: undefined,
|
|
368
|
+
});
|
|
369
|
+
const models = await discoverProviderModels("codex_app_server", "/bin/codex", {});
|
|
370
|
+
expect(models).toEqual(getCatalogModelIds("codex_app_server"));
|
|
371
|
+
});
|
|
372
|
+
it("probes antigravity with stdin closed", async () => {
|
|
373
|
+
vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
|
|
374
|
+
status: 0,
|
|
375
|
+
stdout: [
|
|
376
|
+
"gemini-3.6-flash-medium\tGemini 3.6 Flash (Medium)",
|
|
377
|
+
"claude-sonnet-4-6\tClaude Sonnet 4.6 (Thinking)",
|
|
378
|
+
].join("\n"),
|
|
379
|
+
stderr: "",
|
|
380
|
+
error: undefined,
|
|
381
|
+
});
|
|
382
|
+
const models = await discoverProviderModels("antigravity_cli", "/bin/agy", {});
|
|
383
|
+
expect(models).toEqual(["gemini-3.6-flash-medium", "claude-sonnet-4-6"]);
|
|
384
|
+
expect(cli.runCliProbeAsync).toHaveBeenCalledWith("/bin/agy", expect.any(Object), ["models"], 12_000, { closeStdin: true });
|
|
385
|
+
});
|
|
386
|
+
it("probes opencode models --verbose and returns dispatchable base ids", async () => {
|
|
387
|
+
vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
|
|
388
|
+
status: 0,
|
|
389
|
+
stdout: OPENCODE_VERBOSE_FIXTURE,
|
|
390
|
+
stderr: "",
|
|
391
|
+
error: undefined,
|
|
392
|
+
});
|
|
393
|
+
const models = await discoverProviderModels("opencode_cli", "/bin/opencode", {
|
|
394
|
+
...NO_CACHE_ENV,
|
|
395
|
+
OPENCODE_API_KEY: "sk-live",
|
|
396
|
+
});
|
|
397
|
+
expect(models).toEqual([
|
|
398
|
+
"opencode/big-pickle",
|
|
399
|
+
"opencode/deepseek-v4-flash-free",
|
|
400
|
+
"opencode/some-non-reasoning-model",
|
|
401
|
+
]);
|
|
402
|
+
// The real working command — NOT the `--json`/`list --json` shapes this backend
|
|
403
|
+
// used to (wrongly) probe, which exit 1 against the real CLI.
|
|
404
|
+
expect(cli.runCliProbeAsync).toHaveBeenCalledWith("/bin/opencode", expect.any(Object), ["models", "--verbose"], 8_000);
|
|
405
|
+
});
|
|
406
|
+
it("reuses the OpenCode model scan for opencode_serve", async () => {
|
|
407
|
+
vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
|
|
408
|
+
status: 0,
|
|
409
|
+
stdout: OPENCODE_VERBOSE_FIXTURE,
|
|
410
|
+
stderr: "",
|
|
411
|
+
error: undefined,
|
|
412
|
+
});
|
|
413
|
+
const models = await discoverProviderModels("opencode_serve", "/bin/opencode", NO_CACHE_ENV);
|
|
414
|
+
expect(models).toEqual(expect.arrayContaining(["opencode/big-pickle"]));
|
|
415
|
+
expect(cli.runCliProbeAsync).toHaveBeenCalledWith("/bin/opencode", expect.any(Object), ["models", "--verbose"], 8_000);
|
|
416
|
+
});
|
|
417
|
+
it("falls back to the static OpenCode catalog when the CLI probe fails entirely", async () => {
|
|
418
|
+
vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
|
|
419
|
+
status: 1,
|
|
420
|
+
stdout: "",
|
|
421
|
+
stderr: "unknown flag",
|
|
422
|
+
error: undefined,
|
|
423
|
+
});
|
|
424
|
+
const models = await discoverProviderModels("opencode_cli", "/bin/opencode", {
|
|
425
|
+
...NO_CACHE_ENV,
|
|
426
|
+
OPENCODE_API_KEY: "sk-live",
|
|
427
|
+
});
|
|
428
|
+
expect(models.length).toBeGreaterThan(0);
|
|
429
|
+
expect(models).toEqual(expect.arrayContaining(["opencode/big-pickle"]));
|
|
430
|
+
});
|
|
431
|
+
it("probes grok models text and returns live ids including grok-4.6", async () => {
|
|
432
|
+
vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
|
|
433
|
+
status: 0,
|
|
434
|
+
stdout: GROK_MODELS_FIXTURE,
|
|
435
|
+
stderr: "",
|
|
436
|
+
error: undefined,
|
|
437
|
+
});
|
|
438
|
+
const models = await discoverProviderModels("grok_cli", "/bin/grok", {});
|
|
439
|
+
expect(models).toEqual(["grok-4.6", "grok-4.5"]);
|
|
440
|
+
// Real command only — `models --json` is not supported by grok 1.0.3.
|
|
441
|
+
expect(cli.runCliProbeAsync).toHaveBeenCalledTimes(1);
|
|
442
|
+
expect(cli.runCliProbeAsync).toHaveBeenCalledWith("/bin/grok", expect.objectContaining({ TERM: "xterm-256color" }), ["models"], 8_000);
|
|
443
|
+
});
|
|
444
|
+
it("falls back to the static Grok catalog when the CLI probe fails", async () => {
|
|
445
|
+
vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
|
|
446
|
+
status: 1,
|
|
447
|
+
stdout: "",
|
|
448
|
+
stderr: "not logged in",
|
|
449
|
+
error: undefined,
|
|
450
|
+
});
|
|
451
|
+
const models = await discoverProviderModels("grok_cli", "/bin/grok", {});
|
|
452
|
+
expect(models).toEqual(getCatalogModelIds("grok_cli"));
|
|
453
|
+
expect(models).toEqual(expect.arrayContaining(["grok-4.6", "grok-4.5"]));
|
|
454
|
+
});
|
|
455
|
+
it("falls back to executable scrape when claude CLI has no model list", async () => {
|
|
456
|
+
vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
|
|
457
|
+
status: 1,
|
|
458
|
+
stdout: "",
|
|
459
|
+
stderr: "unknown option",
|
|
460
|
+
error: undefined,
|
|
461
|
+
});
|
|
462
|
+
const tmp = join(tmpdir(), `claude-exe-${randomUUID()}`);
|
|
463
|
+
writeFileSync(tmp, Buffer.from("claude-sonnet-4-6 claude-opus-4-8", "latin1"));
|
|
464
|
+
try {
|
|
465
|
+
const models = await discoverProviderModels("claude_cli", tmp, {});
|
|
466
|
+
expect(models).toEqual(expect.arrayContaining(["claude-sonnet-4-6", "claude-opus-4-8", "sonnet", "opus"]));
|
|
467
|
+
}
|
|
468
|
+
finally {
|
|
469
|
+
unlinkSync(tmp);
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
it("returns raw cursor-agent models without collapsing at discovery time", async () => {
|
|
473
|
+
vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
|
|
474
|
+
status: 0,
|
|
475
|
+
stdout: [
|
|
476
|
+
"gpt-5.3-codex-low - Codex 5.3 Low",
|
|
477
|
+
"gpt-5.3-codex-high - Codex 5.3 High",
|
|
478
|
+
"composer-2.5-fast - Composer 2.5 Fast",
|
|
479
|
+
].join("\n"),
|
|
480
|
+
stderr: "",
|
|
481
|
+
error: undefined,
|
|
482
|
+
});
|
|
483
|
+
const models = await discoverProviderModels("cursor_agent_cli", "/bin/cursor-agent", {});
|
|
484
|
+
expect(models).toEqual(["gpt-5.3-codex-low", "gpt-5.3-codex-high", "composer-2.5-fast"]);
|
|
485
|
+
expect(models).not.toEqual(collapseCursorDiscoveredModelIds([
|
|
486
|
+
"gpt-5.3-codex-low",
|
|
487
|
+
"gpt-5.3-codex-high",
|
|
488
|
+
"composer-2.5-fast",
|
|
489
|
+
]));
|
|
490
|
+
});
|
|
491
|
+
it("carries every model's native variants from the verbose scan", async () => {
|
|
492
|
+
const fixture = [
|
|
493
|
+
"opencode/big-pickle",
|
|
494
|
+
"{",
|
|
495
|
+
' "id": "big-pickle",',
|
|
496
|
+
' "capabilities": { "reasoning": true },',
|
|
497
|
+
' "variants": {}',
|
|
498
|
+
"}",
|
|
499
|
+
"opencode/deepseek-v4-flash-free",
|
|
500
|
+
"{",
|
|
501
|
+
' "id": "deepseek-v4-flash-free",',
|
|
502
|
+
' "capabilities": { "reasoning": true },',
|
|
503
|
+
' "variants": { "high": { "reasoningEffort": "high" }, "max": { "reasoningEffort": "max" } }',
|
|
504
|
+
"}",
|
|
505
|
+
"opencode/north-mini-code-free",
|
|
506
|
+
"{",
|
|
507
|
+
' "id": "north-mini-code-free",',
|
|
508
|
+
' "capabilities": { "reasoning": true },',
|
|
509
|
+
' "variants": { "none": {}, "high": {} }',
|
|
510
|
+
"}",
|
|
511
|
+
"opencode/claude-sonnet-5",
|
|
512
|
+
"{",
|
|
513
|
+
' "id": "claude-sonnet-5",',
|
|
514
|
+
' "capabilities": { "reasoning": true },',
|
|
515
|
+
' "variants": { "low": {}, "medium": {}, "high": {} }',
|
|
516
|
+
"}",
|
|
517
|
+
].join("\n");
|
|
518
|
+
vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
|
|
519
|
+
status: 0,
|
|
520
|
+
stdout: fixture,
|
|
521
|
+
stderr: "",
|
|
522
|
+
error: undefined,
|
|
523
|
+
});
|
|
524
|
+
const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
|
|
525
|
+
...NO_CACHE_ENV,
|
|
526
|
+
OPENCODE_API_KEY: "sk-live",
|
|
527
|
+
});
|
|
528
|
+
expect(discovery).toEqual({
|
|
529
|
+
models: [
|
|
530
|
+
"opencode/big-pickle",
|
|
531
|
+
"opencode/deepseek-v4-flash-free",
|
|
532
|
+
"opencode/north-mini-code-free",
|
|
533
|
+
"opencode/claude-sonnet-5",
|
|
534
|
+
],
|
|
535
|
+
modelVariants: {
|
|
536
|
+
"opencode/big-pickle": [],
|
|
537
|
+
"opencode/deepseek-v4-flash-free": ["high", "max"],
|
|
538
|
+
"opencode/north-mini-code-free": ["none", "high"],
|
|
539
|
+
"opencode/claude-sonnet-5": ["low", "medium", "high"],
|
|
540
|
+
},
|
|
541
|
+
});
|
|
542
|
+
expect(cli.runCliProbeAsync).toHaveBeenCalledWith("/bin/opencode", expect.any(Object), ["models", "--verbose"], 8_000);
|
|
543
|
+
});
|
|
544
|
+
it("falls back to the static OpenCode catalog when the CLI probe fails entirely", async () => {
|
|
545
|
+
vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
|
|
546
|
+
status: 1,
|
|
547
|
+
stdout: "",
|
|
548
|
+
stderr: "unknown flag",
|
|
549
|
+
error: undefined,
|
|
550
|
+
});
|
|
551
|
+
const models = await discoverProviderModels("opencode_cli", "/bin/opencode", {
|
|
552
|
+
...NO_CACHE_ENV,
|
|
553
|
+
OPENCODE_API_KEY: "sk-live",
|
|
554
|
+
});
|
|
555
|
+
expect(models.length).toBeGreaterThan(0);
|
|
556
|
+
expect(models).toEqual(expect.arrayContaining(["opencode/big-pickle"]));
|
|
557
|
+
});
|
|
558
|
+
});
|
|
559
|
+
describe("extractOpenCodeDiscoveredModelIdsFromVerboseOutput", () => {
|
|
560
|
+
it("retains provider-defined variants in the structured capability result", () => {
|
|
561
|
+
const capabilities = extractOpenCodeModelCapabilitiesFromVerboseOutput([
|
|
562
|
+
"opencode/custom-model",
|
|
563
|
+
"{",
|
|
564
|
+
' "capabilities": { "reasoning": true },',
|
|
565
|
+
' "variants": { "extended-thinking": {}, "vendor_x": {} }',
|
|
566
|
+
"}",
|
|
567
|
+
].join("\n"));
|
|
568
|
+
expect(capabilities).toEqual([
|
|
569
|
+
{ id: "opencode/custom-model", variants: ["extended-thinking", "vendor_x"] },
|
|
570
|
+
]);
|
|
571
|
+
});
|
|
572
|
+
it("does not encode reasoning variants into model ids", () => {
|
|
573
|
+
const text = [
|
|
574
|
+
"opencode/deepseek-v4-flash-free",
|
|
575
|
+
"{",
|
|
576
|
+
' "capabilities": { "reasoning": true },',
|
|
577
|
+
' "variants": { "high": {}, "max": {} }',
|
|
578
|
+
"}",
|
|
579
|
+
].join("\n");
|
|
580
|
+
expect(extractOpenCodeDiscoveredModelIdsFromVerboseOutput(text)).toEqual([
|
|
581
|
+
"opencode/deepseek-v4-flash-free",
|
|
582
|
+
]);
|
|
583
|
+
});
|
|
584
|
+
it("emits bare id for reasoning model with no recognized variants", () => {
|
|
585
|
+
const text = [
|
|
586
|
+
"opencode/big-pickle",
|
|
587
|
+
"{",
|
|
588
|
+
' "capabilities": { "reasoning": true },',
|
|
589
|
+
' "variants": {}',
|
|
590
|
+
"}",
|
|
591
|
+
].join("\n");
|
|
592
|
+
expect(extractOpenCodeDiscoveredModelIdsFromVerboseOutput(text)).toEqual([
|
|
593
|
+
"opencode/big-pickle",
|
|
594
|
+
]);
|
|
595
|
+
});
|
|
596
|
+
it("emits bare id for non-reasoning model (no variants emitted)", () => {
|
|
597
|
+
const text = [
|
|
598
|
+
"opencode/some-model",
|
|
599
|
+
"{",
|
|
600
|
+
' "capabilities": { "reasoning": false },',
|
|
601
|
+
' "variants": { "high": {} }',
|
|
602
|
+
"}",
|
|
603
|
+
].join("\n");
|
|
604
|
+
expect(extractOpenCodeDiscoveredModelIdsFromVerboseOutput(text)).toEqual([
|
|
605
|
+
"opencode/some-model",
|
|
606
|
+
]);
|
|
607
|
+
});
|
|
608
|
+
it("preserves OpenCode's none variant instead of renaming it minimal", () => {
|
|
609
|
+
const text = [
|
|
610
|
+
"opencode/north-mini-code-free",
|
|
611
|
+
"{",
|
|
612
|
+
' "capabilities": { "reasoning": true },',
|
|
613
|
+
' "variants": { "none": {}, "high": {} }',
|
|
614
|
+
"}",
|
|
615
|
+
].join("\n");
|
|
616
|
+
expect(extractOpenCodeModelCapabilitiesFromVerboseOutput(text)).toEqual([
|
|
617
|
+
{ id: "opencode/north-mini-code-free", variants: ["none", "high"] },
|
|
618
|
+
]);
|
|
619
|
+
});
|
|
620
|
+
});
|
|
621
|
+
// Live evidence (2026-09-01, opencode 1.18.23): `opencode models opencode` lists 61
|
|
622
|
+
// models for a key whose workspace has no payment method, while Zen's own
|
|
623
|
+
// GET /zen/v1/models returns 5 for that same key. The other 56 answer
|
|
624
|
+
// `401 CreditsError` on dispatch — the catalogue is not the entitlement.
|
|
625
|
+
describe("OpenCode Zen entitlement", () => {
|
|
626
|
+
const zenResponse = (ids) => ({ ok: true, json: async () => ({ data: ids.map((id) => ({ id })) }) });
|
|
627
|
+
const scan = (stdout) => {
|
|
628
|
+
vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
|
|
629
|
+
status: 0,
|
|
630
|
+
stdout,
|
|
631
|
+
stderr: "",
|
|
632
|
+
error: undefined,
|
|
633
|
+
});
|
|
634
|
+
};
|
|
635
|
+
const verbose = (ids) => ids
|
|
636
|
+
.map((id) => [
|
|
637
|
+
id,
|
|
638
|
+
"{",
|
|
639
|
+
` "id": "${id.split("/").slice(1).join("/")}",`,
|
|
640
|
+
' "cost": { "input": 0, "output": 0 },',
|
|
641
|
+
' "capabilities": { "reasoning": true },',
|
|
642
|
+
' "variants": { "high": {} }',
|
|
643
|
+
"}",
|
|
644
|
+
].join("\n"))
|
|
645
|
+
.join("\n");
|
|
646
|
+
afterEach(() => {
|
|
647
|
+
vi.restoreAllMocks();
|
|
648
|
+
});
|
|
649
|
+
it("drops scanned Zen models the credential cannot dispatch", () => {
|
|
650
|
+
expect(applyZenEntitlement({ models: ["opencode/big-pickle", "opencode/claude-opus-5"] }, new Set(["big-pickle"]))).toEqual({
|
|
651
|
+
models: ["opencode/big-pickle"],
|
|
652
|
+
catalogModels: ["opencode/big-pickle", "opencode/claude-opus-5"],
|
|
653
|
+
});
|
|
654
|
+
});
|
|
655
|
+
// opencode-go's list endpoint returns the same 33 models with and without a key, and
|
|
656
|
+
// BYOK ids belong to the user's own provider credentials — neither is Zen's to filter.
|
|
657
|
+
it("leaves opencode-go and BYOK namespaces untouched", () => {
|
|
658
|
+
expect(applyZenEntitlement({
|
|
659
|
+
models: ["opencode-go/glm-5.3", "anthropic/claude-opus-5", "opencode/claude-opus-5"],
|
|
660
|
+
modelVariants: { "opencode-go/glm-5.3": ["low", "high", "max"] },
|
|
661
|
+
}, new Set(["big-pickle"]))).toEqual({
|
|
662
|
+
models: ["opencode-go/glm-5.3", "anthropic/claude-opus-5", "opencode/big-pickle"],
|
|
663
|
+
catalogModels: [
|
|
664
|
+
"opencode-go/glm-5.3",
|
|
665
|
+
"anthropic/claude-opus-5",
|
|
666
|
+
"opencode/claude-opus-5",
|
|
667
|
+
"opencode/big-pickle",
|
|
668
|
+
],
|
|
669
|
+
modelVariants: { "opencode-go/glm-5.3": ["low", "high", "max"] },
|
|
670
|
+
});
|
|
671
|
+
});
|
|
672
|
+
// `opencode/laguna-s-2.1-free` dispatches on a free key yet never appears in
|
|
673
|
+
// `opencode models --verbose`, so intersecting would hide a model that works.
|
|
674
|
+
it("adds back an entitled model the CLI scan never reported", () => {
|
|
675
|
+
const merged = applyZenEntitlement({ models: ["opencode/big-pickle"], modelBilling: { "opencode/big-pickle": "free" } }, new Set(["big-pickle", "laguna-s-2.1-free"]));
|
|
676
|
+
expect(merged.models).toEqual(["opencode/big-pickle", "opencode/laguna-s-2.1-free"]);
|
|
677
|
+
expect(merged.modelBilling).toEqual({ "opencode/big-pickle": "free" });
|
|
678
|
+
});
|
|
679
|
+
// The shared catalogue is fed from catalogModels, so a key entitled to 1 of 2 models
|
|
680
|
+
// must still report both — otherwise one unpaid key shrinks the catalogue for everyone.
|
|
681
|
+
it("keeps the unfiltered list and its metadata for the catalogue feed", () => {
|
|
682
|
+
expect(applyZenEntitlement({
|
|
683
|
+
models: ["opencode/big-pickle", "opencode/claude-opus-5"],
|
|
684
|
+
modelVariants: { "opencode/big-pickle": ["high"], "opencode/claude-opus-5": ["max"] },
|
|
685
|
+
modelBilling: { "opencode/big-pickle": "free", "opencode/claude-opus-5": "paid" },
|
|
686
|
+
}, new Set(["big-pickle"]))).toEqual({
|
|
687
|
+
models: ["opencode/big-pickle"],
|
|
688
|
+
catalogModels: ["opencode/big-pickle", "opencode/claude-opus-5"],
|
|
689
|
+
modelVariants: { "opencode/big-pickle": ["high"], "opencode/claude-opus-5": ["max"] },
|
|
690
|
+
modelBilling: { "opencode/big-pickle": "free", "opencode/claude-opus-5": "paid" },
|
|
691
|
+
});
|
|
692
|
+
});
|
|
693
|
+
it("carries an entitled-but-unscanned model into the catalogue feed too", () => {
|
|
694
|
+
expect(applyZenEntitlement({ models: ["opencode/big-pickle"] }, new Set(["big-pickle", "laguna-s-2.1-free"])).catalogModels).toEqual(["opencode/big-pickle", "opencode/laguna-s-2.1-free"]);
|
|
695
|
+
});
|
|
696
|
+
it("filters the scan with the key from the runtime env", async () => {
|
|
697
|
+
scan(verbose(["opencode/big-pickle", "opencode/claude-opus-5"]));
|
|
698
|
+
const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue(zenResponse(["big-pickle"]));
|
|
699
|
+
const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
|
|
700
|
+
...NO_CACHE_ENV,
|
|
701
|
+
OPENCODE_API_KEY: "sk-live",
|
|
702
|
+
});
|
|
703
|
+
expect(discovery.models).toEqual(["opencode/big-pickle"]);
|
|
704
|
+
expect(fetchSpy).toHaveBeenCalledWith("https://opencode.ai/zen/v1/models", expect.objectContaining({ headers: { Authorization: "Bearer sk-live" } }));
|
|
705
|
+
});
|
|
706
|
+
// A local runtime has no injected env — the user ran `opencode auth login`, which
|
|
707
|
+
// writes the same document the cloud passes as OPENCODE_AUTH_CONTENT.
|
|
708
|
+
it("reads the credential out of an auth.json document", async () => {
|
|
709
|
+
scan(verbose(["opencode/big-pickle", "opencode/claude-opus-5"]));
|
|
710
|
+
const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue(zenResponse(["big-pickle"]));
|
|
711
|
+
await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
|
|
712
|
+
...NO_CACHE_ENV,
|
|
713
|
+
OPENCODE_AUTH_CONTENT: JSON.stringify({ opencode: { type: "api", key: "sk-from-file" } }),
|
|
714
|
+
});
|
|
715
|
+
expect(fetchSpy).toHaveBeenCalledWith("https://opencode.ai/zen/v1/models", expect.objectContaining({ headers: { Authorization: "Bearer sk-from-file" } }));
|
|
716
|
+
});
|
|
717
|
+
// Unauthenticated the endpoint serves the full public catalogue, so the no-credential
|
|
718
|
+
// case needs no branch — and must not send an empty bearer.
|
|
719
|
+
it("asks anonymously when there is no credential anywhere", async () => {
|
|
720
|
+
scan(verbose(["opencode/big-pickle"]));
|
|
721
|
+
const fetchSpy = vi
|
|
722
|
+
.spyOn(globalThis, "fetch")
|
|
723
|
+
.mockResolvedValue(zenResponse(["big-pickle", "claude-opus-5"]));
|
|
724
|
+
const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
|
|
725
|
+
...NO_CACHE_ENV,
|
|
726
|
+
XDG_DATA_HOME: join(tmpdir(), `no-opencode-auth-${randomUUID()}`),
|
|
727
|
+
});
|
|
728
|
+
// The anonymous union still asks Zen, but paid ids it returns are dropped: without
|
|
729
|
+
// a credential they all 401 at dispatch. big-pickle survives on its free billing.
|
|
730
|
+
expect(discovery.models).toEqual(["opencode/big-pickle"]);
|
|
731
|
+
expect(discovery.catalogModels).toContain("opencode/claude-opus-5");
|
|
732
|
+
expect(fetchSpy).toHaveBeenCalledWith("https://opencode.ai/zen/v1/models", expect.objectContaining({ headers: {} }));
|
|
733
|
+
});
|
|
734
|
+
it("keeps the unfiltered scan when Zen is unreachable", async () => {
|
|
735
|
+
scan(verbose(["opencode/big-pickle", "opencode/claude-opus-5"]));
|
|
736
|
+
vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("ENOTFOUND"));
|
|
737
|
+
const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
|
|
738
|
+
...NO_CACHE_ENV,
|
|
739
|
+
OPENCODE_API_KEY: "sk-live",
|
|
740
|
+
});
|
|
741
|
+
expect(discovery.models).toEqual(["opencode/big-pickle", "opencode/claude-opus-5"]);
|
|
742
|
+
});
|
|
743
|
+
it("keeps the unfiltered scan when Zen answers with an error status", async () => {
|
|
744
|
+
scan(verbose(["opencode/big-pickle", "opencode/claude-opus-5"]));
|
|
745
|
+
vi.spyOn(globalThis, "fetch").mockResolvedValue({ ok: false, status: 500 });
|
|
746
|
+
const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
|
|
747
|
+
...NO_CACHE_ENV,
|
|
748
|
+
OPENCODE_API_KEY: "sk-live",
|
|
749
|
+
});
|
|
750
|
+
expect(discovery.models).toEqual(["opencode/big-pickle", "opencode/claude-opus-5"]);
|
|
751
|
+
});
|
|
752
|
+
});
|
|
753
|
+
// `opencode models --verbose` prints only what the current credential can serve — six
|
|
754
|
+
// models unauthenticated, sixty-one with a key — while the CLI has already downloaded the
|
|
755
|
+
// full catalogue to ~/.cache/opencode/models.json and is just filtering its own output.
|
|
756
|
+
// Reading that file recovers the hidden effort tiers with no extra network call.
|
|
757
|
+
describe("OpenCode catalogue cache hydration", () => {
|
|
758
|
+
let cacheHome;
|
|
759
|
+
const writeCache = (payload) => {
|
|
760
|
+
mkdirSync(join(cacheHome, "opencode"), { recursive: true });
|
|
761
|
+
writeFileSync(join(cacheHome, "opencode", "models.json"), JSON.stringify(payload));
|
|
762
|
+
};
|
|
763
|
+
const scan = (stdout) => {
|
|
764
|
+
vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
|
|
765
|
+
status: 0,
|
|
766
|
+
stdout,
|
|
767
|
+
stderr: "",
|
|
768
|
+
error: undefined,
|
|
769
|
+
});
|
|
770
|
+
};
|
|
771
|
+
const verboseEntry = (id, variants) => [
|
|
772
|
+
id,
|
|
773
|
+
"{",
|
|
774
|
+
` "id": "${id.split("/").slice(1).join("/")}",`,
|
|
775
|
+
' "cost": { "input": 0, "output": 0 },',
|
|
776
|
+
' "capabilities": { "reasoning": true },',
|
|
777
|
+
` "variants": { ${variants.map((v) => `"${v}": {}`).join(", ")} }`,
|
|
778
|
+
"}",
|
|
779
|
+
].join("\n");
|
|
780
|
+
const zen = (ids) => ({ ok: true, json: async () => ({ data: ids.map((id) => ({ id })) }) });
|
|
781
|
+
beforeEach(() => {
|
|
782
|
+
cacheHome = join(tmpdir(), `opencode-cache-${randomUUID()}`);
|
|
783
|
+
});
|
|
784
|
+
afterEach(() => {
|
|
785
|
+
rmSync(cacheHome, { recursive: true, force: true });
|
|
786
|
+
vi.restoreAllMocks();
|
|
787
|
+
});
|
|
788
|
+
it("fills effort tiers and billing for models the CLI never printed", async () => {
|
|
789
|
+
scan(verboseEntry("opencode/big-pickle", []));
|
|
790
|
+
vi.spyOn(globalThis, "fetch").mockResolvedValue(zen(["big-pickle", "claude-opus-4-8"]));
|
|
791
|
+
writeCache({
|
|
792
|
+
opencode: {
|
|
793
|
+
models: {
|
|
794
|
+
"claude-opus-4-8": {
|
|
795
|
+
reasoning_options: [{ type: "effort", values: ["low", "high", "max"] }],
|
|
796
|
+
cost: { input: 5, output: 25 },
|
|
797
|
+
},
|
|
798
|
+
},
|
|
799
|
+
},
|
|
800
|
+
});
|
|
801
|
+
// Keyed: without a credential the anonymous free-only rule would hide this paid id.
|
|
802
|
+
const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
|
|
803
|
+
XDG_CACHE_HOME: cacheHome,
|
|
804
|
+
OPENCODE_API_KEY: "sk-live",
|
|
805
|
+
});
|
|
806
|
+
expect(discovery.models).toContain("opencode/claude-opus-4-8");
|
|
807
|
+
expect(discovery.modelVariants?.["opencode/claude-opus-4-8"]).toEqual(["low", "high", "max"]);
|
|
808
|
+
expect(discovery.modelBilling?.["opencode/claude-opus-4-8"]).toBe("paid");
|
|
809
|
+
});
|
|
810
|
+
// Live 2026-09-01: for claude-haiku-4-5, claude-sonnet-4/4-5, qwen3.5/3.6-plus and
|
|
811
|
+
// ling-3.0-flash-fin-free the CLI reports tiers the cache does not describe as efforts.
|
|
812
|
+
// Letting the cache win would delete effort levels that currently work.
|
|
813
|
+
it("keeps the live scan's tiers when the cache disagrees", async () => {
|
|
814
|
+
scan(verboseEntry("opencode/claude-haiku-4-5", ["high", "max"]));
|
|
815
|
+
vi.spyOn(globalThis, "fetch").mockResolvedValue(zen(["claude-haiku-4-5"]));
|
|
816
|
+
writeCache({
|
|
817
|
+
opencode: {
|
|
818
|
+
models: {
|
|
819
|
+
"claude-haiku-4-5": {
|
|
820
|
+
reasoning_options: [{ type: "effort", values: ["low", "medium"] }],
|
|
821
|
+
cost: { input: 1, output: 5 },
|
|
822
|
+
},
|
|
823
|
+
},
|
|
824
|
+
},
|
|
825
|
+
});
|
|
826
|
+
const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
|
|
827
|
+
XDG_CACHE_HOME: cacheHome,
|
|
828
|
+
});
|
|
829
|
+
expect(discovery.modelVariants?.["opencode/claude-haiku-4-5"]).toEqual(["high", "max"]);
|
|
830
|
+
});
|
|
831
|
+
// Nineteen OpenCode models express reasoning as `budget_tokens`, and the CLI synthesises
|
|
832
|
+
// high/max from them by a mapping that lives in its binary. Recording `[]` would assert
|
|
833
|
+
// "no effort tiers" and override a catalogue entry that is more correct than we are.
|
|
834
|
+
it("contributes nothing for a budget_tokens-only model rather than asserting none", async () => {
|
|
835
|
+
scan(verboseEntry("opencode/big-pickle", []));
|
|
836
|
+
vi.spyOn(globalThis, "fetch").mockResolvedValue(zen(["big-pickle", "claude-sonnet-4"]));
|
|
837
|
+
writeCache({
|
|
838
|
+
opencode: {
|
|
839
|
+
models: {
|
|
840
|
+
"claude-sonnet-4": {
|
|
841
|
+
reasoning_options: [{ type: "budget_tokens", min: 1024 }],
|
|
842
|
+
cost: { input: 3, output: 15 },
|
|
843
|
+
},
|
|
844
|
+
},
|
|
845
|
+
},
|
|
846
|
+
});
|
|
847
|
+
const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
|
|
848
|
+
XDG_CACHE_HOME: cacheHome,
|
|
849
|
+
OPENCODE_API_KEY: "sk-live",
|
|
850
|
+
});
|
|
851
|
+
expect(discovery.models).toContain("opencode/claude-sonnet-4");
|
|
852
|
+
expect(discovery.modelVariants?.["opencode/claude-sonnet-4"]).toBeUndefined();
|
|
853
|
+
expect(discovery.modelBilling?.["opencode/claude-sonnet-4"]).toBe("paid");
|
|
854
|
+
});
|
|
855
|
+
it("hydrates the opencode-go namespace too", async () => {
|
|
856
|
+
scan(verboseEntry("opencode-go/glm-5.3", []));
|
|
857
|
+
vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("offline"));
|
|
858
|
+
writeCache({
|
|
859
|
+
"opencode-go": {
|
|
860
|
+
models: {
|
|
861
|
+
"qwen3.8-max": {
|
|
862
|
+
reasoning_options: [{ type: "effort", values: ["low", "medium", "xhigh"] }],
|
|
863
|
+
cost: { input: 2, output: 6 },
|
|
864
|
+
},
|
|
865
|
+
},
|
|
866
|
+
},
|
|
867
|
+
});
|
|
868
|
+
const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
|
|
869
|
+
XDG_CACHE_HOME: cacheHome,
|
|
870
|
+
});
|
|
871
|
+
// Not offered (the scan never saw it), but metadata is ready if a later scan does.
|
|
872
|
+
expect(discovery.modelVariants?.["opencode-go/qwen3.8-max"]).toBeUndefined();
|
|
873
|
+
});
|
|
874
|
+
// Zen serves a deprecated model for a grace period, during which it appears in the
|
|
875
|
+
// anonymous /models yet fails for anonymous callers (live: deepseek-v4-flash-free).
|
|
876
|
+
it("hides cache-deprecated models from the credential-less picker", async () => {
|
|
877
|
+
scan(verboseEntry("opencode/big-pickle", []));
|
|
878
|
+
vi.spyOn(globalThis, "fetch").mockResolvedValue(zen(["big-pickle", "deepseek-v4-flash-free"]));
|
|
879
|
+
writeCache({
|
|
880
|
+
opencode: {
|
|
881
|
+
models: {
|
|
882
|
+
"deepseek-v4-flash-free": { status: "deprecated", cost: { input: 0, output: 0 } },
|
|
883
|
+
},
|
|
884
|
+
},
|
|
885
|
+
});
|
|
886
|
+
const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
|
|
887
|
+
XDG_CACHE_HOME: cacheHome,
|
|
888
|
+
XDG_DATA_HOME: join(tmpdir(), `no-auth-${randomUUID()}`),
|
|
889
|
+
});
|
|
890
|
+
expect(discovery.models).toEqual(["opencode/big-pickle"]);
|
|
891
|
+
// The catalogue feed keeps it: Zen still serves it, so removal must not fire.
|
|
892
|
+
expect(discovery.catalogModels).toContain("opencode/deepseek-v4-flash-free");
|
|
893
|
+
});
|
|
894
|
+
// laguna-s-2.1-free is deprecated AND entitled AND dispatches on a real key. The
|
|
895
|
+
// entitlement is the stronger signal — a key's own list must never be second-guessed.
|
|
896
|
+
it("keeps a deprecated model the credential is entitled to", async () => {
|
|
897
|
+
scan(verboseEntry("opencode/big-pickle", []));
|
|
898
|
+
vi.spyOn(globalThis, "fetch").mockResolvedValue(zen(["big-pickle", "laguna-s-2.1-free"]));
|
|
899
|
+
writeCache({
|
|
900
|
+
opencode: {
|
|
901
|
+
models: {
|
|
902
|
+
"laguna-s-2.1-free": { status: "deprecated", cost: { input: 0, output: 0 } },
|
|
903
|
+
},
|
|
904
|
+
},
|
|
905
|
+
});
|
|
906
|
+
const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
|
|
907
|
+
XDG_CACHE_HOME: cacheHome,
|
|
908
|
+
OPENCODE_API_KEY: "sk-live",
|
|
909
|
+
});
|
|
910
|
+
expect(discovery.models).toContain("opencode/laguna-s-2.1-free");
|
|
911
|
+
});
|
|
912
|
+
it("survives a missing cache file", async () => {
|
|
913
|
+
scan(verboseEntry("opencode/big-pickle", ["high"]));
|
|
914
|
+
vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("offline"));
|
|
915
|
+
const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
|
|
916
|
+
XDG_CACHE_HOME: join(cacheHome, "does-not-exist"),
|
|
917
|
+
});
|
|
918
|
+
expect(discovery.models).toEqual(["opencode/big-pickle"]);
|
|
919
|
+
expect(discovery.modelVariants?.["opencode/big-pickle"]).toEqual(["high"]);
|
|
920
|
+
});
|
|
921
|
+
it("survives a corrupt cache file", async () => {
|
|
922
|
+
scan(verboseEntry("opencode/big-pickle", ["high"]));
|
|
923
|
+
vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("offline"));
|
|
924
|
+
mkdirSync(join(cacheHome, "opencode"), { recursive: true });
|
|
925
|
+
writeFileSync(join(cacheHome, "opencode", "models.json"), "{ not json");
|
|
926
|
+
const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
|
|
927
|
+
XDG_CACHE_HOME: cacheHome,
|
|
928
|
+
});
|
|
929
|
+
expect(discovery.models).toEqual(["opencode/big-pickle"]);
|
|
930
|
+
});
|
|
931
|
+
});
|
|
932
|
+
// OpenCode auto-detects ambient cloud credential chains — a `~/.aws` configured for
|
|
933
|
+
// unrelated tooling made it list 79 Bedrock models for a user who never connected
|
|
934
|
+
// Bedrock to OpenCode. Only providers explicitly configured inside OpenCode (an
|
|
935
|
+
// auth.json entry or a provider API-key env var) may contribute models.
|
|
936
|
+
describe("OpenCode configured-provider filter", () => {
|
|
937
|
+
let dataHome;
|
|
938
|
+
const writeAuth = (doc) => {
|
|
939
|
+
mkdirSync(join(dataHome, "opencode"), { recursive: true });
|
|
940
|
+
writeFileSync(join(dataHome, "opencode", "auth.json"), JSON.stringify(doc));
|
|
941
|
+
};
|
|
942
|
+
const scanWith = (ids) => {
|
|
943
|
+
vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
|
|
944
|
+
status: 0,
|
|
945
|
+
stdout: ids
|
|
946
|
+
.map((id) => [
|
|
947
|
+
id,
|
|
948
|
+
"{",
|
|
949
|
+
` "id": "${id.split("/").slice(1).join("/")}",`,
|
|
950
|
+
' "cost": { "input": 0, "output": 0 },',
|
|
951
|
+
' "capabilities": { "reasoning": true },',
|
|
952
|
+
' "variants": {}',
|
|
953
|
+
"}",
|
|
954
|
+
].join("\n"))
|
|
955
|
+
.join("\n"),
|
|
956
|
+
stderr: "",
|
|
957
|
+
error: undefined,
|
|
958
|
+
});
|
|
959
|
+
};
|
|
960
|
+
beforeEach(() => {
|
|
961
|
+
dataHome = join(tmpdir(), `opencode-auth-${randomUUID()}`);
|
|
962
|
+
});
|
|
963
|
+
afterEach(() => {
|
|
964
|
+
rmSync(dataHome, { recursive: true, force: true });
|
|
965
|
+
vi.restoreAllMocks();
|
|
966
|
+
});
|
|
967
|
+
it("drops ambient-credential providers and keeps auth.json ones", async () => {
|
|
968
|
+
scanWith([
|
|
969
|
+
"opencode/big-pickle",
|
|
970
|
+
"openrouter/z-ai/glm-latest",
|
|
971
|
+
"amazon-bedrock/anthropic.claude-opus-5",
|
|
972
|
+
]);
|
|
973
|
+
writeAuth({ openrouter: { type: "api", key: "sk-or" } });
|
|
974
|
+
const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
|
|
975
|
+
XDG_DATA_HOME: dataHome,
|
|
976
|
+
XDG_CACHE_HOME: join(tmpdir(), `no-cache-${randomUUID()}`),
|
|
977
|
+
});
|
|
978
|
+
expect(discovery.models).toEqual(["opencode/big-pickle", "openrouter/z-ai/glm-latest"]);
|
|
979
|
+
});
|
|
980
|
+
it("admits a provider configured through its explicit env key", () => {
|
|
981
|
+
const configured = readConfiguredOpenCodeProviders({
|
|
982
|
+
XDG_DATA_HOME: join(tmpdir(), `no-auth-${randomUUID()}`),
|
|
983
|
+
OPENROUTER_API_KEY: "sk-or",
|
|
984
|
+
});
|
|
985
|
+
expect(configured.has("openrouter")).toBe(true);
|
|
986
|
+
expect(configured.has("amazon-bedrock")).toBe(false);
|
|
987
|
+
// Zen's namespaces are always in scope; entitlement governs them downstream.
|
|
988
|
+
expect(configured.has("opencode")).toBe(true);
|
|
989
|
+
expect(configured.has("opencode-go")).toBe(true);
|
|
990
|
+
});
|
|
991
|
+
it("reads providers from an injected OPENCODE_AUTH_CONTENT document", () => {
|
|
992
|
+
const configured = readConfiguredOpenCodeProviders({
|
|
993
|
+
XDG_DATA_HOME: join(tmpdir(), `no-auth-${randomUUID()}`),
|
|
994
|
+
OPENCODE_AUTH_CONTENT: JSON.stringify({
|
|
995
|
+
opencode: { type: "api", key: "sk" },
|
|
996
|
+
"amazon-bedrock": { type: "api", key: "explicit" },
|
|
997
|
+
}),
|
|
998
|
+
});
|
|
999
|
+
// Explicitly configured Bedrock is a decision, not an ambient chain — allowed.
|
|
1000
|
+
expect(configured.has("amazon-bedrock")).toBe(true);
|
|
1001
|
+
});
|
|
1002
|
+
});
|
|
1003
|
+
// Without a credential every paid Zen model answers 401 CreditsError at dispatch, so
|
|
1004
|
+
// the anonymous picker carries only the free tier — same list `opencode models` itself
|
|
1005
|
+
// prints when unauthenticated. BYOK namespaces keep their own key and are untouched.
|
|
1006
|
+
describe("OpenCode anonymous free-tier filter", () => {
|
|
1007
|
+
const zen = (ids) => ({ ok: true, json: async () => ({ data: ids.map((id) => ({ id })) }) });
|
|
1008
|
+
afterEach(() => {
|
|
1009
|
+
vi.restoreAllMocks();
|
|
1010
|
+
});
|
|
1011
|
+
it("hides paid Zen models when no credential exists anywhere", async () => {
|
|
1012
|
+
vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
|
|
1013
|
+
status: 0,
|
|
1014
|
+
stdout: [
|
|
1015
|
+
"opencode/big-pickle",
|
|
1016
|
+
"{",
|
|
1017
|
+
' "cost": { "input": 0, "output": 0 },',
|
|
1018
|
+
' "capabilities": { "reasoning": true },',
|
|
1019
|
+
' "variants": {}',
|
|
1020
|
+
"}",
|
|
1021
|
+
"opencode/claude-opus-5",
|
|
1022
|
+
"{",
|
|
1023
|
+
' "cost": { "input": 5, "output": 25 },',
|
|
1024
|
+
' "capabilities": { "reasoning": true },',
|
|
1025
|
+
' "variants": {}',
|
|
1026
|
+
"}",
|
|
1027
|
+
"openrouter/z-ai/glm-latest",
|
|
1028
|
+
"{",
|
|
1029
|
+
' "cost": { "input": 1, "output": 3 },',
|
|
1030
|
+
' "capabilities": { "reasoning": true },',
|
|
1031
|
+
' "variants": {}',
|
|
1032
|
+
"}",
|
|
1033
|
+
].join("\n"),
|
|
1034
|
+
stderr: "",
|
|
1035
|
+
error: undefined,
|
|
1036
|
+
});
|
|
1037
|
+
vi.spyOn(globalThis, "fetch").mockResolvedValue(zen(["big-pickle", "claude-opus-5"]));
|
|
1038
|
+
const dataHome = join(tmpdir(), `anon-auth-${randomUUID()}`);
|
|
1039
|
+
mkdirSync(join(dataHome, "opencode"), { recursive: true });
|
|
1040
|
+
writeFileSync(join(dataHome, "opencode", "auth.json"), JSON.stringify({ openrouter: { type: "api", key: "sk-or" } }));
|
|
1041
|
+
const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
|
|
1042
|
+
XDG_DATA_HOME: dataHome,
|
|
1043
|
+
XDG_CACHE_HOME: join(tmpdir(), `no-cache-${randomUUID()}`),
|
|
1044
|
+
});
|
|
1045
|
+
expect(discovery.models).toEqual(["opencode/big-pickle", "openrouter/z-ai/glm-latest"]);
|
|
1046
|
+
// The catalogue feed still carries the paid ids — global state, not this machine's.
|
|
1047
|
+
expect(discovery.catalogModels).toContain("opencode/claude-opus-5");
|
|
1048
|
+
});
|
|
1049
|
+
it("keeps paid Zen models when a key is present — entitlement governs, not billing", async () => {
|
|
1050
|
+
vi.spyOn(cli, "runCliProbeAsync").mockResolvedValue({
|
|
1051
|
+
status: 0,
|
|
1052
|
+
stdout: [
|
|
1053
|
+
"opencode/muse-spark-1.2",
|
|
1054
|
+
"{",
|
|
1055
|
+
' "cost": { "input": 1.25, "output": 4.25 },',
|
|
1056
|
+
' "capabilities": { "reasoning": true },',
|
|
1057
|
+
' "variants": {}',
|
|
1058
|
+
"}",
|
|
1059
|
+
].join("\n"),
|
|
1060
|
+
stderr: "",
|
|
1061
|
+
error: undefined,
|
|
1062
|
+
});
|
|
1063
|
+
vi.spyOn(globalThis, "fetch").mockResolvedValue(zen(["muse-spark-1.2"]));
|
|
1064
|
+
const discovery = await discoverProviderModelDetails("opencode_cli", "/bin/opencode", {
|
|
1065
|
+
OPENCODE_API_KEY: "sk-live",
|
|
1066
|
+
XDG_CACHE_HOME: join(tmpdir(), `no-cache-${randomUUID()}`),
|
|
1067
|
+
});
|
|
1068
|
+
expect(discovery.models).toEqual(["opencode/muse-spark-1.2"]);
|
|
1069
|
+
});
|
|
1070
|
+
});
|
|
1071
|
+
//# sourceMappingURL=provider-model-discovery.test.js.map
|