ocpipe 0.6.10 → 0.6.11
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 +19 -22
- package/example/correction.ts +1 -2
- package/example/index.ts +6 -7
- package/package.json +4 -3
- package/src/agent.ts +179 -210
- package/src/claude-code.ts +19 -24
- package/src/index.ts +2 -3
- package/src/pipeline.ts +7 -7
- package/src/predict.ts +6 -8
- package/src/testing.ts +7 -5
- package/src/types.ts +36 -41
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
|
|
2
|
+
<p align="center">Build LLM pipelines with Oh My Pi, OpenCode, Claude Code, Codex, Pi, 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-backend** Choose between
|
|
14
|
+
- **Multi-backend** Choose between Oh My Pi, OpenCode, Claude Code SDK, Codex SDK, or Pi
|
|
15
15
|
- **Auto-correction** Fixes schema mismatches automatically
|
|
16
16
|
|
|
17
17
|
### Quick Start
|
|
@@ -32,8 +32,9 @@ const Greet = signature({
|
|
|
32
32
|
const pipeline = new Pipeline(
|
|
33
33
|
{
|
|
34
34
|
name: 'hello-world',
|
|
35
|
-
defaultModel: {
|
|
36
|
-
|
|
35
|
+
defaultModel: { backend: 'omp', modelID: 'gpt-5.5' },
|
|
36
|
+
checkpointDir: './ckpt',
|
|
37
|
+
logDir: './logs',
|
|
37
38
|
},
|
|
38
39
|
createBaseState,
|
|
39
40
|
)
|
|
@@ -49,24 +50,28 @@ type GreetOut = InferOutputs<typeof Greet> // { greeting: string }
|
|
|
49
50
|
|
|
50
51
|
### Backends
|
|
51
52
|
|
|
52
|
-
ocpipe supports five backends for running LLM
|
|
53
|
+
ocpipe supports five backends for running LLM prompts:
|
|
53
54
|
|
|
54
|
-
**
|
|
55
|
+
**Oh My Pi** (default) - Uses the `omp` CLI in headless JSON print mode.
|
|
55
56
|
|
|
56
57
|
```typescript
|
|
57
58
|
const pipeline = new Pipeline(
|
|
58
59
|
{
|
|
59
60
|
name: 'my-pipeline',
|
|
60
|
-
defaultModel: {
|
|
61
|
-
|
|
62
|
-
modelID: 'claude-sonnet-4-20250514',
|
|
63
|
-
},
|
|
64
|
-
defaultAgent: 'default',
|
|
61
|
+
defaultModel: { backend: 'omp', modelID: 'gpt-5.5' },
|
|
62
|
+
omp: { command: 'omp', approvalMode: 'yolo', thinking: 'high' },
|
|
65
63
|
},
|
|
66
64
|
createBaseState,
|
|
67
65
|
)
|
|
68
66
|
```
|
|
69
67
|
|
|
68
|
+
**OpenCode** - Uses the `opencode` CLI. ocpipe passes prompt files directly and does not create or reference `.opencode/agents` definitions.
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
defaultModel: { backend: 'opencode', providerID: 'anthropic', modelID: 'claude-sonnet-4' },
|
|
72
|
+
opencode: { command: 'opencode' },
|
|
73
|
+
```
|
|
74
|
+
|
|
70
75
|
**Claude Code** - Uses `@anthropic-ai/claude-agent-sdk`. Install as a peer dependency.
|
|
71
76
|
|
|
72
77
|
```typescript
|
|
@@ -83,13 +88,6 @@ defaultModel: { backend: 'codex', modelID: 'gpt-5.4' },
|
|
|
83
88
|
codex: { sandbox: 'read-only', reasoningEffort: 'high' },
|
|
84
89
|
```
|
|
85
90
|
|
|
86
|
-
**Oh My Pi** - Uses the `omp` CLI in headless JSON print mode.
|
|
87
|
-
|
|
88
|
-
```typescript
|
|
89
|
-
defaultModel: { backend: 'omp', modelID: 'gpt-5.5' },
|
|
90
|
-
omp: { command: 'omp', approvalMode: 'yolo', thinking: 'high' },
|
|
91
|
-
```
|
|
92
|
-
|
|
93
91
|
**Pi** - Uses the `pi` coding-agent CLI JSONL RPC mode.
|
|
94
92
|
|
|
95
93
|
```typescript
|
|
@@ -99,10 +97,9 @@ pi: { command: 'pi' },
|
|
|
99
97
|
|
|
100
98
|
### Requirements
|
|
101
99
|
|
|
102
|
-
**For
|
|
100
|
+
**For Oh My Pi:** Install the `omp` CLI and authenticate the models it uses.
|
|
103
101
|
|
|
104
|
-
|
|
105
|
-
- [#5339](https://github.com/anomalyco/opencode/pull/5339) - Adds `--tools` flag to limit available tools (optional)
|
|
102
|
+
**For OpenCode backend:** Install the `opencode` CLI and authenticate the providers it uses. ocpipe does not require a `.opencode` directory.
|
|
106
103
|
|
|
107
104
|
**For Claude Code backend:** Install the SDK as a peer dependency:
|
|
108
105
|
|
|
@@ -126,6 +123,6 @@ bun add @openai/codex-sdk
|
|
|
126
123
|
|
|
127
124
|
---
|
|
128
125
|
|
|
129
|
-
[
|
|
126
|
+
[Oh My Pi](https://github.com/aperturerobotics/oh-my-pi) · [OpenCode](https://github.com/sst/opencode)
|
|
130
127
|
|
|
131
128
|
<sub>An [Aperture Robotics](https://github.com/aperturerobotics) project.</sub>
|
package/example/correction.ts
CHANGED
|
@@ -71,8 +71,7 @@ async function main() {
|
|
|
71
71
|
const pipeline = new Pipeline(
|
|
72
72
|
{
|
|
73
73
|
name: 'correction-demo',
|
|
74
|
-
defaultModel: {
|
|
75
|
-
defaultAgent: 'default',
|
|
74
|
+
defaultModel: { backend: 'omp', modelID: 'gpt-5.5' },
|
|
76
75
|
checkpointDir: './ckpt',
|
|
77
76
|
logDir: './logs',
|
|
78
77
|
},
|
package/example/index.ts
CHANGED
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Demonstrates running an ocpipe module in a pipeline.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* npx tsx example/index.ts
|
|
8
|
-
* npx tsx example/index.ts --claude-code # Use claude-code backend
|
|
6
|
+
* npx tsx example/index.ts # Use OMP backend
|
|
7
|
+
* npx tsx example/index.ts --claude-code # Use Claude Code backend
|
|
9
8
|
*/
|
|
10
9
|
|
|
11
10
|
import { Pipeline, createBaseState } from '../src/index.js'
|
|
@@ -18,10 +17,10 @@ async function main() {
|
|
|
18
17
|
const pipeline = new Pipeline(
|
|
19
18
|
{
|
|
20
19
|
name: 'hello-world',
|
|
21
|
-
defaultModel:
|
|
22
|
-
?
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
defaultModel:
|
|
21
|
+
useClaudeCode ?
|
|
22
|
+
{ backend: 'claude-code', modelID: 'sonnet' }
|
|
23
|
+
: { backend: 'omp', modelID: 'gpt-5.5' },
|
|
25
24
|
checkpointDir: './ckpt',
|
|
26
25
|
logDir: './logs',
|
|
27
26
|
},
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ocpipe",
|
|
3
|
-
"version": "0.6.
|
|
4
|
-
"description": "SDK for LLM pipelines with OpenCode, Codex, and Zod",
|
|
3
|
+
"version": "0.6.11",
|
|
4
|
+
"description": "SDK for LLM pipelines with OpenCode, OMP, Codex, Claude Code, Pi, and Zod",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"types": "src/index.ts",
|
|
@@ -17,9 +17,10 @@
|
|
|
17
17
|
"author": "s4wave",
|
|
18
18
|
"keywords": [
|
|
19
19
|
"dspy",
|
|
20
|
-
"
|
|
20
|
+
"omp",
|
|
21
21
|
"opencode",
|
|
22
22
|
"codex",
|
|
23
|
+
"claude-code",
|
|
23
24
|
"typescript",
|
|
24
25
|
"ai",
|
|
25
26
|
"workflow",
|
package/src/agent.ts
CHANGED
|
@@ -1,41 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ocpipe agent integration.
|
|
3
3
|
*
|
|
4
|
-
* Dispatches to
|
|
5
|
-
* configuration.
|
|
4
|
+
* Dispatches prompts to configured coding-agent backends.
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
7
|
import { spawn } from 'child_process'
|
|
9
|
-
import {
|
|
8
|
+
import { randomUUID } from 'crypto'
|
|
9
|
+
import { mkdir, unlink, writeFile } from 'fs/promises'
|
|
10
10
|
import { join } from 'path'
|
|
11
11
|
import { PROJECT_ROOT, TMP_DIR } from './paths.js'
|
|
12
|
+
import type { RunAgentOptions, RunAgentResult } from './types.js'
|
|
12
13
|
import { runOmpAgent } from './omp.js'
|
|
13
14
|
import { runPiAgent } from './pi.js'
|
|
14
|
-
import type { RunAgentOptions, RunAgentResult } from './types.js'
|
|
15
|
-
|
|
16
|
-
/** Get command and args to invoke opencode from PATH */
|
|
17
|
-
function getOpencodeCommand(args: string[]): { cmd: string; args: string[] } {
|
|
18
|
-
return { cmd: 'opencode', args }
|
|
19
|
-
}
|
|
20
15
|
|
|
21
|
-
/** runAgent dispatches to the
|
|
16
|
+
/** runAgent dispatches to the selected backend. */
|
|
22
17
|
export async function runAgent(
|
|
23
18
|
options: RunAgentOptions,
|
|
24
19
|
): Promise<RunAgentResult> {
|
|
25
|
-
const backend = options.model.backend ?? '
|
|
20
|
+
const backend = options.model.backend ?? 'omp'
|
|
26
21
|
|
|
27
22
|
if (backend === 'claude-code') {
|
|
28
|
-
// Dynamic import is required: @anthropic-ai/claude-agent-sdk is an optional peer absent for other backends.
|
|
29
23
|
const { runClaudeCodeAgent } = await import('./claude-code.js')
|
|
30
24
|
return runClaudeCodeAgent(options)
|
|
31
25
|
}
|
|
32
26
|
|
|
33
27
|
if (backend === 'codex') {
|
|
34
|
-
// Dynamic import is required: @openai/codex-sdk is an optional peer absent for other backends.
|
|
35
28
|
const { runCodexAgent } = await import('./codex.js')
|
|
36
29
|
return runCodexAgent(options)
|
|
37
30
|
}
|
|
38
31
|
|
|
32
|
+
if (backend === 'opencode') {
|
|
33
|
+
return runOpencodeAgent(options)
|
|
34
|
+
}
|
|
35
|
+
|
|
39
36
|
if (backend === 'pi') {
|
|
40
37
|
return runPiAgent(options)
|
|
41
38
|
}
|
|
@@ -44,52 +41,48 @@ export async function runAgent(
|
|
|
44
41
|
return runOmpAgent(options)
|
|
45
42
|
}
|
|
46
43
|
|
|
47
|
-
|
|
44
|
+
const unreachable: never = backend
|
|
45
|
+
throw new Error(`Unsupported backend: ${unreachable}`)
|
|
48
46
|
}
|
|
49
47
|
|
|
50
|
-
/** runOpencodeAgent executes an OpenCode agent with a prompt, streaming output in real-time. */
|
|
51
48
|
async function runOpencodeAgent(
|
|
52
49
|
options: RunAgentOptions,
|
|
53
50
|
): Promise<RunAgentResult> {
|
|
54
51
|
const {
|
|
55
52
|
prompt,
|
|
56
|
-
agent,
|
|
57
53
|
model,
|
|
58
54
|
sessionId,
|
|
59
55
|
timeoutSec = 3600,
|
|
60
56
|
workdir,
|
|
57
|
+
opencode,
|
|
61
58
|
signal,
|
|
62
59
|
} = options
|
|
63
60
|
|
|
64
61
|
if (!model.providerID) {
|
|
65
62
|
throw new Error('providerID is required for OpenCode backend')
|
|
66
63
|
}
|
|
67
|
-
const modelStr = `${model.providerID}/${model.modelID}`
|
|
68
|
-
const sessionInfo = sessionId ? `[session:${sessionId}]` : '[new session]'
|
|
69
|
-
const promptPreview = prompt.slice(0, 50).replace(/\n/g, ' ')
|
|
70
64
|
|
|
71
|
-
console.error(
|
|
72
|
-
`\n>>> OpenCode [${agent}] [${modelStr}] ${sessionInfo}: ${promptPreview}...`,
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
// Check if already aborted
|
|
76
65
|
if (signal?.aborted) {
|
|
77
66
|
throw new Error('Request aborted')
|
|
78
67
|
}
|
|
79
68
|
|
|
80
|
-
// Write prompt to .opencode/prompts/ within the working directory
|
|
81
69
|
const cwd = workdir ?? PROJECT_ROOT
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
const
|
|
70
|
+
const modelStr = `${model.providerID}/${model.modelID}`
|
|
71
|
+
const sessionInfo = sessionId ? `[session:${sessionId}]` : '[new session]'
|
|
72
|
+
const promptPreview = prompt.slice(0, 50).replace(/\n/g, ' ')
|
|
73
|
+
console.error(
|
|
74
|
+
`\n>>> OpenCode [${modelStr}] ${sessionInfo}: ${promptPreview}...`,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
const promptDir = opencode?.promptDir ?? TMP_DIR
|
|
78
|
+
await mkdir(promptDir, { recursive: true })
|
|
79
|
+
const promptFile = join(promptDir, `ocpipe_prompt_${randomUUID()}.txt`)
|
|
85
80
|
await writeFile(promptFile, prompt)
|
|
86
81
|
|
|
87
82
|
const args = [
|
|
88
83
|
'run',
|
|
89
84
|
'--format',
|
|
90
85
|
'default',
|
|
91
|
-
'--agent',
|
|
92
|
-
agent,
|
|
93
86
|
'--model',
|
|
94
87
|
modelStr,
|
|
95
88
|
'--prompt-file',
|
|
@@ -99,11 +92,9 @@ async function runOpencodeAgent(
|
|
|
99
92
|
if (sessionId) {
|
|
100
93
|
args.push('--session', sessionId)
|
|
101
94
|
}
|
|
102
|
-
|
|
103
95
|
if (model.variant) {
|
|
104
96
|
args.push('--model-variant', model.variant)
|
|
105
97
|
}
|
|
106
|
-
|
|
107
98
|
if (model.variantThinkingBudget !== undefined) {
|
|
108
99
|
args.push(
|
|
109
100
|
'--model-variant-thinking-budget',
|
|
@@ -111,194 +102,164 @@ async function runOpencodeAgent(
|
|
|
111
102
|
)
|
|
112
103
|
}
|
|
113
104
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
let newSessionId = sessionId || ''
|
|
125
|
-
const stdoutChunks: string[] = []
|
|
126
|
-
const stderrChunks: string[] = []
|
|
127
|
-
let aborted = false
|
|
128
|
-
|
|
129
|
-
// Handle abort signal - kill subprocess when aborted
|
|
130
|
-
const abortHandler = async () => {
|
|
131
|
-
if (aborted) return
|
|
132
|
-
aborted = true
|
|
133
|
-
console.error(`\n[abort] Killing OpenCode subprocess...`)
|
|
134
|
-
proc.kill('SIGTERM')
|
|
135
|
-
// Give it a moment to clean up, then force kill
|
|
136
|
-
setTimeout(() => {
|
|
137
|
-
if (!proc.killed) proc.kill('SIGKILL')
|
|
138
|
-
}, 1000)
|
|
139
|
-
await unlink(promptFile).catch(() => {})
|
|
140
|
-
reject(new Error('Request aborted'))
|
|
141
|
-
}
|
|
142
|
-
signal?.addEventListener('abort', abortHandler, { once: true })
|
|
143
|
-
|
|
144
|
-
// Stream stderr in real-time (OpenCode progress output)
|
|
145
|
-
proc.stderr.on('data', (data: Buffer) => {
|
|
146
|
-
const text = data.toString()
|
|
147
|
-
stderrChunks.push(text)
|
|
148
|
-
|
|
149
|
-
// Parse session ID from output
|
|
150
|
-
for (const line of text.split('\n')) {
|
|
151
|
-
if (line.startsWith('[session:')) {
|
|
152
|
-
newSessionId = line.trim().slice(9, -1)
|
|
153
|
-
continue
|
|
154
|
-
}
|
|
155
|
-
// Filter noise
|
|
156
|
-
if (line.includes('baseline-browser-mapping')) continue
|
|
157
|
-
if (line.startsWith('$ bun run')) continue
|
|
158
|
-
if (line.trim()) {
|
|
159
|
-
process.stderr.write(line + '\n')
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
// Collect stdout
|
|
165
|
-
proc.stdout.on('data', (data: Buffer) => {
|
|
166
|
-
const text = data.toString()
|
|
167
|
-
stdoutChunks.push(text)
|
|
168
|
-
process.stderr.write(text)
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
// Timeout handling (0 = no timeout)
|
|
172
|
-
const timeout =
|
|
173
|
-
timeoutSec > 0 ?
|
|
174
|
-
setTimeout(async () => {
|
|
175
|
-
proc.kill()
|
|
176
|
-
await unlink(promptFile).catch(() => {})
|
|
177
|
-
reject(new Error(`Timeout after ${timeoutSec}s`))
|
|
178
|
-
}, timeoutSec * 1000)
|
|
179
|
-
: null
|
|
180
|
-
|
|
181
|
-
proc.on('close', async (code) => {
|
|
182
|
-
if (timeout) clearTimeout(timeout)
|
|
183
|
-
signal?.removeEventListener('abort', abortHandler)
|
|
184
|
-
|
|
185
|
-
// If aborted, we already rejected
|
|
186
|
-
if (aborted) return
|
|
187
|
-
|
|
188
|
-
// Clean up prompt file
|
|
189
|
-
await unlink(promptFile).catch(() => {})
|
|
190
|
-
|
|
191
|
-
const stderr = stderrChunks.join('').trim()
|
|
192
|
-
|
|
193
|
-
if (code !== 0) {
|
|
194
|
-
const lastLines = stderr.split('\n').slice(-5).join('\n')
|
|
195
|
-
const detail = lastLines ? `\n${lastLines}` : ''
|
|
196
|
-
reject(new Error(`OpenCode exited with code ${code}${detail}`))
|
|
197
|
-
return
|
|
198
|
-
}
|
|
105
|
+
const command = opencode?.command ?? 'opencode'
|
|
106
|
+
console.error(`[DEBUG] Running: ${command} ${args.join(' ')}`)
|
|
107
|
+
const proc = spawn(command, args, {
|
|
108
|
+
cwd,
|
|
109
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
110
|
+
env: { ...process.env, ...opencode?.env },
|
|
111
|
+
})
|
|
199
112
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const errorLines = stderr
|
|
219
|
-
.split('\n')
|
|
220
|
-
.filter(
|
|
221
|
-
(line) =>
|
|
222
|
-
pattern.test(line) ||
|
|
223
|
-
line.includes('Error') ||
|
|
224
|
-
line.includes('error:'),
|
|
225
|
-
)
|
|
226
|
-
.slice(0, 5)
|
|
227
|
-
.join('\n')
|
|
228
|
-
reject(new Error(`OpenCode ${message}:\n${errorLines}`))
|
|
229
|
-
return
|
|
230
|
-
}
|
|
231
|
-
}
|
|
113
|
+
const { promise, resolve, reject } = Promise.withResolvers<RunAgentResult>()
|
|
114
|
+
let newSessionId = sessionId || ''
|
|
115
|
+
const stdoutChunks: string[] = []
|
|
116
|
+
const stderrChunks: string[] = []
|
|
117
|
+
let aborted = false
|
|
118
|
+
|
|
119
|
+
const abortHandler = () => {
|
|
120
|
+
if (aborted) return
|
|
121
|
+
aborted = true
|
|
122
|
+
console.error(`\n[abort] Killing OpenCode subprocess...`)
|
|
123
|
+
proc.kill('SIGTERM')
|
|
124
|
+
setTimeout(() => {
|
|
125
|
+
if (!proc.killed) proc.kill('SIGKILL')
|
|
126
|
+
}, 1000)
|
|
127
|
+
void unlink(promptFile).catch(() => {})
|
|
128
|
+
reject(new Error('Request aborted'))
|
|
129
|
+
}
|
|
130
|
+
signal?.addEventListener('abort', abortHandler, { once: true })
|
|
232
131
|
|
|
233
|
-
|
|
234
|
-
|
|
132
|
+
proc.stderr.on('data', (data: Buffer) => {
|
|
133
|
+
const text = data.toString()
|
|
134
|
+
stderrChunks.push(text)
|
|
235
135
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
136
|
+
for (const line of text.split('\n')) {
|
|
137
|
+
if (line.startsWith('[session:')) {
|
|
138
|
+
newSessionId = line.trim().slice(9, -1)
|
|
139
|
+
continue
|
|
140
|
+
}
|
|
141
|
+
if (line.includes('baseline-browser-mapping')) continue
|
|
142
|
+
if (line.startsWith('$ bun run')) continue
|
|
143
|
+
if (line.trim()) {
|
|
144
|
+
process.stderr.write(line + '\n')
|
|
241
145
|
}
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
proc.stdout.on('data', (data: Buffer) => {
|
|
150
|
+
const text = data.toString()
|
|
151
|
+
stdoutChunks.push(text)
|
|
152
|
+
process.stderr.write(text)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
const timeout =
|
|
156
|
+
timeoutSec > 0 ?
|
|
157
|
+
setTimeout(() => {
|
|
158
|
+
proc.kill()
|
|
159
|
+
void unlink(promptFile).catch(() => {})
|
|
160
|
+
reject(new Error(`Timeout after ${timeoutSec}s`))
|
|
161
|
+
}, timeoutSec * 1000)
|
|
162
|
+
: undefined
|
|
163
|
+
|
|
164
|
+
proc.on('close', async (code) => {
|
|
165
|
+
clearTimeout(timeout)
|
|
166
|
+
signal?.removeEventListener('abort', abortHandler)
|
|
167
|
+
if (aborted) return
|
|
168
|
+
|
|
169
|
+
void unlink(promptFile).catch(() => {})
|
|
170
|
+
const stderr = stderrChunks.join('').trim()
|
|
171
|
+
|
|
172
|
+
if (code !== 0) {
|
|
173
|
+
const lastLines = stderr.split('\n').slice(-5).join('\n')
|
|
174
|
+
const detail = lastLines ? `\n${lastLines}` : ''
|
|
175
|
+
reject(new Error(`OpenCode exited with code ${code}${detail}`))
|
|
176
|
+
return
|
|
177
|
+
}
|
|
242
178
|
|
|
243
|
-
|
|
244
|
-
if (
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
179
|
+
for (const { pattern, message } of knownOpenCodeErrors) {
|
|
180
|
+
if (pattern.test(stderr)) {
|
|
181
|
+
const errorLines = stderr
|
|
182
|
+
.split('\n')
|
|
183
|
+
.filter(
|
|
184
|
+
(line) =>
|
|
185
|
+
pattern.test(line) ||
|
|
186
|
+
line.includes('Error') ||
|
|
187
|
+
line.includes('error:'),
|
|
188
|
+
)
|
|
189
|
+
.slice(0, 5)
|
|
190
|
+
.join('\n')
|
|
191
|
+
reject(new Error(`OpenCode ${message}:\n${errorLines}`))
|
|
251
192
|
return
|
|
252
193
|
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let response = stdoutChunks.join('').trim()
|
|
197
|
+
if (newSessionId) {
|
|
198
|
+
response =
|
|
199
|
+
(await exportSession(newSessionId, cwd, command, opencode?.env)) ??
|
|
200
|
+
response
|
|
201
|
+
}
|
|
253
202
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
203
|
+
if (response.length === 0 && stderr.includes('Error')) {
|
|
204
|
+
const lastLines = stderr.split('\n').slice(-10).join('\n')
|
|
205
|
+
reject(
|
|
206
|
+
new Error(
|
|
207
|
+
`OpenCode returned empty response with errors:\n${lastLines}`,
|
|
208
|
+
),
|
|
257
209
|
)
|
|
210
|
+
return
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const sessionStr = newSessionId || 'none'
|
|
214
|
+
console.error(
|
|
215
|
+
`<<< OpenCode done (${response.length} chars) [session:${sessionStr}]`,
|
|
216
|
+
)
|
|
217
|
+
resolve({ text: response, sessionId: newSessionId })
|
|
218
|
+
})
|
|
258
219
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
proc.on('error', async (err) => {
|
|
266
|
-
if (timeout) clearTimeout(timeout)
|
|
267
|
-
await unlink(promptFile).catch(() => {})
|
|
268
|
-
reject(err)
|
|
269
|
-
})
|
|
220
|
+
proc.on('error', (err) => {
|
|
221
|
+
clearTimeout(timeout)
|
|
222
|
+
signal?.removeEventListener('abort', abortHandler)
|
|
223
|
+
void unlink(promptFile).catch(() => {})
|
|
224
|
+
reject(err)
|
|
270
225
|
})
|
|
226
|
+
|
|
227
|
+
return promise
|
|
271
228
|
}
|
|
272
229
|
|
|
273
|
-
/** exportSession exports a session and extracts assistant text responses. */
|
|
274
230
|
async function exportSession(
|
|
275
231
|
sessionId: string,
|
|
276
|
-
workdir
|
|
232
|
+
workdir: string,
|
|
233
|
+
command: string,
|
|
234
|
+
env: Record<string, string> | undefined,
|
|
277
235
|
): Promise<string | null> {
|
|
278
|
-
const
|
|
236
|
+
const exportFile = join(TMP_DIR, `opencode_export_${randomUUID()}.json`)
|
|
279
237
|
|
|
280
238
|
try {
|
|
281
239
|
await mkdir(TMP_DIR, { recursive: true })
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
240
|
+
const proc = Bun.spawn(
|
|
241
|
+
[
|
|
242
|
+
command,
|
|
243
|
+
'session',
|
|
244
|
+
'export',
|
|
245
|
+
sessionId,
|
|
246
|
+
'--format',
|
|
247
|
+
'json',
|
|
248
|
+
'--turn',
|
|
249
|
+
'-1',
|
|
250
|
+
'-o',
|
|
251
|
+
exportFile,
|
|
252
|
+
],
|
|
253
|
+
{
|
|
254
|
+
cwd: workdir,
|
|
255
|
+
stdout: 'pipe',
|
|
256
|
+
stderr: 'pipe',
|
|
257
|
+
env: { ...process.env, ...env },
|
|
258
|
+
},
|
|
259
|
+
)
|
|
298
260
|
|
|
299
261
|
await proc.exited
|
|
300
|
-
|
|
301
|
-
const file = Bun.file(tmpPath)
|
|
262
|
+
const file = Bun.file(exportFile)
|
|
302
263
|
if (!(await file.exists())) return null
|
|
303
264
|
|
|
304
265
|
const data = (await file.json()) as {
|
|
@@ -307,29 +268,37 @@ async function exportSession(
|
|
|
307
268
|
parts?: Array<{ type?: string; text?: string }>
|
|
308
269
|
}>
|
|
309
270
|
}
|
|
310
|
-
|
|
271
|
+
void unlink(exportFile).catch(() => {})
|
|
311
272
|
|
|
312
|
-
// Extract all assistant text parts from the exported turn.
|
|
313
|
-
// The --turn -1 flag ensures we only get the last turn's messages.
|
|
314
|
-
const messages = data.messages || []
|
|
315
273
|
const textParts: string[] = []
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
textParts.push(part.text)
|
|
322
|
-
}
|
|
274
|
+
for (const msg of data.messages ?? []) {
|
|
275
|
+
if (msg.info?.role !== 'assistant') continue
|
|
276
|
+
for (const part of msg.parts ?? []) {
|
|
277
|
+
if (part.type === 'text' && part.text) {
|
|
278
|
+
textParts.push(part.text)
|
|
323
279
|
}
|
|
324
280
|
}
|
|
325
281
|
}
|
|
326
282
|
|
|
327
283
|
return textParts.length > 0 ? textParts.join('\n') : null
|
|
328
284
|
} catch {
|
|
285
|
+
void unlink(exportFile).catch(() => {})
|
|
329
286
|
return null
|
|
330
287
|
}
|
|
331
288
|
}
|
|
332
289
|
|
|
290
|
+
const knownOpenCodeErrors: ReadonlyArray<{ pattern: RegExp; message: string }> =
|
|
291
|
+
[
|
|
292
|
+
{
|
|
293
|
+
pattern: /ProviderModelNotFoundError/,
|
|
294
|
+
message: 'Provider/model not found',
|
|
295
|
+
},
|
|
296
|
+
{ pattern: /ModelNotFoundError/, message: 'Model not found' },
|
|
297
|
+
{ pattern: /ProviderNotFoundError/, message: 'Provider not found' },
|
|
298
|
+
{ pattern: /API key.*not.*found/i, message: 'API key not configured' },
|
|
299
|
+
{ pattern: /authentication.*failed/i, message: 'Authentication failed' },
|
|
300
|
+
]
|
|
301
|
+
|
|
333
302
|
/** logStep logs a step header for workflow progress. */
|
|
334
303
|
export function logStep(step: number, title: string, detail = ''): void {
|
|
335
304
|
const detailStr = detail ? ` (${detail})` : ''
|
package/src/claude-code.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { execSync } from 'child_process'
|
|
14
|
-
import { existsSync
|
|
14
|
+
import { existsSync } from 'fs'
|
|
15
15
|
import { join } from 'path'
|
|
16
16
|
import { homedir } from 'os'
|
|
17
17
|
import {
|
|
@@ -66,9 +66,14 @@ function resolveClaudeCodePath(explicit?: string): string | undefined {
|
|
|
66
66
|
if (explicit) return explicit
|
|
67
67
|
|
|
68
68
|
try {
|
|
69
|
-
const found = execSync('which claude', {
|
|
69
|
+
const found = execSync('which claude', {
|
|
70
|
+
encoding: 'utf8',
|
|
71
|
+
timeout: 3000,
|
|
72
|
+
}).trim()
|
|
70
73
|
if (found) return found
|
|
71
|
-
} catch {
|
|
74
|
+
} catch {
|
|
75
|
+
/* which not found or timed out — fall through to defaults */
|
|
76
|
+
}
|
|
72
77
|
|
|
73
78
|
const defaults = [
|
|
74
79
|
join(homedir(), '.local', 'bin', 'claude'),
|
|
@@ -90,21 +95,6 @@ function normalizeModelId(modelId: string): string {
|
|
|
90
95
|
return modelId
|
|
91
96
|
}
|
|
92
97
|
|
|
93
|
-
/** loadAgentDefinition loads an OpenCode agent definition file and extracts the
|
|
94
|
-
* markdown body (stripping the YAML frontmatter) for use as a system prompt. */
|
|
95
|
-
function loadAgentDefinition(agent: string, workdir?: string): string | undefined {
|
|
96
|
-
if (!agent || !workdir) return undefined
|
|
97
|
-
|
|
98
|
-
const agentPath = join(workdir, '.opencode', 'agents', `${agent}.md`)
|
|
99
|
-
if (!existsSync(agentPath)) return undefined
|
|
100
|
-
|
|
101
|
-
const content = readFileSync(agentPath, 'utf8')
|
|
102
|
-
|
|
103
|
-
// Strip YAML frontmatter (--- delimited block at the start).
|
|
104
|
-
const stripped = content.replace(/^---\n[\s\S]*?\n---\n*/, '')
|
|
105
|
-
return stripped.trim() || undefined
|
|
106
|
-
}
|
|
107
|
-
|
|
108
98
|
/** Extract text from assistant messages. */
|
|
109
99
|
function getAssistantText(msg: SDKMessage): string | null {
|
|
110
100
|
if (msg.type !== 'assistant') return null
|
|
@@ -147,7 +137,6 @@ export async function runClaudeCodeAgent(
|
|
|
147
137
|
): Promise<RunAgentResult> {
|
|
148
138
|
const {
|
|
149
139
|
prompt,
|
|
150
|
-
agent,
|
|
151
140
|
model,
|
|
152
141
|
sessionId,
|
|
153
142
|
timeoutSec = 3600,
|
|
@@ -169,8 +158,8 @@ export async function runClaudeCodeAgent(
|
|
|
169
158
|
// Build query options with configurable permission mode (default: acceptEdits)
|
|
170
159
|
const permissionMode = claudeCode?.permissionMode ?? 'acceptEdits'
|
|
171
160
|
|
|
172
|
-
// Resolve system prompt
|
|
173
|
-
const systemPrompt = claudeCode?.systemPrompt
|
|
161
|
+
// Resolve system prompt from explicit configuration only.
|
|
162
|
+
const systemPrompt = claudeCode?.systemPrompt
|
|
174
163
|
|
|
175
164
|
// Bridge external abort signal to an AbortController for the SDK
|
|
176
165
|
const abortController = new AbortController()
|
|
@@ -217,16 +206,22 @@ export async function runClaudeCodeAgent(
|
|
|
217
206
|
}),
|
|
218
207
|
// Resolve executable path: explicit option > PATH lookup > default locations
|
|
219
208
|
...(() => {
|
|
220
|
-
const resolved = resolveClaudeCodePath(
|
|
209
|
+
const resolved = resolveClaudeCodePath(
|
|
210
|
+
claudeCode?.pathToClaudeCodeExecutable,
|
|
211
|
+
)
|
|
221
212
|
return resolved ? { pathToClaudeCodeExecutable: resolved } : {}
|
|
222
213
|
})(),
|
|
223
214
|
}
|
|
224
215
|
|
|
225
216
|
if (claudeCode?.agents) {
|
|
226
|
-
console.error(
|
|
217
|
+
console.error(
|
|
218
|
+
`[ocpipe] Subagents defined: ${Object.keys(claudeCode.agents).join(', ')}`,
|
|
219
|
+
)
|
|
227
220
|
}
|
|
228
221
|
if (claudeCode?.allowedTools) {
|
|
229
|
-
console.error(
|
|
222
|
+
console.error(
|
|
223
|
+
`[ocpipe] Allowed tools: ${claudeCode.allowedTools.join(', ')}`,
|
|
224
|
+
)
|
|
230
225
|
}
|
|
231
226
|
console.error(
|
|
232
227
|
`\n>>> Claude Code [${modelStr}] [${permissionMode}] ${sessionInfo}: ${promptPreview}...`,
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ocpipe: LLM pipelines with
|
|
2
|
+
* ocpipe: LLM pipelines with coding-agent backends and Zod.
|
|
3
3
|
*
|
|
4
4
|
* Inspired by DSPy.
|
|
5
5
|
*
|
|
@@ -22,8 +22,7 @@
|
|
|
22
22
|
* // Run in a pipeline
|
|
23
23
|
* const pipeline = new Pipeline({
|
|
24
24
|
* name: 'my-workflow',
|
|
25
|
-
* defaultModel: {
|
|
26
|
-
* defaultAgent: 'general',
|
|
25
|
+
* defaultModel: { backend: 'omp', modelID: 'gpt-5.5' },
|
|
27
26
|
* checkpointDir: './ckpt',
|
|
28
27
|
* logDir: './logs',
|
|
29
28
|
* }, createBaseState)
|
package/src/pipeline.ts
CHANGED
|
@@ -30,9 +30,9 @@ export class Pipeline<S extends BaseState> {
|
|
|
30
30
|
this.ctx = {
|
|
31
31
|
sessionId: undefined,
|
|
32
32
|
defaultModel: config.defaultModel,
|
|
33
|
-
defaultAgent: config.defaultAgent,
|
|
34
33
|
timeoutSec: config.timeoutSec ?? 3600,
|
|
35
34
|
workdir: config.workdir,
|
|
35
|
+
opencode: config.opencode,
|
|
36
36
|
claudeCode: config.claudeCode,
|
|
37
37
|
codex: config.codex,
|
|
38
38
|
pi: config.pi,
|
|
@@ -89,8 +89,8 @@ export class Pipeline<S extends BaseState> {
|
|
|
89
89
|
result: result as StepResult<unknown>,
|
|
90
90
|
})
|
|
91
91
|
|
|
92
|
-
// Update
|
|
93
|
-
this.state.
|
|
92
|
+
// Update backend session in state.
|
|
93
|
+
this.state.agentSessionId = this.ctx.sessionId
|
|
94
94
|
|
|
95
95
|
await this.saveCheckpoint()
|
|
96
96
|
return result
|
|
@@ -159,7 +159,7 @@ export class Pipeline<S extends BaseState> {
|
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
/** getSessionId returns the current
|
|
162
|
+
/** getSessionId returns the current backend session ID. */
|
|
163
163
|
getSessionId(): string | undefined {
|
|
164
164
|
return this.ctx.sessionId
|
|
165
165
|
}
|
|
@@ -189,9 +189,9 @@ export class Pipeline<S extends BaseState> {
|
|
|
189
189
|
const pipeline = new Pipeline<S>(config, () => state)
|
|
190
190
|
pipeline.state = state
|
|
191
191
|
|
|
192
|
-
// Restore context from state
|
|
193
|
-
if (state.
|
|
194
|
-
pipeline.ctx.sessionId = state.
|
|
192
|
+
// Restore context from state.
|
|
193
|
+
if (state.agentSessionId) {
|
|
194
|
+
pipeline.ctx.sessionId = state.agentSessionId
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
// Restore step number
|
package/src/predict.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ocpipe Predict class.
|
|
3
3
|
*
|
|
4
|
-
* Executes a signature by generating a prompt, calling
|
|
4
|
+
* Executes a signature by generating a prompt, calling a backend, and parsing
|
|
5
|
+
* the response.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import { z } from 'zod/v4'
|
|
@@ -37,8 +38,6 @@ import {
|
|
|
37
38
|
|
|
38
39
|
/** Configuration for a Predict instance. */
|
|
39
40
|
export interface PredictConfig {
|
|
40
|
-
/** Override the pipeline's default agent. */
|
|
41
|
-
agent?: string
|
|
42
41
|
/** Override the pipeline's default model. */
|
|
43
42
|
model?: ModelConfig
|
|
44
43
|
/** Start a fresh session (default: false, reuses context). */
|
|
@@ -71,12 +70,12 @@ export class Predict<S extends AnySignature> {
|
|
|
71
70
|
|
|
72
71
|
const agentResult = await runAgent({
|
|
73
72
|
prompt,
|
|
74
|
-
agent: this.config.agent ?? ctx.defaultAgent,
|
|
75
73
|
model: this.config.model ?? ctx.defaultModel,
|
|
76
74
|
sessionId: this.config.newSession ? undefined : ctx.sessionId,
|
|
77
75
|
timeoutSec: ctx.timeoutSec,
|
|
78
76
|
workdir: ctx.workdir,
|
|
79
77
|
claudeCode: ctx.claudeCode,
|
|
78
|
+
opencode: ctx.opencode,
|
|
80
79
|
codex: ctx.codex,
|
|
81
80
|
pi: ctx.pi,
|
|
82
81
|
omp: ctx.omp,
|
|
@@ -206,10 +205,10 @@ export class Predict<S extends AnySignature> {
|
|
|
206
205
|
prompt: repairPrompt,
|
|
207
206
|
model: correctionModel ?? ctx.defaultModel,
|
|
208
207
|
sessionId: correctionModel ? undefined : sessionId,
|
|
209
|
-
agent: ctx.defaultAgent,
|
|
210
208
|
timeoutSec: ctx.timeoutSec,
|
|
211
209
|
workdir: ctx.workdir,
|
|
212
210
|
claudeCode: ctx.claudeCode,
|
|
211
|
+
opencode: ctx.opencode,
|
|
213
212
|
codex: ctx.codex,
|
|
214
213
|
pi: ctx.pi,
|
|
215
214
|
omp: ctx.omp,
|
|
@@ -286,10 +285,10 @@ export class Predict<S extends AnySignature> {
|
|
|
286
285
|
prompt: patchPrompt,
|
|
287
286
|
model: correctionModel ?? ctx.defaultModel,
|
|
288
287
|
sessionId: correctionModel ? undefined : sessionId,
|
|
289
|
-
agent: ctx.defaultAgent,
|
|
290
288
|
timeoutSec: ctx.timeoutSec,
|
|
291
289
|
workdir: ctx.workdir,
|
|
292
290
|
claudeCode: ctx.claudeCode,
|
|
291
|
+
opencode: ctx.opencode,
|
|
293
292
|
codex: ctx.codex,
|
|
294
293
|
pi: ctx.pi,
|
|
295
294
|
omp: ctx.omp,
|
|
@@ -399,8 +398,7 @@ export class Predict<S extends AnySignature> {
|
|
|
399
398
|
// Add field descriptions from our config (toJSONSchema uses .describe() metadata)
|
|
400
399
|
// Since our FieldConfig has a separate desc field, merge it in
|
|
401
400
|
const props = jsonSchema.properties as
|
|
402
|
-
|
|
403
|
-
| undefined
|
|
401
|
+
Record<string, Record<string, unknown>> | undefined
|
|
404
402
|
if (props) {
|
|
405
403
|
for (const [name, config] of Object.entries(this.sig.outputs) as [
|
|
406
404
|
string,
|
package/src/testing.ts
CHANGED
|
@@ -134,18 +134,20 @@ export class MockAgentBackend {
|
|
|
134
134
|
export function createMockContext(
|
|
135
135
|
overrides?: Partial<{
|
|
136
136
|
sessionId: string
|
|
137
|
-
defaultModel: {
|
|
138
|
-
|
|
137
|
+
defaultModel: {
|
|
138
|
+
backend?: 'claude-code' | 'codex' | 'pi' | 'omp'
|
|
139
|
+
providerID?: string
|
|
140
|
+
modelID: string
|
|
141
|
+
}
|
|
139
142
|
timeoutSec: number
|
|
140
143
|
}>,
|
|
141
144
|
) {
|
|
142
145
|
return {
|
|
143
146
|
sessionId: overrides?.sessionId,
|
|
144
147
|
defaultModel: overrides?.defaultModel ?? {
|
|
145
|
-
|
|
146
|
-
modelID: '
|
|
148
|
+
backend: 'omp',
|
|
149
|
+
modelID: 'gpt-5.5',
|
|
147
150
|
},
|
|
148
|
-
defaultAgent: overrides?.defaultAgent ?? 'general',
|
|
149
151
|
timeoutSec: overrides?.timeoutSec ?? 60,
|
|
150
152
|
}
|
|
151
153
|
}
|
package/src/types.ts
CHANGED
|
@@ -8,29 +8,20 @@ import type { z } from 'zod/v4'
|
|
|
8
8
|
// Model Configuration
|
|
9
9
|
// ============================================================================
|
|
10
10
|
|
|
11
|
-
/** Backend type for running
|
|
11
|
+
/** Backend type for running prompts. */
|
|
12
12
|
export type BackendType = 'opencode' | 'claude-code' | 'codex' | 'pi' | 'omp'
|
|
13
13
|
|
|
14
14
|
/** Reasoning effort for Codex SDK threads. */
|
|
15
15
|
export type CodexReasoningEffort =
|
|
16
|
-
| '
|
|
17
|
-
| 'low'
|
|
18
|
-
| 'medium'
|
|
19
|
-
| 'high'
|
|
20
|
-
| 'xhigh'
|
|
16
|
+
'minimal' | 'low' | 'medium' | 'high' | 'xhigh'
|
|
21
17
|
|
|
22
18
|
/** Codex sandbox mode. */
|
|
23
19
|
export type CodexSandboxMode =
|
|
24
|
-
|
|
25
|
-
| 'workspace-write'
|
|
26
|
-
| 'danger-full-access'
|
|
20
|
+
'read-only' | 'workspace-write' | 'danger-full-access'
|
|
27
21
|
|
|
28
22
|
/** Codex approval policy. */
|
|
29
23
|
export type CodexApprovalPolicy =
|
|
30
|
-
| '
|
|
31
|
-
| 'on-request'
|
|
32
|
-
| 'on-failure'
|
|
33
|
-
| 'untrusted'
|
|
24
|
+
'never' | 'on-request' | 'on-failure' | 'untrusted'
|
|
34
25
|
|
|
35
26
|
/** Codex web search mode. */
|
|
36
27
|
export type CodexWebSearchMode = 'disabled' | 'cached' | 'live'
|
|
@@ -45,10 +36,7 @@ export type CodexConfigValue =
|
|
|
45
36
|
|
|
46
37
|
/** Permission mode for Claude Code sessions. */
|
|
47
38
|
export type PermissionMode =
|
|
48
|
-
| '
|
|
49
|
-
| 'acceptEdits'
|
|
50
|
-
| 'bypassPermissions'
|
|
51
|
-
| 'plan'
|
|
39
|
+
'default' | 'acceptEdits' | 'bypassPermissions' | 'plan'
|
|
52
40
|
|
|
53
41
|
/** Subagent definition for Claude Code's Task tool dispatch. */
|
|
54
42
|
export interface AgentDefinition {
|
|
@@ -75,8 +63,7 @@ export interface ClaudeCodeOptions {
|
|
|
75
63
|
* Can be a full string or use the preset format with append.
|
|
76
64
|
*/
|
|
77
65
|
systemPrompt?:
|
|
78
|
-
| string
|
|
79
|
-
| { type: 'preset'; preset: 'claude_code'; append: string }
|
|
66
|
+
string | { type: 'preset'; preset: 'claude_code'; append: string }
|
|
80
67
|
/**
|
|
81
68
|
* Subagent definitions for parallel task dispatch via the Task tool.
|
|
82
69
|
* Keys are agent names, values are agent definitions.
|
|
@@ -133,6 +120,16 @@ export interface PiOptions {
|
|
|
133
120
|
env?: Record<string, string>
|
|
134
121
|
}
|
|
135
122
|
|
|
123
|
+
/** OpenCode CLI specific session options. */
|
|
124
|
+
export interface OpenCodeOptions {
|
|
125
|
+
/** Path to the OpenCode executable (default: `opencode` from PATH). */
|
|
126
|
+
command?: string
|
|
127
|
+
/** Prompt-file directory; defaults to ocpipe's temporary directory, not `.opencode`. */
|
|
128
|
+
promptDir?: string
|
|
129
|
+
/** Extra environment variables passed to the OpenCode subprocess. */
|
|
130
|
+
env?: Record<string, string>
|
|
131
|
+
}
|
|
132
|
+
|
|
136
133
|
/** Oh My Pi CLI specific session options. */
|
|
137
134
|
export interface OmpOptions {
|
|
138
135
|
/** Path to the Oh My Pi executable (default: `omp` from PATH). */
|
|
@@ -167,9 +164,9 @@ export interface OmpOptions {
|
|
|
167
164
|
|
|
168
165
|
/** Model configuration for LLM backends. */
|
|
169
166
|
export interface ModelConfig {
|
|
170
|
-
/** Backend to use (default: '
|
|
167
|
+
/** Backend to use (default: 'omp'). */
|
|
171
168
|
backend?: BackendType
|
|
172
|
-
/** Provider ID
|
|
169
|
+
/** Provider ID for provider-qualified backends such as OpenCode. */
|
|
173
170
|
providerID?: string
|
|
174
171
|
modelID: string
|
|
175
172
|
/** Model variant for reasoning effort (e.g. 'low', 'medium', 'high', 'max'). */
|
|
@@ -184,16 +181,16 @@ export interface ModelConfig {
|
|
|
184
181
|
|
|
185
182
|
/** Execution context passed through pipeline execution. */
|
|
186
183
|
export interface ExecutionContext {
|
|
187
|
-
/** Current
|
|
184
|
+
/** Current backend session ID for continuity. */
|
|
188
185
|
sessionId?: string
|
|
189
186
|
/** Default model for predictions. */
|
|
190
187
|
defaultModel: ModelConfig
|
|
191
|
-
/** Default agent for predictions. */
|
|
192
|
-
defaultAgent: string
|
|
193
188
|
/** Timeout in seconds for agent calls. */
|
|
194
189
|
timeoutSec: number
|
|
195
|
-
/**
|
|
190
|
+
/** Target working directory for backend tools. */
|
|
196
191
|
workdir?: string
|
|
192
|
+
/** OpenCode CLI specific options. */
|
|
193
|
+
opencode?: OpenCodeOptions
|
|
197
194
|
/** Claude Code specific options. */
|
|
198
195
|
claudeCode?: ClaudeCodeOptions
|
|
199
196
|
/** Codex SDK specific options. */
|
|
@@ -218,7 +215,7 @@ export interface StepResult<T> {
|
|
|
218
215
|
stepName: string
|
|
219
216
|
/** Execution duration in milliseconds. */
|
|
220
217
|
duration: number
|
|
221
|
-
/**
|
|
218
|
+
/** Backend session ID used. */
|
|
222
219
|
sessionId: string
|
|
223
220
|
/** Model used for this step. */
|
|
224
221
|
model: ModelConfig
|
|
@@ -251,8 +248,8 @@ export interface BaseState {
|
|
|
251
248
|
sessionId: string
|
|
252
249
|
/** ISO timestamp when pipeline started. */
|
|
253
250
|
startedAt: string
|
|
254
|
-
/** Current
|
|
255
|
-
|
|
251
|
+
/** Current backend session ID for continuity. */
|
|
252
|
+
agentSessionId?: string
|
|
256
253
|
/** Current phase name (for resume). */
|
|
257
254
|
phase: string
|
|
258
255
|
/** All completed steps. */
|
|
@@ -271,7 +268,7 @@ export interface PredictResult<T> {
|
|
|
271
268
|
data: T
|
|
272
269
|
/** Raw response text from the LLM. */
|
|
273
270
|
raw: string
|
|
274
|
-
/**
|
|
271
|
+
/** Backend session ID. */
|
|
275
272
|
sessionId: string
|
|
276
273
|
/** Execution duration in milliseconds. */
|
|
277
274
|
duration: number
|
|
@@ -364,9 +361,7 @@ export interface CorrectionConfig {
|
|
|
364
361
|
|
|
365
362
|
/** Error codes for field errors, enabling robust error type detection. */
|
|
366
363
|
export type FieldErrorCode =
|
|
367
|
-
| '
|
|
368
|
-
| 'no_json_found'
|
|
369
|
-
| 'schema_validation_failed'
|
|
364
|
+
'json_parse_failed' | 'no_json_found' | 'schema_validation_failed'
|
|
370
365
|
|
|
371
366
|
/** A field-level error from schema validation. */
|
|
372
367
|
export interface FieldError {
|
|
@@ -406,8 +401,6 @@ export interface PipelineConfig {
|
|
|
406
401
|
name: string
|
|
407
402
|
/** Default model for predictions. */
|
|
408
403
|
defaultModel: ModelConfig
|
|
409
|
-
/** Default agent for predictions. */
|
|
410
|
-
defaultAgent: string
|
|
411
404
|
/** Directory for checkpoint files. */
|
|
412
405
|
checkpointDir: string
|
|
413
406
|
/** Directory for log files. */
|
|
@@ -416,8 +409,10 @@ export interface PipelineConfig {
|
|
|
416
409
|
retry?: RetryConfig
|
|
417
410
|
/** Default timeout in seconds. */
|
|
418
411
|
timeoutSec?: number
|
|
419
|
-
/**
|
|
412
|
+
/** Target working directory for backend tools. */
|
|
420
413
|
workdir?: string
|
|
414
|
+
/** OpenCode CLI specific options. */
|
|
415
|
+
opencode?: OpenCodeOptions
|
|
421
416
|
/** Claude Code specific options. */
|
|
422
417
|
claudeCode?: ClaudeCodeOptions
|
|
423
418
|
/** Codex SDK specific options. */
|
|
@@ -444,20 +439,20 @@ export interface RunOptions {
|
|
|
444
439
|
// Agent Types
|
|
445
440
|
// ============================================================================
|
|
446
441
|
|
|
447
|
-
/** Options for running
|
|
442
|
+
/** Options for running a prompt through a backend. */
|
|
448
443
|
export interface RunAgentOptions {
|
|
449
|
-
/** The prompt to send to the
|
|
444
|
+
/** The prompt to send to the backend. */
|
|
450
445
|
prompt: string
|
|
451
|
-
/** Agent type (e.g., "journey-creator", "explore", "general"). */
|
|
452
|
-
agent: string
|
|
453
446
|
/** Model to use. */
|
|
454
447
|
model: ModelConfig
|
|
455
448
|
/** Existing session ID to continue. */
|
|
456
449
|
sessionId?: string
|
|
457
450
|
/** Timeout in seconds. */
|
|
458
451
|
timeoutSec?: number
|
|
459
|
-
/**
|
|
452
|
+
/** Target working directory for backend tools. */
|
|
460
453
|
workdir?: string
|
|
454
|
+
/** OpenCode CLI specific options. */
|
|
455
|
+
opencode?: OpenCodeOptions
|
|
461
456
|
/** Claude Code specific options. */
|
|
462
457
|
claudeCode?: ClaudeCodeOptions
|
|
463
458
|
/** Codex SDK specific options. */
|
|
@@ -470,7 +465,7 @@ export interface RunAgentOptions {
|
|
|
470
465
|
signal?: AbortSignal
|
|
471
466
|
}
|
|
472
467
|
|
|
473
|
-
/** Result from running
|
|
468
|
+
/** Result from running a prompt through a backend. */
|
|
474
469
|
export interface RunAgentResult {
|
|
475
470
|
/** The text response from the agent. */
|
|
476
471
|
text: string
|