Skip to content

Commit fa5307a

Browse files
feat(agents): modular SDK runtimes + Anthropic fixes + OpenCode SDK (#293)
* feat: add anthropic-sdk agent type for web/API-key-based usage - Add 'anthropic-sdk' to AgentType union - Add apiKey field to AgentRunOptions and LoopOptions - Implement runAnthropicSdkAgent using @anthropic-ai/sdk with streaming - Update detectAvailableAgents/detectBestAgent to accept apiKeys option - SDK agents are available when API key is provided (no CLI binary needed) - Enables ralph-starter usage in web applications without CLI dependencies Amp-Thread-ID: https://ampcode.com/threads/T-019ce458-6e75-7448-b1cd-1839264ca386 Co-authored-by: Amp <amp@ampcode.com> * chore: release v0.4.4 Amp-Thread-ID: https://ampcode.com/threads/T-019ce458-6e75-7448-b1cd-1839264ca386 Co-authored-by: Amp <amp@ampcode.com> * fix: update agent test to expect 7 agents (includes anthropic-sdk) Amp-Thread-ID: https://ampcode.com/threads/T-019ce458-6e75-7448-b1cd-1839264ca386 Co-authored-by: Amp <amp@ampcode.com> * fix: anthropic-sdk agent with proper tool-use loop - Add file tools (read_file, write_file, list_directory, run_command) - Implement multi-turn tool-use loop so agent can actually edit files - Fix timeout: pass as RequestOptions (2nd arg), not in message body - Fix max_tokens: use fixed 16384 instead of deriving from maxTurns - Fix system prompt: accurately describes tool capabilities Amp-Thread-ID: https://ampcode.com/threads/T-019ce458-6e75-7448-b1cd-1839264ca386 Co-authored-by: Amp <amp@ampcode.com> * fix: buffer onOutput to emit complete lines, matching CLI agent contract Amp-Thread-ID: https://ampcode.com/threads/T-019ce458-6e75-7448-b1cd-1839264ca386 Co-authored-by: Amp <amp@ampcode.com> * fix: security hardening for anthropic-sdk agent - Prevent path traversal: resolve + realpath guard on all file tools - Gate run_command behind allowShellExecution option (default: false) - Exclude run_command from tool list entirely when shell disabled - Use remaining time budget for per-request timeout instead of full value Amp-Thread-ID: https://ampcode.com/threads/T-019ce458-6e75-7448-b1cd-1839264ca386 Co-authored-by: Amp <amp@ampcode.com> * feat(agents): modularize SDK runtimes and add opencode-sdk Amp-Thread-ID: https://ampcode.com/threads/T-019ce48e-f36c-74d2-abf4-e980f10d7bdf Co-authored-by: Amp <amp@ampcode.com> * fix: address greptile env fragility and model warning Amp-Thread-ID: https://ampcode.com/threads/T-019ce48e-f36c-74d2-abf4-e980f10d7bdf Co-authored-by: Amp <amp@ampcode.com> * fix(agents): harden opencode event flow and anthropic command execution Amp-Thread-ID: https://ampcode.com/threads/T-019ce48e-f36c-74d2-abf4-e980f10d7bdf Co-authored-by: Amp <amp@ampcode.com> * fix(opencode-sdk): stream events and provider-aware auth handling Amp-Thread-ID: https://ampcode.com/threads/T-019ce48e-f36c-74d2-abf4-e980f10d7bdf Co-authored-by: Amp <amp@ampcode.com> * fix: address minor greptile feedback on execa and sdk availability Amp-Thread-ID: https://ampcode.com/threads/T-019ce48e-f36c-74d2-abf4-e980f10d7bdf Co-authored-by: Amp <amp@ampcode.com> * fix(opencode-sdk): prevent duplicate text output after delta streaming Amp-Thread-ID: https://ampcode.com/threads/T-019ce48e-f36c-74d2-abf4-e980f10d7bdf Co-authored-by: Amp <amp@ampcode.com> * fix(opencode-sdk): guard stream promise rejections Amp-Thread-ID: https://ampcode.com/threads/T-019ce4df-e4cc-77c7-b880-50cdc69cd3db Co-authored-by: Amp <amp@ampcode.com> * fix(sdk-agents): honor apiKey fallback and preserve command streams Amp-Thread-ID: https://ampcode.com/threads/T-019ce4df-e4cc-77c7-b880-50cdc69cd3db Co-authored-by: Amp <amp@ampcode.com> --------- Co-authored-by: Amp <amp@ampcode.com>
1 parent 2b3385a commit fa5307a

File tree

10 files changed

+944
-216
lines changed

10 files changed

+944
-216
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ralph-starter",
3-
"version": "0.4.3",
3+
"version": "0.4.4",
44
"description": "Ralph Wiggum made easy. One command to run autonomous AI coding loops with auto-commit, PRs, and Docker sandbox.",
55
"main": "dist/index.js",
66
"bin": {
@@ -71,6 +71,7 @@
7171
"dependencies": {
7272
"@anthropic-ai/sdk": "^0.78.0",
7373
"@modelcontextprotocol/sdk": "^1.0.0",
74+
"@opencode-ai/sdk": "^1.2.25",
7475
"chalk": "^5.3.0",
7576
"chalk-animation": "^2.0.3",
7677
"commander": "^14.0.2",

pnpm-lock.yaml

Lines changed: 12 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cli.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,10 @@ program
6363
.option('--docker', 'Run in Docker sandbox (coming soon)')
6464
.option('--prd <file>', 'Read tasks from a PRD markdown file')
6565
.option('--max-iterations <n>', 'Maximum loop iterations (auto-calculated if not specified)')
66-
.option('--agent <name>', 'Specify agent (claude-code, cursor, codex, opencode, openclaw, amp)')
66+
.option(
67+
'--agent <name>',
68+
'Specify agent (claude-code, cursor, codex, opencode, openclaw, amp, anthropic-sdk, opencode-sdk)'
69+
)
6770
.option('--model <name>', 'Model to use (e.g., claude-sonnet-4-5-20250929, claude-opus-4-6)')
6871
.option(
6972
'--amp-mode <mode>',
@@ -415,7 +418,10 @@ program
415418
.option('--pr', 'Create a pull request when done')
416419
.option('--validate', 'Run tests/lint/build after each iteration')
417420
.option('--max-iterations <n>', 'Maximum loop iterations')
418-
.option('--agent <name>', 'Specify agent (claude-code, cursor, codex, opencode, openclaw, amp)')
421+
.option(
422+
'--agent <name>',
423+
'Specify agent (claude-code, cursor, codex, opencode, openclaw, amp, anthropic-sdk, opencode-sdk)'
424+
)
419425
.option(
420426
'--amp-mode <mode>',
421427
'Amp agent mode: smart (frontier), rush (fast), deep (extended reasoning)'

src/loop/__tests__/agents.test.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ function createMockChildProcess(exitCode: number, stdout = '', stderr = '') {
5151
describe('agents', () => {
5252
beforeEach(() => {
5353
vi.clearAllMocks();
54+
vi.unstubAllEnvs();
55+
vi.stubEnv('ANTHROPIC_API_KEY', '');
56+
vi.stubEnv('OPENCODE_API_KEY', '');
5457
});
5558

5659
describe('checkAgentAvailable', () => {
@@ -59,6 +62,25 @@ describe('agents', () => {
5962
expect(result).toBe(false);
6063
});
6164

65+
it('should return true for opencode-sdk when API key is set and opencode CLI exists', async () => {
66+
mockExeca.mockResolvedValueOnce({
67+
stdout: '1.0.0',
68+
stderr: '',
69+
exitCode: 0,
70+
} as any);
71+
72+
const result = await checkAgentAvailable('opencode-sdk', { apiKey: 'test-key' });
73+
expect(result).toBe(true);
74+
expect(mockExeca).toHaveBeenCalledWith('opencode', ['--version'], { timeout: 5000 });
75+
});
76+
77+
it('should return false for opencode-sdk when API key is set but opencode CLI is missing', async () => {
78+
mockExeca.mockRejectedValueOnce(new Error('opencode not found'));
79+
80+
const result = await checkAgentAvailable('opencode-sdk', { apiKey: 'test-key' });
81+
expect(result).toBe(false);
82+
});
83+
6284
it('should return true when agent command succeeds', async () => {
6385
mockExeca.mockResolvedValueOnce({
6486
stdout: '1.0.0',
@@ -89,10 +111,11 @@ describe('agents', () => {
89111
.mockRejectedValueOnce(new Error('not found')) // opencode
90112
.mockRejectedValueOnce(new Error('not found')) // openclaw
91113
.mockRejectedValueOnce(new Error('not found')); // amp
114+
// anthropic-sdk has no CLI check — availability is based on API key
92115

93116
const agents = await detectAvailableAgents();
94117

95-
expect(agents).toHaveLength(6);
118+
expect(agents).toHaveLength(8);
96119
expect(agents.find((a) => a.type === 'claude-code')?.available).toBe(true);
97120
expect(agents.find((a) => a.type === 'cursor')?.available).toBe(false);
98121
});

0 commit comments

Comments
 (0)