diff --git a/.changeset/spicy-mice-dance.md b/.changeset/spicy-mice-dance.md new file mode 100644 index 0000000..4b778b4 --- /dev/null +++ b/.changeset/spicy-mice-dance.md @@ -0,0 +1,6 @@ +--- +"@upstash/box-cli": patch +"@upstash/box": patch +--- + +Rename agent.runner to agent.provider with backwards compatibility diff --git a/packages/cli/README.md b/packages/cli/README.md index 9fb17bd..3efed81 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -46,15 +46,15 @@ box create \ --env DEBUG=true ``` -| Flag | Description | Default | -| ----------------- | ------------------------------------------------------------------------------------------- | -------- | -| `--token` | Upstash Box API token | | -| `--runtime` | Runtime environment (`node`, `python`, `golang`, `ruby`, `rust`) | | -| `--agent-model` | Agent model identifier | | -| `--agent-runner` | Agent runner (`claude-code`, `codex`, `opencode`) — inferred from model prefix if omitted | inferred | -| `--agent-api-key` | Agent API key — omit for Upstash-managed key, `stored` for a saved key, or a direct API key | Upstash | -| `--git-token` | GitHub personal access token | | -| `--env KEY=VAL` | Environment variable (repeatable) | | +| Flag | Description | Default | +| ------------------ | ------------------------------------------------------------------------------------------- | -------- | +| `--token` | Upstash Box API token | | +| `--runtime` | Runtime environment (`node`, `python`, `golang`, `ruby`, `rust`) | | +| `--agent-model` | Agent model identifier | | +| `--agent-provider` | Agent provider (`claude-code`, `codex`, `opencode`) — inferred from model prefix if omitted | inferred | +| `--agent-api-key` | Agent API key — omit for Upstash-managed key, `stored` for a saved key, or a direct API key | Upstash | +| `--git-token` | GitHub personal access token | | +| `--env KEY=VAL` | Environment variable (repeatable) | | ### `box connect [box-id]` @@ -71,7 +71,7 @@ Create a new box from a snapshot and enter the REPL. Accepts the same flags as ` ```bash box from-snapshot snap_abc123 --agent-model claude/sonnet_4_5 -box from-snapshot snap_abc123 --agent-model claude/sonnet_4_5 --agent-runner codex --agent-api-key $CLAUDE_KEY +box from-snapshot snap_abc123 --agent-model claude/sonnet_4_5 --agent-provider codex --agent-api-key $CLAUDE_KEY ``` ### `box list` @@ -147,7 +147,7 @@ Any text entered is sent to the agent by default. You can also use explicit comm | `git create-pr ` | Create a pull request | | `snapshot [name]` | Save a snapshot of the current state | | `model` | Change the agent model (interactive picker) | -| `model <runner> <model>` | Change the agent model directly | +| `model <provider> <model>` | Change the agent model directly | | `pause` | Pause the box and exit | | `delete` | Delete the box and exit | | `exit` | Exit the REPL (box keeps running) | diff --git a/packages/cli/src/__tests__/commands/create-wizard.test.ts b/packages/cli/src/__tests__/commands/create-wizard.test.ts index 3e82e04..d079345 100644 --- a/packages/cli/src/__tests__/commands/create-wizard.test.ts +++ b/packages/cli/src/__tests__/commands/create-wizard.test.ts @@ -45,7 +45,7 @@ describe("createWizard", () => { expect(result).toEqual({ runtime: "python", agentModel: "claude/sonnet_4_5", - agentRunner: "claude-code", + agentProvider: "claude-code", }); }); @@ -77,7 +77,7 @@ describe("createWizard", () => { expect(result).toEqual({ runtime: "node", agentModel: "openai/gpt-5.3-codex", - agentRunner: "codex", + agentProvider: "codex", agentApiKey: "sk-my-openai-key", }); }); diff --git a/packages/cli/src/__tests__/commands/create.test.ts b/packages/cli/src/__tests__/commands/create.test.ts index d2cdd2e..8dc0707 100644 --- a/packages/cli/src/__tests__/commands/create.test.ts +++ b/packages/cli/src/__tests__/commands/create.test.ts @@ -9,7 +9,7 @@ vi.mock("@upstash/box", () => ({ UpstashKey: "UPSTASH_KEY", StoredKey: "STORED_KEY", }, - inferDefaultRunner: (model: string) => { + inferDefaultProvider: (model: string) => { if (model.startsWith("openrouter/")) return "claude-code"; if (model.startsWith("opencode/")) return "opencode"; if (model.startsWith("openai/")) return "codex"; @@ -60,7 +60,7 @@ describe("createCommand", () => { expect(Box.create).toHaveBeenCalledWith( expect.objectContaining({ apiKey: "my-key", - agent: { runner: "claude-code", model: "claude/sonnet_4_5", apiKey: "agent-key" }, + agent: { provider: "claude-code", model: "claude/sonnet_4_5", apiKey: "agent-key" }, }), ); expect(startRepl).toHaveBeenCalledWith(mockBox); @@ -74,7 +74,7 @@ describe("createCommand", () => { expect(Box.create).toHaveBeenCalledWith( expect.objectContaining({ - agent: { runner: "claude-code", model: "model", apiKey: undefined }, + agent: { provider: "claude-code", model: "model", apiKey: undefined }, }), ); }); @@ -87,7 +87,38 @@ describe("createCommand", () => { expect(Box.create).toHaveBeenCalledWith( expect.objectContaining({ - agent: { runner: "claude-code", model: "model", apiKey: "STORED_KEY" }, + agent: { provider: "claude-code", model: "model", apiKey: "STORED_KEY" }, + }), + ); + }); + + it("supports deprecated agentRunner flag", async () => { + const mockBox = { id: "box-1" }; + vi.mocked(Box.create).mockResolvedValueOnce(mockBox as any); + + await createCommand({ token: "key", agentModel: "model", agentRunner: "codex" }); + + expect(Box.create).toHaveBeenCalledWith( + expect.objectContaining({ + agent: { provider: "codex", model: "model", apiKey: undefined }, + }), + ); + }); + + it("prioritizes agentProvider over agentRunner", async () => { + const mockBox = { id: "box-1" }; + vi.mocked(Box.create).mockResolvedValueOnce(mockBox as any); + + await createCommand({ + token: "key", + agentModel: "model", + agentProvider: "claude-code", + agentRunner: "codex", + }); + + expect(Box.create).toHaveBeenCalledWith( + expect.objectContaining({ + agent: { provider: "claude-code", model: "model", apiKey: undefined }, }), ); }); diff --git a/packages/cli/src/__tests__/commands/from-snapshot.test.ts b/packages/cli/src/__tests__/commands/from-snapshot.test.ts index 17e82b9..6e79705 100644 --- a/packages/cli/src/__tests__/commands/from-snapshot.test.ts +++ b/packages/cli/src/__tests__/commands/from-snapshot.test.ts @@ -9,7 +9,7 @@ vi.mock("@upstash/box", () => ({ UpstashKey: "UPSTASH_KEY", StoredKey: "STORED_KEY", }, - inferDefaultRunner: (model: string) => { + inferDefaultProvider: (model: string) => { if (model.startsWith("openrouter/")) return "claude-code"; if (model.startsWith("opencode/")) return "opencode"; if (model.startsWith("openai/")) return "codex"; @@ -56,7 +56,7 @@ describe("fromSnapshotCommand", () => { "snap-1", expect.objectContaining({ apiKey: "key", - agent: { runner: "claude-code", model: "model", apiKey: "agent-key" }, + agent: { provider: "claude-code", model: "model", apiKey: "agent-key" }, }), ); expect(startRepl).toHaveBeenCalledWith(mockBox); @@ -71,7 +71,7 @@ describe("fromSnapshotCommand", () => { expect(Box.fromSnapshot).toHaveBeenCalledWith( "snap-1", expect.objectContaining({ - agent: { runner: "claude-code", model: "model", apiKey: undefined }, + agent: { provider: "claude-code", model: "model", apiKey: undefined }, }), ); expect(startRepl).toHaveBeenCalledWith(mockBox); diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index d6ac964..b03eea5 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -23,7 +23,8 @@ program .option("--token <token>", "Upstash Box API token") .option("--runtime <runtime>", "Runtime environment (node, python, golang, ruby, rust)") .option("--agent-model <model>", "Agent model identifier") - .option("--agent-runner <runner>", "Agent runner (claude-code, codex, opencode)") + .option("--agent-provider <provider>", "Agent provider (claude-code, codex, opencode)") + .option("--agent-runner <runner>") .option( "--agent-api-key [key]", 'Agent API key — omit to use Upstash-managed key, or pass "stored" to use a key saved in the Upstash console', @@ -49,7 +50,8 @@ program .option("--token <token>", "Upstash Box API token") .option("--runtime <runtime>", "Runtime environment") .option("--agent-model <model>", "Agent model identifier") - .option("--agent-runner <runner>", "Agent runner (claude-code, codex, opencode)") + .option("--agent-provider <provider>", "Agent provider (claude-code, codex, opencode)") + .option("--agent-runner <runner>") .option( "--agent-api-key [key]", 'Agent API key — omit to use Upstash-managed key, or pass "stored" to use a key saved in the Upstash console', diff --git a/packages/cli/src/commands/create-wizard.ts b/packages/cli/src/commands/create-wizard.ts index fa43453..dc8b5de 100644 --- a/packages/cli/src/commands/create-wizard.ts +++ b/packages/cli/src/commands/create-wizard.ts @@ -114,7 +114,7 @@ async function configureAgent(result: Partial<CreateFlags>): Promise<boolean> { }); if (model === undefined) return false; result.agentModel = model; - result.agentRunner = provider; + result.agentProvider = provider; const keyOption = await interactiveSelect({ prompt: cyan("Agent API key:"), diff --git a/packages/cli/src/commands/create.ts b/packages/cli/src/commands/create.ts index 062f662..e3a3f5b 100644 --- a/packages/cli/src/commands/create.ts +++ b/packages/cli/src/commands/create.ts @@ -1,4 +1,4 @@ -import { Box, inferDefaultRunner } from "@upstash/box"; +import { Box, inferDefaultProvider } from "@upstash/box"; import type { Runtime } from "@upstash/box"; import { resolveToken } from "../auth.js"; import { resolveAgentApiKey } from "../agent-key.js"; @@ -10,6 +10,8 @@ export interface CreateFlags { token?: string; runtime?: string; agentModel?: string; + agentProvider?: string; + /** @deprecated Use `agentProvider` instead. */ agentRunner?: string; agentApiKey?: string | true; gitToken?: string; @@ -53,7 +55,8 @@ export async function createCommand(flags: CreateFlags): Promise<void> { runtime: flags.runtime as Runtime, agent: flags.agentModel ? { - runner: flags.agentRunner ?? inferDefaultRunner(flags.agentModel), + provider: + flags.agentProvider ?? flags.agentRunner ?? inferDefaultProvider(flags.agentModel), model: flags.agentModel, apiKey: resolveAgentApiKey(flags.agentApiKey), } diff --git a/packages/cli/src/commands/from-snapshot.ts b/packages/cli/src/commands/from-snapshot.ts index be489ee..30121b1 100644 --- a/packages/cli/src/commands/from-snapshot.ts +++ b/packages/cli/src/commands/from-snapshot.ts @@ -1,4 +1,4 @@ -import { Box, inferDefaultRunner } from "@upstash/box"; +import { Box, inferDefaultProvider } from "@upstash/box"; import type { Runtime } from "@upstash/box"; import { resolveToken } from "../auth.js"; import { resolveAgentApiKey } from "../agent-key.js"; @@ -8,6 +8,8 @@ interface FromSnapshotFlags { token?: string; runtime?: string; agentModel?: string; + agentProvider?: string; + /** @deprecated Use `agentProvider` instead. */ agentRunner?: string; agentApiKey?: string | true; gitToken?: string; @@ -38,7 +40,8 @@ export async function fromSnapshotCommand( runtime: flags.runtime as Runtime, agent: flags.agentModel ? { - runner: flags.agentRunner ?? inferDefaultRunner(flags.agentModel), + provider: + flags.agentProvider ?? flags.agentRunner ?? inferDefaultProvider(flags.agentModel), model: flags.agentModel, apiKey: resolveAgentApiKey(flags.agentApiKey), } diff --git a/packages/cli/src/repl/commands/model.ts b/packages/cli/src/repl/commands/model.ts index 624bb99..3a51b0b 100644 --- a/packages/cli/src/repl/commands/model.ts +++ b/packages/cli/src/repl/commands/model.ts @@ -2,7 +2,7 @@ import type { Box } from "@upstash/box"; import type { BoxREPLEvent } from "../types.js"; /** - * /model [runner] [model] + * /model [provider] [model] * * With args: directly set the model via the config API. * Without args: yield a model-picker event for the terminal/UI to handle. @@ -13,7 +13,7 @@ export async function* handleModel(box: Box, args: string): AsyncGenerator<BoxRE if (parts.length < 2) { yield { type: "error", - message: "Usage: /model <runner> <model> (e.g. /model claude-code claude/opus_4_5)", + message: "Usage: /model <provider> <model> (e.g. /model claude-code claude/opus_4_5)", }; return; } diff --git a/packages/cli/src/repl/terminal.ts b/packages/cli/src/repl/terminal.ts index 2081e69..d9a5d85 100644 --- a/packages/cli/src/repl/terminal.ts +++ b/packages/cli/src/repl/terminal.ts @@ -38,7 +38,7 @@ export async function startRepl(box: Box, options?: BoxREPLClientOptions): Promi const client = new BoxREPLClient(box, { ...options, onModelConfiguration: async () => { - const agent = box.modelConfig.runner; + const agent = box.modelConfig.provider; const groups = MODEL_OPTIONS_BY_AGENT[(agent as Agent) ?? ("claude-code" as Agent)] ?? Object.values(MODEL_OPTIONS_BY_AGENT)[0]!; diff --git a/packages/sdk/src/__tests__/box-create.test.ts b/packages/sdk/src/__tests__/box-create.test.ts index 87e530b..d8fa3a3 100644 --- a/packages/sdk/src/__tests__/box-create.test.ts +++ b/packages/sdk/src/__tests__/box-create.test.ts @@ -26,13 +26,13 @@ describe("Box.create", () => { expect(body.agent_api_key).toBe("test-agent-key"); }); - it("sends explicit runner when provided", async () => { + it("sends explicit provider when provided", async () => { const data = { ...TEST_BOX_DATA, status: "running" }; vi.mocked(fetch).mockResolvedValueOnce(mockResponse(data)); await Box.create({ ...TEST_CONFIG, - agent: { runner: Agent.Codex, model: OpenAICodex.GPT_5_3_Codex, apiKey: "k" }, + agent: { provider: Agent.Codex, model: OpenAICodex.GPT_5_3_Codex, apiKey: "k" }, }); const body = JSON.parse(vi.mocked(fetch).mock.calls[0]![1]?.body as string); @@ -40,6 +40,37 @@ describe("Box.create", () => { expect(body.model).toBe(OpenAICodex.GPT_5_3_Codex); }); + it("supports deprecated runner field", async () => { + const data = { ...TEST_BOX_DATA, status: "running" }; + vi.mocked(fetch).mockResolvedValueOnce(mockResponse(data)); + + await Box.create({ + ...TEST_CONFIG, + agent: { + provider: Agent.Codex, + runner: "ignored", + model: OpenAICodex.GPT_5_3_Codex, + apiKey: "k", + }, + }); + + const body = JSON.parse(vi.mocked(fetch).mock.calls[0]![1]?.body as string); + expect(body.agent).toBe(Agent.Codex); + }); + + it("falls back to runner when provider is absent", async () => { + const data = { ...TEST_BOX_DATA, status: "running" }; + vi.mocked(fetch).mockResolvedValueOnce(mockResponse(data)); + + await Box.create({ + ...TEST_CONFIG, + agent: { runner: "codex", model: "openai/gpt-5.3-codex", apiKey: "k" } as any, + }); + + const body = JSON.parse(vi.mocked(fetch).mock.calls[0]![1]?.body as string); + expect(body.agent).toBe("codex"); + }); + it("polls until box is ready", async () => { const creating = { ...TEST_BOX_DATA, status: "creating" }; const running = { ...TEST_BOX_DATA, status: "running" }; @@ -84,7 +115,7 @@ describe("Box.create", () => { }); it("throws when agent.model is missing", async () => { - const config = { ...TEST_CONFIG, agent: { runner: "claude-code", model: "", apiKey: "key" } }; + const config = { ...TEST_CONFIG, agent: { provider: "claude-code", model: "", apiKey: "key" } }; await expect(Box.create(config)).rejects.toThrow("agent.model is required"); }); diff --git a/packages/sdk/src/__tests__/box-from-snapshot.test.ts b/packages/sdk/src/__tests__/box-from-snapshot.test.ts index 7a296bb..84c43e7 100644 --- a/packages/sdk/src/__tests__/box-from-snapshot.test.ts +++ b/packages/sdk/src/__tests__/box-from-snapshot.test.ts @@ -25,13 +25,13 @@ describe("Box.fromSnapshot", () => { expect(body.model).toBe("claude/sonnet_4_5"); }); - it("sends explicit runner when provided", async () => { + it("sends explicit provider when provided", async () => { const data = { ...TEST_BOX_DATA, status: "running" }; vi.mocked(fetch).mockResolvedValueOnce(mockResponse(data)); await Box.fromSnapshot("snap-1", { ...TEST_CONFIG, - agent: { runner: Agent.Codex, model: OpenAICodex.GPT_5_3_Codex, apiKey: "k" }, + agent: { provider: Agent.Codex, model: OpenAICodex.GPT_5_3_Codex, apiKey: "k" }, }); const body = JSON.parse(vi.mocked(fetch).mock.calls[0]![1]?.body as string); diff --git a/packages/sdk/src/__tests__/infer-runner.test.ts b/packages/sdk/src/__tests__/infer-runner.test.ts index a68fcb7..5f724e8 100644 --- a/packages/sdk/src/__tests__/infer-runner.test.ts +++ b/packages/sdk/src/__tests__/infer-runner.test.ts @@ -1,25 +1,31 @@ import { describe, it, expect } from "vitest"; -import { inferDefaultRunner } from "../client.js"; +import { inferDefaultProvider, inferDefaultRunner } from "../client.js"; import { Agent } from "../types.js"; -describe("inferDefaultRunner", () => { +describe("inferDefaultProvider", () => { it("returns ClaudeCode for openrouter/ prefix", () => { - expect(inferDefaultRunner("openrouter/deepseek-r1")).toBe(Agent.ClaudeCode); + expect(inferDefaultProvider("openrouter/deepseek-r1")).toBe(Agent.ClaudeCode); }); it("returns OpenCode for opencode/ prefix", () => { - expect(inferDefaultRunner("opencode/zen-claude-sonnet-4.5")).toBe(Agent.OpenCode); + expect(inferDefaultProvider("opencode/zen-claude-sonnet-4.5")).toBe(Agent.OpenCode); }); it("returns Codex for openai/ prefix", () => { - expect(inferDefaultRunner("openai/gpt-5.3-codex")).toBe(Agent.Codex); + expect(inferDefaultProvider("openai/gpt-5.3-codex")).toBe(Agent.Codex); }); it("returns ClaudeCode for claude/ prefix (default)", () => { - expect(inferDefaultRunner("claude/sonnet_4_5")).toBe(Agent.ClaudeCode); + expect(inferDefaultProvider("claude/sonnet_4_5")).toBe(Agent.ClaudeCode); }); it("returns ClaudeCode for unknown prefix", () => { - expect(inferDefaultRunner("some-custom-model")).toBe(Agent.ClaudeCode); + expect(inferDefaultProvider("some-custom-model")).toBe(Agent.ClaudeCode); + }); +}); + +describe("inferDefaultRunner (deprecated alias)", () => { + it("is the same function as inferDefaultProvider", () => { + expect(inferDefaultRunner).toBe(inferDefaultProvider); }); }); diff --git a/packages/sdk/src/__tests__/integration/configure-model.integration.test.ts b/packages/sdk/src/__tests__/integration/configure-model.integration.test.ts index 65f94b1..61a8499 100644 --- a/packages/sdk/src/__tests__/integration/configure-model.integration.test.ts +++ b/packages/sdk/src/__tests__/integration/configure-model.integration.test.ts @@ -8,7 +8,7 @@ describe.skipIf(!UPSTASH_BOX_API_KEY)("configureModel", () => { beforeAll(async () => { box = await Box.create({ apiKey: UPSTASH_BOX_API_KEY!, - agent: { runner: Agent.ClaudeCode, model: ClaudeCode.Sonnet_4_5 }, + agent: { provider: Agent.ClaudeCode, model: ClaudeCode.Sonnet_4_5 }, }); }, 120000); @@ -23,6 +23,7 @@ describe.skipIf(!UPSTASH_BOX_API_KEY)("configureModel", () => { it("starts with the initial model config", () => { const config = box.modelConfig; expect(config.model).toBe(ClaudeCode.Sonnet_4_5); + expect(config.provider).toBe(Agent.ClaudeCode); expect(config.runner).toBe(Agent.ClaudeCode); }); @@ -52,6 +53,7 @@ describe.skipIf(!UPSTASH_BOX_API_KEY)("configureModel", () => { const reconnected = await Box.get(box.id, { apiKey: UPSTASH_BOX_API_KEY! }); expect(reconnected.modelConfig.model).toBe(ClaudeCode.Haiku_4_5); + expect(reconnected.modelConfig.provider).toBe(Agent.ClaudeCode); expect(reconnected.modelConfig.runner).toBe(Agent.ClaudeCode); }); }); diff --git a/packages/sdk/src/client.ts b/packages/sdk/src/client.ts index d8b81c2..a1b4fa0 100644 --- a/packages/sdk/src/client.ts +++ b/packages/sdk/src/client.ts @@ -35,14 +35,17 @@ import type { ZodType } from "zod/v3"; const DEFAULT_BASE_URL = "https://us-east-1.box.upstash.com"; -/** Infer the runner agent from a model string prefix. */ -export function inferDefaultRunner(model: string): Agent { +/** Infer the provider agent from a model string prefix. */ +export function inferDefaultProvider(model: string): Agent { if (model.startsWith("openrouter/")) return Agent.ClaudeCode; if (model.startsWith("opencode/")) return Agent.OpenCode; if (model.startsWith("openai/")) return Agent.Codex; return Agent.ClaudeCode; } +/** @deprecated Use `inferDefaultProvider` instead. */ +export const inferDefaultRunner = inferDefaultProvider; + /** * Error thrown by the Box SDK */ @@ -281,9 +284,14 @@ export class Box { return this._cwd; } - /** Current runner and model configured for this box. */ - get modelConfig(): { runner: Agent | undefined; model: string | undefined } { - return { runner: this._agent, model: this._model }; + /** Current provider and model configured for this box. */ + get modelConfig(): { + provider: Agent | undefined; + /** @deprecated Use `provider`. */ + runner: Agent | undefined; + model: string | undefined; + } { + return { provider: this._agent, runner: this._agent, model: this._model }; } private _cwd: string; @@ -413,7 +421,7 @@ export class Box { const body: Record<string, unknown> = {}; if (config?.agent) { body.model = config.agent.model; - body.agent = config.agent.runner; + body.agent = config.agent.provider ?? config.agent.runner; body.agent_api_key = config.agent.apiKey; } if (config?.runtime) body.runtime = config.runtime; @@ -1492,7 +1500,7 @@ export class Box { }; if (config?.agent) { body.model = config.agent.model; - body.agent = config.agent.runner; + body.agent = config.agent.provider ?? config.agent.runner; body.agent_api_key = config.agent.apiKey; } if (config?.runtime) body.runtime = config.runtime; diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index f224a33..b503995 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -1,4 +1,11 @@ -export { Box, BoxError, Run, StreamRun, inferDefaultRunner } from "./client.js"; +export { + Box, + BoxError, + Run, + StreamRun, + inferDefaultProvider, + inferDefaultRunner, +} from "./client.js"; export { ClaudeCode, OpenAICodex, diff --git a/packages/sdk/src/types.ts b/packages/sdk/src/types.ts index be528c1..f23e2f8 100644 --- a/packages/sdk/src/types.ts +++ b/packages/sdk/src/types.ts @@ -114,10 +114,26 @@ export type AgentConfig = { */ apiKey?: BoxApiKey | string; } & ( - | { runner: Agent.ClaudeCode; model: ClaudeCode | OpenRouterModel } - | { runner: Agent.Codex; model: OpenAICodex | OpenRouterModel } - | { runner: Agent.OpenCode; model: OpenCodeModel | ClaudeCode | OpenAICodex | OpenRouterModel } - | { runner: string; model: string } + | { provider: Agent.ClaudeCode; model: ClaudeCode | OpenRouterModel; runner?: never } + | { provider: Agent.Codex; model: OpenAICodex | OpenRouterModel; runner?: never } + | { + provider: Agent.OpenCode; + model: OpenCodeModel | ClaudeCode | OpenAICodex | OpenRouterModel; + runner?: never; + } + | { provider: string; model: string; runner?: never } + | { + /** @deprecated Use `provider` instead. */ + runner: Agent; + model: OpenCodeModel | ClaudeCode | OpenAICodex | OpenRouterModel; + provider?: never; + } + | { + /** @deprecated Use `provider` instead. */ + runner: string; + model: string; + provider?: never; + } ); export interface BoxConfig {