@beanx/cathygo-protocol 0.1.1 → 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 = {
@@ -59,8 +64,12 @@ type LearningTurnEventPayload = {
59
64
  input_mode?: "text" | "image" | "text_image" | string;
60
65
  required_model_capabilities?: string[];
61
66
  message_id?: string;
67
+ role?: "user" | "assistant" | "system";
62
68
  delta?: string;
63
69
  message?: string;
70
+ parts?: ConversationMessagePart[];
71
+ metadata?: Record<string, unknown>;
72
+ created_at?: string;
64
73
  blocks?: unknown[];
65
74
  status?: "accepted" | "idle" | "running" | "stopping" | "completed" | "failed" | "cancelled" | string;
66
75
  reason?: string;
@@ -78,7 +87,7 @@ type LearningTurnEventPayload = {
78
87
  };
79
88
  type LearningTurnEvent = {
80
89
  type: "event";
81
- event: "session.created" | "session.deleted" | "session.input.accepted" | "session.stop.requested" | "turn.started" | "assistant.delta" | "assistant.completed" | "turn.completed" | "turn.failed" | "turn.cancelled" | "agent.activity.started" | "agent.activity.updated" | "agent.activity.completed" | "agent.progress.delta" | `debug.${string}` | `experimental.${string}`;
90
+ event: "session.created" | "session.deleted" | "session.input.accepted" | "session.stop.requested" | "user.message.created" | "turn.started" | "assistant.delta" | "assistant.completed" | "turn.completed" | "turn.failed" | "turn.cancelled" | "agent.activity.started" | "agent.activity.updated" | "agent.activity.completed" | "agent.progress.delta" | `debug.${string}` | `experimental.${string}`;
82
91
  seq: number;
83
92
  payload: LearningTurnEventPayload;
84
93
  };
@@ -93,7 +102,21 @@ type AgentPresenceEvent = {
93
102
  [key: string]: unknown;
94
103
  };
95
104
  };
96
- 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;
97
120
  type CathyGOFrame = CathyGORequest | CathyGOIncomingFrame;
98
121
  type TextTurnInput = {
99
122
  text: string;
@@ -175,6 +198,7 @@ interface ConversationMessage {
175
198
  parts?: ConversationMessagePart[];
176
199
  turn_id?: string | null;
177
200
  created_at: string;
201
+ metadata?: Record<string, unknown>;
178
202
  }
179
203
  interface ConversationDetail {
180
204
  session: ConversationSummary & {
@@ -211,6 +235,13 @@ interface GatewayAttachment {
211
235
  width?: number | null;
212
236
  height?: number | null;
213
237
  created_at?: string | null;
238
+ thumbnail?: GatewayAttachmentThumbnail | null;
239
+ }
240
+ interface GatewayAttachmentThumbnail {
241
+ mime_type: string;
242
+ data_base64: string;
243
+ width?: number | null;
244
+ height?: number | null;
214
245
  }
215
246
  interface ConversationMessagePart {
216
247
  id?: string;
@@ -336,6 +367,7 @@ type AttachmentCommitParams = {
336
367
  blob?: AttachmentCommitBlob;
337
368
  metadata?: Record<string, unknown>;
338
369
  };
370
+ type AttachmentRelayProgressHandler = (progress: AttachmentRelayProgress) => void;
339
371
  type LearningTurnEventHandler = (event: LearningTurnEvent) => void;
340
372
  type CathyGOProtocolClientCallbacks = {
341
373
  onOpen?: () => void;
@@ -343,6 +375,7 @@ type CathyGOProtocolClientCallbacks = {
343
375
  onError?: () => void;
344
376
  onFrame?: (frame: CathyGOIncomingFrame) => void;
345
377
  onTurnEvent?: LearningTurnEventHandler;
378
+ onAttachmentRelayProgress?: AttachmentRelayProgressHandler;
346
379
  };
347
380
 
348
381
  type CathyGOTransportFrameHandler = (frame: CathyGOIncomingFrame) => void;
@@ -370,6 +403,7 @@ declare class CathyGOProtocolClient {
370
403
  private requestSeq;
371
404
  private pending;
372
405
  private turnHandlers;
406
+ private attachmentRelayProgressHandlers;
373
407
  private lastConnect?;
374
408
  private lastEventSeq;
375
409
  private connectOptions;
@@ -378,6 +412,7 @@ declare class CathyGOProtocolClient {
378
412
  updateConnectOptions(patch: Partial<CathyGOConnectOptions>): void;
379
413
  updateCallbacks(callbacks: CathyGOProtocolClientCallbacks): void;
380
414
  onTurnEvent(handler: LearningTurnEventHandler): () => void;
415
+ onAttachmentRelayProgress(handler: AttachmentRelayProgressHandler): () => void;
381
416
  connect(): Promise<CathyGOConnectResult>;
382
417
  reconnect(options?: Partial<CathyGOConnectOptions>): Promise<CathyGOConnectResult>;
383
418
  disconnect(): void;
@@ -386,6 +421,11 @@ declare class CathyGOProtocolClient {
386
421
  max_payload_bytes: number;
387
422
  max_upload_bytes: number;
388
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;
389
429
  } | undefined;
390
430
  createSession(metadata?: Record<string, unknown>): Promise<string>;
391
431
  listSessions(limit?: number): Promise<ConversationSummary[]>;
@@ -411,6 +451,17 @@ declare function connectPayload(options: CathyGOConnectOptions): Record<string,
411
451
  declare function parseConnectPayload(payload: Record<string, unknown>): CathyGOConnectResult;
412
452
  declare function settingsFromPayload(payload: Record<string, unknown>): GatewayVisibleSettings;
413
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>;
414
465
 
415
466
  type LearningCapability = NonNullable<SessionInputOptions["capability"]>;
416
467
  declare function textTurnInput(text: string): TurnInput;
@@ -418,4 +469,4 @@ declare function photoQuestionTurnInput(text: string, attachments: GatewayAttach
418
469
  declare function capabilityForTurn(attachments: GatewayAttachment[]): LearningCapability;
419
470
  declare function learningTurnInput(text: string, attachments: GatewayAttachment[]): TurnInput;
420
471
 
421
- 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.1",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"