@yolo-labs/core-agent-service 1.0.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/dist/index.d.ts +178 -0
- package/dist/index.js +613 -0
- package/dist/index.js.map +1 -0
- package/package.json +42 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { AIProvider, ToolRegistry, SandboxContext, GenerationRequest, SessionEvent, SessionStore, Message, GenerationSession } from '@yolo-labs/core-types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configuration for retry behavior on transient AI provider errors.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* Uses exponential backoff: delay = `backoffMs * backoffMultiplier^(attempt-1)`.
|
|
8
|
+
* The optional `retryableErrors` array restricts which error codes trigger retries.
|
|
9
|
+
*/
|
|
10
|
+
interface RetryPolicy {
|
|
11
|
+
maxRetries: number;
|
|
12
|
+
backoffMs: number;
|
|
13
|
+
backoffMultiplier: number;
|
|
14
|
+
retryableErrors?: string[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Top-level configuration for the agent service.
|
|
18
|
+
*
|
|
19
|
+
* @remarks
|
|
20
|
+
* Controls the agent loop's behavior: `maxTurns` prevents infinite loops,
|
|
21
|
+
* `maxTokens` caps each AI request, `timeout` sets a per-request deadline,
|
|
22
|
+
* and `retryPolicy` governs retries on transient errors. Optional
|
|
23
|
+
* `eventFilters` can restrict which event types are emitted.
|
|
24
|
+
*/
|
|
25
|
+
interface AgentServiceConfig {
|
|
26
|
+
maxTurns: number;
|
|
27
|
+
maxTokens: number;
|
|
28
|
+
timeout: number;
|
|
29
|
+
retryPolicy: RetryPolicy;
|
|
30
|
+
eventFilters?: string[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Default agent service configuration.
|
|
34
|
+
*
|
|
35
|
+
* @remarks
|
|
36
|
+
* maxTurns: 25, maxTokens: 4096, timeout: 120 s, retries: 2 with exponential backoff.
|
|
37
|
+
*/
|
|
38
|
+
declare const defaultConfig: AgentServiceConfig;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Configuration options for the agent loop.
|
|
42
|
+
*
|
|
43
|
+
* @remarks
|
|
44
|
+
* At minimum, a `provider` must be supplied. Provide `toolRegistry` and
|
|
45
|
+
* `sandboxContext` to enable tool execution during the agentic loop.
|
|
46
|
+
* The optional `config` merges with {@link defaultConfig} defaults.
|
|
47
|
+
* Pass an `AbortSignal` to enable external cancellation of the loop.
|
|
48
|
+
*/
|
|
49
|
+
interface AgentLoopOptions {
|
|
50
|
+
provider: AIProvider;
|
|
51
|
+
toolRegistry?: ToolRegistry;
|
|
52
|
+
sandboxContext?: SandboxContext;
|
|
53
|
+
config?: Partial<AgentServiceConfig>;
|
|
54
|
+
/** Optional signal to abort the agent loop externally (e.g. on interrupt). */
|
|
55
|
+
signal?: AbortSignal;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Core agentic loop that orchestrates AI calls, tool execution, and event streaming.
|
|
59
|
+
*
|
|
60
|
+
* @param request - The generation request containing the user prompt and settings.
|
|
61
|
+
* @param options - Provider, tool registry, sandbox context, and config overrides.
|
|
62
|
+
* @returns An async generator yielding {@link SessionEvent} objects as the agent works.
|
|
63
|
+
*
|
|
64
|
+
* @remarks
|
|
65
|
+
* The loop calls the AI provider, streams content deltas, detects tool use blocks,
|
|
66
|
+
* executes tools via the registry, feeds results back, and repeats until the AI
|
|
67
|
+
* signals `end_turn` or the configured `maxTurns` is reached. Errors are emitted
|
|
68
|
+
* as `error` events and the loop terminates with an `agent_complete` event.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* for await (const event of agentLoop(request, options)) {
|
|
73
|
+
* if (event.type === 'text_delta') process.stdout.write(event.text);
|
|
74
|
+
* }
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
declare function agentLoop(request: GenerationRequest, options: AgentLoopOptions): AsyncGenerator<SessionEvent>;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Options for constructing a SessionManager.
|
|
81
|
+
*
|
|
82
|
+
* @remarks
|
|
83
|
+
* The `maxHistoryLength` controls when truncation kicks in (default 100).
|
|
84
|
+
* Supply `onTruncate` for custom summarization; otherwise oldest messages
|
|
85
|
+
* are dropped.
|
|
86
|
+
*/
|
|
87
|
+
interface SessionManagerOptions {
|
|
88
|
+
store: SessionStore;
|
|
89
|
+
maxHistoryLength?: number;
|
|
90
|
+
onTruncate?: (messages: Message[]) => Promise<Message[]>;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Manages session lifecycle, event logging, and conversation history.
|
|
94
|
+
*
|
|
95
|
+
* @remarks
|
|
96
|
+
* Wraps a {@link SessionStore} implementation to provide higher-level
|
|
97
|
+
* operations: session creation from a request, event append, session
|
|
98
|
+
* resumption, and automatic history truncation.
|
|
99
|
+
*/
|
|
100
|
+
declare class SessionManager {
|
|
101
|
+
private store;
|
|
102
|
+
private maxHistoryLength;
|
|
103
|
+
private onTruncate?;
|
|
104
|
+
constructor(options: SessionManagerOptions);
|
|
105
|
+
/**
|
|
106
|
+
* Creates a new session from a generation request.
|
|
107
|
+
*
|
|
108
|
+
* @param request - The generation request to create a session for.
|
|
109
|
+
* @param userId - Optional user ID to associate with the session.
|
|
110
|
+
* @returns The newly created {@link GenerationSession}.
|
|
111
|
+
*/
|
|
112
|
+
createSession(request: GenerationRequest, userId?: string): Promise<GenerationSession>;
|
|
113
|
+
/**
|
|
114
|
+
* Appends a session event to the session's event log.
|
|
115
|
+
*
|
|
116
|
+
* @param sessionId - The session to append the event to.
|
|
117
|
+
* @param event - The {@link SessionEvent} to record.
|
|
118
|
+
*/
|
|
119
|
+
appendEvent(sessionId: string, event: SessionEvent): Promise<void>;
|
|
120
|
+
/**
|
|
121
|
+
* Resumes an existing session, returning the session and events emitted since a given point.
|
|
122
|
+
*
|
|
123
|
+
* @param sessionId - The session to resume.
|
|
124
|
+
* @param sinceEventId - Optional event ID to replay from. Returns all events if omitted.
|
|
125
|
+
* @returns The session object and the events since the specified event ID.
|
|
126
|
+
* @throws Error if the session does not exist.
|
|
127
|
+
*/
|
|
128
|
+
resumeSession(sessionId: string, sinceEventId?: string): Promise<{
|
|
129
|
+
session: GenerationSession;
|
|
130
|
+
events: SessionEvent[];
|
|
131
|
+
}>;
|
|
132
|
+
/** Retrieves the conversation history for a given session. */
|
|
133
|
+
getConversationHistory(sessionId: string): Promise<Message[]>;
|
|
134
|
+
/**
|
|
135
|
+
* Updates conversation history, applying truncation if it exceeds the configured max length.
|
|
136
|
+
*
|
|
137
|
+
* @param sessionId - The session to update.
|
|
138
|
+
* @param messages - The full updated conversation history.
|
|
139
|
+
*
|
|
140
|
+
* @remarks
|
|
141
|
+
* If the history exceeds `maxHistoryLength`, the custom `onTruncate` hook
|
|
142
|
+
* is called if provided; otherwise the oldest messages are dropped.
|
|
143
|
+
*/
|
|
144
|
+
updateConversationHistory(sessionId: string, messages: Message[]): Promise<void>;
|
|
145
|
+
/** Retrieves a session by ID, or undefined if not found. */
|
|
146
|
+
getSession(sessionId: string): Promise<GenerationSession | undefined>;
|
|
147
|
+
/** Deletes a session and its associated data from the store. */
|
|
148
|
+
deleteSession(sessionId: string): Promise<void>;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* In-memory implementation of {@link SessionStore} using Maps.
|
|
153
|
+
*
|
|
154
|
+
* @remarks
|
|
155
|
+
* Suitable for development, testing, and single-process deployments.
|
|
156
|
+
* All data is lost when the process exits. For persistent storage,
|
|
157
|
+
* implement a custom {@link SessionStore} backed by a database.
|
|
158
|
+
*/
|
|
159
|
+
declare class InMemorySessionStore implements SessionStore {
|
|
160
|
+
private sessions;
|
|
161
|
+
private events;
|
|
162
|
+
/** Creates a new session with a generated ID and timestamps. */
|
|
163
|
+
create(session: Omit<GenerationSession, 'id' | 'createdAt' | 'updatedAt'>): Promise<GenerationSession>;
|
|
164
|
+
/** Retrieves a session by ID, or undefined if not found. */
|
|
165
|
+
get(id: string): Promise<GenerationSession | undefined>;
|
|
166
|
+
/** Merges partial updates into an existing session, preserving ID and createdAt. */
|
|
167
|
+
update(id: string, updates: Partial<GenerationSession>): Promise<GenerationSession>;
|
|
168
|
+
/** Deletes a session and its associated events. */
|
|
169
|
+
delete(id: string): Promise<void>;
|
|
170
|
+
/** Lists all sessions belonging to a given user. */
|
|
171
|
+
listByUser(userId: string): Promise<GenerationSession[]>;
|
|
172
|
+
/** Appends an event to the session's event log. */
|
|
173
|
+
appendEvent(sessionId: string, event: SessionEvent): Promise<void>;
|
|
174
|
+
/** Returns events for a session, optionally starting from a given event index. */
|
|
175
|
+
getEventsSince(sessionId: string, eventId?: string): Promise<SessionEvent[]>;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export { type AgentLoopOptions, type AgentServiceConfig, InMemorySessionStore, type RetryPolicy, SessionManager, type SessionManagerOptions, agentLoop, defaultConfig };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
// src/config.ts
|
|
2
|
+
var defaultConfig = {
|
|
3
|
+
maxTurns: 25,
|
|
4
|
+
maxTokens: 4096,
|
|
5
|
+
timeout: 12e4,
|
|
6
|
+
retryPolicy: {
|
|
7
|
+
maxRetries: 2,
|
|
8
|
+
backoffMs: 1e3,
|
|
9
|
+
backoffMultiplier: 2
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// src/agent-loop.ts
|
|
14
|
+
async function* agentLoop(request, options) {
|
|
15
|
+
const config = { ...defaultConfig, ...options.config };
|
|
16
|
+
const { provider, toolRegistry, sandboxContext, signal } = options;
|
|
17
|
+
const sessionId = request.sessionId ?? crypto.randomUUID();
|
|
18
|
+
yield {
|
|
19
|
+
type: "agent_start",
|
|
20
|
+
sessionId,
|
|
21
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
22
|
+
};
|
|
23
|
+
const messages = [];
|
|
24
|
+
if (request.context?.conversationHistory) {
|
|
25
|
+
messages.push(...request.context.conversationHistory);
|
|
26
|
+
}
|
|
27
|
+
messages.push({ role: "user", content: request.prompt });
|
|
28
|
+
const tools = toolRegistry?.toAIToolDefinitions() ?? request.tools ?? [];
|
|
29
|
+
let turn = 0;
|
|
30
|
+
let totalInputTokens = 0;
|
|
31
|
+
let totalOutputTokens = 0;
|
|
32
|
+
try {
|
|
33
|
+
while (turn < config.maxTurns) {
|
|
34
|
+
turn++;
|
|
35
|
+
if (signal?.aborted) {
|
|
36
|
+
yield {
|
|
37
|
+
type: "error",
|
|
38
|
+
error: "Agent loop aborted",
|
|
39
|
+
code: "ABORTED",
|
|
40
|
+
recoverable: false
|
|
41
|
+
};
|
|
42
|
+
yield {
|
|
43
|
+
type: "agent_complete",
|
|
44
|
+
sessionId,
|
|
45
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
46
|
+
usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens, totalTurns: turn }
|
|
47
|
+
};
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const params = {
|
|
51
|
+
model: request.model ?? "claude-sonnet-4-20250514",
|
|
52
|
+
messages,
|
|
53
|
+
system: request.system,
|
|
54
|
+
tools: tools.length > 0 ? tools : void 0,
|
|
55
|
+
maxTokens: request.maxTokens ?? config.maxTokens,
|
|
56
|
+
temperature: request.temperature,
|
|
57
|
+
metadata: request.metadata
|
|
58
|
+
};
|
|
59
|
+
const assistantContentBlocks = [];
|
|
60
|
+
const pendingToolCalls = [];
|
|
61
|
+
let currentBlockType = null;
|
|
62
|
+
let currentBlockId;
|
|
63
|
+
let currentBlockName;
|
|
64
|
+
let currentInputJson = "";
|
|
65
|
+
let stopReason = null;
|
|
66
|
+
let requestRetries = 0;
|
|
67
|
+
let stream;
|
|
68
|
+
while (true) {
|
|
69
|
+
try {
|
|
70
|
+
stream = provider.createMessage(params);
|
|
71
|
+
break;
|
|
72
|
+
} catch (err) {
|
|
73
|
+
if (requestRetries >= config.retryPolicy.maxRetries) {
|
|
74
|
+
yield {
|
|
75
|
+
type: "error",
|
|
76
|
+
error: err instanceof Error ? err.message : String(err),
|
|
77
|
+
code: "AI_REQUEST_FAILED",
|
|
78
|
+
recoverable: false
|
|
79
|
+
};
|
|
80
|
+
yield {
|
|
81
|
+
type: "agent_complete",
|
|
82
|
+
sessionId,
|
|
83
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
84
|
+
usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens, totalTurns: turn }
|
|
85
|
+
};
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
requestRetries++;
|
|
89
|
+
const delay = config.retryPolicy.backoffMs * Math.pow(config.retryPolicy.backoffMultiplier, requestRetries - 1);
|
|
90
|
+
await sleep(delay);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
let streamRetries = 0;
|
|
94
|
+
let streamSuccess = false;
|
|
95
|
+
while (!streamSuccess) {
|
|
96
|
+
assistantContentBlocks.length = 0;
|
|
97
|
+
pendingToolCalls.length = 0;
|
|
98
|
+
currentBlockType = null;
|
|
99
|
+
currentBlockId = void 0;
|
|
100
|
+
currentBlockName = void 0;
|
|
101
|
+
currentInputJson = "";
|
|
102
|
+
stopReason = null;
|
|
103
|
+
try {
|
|
104
|
+
const streamIterable = streamRetries > 0 ? provider.createMessage(params) : stream;
|
|
105
|
+
const timedStream = config.timeout > 0 ? withTimeout(streamIterable, config.timeout, signal) : streamIterable;
|
|
106
|
+
for await (const event of timedStream) {
|
|
107
|
+
if (signal?.aborted) {
|
|
108
|
+
yield {
|
|
109
|
+
type: "error",
|
|
110
|
+
error: "Agent loop aborted",
|
|
111
|
+
code: "ABORTED",
|
|
112
|
+
recoverable: false
|
|
113
|
+
};
|
|
114
|
+
yield {
|
|
115
|
+
type: "agent_complete",
|
|
116
|
+
sessionId,
|
|
117
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
118
|
+
usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens, totalTurns: turn }
|
|
119
|
+
};
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
switch (event.type) {
|
|
123
|
+
case "content_block_start": {
|
|
124
|
+
currentBlockType = event.contentBlock.type;
|
|
125
|
+
currentBlockId = event.contentBlock.id;
|
|
126
|
+
currentBlockName = event.contentBlock.name;
|
|
127
|
+
currentInputJson = "";
|
|
128
|
+
if (currentBlockType === "tool_use" && currentBlockId && currentBlockName) {
|
|
129
|
+
}
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case "content_block_delta": {
|
|
133
|
+
if (event.delta.type === "text_delta") {
|
|
134
|
+
yield { type: "text_delta", text: event.delta.text };
|
|
135
|
+
const lastBlock = assistantContentBlocks[assistantContentBlocks.length - 1];
|
|
136
|
+
if (lastBlock && lastBlock.type === "text") {
|
|
137
|
+
lastBlock.text += event.delta.text;
|
|
138
|
+
} else {
|
|
139
|
+
assistantContentBlocks.push({ type: "text", text: event.delta.text });
|
|
140
|
+
}
|
|
141
|
+
} else if (event.delta.type === "thinking_delta") {
|
|
142
|
+
yield { type: "thought_delta", text: event.delta.thinking };
|
|
143
|
+
} else if (event.delta.type === "input_json_delta") {
|
|
144
|
+
currentInputJson += event.delta.partial_json;
|
|
145
|
+
yield {
|
|
146
|
+
type: "tool_input_delta",
|
|
147
|
+
toolCallId: currentBlockId ?? "",
|
|
148
|
+
partialInput: event.delta.partial_json
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
case "content_block_stop": {
|
|
154
|
+
if (currentBlockType === "tool_use" && currentBlockId) {
|
|
155
|
+
pendingToolCalls.push({
|
|
156
|
+
id: currentBlockId,
|
|
157
|
+
name: currentBlockName ?? "",
|
|
158
|
+
inputJson: currentInputJson
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
currentBlockType = null;
|
|
162
|
+
currentBlockId = void 0;
|
|
163
|
+
currentBlockName = void 0;
|
|
164
|
+
currentInputJson = "";
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
case "message_delta": {
|
|
168
|
+
stopReason = event.delta.stop_reason;
|
|
169
|
+
if (event.usage) {
|
|
170
|
+
totalOutputTokens += event.usage.output_tokens;
|
|
171
|
+
}
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
case "message_start": {
|
|
175
|
+
if (event.message.usage) {
|
|
176
|
+
totalInputTokens += event.message.usage.input_tokens;
|
|
177
|
+
}
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
case "message_stop":
|
|
181
|
+
break;
|
|
182
|
+
case "error": {
|
|
183
|
+
yield {
|
|
184
|
+
type: "error",
|
|
185
|
+
error: event.error.message,
|
|
186
|
+
code: event.error.type,
|
|
187
|
+
recoverable: false
|
|
188
|
+
};
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
streamSuccess = true;
|
|
194
|
+
} catch (err) {
|
|
195
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
196
|
+
const isTimeout = errMsg === "TIMEOUT";
|
|
197
|
+
const isAbort = signal?.aborted;
|
|
198
|
+
if (isAbort) {
|
|
199
|
+
yield {
|
|
200
|
+
type: "error",
|
|
201
|
+
error: "Agent loop aborted",
|
|
202
|
+
code: "ABORTED",
|
|
203
|
+
recoverable: false
|
|
204
|
+
};
|
|
205
|
+
yield {
|
|
206
|
+
type: "agent_complete",
|
|
207
|
+
sessionId,
|
|
208
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
209
|
+
usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens, totalTurns: turn }
|
|
210
|
+
};
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (isTimeout) {
|
|
214
|
+
yield {
|
|
215
|
+
type: "error",
|
|
216
|
+
error: `Request timed out after ${config.timeout}ms`,
|
|
217
|
+
code: "TIMEOUT",
|
|
218
|
+
recoverable: false
|
|
219
|
+
};
|
|
220
|
+
yield {
|
|
221
|
+
type: "agent_complete",
|
|
222
|
+
sessionId,
|
|
223
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
224
|
+
usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens, totalTurns: turn }
|
|
225
|
+
};
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (streamRetries < config.retryPolicy.maxRetries) {
|
|
229
|
+
streamRetries++;
|
|
230
|
+
const delay = config.retryPolicy.backoffMs * Math.pow(config.retryPolicy.backoffMultiplier, streamRetries - 1);
|
|
231
|
+
yield {
|
|
232
|
+
type: "error",
|
|
233
|
+
error: errMsg,
|
|
234
|
+
code: "STREAM_ERROR",
|
|
235
|
+
recoverable: true
|
|
236
|
+
};
|
|
237
|
+
await sleep(delay);
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
yield {
|
|
241
|
+
type: "error",
|
|
242
|
+
error: errMsg,
|
|
243
|
+
code: "STREAM_ERROR",
|
|
244
|
+
recoverable: false
|
|
245
|
+
};
|
|
246
|
+
yield {
|
|
247
|
+
type: "agent_complete",
|
|
248
|
+
sessionId,
|
|
249
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
250
|
+
usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens, totalTurns: turn }
|
|
251
|
+
};
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const fullAssistantContent = [...assistantContentBlocks];
|
|
256
|
+
for (const tc of pendingToolCalls) {
|
|
257
|
+
let parsedInput = {};
|
|
258
|
+
try {
|
|
259
|
+
parsedInput = tc.inputJson ? JSON.parse(tc.inputJson) : {};
|
|
260
|
+
} catch {
|
|
261
|
+
}
|
|
262
|
+
const toolUseBlock = {
|
|
263
|
+
type: "tool_use",
|
|
264
|
+
id: tc.id,
|
|
265
|
+
name: tc.name,
|
|
266
|
+
input: parsedInput
|
|
267
|
+
};
|
|
268
|
+
fullAssistantContent.push(toolUseBlock);
|
|
269
|
+
}
|
|
270
|
+
if (fullAssistantContent.length > 0) {
|
|
271
|
+
messages.push({ role: "assistant", content: fullAssistantContent });
|
|
272
|
+
}
|
|
273
|
+
if (signal?.aborted) {
|
|
274
|
+
yield {
|
|
275
|
+
type: "error",
|
|
276
|
+
error: "Agent loop aborted",
|
|
277
|
+
code: "ABORTED",
|
|
278
|
+
recoverable: false
|
|
279
|
+
};
|
|
280
|
+
yield {
|
|
281
|
+
type: "agent_complete",
|
|
282
|
+
sessionId,
|
|
283
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
284
|
+
usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens, totalTurns: turn }
|
|
285
|
+
};
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (pendingToolCalls.length > 0 && toolRegistry && sandboxContext) {
|
|
289
|
+
const toolResults = [];
|
|
290
|
+
for (const tc of pendingToolCalls) {
|
|
291
|
+
if (signal?.aborted) {
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
let parsedInput = {};
|
|
295
|
+
try {
|
|
296
|
+
parsedInput = tc.inputJson ? JSON.parse(tc.inputJson) : {};
|
|
297
|
+
} catch {
|
|
298
|
+
yield {
|
|
299
|
+
type: "error",
|
|
300
|
+
error: `Failed to parse tool input JSON for ${tc.name}`,
|
|
301
|
+
code: "TOOL_INPUT_PARSE_ERROR",
|
|
302
|
+
recoverable: true
|
|
303
|
+
};
|
|
304
|
+
toolResults.push({
|
|
305
|
+
content: JSON.stringify({ error: "Failed to parse tool input JSON" }),
|
|
306
|
+
isError: true
|
|
307
|
+
});
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
yield {
|
|
311
|
+
type: "tool_call",
|
|
312
|
+
toolCallId: tc.id,
|
|
313
|
+
name: tc.name,
|
|
314
|
+
input: parsedInput
|
|
315
|
+
};
|
|
316
|
+
const tool = toolRegistry.get(tc.name);
|
|
317
|
+
if (!tool) {
|
|
318
|
+
const errorResult = {
|
|
319
|
+
type: "tool_result",
|
|
320
|
+
toolCallId: tc.id,
|
|
321
|
+
name: tc.name,
|
|
322
|
+
success: false,
|
|
323
|
+
output: null,
|
|
324
|
+
error: `Tool '${tc.name}' not found in registry`
|
|
325
|
+
};
|
|
326
|
+
yield errorResult;
|
|
327
|
+
toolResults.push({
|
|
328
|
+
content: JSON.stringify({ error: `Tool '${tc.name}' not found` }),
|
|
329
|
+
isError: true
|
|
330
|
+
});
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
try {
|
|
334
|
+
const result = await tool.execute(
|
|
335
|
+
parsedInput,
|
|
336
|
+
sandboxContext,
|
|
337
|
+
(progress) => {
|
|
338
|
+
}
|
|
339
|
+
);
|
|
340
|
+
yield {
|
|
341
|
+
type: "tool_result",
|
|
342
|
+
toolCallId: tc.id,
|
|
343
|
+
name: tc.name,
|
|
344
|
+
success: result.success,
|
|
345
|
+
output: result.output,
|
|
346
|
+
error: result.error
|
|
347
|
+
};
|
|
348
|
+
toolResults.push({
|
|
349
|
+
content: typeof result.output === "string" ? result.output : JSON.stringify(result.output),
|
|
350
|
+
isError: false
|
|
351
|
+
});
|
|
352
|
+
} catch (err) {
|
|
353
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
354
|
+
yield {
|
|
355
|
+
type: "tool_result",
|
|
356
|
+
toolCallId: tc.id,
|
|
357
|
+
name: tc.name,
|
|
358
|
+
success: false,
|
|
359
|
+
output: null,
|
|
360
|
+
error: errorMsg
|
|
361
|
+
};
|
|
362
|
+
toolResults.push({
|
|
363
|
+
content: JSON.stringify({ error: errorMsg }),
|
|
364
|
+
isError: true
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
const toolResultBlocks = toolResults.map((r, i) => ({
|
|
369
|
+
type: "tool_result",
|
|
370
|
+
tool_use_id: pendingToolCalls[i].id,
|
|
371
|
+
content: r.content,
|
|
372
|
+
...r.isError ? { is_error: true } : {}
|
|
373
|
+
}));
|
|
374
|
+
messages.push({ role: "user", content: toolResultBlocks });
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
if (stopReason === "end_turn" || stopReason === "stop" || pendingToolCalls.length === 0) {
|
|
378
|
+
yield {
|
|
379
|
+
type: "agent_complete",
|
|
380
|
+
sessionId,
|
|
381
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
382
|
+
usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens, totalTurns: turn }
|
|
383
|
+
};
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
yield {
|
|
388
|
+
type: "error",
|
|
389
|
+
error: `Max turns (${config.maxTurns}) exceeded`,
|
|
390
|
+
code: "MAX_TURNS_EXCEEDED",
|
|
391
|
+
recoverable: false
|
|
392
|
+
};
|
|
393
|
+
yield {
|
|
394
|
+
type: "agent_complete",
|
|
395
|
+
sessionId,
|
|
396
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
397
|
+
usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens, totalTurns: turn }
|
|
398
|
+
};
|
|
399
|
+
} catch (err) {
|
|
400
|
+
yield {
|
|
401
|
+
type: "error",
|
|
402
|
+
error: err instanceof Error ? err.message : String(err),
|
|
403
|
+
code: "AGENT_LOOP_ERROR",
|
|
404
|
+
recoverable: false
|
|
405
|
+
};
|
|
406
|
+
yield {
|
|
407
|
+
type: "agent_complete",
|
|
408
|
+
sessionId,
|
|
409
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
410
|
+
usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens, totalTurns: turn }
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
function sleep(ms) {
|
|
415
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
416
|
+
}
|
|
417
|
+
async function* withTimeout(iterable, timeoutMs, signal) {
|
|
418
|
+
const iterator = iterable[Symbol.asyncIterator]();
|
|
419
|
+
const timeoutError = new Error("TIMEOUT");
|
|
420
|
+
while (true) {
|
|
421
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
422
|
+
const timer = setTimeout(() => reject(timeoutError), timeoutMs);
|
|
423
|
+
if (signal) {
|
|
424
|
+
const onAbort = () => {
|
|
425
|
+
clearTimeout(timer);
|
|
426
|
+
reject(new Error("ABORTED"));
|
|
427
|
+
};
|
|
428
|
+
if (signal.aborted) {
|
|
429
|
+
clearTimeout(timer);
|
|
430
|
+
reject(new Error("ABORTED"));
|
|
431
|
+
} else {
|
|
432
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
const result = await Promise.race([
|
|
437
|
+
iterator.next(),
|
|
438
|
+
timeoutPromise
|
|
439
|
+
]);
|
|
440
|
+
if (result.done) break;
|
|
441
|
+
yield result.value;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// src/session-manager.ts
|
|
446
|
+
var SessionManager = class {
|
|
447
|
+
store;
|
|
448
|
+
maxHistoryLength;
|
|
449
|
+
onTruncate;
|
|
450
|
+
constructor(options) {
|
|
451
|
+
this.store = options.store;
|
|
452
|
+
this.maxHistoryLength = options.maxHistoryLength ?? 100;
|
|
453
|
+
this.onTruncate = options.onTruncate;
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Creates a new session from a generation request.
|
|
457
|
+
*
|
|
458
|
+
* @param request - The generation request to create a session for.
|
|
459
|
+
* @param userId - Optional user ID to associate with the session.
|
|
460
|
+
* @returns The newly created {@link GenerationSession}.
|
|
461
|
+
*/
|
|
462
|
+
async createSession(request, userId) {
|
|
463
|
+
return this.store.create({
|
|
464
|
+
userId,
|
|
465
|
+
conversationHistory: [],
|
|
466
|
+
toolCallLog: [],
|
|
467
|
+
metadata: request.metadata ?? {}
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Appends a session event to the session's event log.
|
|
472
|
+
*
|
|
473
|
+
* @param sessionId - The session to append the event to.
|
|
474
|
+
* @param event - The {@link SessionEvent} to record.
|
|
475
|
+
*/
|
|
476
|
+
async appendEvent(sessionId, event) {
|
|
477
|
+
await this.store.appendEvent(sessionId, event);
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Resumes an existing session, returning the session and events emitted since a given point.
|
|
481
|
+
*
|
|
482
|
+
* @param sessionId - The session to resume.
|
|
483
|
+
* @param sinceEventId - Optional event ID to replay from. Returns all events if omitted.
|
|
484
|
+
* @returns The session object and the events since the specified event ID.
|
|
485
|
+
* @throws Error if the session does not exist.
|
|
486
|
+
*/
|
|
487
|
+
async resumeSession(sessionId, sinceEventId) {
|
|
488
|
+
const session = await this.store.get(sessionId);
|
|
489
|
+
if (!session) {
|
|
490
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
491
|
+
}
|
|
492
|
+
const events = await this.store.getEventsSince(sessionId, sinceEventId);
|
|
493
|
+
return { session, events };
|
|
494
|
+
}
|
|
495
|
+
/** Retrieves the conversation history for a given session. */
|
|
496
|
+
async getConversationHistory(sessionId) {
|
|
497
|
+
const session = await this.store.get(sessionId);
|
|
498
|
+
if (!session) {
|
|
499
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
500
|
+
}
|
|
501
|
+
return session.conversationHistory;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Updates conversation history, applying truncation if it exceeds the configured max length.
|
|
505
|
+
*
|
|
506
|
+
* @param sessionId - The session to update.
|
|
507
|
+
* @param messages - The full updated conversation history.
|
|
508
|
+
*
|
|
509
|
+
* @remarks
|
|
510
|
+
* If the history exceeds `maxHistoryLength`, the custom `onTruncate` hook
|
|
511
|
+
* is called if provided; otherwise the oldest messages are dropped.
|
|
512
|
+
*/
|
|
513
|
+
async updateConversationHistory(sessionId, messages) {
|
|
514
|
+
let history = messages;
|
|
515
|
+
if (history.length > this.maxHistoryLength) {
|
|
516
|
+
if (this.onTruncate) {
|
|
517
|
+
history = await this.onTruncate(history);
|
|
518
|
+
} else {
|
|
519
|
+
history = history.slice(-this.maxHistoryLength);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
await this.store.update(sessionId, { conversationHistory: history });
|
|
523
|
+
}
|
|
524
|
+
/** Retrieves a session by ID, or undefined if not found. */
|
|
525
|
+
async getSession(sessionId) {
|
|
526
|
+
return this.store.get(sessionId);
|
|
527
|
+
}
|
|
528
|
+
/** Deletes a session and its associated data from the store. */
|
|
529
|
+
async deleteSession(sessionId) {
|
|
530
|
+
await this.store.delete(sessionId);
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
// src/in-memory-session-store.ts
|
|
535
|
+
var InMemorySessionStore = class {
|
|
536
|
+
sessions = /* @__PURE__ */ new Map();
|
|
537
|
+
events = /* @__PURE__ */ new Map();
|
|
538
|
+
/** Creates a new session with a generated ID and timestamps. */
|
|
539
|
+
async create(session) {
|
|
540
|
+
const id = crypto.randomUUID();
|
|
541
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
542
|
+
const full = {
|
|
543
|
+
...session,
|
|
544
|
+
id,
|
|
545
|
+
createdAt: now,
|
|
546
|
+
updatedAt: now
|
|
547
|
+
};
|
|
548
|
+
this.sessions.set(id, full);
|
|
549
|
+
this.events.set(id, []);
|
|
550
|
+
return full;
|
|
551
|
+
}
|
|
552
|
+
/** Retrieves a session by ID, or undefined if not found. */
|
|
553
|
+
async get(id) {
|
|
554
|
+
return this.sessions.get(id);
|
|
555
|
+
}
|
|
556
|
+
/** Merges partial updates into an existing session, preserving ID and createdAt. */
|
|
557
|
+
async update(id, updates) {
|
|
558
|
+
const existing = this.sessions.get(id);
|
|
559
|
+
if (!existing) {
|
|
560
|
+
throw new Error(`Session not found: ${id}`);
|
|
561
|
+
}
|
|
562
|
+
const updated = {
|
|
563
|
+
...existing,
|
|
564
|
+
...updates,
|
|
565
|
+
id: existing.id,
|
|
566
|
+
createdAt: existing.createdAt,
|
|
567
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
568
|
+
};
|
|
569
|
+
this.sessions.set(id, updated);
|
|
570
|
+
return updated;
|
|
571
|
+
}
|
|
572
|
+
/** Deletes a session and its associated events. */
|
|
573
|
+
async delete(id) {
|
|
574
|
+
this.sessions.delete(id);
|
|
575
|
+
this.events.delete(id);
|
|
576
|
+
}
|
|
577
|
+
/** Lists all sessions belonging to a given user. */
|
|
578
|
+
async listByUser(userId) {
|
|
579
|
+
return Array.from(this.sessions.values()).filter(
|
|
580
|
+
(s) => s.userId === userId
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
/** Appends an event to the session's event log. */
|
|
584
|
+
async appendEvent(sessionId, event) {
|
|
585
|
+
const events = this.events.get(sessionId);
|
|
586
|
+
if (!events) {
|
|
587
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
588
|
+
}
|
|
589
|
+
events.push(event);
|
|
590
|
+
}
|
|
591
|
+
/** Returns events for a session, optionally starting from a given event index. */
|
|
592
|
+
async getEventsSince(sessionId, eventId) {
|
|
593
|
+
const events = this.events.get(sessionId);
|
|
594
|
+
if (!events) {
|
|
595
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
596
|
+
}
|
|
597
|
+
if (!eventId) {
|
|
598
|
+
return [...events];
|
|
599
|
+
}
|
|
600
|
+
const index = parseInt(eventId, 10);
|
|
601
|
+
if (isNaN(index)) {
|
|
602
|
+
return [...events];
|
|
603
|
+
}
|
|
604
|
+
return events.slice(index);
|
|
605
|
+
}
|
|
606
|
+
};
|
|
607
|
+
export {
|
|
608
|
+
InMemorySessionStore,
|
|
609
|
+
SessionManager,
|
|
610
|
+
agentLoop,
|
|
611
|
+
defaultConfig
|
|
612
|
+
};
|
|
613
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/agent-loop.ts","../src/session-manager.ts","../src/in-memory-session-store.ts"],"sourcesContent":["// Task 23: AgentServiceConfig\n\n/**\n * Configuration for retry behavior on transient AI provider errors.\n *\n * @remarks\n * Uses exponential backoff: delay = `backoffMs * backoffMultiplier^(attempt-1)`.\n * The optional `retryableErrors` array restricts which error codes trigger retries.\n */\nexport interface RetryPolicy {\n maxRetries: number;\n backoffMs: number;\n backoffMultiplier: number;\n retryableErrors?: string[];\n}\n\n/**\n * Top-level configuration for the agent service.\n *\n * @remarks\n * Controls the agent loop's behavior: `maxTurns` prevents infinite loops,\n * `maxTokens` caps each AI request, `timeout` sets a per-request deadline,\n * and `retryPolicy` governs retries on transient errors. Optional\n * `eventFilters` can restrict which event types are emitted.\n */\nexport interface AgentServiceConfig {\n maxTurns: number;\n maxTokens: number;\n timeout: number;\n retryPolicy: RetryPolicy;\n eventFilters?: string[];\n}\n\n/**\n * Default agent service configuration.\n *\n * @remarks\n * maxTurns: 25, maxTokens: 4096, timeout: 120 s, retries: 2 with exponential backoff.\n */\nexport const defaultConfig: AgentServiceConfig = {\n maxTurns: 25,\n maxTokens: 4096,\n timeout: 120_000,\n retryPolicy: {\n maxRetries: 2,\n backoffMs: 1000,\n backoffMultiplier: 2,\n },\n};\n","// Task 20: AgentLoop core implementation\n\nimport type {\n AIProvider,\n CreateMessageParams,\n GenerationRequest,\n Message,\n SessionEvent,\n StreamEvent,\n ToolRegistry,\n SandboxContext,\n ContentBlock,\n ToolUseBlock,\n ToolResultBlock,\n} from '@yolo-labs/core-types';\nimport type { AgentServiceConfig } from './config.js';\nimport { defaultConfig } from './config.js';\n\n/**\n * Configuration options for the agent loop.\n *\n * @remarks\n * At minimum, a `provider` must be supplied. Provide `toolRegistry` and\n * `sandboxContext` to enable tool execution during the agentic loop.\n * The optional `config` merges with {@link defaultConfig} defaults.\n * Pass an `AbortSignal` to enable external cancellation of the loop.\n */\nexport interface AgentLoopOptions {\n provider: AIProvider;\n toolRegistry?: ToolRegistry;\n sandboxContext?: SandboxContext;\n config?: Partial<AgentServiceConfig>;\n /** Optional signal to abort the agent loop externally (e.g. on interrupt). */\n signal?: AbortSignal;\n}\n\ninterface PendingToolCall {\n id: string;\n name: string;\n inputJson: string;\n}\n\n/**\n * Core agentic loop that orchestrates AI calls, tool execution, and event streaming.\n *\n * @param request - The generation request containing the user prompt and settings.\n * @param options - Provider, tool registry, sandbox context, and config overrides.\n * @returns An async generator yielding {@link SessionEvent} objects as the agent works.\n *\n * @remarks\n * The loop calls the AI provider, streams content deltas, detects tool use blocks,\n * executes tools via the registry, feeds results back, and repeats until the AI\n * signals `end_turn` or the configured `maxTurns` is reached. Errors are emitted\n * as `error` events and the loop terminates with an `agent_complete` event.\n *\n * @example\n * ```ts\n * for await (const event of agentLoop(request, options)) {\n * if (event.type === 'text_delta') process.stdout.write(event.text);\n * }\n * ```\n */\nexport async function* agentLoop(\n request: GenerationRequest,\n options: AgentLoopOptions,\n): AsyncGenerator<SessionEvent> {\n const config = { ...defaultConfig, ...options.config };\n const { provider, toolRegistry, sandboxContext, signal } = options;\n const sessionId = request.sessionId ?? crypto.randomUUID();\n\n // 20a: Emit agent_start\n yield {\n type: 'agent_start',\n sessionId,\n timestamp: new Date().toISOString(),\n };\n\n // Build initial conversation history\n const messages: Message[] = [];\n if (request.context?.conversationHistory) {\n messages.push(...(request.context.conversationHistory as Message[]));\n }\n messages.push({ role: 'user', content: request.prompt });\n\n // Build tool definitions for the AI\n const tools = toolRegistry?.toAIToolDefinitions() ?? request.tools ?? [];\n\n let turn = 0;\n // Task 97: Accumulate token usage across turns\n let totalInputTokens = 0;\n let totalOutputTokens = 0;\n\n try {\n while (turn < config.maxTurns) {\n turn++;\n\n // Task 117: Check abort signal before each AI call\n if (signal?.aborted) {\n yield {\n type: 'error',\n error: 'Agent loop aborted',\n code: 'ABORTED',\n recoverable: false,\n };\n yield {\n type: 'agent_complete',\n sessionId,\n timestamp: new Date().toISOString(),\n usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens, totalTurns: turn },\n };\n return;\n }\n\n // 20a: Build the AI request\n const params: CreateMessageParams = {\n model: request.model ?? 'claude-sonnet-4-20250514',\n messages,\n system: request.system,\n tools: tools.length > 0 ? tools : undefined,\n maxTokens: request.maxTokens ?? config.maxTokens,\n temperature: request.temperature,\n metadata: request.metadata,\n };\n\n // Track content blocks for the assistant turn\n const assistantContentBlocks: ContentBlock[] = [];\n const pendingToolCalls: PendingToolCall[] = [];\n let currentBlockType: string | null = null;\n let currentBlockId: string | undefined;\n let currentBlockName: string | undefined;\n let currentInputJson = '';\n let stopReason: string | null = null;\n\n // 20a/20b: Stream the AI response with request-level retry\n let requestRetries = 0;\n let stream: AsyncIterable<StreamEvent>;\n\n while (true) {\n try {\n stream = provider.createMessage(params);\n break;\n } catch (err) {\n // 20f: Retry logic for AI errors\n if (requestRetries >= config.retryPolicy.maxRetries) {\n yield {\n type: 'error',\n error: err instanceof Error ? err.message : String(err),\n code: 'AI_REQUEST_FAILED',\n recoverable: false,\n };\n yield {\n type: 'agent_complete',\n sessionId,\n timestamp: new Date().toISOString(),\n usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens, totalTurns: turn },\n };\n return;\n }\n requestRetries++;\n const delay =\n config.retryPolicy.backoffMs *\n Math.pow(config.retryPolicy.backoffMultiplier, requestRetries - 1);\n await sleep(delay);\n }\n }\n\n // Task 119: Stream-level retry — retry the current turn on stream error\n let streamRetries = 0;\n let streamSuccess = false;\n\n while (!streamSuccess) {\n // Reset per-turn accumulators on each stream attempt\n assistantContentBlocks.length = 0;\n pendingToolCalls.length = 0;\n currentBlockType = null;\n currentBlockId = undefined;\n currentBlockName = undefined;\n currentInputJson = '';\n stopReason = null;\n\n try {\n // Task 118: Wrap stream consumption with timeout\n const streamIterable = streamRetries > 0 ? provider.createMessage(params) : stream!;\n const timedStream = config.timeout > 0\n ? withTimeout(streamIterable, config.timeout, signal)\n : streamIterable;\n\n for await (const event of timedStream) {\n // Task 117: Check abort signal during streaming\n if (signal?.aborted) {\n yield {\n type: 'error',\n error: 'Agent loop aborted',\n code: 'ABORTED',\n recoverable: false,\n };\n yield {\n type: 'agent_complete',\n sessionId,\n timestamp: new Date().toISOString(),\n usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens, totalTurns: turn },\n };\n return;\n }\n\n switch (event.type) {\n case 'content_block_start': {\n currentBlockType = event.contentBlock.type;\n currentBlockId = event.contentBlock.id;\n currentBlockName = event.contentBlock.name;\n currentInputJson = '';\n\n // 20c: Emit tool_call event when tool_use block starts\n if (currentBlockType === 'tool_use' && currentBlockId && currentBlockName) {\n // We'll emit tool_call after we have the full input\n }\n break;\n }\n\n case 'content_block_delta': {\n if (event.delta.type === 'text_delta') {\n // 20b: Stream text deltas\n yield { type: 'text_delta', text: event.delta.text };\n // Accumulate for conversation history\n const lastBlock = assistantContentBlocks[assistantContentBlocks.length - 1];\n if (lastBlock && lastBlock.type === 'text') {\n lastBlock.text += event.delta.text;\n } else {\n assistantContentBlocks.push({ type: 'text', text: event.delta.text });\n }\n } else if (event.delta.type === 'thinking_delta') {\n // 20b: Stream thinking/reasoning text\n yield { type: 'thought_delta', text: event.delta.thinking };\n } else if (event.delta.type === 'input_json_delta') {\n // 20c: Accumulate tool input JSON\n currentInputJson += event.delta.partial_json;\n yield {\n type: 'tool_input_delta',\n toolCallId: currentBlockId ?? '',\n partialInput: event.delta.partial_json,\n };\n }\n break;\n }\n\n case 'content_block_stop': {\n // 20c: When a tool_use block completes, record it\n if (currentBlockType === 'tool_use' && currentBlockId) {\n pendingToolCalls.push({\n id: currentBlockId,\n name: currentBlockName ?? '',\n inputJson: currentInputJson,\n });\n }\n currentBlockType = null;\n currentBlockId = undefined;\n currentBlockName = undefined;\n currentInputJson = '';\n break;\n }\n\n case 'message_delta': {\n stopReason = event.delta.stop_reason;\n // Task 97: Extract output_tokens from message_delta\n if (event.usage) {\n totalOutputTokens += event.usage.output_tokens;\n }\n break;\n }\n\n case 'message_start': {\n // Task 97: Extract input_tokens from message_start\n if (event.message.usage) {\n totalInputTokens += event.message.usage.input_tokens;\n }\n break;\n }\n case 'message_stop':\n break;\n\n case 'error': {\n // 20f: AI stream error\n yield {\n type: 'error',\n error: event.error.message,\n code: event.error.type,\n recoverable: false,\n };\n break;\n }\n }\n }\n streamSuccess = true;\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n const isTimeout = errMsg === 'TIMEOUT';\n const isAbort = signal?.aborted;\n\n // Task 117: Abort is not retryable\n if (isAbort) {\n yield {\n type: 'error',\n error: 'Agent loop aborted',\n code: 'ABORTED',\n recoverable: false,\n };\n yield {\n type: 'agent_complete',\n sessionId,\n timestamp: new Date().toISOString(),\n usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens, totalTurns: turn },\n };\n return;\n }\n\n // Task 118: Timeout is not retryable\n if (isTimeout) {\n yield {\n type: 'error',\n error: `Request timed out after ${config.timeout}ms`,\n code: 'TIMEOUT',\n recoverable: false,\n };\n yield {\n type: 'agent_complete',\n sessionId,\n timestamp: new Date().toISOString(),\n usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens, totalTurns: turn },\n };\n return;\n }\n\n // Task 119: Stream-level retry — retry the current turn\n if (streamRetries < config.retryPolicy.maxRetries) {\n streamRetries++;\n const delay =\n config.retryPolicy.backoffMs *\n Math.pow(config.retryPolicy.backoffMultiplier, streamRetries - 1);\n yield {\n type: 'error',\n error: errMsg,\n code: 'STREAM_ERROR',\n recoverable: true,\n };\n await sleep(delay);\n continue;\n }\n\n // All retries exhausted\n yield {\n type: 'error',\n error: errMsg,\n code: 'STREAM_ERROR',\n recoverable: false,\n };\n yield {\n type: 'agent_complete',\n sessionId,\n timestamp: new Date().toISOString(),\n usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens, totalTurns: turn },\n };\n return;\n }\n }\n\n // Task 95: Add assistant message to history with ToolUseBlock entries\n const fullAssistantContent: ContentBlock[] = [...assistantContentBlocks];\n for (const tc of pendingToolCalls) {\n let parsedInput: Record<string, unknown> = {};\n try {\n parsedInput = tc.inputJson ? JSON.parse(tc.inputJson) : {};\n } catch {\n // Will be handled during tool execution below\n }\n const toolUseBlock: ToolUseBlock = {\n type: 'tool_use',\n id: tc.id,\n name: tc.name,\n input: parsedInput,\n };\n fullAssistantContent.push(toolUseBlock);\n }\n if (fullAssistantContent.length > 0) {\n messages.push({ role: 'assistant', content: fullAssistantContent });\n }\n\n // 20c/20d: Execute pending tool calls\n // Task 117: Check abort signal before tool execution\n if (signal?.aborted) {\n yield {\n type: 'error',\n error: 'Agent loop aborted',\n code: 'ABORTED',\n recoverable: false,\n };\n yield {\n type: 'agent_complete',\n sessionId,\n timestamp: new Date().toISOString(),\n usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens, totalTurns: turn },\n };\n return;\n }\n\n if (pendingToolCalls.length > 0 && toolRegistry && sandboxContext) {\n const toolResults: { content: string; isError: boolean }[] = [];\n\n for (const tc of pendingToolCalls) {\n // Task 117: Check abort signal before each tool execution\n if (signal?.aborted) {\n break;\n }\n\n let parsedInput: Record<string, unknown> = {};\n try {\n parsedInput = tc.inputJson ? JSON.parse(tc.inputJson) : {};\n } catch {\n yield {\n type: 'error',\n error: `Failed to parse tool input JSON for ${tc.name}`,\n code: 'TOOL_INPUT_PARSE_ERROR',\n recoverable: true,\n };\n toolResults.push({\n content: JSON.stringify({ error: 'Failed to parse tool input JSON' }),\n isError: true,\n });\n continue;\n }\n\n // Emit tool_call event\n yield {\n type: 'tool_call',\n toolCallId: tc.id,\n name: tc.name,\n input: parsedInput,\n };\n\n const tool = toolRegistry.get(tc.name);\n if (!tool) {\n const errorResult = {\n type: 'tool_result' as const,\n toolCallId: tc.id,\n name: tc.name,\n success: false,\n output: null,\n error: `Tool '${tc.name}' not found in registry`,\n };\n yield errorResult;\n toolResults.push({\n content: JSON.stringify({ error: `Tool '${tc.name}' not found` }),\n isError: true,\n });\n continue;\n }\n\n try {\n // Execute tool with progress callback\n const result = await tool.execute(\n parsedInput,\n sandboxContext,\n (progress) => {\n // Cannot yield from a callback in a generator, so we skip progress events here.\n // In practice, consumers would use a different pattern for progress.\n },\n );\n\n yield {\n type: 'tool_result',\n toolCallId: tc.id,\n name: tc.name,\n success: result.success,\n output: result.output,\n error: result.error,\n };\n\n toolResults.push({\n content: typeof result.output === 'string'\n ? result.output\n : JSON.stringify(result.output),\n isError: false,\n });\n } catch (err) {\n // 20f: Tool execution error\n const errorMsg = err instanceof Error ? err.message : String(err);\n yield {\n type: 'tool_result',\n toolCallId: tc.id,\n name: tc.name,\n success: false,\n output: null,\n error: errorMsg,\n };\n toolResults.push({\n content: JSON.stringify({ error: errorMsg }),\n isError: true,\n });\n }\n }\n\n // Task 96: Feed tool results back as structured ToolResultBlock entries\n const toolResultBlocks: ToolResultBlock[] = toolResults.map((r, i) => ({\n type: 'tool_result' as const,\n tool_use_id: pendingToolCalls[i].id,\n content: r.content,\n ...(r.isError ? { is_error: true } : {}),\n }));\n messages.push({ role: 'user', content: toolResultBlocks });\n\n // Continue the loop — re-call AI with tool results\n continue;\n }\n\n // 20e: Check stop reason — if end_turn or stop, we're done\n if (stopReason === 'end_turn' || stopReason === 'stop' || pendingToolCalls.length === 0) {\n yield {\n type: 'agent_complete',\n sessionId,\n timestamp: new Date().toISOString(),\n usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens, totalTurns: turn },\n };\n return;\n }\n }\n\n // Max turns exceeded\n yield {\n type: 'error',\n error: `Max turns (${config.maxTurns}) exceeded`,\n code: 'MAX_TURNS_EXCEEDED',\n recoverable: false,\n };\n yield {\n type: 'agent_complete',\n sessionId,\n timestamp: new Date().toISOString(),\n usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens, totalTurns: turn },\n };\n } catch (err) {\n // 20f: Top-level error handling\n yield {\n type: 'error',\n error: err instanceof Error ? err.message : String(err),\n code: 'AGENT_LOOP_ERROR',\n recoverable: false,\n };\n yield {\n type: 'agent_complete',\n sessionId,\n timestamp: new Date().toISOString(),\n usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens, totalTurns: turn },\n };\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Task 118: Wraps an async iterable with a timeout. If the timeout elapses\n * before the iterable completes, throws with message 'TIMEOUT'.\n * Also respects an optional AbortSignal for early cancellation.\n */\nasync function* withTimeout<T>(\n iterable: AsyncIterable<T>,\n timeoutMs: number,\n signal?: AbortSignal,\n): AsyncGenerator<T> {\n const iterator = iterable[Symbol.asyncIterator]();\n const timeoutError = new Error('TIMEOUT');\n\n while (true) {\n const timeoutPromise = new Promise<never>((_, reject) => {\n const timer = setTimeout(() => reject(timeoutError), timeoutMs);\n // If signal aborts, also reject (and clear timer)\n if (signal) {\n const onAbort = () => {\n clearTimeout(timer);\n reject(new Error('ABORTED'));\n };\n if (signal.aborted) {\n clearTimeout(timer);\n reject(new Error('ABORTED'));\n } else {\n signal.addEventListener('abort', onAbort, { once: true });\n }\n }\n });\n\n const result = await Promise.race([\n iterator.next(),\n timeoutPromise,\n ]);\n\n if (result.done) break;\n yield result.value;\n }\n}\n","// Task 21: SessionManager implementation\n\nimport type {\n GenerationRequest,\n GenerationSession,\n Message,\n SessionEvent,\n SessionStore,\n} from '@yolo-labs/core-types';\n\n/**\n * Options for constructing a SessionManager.\n *\n * @remarks\n * The `maxHistoryLength` controls when truncation kicks in (default 100).\n * Supply `onTruncate` for custom summarization; otherwise oldest messages\n * are dropped.\n */\nexport interface SessionManagerOptions {\n store: SessionStore;\n maxHistoryLength?: number;\n onTruncate?: (messages: Message[]) => Promise<Message[]>;\n}\n\n/**\n * Manages session lifecycle, event logging, and conversation history.\n *\n * @remarks\n * Wraps a {@link SessionStore} implementation to provide higher-level\n * operations: session creation from a request, event append, session\n * resumption, and automatic history truncation.\n */\nexport class SessionManager {\n private store: SessionStore;\n private maxHistoryLength: number;\n private onTruncate?: (messages: Message[]) => Promise<Message[]>;\n\n constructor(options: SessionManagerOptions) {\n this.store = options.store;\n this.maxHistoryLength = options.maxHistoryLength ?? 100;\n this.onTruncate = options.onTruncate;\n }\n\n /**\n * Creates a new session from a generation request.\n *\n * @param request - The generation request to create a session for.\n * @param userId - Optional user ID to associate with the session.\n * @returns The newly created {@link GenerationSession}.\n */\n async createSession(\n request: GenerationRequest,\n userId?: string,\n ): Promise<GenerationSession> {\n return this.store.create({\n userId,\n conversationHistory: [],\n toolCallLog: [],\n metadata: request.metadata ?? {},\n });\n }\n\n /**\n * Appends a session event to the session's event log.\n *\n * @param sessionId - The session to append the event to.\n * @param event - The {@link SessionEvent} to record.\n */\n async appendEvent(sessionId: string, event: SessionEvent): Promise<void> {\n await this.store.appendEvent(sessionId, event);\n }\n\n /**\n * Resumes an existing session, returning the session and events emitted since a given point.\n *\n * @param sessionId - The session to resume.\n * @param sinceEventId - Optional event ID to replay from. Returns all events if omitted.\n * @returns The session object and the events since the specified event ID.\n * @throws Error if the session does not exist.\n */\n async resumeSession(\n sessionId: string,\n sinceEventId?: string,\n ): Promise<{\n session: GenerationSession;\n events: SessionEvent[];\n }> {\n const session = await this.store.get(sessionId);\n if (!session) {\n throw new Error(`Session not found: ${sessionId}`);\n }\n const events = await this.store.getEventsSince(sessionId, sinceEventId);\n return { session, events };\n }\n\n /** Retrieves the conversation history for a given session. */\n async getConversationHistory(sessionId: string): Promise<Message[]> {\n const session = await this.store.get(sessionId);\n if (!session) {\n throw new Error(`Session not found: ${sessionId}`);\n }\n return session.conversationHistory;\n }\n\n /**\n * Updates conversation history, applying truncation if it exceeds the configured max length.\n *\n * @param sessionId - The session to update.\n * @param messages - The full updated conversation history.\n *\n * @remarks\n * If the history exceeds `maxHistoryLength`, the custom `onTruncate` hook\n * is called if provided; otherwise the oldest messages are dropped.\n */\n async updateConversationHistory(\n sessionId: string,\n messages: Message[],\n ): Promise<void> {\n // 21d: Truncation/summarization\n let history = messages;\n if (history.length > this.maxHistoryLength) {\n if (this.onTruncate) {\n history = await this.onTruncate(history);\n } else {\n // Default: keep the most recent messages\n history = history.slice(-this.maxHistoryLength);\n }\n }\n\n await this.store.update(sessionId, { conversationHistory: history });\n }\n\n /** Retrieves a session by ID, or undefined if not found. */\n async getSession(sessionId: string): Promise<GenerationSession | undefined> {\n return this.store.get(sessionId);\n }\n\n /** Deletes a session and its associated data from the store. */\n async deleteSession(sessionId: string): Promise<void> {\n await this.store.delete(sessionId);\n }\n}\n","// Task 22: InMemorySessionStore\n\nimport type { GenerationSession, SessionEvent, SessionStore } from '@yolo-labs/core-types';\n\n/**\n * In-memory implementation of {@link SessionStore} using Maps.\n *\n * @remarks\n * Suitable for development, testing, and single-process deployments.\n * All data is lost when the process exits. For persistent storage,\n * implement a custom {@link SessionStore} backed by a database.\n */\nexport class InMemorySessionStore implements SessionStore {\n private sessions = new Map<string, GenerationSession>();\n private events = new Map<string, SessionEvent[]>();\n\n /** Creates a new session with a generated ID and timestamps. */\n async create(\n session: Omit<GenerationSession, 'id' | 'createdAt' | 'updatedAt'>,\n ): Promise<GenerationSession> {\n const id = crypto.randomUUID();\n const now = new Date().toISOString();\n const full: GenerationSession = {\n ...session,\n id,\n createdAt: now,\n updatedAt: now,\n };\n this.sessions.set(id, full);\n this.events.set(id, []);\n return full;\n }\n\n /** Retrieves a session by ID, or undefined if not found. */\n async get(id: string): Promise<GenerationSession | undefined> {\n return this.sessions.get(id);\n }\n\n /** Merges partial updates into an existing session, preserving ID and createdAt. */\n async update(\n id: string,\n updates: Partial<GenerationSession>,\n ): Promise<GenerationSession> {\n const existing = this.sessions.get(id);\n if (!existing) {\n throw new Error(`Session not found: ${id}`);\n }\n const updated: GenerationSession = {\n ...existing,\n ...updates,\n id: existing.id,\n createdAt: existing.createdAt,\n updatedAt: new Date().toISOString(),\n };\n this.sessions.set(id, updated);\n return updated;\n }\n\n /** Deletes a session and its associated events. */\n async delete(id: string): Promise<void> {\n this.sessions.delete(id);\n this.events.delete(id);\n }\n\n /** Lists all sessions belonging to a given user. */\n async listByUser(userId: string): Promise<GenerationSession[]> {\n return Array.from(this.sessions.values()).filter(\n (s) => s.userId === userId,\n );\n }\n\n /** Appends an event to the session's event log. */\n async appendEvent(sessionId: string, event: SessionEvent): Promise<void> {\n const events = this.events.get(sessionId);\n if (!events) {\n throw new Error(`Session not found: ${sessionId}`);\n }\n events.push(event);\n }\n\n /** Returns events for a session, optionally starting from a given event index. */\n async getEventsSince(\n sessionId: string,\n eventId?: string,\n ): Promise<SessionEvent[]> {\n const events = this.events.get(sessionId);\n if (!events) {\n throw new Error(`Session not found: ${sessionId}`);\n }\n if (!eventId) {\n return [...events];\n }\n // For in-memory store, eventId is treated as an index\n const index = parseInt(eventId, 10);\n if (isNaN(index)) {\n return [...events];\n }\n return events.slice(index);\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yolo-labs/core-agent-service",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"import": "./dist/index.js"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"main": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@yolo-labs/core-types": "1.0.0"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"tsup": "^8.0.0",
|
|
21
|
+
"typescript": "^5.5.0"
|
|
22
|
+
},
|
|
23
|
+
"description": "Agent loop (async generator) and session management for AI agents",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/yolo-labs-hq/monorepo.git",
|
|
28
|
+
"directory": "yolo-core/packages/agent-service"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public",
|
|
32
|
+
"registry": "https://registry.npmjs.org/"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/yolo-labs-hq/monorepo/tree/main/yolo-core#readme",
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/yolo-labs-hq/monorepo/issues"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsup",
|
|
40
|
+
"clean": "rm -rf dist *.tsbuildinfo"
|
|
41
|
+
}
|
|
42
|
+
}
|