@edukors/ai-cli 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.
Files changed (106) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +162 -2
  3. package/dist/cli/main.d.ts +2 -0
  4. package/dist/cli/main.js +256 -0
  5. package/dist/cli/main.js.map +1 -0
  6. package/dist/constants.d.ts +2 -0
  7. package/dist/constants.js +3 -0
  8. package/dist/constants.js.map +1 -0
  9. package/dist/diagnostics.d.ts +28 -0
  10. package/dist/diagnostics.js +35 -0
  11. package/dist/diagnostics.js.map +1 -0
  12. package/dist/engine.d.ts +157 -0
  13. package/dist/engine.js +395 -0
  14. package/dist/engine.js.map +1 -0
  15. package/dist/errors.d.ts +27 -0
  16. package/dist/errors.js +59 -0
  17. package/dist/errors.js.map +1 -0
  18. package/dist/events.d.ts +95 -0
  19. package/dist/events.js +8 -0
  20. package/dist/events.js.map +1 -0
  21. package/dist/host/main.d.ts +2 -0
  22. package/dist/host/main.js +30 -0
  23. package/dist/host/main.js.map +1 -0
  24. package/dist/host/stdio.d.ts +34 -0
  25. package/dist/host/stdio.js +254 -0
  26. package/dist/host/stdio.js.map +1 -0
  27. package/dist/index.d.ts +34 -0
  28. package/dist/index.js +21 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/mcp/config.d.ts +19 -0
  31. package/dist/mcp/config.js +63 -0
  32. package/dist/mcp/config.js.map +1 -0
  33. package/dist/mcp/external.d.ts +55 -0
  34. package/dist/mcp/external.js +129 -0
  35. package/dist/mcp/external.js.map +1 -0
  36. package/dist/mcp/toolServer.d.ts +30 -0
  37. package/dist/mcp/toolServer.js +102 -0
  38. package/dist/mcp/toolServer.js.map +1 -0
  39. package/dist/platform/discovery.d.ts +23 -0
  40. package/dist/platform/discovery.js +64 -0
  41. package/dist/platform/discovery.js.map +1 -0
  42. package/dist/platform/env.d.ts +12 -0
  43. package/dist/platform/env.js +104 -0
  44. package/dist/platform/env.js.map +1 -0
  45. package/dist/platform/login.d.ts +16 -0
  46. package/dist/platform/login.js +42 -0
  47. package/dist/platform/login.js.map +1 -0
  48. package/dist/platform/paths.d.ts +19 -0
  49. package/dist/platform/paths.js +46 -0
  50. package/dist/platform/paths.js.map +1 -0
  51. package/dist/platform/process.d.ts +55 -0
  52. package/dist/platform/process.js +186 -0
  53. package/dist/platform/process.js.map +1 -0
  54. package/dist/provider.d.ts +156 -0
  55. package/dist/provider.js +2 -0
  56. package/dist/provider.js.map +1 -0
  57. package/dist/providers/claude/adapter.d.ts +43 -0
  58. package/dist/providers/claude/adapter.js +398 -0
  59. package/dist/providers/claude/adapter.js.map +1 -0
  60. package/dist/providers/claude/events.d.ts +36 -0
  61. package/dist/providers/claude/events.js +90 -0
  62. package/dist/providers/claude/events.js.map +1 -0
  63. package/dist/providers/codex/adapter.d.ts +28 -0
  64. package/dist/providers/codex/adapter.js +497 -0
  65. package/dist/providers/codex/adapter.js.map +1 -0
  66. package/dist/providers/codex/rpc.d.ts +17 -0
  67. package/dist/providers/codex/rpc.js +75 -0
  68. package/dist/providers/codex/rpc.js.map +1 -0
  69. package/dist/providers.d.ts +9 -0
  70. package/dist/providers.js +15 -0
  71. package/dist/providers.js.map +1 -0
  72. package/dist/redact.d.ts +7 -0
  73. package/dist/redact.js +48 -0
  74. package/dist/redact.js.map +1 -0
  75. package/dist/registry/lock.d.ts +18 -0
  76. package/dist/registry/lock.js +83 -0
  77. package/dist/registry/lock.js.map +1 -0
  78. package/dist/registry/registry.d.ts +38 -0
  79. package/dist/registry/registry.js +119 -0
  80. package/dist/registry/registry.js.map +1 -0
  81. package/dist/session.d.ts +126 -0
  82. package/dist/session.js +333 -0
  83. package/dist/session.js.map +1 -0
  84. package/dist/settings.d.ts +8 -0
  85. package/dist/settings.js +7 -0
  86. package/dist/settings.js.map +1 -0
  87. package/dist/skills.d.ts +24 -0
  88. package/dist/skills.js +91 -0
  89. package/dist/skills.js.map +1 -0
  90. package/dist/testing/fakeProvider.d.ts +22 -0
  91. package/dist/testing/fakeProvider.js +266 -0
  92. package/dist/testing/fakeProvider.js.map +1 -0
  93. package/dist/testing/index.d.ts +2 -0
  94. package/dist/testing/index.js +2 -0
  95. package/dist/testing/index.js.map +1 -0
  96. package/dist/tools.d.ts +45 -0
  97. package/dist/tools.js +32 -0
  98. package/dist/tools.js.map +1 -0
  99. package/dist/version.d.ts +2 -0
  100. package/dist/version.js +3 -0
  101. package/dist/version.js.map +1 -0
  102. package/fixtures/fake-ai.mjs +230 -0
  103. package/fixtures/fake-claude.mjs +62 -0
  104. package/fixtures/fake-codex.mjs +281 -0
  105. package/fixtures/fake-mcp-server.mjs +14 -0
  106. package/package.json +74 -4
@@ -0,0 +1,266 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import { z } from "zod";
3
+ import { AiCliError } from "../errors.js";
4
+ import { buildEnv } from "../platform/env.js";
5
+ import { compareVersions } from "../platform/discovery.js";
6
+ import { ManagedProcess, readLines, runCommand } from "../platform/process.js";
7
+ /**
8
+ * An adapter for fixtures/fake-ai.mjs. It is written the way a real adapter
9
+ * is: detection, sign-in and status through short commands, a long-running
10
+ * process for the session, runtime validation of everything it reads.
11
+ * Applications can use it to test their integration without any account.
12
+ */
13
+ export const FAKE_CLI_PATH = fileURLToPath(new URL("../../fixtures/fake-ai.mjs", import.meta.url));
14
+ const Incoming = z.discriminatedUnion("type", [
15
+ z.object({ type: z.literal("ready"), sessionId: z.string(), resumed: z.boolean(), user: z.string(), turns: z.number() }),
16
+ z.object({ type: z.literal("fatal"), code: z.string(), message: z.string() }),
17
+ z.object({ type: z.literal("text"), turnId: z.string(), delta: z.string() }),
18
+ z.object({ type: z.literal("activity"), turnId: z.string(), phase: z.enum(["thinking", "reading", "researching", "applying"]) }),
19
+ z.object({ type: z.literal("tool_call"), turnId: z.string(), callId: z.string(), name: z.string(), args: z.unknown() }),
20
+ z.object({ type: z.literal("mcp_call"), turnId: z.string(), callId: z.string(), server: z.string(), name: z.string(), args: z.unknown() }),
21
+ z.object({ type: z.literal("question"), turnId: z.string(), questionId: z.string(), prompt: z.string(), options: z.array(z.string()).optional() }),
22
+ z.object({ type: z.literal("usage"), turnId: z.string(), inputTokens: z.number(), outputTokens: z.number() }),
23
+ z.object({ type: z.literal("done"), turnId: z.string() }),
24
+ z.object({ type: z.literal("cancelled"), turnId: z.string() }),
25
+ z.object({ type: z.literal("error"), turnId: z.string().optional(), code: z.string(), message: z.string() }),
26
+ ]);
27
+ const StatusOutput = z.object({ signedIn: z.boolean(), user: z.string().optional(), expired: z.boolean().optional() });
28
+ const ModelsOutput = z.object({ models: z.array(z.object({ id: z.string(), label: z.string().optional(), efforts: z.array(z.string()).optional(), default: z.boolean().optional() })) });
29
+ function parseJson(schema, text, what) {
30
+ const line = text.trim().split("\n").filter(Boolean).pop() ?? "";
31
+ let value;
32
+ try {
33
+ value = JSON.parse(line);
34
+ }
35
+ catch (e) {
36
+ throw new AiCliError("PROVIDER_PROTOCOL", `${what}: output is not JSON`, { cause: e });
37
+ }
38
+ const parsed = schema.safeParse(value);
39
+ if (!parsed.success)
40
+ throw new AiCliError("PROVIDER_PROTOCOL", `${what}: unexpected output`, { meta: { issue: z.prettifyError(parsed.error) } });
41
+ return parsed.data;
42
+ }
43
+ const errorFromProvider = (code, message) => code === "rate_limited"
44
+ ? new AiCliError("RATE_LIMITED", message)
45
+ : code === "auth"
46
+ ? new AiCliError("ACCOUNT_DISCONNECTED", message)
47
+ : code === "expired"
48
+ ? new AiCliError("ACCOUNT_EXPIRED", message)
49
+ : new AiCliError("PROVIDER_ERROR", message, { meta: { providerCode: code } });
50
+ export function createFakeProvider(options = {}) {
51
+ const id = options.id ?? "fake";
52
+ const command = options.command ?? { file: process.execPath, args: [FAKE_CLI_PATH] };
53
+ const minVersion = options.minVersion ?? "1.0.0";
54
+ const env = (context) => buildEnv({ set: { ...options.env, ...(context ? { FAKE_AI_HOME: context.homeDir } : {}) } });
55
+ const run = (context, args, timeoutMs = 15_000) => runCommand({ file: command.file, args: [...command.args, ...args], env: env(context), timeoutMs, ...(context.signal ? { signal: context.signal } : {}) });
56
+ const capabilities = {
57
+ login: "browser",
58
+ accountStatus: true,
59
+ multiAccount: options.multiAccount ?? true,
60
+ logout: true,
61
+ persistentSessions: true,
62
+ models: true,
63
+ efforts: true,
64
+ usage: false,
65
+ tools: true,
66
+ questions: true,
67
+ liveOptions: true,
68
+ };
69
+ return {
70
+ id,
71
+ displayName: "Fake AI",
72
+ installUrl: "https://example.invalid/fake-ai",
73
+ async detect() {
74
+ const result = await runCommand({ file: command.file, args: [...command.args, "--version"], env: env(), timeoutMs: 10_000 }).catch(() => null);
75
+ if (!result || result.code !== 0)
76
+ return { providerId: id, status: "missing" };
77
+ const version = result.stdout.match(/\d+\.\d+\.\d+/)?.[0];
78
+ if (!version)
79
+ return { providerId: id, status: "error", executable: command.file, detail: "unrecognized version output" };
80
+ if (compareVersions(version, minVersion) < 0)
81
+ return { providerId: id, status: "incompatible", executable: command.file, version, requiredVersion: minVersion };
82
+ return { providerId: id, status: "ready", executable: command.file, version };
83
+ },
84
+ capabilities: () => capabilities,
85
+ async connectAccount(context) {
86
+ const result = await run(context, ["login", "--label", context.label], 120_000);
87
+ if (result.timedOut)
88
+ throw new AiCliError("TIMEOUT", "sign-in did not finish in time");
89
+ if (result.code !== 0)
90
+ throw new AiCliError("LOGIN_FAILED", "sign-in failed", { meta: { detail: result.stderr.trim().slice(0, 300) } });
91
+ return this.accountStatus(context);
92
+ },
93
+ async accountStatus(context) {
94
+ const result = await run(context, ["status"]);
95
+ if (result.code !== 0)
96
+ throw new AiCliError("PROVIDER_ERROR", "status check failed", { meta: { detail: result.stderr.trim().slice(0, 300) } });
97
+ const status = parseJson(StatusOutput, result.stdout, "status");
98
+ if (!status.signedIn)
99
+ return { state: "disconnected" };
100
+ return { state: status.expired ? "expired" : "connected", ...(status.user ? { identity: status.user } : {}) };
101
+ },
102
+ async logout(context) {
103
+ const result = await run(context, ["logout"]);
104
+ if (result.code !== 0)
105
+ throw new AiCliError("PROVIDER_ERROR", "sign-out failed");
106
+ },
107
+ async listModels(context) {
108
+ const result = await run(context, ["models"]);
109
+ if (result.code !== 0)
110
+ throw new AiCliError("PROVIDER_ERROR", "could not list models");
111
+ return parseJson(ModelsOutput, result.stdout, "models").models;
112
+ },
113
+ openSession: (context, hooks) => FakeSession.open(command, env(context), context, hooks, options.startupTimeoutMs ?? 10_000),
114
+ };
115
+ }
116
+ class FakeSession {
117
+ proc;
118
+ log;
119
+ hooks;
120
+ resumeId;
121
+ pending = null;
122
+ /**
123
+ * Sinks of recent turns, kept after they end: output that arrives late is
124
+ * still delivered to its sink, which the library then drops. The adapter
125
+ * does not have to get this right for the document to be safe.
126
+ */
127
+ sinks = new Map();
128
+ closing = false;
129
+ constructor(proc, log, hooks) {
130
+ this.proc = proc;
131
+ this.log = log;
132
+ this.hooks = hooks;
133
+ }
134
+ static async open(command, env, context, hooks, startupTimeoutMs) {
135
+ const args = [...command.args, "serve", ...(context.resumeId ? ["--resume", context.resumeId] : [])];
136
+ const proc = new ManagedProcess({ file: command.file, args, env, cwd: context.workDir });
137
+ const session = new FakeSession(proc, context.log, hooks);
138
+ const ready = new Promise((resolve, reject) => {
139
+ session.onStartup = (msg) => {
140
+ if (msg.type === "ready")
141
+ resolve(msg.sessionId);
142
+ else if (msg.type === "fatal")
143
+ reject(errorFromProvider(msg.code, msg.message));
144
+ };
145
+ void proc.exited.then((info) => reject(new AiCliError("PROVIDER_START_FAILED", "the provider exited during startup", { meta: { code: info.code, stderr: proc.stderr.toString().slice(-300) } })));
146
+ setTimeout(() => reject(new AiCliError("TIMEOUT", "the provider did not start in time")), startupTimeoutMs).unref();
147
+ });
148
+ readLines(proc.child.stdout, (line) => session.onLine(line), { onOverflow: () => context.log.warn("discarded an oversized line") });
149
+ void proc.exited.then((info) => session.onExit(info.code, info.signal));
150
+ try {
151
+ session.resumeId = await ready;
152
+ }
153
+ catch (e) {
154
+ session.closing = true;
155
+ await proc.terminate(500);
156
+ throw e;
157
+ }
158
+ session.onStartup = null;
159
+ proc.writeLine({ type: "init", instructions: context.instructions, tools: context.tools, model: context.options.model, effort: context.options.effort });
160
+ return session;
161
+ }
162
+ onStartup = null;
163
+ send(input, sink) {
164
+ if (this.pending)
165
+ return Promise.reject(new AiCliError("SESSION_BUSY", "a turn is already running"));
166
+ const turnId = input.requestId;
167
+ this.sinks.set(turnId, sink);
168
+ if (this.sinks.size > 8)
169
+ this.sinks.delete(this.sinks.keys().next().value);
170
+ return new Promise((resolve, reject) => {
171
+ this.pending = { turnId, resolve, reject };
172
+ const text = input.context ? `${input.text}\n\n<document>\n${input.context}\n</document>` : input.text;
173
+ if (!this.proc.writeLine({ type: "turn", turnId, text })) {
174
+ this.pending = null;
175
+ reject(new AiCliError("PROVIDER_CRASHED", "the provider is not running"));
176
+ }
177
+ });
178
+ }
179
+ async interrupt() {
180
+ if (this.pending)
181
+ this.proc.writeLine({ type: "interrupt", turnId: this.pending.turnId });
182
+ }
183
+ async updateOptions(options) {
184
+ this.proc.writeLine({ type: "options", model: options.model, effort: options.effort });
185
+ }
186
+ async close() {
187
+ if (this.closing)
188
+ return;
189
+ this.closing = true;
190
+ this.proc.writeLine({ type: "exit" });
191
+ await this.proc.terminate(1_000);
192
+ this.settle((p) => p.reject(new AiCliError("SESSION_CLOSED", "the session was closed")));
193
+ }
194
+ settle(fn) {
195
+ const pending = this.pending;
196
+ this.pending = null;
197
+ if (pending)
198
+ fn(pending);
199
+ }
200
+ onLine(line) {
201
+ if (!line.trim())
202
+ return;
203
+ let raw;
204
+ try {
205
+ raw = JSON.parse(line);
206
+ }
207
+ catch {
208
+ this.log.warn("ignored a line that is not JSON", { line: line.slice(0, 200) });
209
+ return;
210
+ }
211
+ const parsed = Incoming.safeParse(raw);
212
+ if (!parsed.success) {
213
+ this.log.warn("ignored an unexpected message", { issue: z.prettifyError(parsed.error) });
214
+ return;
215
+ }
216
+ const msg = parsed.data;
217
+ if (this.onStartup)
218
+ return this.onStartup(msg);
219
+ if (msg.type === "ready" || msg.type === "fatal")
220
+ return;
221
+ const sink = msg.turnId ? this.sinks.get(msg.turnId) : undefined;
222
+ if (!sink) {
223
+ this.log.debug("output for an unknown turn", { type: msg.type });
224
+ return;
225
+ }
226
+ const isCurrent = this.pending?.turnId === msg.turnId;
227
+ switch (msg.type) {
228
+ case "text":
229
+ return sink.text(msg.delta);
230
+ case "activity":
231
+ return sink.activity(msg.phase);
232
+ case "usage":
233
+ return sink.usage({ inputTokens: msg.inputTokens, outputTokens: msg.outputTokens });
234
+ case "tool_call":
235
+ void sink.callTool(msg.name, msg.args).then((result) => this.proc.writeLine({ type: "tool_result", callId: msg.callId, ...result }));
236
+ return;
237
+ case "mcp_call":
238
+ void sink.callExternal(msg.server, msg.name, msg.args).then((result) => this.proc.writeLine({ type: "tool_result", callId: msg.callId, ...result }));
239
+ return;
240
+ case "question":
241
+ sink.ask({ prompt: msg.prompt, ...(msg.options ? { options: msg.options } : {}) }).then((answer) => this.proc.writeLine({ type: "answer", questionId: msg.questionId, answer }), () => undefined);
242
+ return;
243
+ case "done":
244
+ if (isCurrent)
245
+ this.settle((p) => p.resolve("completed"));
246
+ return;
247
+ case "cancelled":
248
+ if (isCurrent)
249
+ this.settle((p) => p.resolve("cancelled"));
250
+ return;
251
+ case "error":
252
+ if (isCurrent)
253
+ this.settle((p) => p.reject(errorFromProvider(msg.code, msg.message)));
254
+ return;
255
+ }
256
+ }
257
+ onExit(code, signal) {
258
+ // During startup, `open` reports the failure itself.
259
+ if (this.closing || this.onStartup)
260
+ return;
261
+ const error = new AiCliError("PROVIDER_CRASHED", "the provider stopped unexpectedly", { meta: { code, signal, stderr: this.proc.stderr.toString().slice(-300) } });
262
+ this.hooks.exited(error);
263
+ this.settle((p) => p.reject(error));
264
+ }
265
+ }
266
+ //# sourceMappingURL=fakeProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fakeProvider.js","sourceRoot":"","sources":["../../src/testing/fakeProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAiB/E;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,4BAA4B,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAanG,MAAM,QAAQ,GAAG,CAAC,CAAC,kBAAkB,CAAC,MAAM,EAAE;IAC5C,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;IACxH,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;IAC7E,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;IAC5E,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC;IAChI,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;IACvH,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;IAC1I,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC;IAClJ,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;IAC7G,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;IACzD,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;IAC9D,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;CAC7G,CAAC,CAAC;AAGH,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;AACvH,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AAEzL,SAAS,SAAS,CAAI,MAAoB,EAAE,IAAY,EAAE,IAAY;IACpE,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;IACjE,IAAI,KAAc,CAAC;IACnB,IAAI,CAAC;QACH,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,UAAU,CAAC,mBAAmB,EAAE,GAAG,IAAI,sBAAsB,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IACzF,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACvC,IAAI,CAAC,MAAM,CAAC,OAAO;QAAE,MAAM,IAAI,UAAU,CAAC,mBAAmB,EAAE,GAAG,IAAI,qBAAqB,EAAE,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;IACjJ,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC;AAED,MAAM,iBAAiB,GAAG,CAAC,IAAY,EAAE,OAAe,EAAE,EAAE,CAC1D,IAAI,KAAK,cAAc;IACrB,CAAC,CAAC,IAAI,UAAU,CAAC,cAAc,EAAE,OAAO,CAAC;IACzC,CAAC,CAAC,IAAI,KAAK,MAAM;QACf,CAAC,CAAC,IAAI,UAAU,CAAC,sBAAsB,EAAE,OAAO,CAAC;QACjD,CAAC,CAAC,IAAI,KAAK,SAAS;YAClB,CAAC,CAAC,IAAI,UAAU,CAAC,iBAAiB,EAAE,OAAO,CAAC;YAC5C,CAAC,CAAC,IAAI,UAAU,CAAC,gBAAgB,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;AAEtF,MAAM,UAAU,kBAAkB,CAAC,UAA+B,EAAE;IAClE,MAAM,EAAE,GAAG,OAAO,CAAC,EAAE,IAAI,MAAM,CAAC;IAChC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC;IACrF,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC;IACjD,MAAM,GAAG,GAAG,CAAC,OAAwB,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;IACvI,MAAM,GAAG,GAAG,CAAC,OAAuB,EAAE,IAAc,EAAE,SAAS,GAAG,MAAM,EAAE,EAAE,CAC1E,UAAU,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAE5J,MAAM,YAAY,GAAiB;QACjC,KAAK,EAAE,SAAS;QAChB,aAAa,EAAE,IAAI;QACnB,YAAY,EAAE,OAAO,CAAC,YAAY,IAAI,IAAI;QAC1C,MAAM,EAAE,IAAI;QACZ,kBAAkB,EAAE,IAAI;QACxB,MAAM,EAAE,IAAI;QACZ,OAAO,EAAE,IAAI;QACb,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,IAAI;QACX,SAAS,EAAE,IAAI;QACf,WAAW,EAAE,IAAI;KAClB,CAAC;IAEF,OAAO;QACL,EAAE;QACF,WAAW,EAAE,SAAS;QACtB,UAAU,EAAE,iCAAiC;QAE7C,KAAK,CAAC,MAAM;YACV,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;YAC/I,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;gBAAE,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;YAC/E,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YAC1D,IAAI,CAAC,OAAO;gBAAE,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,6BAA6B,EAAE,CAAC;YAC1H,IAAI,eAAe,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,CAAC;gBAAE,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,cAAc,EAAE,UAAU,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,CAAC;YAChK,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC;QAChF,CAAC;QAED,YAAY,EAAE,GAAG,EAAE,CAAC,YAAY;QAEhC,KAAK,CAAC,cAAc,CAAC,OAAO;YAC1B,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC;YAChF,IAAI,MAAM,CAAC,QAAQ;gBAAE,MAAM,IAAI,UAAU,CAAC,SAAS,EAAE,gCAAgC,CAAC,CAAC;YACvF,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;gBAAE,MAAM,IAAI,UAAU,CAAC,cAAc,EAAE,gBAAgB,EAAE,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;YACxI,OAAO,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QACrC,CAAC;QAED,KAAK,CAAC,aAAa,CAAC,OAAO;YACzB,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;YAC9C,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;gBAAE,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,qBAAqB,EAAE,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;YAC/I,MAAM,MAAM,GAAG,SAAS,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;YAChE,IAAI,CAAC,MAAM,CAAC,QAAQ;gBAAE,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC;YACvD,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAChH,CAAC;QAED,KAAK,CAAC,MAAM,CAAC,OAAO;YAClB,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;YAC9C,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;gBAAE,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,iBAAiB,CAAC,CAAC;QACnF,CAAC;QAED,KAAK,CAAC,UAAU,CAAC,OAAO;YACtB,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;YAC9C,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;gBAAE,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,uBAAuB,CAAC,CAAC;YACvF,OAAO,SAAS,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,MAAM,CAAC;QACjE,CAAC;QAED,WAAW,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,gBAAgB,IAAI,MAAM,CAAC;KAC7H,CAAC;AACJ,CAAC;AAQD,MAAM,WAAW;IAYI;IACA;IACA;IAbnB,QAAQ,CAAqB;IACrB,OAAO,GAAuB,IAAI,CAAC;IAC3C;;;;OAIG;IACc,KAAK,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC7C,OAAO,GAAG,KAAK,CAAC;IAExB,YACmB,IAAoB,EACpB,GAAc,EACd,KAAmB;QAFnB,SAAI,GAAJ,IAAI,CAAgB;QACpB,QAAG,GAAH,GAAG,CAAW;QACd,UAAK,GAAL,KAAK,CAAc;IACnC,CAAC;IAEJ,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAyC,EAAE,GAA2B,EAAE,OAAuB,EAAE,KAAmB,EAAE,gBAAwB;QAC9J,MAAM,IAAI,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,UAAU,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACrG,MAAM,IAAI,GAAG,IAAI,cAAc,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;QACzF,MAAM,OAAO,GAAG,IAAI,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAE1D,MAAM,KAAK,GAAG,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACpD,OAAO,CAAC,SAAS,GAAG,CAAC,GAAG,EAAE,EAAE;gBAC1B,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO;oBAAE,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;qBAC5C,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO;oBAAE,MAAM,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;YAClF,CAAC,CAAC;YACF,KAAK,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAC7B,MAAM,CAAC,IAAI,UAAU,CAAC,uBAAuB,EAAE,oCAAoC,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CACjK,CAAC;YACF,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,UAAU,CAAC,SAAS,EAAE,oCAAoC,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC,KAAK,EAAE,CAAC;QACtH,CAAC,CAAC,CAAC;QAEH,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,6BAA6B,CAAC,EAAE,CAAC,CAAC;QACpI,KAAK,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QAExE,IAAI,CAAC;YACH,OAAO,CAAC,QAAQ,GAAG,MAAM,KAAK,CAAC;QACjC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;YACvB,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YAC1B,MAAM,CAAC,CAAC;QACV,CAAC;QACD,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;QACzB,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QACzJ,OAAO,OAAO,CAAC;IACjB,CAAC;IAEO,SAAS,GAAqC,IAAI,CAAC;IAE3D,IAAI,CAAC,KAAgB,EAAE,IAAc;QACnC,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,UAAU,CAAC,cAAc,EAAE,2BAA2B,CAAC,CAAC,CAAC;QACrG,MAAM,MAAM,GAAG,KAAK,CAAC,SAAS,CAAC;QAC/B,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC7B,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC;YAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAM,CAAC,CAAC;QAC5E,OAAO,IAAI,OAAO,CAAc,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAClD,IAAI,CAAC,OAAO,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;YAC3C,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,mBAAmB,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;YACvG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;gBACzD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;gBACpB,MAAM,CAAC,IAAI,UAAU,CAAC,kBAAkB,EAAE,6BAA6B,CAAC,CAAC,CAAC;YAC5E,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,SAAS;QACb,IAAI,IAAI,CAAC,OAAO;YAAE,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5F,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,OAAuB;QACzC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACzF,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QACzB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QACtC,MAAM,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACjC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,UAAU,CAAC,gBAAgB,EAAE,wBAAwB,CAAC,CAAC,CAAC,CAAC;IAC3F,CAAC;IAEO,MAAM,CAAC,EAAkC;QAC/C,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC7B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,OAAO;YAAE,EAAE,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC;IAEO,MAAM,CAAC,IAAY;QACzB,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;YAAE,OAAO;QACzB,IAAI,GAAY,CAAC;QACjB,IAAI,CAAC;YACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACzB,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,iCAAiC,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;YAC/E,OAAO;QACT,CAAC;QACD,MAAM,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QACvC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,+BAA+B,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACzF,OAAO;QACT,CAAC;QACD,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC;QACxB,IAAI,IAAI,CAAC,SAAS;YAAE,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QAC/C,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO;YAAE,OAAO;QAEzD,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACjE,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,4BAA4B,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;YACjE,OAAO;QACT,CAAC;QACD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,KAAK,GAAG,CAAC,MAAM,CAAC;QACtD,QAAQ,GAAG,CAAC,IAAI,EAAE,CAAC;YACjB,KAAK,MAAM;gBACT,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YAC9B,KAAK,UAAU;gBACb,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YAClC,KAAK,OAAO;gBACV,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,YAAY,EAAE,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC;YACtF,KAAK,WAAW;gBACd,KAAK,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC,CAAC;gBACrI,OAAO;YACT,KAAK,UAAU;gBACb,KAAK,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC,CAAC;gBACrJ,OAAO;YACT,KAAK,UAAU;gBACb,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,CACrF,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC,EACvF,GAAG,EAAE,CAAC,SAAS,CAChB,CAAC;gBACF,OAAO;YACT,KAAK,MAAM;gBACT,IAAI,SAAS;oBAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;gBAC1D,OAAO;YACT,KAAK,WAAW;gBACd,IAAI,SAAS;oBAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;gBAC1D,OAAO;YACT,KAAK,OAAO;gBACV,IAAI,SAAS;oBAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;gBACtF,OAAO;QACX,CAAC;IACH,CAAC;IAEO,MAAM,CAAC,IAAmB,EAAE,MAA6B;QAC/D,qDAAqD;QACrD,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,SAAS;YAAE,OAAO;QAC3C,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,kBAAkB,EAAE,mCAAmC,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;QACnK,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACzB,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACtC,CAAC;CACF"}
@@ -0,0 +1,2 @@
1
+ export { createFakeProvider, FAKE_CLI_PATH } from "./fakeProvider.js";
2
+ export type { FakeProviderOptions } from "./fakeProvider.js";
@@ -0,0 +1,2 @@
1
+ export { createFakeProvider, FAKE_CLI_PATH } from "./fakeProvider.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,45 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Application-owned tools. The AI requests an operation; the application
4
+ * validates and applies it through its normal state-management path.
5
+ */
6
+ export interface ToolResult {
7
+ /** What the model reads back. */
8
+ content: string;
9
+ isError?: boolean;
10
+ }
11
+ export interface ToolCallContext {
12
+ documentId: string;
13
+ requestId: string;
14
+ /** The `callId` of this call's toolStarted/toolCompleted events. */
15
+ callId: string;
16
+ /** Aborted when the request is interrupted or the session closes. */
17
+ signal: AbortSignal;
18
+ }
19
+ /** What crosses to a provider: no code, only a description and a JSON Schema. */
20
+ export interface ToolSpec {
21
+ name: string;
22
+ description: string;
23
+ inputSchema: Record<string, unknown>;
24
+ }
25
+ export type ValidationResult = {
26
+ ok: true;
27
+ value: Record<string, unknown>;
28
+ } | {
29
+ ok: false;
30
+ message: string;
31
+ };
32
+ export interface ToolDefinition extends ToolSpec {
33
+ /** Rejects malformed arguments before `run` sees them. Without it, arguments reach `run` unchecked. */
34
+ validate?: (args: unknown) => ValidationResult;
35
+ run: (args: Record<string, unknown>, context: ToolCallContext) => Promise<ToolResult> | ToolResult;
36
+ }
37
+ /** A tool whose arguments are described and validated by a zod object schema. */
38
+ export declare function defineTool<S extends z.ZodObject>(definition: {
39
+ name: string;
40
+ description: string;
41
+ input: S;
42
+ run: (args: z.infer<S>, context: ToolCallContext) => Promise<ToolResult> | ToolResult;
43
+ }): ToolDefinition;
44
+ export declare function assertToolNames(tools: readonly ToolSpec[]): void;
45
+ export declare const toolSpec: ({ name, description, inputSchema }: ToolSpec) => ToolSpec;
package/dist/tools.js ADDED
@@ -0,0 +1,32 @@
1
+ import { z } from "zod";
2
+ const TOOL_NAME = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
3
+ /** A tool whose arguments are described and validated by a zod object schema. */
4
+ export function defineTool(definition) {
5
+ const { name, description, input, run } = definition;
6
+ const inputSchema = z.toJSONSchema(input);
7
+ delete inputSchema.$schema;
8
+ return {
9
+ name,
10
+ description,
11
+ inputSchema,
12
+ validate(args) {
13
+ const parsed = input.safeParse(args);
14
+ if (parsed.success)
15
+ return { ok: true, value: parsed.data };
16
+ return { ok: false, message: z.prettifyError(parsed.error) };
17
+ },
18
+ run: (args, context) => run(args, context),
19
+ };
20
+ }
21
+ export function assertToolNames(tools) {
22
+ const seen = new Set();
23
+ for (const tool of tools) {
24
+ if (!TOOL_NAME.test(tool.name))
25
+ throw new Error(`invalid tool name: ${tool.name}`);
26
+ if (seen.has(tool.name))
27
+ throw new Error(`duplicate tool name: ${tool.name}`);
28
+ seen.add(tool.name);
29
+ }
30
+ }
31
+ export const toolSpec = ({ name, description, inputSchema }) => ({ name, description, inputSchema });
32
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAqCxB,MAAM,SAAS,GAAG,+BAA+B,CAAC;AAElD,iFAAiF;AACjF,MAAM,UAAU,UAAU,CAAwB,UAKjD;IACC,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,UAAU,CAAC;IACrD,MAAM,WAAW,GAAG,CAAC,CAAC,YAAY,CAAC,KAAK,CAA4B,CAAC;IACrE,OAAO,WAAW,CAAC,OAAO,CAAC;IAC3B,OAAO;QACL,IAAI;QACJ,WAAW;QACX,WAAW;QACX,QAAQ,CAAC,IAAI;YACX,MAAM,MAAM,GAAG,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YACrC,IAAI,MAAM,CAAC,OAAO;gBAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,IAA+B,EAAE,CAAC;YACvF,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/D,CAAC;QACD,GAAG,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,IAAkB,EAAE,OAAO,CAAC;KACzD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAA0B;IACxD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QACnF,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAC9E,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,EAAY,EAAY,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ /** Kept in step with package.json by the release process. */
2
+ export declare const VERSION = "0.1.1";
@@ -0,0 +1,3 @@
1
+ /** Kept in step with package.json by the release process. */
2
+ export const VERSION = "0.1.1";
3
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC"}
@@ -0,0 +1,230 @@
1
+ #!/usr/bin/env node
2
+ // A stand-in for a provider CLI, for deterministic tests. It behaves like a
3
+ // real one where it matters to the library: a separate process, its own
4
+ // sign-in state in a home directory (FAKE_AI_HOME), a version, a JSON-lines
5
+ // session protocol, persisted conversations, and failures on demand.
6
+ //
7
+ // Commands: --version | login --label <l> | status | logout | models | serve [--resume <id>]
8
+ //
9
+ // What a turn does depends on words in the request's last paragraph (an
10
+ // editor may put its own context before the person's words):
11
+ // tool <name> [<json>] calls an application tool and reports the result
12
+ // mcp <server> <tool> [<json>] calls a tool of one of the user's MCP servers
13
+ // ask asks the user a question and reports the answer
14
+ // slow streams for two seconds, stopping when interrupted
15
+ // ignore-interrupt streams for two seconds and ignores interruptions
16
+ // late after an interruption, still sends a stale text
17
+ // crash exits in the middle of the turn
18
+ // garbage prints a line that is not JSON first
19
+ // rate-limit fails with a rate-limit error
20
+ // env reports which credential variables it inherited
21
+ // anything else echoes the request
22
+ //
23
+ // Environment toggles: FAKE_AI_VERSION, FAKE_AI_LOGIN=fail|hang,
24
+ // FAKE_AI_SERVE=silent (never becomes ready).
25
+ import { createInterface } from "node:readline";
26
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
27
+ import { join } from "node:path";
28
+ import { randomUUID } from "node:crypto";
29
+
30
+ const VERSION = process.env.FAKE_AI_VERSION ?? "1.4.0";
31
+ const HOME = process.env.FAKE_AI_HOME;
32
+ const [command, ...rest] = process.argv.slice(2);
33
+ const flag = (name) => {
34
+ const i = rest.indexOf(name);
35
+ return i === -1 ? undefined : rest[i + 1];
36
+ };
37
+ const out = (msg) => process.stdout.write(JSON.stringify(msg) + "\n");
38
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
39
+
40
+ if (command === "--version") {
41
+ console.log(`fake-ai ${VERSION}`);
42
+ process.exit(0);
43
+ }
44
+ if (!HOME) {
45
+ console.error("FAKE_AI_HOME is not set");
46
+ process.exit(2);
47
+ }
48
+ const authFile = join(HOME, "auth.json");
49
+ const readAuth = () => (existsSync(authFile) ? JSON.parse(readFileSync(authFile, "utf8")) : null);
50
+
51
+ switch (command) {
52
+ case "login": {
53
+ if (process.env.FAKE_AI_LOGIN === "fail") {
54
+ console.error("login rejected by the provider");
55
+ process.exit(1);
56
+ }
57
+ if (process.env.FAKE_AI_LOGIN === "hang") await sleep(60_000);
58
+ mkdirSync(HOME, { recursive: true });
59
+ writeFileSync(authFile, JSON.stringify({ user: flag("--label") ?? "user", token: `fake-token-${randomUUID()}`, expired: false }));
60
+ console.log("signed in");
61
+ process.exit(0);
62
+ }
63
+ case "status": {
64
+ const auth = readAuth();
65
+ out(auth ? { signedIn: true, user: auth.user, expired: Boolean(auth.expired) } : { signedIn: false });
66
+ process.exit(0);
67
+ }
68
+ case "logout":
69
+ rmSync(authFile, { force: true });
70
+ process.exit(0);
71
+ case "models":
72
+ out({
73
+ models: [
74
+ { id: "fake-fast", label: "Fake Fast", default: true },
75
+ { id: "fake-deep", label: "Fake Deep", efforts: ["low", "medium", "high"] },
76
+ ],
77
+ });
78
+ process.exit(0);
79
+ case "serve":
80
+ await serve();
81
+ break;
82
+ default:
83
+ console.error(`unknown command: ${command}`);
84
+ process.exit(2);
85
+ }
86
+
87
+ async function serve() {
88
+ const auth = readAuth();
89
+ if (!auth) {
90
+ out({ type: "fatal", code: "auth", message: "not signed in" });
91
+ process.exit(3);
92
+ }
93
+ if (auth.expired) {
94
+ out({ type: "fatal", code: "expired", message: "the sign-in expired" });
95
+ process.exit(3);
96
+ }
97
+ if (process.env.FAKE_AI_SERVE === "silent") {
98
+ await sleep(60_000);
99
+ return;
100
+ }
101
+
102
+ const sessionsDir = join(HOME, "sessions");
103
+ mkdirSync(sessionsDir, { recursive: true });
104
+ const resume = flag("--resume");
105
+ const sessionId = resume && existsSync(join(sessionsDir, `${resume}.json`)) ? resume : randomUUID();
106
+ const file = join(sessionsDir, `${sessionId}.json`);
107
+ const history = existsSync(file) ? JSON.parse(readFileSync(file, "utf8")) : { turns: [] };
108
+ const save = () => writeFileSync(file, JSON.stringify(history));
109
+ save();
110
+
111
+ let options = { tools: [], instructions: "", model: undefined, effort: undefined };
112
+ let current = null;
113
+ const waiting = new Map();
114
+
115
+ out({ type: "ready", sessionId, resumed: sessionId === resume, user: auth.user, turns: history.turns.length });
116
+
117
+ const rl = createInterface({ input: process.stdin });
118
+ rl.on("line", (line) => {
119
+ let msg;
120
+ try {
121
+ msg = JSON.parse(line);
122
+ } catch {
123
+ return;
124
+ }
125
+ switch (msg.type) {
126
+ case "init":
127
+ options = { ...options, ...msg };
128
+ break;
129
+ case "options":
130
+ options = { ...options, model: msg.model, effort: msg.effort };
131
+ break;
132
+ case "turn":
133
+ current = { turnId: msg.turnId, interrupted: false };
134
+ runTurn(current, lastParagraph(msg.text)).catch((e) => out({ type: "error", turnId: msg.turnId, code: "internal", message: String(e?.message ?? e) }));
135
+ break;
136
+ case "interrupt":
137
+ if (current) current.interrupted = true;
138
+ for (const [id, resolve] of waiting) {
139
+ waiting.delete(id);
140
+ resolve(null);
141
+ }
142
+ break;
143
+ case "tool_result":
144
+ case "answer": {
145
+ const id = msg.callId ?? msg.questionId;
146
+ waiting.get(id)?.(msg);
147
+ waiting.delete(id);
148
+ break;
149
+ }
150
+ case "exit":
151
+ process.exit(0);
152
+ }
153
+ });
154
+ rl.on("close", () => process.exit(0));
155
+
156
+ const wait = (id) => new Promise((resolve) => waiting.set(id, resolve));
157
+ const lastParagraph = (text) => text.trim().split(/\n\s*\n/).pop() ?? "";
158
+
159
+ async function runTurn(turn, text) {
160
+ const send = (m) => out({ ...m, turnId: turn.turnId });
161
+ const stopped = () => turn.interrupted && !/ignore-interrupt/.test(text);
162
+ const cancelled = async () => {
163
+ send({ type: "cancelled" });
164
+ current = null;
165
+ if (/late/.test(text)) {
166
+ await sleep(30);
167
+ send({ type: "text", delta: "LATE OUTPUT" });
168
+ send({ type: "done" });
169
+ }
170
+ };
171
+
172
+ send({ type: "activity", phase: "thinking" });
173
+ if (/garbage/.test(text)) process.stdout.write("this is not json\n");
174
+ if (/crash/.test(text)) {
175
+ send({ type: "text", delta: "about to crash" });
176
+ await sleep(20);
177
+ process.exit(70);
178
+ }
179
+ if (/rate-limit/.test(text)) {
180
+ send({ type: "error", code: "rate_limited", message: "too many requests" });
181
+ current = null;
182
+ return;
183
+ }
184
+
185
+ let reply;
186
+ const tool = /\btool (\w+)(?: (\{.*\}))?/.exec(text);
187
+ const mcp = /\bmcp ([\w-]+) (\w+)(?: (\{.*\}))?/.exec(text);
188
+ if (mcp) {
189
+ const callId = randomUUID();
190
+ send({ type: "activity", phase: "applying" });
191
+ send({ type: "mcp_call", callId, server: mcp[1], name: mcp[2], args: mcp[3] ? JSON.parse(mcp[3]) : {} });
192
+ const result = await wait(callId);
193
+ if (stopped() || !result) return cancelled();
194
+ reply = `${mcp[1]}.${mcp[2]} said: ${result.content}${result.isError ? " (error)" : ""}`;
195
+ } else if (tool) {
196
+ const callId = randomUUID();
197
+ send({ type: "activity", phase: "applying" });
198
+ send({ type: "tool_call", callId, name: tool[1], args: tool[2] ? JSON.parse(tool[2]) : {} });
199
+ const result = await wait(callId);
200
+ if (stopped() || !result) return cancelled();
201
+ reply = `Tool ${tool[1]} said: ${result.content}${result.isError ? " (error)" : ""}`;
202
+ } else if (/\bask\b/.test(text)) {
203
+ const questionId = randomUUID();
204
+ send({ type: "question", questionId, prompt: "Which color?", options: ["red", "blue"] });
205
+ const answer = await wait(questionId);
206
+ if (stopped() || !answer) return cancelled();
207
+ reply = `You chose ${answer.answer}`;
208
+ } else if (/slow|ignore-interrupt|late/.test(text)) {
209
+ for (let i = 0; i < 40; i++) {
210
+ if (stopped()) return cancelled();
211
+ send({ type: "text", delta: "." });
212
+ await sleep(50);
213
+ }
214
+ reply = "done slowly";
215
+ } else if (/\benv\b/.test(text)) {
216
+ const seen = (name) => `${name}=${process.env[name] ? "present" : "absent"}`;
217
+ reply = [seen("ANTHROPIC_API_KEY"), seen("OPENAI_API_KEY"), seen("CLAUDE_CONFIG_DIR"), `user=${auth.user}`].join(" ");
218
+ } else {
219
+ reply = `Echo: ${text} [history ${history.turns.length}, model ${options.model ?? "default"}, tools ${options.tools.length}]`;
220
+ }
221
+
222
+ const third = Math.ceil(reply.length / 3);
223
+ for (let i = 0; i < reply.length; i += third) send({ type: "text", delta: reply.slice(i, i + third) });
224
+ history.turns.push(text);
225
+ save();
226
+ send({ type: "usage", inputTokens: text.length, outputTokens: reply.length });
227
+ send({ type: "done" });
228
+ current = null;
229
+ }
230
+ }