@aurelo_npm/sdk 0.1.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.
@@ -0,0 +1,128 @@
1
+ export type AureloJson = null | boolean | number | string | AureloJson[] | {
2
+ [key: string]: AureloJson;
3
+ };
4
+ export interface AureloStorage {
5
+ get(key: string): string | null | Promise<string | null>;
6
+ set(key: string, value: string): void | Promise<void>;
7
+ delete?(key: string): void | Promise<void>;
8
+ }
9
+ export interface AureloSDKOptions {
10
+ apiKey: string;
11
+ baseUrl?: string;
12
+ queueUrl?: string;
13
+ clientId?: string;
14
+ storage?: AureloStorage;
15
+ fetch?: typeof fetch;
16
+ }
17
+ export interface AureloSessionManagerOptions {
18
+ clientId?: string;
19
+ storage?: AureloStorage;
20
+ }
21
+ export interface AureloSession {
22
+ clientId: string;
23
+ requestId: string;
24
+ sessionId: string;
25
+ createdAt: number;
26
+ status: 'active' | 'closed' | 'rotation_required';
27
+ rotationReason?: string;
28
+ }
29
+ export interface AureloResponseRequest {
30
+ model: string;
31
+ input?: unknown;
32
+ stream?: boolean;
33
+ metadata?: Record<string, unknown>;
34
+ [key: string]: unknown;
35
+ }
36
+ export interface AureloReconnectRequest {
37
+ sessionId?: string;
38
+ cursor?: string | number;
39
+ model?: string;
40
+ input?: unknown;
41
+ signal?: AbortSignal;
42
+ }
43
+ export interface AureloQueueMessageRequest {
44
+ message: string;
45
+ sessionId?: string;
46
+ source?: string;
47
+ requestId?: string;
48
+ publicModel?: string;
49
+ model?: string;
50
+ metadata?: Record<string, unknown>;
51
+ signal?: AbortSignal;
52
+ }
53
+ export interface AureloStreamEvent {
54
+ type: 'comment' | 'event' | 'done';
55
+ raw: string;
56
+ comment?: string;
57
+ data?: unknown;
58
+ }
59
+ export declare class AureloSDKError extends Error {
60
+ status: number;
61
+ reason: string;
62
+ rotateSession: boolean;
63
+ payload: unknown;
64
+ constructor(message: string, options: {
65
+ status: number;
66
+ reason?: string;
67
+ rotateSession?: boolean;
68
+ payload?: unknown;
69
+ });
70
+ }
71
+ export declare function createAureloClientId(): string;
72
+ export declare function normalizeAureloClientId(value: string): string;
73
+ export declare function getAureloFullClientId(clientId: string): string;
74
+ export declare function createAureloRequestId(): string;
75
+ export declare function createAureloSession(clientId: string): AureloSession;
76
+ export declare class AureloSessionManager {
77
+ private readonly storage;
78
+ private clientIdValue;
79
+ private sessionValue;
80
+ constructor(options?: AureloSessionManagerOptions);
81
+ getClientId(): Promise<string>;
82
+ getFullClientId(): Promise<string>;
83
+ getSession(): Promise<AureloSession>;
84
+ private getStoredActiveSession;
85
+ rotateSession(reason?: string): Promise<AureloSession>;
86
+ closeSession(reason?: string): Promise<void>;
87
+ }
88
+ export declare class AureloSDK {
89
+ private readonly apiKey;
90
+ private readonly baseUrl;
91
+ private readonly queueUrl;
92
+ private readonly fetchImpl;
93
+ private readonly storage;
94
+ private clientIdValue;
95
+ private sessionValue;
96
+ private reconnectCursors;
97
+ constructor(options: AureloSDKOptions);
98
+ getClientId(): Promise<string>;
99
+ getFullClientId(): Promise<string>;
100
+ getSession(): Promise<AureloSession>;
101
+ private getStoredActiveSession;
102
+ rotateSession(reason?: string): Promise<AureloSession>;
103
+ closeSession(reason?: string): Promise<void>;
104
+ private markSessionClosed;
105
+ private getReconnectCursor;
106
+ private setReconnectCursor;
107
+ private clearReconnectCursor;
108
+ observeSseComment(comment: string): Promise<void>;
109
+ queueMessage(request: string | AureloQueueMessageRequest): Promise<unknown>;
110
+ createResponse(request: AureloResponseRequest, options?: {
111
+ signal?: AbortSignal;
112
+ }): Promise<Response>;
113
+ private createResponseWithRotationRetry;
114
+ createOrContinueResponse(request: AureloResponseRequest, options?: {
115
+ signal?: AbortSignal;
116
+ }): Promise<Response>;
117
+ streamResponse(request: AureloResponseRequest, options?: {
118
+ signal?: AbortSignal;
119
+ }): AsyncGenerator<AureloStreamEvent>;
120
+ streamReconnect(request?: AureloReconnectRequest): AsyncGenerator<AureloStreamEvent>;
121
+ continueResponse(message: string, request?: Omit<AureloQueueMessageRequest, 'message'> & AureloReconnectRequest): AsyncGenerator<AureloStreamEvent>;
122
+ private streamEventsFromResponse;
123
+ reconnect(request?: AureloReconnectRequest): Promise<Response>;
124
+ private handleSseLine;
125
+ private readErrorPayload;
126
+ private readResponsePayload;
127
+ }
128
+ export default AureloSDK;
package/dist/index.js ADDED
@@ -0,0 +1,727 @@
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 RECONNECT_CURSOR_KEY_PREFIX = 'aurelo.sdk.reconnectCursor.';
18
+ const ROTATION_REASONS = new Set([
19
+ 'input_threshold_reached',
20
+ 'output_threshold_reached',
21
+ 'no_active_runtime',
22
+ 'no_active_reconnect_stream',
23
+ 'window_expired',
24
+ 'loop_rollover_pending',
25
+ 'request_closed',
26
+ 'response_completed',
27
+ 'response_done',
28
+ 'tracked_user_mismatch',
29
+ ]);
30
+ class MemoryStorage {
31
+ values = new Map();
32
+ get(key) {
33
+ return this.values.get(key) || null;
34
+ }
35
+ set(key, value) {
36
+ this.values.set(key, value);
37
+ }
38
+ delete(key) {
39
+ this.values.delete(key);
40
+ }
41
+ }
42
+ function createDefaultStorage() {
43
+ const localStorageRef = globalThis.localStorage;
44
+ if (localStorageRef) {
45
+ return {
46
+ get: (key) => localStorageRef.getItem(key),
47
+ set: (key, value) => localStorageRef.setItem(key, value),
48
+ delete: (key) => localStorageRef.removeItem(key),
49
+ };
50
+ }
51
+ return new MemoryStorage();
52
+ }
53
+ function normalizeBaseUrl(value) {
54
+ return String(value || '').trim().replace(/\/+$/, '') || 'https://aurelo.tech';
55
+ }
56
+ function defaultQueueUrl(baseUrl) {
57
+ try {
58
+ const parsed = new URL(baseUrl);
59
+ if (parsed.hostname === 'aurelo.tech' || parsed.hostname.endsWith('.aurelo.tech')) {
60
+ return 'https://api.aurelo.tech/mcp/queue';
61
+ }
62
+ }
63
+ catch { }
64
+ return `${baseUrl.replace(/\/+$/, '')}/mcp/queue`;
65
+ }
66
+ function randomId() {
67
+ const cryptoRef = globalThis.crypto;
68
+ if (cryptoRef && typeof cryptoRef.randomUUID === 'function') {
69
+ return cryptoRef.randomUUID().replace(/-/g, '');
70
+ }
71
+ const parts = new Uint8Array(16);
72
+ if (cryptoRef && typeof cryptoRef.getRandomValues === 'function') {
73
+ cryptoRef.getRandomValues(parts);
74
+ return Array.from(parts, (part) => part.toString(16).padStart(2, '0')).join('');
75
+ }
76
+ return `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`;
77
+ }
78
+ function normalizeClientId(value) {
79
+ const trimmed = String(value || '').trim();
80
+ const withoutPrefix = trimmed.startsWith('ext_') ? trimmed.slice(4) : trimmed;
81
+ return withoutPrefix.replace(/[^A-Za-z0-9_-]/g, '') || randomId();
82
+ }
83
+ function fullClientId(clientId) {
84
+ return clientId.startsWith('ext_') ? clientId : `ext_${clientId}`;
85
+ }
86
+ function createRequestId() {
87
+ return `req_${randomId()}`;
88
+ }
89
+ function createSession(clientId) {
90
+ const requestId = createRequestId();
91
+ return {
92
+ clientId,
93
+ requestId,
94
+ sessionId: `${fullClientId(clientId)}_${requestId}`,
95
+ createdAt: Date.now(),
96
+ status: 'active',
97
+ };
98
+ }
99
+ function reconnectCursorKey(sessionId) {
100
+ return `${RECONNECT_CURSOR_KEY_PREFIX}${sessionId}`;
101
+ }
102
+ export function createAureloClientId() {
103
+ return randomId();
104
+ }
105
+ export function normalizeAureloClientId(value) {
106
+ return normalizeClientId(value);
107
+ }
108
+ export function getAureloFullClientId(clientId) {
109
+ return fullClientId(normalizeClientId(clientId));
110
+ }
111
+ export function createAureloRequestId() {
112
+ return createRequestId();
113
+ }
114
+ export function createAureloSession(clientId) {
115
+ return createSession(normalizeClientId(clientId));
116
+ }
117
+ function readReason(payload) {
118
+ if (!payload || typeof payload !== 'object') {
119
+ return '';
120
+ }
121
+ const value = payload;
122
+ const billing = value.billing && typeof value.billing === 'object'
123
+ ? value.billing
124
+ : null;
125
+ const rolloverReason = typeof billing?.rolloverReason === 'string' ? billing.rolloverReason : '';
126
+ if (rolloverReason === 'input') {
127
+ return 'input_threshold_reached';
128
+ }
129
+ if (rolloverReason === 'output') {
130
+ return 'output_threshold_reached';
131
+ }
132
+ return String(value.reason || value.code || '').trim();
133
+ }
134
+ function shouldRotate(payload) {
135
+ if (!payload || typeof payload !== 'object') {
136
+ return { rotate: false, reason: '' };
137
+ }
138
+ const value = payload;
139
+ const reason = readReason(payload);
140
+ const billing = value.billing && typeof value.billing === 'object'
141
+ ? value.billing
142
+ : null;
143
+ const rotate = value.rotateSession === true ||
144
+ ROTATION_REASONS.has(reason) ||
145
+ billing?.rolloverTriggered === true;
146
+ return { rotate, reason };
147
+ }
148
+ function parseSseCommentReason(comment) {
149
+ if (!comment.includes('aurelo-rotate-session')) {
150
+ return '';
151
+ }
152
+ const match = comment.match(/\breason=([^\s]+)/);
153
+ return match ? match[1] : 'request_closed';
154
+ }
155
+ function parseReconnectCursorComment(comment) {
156
+ if (!comment.includes('aurelo-reconnect-close')) {
157
+ return null;
158
+ }
159
+ const sessionMatch = comment.match(/\bsessionId=([^\s]+)/);
160
+ const cursorMatch = comment.match(/\bcursor=([0-9]+)/);
161
+ if (!sessionMatch || !cursorMatch) {
162
+ return null;
163
+ }
164
+ return {
165
+ sessionId: sessionMatch[1],
166
+ cursor: Number(cursorMatch[1]) || 0,
167
+ };
168
+ }
169
+ function isTerminalResponseType(type) {
170
+ return type === 'response.completed' || type === 'response.done';
171
+ }
172
+ function isAureloSdkError(value) {
173
+ return value instanceof AureloSDKError;
174
+ }
175
+ function getPayloadErrorMessage(payload) {
176
+ if (!payload || typeof payload !== 'object') {
177
+ return '';
178
+ }
179
+ const value = payload;
180
+ return String(value.error || value.message || '').trim();
181
+ }
182
+ export class AureloSessionManager {
183
+ storage;
184
+ clientIdValue = null;
185
+ sessionValue = null;
186
+ constructor(options = {}) {
187
+ this.storage = options.storage || createDefaultStorage();
188
+ if (options.clientId) {
189
+ this.clientIdValue = normalizeClientId(options.clientId);
190
+ }
191
+ }
192
+ async getClientId() {
193
+ if (this.clientIdValue) {
194
+ return this.clientIdValue;
195
+ }
196
+ const stored = await this.storage.get(CLIENT_ID_KEY);
197
+ if (stored && stored.trim()) {
198
+ this.clientIdValue = normalizeClientId(stored);
199
+ return this.clientIdValue;
200
+ }
201
+ this.clientIdValue = randomId();
202
+ await this.storage.set(CLIENT_ID_KEY, this.clientIdValue);
203
+ return this.clientIdValue;
204
+ }
205
+ async getFullClientId() {
206
+ return fullClientId(await this.getClientId());
207
+ }
208
+ async getSession() {
209
+ if (this.sessionValue && this.sessionValue.status === 'active') {
210
+ return this.sessionValue;
211
+ }
212
+ const stored = await this.storage.get(SESSION_KEY);
213
+ if (stored) {
214
+ try {
215
+ const parsed = JSON.parse(stored);
216
+ if (parsed?.sessionId && parsed.status === 'active') {
217
+ this.sessionValue = parsed;
218
+ return parsed;
219
+ }
220
+ }
221
+ catch { }
222
+ }
223
+ return this.rotateSession('initial');
224
+ }
225
+ async getStoredActiveSession() {
226
+ if (this.sessionValue && this.sessionValue.status === 'active') {
227
+ return this.sessionValue;
228
+ }
229
+ const stored = await this.storage.get(SESSION_KEY);
230
+ if (!stored) {
231
+ return null;
232
+ }
233
+ try {
234
+ const parsed = JSON.parse(stored);
235
+ if (parsed?.sessionId && parsed.status === 'active') {
236
+ this.sessionValue = parsed;
237
+ return parsed;
238
+ }
239
+ }
240
+ catch { }
241
+ return null;
242
+ }
243
+ async rotateSession(reason = 'manual') {
244
+ const clientId = await this.getClientId();
245
+ this.sessionValue = createSession(clientId);
246
+ this.sessionValue.rotationReason = reason;
247
+ await this.storage.set(SESSION_KEY, JSON.stringify(this.sessionValue));
248
+ return this.sessionValue;
249
+ }
250
+ async closeSession(reason = 'request_closed') {
251
+ const session = await this.getSession();
252
+ this.sessionValue = {
253
+ ...session,
254
+ status: 'closed',
255
+ rotationReason: reason,
256
+ };
257
+ await this.storage.set(SESSION_KEY, JSON.stringify(this.sessionValue));
258
+ }
259
+ }
260
+ export class AureloSDK {
261
+ apiKey;
262
+ baseUrl;
263
+ queueUrl;
264
+ fetchImpl;
265
+ storage;
266
+ clientIdValue = null;
267
+ sessionValue = null;
268
+ reconnectCursors = new Map();
269
+ constructor(options) {
270
+ if (!options.apiKey || !String(options.apiKey).trim()) {
271
+ throw new Error('AureloSDK requires apiKey.');
272
+ }
273
+ this.apiKey = String(options.apiKey).trim();
274
+ this.baseUrl = normalizeBaseUrl(options.baseUrl || 'https://aurelo.tech');
275
+ this.queueUrl = String(options.queueUrl || defaultQueueUrl(this.baseUrl)).trim();
276
+ this.fetchImpl = options.fetch || globalThis.fetch;
277
+ if (typeof this.fetchImpl !== 'function') {
278
+ throw new Error('AureloSDK requires fetch. Pass options.fetch in this runtime.');
279
+ }
280
+ this.storage = options.storage || createDefaultStorage();
281
+ if (options.clientId) {
282
+ this.clientIdValue = normalizeClientId(options.clientId);
283
+ }
284
+ }
285
+ async getClientId() {
286
+ if (this.clientIdValue) {
287
+ return this.clientIdValue;
288
+ }
289
+ const stored = await this.storage.get(CLIENT_ID_KEY);
290
+ if (stored && stored.trim()) {
291
+ this.clientIdValue = normalizeClientId(stored);
292
+ return this.clientIdValue;
293
+ }
294
+ this.clientIdValue = randomId();
295
+ await this.storage.set(CLIENT_ID_KEY, this.clientIdValue);
296
+ return this.clientIdValue;
297
+ }
298
+ async getFullClientId() {
299
+ return fullClientId(await this.getClientId());
300
+ }
301
+ async getSession() {
302
+ if (this.sessionValue && this.sessionValue.status === 'active') {
303
+ return this.sessionValue;
304
+ }
305
+ const stored = await this.storage.get(SESSION_KEY);
306
+ if (stored) {
307
+ try {
308
+ const parsed = JSON.parse(stored);
309
+ if (parsed?.sessionId && parsed.status === 'active') {
310
+ this.sessionValue = parsed;
311
+ return parsed;
312
+ }
313
+ }
314
+ catch { }
315
+ }
316
+ return this.rotateSession('initial');
317
+ }
318
+ async getStoredActiveSession() {
319
+ if (this.sessionValue && this.sessionValue.status === 'active') {
320
+ return this.sessionValue;
321
+ }
322
+ const stored = await this.storage.get(SESSION_KEY);
323
+ if (!stored) {
324
+ return null;
325
+ }
326
+ try {
327
+ const parsed = JSON.parse(stored);
328
+ if (parsed?.sessionId && parsed.status === 'active') {
329
+ this.sessionValue = parsed;
330
+ return parsed;
331
+ }
332
+ }
333
+ catch { }
334
+ return null;
335
+ }
336
+ async rotateSession(reason = 'manual') {
337
+ const previousSessionId = this.sessionValue?.sessionId;
338
+ const clientId = await this.getClientId();
339
+ this.sessionValue = createSession(clientId);
340
+ this.sessionValue.rotationReason = reason;
341
+ if (previousSessionId) {
342
+ await this.clearReconnectCursor(previousSessionId);
343
+ }
344
+ await this.storage.set(SESSION_KEY, JSON.stringify(this.sessionValue));
345
+ return this.sessionValue;
346
+ }
347
+ async closeSession(reason = 'request_closed') {
348
+ const session = await this.getSession();
349
+ this.sessionValue = {
350
+ ...session,
351
+ status: 'closed',
352
+ rotationReason: reason,
353
+ };
354
+ await this.clearReconnectCursor(session.sessionId);
355
+ await this.storage.set(SESSION_KEY, JSON.stringify(this.sessionValue));
356
+ }
357
+ async markSessionClosed(session, reason = 'request_closed') {
358
+ this.sessionValue = {
359
+ ...session,
360
+ status: 'closed',
361
+ rotationReason: reason,
362
+ };
363
+ await this.clearReconnectCursor(session.sessionId);
364
+ await this.storage.set(SESSION_KEY, JSON.stringify(this.sessionValue));
365
+ }
366
+ async getReconnectCursor(sessionId) {
367
+ if (this.reconnectCursors.has(sessionId)) {
368
+ return this.reconnectCursors.get(sessionId) ?? null;
369
+ }
370
+ const stored = await this.storage.get(reconnectCursorKey(sessionId));
371
+ if (!stored) {
372
+ return null;
373
+ }
374
+ const cursor = Number(stored);
375
+ if (!Number.isFinite(cursor) || cursor < 0) {
376
+ return null;
377
+ }
378
+ this.reconnectCursors.set(sessionId, cursor);
379
+ return cursor;
380
+ }
381
+ async setReconnectCursor(sessionId, cursor) {
382
+ const normalizedCursor = Math.max(0, Math.floor(cursor));
383
+ this.reconnectCursors.set(sessionId, normalizedCursor);
384
+ await this.storage.set(reconnectCursorKey(sessionId), String(normalizedCursor));
385
+ }
386
+ async clearReconnectCursor(sessionId) {
387
+ this.reconnectCursors.delete(sessionId);
388
+ if (this.storage.delete) {
389
+ await this.storage.delete(reconnectCursorKey(sessionId));
390
+ }
391
+ else {
392
+ await this.storage.set(reconnectCursorKey(sessionId), '');
393
+ }
394
+ }
395
+ async observeSseComment(comment) {
396
+ const normalizedComment = String(comment || '').trim();
397
+ if (!normalizedComment) {
398
+ return;
399
+ }
400
+ const reconnectCursor = parseReconnectCursorComment(normalizedComment);
401
+ if (reconnectCursor) {
402
+ await this.setReconnectCursor(reconnectCursor.sessionId, reconnectCursor.cursor);
403
+ }
404
+ const reason = parseSseCommentReason(normalizedComment);
405
+ if (reason) {
406
+ await this.rotateSession(reason);
407
+ }
408
+ }
409
+ async queueMessage(request) {
410
+ const session = await this.getSession();
411
+ const queueRequest = typeof request === 'string'
412
+ ? { message: request }
413
+ : request;
414
+ const message = String(queueRequest.message || '').trim();
415
+ if (!message) {
416
+ throw new Error('AureloSDK queueMessage requires message.');
417
+ }
418
+ const sessionId = String(queueRequest.sessionId || session.sessionId).trim();
419
+ const metadata = {
420
+ ...(queueRequest.metadata || {}),
421
+ clientId: fullClientId(session.clientId),
422
+ requestId: session.requestId,
423
+ sessionId,
424
+ };
425
+ const response = await this.fetchImpl(this.queueUrl, {
426
+ method: 'POST',
427
+ headers: {
428
+ 'Authorization': `Bearer ${this.apiKey}`,
429
+ 'Content-Type': 'application/json',
430
+ },
431
+ body: JSON.stringify({
432
+ sessionId,
433
+ message,
434
+ source: queueRequest.source || 'aurelo-sdk',
435
+ requestId: queueRequest.requestId || session.requestId,
436
+ publicModel: queueRequest.publicModel || queueRequest.model,
437
+ metadata,
438
+ }),
439
+ signal: queueRequest.signal,
440
+ });
441
+ if (!response.ok) {
442
+ const payload = await this.readErrorPayload(response);
443
+ const rotation = shouldRotate(payload);
444
+ if (rotation.rotate) {
445
+ await this.rotateSession(rotation.reason || 'backend_rotation');
446
+ }
447
+ throw new AureloSDKError(`Aurelo queue failed with HTTP ${response.status}`, {
448
+ status: response.status,
449
+ reason: rotation.reason,
450
+ rotateSession: rotation.rotate,
451
+ payload,
452
+ });
453
+ }
454
+ return this.readResponsePayload(response);
455
+ }
456
+ async createResponse(request, options = {}) {
457
+ const session = await this.getSession();
458
+ const metadata = {
459
+ ...(request.metadata || {}),
460
+ clientId: fullClientId(session.clientId),
461
+ requestId: session.requestId,
462
+ sessionId: session.sessionId,
463
+ };
464
+ const body = {
465
+ ...request,
466
+ metadata,
467
+ };
468
+ const response = await this.fetchImpl(`${this.baseUrl}/v1/responses`, {
469
+ method: 'POST',
470
+ headers: {
471
+ 'Authorization': `Bearer ${this.apiKey}`,
472
+ 'Content-Type': 'application/json',
473
+ },
474
+ body: JSON.stringify(body),
475
+ signal: options.signal,
476
+ });
477
+ if (!response.ok) {
478
+ const payload = await this.readErrorPayload(response);
479
+ const rotation = shouldRotate(payload);
480
+ const reason = rotation.reason || `http_${response.status}`;
481
+ const payloadMessage = getPayloadErrorMessage(payload);
482
+ await this.markSessionClosed(session, reason);
483
+ throw new AureloSDKError(`Aurelo request failed with HTTP ${response.status}${rotation.reason ? ` (${rotation.reason})` : ''}${payloadMessage ? `: ${payloadMessage}` : ''}`, {
484
+ status: response.status,
485
+ reason: rotation.reason,
486
+ rotateSession: rotation.rotate,
487
+ payload,
488
+ });
489
+ }
490
+ const responseSessionId = response.headers.get('x-aurelo-session-id');
491
+ if (responseSessionId && responseSessionId !== session.sessionId) {
492
+ this.sessionValue = {
493
+ ...session,
494
+ sessionId: responseSessionId,
495
+ };
496
+ await this.storage.set(SESSION_KEY, JSON.stringify(this.sessionValue));
497
+ }
498
+ return response;
499
+ }
500
+ async createResponseWithRotationRetry(request, options = {}) {
501
+ try {
502
+ return await this.createResponse(request, options);
503
+ }
504
+ catch (error) {
505
+ if (isAureloSdkError(error) && error.rotateSession) {
506
+ return this.createResponse(request, options);
507
+ }
508
+ throw error;
509
+ }
510
+ }
511
+ async createOrContinueResponse(request, options = {}) {
512
+ const message = typeof request.input === 'string' ? request.input.trim() : '';
513
+ const existingSession = await this.getStoredActiveSession();
514
+ if (!existingSession || !message) {
515
+ return this.createResponseWithRotationRetry(request, options);
516
+ }
517
+ const existingCursor = await this.getReconnectCursor(existingSession.sessionId);
518
+ if (existingCursor === null) {
519
+ await this.markSessionClosed(existingSession, 'missing_reconnect_cursor');
520
+ return this.createResponseWithRotationRetry(request, options);
521
+ }
522
+ try {
523
+ await this.queueMessage({
524
+ message,
525
+ model: request.model,
526
+ publicModel: request.model,
527
+ metadata: request.metadata,
528
+ signal: options.signal,
529
+ });
530
+ return this.reconnect({
531
+ model: request.model,
532
+ input: request.input,
533
+ signal: options.signal,
534
+ });
535
+ }
536
+ catch (error) {
537
+ if (isAureloSdkError(error) && error.rotateSession) {
538
+ return this.createResponseWithRotationRetry(request, options);
539
+ }
540
+ throw error;
541
+ }
542
+ }
543
+ async *streamResponse(request, options = {}) {
544
+ const response = await this.createResponseWithRotationRetry({ ...request, stream: true }, options);
545
+ yield* this.streamEventsFromResponse(response);
546
+ }
547
+ async *streamReconnect(request = {}) {
548
+ const response = await this.reconnect(request);
549
+ yield* this.streamEventsFromResponse(response);
550
+ }
551
+ async *continueResponse(message, request = {}) {
552
+ const model = request.model || request.publicModel || 'M1';
553
+ try {
554
+ await this.queueMessage({
555
+ ...request,
556
+ message,
557
+ });
558
+ yield* this.streamReconnect({
559
+ ...request,
560
+ model,
561
+ input: message,
562
+ });
563
+ }
564
+ catch (error) {
565
+ if (isAureloSdkError(error) && error.rotateSession) {
566
+ const response = await this.createResponseWithRotationRetry({
567
+ model,
568
+ input: message,
569
+ stream: true,
570
+ metadata: request.metadata,
571
+ }, { signal: request.signal });
572
+ yield* this.streamEventsFromResponse(response);
573
+ return;
574
+ }
575
+ throw error;
576
+ }
577
+ }
578
+ async *streamEventsFromResponse(response) {
579
+ if (!response.body) {
580
+ yield { type: 'done', raw: '' };
581
+ await this.closeSession('request_closed');
582
+ return;
583
+ }
584
+ const reader = response.body.getReader();
585
+ const decoder = new TextDecoder();
586
+ let buffer = '';
587
+ try {
588
+ while (true) {
589
+ const { done, value } = await reader.read();
590
+ if (done) {
591
+ break;
592
+ }
593
+ buffer += decoder.decode(value, { stream: true });
594
+ const lines = buffer.split('\n');
595
+ buffer = lines.pop() || '';
596
+ for (const line of lines) {
597
+ const event = await this.handleSseLine(line);
598
+ if (event) {
599
+ yield event;
600
+ }
601
+ }
602
+ }
603
+ buffer += decoder.decode();
604
+ if (buffer.trim()) {
605
+ const event = await this.handleSseLine(buffer);
606
+ if (event) {
607
+ yield event;
608
+ }
609
+ }
610
+ yield { type: 'done', raw: '' };
611
+ }
612
+ finally {
613
+ reader.releaseLock();
614
+ }
615
+ }
616
+ async reconnect(request = {}) {
617
+ const session = request.sessionId
618
+ ? { ...(await this.getSession()), sessionId: request.sessionId }
619
+ : await this.getSession();
620
+ const storedCursor = await this.getReconnectCursor(session.sessionId);
621
+ const cursor = request.cursor ?? storedCursor ?? 0;
622
+ const metadata = {
623
+ clientId: fullClientId(session.clientId),
624
+ requestId: session.requestId,
625
+ sessionId: session.sessionId,
626
+ };
627
+ const body = {
628
+ model: request.model || 'M1',
629
+ input: request.input || '',
630
+ stream: true,
631
+ metadata: {
632
+ ...metadata,
633
+ aureloAction: 'reconnect',
634
+ cursor,
635
+ },
636
+ };
637
+ const response = await this.fetchImpl(`${this.baseUrl}/v1/responses`, {
638
+ method: 'POST',
639
+ headers: {
640
+ 'Authorization': `Bearer ${this.apiKey}`,
641
+ 'Content-Type': 'application/json',
642
+ },
643
+ body: JSON.stringify(body),
644
+ signal: request.signal,
645
+ });
646
+ if (!response.ok) {
647
+ const payload = await this.readErrorPayload(response);
648
+ const rotation = shouldRotate(payload);
649
+ if (rotation.rotate) {
650
+ await this.rotateSession(rotation.reason || 'backend_rotation');
651
+ }
652
+ if (rotation.rotate && String(payload?.nextAction || '') === 'start_new_request') {
653
+ return this.createResponse({
654
+ model: body.model,
655
+ input: body.input,
656
+ stream: true,
657
+ }, { signal: request.signal });
658
+ }
659
+ throw new AureloSDKError(`Aurelo reconnect failed with HTTP ${response.status}`, {
660
+ status: response.status,
661
+ reason: rotation.reason,
662
+ rotateSession: rotation.rotate,
663
+ payload,
664
+ });
665
+ }
666
+ return response;
667
+ }
668
+ async handleSseLine(line) {
669
+ const trimmed = line.trim();
670
+ if (!trimmed) {
671
+ return null;
672
+ }
673
+ if (trimmed.startsWith(':')) {
674
+ const comment = trimmed.slice(1).trim();
675
+ await this.observeSseComment(comment);
676
+ return { type: 'comment', raw: line, comment };
677
+ }
678
+ if (trimmed === 'data: [DONE]') {
679
+ await this.closeSession('request_closed');
680
+ return { type: 'done', raw: line };
681
+ }
682
+ if (!trimmed.startsWith('data: ')) {
683
+ return { type: 'event', raw: line, data: trimmed };
684
+ }
685
+ let data = trimmed.slice(6);
686
+ try {
687
+ data = JSON.parse(trimmed.slice(6));
688
+ }
689
+ catch { }
690
+ const eventType = data && typeof data === 'object'
691
+ ? String(data.type || '')
692
+ : '';
693
+ const rotation = shouldRotate(data);
694
+ if (rotation.rotate) {
695
+ await this.rotateSession(rotation.reason || 'backend_rotation');
696
+ }
697
+ else if (isTerminalResponseType(eventType)) {
698
+ await this.closeSession(eventType === 'response.done' ? 'response_done' : 'response_completed');
699
+ }
700
+ return { type: 'event', raw: line, data };
701
+ }
702
+ async readErrorPayload(response) {
703
+ const text = await response.text();
704
+ if (!text) {
705
+ return null;
706
+ }
707
+ try {
708
+ return JSON.parse(text);
709
+ }
710
+ catch {
711
+ return { error: text };
712
+ }
713
+ }
714
+ async readResponsePayload(response) {
715
+ const text = await response.text();
716
+ if (!text) {
717
+ return null;
718
+ }
719
+ try {
720
+ return JSON.parse(text);
721
+ }
722
+ catch {
723
+ return text;
724
+ }
725
+ }
726
+ }
727
+ export default AureloSDK;
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@aurelo_npm/sdk",
3
+ "version": "0.1.0",
4
+ "description": "Aurelo API-key SDK with stable client identity and backend-driven session rotation.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist/index.js",
10
+ "dist/index.d.ts"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc -p tsconfig.json",
14
+ "typecheck": "tsc --noEmit -p tsconfig.json"
15
+ },
16
+ "devDependencies": {
17
+ "typescript": "^5.3.2"
18
+ }
19
+ }