@mohanscodex/spectra-agent 0.4.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/README.md ADDED
@@ -0,0 +1,241 @@
1
+ # @singularity-ai/spectra-agent
2
+
3
+ **Agent runtime with multi-turn tool dispatch and streaming event delivery.**
4
+
5
+ Orchestrates conversations with LLMs: streams responses, dispatches tool calls (parallel or sequential), injects intermediate messages mid-turn, and emits typed events for every phase of execution.
6
+
7
+ ## Features
8
+
9
+ - **Multi-turn loop** — Automatically feeds tool results back to the model. Configurable `maxTurns`.
10
+ - **Tool execution** — Parallel or sequential dispatch. Tools defined with Zod schemas for type-safe argument validation.
11
+ - **Streaming events** — AsyncGenerator yields `AgentEvent` discriminated unions. Subscribe via `for await` or side-channel listeners.
12
+ - **Hooks** — `beforeToolCall` (block/modify), `afterToolCall` (modify results), `transformContext` (rewrite messages), `getApiKey` (dynamic key resolution).
13
+ - **Steering & follow-up queues** — Inject messages mid-turn (`steer()`) or queue them for the next run (`followUp()`).
14
+ - **Automatic retry** — Exponential backoff (up to 3 retries) for transient API errors. Configurable max delay.
15
+ - **Abort support** — `agent.abort()` cancels in-flight requests with `AbortController`.
16
+ - **Subscriber pattern** — Push-based listeners for side-channel logging, metrics, or persistence.
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ bun add @singularity-ai/spectra-agent
22
+ ```
23
+
24
+ Depends on `@singularity-ai/spectra-ai` (automatically resolved as a workspace dependency).
25
+
26
+ ## Quick Start
27
+
28
+ ```typescript
29
+ import { Agent, defineTool } from "@singularity-ai/spectra-agent";
30
+ import { z } from "zod";
31
+
32
+ const weatherTool = defineTool({
33
+ name: "get_weather",
34
+ description: "Get current weather for a location",
35
+ parameters: z.object({
36
+ location: z.string().describe("City name"),
37
+ }),
38
+ execute: async ({ location }) => ({
39
+ content: [{ type: "text", text: `The weather in ${location} is sunny.` }],
40
+ }),
41
+ });
42
+
43
+ const agent = new Agent({
44
+ model: { id: "gpt-4o", name: "GPT-4o", provider: "openai-completions", api: "openai" },
45
+ systemPrompt: "You are a helpful assistant with weather data.",
46
+ tools: [weatherTool],
47
+ });
48
+
49
+ for await (const event of agent.run("What's the weather in Tokyo?")) {
50
+ switch (event.type) {
51
+ case "message_update":
52
+ const text = event.message.content
53
+ .filter((c) => c.type === "text")
54
+ .map((c) => c.text)
55
+ .join("");
56
+ process.stdout.write(text);
57
+ break;
58
+ case "tool_execution_start":
59
+ console.log(`\n[Tool: ${event.toolName}]`);
60
+ break;
61
+ case "tool_execution_end":
62
+ console.log(`\n[Result: ${event.isError ? "error" : "ok"}]`);
63
+ break;
64
+ case "agent_end":
65
+ console.log("\nDone. Transcript length:", event.messages.length);
66
+ break;
67
+ }
68
+ }
69
+ ```
70
+
71
+ ## Architecture
72
+
73
+ ```
74
+ agent.run("Hello")
75
+
76
+ ├─ emit agent_start
77
+ ├─ emit message_start/user
78
+ ├─ emit turn_start
79
+
80
+ ├─ LLM stream (via @spectra-ai)
81
+ │ ├─ emit message_start (assistant)
82
+ │ ├─ emit message_update (deltas)
83
+ │ └─ emit message_end (complete)
84
+
85
+ ├─ toolCalls detected?
86
+ │ ├─ NO → emit turn_end → emit agent_end → done
87
+ │ └─ YES → prepare (beforeToolCall hook)
88
+ │ ├─ sequential: execute one-by-one
89
+ │ └─ parallel: prepare all, then execute concurrently
90
+ │ → attach tool results to transcript
91
+ │ → emit turn_end
92
+ │ → check steering queue / follow-up queue
93
+ │ → loop back to LLM stream
94
+
95
+ └─ agent_end (final transcript in event.messages)
96
+ ```
97
+
98
+ ## API
99
+
100
+ ### `new Agent(config)`
101
+
102
+ ```typescript
103
+ interface AgentConfig {
104
+ model: Model; // LLM model to use
105
+ systemPrompt?: string; // system prompt
106
+ tools?: AgentTool[]; // registered tools
107
+ maxTurns?: number; // max loop iterations (default: unlimited)
108
+ toolExecution?: "parallel" | "sequential"; // default: "parallel"
109
+ beforeToolCall?: (ctx, signal?) => Promise<BeforeToolCallResult | undefined>;
110
+ afterToolCall?: (ctx, signal?) => Promise<AfterToolCallResult | undefined>;
111
+ transformContext?: (messages, signal?) => Promise<Message[]>;
112
+ getApiKey?: (provider) => string | undefined | Promise<string | undefined>;
113
+ }
114
+ ```
115
+
116
+ ### `agent.run(input)`
117
+
118
+ Returns `AsyncGenerator<AgentEvent>`. Input is a `string`, `Message`, or `Message[]`.
119
+
120
+ ### `agent.subscribe(listener)`
121
+
122
+ Returns an unsubscribe function. Listeners fire for every event in parallel with the generator consumer.
123
+
124
+ ```typescript
125
+ const unsub = agent.subscribe((event, signal) => {
126
+ if (event.type === "tool_execution_start") {
127
+ metrics.counter.inc({ tool: event.toolName });
128
+ }
129
+ });
130
+ ```
131
+
132
+ ### `agent.steer(message)` / `agent.followUp(message)`
133
+
134
+ - `steer()` — Injects a user message into the current run loop. Processed on the next turn.
135
+ - `followUp()` — Queues a message for after the current `run()` completes.
136
+
137
+ ### State
138
+
139
+ ```typescript
140
+ agent.messages // Message[] — full conversation transcript
141
+ agent.isStreaming // boolean — run in progress
142
+ agent.streamingMessage // AssistantMessage | undefined
143
+ agent.pendingToolCalls // Set<string> — tool call IDs in flight
144
+ agent.errorMessage // string | undefined
145
+ agent.signal // AbortSignal | undefined
146
+ ```
147
+
148
+ ### `agent.abort()` / `agent.reset()` / `agent.restoreHistory(messages)`
149
+
150
+ ## Tool Definition
151
+
152
+ ```typescript
153
+ import { defineTool, textResult, errorResult } from "@singularity-ai/spectra-agent";
154
+ import { z } from "zod";
155
+
156
+ const searchTool = defineTool({
157
+ name: "search_web",
158
+ description: "Search the web for information",
159
+ parameters: z.object({
160
+ query: z.string().describe("Search query"),
161
+ maxResults: z.number().optional().default(5),
162
+ }),
163
+ execute: async (args, { toolCallId, signal, onUpdate }) => {
164
+ // args is fully typed: { query: string; maxResults?: number }
165
+ if (!args.query) return errorResult("Query is required");
166
+ onUpdate?.({ content: [{ type: "text", text: "Searching..." }] });
167
+ const results = await fetchResults(args.query, args.maxResults, signal);
168
+ return textResult(results);
169
+ },
170
+ });
171
+ ```
172
+
173
+ ## Events
174
+
175
+ | Event | When | Payload |
176
+ |---|---|---|
177
+ | `agent_start` | Run begins | — |
178
+ | `agent_end` | Run complete | `messages: Message[]` |
179
+ | `turn_start` | LLM turn begins | — |
180
+ | `turn_end` | Turn complete | `message`, `toolResults` |
181
+ | `message_start` | Message added to transcript | `message` |
182
+ | `message_update` | Assistant message delta | `message`, `assistantMessageEvent` |
183
+ | `message_end` | Message fully formed | `message` |
184
+ | `tool_execution_start` | Tool call begins | `toolCallId`, `toolName`, `args` |
185
+ | `tool_execution_update` | Tool reports partial progress | `toolCallId`, `toolName`, `partialResult` |
186
+ | `tool_execution_end` | Tool call completes | `toolCallId`, `toolName`, `result`, `isError` |
187
+
188
+ ## Hooks
189
+
190
+ ### `beforeToolCall`
191
+
192
+ Block or modify a tool call before execution:
193
+
194
+ ```typescript
195
+ beforeToolCall: async ({ toolCall, args }) => {
196
+ if (toolCall.name === "delete_file") {
197
+ return { block: true, reason: "Not allowed in current context" };
198
+ }
199
+ }
200
+ ```
201
+
202
+ ### `afterToolCall`
203
+
204
+ Transform tool results:
205
+
206
+ ```typescript
207
+ afterToolCall: async ({ result, isError }) => {
208
+ if (isError) return { content: [{ type: "text", text: "Tool failed, retrying..." }], isError: false };
209
+ }
210
+ ```
211
+
212
+ ### `transformContext`
213
+
214
+ Rewrite messages before sending to the LLM:
215
+
216
+ ```typescript
217
+ transformContext: async (messages) => {
218
+ return messages.filter(m => m.role !== "system");
219
+ }
220
+ ```
221
+
222
+ ### `getApiKey`
223
+
224
+ Resolve API keys dynamically:
225
+
226
+ ```typescript
227
+ getApiKey: async (provider) => {
228
+ if (provider === "anthropic") return process.env.ANTHROPIC_KEY;
229
+ return process.env.OPENAI_API_KEY;
230
+ }
231
+ ```
232
+
233
+ ## Retry Behavior
234
+
235
+ The agent retries LLM calls up to 3 times with exponential backoff (1s, 2s, 4s) capped at `maxRetryDelayMs` (default 30s). Does not retry on:
236
+ - 4xx client errors (400, 401, 403, 404)
237
+ - Aborted requests
238
+
239
+ ## License
240
+
241
+ MIT
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=agent-features.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-features.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/agent-features.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,108 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { Agent } from "../agent.js";
3
+ const testModel = {
4
+ id: "test-model",
5
+ name: "Test Model",
6
+ provider: "test",
7
+ api: "test",
8
+ };
9
+ describe("Agent Steering Queue", () => {
10
+ it("should queue steering messages", () => {
11
+ const agent = new Agent({ model: testModel });
12
+ agent.steer("Be more concise");
13
+ agent.steer("Use bullet points");
14
+ // Should not throw and should queue internally
15
+ expect(agent).toBeDefined();
16
+ });
17
+ it("should accept string steering messages", () => {
18
+ const agent = new Agent({ model: testModel });
19
+ expect(() => agent.steer("Hello")).not.toThrow();
20
+ });
21
+ it("should accept Message steering messages", () => {
22
+ const agent = new Agent({ model: testModel });
23
+ const message = {
24
+ role: "user",
25
+ content: "Custom steering",
26
+ timestamp: Date.now(),
27
+ };
28
+ expect(() => agent.steer(message)).not.toThrow();
29
+ });
30
+ });
31
+ describe("Agent Follow-Up Queue", () => {
32
+ it("should queue follow-up messages", () => {
33
+ const agent = new Agent({ model: testModel });
34
+ agent.followUp("What about tomorrow?");
35
+ agent.followUp("And the weekend?");
36
+ expect(agent).toBeDefined();
37
+ });
38
+ it("should accept string follow-up messages", () => {
39
+ const agent = new Agent({ model: testModel });
40
+ expect(() => agent.followUp("Hello")).not.toThrow();
41
+ });
42
+ it("should accept Message follow-up messages", () => {
43
+ const agent = new Agent({ model: testModel });
44
+ const message = {
45
+ role: "user",
46
+ content: "Custom follow-up",
47
+ timestamp: Date.now(),
48
+ };
49
+ expect(() => agent.followUp(message)).not.toThrow();
50
+ });
51
+ });
52
+ describe("Agent Configuration", () => {
53
+ it("should accept steeringMode option", () => {
54
+ const agent = new Agent({
55
+ model: testModel,
56
+ steeringMode: "all",
57
+ });
58
+ expect(agent).toBeDefined();
59
+ });
60
+ it("should accept followUpMode option", () => {
61
+ const agent = new Agent({
62
+ model: testModel,
63
+ followUpMode: "one-at-a-time",
64
+ });
65
+ expect(agent).toBeDefined();
66
+ });
67
+ it("should accept convertToLlm hook", () => {
68
+ const convertToLlm = (messages) => messages;
69
+ const agent = new Agent({
70
+ model: testModel,
71
+ convertToLlm,
72
+ });
73
+ expect(agent).toBeDefined();
74
+ });
75
+ it("should accept maxRetryDelayMs option", () => {
76
+ const agent = new Agent({
77
+ model: testModel,
78
+ maxRetryDelayMs: 5000,
79
+ });
80
+ expect(agent).toBeDefined();
81
+ });
82
+ });
83
+ describe("Agent Error Recovery", () => {
84
+ it("should configure retry parameters", () => {
85
+ const agent = new Agent({
86
+ model: testModel,
87
+ maxRetryDelayMs: 10000,
88
+ });
89
+ expect(agent).toBeDefined();
90
+ });
91
+ });
92
+ describe("Agent Queue Integration", () => {
93
+ it("should handle mixed steer and followUp calls", () => {
94
+ const agent = new Agent({ model: testModel });
95
+ agent.steer("First steering");
96
+ agent.followUp("First follow-up");
97
+ agent.steer("Second steering");
98
+ agent.followUp("Second follow-up");
99
+ expect(agent).toBeDefined();
100
+ });
101
+ it("should not throw when queuing during non-streaming state", () => {
102
+ const agent = new Agent({ model: testModel });
103
+ expect(agent.isStreaming).toBe(false);
104
+ expect(() => agent.steer("test")).not.toThrow();
105
+ expect(() => agent.followUp("test")).not.toThrow();
106
+ });
107
+ });
108
+ //# sourceMappingURL=agent-features.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-features.test.js","sourceRoot":"","sources":["../../src/__tests__/agent-features.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=agent.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/agent.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,109 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { Agent } from "../agent.js";
3
+ import { defineTool } from "../define-tool.js";
4
+ import { z } from "zod";
5
+ const testModel = { id: "claude-sonnet-4-20250514", name: "Claude Sonnet 4", provider: "anthropic", api: "anthropic-messages" };
6
+ describe("Agent", () => {
7
+ it("should create agent instance", () => {
8
+ const agent = new Agent({
9
+ model: testModel,
10
+ systemPrompt: "You are a helpful assistant.",
11
+ });
12
+ expect(agent).toBeDefined();
13
+ });
14
+ it("should store messages", () => {
15
+ const agent = new Agent({ model: testModel });
16
+ expect(agent.messages).toEqual([]);
17
+ });
18
+ it("should register tools", () => {
19
+ const agent = new Agent({ model: testModel });
20
+ const tool = defineTool({
21
+ name: "get_weather",
22
+ description: "Get the current weather",
23
+ parameters: z.object({
24
+ location: z.string().describe("The location to get weather for"),
25
+ }),
26
+ execute: async (args, context) => {
27
+ return {
28
+ content: [{ type: "text", text: `The weather in ${args.location} is sunny.` }],
29
+ };
30
+ },
31
+ });
32
+ agent.registerTool(tool);
33
+ expect(agent.messages).toEqual([]);
34
+ });
35
+ it("should subscribe and unsubscribe listeners", () => {
36
+ const agent = new Agent({ model: testModel });
37
+ const listener = vi.fn();
38
+ const unsubscribe = agent.subscribe(listener);
39
+ expect(unsubscribe).toBeDefined();
40
+ unsubscribe();
41
+ });
42
+ it("should abort request", () => {
43
+ const agent = new Agent({ model: testModel });
44
+ expect(() => agent.abort()).not.toThrow();
45
+ });
46
+ it("should reset state", () => {
47
+ const agent = new Agent({ model: testModel });
48
+ agent.reset();
49
+ expect(agent.messages).toEqual([]);
50
+ expect(agent.isStreaming).toBe(false);
51
+ });
52
+ it("should return signal", () => {
53
+ const agent = new Agent({ model: testModel });
54
+ const signal = agent.signal;
55
+ expect(signal).toBeUndefined();
56
+ });
57
+ });
58
+ describe("Agent Queues and Hooks", () => {
59
+ it("should add steering messages to queue", () => {
60
+ const agent = new Agent({ model: testModel });
61
+ agent.steer("Please be more concise");
62
+ // Steering message should be queued (internal state)
63
+ // We can't directly check the queue, but we can verify it doesn't throw
64
+ expect(() => agent.steer("Another message")).not.toThrow();
65
+ });
66
+ it("should add follow-up messages to queue", () => {
67
+ const agent = new Agent({ model: testModel });
68
+ agent.followUp("Can you also check the forecast?");
69
+ // Follow-up message should be queued
70
+ expect(() => agent.followUp("And the temperature?")).not.toThrow();
71
+ });
72
+ it("should accept Message objects in steer and followUp", () => {
73
+ const agent = new Agent({ model: testModel });
74
+ const message = {
75
+ role: "user",
76
+ content: "Custom message",
77
+ timestamp: Date.now(),
78
+ };
79
+ expect(() => agent.steer(message)).not.toThrow();
80
+ expect(() => agent.followUp(message)).not.toThrow();
81
+ });
82
+ });
83
+ describe("Agent Retry Logic", () => {
84
+ it("should configure max retry delay", () => {
85
+ const agent = new Agent({
86
+ model: testModel,
87
+ maxRetryDelayMs: 5000,
88
+ });
89
+ expect(agent).toBeDefined();
90
+ });
91
+ });
92
+ describe("defineTool", () => {
93
+ it("should create tool with Zod schema", () => {
94
+ const tool = defineTool({
95
+ name: "get_weather",
96
+ description: "Get the current weather",
97
+ parameters: z.object({
98
+ location: z.string().describe("The location"),
99
+ }),
100
+ execute: async (args, context) => ({
101
+ content: [{ type: "text", text: "sunny" }],
102
+ }),
103
+ });
104
+ expect(tool.name).toBe("get_weather");
105
+ expect(tool.description).toBe("Get the current weather");
106
+ expect(tool.parameters).toBeDefined();
107
+ });
108
+ });
109
+ //# sourceMappingURL=agent.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.test.js","sourceRoot":"","sources":["../../src/__tests__/agent.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=e2e.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"e2e.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/e2e.test.ts"],"names":[],"mappings":""}