agentsdk-agent 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.
- package/LICENSE +21 -0
- package/README.md +290 -0
- package/dist/chunk-55J6XMHW.js +3 -0
- package/dist/chunk-55J6XMHW.js.map +1 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.js +283 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +90 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +58 -0
- package/src/handoff.ts +116 -0
- package/src/index.ts +9 -0
- package/src/memory.ts +62 -0
- package/src/runtime.ts +277 -0
- package/src/types.ts +101 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/memory.ts","../src/runtime.ts","../src/handoff.ts"],"names":["AgentSDKError"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * AgentSDK Agent Runtime — Memory strategies\n * MIT License — see LICENSE. Copyright (c) 2026 Shetan Mehra.\n *\n * Reduces the message history between steps according to the agent's\n * `memoryStrategy`. Each function returns the (possibly trimmed) history.\n */\n\nimport type { MemoryStrategy } from './types';\nimport type { Message } from 'agentsdk-core';\n\n/** Apply the memory strategy to a message history. */\nexport function applyMemory(\n messages: Message[],\n strategy: MemoryStrategy,\n windowSize = 8\n): Message[] {\n switch (strategy) {\n case 'none':\n // Keep only the system prompt + the last user message.\n return keepHeadAndLastUser(messages);\n case 'full':\n return messages;\n case 'summary':\n // 'summary' is not yet implemented. Rather than silently degrading to\n // 'window' (which would surprise users whose prompts relied on full\n // history being summarised), we fall back to 'window' behaviour and\n // emit a one-time warning so they know to switch strategies.\n warnSummaryDeprecated();\n return applyWindow(messages, windowSize);\n case 'window':\n default:\n return applyWindow(messages, windowSize);\n }\n}\n\nfunction applyWindow(messages: Message[], windowSize: number): Message[] {\n // Always keep the system message(s) at the head.\n const system: Message[] = [];\n const rest: Message[] = [];\n for (const m of messages) {\n (m.role === 'system' ? system : rest).push(m);\n }\n const tail = rest.slice(-Math.max(1, windowSize));\n return [...system, ...tail];\n}\n\nfunction keepHeadAndLastUser(messages: Message[]): Message[] {\n const system = messages.filter((m) => m.role === 'system');\n const lastUser = [...messages].reverse().find((m) => m.role === 'user');\n return [...system, ...(lastUser ? [lastUser] : [])];\n}\n\nlet summaryWarned = false;\nfunction warnSummaryDeprecated() {\n if (summaryWarned) return;\n summaryWarned = true;\n console.warn(\n '[agentsdk:agent] memoryStrategy \"summary\" is not yet implemented; ' +\n 'falling back to \"window\" behaviour. Use \"window\" or \"full\" to silence this warning.'\n );\n}\n","/**\n * AgentSDK Agent Runtime — Step loop\n * MIT License — see LICENSE. Copyright (c) 2026 Shetan Mehra.\n *\n * The core agent execution loop:\n * 1. Seed history with the agent's system prompt + the input messages.\n * 2. Call the provider's generateText (with automatic retry on transient errors).\n * 3. If the model emitted tool calls, execute them in PARALLEL and append\n * tool results.\n * 4. Otherwise, the agent is done — return the final text.\n * 5. Apply the memory strategy before each step to bound history size.\n * 6. Stop when maxSteps is hit or the model stops calling tools.\n */\n\nimport { addCost, addUsage } from 'agentsdk-core';\nimport { generateText } from 'agentsdk-core';\nimport { AgentSDKError, normalizeError } from 'agentsdk-core';\nimport type {\n Cost,\n Message,\n ToolCall,\n ToolResult,\n Usage,\n} from 'agentsdk-core';\nimport type {\n AgentConfig,\n AgentRunOptions,\n AgentRunResult,\n StepEvent,\n StepHook,\n} from './types';\nimport { applyMemory } from './memory';\n\nconst DEFAULT_MAX_STEPS = 10;\nconst DEFAULT_MAX_RETRIES = 2;\nconst RETRYABLE_TYPES = new Set(['rate_limit', 'server', 'timeout']);\nconst RETRY_BASE_DELAY_MS = 800;\n\nexport async function runAgent(\n config: AgentConfig,\n options: AgentRunOptions\n): Promise<AgentRunResult> {\n const maxSteps = options.maxSteps ?? config.maxSteps ?? DEFAULT_MAX_STEPS;\n const runId = options.runId ?? makeRunId();\n const onStep = options.onStepFinish;\n\n // Seed history.\n let history: Message[] = [\n { role: 'system', content: config.systemPrompt },\n ...options.messages,\n ];\n\n const steps: StepEvent[] = [];\n let totalUsage: Usage = {\n promptTokens: 0,\n completionTokens: 0,\n totalTokens: 0,\n };\n let totalCost: Cost = { input: 0, output: 0, total: 0, currency: 'USD' };\n let finalText = '';\n let truncated = false;\n\n for (let step = 1; step <= maxSteps; step++) {\n // Apply memory strategy before the call.\n const memStrategy = config.memoryStrategy ?? 'full';\n const windowSize = config.memoryWindow ?? 8;\n const visible = applyMemory(history, memStrategy, windowSize);\n\n let event: StepEvent;\n try {\n const result = await generateTextWithRetry(\n {\n model: config.model,\n messages: visible,\n tools: config.tools,\n temperature: config.temperature,\n maxTokens: config.maxTokens,\n thinking: config.thinking,\n providerOptions: config.providerOptions,\n },\n { provider: config.provider }\n );\n\n totalUsage = addUsage(totalUsage, result.usage);\n totalCost = addCost(totalCost, result.cost);\n\n // Append the assistant turn to history.\n const assistantMsg: Message = {\n role: 'assistant',\n content: result.text,\n toolCalls: result.toolCalls.length ? result.toolCalls : undefined,\n };\n history.push(assistantMsg);\n\n if (result.toolCalls.length === 0) {\n // Pure text answer — done.\n finalText = result.text;\n event = {\n step,\n kind: 'text',\n text: result.text,\n usage: result.usage,\n cost: result.cost,\n timestamp: new Date().toISOString(),\n };\n steps.push(event);\n await onStep?.(event);\n break;\n }\n\n // Execute tool calls IN PARALLEL. Tools are independent of each other\n // (the model emitted them all in one turn) so running them concurrently\n // is safe and dramatically speeds up agents that call multiple tools\n // (e.g. several web searches in one step).\n const toolResults = await Promise.all(\n result.toolCalls.map((tc) => executeToolCall(tc, config, step, runId))\n );\n\n // Append tool results to history in the SAME ORDER as the model emitted\n // them — order matters for the next LLM turn's context.\n for (const toolResult of toolResults) {\n history.push({\n role: 'tool',\n content: stringifyToolResult(toolResult.result),\n name: toolResult.name,\n toolCallId: toolResult.toolCallId,\n });\n }\n\n event = {\n step,\n kind: 'tool_call',\n text: result.text,\n toolCalls: result.toolCalls,\n toolResults,\n usage: result.usage,\n cost: result.cost,\n timestamp: new Date().toISOString(),\n };\n steps.push(event);\n await onStep?.(event);\n\n // If this was the last allowed step and we still called tools, mark truncated.\n if (step === maxSteps) truncated = true;\n } catch (err) {\n const norm = normalizeError(err, config.provider);\n event = {\n step,\n kind: 'error',\n error: norm.message,\n timestamp: new Date().toISOString(),\n };\n steps.push(event);\n await onStep?.(event);\n // Re-throw after the hook has had a chance to log.\n throw norm;\n }\n }\n\n // If the loop ended without a final text (e.g. hit maxSteps mid-tool-call),\n // synthesise a final answer by running one more non-tool generation.\n if (!finalText && steps.length > 0) {\n const last = await generateTextWithRetry(\n {\n model: config.model,\n messages: [\n ...applyMemory(history, config.memoryStrategy ?? 'full', config.memoryWindow ?? 8),\n {\n role: 'system',\n content:\n 'Summarise the result of the previous tool calls in a concise final answer for the user.',\n },\n ],\n temperature: config.temperature,\n maxTokens: config.maxTokens,\n providerOptions: config.providerOptions,\n },\n { provider: config.provider }\n );\n finalText = last.text;\n totalUsage = addUsage(totalUsage, last.usage);\n totalCost = addCost(totalCost, last.cost);\n }\n\n return {\n agentId: config.id,\n text: finalText,\n messages: history,\n steps,\n usage: totalUsage,\n cost: totalCost,\n stepsExecuted: steps.length,\n truncated,\n };\n}\n\n/**\n * Wrap generateText with automatic retry on rate_limit / server / timeout\n * errors. Uses exponential backoff with jitter. Non-retryable errors are\n * re-thrown immediately.\n */\nasync function generateTextWithRetry(\n params: Parameters<typeof generateText>[0],\n opts: Parameters<typeof generateText>[1]\n): ReturnType<typeof generateText> {\n const maxRetries = DEFAULT_MAX_RETRIES;\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return await generateText(params, opts);\n } catch (err) {\n lastError = err;\n const providerId = opts?.provider;\n const norm = err instanceof AgentSDKError\n ? err\n : normalizeError(err, providerId);\n\n // Don't retry on the last attempt or non-retryable errors.\n if (attempt === maxRetries || !RETRYABLE_TYPES.has(norm.type)) {\n throw norm;\n }\n\n // Exponential backoff with jitter: 800ms, 1600ms, ... +/- 20% jitter.\n const baseDelay = RETRY_BASE_DELAY_MS * Math.pow(2, attempt);\n const jitter = baseDelay * (0.8 + Math.random() * 0.4);\n await new Promise((resolve) => setTimeout(resolve, jitter));\n }\n }\n\n // Should be unreachable, but keeps TS happy.\n throw lastError;\n}\n\nasync function executeToolCall(\n call: ToolCall,\n config: AgentConfig,\n step: number,\n runId: string\n): Promise<ToolResult> {\n const tool = config.tools?.find((t) => t.name === call.name);\n if (!tool || typeof tool.execute !== 'function') {\n return {\n toolCallId: call.id,\n name: call.name,\n result: { error: `Tool \"${call.name}\" is not defined or has no executor.` },\n isError: true,\n };\n }\n try {\n const out = await tool.execute(call.arguments, { runId, step });\n return { toolCallId: call.id, name: call.name, result: out };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n toolCallId: call.id,\n name: call.name,\n result: { error: msg },\n isError: true,\n };\n }\n}\n\nfunction stringifyToolResult(result: unknown): string {\n if (typeof result === 'string') return result;\n try {\n return JSON.stringify(result);\n } catch {\n return String(result);\n }\n}\n\nfunction makeRunId(): string {\n return `run_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;\n}\n\nexport type { StepHook };\n","/**\n * AgentSDK Agent Runtime — Agent-as-tool handoff\n * MIT License — see LICENSE. Copyright (c) 2026 Shetan Mehra.\n *\n * The core differentiator vs Vercel AI SDK: an agent can be exposed as a\n * callable \"tool\" to another agent. When the parent agent invokes the tool,\n * the child agent runs to completion and its final text is returned as the\n * tool result — the parent then continues with that context.\n *\n * This enables true multi-agent orchestration: a \"router\" agent can hand off\n * to specialised agents (researcher, writer, critic) as if they were tools.\n */\n\nimport { AgentSDKError } from 'agentsdk-core';\nimport type { ResolvedTool } from 'agentsdk-core';\nimport type { AgentConfig, AgentRunOptions } from './types';\nimport { runAgent } from './runtime';\n\nexport interface HandoffOptions {\n /** Description the parent agent sees for this \"tool\". */\n description?: string;\n /** Input schema describing what the parent should pass. Defaults to a single `input` string. */\n parameters?: Record<string, unknown>;\n /** The argument key that becomes the user message to the child agent. Default: \"input\". */\n inputKey?: string;\n /** Extra seed messages to prepend for the child agent (beyond the input). */\n seedMessages?: import('agentsdk-core').Message[];\n /** Hook fired for every child step (for live UI updates). */\n onChildStep?: AgentRunOptions['onStepFinish'];\n /** Maximum handoff depth before recursion is rejected. Default: 5. */\n maxDepth?: number;\n}\n\n/**\n * Wrap an agent as a tool that another agent can call. The resulting tool can\n * be appended to a parent agent's `tools` array.\n *\n * Includes a recursion guard: if the same child agent appears more than\n * `maxDepth` times in the call chain (e.g. A→B→A→B→...), the tool throws\n * instead of looping forever.\n */\nexport function asTool(\n child: AgentConfig,\n opts: HandoffOptions = {}\n): ResolvedTool {\n const inputKey = opts.inputKey ?? 'input';\n const maxDepth = opts.maxDepth ?? 5;\n\n return {\n name: child.id,\n description:\n opts.description ??\n `Hand off to the \"${child.name}\" agent. Pass a natural-language ${inputKey} describing what the child agent should do.`,\n parameters:\n opts.parameters ?? {\n type: 'object',\n properties: {\n [inputKey]: {\n type: 'string',\n description: 'The task or question to delegate to this agent.',\n },\n },\n required: [inputKey],\n additionalProperties: false,\n },\n async execute(args, ctx) {\n // Recursion guard: the runId encodes the agent call chain as\n // \"run_xxx:childA:childB:...\". Count how many times `child.id` already\n // appears — if it exceeds maxDepth, we're in a cycle.\n const chain = ctx.runId.split(':');\n const occurrences = chain.filter((x) => x === child.id).length;\n if (occurrences > maxDepth) {\n throw new AgentSDKError(\n `Handoff recursion limit exceeded: agent \"${child.id}\" appears ${occurrences} times ` +\n `in the call chain (max ${maxDepth}). This usually means two agents are handing off ` +\n `to each other in a loop. Check your agent tool lists for cycles.`,\n { type: 'invalid_request' }\n );\n }\n\n const input = String(args?.[inputKey] ?? '');\n const messages = [\n ...(opts.seedMessages ?? []),\n { role: 'user' as const, content: input },\n ];\n const result = await runAgent(child, {\n messages,\n runId: `${ctx.runId}:${child.id}`,\n onStepFinish: opts.onChildStep,\n });\n // Return a structured payload so the parent agent has rich context.\n return {\n text: result.text,\n agentId: result.agentId,\n stepsExecuted: result.stepsExecuted,\n usage: result.usage,\n cost: result.cost,\n truncated: result.truncated,\n };\n },\n };\n}\n\n/**\n * Convenience: build a parent agent's tool list by mixing ordinary tools with\n * a set of child agents (each converted to a tool via `asTool`).\n */\nexport function withHandoffs(\n baseTools: ResolvedTool[],\n children: Array<{ agent: AgentConfig; opts?: HandoffOptions }>\n): ResolvedTool[] {\n return [\n ...baseTools,\n ...children.map(({ agent, opts }) => asTool(agent, opts)),\n ];\n}\n"]}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { ResolvedTool, Message, ToolCall, ToolResult, Usage, Cost } from 'agentsdk-core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AgentSDK Agent Runtime — Types
|
|
5
|
+
* MIT License — see LICENSE. Copyright (c) 2026 Shetan Mehra.
|
|
6
|
+
*
|
|
7
|
+
* An "agent" is a model + system prompt + tools + a step loop. Each step the
|
|
8
|
+
* model may emit text or tool calls; tool calls are executed and their results
|
|
9
|
+
* fed back, until the model stops calling tools or maxSteps is reached.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
type MemoryStrategy = 'none' | 'full' | 'summary' | 'window';
|
|
13
|
+
interface AgentConfig {
|
|
14
|
+
/** Stable id for this agent within a graph. */
|
|
15
|
+
id: string;
|
|
16
|
+
/** Display name. */
|
|
17
|
+
name: string;
|
|
18
|
+
/** Provider id, defaults to "glm". */
|
|
19
|
+
provider?: string;
|
|
20
|
+
/** Model id, e.g. "glm-4.6". */
|
|
21
|
+
model: string;
|
|
22
|
+
/** System prompt. */
|
|
23
|
+
systemPrompt: string;
|
|
24
|
+
/** Tools available to the agent. */
|
|
25
|
+
tools?: ResolvedTool[];
|
|
26
|
+
/** Max number of LLM round-trips before the loop is forced to stop. */
|
|
27
|
+
maxSteps?: number;
|
|
28
|
+
/** Sampling temperature. */
|
|
29
|
+
temperature?: number;
|
|
30
|
+
/** Max tokens per step. */
|
|
31
|
+
maxTokens?: number;
|
|
32
|
+
/** Enable thinking/reasoning mode if the provider supports it. */
|
|
33
|
+
thinking?: boolean;
|
|
34
|
+
/** How conversation history is carried between steps. */
|
|
35
|
+
memoryStrategy?: MemoryStrategy;
|
|
36
|
+
/** For 'window' strategy: how many most-recent messages to keep. */
|
|
37
|
+
memoryWindow?: number;
|
|
38
|
+
/** Extra provider-specific options. */
|
|
39
|
+
providerOptions?: Record<string, unknown>;
|
|
40
|
+
}
|
|
41
|
+
type StepKind = 'text' | 'tool_call' | 'tool_result' | 'error';
|
|
42
|
+
interface StepEvent {
|
|
43
|
+
/** 1-indexed step number. */
|
|
44
|
+
step: number;
|
|
45
|
+
kind: StepKind;
|
|
46
|
+
/** The assistant message produced at this step (if any). */
|
|
47
|
+
text?: string;
|
|
48
|
+
/** Tool calls requested by the model at this step (if any). */
|
|
49
|
+
toolCalls?: ToolCall[];
|
|
50
|
+
/** Tool results produced this step (if any). */
|
|
51
|
+
toolResults?: ToolResult[];
|
|
52
|
+
/** Error message, if kind === 'error'. */
|
|
53
|
+
error?: string;
|
|
54
|
+
/** Token usage for this step's LLM call. */
|
|
55
|
+
usage?: Usage;
|
|
56
|
+
/** Cost for this step's LLM call. */
|
|
57
|
+
cost?: Cost;
|
|
58
|
+
/** ISO timestamp. */
|
|
59
|
+
timestamp: string;
|
|
60
|
+
}
|
|
61
|
+
interface AgentRunResult {
|
|
62
|
+
agentId: string;
|
|
63
|
+
/** Final text answer from the agent. */
|
|
64
|
+
text: string;
|
|
65
|
+
/** Full message history (post-run). */
|
|
66
|
+
messages: Message[];
|
|
67
|
+
/** Every step event, in order. */
|
|
68
|
+
steps: StepEvent[];
|
|
69
|
+
/** Aggregated token usage across all steps. */
|
|
70
|
+
usage: Usage;
|
|
71
|
+
/** Aggregated cost across all steps. */
|
|
72
|
+
cost: Cost;
|
|
73
|
+
/** Number of steps actually executed. */
|
|
74
|
+
stepsExecuted: number;
|
|
75
|
+
/** True if the loop hit maxSteps without a clean stop. */
|
|
76
|
+
truncated: boolean;
|
|
77
|
+
}
|
|
78
|
+
type StepHook = (event: StepEvent) => void | Promise<void>;
|
|
79
|
+
interface AgentRunOptions {
|
|
80
|
+
/** The user/input message(s) to seed the run. */
|
|
81
|
+
messages: Message[];
|
|
82
|
+
/** Called after each step completes. */
|
|
83
|
+
onStepFinish?: StepHook;
|
|
84
|
+
/** Optional run id (auto-generated if omitted). */
|
|
85
|
+
runId?: string;
|
|
86
|
+
/** Override maxSteps for this run only. */
|
|
87
|
+
maxSteps?: number;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export type { AgentConfig, AgentRunOptions, AgentRunResult, MemoryStrategy, StepEvent, StepHook, StepKind };
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"types.js"}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agentsdk-agent",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Multi-step agent runtime for AgentSDK — step loop, hooks, memory strategies, agent-as-tool handoff.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "shetanmehra (https://github.com/shetanmehra)",
|
|
7
|
+
"homepage": "https://github.com/shetanmehra/agentsdk#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/shetanmehra/agentsdk.git",
|
|
11
|
+
"directory": "packages/agent"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/shetanmehra/agentsdk/issues"
|
|
15
|
+
},
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"import": "./dist/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./types": {
|
|
25
|
+
"types": "./dist/types.d.ts",
|
|
26
|
+
"import": "./dist/types.js"
|
|
27
|
+
},
|
|
28
|
+
"./package.json": "./package.json"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"src",
|
|
33
|
+
"README.md",
|
|
34
|
+
"LICENSE"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsup",
|
|
38
|
+
"clean": "rm -rf dist"
|
|
39
|
+
},
|
|
40
|
+
"keywords": [
|
|
41
|
+
"ai",
|
|
42
|
+
"agent",
|
|
43
|
+
"llm",
|
|
44
|
+
"sdk",
|
|
45
|
+
"orchestration"
|
|
46
|
+
],
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"agentsdk-core": "^0.1.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "^22.10.0",
|
|
52
|
+
"tsup": "^8.5.1",
|
|
53
|
+
"typescript": "^5.7.3"
|
|
54
|
+
},
|
|
55
|
+
"publishConfig": {
|
|
56
|
+
"access": "public"
|
|
57
|
+
}
|
|
58
|
+
}
|
package/src/handoff.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentSDK Agent Runtime — Agent-as-tool handoff
|
|
3
|
+
* MIT License — see LICENSE. Copyright (c) 2026 Shetan Mehra.
|
|
4
|
+
*
|
|
5
|
+
* The core differentiator vs Vercel AI SDK: an agent can be exposed as a
|
|
6
|
+
* callable "tool" to another agent. When the parent agent invokes the tool,
|
|
7
|
+
* the child agent runs to completion and its final text is returned as the
|
|
8
|
+
* tool result — the parent then continues with that context.
|
|
9
|
+
*
|
|
10
|
+
* This enables true multi-agent orchestration: a "router" agent can hand off
|
|
11
|
+
* to specialised agents (researcher, writer, critic) as if they were tools.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { AgentSDKError } from 'agentsdk-core';
|
|
15
|
+
import type { ResolvedTool } from 'agentsdk-core';
|
|
16
|
+
import type { AgentConfig, AgentRunOptions } from './types';
|
|
17
|
+
import { runAgent } from './runtime';
|
|
18
|
+
|
|
19
|
+
export interface HandoffOptions {
|
|
20
|
+
/** Description the parent agent sees for this "tool". */
|
|
21
|
+
description?: string;
|
|
22
|
+
/** Input schema describing what the parent should pass. Defaults to a single `input` string. */
|
|
23
|
+
parameters?: Record<string, unknown>;
|
|
24
|
+
/** The argument key that becomes the user message to the child agent. Default: "input". */
|
|
25
|
+
inputKey?: string;
|
|
26
|
+
/** Extra seed messages to prepend for the child agent (beyond the input). */
|
|
27
|
+
seedMessages?: import('agentsdk-core').Message[];
|
|
28
|
+
/** Hook fired for every child step (for live UI updates). */
|
|
29
|
+
onChildStep?: AgentRunOptions['onStepFinish'];
|
|
30
|
+
/** Maximum handoff depth before recursion is rejected. Default: 5. */
|
|
31
|
+
maxDepth?: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Wrap an agent as a tool that another agent can call. The resulting tool can
|
|
36
|
+
* be appended to a parent agent's `tools` array.
|
|
37
|
+
*
|
|
38
|
+
* Includes a recursion guard: if the same child agent appears more than
|
|
39
|
+
* `maxDepth` times in the call chain (e.g. A→B→A→B→...), the tool throws
|
|
40
|
+
* instead of looping forever.
|
|
41
|
+
*/
|
|
42
|
+
export function asTool(
|
|
43
|
+
child: AgentConfig,
|
|
44
|
+
opts: HandoffOptions = {}
|
|
45
|
+
): ResolvedTool {
|
|
46
|
+
const inputKey = opts.inputKey ?? 'input';
|
|
47
|
+
const maxDepth = opts.maxDepth ?? 5;
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
name: child.id,
|
|
51
|
+
description:
|
|
52
|
+
opts.description ??
|
|
53
|
+
`Hand off to the "${child.name}" agent. Pass a natural-language ${inputKey} describing what the child agent should do.`,
|
|
54
|
+
parameters:
|
|
55
|
+
opts.parameters ?? {
|
|
56
|
+
type: 'object',
|
|
57
|
+
properties: {
|
|
58
|
+
[inputKey]: {
|
|
59
|
+
type: 'string',
|
|
60
|
+
description: 'The task or question to delegate to this agent.',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
required: [inputKey],
|
|
64
|
+
additionalProperties: false,
|
|
65
|
+
},
|
|
66
|
+
async execute(args, ctx) {
|
|
67
|
+
// Recursion guard: the runId encodes the agent call chain as
|
|
68
|
+
// "run_xxx:childA:childB:...". Count how many times `child.id` already
|
|
69
|
+
// appears — if it exceeds maxDepth, we're in a cycle.
|
|
70
|
+
const chain = ctx.runId.split(':');
|
|
71
|
+
const occurrences = chain.filter((x) => x === child.id).length;
|
|
72
|
+
if (occurrences > maxDepth) {
|
|
73
|
+
throw new AgentSDKError(
|
|
74
|
+
`Handoff recursion limit exceeded: agent "${child.id}" appears ${occurrences} times ` +
|
|
75
|
+
`in the call chain (max ${maxDepth}). This usually means two agents are handing off ` +
|
|
76
|
+
`to each other in a loop. Check your agent tool lists for cycles.`,
|
|
77
|
+
{ type: 'invalid_request' }
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const input = String(args?.[inputKey] ?? '');
|
|
82
|
+
const messages = [
|
|
83
|
+
...(opts.seedMessages ?? []),
|
|
84
|
+
{ role: 'user' as const, content: input },
|
|
85
|
+
];
|
|
86
|
+
const result = await runAgent(child, {
|
|
87
|
+
messages,
|
|
88
|
+
runId: `${ctx.runId}:${child.id}`,
|
|
89
|
+
onStepFinish: opts.onChildStep,
|
|
90
|
+
});
|
|
91
|
+
// Return a structured payload so the parent agent has rich context.
|
|
92
|
+
return {
|
|
93
|
+
text: result.text,
|
|
94
|
+
agentId: result.agentId,
|
|
95
|
+
stepsExecuted: result.stepsExecuted,
|
|
96
|
+
usage: result.usage,
|
|
97
|
+
cost: result.cost,
|
|
98
|
+
truncated: result.truncated,
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Convenience: build a parent agent's tool list by mixing ordinary tools with
|
|
106
|
+
* a set of child agents (each converted to a tool via `asTool`).
|
|
107
|
+
*/
|
|
108
|
+
export function withHandoffs(
|
|
109
|
+
baseTools: ResolvedTool[],
|
|
110
|
+
children: Array<{ agent: AgentConfig; opts?: HandoffOptions }>
|
|
111
|
+
): ResolvedTool[] {
|
|
112
|
+
return [
|
|
113
|
+
...baseTools,
|
|
114
|
+
...children.map(({ agent, opts }) => asTool(agent, opts)),
|
|
115
|
+
];
|
|
116
|
+
}
|
package/src/index.ts
ADDED
package/src/memory.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentSDK Agent Runtime — Memory strategies
|
|
3
|
+
* MIT License — see LICENSE. Copyright (c) 2026 Shetan Mehra.
|
|
4
|
+
*
|
|
5
|
+
* Reduces the message history between steps according to the agent's
|
|
6
|
+
* `memoryStrategy`. Each function returns the (possibly trimmed) history.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { MemoryStrategy } from './types';
|
|
10
|
+
import type { Message } from 'agentsdk-core';
|
|
11
|
+
|
|
12
|
+
/** Apply the memory strategy to a message history. */
|
|
13
|
+
export function applyMemory(
|
|
14
|
+
messages: Message[],
|
|
15
|
+
strategy: MemoryStrategy,
|
|
16
|
+
windowSize = 8
|
|
17
|
+
): Message[] {
|
|
18
|
+
switch (strategy) {
|
|
19
|
+
case 'none':
|
|
20
|
+
// Keep only the system prompt + the last user message.
|
|
21
|
+
return keepHeadAndLastUser(messages);
|
|
22
|
+
case 'full':
|
|
23
|
+
return messages;
|
|
24
|
+
case 'summary':
|
|
25
|
+
// 'summary' is not yet implemented. Rather than silently degrading to
|
|
26
|
+
// 'window' (which would surprise users whose prompts relied on full
|
|
27
|
+
// history being summarised), we fall back to 'window' behaviour and
|
|
28
|
+
// emit a one-time warning so they know to switch strategies.
|
|
29
|
+
warnSummaryDeprecated();
|
|
30
|
+
return applyWindow(messages, windowSize);
|
|
31
|
+
case 'window':
|
|
32
|
+
default:
|
|
33
|
+
return applyWindow(messages, windowSize);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function applyWindow(messages: Message[], windowSize: number): Message[] {
|
|
38
|
+
// Always keep the system message(s) at the head.
|
|
39
|
+
const system: Message[] = [];
|
|
40
|
+
const rest: Message[] = [];
|
|
41
|
+
for (const m of messages) {
|
|
42
|
+
(m.role === 'system' ? system : rest).push(m);
|
|
43
|
+
}
|
|
44
|
+
const tail = rest.slice(-Math.max(1, windowSize));
|
|
45
|
+
return [...system, ...tail];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function keepHeadAndLastUser(messages: Message[]): Message[] {
|
|
49
|
+
const system = messages.filter((m) => m.role === 'system');
|
|
50
|
+
const lastUser = [...messages].reverse().find((m) => m.role === 'user');
|
|
51
|
+
return [...system, ...(lastUser ? [lastUser] : [])];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let summaryWarned = false;
|
|
55
|
+
function warnSummaryDeprecated() {
|
|
56
|
+
if (summaryWarned) return;
|
|
57
|
+
summaryWarned = true;
|
|
58
|
+
console.warn(
|
|
59
|
+
'[agentsdk:agent] memoryStrategy "summary" is not yet implemented; ' +
|
|
60
|
+
'falling back to "window" behaviour. Use "window" or "full" to silence this warning.'
|
|
61
|
+
);
|
|
62
|
+
}
|
package/src/runtime.ts
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentSDK Agent Runtime — Step loop
|
|
3
|
+
* MIT License — see LICENSE. Copyright (c) 2026 Shetan Mehra.
|
|
4
|
+
*
|
|
5
|
+
* The core agent execution loop:
|
|
6
|
+
* 1. Seed history with the agent's system prompt + the input messages.
|
|
7
|
+
* 2. Call the provider's generateText (with automatic retry on transient errors).
|
|
8
|
+
* 3. If the model emitted tool calls, execute them in PARALLEL and append
|
|
9
|
+
* tool results.
|
|
10
|
+
* 4. Otherwise, the agent is done — return the final text.
|
|
11
|
+
* 5. Apply the memory strategy before each step to bound history size.
|
|
12
|
+
* 6. Stop when maxSteps is hit or the model stops calling tools.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { addCost, addUsage } from 'agentsdk-core';
|
|
16
|
+
import { generateText } from 'agentsdk-core';
|
|
17
|
+
import { AgentSDKError, normalizeError } from 'agentsdk-core';
|
|
18
|
+
import type {
|
|
19
|
+
Cost,
|
|
20
|
+
Message,
|
|
21
|
+
ToolCall,
|
|
22
|
+
ToolResult,
|
|
23
|
+
Usage,
|
|
24
|
+
} from 'agentsdk-core';
|
|
25
|
+
import type {
|
|
26
|
+
AgentConfig,
|
|
27
|
+
AgentRunOptions,
|
|
28
|
+
AgentRunResult,
|
|
29
|
+
StepEvent,
|
|
30
|
+
StepHook,
|
|
31
|
+
} from './types';
|
|
32
|
+
import { applyMemory } from './memory';
|
|
33
|
+
|
|
34
|
+
const DEFAULT_MAX_STEPS = 10;
|
|
35
|
+
const DEFAULT_MAX_RETRIES = 2;
|
|
36
|
+
const RETRYABLE_TYPES = new Set(['rate_limit', 'server', 'timeout']);
|
|
37
|
+
const RETRY_BASE_DELAY_MS = 800;
|
|
38
|
+
|
|
39
|
+
export async function runAgent(
|
|
40
|
+
config: AgentConfig,
|
|
41
|
+
options: AgentRunOptions
|
|
42
|
+
): Promise<AgentRunResult> {
|
|
43
|
+
const maxSteps = options.maxSteps ?? config.maxSteps ?? DEFAULT_MAX_STEPS;
|
|
44
|
+
const runId = options.runId ?? makeRunId();
|
|
45
|
+
const onStep = options.onStepFinish;
|
|
46
|
+
|
|
47
|
+
// Seed history.
|
|
48
|
+
let history: Message[] = [
|
|
49
|
+
{ role: 'system', content: config.systemPrompt },
|
|
50
|
+
...options.messages,
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
const steps: StepEvent[] = [];
|
|
54
|
+
let totalUsage: Usage = {
|
|
55
|
+
promptTokens: 0,
|
|
56
|
+
completionTokens: 0,
|
|
57
|
+
totalTokens: 0,
|
|
58
|
+
};
|
|
59
|
+
let totalCost: Cost = { input: 0, output: 0, total: 0, currency: 'USD' };
|
|
60
|
+
let finalText = '';
|
|
61
|
+
let truncated = false;
|
|
62
|
+
|
|
63
|
+
for (let step = 1; step <= maxSteps; step++) {
|
|
64
|
+
// Apply memory strategy before the call.
|
|
65
|
+
const memStrategy = config.memoryStrategy ?? 'full';
|
|
66
|
+
const windowSize = config.memoryWindow ?? 8;
|
|
67
|
+
const visible = applyMemory(history, memStrategy, windowSize);
|
|
68
|
+
|
|
69
|
+
let event: StepEvent;
|
|
70
|
+
try {
|
|
71
|
+
const result = await generateTextWithRetry(
|
|
72
|
+
{
|
|
73
|
+
model: config.model,
|
|
74
|
+
messages: visible,
|
|
75
|
+
tools: config.tools,
|
|
76
|
+
temperature: config.temperature,
|
|
77
|
+
maxTokens: config.maxTokens,
|
|
78
|
+
thinking: config.thinking,
|
|
79
|
+
providerOptions: config.providerOptions,
|
|
80
|
+
},
|
|
81
|
+
{ provider: config.provider }
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
totalUsage = addUsage(totalUsage, result.usage);
|
|
85
|
+
totalCost = addCost(totalCost, result.cost);
|
|
86
|
+
|
|
87
|
+
// Append the assistant turn to history.
|
|
88
|
+
const assistantMsg: Message = {
|
|
89
|
+
role: 'assistant',
|
|
90
|
+
content: result.text,
|
|
91
|
+
toolCalls: result.toolCalls.length ? result.toolCalls : undefined,
|
|
92
|
+
};
|
|
93
|
+
history.push(assistantMsg);
|
|
94
|
+
|
|
95
|
+
if (result.toolCalls.length === 0) {
|
|
96
|
+
// Pure text answer — done.
|
|
97
|
+
finalText = result.text;
|
|
98
|
+
event = {
|
|
99
|
+
step,
|
|
100
|
+
kind: 'text',
|
|
101
|
+
text: result.text,
|
|
102
|
+
usage: result.usage,
|
|
103
|
+
cost: result.cost,
|
|
104
|
+
timestamp: new Date().toISOString(),
|
|
105
|
+
};
|
|
106
|
+
steps.push(event);
|
|
107
|
+
await onStep?.(event);
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Execute tool calls IN PARALLEL. Tools are independent of each other
|
|
112
|
+
// (the model emitted them all in one turn) so running them concurrently
|
|
113
|
+
// is safe and dramatically speeds up agents that call multiple tools
|
|
114
|
+
// (e.g. several web searches in one step).
|
|
115
|
+
const toolResults = await Promise.all(
|
|
116
|
+
result.toolCalls.map((tc) => executeToolCall(tc, config, step, runId))
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// Append tool results to history in the SAME ORDER as the model emitted
|
|
120
|
+
// them — order matters for the next LLM turn's context.
|
|
121
|
+
for (const toolResult of toolResults) {
|
|
122
|
+
history.push({
|
|
123
|
+
role: 'tool',
|
|
124
|
+
content: stringifyToolResult(toolResult.result),
|
|
125
|
+
name: toolResult.name,
|
|
126
|
+
toolCallId: toolResult.toolCallId,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
event = {
|
|
131
|
+
step,
|
|
132
|
+
kind: 'tool_call',
|
|
133
|
+
text: result.text,
|
|
134
|
+
toolCalls: result.toolCalls,
|
|
135
|
+
toolResults,
|
|
136
|
+
usage: result.usage,
|
|
137
|
+
cost: result.cost,
|
|
138
|
+
timestamp: new Date().toISOString(),
|
|
139
|
+
};
|
|
140
|
+
steps.push(event);
|
|
141
|
+
await onStep?.(event);
|
|
142
|
+
|
|
143
|
+
// If this was the last allowed step and we still called tools, mark truncated.
|
|
144
|
+
if (step === maxSteps) truncated = true;
|
|
145
|
+
} catch (err) {
|
|
146
|
+
const norm = normalizeError(err, config.provider);
|
|
147
|
+
event = {
|
|
148
|
+
step,
|
|
149
|
+
kind: 'error',
|
|
150
|
+
error: norm.message,
|
|
151
|
+
timestamp: new Date().toISOString(),
|
|
152
|
+
};
|
|
153
|
+
steps.push(event);
|
|
154
|
+
await onStep?.(event);
|
|
155
|
+
// Re-throw after the hook has had a chance to log.
|
|
156
|
+
throw norm;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// If the loop ended without a final text (e.g. hit maxSteps mid-tool-call),
|
|
161
|
+
// synthesise a final answer by running one more non-tool generation.
|
|
162
|
+
if (!finalText && steps.length > 0) {
|
|
163
|
+
const last = await generateTextWithRetry(
|
|
164
|
+
{
|
|
165
|
+
model: config.model,
|
|
166
|
+
messages: [
|
|
167
|
+
...applyMemory(history, config.memoryStrategy ?? 'full', config.memoryWindow ?? 8),
|
|
168
|
+
{
|
|
169
|
+
role: 'system',
|
|
170
|
+
content:
|
|
171
|
+
'Summarise the result of the previous tool calls in a concise final answer for the user.',
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
temperature: config.temperature,
|
|
175
|
+
maxTokens: config.maxTokens,
|
|
176
|
+
providerOptions: config.providerOptions,
|
|
177
|
+
},
|
|
178
|
+
{ provider: config.provider }
|
|
179
|
+
);
|
|
180
|
+
finalText = last.text;
|
|
181
|
+
totalUsage = addUsage(totalUsage, last.usage);
|
|
182
|
+
totalCost = addCost(totalCost, last.cost);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
agentId: config.id,
|
|
187
|
+
text: finalText,
|
|
188
|
+
messages: history,
|
|
189
|
+
steps,
|
|
190
|
+
usage: totalUsage,
|
|
191
|
+
cost: totalCost,
|
|
192
|
+
stepsExecuted: steps.length,
|
|
193
|
+
truncated,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Wrap generateText with automatic retry on rate_limit / server / timeout
|
|
199
|
+
* errors. Uses exponential backoff with jitter. Non-retryable errors are
|
|
200
|
+
* re-thrown immediately.
|
|
201
|
+
*/
|
|
202
|
+
async function generateTextWithRetry(
|
|
203
|
+
params: Parameters<typeof generateText>[0],
|
|
204
|
+
opts: Parameters<typeof generateText>[1]
|
|
205
|
+
): ReturnType<typeof generateText> {
|
|
206
|
+
const maxRetries = DEFAULT_MAX_RETRIES;
|
|
207
|
+
let lastError: unknown;
|
|
208
|
+
|
|
209
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
210
|
+
try {
|
|
211
|
+
return await generateText(params, opts);
|
|
212
|
+
} catch (err) {
|
|
213
|
+
lastError = err;
|
|
214
|
+
const providerId = opts?.provider;
|
|
215
|
+
const norm = err instanceof AgentSDKError
|
|
216
|
+
? err
|
|
217
|
+
: normalizeError(err, providerId);
|
|
218
|
+
|
|
219
|
+
// Don't retry on the last attempt or non-retryable errors.
|
|
220
|
+
if (attempt === maxRetries || !RETRYABLE_TYPES.has(norm.type)) {
|
|
221
|
+
throw norm;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Exponential backoff with jitter: 800ms, 1600ms, ... +/- 20% jitter.
|
|
225
|
+
const baseDelay = RETRY_BASE_DELAY_MS * Math.pow(2, attempt);
|
|
226
|
+
const jitter = baseDelay * (0.8 + Math.random() * 0.4);
|
|
227
|
+
await new Promise((resolve) => setTimeout(resolve, jitter));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Should be unreachable, but keeps TS happy.
|
|
232
|
+
throw lastError;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function executeToolCall(
|
|
236
|
+
call: ToolCall,
|
|
237
|
+
config: AgentConfig,
|
|
238
|
+
step: number,
|
|
239
|
+
runId: string
|
|
240
|
+
): Promise<ToolResult> {
|
|
241
|
+
const tool = config.tools?.find((t) => t.name === call.name);
|
|
242
|
+
if (!tool || typeof tool.execute !== 'function') {
|
|
243
|
+
return {
|
|
244
|
+
toolCallId: call.id,
|
|
245
|
+
name: call.name,
|
|
246
|
+
result: { error: `Tool "${call.name}" is not defined or has no executor.` },
|
|
247
|
+
isError: true,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
try {
|
|
251
|
+
const out = await tool.execute(call.arguments, { runId, step });
|
|
252
|
+
return { toolCallId: call.id, name: call.name, result: out };
|
|
253
|
+
} catch (err) {
|
|
254
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
255
|
+
return {
|
|
256
|
+
toolCallId: call.id,
|
|
257
|
+
name: call.name,
|
|
258
|
+
result: { error: msg },
|
|
259
|
+
isError: true,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function stringifyToolResult(result: unknown): string {
|
|
265
|
+
if (typeof result === 'string') return result;
|
|
266
|
+
try {
|
|
267
|
+
return JSON.stringify(result);
|
|
268
|
+
} catch {
|
|
269
|
+
return String(result);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function makeRunId(): string {
|
|
274
|
+
return `run_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export type { StepHook };
|