From 628c0a065f783683adcdcd30ed12e60b1a90c668 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Nov 2025 07:53:12 +0000 Subject: [PATCH 1/5] Initial plan From e99d33117349ef00c1366787e49a39ebb2c6e41b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Nov 2025 08:07:16 +0000 Subject: [PATCH 2/5] Add CallSettings properties to DurableAgent Port easier properties from AI SDK Agent class (temperature, maxOutputTokens, topP, topK, presencePenalty, frequencyPenalty, stopSequences, seed) to bring DurableAgent closer to functional parity with ToolLoopAgent/Agent class. Co-authored-by: pranaygp <1797812+pranaygp@users.noreply.github.com> --- packages/ai/src/agent/do-stream-step.ts | 20 ++- packages/ai/src/agent/durable-agent.test.ts | 169 ++++++++++++++++++ packages/ai/src/agent/durable-agent.ts | 85 +++++++++ packages/ai/src/agent/stream-text-iterator.ts | 28 ++- 4 files changed, 300 insertions(+), 2 deletions(-) create mode 100644 packages/ai/src/agent/durable-agent.test.ts diff --git a/packages/ai/src/agent/do-stream-step.ts b/packages/ai/src/agent/do-stream-step.ts index 768910800..43f33a200 100644 --- a/packages/ai/src/agent/do-stream-step.ts +++ b/packages/ai/src/agent/do-stream-step.ts @@ -12,7 +12,17 @@ export async function doStreamStep( conversationPrompt: LanguageModelV2Prompt, modelId: string, writable: WritableStream, - tools?: LanguageModelV2CallOptions['tools'] + tools?: LanguageModelV2CallOptions['tools'], + callOptions?: { + temperature?: number; + maxOutputTokens?: number; + topP?: number; + topK?: number; + presencePenalty?: number; + frequencyPenalty?: number; + stopSequences?: string[]; + seed?: number; + } ) { 'use step'; @@ -20,6 +30,14 @@ export async function doStreamStep( const result = await model.doStream({ prompt: conversationPrompt, tools, + temperature: callOptions?.temperature, + maxOutputTokens: callOptions?.maxOutputTokens, + topP: callOptions?.topP, + topK: callOptions?.topK, + presencePenalty: callOptions?.presencePenalty, + frequencyPenalty: callOptions?.frequencyPenalty, + stopSequences: callOptions?.stopSequences, + seed: callOptions?.seed, }); let finish: FinishPart | undefined; diff --git a/packages/ai/src/agent/durable-agent.test.ts b/packages/ai/src/agent/durable-agent.test.ts new file mode 100644 index 000000000..e0aa1fac5 --- /dev/null +++ b/packages/ai/src/agent/durable-agent.test.ts @@ -0,0 +1,169 @@ +/** + * Tests for DurableAgent + * + * These tests verify that the DurableAgent constructor properly accepts + * and stores configuration options from the AI SDK Agent class. + */ +import { describe, expect, it } from 'vitest'; +import { DurableAgent } from './durable-agent.js'; + +describe('DurableAgent', () => { + describe('constructor', () => { + it('should accept basic required options', () => { + const agent = new DurableAgent({ + model: 'anthropic/claude-opus', + tools: {}, + }); + + expect(agent).toBeDefined(); + }); + + it('should accept system prompt', () => { + const agent = new DurableAgent({ + model: 'anthropic/claude-opus', + tools: {}, + system: 'You are a helpful assistant.', + }); + + expect(agent).toBeDefined(); + }); + + it('should accept temperature option', () => { + const agent = new DurableAgent({ + model: 'anthropic/claude-opus', + tools: {}, + temperature: 0.7, + }); + + expect(agent).toBeDefined(); + }); + + it('should accept maxOutputTokens option', () => { + const agent = new DurableAgent({ + model: 'anthropic/claude-opus', + tools: {}, + maxOutputTokens: 1000, + }); + + expect(agent).toBeDefined(); + }); + + it('should accept topP option', () => { + const agent = new DurableAgent({ + model: 'anthropic/claude-opus', + tools: {}, + topP: 0.9, + }); + + expect(agent).toBeDefined(); + }); + + it('should accept topK option', () => { + const agent = new DurableAgent({ + model: 'anthropic/claude-opus', + tools: {}, + topK: 40, + }); + + expect(agent).toBeDefined(); + }); + + it('should accept presencePenalty option', () => { + const agent = new DurableAgent({ + model: 'anthropic/claude-opus', + tools: {}, + presencePenalty: 0.5, + }); + + expect(agent).toBeDefined(); + }); + + it('should accept frequencyPenalty option', () => { + const agent = new DurableAgent({ + model: 'anthropic/claude-opus', + tools: {}, + frequencyPenalty: 0.5, + }); + + expect(agent).toBeDefined(); + }); + + it('should accept stopSequences option', () => { + const agent = new DurableAgent({ + model: 'anthropic/claude-opus', + tools: {}, + stopSequences: ['STOP', 'END'], + }); + + expect(agent).toBeDefined(); + }); + + it('should accept seed option', () => { + const agent = new DurableAgent({ + model: 'anthropic/claude-opus', + tools: {}, + seed: 12345, + }); + + expect(agent).toBeDefined(); + }); + + it('should accept all options together', () => { + const agent = new DurableAgent({ + model: 'anthropic/claude-opus', + tools: {}, + system: 'You are a helpful assistant.', + temperature: 0.7, + maxOutputTokens: 1000, + topP: 0.9, + topK: 40, + presencePenalty: 0.5, + frequencyPenalty: 0.3, + stopSequences: ['STOP', 'END'], + seed: 12345, + }); + + expect(agent).toBeDefined(); + }); + + it('should accept tools with proper structure', () => { + const agent = new DurableAgent({ + model: 'anthropic/claude-opus', + tools: { + testTool: { + description: 'A test tool', + inputSchema: { + type: 'object', + properties: {}, + }, + execute: async () => 'result', + }, + }, + }); + + expect(agent).toBeDefined(); + }); + }); + + describe('methods', () => { + it('should have generate method', () => { + const agent = new DurableAgent({ + model: 'anthropic/claude-opus', + tools: {}, + }); + + expect(agent.generate).toBeDefined(); + expect(typeof agent.generate).toBe('function'); + }); + + it('should have stream method', () => { + const agent = new DurableAgent({ + model: 'anthropic/claude-opus', + tools: {}, + }); + + expect(agent.stream).toBeDefined(); + expect(typeof agent.stream).toBe('function'); + }); + }); +}); diff --git a/packages/ai/src/agent/durable-agent.ts b/packages/ai/src/agent/durable-agent.ts index 7c938dc59..f94d8a4a8 100644 --- a/packages/ai/src/agent/durable-agent.ts +++ b/packages/ai/src/agent/durable-agent.ts @@ -33,6 +33,67 @@ export interface DurableAgentOptions { * Optional system prompt to guide the agent's behavior. */ system?: string; + + /** + * Temperature setting. The range depends on the provider and model. + * + * It is recommended to set either `temperature` or `topP`, but not both. + */ + temperature?: number; + + /** + * Maximum number of tokens to generate. + */ + maxOutputTokens?: number; + + /** + * Nucleus sampling. This is a number between 0 and 1. + * + * E.g. 0.1 would mean that only tokens with the top 10% probability mass + * are considered. + * + * It is recommended to set either `temperature` or `topP`, but not both. + */ + topP?: number; + + /** + * Only sample from the top K options for each subsequent token. + * + * Used to remove "long tail" low probability responses. + * Recommended for advanced use cases only. You usually only need to use temperature. + */ + topK?: number; + + /** + * Presence penalty setting. It affects the likelihood of the model to + * repeat information that is already in the prompt. + * + * The presence penalty is a number between -1 (increase repetition) + * and 1 (maximum penalty, decrease repetition). 0 means no penalty. + */ + presencePenalty?: number; + + /** + * Frequency penalty setting. It affects the likelihood of the model + * to repeatedly use the same words or phrases. + * + * The frequency penalty is a number between -1 (increase repetition) + * and 1 (maximum penalty, decrease repetition). 0 means no penalty. + */ + frequencyPenalty?: number; + + /** + * Stop sequences. + * If set, the model will stop generating text when one of the stop sequences is generated. + * Providers may have limits on the number of stop sequences. + */ + stopSequences?: string[]; + + /** + * The seed (integer) to use for random sampling. If set and supported + * by the model, calls will generate deterministic results. + */ + seed?: number; } /** @@ -92,11 +153,27 @@ export class DurableAgent { private model: string; private tools: ToolSet; private system?: string; + private temperature?: number; + private maxOutputTokens?: number; + private topP?: number; + private topK?: number; + private presencePenalty?: number; + private frequencyPenalty?: number; + private stopSequences?: string[]; + private seed?: number; constructor(options: DurableAgentOptions) { this.model = options.model; this.tools = options.tools; this.system = options.system; + this.temperature = options.temperature; + this.maxOutputTokens = options.maxOutputTokens; + this.topP = options.topP; + this.topK = options.topK; + this.presencePenalty = options.presencePenalty; + this.frequencyPenalty = options.frequencyPenalty; + this.stopSequences = options.stopSequences; + this.seed = options.seed; } generate() { @@ -124,6 +201,14 @@ export class DurableAgent { tools: this.tools, writable, prompt: modelPrompt, + temperature: this.temperature, + maxOutputTokens: this.maxOutputTokens, + topP: this.topP, + topK: this.topK, + presencePenalty: this.presencePenalty, + frequencyPenalty: this.frequencyPenalty, + stopSequences: this.stopSequences, + seed: this.seed, }); let result = await iterator.next(); diff --git a/packages/ai/src/agent/stream-text-iterator.ts b/packages/ai/src/agent/stream-text-iterator.ts index 1b4ef5ea8..e2d83a742 100644 --- a/packages/ai/src/agent/stream-text-iterator.ts +++ b/packages/ai/src/agent/stream-text-iterator.ts @@ -13,11 +13,27 @@ export async function* streamTextIterator({ tools = {}, writable, model, + temperature, + maxOutputTokens, + topP, + topK, + presencePenalty, + frequencyPenalty, + stopSequences, + seed, }: { prompt: LanguageModelV2Prompt; tools: ToolSet; writable: WritableStream; model: string; + temperature?: number; + maxOutputTokens?: number; + topP?: number; + topK?: number; + presencePenalty?: number; + frequencyPenalty?: number; + stopSequences?: string[]; + seed?: number; }): AsyncGenerator< LanguageModelV2ToolCall[], void, @@ -31,7 +47,17 @@ export async function* streamTextIterator({ conversationPrompt, model, writable, - toolsToModelTools(tools) + toolsToModelTools(tools), + { + temperature, + maxOutputTokens, + topP, + topK, + presencePenalty, + frequencyPenalty, + stopSequences, + seed, + } ); if (finish?.finishReason === 'tool-calls') { From 17eb5016b509bb266ec72a2f4c2919ecd89d7e68 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Nov 2025 08:08:45 +0000 Subject: [PATCH 3/5] Update DurableAgent documentation with new properties Add examples showing the new CallSettings properties (temperature, maxOutputTokens, topP, etc.) in the DurableAgent documentation. Co-authored-by: pranaygp <1797812+pranaygp@users.noreply.github.com> --- .../workflow-ai/durable-agent.mdx | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/docs/content/docs/api-reference/workflow-ai/durable-agent.mdx b/docs/content/docs/api-reference/workflow-ai/durable-agent.mdx index 2fc4812c8..331c835b8 100644 --- a/docs/content/docs/api-reference/workflow-ai/durable-agent.mdx +++ b/docs/content/docs/api-reference/workflow-ai/durable-agent.mdx @@ -29,6 +29,8 @@ async function myAgent() { const agent = new DurableAgent({ model: 'anthropic/claude-haiku-4.5', system: 'You are a helpful weather assistant.', + temperature: 0.7, // Control output randomness + maxOutputTokens: 1000, // Limit response length tools: { getWeather: { description: 'Get weather for a city', @@ -177,6 +179,52 @@ async function multiToolAgentWorkflow(userQuery: string) { } ``` +### Advanced Configuration + +```typescript +import { DurableAgent } from '@workflow/ai/agent'; +import { z } from 'zod'; + +async function calculateResult({ formula }: { formula: string }) { + "use step"; + // Perform calculation + return "42"; +} + +async function advancedAgentWorkflow(userQuery: string) { + 'use workflow'; + + const agent = new DurableAgent({ + model: 'anthropic/claude-haiku-4.5', + system: 'You are a precise calculator assistant.', + // Model behavior controls + temperature: 0.3, // Lower temperature for more deterministic responses + maxOutputTokens: 500, // Limit response length + topP: 0.9, // Nucleus sampling for response variety + presencePenalty: 0.2, // Reduce repetition + frequencyPenalty: 0.2, // Reduce word repetition + seed: 12345, // For reproducible results + stopSequences: ['END'], // Stop generation at specific sequences + tools: { + calculateResult: { + description: 'Calculate a mathematical result', + inputSchema: z.object({ formula: z.string() }), + execute: calculateResult, + }, + }, + }); + + await agent.stream({ + messages: [ + { + role: 'user', + content: userQuery, + }, + ], + }); +} +``` + ### Tools with Workflow Library Features ```typescript From cb5acbd322512aaa38945bd5e9c09fd869a4e122 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Nov 2025 07:52:54 +0000 Subject: [PATCH 4/5] Strengthen test assertions to verify properties are stored Instead of just checking that the agent is defined, tests now verify that each property value is correctly stored on the class instance using type assertions to access private properties. Co-authored-by: TooTallNate <71256+TooTallNate@users.noreply.github.com> --- packages/ai/src/agent/durable-agent.test.ts | 44 ++++++++++++++++----- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/packages/ai/src/agent/durable-agent.test.ts b/packages/ai/src/agent/durable-agent.test.ts index e0aa1fac5..24bf0fab5 100644 --- a/packages/ai/src/agent/durable-agent.test.ts +++ b/packages/ai/src/agent/durable-agent.test.ts @@ -16,6 +16,8 @@ describe('DurableAgent', () => { }); expect(agent).toBeDefined(); + expect((agent as any).model).toBe('anthropic/claude-opus'); + expect((agent as any).tools).toEqual({}); }); it('should accept system prompt', () => { @@ -26,6 +28,7 @@ describe('DurableAgent', () => { }); expect(agent).toBeDefined(); + expect((agent as any).system).toBe('You are a helpful assistant.'); }); it('should accept temperature option', () => { @@ -36,6 +39,7 @@ describe('DurableAgent', () => { }); expect(agent).toBeDefined(); + expect((agent as any).temperature).toBe(0.7); }); it('should accept maxOutputTokens option', () => { @@ -46,6 +50,7 @@ describe('DurableAgent', () => { }); expect(agent).toBeDefined(); + expect((agent as any).maxOutputTokens).toBe(1000); }); it('should accept topP option', () => { @@ -56,6 +61,7 @@ describe('DurableAgent', () => { }); expect(agent).toBeDefined(); + expect((agent as any).topP).toBe(0.9); }); it('should accept topK option', () => { @@ -66,6 +72,7 @@ describe('DurableAgent', () => { }); expect(agent).toBeDefined(); + expect((agent as any).topK).toBe(40); }); it('should accept presencePenalty option', () => { @@ -76,6 +83,7 @@ describe('DurableAgent', () => { }); expect(agent).toBeDefined(); + expect((agent as any).presencePenalty).toBe(0.5); }); it('should accept frequencyPenalty option', () => { @@ -86,6 +94,7 @@ describe('DurableAgent', () => { }); expect(agent).toBeDefined(); + expect((agent as any).frequencyPenalty).toBe(0.5); }); it('should accept stopSequences option', () => { @@ -96,6 +105,7 @@ describe('DurableAgent', () => { }); expect(agent).toBeDefined(); + expect((agent as any).stopSequences).toEqual(['STOP', 'END']); }); it('should accept seed option', () => { @@ -106,6 +116,7 @@ describe('DurableAgent', () => { }); expect(agent).toBeDefined(); + expect((agent as any).seed).toBe(12345); }); it('should accept all options together', () => { @@ -124,24 +135,37 @@ describe('DurableAgent', () => { }); expect(agent).toBeDefined(); + expect((agent as any).model).toBe('anthropic/claude-opus'); + expect((agent as any).system).toBe('You are a helpful assistant.'); + expect((agent as any).temperature).toBe(0.7); + expect((agent as any).maxOutputTokens).toBe(1000); + expect((agent as any).topP).toBe(0.9); + expect((agent as any).topK).toBe(40); + expect((agent as any).presencePenalty).toBe(0.5); + expect((agent as any).frequencyPenalty).toBe(0.3); + expect((agent as any).stopSequences).toEqual(['STOP', 'END']); + expect((agent as any).seed).toBe(12345); }); it('should accept tools with proper structure', () => { - const agent = new DurableAgent({ - model: 'anthropic/claude-opus', - tools: { - testTool: { - description: 'A test tool', - inputSchema: { - type: 'object', - properties: {}, - }, - execute: async () => 'result', + const tools = { + testTool: { + description: 'A test tool', + inputSchema: { + type: 'object', + properties: {}, }, + execute: async () => 'result', }, + }; + + const agent = new DurableAgent({ + model: 'anthropic/claude-opus', + tools, }); expect(agent).toBeDefined(); + expect((agent as any).tools).toBe(tools); }); }); From 528f3ed6563c95293e3c3a582fcc82991e811f51 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Nov 2025 22:09:26 +0000 Subject: [PATCH 5/5] Remove private modifiers from DurableAgent properties Remove private modifiers from class properties and update tests to access properties directly without type assertions, per code review feedback. Co-authored-by: TooTallNate <71256+TooTallNate@users.noreply.github.com> --- packages/ai/src/agent/durable-agent.test.ts | 44 ++++++++++----------- packages/ai/src/agent/durable-agent.ts | 22 +++++------ 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/packages/ai/src/agent/durable-agent.test.ts b/packages/ai/src/agent/durable-agent.test.ts index 24bf0fab5..341d9839a 100644 --- a/packages/ai/src/agent/durable-agent.test.ts +++ b/packages/ai/src/agent/durable-agent.test.ts @@ -16,8 +16,8 @@ describe('DurableAgent', () => { }); expect(agent).toBeDefined(); - expect((agent as any).model).toBe('anthropic/claude-opus'); - expect((agent as any).tools).toEqual({}); + expect(agent.model).toBe('anthropic/claude-opus'); + expect(agent.tools).toEqual({}); }); it('should accept system prompt', () => { @@ -28,7 +28,7 @@ describe('DurableAgent', () => { }); expect(agent).toBeDefined(); - expect((agent as any).system).toBe('You are a helpful assistant.'); + expect(agent.system).toBe('You are a helpful assistant.'); }); it('should accept temperature option', () => { @@ -39,7 +39,7 @@ describe('DurableAgent', () => { }); expect(agent).toBeDefined(); - expect((agent as any).temperature).toBe(0.7); + expect(agent.temperature).toBe(0.7); }); it('should accept maxOutputTokens option', () => { @@ -50,7 +50,7 @@ describe('DurableAgent', () => { }); expect(agent).toBeDefined(); - expect((agent as any).maxOutputTokens).toBe(1000); + expect(agent.maxOutputTokens).toBe(1000); }); it('should accept topP option', () => { @@ -61,7 +61,7 @@ describe('DurableAgent', () => { }); expect(agent).toBeDefined(); - expect((agent as any).topP).toBe(0.9); + expect(agent.topP).toBe(0.9); }); it('should accept topK option', () => { @@ -72,7 +72,7 @@ describe('DurableAgent', () => { }); expect(agent).toBeDefined(); - expect((agent as any).topK).toBe(40); + expect(agent.topK).toBe(40); }); it('should accept presencePenalty option', () => { @@ -83,7 +83,7 @@ describe('DurableAgent', () => { }); expect(agent).toBeDefined(); - expect((agent as any).presencePenalty).toBe(0.5); + expect(agent.presencePenalty).toBe(0.5); }); it('should accept frequencyPenalty option', () => { @@ -94,7 +94,7 @@ describe('DurableAgent', () => { }); expect(agent).toBeDefined(); - expect((agent as any).frequencyPenalty).toBe(0.5); + expect(agent.frequencyPenalty).toBe(0.5); }); it('should accept stopSequences option', () => { @@ -105,7 +105,7 @@ describe('DurableAgent', () => { }); expect(agent).toBeDefined(); - expect((agent as any).stopSequences).toEqual(['STOP', 'END']); + expect(agent.stopSequences).toEqual(['STOP', 'END']); }); it('should accept seed option', () => { @@ -116,7 +116,7 @@ describe('DurableAgent', () => { }); expect(agent).toBeDefined(); - expect((agent as any).seed).toBe(12345); + expect(agent.seed).toBe(12345); }); it('should accept all options together', () => { @@ -135,16 +135,16 @@ describe('DurableAgent', () => { }); expect(agent).toBeDefined(); - expect((agent as any).model).toBe('anthropic/claude-opus'); - expect((agent as any).system).toBe('You are a helpful assistant.'); - expect((agent as any).temperature).toBe(0.7); - expect((agent as any).maxOutputTokens).toBe(1000); - expect((agent as any).topP).toBe(0.9); - expect((agent as any).topK).toBe(40); - expect((agent as any).presencePenalty).toBe(0.5); - expect((agent as any).frequencyPenalty).toBe(0.3); - expect((agent as any).stopSequences).toEqual(['STOP', 'END']); - expect((agent as any).seed).toBe(12345); + expect(agent.model).toBe('anthropic/claude-opus'); + expect(agent.system).toBe('You are a helpful assistant.'); + expect(agent.temperature).toBe(0.7); + expect(agent.maxOutputTokens).toBe(1000); + expect(agent.topP).toBe(0.9); + expect(agent.topK).toBe(40); + expect(agent.presencePenalty).toBe(0.5); + expect(agent.frequencyPenalty).toBe(0.3); + expect(agent.stopSequences).toEqual(['STOP', 'END']); + expect(agent.seed).toBe(12345); }); it('should accept tools with proper structure', () => { @@ -165,7 +165,7 @@ describe('DurableAgent', () => { }); expect(agent).toBeDefined(); - expect((agent as any).tools).toBe(tools); + expect(agent.tools).toBe(tools); }); }); diff --git a/packages/ai/src/agent/durable-agent.ts b/packages/ai/src/agent/durable-agent.ts index f94d8a4a8..87b2ac8a0 100644 --- a/packages/ai/src/agent/durable-agent.ts +++ b/packages/ai/src/agent/durable-agent.ts @@ -150,17 +150,17 @@ export interface DurableAgentStreamOptions { * ``` */ export class DurableAgent { - private model: string; - private tools: ToolSet; - private system?: string; - private temperature?: number; - private maxOutputTokens?: number; - private topP?: number; - private topK?: number; - private presencePenalty?: number; - private frequencyPenalty?: number; - private stopSequences?: string[]; - private seed?: number; + model: string; + tools: ToolSet; + system?: string; + temperature?: number; + maxOutputTokens?: number; + topP?: number; + topK?: number; + presencePenalty?: number; + frequencyPenalty?: number; + stopSequences?: string[]; + seed?: number; constructor(options: DurableAgentOptions) { this.model = options.model;