zhuye 0.0.0-stage → 0.1.1

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 CHANGED
@@ -1,3 +1,80 @@
1
- # Temporary Holding Version
1
+ # zhuye (TypeScript SDK)
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 让脚本项目(同样适用于 TS 服务端)直接驱动 ZhuYe Agent 的轻量 SDK。底层通过 spawn `zhuye serve` 子进程、走 JSONL stdio 协议,**零原生编译、零原生依赖**。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install zhuye
9
+ ```
10
+
11
+ 需要本机有 `zhuye` 二进制在 PATH(或通过 `ZHUYE_BIN` 指向)。从 [GitHub Releases](https://github.com/Asketisch/ZhuYe/releases) 下载,或从本仓库 `make build` 产出 `target/release/zhuye`。
12
+
13
+ ## 用法
14
+
15
+ ```ts
16
+ import { ZhuyeAgent } from 'zhuye';
17
+
18
+ const agent = await ZhuyeAgent.spawn();
19
+
20
+ // 1) 注册客户端自定义工具(LLM 调用时由本地函数返回结果)
21
+ await agent.registerTool(
22
+ 'get_weather',
23
+ '查询指定城市的天气',
24
+ {
25
+ type: 'object',
26
+ properties: { city: { type: 'string' } },
27
+ required: ['city'],
28
+ },
29
+ async ({ city }) => ({
30
+ content: [{ type: 'text', text: `${city} 今天晴 26℃` }],
31
+ is_error: false,
32
+ metadata: {},
33
+ elapsed_ms: 0,
34
+ }),
35
+ );
36
+
37
+ // 2) 提交 prompt,流式接收事件
38
+ const text = await agent.prompt('北京天气如何?');
39
+ console.log(text); // "北京 今天晴 26℃"
40
+
41
+ // 3) 也可手动遍历事件:
42
+ for await (const ev of await agent.submit('第二轮')) {
43
+ if (ev.msg.type === 'agent_message_delta') process.stdout.write(ev.msg.delta);
44
+ if (ev.msg.type === 'turn_complete') break;
45
+ }
46
+
47
+ await agent.close();
48
+ ```
49
+
50
+ ## 全局事件
51
+
52
+ `nextGlobalEvent({ timeoutMs })` 读取 `id=""` 的会话事件(`approval_request` /
53
+ `token_count` / `permission_mode_changed` 等),与 `submit()` 的按 submission
54
+ 路由的事件迭代器独立。超时抛错,进程 stdout 关闭后返回 `null`;未知变体
55
+ 原样返回。
56
+
57
+ ```ts
58
+ const handshake = await agent.nextGlobalEvent({ timeoutMs: 5000 });
59
+ ```
60
+
61
+ ## 环境变量
62
+
63
+ | 变量 | 作用 |
64
+ |------|------|
65
+ | `ZHUYE_BIN` | `zhuye` 二进制路径(默认搜索 PATH)。 |
66
+ | `ZHUYE_MODEL` | 模型名(spec,例如 `mock/mock-1` 用于离线 SDK 测试)。 |
67
+ | `ZHUYE_REMOTE_TOOL_TIMEOUT_SECS` | 远程工具等待客户端回执的上限,默认 120s。 |
68
+
69
+ ## 协议
70
+
71
+ SDK 与 `zhuye serve` 之间的 wire 协议见 `../PROTOCOL.md`(同仓库 `sdks/PROTOCOL.md`)。Rust 核心新增协议变体时,SDK 自动降为 `UnknownEventMsg`,不会中断下游。
72
+
73
+ ## 测试
74
+
75
+ ```bash
76
+ npm install
77
+ npm test # 跑协议层 vitest(无需 zhuye 二进制)
78
+ ```
79
+
80
+ 完整的端到端测试(包含 `zhuye serve` 子进程)需先在仓库根 `cargo build -p zhuye-cli`,再把 `target/debug/zhuye` 暴露到 PATH 或 `ZHUYE_BIN`。
@@ -0,0 +1,100 @@
1
+ import { ChildProcessWithoutNullStreams } from 'node:child_process';
2
+ import { Readable, Writable } from 'node:stream';
3
+ import { Event, Op, ReviewDecision, ToolOutput } from './protocol.js';
4
+ /** 单次工具执行的本地 handler(远程工具回执内容)。 */
5
+ export type ToolHandler = (args: Record<string, unknown>) => Promise<ToolOutput> | ToolOutput;
6
+ /** ZhuyeAgent 启动选项。 */
7
+ export interface ZhuyeAgentOptions {
8
+ /** `zhuye` 二进制路径。默认读 env `ZHUYE_BIN` → PATH 上的 `zhuye`。 */
9
+ bin?: string;
10
+ /** 工作目录(传给 serve 的 env)。 */
11
+ cwd?: string;
12
+ /** 透传给 serve 进程的 env(如 `ZHUYE_MODEL=mock`)。 */
13
+ env?: NodeJS.ProcessEnv;
14
+ /** 反射 spawn options(覆盖/补充默认的 cwd / env)。 */
15
+ spawnOptions?: Omit<SpawnOptions, 'bin' | 'args' | 'env' | 'cwd'>;
16
+ /** `session_configured` 握手超时(ms)。默认 30s。 */
17
+ handshakeTimeoutMs?: number;
18
+ }
19
+ interface SpawnOptions {
20
+ bin: string;
21
+ args: string[];
22
+ cwd?: string;
23
+ env?: NodeJS.ProcessEnv;
24
+ }
25
+ /**
26
+ * 主类:启动并维护一个常驻 `zhuye serve` 子进程,把 JSONL 协议包装
27
+ * 成 Promise / AsyncIterable 给 TS 用户。
28
+ */
29
+ export declare class ZhuyeAgent {
30
+ private readonly proc;
31
+ private readonly tools;
32
+ private readonly pending;
33
+ private readonly ready;
34
+ private readonly _closeListeners;
35
+ private closesHandled;
36
+ private closed;
37
+ private onceSessionConfigured;
38
+ private constructor();
39
+ /** 启动一个 `zhuye serve` 子进程,等到 `session_configured` 即视为就绪。 */
40
+ static spawn(options?: ZhuyeAgentOptions): Promise<ZhuyeAgent>;
41
+ /** 测试 / mock 注入用:用现成的 stdio 流替代 spawn(单测走这条路)。 */
42
+ static fromStdio(stdin: Writable, stdout: Readable, handshakeTimeoutMs?: number): Promise<ZhuyeAgent>;
43
+ /** 注册客户端自定义工具(`ZhuyeAgent` 启动后任何时刻可调用)。 */
44
+ registerTool(name: string, description: string, parameters: Record<string, unknown>, handler: ToolHandler): Promise<void>;
45
+ /** 提交一条用户 prompt;返回该 submission 的事件 AsyncIterable。 */
46
+ submit(text: string): Promise<AsyncIterable<Event>>;
47
+ /** 提交一条任意 Op(用于 shutdown / interrupt / approval / 等等)。 */
48
+ submit(op: Op, opts?: {
49
+ submissionId?: string;
50
+ }): Promise<AsyncIterable<Event>>;
51
+ /** 便捷:`prompt(text)` → 最终 assistant 文本(聚合所有 delta)。 */
52
+ prompt(text: string, opts?: {
53
+ timeoutMs?: number;
54
+ }): Promise<string>;
55
+ /** 中断当前 turn。 */
56
+ interrupt(childId?: string): Promise<void>;
57
+ /**
58
+ * 响应 `ApprovalRequest` 或 `HookApprovalRequest`。
59
+ *
60
+ * `decision` 是 wire 形态的 `ReviewDecision`(serde snake_case):
61
+ * `"approve"` / `"approve_for_session"`,或
62
+ * `{ deny: { reason: string } }`。与 Python SDK 一致 —— 透传 wire
63
+ * 值,不做形状转换(旧版发 `{type:'approve'}` 无法被服务端反序列化,
64
+ * 审批会永久挂起,已修)。
65
+ */
66
+ approve(id: string, decision: ReviewDecision): Promise<void>;
67
+ /**
68
+ * 读取一条全局事件(`id=""`:approval_request / ask_user_question /
69
+ * token_count / permission_mode_changed 等,含握手的
70
+ * `session_configured` 本身),与 submit() 的事件迭代器相互独立;
71
+ * 未知事件原样返回。
72
+ *
73
+ * stdout EOF / close 之后返回 `null`;`timeoutMs` 到点抛错 ——
74
+ * 与 Python SDK 的 `next_global_event` 语义对齐(超时抛 `queue.Empty`,
75
+ * EOF 返回 None)。
76
+ */
77
+ nextGlobalEvent(opts?: {
78
+ timeoutMs?: number;
79
+ }): Promise<Event | null>;
80
+ /** 优雅关闭:发 Shutdown → 等 ShutdownComplete → 杀进程。 */
81
+ close(): Promise<void>;
82
+ /** 暴露当前进程句柄(测试需要直接操作 stdin / kill 时用)。 */
83
+ get childProcess(): ChildProcessWithoutNullStreams | undefined;
84
+ /** 子进程意外退出时通知所有 close 监听者。 */
85
+ private handleServeExit;
86
+ private consumeStdout;
87
+ private handleToolRequest;
88
+ private eventListeners;
89
+ /** 全局事件(`id=""`)缓冲:nextGlobalEvent() 的队列 + 挂起等待者。 */
90
+ private globalQueue;
91
+ private globalWaiters;
92
+ private globalEof;
93
+ private dispatchEvent;
94
+ /** 全局事件入队;`ev === null` 表示 EOF:标记收尾并唤醒等待者。 */
95
+ private pushGlobalEvent;
96
+ private filterEventsById;
97
+ private writeSubmission;
98
+ private writeSubmissionUnchecked;
99
+ }
100
+ export {};
package/dist/agent.js ADDED
@@ -0,0 +1,533 @@
1
+ //! ZhuyeAgent —— TS SDK 主类。
2
+ //!
3
+ //! 用法:
4
+ //! ```ts
5
+ //! const agent = await ZhuyeAgent.spawn();
6
+ //! await agent.registerTool('get_weather', '查询天气', {
7
+ //! type: 'object', properties: { city: { type: 'string' } }, required: ['city']
8
+ //! }, async ({ city }) => ({ content: [{ type: 'text', text: `晴 26℃` }] }));
9
+ //! for await (const ev of agent.submit('北京天气如何')) {
10
+ //! if (ev.msg.type === 'agent_message_delta') process.stdout.write(ev.msg.delta);
11
+ //! }
12
+ //! await agent.close();
13
+ //! ```
14
+ import { spawn } from 'node:child_process';
15
+ import { randomUUID } from 'node:crypto';
16
+ const ZHUYE_BIN_ENV = 'ZHUYE_BIN';
17
+ const DEFAULT_BIN = 'zhuye';
18
+ const READLINE_LIMIT = 16 * 1024 * 1024;
19
+ const STDIN_HANDSHAKE_TIMEOUT_MS = 30_000;
20
+ /**
21
+ * 主类:启动并维护一个常驻 `zhuye serve` 子进程,把 JSONL 协议包装
22
+ * 成 Promise / AsyncIterable 给 TS 用户。
23
+ */
24
+ export class ZhuyeAgent {
25
+ proc;
26
+ tools = new Map();
27
+ pending = new Map();
28
+ ready;
29
+ _closeListeners = [];
30
+ closesHandled = false;
31
+ closed = false;
32
+ onceSessionConfigured = () => { };
33
+ constructor(proc, ready) {
34
+ this.proc = proc;
35
+ this.ready = ready;
36
+ // 后台消费 stdout:按 type 分发到等待中的本地 handler(若已注册)
37
+ // 或作为 turn 事件交给 submit() 的消费者。
38
+ this.consumeStdout().catch((err) => {
39
+ if (!this.closed)
40
+ throw err;
41
+ });
42
+ }
43
+ /** 启动一个 `zhuye serve` 子进程,等到 `session_configured` 即视为就绪。 */
44
+ static async spawn(options = {}) {
45
+ // env 合并而非替换:保留 HOME / PATH 等(替换会让 zhuye 连
46
+ // config 目录都定位不到),options.env 只做增量覆盖。
47
+ const env = { ...process.env, ...options.env };
48
+ const spawnOpts = {
49
+ bin: options.bin ?? process.env[ZHUYE_BIN_ENV] ?? DEFAULT_BIN,
50
+ args: ['serve'],
51
+ cwd: options.cwd,
52
+ env,
53
+ };
54
+ const child = spawn(spawnOpts.bin, spawnOpts.args, {
55
+ cwd: spawnOpts.cwd,
56
+ env: spawnOpts.env,
57
+ stdio: ['pipe', 'pipe', 'pipe'],
58
+ });
59
+ // 子进程 stderr → 当前进程 stderr(让 tracing 日志透传)。
60
+ child.stderr.on('data', (chunk) => {
61
+ process.stderr.write(chunk);
62
+ });
63
+ const proc = {
64
+ child,
65
+ stdin: child.stdin,
66
+ stdoutLine: lineStream(child.stdout),
67
+ };
68
+ // ready 在构造前组装:构造期 handshake 同步需要,放外面。
69
+ let readyResolve;
70
+ let readyReject;
71
+ const ready = new Promise((res, rej) => {
72
+ readyResolve = res;
73
+ readyReject = rej;
74
+ });
75
+ const agent = new ZhuyeAgent(proc, ready);
76
+ // 子进程意外退出 → 通知所有 close 监听者(submit / prompt 等
77
+ // 仍 await 中的 promise 借此抛错 / 收尾)。
78
+ child.on('exit', (code, signal) => {
79
+ agent.handleServeExit(new Error(`zhuye serve exited unexpectedly (code=${code} signal=${signal ?? 'none'})`));
80
+ });
81
+ // spawn 失败(典型:二进制路径不存在,ENOENT):ChildProcess 的
82
+ // error 事件没有监听者会以 uncaught exception 打爆宿主进程;这里
83
+ // 转为握手失败(ready 尚未 settle 时立即拒绝,否则等握手超时)。
84
+ child.on('error', (err) => {
85
+ const failure = new Error(`zhuye serve failed to start: ${err.message}`);
86
+ readyReject(failure);
87
+ agent.handleServeExit(failure);
88
+ });
89
+ const handshakeMs = options.handshakeTimeoutMs ?? STDIN_HANDSHAKE_TIMEOUT_MS;
90
+ const timer = setTimeout(() => {
91
+ readyReject(new Error(`zhuye serve handshake timed out after ${handshakeMs}ms`));
92
+ }, handshakeMs);
93
+ timer.unref?.();
94
+ // 在握手阶段消费 stdout,直到见到 session_configured 后再切给
95
+ // 业务消费者 —— 简单做法:让后台 consumer 自己处理,ready 在
96
+ // session_configured 时 resolve。
97
+ agent.onceSessionConfigured = () => {
98
+ clearTimeout(timer);
99
+ readyResolve();
100
+ };
101
+ return agent;
102
+ }
103
+ /** 测试 / mock 注入用:用现成的 stdio 流替代 spawn(单测走这条路)。 */
104
+ static async fromStdio(stdin, stdout, handshakeTimeoutMs = STDIN_HANDSHAKE_TIMEOUT_MS) {
105
+ const proc = {
106
+ // child 字段仅类型占位;内部不会触发 spawn 路径。
107
+ child: undefined,
108
+ stdin,
109
+ stdoutLine: lineStream(stdout),
110
+ };
111
+ let readyResolve;
112
+ let readyReject;
113
+ const ready = new Promise((res, rej) => {
114
+ readyResolve = res;
115
+ readyReject = rej;
116
+ });
117
+ const agent = new ZhuyeAgent(proc, ready);
118
+ const timer = setTimeout(() => {
119
+ readyReject(new Error(`fromStdio handshake timed out after ${handshakeTimeoutMs}ms`));
120
+ }, handshakeTimeoutMs);
121
+ timer.unref?.();
122
+ agent.onceSessionConfigured = () => {
123
+ clearTimeout(timer);
124
+ readyResolve();
125
+ };
126
+ return agent;
127
+ }
128
+ /** 注册客户端自定义工具(`ZhuyeAgent` 启动后任何时刻可调用)。 */
129
+ async registerTool(name, description, parameters, handler) {
130
+ await this.ready;
131
+ const spec = { name, description, parameters };
132
+ this.tools.set(name, { spec, handler });
133
+ const sub = {
134
+ id: randomUUID(),
135
+ op: { type: 'register_tools', tools: [spec] },
136
+ };
137
+ await this.writeSubmission(sub);
138
+ }
139
+ async submit(input, opts = {}) {
140
+ await this.ready;
141
+ const id = opts.submissionId ?? randomUUID();
142
+ const op = typeof input === 'string'
143
+ ? {
144
+ type: 'user_input',
145
+ items: [{ type: 'text', text: input }],
146
+ }
147
+ : input;
148
+ const sub = { id, op };
149
+ await this.writeSubmission(sub);
150
+ return this.filterEventsById(id);
151
+ }
152
+ /** 便捷:`prompt(text)` → 最终 assistant 文本(聚合所有 delta)。 */
153
+ async prompt(text, opts = {}) {
154
+ let result = '';
155
+ for await (const ev of await this.submit(text)) {
156
+ const msg = ev.msg;
157
+ if (msg.type === 'agent_message_delta')
158
+ result += msg.delta;
159
+ else if (msg.type === 'agent_message')
160
+ result += msg.text;
161
+ else if (msg.type === 'turn_complete')
162
+ break;
163
+ else if (msg.type === 'turn_aborted') {
164
+ // 对齐 Python SDK:turn_aborted 不是正常完成,返回部分文本
165
+ // 容易被调用方误认为完整答案 —— 抛错强制处理。
166
+ throw new Error(`zhuye turn aborted: ${JSON.stringify(msg.reason)}`);
167
+ }
168
+ else if (msg.type === 'error') {
169
+ throw new Error(`zhuye error: ${msg.code}: ${msg.message}`);
170
+ }
171
+ }
172
+ return result;
173
+ }
174
+ /** 中断当前 turn。 */
175
+ async interrupt(childId) {
176
+ await this.writeSubmission({
177
+ id: randomUUID(),
178
+ op: { type: 'interrupt', child_id: childId ?? null },
179
+ });
180
+ }
181
+ /**
182
+ * 响应 `ApprovalRequest` 或 `HookApprovalRequest`。
183
+ *
184
+ * `decision` 是 wire 形态的 `ReviewDecision`(serde snake_case):
185
+ * `"approve"` / `"approve_for_session"`,或
186
+ * `{ deny: { reason: string } }`。与 Python SDK 一致 —— 透传 wire
187
+ * 值,不做形状转换(旧版发 `{type:'approve'}` 无法被服务端反序列化,
188
+ * 审批会永久挂起,已修)。
189
+ */
190
+ async approve(id, decision) {
191
+ await this.writeSubmission({
192
+ id: randomUUID(),
193
+ op: {
194
+ type: 'tool_approval', // Hook 审批也用同一个 decision 形态;serve 层自行分发
195
+ id,
196
+ decision,
197
+ },
198
+ });
199
+ }
200
+ /**
201
+ * 读取一条全局事件(`id=""`:approval_request / ask_user_question /
202
+ * token_count / permission_mode_changed 等,含握手的
203
+ * `session_configured` 本身),与 submit() 的事件迭代器相互独立;
204
+ * 未知事件原样返回。
205
+ *
206
+ * stdout EOF / close 之后返回 `null`;`timeoutMs` 到点抛错 ——
207
+ * 与 Python SDK 的 `next_global_event` 语义对齐(超时抛 `queue.Empty`,
208
+ * EOF 返回 None)。
209
+ */
210
+ async nextGlobalEvent(opts = {}) {
211
+ const queued = this.globalQueue.shift();
212
+ if (queued)
213
+ return queued;
214
+ if (this.globalEof)
215
+ return null;
216
+ return new Promise((resolve, reject) => {
217
+ let timer;
218
+ const waiter = (ev) => {
219
+ if (timer !== undefined)
220
+ clearTimeout(timer);
221
+ resolve(ev);
222
+ };
223
+ this.globalWaiters.push(waiter);
224
+ if (opts.timeoutMs !== undefined) {
225
+ timer = setTimeout(() => {
226
+ const idx = this.globalWaiters.indexOf(waiter);
227
+ if (idx >= 0)
228
+ this.globalWaiters.splice(idx, 1);
229
+ reject(new Error(`zhuye nextGlobalEvent timed out after ${opts.timeoutMs}ms`));
230
+ }, opts.timeoutMs);
231
+ timer.unref?.();
232
+ }
233
+ });
234
+ }
235
+ /** 优雅关闭:发 Shutdown → 等 ShutdownComplete → 杀进程。 */
236
+ async close() {
237
+ if (this.closed)
238
+ return;
239
+ // 先置 closed(同步挡住并发 close 与后续 writeSubmission),再发
240
+ // shutdown。共享 stdin 可能已被别处(另一个 agent / 外部)end 掉,
241
+ // 此时跳过写入与 end,直接走进程收尾。
242
+ this.closed = true;
243
+ this.closesHandled = true;
244
+ const stdinUsable = !this.proc.stdin.destroyed && !this.proc.stdin.writableEnded;
245
+ if (stdinUsable) {
246
+ try {
247
+ await this.writeSubmissionUnchecked({ id: randomUUID(), op: { type: 'shutdown' } });
248
+ }
249
+ catch {
250
+ // stdin 已关,忽略
251
+ }
252
+ this.proc.stdin.end();
253
+ }
254
+ await new Promise((resolve) => {
255
+ if (!this.proc.child) {
256
+ resolve();
257
+ return;
258
+ }
259
+ this.proc.child.once('exit', () => resolve());
260
+ setTimeout(() => {
261
+ try {
262
+ this.proc.child?.kill();
263
+ }
264
+ catch {
265
+ // ignore
266
+ }
267
+ resolve();
268
+ // 2s 对齐 Python SDK(agent.close 的 wait(timeout=2.0))。
269
+ }, 2000).unref?.();
270
+ });
271
+ }
272
+ /** 暴露当前进程句柄(测试需要直接操作 stdin / kill 时用)。 */
273
+ get childProcess() {
274
+ return this.proc.child;
275
+ }
276
+ /** 子进程意外退出时通知所有 close 监听者。 */
277
+ handleServeExit(err) {
278
+ if (this.closesHandled)
279
+ return;
280
+ for (const cb of this._closeListeners)
281
+ cb(err);
282
+ }
283
+ // ── 内部:握手 / 事件分发 / 写 Submission ─────────────────────────
284
+ async consumeStdout() {
285
+ for await (const line of this.proc.stdoutLine) {
286
+ let ev;
287
+ try {
288
+ ev = JSON.parse(line);
289
+ }
290
+ catch {
291
+ process.stderr.write(`[zhuye-sdk] unparseable line: ${line.slice(0, 200)}\n`);
292
+ continue;
293
+ }
294
+ // 握手阶段:见 session_configured 就 resolve ready。
295
+ if (!this.onceSessionConfigured && ev.msg.type === 'session_configured') {
296
+ // onceSessionConfigured 已被 fromStdio/spawn 重新赋值,这里只是兜底。
297
+ }
298
+ if (ev.msg.type === 'session_configured') {
299
+ const cb = this.onceSessionConfigured;
300
+ this.onceSessionConfigured = () => { };
301
+ cb();
302
+ }
303
+ // 远程工具请求 → 调用本地 handler → 回执。
304
+ if (ev.msg.type === 'tool_execution_request') {
305
+ const req = ev.msg;
306
+ void this.handleToolRequest(req.call_id, req.tool, req.args);
307
+ continue;
308
+ }
309
+ // 其他事件交给按 submission id 过滤的消费者。
310
+ this.dispatchEvent(ev);
311
+ }
312
+ // stdout EOF(子进程退出):唤醒所有仍挂起的 submission 迭代器
313
+ // 与全局事件等待者 —— 对齐 Python `_on_eof`:连接断开即按流末尾
314
+ // 收尾,而不是永久挂起。
315
+ for (const cb of [...this.eventListeners.values()])
316
+ cb(null);
317
+ this.eventListeners.clear();
318
+ this.pushGlobalEvent(null);
319
+ }
320
+ async handleToolRequest(callId, tool, args) {
321
+ const entry = this.tools.get(tool);
322
+ if (!entry) {
323
+ // 没注册 → 回执失败(让 LLM 看到错误)。
324
+ await this.writeSubmission({
325
+ id: randomUUID(),
326
+ op: {
327
+ type: 'tool_execution_response',
328
+ call_id: callId,
329
+ output: errorOutput(`tool '${tool}' not registered in this SDK`),
330
+ },
331
+ });
332
+ return;
333
+ }
334
+ try {
335
+ const output = await entry.handler(args);
336
+ await this.writeSubmission({
337
+ id: randomUUID(),
338
+ op: { type: 'tool_execution_response', call_id: callId, output },
339
+ });
340
+ }
341
+ catch (err) {
342
+ const message = err instanceof Error ? err.message : String(err);
343
+ await this.writeSubmission({
344
+ id: randomUUID(),
345
+ op: {
346
+ type: 'tool_execution_response',
347
+ call_id: callId,
348
+ output: errorOutput(message),
349
+ },
350
+ });
351
+ }
352
+ }
353
+ eventListeners = new Map();
354
+ /** 全局事件(`id=""`)缓冲:nextGlobalEvent() 的队列 + 挂起等待者。 */
355
+ globalQueue = [];
356
+ globalWaiters = [];
357
+ globalEof = false;
358
+ dispatchEvent(ev) {
359
+ if (ev.id) {
360
+ const cb = this.eventListeners.get(ev.id);
361
+ if (cb) {
362
+ cb(ev);
363
+ // 终态事件(turn_complete / turn_aborted / shutdown_complete /
364
+ // submission_closed)视为 stream 末尾,清掉监听者。
365
+ if (isTerminal(ev.msg))
366
+ this.eventListeners.delete(ev.id);
367
+ }
368
+ }
369
+ else {
370
+ // 全局事件(含握手的 session_configured)入会话级队列,由
371
+ // nextGlobalEvent() 消费 —— 不入任何 submission 的迭代器。
372
+ this.pushGlobalEvent(ev);
373
+ }
374
+ }
375
+ /** 全局事件入队;`ev === null` 表示 EOF:标记收尾并唤醒等待者。 */
376
+ pushGlobalEvent(ev) {
377
+ if (ev === null)
378
+ this.globalEof = true;
379
+ const waiter = this.globalWaiters.shift();
380
+ if (waiter) {
381
+ waiter(ev);
382
+ return;
383
+ }
384
+ if (ev !== null)
385
+ this.globalQueue.push(ev);
386
+ }
387
+ filterEventsById(submissionId) {
388
+ const queue = [];
389
+ const pending = [];
390
+ let closed = false;
391
+ // ev === null 表示 stdout EOF / 连接断开:按流末尾收尾(效果与
392
+ // 收到终态事件一致),对齐 Python `_on_eof` 唤醒全部监听者。
393
+ const onEvent = (ev) => {
394
+ if (ev === null) {
395
+ cleanup();
396
+ return;
397
+ }
398
+ const next = pending.shift();
399
+ if (next)
400
+ next(ev);
401
+ else
402
+ queue.push(ev);
403
+ };
404
+ this.eventListeners.set(submissionId, onEvent);
405
+ const cleanup = () => {
406
+ closed = true;
407
+ this.eventListeners.delete(submissionId);
408
+ // 唤醒剩余 pending 等待者,让他们抛错或退出。
409
+ while (pending.length) {
410
+ const next = pending.shift();
411
+ next?.(null);
412
+ }
413
+ };
414
+ return {
415
+ [Symbol.asyncIterator]() {
416
+ return {
417
+ next: async () => {
418
+ if (queue.length > 0) {
419
+ const ev = queue.shift();
420
+ if (isTerminal(ev.msg))
421
+ cleanup();
422
+ return { value: ev, done: false };
423
+ }
424
+ if (closed)
425
+ return { value: undefined, done: true };
426
+ return new Promise((resolve) => {
427
+ pending.push((ev) => {
428
+ if (ev === null)
429
+ resolve({ value: undefined, done: true });
430
+ else {
431
+ if (isTerminal(ev.msg))
432
+ cleanup();
433
+ resolve({ value: ev, done: false });
434
+ }
435
+ });
436
+ });
437
+ },
438
+ return: async () => {
439
+ cleanup();
440
+ return { value: undefined, done: true };
441
+ },
442
+ };
443
+ },
444
+ };
445
+ }
446
+ async writeSubmission(sub) {
447
+ // close() 之后(end 掉 stdin)仍可能有异步路径想写 —— 典型是挂起的
448
+ // 工具 handler 回执。静默丢弃:连接已关,回执无处可去;抛"write
449
+ // after end"反而变成 uncaught exception 打爆宿主进程。
450
+ if (this.closed || this.proc.stdin.destroyed || this.proc.stdin.writableEnded) {
451
+ return;
452
+ }
453
+ return this.writeSubmissionUnchecked(sub);
454
+ }
455
+ writeSubmissionUnchecked(sub) {
456
+ return new Promise((resolve, reject) => {
457
+ const payload = JSON.stringify(sub) + '\n';
458
+ this.proc.stdin.write(payload, (err) => {
459
+ if (err)
460
+ reject(err);
461
+ else
462
+ resolve();
463
+ });
464
+ });
465
+ }
466
+ }
467
+ function errorOutput(message) {
468
+ return {
469
+ content: [{ type: 'text', text: `error: ${message}` }],
470
+ is_error: true,
471
+ metadata: {},
472
+ elapsed_ms: 0,
473
+ };
474
+ }
475
+ function isTerminal(msg) {
476
+ // `submission_closed` 是 serve 在某条 submission 的 per-turn 通道排空后
477
+ // 发出的收尾标记(挂该 submission id)。非 turn 操作(compact / rewind /
478
+ // set_permission_mode 等)没有 turn_complete 之类终态事件,迭代器靠它
479
+ // 收尾;turn 类操作先被 turn_complete / turn_aborted 终结,本事件届时
480
+ // 已无监听者,被无害忽略。
481
+ return (msg.type === 'turn_complete' ||
482
+ msg.type === 'turn_aborted' ||
483
+ msg.type === 'shutdown_complete' ||
484
+ msg.type === 'submission_closed');
485
+ }
486
+ /**
487
+ * 把 Readable 流切成按行产生的 AsyncIterable。超长行(> 16 MiB)
488
+ * 抛错并跳过 —— 对应 `zhuye serve` 不应输出如此长的 JSONL 行。
489
+ */
490
+ function lineStream(stream) {
491
+ // Node 的 Readable 默认 paused,data 事件不会自动 emit。.resume() 切
492
+ // 到 flowing 模式,iter.next() 才有数据。
493
+ stream.resume();
494
+ const iter = stream[Symbol.asyncIterator]();
495
+ let buffer = '';
496
+ const MAX_LINE = READLINE_LIMIT;
497
+ return {
498
+ [Symbol.asyncIterator]() {
499
+ return {
500
+ next: async () => {
501
+ while (true) {
502
+ const nl = buffer.indexOf('\n');
503
+ if (nl >= 0) {
504
+ const line = buffer.slice(0, nl);
505
+ buffer = buffer.slice(nl + 1);
506
+ return { value: line, done: false };
507
+ }
508
+ if (buffer.length > MAX_LINE) {
509
+ const over = buffer.slice(0, 256);
510
+ buffer = '';
511
+ process.stderr.write(`[zhuye-sdk] line > ${MAX_LINE} bytes, dropping: ${over}\n`);
512
+ continue;
513
+ }
514
+ const { value, done } = await iter.next();
515
+ if (done) {
516
+ if (buffer.length === 0)
517
+ return { value: undefined, done: true };
518
+ const tail = buffer;
519
+ buffer = '';
520
+ return { value: tail, done: false };
521
+ }
522
+ buffer += String(value);
523
+ }
524
+ },
525
+ return: async () => {
526
+ await iter.return?.();
527
+ buffer = '';
528
+ return { value: undefined, done: true };
529
+ },
530
+ };
531
+ },
532
+ };
533
+ }
@@ -0,0 +1,2 @@
1
+ export * from './protocol.js';
2
+ export { ZhuyeAgent, ToolHandler, ZhuyeAgentOptions, } from './agent.js';
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ //! `zhuye` —— TS SDK 公共入口。
2
+ export * from './protocol.js';
3
+ export { ZhuyeAgent, } from './agent.js';
@@ -0,0 +1,438 @@
1
+ /**
2
+ * Submission 是 client → serve 的一条命令。`id` 用于把随之产生的事件
3
+ * (`Event.id`)关联回原始 Submission。
4
+ */
5
+ export interface Submission {
6
+ id: string;
7
+ op: Op;
8
+ client_user_message_id?: string;
9
+ }
10
+ /**
11
+ * Op —— 客户端可向 core 提交的操作。`type` 字段是判别符,与 Rust
12
+ * `#[serde(tag = "type", rename_all = "snake_case")]` 一致。
13
+ */
14
+ export type Op = {
15
+ type: 'user_input';
16
+ items: UserInputItem[];
17
+ thread_settings?: ThreadSettings;
18
+ } | {
19
+ type: 'compact';
20
+ } | {
21
+ type: 'interrupt';
22
+ child_id?: string | null;
23
+ } | {
24
+ type: 'rewind';
25
+ to_turn_id?: string | null;
26
+ } | {
27
+ type: 'shutdown';
28
+ } | {
29
+ type: 'tool_approval';
30
+ id: string;
31
+ decision: ReviewDecision;
32
+ } | {
33
+ type: 'hook_approval';
34
+ id: string;
35
+ decision: ReviewDecision;
36
+ } | {
37
+ type: 'enter_plan_mode';
38
+ task: string;
39
+ } | {
40
+ type: 'exit_plan_mode';
41
+ } | {
42
+ type: 'plan_approval';
43
+ id: string;
44
+ choice: PlanApprovalChoice;
45
+ } | {
46
+ type: 'set_effort';
47
+ effort: ReasoningEffort;
48
+ } | {
49
+ type: 'ask_user_question_response';
50
+ id: string;
51
+ answers: AskUserAnswer;
52
+ } | {
53
+ type: 'ask_user_input_response';
54
+ id: string;
55
+ text: string;
56
+ } | {
57
+ type: 'set_permission_mode';
58
+ mode: PermissionMode;
59
+ } | {
60
+ type: 'cycle_permission_mode';
61
+ } | {
62
+ type: 'enter_goal_mode';
63
+ goal: string;
64
+ verify_command?: string | null;
65
+ token_budget?: number | null;
66
+ } | {
67
+ type: 'exit_goal_mode';
68
+ }
69
+ /** v1.3 SDK:注册客户端自定义工具(实现留在客户端进程)。 */
70
+ | {
71
+ type: 'register_tools';
72
+ tools: RemoteToolSpec[];
73
+ }
74
+ /** v1.3 SDK:对 `EventMsg::ToolExecutionRequest` 的回执。 */
75
+ | {
76
+ type: 'tool_execution_response';
77
+ call_id: string;
78
+ output: ToolOutput;
79
+ };
80
+ export interface UserInputItem {
81
+ type: 'text' | 'image';
82
+ text?: string;
83
+ }
84
+ export interface ThreadSettings {
85
+ effort?: ReasoningEffort | null;
86
+ permission_mode?: PermissionMode | null;
87
+ model?: string | null;
88
+ }
89
+ export type ReasoningEffort = 'low' | 'medium' | 'high';
90
+ export type PermissionMode = 'auto' | 'prompt' | 'plan' | 'deny' | 'bubble';
91
+ export type PlanApprovalChoice = 'auto_mode' | 'manual_approve' | 'revise';
92
+ /**
93
+ * `ReviewDecision` 的 wire 形态(serde snake_case)。
94
+ *
95
+ * 注意是**扁平**结构而非 tagged 对象:`Approve` 序列化为裸字符串
96
+ * `"approve"`,`Deny` 序列化为 `{"deny":{"reason":...}}`。旧版误写成
97
+ * `{type:'approve'}`,服务端无法反序列化,审批会永久挂起(v1.3 修复)。
98
+ */
99
+ export type ReviewDecision = 'approve' | 'approve_for_session' | {
100
+ deny: {
101
+ reason: string;
102
+ };
103
+ };
104
+ export interface AskUserAnswer {
105
+ answers: Answer[];
106
+ }
107
+ export interface Answer {
108
+ selected: number[];
109
+ custom?: string | null;
110
+ }
111
+ /** v1.3 SDK:客户端自定义工具声明。 */
112
+ export interface RemoteToolSpec {
113
+ name: string;
114
+ description: string;
115
+ /** JSON Schema 对象。 */
116
+ parameters: Record<string, unknown>;
117
+ }
118
+ /** v1.3 SDK:远程工具执行结果(回执 wire 格式)。 */
119
+ export interface ToolOutput {
120
+ content: ContentBlock[];
121
+ is_error: boolean;
122
+ metadata?: Record<string, unknown>;
123
+ elapsed_ms?: number;
124
+ }
125
+ export interface ContentBlock {
126
+ type: 'text';
127
+ text: string;
128
+ }
129
+ /**
130
+ * Event —— core → client 的状态单元。`id` 与 `Submission.id` 对齐;
131
+ * 不绑定 Submission 时(如 `session_configured` / `shutdown_complete`)
132
+ * 取 `EVENT_ID_NONE`(空字符串)。
133
+ */
134
+ export interface Event {
135
+ id: string;
136
+ msg: EventMsg;
137
+ }
138
+ export declare const EVENT_ID_NONE = "";
139
+ /**
140
+ * 已知 EventMsg 变体的联合。未知变体降为 `UnknownEventMsg`(让 Rust
141
+ * 核心新增变体时下游仍能解析)。
142
+ */
143
+ export type EventMsg = SessionConfigured | TurnStarted | TurnComplete | TurnAborted | TurnRewound | ShutdownComplete | SubmissionClosed | AgentMessage | AgentMessageDelta | ThinkingDelta | TokenCount | ToolCallBegin | ToolCallArgsDelta | ToolCallOutputDelta | ToolCallEnd | ToolExecutionRequest | ApprovalRequest | AskUserQuestion | AskUserInput | PermissionBubble | ContextCompacted | ErrorEvent | StreamError | ConfigReloaded | CollabStarted | CollabMessage | CollabFinished | McpServerStarted | McpServerFailed | McpToolInvoked | LspServerStarted | LspServerFailed | PlanRequest | PlanReady | PlanApproved | PlanRejected | PermissionModeChanged | PlanDraftUpdated | PlanStep | PluginLoaded | QuotaExhausted | Routing | UnknownEventMsg;
144
+ export interface SessionConfigured {
145
+ type: 'session_configured';
146
+ session_id: string;
147
+ model: string;
148
+ provider: string;
149
+ approval_policy: 'auto' | 'prompt';
150
+ sandbox_policy: 'workspace_only' | 'full_access';
151
+ context_window_size?: number | null;
152
+ }
153
+ export interface TurnStarted {
154
+ type: 'turn_started';
155
+ turn_id: string;
156
+ user_message_id: string;
157
+ }
158
+ export interface TurnComplete {
159
+ type: 'turn_complete';
160
+ turn_id: string;
161
+ status: 'success' | 'max_iterations' | 'stopped' | 'token_budget_exceeded';
162
+ usage: TokenUsage;
163
+ }
164
+ export interface TurnAborted {
165
+ type: 'turn_aborted';
166
+ turn_id: string;
167
+ reason: AbortReason;
168
+ }
169
+ export type AbortReason = {
170
+ type: 'user_interrupt';
171
+ } | {
172
+ type: 'shutdown';
173
+ } | {
174
+ type: 'error';
175
+ code: string;
176
+ message: string;
177
+ };
178
+ export interface TurnRewound {
179
+ type: 'turn_rewound';
180
+ to_turn_id: string | null;
181
+ truncated_after: number;
182
+ }
183
+ export interface ShutdownComplete {
184
+ type: 'shutdown_complete';
185
+ }
186
+ /**
187
+ * v1.3 SDK:某条 submission 的 per-turn 通道已排空(该 submission 在
188
+ * core 处理完毕,不会再有任何事件)。`Event.id` 携带对应 submission id。
189
+ * serve 在 drain task 结束时发出;非 turn 操作(compact / rewind / 权限
190
+ * 模式切换 / goal 等)没有 `turn_complete` 之类的终态事件,`submit()`
191
+ * 迭代器靠本事件收尾,否则会永久挂起。
192
+ */
193
+ export interface SubmissionClosed {
194
+ type: 'submission_closed';
195
+ }
196
+ export interface AgentMessage {
197
+ type: 'agent_message';
198
+ text: string;
199
+ }
200
+ export interface AgentMessageDelta {
201
+ type: 'agent_message_delta';
202
+ delta: string;
203
+ }
204
+ export interface ThinkingDelta {
205
+ type: 'thinking_delta';
206
+ delta: string;
207
+ }
208
+ export interface TokenCount {
209
+ type: 'token_count';
210
+ input_tokens: number;
211
+ output_tokens: number;
212
+ cached_tokens?: number;
213
+ cache_write_tokens?: number;
214
+ total_tokens?: number;
215
+ provider?: string;
216
+ credential_label?: string;
217
+ }
218
+ export interface TokenUsage {
219
+ input_tokens: number;
220
+ output_tokens: number;
221
+ cached_tokens?: number;
222
+ cache_write_tokens?: number;
223
+ }
224
+ export interface ToolCallBegin {
225
+ type: 'tool_call_begin';
226
+ call_id: string;
227
+ tool_name: string;
228
+ args: Record<string, unknown>;
229
+ child_id?: string | null;
230
+ }
231
+ /**
232
+ * v1.5:模型生成工具参数期间的原始 JSON 片段(逐段转发)。
233
+ *
234
+ * 与 `tool_call_begin` 的 `call_id` 对应同一通调用;客户端把各片段按
235
+ * 到达顺序拼接即得完整参数 JSON。additive 变体 —— 老客户端按未知
236
+ * `type` 忽略即可。
237
+ */
238
+ export interface ToolCallArgsDelta {
239
+ type: 'tool_call_args_delta';
240
+ call_id: string;
241
+ delta: string;
242
+ }
243
+ /** 工具执行期间的输出预览,最终结果以 tool_call_end 为准。 */
244
+ export interface ToolCallOutputDelta {
245
+ type: 'tool_call_output_delta';
246
+ call_id: string;
247
+ delta: string;
248
+ is_stderr: boolean;
249
+ }
250
+ export interface ToolCallEnd {
251
+ type: 'tool_call_end';
252
+ call_id: string;
253
+ output: ToolOutput;
254
+ is_error: boolean;
255
+ elapsed_ms: number;
256
+ child_id?: string | null;
257
+ }
258
+ /** v1.3 SDK:请求客户端执行其注册的远程工具。 */
259
+ export interface ToolExecutionRequest {
260
+ type: 'tool_execution_request';
261
+ call_id: string;
262
+ tool: string;
263
+ args: Record<string, unknown>;
264
+ }
265
+ export interface ApprovalRequest {
266
+ type: 'approval_request';
267
+ request_id: string;
268
+ kind: ApprovalKind;
269
+ risk: 'low' | 'medium' | 'high';
270
+ }
271
+ export type ApprovalKind = {
272
+ type: 'tool';
273
+ tool_name: string;
274
+ args: Record<string, unknown>;
275
+ } | {
276
+ type: 'hook';
277
+ hook_name: string;
278
+ decision_preview: string;
279
+ };
280
+ export interface AskUserQuestion {
281
+ type: 'ask_user_question';
282
+ request_id: string;
283
+ questions: Question[];
284
+ }
285
+ export interface Question {
286
+ header: string;
287
+ question: string;
288
+ options: QuestionOption[];
289
+ multi_select?: boolean;
290
+ }
291
+ export interface QuestionOption {
292
+ label: string;
293
+ description: string;
294
+ preview?: string;
295
+ }
296
+ export interface AskUserInput {
297
+ type: 'ask_user';
298
+ request_id: string;
299
+ prompt: string;
300
+ secret?: boolean;
301
+ placeholder?: string | null;
302
+ }
303
+ export interface PermissionBubble {
304
+ type: 'permission_bubble';
305
+ tool_name: string;
306
+ args_preview?: string | null;
307
+ risk: 'low' | 'medium' | 'high';
308
+ }
309
+ export interface ContextCompacted {
310
+ type: 'context_compacted';
311
+ strategy: 'truncate' | 'summarize';
312
+ before_tokens?: number;
313
+ after_tokens?: number;
314
+ }
315
+ export interface ErrorEvent {
316
+ type: 'error';
317
+ code: string;
318
+ message: string;
319
+ details?: Record<string, unknown>;
320
+ }
321
+ export interface StreamError {
322
+ type: 'stream_error';
323
+ code: string;
324
+ message: string;
325
+ retry_in_ms: number;
326
+ provider?: string;
327
+ credential_label?: string;
328
+ }
329
+ export interface ConfigReloaded {
330
+ type: 'config_reloaded';
331
+ sections_changed?: string[];
332
+ }
333
+ export interface CollabStarted {
334
+ type: 'collab_started';
335
+ discussion_id: string;
336
+ topic: string;
337
+ }
338
+ export interface CollabMessage {
339
+ type: 'collab_message';
340
+ discussion_id: string;
341
+ participant: string;
342
+ text: string;
343
+ token_usage?: TokenUsage;
344
+ }
345
+ export interface CollabFinished {
346
+ type: 'collab_finished';
347
+ discussion_id: string;
348
+ result?: string | null;
349
+ }
350
+ export interface McpServerStarted {
351
+ type: 'mcp_server_started';
352
+ server: string;
353
+ tool_count: number;
354
+ tool_names?: string[];
355
+ transport?: string;
356
+ }
357
+ export interface McpServerFailed {
358
+ type: 'mcp_server_failed';
359
+ server: string;
360
+ reason: string;
361
+ will_retry: boolean;
362
+ }
363
+ export interface McpToolInvoked {
364
+ type: 'mcp_tool_invoked';
365
+ server: string;
366
+ tool: string;
367
+ elapsed_ms: number;
368
+ is_error: boolean;
369
+ }
370
+ export interface LspServerStarted {
371
+ type: 'lsp_server_started';
372
+ server: string;
373
+ methods: string[];
374
+ language_ids: string[];
375
+ }
376
+ export interface LspServerFailed {
377
+ type: 'lsp_server_failed';
378
+ server: string;
379
+ reason: string;
380
+ }
381
+ export interface PlanRequest {
382
+ type: 'plan_request';
383
+ task: string;
384
+ }
385
+ export interface PlanReady {
386
+ type: 'plan_ready';
387
+ plan_id: string;
388
+ markdown: string;
389
+ }
390
+ export interface PlanApproved {
391
+ type: 'plan_approved';
392
+ plan_id: string;
393
+ choice: PlanApprovalChoice;
394
+ }
395
+ export interface PlanRejected {
396
+ type: 'plan_rejected';
397
+ plan_id: string;
398
+ reason?: string | null;
399
+ }
400
+ export interface PermissionModeChanged {
401
+ type: 'permission_mode_changed';
402
+ from: PermissionMode;
403
+ to: PermissionMode;
404
+ }
405
+ export interface PlanDraftUpdated {
406
+ type: 'plan_draft_updated';
407
+ draft_id: string;
408
+ markdown: string;
409
+ }
410
+ export interface PlanStep {
411
+ type: 'plan_step';
412
+ plan_id: string;
413
+ index: number;
414
+ total: number;
415
+ status: 'pending' | 'in_progress' | 'completed' | 'failed';
416
+ step: string;
417
+ }
418
+ export interface PluginLoaded {
419
+ type: 'plugin_loaded';
420
+ plugin: string;
421
+ skill_count: number;
422
+ command_count: number;
423
+ }
424
+ export interface QuotaExhausted {
425
+ type: 'quota_exhausted';
426
+ credential: string;
427
+ cooldown_until?: string | null;
428
+ }
429
+ export interface Routing {
430
+ type: 'routing';
431
+ role: string;
432
+ kind: string;
433
+ detail?: string;
434
+ }
435
+ export interface UnknownEventMsg {
436
+ type: string;
437
+ [k: string]: unknown;
438
+ }
@@ -0,0 +1,5 @@
1
+ //! ZhuYe 协议 v0 —— SDK 与 `zhuye serve` 子命令之间的 wire 格式。
2
+ //!
3
+ //! 协议冻结,新增变体属 non-breaking addition;SDK 解析未知事件时
4
+ //! 退到 `UnknownEventMsg` 不抛错,让 Rust 核心升级不破坏下游。
5
+ export const EVENT_ID_NONE = '';
package/package.json CHANGED
@@ -1,6 +1,39 @@
1
1
  {
2
2
  "name": "zhuye",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.1",
4
+ "description": "ZhuYe Agent SDK for TypeScript: spawn `zhuye serve` and drive it over JSONL stdio.",
5
+ "author": "ZhuYe Agent Contributors",
6
+ "homepage": "https://github.com/Asketisch/ZhuYe#readme",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/Asketisch/ZhuYe.git"
10
+ },
11
+ "bugs": "https://github.com/Asketisch/ZhuYe/issues",
12
+ "keywords": [
13
+ "ai",
14
+ "agent",
15
+ "llm",
16
+ "jsonl",
17
+ "subprocess"
18
+ ],
19
+ "main": "./dist/index.js",
20
+ "types": "./dist/index.d.ts",
21
+ "type": "module",
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "scripts": {
26
+ "build": "tsc -p tsconfig.json",
27
+ "test": "vitest run",
28
+ "typecheck": "tsc -p tsconfig.json --noEmit"
29
+ },
30
+ "engines": {
31
+ "node": ">=18"
32
+ },
33
+ "license": "MIT",
34
+ "devDependencies": {
35
+ "@types/node": "^20.11.0",
36
+ "typescript": "^5.4.0",
37
+ "vitest": "^1.6.0"
38
+ }
6
39
  }