ocpipe 0.5.13 → 0.5.15
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 +5 -5
- package/src/claude-code.ts +39 -39
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ocpipe",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.15",
|
|
4
4
|
"description": "SDK for LLM pipelines with OpenCode and Zod",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"dependencies": {},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"zod": "4.3.6",
|
|
34
|
-
"@anthropic-ai/claude-agent-sdk": "0.2.
|
|
34
|
+
"@anthropic-ai/claude-agent-sdk": "0.2.44"
|
|
35
35
|
},
|
|
36
36
|
"peerDependenciesMeta": {
|
|
37
37
|
"@anthropic-ai/claude-agent-sdk": {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
}
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@anthropic-ai/claude-agent-sdk": "^0.2.
|
|
42
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.44",
|
|
43
43
|
"@eslint/js": "^10.0.1",
|
|
44
44
|
"bun-types": "^1.3.9",
|
|
45
45
|
"eslint": "^10.0.0",
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
"jiti": "^2.6.1",
|
|
48
48
|
"prettier": "^3.8.1",
|
|
49
49
|
"typescript": "^5.0.0",
|
|
50
|
-
"typescript-eslint": "^8.
|
|
51
|
-
"@typescript/native-preview": "^7.0.0-dev.
|
|
50
|
+
"typescript-eslint": "^8.56.0",
|
|
51
|
+
"@typescript/native-preview": "^7.0.0-dev.20260216.1",
|
|
52
52
|
"vitest": "^4.0.18"
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
package/src/claude-code.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ocpipe Claude Code agent integration.
|
|
3
3
|
*
|
|
4
|
-
* Uses the Claude Agent SDK
|
|
4
|
+
* Uses the Claude Agent SDK v1 query() API for running LLM agents with session management.
|
|
5
|
+
* The v1 API properly supports session persistence — the subprocess exits naturally when
|
|
6
|
+
* the query completes, giving it time to save session data to disk. The v2 API's close()
|
|
7
|
+
* sends SIGTERM immediately, which kills the subprocess before it can persist.
|
|
8
|
+
*
|
|
9
|
+
* See: https://github.com/s4wave/ocpipe/issues/10
|
|
10
|
+
* See: https://github.com/anthropics/anthropic-sdk-typescript/issues/911
|
|
5
11
|
*/
|
|
6
12
|
|
|
7
13
|
import { execSync } from 'child_process'
|
|
@@ -9,12 +15,11 @@ import { existsSync, readFileSync } from 'fs'
|
|
|
9
15
|
import { join } from 'path'
|
|
10
16
|
import { homedir } from 'os'
|
|
11
17
|
import {
|
|
12
|
-
|
|
13
|
-
unstable_v2_resumeSession,
|
|
18
|
+
query,
|
|
14
19
|
type HookCallback,
|
|
20
|
+
type Options,
|
|
15
21
|
type PreToolUseHookInput,
|
|
16
22
|
type SDKMessage,
|
|
17
|
-
type SDKSessionOptions,
|
|
18
23
|
} from '@anthropic-ai/claude-agent-sdk'
|
|
19
24
|
import type { RunAgentOptions, RunAgentResult } from './types.js'
|
|
20
25
|
|
|
@@ -161,22 +166,35 @@ export async function runClaudeCodeAgent(
|
|
|
161
166
|
const sessionInfo = sessionId ? `[session:${sessionId}]` : '[new session]'
|
|
162
167
|
const promptPreview = prompt.slice(0, 50).replace(/\n/g, ' ')
|
|
163
168
|
|
|
164
|
-
// Build
|
|
169
|
+
// Build query options with configurable permission mode (default: acceptEdits)
|
|
165
170
|
const permissionMode = claudeCode?.permissionMode ?? 'acceptEdits'
|
|
166
171
|
|
|
167
172
|
// Resolve system prompt: explicit option > agent definition file > none
|
|
168
173
|
const systemPrompt = claudeCode?.systemPrompt ?? loadAgentDefinition(agent, workdir)
|
|
169
174
|
|
|
170
|
-
|
|
175
|
+
// Bridge external abort signal to an AbortController for the SDK
|
|
176
|
+
const abortController = new AbortController()
|
|
177
|
+
if (signal) {
|
|
178
|
+
signal.addEventListener(
|
|
179
|
+
'abort',
|
|
180
|
+
() => {
|
|
181
|
+
console.error(`\n[abort] Aborting Claude Code query...`)
|
|
182
|
+
abortController.abort()
|
|
183
|
+
},
|
|
184
|
+
{ once: true },
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const queryOptions: Options = {
|
|
171
189
|
model: modelStr,
|
|
172
190
|
permissionMode,
|
|
191
|
+
abortController,
|
|
192
|
+
// v1 persistSession defaults to true, but set explicitly for clarity
|
|
193
|
+
persistSession: true,
|
|
173
194
|
...(workdir && { cwd: workdir }),
|
|
174
195
|
...(systemPrompt && { systemPrompt }),
|
|
175
|
-
//
|
|
176
|
-
|
|
177
|
-
// Without this, session.close() destroys the session and resumeSession() fails
|
|
178
|
-
// with "No conversation found with session ID".
|
|
179
|
-
...({ persistSession: true }),
|
|
196
|
+
// Resume from previous session if sessionId provided
|
|
197
|
+
...(sessionId && { resume: sessionId }),
|
|
180
198
|
hooks: {
|
|
181
199
|
PreToolUse: [{ hooks: [logToolCall] }],
|
|
182
200
|
},
|
|
@@ -196,36 +214,22 @@ export async function runClaudeCodeAgent(
|
|
|
196
214
|
`\n>>> Claude Code [${modelStr}] [${permissionMode}] ${sessionInfo}: ${promptPreview}...`,
|
|
197
215
|
)
|
|
198
216
|
|
|
199
|
-
//
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
unstable_v2_resumeSession(sessionId, sessionOptions)
|
|
203
|
-
: unstable_v2_createSession(sessionOptions)
|
|
217
|
+
// v1 query() returns an AsyncGenerator — subprocess exits naturally when done,
|
|
218
|
+
// allowing session data to be persisted to disk before the process ends.
|
|
219
|
+
const q = query({ prompt, options: queryOptions })
|
|
204
220
|
|
|
205
|
-
// Handle abort signal
|
|
206
|
-
const abortHandler = () => {
|
|
207
|
-
console.error(`\n[abort] Closing Claude Code session...`)
|
|
208
|
-
session.close()
|
|
209
|
-
}
|
|
210
|
-
signal?.addEventListener('abort', abortHandler, { once: true })
|
|
211
|
-
|
|
212
|
-
// Declare outside try block so finally can access it
|
|
213
221
|
let timeoutId: ReturnType<typeof setTimeout> | null = null
|
|
214
222
|
|
|
215
223
|
try {
|
|
216
|
-
// Send the prompt
|
|
217
|
-
await session.send(prompt)
|
|
218
|
-
|
|
219
|
-
// Collect the response
|
|
220
224
|
const textParts: string[] = []
|
|
221
225
|
let newSessionId = sessionId || ''
|
|
222
226
|
|
|
223
|
-
// Set up timeout
|
|
227
|
+
// Set up timeout
|
|
224
228
|
const timeoutPromise =
|
|
225
229
|
timeoutSec > 0 ?
|
|
226
230
|
new Promise<never>((_, reject) => {
|
|
227
231
|
timeoutId = setTimeout(() => {
|
|
228
|
-
|
|
232
|
+
q.close()
|
|
229
233
|
reject(new Error(`Timeout after ${timeoutSec}s`))
|
|
230
234
|
}, timeoutSec * 1000)
|
|
231
235
|
})
|
|
@@ -237,18 +241,15 @@ export async function runClaudeCodeAgent(
|
|
|
237
241
|
new Promise<never>((_, reject) => {
|
|
238
242
|
signal.addEventListener(
|
|
239
243
|
'abort',
|
|
240
|
-
() =>
|
|
241
|
-
reject(new Error('Request aborted'))
|
|
242
|
-
},
|
|
244
|
+
() => reject(new Error('Request aborted')),
|
|
243
245
|
{ once: true },
|
|
244
246
|
)
|
|
245
247
|
})
|
|
246
248
|
: null
|
|
247
249
|
|
|
248
|
-
// Stream the response
|
|
250
|
+
// Stream the response — iterate the AsyncGenerator
|
|
249
251
|
const streamPromise = (async () => {
|
|
250
|
-
for await (const msg of
|
|
251
|
-
// Capture session ID from any message
|
|
252
|
+
for await (const msg of q) {
|
|
252
253
|
if (msg.session_id) {
|
|
253
254
|
newSessionId = msg.session_id
|
|
254
255
|
}
|
|
@@ -268,7 +269,6 @@ export async function runClaudeCodeAgent(
|
|
|
268
269
|
if (abortPromise) promises.push(abortPromise)
|
|
269
270
|
await Promise.race(promises)
|
|
270
271
|
|
|
271
|
-
// Clear the timeout to prevent it from keeping the event loop alive
|
|
272
272
|
if (timeoutId) clearTimeout(timeoutId)
|
|
273
273
|
|
|
274
274
|
const response = textParts.join('')
|
|
@@ -287,8 +287,8 @@ export async function runClaudeCodeAgent(
|
|
|
287
287
|
sessionId: newSessionId,
|
|
288
288
|
}
|
|
289
289
|
} finally {
|
|
290
|
-
signal?.removeEventListener('abort', abortHandler)
|
|
291
290
|
if (timeoutId) clearTimeout(timeoutId)
|
|
292
|
-
|
|
291
|
+
// v1 subprocess exits naturally when the generator completes — no close() needed.
|
|
292
|
+
// close() is only called on timeout (above) to force-terminate.
|
|
293
293
|
}
|
|
294
294
|
}
|