Skip to content

Commit f4feb99

Browse files
feat(vercel): add variations for streaming/blocking, anthropic/openai and functions/ToolLoopAgent (#129)
* feat(vercel): add variations for streaming/blocking, anthropic/openai and functions/ToolLoopAgent * fix: addressed review comments
1 parent be72cc0 commit f4feb99

File tree

6 files changed

+377
-15
lines changed

6 files changed

+377
-15
lines changed

src/runner/templates/agents/cloudflare/vercel/config.json

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"displayName": "Vercel AI SDK (Cloudflare)",
44
"type": "agentic",
55
"platform": "cloudflare",
6+
"streamingMode": "both",
67
"dependencies": [
78
{
89
"package": "ai",
@@ -11,8 +12,27 @@
1112
{
1213
"package": "@ai-sdk/openai",
1314
"version": "latest"
15+
},
16+
{
17+
"package": "@ai-sdk/anthropic",
18+
"version": "latest"
1419
}
1520
],
1621
"versions": ["6.0.116"],
17-
"sentryVersions": ["latest"]
22+
"sentryVersions": ["latest"],
23+
"options": {
24+
"agentStyle": ["function", "class"],
25+
"provider": [
26+
"openai",
27+
{
28+
"value": "anthropic",
29+
"overrides": {
30+
"modelOverrides": {
31+
"request": "claude-haiku-4-5",
32+
"response": "claude-haiku-4-5*"
33+
}
34+
}
35+
}
36+
]
37+
}
1838
}

src/runner/templates/agents/cloudflare/vercel/template.njk

Lines changed: 120 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,33 @@
2727
{% endblock %}
2828

2929
{% block imports %}
30+
{% if agentStyle == "class" %}
31+
import { ToolLoopAgent, tool, jsonSchema, stepCountIs } from "ai";
32+
{% elif isStreaming %}
33+
import { streamText, tool, jsonSchema, stepCountIs } from "ai";
34+
{% else %}
3035
import { generateText, tool, jsonSchema, stepCountIs } from "ai";
36+
{% endif %}
37+
{% if provider == "anthropic" %}
38+
import { createAnthropic } from "@ai-sdk/anthropic";
39+
{% else %}
3140
import { createOpenAI } from "@ai-sdk/openai";
41+
{% endif %}
3242
{% endblock %}
3343

3444
{% block dynamic_imports %}
45+
{% if provider == "anthropic" %}
46+
const anthropic = createAnthropic({ apiKey: env.ANTHROPIC_API_KEY });
47+
{% else %}
3548
const openai = createOpenAI({ apiKey: env.OPENAI_API_KEY });
49+
{% endif %}
3650
{% endblock %}
3751

52+
{# Helper macro for model reference #}
53+
{% macro modelRef(input) %}
54+
{% if provider == "anthropic" %}anthropic({% if causeAPIError %}"invalid-model"{% else %}"{{ input.model }}"{% endif %}){% else %}openai({% if causeAPIError %}"invalid-model"{% else %}"{{ input.model }}"{% endif %}){% endif %}
55+
{% endmacro %}
56+
3857
{% block test %}
3958
{% if agent and agent.tools and agent.tools.length > 0 %}
4059
const tools = {
@@ -70,10 +89,106 @@ import { createOpenAI } from "@ai-sdk/openai";
7089
// Request {{ loop.index }}{% if loop.length > 1 %} of {{ loop.length }}{% endif %}
7190

7291
try {
92+
{% if agentStyle == "class" %}
93+
// ToolLoopAgent class-based approach
94+
const agent = new ToolLoopAgent({
95+
model: {{ modelRef(input) | trim }},
96+
{% if agent and agent.tools and agent.tools.length > 0 %}
97+
tools,
98+
{% endif %}
99+
{% if system_content %}
100+
instructions: "{{ system_content }}",
101+
{% endif %}
102+
stopWhen: stepCountIs(10),
103+
experimental_telemetry: {
104+
isEnabled: true,
105+
functionId: "{{ agent.name if agent else 'assistant' }}",
106+
recordInputs: true,
107+
recordOutputs: true,
108+
},
109+
});
110+
111+
{% if isStreaming %}
112+
{% if user_content is iterable and user_content is not string %}
113+
const { textStream } = await agent.stream({
114+
messages: [
115+
{
116+
role: "user",
117+
content: {{ renderVercelContent(user_content) }},
118+
},
119+
],
120+
});
121+
{% else %}
122+
const { textStream } = await agent.stream({
123+
prompt: "{{ user_content }}",
124+
});
125+
{% endif %}
126+
const chunks = [];
127+
for await (const chunk of textStream) {
128+
chunks.push(chunk);
129+
}
130+
console.log("Response:", chunks.join(""));
131+
{% else %}
132+
{% if user_content is iterable and user_content is not string %}
133+
const { text } = await agent.generate({
134+
messages: [
135+
{
136+
role: "user",
137+
content: {{ renderVercelContent(user_content) }},
138+
},
139+
],
140+
});
141+
{% else %}
142+
const { text } = await agent.generate({
143+
prompt: "{{ user_content }}",
144+
});
145+
{% endif %}
146+
console.log("Response:", text);
147+
{% endif %}
148+
{% else %}
149+
// Function-based approach
150+
{% if isStreaming %}
151+
{% if user_content is iterable and user_content is not string %}
152+
const { textStream } = streamText({
153+
model: {{ modelRef(input) | trim }},
154+
{% if system_content %}
155+
system: "{{ system_content }}",
156+
{% endif %}
157+
messages: [
158+
{
159+
role: "user",
160+
content: {{ renderVercelContent(user_content) }},
161+
},
162+
],
163+
{% if agent and agent.tools and agent.tools.length > 0 %}
164+
tools,
165+
stopWhen: stepCountIs(10),
166+
{% endif %}
167+
experimental_telemetry: { isEnabled: true, recordInputs: true, recordOutputs: true },
168+
});
169+
{% else %}
170+
const { textStream } = streamText({
171+
model: {{ modelRef(input) | trim }},
172+
{% if system_content %}
173+
system: "{{ system_content }}",
174+
{% endif %}
175+
prompt: "{{ user_content }}",
176+
{% if agent and agent.tools and agent.tools.length > 0 %}
177+
tools,
178+
stopWhen: stepCountIs(10),
179+
{% endif %}
180+
experimental_telemetry: { isEnabled: true, recordInputs: true, recordOutputs: true },
181+
});
182+
{% endif %}
183+
const chunks = [];
184+
for await (const chunk of textStream) {
185+
chunks.push(chunk);
186+
}
187+
console.log("Response:", chunks.join(""));
188+
{% else %}
73189
{% if user_content is iterable and user_content is not string %}
74-
// Multimodal content - use messages array
75190
const { text } = await generateText({
76-
model: openai({% if causeAPIError %}"invalid-model"{% else %}"{{ input.model }}"{% endif %}),
191+
model: {{ modelRef(input) | trim }},
77192
{% if system_content %}
78193
system: "{{ system_content }}",
79194
{% endif %}
@@ -90,9 +205,8 @@ import { createOpenAI } from "@ai-sdk/openai";
90205
experimental_telemetry: { isEnabled: true, recordInputs: true, recordOutputs: true },
91206
});
92207
{% else %}
93-
// Simple text prompt
94208
const { text } = await generateText({
95-
model: openai({% if causeAPIError %}"invalid-model"{% else %}"{{ input.model }}"{% endif %}),
209+
model: {{ modelRef(input) | trim }},
96210
{% if system_content %}
97211
system: "{{ system_content }}",
98212
{% endif %}
@@ -105,6 +219,8 @@ import { createOpenAI } from "@ai-sdk/openai";
105219
});
106220
{% endif %}
107221
console.log("Response:", text);
222+
{% endif %}
223+
{% endif %}
108224
} catch (error) {
109225
Sentry.captureException(error);
110226
console.error("Error:", error.message);

src/runner/templates/agents/nextjs/vercel/config.json

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
"package": "@ai-sdk/openai",
1414
"version": "latest"
1515
},
16+
{
17+
"package": "@ai-sdk/anthropic",
18+
"version": "latest"
19+
},
1620
{
1721
"package": "@sentry/nextjs",
1822
"version": "sentry"
@@ -23,5 +27,20 @@
2327
}
2428
],
2529
"versions": ["6.0.116"],
26-
"sentryVersions": ["latest"]
30+
"sentryVersions": ["latest"],
31+
"options": {
32+
"agentStyle": ["function", "class"],
33+
"provider": [
34+
"openai",
35+
{
36+
"value": "anthropic",
37+
"overrides": {
38+
"modelOverrides": {
39+
"request": "claude-haiku-4-5",
40+
"response": "claude-haiku-4-5*"
41+
}
42+
}
43+
}
44+
]
45+
}
2746
}

src/runner/templates/agents/nextjs/vercel/template.njk

Lines changed: 81 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,24 @@
1818
{% endmacro %}
1919

2020
{% block dynamic_imports %}
21+
{% if agentStyle == "class" %}
22+
const { ToolLoopAgent, tool, jsonSchema, stepCountIs } = await import("ai");
23+
{% else %}
2124
const { generateText, streamText, tool, jsonSchema, stepCountIs } = await import("ai");
25+
{% endif %}
26+
{% if provider == "anthropic" %}
27+
const { anthropic } = await import("@ai-sdk/anthropic");
28+
{% else %}
2229
const { openai } = await import("@ai-sdk/openai");
30+
{% endif %}
2331
console.log('Vercel AI SDK initialized (auto-instrumentation enabled)');
2432
{% endblock %}
2533

34+
{# Helper macro for model reference #}
35+
{% macro modelRef(input) %}
36+
{% if provider == "anthropic" %}anthropic({% if causeAPIError %}"invalid-model"{% else %}"{{ input.model }}"{% endif %}){% else %}openai({% if causeAPIError %}"invalid-model"{% else %}"{{ input.model }}"{% endif %}){% endif %}
37+
{% endmacro %}
38+
2639
{% block test %}
2740
{% if agent and agent.tools and agent.tools.length > 0 %}
2841
const tools = {
@@ -58,13 +71,76 @@ console.log('Vercel AI SDK initialized (auto-instrumentation enabled)');
5871
// Request {{ loop.index }}{% if loop.length > 1 %} of {{ loop.length }}{% endif %}
5972

6073
try {
74+
{% if agentStyle == "class" %}
75+
// ToolLoopAgent class-based approach
76+
const agent = new ToolLoopAgent({
77+
model: {{ modelRef(input) | trim }},
78+
{% if agent and agent.tools and agent.tools.length > 0 %}
79+
tools,
80+
{% endif %}
81+
{% if system_content %}
82+
instructions: "{{ system_content }}",
83+
{% endif %}
84+
stopWhen: stepCountIs(10),
85+
experimental_telemetry: {
86+
isEnabled: true,
87+
functionId: "{{ agent.name if agent else 'assistant' }}",
88+
recordInputs: true,
89+
recordOutputs: true,
90+
},
91+
});
92+
93+
{% if isStreaming %}
94+
console.log('Starting streaming request {{ loop.index }}...');
95+
96+
{% if user_content is iterable and user_content is not string %}
97+
const { textStream } = await agent.stream({
98+
messages: [
99+
{
100+
role: "user",
101+
content: {{ renderVercelContent(user_content) }},
102+
},
103+
],
104+
});
105+
{% else %}
106+
const { textStream } = await agent.stream({
107+
prompt: "{{ user_content }}",
108+
});
109+
{% endif %}
110+
111+
const chunks = [];
112+
for await (const chunk of textStream) {
113+
chunks.push(chunk);
114+
}
115+
console.log("Response {{ loop.index }}:", chunks.join(""));
116+
{% else %}
117+
console.log('Starting blocking request {{ loop.index }}...');
118+
119+
{% if user_content is iterable and user_content is not string %}
120+
const { text } = await agent.generate({
121+
messages: [
122+
{
123+
role: "user",
124+
content: {{ renderVercelContent(user_content) }},
125+
},
126+
],
127+
});
128+
{% else %}
129+
const { text } = await agent.generate({
130+
prompt: "{{ user_content }}",
131+
});
132+
{% endif %}
133+
console.log("Response {{ loop.index }}:", text);
134+
{% endif %}
135+
{% else %}
136+
// Function-based approach
61137
{% if isStreaming %}
62138
console.log('Starting streaming request {{ loop.index }}...');
63139

64140
{% if user_content is iterable and user_content is not string %}
65141
// Multimodal content - use messages array
66142
const { textStream } = streamText({
67-
model: openai({% if causeAPIError %}"invalid-model"{% else %}"{{ input.model }}"{% endif %}),
143+
model: {{ modelRef(input) | trim }},
68144
{% if system_content %}
69145
system: "{{ system_content }}",
70146
{% endif %}
@@ -87,7 +163,7 @@ console.log('Vercel AI SDK initialized (auto-instrumentation enabled)');
87163
{% else %}
88164
// Simple text prompt
89165
const { textStream } = streamText({
90-
model: openai({% if causeAPIError %}"invalid-model"{% else %}"{{ input.model }}"{% endif %}),
166+
model: {{ modelRef(input) | trim }},
91167
{% if system_content %}
92168
system: "{{ system_content }}",
93169
{% endif %}
@@ -115,7 +191,7 @@ console.log('Vercel AI SDK initialized (auto-instrumentation enabled)');
115191
{% if user_content is iterable and user_content is not string %}
116192
// Multimodal content - use messages array
117193
const { text } = await generateText({
118-
model: openai({% if causeAPIError %}"invalid-model"{% else %}"{{ input.model }}"{% endif %}),
194+
model: {{ modelRef(input) | trim }},
119195
{% if system_content %}
120196
system: "{{ system_content }}",
121197
{% endif %}
@@ -138,7 +214,7 @@ console.log('Vercel AI SDK initialized (auto-instrumentation enabled)');
138214
{% else %}
139215
// Simple text prompt
140216
const { text } = await generateText({
141-
model: openai({% if causeAPIError %}"invalid-model"{% else %}"{{ input.model }}"{% endif %}),
217+
model: {{ modelRef(input) | trim }},
142218
{% if system_content %}
143219
system: "{{ system_content }}",
144220
{% endif %}
@@ -155,6 +231,7 @@ console.log('Vercel AI SDK initialized (auto-instrumentation enabled)');
155231
});
156232
{% endif %}
157233
console.log("Response {{ loop.index }}:", text);
234+
{% endif %}
158235
{% endif %}
159236
} catch (error) {
160237
Sentry.captureException(error);

0 commit comments

Comments
 (0)