@pivanov/claude-wire 0.0.2
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/LICENSE +21 -0
- package/README.md +78 -0
- package/dist/client.d.ts +11 -0
- package/dist/client.js +28 -0
- package/dist/constants.d.ts +13 -0
- package/dist/constants.js +15 -0
- package/dist/cost.d.ts +12 -0
- package/dist/cost.js +36 -0
- package/dist/errors.d.ts +29 -0
- package/dist/errors.js +68 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +16 -0
- package/dist/parser/content.d.ts +4 -0
- package/dist/parser/content.js +46 -0
- package/dist/parser/ndjson.d.ts +2 -0
- package/dist/parser/ndjson.js +12 -0
- package/dist/parser/translator.d.ts +7 -0
- package/dist/parser/translator.js +127 -0
- package/dist/pipeline.d.ts +8 -0
- package/dist/pipeline.js +47 -0
- package/dist/process.d.ts +15 -0
- package/dist/process.js +179 -0
- package/dist/reader.d.ts +13 -0
- package/dist/reader.js +94 -0
- package/dist/runtime.d.ts +18 -0
- package/dist/runtime.js +136 -0
- package/dist/session.d.ts +8 -0
- package/dist/session.js +239 -0
- package/dist/stream.d.ts +9 -0
- package/dist/stream.js +130 -0
- package/dist/tools/handler.d.ts +9 -0
- package/dist/tools/handler.js +18 -0
- package/dist/tools/registry.d.ts +2 -0
- package/dist/tools/registry.js +42 -0
- package/dist/types/events.d.ts +41 -0
- package/dist/types/events.js +1 -0
- package/dist/types/options.d.ts +41 -0
- package/dist/types/options.js +1 -0
- package/dist/types/protocol.d.ts +42 -0
- package/dist/types/protocol.js +1 -0
- package/dist/types/results.d.ts +17 -0
- package/dist/types/results.js +1 -0
- package/dist/writer.d.ts +7 -0
- package/dist/writer.js +26 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Pavel Ivanov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# claude-wire
|
|
2
|
+
|
|
3
|
+
Run [Claude Code](https://claude.ai/download) programmatically from TypeScript.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@pivanov/claude-wire)
|
|
6
|
+
[](https://bundlephobia.com/package/@pivanov/claude-wire)
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { claude } from "@pivanov/claude-wire";
|
|
11
|
+
|
|
12
|
+
const result = await claude.ask("Fix the bug in main.ts", {
|
|
13
|
+
model: "haiku",
|
|
14
|
+
maxCostUsd: 0.50,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
console.log(result.text); // "Fixed the undefined variable..."
|
|
18
|
+
console.log(result.costUsd); // 0.0084
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
- **Simple API** - `claude.ask()` returns a typed result, `claude.stream()` yields events
|
|
24
|
+
- **Tool control** - allow, block, or intercept any tool at runtime
|
|
25
|
+
- **Multi-turn sessions** - persistent process across multiple prompts
|
|
26
|
+
- **Cost tracking** - per-request budgets with auto-abort
|
|
27
|
+
- **Fully typed** - discriminated union events, full IntelliSense
|
|
28
|
+
- **Resilient** - auto-respawn, transient error detection, AbortSignal
|
|
29
|
+
- **Zero dependencies** - 13 kB gzipped
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
bun add @pivanov/claude-wire
|
|
35
|
+
# or
|
|
36
|
+
npm install @pivanov/claude-wire
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Requires [Claude Code CLI](https://claude.ai/download) installed and authenticated. Runs on [Bun](https://bun.sh) >= 1.0 or Node.js >= 22.
|
|
40
|
+
|
|
41
|
+
> This SDK wraps Claude Code's `--output-format stream-json` protocol, which is not officially documented by Anthropic and may change between releases.
|
|
42
|
+
|
|
43
|
+
## Documentation
|
|
44
|
+
|
|
45
|
+
Full docs, API reference, and protocol guide at **[pivanov.github.io/claude-wire](https://pivanov.github.io/claude-wire/)**
|
|
46
|
+
|
|
47
|
+
## Try the Examples
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
git clone https://github.com/pivanov/claude-wire
|
|
51
|
+
cd claude-wire && bun install
|
|
52
|
+
bun run examples
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Interactive menu with 8 runnable demos covering ask, streaming, sessions, tool control, cost budgets, abort, system prompts, and session resume.
|
|
56
|
+
|
|
57
|
+
## Project Structure
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
packages/claude-wire/ the npm package
|
|
61
|
+
apps/docs/ VitePress documentation site
|
|
62
|
+
apps/examples/ interactive example runner
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Development
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
bun install
|
|
69
|
+
bun run test # 147 tests
|
|
70
|
+
bun run typecheck
|
|
71
|
+
bun run lint
|
|
72
|
+
bun run docs:dev # local docs server
|
|
73
|
+
bun run examples # try the examples
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## License
|
|
77
|
+
|
|
78
|
+
MIT
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { IClaudeSession } from "./session.js";
|
|
2
|
+
import type { IClaudeStream } from "./stream.js";
|
|
3
|
+
import type { IClaudeOptions, ISessionOptions } from "./types/options.js";
|
|
4
|
+
import type { TAskResult } from "./types/results.js";
|
|
5
|
+
export interface IClaudeClient {
|
|
6
|
+
ask: (prompt: string, options?: IClaudeOptions) => Promise<TAskResult>;
|
|
7
|
+
stream: (prompt: string, options?: IClaudeOptions) => IClaudeStream;
|
|
8
|
+
session: (options?: ISessionOptions) => IClaudeSession;
|
|
9
|
+
create: (defaults: IClaudeOptions) => IClaudeClient;
|
|
10
|
+
}
|
|
11
|
+
export declare const createClient: (defaults?: IClaudeOptions) => IClaudeClient;
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createSession } from "./session.js";
|
|
2
|
+
import { createStream } from "./stream.js";
|
|
3
|
+
const mergeOptions = (defaults, overrides) => ({
|
|
4
|
+
...defaults,
|
|
5
|
+
...overrides,
|
|
6
|
+
tools: overrides && "tools" in overrides ? (overrides.tools ? { ...defaults.tools, ...overrides.tools } : overrides.tools) : defaults.tools,
|
|
7
|
+
env: overrides && "env" in overrides ? (overrides.env ? { ...defaults.env, ...overrides.env } : overrides.env) : defaults.env,
|
|
8
|
+
});
|
|
9
|
+
export const createClient = (defaults = {}) => {
|
|
10
|
+
const ask = async (prompt, options) => {
|
|
11
|
+
const merged = mergeOptions(defaults, options);
|
|
12
|
+
const stream = createStream(prompt, merged);
|
|
13
|
+
return stream.result();
|
|
14
|
+
};
|
|
15
|
+
const stream = (prompt, options) => {
|
|
16
|
+
const merged = mergeOptions(defaults, options);
|
|
17
|
+
return createStream(prompt, merged);
|
|
18
|
+
};
|
|
19
|
+
const session = (options) => {
|
|
20
|
+
const merged = mergeOptions(defaults, options);
|
|
21
|
+
return createSession(merged);
|
|
22
|
+
};
|
|
23
|
+
const create = (newDefaults) => {
|
|
24
|
+
const merged = mergeOptions(defaults, newDefaults);
|
|
25
|
+
return createClient(merged);
|
|
26
|
+
};
|
|
27
|
+
return { ask, stream, session, create };
|
|
28
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const TIMEOUTS: {
|
|
2
|
+
readonly defaultAbortMs: 300000;
|
|
3
|
+
readonly gracefulExitMs: 5000;
|
|
4
|
+
};
|
|
5
|
+
export declare const LIMITS: {
|
|
6
|
+
readonly maxRespawnAttempts: 3;
|
|
7
|
+
readonly sessionMaxTurnsBeforeRecycle: 100;
|
|
8
|
+
readonly ndjsonMaxLineChars: number;
|
|
9
|
+
};
|
|
10
|
+
export declare const BINARY: {
|
|
11
|
+
readonly name: "claude";
|
|
12
|
+
readonly commonPaths: readonly [`${string}/.local/bin/claude`, `${string}/.claude/bin/claude`, "/usr/local/bin/claude", "/opt/homebrew/bin/claude"];
|
|
13
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
export const TIMEOUTS = {
|
|
3
|
+
defaultAbortMs: 300_000,
|
|
4
|
+
gracefulExitMs: 5_000,
|
|
5
|
+
};
|
|
6
|
+
export const LIMITS = {
|
|
7
|
+
maxRespawnAttempts: 3,
|
|
8
|
+
sessionMaxTurnsBeforeRecycle: 100,
|
|
9
|
+
ndjsonMaxLineChars: 10 * 1024 * 1024,
|
|
10
|
+
};
|
|
11
|
+
const home = homedir();
|
|
12
|
+
export const BINARY = {
|
|
13
|
+
name: "claude",
|
|
14
|
+
commonPaths: [`${home}/.local/bin/claude`, `${home}/.claude/bin/claude`, "/usr/local/bin/claude", "/opt/homebrew/bin/claude"],
|
|
15
|
+
};
|
package/dist/cost.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TCostSnapshot } from "./types/results.js";
|
|
2
|
+
export interface ICostTracker {
|
|
3
|
+
update: (totalCostUsd: number, totalInputTokens: number, totalOutputTokens: number) => void;
|
|
4
|
+
snapshot: () => TCostSnapshot;
|
|
5
|
+
checkBudget: () => void;
|
|
6
|
+
reset: () => void;
|
|
7
|
+
}
|
|
8
|
+
export interface ICostTrackerOptions {
|
|
9
|
+
maxCostUsd?: number;
|
|
10
|
+
onCostUpdate?: (cost: TCostSnapshot) => void;
|
|
11
|
+
}
|
|
12
|
+
export declare const createCostTracker: (options?: ICostTrackerOptions) => ICostTracker;
|
package/dist/cost.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { assertPositiveNumber, BudgetExceededError } from "./errors.js";
|
|
2
|
+
export const createCostTracker = (options = {}) => {
|
|
3
|
+
assertPositiveNumber(options.maxCostUsd, "maxCostUsd");
|
|
4
|
+
let totalUsd = 0;
|
|
5
|
+
let inputTokens = 0;
|
|
6
|
+
let outputTokens = 0;
|
|
7
|
+
const snapshot = () => ({
|
|
8
|
+
totalUsd,
|
|
9
|
+
inputTokens,
|
|
10
|
+
outputTokens,
|
|
11
|
+
});
|
|
12
|
+
const update = (totalCostUsd, totalInputToks, totalOutputToks) => {
|
|
13
|
+
totalUsd = totalCostUsd;
|
|
14
|
+
inputTokens = totalInputToks;
|
|
15
|
+
outputTokens = totalOutputToks;
|
|
16
|
+
if (options.onCostUpdate) {
|
|
17
|
+
try {
|
|
18
|
+
options.onCostUpdate(snapshot());
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// user callback error - don't crash the stream/session
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
const checkBudget = () => {
|
|
26
|
+
if (options.maxCostUsd !== undefined && totalUsd > options.maxCostUsd) {
|
|
27
|
+
throw new BudgetExceededError(totalUsd, options.maxCostUsd);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const reset = () => {
|
|
31
|
+
totalUsd = 0;
|
|
32
|
+
inputTokens = 0;
|
|
33
|
+
outputTokens = 0;
|
|
34
|
+
};
|
|
35
|
+
return { update, snapshot, checkBudget, reset };
|
|
36
|
+
};
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export declare class ClaudeError extends Error {
|
|
2
|
+
constructor(message: string);
|
|
3
|
+
}
|
|
4
|
+
export declare class BudgetExceededError extends ClaudeError {
|
|
5
|
+
readonly spent: number;
|
|
6
|
+
readonly budget: number;
|
|
7
|
+
constructor(spent: number, budget: number);
|
|
8
|
+
}
|
|
9
|
+
export declare class AbortError extends ClaudeError {
|
|
10
|
+
constructor(message?: string);
|
|
11
|
+
}
|
|
12
|
+
export declare class TimeoutError extends ClaudeError {
|
|
13
|
+
constructor(message?: string);
|
|
14
|
+
}
|
|
15
|
+
export declare class ProcessError extends ClaudeError {
|
|
16
|
+
readonly exitCode?: number | undefined;
|
|
17
|
+
constructor(message: string, exitCode?: number | undefined);
|
|
18
|
+
}
|
|
19
|
+
declare const KNOWN_ERROR_CODES: readonly ["not-authenticated", "binary-not-found", "session-expired", "permission-denied", "invalid-model"];
|
|
20
|
+
type TKnownErrorCode = (typeof KNOWN_ERROR_CODES)[number];
|
|
21
|
+
export declare class KnownError extends ClaudeError {
|
|
22
|
+
readonly code: TKnownErrorCode;
|
|
23
|
+
constructor(code: TKnownErrorCode, message?: string);
|
|
24
|
+
}
|
|
25
|
+
export declare const isKnownError: (error: unknown) => error is KnownError;
|
|
26
|
+
export declare const isTransientError: (error: unknown) => boolean;
|
|
27
|
+
export declare const errorMessage: (error: unknown) => string;
|
|
28
|
+
export declare const assertPositiveNumber: (value: number | undefined, name: string) => void;
|
|
29
|
+
export {};
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export class ClaudeError extends Error {
|
|
2
|
+
constructor(message) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = "ClaudeError";
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export class BudgetExceededError extends ClaudeError {
|
|
8
|
+
spent;
|
|
9
|
+
budget;
|
|
10
|
+
constructor(spent, budget) {
|
|
11
|
+
super(`Budget exceeded: $${spent.toFixed(4)} spent, $${budget.toFixed(4)} limit`);
|
|
12
|
+
this.spent = spent;
|
|
13
|
+
this.budget = budget;
|
|
14
|
+
this.name = "BudgetExceededError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export class AbortError extends ClaudeError {
|
|
18
|
+
constructor(message = "Operation aborted") {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = "AbortError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export class TimeoutError extends ClaudeError {
|
|
24
|
+
constructor(message = "Operation timed out") {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = "TimeoutError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export class ProcessError extends ClaudeError {
|
|
30
|
+
exitCode;
|
|
31
|
+
constructor(message, exitCode) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.exitCode = exitCode;
|
|
34
|
+
this.name = "ProcessError";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const KNOWN_ERROR_CODES = ["not-authenticated", "binary-not-found", "session-expired", "permission-denied", "invalid-model"];
|
|
38
|
+
export class KnownError extends ClaudeError {
|
|
39
|
+
code;
|
|
40
|
+
constructor(code, message) {
|
|
41
|
+
super(message ?? code);
|
|
42
|
+
this.code = code;
|
|
43
|
+
this.name = "KnownError";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export const isKnownError = (error) => {
|
|
47
|
+
return error instanceof KnownError;
|
|
48
|
+
};
|
|
49
|
+
const TRANSIENT_PATTERN = /fetch failed|ECONNREFUSED|ETIMEDOUT|ECONNRESET|EAI_AGAIN|network error|network timeout|EPIPE|SIGPIPE|broken pipe/i;
|
|
50
|
+
export const isTransientError = (error) => {
|
|
51
|
+
if (error instanceof AbortError || error instanceof BudgetExceededError) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
if (error instanceof ProcessError) {
|
|
55
|
+
const transientCodes = [137, 143];
|
|
56
|
+
return error.exitCode !== undefined && transientCodes.includes(error.exitCode);
|
|
57
|
+
}
|
|
58
|
+
const message = errorMessage(error);
|
|
59
|
+
return TRANSIENT_PATTERN.test(message);
|
|
60
|
+
};
|
|
61
|
+
export const errorMessage = (error) => {
|
|
62
|
+
return error instanceof Error ? error.message : String(error);
|
|
63
|
+
};
|
|
64
|
+
export const assertPositiveNumber = (value, name) => {
|
|
65
|
+
if (value !== undefined && (!Number.isFinite(value) || value <= 0)) {
|
|
66
|
+
throw new ClaudeError(`${name} must be a finite positive number`);
|
|
67
|
+
}
|
|
68
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type { IClaudeClient } from "./client.js";
|
|
2
|
+
export { createClient } from "./client.js";
|
|
3
|
+
export { BINARY, LIMITS, TIMEOUTS } from "./constants.js";
|
|
4
|
+
export type { ICostTracker, ICostTrackerOptions } from "./cost.js";
|
|
5
|
+
export { createCostTracker } from "./cost.js";
|
|
6
|
+
export { AbortError, assertPositiveNumber, BudgetExceededError, ClaudeError, errorMessage, isKnownError, isTransientError, KnownError, ProcessError, TimeoutError, } from "./errors.js";
|
|
7
|
+
export { blockFingerprint, extractContent, parseDoubleEncoded } from "./parser/content.js";
|
|
8
|
+
export { parseLine } from "./parser/ndjson.js";
|
|
9
|
+
export type { ITranslator } from "./parser/translator.js";
|
|
10
|
+
export { createTranslator } from "./parser/translator.js";
|
|
11
|
+
export type { IClaudeProcess, ISpawnOptions } from "./process.js";
|
|
12
|
+
export { buildArgs, resetBinaryCache, spawnClaude } from "./process.js";
|
|
13
|
+
export type { IReaderOptions } from "./reader.js";
|
|
14
|
+
export { readNdjsonEvents } from "./reader.js";
|
|
15
|
+
export type { IClaudeSession } from "./session.js";
|
|
16
|
+
export { createSession } from "./session.js";
|
|
17
|
+
export type { IClaudeStream } from "./stream.js";
|
|
18
|
+
export { createStream } from "./stream.js";
|
|
19
|
+
export type { IToolHandlerInstance, TToolDecision } from "./tools/handler.js";
|
|
20
|
+
export { createToolHandler } from "./tools/handler.js";
|
|
21
|
+
export { BUILT_IN_TOOLS, isBuiltInTool } from "./tools/registry.js";
|
|
22
|
+
export type { TErrorEvent, TRelayEvent, TSessionMetaEvent, TTextEvent, TThinkingEvent, TToolResultEvent, TToolUseEvent, TTurnCompleteEvent, } from "./types/events.js";
|
|
23
|
+
export type { IClaudeOptions, ISessionOptions, IToolHandler } from "./types/options.js";
|
|
24
|
+
export type { TClaudeContent, TClaudeContentType, TClaudeEvent, TClaudeEventType, TClaudeMessage, TModelUsageEntry } from "./types/protocol.js";
|
|
25
|
+
export type { TAskResult, TCostSnapshot } from "./types/results.js";
|
|
26
|
+
export { writer } from "./writer.js";
|
|
27
|
+
export declare const claude: import("./client.js").IClaudeClient;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { createClient } from "./client.js";
|
|
2
|
+
export { BINARY, LIMITS, TIMEOUTS } from "./constants.js";
|
|
3
|
+
export { createCostTracker } from "./cost.js";
|
|
4
|
+
export { AbortError, assertPositiveNumber, BudgetExceededError, ClaudeError, errorMessage, isKnownError, isTransientError, KnownError, ProcessError, TimeoutError, } from "./errors.js";
|
|
5
|
+
export { blockFingerprint, extractContent, parseDoubleEncoded } from "./parser/content.js";
|
|
6
|
+
export { parseLine } from "./parser/ndjson.js";
|
|
7
|
+
export { createTranslator } from "./parser/translator.js";
|
|
8
|
+
export { buildArgs, resetBinaryCache, spawnClaude } from "./process.js";
|
|
9
|
+
export { readNdjsonEvents } from "./reader.js";
|
|
10
|
+
export { createSession } from "./session.js";
|
|
11
|
+
export { createStream } from "./stream.js";
|
|
12
|
+
export { createToolHandler } from "./tools/handler.js";
|
|
13
|
+
export { BUILT_IN_TOOLS, isBuiltInTool } from "./tools/registry.js";
|
|
14
|
+
export { writer } from "./writer.js";
|
|
15
|
+
import { createClient } from "./client.js";
|
|
16
|
+
export const claude = createClient();
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { TClaudeContent } from "../types/protocol.js";
|
|
2
|
+
export declare const blockFingerprint: (block: TClaudeContent) => string;
|
|
3
|
+
export declare const extractContent: (content: unknown) => string;
|
|
4
|
+
export declare const parseDoubleEncoded: (value: unknown) => string;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export const blockFingerprint = (block) => {
|
|
2
|
+
if (block.type === "tool_use" && block.id) {
|
|
3
|
+
return `tool_use:${block.id}`;
|
|
4
|
+
}
|
|
5
|
+
const text = block.type === "thinking" ? (block.thinking ?? block.text ?? "") : (block.text ?? "");
|
|
6
|
+
if (text) {
|
|
7
|
+
return `${block.type}:${text.slice(0, 64)}`;
|
|
8
|
+
}
|
|
9
|
+
return `${block.type}:${block.tool_use_id ?? "unknown"}`;
|
|
10
|
+
};
|
|
11
|
+
export const extractContent = (content) => {
|
|
12
|
+
if (content === null || content === undefined) {
|
|
13
|
+
return "";
|
|
14
|
+
}
|
|
15
|
+
if (typeof content === "string") {
|
|
16
|
+
return content;
|
|
17
|
+
}
|
|
18
|
+
if (Array.isArray(content)) {
|
|
19
|
+
return content
|
|
20
|
+
.filter((block) => typeof block === "object" && block !== null && "text" in block)
|
|
21
|
+
.map((block) => block.text)
|
|
22
|
+
.join("\n");
|
|
23
|
+
}
|
|
24
|
+
return "";
|
|
25
|
+
};
|
|
26
|
+
export const parseDoubleEncoded = (value) => {
|
|
27
|
+
if (typeof value !== "string") {
|
|
28
|
+
if (typeof value === "object" && value !== null) {
|
|
29
|
+
return JSON.stringify(value);
|
|
30
|
+
}
|
|
31
|
+
return String(value ?? "");
|
|
32
|
+
}
|
|
33
|
+
if (!value.startsWith('"')) {
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const parsed = JSON.parse(value);
|
|
38
|
+
if (typeof parsed === "string") {
|
|
39
|
+
return parsed;
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { TRelayEvent } from "../types/events.js";
|
|
2
|
+
import type { TClaudeEvent } from "../types/protocol.js";
|
|
3
|
+
export interface ITranslator {
|
|
4
|
+
translate: (raw: TClaudeEvent) => TRelayEvent[];
|
|
5
|
+
reset: () => void;
|
|
6
|
+
}
|
|
7
|
+
export declare const createTranslator: () => ITranslator;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { blockFingerprint, extractContent, parseDoubleEncoded } from "./content.js";
|
|
2
|
+
const extractTokens = (modelUsage) => {
|
|
3
|
+
let inputTokens;
|
|
4
|
+
let outputTokens;
|
|
5
|
+
let contextWindow;
|
|
6
|
+
if (modelUsage) {
|
|
7
|
+
for (const entry of Object.values(modelUsage)) {
|
|
8
|
+
inputTokens = (inputTokens ?? 0) + entry.inputTokens + (entry.cacheReadInputTokens ?? 0) + (entry.cacheCreationInputTokens ?? 0);
|
|
9
|
+
outputTokens = (outputTokens ?? 0) + entry.outputTokens;
|
|
10
|
+
contextWindow = entry.contextWindow;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return { inputTokens, outputTokens, contextWindow };
|
|
14
|
+
};
|
|
15
|
+
const buildTurnComplete = (raw) => {
|
|
16
|
+
const { inputTokens, outputTokens, contextWindow } = extractTokens(raw.modelUsage);
|
|
17
|
+
return {
|
|
18
|
+
type: "turn_complete",
|
|
19
|
+
sessionId: raw.session_id,
|
|
20
|
+
costUsd: raw.total_cost_usd,
|
|
21
|
+
inputTokens,
|
|
22
|
+
outputTokens,
|
|
23
|
+
contextWindow,
|
|
24
|
+
durationMs: raw.duration_ms,
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
const translateContentBlock = (block) => {
|
|
28
|
+
switch (block.type) {
|
|
29
|
+
case "thinking": {
|
|
30
|
+
const content = block.thinking ?? block.text ?? "";
|
|
31
|
+
if (content) {
|
|
32
|
+
return { type: "thinking", content };
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
case "text": {
|
|
37
|
+
const content = block.text ?? "";
|
|
38
|
+
if (content) {
|
|
39
|
+
return { type: "text", content };
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
case "tool_use": {
|
|
44
|
+
if (!block.id) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
type: "tool_use",
|
|
49
|
+
toolUseId: block.id,
|
|
50
|
+
toolName: block.name ?? "",
|
|
51
|
+
input: typeof block.input === "string" ? block.input : JSON.stringify(block.input ?? {}),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
case "tool_result": {
|
|
55
|
+
return {
|
|
56
|
+
type: "tool_result",
|
|
57
|
+
toolUseId: block.tool_use_id ?? "",
|
|
58
|
+
output: extractContent(block.content),
|
|
59
|
+
isError: block.is_error ?? false,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
default:
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
export const createTranslator = () => {
|
|
67
|
+
let lastContentIndex = 0;
|
|
68
|
+
let lastFirstBlockKey;
|
|
69
|
+
const reset = () => {
|
|
70
|
+
lastContentIndex = 0;
|
|
71
|
+
lastFirstBlockKey = undefined;
|
|
72
|
+
};
|
|
73
|
+
const translate = (raw) => {
|
|
74
|
+
const events = [];
|
|
75
|
+
if (raw.type === "system" && raw.subtype === "init") {
|
|
76
|
+
events.push({
|
|
77
|
+
type: "session_meta",
|
|
78
|
+
sessionId: raw.session_id ?? "",
|
|
79
|
+
model: raw.model ?? "",
|
|
80
|
+
tools: raw.tools ?? [],
|
|
81
|
+
});
|
|
82
|
+
return events;
|
|
83
|
+
}
|
|
84
|
+
if (raw.type === "result" || (raw.type === "system" && raw.subtype === "result")) {
|
|
85
|
+
if (raw.is_error) {
|
|
86
|
+
const text = parseDoubleEncoded(raw.result);
|
|
87
|
+
events.push({ type: "error", message: text, sessionId: raw.session_id });
|
|
88
|
+
}
|
|
89
|
+
events.push(buildTurnComplete(raw));
|
|
90
|
+
reset();
|
|
91
|
+
return events;
|
|
92
|
+
}
|
|
93
|
+
if (raw.type === "assistant" && raw.message?.content) {
|
|
94
|
+
const content = raw.message.content;
|
|
95
|
+
if (content.length > 0) {
|
|
96
|
+
const firstBlock = content[0];
|
|
97
|
+
if (firstBlock) {
|
|
98
|
+
const key = blockFingerprint(firstBlock);
|
|
99
|
+
if (lastFirstBlockKey !== undefined && key !== lastFirstBlockKey) {
|
|
100
|
+
lastContentIndex = 0;
|
|
101
|
+
}
|
|
102
|
+
lastFirstBlockKey = key;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const newBlocks = content.slice(lastContentIndex);
|
|
106
|
+
lastContentIndex = content.length;
|
|
107
|
+
for (const block of newBlocks) {
|
|
108
|
+
const event = translateContentBlock(block);
|
|
109
|
+
if (event) {
|
|
110
|
+
events.push(event);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return events;
|
|
114
|
+
}
|
|
115
|
+
if (raw.type === "user" && raw.message?.content) {
|
|
116
|
+
for (const block of raw.message.content) {
|
|
117
|
+
const event = translateContentBlock(block);
|
|
118
|
+
if (event) {
|
|
119
|
+
events.push(event);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return events;
|
|
123
|
+
}
|
|
124
|
+
return events;
|
|
125
|
+
};
|
|
126
|
+
return { translate, reset };
|
|
127
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ICostTracker } from "./cost.js";
|
|
2
|
+
import type { IClaudeProcess } from "./process.js";
|
|
3
|
+
import type { IToolHandlerInstance } from "./tools/handler.js";
|
|
4
|
+
import type { TRelayEvent, TToolUseEvent } from "./types/events.js";
|
|
5
|
+
import type { TAskResult } from "./types/results.js";
|
|
6
|
+
export declare const dispatchToolDecision: (proc: IClaudeProcess, toolHandler: IToolHandlerInstance, event: TToolUseEvent) => Promise<void>;
|
|
7
|
+
export declare const extractText: (events: TRelayEvent[]) => string;
|
|
8
|
+
export declare const buildResult: (events: TRelayEvent[], costTracker: ICostTracker, sessionId: string | undefined) => TAskResult;
|
package/dist/pipeline.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { writer } from "./writer.js";
|
|
2
|
+
export const dispatchToolDecision = async (proc, toolHandler, event) => {
|
|
3
|
+
let decision;
|
|
4
|
+
try {
|
|
5
|
+
decision = await toolHandler.decide(event);
|
|
6
|
+
}
|
|
7
|
+
catch (error) {
|
|
8
|
+
console.warn(`[claude-wire] Tool handler threw, defaulting to deny: ${error instanceof Error ? error.message : String(error)}`);
|
|
9
|
+
decision = "deny";
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
if (decision === "approve") {
|
|
13
|
+
proc.write(writer.approve(event.toolUseId));
|
|
14
|
+
}
|
|
15
|
+
else if (decision === "deny") {
|
|
16
|
+
proc.write(writer.deny(event.toolUseId));
|
|
17
|
+
}
|
|
18
|
+
else if (typeof decision === "object" && decision !== null && typeof decision.result === "string") {
|
|
19
|
+
proc.write(writer.toolResult(event.toolUseId, decision.result));
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
console.warn("[claude-wire] Invalid tool decision, defaulting to deny");
|
|
23
|
+
proc.write(writer.deny(event.toolUseId));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// stdin closed - process died, error will surface through read path
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
export const extractText = (events) => {
|
|
31
|
+
return events
|
|
32
|
+
.filter((e) => e.type === "text")
|
|
33
|
+
.map((e) => e.content)
|
|
34
|
+
.join("");
|
|
35
|
+
};
|
|
36
|
+
export const buildResult = (events, costTracker, sessionId) => {
|
|
37
|
+
const tc = events.findLast((e) => e.type === "turn_complete");
|
|
38
|
+
const snap = costTracker.snapshot();
|
|
39
|
+
return {
|
|
40
|
+
text: extractText(events),
|
|
41
|
+
costUsd: snap.totalUsd,
|
|
42
|
+
tokens: { input: snap.inputTokens, output: snap.outputTokens },
|
|
43
|
+
duration: tc?.durationMs ?? 0,
|
|
44
|
+
sessionId,
|
|
45
|
+
events,
|
|
46
|
+
};
|
|
47
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { IClaudeOptions } from "./types/options.js";
|
|
2
|
+
export interface IClaudeProcess {
|
|
3
|
+
write: (message: string) => void;
|
|
4
|
+
kill: () => void;
|
|
5
|
+
exited: Promise<number>;
|
|
6
|
+
stdout: ReadableStream<Uint8Array>;
|
|
7
|
+
stderr: ReadableStream<Uint8Array>;
|
|
8
|
+
pid: number;
|
|
9
|
+
}
|
|
10
|
+
export interface ISpawnOptions extends IClaudeOptions {
|
|
11
|
+
prompt?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const resetBinaryCache: () => void;
|
|
14
|
+
export declare const buildArgs: (options: ISpawnOptions, binaryPath: string) => string[];
|
|
15
|
+
export declare const spawnClaude: (options: ISpawnOptions) => IClaudeProcess;
|