@coder/ai-sdk-agent 0.1.0 → 0.2.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 CHANGED
@@ -42,7 +42,70 @@ var CoderChatError = class extends CoderAgentError {
42
42
  }
43
43
  };
44
44
 
45
+ // src/files.ts
46
+ function isBlob(v) {
47
+ return typeof Blob !== "undefined" && v instanceof Blob;
48
+ }
49
+ function isReadableStream(v) {
50
+ return typeof ReadableStream !== "undefined" && v instanceof ReadableStream;
51
+ }
52
+ function resolveFileContent(content, opts) {
53
+ let mediaType = opts?.mediaType;
54
+ let name = opts?.name;
55
+ let body;
56
+ let size;
57
+ if (isBlob(content)) {
58
+ body = content;
59
+ size = content.size;
60
+ if (!mediaType && content.type) mediaType = content.type;
61
+ const maybeName = content.name;
62
+ if (!name && typeof maybeName === "string") name = maybeName;
63
+ } else if (isReadableStream(content)) {
64
+ body = content;
65
+ size = void 0;
66
+ } else if (content instanceof Uint8Array || content instanceof ArrayBuffer) {
67
+ size = content.byteLength;
68
+ body = new Blob([content]);
69
+ } else {
70
+ throw new CoderAgentError(
71
+ "Unsupported file content: expected a Blob/File, ReadableStream, Uint8Array, or ArrayBuffer."
72
+ );
73
+ }
74
+ if (!mediaType) {
75
+ throw new CoderAgentError(
76
+ "Could not determine the file's media type. Pass `mediaType`, or provide a Blob/File that carries its own `type`."
77
+ );
78
+ }
79
+ return { body, mediaType, name, size };
80
+ }
81
+ function base64ToBytes(b64) {
82
+ if (typeof Buffer !== "undefined") return Buffer.from(b64, "base64");
83
+ const bin = atob(b64);
84
+ const bytes = new Uint8Array(bin.length);
85
+ for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
86
+ return bytes;
87
+ }
88
+ function dataContentToFileContent(data) {
89
+ if (data instanceof Uint8Array) return data;
90
+ if (typeof data === "string") return base64ToBytes(data);
91
+ throw new CoderAgentError(
92
+ "File parts referencing a URL are not supported; provide the file bytes instead."
93
+ );
94
+ }
95
+
45
96
  // src/coder/types.ts
97
+ var CHAT_ATTACHMENT_MEDIA_TYPES = /* @__PURE__ */ new Set([
98
+ "application/json",
99
+ "application/pdf",
100
+ "image/gif",
101
+ "image/jpeg",
102
+ "image/png",
103
+ "image/webp",
104
+ "text/csv",
105
+ "text/markdown",
106
+ "text/plain"
107
+ ]);
108
+ var MAX_CHAT_FILE_SIZE_BYTES = 10 * 1024 * 1024;
46
109
  var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
47
110
  "waiting",
48
111
  "completed",
@@ -162,24 +225,23 @@ var CoderChatClient = class {
162
225
  this.#fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
163
226
  this.#webSocketFactory = options.webSocketFactory;
164
227
  }
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;
228
+ /**
229
+ * Issue a request and return the raw `Response` on success (body unread). On a
230
+ * non-2xx status the body is consumed to build a {@link CoderApiError}. Shared
231
+ * by JSON requests and the raw upload/download endpoints.
232
+ */
233
+ async #send(method, path, opts) {
234
+ const headers = {
235
+ "Coder-Session-Token": this.#token,
236
+ ...opts?.headers
237
+ };
238
+ const init = { method, headers, body: opts?.body, signal: opts?.signal };
239
+ if (typeof ReadableStream !== "undefined" && opts?.body instanceof ReadableStream) {
240
+ init.duplex = "half";
180
241
  }
242
+ const res = await this.#fetch(`${this.baseUrl}${path}`, init);
181
243
  if (!res.ok) {
182
- const errObj = parsed ?? {};
244
+ const errObj = await this.#json(res) ?? {};
183
245
  throw new CoderApiError({
184
246
  status: res.status,
185
247
  method,
@@ -188,7 +250,25 @@ var CoderChatClient = class {
188
250
  detail: errObj.detail
189
251
  });
190
252
  }
191
- return parsed;
253
+ return res;
254
+ }
255
+ /** Read a response body as JSON, tolerating an unreadable/empty/malformed body (→ undefined). */
256
+ async #json(res) {
257
+ const text = await res.text().catch(() => "");
258
+ if (text.length === 0) return void 0;
259
+ try {
260
+ return JSON.parse(text);
261
+ } catch {
262
+ return void 0;
263
+ }
264
+ }
265
+ async #request(method, path, body, signal) {
266
+ const res = await this.#send(method, path, {
267
+ body: body === void 0 ? void 0 : JSON.stringify(body),
268
+ headers: body === void 0 ? void 0 : { "Content-Type": "application/json" },
269
+ signal
270
+ });
271
+ return await this.#json(res);
192
272
  }
193
273
  // --- REST -----------------------------------------------------------------
194
274
  listModelConfigs(signal) {
@@ -239,6 +319,66 @@ var CoderChatClient = class {
239
319
  archiveChat(chatId, signal) {
240
320
  return this.updateChat(chatId, { archived: true }, signal);
241
321
  }
322
+ // --- Files ----------------------------------------------------------------
323
+ /**
324
+ * Upload a file as a durable chat attachment, returning its id (referenced
325
+ * from message content via a `file` input part). The server enforces a narrow
326
+ * media-type allowlist and a 10 MiB cap; both are checked client-side first so
327
+ * unsupported files fail fast with a clear error instead of an opaque 4xx.
328
+ *
329
+ * The body is sent raw (not multipart): `Content-Type` carries the media type
330
+ * and `Content-Disposition` the filename, mirroring `codersdk`.
331
+ */
332
+ async uploadChatFile(organizationId, file, signal) {
333
+ const resolved = resolveFileContent(file.content, {
334
+ mediaType: file.mediaType,
335
+ name: file.name
336
+ });
337
+ const mediaType = (resolved.mediaType.split(";")[0] ?? resolved.mediaType).trim().toLowerCase();
338
+ if (!CHAT_ATTACHMENT_MEDIA_TYPES.has(mediaType)) {
339
+ throw new CoderAgentError(
340
+ `Media type "${mediaType}" is not allowed for chat attachments (allowed: ${[...CHAT_ATTACHMENT_MEDIA_TYPES].join(", ")}). Write other file types to a workspace instead.`
341
+ );
342
+ }
343
+ if (resolved.size !== void 0 && resolved.size > MAX_CHAT_FILE_SIZE_BYTES) {
344
+ throw new CoderAgentError(
345
+ `File is ${resolved.size} bytes, over the ${MAX_CHAT_FILE_SIZE_BYTES}-byte (10 MiB) chat attachment limit. Write large files to a workspace instead.`
346
+ );
347
+ }
348
+ const headers = { "Content-Type": mediaType };
349
+ if (resolved.name) {
350
+ const ascii = resolved.name.replace(/[^\x20-\x7e]/g, "_").replace(/(["\\])/g, "\\$1");
351
+ const utf8 = encodeURIComponent(resolved.name).replace(
352
+ /['()*]/g,
353
+ (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`
354
+ );
355
+ headers["Content-Disposition"] = `attachment; filename="${ascii}"; filename*=UTF-8''${utf8}`;
356
+ }
357
+ const res = await this.#send(
358
+ "POST",
359
+ `${API_PREFIX}/files?organization=${encodeURIComponent(organizationId)}`,
360
+ { body: resolved.body, headers, signal }
361
+ );
362
+ const parsed = await this.#json(res);
363
+ if (!parsed?.id) {
364
+ throw new CoderApiError({
365
+ status: res.status,
366
+ method: "POST",
367
+ path: `${API_PREFIX}/files`,
368
+ message: "upload succeeded but the response contained no file id"
369
+ });
370
+ }
371
+ return { id: parsed.id, mediaType, name: resolved.name };
372
+ }
373
+ /** Download a chat file's bytes and media type by id. */
374
+ async getChatFile(fileId, signal) {
375
+ const res = await this.#send("GET", `${API_PREFIX}/files/${fileId}`, { signal });
376
+ const buf = await res.arrayBuffer();
377
+ return {
378
+ bytes: new Uint8Array(buf),
379
+ mediaType: res.headers.get("Content-Type") ?? "application/octet-stream"
380
+ };
381
+ }
242
382
  // --- Streaming ------------------------------------------------------------
243
383
  streamEvents(chatId, opts) {
244
384
  return streamChatEvents({
@@ -283,6 +423,7 @@ var CoderChatClient = class {
283
423
  };
284
424
 
285
425
  // src/model/prompt.ts
426
+ var CODER_PROVIDER_OPTIONS = "coder";
286
427
  function extractSystemPrompt(prompt) {
287
428
  const parts = prompt.filter(
288
429
  (m) => m.role === "system"
@@ -291,14 +432,28 @@ function extractSystemPrompt(prompt) {
291
432
  const joined = parts.map((m) => m.content).join("\n\n").trim();
292
433
  return joined.length > 0 ? joined : void 0;
293
434
  }
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;
435
+ function coderFileId(providerOptions) {
436
+ const ns = providerOptions?.[CODER_PROVIDER_OPTIONS];
437
+ return typeof ns?.fileId === "string" && ns.fileId.length > 0 ? ns.fileId : void 0;
438
+ }
439
+ async function userContentToInputParts(content, uploadFile) {
440
+ const parts = await Promise.all(
441
+ content.map(async (part) => {
442
+ if (part.type === "text") {
443
+ return part.text.length > 0 ? { type: "text", text: part.text } : null;
444
+ }
445
+ if (part.type === "file") {
446
+ const fileId = coderFileId(part.providerOptions) ?? await uploadFile({
447
+ data: part.data,
448
+ mediaType: part.mediaType,
449
+ filename: part.filename
450
+ });
451
+ return { type: "file", file_id: fileId };
452
+ }
453
+ return null;
454
+ })
455
+ );
456
+ return parts.filter((p) => p !== null);
302
457
  }
303
458
  function toolResultOutputToChatd(output) {
304
459
  switch (output.type) {
@@ -336,7 +491,7 @@ function classifyTurnAction(prompt) {
336
491
  return { kind: "resume", toolResults };
337
492
  }
338
493
  if (last.role === "user") {
339
- return { kind: "new-turn", content: userContentToInputParts(last) };
494
+ return { kind: "new-turn", content: last.content };
340
495
  }
341
496
  return { kind: "noop" };
342
497
  }
@@ -678,6 +833,23 @@ var CoderLanguageModel = class {
678
833
  this.#modelResolved = true;
679
834
  return this.#resolvedModelConfigId;
680
835
  }
836
+ /**
837
+ * Resolve a user message's content to chatd input parts, uploading any file
838
+ * parts to chat-file storage (the upload endpoint needs only the organization
839
+ * id, so this runs before the chat exists). Pre-uploaded files carried via
840
+ * `providerOptions.coder.fileId` are referenced without re-uploading.
841
+ */
842
+ #buildContent(content, signal) {
843
+ const uploadFile = async (f) => {
844
+ const uploaded = await this.#config.client.uploadChatFile(
845
+ this.#config.organizationId,
846
+ { content: dataContentToFileContent(f.data), mediaType: f.mediaType, name: f.filename },
847
+ signal
848
+ );
849
+ return uploaded.id;
850
+ };
851
+ return userContentToInputParts(content, uploadFile);
852
+ }
681
853
  async *#runTurn(options) {
682
854
  if (this.#inFlight) {
683
855
  throw new CoderAgentError(
@@ -685,23 +857,62 @@ var CoderLanguageModel = class {
685
857
  );
686
858
  }
687
859
  this.#inFlight = true;
860
+ const externalSignal = options.abortSignal;
861
+ const timeoutMs = this.#config.requestTimeoutMs;
862
+ const timeoutSignal = timeoutMs !== void 0 && timeoutMs > 0 ? AbortSignal.timeout(timeoutMs) : void 0;
863
+ const sources = [externalSignal, timeoutSignal].filter(
864
+ (s) => s !== void 0
865
+ );
866
+ const signal = sources.length > 0 ? AbortSignal.any(sources) : void 0;
867
+ const translator = new TurnTranslator({ dynamicToolNames: dynamicToolNames(options.tools) });
868
+ let interruptSent = false;
869
+ const interrupt = () => {
870
+ const id = this.#chatId;
871
+ if (interruptSent || !id) return;
872
+ interruptSent = true;
873
+ void this.#config.client.interruptChat(id).catch(() => {
874
+ });
875
+ };
876
+ signal?.addEventListener("abort", interrupt, { once: true });
877
+ const throwIfAborted = () => {
878
+ if (timeoutSignal?.aborted) {
879
+ throw new CoderChatError({
880
+ message: `Coder Agent turn exceeded its ${timeoutMs}ms requestTimeoutMs budget.`,
881
+ kind: "timeout",
882
+ retryable: true
883
+ });
884
+ }
885
+ if (signal?.aborted) {
886
+ throw externalSignal?.reason ?? new DOMException("The operation was aborted.", "AbortError");
887
+ }
888
+ };
688
889
  try {
689
- const { prompt, abortSignal: signal } = options;
690
- yield { type: "stream-start", warnings: [] };
890
+ const { prompt } = options;
891
+ const warnings = [];
892
+ if (options.responseFormat?.type === "json") {
893
+ warnings.push({
894
+ type: "unsupported",
895
+ feature: "responseFormat",
896
+ details: "Coder Agents does not enforce a JSON schema server-side, so structured output is best-effort (not schema-constrained). For reliable structured output, use @coder/ai-sdk-provider (createCoder) with generateObject / Output.object."
897
+ });
898
+ }
899
+ yield { type: "stream-start", warnings };
691
900
  const action = classifyTurnAction(prompt);
692
901
  if (action.kind === "noop") {
693
902
  throw new CoderAgentError(
694
903
  "CoderAgent received a prompt with no user message or tool results to act on."
695
904
  );
696
905
  }
697
- const translator = new TurnTranslator({ dynamicToolNames: dynamicToolNames(options.tools) });
698
906
  let afterId;
699
907
  if (action.kind === "new-turn") {
700
- const modelConfigId = await this.#resolveModelConfigId(signal);
908
+ const [modelConfigId, content] = await Promise.all([
909
+ this.#resolveModelConfigId(signal),
910
+ this.#buildContent(action.content, signal)
911
+ ]);
701
912
  if (!this.#chatId) {
702
913
  const req = {
703
914
  organization_id: this.#config.organizationId,
704
- content: action.content,
915
+ content,
705
916
  client_type: "api"
706
917
  };
707
918
  const system = extractSystemPrompt(prompt);
@@ -719,7 +930,7 @@ var CoderLanguageModel = class {
719
930
  const resp = await this.#config.client.createChatMessage(
720
931
  this.#chatId,
721
932
  {
722
- content: action.content,
933
+ content,
723
934
  ...modelConfigId ? { model_config_id: modelConfigId } : {}
724
935
  },
725
936
  signal
@@ -740,27 +951,51 @@ var CoderLanguageModel = class {
740
951
  }
741
952
  const chatId = this.#chatId;
742
953
  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;
954
+ try {
955
+ for await (const ev of this.#config.client.streamEvents(chatId, { afterId, signal })) {
956
+ for (const part of translator.ingest(ev)) yield part;
957
+ const status = translator.terminalStatus;
958
+ if (status) {
959
+ if (status !== "requires_action") break;
960
+ if (translator.clientToolCallSeen) break;
961
+ if (++sinceRequiresAction > 200) break;
962
+ }
750
963
  }
964
+ } catch (err) {
965
+ throwIfAborted();
966
+ if (err instanceof CoderAgentError && !(err instanceof CoderApiError) && !(err instanceof CoderChatError)) {
967
+ throw new CoderChatError({
968
+ message: `Coder chat stream failed mid-turn: ${err.message}`,
969
+ kind: "stream_closed",
970
+ retryable: true
971
+ });
972
+ }
973
+ throw err;
751
974
  }
752
- if (signal?.aborted && !translator.terminalStatus) {
753
- throw signal.reason ?? new DOMException("The operation was aborted.", "AbortError");
975
+ throwIfAborted();
976
+ if (!translator.terminalStatus && !translator.error) {
977
+ throw new CoderChatError({
978
+ message: "Coder chat stream ended before the turn settled (connection closed or the server ended the stream without a terminal status).",
979
+ kind: "stream_closed",
980
+ retryable: true
981
+ });
754
982
  }
755
983
  for (const part of translator.finish()) yield part;
984
+ } catch (err) {
985
+ throwIfAborted();
986
+ throw err;
987
+ } finally {
756
988
  if (translator.maxMessageId > this.#lastSeenMessageId)
757
989
  this.#lastSeenMessageId = translator.maxMessageId;
758
- } finally {
990
+ signal?.removeEventListener("abort", interrupt);
991
+ if (!translator.terminalStatus) interrupt();
759
992
  this.#inFlight = false;
760
993
  }
761
994
  }
762
995
  async doStream(options) {
763
- const gen = this.#runTurn(options);
996
+ const cancelController = new AbortController();
997
+ const abortSignal = options.abortSignal ? AbortSignal.any([options.abortSignal, cancelController.signal]) : cancelController.signal;
998
+ const gen = this.#runTurn({ ...options, abortSignal });
764
999
  const stream = new ReadableStream({
765
1000
  async pull(controller) {
766
1001
  try {
@@ -768,11 +1003,14 @@ var CoderLanguageModel = class {
768
1003
  if (done) controller.close();
769
1004
  else controller.enqueue(value);
770
1005
  } catch (err) {
771
- controller.error(err);
1006
+ if (cancelController.signal.aborted) controller.close();
1007
+ else controller.error(err);
772
1008
  }
773
1009
  },
774
1010
  async cancel() {
775
- await gen.return();
1011
+ cancelController.abort();
1012
+ await gen.return().catch(() => {
1013
+ });
776
1014
  }
777
1015
  });
778
1016
  return { stream };
@@ -837,12 +1075,27 @@ var CoderLanguageModel = class {
837
1075
  };
838
1076
 
839
1077
  // src/agent/coder-agent.ts
1078
+ function makeChatAttachment(file) {
1079
+ return {
1080
+ ...file,
1081
+ toFilePart: () => ({
1082
+ type: "file",
1083
+ // Bytes are unused: the reference travels in providerOptions.coder.fileId.
1084
+ data: "",
1085
+ mediaType: file.mediaType,
1086
+ ...file.name ? { filename: file.name } : {},
1087
+ providerOptions: { [CODER_PROVIDER_OPTIONS]: { fileId: file.id } }
1088
+ })
1089
+ };
1090
+ }
840
1091
  var DEFAULT_STOP = stepCountIs(64);
841
1092
  var CoderAgent = class {
842
1093
  version = "agent-v1";
843
1094
  #client;
844
1095
  #model;
845
1096
  #inner;
1097
+ #organizationId;
1098
+ #workspaceFiles;
846
1099
  constructor(settings) {
847
1100
  if (settings.client) {
848
1101
  this.#client = settings.client;
@@ -858,6 +1111,13 @@ var CoderAgent = class {
858
1111
  "CoderAgent requires either `client` or both `baseUrl` and `token`."
859
1112
  );
860
1113
  }
1114
+ this.#organizationId = settings.organizationId;
1115
+ this.#workspaceFiles = settings.workspaceFiles;
1116
+ if (settings.workspaceFiles && settings.workspaceId && settings.workspaceFiles.workspaceId !== settings.workspaceId) {
1117
+ throw new CoderAgentError(
1118
+ `workspaceFiles.workspaceId (${settings.workspaceFiles.workspaceId}) does not match the agent's workspaceId (${settings.workspaceId}); the chat's tools would not see uploaded files.`
1119
+ );
1120
+ }
861
1121
  this.#model = new CoderLanguageModel({
862
1122
  client: this.#client,
863
1123
  organizationId: settings.organizationId,
@@ -865,7 +1125,8 @@ var CoderAgent = class {
865
1125
  workspaceId: settings.workspaceId,
866
1126
  mcpServerIds: settings.mcpServerIds,
867
1127
  planMode: settings.planMode,
868
- chatId: settings.chatId
1128
+ chatId: settings.chatId,
1129
+ requestTimeoutMs: settings.requestTimeoutMs
869
1130
  });
870
1131
  this.#inner = new ToolLoopAgent({
871
1132
  model: this.#model,
@@ -898,6 +1159,14 @@ var CoderAgent = class {
898
1159
  return this.#inner.stream(options);
899
1160
  }
900
1161
  // --- session helpers ------------------------------------------------------
1162
+ /**
1163
+ * List the model configs available on the deployment. Use this to discover
1164
+ * valid values for the `model` hint instead of guessing ids — match on
1165
+ * `id`, `provider`/`model`, or `display_name`.
1166
+ */
1167
+ listModels(signal) {
1168
+ return this.#client.listModelConfigs(signal);
1169
+ }
901
1170
  /** Start a fresh chatd chat on the next turn. */
902
1171
  resetSession() {
903
1172
  this.#model.resetSession();
@@ -912,19 +1181,89 @@ var CoderAgent = class {
912
1181
  const id = this.#model.chatId;
913
1182
  if (id) await this.#client.archiveChat(id);
914
1183
  }
1184
+ /**
1185
+ * Clean up the chat when the agent leaves an `await using` scope, so cleanup
1186
+ * rides scope exit instead of a separate call you have to remember in a
1187
+ * `finally`. Interrupts any in-flight server run, then archives the chat.
1188
+ * Best-effort: disposal errors are swallowed so they can't mask the scope's
1189
+ * own error.
1190
+ *
1191
+ * @example
1192
+ * ```ts
1193
+ * await using agent = new CoderAgent({ ... });
1194
+ * const { text } = await agent.generate({ prompt: "…" });
1195
+ * // agent.interrupt() + agent.archive() run automatically here.
1196
+ * ```
1197
+ */
1198
+ async [Symbol.asyncDispose]() {
1199
+ try {
1200
+ await this.interrupt();
1201
+ } catch {
1202
+ }
1203
+ try {
1204
+ await this.archive();
1205
+ } catch {
1206
+ }
1207
+ }
1208
+ // --- files ----------------------------------------------------------------
1209
+ /**
1210
+ * Upload a file as a chat attachment — content for the model to *read* (a PDF,
1211
+ * image, CSV, …). Returns a handle you can reference from a later turn via
1212
+ * {@link ChatAttachment.toFilePart} (no re-upload). Validated against the chat
1213
+ * allowlist and 10 MiB cap up front.
1214
+ *
1215
+ * You usually don't need this: a native AI SDK `file` part dropped into a
1216
+ * message's `content` is uploaded transparently. Reach for `attach()` to
1217
+ * pre-upload, to reuse one file across turns, or to validate before sending.
1218
+ *
1219
+ * For large or non-allowlisted files (zips, datasets, binaries) — material for
1220
+ * the agent to *operate on* — use {@link CoderAgent.uploadToWorkspace}.
1221
+ */
1222
+ async attach(file, signal) {
1223
+ const uploaded = await this.#client.uploadChatFile(this.#organizationId, file, signal);
1224
+ return makeChatAttachment(uploaded);
1225
+ }
1226
+ /**
1227
+ * Write a file onto the bound workspace's filesystem for the agent to operate
1228
+ * on (extract, build, read with its tools). Requires a {@link WorkspaceFileStore}
1229
+ * via the `workspaceFiles` setting. The file is written as-is — no unpacking;
1230
+ * instruct the agent to `unzip`/`tar -x`, or extract over your own connection.
1231
+ *
1232
+ * The chat should be bound to the same workspace (`workspaceId`) so the agent's
1233
+ * server-side tools can see the file.
1234
+ */
1235
+ async uploadToWorkspace(file) {
1236
+ if (!this.#workspaceFiles) {
1237
+ throw new CoderAgentError(
1238
+ "uploadToWorkspace requires a `workspaceFiles` adapter. Construct the agent with `workspaceFiles` (e.g. backed by a @coder/ai-sdk-eve-sandbox session) to enable it."
1239
+ );
1240
+ }
1241
+ const { path } = await this.#workspaceFiles.write({
1242
+ content: file.content,
1243
+ path: file.path,
1244
+ signal: file.signal
1245
+ });
1246
+ return { workspaceId: this.#workspaceFiles.workspaceId, path };
1247
+ }
915
1248
  };
916
1249
  export {
1250
+ CHAT_ATTACHMENT_MEDIA_TYPES,
1251
+ CODER_PROVIDER_OPTIONS,
917
1252
  CoderAgent,
918
1253
  CoderAgentError,
919
1254
  CoderApiError,
920
1255
  CoderChatClient,
921
1256
  CoderChatError,
922
1257
  CoderLanguageModel,
1258
+ MAX_CHAT_FILE_SIZE_BYTES,
923
1259
  TurnTranslator,
924
1260
  classifyTurnAction,
1261
+ dataContentToFileContent,
925
1262
  dynamicToolNames,
926
1263
  extractSystemPrompt,
1264
+ resolveFileContent,
927
1265
  streamChatEvents,
928
- toolsToDynamicTools
1266
+ toolsToDynamicTools,
1267
+ userContentToInputParts
929
1268
  };
930
1269
  //# sourceMappingURL=index.js.map