@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/README.md +304 -17
- package/dist/index.d.ts +264 -4
- package/dist/index.js +400 -50
- package/dist/index.js.map +1 -1
- package/package.json +4 -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,8 @@ 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)
|
|
99
|
+
pnpm example:structured # typed structured output via the structured_output tool
|
|
79
100
|
```
|
|
80
101
|
|
|
81
102
|
Each example creates a new chat and archives it when done — it never touches workspaces. See
|
|
@@ -93,6 +114,74 @@ AI SDK tool loop — your `execute` runs in your process.
|
|
|
93
114
|
appear in the transcript as `providerExecuted` tool calls/results — you observe them, you
|
|
94
115
|
don't execute them.
|
|
95
116
|
|
|
117
|
+
## Files
|
|
118
|
+
|
|
119
|
+
There are two distinct ways to get a file to the agent, depending on whether the model should
|
|
120
|
+
**read** it or **operate on** it.
|
|
121
|
+
|
|
122
|
+
**Chat attachments** — content for the model to read (a PDF, image, CSV…). Drop a native AI SDK
|
|
123
|
+
`file` part into a message and it's uploaded transparently:
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
import { readFile } from "node:fs/promises";
|
|
127
|
+
|
|
128
|
+
await agent.generate({
|
|
129
|
+
messages: [
|
|
130
|
+
{
|
|
131
|
+
role: "user",
|
|
132
|
+
content: [
|
|
133
|
+
{ type: "text", text: "Summarize this report." },
|
|
134
|
+
{
|
|
135
|
+
type: "file",
|
|
136
|
+
data: await readFile("report.pdf"),
|
|
137
|
+
mediaType: "application/pdf",
|
|
138
|
+
filename: "report.pdf",
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Or upload once and reuse across turns with `attach()` — which also accepts a `Blob`/`File` or
|
|
147
|
+
stream (use `fs.openAsBlob` to avoid reading the whole file into memory):
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
const file = await agent.attach({
|
|
151
|
+
content: await openAsBlob("report.pdf"),
|
|
152
|
+
mediaType: "application/pdf",
|
|
153
|
+
});
|
|
154
|
+
await agent.generate({
|
|
155
|
+
messages: [
|
|
156
|
+
{
|
|
157
|
+
role: "user",
|
|
158
|
+
content: [
|
|
159
|
+
{ type: "text", text: "List the risks." },
|
|
160
|
+
file.toFilePart(), // references the upload by id — no re-upload
|
|
161
|
+
],
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Attachments are capped at **10 MiB** and restricted to a narrow media‑type allowlist
|
|
168
|
+
(`application/pdf`, `application/json`, `text/{plain,markdown,csv}`, `image/{png,jpeg,gif,webp}`).
|
|
169
|
+
Oversized or unsupported files throw a clear error up front.
|
|
170
|
+
|
|
171
|
+
**Workspace files** — material for the agent to operate on (a zip of assets, a dataset, a
|
|
172
|
+
binary — anything outside the allowlist or over the cap). Write it onto the workspace filesystem
|
|
173
|
+
and let the agent's tools take over. This needs a `workspaceFiles` adapter (the agent core stays
|
|
174
|
+
dependency‑free; whoever holds a workspace connection supplies a few‑line adapter):
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
const agent = new CoderAgent({ /* … */ workspaceId: ws.id, workspaceFiles });
|
|
178
|
+
const { path } = await agent.uploadToWorkspace({
|
|
179
|
+
content: await openAsBlob("assets.zip"),
|
|
180
|
+
path: "assets.zip",
|
|
181
|
+
});
|
|
182
|
+
// Then ask the agent to `unzip assets.zip` — uploadToWorkspace writes bytes as-is; it does not unpack.
|
|
183
|
+
```
|
|
184
|
+
|
|
96
185
|
## Auth
|
|
97
186
|
|
|
98
187
|
Pass a Coder **API token** or **session token** as `token`; it is sent as the
|
|
@@ -113,30 +202,207 @@ One `CoderAgent` instance maps to one chat ("session") on the Coder server. The
|
|
|
113
202
|
created on the first turn and reused for subsequent `generate()`/`stream()` calls (multi‑turn
|
|
114
203
|
conversation with server‑side history). `agent.chatId` is the current chat id.
|
|
115
204
|
|
|
116
|
-
- `agent.resetSession()` — start a fresh chat on the next turn.
|
|
205
|
+
- `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
206
|
- `agent.interrupt()` — interrupt an in‑flight generation.
|
|
118
|
-
- `agent.archive()` — archive the underlying chat (cleanup).
|
|
207
|
+
- `agent.archive()` — archive the underlying chat (cleanup; see [Cleanup](#cleanup)).
|
|
208
|
+
- `agent.listModels()` — list the deployment's model configs, so you don't have to guess the `model` hint.
|
|
119
209
|
- Resume a prior chat: `new CoderAgent({ …, chatId: "…" })`.
|
|
120
210
|
|
|
121
|
-
A single instance is **single‑flight** — don't run concurrent generations against it.
|
|
211
|
+
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)).
|
|
212
|
+
|
|
213
|
+
## Timeouts & cancellation
|
|
214
|
+
|
|
215
|
+
Pass an `abortSignal` to `generate()`/`stream()` to cancel a turn. Aborting
|
|
216
|
+
**interrupts the server‑side run** (not just the local socket), so the chat stops
|
|
217
|
+
generating and releases its resources instead of running on, orphaned. Tearing
|
|
218
|
+
down a `stream()` early (cancelling the stream) interrupts the run too.
|
|
219
|
+
|
|
220
|
+
For a hard ceiling, set `requestTimeoutMs`. If a segment runs longer (e.g. the
|
|
221
|
+
server is wedged, or a workspace can't be scheduled), the run is interrupted and
|
|
222
|
+
the call rejects with a retryable `CoderChatError` (`kind: "timeout"`) instead of
|
|
223
|
+
hanging:
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
const agent = new CoderAgent({ /* … */ requestTimeoutMs: 120_000 });
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
`requestTimeoutMs` bounds **each server segment** — one model round‑trip until it
|
|
230
|
+
settles or pauses for a client tool. A multi‑step `generate()` that drives client
|
|
231
|
+
tools runs several segments, so it bounds each one, not the whole call. To cap the
|
|
232
|
+
**total** wall‑clock of a multi‑step call, pass a deadline as the signal instead:
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
await agent.generate({ prompt: "…", abortSignal: AbortSignal.timeout(120_000) });
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
If the event stream drops before the turn settles, the call rejects with
|
|
239
|
+
`CoderChatError` (`kind: "stream_closed"`, retryable) rather than returning a
|
|
240
|
+
truncated result as if the turn had finished.
|
|
241
|
+
|
|
242
|
+
## Cleanup
|
|
243
|
+
|
|
244
|
+
`archive()` soft‑hides the chat (it stays in listings as `archived: true`; there
|
|
245
|
+
is no hard delete yet). To make cleanup ride scope exit instead of a `finally` you
|
|
246
|
+
have to remember, the agent is an **async disposable**:
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
await using agent = new CoderAgent({
|
|
250
|
+
/* … */
|
|
251
|
+
});
|
|
252
|
+
const { text } = await agent.generate({ prompt: "…" });
|
|
253
|
+
// agent.archive() runs automatically when the scope exits (best‑effort).
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
In a request handler that returns before a fire‑and‑forget `archive()` settles, the
|
|
257
|
+
archive can be abandoned — `await using` (or an awaited `archive()` in `finally`)
|
|
258
|
+
avoids accumulating live chats.
|
|
259
|
+
|
|
260
|
+
## Handling errors
|
|
261
|
+
|
|
262
|
+
All errors extend `CoderAgentError`. Two carry structured detail you can branch on:
|
|
263
|
+
|
|
264
|
+
- **`CoderApiError`** — an HTTP request failed. Fields: `status`, `method`, `path`, `detail`.
|
|
265
|
+
- **`CoderChatError`** — a turn ended in an error, timed out, or lost its stream. Fields: `kind`, `retryable`, `statusCode`, `provider`.
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
import { CoderApiError, CoderChatError } from "@coder/ai-sdk-agent";
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
await agent.generate({ prompt: "…" });
|
|
272
|
+
} catch (err) {
|
|
273
|
+
if (err instanceof CoderChatError && err.retryable) {
|
|
274
|
+
// transient (timeout, stream_closed, an upstream 5xx) — back off and retry
|
|
275
|
+
} else if (err instanceof CoderApiError && err.status === 429) {
|
|
276
|
+
// rate limited
|
|
277
|
+
} else {
|
|
278
|
+
throw err;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
`maxRetries` defaults to `0`: this agent owns server‑side chat state, so an
|
|
284
|
+
SDK‑level retry could duplicate a turn. Prefer catching `retryable` errors and
|
|
285
|
+
retrying the whole step deliberately.
|
|
286
|
+
|
|
287
|
+
## Structured output
|
|
288
|
+
|
|
289
|
+
Coder Agents has no server‑side `response_format`, so `CoderAgent` cannot
|
|
290
|
+
constrain what the model **says** to a JSON schema — a `responseFormat` /
|
|
291
|
+
`experimental_output` request emits a warning and is best‑effort at most. Pick
|
|
292
|
+
by what the step needs:
|
|
293
|
+
|
|
294
|
+
- **Pure text‑in / JSON‑out, no server‑side tools** → use
|
|
295
|
+
**[`@coder/ai-sdk-provider`](../provider)** with `generateObject` /
|
|
296
|
+
`Output.object` (schema‑constrained; requires AI Gateway on the deployment).
|
|
297
|
+
- **The answer must come out of an agent run** (server‑side tools, MCP, a
|
|
298
|
+
workspace) → use the **`structured_output` tool pattern** below. What the
|
|
299
|
+
model _says_ isn't schema‑constrained, but what it passes **into a tool** is
|
|
300
|
+
typed — so have it submit its answer by _calling a tool_ whose `inputSchema`
|
|
301
|
+
is your Zod schema. The answer arrives as the tool call's typed `input`; no
|
|
302
|
+
fishing JSON out of prose.
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
import { stepCountIs, tool } from "ai";
|
|
306
|
+
import { z } from "zod";
|
|
307
|
+
|
|
308
|
+
const Answer = z.object({ severity: z.enum(["critical", "major", "minor"]), summary: z.string() });
|
|
309
|
+
|
|
310
|
+
const agent = new CoderAgent({
|
|
311
|
+
/* … */
|
|
312
|
+
instructions: "… Submit your final answer by calling the structured_output tool exactly once.",
|
|
313
|
+
tools: {
|
|
314
|
+
structured_output: tool({
|
|
315
|
+
description:
|
|
316
|
+
"Submit your final structured answer as JSON. Call this exactly once, when your work is complete.",
|
|
317
|
+
inputSchema: Answer, // your schema IS the tool's input schema
|
|
318
|
+
// Ack instead of stopping the turn: the model finishes naturally and can
|
|
319
|
+
// wind down anything it still has running (dev servers, watchers, …).
|
|
320
|
+
execute: async () =>
|
|
321
|
+
"Output received. Wind down and end your turn. Do not call structured_output again.",
|
|
322
|
+
}),
|
|
323
|
+
},
|
|
324
|
+
stopWhen: stepCountIs(6), // happy path is 2 steps: file + ack, wind down
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const result = await agent.generate({ prompt: "…" });
|
|
328
|
+
// toolCalls only holds the LAST step's calls — scan all steps, last call wins.
|
|
329
|
+
const raw = result.steps
|
|
330
|
+
.flatMap((s) => s.toolCalls)
|
|
331
|
+
.findLast((c) => c.toolName === "structured_output")?.input;
|
|
332
|
+
if (raw === undefined)
|
|
333
|
+
throw new Error("model never called structured_output — nudge once on an idle chat (rule 3)");
|
|
334
|
+
const answer = Answer.parse(raw); // typed: { severity: "critical" | "major" | "minor"; summary: string }
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Rules that keep it robust — each guards against a failure mode observed live:
|
|
338
|
+
|
|
339
|
+
1. **Don't force `toolChoice`, don't stop on the call.** `toolChoice` is
|
|
340
|
+
construction‑time and applies to _every_ segment, so after the ack it would
|
|
341
|
+
force the tool again and again up to the step ceiling (and it blocks any
|
|
342
|
+
other tools the step needs). A `hasToolCall` stop is worse: the server only
|
|
343
|
+
receives a client tool result as a side effect of the _next_ loop segment,
|
|
344
|
+
so ending the loop on the call strands the chat in `requires_action` —
|
|
345
|
+
follow‑up messages queue forever and `archive()` 409s. Instructions plus the
|
|
346
|
+
tool's own description are enough; models file unprompted most of the time.
|
|
347
|
+
2. **Validate client‑side.** The schema is not enforced server‑side —
|
|
348
|
+
`schema.safeParse` on the tool input is the real gate. (Schema‑invalid calls
|
|
349
|
+
that the AI SDK catches in‑loop are automatically answered with a
|
|
350
|
+
`tool-error` result the model retries against.)
|
|
351
|
+
3. **Nudge at most once, and only an idle chat.** If the turn ends in prose
|
|
352
|
+
(`finishReason: "stop"`) without a valid call, send one typed re‑prompt
|
|
353
|
+
("Call the structured_output tool now …"), then fail into your normal error
|
|
354
|
+
handling. Never re‑prompt a chat that isn't idle — the message would queue
|
|
355
|
+
behind whatever the server is still doing.
|
|
356
|
+
4. **Settle a turn that stopped on a tool call.** If the loop stops on a
|
|
357
|
+
tool‑call step — e.g. your `stopWhen` ceiling lands exactly on the
|
|
358
|
+
`structured_output` call (`finishReason: "tool-calls"`) — the tool results
|
|
359
|
+
ran locally but never reached the server. Submit the stranded step's
|
|
360
|
+
(`result.steps.at(-1)`) locally‑executed client results directly via
|
|
361
|
+
`agent.client.submitToolResults(agent.chatId, { results: [{ tool_call_id, output }] })`
|
|
362
|
+
before touching the chat again, or it strands as in rule 1; if a pending
|
|
363
|
+
call has no local result (or the submit fails), `agent.interrupt()` ends
|
|
364
|
+
the stranded turn instead. A settled chat resumes its wind‑down server‑side
|
|
365
|
+
for a few seconds, so retry a 409ing `archive()` under a short deadline
|
|
366
|
+
instead of giving up.
|
|
367
|
+
|
|
368
|
+
[`examples/06-structured-output.ts`](./examples/06-structured-output.ts) packages
|
|
369
|
+
all four rules into a small copyable helper — `structuredOutput(schema)` returns
|
|
370
|
+
`agentOpts` to spread into the constructor plus a typed `ask(agent, prompt)`
|
|
371
|
+
that runs the settle + one‑nudge ladder and returns a `z.infer<typeof schema>`.
|
|
372
|
+
|
|
373
|
+
## Workspaces & quota
|
|
374
|
+
|
|
375
|
+
A `CoderAgent` is one server‑side chat, and — depending on its configuration and
|
|
376
|
+
the deployment — a chat may provision a **Coder workspace** to run its tools. A
|
|
377
|
+
deployment caps how many workspaces an account may run at once, so **N agents
|
|
378
|
+
running concurrently can need N free workspace slots.** Past the cap, a turn can
|
|
379
|
+
sit unscheduled and never settle. This is the most important operational fact when
|
|
380
|
+
running many agents at once:
|
|
381
|
+
|
|
382
|
+
- Keep your own concurrency below the deployment's workspace limit.
|
|
383
|
+
- Set `requestTimeoutMs` so an unschedulable turn fails loudly instead of hanging.
|
|
384
|
+
- `archive()` / `await using` each agent so finished chats stop holding resources.
|
|
385
|
+
- For steps that don't need server‑side tools, prefer the provider — it never touches a workspace.
|
|
122
386
|
|
|
123
387
|
## Configuration
|
|
124
388
|
|
|
125
389
|
`CoderAgentSettings`:
|
|
126
390
|
|
|
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
|
-
| `
|
|
391
|
+
| field | description |
|
|
392
|
+
| --------------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
393
|
+
| `client` \| (`baseUrl` + `token`) | connection (one or the other) |
|
|
394
|
+
| `organizationId` | org UUID that owns the chat (required) |
|
|
395
|
+
| `model` | model hint: UUID, `provider:model`, model id, or display‑name substring |
|
|
396
|
+
| `instructions` | system prompt |
|
|
397
|
+
| `tools` | AI SDK `ToolSet` (client‑executed) |
|
|
398
|
+
| `workspaceId` | bind the chat to a Coder workspace (enables workspace‑scoped tools) |
|
|
399
|
+
| `workspaceFiles` | adapter enabling `uploadToWorkspace()` (write files to the workspace FS) |
|
|
400
|
+
| `mcpServerIds` | server‑side MCP servers to enable |
|
|
401
|
+
| `planMode` | enable plan mode (`"plan"`) |
|
|
402
|
+
| `stopWhen` | AI SDK stop condition(s); default `stepCountIs(64)` |
|
|
403
|
+
| `maxRetries` | default `0` — SDK retries can duplicate server‑side turns; override with care |
|
|
404
|
+
| `requestTimeoutMs` | per‑turn time budget (ms); interrupts the run and rejects (`kind: "timeout"`) instead of hanging |
|
|
405
|
+
| `chatId` | resume an existing chat |
|
|
140
406
|
|
|
141
407
|
## How it works
|
|
142
408
|
|
|
@@ -154,6 +420,28 @@ CoderAgent (implements ai.Agent)
|
|
|
154
420
|
- Streaming text is emitted from `message_part` deltas; fast turns that only produce a full
|
|
155
421
|
`message` snapshot are diffed against an emitted‑length cursor — so neither double‑counts.
|
|
156
422
|
|
|
423
|
+
## Durable workflows (Vercel Workflow, step functions, …)
|
|
424
|
+
|
|
425
|
+
`CoderAgent` talks to Coder over its own REST + WebSocket client, so it can't ride
|
|
426
|
+
a `fetch`‑shim durability layer — each turn must run **inside** a durable step. A
|
|
427
|
+
few rules keep it well‑behaved across replays:
|
|
428
|
+
|
|
429
|
+
- **One turn per step.** Create the agent, run a single `generate()` (not
|
|
430
|
+
`stream()`, so the checkpointed value is the finished result), return.
|
|
431
|
+
- **Don't persist the instance across steps.** Persist `agent.chatId` (a string)
|
|
432
|
+
and resume with `new CoderAgent({ …, chatId })` in the next step. Never persist
|
|
433
|
+
or log the token — read it from the environment in each step.
|
|
434
|
+
- **Clean up in the step.** `await using` the agent (or `await agent.archive()` in
|
|
435
|
+
a `finally`) so a step that returns early doesn't abandon the chat.
|
|
436
|
+
- **Bound each step.** Set `requestTimeoutMs` so a wedged turn fails the step (and
|
|
437
|
+
lets the workflow retry) instead of hanging the whole run.
|
|
438
|
+
- **Mind concurrency vs. workspaces.** Keep fan‑out width under the deployment's
|
|
439
|
+
workspace cap — see [Workspaces & quota](#workspaces--quota).
|
|
440
|
+
- **Use the provider for pure steps.** Steps that don't need server‑side tools
|
|
441
|
+
(plan / extract / synthesize) are cheaper and natively structured through
|
|
442
|
+
[`@coder/ai-sdk-provider`](../provider) + `generateObject` — no chat, no
|
|
443
|
+
workspace, no cleanup.
|
|
444
|
+
|
|
157
445
|
## Testing
|
|
158
446
|
|
|
159
447
|
```bash
|
|
@@ -178,7 +466,6 @@ The e2e suite creates **new chats only** (no workspaces) and archives them after
|
|
|
178
466
|
## Limitations
|
|
179
467
|
|
|
180
468
|
- 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
469
|
- Designed for Node (WebSocket via `ws`); a browser build can inject a `webSocketFactory`.
|
|
183
470
|
- A v7 `@ai-sdk/harness` adapter (the conceptually exact fit) is a future direction once that
|
|
184
471
|
experimental API stabilizes.
|