ocpipe 0.6.10 → 0.6.12

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.
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  import { execSync } from 'child_process'
14
- import { existsSync, readFileSync } from 'fs'
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', { encoding: 'utf8', timeout: 3000 }).trim()
69
+ const found = execSync('which claude', {
70
+ encoding: 'utf8',
71
+ timeout: 3000,
72
+ }).trim()
70
73
  if (found) return found
71
- } catch { /* which not found or timed out — fall through to defaults */ }
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: explicit option > agent definition file > none
173
- const systemPrompt = claudeCode?.systemPrompt ?? loadAgentDefinition(agent, workdir)
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(claudeCode?.pathToClaudeCodeExecutable)
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(`[ocpipe] Subagents defined: ${Object.keys(claudeCode.agents).join(', ')}`)
217
+ console.error(
218
+ `[ocpipe] Subagents defined: ${Object.keys(claudeCode.agents).join(', ')}`,
219
+ )
227
220
  }
228
221
  if (claudeCode?.allowedTools) {
229
- console.error(`[ocpipe] Allowed tools: ${claudeCode.allowedTools.join(', ')}`)
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/codex.ts CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  type RunResult,
11
11
  type ThreadOptions,
12
12
  } from '@openai/codex-sdk'
13
+ import { OutputLimitError } from './errors.js'
13
14
  import { PROJECT_ROOT } from './paths.js'
14
15
  import type {
15
16
  CodexOptions,
@@ -18,22 +19,29 @@ import type {
18
19
  RunAgentResult,
19
20
  } from './types.js'
20
21
 
22
+ const MAX_CODEX_LOG_LINE_BYTES = 1024 * 1024
23
+
21
24
  class CodexLogFilter {
22
25
  private buf = ''
23
26
  private suppressHtml = false
24
27
 
25
28
  write(text: string): string {
26
- this.buf += text
27
29
  let out = ''
28
- for (;;) {
29
- const idx = this.buf.indexOf('\n')
30
+ let cursor = 0
31
+ while (cursor < text.length) {
32
+ const idx = text.indexOf('\n', cursor)
30
33
  if (idx < 0) {
31
- return out
34
+ this.append(text.slice(cursor))
35
+ break
32
36
  }
33
- const line = this.buf.slice(0, idx + 1)
34
- this.buf = this.buf.slice(idx + 1)
35
- out += this.filterLine(line)
37
+
38
+ const line = text.slice(cursor, idx + 1)
39
+ cursor = idx + 1
40
+ this.append(line)
41
+ out += this.filterLine(this.buf)
42
+ this.buf = ''
36
43
  }
44
+ return out
37
45
  }
38
46
 
39
47
  flush(): string {
@@ -42,6 +50,16 @@ class CodexLogFilter {
42
50
  return this.filterLine(line)
43
51
  }
44
52
 
53
+ private append(text: string): void {
54
+ if (
55
+ Buffer.byteLength(this.buf, 'utf8') + Buffer.byteLength(text, 'utf8') >
56
+ MAX_CODEX_LOG_LINE_BYTES
57
+ ) {
58
+ throw new OutputLimitError('Codex log line', MAX_CODEX_LOG_LINE_BYTES)
59
+ }
60
+ this.buf += text
61
+ }
62
+
45
63
  private filterLine(line: string): string {
46
64
  if (this.suppressHtml) {
47
65
  if (line.includes('</html>')) {
@@ -239,7 +257,9 @@ export function formatCodexRunSummary(summary: CodexRunSummary): string {
239
257
  return lines.join('\n')
240
258
  }
241
259
 
242
- function isFailedCommand(command: CodexRunSummary['commands'][number]): boolean {
260
+ function isFailedCommand(
261
+ command: CodexRunSummary['commands'][number],
262
+ ): boolean {
243
263
  return (
244
264
  command.status === 'failed' ||
245
265
  (command.exitCode !== null && command.exitCode !== 0)
package/src/errors.ts ADDED
@@ -0,0 +1,19 @@
1
+ /** ocpipe typed runtime errors. */
2
+
3
+ function formatBytes(bytes: number): string {
4
+ if (bytes >= 1024 * 1024 * 1024) return `${bytes / (1024 * 1024 * 1024)} GiB`
5
+ if (bytes >= 1024 * 1024) return `${bytes / (1024 * 1024)} MiB`
6
+ if (bytes >= 1024) return `${bytes / 1024} KiB`
7
+ return `${bytes} bytes`
8
+ }
9
+
10
+ /** OutputLimitError reports a bounded output buffer or file limit breach. */
11
+ export class OutputLimitError extends Error {
12
+ constructor(
13
+ public readonly source: string,
14
+ public readonly limitBytes: number,
15
+ ) {
16
+ super(`${source} exceeded ${formatBytes(limitBytes)} limit`)
17
+ this.name = 'OutputLimitError'
18
+ }
19
+ }
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * ocpipe: LLM pipelines with OpenCode and Zod.
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: { providerID: 'opencode', modelID: 'minimax-m2.1-free' },
26
- * defaultAgent: 'general',
25
+ * defaultModel: { backend: 'omp', modelID: 'gpt-5.5' },
27
26
  * checkpointDir: './ckpt',
28
27
  * logDir: './logs',
29
28
  * }, createBaseState)
@@ -51,6 +50,7 @@ export { createSessionId, createBaseState, extendBaseState } from './state.js'
51
50
  // Agent integration
52
51
  export { runAgent, logStep } from './agent.js'
53
52
  export { buildCodexRunSummary, formatCodexRunSummary } from './codex.js'
53
+ export { OutputLimitError } from './errors.js'
54
54
 
55
55
  // Response parsing
56
56
  export {
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 opencode session in state
93
- this.state.opencodeSessionId = this.ctx.sessionId
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 OpenCode session ID. */
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.opencodeSessionId) {
194
- pipeline.ctx.sessionId = state.opencodeSessionId
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 OpenCode, and parsing the response.
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
- | Record<string, Record<string, unknown>>
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: { providerID: string; modelID: string }
138
- defaultAgent: string
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
- providerID: 'github-copilot',
146
- modelID: 'grok-code-fast-1',
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 agents. */
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
- | 'minimal'
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
- | 'read-only'
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
- | 'never'
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
- | 'default'
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: 'opencode'). */
167
+ /** Backend to use (default: 'omp'). */
171
168
  backend?: BackendType
172
- /** Provider ID (required for OpenCode, ignored for Claude Code). */
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 OpenCode session ID (for continuity). */
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
- /** Working directory for opencode (where .opencode/agents/ lives). */
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
- /** OpenCode session ID used. */
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 OpenCode session ID (for continuity). */
255
- opencodeSessionId?: string
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
- /** OpenCode session ID. */
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
- | 'json_parse_failed'
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
- /** Working directory for opencode (where .opencode/agents/ lives). */
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 an OpenCode agent. */
442
+ /** Options for running a prompt through a backend. */
448
443
  export interface RunAgentOptions {
449
- /** The prompt to send to the agent. */
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
- /** Working directory for opencode (where .opencode/agents/ lives). */
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 an OpenCode agent. */
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