Context
Factory module that maps provider name strings to AgentRuntime instances. This is the single entry point for runtime instantiation — callers never construct runtime classes directly.
src/middleware/
├── types.ts ← AgentRuntime interface (PR #4)
├── cli-runtime-base.ts ← CLIRuntimeBase abstract class (PR #6)
├── runtimes/
│ ├── claude.ts ← ClaudeCliRuntime (#8, PR #9)
│ ├── gemini.ts ← GeminiCliRuntime (#10, PR #11)
│ ├── codex.ts ← CodexCliRuntime (#12, PR #13)
│ └── opencode.ts ← OpenCodeCliRuntime (OpenCodeCliRuntime (#16), PR #17)
└── runtime-factory.ts ← THIS ISSUE
Dependencies: all four CLI runtimes (PRs #9, #11, #13, #17 — all merged).
Specification
createCliRuntime(provider: string): AgentRuntime
Exported function that maps a provider name string to a new runtime instance.
Provider mapping:
| Input string |
Runtime class |
CLI binary |
"claude" |
ClaudeCliRuntime |
claude |
"gemini" |
GeminiCliRuntime |
gemini |
"codex" |
CodexCliRuntime |
codex |
"opencode" |
OpenCodeCliRuntime |
opencode |
Behavior:
- Input is case-insensitive:
"Claude", "CLAUDE", "claude" all map to ClaudeCliRuntime
- Whitespace is trimmed:
" claude " maps to ClaudeCliRuntime
- Unknown provider names throw a descriptive
Error:
Unknown runtime provider "foo". Supported providers: claude, gemini, codex, opencode
- Returns a new instance on each call (no singleton/caching — callers may need separate state)
Implementation approach
import { ClaudeCliRuntime } from "./runtimes/claude.js";
import { GeminiCliRuntime } from "./runtimes/gemini.js";
import { CodexCliRuntime } from "./runtimes/codex.js";
import { OpenCodeCliRuntime } from "./runtimes/opencode.js";
import type { AgentRuntime } from "./types.js";
const SUPPORTED_PROVIDERS = ["claude", "gemini", "codex", "opencode"] as const;
type SupportedProvider = (typeof SUPPORTED_PROVIDERS)[number];
export function createCliRuntime(provider: string): AgentRuntime {
const normalized = provider.trim().toLowerCase();
switch (normalized) {
case "claude":
return new ClaudeCliRuntime();
case "gemini":
return new GeminiCliRuntime();
case "codex":
return new CodexCliRuntime();
case "opencode":
return new OpenCodeCliRuntime();
default:
throw new Error(
`Unknown runtime provider "${provider}". Supported providers: ${SUPPORTED_PROVIDERS.join(", ")}`
);
}
}
Exports
The module should also export:
SUPPORTED_PROVIDERS — the array of supported provider name strings (for validation, help text, etc.)
SupportedProvider — the union type of supported provider names
Test file specification
Create src/middleware/runtime-factory.test.ts.
Test categories
Provider mapping (~5 tests):
"claude" returns instance of ClaudeCliRuntime
"gemini" returns instance of GeminiCliRuntime
"codex" returns instance of CodexCliRuntime
"opencode" returns instance of OpenCodeCliRuntime
- Each returned instance satisfies
AgentRuntime interface (has execute method)
Input normalization (~3 tests):
- Case-insensitive:
"Claude", "GEMINI", "Codex" map correctly
- Whitespace trimmed:
" claude " maps correctly
- Mixed case + whitespace:
" OpenCode " maps correctly
Error handling (~2 tests):
- Unknown provider throws Error with descriptive message including the invalid name
- Error message lists all supported providers
Instance freshness (~1 test):
- Two calls with same provider return distinct instances (not the same reference)
SUPPORTED_PROVIDERS export (~1 test):
- Contains exactly
["claude", "gemini", "codex", "opencode"]
Total: ~12 tests
Acceptance criteria
References
src/middleware/types.ts — AgentRuntime interface
src/middleware/runtimes/claude.ts — ClaudeCliRuntime
src/middleware/runtimes/gemini.ts — GeminiCliRuntime
src/middleware/runtimes/codex.ts — CodexCliRuntime
src/middleware/runtimes/opencode.ts — OpenCodeCliRuntime
Context
Factory module that maps provider name strings to
AgentRuntimeinstances. This is the single entry point for runtime instantiation — callers never construct runtime classes directly.Dependencies: all four CLI runtimes (PRs #9, #11, #13, #17 — all merged).
Specification
createCliRuntime(provider: string): AgentRuntimeExported function that maps a provider name string to a new runtime instance.
Provider mapping:
"claude"ClaudeCliRuntimeclaude"gemini"GeminiCliRuntimegemini"codex"CodexCliRuntimecodex"opencode"OpenCodeCliRuntimeopencodeBehavior:
"Claude","CLAUDE","claude"all map toClaudeCliRuntime" claude "maps toClaudeCliRuntimeError:Implementation approach
Exports
The module should also export:
SUPPORTED_PROVIDERS— the array of supported provider name strings (for validation, help text, etc.)SupportedProvider— the union type of supported provider namesTest file specification
Create
src/middleware/runtime-factory.test.ts.Test categories
Provider mapping (~5 tests):
"claude"returns instance ofClaudeCliRuntime"gemini"returns instance ofGeminiCliRuntime"codex"returns instance ofCodexCliRuntime"opencode"returns instance ofOpenCodeCliRuntimeAgentRuntimeinterface (hasexecutemethod)Input normalization (~3 tests):
"Claude","GEMINI","Codex"map correctly" claude "maps correctly" OpenCode "maps correctlyError handling (~2 tests):
Instance freshness (~1 test):
SUPPORTED_PROVIDERS export (~1 test):
["claude", "gemini", "codex", "opencode"]Total: ~12 tests
Acceptance criteria
src/middleware/runtime-factory.tsexportscreateCliRuntimefunctionsrc/middleware/runtime-factory.tsexportsSUPPORTED_PROVIDERSarraysrc/middleware/runtime-factory.tsexportsSupportedProvidertypesrc/middleware/runtime-factory.test.tscovers all specified categories (~12 tests)npx vitest run src/middleware/runtime-factory.test.tsReferences
src/middleware/types.ts—AgentRuntimeinterfacesrc/middleware/runtimes/claude.ts—ClaudeCliRuntimesrc/middleware/runtimes/gemini.ts—GeminiCliRuntimesrc/middleware/runtimes/codex.ts—CodexCliRuntimesrc/middleware/runtimes/opencode.ts—OpenCodeCliRuntime