@nlindstedt/zag-agent 0.2.3
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 +110 -0
- package/dist/src/builder.d.ts +151 -0
- package/dist/src/builder.js +322 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +2 -0
- package/dist/src/process.d.ts +39 -0
- package/dist/src/process.js +134 -0
- package/dist/src/types.d.ts +87 -0
- package/dist/src/types.js +11 -0
- package/dist/tests/builder.test.d.ts +1 -0
- package/dist/tests/builder.test.js +133 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Zag TypeScript Binding
|
|
2
|
+
|
|
3
|
+
TypeScript binding for [zag](https://github.com/niclaslindstedt/zag) — a unified CLI for AI coding agents.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- Node.js 18+
|
|
8
|
+
- The `zag` CLI binary installed and on your `PATH` (or set via `ZAG_BIN` env var)
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @nlindstedt/zag-agent
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### Development setup
|
|
17
|
+
|
|
18
|
+
To work with the binding from source:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
cd bindings/typescript
|
|
22
|
+
npm install
|
|
23
|
+
npm run build
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick start
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { ZagBuilder } from "@nlindstedt/zag-agent";
|
|
30
|
+
|
|
31
|
+
// Non-interactive execution
|
|
32
|
+
const output = await new ZagBuilder()
|
|
33
|
+
.provider("claude")
|
|
34
|
+
.model("sonnet")
|
|
35
|
+
.autoApprove()
|
|
36
|
+
.exec("write a hello world program");
|
|
37
|
+
|
|
38
|
+
console.log(output.result);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Streaming
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { ZagBuilder } from "@nlindstedt/zag-agent";
|
|
45
|
+
|
|
46
|
+
// Stream events as they arrive (NDJSON)
|
|
47
|
+
for await (const event of new ZagBuilder().provider("claude").stream("analyze code")) {
|
|
48
|
+
console.log(event.type, event);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Builder methods
|
|
53
|
+
|
|
54
|
+
| Method | Description |
|
|
55
|
+
|--------|-------------|
|
|
56
|
+
| `.provider(name)` | Set provider: `"claude"`, `"codex"`, `"gemini"`, `"copilot"`, `"ollama"` |
|
|
57
|
+
| `.model(name)` | Set model name or size alias (`"small"`, `"medium"`, `"large"`) |
|
|
58
|
+
| `.systemPrompt(text)` | Set a system prompt |
|
|
59
|
+
| `.root(path)` | Set the working directory |
|
|
60
|
+
| `.autoApprove()` | Skip permission prompts |
|
|
61
|
+
| `.addDir(path)` | Add an additional directory (chainable) |
|
|
62
|
+
| `.json()` | Request JSON output |
|
|
63
|
+
| `.jsonSchema(schema)` | Validate output against a JSON schema (implies `.json()`) |
|
|
64
|
+
| `.jsonStream()` | Enable streaming NDJSON output |
|
|
65
|
+
| `.worktree(name?)` | Run in an isolated git worktree |
|
|
66
|
+
| `.sandbox(name?)` | Run in a Docker sandbox |
|
|
67
|
+
| `.sessionId(uuid)` | Use a specific session ID |
|
|
68
|
+
| `.outputFormat(fmt)` | Set output format (`"text"`, `"json"`, `"json-pretty"`, `"stream-json"`) |
|
|
69
|
+
| `.inputFormat(fmt)` | Set input format (`"text"`, `"stream-json"` — Claude only) |
|
|
70
|
+
| `.replayUserMessages()` | Re-emit user messages on stdout (Claude only) |
|
|
71
|
+
| `.includePartialMessages()` | Include partial message chunks (Claude only) |
|
|
72
|
+
| `.maxTurns(n)` | Set the maximum number of agentic turns |
|
|
73
|
+
| `.showUsage()` | Show token usage statistics (JSON output mode) |
|
|
74
|
+
| `.size(size)` | Set Ollama model parameter size (e.g., `"2b"`, `"9b"`, `"35b"`) |
|
|
75
|
+
| `.verbose()` | Enable verbose output |
|
|
76
|
+
| `.quiet()` | Suppress non-essential output |
|
|
77
|
+
| `.debug()` | Enable debug logging |
|
|
78
|
+
| `.bin(path)` | Override the `zag` binary path |
|
|
79
|
+
|
|
80
|
+
## Terminal methods
|
|
81
|
+
|
|
82
|
+
| Method | Returns | Description |
|
|
83
|
+
|--------|---------|-------------|
|
|
84
|
+
| `.exec(prompt)` | `Promise<AgentOutput>` | Run non-interactively, return structured output |
|
|
85
|
+
| `.stream(prompt)` | `AsyncGenerator<Event>` | Stream NDJSON events |
|
|
86
|
+
| `.execStreaming(prompt)` | `StreamingSession` | Bidirectional streaming (Claude only) |
|
|
87
|
+
| `.run(prompt?)` | `Promise<void>` | Start an interactive session (inherits stdio) |
|
|
88
|
+
| `.resume(sessionId)` | `Promise<void>` | Resume a previous session by ID |
|
|
89
|
+
| `.continueLast()` | `Promise<void>` | Resume the most recent session |
|
|
90
|
+
|
|
91
|
+
## How it works
|
|
92
|
+
|
|
93
|
+
The SDK spawns the `zag` CLI as a subprocess (`zag exec -o json` or `-o stream-json`) and parses the JSON/NDJSON output into typed models. Zero external runtime dependencies — only Node.js built-ins.
|
|
94
|
+
|
|
95
|
+
## Testing
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
npm run build && npm test
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## See also
|
|
102
|
+
|
|
103
|
+
- [Python SDK](../python/README.md)
|
|
104
|
+
- [C# SDK](../csharp/README.md)
|
|
105
|
+
- [Rust API (zag-agent)](../../zag-agent/README.md)
|
|
106
|
+
- [All bindings](../README.md)
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
[MIT](../../LICENSE)
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import type { AgentOutput, Event } from "./types.js";
|
|
2
|
+
import type { StreamingSession } from "./process.js";
|
|
3
|
+
/**
|
|
4
|
+
* Fluent builder for configuring and running zag agent sessions.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { ZagBuilder } from "zag-agent";
|
|
9
|
+
*
|
|
10
|
+
* const output = await new ZagBuilder()
|
|
11
|
+
* .provider("claude")
|
|
12
|
+
* .model("sonnet")
|
|
13
|
+
* .autoApprove()
|
|
14
|
+
* .exec("write a hello world program");
|
|
15
|
+
*
|
|
16
|
+
* console.log(output.result);
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare class ZagBuilder {
|
|
20
|
+
private _bin;
|
|
21
|
+
private _provider?;
|
|
22
|
+
private _model?;
|
|
23
|
+
private _systemPrompt?;
|
|
24
|
+
private _root?;
|
|
25
|
+
private _autoApprove;
|
|
26
|
+
private _addDirs;
|
|
27
|
+
private _json;
|
|
28
|
+
private _jsonSchema?;
|
|
29
|
+
private _jsonStream;
|
|
30
|
+
private _worktree?;
|
|
31
|
+
private _sandbox?;
|
|
32
|
+
private _verbose;
|
|
33
|
+
private _quiet;
|
|
34
|
+
private _debug;
|
|
35
|
+
private _sessionId?;
|
|
36
|
+
private _outputFormat?;
|
|
37
|
+
private _inputFormat?;
|
|
38
|
+
private _replayUserMessages;
|
|
39
|
+
private _includePartialMessages;
|
|
40
|
+
private _maxTurns?;
|
|
41
|
+
private _showUsage;
|
|
42
|
+
private _size?;
|
|
43
|
+
/** Override the zag binary path (default: `ZAG_BIN` env or `"zag"`). */
|
|
44
|
+
bin(path: string): this;
|
|
45
|
+
/** Set the provider (e.g., "claude", "codex", "gemini", "copilot", "ollama"). */
|
|
46
|
+
provider(p: string): this;
|
|
47
|
+
/** Set the model (e.g., "sonnet", "opus", "small", "large"). */
|
|
48
|
+
model(m: string): this;
|
|
49
|
+
/** Set a system prompt to configure agent behavior. */
|
|
50
|
+
systemPrompt(p: string): this;
|
|
51
|
+
/** Set the root directory for the agent to operate in. */
|
|
52
|
+
root(r: string): this;
|
|
53
|
+
/** Enable auto-approve mode (skip permission prompts). */
|
|
54
|
+
autoApprove(a?: boolean): this;
|
|
55
|
+
/** Add an additional directory for the agent to include. */
|
|
56
|
+
addDir(d: string): this;
|
|
57
|
+
/** Request JSON output from the agent. */
|
|
58
|
+
json(): this;
|
|
59
|
+
/** Set a JSON schema for structured output validation. Implies json(). */
|
|
60
|
+
jsonSchema(s: object): this;
|
|
61
|
+
/** Enable streaming JSON output (NDJSON format). */
|
|
62
|
+
jsonStream(): this;
|
|
63
|
+
/** Enable worktree mode with an optional name. */
|
|
64
|
+
worktree(name?: string): this;
|
|
65
|
+
/** Enable sandbox mode with an optional name. */
|
|
66
|
+
sandbox(name?: string): this;
|
|
67
|
+
/** Enable verbose output. */
|
|
68
|
+
verbose(v?: boolean): this;
|
|
69
|
+
/** Enable quiet mode. */
|
|
70
|
+
quiet(q?: boolean): this;
|
|
71
|
+
/** Enable debug logging. */
|
|
72
|
+
debug(d?: boolean): this;
|
|
73
|
+
/** Pre-set a session ID (UUID). */
|
|
74
|
+
sessionId(id: string): this;
|
|
75
|
+
/** Set the output format (e.g., "text", "json", "json-pretty", "stream-json"). */
|
|
76
|
+
outputFormat(f: string): this;
|
|
77
|
+
/** Set the input format (Claude only, e.g., "text", "stream-json"). */
|
|
78
|
+
inputFormat(f: string): this;
|
|
79
|
+
/** Re-emit user messages from stdin on stdout (Claude only). */
|
|
80
|
+
replayUserMessages(r?: boolean): this;
|
|
81
|
+
/** Include partial message chunks in streaming output (Claude only). */
|
|
82
|
+
includePartialMessages(i?: boolean): this;
|
|
83
|
+
/** Set the maximum number of agentic turns. */
|
|
84
|
+
maxTurns(n: number): this;
|
|
85
|
+
/** Show token usage statistics (only applies to JSON output mode). */
|
|
86
|
+
showUsage(s?: boolean): this;
|
|
87
|
+
/** Set the Ollama model parameter size (e.g., "2b", "9b", "35b"). */
|
|
88
|
+
size(s: string): this;
|
|
89
|
+
/** Build the shared CLI flags (before the subcommand). */
|
|
90
|
+
private buildGlobalArgs;
|
|
91
|
+
/** Build CLI args for exec mode. */
|
|
92
|
+
private buildExecArgs;
|
|
93
|
+
/**
|
|
94
|
+
* Run the agent non-interactively and return structured output.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```ts
|
|
98
|
+
* const output = await new ZagBuilder()
|
|
99
|
+
* .provider("claude")
|
|
100
|
+
* .exec("say hello");
|
|
101
|
+
* console.log(output.result);
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
exec(prompt: string): Promise<AgentOutput>;
|
|
105
|
+
/**
|
|
106
|
+
* Run the agent in streaming mode, yielding events as they arrive.
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```ts
|
|
110
|
+
* for await (const event of new ZagBuilder()
|
|
111
|
+
* .provider("claude")
|
|
112
|
+
* .stream("analyze this code")) {
|
|
113
|
+
* console.log(event.type);
|
|
114
|
+
* }
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
stream(prompt: string): AsyncGenerator<Event>;
|
|
118
|
+
/**
|
|
119
|
+
* Run the agent with streaming input and output (Claude only).
|
|
120
|
+
*
|
|
121
|
+
* Returns a StreamingSession with piped stdin for sending NDJSON messages
|
|
122
|
+
* and an async iterator for reading events. Automatically enables
|
|
123
|
+
* `--input-format stream-json`, `--replay-user-messages`, and
|
|
124
|
+
* `-o stream-json`.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```ts
|
|
128
|
+
* const session = new ZagBuilder()
|
|
129
|
+
* .provider("claude")
|
|
130
|
+
* .execStreaming("initial prompt");
|
|
131
|
+
*
|
|
132
|
+
* session.send('{"type":"user_message","content":"hello"}');
|
|
133
|
+
*
|
|
134
|
+
* for await (const event of session.events()) {
|
|
135
|
+
* console.log(event.type);
|
|
136
|
+
* }
|
|
137
|
+
*
|
|
138
|
+
* await session.wait();
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
execStreaming(prompt: string): StreamingSession;
|
|
142
|
+
/**
|
|
143
|
+
* Start an interactive agent session.
|
|
144
|
+
* Inherits stdin/stdout/stderr.
|
|
145
|
+
*/
|
|
146
|
+
run(prompt?: string): Promise<void>;
|
|
147
|
+
/** Resume a previous session by ID. */
|
|
148
|
+
resume(sessionId: string): Promise<void>;
|
|
149
|
+
/** Resume the most recent session. */
|
|
150
|
+
continueLast(): Promise<void>;
|
|
151
|
+
}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { defaultBin, execZag, runZag, streamZag, streamWithInput, } from "./process.js";
|
|
2
|
+
/**
|
|
3
|
+
* Fluent builder for configuring and running zag agent sessions.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { ZagBuilder } from "zag-agent";
|
|
8
|
+
*
|
|
9
|
+
* const output = await new ZagBuilder()
|
|
10
|
+
* .provider("claude")
|
|
11
|
+
* .model("sonnet")
|
|
12
|
+
* .autoApprove()
|
|
13
|
+
* .exec("write a hello world program");
|
|
14
|
+
*
|
|
15
|
+
* console.log(output.result);
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export class ZagBuilder {
|
|
19
|
+
_bin = defaultBin();
|
|
20
|
+
_provider;
|
|
21
|
+
_model;
|
|
22
|
+
_systemPrompt;
|
|
23
|
+
_root;
|
|
24
|
+
_autoApprove = false;
|
|
25
|
+
_addDirs = [];
|
|
26
|
+
_json = false;
|
|
27
|
+
_jsonSchema;
|
|
28
|
+
_jsonStream = false;
|
|
29
|
+
_worktree;
|
|
30
|
+
_sandbox;
|
|
31
|
+
_verbose = false;
|
|
32
|
+
_quiet = false;
|
|
33
|
+
_debug = false;
|
|
34
|
+
_sessionId;
|
|
35
|
+
_outputFormat;
|
|
36
|
+
_inputFormat;
|
|
37
|
+
_replayUserMessages = false;
|
|
38
|
+
_includePartialMessages = false;
|
|
39
|
+
_maxTurns;
|
|
40
|
+
_showUsage = false;
|
|
41
|
+
_size;
|
|
42
|
+
/** Override the zag binary path (default: `ZAG_BIN` env or `"zag"`). */
|
|
43
|
+
bin(path) {
|
|
44
|
+
this._bin = path;
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
/** Set the provider (e.g., "claude", "codex", "gemini", "copilot", "ollama"). */
|
|
48
|
+
provider(p) {
|
|
49
|
+
this._provider = p;
|
|
50
|
+
return this;
|
|
51
|
+
}
|
|
52
|
+
/** Set the model (e.g., "sonnet", "opus", "small", "large"). */
|
|
53
|
+
model(m) {
|
|
54
|
+
this._model = m;
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
57
|
+
/** Set a system prompt to configure agent behavior. */
|
|
58
|
+
systemPrompt(p) {
|
|
59
|
+
this._systemPrompt = p;
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
/** Set the root directory for the agent to operate in. */
|
|
63
|
+
root(r) {
|
|
64
|
+
this._root = r;
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
/** Enable auto-approve mode (skip permission prompts). */
|
|
68
|
+
autoApprove(a = true) {
|
|
69
|
+
this._autoApprove = a;
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
/** Add an additional directory for the agent to include. */
|
|
73
|
+
addDir(d) {
|
|
74
|
+
this._addDirs.push(d);
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
/** Request JSON output from the agent. */
|
|
78
|
+
json() {
|
|
79
|
+
this._json = true;
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
82
|
+
/** Set a JSON schema for structured output validation. Implies json(). */
|
|
83
|
+
jsonSchema(s) {
|
|
84
|
+
this._jsonSchema = s;
|
|
85
|
+
this._json = true;
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
88
|
+
/** Enable streaming JSON output (NDJSON format). */
|
|
89
|
+
jsonStream() {
|
|
90
|
+
this._jsonStream = true;
|
|
91
|
+
return this;
|
|
92
|
+
}
|
|
93
|
+
/** Enable worktree mode with an optional name. */
|
|
94
|
+
worktree(name) {
|
|
95
|
+
this._worktree = name ?? true;
|
|
96
|
+
return this;
|
|
97
|
+
}
|
|
98
|
+
/** Enable sandbox mode with an optional name. */
|
|
99
|
+
sandbox(name) {
|
|
100
|
+
this._sandbox = name ?? true;
|
|
101
|
+
return this;
|
|
102
|
+
}
|
|
103
|
+
/** Enable verbose output. */
|
|
104
|
+
verbose(v = true) {
|
|
105
|
+
this._verbose = v;
|
|
106
|
+
return this;
|
|
107
|
+
}
|
|
108
|
+
/** Enable quiet mode. */
|
|
109
|
+
quiet(q = true) {
|
|
110
|
+
this._quiet = q;
|
|
111
|
+
return this;
|
|
112
|
+
}
|
|
113
|
+
/** Enable debug logging. */
|
|
114
|
+
debug(d = true) {
|
|
115
|
+
this._debug = d;
|
|
116
|
+
return this;
|
|
117
|
+
}
|
|
118
|
+
/** Pre-set a session ID (UUID). */
|
|
119
|
+
sessionId(id) {
|
|
120
|
+
this._sessionId = id;
|
|
121
|
+
return this;
|
|
122
|
+
}
|
|
123
|
+
/** Set the output format (e.g., "text", "json", "json-pretty", "stream-json"). */
|
|
124
|
+
outputFormat(f) {
|
|
125
|
+
this._outputFormat = f;
|
|
126
|
+
return this;
|
|
127
|
+
}
|
|
128
|
+
/** Set the input format (Claude only, e.g., "text", "stream-json"). */
|
|
129
|
+
inputFormat(f) {
|
|
130
|
+
this._inputFormat = f;
|
|
131
|
+
return this;
|
|
132
|
+
}
|
|
133
|
+
/** Re-emit user messages from stdin on stdout (Claude only). */
|
|
134
|
+
replayUserMessages(r = true) {
|
|
135
|
+
this._replayUserMessages = r;
|
|
136
|
+
return this;
|
|
137
|
+
}
|
|
138
|
+
/** Include partial message chunks in streaming output (Claude only). */
|
|
139
|
+
includePartialMessages(i = true) {
|
|
140
|
+
this._includePartialMessages = i;
|
|
141
|
+
return this;
|
|
142
|
+
}
|
|
143
|
+
/** Set the maximum number of agentic turns. */
|
|
144
|
+
maxTurns(n) {
|
|
145
|
+
this._maxTurns = n;
|
|
146
|
+
return this;
|
|
147
|
+
}
|
|
148
|
+
/** Show token usage statistics (only applies to JSON output mode). */
|
|
149
|
+
showUsage(s = true) {
|
|
150
|
+
this._showUsage = s;
|
|
151
|
+
return this;
|
|
152
|
+
}
|
|
153
|
+
/** Set the Ollama model parameter size (e.g., "2b", "9b", "35b"). */
|
|
154
|
+
size(s) {
|
|
155
|
+
this._size = s;
|
|
156
|
+
return this;
|
|
157
|
+
}
|
|
158
|
+
/** Build the shared CLI flags (before the subcommand). */
|
|
159
|
+
buildGlobalArgs() {
|
|
160
|
+
const args = [];
|
|
161
|
+
if (this._provider)
|
|
162
|
+
args.push("-p", this._provider);
|
|
163
|
+
if (this._model)
|
|
164
|
+
args.push("--model", this._model);
|
|
165
|
+
if (this._systemPrompt)
|
|
166
|
+
args.push("--system-prompt", this._systemPrompt);
|
|
167
|
+
if (this._root)
|
|
168
|
+
args.push("--root", this._root);
|
|
169
|
+
if (this._autoApprove)
|
|
170
|
+
args.push("--auto-approve");
|
|
171
|
+
for (const d of this._addDirs)
|
|
172
|
+
args.push("--add-dir", d);
|
|
173
|
+
if (this._worktree === true) {
|
|
174
|
+
args.push("-w");
|
|
175
|
+
}
|
|
176
|
+
else if (typeof this._worktree === "string") {
|
|
177
|
+
args.push("-w", this._worktree);
|
|
178
|
+
}
|
|
179
|
+
if (this._sandbox === true) {
|
|
180
|
+
args.push("--sandbox");
|
|
181
|
+
}
|
|
182
|
+
else if (typeof this._sandbox === "string") {
|
|
183
|
+
args.push("--sandbox", this._sandbox);
|
|
184
|
+
}
|
|
185
|
+
if (this._verbose)
|
|
186
|
+
args.push("--verbose");
|
|
187
|
+
if (this._quiet)
|
|
188
|
+
args.push("--quiet");
|
|
189
|
+
if (this._debug)
|
|
190
|
+
args.push("--debug");
|
|
191
|
+
if (this._sessionId)
|
|
192
|
+
args.push("--session", this._sessionId);
|
|
193
|
+
if (this._maxTurns != null)
|
|
194
|
+
args.push("--max-turns", String(this._maxTurns));
|
|
195
|
+
if (this._showUsage)
|
|
196
|
+
args.push("--show-usage");
|
|
197
|
+
if (this._size)
|
|
198
|
+
args.push("--size", this._size);
|
|
199
|
+
return args;
|
|
200
|
+
}
|
|
201
|
+
/** Build CLI args for exec mode. */
|
|
202
|
+
buildExecArgs(prompt, streaming) {
|
|
203
|
+
const args = this.buildGlobalArgs();
|
|
204
|
+
args.push("exec");
|
|
205
|
+
if (this._json)
|
|
206
|
+
args.push("--json");
|
|
207
|
+
if (this._jsonSchema) {
|
|
208
|
+
args.push("--json-schema", JSON.stringify(this._jsonSchema));
|
|
209
|
+
}
|
|
210
|
+
if (this._jsonStream || streaming)
|
|
211
|
+
args.push("--json-stream");
|
|
212
|
+
if (this._outputFormat)
|
|
213
|
+
args.push("-o", this._outputFormat);
|
|
214
|
+
if (this._inputFormat)
|
|
215
|
+
args.push("-i", this._inputFormat);
|
|
216
|
+
if (this._replayUserMessages)
|
|
217
|
+
args.push("--replay-user-messages");
|
|
218
|
+
if (this._includePartialMessages)
|
|
219
|
+
args.push("--include-partial-messages");
|
|
220
|
+
// For non-streaming exec, default to json output for structured parsing
|
|
221
|
+
if (!streaming && !this._outputFormat && !this._jsonStream) {
|
|
222
|
+
args.push("-o", "json");
|
|
223
|
+
}
|
|
224
|
+
args.push(prompt);
|
|
225
|
+
return args;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Run the agent non-interactively and return structured output.
|
|
229
|
+
*
|
|
230
|
+
* @example
|
|
231
|
+
* ```ts
|
|
232
|
+
* const output = await new ZagBuilder()
|
|
233
|
+
* .provider("claude")
|
|
234
|
+
* .exec("say hello");
|
|
235
|
+
* console.log(output.result);
|
|
236
|
+
* ```
|
|
237
|
+
*/
|
|
238
|
+
async exec(prompt) {
|
|
239
|
+
const args = this.buildExecArgs(prompt, false);
|
|
240
|
+
return execZag(this._bin, args);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Run the agent in streaming mode, yielding events as they arrive.
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* ```ts
|
|
247
|
+
* for await (const event of new ZagBuilder()
|
|
248
|
+
* .provider("claude")
|
|
249
|
+
* .stream("analyze this code")) {
|
|
250
|
+
* console.log(event.type);
|
|
251
|
+
* }
|
|
252
|
+
* ```
|
|
253
|
+
*/
|
|
254
|
+
async *stream(prompt) {
|
|
255
|
+
const args = this.buildExecArgs(prompt, true);
|
|
256
|
+
yield* streamZag(this._bin, args);
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Run the agent with streaming input and output (Claude only).
|
|
260
|
+
*
|
|
261
|
+
* Returns a StreamingSession with piped stdin for sending NDJSON messages
|
|
262
|
+
* and an async iterator for reading events. Automatically enables
|
|
263
|
+
* `--input-format stream-json`, `--replay-user-messages`, and
|
|
264
|
+
* `-o stream-json`.
|
|
265
|
+
*
|
|
266
|
+
* @example
|
|
267
|
+
* ```ts
|
|
268
|
+
* const session = new ZagBuilder()
|
|
269
|
+
* .provider("claude")
|
|
270
|
+
* .execStreaming("initial prompt");
|
|
271
|
+
*
|
|
272
|
+
* session.send('{"type":"user_message","content":"hello"}');
|
|
273
|
+
*
|
|
274
|
+
* for await (const event of session.events()) {
|
|
275
|
+
* console.log(event.type);
|
|
276
|
+
* }
|
|
277
|
+
*
|
|
278
|
+
* await session.wait();
|
|
279
|
+
* ```
|
|
280
|
+
*/
|
|
281
|
+
execStreaming(prompt) {
|
|
282
|
+
const args = this.buildGlobalArgs();
|
|
283
|
+
args.push("exec");
|
|
284
|
+
args.push("-i", "stream-json");
|
|
285
|
+
args.push("-o", "stream-json");
|
|
286
|
+
args.push("--replay-user-messages");
|
|
287
|
+
if (this._includePartialMessages)
|
|
288
|
+
args.push("--include-partial-messages");
|
|
289
|
+
if (this._outputFormat)
|
|
290
|
+
args.push("-o", this._outputFormat);
|
|
291
|
+
args.push(prompt);
|
|
292
|
+
return streamWithInput(this._bin, args);
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Start an interactive agent session.
|
|
296
|
+
* Inherits stdin/stdout/stderr.
|
|
297
|
+
*/
|
|
298
|
+
async run(prompt) {
|
|
299
|
+
const args = this.buildGlobalArgs();
|
|
300
|
+
args.push("run");
|
|
301
|
+
if (this._json)
|
|
302
|
+
args.push("--json");
|
|
303
|
+
if (this._jsonSchema) {
|
|
304
|
+
args.push("--json-schema", JSON.stringify(this._jsonSchema));
|
|
305
|
+
}
|
|
306
|
+
if (prompt)
|
|
307
|
+
args.push(prompt);
|
|
308
|
+
return runZag(this._bin, args);
|
|
309
|
+
}
|
|
310
|
+
/** Resume a previous session by ID. */
|
|
311
|
+
async resume(sessionId) {
|
|
312
|
+
const args = this.buildGlobalArgs();
|
|
313
|
+
args.push("run", "--resume", sessionId);
|
|
314
|
+
return runZag(this._bin, args);
|
|
315
|
+
}
|
|
316
|
+
/** Resume the most recent session. */
|
|
317
|
+
async continueLast() {
|
|
318
|
+
const args = this.buildGlobalArgs();
|
|
319
|
+
args.push("run", "--continue");
|
|
320
|
+
return runZag(this._bin, args);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { ZagBuilder } from "./builder.js";
|
|
2
|
+
export type { AgentOutput, Usage, Event, InitEvent, AssistantMessageEvent, ToolExecutionEvent, ResultEvent, ErrorEvent, PermissionRequestEvent, ContentBlock, TextBlock, ToolUseBlock, ToolResult, } from "./types.js";
|
|
3
|
+
export { ZagError } from "./types.js";
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { AgentOutput, Event } from "./types.js";
|
|
2
|
+
/** Default binary name — override with `ZAG_BIN` env var or builder option. */
|
|
3
|
+
export declare function defaultBin(): string;
|
|
4
|
+
/**
|
|
5
|
+
* Run `zag` and collect stdout as a parsed `AgentOutput`.
|
|
6
|
+
* Throws `ZagError` on non-zero exit.
|
|
7
|
+
*/
|
|
8
|
+
export declare function execZag(bin: string, args: string[]): Promise<AgentOutput>;
|
|
9
|
+
/**
|
|
10
|
+
* Run `zag` in streaming mode and yield parsed `Event` objects (NDJSON).
|
|
11
|
+
*/
|
|
12
|
+
export declare function streamZag(bin: string, args: string[]): AsyncGenerator<Event>;
|
|
13
|
+
/**
|
|
14
|
+
* A live streaming session with piped stdin and stdout.
|
|
15
|
+
*
|
|
16
|
+
* Send NDJSON messages via `send()`, read events via `events()`,
|
|
17
|
+
* then call `wait()` when done.
|
|
18
|
+
*/
|
|
19
|
+
export interface StreamingSession {
|
|
20
|
+
/** Send a raw NDJSON line to the agent's stdin. */
|
|
21
|
+
send(message: string): void;
|
|
22
|
+
/** Send a user message to the agent. */
|
|
23
|
+
sendUserMessage(content: string): void;
|
|
24
|
+
/** Close stdin to signal no more input. */
|
|
25
|
+
closeInput(): void;
|
|
26
|
+
/** Async iterator over parsed Event objects from stdout. */
|
|
27
|
+
events(): AsyncGenerator<Event>;
|
|
28
|
+
/** Wait for the process to exit. Throws ZagError on non-zero exit. */
|
|
29
|
+
wait(): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Spawn `zag` with piped stdin and stdout for bidirectional streaming.
|
|
33
|
+
*/
|
|
34
|
+
export declare function streamWithInput(bin: string, args: string[]): StreamingSession;
|
|
35
|
+
/**
|
|
36
|
+
* Run `zag` interactively with inherited stdio.
|
|
37
|
+
* Returns when the process exits.
|
|
38
|
+
*/
|
|
39
|
+
export declare function runZag(bin: string, args: string[]): Promise<void>;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createInterface } from "node:readline";
|
|
3
|
+
import { ZagError } from "./types.js";
|
|
4
|
+
/** Default binary name — override with `ZAG_BIN` env var or builder option. */
|
|
5
|
+
export function defaultBin() {
|
|
6
|
+
return process.env.ZAG_BIN ?? "zag";
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Run `zag` and collect stdout as a parsed `AgentOutput`.
|
|
10
|
+
* Throws `ZagError` on non-zero exit.
|
|
11
|
+
*/
|
|
12
|
+
export async function execZag(bin, args) {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
const child = spawn(bin, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
15
|
+
const stdoutChunks = [];
|
|
16
|
+
const stderrChunks = [];
|
|
17
|
+
child.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
|
|
18
|
+
child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
|
|
19
|
+
child.on("error", (err) => {
|
|
20
|
+
reject(new ZagError(`Failed to spawn '${bin}': ${err.message}`, null, Buffer.concat(stderrChunks).toString()));
|
|
21
|
+
});
|
|
22
|
+
child.on("close", (code) => {
|
|
23
|
+
const stdout = Buffer.concat(stdoutChunks).toString();
|
|
24
|
+
const stderr = Buffer.concat(stderrChunks).toString();
|
|
25
|
+
if (code !== 0) {
|
|
26
|
+
reject(new ZagError(`zag exited with code ${code}: ${stderr || stdout}`, code, stderr));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const output = JSON.parse(stdout);
|
|
31
|
+
resolve(output);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
reject(new ZagError(`Failed to parse zag JSON output: ${stdout.slice(0, 200)}`, code, stderr));
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Run `zag` in streaming mode and yield parsed `Event` objects (NDJSON).
|
|
41
|
+
*/
|
|
42
|
+
export async function* streamZag(bin, args) {
|
|
43
|
+
const child = spawn(bin, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
44
|
+
const stderrChunks = [];
|
|
45
|
+
child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
|
|
46
|
+
const rl = createInterface({ input: child.stdout });
|
|
47
|
+
for await (const line of rl) {
|
|
48
|
+
const trimmed = line.trim();
|
|
49
|
+
if (!trimmed)
|
|
50
|
+
continue;
|
|
51
|
+
try {
|
|
52
|
+
const event = JSON.parse(trimmed);
|
|
53
|
+
yield event;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Skip unparseable lines
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const exitCode = await new Promise((resolve) => {
|
|
60
|
+
child.on("close", resolve);
|
|
61
|
+
});
|
|
62
|
+
if (exitCode !== 0) {
|
|
63
|
+
const stderr = Buffer.concat(stderrChunks).toString();
|
|
64
|
+
throw new ZagError(`zag exited with code ${exitCode}`, exitCode, stderr);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Spawn `zag` with piped stdin and stdout for bidirectional streaming.
|
|
69
|
+
*/
|
|
70
|
+
export function streamWithInput(bin, args) {
|
|
71
|
+
const child = spawn(bin, args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
72
|
+
const stderrChunks = [];
|
|
73
|
+
child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
|
|
74
|
+
return {
|
|
75
|
+
send(message) {
|
|
76
|
+
child.stdin.write(message + "\n");
|
|
77
|
+
},
|
|
78
|
+
sendUserMessage(content) {
|
|
79
|
+
const msg = JSON.stringify({ type: "user_message", content });
|
|
80
|
+
child.stdin.write(msg + "\n");
|
|
81
|
+
},
|
|
82
|
+
closeInput() {
|
|
83
|
+
child.stdin.end();
|
|
84
|
+
},
|
|
85
|
+
async *events() {
|
|
86
|
+
const rl = createInterface({ input: child.stdout });
|
|
87
|
+
for await (const line of rl) {
|
|
88
|
+
const trimmed = line.trim();
|
|
89
|
+
if (!trimmed)
|
|
90
|
+
continue;
|
|
91
|
+
try {
|
|
92
|
+
const event = JSON.parse(trimmed);
|
|
93
|
+
yield event;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// Skip unparseable lines
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
wait() {
|
|
101
|
+
return new Promise((resolve, reject) => {
|
|
102
|
+
child.on("close", (code) => {
|
|
103
|
+
if (code !== 0) {
|
|
104
|
+
const stderr = Buffer.concat(stderrChunks).toString();
|
|
105
|
+
reject(new ZagError(`zag exited with code ${code}`, code, stderr));
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
resolve();
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Run `zag` interactively with inherited stdio.
|
|
117
|
+
* Returns when the process exits.
|
|
118
|
+
*/
|
|
119
|
+
export async function runZag(bin, args) {
|
|
120
|
+
return new Promise((resolve, reject) => {
|
|
121
|
+
const child = spawn(bin, args, { stdio: "inherit" });
|
|
122
|
+
child.on("error", (err) => {
|
|
123
|
+
reject(new ZagError(`Failed to spawn '${bin}': ${err.message}`, null, ""));
|
|
124
|
+
});
|
|
125
|
+
child.on("close", (code) => {
|
|
126
|
+
if (code !== 0) {
|
|
127
|
+
reject(new ZagError(`zag exited with code ${code}`, code, ""));
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
resolve();
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/** Unified output from an agent session. */
|
|
2
|
+
export interface AgentOutput {
|
|
3
|
+
agent: string;
|
|
4
|
+
session_id: string;
|
|
5
|
+
events: Event[];
|
|
6
|
+
result: string | null;
|
|
7
|
+
is_error: boolean;
|
|
8
|
+
total_cost_usd: number | null;
|
|
9
|
+
usage: Usage | null;
|
|
10
|
+
}
|
|
11
|
+
/** Usage statistics for an agent session. */
|
|
12
|
+
export interface Usage {
|
|
13
|
+
input_tokens: number;
|
|
14
|
+
output_tokens: number;
|
|
15
|
+
cache_read_tokens?: number;
|
|
16
|
+
cache_creation_tokens?: number;
|
|
17
|
+
web_search_requests?: number;
|
|
18
|
+
web_fetch_requests?: number;
|
|
19
|
+
}
|
|
20
|
+
/** A single event in an agent session (tagged union on `type`). */
|
|
21
|
+
export type Event = InitEvent | UserMessageEvent | AssistantMessageEvent | ToolExecutionEvent | ResultEvent | ErrorEvent | PermissionRequestEvent;
|
|
22
|
+
export interface InitEvent {
|
|
23
|
+
type: "init";
|
|
24
|
+
model: string;
|
|
25
|
+
tools: string[];
|
|
26
|
+
working_directory: string | null;
|
|
27
|
+
metadata: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
export interface UserMessageEvent {
|
|
30
|
+
type: "user_message";
|
|
31
|
+
content: ContentBlock[];
|
|
32
|
+
}
|
|
33
|
+
export interface AssistantMessageEvent {
|
|
34
|
+
type: "assistant_message";
|
|
35
|
+
content: ContentBlock[];
|
|
36
|
+
usage: Usage | null;
|
|
37
|
+
}
|
|
38
|
+
export interface ToolExecutionEvent {
|
|
39
|
+
type: "tool_execution";
|
|
40
|
+
tool_name: string;
|
|
41
|
+
tool_id: string;
|
|
42
|
+
input: unknown;
|
|
43
|
+
result: ToolResult;
|
|
44
|
+
}
|
|
45
|
+
export interface ResultEvent {
|
|
46
|
+
type: "result";
|
|
47
|
+
success: boolean;
|
|
48
|
+
message: string | null;
|
|
49
|
+
duration_ms: number | null;
|
|
50
|
+
num_turns: number | null;
|
|
51
|
+
}
|
|
52
|
+
export interface ErrorEvent {
|
|
53
|
+
type: "error";
|
|
54
|
+
message: string;
|
|
55
|
+
details: unknown | null;
|
|
56
|
+
}
|
|
57
|
+
export interface PermissionRequestEvent {
|
|
58
|
+
type: "permission_request";
|
|
59
|
+
tool_name: string;
|
|
60
|
+
description: string;
|
|
61
|
+
granted: boolean;
|
|
62
|
+
}
|
|
63
|
+
/** A block of content in an assistant message. */
|
|
64
|
+
export type ContentBlock = TextBlock | ToolUseBlock;
|
|
65
|
+
export interface TextBlock {
|
|
66
|
+
type: "text";
|
|
67
|
+
text: string;
|
|
68
|
+
}
|
|
69
|
+
export interface ToolUseBlock {
|
|
70
|
+
type: "tool_use";
|
|
71
|
+
id: string;
|
|
72
|
+
name: string;
|
|
73
|
+
input: unknown;
|
|
74
|
+
}
|
|
75
|
+
/** Result from a tool execution. */
|
|
76
|
+
export interface ToolResult {
|
|
77
|
+
success: boolean;
|
|
78
|
+
output: string | null;
|
|
79
|
+
error: string | null;
|
|
80
|
+
data: unknown | null;
|
|
81
|
+
}
|
|
82
|
+
/** Error thrown when the zag process fails. */
|
|
83
|
+
export declare class ZagError extends Error {
|
|
84
|
+
readonly exitCode: number | null;
|
|
85
|
+
readonly stderr: string;
|
|
86
|
+
constructor(message: string, exitCode: number | null, stderr: string);
|
|
87
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { describe, it } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { ZagBuilder } from "../src/builder.js";
|
|
4
|
+
import { ZagError } from "../src/types.js";
|
|
5
|
+
describe("ZagBuilder", () => {
|
|
6
|
+
it("should construct with defaults", () => {
|
|
7
|
+
const builder = new ZagBuilder();
|
|
8
|
+
assert.ok(builder);
|
|
9
|
+
});
|
|
10
|
+
it("should support method chaining", () => {
|
|
11
|
+
const builder = new ZagBuilder()
|
|
12
|
+
.provider("claude")
|
|
13
|
+
.model("sonnet")
|
|
14
|
+
.systemPrompt("You are helpful")
|
|
15
|
+
.root("/tmp/test")
|
|
16
|
+
.autoApprove()
|
|
17
|
+
.addDir("/extra")
|
|
18
|
+
.verbose()
|
|
19
|
+
.quiet()
|
|
20
|
+
.debug()
|
|
21
|
+
.sessionId("abc-123")
|
|
22
|
+
.maxTurns(5)
|
|
23
|
+
.showUsage()
|
|
24
|
+
.size("9b");
|
|
25
|
+
assert.ok(builder);
|
|
26
|
+
});
|
|
27
|
+
it("should support json options", () => {
|
|
28
|
+
const builder = new ZagBuilder()
|
|
29
|
+
.json()
|
|
30
|
+
.jsonSchema({ type: "object" })
|
|
31
|
+
.jsonStream();
|
|
32
|
+
assert.ok(builder);
|
|
33
|
+
});
|
|
34
|
+
it("should support isolation modes", () => {
|
|
35
|
+
const wt = new ZagBuilder().worktree();
|
|
36
|
+
assert.ok(wt);
|
|
37
|
+
const wtNamed = new ZagBuilder().worktree("my-feature");
|
|
38
|
+
assert.ok(wtNamed);
|
|
39
|
+
const sb = new ZagBuilder().sandbox();
|
|
40
|
+
assert.ok(sb);
|
|
41
|
+
const sbNamed = new ZagBuilder().sandbox("my-sandbox");
|
|
42
|
+
assert.ok(sbNamed);
|
|
43
|
+
});
|
|
44
|
+
it("should support bin override", () => {
|
|
45
|
+
const builder = new ZagBuilder().bin("/usr/local/bin/zag");
|
|
46
|
+
assert.ok(builder);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
describe("ZagError", () => {
|
|
50
|
+
it("should contain exit code and stderr", () => {
|
|
51
|
+
const err = new ZagError("test error", 1, "stderr output");
|
|
52
|
+
assert.equal(err.message, "test error");
|
|
53
|
+
assert.equal(err.exitCode, 1);
|
|
54
|
+
assert.equal(err.stderr, "stderr output");
|
|
55
|
+
assert.equal(err.name, "ZagError");
|
|
56
|
+
assert.ok(err instanceof Error);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
describe("AgentOutput type", () => {
|
|
60
|
+
it("should parse a sample JSON output", () => {
|
|
61
|
+
const raw = `{
|
|
62
|
+
"agent": "claude",
|
|
63
|
+
"session_id": "sess-123",
|
|
64
|
+
"events": [
|
|
65
|
+
{
|
|
66
|
+
"type": "init",
|
|
67
|
+
"model": "sonnet",
|
|
68
|
+
"tools": ["Bash", "Read"],
|
|
69
|
+
"working_directory": "/home/user",
|
|
70
|
+
"metadata": {}
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"type": "assistant_message",
|
|
74
|
+
"content": [{"type": "text", "text": "Hello!"}],
|
|
75
|
+
"usage": {"input_tokens": 100, "output_tokens": 50}
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"type": "tool_execution",
|
|
79
|
+
"tool_name": "Bash",
|
|
80
|
+
"tool_id": "tool_123",
|
|
81
|
+
"input": {"command": "echo hello"},
|
|
82
|
+
"result": {"success": true, "output": "hello", "error": null, "data": null}
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"type": "result",
|
|
86
|
+
"success": true,
|
|
87
|
+
"message": "Done",
|
|
88
|
+
"duration_ms": 1500,
|
|
89
|
+
"num_turns": 2
|
|
90
|
+
}
|
|
91
|
+
],
|
|
92
|
+
"result": "Hello!",
|
|
93
|
+
"is_error": false,
|
|
94
|
+
"total_cost_usd": 0.01,
|
|
95
|
+
"usage": {"input_tokens": 100, "output_tokens": 50}
|
|
96
|
+
}`;
|
|
97
|
+
const output = JSON.parse(raw);
|
|
98
|
+
assert.equal(output.agent, "claude");
|
|
99
|
+
assert.equal(output.session_id, "sess-123");
|
|
100
|
+
assert.equal(output.events.length, 4);
|
|
101
|
+
assert.equal(output.result, "Hello!");
|
|
102
|
+
assert.equal(output.is_error, false);
|
|
103
|
+
assert.equal(output.total_cost_usd, 0.01);
|
|
104
|
+
assert.equal(output.usage?.input_tokens, 100);
|
|
105
|
+
// Check event types
|
|
106
|
+
assert.equal(output.events[0].type, "init");
|
|
107
|
+
assert.equal(output.events[1].type, "assistant_message");
|
|
108
|
+
assert.equal(output.events[2].type, "tool_execution");
|
|
109
|
+
assert.equal(output.events[3].type, "result");
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
describe("Event parsing", () => {
|
|
113
|
+
it("should parse NDJSON events", () => {
|
|
114
|
+
const lines = [
|
|
115
|
+
'{"type":"init","model":"opus","tools":[],"working_directory":null,"metadata":{}}',
|
|
116
|
+
'{"type":"assistant_message","content":[{"type":"text","text":"Hi"}],"usage":null}',
|
|
117
|
+
'{"type":"error","message":"oops","details":null}',
|
|
118
|
+
'{"type":"permission_request","tool_name":"Bash","description":"run cmd","granted":true}',
|
|
119
|
+
];
|
|
120
|
+
const events = lines.map((l) => JSON.parse(l));
|
|
121
|
+
assert.equal(events.length, 4);
|
|
122
|
+
assert.equal(events[0].type, "init");
|
|
123
|
+
assert.equal(events[1].type, "assistant_message");
|
|
124
|
+
assert.equal(events[2].type, "error");
|
|
125
|
+
if (events[2].type === "error") {
|
|
126
|
+
assert.equal(events[2].message, "oops");
|
|
127
|
+
}
|
|
128
|
+
assert.equal(events[3].type, "permission_request");
|
|
129
|
+
if (events[3].type === "permission_request") {
|
|
130
|
+
assert.equal(events[3].granted, true);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nlindstedt/zag-agent",
|
|
3
|
+
"version": "0.2.3",
|
|
4
|
+
"description": "TypeScript SDK for zag — a unified CLI for AI coding agents",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"test": "node --test dist/tests/builder.test.js",
|
|
21
|
+
"prepublishOnly": "npm run build"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18.0.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"typescript": "^5.5.0",
|
|
28
|
+
"@types/node": "^20.0.0"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/niclaslindstedt/zag",
|
|
33
|
+
"directory": "bindings/typescript"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"zag",
|
|
37
|
+
"ai",
|
|
38
|
+
"agent",
|
|
39
|
+
"claude",
|
|
40
|
+
"codex",
|
|
41
|
+
"gemini",
|
|
42
|
+
"copilot",
|
|
43
|
+
"ollama"
|
|
44
|
+
]
|
|
45
|
+
}
|