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 +1 -1
- package/src/agent.ts +26 -1
- package/src/claude-code.ts +28 -7
- package/src/predict.ts +3 -0
- package/src/types.ts +4 -0
package/package.json
CHANGED
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(() => {})
|
package/src/claude-code.ts
CHANGED
|
@@ -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
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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. */
|