@alveus-ai/std 0.1.0

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.
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Basic Chat Agent Factory
3
+ *
4
+ * Creates reusable chat agents with a configurable system prompt.
5
+ * The agent is model-agnostic - it works with any LLM provider.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * // Create a helpful assistant
10
+ * const assistant = createChatAgent('You are a helpful assistant.');
11
+ *
12
+ * // Create a code reviewer with specific model
13
+ * const reviewer = createChatAgent(
14
+ * 'You are a senior engineer reviewing code for bugs and improvements.',
15
+ * { model: 'gpt-4o', provider: 'openai' }
16
+ * );
17
+ *
18
+ * // Execute via runtime
19
+ * const result = await runtime.execute({
20
+ * hash: assistant.definition.hash,
21
+ * state: {},
22
+ * event: { message: 'Hello, how can you help me?' }
23
+ * });
24
+ * ```
25
+ */
26
+ import type { ChatOptions, ExecutionContext } from '@alveus-ai/core';
27
+ /**
28
+ * Input event for chat agents
29
+ */
30
+ export interface ChatInput {
31
+ /** The user's message */
32
+ message: string;
33
+ /** Optional conversation history */
34
+ history?: Array<{
35
+ role: 'user' | 'assistant';
36
+ content: string;
37
+ }>;
38
+ }
39
+ /**
40
+ * Output from chat agents
41
+ */
42
+ export interface ChatOutput {
43
+ /** The assistant's reply */
44
+ reply: string;
45
+ /** Provider used for this response */
46
+ provider?: string;
47
+ /** Model used for this response */
48
+ model?: string;
49
+ }
50
+ /**
51
+ * Options for creating a chat agent
52
+ */
53
+ export interface ChatAgentOptions extends ChatOptions {
54
+ /** Name for the agent (for debugging/logging) */
55
+ name?: string;
56
+ /** Description of the agent's purpose */
57
+ description?: string;
58
+ }
59
+ /**
60
+ * Create a chat agent with a configurable system prompt
61
+ *
62
+ * @param systemPrompt - The system prompt defining the agent's behavior
63
+ * @param options - Optional configuration for provider, model, etc.
64
+ * @returns An Agent that can be executed via any Alveus runtime
65
+ */
66
+ export declare function createChatAgent(systemPrompt: string, options?: ChatAgentOptions): import("@alveus-ai/core").Agent<Record<string, unknown>, ChatInput, ExecutionContext, ChatOutput>;
67
+ /**
68
+ * Create a JSON-structured chat agent
69
+ *
70
+ * Forces the LLM to respond with valid JSON, useful for structured outputs.
71
+ *
72
+ * @param systemPrompt - The system prompt (should describe the expected JSON format)
73
+ * @param options - Optional configuration
74
+ * @returns An Agent that returns structured JSON responses
75
+ */
76
+ export declare function createJsonChatAgent<T = unknown>(systemPrompt: string, options?: ChatAgentOptions): import("@alveus-ai/core").Agent<Record<string, unknown>, ChatInput, ExecutionContext, {
77
+ data: T;
78
+ raw: string;
79
+ }>;
80
+ //# sourceMappingURL=basic-chat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"basic-chat.d.ts","sourceRoot":"","sources":["../../src/agents/basic-chat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAErE;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAC;IAEhB,oCAAoC;IACpC,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClE;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAC;IAEd,sCAAsC;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,mCAAmC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAiB,SAAQ,WAAW;IACnD,iDAAiD;IACjD,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,yCAAyC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,qGAoCnF;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,GAAG,OAAO,EAC7C,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,gBAAqB;UAST,CAAC;SAAO,MAAM;GA2BpC"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Basic Chat Agent Factory
3
+ *
4
+ * Creates reusable chat agents with a configurable system prompt.
5
+ * The agent is model-agnostic - it works with any LLM provider.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * // Create a helpful assistant
10
+ * const assistant = createChatAgent('You are a helpful assistant.');
11
+ *
12
+ * // Create a code reviewer with specific model
13
+ * const reviewer = createChatAgent(
14
+ * 'You are a senior engineer reviewing code for bugs and improvements.',
15
+ * { model: 'gpt-4o', provider: 'openai' }
16
+ * );
17
+ *
18
+ * // Execute via runtime
19
+ * const result = await runtime.execute({
20
+ * hash: assistant.definition.hash,
21
+ * state: {},
22
+ * event: { message: 'Hello, how can you help me?' }
23
+ * });
24
+ * ```
25
+ */
26
+ import { agent$ } from '@alveus-ai/core';
27
+ /**
28
+ * Create a chat agent with a configurable system prompt
29
+ *
30
+ * @param systemPrompt - The system prompt defining the agent's behavior
31
+ * @param options - Optional configuration for provider, model, etc.
32
+ * @returns An Agent that can be executed via any Alveus runtime
33
+ */
34
+ export function createChatAgent(systemPrompt, options = {}) {
35
+ const { name, description, ...chatOptions } = options;
36
+ return agent$(async (_state, event, ctx) => {
37
+ // Build the message array
38
+ const messages = [
39
+ { role: 'system', content: systemPrompt },
40
+ ];
41
+ // Add conversation history if provided
42
+ if (event.history) {
43
+ messages.push(...event.history);
44
+ }
45
+ // Add the current user message
46
+ messages.push({ role: 'user', content: event.message });
47
+ // Generate response using the injected LLM capability
48
+ const reply = await ctx.llm.chat(messages, chatOptions);
49
+ return {
50
+ reply,
51
+ provider: chatOptions.provider,
52
+ model: chatOptions.model,
53
+ };
54
+ }, {
55
+ name: name || 'chat-agent',
56
+ description: description || 'A basic chat agent',
57
+ });
58
+ }
59
+ /**
60
+ * Create a JSON-structured chat agent
61
+ *
62
+ * Forces the LLM to respond with valid JSON, useful for structured outputs.
63
+ *
64
+ * @param systemPrompt - The system prompt (should describe the expected JSON format)
65
+ * @param options - Optional configuration
66
+ * @returns An Agent that returns structured JSON responses
67
+ */
68
+ export function createJsonChatAgent(systemPrompt, options = {}) {
69
+ const { name, description, ...chatOptions } = options;
70
+ return agent$(async (_state, event, ctx) => {
71
+ const messages = [
72
+ { role: 'system', content: systemPrompt },
73
+ ];
74
+ if (event.history) {
75
+ messages.push(...event.history);
76
+ }
77
+ messages.push({ role: 'user', content: event.message });
78
+ // Force JSON mode
79
+ const reply = await ctx.llm.chat(messages, {
80
+ ...chatOptions,
81
+ jsonMode: true,
82
+ });
83
+ // Parse the JSON response
84
+ const data = JSON.parse(reply);
85
+ return { data, raw: reply };
86
+ }, {
87
+ name: name || 'json-chat-agent',
88
+ description: description || 'A structured JSON chat agent',
89
+ });
90
+ }
91
+ //# sourceMappingURL=basic-chat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"basic-chat.js","sourceRoot":"","sources":["../../src/agents/basic-chat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAuCzC;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,YAAoB,EAAE,UAA4B,EAAE;IAClF,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,WAAW,EAAE,GAAG,OAAO,CAAC;IAEtD,OAAO,MAAM,CACX,KAAK,EACH,MAA+B,EAC/B,KAAgB,EAChB,GAAqB,EACA,EAAE;QACvB,0BAA0B;QAC1B,MAAM,QAAQ,GAAsE;YAClF,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE;SAC1C,CAAC;QAEF,uCAAuC;QACvC,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YAClB,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;QAClC,CAAC;QAED,+BAA+B;QAC/B,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAExD,sDAAsD;QACtD,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QAExD,OAAO;YACL,KAAK;YACL,QAAQ,EAAE,WAAW,CAAC,QAAQ;YAC9B,KAAK,EAAE,WAAW,CAAC,KAAK;SACzB,CAAC;IACJ,CAAC,EACD;QACE,IAAI,EAAE,IAAI,IAAI,YAAY;QAC1B,WAAW,EAAE,WAAW,IAAI,oBAAoB;KACjD,CACF,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CACjC,YAAoB,EACpB,UAA4B,EAAE;IAE9B,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,WAAW,EAAE,GAAG,OAAO,CAAC;IAEtD,OAAO,MAAM,CACX,KAAK,EACH,MAA+B,EAC/B,KAAgB,EAChB,GAAqB,EACc,EAAE;QACrC,MAAM,QAAQ,GAAsE;YAClF,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE;SAC1C,CAAC;QAEF,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YAClB,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;QAClC,CAAC;QAED,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAExD,kBAAkB;QAClB,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE;YACzC,GAAG,WAAW;YACd,QAAQ,EAAE,IAAI;SACf,CAAC,CAAC;QAEH,0BAA0B;QAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAM,CAAC;QAEpC,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;IAC9B,CAAC,EACD;QACE,IAAI,EAAE,IAAI,IAAI,iBAAiB;QAC/B,WAAW,EAAE,WAAW,IAAI,8BAA8B;KAC3D,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Standard Agent Factories
3
+ *
4
+ * Pre-built agent factories for common use cases.
5
+ */
6
+ export { createChatAgent, createJsonChatAgent, type ChatInput, type ChatOutput, type ChatAgentOptions, } from './basic-chat.js';
7
+ export { ReActLogic, createReActAgent, availableTools, type ReActInput, type ReActOutput, type ReActStep, type ReActAgentOptions, } from './react.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/agents/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,gBAAgB,GACtB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAEL,UAAU,EAEV,gBAAgB,EAEhB,cAAc,EAEd,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,iBAAiB,GACvB,MAAM,YAAY,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Standard Agent Factories
3
+ *
4
+ * Pre-built agent factories for common use cases.
5
+ */
6
+ export { createChatAgent, createJsonChatAgent, } from './basic-chat.js';
7
+ export {
8
+ // The master logic (compiled agent)
9
+ ReActLogic,
10
+ // The factory (returns VirtualAgent)
11
+ createReActAgent,
12
+ // Available tools list
13
+ availableTools, } from './react.js';
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/agents/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,eAAe,EACf,mBAAmB,GAIpB,MAAM,iBAAiB,CAAC;AAEzB,OAAO;AACL,oCAAoC;AACpC,UAAU;AACV,qCAAqC;AACrC,gBAAgB;AAChB,uBAAuB;AACvB,cAAc,GAMf,MAAM,YAAY,CAAC"}
@@ -0,0 +1,153 @@
1
+ /**
2
+ * ReAct Agent Pattern - Using the "Curried Agent" (VirtualAgent) Pattern
3
+ *
4
+ * Tools are Agents!
5
+ *
6
+ * Custom tools must be wrapped in agent$() so they are:
7
+ * - Durable (the runtime handles retries)
8
+ * - Distributed (can run on different workers)
9
+ * - Serializable (only the hash is passed, not the function)
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { agent$ } from '@alveus-ai/core';
14
+ * import { createReActAgent } from '@alveus-ai/std';
15
+ *
16
+ * // 1. Define custom tool as an Agent
17
+ * const HNTool = agent$(
18
+ * async (state, event) => {
19
+ * const stories = await fetch('https://...').then(r => r.json());
20
+ * return stories.map(s => s.title).join('\n');
21
+ * },
22
+ * { name: 'hn-tool', description: 'Fetches Hacker News stories' }
23
+ * );
24
+ *
25
+ * // 2. Create ReAct agent with custom tools
26
+ * const ResearchAgent = createReActAgent({
27
+ * systemPrompt: 'You are a researcher.',
28
+ * tools: {
29
+ * 'get_news': { agent: HNTool, description: 'Fetches top HN stories' }
30
+ * }
31
+ * });
32
+ * ```
33
+ */
34
+ import type { Agent, ExecutionContext, VirtualAgent } from '@alveus-ai/core';
35
+ /**
36
+ * Tool definition for ReAct agents
37
+ *
38
+ * Tools can be:
39
+ * - Built-in (calculator, string) - executed inline
40
+ * - Custom Agents - executed via ctx.call() for durability
41
+ */
42
+ export interface ToolDefinition {
43
+ /** The tool agent to call (or 'builtin:calculator', 'builtin:string') */
44
+ agent: Agent | string;
45
+ /** Human-readable description for the LLM */
46
+ description: string;
47
+ /** JSON Schema for parameters (as string for LLM prompt) */
48
+ parameters?: string;
49
+ }
50
+ /**
51
+ * Input schema for the ReAct Logic
52
+ */
53
+ export interface ReActInput {
54
+ /** System prompt defining the agent's behavior */
55
+ systemPrompt: string;
56
+ /**
57
+ * Tools available to the agent
58
+ *
59
+ * Map of tool name -> tool config (hash + description)
60
+ * The hash is used to call the tool agent via ctx.call()
61
+ */
62
+ tools: Record<string, {
63
+ /** Agent hash (or 'builtin:calculator', 'builtin:string') */
64
+ hash: string;
65
+ /** Description for the LLM */
66
+ description: string;
67
+ /** Parameter schema for the LLM */
68
+ parameters?: string;
69
+ }>;
70
+ /** The user's query/input */
71
+ input: string;
72
+ /** Optional context for the query */
73
+ context?: string;
74
+ /** Maximum iterations (default: 10) */
75
+ maxIterations?: number;
76
+ }
77
+ /**
78
+ * A single step in the ReAct execution trace
79
+ */
80
+ export interface ReActStep {
81
+ type: 'thought' | 'action' | 'observation';
82
+ content: string;
83
+ tool?: string;
84
+ params?: Record<string, any>;
85
+ }
86
+ /**
87
+ * Output from ReAct agents
88
+ */
89
+ export interface ReActOutput {
90
+ answer: string;
91
+ steps: ReActStep[];
92
+ iterations: number;
93
+ }
94
+ /**
95
+ * The ReAct Logic - Compiled and frozen ONCE at build time
96
+ *
97
+ * This is the "master brain" that gets reused for all ReAct agents.
98
+ * Configuration (systemPrompt, tools) comes from the event input.
99
+ *
100
+ * Tools are called via ctx.call() for durability,
101
+ * except for built-in tools which are executed inline.
102
+ */
103
+ export declare const ReActLogic: Agent<{}, ReActInput, ExecutionContext, any>;
104
+ /**
105
+ * Options for creating a ReAct agent
106
+ */
107
+ export interface ReActAgentOptions {
108
+ /** System prompt defining the agent's behavior */
109
+ systemPrompt: string;
110
+ /**
111
+ * Tools available to this agent
112
+ *
113
+ * Can be:
114
+ * - Built-in tool names: 'calculator', 'string'
115
+ * - Custom tool agents: { agent: MyToolAgent, description: '...' }
116
+ */
117
+ tools?: Record<string, ToolDefinition | string>;
118
+ /** Maximum iterations before giving up (default: 10) */
119
+ maxIterations?: number;
120
+ }
121
+ /**
122
+ * Create a ReAct agent with the given configuration
123
+ *
124
+ * This returns a VirtualAgent that points to the pre-compiled ReActLogic
125
+ * with pre-filled configuration. No new code is generated.
126
+ *
127
+ * @param options - Agent configuration
128
+ * @returns A VirtualAgent ready for execution
129
+ *
130
+ * @example
131
+ * ```ts
132
+ * // With built-in tools only
133
+ * const mathAgent = createReActAgent({
134
+ * systemPrompt: 'You are a math assistant.',
135
+ * tools: { calculator: 'calculator', string: 'string' }
136
+ * });
137
+ *
138
+ * // With custom tool agents
139
+ * const researchAgent = createReActAgent({
140
+ * systemPrompt: 'You are a researcher.',
141
+ * tools: {
142
+ * calculator: 'calculator', // built-in
143
+ * get_news: { agent: HNToolAgent, description: 'Fetches HN stories' }
144
+ * }
145
+ * });
146
+ * ```
147
+ */
148
+ export declare function createReActAgent(options: ReActAgentOptions): VirtualAgent<ReActInput>;
149
+ /**
150
+ * List of available built-in tools
151
+ */
152
+ export declare const availableTools: string[];
153
+ //# sourceMappingURL=react.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../../src/agents/react.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAGH,OAAO,KAAK,EAAE,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE7E;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,yEAAyE;IACzE,KAAK,EAAE,KAAK,GAAG,MAAM,CAAC;IAEtB,6CAA6C;IAC7C,WAAW,EAAE,MAAM,CAAC;IAEpB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,kDAAkD;IAClD,YAAY,EAAE,MAAM,CAAC;IAErB;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CACX,MAAM,EACN;QACE,6DAA6D;QAC7D,IAAI,EAAE,MAAM,CAAC;QACb,8BAA8B;QAC9B,WAAW,EAAE,MAAM,CAAC;QACpB,mCAAmC;QACnC,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CACF,CAAC;IAEF,6BAA6B;IAC7B,KAAK,EAAE,MAAM,CAAC;IAEd,qCAAqC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,uCAAuC;IACvC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,SAAS,GAAG,QAAQ,GAAG,aAAa,CAAC;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,UAAU,8CAqNtB,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,kDAAkD;IAClD,YAAY,EAAE,MAAM,CAAC;IAErB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAAC,CAAC;IAEhD,wDAAwD;IACxD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAkBD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,YAAY,CAAC,UAAU,CAAC,CAoCrF;AAED;;GAEG;AACH,eAAO,MAAM,cAAc,UAA6B,CAAC"}
@@ -0,0 +1,298 @@
1
+ /**
2
+ * ReAct Agent Pattern - Using the "Curried Agent" (VirtualAgent) Pattern
3
+ *
4
+ * Tools are Agents!
5
+ *
6
+ * Custom tools must be wrapped in agent$() so they are:
7
+ * - Durable (the runtime handles retries)
8
+ * - Distributed (can run on different workers)
9
+ * - Serializable (only the hash is passed, not the function)
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { agent$ } from '@alveus-ai/core';
14
+ * import { createReActAgent } from '@alveus-ai/std';
15
+ *
16
+ * // 1. Define custom tool as an Agent
17
+ * const HNTool = agent$(
18
+ * async (state, event) => {
19
+ * const stories = await fetch('https://...').then(r => r.json());
20
+ * return stories.map(s => s.title).join('\n');
21
+ * },
22
+ * { name: 'hn-tool', description: 'Fetches Hacker News stories' }
23
+ * );
24
+ *
25
+ * // 2. Create ReAct agent with custom tools
26
+ * const ResearchAgent = createReActAgent({
27
+ * systemPrompt: 'You are a researcher.',
28
+ * tools: {
29
+ * 'get_news': { agent: HNTool, description: 'Fetches top HN stories' }
30
+ * }
31
+ * });
32
+ * ```
33
+ */
34
+ import { agent$ } from '@alveus-ai/core';
35
+ /**
36
+ * The ReAct Logic - Compiled and frozen ONCE at build time
37
+ *
38
+ * This is the "master brain" that gets reused for all ReAct agents.
39
+ * Configuration (systemPrompt, tools) comes from the event input.
40
+ *
41
+ * Tools are called via ctx.call() for durability,
42
+ * except for built-in tools which are executed inline.
43
+ */
44
+ export const ReActLogic = agent$(async (_state, event, ctx) => {
45
+ // ===== BUILT-IN TOOL EXECUTORS =====
46
+ // These run inline (not as separate agents) for simplicity
47
+ const BUILTIN_EXECUTORS = {
48
+ 'builtin:calculator': async ({ expression }) => {
49
+ const sanitized = String(expression).replace(/[^0-9+\-*/().%\s]/g, '');
50
+ if (sanitized !== String(expression)) {
51
+ throw new Error('Invalid characters in expression');
52
+ }
53
+ try {
54
+ const result = new Function(`return ${sanitized}`)();
55
+ return String(result);
56
+ }
57
+ catch {
58
+ throw new Error(`Failed to evaluate: ${expression}`);
59
+ }
60
+ },
61
+ 'builtin:string': async ({ operation, text }) => {
62
+ switch (operation) {
63
+ case 'length':
64
+ return String(text.length);
65
+ case 'uppercase':
66
+ return text.toUpperCase();
67
+ case 'lowercase':
68
+ return text.toLowerCase();
69
+ case 'reverse':
70
+ return text.split('').reverse().join('');
71
+ default:
72
+ throw new Error(`Unknown operation: ${operation}`);
73
+ }
74
+ },
75
+ };
76
+ // ===== BUILD SYSTEM PROMPT =====
77
+ const buildPrompt = (basePrompt, tools) => {
78
+ const toolDescriptions = Object.entries(tools)
79
+ .map(([name, tool]) => {
80
+ const params = tool.parameters || '{ ... }';
81
+ return `- ${name}: ${tool.description}\n Parameters: ${params}`;
82
+ })
83
+ .join('\n');
84
+ return `${basePrompt}
85
+
86
+ Available tools:
87
+ ${toolDescriptions}
88
+
89
+ You MUST respond with a JSON object in this exact format:
90
+ {
91
+ "thought": "Your reasoning about what to do next",
92
+ "action": { "tool": "tool_name", "params": { ... } } OR null if you have the final answer,
93
+ "answer": "Your final answer to the user" OR null if you need to use a tool
94
+ }
95
+
96
+ Rules:
97
+ 1. Always start with a thought explaining your reasoning
98
+ 2. If you need information or need to calculate something, use a tool by setting "action"
99
+ 3. When you have enough information, set "answer" and leave "action" null
100
+ 4. Use exactly one tool per response
101
+ 5. Never make up information - use tools to get facts`;
102
+ };
103
+ const buildMsgs = (sysPrompt, userInput, steps) => {
104
+ const messages = [
105
+ { role: 'system', content: sysPrompt },
106
+ { role: 'user', content: userInput },
107
+ ];
108
+ for (const step of steps) {
109
+ if (step.type === 'thought') {
110
+ messages.push({
111
+ role: 'assistant',
112
+ content: JSON.stringify({
113
+ thought: step.content,
114
+ action: step.tool ? { tool: step.tool, params: step.params } : null,
115
+ answer: null,
116
+ }),
117
+ });
118
+ }
119
+ else if (step.type === 'observation') {
120
+ messages.push({
121
+ role: 'user',
122
+ content: `Tool result: ${step.content}`,
123
+ });
124
+ }
125
+ }
126
+ return messages;
127
+ };
128
+ // ===== EXECUTE TOOL =====
129
+ const executeTool = async (toolName, hash, params) => {
130
+ // Check if it's a built-in tool
131
+ if (hash.startsWith('builtin:')) {
132
+ const executor = BUILTIN_EXECUTORS[hash];
133
+ if (!executor) {
134
+ throw new Error(`Unknown built-in tool: ${hash}`);
135
+ }
136
+ return await executor(params);
137
+ }
138
+ // It's a custom tool agent - call via ctx.call() for durability!
139
+ ctx.logger.info(`Calling tool agent: ${toolName}`, { hash: hash.slice(0, 16), params });
140
+ const result = await ctx.call(hash, params);
141
+ // Tool agents should return a string or something stringifiable
142
+ return typeof result === 'string' ? result : JSON.stringify(result);
143
+ };
144
+ // ===== MAIN REACT LOOP =====
145
+ const { systemPrompt, tools, input, context, maxIterations = 10 } = event;
146
+ const steps = [];
147
+ let iterations = 0;
148
+ const fullPrompt = buildPrompt(systemPrompt, tools);
149
+ const fullInput = context ? `Context: ${context}\n\nQuestion: ${input}` : input;
150
+ ctx.logger.info('Starting ReAct loop', { maxIterations, toolCount: Object.keys(tools).length });
151
+ while (iterations < maxIterations) {
152
+ iterations++;
153
+ const messages = buildMsgs(fullPrompt, fullInput, steps);
154
+ // Get LLM response with JSON mode
155
+ const responseText = await ctx.llm.chat(messages, { jsonMode: true });
156
+ // Parse response
157
+ let response;
158
+ try {
159
+ response = JSON.parse(responseText);
160
+ }
161
+ catch {
162
+ steps.push({
163
+ type: 'observation',
164
+ content: 'Error: Invalid JSON response. Please respond with valid JSON.',
165
+ });
166
+ continue;
167
+ }
168
+ // Record thought
169
+ if (response.thought) {
170
+ ctx.logger.info(`Thought: ${response.thought.slice(0, 100)}...`);
171
+ steps.push({ type: 'thought', content: response.thought });
172
+ }
173
+ // Check for final answer
174
+ if (response.answer && !response.action) {
175
+ ctx.logger.info('ReAct completed with answer', { iterations });
176
+ return { answer: response.answer, steps, iterations };
177
+ }
178
+ // Execute action
179
+ if (response.action) {
180
+ const toolName = response.action.tool;
181
+ const toolDef = tools[toolName];
182
+ if (!toolDef) {
183
+ steps.push({
184
+ type: 'action',
185
+ content: `Attempted unknown tool: ${toolName}`,
186
+ tool: toolName,
187
+ params: response.action.params,
188
+ });
189
+ steps.push({
190
+ type: 'observation',
191
+ content: `Error: Tool "${toolName}" not available. Available tools: ${Object.keys(tools).join(', ')}`,
192
+ });
193
+ continue;
194
+ }
195
+ steps.push({
196
+ type: 'action',
197
+ content: `Using ${toolName}`,
198
+ tool: toolName,
199
+ params: response.action.params,
200
+ });
201
+ try {
202
+ const result = await executeTool(toolName, toolDef.hash, response.action.params);
203
+ steps.push({ type: 'observation', content: result });
204
+ }
205
+ catch (error) {
206
+ steps.push({
207
+ type: 'observation',
208
+ content: `Error: ${error instanceof Error ? error.message : String(error)}`,
209
+ });
210
+ }
211
+ }
212
+ }
213
+ ctx.logger.warn('ReAct hit max iterations', { maxIterations });
214
+ return {
215
+ answer: `Unable to complete within ${maxIterations} iterations.`,
216
+ steps,
217
+ iterations,
218
+ };
219
+ }, { name: 'ReActLogic', version: '2.0.0' });
220
+ /**
221
+ * Built-in tool definitions
222
+ */
223
+ const BUILTIN_TOOLS = {
224
+ calculator: {
225
+ hash: 'builtin:calculator',
226
+ description: 'Performs arithmetic calculations. Supports +, -, *, /, and parentheses.',
227
+ parameters: '{ expression: string }',
228
+ },
229
+ string: {
230
+ hash: 'builtin:string',
231
+ description: 'Performs string operations: length, uppercase, lowercase, reverse',
232
+ parameters: '{ operation: "length" | "uppercase" | "lowercase" | "reverse", text: string }',
233
+ },
234
+ };
235
+ /**
236
+ * Create a ReAct agent with the given configuration
237
+ *
238
+ * This returns a VirtualAgent that points to the pre-compiled ReActLogic
239
+ * with pre-filled configuration. No new code is generated.
240
+ *
241
+ * @param options - Agent configuration
242
+ * @returns A VirtualAgent ready for execution
243
+ *
244
+ * @example
245
+ * ```ts
246
+ * // With built-in tools only
247
+ * const mathAgent = createReActAgent({
248
+ * systemPrompt: 'You are a math assistant.',
249
+ * tools: { calculator: 'calculator', string: 'string' }
250
+ * });
251
+ *
252
+ * // With custom tool agents
253
+ * const researchAgent = createReActAgent({
254
+ * systemPrompt: 'You are a researcher.',
255
+ * tools: {
256
+ * calculator: 'calculator', // built-in
257
+ * get_news: { agent: HNToolAgent, description: 'Fetches HN stories' }
258
+ * }
259
+ * });
260
+ * ```
261
+ */
262
+ export function createReActAgent(options) {
263
+ // Convert tool definitions to the format expected by ReActInput
264
+ const tools = {};
265
+ const inputTools = options.tools ?? { calculator: 'calculator', string: 'string' };
266
+ for (const [name, def] of Object.entries(inputTools)) {
267
+ if (typeof def === 'string') {
268
+ // Built-in tool by name
269
+ const builtin = BUILTIN_TOOLS[def];
270
+ if (!builtin) {
271
+ throw new Error(`Unknown built-in tool: ${def}. Available: ${Object.keys(BUILTIN_TOOLS).join(', ')}`);
272
+ }
273
+ tools[name] = builtin;
274
+ }
275
+ else {
276
+ // Custom tool agent
277
+ const agentHash = typeof def.agent === 'string' ? def.agent : def.agent.definition.hash;
278
+ tools[name] = {
279
+ hash: agentHash,
280
+ description: def.description,
281
+ parameters: def.parameters,
282
+ };
283
+ }
284
+ }
285
+ return {
286
+ definition: ReActLogic,
287
+ defaultInput: {
288
+ systemPrompt: options.systemPrompt,
289
+ tools,
290
+ maxIterations: options.maxIterations ?? 10,
291
+ },
292
+ };
293
+ }
294
+ /**
295
+ * List of available built-in tools
296
+ */
297
+ export const availableTools = Object.keys(BUILTIN_TOOLS);
298
+ //# sourceMappingURL=react.js.map