ocpipe 0.6.2 → 0.6.4
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 +20 -15
- package/src/agent.ts +7 -1
- package/src/codex.ts +160 -0
- package/src/index.ts +1 -0
- package/src/pipeline.ts +1 -0
- package/src/types.ts +25 -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.4",
|
|
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,28 +29,32 @@
|
|
|
28
29
|
"engines": {
|
|
29
30
|
"bun": ">=1.0.0"
|
|
30
31
|
},
|
|
31
|
-
"dependencies": {},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"zod": "4.3
|
|
34
|
-
"@anthropic-ai/claude-agent-sdk": "0.2.
|
|
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
|
-
"bun-types": "^1.3.
|
|
45
|
-
"eslint": "^10.
|
|
46
|
-
"globals": "^17.
|
|
47
|
-
"jiti": "^2.
|
|
48
|
-
"prettier": "^3.8.
|
|
49
|
-
"typescript": "^6.0.
|
|
50
|
-
"typescript-eslint": "^8.
|
|
51
|
-
"@typescript/native-preview": "^7.0.0-dev.
|
|
52
|
-
"vitest": "^4.1.
|
|
49
|
+
"bun-types": "^1.3.13",
|
|
50
|
+
"eslint": "^10.3.0",
|
|
51
|
+
"globals": "^17.6.0",
|
|
52
|
+
"jiti": "^2.7.0",
|
|
53
|
+
"prettier": "^3.8.3",
|
|
54
|
+
"typescript": "^6.0.3",
|
|
55
|
+
"typescript-eslint": "^8.59.2",
|
|
56
|
+
"@typescript/native-preview": "^7.0.0-dev.20260506.1",
|
|
57
|
+
"vitest": "^4.1.5"
|
|
53
58
|
},
|
|
54
59
|
"scripts": {
|
|
55
60
|
"lint": "eslint .",
|
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,160 @@
|
|
|
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
|
+
/** runCodexAgent executes a Codex agent with a prompt. */
|
|
14
|
+
export async function runCodexAgent(
|
|
15
|
+
options: RunAgentOptions,
|
|
16
|
+
): Promise<RunAgentResult> {
|
|
17
|
+
const {
|
|
18
|
+
prompt,
|
|
19
|
+
model,
|
|
20
|
+
sessionId,
|
|
21
|
+
timeoutSec = 3600,
|
|
22
|
+
workdir,
|
|
23
|
+
codex,
|
|
24
|
+
signal,
|
|
25
|
+
} = options
|
|
26
|
+
|
|
27
|
+
if (sessionId) {
|
|
28
|
+
throw new Error('Codex backend does not support session resume yet')
|
|
29
|
+
}
|
|
30
|
+
if (signal?.aborted) {
|
|
31
|
+
throw new Error('Request aborted')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const cwd = workdir ?? PROJECT_ROOT
|
|
35
|
+
await mkdir(TMP_DIR, { recursive: true })
|
|
36
|
+
const stamp = `${Date.now()}_${Math.random().toString(36).slice(2)}`
|
|
37
|
+
const outputFile = join(TMP_DIR, `codex_output_${stamp}.txt`)
|
|
38
|
+
|
|
39
|
+
const cmd = codex?.pathToCodexExecutable ?? 'codex'
|
|
40
|
+
const args = [
|
|
41
|
+
'exec',
|
|
42
|
+
'--color',
|
|
43
|
+
'never',
|
|
44
|
+
'--model',
|
|
45
|
+
model.modelID,
|
|
46
|
+
'--sandbox',
|
|
47
|
+
codex?.sandbox ?? 'read-only',
|
|
48
|
+
'--cd',
|
|
49
|
+
cwd,
|
|
50
|
+
'--output-last-message',
|
|
51
|
+
outputFile,
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
if (codex?.ephemeral ?? true) {
|
|
55
|
+
args.push('--ephemeral')
|
|
56
|
+
}
|
|
57
|
+
if (codex?.ignoreUserConfig) {
|
|
58
|
+
args.push('--ignore-user-config')
|
|
59
|
+
}
|
|
60
|
+
if (codex?.ignoreRules) {
|
|
61
|
+
args.push('--ignore-rules')
|
|
62
|
+
}
|
|
63
|
+
for (const dir of codex?.addDirs ?? []) {
|
|
64
|
+
args.push('--add-dir', dir)
|
|
65
|
+
}
|
|
66
|
+
for (const [key, value] of Object.entries(codex?.config ?? {})) {
|
|
67
|
+
args.push('-c', `${key}=${value}`)
|
|
68
|
+
}
|
|
69
|
+
args.push('-')
|
|
70
|
+
|
|
71
|
+
const promptPreview = prompt.slice(0, 50).replace(/\n/g, ' ')
|
|
72
|
+
console.error(
|
|
73
|
+
`\n>>> Codex [${model.modelID}] [new session]: ${promptPreview}...`,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
const proc = spawn(cmd, args, {
|
|
78
|
+
cwd,
|
|
79
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
const stderrChunks: string[] = []
|
|
83
|
+
let aborted = false
|
|
84
|
+
|
|
85
|
+
const cleanup = async () => {
|
|
86
|
+
await unlink(outputFile).catch(() => {})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const abortHandler = () => {
|
|
90
|
+
if (aborted) return
|
|
91
|
+
aborted = true
|
|
92
|
+
console.error('\n[abort] Killing Codex subprocess...')
|
|
93
|
+
proc.kill('SIGTERM')
|
|
94
|
+
setTimeout(() => {
|
|
95
|
+
if (!proc.killed) proc.kill('SIGKILL')
|
|
96
|
+
}, 1000)
|
|
97
|
+
void cleanup()
|
|
98
|
+
reject(new Error('Request aborted'))
|
|
99
|
+
}
|
|
100
|
+
signal?.addEventListener('abort', abortHandler, { once: true })
|
|
101
|
+
|
|
102
|
+
proc.stdout.on('data', (data: Buffer) => {
|
|
103
|
+
process.stderr.write(data.toString())
|
|
104
|
+
})
|
|
105
|
+
proc.stderr.on('data', (data: Buffer) => {
|
|
106
|
+
const text = data.toString()
|
|
107
|
+
stderrChunks.push(text)
|
|
108
|
+
process.stderr.write(text)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
const timeout =
|
|
112
|
+
timeoutSec > 0 ?
|
|
113
|
+
setTimeout(() => {
|
|
114
|
+
proc.kill()
|
|
115
|
+
void cleanup()
|
|
116
|
+
reject(new Error(`Timeout after ${timeoutSec}s`))
|
|
117
|
+
}, timeoutSec * 1000)
|
|
118
|
+
: null
|
|
119
|
+
|
|
120
|
+
proc.stdin.end(prompt)
|
|
121
|
+
|
|
122
|
+
proc.on('close', async (code) => {
|
|
123
|
+
if (timeout) clearTimeout(timeout)
|
|
124
|
+
signal?.removeEventListener('abort', abortHandler)
|
|
125
|
+
if (aborted) return
|
|
126
|
+
|
|
127
|
+
const stderr = stderrChunks.join('').trim()
|
|
128
|
+
if (code !== 0) {
|
|
129
|
+
await cleanup()
|
|
130
|
+
const detail = stderr ? `\n${stderr.split('\n').slice(-10).join('\n')}` : ''
|
|
131
|
+
reject(new Error(`Codex exited with code ${code}${detail}`))
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const response = (await readFile(outputFile, 'utf8')).trim()
|
|
137
|
+
await cleanup()
|
|
138
|
+
if (!response) {
|
|
139
|
+
reject(new Error('Codex returned an empty response'))
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
console.error(`<<< Codex done (${response.length} chars)`)
|
|
143
|
+
resolve({
|
|
144
|
+
text: response,
|
|
145
|
+
sessionId: '',
|
|
146
|
+
})
|
|
147
|
+
} catch (err) {
|
|
148
|
+
await cleanup()
|
|
149
|
+
reject(err)
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
proc.on('error', async (err) => {
|
|
154
|
+
if (timeout) clearTimeout(timeout)
|
|
155
|
+
signal?.removeEventListener('abort', abortHandler)
|
|
156
|
+
await cleanup()
|
|
157
|
+
reject(err)
|
|
158
|
+
})
|
|
159
|
+
})
|
|
160
|
+
}
|
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,24 @@ 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
|
+
/** Run without persisting Codex session files (default: true). */
|
|
69
|
+
ephemeral?: boolean
|
|
70
|
+
/** Ignore user config for deterministic automation (default: false). */
|
|
71
|
+
ignoreUserConfig?: boolean
|
|
72
|
+
/** Ignore user and project execpolicy rules (default: false). */
|
|
73
|
+
ignoreRules?: boolean
|
|
74
|
+
/** Additional directories Codex may access alongside the working directory. */
|
|
75
|
+
addDirs?: string[]
|
|
76
|
+
}
|
|
77
|
+
|
|
60
78
|
/** Model configuration for LLM backends. */
|
|
61
79
|
export interface ModelConfig {
|
|
62
80
|
/** Backend to use (default: 'opencode'). */
|
|
@@ -88,6 +106,8 @@ export interface ExecutionContext {
|
|
|
88
106
|
workdir?: string
|
|
89
107
|
/** Claude Code specific options. */
|
|
90
108
|
claudeCode?: ClaudeCodeOptions
|
|
109
|
+
/** Codex CLI specific options. */
|
|
110
|
+
codex?: CodexOptions
|
|
91
111
|
/** AbortSignal for cancelling requests. When aborted, kills subprocesses. */
|
|
92
112
|
signal?: AbortSignal
|
|
93
113
|
}
|
|
@@ -306,6 +326,8 @@ export interface PipelineConfig {
|
|
|
306
326
|
workdir?: string
|
|
307
327
|
/** Claude Code specific options. */
|
|
308
328
|
claudeCode?: ClaudeCodeOptions
|
|
329
|
+
/** Codex CLI specific options. */
|
|
330
|
+
codex?: CodexOptions
|
|
309
331
|
}
|
|
310
332
|
|
|
311
333
|
/** Options for running a pipeline step. */
|
|
@@ -340,6 +362,8 @@ export interface RunAgentOptions {
|
|
|
340
362
|
workdir?: string
|
|
341
363
|
/** Claude Code specific options. */
|
|
342
364
|
claudeCode?: ClaudeCodeOptions
|
|
365
|
+
/** Codex CLI specific options. */
|
|
366
|
+
codex?: CodexOptions
|
|
343
367
|
/** AbortSignal for cancelling the request. When aborted, kills the subprocess. */
|
|
344
368
|
signal?: AbortSignal
|
|
345
369
|
}
|