@coder/ai-sdk-agent 0.2.1 → 0.4.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/README.md +210 -21
- package/dist/index.d.ts +280 -39
- package/dist/index.js +672 -31
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/agent/coder-agent.ts
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
isStepCount,
|
|
4
4
|
ToolLoopAgent
|
|
5
5
|
} from "ai";
|
|
6
6
|
|
|
@@ -42,6 +42,32 @@ var CoderChatError = class extends CoderAgentError {
|
|
|
42
42
|
}
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
+
// src/ai-version.ts
|
|
46
|
+
var SUPPORTED_AI_MAJOR = 7;
|
|
47
|
+
function unsupportedAiVersionMessage(version) {
|
|
48
|
+
const major = Number(/^v?(\d+)(?:[.+-]|$)/.exec(version.trim())?.[1]);
|
|
49
|
+
if (!Number.isSafeInteger(major) || major === SUPPORTED_AI_MAJOR) return void 0;
|
|
50
|
+
return `@coder/ai-sdk-agent supports the \`ai\` package v${SUPPORTED_AI_MAJOR} only (peer dependency \`ai@^${SUPPORTED_AI_MAJOR}\`), but ai@${version} is installed. Other majors fail at runtime in confusing ways (brand-check TypeErrors, silently empty streams). Install a compatible version, e.g. \`pnpm add ai@^${SUPPORTED_AI_MAJOR}\`.`;
|
|
51
|
+
}
|
|
52
|
+
function resolveInstalledAiVersion() {
|
|
53
|
+
try {
|
|
54
|
+
const nodeModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("node:module") : void 0;
|
|
55
|
+
const requireFromHere = nodeModule?.createRequire(import.meta.url);
|
|
56
|
+
const pkg = requireFromHere?.("ai/package.json");
|
|
57
|
+
return typeof pkg?.version === "string" ? pkg.version : void 0;
|
|
58
|
+
} catch {
|
|
59
|
+
return void 0;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
var cachedMessage;
|
|
63
|
+
function assertSupportedAiVersion(resolveVersion = resolveInstalledAiVersion) {
|
|
64
|
+
if (cachedMessage === void 0) {
|
|
65
|
+
const version = resolveVersion();
|
|
66
|
+
cachedMessage = version === void 0 ? null : unsupportedAiVersionMessage(version) ?? null;
|
|
67
|
+
}
|
|
68
|
+
if (cachedMessage !== null) throw new CoderAgentError(cachedMessage);
|
|
69
|
+
}
|
|
70
|
+
|
|
45
71
|
// src/files.ts
|
|
46
72
|
function isBlob(v) {
|
|
47
73
|
return typeof Blob !== "undefined" && v instanceof Blob;
|
|
@@ -86,11 +112,16 @@ function base64ToBytes(b64) {
|
|
|
86
112
|
return bytes;
|
|
87
113
|
}
|
|
88
114
|
function dataContentToFileContent(data) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
115
|
+
switch (data.type) {
|
|
116
|
+
case "data":
|
|
117
|
+
return data.data instanceof Uint8Array ? data.data : base64ToBytes(data.data);
|
|
118
|
+
case "text":
|
|
119
|
+
return new TextEncoder().encode(data.text);
|
|
120
|
+
default:
|
|
121
|
+
throw new CoderAgentError(
|
|
122
|
+
`File parts referencing a ${data.type === "url" ? "URL" : "provider reference"} are not supported; provide the file bytes instead.`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
94
125
|
}
|
|
95
126
|
|
|
96
127
|
// src/coder/types.ts
|
|
@@ -211,6 +242,176 @@ async function* streamChatEvents(options) {
|
|
|
211
242
|
}
|
|
212
243
|
}
|
|
213
244
|
}
|
|
245
|
+
var WATCH_PATH = "/api/experimental/chats/watch";
|
|
246
|
+
var WATCH_BACKOFF_INITIAL_MS = 1e3;
|
|
247
|
+
var WATCH_BACKOFF_CAP_MS = 3e4;
|
|
248
|
+
function sleep(ms, signal) {
|
|
249
|
+
return new Promise((resolve) => {
|
|
250
|
+
if (signal?.aborted) {
|
|
251
|
+
resolve();
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const onAbort = () => {
|
|
255
|
+
clearTimeout(timer);
|
|
256
|
+
resolve();
|
|
257
|
+
};
|
|
258
|
+
const timer = setTimeout(() => {
|
|
259
|
+
signal?.removeEventListener("abort", onAbort);
|
|
260
|
+
resolve();
|
|
261
|
+
}, ms);
|
|
262
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
function upgradeStatus(message) {
|
|
266
|
+
const match = /unexpected server response: (\d{3})$/i.exec(message.trim());
|
|
267
|
+
return match ? Number(match[1]) : void 0;
|
|
268
|
+
}
|
|
269
|
+
function watchChatEvents(options) {
|
|
270
|
+
const controller = new AbortController();
|
|
271
|
+
const external = options.signal;
|
|
272
|
+
const chain = () => controller.abort(external?.reason);
|
|
273
|
+
if (external) {
|
|
274
|
+
if (external.aborted) chain();
|
|
275
|
+
else external.addEventListener("abort", chain, { once: true });
|
|
276
|
+
}
|
|
277
|
+
const inner = watchChatEventsLoop({ ...options, signal: controller.signal });
|
|
278
|
+
const detach = () => external?.removeEventListener("abort", chain);
|
|
279
|
+
const finish = async () => {
|
|
280
|
+
controller.abort();
|
|
281
|
+
detach();
|
|
282
|
+
try {
|
|
283
|
+
await inner.return();
|
|
284
|
+
} catch {
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
return {
|
|
288
|
+
// Detach the signal chain once the loop settles for good (done, or the
|
|
289
|
+
// terminal 4xx rejection) — the iteration protocol never calls return()
|
|
290
|
+
// on a failed iterator, and the {once} listener would otherwise pile up
|
|
291
|
+
// on a long-lived signal across re-created watchers.
|
|
292
|
+
async next() {
|
|
293
|
+
try {
|
|
294
|
+
const result = await inner.next();
|
|
295
|
+
if (result.done) detach();
|
|
296
|
+
return result;
|
|
297
|
+
} catch (err) {
|
|
298
|
+
detach();
|
|
299
|
+
throw err;
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
async return() {
|
|
303
|
+
await finish();
|
|
304
|
+
return { done: true, value: void 0 };
|
|
305
|
+
},
|
|
306
|
+
async throw(err) {
|
|
307
|
+
await finish();
|
|
308
|
+
throw err;
|
|
309
|
+
},
|
|
310
|
+
[Symbol.asyncIterator]() {
|
|
311
|
+
return this;
|
|
312
|
+
},
|
|
313
|
+
// Native async generators are async-disposable; the wrapper must be too,
|
|
314
|
+
// so `await using events = client.watchChats(…)` keeps tearing down.
|
|
315
|
+
async [Symbol.asyncDispose]() {
|
|
316
|
+
await finish();
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
async function* watchChatEventsLoop(options) {
|
|
321
|
+
const { baseUrl, token, signal } = options;
|
|
322
|
+
const factory = options.webSocketFactory ?? defaultFactory;
|
|
323
|
+
const url = `${httpToWs(baseUrl)}${WATCH_PATH}`;
|
|
324
|
+
let backoffMs = WATCH_BACKOFF_INITIAL_MS;
|
|
325
|
+
while (!signal?.aborted) {
|
|
326
|
+
const queue = [];
|
|
327
|
+
let resolveNext;
|
|
328
|
+
let finished = false;
|
|
329
|
+
let failure;
|
|
330
|
+
const wake = () => {
|
|
331
|
+
resolveNext?.();
|
|
332
|
+
resolveNext = void 0;
|
|
333
|
+
};
|
|
334
|
+
const ws = factory(url, { headers: { "Coder-Session-Token": token } });
|
|
335
|
+
const onAbort = () => {
|
|
336
|
+
finished = true;
|
|
337
|
+
try {
|
|
338
|
+
ws.close(1e3);
|
|
339
|
+
} catch {
|
|
340
|
+
}
|
|
341
|
+
wake();
|
|
342
|
+
};
|
|
343
|
+
let abortListenerAdded = false;
|
|
344
|
+
if (signal) {
|
|
345
|
+
if (signal.aborted) onAbort();
|
|
346
|
+
else {
|
|
347
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
348
|
+
abortListenerAdded = true;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
const onMessage = (ev) => {
|
|
352
|
+
if (finished) return;
|
|
353
|
+
let payload;
|
|
354
|
+
try {
|
|
355
|
+
const data = typeof ev.data === "string" ? ev.data : String(ev.data);
|
|
356
|
+
payload = JSON.parse(data);
|
|
357
|
+
} catch (err) {
|
|
358
|
+
failure = new CoderAgentError("failed to parse chat watch frame", { cause: err });
|
|
359
|
+
finished = true;
|
|
360
|
+
wake();
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
if (Array.isArray(payload)) {
|
|
364
|
+
for (const e of payload) queue.push(e);
|
|
365
|
+
} else if (payload && typeof payload === "object") {
|
|
366
|
+
queue.push(payload);
|
|
367
|
+
}
|
|
368
|
+
wake();
|
|
369
|
+
};
|
|
370
|
+
const onError = (ev) => {
|
|
371
|
+
if (finished) return;
|
|
372
|
+
const message = ev && typeof ev === "object" && "message" in ev ? String(ev.message) : "chat watch socket error";
|
|
373
|
+
const status = upgradeStatus(message);
|
|
374
|
+
failure = status !== void 0 && status >= 400 && status < 500 ? new CoderApiError({ status, method: "GET", path: WATCH_PATH, message }) : new CoderAgentError(message);
|
|
375
|
+
finished = true;
|
|
376
|
+
wake();
|
|
377
|
+
};
|
|
378
|
+
const onClose = () => {
|
|
379
|
+
finished = true;
|
|
380
|
+
wake();
|
|
381
|
+
};
|
|
382
|
+
ws.addEventListener("message", onMessage);
|
|
383
|
+
ws.addEventListener("error", onError);
|
|
384
|
+
ws.addEventListener("close", onClose);
|
|
385
|
+
try {
|
|
386
|
+
while (true) {
|
|
387
|
+
while (queue.length > 0) {
|
|
388
|
+
const next = queue.shift();
|
|
389
|
+
backoffMs = WATCH_BACKOFF_INITIAL_MS;
|
|
390
|
+
yield next;
|
|
391
|
+
}
|
|
392
|
+
if (finished) break;
|
|
393
|
+
await new Promise((resolve) => {
|
|
394
|
+
resolveNext = resolve;
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
} finally {
|
|
398
|
+
finished = true;
|
|
399
|
+
if (signal && abortListenerAdded) signal.removeEventListener("abort", onAbort);
|
|
400
|
+
ws.removeEventListener("message", onMessage);
|
|
401
|
+
ws.removeEventListener("error", onError);
|
|
402
|
+
ws.removeEventListener("close", onClose);
|
|
403
|
+
try {
|
|
404
|
+
ws.close(1e3);
|
|
405
|
+
} catch {
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
if (signal?.aborted) return;
|
|
409
|
+
if (failure instanceof CoderApiError) throw failure;
|
|
410
|
+
await sleep(backoffMs, signal);
|
|
411
|
+
if (signal?.aborted) return;
|
|
412
|
+
backoffMs = Math.min(backoffMs * 2, WATCH_BACKOFF_CAP_MS);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
214
415
|
|
|
215
416
|
// src/coder/client.ts
|
|
216
417
|
var API_PREFIX = "/api/experimental/chats";
|
|
@@ -309,8 +510,28 @@ var CoderChatClient = class {
|
|
|
309
510
|
submitToolResults(chatId, req, signal) {
|
|
310
511
|
return this.#request("POST", `${API_PREFIX}/${chatId}/tool-results`, req, signal);
|
|
311
512
|
}
|
|
312
|
-
|
|
313
|
-
|
|
513
|
+
/**
|
|
514
|
+
* Interrupt the chat's in-flight run (`POST /interrupt`). The server flips
|
|
515
|
+
* an active run to `interrupting` and responds immediately with the updated
|
|
516
|
+
* chat — it does not wait for the run to actually stop.
|
|
517
|
+
*
|
|
518
|
+
* Pass `{ wait: true }` to send `?wait=true`, asking the server to hold the
|
|
519
|
+
* response until the run has stopped. Coder servers without that param
|
|
520
|
+
* ignore the unknown query harmlessly and still return immediately, so
|
|
521
|
+
* callers should be prepared to confirm completion via the event stream.
|
|
522
|
+
*
|
|
523
|
+
* The second parameter also accepts a bare `AbortSignal` (the historical
|
|
524
|
+
* signature).
|
|
525
|
+
*/
|
|
526
|
+
interruptChat(chatId, optsOrSignal) {
|
|
527
|
+
const opts = optsOrSignal instanceof AbortSignal ? { signal: optsOrSignal } : optsOrSignal;
|
|
528
|
+
const query = opts?.wait ? "?wait=true" : "";
|
|
529
|
+
return this.#request(
|
|
530
|
+
"POST",
|
|
531
|
+
`${API_PREFIX}/${chatId}/interrupt${query}`,
|
|
532
|
+
void 0,
|
|
533
|
+
opts?.signal
|
|
534
|
+
);
|
|
314
535
|
}
|
|
315
536
|
updateChat(chatId, req, signal) {
|
|
316
537
|
return this.#request("PATCH", `${API_PREFIX}/${chatId}`, req, signal);
|
|
@@ -390,6 +611,22 @@ var CoderChatClient = class {
|
|
|
390
611
|
webSocketFactory: this.#webSocketFactory
|
|
391
612
|
});
|
|
392
613
|
}
|
|
614
|
+
/**
|
|
615
|
+
* Watch lifecycle events (status/title changes, creation, deletion, …) for
|
|
616
|
+
* every chat visible to the authenticated user via the `/chats/watch`
|
|
617
|
+
* WebSocket. Dropped connections are redialed with exponential backoff; the
|
|
618
|
+
* iteration ends only when `opts.signal` aborts, or with a terminal
|
|
619
|
+
* {@link CoderApiError} when the server rejects the upgrade with a 4xx
|
|
620
|
+
* (bad/expired token, or an older Coder server without the endpoint → 404).
|
|
621
|
+
*/
|
|
622
|
+
watchChats(opts) {
|
|
623
|
+
return watchChatEvents({
|
|
624
|
+
baseUrl: this.baseUrl,
|
|
625
|
+
token: this.#token,
|
|
626
|
+
signal: opts?.signal,
|
|
627
|
+
webSocketFactory: this.#webSocketFactory
|
|
628
|
+
});
|
|
629
|
+
}
|
|
393
630
|
// --- Helpers --------------------------------------------------------------
|
|
394
631
|
/**
|
|
395
632
|
* Resolves a user-friendly model hint to a model-config UUID.
|
|
@@ -398,30 +635,159 @@ var CoderChatClient = class {
|
|
|
398
635
|
* (e.g. `anthropic:claude-haiku-4-5-20251001`), a bare model id, or a
|
|
399
636
|
* display-name substring (case-insensitive). Returns `undefined` if no
|
|
400
637
|
* match is found, in which case the caller should let chatd pick the default.
|
|
638
|
+
*
|
|
639
|
+
* Tolerates partial payloads from older/newer servers: a malformed listing
|
|
640
|
+
* (empty body, non-array JSON, null entries) resolves as no match, and
|
|
641
|
+
* entries missing `provider`, `model`, or `display_name` are matched on the
|
|
642
|
+
* fields they do carry — an entry without `provider` still matches by its
|
|
643
|
+
* model id, one without `model` only by display name.
|
|
401
644
|
*/
|
|
402
645
|
async resolveModelConfigId(hint, signal) {
|
|
403
646
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
404
647
|
if (UUID_RE.test(hint)) return hint;
|
|
405
|
-
const
|
|
648
|
+
const raw = await this.listModelConfigs(signal);
|
|
649
|
+
const configs = (Array.isArray(raw) ? raw : []).filter(
|
|
650
|
+
(c) => typeof c === "object" && c !== null
|
|
651
|
+
);
|
|
406
652
|
const lower = hint.toLowerCase();
|
|
407
653
|
const colon = hint.indexOf(":");
|
|
408
654
|
const provider = colon >= 0 ? hint.slice(0, colon).toLowerCase() : void 0;
|
|
409
655
|
const model = colon >= 0 ? hint.slice(colon + 1).toLowerCase() : lower;
|
|
410
656
|
const candidates = configs.filter((c) => c.enabled !== false);
|
|
411
657
|
const pool = candidates.length > 0 ? candidates : configs;
|
|
658
|
+
const modelOf = (c) => typeof c.model === "string" ? c.model.toLowerCase() : void 0;
|
|
659
|
+
const providerOf = (c) => typeof c.provider === "string" ? c.provider.toLowerCase() : void 0;
|
|
660
|
+
const displayNameOf = (c) => typeof c.display_name === "string" ? c.display_name.toLowerCase() : void 0;
|
|
412
661
|
const exact = pool.find(
|
|
413
|
-
(c) => c
|
|
662
|
+
(c) => modelOf(c) === model && (provider === void 0 || providerOf(c) === provider)
|
|
414
663
|
);
|
|
415
664
|
if (exact) return exact.id;
|
|
416
|
-
const byModel = pool.find((c) => c
|
|
665
|
+
const byModel = pool.find((c) => modelOf(c) === model);
|
|
417
666
|
if (byModel) return byModel.id;
|
|
418
|
-
const byDisplay = pool.find((c) => c
|
|
667
|
+
const byDisplay = pool.find((c) => displayNameOf(c)?.includes(lower));
|
|
419
668
|
if (byDisplay) return byDisplay.id;
|
|
420
|
-
const byModelSubstring = pool.find((c) => c
|
|
669
|
+
const byModelSubstring = pool.find((c) => modelOf(c)?.includes(model));
|
|
421
670
|
return byModelSubstring?.id;
|
|
422
671
|
}
|
|
423
672
|
};
|
|
424
673
|
|
|
674
|
+
// src/coder/workspaces.ts
|
|
675
|
+
async function request(conn, method, path, body, signal) {
|
|
676
|
+
const fetchFn = conn.fetch ?? globalThis.fetch.bind(globalThis);
|
|
677
|
+
const headers = { "Coder-Session-Token": conn.token };
|
|
678
|
+
if (body !== void 0) headers["Content-Type"] = "application/json";
|
|
679
|
+
const res = await fetchFn(`${conn.baseUrl.replace(/\/$/, "")}${path}`, {
|
|
680
|
+
method,
|
|
681
|
+
headers,
|
|
682
|
+
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
683
|
+
signal
|
|
684
|
+
});
|
|
685
|
+
const text = await res.text().catch(() => "");
|
|
686
|
+
let parsed;
|
|
687
|
+
try {
|
|
688
|
+
parsed = text.length > 0 ? JSON.parse(text) : void 0;
|
|
689
|
+
} catch {
|
|
690
|
+
parsed = void 0;
|
|
691
|
+
}
|
|
692
|
+
if (!res.ok) {
|
|
693
|
+
const errObj = parsed ?? {};
|
|
694
|
+
throw new CoderApiError({
|
|
695
|
+
status: res.status,
|
|
696
|
+
method,
|
|
697
|
+
path,
|
|
698
|
+
message: errObj.message ?? res.statusText ?? "request failed",
|
|
699
|
+
detail: errObj.detail
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
return parsed;
|
|
703
|
+
}
|
|
704
|
+
function getWorkspace(conn, workspaceId, signal) {
|
|
705
|
+
return request(
|
|
706
|
+
conn,
|
|
707
|
+
"GET",
|
|
708
|
+
`/api/v2/workspaces/${workspaceId}`,
|
|
709
|
+
void 0,
|
|
710
|
+
signal
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
function getAppsHost(conn, signal) {
|
|
714
|
+
return request(conn, "GET", "/api/v2/applications/host", void 0, signal);
|
|
715
|
+
}
|
|
716
|
+
async function upsertPortShare(conn, workspaceId, req, signal) {
|
|
717
|
+
const path = `/api/v2/workspaces/${workspaceId}/port-share`;
|
|
718
|
+
try {
|
|
719
|
+
return await request(conn, "POST", path, req, signal);
|
|
720
|
+
} catch (err) {
|
|
721
|
+
if (err instanceof CoderApiError && err.status === 404) {
|
|
722
|
+
throw new CoderApiError({
|
|
723
|
+
status: 404,
|
|
724
|
+
method: "POST",
|
|
725
|
+
path,
|
|
726
|
+
message: "port sharing is unavailable",
|
|
727
|
+
detail: "the workspace was not found, or this Coder server predates port sharing (added in Coder v2.9)"
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
throw err;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
function defaultAgentName(workspace) {
|
|
734
|
+
const agents = (workspace.latest_build?.resources ?? []).flatMap((r) => r.agents ?? []);
|
|
735
|
+
const first = agents[0];
|
|
736
|
+
if (agents.length === 1 && first) return first.name;
|
|
737
|
+
if (agents.length === 0) {
|
|
738
|
+
throw new CoderAgentError(
|
|
739
|
+
`Workspace "${workspace.name}" has no agents in its latest build (is it running?); pass agentName explicitly.`
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
throw new CoderAgentError(
|
|
743
|
+
`Workspace "${workspace.name}" has ${agents.length} agents (${agents.map((a) => a.name).join(", ")}); pass agentName to pick one.`
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
function assertValidPort(port) {
|
|
747
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
748
|
+
throw new CoderAgentError(`Invalid port ${port}: expected an integer between 1 and 65535.`);
|
|
749
|
+
}
|
|
750
|
+
if (port < 1e3) {
|
|
751
|
+
throw new CoderAgentError(
|
|
752
|
+
`Port ${port} cannot be previewed: Coder subdomain app URLs only encode 4-5 digit ports (1000-65535). Serve the preview on a higher port instead.`
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
async function resolveWorkspacePreview(conn, opts, signal) {
|
|
757
|
+
assertValidPort(opts.port);
|
|
758
|
+
const [workspace, appHost] = await Promise.all([
|
|
759
|
+
getWorkspace(conn, opts.workspaceId, signal),
|
|
760
|
+
getAppsHost(conn, signal)
|
|
761
|
+
]);
|
|
762
|
+
if (!appHost.host) {
|
|
763
|
+
throw new CoderAgentError(
|
|
764
|
+
"This Coder deployment has no wildcard app host configured (--wildcard-access-url), so subdomain preview URLs are unavailable."
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
const agentName = opts.agentName ?? defaultAgentName(workspace);
|
|
768
|
+
const suffix = opts.protocol === "https" ? "s" : "";
|
|
769
|
+
const label = `${opts.port}${suffix}--${agentName}--${workspace.name}--${workspace.owner_name}`;
|
|
770
|
+
const scheme = new URL(conn.baseUrl).protocol;
|
|
771
|
+
return { url: `${scheme}//${appHost.host.replace(/\*/g, label)}`, agentName };
|
|
772
|
+
}
|
|
773
|
+
async function shareWorkspacePreview(conn, opts, signal) {
|
|
774
|
+
const requested = opts.shareLevel ?? "authenticated";
|
|
775
|
+
const { url, agentName } = await resolveWorkspacePreview(conn, opts, signal);
|
|
776
|
+
const share = await upsertPortShare(
|
|
777
|
+
conn,
|
|
778
|
+
opts.workspaceId,
|
|
779
|
+
{
|
|
780
|
+
agent_name: agentName,
|
|
781
|
+
port: opts.port,
|
|
782
|
+
share_level: requested,
|
|
783
|
+
protocol: opts.protocol ?? "http"
|
|
784
|
+
},
|
|
785
|
+
signal
|
|
786
|
+
);
|
|
787
|
+
const level = share?.share_level && share.share_level !== "owner" ? share.share_level : requested;
|
|
788
|
+
return { url, shareLevel: level };
|
|
789
|
+
}
|
|
790
|
+
|
|
425
791
|
// src/model/prompt.ts
|
|
426
792
|
var CODER_PROVIDER_OPTIONS = "coder";
|
|
427
793
|
function extractSystemPrompt(prompt) {
|
|
@@ -529,7 +895,10 @@ function mapUsage(u) {
|
|
|
529
895
|
total: u?.output_tokens,
|
|
530
896
|
text: void 0,
|
|
531
897
|
reasoning: u?.reasoning_tokens
|
|
532
|
-
}
|
|
898
|
+
},
|
|
899
|
+
// Preserve the verbatim wire usage (snake_case) so callers can reach fields
|
|
900
|
+
// the normalized shape has no slot for (context_limit, cost, runtime, …).
|
|
901
|
+
...u ? { raw: u } : {}
|
|
533
902
|
};
|
|
534
903
|
}
|
|
535
904
|
function jsonResult(value) {
|
|
@@ -541,10 +910,15 @@ var TurnTranslator = class {
|
|
|
541
910
|
#text = { id: void 0, len: 0, sawDelta: false };
|
|
542
911
|
#reasoning = { id: void 0, len: 0, sawDelta: false };
|
|
543
912
|
#currentAssistantId;
|
|
913
|
+
// Whether text/reasoning deltas arrived since the last assistant `message`
|
|
914
|
+
// snapshot — i.e. the next assistant snapshot is the trailing snapshot of the
|
|
915
|
+
// message those deltas streamed (deltas carry no message id of their own).
|
|
916
|
+
#deltasSinceSnapshot = false;
|
|
544
917
|
#serverToolCalls = /* @__PURE__ */ new Set();
|
|
545
918
|
#serverToolResults = /* @__PURE__ */ new Set();
|
|
546
919
|
#clientToolCalls = /* @__PURE__ */ new Set();
|
|
547
920
|
#clientToolCallSeen = false;
|
|
921
|
+
#sources = /* @__PURE__ */ new Set();
|
|
548
922
|
#usage;
|
|
549
923
|
#error;
|
|
550
924
|
#terminalStatus;
|
|
@@ -667,6 +1041,26 @@ var TurnTranslator = class {
|
|
|
667
1041
|
dynamic: true
|
|
668
1042
|
});
|
|
669
1043
|
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Emits a chatd `source` part as a standalone V4 url source (no text-block
|
|
1046
|
+
* bracketing needed). Deduped by the emitted id so a part streamed as a
|
|
1047
|
+
* `message_part` isn't re-emitted by its trailing `message` snapshot, while
|
|
1048
|
+
* snapshot-only turns still emit theirs.
|
|
1049
|
+
*/
|
|
1050
|
+
#emitSource(out, part) {
|
|
1051
|
+
const url = part.url;
|
|
1052
|
+
if (!url) return;
|
|
1053
|
+
const id = part.source_id || url;
|
|
1054
|
+
if (this.#sources.has(id)) return;
|
|
1055
|
+
this.#sources.add(id);
|
|
1056
|
+
out.push({
|
|
1057
|
+
type: "source",
|
|
1058
|
+
sourceType: "url",
|
|
1059
|
+
id,
|
|
1060
|
+
url,
|
|
1061
|
+
...part.title !== void 0 ? { title: part.title } : {}
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
670
1064
|
// --- ingest ---------------------------------------------------------------
|
|
671
1065
|
ingest(ev) {
|
|
672
1066
|
const out = [];
|
|
@@ -717,6 +1111,7 @@ var TurnTranslator = class {
|
|
|
717
1111
|
switch (part.type) {
|
|
718
1112
|
case "text":
|
|
719
1113
|
this.#text.sawDelta = true;
|
|
1114
|
+
this.#deltasSinceSnapshot = true;
|
|
720
1115
|
this.#openText(out);
|
|
721
1116
|
if (part.text) {
|
|
722
1117
|
out.push({ type: "text-delta", id: this.#text.id, delta: part.text });
|
|
@@ -725,6 +1120,7 @@ var TurnTranslator = class {
|
|
|
725
1120
|
break;
|
|
726
1121
|
case "reasoning":
|
|
727
1122
|
this.#reasoning.sawDelta = true;
|
|
1123
|
+
this.#deltasSinceSnapshot = true;
|
|
728
1124
|
this.#openReasoning(out);
|
|
729
1125
|
if (part.text) {
|
|
730
1126
|
out.push({ type: "reasoning-delta", id: this.#reasoning.id, delta: part.text });
|
|
@@ -739,6 +1135,9 @@ var TurnTranslator = class {
|
|
|
739
1135
|
case "tool-result":
|
|
740
1136
|
if (!this.#isClientTool(part.tool_name)) this.#emitServerToolResult(out, part);
|
|
741
1137
|
break;
|
|
1138
|
+
case "source":
|
|
1139
|
+
this.#emitSource(out, part);
|
|
1140
|
+
break;
|
|
742
1141
|
default:
|
|
743
1142
|
break;
|
|
744
1143
|
}
|
|
@@ -750,7 +1149,7 @@ var TurnTranslator = class {
|
|
|
750
1149
|
}
|
|
751
1150
|
const content = message.content ?? [];
|
|
752
1151
|
if (message.role === "assistant") {
|
|
753
|
-
if (this.#currentAssistantId !== void 0 && this.#currentAssistantId !== message.id) {
|
|
1152
|
+
if (this.#currentAssistantId !== void 0 && this.#currentAssistantId !== message.id && !this.#deltasSinceSnapshot) {
|
|
754
1153
|
this.#closeText(out);
|
|
755
1154
|
this.#closeReasoning(out);
|
|
756
1155
|
this.#text.sawDelta = false;
|
|
@@ -770,11 +1169,14 @@ var TurnTranslator = class {
|
|
|
770
1169
|
this.#emitServerToolCall(out, part);
|
|
771
1170
|
else if (part.type === "tool-result" && !this.#isClientTool(part.tool_name))
|
|
772
1171
|
this.#emitServerToolResult(out, part);
|
|
1172
|
+
else if (part.type === "source") this.#emitSource(out, part);
|
|
773
1173
|
}
|
|
1174
|
+
this.#deltasSinceSnapshot = false;
|
|
774
1175
|
} else if (message.role === "tool") {
|
|
775
1176
|
for (const part of content) {
|
|
776
1177
|
if (part.type === "tool-result" && !this.#isClientTool(part.tool_name))
|
|
777
1178
|
this.#emitServerToolResult(out, part);
|
|
1179
|
+
else if (part.type === "source") this.#emitSource(out, part);
|
|
778
1180
|
}
|
|
779
1181
|
}
|
|
780
1182
|
}
|
|
@@ -788,10 +1190,16 @@ var TurnTranslator = class {
|
|
|
788
1190
|
else if (this.#clientToolCallSeen || this.#terminalStatus === "requires_action")
|
|
789
1191
|
unified = "tool-calls";
|
|
790
1192
|
else unified = "stop";
|
|
1193
|
+
const coder = {};
|
|
1194
|
+
if (this.#usage?.total_cost_micros !== void 0)
|
|
1195
|
+
coder.total_cost_micros = this.#usage.total_cost_micros;
|
|
1196
|
+
if (this.#usage?.total_runtime_ms !== void 0)
|
|
1197
|
+
coder.total_runtime_ms = this.#usage.total_runtime_ms;
|
|
791
1198
|
out.push({
|
|
792
1199
|
type: "finish",
|
|
793
1200
|
usage: mapUsage(this.#usage),
|
|
794
|
-
finishReason: { unified, raw: this.#terminalStatus }
|
|
1201
|
+
finishReason: { unified, raw: this.#terminalStatus },
|
|
1202
|
+
...Object.keys(coder).length > 0 ? { providerMetadata: { coder } } : {}
|
|
795
1203
|
});
|
|
796
1204
|
return out;
|
|
797
1205
|
}
|
|
@@ -808,7 +1216,7 @@ var EMPTY_USAGE = {
|
|
|
808
1216
|
outputTokens: { total: void 0, text: void 0, reasoning: void 0 }
|
|
809
1217
|
};
|
|
810
1218
|
var CoderLanguageModel = class {
|
|
811
|
-
specificationVersion = "
|
|
1219
|
+
specificationVersion = "v4";
|
|
812
1220
|
provider = "coder.chatd";
|
|
813
1221
|
modelId;
|
|
814
1222
|
supportedUrls = {};
|
|
@@ -820,6 +1228,7 @@ var CoderLanguageModel = class {
|
|
|
820
1228
|
#submittedToolCallIds = /* @__PURE__ */ new Set();
|
|
821
1229
|
#inFlight = false;
|
|
822
1230
|
constructor(config) {
|
|
1231
|
+
assertSupportedAiVersion();
|
|
823
1232
|
this.#config = config;
|
|
824
1233
|
this.modelId = config.model ?? "chatd";
|
|
825
1234
|
this.#chatId = config.chatId;
|
|
@@ -1035,6 +1444,7 @@ var CoderLanguageModel = class {
|
|
|
1035
1444
|
unified: "stop",
|
|
1036
1445
|
raw: void 0
|
|
1037
1446
|
};
|
|
1447
|
+
let providerMetadata;
|
|
1038
1448
|
const warnings = [];
|
|
1039
1449
|
for await (const part of this.#runTurn(options)) {
|
|
1040
1450
|
switch (part.type) {
|
|
@@ -1074,6 +1484,7 @@ var CoderLanguageModel = class {
|
|
|
1074
1484
|
case "finish":
|
|
1075
1485
|
usage = part.usage;
|
|
1076
1486
|
finishReason = part.finishReason;
|
|
1487
|
+
providerMetadata = part.providerMetadata;
|
|
1077
1488
|
break;
|
|
1078
1489
|
case "error":
|
|
1079
1490
|
throw part.error instanceof Error ? part.error : new CoderChatError({ message: String(part.error) });
|
|
@@ -1081,7 +1492,13 @@ var CoderLanguageModel = class {
|
|
|
1081
1492
|
break;
|
|
1082
1493
|
}
|
|
1083
1494
|
}
|
|
1084
|
-
return {
|
|
1495
|
+
return {
|
|
1496
|
+
content,
|
|
1497
|
+
finishReason,
|
|
1498
|
+
usage,
|
|
1499
|
+
warnings,
|
|
1500
|
+
...providerMetadata ? { providerMetadata } : {}
|
|
1501
|
+
};
|
|
1085
1502
|
}
|
|
1086
1503
|
};
|
|
1087
1504
|
|
|
@@ -1099,7 +1516,48 @@ function makeChatAttachment(file) {
|
|
|
1099
1516
|
})
|
|
1100
1517
|
};
|
|
1101
1518
|
}
|
|
1102
|
-
var DEFAULT_STOP =
|
|
1519
|
+
var DEFAULT_STOP = isStepCount(64);
|
|
1520
|
+
var SETTLE_DEADLINE_MS = 15e3;
|
|
1521
|
+
var SETTLE_RETRY_DELAY_MS = 1e3;
|
|
1522
|
+
function sanitizeDelayMs(value, fallback) {
|
|
1523
|
+
const MAX_TIMER_MS = 2 ** 31 - 1;
|
|
1524
|
+
if (value === void 0 || Number.isNaN(value)) return fallback;
|
|
1525
|
+
if (value === Number.POSITIVE_INFINITY) return MAX_TIMER_MS;
|
|
1526
|
+
if (!Number.isFinite(value) || value < 0) return fallback;
|
|
1527
|
+
return Math.min(Math.floor(value), MAX_TIMER_MS);
|
|
1528
|
+
}
|
|
1529
|
+
function sleep2(ms, signal) {
|
|
1530
|
+
return new Promise((resolve, reject) => {
|
|
1531
|
+
if (signal?.aborted) {
|
|
1532
|
+
reject(signal.reason);
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1535
|
+
const onAbort = () => {
|
|
1536
|
+
clearTimeout(timer);
|
|
1537
|
+
reject(signal?.reason);
|
|
1538
|
+
};
|
|
1539
|
+
const timer = setTimeout(() => {
|
|
1540
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1541
|
+
resolve();
|
|
1542
|
+
}, ms);
|
|
1543
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1546
|
+
async function archiveWhenSettled(client, chatId, opts) {
|
|
1547
|
+
const timeout = AbortSignal.timeout(opts.deadlineMs);
|
|
1548
|
+
const signal = opts.signal ? AbortSignal.any([opts.signal, timeout]) : timeout;
|
|
1549
|
+
const deadline = Date.now() + opts.deadlineMs;
|
|
1550
|
+
for (; ; ) {
|
|
1551
|
+
try {
|
|
1552
|
+
await client.archiveChat(chatId, signal);
|
|
1553
|
+
return;
|
|
1554
|
+
} catch (err) {
|
|
1555
|
+
if (!(err instanceof CoderApiError) || err.status !== 409) throw err;
|
|
1556
|
+
if (Date.now() + opts.retryDelayMs > deadline) throw err;
|
|
1557
|
+
await sleep2(opts.retryDelayMs, opts.signal);
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1103
1561
|
var CoderAgent = class {
|
|
1104
1562
|
version = "agent-v1";
|
|
1105
1563
|
#client;
|
|
@@ -1107,7 +1565,12 @@ var CoderAgent = class {
|
|
|
1107
1565
|
#inner;
|
|
1108
1566
|
#organizationId;
|
|
1109
1567
|
#workspaceFiles;
|
|
1568
|
+
#workspaceId;
|
|
1569
|
+
#workspaceApi;
|
|
1570
|
+
#settleDeadlineMs;
|
|
1571
|
+
#settleRetryDelayMs;
|
|
1110
1572
|
constructor(settings) {
|
|
1573
|
+
assertSupportedAiVersion();
|
|
1111
1574
|
if (settings.client) {
|
|
1112
1575
|
this.#client = settings.client;
|
|
1113
1576
|
} else if (settings.baseUrl && settings.token) {
|
|
@@ -1124,6 +1587,10 @@ var CoderAgent = class {
|
|
|
1124
1587
|
}
|
|
1125
1588
|
this.#organizationId = settings.organizationId;
|
|
1126
1589
|
this.#workspaceFiles = settings.workspaceFiles;
|
|
1590
|
+
this.#workspaceId = settings.workspaceId;
|
|
1591
|
+
this.#workspaceApi = settings.baseUrl && settings.token ? { baseUrl: settings.baseUrl, token: settings.token, fetch: settings.fetch } : void 0;
|
|
1592
|
+
this.#settleDeadlineMs = sanitizeDelayMs(settings.settleDeadlineMs, SETTLE_DEADLINE_MS);
|
|
1593
|
+
this.#settleRetryDelayMs = sanitizeDelayMs(settings.settleRetryDelayMs, SETTLE_RETRY_DELAY_MS);
|
|
1127
1594
|
if (settings.workspaceFiles && settings.workspaceId && settings.workspaceFiles.workspaceId !== settings.workspaceId) {
|
|
1128
1595
|
throw new CoderAgentError(
|
|
1129
1596
|
`workspaceFiles.workspaceId (${settings.workspaceFiles.workspaceId}) does not match the agent's workspaceId (${settings.workspaceId}); the chat's tools would not see uploaded files.`
|
|
@@ -1144,6 +1611,7 @@ var CoderAgent = class {
|
|
|
1144
1611
|
id: settings.id,
|
|
1145
1612
|
instructions: settings.instructions,
|
|
1146
1613
|
tools: settings.tools,
|
|
1614
|
+
toolsContext: settings.toolsContext,
|
|
1147
1615
|
toolChoice: settings.toolChoice,
|
|
1148
1616
|
stopWhen: settings.stopWhen ?? DEFAULT_STOP,
|
|
1149
1617
|
maxRetries: settings.maxRetries ?? 0
|
|
@@ -1182,22 +1650,41 @@ var CoderAgent = class {
|
|
|
1182
1650
|
resetSession() {
|
|
1183
1651
|
this.#model.resetSession();
|
|
1184
1652
|
}
|
|
1185
|
-
/**
|
|
1186
|
-
|
|
1653
|
+
/**
|
|
1654
|
+
* Interrupt the in-flight generation, if any. Resolves as soon as the server
|
|
1655
|
+
* acknowledges the interrupt — the run keeps winding down asynchronously for
|
|
1656
|
+
* a few seconds afterwards (see {@link CoderAgent.archive}).
|
|
1657
|
+
*/
|
|
1658
|
+
async interrupt(opts) {
|
|
1187
1659
|
const id = this.#model.chatId;
|
|
1188
|
-
if (id) await this.#client.interruptChat(id);
|
|
1660
|
+
if (id) await this.#client.interruptChat(id, opts?.signal);
|
|
1189
1661
|
}
|
|
1190
|
-
/**
|
|
1191
|
-
|
|
1662
|
+
/**
|
|
1663
|
+
* Archive the underlying chat (safe cleanup; hides it from listings).
|
|
1664
|
+
*
|
|
1665
|
+
* A freshly interrupted/settled chat can keep winding down server-side for a
|
|
1666
|
+
* few seconds, during which the server rejects archiving with a 409. Those
|
|
1667
|
+
* 409s are retried (~1s apart, ~15s overall — see `settleDeadlineMs` /
|
|
1668
|
+
* `settleRetryDelayMs`) and the last one is rethrown if the chat never
|
|
1669
|
+
* settles; any other failure, including a caller abort, rethrows immediately.
|
|
1670
|
+
*/
|
|
1671
|
+
async archive(opts) {
|
|
1192
1672
|
const id = this.#model.chatId;
|
|
1193
|
-
if (id)
|
|
1673
|
+
if (!id) return;
|
|
1674
|
+
await archiveWhenSettled(this.#client, id, {
|
|
1675
|
+
deadlineMs: this.#settleDeadlineMs,
|
|
1676
|
+
retryDelayMs: this.#settleRetryDelayMs,
|
|
1677
|
+
signal: opts?.signal
|
|
1678
|
+
});
|
|
1194
1679
|
}
|
|
1195
1680
|
/**
|
|
1196
1681
|
* Clean up the chat when the agent leaves an `await using` scope, so cleanup
|
|
1197
1682
|
* rides scope exit instead of a separate call you have to remember in a
|
|
1198
|
-
* `finally`. Interrupts any in-flight server run, then archives the chat
|
|
1199
|
-
*
|
|
1200
|
-
*
|
|
1683
|
+
* `finally`. Interrupts any in-flight server run, then archives the chat
|
|
1684
|
+
* (retrying while the interrupted run settles). Best-effort and bounded
|
|
1685
|
+
* (~15s overall): disposal errors are swallowed after the bounded attempts
|
|
1686
|
+
* so they can't mask the scope's own error — call {@link CoderAgent.archive}
|
|
1687
|
+
* directly when you need guaranteed cleanup.
|
|
1201
1688
|
*
|
|
1202
1689
|
* @example
|
|
1203
1690
|
* ```ts
|
|
@@ -1207,12 +1694,13 @@ var CoderAgent = class {
|
|
|
1207
1694
|
* ```
|
|
1208
1695
|
*/
|
|
1209
1696
|
async [Symbol.asyncDispose]() {
|
|
1697
|
+
const deadline = AbortSignal.timeout(this.#settleDeadlineMs);
|
|
1210
1698
|
try {
|
|
1211
|
-
await this.interrupt();
|
|
1699
|
+
await this.interrupt({ signal: deadline });
|
|
1212
1700
|
} catch {
|
|
1213
1701
|
}
|
|
1214
1702
|
try {
|
|
1215
|
-
await this.archive();
|
|
1703
|
+
await this.archive({ signal: deadline });
|
|
1216
1704
|
} catch {
|
|
1217
1705
|
}
|
|
1218
1706
|
}
|
|
@@ -1256,7 +1744,158 @@ var CoderAgent = class {
|
|
|
1256
1744
|
});
|
|
1257
1745
|
return { workspaceId: this.#workspaceFiles.workspaceId, path };
|
|
1258
1746
|
}
|
|
1747
|
+
// --- previews ---------------------------------------------------------------
|
|
1748
|
+
/** The v2 connection + workspace id the preview helpers need, or a clear error. */
|
|
1749
|
+
#previewContext(method) {
|
|
1750
|
+
if (!this.#workspaceId) {
|
|
1751
|
+
throw new CoderAgentError(
|
|
1752
|
+
`${method}() requires the agent to be bound to a workspace \u2014 construct the CoderAgent with the \`workspaceId\` setting.`
|
|
1753
|
+
);
|
|
1754
|
+
}
|
|
1755
|
+
if (!this.#workspaceApi) {
|
|
1756
|
+
throw new CoderAgentError(
|
|
1757
|
+
`${method}() needs the deployment's REST credentials \u2014 construct the CoderAgent with \`baseUrl\` and \`token\` (they can accompany \`client\`).`
|
|
1758
|
+
);
|
|
1759
|
+
}
|
|
1760
|
+
return { conn: this.#workspaceApi, workspaceId: this.#workspaceId };
|
|
1761
|
+
}
|
|
1762
|
+
/**
|
|
1763
|
+
* Resolve the browser URL where a port on the bound workspace can be
|
|
1764
|
+
* previewed, e.g. `https://3000--main--dev--alice.apps.example.com`.
|
|
1765
|
+
* Composed from stable v2 endpoints (workspace lookup + wildcard app host),
|
|
1766
|
+
* so it works against old Coder servers. Requires the `workspaceId` setting,
|
|
1767
|
+
* `baseUrl`/`token` credentials, and a deployment with a wildcard access URL
|
|
1768
|
+
* configured.
|
|
1769
|
+
*
|
|
1770
|
+
* The URL honors the port's current share level — private to the workspace
|
|
1771
|
+
* owner unless shared (see {@link CoderAgent.sharePreview}).
|
|
1772
|
+
*/
|
|
1773
|
+
async getPreview(opts) {
|
|
1774
|
+
const { conn, workspaceId } = this.#previewContext("getPreview");
|
|
1775
|
+
const { url } = await resolveWorkspacePreview(
|
|
1776
|
+
conn,
|
|
1777
|
+
{ workspaceId, port: opts.port, agentName: opts.agentName, protocol: opts.protocol },
|
|
1778
|
+
opts.signal
|
|
1779
|
+
);
|
|
1780
|
+
return { url };
|
|
1781
|
+
}
|
|
1782
|
+
/**
|
|
1783
|
+
* Share a port on the bound workspace and return its preview URL. Upserts
|
|
1784
|
+
* the port's share level — `authenticated` by default — so teammates (or,
|
|
1785
|
+
* with `public`, anyone) can open what the agent is serving; re-invoking
|
|
1786
|
+
* updates the level in place. On Coder servers that predate port sharing
|
|
1787
|
+
* (< v2.9) this fails with a 404 {@link CoderApiError} saying so.
|
|
1788
|
+
*/
|
|
1789
|
+
async sharePreview(opts) {
|
|
1790
|
+
const { conn, workspaceId } = this.#previewContext("sharePreview");
|
|
1791
|
+
return shareWorkspacePreview(
|
|
1792
|
+
conn,
|
|
1793
|
+
{
|
|
1794
|
+
workspaceId,
|
|
1795
|
+
port: opts.port,
|
|
1796
|
+
agentName: opts.agentName,
|
|
1797
|
+
protocol: opts.protocol,
|
|
1798
|
+
shareLevel: opts.shareLevel
|
|
1799
|
+
},
|
|
1800
|
+
opts.signal
|
|
1801
|
+
);
|
|
1802
|
+
}
|
|
1259
1803
|
};
|
|
1804
|
+
|
|
1805
|
+
// src/model/history.ts
|
|
1806
|
+
function toolErrorText(result) {
|
|
1807
|
+
if (typeof result === "string") return result;
|
|
1808
|
+
if (result === void 0 || result === null) return "";
|
|
1809
|
+
return JSON.stringify(result) ?? "";
|
|
1810
|
+
}
|
|
1811
|
+
function toDynamicToolPart(call, result) {
|
|
1812
|
+
const toolCallId = call.tool_call_id;
|
|
1813
|
+
const toolName = call.tool_name;
|
|
1814
|
+
if (!toolCallId || !toolName) return void 0;
|
|
1815
|
+
const providerExecuted = call.provider_executed ?? result?.provider_executed;
|
|
1816
|
+
const base = {
|
|
1817
|
+
type: "dynamic-tool",
|
|
1818
|
+
toolName,
|
|
1819
|
+
toolCallId,
|
|
1820
|
+
...providerExecuted !== void 0 ? { providerExecuted } : {}
|
|
1821
|
+
};
|
|
1822
|
+
if (!result) return { ...base, state: "input-available", input: call.args };
|
|
1823
|
+
if (result.is_error) {
|
|
1824
|
+
return {
|
|
1825
|
+
...base,
|
|
1826
|
+
state: "output-error",
|
|
1827
|
+
input: call.args,
|
|
1828
|
+
errorText: toolErrorText(result.result)
|
|
1829
|
+
};
|
|
1830
|
+
}
|
|
1831
|
+
return { ...base, state: "output-available", input: call.args, output: result.result };
|
|
1832
|
+
}
|
|
1833
|
+
function toUIParts(content, resultsByCallId, opts) {
|
|
1834
|
+
const parts = [];
|
|
1835
|
+
for (const part of content) {
|
|
1836
|
+
switch (part.type) {
|
|
1837
|
+
case "text":
|
|
1838
|
+
parts.push({ type: "text", text: part.text ?? "", state: "done" });
|
|
1839
|
+
break;
|
|
1840
|
+
case "reasoning":
|
|
1841
|
+
parts.push({ type: "reasoning", text: part.text ?? "", state: "done" });
|
|
1842
|
+
break;
|
|
1843
|
+
case "tool-call": {
|
|
1844
|
+
const result = part.tool_call_id ? resultsByCallId.get(part.tool_call_id) : void 0;
|
|
1845
|
+
const ui = toDynamicToolPart(part, result);
|
|
1846
|
+
if (ui) parts.push(ui);
|
|
1847
|
+
break;
|
|
1848
|
+
}
|
|
1849
|
+
case "source":
|
|
1850
|
+
if (part.url) {
|
|
1851
|
+
parts.push({
|
|
1852
|
+
type: "source-url",
|
|
1853
|
+
sourceId: part.source_id || part.url,
|
|
1854
|
+
url: part.url,
|
|
1855
|
+
...part.title ? { title: part.title } : {}
|
|
1856
|
+
});
|
|
1857
|
+
}
|
|
1858
|
+
break;
|
|
1859
|
+
case "file": {
|
|
1860
|
+
const url = opts?.fileUrl?.(part) ?? part.url;
|
|
1861
|
+
if (url) {
|
|
1862
|
+
parts.push({
|
|
1863
|
+
type: "file",
|
|
1864
|
+
mediaType: part.media_type ?? "application/octet-stream",
|
|
1865
|
+
...part.name ? { filename: part.name } : {},
|
|
1866
|
+
url
|
|
1867
|
+
});
|
|
1868
|
+
}
|
|
1869
|
+
break;
|
|
1870
|
+
}
|
|
1871
|
+
case "tool-result":
|
|
1872
|
+
break;
|
|
1873
|
+
default:
|
|
1874
|
+
break;
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
return parts;
|
|
1878
|
+
}
|
|
1879
|
+
function chatMessagesToUIMessages(messages, opts) {
|
|
1880
|
+
const ordered = [...messages].sort((a, b) => a.id - b.id);
|
|
1881
|
+
const resultsByCallId = /* @__PURE__ */ new Map();
|
|
1882
|
+
for (const message of ordered) {
|
|
1883
|
+
for (const part of message.content ?? []) {
|
|
1884
|
+
if (part.type !== "tool-result" || !part.tool_call_id) continue;
|
|
1885
|
+
if (!resultsByCallId.has(part.tool_call_id)) resultsByCallId.set(part.tool_call_id, part);
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
const out = [];
|
|
1889
|
+
for (const message of ordered) {
|
|
1890
|
+
if (message.role === "tool") continue;
|
|
1891
|
+
out.push({
|
|
1892
|
+
id: String(message.id),
|
|
1893
|
+
role: message.role,
|
|
1894
|
+
parts: toUIParts(message.content ?? [], resultsByCallId, opts)
|
|
1895
|
+
});
|
|
1896
|
+
}
|
|
1897
|
+
return out;
|
|
1898
|
+
}
|
|
1260
1899
|
export {
|
|
1261
1900
|
CHAT_ATTACHMENT_MEDIA_TYPES,
|
|
1262
1901
|
CODER_PROVIDER_OPTIONS,
|
|
@@ -1268,6 +1907,7 @@ export {
|
|
|
1268
1907
|
CoderLanguageModel,
|
|
1269
1908
|
MAX_CHAT_FILE_SIZE_BYTES,
|
|
1270
1909
|
TurnTranslator,
|
|
1910
|
+
chatMessagesToUIMessages,
|
|
1271
1911
|
classifyTurnAction,
|
|
1272
1912
|
dataContentToFileContent,
|
|
1273
1913
|
dynamicToolNames,
|
|
@@ -1275,6 +1915,7 @@ export {
|
|
|
1275
1915
|
resolveFileContent,
|
|
1276
1916
|
streamChatEvents,
|
|
1277
1917
|
toolsToDynamicTools,
|
|
1278
|
-
userContentToInputParts
|
|
1918
|
+
userContentToInputParts,
|
|
1919
|
+
watchChatEvents
|
|
1279
1920
|
};
|
|
1280
1921
|
//# sourceMappingURL=index.js.map
|