@symbolica/agentica 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -77
- package/README.md +10 -9
- package/THIRD_PARTY_NOTICES.md +27 -0
- package/dist/src/agentica/agent.d.ts +3 -2
- package/dist/src/agentica/agent.d.ts.map +1 -1
- package/dist/src/agentica/agent.js +3 -4
- package/dist/src/agentica/agent.js.map +1 -1
- package/dist/src/agentica/agentic.d.ts.map +1 -1
- package/dist/src/agentica/agentic.js +1 -3
- package/dist/src/agentica/agentic.js.map +1 -1
- package/dist/src/agentica/common.d.ts +10 -5
- package/dist/src/agentica/common.d.ts.map +1 -1
- package/dist/src/agentica/common.js +32 -17
- package/dist/src/agentica/common.js.map +1 -1
- package/dist/src/agentica-unplugin.d.ts +2 -2
- package/dist/src/agentica-unplugin.d.ts.map +1 -1
- package/dist/src/bundlers/esbuild.d.ts +1 -1
- package/dist/src/bundlers/vite.d.ts +1 -1
- package/dist/src/client-session-manager/client-session-manager.d.ts +10 -8
- package/dist/src/client-session-manager/client-session-manager.d.ts.map +1 -1
- package/dist/src/client-session-manager/client-session-manager.js +172 -95
- package/dist/src/client-session-manager/client-session-manager.js.map +1 -1
- package/dist/src/client-session-manager/types.d.ts +1 -1
- package/dist/src/client-session-manager/types.d.ts.map +1 -1
- package/dist/src/coming-soon.d.ts +7 -18
- package/dist/src/coming-soon.d.ts.map +1 -1
- package/dist/src/coming-soon.js +21 -41
- package/dist/src/coming-soon.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/warpc/msg-protocol/concept/python-mirrors/str.d.ts +7 -0
- package/dist/src/warpc/msg-protocol/concept/python-mirrors/str.d.ts.map +1 -0
- package/dist/src/warpc/msg-protocol/concept/python-mirrors/str.js +144 -0
- package/dist/src/warpc/msg-protocol/concept/python-mirrors/str.js.map +1 -0
- package/dist/src/warpc/msg-protocol/concept/python-mirrors/str.test.d.ts +2 -0
- package/dist/src/warpc/msg-protocol/concept/python-mirrors/str.test.d.ts.map +1 -0
- package/dist/src/warpc/msg-protocol/concept/python-mirrors/str.test.js +253 -0
- package/dist/src/warpc/msg-protocol/concept/python-mirrors/str.test.js.map +1 -0
- package/dist/src/warpc/msg-protocol/concept/python-mirrors/utils.d.ts +5 -0
- package/dist/src/warpc/msg-protocol/concept/python-mirrors/utils.d.ts.map +1 -0
- package/dist/src/warpc/msg-protocol/concept/python-mirrors/utils.js +17 -0
- package/dist/src/warpc/msg-protocol/concept/python-mirrors/utils.js.map +1 -0
- package/dist/src/warpc/msg-protocol/concept/resource/system-msg.d.ts +2 -1
- package/dist/src/warpc/msg-protocol/concept/resource/system-msg.d.ts.map +1 -1
- package/dist/src/warpc/msg-protocol/concept/resource/system-msg.js +14 -10
- package/dist/src/warpc/msg-protocol/concept/resource/system-msg.js.map +1 -1
- package/dist-transformer/agentica/agent.d.ts +4 -2
- package/dist-transformer/agentica/agent.d.ts.map +1 -1
- package/dist-transformer/agentica/agent.js +3 -3
- package/dist-transformer/agentica/agentic.d.ts.map +1 -1
- package/dist-transformer/agentica/agentic.js +1 -3
- package/dist-transformer/agentica/common.d.ts +23 -13
- package/dist-transformer/agentica/common.d.ts.map +1 -1
- package/dist-transformer/agentica/common.js +46 -25
- package/dist-transformer/client-session-manager/client-session-manager.d.ts +20 -12
- package/dist-transformer/client-session-manager/client-session-manager.d.ts.map +1 -1
- package/dist-transformer/client-session-manager/client-session-manager.js +214 -108
- package/dist-transformer/client-session-manager/types.d.ts +1 -1
- package/dist-transformer/client-session-manager/types.d.ts.map +1 -1
- package/dist-transformer/coming-soon.d.ts +7 -18
- package/dist-transformer/coming-soon.d.ts.map +1 -1
- package/dist-transformer/coming-soon.js +21 -39
- package/dist-transformer/version.d.ts +1 -1
- package/dist-transformer/version.js +1 -1
- package/dist-transformer/warpc/msg-protocol/concept/python-mirrors/str.d.ts +7 -0
- package/dist-transformer/warpc/msg-protocol/concept/python-mirrors/str.d.ts.map +1 -0
- package/dist-transformer/warpc/msg-protocol/concept/python-mirrors/str.js +151 -0
- package/dist-transformer/warpc/msg-protocol/concept/python-mirrors/utils.d.ts +5 -0
- package/dist-transformer/warpc/msg-protocol/concept/python-mirrors/utils.d.ts.map +1 -0
- package/dist-transformer/warpc/msg-protocol/concept/python-mirrors/utils.js +16 -0
- package/dist-transformer/warpc/msg-protocol/concept/resource/system-msg.d.ts +2 -1
- package/dist-transformer/warpc/msg-protocol/concept/resource/system-msg.d.ts.map +1 -1
- package/dist-transformer/warpc/msg-protocol/concept/resource/system-msg.js +14 -11
- package/package.json +5 -3
|
@@ -35,9 +35,10 @@ import { version } from '../version.js';
|
|
|
35
35
|
*/
|
|
36
36
|
export class ClientSessionManager {
|
|
37
37
|
constructor(config = {}) {
|
|
38
|
-
|
|
39
|
-
this.
|
|
40
|
-
this.
|
|
38
|
+
// Per-session-manager WebSocket state (sm_id → connection state)
|
|
39
|
+
this.sessionManagers = new Map();
|
|
40
|
+
this.smLocks = new Map();
|
|
41
|
+
this.uidToSm = new Map(); // agent uid → sm_id
|
|
41
42
|
this.baseHttp = null;
|
|
42
43
|
this.baseWs = null;
|
|
43
44
|
this.apiKey = null;
|
|
@@ -158,31 +159,30 @@ export class ClientSessionManager {
|
|
|
158
159
|
return headers;
|
|
159
160
|
}
|
|
160
161
|
/**
|
|
161
|
-
* Background task for writing messages to WebSocket
|
|
162
|
+
* Background task for writing messages to WebSocket for a specific session manager.
|
|
162
163
|
*/
|
|
163
|
-
async
|
|
164
|
+
async smWriter(smId, control) {
|
|
164
165
|
await waitForTracing;
|
|
165
|
-
const wsLogger = this.logger.withScope(
|
|
166
|
+
const wsLogger = this.logger.withScope(`ws-writer-${smId.slice(0, 8)}`);
|
|
166
167
|
wsLogger.debug('Writer task started');
|
|
167
|
-
const ws = this.websocket;
|
|
168
168
|
try {
|
|
169
169
|
while (!control.shouldStop) {
|
|
170
|
+
const smc = this.sessionManagers.get(smId);
|
|
171
|
+
if (!smc) {
|
|
172
|
+
wsLogger.debug('Session manager connection removed, exiting writer');
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
170
175
|
try {
|
|
171
|
-
const
|
|
172
|
-
if (!sendQueue) {
|
|
173
|
-
wsLogger.debug('Send queue removed, exiting writer');
|
|
174
|
-
break;
|
|
175
|
-
}
|
|
176
|
-
const msg = await sendQueue.get();
|
|
176
|
+
const msg = await smc.sendQueue.get();
|
|
177
177
|
const msgUid = msg.uid;
|
|
178
178
|
const msgLogger = msgUid ? (this.uidToLogger.get(msgUid) ?? wsLogger) : wsLogger;
|
|
179
|
-
if (
|
|
179
|
+
if (smc.websocket.readyState !== 1) {
|
|
180
180
|
const error = new WebSocketConnectionError(`cannot send as websocket is not open.`);
|
|
181
181
|
enrichError(error, { uid: msgUid, sessionId: this.clientSessionId });
|
|
182
182
|
throw error;
|
|
183
183
|
}
|
|
184
184
|
msgLogger.debug(`Sending ${msg.type} message`);
|
|
185
|
-
|
|
185
|
+
smc.websocket.send(encodeMessage(msg));
|
|
186
186
|
}
|
|
187
187
|
catch (error) {
|
|
188
188
|
if (error instanceof Error && error.message === 'Queue closed') {
|
|
@@ -196,26 +196,24 @@ export class ClientSessionManager {
|
|
|
196
196
|
}
|
|
197
197
|
catch (error) {
|
|
198
198
|
wsLogger.error('Writer task failed', error);
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
this.matchIid.delete(matchId);
|
|
202
|
-
}
|
|
203
|
-
for (const [_, exceptionMap] of this.uidIidException.entries()) {
|
|
204
|
-
for (const [_iid, handlers] of exceptionMap.entries()) {
|
|
205
|
-
handlers.reject(new WebSocketConnectionError(`WebSocket writer failed: ${error.message}`));
|
|
206
|
-
}
|
|
207
|
-
}
|
|
199
|
+
// Clean up this session manager only - don't affect others
|
|
200
|
+
await this._cleanupSessionManager(smId);
|
|
208
201
|
throw error;
|
|
209
202
|
}
|
|
210
203
|
}
|
|
211
204
|
/**
|
|
212
|
-
* Background task for reading messages from WebSocket
|
|
205
|
+
* Background task for reading messages from WebSocket for a specific session manager.
|
|
213
206
|
*/
|
|
214
|
-
async
|
|
207
|
+
async smReader(smId, control) {
|
|
215
208
|
await waitForTracing;
|
|
216
|
-
const wsLogger = this.logger.withScope(
|
|
209
|
+
const wsLogger = this.logger.withScope(`ws-reader-${smId.slice(0, 8)}`);
|
|
217
210
|
wsLogger.debug('Reader task started');
|
|
218
|
-
const
|
|
211
|
+
const smc = this.sessionManagers.get(smId);
|
|
212
|
+
if (!smc) {
|
|
213
|
+
wsLogger.warn('Session manager connection not found');
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const ws = smc.websocket;
|
|
219
217
|
ws.onmessage = (event) => {
|
|
220
218
|
if (control.shouldStop)
|
|
221
219
|
return;
|
|
@@ -317,48 +315,125 @@ export class ClientSessionManager {
|
|
|
317
315
|
ws.onclose = (event) => {
|
|
318
316
|
wsLogger.debug(`WebSocket closed (code=${event.code}, reason=${event.reason || 'none'})`);
|
|
319
317
|
control.shouldStop = true;
|
|
320
|
-
for
|
|
321
|
-
|
|
322
|
-
this.matchIid.delete(matchId);
|
|
323
|
-
}
|
|
324
|
-
for (const [_, exceptionMap] of this.uidIidException.entries()) {
|
|
325
|
-
for (const [_iid, handlers] of exceptionMap.entries()) {
|
|
326
|
-
handlers.reject(new WebSocketConnectionError(`WebSocket closed: ${event.reason || 'Connection closed'}`));
|
|
327
|
-
}
|
|
328
|
-
exceptionMap.clear();
|
|
329
|
-
}
|
|
318
|
+
// Only fail futures for agents on THIS session manager
|
|
319
|
+
this._failSmFutures(smId, `WebSocket closed: ${event.reason || 'Connection closed'}`);
|
|
330
320
|
resolve();
|
|
331
321
|
};
|
|
332
322
|
ws.onerror = (_error) => {
|
|
333
323
|
wsLogger.error('WebSocket error');
|
|
334
324
|
control.shouldStop = true;
|
|
335
|
-
for
|
|
336
|
-
|
|
337
|
-
this.matchIid.delete(matchId);
|
|
338
|
-
}
|
|
339
|
-
for (const [_, exceptionMap] of this.uidIidException.entries()) {
|
|
340
|
-
for (const [_iid, handlers] of exceptionMap.entries()) {
|
|
341
|
-
handlers.reject(new WebSocketConnectionError('WebSocket error occurred'));
|
|
342
|
-
}
|
|
343
|
-
exceptionMap.clear();
|
|
344
|
-
}
|
|
325
|
+
// Only fail futures for agents on THIS session manager
|
|
326
|
+
this._failSmFutures(smId, 'WebSocket error occurred');
|
|
345
327
|
resolve();
|
|
346
328
|
};
|
|
347
329
|
});
|
|
348
330
|
}
|
|
349
331
|
/**
|
|
350
|
-
*
|
|
332
|
+
* Fail pending futures for agents on a specific session manager.
|
|
351
333
|
*/
|
|
352
|
-
|
|
353
|
-
|
|
334
|
+
_failSmFutures(smId, reason) {
|
|
335
|
+
// Get all UIDs that belong to this session manager
|
|
336
|
+
const affectedUids = [];
|
|
337
|
+
for (const [uid, sid] of this.uidToSm.entries()) {
|
|
338
|
+
if (sid === smId) {
|
|
339
|
+
affectedUids.push(uid);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
// Fail matchIid promises for affected UIDs (we don't track which session manager a match_id belongs to,
|
|
343
|
+
// so we fail all of them when any session manager fails - this is conservative but safe)
|
|
344
|
+
for (const [matchId, handlers] of this.matchIid.entries()) {
|
|
345
|
+
handlers.reject(new WebSocketConnectionError(reason));
|
|
346
|
+
this.matchIid.delete(matchId);
|
|
347
|
+
}
|
|
348
|
+
// Fail exception handlers for affected UIDs
|
|
349
|
+
for (const uid of affectedUids) {
|
|
350
|
+
const exceptionMap = this.uidIidException.get(uid);
|
|
351
|
+
if (exceptionMap) {
|
|
352
|
+
for (const [_iid, handlers] of exceptionMap.entries()) {
|
|
353
|
+
handlers.reject(new WebSocketConnectionError(reason));
|
|
354
|
+
}
|
|
355
|
+
exceptionMap.clear();
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Clean up a specific session manager's connection without affecting others.
|
|
361
|
+
*/
|
|
362
|
+
async _cleanupSessionManager(smId) {
|
|
363
|
+
const smc = this.sessionManagers.get(smId);
|
|
364
|
+
if (!smc) {
|
|
354
365
|
return;
|
|
355
366
|
}
|
|
367
|
+
this.logger.debug(`Cleaning up session manager ${smId.slice(0, 8)}`);
|
|
368
|
+
this.sessionManagers.delete(smId);
|
|
369
|
+
this.smLocks.delete(smId);
|
|
370
|
+
// Stop tasks
|
|
371
|
+
const [readerControl, writerControl] = smc.tasks;
|
|
372
|
+
readerControl.shouldStop = true;
|
|
373
|
+
writerControl.shouldStop = true;
|
|
374
|
+
// Close send queue
|
|
375
|
+
smc.sendQueue.close();
|
|
376
|
+
// Close WebSocket gracefully
|
|
377
|
+
try {
|
|
378
|
+
if (smc.websocket.readyState === 0 || smc.websocket.readyState === 1) {
|
|
379
|
+
smc.websocket.close(1000, 'Normal closure');
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
catch (error) {
|
|
383
|
+
this.logger.warn(`Failed to close WebSocket for session manager ${smId.slice(0, 8)}`, error);
|
|
384
|
+
}
|
|
385
|
+
// Wait for tasks to complete
|
|
386
|
+
await Promise.allSettled([readerControl.promise, writerControl.promise]);
|
|
387
|
+
// Fail pending futures for agents on this session manager
|
|
388
|
+
this._failSmFutures(smId, 'Session manager connection closed');
|
|
389
|
+
// Clean up agent-to-session-manager mappings
|
|
390
|
+
const affectedUids = [];
|
|
391
|
+
for (const [uid, sid] of this.uidToSm.entries()) {
|
|
392
|
+
if (sid === smId) {
|
|
393
|
+
affectedUids.push(uid);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
for (const uid of affectedUids) {
|
|
397
|
+
this.uidToSm.delete(uid);
|
|
398
|
+
}
|
|
399
|
+
this.logger.debug(`Cleaned up session manager ${smId.slice(0, 8)}`);
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Ensure WebSocket connection exists for a specific session manager, creating it if needed.
|
|
403
|
+
*/
|
|
404
|
+
async _ensureSmConnection(smId) {
|
|
405
|
+
// Fast path: connection already exists
|
|
406
|
+
const existing = this.sessionManagers.get(smId);
|
|
407
|
+
if (existing && existing.websocket.readyState === 1) {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
// Check if another call is already creating this connection
|
|
411
|
+
const existingLock = this.smLocks.get(smId);
|
|
412
|
+
if (existingLock) {
|
|
413
|
+
await existingLock;
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
// Create lock IMMEDIATELY after check - no await between check and set
|
|
417
|
+
let resolveLock;
|
|
418
|
+
const lockPromise = new Promise((resolve) => {
|
|
419
|
+
resolveLock = resolve;
|
|
420
|
+
});
|
|
421
|
+
this.smLocks.set(smId, lockPromise);
|
|
422
|
+
// Now safe to yield
|
|
356
423
|
await waitForTracing;
|
|
357
|
-
const span = this.logger.startSpan('sdk.
|
|
424
|
+
const span = this.logger.startSpan('sdk.sm_connection');
|
|
425
|
+
span.setAttribute('session_manager.id', smId);
|
|
358
426
|
try {
|
|
359
|
-
|
|
427
|
+
// Double-check after acquiring lock
|
|
428
|
+
const doubleCheck = this.sessionManagers.get(smId);
|
|
429
|
+
if (doubleCheck && doubleCheck.websocket.readyState === 1) {
|
|
430
|
+
resolveLock();
|
|
431
|
+
this.smLocks.delete(smId);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
const sendQueue = new Queue();
|
|
360
435
|
const websocket_uri = `${this.baseWs}/socket`;
|
|
361
|
-
this.logger.debug(`Connecting WebSocket`);
|
|
436
|
+
this.logger.debug(`Connecting WebSocket for session manager ${smId.slice(0, 8)}`);
|
|
362
437
|
span.setAttribute('websocket.uri', websocket_uri);
|
|
363
438
|
span.setAttribute('websocket.state', 'connecting');
|
|
364
439
|
// Create headers object with trace context for distributed tracing
|
|
@@ -368,22 +443,18 @@ export class ClientSessionManager {
|
|
|
368
443
|
}
|
|
369
444
|
headers['x-client-session-id'] = this.clientSessionId;
|
|
370
445
|
// Inject trace context into WebSocket headers for distributed tracing
|
|
371
|
-
// Execute in span's context to ensure proper trace propagation (like Python's inject(headers, context=ctx))
|
|
372
446
|
if (span.executeInContext) {
|
|
373
447
|
span.executeInContext(() => {
|
|
374
448
|
injectTraceContext(headers);
|
|
375
449
|
});
|
|
376
450
|
}
|
|
377
451
|
else {
|
|
378
|
-
// Fallback: pass span context directly
|
|
379
452
|
injectTraceContext(headers, span);
|
|
380
453
|
}
|
|
381
|
-
// Pass null for apiKey since headers already has Authorization
|
|
382
454
|
const ws = await createWebSocket(websocket_uri, this.apiKey, headers);
|
|
383
455
|
ws.binaryType = 'arraybuffer';
|
|
384
456
|
return new Promise((resolve, reject) => {
|
|
385
457
|
ws.onopen = () => {
|
|
386
|
-
this.websocket = ws;
|
|
387
458
|
try {
|
|
388
459
|
ws._socket?.unref?.();
|
|
389
460
|
}
|
|
@@ -398,23 +469,34 @@ export class ClientSessionManager {
|
|
|
398
469
|
shouldStop: false,
|
|
399
470
|
promise: Promise.resolve(),
|
|
400
471
|
};
|
|
401
|
-
|
|
402
|
-
|
|
472
|
+
// Store the session manager connection first so tasks can access it
|
|
473
|
+
const smc = {
|
|
474
|
+
websocket: ws,
|
|
475
|
+
sendQueue,
|
|
476
|
+
tasks: [readerControl, writerControl],
|
|
477
|
+
};
|
|
478
|
+
this.sessionManagers.set(smId, smc);
|
|
479
|
+
// Start per-session-manager reader and writer tasks
|
|
480
|
+
readerControl.promise = this.smReader(smId, readerControl).catch((error) => {
|
|
481
|
+
this.logger.error(`Reader task crashed for session manager ${smId.slice(0, 8)}`, error);
|
|
403
482
|
});
|
|
404
|
-
writerControl.promise = this.
|
|
405
|
-
this.logger.error(
|
|
483
|
+
writerControl.promise = this.smWriter(smId, writerControl).catch((error) => {
|
|
484
|
+
this.logger.error(`Writer task crashed for session manager ${smId.slice(0, 8)}`, error);
|
|
406
485
|
});
|
|
407
|
-
this.
|
|
408
|
-
this.logger.info(`WebSocket connected`);
|
|
486
|
+
this.logger.info(`WebSocket connected for session manager ${smId.slice(0, 8)}`);
|
|
409
487
|
span.setAttribute('websocket.state', 'connected');
|
|
410
488
|
span.end();
|
|
489
|
+
resolveLock();
|
|
490
|
+
this.smLocks.delete(smId);
|
|
411
491
|
resolve();
|
|
412
492
|
};
|
|
413
493
|
ws.onerror = (error) => {
|
|
414
|
-
this.logger.error(`WebSocket connection failed:\n${error.toString()}`);
|
|
494
|
+
this.logger.error(`WebSocket connection failed for session manager ${smId.slice(0, 8)}:\n${error.toString()}`);
|
|
415
495
|
span.setAttribute('websocket.state', 'error');
|
|
416
496
|
span.recordException(error);
|
|
417
497
|
span.end();
|
|
498
|
+
resolveLock();
|
|
499
|
+
this.smLocks.delete(smId);
|
|
418
500
|
reject(new WebSocketConnectionError('WebSocket connection failed'));
|
|
419
501
|
};
|
|
420
502
|
});
|
|
@@ -423,6 +505,8 @@ export class ClientSessionManager {
|
|
|
423
505
|
span.setAttribute('websocket.state', 'error');
|
|
424
506
|
span.recordException(error);
|
|
425
507
|
span.end();
|
|
508
|
+
resolveLock();
|
|
509
|
+
this.smLocks.delete(smId);
|
|
426
510
|
throw error;
|
|
427
511
|
}
|
|
428
512
|
}
|
|
@@ -503,10 +587,26 @@ export class ClientSessionManager {
|
|
|
503
587
|
logger.warn(message);
|
|
504
588
|
}
|
|
505
589
|
}
|
|
506
|
-
|
|
590
|
+
// Parse JSON response: { uid, session_manager_id }
|
|
591
|
+
const responseData = await response.json();
|
|
592
|
+
const uid = responseData.uid;
|
|
593
|
+
const smId = responseData.session_manager_id;
|
|
594
|
+
// Validate response fields
|
|
595
|
+
if (!uid) {
|
|
596
|
+
logger.error(`Server response missing "uid" field: ${JSON.stringify(responseData)}`);
|
|
597
|
+
throw new ConnectionError('Invalid server response: missing "uid" field');
|
|
598
|
+
}
|
|
599
|
+
if (!smId) {
|
|
600
|
+
logger.error(`Server response missing "session_manager_id" field: ${JSON.stringify(responseData)}`);
|
|
601
|
+
throw new ConnectionError('Invalid server response: missing "session_manager_id" field');
|
|
602
|
+
}
|
|
507
603
|
logger.info('Created agent');
|
|
508
604
|
span.setAttribute('agent.uid', uid);
|
|
509
|
-
|
|
605
|
+
span.setAttribute('agent.session_manager_id', smId);
|
|
606
|
+
// Track which session manager this agent belongs to
|
|
607
|
+
this.uidToSm.set(uid, smId);
|
|
608
|
+
// Ensure WebSocket connection for this session manager
|
|
609
|
+
await this._ensureSmConnection(smId);
|
|
510
610
|
this.knownUids.add(uid);
|
|
511
611
|
this._initAgentState(uid, parentLogger);
|
|
512
612
|
return uid;
|
|
@@ -542,8 +642,22 @@ export class ClientSessionManager {
|
|
|
542
642
|
enrichError(error, { uid, sessionId: this.clientSessionId });
|
|
543
643
|
throw error;
|
|
544
644
|
}
|
|
645
|
+
// Get session manager for this agent
|
|
646
|
+
const smId = this.uidToSm.get(uid);
|
|
647
|
+
if (!smId) {
|
|
648
|
+
const error = new WebSocketConnectionError(`No session manager found for agent ${uid}`);
|
|
649
|
+
enrichError(error, { uid, sessionId: this.clientSessionId });
|
|
650
|
+
throw error;
|
|
651
|
+
}
|
|
652
|
+
const smc = this.sessionManagers.get(smId);
|
|
653
|
+
if (!smc || smc.websocket.readyState !== 1) {
|
|
654
|
+
const error = new WebSocketConnectionError(`No active connection for session manager ${smId}`);
|
|
655
|
+
enrichError(error, { uid, sessionId: this.clientSessionId });
|
|
656
|
+
throw error;
|
|
657
|
+
}
|
|
545
658
|
// Add attributes matching Python SDK
|
|
546
659
|
span.setAttribute('agent.uid', uid);
|
|
660
|
+
span.setAttribute('agent.session_manager_id', smId);
|
|
547
661
|
span.setAttribute('agent.task_desc', typeof taskDesc === 'string' ? taskDesc : taskDesc.template);
|
|
548
662
|
span.setAttribute('invocation.streaming', streaming);
|
|
549
663
|
const matchId = this.idIssuer();
|
|
@@ -556,11 +670,12 @@ export class ClientSessionManager {
|
|
|
556
670
|
prompt: taskDesc,
|
|
557
671
|
streaming: streaming,
|
|
558
672
|
};
|
|
559
|
-
invLogger.debug(`Invoking with match_id=${matchId}`);
|
|
673
|
+
invLogger.debug(`Invoking with match_id=${matchId} on session manager ${smId.slice(0, 8)}`);
|
|
560
674
|
const iidPromise = new Promise((resolve, reject) => {
|
|
561
675
|
this.matchIid.set(matchId, { resolve, reject });
|
|
562
676
|
});
|
|
563
|
-
|
|
677
|
+
// Route to the correct session manager's send queue
|
|
678
|
+
smc.sendQueue.put(msg);
|
|
564
679
|
const iid = await iidPromise;
|
|
565
680
|
this.iidToUid.set(iid, uid);
|
|
566
681
|
invLogger.info(`Invocation created with iid=${iid}`);
|
|
@@ -577,8 +692,14 @@ export class ClientSessionManager {
|
|
|
577
692
|
return { resolvers: { resolve, reject, promise }, promise };
|
|
578
693
|
})();
|
|
579
694
|
this.uidIidException.get(uid).set(iid, resolvers);
|
|
695
|
+
// Capture smId for the closure
|
|
696
|
+
const capturedSmId = smId;
|
|
580
697
|
return {
|
|
581
698
|
send_message: async (data) => {
|
|
699
|
+
const currentSmc = this.sessionManagers.get(capturedSmId);
|
|
700
|
+
if (!currentSmc || currentSmc.websocket.readyState !== 1) {
|
|
701
|
+
throw new WebSocketConnectionError(`Session manager connection closed for ${capturedSmId}`);
|
|
702
|
+
}
|
|
582
703
|
const dataMsg = {
|
|
583
704
|
type: 'data',
|
|
584
705
|
uid,
|
|
@@ -586,7 +707,7 @@ export class ClientSessionManager {
|
|
|
586
707
|
data,
|
|
587
708
|
timestamp: new Date().toISOString(),
|
|
588
709
|
};
|
|
589
|
-
|
|
710
|
+
currentSmc.sendQueue.put(dataMsg);
|
|
590
711
|
invLogger.debug(`Queued ${data.length} bytes to send`);
|
|
591
712
|
},
|
|
592
713
|
recv_message: async () => {
|
|
@@ -804,29 +925,34 @@ export class ClientSessionManager {
|
|
|
804
925
|
* @param lastTotal - Previous cumulative total (for cross-invocation adjustment)
|
|
805
926
|
* @returns Object containing the usage for this invocation and the new cumulative total
|
|
806
927
|
*/
|
|
807
|
-
async fetchUsage(uid, iid,
|
|
928
|
+
async fetchUsage(uid, iid, _lastTotal) {
|
|
808
929
|
// Fetch logs for this invocation and sum usages
|
|
930
|
+
// Each log entry contains non-cumulative usage values, so we just add them up
|
|
931
|
+
// The logs are already filtered by iid, so they only contain this invocation's data
|
|
809
932
|
let total = Usage.zero();
|
|
810
933
|
const logs = await this.logs(uid, iid, { type: 'sm_inference_usage' });
|
|
811
934
|
for (const log of logs) {
|
|
812
935
|
const logUsage = log.usage;
|
|
813
936
|
if (logUsage) {
|
|
814
|
-
|
|
937
|
+
// Don't pass lastUsage - each log entry is already non-cumulative
|
|
938
|
+
total = total.add(Usage.fromCompletions(logUsage));
|
|
815
939
|
}
|
|
816
940
|
}
|
|
817
|
-
|
|
818
|
-
if (lastTotal) {
|
|
819
|
-
// Subtract previous cumulative total, but keep output_tokens as-is
|
|
820
|
-
// since output_tokens is already absolute (not cumulative)
|
|
821
|
-
usage = total.sub(lastTotal.replace({ outputTokens: 0 }));
|
|
822
|
-
}
|
|
823
|
-
return { usage, newTotal: total };
|
|
941
|
+
return { usage: total, newTotal: total };
|
|
824
942
|
}
|
|
825
943
|
/**
|
|
826
944
|
* Check if agent exists - matches Python agent_exists
|
|
827
945
|
*/
|
|
828
946
|
agentExists(uid) {
|
|
829
|
-
|
|
947
|
+
if (!this.knownUids.has(uid)) {
|
|
948
|
+
return false;
|
|
949
|
+
}
|
|
950
|
+
const smId = this.uidToSm.get(uid);
|
|
951
|
+
if (!smId) {
|
|
952
|
+
return false;
|
|
953
|
+
}
|
|
954
|
+
const smc = this.sessionManagers.get(smId);
|
|
955
|
+
return smc !== undefined && smc.websocket.readyState === 1; // 1 = OPEN
|
|
830
956
|
}
|
|
831
957
|
/**
|
|
832
958
|
* Get uid from iid (used for parent-child agent tracking)
|
|
@@ -867,7 +993,7 @@ export class ClientSessionManager {
|
|
|
867
993
|
}
|
|
868
994
|
}
|
|
869
995
|
/**
|
|
870
|
-
* Close a specific agent's resources (does not close the
|
|
996
|
+
* Close a specific agent's resources (does not close the session manager's WebSocket unless it's the last agent)
|
|
871
997
|
*/
|
|
872
998
|
async closeAgent(uid) {
|
|
873
999
|
const logger = this.uidToLogger.get(uid) ?? this.logger;
|
|
@@ -890,6 +1016,7 @@ export class ClientSessionManager {
|
|
|
890
1016
|
this.uidIidException.delete(uid);
|
|
891
1017
|
this.knownUids.delete(uid);
|
|
892
1018
|
this.uidToLogger.delete(uid);
|
|
1019
|
+
this.uidToSm.delete(uid);
|
|
893
1020
|
logger.debug(`Closed agent ${uid.slice(0, 8)}`);
|
|
894
1021
|
// Finally, destroy the agent on the server
|
|
895
1022
|
await this.destroyAgent(uid);
|
|
@@ -905,37 +1032,16 @@ export class ClientSessionManager {
|
|
|
905
1032
|
const span = this.logger.startSpan('stop');
|
|
906
1033
|
try {
|
|
907
1034
|
const agentCount = this.knownUids.size;
|
|
908
|
-
this.
|
|
1035
|
+
const smCount = this.sessionManagers.size;
|
|
1036
|
+
this.logger.debug(`CSM Stop: stopping with ${agentCount} agent(s) on ${smCount} session manager(s)`);
|
|
909
1037
|
span.setAttribute('agent_count', agentCount);
|
|
1038
|
+
span.setAttribute('session_manager_count', smCount);
|
|
910
1039
|
// Close all agents (cleans up per-agent state and destroys on server)
|
|
911
1040
|
const uids = Array.from(this.knownUids);
|
|
912
1041
|
await Promise.allSettled(uids.map((uid) => this.closeAgent(uid)));
|
|
913
|
-
//
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
this.sendQueue = null;
|
|
917
|
-
}
|
|
918
|
-
// Close WebSocket gracefully
|
|
919
|
-
if (this.websocket) {
|
|
920
|
-
try {
|
|
921
|
-
// 0 = CONNECTING, 1 = OPEN
|
|
922
|
-
if (this.websocket.readyState === 0 || this.websocket.readyState === 1) {
|
|
923
|
-
this.websocket.close(1000, 'Normal closure');
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
catch (error) {
|
|
927
|
-
this.logger.warn('Failed to close WebSocket', error);
|
|
928
|
-
}
|
|
929
|
-
this.websocket = null;
|
|
930
|
-
}
|
|
931
|
-
// Stop tasks
|
|
932
|
-
if (this.tasks) {
|
|
933
|
-
const [readerControl, writerControl] = this.tasks;
|
|
934
|
-
readerControl.shouldStop = true;
|
|
935
|
-
writerControl.shouldStop = true;
|
|
936
|
-
await Promise.allSettled([readerControl.promise, writerControl.promise]);
|
|
937
|
-
this.tasks = null;
|
|
938
|
-
}
|
|
1042
|
+
// Clean up all session manager connections
|
|
1043
|
+
const smIds = Array.from(this.sessionManagers.keys());
|
|
1044
|
+
await Promise.allSettled(smIds.map((smId) => this._cleanupSessionManager(smId)));
|
|
939
1045
|
this.logger.debug('All resources cleaned up');
|
|
940
1046
|
this.isStopped = true;
|
|
941
1047
|
// Ensure invocation guard is cleared
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type { components, operations, paths } from '../types/api.d';
|
|
2
2
|
import type { components } from '../types/api.d';
|
|
3
|
-
export type CreateAgentRequest = Omit<components['schemas']['CreateAgentRequest'], 'warp_globals_payload' | 'protocol'> & {
|
|
3
|
+
export type CreateAgentRequest = Omit<components['schemas']['CreateAgentRequest'], 'warp_globals_payload' | 'protocol' | 'json'> & {
|
|
4
4
|
warp_globals_payload: Uint8Array;
|
|
5
5
|
protocol?: string;
|
|
6
6
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/client-session-manager/types.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAGpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAIjD,MAAM,MAAM,kBAAkB,GAAG,IAAI,CACjC,UAAU,CAAC,SAAS,CAAC,CAAC,oBAAoB,CAAC,EAC3C,sBAAsB,GAAG,UAAU,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/client-session-manager/types.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAGpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAIjD,MAAM,MAAM,kBAAkB,GAAG,IAAI,CACjC,UAAU,CAAC,SAAS,CAAC,CAAC,oBAAoB,CAAC,EAC3C,sBAAsB,GAAG,UAAU,GAAG,MAAM,CAC/C,GAAG;IACA,oBAAoB,EAAE,UAAU,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,gBAAgB,CAAC,CAAC;AAGrE,MAAM,MAAM,sBAAsB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,wBAAwB,CAAC,CAAC;AACrF,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC,GAAG;IAC7F,IAAI,EAAE,UAAU,CAAC;CACpB,CAAC;AACF,MAAM,MAAM,qBAAqB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,uBAAuB,CAAC,CAAC;AACnF,MAAM,MAAM,0BAA0B,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,iCAAiC,CAAC,CAAC;AAClG,MAAM,MAAM,sBAAsB,GAAG,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,wBAAwB,CAAC,EAAE,qBAAqB,CAAC,GAAG;IAChH,mBAAmB,EAAE,UAAU,CAAC;CACnC,CAAC;AACF,MAAM,MAAM,uBAAuB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,yBAAyB,CAAC,CAAC;AAGvF,MAAM,MAAM,gBAAgB,GACtB,oBAAoB,GACpB,qBAAqB,GACrB,0BAA0B,GAC1B,uBAAuB,GACvB,sBAAsB,GACtB,sBAAsB,CAAC;AAE7B,MAAM,MAAM,sBAAsB,GAAG,sBAAsB,GAAG,oBAAoB,GAAG,sBAAsB,CAAC;AAE5G,MAAM,MAAM,8BAA8B,GAAG,oBAAoB,GAAG,sBAAsB,CAAC;AAE3F,MAAM,MAAM,sBAAsB,GAC5B,uBAAuB,GACvB,qBAAqB,GACrB,oBAAoB,GACpB,0BAA0B,CAAC;AAEjC,MAAM,MAAM,8BAA8B,GAAG,qBAAqB,GAAG,oBAAoB,GAAG,0BAA0B,CAAC"}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { FrameContext } from './warpc/frame-context/frame-ctx.js';
|
|
2
|
-
import type { ToolModeStrings } from './agentica/common.js';
|
|
3
2
|
declare enum Feature {
|
|
4
|
-
JSON_MODE = "JSON mode",
|
|
5
3
|
ENUM_VALUES = "Enum support",
|
|
6
4
|
FUTURE_VALUES = "Promises and futures",
|
|
7
5
|
LOCAL_FILE_ACCESS = "Local file access",
|
|
@@ -9,11 +7,9 @@ declare enum Feature {
|
|
|
9
7
|
NUMERIC_RANGES = "Numeric range objects",
|
|
10
8
|
BINARY_BUFFERS = "Binary buffer objects",
|
|
11
9
|
URL_OBJECTS = "URL objects",
|
|
12
|
-
JSON_BASE_EXCEPTIONS = "Richer exceptions in JSON mode",
|
|
13
10
|
CUSTOM_EXCEPTIONS = "Custom exceptions",
|
|
14
11
|
TYPE = "Type",
|
|
15
12
|
TYPE_CONSTRUCTORS = "Type constructor",
|
|
16
|
-
ARBITRARY = "Arbitrary",
|
|
17
13
|
VIRTUALIZATION = "Virtualization",
|
|
18
14
|
UNSUPPORTED_BUILTIN_CLASS = "Unsupported builtin class",
|
|
19
15
|
GENERATORS = "Generators",
|
|
@@ -22,23 +18,16 @@ declare enum Feature {
|
|
|
22
18
|
}
|
|
23
19
|
export declare class ComingSoon extends Error {
|
|
24
20
|
feature: Feature;
|
|
25
|
-
mode?: ToolModeStrings;
|
|
26
21
|
detail?: string;
|
|
27
|
-
constructor(feature: Feature, mode?: ToolModeStrings, detail?: string);
|
|
28
|
-
}
|
|
29
|
-
export declare class TryCodeMode extends Error {
|
|
30
|
-
feature: Feature;
|
|
31
|
-
detail: string;
|
|
32
22
|
constructor(feature: Feature, detail?: string);
|
|
33
23
|
}
|
|
34
|
-
export declare const validateFeature: (thing: any
|
|
35
|
-
export declare const constructorTypes: (
|
|
36
|
-
export declare const unsupportedBultinClass: (className: string
|
|
37
|
-
export declare const unsupportedEnum: (enumName: string
|
|
38
|
-
export declare const interfaceReturnType: (
|
|
39
|
-
export declare const intersectionReturnType: (
|
|
40
|
-
export declare const validateReturnType: (returnType: any, context: FrameContext
|
|
41
|
-
export declare const throwNoJsonMode: () => never;
|
|
24
|
+
export declare const validateFeature: (thing: any) => void;
|
|
25
|
+
export declare const constructorTypes: () => ComingSoon;
|
|
26
|
+
export declare const unsupportedBultinClass: (className: string) => ComingSoon;
|
|
27
|
+
export declare const unsupportedEnum: (enumName: string) => ComingSoon;
|
|
28
|
+
export declare const interfaceReturnType: () => ComingSoon;
|
|
29
|
+
export declare const intersectionReturnType: () => ComingSoon;
|
|
30
|
+
export declare const validateReturnType: (returnType: any, context: FrameContext) => void;
|
|
42
31
|
export declare const throwNoVirtualization: () => never;
|
|
43
32
|
export declare const throwNoBareFutures: () => never;
|
|
44
33
|
export default ComingSoon;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"coming-soon.d.ts","sourceRoot":"","sources":["../src/coming-soon.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"coming-soon.d.ts","sourceRoot":"","sources":["../src/coming-soon.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAIpE,aAAK,OAAO;IACR,WAAW,iBAAiB;IAC5B,aAAa,yBAAyB;IACtC,iBAAiB,sBAAsB;IACvC,kBAAkB,8BAA8B;IAChD,cAAc,0BAA0B;IACxC,cAAc,0BAA0B;IACxC,WAAW,gBAAgB;IAC3B,iBAAiB,sBAAsB;IACvC,IAAI,SAAS;IACb,iBAAiB,qBAAqB;IACtC,cAAc,mBAAmB;IACjC,yBAAyB,8BAA8B;IACvD,UAAU,eAAe;IACzB,qBAAqB,6BAA6B;IAClD,wBAAwB,gCAAgC;CAC3D;AAED,qBAAa,UAAW,SAAQ,KAAK;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;gBAEJ,OAAO,EAAE,OAAO,EAAE,MAAM,GAAE,MAAW;CAKpD;AAwDD,eAAO,MAAM,eAAe,GAAI,OAAO,GAAG,KAAG,IA8C5C,CAAC;AAEF,eAAO,MAAM,gBAAgB,QAAO,UAEnC,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,WAAW,MAAM,KAAG,UAE1D,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,UAAU,MAAM,KAAG,UAKlD,CAAC;AAEF,eAAO,MAAM,mBAAmB,QAAO,UAEtC,CAAC;AAEF,eAAO,MAAM,sBAAsB,QAAO,UAEzC,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,YAAY,GAAG,EAAE,SAAS,YAAY,KAAG,IAqC3E,CAAC;AAEF,eAAO,MAAM,qBAAqB,aAEjC,CAAC;AAEF,eAAO,MAAM,kBAAkB,aAE9B,CAAC;AAEF,eAAe,UAAU,CAAC"}
|