@agnt5/sdk 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +183 -0
- package/dist/__tests__/integration/helpers.d.ts +41 -0
- package/dist/__tests__/integration/helpers.d.ts.map +1 -0
- package/dist/__tests__/integration/helpers.js +78 -0
- package/dist/__tests__/integration/helpers.js.map +1 -0
- package/dist/agent.d.ts +260 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +493 -0
- package/dist/agent.js.map +1 -0
- package/dist/async-context.d.ts +57 -0
- package/dist/async-context.d.ts.map +1 -0
- package/dist/async-context.js +52 -0
- package/dist/async-context.js.map +1 -0
- package/dist/batch.d.ts +116 -0
- package/dist/batch.d.ts.map +1 -0
- package/dist/batch.js +98 -0
- package/dist/batch.js.map +1 -0
- package/dist/chat.d.ts +137 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +278 -0
- package/dist/chat.js.map +1 -0
- package/dist/client.d.ts +394 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +757 -0
- package/dist/client.js.map +1 -0
- package/dist/context.d.ts +47 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +244 -0
- package/dist/context.js.map +1 -0
- package/dist/errors.d.ts +148 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +201 -0
- package/dist/errors.js.map +1 -0
- package/dist/eval.d.ts +242 -0
- package/dist/eval.d.ts.map +1 -0
- package/dist/eval.js +452 -0
- package/dist/eval.js.map +1 -0
- package/dist/event-emitter.d.ts +28 -0
- package/dist/event-emitter.d.ts.map +1 -0
- package/dist/event-emitter.js +79 -0
- package/dist/event-emitter.js.map +1 -0
- package/dist/events.d.ts +285 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +256 -0
- package/dist/events.js.map +1 -0
- package/dist/function.d.ts +61 -0
- package/dist/function.d.ts.map +1 -0
- package/dist/function.js +78 -0
- package/dist/function.js.map +1 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +80 -0
- package/dist/index.js.map +1 -0
- package/dist/lm.d.ts +301 -0
- package/dist/lm.d.ts.map +1 -0
- package/dist/lm.js +283 -0
- package/dist/lm.js.map +1 -0
- package/dist/logging.d.ts +68 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/logging.js +165 -0
- package/dist/logging.js.map +1 -0
- package/dist/mcp-server.d.ts +98 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +307 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/mcp.d.ts +73 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +224 -0
- package/dist/mcp.js.map +1 -0
- package/dist/memory.d.ts +234 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/memory.js +609 -0
- package/dist/memory.js.map +1 -0
- package/dist/platform-adapters.d.ts +121 -0
- package/dist/platform-adapters.d.ts.map +1 -0
- package/dist/platform-adapters.js +174 -0
- package/dist/platform-adapters.js.map +1 -0
- package/dist/platform-context.d.ts +55 -0
- package/dist/platform-context.d.ts.map +1 -0
- package/dist/platform-context.js +196 -0
- package/dist/platform-context.js.map +1 -0
- package/dist/retry-utils.d.ts +169 -0
- package/dist/retry-utils.d.ts.map +1 -0
- package/dist/retry-utils.js +304 -0
- package/dist/retry-utils.js.map +1 -0
- package/dist/sandbox.d.ts +103 -0
- package/dist/sandbox.d.ts.map +1 -0
- package/dist/sandbox.js +168 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/schema-utils.d.ts +250 -0
- package/dist/schema-utils.d.ts.map +1 -0
- package/dist/schema-utils.js +444 -0
- package/dist/schema-utils.js.map +1 -0
- package/dist/scorer.d.ts +130 -0
- package/dist/scorer.d.ts.map +1 -0
- package/dist/scorer.js +211 -0
- package/dist/scorer.js.map +1 -0
- package/dist/state.d.ts +92 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +151 -0
- package/dist/state.js.map +1 -0
- package/dist/tool.d.ts +120 -0
- package/dist/tool.d.ts.map +1 -0
- package/dist/tool.js +215 -0
- package/dist/tool.js.map +1 -0
- package/dist/tracing.d.ts +82 -0
- package/dist/tracing.d.ts.map +1 -0
- package/dist/tracing.js +206 -0
- package/dist/tracing.js.map +1 -0
- package/dist/types.d.ts +139 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/worker.d.ts +111 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +944 -0
- package/dist/worker.js.map +1 -0
- package/dist/workflow-utils.d.ts +257 -0
- package/dist/workflow-utils.d.ts.map +1 -0
- package/dist/workflow-utils.js +370 -0
- package/dist/workflow-utils.js.map +1 -0
- package/dist/workflow.d.ts +78 -0
- package/dist/workflow.d.ts.map +1 -0
- package/dist/workflow.js +138 -0
- package/dist/workflow.js.map +1 -0
- package/package.json +86 -0
package/dist/worker.js
ADDED
|
@@ -0,0 +1,944 @@
|
|
|
1
|
+
import { FunctionRegistry } from './function.js';
|
|
2
|
+
import { WorkflowRegistry } from './workflow.js';
|
|
3
|
+
import { ToolRegistry } from './tool.js';
|
|
4
|
+
import { ScorerRegistry, isScorer } from './scorer.js';
|
|
5
|
+
import { Message } from './agent.js';
|
|
6
|
+
import { ChatBot } from './chat.js';
|
|
7
|
+
import { runWithContext } from './async-context.js';
|
|
8
|
+
import { WaitingForUserInputError } from './errors.js';
|
|
9
|
+
import { EventEmitter } from './event-emitter.js';
|
|
10
|
+
import { generateCid, runStarted, runCompleted, runFailed, functionStarted, functionCompleted, functionFailed, workflowStarted, workflowCompleted, workflowFailed, workflowPaused, toolStarted, toolCompleted, toolFailed, } from './events.js';
|
|
11
|
+
import { createRequire } from 'module';
|
|
12
|
+
import { fileURLToPath } from 'url';
|
|
13
|
+
import { dirname, join } from 'path';
|
|
14
|
+
// Dynamic import for native bindings
|
|
15
|
+
let nativeBindings = null;
|
|
16
|
+
/**
|
|
17
|
+
* Load native bindings based on platform
|
|
18
|
+
*/
|
|
19
|
+
function loadNativeBindings() {
|
|
20
|
+
if (nativeBindings)
|
|
21
|
+
return nativeBindings;
|
|
22
|
+
try {
|
|
23
|
+
const runtime = getRuntime();
|
|
24
|
+
if (runtime === 'edge') {
|
|
25
|
+
// TODO: Load WASM bindings for edge runtimes
|
|
26
|
+
throw new Error('WASM bindings not yet implemented');
|
|
27
|
+
}
|
|
28
|
+
// Load NAPI bindings for Node.js/Bun/Deno
|
|
29
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
30
|
+
const __dirname = dirname(__filename);
|
|
31
|
+
const require = createRequire(import.meta.url);
|
|
32
|
+
// Try multiple paths to find the native module
|
|
33
|
+
const possiblePaths = [
|
|
34
|
+
join(__dirname, '../../native/agnt5-sdk-native.darwin-arm64.node'), // From dist/src (macOS)
|
|
35
|
+
join(__dirname, '../native/agnt5-sdk-native.darwin-arm64.node'), // From src (macOS)
|
|
36
|
+
join(__dirname, '../../native/agnt5-sdk-native.linux-x64-gnu.node'), // From dist/src (Linux)
|
|
37
|
+
join(__dirname, '../native/agnt5-sdk-native.linux-x64-gnu.node'), // From src (Linux)
|
|
38
|
+
join(__dirname, '../../native/agnt5-sdk-native.linux-x64.node'), // From dist/src (Linux fallback)
|
|
39
|
+
join(__dirname, '../native/agnt5-sdk-native.linux-x64.node'), // From src (Linux fallback)
|
|
40
|
+
];
|
|
41
|
+
for (const nativePath of possiblePaths) {
|
|
42
|
+
try {
|
|
43
|
+
nativeBindings = require(nativePath);
|
|
44
|
+
return nativeBindings;
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
// Try next path
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
throw new Error('Could not find native bindings in any expected location');
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
throw new Error(`Failed to load native bindings: ${error.message}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Simple context implementation
|
|
59
|
+
*/
|
|
60
|
+
class SimpleContext {
|
|
61
|
+
constructor(invocationId, runId, attempt, serviceName, state = new Map()) {
|
|
62
|
+
this.invocationId = invocationId;
|
|
63
|
+
this.runId = runId;
|
|
64
|
+
this.attempt = attempt;
|
|
65
|
+
this.serviceName = serviceName;
|
|
66
|
+
this.state = state;
|
|
67
|
+
this._stepCounter = 0;
|
|
68
|
+
this._stepCache = new Map();
|
|
69
|
+
// HITL state — populated by Worker.processMessage on resume from message metadata.
|
|
70
|
+
// _pauseIndex is the running counter incremented by each waitForUser call.
|
|
71
|
+
// _userResponses is a sparse cache of {pauseIndex -> user response} populated
|
|
72
|
+
// from the resume metadata so the workflow handler can replay past pauses.
|
|
73
|
+
//
|
|
74
|
+
// Multi-step replay works by persisting the full history of past responses
|
|
75
|
+
// into the `step_events` field of the workflow.paused event metadata. On
|
|
76
|
+
// every resume, loadReplayState hydrates _userResponses from step_events
|
|
77
|
+
// (historical) + user_response/pause_index (current), so the workflow
|
|
78
|
+
// re-execution can advance through all prior pauses before reaching a new
|
|
79
|
+
// one. Mirrors sdk-python's WorkflowEntity._completed_steps persistence.
|
|
80
|
+
this._pauseIndex = 0;
|
|
81
|
+
this._userResponses = new Map();
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Seed HITL replay state from incoming message metadata.
|
|
85
|
+
* Called by Worker.processMessage before invoking the workflow handler.
|
|
86
|
+
*
|
|
87
|
+
* Reads `user_response` and `pause_index` from metadata (set by the gateway's
|
|
88
|
+
* resume endpoint at runtime/crates/gateway/src/handlers/signals.rs) and
|
|
89
|
+
* caches the response so the next waitForUser call at that index returns it
|
|
90
|
+
* instead of throwing.
|
|
91
|
+
*/
|
|
92
|
+
loadReplayState(metadata) {
|
|
93
|
+
if (!metadata)
|
|
94
|
+
return;
|
|
95
|
+
// First, rehydrate any historical responses from step_events. These are
|
|
96
|
+
// accumulated by the emit path below and propagated through the gateway's
|
|
97
|
+
// resume handler (which copies the latest workflow.paused metadata into
|
|
98
|
+
// the next dispatch). Each entry is {pauseIndex -> decoded response}.
|
|
99
|
+
const stepEventsStr = metadata.step_events;
|
|
100
|
+
if (stepEventsStr) {
|
|
101
|
+
try {
|
|
102
|
+
const parsed = JSON.parse(stepEventsStr);
|
|
103
|
+
for (const [idxStr, value] of Object.entries(parsed)) {
|
|
104
|
+
const idx = Number.parseInt(idxStr, 10);
|
|
105
|
+
if (!Number.isNaN(idx)) {
|
|
106
|
+
this._userResponses.set(idx, value);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// Ignore corrupt step_events — fall back to the current pause only.
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Rehydrate persistent ctx.step() cache from completed_steps metadata.
|
|
115
|
+
// Any ctx.step() invocation persisted prior to the last pause is replayed
|
|
116
|
+
// on resume without re-execution; step() emits workflow.step.completed
|
|
117
|
+
// with cache_hit=true on each hit so the durability projection can prove
|
|
118
|
+
// replay happened.
|
|
119
|
+
const completedStepsStr = metadata.completed_steps;
|
|
120
|
+
if (completedStepsStr) {
|
|
121
|
+
try {
|
|
122
|
+
const parsed = JSON.parse(completedStepsStr);
|
|
123
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
124
|
+
this._stepCache.set(key, value);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// Best effort: ignore corrupt completed_steps
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Then layer the current resume's user_response on top at its pause_index.
|
|
132
|
+
const userResponse = metadata.user_response;
|
|
133
|
+
if (userResponse === undefined)
|
|
134
|
+
return;
|
|
135
|
+
const pauseIndexStr = metadata.pause_index ?? '0';
|
|
136
|
+
const pauseIndex = Number.parseInt(pauseIndexStr, 10);
|
|
137
|
+
if (Number.isNaN(pauseIndex))
|
|
138
|
+
return;
|
|
139
|
+
// Wire-format decoding mirrors sdk-python's wait_for_user (workflow.py:1520):
|
|
140
|
+
// "__skipped__" → null, "__custom__:..." → strip prefix.
|
|
141
|
+
let decoded = userResponse;
|
|
142
|
+
if (userResponse === '__skipped__' || userResponse === '__skip__') {
|
|
143
|
+
decoded = null;
|
|
144
|
+
}
|
|
145
|
+
else if (userResponse.startsWith('__custom__:')) {
|
|
146
|
+
decoded = userResponse.slice('__custom__:'.length);
|
|
147
|
+
}
|
|
148
|
+
this._userResponses.set(pauseIndex, decoded);
|
|
149
|
+
}
|
|
150
|
+
setEmitter(emitter) {
|
|
151
|
+
this._emitter = emitter;
|
|
152
|
+
}
|
|
153
|
+
setNativeWorker(worker) {
|
|
154
|
+
this._nativeWorker = worker;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Store the workflow + run correlation IDs so waitForUser can emit
|
|
158
|
+
* workflow.paused events with the correct parent/child linkage.
|
|
159
|
+
* Called by Worker.processMessage before invoking a workflow handler.
|
|
160
|
+
*/
|
|
161
|
+
setWorkflowCorrelation(workflowCid, runCid) {
|
|
162
|
+
this._workflowCid = workflowCid;
|
|
163
|
+
this._runCid = runCid;
|
|
164
|
+
}
|
|
165
|
+
async emit(event) {
|
|
166
|
+
if (this._emitter) {
|
|
167
|
+
await this._emitter.emit(event);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
get logger() {
|
|
171
|
+
const runId = this.runId;
|
|
172
|
+
return {
|
|
173
|
+
info: (message, meta) => {
|
|
174
|
+
console.log(`[INFO] ${message}`, meta || {});
|
|
175
|
+
nativeBindings?.logFromTypescript('INFO', message, runId, null, null, meta ?? null);
|
|
176
|
+
},
|
|
177
|
+
error: (message, meta) => {
|
|
178
|
+
console.error(`[ERROR] ${message}`, meta || {});
|
|
179
|
+
nativeBindings?.logFromTypescript('ERROR', message, runId, null, null, meta ?? null);
|
|
180
|
+
},
|
|
181
|
+
warn: (message, meta) => {
|
|
182
|
+
console.warn(`[WARN] ${message}`, meta || {});
|
|
183
|
+
nativeBindings?.logFromTypescript('WARN', message, runId, null, null, meta ?? null);
|
|
184
|
+
},
|
|
185
|
+
debug: (message, meta) => {
|
|
186
|
+
console.debug(`[DEBUG] ${message}`, meta || {});
|
|
187
|
+
nativeBindings?.logFromTypescript('DEBUG', message, runId, null, null, meta ?? null);
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
async get(key, defaultValue) {
|
|
192
|
+
return this.state.has(key) ? this.state.get(key) : defaultValue;
|
|
193
|
+
}
|
|
194
|
+
async set(key, value) {
|
|
195
|
+
this.state.set(key, value);
|
|
196
|
+
}
|
|
197
|
+
async delete(key) {
|
|
198
|
+
return this.state.delete(key);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Pause the workflow and request user input (HITL).
|
|
202
|
+
*
|
|
203
|
+
* On first call: throws WaitingForUserInputError, which Worker.processMessage
|
|
204
|
+
* catches and propagates as a `waiting_for_user_input` response to the
|
|
205
|
+
* platform. On resume, the platform re-dispatches the workflow with the
|
|
206
|
+
* user's response in metadata; loadReplayState seeds _userResponses, and
|
|
207
|
+
* the next call at the matching pauseIndex returns the cached value.
|
|
208
|
+
*
|
|
209
|
+
* Mirrors sdk-python/src/agnt5/workflow.py wait_for_user. The TS resume
|
|
210
|
+
* cache currently only restores the LATEST pause — see the FIXME at
|
|
211
|
+
* _userResponses for the multi-step replay limitation.
|
|
212
|
+
*/
|
|
213
|
+
async waitForUser(question, options) {
|
|
214
|
+
const pauseIndex = this._pauseIndex++;
|
|
215
|
+
// Resume path: response was injected from metadata before handler ran.
|
|
216
|
+
if (this._userResponses.has(pauseIndex)) {
|
|
217
|
+
return this._userResponses.get(pauseIndex);
|
|
218
|
+
}
|
|
219
|
+
// First-time path: emit workflow.paused, then throw to unwind the handler.
|
|
220
|
+
//
|
|
221
|
+
// The runtime's gateway tail treats `workflow.paused` as a terminal event
|
|
222
|
+
// for /v1/workflows/:name/run and transitions the run to the `paused`
|
|
223
|
+
// status via apply_paused. Without this emission, the /run endpoint hangs
|
|
224
|
+
// until the sync deadline. Mirrors sdk-python's wait_for_input which
|
|
225
|
+
// emits Paused via ctx.emit() before raising WaitingForUserInputException.
|
|
226
|
+
const stepName = `wait_for_user_${pauseIndex}`;
|
|
227
|
+
if (this._emitter && this._workflowCid && this._runCid) {
|
|
228
|
+
const metadata = {
|
|
229
|
+
pause_reason: 'user_input_required',
|
|
230
|
+
pause_index: String(pauseIndex),
|
|
231
|
+
step_name: stepName,
|
|
232
|
+
question,
|
|
233
|
+
};
|
|
234
|
+
if (options?.inputType)
|
|
235
|
+
metadata.input_type = options.inputType;
|
|
236
|
+
if (options?.allowCustom !== undefined)
|
|
237
|
+
metadata.allow_custom = String(options.allowCustom);
|
|
238
|
+
if (options?.skippable !== undefined)
|
|
239
|
+
metadata.skippable = String(options.skippable);
|
|
240
|
+
if (options?.options)
|
|
241
|
+
metadata.options = JSON.stringify(options.options);
|
|
242
|
+
// Persist history of completed pauses so subsequent resumes can replay
|
|
243
|
+
// the full chain. The gateway's resume handler copies the latest
|
|
244
|
+
// workflow.paused metadata verbatim onto the next dispatch, so every
|
|
245
|
+
// entry here will flow back into loadReplayState on the next run.
|
|
246
|
+
if (this._userResponses.size > 0) {
|
|
247
|
+
const stepEvents = {};
|
|
248
|
+
for (const [idx, resp] of this._userResponses.entries()) {
|
|
249
|
+
stepEvents[String(idx)] = resp;
|
|
250
|
+
}
|
|
251
|
+
metadata.step_events = JSON.stringify(stepEvents);
|
|
252
|
+
}
|
|
253
|
+
// Also persist ctx.step() results so durable steps replay across the
|
|
254
|
+
// resume boundary. Without this, _stepCache is instance-scoped to this
|
|
255
|
+
// dispatch and all prior step results would re-execute on every resume.
|
|
256
|
+
if (this._stepCache.size > 0) {
|
|
257
|
+
const completedSteps = {};
|
|
258
|
+
for (const [key, value] of this._stepCache.entries()) {
|
|
259
|
+
completedSteps[key] = value;
|
|
260
|
+
}
|
|
261
|
+
metadata.completed_steps = JSON.stringify(completedSteps);
|
|
262
|
+
}
|
|
263
|
+
try {
|
|
264
|
+
await this._emitter.emit(workflowPaused(this._workflowCid, this._runCid, {
|
|
265
|
+
reason: 'user_input_required',
|
|
266
|
+
pauseData: {
|
|
267
|
+
question,
|
|
268
|
+
input_type: options?.inputType,
|
|
269
|
+
options: options?.options,
|
|
270
|
+
pause_index: pauseIndex,
|
|
271
|
+
step_name: stepName,
|
|
272
|
+
allow_custom: options?.allowCustom,
|
|
273
|
+
skippable: options?.skippable,
|
|
274
|
+
},
|
|
275
|
+
metadata,
|
|
276
|
+
}));
|
|
277
|
+
}
|
|
278
|
+
catch (emitErr) {
|
|
279
|
+
// Best-effort: if the checkpoint write fails, we still want to throw
|
|
280
|
+
// the pause exception so the handler unwinds. The /run endpoint will
|
|
281
|
+
// time out rather than hang, and the caller gets a clear error.
|
|
282
|
+
console.error('Failed to emit workflow.paused event:', emitErr);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
throw new WaitingForUserInputError({
|
|
286
|
+
runId: this.runId,
|
|
287
|
+
question,
|
|
288
|
+
inputType: options?.inputType,
|
|
289
|
+
options: options?.options,
|
|
290
|
+
pauseIndex,
|
|
291
|
+
allowCustom: options?.allowCustom,
|
|
292
|
+
skippable: options?.skippable,
|
|
293
|
+
stepName,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Execute a durable step with checkpointing.
|
|
298
|
+
*
|
|
299
|
+
* On first execution: runs fn(), caches result, emits checkpoint.
|
|
300
|
+
* On replay: returns cached result without re-executing.
|
|
301
|
+
*/
|
|
302
|
+
async step(stepName, fn) {
|
|
303
|
+
const stepKey = `step:${stepName}:${this._stepCounter++}`;
|
|
304
|
+
// Cache hit: either same-run (local) or cross-dispatch (rehydrated from
|
|
305
|
+
// workflow.paused metadata by loadReplayState). On a hit, emit a
|
|
306
|
+
// workflow.step.completed event with cache_hit=true so downstream
|
|
307
|
+
// projections and tests can prove the step was replayed rather than
|
|
308
|
+
// re-executed. Mirrors sdk-python's durable task replay semantics.
|
|
309
|
+
if (this._stepCache.has(stepKey)) {
|
|
310
|
+
const cached = this._stepCache.get(stepKey);
|
|
311
|
+
if (this._nativeWorker?.emitCheckpoint) {
|
|
312
|
+
try {
|
|
313
|
+
await this._nativeWorker.emitCheckpoint(this.runId, 'workflow.step.completed', JSON.stringify({
|
|
314
|
+
step_key: stepKey,
|
|
315
|
+
step_name: stepName,
|
|
316
|
+
output: cached,
|
|
317
|
+
duration_ms: 0,
|
|
318
|
+
}), this._stepCounter, { cache_hit: 'true' }, Date.now() * 1000000);
|
|
319
|
+
}
|
|
320
|
+
catch { /* checkpoint emission is best-effort */ }
|
|
321
|
+
}
|
|
322
|
+
return cached;
|
|
323
|
+
}
|
|
324
|
+
// Emit step.started checkpoint via NAPI if available
|
|
325
|
+
const seqNum = this._stepCounter;
|
|
326
|
+
const tsNs = Date.now() * 1000000;
|
|
327
|
+
if (this._nativeWorker?.emitCheckpoint) {
|
|
328
|
+
try {
|
|
329
|
+
await this._nativeWorker.emitCheckpoint(this.runId, 'workflow.step.started', JSON.stringify({ step_key: stepKey, step_name: stepName }), seqNum, {}, tsNs);
|
|
330
|
+
}
|
|
331
|
+
catch { /* checkpoint emission is best-effort */ }
|
|
332
|
+
}
|
|
333
|
+
// Execute the step
|
|
334
|
+
const startMs = Date.now();
|
|
335
|
+
const result = await fn();
|
|
336
|
+
const durationMs = Date.now() - startMs;
|
|
337
|
+
// Cache locally
|
|
338
|
+
this._stepCache.set(stepKey, result);
|
|
339
|
+
// Emit step.completed checkpoint
|
|
340
|
+
if (this._nativeWorker?.emitCheckpoint) {
|
|
341
|
+
try {
|
|
342
|
+
await this._nativeWorker.emitCheckpoint(this.runId, 'workflow.step.completed', JSON.stringify({
|
|
343
|
+
step_key: stepKey,
|
|
344
|
+
step_name: stepName,
|
|
345
|
+
output: result,
|
|
346
|
+
duration_ms: durationMs,
|
|
347
|
+
}), seqNum + 1, {}, Date.now() * 1000000);
|
|
348
|
+
}
|
|
349
|
+
catch { /* checkpoint emission is best-effort */ }
|
|
350
|
+
}
|
|
351
|
+
return result;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Worker class for running AGNT5 functions with platform integration
|
|
356
|
+
*/
|
|
357
|
+
export class Worker {
|
|
358
|
+
constructor(serviceName, options = {}) {
|
|
359
|
+
this.isInitialized = false;
|
|
360
|
+
/** Registered agents (keyed by name) for dispatch */
|
|
361
|
+
this.agents = new Map();
|
|
362
|
+
/** Registered ChatBots (keyed by agent name) for webhook dispatch */
|
|
363
|
+
this.chatbots = new Map();
|
|
364
|
+
this.serviceName = serviceName;
|
|
365
|
+
this.options = {
|
|
366
|
+
...options,
|
|
367
|
+
serviceName,
|
|
368
|
+
runtime: 'standalone',
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Initialize the worker and load native bindings
|
|
373
|
+
*/
|
|
374
|
+
async initialize() {
|
|
375
|
+
if (this.isInitialized)
|
|
376
|
+
return;
|
|
377
|
+
const native = loadNativeBindings();
|
|
378
|
+
// Initialize SDK
|
|
379
|
+
try {
|
|
380
|
+
native.initialize(this.serviceName, this.options.serviceVersion || '0.1.0');
|
|
381
|
+
console.log('✓ SDK initialized with telemetry');
|
|
382
|
+
}
|
|
383
|
+
catch (error) {
|
|
384
|
+
// Telemetry might already be initialized, that's okay
|
|
385
|
+
console.log('SDK initialization:', error.message);
|
|
386
|
+
}
|
|
387
|
+
// Create native worker
|
|
388
|
+
this.nativeWorker = new native.Worker({
|
|
389
|
+
serviceName: this.serviceName,
|
|
390
|
+
serviceVersion: this.options.serviceVersion,
|
|
391
|
+
serviceType: this.options.serviceType || 'function',
|
|
392
|
+
coordinatorEndpoint: this.options.coordinatorEndpoint ||
|
|
393
|
+
process.env.AGNT5_COORDINATOR_ENDPOINT ||
|
|
394
|
+
'http://localhost:34186',
|
|
395
|
+
tenantId: this.options.tenantId ||
|
|
396
|
+
process.env.AGNT5_PROJECT_ID ||
|
|
397
|
+
process.env.AGNT5_TENANT_ID,
|
|
398
|
+
deploymentId: this.options.deploymentId ||
|
|
399
|
+
process.env.AGNT5_DEPLOYMENT_ID,
|
|
400
|
+
});
|
|
401
|
+
this.isInitialized = true;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Get worker ID
|
|
405
|
+
*/
|
|
406
|
+
get workerId() {
|
|
407
|
+
if (!this.nativeWorker) {
|
|
408
|
+
throw new Error('Worker not initialized. Call run() first.');
|
|
409
|
+
}
|
|
410
|
+
return this.nativeWorker.workerId;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Get coordinator endpoint
|
|
414
|
+
*/
|
|
415
|
+
get coordinatorEndpoint() {
|
|
416
|
+
if (!this.nativeWorker) {
|
|
417
|
+
throw new Error('Worker not initialized. Call run() first.');
|
|
418
|
+
}
|
|
419
|
+
return this.nativeWorker.coordinatorEndpoint;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Get tenant ID
|
|
423
|
+
*/
|
|
424
|
+
get tenantId() {
|
|
425
|
+
if (!this.nativeWorker) {
|
|
426
|
+
throw new Error('Worker not initialized. Call run() first.');
|
|
427
|
+
}
|
|
428
|
+
return this.nativeWorker.tenantId;
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Get deployment ID
|
|
432
|
+
*/
|
|
433
|
+
get deploymentId() {
|
|
434
|
+
if (!this.nativeWorker) {
|
|
435
|
+
throw new Error('Worker not initialized. Call run() first.');
|
|
436
|
+
}
|
|
437
|
+
return this.nativeWorker.deploymentId;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Register agents that can be dispatched by the worker.
|
|
441
|
+
* Accepts both Agent and ChatBot instances. ChatBot instances
|
|
442
|
+
* will have their wrapped agent registered for normal dispatch,
|
|
443
|
+
* plus the ChatBot tracked for webhook routing.
|
|
444
|
+
*/
|
|
445
|
+
registerAgents(agents) {
|
|
446
|
+
for (const item of agents) {
|
|
447
|
+
if (item instanceof ChatBot) {
|
|
448
|
+
this.chatbots.set(item.name, item);
|
|
449
|
+
// ChatBot wraps an agent — register the agent for the platform
|
|
450
|
+
// (The ChatBot.agent getter would need to be public for this)
|
|
451
|
+
// For now, just track by name — webhook dispatch uses ChatBot directly
|
|
452
|
+
console.log(`Registered ChatBot for agent '${item.name}'`);
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
this.agents.set(item.name, item);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Handle incoming execution requests from the platform.
|
|
461
|
+
* This is a SYNC callback (returns void) — napi-rs ThreadsafeFunction cannot
|
|
462
|
+
* properly handle async (Promise) return values. Instead, async processing
|
|
463
|
+
* starts here and calls nativeWorker.resolveResponse() when done, which sends
|
|
464
|
+
* the result back through a Rust oneshot channel.
|
|
465
|
+
*/
|
|
466
|
+
handleMessage(message) {
|
|
467
|
+
this.processMessage(message).then((responseJson) => {
|
|
468
|
+
this.nativeWorker.resolveResponse(message.invocationId, responseJson);
|
|
469
|
+
}, (error) => {
|
|
470
|
+
this.nativeWorker.resolveResponse(message.invocationId, JSON.stringify({
|
|
471
|
+
invocationId: message.invocationId,
|
|
472
|
+
error: error.message || 'Unknown error',
|
|
473
|
+
}));
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Async message processing — dispatches to the appropriate component handler.
|
|
478
|
+
* Wraps each dispatch with lifecycle events matching the Python SDK executor pattern:
|
|
479
|
+
* run.started → component.started → handler → component.completed/failed → run.completed/failed
|
|
480
|
+
*/
|
|
481
|
+
async processMessage(message) {
|
|
482
|
+
const runId = message.metadata?.run_id || message.invocationId;
|
|
483
|
+
return runWithContext({
|
|
484
|
+
runId,
|
|
485
|
+
sessionId: message.metadata?.session_id,
|
|
486
|
+
userId: message.metadata?.user_id,
|
|
487
|
+
correlationId: message.metadata?.correlation_id || message.invocationId,
|
|
488
|
+
tenantId: message.metadata?.tenant_id,
|
|
489
|
+
}, async () => {
|
|
490
|
+
// Create EventEmitter wired to NAPI worker for event emission
|
|
491
|
+
const emitter = new EventEmitter(runId, {
|
|
492
|
+
traceparent: message.metadata?.traceparent || '',
|
|
493
|
+
tracestate: message.metadata?.tracestate || '',
|
|
494
|
+
});
|
|
495
|
+
emitter.setWorker(this.nativeWorker);
|
|
496
|
+
// Correlation IDs: run CID from run_id[:8], component CID random
|
|
497
|
+
const runCid = runId.slice(0, 8);
|
|
498
|
+
const parentCid = message.metadata?.parent_correlation_id || null;
|
|
499
|
+
const attempt = parseInt(message.metadata?.attempt || '0', 10);
|
|
500
|
+
const maxAttempts = parseInt(message.metadata?.max_attempts || '1', 10);
|
|
501
|
+
try {
|
|
502
|
+
console.log(`📨 Received ${message.componentType} execution: ${message.componentName}`);
|
|
503
|
+
// Parse input data
|
|
504
|
+
const inputData = JSON.parse(message.inputJson);
|
|
505
|
+
// Create context with emitter
|
|
506
|
+
const ctx = new SimpleContext(message.invocationId, runId, attempt, this.serviceName);
|
|
507
|
+
ctx.setEmitter(emitter);
|
|
508
|
+
if (this.nativeWorker) {
|
|
509
|
+
ctx.setNativeWorker(this.nativeWorker);
|
|
510
|
+
}
|
|
511
|
+
// Seed HITL replay state from incoming resume metadata (no-op on
|
|
512
|
+
// fresh dispatches). Mirrors sdk-python's executors which read
|
|
513
|
+
// user_response/pause_index from request.metadata on resume.
|
|
514
|
+
ctx.loadReplayState(message.metadata);
|
|
515
|
+
// ── run.started ──
|
|
516
|
+
await emitter.emit(runStarted(runCid, parentCid, {
|
|
517
|
+
inputData,
|
|
518
|
+
attempt,
|
|
519
|
+
}));
|
|
520
|
+
let result;
|
|
521
|
+
const startTimeNs = BigInt(Date.now()) * 1000000n;
|
|
522
|
+
switch (message.componentType) {
|
|
523
|
+
case 'function': {
|
|
524
|
+
const fn = FunctionRegistry.get(message.componentName);
|
|
525
|
+
if (!fn) {
|
|
526
|
+
throw new Error(`Function not found: ${message.componentName}`);
|
|
527
|
+
}
|
|
528
|
+
const fnCid = generateCid();
|
|
529
|
+
// ── function.started ──
|
|
530
|
+
await emitter.emit(functionStarted(fnCid, runCid, {
|
|
531
|
+
inputData,
|
|
532
|
+
attempt,
|
|
533
|
+
}));
|
|
534
|
+
try {
|
|
535
|
+
result = await fn.handler(ctx, inputData);
|
|
536
|
+
const durationMs = Number((BigInt(Date.now()) * 1000000n - startTimeNs) / 1000000n);
|
|
537
|
+
// ── function.completed ──
|
|
538
|
+
await emitter.emit(functionCompleted(fnCid, runCid, {
|
|
539
|
+
outputData: result,
|
|
540
|
+
durationMs,
|
|
541
|
+
}));
|
|
542
|
+
}
|
|
543
|
+
catch (fnError) {
|
|
544
|
+
const durationMs = Number((BigInt(Date.now()) * 1000000n - startTimeNs) / 1000000n);
|
|
545
|
+
// ── function.failed ──
|
|
546
|
+
await emitter.emit(functionFailed(fnCid, runCid, {
|
|
547
|
+
errorCode: 'FUNCTION_ERROR',
|
|
548
|
+
errorMessage: fnError.message,
|
|
549
|
+
durationMs,
|
|
550
|
+
}));
|
|
551
|
+
throw fnError;
|
|
552
|
+
}
|
|
553
|
+
break;
|
|
554
|
+
}
|
|
555
|
+
case 'workflow': {
|
|
556
|
+
const wf = WorkflowRegistry.get(message.componentName);
|
|
557
|
+
if (!wf) {
|
|
558
|
+
throw new Error(`Workflow not found: ${message.componentName}`);
|
|
559
|
+
}
|
|
560
|
+
const wfCid = generateCid();
|
|
561
|
+
// Expose workflow + run correlation IDs so ctx.waitForUser can
|
|
562
|
+
// emit a properly-parented workflow.paused event.
|
|
563
|
+
ctx.setWorkflowCorrelation(wfCid, runCid);
|
|
564
|
+
// ── workflow.started ──
|
|
565
|
+
await emitter.emit(workflowStarted(wfCid, runCid, {
|
|
566
|
+
inputData,
|
|
567
|
+
attempt,
|
|
568
|
+
}));
|
|
569
|
+
try {
|
|
570
|
+
result = await wf.handler(ctx, inputData);
|
|
571
|
+
const durationMs = Number((BigInt(Date.now()) * 1000000n - startTimeNs) / 1000000n);
|
|
572
|
+
// ── workflow.completed ──
|
|
573
|
+
await emitter.emit(workflowCompleted(wfCid, runCid, {
|
|
574
|
+
outputData: result,
|
|
575
|
+
durationMs,
|
|
576
|
+
}));
|
|
577
|
+
}
|
|
578
|
+
catch (wfError) {
|
|
579
|
+
// HITL: waitForUser already emitted workflow.paused — don't
|
|
580
|
+
// emit workflow.failed. Let the outer catch short-circuit
|
|
581
|
+
// the run.failed emission and return a success response to
|
|
582
|
+
// the coordinator (the run is in the paused status already).
|
|
583
|
+
if (wfError instanceof WaitingForUserInputError) {
|
|
584
|
+
throw wfError;
|
|
585
|
+
}
|
|
586
|
+
const durationMs = Number((BigInt(Date.now()) * 1000000n - startTimeNs) / 1000000n);
|
|
587
|
+
// ── workflow.failed ──
|
|
588
|
+
await emitter.emit(workflowFailed(wfCid, runCid, {
|
|
589
|
+
errorCode: 'WORKFLOW_ERROR',
|
|
590
|
+
errorMessage: wfError.message,
|
|
591
|
+
durationMs,
|
|
592
|
+
}));
|
|
593
|
+
throw wfError;
|
|
594
|
+
}
|
|
595
|
+
break;
|
|
596
|
+
}
|
|
597
|
+
case 'agent': {
|
|
598
|
+
// Check if this is a chat webhook dispatch
|
|
599
|
+
if (inputData._chat_webhook && this.chatbots.has(message.componentName)) {
|
|
600
|
+
const chatbot = this.chatbots.get(message.componentName);
|
|
601
|
+
const platform = inputData.platform;
|
|
602
|
+
const headers = (inputData.headers || {});
|
|
603
|
+
const body = Buffer.from(inputData.body || '', 'utf-8');
|
|
604
|
+
console.log(`💬 Chat webhook received: platform=${platform}, bot=${message.componentName}`);
|
|
605
|
+
const challengeResult = await chatbot.handleWebhook(platform, headers, body);
|
|
606
|
+
result = challengeResult ?? {};
|
|
607
|
+
break;
|
|
608
|
+
}
|
|
609
|
+
const agent = this.agents.get(message.componentName);
|
|
610
|
+
if (!agent) {
|
|
611
|
+
throw new Error(`Agent not found: ${message.componentName}`);
|
|
612
|
+
}
|
|
613
|
+
// Session history: load prior conversation from entity storage
|
|
614
|
+
const sessionId = inputData.session_id || message.metadata?.session_id || runId;
|
|
615
|
+
const history = await this._loadSessionHistory(sessionId, message.componentName, message.metadata);
|
|
616
|
+
// Consume the agent stream so internal events (agent.started,
|
|
617
|
+
// iteration.started, tool_call.started, etc.) are forwarded to the platform.
|
|
618
|
+
const userMessage = inputData.prompt || inputData.message || JSON.stringify(inputData);
|
|
619
|
+
let agentResult;
|
|
620
|
+
for await (const event of agent.stream(userMessage, ctx, history)) {
|
|
621
|
+
if ('output' in event && 'toolCalls' in event && 'context' in event) {
|
|
622
|
+
agentResult = event;
|
|
623
|
+
}
|
|
624
|
+
else {
|
|
625
|
+
// Forward AgentEvent to platform via emitter
|
|
626
|
+
await emitter.emit(event);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
if (!agentResult) {
|
|
630
|
+
throw new Error(`Agent '${message.componentName}' completed without producing a result`);
|
|
631
|
+
}
|
|
632
|
+
result = agentResult.output;
|
|
633
|
+
// Session history: save updated conversation to entity storage
|
|
634
|
+
const updatedMessages = [
|
|
635
|
+
...history,
|
|
636
|
+
Message.user(userMessage),
|
|
637
|
+
Message.assistant(typeof result === 'string' ? result : JSON.stringify(result)),
|
|
638
|
+
];
|
|
639
|
+
await this._saveSessionHistory(sessionId, message.componentName, updatedMessages, message.metadata);
|
|
640
|
+
// Emit session.created so GET /v1/sessions/{id} works
|
|
641
|
+
await emitter.emit({
|
|
642
|
+
name: 'session.created',
|
|
643
|
+
eventType: 'session.created',
|
|
644
|
+
eventId: generateCid(),
|
|
645
|
+
correlationId: generateCid(),
|
|
646
|
+
parentCorrelationId: null,
|
|
647
|
+
timestampNs: BigInt(Date.now()) * 1000000n,
|
|
648
|
+
metadata: {
|
|
649
|
+
session_id: sessionId,
|
|
650
|
+
component_name: message.componentName,
|
|
651
|
+
session_type: 'agent',
|
|
652
|
+
},
|
|
653
|
+
});
|
|
654
|
+
break;
|
|
655
|
+
}
|
|
656
|
+
case 'tool': {
|
|
657
|
+
const tool = ToolRegistry.get(message.componentName);
|
|
658
|
+
if (!tool) {
|
|
659
|
+
throw new Error(`Tool not found: ${message.componentName}`);
|
|
660
|
+
}
|
|
661
|
+
const toolCid = generateCid();
|
|
662
|
+
// ── tool.started ──
|
|
663
|
+
await emitter.emit(toolStarted(toolCid, runCid, {
|
|
664
|
+
inputData,
|
|
665
|
+
attempt,
|
|
666
|
+
}));
|
|
667
|
+
try {
|
|
668
|
+
result = await tool.invoke(ctx, inputData);
|
|
669
|
+
const durationMs = Number((BigInt(Date.now()) * 1000000n - startTimeNs) / 1000000n);
|
|
670
|
+
// ── tool.completed ──
|
|
671
|
+
await emitter.emit(toolCompleted(toolCid, runCid, {
|
|
672
|
+
outputData: result,
|
|
673
|
+
durationMs,
|
|
674
|
+
}));
|
|
675
|
+
}
|
|
676
|
+
catch (toolError) {
|
|
677
|
+
const durationMs = Number((BigInt(Date.now()) * 1000000n - startTimeNs) / 1000000n);
|
|
678
|
+
// ── tool.failed ──
|
|
679
|
+
await emitter.emit(toolFailed(toolCid, runCid, {
|
|
680
|
+
errorCode: 'TOOL_ERROR',
|
|
681
|
+
errorMessage: toolError.message,
|
|
682
|
+
durationMs,
|
|
683
|
+
}));
|
|
684
|
+
throw toolError;
|
|
685
|
+
}
|
|
686
|
+
break;
|
|
687
|
+
}
|
|
688
|
+
case 'scorer': {
|
|
689
|
+
const scorerConfig = ScorerRegistry.get(message.componentName);
|
|
690
|
+
if (!scorerConfig) {
|
|
691
|
+
throw new Error(`Scorer not found: ${message.componentName}`);
|
|
692
|
+
}
|
|
693
|
+
const scorerRequest = {
|
|
694
|
+
output: inputData.output,
|
|
695
|
+
expected: inputData.expected,
|
|
696
|
+
input: inputData.input,
|
|
697
|
+
config: inputData.config,
|
|
698
|
+
};
|
|
699
|
+
const scorerCtx = {
|
|
700
|
+
runId,
|
|
701
|
+
correlationId: runCid,
|
|
702
|
+
attempt,
|
|
703
|
+
log: (msg, extra) => {
|
|
704
|
+
console.log(`[SCORER] ${msg}`, extra || {});
|
|
705
|
+
},
|
|
706
|
+
};
|
|
707
|
+
const scorerResult = await scorerConfig.handler(scorerCtx, scorerRequest);
|
|
708
|
+
result = {
|
|
709
|
+
score: scorerResult.score,
|
|
710
|
+
passed: scorerResult.passed,
|
|
711
|
+
explanation: scorerResult.explanation,
|
|
712
|
+
label: scorerResult.label,
|
|
713
|
+
metadata: scorerResult.metadata,
|
|
714
|
+
};
|
|
715
|
+
break;
|
|
716
|
+
}
|
|
717
|
+
default:
|
|
718
|
+
throw new Error(`Unknown component type: ${message.componentType}`);
|
|
719
|
+
}
|
|
720
|
+
// ── run.completed ──
|
|
721
|
+
await emitter.emit(runCompleted(runCid, parentCid, {
|
|
722
|
+
outputData: result,
|
|
723
|
+
}));
|
|
724
|
+
return JSON.stringify({
|
|
725
|
+
invocationId: message.invocationId,
|
|
726
|
+
outputJson: JSON.stringify(result),
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
catch (error) {
|
|
730
|
+
// HITL: workflow.paused has already been journaled by waitForUser
|
|
731
|
+
// and the run is in the `paused` status. Return a success response
|
|
732
|
+
// (no `error` field) so the native layer emits a dispatch response
|
|
733
|
+
// with success=true — this lets the coordinator drain the lease
|
|
734
|
+
// and decrement worker load without writing any additional run
|
|
735
|
+
// lifecycle events. Do NOT emit run.completed: the run is paused,
|
|
736
|
+
// not completed.
|
|
737
|
+
if (error instanceof WaitingForUserInputError) {
|
|
738
|
+
return JSON.stringify({
|
|
739
|
+
invocationId: message.invocationId,
|
|
740
|
+
outputJson: JSON.stringify({
|
|
741
|
+
_paused: true,
|
|
742
|
+
question: error.question,
|
|
743
|
+
pauseIndex: error.pauseIndex,
|
|
744
|
+
}),
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
// ── run.failed ──
|
|
748
|
+
try {
|
|
749
|
+
await emitter.emit(runFailed(runCid, parentCid, {
|
|
750
|
+
errorCode: 'EXECUTION_ERROR',
|
|
751
|
+
errorMessage: error.message,
|
|
752
|
+
attempt,
|
|
753
|
+
maxAttempts,
|
|
754
|
+
}));
|
|
755
|
+
}
|
|
756
|
+
catch (emitError) {
|
|
757
|
+
console.error('Failed to emit run.failed event:', emitError);
|
|
758
|
+
}
|
|
759
|
+
console.error(`❌ Execution failed:`, error);
|
|
760
|
+
return JSON.stringify({
|
|
761
|
+
invocationId: message.invocationId,
|
|
762
|
+
error: error.message,
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Load conversation history from entity storage (mirrors Python AgentContext).
|
|
769
|
+
*/
|
|
770
|
+
async _loadSessionHistory(sessionId, agentName, metadata) {
|
|
771
|
+
const gatewayUrl = process.env.AGNT5_GATEWAY_URL || 'http://localhost:34181';
|
|
772
|
+
const entityKey = `agent_session_${sessionId}`;
|
|
773
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
774
|
+
if (metadata?.tenant_id)
|
|
775
|
+
headers['X-Tenant-ID'] = metadata.tenant_id;
|
|
776
|
+
if (metadata?.deployment_id)
|
|
777
|
+
headers['X-Deployment-ID'] = metadata.deployment_id;
|
|
778
|
+
try {
|
|
779
|
+
const resp = await fetch(`${gatewayUrl}/v1/entity/AgentSession/${entityKey}/get`, {
|
|
780
|
+
method: 'POST',
|
|
781
|
+
headers,
|
|
782
|
+
body: JSON.stringify({ scope: 'session', scope_id: sessionId }),
|
|
783
|
+
});
|
|
784
|
+
if (resp.status === 404)
|
|
785
|
+
return [];
|
|
786
|
+
if (!resp.ok)
|
|
787
|
+
return [];
|
|
788
|
+
const data = (await resp.json());
|
|
789
|
+
const messages = data?.state?.messages || [];
|
|
790
|
+
return messages.map((m) => ({ role: m.role || 'user', content: m.content || '' }));
|
|
791
|
+
}
|
|
792
|
+
catch {
|
|
793
|
+
return [];
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Save conversation history to entity storage (mirrors Python AgentContext).
|
|
798
|
+
*/
|
|
799
|
+
async _saveSessionHistory(sessionId, agentName, messages, metadata) {
|
|
800
|
+
const gatewayUrl = process.env.AGNT5_GATEWAY_URL || 'http://localhost:34181';
|
|
801
|
+
const entityKey = `agent_session_${sessionId}`;
|
|
802
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
803
|
+
if (metadata?.tenant_id)
|
|
804
|
+
headers['X-Tenant-ID'] = metadata.tenant_id;
|
|
805
|
+
if (metadata?.deployment_id)
|
|
806
|
+
headers['X-Deployment-ID'] = metadata.deployment_id;
|
|
807
|
+
const messagesData = messages.map((m) => ({
|
|
808
|
+
role: m.role,
|
|
809
|
+
content: m.content,
|
|
810
|
+
timestamp: Date.now() / 1000,
|
|
811
|
+
}));
|
|
812
|
+
try {
|
|
813
|
+
await fetch(`${gatewayUrl}/v1/entity/AgentSession/${entityKey}/set`, {
|
|
814
|
+
method: 'POST',
|
|
815
|
+
headers,
|
|
816
|
+
body: JSON.stringify({
|
|
817
|
+
input: {
|
|
818
|
+
agent_name: agentName,
|
|
819
|
+
session_id: sessionId,
|
|
820
|
+
messages: messagesData,
|
|
821
|
+
updated_at: Date.now() / 1000,
|
|
822
|
+
turn_count: messagesData.length,
|
|
823
|
+
},
|
|
824
|
+
scope: 'session',
|
|
825
|
+
scope_id: sessionId,
|
|
826
|
+
}),
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
catch {
|
|
830
|
+
// Best-effort — don't fail the dispatch on save errors
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Start the worker and connect to platform
|
|
835
|
+
*/
|
|
836
|
+
async run() {
|
|
837
|
+
await this.initialize();
|
|
838
|
+
console.log(`
|
|
839
|
+
🚀 AGNT5 Worker Starting
|
|
840
|
+
Service: ${this.serviceName}
|
|
841
|
+
Worker ID: ${this.workerId}
|
|
842
|
+
Coordinator: ${this.coordinatorEndpoint}
|
|
843
|
+
Tenant: ${this.tenantId}
|
|
844
|
+
Deployment: ${this.deploymentId}
|
|
845
|
+
Runtime: ${getRuntime()}
|
|
846
|
+
`);
|
|
847
|
+
// Collect all registered components
|
|
848
|
+
const components = [];
|
|
849
|
+
// Functions
|
|
850
|
+
for (const [name, fnConfig] of FunctionRegistry.getAll()) {
|
|
851
|
+
const config = {};
|
|
852
|
+
if (fnConfig.options.retries?.maxAttempts !== undefined) {
|
|
853
|
+
config.max_attempts = String(fnConfig.options.retries.maxAttempts);
|
|
854
|
+
}
|
|
855
|
+
if (fnConfig.options.retries?.initialIntervalMs !== undefined) {
|
|
856
|
+
config.initial_interval_ms = String(fnConfig.options.retries.initialIntervalMs);
|
|
857
|
+
}
|
|
858
|
+
if (fnConfig.options.retries?.maxIntervalMs !== undefined) {
|
|
859
|
+
config.max_interval_ms = String(fnConfig.options.retries.maxIntervalMs);
|
|
860
|
+
}
|
|
861
|
+
if (fnConfig.options.backoff?.type) {
|
|
862
|
+
config.backoff_type = fnConfig.options.backoff.type;
|
|
863
|
+
}
|
|
864
|
+
if (fnConfig.options.backoff?.multiplier !== undefined) {
|
|
865
|
+
config.backoff_multiplier = String(fnConfig.options.backoff.multiplier);
|
|
866
|
+
}
|
|
867
|
+
if (fnConfig.options.timeout_ms !== undefined) {
|
|
868
|
+
config.timeout_ms = String(fnConfig.options.timeout_ms);
|
|
869
|
+
}
|
|
870
|
+
components.push({ name, componentType: 'function', config, metadata: {} });
|
|
871
|
+
}
|
|
872
|
+
// Workflows (auto-discover from registry)
|
|
873
|
+
for (const [name, cfg] of WorkflowRegistry.all()) {
|
|
874
|
+
const metadata = {};
|
|
875
|
+
if (cfg.cron)
|
|
876
|
+
metadata.cron = cfg.cron;
|
|
877
|
+
components.push({ name, componentType: 'workflow', config: {}, metadata });
|
|
878
|
+
}
|
|
879
|
+
// Tools (auto-discover from registry)
|
|
880
|
+
for (const [name] of ToolRegistry.all()) {
|
|
881
|
+
components.push({ name, componentType: 'tool', config: {}, metadata: {} });
|
|
882
|
+
}
|
|
883
|
+
// Agents (explicitly registered via registerAgents)
|
|
884
|
+
for (const [name] of this.agents) {
|
|
885
|
+
components.push({ name, componentType: 'agent', config: {}, metadata: {} });
|
|
886
|
+
}
|
|
887
|
+
// Scorers (custom only — built-ins are handled by Rust fast path)
|
|
888
|
+
for (const [name, cfg] of ScorerRegistry.all()) {
|
|
889
|
+
if (isScorer(cfg.handler)) {
|
|
890
|
+
components.push({ name, componentType: 'scorer', config: {}, metadata: {} });
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
const counts = {
|
|
894
|
+
function: components.filter(c => c.componentType === 'function').length,
|
|
895
|
+
workflow: components.filter(c => c.componentType === 'workflow').length,
|
|
896
|
+
tool: components.filter(c => c.componentType === 'tool').length,
|
|
897
|
+
agent: components.filter(c => c.componentType === 'agent').length,
|
|
898
|
+
scorer: components.filter(c => c.componentType === 'scorer').length,
|
|
899
|
+
};
|
|
900
|
+
const summary = Object.entries(counts).filter(([, n]) => n > 0).map(([t, n]) => `${n} ${t}(s)`).join(', ');
|
|
901
|
+
console.log(`📦 Registered components: ${summary || 'none'}`);
|
|
902
|
+
await this.nativeWorker.setComponents(components);
|
|
903
|
+
// Set message handler
|
|
904
|
+
this.nativeWorker.setMessageHandler(this.handleMessage.bind(this));
|
|
905
|
+
console.log('✓ Message handler configured');
|
|
906
|
+
console.log('🔗 Connecting to platform...\n');
|
|
907
|
+
// Run the worker (this will block until shutdown)
|
|
908
|
+
await this.nativeWorker.run();
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* Detect current JavaScript runtime
|
|
913
|
+
*/
|
|
914
|
+
export function getRuntime() {
|
|
915
|
+
// @ts-ignore
|
|
916
|
+
if (typeof process !== 'undefined' && process.versions?.node) {
|
|
917
|
+
// @ts-ignore
|
|
918
|
+
if (typeof Bun !== 'undefined')
|
|
919
|
+
return 'bun';
|
|
920
|
+
// @ts-ignore
|
|
921
|
+
if (typeof Deno !== 'undefined')
|
|
922
|
+
return 'deno';
|
|
923
|
+
return 'node';
|
|
924
|
+
}
|
|
925
|
+
// @ts-ignore
|
|
926
|
+
if (typeof EdgeRuntime !== 'undefined')
|
|
927
|
+
return 'edge';
|
|
928
|
+
return 'unknown';
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Check platform connectivity
|
|
932
|
+
*/
|
|
933
|
+
export async function checkPlatformConnectivity(coordinatorUrl) {
|
|
934
|
+
try {
|
|
935
|
+
const native = loadNativeBindings();
|
|
936
|
+
const url = coordinatorUrl || process.env.AGNT5_COORDINATOR_ENDPOINT || 'http://localhost:34186';
|
|
937
|
+
return await native.checkPlatformConnectivity(url);
|
|
938
|
+
}
|
|
939
|
+
catch (error) {
|
|
940
|
+
console.error('Platform connectivity check failed:', error);
|
|
941
|
+
return false;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
//# sourceMappingURL=worker.js.map
|