connectbase-client 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -226,6 +226,22 @@ var HttpClient = class {
226
226
  });
227
227
  return this.handleResponse(response);
228
228
  }
229
+ /**
230
+ * Raw fetch 요청 (SSE 스트리밍 등에 사용)
231
+ * 인증 헤더가 자동으로 추가됩니다.
232
+ */
233
+ async fetchRaw(url, init) {
234
+ const headers = await this.prepareHeaders();
235
+ const mergedHeaders = new Headers(headers);
236
+ if (init?.headers) {
237
+ const extra = new Headers(init.headers);
238
+ extra.forEach((value, key) => mergedHeaders.set(key, value));
239
+ }
240
+ return fetch(`${this.config.baseUrl}${url}`, {
241
+ ...init,
242
+ headers: mergedHeaders
243
+ });
244
+ }
229
245
  };
230
246
 
231
247
  // src/api/auth.ts
@@ -4375,7 +4391,7 @@ var SubscriptionAPI = class {
4375
4391
  * order_name: '추가 결제'
4376
4392
  * })
4377
4393
  *
4378
- * if (result.status === 'DONE') {
4394
+ * if (result.status === 'done') {
4379
4395
  * console.log('결제 완료!')
4380
4396
  * }
4381
4397
  * ```
@@ -4431,71 +4447,48 @@ var PushAPI = class {
4431
4447
  /**
4432
4448
  * 디바이스 등록 해제
4433
4449
  *
4434
- * @param deviceId 디바이스 ID
4450
+ * @param deviceToken 디바이스 토큰
4435
4451
  *
4436
4452
  * @example
4437
4453
  * ```typescript
4438
- * await cb.push.unregisterDevice('device-id-here')
4454
+ * await cb.push.unregisterDevice('fcm-token-here')
4439
4455
  * ```
4440
4456
  */
4441
- async unregisterDevice(deviceId) {
4442
- const prefix = this.getPublicPrefix();
4443
- await this.http.delete(`${prefix}/push/devices/${deviceId}`);
4444
- }
4445
- /**
4446
- * 현재 등록된 디바이스 목록 조회
4447
- *
4448
- * @returns 디바이스 목록
4449
- */
4450
- async getDevices() {
4457
+ async unregisterDevice(deviceToken) {
4451
4458
  const prefix = this.getPublicPrefix();
4452
- const response = await this.http.get(`${prefix}/push/devices`);
4453
- return response.devices || [];
4459
+ await this.http.delete(`${prefix}/push/devices/${deviceToken}`);
4454
4460
  }
4455
4461
  // ============ Topic Subscription ============
4456
4462
  /**
4457
4463
  * 토픽 구독
4458
4464
  *
4465
+ * @param deviceToken 디바이스 토큰
4459
4466
  * @param topicName 토픽 이름
4460
4467
  *
4461
4468
  * @example
4462
4469
  * ```typescript
4463
- * // 공지사항 토픽 구독
4464
- * await cb.push.subscribeTopic('announcements')
4465
- *
4466
- * // 마케팅 토픽 구독
4467
- * await cb.push.subscribeTopic('marketing')
4470
+ * await cb.push.subscribeTopic('fcm-token-here', 'announcements')
4468
4471
  * ```
4469
4472
  */
4470
- async subscribeTopic(topicName) {
4473
+ async subscribeTopic(deviceToken, topicName) {
4471
4474
  const prefix = this.getPublicPrefix();
4472
4475
  const request = { topic_name: topicName };
4473
- await this.http.post(`${prefix}/push/topics/subscribe`, request);
4476
+ await this.http.post(`${prefix}/push/devices/${deviceToken}/topics/subscribe`, request);
4474
4477
  }
4475
4478
  /**
4476
4479
  * 토픽 구독 해제
4477
4480
  *
4481
+ * @param deviceToken 디바이스 토큰
4478
4482
  * @param topicName 토픽 이름
4479
4483
  *
4480
4484
  * @example
4481
4485
  * ```typescript
4482
- * await cb.push.unsubscribeTopic('marketing')
4486
+ * await cb.push.unsubscribeTopic('fcm-token-here', 'marketing')
4483
4487
  * ```
4484
4488
  */
4485
- async unsubscribeTopic(topicName) {
4489
+ async unsubscribeTopic(deviceToken, topicName) {
4486
4490
  const prefix = this.getPublicPrefix();
4487
- const request = { topic_name: topicName };
4488
- await this.http.post(`${prefix}/push/topics/unsubscribe`, request);
4489
- }
4490
- /**
4491
- * 구독 중인 토픽 목록 조회
4492
- *
4493
- * @returns 구독 중인 토픽 이름 목록
4494
- */
4495
- async getSubscribedTopics() {
4496
- const prefix = this.getPublicPrefix();
4497
- const response = await this.http.get(`${prefix}/push/topics/subscribed`);
4498
- return response.topics || [];
4491
+ await this.http.delete(`${prefix}/push/devices/${deviceToken}/topics/${topicName}/unsubscribe`);
4499
4492
  }
4500
4493
  // ============ Web Push ============
4501
4494
  /**
@@ -4520,7 +4513,7 @@ var PushAPI = class {
4520
4513
  */
4521
4514
  async getVAPIDPublicKey() {
4522
4515
  const prefix = this.getPublicPrefix();
4523
- return this.http.get(`${prefix}/push/vapid-key`);
4516
+ return this.http.get(`${prefix}/push/vapid-public-key`);
4524
4517
  }
4525
4518
  /**
4526
4519
  * Web Push 구독 등록
@@ -4562,17 +4555,19 @@ var PushAPI = class {
4562
4555
  device_name: this.getBrowserName(),
4563
4556
  os_version: this.getOSInfo()
4564
4557
  };
4565
- return this.http.post(`${prefix}/push/devices/web`, {
4558
+ return this.http.post(`${prefix}/push/devices`, {
4566
4559
  ...request,
4567
4560
  web_push_subscription: webPushData
4568
4561
  });
4569
4562
  }
4570
4563
  /**
4571
4564
  * Web Push 구독 해제
4565
+ *
4566
+ * @param deviceToken Web Push endpoint URL (등록 시 사용한 endpoint)
4572
4567
  */
4573
- async unregisterWebPush() {
4568
+ async unregisterWebPush(deviceToken) {
4574
4569
  const prefix = this.getPublicPrefix();
4575
- await this.http.delete(`${prefix}/push/devices/web`);
4570
+ await this.http.delete(`${prefix}/push/devices/${encodeURIComponent(deviceToken)}`);
4576
4571
  }
4577
4572
  // ============ Helper Methods ============
4578
4573
  /**
@@ -4646,6 +4641,146 @@ var VideoProcessingError = class extends Error {
4646
4641
  var VideoAPI = class {
4647
4642
  constructor(http, videoBaseUrl) {
4648
4643
  this.http = http;
4644
+ // ========== Storage Operations ==========
4645
+ // Storage methods use core-server proxy (main API URL), not video-server directly
4646
+ /**
4647
+ * Storage sub-namespace for video storage container operations
4648
+ */
4649
+ this.storage = {
4650
+ /**
4651
+ * Create a video storage container
4652
+ */
4653
+ create: async (data) => {
4654
+ return this.http.post("/v1/public/storages/videos", data);
4655
+ },
4656
+ /**
4657
+ * List video storage containers
4658
+ */
4659
+ list: async () => {
4660
+ return this.http.get("/v1/public/storages/videos");
4661
+ },
4662
+ /**
4663
+ * Get a video storage container
4664
+ */
4665
+ get: async (storageId) => {
4666
+ return this.http.get(`/v1/public/storages/videos/${storageId}`);
4667
+ },
4668
+ /**
4669
+ * Update a video storage container
4670
+ */
4671
+ update: async (storageId, data) => {
4672
+ return this.http.put(`/v1/public/storages/videos/${storageId}`, data);
4673
+ },
4674
+ /**
4675
+ * Delete a video storage container
4676
+ */
4677
+ delete: async (storageId) => {
4678
+ await this.http.delete(`/v1/public/storages/videos/${storageId}`);
4679
+ },
4680
+ /**
4681
+ * Upload a video to a specific storage via core-server proxy (chunked)
4682
+ */
4683
+ upload: async (storageId, file, options) => {
4684
+ const session = await this.http.post(
4685
+ `/v1/public/storages/videos/${storageId}/uploads/init`,
4686
+ {
4687
+ filename: file.name,
4688
+ size: file.size,
4689
+ mime_type: file.type,
4690
+ title: options.title,
4691
+ description: options.description,
4692
+ visibility: options.visibility || "private",
4693
+ tags: options.tags
4694
+ }
4695
+ );
4696
+ const chunkSize = session.chunk_size || DEFAULT_CHUNK_SIZE;
4697
+ const totalChunks = Math.ceil(file.size / chunkSize);
4698
+ let uploadedChunks = 0;
4699
+ let lastProgressTime = Date.now();
4700
+ let lastUploadedBytes = 0;
4701
+ for (let i = 0; i < totalChunks; i++) {
4702
+ const start = i * chunkSize;
4703
+ const end = Math.min(start + chunkSize, file.size);
4704
+ const chunk = file.slice(start, end);
4705
+ const formData = new FormData();
4706
+ formData.append("chunk", chunk);
4707
+ formData.append("chunk_index", String(i));
4708
+ await this.http.post(
4709
+ `/v1/public/storages/videos/${storageId}/uploads/${session.session_id}/chunk`,
4710
+ formData
4711
+ );
4712
+ uploadedChunks++;
4713
+ const now = Date.now();
4714
+ const timeDiff = (now - lastProgressTime) / 1e3;
4715
+ const uploadedBytes = end;
4716
+ const bytesDiff = uploadedBytes - lastUploadedBytes;
4717
+ const currentSpeed = timeDiff > 0 ? bytesDiff / timeDiff : 0;
4718
+ lastProgressTime = now;
4719
+ lastUploadedBytes = uploadedBytes;
4720
+ if (options.onProgress) {
4721
+ options.onProgress({
4722
+ phase: "uploading",
4723
+ uploadedChunks,
4724
+ totalChunks,
4725
+ percentage: Math.round(uploadedChunks / totalChunks * 100),
4726
+ currentSpeed
4727
+ });
4728
+ }
4729
+ }
4730
+ const result = await this.http.post(
4731
+ `/v1/public/storages/videos/${storageId}/uploads/${session.session_id}/complete`,
4732
+ {}
4733
+ );
4734
+ return result.video;
4735
+ },
4736
+ /**
4737
+ * List videos in a specific storage
4738
+ */
4739
+ listVideos: async (storageId, options) => {
4740
+ const params = new URLSearchParams();
4741
+ if (options?.status) params.set("status", options.status);
4742
+ if (options?.visibility) params.set("visibility", options.visibility);
4743
+ if (options?.search) params.set("search", options.search);
4744
+ if (options?.page) params.set("page", String(options.page));
4745
+ if (options?.limit) params.set("limit", String(options.limit));
4746
+ const query = params.toString();
4747
+ return this.http.get(
4748
+ `/v1/public/storages/videos/${storageId}/videos${query ? `?${query}` : ""}`
4749
+ );
4750
+ },
4751
+ /**
4752
+ * Get a video in a specific storage
4753
+ */
4754
+ getVideo: async (storageId, videoId) => {
4755
+ return this.http.get(
4756
+ `/v1/public/storages/videos/${storageId}/videos/${videoId}`
4757
+ );
4758
+ },
4759
+ /**
4760
+ * Delete a video from a specific storage
4761
+ */
4762
+ deleteVideo: async (storageId, videoId) => {
4763
+ await this.http.delete(
4764
+ `/v1/public/storages/videos/${storageId}/videos/${videoId}`
4765
+ );
4766
+ },
4767
+ /**
4768
+ * Get streaming URL for a video in a specific storage
4769
+ */
4770
+ getStreamUrl: async (storageId, videoId) => {
4771
+ return this.http.get(
4772
+ `/v1/public/storages/videos/${storageId}/videos/${videoId}/stream`
4773
+ );
4774
+ },
4775
+ /**
4776
+ * Get transcode status for a video in a specific storage
4777
+ */
4778
+ getTranscodeStatus: async (storageId, videoId) => {
4779
+ return this.http.get(
4780
+ `/v1/public/storages/videos/${storageId}/videos/${videoId}/transcode`
4781
+ );
4782
+ }
4783
+ };
4649
4784
  this.videoBaseUrl = videoBaseUrl || this.getDefaultVideoUrl();
4650
4785
  }
4651
4786
  getDefaultVideoUrl() {
@@ -6123,6 +6258,183 @@ var GameAPI = class {
6123
6258
  expiresAt: inv.expires_at
6124
6259
  }));
6125
6260
  }
6261
+ // --- Party API ---
6262
+ async createParty(playerId, maxSize) {
6263
+ const id = this.appId || "";
6264
+ const response = await fetch(`${this.gameServerUrl}/v1/game/${id}/parties`, {
6265
+ method: "POST",
6266
+ headers: { ...this.getHeaders(), "Content-Type": "application/json" },
6267
+ body: JSON.stringify({ player_id: playerId, max_size: maxSize || 4 })
6268
+ });
6269
+ if (!response.ok) throw new Error(`Failed to create party: ${response.statusText}`);
6270
+ return response.json();
6271
+ }
6272
+ async joinParty(partyId, playerId) {
6273
+ const id = this.appId || "";
6274
+ const response = await fetch(`${this.gameServerUrl}/v1/game/${id}/parties/${partyId}/join`, {
6275
+ method: "POST",
6276
+ headers: { ...this.getHeaders(), "Content-Type": "application/json" },
6277
+ body: JSON.stringify({ player_id: playerId })
6278
+ });
6279
+ if (!response.ok) throw new Error(`Failed to join party: ${response.statusText}`);
6280
+ return response.json();
6281
+ }
6282
+ async leaveParty(partyId, playerId) {
6283
+ const id = this.appId || "";
6284
+ const response = await fetch(`${this.gameServerUrl}/v1/game/${id}/parties/${partyId}/leave`, {
6285
+ method: "POST",
6286
+ headers: { ...this.getHeaders(), "Content-Type": "application/json" },
6287
+ body: JSON.stringify({ player_id: playerId })
6288
+ });
6289
+ if (!response.ok) throw new Error(`Failed to leave party: ${response.statusText}`);
6290
+ }
6291
+ async kickFromParty(partyId, playerId) {
6292
+ const id = this.appId || "";
6293
+ const response = await fetch(`${this.gameServerUrl}/v1/game/${id}/parties/${partyId}/kick/${playerId}`, {
6294
+ method: "POST",
6295
+ headers: this.getHeaders()
6296
+ });
6297
+ if (!response.ok) throw new Error(`Failed to kick from party: ${response.statusText}`);
6298
+ }
6299
+ async inviteToParty(partyId, inviterId, inviteeId) {
6300
+ const id = this.appId || "";
6301
+ const response = await fetch(`${this.gameServerUrl}/v1/game/${id}/parties/${partyId}/invite/${inviteeId}`, {
6302
+ method: "POST",
6303
+ headers: { ...this.getHeaders(), "Content-Type": "application/json" },
6304
+ body: JSON.stringify({ inviter_id: inviterId })
6305
+ });
6306
+ if (!response.ok) throw new Error(`Failed to invite to party: ${response.statusText}`);
6307
+ return response.json();
6308
+ }
6309
+ async sendPartyChat(partyId, playerId, message) {
6310
+ const id = this.appId || "";
6311
+ const response = await fetch(`${this.gameServerUrl}/v1/game/${id}/parties/${partyId}/chat`, {
6312
+ method: "POST",
6313
+ headers: { ...this.getHeaders(), "Content-Type": "application/json" },
6314
+ body: JSON.stringify({ player_id: playerId, message })
6315
+ });
6316
+ if (!response.ok) throw new Error(`Failed to send party chat: ${response.statusText}`);
6317
+ }
6318
+ // --- Spectator API ---
6319
+ async joinSpectator(roomId, playerId) {
6320
+ const id = this.appId || "";
6321
+ const response = await fetch(`${this.gameServerUrl}/v1/game/${id}/rooms/${roomId}/spectate`, {
6322
+ method: "POST",
6323
+ headers: { ...this.getHeaders(), "Content-Type": "application/json" },
6324
+ body: JSON.stringify({ player_id: playerId })
6325
+ });
6326
+ if (!response.ok) throw new Error(`Failed to join spectator: ${response.statusText}`);
6327
+ return response.json();
6328
+ }
6329
+ async leaveSpectator(roomId, spectatorId) {
6330
+ const id = this.appId || "";
6331
+ const response = await fetch(`${this.gameServerUrl}/v1/game/${id}/rooms/${roomId}/spectate/stop`, {
6332
+ method: "POST",
6333
+ headers: { ...this.getHeaders(), "Content-Type": "application/json" },
6334
+ body: JSON.stringify({ spectator_id: spectatorId })
6335
+ });
6336
+ if (!response.ok) throw new Error(`Failed to leave spectator: ${response.statusText}`);
6337
+ }
6338
+ async getSpectators(roomId) {
6339
+ const id = this.appId || "";
6340
+ const response = await fetch(`${this.gameServerUrl}/v1/game/${id}/rooms/${roomId}/spectators`, {
6341
+ headers: this.getHeaders()
6342
+ });
6343
+ if (!response.ok) throw new Error(`Failed to get spectators: ${response.statusText}`);
6344
+ const data = await response.json();
6345
+ return data.spectators || [];
6346
+ }
6347
+ // --- Ranking/Leaderboard API ---
6348
+ async getLeaderboard(top) {
6349
+ const id = this.appId || "";
6350
+ const limit = top || 100;
6351
+ const response = await fetch(`${this.gameServerUrl}/v1/game/${id}/ranking/leaderboard/top?limit=${limit}`, {
6352
+ headers: this.getHeaders()
6353
+ });
6354
+ if (!response.ok) throw new Error(`Failed to get leaderboard: ${response.statusText}`);
6355
+ const data = await response.json();
6356
+ return data.entries || [];
6357
+ }
6358
+ async getPlayerStats(playerId) {
6359
+ const id = this.appId || "";
6360
+ const response = await fetch(`${this.gameServerUrl}/v1/game/${id}/ranking/players/${playerId}/stats`, {
6361
+ headers: this.getHeaders()
6362
+ });
6363
+ if (!response.ok) throw new Error(`Failed to get player stats: ${response.statusText}`);
6364
+ return response.json();
6365
+ }
6366
+ async getPlayerRank(playerId) {
6367
+ const id = this.appId || "";
6368
+ const response = await fetch(`${this.gameServerUrl}/v1/game/${id}/ranking/players/${playerId}/rank`, {
6369
+ headers: this.getHeaders()
6370
+ });
6371
+ if (!response.ok) throw new Error(`Failed to get player rank: ${response.statusText}`);
6372
+ return response.json();
6373
+ }
6374
+ // --- Voice API ---
6375
+ async joinVoiceChannel(roomId, playerId) {
6376
+ const id = this.appId || "";
6377
+ const response = await fetch(`${this.gameServerUrl}/v1/game/${id}/voice/rooms/${roomId}/join`, {
6378
+ method: "POST",
6379
+ headers: { ...this.getHeaders(), "Content-Type": "application/json" },
6380
+ body: JSON.stringify({ player_id: playerId })
6381
+ });
6382
+ if (!response.ok) throw new Error(`Failed to join voice channel: ${response.statusText}`);
6383
+ return response.json();
6384
+ }
6385
+ async leaveVoiceChannel(roomId, playerId) {
6386
+ const id = this.appId || "";
6387
+ const response = await fetch(`${this.gameServerUrl}/v1/game/${id}/voice/rooms/${roomId}/leave`, {
6388
+ method: "POST",
6389
+ headers: { ...this.getHeaders(), "Content-Type": "application/json" },
6390
+ body: JSON.stringify({ player_id: playerId })
6391
+ });
6392
+ if (!response.ok) throw new Error(`Failed to leave voice channel: ${response.statusText}`);
6393
+ }
6394
+ async setMute(playerId, muted) {
6395
+ const id = this.appId || "";
6396
+ const response = await fetch(`${this.gameServerUrl}/v1/game/${id}/voice/mute`, {
6397
+ method: "POST",
6398
+ headers: { ...this.getHeaders(), "Content-Type": "application/json" },
6399
+ body: JSON.stringify({ player_id: playerId, muted })
6400
+ });
6401
+ if (!response.ok) throw new Error(`Failed to set mute: ${response.statusText}`);
6402
+ }
6403
+ // --- Replay API ---
6404
+ async listReplays(roomId) {
6405
+ const id = this.appId || "";
6406
+ let url = `${this.gameServerUrl}/v1/game/${id}/replays`;
6407
+ if (roomId) url += `?room_id=${roomId}`;
6408
+ const response = await fetch(url, { headers: this.getHeaders() });
6409
+ if (!response.ok) throw new Error(`Failed to list replays: ${response.statusText}`);
6410
+ const data = await response.json();
6411
+ return data.replays || [];
6412
+ }
6413
+ async getReplay(replayId) {
6414
+ const id = this.appId || "";
6415
+ const response = await fetch(`${this.gameServerUrl}/v1/game/${id}/replays/${replayId}`, {
6416
+ headers: this.getHeaders()
6417
+ });
6418
+ if (!response.ok) throw new Error(`Failed to get replay: ${response.statusText}`);
6419
+ return response.json();
6420
+ }
6421
+ async downloadReplay(replayId) {
6422
+ const id = this.appId || "";
6423
+ const response = await fetch(`${this.gameServerUrl}/v1/game/${id}/replays/${replayId}/download`, {
6424
+ headers: this.getHeaders()
6425
+ });
6426
+ if (!response.ok) throw new Error(`Failed to download replay: ${response.statusText}`);
6427
+ return response.arrayBuffer();
6428
+ }
6429
+ async getReplayHighlights(replayId) {
6430
+ const id = this.appId || "";
6431
+ const response = await fetch(`${this.gameServerUrl}/v1/game/${id}/replays/${replayId}/highlights`, {
6432
+ headers: this.getHeaders()
6433
+ });
6434
+ if (!response.ok) throw new Error(`Failed to get highlights: ${response.statusText}`);
6435
+ const data = await response.json();
6436
+ return data.highlights || [];
6437
+ }
6126
6438
  getHeaders() {
6127
6439
  const headers = {};
6128
6440
  const apiKey = this.http.getApiKey();
@@ -6813,6 +7125,106 @@ var KnowledgeAPI = class {
6813
7125
  `/v1/public/knowledge-bases/${kbID}/search?${params.toString()}`
6814
7126
  );
6815
7127
  }
7128
+ // ========== Chat (RAG) ==========
7129
+ /**
7130
+ * RAG 챗 (동기)
7131
+ *
7132
+ * 지식 베이스의 문서를 검색하고 AI가 답변을 생성합니다.
7133
+ *
7134
+ * @param kbID - 지식 베이스 ID
7135
+ * @param request - 챗 요청
7136
+ * @returns AI 답변 + 참조 소스
7137
+ *
7138
+ * @example
7139
+ * ```typescript
7140
+ * const response = await cb.knowledge.chat('kb-id', {
7141
+ * message: '환불 정책이 어떻게 되나요?',
7142
+ * top_k: 5
7143
+ * })
7144
+ * console.log(response.answer)
7145
+ * console.log('참조:', response.sources)
7146
+ * ```
7147
+ */
7148
+ async chat(kbID, request) {
7149
+ return this.http.post(
7150
+ `/v1/public/knowledge-bases/${kbID}/chat`,
7151
+ request
7152
+ );
7153
+ }
7154
+ /**
7155
+ * RAG 챗 스트리밍 (SSE)
7156
+ *
7157
+ * AI 답변을 실시간으로 토큰 단위로 수신합니다.
7158
+ *
7159
+ * @param kbID - 지식 베이스 ID
7160
+ * @param request - 챗 요청
7161
+ * @param callbacks - 스트리밍 콜백
7162
+ *
7163
+ * @example
7164
+ * ```typescript
7165
+ * await cb.knowledge.chatStream('kb-id', {
7166
+ * message: '환불 정책이 어떻게 되나요?'
7167
+ * }, {
7168
+ * onSources: (sources, model) => console.log('참조:', sources),
7169
+ * onToken: (content) => process.stdout.write(content),
7170
+ * onDone: (usage) => console.log('\\n완료:', usage),
7171
+ * onError: (error) => console.error('에러:', error)
7172
+ * })
7173
+ * ```
7174
+ */
7175
+ async chatStream(kbID, request, callbacks) {
7176
+ const response = await this.http.fetchRaw(
7177
+ `/v1/public/knowledge-bases/${kbID}/chat/stream`,
7178
+ {
7179
+ method: "POST",
7180
+ headers: { "Content-Type": "application/json" },
7181
+ body: JSON.stringify(request)
7182
+ }
7183
+ );
7184
+ if (!response.ok) {
7185
+ const errorData = await response.json().catch(() => ({ error: "Stream request failed" }));
7186
+ callbacks.onError?.(errorData.error || "Stream request failed");
7187
+ return;
7188
+ }
7189
+ const reader = response.body?.getReader();
7190
+ if (!reader) {
7191
+ callbacks.onError?.("ReadableStream not supported");
7192
+ return;
7193
+ }
7194
+ const decoder = new TextDecoder();
7195
+ let buffer = "";
7196
+ while (true) {
7197
+ const { done, value } = await reader.read();
7198
+ if (done) break;
7199
+ buffer += decoder.decode(value, { stream: true });
7200
+ const lines = buffer.split("\n");
7201
+ buffer = lines.pop() || "";
7202
+ for (const line of lines) {
7203
+ if (!line.startsWith("data: ")) continue;
7204
+ const data = line.slice(6).trim();
7205
+ if (data === "[DONE]") {
7206
+ callbacks.onDone?.();
7207
+ return;
7208
+ }
7209
+ try {
7210
+ const event = JSON.parse(data);
7211
+ switch (event.type) {
7212
+ case "sources":
7213
+ callbacks.onSources?.(event.sources, event.model);
7214
+ break;
7215
+ case "token":
7216
+ if (event.content) callbacks.onToken?.(event.content);
7217
+ if (event.done) callbacks.onDone?.(event.usage);
7218
+ break;
7219
+ case "error":
7220
+ callbacks.onError?.(event.error || "Unknown error");
7221
+ return;
7222
+ }
7223
+ } catch {
7224
+ }
7225
+ }
7226
+ }
7227
+ }
6816
7228
  };
6817
7229
 
6818
7230
  // src/api/queue.ts