diff --git a/packages/cli/src/__tests__/do-snapshot.test.ts b/packages/cli/src/__tests__/do-snapshot.test.ts index 0935acae..d1c07663 100644 --- a/packages/cli/src/__tests__/do-snapshot.test.ts +++ b/packages/cli/src/__tests__/do-snapshot.test.ts @@ -11,7 +11,6 @@ import { afterAll, afterEach, describe, expect, it, mock } from "bun:test"; mock.module("../shared/oauth", () => ({ getOrPromptApiKey: mock(() => Promise.resolve("sk-test")), - getModelIdInteractive: mock(() => Promise.resolve("openrouter/auto")), })); // ── Import under test ───────────────────────────────────────────────────── diff --git a/packages/cli/src/__tests__/junie-agent.test.ts b/packages/cli/src/__tests__/junie-agent.test.ts index 7b2aff9b..dc50ddd7 100644 --- a/packages/cli/src/__tests__/junie-agent.test.ts +++ b/packages/cli/src/__tests__/junie-agent.test.ts @@ -22,7 +22,6 @@ beforeEach(() => { mock.module("../shared/oauth", () => ({ getOrPromptApiKey: mock(() => Promise.resolve("sk-or-v1-test-key")), - getModelIdInteractive: mock(() => Promise.resolve("openrouter/auto")), })); // ── Import module under test ────────────────────────────────────────────────── diff --git a/packages/cli/src/__tests__/orchestrate.test.ts b/packages/cli/src/__tests__/orchestrate.test.ts index 29d15c14..5544b7de 100644 --- a/packages/cli/src/__tests__/orchestrate.test.ts +++ b/packages/cli/src/__tests__/orchestrate.test.ts @@ -16,11 +16,9 @@ import { isNumber } from "../shared/type-guards.js"; // ── Mock oauth + tarball (needed to avoid interactive prompts / network) ── const mockGetOrPromptApiKey = mock(() => Promise.resolve("sk-or-v1-test-key")); -const mockGetModelIdInteractive = mock(() => Promise.resolve("openrouter/auto")); mock.module("../shared/oauth", () => ({ getOrPromptApiKey: mockGetOrPromptApiKey, - getModelIdInteractive: mockGetModelIdInteractive, })); // ── Import the real module under test ───────────────────────────────────── @@ -109,8 +107,6 @@ describe("runOrchestration", () => { }); mockGetOrPromptApiKey.mockClear(); mockGetOrPromptApiKey.mockImplementation(() => Promise.resolve("sk-or-v1-test-key")); - mockGetModelIdInteractive.mockClear(); - mockGetModelIdInteractive.mockImplementation(() => Promise.resolve("openrouter/auto")); mockTryTarballInstall.mockClear(); mockTryTarballInstall.mockImplementation(() => Promise.resolve(false)); }); @@ -258,43 +254,53 @@ describe("runOrchestration", () => { exitSpy.mockRestore(); }); - // ── Model selection ───────────────────────────────────────────────── + // ── Model default ────────────────────────────────────────────────── - it("calls getModelIdInteractive when agent.modelPrompt is true", async () => { + it("passes modelDefault to configure without prompting", async () => { + const configure = mock(() => Promise.resolve()); const cloud = createMockCloud(); const agent = createMockAgent({ - modelPrompt: true, modelDefault: "anthropic/claude-3", + configure, }); await runOrchestrationSafe(cloud, agent, "testagent"); - expect(mockGetModelIdInteractive).toHaveBeenCalledTimes(1); - expect(mockGetModelIdInteractive).toHaveBeenCalledWith("anthropic/claude-3", "TestAgent"); + expect(configure).toHaveBeenCalledWith("sk-or-v1-test-key", "anthropic/claude-3"); stderrSpy.mockRestore(); exitSpy.mockRestore(); }); - it("uses 'openrouter/auto' as default model when modelDefault is not set", async () => { + it("uses MODEL_ID env var when modelDefault is not set", async () => { + const originalModelId = process.env.MODEL_ID; + process.env.MODEL_ID = "google/gemini-pro"; + const configure = mock(() => Promise.resolve()); const cloud = createMockCloud(); const agent = createMockAgent({ - modelPrompt: true, - }); // no modelDefault + configure, + }); await runOrchestrationSafe(cloud, agent, "testagent"); - expect(mockGetModelIdInteractive).toHaveBeenCalledWith("openrouter/auto", "TestAgent"); + expect(configure).toHaveBeenCalledWith("sk-or-v1-test-key", "google/gemini-pro"); + process.env.MODEL_ID = originalModelId; stderrSpy.mockRestore(); exitSpy.mockRestore(); }); - it("skips model selection when modelPrompt is falsy", async () => { + it("passes undefined modelId when neither modelDefault nor MODEL_ID is set", async () => { + const originalModelId = process.env.MODEL_ID; + delete process.env.MODEL_ID; + const configure = mock(() => Promise.resolve()); const cloud = createMockCloud(); - const agent = createMockAgent(); // modelPrompt undefined + const agent = createMockAgent({ + configure, + }); await runOrchestrationSafe(cloud, agent, "testagent"); - expect(mockGetModelIdInteractive).not.toHaveBeenCalled(); + expect(configure).toHaveBeenCalledWith("sk-or-v1-test-key", undefined); + process.env.MODEL_ID = originalModelId; stderrSpy.mockRestore(); exitSpy.mockRestore(); }); diff --git a/packages/cli/src/shared/agent-setup.ts b/packages/cli/src/shared/agent-setup.ts index d40ff0d4..c0172e72 100644 --- a/packages/cli/src/shared/agent-setup.ts +++ b/packages/cli/src/shared/agent-setup.ts @@ -568,7 +568,6 @@ function createAgents(runner: CloudRunner): Record { name: "OpenClaw", cloudInitTier: "full", preProvision: detectGithubAuth, - modelPrompt: true, modelDefault: "openrouter/auto", install: () => installAgent( diff --git a/packages/cli/src/shared/agents.ts b/packages/cli/src/shared/agents.ts index 4a444a68..c512be61 100644 --- a/packages/cli/src/shared/agents.ts +++ b/packages/cli/src/shared/agents.ts @@ -9,9 +9,7 @@ export type CloudInitTier = "minimal" | "node" | "bun" | "full"; export interface AgentConfig { name: string; - /** If true, prompt for model selection before provisioning. */ - modelPrompt?: boolean; - /** Default model ID when modelPrompt is true. */ + /** Default model ID passed to configure() (no interactive prompt — override via MODEL_ID env var). */ modelDefault?: string; /** Pre-provision hook (runs before server creation, e.g., prompt for GitHub auth). */ preProvision?: () => Promise; diff --git a/packages/cli/src/shared/oauth.ts b/packages/cli/src/shared/oauth.ts index 201c7f0b..a955004f 100644 --- a/packages/cli/src/shared/oauth.ts +++ b/packages/cli/src/shared/oauth.ts @@ -3,7 +3,7 @@ import * as v from "valibot"; import { OAUTH_CODE_REGEX } from "./oauth-constants"; import { parseJsonWith } from "./parse"; -import { logError, logInfo, logStep, logWarn, openBrowser, prompt, validateModelId } from "./ui"; +import { logError, logInfo, logStep, logWarn, openBrowser, prompt } from "./ui"; // ─── Schemas ───────────────────────────────────────────────────────────────── @@ -285,38 +285,3 @@ export async function getOrPromptApiKey(agentSlug?: string, cloudSlug?: string): logError("No valid API key after 3 attempts"); throw new Error("API key acquisition failed"); } - -// ─── Model Selection ───────────────────────────────────────────────────────── - -export async function getModelIdInteractive(defaultModel = "openrouter/auto", agentName?: string): Promise { - // Check env var first - if (process.env.MODEL_ID) { - if (!validateModelId(process.env.MODEL_ID)) { - logError("MODEL_ID environment variable contains invalid characters"); - throw new Error("Invalid MODEL_ID"); - } - return process.env.MODEL_ID; - } - - for (let attempt = 1; attempt <= 3; attempt++) { - process.stderr.write("\n"); - logInfo("Browse models at: https://openrouter.ai/models"); - if (agentName) { - logInfo(`Which model would you like to use with ${agentName}?`); - } else { - logInfo("Which model would you like to use?"); - } - - const modelId = (await prompt(`Enter model ID [${defaultModel}]: `)) || defaultModel; - - if (!validateModelId(modelId)) { - logError("Invalid characters in model ID, try again"); - continue; - } - - return modelId; - } - - logError("No valid model after 3 attempts"); - throw new Error("Model selection failed"); -} diff --git a/packages/cli/src/shared/orchestrate.ts b/packages/cli/src/shared/orchestrate.ts index 23625776..6222f567 100644 --- a/packages/cli/src/shared/orchestrate.ts +++ b/packages/cli/src/shared/orchestrate.ts @@ -8,7 +8,7 @@ import { generateSpawnId, saveSpawnRecord } from "../history.js"; import { offerGithubAuth, wrapSshCall } from "./agent-setup"; import { tryTarballInstall } from "./agent-tarball"; import { generateEnvConfig } from "./agents"; -import { getModelIdInteractive, getOrPromptApiKey } from "./oauth"; +import { getOrPromptApiKey } from "./oauth"; import { logInfo, logStep, logWarn, prepareStdinForHandoff, withRetry } from "./ui"; export interface CloudOrchestrator { @@ -91,11 +91,8 @@ export async function runOrchestration( // 3. Get API key (before provisioning so user isn't waiting) const apiKey = await getOrPromptApiKey(agentName, cloud.cloudName); - // 4. Model selection (if agent needs it) - let modelId: string | undefined; - if (agent.modelPrompt) { - modelId = await getModelIdInteractive(agent.modelDefault || "openrouter/auto", agent.name); - } + // 4. Model ID (use agent default — no interactive prompt) + const modelId = agent.modelDefault || process.env.MODEL_ID; // 5. Size/bundle selection await cloud.promptSize();