@coder/ai-sdk-agent 0.4.1 → 0.4.3
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 +58 -13
- package/dist/index.d.ts +56 -5
- package/dist/index.js +458 -99
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -311,9 +311,27 @@ tools runs several segments, so it bounds each one, not the whole call. To cap t
|
|
|
311
311
|
await agent.generate({ prompt: "…", abortSignal: AbortSignal.timeout(120_000) });
|
|
312
312
|
```
|
|
313
313
|
|
|
314
|
-
If the event stream drops
|
|
315
|
-
|
|
316
|
-
|
|
314
|
+
If the event stream drops mid‑turn, the agent redials it automatically with
|
|
315
|
+
exponential backoff, replaying the turn's events from its starting cursor and
|
|
316
|
+
deduplicating them on receipt — the server keeps generating during the gap, so
|
|
317
|
+
a transient drop costs nothing and the run is **not** interrupted. Only when the stream cannot be re‑established (several
|
|
318
|
+
consecutive failed attempts, ~15s) is the server run interrupted and the call
|
|
319
|
+
rejected with a `CoderStreamError` — an AI SDK `APICallError`. When the failed
|
|
320
|
+
turn had just created its chat AND had no external effects a replay would
|
|
321
|
+
repeat — no `workspaceId`, no `mcpServerIds`, and no freshly uploaded inline
|
|
322
|
+
attachments (pre‑uploaded `fileId` references are fine) — the dead session is
|
|
323
|
+
discarded and the error is `isRetryable: true`, so `generate()` calls with
|
|
324
|
+
`maxRetries` set retry the whole turn on a fresh chat automatically. Otherwise
|
|
325
|
+
the error is `isRetryable: false` and retrying is the caller's deliberate
|
|
326
|
+
decision: on a chat with prior state (resumed sessions, later turns,
|
|
327
|
+
tool‑result segments) a re‑invocation would resubmit the same prompt as a new
|
|
328
|
+
user turn, workspace/MCP tools may already have executed side effects, and
|
|
329
|
+
inline attachments would upload again. (For `stream()`, a mid‑stream failure
|
|
330
|
+
surfaces on the stream itself, outside the SDK's retry wrapper — handle it in
|
|
331
|
+
your consumption loop.)
|
|
332
|
+
A non‑transient 4xx upgrade rejection (bad/expired token,
|
|
333
|
+
deleted chat) fails fast with a `CoderApiError` instead of retrying; 408/425/429
|
|
334
|
+
consume the redial budget like any other transient failure.
|
|
317
335
|
|
|
318
336
|
## Cleanup
|
|
319
337
|
|
|
@@ -344,10 +362,20 @@ avoids accumulating live chats.
|
|
|
344
362
|
|
|
345
363
|
## Handling errors
|
|
346
364
|
|
|
347
|
-
All errors extend `CoderAgentError
|
|
365
|
+
All errors extend `CoderAgentError`, except `CoderStreamError` (below). Two
|
|
366
|
+
carry structured detail you can branch on:
|
|
348
367
|
|
|
349
368
|
- **`CoderApiError`** — an HTTP request failed. Fields: `status`, `method`, `path`, `detail`.
|
|
350
369
|
- **`CoderChatError`** — a turn ended in an error, timed out, or lost its stream. Fields: `kind`, `retryable`, `statusCode`, `provider`.
|
|
370
|
+
- **`CoderStreamError`** — the event stream dropped and could not be re‑established
|
|
371
|
+
within its redial budget. Extends the AI SDK's `APICallError` (not
|
|
372
|
+
`CoderAgentError`), so `generate()`'s `maxRetries` machinery recognizes it.
|
|
373
|
+
`isRetryable` is `true` only when the failed turn created its chat (the dead
|
|
374
|
+
session is discarded, so a retry starts fresh) and had no external effects to
|
|
375
|
+
repeat (workspace/MCP tooling, fresh attachment uploads); otherwise it is
|
|
376
|
+
`false` — a retry would resubmit the prompt as a new user turn and could
|
|
377
|
+
duplicate those effects. A failure mid‑`stream()` surfaces on the stream,
|
|
378
|
+
outside the retry wrapper. The last transport failure is in `cause`.
|
|
351
379
|
|
|
352
380
|
```ts
|
|
353
381
|
import { CoderApiError, CoderChatError } from "@coder/ai-sdk-agent";
|
|
@@ -371,14 +399,31 @@ retrying the whole step deliberately.
|
|
|
371
399
|
|
|
372
400
|
## Usage & cost
|
|
373
401
|
|
|
374
|
-
Results carry normalized token usage in `usage
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
402
|
+
Results carry normalized token usage in `usage`. A chatd turn runs several
|
|
403
|
+
model steps server‑side (one per server tool round), each reporting its own
|
|
404
|
+
usage — the SDK **sums every step**, and the AI SDK adds up the steps of a
|
|
405
|
+
turn that paused for client tools, so `result.usage` reflects what the whole
|
|
406
|
+
turn actually consumed. `inputTokens` is the full prompt size: Coder
|
|
407
|
+
normalizes the wire `input_tokens` to the _uncached_ count (cache reads/writes
|
|
408
|
+
are separate fields), so the SDK adds them back into the total and exposes the
|
|
409
|
+
split via `inputTokenDetails` (`noCacheTokens`, `cacheReadTokens`,
|
|
410
|
+
`cacheWriteTokens`).
|
|
411
|
+
|
|
412
|
+
The snake_case wire usage lives **per step** at `result.steps[i].usage.raw`
|
|
413
|
+
(the AI SDK does not carry `raw` onto the summed `result.usage`) for fields
|
|
414
|
+
the normalized shape has no slot for (`context_limit`, cost, runtime, and any
|
|
415
|
+
newer wire fields, which pass through newest‑value‑wins). `raw` keeps the wire
|
|
416
|
+
convention (`input_tokens` = uncached only) with counters summed over that
|
|
417
|
+
step's server‑side model steps and `context_limit` from the newest one — so
|
|
418
|
+
don't divide `raw`'s summed counters by `context_limit` to estimate context
|
|
419
|
+
fullness; they are turn consumption, not a prompt‑size snapshot. When the
|
|
420
|
+
server reports them, `total_cost_micros` (micro‑USD) and `total_runtime_ms`
|
|
421
|
+
are mirrored the same way under each step's `providerMetadata.coder`
|
|
422
|
+
(`result.providerMetadata` reflects only the final step — sum
|
|
423
|
+
`result.steps[*].providerMetadata.coder` for whole‑turn cost when client tools
|
|
424
|
+
ran). Both are **absence‑tolerant mirrors**: on servers that don't send them
|
|
425
|
+
(cost is otherwise only on the aggregate cost endpoints,
|
|
426
|
+
`/api/experimental/chats/cost/*`), nothing is emitted.
|
|
382
427
|
|
|
383
428
|
Forward usage to a UI via message metadata:
|
|
384
429
|
|
|
@@ -596,7 +641,7 @@ what's available.
|
|
|
596
641
|
```
|
|
597
642
|
CoderAgent (implements ai.Agent)
|
|
598
643
|
└─ ToolLoopAgent (ai) ← inherits generate()/stream(), loop control
|
|
599
|
-
└─ CoderLanguageModel ← implements @ai-sdk/provider
|
|
644
|
+
└─ CoderLanguageModel ← implements @ai-sdk/provider LanguageModelV4
|
|
600
645
|
└─ CoderChatClient ← REST + WebSocket to /api/experimental/chats
|
|
601
646
|
└─ Coder Agents ← runs the agent loop SERVER-side
|
|
602
647
|
```
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FilePart, ToolSet, Agent, SystemModelMessage, ToolLoopAgentSettings, ToolChoice, StopCondition, ToolLoopAgent, UIMessage } from 'ai';
|
|
2
|
-
import { SharedV4FileData, LanguageModelV4, LanguageModelV4CallOptions, LanguageModelV4StreamResult, LanguageModelV4GenerateResult, LanguageModelV4StreamPart, LanguageModelV4Message, LanguageModelV4Prompt } from '@ai-sdk/provider';
|
|
2
|
+
import { SharedV4FileData, LanguageModelV4, LanguageModelV4CallOptions, LanguageModelV4StreamResult, LanguageModelV4GenerateResult, LanguageModelV4StreamPart, LanguageModelV4Message, LanguageModelV4Prompt, APICallError } from '@ai-sdk/provider';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Bytes to upload, in any convenient shape.
|
|
@@ -365,6 +365,29 @@ interface StreamChatEventsOptions {
|
|
|
365
365
|
* The chat stream is a live subscription that stays open after a turn settles,
|
|
366
366
|
* so callers should `break` out of the loop once they observe a terminal status
|
|
367
367
|
* — that triggers generator cleanup, which closes the socket.
|
|
368
|
+
*
|
|
369
|
+
* Dropped connections are redialed with exponential backoff (like
|
|
370
|
+
* {@link watchChatEvents}), replaying from the turn's ORIGINAL `after_id`. The
|
|
371
|
+
* cursor is deliberately NOT advanced past messages already yielded: chatd's
|
|
372
|
+
* initial sync filters out ids at or below `after_id`, so an advanced cursor
|
|
373
|
+
* would silently lose same-id REVISION snapshots (updated content, tool
|
|
374
|
+
* results, usage) committed while the connection was down. Consumers must
|
|
375
|
+
* dedupe repeated `message` snapshots — the wire contract demands that anyway,
|
|
376
|
+
* since chatd re-sends full snapshots on revision bumps even without a
|
|
377
|
+
* reconnect (TurnTranslator does). On reconnect chatd also replays the
|
|
378
|
+
* in-progress generation attempt's `message_part` deltas from `seq` 1, so
|
|
379
|
+
* parts of an episode
|
|
380
|
+
* (`history_version`, `generation_attempt`) at or below the last yielded `seq`
|
|
381
|
+
* are suppressed here rather than double-yielded. The iteration ends in five
|
|
382
|
+
* ways: `signal` aborting (clean return), a non-transient 4xx upgrade
|
|
383
|
+
* rejection (terminal {@link CoderApiError} — bad/expired token or a deleted
|
|
384
|
+
* chat cannot succeed on retry, while 408/425/429 consume the redial budget),
|
|
385
|
+
* an unparseable frame (terminal {@link CoderAgentError} — a redial would
|
|
386
|
+
* replay the same frame forever), a drop while deltas WITHOUT episode
|
|
387
|
+
* coordinates are outstanding (older chatd; a {@link CoderStreamError},
|
|
388
|
+
* because a delta replay could not be deduped and would double-emit — a NEW
|
|
389
|
+
* committed `message` finalizes them into dedupable snapshots and re-enables
|
|
390
|
+
* redial), or the redial budget running out (also a {@link CoderStreamError}).
|
|
368
391
|
*/
|
|
369
392
|
declare function streamChatEvents(options: StreamChatEventsOptions): AsyncGenerator<ChatStreamEvent, void, void>;
|
|
370
393
|
interface WatchChatEventsOptions {
|
|
@@ -518,7 +541,9 @@ type PreviewShareLevel = Exclude<PortShareLevel, "owner">;
|
|
|
518
541
|
* binaries, oversized files, or anything outside the narrow chat-attachment
|
|
519
542
|
* allowlist. It is intentionally NOT bundled into the agent package: the agent
|
|
520
543
|
* core stays dependency-free, and whoever already holds a workspace connection
|
|
521
|
-
* (e.g. a `@coder/ai-sdk-
|
|
544
|
+
* (e.g. a `CoderWorkspaceSession` from `@coder/ai-sdk-sandbox`) supplies a
|
|
545
|
+
* few-line adapter. Note the session does not report the workspace UUID —
|
|
546
|
+
* retain it from `ensureCoderWorkspace(...).id` (same package).
|
|
522
547
|
*
|
|
523
548
|
* The store does one thing — write bytes to a path. It deliberately does not
|
|
524
549
|
* unpack archives or mutate the working tree: instruct the agent to `unzip`/
|
|
@@ -526,8 +551,10 @@ type PreviewShareLevel = Exclude<PortShareLevel, "owner">;
|
|
|
526
551
|
*
|
|
527
552
|
* @example
|
|
528
553
|
* ```ts
|
|
554
|
+
* // UUID via `ensureCoderWorkspace`; bytes over a `CoderWorkspaceSession`.
|
|
555
|
+
* const ws = await ensureCoderWorkspace({ workspace: "my-ws" });
|
|
529
556
|
* const store: WorkspaceFileStore = {
|
|
530
|
-
* workspaceId:
|
|
557
|
+
* workspaceId: ws.id!, // workspace UUID (reported by the CLI/native transports)
|
|
531
558
|
* write: async ({ content, path, signal }) => {
|
|
532
559
|
* await session.writeFile({ path, content: toStream(content), abortSignal: signal });
|
|
533
560
|
* return { path };
|
|
@@ -595,7 +622,7 @@ interface CoderAgentSettings<TOOLS extends ToolSet = {}> {
|
|
|
595
622
|
/**
|
|
596
623
|
* Adapter for writing files onto a workspace filesystem, enabling
|
|
597
624
|
* {@link CoderAgent.uploadToWorkspace}. Supply one backed by a workspace
|
|
598
|
-
* connection (e.g. a `@coder/ai-sdk-
|
|
625
|
+
* connection (e.g. a `CoderWorkspaceSession` from `@coder/ai-sdk-sandbox`).
|
|
599
626
|
*/
|
|
600
627
|
workspaceFiles?: WorkspaceFileStore;
|
|
601
628
|
/** chatd-side MCP servers to enable for this chat. */
|
|
@@ -883,6 +910,7 @@ declare class TurnTranslator {
|
|
|
883
910
|
#private;
|
|
884
911
|
constructor(opts: {
|
|
885
912
|
dynamicToolNames: ReadonlySet<string>;
|
|
913
|
+
turnCursor?: number;
|
|
886
914
|
});
|
|
887
915
|
get terminalStatus(): ChatStatus | undefined;
|
|
888
916
|
/** Whether a client (custom) tool call has been emitted this turn. */
|
|
@@ -1014,6 +1042,29 @@ declare class CoderApiError extends CoderAgentError {
|
|
|
1014
1042
|
detail?: string;
|
|
1015
1043
|
});
|
|
1016
1044
|
}
|
|
1045
|
+
/**
|
|
1046
|
+
* The per-chat event stream dropped and could not be re-established within its
|
|
1047
|
+
* redial budget (or could not be safely resumed — see `streamChatEvents` on
|
|
1048
|
+
* servers without delta coordinates). Extends the AI SDK's {@link APICallError} with
|
|
1049
|
+
* `isRetryable: true` because the `ai` package's retry machinery (`maxRetries`)
|
|
1050
|
+
* only honors `APICallError`/`GatewayError`-branded errors — plain `Error`
|
|
1051
|
+
* subclasses are never retried no matter what they claim. Note the retry
|
|
1052
|
+
* wrapper only sees errors that reject the model call itself (`generate()`);
|
|
1053
|
+
* a failure mid-`stream()` surfaces on the stream, past that wrapper. The last
|
|
1054
|
+
* underlying transport failure is preserved as `cause`.
|
|
1055
|
+
*
|
|
1056
|
+
* NOTE: this is deliberately NOT a {@link CoderAgentError} (single
|
|
1057
|
+
* inheritance); match it with `APICallError.isInstance(err)` or `err.name`.
|
|
1058
|
+
*/
|
|
1059
|
+
declare class CoderStreamError extends APICallError {
|
|
1060
|
+
name: string;
|
|
1061
|
+
constructor(args: {
|
|
1062
|
+
message: string;
|
|
1063
|
+
url: string;
|
|
1064
|
+
cause?: unknown;
|
|
1065
|
+
isRetryable?: boolean;
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1017
1068
|
/** A chat generation ended in an error status. */
|
|
1018
1069
|
declare class CoderChatError extends CoderAgentError {
|
|
1019
1070
|
name: string;
|
|
@@ -1024,4 +1075,4 @@ declare class CoderChatError extends CoderAgentError {
|
|
|
1024
1075
|
constructor(payload: ChatErrorPayload);
|
|
1025
1076
|
}
|
|
1026
1077
|
|
|
1027
|
-
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 };
|
|
1078
|
+
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, CoderStreamError, 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 };
|