Skip to content

Commit e060896

Browse files
tsuzaki430vercel-ai-sdk[bot]
authored andcommitted
Backport conflicts for PR #9669 to release-v5.0
1 parent 50b138d commit e060896

File tree

10 files changed

+7101
-0
lines changed

10 files changed

+7101
-0
lines changed

.changeset/shaggy-zebras-fetch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@ai-sdk/anthropic': patch
3+
---
4+
5+
add return `file_id` property for anthropic code-execution-20250825 to download output files.
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { anthropic } from '@ai-sdk/anthropic';
2+
import { generateText } from 'ai';
3+
import { run } from '../lib/run';
4+
import * as fs from 'fs';
5+
6+
run(async () => {
7+
const result = await generateText({
8+
model: anthropic('claude-sonnet-4-5'),
9+
prompt:
10+
'Write a Python script to calculate fibonacci number' +
11+
' and then execute it to find the 10th fibonacci number' +
12+
' finally output data to excel file and python code.',
13+
tools: {
14+
code_execution: anthropic.tools.codeExecution_20250825(),
15+
},
16+
});
17+
18+
console.dir(result.content, { depth: Infinity });
19+
20+
const fileIdList = result.staticToolResults.flatMap(t => {
21+
if (
22+
t.toolName === 'code_execution' &&
23+
t.output.type === 'bash_code_execution_result'
24+
) {
25+
return t.output.content.map(o => o.file_id);
26+
}
27+
return [];
28+
});
29+
30+
await Promise.all(fileIdList.map(fileId => downloadFile(fileId)));
31+
});
32+
33+
async function downloadFile(file: string) {
34+
try {
35+
const apiKey = process.env.ANTHROPIC_API_KEY;
36+
37+
if (!apiKey) {
38+
throw new Error('ANTHROPIC_API_KEY is not set');
39+
}
40+
const infoUrl = `https://api.anthropic.com/v1/files/${file}`;
41+
const infoPromise = fetch(infoUrl, {
42+
method: 'GET',
43+
headers: {
44+
'x-api-key': apiKey,
45+
'anthropic-version': '2023-06-01',
46+
'anthropic-beta': 'files-api-2025-04-14',
47+
},
48+
});
49+
50+
const downloadUrl = `https://api.anthropic.com/v1/files/${file}/content`;
51+
const downloadPromise = fetch(downloadUrl, {
52+
method: 'GET',
53+
headers: {
54+
'x-api-key': apiKey,
55+
'anthropic-version': '2023-06-01',
56+
'anthropic-beta': 'files-api-2025-04-14',
57+
},
58+
});
59+
60+
const [infoResponse, downloadResponse] = await Promise.all([
61+
infoPromise,
62+
downloadPromise,
63+
]);
64+
65+
if (!infoResponse.ok) {
66+
throw new Error(
67+
`HTTP Error: ${infoResponse.status} ${infoResponse.statusText}`,
68+
);
69+
}
70+
71+
const {
72+
filename,
73+
}: {
74+
type: 'file';
75+
id: string;
76+
size_bytes: number;
77+
created_at: Date;
78+
filename: string;
79+
mime_type: string;
80+
downloadable?: boolean;
81+
} = await infoResponse.json();
82+
83+
if (!downloadResponse.ok) {
84+
throw new Error(
85+
`HTTP Error: ${downloadResponse.status} ${downloadResponse.statusText}`,
86+
);
87+
}
88+
89+
// get as binary data
90+
const arrayBuffer = await downloadResponse.arrayBuffer();
91+
const buffer = Buffer.from(arrayBuffer);
92+
93+
const outputPath = `output/${filename}`;
94+
95+
fs.writeFileSync(outputPath, buffer);
96+
97+
console.log(`file saved: ${outputPath}`);
98+
console.log(`file size: ${buffer.length} bytes`);
99+
100+
return {
101+
path: outputPath,
102+
size: buffer.length,
103+
};
104+
} catch (error) {
105+
console.error('error:', error);
106+
throw error;
107+
}
108+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import { anthropic } from '@ai-sdk/anthropic';
2+
import { streamText } from 'ai';
3+
import { run } from '../lib/run';
4+
import * as fs from 'fs';
5+
6+
run(async () => {
7+
const result = streamText({
8+
model: anthropic('claude-sonnet-4-5'),
9+
prompt:
10+
'Write a Python script to calculate fibonacci number' +
11+
' and then execute it to find the 10th fibonacci number' +
12+
' finally output data to excel file and python code.',
13+
tools: {
14+
code_execution: anthropic.tools.codeExecution_20250825(),
15+
},
16+
});
17+
18+
for await (const part of result.fullStream) {
19+
switch (part.type) {
20+
case 'text-delta': {
21+
process.stdout.write(part.text);
22+
break;
23+
}
24+
25+
case 'tool-call': {
26+
process.stdout.write(
27+
`\n\nTool call: '${part.toolName}'\nInput: ${JSON.stringify(part.input, null, 2)}\n`,
28+
);
29+
break;
30+
}
31+
32+
case 'tool-result': {
33+
process.stdout.write(
34+
`\nTool result: '${part.toolName}'\nOutput: ${JSON.stringify(part.output, null, 2)}\n`,
35+
);
36+
break;
37+
}
38+
39+
case 'error': {
40+
console.error('\n\nCode execution error:', part.error);
41+
break;
42+
}
43+
}
44+
}
45+
46+
process.stdout.write('\n\n');
47+
48+
const fileIdList = (await result.staticToolResults).flatMap(t => {
49+
if (
50+
t.toolName === 'code_execution' &&
51+
t.output.type === 'bash_code_execution_result'
52+
) {
53+
return t.output.content.map(o => o.file_id);
54+
}
55+
return [];
56+
});
57+
58+
await Promise.all(fileIdList.map(fileId => downloadFile(fileId)));
59+
});
60+
61+
async function downloadFile(file: string) {
62+
try {
63+
const apiKey = process.env.ANTHROPIC_API_KEY;
64+
65+
if (!apiKey) {
66+
throw new Error('ANTHROPIC_API_KEY is not set');
67+
}
68+
const infoUrl = `https://api.anthropic.com/v1/files/${file}`;
69+
const infoPromise = fetch(infoUrl, {
70+
method: 'GET',
71+
headers: {
72+
'x-api-key': apiKey,
73+
'anthropic-version': '2023-06-01',
74+
'anthropic-beta': 'files-api-2025-04-14',
75+
},
76+
});
77+
78+
const downloadUrl = `https://api.anthropic.com/v1/files/${file}/content`;
79+
const downloadPromise = fetch(downloadUrl, {
80+
method: 'GET',
81+
headers: {
82+
'x-api-key': apiKey,
83+
'anthropic-version': '2023-06-01',
84+
'anthropic-beta': 'files-api-2025-04-14',
85+
},
86+
});
87+
88+
const [infoResponse, downloadResponse] = await Promise.all([
89+
infoPromise,
90+
downloadPromise,
91+
]);
92+
93+
if (!infoResponse.ok) {
94+
throw new Error(
95+
`HTTP Error: ${infoResponse.status} ${infoResponse.statusText}`,
96+
);
97+
}
98+
99+
const {
100+
filename,
101+
}: {
102+
type: 'file';
103+
id: string;
104+
size_bytes: number;
105+
created_at: Date;
106+
filename: string;
107+
mime_type: string;
108+
downloadable?: boolean;
109+
} = await infoResponse.json();
110+
111+
if (!downloadResponse.ok) {
112+
throw new Error(
113+
`HTTP Error: ${downloadResponse.status} ${downloadResponse.statusText}`,
114+
);
115+
}
116+
117+
// get as binary data
118+
const arrayBuffer = await downloadResponse.arrayBuffer();
119+
const buffer = Buffer.from(arrayBuffer);
120+
121+
const outputPath = `output/${filename}`;
122+
123+
fs.writeFileSync(outputPath, buffer);
124+
125+
console.log(`file saved: ${outputPath}`);
126+
console.log(`file size: ${buffer.length} bytes`);
127+
128+
return {
129+
path: outputPath,
130+
size: buffer.length,
131+
};
132+
} catch (error) {
133+
console.error('error:', error);
134+
throw error;
135+
}
136+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import 'dotenv/config';
2+
3+
const dynamic = 'force-dynamic';
4+
5+
const execute = async (
6+
_req: Request,
7+
{
8+
params,
9+
}: {
10+
params: Promise<{
11+
file: string;
12+
}>;
13+
},
14+
) => {
15+
const { file } = await params;
16+
17+
const apiKey = process.env.ANTHROPIC_API_KEY;
18+
19+
if (!apiKey) {
20+
throw new Error('ANTHROPIC_API_KEY is not set');
21+
}
22+
23+
const infoUrl = `https://api.anthropic.com/v1/files/${file}`;
24+
const infoPromise = fetch(infoUrl, {
25+
method: 'GET',
26+
headers: {
27+
'x-api-key': apiKey,
28+
'anthropic-version': '2023-06-01',
29+
'anthropic-beta': 'files-api-2025-04-14',
30+
},
31+
});
32+
33+
const downloadUrl = `https://api.anthropic.com/v1/files/${file}/content`;
34+
const downloadPromise = fetch(downloadUrl, {
35+
method: 'GET',
36+
headers: {
37+
'x-api-key': apiKey,
38+
'anthropic-version': '2023-06-01',
39+
'anthropic-beta': 'files-api-2025-04-14',
40+
},
41+
});
42+
43+
const [infoResponse, downloadResponse] = await Promise.all([
44+
infoPromise,
45+
downloadPromise,
46+
]);
47+
48+
if (!infoResponse.ok) {
49+
throw new Error(
50+
`HTTP Error: ${infoResponse.status} ${infoResponse.statusText}`,
51+
);
52+
}
53+
54+
if (!downloadResponse.ok) {
55+
throw new Error(
56+
`HTTP Error: ${downloadResponse.status} ${downloadResponse.statusText}`,
57+
);
58+
}
59+
60+
// https://github.com/anthropics/anthropic-sdk-typescript/blob/main/src/resources/beta/files.ts
61+
const {
62+
filename,
63+
size_bytes,
64+
}: {
65+
type: 'file';
66+
id: string;
67+
size_bytes: number;
68+
created_at: Date;
69+
filename: string;
70+
mime_type: string;
71+
downloadable?: boolean;
72+
} = await infoResponse.json();
73+
74+
// get as binary data
75+
const arrayBuffer = await downloadResponse.arrayBuffer();
76+
const buffer = Buffer.from(arrayBuffer);
77+
78+
return new Response(buffer, {
79+
status: 200,
80+
headers: {
81+
'Content-Disposition': `attachment; filename*=UTF-8''${encodeURIComponent(filename)}`,
82+
'Content-Type': 'application/octet-stream',
83+
'Content-Length': size_bytes.toString(),
84+
'X-File-Name': encodeURIComponent(filename),
85+
},
86+
});
87+
};
88+
89+
export { dynamic, execute as GET, execute as POST };

examples/next-openai/components/tool/anthropic-code-execution-view.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { anthropic } from '@ai-sdk/anthropic';
22
import { UIToolInvocation } from 'ai';
3+
import { Download } from 'lucide-react';
34

45
export default function AnthropicCodeExecutionView({
56
invocation,
@@ -34,6 +35,35 @@ export default function AnthropicCodeExecutionView({
3435
<br />
3536
</>
3637
)}
38+
<br />
39+
{invocation.output.content.length > 0 && (
40+
<div className="bg-gray-200 py-2 px-2 rounded-lg flex flex-col gap-1">
41+
<div className="px-1">
42+
{invocation.output.content.length > 1 ? (
43+
<p className="text-black">downloads</p>
44+
) : (
45+
<p className="text-black">download</p>
46+
)}
47+
</div>
48+
{invocation.output.content.map(file => (
49+
<button
50+
className="bg-cyan-800 hover:bg-cyan-700 text-white rounded-lg py-1 px-2 border border-white cursor-pointer"
51+
key={file.file_id}
52+
onClick={() =>
53+
window.open(
54+
`/api/code-execution-files/anthropic/${file.file_id}`,
55+
'_blank',
56+
)
57+
}
58+
>
59+
<div className="flex gap-1 items-center justify-center">
60+
<Download />
61+
<p>{file.file_id}</p>
62+
</div>
63+
</button>
64+
))}
65+
</div>
66+
)}
3767
{invocation.output.return_code != null && (
3868
<>
3969
<span className="font-semibold">Return Code:</span>

0 commit comments

Comments
 (0)