ocpipe 0.3.9 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center"><strong>ocpipe</strong></p>
2
- <p align="center">Build LLM pipelines with <a href="https://github.com/sst/opencode">OpenCode</a> and <a href="https://zod.dev">Zod</a>.</p>
2
+ <p align="center">Build LLM pipelines with <a href="https://github.com/sst/opencode">OpenCode</a>, <a href="https://github.com/anthropics/claude-code">Claude Code</a>, and <a href="https://zod.dev">Zod</a>.</p>
3
3
  <p align="center">Inspired by <a href="https://github.com/stanfordnlp/dspy">DSPy</a>.</p>
4
4
  <p align="center">
5
5
  <a href="https://www.npmjs.com/package/ocpipe"><img alt="npm" src="https://img.shields.io/npm/v/ocpipe?style=flat-square" /></a>
@@ -11,7 +11,7 @@
11
11
  - **Type-safe** Define inputs and outputs with Zod schemas
12
12
  - **Modular** Compose modules into complex pipelines
13
13
  - **Checkpoints** Resume from any step
14
- - **Multi-model** Works with 75+ providers through OpenCode
14
+ - **Multi-backend** Choose between OpenCode (75+ providers) or Claude Code SDK
15
15
  - **Auto-correction** Fixes schema mismatches automatically
16
16
 
17
17
  ### Quick Start
@@ -47,15 +47,43 @@ type GreetIn = InferInputs<typeof Greet> // { name: string }
47
47
  type GreetOut = InferOutputs<typeof Greet> // { greeting: string }
48
48
  ```
49
49
 
50
- OpenCode CLI is bundled — run `bun run opencode` or use your system `opencode` if installed.
50
+ ### Backends
51
+
52
+ ocpipe supports two backends for running LLM agents:
53
+
54
+ **OpenCode** (default) - Requires `opencode` CLI in your PATH. Supports 75+ providers.
55
+
56
+ ```typescript
57
+ const pipeline = new Pipeline({
58
+ name: 'my-pipeline',
59
+ defaultModel: { providerID: 'anthropic', modelID: 'claude-sonnet-4-20250514' },
60
+ defaultAgent: 'default',
61
+ }, createBaseState)
62
+ ```
63
+
64
+ **Claude Code** - Uses `@anthropic-ai/claude-agent-sdk`. Install as a peer dependency.
65
+
66
+ ```typescript
67
+ const pipeline = new Pipeline({
68
+ name: 'my-pipeline',
69
+ defaultModel: { backend: 'claude-code', providerID: 'anthropic', modelID: 'claude-sonnet-4-20250514' },
70
+ defaultAgent: 'default',
71
+ }, createBaseState)
72
+ ```
51
73
 
52
74
  ### Requirements
53
75
 
54
- Currently requires [this OpenCode fork](https://github.com/paralin/opencode). Once the following PRs are merged, the official release will work:
76
+ **For OpenCode backend:** Currently requires [this OpenCode fork](https://github.com/paralin/opencode). Once the following PRs are merged, the official release will work:
55
77
 
56
78
  - [#5426](https://github.com/anomalyco/opencode/pull/5426) - Adds `--prompt-file` flag
57
79
  - [#5339](https://github.com/anomalyco/opencode/pull/5339) - Session export fixes
58
80
 
81
+ **For Claude Code backend:** Install the SDK as a peer dependency:
82
+
83
+ ```bash
84
+ bun add @anthropic-ai/claude-agent-sdk
85
+ ```
86
+
59
87
  ### Documentation
60
88
 
61
89
  - [Getting Started](./GETTING_STARTED.md) - Tutorial with examples
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ocpipe",
3
- "version": "0.3.9",
3
+ "version": "0.4.0",
4
4
  "description": "SDK for LLM pipelines with OpenCode and Zod",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -28,13 +28,18 @@
28
28
  "engines": {
29
29
  "bun": ">=1.0.0"
30
30
  },
31
- "dependencies": {
32
- "opencode-ai": "1.1.25"
33
- },
31
+ "dependencies": {},
34
32
  "peerDependencies": {
35
- "zod": "4.3.5"
33
+ "zod": "4.3.6",
34
+ "@anthropic-ai/claude-agent-sdk": "0.2.19"
35
+ },
36
+ "peerDependenciesMeta": {
37
+ "@anthropic-ai/claude-agent-sdk": {
38
+ "optional": true
39
+ }
36
40
  },
37
41
  "devDependencies": {
42
+ "@anthropic-ai/claude-agent-sdk": "^0.2.14",
38
43
  "@eslint/js": "^9.39.2",
39
44
  "bun-types": "^1.3.5",
40
45
  "eslint": "^9.39.2",
package/src/agent.ts CHANGED
@@ -1,53 +1,37 @@
1
1
  /**
2
- * ocpipe OpenCode agent integration.
2
+ * ocpipe agent integration.
3
3
  *
4
- * Wraps the OpenCode CLI for running LLM agents with session management.
4
+ * Dispatches to OpenCode CLI or Claude Code SDK based on backend configuration.
5
5
  */
6
6
 
7
7
  import { spawn } from 'child_process'
8
- import { existsSync } from 'fs'
9
8
  import { mkdir, writeFile, unlink } from 'fs/promises'
10
9
  import { join } from 'path'
11
10
  import { PROJECT_ROOT, TMP_DIR } from './paths.js'
12
11
  import type { RunAgentOptions, RunAgentResult } from './types.js'
13
12
 
14
- /** Find opencode binary from PATH, preferring non-node_modules locations */
15
- function findOpencode(): string | null {
16
- const pathDirs = (process.env.PATH || '').split(':')
13
+ /** Get command and args to invoke opencode from PATH */
14
+ function getOpencodeCommand(args: string[]): { cmd: string; args: string[] } {
15
+ return { cmd: 'opencode', args }
16
+ }
17
17
 
18
- // First pass: look for opencode in non-node_modules directories
19
- for (const dir of pathDirs) {
20
- if (dir.includes('node_modules')) continue
21
- const candidate = join(dir, 'opencode')
22
- if (existsSync(candidate)) {
23
- return candidate
24
- }
25
- }
18
+ /** runAgent dispatches to the appropriate backend based on model configuration. */
19
+ export async function runAgent(
20
+ options: RunAgentOptions,
21
+ ): Promise<RunAgentResult> {
22
+ const backend = options.model.backend ?? 'opencode'
26
23
 
27
- // Second pass: check node_modules/.bin as fallback
28
- for (const dir of pathDirs) {
29
- if (!dir.includes('node_modules')) continue
30
- const candidate = join(dir, 'opencode')
31
- if (existsSync(candidate)) {
32
- return candidate
33
- }
24
+ if (backend === 'claude-code') {
25
+ // Dynamic import to avoid requiring @anthropic-ai/claude-agent-sdk when using opencode
26
+ const { runClaudeCodeAgent } = await import('./claude-code.js')
27
+ return runClaudeCodeAgent(options)
34
28
  }
35
29
 
36
- return null
37
- }
38
-
39
- /** Get command and args to invoke opencode */
40
- function getOpencodeCommand(args: string[]): { cmd: string; args: string[] } {
41
- const opencode = findOpencode()
42
- if (opencode) {
43
- return { cmd: opencode, args }
44
- }
45
- // Fallback to bunx with ocpipe package (which has opencode-ai as dependency)
46
- return { cmd: 'bunx', args: ['-p', 'ocpipe', 'opencode', ...args] }
30
+ return runOpencodeAgent(options)
47
31
  }
48
32
 
49
- /** runAgent executes an OpenCode agent with a prompt, streaming output in real-time. */
50
- export async function runAgent(
33
+ /** runOpencodeAgent executes an OpenCode agent with a prompt, streaming output in real-time. */
34
+ async function runOpencodeAgent(
51
35
  options: RunAgentOptions,
52
36
  ): Promise<RunAgentResult> {
53
37
  const { prompt, agent, model, sessionId, timeoutSec = 300, workdir } = options
@@ -0,0 +1,102 @@
1
+ /**
2
+ * ocpipe Claude Code agent integration.
3
+ *
4
+ * Uses the Claude Agent SDK v2 for running LLM agents with session management.
5
+ */
6
+
7
+ import {
8
+ unstable_v2_createSession,
9
+ unstable_v2_resumeSession,
10
+ type SDKMessage,
11
+ } from '@anthropic-ai/claude-agent-sdk'
12
+ import type { RunAgentOptions, RunAgentResult } from './types.js'
13
+
14
+ /** Extract text from assistant messages. */
15
+ function getAssistantText(msg: SDKMessage): string | null {
16
+ if (msg.type !== 'assistant') return null
17
+ const textParts: string[] = []
18
+ for (const block of msg.message.content) {
19
+ if (block.type === 'text') {
20
+ textParts.push(block.text)
21
+ }
22
+ }
23
+ return textParts.join('')
24
+ }
25
+
26
+ /** runClaudeCodeAgent executes a Claude Code agent with a prompt. */
27
+ export async function runClaudeCodeAgent(
28
+ options: RunAgentOptions,
29
+ ): Promise<RunAgentResult> {
30
+ const { prompt, model, sessionId, timeoutSec = 300 } = options
31
+
32
+ // Claude Agent SDK only uses modelID, not providerID
33
+ const modelStr = model.modelID
34
+ const sessionInfo = sessionId ? `[session:${sessionId}]` : '[new session]'
35
+ const promptPreview = prompt.slice(0, 50).replace(/\n/g, ' ')
36
+
37
+ console.error(
38
+ `\n>>> Claude Code [${modelStr}] ${sessionInfo}: ${promptPreview}...`,
39
+ )
40
+
41
+ // Create or resume session
42
+ const session =
43
+ sessionId ?
44
+ unstable_v2_resumeSession(sessionId, { model: modelStr })
45
+ : unstable_v2_createSession({ model: modelStr })
46
+
47
+ try {
48
+ // Send the prompt
49
+ await session.send(prompt)
50
+
51
+ // Collect the response
52
+ const textParts: string[] = []
53
+ let newSessionId = sessionId || ''
54
+
55
+ // Set up timeout
56
+ const timeoutPromise =
57
+ timeoutSec > 0 ?
58
+ new Promise<never>((_, reject) => {
59
+ setTimeout(() => {
60
+ session.close()
61
+ reject(new Error(`Timeout after ${timeoutSec}s`))
62
+ }, timeoutSec * 1000)
63
+ })
64
+ : null
65
+
66
+ // Stream the response
67
+ const streamPromise = (async () => {
68
+ for await (const msg of session.stream()) {
69
+ // Capture session ID from any message
70
+ if (msg.session_id) {
71
+ newSessionId = msg.session_id
72
+ }
73
+
74
+ const text = getAssistantText(msg)
75
+ if (text) {
76
+ textParts.push(text)
77
+ process.stderr.write(text)
78
+ }
79
+ }
80
+ })()
81
+
82
+ // Race between stream and timeout
83
+ if (timeoutPromise) {
84
+ await Promise.race([streamPromise, timeoutPromise])
85
+ } else {
86
+ await streamPromise
87
+ }
88
+
89
+ const response = textParts.join('')
90
+ const sessionStr = newSessionId || 'none'
91
+ console.error(
92
+ `\n<<< Claude Code done (${response.length} chars) [session:${sessionStr}]`,
93
+ )
94
+
95
+ return {
96
+ text: response,
97
+ sessionId: newSessionId,
98
+ }
99
+ } finally {
100
+ session.close()
101
+ }
102
+ }
package/src/index.ts CHANGED
@@ -87,6 +87,7 @@ export type { MockResponse } from './testing.js'
87
87
  // Types
88
88
  export type {
89
89
  // Core types
90
+ BackendType,
90
91
  ModelConfig,
91
92
  ExecutionContext,
92
93
  StepResult,
package/src/types.ts CHANGED
@@ -8,8 +8,13 @@ import type { z } from 'zod/v4'
8
8
  // Model Configuration
9
9
  // ============================================================================
10
10
 
11
- /** Model configuration for OpenCode. */
11
+ /** Backend type for running agents. */
12
+ export type BackendType = 'opencode' | 'claude-code'
13
+
14
+ /** Model configuration for LLM backends. */
12
15
  export interface ModelConfig {
16
+ /** Backend to use (default: 'opencode'). */
17
+ backend?: BackendType
13
18
  providerID: string
14
19
  modelID: string
15
20
  }