groundswell 0.0.1
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.
- package/.claude/settings.local.json +9 -0
- package/.claude/system_prompts/task-breakdown.md +100 -0
- package/PRPs/001-hierarchical-workflow-engine.md +2438 -0
- package/PRPs/PRDs/001-hierarchical-workflow-engine.md +543 -0
- package/PRPs/PRDs/002-agent-prompt.md +390 -0
- package/PRPs/PRDs/003-agent-prompt.md +943 -0
- package/PRPs/PRDs/004-agent-prompt.md +1136 -0
- package/PRPs/PRDs/tasks-001.json +492 -0
- package/PRPs/README.md +83 -0
- package/PRPs/templates/prp_base.md +222 -0
- package/README.md +218 -0
- package/docs/agent.md +422 -0
- package/docs/prompt.md +419 -0
- package/docs/workflow.md +600 -0
- package/examples/README.md +244 -0
- package/examples/examples/01-basic-workflow.ts +100 -0
- package/examples/examples/02-decorator-options.ts +217 -0
- package/examples/examples/03-parent-child.ts +241 -0
- package/examples/examples/04-observers-debugger.ts +340 -0
- package/examples/examples/05-error-handling.ts +387 -0
- package/examples/examples/06-concurrent-tasks.ts +352 -0
- package/examples/examples/07-agent-loops.ts +432 -0
- package/examples/examples/08-sdk-features.ts +667 -0
- package/examples/examples/09-reflection.ts +573 -0
- package/examples/examples/10-introspection.ts +550 -0
- package/examples/index.ts +143 -0
- package/examples/utils/helpers.ts +57 -0
- package/llms_full.txt +5890 -0
- package/package.json +63 -0
- package/plan/P1P2/PRP.md +527 -0
- package/plan/P1P2/research/LRU_CACHE_BEST_PRACTICES.md +1929 -0
- package/plan/P1P2/research/LRU_CACHE_CODE_PATTERNS.md +857 -0
- package/plan/P1P2/research/LRU_CACHE_INTEGRATION_GUIDE.md +738 -0
- package/plan/P1P2/research/LRU_CACHE_RESEARCH_INDEX.md +424 -0
- package/plan/P1P2/research/REFLECTION_INDEX.md +291 -0
- package/plan/P1P2/research/REFLECTION_RESEARCH_REPORT.md +1342 -0
- package/plan/P1P2/research/RESEARCH_SUMMARY.md +342 -0
- package/plan/P1P2/research/anthropic-sdk.md +174 -0
- package/plan/P1P2/research/async-local-storage.md +200 -0
- package/plan/P1P2/research/reflection-code-patterns.md +1205 -0
- package/plan/P1P2/research/reflection-decision-matrix.md +421 -0
- package/plan/P1P2/research/reflection-implementation-guide.md +1341 -0
- package/plan/P1P2/research/reflection-integration-guide.md +834 -0
- package/plan/P1P2/research/reflection-patterns.md +1468 -0
- package/plan/P1P2/research/reflection-quick-reference.md +558 -0
- package/plan/P1P2/research/zod-schema.md +152 -0
- package/plan/P3P4/PRP.md +1388 -0
- package/plan/P3P4/research/caching-lru.md +116 -0
- package/plan/P3P4/research/introspection-tools.md +177 -0
- package/plan/P3P4/research/reflection-patterns.md +117 -0
- package/plan/P4P5/PRP.md +1136 -0
- package/plan/P4P5/research/RESEARCH_SUMMARY.md +151 -0
- package/plan/architecture/external_deps.md +358 -0
- package/plan/architecture/system_context.md +242 -0
- package/plan/backlog.json +867 -0
- package/plan/research/INTROSPECTION_RESEARCH_SUMMARY.md +378 -0
- package/plan/research/README-INTROSPECTION.md +352 -0
- package/plan/research/agent-introspection-patterns.md +1085 -0
- package/plan/research/introspection-security-guide.md +928 -0
- package/plan/research/introspection-tool-examples.md +875 -0
- package/scripts/generate-llms-full.ts +206 -0
- package/src/__tests__/integration/agent-workflow.test.ts +256 -0
- package/src/__tests__/integration/tree-mirroring.test.ts +114 -0
- package/src/__tests__/unit/agent.test.ts +169 -0
- package/src/__tests__/unit/cache-key.test.ts +182 -0
- package/src/__tests__/unit/cache.test.ts +172 -0
- package/src/__tests__/unit/context.test.ts +138 -0
- package/src/__tests__/unit/decorators.test.ts +100 -0
- package/src/__tests__/unit/introspection-tools.test.ts +277 -0
- package/src/__tests__/unit/prompt.test.ts +135 -0
- package/src/__tests__/unit/reflection.test.ts +210 -0
- package/src/__tests__/unit/tree-debugger.test.ts +85 -0
- package/src/__tests__/unit/workflow.test.ts +81 -0
- package/src/cache/cache-key.ts +244 -0
- package/src/cache/cache.ts +236 -0
- package/src/cache/index.ts +8 -0
- package/src/core/agent.ts +573 -0
- package/src/core/context.ts +119 -0
- package/src/core/event-tree.ts +260 -0
- package/src/core/factory.ts +123 -0
- package/src/core/index.ts +17 -0
- package/src/core/logger.ts +87 -0
- package/src/core/mcp-handler.ts +184 -0
- package/src/core/prompt.ts +150 -0
- package/src/core/workflow-context.ts +349 -0
- package/src/core/workflow.ts +302 -0
- package/src/debugger/index.ts +1 -0
- package/src/debugger/tree-debugger.ts +210 -0
- package/src/decorators/index.ts +3 -0
- package/src/decorators/observed-state.ts +95 -0
- package/src/decorators/step.ts +139 -0
- package/src/decorators/task.ts +96 -0
- package/src/examples/index.ts +2 -0
- package/src/examples/tdd-orchestrator.ts +65 -0
- package/src/examples/test-cycle-workflow.ts +64 -0
- package/src/index.ts +140 -0
- package/src/reflection/index.ts +5 -0
- package/src/reflection/reflection.ts +407 -0
- package/src/tools/index.ts +36 -0
- package/src/tools/introspection.ts +464 -0
- package/src/types/agent.ts +90 -0
- package/src/types/decorators.ts +25 -0
- package/src/types/error-strategy.ts +13 -0
- package/src/types/error.ts +20 -0
- package/src/types/events.ts +74 -0
- package/src/types/index.ts +55 -0
- package/src/types/logging.ts +24 -0
- package/src/types/observer.ts +18 -0
- package/src/types/prompt.ts +40 -0
- package/src/types/reflection.ts +117 -0
- package/src/types/sdk-primitives.ts +128 -0
- package/src/types/snapshot.ts +14 -0
- package/src/types/workflow-context.ts +163 -0
- package/src/types/workflow.ts +37 -0
- package/src/utils/id.ts +11 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/observable.ts +77 -0
- package/tasks.json +0 -0
- package/tsconfig.json +22 -0
- package/vitest.config.ts +16 -0
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent - Lightweight wrapper around Anthropic's Agent SDK
|
|
3
|
+
*
|
|
4
|
+
* Agents execute prompts and manage tool invocation cycles.
|
|
5
|
+
* All configuration properties map 1:1 to Anthropic SDK.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
9
|
+
import type {
|
|
10
|
+
AgentConfig,
|
|
11
|
+
PromptOverrides,
|
|
12
|
+
Tool,
|
|
13
|
+
AgentHooks,
|
|
14
|
+
TokenUsage,
|
|
15
|
+
PreToolUseContext,
|
|
16
|
+
PostToolUseContext,
|
|
17
|
+
SessionStartContext,
|
|
18
|
+
SessionEndContext,
|
|
19
|
+
WorkflowEvent,
|
|
20
|
+
} from '../types/index.js';
|
|
21
|
+
import type { Prompt } from './prompt.js';
|
|
22
|
+
import { MCPHandler } from './mcp-handler.js';
|
|
23
|
+
import { generateId } from '../utils/id.js';
|
|
24
|
+
import { getExecutionContext } from './context.js';
|
|
25
|
+
import { generateCacheKey, defaultCache } from '../cache/index.js';
|
|
26
|
+
import type { CacheKeyInputs } from '../cache/index.js';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Result from a prompt execution including metadata
|
|
30
|
+
*/
|
|
31
|
+
export interface PromptResult<T> {
|
|
32
|
+
/** Validated response data */
|
|
33
|
+
data: T;
|
|
34
|
+
/** Token usage from the API */
|
|
35
|
+
usage: TokenUsage;
|
|
36
|
+
/** Total duration in milliseconds */
|
|
37
|
+
duration: number;
|
|
38
|
+
/** Number of tool invocations */
|
|
39
|
+
toolCalls: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Internal message type for conversation history
|
|
44
|
+
*/
|
|
45
|
+
type Message = Anthropic.MessageParam;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Agent class - executes prompts via Anthropic SDK
|
|
49
|
+
*/
|
|
50
|
+
export class Agent {
|
|
51
|
+
/** Unique identifier for this agent instance */
|
|
52
|
+
public readonly id: string;
|
|
53
|
+
|
|
54
|
+
/** Agent name */
|
|
55
|
+
public readonly name: string;
|
|
56
|
+
|
|
57
|
+
/** Stored configuration */
|
|
58
|
+
private readonly config: AgentConfig;
|
|
59
|
+
|
|
60
|
+
/** Anthropic client instance */
|
|
61
|
+
private readonly client: Anthropic;
|
|
62
|
+
|
|
63
|
+
/** MCP handler for tool management */
|
|
64
|
+
private readonly mcpHandler: MCPHandler;
|
|
65
|
+
|
|
66
|
+
/** Default model to use */
|
|
67
|
+
private readonly model: string;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Create a new Agent instance
|
|
71
|
+
* @param config Agent configuration
|
|
72
|
+
*/
|
|
73
|
+
constructor(config: AgentConfig = {}) {
|
|
74
|
+
this.id = generateId();
|
|
75
|
+
this.name = config.name ?? 'Agent';
|
|
76
|
+
this.config = config;
|
|
77
|
+
this.model = config.model ?? 'claude-sonnet-4-20250514';
|
|
78
|
+
|
|
79
|
+
// Create Anthropic client
|
|
80
|
+
this.client = new Anthropic({
|
|
81
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Initialize MCP handler
|
|
85
|
+
this.mcpHandler = new MCPHandler();
|
|
86
|
+
|
|
87
|
+
// Register MCP servers
|
|
88
|
+
if (config.mcps) {
|
|
89
|
+
for (const mcp of config.mcps) {
|
|
90
|
+
this.mcpHandler.registerServer(mcp);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Execute a prompt and return validated response
|
|
97
|
+
* @param prompt Prompt to execute
|
|
98
|
+
* @param overrides Optional overrides for this execution
|
|
99
|
+
* @returns Validated response of type T
|
|
100
|
+
*/
|
|
101
|
+
public async prompt<T>(
|
|
102
|
+
prompt: Prompt<T>,
|
|
103
|
+
overrides?: PromptOverrides
|
|
104
|
+
): Promise<T> {
|
|
105
|
+
const result = await this.executePrompt(prompt, overrides);
|
|
106
|
+
return result.data;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Execute a prompt with full result metadata
|
|
111
|
+
* @param prompt Prompt to execute
|
|
112
|
+
* @param overrides Optional overrides for this execution
|
|
113
|
+
* @returns Full result including metadata
|
|
114
|
+
*/
|
|
115
|
+
public async promptWithMetadata<T>(
|
|
116
|
+
prompt: Prompt<T>,
|
|
117
|
+
overrides?: PromptOverrides
|
|
118
|
+
): Promise<PromptResult<T>> {
|
|
119
|
+
return this.executePrompt(prompt, overrides);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Execute a prompt with reflection capabilities
|
|
124
|
+
* @param prompt Prompt to execute
|
|
125
|
+
* @param overrides Optional overrides for this execution
|
|
126
|
+
* @returns Validated response of type T
|
|
127
|
+
*/
|
|
128
|
+
public async reflect<T>(
|
|
129
|
+
prompt: Prompt<T>,
|
|
130
|
+
overrides?: PromptOverrides
|
|
131
|
+
): Promise<T> {
|
|
132
|
+
// Add reflection system prefix if reflection is enabled
|
|
133
|
+
const reflectionEnabled =
|
|
134
|
+
prompt.enableReflection ??
|
|
135
|
+
overrides?.enableReflection ??
|
|
136
|
+
this.config.enableReflection;
|
|
137
|
+
|
|
138
|
+
const systemPrefix = reflectionEnabled
|
|
139
|
+
? 'Before answering, reflect on your reasoning step by step. Consider alternative approaches and potential errors. Then provide your final answer.\n\n'
|
|
140
|
+
: '';
|
|
141
|
+
|
|
142
|
+
const effectiveOverrides: PromptOverrides = {
|
|
143
|
+
...overrides,
|
|
144
|
+
system:
|
|
145
|
+
systemPrefix +
|
|
146
|
+
(prompt.systemOverride ?? overrides?.system ?? this.config.system ?? ''),
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const result = await this.executePrompt(prompt, effectiveOverrides);
|
|
150
|
+
return result.data;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get the MCP handler for custom tool registration
|
|
155
|
+
*/
|
|
156
|
+
public getMcpHandler(): MCPHandler {
|
|
157
|
+
return this.mcpHandler;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Emit an event if within workflow context
|
|
162
|
+
*/
|
|
163
|
+
private emitWorkflowEvent(event: WorkflowEvent): void {
|
|
164
|
+
const ctx = getExecutionContext();
|
|
165
|
+
if (ctx) {
|
|
166
|
+
ctx.emitEvent(event);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Internal prompt execution with full flow
|
|
172
|
+
*/
|
|
173
|
+
private async executePrompt<T>(
|
|
174
|
+
prompt: Prompt<T>,
|
|
175
|
+
overrides?: PromptOverrides
|
|
176
|
+
): Promise<PromptResult<T>> {
|
|
177
|
+
const startTime = Date.now();
|
|
178
|
+
let toolCallCount = 0;
|
|
179
|
+
let totalUsage: TokenUsage = { input_tokens: 0, output_tokens: 0 };
|
|
180
|
+
|
|
181
|
+
// Get execution context for event emission
|
|
182
|
+
const ctx = getExecutionContext();
|
|
183
|
+
|
|
184
|
+
// Merge configuration: Prompt > Overrides > Config
|
|
185
|
+
const effectiveSystem =
|
|
186
|
+
prompt.systemOverride ?? overrides?.system ?? this.config.system;
|
|
187
|
+
|
|
188
|
+
const effectiveModel = overrides?.model ?? this.model;
|
|
189
|
+
const effectiveMaxTokens = overrides?.maxTokens ?? this.config.maxTokens ?? 4096;
|
|
190
|
+
const effectiveTemperature =
|
|
191
|
+
overrides?.temperature ?? this.config.temperature;
|
|
192
|
+
|
|
193
|
+
// Check cache if enabled
|
|
194
|
+
const cacheEnabled = this.config.enableCache && !overrides?.disableCache;
|
|
195
|
+
let cacheKey: string | undefined;
|
|
196
|
+
|
|
197
|
+
if (cacheEnabled) {
|
|
198
|
+
const cacheInputs: CacheKeyInputs = {
|
|
199
|
+
user: prompt.buildUserMessage(),
|
|
200
|
+
data: prompt.getData(),
|
|
201
|
+
system: effectiveSystem,
|
|
202
|
+
model: effectiveModel,
|
|
203
|
+
temperature: effectiveTemperature,
|
|
204
|
+
maxTokens: effectiveMaxTokens,
|
|
205
|
+
tools: this.config.tools,
|
|
206
|
+
mcps: this.config.mcps,
|
|
207
|
+
skills: this.config.skills,
|
|
208
|
+
responseFormat: prompt.getResponseFormat(),
|
|
209
|
+
};
|
|
210
|
+
cacheKey = generateCacheKey(cacheInputs);
|
|
211
|
+
|
|
212
|
+
const cached = await defaultCache.get(cacheKey) as PromptResult<T> | undefined;
|
|
213
|
+
if (cached) {
|
|
214
|
+
// Emit cache hit event
|
|
215
|
+
if (ctx) {
|
|
216
|
+
this.emitWorkflowEvent({
|
|
217
|
+
type: 'cacheHit',
|
|
218
|
+
key: cacheKey,
|
|
219
|
+
node: ctx.workflowNode,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
return cached;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Emit cache miss event
|
|
226
|
+
if (ctx) {
|
|
227
|
+
this.emitWorkflowEvent({
|
|
228
|
+
type: 'cacheMiss',
|
|
229
|
+
key: cacheKey,
|
|
230
|
+
node: ctx.workflowNode,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Emit prompt start event if in workflow context
|
|
236
|
+
if (ctx) {
|
|
237
|
+
this.emitWorkflowEvent({
|
|
238
|
+
type: 'agentPromptStart',
|
|
239
|
+
agentId: this.id,
|
|
240
|
+
agentName: this.name,
|
|
241
|
+
promptId: prompt.id,
|
|
242
|
+
node: ctx.workflowNode,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const effectiveTools = this.mergeTools(
|
|
247
|
+
prompt.toolsOverride ?? overrides?.tools ?? this.config.tools
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
const effectiveHooks = this.mergeHooks(
|
|
251
|
+
prompt.hooksOverride,
|
|
252
|
+
overrides?.hooks,
|
|
253
|
+
this.config.hooks
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
const effectiveStop = overrides?.stop;
|
|
257
|
+
|
|
258
|
+
// Set up environment variables
|
|
259
|
+
const originalEnv = this.setupEnvironment(overrides?.env ?? this.config.env);
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
// Call session start hooks
|
|
263
|
+
await this.callHooks(effectiveHooks?.sessionStart, {
|
|
264
|
+
agentId: this.id,
|
|
265
|
+
agentName: this.name,
|
|
266
|
+
} as SessionStartContext);
|
|
267
|
+
|
|
268
|
+
// Build initial messages
|
|
269
|
+
const messages: Message[] = [
|
|
270
|
+
{ role: 'user', content: prompt.buildUserMessage() },
|
|
271
|
+
];
|
|
272
|
+
|
|
273
|
+
// Execute conversation loop
|
|
274
|
+
let response = await this.callApi(
|
|
275
|
+
messages,
|
|
276
|
+
effectiveSystem,
|
|
277
|
+
effectiveTools,
|
|
278
|
+
effectiveModel,
|
|
279
|
+
effectiveMaxTokens,
|
|
280
|
+
effectiveTemperature,
|
|
281
|
+
effectiveStop
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
totalUsage = this.addUsage(totalUsage, response.usage);
|
|
285
|
+
|
|
286
|
+
// Handle tool use loop
|
|
287
|
+
while (response.stop_reason === 'tool_use') {
|
|
288
|
+
const toolUseBlocks = response.content.filter(
|
|
289
|
+
(block): block is Anthropic.ToolUseBlock => block.type === 'tool_use'
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
const toolResults: Anthropic.ToolResultBlockParam[] = [];
|
|
293
|
+
|
|
294
|
+
for (const toolUse of toolUseBlocks) {
|
|
295
|
+
toolCallCount++;
|
|
296
|
+
|
|
297
|
+
// Call pre-tool hooks
|
|
298
|
+
await this.callHooks(effectiveHooks?.preToolUse, {
|
|
299
|
+
toolName: toolUse.name,
|
|
300
|
+
toolInput: toolUse.input,
|
|
301
|
+
agentId: this.id,
|
|
302
|
+
} as PreToolUseContext);
|
|
303
|
+
|
|
304
|
+
const toolStartTime = Date.now();
|
|
305
|
+
|
|
306
|
+
// Execute tool
|
|
307
|
+
const result = await this.executeTool(toolUse.name, toolUse.input);
|
|
308
|
+
|
|
309
|
+
const toolDuration = Date.now() - toolStartTime;
|
|
310
|
+
|
|
311
|
+
// Emit tool invocation event if in workflow context
|
|
312
|
+
if (ctx) {
|
|
313
|
+
this.emitWorkflowEvent({
|
|
314
|
+
type: 'toolInvocation',
|
|
315
|
+
toolName: toolUse.name,
|
|
316
|
+
input: toolUse.input,
|
|
317
|
+
output: result,
|
|
318
|
+
duration: toolDuration,
|
|
319
|
+
node: ctx.workflowNode,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Call post-tool hooks
|
|
324
|
+
await this.callHooks(effectiveHooks?.postToolUse, {
|
|
325
|
+
toolName: toolUse.name,
|
|
326
|
+
toolInput: toolUse.input,
|
|
327
|
+
toolOutput: result,
|
|
328
|
+
agentId: this.id,
|
|
329
|
+
duration: toolDuration,
|
|
330
|
+
} as PostToolUseContext);
|
|
331
|
+
|
|
332
|
+
toolResults.push({
|
|
333
|
+
type: 'tool_result',
|
|
334
|
+
tool_use_id: toolUse.id,
|
|
335
|
+
content:
|
|
336
|
+
typeof result === 'string' ? result : JSON.stringify(result),
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Add assistant message with tool uses
|
|
341
|
+
messages.push({ role: 'assistant', content: response.content });
|
|
342
|
+
|
|
343
|
+
// Add tool results
|
|
344
|
+
messages.push({ role: 'user', content: toolResults });
|
|
345
|
+
|
|
346
|
+
// Continue conversation
|
|
347
|
+
response = await this.callApi(
|
|
348
|
+
messages,
|
|
349
|
+
effectiveSystem,
|
|
350
|
+
effectiveTools,
|
|
351
|
+
effectiveModel,
|
|
352
|
+
effectiveMaxTokens,
|
|
353
|
+
effectiveTemperature,
|
|
354
|
+
effectiveStop
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
totalUsage = this.addUsage(totalUsage, response.usage);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Extract text response
|
|
361
|
+
const textContent = response.content.find(
|
|
362
|
+
(block): block is Anthropic.TextBlock => block.type === 'text'
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
if (!textContent) {
|
|
366
|
+
throw new Error('No text response received from API');
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Parse JSON from response
|
|
370
|
+
const jsonMatch = textContent.text.match(/\{[\s\S]*\}/);
|
|
371
|
+
if (!jsonMatch) {
|
|
372
|
+
throw new Error('No JSON object found in response');
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
376
|
+
|
|
377
|
+
// Validate with schema
|
|
378
|
+
const validated = prompt.validateResponse(parsed);
|
|
379
|
+
|
|
380
|
+
// Call session end hooks
|
|
381
|
+
await this.callHooks(effectiveHooks?.sessionEnd, {
|
|
382
|
+
agentId: this.id,
|
|
383
|
+
agentName: this.name,
|
|
384
|
+
totalDuration: Date.now() - startTime,
|
|
385
|
+
} as SessionEndContext);
|
|
386
|
+
|
|
387
|
+
const duration = Date.now() - startTime;
|
|
388
|
+
|
|
389
|
+
// Emit prompt end event if in workflow context
|
|
390
|
+
if (ctx) {
|
|
391
|
+
this.emitWorkflowEvent({
|
|
392
|
+
type: 'agentPromptEnd',
|
|
393
|
+
agentId: this.id,
|
|
394
|
+
agentName: this.name,
|
|
395
|
+
promptId: prompt.id,
|
|
396
|
+
node: ctx.workflowNode,
|
|
397
|
+
duration,
|
|
398
|
+
tokenUsage: totalUsage,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const result: PromptResult<T> = {
|
|
403
|
+
data: validated,
|
|
404
|
+
usage: totalUsage,
|
|
405
|
+
duration,
|
|
406
|
+
toolCalls: toolCallCount,
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
// Store in cache if enabled
|
|
410
|
+
if (cacheEnabled && cacheKey) {
|
|
411
|
+
await defaultCache.set(cacheKey, result, { prefix: this.id });
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return result;
|
|
415
|
+
} finally {
|
|
416
|
+
// Restore environment
|
|
417
|
+
this.restoreEnvironment(originalEnv);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Call the Anthropic API
|
|
423
|
+
*/
|
|
424
|
+
private async callApi(
|
|
425
|
+
messages: Message[],
|
|
426
|
+
system: string | undefined,
|
|
427
|
+
tools: Tool[] | undefined,
|
|
428
|
+
model: string,
|
|
429
|
+
maxTokens: number,
|
|
430
|
+
temperature: number | undefined,
|
|
431
|
+
stop: string[] | undefined
|
|
432
|
+
): Promise<Anthropic.Message> {
|
|
433
|
+
const params: Anthropic.MessageCreateParams = {
|
|
434
|
+
model,
|
|
435
|
+
max_tokens: maxTokens,
|
|
436
|
+
messages,
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
if (system) {
|
|
440
|
+
params.system = system;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (tools && tools.length > 0) {
|
|
444
|
+
params.tools = tools.map((tool) => ({
|
|
445
|
+
name: tool.name,
|
|
446
|
+
description: tool.description,
|
|
447
|
+
input_schema: tool.input_schema as Anthropic.Tool['input_schema'],
|
|
448
|
+
}));
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (temperature !== undefined) {
|
|
452
|
+
params.temperature = temperature;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (stop && stop.length > 0) {
|
|
456
|
+
params.stop_sequences = stop;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return this.client.messages.create(params);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Execute a tool (either direct or via MCP)
|
|
464
|
+
*/
|
|
465
|
+
private async executeTool(name: string, input: unknown): Promise<unknown> {
|
|
466
|
+
// Check if it's an MCP tool
|
|
467
|
+
if (this.mcpHandler.hasTool(name)) {
|
|
468
|
+
const result = await this.mcpHandler.executeTool(name, input);
|
|
469
|
+
if (result.is_error) {
|
|
470
|
+
throw new Error(result.content as string);
|
|
471
|
+
}
|
|
472
|
+
return result.content;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Look for direct tool handler - this would be set by subclasses
|
|
476
|
+
throw new Error(`No handler found for tool '${name}'`);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Merge tools from config and MCP servers
|
|
481
|
+
*/
|
|
482
|
+
private mergeTools(configTools?: Tool[]): Tool[] | undefined {
|
|
483
|
+
const mcpTools = this.mcpHandler.getTools();
|
|
484
|
+
const directTools = configTools ?? [];
|
|
485
|
+
|
|
486
|
+
const allTools = [...directTools, ...mcpTools];
|
|
487
|
+
return allTools.length > 0 ? allTools : undefined;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Merge hooks from multiple sources
|
|
492
|
+
*/
|
|
493
|
+
private mergeHooks(
|
|
494
|
+
promptHooks?: AgentHooks,
|
|
495
|
+
overrideHooks?: AgentHooks,
|
|
496
|
+
configHooks?: AgentHooks
|
|
497
|
+
): AgentHooks {
|
|
498
|
+
return {
|
|
499
|
+
preToolUse: [
|
|
500
|
+
...(configHooks?.preToolUse ?? []),
|
|
501
|
+
...(overrideHooks?.preToolUse ?? []),
|
|
502
|
+
...(promptHooks?.preToolUse ?? []),
|
|
503
|
+
],
|
|
504
|
+
postToolUse: [
|
|
505
|
+
...(configHooks?.postToolUse ?? []),
|
|
506
|
+
...(overrideHooks?.postToolUse ?? []),
|
|
507
|
+
...(promptHooks?.postToolUse ?? []),
|
|
508
|
+
],
|
|
509
|
+
sessionStart: [
|
|
510
|
+
...(configHooks?.sessionStart ?? []),
|
|
511
|
+
...(overrideHooks?.sessionStart ?? []),
|
|
512
|
+
...(promptHooks?.sessionStart ?? []),
|
|
513
|
+
],
|
|
514
|
+
sessionEnd: [
|
|
515
|
+
...(configHooks?.sessionEnd ?? []),
|
|
516
|
+
...(overrideHooks?.sessionEnd ?? []),
|
|
517
|
+
...(promptHooks?.sessionEnd ?? []),
|
|
518
|
+
],
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Call hooks of a specific type
|
|
524
|
+
*/
|
|
525
|
+
private async callHooks<T>(
|
|
526
|
+
hooks: ((context: T) => Promise<void> | void)[] | undefined,
|
|
527
|
+
context: T
|
|
528
|
+
): Promise<void> {
|
|
529
|
+
if (!hooks) return;
|
|
530
|
+
for (const hook of hooks) {
|
|
531
|
+
await hook(context);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Set up environment variables
|
|
537
|
+
*/
|
|
538
|
+
private setupEnvironment(
|
|
539
|
+
env?: Record<string, string>
|
|
540
|
+
): Record<string, string | undefined> {
|
|
541
|
+
if (!env) return {};
|
|
542
|
+
|
|
543
|
+
const original: Record<string, string | undefined> = {};
|
|
544
|
+
for (const [key, value] of Object.entries(env)) {
|
|
545
|
+
original[key] = process.env[key];
|
|
546
|
+
process.env[key] = value;
|
|
547
|
+
}
|
|
548
|
+
return original;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Restore environment variables
|
|
553
|
+
*/
|
|
554
|
+
private restoreEnvironment(original: Record<string, string | undefined>): void {
|
|
555
|
+
for (const [key, value] of Object.entries(original)) {
|
|
556
|
+
if (value === undefined) {
|
|
557
|
+
delete process.env[key];
|
|
558
|
+
} else {
|
|
559
|
+
process.env[key] = value;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Add token usage from response
|
|
566
|
+
*/
|
|
567
|
+
private addUsage(total: TokenUsage, usage: Anthropic.Usage): TokenUsage {
|
|
568
|
+
return {
|
|
569
|
+
input_tokens: total.input_tokens + usage.input_tokens,
|
|
570
|
+
output_tokens: total.output_tokens + usage.output_tokens,
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentExecutionContext - Provides zero-plumbing context propagation
|
|
3
|
+
*
|
|
4
|
+
* Uses Node.js AsyncLocalStorage to automatically propagate context
|
|
5
|
+
* through async call chains without manual passing.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
9
|
+
import type { WorkflowNode, WorkflowEvent } from '../types/index.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Context available during agent/prompt execution
|
|
13
|
+
*/
|
|
14
|
+
export interface AgentExecutionContext {
|
|
15
|
+
/** Current workflow node in the hierarchy */
|
|
16
|
+
workflowNode: WorkflowNode;
|
|
17
|
+
|
|
18
|
+
/** Function to emit events to the workflow tree */
|
|
19
|
+
emitEvent: (event: WorkflowEvent) => void;
|
|
20
|
+
|
|
21
|
+
/** Workflow ID for tracking */
|
|
22
|
+
workflowId: string;
|
|
23
|
+
|
|
24
|
+
/** Parent workflow ID if nested */
|
|
25
|
+
parentWorkflowId?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Global AsyncLocalStorage instance for execution context
|
|
30
|
+
* Single shared instance per application (recommended pattern)
|
|
31
|
+
*/
|
|
32
|
+
const executionContext = new AsyncLocalStorage<AgentExecutionContext>();
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get the current execution context
|
|
36
|
+
* @returns Current context or undefined if not in a workflow
|
|
37
|
+
*/
|
|
38
|
+
export function getExecutionContext(): AgentExecutionContext | undefined {
|
|
39
|
+
return executionContext.getStore();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get the current execution context, throwing if not available
|
|
44
|
+
* @param operation Name of the operation requiring context
|
|
45
|
+
* @returns Current context
|
|
46
|
+
* @throws Error if not within a workflow context
|
|
47
|
+
*/
|
|
48
|
+
export function requireExecutionContext(
|
|
49
|
+
operation: string
|
|
50
|
+
): AgentExecutionContext {
|
|
51
|
+
const context = executionContext.getStore();
|
|
52
|
+
if (!context) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`${operation} called outside of workflow context. ` +
|
|
55
|
+
`Agent/Prompt operations must be executed within a workflow step.`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return context;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Run a function within an execution context
|
|
63
|
+
* @param context Context to establish
|
|
64
|
+
* @param fn Function to execute
|
|
65
|
+
* @returns Result of the function
|
|
66
|
+
*/
|
|
67
|
+
export async function runInContext<T>(
|
|
68
|
+
context: AgentExecutionContext,
|
|
69
|
+
fn: () => Promise<T>
|
|
70
|
+
): Promise<T> {
|
|
71
|
+
return executionContext.run(context, fn);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Run a synchronous function within an execution context
|
|
76
|
+
* @param context Context to establish
|
|
77
|
+
* @param fn Function to execute
|
|
78
|
+
* @returns Result of the function
|
|
79
|
+
*/
|
|
80
|
+
export function runInContextSync<T>(
|
|
81
|
+
context: AgentExecutionContext,
|
|
82
|
+
fn: () => T
|
|
83
|
+
): T {
|
|
84
|
+
return executionContext.run(context, fn);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Check if currently within an execution context
|
|
89
|
+
* @returns true if context is available
|
|
90
|
+
*/
|
|
91
|
+
export function hasExecutionContext(): boolean {
|
|
92
|
+
return executionContext.getStore() !== undefined;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Create a child context with updated node
|
|
97
|
+
* @param childNode The new workflow node for the child context
|
|
98
|
+
* @returns New context with child node
|
|
99
|
+
*/
|
|
100
|
+
export function createChildContext(
|
|
101
|
+
childNode: WorkflowNode
|
|
102
|
+
): AgentExecutionContext | undefined {
|
|
103
|
+
const parent = getExecutionContext();
|
|
104
|
+
if (!parent) {
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
workflowNode: childNode,
|
|
110
|
+
emitEvent: parent.emitEvent,
|
|
111
|
+
workflowId: parent.workflowId,
|
|
112
|
+
parentWorkflowId: parent.parentWorkflowId,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Export the raw AsyncLocalStorage for advanced use cases
|
|
118
|
+
*/
|
|
119
|
+
export { executionContext as agentExecutionStorage };
|