ocpipe 0.5.3 → 0.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ocpipe",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
4
4
  "description": "SDK for LLM pipelines with OpenCode and Zod",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/agent.ts CHANGED
@@ -34,7 +34,7 @@ export async function runAgent(
34
34
  async function runOpencodeAgent(
35
35
  options: RunAgentOptions,
36
36
  ): Promise<RunAgentResult> {
37
- const { prompt, agent, model, sessionId, timeoutSec = 600, workdir } = options
37
+ const { prompt, agent, model, sessionId, timeoutSec = 600, workdir, signal } = options
38
38
 
39
39
  if (!model.providerID) {
40
40
  throw new Error('providerID is required for OpenCode backend')
@@ -47,6 +47,11 @@ async function runOpencodeAgent(
47
47
  `\n>>> OpenCode [${agent}] [${modelStr}] ${sessionInfo}: ${promptPreview}...`,
48
48
  )
49
49
 
50
+ // Check if already aborted
51
+ if (signal?.aborted) {
52
+ throw new Error('Request aborted')
53
+ }
54
+
50
55
  // Write prompt to .opencode/prompts/ within the working directory
51
56
  const cwd = workdir ?? PROJECT_ROOT
52
57
  const promptsDir = join(cwd, '.opencode', 'prompts')
@@ -85,6 +90,22 @@ async function runOpencodeAgent(
85
90
  let newSessionId = sessionId || ''
86
91
  const stdoutChunks: string[] = []
87
92
  const stderrChunks: string[] = []
93
+ let aborted = false
94
+
95
+ // Handle abort signal - kill subprocess when aborted
96
+ const abortHandler = async () => {
97
+ if (aborted) return
98
+ aborted = true
99
+ console.error(`\n[abort] Killing OpenCode subprocess...`)
100
+ proc.kill('SIGTERM')
101
+ // Give it a moment to clean up, then force kill
102
+ setTimeout(() => {
103
+ if (!proc.killed) proc.kill('SIGKILL')
104
+ }, 1000)
105
+ await unlink(promptFile).catch(() => {})
106
+ reject(new Error('Request aborted'))
107
+ }
108
+ signal?.addEventListener('abort', abortHandler, { once: true })
88
109
 
89
110
  // Stream stderr in real-time (OpenCode progress output)
90
111
  proc.stderr.on('data', (data: Buffer) => {
@@ -125,6 +146,10 @@ async function runOpencodeAgent(
125
146
 
126
147
  proc.on('close', async (code) => {
127
148
  if (timeout) clearTimeout(timeout)
149
+ signal?.removeEventListener('abort', abortHandler)
150
+
151
+ // If aborted, we already rejected
152
+ if (aborted) return
128
153
 
129
154
  // Clean up prompt file
130
155
  await unlink(promptFile).catch(() => {})
@@ -62,7 +62,12 @@ const logToolCall: HookCallback = async (input) => {
62
62
  export async function runClaudeCodeAgent(
63
63
  options: RunAgentOptions,
64
64
  ): Promise<RunAgentResult> {
65
- const { prompt, model, sessionId, timeoutSec = 600, claudeCode } = options
65
+ const { prompt, model, sessionId, timeoutSec = 600, claudeCode, signal } = options
66
+
67
+ // Check if already aborted
68
+ if (signal?.aborted) {
69
+ throw new Error('Request aborted')
70
+ }
66
71
 
67
72
  // Claude Code understands simple names: opus, sonnet, haiku
68
73
  const modelStr = normalizeModelId(model.modelID)
@@ -94,6 +99,13 @@ export async function runClaudeCodeAgent(
94
99
  unstable_v2_resumeSession(sessionId, sessionOptions)
95
100
  : unstable_v2_createSession(sessionOptions)
96
101
 
102
+ // Handle abort signal
103
+ const abortHandler = () => {
104
+ console.error(`\n[abort] Closing Claude Code session...`)
105
+ session.close()
106
+ }
107
+ signal?.addEventListener('abort', abortHandler, { once: true })
108
+
97
109
  try {
98
110
  // Send the prompt
99
111
  await session.send(prompt)
@@ -113,6 +125,15 @@ export async function runClaudeCodeAgent(
113
125
  })
114
126
  : null
115
127
 
128
+ // Set up abort promise
129
+ const abortPromise = signal ?
130
+ new Promise<never>((_, reject) => {
131
+ signal.addEventListener('abort', () => {
132
+ reject(new Error('Request aborted'))
133
+ }, { once: true })
134
+ })
135
+ : null
136
+
116
137
  // Stream the response
117
138
  const streamPromise = (async () => {
118
139
  for await (const msg of session.stream()) {
@@ -129,12 +150,11 @@ export async function runClaudeCodeAgent(
129
150
  }
130
151
  })()
131
152
 
132
- // Race between stream and timeout
133
- if (timeoutPromise) {
134
- await Promise.race([streamPromise, timeoutPromise])
135
- } else {
136
- await streamPromise
137
- }
153
+ // Race between stream, timeout, and abort
154
+ const promises: Promise<void | never>[] = [streamPromise]
155
+ if (timeoutPromise) promises.push(timeoutPromise)
156
+ if (abortPromise) promises.push(abortPromise)
157
+ await Promise.race(promises)
138
158
 
139
159
  const response = textParts.join('')
140
160
  const sessionStr = newSessionId || 'none'
@@ -147,6 +167,7 @@ export async function runClaudeCodeAgent(
147
167
  sessionId: newSessionId,
148
168
  }
149
169
  } finally {
170
+ signal?.removeEventListener('abort', abortHandler)
150
171
  session.close()
151
172
  }
152
173
  }
package/src/predict.ts CHANGED
@@ -77,6 +77,7 @@ export class Predict<S extends AnySignature> {
77
77
  timeoutSec: ctx.timeoutSec,
78
78
  workdir: ctx.workdir,
79
79
  claudeCode: ctx.claudeCode,
80
+ signal: ctx.signal,
80
81
  })
81
82
 
82
83
  // Update context with new session ID for continuity
@@ -206,6 +207,7 @@ export class Predict<S extends AnySignature> {
206
207
  timeoutSec: 60,
207
208
  workdir: ctx.workdir,
208
209
  claudeCode: ctx.claudeCode,
210
+ signal: ctx.signal,
209
211
  })
210
212
 
211
213
  // Try to parse the repaired JSON
@@ -282,6 +284,7 @@ export class Predict<S extends AnySignature> {
282
284
  timeoutSec: 60, // Short timeout for simple patches
283
285
  workdir: ctx.workdir,
284
286
  claudeCode: ctx.claudeCode,
287
+ signal: ctx.signal,
285
288
  })
286
289
 
287
290
  // Extract and apply the patch based on method
package/src/types.ts CHANGED
@@ -53,6 +53,8 @@ export interface ExecutionContext {
53
53
  workdir?: string
54
54
  /** Claude Code specific options. */
55
55
  claudeCode?: ClaudeCodeOptions
56
+ /** AbortSignal for cancelling requests. When aborted, kills subprocesses. */
57
+ signal?: AbortSignal
56
58
  }
57
59
 
58
60
  // ============================================================================
@@ -303,6 +305,8 @@ export interface RunAgentOptions {
303
305
  workdir?: string
304
306
  /** Claude Code specific options. */
305
307
  claudeCode?: ClaudeCodeOptions
308
+ /** AbortSignal for cancelling the request. When aborted, kills the subprocess. */
309
+ signal?: AbortSignal
306
310
  }
307
311
 
308
312
  /** Result from running an OpenCode agent. */