aurelo.ai 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1178 @@
1
+ export class AureloSDKError extends Error {
2
+ status;
3
+ reason;
4
+ rotateSession;
5
+ payload;
6
+ constructor(message, options) {
7
+ super(message);
8
+ this.name = 'AureloSDKError';
9
+ this.status = options.status;
10
+ this.reason = options.reason || '';
11
+ this.rotateSession = !!options.rotateSession;
12
+ this.payload = options.payload;
13
+ }
14
+ }
15
+ const CLIENT_ID_KEY = 'aurelo.sdk.clientId';
16
+ const SESSION_KEY = 'aurelo.sdk.session';
17
+ const MODEL_SESSIONS_KEY = 'aurelo.sdk.modelSessions';
18
+ const RECONNECT_CURSOR_KEY_PREFIX = 'aurelo.sdk.reconnectCursor.';
19
+ const ROTATION_REASONS = new Set([
20
+ 'input_threshold_reached',
21
+ 'output_threshold_reached',
22
+ 'no_active_runtime',
23
+ 'no_active_reconnect_stream',
24
+ 'window_expired',
25
+ 'session_rollover_pending',
26
+ 'request_closed',
27
+ 'response_completed',
28
+ 'response_done',
29
+ 'tracked_user_mismatch',
30
+ 'model_changed',
31
+ 'session_model_changed',
32
+ ]);
33
+ class MemoryStorage {
34
+ values = new Map();
35
+ get(key) {
36
+ return this.values.get(key) || null;
37
+ }
38
+ set(key, value) {
39
+ this.values.set(key, value);
40
+ }
41
+ delete(key) {
42
+ this.values.delete(key);
43
+ }
44
+ }
45
+ function createDefaultStorage() {
46
+ const localStorageRef = globalThis.localStorage;
47
+ if (localStorageRef) {
48
+ return {
49
+ get: (key) => localStorageRef.getItem(key),
50
+ set: (key, value) => localStorageRef.setItem(key, value),
51
+ delete: (key) => localStorageRef.removeItem(key),
52
+ };
53
+ }
54
+ return new MemoryStorage();
55
+ }
56
+ function normalizeBaseUrl(value) {
57
+ return String(value || '').trim().replace(/\/+$/, '') || 'https://aurelo.tech';
58
+ }
59
+ function randomId() {
60
+ const cryptoRef = globalThis.crypto;
61
+ if (cryptoRef && typeof cryptoRef.randomUUID === 'function') {
62
+ return cryptoRef.randomUUID().replace(/-/g, '');
63
+ }
64
+ const parts = new Uint8Array(16);
65
+ if (cryptoRef && typeof cryptoRef.getRandomValues === 'function') {
66
+ cryptoRef.getRandomValues(parts);
67
+ return Array.from(parts, (part) => part.toString(16).padStart(2, '0')).join('');
68
+ }
69
+ return `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`;
70
+ }
71
+ function normalizeClientId(value) {
72
+ const trimmed = String(value || '').trim();
73
+ const withoutPrefix = trimmed.startsWith('ext_') ? trimmed.slice(4) : trimmed;
74
+ return withoutPrefix.replace(/[^A-Za-z0-9_-]/g, '') || randomId();
75
+ }
76
+ function fullClientId(clientId) {
77
+ return clientId.startsWith('ext_') ? clientId : `ext_${clientId}`;
78
+ }
79
+ function createRequestId() {
80
+ return `req_${randomId()}`;
81
+ }
82
+ function createSession(clientId, model) {
83
+ const requestId = createRequestId();
84
+ const session = {
85
+ clientId,
86
+ requestId,
87
+ sessionId: `${fullClientId(clientId)}_${requestId}`,
88
+ createdAt: Date.now(),
89
+ status: 'active',
90
+ };
91
+ const modelKey = normalizeSessionModel(model);
92
+ if (modelKey) {
93
+ session.model = modelKey;
94
+ }
95
+ return session;
96
+ }
97
+ function normalizeSessionModel(model) {
98
+ return String(model || '').trim().toLowerCase();
99
+ }
100
+ function modelForOutbound(model, fallback = 'M1') {
101
+ const raw = String(model || '').trim();
102
+ if (!raw) {
103
+ return fallback;
104
+ }
105
+ const normalized = raw.toLowerCase();
106
+ if (normalized === 'm1' || normalized === 'm2' || normalized === 'x1' || normalized === 'x2') {
107
+ return normalized.toUpperCase();
108
+ }
109
+ const compact = normalized.replace(/[\s_-]+/g, '');
110
+ if (compact === 'm1long' || compact === 'm1longcontext') {
111
+ return 'M1 - Long Context';
112
+ }
113
+ if (compact === 'm2long' || compact === 'm2longcontext') {
114
+ return 'M2 - Long Context';
115
+ }
116
+ return raw;
117
+ }
118
+ function parseStoredJson(stored) {
119
+ if (!stored) {
120
+ return null;
121
+ }
122
+ if (typeof stored === 'string') {
123
+ try {
124
+ return JSON.parse(stored);
125
+ }
126
+ catch {
127
+ return null;
128
+ }
129
+ }
130
+ if (typeof stored === 'object') {
131
+ return stored;
132
+ }
133
+ return null;
134
+ }
135
+ function sessionMatchesModel(session, modelKey) {
136
+ if (!modelKey) {
137
+ return true;
138
+ }
139
+ return normalizeSessionModel(session?.model) === modelKey;
140
+ }
141
+ function requestIdFromSessionId(sessionId) {
142
+ const normalizedSessionId = String(sessionId || '').trim();
143
+ const markerIndex = normalizedSessionId.lastIndexOf('_req_');
144
+ if (markerIndex < 0) {
145
+ return '';
146
+ }
147
+ return normalizedSessionId.slice(markerIndex + 1);
148
+ }
149
+ function clientIdFromSessionId(sessionId) {
150
+ const normalizedSessionId = String(sessionId || '').trim();
151
+ const markerIndex = normalizedSessionId.lastIndexOf('_req_');
152
+ if (markerIndex <= 0) {
153
+ return '';
154
+ }
155
+ return normalizedSessionId.slice(0, markerIndex);
156
+ }
157
+ function reconnectCursorKey(sessionId) {
158
+ return `${RECONNECT_CURSOR_KEY_PREFIX}${sessionId}`;
159
+ }
160
+ export function createAureloClientId() {
161
+ return randomId();
162
+ }
163
+ export function normalizeAureloClientId(value) {
164
+ return normalizeClientId(value);
165
+ }
166
+ export function getAureloFullClientId(clientId) {
167
+ return fullClientId(normalizeClientId(clientId));
168
+ }
169
+ export function createAureloRequestId() {
170
+ return createRequestId();
171
+ }
172
+ export function createAureloSession(clientId, model) {
173
+ return createSession(normalizeClientId(clientId), model);
174
+ }
175
+ function readReason(payload) {
176
+ if (!payload || typeof payload !== 'object') {
177
+ return '';
178
+ }
179
+ const value = payload;
180
+ const billing = value.billing && typeof value.billing === 'object'
181
+ ? value.billing
182
+ : null;
183
+ const rolloverReason = typeof billing?.rolloverReason === 'string' ? billing.rolloverReason : '';
184
+ if (rolloverReason === 'input') {
185
+ return 'input_threshold_reached';
186
+ }
187
+ if (rolloverReason === 'output') {
188
+ return 'output_threshold_reached';
189
+ }
190
+ if (billing?.rolloverTriggered === true) {
191
+ return 'session_rollover_pending';
192
+ }
193
+ return String(value.reason || value.code || '').trim();
194
+ }
195
+ function shouldRotate(payload) {
196
+ if (!payload || typeof payload !== 'object') {
197
+ return { rotate: false, reason: '' };
198
+ }
199
+ const value = payload;
200
+ const reason = readReason(payload);
201
+ const billing = value.billing && typeof value.billing === 'object'
202
+ ? value.billing
203
+ : null;
204
+ const rotate = value.rotateSession === true ||
205
+ ROTATION_REASONS.has(reason) ||
206
+ billing?.rolloverTriggered === true;
207
+ return { rotate, reason };
208
+ }
209
+ function parseSseRotationComment(comment) {
210
+ if (!comment.includes('aurelo-rotate-session')) {
211
+ return null;
212
+ }
213
+ const reasonMatch = comment.match(/\breason=([^\s]+)/);
214
+ const sessionMatch = comment.match(/\bsessionId=([^\s]+)/);
215
+ return {
216
+ reason: reasonMatch ? reasonMatch[1] : 'request_closed',
217
+ sessionId: sessionMatch ? sessionMatch[1] : '',
218
+ };
219
+ }
220
+ function getPayloadSessionId(payload) {
221
+ if (!payload || typeof payload !== 'object') {
222
+ return '';
223
+ }
224
+ const value = payload;
225
+ const readSessionId = (record) => {
226
+ if (!record) {
227
+ return '';
228
+ }
229
+ const metadata = record.metadata && typeof record.metadata === 'object'
230
+ ? record.metadata
231
+ : null;
232
+ return String(record.sessionId || record.session_id || metadata?.sessionId || metadata?.session_id || '').trim();
233
+ };
234
+ const directSessionId = readSessionId(value);
235
+ if (directSessionId) {
236
+ return directSessionId;
237
+ }
238
+ const response = value.response && typeof value.response === 'object'
239
+ ? value.response
240
+ : null;
241
+ const responseSessionId = readSessionId(response);
242
+ if (responseSessionId) {
243
+ return responseSessionId;
244
+ }
245
+ const item = value.item && typeof value.item === 'object'
246
+ ? value.item
247
+ : null;
248
+ const itemSessionId = readSessionId(item);
249
+ if (itemSessionId) {
250
+ return itemSessionId;
251
+ }
252
+ const metadata = value.metadata && typeof value.metadata === 'object'
253
+ ? value.metadata
254
+ : null;
255
+ return String(value.sessionId || value.session_id || metadata?.sessionId || metadata?.session_id || '').trim();
256
+ }
257
+ function parseReconnectCursorComment(comment) {
258
+ if (!comment.includes('aurelo-reconnect-close')) {
259
+ return null;
260
+ }
261
+ const sessionMatch = comment.match(/\bsessionId=([^\s]+)/);
262
+ const cursorMatch = comment.match(/\bcursor=([0-9]+)/);
263
+ if (!sessionMatch || !cursorMatch) {
264
+ return null;
265
+ }
266
+ return {
267
+ sessionId: sessionMatch[1],
268
+ cursor: Number(cursorMatch[1]) || 0,
269
+ };
270
+ }
271
+ function isTerminalResponseType(type) {
272
+ return type === 'response.completed' || type === 'response.done';
273
+ }
274
+ function isAureloSdkError(value) {
275
+ return value instanceof AureloSDKError;
276
+ }
277
+ function definedEntries(values) {
278
+ return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined && value !== null && value !== ''));
279
+ }
280
+ function buildContinuationMetadata(request) {
281
+ const context = request.context || {};
282
+ return definedEntries({
283
+ ...(request.metadata || {}),
284
+ toolStackId: context.toolStackId,
285
+ permissionsVersion: context.permissionsVersion,
286
+ contextId: context.contextId,
287
+ contextVersion: context.contextVersion,
288
+ });
289
+ }
290
+ function buildContinuationContext(request) {
291
+ const context = request.context || {};
292
+ const nextContext = definedEntries({
293
+ tools: context.tools,
294
+ exposedTools: context.exposedTools,
295
+ toolStackId: context.toolStackId,
296
+ permissionsVersion: context.permissionsVersion,
297
+ contextId: context.contextId,
298
+ contextVersion: context.contextVersion,
299
+ });
300
+ return Object.keys(nextContext).length ? nextContext : undefined;
301
+ }
302
+ function getPayloadErrorMessage(payload) {
303
+ if (!payload || typeof payload !== 'object') {
304
+ return '';
305
+ }
306
+ const value = payload;
307
+ return String(value.error || value.message || '').trim();
308
+ }
309
+ export class AureloSessionManager {
310
+ storage;
311
+ clientIdValue = null;
312
+ sessionValue = null;
313
+ constructor(options = {}) {
314
+ this.storage = options.storage || createDefaultStorage();
315
+ if (options.clientId) {
316
+ this.clientIdValue = normalizeClientId(options.clientId);
317
+ }
318
+ }
319
+ async getClientId() {
320
+ if (this.clientIdValue) {
321
+ return this.clientIdValue;
322
+ }
323
+ const stored = await this.storage.get(CLIENT_ID_KEY);
324
+ if (stored && stored.trim()) {
325
+ this.clientIdValue = normalizeClientId(stored);
326
+ return this.clientIdValue;
327
+ }
328
+ this.clientIdValue = randomId();
329
+ await this.storage.set(CLIENT_ID_KEY, this.clientIdValue);
330
+ return this.clientIdValue;
331
+ }
332
+ async getFullClientId() {
333
+ return fullClientId(await this.getClientId());
334
+ }
335
+ async getStoredModelSessions() {
336
+ return parseStoredJson(await this.storage.get(MODEL_SESSIONS_KEY)) || {};
337
+ }
338
+ async saveSession(session) {
339
+ this.sessionValue = session;
340
+ await this.storage.set(SESSION_KEY, JSON.stringify(session));
341
+ const modelKey = normalizeSessionModel(session.model);
342
+ if (modelKey) {
343
+ const sessions = await this.getStoredModelSessions();
344
+ sessions[modelKey] = session;
345
+ await this.storage.set(MODEL_SESSIONS_KEY, JSON.stringify(sessions));
346
+ }
347
+ }
348
+ async getSession(model) {
349
+ const modelKey = normalizeSessionModel(model);
350
+ if (this.sessionValue &&
351
+ this.sessionValue.status === 'active' &&
352
+ sessionMatchesModel(this.sessionValue, modelKey)) {
353
+ return this.sessionValue;
354
+ }
355
+ if (modelKey) {
356
+ const modelSession = (await this.getStoredModelSessions())[modelKey];
357
+ if (modelSession?.sessionId &&
358
+ modelSession.status === 'active' &&
359
+ (!modelSession.model || sessionMatchesModel(modelSession, modelKey))) {
360
+ this.sessionValue = modelSession;
361
+ return modelSession;
362
+ }
363
+ }
364
+ const stored = await this.storage.get(SESSION_KEY);
365
+ const parsed = parseStoredJson(stored);
366
+ if (parsed?.sessionId && parsed.status === 'active' && sessionMatchesModel(parsed, modelKey)) {
367
+ this.sessionValue = parsed;
368
+ return parsed;
369
+ }
370
+ return this.rotateSession('initial', modelKey || undefined);
371
+ }
372
+ async getStoredActiveSession(model) {
373
+ const modelKey = normalizeSessionModel(model);
374
+ if (this.sessionValue &&
375
+ this.sessionValue.status === 'active' &&
376
+ sessionMatchesModel(this.sessionValue, modelKey)) {
377
+ return this.sessionValue;
378
+ }
379
+ if (modelKey) {
380
+ const modelSession = (await this.getStoredModelSessions())[modelKey];
381
+ if (modelSession?.sessionId &&
382
+ modelSession.status === 'active' &&
383
+ (!modelSession.model || sessionMatchesModel(modelSession, modelKey))) {
384
+ this.sessionValue = modelSession;
385
+ return modelSession;
386
+ }
387
+ }
388
+ const stored = await this.storage.get(SESSION_KEY);
389
+ const parsed = parseStoredJson(stored);
390
+ if (parsed?.sessionId && parsed.status === 'active' && sessionMatchesModel(parsed, modelKey)) {
391
+ this.sessionValue = parsed;
392
+ return parsed;
393
+ }
394
+ return null;
395
+ }
396
+ async getSessionSelection(model) {
397
+ const existing = await this.getStoredActiveSession(model);
398
+ if (existing) {
399
+ return { session: existing, created: false };
400
+ }
401
+ return { session: await this.rotateSession('initial', model), created: true };
402
+ }
403
+ async rotateSession(reason = 'manual', model) {
404
+ const clientId = await this.getClientId();
405
+ this.sessionValue = createSession(clientId, model);
406
+ this.sessionValue.rotationReason = reason;
407
+ await this.saveSession(this.sessionValue);
408
+ return this.sessionValue;
409
+ }
410
+ async closeSession(reason = 'request_closed', model) {
411
+ const session = await this.getStoredActiveSession(model);
412
+ if (!session) {
413
+ return;
414
+ }
415
+ this.sessionValue = {
416
+ ...session,
417
+ status: 'closed',
418
+ rotationReason: reason,
419
+ };
420
+ await this.saveSession(this.sessionValue);
421
+ }
422
+ }
423
+ export class AureloSDK {
424
+ apiKey;
425
+ baseUrl;
426
+ fetchImpl;
427
+ storage;
428
+ clientIdValue = null;
429
+ sessionValue = null;
430
+ reconnectCursors = new Map();
431
+ observedStreamResponses = new WeakSet();
432
+ observedStreamResponseSessions = new WeakMap();
433
+ observedSseComments = new Set();
434
+ observedSseCommentOrder = [];
435
+ constructor(options) {
436
+ if (!options.apiKey || !String(options.apiKey).trim()) {
437
+ throw new Error('AureloSDK requires apiKey.');
438
+ }
439
+ this.apiKey = String(options.apiKey).trim();
440
+ this.baseUrl = normalizeBaseUrl(options.baseUrl || 'https://aurelo.tech');
441
+ const resolvedFetch = options.fetch || globalThis.fetch;
442
+ if (typeof resolvedFetch !== 'function') {
443
+ throw new Error('AureloSDK requires fetch. Pass options.fetch in this runtime.');
444
+ }
445
+ this.fetchImpl = resolvedFetch.bind(globalThis);
446
+ this.storage = options.storage || createDefaultStorage();
447
+ if (options.clientId) {
448
+ this.clientIdValue = normalizeClientId(options.clientId);
449
+ }
450
+ }
451
+ async getClientId() {
452
+ if (this.clientIdValue) {
453
+ return this.clientIdValue;
454
+ }
455
+ const stored = await this.storage.get(CLIENT_ID_KEY);
456
+ if (stored && stored.trim()) {
457
+ this.clientIdValue = normalizeClientId(stored);
458
+ return this.clientIdValue;
459
+ }
460
+ this.clientIdValue = randomId();
461
+ await this.storage.set(CLIENT_ID_KEY, this.clientIdValue);
462
+ return this.clientIdValue;
463
+ }
464
+ async getFullClientId() {
465
+ return fullClientId(await this.getClientId());
466
+ }
467
+ async getStoredModelSessions() {
468
+ return parseStoredJson(await this.storage.get(MODEL_SESSIONS_KEY)) || {};
469
+ }
470
+ async saveSession(session) {
471
+ this.sessionValue = session;
472
+ await this.storage.set(SESSION_KEY, JSON.stringify(session));
473
+ const modelKey = normalizeSessionModel(session.model);
474
+ if (modelKey) {
475
+ const sessions = await this.getStoredModelSessions();
476
+ sessions[modelKey] = session;
477
+ await this.storage.set(MODEL_SESSIONS_KEY, JSON.stringify(sessions));
478
+ }
479
+ }
480
+ async findModelForSessionId(sessionId) {
481
+ const normalizedSessionId = String(sessionId || '').trim();
482
+ if (!normalizedSessionId) {
483
+ return '';
484
+ }
485
+ const session = await this.findSessionById(normalizedSessionId);
486
+ return normalizeSessionModel(session?.model);
487
+ }
488
+ async findSessionById(sessionId) {
489
+ const normalizedSessionId = String(sessionId || '').trim();
490
+ if (!normalizedSessionId) {
491
+ return null;
492
+ }
493
+ if (this.sessionValue?.sessionId === normalizedSessionId) {
494
+ return this.sessionValue;
495
+ }
496
+ const sessions = await this.getStoredModelSessions();
497
+ for (const [modelKey, session] of Object.entries(sessions)) {
498
+ if (session?.sessionId === normalizedSessionId) {
499
+ return {
500
+ ...session,
501
+ model: normalizeSessionModel(session.model || modelKey) || session.model,
502
+ };
503
+ }
504
+ }
505
+ const stored = await this.storage.get(SESSION_KEY);
506
+ const parsed = parseStoredJson(stored);
507
+ if (parsed?.sessionId === normalizedSessionId) {
508
+ return parsed;
509
+ }
510
+ return null;
511
+ }
512
+ async getSession(model) {
513
+ return (await this.getSessionSelection(model)).session;
514
+ }
515
+ async getStoredActiveSession(model) {
516
+ const modelKey = normalizeSessionModel(model);
517
+ if (this.sessionValue &&
518
+ this.sessionValue.status === 'active' &&
519
+ sessionMatchesModel(this.sessionValue, modelKey)) {
520
+ return this.sessionValue;
521
+ }
522
+ if (modelKey) {
523
+ const modelSession = (await this.getStoredModelSessions())[modelKey];
524
+ if (modelSession?.sessionId &&
525
+ modelSession.status === 'active' &&
526
+ (!modelSession.model || sessionMatchesModel(modelSession, modelKey))) {
527
+ this.sessionValue = modelSession;
528
+ return modelSession;
529
+ }
530
+ }
531
+ const stored = await this.storage.get(SESSION_KEY);
532
+ const parsed = parseStoredJson(stored);
533
+ if (parsed?.sessionId && parsed.status === 'active' && sessionMatchesModel(parsed, modelKey)) {
534
+ this.sessionValue = parsed;
535
+ return parsed;
536
+ }
537
+ return null;
538
+ }
539
+ async getSessionSelection(model) {
540
+ const existing = await this.getStoredActiveSession(model);
541
+ if (existing) {
542
+ return { session: existing, created: false };
543
+ }
544
+ return { session: await this.rotateSession('initial', model), created: true };
545
+ }
546
+ async rotateSession(reason = 'manual', model) {
547
+ const modelKey = normalizeSessionModel(model);
548
+ const previousSessionId = modelKey
549
+ ? (await this.getStoredActiveSession(modelKey))?.sessionId
550
+ : this.sessionValue?.sessionId;
551
+ const clientId = await this.getClientId();
552
+ this.sessionValue = createSession(clientId, modelKey || undefined);
553
+ this.sessionValue.rotationReason = reason;
554
+ if (previousSessionId) {
555
+ await this.clearReconnectCursor(previousSessionId);
556
+ }
557
+ await this.saveSession(this.sessionValue);
558
+ return this.sessionValue;
559
+ }
560
+ async closeSession(reason = 'request_closed', model) {
561
+ const session = await this.getStoredActiveSession(model);
562
+ if (!session) {
563
+ return;
564
+ }
565
+ this.sessionValue = {
566
+ ...session,
567
+ status: 'closed',
568
+ rotationReason: reason,
569
+ };
570
+ await this.clearReconnectCursor(session.sessionId);
571
+ await this.saveSession(this.sessionValue);
572
+ }
573
+ async markSessionClosed(session, reason = 'request_closed') {
574
+ this.sessionValue = {
575
+ ...session,
576
+ status: 'closed',
577
+ rotationReason: reason,
578
+ };
579
+ await this.clearReconnectCursor(session.sessionId);
580
+ await this.saveSession(this.sessionValue);
581
+ }
582
+ async getReconnectCursor(sessionId) {
583
+ if (this.reconnectCursors.has(sessionId)) {
584
+ return this.reconnectCursors.get(sessionId) ?? null;
585
+ }
586
+ const stored = await this.storage.get(reconnectCursorKey(sessionId));
587
+ if (!stored) {
588
+ return null;
589
+ }
590
+ const cursor = Number(stored);
591
+ if (!Number.isFinite(cursor) || cursor < 0) {
592
+ return null;
593
+ }
594
+ this.reconnectCursors.set(sessionId, cursor);
595
+ return cursor;
596
+ }
597
+ async setReconnectCursor(sessionId, cursor) {
598
+ const normalizedCursor = Math.max(0, Math.floor(cursor));
599
+ this.reconnectCursors.set(sessionId, normalizedCursor);
600
+ await this.storage.set(reconnectCursorKey(sessionId), String(normalizedCursor));
601
+ }
602
+ async clearReconnectCursor(sessionId) {
603
+ this.reconnectCursors.delete(sessionId);
604
+ if (this.storage.delete) {
605
+ await this.storage.delete(reconnectCursorKey(sessionId));
606
+ }
607
+ else {
608
+ await this.storage.set(reconnectCursorKey(sessionId), '');
609
+ }
610
+ }
611
+ async observeSseComment(comment) {
612
+ const normalizedComment = String(comment || '').trim();
613
+ if (!normalizedComment) {
614
+ return;
615
+ }
616
+ if (this.rememberSseComment(normalizedComment)) {
617
+ return;
618
+ }
619
+ const reconnectCursor = parseReconnectCursorComment(normalizedComment);
620
+ if (reconnectCursor) {
621
+ await this.setReconnectCursor(reconnectCursor.sessionId, reconnectCursor.cursor);
622
+ }
623
+ const rotation = parseSseRotationComment(normalizedComment);
624
+ if (rotation) {
625
+ const model = rotation.sessionId ? await this.findModelForSessionId(rotation.sessionId) : '';
626
+ await this.rotateSession(rotation.reason, model || undefined);
627
+ }
628
+ }
629
+ rememberSseComment(comment) {
630
+ if (this.observedSseComments.has(comment)) {
631
+ return true;
632
+ }
633
+ this.observedSseComments.add(comment);
634
+ this.observedSseCommentOrder.push(comment);
635
+ while (this.observedSseCommentOrder.length > 500) {
636
+ const oldest = this.observedSseCommentOrder.shift();
637
+ if (oldest) {
638
+ this.observedSseComments.delete(oldest);
639
+ }
640
+ }
641
+ return false;
642
+ }
643
+ async continueWithMessage(request) {
644
+ const continuationRequest = typeof request === 'string'
645
+ ? { message: request }
646
+ : request;
647
+ const message = String(continuationRequest.message || '').trim();
648
+ if (!message) {
649
+ throw new Error('AureloSDK continueWithMessage requires message.');
650
+ }
651
+ return this.createResponseWithRotationRetry({
652
+ model: continuationRequest.model || continuationRequest.publicModel || 'M1',
653
+ input: message,
654
+ stream: true,
655
+ sessionId: continuationRequest.sessionId,
656
+ requestId: continuationRequest.requestId,
657
+ metadata: {
658
+ ...(continuationRequest.metadata || {}),
659
+ aureloAction: 'continue_or_create',
660
+ sessionId: continuationRequest.sessionId,
661
+ requestId: continuationRequest.requestId,
662
+ source: continuationRequest.source || 'aurelo-sdk',
663
+ },
664
+ context: continuationRequest.context,
665
+ }, { signal: continuationRequest.signal });
666
+ }
667
+ async createResponse(request, options = {}) {
668
+ const { session, explicitOverride, created } = await this.resolveOutboundSession(request);
669
+ const metadata = {
670
+ ...(request.metadata || {}),
671
+ model: (request.metadata || {}).model || request.model,
672
+ clientId: fullClientId(session.clientId),
673
+ requestId: session.requestId,
674
+ sessionId: session.sessionId,
675
+ aureloSessionState: created ? 'new' : 'active',
676
+ aureloSessionCreated: created,
677
+ };
678
+ const { sessionId: _sessionId, session_id: _sessionIdSnake, requestId: _requestId, request_id: _requestIdSnake, latestInput: _latestInput, sessionStartInput: _sessionStartInput, fullContextInput: _fullContextInput, rotationRetryInput: _rotationRetryInput, ...requestBody } = request;
679
+ const sessionStartInput = request.sessionStartInput ?? request.fullContextInput ?? request.rotationRetryInput;
680
+ if (!explicitOverride && created && sessionStartInput !== undefined) {
681
+ requestBody.input = sessionStartInput;
682
+ }
683
+ else if (!explicitOverride && !created && request.latestInput !== undefined) {
684
+ requestBody.input = request.latestInput;
685
+ }
686
+ const body = {
687
+ ...requestBody,
688
+ metadata,
689
+ };
690
+ const response = await this.fetchImpl(`${this.baseUrl}/v1/responses`, {
691
+ method: 'POST',
692
+ headers: {
693
+ 'Authorization': `Bearer ${this.apiKey}`,
694
+ 'Content-Type': 'application/json',
695
+ },
696
+ body: JSON.stringify(body),
697
+ signal: options.signal,
698
+ });
699
+ if (!response.ok) {
700
+ const payload = await this.readErrorPayload(response);
701
+ const rotation = shouldRotate(payload);
702
+ const reason = rotation.reason || `http_${response.status}`;
703
+ const payloadMessage = getPayloadErrorMessage(payload);
704
+ await this.markSessionClosed(session, reason);
705
+ throw new AureloSDKError(`Aurelo request failed with HTTP ${response.status}${rotation.reason ? ` (${rotation.reason})` : ''}${payloadMessage ? `: ${payloadMessage}` : ''}`, {
706
+ status: response.status,
707
+ reason: rotation.reason,
708
+ rotateSession: rotation.rotate,
709
+ payload,
710
+ });
711
+ }
712
+ const responseSessionId = response.headers.get('x-aurelo-session-id');
713
+ if (!explicitOverride && responseSessionId && responseSessionId !== session.sessionId) {
714
+ const updatedSession = {
715
+ ...session,
716
+ clientId: clientIdFromSessionId(responseSessionId) || session.clientId,
717
+ requestId: requestIdFromSessionId(responseSessionId) || session.requestId,
718
+ sessionId: responseSessionId,
719
+ };
720
+ await this.clearReconnectCursor(session.sessionId);
721
+ await this.saveSession(updatedSession);
722
+ }
723
+ return this.observeResponseStream(response, responseSessionId || session.sessionId);
724
+ }
725
+ async createResponseWithRotationRetry(request, options = {}) {
726
+ try {
727
+ return await this.createResponse(request, options);
728
+ }
729
+ catch (error) {
730
+ if (isAureloSdkError(error) && error.rotateSession && !this.hasRequestSessionOverride(request)) {
731
+ return this.createResponse(request, options);
732
+ }
733
+ throw error;
734
+ }
735
+ }
736
+ async createOrContinueResponse(request, options = {}) {
737
+ return this.createResponseWithRotationRetry({
738
+ ...request,
739
+ metadata: {
740
+ ...(request.metadata || {}),
741
+ aureloAction: 'continue_or_create',
742
+ },
743
+ }, options);
744
+ }
745
+ async *streamResponse(request, options = {}) {
746
+ const response = await this.createResponseWithRotationRetry({ ...request, stream: true }, options);
747
+ yield* this.streamEventsFromResponse(response);
748
+ }
749
+ async *streamReconnect(request = {}) {
750
+ const response = await this.reconnect(request);
751
+ yield* this.streamEventsFromResponse(response);
752
+ }
753
+ async *continueResponse(message, request = {}) {
754
+ const model = request.model || request.publicModel || 'M1';
755
+ const response = await this.createResponseWithRotationRetry({
756
+ model,
757
+ input: message,
758
+ stream: true,
759
+ sessionId: request.sessionId,
760
+ requestId: request.requestId,
761
+ metadata: {
762
+ ...(request.metadata || {}),
763
+ aureloAction: 'continue_or_create',
764
+ sessionId: request.sessionId,
765
+ requestId: request.requestId,
766
+ },
767
+ context: request.context,
768
+ }, { signal: request.signal });
769
+ yield* this.streamEventsFromResponse(response);
770
+ }
771
+ hasRequestSessionOverride(request) {
772
+ const metadata = request.metadata || {};
773
+ return Boolean(String(request.sessionId || request.session_id || metadata.sessionId || metadata.session_id || '').trim() ||
774
+ String(request.requestId || request.request_id || metadata.requestId || metadata.request_id || '').trim());
775
+ }
776
+ async resolveOutboundSession(request) {
777
+ const model = normalizeSessionModel(request.model || request.metadata?.model);
778
+ const metadata = request.metadata || {};
779
+ const sessionId = String(request.sessionId ||
780
+ request.session_id ||
781
+ metadata.sessionId ||
782
+ metadata.session_id ||
783
+ '').trim();
784
+ const requestId = String(request.requestId ||
785
+ request.request_id ||
786
+ metadata.requestId ||
787
+ metadata.request_id ||
788
+ '').trim();
789
+ if (!sessionId && !requestId) {
790
+ const selection = await this.getSessionSelection(model || undefined);
791
+ const session = selection.session;
792
+ return { session, explicitOverride: false, created: selection.created };
793
+ }
794
+ const explicitSession = sessionId ? await this.findSessionById(sessionId) : null;
795
+ const fallbackSelection = explicitSession || sessionId
796
+ ? null
797
+ : await this.getSessionSelection(model || undefined);
798
+ const fallbackSession = explicitSession || fallbackSelection?.session;
799
+ const derivedClientId = sessionId ? clientIdFromSessionId(sessionId) : '';
800
+ const derivedRequestId = sessionId ? requestIdFromSessionId(sessionId) : '';
801
+ const clientId = fallbackSession?.clientId || derivedClientId || await this.getClientId();
802
+ return {
803
+ session: {
804
+ clientId,
805
+ requestId: requestId || fallbackSession?.requestId || derivedRequestId || createRequestId(),
806
+ sessionId: sessionId || fallbackSession?.sessionId || `${fullClientId(clientId)}_${requestId || createRequestId()}`,
807
+ createdAt: fallbackSession?.createdAt || Date.now(),
808
+ status: fallbackSession?.status || 'active',
809
+ rotationReason: fallbackSession?.rotationReason,
810
+ model: model || fallbackSession?.model,
811
+ },
812
+ explicitOverride: true,
813
+ created: false,
814
+ };
815
+ }
816
+ async *streamEventsFromResponse(response) {
817
+ if (!response.body) {
818
+ yield { type: 'done', raw: '' };
819
+ await this.closeSession('request_closed');
820
+ return;
821
+ }
822
+ const observeComments = !this.observedStreamResponses.has(response);
823
+ const reader = response.body.getReader();
824
+ const decoder = new TextDecoder();
825
+ let buffer = '';
826
+ try {
827
+ while (true) {
828
+ const { done, value } = await reader.read();
829
+ if (done) {
830
+ break;
831
+ }
832
+ buffer += decoder.decode(value, { stream: true });
833
+ const lines = buffer.split('\n');
834
+ buffer = lines.pop() || '';
835
+ for (const line of lines) {
836
+ const event = await this.handleSseLine(line, {
837
+ observeComments,
838
+ sessionId: this.observedStreamResponseSessions.get(response) || response.headers.get('x-aurelo-session-id') || '',
839
+ });
840
+ if (event) {
841
+ yield event;
842
+ }
843
+ }
844
+ }
845
+ buffer += decoder.decode();
846
+ if (buffer.trim()) {
847
+ const event = await this.handleSseLine(buffer, {
848
+ observeComments,
849
+ sessionId: this.observedStreamResponseSessions.get(response) || response.headers.get('x-aurelo-session-id') || '',
850
+ });
851
+ if (event) {
852
+ yield event;
853
+ }
854
+ }
855
+ yield { type: 'done', raw: '' };
856
+ }
857
+ finally {
858
+ reader.releaseLock();
859
+ }
860
+ }
861
+ async reconnect(request = {}) {
862
+ const model = normalizeSessionModel(request.model || request.metadata?.model);
863
+ const requestedSessionId = String(request.sessionId || '').trim();
864
+ const explicitSession = requestedSessionId ? await this.findSessionById(requestedSessionId) : null;
865
+ const sessionModel = model || normalizeSessionModel(explicitSession?.model);
866
+ const session = requestedSessionId
867
+ ? {
868
+ clientId: explicitSession?.clientId || clientIdFromSessionId(requestedSessionId) || await this.getClientId(),
869
+ requestId: explicitSession?.requestId || requestIdFromSessionId(requestedSessionId) || createRequestId(),
870
+ sessionId: requestedSessionId,
871
+ createdAt: explicitSession?.createdAt || Date.now(),
872
+ status: explicitSession?.status || 'active',
873
+ rotationReason: explicitSession?.rotationReason,
874
+ model: sessionModel || explicitSession?.model,
875
+ }
876
+ : await this.getSession(model || undefined);
877
+ const storedCursor = await this.getReconnectCursor(session.sessionId);
878
+ const cursor = request.cursor ?? storedCursor ?? 0;
879
+ const effectiveModel = modelForOutbound(request.model || request.metadata?.model || session.model);
880
+ const metadata = {
881
+ ...(request.metadata || {}),
882
+ model: request.metadata?.model || effectiveModel,
883
+ clientId: fullClientId(session.clientId),
884
+ requestId: session.requestId,
885
+ sessionId: session.sessionId,
886
+ };
887
+ const body = {
888
+ model: effectiveModel,
889
+ input: request.input || '',
890
+ stream: true,
891
+ metadata: {
892
+ ...metadata,
893
+ aureloAction: 'reconnect',
894
+ cursor,
895
+ },
896
+ context: request.context,
897
+ };
898
+ const response = await this.fetchImpl(`${this.baseUrl}/v1/responses`, {
899
+ method: 'POST',
900
+ headers: {
901
+ 'Authorization': `Bearer ${this.apiKey}`,
902
+ 'Content-Type': 'application/json',
903
+ },
904
+ body: JSON.stringify(body),
905
+ signal: request.signal,
906
+ });
907
+ if (!response.ok) {
908
+ const payload = await this.readErrorPayload(response);
909
+ const rotation = shouldRotate(payload);
910
+ if (rotation.rotate) {
911
+ const payloadSessionModel = await this.findModelForSessionId(getPayloadSessionId(payload));
912
+ await this.rotateSession(rotation.reason || 'backend_rotation', payloadSessionModel || model || undefined);
913
+ }
914
+ if (rotation.rotate && String(payload?.nextAction || '') === 'start_new_request') {
915
+ return this.createResponse({
916
+ model: body.model,
917
+ input: body.input,
918
+ stream: true,
919
+ metadata: request.metadata,
920
+ context: request.context,
921
+ }, { signal: request.signal });
922
+ }
923
+ throw new AureloSDKError(`Aurelo reconnect failed with HTTP ${response.status}`, {
924
+ status: response.status,
925
+ reason: rotation.reason,
926
+ rotateSession: rotation.rotate,
927
+ payload,
928
+ });
929
+ }
930
+ return this.observeResponseStream(response, session.sessionId);
931
+ }
932
+ async submitToolResult(request) {
933
+ const requestedSessionId = String(request.sessionId ||
934
+ request.toolCall?.sessionId ||
935
+ request.toolCall?.session_id ||
936
+ '').trim();
937
+ const explicitSession = requestedSessionId ? await this.findSessionById(requestedSessionId) : null;
938
+ const activeSession = explicitSession || (requestedSessionId
939
+ ? {
940
+ clientId: clientIdFromSessionId(requestedSessionId) || await this.getClientId(),
941
+ requestId: requestIdFromSessionId(requestedSessionId) || createRequestId(),
942
+ sessionId: requestedSessionId,
943
+ model: normalizeSessionModel(request.model || request.publicModel) || undefined,
944
+ createdAt: Date.now(),
945
+ status: 'active',
946
+ }
947
+ : await this.getSession(request.model || request.publicModel));
948
+ const sessionId = String(requestedSessionId ||
949
+ activeSession.sessionId).trim();
950
+ const requestId = String(request.requestId ||
951
+ request.toolCall?.requestId ||
952
+ request.toolCall?.request_id ||
953
+ activeSession.requestId ||
954
+ requestIdFromSessionId(sessionId)).trim();
955
+ const callId = String(request.callId ||
956
+ request.call_id ||
957
+ request.toolCallId ||
958
+ request.tool_call_id ||
959
+ request.toolCall?.call_id ||
960
+ request.toolCall?.callId ||
961
+ request.toolCall?.id ||
962
+ '').trim();
963
+ if (!callId) {
964
+ throw new Error('AureloSDK submitToolResult requires callId.');
965
+ }
966
+ const effectiveModel = modelForOutbound(request.model || request.publicModel || activeSession.model);
967
+ const metadata = {
968
+ ...(request.metadata || {}),
969
+ aureloAction: 'tool_result',
970
+ clientId: fullClientId(activeSession.clientId),
971
+ requestId,
972
+ sessionId,
973
+ call_id: callId,
974
+ callId,
975
+ toolCallId: callId,
976
+ tool_call_id: callId,
977
+ model: effectiveModel,
978
+ };
979
+ const response = await this.fetchImpl(`${this.baseUrl}/v1/responses`, {
980
+ method: 'POST',
981
+ headers: {
982
+ 'Authorization': `Bearer ${this.apiKey}`,
983
+ 'Content-Type': 'application/json',
984
+ },
985
+ body: JSON.stringify({
986
+ model: effectiveModel,
987
+ publicModel: request.publicModel || request.model || effectiveModel,
988
+ stream: request.stream !== false,
989
+ sessionId,
990
+ requestId,
991
+ call_id: callId,
992
+ callId,
993
+ toolCallId: callId,
994
+ tool_call_id: callId,
995
+ tools: request.tools || request.context?.tools,
996
+ context: request.context,
997
+ output: request.output,
998
+ result: request.result,
999
+ error: request.error,
1000
+ metadata,
1001
+ }),
1002
+ signal: request.signal,
1003
+ });
1004
+ if (!response.ok) {
1005
+ const payload = await this.readErrorPayload(response);
1006
+ const rotation = shouldRotate(payload);
1007
+ if (rotation.rotate) {
1008
+ const payloadSessionModel = await this.findModelForSessionId(getPayloadSessionId(payload) || sessionId);
1009
+ await this.rotateSession(rotation.reason || 'backend_rotation', payloadSessionModel || request.model || request.publicModel);
1010
+ }
1011
+ throw new AureloSDKError(`Aurelo tool result submit failed with HTTP ${response.status}`, {
1012
+ status: response.status,
1013
+ reason: rotation.reason,
1014
+ rotateSession: rotation.rotate,
1015
+ payload,
1016
+ });
1017
+ }
1018
+ const contentType = response.headers.get('content-type') || '';
1019
+ if (contentType.includes('text/event-stream')) {
1020
+ return this.observeResponseStream(response, sessionId);
1021
+ }
1022
+ return this.readResponsePayload(response);
1023
+ }
1024
+ async *streamToolResult(request) {
1025
+ const result = await this.submitToolResult({ ...request, stream: true });
1026
+ if (result instanceof Response) {
1027
+ yield* this.streamEventsFromResponse(result);
1028
+ return;
1029
+ }
1030
+ yield { type: 'event', raw: '', data: result };
1031
+ }
1032
+ observeResponseStream(response, sessionId = '') {
1033
+ const contentType = response.headers.get('content-type') || '';
1034
+ if (!response.body || !contentType.includes('text/event-stream') || typeof ReadableStream === 'undefined') {
1035
+ return response;
1036
+ }
1037
+ const reader = response.body.getReader();
1038
+ const decoder = new TextDecoder();
1039
+ let buffer = '';
1040
+ const body = new ReadableStream({
1041
+ start: async (controller) => {
1042
+ try {
1043
+ while (true) {
1044
+ const { done, value } = await reader.read();
1045
+ if (done) {
1046
+ break;
1047
+ }
1048
+ if (value) {
1049
+ buffer = await this.observeSseTextFragment(decoder.decode(value, { stream: true }), buffer, sessionId);
1050
+ controller.enqueue(value);
1051
+ }
1052
+ }
1053
+ const trailing = decoder.decode();
1054
+ if (trailing) {
1055
+ buffer = await this.observeSseTextFragment(trailing, buffer, sessionId);
1056
+ }
1057
+ if (buffer.trim()) {
1058
+ await this.handleSseLine(buffer, { sessionId });
1059
+ }
1060
+ controller.close();
1061
+ }
1062
+ catch (error) {
1063
+ controller.error(error);
1064
+ }
1065
+ finally {
1066
+ reader.releaseLock();
1067
+ }
1068
+ },
1069
+ cancel: async (reason) => {
1070
+ try {
1071
+ await reader.cancel(reason);
1072
+ }
1073
+ finally {
1074
+ reader.releaseLock();
1075
+ }
1076
+ },
1077
+ });
1078
+ const observedResponse = new Response(body, {
1079
+ status: response.status,
1080
+ statusText: response.statusText,
1081
+ headers: response.headers,
1082
+ });
1083
+ this.observedStreamResponses.add(observedResponse);
1084
+ if (sessionId) {
1085
+ this.observedStreamResponseSessions.set(observedResponse, sessionId);
1086
+ }
1087
+ return observedResponse;
1088
+ }
1089
+ async observeSseTextFragment(fragment, previousBuffer, sessionId = '') {
1090
+ if (!fragment) {
1091
+ return previousBuffer;
1092
+ }
1093
+ const lines = `${previousBuffer}${fragment}`.split(/\r?\n/);
1094
+ const buffer = lines.pop() || '';
1095
+ for (const line of lines) {
1096
+ await this.handleSseLine(line, { sessionId });
1097
+ }
1098
+ return buffer;
1099
+ }
1100
+ async handleSseLine(line, options = {}) {
1101
+ const trimmed = line.trim();
1102
+ if (!trimmed) {
1103
+ return null;
1104
+ }
1105
+ if (trimmed.startsWith(':')) {
1106
+ const comment = trimmed.slice(1).trim();
1107
+ if (options.observeComments !== false) {
1108
+ await this.observeSseComment(comment);
1109
+ }
1110
+ return { type: 'comment', raw: line, comment };
1111
+ }
1112
+ if (trimmed === 'data: [DONE]') {
1113
+ await this.closeSessionForPayloadSession(options.sessionId || '', 'request_closed');
1114
+ return { type: 'done', raw: line };
1115
+ }
1116
+ if (!trimmed.startsWith('data: ')) {
1117
+ return { type: 'event', raw: line, data: trimmed };
1118
+ }
1119
+ let data = trimmed.slice(6);
1120
+ try {
1121
+ data = JSON.parse(trimmed.slice(6));
1122
+ }
1123
+ catch { }
1124
+ const eventType = data && typeof data === 'object'
1125
+ ? String(data.type || '')
1126
+ : '';
1127
+ const rotation = shouldRotate(data);
1128
+ if (rotation.rotate) {
1129
+ const model = await this.findModelForSessionId(getPayloadSessionId(data) || options.sessionId || '');
1130
+ await this.rotateSession(rotation.reason || 'backend_rotation', model || undefined);
1131
+ }
1132
+ else if (isTerminalResponseType(eventType)) {
1133
+ await this.closeSessionForPayloadSession(getPayloadSessionId(data) || options.sessionId || '', eventType === 'response.done' ? 'response_done' : 'response_completed');
1134
+ }
1135
+ return { type: 'event', raw: line, data };
1136
+ }
1137
+ async closeSessionForPayloadSession(sessionId, reason) {
1138
+ const normalizedSessionId = String(sessionId || '').trim();
1139
+ if (normalizedSessionId) {
1140
+ const session = await this.findSessionById(normalizedSessionId);
1141
+ if (session) {
1142
+ await this.markSessionClosed(session, reason);
1143
+ return;
1144
+ }
1145
+ const model = await this.findModelForSessionId(normalizedSessionId);
1146
+ if (model) {
1147
+ await this.closeSession(reason, model);
1148
+ }
1149
+ return;
1150
+ }
1151
+ await this.closeSession(reason);
1152
+ }
1153
+ async readErrorPayload(response) {
1154
+ const text = await response.text();
1155
+ if (!text) {
1156
+ return null;
1157
+ }
1158
+ try {
1159
+ return JSON.parse(text);
1160
+ }
1161
+ catch {
1162
+ return { error: text };
1163
+ }
1164
+ }
1165
+ async readResponsePayload(response) {
1166
+ const text = await response.text();
1167
+ if (!text) {
1168
+ return null;
1169
+ }
1170
+ try {
1171
+ return JSON.parse(text);
1172
+ }
1173
+ catch {
1174
+ return text;
1175
+ }
1176
+ }
1177
+ }
1178
+ export default AureloSDK;