@teamlearners/clawops 0.34.0 → 0.36.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/README.md CHANGED
@@ -372,6 +372,77 @@ for await (const m of (await client.messages.list()).autoPagingIter()) {
372
372
  const detail = await client.messages.get('MG0123456789abcdef');
373
373
  ```
374
374
 
375
+ ### 카카오 알림톡 (Kakao)
376
+
377
+ 승인된 템플릿으로 알림톡을 보냅니다. 발송에 필요한 채널·템플릿 ID 는 SDK 로 조회합니다.
378
+
379
+ ```typescript
380
+ // 1. 연결된 카카오 채널
381
+ const channels = await client.kakao.channels.list({ status: 'connected' });
382
+ const channel = channels.data[0];
383
+
384
+ // 2. 그 채널의 템플릿 — sendable: true 인 것만 보낼 수 있습니다
385
+ const templates = await client.kakao.templates.list({ channelId: channel.id });
386
+ const template = templates.data.find((t) => t.sendable);
387
+ console.log(template.variables); // ['#{고객명}'] — 이 목록을 모두 채워야 합니다
388
+
389
+ // 3. 발송
390
+ const msg = await client.messages.create({
391
+ to: '01012345678',
392
+ from: '07052358010',
393
+ kakao: {
394
+ channelId: channel.id,
395
+ templateId: template.id,
396
+ variables: { 고객명: '홍길동' }, // '#{고객명}' 표기도 받습니다
397
+ },
398
+ fallback: { body: '주문이 접수되었습니다.' },
399
+ });
400
+ console.log(msg.type); // 'ata'
401
+ ```
402
+
403
+ **본문은 템플릿이 정합니다.** 알림톡에는 `body`·`subject`·`mediaUrl` 을 실을 수 없고(컴파일
404
+ 에러입니다), 버튼·아이템 리스트·강조 문구는 카카오 검수를 받은 그대로 나갑니다 — 발송 요청으로
405
+ 바꿀 수 없습니다. 요청에서 바꿀 수 있는 것은 `variables` 값뿐입니다.
406
+
407
+ **대체발송(`fallback`)은 별도의 메시지 1건으로 기록되고 문자 단가로 청구됩니다.** 생략하면
408
+ 템플릿 본문이 그대로 문자로 나가고, `fallback: { disabled: true }` 면 알림톡 실패가 그대로
409
+ 실패로 남습니다.
410
+
411
+ 변수를 빠뜨리면 발송 전에 `400 kakao_variable_missing` 으로 막힙니다(카카오는 이런 요청도
412
+ 접수한 뒤 조용히 실패시키므로 ClawOps 가 미리 잡습니다). 사유는 `e.code` 로 분기하세요.
413
+
414
+ #### 채널 연결
415
+
416
+ 채널 연결은 두 단계입니다 — 인증번호는 카카오 비즈니스에 등록된 **담당자 휴대전화로만** 갑니다.
417
+
418
+ ```typescript
419
+ const categories = await client.kakao.channelCategories();
420
+
421
+ const requested = await client.kakao.channels.requestToken({
422
+ searchId: '@example',
423
+ phoneNumber: '010-1234-5678',
424
+ });
425
+ console.log(requested.phoneNumberMasked); // '010-****-5678'
426
+
427
+ // 담당자가 받은 인증번호로 완료 (이미 연결된 채널이면 인증번호를 쓰지 않고 기존 연결을 돌려줍니다)
428
+ const channel = await client.kakao.channels.connect({
429
+ searchId: requested.searchId,
430
+ phoneNumber: '010-1234-5678',
431
+ categoryCode: categories.data[0].code,
432
+ token: '394812',
433
+ });
434
+ ```
435
+
436
+ ⚠️ `connect()` 가 **타임아웃되면 재호출하지 마세요.** 이미 연결에 성공했을 수 있습니다 —
437
+ `channels.retrieve(id)` 로 실제 등록 여부를 확인하세요(이 조회는 몇 번을 불러도 안전합니다).
438
+ 연결에 **실패해도 인증번호는 소모되므로** 원인을 해결한 뒤 `requestToken()` 부터 다시 시작해야 합니다.
439
+
440
+ ⚠️ `channels.disconnect(id)` 는 **되돌릴 수 없고 그 채널의 알림톡 템플릿까지 함께 삭제합니다.**
441
+ 템플릿은 카카오 검수를 다시 받아야 합니다.
442
+
443
+ `channels.list()` 는 저장된 연결 정보를 그대로 돌려줍니다(빠릅니다). 카카오 쪽 상태까지 실제로
444
+ 확인하는 것은 `channels.retrieve()` 뿐이며, 이 호출이 `status` 를 갱신합니다.
445
+
375
446
  ### 솔라피(SOLAPI) 호환 — 문자만 ClawOps 로
376
447
 
377
448
  이미 솔라피 SDK 로 작성된 코드를 **그대로 두고** 문자(SMS/LMS/MMS)만 ClawOps 로 보냅니다.
@@ -493,14 +564,35 @@ const messageService = new ClawOpsMessageService({
493
564
  mode: 'sweep', // 리포트를 주기적으로 훑는다
494
565
  intervalMs: 5 * 60_000, // 기본 5분
495
566
  lookbackMs: 60 * 60_000, // 커서가 없을 때 거슬러 볼 구간. 기본 1시간
496
- on: ['3104', '3107', '3102'], // 기본값 (수신자 사유만)
567
+ except: RECOMMENDED_EXCLUDED_CODES, // 기본은 3XXX 전부. 덮으면 안 되는 것만 뺀다
497
568
  types: ['ATA'], // 훑을 타입. 친구톡까지 보려면 ['ATA','CTA','CTI']
498
569
  onFallback: (e) => logger.info({ messageId: e.messageId, statusCode: e.statusCode }, '대체발송'),
499
570
  onBlocked: (e) => logger.warn({ messageId: e.messageId, reason: e.reason }, '대체발송 못 함'),
571
+ onError: (e) => logger.error({ err: e }, '스윕 실패'), // ← 지정하지 않으면 조용히 넘어갑니다
500
572
  },
501
573
  });
502
574
  ```
503
575
 
576
+ ⚠️ **`onError` 를 지정하지 않으면 스윕이 통째로 실패해도 아무 소리가 나지 않습니다.** 솔라피
577
+ 조회가 막히거나 자격증명이 만료되면 다음 주기에 다시 시도할 뿐이고, 그동안 대체발송은
578
+ 한 건도 나가지 않습니다. 실패가 계속되면 **문자가 안 나가는데 로그도 조용한** 상태가 되므로,
579
+ 운영에 올리실 때는 반드시 지정하십시오.
580
+
581
+ `onError` 는 스윕 **전체**가 실패했을 때만 불립니다. 개별 건의 실패는 `onBlocked` 로 갑니다
582
+ (문구를 만들지 못했거나 ClawOps 가 거절한 경우) — 한 건이 실패해도 나머지는 그대로 진행됩니다.
583
+
584
+ 커서를 프로세스 밖에 보관하시려면 `initialCursor` 로 넣고 `onCursor` 로 받으십시오. 재시작 후
585
+ 같은 구간을 다시 훑는 것을 줄여 줍니다(멱등키가 있어 중복 발송은 어차피 막히지만, 요청은 아낍니다).
586
+
587
+ ```typescript
588
+ fallback: {
589
+ enabled: true,
590
+ mode: 'sweep',
591
+ initialCursor: await loadCursor(), // 없으면 lookbackMs 만큼 거슬러 본다
592
+ onCursor: (cursor) => saveCursor(cursor), // 직렬화 가능한 값만 담겨 있습니다
593
+ }
594
+ ```
595
+
504
596
  **켜기만 하면 됩니다.** 커서·저장소·크론이 필요 없습니다. 발송할 때 `customFields` 에 마커를
505
597
  심어 두고, 스윕이 그 마커가 있는 실패 건만 골라 문자를 보냅니다. 고객이 솔라피로 직접 보낸
506
598
  알림톡은 마커가 없어 건드리지 않습니다.
@@ -513,10 +605,37 @@ const messageService = new ClawOpsMessageService({
513
605
  ClawOps 가 대체 문자를 거절하면 `onBlocked` 에 `reason: 'send_rejected'` 로 알리고, 다음 스윕이
514
606
  다시 시도합니다(멱등키가 같아 중복 발송은 되지 않습니다).
515
607
 
516
- 기본 대상은 **수신자 사유만**입니다. 설정 오류(`3101` 발신프로필 무효 · `3105` 미등록 템플릿 ·
517
- `3106` 유효하지 않은 채널)를 문자로 덮으면 알림톡이 깨진 걸 모르게 되고, `3108`(발송 가능 시간
518
- 아님)을 대체하면 21시 이후 발송이 되어 야간 규제에 걸립니다. 이런 건은 보내지 않고
519
- `onBlocked` `reason: 'code_not_eligible'` **알리기만** 합니다.
608
+ ##### 어떤 코드를 대체발송할지
609
+
610
+ **기본은 3XXX 전부입니다.** 코드를 골라 담지 않았습니다 알림톡이라고 `31xx` 오는
611
+ 아니기 때문입니다. 실측에서 알림톡 건에 `3058`(전송경로 없음)이 돌아왔습니다. 목록을 추리면
612
+ 그렇게 새는 코드가 조용히 미발송이 됩니다.
613
+
614
+ 대신 **문자로 덮으면 안 되는 코드가 섞여 있습니다.** `RECOMMENDED_EXCLUDED_CODES` 를
615
+ `except` 로 넘겨 빼시는 것을 권합니다.
616
+
617
+ | 군 | 코드 | 덮으면 생기는 일 |
618
+ |---|---|---|
619
+ | 수신거부 | `3061` | 080 수신거부한 분께 문자가 나갑니다 |
620
+ | 스팸·발신번호 변작 차단 | `3054` `3055` `3059` `3112` `3113` | 막힌 발송을 문자로 우회하는 셈이 됩니다 |
621
+ | 설정 오류 | `3013` `3101` `3103` `3105` `3106` `3109` `3117` | 전건이 실패하는데 문자가 덮어, 알림톡이 깨진 걸 오래 모르게 됩니다. 단가도 조용히 올라갑니다 |
622
+ | 시간 규제 | `3108` | 발송 가능 시간이 아닌데 문자로 나갑니다 |
623
+
624
+ ```typescript
625
+ import { RECOMMENDED_EXCLUDED_CODES } from '@teamlearners/clawops/solapi';
626
+
627
+ fallback: {
628
+ enabled: true,
629
+ mode: 'sweep',
630
+ except: RECOMMENDED_EXCLUDED_CODES, // 권장 제외
631
+ // except: [...RECOMMENDED_EXCLUDED_CODES, '3032'], // 더 빼려면 이어 붙이십시오
632
+ }
633
+ ```
634
+
635
+ `on` 은 반대로 **대상을 좁힙니다** — 주시면 그 코드만 나갑니다. `on` 으로 좁힌 뒤 `except` 로
636
+ 다시 뺄 수도 있습니다. `on: []` 는 아무것도 보내지 않는다는 뜻이라 기본(전부)과 구별됩니다.
637
+
638
+ 빠진 코드는 조용히 사라지지 않고 `onBlocked` 에 `reason: 'code_not_eligible'` 로 **알립니다.**
520
639
 
521
640
  **상주 프로세스가 없다면**(서버리스·크론) 같은 일을 하는 함수를 직접 부르십시오.
522
641
 
@@ -598,16 +717,33 @@ try {
598
717
  const call = await client.calls.create({ to: '01012345678', from: '07052358010', url: 'https://...' });
599
718
  } catch (e) {
600
719
  if (e instanceof BadRequestError) {
601
- console.log(`잘못된 요청: ${e.statusCode} - ${JSON.stringify(e.body)}`);
720
+ console.log(`잘못된 요청: ${e.status} - ${JSON.stringify(e.body)}`);
602
721
  } else if (e instanceof AuthenticationError) {
603
- console.log(`유효하지 않은 API 키: ${e.statusCode}`);
722
+ console.log(`유효하지 않은 API 키: ${e.status}`);
604
723
  } else if (e instanceof NotFoundError) {
605
- console.log(`리소스를 찾을 수 없음: ${e.statusCode}`);
724
+ console.log(`리소스를 찾을 수 없음: ${e.status}`);
606
725
  }
607
726
  }
608
727
  ```
609
728
 
610
- 모든 에러는 `ClawOpsError`를 상속합니다. HTTP 에러는 `statusCode`, `body` 속성을 제공합니다.
729
+ 모든 에러는 `ClawOpsError`를 상속합니다. HTTP 에러는 `status`, `code`, `body` 속성을 제공합니다.
730
+
731
+ **사유는 `code` 로 분기하세요** — 한 상태 코드가 서로 다른 사유를 담고, 한글 메시지는 바뀔 수
732
+ 있습니다. 서버가 `code` 를 싣지 않은 응답에서는 `undefined` 입니다.
733
+
734
+ ```typescript
735
+ import { UnprocessableEntityError } from '@teamlearners/clawops';
736
+
737
+ try {
738
+ await client.messages.create({ to, from, kakao: { channelId, templateId, variables } });
739
+ } catch (e) {
740
+ if (e instanceof UnprocessableEntityError && e.code === 'recipient_blocked') {
741
+ // 수신거부 — 재시도하면 안 됩니다
742
+ } else if (e instanceof BadRequestError && e.code === 'kakao_variable_missing') {
743
+ // 템플릿 변수 누락 — templates.list() 의 variables 를 다시 확인하세요
744
+ }
745
+ }
746
+ ```
611
747
 
612
748
  | 에러 | 상태 코드 |
613
749
  | -------------------------- | --------- |
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var chunk2IHVQDIP_cjs = require('../chunk-2IHVQDIP.cjs');
4
- var chunkTVDUOKJD_cjs = require('../chunk-TVDUOKJD.cjs');
3
+ var chunkLKFNV2GI_cjs = require('../chunk-LKFNV2GI.cjs');
4
+ var chunkIYSPOOKH_cjs = require('../chunk-IYSPOOKH.cjs');
5
5
  var chunkQD2ZY4UP_cjs = require('../chunk-QD2ZY4UP.cjs');
6
6
  var fs = require('fs');
7
7
  var path = require('path');
@@ -729,7 +729,7 @@ var MAX_ERROR_MESSAGE_LENGTH = 200;
729
729
  function getSdkInfo() {
730
730
  return {
731
731
  name: "clawops-node",
732
- version: chunk2IHVQDIP_cjs.VERSION,
732
+ version: chunkLKFNV2GI_cjs.VERSION,
733
733
  runtime: `node/${process.versions.node}`,
734
734
  os: `${process.platform}/${os__default.default.arch()}`
735
735
  };
@@ -1474,7 +1474,7 @@ var ClawOpsAgent = class _ClawOpsAgent {
1474
1474
  constructor(options) {
1475
1475
  this._apiKey = options.apiKey ?? process.env["CLAWOPS_API_KEY"] ?? "";
1476
1476
  this._accountId = options.accountId ?? process.env["CLAWOPS_ACCOUNT_ID"] ?? "";
1477
- this._baseUrl = options.baseUrl ?? chunk2IHVQDIP_cjs.DEFAULT_BASE_URL;
1477
+ this._baseUrl = options.baseUrl ?? chunkLKFNV2GI_cjs.DEFAULT_BASE_URL;
1478
1478
  this._fromNumber = options.from;
1479
1479
  this._session = options.session;
1480
1480
  this._recording = options.recording ?? false;
@@ -1498,7 +1498,7 @@ var ClawOpsAgent = class _ClawOpsAgent {
1498
1498
  }
1499
1499
  static _validateGain(name, gain) {
1500
1500
  if (typeof gain !== "number" || !Number.isFinite(gain) || gain < 0) {
1501
- throw new chunkTVDUOKJD_cjs.AgentError(`${name}=${gain} must be a finite number >= 0`);
1501
+ throw new chunkIYSPOOKH_cjs.AgentError(`${name}=${gain} must be a finite number >= 0`);
1502
1502
  }
1503
1503
  return gain;
1504
1504
  }
@@ -1512,7 +1512,7 @@ var ClawOpsAgent = class _ClawOpsAgent {
1512
1512
  tool(nameOrTool, description, parameters, handler) {
1513
1513
  if (typeof nameOrTool === "string") {
1514
1514
  if (!description || !parameters || !handler) {
1515
- throw new chunkTVDUOKJD_cjs.AgentError(
1515
+ throw new chunkIYSPOOKH_cjs.AgentError(
1516
1516
  "tool(name, description, parameters, handler) requires all arguments."
1517
1517
  );
1518
1518
  }
@@ -1548,10 +1548,10 @@ var ClawOpsAgent = class _ClawOpsAgent {
1548
1548
  async connect() {
1549
1549
  if (this._controlWs) return;
1550
1550
  if (!this._apiKey) {
1551
- throw new chunkTVDUOKJD_cjs.AgentError("API key is required. Set CLAWOPS_API_KEY or pass apiKey option.");
1551
+ throw new chunkIYSPOOKH_cjs.AgentError("API key is required. Set CLAWOPS_API_KEY or pass apiKey option.");
1552
1552
  }
1553
1553
  if (!this._accountId) {
1554
- throw new chunkTVDUOKJD_cjs.AgentError(
1554
+ throw new chunkIYSPOOKH_cjs.AgentError(
1555
1555
  "Account ID is required. Set CLAWOPS_ACCOUNT_ID or pass accountId option."
1556
1556
  );
1557
1557
  }
@@ -1575,7 +1575,7 @@ var ClawOpsAgent = class _ClawOpsAgent {
1575
1575
  } catch {
1576
1576
  }
1577
1577
  } catch (err) {
1578
- throw new chunkTVDUOKJD_cjs.AgentConnectionError(
1578
+ throw new chunkIYSPOOKH_cjs.AgentConnectionError(
1579
1579
  `Failed to connect to ClawOps: ${err instanceof Error ? err.message : String(err)}`
1580
1580
  );
1581
1581
  }
@@ -1640,7 +1640,7 @@ var ClawOpsAgent = class _ClawOpsAgent {
1640
1640
  });
1641
1641
  if (resp.status !== 201) {
1642
1642
  const error = await resp.json();
1643
- throw new chunkTVDUOKJD_cjs.AgentError(`\uBC1C\uC2E0 \uC2E4\uD328 (${resp.status}): ${error["error"] ?? ""}`);
1643
+ throw new chunkIYSPOOKH_cjs.AgentError(`\uBC1C\uC2E0 \uC2E4\uD328 (${resp.status}): ${error["error"] ?? ""}`);
1644
1644
  }
1645
1645
  const data = await resp.json();
1646
1646
  const callSession = new CallSession({
@@ -1,5 +1,5 @@
1
- import { DEFAULT_BASE_URL, VERSION } from '../chunk-QNWXGJTJ.js';
2
- import { AgentError, AgentConnectionError } from '../chunk-36JABW7P.js';
1
+ import { DEFAULT_BASE_URL, VERSION } from '../chunk-XL2VWU36.js';
2
+ import { AgentError, AgentConnectionError } from '../chunk-VYGIWHRU.js';
3
3
  import { NOOP_LOGGER, resolveBuiltinTools, createAgentLogger, createPipelineLogger, BufferingCall, attachBuffered, ulawToPcm16, resamplePcm16, getBuiltinToolSchemas, BUILTIN_TOOL_NAMES, executeBuiltinTool, CALL_NOT_READY_RESULT, pcm16ToUlaw, applyUlawGain } from '../chunk-4YLSDMLL.js';
4
4
  export { BUILTIN_TOOL_NAMES, BuiltinTool, DECODE_TABLE, createAgentLogger, createPipelineLogger, executeBuiltinTool, getBuiltinToolSchemas, isBuiltinTool, pcm16ToUlaw, resamplePcm16, ulawToPcm16 } from '../chunk-4YLSDMLL.js';
5
5
  import * as fs from 'fs';
@@ -19,14 +19,26 @@ var APIStatusError = class extends APIError {
19
19
  status;
20
20
  body;
21
21
  headers;
22
+ /**
23
+ * 서버가 함께 보낸 기계 판독용 코드(`{ error, code }`). 없으면 undefined 다.
24
+ *
25
+ * 같은 상태 코드가 서로 다른 사유를 담는다 — 422 만 해도 수신거부(`recipient_blocked`)와
26
+ * 할당량 초과(`quota_exceeded`)가 갈리고, 한글 문구로 구분하면 문구가 바뀔 때 깨진다.
27
+ */
28
+ code;
22
29
  constructor(message, response, body, request) {
23
30
  super(message, request);
24
31
  this.name = "APIStatusError";
25
32
  this.status = response.status;
26
33
  this.body = body;
27
34
  this.headers = response.headers;
35
+ this.code = extractErrorCode(body);
28
36
  }
29
37
  };
38
+ function extractErrorCode(body) {
39
+ const code = body?.code;
40
+ return typeof code === "string" && code !== "" ? code : void 0;
41
+ }
30
42
  var BadRequestError = class extends APIStatusError {
31
43
  status = 400;
32
44
  constructor(message, response, body, request) {
@@ -175,5 +187,5 @@ exports.ServiceUnavailableError = ServiceUnavailableError;
175
187
  exports.SolapiBridgeError = SolapiBridgeError;
176
188
  exports.UnprocessableEntityError = UnprocessableEntityError;
177
189
  exports.makeStatusError = makeStatusError;
178
- //# sourceMappingURL=chunk-TVDUOKJD.cjs.map
179
- //# sourceMappingURL=chunk-TVDUOKJD.cjs.map
190
+ //# sourceMappingURL=chunk-IYSPOOKH.cjs.map
191
+ //# sourceMappingURL=chunk-IYSPOOKH.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/error.ts"],"names":[],"mappings":"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","file":"chunk-IYSPOOKH.cjs","sourcesContent":["/**\n * 서버가 에러 응답의 `code` 로 싣는 값.\n *\n * 문구가 아니라 **이 값으로 분기한다** — 한글 메시지는 바뀔 수 있다.\n *\n * ⚠️ 문자 도메인은 snake_case, 카카오 채널 도메인은 SCREAMING_CASE 다. 서버가 실제로\n * 그렇게 보내므로 SDK 가 임의로 정규화하지 않는다. 열린 유니온이라 목록에 없는 코드도\n * 그대로 실린다(자동완성만 돕는다).\n */\nexport type ClawOpsErrorCode =\n // 발송 일반\n | 'invalid_phone'\n | 'invalid_type'\n | 'invalid_input'\n | 'from_not_registered'\n | 'body_too_long'\n | 'sms_no_subject'\n | 'sms_no_media'\n | 'lms_no_media'\n | 'too_many_media'\n | 'invalid_media_ext'\n | 'media_download_failed'\n | 'type_not_supported'\n | 'messaging_blocked'\n | 'recipient_blocked'\n | 'quota_exceeded'\n | 'override_quota_exceeded'\n | 'no_active_subscription'\n // 알림톡 발송\n | 'kakao_required'\n | 'kakao_type_conflict'\n | 'kakao_body_not_allowed'\n | 'kakao_subject_not_allowed'\n | 'kakao_media_not_allowed'\n | 'kakao_channel_not_found'\n | 'kakao_template_not_found'\n | 'kakao_template_not_approved'\n | 'kakao_template_dormant'\n | 'kakao_variable_missing'\n | 'kakao_variable_unknown'\n | 'kakao_send_failed'\n | 'kakao_unavailable'\n | 'invalid_fallback_type'\n // 카카오 채널 연동\n | 'KAKAO_TOKEN_INVALID'\n | 'KAKAO_CHANNEL_ALREADY_LINKED'\n | 'KAKAO_CHANNEL_REJECTED'\n | 'KAKAO_RATE_LIMITED'\n | 'KAKAO_PROVIDER_UNAVAILABLE'\n // 공통\n | 'VALIDATION'\n | 'NOT_FOUND'\n | (string & {});\n\nexport class ClawOpsError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ClawOpsError';\n }\n}\n\nexport class APIError extends ClawOpsError {\n readonly request: { method: string; url: string };\n\n constructor(message: string, request: { method: string; url: string }) {\n super(message);\n this.name = 'APIError';\n this.request = request;\n }\n}\n\nexport class APIStatusError extends APIError {\n readonly status: number;\n readonly body: unknown;\n readonly headers: Headers | undefined;\n /**\n * 서버가 함께 보낸 기계 판독용 코드(`{ error, code }`). 없으면 undefined 다.\n *\n * 같은 상태 코드가 서로 다른 사유를 담는다 — 422 만 해도 수신거부(`recipient_blocked`)와\n * 할당량 초과(`quota_exceeded`)가 갈리고, 한글 문구로 구분하면 문구가 바뀔 때 깨진다.\n */\n readonly code: ClawOpsErrorCode | undefined;\n\n constructor(\n message: string,\n response: { status: number; headers?: Headers },\n body: unknown,\n request: { method: string; url: string },\n ) {\n super(message, request);\n this.name = 'APIStatusError';\n this.status = response.status;\n this.body = body;\n this.headers = response.headers;\n this.code = extractErrorCode(body);\n }\n}\n\n/** 응답 본문에서 `code` 를 꺼낸다. 문자열이 아니면 없는 것으로 본다. */\nfunction extractErrorCode(body: unknown): ClawOpsErrorCode | undefined {\n const code = (body as { code?: unknown } | null | undefined)?.code;\n return typeof code === 'string' && code !== '' ? code : undefined;\n}\n\nexport class BadRequestError extends APIStatusError {\n override readonly status = 400 as const;\n constructor(\n message: string,\n response: { status: number; headers?: Headers },\n body: unknown,\n request: { method: string; url: string },\n ) {\n super(message, response, body, request);\n this.name = 'BadRequestError';\n }\n}\n\nexport class AuthenticationError extends APIStatusError {\n override readonly status = 401 as const;\n constructor(\n message: string,\n response: { status: number; headers?: Headers },\n body: unknown,\n request: { method: string; url: string },\n ) {\n super(message, response, body, request);\n this.name = 'AuthenticationError';\n }\n}\n\nexport class PermissionDeniedError extends APIStatusError {\n override readonly status = 403 as const;\n constructor(\n message: string,\n response: { status: number; headers?: Headers },\n body: unknown,\n request: { method: string; url: string },\n ) {\n super(message, response, body, request);\n this.name = 'PermissionDeniedError';\n }\n}\n\nexport class NotFoundError extends APIStatusError {\n override readonly status = 404 as const;\n constructor(\n message: string,\n response: { status: number; headers?: Headers },\n body: unknown,\n request: { method: string; url: string },\n ) {\n super(message, response, body, request);\n this.name = 'NotFoundError';\n }\n}\n\nexport class ConflictError extends APIStatusError {\n override readonly status = 409 as const;\n constructor(\n message: string,\n response: { status: number; headers?: Headers },\n body: unknown,\n request: { method: string; url: string },\n ) {\n super(message, response, body, request);\n this.name = 'ConflictError';\n }\n}\n\nexport class UnprocessableEntityError extends APIStatusError {\n override readonly status = 422 as const;\n constructor(\n message: string,\n response: { status: number; headers?: Headers },\n body: unknown,\n request: { method: string; url: string },\n ) {\n super(message, response, body, request);\n this.name = 'UnprocessableEntityError';\n }\n}\n\n/**\n * HTTP 429 Too Many Requests.\n *\n * 동시 통화 한도 초과 등 일시적 제한. SDK는 자동 재시도(최대 2회, 지수 backoff)를 수행한다.\n * 즉각 피드백이 필요하면 client 생성 시 maxRetries: 0으로 재시도를 비활성화하라.\n */\nexport class RateLimitError extends APIStatusError {\n override readonly status = 429 as const;\n constructor(\n message: string,\n response: { status: number; headers?: Headers },\n body: unknown,\n request: { method: string; url: string },\n ) {\n super(message, response, body, request);\n this.name = 'RateLimitError';\n }\n}\n\nexport class InternalServerError extends APIStatusError {\n override readonly status = 500 as const;\n constructor(\n message: string,\n response: { status: number; headers?: Headers },\n body: unknown,\n request: { method: string; url: string },\n ) {\n super(message, response, body, request);\n this.name = 'InternalServerError';\n }\n}\n\nexport class ServiceUnavailableError extends APIStatusError {\n override readonly status = 503 as const;\n constructor(\n message: string,\n response: { status: number; headers?: Headers },\n body: unknown,\n request: { method: string; url: string },\n ) {\n super(message, response, body, request);\n this.name = 'ServiceUnavailableError';\n }\n}\n\nexport class APIConnectionError extends APIError {\n constructor(request: { method: string; url: string }, message = 'Connection error.') {\n super(message, request);\n this.name = 'APIConnectionError';\n }\n}\n\nexport class APITimeoutError extends APIConnectionError {\n constructor(request: { method: string; url: string }) {\n super(request, 'Request timed out.');\n this.name = 'APITimeoutError';\n }\n}\n\nexport class APIResponseValidationError extends APIError {\n readonly status: number;\n\n constructor(response: { status: number }, request: { method: string; url: string }) {\n super('API response validation failed.', request);\n this.name = 'APIResponseValidationError';\n this.status = response.status;\n }\n}\n\n/**\n * 솔라피 호환 레이어가 호출을 거절할 때 쓴다.\n * 조용히 다르게 동작하느니 여기서 멈추는 편이 낫다고 판단한 경우들이다.\n */\nexport class SolapiBridgeError extends ClawOpsError {\n constructor(message: string) {\n super(message);\n this.name = 'SolapiBridgeError';\n }\n}\n\nexport class AgentError extends ClawOpsError {\n constructor(message: string) {\n super(message);\n this.name = 'AgentError';\n }\n}\n\nexport class AgentConnectionError extends AgentError {\n constructor(message = 'Agent WebSocket connection failed.') {\n super(message);\n this.name = 'AgentConnectionError';\n }\n}\n\nconst STATUS_CODE_TO_ERROR: Record<\n number,\n new (\n message: string,\n response: { status: number; headers?: Headers },\n body: unknown,\n request: { method: string; url: string },\n ) => APIStatusError\n> = {\n 400: BadRequestError,\n 401: AuthenticationError,\n 403: PermissionDeniedError,\n 404: NotFoundError,\n 409: ConflictError,\n 422: UnprocessableEntityError,\n 429: RateLimitError,\n 500: InternalServerError,\n 503: ServiceUnavailableError,\n};\n\nexport function makeStatusError(\n status: number,\n body: unknown,\n headers: Headers | undefined,\n request: { method: string; url: string },\n): APIStatusError {\n let message = '';\n if (body && typeof body === 'object' && 'error' in body) {\n message = (body as { error: string }).error;\n } else {\n message = `HTTP ${status}`;\n }\n\n const ErrorClass = STATUS_CODE_TO_ERROR[status];\n if (ErrorClass) {\n return new ErrorClass(message, { status, headers }, body, request);\n }\n if (status >= 500) {\n return new InternalServerError(message, { status, headers }, body, request);\n }\n return new APIStatusError(message, { status, headers }, body, request);\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  // src/version.ts
4
- var VERSION = "0.34.0" ;
4
+ var VERSION = "0.36.0" ;
5
5
 
6
6
  // src/constants.ts
7
7
  var DEFAULT_BASE_URL = "https://api.claw-ops.com";
@@ -16,5 +16,5 @@ exports.DEFAULT_TIMEOUT = DEFAULT_TIMEOUT;
16
16
  exports.INITIAL_RETRY_DELAY = INITIAL_RETRY_DELAY;
17
17
  exports.MAX_RETRY_DELAY = MAX_RETRY_DELAY;
18
18
  exports.VERSION = VERSION;
19
- //# sourceMappingURL=chunk-2IHVQDIP.cjs.map
20
- //# sourceMappingURL=chunk-2IHVQDIP.cjs.map
19
+ //# sourceMappingURL=chunk-LKFNV2GI.cjs.map
20
+ //# sourceMappingURL=chunk-LKFNV2GI.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/version.ts","../src/constants.ts"],"names":[],"mappings":";;;AAEO,IAAM,OAAA,GACT,QAAA;;;ACHG,IAAM,gBAAA,GAAmB;AACzB,IAAM,mBAAA,GAAsB;AAC5B,IAAM,eAAA,GAAkB;AACxB,IAAM,mBAAA,GAAsB;AAC5B,IAAM,eAAA,GAAkB","file":"chunk-2IHVQDIP.cjs","sourcesContent":["declare const __PKG_VERSION__: string;\n\nexport const VERSION: string = typeof __PKG_VERSION__ !== 'undefined'\n ? __PKG_VERSION__\n : 'dev';\n","export const DEFAULT_BASE_URL = 'https://api.claw-ops.com';\nexport const DEFAULT_MAX_RETRIES = 2;\nexport const DEFAULT_TIMEOUT = 600_000; // ms\nexport const INITIAL_RETRY_DELAY = 500; // ms\nexport const MAX_RETRY_DELAY = 8_000; // ms\n"]}
1
+ {"version":3,"sources":["../src/version.ts","../src/constants.ts"],"names":[],"mappings":";;;AAEO,IAAM,OAAA,GACT,QAAA;;;ACHG,IAAM,gBAAA,GAAmB;AACzB,IAAM,mBAAA,GAAsB;AAC5B,IAAM,eAAA,GAAkB;AACxB,IAAM,mBAAA,GAAsB;AAC5B,IAAM,eAAA,GAAkB","file":"chunk-LKFNV2GI.cjs","sourcesContent":["declare const __PKG_VERSION__: string;\n\nexport const VERSION: string = typeof __PKG_VERSION__ !== 'undefined'\n ? __PKG_VERSION__\n : 'dev';\n","export const DEFAULT_BASE_URL = 'https://api.claw-ops.com';\nexport const DEFAULT_MAX_RETRIES = 2;\nexport const DEFAULT_TIMEOUT = 600_000; // ms\nexport const INITIAL_RETRY_DELAY = 500; // ms\nexport const MAX_RETRY_DELAY = 8_000; // ms\n"]}
@@ -17,14 +17,26 @@ var APIStatusError = class extends APIError {
17
17
  status;
18
18
  body;
19
19
  headers;
20
+ /**
21
+ * 서버가 함께 보낸 기계 판독용 코드(`{ error, code }`). 없으면 undefined 다.
22
+ *
23
+ * 같은 상태 코드가 서로 다른 사유를 담는다 — 422 만 해도 수신거부(`recipient_blocked`)와
24
+ * 할당량 초과(`quota_exceeded`)가 갈리고, 한글 문구로 구분하면 문구가 바뀔 때 깨진다.
25
+ */
26
+ code;
20
27
  constructor(message, response, body, request) {
21
28
  super(message, request);
22
29
  this.name = "APIStatusError";
23
30
  this.status = response.status;
24
31
  this.body = body;
25
32
  this.headers = response.headers;
33
+ this.code = extractErrorCode(body);
26
34
  }
27
35
  };
36
+ function extractErrorCode(body) {
37
+ const code = body?.code;
38
+ return typeof code === "string" && code !== "" ? code : void 0;
39
+ }
28
40
  var BadRequestError = class extends APIStatusError {
29
41
  status = 400;
30
42
  constructor(message, response, body, request) {
@@ -155,5 +167,5 @@ function makeStatusError(status, body, headers, request) {
155
167
  }
156
168
 
157
169
  export { APIConnectionError, APIError, APIResponseValidationError, APIStatusError, APITimeoutError, AgentConnectionError, AgentError, AuthenticationError, BadRequestError, ClawOpsError, ConflictError, InternalServerError, NotFoundError, PermissionDeniedError, RateLimitError, ServiceUnavailableError, SolapiBridgeError, UnprocessableEntityError, makeStatusError };
158
- //# sourceMappingURL=chunk-36JABW7P.js.map
159
- //# sourceMappingURL=chunk-36JABW7P.js.map
170
+ //# sourceMappingURL=chunk-VYGIWHRU.js.map
171
+ //# sourceMappingURL=chunk-VYGIWHRU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/error.ts"],"names":[],"mappings":"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","file":"chunk-VYGIWHRU.js","sourcesContent":["/**\n * 서버가 에러 응답의 `code` 로 싣는 값.\n *\n * 문구가 아니라 **이 값으로 분기한다** — 한글 메시지는 바뀔 수 있다.\n *\n * ⚠️ 문자 도메인은 snake_case, 카카오 채널 도메인은 SCREAMING_CASE 다. 서버가 실제로\n * 그렇게 보내므로 SDK 가 임의로 정규화하지 않는다. 열린 유니온이라 목록에 없는 코드도\n * 그대로 실린다(자동완성만 돕는다).\n */\nexport type ClawOpsErrorCode =\n // 발송 일반\n | 'invalid_phone'\n | 'invalid_type'\n | 'invalid_input'\n | 'from_not_registered'\n | 'body_too_long'\n | 'sms_no_subject'\n | 'sms_no_media'\n | 'lms_no_media'\n | 'too_many_media'\n | 'invalid_media_ext'\n | 'media_download_failed'\n | 'type_not_supported'\n | 'messaging_blocked'\n | 'recipient_blocked'\n | 'quota_exceeded'\n | 'override_quota_exceeded'\n | 'no_active_subscription'\n // 알림톡 발송\n | 'kakao_required'\n | 'kakao_type_conflict'\n | 'kakao_body_not_allowed'\n | 'kakao_subject_not_allowed'\n | 'kakao_media_not_allowed'\n | 'kakao_channel_not_found'\n | 'kakao_template_not_found'\n | 'kakao_template_not_approved'\n | 'kakao_template_dormant'\n | 'kakao_variable_missing'\n | 'kakao_variable_unknown'\n | 'kakao_send_failed'\n | 'kakao_unavailable'\n | 'invalid_fallback_type'\n // 카카오 채널 연동\n | 'KAKAO_TOKEN_INVALID'\n | 'KAKAO_CHANNEL_ALREADY_LINKED'\n | 'KAKAO_CHANNEL_REJECTED'\n | 'KAKAO_RATE_LIMITED'\n | 'KAKAO_PROVIDER_UNAVAILABLE'\n // 공통\n | 'VALIDATION'\n | 'NOT_FOUND'\n | (string & {});\n\nexport class ClawOpsError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ClawOpsError';\n }\n}\n\nexport class APIError extends ClawOpsError {\n readonly request: { method: string; url: string };\n\n constructor(message: string, request: { method: string; url: string }) {\n super(message);\n this.name = 'APIError';\n this.request = request;\n }\n}\n\nexport class APIStatusError extends APIError {\n readonly status: number;\n readonly body: unknown;\n readonly headers: Headers | undefined;\n /**\n * 서버가 함께 보낸 기계 판독용 코드(`{ error, code }`). 없으면 undefined 다.\n *\n * 같은 상태 코드가 서로 다른 사유를 담는다 — 422 만 해도 수신거부(`recipient_blocked`)와\n * 할당량 초과(`quota_exceeded`)가 갈리고, 한글 문구로 구분하면 문구가 바뀔 때 깨진다.\n */\n readonly code: ClawOpsErrorCode | undefined;\n\n constructor(\n message: string,\n response: { status: number; headers?: Headers },\n body: unknown,\n request: { method: string; url: string },\n ) {\n super(message, request);\n this.name = 'APIStatusError';\n this.status = response.status;\n this.body = body;\n this.headers = response.headers;\n this.code = extractErrorCode(body);\n }\n}\n\n/** 응답 본문에서 `code` 를 꺼낸다. 문자열이 아니면 없는 것으로 본다. */\nfunction extractErrorCode(body: unknown): ClawOpsErrorCode | undefined {\n const code = (body as { code?: unknown } | null | undefined)?.code;\n return typeof code === 'string' && code !== '' ? code : undefined;\n}\n\nexport class BadRequestError extends APIStatusError {\n override readonly status = 400 as const;\n constructor(\n message: string,\n response: { status: number; headers?: Headers },\n body: unknown,\n request: { method: string; url: string },\n ) {\n super(message, response, body, request);\n this.name = 'BadRequestError';\n }\n}\n\nexport class AuthenticationError extends APIStatusError {\n override readonly status = 401 as const;\n constructor(\n message: string,\n response: { status: number; headers?: Headers },\n body: unknown,\n request: { method: string; url: string },\n ) {\n super(message, response, body, request);\n this.name = 'AuthenticationError';\n }\n}\n\nexport class PermissionDeniedError extends APIStatusError {\n override readonly status = 403 as const;\n constructor(\n message: string,\n response: { status: number; headers?: Headers },\n body: unknown,\n request: { method: string; url: string },\n ) {\n super(message, response, body, request);\n this.name = 'PermissionDeniedError';\n }\n}\n\nexport class NotFoundError extends APIStatusError {\n override readonly status = 404 as const;\n constructor(\n message: string,\n response: { status: number; headers?: Headers },\n body: unknown,\n request: { method: string; url: string },\n ) {\n super(message, response, body, request);\n this.name = 'NotFoundError';\n }\n}\n\nexport class ConflictError extends APIStatusError {\n override readonly status = 409 as const;\n constructor(\n message: string,\n response: { status: number; headers?: Headers },\n body: unknown,\n request: { method: string; url: string },\n ) {\n super(message, response, body, request);\n this.name = 'ConflictError';\n }\n}\n\nexport class UnprocessableEntityError extends APIStatusError {\n override readonly status = 422 as const;\n constructor(\n message: string,\n response: { status: number; headers?: Headers },\n body: unknown,\n request: { method: string; url: string },\n ) {\n super(message, response, body, request);\n this.name = 'UnprocessableEntityError';\n }\n}\n\n/**\n * HTTP 429 Too Many Requests.\n *\n * 동시 통화 한도 초과 등 일시적 제한. SDK는 자동 재시도(최대 2회, 지수 backoff)를 수행한다.\n * 즉각 피드백이 필요하면 client 생성 시 maxRetries: 0으로 재시도를 비활성화하라.\n */\nexport class RateLimitError extends APIStatusError {\n override readonly status = 429 as const;\n constructor(\n message: string,\n response: { status: number; headers?: Headers },\n body: unknown,\n request: { method: string; url: string },\n ) {\n super(message, response, body, request);\n this.name = 'RateLimitError';\n }\n}\n\nexport class InternalServerError extends APIStatusError {\n override readonly status = 500 as const;\n constructor(\n message: string,\n response: { status: number; headers?: Headers },\n body: unknown,\n request: { method: string; url: string },\n ) {\n super(message, response, body, request);\n this.name = 'InternalServerError';\n }\n}\n\nexport class ServiceUnavailableError extends APIStatusError {\n override readonly status = 503 as const;\n constructor(\n message: string,\n response: { status: number; headers?: Headers },\n body: unknown,\n request: { method: string; url: string },\n ) {\n super(message, response, body, request);\n this.name = 'ServiceUnavailableError';\n }\n}\n\nexport class APIConnectionError extends APIError {\n constructor(request: { method: string; url: string }, message = 'Connection error.') {\n super(message, request);\n this.name = 'APIConnectionError';\n }\n}\n\nexport class APITimeoutError extends APIConnectionError {\n constructor(request: { method: string; url: string }) {\n super(request, 'Request timed out.');\n this.name = 'APITimeoutError';\n }\n}\n\nexport class APIResponseValidationError extends APIError {\n readonly status: number;\n\n constructor(response: { status: number }, request: { method: string; url: string }) {\n super('API response validation failed.', request);\n this.name = 'APIResponseValidationError';\n this.status = response.status;\n }\n}\n\n/**\n * 솔라피 호환 레이어가 호출을 거절할 때 쓴다.\n * 조용히 다르게 동작하느니 여기서 멈추는 편이 낫다고 판단한 경우들이다.\n */\nexport class SolapiBridgeError extends ClawOpsError {\n constructor(message: string) {\n super(message);\n this.name = 'SolapiBridgeError';\n }\n}\n\nexport class AgentError extends ClawOpsError {\n constructor(message: string) {\n super(message);\n this.name = 'AgentError';\n }\n}\n\nexport class AgentConnectionError extends AgentError {\n constructor(message = 'Agent WebSocket connection failed.') {\n super(message);\n this.name = 'AgentConnectionError';\n }\n}\n\nconst STATUS_CODE_TO_ERROR: Record<\n number,\n new (\n message: string,\n response: { status: number; headers?: Headers },\n body: unknown,\n request: { method: string; url: string },\n ) => APIStatusError\n> = {\n 400: BadRequestError,\n 401: AuthenticationError,\n 403: PermissionDeniedError,\n 404: NotFoundError,\n 409: ConflictError,\n 422: UnprocessableEntityError,\n 429: RateLimitError,\n 500: InternalServerError,\n 503: ServiceUnavailableError,\n};\n\nexport function makeStatusError(\n status: number,\n body: unknown,\n headers: Headers | undefined,\n request: { method: string; url: string },\n): APIStatusError {\n let message = '';\n if (body && typeof body === 'object' && 'error' in body) {\n message = (body as { error: string }).error;\n } else {\n message = `HTTP ${status}`;\n }\n\n const ErrorClass = STATUS_CODE_TO_ERROR[status];\n if (ErrorClass) {\n return new ErrorClass(message, { status, headers }, body, request);\n }\n if (status >= 500) {\n return new InternalServerError(message, { status, headers }, body, request);\n }\n return new APIStatusError(message, { status, headers }, body, request);\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var VERSION = "0.34.0" ;
2
+ var VERSION = "0.36.0" ;
3
3
 
4
4
  // src/constants.ts
5
5
  var DEFAULT_BASE_URL = "https://api.claw-ops.com";
@@ -9,5 +9,5 @@ var INITIAL_RETRY_DELAY = 500;
9
9
  var MAX_RETRY_DELAY = 8e3;
10
10
 
11
11
  export { DEFAULT_BASE_URL, DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT, INITIAL_RETRY_DELAY, MAX_RETRY_DELAY, VERSION };
12
- //# sourceMappingURL=chunk-QNWXGJTJ.js.map
13
- //# sourceMappingURL=chunk-QNWXGJTJ.js.map
12
+ //# sourceMappingURL=chunk-XL2VWU36.js.map
13
+ //# sourceMappingURL=chunk-XL2VWU36.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/version.ts","../src/constants.ts"],"names":[],"mappings":";AAEO,IAAM,OAAA,GACT,QAAA;;;ACHG,IAAM,gBAAA,GAAmB;AACzB,IAAM,mBAAA,GAAsB;AAC5B,IAAM,eAAA,GAAkB;AACxB,IAAM,mBAAA,GAAsB;AAC5B,IAAM,eAAA,GAAkB","file":"chunk-QNWXGJTJ.js","sourcesContent":["declare const __PKG_VERSION__: string;\n\nexport const VERSION: string = typeof __PKG_VERSION__ !== 'undefined'\n ? __PKG_VERSION__\n : 'dev';\n","export const DEFAULT_BASE_URL = 'https://api.claw-ops.com';\nexport const DEFAULT_MAX_RETRIES = 2;\nexport const DEFAULT_TIMEOUT = 600_000; // ms\nexport const INITIAL_RETRY_DELAY = 500; // ms\nexport const MAX_RETRY_DELAY = 8_000; // ms\n"]}
1
+ {"version":3,"sources":["../src/version.ts","../src/constants.ts"],"names":[],"mappings":";AAEO,IAAM,OAAA,GACT,QAAA;;;ACHG,IAAM,gBAAA,GAAmB;AACzB,IAAM,mBAAA,GAAsB;AAC5B,IAAM,eAAA,GAAkB;AACxB,IAAM,mBAAA,GAAsB;AAC5B,IAAM,eAAA,GAAkB","file":"chunk-XL2VWU36.js","sourcesContent":["declare const __PKG_VERSION__: string;\n\nexport const VERSION: string = typeof __PKG_VERSION__ !== 'undefined'\n ? __PKG_VERSION__\n : 'dev';\n","export const DEFAULT_BASE_URL = 'https://api.claw-ops.com';\nexport const DEFAULT_MAX_RETRIES = 2;\nexport const DEFAULT_TIMEOUT = 600_000; // ms\nexport const INITIAL_RETRY_DELAY = 500; // ms\nexport const MAX_RETRY_DELAY = 8_000; // ms\n"]}