@coder/ai-sdk-agent 0.2.0 → 0.3.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.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { FilePart, ToolSet, Agent, SystemModelMessage, ToolChoice, StopCondition, ToolLoopAgent } from 'ai';
1
+ import { FilePart, ToolSet, Agent, SystemModelMessage, ToolChoice, StopCondition, ToolLoopAgent, UIMessage } from 'ai';
2
2
  import { LanguageModelV3DataContent, LanguageModelV3, LanguageModelV3CallOptions, LanguageModelV3StreamResult, LanguageModelV3GenerateResult, LanguageModelV3StreamPart, LanguageModelV3Message, LanguageModelV3Prompt } from '@ai-sdk/provider';
3
3
 
4
4
  /**
@@ -175,6 +175,13 @@ interface ChatMessageUsage {
175
175
  cache_creation_tokens?: number;
176
176
  cache_read_tokens?: number;
177
177
  context_limit?: number;
178
+ /**
179
+ * Cost in micro-USD, when the server prices usage. Optional: servers that
180
+ * don't price usage (or predate cost reporting) omit it.
181
+ */
182
+ total_cost_micros?: number;
183
+ /** Model runtime in milliseconds, when the server reports it. */
184
+ total_runtime_ms?: number;
178
185
  }
179
186
  interface ChatMessage {
180
187
  id: number;
@@ -235,12 +242,18 @@ interface ChatMessagesResponse {
235
242
  interface UploadChatFileResponse {
236
243
  id: string;
237
244
  }
245
+ /**
246
+ * A configured model (`GET /api/experimental/chats/model-configs`). Depending
247
+ * on the server version and how a config was created, `provider`, `model`, and
248
+ * `display_name` may be absent on individual entries, so treat them as
249
+ * optional and guard reads.
250
+ */
238
251
  interface ChatModelConfig {
239
252
  id: string;
240
- provider: string;
253
+ provider?: string;
241
254
  ai_provider_id?: string;
242
- model: string;
243
- display_name: string;
255
+ model?: string;
256
+ display_name?: string;
244
257
  enabled?: boolean;
245
258
  is_default?: boolean;
246
259
  context_limit?: number;
@@ -288,6 +301,19 @@ interface ChatStreamEvent {
288
301
  }
289
302
  /** Terminal statuses: the chat has stopped generating for this turn. */
290
303
  declare const TERMINAL_STATUSES: ReadonlySet<ChatStatus>;
304
+ /** Kinds of chat lifecycle events emitted on the per-user watch socket. */
305
+ type ChatWatchEventKind = "status_change" | "summary_change" | "title_change" | "created" | "deleted" | "diff_status_change" | "action_required" | "context_dirty";
306
+ /**
307
+ * An event from `GET /api/experimental/chats/watch`, which covers every chat
308
+ * visible to the authenticated user (not a single chat). Mirrors
309
+ * `codersdk.ChatWatchEvent`.
310
+ */
311
+ interface ChatWatchEvent {
312
+ kind: ChatWatchEventKind;
313
+ chat: Chat;
314
+ /** Pending client-executed tool calls; sent with `action_required` events. */
315
+ tool_calls?: ChatStreamToolCall[];
316
+ }
291
317
 
292
318
  /**
293
319
  * A minimal WebSocket factory abstraction so the stream reader can run on
@@ -339,6 +365,27 @@ interface StreamChatEventsOptions {
339
365
  * — that triggers generator cleanup, which closes the socket.
340
366
  */
341
367
  declare function streamChatEvents(options: StreamChatEventsOptions): AsyncGenerator<ChatStreamEvent, void, void>;
368
+ interface WatchChatEventsOptions {
369
+ baseUrl: string;
370
+ token: string;
371
+ signal?: AbortSignal;
372
+ webSocketFactory?: WebSocketFactory;
373
+ }
374
+ /**
375
+ * Opens the chatd `/watch` WebSocket — lifecycle events for every chat visible
376
+ * to the authenticated user — and yields decoded {@link ChatWatchEvent}s as an
377
+ * async iterable.
378
+ *
379
+ * Unlike the per-chat `/stream`, this is a long-lived subscription, so dropped
380
+ * connections are redialed automatically with exponential backoff (1s doubling
381
+ * to a 30s cap, reset once an event arrives). It ends in exactly three ways:
382
+ * `signal` aborting, the consumer ending iteration (`break` from `for await`,
383
+ * `iterator.return()`) — both tear the socket down promptly even while a read
384
+ * is pending — or the server rejecting the upgrade with a 4xx — bad/expired
385
+ * token, or an older Coder server without the endpoint (404) — which throws a
386
+ * terminal {@link CoderApiError} instead of retrying forever.
387
+ */
388
+ declare function watchChatEvents(options: WatchChatEventsOptions): AsyncGenerator<ChatWatchEvent, void, void>;
342
389
 
343
390
  /** A file to upload as a chat attachment. */
344
391
  interface ChatFileInput {
@@ -386,7 +433,23 @@ declare class CoderChatClient {
386
433
  limit?: number;
387
434
  }, signal?: AbortSignal): Promise<ChatMessagesResponse>;
388
435
  submitToolResults(chatId: string, req: SubmitToolResultsRequest, signal?: AbortSignal): Promise<void>;
389
- interruptChat(chatId: string, signal?: AbortSignal): Promise<Chat>;
436
+ /**
437
+ * Interrupt the chat's in-flight run (`POST /interrupt`). The server flips
438
+ * an active run to `interrupting` and responds immediately with the updated
439
+ * chat — it does not wait for the run to actually stop.
440
+ *
441
+ * Pass `{ wait: true }` to send `?wait=true`, asking the server to hold the
442
+ * response until the run has stopped. Coder servers without that param
443
+ * ignore the unknown query harmlessly and still return immediately, so
444
+ * callers should be prepared to confirm completion via the event stream.
445
+ *
446
+ * The second parameter also accepts a bare `AbortSignal` (the historical
447
+ * signature).
448
+ */
449
+ interruptChat(chatId: string, optsOrSignal?: AbortSignal | {
450
+ wait?: boolean;
451
+ signal?: AbortSignal;
452
+ }): Promise<Chat>;
390
453
  updateChat(chatId: string, req: UpdateChatRequest, signal?: AbortSignal): Promise<void>;
391
454
  /** Convenience: archive a chat (soft-hide; safe for cleanup). */
392
455
  archiveChat(chatId: string, signal?: AbortSignal): Promise<void>;
@@ -409,6 +472,17 @@ declare class CoderChatClient {
409
472
  afterId?: number;
410
473
  signal?: AbortSignal;
411
474
  }): AsyncGenerator<ChatStreamEvent, void, void>;
475
+ /**
476
+ * Watch lifecycle events (status/title changes, creation, deletion, …) for
477
+ * every chat visible to the authenticated user via the `/chats/watch`
478
+ * WebSocket. Dropped connections are redialed with exponential backoff; the
479
+ * iteration ends only when `opts.signal` aborts, or with a terminal
480
+ * {@link CoderApiError} when the server rejects the upgrade with a 4xx
481
+ * (bad/expired token, or an older Coder server without the endpoint → 404).
482
+ */
483
+ watchChats(opts?: {
484
+ signal?: AbortSignal;
485
+ }): AsyncGenerator<ChatWatchEvent, void, void>;
412
486
  /**
413
487
  * Resolves a user-friendly model hint to a model-config UUID.
414
488
  *
@@ -416,10 +490,24 @@ declare class CoderChatClient {
416
490
  * (e.g. `anthropic:claude-haiku-4-5-20251001`), a bare model id, or a
417
491
  * display-name substring (case-insensitive). Returns `undefined` if no
418
492
  * match is found, in which case the caller should let chatd pick the default.
493
+ *
494
+ * Tolerates partial payloads from older/newer servers: a malformed listing
495
+ * (empty body, non-array JSON, null entries) resolves as no match, and
496
+ * entries missing `provider`, `model`, or `display_name` are matched on the
497
+ * fields they do carry — an entry without `provider` still matches by its
498
+ * model id, one without `model` only by display name.
419
499
  */
420
500
  resolveModelConfigId(hint: string, signal?: AbortSignal): Promise<string | undefined>;
421
501
  }
422
502
 
503
+ /** Share levels a port can be in; `owner` is the implicit default when no share exists. */
504
+ type PortShareLevel = "owner" | "authenticated" | "organization" | "public";
505
+ /**
506
+ * Share levels the upsert endpoint accepts. `owner` is rejected server-side —
507
+ * reverting to owner-only is done by deleting the share, not setting it.
508
+ */
509
+ type PreviewShareLevel = Exclude<PortShareLevel, "owner">;
510
+
423
511
  /**
424
512
  * Adapter that writes bytes onto a Coder workspace's filesystem, injected into
425
513
  * a {@link CoderAgent} to enable {@link CoderAgent.uploadToWorkspace}.
@@ -482,7 +570,13 @@ interface ChatAttachment extends UploadedChatFile {
482
570
  toFilePart(): FilePart;
483
571
  }
484
572
  interface CoderAgentSettings<TOOLS extends ToolSet = {}> {
485
- /** A pre-built {@link CoderChatClient}. Mutually exclusive with baseUrl/token. */
573
+ /**
574
+ * A pre-built {@link CoderChatClient}, used instead of constructing one from
575
+ * `baseUrl`/`token`. The preview helpers ({@link CoderAgent.getPreview},
576
+ * {@link CoderAgent.sharePreview}) call non-chat endpoints and therefore
577
+ * still need credentials — pass `baseUrl` + `token` alongside `client` to
578
+ * enable them.
579
+ */
486
580
  client?: CoderChatClient;
487
581
  /** Coder deployment base URL, e.g. `https://dev.coder.com`. */
488
582
  baseUrl?: string;
@@ -534,6 +628,56 @@ interface CoderAgentSettings<TOOLS extends ToolSet = {}> {
534
628
  * non-positive means no limit.
535
629
  */
536
630
  requestTimeoutMs?: number;
631
+ /**
632
+ * Overall deadline in milliseconds for bounded cleanup: how long
633
+ * {@link CoderAgent.archive} keeps retrying while a freshly interrupted chat
634
+ * settles server-side, and how long `[Symbol.asyncDispose]` may take in
635
+ * total. Default 15 000. Primarily a test/tuning knob.
636
+ */
637
+ settleDeadlineMs?: number;
638
+ /**
639
+ * Pause in milliseconds between {@link CoderAgent.archive} retries while the
640
+ * chat settles. Default 1000. Primarily a test/tuning knob.
641
+ */
642
+ settleRetryDelayMs?: number;
643
+ }
644
+ /** Options for {@link CoderAgent.getPreview}. */
645
+ interface WorkspacePreviewOptions {
646
+ /** Workspace port the app listens on (4–5 digit ports parse most reliably). */
647
+ port: number;
648
+ /**
649
+ * Agent that serves the port. Optional when the workspace has exactly one
650
+ * agent; required when it has several (the error lists the candidates).
651
+ */
652
+ agentName?: string;
653
+ /**
654
+ * Protocol the app speaks *inside the workspace* on that port — `https` adds
655
+ * the `s` suffix (`3000s--…`) so the proxy connects over TLS. This is not
656
+ * the browser scheme, which follows the deployment's access URL.
657
+ * Default `"http"`.
658
+ */
659
+ protocol?: "http" | "https";
660
+ signal?: AbortSignal;
661
+ }
662
+ /** A resolved workspace preview URL (see {@link CoderAgent.getPreview}). */
663
+ interface WorkspacePreview {
664
+ /** Browser-openable subdomain app URL, e.g. `https://3000--main--dev--alice.apps.example.com`. */
665
+ url: string;
666
+ }
667
+ /** Options for {@link CoderAgent.sharePreview}. */
668
+ interface SharePreviewOptions extends WorkspacePreviewOptions {
669
+ /**
670
+ * Who may open the preview: `authenticated` (any logged-in user; the
671
+ * default), `organization` (members of the workspace's organization;
672
+ * requires a newer Coder server), or `public` (no auth at all — mind what
673
+ * the port serves).
674
+ */
675
+ shareLevel?: PreviewShareLevel;
676
+ }
677
+ /** A shared workspace preview (see {@link CoderAgent.sharePreview}). */
678
+ interface SharedWorkspacePreview extends WorkspacePreview {
679
+ /** The share level now in effect for the port. */
680
+ shareLevel: PreviewShareLevel;
537
681
  }
538
682
  /**
539
683
  * An AI SDK-compliant agent backed by a remote Coder `chatd` agent runtime.
@@ -567,16 +711,34 @@ declare class CoderAgent<TOOLS extends ToolSet = {}> implements Agent<never, TOO
567
711
  listModels(signal?: AbortSignal): Promise<ChatModelConfig[]>;
568
712
  /** Start a fresh chatd chat on the next turn. */
569
713
  resetSession(): void;
570
- /** Interrupt the in-flight generation, if any. */
571
- interrupt(): Promise<void>;
572
- /** Archive the underlying chat (safe cleanup; hides it from listings). */
573
- archive(): Promise<void>;
714
+ /**
715
+ * Interrupt the in-flight generation, if any. Resolves as soon as the server
716
+ * acknowledges the interrupt — the run keeps winding down asynchronously for
717
+ * a few seconds afterwards (see {@link CoderAgent.archive}).
718
+ */
719
+ interrupt(opts?: {
720
+ signal?: AbortSignal;
721
+ }): Promise<void>;
722
+ /**
723
+ * Archive the underlying chat (safe cleanup; hides it from listings).
724
+ *
725
+ * A freshly interrupted/settled chat can keep winding down server-side for a
726
+ * few seconds, during which the server rejects archiving with a 409. Those
727
+ * 409s are retried (~1s apart, ~15s overall — see `settleDeadlineMs` /
728
+ * `settleRetryDelayMs`) and the last one is rethrown if the chat never
729
+ * settles; any other failure, including a caller abort, rethrows immediately.
730
+ */
731
+ archive(opts?: {
732
+ signal?: AbortSignal;
733
+ }): Promise<void>;
574
734
  /**
575
735
  * Clean up the chat when the agent leaves an `await using` scope, so cleanup
576
736
  * 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.
737
+ * `finally`. Interrupts any in-flight server run, then archives the chat
738
+ * (retrying while the interrupted run settles). Best-effort and bounded
739
+ * (~15s overall): disposal errors are swallowed after the bounded attempts
740
+ * so they can't mask the scope's own error — call {@link CoderAgent.archive}
741
+ * directly when you need guaranteed cleanup.
580
742
  *
581
743
  * @example
582
744
  * ```ts
@@ -615,6 +777,26 @@ declare class CoderAgent<TOOLS extends ToolSet = {}> implements Agent<never, TOO
615
777
  path: string;
616
778
  signal?: AbortSignal;
617
779
  }): Promise<WorkspacePlacement>;
780
+ /**
781
+ * Resolve the browser URL where a port on the bound workspace can be
782
+ * previewed, e.g. `https://3000--main--dev--alice.apps.example.com`.
783
+ * Composed from stable v2 endpoints (workspace lookup + wildcard app host),
784
+ * so it works against old Coder servers. Requires the `workspaceId` setting,
785
+ * `baseUrl`/`token` credentials, and a deployment with a wildcard access URL
786
+ * configured.
787
+ *
788
+ * The URL honors the port's current share level — private to the workspace
789
+ * owner unless shared (see {@link CoderAgent.sharePreview}).
790
+ */
791
+ getPreview(opts: WorkspacePreviewOptions): Promise<WorkspacePreview>;
792
+ /**
793
+ * Share a port on the bound workspace and return its preview URL. Upserts
794
+ * the port's share level — `authenticated` by default — so teammates (or,
795
+ * with `public`, anyone) can open what the agent is serving; re-invoking
796
+ * updates the level in place. On Coder servers that predate port sharing
797
+ * (< v2.9) this fails with a 404 {@link CoderApiError} saying so.
798
+ */
799
+ sharePreview(opts: SharePreviewOptions): Promise<SharedWorkspacePreview>;
618
800
  }
619
801
 
620
802
  interface CoderLanguageModelConfig {
@@ -675,9 +857,12 @@ declare class CoderLanguageModel implements LanguageModelV3 {
675
857
  * Translates one chatd turn's {@link ChatStreamEvent} stream into a sequence of
676
858
  * `LanguageModelV3StreamPart`s for the AI SDK.
677
859
  *
678
- * Two text/reasoning modes, decided per turn from the wire behavior:
860
+ * Two text/reasoning modes, decided per assistant message from the wire
861
+ * behavior:
679
862
  * - **delta mode** — chatd streamed `message_part` deltas; we emit those and
680
- * treat the trailing full `message` snapshot as a no-op for text/reasoning.
863
+ * treat the message's trailing full `message` snapshot as a no-op for
864
+ * text/reasoning. Deltas carry no message id, so "deltas arrived since the
865
+ * last assistant snapshot" is what marks a snapshot as trailing.
681
866
  * - **snapshot mode** — fast turns where only full `message` snapshots arrive;
682
867
  * we diff each snapshot's full text against what we've already emitted.
683
868
  * Both paths track an emitted-length cursor, so neither double-counts.
@@ -700,6 +885,54 @@ declare class TurnTranslator {
700
885
  finish(): LanguageModelV3StreamPart[];
701
886
  }
702
887
 
888
+ /** Options for {@link chatMessagesToUIMessages}. */
889
+ interface ChatMessagesToUIMessagesOptions {
890
+ /**
891
+ * Resolves a persisted `file` part to a URL for its `FileUIPart`. Persisted
892
+ * file parts carry only a `file_id` (no bytes, and usually no `url`), so
893
+ * without a URL there is nothing for the UI to render and the part is
894
+ * skipped. Download the bytes with `CoderChatClient.getChatFile(fileId)`
895
+ * and return a data:/object/proxy URL here to keep attachments visible.
896
+ * Returning `undefined` falls back to the part's own `url`, if any.
897
+ */
898
+ fileUrl?: (part: ChatMessagePart) => string | undefined;
899
+ }
900
+ /**
901
+ * Converts persisted chat history (`CoderChatClient.getMessages`) into AI SDK
902
+ * v6 `UIMessage`s, e.g. to rehydrate `useChat({ messages })` for an existing
903
+ * chat. Pure and side-effect free.
904
+ *
905
+ * The mapping mirrors what a live-streamed transcript of the same turn looks
906
+ * like, with one caveat around client-tool typing (below):
907
+ * - One `ChatMessage` becomes one `UIMessage` (ids stringified), sorted
908
+ * chronologically by id regardless of input order — the messages endpoint
909
+ * pages newest-first by default, while `useChat` needs oldest-first. The
910
+ * exception is `role: "tool"` messages: their `tool-result` parts are
911
+ * folded into the originating assistant `dynamic-tool` part and the message
912
+ * itself is dropped.
913
+ * - `text`/`reasoning` parts map to their UI counterparts with
914
+ * `state: "done"` (history is complete, never streaming).
915
+ * - Every `tool-call` part becomes a `dynamic-tool` part, in state
916
+ * `"output-available"` once a result exists (`"output-error"` when it is
917
+ * marked `is_error`), or `"input-available"` while no result has been
918
+ * persisted yet. For server-executed tools this matches the live stream
919
+ * (they stream with `dynamic: true`); client (`ToolSet`) tools stream live
920
+ * as statically-typed `tool-{name}` parts, but persisted history does not
921
+ * record which names were client tools, so they rehydrate as `dynamic-tool`
922
+ * too. Render tools by name — ai's `isToolOrDynamicToolUIPart` +
923
+ * `getToolOrDynamicToolName` — rather than exact `part.type` matches and
924
+ * both shapes look identical.
925
+ * - `source` parts become `source-url` parts; skipped when the wire part has
926
+ * no `url`.
927
+ * - `file` parts become `file` UI parts only when a URL is available (see
928
+ * {@link ChatMessagesToUIMessagesOptions.fileUrl}): persisted file parts
929
+ * carry only a `file_id`, and `FileUIPart` requires a `url`. Parts without
930
+ * one are skipped.
931
+ * - `file-reference` / `context-file` / `skill` / unknown part kinds have no
932
+ * UI equivalent and are skipped silently (forward compatible).
933
+ */
934
+ declare function chatMessagesToUIMessages(messages: ChatMessage[], opts?: ChatMessagesToUIMessagesOptions): UIMessage[];
935
+
703
936
  /** Namespace key for this package's options on AI SDK message-part `providerOptions`. */
704
937
  declare const CODER_PROVIDER_OPTIONS = "coder";
705
938
  /** Shape of this package's `providerOptions.coder` payload on a file part. */
@@ -783,4 +1016,4 @@ declare class CoderChatError extends CoderAgentError {
783
1016
  constructor(payload: ChatErrorPayload);
784
1017
  }
785
1018
 
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 };
1019
+ 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 ChatMessagesToUIMessagesOptions, type ChatModelConfig, type ChatPlanMode, type ChatQueuedMessage, type ChatStatus, type ChatStreamActionRequired, type ChatStreamEvent, type ChatStreamEventType, type ChatStreamMessagePart, type ChatStreamRetry, type ChatStreamStatus, type ChatStreamToolCall, type ChatWatchEvent, type ChatWatchEventKind, 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 PreviewShareLevel, type ResolvedFile, type SharePreviewOptions, type SharedWorkspacePreview, type SubmitToolResultsRequest, TERMINAL_STATUSES, type ToolResult, type TurnAction, TurnTranslator, type UpdateChatRequest, type UploadChatFileResponse, type UploadedChatFile, type UserContent, type WatchChatEventsOptions, type WebSocketFactory, type WebSocketLike, type WorkspaceFileStore, type WorkspacePlacement, type WorkspacePreview, type WorkspacePreviewOptions, chatMessagesToUIMessages, classifyTurnAction, dataContentToFileContent, dynamicToolNames, extractSystemPrompt, resolveFileContent, streamChatEvents, toolsToDynamicTools, userContentToInputParts, watchChatEvents };