@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/client.js
ADDED
|
@@ -0,0 +1,757 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AGNT5 Client SDK for invoking components
|
|
3
|
+
*/
|
|
4
|
+
import { RunError, TimeoutError, ValidationError, createErrorFromResponse, } from './errors.js';
|
|
5
|
+
import { BatchResult, BatchStatusResult } from './batch.js';
|
|
6
|
+
import { EvalResponse, BatchEvalResult, BatchEvalItemResult, normalizeBatchEvalItems, normalizeScorerSpecs } from './eval.js';
|
|
7
|
+
/**
|
|
8
|
+
* Typed response from run(), getResult(), waitForResult().
|
|
9
|
+
*
|
|
10
|
+
* Follows httpx-style patterns with isSuccess, isPending, isError, raiseForStatus().
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const response = await client.run('greet', { name: 'Alice' });
|
|
15
|
+
* if (response.isSuccess) {
|
|
16
|
+
* console.log(response.output);
|
|
17
|
+
* } else {
|
|
18
|
+
* response.raiseForStatus();
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export class RunResponse {
|
|
23
|
+
constructor(raw) {
|
|
24
|
+
this.runId = raw.run_id || raw.runId || '';
|
|
25
|
+
this.statusCode = raw.status_code ?? (raw.status === 'completed' ? 200 : raw.status === 'failed' ? 500 : 202);
|
|
26
|
+
this.status = raw.status || 'unknown';
|
|
27
|
+
const nestedOutput = raw.result?.output;
|
|
28
|
+
this.output = (raw.output ??
|
|
29
|
+
raw.output_data ??
|
|
30
|
+
nestedOutput?.output_data ??
|
|
31
|
+
nestedOutput);
|
|
32
|
+
this.durationMs = raw.duration_ms;
|
|
33
|
+
this.traceId = raw.trace_id;
|
|
34
|
+
this.component = raw.component;
|
|
35
|
+
this.createdAt = raw.created_at;
|
|
36
|
+
this.startedAt = raw.started_at;
|
|
37
|
+
this.completedAt = raw.completed_at;
|
|
38
|
+
this.failedAt = raw.failed_at;
|
|
39
|
+
this.sessionId = raw.session_id;
|
|
40
|
+
this.metadata = raw.metadata;
|
|
41
|
+
// Parse error — could be a string or a structured object
|
|
42
|
+
if (raw.error || raw.error_message) {
|
|
43
|
+
if (typeof raw.error === 'string') {
|
|
44
|
+
this.error = { code: raw.error_code || 'EXECUTION_FAILED', message: raw.error };
|
|
45
|
+
}
|
|
46
|
+
else if (typeof raw.error === 'object') {
|
|
47
|
+
this.error = {
|
|
48
|
+
code: raw.error.code || raw.error_code || 'EXECUTION_FAILED',
|
|
49
|
+
message: raw.error.message || String(raw.error),
|
|
50
|
+
details: raw.error.details,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
else if (raw.error_message) {
|
|
54
|
+
this.error = { code: raw.error_code || 'EXECUTION_FAILED', message: raw.error_message };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** True if the run completed successfully */
|
|
59
|
+
get isSuccess() {
|
|
60
|
+
return this.status === 'completed' && !this.error;
|
|
61
|
+
}
|
|
62
|
+
/** True if the run is still in progress */
|
|
63
|
+
get isPending() {
|
|
64
|
+
return ['enqueued', 'started', 'running', 'paused', 'awaiting_input'].includes(this.status);
|
|
65
|
+
}
|
|
66
|
+
/** True if the run failed, was cancelled, or timed out */
|
|
67
|
+
get isError() {
|
|
68
|
+
return ['failed', 'cancelled', 'timeout'].includes(this.status) || this.statusCode === 500;
|
|
69
|
+
}
|
|
70
|
+
/** Execution duration as milliseconds (undefined if not available) */
|
|
71
|
+
get elapsed() {
|
|
72
|
+
return this.durationMs;
|
|
73
|
+
}
|
|
74
|
+
/** Throw RunError if the run failed */
|
|
75
|
+
raiseForStatus() {
|
|
76
|
+
if (this.isError) {
|
|
77
|
+
if (this.error) {
|
|
78
|
+
throw new RunError(this.error.message, this.runId, this.status, this.error.code);
|
|
79
|
+
}
|
|
80
|
+
throw new RunError(`Run failed with status: ${this.status}`, this.runId, this.status);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Entity proxy
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
/**
|
|
88
|
+
* Proxy for calling methods on durable entities
|
|
89
|
+
*/
|
|
90
|
+
export class EntityProxy {
|
|
91
|
+
constructor(client, entityType, key) {
|
|
92
|
+
this.client = client;
|
|
93
|
+
this.entityType = entityType;
|
|
94
|
+
this.key = key;
|
|
95
|
+
}
|
|
96
|
+
async call(method, args = {}) {
|
|
97
|
+
const url = `${this.client['gatewayUrl']}/v1/entity/${this.entityType}/${this.key}/${method}`;
|
|
98
|
+
const response = await fetch(url, {
|
|
99
|
+
method: 'POST',
|
|
100
|
+
headers: this.client['buildHeaders'](),
|
|
101
|
+
body: JSON.stringify(args),
|
|
102
|
+
signal: AbortSignal.timeout(this.client['timeout']),
|
|
103
|
+
});
|
|
104
|
+
if (!response.ok) {
|
|
105
|
+
const errorData = (await response.json().catch(() => ({})));
|
|
106
|
+
throw new RunError(errorData.error || `HTTP ${response.status}: Entity method call failed`, errorData.runId);
|
|
107
|
+
}
|
|
108
|
+
const data = (await response.json());
|
|
109
|
+
if (data.status === 'failed') {
|
|
110
|
+
const errMsg = typeof data.error === 'string' ? data.error : data.error?.message || 'Unknown error';
|
|
111
|
+
throw new RunError(errMsg, data.run_id || data.runId);
|
|
112
|
+
}
|
|
113
|
+
return data.output;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
// Client
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
/**
|
|
120
|
+
* Client for invoking AGNT5 components
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```typescript
|
|
124
|
+
* import { Client } from '@agnt5/sdk';
|
|
125
|
+
*
|
|
126
|
+
* // Local development
|
|
127
|
+
* const client = new Client({ gatewayUrl: 'http://localhost:34181' });
|
|
128
|
+
*
|
|
129
|
+
* // Production with API key
|
|
130
|
+
* const client = new Client({
|
|
131
|
+
* gatewayUrl: 'https://api.agnt5.com',
|
|
132
|
+
* apiKey: 'agnt5_sk_...',
|
|
133
|
+
* });
|
|
134
|
+
*
|
|
135
|
+
* // Synchronous execution with typed response
|
|
136
|
+
* const response = await client.run('greet', { name: 'Alice' });
|
|
137
|
+
* if (response.isSuccess) {
|
|
138
|
+
* console.log(response.output);
|
|
139
|
+
* }
|
|
140
|
+
*
|
|
141
|
+
* // Async execution
|
|
142
|
+
* const submit = await client.submit('long_task', { data: '...' });
|
|
143
|
+
* const result = await client.waitForResult(submit.runId);
|
|
144
|
+
*
|
|
145
|
+
* // Stream typed events
|
|
146
|
+
* for await (const event of client.events(runId)) {
|
|
147
|
+
* console.log(event.eventType, event.data);
|
|
148
|
+
* }
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
export class Client {
|
|
152
|
+
constructor(options = {}) {
|
|
153
|
+
const env = typeof process !== 'undefined' ? process.env : undefined;
|
|
154
|
+
this.gatewayUrl = (options.gatewayUrl || env?.AGNT5_GATEWAY_URL || 'http://localhost:34181').replace(/\/$/, '');
|
|
155
|
+
this.apiKey = options.apiKey || env?.AGNT5_API_KEY;
|
|
156
|
+
this.tenantId = options.tenantId || env?.AGNT5_PROJECT_ID || env?.AGNT5_TENANT_ID;
|
|
157
|
+
this.deploymentId = options.deploymentId || env?.AGNT5_DEPLOYMENT_ID;
|
|
158
|
+
this.timeout = options.timeout || 30000;
|
|
159
|
+
this.maxRetries = options.maxRetries ?? 3;
|
|
160
|
+
this.retryDelayMs = options.retryDelayMs || 1000;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Build request headers with authentication and routing. `tenantOverride`
|
|
164
|
+
* wins over the client-level `tenantId` when set.
|
|
165
|
+
*/
|
|
166
|
+
buildHeaders(extra, tenantOverride) {
|
|
167
|
+
const headers = {
|
|
168
|
+
'Content-Type': 'application/json',
|
|
169
|
+
};
|
|
170
|
+
if (this.apiKey) {
|
|
171
|
+
headers['X-API-KEY'] = this.apiKey;
|
|
172
|
+
}
|
|
173
|
+
const effectiveTenant = tenantOverride ?? this.tenantId;
|
|
174
|
+
if (effectiveTenant) {
|
|
175
|
+
headers['X-Tenant-ID'] = effectiveTenant;
|
|
176
|
+
}
|
|
177
|
+
if (this.deploymentId) {
|
|
178
|
+
headers['X-Deployment-ID'] = this.deploymentId;
|
|
179
|
+
}
|
|
180
|
+
if (extra) {
|
|
181
|
+
Object.assign(headers, extra);
|
|
182
|
+
}
|
|
183
|
+
return headers;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Retry failed requests with exponential backoff
|
|
187
|
+
*/
|
|
188
|
+
async withRetry(operation, maxRetries) {
|
|
189
|
+
const retries = maxRetries ?? this.maxRetries;
|
|
190
|
+
let lastError;
|
|
191
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
192
|
+
try {
|
|
193
|
+
return await operation();
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
lastError = error;
|
|
197
|
+
// Don't retry on these errors
|
|
198
|
+
if (error instanceof ValidationError ||
|
|
199
|
+
error instanceof RunError ||
|
|
200
|
+
error instanceof TimeoutError) {
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
if (attempt === retries)
|
|
204
|
+
break;
|
|
205
|
+
const delay = this.retryDelayMs * Math.pow(2, attempt);
|
|
206
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
throw lastError;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Execute a component synchronously and wait for the result.
|
|
213
|
+
*
|
|
214
|
+
* Returns a typed RunResponse with metadata (traceId, durationMs, status).
|
|
215
|
+
*/
|
|
216
|
+
async run(component, inputData = {}, options = {}) {
|
|
217
|
+
return this.withRetry(async () => {
|
|
218
|
+
const componentType = options.componentType || 'function';
|
|
219
|
+
const url = `${this.gatewayUrl}/v1/${componentType}s/${component}/run`;
|
|
220
|
+
const extra = {};
|
|
221
|
+
if (options.sessionId) {
|
|
222
|
+
extra['X-Session-ID'] = options.sessionId;
|
|
223
|
+
}
|
|
224
|
+
if (options.userId) {
|
|
225
|
+
extra['X-User-ID'] = options.userId;
|
|
226
|
+
}
|
|
227
|
+
const response = await fetch(url, {
|
|
228
|
+
method: 'POST',
|
|
229
|
+
headers: this.buildHeaders(extra, options.tenant),
|
|
230
|
+
body: JSON.stringify(inputData),
|
|
231
|
+
signal: AbortSignal.timeout(this.timeout),
|
|
232
|
+
});
|
|
233
|
+
if (!response.ok) {
|
|
234
|
+
const errorData = (await response.json().catch(() => ({})));
|
|
235
|
+
const message = errorData.error || `Component '${component}' execution failed`;
|
|
236
|
+
throw createErrorFromResponse(response.status, message, errorData.runId || errorData.run_id, url);
|
|
237
|
+
}
|
|
238
|
+
const data = (await response.json());
|
|
239
|
+
return new RunResponse(data);
|
|
240
|
+
}, options.maxRetries);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Submit a component for async execution and return immediately.
|
|
244
|
+
*/
|
|
245
|
+
async submit(component, inputData = {}, options = {}) {
|
|
246
|
+
const componentType = options.componentType || 'function';
|
|
247
|
+
const url = `${this.gatewayUrl}/v1/${componentType}s/${component}/submit`;
|
|
248
|
+
const response = await fetch(url, {
|
|
249
|
+
method: 'POST',
|
|
250
|
+
headers: this.buildHeaders(undefined, options.tenant),
|
|
251
|
+
body: JSON.stringify(inputData),
|
|
252
|
+
signal: AbortSignal.timeout(this.timeout),
|
|
253
|
+
});
|
|
254
|
+
if (!response.ok) {
|
|
255
|
+
throw new Error(`HTTP ${response.status}: Submission failed`);
|
|
256
|
+
}
|
|
257
|
+
const data = (await response.json());
|
|
258
|
+
return {
|
|
259
|
+
runId: data.run_id || data.runId || '',
|
|
260
|
+
status: data.status || 'enqueued',
|
|
261
|
+
traceId: data.trace_id,
|
|
262
|
+
component: data.component,
|
|
263
|
+
createdAt: data.created_at,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Get the current status of a run.
|
|
268
|
+
*/
|
|
269
|
+
async getStatus(runId) {
|
|
270
|
+
const url = `${this.gatewayUrl}/v1/status/${runId}`;
|
|
271
|
+
const response = await fetch(url, {
|
|
272
|
+
method: 'GET',
|
|
273
|
+
headers: this.buildHeaders(),
|
|
274
|
+
signal: AbortSignal.timeout(this.timeout),
|
|
275
|
+
});
|
|
276
|
+
if (!response.ok) {
|
|
277
|
+
throw new Error(`HTTP ${response.status}: Failed to get status`);
|
|
278
|
+
}
|
|
279
|
+
const data = (await response.json());
|
|
280
|
+
return new RunResponse(data);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Get the result of a completed run.
|
|
284
|
+
*/
|
|
285
|
+
async getResult(runId) {
|
|
286
|
+
const url = `${this.gatewayUrl}/v1/result/${runId}`;
|
|
287
|
+
const response = await fetch(url, {
|
|
288
|
+
method: 'GET',
|
|
289
|
+
headers: this.buildHeaders(),
|
|
290
|
+
signal: AbortSignal.timeout(this.timeout),
|
|
291
|
+
});
|
|
292
|
+
if (response.status === 404) {
|
|
293
|
+
const errorData = (await response.json().catch(() => ({})));
|
|
294
|
+
const errorMsg = errorData.error || 'Run not found or not complete';
|
|
295
|
+
const currentStatus = errorData.status || 'unknown';
|
|
296
|
+
throw new RunError(`${errorMsg} (status: ${currentStatus})`, runId);
|
|
297
|
+
}
|
|
298
|
+
if (!response.ok) {
|
|
299
|
+
throw new Error(`HTTP ${response.status}: Failed to get result`);
|
|
300
|
+
}
|
|
301
|
+
const data = (await response.json());
|
|
302
|
+
return new RunResponse(data);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Wait for a run to complete and return the result.
|
|
306
|
+
*/
|
|
307
|
+
async waitForResult(runId, timeoutMs = 300000, pollIntervalMs = 1000) {
|
|
308
|
+
const startTime = Date.now();
|
|
309
|
+
while (true) {
|
|
310
|
+
const elapsed = Date.now() - startTime;
|
|
311
|
+
if (elapsed >= timeoutMs) {
|
|
312
|
+
throw new RunError(`Timeout waiting for run to complete after ${timeoutMs}ms`, runId);
|
|
313
|
+
}
|
|
314
|
+
const status = await this.getStatus(runId);
|
|
315
|
+
if (['completed', 'failed', 'cancelled', 'timeout'].includes(status.status)) {
|
|
316
|
+
return await this.getResult(runId);
|
|
317
|
+
}
|
|
318
|
+
await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Stream text chunks from a component using SSE.
|
|
323
|
+
* For typed events, use events() instead.
|
|
324
|
+
*/
|
|
325
|
+
async *stream(component, inputData = {}, options = {}) {
|
|
326
|
+
for await (const event of this.events(component, inputData, options)) {
|
|
327
|
+
if (event.data.chunk !== undefined) {
|
|
328
|
+
yield event.data.chunk;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Stream typed events from a component using Server-Sent Events.
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* ```typescript
|
|
337
|
+
* for await (const event of client.events('my-workflow', { data: '...' })) {
|
|
338
|
+
* switch (event.eventType) {
|
|
339
|
+
* case 'output.delta':
|
|
340
|
+
* process.stdout.write(event.data.chunk);
|
|
341
|
+
* break;
|
|
342
|
+
* case 'run.completed':
|
|
343
|
+
* console.log('Done:', event.data.output);
|
|
344
|
+
* break;
|
|
345
|
+
* }
|
|
346
|
+
* }
|
|
347
|
+
* ```
|
|
348
|
+
*/
|
|
349
|
+
async *events(component, inputData = {}, options = {}) {
|
|
350
|
+
const componentType = options.componentType || 'function';
|
|
351
|
+
const url = `${this.gatewayUrl}/v1/${componentType}s/${component}/stream`;
|
|
352
|
+
const response = await fetch(url, {
|
|
353
|
+
method: 'POST',
|
|
354
|
+
headers: this.buildHeaders(),
|
|
355
|
+
body: JSON.stringify(inputData),
|
|
356
|
+
signal: AbortSignal.timeout(300000), // 5 minute timeout for streaming
|
|
357
|
+
});
|
|
358
|
+
if (!response.ok) {
|
|
359
|
+
throw new RunError(`HTTP ${response.status}: Streaming request failed`);
|
|
360
|
+
}
|
|
361
|
+
if (!response.body) {
|
|
362
|
+
throw new RunError('No response body for streaming');
|
|
363
|
+
}
|
|
364
|
+
const reader = response.body.getReader();
|
|
365
|
+
const decoder = new TextDecoder();
|
|
366
|
+
let buffer = '';
|
|
367
|
+
let currentEventType = 'message';
|
|
368
|
+
let sequence = 0;
|
|
369
|
+
try {
|
|
370
|
+
while (true) {
|
|
371
|
+
const { done, value } = await reader.read();
|
|
372
|
+
if (done)
|
|
373
|
+
break;
|
|
374
|
+
buffer += decoder.decode(value, { stream: true });
|
|
375
|
+
const lines = buffer.split('\n');
|
|
376
|
+
buffer = lines.pop() || '';
|
|
377
|
+
for (const line of lines) {
|
|
378
|
+
const trimmed = line.trim();
|
|
379
|
+
// Skip comments
|
|
380
|
+
if (trimmed.startsWith(':'))
|
|
381
|
+
continue;
|
|
382
|
+
// Empty line = end of event block
|
|
383
|
+
if (!trimmed) {
|
|
384
|
+
currentEventType = 'message';
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
// Parse "event: <type>"
|
|
388
|
+
if (trimmed.startsWith('event: ') || trimmed.startsWith('event:')) {
|
|
389
|
+
currentEventType = trimmed.substring(trimmed.indexOf(':') + 1).trim();
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
// Parse "data: <json>"
|
|
393
|
+
if (trimmed.startsWith('data: ') || trimmed.startsWith('data:')) {
|
|
394
|
+
const dataStr = trimmed.substring(trimmed.indexOf(':') + 1).trim();
|
|
395
|
+
try {
|
|
396
|
+
const data = JSON.parse(dataStr);
|
|
397
|
+
// Check for stream-end signal
|
|
398
|
+
if (data.done)
|
|
399
|
+
return;
|
|
400
|
+
// Check for error
|
|
401
|
+
if (data.error && currentEventType === 'error') {
|
|
402
|
+
throw new RunError(data.error, data.runId || data.run_id);
|
|
403
|
+
}
|
|
404
|
+
sequence++;
|
|
405
|
+
yield {
|
|
406
|
+
eventType: currentEventType,
|
|
407
|
+
data,
|
|
408
|
+
contentIndex: data.index ?? 0,
|
|
409
|
+
sequence,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
catch (e) {
|
|
413
|
+
if (e instanceof RunError)
|
|
414
|
+
throw e;
|
|
415
|
+
// Skip malformed JSON
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
finally {
|
|
422
|
+
reader.releaseLock();
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Get a proxy for calling methods on a durable entity
|
|
427
|
+
*/
|
|
428
|
+
entity(entityType, key) {
|
|
429
|
+
return new EntityProxy(this, entityType, key);
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Get all journal events for a completed run.
|
|
433
|
+
*
|
|
434
|
+
* @example
|
|
435
|
+
* ```typescript
|
|
436
|
+
* const events = await client.getEvents(runId);
|
|
437
|
+
* for (const event of events.events) {
|
|
438
|
+
* console.log(event.eventType, event.data);
|
|
439
|
+
* }
|
|
440
|
+
* ```
|
|
441
|
+
*/
|
|
442
|
+
async getEvents(runId) {
|
|
443
|
+
const url = `${this.gatewayUrl}/v1/runs/${runId}/events`;
|
|
444
|
+
const response = await fetch(url, {
|
|
445
|
+
method: 'GET',
|
|
446
|
+
headers: this.buildHeaders(),
|
|
447
|
+
});
|
|
448
|
+
if (!response.ok) {
|
|
449
|
+
throw createErrorFromResponse(response.status, await response.text(), runId);
|
|
450
|
+
}
|
|
451
|
+
const data = await response.json();
|
|
452
|
+
const rawItems = Array.isArray(data?.items)
|
|
453
|
+
? data.items
|
|
454
|
+
: Array.isArray(data?.events)
|
|
455
|
+
? data.events
|
|
456
|
+
: Array.isArray(data)
|
|
457
|
+
? data
|
|
458
|
+
: [];
|
|
459
|
+
const events = rawItems.map((e) => ({
|
|
460
|
+
eventType: e.event_type || e.eventType || '',
|
|
461
|
+
data: e.data || e.output_data || {},
|
|
462
|
+
timestamp: e.timestamp || e.created_at,
|
|
463
|
+
sequence: e.sequence ?? 0,
|
|
464
|
+
correlationId: e.correlation_id || e.correlationId,
|
|
465
|
+
}));
|
|
466
|
+
return { events, runId };
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Get a proxy for invoking a workflow with fluent API.
|
|
470
|
+
*
|
|
471
|
+
* @example
|
|
472
|
+
* ```typescript
|
|
473
|
+
* const result = await client.workflow('support_bot').chat('Help me', 'session-123');
|
|
474
|
+
* ```
|
|
475
|
+
*/
|
|
476
|
+
workflow(workflowName) {
|
|
477
|
+
return new WorkflowProxy(this, workflowName);
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Get a proxy for a session entity.
|
|
481
|
+
*
|
|
482
|
+
* @example
|
|
483
|
+
* ```typescript
|
|
484
|
+
* const session = client.session('Conversation', 'user-alice');
|
|
485
|
+
* const response = await session.chat('Hello!');
|
|
486
|
+
* ```
|
|
487
|
+
*/
|
|
488
|
+
session(sessionType, key) {
|
|
489
|
+
return new SessionProxy(this, sessionType, key);
|
|
490
|
+
}
|
|
491
|
+
// ─── Batch operations ───────────────────────────────────────────────
|
|
492
|
+
/**
|
|
493
|
+
* Execute a component in batch with multiple inputs.
|
|
494
|
+
*
|
|
495
|
+
* @example
|
|
496
|
+
* ```typescript
|
|
497
|
+
* const result = await client.batch('greet', [
|
|
498
|
+
* { input: { name: 'Alice' } },
|
|
499
|
+
* { input: { name: 'Bob' } },
|
|
500
|
+
* ], { maxConcurrency: 5 });
|
|
501
|
+
*
|
|
502
|
+
* if (result.isSuccess) {
|
|
503
|
+
* console.log(result.outputs);
|
|
504
|
+
* }
|
|
505
|
+
* ```
|
|
506
|
+
*/
|
|
507
|
+
async batch(component, items, options = {}) {
|
|
508
|
+
const componentType = options.componentType || 'function';
|
|
509
|
+
const url = `${this.gatewayUrl}/v1/${componentType}s/${component}/batch`;
|
|
510
|
+
// Normalize items: plain objects become { input: obj }
|
|
511
|
+
const normalizedItems = items.map((item, i) => {
|
|
512
|
+
if ('input' in item) {
|
|
513
|
+
return { ...item, index: item.index ?? i };
|
|
514
|
+
}
|
|
515
|
+
return { input: item, index: i };
|
|
516
|
+
});
|
|
517
|
+
const body = {
|
|
518
|
+
items: normalizedItems,
|
|
519
|
+
config: {
|
|
520
|
+
max_concurrency: options.maxConcurrency ?? 10,
|
|
521
|
+
continue_on_failure: options.continueOnFailure ?? true,
|
|
522
|
+
...(options.batchTimeoutMs && { batch_timeout_ms: options.batchTimeoutMs }),
|
|
523
|
+
...(options.defaultItemTimeoutMs && { default_item_timeout_ms: options.defaultItemTimeoutMs }),
|
|
524
|
+
},
|
|
525
|
+
};
|
|
526
|
+
if (options.metadata) {
|
|
527
|
+
body.metadata = options.metadata;
|
|
528
|
+
}
|
|
529
|
+
const response = await fetch(url, {
|
|
530
|
+
method: 'POST',
|
|
531
|
+
headers: this.buildHeaders(),
|
|
532
|
+
body: JSON.stringify(body),
|
|
533
|
+
signal: AbortSignal.timeout(options.batchTimeoutMs || 3600000),
|
|
534
|
+
});
|
|
535
|
+
if (!response.ok) {
|
|
536
|
+
const errorData = (await response.json().catch(() => ({})));
|
|
537
|
+
throw createErrorFromResponse(response.status, errorData.error || `Batch execution failed for '${component}'`, undefined, url);
|
|
538
|
+
}
|
|
539
|
+
const data = await response.json();
|
|
540
|
+
return new BatchResult(data);
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Get the status of a batch execution.
|
|
544
|
+
*/
|
|
545
|
+
async getBatchStatus(batchId, includeResults = true) {
|
|
546
|
+
const url = `${this.gatewayUrl}/v1/batches/${batchId}?include_results=${includeResults}`;
|
|
547
|
+
const response = await fetch(url, {
|
|
548
|
+
method: 'GET',
|
|
549
|
+
headers: this.buildHeaders(),
|
|
550
|
+
signal: AbortSignal.timeout(this.timeout),
|
|
551
|
+
});
|
|
552
|
+
if (!response.ok) {
|
|
553
|
+
throw new RunError(`HTTP ${response.status}: Failed to get batch status`, batchId);
|
|
554
|
+
}
|
|
555
|
+
const data = await response.json();
|
|
556
|
+
return new BatchStatusResult(data);
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Cancel a running batch execution.
|
|
560
|
+
*/
|
|
561
|
+
async cancelBatch(batchId) {
|
|
562
|
+
const url = `${this.gatewayUrl}/v1/batches/${batchId}/cancel`;
|
|
563
|
+
const response = await fetch(url, {
|
|
564
|
+
method: 'POST',
|
|
565
|
+
headers: this.buildHeaders(),
|
|
566
|
+
signal: AbortSignal.timeout(this.timeout),
|
|
567
|
+
});
|
|
568
|
+
if (!response.ok) {
|
|
569
|
+
throw new RunError(`HTTP ${response.status}: Failed to cancel batch`, batchId);
|
|
570
|
+
}
|
|
571
|
+
const data = (await response.json());
|
|
572
|
+
return {
|
|
573
|
+
batchId: data.batch_id || data.batchId || batchId,
|
|
574
|
+
status: data.status || 'cancelled',
|
|
575
|
+
cancelledItems: data.cancelled_items ?? data.cancelledItems ?? 0,
|
|
576
|
+
completedItems: data.completed_items ?? data.completedItems ?? 0,
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
// ─── Evaluation operations ──────────────────────────────────────────
|
|
580
|
+
/**
|
|
581
|
+
* Evaluate a component with scorers.
|
|
582
|
+
*
|
|
583
|
+
* @example
|
|
584
|
+
* ```typescript
|
|
585
|
+
* const result = await client.eval('greet', { name: 'Alice' }, {
|
|
586
|
+
* expected: 'Hello, Alice!',
|
|
587
|
+
* scorers: ['exact_match', 'contains'],
|
|
588
|
+
* });
|
|
589
|
+
*
|
|
590
|
+
* if (result.passed) {
|
|
591
|
+
* console.log('All scorers passed');
|
|
592
|
+
* }
|
|
593
|
+
* ```
|
|
594
|
+
*/
|
|
595
|
+
async eval(component, inputData, options = {}) {
|
|
596
|
+
const componentType = options.componentType || 'function';
|
|
597
|
+
// Gateway exposes a single global eval route at POST /v1/eval; the
|
|
598
|
+
// component identity goes in the body, not the URL. See
|
|
599
|
+
// runtime/crates/gateway/src/server.rs and handlers/eval.rs
|
|
600
|
+
// (EvalRequest fields). Mirrors sdk-python/src/agnt5/client.py:709.
|
|
601
|
+
const url = `${this.gatewayUrl}/v1/eval`;
|
|
602
|
+
// Default to exact_match if expected is provided but no scorers
|
|
603
|
+
let scorers = options.scorers;
|
|
604
|
+
if (options.expected !== undefined && (!scorers || scorers.length === 0)) {
|
|
605
|
+
scorers = ['exact_match'];
|
|
606
|
+
}
|
|
607
|
+
const body = {
|
|
608
|
+
component,
|
|
609
|
+
component_type: componentType,
|
|
610
|
+
};
|
|
611
|
+
if (inputData !== undefined)
|
|
612
|
+
body.input = inputData;
|
|
613
|
+
if (options.expected !== undefined)
|
|
614
|
+
body.expected = options.expected;
|
|
615
|
+
if (scorers && scorers.length > 0)
|
|
616
|
+
body.scorers = normalizeScorerSpecs(scorers);
|
|
617
|
+
const extra = {};
|
|
618
|
+
if (options.sessionId)
|
|
619
|
+
extra['X-Session-ID'] = options.sessionId;
|
|
620
|
+
if (options.userId)
|
|
621
|
+
extra['X-User-ID'] = options.userId;
|
|
622
|
+
const response = await fetch(url, {
|
|
623
|
+
method: 'POST',
|
|
624
|
+
headers: this.buildHeaders(extra),
|
|
625
|
+
body: JSON.stringify(body),
|
|
626
|
+
signal: AbortSignal.timeout(options.timeout || this.timeout),
|
|
627
|
+
});
|
|
628
|
+
if (!response.ok) {
|
|
629
|
+
const errorData = (await response.json().catch(() => ({})));
|
|
630
|
+
throw createErrorFromResponse(response.status, errorData.error || `Evaluation failed for '${component}'`, errorData.run_id || errorData.runId, url);
|
|
631
|
+
}
|
|
632
|
+
const data = await response.json();
|
|
633
|
+
return new EvalResponse(data);
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Evaluate a component in batch with multiple inputs and scorers.
|
|
637
|
+
*
|
|
638
|
+
* @example
|
|
639
|
+
* ```typescript
|
|
640
|
+
* const result = await client.batchEval('greet', [
|
|
641
|
+
* { input: { name: 'Alice' }, expected: 'Hello, Alice!' },
|
|
642
|
+
* { input: { name: 'Bob' }, expected: 'Hello, Bob!' },
|
|
643
|
+
* ], {
|
|
644
|
+
* scorers: ['exact_match'],
|
|
645
|
+
* maxConcurrency: 5,
|
|
646
|
+
* });
|
|
647
|
+
*
|
|
648
|
+
* console.log(`Pass rate: ${(result.passRate * 100).toFixed(1)}%`);
|
|
649
|
+
* ```
|
|
650
|
+
*/
|
|
651
|
+
async batchEval(component, items, options = {}) {
|
|
652
|
+
const normalized = normalizeBatchEvalItems(items, options.expected);
|
|
653
|
+
const maxConcurrency = options.maxConcurrency ?? 10;
|
|
654
|
+
const startTime = Date.now();
|
|
655
|
+
// Run evaluations with concurrency limit
|
|
656
|
+
const results = [];
|
|
657
|
+
let running = 0;
|
|
658
|
+
let nextIdx = 0;
|
|
659
|
+
const runOne = async (item, idx) => {
|
|
660
|
+
try {
|
|
661
|
+
const evalResponse = await this.eval(component, item.input, {
|
|
662
|
+
expected: item.expected,
|
|
663
|
+
scorers: options.scorers,
|
|
664
|
+
componentType: options.componentType,
|
|
665
|
+
timeout: options.timeout,
|
|
666
|
+
});
|
|
667
|
+
results.push(BatchEvalItemResult.fromEvalResponse(evalResponse, idx, item.itemId));
|
|
668
|
+
}
|
|
669
|
+
catch (error) {
|
|
670
|
+
results.push(BatchEvalItemResult.fromException(error, idx, item.itemId));
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
// Simple semaphore-based concurrency
|
|
674
|
+
await new Promise((resolve) => {
|
|
675
|
+
const tryNext = () => {
|
|
676
|
+
while (running < maxConcurrency && nextIdx < normalized.length) {
|
|
677
|
+
const item = normalized[nextIdx];
|
|
678
|
+
const idx = nextIdx;
|
|
679
|
+
nextIdx++;
|
|
680
|
+
running++;
|
|
681
|
+
runOne(item, idx).then(() => {
|
|
682
|
+
running--;
|
|
683
|
+
if (results.length === normalized.length) {
|
|
684
|
+
resolve();
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
687
|
+
tryNext();
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
if (normalized.length === 0)
|
|
692
|
+
resolve();
|
|
693
|
+
};
|
|
694
|
+
tryNext();
|
|
695
|
+
});
|
|
696
|
+
const totalDurationMs = Date.now() - startTime;
|
|
697
|
+
const hasErrors = results.some(r => r.isFailed);
|
|
698
|
+
const allErrors = results.every(r => r.isFailed);
|
|
699
|
+
const status = allErrors ? 'failed' : hasErrors ? 'partial_failure' : 'completed';
|
|
700
|
+
return new BatchEvalResult({
|
|
701
|
+
batchId: `batch_eval_${Date.now()}`,
|
|
702
|
+
status,
|
|
703
|
+
results,
|
|
704
|
+
durationMs: totalDurationMs,
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
// ---------------------------------------------------------------------------
|
|
709
|
+
// WorkflowProxy
|
|
710
|
+
// ---------------------------------------------------------------------------
|
|
711
|
+
export class WorkflowProxy {
|
|
712
|
+
constructor(client, workflowName) {
|
|
713
|
+
this.client = client;
|
|
714
|
+
this.workflowName = workflowName;
|
|
715
|
+
}
|
|
716
|
+
/** Execute the workflow synchronously */
|
|
717
|
+
async run(input, options) {
|
|
718
|
+
return this.client.run(this.workflowName, input, {
|
|
719
|
+
componentType: 'workflow',
|
|
720
|
+
sessionId: options?.sessionId,
|
|
721
|
+
userId: options?.userId,
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
/** Send a message to a chat-enabled workflow */
|
|
725
|
+
async chat(message, sessionId, options) {
|
|
726
|
+
const input = { message, ...(options?.extra || {}) };
|
|
727
|
+
return this.client.run(this.workflowName, input, {
|
|
728
|
+
componentType: 'workflow',
|
|
729
|
+
sessionId,
|
|
730
|
+
userId: options?.userId,
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
/** Submit the workflow for async execution */
|
|
734
|
+
async submit(input) {
|
|
735
|
+
return this.client.submit(this.workflowName, input, { componentType: 'workflow' });
|
|
736
|
+
}
|
|
737
|
+
/** Stream events from workflow execution */
|
|
738
|
+
async *events(input) {
|
|
739
|
+
yield* this.client.events(this.workflowName, input, {
|
|
740
|
+
componentType: 'workflow',
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
// ---------------------------------------------------------------------------
|
|
745
|
+
// SessionProxy
|
|
746
|
+
// ---------------------------------------------------------------------------
|
|
747
|
+
export class SessionProxy extends EntityProxy {
|
|
748
|
+
/** Send a chat message to the session entity */
|
|
749
|
+
async chat(message, extra) {
|
|
750
|
+
return this.call('chat', { message, ...(extra || {}) });
|
|
751
|
+
}
|
|
752
|
+
/** Get conversation history from the session entity */
|
|
753
|
+
async getHistory() {
|
|
754
|
+
return this.call('get_history', {});
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
//# sourceMappingURL=client.js.map
|