@beanx/cathygo-protocol 0.1.2 → 0.1.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/dist/index.d.ts CHANGED
@@ -26,6 +26,11 @@ type CathyGOConnectResult = {
26
26
  max_payload_bytes: number;
27
27
  max_upload_bytes: number;
28
28
  heartbeat_interval_ms: number;
29
+ attachment_relay_max_files_per_send?: number;
30
+ attachment_relay_max_concurrent_transfers?: number;
31
+ attachment_relay_chunk_bytes?: number;
32
+ attachment_relay_max_file_bytes?: number;
33
+ attachment_relay_progress_min_interval_ms?: number;
29
34
  };
30
35
  };
31
36
  type CathyGORequest = {
@@ -97,7 +102,21 @@ type AgentPresenceEvent = {
97
102
  [key: string]: unknown;
98
103
  };
99
104
  };
100
- type CathyGOIncomingFrame = CathyGOResponse | LearningTurnEvent | AgentPresenceEvent;
105
+ type AttachmentRelayProgress = {
106
+ transfer_id: string;
107
+ received_bytes: number;
108
+ total_bytes: number;
109
+ percent: number;
110
+ batch_id?: string;
111
+ index?: number;
112
+ };
113
+ type AttachmentRelayProgressEvent = {
114
+ type: "event";
115
+ event: "attachment.relay.progress";
116
+ seq?: number;
117
+ payload: AttachmentRelayProgress;
118
+ };
119
+ type CathyGOIncomingFrame = CathyGOResponse | LearningTurnEvent | AgentPresenceEvent | AttachmentRelayProgressEvent;
101
120
  type CathyGOFrame = CathyGORequest | CathyGOIncomingFrame;
102
121
  type TextTurnInput = {
103
122
  text: string;
@@ -348,6 +367,7 @@ type AttachmentCommitParams = {
348
367
  blob?: AttachmentCommitBlob;
349
368
  metadata?: Record<string, unknown>;
350
369
  };
370
+ type AttachmentRelayProgressHandler = (progress: AttachmentRelayProgress) => void;
351
371
  type LearningTurnEventHandler = (event: LearningTurnEvent) => void;
352
372
  type CathyGOProtocolClientCallbacks = {
353
373
  onOpen?: () => void;
@@ -355,6 +375,7 @@ type CathyGOProtocolClientCallbacks = {
355
375
  onError?: () => void;
356
376
  onFrame?: (frame: CathyGOIncomingFrame) => void;
357
377
  onTurnEvent?: LearningTurnEventHandler;
378
+ onAttachmentRelayProgress?: AttachmentRelayProgressHandler;
358
379
  };
359
380
 
360
381
  type CathyGOTransportFrameHandler = (frame: CathyGOIncomingFrame) => void;
@@ -382,6 +403,7 @@ declare class CathyGOProtocolClient {
382
403
  private requestSeq;
383
404
  private pending;
384
405
  private turnHandlers;
406
+ private attachmentRelayProgressHandlers;
385
407
  private lastConnect?;
386
408
  private lastEventSeq;
387
409
  private connectOptions;
@@ -390,6 +412,7 @@ declare class CathyGOProtocolClient {
390
412
  updateConnectOptions(patch: Partial<CathyGOConnectOptions>): void;
391
413
  updateCallbacks(callbacks: CathyGOProtocolClientCallbacks): void;
392
414
  onTurnEvent(handler: LearningTurnEventHandler): () => void;
415
+ onAttachmentRelayProgress(handler: AttachmentRelayProgressHandler): () => void;
393
416
  connect(): Promise<CathyGOConnectResult>;
394
417
  reconnect(options?: Partial<CathyGOConnectOptions>): Promise<CathyGOConnectResult>;
395
418
  disconnect(): void;
@@ -398,6 +421,11 @@ declare class CathyGOProtocolClient {
398
421
  max_payload_bytes: number;
399
422
  max_upload_bytes: number;
400
423
  heartbeat_interval_ms: number;
424
+ attachment_relay_max_files_per_send?: number;
425
+ attachment_relay_max_concurrent_transfers?: number;
426
+ attachment_relay_chunk_bytes?: number;
427
+ attachment_relay_max_file_bytes?: number;
428
+ attachment_relay_progress_min_interval_ms?: number;
401
429
  } | undefined;
402
430
  createSession(metadata?: Record<string, unknown>): Promise<string>;
403
431
  listSessions(limit?: number): Promise<ConversationSummary[]>;
@@ -423,6 +451,17 @@ declare function connectPayload(options: CathyGOConnectOptions): Record<string,
423
451
  declare function parseConnectPayload(payload: Record<string, unknown>): CathyGOConnectResult;
424
452
  declare function settingsFromPayload(payload: Record<string, unknown>): GatewayVisibleSettings;
425
453
  declare function isLearningTurnEvent(frame: CathyGOIncomingFrame): frame is LearningTurnEvent;
454
+ declare function isAttachmentRelayProgressEvent(frame: CathyGOIncomingFrame): frame is AttachmentRelayProgressEvent;
455
+
456
+ type UploadAttachmentViaRelayOptions = {
457
+ transferId?: string;
458
+ sessionId?: string;
459
+ metadata?: Record<string, unknown>;
460
+ chunkBytes?: number;
461
+ onProgress?: (progress: AttachmentRelayProgress) => void;
462
+ signal?: AbortSignal;
463
+ };
464
+ declare function uploadAttachmentViaRelay(client: CathyGOProtocolClient, file: File, options?: UploadAttachmentViaRelayOptions): Promise<GatewayAttachment>;
426
465
 
427
466
  type LearningCapability = NonNullable<SessionInputOptions["capability"]>;
428
467
  declare function textTurnInput(text: string): TurnInput;
@@ -430,4 +469,4 @@ declare function photoQuestionTurnInput(text: string, attachments: GatewayAttach
430
469
  declare function capabilityForTurn(attachments: GatewayAttachment[]): LearningCapability;
431
470
  declare function learningTurnInput(text: string, attachments: GatewayAttachment[]): TurnInput;
432
471
 
433
- export { type AgentActivity, type AgentPresenceEvent, type AttachmentCommitBlob, type AttachmentCommitParams, type AttachmentInput, type BeanXAccountRuntimeConfig, CathyGOClientError, type CathyGOClientRole, type CathyGOConnectOptions, type CathyGOConnectResult, type CathyGODeviceContext, type CathyGOFrame, type CathyGOIncomingFrame, CathyGOProtocolClient, type CathyGOProtocolClientCallbacks, type CathyGOProtocolClientOptions, type CathyGORequest, type CathyGOResponse, type CathyGOTransport, type CathyGOTransportEventHandler, type CathyGOTransportFrameHandler, type ConversationDetail, type ConversationMessage, type ConversationMessagePart, type ConversationSummary, type GatewayAgentProfile, type GatewayAttachment, type GatewayModelConfig, type GatewayModelConfigUpdate, type GatewayModelOption, type GatewayModelStatus, type GatewayVisibleSettings, type LearningCapability, type LearningTurnEvent, type LearningTurnEventHandler, type LearningTurnEventPayload, type ListResponse, type MessagePartInput, type MultimodalTurnInput, type SameSessionPolicy, type SessionInputOptions, type SessionInputResult, type SessionRuntimeSnapshot, type SessionStopResult, type SettingsItem, type SettingsItemKind, type SettingsSection, type TextTurnInput, type TurnInput, type UploadScope, capabilityForTurn, connectPayload, isLearningTurnEvent, learningTurnInput, parseConnectPayload, photoQuestionTurnInput, settingsFromPayload, textTurnInput };
472
+ export { type AgentActivity, type AgentPresenceEvent, type AttachmentCommitBlob, type AttachmentCommitParams, type AttachmentInput, type AttachmentRelayProgress, type AttachmentRelayProgressEvent, type AttachmentRelayProgressHandler, type BeanXAccountRuntimeConfig, CathyGOClientError, type CathyGOClientRole, type CathyGOConnectOptions, type CathyGOConnectResult, type CathyGODeviceContext, type CathyGOFrame, type CathyGOIncomingFrame, CathyGOProtocolClient, type CathyGOProtocolClientCallbacks, type CathyGOProtocolClientOptions, type CathyGORequest, type CathyGOResponse, type CathyGOTransport, type CathyGOTransportEventHandler, type CathyGOTransportFrameHandler, type ConversationDetail, type ConversationMessage, type ConversationMessagePart, type ConversationSummary, type GatewayAgentProfile, type GatewayAttachment, type GatewayModelConfig, type GatewayModelConfigUpdate, type GatewayModelOption, type GatewayModelStatus, type GatewayVisibleSettings, type LearningCapability, type LearningTurnEvent, type LearningTurnEventHandler, type LearningTurnEventPayload, type ListResponse, type MessagePartInput, type MultimodalTurnInput, type SameSessionPolicy, type SessionInputOptions, type SessionInputResult, type SessionRuntimeSnapshot, type SessionStopResult, type SettingsItem, type SettingsItemKind, type SettingsSection, type TextTurnInput, type TurnInput, type UploadAttachmentViaRelayOptions, type UploadScope, capabilityForTurn, connectPayload, isAttachmentRelayProgressEvent, isLearningTurnEvent, learningTurnInput, parseConnectPayload, photoQuestionTurnInput, settingsFromPayload, textTurnInput, uploadAttachmentViaRelay };
package/dist/index.js CHANGED
@@ -26,6 +26,7 @@ var CathyGOProtocolClient = class {
26
26
  requestSeq = 1;
27
27
  pending = /* @__PURE__ */ new Map();
28
28
  turnHandlers = /* @__PURE__ */ new Set();
29
+ attachmentRelayProgressHandlers = /* @__PURE__ */ new Set();
29
30
  lastConnect;
30
31
  lastEventSeq = 0;
31
32
  connectOptions;
@@ -40,6 +41,10 @@ var CathyGOProtocolClient = class {
40
41
  this.turnHandlers.add(handler);
41
42
  return () => this.turnHandlers.delete(handler);
42
43
  }
44
+ onAttachmentRelayProgress(handler) {
45
+ this.attachmentRelayProgressHandlers.add(handler);
46
+ return () => this.attachmentRelayProgressHandlers.delete(handler);
47
+ }
43
48
  async connect() {
44
49
  if (this.isConnected() && this.lastConnect) {
45
50
  return this.lastConnect;
@@ -205,6 +210,12 @@ var CathyGOProtocolClient = class {
205
210
  handler(frame);
206
211
  }
207
212
  }
213
+ if (isAttachmentRelayProgressEvent(frame)) {
214
+ this.callbacks.onAttachmentRelayProgress?.(frame.payload);
215
+ for (const handler of this.attachmentRelayProgressHandlers) {
216
+ handler(frame.payload);
217
+ }
218
+ }
208
219
  this.callbacks.onFrame?.(frame);
209
220
  }
210
221
  };
@@ -254,6 +265,9 @@ function settingsFromPayload(payload) {
254
265
  function isLearningTurnEvent(frame) {
255
266
  return frame.type === "event" && (frame.event.startsWith("session.") || frame.event.startsWith("turn.") || frame.event.startsWith("agent.activity.") || frame.event.startsWith("agent.progress.") || frame.event.startsWith("assistant.") || frame.event.startsWith("debug.") || frame.event.startsWith("experimental."));
256
267
  }
268
+ function isAttachmentRelayProgressEvent(frame) {
269
+ return frame.type === "event" && frame.event === "attachment.relay.progress";
270
+ }
257
271
  function turnStartResultFromPayload(payload, fallbackSessionId) {
258
272
  return {
259
273
  session_id: String(payload.session_id ?? fallbackSessionId),
@@ -280,6 +294,126 @@ function compactParams(params) {
280
294
  return Object.fromEntries(Object.entries(params).filter(([, value]) => value !== void 0));
281
295
  }
282
296
 
297
+ // src/relay-attachment-upload.ts
298
+ async function uploadAttachmentViaRelay(client, file, options = {}) {
299
+ const transferId = options.transferId ?? createTransferId();
300
+ const mimeType = file.type || "application/octet-stream";
301
+ const buffer = await file.arrayBuffer();
302
+ const bytes = new Uint8Array(buffer);
303
+ const sha256 = await sha256Hex(buffer);
304
+ const limits = client.getLimits();
305
+ const chunkBytes = options.chunkBytes ?? Number(limits?.attachment_relay_chunk_bytes ?? DEFAULT_RELAY_CHUNK_BYTES);
306
+ if (options.signal?.aborted) {
307
+ throw new DOMException("Attachment relay upload aborted.", "AbortError");
308
+ }
309
+ const openPayload = await client.request("attachment.relay.open", {
310
+ transfer_id: transferId,
311
+ session_id: options.sessionId,
312
+ mime_type: mimeType,
313
+ size_bytes: bytes.byteLength,
314
+ sha256,
315
+ original_name: file.name || void 0,
316
+ metadata: options.metadata
317
+ });
318
+ const negotiatedChunkBytes = Number(openPayload.chunk_bytes ?? chunkBytes) || chunkBytes;
319
+ try {
320
+ let seq = 0;
321
+ for (let offset = 0; offset < bytes.byteLength; offset += negotiatedChunkBytes) {
322
+ if (options.signal?.aborted) {
323
+ await client.request("attachment.relay.abort", { transfer_id: transferId });
324
+ throw new DOMException("Attachment relay upload aborted.", "AbortError");
325
+ }
326
+ const chunk = bytes.subarray(offset, offset + negotiatedChunkBytes);
327
+ const pushPayload = await client.request("attachment.relay.push", {
328
+ transfer_id: transferId,
329
+ seq,
330
+ data_base64: bytesToBase64(chunk)
331
+ });
332
+ seq += 1;
333
+ options.onProgress?.({
334
+ transfer_id: transferId,
335
+ received_bytes: Number(pushPayload.received_bytes ?? offset + chunk.byteLength),
336
+ total_bytes: Number(pushPayload.total_bytes ?? bytes.byteLength),
337
+ percent: percent(
338
+ Number(pushPayload.received_bytes ?? offset + chunk.byteLength),
339
+ Number(pushPayload.total_bytes ?? bytes.byteLength)
340
+ ),
341
+ batch_id: stringOrUndefined(options.metadata?.batch_id),
342
+ index: numberOrUndefined(options.metadata?.index)
343
+ });
344
+ }
345
+ const closePayload = await client.request("attachment.relay.close", {
346
+ transfer_id: transferId
347
+ });
348
+ return normalizeGatewayAttachment(closePayload.attachment);
349
+ } catch (error) {
350
+ if (!(error instanceof DOMException && error.name === "AbortError")) {
351
+ try {
352
+ await client.request("attachment.relay.abort", { transfer_id: transferId });
353
+ } catch {
354
+ }
355
+ }
356
+ throw error;
357
+ }
358
+ }
359
+ var DEFAULT_RELAY_CHUNK_BYTES = 65536;
360
+ function createTransferId() {
361
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
362
+ return `xfer_${crypto.randomUUID().replace(/-/g, "").slice(0, 16)}`;
363
+ }
364
+ return `xfer_${Date.now().toString(36)}`;
365
+ }
366
+ function percent(receivedBytes, totalBytes) {
367
+ if (totalBytes <= 0) return 0;
368
+ return Math.min(100, Math.max(0, Math.round(receivedBytes * 100 / totalBytes)));
369
+ }
370
+ function bytesToBase64(bytes) {
371
+ let binary = "";
372
+ for (let index = 0; index < bytes.length; index += 1) {
373
+ binary += String.fromCharCode(bytes[index] ?? 0);
374
+ }
375
+ return btoa(binary);
376
+ }
377
+ async function sha256Hex(buffer) {
378
+ const digest = await crypto.subtle.digest("SHA-256", buffer);
379
+ return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
380
+ }
381
+ function normalizeGatewayAttachment(value) {
382
+ const attachment = value ?? {};
383
+ const id = String(attachment.id ?? attachment.attachment_id ?? "");
384
+ if (!id) {
385
+ throw new Error("attachment.relay.close did not return an attachment id.");
386
+ }
387
+ return {
388
+ id,
389
+ kind: "image",
390
+ uri: String(attachment.uri ?? ""),
391
+ original_name: stringOrNull(attachment.original_name),
392
+ mime_type: stringOrNull(attachment.mime_type),
393
+ size_bytes: numberOrNull(attachment.size_bytes),
394
+ sha256: stringOrNull(attachment.sha256),
395
+ width: numberOrNull(attachment.width),
396
+ height: numberOrNull(attachment.height),
397
+ created_at: stringOrNull(attachment.created_at),
398
+ thumbnail: attachment.thumbnail
399
+ };
400
+ }
401
+ function stringOrUndefined(value) {
402
+ if (typeof value !== "string") return void 0;
403
+ const text = value.trim();
404
+ return text || void 0;
405
+ }
406
+ function stringOrNull(value) {
407
+ return stringOrUndefined(value) ?? null;
408
+ }
409
+ function numberOrUndefined(value) {
410
+ if (typeof value !== "number" || Number.isNaN(value)) return void 0;
411
+ return value;
412
+ }
413
+ function numberOrNull(value) {
414
+ return numberOrUndefined(value) ?? null;
415
+ }
416
+
283
417
  // src/turn-input.ts
284
418
  function textTurnInput(text) {
285
419
  return { text };
@@ -315,10 +449,12 @@ export {
315
449
  CathyGOProtocolClient,
316
450
  capabilityForTurn,
317
451
  connectPayload,
452
+ isAttachmentRelayProgressEvent,
318
453
  isLearningTurnEvent,
319
454
  learningTurnInput,
320
455
  parseConnectPayload,
321
456
  photoQuestionTurnInput,
322
457
  settingsFromPayload,
323
- textTurnInput
458
+ textTurnInput,
459
+ uploadAttachmentViaRelay
324
460
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beanx/cathygo-protocol",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"