veryfront 0.1.62 → 0.1.64

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.
Files changed (73) hide show
  1. package/esm/cli/templates/manifest.js +37 -37
  2. package/esm/deno.d.ts +3 -0
  3. package/esm/deno.js +6 -3
  4. package/esm/src/agent/composition/composition.d.ts.map +1 -1
  5. package/esm/src/agent/composition/composition.js +13 -3
  6. package/esm/src/agent/factory.d.ts.map +1 -1
  7. package/esm/src/agent/factory.js +3 -3
  8. package/esm/src/agent/middleware/security/validator.d.ts +92 -0
  9. package/esm/src/agent/middleware/security/validator.d.ts.map +1 -0
  10. package/esm/src/agent/middleware/security/validator.js +187 -0
  11. package/esm/src/agent/runtime/index.d.ts +3 -2
  12. package/esm/src/agent/runtime/index.d.ts.map +1 -1
  13. package/esm/src/agent/runtime/index.js +16 -8
  14. package/esm/src/agent/types.d.ts +4 -0
  15. package/esm/src/agent/types.d.ts.map +1 -1
  16. package/esm/src/channels/invoke.d.ts +491 -0
  17. package/esm/src/channels/invoke.d.ts.map +1 -0
  18. package/esm/src/channels/invoke.js +417 -0
  19. package/esm/src/embedding/embedding.js +2 -2
  20. package/esm/src/integrations/endpoint-executor.d.ts +1 -0
  21. package/esm/src/integrations/endpoint-executor.d.ts.map +1 -1
  22. package/esm/src/integrations/endpoint-executor.js +44 -0
  23. package/esm/src/integrations/schema.d.ts +2 -2
  24. package/esm/src/oauth/handlers/init-handler.d.ts +6 -2
  25. package/esm/src/oauth/handlers/init-handler.d.ts.map +1 -1
  26. package/esm/src/oauth/handlers/init-handler.js +8 -2
  27. package/esm/src/platform/compat/opaque-deps.d.ts.map +1 -1
  28. package/esm/src/platform/compat/opaque-deps.js +10 -1
  29. package/esm/src/prompt/factory.d.ts.map +1 -1
  30. package/esm/src/prompt/factory.js +9 -1
  31. package/esm/src/react/components/ai/markdown.d.ts.map +1 -1
  32. package/esm/src/react/components/ai/markdown.js +4 -4
  33. package/esm/src/server/handlers/dev/framework-candidates.generated.d.ts.map +1 -1
  34. package/esm/src/server/handlers/dev/framework-candidates.generated.js +5 -4
  35. package/esm/src/server/handlers/preview/markdown-html-generator.js +1 -1
  36. package/esm/src/server/handlers/request/api/api-handler-wrapper.d.ts.map +1 -1
  37. package/esm/src/server/handlers/request/api/api-handler-wrapper.js +1 -74
  38. package/esm/src/server/handlers/request/api/project-discovery.d.ts +9 -0
  39. package/esm/src/server/handlers/request/api/project-discovery.d.ts.map +1 -0
  40. package/esm/src/server/handlers/request/api/project-discovery.js +74 -0
  41. package/esm/src/server/handlers/request/channel-assistants.handler.d.ts +11 -0
  42. package/esm/src/server/handlers/request/channel-assistants.handler.d.ts.map +1 -0
  43. package/esm/src/server/handlers/request/channel-assistants.handler.js +71 -0
  44. package/esm/src/server/handlers/request/channel-invoke.handler.d.ts +11 -0
  45. package/esm/src/server/handlers/request/channel-invoke.handler.d.ts.map +1 -0
  46. package/esm/src/server/handlers/request/channel-invoke.handler.js +72 -0
  47. package/esm/src/server/runtime-handler/index.d.ts.map +1 -1
  48. package/esm/src/server/runtime-handler/index.js +4 -0
  49. package/esm/src/transforms/md/compiler/md-compiler.d.ts.map +1 -1
  50. package/esm/src/transforms/md/compiler/md-compiler.js +25 -1
  51. package/package.json +3 -1
  52. package/src/cli/templates/manifest.js +37 -37
  53. package/src/deno.js +6 -3
  54. package/src/src/agent/composition/composition.ts +15 -3
  55. package/src/src/agent/factory.ts +19 -6
  56. package/src/src/agent/middleware/security/validator.ts +288 -0
  57. package/src/src/agent/runtime/index.ts +26 -3
  58. package/src/src/agent/types.ts +4 -0
  59. package/src/src/channels/invoke.ts +546 -0
  60. package/src/src/embedding/embedding.ts +2 -2
  61. package/src/src/integrations/endpoint-executor.ts +51 -0
  62. package/src/src/oauth/handlers/init-handler.ts +20 -5
  63. package/src/src/platform/compat/opaque-deps.ts +19 -4
  64. package/src/src/prompt/factory.ts +10 -1
  65. package/src/src/react/components/ai/markdown.tsx +5 -4
  66. package/src/src/server/handlers/dev/framework-candidates.generated.ts +5 -4
  67. package/src/src/server/handlers/preview/markdown-html-generator.ts +1 -1
  68. package/src/src/server/handlers/request/api/api-handler-wrapper.ts +1 -85
  69. package/src/src/server/handlers/request/api/project-discovery.ts +86 -0
  70. package/src/src/server/handlers/request/channel-assistants.handler.ts +94 -0
  71. package/src/src/server/handlers/request/channel-invoke.handler.ts +95 -0
  72. package/src/src/server/runtime-handler/index.ts +4 -0
  73. package/src/src/transforms/md/compiler/md-compiler.ts +27 -1
package/src/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.62",
3
+ "version": "0.1.64",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "exclude": [
@@ -225,6 +225,8 @@ export default {
225
225
  "rehype-highlight": "npm:rehype-highlight@7.0.2",
226
226
  "rehype-starry-night": "npm:rehype-starry-night@2.2.0",
227
227
  "rehype-slug": "npm:rehype-slug@6.0.0",
228
+ "rehype-raw": "npm:rehype-raw@7.0.0",
229
+ "rehype-sanitize": "npm:rehype-sanitize@6.0.0",
228
230
  "rehype-stringify": "npm:rehype-stringify@10.0.1",
229
231
  "esbuild": "npm:esbuild@0.20.2",
230
232
  "esbuild/mod.js": "npm:esbuild@0.20.2",
@@ -263,7 +265,8 @@ export default {
263
265
  "class-variance-authority": "npm:class-variance-authority@0.7.1",
264
266
  "clsx": "npm:clsx@2.1.1",
265
267
  "tailwind-merge": "npm:tailwind-merge@2.6.0",
266
- "@kreuzberg/wasm": "npm:@kreuzberg/wasm@4.4.2"
268
+ "@kreuzberg/wasm": "npm:@kreuzberg/wasm@4.4.2",
269
+ "#kreuzberg-wasm-glue": "npm:@kreuzberg/wasm@4.4.2/dist/pkg/kreuzberg_wasm.js"
267
270
  },
268
271
  "compilerOptions": {
269
272
  "jsx": "react-jsx",
@@ -291,7 +294,7 @@ export default {
291
294
  "dev": "deno task generate && deno run --allow-all cli/main.ts dev",
292
295
  "production": "deno task generate && deno run --allow-all cli/main.ts serve --mode=production",
293
296
  "build:prepare": "deno run -A scripts/build/generate-integrations-module.ts && deno task generate && deno run -A scripts/build/prepare-framework-sources.ts",
294
- "build": "deno task build:prepare && deno compile --allow-all --include src/platform/polyfills --include src/proxy/main.ts --include dist/framework-src --output ./bin/veryfront cli/main.ts",
297
+ "build": "deno task build:prepare && deno run -A scripts/build/compile-binary.ts --output ./bin/veryfront",
295
298
  "build:npm": "deno run -A scripts/build/generate-integrations-module.ts && deno task generate && deno run -A scripts/build/build-npm-dnt.ts",
296
299
  "release": "deno run -A scripts/release.ts",
297
300
  "test": "deno task generate && VF_DISABLE_LRU_INTERVAL=1 SSR_TRANSFORM_PER_PROJECT_LIMIT=0 REVALIDATION_PER_PROJECT_LIMIT=0 NODE_ENV=production LOG_FORMAT=text deno test --no-check --parallel --allow-all '--ignore=tests/e2e,tests/integration/compiled-binary-e2e.test.ts' --unstable-worker-options --unstable-net",
@@ -149,9 +149,21 @@ export function getAllAgentIds(): string[] {
149
149
  // Register on globalThis so compiled-binary runtime shim can delegate to the
150
150
  // real registry. External temp-file modules can't import from the embedded
151
151
  // binary FS, so they use globalThis bridges instead.
152
- (dntShim.dntGlobalThis as Record<string, unknown>).__vfGetAgent = getAgent;
153
- (dntShim.dntGlobalThis as Record<string, unknown>).__vfRegisterAgent = registerAgent;
154
- (dntShim.dntGlobalThis as Record<string, unknown>).__vfGetAllAgentIds = getAllAgentIds;
152
+ // Use Object.defineProperty to prevent accidental overwriting or enumeration.
153
+ for (
154
+ const [key, value] of Object.entries({
155
+ __vfGetAgent: getAgent,
156
+ __vfRegisterAgent: registerAgent,
157
+ __vfGetAllAgentIds: getAllAgentIds,
158
+ })
159
+ ) {
160
+ Object.defineProperty(dntShim.dntGlobalThis, key, {
161
+ value,
162
+ writable: false,
163
+ enumerable: false,
164
+ configurable: false,
165
+ });
166
+ }
155
167
 
156
168
  export function getAgentsAsTools(descriptions?: Record<string, string>): Record<string, Tool> {
157
169
  const tools: Record<string, Tool> = {};
@@ -160,7 +160,7 @@ export function agent(config: AgentConfig): Agent {
160
160
  generate(input): Promise<AgentResponse> {
161
161
  return withSpan(
162
162
  "agent.factory.generate",
163
- () => runtime.generate(input.input, input.context, input.model),
163
+ () => runtime.generate(input.input, input.context, input.model, input.maxOutputTokens),
164
164
  { "agent.id": id },
165
165
  );
166
166
  },
@@ -179,10 +179,16 @@ export function agent(config: AgentConfig): Agent {
179
179
  ]
180
180
  : (input.messages ?? []);
181
181
 
182
- const stream = await runtime.stream(inputMessages, input.context, {
183
- onToolCall: input.onToolCall,
184
- onChunk: input.onChunk,
185
- }, input.model);
182
+ const stream = await runtime.stream(
183
+ inputMessages,
184
+ input.context,
185
+ {
186
+ onToolCall: input.onToolCall,
187
+ onChunk: input.onChunk,
188
+ },
189
+ input.model,
190
+ input.maxOutputTokens,
191
+ );
186
192
 
187
193
  return createAgentStreamResult(stream);
188
194
  },
@@ -198,6 +204,7 @@ export function agent(config: AgentConfig): Agent {
198
204
  messages?: Message[];
199
205
  context?: Record<string, unknown>;
200
206
  model?: string;
207
+ maxOutputTokens?: number;
201
208
  } = await request.json();
202
209
 
203
210
  // Validate model override against allowlist when configured
@@ -216,7 +223,13 @@ export function agent(config: AgentConfig): Agent {
216
223
  }
217
224
 
218
225
  const messages = body.messages ?? [];
219
- const stream = await runtime.stream(messages, body.context, undefined, modelOverride);
226
+ const stream = await runtime.stream(
227
+ messages,
228
+ body.context,
229
+ undefined,
230
+ modelOverride,
231
+ body.maxOutputTokens,
232
+ );
220
233
 
221
234
  return new dntShim.Response(stream, { headers: STREAMING_HEADERS });
222
235
  },
@@ -0,0 +1,288 @@
1
+ import type { AgentContext, AgentResponse } from "../../types.js";
2
+ import { createError, toError } from "../../../errors/veryfront-error.js";
3
+
4
+ export interface SecurityConfig {
5
+ /** Input validation rules */
6
+ input?: {
7
+ /** Maximum input length */
8
+ maxLength?: number;
9
+
10
+ /** Blocked patterns (regex) */
11
+ blockedPatterns?: RegExp[];
12
+
13
+ /** Sanitize input */
14
+ sanitize?: boolean;
15
+
16
+ /** Custom validator */
17
+ validate?: (input: string) => boolean | Promise<boolean>;
18
+ };
19
+
20
+ /** Output filtering rules */
21
+ output?: {
22
+ /** Blocked patterns in output */
23
+ blockedPatterns?: RegExp[];
24
+
25
+ /** Filter PII (Personal Identifiable Information) */
26
+ filterPII?: boolean;
27
+
28
+ /** Custom filter */
29
+ filter?: (output: string) => string | Promise<string>;
30
+ };
31
+
32
+ /** Action when violation detected */
33
+ onViolation?: (violation: SecurityViolation) => void;
34
+ }
35
+
36
+ export interface SecurityViolation {
37
+ /** Violation type */
38
+ type: "input" | "output";
39
+
40
+ /** Violation reason */
41
+ reason: string;
42
+
43
+ /** Original content */
44
+ content: string;
45
+
46
+ /** Matched pattern (if any) */
47
+ pattern?: RegExp;
48
+ }
49
+
50
+ /**
51
+ * Common blocked patterns
52
+ */
53
+ export const COMMON_BLOCKED_PATTERNS = {
54
+ /** Prompt injection attempts */
55
+ promptInjection: [
56
+ /ignore\s+previous\s+instructions/i,
57
+ /ignore\s+all\s+previous\s+prompts/i,
58
+ /you\s+are\s+now\s+a/i,
59
+ /pretend\s+you\s+are/i,
60
+ /system:\s*/i,
61
+ /<\|im_start\|>/i,
62
+ /<\|im_end\|>/i,
63
+ ],
64
+
65
+ /** Potential data exfiltration */
66
+ dataExfiltration: [
67
+ /password/i,
68
+ /api[_\s-]?key/i,
69
+ /secret/i,
70
+ /token/i,
71
+ /credit\s+card/i,
72
+ ],
73
+
74
+ /** SQL injection patterns */
75
+ sqlInjection: [
76
+ /(\bUNION\b|\bSELECT\b).*\bFROM\b/i,
77
+ /;\s*(DROP|DELETE|UPDATE|INSERT)/i,
78
+ ],
79
+
80
+ /** XSS patterns */
81
+ xss: [
82
+ /<script[^>]*>.*?<\/script>/gi,
83
+ /javascript:/i,
84
+ /on\w+\s*=/i, // Event handlers
85
+ ],
86
+ };
87
+
88
+ /**
89
+ * PII patterns with replacement labels
90
+ */
91
+ const PII_REPLACEMENTS: Array<{ pattern: RegExp; label: string }> = [
92
+ { pattern: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi, label: "[EMAIL]" },
93
+ {
94
+ pattern: /\b(\+\d{1,3}[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b/g,
95
+ label: "[PHONE]",
96
+ },
97
+ { pattern: /\b\d{3}-\d{2}-\d{4}\b/g, label: "[SSN]" },
98
+ { pattern: /\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/g, label: "[CREDIT_CARD]" },
99
+ ];
100
+
101
+ /**
102
+ * Input Validator
103
+ */
104
+ export class InputValidator {
105
+ private config: NonNullable<SecurityConfig["input"]>;
106
+
107
+ constructor(config?: SecurityConfig["input"]) {
108
+ this.config = config ?? {};
109
+ }
110
+
111
+ /**
112
+ * Validate input
113
+ */
114
+ async validate(input: string): Promise<{
115
+ valid: boolean;
116
+ sanitized?: string;
117
+ violations: SecurityViolation[];
118
+ }> {
119
+ const violations: SecurityViolation[] = [];
120
+
121
+ const maxLength = this.config.maxLength;
122
+ if (maxLength != null && input.length > maxLength) {
123
+ violations.push({
124
+ type: "input",
125
+ reason: `Input exceeds maximum length of ${maxLength}`,
126
+ content: `${input.substring(0, 100)}...`,
127
+ });
128
+ }
129
+
130
+ for (const pattern of this.config.blockedPatterns ?? []) {
131
+ if (!pattern.test(input)) continue;
132
+
133
+ violations.push({
134
+ type: "input",
135
+ reason: "Input matches blocked pattern",
136
+ content: input,
137
+ pattern,
138
+ });
139
+ }
140
+
141
+ const customValidate = this.config.validate;
142
+ if (customValidate) {
143
+ const customValid = await customValidate(input);
144
+ if (!customValid) {
145
+ violations.push({
146
+ type: "input",
147
+ reason: "Custom validation failed",
148
+ content: input,
149
+ });
150
+ }
151
+ }
152
+
153
+ const sanitized = this.config.sanitize ? this.sanitizeInput(input) : undefined;
154
+
155
+ return {
156
+ valid: violations.length === 0,
157
+ sanitized,
158
+ violations,
159
+ };
160
+ }
161
+
162
+ /** Sanitization patterns to remove harmful content */
163
+ private static readonly SANITIZE_PATTERNS: RegExp[] = [
164
+ /<script[^>]*>.*?<\/script>/gi, // Script tags
165
+ /on\w+\s*=\s*["'][^"']*["']/gi, // Event handlers
166
+ /javascript:/gi, // JavaScript protocol
167
+ ];
168
+
169
+ /**
170
+ * Sanitize input (remove potentially harmful content)
171
+ */
172
+ private sanitizeInput(input: string): string {
173
+ return InputValidator.SANITIZE_PATTERNS.reduce(
174
+ (text, pattern) => text.replace(pattern, ""),
175
+ input,
176
+ );
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Output Filter
182
+ */
183
+ export class OutputFilter {
184
+ private config: NonNullable<SecurityConfig["output"]>;
185
+
186
+ constructor(config?: SecurityConfig["output"]) {
187
+ this.config = config ?? {};
188
+ }
189
+
190
+ /**
191
+ * Filter output
192
+ */
193
+ async filter(output: string): Promise<{
194
+ filtered: string;
195
+ violations: SecurityViolation[];
196
+ }> {
197
+ const violations: SecurityViolation[] = [];
198
+ let filtered = output;
199
+
200
+ for (const pattern of this.config.blockedPatterns ?? []) {
201
+ if (!pattern.test(filtered)) continue;
202
+
203
+ violations.push({
204
+ type: "output",
205
+ reason: "Output contains blocked pattern",
206
+ content: filtered,
207
+ pattern,
208
+ });
209
+
210
+ filtered = filtered.replace(pattern, "[REDACTED]");
211
+ }
212
+
213
+ if (this.config.filterPII) {
214
+ filtered = this.filterPII(filtered);
215
+ }
216
+
217
+ const customFilter = this.config.filter;
218
+ if (customFilter) {
219
+ filtered = await customFilter(filtered);
220
+ }
221
+
222
+ return { filtered, violations };
223
+ }
224
+
225
+ /**
226
+ * Filter PII from output
227
+ */
228
+ private filterPII(output: string): string {
229
+ return PII_REPLACEMENTS.reduce(
230
+ (text, { pattern, label }) => text.replace(pattern, label),
231
+ output,
232
+ );
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Report violations to the configured handler
238
+ */
239
+ function reportViolations(
240
+ violations: SecurityViolation[],
241
+ onViolation?: (violation: SecurityViolation) => void,
242
+ ): void {
243
+ if (!onViolation) return;
244
+ for (const violation of violations) onViolation(violation);
245
+ }
246
+
247
+ /**
248
+ * Create security middleware for agents
249
+ */
250
+ export function securityMiddleware(
251
+ config: SecurityConfig,
252
+ ): (context: AgentContext, next: () => Promise<AgentResponse>) => Promise<AgentResponse> {
253
+ const inputValidator = new InputValidator(config.input);
254
+ const outputFilter = new OutputFilter(config.output);
255
+
256
+ return async (
257
+ context: AgentContext,
258
+ next: () => Promise<AgentResponse>,
259
+ ): Promise<AgentResponse> => {
260
+ const inputString = typeof context.input === "string"
261
+ ? context.input
262
+ : JSON.stringify(context.input);
263
+ const inputValidation = await inputValidator.validate(inputString);
264
+
265
+ if (!inputValidation.valid) {
266
+ reportViolations(inputValidation.violations, config.onViolation);
267
+
268
+ const firstViolation = inputValidation.violations[0];
269
+ throw toError(
270
+ createError({
271
+ type: "agent",
272
+ message: `Input validation failed: ${firstViolation?.reason ?? "Unknown reason"}`,
273
+ }),
274
+ );
275
+ }
276
+
277
+ if (inputValidation.sanitized != null) {
278
+ context.input = inputValidation.sanitized;
279
+ }
280
+
281
+ const result = await next();
282
+
283
+ const outputFiltering = await outputFilter.filter(result.text);
284
+ reportViolations(outputFiltering.violations, config.onViolation);
285
+
286
+ return { ...result, text: outputFiltering.filtered };
287
+ };
288
+ }
@@ -194,6 +194,7 @@ export class AgentRuntime {
194
194
  input: string | Message[],
195
195
  context?: Record<string, unknown>,
196
196
  modelOverride?: string,
197
+ maxOutputTokensOverride?: number,
197
198
  ): Promise<AgentResponse> {
198
199
  const requestedModel = resolveConfiguredAgentModel(modelOverride || this.config.model);
199
200
  const resolvedModelString = maybeUpgradeLocalModel(requestedModel);
@@ -221,7 +222,13 @@ export class AgentRuntime {
221
222
  const chain = new MiddlewareChain(this.config.middleware);
222
223
  return chain.execute(
223
224
  agentContext,
224
- () => this.executeAgentLoop(systemPrompt, messages, resolvedModelString),
225
+ () =>
226
+ this.executeAgentLoop(
227
+ systemPrompt,
228
+ messages,
229
+ resolvedModelString,
230
+ maxOutputTokensOverride,
231
+ ),
225
232
  );
226
233
  });
227
234
  }
@@ -238,6 +245,7 @@ export class AgentRuntime {
238
245
  onChunk?: (chunk: string) => void;
239
246
  },
240
247
  modelOverride?: string,
248
+ maxOutputTokensOverride?: number,
241
249
  ): Promise<ReadableStream<Uint8Array>> {
242
250
  const requestedModel = resolveConfiguredAgentModel(modelOverride || this.config.model);
243
251
  // Auto-upgrade local/* to a cloud provider when API keys are available.
@@ -300,6 +308,7 @@ export class AgentRuntime {
300
308
  toolContext,
301
309
  resolvedModelString,
302
310
  languageModel,
311
+ maxOutputTokensOverride,
303
312
  );
304
313
 
305
314
  sendSSE(controller, encoder, { type: "text-end", id: textPartId });
@@ -325,6 +334,7 @@ export class AgentRuntime {
325
334
  systemPrompt: string,
326
335
  messages: Message[],
327
336
  modelString?: string,
337
+ maxOutputTokensOverride?: number,
328
338
  ): Promise<AgentResponse> {
329
339
  return withSpan("agent.execution_loop", async (loopSpan) => {
330
340
  const { maxAgentSteps } = getPlatformCapabilities();
@@ -373,7 +383,7 @@ export class AgentRuntime {
373
383
  system: systemPrompt,
374
384
  messages: convertToModelMessages(currentMessages),
375
385
  tools: convertToolsToAISDK(tools),
376
- maxOutputTokens: this.config.memory?.maxTokens ?? DEFAULT_MAX_TOKENS,
386
+ maxOutputTokens: this.resolveMaxOutputTokens(maxOutputTokensOverride),
377
387
  temperature: DEFAULT_TEMPERATURE,
378
388
  });
379
389
  });
@@ -556,6 +566,7 @@ export class AgentRuntime {
556
566
  toolContext?: Record<string, unknown>,
557
567
  modelString?: string,
558
568
  resolvedModel?: LanguageModel,
569
+ maxOutputTokensOverride?: number,
559
570
  ): Promise<AgentResponse> {
560
571
  const { maxAgentSteps } = getPlatformCapabilities();
561
572
  const maxSteps = this.computeMaxSteps(maxAgentSteps);
@@ -597,7 +608,7 @@ export class AgentRuntime {
597
608
  system: systemPrompt,
598
609
  messages: convertToModelMessages(currentMessages),
599
610
  tools: convertToolsToAISDK(tools),
600
- maxOutputTokens: this.config.memory?.maxTokens ?? DEFAULT_MAX_TOKENS,
611
+ maxOutputTokens: this.resolveMaxOutputTokens(maxOutputTokensOverride),
601
612
  temperature: DEFAULT_TEMPERATURE,
602
613
  });
603
614
 
@@ -818,6 +829,18 @@ export class AgentRuntime {
818
829
  return getMaxSteps(this.config.maxSteps, edgeMaxSteps, platformLimit);
819
830
  }
820
831
 
832
+ private resolveMaxOutputTokens(maxOutputTokensOverride?: number): number {
833
+ if (
834
+ typeof maxOutputTokensOverride === "number" &&
835
+ Number.isFinite(maxOutputTokensOverride) &&
836
+ maxOutputTokensOverride > 0
837
+ ) {
838
+ return Math.floor(maxOutputTokensOverride);
839
+ }
840
+
841
+ return this.config.memory?.maxTokens ?? DEFAULT_MAX_TOKENS;
842
+ }
843
+
821
844
  /**
822
845
  * Get memory instance (for advanced use cases)
823
846
  */
@@ -134,6 +134,8 @@ export interface Agent {
134
134
  context?: Record<string, unknown>;
135
135
  /** Override the agent's default model for this request. Must be in `allowedModels` if configured. */
136
136
  model?: ModelString;
137
+ /** Override the maximum model output tokens for this request. */
138
+ maxOutputTokens?: number;
137
139
  }): Promise<AgentResponse>;
138
140
 
139
141
  stream(input: {
@@ -142,6 +144,8 @@ export interface Agent {
142
144
  context?: Record<string, unknown>;
143
145
  /** Override the agent's default model for this request. Must be in `allowedModels` if configured. */
144
146
  model?: ModelString;
147
+ /** Override the maximum model output tokens for this request. */
148
+ maxOutputTokens?: number;
145
149
  onToolCall?: (toolCall: ToolCall) => void;
146
150
  onChunk?: (chunk: string) => void;
147
151
  }): Promise<AgentStreamResult>;