deel-local-cli 1.12.0 → 1.13.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.ko.md +5 -5
- package/README.md +5 -5
- package/package.json +60 -60
- package/src/acp/map.js +367 -362
- package/src/agent/commit.js +534 -511
- package/src/agent/compact.js +36 -15
- package/src/agent/loop.js +25 -4
- package/src/agent/recall.js +222 -209
- package/src/agent/session.js +10 -3
- package/src/backend/adapter.js +219 -23
- package/src/backend/cachemark.js +82 -9
- package/src/backend/http.js +460 -458
- package/src/backend/mcp.js +407 -395
- package/src/backend/price.js +260 -197
- package/src/backend/probe.js +487 -473
- package/src/backend/quota.js +35 -11
- package/src/backend/tokens.js +59 -37
- package/src/backend/wire.js +121 -6
- package/src/commands.js +56 -7
- package/src/config.js +283 -244
- package/src/i18n/en.js +1 -0
- package/src/i18n/ja.js +1 -0
- package/src/i18n/ko.js +1 -0
- package/src/i18n/zh.js +1 -0
- package/src/oneshot.js +6 -1
- package/src/pack/tar.js +154 -132
- package/src/pack/zip.js +235 -217
- package/src/plugins/manage.js +416 -336
- package/src/repl.js +17 -3
- package/src/safety/audit.js +148 -129
- package/src/safety/guard.js +641 -526
- package/src/safety/network.js +200 -157
- package/src/tools/fastgrep.js +229 -214
- package/src/tools/fsutil.js +28 -2
- package/src/tools/index.js +2560 -2523
- package/src/tools/jobs.js +876 -795
- package/src/tools/label.js +52 -0
- package/src/tools/spawn.js +213 -99
- package/src/tools/webfetch.js +418 -393
- package/src/ui/export.js +233 -217
- package/src/ui/status.js +5 -1
package/src/backend/quota.js
CHANGED
|
@@ -28,17 +28,29 @@ const 요청남음 = [
|
|
|
28
28
|
'x-ratelimit-remaining-requests', 'ratelimit-remaining-requests', 'x-ratelimit-remaining',
|
|
29
29
|
'anthropic-ratelimit-requests-remaining',
|
|
30
30
|
];
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
/*
|
|
32
|
+
* 토큰 통은 **여러 개일 수 있다.**
|
|
33
|
+
*
|
|
34
|
+
* Anthropic 은 입력과 출력을 따로 센다. 둘은 서로 다른 통이고, 둘 중
|
|
35
|
+
* **먼저 바닥나는 쪽**이 곧 막히는 쪽이다.
|
|
36
|
+
*
|
|
37
|
+
* 여태 이걸 한 줄짜리 이름 목록으로 두고 골라() 로 「처음 있는 것」 을
|
|
38
|
+
* 집었다. 목록 차례상 입력이 앞이라, 입력 100,000 · 출력 0 인 응답에서
|
|
39
|
+
* 남은 양을 **100,000 으로 적었다.** 그러면 미리기다릴까() 가 「아직
|
|
40
|
+
* 넉넉하다」 고 보고 곧장 다음 요청을 보내 429 를 맞는다. 주석에는
|
|
41
|
+
* 「먼저 바닥나는 쪽」 이라고 적혀 있었는데 코드는 그 반대를 했다.
|
|
42
|
+
*
|
|
43
|
+
* 남음과 한도를 **짝으로** 묶어 두고, 남은 것이 가장 적은 짝을 쓴다.
|
|
44
|
+
* 짝으로 안 묶으면 화면이 「출력 0 / 입력 한도 100만」 같은 소리를 한다.
|
|
45
|
+
*/
|
|
46
|
+
const 토큰통 = [
|
|
47
|
+
['x-ratelimit-remaining-tokens', 'x-ratelimit-limit-tokens'],
|
|
48
|
+
['ratelimit-remaining-tokens', 'ratelimit-limit-tokens'],
|
|
49
|
+
['anthropic-ratelimit-tokens-remaining', 'anthropic-ratelimit-tokens-limit'],
|
|
50
|
+
['anthropic-ratelimit-input-tokens-remaining', 'anthropic-ratelimit-input-tokens-limit'],
|
|
51
|
+
['anthropic-ratelimit-output-tokens-remaining', 'anthropic-ratelimit-output-tokens-limit'],
|
|
36
52
|
];
|
|
37
53
|
const 요청한도 = ['x-ratelimit-limit-requests', 'ratelimit-limit-requests', 'anthropic-ratelimit-requests-limit'];
|
|
38
|
-
const 토큰한도 = [
|
|
39
|
-
'x-ratelimit-limit-tokens', 'ratelimit-limit-tokens', 'anthropic-ratelimit-tokens-limit',
|
|
40
|
-
'anthropic-ratelimit-input-tokens-limit', 'anthropic-ratelimit-output-tokens-limit',
|
|
41
|
-
];
|
|
42
54
|
/*
|
|
43
55
|
* 풀리는 때. Anthropic 은 초가 아니라 **날짜(RFC 3339)** 로 준다 —
|
|
44
56
|
* `언제풀리나` 가 Date.parse 로 받아 지금과의 차이를 초로 바꾼다.
|
|
@@ -87,6 +99,19 @@ export function 언제풀리나(v) {
|
|
|
87
99
|
return null;
|
|
88
100
|
}
|
|
89
101
|
|
|
102
|
+
/** 토큰 통 중 **가장 적게 남은** 짝. 없으면 둘 다 null. */
|
|
103
|
+
function 가장빠듯한통(머리) {
|
|
104
|
+
let 고른것 = { 토큰: null, 토큰한도: null };
|
|
105
|
+
for (const [남음이름, 한도이름] of 토큰통) {
|
|
106
|
+
const 남 = 숫자(골라(머리, [남음이름]));
|
|
107
|
+
if (남 === null) continue;
|
|
108
|
+
if (고른것.토큰 === null || 남 < 고른것.토큰) {
|
|
109
|
+
고른것 = { 토큰: 남, 토큰한도: 숫자(골라(머리, [한도이름])) };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return 고른것;
|
|
113
|
+
}
|
|
114
|
+
|
|
90
115
|
/**
|
|
91
116
|
* 응답 머리에서 할당량을 읽는다.
|
|
92
117
|
*
|
|
@@ -96,8 +121,7 @@ export function 할당량읽기(머리) {
|
|
|
96
121
|
const 것 = {
|
|
97
122
|
요청: 숫자(골라(머리, 요청남음)),
|
|
98
123
|
요청한도: 숫자(골라(머리, 요청한도)),
|
|
99
|
-
|
|
100
|
-
토큰한도: 숫자(골라(머리, 토큰한도)),
|
|
124
|
+
...가장빠듯한통(머리),
|
|
101
125
|
풀림: 언제풀리나(골라(머리, 다시언제)),
|
|
102
126
|
};
|
|
103
127
|
것.있나 = 것.요청 !== null || 것.토큰 !== null || 것.풀림 !== null;
|
package/src/backend/tokens.js
CHANGED
|
@@ -1,37 +1,59 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* 토큰을 대략 센다.
|
|
3
|
-
*
|
|
4
|
-
* ── 왜 여기 있나 ────────────────────────────────────────────────────────
|
|
5
|
-
*
|
|
6
|
-
* 이 함수는 agent/session.js 안에 있었다. 그런데 재는 자가 필요한 자리가
|
|
7
|
-
* 거기 하나가 아니다 — backend/cachemark.js 도 「이 대화가 캐시에 잡힐 만한
|
|
8
|
-
* 크기인가」 를 재야 하는데, cachemark 는 아무것도 안 들여오는 잎 모듈이라
|
|
9
|
-
* session.js 를 부르면 순환이 된다(session 이 cachemark 의 조각표를 쓴다).
|
|
10
|
-
*
|
|
11
|
-
* 그래서 cachemark 는 제 나름대로 **글자 수를 토큰의 두 배로** 쳤다. 영어와
|
|
12
|
-
* 코드에서만 맞는 셈이라, 실제로는 2,300토큰인 대화를 4,096토큰 문턱을 넘었다고
|
|
13
|
-
* 답했다. 안 잡힐 표식을 붙여 놓고 화면에는 「캐시 표식」 이라고 적혔다.
|
|
14
|
-
*
|
|
15
|
-
* 재는 자가 둘이면 언젠가 둘이 어긋난다. 그래서 자를 **잎으로 내려서** 한
|
|
16
|
-
* 벌만 둔다. session.js 는 이것을 그대로 다시 내보내므로 부르던 자리는 그대로다.
|
|
17
|
-
*
|
|
18
|
-
* ── 얼마나 맞나 ────────────────────────────────────────────────────────
|
|
19
|
-
*
|
|
20
|
-
* 정확한 토크나이저는 안 쓴다 — 의존성 0개가 이 프로그램의 약속이고, 창구마다
|
|
21
|
-
* 토크나이저가 다르므로 하나를 골라 박아도 다른 창구에서는 또 틀린다.
|
|
22
|
-
* 대신 서버가 실제 값을 알려 주면 그 값으로 배수를 배운다(session.js 의 배운다).
|
|
23
|
-
* 여기는 **배우기 전의 첫 짐작**이다.
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* 그 밖(영문·코드)
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
1
|
+
/*
|
|
2
|
+
* 토큰을 대략 센다.
|
|
3
|
+
*
|
|
4
|
+
* ── 왜 여기 있나 ────────────────────────────────────────────────────────
|
|
5
|
+
*
|
|
6
|
+
* 이 함수는 agent/session.js 안에 있었다. 그런데 재는 자가 필요한 자리가
|
|
7
|
+
* 거기 하나가 아니다 — backend/cachemark.js 도 「이 대화가 캐시에 잡힐 만한
|
|
8
|
+
* 크기인가」 를 재야 하는데, cachemark 는 아무것도 안 들여오는 잎 모듈이라
|
|
9
|
+
* session.js 를 부르면 순환이 된다(session 이 cachemark 의 조각표를 쓴다).
|
|
10
|
+
*
|
|
11
|
+
* 그래서 cachemark 는 제 나름대로 **글자 수를 토큰의 두 배로** 쳤다. 영어와
|
|
12
|
+
* 코드에서만 맞는 셈이라, 실제로는 2,300토큰인 대화를 4,096토큰 문턱을 넘었다고
|
|
13
|
+
* 답했다. 안 잡힐 표식을 붙여 놓고 화면에는 「캐시 표식」 이라고 적혔다.
|
|
14
|
+
*
|
|
15
|
+
* 재는 자가 둘이면 언젠가 둘이 어긋난다. 그래서 자를 **잎으로 내려서** 한
|
|
16
|
+
* 벌만 둔다. session.js 는 이것을 그대로 다시 내보내므로 부르던 자리는 그대로다.
|
|
17
|
+
*
|
|
18
|
+
* ── 얼마나 맞나 ────────────────────────────────────────────────────────
|
|
19
|
+
*
|
|
20
|
+
* 정확한 토크나이저는 안 쓴다 — 의존성 0개가 이 프로그램의 약속이고, 창구마다
|
|
21
|
+
* 토크나이저가 다르므로 하나를 골라 박아도 다른 창구에서는 또 틀린다.
|
|
22
|
+
* 대신 서버가 실제 값을 알려 주면 그 값으로 배수를 배운다(session.js 의 배운다).
|
|
23
|
+
* 여기는 **배우기 전의 첫 짐작**이다.
|
|
24
|
+
*
|
|
25
|
+
* 한글(자모 포함)·일본어·한자 글자당 1토큰
|
|
26
|
+
* 그 밖(영문·코드) 3.6글자당 1토큰
|
|
27
|
+
*/
|
|
28
|
+
/*
|
|
29
|
+
* ── 한글은 완성형만 한글이 아니다 ───────────────────────────────────────
|
|
30
|
+
*
|
|
31
|
+
* 여기가 완성형 음절(가–힣)만 셌다. 그래서 아래 것들이 **영문 취급**을 받아
|
|
32
|
+
* 3.6분의 1로 세어졌다:
|
|
33
|
+
*
|
|
34
|
+
* ㅋㅋㅋ ㅎㅎ ㅠㅠ ㅡㅡ 호환 자모 (U+3130–318F) — 대화에 제일 흔하다
|
|
35
|
+
* ᄀ ᅡ ᆨ 첫가끝 자모 (U+1100–11FF) — 맥에서 만든 파일
|
|
36
|
+
* 이름과 그 파일 내용이 이 꼴(NFD)로 온다
|
|
37
|
+
*
|
|
38
|
+
* 덜 센 값은 화면 숫자로 안 끝난다. 남은 자리를 넉넉히 보고 → 접기를 늦게
|
|
39
|
+
* 켜고 → 실제로는 넘친 채로 요청을 보낸다. 한국어를 쓰는 사람이 가장 자주
|
|
40
|
+
* 치는 글자가 하필 이 칸이었다.
|
|
41
|
+
*/
|
|
42
|
+
const 자모인가 = (cp) => (
|
|
43
|
+
(cp >= 0x1100 && cp <= 0x11ff) // 첫가끝 (NFD)
|
|
44
|
+
|| (cp >= 0x3130 && cp <= 0x318f) // 호환 자모 (ㅋ ㅎ ㅠ …)
|
|
45
|
+
|| (cp >= 0xa960 && cp <= 0xa97f) // 확장 A
|
|
46
|
+
|| (cp >= 0xd7b0 && cp <= 0xd7ff) // 확장 B
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
export function estimateTokens(text) {
|
|
50
|
+
const s = String(text ?? '');
|
|
51
|
+
let cjk = 0;
|
|
52
|
+
for (const ch of s) {
|
|
53
|
+
const cp = ch.codePointAt(0);
|
|
54
|
+
if ((cp >= 0xac00 && cp <= 0xd7a3) || (cp >= 0x3040 && cp <= 0x30ff) || (cp >= 0x4e00 && cp <= 0x9fff)
|
|
55
|
+
|| 자모인가(cp)) cjk++;
|
|
56
|
+
}
|
|
57
|
+
const rest = s.length - cjk;
|
|
58
|
+
return Math.ceil(cjk + rest / 3.6);
|
|
59
|
+
}
|
package/src/backend/wire.js
CHANGED
|
@@ -158,8 +158,30 @@ export function 기본카드(conn) {
|
|
|
158
158
|
효력칸: null,
|
|
159
159
|
// 캐시 표식을 우리가 붙이나
|
|
160
160
|
캐시: 'none',
|
|
161
|
+
// 표식을 **어느 칸에** 적나 (backend/cachemark.js 의 표식칸들)
|
|
162
|
+
표식칸: 'cache_control',
|
|
161
163
|
// 캐시 표식이 잡히는 최소 크기 (그 아래면 붙여도 안 잡힌다 — 탈은 아니다)
|
|
162
164
|
캐시최소: 1024,
|
|
165
|
+
/*
|
|
166
|
+
* 출력 상한을 **어느 이름으로** 싣나 — '둘다' · '새것' · '옛것'
|
|
167
|
+
*
|
|
168
|
+
* 옛 규격은 `max_tokens` 하나였다. 추론 모델(o 계열·GPT-5 계열)은 그
|
|
169
|
+
* 이름을 안 볼 뿐 아니라 **튕긴다**: "Unsupported parameter:
|
|
170
|
+
* 'max_tokens' is not supported with this model."
|
|
171
|
+
*
|
|
172
|
+
* 여태 이걸 `회사 !== 'openai'` 로 갈랐다. 즉 **주소로** 정한 것이다.
|
|
173
|
+
* 그러면 사내 게이트웨이(우리가 이름을 모르는 주소) 뒤에 GPT-5 를
|
|
174
|
+
* 물린 사람은 회사가 null 이라 둘 다 보내고, 첫 요청부터 400 이다.
|
|
175
|
+
* 우리 집안 규칙이 「업체 이름으로 갈래를 만들지 않는다」 인데 그걸
|
|
176
|
+
* 어긴 자리였고, 어긴 대가가 정확히 그 창구에서 나왔다.
|
|
177
|
+
*
|
|
178
|
+
* 그래서 두 가지로 바꾼다.
|
|
179
|
+
* 1) 짐작은 **모델 이름**으로 한다 — 게이트웨이 뒤에 무엇이 있는지는
|
|
180
|
+
* 모델 이름에 적혀 있다. 주소에는 안 적혀 있다.
|
|
181
|
+
* 2) 짐작이 틀리면 **배운다**(배울전선). 400 한 번이면 고쳐지고
|
|
182
|
+
* 디스크에 남아서 다음 판에는 안 틀린다.
|
|
183
|
+
*/
|
|
184
|
+
출력칸: '둘다',
|
|
163
185
|
// 세션 이름을 어느 칸에 싣나
|
|
164
186
|
세션자리: null,
|
|
165
187
|
// 흘려받기에서 usage 를 달라고 할까
|
|
@@ -204,6 +226,43 @@ export function 기본카드(conn) {
|
|
|
204
226
|
*/
|
|
205
227
|
카드.스트림usage = !!회사;
|
|
206
228
|
|
|
229
|
+
/*
|
|
230
|
+
* ── 몸이 OpenAI 꼴이어도, 캐시를 정하는 것은 **모델**이다 ────────────
|
|
231
|
+
*
|
|
232
|
+
* 여태 여기는 「이 창구들은 서버가 알아서 앞머리를 캐시한다」 고 믿고
|
|
233
|
+
* 표식을 하나도 안 붙였다. 그 믿음이 Claude 앞에서 틀린다. Claude 의 캐시는
|
|
234
|
+
* 자라는 앞머리 캐시가 아니라 **표식으로 끊는 방식**이다. 표식이 없으면
|
|
235
|
+
* 서버가 기본으로 잡아 주는 앞머리에서 멈춘다 — 대화가 60k 로 자라도 읽히는
|
|
236
|
+
* 것은 처음 몇 k 뿐이고, 늘어난 만큼이 걸음마다 전액 다시 나간다.
|
|
237
|
+
* (실제로 그랬다: 6k→59k 로 자라는 동안 읽힌 것은 5.9k 에 못 박혀 있었고
|
|
238
|
+
* 새로 쓴 것은 0 이었다.)
|
|
239
|
+
*
|
|
240
|
+
* ── 왜 창구가 아니라 모델로 가르나 ───────────────────────────────────
|
|
241
|
+
*
|
|
242
|
+
* 창구로 가르면 창구마다 예외가 하나씩 는다. 그건 이 파일이 없애려는
|
|
243
|
+
* 종류의 코드다. 칸을 받는지 안 받는지는 **그 몸을 결국 누가 읽느냐**로
|
|
244
|
+
* 정해지고, 그건 모델이다. Claude 를 앞에 둔 창구는 몸이 OpenAI 꼴이어도
|
|
245
|
+
* 이 칸을 제 규격으로 알아듣는다.
|
|
246
|
+
*
|
|
247
|
+
* 그리고 이건 지어낸 칸이 아니다. 두 이름 다 문서에 있다 —
|
|
248
|
+
* `cache_control` 은 Anthropic 규격, `prompt_cache_breakpoint` 는 OpenAI
|
|
249
|
+
* 규격의 text 조각에 정식으로 들어 있는 칸이다. 안 받는 서버는 400 으로
|
|
250
|
+
* 말해 주고, 그러면 아래 배울전선이 다른 이름으로 바꿔 보고, 그것도
|
|
251
|
+
* 안 되면 끈다. 짐작 → 배움 → 남김, 이 파일이 늘 하던 세 걸음이다.
|
|
252
|
+
*
|
|
253
|
+
* OpenAI 직통은 여기 안 걸린다 — 아래에서 제 칸(prompt_cache_key)으로
|
|
254
|
+
* 덮어쓴다. 거기는 서버가 알아서 캐시하는 것이 문서에 적혀 있다.
|
|
255
|
+
*/
|
|
256
|
+
/*
|
|
257
|
+
* 추론 모델은 옛 이름을 튕긴다. 이름으로 알아본다 — o1·o3·o4·gpt-5·gpt-6.
|
|
258
|
+
* 앞에 무엇이 붙어 있어도(`openai/gpt-5`, `azure-gpt-5-chat`) 잡히게 둔다.
|
|
259
|
+
*/
|
|
260
|
+
if (/(^|[^a-z0-9])(o[1-9]|gpt-[5-9])([^a-z0-9]|$)/i.test(모델)) 카드.출력칸 = '새것';
|
|
261
|
+
if (클로드인가(모델)) {
|
|
262
|
+
카드.캐시 = 'explicit';
|
|
263
|
+
카드.캐시최소 = 회사 === 'bedrock' ? 4096 : 1024;
|
|
264
|
+
}
|
|
265
|
+
|
|
207
266
|
if (회사 === 'openai') {
|
|
208
267
|
/*
|
|
209
268
|
* 여기가 이 칸들이 **문서에 있는** 유일한 자리다.
|
|
@@ -222,7 +281,8 @@ export function 기본카드(conn) {
|
|
|
222
281
|
* 실어 보내는 것보다, **앞머리를 안 흔드는 것**이 여기서 할 일이다
|
|
223
282
|
* (agent/session.js 의 차례 · loop.js 의 전선 고정).
|
|
224
283
|
*/
|
|
225
|
-
|
|
284
|
+
// Claude 를 앞에 뒀으면 위에서 표식으로 정해졌다. 그걸 덮지 않는다.
|
|
285
|
+
if (카드.캐시 !== 'explicit') 카드.캐시 = 'auto';
|
|
226
286
|
/*
|
|
227
287
|
* ── 대화 이름은 여기에 안 싣는다 ──────────────────────────────────
|
|
228
288
|
*
|
|
@@ -305,21 +365,27 @@ export function 눈금맞추기(카드, 강도) {
|
|
|
305
365
|
*
|
|
306
366
|
* @returns {{무엇:string, 값:any, 왜:string}|null}
|
|
307
367
|
*/
|
|
308
|
-
export function 배울전선(
|
|
368
|
+
export function 배울전선(문구, 규격) {
|
|
309
369
|
const s = String(문구 ?? '');
|
|
310
370
|
if (!s) return null;
|
|
311
371
|
/*
|
|
312
372
|
* 「안 받는다」 를 뜻하는 말들. 창구마다 쓰는 낱말이 다르다.
|
|
313
373
|
*
|
|
314
374
|
* Anthropic "is deprecated" (아직 400 은 아닌데 곧 그렇게 된다)
|
|
375
|
+
* Anthropic "Extra inputs are not permitted" ← 모르는 칸에 이 말이 온다
|
|
315
376
|
* OpenAI "Unrecognized request argument supplied"
|
|
316
377
|
* Bedrock "ValidationException ... not supported"
|
|
317
378
|
*
|
|
379
|
+
* `is not permitted` 라고 못 박아 뒀던 적이 있다. Anthropic 이 실제로 쓰는
|
|
380
|
+
* 말은 **`are` 쪽**(`Extra inputs are not permitted`)이라, 그 창구에서 모르는
|
|
381
|
+
* 칸을 보냈을 때 한 줄도 못 배웠다. 낱말을 못 박을 때는 실제로 오는 문장을
|
|
382
|
+
* 보고 박아야 한다 — 한 글자 차이로 그 창구만 조용히 안 배운다.
|
|
383
|
+
*
|
|
318
384
|
* 하나라도 빠지면 그 창구에서만 못 배운다 — 그리고 못 배우면 같은 400 을
|
|
319
385
|
* 세션마다 다시 맞는다. 넓게 잡되, 아래에서 **칸 이름과 함께** 있을 때만
|
|
320
386
|
* 쓴다. 낱말 하나로는 아무 오류 문장에나 걸린다.
|
|
321
387
|
*/
|
|
322
|
-
const 거절 = /not supported|unsupported|unrecognized|unknown|unexpected|invalid|
|
|
388
|
+
const 거절 = /not supported|unsupported|unrecognized|unknown|unexpected|invalid|not permitted|not allowed|removed|deprecated|cannot be used|no longer|extra (?:fields|inputs)|additional propert/i;
|
|
323
389
|
|
|
324
390
|
/*
|
|
325
391
|
* ── 「그 칸을 안 받는다」 와 「그 값이 틀렸다」 는 다른 말이다 ─────────
|
|
@@ -377,8 +443,57 @@ export function 배울전선(문구) {
|
|
|
377
443
|
return { 무엇: '생각형식', 값: 'none', 왜: 짧게(s) };
|
|
378
444
|
}
|
|
379
445
|
|
|
380
|
-
|
|
381
|
-
|
|
446
|
+
/*
|
|
447
|
+
* ── 출력 상한 칸 ───────────────────────────────────────────────────
|
|
448
|
+
*
|
|
449
|
+
* 이 둘은 같은 뜻을 다른 이름으로 적은 것이다. 어느 쪽을 받는지 서버마다
|
|
450
|
+
* 다르고, 게이트웨이 뒤에 무엇이 있는지는 주소로 알 수 없다. 그러니
|
|
451
|
+
* 튕기면 **반대 이름으로 갈아탄다.** 한 번 배우면 디스크에 남는다.
|
|
452
|
+
*
|
|
453
|
+
* 아래 「문맥이 넘쳤다」 판정보다 **먼저** 봐야 한다. 그쪽 정규식이
|
|
454
|
+
* `max_tokens` 라는 낱말만으로도 걸려서, 「128k 를 64k 로 줄였다」 는
|
|
455
|
+
* 엉뚱한 배움을 남기고 같은 400 을 다시 맞았다.
|
|
456
|
+
*/
|
|
457
|
+
if (/max_completion_tokens/i.test(s) && 거절.test(s)) {
|
|
458
|
+
return { 무엇: '출력칸', 값: '옛것', 왜: 짧게(s) };
|
|
459
|
+
}
|
|
460
|
+
if (/max_tokens/i.test(s) && 거절.test(s) && !/thinking|budget|greater than|less than|at least|must be/i.test(s)) {
|
|
461
|
+
return { 무엇: '출력칸', 값: '새것', 왜: 짧게(s) };
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/*
|
|
465
|
+
* ── 캐시 표식 ───────────────────────────────────────────────────────
|
|
466
|
+
*
|
|
467
|
+
* 「이 칸을 안 받는다」 는 말이 곧 「캐시를 못 한다」 는 뜻은 아니다.
|
|
468
|
+
* 같은 뜻을 두 규격이 다른 이름으로 적기 때문이다(cachemark.js 의
|
|
469
|
+
* 표식칸들). 그래서 **한 번은 다른 이름으로 바꿔 본다.** 그것까지
|
|
470
|
+
* 거절당해야 끈다.
|
|
471
|
+
*
|
|
472
|
+
* 바로 끄면 받을 수 있었던 창구에서도 캐시를 영영 안 쓰게 된다. 그건
|
|
473
|
+
* 조용히 비싸지는 쪽이라 화면에 아무 표시도 안 난다 — 이 파일이 없애려던
|
|
474
|
+
* 바로 그 고장이다.
|
|
475
|
+
*/
|
|
476
|
+
if (/cache_control/i.test(s) && 거절.test(s)) {
|
|
477
|
+
/*
|
|
478
|
+
* 다른 이름으로 바꿔 보는 것은 **OpenAI 규격일 때만** 이다.
|
|
479
|
+
*
|
|
480
|
+
* `prompt_cache_breakpoint` 는 OpenAI 스키마의 칸이다. Anthropic 규격
|
|
481
|
+
* 창구에 그 이름을 보내면 받아 줄 리가 없다 — `cache_control` 이 그
|
|
482
|
+
* 규격의 유일한 이름이라, 거기서 거절당했다는 것은 그 창구가 캐시를
|
|
483
|
+
* 아예 안 한다는 뜻이다.
|
|
484
|
+
*
|
|
485
|
+
* 규격을 안 보고 바꾸면 이렇게 된다: `cache_control` 400 → 「배웠다」 며
|
|
486
|
+
* OpenAI 칸으로 갈아탐 → Anthropic 창구가 `Extra inputs are not
|
|
487
|
+
* permitted` 로 또 400 → 두 번째 배움은 이미 썼으니 **끄지도 못한다.**
|
|
488
|
+
* 그 창구에서는 매 턴이 죽는데 화면에는 「배웠습니다」 가 두 번 뜬다.
|
|
489
|
+
*
|
|
490
|
+
* 규격을 모르면(옛 부름) 바꿔 보는 쪽으로 간다. 그쪽이 여태 하던 일이고,
|
|
491
|
+
* 그 다음 거절에서 어차피 꺼진다.
|
|
492
|
+
*/
|
|
493
|
+
if (규격 === 'anthropic') return { 무엇: '캐시', 값: 'none', 왜: 짧게(s) };
|
|
494
|
+
return { 무엇: '표식칸', 값: 'prompt_cache_breakpoint', 왜: 짧게(s) };
|
|
495
|
+
}
|
|
496
|
+
if (/prompt_cache_breakpoint|prompt_cache_key|cache_creation/i.test(s) && 거절.test(s)) {
|
|
382
497
|
return { 무엇: '캐시', 값: 'none', 왜: 짧게(s) };
|
|
383
498
|
}
|
|
384
499
|
|
|
@@ -446,7 +561,7 @@ export function 카드고치기(카드, 고침) {
|
|
|
446
561
|
return 새;
|
|
447
562
|
}
|
|
448
563
|
|
|
449
|
-
export const 카드칸들 = ['생각형식', '눈금', '끄는말', '효력칸', '캐시', '세션자리', '스트림usage'];
|
|
564
|
+
export const 카드칸들 = ['생각형식', '눈금', '끄는말', '효력칸', '캐시', '표식칸', '출력칸', '세션자리', '스트림usage'];
|
|
450
565
|
|
|
451
566
|
/**
|
|
452
567
|
* 집 파일에 적을 만한 것만. 주소·모델은 부르는 쪽이 열쇠로 쓴다.
|
package/src/commands.js
CHANGED
|
@@ -26,7 +26,7 @@ import { 종, 알릴만한초 } from './ui/notify.js';
|
|
|
26
26
|
import { 말, 말 as 옮긴말, 언어, 언어들, 언어정하기, 언어고르기, 옮긴만큼, 지시말, 지시말정하기, 지시말따로정했나 } from './i18n/index.js';
|
|
27
27
|
import { 프로필찾기, 쓸수있나, 연결만들기, 알릴말, 목록보기 } from './agent/models.js';
|
|
28
28
|
import { allowTemporarily } from './safety/network.js';
|
|
29
|
-
import { chat,
|
|
29
|
+
import { chat, 규격이름, 더할머리 } from './backend/adapter.js';
|
|
30
30
|
import { 알림채움 } from './backend/retry.js';
|
|
31
31
|
import { 프록시고르기, 프록시설정 } from './backend/proxy.js';
|
|
32
32
|
import { 정한셸 } from './tools/shell.js';
|
|
@@ -954,7 +954,16 @@ export async function handle(line, session, ctx) {
|
|
|
954
954
|
* 줄어든 것처럼 읽힌다. 새로 읽은 몫과의 차이는 바로 아래 캐시 줄이 적는다.
|
|
955
955
|
*/
|
|
956
956
|
const 보낸것 = session.usage.prompt || session.usage.in;
|
|
957
|
-
|
|
957
|
+
/*
|
|
958
|
+
* 창구가 usage 를 안 준 부름이 있었으면 **그렇다고 적는다.**
|
|
959
|
+
*
|
|
960
|
+
* 안 적으면 그 몫이 0 으로 합쳐져서, 유료로 부르고도 화면이 「입력 0 ·
|
|
961
|
+
* $0.00」 이라고 말한다. 이 프로그램에서 제일 나쁜 고장이 화면이
|
|
962
|
+
* 거짓말하는 것이고, 0 은 「모른다」 를 숫자로 지어낸 것이다.
|
|
963
|
+
*/
|
|
964
|
+
const 못잰것 = session.usage.못잰것 ?? 0;
|
|
965
|
+
const 덧말 = 못잰것 ? c.yellow(` (${못잰것}번은 창구가 안 알려 줘서 안 들어감)`) : '';
|
|
966
|
+
say(` ${c.gray(pad(말('cost.tokIn'), 14))} ${보낸것.toLocaleString()}${덧말}`);
|
|
958
967
|
say(` ${c.gray(pad(말('cost.tokOut'), 14))} ${session.usage.out.toLocaleString()}`);
|
|
959
968
|
/*
|
|
960
969
|
* ── 캐시가 얼마나 맞았나 ────────────────────────────────────────
|
|
@@ -1010,8 +1019,21 @@ export async function handle(line, session, ctx) {
|
|
|
1010
1019
|
const 요금값 = 세션요금(session);
|
|
1011
1020
|
const 쓴돈 = 돈셈(session.usage, 요금값);
|
|
1012
1021
|
if (쓴돈) {
|
|
1022
|
+
/*
|
|
1023
|
+
* 캐시 요금을 모르면 캐시 토큰도 정가로 셌다는 뜻이다. 그 금액은
|
|
1024
|
+
* 실제보다 **크다**. 그냥 찍으면 사람은 그 숫자를 예산으로 잡는데,
|
|
1025
|
+
* 캐시가 잘 걸리는 대화일수록 몇 배씩 부풀어 있다. 숫자를 감추지
|
|
1026
|
+
* 말고 「위쪽 값」 이라고 말한다 — 그리고 어디에 적으면 정확해지는지
|
|
1027
|
+
* 같이 알려 준다.
|
|
1028
|
+
*/
|
|
1029
|
+
const 위쪽 = 쓴돈.캐시모름;
|
|
1013
1030
|
say(` ${c.gray(pad(말('cost.money'), 14))} ${c.bold(돈말(쓴돈.달러))}`
|
|
1031
|
+
+ (위쪽 ? ` ${c.gray('이하')}` : '')
|
|
1014
1032
|
+ ` ${c.gray(`(${어디서온값(요금값)})`)}`);
|
|
1033
|
+
if (위쪽) {
|
|
1034
|
+
say(` ${c.gray(' '.repeat(14))} ${c.gray('캐시 요금을 몰라 캐시 토큰도 정가로 셌습니다 — 실제로는 이보다 쌉니다.')}`);
|
|
1035
|
+
say(` ${c.gray(' '.repeat(14))} ${c.gray('요금표에 `캐시읽기`·`캐시쓰기` 를 적으면 정확해집니다.')}`);
|
|
1036
|
+
}
|
|
1015
1037
|
} else if (바깥인가(session.conn.base) && (session.usage.in || session.usage.out)) {
|
|
1016
1038
|
const 붙은곳 = session.제공자 ? 제공자고르기(session.제공자) : null;
|
|
1017
1039
|
for (const 줄 of 요금적는법(session.conn.model, { 제공자: 붙은곳, 설정파일: configPath() })) {
|
|
@@ -1830,9 +1852,28 @@ function showThink(session, { 자세히 = false } = {}) {
|
|
|
1830
1852
|
}
|
|
1831
1853
|
}
|
|
1832
1854
|
if (session.conn?.전선) {
|
|
1833
|
-
const
|
|
1834
|
-
|
|
1835
|
-
|
|
1855
|
+
const 카드 = session.conn.전선;
|
|
1856
|
+
const 나가는것 = 눈금맞추기(카드, session.think);
|
|
1857
|
+
/*
|
|
1858
|
+
* ── 「끈다」 가 정말로 꺼지는 창구인가 ────────────────────────────
|
|
1859
|
+
*
|
|
1860
|
+
* off 인데 나갈 말이 없으면 두 가지 중 하나다.
|
|
1861
|
+
*
|
|
1862
|
+
* a. 이 규격에서는 **칸을 안 싣는 것이 곧 끄는 것**이다.
|
|
1863
|
+
* anthropic 은 thinking 을 빼면 안 생각하고, ollama 는 think:false
|
|
1864
|
+
* 를 싣는다. 이쪽은 아무 말도 안 해도 된다.
|
|
1865
|
+
* b. 이 규격에서는 칸을 안 실으면 **서버 기본값**으로 돈다. openai 꼴의
|
|
1866
|
+
* 추론 모델이 그렇다 — reasoning_effort 를 빼면 서버가 제 기본값
|
|
1867
|
+
* (보통 medium)으로 생각한다. 여기서 아무 말도 안 하면 화면은 off
|
|
1868
|
+
* 라고 적어 두고 실제로는 생각이 도는 셈이 된다.
|
|
1869
|
+
*
|
|
1870
|
+
* b 를 잠자코 두면 화면이 거짓말을 한다. 창구가 끄는 말을 알려 준 적이
|
|
1871
|
+
* 없으면 우리는 못 끈다 — 못 끄면 못 끈다고 적는다.
|
|
1872
|
+
*/
|
|
1873
|
+
const 못끄나 = session.think === 'off' && !나가는것 && 카드.생각형식 === 'effort';
|
|
1874
|
+
say(` ${c.gray(pad(말('think.wire'), 한국어 ? 8 : 10))}${c.gray(전선말(카드))}`
|
|
1875
|
+
+ (나가는것 ? ` ${c.gray(말('think.wireSends', { 값: 나가는것 }))}` : '')
|
|
1876
|
+
+ (못끄나 ? ` ${c.yellow(말('think.wireNoOff'))}` : ''));
|
|
1836
1877
|
}
|
|
1837
1878
|
if (개발자 || 자세히) say(` ${c.gray(pad(말('think.profile'), 한국어 ? 8 : 10))}${c.bold(t.name)} ${c.gray(t.desc)}`);
|
|
1838
1879
|
|
|
@@ -2859,12 +2900,20 @@ async function 서버모델들(conn) {
|
|
|
2859
2900
|
const { 애저인가, 애저풀기, 배포목록 } = await import('./backend/azure.js');
|
|
2860
2901
|
if (애저인가(conn.base)) {
|
|
2861
2902
|
const 푼것 = 애저풀기(conn.base);
|
|
2862
|
-
const a = await req(푼것.목록주소,
|
|
2903
|
+
const a = await req(푼것.목록주소,
|
|
2904
|
+
{ headers: headersFor(conn.auth ?? 'none', conn.key, 더할머리(conn.kind)), timeout: 4000 });
|
|
2863
2905
|
if (!a.ok) return null;
|
|
2864
2906
|
return 배포목록(a.json).map((m) => m.id);
|
|
2865
2907
|
}
|
|
2908
|
+
/*
|
|
2909
|
+
* 판 머리를 같이 얹는다 (adapter.js 의 더할머리).
|
|
2910
|
+
*
|
|
2911
|
+
* Anthropic 은 `anthropic-version` 이 없으면 400 이다. 이 자리가 그것을
|
|
2912
|
+
* 빠뜨리고 있어서, 그 창구에서는 `/model` 이 목록을 못 받아 왔다 — 그리고
|
|
2913
|
+
* 목록을 못 받으면 화면은 「모델이 없습니다」 라고 적는다. 있는데.
|
|
2914
|
+
*/
|
|
2866
2915
|
const r = await req(`${conn.base.replace(/\/$/, '')}/models`, {
|
|
2867
|
-
headers: headersFor(conn.auth ?? 'none', conn.key), timeout: 4000,
|
|
2916
|
+
headers: headersFor(conn.auth ?? 'none', conn.key, 더할머리(conn.kind)), timeout: 4000,
|
|
2868
2917
|
});
|
|
2869
2918
|
if (!r.ok) return null;
|
|
2870
2919
|
const list = r.json?.data ?? r.json?.models ?? [];
|