@pivanov/claude-wire 0.0.3 → 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/README.md +8 -4
- package/dist/async.d.ts +10 -0
- package/dist/async.js +27 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +21 -3
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +7 -0
- package/dist/cost.d.ts +8 -0
- package/dist/cost.js +32 -11
- package/dist/errors.d.ts +5 -2
- package/dist/errors.js +38 -6
- package/dist/index.d.ts +10 -6
- package/dist/index.js +10 -3
- package/dist/json.d.ts +35 -0
- package/dist/json.js +43 -0
- package/dist/parser/translator.js +7 -2
- package/dist/pipeline.d.ts +14 -4
- package/dist/pipeline.js +39 -19
- package/dist/process.d.ts +15 -3
- package/dist/process.js +86 -25
- package/dist/reader.d.ts +3 -0
- package/dist/reader.js +32 -30
- package/dist/runtime.d.ts +5 -4
- package/dist/runtime.js +8 -5
- package/dist/session.d.ts +31 -2
- package/dist/session.js +138 -71
- package/dist/stderr.d.ts +10 -0
- package/dist/stderr.js +31 -0
- package/dist/stream.js +61 -26
- package/dist/tools/handler.d.ts +1 -0
- package/dist/tools/registry.d.ts +4 -2
- package/dist/tools/registry.js +7 -4
- package/dist/types/events.d.ts +1 -1
- package/dist/types/options.d.ts +43 -6
- package/dist/types/protocol.d.ts +1 -5
- package/dist/types/results.d.ts +6 -6
- package/dist/validation.d.ts +10 -0
- package/dist/validation.js +23 -0
- package/dist/warnings.d.ts +2 -0
- package/dist/warnings.js +24 -0
- package/dist/writer.d.ts +10 -1
- package/dist/writer.js +14 -8
- package/package.json +1 -1
package/dist/process.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { IClaudeOptions } from "./types/options.js";
|
|
2
2
|
export interface IClaudeProcess {
|
|
3
3
|
write: (message: string) => void;
|
|
4
|
-
kill: () => void;
|
|
4
|
+
kill: (signal?: NodeJS.Signals | number) => void;
|
|
5
5
|
exited: Promise<number>;
|
|
6
6
|
stdout: ReadableStream<Uint8Array>;
|
|
7
7
|
stderr: ReadableStream<Uint8Array>;
|
|
@@ -10,8 +10,20 @@ export interface IClaudeProcess {
|
|
|
10
10
|
export interface ISpawnOptions extends IClaudeOptions {
|
|
11
11
|
prompt?: string;
|
|
12
12
|
}
|
|
13
|
+
export declare const safeKill: (proc: Pick<IClaudeProcess, "kill">, signal?: NodeJS.Signals | number) => void;
|
|
14
|
+
export declare const safeWrite: (proc: Pick<IClaudeProcess, "write">, line: string) => boolean;
|
|
13
15
|
export declare const ALIAS_PATTERN: RegExp;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Clears the cached resolved environment (binary path + alias-detected
|
|
18
|
+
* `CLAUDE_CONFIG_DIR`). Call this when either has changed mid-process -- for
|
|
19
|
+
* example after installing the Claude CLI during a test run, or when a long-
|
|
20
|
+
* running daemon updates the user's shell rc file. The next `spawnClaude()`
|
|
21
|
+
* will re-resolve from scratch.
|
|
22
|
+
*
|
|
23
|
+
* Normal applications should never need this; the cache is populated once at
|
|
24
|
+
* first use and kept for the process lifetime.
|
|
25
|
+
*/
|
|
26
|
+
export declare const resetResolvedEnvCache: () => void;
|
|
16
27
|
export declare const buildArgs: (options: ISpawnOptions, binaryPath: string) => string[];
|
|
28
|
+
export declare const buildSpawnEnv: (baseEnv: Record<string, string | undefined>, aliasConfigDir: string | undefined, options: Pick<ISpawnOptions, "configDir" | "env">) => Record<string, string | undefined> | undefined;
|
|
17
29
|
export declare const spawnClaude: (options: ISpawnOptions) => IClaudeProcess;
|
package/dist/process.js
CHANGED
|
@@ -2,16 +2,41 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { BINARY } from "./constants.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { errorMessage, KnownError, ProcessError } from "./errors.js";
|
|
6
|
+
import { isExecutableNonEmpty, spawnProcess, whichSync } from "./runtime.js";
|
|
7
|
+
import { assertPositiveNumber } from "./validation.js";
|
|
7
8
|
import { writer } from "./writer.js";
|
|
9
|
+
// Swallow ESRCH/EPIPE-style throws from kill()/write() when the child is
|
|
10
|
+
// already gone. Every call site had the same try/catch -- keeping it in one
|
|
11
|
+
// place stops future adders from forgetting the guard.
|
|
12
|
+
export const safeKill = (proc, signal) => {
|
|
13
|
+
try {
|
|
14
|
+
proc.kill(signal);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// already dead
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
export const safeWrite = (proc, line) => {
|
|
21
|
+
try {
|
|
22
|
+
proc.write(line);
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// stdin closed / process died -- caller surfaces the error via the read path
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
// Resolves the `claude` CLI binary path. POSIX-only today: uses `which` and
|
|
31
|
+
// `$HOME`-rooted common install paths. Windows users running under WSL get
|
|
32
|
+
// the Linux layout, which works; native Windows is not supported yet.
|
|
8
33
|
const resolveBinaryPath = () => {
|
|
9
34
|
const found = whichSync("claude");
|
|
10
35
|
if (found) {
|
|
11
36
|
return found;
|
|
12
37
|
}
|
|
13
38
|
for (const p of BINARY.commonPaths) {
|
|
14
|
-
if (
|
|
39
|
+
if (isExecutableNonEmpty(p)) {
|
|
15
40
|
return p;
|
|
16
41
|
}
|
|
17
42
|
}
|
|
@@ -20,9 +45,12 @@ const resolveBinaryPath = () => {
|
|
|
20
45
|
// Rejects lines whose first non-whitespace char is `#` so commented-out
|
|
21
46
|
// aliases/exports don't silently apply. /m anchors to each line in rc files.
|
|
22
47
|
export const ALIAS_PATTERN = /^(?!\s*#).*?(?:alias\s+claude\s*=|export\s+).*CLAUDE_CONFIG_DIR=["']?\$?(?:HOME|\{HOME\}|~)\/?([^\s"']+?)["']?(?:\s|$)/m;
|
|
23
|
-
|
|
48
|
+
const resolveConfigDirFromAlias = () => {
|
|
24
49
|
const home = homedir();
|
|
25
|
-
|
|
50
|
+
// .zshenv is the one file zsh sources for NON-interactive shells, so
|
|
51
|
+
// users who export CLAUDE_CONFIG_DIR for cron/CI-like contexts often
|
|
52
|
+
// put it there. Include it alongside the interactive-shell rc files.
|
|
53
|
+
const rcFiles = [".zshenv", ".zshrc", ".bashrc", ".zprofile", ".bash_profile", ".aliases"];
|
|
26
54
|
for (const rcFile of rcFiles) {
|
|
27
55
|
try {
|
|
28
56
|
const content = readFileSync(join(home, rcFile), "utf-8");
|
|
@@ -38,7 +66,17 @@ export const resolveConfigDirFromAlias = () => {
|
|
|
38
66
|
return undefined;
|
|
39
67
|
};
|
|
40
68
|
let cached;
|
|
41
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Clears the cached resolved environment (binary path + alias-detected
|
|
71
|
+
* `CLAUDE_CONFIG_DIR`). Call this when either has changed mid-process -- for
|
|
72
|
+
* example after installing the Claude CLI during a test run, or when a long-
|
|
73
|
+
* running daemon updates the user's shell rc file. The next `spawnClaude()`
|
|
74
|
+
* will re-resolve from scratch.
|
|
75
|
+
*
|
|
76
|
+
* Normal applications should never need this; the cache is populated once at
|
|
77
|
+
* first use and kept for the process lifetime.
|
|
78
|
+
*/
|
|
79
|
+
export const resetResolvedEnvCache = () => {
|
|
42
80
|
cached = undefined;
|
|
43
81
|
};
|
|
44
82
|
const resolve = () => {
|
|
@@ -62,6 +100,9 @@ export const buildArgs = (options, binaryPath) => {
|
|
|
62
100
|
args.push(name, value);
|
|
63
101
|
}
|
|
64
102
|
};
|
|
103
|
+
// Default ON: the translator's block-dedup relies on --verbose emitting
|
|
104
|
+
// cumulative assistant content. Consumers must explicitly pass `false`
|
|
105
|
+
// to opt out (`undefined` still yields --verbose).
|
|
65
106
|
flag(options.verbose !== false, "--verbose");
|
|
66
107
|
kv(options.model, "--model");
|
|
67
108
|
kv(options.systemPrompt, "--system-prompt");
|
|
@@ -103,36 +144,56 @@ export const buildArgs = (options, binaryPath) => {
|
|
|
103
144
|
flag(options.disableSlashCommands, "--disable-slash-commands");
|
|
104
145
|
return args;
|
|
105
146
|
};
|
|
147
|
+
// Priority (lowest → highest): baseEnv < alias-detected config <
|
|
148
|
+
// user's explicit `options.env` < explicit `options.configDir`. User
|
|
149
|
+
// input always outranks the alias heuristic. Returns undefined when no
|
|
150
|
+
// override is needed, so spawnProcess can pass the parent env through.
|
|
151
|
+
export const buildSpawnEnv = (baseEnv, aliasConfigDir, options) => {
|
|
152
|
+
const needsEnv = aliasConfigDir || options.configDir || options.env;
|
|
153
|
+
if (!needsEnv) {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
const spawnEnv = { ...baseEnv };
|
|
157
|
+
if (aliasConfigDir) {
|
|
158
|
+
spawnEnv.CLAUDE_CONFIG_DIR = aliasConfigDir;
|
|
159
|
+
}
|
|
160
|
+
if (options.env) {
|
|
161
|
+
Object.assign(spawnEnv, options.env);
|
|
162
|
+
}
|
|
163
|
+
if (options.configDir) {
|
|
164
|
+
spawnEnv.CLAUDE_CONFIG_DIR = options.configDir;
|
|
165
|
+
}
|
|
166
|
+
return spawnEnv;
|
|
167
|
+
};
|
|
106
168
|
export const spawnClaude = (options) => {
|
|
107
169
|
assertPositiveNumber(options.maxBudgetUsd, "maxBudgetUsd");
|
|
108
170
|
const resolved = resolve();
|
|
109
171
|
const args = buildArgs(options, resolved.binaryPath);
|
|
110
172
|
try {
|
|
111
|
-
const
|
|
112
|
-
let spawnEnv;
|
|
113
|
-
if (needsEnv) {
|
|
114
|
-
// Priority (lowest → highest): process.env < alias-detected config <
|
|
115
|
-
// user's explicit `options.env` < explicit `options.configDir`. User
|
|
116
|
-
// input always outranks the alias heuristic.
|
|
117
|
-
spawnEnv = { ...process.env };
|
|
118
|
-
if (resolved.aliasConfigDir) {
|
|
119
|
-
spawnEnv.CLAUDE_CONFIG_DIR = resolved.aliasConfigDir;
|
|
120
|
-
}
|
|
121
|
-
if (options.env) {
|
|
122
|
-
Object.assign(spawnEnv, options.env);
|
|
123
|
-
}
|
|
124
|
-
if (options.configDir) {
|
|
125
|
-
spawnEnv.CLAUDE_CONFIG_DIR = options.configDir;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
173
|
+
const spawnEnv = buildSpawnEnv(process.env, resolved.aliasConfigDir, options);
|
|
128
174
|
const rawProc = spawnProcess(args, { cwd: options.cwd, env: spawnEnv });
|
|
129
175
|
rawProc.exited.catch(() => { });
|
|
176
|
+
// Tear the child down when the caller's signal aborts. Without this,
|
|
177
|
+
// a signal that fires BEFORE stdout emits anything leaves the reader
|
|
178
|
+
// loop to eventually notice -- the child keeps running in the meantime.
|
|
179
|
+
// Register FIRST, then re-check `aborted`: closes the gap where abort
|
|
180
|
+
// could fire between the check and listener attach. `once: true` lets
|
|
181
|
+
// the listener be GC'd after firing.
|
|
182
|
+
if (options.signal) {
|
|
183
|
+
const onAbort = () => {
|
|
184
|
+
safeKill(rawProc);
|
|
185
|
+
};
|
|
186
|
+
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
187
|
+
if (options.signal.aborted) {
|
|
188
|
+
safeKill(rawProc);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
130
191
|
const claudeProc = {
|
|
131
192
|
write: (msg) => {
|
|
132
193
|
rawProc.stdin.write(msg);
|
|
133
194
|
},
|
|
134
|
-
kill: () => {
|
|
135
|
-
rawProc.kill();
|
|
195
|
+
kill: (signal) => {
|
|
196
|
+
rawProc.kill(signal);
|
|
136
197
|
},
|
|
137
198
|
exited: rawProc.exited,
|
|
138
199
|
stdout: rawProc.stdout,
|
package/dist/reader.d.ts
CHANGED
|
@@ -2,16 +2,19 @@ import type { ITranslator } from "./parser/translator.js";
|
|
|
2
2
|
import type { IClaudeProcess } from "./process.js";
|
|
3
3
|
import type { IToolHandlerInstance } from "./tools/handler.js";
|
|
4
4
|
import type { TRelayEvent } from "./types/events.js";
|
|
5
|
+
import type { TWarn } from "./warnings.js";
|
|
5
6
|
export interface IReaderOptions {
|
|
6
7
|
reader: ReadableStreamDefaultReader<Uint8Array>;
|
|
7
8
|
translator: ITranslator;
|
|
8
9
|
toolHandler?: IToolHandlerInstance;
|
|
9
10
|
proc?: IClaudeProcess;
|
|
10
11
|
signal?: AbortSignal;
|
|
12
|
+
onWarning?: TWarn;
|
|
11
13
|
}
|
|
12
14
|
export interface IStderrDrain {
|
|
13
15
|
chunks: string[];
|
|
14
16
|
done: Promise<void>;
|
|
17
|
+
text: () => string;
|
|
15
18
|
}
|
|
16
19
|
export declare const drainStderr: (proc: {
|
|
17
20
|
stderr: ReadableStream<Uint8Array>;
|
package/dist/reader.js
CHANGED
|
@@ -2,6 +2,7 @@ import { LIMITS, TIMEOUTS } from "./constants.js";
|
|
|
2
2
|
import { AbortError, ClaudeError, TimeoutError } from "./errors.js";
|
|
3
3
|
import { parseLine } from "./parser/ndjson.js";
|
|
4
4
|
import { dispatchToolDecision } from "./pipeline.js";
|
|
5
|
+
import { safeKill, safeWrite } from "./process.js";
|
|
5
6
|
import { writer } from "./writer.js";
|
|
6
7
|
export const drainStderr = (proc) => {
|
|
7
8
|
const chunks = [];
|
|
@@ -29,7 +30,11 @@ export const drainStderr = (proc) => {
|
|
|
29
30
|
stderrReader.releaseLock();
|
|
30
31
|
}
|
|
31
32
|
})().catch(() => { });
|
|
32
|
-
return {
|
|
33
|
+
return {
|
|
34
|
+
chunks,
|
|
35
|
+
done,
|
|
36
|
+
text: () => chunks.join("").trim(),
|
|
37
|
+
};
|
|
33
38
|
};
|
|
34
39
|
export async function* readNdjsonEvents(opts) {
|
|
35
40
|
const { reader, translator, signal } = opts;
|
|
@@ -43,13 +48,29 @@ export async function* readNdjsonEvents(opts) {
|
|
|
43
48
|
});
|
|
44
49
|
// Swallow unhandled rejection if nothing ever races against this promise.
|
|
45
50
|
abortPromise.catch(() => { });
|
|
46
|
-
// Single resettable timeout shared across all iterations
|
|
51
|
+
// Single resettable timeout shared across all iterations -- avoids leaking
|
|
47
52
|
// a new Promise + setTimeout per read loop.
|
|
48
53
|
let timeoutReject;
|
|
49
54
|
const timeoutPromise = new Promise((_, reject) => {
|
|
50
55
|
timeoutReject = reject;
|
|
51
56
|
});
|
|
52
57
|
timeoutPromise.catch(() => { });
|
|
58
|
+
// Shared per-raw-event dispatch. Used by both the main read loop and the
|
|
59
|
+
// trailing-buffer flush so the translate → tool-dispatch → yield sequence
|
|
60
|
+
// lives in one place. `!turnComplete` guards dispatch so we don't approve
|
|
61
|
+
// or deny a tool call the CLI emits after it already said it's done.
|
|
62
|
+
const processRaw = async function* (raw) {
|
|
63
|
+
const translated = translator.translate(raw);
|
|
64
|
+
for (const event of translated) {
|
|
65
|
+
if (event.type === "tool_use" && !turnComplete && opts.toolHandler && opts.proc) {
|
|
66
|
+
await dispatchToolDecision(opts.proc, opts.toolHandler, event, opts.onWarning);
|
|
67
|
+
}
|
|
68
|
+
yield event;
|
|
69
|
+
if (event.type === "turn_complete") {
|
|
70
|
+
turnComplete = true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
53
74
|
const resetReadTimeout = () => {
|
|
54
75
|
if (timeoutId) {
|
|
55
76
|
clearTimeout(timeoutId);
|
|
@@ -62,13 +83,8 @@ export async function* readNdjsonEvents(opts) {
|
|
|
62
83
|
? () => {
|
|
63
84
|
abortReject?.(new AbortError());
|
|
64
85
|
if (opts.proc) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
catch {
|
|
69
|
-
// stdin closed
|
|
70
|
-
}
|
|
71
|
-
opts.proc.kill();
|
|
86
|
+
safeWrite(opts.proc, writer.abort());
|
|
87
|
+
safeKill(opts.proc);
|
|
72
88
|
}
|
|
73
89
|
}
|
|
74
90
|
: undefined;
|
|
@@ -90,8 +106,12 @@ export async function* readNdjsonEvents(opts) {
|
|
|
90
106
|
break;
|
|
91
107
|
}
|
|
92
108
|
buffer += decoder.decode(value, { stream: true });
|
|
109
|
+
// The limit applies to the accumulated buffer (which contains at most
|
|
110
|
+
// one in-progress line plus any already-split lines being held), so
|
|
111
|
+
// a single oversize line trips the same guard. Name is legacy -- the
|
|
112
|
+
// check is effectively "no NDJSON message may grow past this size".
|
|
93
113
|
if (buffer.length > LIMITS.ndjsonMaxLineChars) {
|
|
94
|
-
throw new ClaudeError(`NDJSON buffer exceeded ${LIMITS.ndjsonMaxLineChars} chars`);
|
|
114
|
+
throw new ClaudeError(`NDJSON buffer exceeded ${LIMITS.ndjsonMaxLineChars} chars (single line or accumulated pending lines)`);
|
|
95
115
|
}
|
|
96
116
|
const lines = buffer.split("\n");
|
|
97
117
|
buffer = lines.pop() ?? "";
|
|
@@ -100,16 +120,7 @@ export async function* readNdjsonEvents(opts) {
|
|
|
100
120
|
if (!raw) {
|
|
101
121
|
continue;
|
|
102
122
|
}
|
|
103
|
-
|
|
104
|
-
for (const event of events) {
|
|
105
|
-
if (event.type === "tool_use" && opts.toolHandler && opts.proc) {
|
|
106
|
-
await dispatchToolDecision(opts.proc, opts.toolHandler, event);
|
|
107
|
-
}
|
|
108
|
-
yield event;
|
|
109
|
-
if (event.type === "turn_complete") {
|
|
110
|
-
turnComplete = true;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
123
|
+
yield* processRaw(raw);
|
|
113
124
|
}
|
|
114
125
|
if (turnComplete) {
|
|
115
126
|
break;
|
|
@@ -118,16 +129,7 @@ export async function* readNdjsonEvents(opts) {
|
|
|
118
129
|
if (buffer.trim()) {
|
|
119
130
|
const raw = parseLine(buffer);
|
|
120
131
|
if (raw) {
|
|
121
|
-
|
|
122
|
-
for (const event of events) {
|
|
123
|
-
if (event.type === "tool_use" && opts.toolHandler && opts.proc && !turnComplete) {
|
|
124
|
-
await dispatchToolDecision(opts.proc, opts.toolHandler, event);
|
|
125
|
-
}
|
|
126
|
-
yield event;
|
|
127
|
-
if (event.type === "turn_complete") {
|
|
128
|
-
turnComplete = true;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
132
|
+
yield* processRaw(raw);
|
|
131
133
|
}
|
|
132
134
|
}
|
|
133
135
|
}
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
interface IRawProcess {
|
|
2
2
|
stdin: {
|
|
3
3
|
write: (data: string) => void;
|
|
4
4
|
end: () => void;
|
|
5
5
|
};
|
|
6
6
|
stdout: ReadableStream<Uint8Array>;
|
|
7
7
|
stderr: ReadableStream<Uint8Array>;
|
|
8
|
-
kill: () => void;
|
|
8
|
+
kill: (signal?: NodeJS.Signals | number) => void;
|
|
9
9
|
exited: Promise<number>;
|
|
10
10
|
pid: number;
|
|
11
11
|
}
|
|
12
|
-
|
|
12
|
+
interface ISpawnOpts {
|
|
13
13
|
cwd?: string;
|
|
14
14
|
env?: Record<string, string | undefined>;
|
|
15
15
|
}
|
|
16
16
|
export declare const spawnProcess: (args: string[], opts: ISpawnOpts) => IRawProcess;
|
|
17
17
|
export declare const whichSync: (name: string) => string | undefined;
|
|
18
|
-
export declare const
|
|
18
|
+
export declare const isExecutableNonEmpty: (path: string) => boolean;
|
|
19
|
+
export {};
|
package/dist/runtime.js
CHANGED
|
@@ -30,7 +30,10 @@ export const whichSync = (name) => {
|
|
|
30
30
|
return undefined;
|
|
31
31
|
}
|
|
32
32
|
};
|
|
33
|
-
|
|
33
|
+
// Used to vet candidate `claude` binary paths -- a zero-byte stub or a
|
|
34
|
+
// non-executable regular file both count as "not a usable binary" here.
|
|
35
|
+
// Name reflects behavior: this is NOT a generic fs.exists check.
|
|
36
|
+
export const isExecutableNonEmpty = (path) => {
|
|
34
37
|
try {
|
|
35
38
|
accessSync(path, constants.X_OK);
|
|
36
39
|
return statSync(path).size > 0;
|
|
@@ -58,8 +61,8 @@ const spawnBun = (args, opts) => {
|
|
|
58
61
|
},
|
|
59
62
|
stdout: proc.stdout,
|
|
60
63
|
stderr: proc.stderr,
|
|
61
|
-
kill: () => {
|
|
62
|
-
proc.kill();
|
|
64
|
+
kill: (signal) => {
|
|
65
|
+
proc.kill(signal);
|
|
63
66
|
},
|
|
64
67
|
exited: proc.exited,
|
|
65
68
|
pid: proc.pid,
|
|
@@ -99,8 +102,8 @@ const spawnNode = (args, opts) => {
|
|
|
99
102
|
},
|
|
100
103
|
stdout: child.stdout ? toWeb(child.stdout) : new ReadableStream(),
|
|
101
104
|
stderr: child.stderr ? toWeb(child.stderr) : new ReadableStream(),
|
|
102
|
-
kill: () => {
|
|
103
|
-
child.kill();
|
|
105
|
+
kill: (signal) => {
|
|
106
|
+
child.kill(signal);
|
|
104
107
|
},
|
|
105
108
|
exited,
|
|
106
109
|
pid: child.pid,
|
package/dist/session.d.ts
CHANGED
|
@@ -1,8 +1,37 @@
|
|
|
1
|
-
import type { ISessionOptions } from "./types/options.js";
|
|
1
|
+
import type { IAskOptions, ISessionOptions } from "./types/options.js";
|
|
2
2
|
import type { TAskResult } from "./types/results.js";
|
|
3
3
|
export interface IClaudeSession extends AsyncDisposable {
|
|
4
|
-
ask: (prompt: string) => Promise<TAskResult>;
|
|
4
|
+
ask: (prompt: string, options?: IAskOptions) => Promise<TAskResult>;
|
|
5
|
+
askJson: <T>(prompt: string, schema: import("./json.js").TSchemaInput<T>, options?: IAskOptions) => Promise<import("./json.js").IJsonResult<T>>;
|
|
5
6
|
close: () => Promise<void>;
|
|
6
7
|
sessionId: string | undefined;
|
|
7
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Creates a multi-turn Claude session backed by a single long-lived CLI
|
|
11
|
+
* process. Each `ask()` sends a user prompt and resolves with `TAskResult`
|
|
12
|
+
* for that turn. Calls are serialized -- a second `ask()` waits for the
|
|
13
|
+
* first to complete. Use `close()` (or `await using`) to free the process.
|
|
14
|
+
*
|
|
15
|
+
* ### Retry behavior
|
|
16
|
+
* Each `ask()` automatically retries transient failures -- process crashes
|
|
17
|
+
* matching SIGKILL/SIGTERM/SIGPIPE exit codes, `ECONNRESET`, `ECONNREFUSED`,
|
|
18
|
+
* `ETIMEDOUT`, `EHOSTUNREACH`, `ENETUNREACH`, `EAI_AGAIN`, Anthropic
|
|
19
|
+
* `overloaded_error` / 529s, broken-pipe / "socket hang up" messages, etc.
|
|
20
|
+
* (see `isTransientError`). Backoff is `500ms → 1s → 2s`; the budget is
|
|
21
|
+
* `LIMITS.maxRespawnAttempts` (currently 3) and is shared across a single
|
|
22
|
+
* `ask()`. When the budget is exhausted the session throws
|
|
23
|
+
* `KnownError("retry-exhausted")` and marks itself closed.
|
|
24
|
+
*
|
|
25
|
+
* Fatal errors -- `KnownError` and `BudgetExceededError` -- also close the
|
|
26
|
+
* session. Any subsequent `ask()` on a closed session rejects with
|
|
27
|
+
* `ClaudeError("Session is closed")`. All other errors (abort, timeout,
|
|
28
|
+
* non-transient `ProcessError`) propagate without closing, and the caller
|
|
29
|
+
* may decide whether to retry at a higher level.
|
|
30
|
+
*
|
|
31
|
+
* ### Observability
|
|
32
|
+
* - `onCostUpdate(snapshot)` -- fires after every `turn_complete`.
|
|
33
|
+
* - `onRetry(attempt, error)` -- fires each time a transient failure triggers
|
|
34
|
+
* a respawn inside one `ask()`. Attempt is 1-indexed.
|
|
35
|
+
* - `onWarning(message, cause)` -- routes all library-emitted warnings.
|
|
36
|
+
*/
|
|
8
37
|
export declare const createSession: (options?: ISessionOptions) => IClaudeSession;
|