@starcite/sdk 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -1
- package/dist/index.cjs +317 -66
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +44 -2
- package/dist/index.d.ts +44 -2
- package/dist/index.js +317 -66
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -33,10 +33,17 @@ var StarciteConnectionError = class extends StarciteError {
|
|
|
33
33
|
// src/types.ts
|
|
34
34
|
import { z } from "zod";
|
|
35
35
|
var ArbitraryObjectSchema = z.record(z.unknown());
|
|
36
|
+
var CreatorTypeSchema = z.union([z.literal("user"), z.literal("agent")]);
|
|
37
|
+
var SessionCreatorPrincipalSchema = z.object({
|
|
38
|
+
tenant_id: z.string().min(1),
|
|
39
|
+
id: z.string().min(1),
|
|
40
|
+
type: CreatorTypeSchema
|
|
41
|
+
});
|
|
36
42
|
var CreateSessionInputSchema = z.object({
|
|
37
43
|
id: z.string().optional(),
|
|
38
44
|
title: z.string().optional(),
|
|
39
|
-
metadata: ArbitraryObjectSchema.optional()
|
|
45
|
+
metadata: ArbitraryObjectSchema.optional(),
|
|
46
|
+
creator_principal: SessionCreatorPrincipalSchema.optional()
|
|
40
47
|
});
|
|
41
48
|
var SessionRecordSchema = z.object({
|
|
42
49
|
id: z.string(),
|
|
@@ -114,6 +121,12 @@ var StarciteErrorPayloadSchema = z.object({
|
|
|
114
121
|
var DEFAULT_BASE_URL = typeof process !== "undefined" && process.env.STARCITE_BASE_URL ? process.env.STARCITE_BASE_URL : "http://localhost:4000";
|
|
115
122
|
var TRAILING_SLASHES_REGEX = /\/+$/;
|
|
116
123
|
var BEARER_PREFIX_REGEX = /^bearer\s+/i;
|
|
124
|
+
var DEFAULT_TAIL_RECONNECT_DELAY_MS = 3e3;
|
|
125
|
+
var CATCH_UP_IDLE_MS = 1e3;
|
|
126
|
+
var NORMAL_WEBSOCKET_CLOSE_CODE = 1e3;
|
|
127
|
+
var SERVICE_TOKEN_SUB_ORG_PREFIX = "org:";
|
|
128
|
+
var SERVICE_TOKEN_SUB_AGENT_PREFIX = "agent:";
|
|
129
|
+
var SERVICE_TOKEN_SUB_USER_PREFIX = "user:";
|
|
117
130
|
var TailFrameSchema = z2.string().transform((frame, context) => {
|
|
118
131
|
try {
|
|
119
132
|
return JSON.parse(frame);
|
|
@@ -234,6 +247,105 @@ function formatAuthorizationHeader(apiKey) {
|
|
|
234
247
|
}
|
|
235
248
|
return `Bearer ${normalized}`;
|
|
236
249
|
}
|
|
250
|
+
function firstNonEmptyString(value) {
|
|
251
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
252
|
+
}
|
|
253
|
+
function parseJwtSegment(segment) {
|
|
254
|
+
const base64 = segment.replace(/-/g, "+").replace(/_/g, "/").padEnd(segment.length + (4 - segment.length % 4) % 4, "=");
|
|
255
|
+
try {
|
|
256
|
+
if (typeof atob === "function") {
|
|
257
|
+
return atob(base64);
|
|
258
|
+
}
|
|
259
|
+
if (typeof Buffer !== "undefined") {
|
|
260
|
+
return Buffer.from(base64, "base64").toString("utf8");
|
|
261
|
+
}
|
|
262
|
+
} catch {
|
|
263
|
+
return void 0;
|
|
264
|
+
}
|
|
265
|
+
return void 0;
|
|
266
|
+
}
|
|
267
|
+
function parseJwtClaims(apiKey) {
|
|
268
|
+
const token = apiKey.replace(BEARER_PREFIX_REGEX, "").trim();
|
|
269
|
+
const parts = token.split(".");
|
|
270
|
+
if (parts.length !== 3) {
|
|
271
|
+
return void 0;
|
|
272
|
+
}
|
|
273
|
+
const [, payloadSegment] = parts;
|
|
274
|
+
if (payloadSegment === void 0) {
|
|
275
|
+
return void 0;
|
|
276
|
+
}
|
|
277
|
+
const payload = parseJwtSegment(payloadSegment);
|
|
278
|
+
if (payload === void 0) {
|
|
279
|
+
return void 0;
|
|
280
|
+
}
|
|
281
|
+
try {
|
|
282
|
+
const decoded = JSON.parse(payload);
|
|
283
|
+
return decoded !== null && typeof decoded === "object" ? decoded : void 0;
|
|
284
|
+
} catch {
|
|
285
|
+
return void 0;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
function parseClaimStrings(source, keys) {
|
|
289
|
+
for (const key of keys) {
|
|
290
|
+
const value = firstNonEmptyString(source[key]);
|
|
291
|
+
if (value) {
|
|
292
|
+
return value;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return void 0;
|
|
296
|
+
}
|
|
297
|
+
function parseActorIdentityFromSubject(subject) {
|
|
298
|
+
if (subject.startsWith(SERVICE_TOKEN_SUB_AGENT_PREFIX)) {
|
|
299
|
+
return { id: subject, type: "agent" };
|
|
300
|
+
}
|
|
301
|
+
if (subject.startsWith(SERVICE_TOKEN_SUB_USER_PREFIX)) {
|
|
302
|
+
return { id: subject, type: "user" };
|
|
303
|
+
}
|
|
304
|
+
return void 0;
|
|
305
|
+
}
|
|
306
|
+
function parseTenantIdFromSubject(subject) {
|
|
307
|
+
const actorIdentity = parseActorIdentityFromSubject(subject);
|
|
308
|
+
if (actorIdentity !== void 0) {
|
|
309
|
+
return "";
|
|
310
|
+
}
|
|
311
|
+
if (subject.startsWith(SERVICE_TOKEN_SUB_ORG_PREFIX)) {
|
|
312
|
+
return subject.slice(SERVICE_TOKEN_SUB_ORG_PREFIX.length).trim();
|
|
313
|
+
}
|
|
314
|
+
return subject;
|
|
315
|
+
}
|
|
316
|
+
function parseCreatorPrincipalFromClaims(claims) {
|
|
317
|
+
const subject = firstNonEmptyString(claims.sub);
|
|
318
|
+
const explicitPrincipal = claims.principal && typeof claims.principal === "object" ? claims.principal : void 0;
|
|
319
|
+
const mergedClaims = explicitPrincipal ? { ...claims, ...explicitPrincipal } : claims;
|
|
320
|
+
const actorFromSubject = subject ? parseActorIdentityFromSubject(subject) : void 0;
|
|
321
|
+
const principalTypeFromClaims = parseClaimStrings(mergedClaims, [
|
|
322
|
+
"principal_type",
|
|
323
|
+
"principalType",
|
|
324
|
+
"type"
|
|
325
|
+
]);
|
|
326
|
+
const tenantId = parseClaimStrings(mergedClaims, ["tenant_id", "tenantId"]);
|
|
327
|
+
const rawPrincipalId = parseClaimStrings(mergedClaims, [
|
|
328
|
+
"principal_id",
|
|
329
|
+
"principalId",
|
|
330
|
+
"id",
|
|
331
|
+
"sub"
|
|
332
|
+
]);
|
|
333
|
+
const actorFromRawId = rawPrincipalId ? parseActorIdentityFromSubject(rawPrincipalId) : void 0;
|
|
334
|
+
const principal = {
|
|
335
|
+
tenant_id: tenantId ?? (subject ? parseTenantIdFromSubject(subject) : ""),
|
|
336
|
+
id: rawPrincipalId ?? actorFromSubject?.id ?? "",
|
|
337
|
+
type: principalTypeFromClaims === "agent" || principalTypeFromClaims === "user" ? principalTypeFromClaims : actorFromSubject?.type ?? actorFromRawId?.type ?? "user"
|
|
338
|
+
};
|
|
339
|
+
if (principal.tenant_id.length === 0 || principal.id.length === 0 || principal.type.length === 0) {
|
|
340
|
+
return void 0;
|
|
341
|
+
}
|
|
342
|
+
const result = SessionCreatorPrincipalSchema.safeParse(principal);
|
|
343
|
+
return result.success ? result.data : void 0;
|
|
344
|
+
}
|
|
345
|
+
function parseCreatorPrincipalFromClaimsSafe(apiKey) {
|
|
346
|
+
const claims = parseJwtClaims(apiKey);
|
|
347
|
+
return claims ? parseCreatorPrincipalFromClaims(claims) : void 0;
|
|
348
|
+
}
|
|
237
349
|
function parseEventFrame(data) {
|
|
238
350
|
const result = TailFrameSchema.safeParse(data);
|
|
239
351
|
if (!result.success) {
|
|
@@ -248,6 +360,58 @@ function getEventData(event) {
|
|
|
248
360
|
}
|
|
249
361
|
return void 0;
|
|
250
362
|
}
|
|
363
|
+
function getCloseCode(event) {
|
|
364
|
+
if (event && typeof event === "object" && "code" in event) {
|
|
365
|
+
const code = event.code;
|
|
366
|
+
return typeof code === "number" ? code : void 0;
|
|
367
|
+
}
|
|
368
|
+
return void 0;
|
|
369
|
+
}
|
|
370
|
+
function getCloseReason(event) {
|
|
371
|
+
if (event && typeof event === "object" && "reason" in event) {
|
|
372
|
+
const reason = event.reason;
|
|
373
|
+
return typeof reason === "string" && reason.length > 0 ? reason : void 0;
|
|
374
|
+
}
|
|
375
|
+
return void 0;
|
|
376
|
+
}
|
|
377
|
+
function describeClose(code, reason) {
|
|
378
|
+
const codeText = `code ${typeof code === "number" ? code : "unknown"}`;
|
|
379
|
+
return reason ? `${codeText}, reason '${reason}'` : codeText;
|
|
380
|
+
}
|
|
381
|
+
async function waitForDelay(ms, signal) {
|
|
382
|
+
if (ms <= 0) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
await new Promise((resolve) => {
|
|
386
|
+
let settled = false;
|
|
387
|
+
const timeout = setTimeout(() => {
|
|
388
|
+
if (settled) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
settled = true;
|
|
392
|
+
if (signal) {
|
|
393
|
+
signal.removeEventListener("abort", onAbort);
|
|
394
|
+
}
|
|
395
|
+
resolve();
|
|
396
|
+
}, ms);
|
|
397
|
+
const onAbort = () => {
|
|
398
|
+
if (settled) {
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
settled = true;
|
|
402
|
+
clearTimeout(timeout);
|
|
403
|
+
signal?.removeEventListener("abort", onAbort);
|
|
404
|
+
resolve();
|
|
405
|
+
};
|
|
406
|
+
if (signal) {
|
|
407
|
+
if (signal.aborted) {
|
|
408
|
+
onAbort();
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
}
|
|
251
415
|
function agentFromActor(actor) {
|
|
252
416
|
if (actor.startsWith("agent:")) {
|
|
253
417
|
return actor.slice("agent:".length);
|
|
@@ -317,6 +481,7 @@ var StarciteSession = class {
|
|
|
317
481
|
var StarciteClient = class {
|
|
318
482
|
/** Normalized API base URL ending with `/v1`. */
|
|
319
483
|
baseUrl;
|
|
484
|
+
inferredCreatorPrincipal;
|
|
320
485
|
websocketBaseUrl;
|
|
321
486
|
fetchFn;
|
|
322
487
|
headers;
|
|
@@ -330,10 +495,9 @@ var StarciteClient = class {
|
|
|
330
495
|
this.fetchFn = options.fetch ?? defaultFetch;
|
|
331
496
|
this.headers = new Headers(options.headers);
|
|
332
497
|
if (options.apiKey !== void 0) {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
);
|
|
498
|
+
const authorization = formatAuthorizationHeader(options.apiKey);
|
|
499
|
+
this.headers.set("authorization", authorization);
|
|
500
|
+
this.inferredCreatorPrincipal = parseCreatorPrincipalFromClaimsSafe(authorization);
|
|
337
501
|
}
|
|
338
502
|
this.websocketFactory = options.websocketFactory ?? defaultWebSocketFactory;
|
|
339
503
|
}
|
|
@@ -354,7 +518,10 @@ var StarciteClient = class {
|
|
|
354
518
|
* Creates a new session and returns the raw session record.
|
|
355
519
|
*/
|
|
356
520
|
createSession(input = {}) {
|
|
357
|
-
const payload = CreateSessionInputSchema.parse(
|
|
521
|
+
const payload = CreateSessionInputSchema.parse({
|
|
522
|
+
...input,
|
|
523
|
+
creator_principal: input.creator_principal ?? this.inferredCreatorPrincipal
|
|
524
|
+
});
|
|
358
525
|
return this.request(
|
|
359
526
|
"/sessions",
|
|
360
527
|
{
|
|
@@ -419,77 +586,161 @@ var StarciteClient = class {
|
|
|
419
586
|
/**
|
|
420
587
|
* Opens a WebSocket tail stream and yields raw events.
|
|
421
588
|
*/
|
|
589
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: single-loop reconnect state machine is intentionally explicit for stream correctness.
|
|
422
590
|
async *tailRawEvents(sessionId, options = {}) {
|
|
423
|
-
const
|
|
424
|
-
const
|
|
425
|
-
|
|
591
|
+
const initialCursor = options.cursor ?? 0;
|
|
592
|
+
const follow = options.follow ?? true;
|
|
593
|
+
const reconnectEnabled = follow ? options.reconnect ?? true : false;
|
|
594
|
+
const reconnectDelayMs = options.reconnectDelayMs ?? DEFAULT_TAIL_RECONNECT_DELAY_MS;
|
|
595
|
+
if (!Number.isInteger(initialCursor) || initialCursor < 0) {
|
|
426
596
|
throw new StarciteError("tail() cursor must be a non-negative integer");
|
|
427
597
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
const authorization = this.headers.get("authorization");
|
|
433
|
-
if (authorization) {
|
|
434
|
-
websocketHeaders.set("authorization", authorization);
|
|
598
|
+
if (!Number.isFinite(reconnectDelayMs) || reconnectDelayMs < 0) {
|
|
599
|
+
throw new StarciteError(
|
|
600
|
+
"tail() reconnectDelayMs must be a non-negative number"
|
|
601
|
+
);
|
|
435
602
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
|
|
603
|
+
let cursor = initialCursor;
|
|
604
|
+
while (true) {
|
|
605
|
+
if (options.signal?.aborted) {
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
const wsUrl = `${this.websocketBaseUrl}/sessions/${encodeURIComponent(
|
|
609
|
+
sessionId
|
|
610
|
+
)}/tail?cursor=${cursor}`;
|
|
611
|
+
const websocketHeaders = new Headers();
|
|
612
|
+
const authorization = this.headers.get("authorization");
|
|
613
|
+
if (authorization) {
|
|
614
|
+
websocketHeaders.set("authorization", authorization);
|
|
615
|
+
}
|
|
616
|
+
let socket;
|
|
443
617
|
try {
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
618
|
+
socket = this.websocketFactory(
|
|
619
|
+
wsUrl,
|
|
620
|
+
hasAnyHeaders(websocketHeaders) ? {
|
|
621
|
+
headers: websocketHeaders
|
|
622
|
+
} : void 0
|
|
623
|
+
);
|
|
449
624
|
} catch (error) {
|
|
450
|
-
|
|
625
|
+
const rootCause = toError(error).message;
|
|
626
|
+
if (!reconnectEnabled || options.signal?.aborted) {
|
|
627
|
+
throw new StarciteConnectionError(
|
|
628
|
+
`Tail connection failed for session '${sessionId}': ${rootCause}`
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
await waitForDelay(reconnectDelayMs, options.signal);
|
|
632
|
+
continue;
|
|
451
633
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
)
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
634
|
+
const queue = new AsyncQueue();
|
|
635
|
+
let sawTransportError = false;
|
|
636
|
+
let closeCode;
|
|
637
|
+
let closeReason;
|
|
638
|
+
let abortRequested = false;
|
|
639
|
+
let catchUpTimer = null;
|
|
640
|
+
const resetCatchUpTimer = () => {
|
|
641
|
+
if (!follow) {
|
|
642
|
+
if (catchUpTimer) {
|
|
643
|
+
clearTimeout(catchUpTimer);
|
|
644
|
+
}
|
|
645
|
+
catchUpTimer = setTimeout(() => {
|
|
646
|
+
queue.close();
|
|
647
|
+
}, CATCH_UP_IDLE_MS);
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
const onMessage = (event) => {
|
|
651
|
+
try {
|
|
652
|
+
const parsed = parseEventFrame(getEventData(event));
|
|
653
|
+
cursor = Math.max(cursor, parsed.seq);
|
|
654
|
+
if (options.agent && agentFromActor(parsed.actor) !== options.agent) {
|
|
655
|
+
resetCatchUpTimer();
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
queue.push(parsed);
|
|
659
|
+
resetCatchUpTimer();
|
|
660
|
+
} catch (error) {
|
|
661
|
+
queue.fail(error);
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
const onError = () => {
|
|
665
|
+
sawTransportError = true;
|
|
666
|
+
if (catchUpTimer) {
|
|
667
|
+
clearTimeout(catchUpTimer);
|
|
668
|
+
}
|
|
669
|
+
queue.close();
|
|
670
|
+
};
|
|
671
|
+
const onClose = (event) => {
|
|
672
|
+
closeCode = getCloseCode(event);
|
|
673
|
+
closeReason = getCloseReason(event);
|
|
674
|
+
if (catchUpTimer) {
|
|
675
|
+
clearTimeout(catchUpTimer);
|
|
676
|
+
}
|
|
677
|
+
queue.close();
|
|
678
|
+
};
|
|
679
|
+
const onAbort = () => {
|
|
680
|
+
abortRequested = true;
|
|
681
|
+
if (catchUpTimer) {
|
|
682
|
+
clearTimeout(catchUpTimer);
|
|
683
|
+
}
|
|
684
|
+
queue.close();
|
|
685
|
+
socket.close(NORMAL_WEBSOCKET_CLOSE_CODE, "aborted");
|
|
686
|
+
};
|
|
687
|
+
const onOpen = () => {
|
|
688
|
+
resetCatchUpTimer();
|
|
689
|
+
};
|
|
690
|
+
socket.addEventListener("open", onOpen);
|
|
691
|
+
socket.addEventListener("message", onMessage);
|
|
692
|
+
socket.addEventListener("error", onError);
|
|
693
|
+
socket.addEventListener("close", onClose);
|
|
694
|
+
if (options.signal) {
|
|
695
|
+
if (options.signal.aborted) {
|
|
696
|
+
onAbort();
|
|
697
|
+
} else {
|
|
698
|
+
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
699
|
+
}
|
|
475
700
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
701
|
+
let iterationError = null;
|
|
702
|
+
try {
|
|
703
|
+
while (true) {
|
|
704
|
+
const next = await queue.next();
|
|
705
|
+
if (next.done) {
|
|
706
|
+
break;
|
|
707
|
+
}
|
|
708
|
+
yield next.value;
|
|
709
|
+
}
|
|
710
|
+
} catch (error) {
|
|
711
|
+
iterationError = toError(error);
|
|
712
|
+
} finally {
|
|
713
|
+
if (catchUpTimer) {
|
|
714
|
+
clearTimeout(catchUpTimer);
|
|
715
|
+
}
|
|
716
|
+
socket.removeEventListener("open", onOpen);
|
|
717
|
+
socket.removeEventListener("message", onMessage);
|
|
718
|
+
socket.removeEventListener("error", onError);
|
|
719
|
+
socket.removeEventListener("close", onClose);
|
|
720
|
+
if (options.signal) {
|
|
721
|
+
options.signal.removeEventListener("abort", onAbort);
|
|
482
722
|
}
|
|
483
|
-
|
|
723
|
+
socket.close(NORMAL_WEBSOCKET_CLOSE_CODE, "finished");
|
|
484
724
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
725
|
+
if (iterationError) {
|
|
726
|
+
throw iterationError;
|
|
727
|
+
}
|
|
728
|
+
if (abortRequested || options.signal?.aborted || !follow) {
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
const gracefullyClosed = !sawTransportError && closeCode === NORMAL_WEBSOCKET_CLOSE_CODE;
|
|
732
|
+
if (gracefullyClosed) {
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
if (!reconnectEnabled) {
|
|
736
|
+
throw new StarciteConnectionError(
|
|
737
|
+
`Tail connection dropped for session '${sessionId}' (${describeClose(
|
|
738
|
+
closeCode,
|
|
739
|
+
closeReason
|
|
740
|
+
)})`
|
|
741
|
+
);
|
|
491
742
|
}
|
|
492
|
-
|
|
743
|
+
await waitForDelay(reconnectDelayMs, options.signal);
|
|
493
744
|
}
|
|
494
745
|
}
|
|
495
746
|
/**
|