@coder/ai-sdk-agent 0.1.0

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/dist/index.js ADDED
@@ -0,0 +1,930 @@
1
+ // src/agent/coder-agent.ts
2
+ import {
3
+ stepCountIs,
4
+ ToolLoopAgent
5
+ } from "ai";
6
+
7
+ // src/errors.ts
8
+ var CoderAgentError = class extends Error {
9
+ name = "CoderAgentError";
10
+ constructor(message, options) {
11
+ super(message, options);
12
+ }
13
+ };
14
+ var CoderApiError = class extends CoderAgentError {
15
+ name = "CoderApiError";
16
+ status;
17
+ detail;
18
+ method;
19
+ path;
20
+ constructor(args) {
21
+ super(
22
+ `Coder API ${args.method} ${args.path} failed (${args.status}): ${args.message}` + (args.detail ? ` \u2014 ${args.detail}` : "")
23
+ );
24
+ this.status = args.status;
25
+ this.detail = args.detail;
26
+ this.method = args.method;
27
+ this.path = args.path;
28
+ }
29
+ };
30
+ var CoderChatError = class extends CoderAgentError {
31
+ name = "CoderChatError";
32
+ kind;
33
+ provider;
34
+ retryable;
35
+ statusCode;
36
+ constructor(payload) {
37
+ super(payload.detail ? `${payload.message}: ${payload.detail}` : payload.message);
38
+ this.kind = payload.kind;
39
+ this.provider = payload.provider;
40
+ this.retryable = payload.retryable ?? false;
41
+ this.statusCode = payload.status_code;
42
+ }
43
+ };
44
+
45
+ // src/coder/types.ts
46
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
47
+ "waiting",
48
+ "completed",
49
+ "error",
50
+ "requires_action"
51
+ ]);
52
+
53
+ // src/coder/ws.ts
54
+ import NodeWebSocket from "ws";
55
+ var defaultFactory = (url, { headers }) => {
56
+ return new NodeWebSocket(url, { headers });
57
+ };
58
+ function httpToWs(baseUrl) {
59
+ if (baseUrl.startsWith("https://")) return `wss://${baseUrl.slice("https://".length)}`;
60
+ if (baseUrl.startsWith("http://")) return `ws://${baseUrl.slice("http://".length)}`;
61
+ return baseUrl;
62
+ }
63
+ async function* streamChatEvents(options) {
64
+ const { baseUrl, token, chatId, afterId, signal } = options;
65
+ const factory = options.webSocketFactory ?? defaultFactory;
66
+ const wsBase = httpToWs(baseUrl);
67
+ const query = afterId !== void 0 ? `?after_id=${afterId}` : "";
68
+ const url = `${wsBase}/api/experimental/chats/${chatId}/stream${query}`;
69
+ const queue = [];
70
+ let resolveNext;
71
+ let finished = false;
72
+ let failure;
73
+ const wake = () => {
74
+ resolveNext?.();
75
+ resolveNext = void 0;
76
+ };
77
+ const ws = factory(url, { headers: { "Coder-Session-Token": token } });
78
+ const onAbort = () => {
79
+ finished = true;
80
+ try {
81
+ ws.close(1e3);
82
+ } catch {
83
+ }
84
+ wake();
85
+ };
86
+ let abortListenerAdded = false;
87
+ if (signal) {
88
+ if (signal.aborted) onAbort();
89
+ else {
90
+ signal.addEventListener("abort", onAbort, { once: true });
91
+ abortListenerAdded = true;
92
+ }
93
+ }
94
+ const onMessage = (ev) => {
95
+ if (finished) return;
96
+ let batch;
97
+ try {
98
+ const data = typeof ev.data === "string" ? ev.data : String(ev.data);
99
+ batch = JSON.parse(data);
100
+ } catch (err) {
101
+ failure = new CoderAgentError("failed to parse chat stream frame", { cause: err });
102
+ finished = true;
103
+ wake();
104
+ return;
105
+ }
106
+ if (Array.isArray(batch)) {
107
+ for (const e of batch) queue.push(e);
108
+ } else if (batch && typeof batch === "object") {
109
+ queue.push(batch);
110
+ }
111
+ wake();
112
+ };
113
+ const onError = (ev) => {
114
+ if (finished) return;
115
+ const message = ev && typeof ev === "object" && "message" in ev ? String(ev.message) : "chat stream socket error";
116
+ failure = new CoderAgentError(message);
117
+ finished = true;
118
+ wake();
119
+ };
120
+ const onClose = () => {
121
+ finished = true;
122
+ wake();
123
+ };
124
+ ws.addEventListener("message", onMessage);
125
+ ws.addEventListener("error", onError);
126
+ ws.addEventListener("close", onClose);
127
+ try {
128
+ while (true) {
129
+ while (queue.length > 0) {
130
+ const next = queue.shift();
131
+ yield next;
132
+ }
133
+ if (failure) throw failure;
134
+ if (finished) return;
135
+ await new Promise((resolve) => {
136
+ resolveNext = resolve;
137
+ });
138
+ }
139
+ } finally {
140
+ finished = true;
141
+ if (signal && abortListenerAdded) signal.removeEventListener("abort", onAbort);
142
+ ws.removeEventListener("message", onMessage);
143
+ ws.removeEventListener("error", onError);
144
+ ws.removeEventListener("close", onClose);
145
+ try {
146
+ ws.close(1e3);
147
+ } catch {
148
+ }
149
+ }
150
+ }
151
+
152
+ // src/coder/client.ts
153
+ var API_PREFIX = "/api/experimental/chats";
154
+ var CoderChatClient = class {
155
+ baseUrl;
156
+ #token;
157
+ #fetch;
158
+ #webSocketFactory;
159
+ constructor(options) {
160
+ this.baseUrl = options.baseUrl.replace(/\/$/, "");
161
+ this.#token = options.token;
162
+ this.#fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
163
+ this.#webSocketFactory = options.webSocketFactory;
164
+ }
165
+ async #request(method, path, body, signal) {
166
+ const headers = { "Coder-Session-Token": this.#token };
167
+ if (body !== void 0) headers["Content-Type"] = "application/json";
168
+ const res = await this.#fetch(`${this.baseUrl}${path}`, {
169
+ method,
170
+ headers,
171
+ body: body === void 0 ? void 0 : JSON.stringify(body),
172
+ signal
173
+ });
174
+ const text = await res.text();
175
+ let parsed;
176
+ try {
177
+ parsed = text.length > 0 ? JSON.parse(text) : void 0;
178
+ } catch {
179
+ parsed = void 0;
180
+ }
181
+ if (!res.ok) {
182
+ const errObj = parsed ?? {};
183
+ throw new CoderApiError({
184
+ status: res.status,
185
+ method,
186
+ path,
187
+ message: errObj.message ?? res.statusText ?? "request failed",
188
+ detail: errObj.detail
189
+ });
190
+ }
191
+ return parsed;
192
+ }
193
+ // --- REST -----------------------------------------------------------------
194
+ listModelConfigs(signal) {
195
+ return this.#request(
196
+ "GET",
197
+ `${API_PREFIX}/model-configs`,
198
+ void 0,
199
+ signal
200
+ );
201
+ }
202
+ createChat(req, signal) {
203
+ return this.#request("POST", API_PREFIX, req, signal);
204
+ }
205
+ getChat(chatId, signal) {
206
+ return this.#request("GET", `${API_PREFIX}/${chatId}`, void 0, signal);
207
+ }
208
+ createChatMessage(chatId, req, signal) {
209
+ return this.#request(
210
+ "POST",
211
+ `${API_PREFIX}/${chatId}/messages`,
212
+ req,
213
+ signal
214
+ );
215
+ }
216
+ getMessages(chatId, opts, signal) {
217
+ const params = new URLSearchParams();
218
+ if (opts?.before_id !== void 0) params.set("before_id", String(opts.before_id));
219
+ if (opts?.after_id !== void 0) params.set("after_id", String(opts.after_id));
220
+ if (opts?.limit !== void 0) params.set("limit", String(opts.limit));
221
+ const q = params.toString();
222
+ return this.#request(
223
+ "GET",
224
+ `${API_PREFIX}/${chatId}/messages${q ? `?${q}` : ""}`,
225
+ void 0,
226
+ signal
227
+ );
228
+ }
229
+ submitToolResults(chatId, req, signal) {
230
+ return this.#request("POST", `${API_PREFIX}/${chatId}/tool-results`, req, signal);
231
+ }
232
+ interruptChat(chatId, signal) {
233
+ return this.#request("POST", `${API_PREFIX}/${chatId}/interrupt`, void 0, signal);
234
+ }
235
+ updateChat(chatId, req, signal) {
236
+ return this.#request("PATCH", `${API_PREFIX}/${chatId}`, req, signal);
237
+ }
238
+ /** Convenience: archive a chat (soft-hide; safe for cleanup). */
239
+ archiveChat(chatId, signal) {
240
+ return this.updateChat(chatId, { archived: true }, signal);
241
+ }
242
+ // --- Streaming ------------------------------------------------------------
243
+ streamEvents(chatId, opts) {
244
+ return streamChatEvents({
245
+ baseUrl: this.baseUrl,
246
+ token: this.#token,
247
+ chatId,
248
+ afterId: opts?.afterId,
249
+ signal: opts?.signal,
250
+ webSocketFactory: this.#webSocketFactory
251
+ });
252
+ }
253
+ // --- Helpers --------------------------------------------------------------
254
+ /**
255
+ * Resolves a user-friendly model hint to a model-config UUID.
256
+ *
257
+ * Accepts: a config UUID (returned as-is), a `provider:model` id
258
+ * (e.g. `anthropic:claude-haiku-4-5-20251001`), a bare model id, or a
259
+ * display-name substring (case-insensitive). Returns `undefined` if no
260
+ * match is found, in which case the caller should let chatd pick the default.
261
+ */
262
+ async resolveModelConfigId(hint, signal) {
263
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
264
+ if (UUID_RE.test(hint)) return hint;
265
+ const configs = await this.listModelConfigs(signal);
266
+ const lower = hint.toLowerCase();
267
+ const colon = hint.indexOf(":");
268
+ const provider = colon >= 0 ? hint.slice(0, colon).toLowerCase() : void 0;
269
+ const model = colon >= 0 ? hint.slice(colon + 1).toLowerCase() : lower;
270
+ const candidates = configs.filter((c) => c.enabled !== false);
271
+ const pool = candidates.length > 0 ? candidates : configs;
272
+ const exact = pool.find(
273
+ (c) => c.model.toLowerCase() === model && (provider === void 0 || c.provider.toLowerCase() === provider)
274
+ );
275
+ if (exact) return exact.id;
276
+ const byModel = pool.find((c) => c.model.toLowerCase() === model);
277
+ if (byModel) return byModel.id;
278
+ const byDisplay = pool.find((c) => c.display_name.toLowerCase().includes(lower));
279
+ if (byDisplay) return byDisplay.id;
280
+ const byModelSubstring = pool.find((c) => c.model.toLowerCase().includes(model));
281
+ return byModelSubstring?.id;
282
+ }
283
+ };
284
+
285
+ // src/model/prompt.ts
286
+ function extractSystemPrompt(prompt) {
287
+ const parts = prompt.filter(
288
+ (m) => m.role === "system"
289
+ );
290
+ if (parts.length === 0) return void 0;
291
+ const joined = parts.map((m) => m.content).join("\n\n").trim();
292
+ return joined.length > 0 ? joined : void 0;
293
+ }
294
+ function userContentToInputParts(message) {
295
+ const out = [];
296
+ for (const part of message.content) {
297
+ if (part.type === "text" && part.text.length > 0) {
298
+ out.push({ type: "text", text: part.text });
299
+ }
300
+ }
301
+ return out;
302
+ }
303
+ function toolResultOutputToChatd(output) {
304
+ switch (output.type) {
305
+ case "text":
306
+ return { value: output.value, isError: false };
307
+ case "json":
308
+ return { value: output.value, isError: false };
309
+ case "error-text":
310
+ return { value: output.value, isError: true };
311
+ case "error-json":
312
+ return { value: output.value, isError: true };
313
+ case "execution-denied":
314
+ return { value: { execution_denied: true, reason: output.reason }, isError: true };
315
+ case "content":
316
+ return { value: output.value, isError: false };
317
+ default:
318
+ return { value: output, isError: false };
319
+ }
320
+ }
321
+ function classifyTurnAction(prompt) {
322
+ if (prompt.length === 0) return { kind: "noop" };
323
+ const last = prompt[prompt.length - 1];
324
+ if (!last) return { kind: "noop" };
325
+ if (last.role === "tool") {
326
+ const toolResults = [];
327
+ for (let i = prompt.length - 1; i >= 0; i--) {
328
+ const m = prompt[i];
329
+ if (!m || m.role !== "tool") break;
330
+ for (const part of m.content) {
331
+ if (part.type !== "tool-result") continue;
332
+ const { value, isError } = toolResultOutputToChatd(part.output);
333
+ toolResults.unshift({ tool_call_id: part.toolCallId, output: value, is_error: isError });
334
+ }
335
+ }
336
+ return { kind: "resume", toolResults };
337
+ }
338
+ if (last.role === "user") {
339
+ return { kind: "new-turn", content: userContentToInputParts(last) };
340
+ }
341
+ return { kind: "noop" };
342
+ }
343
+ function toolsToDynamicTools(tools) {
344
+ if (!tools) return [];
345
+ const out = [];
346
+ for (const tool of tools) {
347
+ if (tool.type !== "function") continue;
348
+ out.push({
349
+ name: tool.name,
350
+ description: tool.description,
351
+ input_schema: tool.inputSchema
352
+ });
353
+ }
354
+ return out;
355
+ }
356
+ function dynamicToolNames(tools) {
357
+ const names = /* @__PURE__ */ new Set();
358
+ for (const tool of tools ?? []) {
359
+ if (tool.type === "function") names.add(tool.name);
360
+ }
361
+ return names;
362
+ }
363
+
364
+ // src/model/translate.ts
365
+ function mapUsage(u) {
366
+ return {
367
+ inputTokens: {
368
+ total: u?.input_tokens,
369
+ noCache: void 0,
370
+ cacheRead: u?.cache_read_tokens,
371
+ cacheWrite: u?.cache_creation_tokens
372
+ },
373
+ outputTokens: {
374
+ total: u?.output_tokens,
375
+ text: void 0,
376
+ reasoning: u?.reasoning_tokens
377
+ }
378
+ };
379
+ }
380
+ function jsonResult(value) {
381
+ return value ?? {};
382
+ }
383
+ var TurnTranslator = class {
384
+ #dynamicToolNames;
385
+ #seq = 0;
386
+ #text = { id: void 0, len: 0, sawDelta: false };
387
+ #reasoning = { id: void 0, len: 0, sawDelta: false };
388
+ #currentAssistantId;
389
+ #serverToolCalls = /* @__PURE__ */ new Set();
390
+ #serverToolResults = /* @__PURE__ */ new Set();
391
+ #clientToolCalls = /* @__PURE__ */ new Set();
392
+ #clientToolCallSeen = false;
393
+ #usage;
394
+ #error;
395
+ #terminalStatus;
396
+ #maxMessageId = 0;
397
+ constructor(opts) {
398
+ this.#dynamicToolNames = opts.dynamicToolNames;
399
+ }
400
+ get terminalStatus() {
401
+ return this.#terminalStatus;
402
+ }
403
+ /** Whether a client (custom) tool call has been emitted this turn. */
404
+ get clientToolCallSeen() {
405
+ return this.#clientToolCallSeen;
406
+ }
407
+ get maxMessageId() {
408
+ return this.#maxMessageId;
409
+ }
410
+ get error() {
411
+ return this.#error;
412
+ }
413
+ // --- block helpers --------------------------------------------------------
414
+ #openText(out) {
415
+ if (this.#reasoning.id) this.#closeReasoning(out);
416
+ if (!this.#text.id) {
417
+ this.#text.id = `text-${++this.#seq}`;
418
+ this.#text.len = 0;
419
+ out.push({ type: "text-start", id: this.#text.id });
420
+ }
421
+ }
422
+ #closeText(out) {
423
+ if (this.#text.id) {
424
+ out.push({ type: "text-end", id: this.#text.id });
425
+ this.#text.id = void 0;
426
+ this.#text.len = 0;
427
+ }
428
+ }
429
+ #openReasoning(out) {
430
+ if (this.#text.id) this.#closeText(out);
431
+ if (!this.#reasoning.id) {
432
+ this.#reasoning.id = `reasoning-${++this.#seq}`;
433
+ this.#reasoning.len = 0;
434
+ out.push({ type: "reasoning-start", id: this.#reasoning.id });
435
+ }
436
+ }
437
+ #closeReasoning(out) {
438
+ if (this.#reasoning.id) {
439
+ out.push({ type: "reasoning-end", id: this.#reasoning.id });
440
+ this.#reasoning.id = void 0;
441
+ this.#reasoning.len = 0;
442
+ }
443
+ }
444
+ #emitTextUpTo(out, full) {
445
+ if (full.length <= this.#text.len && this.#text.id) return;
446
+ this.#openText(out);
447
+ if (full.length > this.#text.len) {
448
+ out.push({
449
+ type: "text-delta",
450
+ id: this.#text.id,
451
+ delta: full.slice(this.#text.len)
452
+ });
453
+ this.#text.len = full.length;
454
+ }
455
+ }
456
+ #emitReasoningUpTo(out, full) {
457
+ if (full.length <= this.#reasoning.len && this.#reasoning.id) return;
458
+ this.#openReasoning(out);
459
+ if (full.length > this.#reasoning.len) {
460
+ out.push({
461
+ type: "reasoning-delta",
462
+ id: this.#reasoning.id,
463
+ delta: full.slice(this.#reasoning.len)
464
+ });
465
+ this.#reasoning.len = full.length;
466
+ }
467
+ }
468
+ // --- tool helpers ---------------------------------------------------------
469
+ #isClientTool(name) {
470
+ return name !== void 0 && this.#dynamicToolNames.has(name);
471
+ }
472
+ #emitServerToolCall(out, part) {
473
+ const id = part.tool_call_id;
474
+ const name = part.tool_name;
475
+ if (!id || !name) return;
476
+ if (part.args === void 0) return;
477
+ if (this.#serverToolCalls.has(id)) return;
478
+ this.#serverToolCalls.add(id);
479
+ out.push({ type: "tool-input-start", id, toolName: name, providerExecuted: true });
480
+ out.push({ type: "tool-input-end", id });
481
+ out.push({
482
+ type: "tool-call",
483
+ toolCallId: id,
484
+ toolName: name,
485
+ input: typeof part.args === "string" ? part.args : JSON.stringify(part.args),
486
+ providerExecuted: true
487
+ });
488
+ }
489
+ #emitServerToolResult(out, part) {
490
+ const id = part.tool_call_id;
491
+ const name = part.tool_name;
492
+ if (!id || !name) return;
493
+ if (part.result === void 0) return;
494
+ if (this.#serverToolResults.has(id)) return;
495
+ this.#serverToolResults.add(id);
496
+ out.push({
497
+ type: "tool-result",
498
+ toolCallId: id,
499
+ toolName: name,
500
+ result: jsonResult(part.result),
501
+ isError: part.is_error ?? false
502
+ });
503
+ }
504
+ // --- ingest ---------------------------------------------------------------
505
+ ingest(ev) {
506
+ const out = [];
507
+ switch (ev.type) {
508
+ case "message_part":
509
+ this.#ingestMessagePart(out, ev);
510
+ break;
511
+ case "message":
512
+ if (ev.message) this.#ingestMessage(out, ev.message);
513
+ break;
514
+ case "action_required":
515
+ for (const tc of ev.action_required?.tool_calls ?? []) {
516
+ if (this.#clientToolCalls.has(tc.tool_call_id)) continue;
517
+ this.#closeText(out);
518
+ this.#closeReasoning(out);
519
+ this.#clientToolCalls.add(tc.tool_call_id);
520
+ this.#clientToolCallSeen = true;
521
+ out.push({ type: "tool-input-start", id: tc.tool_call_id, toolName: tc.tool_name });
522
+ out.push({ type: "tool-input-end", id: tc.tool_call_id });
523
+ out.push({
524
+ type: "tool-call",
525
+ toolCallId: tc.tool_call_id,
526
+ toolName: tc.tool_name,
527
+ input: tc.args
528
+ });
529
+ }
530
+ break;
531
+ case "error":
532
+ if (ev.error) {
533
+ this.#error = ev.error;
534
+ out.push({ type: "error", error: new CoderChatError(ev.error) });
535
+ }
536
+ break;
537
+ case "status":
538
+ if (ev.status && TERMINAL_STATUSES.has(ev.status.status)) {
539
+ this.#terminalStatus = ev.status.status;
540
+ }
541
+ break;
542
+ default:
543
+ break;
544
+ }
545
+ return out;
546
+ }
547
+ #ingestMessagePart(out, ev) {
548
+ const mp = ev.message_part;
549
+ if (!mp || mp.role !== "assistant" && mp.role !== "tool") return;
550
+ const part = mp.part;
551
+ switch (part.type) {
552
+ case "text":
553
+ this.#text.sawDelta = true;
554
+ this.#openText(out);
555
+ if (part.text) {
556
+ out.push({ type: "text-delta", id: this.#text.id, delta: part.text });
557
+ this.#text.len += part.text.length;
558
+ }
559
+ break;
560
+ case "reasoning":
561
+ this.#reasoning.sawDelta = true;
562
+ this.#openReasoning(out);
563
+ if (part.text) {
564
+ out.push({ type: "reasoning-delta", id: this.#reasoning.id, delta: part.text });
565
+ this.#reasoning.len += part.text.length;
566
+ }
567
+ break;
568
+ case "tool-call":
569
+ this.#closeText(out);
570
+ this.#closeReasoning(out);
571
+ if (!this.#isClientTool(part.tool_name)) this.#emitServerToolCall(out, part);
572
+ break;
573
+ case "tool-result":
574
+ if (!this.#isClientTool(part.tool_name)) this.#emitServerToolResult(out, part);
575
+ break;
576
+ default:
577
+ break;
578
+ }
579
+ }
580
+ #ingestMessage(out, message) {
581
+ if (message.id > this.#maxMessageId) this.#maxMessageId = message.id;
582
+ if (message.usage && (message.role === "assistant" || message.role === "tool")) {
583
+ this.#usage = message.usage;
584
+ }
585
+ const content = message.content ?? [];
586
+ if (message.role === "assistant") {
587
+ if (this.#currentAssistantId !== void 0 && this.#currentAssistantId !== message.id) {
588
+ this.#closeText(out);
589
+ this.#closeReasoning(out);
590
+ this.#text.sawDelta = false;
591
+ this.#reasoning.sawDelta = false;
592
+ }
593
+ this.#currentAssistantId = message.id;
594
+ if (!this.#text.sawDelta) {
595
+ const full = content.filter((p) => p.type === "text").map((p) => p.text ?? "").join("");
596
+ if (full.length > 0) this.#emitTextUpTo(out, full);
597
+ }
598
+ if (!this.#reasoning.sawDelta) {
599
+ const full = content.filter((p) => p.type === "reasoning").map((p) => p.text ?? "").join("");
600
+ if (full.length > 0) this.#emitReasoningUpTo(out, full);
601
+ }
602
+ for (const part of content) {
603
+ if (part.type === "tool-call" && !this.#isClientTool(part.tool_name))
604
+ this.#emitServerToolCall(out, part);
605
+ else if (part.type === "tool-result" && !this.#isClientTool(part.tool_name))
606
+ this.#emitServerToolResult(out, part);
607
+ }
608
+ } else if (message.role === "tool") {
609
+ for (const part of content) {
610
+ if (part.type === "tool-result" && !this.#isClientTool(part.tool_name))
611
+ this.#emitServerToolResult(out, part);
612
+ }
613
+ }
614
+ }
615
+ // --- finish ---------------------------------------------------------------
616
+ finish() {
617
+ const out = [];
618
+ this.#closeText(out);
619
+ this.#closeReasoning(out);
620
+ let unified;
621
+ if (this.#error || this.#terminalStatus === "error") unified = "error";
622
+ else if (this.#clientToolCallSeen || this.#terminalStatus === "requires_action")
623
+ unified = "tool-calls";
624
+ else unified = "stop";
625
+ out.push({
626
+ type: "finish",
627
+ usage: mapUsage(this.#usage),
628
+ finishReason: { unified, raw: this.#terminalStatus }
629
+ });
630
+ return out;
631
+ }
632
+ };
633
+
634
+ // src/model/language-model.ts
635
+ var EMPTY_USAGE = {
636
+ inputTokens: {
637
+ total: void 0,
638
+ noCache: void 0,
639
+ cacheRead: void 0,
640
+ cacheWrite: void 0
641
+ },
642
+ outputTokens: { total: void 0, text: void 0, reasoning: void 0 }
643
+ };
644
+ var CoderLanguageModel = class {
645
+ specificationVersion = "v3";
646
+ provider = "coder.chatd";
647
+ modelId;
648
+ supportedUrls = {};
649
+ #config;
650
+ #chatId;
651
+ #lastSeenMessageId = 0;
652
+ #resolvedModelConfigId;
653
+ #modelResolved = false;
654
+ #submittedToolCallIds = /* @__PURE__ */ new Set();
655
+ #inFlight = false;
656
+ constructor(config) {
657
+ this.#config = config;
658
+ this.modelId = config.model ?? "chatd";
659
+ this.#chatId = config.chatId;
660
+ }
661
+ get chatId() {
662
+ return this.#chatId;
663
+ }
664
+ /** Drops the current session so the next turn creates a fresh chat. */
665
+ resetSession() {
666
+ this.#chatId = void 0;
667
+ this.#lastSeenMessageId = 0;
668
+ this.#submittedToolCallIds.clear();
669
+ }
670
+ async #resolveModelConfigId(signal) {
671
+ if (this.#modelResolved) return this.#resolvedModelConfigId;
672
+ if (this.#config.model) {
673
+ this.#resolvedModelConfigId = await this.#config.client.resolveModelConfigId(
674
+ this.#config.model,
675
+ signal
676
+ );
677
+ }
678
+ this.#modelResolved = true;
679
+ return this.#resolvedModelConfigId;
680
+ }
681
+ async *#runTurn(options) {
682
+ if (this.#inFlight) {
683
+ throw new CoderAgentError(
684
+ "A generation is already in flight on this CoderAgent (single-flight). Use a separate CoderAgent instance for concurrent sessions."
685
+ );
686
+ }
687
+ this.#inFlight = true;
688
+ try {
689
+ const { prompt, abortSignal: signal } = options;
690
+ yield { type: "stream-start", warnings: [] };
691
+ const action = classifyTurnAction(prompt);
692
+ if (action.kind === "noop") {
693
+ throw new CoderAgentError(
694
+ "CoderAgent received a prompt with no user message or tool results to act on."
695
+ );
696
+ }
697
+ const translator = new TurnTranslator({ dynamicToolNames: dynamicToolNames(options.tools) });
698
+ let afterId;
699
+ if (action.kind === "new-turn") {
700
+ const modelConfigId = await this.#resolveModelConfigId(signal);
701
+ if (!this.#chatId) {
702
+ const req = {
703
+ organization_id: this.#config.organizationId,
704
+ content: action.content,
705
+ client_type: "api"
706
+ };
707
+ const system = extractSystemPrompt(prompt);
708
+ if (system) req.system_prompt = system;
709
+ const tools = toolsToDynamicTools(options.tools);
710
+ if (tools.length > 0) req.unsafe_dynamic_tools = tools;
711
+ if (modelConfigId) req.model_config_id = modelConfigId;
712
+ if (this.#config.workspaceId) req.workspace_id = this.#config.workspaceId;
713
+ if (this.#config.mcpServerIds?.length) req.mcp_server_ids = this.#config.mcpServerIds;
714
+ if (this.#config.planMode) req.plan_mode = this.#config.planMode;
715
+ const chat = await this.#config.client.createChat(req, signal);
716
+ this.#chatId = chat.id;
717
+ afterId = this.#lastSeenMessageId > 0 ? this.#lastSeenMessageId : void 0;
718
+ } else {
719
+ const resp = await this.#config.client.createChatMessage(
720
+ this.#chatId,
721
+ {
722
+ content: action.content,
723
+ ...modelConfigId ? { model_config_id: modelConfigId } : {}
724
+ },
725
+ signal
726
+ );
727
+ afterId = resp.message?.id ?? this.#lastSeenMessageId;
728
+ }
729
+ } else {
730
+ if (!this.#chatId)
731
+ throw new CoderChatError({ message: "cannot submit tool results before a chat exists" });
732
+ const fresh = action.toolResults.filter(
733
+ (r) => !this.#submittedToolCallIds.has(r.tool_call_id)
734
+ );
735
+ if (fresh.length > 0) {
736
+ await this.#config.client.submitToolResults(this.#chatId, { results: fresh }, signal);
737
+ for (const r of fresh) this.#submittedToolCallIds.add(r.tool_call_id);
738
+ }
739
+ afterId = this.#lastSeenMessageId;
740
+ }
741
+ const chatId = this.#chatId;
742
+ let sinceRequiresAction = 0;
743
+ for await (const ev of this.#config.client.streamEvents(chatId, { afterId, signal })) {
744
+ for (const part of translator.ingest(ev)) yield part;
745
+ const status = translator.terminalStatus;
746
+ if (status) {
747
+ if (status !== "requires_action") break;
748
+ if (translator.clientToolCallSeen) break;
749
+ if (++sinceRequiresAction > 200) break;
750
+ }
751
+ }
752
+ if (signal?.aborted && !translator.terminalStatus) {
753
+ throw signal.reason ?? new DOMException("The operation was aborted.", "AbortError");
754
+ }
755
+ for (const part of translator.finish()) yield part;
756
+ if (translator.maxMessageId > this.#lastSeenMessageId)
757
+ this.#lastSeenMessageId = translator.maxMessageId;
758
+ } finally {
759
+ this.#inFlight = false;
760
+ }
761
+ }
762
+ async doStream(options) {
763
+ const gen = this.#runTurn(options);
764
+ const stream = new ReadableStream({
765
+ async pull(controller) {
766
+ try {
767
+ const { value, done } = await gen.next();
768
+ if (done) controller.close();
769
+ else controller.enqueue(value);
770
+ } catch (err) {
771
+ controller.error(err);
772
+ }
773
+ },
774
+ async cancel() {
775
+ await gen.return();
776
+ }
777
+ });
778
+ return { stream };
779
+ }
780
+ async doGenerate(options) {
781
+ const content = [];
782
+ const textBuf = /* @__PURE__ */ new Map();
783
+ const reasoningBuf = /* @__PURE__ */ new Map();
784
+ let usage = EMPTY_USAGE;
785
+ let finishReason = {
786
+ unified: "stop",
787
+ raw: void 0
788
+ };
789
+ const warnings = [];
790
+ for await (const part of this.#runTurn(options)) {
791
+ switch (part.type) {
792
+ case "stream-start":
793
+ warnings.push(...part.warnings);
794
+ break;
795
+ case "text-start":
796
+ textBuf.set(part.id, "");
797
+ break;
798
+ case "text-delta":
799
+ textBuf.set(part.id, (textBuf.get(part.id) ?? "") + part.delta);
800
+ break;
801
+ case "text-end": {
802
+ const t = textBuf.get(part.id) ?? "";
803
+ if (t.length > 0) content.push({ type: "text", text: t });
804
+ textBuf.delete(part.id);
805
+ break;
806
+ }
807
+ case "reasoning-start":
808
+ reasoningBuf.set(part.id, "");
809
+ break;
810
+ case "reasoning-delta":
811
+ reasoningBuf.set(part.id, (reasoningBuf.get(part.id) ?? "") + part.delta);
812
+ break;
813
+ case "reasoning-end": {
814
+ const t = reasoningBuf.get(part.id) ?? "";
815
+ if (t.length > 0) content.push({ type: "reasoning", text: t });
816
+ reasoningBuf.delete(part.id);
817
+ break;
818
+ }
819
+ case "tool-call":
820
+ case "tool-result":
821
+ case "source":
822
+ case "file":
823
+ content.push(part);
824
+ break;
825
+ case "finish":
826
+ usage = part.usage;
827
+ finishReason = part.finishReason;
828
+ break;
829
+ case "error":
830
+ throw part.error instanceof Error ? part.error : new CoderChatError({ message: String(part.error) });
831
+ default:
832
+ break;
833
+ }
834
+ }
835
+ return { content, finishReason, usage, warnings };
836
+ }
837
+ };
838
+
839
+ // src/agent/coder-agent.ts
840
+ var DEFAULT_STOP = stepCountIs(64);
841
+ var CoderAgent = class {
842
+ version = "agent-v1";
843
+ #client;
844
+ #model;
845
+ #inner;
846
+ constructor(settings) {
847
+ if (settings.client) {
848
+ this.#client = settings.client;
849
+ } else if (settings.baseUrl && settings.token) {
850
+ this.#client = new CoderChatClient({
851
+ baseUrl: settings.baseUrl,
852
+ token: settings.token,
853
+ fetch: settings.fetch,
854
+ webSocketFactory: settings.webSocketFactory
855
+ });
856
+ } else {
857
+ throw new CoderAgentError(
858
+ "CoderAgent requires either `client` or both `baseUrl` and `token`."
859
+ );
860
+ }
861
+ this.#model = new CoderLanguageModel({
862
+ client: this.#client,
863
+ organizationId: settings.organizationId,
864
+ model: settings.model,
865
+ workspaceId: settings.workspaceId,
866
+ mcpServerIds: settings.mcpServerIds,
867
+ planMode: settings.planMode,
868
+ chatId: settings.chatId
869
+ });
870
+ this.#inner = new ToolLoopAgent({
871
+ model: this.#model,
872
+ id: settings.id,
873
+ instructions: settings.instructions,
874
+ tools: settings.tools,
875
+ toolChoice: settings.toolChoice,
876
+ stopWhen: settings.stopWhen ?? DEFAULT_STOP,
877
+ maxRetries: settings.maxRetries ?? 0
878
+ });
879
+ }
880
+ get id() {
881
+ return this.#inner.id;
882
+ }
883
+ get tools() {
884
+ return this.#inner.tools;
885
+ }
886
+ /** The underlying chatd client. */
887
+ get client() {
888
+ return this.#client;
889
+ }
890
+ /** The current chatd chat id, once a turn has started. */
891
+ get chatId() {
892
+ return this.#model.chatId;
893
+ }
894
+ generate(options) {
895
+ return this.#inner.generate(options);
896
+ }
897
+ stream(options) {
898
+ return this.#inner.stream(options);
899
+ }
900
+ // --- session helpers ------------------------------------------------------
901
+ /** Start a fresh chatd chat on the next turn. */
902
+ resetSession() {
903
+ this.#model.resetSession();
904
+ }
905
+ /** Interrupt the in-flight generation, if any. */
906
+ async interrupt() {
907
+ const id = this.#model.chatId;
908
+ if (id) await this.#client.interruptChat(id);
909
+ }
910
+ /** Archive the underlying chat (safe cleanup; hides it from listings). */
911
+ async archive() {
912
+ const id = this.#model.chatId;
913
+ if (id) await this.#client.archiveChat(id);
914
+ }
915
+ };
916
+ export {
917
+ CoderAgent,
918
+ CoderAgentError,
919
+ CoderApiError,
920
+ CoderChatClient,
921
+ CoderChatError,
922
+ CoderLanguageModel,
923
+ TurnTranslator,
924
+ classifyTurnAction,
925
+ dynamicToolNames,
926
+ extractSystemPrompt,
927
+ streamChatEvents,
928
+ toolsToDynamicTools
929
+ };
930
+ //# sourceMappingURL=index.js.map