@pivanov/claude-wire 0.0.2 → 0.0.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.
@@ -1,20 +1,33 @@
1
1
  import type { TToolDecision } from "../tools/handler.js";
2
+ import type { TBuiltInToolName } from "../tools/registry.js";
2
3
  import type { TToolUseEvent } from "./events.js";
3
4
  import type { TCostSnapshot } from "./results.js";
5
+ export type TToolName = TBuiltInToolName | (string & {});
4
6
  export interface IToolHandler {
5
- allowed?: string[];
6
- blocked?: string[];
7
+ allowed?: TToolName[];
8
+ blocked?: TToolName[];
7
9
  onToolUse?: (tool: TToolUseEvent) => Promise<TToolDecision>;
10
+ onError?: (error: unknown, tool: TToolUseEvent) => TToolDecision | Promise<TToolDecision>;
8
11
  }
9
12
  export interface IClaudeOptions {
10
13
  cwd?: string;
11
14
  model?: "opus" | "sonnet" | "haiku" | (string & {});
12
15
  systemPrompt?: string;
13
16
  appendSystemPrompt?: string;
14
- allowedTools?: string[];
15
- disallowedTools?: string[];
17
+ allowedTools?: TToolName[];
18
+ disallowedTools?: TToolName[];
16
19
  tools?: IToolHandler;
20
+ /**
21
+ * SDK-side budget limit, evaluated after each turn. Throws `BudgetExceededError`
22
+ * and kills the process when `total_cost_usd` exceeds this value. `0` means
23
+ * "disallow any spend" (useful for tests).
24
+ */
17
25
  maxCostUsd?: number;
26
+ /**
27
+ * CLI-level budget forwarded as `--max-budget-usd`. Enforced by the Claude
28
+ * binary itself, independent of {@link IClaudeOptions.maxCostUsd}. Either
29
+ * can fire first; set both for belt-and-suspenders.
30
+ */
18
31
  maxBudgetUsd?: number;
19
32
  onCostUpdate?: (cost: TCostSnapshot) => void;
20
33
  signal?: AbortSignal;
@@ -34,8 +47,43 @@ export interface IClaudeOptions {
34
47
  forkSession?: boolean;
35
48
  noSessionPersistence?: boolean;
36
49
  sessionId?: string;
37
- settingSources?: string;
50
+ settingSources?: "project" | "user" | "local" | "all" | "" | (string & {});
38
51
  disableSlashCommands?: boolean;
52
+ /**
53
+ * Called for every library-emitted warning (user-callback threw, malformed
54
+ * tool decision, etc.). Set this to route warnings to your telemetry or
55
+ * silence them with `() => {}`. When omitted, warnings go to `console.warn`
56
+ * prefixed with `[claude-wire]`.
57
+ */
58
+ onWarning?: (message: string, cause?: unknown) => void;
39
59
  }
40
60
  export interface ISessionOptions extends IClaudeOptions {
61
+ /**
62
+ * Fires each time a transient failure triggers a respawn inside a single
63
+ * `ask()`. `attempt` is 1-indexed. The error is the one that caused the
64
+ * retry (e.g. `ProcessError` with a SIGKILL exit code). Use this to
65
+ * surface retry activity in UI/telemetry; the SDK still handles the retry.
66
+ *
67
+ * Can also be passed per-ask via `session.ask(prompt, { onRetry })` for
68
+ * request-scoped correlation. Both fire if both are set.
69
+ */
70
+ onRetry?: (attempt: number, error: unknown) => void;
71
+ }
72
+ /**
73
+ * Per-ask options passed to `session.ask(prompt, options?)`. Override or
74
+ * supplement session-level callbacks for a single call -- useful for
75
+ * request-scoped logging/correlation in daemon-style consumers.
76
+ */
77
+ export interface IAskOptions {
78
+ /**
79
+ * Per-ask retry observer. Fires alongside the session-level `onRetry` when
80
+ * both are set, so callers can attach request-scoped context (request id,
81
+ * trace span, user id) without reaching outside the callback.
82
+ */
83
+ onRetry?: (attempt: number, error: unknown) => void;
84
+ /**
85
+ * Per-ask abort signal. Aborts this ask only (the session stays alive).
86
+ * Composes with the session-level `signal` -- either firing aborts the ask.
87
+ */
88
+ signal?: AbortSignal;
41
89
  }
@@ -3,7 +3,7 @@ export type TModelUsageEntry = {
3
3
  outputTokens: number;
4
4
  cacheReadInputTokens?: number;
5
5
  cacheCreationInputTokens?: number;
6
- contextWindow: number;
6
+ contextWindow?: number;
7
7
  };
8
8
  export type TClaudeContentType = "text" | "thinking" | "tool_use" | "tool_result" | (string & {});
9
9
  export type TClaudeContent = {
@@ -32,11 +32,7 @@ export type TClaudeEvent = {
32
32
  model?: string;
33
33
  tools?: string[];
34
34
  duration_ms?: number;
35
- duration_api_ms?: number;
36
- cost_usd?: number;
37
35
  total_cost_usd?: number;
38
36
  is_error?: boolean;
39
- num_turns?: number;
40
37
  modelUsage?: Record<string, TModelUsageEntry>;
41
- usage?: unknown;
42
38
  };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Rejects non-finite values and negatives. Zero is intentionally allowed
3
+ * so callers can express "disallow any spend" (useful in tests).
4
+ */
5
+ export declare const assertPositiveNumber: (value: number | undefined, name: string) => void;
6
+ /**
7
+ * Rejects empty strings. Used on writer payloads where an empty value
8
+ * would produce a malformed JSON line on the CLI's stdin.
9
+ */
10
+ export declare const requireNonEmpty: (value: string, name: string) => void;
@@ -0,0 +1,23 @@
1
+ import { ClaudeError } from "./errors.js";
2
+ // Boundary validators -- throw ClaudeError with a stable message shape so
3
+ // callers (SDK tests, consumer apps) can pattern-match on field name.
4
+ // Both helpers are cheap; performance-sensitive paths should still prefer
5
+ // type-level guards, but runtime checks catch undeclared-undefined drift.
6
+ /**
7
+ * Rejects non-finite values and negatives. Zero is intentionally allowed
8
+ * so callers can express "disallow any spend" (useful in tests).
9
+ */
10
+ export const assertPositiveNumber = (value, name) => {
11
+ if (value !== undefined && (!Number.isFinite(value) || value < 0)) {
12
+ throw new ClaudeError(`${name} must be a finite non-negative number`);
13
+ }
14
+ };
15
+ /**
16
+ * Rejects empty strings. Used on writer payloads where an empty value
17
+ * would produce a malformed JSON line on the CLI's stdin.
18
+ */
19
+ export const requireNonEmpty = (value, name) => {
20
+ if (!value) {
21
+ throw new ClaudeError(`${name} must be a non-empty string`);
22
+ }
23
+ };
@@ -0,0 +1,2 @@
1
+ export type TWarn = (message: string, cause?: unknown) => void;
2
+ export declare const createWarn: (onWarning?: TWarn) => TWarn;
@@ -0,0 +1,24 @@
1
+ // One-line library-warning emitter. Consumers set `onWarning` on
2
+ // IClaudeOptions to route warnings anywhere; when unset we fall back to
3
+ // `console.warn` so behavior is unchanged for casual users.
4
+ const DEFAULT = (message, cause) => {
5
+ if (cause === undefined) {
6
+ console.warn(`[claude-wire] ${message}`);
7
+ }
8
+ else {
9
+ console.warn(`[claude-wire] ${message}`, cause);
10
+ }
11
+ };
12
+ export const createWarn = (onWarning) => {
13
+ return onWarning
14
+ ? (message, cause) => {
15
+ try {
16
+ onWarning(message, cause);
17
+ }
18
+ catch {
19
+ // A user hook that itself throws shouldn't take down the stream.
20
+ DEFAULT(message, cause);
21
+ }
22
+ }
23
+ : DEFAULT;
24
+ };
package/dist/writer.d.ts CHANGED
@@ -2,6 +2,15 @@ export declare const writer: {
2
2
  user: (content: string) => string;
3
3
  approve: (toolUseId: string) => string;
4
4
  deny: (toolUseId: string) => string;
5
- toolResult: (toolUseId: string, content: string) => string;
5
+ /**
6
+ * Send a tool result in response to a `tool_use` event. Pass
7
+ * `{ isError: true }` to mark the result as a tool-side error -- the model
8
+ * will see it as an error and can react (retry, apologize, pick another
9
+ * tool) rather than treating it as success. The protocol supports the
10
+ * flag natively; without it, results are assumed successful.
11
+ */
12
+ toolResult: (toolUseId: string, content: string, options?: {
13
+ isError?: boolean;
14
+ }) => string;
6
15
  abort: () => string;
7
16
  };
package/dist/writer.js CHANGED
@@ -1,10 +1,5 @@
1
- import { ClaudeError } from "./errors.js";
1
+ import { requireNonEmpty } from "./validation.js";
2
2
  const ABORT_LINE = `${JSON.stringify({ type: "abort" })}\n`;
3
- const requireNonEmpty = (value, name) => {
4
- if (!value) {
5
- throw new ClaudeError(`${name} must be a non-empty string`);
6
- }
7
- };
8
3
  export const writer = {
9
4
  user: (content) => {
10
5
  requireNonEmpty(content, "content");
@@ -18,9 +13,20 @@ export const writer = {
18
13
  requireNonEmpty(toolUseId, "toolUseId");
19
14
  return `${JSON.stringify({ type: "deny", tool_use_id: toolUseId })}\n`;
20
15
  },
21
- toolResult: (toolUseId, content) => {
16
+ /**
17
+ * Send a tool result in response to a `tool_use` event. Pass
18
+ * `{ isError: true }` to mark the result as a tool-side error -- the model
19
+ * will see it as an error and can react (retry, apologize, pick another
20
+ * tool) rather than treating it as success. The protocol supports the
21
+ * flag natively; without it, results are assumed successful.
22
+ */
23
+ toolResult: (toolUseId, content, options) => {
22
24
  requireNonEmpty(toolUseId, "toolUseId");
23
- return `${JSON.stringify({ type: "tool_result", tool_use_id: toolUseId, content })}\n`;
25
+ const payload = { type: "tool_result", tool_use_id: toolUseId, content };
26
+ if (options?.isError) {
27
+ payload.is_error = true;
28
+ }
29
+ return `${JSON.stringify(payload)}\n`;
24
30
  },
25
31
  abort: () => ABORT_LINE,
26
32
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pivanov/claude-wire",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Run Claude Code programmatically. Typed SDK for spawning, streaming, and controlling the CLI.",
5
5
  "type": "module",
6
6
  "engines": {