Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/shaggy-zebras-fetch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ai-sdk/anthropic': patch
---

add return `file_id` property for anthropic code-execution-20250825 to download output files.
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { anthropic } from '@ai-sdk/anthropic';
import { generateText } from 'ai';
import { run } from '../lib/run';
import * as fs from 'fs';

run(async () => {
const result = await generateText({
model: anthropic('claude-sonnet-4-5'),
prompt:
'Write a Python script to calculate fibonacci number' +
' and then execute it to find the 10th fibonacci number' +
' finally output data to excel file and python code.',
tools: {
code_execution: anthropic.tools.codeExecution_20250825(),
},
});

console.dir(result.content, { depth: Infinity });

const fileIdList = result.staticToolResults.flatMap(t => {
if (
t.toolName === 'code_execution' &&
t.output.type === 'bash_code_execution_result'
) {
return t.output.content.map(o => o.file_id);
}
return [];
});

await Promise.all(fileIdList.map(fileId => downloadFile(fileId)));
});

async function downloadFile(file: string) {
try {
const apiKey = process.env.ANTHROPIC_API_KEY;

if (!apiKey) {
throw new Error('ANTHROPIC_API_KEY is not set');
}
const infoUrl = `https://api.anthropic.com/v1/files/${file}`;
const infoPromise = fetch(infoUrl, {
method: 'GET',
headers: {
'x-api-key': apiKey,
'anthropic-version': '2023-06-01',
'anthropic-beta': 'files-api-2025-04-14',
},
});

const downloadUrl = `https://api.anthropic.com/v1/files/${file}/content`;
const downloadPromise = fetch(downloadUrl, {
method: 'GET',
headers: {
'x-api-key': apiKey,
'anthropic-version': '2023-06-01',
'anthropic-beta': 'files-api-2025-04-14',
},
});

const [infoResponse, downloadResponse] = await Promise.all([
infoPromise,
downloadPromise,
]);

if (!infoResponse.ok) {
throw new Error(
`HTTP Error: ${infoResponse.status} ${infoResponse.statusText}`,
);
}

const {
filename,
}: {
type: 'file';
id: string;
size_bytes: number;
created_at: Date;
filename: string;
mime_type: string;
downloadable?: boolean;
} = await infoResponse.json();

if (!downloadResponse.ok) {
throw new Error(
`HTTP Error: ${downloadResponse.status} ${downloadResponse.statusText}`,
);
}

// get as binary data
const arrayBuffer = await downloadResponse.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);

const outputPath = `output/${filename}`;

fs.writeFileSync(outputPath, buffer);

console.log(`file saved: ${outputPath}`);
console.log(`file size: ${buffer.length} bytes`);

return {
path: outputPath,
size: buffer.length,
};
} catch (error) {
console.error('error:', error);
throw error;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { anthropic } from '@ai-sdk/anthropic';
import { streamText } from 'ai';
import { run } from '../lib/run';
import * as fs from 'fs';

run(async () => {
const result = streamText({
model: anthropic('claude-sonnet-4-5'),
prompt:
'Write a Python script to calculate fibonacci number' +
' and then execute it to find the 10th fibonacci number' +
' finally output data to excel file and python code.',
tools: {
code_execution: anthropic.tools.codeExecution_20250825(),
},
});

for await (const part of result.fullStream) {
switch (part.type) {
case 'text-delta': {
process.stdout.write(part.text);
break;
}

case 'tool-call': {
process.stdout.write(
`\n\nTool call: '${part.toolName}'\nInput: ${JSON.stringify(part.input, null, 2)}\n`,
);
break;
}

case 'tool-result': {
process.stdout.write(
`\nTool result: '${part.toolName}'\nOutput: ${JSON.stringify(part.output, null, 2)}\n`,
);
break;
}

case 'error': {
console.error('\n\nCode execution error:', part.error);
break;
}
}
}

process.stdout.write('\n\n');

const fileIdList = (await result.staticToolResults).flatMap(t => {
if (
t.toolName === 'code_execution' &&
t.output.type === 'bash_code_execution_result'
) {
return t.output.content.map(o => o.file_id);
}
return [];
});

await Promise.all(fileIdList.map(fileId => downloadFile(fileId)));
});

async function downloadFile(file: string) {
try {
const apiKey = process.env.ANTHROPIC_API_KEY;

if (!apiKey) {
throw new Error('ANTHROPIC_API_KEY is not set');
}
const infoUrl = `https://api.anthropic.com/v1/files/${file}`;
const infoPromise = fetch(infoUrl, {
method: 'GET',
headers: {
'x-api-key': apiKey,
'anthropic-version': '2023-06-01',
'anthropic-beta': 'files-api-2025-04-14',
},
});

const downloadUrl = `https://api.anthropic.com/v1/files/${file}/content`;
const downloadPromise = fetch(downloadUrl, {
method: 'GET',
headers: {
'x-api-key': apiKey,
'anthropic-version': '2023-06-01',
'anthropic-beta': 'files-api-2025-04-14',
},
});

const [infoResponse, downloadResponse] = await Promise.all([
infoPromise,
downloadPromise,
]);

if (!infoResponse.ok) {
throw new Error(
`HTTP Error: ${infoResponse.status} ${infoResponse.statusText}`,
);
}

const {
filename,
}: {
type: 'file';
id: string;
size_bytes: number;
created_at: Date;
filename: string;
mime_type: string;
downloadable?: boolean;
} = await infoResponse.json();

if (!downloadResponse.ok) {
throw new Error(
`HTTP Error: ${downloadResponse.status} ${downloadResponse.statusText}`,
);
}

// get as binary data
const arrayBuffer = await downloadResponse.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);

const outputPath = `output/${filename}`;

fs.writeFileSync(outputPath, buffer);

console.log(`file saved: ${outputPath}`);
console.log(`file size: ${buffer.length} bytes`);

return {
path: outputPath,
size: buffer.length,
};
} catch (error) {
console.error('error:', error);
throw error;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import 'dotenv/config';

const dynamic = 'force-dynamic';

const execute = async (
_req: Request,
{
params,
}: {
params: Promise<{
file: string;
}>;
},
) => {
const { file } = await params;

const apiKey = process.env.ANTHROPIC_API_KEY;

if (!apiKey) {
throw new Error('ANTHROPIC_API_KEY is not set');
}

const infoUrl = `https://api.anthropic.com/v1/files/${file}`;
const infoPromise = fetch(infoUrl, {
method: 'GET',
headers: {
'x-api-key': apiKey,
'anthropic-version': '2023-06-01',
'anthropic-beta': 'files-api-2025-04-14',
},
});

const downloadUrl = `https://api.anthropic.com/v1/files/${file}/content`;
const downloadPromise = fetch(downloadUrl, {
method: 'GET',
headers: {
'x-api-key': apiKey,
'anthropic-version': '2023-06-01',
'anthropic-beta': 'files-api-2025-04-14',
},
});

const [infoResponse, downloadResponse] = await Promise.all([
infoPromise,
downloadPromise,
]);

if (!infoResponse.ok) {
throw new Error(
`HTTP Error: ${infoResponse.status} ${infoResponse.statusText}`,
);
}

if (!downloadResponse.ok) {
throw new Error(
`HTTP Error: ${downloadResponse.status} ${downloadResponse.statusText}`,
);
}

// https://github.com/anthropics/anthropic-sdk-typescript/blob/main/src/resources/beta/files.ts
const {
filename,
size_bytes,
}: {
type: 'file';
id: string;
size_bytes: number;
created_at: Date;
filename: string;
mime_type: string;
downloadable?: boolean;
} = await infoResponse.json();

// get as binary data
const arrayBuffer = await downloadResponse.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);

return new Response(buffer, {
status: 200,
headers: {
'Content-Disposition': `attachment; filename*=UTF-8''${encodeURIComponent(filename)}`,
'Content-Type': 'application/octet-stream',
'Content-Length': size_bytes.toString(),
'X-File-Name': encodeURIComponent(filename),
},
});
};

export { dynamic, execute as GET, execute as POST };
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { anthropic } from '@ai-sdk/anthropic';
import { UIToolInvocation } from 'ai';
import { Download } from 'lucide-react';

export default function AnthropicCodeExecutionView({
invocation,
Expand Down Expand Up @@ -34,6 +35,35 @@ export default function AnthropicCodeExecutionView({
<br />
</>
)}
<br />
{invocation.output.content.length > 0 && (
<div className="bg-gray-200 py-2 px-2 rounded-lg flex flex-col gap-1">
<div className="px-1">
{invocation.output.content.length > 1 ? (
<p className="text-black">downloads</p>
) : (
<p className="text-black">download</p>
)}
</div>
{invocation.output.content.map(file => (
<button
className="bg-cyan-800 hover:bg-cyan-700 text-white rounded-lg py-1 px-2 border border-white cursor-pointer"
key={file.file_id}
onClick={() =>
window.open(
`/api/code-execution-files/anthropic/${file.file_id}`,
'_blank',
)
}
>
<div className="flex gap-1 items-center justify-center">
<Download />
<p>{file.file_id}</p>
</div>
</button>
))}
</div>
)}
{invocation.output.return_code != null && (
<>
<span className="font-semibold">Return Code:</span>
Expand Down
Loading
Loading