@paneui/core 0.0.4 → 0.0.6

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/client.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import type { ArtifactRecord, ArtifactSummary, ArtifactType, ArtifactVersion, CreateArtifactResponse, CreateSessionRequest, CreateSessionResponse, EventsPage, FeedbackPage, FeedbackSubmission, FeedbackType, KeyInfo, PaneEvent, SessionState, TasteInfo } from "./types.js";
1
+ import type { ArtifactRecord, ArtifactSummary, ArtifactType, ArtifactVersion, CreateArtifactResponse, CreateSessionRequest, CreateSessionResponse, EventsPage, FeedbackPage, FeedbackSubmission, FeedbackType, KeyInfo, MintParticipantResponse, PaneEvent, ParticipantsList, SessionState, SessionsPage, TasteInfo } from "./types.js";
2
+ import type { ListSessionsQuery } from "./schemas.js";
2
3
  export interface ClientOptions {
3
4
  /** Relay base URL, e.g. https://pane.example.com. Trailing slash is trimmed. */
4
5
  url: string;
@@ -202,6 +203,47 @@ export declare class PaneClient {
202
203
  limit?: number;
203
204
  before?: string;
204
205
  }): Promise<FeedbackPage>;
206
+ /**
207
+ * GET /v1/sessions — list the calling agent's sessions. Default filter is
208
+ * `status=open` (effective status — respects expiresAt). Response items
209
+ * carry NO secrets: no participant token plaintext, no callback URL, no
210
+ * metadata or input_data. Use `participant_id` from the list as the handle
211
+ * for {@link revokeParticipant}; use {@link mintParticipant} to issue a
212
+ * fresh URL when the original was lost.
213
+ */
214
+ listSessions(opts?: ListSessionsQuery): Promise<SessionsPage>;
215
+ /**
216
+ * GET /v1/sessions/:id/participants — list every participant on one
217
+ * session (active and revoked). Bounded by MAX_PARTICIPANTS_PER_SESSION
218
+ * on the relay, so the full list is returned with no pagination.
219
+ * Use this to find the `participant_id` you need to pass to
220
+ * {@link revokeParticipant}, or to audit revoked rows.
221
+ */
222
+ listParticipants(sessionId: string): Promise<ParticipantsList>;
223
+ /**
224
+ * POST /v1/sessions/:id/participants — mint a fresh participant URL for an
225
+ * existing session. The one-shot recovery primitive when the original URL
226
+ * was dropped: the session keeps its event log, artifact pin, and created_at.
227
+ * v1 supports `kind: "human"` only.
228
+ *
229
+ * The plaintext token is returned EXACTLY ONCE in the response — the relay
230
+ * stores only the hash. Save the response (e.g. pipe to a JSONL log) before
231
+ * delivering the URL to the human.
232
+ */
233
+ mintParticipant(sessionId: string, opts?: {
234
+ kind?: "human";
235
+ }): Promise<MintParticipantResponse>;
236
+ /**
237
+ * DELETE /v1/sessions/:id/participants/:participant_id — revoke a single
238
+ * participant URL. The session's other participants (and the agent's own
239
+ * WebSocket) are untouched. Idempotent: revoking an unknown or already-
240
+ * revoked participant returns 204. The agent participant cannot be revoked
241
+ * via this endpoint — use {@link deleteSession} instead.
242
+ *
243
+ * Existing WebSocket connections held under the revoked token are NOT
244
+ * actively kicked in v1; new HTTP and WS connections are refused.
245
+ */
246
+ revokeParticipant(sessionId: string, participantId: string): Promise<void>;
205
247
  /**
206
248
  * DELETE /v1/sessions/:id — close/delete a session. Idempotent on the relay
207
249
  * side (an already-closed session still returns 204 with no body).
@@ -215,4 +257,156 @@ export declare class PaneClient {
215
257
  * instead of swallowing it.
216
258
  */
217
259
  deleteArtifact(idOrSlug: string): Promise<void>;
260
+ /**
261
+ * Upload a blob to the relay. Returns a `BlobRef` that can be referenced
262
+ * in event payloads (the relay's `format: pane-blob-id` schema vocab
263
+ * validates the id) or in `pane create --input-data`.
264
+ *
265
+ * Scope defaults to "agent" (reusable across the agent's sessions). For
266
+ * `scope: "session"` pass `sessionId`; for `scope: "artifact"` pass
267
+ * `artifactId`. The agent must own the referenced session / artifact;
268
+ * cross-tenant attempts return blob_not_found.
269
+ *
270
+ * MIME is inferred from `mime` if supplied; otherwise the relay sniffs
271
+ * leading bytes and may reject with mime_mismatch / mime_disallowed.
272
+ *
273
+ * Backed by the relay's multipart `POST /v1/blobs` (the fallback path).
274
+ * For large uploads (>1 MB on hosted Azure) call `presignBlob()` +
275
+ * `confirmBlob()` instead — those use SAS direct-to-storage and don't
276
+ * stream bytes through the relay.
277
+ */
278
+ uploadBlob(file: Blob | Buffer | Uint8Array, opts?: UploadBlobOptions): Promise<BlobRef>;
279
+ /** GET /v1/blobs/:id — download bytes as an ArrayBuffer. */
280
+ downloadBlob(blobId: string): Promise<ArrayBuffer>;
281
+ /**
282
+ * GET a blob's metadata only — useful before downloading large blobs, or
283
+ * for `pane blob show <id>` which doesn't want the bytes. Returns the full
284
+ * BlobRef (the same shape POST /v1/blobs returns): id, scope, mime, size,
285
+ * sha256, filename, width, height, status, scope FKs, timestamps.
286
+ *
287
+ * Backed by GET /v1/blobs/:id/metadata which serves the JSON BlobRef
288
+ * without streaming the bytes — cheap on the relay and avoids the
289
+ * encrypt-at-rest decrypt cost when only the metadata is needed.
290
+ */
291
+ getBlob(blobId: string): Promise<BlobRef>;
292
+ /** DELETE /v1/blobs/:id — soft-delete (idempotent). */
293
+ deleteBlob(blobId: string): Promise<{
294
+ deleted: true;
295
+ }>;
296
+ /**
297
+ * Mint a `/b/<token>` capability URL for `blobId`. Default TTL is set by
298
+ * the relay (24h agent, session-TTL session, 30d artifact). `once: true`
299
+ * tokens self-delete on first GET.
300
+ */
301
+ mintBlobToken(blobId: string, opts?: {
302
+ ttlSeconds?: number;
303
+ once?: boolean;
304
+ }): Promise<BlobTokenMintResponse>;
305
+ /** Revoke a previously-minted token. Idempotent. */
306
+ revokeBlobToken(blobId: string, tokenId: string): Promise<{
307
+ token_id: string;
308
+ revoked: true;
309
+ }>;
310
+ /**
311
+ * GET /v1/blobs — list YOUR agent's non-deleted blobs (newest first).
312
+ * Paginated via opaque cursor: when `next_cursor` is non-null, pass it
313
+ * back as `cursor` on the next call.
314
+ */
315
+ listBlobs(opts?: ListBlobsOptions): Promise<{
316
+ items: BlobRef[];
317
+ next_cursor: string | null;
318
+ }>;
319
+ /**
320
+ * GET /v1/blobs/:id/tokens — enumerate the capability tokens minted
321
+ * against one blob, including revoked rows (for audit). The plaintext
322
+ * token is NEVER returned — it isn't stored, only its sha256 is.
323
+ */
324
+ listBlobTokens(blobId: string): Promise<BlobTokenListResponse>;
325
+ /**
326
+ * Issue a presigned PUT URL for direct-to-storage upload. Returns the
327
+ * upload URL + the blob_id (already reserved in the relay's DB with
328
+ * status=pending) + expiry. After PUTting the bytes to the URL, call
329
+ * `confirmBlob(blob_id)` to finalise.
330
+ *
331
+ * Filesystem backend returns 501 not_implemented — use uploadBlob()
332
+ * (multipart fallback) instead. Azure backend returns a SAS URL.
333
+ */
334
+ presignBlob(opts: PresignBlobOptions): Promise<{
335
+ blob_id: string;
336
+ upload_url: string;
337
+ expires_at: string;
338
+ }>;
339
+ /** Finalise a presigned upload — relay HEADs the bytes, verifies, flips ready. */
340
+ confirmBlob(blobId: string): Promise<BlobRef>;
341
+ }
342
+ /** Per-blob metadata as returned by `POST /v1/blobs` and friends. */
343
+ export interface BlobRef {
344
+ blob_id: string;
345
+ scope: "agent" | "session" | "artifact";
346
+ mime: string;
347
+ size: number;
348
+ sha256: string;
349
+ url?: string;
350
+ width?: number | null;
351
+ height?: number | null;
352
+ filename?: string | null;
353
+ status?: string;
354
+ session_id?: string | null;
355
+ artifact_id?: string | null;
356
+ created_at?: string;
357
+ confirmed_at?: string | null;
358
+ deleted_at?: string | null;
359
+ }
360
+ export interface UploadBlobOptions {
361
+ scope?: "agent" | "session" | "artifact";
362
+ sessionId?: string;
363
+ artifactId?: string;
364
+ /** Declared Content-Type. Defaults to `application/octet-stream`. The
365
+ * relay sniffs leading bytes and may reject with `mime_mismatch`. */
366
+ mime?: string;
367
+ /** Optional display name (the relay records it for UX; never a path component). */
368
+ filename?: string;
369
+ }
370
+ export interface PresignBlobOptions {
371
+ mime: string;
372
+ size: number;
373
+ sha256: string;
374
+ scope?: "agent" | "session" | "artifact";
375
+ sessionId?: string;
376
+ artifactId?: string;
377
+ filename?: string;
378
+ }
379
+ export interface BlobTokenMintResponse {
380
+ token_id: string;
381
+ token: string;
382
+ token_prefix: string;
383
+ url: string;
384
+ expires_at: string;
385
+ once: boolean;
386
+ }
387
+ /** Options for `listBlobs()` — opaque cursor + page-size knob. */
388
+ export interface ListBlobsOptions {
389
+ /** Opaque pagination cursor from a prior `next_cursor`. */
390
+ cursor?: string;
391
+ /** Page size; relay clamps to 1..100. Defaults to the relay default (50). */
392
+ limit?: number;
393
+ }
394
+ /** One row in the response from `listBlobTokens()`. */
395
+ export interface BlobTokenAuditEntry {
396
+ token_id: string;
397
+ token_prefix: string;
398
+ expires_at: string;
399
+ once: boolean;
400
+ created_at: string;
401
+ last_used_at: string | null;
402
+ use_count: number;
403
+ /** Non-null when the token has been revoked. Expired-but-unrevoked rows
404
+ * carry `revoked_at: null` and an `expires_at` in the past — both are
405
+ * useful for audit. */
406
+ revoked_at: string | null;
407
+ }
408
+ /** Shape returned by `listBlobTokens()`. */
409
+ export interface BlobTokenListResponse {
410
+ blob_id: string;
411
+ items: BlobTokenAuditEntry[];
218
412
  }
package/dist/client.js CHANGED
@@ -125,6 +125,7 @@ export class PaneClient {
125
125
  async createSession(req) {
126
126
  const r = await this.call("POST", "/v1/sessions", {
127
127
  artifact: req.artifact,
128
+ title: req.title,
128
129
  input_data: req.input_data,
129
130
  participants: req.participants,
130
131
  ttl: req.ttl,
@@ -341,6 +342,74 @@ export class PaneClient {
341
342
  this.fail(r);
342
343
  return this.asObject(r);
343
344
  }
345
+ /**
346
+ * GET /v1/sessions — list the calling agent's sessions. Default filter is
347
+ * `status=open` (effective status — respects expiresAt). Response items
348
+ * carry NO secrets: no participant token plaintext, no callback URL, no
349
+ * metadata or input_data. Use `participant_id` from the list as the handle
350
+ * for {@link revokeParticipant}; use {@link mintParticipant} to issue a
351
+ * fresh URL when the original was lost.
352
+ */
353
+ async listSessions(opts = {}) {
354
+ const q = new URLSearchParams();
355
+ if (opts.status !== undefined)
356
+ q.set("status", opts.status);
357
+ if (opts.limit !== undefined)
358
+ q.set("limit", String(opts.limit));
359
+ if (opts.cursor !== undefined && opts.cursor !== "")
360
+ q.set("cursor", opts.cursor);
361
+ if (opts.artifact_id !== undefined && opts.artifact_id !== "")
362
+ q.set("artifact_id", opts.artifact_id);
363
+ const qs = q.toString();
364
+ const r = await this.call("GET", `/v1/sessions${qs ? "?" + qs : ""}`);
365
+ if (!r.ok)
366
+ this.fail(r);
367
+ return this.asObject(r);
368
+ }
369
+ /**
370
+ * GET /v1/sessions/:id/participants — list every participant on one
371
+ * session (active and revoked). Bounded by MAX_PARTICIPANTS_PER_SESSION
372
+ * on the relay, so the full list is returned with no pagination.
373
+ * Use this to find the `participant_id` you need to pass to
374
+ * {@link revokeParticipant}, or to audit revoked rows.
375
+ */
376
+ async listParticipants(sessionId) {
377
+ const r = await this.call("GET", `/v1/sessions/${encodeURIComponent(sessionId)}/participants`);
378
+ if (!r.ok)
379
+ this.fail(r);
380
+ return this.asObject(r);
381
+ }
382
+ /**
383
+ * POST /v1/sessions/:id/participants — mint a fresh participant URL for an
384
+ * existing session. The one-shot recovery primitive when the original URL
385
+ * was dropped: the session keeps its event log, artifact pin, and created_at.
386
+ * v1 supports `kind: "human"` only.
387
+ *
388
+ * The plaintext token is returned EXACTLY ONCE in the response — the relay
389
+ * stores only the hash. Save the response (e.g. pipe to a JSONL log) before
390
+ * delivering the URL to the human.
391
+ */
392
+ async mintParticipant(sessionId, opts = {}) {
393
+ const r = await this.call("POST", `/v1/sessions/${encodeURIComponent(sessionId)}/participants`, { kind: opts.kind ?? "human" });
394
+ if (!r.ok)
395
+ this.fail(r);
396
+ return this.asObject(r);
397
+ }
398
+ /**
399
+ * DELETE /v1/sessions/:id/participants/:participant_id — revoke a single
400
+ * participant URL. The session's other participants (and the agent's own
401
+ * WebSocket) are untouched. Idempotent: revoking an unknown or already-
402
+ * revoked participant returns 204. The agent participant cannot be revoked
403
+ * via this endpoint — use {@link deleteSession} instead.
404
+ *
405
+ * Existing WebSocket connections held under the revoked token are NOT
406
+ * actively kicked in v1; new HTTP and WS connections are refused.
407
+ */
408
+ async revokeParticipant(sessionId, participantId) {
409
+ const r = await this.call("DELETE", `/v1/sessions/${encodeURIComponent(sessionId)}/participants/${encodeURIComponent(participantId)}`);
410
+ if (!r.ok)
411
+ this.fail(r);
412
+ }
344
413
  /**
345
414
  * DELETE /v1/sessions/:id — close/delete a session. Idempotent on the relay
346
415
  * side (an already-closed session still returns 204 with no body).
@@ -362,4 +431,212 @@ export class PaneClient {
362
431
  if (!r.ok)
363
432
  this.fail(r);
364
433
  }
434
+ // ------------------------------------------------------------------------
435
+ // Blobs (v0.1.0). Three-scope binary attachments with multipart upload.
436
+ // See proposal pane#152 for the full design.
437
+ // ------------------------------------------------------------------------
438
+ /**
439
+ * Upload a blob to the relay. Returns a `BlobRef` that can be referenced
440
+ * in event payloads (the relay's `format: pane-blob-id` schema vocab
441
+ * validates the id) or in `pane create --input-data`.
442
+ *
443
+ * Scope defaults to "agent" (reusable across the agent's sessions). For
444
+ * `scope: "session"` pass `sessionId`; for `scope: "artifact"` pass
445
+ * `artifactId`. The agent must own the referenced session / artifact;
446
+ * cross-tenant attempts return blob_not_found.
447
+ *
448
+ * MIME is inferred from `mime` if supplied; otherwise the relay sniffs
449
+ * leading bytes and may reject with mime_mismatch / mime_disallowed.
450
+ *
451
+ * Backed by the relay's multipart `POST /v1/blobs` (the fallback path).
452
+ * For large uploads (>1 MB on hosted Azure) call `presignBlob()` +
453
+ * `confirmBlob()` instead — those use SAS direct-to-storage and don't
454
+ * stream bytes through the relay.
455
+ */
456
+ async uploadBlob(file, opts = {}) {
457
+ const fd = new FormData();
458
+ let blob;
459
+ if (file instanceof Blob) {
460
+ blob = file;
461
+ }
462
+ else {
463
+ // Buffer / Uint8Array path — wrap in a Blob with the declared MIME.
464
+ // Copy into a freshly allocated Uint8Array so the buffer type
465
+ // narrows from `ArrayBufferLike` (which includes SharedArrayBuffer)
466
+ // to `ArrayBuffer` specifically — the Blob constructor accepts only
467
+ // the latter under @types/node ≥25 + TS ≥5.7's generic narrowing of
468
+ // Uint8Array<TArrayBuffer>. `new Uint8Array(length)` returns
469
+ // `Uint8Array<ArrayBuffer>` by construction, satisfying BlobPart
470
+ // without a type cast. The extra copy is one walk over the bytes —
471
+ // negligible vs the network upload that follows.
472
+ const src = file instanceof Uint8Array ? file : new Uint8Array(file);
473
+ const u8 = new Uint8Array(src.byteLength);
474
+ u8.set(src);
475
+ blob = new Blob([u8], {
476
+ type: opts.mime ?? "application/octet-stream",
477
+ });
478
+ }
479
+ fd.set("file", blob, opts.filename ?? "blob");
480
+ if (opts.scope)
481
+ fd.set("scope", opts.scope);
482
+ if (opts.sessionId)
483
+ fd.set("session_id", opts.sessionId);
484
+ if (opts.artifactId)
485
+ fd.set("artifact_id", opts.artifactId);
486
+ if (opts.filename)
487
+ fd.set("filename", opts.filename);
488
+ const url = this.base + "/v1/blobs";
489
+ let res;
490
+ try {
491
+ res = await this.fetchImpl(url, {
492
+ method: "POST",
493
+ headers: {
494
+ authorization: "Bearer " + this.apiKey,
495
+ ...(this.cliVersion ? { "x-pane-cli-version": this.cliVersion } : {}),
496
+ },
497
+ body: fd,
498
+ });
499
+ }
500
+ catch (e) {
501
+ const msg = e instanceof Error ? e.message : String(e);
502
+ throw new PaneApiError(0, "fetch_error", msg);
503
+ }
504
+ const text = await res.text().catch(() => "");
505
+ let data;
506
+ try {
507
+ data = text ? JSON.parse(text) : null;
508
+ }
509
+ catch {
510
+ throw new PaneApiError(res.status, "non_json_response", `relay returned a non-JSON body (status ${res.status})`);
511
+ }
512
+ if (!res.ok) {
513
+ this.fail({ ok: false, status: res.status, data });
514
+ }
515
+ return data;
516
+ }
517
+ /** GET /v1/blobs/:id — download bytes as an ArrayBuffer. */
518
+ async downloadBlob(blobId) {
519
+ const url = this.base + "/v1/blobs/" + encodeURIComponent(blobId);
520
+ const res = await this.fetchImpl(url, {
521
+ method: "GET",
522
+ headers: {
523
+ authorization: "Bearer " + this.apiKey,
524
+ ...(this.cliVersion ? { "x-pane-cli-version": this.cliVersion } : {}),
525
+ },
526
+ });
527
+ if (!res.ok) {
528
+ const text = await res.text().catch(() => "");
529
+ let data;
530
+ try {
531
+ data = text ? JSON.parse(text) : null;
532
+ }
533
+ catch {
534
+ data = null;
535
+ }
536
+ this.fail({ ok: false, status: res.status, data });
537
+ }
538
+ return res.arrayBuffer();
539
+ }
540
+ /**
541
+ * GET a blob's metadata only — useful before downloading large blobs, or
542
+ * for `pane blob show <id>` which doesn't want the bytes. Returns the full
543
+ * BlobRef (the same shape POST /v1/blobs returns): id, scope, mime, size,
544
+ * sha256, filename, width, height, status, scope FKs, timestamps.
545
+ *
546
+ * Backed by GET /v1/blobs/:id/metadata which serves the JSON BlobRef
547
+ * without streaming the bytes — cheap on the relay and avoids the
548
+ * encrypt-at-rest decrypt cost when only the metadata is needed.
549
+ */
550
+ async getBlob(blobId) {
551
+ const r = await this.call("GET", "/v1/blobs/" + encodeURIComponent(blobId) + "/metadata");
552
+ if (!r.ok)
553
+ this.fail(r);
554
+ return this.asObject(r);
555
+ }
556
+ /** DELETE /v1/blobs/:id — soft-delete (idempotent). */
557
+ async deleteBlob(blobId) {
558
+ const r = await this.call("DELETE", "/v1/blobs/" + encodeURIComponent(blobId));
559
+ if (!r.ok)
560
+ this.fail(r);
561
+ return { deleted: true };
562
+ }
563
+ /**
564
+ * Mint a `/b/<token>` capability URL for `blobId`. Default TTL is set by
565
+ * the relay (24h agent, session-TTL session, 30d artifact). `once: true`
566
+ * tokens self-delete on first GET.
567
+ */
568
+ async mintBlobToken(blobId, opts = {}) {
569
+ const r = await this.call("POST", "/v1/blobs/" + encodeURIComponent(blobId) + "/tokens", { ttl_seconds: opts.ttlSeconds, once: opts.once });
570
+ if (!r.ok)
571
+ this.fail(r);
572
+ return r.data;
573
+ }
574
+ /** Revoke a previously-minted token. Idempotent. */
575
+ async revokeBlobToken(blobId, tokenId) {
576
+ const r = await this.call("DELETE", "/v1/blobs/" +
577
+ encodeURIComponent(blobId) +
578
+ "/tokens/" +
579
+ encodeURIComponent(tokenId));
580
+ if (!r.ok)
581
+ this.fail(r);
582
+ return r.data;
583
+ }
584
+ /**
585
+ * GET /v1/blobs — list YOUR agent's non-deleted blobs (newest first).
586
+ * Paginated via opaque cursor: when `next_cursor` is non-null, pass it
587
+ * back as `cursor` on the next call.
588
+ */
589
+ async listBlobs(opts = {}) {
590
+ const params = new URLSearchParams();
591
+ if (opts.cursor !== undefined)
592
+ params.set("cursor", opts.cursor);
593
+ if (opts.limit !== undefined)
594
+ params.set("limit", String(opts.limit));
595
+ const qs = params.toString();
596
+ const r = await this.call("GET", "/v1/blobs" + (qs ? "?" + qs : ""));
597
+ if (!r.ok)
598
+ this.fail(r);
599
+ return r.data;
600
+ }
601
+ /**
602
+ * GET /v1/blobs/:id/tokens — enumerate the capability tokens minted
603
+ * against one blob, including revoked rows (for audit). The plaintext
604
+ * token is NEVER returned — it isn't stored, only its sha256 is.
605
+ */
606
+ async listBlobTokens(blobId) {
607
+ const r = await this.call("GET", "/v1/blobs/" + encodeURIComponent(blobId) + "/tokens");
608
+ if (!r.ok)
609
+ this.fail(r);
610
+ return r.data;
611
+ }
612
+ /**
613
+ * Issue a presigned PUT URL for direct-to-storage upload. Returns the
614
+ * upload URL + the blob_id (already reserved in the relay's DB with
615
+ * status=pending) + expiry. After PUTting the bytes to the URL, call
616
+ * `confirmBlob(blob_id)` to finalise.
617
+ *
618
+ * Filesystem backend returns 501 not_implemented — use uploadBlob()
619
+ * (multipart fallback) instead. Azure backend returns a SAS URL.
620
+ */
621
+ async presignBlob(opts) {
622
+ const r = await this.call("POST", "/v1/blobs/presign", {
623
+ mime: opts.mime,
624
+ size: opts.size,
625
+ sha256: opts.sha256,
626
+ scope: opts.scope,
627
+ session_id: opts.sessionId,
628
+ artifact_id: opts.artifactId,
629
+ filename: opts.filename,
630
+ });
631
+ if (!r.ok)
632
+ this.fail(r);
633
+ return r.data;
634
+ }
635
+ /** Finalise a presigned upload — relay HEADs the bytes, verifies, flips ready. */
636
+ async confirmBlob(blobId) {
637
+ const r = await this.call("POST", "/v1/blobs/" + encodeURIComponent(blobId) + "/confirm");
638
+ if (!r.ok)
639
+ this.fail(r);
640
+ return r.data;
641
+ }
365
642
  }
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  export { PaneClient, PaneApiError } from "./client.js";
2
- export type { ClientOptions, RelayResponse, CreateArtifactRequest, CreateArtifactVersionRequest, PatchArtifactMetadataRequest, } from "./client.js";
2
+ export type { ClientOptions, RelayResponse, CreateArtifactRequest, CreateArtifactVersionRequest, PatchArtifactMetadataRequest, BlobRef, UploadBlobOptions, PresignBlobOptions, BlobTokenMintResponse, ListBlobsOptions, BlobTokenAuditEntry, BlobTokenListResponse, } from "./client.js";
3
3
  export { openStream } from "./stream.js";
4
4
  export type { OpenStreamOptions, StreamHandlers, StreamHandle, } from "./stream.js";
5
5
  export { registerAgent } from "./register.js";
6
6
  export type { RegisterAgentOptions, RegisterAgentResult } from "./register.js";
7
- export { artifactSchema, callbackSchema, createSessionSchema, artifactTypeSchema, createArtifactSchema, createArtifactVersionSchema, patchArtifactMetadataSchema, feedbackTypeSchema, submitFeedbackSchema, } from "./schemas.js";
8
- export type { CreateSessionInput } from "./schemas.js";
7
+ export { artifactSchema, callbackSchema, createSessionSchema, artifactTypeSchema, createArtifactSchema, createArtifactVersionSchema, patchArtifactMetadataSchema, feedbackTypeSchema, submitFeedbackSchema, listSessionsStatusSchema, listSessionsQuerySchema, mintParticipantSchema, } from "./schemas.js";
8
+ export type { CreateSessionInput, ListSessionsStatus, ListSessionsQuery, MintParticipantInput, } from "./schemas.js";
9
9
  export { MAX_EVENT_TYPE_LENGTH, MAX_IDEMPOTENCY_KEY_LENGTH, MAX_RESPONSE_SNIPPET_LENGTH, MAX_FRAME_SNIPPET_LENGTH, } from "./limits.js";
10
- export type { AuthorKind, PaneEvent, Artifact, ArtifactType, ArtifactVersion, ArtifactRecord, ArtifactSummary, CreateArtifactResponse, KeyInfo, TasteInfo, FeedbackType, FeedbackSubmission, FeedbackRecord, FeedbackPage, Callback, CreateSessionRequest, CreateSessionResponse, SessionState, EventsPage, RelayError, } from "./types.js";
10
+ export type { AuthorKind, PaneEvent, Artifact, ArtifactType, ArtifactVersion, ArtifactRecord, ArtifactSummary, CreateArtifactResponse, KeyInfo, TasteInfo, FeedbackType, FeedbackSubmission, FeedbackRecord, FeedbackPage, Callback, CreateSessionRequest, CreateSessionResponse, SessionState, EventsPage, ParticipantSummary, ParticipantsList, SessionSummary, SessionsPage, MintParticipantResponse, RelayError, } from "./types.js";
package/dist/index.js CHANGED
@@ -3,5 +3,5 @@
3
3
  export { PaneClient, PaneApiError } from "./client.js";
4
4
  export { openStream } from "./stream.js";
5
5
  export { registerAgent } from "./register.js";
6
- export { artifactSchema, callbackSchema, createSessionSchema, artifactTypeSchema, createArtifactSchema, createArtifactVersionSchema, patchArtifactMetadataSchema, feedbackTypeSchema, submitFeedbackSchema, } from "./schemas.js";
6
+ export { artifactSchema, callbackSchema, createSessionSchema, artifactTypeSchema, createArtifactSchema, createArtifactVersionSchema, patchArtifactMetadataSchema, feedbackTypeSchema, submitFeedbackSchema, listSessionsStatusSchema, listSessionsQuerySchema, mintParticipantSchema, } from "./schemas.js";
7
7
  export { MAX_EVENT_TYPE_LENGTH, MAX_IDEMPOTENCY_KEY_LENGTH, MAX_RESPONSE_SNIPPET_LENGTH, MAX_FRAME_SNIPPET_LENGTH, } from "./limits.js";
package/dist/schemas.d.ts CHANGED
@@ -1,211 +1,108 @@
1
1
  import { z } from "zod";
2
- export declare const artifactTypeSchema: z.ZodEnum<["html-inline", "html-ref"]>;
3
- export declare const artifactSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
2
+ export declare const artifactTypeSchema: z.ZodEnum<{
3
+ "html-inline": "html-inline";
4
+ "html-ref": "html-ref";
5
+ }>;
6
+ export declare const artifactSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
4
7
  type: z.ZodLiteral<"html-inline">;
5
8
  source: z.ZodString;
6
- }, "strip", z.ZodTypeAny, {
7
- type: "html-inline";
8
- source: string;
9
- }, {
10
- type: "html-inline";
11
- source: string;
12
- }>, z.ZodObject<{
9
+ }, z.core.$strip>, z.ZodObject<{
13
10
  type: z.ZodLiteral<"html-ref">;
14
11
  source: z.ZodString;
15
- }, "strip", z.ZodTypeAny, {
16
- type: "html-ref";
17
- source: string;
18
- }, {
19
- type: "html-ref";
20
- source: string;
21
- }>]>;
12
+ }, z.core.$strip>], "type">;
22
13
  export declare const callbackSchema: z.ZodObject<{
23
14
  url: z.ZodString;
24
- events: z.ZodArray<z.ZodString, "many">;
15
+ events: z.ZodArray<z.ZodString>;
25
16
  secret: z.ZodString;
26
- }, "strip", z.ZodTypeAny, {
27
- url: string;
28
- events: string[];
29
- secret: string;
30
- }, {
31
- url: string;
32
- events: string[];
33
- secret: string;
34
- }>;
17
+ }, z.core.$strip>;
35
18
  export declare const createSessionSchema: z.ZodObject<{
36
- artifact: z.ZodEffects<z.ZodUnion<[z.ZodObject<{
19
+ artifact: z.ZodUnion<readonly [z.ZodObject<{
37
20
  id: z.ZodString;
38
21
  version: z.ZodOptional<z.ZodNumber>;
39
- }, "strip", z.ZodTypeAny, {
40
- id: string;
41
- version?: number | undefined;
42
- }, {
43
- id: string;
44
- version?: number | undefined;
45
- }>, z.ZodObject<{
22
+ }, z.core.$strip>, z.ZodObject<{
46
23
  source: z.ZodString;
47
- type: z.ZodEnum<["html-inline", "html-ref"]>;
24
+ type: z.ZodEnum<{
25
+ "html-inline": "html-inline";
26
+ "html-ref": "html-ref";
27
+ }>;
48
28
  event_schema: z.ZodOptional<z.ZodUnknown>;
49
- }, "strip", z.ZodTypeAny, {
50
- type: "html-inline" | "html-ref";
51
- source: string;
52
- event_schema?: unknown;
53
- }, {
54
- type: "html-inline" | "html-ref";
55
- source: string;
56
- event_schema?: unknown;
57
- }>]>, {
58
- type: "html-inline" | "html-ref";
59
- source: string;
60
- event_schema?: unknown;
61
- } | {
62
- id: string;
63
- version?: number | undefined;
64
- }, {
65
- type: "html-inline" | "html-ref";
66
- source: string;
67
- event_schema?: unknown;
68
- } | {
69
- id: string;
70
- version?: number | undefined;
71
- }>;
29
+ input_schema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
30
+ }, z.core.$strip>]>;
72
31
  input_data: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
73
32
  participants: z.ZodOptional<z.ZodObject<{
74
33
  humans: z.ZodNumber;
75
- }, "strip", z.ZodTypeAny, {
76
- humans: number;
77
- }, {
78
- humans: number;
79
- }>>;
34
+ }, z.core.$strip>>;
80
35
  ttl: z.ZodOptional<z.ZodNumber>;
81
36
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
82
37
  callback: z.ZodOptional<z.ZodObject<{
83
38
  url: z.ZodString;
84
- events: z.ZodArray<z.ZodString, "many">;
39
+ events: z.ZodArray<z.ZodString>;
85
40
  secret: z.ZodString;
86
- }, "strip", z.ZodTypeAny, {
87
- url: string;
88
- events: string[];
89
- secret: string;
90
- }, {
91
- url: string;
92
- events: string[];
93
- secret: string;
94
- }>>;
95
- }, "strip", z.ZodTypeAny, {
96
- artifact: {
97
- type: "html-inline" | "html-ref";
98
- source: string;
99
- event_schema?: unknown;
100
- } | {
101
- id: string;
102
- version?: number | undefined;
103
- };
104
- input_data?: Record<string, unknown> | undefined;
105
- participants?: {
106
- humans: number;
107
- } | undefined;
108
- ttl?: number | undefined;
109
- metadata?: Record<string, unknown> | undefined;
110
- callback?: {
111
- url: string;
112
- events: string[];
113
- secret: string;
114
- } | undefined;
115
- }, {
116
- artifact: {
117
- type: "html-inline" | "html-ref";
118
- source: string;
119
- event_schema?: unknown;
120
- } | {
121
- id: string;
122
- version?: number | undefined;
123
- };
124
- input_data?: Record<string, unknown> | undefined;
125
- participants?: {
126
- humans: number;
127
- } | undefined;
128
- ttl?: number | undefined;
129
- metadata?: Record<string, unknown> | undefined;
130
- callback?: {
131
- url: string;
132
- events: string[];
133
- secret: string;
134
- } | undefined;
135
- }>;
41
+ }, z.core.$strip>>;
42
+ title: z.ZodOptional<z.ZodString>;
43
+ }, z.core.$strip>;
136
44
  export declare const createArtifactSchema: z.ZodObject<{
137
45
  name: z.ZodString;
138
46
  slug: z.ZodOptional<z.ZodString>;
139
47
  description: z.ZodOptional<z.ZodString>;
140
- tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
48
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
141
49
  source: z.ZodString;
142
- type: z.ZodEnum<["html-inline", "html-ref"]>;
50
+ type: z.ZodEnum<{
51
+ "html-inline": "html-inline";
52
+ "html-ref": "html-ref";
53
+ }>;
143
54
  event_schema: z.ZodOptional<z.ZodUnknown>;
144
55
  input_schema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
145
- }, "strip", z.ZodTypeAny, {
146
- type: "html-inline" | "html-ref";
147
- source: string;
148
- name: string;
149
- event_schema?: unknown;
150
- slug?: string | undefined;
151
- description?: string | undefined;
152
- tags?: string[] | undefined;
153
- input_schema?: Record<string, unknown> | undefined;
154
- }, {
155
- type: "html-inline" | "html-ref";
156
- source: string;
157
- name: string;
158
- event_schema?: unknown;
159
- slug?: string | undefined;
160
- description?: string | undefined;
161
- tags?: string[] | undefined;
162
- input_schema?: Record<string, unknown> | undefined;
163
- }>;
56
+ }, z.core.$strip>;
164
57
  export declare const createArtifactVersionSchema: z.ZodObject<{
165
58
  source: z.ZodString;
166
- type: z.ZodEnum<["html-inline", "html-ref"]>;
59
+ type: z.ZodEnum<{
60
+ "html-inline": "html-inline";
61
+ "html-ref": "html-ref";
62
+ }>;
167
63
  event_schema: z.ZodOptional<z.ZodUnknown>;
168
64
  input_schema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
169
- }, "strip", z.ZodTypeAny, {
170
- type: "html-inline" | "html-ref";
171
- source: string;
172
- event_schema?: unknown;
173
- input_schema?: Record<string, unknown> | undefined;
174
- }, {
175
- type: "html-inline" | "html-ref";
176
- source: string;
177
- event_schema?: unknown;
178
- input_schema?: Record<string, unknown> | undefined;
179
- }>;
65
+ }, z.core.$strip>;
180
66
  export declare const patchArtifactMetadataSchema: z.ZodObject<{
181
67
  name: z.ZodOptional<z.ZodString>;
182
68
  slug: z.ZodOptional<z.ZodString>;
183
69
  description: z.ZodOptional<z.ZodString>;
184
- tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
185
- }, "strip", z.ZodTypeAny, {
186
- name?: string | undefined;
187
- slug?: string | undefined;
188
- description?: string | undefined;
189
- tags?: string[] | undefined;
190
- }, {
191
- name?: string | undefined;
192
- slug?: string | undefined;
193
- description?: string | undefined;
194
- tags?: string[] | undefined;
70
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
71
+ }, z.core.$strip>;
72
+ export declare const feedbackTypeSchema: z.ZodEnum<{
73
+ bug: "bug";
74
+ feature: "feature";
75
+ note: "note";
195
76
  }>;
196
- export declare const feedbackTypeSchema: z.ZodEnum<["bug", "feature", "note"]>;
197
77
  export declare const submitFeedbackSchema: z.ZodObject<{
198
- type: z.ZodEnum<["bug", "feature", "note"]>;
199
- message: z.ZodPipeline<z.ZodEffects<z.ZodString, string, string>, z.ZodString>;
78
+ type: z.ZodEnum<{
79
+ bug: "bug";
80
+ feature: "feature";
81
+ note: "note";
82
+ }>;
83
+ message: z.ZodPipe<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>, z.ZodString>;
200
84
  session_id: z.ZodOptional<z.ZodString>;
201
- }, "strip", z.ZodTypeAny, {
202
- type: "bug" | "feature" | "note";
203
- message: string;
204
- session_id?: string | undefined;
205
- }, {
206
- type: "bug" | "feature" | "note";
207
- message: string;
208
- session_id?: string | undefined;
209
- }>;
85
+ }, z.core.$strip>;
210
86
  /** @deprecated use `CreateSessionRequest` from ./types.js (same type). */
211
87
  export type CreateSessionInput = z.infer<typeof createSessionSchema>;
88
+ export declare const listSessionsStatusSchema: z.ZodEnum<{
89
+ open: "open";
90
+ closed: "closed";
91
+ all: "all";
92
+ }>;
93
+ export type ListSessionsStatus = z.infer<typeof listSessionsStatusSchema>;
94
+ export declare const listSessionsQuerySchema: z.ZodObject<{
95
+ status: z.ZodOptional<z.ZodEnum<{
96
+ open: "open";
97
+ closed: "closed";
98
+ all: "all";
99
+ }>>;
100
+ limit: z.ZodOptional<z.ZodNumber>;
101
+ cursor: z.ZodOptional<z.ZodString>;
102
+ artifact_id: z.ZodOptional<z.ZodString>;
103
+ }, z.core.$strip>;
104
+ export type ListSessionsQuery = z.infer<typeof listSessionsQuerySchema>;
105
+ export declare const mintParticipantSchema: z.ZodObject<{
106
+ kind: z.ZodLiteral<"human">;
107
+ }, z.core.$strip>;
108
+ export type MintParticipantInput = z.infer<typeof mintParticipantSchema>;
package/dist/schemas.js CHANGED
@@ -25,6 +25,14 @@ const inlineArtifactSchema = z.object({
25
25
  // Optional: omit for a view-only one-off (a report/dashboard the human only
26
26
  // views — the session then accepts no page/agent events).
27
27
  event_schema: z.unknown().optional(),
28
+ // Optional: when present, the session's `input_data` is validated against
29
+ // this JSON Schema before the session row is created — and any blob refs
30
+ // declared at `format: pane-blob-id` sites become reachable from the page
31
+ // via `window.pane.downloadBlob()`. Without this, blob refs in
32
+ // `input_data` are silently unreachable for inline sessions (the
33
+ // participant blob-download bridge walks input_data against the artifact
34
+ // version's inputSchema; no schema means no walkable sites). See #208.
35
+ input_schema: z.record(z.string(), z.unknown()).optional(),
28
36
  });
29
37
  // The reference form for POST /v1/sessions — instances an existing named
30
38
  // artifact. `id` accepts the artifact id or its slug; `version` is optional
@@ -47,11 +55,16 @@ const sessionArtifactSchema = z
47
55
  });
48
56
  export const createSessionSchema = z.object({
49
57
  artifact: sessionArtifactSchema,
50
- input_data: z.record(z.unknown()).optional(),
58
+ input_data: z.record(z.string(), z.unknown()).optional(),
51
59
  participants: z.object({ humans: z.number().int().positive() }).optional(),
52
60
  ttl: z.number().int().positive().optional(),
53
- metadata: z.record(z.unknown()).optional(),
61
+ metadata: z.record(z.string(), z.unknown()).optional(),
54
62
  callback: callbackSchema.optional(),
63
+ // Tab title for the human's browser. Optional on the wire because the relay
64
+ // also accepts the implicit fallback (an Artifact.name on the reference
65
+ // form). The relay enforces "required-or-fallback" + length/control-char
66
+ // rules — Zod only confirms it's a string here.
67
+ title: z.string().optional(),
55
68
  });
56
69
  // POST /v1/artifacts — create a named, reusable artifact plus its v1 content.
57
70
  export const createArtifactSchema = z.object({
@@ -63,7 +76,7 @@ export const createArtifactSchema = z.object({
63
76
  type: artifactTypeSchema,
64
77
  // Optional: omit for a view-only artifact (no event vocabulary).
65
78
  event_schema: z.unknown().optional(),
66
- input_schema: z.record(z.unknown()).optional(),
79
+ input_schema: z.record(z.string(), z.unknown()).optional(),
67
80
  });
68
81
  // POST /v1/artifacts/:id/versions — append a new version (content only).
69
82
  export const createArtifactVersionSchema = z.object({
@@ -71,7 +84,7 @@ export const createArtifactVersionSchema = z.object({
71
84
  type: artifactTypeSchema,
72
85
  // Optional: omit for a view-only artifact (no event vocabulary).
73
86
  event_schema: z.unknown().optional(),
74
- input_schema: z.record(z.unknown()).optional(),
87
+ input_schema: z.record(z.string(), z.unknown()).optional(),
75
88
  });
76
89
  // PATCH /v1/artifacts/:id — update head metadata only (never content).
77
90
  export const patchArtifactMetadataSchema = z.object({
@@ -92,3 +105,19 @@ export const submitFeedbackSchema = z.object({
92
105
  .pipe(z.string().min(1).max(4000)),
93
106
  session_id: z.string().min(1).optional(),
94
107
  });
108
+ // GET /v1/sessions — list the calling agent's sessions. The relay also
109
+ // re-parses these on its side (defence in depth); this schema is for the CLI
110
+ // to fail fast with a clear error before a round trip.
111
+ export const listSessionsStatusSchema = z.enum(["open", "closed", "all"]);
112
+ export const listSessionsQuerySchema = z.object({
113
+ status: listSessionsStatusSchema.optional(),
114
+ limit: z.number().int().positive().max(200).optional(),
115
+ cursor: z.string().min(1).optional(),
116
+ artifact_id: z.string().min(1).optional(),
117
+ });
118
+ // POST /v1/sessions/:id/participants — mint a fresh participant URL for an
119
+ // existing session. v1 supports human participants only (the agent token is
120
+ // minted at session-create and cannot be re-minted via this endpoint).
121
+ export const mintParticipantSchema = z.object({
122
+ kind: z.literal("human"),
123
+ });
package/dist/types.d.ts CHANGED
@@ -52,6 +52,9 @@ export interface CreateSessionResponse {
52
52
  agent_stream: string;
53
53
  };
54
54
  expires_at: string;
55
+ /** The resolved tab title persisted on the session (the agent's value, or
56
+ * the Artifact.name fallback). */
57
+ title: string;
55
58
  }
56
59
  /** Response from GET /v1/sessions/:id. */
57
60
  export interface SessionState {
@@ -61,6 +64,8 @@ export interface SessionState {
61
64
  artifact_id: string;
62
65
  artifact_version_id: string;
63
66
  artifact_version: number;
67
+ /** The tab title this session was created with (frozen for its lifetime). */
68
+ title: string;
64
69
  metadata: Record<string, unknown> | null;
65
70
  input_data: Record<string, unknown> | null;
66
71
  created_at: string;
@@ -71,6 +76,66 @@ export interface EventsPage {
71
76
  events: PaneEvent[];
72
77
  next_cursor: string | null;
73
78
  }
79
+ /** A non-secret summary of one participant on a session — safe to list. */
80
+ export interface ParticipantSummary {
81
+ /** The revoke handle (Participant.id). */
82
+ participant_id: string;
83
+ /** "agent" or "human". The agent's own participant is always present. */
84
+ kind: "agent" | "human";
85
+ /** Short, non-secret correlator for a saved URL ("tok_h_..." / "tok_a_..."). */
86
+ token_prefix: string;
87
+ /** ISO timestamp of first WebSocket connect, or null if never joined. */
88
+ joined_at: string | null;
89
+ /** ISO timestamp the participant was revoked; null while still active. */
90
+ revoked_at: string | null;
91
+ }
92
+ /** A row in the GET /v1/sessions list response (no secrets, lean). */
93
+ export interface SessionSummary {
94
+ session_id: string;
95
+ /** Tab title (required column; agent input or Artifact.name fallback). */
96
+ title: string;
97
+ /** Effective status — respects expiresAt projection (the column may say
98
+ * "open" while `expires_at` is in the past; the projection reports "closed"). */
99
+ status: "open" | "closed";
100
+ /** Owning artifact's head id. Null for inline (anonymous) artifacts. */
101
+ artifact_id: string | null;
102
+ artifact_version_id: string;
103
+ artifact_version: number;
104
+ /** Count of active (non-revoked) human participants. The full participant
105
+ * array is intentionally NOT inlined here — agents with many sessions
106
+ * would pay the bandwidth on every list call. Fetch
107
+ * `GET /v1/sessions/:id/participants` when you need the rows. */
108
+ active_human_participants: number;
109
+ created_at: string;
110
+ expires_at: string;
111
+ /** Whether the session has a webhook callback configured (URL is NOT
112
+ * returned — it may carry a secret in the path). */
113
+ has_callback: boolean;
114
+ }
115
+ /** Response from GET /v1/sessions/:id/participants — every participant on
116
+ * one session (active and revoked). Bounded by MAX_PARTICIPANTS_PER_SESSION
117
+ * on the relay so no pagination is needed. */
118
+ export interface ParticipantsList {
119
+ session_id: string;
120
+ items: ParticipantSummary[];
121
+ }
122
+ /** Response from GET /v1/sessions. */
123
+ export interface SessionsPage {
124
+ items: SessionSummary[];
125
+ /** Opaque cursor for the next page; null when no more rows. */
126
+ next_cursor: string | null;
127
+ }
128
+ /** Response from POST /v1/sessions/:id/participants — one-shot, includes the
129
+ * plaintext token exactly once. The relay stores only the hash. */
130
+ export interface MintParticipantResponse {
131
+ participant_id: string;
132
+ kind: "human";
133
+ /** The plaintext participant token. Returned ONCE — not recoverable. */
134
+ token: string;
135
+ /** The shareable human URL containing the token. */
136
+ url: string;
137
+ created_at: string;
138
+ }
74
139
  /** One immutable version of an artifact's content. */
75
140
  export interface ArtifactVersion {
76
141
  id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paneui/core",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "Pane relay client: typed HTTP + WebSocket operations against a Pane relay. Framework-free.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -47,12 +47,12 @@
47
47
  },
48
48
  "dependencies": {
49
49
  "ws": "^8.20.1",
50
- "zod": "^3.23.0"
50
+ "zod": "^4.4.3"
51
51
  },
52
52
  "devDependencies": {
53
- "@types/node": "^22.7.0",
53
+ "@types/node": "^25.9.1",
54
54
  "@types/ws": "^8.18.1",
55
- "typescript": "^5.6.0",
55
+ "typescript": "^6.0.3",
56
56
  "vitest": "^4.1.6"
57
57
  }
58
58
  }