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 +32 -4
- package/package.json +10 -5
- package/src/agent.ts +18 -34
- package/src/claude-code.ts +102 -0
- package/src/index.ts +1 -0
- package/src/types.ts +6 -1
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-
|
|
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
|
-
|
|
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
|
+
"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.
|
|
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
|
|
2
|
+
* ocpipe agent integration.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
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
|
-
/**
|
|
15
|
-
function
|
|
16
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
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
|
-
/**
|
|
50
|
-
|
|
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
package/src/types.ts
CHANGED
|
@@ -8,8 +8,13 @@ import type { z } from 'zod/v4'
|
|
|
8
8
|
// Model Configuration
|
|
9
9
|
// ============================================================================
|
|
10
10
|
|
|
11
|
-
/**
|
|
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
|
}
|