@coder/ai-sdk-agent 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,49 @@
1
- import { ToolSet, Agent, SystemModelMessage, ToolChoice, StopCondition, ToolLoopAgent } from 'ai';
2
- import { LanguageModelV3, LanguageModelV3CallOptions, LanguageModelV3StreamResult, LanguageModelV3GenerateResult, LanguageModelV3StreamPart, LanguageModelV3Prompt } from '@ai-sdk/provider';
1
+ import { FilePart, ToolSet, Agent, SystemModelMessage, ToolChoice, StopCondition, ToolLoopAgent } from 'ai';
2
+ import { LanguageModelV3DataContent, LanguageModelV3, LanguageModelV3CallOptions, LanguageModelV3StreamResult, LanguageModelV3GenerateResult, LanguageModelV3StreamPart, LanguageModelV3Message, LanguageModelV3Prompt } from '@ai-sdk/provider';
3
+
4
+ /**
5
+ * Bytes to upload, in any convenient shape.
6
+ *
7
+ * `Blob`/`File` is the recommended form: it is lazy (not forced fully into
8
+ * memory), carries its own size (so the 10 MiB chat cap can fail fast and a
9
+ * `Content-Length` can be sent), is re-readable (so a failed upload can be
10
+ * retried), and works both in Node (`fs.openAsBlob("./x.pdf")`) and the browser
11
+ * (an `<input type=file>` yields a `File`). A `ReadableStream` is accepted for
12
+ * genuinely unbounded sources, but its size is unknown up front — the chat size
13
+ * cap can then only be enforced server-side.
14
+ */
15
+ type FileContent = Blob | ReadableStream<Uint8Array> | Uint8Array | ArrayBuffer;
16
+ /** A {@link FileContent} normalized for upload: a fetch-ready body plus derived metadata. */
17
+ interface ResolvedFile {
18
+ /**
19
+ * Body to hand to `fetch`. Raw bytes are wrapped in a `Blob` so the body is
20
+ * always a clean `BodyInit` (and bytes get a `Content-Length` for free).
21
+ */
22
+ body: Blob | ReadableStream<Uint8Array>;
23
+ /** Resolved IANA media type. */
24
+ mediaType: string;
25
+ /** Original filename, if known. */
26
+ name: string | undefined;
27
+ /** Byte length, if known up front (undefined for streams). */
28
+ size: number | undefined;
29
+ }
30
+ /**
31
+ * Normalize {@link FileContent} into a fetch-ready body and derive its media
32
+ * type, name, and (when known) size. `Blob`/`File` supply their own
33
+ * `type`/`name`/`size`; for raw bytes or a stream, `mediaType` must be provided
34
+ * by the caller. Explicit `opts.mediaType`/`opts.name` always win.
35
+ */
36
+ declare function resolveFileContent(content: FileContent, opts?: {
37
+ mediaType?: string;
38
+ name?: string;
39
+ }): ResolvedFile;
40
+ /**
41
+ * Convert an AI SDK file-part `data` value into {@link FileContent} for upload.
42
+ * The provider receives bytes or a base64 string (and, rarely, a URL). URLs are
43
+ * not expected: this model declares no `supportedUrls`, so the SDK downloads
44
+ * them to bytes before calling us — a URL here is therefore an error.
45
+ */
46
+ declare function dataContentToFileContent(data: LanguageModelV3DataContent): FileContent;
3
47
 
4
48
  /**
5
49
  * Wire types for Coder's experimental `chatd` chat API
@@ -19,6 +63,18 @@ interface ChatInputPart {
19
63
  end_line?: number;
20
64
  content?: string;
21
65
  }
66
+ /**
67
+ * Media types accepted by the chat-file upload endpoint
68
+ * (`POST /api/experimental/chats/files`). The server allowlist is intentionally
69
+ * narrow — anything else (zip, tarball, binaries, …) is not a chat attachment
70
+ * and belongs on the workspace filesystem instead. Mirrors
71
+ * `codersdk.AllChatAttachmentMediaTypes`.
72
+ */
73
+ type ChatAttachmentMediaType = "application/json" | "application/pdf" | "image/gif" | "image/jpeg" | "image/png" | "image/webp" | "text/csv" | "text/markdown" | "text/plain";
74
+ /** The chat-attachment media-type allowlist, as a runtime-checkable set. */
75
+ declare const CHAT_ATTACHMENT_MEDIA_TYPES: ReadonlySet<string>;
76
+ /** Per-file size cap enforced by the chat-file upload endpoint (10 MiB). */
77
+ declare const MAX_CHAT_FILE_SIZE_BYTES: number;
22
78
  type ChatClientType = "ui" | "api";
23
79
  type ChatPlanMode = "" | "plan";
24
80
  type ChatBusyBehavior = "queue" | "interrupt";
@@ -175,6 +231,10 @@ interface ChatMessagesResponse {
175
231
  queued_messages: ChatQueuedMessage[];
176
232
  has_more: boolean;
177
233
  }
234
+ /** Response from `POST /api/experimental/chats/files`: the uploaded file's id. */
235
+ interface UploadChatFileResponse {
236
+ id: string;
237
+ }
178
238
  interface ChatModelConfig {
179
239
  id: string;
180
240
  provider: string;
@@ -280,6 +340,24 @@ interface StreamChatEventsOptions {
280
340
  */
281
341
  declare function streamChatEvents(options: StreamChatEventsOptions): AsyncGenerator<ChatStreamEvent, void, void>;
282
342
 
343
+ /** A file to upload as a chat attachment. */
344
+ interface ChatFileInput {
345
+ content: FileContent;
346
+ /**
347
+ * Media type. Required unless `content` is a Blob/File with a non-empty `type`
348
+ * — note `fs.openAsBlob()` returns a Blob with no type, so pass it explicitly there.
349
+ */
350
+ mediaType?: string;
351
+ /** Original filename, surfaced to the model and UI. Defaults to a File's `name`. */
352
+ name?: string;
353
+ }
354
+ /** A file that has been uploaded to chat-file storage. */
355
+ interface UploadedChatFile {
356
+ /** Server-assigned file id, referenced from message content via a `file` part. */
357
+ id: string;
358
+ mediaType: string;
359
+ name?: string;
360
+ }
283
361
  interface CoderChatClientOptions {
284
362
  /** Base URL of the Coder deployment, e.g. `https://dev.coder.com`. */
285
363
  baseUrl: string;
@@ -312,6 +390,21 @@ declare class CoderChatClient {
312
390
  updateChat(chatId: string, req: UpdateChatRequest, signal?: AbortSignal): Promise<void>;
313
391
  /** Convenience: archive a chat (soft-hide; safe for cleanup). */
314
392
  archiveChat(chatId: string, signal?: AbortSignal): Promise<void>;
393
+ /**
394
+ * Upload a file as a durable chat attachment, returning its id (referenced
395
+ * from message content via a `file` input part). The server enforces a narrow
396
+ * media-type allowlist and a 10 MiB cap; both are checked client-side first so
397
+ * unsupported files fail fast with a clear error instead of an opaque 4xx.
398
+ *
399
+ * The body is sent raw (not multipart): `Content-Type` carries the media type
400
+ * and `Content-Disposition` the filename, mirroring `codersdk`.
401
+ */
402
+ uploadChatFile(organizationId: string, file: ChatFileInput, signal?: AbortSignal): Promise<UploadedChatFile>;
403
+ /** Download a chat file's bytes and media type by id. */
404
+ getChatFile(fileId: string, signal?: AbortSignal): Promise<{
405
+ bytes: Uint8Array;
406
+ mediaType: string;
407
+ }>;
315
408
  streamEvents(chatId: string, opts?: {
316
409
  afterId?: number;
317
410
  signal?: AbortSignal;
@@ -327,7 +420,67 @@ declare class CoderChatClient {
327
420
  resolveModelConfigId(hint: string, signal?: AbortSignal): Promise<string | undefined>;
328
421
  }
329
422
 
423
+ /**
424
+ * Adapter that writes bytes onto a Coder workspace's filesystem, injected into
425
+ * a {@link CoderAgent} to enable {@link CoderAgent.uploadToWorkspace}.
426
+ *
427
+ * This is the "operate on this material" path — for archives, datasets,
428
+ * binaries, oversized files, or anything outside the narrow chat-attachment
429
+ * allowlist. It is intentionally NOT bundled into the agent package: the agent
430
+ * core stays dependency-free, and whoever already holds a workspace connection
431
+ * (e.g. a `@coder/ai-sdk-eve-sandbox` session) supplies a few-line adapter.
432
+ *
433
+ * The store does one thing — write bytes to a path. It deliberately does not
434
+ * unpack archives or mutate the working tree: instruct the agent to `unzip`/
435
+ * `tar -x`, or do it over your own connection.
436
+ *
437
+ * @example
438
+ * ```ts
439
+ * const store: WorkspaceFileStore = {
440
+ * workspaceId: session.workspaceId,
441
+ * write: async ({ content, path, signal }) => {
442
+ * await session.writeFile({ path, content: toStream(content), abortSignal: signal });
443
+ * return { path };
444
+ * },
445
+ * };
446
+ * ```
447
+ */
448
+ interface WorkspaceFileStore {
449
+ /** The workspace these writes land in. Should match the agent's bound `workspaceId`. */
450
+ readonly workspaceId: string;
451
+ /**
452
+ * Write `content` to `path` inside the workspace and return the absolute path
453
+ * it landed at (the adapter may resolve a relative path against a working dir).
454
+ */
455
+ write(file: {
456
+ content: FileContent;
457
+ path: string;
458
+ signal?: AbortSignal;
459
+ }): Promise<{
460
+ path: string;
461
+ }>;
462
+ }
463
+ /** Where an uploaded file ended up after {@link CoderAgent.uploadToWorkspace}. */
464
+ interface WorkspacePlacement {
465
+ workspaceId: string;
466
+ /** Absolute path of the file inside the workspace. */
467
+ path: string;
468
+ }
469
+
330
470
  type InnerAgent<TOOLS extends ToolSet> = ToolLoopAgent<never, TOOLS, never>;
471
+ /** A handle to a file uploaded as a chat attachment (see {@link CoderAgent.attach}). */
472
+ interface ChatAttachment extends UploadedChatFile {
473
+ /**
474
+ * A native AI SDK `file` part that references this upload by id. Drop it into
475
+ * a user message's `content` to reuse the file across turns without
476
+ * re-uploading — the agent recognizes the id and skips the upload.
477
+ *
478
+ * The reference travels in `providerOptions.coder.fileId`; if you persist the
479
+ * part through a store that strips `providerOptions`, the reference is lost
480
+ * (re-`attach()` instead of persisting the handle).
481
+ */
482
+ toFilePart(): FilePart;
483
+ }
331
484
  interface CoderAgentSettings<TOOLS extends ToolSet = {}> {
332
485
  /** A pre-built {@link CoderChatClient}. Mutually exclusive with baseUrl/token. */
333
486
  client?: CoderChatClient;
@@ -343,6 +496,12 @@ interface CoderAgentSettings<TOOLS extends ToolSet = {}> {
343
496
  model?: string;
344
497
  /** Bind the chat to a Coder workspace (enables workspace-scoped tools). */
345
498
  workspaceId?: string;
499
+ /**
500
+ * Adapter for writing files onto a workspace filesystem, enabling
501
+ * {@link CoderAgent.uploadToWorkspace}. Supply one backed by a workspace
502
+ * connection (e.g. a `@coder/ai-sdk-eve-sandbox` session).
503
+ */
504
+ workspaceFiles?: WorkspaceFileStore;
346
505
  /** chatd-side MCP servers to enable for this chat. */
347
506
  mcpServerIds?: string[];
348
507
  /** chatd plan mode. */
@@ -363,6 +522,18 @@ interface CoderAgentSettings<TOOLS extends ToolSet = {}> {
363
522
  * SDK retries could duplicate a turn. Override with care.
364
523
  */
365
524
  maxRetries?: number;
525
+ /**
526
+ * Per-segment time budget in milliseconds, applied to each model round-trip
527
+ * (chat creation / message / tool-result submission plus the server run until
528
+ * it settles). If a segment runs longer (e.g. the server is wedged or a
529
+ * workspace can't be scheduled), the run is interrupted server-side and the
530
+ * call rejects with a retryable {@link CoderChatError} (`kind: "timeout"`)
531
+ * instead of hanging. A multi-step `generate()` driving client tools makes
532
+ * several segments, so this bounds each one — to cap total wall-clock for the
533
+ * whole call, pass `abortSignal: AbortSignal.timeout(ms)`. Unset or
534
+ * non-positive means no limit.
535
+ */
536
+ requestTimeoutMs?: number;
366
537
  }
367
538
  /**
368
539
  * An AI SDK-compliant agent backed by a remote Coder `chatd` agent runtime.
@@ -388,12 +559,62 @@ declare class CoderAgent<TOOLS extends ToolSet = {}> implements Agent<never, TOO
388
559
  get chatId(): string | undefined;
389
560
  generate(options: Parameters<InnerAgent<TOOLS>["generate"]>[0]): ReturnType<InnerAgent<TOOLS>["generate"]>;
390
561
  stream(options: Parameters<InnerAgent<TOOLS>["stream"]>[0]): ReturnType<InnerAgent<TOOLS>["stream"]>;
562
+ /**
563
+ * List the model configs available on the deployment. Use this to discover
564
+ * valid values for the `model` hint instead of guessing ids — match on
565
+ * `id`, `provider`/`model`, or `display_name`.
566
+ */
567
+ listModels(signal?: AbortSignal): Promise<ChatModelConfig[]>;
391
568
  /** Start a fresh chatd chat on the next turn. */
392
569
  resetSession(): void;
393
570
  /** Interrupt the in-flight generation, if any. */
394
571
  interrupt(): Promise<void>;
395
572
  /** Archive the underlying chat (safe cleanup; hides it from listings). */
396
573
  archive(): Promise<void>;
574
+ /**
575
+ * Clean up the chat when the agent leaves an `await using` scope, so cleanup
576
+ * rides scope exit instead of a separate call you have to remember in a
577
+ * `finally`. Interrupts any in-flight server run, then archives the chat.
578
+ * Best-effort: disposal errors are swallowed so they can't mask the scope's
579
+ * own error.
580
+ *
581
+ * @example
582
+ * ```ts
583
+ * await using agent = new CoderAgent({ ... });
584
+ * const { text } = await agent.generate({ prompt: "…" });
585
+ * // agent.interrupt() + agent.archive() run automatically here.
586
+ * ```
587
+ */
588
+ [Symbol.asyncDispose](): Promise<void>;
589
+ /**
590
+ * Upload a file as a chat attachment — content for the model to *read* (a PDF,
591
+ * image, CSV, …). Returns a handle you can reference from a later turn via
592
+ * {@link ChatAttachment.toFilePart} (no re-upload). Validated against the chat
593
+ * allowlist and 10 MiB cap up front.
594
+ *
595
+ * You usually don't need this: a native AI SDK `file` part dropped into a
596
+ * message's `content` is uploaded transparently. Reach for `attach()` to
597
+ * pre-upload, to reuse one file across turns, or to validate before sending.
598
+ *
599
+ * For large or non-allowlisted files (zips, datasets, binaries) — material for
600
+ * the agent to *operate on* — use {@link CoderAgent.uploadToWorkspace}.
601
+ */
602
+ attach(file: ChatFileInput, signal?: AbortSignal): Promise<ChatAttachment>;
603
+ /**
604
+ * Write a file onto the bound workspace's filesystem for the agent to operate
605
+ * on (extract, build, read with its tools). Requires a {@link WorkspaceFileStore}
606
+ * via the `workspaceFiles` setting. The file is written as-is — no unpacking;
607
+ * instruct the agent to `unzip`/`tar -x`, or extract over your own connection.
608
+ *
609
+ * The chat should be bound to the same workspace (`workspaceId`) so the agent's
610
+ * server-side tools can see the file.
611
+ */
612
+ uploadToWorkspace(file: {
613
+ content: FileContent;
614
+ /** Destination path inside the workspace (the adapter may resolve relatives). */
615
+ path: string;
616
+ signal?: AbortSignal;
617
+ }): Promise<WorkspacePlacement>;
397
618
  }
398
619
 
399
620
  interface CoderLanguageModelConfig {
@@ -408,6 +629,17 @@ interface CoderLanguageModelConfig {
408
629
  planMode?: "" | "plan";
409
630
  /** Resume an existing chat instead of creating a new one. */
410
631
  chatId?: string;
632
+ /**
633
+ * Per-segment time budget in milliseconds, applied to each model round-trip
634
+ * (one `doStream`/`doGenerate` call: chat creation or message/tool-result
635
+ * submission, plus the server-side run until it settles or pauses for a client
636
+ * tool). If exceeded, the run is interrupted server-side and the call rejects
637
+ * with a retryable {@link CoderChatError} (`kind: "timeout"`). A multi-step
638
+ * `generate()` that drives client tools makes several segments, so this bounds
639
+ * each segment, not the whole call — to cap total wall-clock, pass
640
+ * `abortSignal: AbortSignal.timeout(ms)`. Unset or non-positive means no limit.
641
+ */
642
+ requestTimeoutMs?: number;
411
643
  }
412
644
  /**
413
645
  * A {@link LanguageModelV3} that is backed by a remote Coder `chatd` agent
@@ -468,11 +700,39 @@ declare class TurnTranslator {
468
700
  finish(): LanguageModelV3StreamPart[];
469
701
  }
470
702
 
703
+ /** Namespace key for this package's options on AI SDK message-part `providerOptions`. */
704
+ declare const CODER_PROVIDER_OPTIONS = "coder";
705
+ /** Shape of this package's `providerOptions.coder` payload on a file part. */
706
+ interface CoderFileProviderOptions {
707
+ /** A pre-uploaded chat-file id to reuse instead of uploading the part's bytes. */
708
+ fileId?: string;
709
+ }
471
710
  /** Concatenated system messages, mapped to chatd's `system_prompt`. */
472
711
  declare function extractSystemPrompt(prompt: LanguageModelV3Prompt): string | undefined;
712
+ /** The content array of a user message (text and file parts). */
713
+ type UserContent = Extract<LanguageModelV3Message, {
714
+ role: "user";
715
+ }>["content"];
716
+ /**
717
+ * Uploads a file part's bytes and resolves to its chatd file id. Supplied by
718
+ * the language model (which holds the client + organization id); kept abstract
719
+ * here so {@link userContentToInputParts} stays pure and testable.
720
+ */
721
+ type FilePartUploader = (file: {
722
+ data: LanguageModelV3DataContent;
723
+ mediaType: string;
724
+ filename?: string;
725
+ }) => Promise<string>;
726
+ /**
727
+ * Maps a user message's content to chatd input parts. Text passes through;
728
+ * file parts are turned into `file` parts referencing an uploaded file id —
729
+ * either reused from `providerOptions.coder.fileId` (a pre-uploaded handle) or
730
+ * uploaded on the fly via {@link FilePartUploader}.
731
+ */
732
+ declare function userContentToInputParts(content: UserContent, uploadFile: FilePartUploader): Promise<ChatInputPart[]>;
473
733
  type TurnAction = {
474
734
  kind: "new-turn";
475
- content: ChatInputPart[];
735
+ content: UserContent;
476
736
  } | {
477
737
  kind: "resume";
478
738
  toolResults: ToolResult[];
@@ -523,4 +783,4 @@ declare class CoderChatError extends CoderAgentError {
523
783
  constructor(payload: ChatErrorPayload);
524
784
  }
525
785
 
526
- export { type Chat, type ChatBusyBehavior, type ChatClientType, type ChatErrorPayload, type ChatInputPart, type ChatInputPartType, type ChatMessage, type ChatMessagePart, type ChatMessagePartType, type ChatMessageRole, type ChatMessageUsage, type ChatMessagesResponse, type ChatModelConfig, type ChatPlanMode, type ChatQueuedMessage, type ChatStatus, type ChatStreamActionRequired, type ChatStreamEvent, type ChatStreamEventType, type ChatStreamMessagePart, type ChatStreamRetry, type ChatStreamStatus, type ChatStreamToolCall, CoderAgent, CoderAgentError, type CoderAgentSettings, CoderApiError, CoderChatClient, type CoderChatClientOptions, CoderChatError, CoderLanguageModel, type CoderLanguageModelConfig, type CreateChatMessageRequest, type CreateChatMessageResponse, type CreateChatRequest, type DynamicTool, type SubmitToolResultsRequest, TERMINAL_STATUSES, type ToolResult, type TurnAction, TurnTranslator, type UpdateChatRequest, type WebSocketFactory, type WebSocketLike, classifyTurnAction, dynamicToolNames, extractSystemPrompt, streamChatEvents, toolsToDynamicTools };
786
+ export { CHAT_ATTACHMENT_MEDIA_TYPES, CODER_PROVIDER_OPTIONS, type Chat, type ChatAttachment, type ChatAttachmentMediaType, type ChatBusyBehavior, type ChatClientType, type ChatErrorPayload, type ChatFileInput, type ChatInputPart, type ChatInputPartType, type ChatMessage, type ChatMessagePart, type ChatMessagePartType, type ChatMessageRole, type ChatMessageUsage, type ChatMessagesResponse, type ChatModelConfig, type ChatPlanMode, type ChatQueuedMessage, type ChatStatus, type ChatStreamActionRequired, type ChatStreamEvent, type ChatStreamEventType, type ChatStreamMessagePart, type ChatStreamRetry, type ChatStreamStatus, type ChatStreamToolCall, CoderAgent, CoderAgentError, type CoderAgentSettings, CoderApiError, CoderChatClient, type CoderChatClientOptions, CoderChatError, type CoderFileProviderOptions, CoderLanguageModel, type CoderLanguageModelConfig, type CreateChatMessageRequest, type CreateChatMessageResponse, type CreateChatRequest, type DynamicTool, type FileContent, type FilePartUploader, MAX_CHAT_FILE_SIZE_BYTES, type ResolvedFile, type SubmitToolResultsRequest, TERMINAL_STATUSES, type ToolResult, type TurnAction, TurnTranslator, type UpdateChatRequest, type UploadChatFileResponse, type UploadedChatFile, type UserContent, type WebSocketFactory, type WebSocketLike, type WorkspaceFileStore, type WorkspacePlacement, classifyTurnAction, dataContentToFileContent, dynamicToolNames, extractSystemPrompt, resolveFileContent, streamChatEvents, toolsToDynamicTools, userContentToInputParts };