@rudderjs/ai 1.3.0 → 1.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 +17 -2
- package/boost/guidelines.md +13 -1
- package/dist/agent.d.ts +80 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +319 -79
- package/dist/agent.js.map +1 -1
- package/dist/conversation-persistence.d.ts +46 -0
- package/dist/conversation-persistence.d.ts.map +1 -0
- package/dist/conversation-persistence.js +152 -0
- package/dist/conversation-persistence.js.map +1 -0
- package/dist/conversation.d.ts +2 -7
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +3 -1
- package/dist/conversation.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/sub-agent-run-store.d.ts +106 -0
- package/dist/sub-agent-run-store.d.ts.map +1 -0
- package/dist/sub-agent-run-store.js +80 -0
- package/dist/sub-agent-run-store.js.map +1 -0
- package/dist/types.d.ts +97 -6
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -231,7 +231,7 @@ new Researcher().asTool({
|
|
|
231
231
|
})
|
|
232
232
|
```
|
|
233
233
|
|
|
234
|
-
The wrapped subagent runs via `prompt()` (non-streaming)
|
|
234
|
+
The wrapped subagent runs via `prompt()` (non-streaming) by default — to surface inner-agent progress as `tool-update` chunks in the parent stream, pass `streaming: true` (or a custom `(chunk) => SubAgentUpdate | null` projector). When the sub-agent's model emits a *client* tool call, opt into the suspend/resume protocol with `suspendable: { runStore }` — the parent loop halts with the inner agent's `pendingClientToolCalls`, the snapshot persists in the run store, and the host resumes via `Agent.resumeAsTool(subRunId, browserResults, { runStore, agent })`. See `docs/guide/ai.md` for the full flow. `InMemorySubAgentRunStore` works for tests; `CachedSubAgentRunStore` plugs into `@rudderjs/cache` for cross-process persistence. Suspend without streaming throws at builder time.
|
|
235
235
|
|
|
236
236
|
### Tool execution context
|
|
237
237
|
|
|
@@ -252,7 +252,7 @@ const myTool = toolDefinition({
|
|
|
252
252
|
})
|
|
253
253
|
```
|
|
254
254
|
|
|
255
|
-
The primary consumer is `@
|
|
255
|
+
The primary consumer is `@pilotiq-pro/ai`'s `runAgentTool`, which uses
|
|
256
256
|
`ctx.toolCallId` to correlate sub-agent suspensions with the parent's
|
|
257
257
|
`run_agent` call (see "Pausing the loop from a server tool" below).
|
|
258
258
|
|
|
@@ -604,6 +604,21 @@ const response = await agent('You are helpful.').prompt('Follow up question', {
|
|
|
604
604
|
|
|
605
605
|
Works with both `.prompt()` and `.stream()`. History messages are prepended after the system prompt, before the current user message.
|
|
606
606
|
|
|
607
|
+
### Auto-persist conversations
|
|
608
|
+
|
|
609
|
+
Override `conversational()` on an agent class to auto-load and auto-save threads without threading user ids through every call site:
|
|
610
|
+
|
|
611
|
+
```ts
|
|
612
|
+
class ChatAgent extends Agent {
|
|
613
|
+
conversational() { return { user: Auth.user()?.id } }
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
await new ChatAgent().prompt('Hi') // auto-loads + auto-saves
|
|
617
|
+
await new ChatAgent().prompt('Continue?') // resumes same thread (per user + class)
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
Returning `false` (the default) keeps the agent stateless. Async returns are awaited; an optional `historyLimit` caps loaded messages. Per-call escape hatches: `prompt(input, { conversation: false })` or `agent.forUser(id).prompt()` / `agent.continue(id).prompt()` — explicit always wins. See `docs/guide/ai.md` for the full precedence chain.
|
|
621
|
+
|
|
607
622
|
### Model Selection
|
|
608
623
|
|
|
609
624
|
Configure available models for user selection (used by `@rudderjs/panels` chat UI):
|
package/boost/guidelines.md
CHANGED
|
@@ -91,7 +91,7 @@ class Planner extends Agent implements HasTools {
|
|
|
91
91
|
}
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
By default the subagent runs via `prompt()` (non-streaming). Pass `streaming: true` to surface inner progress as `tool-update` chunks (default projection emits `agent_start` / `tool_call` / `agent_done`); pass `(chunk) => SubAgentUpdate | null` for a custom projector. To propagate inner client-tool calls upward through the parent loop, also pass `suspendable: { runStore }` (suspend without streaming throws at builder time) — the host's continuation calls `Agent.resumeAsTool(subRunId, results, { runStore, agent })` to resume the inner agent with the browser's results. `InMemorySubAgentRunStore` works for tests; `CachedSubAgentRunStore` plugs into `@rudderjs/cache` for multi-worker persistence.
|
|
95
95
|
|
|
96
96
|
### Middleware
|
|
97
97
|
|
|
@@ -135,6 +135,18 @@ const response = await myAgent.forUser('user-123').prompt('Hello') // creates c
|
|
|
135
135
|
const follow = await myAgent.continue(response.conversationId).prompt('Follow up')
|
|
136
136
|
```
|
|
137
137
|
|
|
138
|
+
For chat agents that should always auto-persist for the active user, override `conversational()` on the class — `agent.prompt(input)` then auto-loads + auto-saves without each caller passing the user id:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
class ChatAgent extends Agent {
|
|
142
|
+
conversational() { return { user: Auth.user()?.id } } // null user → opt-out
|
|
143
|
+
}
|
|
144
|
+
await new ChatAgent().prompt('Hi') // auto-loads thread
|
|
145
|
+
await new ChatAgent().prompt('still you?') // resumes per (user, class)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Returning `false` (default) keeps the agent stateless. Optional `historyLimit: N` caps loaded messages. Per-call `{ conversation: false }` opts out; `forUser`/`continue` always win.
|
|
149
|
+
|
|
138
150
|
### Streaming
|
|
139
151
|
|
|
140
152
|
Use `.stream()` for real-time token delivery:
|
package/dist/agent.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { ServerToolBuilder } from './tool.js';
|
|
3
3
|
import { QueuedPromptBuilder } from './queue-job.js';
|
|
4
|
-
import type {
|
|
4
|
+
import type { SubAgentRunStore } from './sub-agent-run-store.js';
|
|
5
|
+
import type { AgentPromptOptions, AiMessage, AiMiddleware, AgentResponse, AgentStep, AgentStreamResponse, AnyTool, CacheableConfig, ConversationalSpec, ConversationStore, SubAgentUpdate, HasMiddleware, HasTools, PrepareStepResult, StopCondition, StreamChunk } from './types.js';
|
|
5
6
|
/** Stop after N steps */
|
|
6
7
|
export declare function stepCountIs(n: number): StopCondition;
|
|
7
8
|
/** Stop when a specific tool is called in the latest step */
|
|
@@ -51,6 +52,34 @@ export declare abstract class Agent {
|
|
|
51
52
|
* }
|
|
52
53
|
*/
|
|
53
54
|
cacheable(): CacheableConfig | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Opt into auto-persisted conversation behavior. Override on a subclass
|
|
57
|
+
* to declare *which* user owns the thread and (optionally) which
|
|
58
|
+
* specific thread, and the framework will load history before each
|
|
59
|
+
* `prompt()`/`stream()` call and append the new turn after it — without
|
|
60
|
+
* any caller having to remember `forUser()` / `continue()`.
|
|
61
|
+
*
|
|
62
|
+
* Returning `false` (the default) disables auto-persist; the agent runs
|
|
63
|
+
* stateless. Returning a {@link ConversationalSpec} opts in:
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* class ChatAgent extends Agent {
|
|
67
|
+
* conversational() {
|
|
68
|
+
* return { user: Auth.user()?.id } // null user → falsy → opt-out
|
|
69
|
+
* }
|
|
70
|
+
* }
|
|
71
|
+
*
|
|
72
|
+
* await new ChatAgent().prompt('Hi') // auto-loads + auto-saves
|
|
73
|
+
*
|
|
74
|
+
* **Precedence (high → low):**
|
|
75
|
+
* 1. Explicit `agent.forUser(id).prompt()` / `agent.continue(id).prompt()`
|
|
76
|
+
* 2. Per-call `prompt(input, { conversation: false | {...} })`
|
|
77
|
+
* 3. This method's return value
|
|
78
|
+
*
|
|
79
|
+
* Async returns are supported — useful when the user identity is fetched
|
|
80
|
+
* from an async DI binding.
|
|
81
|
+
*/
|
|
82
|
+
conversational(): false | ConversationalSpec | Promise<false | ConversationalSpec>;
|
|
54
83
|
/**
|
|
55
84
|
* Default for `AgentPromptOptions.parallelTools`. When `true` (default),
|
|
56
85
|
* multiple tool calls within a single step run their `execute()` functions
|
|
@@ -102,15 +131,57 @@ export declare abstract class Agent {
|
|
|
102
131
|
inputSchema: TInput;
|
|
103
132
|
prompt: (input: z.infer<TInput>) => string;
|
|
104
133
|
modelOutput?: (response: AgentResponse) => string | Promise<string>;
|
|
134
|
+
streaming?: AsToolStreamingOption;
|
|
135
|
+
suspendable?: AsToolSuspendableOption;
|
|
105
136
|
}): ServerToolBuilder<z.infer<TInput>, AgentResponse>;
|
|
106
137
|
asTool(options: {
|
|
107
138
|
name: string;
|
|
108
139
|
description: string;
|
|
109
140
|
modelOutput?: (response: AgentResponse) => string | Promise<string>;
|
|
141
|
+
streaming?: AsToolStreamingOption;
|
|
142
|
+
suspendable?: AsToolSuspendableOption;
|
|
110
143
|
}): ServerToolBuilder<{
|
|
111
144
|
prompt: string;
|
|
112
145
|
}, AgentResponse>;
|
|
146
|
+
/**
|
|
147
|
+
* Resume a sub-agent run that previously paused with
|
|
148
|
+
* `pauseForClientTools` (typically from {@link Agent.asTool} with
|
|
149
|
+
* `suspendable: { runStore }` set). Loads the snapshot, validates the
|
|
150
|
+
* incoming tool-result ids against the pending set, and re-runs the
|
|
151
|
+
* inner loop with those results appended.
|
|
152
|
+
*
|
|
153
|
+
* Returns either a `'completed'` result (the inner agent finished) or
|
|
154
|
+
* a `'paused'` continuation pointing at a fresh `subRunId` for the
|
|
155
|
+
* next round-trip.
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* const r = await Agent.resumeAsTool(subRunId, browserResults, { runStore, agent: subAgent })
|
|
159
|
+
* if (r.kind === 'completed') {
|
|
160
|
+
* feedToolResultBackToParent(r.response.text)
|
|
161
|
+
* } else {
|
|
162
|
+
* emitPendingClientToolsSse(r.subRunId, r.pendingToolCallIds)
|
|
163
|
+
* }
|
|
164
|
+
*/
|
|
165
|
+
static resumeAsTool(subRunId: string, clientToolResults: ReadonlyArray<{
|
|
166
|
+
toolCallId: string;
|
|
167
|
+
result: unknown;
|
|
168
|
+
}>, options: {
|
|
169
|
+
runStore: SubAgentRunStore;
|
|
170
|
+
agent: Agent;
|
|
171
|
+
}): Promise<{
|
|
172
|
+
kind: 'completed';
|
|
173
|
+
response: AgentResponse;
|
|
174
|
+
} | {
|
|
175
|
+
kind: 'paused';
|
|
176
|
+
subRunId: string;
|
|
177
|
+
pendingToolCallIds: string[];
|
|
178
|
+
}>;
|
|
113
179
|
}
|
|
180
|
+
type ChunkProjector = (chunk: StreamChunk) => SubAgentUpdate | null;
|
|
181
|
+
type AsToolStreamingOption = boolean | ChunkProjector;
|
|
182
|
+
type AsToolSuspendableOption = {
|
|
183
|
+
runStore: SubAgentRunStore;
|
|
184
|
+
};
|
|
114
185
|
/**
|
|
115
186
|
* Wraps an Agent to add conversation memory.
|
|
116
187
|
* Created via `agent.forUser(id)` or `agent.continue(id)`.
|
|
@@ -124,6 +195,13 @@ export declare class ConversableAgent {
|
|
|
124
195
|
continue(conversationId: string): this;
|
|
125
196
|
prompt(input: string, options?: AgentPromptOptions): Promise<AgentResponse>;
|
|
126
197
|
stream(input: string, options?: AgentPromptOptions): AgentStreamResponse;
|
|
198
|
+
/**
|
|
199
|
+
* Translate the wrapper's explicit-form state (`forUser` / `continue`)
|
|
200
|
+
* into a {@link ConversationalSpec}. The explicit chain bypasses the
|
|
201
|
+
* agent's `conversational()` declaration entirely — `forUser` always
|
|
202
|
+
* wins over class defaults.
|
|
203
|
+
*/
|
|
204
|
+
private toSpec;
|
|
127
205
|
}
|
|
128
206
|
/**
|
|
129
207
|
* Create an anonymous agent inline.
|
|
@@ -160,4 +238,5 @@ export interface InvalidToolArgumentsError {
|
|
|
160
238
|
message: string;
|
|
161
239
|
}>;
|
|
162
240
|
}
|
|
241
|
+
export {};
|
|
163
242
|
//# sourceMappingURL=agent.d.ts.map
|
package/dist/agent.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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"}
|
package/dist/agent.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { AiRegistry } from './registry.js';
|
|
3
|
-
import { isPauseForClientToolsChunk, toolDefinition, toolToSchema } from './tool.js';
|
|
3
|
+
import { isPauseForClientToolsChunk, pauseForClientTools, toolDefinition, toolToSchema } from './tool.js';
|
|
4
4
|
import { attachmentsToContentParts, getMessageText } from './attachment.js';
|
|
5
5
|
import { QueuedPromptBuilder } from './queue-job.js';
|
|
6
|
+
import { resolveAutoPersistSpec, runWithPersistence, runWithPersistenceStreaming, } from './conversation-persistence.js';
|
|
6
7
|
import { runOnConfig, runOnChunk, runOnBeforeToolCall, runOnAfterToolCall, runSequential, runOnUsage, runOnAbort, runOnError, } from './middleware.js';
|
|
7
8
|
// ─── AI Observer (lazy accessor) ─────────────────────────
|
|
8
9
|
function _getAiObservers() {
|
|
@@ -79,6 +80,36 @@ export class Agent {
|
|
|
79
80
|
* }
|
|
80
81
|
*/
|
|
81
82
|
cacheable() { return undefined; }
|
|
83
|
+
/**
|
|
84
|
+
* Opt into auto-persisted conversation behavior. Override on a subclass
|
|
85
|
+
* to declare *which* user owns the thread and (optionally) which
|
|
86
|
+
* specific thread, and the framework will load history before each
|
|
87
|
+
* `prompt()`/`stream()` call and append the new turn after it — without
|
|
88
|
+
* any caller having to remember `forUser()` / `continue()`.
|
|
89
|
+
*
|
|
90
|
+
* Returning `false` (the default) disables auto-persist; the agent runs
|
|
91
|
+
* stateless. Returning a {@link ConversationalSpec} opts in:
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* class ChatAgent extends Agent {
|
|
95
|
+
* conversational() {
|
|
96
|
+
* return { user: Auth.user()?.id } // null user → falsy → opt-out
|
|
97
|
+
* }
|
|
98
|
+
* }
|
|
99
|
+
*
|
|
100
|
+
* await new ChatAgent().prompt('Hi') // auto-loads + auto-saves
|
|
101
|
+
*
|
|
102
|
+
* **Precedence (high → low):**
|
|
103
|
+
* 1. Explicit `agent.forUser(id).prompt()` / `agent.continue(id).prompt()`
|
|
104
|
+
* 2. Per-call `prompt(input, { conversation: false | {...} })`
|
|
105
|
+
* 3. This method's return value
|
|
106
|
+
*
|
|
107
|
+
* Async returns are supported — useful when the user identity is fetched
|
|
108
|
+
* from an async DI binding.
|
|
109
|
+
*/
|
|
110
|
+
conversational() {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
82
113
|
/**
|
|
83
114
|
* Default for `AgentPromptOptions.parallelTools`. When `true` (default),
|
|
84
115
|
* multiple tool calls within a single step run their `execute()` functions
|
|
@@ -88,11 +119,15 @@ export class Agent {
|
|
|
88
119
|
parallelTools() { return true; }
|
|
89
120
|
/** Run the agent with a prompt (non-streaming) */
|
|
90
121
|
async prompt(input, options) {
|
|
122
|
+
const spec = await resolveAutoPersistSpec(() => this.conversational(), options?.conversation);
|
|
123
|
+
if (spec) {
|
|
124
|
+
return runWithPersistence(spec, this.constructor.name, resolveConversationStore, input, options, (effOptions) => runAgentLoop(this, input, effOptions));
|
|
125
|
+
}
|
|
91
126
|
return runAgentLoop(this, input, options);
|
|
92
127
|
}
|
|
93
128
|
/** Run the agent with a prompt (streaming) */
|
|
94
129
|
stream(input, options) {
|
|
95
|
-
return
|
|
130
|
+
return runStreamWithMaybeAutoPersist(this, input, options);
|
|
96
131
|
}
|
|
97
132
|
/** Queue the prompt for background execution */
|
|
98
133
|
queue(input, options) {
|
|
@@ -107,17 +142,201 @@ export class Agent {
|
|
|
107
142
|
return new ConversableAgent(this).continue(conversationId);
|
|
108
143
|
}
|
|
109
144
|
asTool(options) {
|
|
145
|
+
if (options.suspendable && !options.streaming) {
|
|
146
|
+
throw new Error('[RudderJS AI] asTool: `suspendable` requires `streaming: true` (or a projector). Silent suspend would leave the parent UI with no progress signal between sub-agent invocations.');
|
|
147
|
+
}
|
|
110
148
|
const schema = options.inputSchema ?? z.object({ prompt: z.string() });
|
|
111
149
|
const promptOf = options.prompt ?? ((input) => input.prompt);
|
|
112
150
|
const modelOutput = options.modelOutput ?? ((response) => response.text);
|
|
151
|
+
if (!options.streaming) {
|
|
152
|
+
// 1.2.0 zero-config path — single prompt() call, single AgentResponse out.
|
|
153
|
+
return toolDefinition({
|
|
154
|
+
name: options.name,
|
|
155
|
+
description: options.description,
|
|
156
|
+
inputSchema: schema,
|
|
157
|
+
})
|
|
158
|
+
.server((input) => this.prompt(promptOf(input)))
|
|
159
|
+
.modelOutput(modelOutput);
|
|
160
|
+
}
|
|
161
|
+
const project = options.streaming === true ? defaultSubAgentProjector : options.streaming;
|
|
162
|
+
const innerAgent = this; // eslint-disable-line @typescript-eslint/no-this-alias
|
|
163
|
+
const agentName = options.name;
|
|
164
|
+
const suspendable = options.suspendable;
|
|
165
|
+
const generatorExecute = async function* (input) {
|
|
166
|
+
const userPrompt = promptOf(input);
|
|
167
|
+
yield { kind: 'agent_start', agentName };
|
|
168
|
+
const streamOpts = suspendable
|
|
169
|
+
? { toolCallStreamingMode: 'stop-on-client-tool' }
|
|
170
|
+
: undefined;
|
|
171
|
+
const { stream, response } = innerAgent.stream(userPrompt, streamOpts);
|
|
172
|
+
for await (const chunk of stream) {
|
|
173
|
+
const update = project(chunk);
|
|
174
|
+
if (update)
|
|
175
|
+
yield update;
|
|
176
|
+
}
|
|
177
|
+
const result = await response;
|
|
178
|
+
if (suspendable &&
|
|
179
|
+
result.finishReason === 'client_tool_calls' &&
|
|
180
|
+
result.pendingClientToolCalls?.length) {
|
|
181
|
+
const subRunId = generateSubRunId();
|
|
182
|
+
const snapshot = {
|
|
183
|
+
messages: buildSubAgentSnapshotMessages(userPrompt, result),
|
|
184
|
+
pendingToolCallIds: result.pendingClientToolCalls.map((tc) => tc.id),
|
|
185
|
+
stepsSoFar: result.steps.length,
|
|
186
|
+
tokensSoFar: result.usage?.totalTokens ?? 0,
|
|
187
|
+
};
|
|
188
|
+
await suspendable.runStore.store(subRunId, snapshot);
|
|
189
|
+
yield { kind: 'subagent_paused', subRunId, pendingToolCallIds: snapshot.pendingToolCallIds };
|
|
190
|
+
yield pauseForClientTools(result.pendingClientToolCalls, subRunId);
|
|
191
|
+
// Unreachable — the parent loop halts iteration after the pause chunk.
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
yield {
|
|
195
|
+
kind: 'agent_done',
|
|
196
|
+
steps: result.steps.length,
|
|
197
|
+
tokens: result.usage?.totalTokens ?? 0,
|
|
198
|
+
};
|
|
199
|
+
return result;
|
|
200
|
+
};
|
|
113
201
|
return toolDefinition({
|
|
114
202
|
name: options.name,
|
|
115
203
|
description: options.description,
|
|
116
204
|
inputSchema: schema,
|
|
117
205
|
})
|
|
118
|
-
.server(
|
|
206
|
+
.server(generatorExecute)
|
|
119
207
|
.modelOutput(modelOutput);
|
|
120
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* Resume a sub-agent run that previously paused with
|
|
211
|
+
* `pauseForClientTools` (typically from {@link Agent.asTool} with
|
|
212
|
+
* `suspendable: { runStore }` set). Loads the snapshot, validates the
|
|
213
|
+
* incoming tool-result ids against the pending set, and re-runs the
|
|
214
|
+
* inner loop with those results appended.
|
|
215
|
+
*
|
|
216
|
+
* Returns either a `'completed'` result (the inner agent finished) or
|
|
217
|
+
* a `'paused'` continuation pointing at a fresh `subRunId` for the
|
|
218
|
+
* next round-trip.
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* const r = await Agent.resumeAsTool(subRunId, browserResults, { runStore, agent: subAgent })
|
|
222
|
+
* if (r.kind === 'completed') {
|
|
223
|
+
* feedToolResultBackToParent(r.response.text)
|
|
224
|
+
* } else {
|
|
225
|
+
* emitPendingClientToolsSse(r.subRunId, r.pendingToolCallIds)
|
|
226
|
+
* }
|
|
227
|
+
*/
|
|
228
|
+
static async resumeAsTool(subRunId, clientToolResults, options) {
|
|
229
|
+
const snapshot = await options.runStore.consume(subRunId);
|
|
230
|
+
if (!snapshot) {
|
|
231
|
+
throw new Error(`[RudderJS AI] resumeAsTool: subRunId "${subRunId}" expired or never existed.`);
|
|
232
|
+
}
|
|
233
|
+
// Forgery guard — every incoming tool-result id must be in the pending set.
|
|
234
|
+
const pending = new Set(snapshot.pendingToolCallIds);
|
|
235
|
+
const seen = new Set();
|
|
236
|
+
for (const r of clientToolResults) {
|
|
237
|
+
if (!pending.has(r.toolCallId)) {
|
|
238
|
+
throw new Error(`[RudderJS AI] resumeAsTool: toolCallId "${r.toolCallId}" was not in the pending set.`);
|
|
239
|
+
}
|
|
240
|
+
if (seen.has(r.toolCallId)) {
|
|
241
|
+
throw new Error(`[RudderJS AI] resumeAsTool: duplicate result for toolCallId "${r.toolCallId}".`);
|
|
242
|
+
}
|
|
243
|
+
seen.add(r.toolCallId);
|
|
244
|
+
}
|
|
245
|
+
// Append client tool-result messages to the snapshot, in incoming order.
|
|
246
|
+
const messages = [...snapshot.messages];
|
|
247
|
+
for (const r of clientToolResults) {
|
|
248
|
+
messages.push({
|
|
249
|
+
role: 'tool',
|
|
250
|
+
content: typeof r.result === 'string' ? r.result : JSON.stringify(r.result),
|
|
251
|
+
toolCallId: r.toolCallId,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
const result = await options.agent.prompt('', {
|
|
255
|
+
messages,
|
|
256
|
+
toolCallStreamingMode: 'stop-on-client-tool',
|
|
257
|
+
});
|
|
258
|
+
if (result.finishReason === 'client_tool_calls' &&
|
|
259
|
+
result.pendingClientToolCalls?.length) {
|
|
260
|
+
const newSubRunId = generateSubRunId();
|
|
261
|
+
const newSnapshot = {
|
|
262
|
+
messages: buildResumeSnapshotMessages(messages, result),
|
|
263
|
+
pendingToolCallIds: result.pendingClientToolCalls.map((tc) => tc.id),
|
|
264
|
+
stepsSoFar: snapshot.stepsSoFar + result.steps.length,
|
|
265
|
+
tokensSoFar: snapshot.tokensSoFar + (result.usage?.totalTokens ?? 0),
|
|
266
|
+
...(snapshot.meta !== undefined ? { meta: snapshot.meta } : {}),
|
|
267
|
+
};
|
|
268
|
+
await options.runStore.store(newSubRunId, newSnapshot);
|
|
269
|
+
return {
|
|
270
|
+
kind: 'paused',
|
|
271
|
+
subRunId: newSubRunId,
|
|
272
|
+
pendingToolCallIds: newSnapshot.pendingToolCallIds,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
return { kind: 'completed', response: result };
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Default projection from inner-agent stream chunks to {@link SubAgentUpdate}
|
|
280
|
+
* events. Emits one `tool_call` per inner `tool-call` chunk; everything
|
|
281
|
+
* else is suppressed (the wrapping execute emits the `agent_start` /
|
|
282
|
+
* `agent_done` bookends and the suspend path emits `subagent_paused`).
|
|
283
|
+
*
|
|
284
|
+
* Hosts wanting different cadence (e.g. surfacing `text-delta` previews
|
|
285
|
+
* or per-step usage) pass `streaming: chunk => …` and own the discriminator.
|
|
286
|
+
*/
|
|
287
|
+
function defaultSubAgentProjector(chunk) {
|
|
288
|
+
if (chunk.type === 'tool-call' && chunk.toolCall?.name) {
|
|
289
|
+
return {
|
|
290
|
+
kind: 'tool_call',
|
|
291
|
+
tool: chunk.toolCall.name,
|
|
292
|
+
...(chunk.toolCall.arguments ? { args: chunk.toolCall.arguments } : {}),
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Reconstruct the inner-agent message history at the point the loop
|
|
299
|
+
* paused, so a subsequent {@link Agent.resumeAsTool} can rerun the loop
|
|
300
|
+
* with the appended client tool results. The shape is `[user, …(message
|
|
301
|
+
* + serverToolResults)*]` — system messages are omitted because the
|
|
302
|
+
* `messages` mode of the agent loop prepends `system` itself.
|
|
303
|
+
*
|
|
304
|
+
* Each step's `message` includes ALL `toolCalls` (server + client).
|
|
305
|
+
* Server-side `toolResults` are interleaved; client-side calls remain
|
|
306
|
+
* unfulfilled until resume appends their results.
|
|
307
|
+
*/
|
|
308
|
+
function buildSubAgentSnapshotMessages(userPrompt, response) {
|
|
309
|
+
const out = [{ role: 'user', content: userPrompt }];
|
|
310
|
+
for (const step of response.steps) {
|
|
311
|
+
out.push(step.message);
|
|
312
|
+
for (const tr of step.toolResults) {
|
|
313
|
+
const resultStr = typeof tr.result === 'string' ? tr.result : JSON.stringify(tr.result);
|
|
314
|
+
out.push({ role: 'tool', content: resultStr, toolCallId: tr.toolCallId });
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return out;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Snapshot reconstruction for a resume-time pause. The `priorMessages`
|
|
321
|
+
* already include the original user prompt + every step prior to the
|
|
322
|
+
* resume call. Append the freshly-completed steps' messages and any
|
|
323
|
+
* server-side tool results so the next resume sees the full history.
|
|
324
|
+
*/
|
|
325
|
+
function buildResumeSnapshotMessages(priorMessages, response) {
|
|
326
|
+
const out = [...priorMessages];
|
|
327
|
+
for (const step of response.steps) {
|
|
328
|
+
out.push(step.message);
|
|
329
|
+
for (const tr of step.toolResults) {
|
|
330
|
+
const resultStr = typeof tr.result === 'string' ? tr.result : JSON.stringify(tr.result);
|
|
331
|
+
out.push({ role: 'tool', content: resultStr, toolCallId: tr.toolCallId });
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return out;
|
|
335
|
+
}
|
|
336
|
+
function generateSubRunId() {
|
|
337
|
+
if (typeof globalThis.crypto?.randomUUID === 'function')
|
|
338
|
+
return globalThis.crypto.randomUUID();
|
|
339
|
+
return `sub-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`;
|
|
121
340
|
}
|
|
122
341
|
// ─── Conversable Agent (conversation persistence) ───────
|
|
123
342
|
/**
|
|
@@ -140,84 +359,35 @@ export class ConversableAgent {
|
|
|
140
359
|
return this;
|
|
141
360
|
}
|
|
142
361
|
async prompt(input, options) {
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
else {
|
|
152
|
-
const meta = this._userId ? { userId: this._userId } : undefined;
|
|
153
|
-
this._conversationId = await store.create(undefined, meta);
|
|
154
|
-
}
|
|
155
|
-
const response = await runAgentLoop(this.agent, input, { ...options, history });
|
|
156
|
-
// Persist messages
|
|
157
|
-
const newMessages = [
|
|
158
|
-
{ role: 'user', content: input },
|
|
159
|
-
...response.steps.flatMap(s => {
|
|
160
|
-
const msgs = [s.message];
|
|
161
|
-
for (const tr of s.toolResults) {
|
|
162
|
-
const resultStr = typeof tr.result === 'string' ? tr.result : JSON.stringify(tr.result);
|
|
163
|
-
msgs.push({ role: 'tool', content: resultStr, toolCallId: tr.toolCallId });
|
|
164
|
-
}
|
|
165
|
-
return msgs;
|
|
166
|
-
}),
|
|
167
|
-
];
|
|
168
|
-
await store.append(this._conversationId, newMessages);
|
|
169
|
-
return { text: response.text, steps: response.steps, usage: response.usage, conversationId: this._conversationId };
|
|
362
|
+
const spec = this.toSpec();
|
|
363
|
+
return runWithPersistence(spec, this.agent.constructor.name, resolveConversationStore, input, options, (effOptions) => runAgentLoop(this.agent, input, effOptions)).then((r) => {
|
|
364
|
+
// Track the resolved id back on the wrapper so a subsequent
|
|
365
|
+
// `wrapper.prompt()` call resumes the same thread.
|
|
366
|
+
if (r.conversationId)
|
|
367
|
+
this._conversationId = r.conversationId;
|
|
368
|
+
return r;
|
|
369
|
+
});
|
|
170
370
|
}
|
|
171
371
|
stream(input, options) {
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
})();
|
|
192
|
-
let resolveResponse;
|
|
193
|
-
const responsePromise = new Promise(r => { resolveResponse = r; });
|
|
194
|
-
const self = this; // eslint-disable-line @typescript-eslint/no-this-alias
|
|
195
|
-
const storeRef = store;
|
|
196
|
-
async function* generateStream() {
|
|
197
|
-
await setupPromise;
|
|
198
|
-
const history = [...loadedHistory, ...(options?.history ?? [])];
|
|
199
|
-
const inner = runAgentLoopStreaming(self.agent, input, { ...options, history });
|
|
200
|
-
for await (const chunk of inner.stream) {
|
|
201
|
-
yield chunk;
|
|
202
|
-
}
|
|
203
|
-
const response = await inner.response;
|
|
204
|
-
// Persist messages
|
|
205
|
-
const newMessages = [
|
|
206
|
-
{ role: 'user', content: input },
|
|
207
|
-
...response.steps.flatMap(s => {
|
|
208
|
-
const msgs = [s.message];
|
|
209
|
-
for (const tr of s.toolResults) {
|
|
210
|
-
const resultStr = typeof tr.result === 'string' ? tr.result : JSON.stringify(tr.result);
|
|
211
|
-
msgs.push({ role: 'tool', content: resultStr, toolCallId: tr.toolCallId });
|
|
212
|
-
}
|
|
213
|
-
return msgs;
|
|
214
|
-
}),
|
|
215
|
-
];
|
|
216
|
-
await storeRef.append(convId, newMessages);
|
|
217
|
-
const result = { text: response.text, steps: response.steps, usage: response.usage, conversationId: convId };
|
|
218
|
-
resolveResponse(result);
|
|
219
|
-
}
|
|
220
|
-
return { stream: generateStream(), response: responsePromise };
|
|
372
|
+
const spec = this.toSpec();
|
|
373
|
+
const persisted = runWithPersistenceStreaming(spec, this.agent.constructor.name, resolveConversationStore, input, options, (effOptions) => runAgentLoopStreaming(this.agent, input, effOptions));
|
|
374
|
+
// Update the wrapper's id once the run completes.
|
|
375
|
+
persisted.response.then((r) => { if (r.conversationId)
|
|
376
|
+
this._conversationId = r.conversationId; }, () => { });
|
|
377
|
+
return persisted;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Translate the wrapper's explicit-form state (`forUser` / `continue`)
|
|
381
|
+
* into a {@link ConversationalSpec}. The explicit chain bypasses the
|
|
382
|
+
* agent's `conversational()` declaration entirely — `forUser` always
|
|
383
|
+
* wins over class defaults.
|
|
384
|
+
*/
|
|
385
|
+
toSpec() {
|
|
386
|
+
if (this._conversationId)
|
|
387
|
+
return { user: this._userId ?? '', id: this._conversationId };
|
|
388
|
+
if (this._userId)
|
|
389
|
+
return { user: this._userId };
|
|
390
|
+
throw new Error('[RudderJS AI] ConversableAgent requires forUser() or continue() to be called before prompt().');
|
|
221
391
|
}
|
|
222
392
|
}
|
|
223
393
|
// ─── Anonymous Agent ─────────────────────────────────────
|
|
@@ -267,6 +437,76 @@ export function setConversationStore(store) {
|
|
|
267
437
|
function resolveConversationStore() {
|
|
268
438
|
return _conversationStore;
|
|
269
439
|
}
|
|
440
|
+
/**
|
|
441
|
+
* Streaming counterpart of `Agent.prompt`'s auto-persist branch. The spec
|
|
442
|
+
* resolution is async (since `conversational()` may return a Promise), so
|
|
443
|
+
* we defer the decision into the outer wrapper that handles the inner
|
|
444
|
+
* stream's setup the same way `runWithPersistenceStreaming` does for the
|
|
445
|
+
* persisted path.
|
|
446
|
+
*/
|
|
447
|
+
function runStreamWithMaybeAutoPersist(a, input, options) {
|
|
448
|
+
// Synchronous fast path — most agents don't override `conversational()`,
|
|
449
|
+
// so we'd pay an extra microtask boundary on every streaming call. Bail
|
|
450
|
+
// out cheaply when we can prove the call is stateless.
|
|
451
|
+
const declared = a.conversational();
|
|
452
|
+
const isFast = (options?.conversation === false ||
|
|
453
|
+
(declared === false && (options?.conversation === undefined)));
|
|
454
|
+
if (isFast) {
|
|
455
|
+
return runAgentLoopStreaming(a, input, options);
|
|
456
|
+
}
|
|
457
|
+
// Async path — resolve the spec, then dispatch to the persisted or plain stream.
|
|
458
|
+
let resolveResp;
|
|
459
|
+
let rejectResp;
|
|
460
|
+
const responsePromise = new Promise((res, rej) => { resolveResp = res; rejectResp = rej; });
|
|
461
|
+
async function* outer() {
|
|
462
|
+
let spec;
|
|
463
|
+
try {
|
|
464
|
+
spec = await resolveAutoPersistSpec(() => a.conversational(), options?.conversation);
|
|
465
|
+
}
|
|
466
|
+
catch (err) {
|
|
467
|
+
rejectResp(err);
|
|
468
|
+
throw err;
|
|
469
|
+
}
|
|
470
|
+
if (!spec) {
|
|
471
|
+
const inner = runAgentLoopStreaming(a, input, options);
|
|
472
|
+
try {
|
|
473
|
+
for await (const chunk of inner.stream)
|
|
474
|
+
yield chunk;
|
|
475
|
+
}
|
|
476
|
+
catch (err) {
|
|
477
|
+
rejectResp(err);
|
|
478
|
+
throw err;
|
|
479
|
+
}
|
|
480
|
+
try {
|
|
481
|
+
const r = await inner.response;
|
|
482
|
+
resolveResp(r);
|
|
483
|
+
}
|
|
484
|
+
catch (err) {
|
|
485
|
+
rejectResp(err);
|
|
486
|
+
throw err;
|
|
487
|
+
}
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
const persisted = runWithPersistenceStreaming(spec, a.constructor.name, resolveConversationStore, input, options, (effOptions) => runAgentLoopStreaming(a, input, effOptions));
|
|
491
|
+
try {
|
|
492
|
+
for await (const chunk of persisted.stream)
|
|
493
|
+
yield chunk;
|
|
494
|
+
}
|
|
495
|
+
catch (err) {
|
|
496
|
+
rejectResp(err);
|
|
497
|
+
throw err;
|
|
498
|
+
}
|
|
499
|
+
try {
|
|
500
|
+
const r = await persisted.response;
|
|
501
|
+
resolveResp(r);
|
|
502
|
+
}
|
|
503
|
+
catch (err) {
|
|
504
|
+
rejectResp(err);
|
|
505
|
+
throw err;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
return { stream: outer(), response: responsePromise };
|
|
509
|
+
}
|
|
270
510
|
// ─── Helpers ─────────────────────────────────────────────
|
|
271
511
|
function getTools(a) {
|
|
272
512
|
return 'tools' in a && typeof a.tools === 'function'
|