connectbase-client 3.27.1 → 3.29.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.mjs CHANGED
@@ -4776,28 +4776,35 @@ var OAuthAPI = class {
4776
4776
  * `error=account_not_found` 를 받아 가입 안내 UX 로 분기해야 한다. 신규 가입까지 한 번에
4777
4777
  * 처리하려면 [signUp] 을 사용한다.
4778
4778
  *
4779
+ * 기본적으로 미가입 사용자는 콜백에서 `error=account_not_found` 로 돌아온다(신규 자동가입 안 됨).
4780
+ * 로그인/가입을 한 버튼으로 처리하려면 `{ createIfNotExists: true }` 를 주거나, 별도 "회원가입"
4781
+ * 버튼에서 [signUp] 을 호출한다.
4782
+ *
4779
4783
  * @param provider - OAuth 프로바이더 (google, naver, github, discord)
4780
4784
  * @param callbackUrl - OAuth 완료 후 리다이렉트될 앱의 URL
4781
4785
  * @param state - 선택적 state 파라미터 (CSRF 방지 등)
4786
+ * @param options - `createIfNotExists: true` 면 미가입 사용자를 자동 가입시킨다([signUp] 과 동일 동작).
4782
4787
  *
4783
4788
  * @example
4784
4789
  * ```typescript
4785
- * // "로그인" 버튼 클릭
4790
+ * // "로그인" 버튼 기존 회원만. 신규는 account_not_found.
4786
4791
  * await cb.oauth.signIn('google', 'https://myapp.com/oauth/callback')
4792
+ *
4793
+ * // 로그인=가입 통합 버튼 — 신규면 자동 가입
4794
+ * await cb.oauth.signIn('google', 'https://myapp.com/oauth/callback', undefined, { createIfNotExists: true })
4787
4795
  * ```
4788
4796
  *
4789
4797
  * @example
4790
4798
  * ```typescript
4791
- * // callback 페이지에서
4799
+ * // callback 페이지에서 — 미가입이면 가입 화면으로 안내
4792
4800
  * const result = await cb.oauth.getCallbackResult()
4793
4801
  * if (result?.error === 'account_not_found') {
4794
- * // 가입되지 않은 사용자 — 회원가입 화면으로 안내
4795
4802
  * window.location.href = '/signup'
4796
4803
  * }
4797
4804
  * ```
4798
4805
  */
4799
- async signIn(provider, callbackUrl, state) {
4800
- return this.startCentralOAuth(provider, callbackUrl, state, "signin");
4806
+ async signIn(provider, callbackUrl, state, options) {
4807
+ return this.startCentralOAuth(provider, callbackUrl, state, options?.createIfNotExists ? "signup" : "signin");
4801
4808
  }
4802
4809
  /**
4803
4810
  * 소셜 회원가입 (리다이렉트 방식) — 사용자가 명시적으로 가입 의사를 표시한 경우에만 호출.
@@ -8496,6 +8503,15 @@ var AIAPI = class {
8496
8503
  /**
8497
8504
  * AI 채팅 스트리밍 (SSE)
8498
8505
  *
8506
+ * @param request AI 채팅 요청
8507
+ * @param callbacks 스트림 이벤트 콜백 (토큰/추론/도구/검색/완료/에러/취소)
8508
+ * @param options 선택적 옵션. `options.signal` 에 `AbortSignal` 을 전달하면
8509
+ * 호출자가 진행 중인 스트림을 취소할 수 있다. abort 시 SSE 연결이 닫히고,
8510
+ * 서버는 요청 컨텍스트(`c.Request.Context()`) 취소를 통해 server-side agent
8511
+ * tool loop (`toolGroupId` 사용 시)까지 함께 중단한다. abort 는 정상적인
8512
+ * 사용자 취소이므로 `onError` 가 아니라 `callbacks.onAbort?.()` 가 호출되며
8513
+ * (지정 시), `onDone` 은 호출되지 않는다.
8514
+ *
8499
8515
  * @example
8500
8516
  * ```typescript
8501
8517
  * await cb.ai.chatStream({
@@ -8509,76 +8525,109 @@ var AIAPI = class {
8509
8525
  * onError: (error) => console.error('에러:', error)
8510
8526
  * })
8511
8527
  * ```
8528
+ *
8529
+ * @example 취소(stop 버튼)
8530
+ * ```typescript
8531
+ * const controller = new AbortController()
8532
+ * // stop 버튼: controller.abort()
8533
+ * await cb.ai.chatStream(
8534
+ * { messages: [{ role: 'user', content: '...' }], toolGroupId: 'tg-id' },
8535
+ * { onToken: (c) => process.stdout.write(c), onAbort: () => console.log('취소됨') },
8536
+ * { signal: controller.signal },
8537
+ * )
8538
+ * ```
8512
8539
  */
8513
- async chatStream(request, callbacks) {
8514
- const response = await this.http.fetchRaw(
8515
- "/v1/public/ai/chat/stream",
8516
- {
8517
- method: "POST",
8518
- headers: { "Content-Type": "application/json" },
8519
- body: JSON.stringify(request)
8520
- }
8521
- );
8522
- if (!response.ok) {
8523
- const errorData = await response.json().catch(() => ({ error: "Stream request failed" }));
8524
- callbacks.onError?.(errorData.error || "Stream request failed");
8525
- return;
8526
- }
8527
- const reader = response.body?.getReader();
8528
- if (!reader) {
8529
- callbacks.onError?.("ReadableStream not supported");
8530
- return;
8531
- }
8532
- const decoder = new TextDecoder();
8533
- let buffer = "";
8534
- while (true) {
8535
- const { done, value } = await reader.read();
8536
- if (done) break;
8537
- buffer += decoder.decode(value, { stream: true });
8538
- const lines = buffer.split("\n");
8539
- buffer = lines.pop() || "";
8540
- for (const line of lines) {
8541
- if (!line.startsWith("data: ")) continue;
8542
- const data = line.slice(6).trim();
8543
- if (data === "[DONE]") {
8544
- callbacks.onDone?.();
8545
- return;
8540
+ async chatStream(request, callbacks, options) {
8541
+ const signal = options?.signal;
8542
+ const isAbort = (err) => signal?.aborted === true || err instanceof DOMException && err.name === "AbortError" || typeof err === "object" && err !== null && err.name === "AbortError";
8543
+ let reader;
8544
+ try {
8545
+ const response = await this.http.fetchRaw(
8546
+ "/v1/public/ai/chat/stream",
8547
+ {
8548
+ method: "POST",
8549
+ headers: { "Content-Type": "application/json" },
8550
+ body: JSON.stringify(request),
8551
+ // 외부 AbortSignal 그대로 전달. fetchRaw 는 init 을 spread 하므로
8552
+ // 시그니처 변경 없이 signal 이 fetch 까지 도달한다. abort 시 fetch 가
8553
+ // SSE 연결을 닫고, 서버는 요청 컨텍스트 취소로 tool loop 까지 중단한다.
8554
+ signal
8546
8555
  }
8547
- try {
8548
- const event = JSON.parse(data);
8549
- if (event.error) {
8550
- callbacks.onError?.(event.message || event.error || "stream error");
8551
- return;
8552
- }
8553
- if (event.type === "sources" && event.sources) {
8554
- callbacks.onSources?.(event.sources);
8555
- continue;
8556
- }
8557
- if (event.type === "tool_start" || event.type === "tool_end") {
8558
- callbacks.onToolEvent?.({
8559
- type: event.type,
8560
- name: event.name,
8561
- toolCallId: event.toolCallId,
8562
- arguments: event.arguments,
8563
- result: event.result,
8564
- success: event.success,
8565
- durationMs: event.durationMs
8566
- });
8567
- continue;
8568
- }
8569
- if (event.type === "searching") {
8570
- if (event.searching) callbacks.onSearching?.(event.searching);
8571
- continue;
8572
- }
8573
- if (event.type === "heartbeat") {
8574
- continue;
8575
- }
8576
- if (event.reasoning) callbacks.onReasoning?.(event.reasoning);
8577
- if (event.content) callbacks.onToken?.(event.content);
8578
- if (event.done) {
8556
+ );
8557
+ if (!response.ok) {
8558
+ const errorData = await response.json().catch(() => ({ error: "Stream request failed" }));
8559
+ callbacks.onError?.(errorData.error || "Stream request failed");
8560
+ return;
8561
+ }
8562
+ reader = response.body?.getReader();
8563
+ if (!reader) {
8564
+ callbacks.onError?.("ReadableStream not supported");
8565
+ return;
8566
+ }
8567
+ const decoder = new TextDecoder();
8568
+ let buffer = "";
8569
+ while (true) {
8570
+ const { done, value } = await reader.read();
8571
+ if (done) break;
8572
+ buffer += decoder.decode(value, { stream: true });
8573
+ const lines = buffer.split("\n");
8574
+ buffer = lines.pop() || "";
8575
+ for (const line of lines) {
8576
+ if (!line.startsWith("data: ")) continue;
8577
+ const data = line.slice(6).trim();
8578
+ if (data === "[DONE]") {
8579
8579
  callbacks.onDone?.();
8580
8580
  return;
8581
8581
  }
8582
+ try {
8583
+ const event = JSON.parse(data);
8584
+ if (event.error) {
8585
+ callbacks.onError?.(event.message || event.error || "stream error");
8586
+ return;
8587
+ }
8588
+ if (event.type === "sources" && event.sources) {
8589
+ callbacks.onSources?.(event.sources);
8590
+ continue;
8591
+ }
8592
+ if (event.type === "tool_start" || event.type === "tool_end") {
8593
+ callbacks.onToolEvent?.({
8594
+ type: event.type,
8595
+ name: event.name,
8596
+ toolCallId: event.toolCallId,
8597
+ arguments: event.arguments,
8598
+ result: event.result,
8599
+ success: event.success,
8600
+ durationMs: event.durationMs
8601
+ });
8602
+ continue;
8603
+ }
8604
+ if (event.type === "searching") {
8605
+ if (event.searching) callbacks.onSearching?.(event.searching);
8606
+ continue;
8607
+ }
8608
+ if (event.type === "heartbeat") {
8609
+ continue;
8610
+ }
8611
+ if (event.reasoning) callbacks.onReasoning?.(event.reasoning);
8612
+ if (event.content) callbacks.onToken?.(event.content);
8613
+ if (event.done) {
8614
+ callbacks.onDone?.();
8615
+ return;
8616
+ }
8617
+ } catch {
8618
+ }
8619
+ }
8620
+ }
8621
+ } catch (err) {
8622
+ if (isAbort(err)) {
8623
+ callbacks.onAbort?.();
8624
+ return;
8625
+ }
8626
+ throw err;
8627
+ } finally {
8628
+ if (reader) {
8629
+ try {
8630
+ await reader.cancel();
8582
8631
  } catch {
8583
8632
  }
8584
8633
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "connectbase-client",
3
- "version": "3.27.1",
3
+ "version": "3.29.0",
4
4
  "description": "Connect Base JavaScript/TypeScript SDK for browser and Node.js",
5
5
  "repository": {
6
6
  "type": "git",