deel-local-cli 1.12.0 → 1.13.1

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.
Files changed (51) hide show
  1. package/README.ko.md +6 -5
  2. package/README.md +6 -5
  3. package/package.json +60 -60
  4. package/src/acp/map.js +30 -25
  5. package/src/acp/serve.js +810 -810
  6. package/src/agent/budget.js +156 -156
  7. package/src/agent/commit.js +32 -9
  8. package/src/agent/compact.js +441 -420
  9. package/src/agent/effort.js +308 -308
  10. package/src/agent/evolve.js +263 -263
  11. package/src/agent/loop.js +1728 -1707
  12. package/src/agent/memory.js +156 -156
  13. package/src/agent/mention.js +210 -210
  14. package/src/agent/recall.js +22 -9
  15. package/src/agent/session.js +1056 -1049
  16. package/src/backend/adapter.js +1355 -1159
  17. package/src/backend/cachemark.js +223 -150
  18. package/src/backend/detect.js +327 -327
  19. package/src/backend/http.js +3 -1
  20. package/src/backend/mcp.js +15 -3
  21. package/src/backend/price.js +69 -6
  22. package/src/backend/probe.js +17 -3
  23. package/src/backend/quota.js +274 -250
  24. package/src/backend/tokens.js +25 -3
  25. package/src/backend/toolfit.js +352 -352
  26. package/src/backend/wire.js +715 -600
  27. package/src/commands.js +3184 -3135
  28. package/src/config.js +39 -0
  29. package/src/i18n/en.js +545 -544
  30. package/src/i18n/ja.js +498 -497
  31. package/src/i18n/ko.js +592 -591
  32. package/src/i18n/zh.js +498 -497
  33. package/src/oneshot.js +625 -620
  34. package/src/pack/tar.js +23 -1
  35. package/src/pack/zip.js +19 -1
  36. package/src/plugins/manage.js +85 -5
  37. package/src/repl.js +2616 -2602
  38. package/src/safety/audit.js +22 -3
  39. package/src/safety/guard.js +124 -9
  40. package/src/safety/network.js +43 -0
  41. package/src/tools/fastgrep.js +15 -0
  42. package/src/tools/fsutil.js +291 -265
  43. package/src/tools/index.js +40 -3
  44. package/src/tools/jobs.js +87 -6
  45. package/src/tools/label.js +52 -0
  46. package/src/tools/spawn.js +114 -0
  47. package/src/tools/verify.js +358 -358
  48. package/src/tools/webfetch.js +28 -3
  49. package/src/ui/export.js +26 -10
  50. package/src/ui/pick.js +115 -115
  51. package/src/ui/status.js +617 -613
@@ -1,150 +1,223 @@
1
- import { estimateTokens } from './tokens.js';
2
-
3
- // 캐시 표식을 어디에 붙일까.
4
- //
5
- // ── 무엇이 문제였나 ─────────────────────────────────────────────────────
6
- //
7
- // 이 프로그램은 캐시 표식을 **어디에도 안 붙이고 있었다.** 그래서 서버가
8
- // 알아서 앞머리를 캐시해 주는 자리(Bedrock · OpenAI)에서는 정적 앞머리만
9
- // 잡혔고, 안 해 주는 자리(Anthropic 직통)에서는 하나도 안 잡혔다.
10
- //
11
- // 실제로 이런 모양이었다 — 열 번을 잇달아 부르는 동안 —
12
- //
13
- // 총 토큰 19,654 → 60,267 대화가 자란다
14
- // 캐시 5.9K → 5.9K 앞머리에서 멈춰 있다
15
- //
16
- // 5.9K 는 시스템 프롬프트(약 4,045)와 도구 정의(약 1,800)를 합친 값이다.
17
- // 즉 **자라는 대화는 한 번도 캐시된 적이 없다.** 매 걸음 전액 다시 보내고
18
- // 전액 다시 냈다.
19
- //
20
- // ── 어디에 붙이나 ───────────────────────────────────────────────────────
21
- //
22
- // 규격이 정한 렌더 차례는 tools → system → messages 다. 표식 하나는 그
23
- // 자리까지의 앞머리를 통째로 잡는다. 그래서 두 자리면 충분하다.
24
- //
25
- // 1. 시스템의 **굳은 부분 끝** — 도구까지 같이 잡힌다 (한 번 쓰고 계속 읽는다)
26
- // 2. 마지막 메시지 끝 — 걸음마다 앞으로 옮겨 간다 (자라는 대화를 잡는다)
27
- //
28
- // 2번이 이 파일의 핵심이다. 표식이 앞으로 옮겨 가면, 다음 요청은 지난번
29
- // 표식 자리까지를 **읽고** 그 뒤만 새로 쓴다. 안 옮기면 대화는 영영 캐시
30
- // 밖에 있다.
31
- //
32
- // ── 되돌아보기 창 ───────────────────────────────────────────────────────
33
- //
34
- // 표식은 뒤로 스무 자리까지만 지난 것을 찾는다. 도구를 길게 늘어놓는 턴은
35
- // 그 스무 자리를 넘길 수 있고, 그러면 **말없이** 캐시를 못 찾는다. 그래서
36
- // 대화가 길면 중간에 닻을 하나 더 박는다. 표식은 넷까지라 셋은 여유가 있다.
37
- //
38
- // ── 안 붙이는 자리 ──────────────────────────────────────────────────────
39
- //
40
- // 생각 블록에는 안 붙인다 — 우리가 나르기만 하는 블록이라 손대지 않는다.
41
- // 빈 글 블록에도 안 붙인다. 둘 다 거절당할 수 있고, 그 400 은 화면에서
42
- // 열쇠가 틀린 것과 구별이 안 된다.
43
-
44
- /** 표식 한 개. 기본 수명(5분)을 쓴다 — 한 시간짜리는 쓰기 값이 두 배다. */
45
- export const 표식 = { type: 'ephemeral' };
46
-
47
- /*
48
- * 시스템 글의 「굳은 부분 / 매 턴 바뀌는 부분」 을 나눠 담는 표.
49
- *
50
- * Symbol 인가: 표는 **우리끼리 쓰는 것**이라 전선에 나가면 안 된다.
51
- * JSON.stringify Symbol 열쇠를 아예 본다. 보통 이름으로 달아 두면
52
- * openai 규격에서는 메시지가 그대로 몸통에 실려 나가므로, 모르는 칸 하나가
53
- * 그 게이트웨이에서 400 을 만든다.
54
- *
55
- * Symbol.for 쓰는 것은 같은 파일이 두 번 올라와도 같은 표가 되게 하려는
56
- * 것이다 (검사에서 모듈을 따로 불러오는 자리가 있다).
57
- */
58
- export const 조각표 = Symbol.for('deel.시스템조각');
59
-
60
- /** 표식을 붙여도 되는 블록인가. */
61
- const 붙일수있나 = (b) => {
62
- const t = b?.type;
63
- if (t === 'thinking' || t === 'redacted_thinking') return false;
64
- if (t === 'text') return String(b.text ?? '').length > 0;
65
- return t === 'tool_result' || t === 'tool_use' || t === 'image' || t === 'document';
66
- };
67
-
68
- /** 대화가 이보다 길면 중간에 닻을 하나 더 박는다 (되돌아보기 창 스무 자리). */
69
- export const 닻문턱 = 20;
70
- /** 닻은 끝에서 이만큼 앞에 박는다. */
71
- const 닻거리 = 12;
72
-
73
- /**
74
- * 메시지 하나의 **마지막 성한 블록**에 표식을 붙인 사본을 돌려준다.
75
- * 붙일 자리가 없으면 원본 그대로.
76
- */
77
- export function 블록에붙이기(msg) {
78
- if (!msg) return msg;
79
- const 본래 = Array.isArray(msg.content)
80
- ? msg.content
81
- : [{ type: 'text', text: String(msg.content ?? '') }];
82
- let 자리 = -1;
83
- for (let i = 본래.length - 1; i >= 0; i--) {
84
- if (붙일수있나(본래[i])) { 자리 = i; break; }
85
- }
86
- if (자리 < 0) return msg;
87
- const = 본래.map((b, i) => (i === 자리 ? { ...b, cache_control: { ...표식 } } : b));
88
- return { ...msg, content: 새 };
89
- }
90
-
91
- /**
92
- * 시스템 조각들을 블록 배열로 만든다.
93
- *
94
- * 첫 조각이 **굳은 부분**이다 (agent/session.js 의 시스템조각). 그 끝에
95
- * 표식을 박으면 도구 정의까지 같이 잡힌다 — 규격이 tools 를 system 앞에
96
- * 렌더하기 때문이다.
97
- *
98
- * 조각이 하나뿐이면 그 끝에 박는다. 그때는 모드가 바뀔 때마다 다시 쓰지만,
99
- * 박는 것보다는 낫다.
100
- *
101
- * @param {string[]} 조각들
102
- * @param {boolean} 표식쓰나
103
- */
104
- export function 시스템블록(조각들, 표식쓰나) {
105
- const 있는것 = (조각들 ?? []).map((x) => String(x ?? '')).filter((x) => x.length);
106
- if (!있는것.length) return null;
107
- if (!표식쓰나) return 있는것.join('');
108
- const 굳은끝 = 0; // 조각이 굳은 부분이다
109
- return 있는것.map((text, i) => (
110
- i === 굳은끝 ? { type: 'text', text, cache_control: { ...표식 } } : { type: 'text', text }
111
- ));
112
- }
113
-
114
- /**
115
- * 메시지 배열에 옮겨 가는 표식을 붙인 **사본**을 돌려준다.
116
- *
117
- * @param {object[]} messages 차례합치기를 이미 거친 배열
118
- * @returns {object[]}
119
- */
120
- export function 메시지표식(messages) {
121
- const ms = messages ?? [];
122
- if (!ms.length) return ms;
123
- const 붙일자리 = new Set([ms.length - 1]);
124
- // 도구를 길게 늘어놓는 턴에서 되돌아보기 창을 넘기지 않게 닻을 하나 더.
125
- if (ms.length > 닻문턱) 붙일자리.add(ms.length - 1 - 닻거리);
126
- return ms.map((m, i) => (붙일자리.has(i) ? 블록에붙이기(m) : m));
127
- }
128
-
129
- /**
130
- * 요청에 표식을 붙일 만한가.
131
- *
132
- * 너무 작으면 서버가 잡는다. 붙여도 탈은 없지만, 잡히는 표식을 붙여
133
- * 놓고 「캐시를 켰다」 고 말하면 화면이 거짓말을 한다.
134
- *
135
- * 재는 자는 이 프로그램이 이미 쓰는 것으로 맞춘다(agent/session.js 의
136
- * estimateTokens). 여기는 **글자 수를 토큰의 두 배로** 치고 있었는데, 그건
137
- * 영어·코드에서만 맞는 셈이다. 한글은 글자당 1토큰이라 배로 나누면 절반이
138
- * 되고, Bedrock 처럼 최소가 4,096 인 창구에서는 실제로는 2,300 토큰밖에 안
139
- * 되는 대화에 「잡힐 만하다」 답했다. 그러면 안 잡힐 표식을 붙여 놓고
140
- * `/status` 가 「캐시 표식」 이라고 적는다 — 이 함수가 막겠다고 적어 둔 바로
141
- * 그 거짓말이다.
142
- */
143
- export function 잡힐만한가(messages, 최소토큰 = 1024) {
144
- let 토큰 = 0;
145
- for (const m of messages ?? []) {
146
- 토큰 += estimateTokens(typeof m?.content === 'string' ? m.content : JSON.stringify(m?.content ?? ''));
147
- if (토큰 > 최소토큰) return true;
148
- }
149
- return false;
150
- }
1
+ import { estimateTokens } from './tokens.js';
2
+
3
+ // 캐시 표식을 어디에 붙일까.
4
+ //
5
+ // ── 무엇이 문제였나 ─────────────────────────────────────────────────────
6
+ //
7
+ // 이 프로그램은 캐시 표식을 **어디에도 안 붙이고 있었다.** 그래서 서버가
8
+ // 알아서 앞머리를 캐시해 주는 자리(Bedrock · OpenAI)에서는 정적 앞머리만
9
+ // 잡혔고, 안 해 주는 자리(Anthropic 직통)에서는 하나도 안 잡혔다.
10
+ //
11
+ // 실제로 이런 모양이었다 — 열 번을 잇달아 부르는 동안 —
12
+ //
13
+ // 총 토큰 19,654 → 60,267 대화가 자란다
14
+ // 캐시 5.9K → 5.9K 앞머리에서 멈춰 있다
15
+ //
16
+ // 5.9K 는 시스템 프롬프트(약 4,045)와 도구 정의(약 1,800)를 합친 값이다.
17
+ // 즉 **자라는 대화는 한 번도 캐시된 적이 없다.** 매 걸음 전액 다시 보내고
18
+ // 전액 다시 냈다.
19
+ //
20
+ // ── 어디에 붙이나 ───────────────────────────────────────────────────────
21
+ //
22
+ // 규격이 정한 렌더 차례는 tools → system → messages 다. 표식 하나는 그
23
+ // 자리까지의 앞머리를 통째로 잡는다. 그래서 두 자리면 충분하다.
24
+ //
25
+ // 1. 시스템의 **굳은 부분 끝** — 도구까지 같이 잡힌다 (한 번 쓰고 계속 읽는다)
26
+ // 2. 마지막 메시지 끝 — 걸음마다 앞으로 옮겨 간다 (자라는 대화를 잡는다)
27
+ //
28
+ // 2번이 이 파일의 핵심이다. 표식이 앞으로 옮겨 가면, 다음 요청은 지난번
29
+ // 표식 자리까지를 **읽고** 그 뒤만 새로 쓴다. 안 옮기면 대화는 영영 캐시
30
+ // 밖에 있다.
31
+ //
32
+ // ── 되돌아보기 창 ───────────────────────────────────────────────────────
33
+ //
34
+ // 표식은 뒤로 스무 자리까지만 지난 것을 찾는다. 도구를 길게 늘어놓는 턴은
35
+ // 그 스무 자리를 넘길 수 있고, 그러면 **말없이** 캐시를 못 찾는다. 그래서
36
+ // 대화가 길면 중간에 닻을 하나 더 박는다. 표식은 넷까지라 셋은 여유가 있다.
37
+ //
38
+ // ── 안 붙이는 자리 ──────────────────────────────────────────────────────
39
+ //
40
+ // 생각 블록에는 안 붙인다 — 우리가 나르기만 하는 블록이라 손대지 않는다.
41
+ // 빈 글 블록에도 안 붙인다. 둘 다 거절당할 수 있고, 그 400 은 화면에서
42
+ // 열쇠가 틀린 것과 구별이 안 된다.
43
+
44
+ /** 표식 한 개. 기본 수명(5분)을 쓴다 — 한 시간짜리는 쓰기 값이 두 배다. */
45
+ export const 표식 = { type: 'ephemeral' };
46
+
47
+ /**
48
+ * 표식을 적는 **칸 이름**들.
49
+ *
50
+ * 같은 뜻을 규격이 다른 이름으로 적는다. 어느 쪽인지는 창구가 아니라
51
+ * **규격**이 정하므로, 여기서는 이름만 알고 고르는 것은 카드가 한다
52
+ * (backend/wire.js 표식칸).
53
+ *
54
+ * cache_control Anthropic 규격의 칸. Claude 를 앞에 둔 창구는
55
+ * 몸이 OpenAI 꼴이어도 칸으로 알아듣는다.
56
+ * prompt_cache_breakpoint OpenAI 규격의 칸. 규격 문서의 text 조각에
57
+ * 정식으로 들어 있다.
58
+ *
59
+ * 어느 것도 「업체별 예외」 가 아니다. 둘 다 문서에 있는 칸이고, 안 받는
60
+ * 서버는 400 으로 말해 준다 — 그러면 카드가 배운다(backend/wire.js).
61
+ */
62
+ export const 표식칸들 = ['cache_control', 'prompt_cache_breakpoint'];
63
+
64
+ /** 칸에 적을 값. 칸마다 값의 모양이 다르다. */
65
+ const 표식값 = (칸) => (칸 === 'prompt_cache_breakpoint'
66
+ ? { mode: 'explicit' }
67
+ : { ...표식 });
68
+
69
+ /*
70
+ * 시스템 글의 「굳은 부분 / 매 턴 바뀌는 부분」 을 나눠 담는 표.
71
+ *
72
+ * 왜 Symbol 인가: 이 표는 **우리끼리 쓰는 것**이라 전선에 나가면 안 된다.
73
+ * JSON.stringify 는 Symbol 열쇠를 아예 안 본다. 보통 이름으로 달아 두면
74
+ * openai 규격에서는 메시지가 그대로 몸통에 실려 나가므로, 모르는 칸 하나가
75
+ * 게이트웨이에서 400 만든다.
76
+ *
77
+ * Symbol.for 를 쓰는 것은 같은 파일이 두 번 올라와도 같은 표가 되게 하려는
78
+ * 것이다 (검사에서 모듈을 따로 불러오는 자리가 있다).
79
+ */
80
+ export const 조각표 = Symbol.for('deel.시스템조각');
81
+
82
+ /** 표식을 붙여도 되는 블록인가. */
83
+ const 붙일수있나 = (b) => {
84
+ const t = b?.type;
85
+ if (t === 'thinking' || t === 'redacted_thinking') return false;
86
+ if (t === 'text') return String(b.text ?? '').length > 0;
87
+ return t === 'tool_result' || t === 'tool_use' || t === 'image' || t === 'document';
88
+ };
89
+
90
+ /** 대화가 이보다 길면 중간에 닻을 하나 더 박는다 (되돌아보기 창 스무 자리). */
91
+ export const 닻문턱 = 20;
92
+ /** 닻은 끝에서 이만큼 앞에 박는다. */
93
+ const 닻거리 = 12;
94
+
95
+ /**
96
+ * 메시지 하나의 **마지막 성한 블록**에 표식을 붙인 사본을 돌려준다.
97
+ * 붙일 자리가 없으면 원본 그대로.
98
+ */
99
+ export function 블록에붙이기(msg, = 'cache_control') {
100
+ if (!msg) return msg;
101
+ const 본래 = Array.isArray(msg.content)
102
+ ? msg.content
103
+ : [{ type: 'text', text: String(msg.content ?? '') }];
104
+ let 자리 = -1;
105
+ for (let i = 본래.length - 1; i >= 0; i--) {
106
+ if (붙일수있나(본래[i])) { 자리 = i; break; }
107
+ }
108
+ if (자리 < 0) return msg;
109
+ const 새 = 본래.map((b, i) => (i === 자리 ? { ...b, [칸]: 표식값(칸) } : b));
110
+ return { ...msg, content: };
111
+ }
112
+
113
+ /**
114
+ * 시스템 글 조각들을 블록 배열로 만든다.
115
+ *
116
+ * 첫 조각이 **굳은 부분**이다 (agent/session.js 의 시스템조각). 그 끝에
117
+ * 표식을 박으면 도구 정의까지 같이 잡힌다 — 규격이 tools 를 system 앞에
118
+ * 렌더하기 때문이다.
119
+ *
120
+ * 조각이 하나뿐이면 그 끝에 박는다. 그때는 모드가 바뀔 때마다 다시 쓰지만,
121
+ * 박는 것보다는 낫다.
122
+ *
123
+ * @param {string[]} 조각들
124
+ * @param {boolean} 표식쓰나
125
+ */
126
+ export function 시스템블록(조각들, 표식쓰나, = 'cache_control') {
127
+ const 있는것 = (조각들 ?? []).map((x) => String(x ?? '')).filter((x) => x.length);
128
+ if (!있는것.length) return null;
129
+ if (!표식쓰나) return 있는것.join('');
130
+ const 굳은끝 = 0; // 조각이 굳은 부분이다
131
+ return 있는것.map((text, i) => (
132
+ i === 굳은끝 ? { type: 'text', text, [칸]: 표식값(칸) } : { type: 'text', text }
133
+ ));
134
+ }
135
+
136
+ /**
137
+ * 메시지 배열에 옮겨 가는 표식을 붙인 **사본**을 돌려준다.
138
+ *
139
+ * @param {object[]} messages 차례합치기를 이미 거친 배열
140
+ * @returns {object[]}
141
+ */
142
+ /**
143
+ * 자리에 표식을 **붙일 수 있나.** 없으면 한 칸씩 앞으로 물러선다.
144
+ *
145
+ * 도구만 부른 차례는 붙일 자리가 없다 — OpenAI 규격에서 그런 차례는
146
+ * `content: null` 이고(adapter.js assistantMessage), 글에는 표식을
147
+ * 붙인다(붙일수있나).
148
+ *
149
+ * 문제는 그때 `블록에붙이기` 가 **원본을 그대로 돌려준다**는 것이다. 아무
150
+ * 말도 안 하므로 그 요청은 닻 없이 나가고, 화면은 여전히 「캐시 표식」 이라고
151
+ * 적는다. 도구를 도는 턴은 `assistant(부름)` ↔ `tool(결과)` 이 번갈아 오므로
152
+ * **닻 자리가 절반 확률로 부름 차례에 떨어진다.** 하필 그 닻이 되돌아보기
153
+ * 창(스무 자리)을 넘기지 않으려고 박아 둔 것이라, 없어지면 앞이 통째로 다시 나간다.
154
+ *
155
+ * 그래서 못 붙이면 앞으로 물러서서 붙는 자리를 찾는다. 한 칸 앞이면 결과
156
+ * 차례라 거의 바로 걸린다.
157
+ */
158
+ function 붙는자리(ms, 부터) {
159
+ for (let i = Math.min(부터, ms.length - 1); i >= 0; i--) {
160
+ const 그자리 = 블록에붙이기(ms[i], 'cache_control');
161
+ if (그자리 !== ms[i]) return i;
162
+ }
163
+ return -1;
164
+ }
165
+
166
+ export function 메시지표식(messages, 칸 = 'cache_control') {
167
+ const ms = messages ?? [];
168
+ if (!ms.length) return ms;
169
+ const 붙일자리 = new Set();
170
+ const 꼬리 = 붙는자리(ms, ms.length - 1);
171
+ if (꼬리 >= 0) 붙일자리.add(꼬리);
172
+ // 도구를 길게 늘어놓는 턴에서 되돌아보기 창을 넘기지 않게 닻을 하나 더.
173
+ if (ms.length > 닻문턱) {
174
+ const 닻 = 붙는자리(ms, ms.length - 1 - 닻거리);
175
+ // 꼬리와 같은 자리면 닻이 아니다 — 그건 표식 하나짜리다.
176
+ if (닻 >= 0 && 닻 !== 꼬리) 붙일자리.add(닻);
177
+ }
178
+ return ms.map((m, i) => (붙일자리.has(i) ? 블록에붙이기(m, 칸) : m));
179
+ }
180
+
181
+ /**
182
+ * 이 요청에 표식을 붙일 만한가.
183
+ *
184
+ * 너무 작으면 서버가 안 잡는다. 붙여도 탈은 없지만, 안 잡히는 표식을 붙여
185
+ * 놓고 「캐시를 켰다」 고 말하면 화면이 거짓말을 한다.
186
+ *
187
+ * 재는 자는 이 프로그램이 이미 쓰는 것으로 맞춘다(agent/session.js 의
188
+ * estimateTokens). 여기는 **글자 수를 토큰의 두 배로** 치고 있었는데, 그건
189
+ * 영어·코드에서만 맞는 셈이다. 한글은 글자당 1토큰이라 두 배로 나누면 절반이
190
+ * 되고, Bedrock 처럼 최소가 4,096 인 창구에서는 실제로는 2,300 토큰밖에 안
191
+ * 되는 대화에 「잡힐 만하다」 고 답했다. 그러면 안 잡힐 표식을 붙여 놓고
192
+ * `/status` 가 「캐시 표식」 이라고 적는다 — 이 함수가 막겠다고 적어 둔 바로
193
+ * 그 거짓말이다.
194
+ */
195
+ export function 잡힐만한가(messages, 최소토큰 = 1024, 곁 = null) {
196
+ let 토큰 = 0;
197
+ /*
198
+ * 도구 정의도 **앞머리에 나가는 것**이다.
199
+ *
200
+ * 이 자가 메시지만 세고 있었다. 그런데 전선에 실릴 때 순서는
201
+ * `tools → system → messages` 라, 도구 스키마가 캐시 앞머리의 **맨 앞**
202
+ * 이다. 내장 도구 스무 개의 스키마는 1,800토큰쯤 된다.
203
+ *
204
+ * 그래서 시킴말과 사람말이 합쳐 500토큰인 판에서 이 자가 「문턱을 못
205
+ * 넘는다」 고 답했고, 표식을 하나도 안 붙였다. 실제로는 2,300토큰이라
206
+ * 넘고도 남는다. **덜 세서 캐시를 안 켠 것**인데, 화면에는 아무 말도
207
+ * 안 난다 — 그냥 매 턴 값이 더 나온다.
208
+ *
209
+ * 세는 것과 보내는 것이 다르면, 세는 쪽이 틀린 것이다.
210
+ */
211
+ if (곁) {
212
+ for (const 것 of [곁.tools, 곁.system]) {
213
+ if (것 == null) continue;
214
+ 토큰 += estimateTokens(typeof 것 === 'string' ? 것 : JSON.stringify(것));
215
+ if (토큰 > 최소토큰) return true;
216
+ }
217
+ }
218
+ for (const m of messages ?? []) {
219
+ 토큰 += estimateTokens(typeof m?.content === 'string' ? m.content : JSON.stringify(m?.content ?? ''));
220
+ if (토큰 > 최소토큰) return true;
221
+ }
222
+ return false;
223
+ }