ai 7.0.114 → 7.0.116
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/CHANGELOG.md +23 -0
- package/dist/index.js +951 -1250
- package/dist/index.js.map +1 -1
- package/dist/internal/index.js +159 -212
- package/dist/internal/index.js.map +1 -1
- package/docs/03-agents/08-terminal-ui.mdx +12 -8
- package/docs/03-ai-sdk-core/15-tools-and-tool-calling.mdx +6 -1
- package/docs/03-ai-sdk-harnesses/02-harness-agent.mdx +97 -107
- package/docs/03-ai-sdk-harnesses/03-tools.mdx +1 -22
- package/docs/03-ai-sdk-harnesses/04-skills.mdx +0 -4
- package/docs/03-ai-sdk-harnesses/06-workflow-utilities.mdx +43 -9
- package/docs/03-ai-sdk-harnesses/07-ui.mdx +33 -30
- package/docs/03-ai-sdk-harnesses/08-terminal-ui.mdx +8 -6
- package/docs/06-advanced/11-secure-url-fetching.mdx +39 -0
- package/docs/07-reference/06-ai-sdk-tui/01-run-agent-tui.mdx +12 -8
- package/package.json +12 -12
- package/src/prompt/file-part-data.ts +9 -3
- package/src/util/download/download.ts +2 -2
|
@@ -82,53 +82,53 @@ Define the `HarnessAgent` on the server:
|
|
|
82
82
|
```ts filename='app/api/chat/agent.ts'
|
|
83
83
|
import { HarnessAgent } from '@ai-sdk/harness/agent';
|
|
84
84
|
import { claudeCode } from '@ai-sdk/harness-claude-code';
|
|
85
|
-
import { createVercelSandbox } from '@ai-sdk/sandbox-vercel';
|
|
86
85
|
|
|
87
86
|
export const agent = new HarnessAgent({
|
|
88
87
|
harness: claudeCode,
|
|
89
|
-
sandbox: createVercelSandbox({
|
|
90
|
-
runtime: 'node24',
|
|
91
|
-
ports: [4000],
|
|
92
|
-
}),
|
|
93
88
|
instructions: 'You are a helpful coding assistant.',
|
|
94
89
|
});
|
|
95
90
|
```
|
|
96
91
|
|
|
97
92
|
## Session Store
|
|
98
93
|
|
|
99
|
-
Persist
|
|
94
|
+
Persist the opaque resume state returned by `session.detach()`. If the
|
|
100
95
|
turn paused for approval or was otherwise interrupted, that resume state carries
|
|
101
96
|
the continuation state internally. The chat id can also be the harness
|
|
102
|
-
`sessionId
|
|
103
|
-
|
|
97
|
+
`sessionId`. Here we derive a stable `sandboxId` from that known chat id. If
|
|
98
|
+
you do not derive one, persist the returned `sandboxSession.id` separately.
|
|
104
99
|
|
|
105
100
|
```ts filename='app/api/chat/session-store.ts'
|
|
106
|
-
import type {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
101
|
+
import type { HarnessAgentResumeSessionState } from '@ai-sdk/harness/agent';
|
|
102
|
+
import {
|
|
103
|
+
createVercelNetworkSandboxSession,
|
|
104
|
+
resumeVercelNetworkSandboxSession,
|
|
105
|
+
} from '@ai-sdk/sandbox-vercel';
|
|
110
106
|
|
|
111
107
|
const states: Record<string, HarnessAgentResumeSessionState | undefined> = {};
|
|
112
108
|
|
|
113
|
-
type SessionFactory = {
|
|
114
|
-
createSession(options?: {
|
|
115
|
-
sessionId?: string;
|
|
116
|
-
resumeFrom?: HarnessAgentResumeSessionState;
|
|
117
|
-
}): Promise<HarnessAgentSession>;
|
|
118
|
-
};
|
|
119
|
-
|
|
120
109
|
export async function resumeOrCreateSession({
|
|
121
110
|
agent,
|
|
122
111
|
chatId,
|
|
123
112
|
}: {
|
|
124
|
-
agent:
|
|
113
|
+
agent: typeof import('./agent').agent;
|
|
125
114
|
chatId: string;
|
|
126
115
|
}) {
|
|
127
116
|
const resumeFrom = states[chatId];
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
117
|
+
const sandboxId = `harness-${chatId}`;
|
|
118
|
+
const sandboxSession = resumeFrom
|
|
119
|
+
? await resumeVercelNetworkSandboxSession({ sandboxId })
|
|
120
|
+
: await createVercelNetworkSandboxSession({
|
|
121
|
+
sandboxId,
|
|
122
|
+
runtime: 'node24',
|
|
123
|
+
ports: [4000],
|
|
124
|
+
template: await agent.getSandboxTemplate(),
|
|
125
|
+
});
|
|
126
|
+
const session = await agent.createSession({
|
|
127
|
+
sessionId: chatId,
|
|
128
|
+
resumeFrom,
|
|
129
|
+
sandboxSession,
|
|
130
|
+
});
|
|
131
|
+
return { session, sandboxSession };
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
export async function detachAndPersist({
|
|
@@ -136,13 +136,15 @@ export async function detachAndPersist({
|
|
|
136
136
|
session,
|
|
137
137
|
}: {
|
|
138
138
|
chatId: string;
|
|
139
|
-
session:
|
|
139
|
+
session: Awaited<ReturnType<typeof resumeOrCreateSession>>['session'];
|
|
140
140
|
}) {
|
|
141
141
|
states[chatId] = await session.detach();
|
|
142
142
|
}
|
|
143
143
|
```
|
|
144
144
|
|
|
145
|
-
Use durable storage instead of an in-memory map in production.
|
|
145
|
+
Use durable storage instead of an in-memory map in production. The creator
|
|
146
|
+
always creates a new sandbox and fails on a conflicting `sandboxId`; only the
|
|
147
|
+
resume function reattaches an existing one.
|
|
146
148
|
|
|
147
149
|
## Route
|
|
148
150
|
|
|
@@ -177,7 +179,7 @@ export async function POST(request: Request) {
|
|
|
177
179
|
return createUIMessageStreamResponse({
|
|
178
180
|
stream: createUIMessageStream({
|
|
179
181
|
execute: async ({ writer }) => {
|
|
180
|
-
const session = await resumeOrCreateSession({ agent, chatId });
|
|
182
|
+
const { session } = await resumeOrCreateSession({ agent, chatId });
|
|
181
183
|
const result = await agent.stream({ session, messages });
|
|
182
184
|
|
|
183
185
|
writer.merge(
|
|
@@ -212,9 +214,10 @@ sandbox warm for the next request. Bridge-backed adapters can usually reattach
|
|
|
212
214
|
or replay efficiently. If the turn is unfinished, `detach()` includes the turn
|
|
213
215
|
continuation state in the returned resume state.
|
|
214
216
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
217
|
+
`session.stop()` saves resume state and stops the harness runtime, but does not
|
|
218
|
+
stop a supplied sandbox. The caller owns that network session and should call
|
|
219
|
+
its `destroy()` method when the chat ends, not while a later request needs to
|
|
220
|
+
reattach. Preserve the chosen `sandboxId` across processes.
|
|
218
221
|
|
|
219
222
|
## Rendering Harness Parts
|
|
220
223
|
|
|
@@ -19,15 +19,11 @@ session for the lifetime of the terminal UI.
|
|
|
19
19
|
```ts filename='tui.ts'
|
|
20
20
|
import { HarnessAgent, type HarnessAgentSession } from '@ai-sdk/harness/agent';
|
|
21
21
|
import { codex } from '@ai-sdk/harness-codex';
|
|
22
|
-
import {
|
|
22
|
+
import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel';
|
|
23
23
|
import { runAgentTUI, type AgentTUIAgent } from '@ai-sdk/tui';
|
|
24
24
|
|
|
25
25
|
const agent = new HarnessAgent({
|
|
26
26
|
harness: codex,
|
|
27
|
-
sandbox: createVercelSandbox({
|
|
28
|
-
runtime: 'node24',
|
|
29
|
-
ports: [4000],
|
|
30
|
-
}),
|
|
31
27
|
});
|
|
32
28
|
|
|
33
29
|
function createTUIAgent({
|
|
@@ -56,7 +52,12 @@ function createTUIAgent({
|
|
|
56
52
|
} as AgentTUIAgent;
|
|
57
53
|
}
|
|
58
54
|
|
|
59
|
-
const
|
|
55
|
+
const sandboxSession = await createVercelNetworkSandboxSession({
|
|
56
|
+
runtime: 'node24',
|
|
57
|
+
ports: [4000],
|
|
58
|
+
template: await agent.getSandboxTemplate(),
|
|
59
|
+
});
|
|
60
|
+
const session = await agent.createSession({ sandboxSession });
|
|
60
61
|
|
|
61
62
|
try {
|
|
62
63
|
await runAgentTUI({
|
|
@@ -67,6 +68,7 @@ try {
|
|
|
67
68
|
});
|
|
68
69
|
} finally {
|
|
69
70
|
await session.destroy();
|
|
71
|
+
await sandboxSession.destroy();
|
|
70
72
|
}
|
|
71
73
|
```
|
|
72
74
|
|
|
@@ -45,6 +45,45 @@ When the SDK fetches a URL taken from a provider response, it:
|
|
|
45
45
|
|
|
46
46
|
A blocked URL surfaces as a `DownloadError`.
|
|
47
47
|
|
|
48
|
+
## Fetching untrusted URLs directly
|
|
49
|
+
|
|
50
|
+
For custom integrations using `@ai-sdk/provider-utils`, use `fetchUntrustedUrl`
|
|
51
|
+
when the URL comes from a response or other untrusted input. It validates URLs
|
|
52
|
+
and redirects and withholds credentials and unknown custom headers on the first
|
|
53
|
+
request unless the URL matches a configured origin:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { fetchUntrustedUrl } from '@ai-sdk/provider-utils';
|
|
57
|
+
|
|
58
|
+
const response = await fetchUntrustedUrl({
|
|
59
|
+
url: result.downloadUrl,
|
|
60
|
+
headers: {
|
|
61
|
+
authorization: `Bearer ${apiKey}`,
|
|
62
|
+
accept: 'image/*',
|
|
63
|
+
},
|
|
64
|
+
credentialedOrigin: providerBaseURL,
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Here, `Authorization` is sent only when the initial URL has the same origin as
|
|
69
|
+
`providerBaseURL`. Otherwise, only allowlisted metadata such as `Accept`,
|
|
70
|
+
`Range`, and `User-Agent` is sent. A cross-origin redirect drops all caller
|
|
71
|
+
headers except `User-Agent`, and credentials are never reattached on later hops.
|
|
72
|
+
|
|
73
|
+
`credentialedOrigin` must come from your configuration, never from the response
|
|
74
|
+
URL. It does not disable URL validation. For a configured private endpoint, pass
|
|
75
|
+
`trustedOrigin` as well; if `credentialedOrigin` is omitted, `fetchUntrustedUrl`
|
|
76
|
+
uses `trustedOrigin` as the origin allowed to receive credentials. Extra protocol
|
|
77
|
+
metadata can be allowed with `untrustedFirstHopHeaders: ['x-protocol-version']`.
|
|
78
|
+
Only list headers that are safe to disclose to any destination; use a configured
|
|
79
|
+
origin for credentials.
|
|
80
|
+
|
|
81
|
+
The existing `fetchWithValidatedRedirects` helper preserves its first-request
|
|
82
|
+
behavior: it sanitizes risky headers but still forwards authentication and
|
|
83
|
+
custom headers. Switch to `fetchUntrustedUrl` to opt into first-request
|
|
84
|
+
credential isolation. Existing calls to `getFromApi` also retain their behavior;
|
|
85
|
+
pass `credentialedOrigin` when fetching response-supplied URLs with credentials.
|
|
86
|
+
|
|
48
87
|
## Self-hosted and local endpoints
|
|
49
88
|
|
|
50
89
|
URLs that are same-origin with the provider endpoint **you configured** (e.g. a
|
|
@@ -191,17 +191,21 @@ await runAgentTUI({
|
|
|
191
191
|
## Example with Sandbox
|
|
192
192
|
|
|
193
193
|
```ts
|
|
194
|
-
import {
|
|
194
|
+
import { createJustBashNetworkSandboxSession } from '@ai-sdk/sandbox-just-bash';
|
|
195
195
|
|
|
196
|
-
const sandboxSession = await
|
|
196
|
+
const sandboxSession = await createJustBashNetworkSandboxSession({
|
|
197
197
|
cwd: '/home/user',
|
|
198
|
-
}).createSession();
|
|
199
|
-
|
|
200
|
-
await runAgentTUI({
|
|
201
|
-
title: 'Sandbox Assistant',
|
|
202
|
-
agent,
|
|
203
|
-
sandbox: sandboxSession.restricted(),
|
|
204
198
|
});
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
await runAgentTUI({
|
|
202
|
+
title: 'Sandbox Assistant',
|
|
203
|
+
agent,
|
|
204
|
+
sandbox: sandboxSession.restricted(),
|
|
205
|
+
});
|
|
206
|
+
} finally {
|
|
207
|
+
await sandboxSession.destroy();
|
|
208
|
+
}
|
|
205
209
|
```
|
|
206
210
|
|
|
207
211
|
The sandbox is forwarded to every `agent.stream()` call as
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.116",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AI SDK by Vercel - build apps like ChatGPT, Claude, Gemini, and more with a single interface for any model using the Vercel AI Gateway or go direct to OpenAI, Anthropic, Google, or any other model provider.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -42,20 +42,20 @@
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@ai-sdk/gateway": "4.0.
|
|
45
|
+
"@ai-sdk/gateway": "4.0.94",
|
|
46
46
|
"@ai-sdk/provider": "4.0.18",
|
|
47
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
47
|
+
"@ai-sdk/provider-utils": "5.0.49"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@ai-sdk/amazon-bedrock": "5.0.
|
|
51
|
-
"@ai-sdk/deepseek": "3.0.
|
|
52
|
-
"@ai-sdk/google": "4.0.
|
|
53
|
-
"@ai-sdk/groq": "4.0.
|
|
54
|
-
"@ai-sdk/huggingface": "2.0.
|
|
55
|
-
"@ai-sdk/moonshotai": "3.0.
|
|
56
|
-
"@ai-sdk/openai": "4.0.
|
|
57
|
-
"@ai-sdk/test-server": "2.0.
|
|
58
|
-
"@ai-sdk/xai": "5.0.
|
|
50
|
+
"@ai-sdk/amazon-bedrock": "5.0.96",
|
|
51
|
+
"@ai-sdk/deepseek": "3.0.54",
|
|
52
|
+
"@ai-sdk/google": "4.0.82",
|
|
53
|
+
"@ai-sdk/groq": "4.0.50",
|
|
54
|
+
"@ai-sdk/huggingface": "2.0.57",
|
|
55
|
+
"@ai-sdk/moonshotai": "3.0.58",
|
|
56
|
+
"@ai-sdk/openai": "4.0.77",
|
|
57
|
+
"@ai-sdk/test-server": "2.0.2",
|
|
58
|
+
"@ai-sdk/xai": "5.0.10",
|
|
59
59
|
"@edge-runtime/vm": "^5.0.0",
|
|
60
60
|
"@smithy/eventstream-codec": "^4.3.3",
|
|
61
61
|
"@smithy/util-utf8": "^4.3.3",
|
|
@@ -24,7 +24,10 @@ type ConvertResult = {
|
|
|
24
24
|
mediaType: string | undefined;
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
function convertUrlToFilePartData(
|
|
27
|
+
function convertUrlToFilePartData(
|
|
28
|
+
url: URL,
|
|
29
|
+
originalUrl?: string,
|
|
30
|
+
): ConvertResult {
|
|
28
31
|
if (url.protocol === 'data:') {
|
|
29
32
|
const { mediaType, base64Content } = splitDataUrl(url.toString());
|
|
30
33
|
|
|
@@ -38,7 +41,10 @@ function convertUrlToFilePartData(url: URL): ConvertResult {
|
|
|
38
41
|
return { data: { type: 'data', data: base64Content }, mediaType };
|
|
39
42
|
}
|
|
40
43
|
|
|
41
|
-
return {
|
|
44
|
+
return {
|
|
45
|
+
data: { type: 'url', url, ...(originalUrl != null ? { originalUrl } : {}) },
|
|
46
|
+
mediaType: undefined,
|
|
47
|
+
};
|
|
42
48
|
}
|
|
43
49
|
|
|
44
50
|
function convertUrlStringToFilePartData(content: string): ConvertResult {
|
|
@@ -98,7 +104,7 @@ export function convertToLanguageModelV4FilePart(
|
|
|
98
104
|
}
|
|
99
105
|
return convertInlineDataToFilePartData(content.data);
|
|
100
106
|
case 'url':
|
|
101
|
-
return convertUrlToFilePartData(content.url);
|
|
107
|
+
return convertUrlToFilePartData(content.url, content.originalUrl);
|
|
102
108
|
case 'reference':
|
|
103
109
|
return {
|
|
104
110
|
data: { type: 'reference', reference: content.reference },
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
DownloadError,
|
|
4
4
|
readResponseWithSizeLimit,
|
|
5
5
|
DEFAULT_MAX_DOWNLOAD_SIZE,
|
|
6
|
-
|
|
6
|
+
fetchUntrustedUrl,
|
|
7
7
|
withUserAgentSuffix,
|
|
8
8
|
getRuntimeEnvironmentUserAgent,
|
|
9
9
|
} from '@ai-sdk/provider-utils';
|
|
@@ -36,7 +36,7 @@ export const download = async ({
|
|
|
36
36
|
getRuntimeEnvironmentUserAgent(),
|
|
37
37
|
);
|
|
38
38
|
|
|
39
|
-
const response = await
|
|
39
|
+
const response = await fetchUntrustedUrl({
|
|
40
40
|
url: urlText,
|
|
41
41
|
headers,
|
|
42
42
|
abortSignal,
|