@ottimis/jack-provider-sdk 0.1.0

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/src/spawner.ts ADDED
@@ -0,0 +1,70 @@
1
+ /**
2
+ * ProcessSpawner — abstraction over "how we get a running provider CLI
3
+ * with stdin/stdout pipes attached". Consumed by every backend so they
4
+ * don't need to know whether the process runs locally or inside a Docker
5
+ * container.
6
+ *
7
+ * Design goals:
8
+ * - The shape matches the Anthropic SDK's `SpawnedProcess` / `SpawnOptions`
9
+ * exactly, so a single spawner can be fed to that SDK without
10
+ * translation. Other providers happily reuse the same shape.
11
+ * - The only Docker-aware code lives in the host's `docker/sandbox.ts`
12
+ * which exposes a `createDockerSpawner()` that returns a ProcessSpawner.
13
+ * - The host decides which spawner to use based on `session.sandboxed`
14
+ * and hands it to the provider via `AgentQueryOptions.spawner`.
15
+ */
16
+
17
+ import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process'
18
+ import type { Readable, Writable } from 'node:stream'
19
+
20
+ /**
21
+ * Minimal process handle that both `ChildProcess` (node) and the Anthropic
22
+ * SDK's `SpawnedProcess` satisfy. Deliberately identical to the SDK
23
+ * interface so that values of this type can be handed back to the SDK
24
+ * verbatim.
25
+ */
26
+ export interface ProcessHandle {
27
+ stdin: Writable
28
+ stdout: Readable
29
+ readonly killed: boolean
30
+ readonly exitCode: number | null
31
+ kill(signal?: NodeJS.Signals): boolean
32
+ on(event: 'exit', listener: (code: number | null, signal: NodeJS.Signals | null) => void): void
33
+ on(event: 'error', listener: (error: Error) => void): void
34
+ once(event: 'exit', listener: (code: number | null, signal: NodeJS.Signals | null) => void): void
35
+ once(event: 'error', listener: (error: Error) => void): void
36
+ off(event: 'exit', listener: (code: number | null, signal: NodeJS.Signals | null) => void): void
37
+ off(event: 'error', listener: (error: Error) => void): void
38
+ }
39
+
40
+ /**
41
+ * Arguments handed to a spawner. Matches the Anthropic SDK `SpawnOptions`
42
+ * shape so the same function can be plugged into `spawnClaudeCodeProcess`.
43
+ */
44
+ export interface SpawnArgs {
45
+ command: string
46
+ args: string[]
47
+ cwd?: string
48
+ env: { [k: string]: string | undefined }
49
+ signal: AbortSignal
50
+ }
51
+
52
+ export type ProcessSpawner = (args: SpawnArgs) => ProcessHandle
53
+
54
+ /**
55
+ * Default local spawner — plain `child_process.spawn`. The returned
56
+ * ChildProcess satisfies ProcessHandle because we pipe all three streams.
57
+ */
58
+ export const localSpawner: ProcessSpawner = ({ command, args, cwd, env, signal }) => {
59
+ const cp: ChildProcessWithoutNullStreams = spawn(command, args, {
60
+ cwd,
61
+ env,
62
+ stdio: ['pipe', 'pipe', 'pipe']
63
+ })
64
+ signal.addEventListener('abort', () => {
65
+ if (!cp.killed) {
66
+ try { cp.kill('SIGTERM') } catch { /* ignore */ }
67
+ }
68
+ })
69
+ return cp
70
+ }