import { describe, expect, test } from "bun:test"; import { resolveSubagentLauncher, resolveSubagentModel, } from "../../agent/subagents/manager"; describe("resolveSubagentLauncher", () => { test("explicit launcher takes precedence over .ts script autodetection", () => { const launcher = resolveSubagentLauncher(["-p", "hi"], { env: { LETTA_CODE_BIN: "custom-bun", LETTA_CODE_BIN_ARGS_JSON: JSON.stringify(["run", "src/index.ts"]), } as NodeJS.ProcessEnv, argv: ["bun", "/tmp/dev-entry.ts"], execPath: "/opt/homebrew/bin/bun", platform: "darwin", }); expect(launcher).toEqual({ command: "custom-bun", args: ["run", "src/index.ts", "-p", "hi"], }); }); test("explicit launcher takes precedence over .js script autodetection", () => { const launcher = resolveSubagentLauncher(["-p", "hi"], { env: { LETTA_CODE_BIN: "custom-node", } as NodeJS.ProcessEnv, argv: ["node", "/tmp/letta.js"], execPath: "/usr/local/bin/node", platform: "win32", }); expect(launcher).toEqual({ command: "custom-node", args: ["-p", "hi"], }); }); test("preserves existing .ts dev behavior for any ts entrypoint", () => { const launcher = resolveSubagentLauncher( ["--output-format", "stream-json"], { env: {} as NodeJS.ProcessEnv, argv: ["bun", "/tmp/custom-runner.ts"], execPath: "/opt/homebrew/bin/bun", platform: "darwin", }, ); expect(launcher).toEqual({ command: "/opt/homebrew/bin/bun", args: ["/tmp/custom-runner.ts", "--output-format", "stream-json"], }); }); test("uses node runtime for bundled js on win32", () => { const launcher = resolveSubagentLauncher(["-p", "prompt"], { env: {} as NodeJS.ProcessEnv, argv: ["node", "C:\\Program Files\\Letta\\letta.js"], execPath: "C:\\Program Files\\nodejs\\node.exe", platform: "win32", }); expect(launcher).toEqual({ command: "C:\\Program Files\\nodejs\\node.exe", args: ["C:\\Program Files\\Letta\\letta.js", "-p", "prompt"], }); }); test("keeps direct js spawn behavior on non-win32", () => { const launcher = resolveSubagentLauncher(["-p", "prompt"], { env: {} as NodeJS.ProcessEnv, argv: ["node", "/usr/local/lib/letta.js"], execPath: "/usr/local/bin/node", platform: "linux", }); expect(launcher).toEqual({ command: "/usr/local/lib/letta.js", args: ["-p", "prompt"], }); }); test("falls back to global letta when no launcher hints available", () => { const launcher = resolveSubagentLauncher(["-p", "prompt"], { env: {} as NodeJS.ProcessEnv, argv: ["node", ""], execPath: "/usr/local/bin/node", platform: "linux", }); expect(launcher).toEqual({ command: "letta", args: ["-p", "prompt"], }); }); test("keeps explicit launcher with spaces as a single command token", () => { const launcher = resolveSubagentLauncher( ["--output-format", "stream-json"], { env: { LETTA_CODE_BIN: '"C:\\Users\\Example User\\AppData\\Roaming\\npm\\letta.cmd"', } as NodeJS.ProcessEnv, argv: ["node", "C:\\Program Files\\Letta\\letta.js"], execPath: "C:\\Program Files\\nodejs\\node.exe", platform: "win32", }, ); expect(launcher).toEqual({ command: "C:\\Users\\Example User\\AppData\\Roaming\\npm\\letta.cmd", args: ["--output-format", "stream-json"], }); }); }); describe("resolveSubagentModel", () => { test("prefers BYOK-swapped handle when available", async () => { const cases = [ { parentProvider: "lc-anthropic", baseProvider: "anthropic" }, { parentProvider: "lc-openai", baseProvider: "openai" }, { parentProvider: "lc-zai", baseProvider: "zai" }, { parentProvider: "lc-gemini", baseProvider: "google_ai" }, { parentProvider: "lc-openrouter", baseProvider: "openrouter" }, { parentProvider: "lc-minimax", baseProvider: "minimax" }, { parentProvider: "lc-bedrock", baseProvider: "bedrock" }, { parentProvider: "chatgpt-plus-pro", baseProvider: "chatgpt-plus-pro" }, ]; for (const { parentProvider, baseProvider } of cases) { const recommendedHandle = `${baseProvider}/test-model`; const swappedHandle = `${parentProvider}/test-model`; const parentHandle = `${parentProvider}/parent-model`; const result = await resolveSubagentModel({ recommendedModel: recommendedHandle, parentModelHandle: parentHandle, availableHandles: new Set([recommendedHandle, swappedHandle]), }); expect(result).toBe(swappedHandle); } }); test("falls back to parent model when recommended is unavailable", async () => { const result = await resolveSubagentModel({ recommendedModel: "anthropic/test-model", parentModelHandle: "lc-anthropic/parent-model", availableHandles: new Set(), }); expect(result).toBe("lc-anthropic/parent-model"); }); test("BYOK parent ignores base-provider recommended when swap is unavailable", async () => { const result = await resolveSubagentModel({ recommendedModel: "anthropic/test-model", parentModelHandle: "lc-anthropic/parent-model", availableHandles: new Set(["anthropic/test-model"]), }); expect(result).toBe("lc-anthropic/parent-model"); }); test("BYOK parent accepts recommended handle when already using same BYOK prefix", async () => { const result = await resolveSubagentModel({ recommendedModel: "lc-anthropic/test-model", parentModelHandle: "lc-anthropic/parent-model", availableHandles: new Set(["lc-anthropic/test-model"]), }); expect(result).toBe("lc-anthropic/test-model"); }); test("uses recommended model when parent is not BYOK and model is available", async () => { const result = await resolveSubagentModel({ recommendedModel: "anthropic/test-model", parentModelHandle: "anthropic/parent-model", availableHandles: new Set(["anthropic/test-model"]), }); expect(result).toBe("anthropic/test-model"); }); test("explicit user model overrides all other resolution", async () => { const result = await resolveSubagentModel({ userModel: "lc-openrouter/custom-model", recommendedModel: "anthropic/test-model", parentModelHandle: "lc-anthropic/parent-model", availableHandles: new Set(["lc-anthropic/test-model"]), }); expect(result).toBe("lc-openrouter/custom-model"); }); test("inherits parent when recommended is inherit", async () => { const result = await resolveSubagentModel({ recommendedModel: "inherit", parentModelHandle: "lc-anthropic/parent-model", availableHandles: new Set(["lc-anthropic/parent-model"]), }); expect(result).toBe("lc-anthropic/parent-model"); }); test("uses GLM-5 default for free tier even when subagent recommends another model", async () => { const result = await resolveSubagentModel({ recommendedModel: "sonnet-4.5", billingTier: "free", availableHandles: new Set(["zai/glm-5"]), }); expect(result).toBe("zai/glm-5"); }); test("keeps inherit behavior for free tier", async () => { const result = await resolveSubagentModel({ recommendedModel: "inherit", parentModelHandle: "openai/gpt-5", billingTier: "free", availableHandles: new Set(["openai/gpt-5"]), }); expect(result).toBe("openai/gpt-5"); }); test("user-provided model still overrides free-tier default", async () => { const result = await resolveSubagentModel({ userModel: "openai/gpt-5", recommendedModel: "sonnet-4.5", billingTier: "free", availableHandles: new Set(["zai/glm-5", "openai/gpt-5"]), }); expect(result).toBe("openai/gpt-5"); }); });