ocpipe 0.6.3 → 0.6.5
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 +14 -1
- package/package.json +16 -11
- package/src/agent.ts +7 -1
- package/src/codex.ts +219 -0
- package/src/index.ts +1 -0
- package/src/pipeline.ts +1 -0
- package/src/types.ts +27 -1
package/README.md
CHANGED
|
@@ -49,7 +49,7 @@ type GreetOut = InferOutputs<typeof Greet> // { greeting: string }
|
|
|
49
49
|
|
|
50
50
|
### Backends
|
|
51
51
|
|
|
52
|
-
ocpipe supports
|
|
52
|
+
ocpipe supports three backends for running LLM agents:
|
|
53
53
|
|
|
54
54
|
**OpenCode** (default) - Requires `opencode` CLI in your PATH. Supports 75+ providers.
|
|
55
55
|
|
|
@@ -76,6 +76,13 @@ defaultModel: { backend: 'claude-code', modelID: 'sonnet' },
|
|
|
76
76
|
claudeCode: { permissionMode: 'acceptEdits' },
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
+
**Codex** - Uses `@openai/codex`. Install as a peer dependency.
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
defaultModel: { backend: 'codex', modelID: 'gpt-5.4' },
|
|
83
|
+
codex: { sandbox: 'read-only', ephemeral: true },
|
|
84
|
+
```
|
|
85
|
+
|
|
79
86
|
### Requirements
|
|
80
87
|
|
|
81
88
|
**For OpenCode backend:** Currently requires [this OpenCode fork](https://github.com/paralin/opencode). Once the following PRs are merged, the official release will work:
|
|
@@ -89,6 +96,12 @@ claudeCode: { permissionMode: 'acceptEdits' },
|
|
|
89
96
|
bun add @anthropic-ai/claude-agent-sdk
|
|
90
97
|
```
|
|
91
98
|
|
|
99
|
+
**For Codex backend:** Install the Codex CLI package as a peer dependency:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
bun add @openai/codex
|
|
103
|
+
```
|
|
104
|
+
|
|
92
105
|
### Documentation
|
|
93
106
|
|
|
94
107
|
- [Getting Started](./GETTING_STARTED.md) - Tutorial with examples
|
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 and Zod",
|
|
3
|
+
"version": "0.6.5",
|
|
4
|
+
"description": "SDK for LLM pipelines with OpenCode, Codex, and Zod",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"types": "src/index.ts",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"dspy",
|
|
20
20
|
"llm",
|
|
21
21
|
"opencode",
|
|
22
|
+
"codex",
|
|
22
23
|
"typescript",
|
|
23
24
|
"ai",
|
|
24
25
|
"workflow",
|
|
@@ -28,27 +29,31 @@
|
|
|
28
29
|
"engines": {
|
|
29
30
|
"bun": ">=1.0.0"
|
|
30
31
|
},
|
|
31
|
-
"dependencies": {},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"zod": "4.4.
|
|
34
|
-
"@anthropic-ai/claude-agent-sdk": "0.2.126"
|
|
33
|
+
"zod": "4.4.3",
|
|
34
|
+
"@anthropic-ai/claude-agent-sdk": "0.2.126",
|
|
35
|
+
"@openai/codex": "0.128.0"
|
|
35
36
|
},
|
|
36
37
|
"peerDependenciesMeta": {
|
|
37
38
|
"@anthropic-ai/claude-agent-sdk": {
|
|
38
39
|
"optional": true
|
|
40
|
+
},
|
|
41
|
+
"@openai/codex": {
|
|
42
|
+
"optional": true
|
|
39
43
|
}
|
|
40
44
|
},
|
|
41
45
|
"devDependencies": {
|
|
42
|
-
"@anthropic-ai/claude-agent-sdk": "^0.2.
|
|
46
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.132",
|
|
47
|
+
"@openai/codex": "^0.128.0",
|
|
43
48
|
"@eslint/js": "^10.0.1",
|
|
44
49
|
"bun-types": "^1.3.13",
|
|
45
|
-
"eslint": "^10.
|
|
46
|
-
"globals": "^17.
|
|
47
|
-
"jiti": "^2.
|
|
50
|
+
"eslint": "^10.3.0",
|
|
51
|
+
"globals": "^17.6.0",
|
|
52
|
+
"jiti": "^2.7.0",
|
|
48
53
|
"prettier": "^3.8.3",
|
|
49
54
|
"typescript": "^6.0.3",
|
|
50
|
-
"typescript-eslint": "^8.59.
|
|
51
|
-
"@typescript/native-preview": "^7.0.0-dev.
|
|
55
|
+
"typescript-eslint": "^8.59.2",
|
|
56
|
+
"@typescript/native-preview": "^7.0.0-dev.20260506.1",
|
|
52
57
|
"vitest": "^4.1.5"
|
|
53
58
|
},
|
|
54
59
|
"scripts": {
|
package/src/agent.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ocpipe agent integration.
|
|
3
3
|
*
|
|
4
|
-
* Dispatches to OpenCode CLI
|
|
4
|
+
* Dispatches to OpenCode CLI, Claude Code SDK, or Codex CLI based on backend
|
|
5
|
+
* configuration.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import { spawn } from 'child_process'
|
|
@@ -27,6 +28,11 @@ export async function runAgent(
|
|
|
27
28
|
return runClaudeCodeAgent(options)
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
if (backend === 'codex') {
|
|
32
|
+
const { runCodexAgent } = await import('./codex.js')
|
|
33
|
+
return runCodexAgent(options)
|
|
34
|
+
}
|
|
35
|
+
|
|
30
36
|
return runOpencodeAgent(options)
|
|
31
37
|
}
|
|
32
38
|
|
package/src/codex.ts
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ocpipe Codex CLI integration.
|
|
3
|
+
*
|
|
4
|
+
* Runs Codex non-interactively through `codex exec`.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { spawn } from 'child_process'
|
|
8
|
+
import { mkdir, readFile, unlink } from 'fs/promises'
|
|
9
|
+
import { join } from 'path'
|
|
10
|
+
import { PROJECT_ROOT, TMP_DIR } from './paths.js'
|
|
11
|
+
import type { RunAgentOptions, RunAgentResult } from './types.js'
|
|
12
|
+
|
|
13
|
+
class CodexLogFilter {
|
|
14
|
+
private buf = ''
|
|
15
|
+
|
|
16
|
+
write(text: string): string {
|
|
17
|
+
this.buf += text
|
|
18
|
+
let out = ''
|
|
19
|
+
for (;;) {
|
|
20
|
+
const idx = this.buf.indexOf('\n')
|
|
21
|
+
if (idx < 0) {
|
|
22
|
+
return out
|
|
23
|
+
}
|
|
24
|
+
const line = this.buf.slice(0, idx + 1)
|
|
25
|
+
this.buf = this.buf.slice(idx + 1)
|
|
26
|
+
if (suppressCodexLogLine(line)) {
|
|
27
|
+
continue
|
|
28
|
+
}
|
|
29
|
+
out += line
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
flush(): string {
|
|
34
|
+
const line = this.buf
|
|
35
|
+
this.buf = ''
|
|
36
|
+
if (suppressCodexLogLine(line)) {
|
|
37
|
+
return ''
|
|
38
|
+
}
|
|
39
|
+
return line
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function filterCodexLogText(text: string): string {
|
|
44
|
+
const filter = new CodexLogFilter()
|
|
45
|
+
return filter.write(text) + filter.flush()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function suppressCodexLogLine(line: string): boolean {
|
|
49
|
+
return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z\s+WARN\s+codex_/.test(line)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** runCodexAgent executes a Codex agent with a prompt. */
|
|
53
|
+
export async function runCodexAgent(
|
|
54
|
+
options: RunAgentOptions,
|
|
55
|
+
): Promise<RunAgentResult> {
|
|
56
|
+
const {
|
|
57
|
+
prompt,
|
|
58
|
+
model,
|
|
59
|
+
sessionId,
|
|
60
|
+
timeoutSec = 3600,
|
|
61
|
+
workdir,
|
|
62
|
+
codex,
|
|
63
|
+
signal,
|
|
64
|
+
} = options
|
|
65
|
+
|
|
66
|
+
if (sessionId) {
|
|
67
|
+
throw new Error('Codex backend does not support session resume yet')
|
|
68
|
+
}
|
|
69
|
+
if (signal?.aborted) {
|
|
70
|
+
throw new Error('Request aborted')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const cwd = workdir ?? PROJECT_ROOT
|
|
74
|
+
await mkdir(TMP_DIR, { recursive: true })
|
|
75
|
+
const stamp = `${Date.now()}_${Math.random().toString(36).slice(2)}`
|
|
76
|
+
const outputFile = join(TMP_DIR, `codex_output_${stamp}.txt`)
|
|
77
|
+
|
|
78
|
+
const cmd = codex?.pathToCodexExecutable ?? 'codex'
|
|
79
|
+
const args = [
|
|
80
|
+
'exec',
|
|
81
|
+
'--color',
|
|
82
|
+
'never',
|
|
83
|
+
'--model',
|
|
84
|
+
model.modelID,
|
|
85
|
+
'--sandbox',
|
|
86
|
+
codex?.sandbox ?? 'read-only',
|
|
87
|
+
'--cd',
|
|
88
|
+
cwd,
|
|
89
|
+
'--output-last-message',
|
|
90
|
+
outputFile,
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
if (codex?.ephemeral ?? true) {
|
|
94
|
+
args.push('--ephemeral')
|
|
95
|
+
}
|
|
96
|
+
if (codex?.ignoreUserConfig) {
|
|
97
|
+
args.push('--ignore-user-config')
|
|
98
|
+
}
|
|
99
|
+
if (codex?.ignoreRules) {
|
|
100
|
+
args.push('--ignore-rules')
|
|
101
|
+
}
|
|
102
|
+
if (codex?.reasoningEffort) {
|
|
103
|
+
args.push('-c', `model_reasoning_effort="${codex.reasoningEffort}"`)
|
|
104
|
+
}
|
|
105
|
+
for (const dir of codex?.addDirs ?? []) {
|
|
106
|
+
args.push('--add-dir', dir)
|
|
107
|
+
}
|
|
108
|
+
for (const [key, value] of Object.entries(codex?.config ?? {})) {
|
|
109
|
+
args.push('-c', `${key}=${value}`)
|
|
110
|
+
}
|
|
111
|
+
args.push('-')
|
|
112
|
+
|
|
113
|
+
const promptPreview = prompt.slice(0, 50).replace(/\n/g, ' ')
|
|
114
|
+
console.error(
|
|
115
|
+
`\n>>> Codex [${model.modelID}] [new session]: ${promptPreview}...`,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
return new Promise((resolve, reject) => {
|
|
119
|
+
const proc = spawn(cmd, args, {
|
|
120
|
+
cwd,
|
|
121
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
const stderrChunks: string[] = []
|
|
125
|
+
const stdoutFilter = new CodexLogFilter()
|
|
126
|
+
const stderrFilter = new CodexLogFilter()
|
|
127
|
+
let aborted = false
|
|
128
|
+
|
|
129
|
+
const cleanup = async () => {
|
|
130
|
+
await unlink(outputFile).catch(() => {})
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const abortHandler = () => {
|
|
134
|
+
if (aborted) return
|
|
135
|
+
aborted = true
|
|
136
|
+
console.error('\n[abort] Killing Codex subprocess...')
|
|
137
|
+
proc.kill('SIGTERM')
|
|
138
|
+
setTimeout(() => {
|
|
139
|
+
if (!proc.killed) proc.kill('SIGKILL')
|
|
140
|
+
}, 1000)
|
|
141
|
+
void cleanup()
|
|
142
|
+
reject(new Error('Request aborted'))
|
|
143
|
+
}
|
|
144
|
+
signal?.addEventListener('abort', abortHandler, { once: true })
|
|
145
|
+
|
|
146
|
+
proc.stdout.on('data', (data: Buffer) => {
|
|
147
|
+
const text = stdoutFilter.write(data.toString())
|
|
148
|
+
if (text) {
|
|
149
|
+
process.stderr.write(text)
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
proc.stderr.on('data', (data: Buffer) => {
|
|
153
|
+
const text = stderrFilter.write(data.toString())
|
|
154
|
+
stderrChunks.push(text)
|
|
155
|
+
if (text) {
|
|
156
|
+
process.stderr.write(text)
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
const timeout =
|
|
161
|
+
timeoutSec > 0 ?
|
|
162
|
+
setTimeout(() => {
|
|
163
|
+
proc.kill()
|
|
164
|
+
void cleanup()
|
|
165
|
+
reject(new Error(`Timeout after ${timeoutSec}s`))
|
|
166
|
+
}, timeoutSec * 1000)
|
|
167
|
+
: null
|
|
168
|
+
|
|
169
|
+
proc.stdin.end(prompt)
|
|
170
|
+
|
|
171
|
+
proc.on('close', async (code) => {
|
|
172
|
+
if (timeout) clearTimeout(timeout)
|
|
173
|
+
signal?.removeEventListener('abort', abortHandler)
|
|
174
|
+
if (aborted) return
|
|
175
|
+
|
|
176
|
+
const stdoutTail = stdoutFilter.flush()
|
|
177
|
+
if (stdoutTail) {
|
|
178
|
+
process.stderr.write(stdoutTail)
|
|
179
|
+
}
|
|
180
|
+
const stderrTail = stderrFilter.flush()
|
|
181
|
+
if (stderrTail) {
|
|
182
|
+
stderrChunks.push(stderrTail)
|
|
183
|
+
process.stderr.write(stderrTail)
|
|
184
|
+
}
|
|
185
|
+
const stderr = stderrChunks.join('').trim()
|
|
186
|
+
if (code !== 0) {
|
|
187
|
+
await cleanup()
|
|
188
|
+
const detail =
|
|
189
|
+
stderr ? `\n${stderr.split('\n').slice(-10).join('\n')}` : ''
|
|
190
|
+
reject(new Error(`Codex exited with code ${code}${detail}`))
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const response = (await readFile(outputFile, 'utf8')).trim()
|
|
196
|
+
await cleanup()
|
|
197
|
+
if (!response) {
|
|
198
|
+
reject(new Error('Codex returned an empty response'))
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
console.error(`<<< Codex done (${response.length} chars)`)
|
|
202
|
+
resolve({
|
|
203
|
+
text: response,
|
|
204
|
+
sessionId: '',
|
|
205
|
+
})
|
|
206
|
+
} catch (err) {
|
|
207
|
+
await cleanup()
|
|
208
|
+
reject(err)
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
proc.on('error', async (err) => {
|
|
213
|
+
if (timeout) clearTimeout(timeout)
|
|
214
|
+
signal?.removeEventListener('abort', abortHandler)
|
|
215
|
+
await cleanup()
|
|
216
|
+
reject(err)
|
|
217
|
+
})
|
|
218
|
+
})
|
|
219
|
+
}
|
package/src/index.ts
CHANGED
package/src/pipeline.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type { z } from 'zod/v4'
|
|
|
9
9
|
// ============================================================================
|
|
10
10
|
|
|
11
11
|
/** Backend type for running agents. */
|
|
12
|
-
export type BackendType = 'opencode' | 'claude-code'
|
|
12
|
+
export type BackendType = 'opencode' | 'claude-code' | 'codex'
|
|
13
13
|
|
|
14
14
|
/** Permission mode for Claude Code sessions. */
|
|
15
15
|
export type PermissionMode =
|
|
@@ -57,6 +57,26 @@ export interface ClaudeCodeOptions {
|
|
|
57
57
|
allowedTools?: string[]
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
/** Codex CLI specific session options. */
|
|
61
|
+
export interface CodexOptions {
|
|
62
|
+
/** Path to Codex executable (default: `codex` from PATH). */
|
|
63
|
+
pathToCodexExecutable?: string
|
|
64
|
+
/** Sandbox mode for `codex exec` (default: `read-only`). */
|
|
65
|
+
sandbox?: 'read-only' | 'workspace-write' | 'danger-full-access'
|
|
66
|
+
/** Extra config overrides passed as repeated `-c key=value` flags. */
|
|
67
|
+
config?: Record<string, string>
|
|
68
|
+
/** Reasoning effort passed to Codex (for example: `low`, `medium`, `high`, `xhigh`). */
|
|
69
|
+
reasoningEffort?: string
|
|
70
|
+
/** Run without persisting Codex session files (default: true). */
|
|
71
|
+
ephemeral?: boolean
|
|
72
|
+
/** Ignore user config for deterministic automation (default: false). */
|
|
73
|
+
ignoreUserConfig?: boolean
|
|
74
|
+
/** Ignore user and project execpolicy rules (default: false). */
|
|
75
|
+
ignoreRules?: boolean
|
|
76
|
+
/** Additional directories Codex may access alongside the working directory. */
|
|
77
|
+
addDirs?: string[]
|
|
78
|
+
}
|
|
79
|
+
|
|
60
80
|
/** Model configuration for LLM backends. */
|
|
61
81
|
export interface ModelConfig {
|
|
62
82
|
/** Backend to use (default: 'opencode'). */
|
|
@@ -88,6 +108,8 @@ export interface ExecutionContext {
|
|
|
88
108
|
workdir?: string
|
|
89
109
|
/** Claude Code specific options. */
|
|
90
110
|
claudeCode?: ClaudeCodeOptions
|
|
111
|
+
/** Codex CLI specific options. */
|
|
112
|
+
codex?: CodexOptions
|
|
91
113
|
/** AbortSignal for cancelling requests. When aborted, kills subprocesses. */
|
|
92
114
|
signal?: AbortSignal
|
|
93
115
|
}
|
|
@@ -306,6 +328,8 @@ export interface PipelineConfig {
|
|
|
306
328
|
workdir?: string
|
|
307
329
|
/** Claude Code specific options. */
|
|
308
330
|
claudeCode?: ClaudeCodeOptions
|
|
331
|
+
/** Codex CLI specific options. */
|
|
332
|
+
codex?: CodexOptions
|
|
309
333
|
}
|
|
310
334
|
|
|
311
335
|
/** Options for running a pipeline step. */
|
|
@@ -340,6 +364,8 @@ export interface RunAgentOptions {
|
|
|
340
364
|
workdir?: string
|
|
341
365
|
/** Claude Code specific options. */
|
|
342
366
|
claudeCode?: ClaudeCodeOptions
|
|
367
|
+
/** Codex CLI specific options. */
|
|
368
|
+
codex?: CodexOptions
|
|
343
369
|
/** AbortSignal for cancelling the request. When aborted, kills the subprocess. */
|
|
344
370
|
signal?: AbortSignal
|
|
345
371
|
}
|