@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/README.md +227 -17
- package/dist/index.d.ts +264 -4
- package/dist/index.js +386 -47
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -21,6 +21,25 @@ context compaction. The Vercel AI SDK runs its loop **client‑side**. This pack
|
|
|
21
21
|
bridges the two, so a Coder agent looks and feels like a native AI SDK agent without
|
|
22
22
|
re‑implementing the loop.
|
|
23
23
|
|
|
24
|
+
## Agent vs. provider — which package?
|
|
25
|
+
|
|
26
|
+
Two packages, two jobs:
|
|
27
|
+
|
|
28
|
+
- **`@coder/ai-sdk-agent` (this package)** — Coder's **server‑side agent**: the
|
|
29
|
+
multi‑step tool loop, built‑in tools, MCP servers, workspace‑scoped file/shell
|
|
30
|
+
tools, sub‑agents, and compaction all run on the deployment. Each `CoderAgent`
|
|
31
|
+
is one server chat ("session") and may provision a workspace. Reach for it when
|
|
32
|
+
you need **server‑side tools, MCP, or a workspace**.
|
|
33
|
+
- **[`@coder/ai-sdk-provider`](../provider)** — **plain model calls** through
|
|
34
|
+
Coder's AI Gateway. A normal AI SDK provider: `generateText`, `streamText`, and
|
|
35
|
+
**`generateObject` for schema‑constrained structured output**. No chat, no
|
|
36
|
+
workspace, natively cancelable. Reach for it when you just need **a model**
|
|
37
|
+
(plan / extract / summarize / classify) with no server‑side tools.
|
|
38
|
+
|
|
39
|
+
Rule of thumb: **need server‑side tools, MCP, or a workspace → Agent; need a model
|
|
40
|
+
→ provider.** They compose — a multi‑step pipeline often uses the provider for its
|
|
41
|
+
pure text/JSON steps and the Agent only for the steps that touch tools.
|
|
42
|
+
|
|
24
43
|
## Install
|
|
25
44
|
|
|
26
45
|
```bash
|
|
@@ -76,6 +95,7 @@ pnpm example:generate # non-streaming generate()
|
|
|
76
95
|
pnpm example:stream # streaming via textStream
|
|
77
96
|
pnpm example:tool # custom (client-executed) tool round-trip
|
|
78
97
|
pnpm example:multi-turn # multi-turn session memory
|
|
98
|
+
pnpm example:file # attach a file to a chat (optional: pass a path)
|
|
79
99
|
```
|
|
80
100
|
|
|
81
101
|
Each example creates a new chat and archives it when done — it never touches workspaces. See
|
|
@@ -93,6 +113,74 @@ AI SDK tool loop — your `execute` runs in your process.
|
|
|
93
113
|
appear in the transcript as `providerExecuted` tool calls/results — you observe them, you
|
|
94
114
|
don't execute them.
|
|
95
115
|
|
|
116
|
+
## Files
|
|
117
|
+
|
|
118
|
+
There are two distinct ways to get a file to the agent, depending on whether the model should
|
|
119
|
+
**read** it or **operate on** it.
|
|
120
|
+
|
|
121
|
+
**Chat attachments** — content for the model to read (a PDF, image, CSV…). Drop a native AI SDK
|
|
122
|
+
`file` part into a message and it's uploaded transparently:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
import { readFile } from "node:fs/promises";
|
|
126
|
+
|
|
127
|
+
await agent.generate({
|
|
128
|
+
messages: [
|
|
129
|
+
{
|
|
130
|
+
role: "user",
|
|
131
|
+
content: [
|
|
132
|
+
{ type: "text", text: "Summarize this report." },
|
|
133
|
+
{
|
|
134
|
+
type: "file",
|
|
135
|
+
data: await readFile("report.pdf"),
|
|
136
|
+
mediaType: "application/pdf",
|
|
137
|
+
filename: "report.pdf",
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Or upload once and reuse across turns with `attach()` — which also accepts a `Blob`/`File` or
|
|
146
|
+
stream (use `fs.openAsBlob` to avoid reading the whole file into memory):
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
const file = await agent.attach({
|
|
150
|
+
content: await openAsBlob("report.pdf"),
|
|
151
|
+
mediaType: "application/pdf",
|
|
152
|
+
});
|
|
153
|
+
await agent.generate({
|
|
154
|
+
messages: [
|
|
155
|
+
{
|
|
156
|
+
role: "user",
|
|
157
|
+
content: [
|
|
158
|
+
{ type: "text", text: "List the risks." },
|
|
159
|
+
file.toFilePart(), // references the upload by id — no re-upload
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Attachments are capped at **10 MiB** and restricted to a narrow media‑type allowlist
|
|
167
|
+
(`application/pdf`, `application/json`, `text/{plain,markdown,csv}`, `image/{png,jpeg,gif,webp}`).
|
|
168
|
+
Oversized or unsupported files throw a clear error up front.
|
|
169
|
+
|
|
170
|
+
**Workspace files** — material for the agent to operate on (a zip of assets, a dataset, a
|
|
171
|
+
binary — anything outside the allowlist or over the cap). Write it onto the workspace filesystem
|
|
172
|
+
and let the agent's tools take over. This needs a `workspaceFiles` adapter (the agent core stays
|
|
173
|
+
dependency‑free; whoever holds a workspace connection supplies a few‑line adapter):
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
const agent = new CoderAgent({ /* … */ workspaceId: ws.id, workspaceFiles });
|
|
177
|
+
const { path } = await agent.uploadToWorkspace({
|
|
178
|
+
content: await openAsBlob("assets.zip"),
|
|
179
|
+
path: "assets.zip",
|
|
180
|
+
});
|
|
181
|
+
// Then ask the agent to `unzip assets.zip` — uploadToWorkspace writes bytes as-is; it does not unpack.
|
|
182
|
+
```
|
|
183
|
+
|
|
96
184
|
## Auth
|
|
97
185
|
|
|
98
186
|
Pass a Coder **API token** or **session token** as `token`; it is sent as the
|
|
@@ -113,30 +201,131 @@ One `CoderAgent` instance maps to one chat ("session") on the Coder server. The
|
|
|
113
201
|
created on the first turn and reused for subsequent `generate()`/`stream()` calls (multi‑turn
|
|
114
202
|
conversation with server‑side history). `agent.chatId` is the current chat id.
|
|
115
203
|
|
|
116
|
-
- `agent.resetSession()` — start a fresh chat on the next turn.
|
|
204
|
+
- `agent.resetSession()` — start a fresh chat on the next turn (reuse one instance for sequential turns; you don't need a new agent per turn).
|
|
117
205
|
- `agent.interrupt()` — interrupt an in‑flight generation.
|
|
118
|
-
- `agent.archive()` — archive the underlying chat (cleanup).
|
|
206
|
+
- `agent.archive()` — archive the underlying chat (cleanup; see [Cleanup](#cleanup)).
|
|
207
|
+
- `agent.listModels()` — list the deployment's model configs, so you don't have to guess the `model` hint.
|
|
119
208
|
- Resume a prior chat: `new CoderAgent({ …, chatId: "…" })`.
|
|
120
209
|
|
|
121
|
-
A single instance is **single‑flight** — don't run concurrent generations against it.
|
|
210
|
+
A single instance is **single‑flight** — don't run concurrent generations against it. For concurrency, use one instance per session (and see [Workspaces & quota](#workspaces--quota)).
|
|
211
|
+
|
|
212
|
+
## Timeouts & cancellation
|
|
213
|
+
|
|
214
|
+
Pass an `abortSignal` to `generate()`/`stream()` to cancel a turn. Aborting
|
|
215
|
+
**interrupts the server‑side run** (not just the local socket), so the chat stops
|
|
216
|
+
generating and releases its resources instead of running on, orphaned. Tearing
|
|
217
|
+
down a `stream()` early (cancelling the stream) interrupts the run too.
|
|
218
|
+
|
|
219
|
+
For a hard ceiling, set `requestTimeoutMs`. If a segment runs longer (e.g. the
|
|
220
|
+
server is wedged, or a workspace can't be scheduled), the run is interrupted and
|
|
221
|
+
the call rejects with a retryable `CoderChatError` (`kind: "timeout"`) instead of
|
|
222
|
+
hanging:
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
const agent = new CoderAgent({ /* … */ requestTimeoutMs: 120_000 });
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
`requestTimeoutMs` bounds **each server segment** — one model round‑trip until it
|
|
229
|
+
settles or pauses for a client tool. A multi‑step `generate()` that drives client
|
|
230
|
+
tools runs several segments, so it bounds each one, not the whole call. To cap the
|
|
231
|
+
**total** wall‑clock of a multi‑step call, pass a deadline as the signal instead:
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
await agent.generate({ prompt: "…", abortSignal: AbortSignal.timeout(120_000) });
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
If the event stream drops before the turn settles, the call rejects with
|
|
238
|
+
`CoderChatError` (`kind: "stream_closed"`, retryable) rather than returning a
|
|
239
|
+
truncated result as if the turn had finished.
|
|
240
|
+
|
|
241
|
+
## Cleanup
|
|
242
|
+
|
|
243
|
+
`archive()` soft‑hides the chat (it stays in listings as `archived: true`; there
|
|
244
|
+
is no hard delete yet). To make cleanup ride scope exit instead of a `finally` you
|
|
245
|
+
have to remember, the agent is an **async disposable**:
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
await using agent = new CoderAgent({
|
|
249
|
+
/* … */
|
|
250
|
+
});
|
|
251
|
+
const { text } = await agent.generate({ prompt: "…" });
|
|
252
|
+
// agent.archive() runs automatically when the scope exits (best‑effort).
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
In a request handler that returns before a fire‑and‑forget `archive()` settles, the
|
|
256
|
+
archive can be abandoned — `await using` (or an awaited `archive()` in `finally`)
|
|
257
|
+
avoids accumulating live chats.
|
|
258
|
+
|
|
259
|
+
## Handling errors
|
|
260
|
+
|
|
261
|
+
All errors extend `CoderAgentError`. Two carry structured detail you can branch on:
|
|
262
|
+
|
|
263
|
+
- **`CoderApiError`** — an HTTP request failed. Fields: `status`, `method`, `path`, `detail`.
|
|
264
|
+
- **`CoderChatError`** — a turn ended in an error, timed out, or lost its stream. Fields: `kind`, `retryable`, `statusCode`, `provider`.
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
import { CoderApiError, CoderChatError } from "@coder/ai-sdk-agent";
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
await agent.generate({ prompt: "…" });
|
|
271
|
+
} catch (err) {
|
|
272
|
+
if (err instanceof CoderChatError && err.retryable) {
|
|
273
|
+
// transient (timeout, stream_closed, an upstream 5xx) — back off and retry
|
|
274
|
+
} else if (err instanceof CoderApiError && err.status === 429) {
|
|
275
|
+
// rate limited
|
|
276
|
+
} else {
|
|
277
|
+
throw err;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
`maxRetries` defaults to `0`: this agent owns server‑side chat state, so an
|
|
283
|
+
SDK‑level retry could duplicate a turn. Prefer catching `retryable` errors and
|
|
284
|
+
retrying the whole step deliberately.
|
|
285
|
+
|
|
286
|
+
## Structured output
|
|
287
|
+
|
|
288
|
+
`CoderAgent` does **not** constrain output to a JSON schema — chatd has no
|
|
289
|
+
server‑side `response_format`, so a `responseFormat` / `experimental_output`
|
|
290
|
+
request emits a warning and is best‑effort at most. For reliable
|
|
291
|
+
schema‑constrained output, use **[`@coder/ai-sdk-provider`](../provider)** with
|
|
292
|
+
`generateObject` / `Output.object` (requires AI Gateway enabled on the
|
|
293
|
+
deployment). Use the Agent for the steps that need server‑side tools; use the
|
|
294
|
+
provider for pure text‑in / JSON‑out steps.
|
|
295
|
+
|
|
296
|
+
## Workspaces & quota
|
|
297
|
+
|
|
298
|
+
A `CoderAgent` is one server‑side chat, and — depending on its configuration and
|
|
299
|
+
the deployment — a chat may provision a **Coder workspace** to run its tools. A
|
|
300
|
+
deployment caps how many workspaces an account may run at once, so **N agents
|
|
301
|
+
running concurrently can need N free workspace slots.** Past the cap, a turn can
|
|
302
|
+
sit unscheduled and never settle. This is the most important operational fact when
|
|
303
|
+
running many agents at once:
|
|
304
|
+
|
|
305
|
+
- Keep your own concurrency below the deployment's workspace limit.
|
|
306
|
+
- Set `requestTimeoutMs` so an unschedulable turn fails loudly instead of hanging.
|
|
307
|
+
- `archive()` / `await using` each agent so finished chats stop holding resources.
|
|
308
|
+
- For steps that don't need server‑side tools, prefer the provider — it never touches a workspace.
|
|
122
309
|
|
|
123
310
|
## Configuration
|
|
124
311
|
|
|
125
312
|
`CoderAgentSettings`:
|
|
126
313
|
|
|
127
|
-
| field | description
|
|
128
|
-
| --------------------------------- |
|
|
129
|
-
| `client` \| (`baseUrl` + `token`) | connection (one or the other)
|
|
130
|
-
| `organizationId` | org UUID that owns the chat (required)
|
|
131
|
-
| `model` | model hint: UUID, `provider:model`, model id, or display‑name substring
|
|
132
|
-
| `instructions` | system prompt
|
|
133
|
-
| `tools` | AI SDK `ToolSet` (client‑executed)
|
|
134
|
-
| `workspaceId` | bind the chat to a Coder workspace (enables workspace‑scoped tools)
|
|
135
|
-
| `
|
|
136
|
-
| `
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
139
|
-
| `
|
|
314
|
+
| field | description |
|
|
315
|
+
| --------------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
316
|
+
| `client` \| (`baseUrl` + `token`) | connection (one or the other) |
|
|
317
|
+
| `organizationId` | org UUID that owns the chat (required) |
|
|
318
|
+
| `model` | model hint: UUID, `provider:model`, model id, or display‑name substring |
|
|
319
|
+
| `instructions` | system prompt |
|
|
320
|
+
| `tools` | AI SDK `ToolSet` (client‑executed) |
|
|
321
|
+
| `workspaceId` | bind the chat to a Coder workspace (enables workspace‑scoped tools) |
|
|
322
|
+
| `workspaceFiles` | adapter enabling `uploadToWorkspace()` (write files to the workspace FS) |
|
|
323
|
+
| `mcpServerIds` | server‑side MCP servers to enable |
|
|
324
|
+
| `planMode` | enable plan mode (`"plan"`) |
|
|
325
|
+
| `stopWhen` | AI SDK stop condition(s); default `stepCountIs(64)` |
|
|
326
|
+
| `maxRetries` | default `0` — SDK retries can duplicate server‑side turns; override with care |
|
|
327
|
+
| `requestTimeoutMs` | per‑turn time budget (ms); interrupts the run and rejects (`kind: "timeout"`) instead of hanging |
|
|
328
|
+
| `chatId` | resume an existing chat |
|
|
140
329
|
|
|
141
330
|
## How it works
|
|
142
331
|
|
|
@@ -154,6 +343,28 @@ CoderAgent (implements ai.Agent)
|
|
|
154
343
|
- Streaming text is emitted from `message_part` deltas; fast turns that only produce a full
|
|
155
344
|
`message` snapshot are diffed against an emitted‑length cursor — so neither double‑counts.
|
|
156
345
|
|
|
346
|
+
## Durable workflows (Vercel Workflow, step functions, …)
|
|
347
|
+
|
|
348
|
+
`CoderAgent` talks to Coder over its own REST + WebSocket client, so it can't ride
|
|
349
|
+
a `fetch`‑shim durability layer — each turn must run **inside** a durable step. A
|
|
350
|
+
few rules keep it well‑behaved across replays:
|
|
351
|
+
|
|
352
|
+
- **One turn per step.** Create the agent, run a single `generate()` (not
|
|
353
|
+
`stream()`, so the checkpointed value is the finished result), return.
|
|
354
|
+
- **Don't persist the instance across steps.** Persist `agent.chatId` (a string)
|
|
355
|
+
and resume with `new CoderAgent({ …, chatId })` in the next step. Never persist
|
|
356
|
+
or log the token — read it from the environment in each step.
|
|
357
|
+
- **Clean up in the step.** `await using` the agent (or `await agent.archive()` in
|
|
358
|
+
a `finally`) so a step that returns early doesn't abandon the chat.
|
|
359
|
+
- **Bound each step.** Set `requestTimeoutMs` so a wedged turn fails the step (and
|
|
360
|
+
lets the workflow retry) instead of hanging the whole run.
|
|
361
|
+
- **Mind concurrency vs. workspaces.** Keep fan‑out width under the deployment's
|
|
362
|
+
workspace cap — see [Workspaces & quota](#workspaces--quota).
|
|
363
|
+
- **Use the provider for pure steps.** Steps that don't need server‑side tools
|
|
364
|
+
(plan / extract / synthesize) are cheaper and natively structured through
|
|
365
|
+
[`@coder/ai-sdk-provider`](../provider) + `generateObject` — no chat, no
|
|
366
|
+
workspace, no cleanup.
|
|
367
|
+
|
|
157
368
|
## Testing
|
|
158
369
|
|
|
159
370
|
```bash
|
|
@@ -178,7 +389,6 @@ The e2e suite creates **new chats only** (no workspaces) and archives them after
|
|
|
178
389
|
## Limitations
|
|
179
390
|
|
|
180
391
|
- The Coder chat API is experimental (`/api/experimental/chats`); wire types may change.
|
|
181
|
-
- File/image **inputs** are not yet forwarded to the server (text prompts only).
|
|
182
392
|
- Designed for Node (WebSocket via `ws`); a browser build can inject a `webSocketFactory`.
|
|
183
393
|
- A v7 `@ai-sdk/harness` adapter (the conceptually exact fit) is a future direction once that
|
|
184
394
|
experimental API stabilizes.
|
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:
|
|
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 };
|