Skip to content
Draft
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
48 changes: 48 additions & 0 deletions docs/content/docs/api-reference/workflow-ai/durable-agent.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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
Expand Down
20 changes: 19 additions & 1 deletion packages/ai/src/agent/do-stream-step.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,32 @@ export async function doStreamStep(
conversationPrompt: LanguageModelV2Prompt,
modelId: string,
writable: WritableStream<UIMessageChunk>,
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';

const model = gateway(modelId);
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;
Expand Down
193 changes: 193 additions & 0 deletions packages/ai/src/agent/durable-agent.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
/**
* 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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot This (and all similar ones in the rest of the tests) is a weak assertion. Can we test that the relevant property actually exists on the class instance?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in cb5acbd. Tests now verify that each property value is correctly stored on the class instance using type assertions (as any) to access the private properties.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot Instead of casting to any, how about we just drop the private statements on the class definition itself. private in TS is kind of dumb anyways.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 528f3ed. Removed private modifiers from all DurableAgent properties and updated tests to access properties directly without type assertions.

expect(agent.model).toBe('anthropic/claude-opus');
expect(agent.tools).toEqual({});
});

it('should accept system prompt', () => {
const agent = new DurableAgent({
model: 'anthropic/claude-opus',
tools: {},
system: 'You are a helpful assistant.',
});

expect(agent).toBeDefined();
expect(agent.system).toBe('You are a helpful assistant.');
});

it('should accept temperature option', () => {
const agent = new DurableAgent({
model: 'anthropic/claude-opus',
tools: {},
temperature: 0.7,
});

expect(agent).toBeDefined();
expect(agent.temperature).toBe(0.7);
});

it('should accept maxOutputTokens option', () => {
const agent = new DurableAgent({
model: 'anthropic/claude-opus',
tools: {},
maxOutputTokens: 1000,
});

expect(agent).toBeDefined();
expect(agent.maxOutputTokens).toBe(1000);
});

it('should accept topP option', () => {
const agent = new DurableAgent({
model: 'anthropic/claude-opus',
tools: {},
topP: 0.9,
});

expect(agent).toBeDefined();
expect(agent.topP).toBe(0.9);
});

it('should accept topK option', () => {
const agent = new DurableAgent({
model: 'anthropic/claude-opus',
tools: {},
topK: 40,
});

expect(agent).toBeDefined();
expect(agent.topK).toBe(40);
});

it('should accept presencePenalty option', () => {
const agent = new DurableAgent({
model: 'anthropic/claude-opus',
tools: {},
presencePenalty: 0.5,
});

expect(agent).toBeDefined();
expect(agent.presencePenalty).toBe(0.5);
});

it('should accept frequencyPenalty option', () => {
const agent = new DurableAgent({
model: 'anthropic/claude-opus',
tools: {},
frequencyPenalty: 0.5,
});

expect(agent).toBeDefined();
expect(agent.frequencyPenalty).toBe(0.5);
});

it('should accept stopSequences option', () => {
const agent = new DurableAgent({
model: 'anthropic/claude-opus',
tools: {},
stopSequences: ['STOP', 'END'],
});

expect(agent).toBeDefined();
expect(agent.stopSequences).toEqual(['STOP', 'END']);
});

it('should accept seed option', () => {
const agent = new DurableAgent({
model: 'anthropic/claude-opus',
tools: {},
seed: 12345,
});

expect(agent).toBeDefined();
expect(agent.seed).toBe(12345);
});

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();
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', () => {
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.tools).toBe(tools);
});
});

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');
});
});
});
Loading