deel-local-cli 1.7.0 → 1.10.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 +1166 -0
- package/README.md +1223 -1107
- package/bin/deel.js +78 -12
- package/package.json +4 -4
- package/src/acp/map.js +1 -1
- package/src/acp/serve.js +40 -3
- package/src/agent/compact.js +314 -296
- package/src/agent/effort.js +27 -7
- package/src/agent/evidence.js +2 -0
- package/src/agent/filemem.js +141 -0
- package/src/agent/grade.js +51 -1
- package/src/agent/loop.js +218 -40
- package/src/agent/session.js +136 -14
- package/src/agent/store.js +186 -9
- package/src/agent/threads.js +26 -1
- package/src/backend/adapter.js +272 -23
- package/src/backend/learn.js +24 -0
- package/src/backend/mcp.js +96 -5
- package/src/backend/probe.js +131 -61
- package/src/backend/quota.js +29 -5
- package/src/backend/retry.js +3 -0
- package/src/backend/scanui.js +1 -1
- package/src/backend/toolfit.js +325 -0
- package/src/commands.js +170 -61
- package/src/completion.js +20 -2
- package/src/config.js +47 -2
- package/src/i18n/en.js +210 -3
- package/src/i18n/index.js +40 -0
- package/src/i18n/ja.js +204 -3
- package/src/i18n/ko.js +270 -3
- package/src/i18n/zh.js +204 -3
- package/src/lsp/client.js +49 -5
- package/src/oneshot.js +92 -5
- package/src/pack/sbom.js +30 -4
- package/src/pack/selfpack.js +26 -10
- package/src/pack/sheet.en.js +288 -0
- package/src/pack/tar.js +65 -2
- package/src/plugins/manage.js +46 -9
- package/src/repl.js +344 -61
- package/src/reset.js +397 -0
- package/src/safety/audit.js +95 -8
- package/src/safety/authcmd.js +316 -0
- package/src/safety/guard.js +143 -0
- package/src/safety/keystore.js +114 -38
- package/src/safety/undo.js +25 -6
- package/src/tools/desc.en.js +19 -14
- package/src/tools/index.js +349 -75
- package/src/tools/jobs.js +175 -42
- package/src/tools/lsp.js +5 -4
- package/src/tools/outline.js +7 -3
- package/src/tools/task.js +10 -6
- package/src/tools/todo.js +9 -2
- package/src/tools/verify.js +42 -7
- package/src/tools/webfetch.js +94 -10
- package/src/ui/export.js +1 -1
- package/src/ui/md.js +201 -5
- package/src/ui/motion.js +0 -1
- package/src/ui/pastechip.js +50 -3
- package/src/ui/pick.js +115 -0
- package/src/ui/screen.js +23 -3
- package/README.en.md +0 -1117
package/src/backend/probe.js
CHANGED
|
@@ -7,7 +7,12 @@
|
|
|
7
7
|
import { req, headersFor, serverMessage } from './http.js';
|
|
8
8
|
import { probeCtx } from './ctxsize.js';
|
|
9
9
|
import { 눈검사메시지 } from './vision.js';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
주소붙이기, endpoint, 더할머리, buildBody, extractMessage,
|
|
12
|
+
assistantMessage, toolMessage,
|
|
13
|
+
} from './adapter.js';
|
|
14
|
+
import { 벤더 } from './toolfit.js';
|
|
15
|
+
import { 말 } from '../i18n/index.js';
|
|
11
16
|
|
|
12
17
|
const READ_TOOL = {
|
|
13
18
|
type: 'function',
|
|
@@ -22,32 +27,32 @@ const READ_TOOL = {
|
|
|
22
27
|
},
|
|
23
28
|
};
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
30
|
+
/*
|
|
31
|
+
* ── 규격 차이는 여기서 흡수하지 않는다 ──────────────────────────────────
|
|
32
|
+
*
|
|
33
|
+
* 여기에 build() 와 extract() 가 따로 있었다. 그 둘이 아는 규격은 `ollama` 와
|
|
34
|
+
* 「나머지 = OpenAI」 **둘뿐**이었고, `anthropic` 은 「나머지」 로 떨어졌다.
|
|
35
|
+
* 그래서 Claude 를 직접 붙이면 진단이 이렇게 갔다 —
|
|
36
|
+
*
|
|
37
|
+
* · 판 머리(anthropic-version)를 안 얹는다 → 그 하나로 400
|
|
38
|
+
* · 문 이름이 /chat/completions (있어야 할 것은 /messages)
|
|
39
|
+
* · 시킴말이 messages 안으로 들어간다 → 「모르는 역할」
|
|
40
|
+
* · 도구가 {type:'function', function:{…}} 모양으로 간다
|
|
41
|
+
* · 답을 choices[0].message 에서 찾는다 (실제로는 content 블록 배열)
|
|
42
|
+
*
|
|
43
|
+
* 첫 칸(기본 대화)이 400 으로 죽으면 나머지 일곱 칸은 전부 「확인 불가」 로
|
|
44
|
+
* 건너뛴다. 그 결과 `deel setup` 이 저장하는 프로필에 streaming·tools·json·
|
|
45
|
+
* vision 이 **다 false** 로 적힌다 — Claude 를 붙였는데 도구를 아예 안 쓰는
|
|
46
|
+
* 연결이 만들어진다. 붙기는 붙으니 아무도 고장이라고 생각하지 않는다.
|
|
47
|
+
*
|
|
48
|
+
* adapter.js 첫 줄은 처음부터 "진단(probe)과 에이전트 루프가 같은 함수를
|
|
49
|
+
* 쓴다" 고 적어 두었다. 실제로는 안 썼다. 이제 쓴다 — 규격이 넷째가 되어도
|
|
50
|
+
* 이 파일은 안 고쳐도 된다.
|
|
51
|
+
*/
|
|
42
52
|
|
|
43
|
-
|
|
44
|
-
function
|
|
45
|
-
|
|
46
|
-
const m = json?.message ?? {};
|
|
47
|
-
return { content: m.content ?? '', toolCalls: m.tool_calls ?? [], thinking: m.thinking ?? '' };
|
|
48
|
-
}
|
|
49
|
-
const m = json?.choices?.[0]?.message ?? {};
|
|
50
|
-
return { content: m.content ?? '', toolCalls: m.tool_calls ?? [], thinking: m.reasoning_content ?? '' };
|
|
53
|
+
/** 이 회사가 무엇을 받는지까지 봐야 몸통이 맞다 (toolfit.js 의 벤더). */
|
|
54
|
+
function 몸통(conn, opts) {
|
|
55
|
+
return buildBody(conn.kind, { model: conn.model, 회사: 벤더(conn), maxTokens: 128, ...opts });
|
|
51
56
|
}
|
|
52
57
|
|
|
53
58
|
// 추론 모델일 때 기본 대화 칸에 덧붙일 설명.
|
|
@@ -55,6 +60,31 @@ function c_note(retried) {
|
|
|
55
60
|
return retried ? ' (추론 모델 — 사고를 끄고 다시 물어 확인)' : ' (추론 모델)';
|
|
56
61
|
}
|
|
57
62
|
|
|
63
|
+
/*
|
|
64
|
+
* ── 이 문으로는 안 나오는 모델 (Codex 계열) ─────────────────────────────
|
|
65
|
+
*
|
|
66
|
+
* `gpt-5-codex` 계열은 OpenAI 가 **Responses API(/responses)로만** 내준다.
|
|
67
|
+
* `/chat/completions` 로 물으면 404 나 400 이 오는데, 그 화면에는 상태 코드
|
|
68
|
+
* 한 줄만 남는다. 그러면 사람은 열쇠를 의심하거나 모델 이름을 잘못 적은 줄
|
|
69
|
+
* 알고, 맞는 이름을 몇 번씩 다시 넣어 본다 — 이름은 처음부터 맞았다.
|
|
70
|
+
*
|
|
71
|
+
* ── 왜 이름을 보나. 이 파일에서만 본다 ─────────────────────────────────
|
|
72
|
+
*
|
|
73
|
+
* 어디로 보낼지는 **주소로만** 정한다(1.9). 그 규칙은 여기서도 안 깬다 —
|
|
74
|
+
* 여기서 이름을 보는 것은 보낼 곳을 고르려는 것이 아니라, **이미 실패한
|
|
75
|
+
* 요청에 까닭을 붙이려는** 것뿐이다. 그래서 미리 막지 않는다. 요청은 그대로
|
|
76
|
+
* 나가고, 실패했을 때만 한 줄이 붙는다. 언젠가 OpenAI 가 이 문으로도 내주면
|
|
77
|
+
* 요청이 그냥 성공하고 이 줄은 저절로 안 나온다.
|
|
78
|
+
*
|
|
79
|
+
* 게이트웨이는 해당 없다. 그 뒤에 무엇이 걸려 있는지 우리는 모르고, 사내
|
|
80
|
+
* 게이트웨이가 제 나름대로 이 계열을 chat 창구로 내주는 일도 있다.
|
|
81
|
+
*/
|
|
82
|
+
const CODEX계열 = /^(?:gpt-5-codex|codex-)/i;
|
|
83
|
+
|
|
84
|
+
export function 코덱스인가(conn) {
|
|
85
|
+
return 벤더(conn) === 'openai' && CODEX계열.test(String(conn?.model ?? ''));
|
|
86
|
+
}
|
|
87
|
+
|
|
58
88
|
const SKIPPED = [
|
|
59
89
|
['system', '시스템 메시지'],
|
|
60
90
|
['stream', '스트리밍'],
|
|
@@ -69,7 +99,14 @@ const SKIPPED = [
|
|
|
69
99
|
export async function probe(conn, onStep = () => {}) {
|
|
70
100
|
const { kind: shape, base, auth, model } = conn;
|
|
71
101
|
const key = conn.key ?? '';
|
|
72
|
-
|
|
102
|
+
/*
|
|
103
|
+
* 판 머리를 여기서도 얹는다 (adapter.js 의 더할머리).
|
|
104
|
+
*
|
|
105
|
+
* Anthropic 규격은 `anthropic-version` 하나가 없으면 400 이다. 열쇠가
|
|
106
|
+
* 멀쩡해도 그렇다. 진단 화면은 그 400 을 「연결 실패」 로 적고, 사람은
|
|
107
|
+
* 열쇠를 다시 받으러 간다 — 여덟 칸이 전부 그 한 줄 때문에 빨개진다.
|
|
108
|
+
*/
|
|
109
|
+
const H = () => headersFor(auth, key, 더할머리(shape));
|
|
73
110
|
/*
|
|
74
111
|
* 물음표 뒤를 끝에 남겨야 한다 (adapter.js 의 주소붙이기).
|
|
75
112
|
*
|
|
@@ -85,23 +122,23 @@ export async function probe(conn, onStep = () => {}) {
|
|
|
85
122
|
const facts = { shape, base, auth, model };
|
|
86
123
|
|
|
87
124
|
const add = (r) => { results.push(r); onStep(r); return r; };
|
|
88
|
-
const call = (opts) => {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
125
|
+
const call = (opts) => req(url(endpoint(shape)), {
|
|
126
|
+
method: 'POST',
|
|
127
|
+
headers: H(),
|
|
128
|
+
body: 몸통(conn, opts),
|
|
129
|
+
timeout: opts.timeout ?? 60000,
|
|
130
|
+
stream: opts.stream,
|
|
131
|
+
});
|
|
132
|
+
// 답 읽기도 루프와 같은 함수를 쓴다. 도구 부름은 { id, name, args } 로
|
|
133
|
+
// 고르게 나온다 — 규격마다 다른 자리를 여기서 또 헤아리지 않는다.
|
|
134
|
+
const 읽기 = (r) => extractMessage(shape, r?.json);
|
|
98
135
|
|
|
99
136
|
// 1. 기본 대화 — 이게 안 되면 나머지는 볼 필요가 없다.
|
|
100
137
|
// 추론 모델은 본문이 전부 thinking 으로 가고 토큰 상한에 잘린다.
|
|
101
138
|
// 그걸 "안됨"으로 볼 수 없으므로, 사고를 끄고 넉넉히 한 번 더 물어본다.
|
|
102
139
|
const ASK = { messages: [{ role: 'user', content: '1+1은? 숫자만 답하세요.' }] };
|
|
103
140
|
let basic = await call({ ...ASK, maxTokens: 256 });
|
|
104
|
-
let got = basic.ok ?
|
|
141
|
+
let got = basic.ok ? 읽기(basic) : { content: '', thinking: '' };
|
|
105
142
|
let thinkingModel = false;
|
|
106
143
|
let retried = false;
|
|
107
144
|
|
|
@@ -110,12 +147,12 @@ export async function probe(conn, onStep = () => {}) {
|
|
|
110
147
|
retried = true;
|
|
111
148
|
const second = await call({ ...ASK, maxTokens: 1024, think: false, timeout: 90000 });
|
|
112
149
|
if (second.ok) {
|
|
113
|
-
const e2 =
|
|
150
|
+
const e2 = 읽기(second);
|
|
114
151
|
if (e2.content) { basic = second; got = e2; }
|
|
115
152
|
else {
|
|
116
153
|
// 사고를 못 끄는 서버 — 상한만 크게 올려 한 번 더.
|
|
117
154
|
const third = await call({ ...ASK, maxTokens: 2048, timeout: 120000 });
|
|
118
|
-
if (third.ok &&
|
|
155
|
+
if (third.ok && 읽기(third).content) { basic = third; got = 읽기(third); }
|
|
119
156
|
}
|
|
120
157
|
}
|
|
121
158
|
}
|
|
@@ -136,7 +173,11 @@ export async function probe(conn, onStep = () => {}) {
|
|
|
136
173
|
? got.thinking
|
|
137
174
|
? '사고만 나오고 본문이 안 나옵니다 — 토큰 상한을 크게 올려야 합니다'
|
|
138
175
|
: '응답이 비어 있습니다 — 모델 이름을 확인하세요'
|
|
139
|
-
|
|
176
|
+
// 못 붙은 까닭을 아는 자리가 하나 있다. 상태 코드만 남기면 사람은
|
|
177
|
+
// 열쇠를 의심하는데, 열쇠도 이름도 처음부터 맞았다 (코덱스인가 머리말).
|
|
178
|
+
: 코덱스인가(conn)
|
|
179
|
+
? `${serverMessage(basic)}\n${말('probe.codexOnly', { 모델: model })}`
|
|
180
|
+
: serverMessage(basic),
|
|
140
181
|
ms: basic.ms,
|
|
141
182
|
});
|
|
142
183
|
if (!basicOk) {
|
|
@@ -154,7 +195,7 @@ export async function probe(conn, onStep = () => {}) {
|
|
|
154
195
|
{ role: 'user', content: '안녕하세요' },
|
|
155
196
|
],
|
|
156
197
|
});
|
|
157
|
-
const sysHit = sys.ok && /DEEL/i.test(
|
|
198
|
+
const sysHit = sys.ok && /DEEL/i.test(읽기(sys).content);
|
|
158
199
|
add({
|
|
159
200
|
id: 'system',
|
|
160
201
|
label: '시스템 메시지',
|
|
@@ -209,15 +250,18 @@ export async function probe(conn, onStep = () => {}) {
|
|
|
209
250
|
maxTokens: 512,
|
|
210
251
|
timeout: 90000,
|
|
211
252
|
});
|
|
212
|
-
const tcalls = tl.ok ?
|
|
253
|
+
const tcalls = tl.ok ? 읽기(tl).toolCalls : [];
|
|
213
254
|
const gotCall = tcalls.length > 0;
|
|
214
|
-
|
|
255
|
+
// 부름은 이미 { id, name, args } 로 고르게 나온다 (extractMessage).
|
|
256
|
+
// 예전에는 여기서 OpenAI 날모양(function.arguments)을 직접 팠다 —
|
|
257
|
+
// 그러면 Anthropic 은 도구를 제대로 불러도 「안 불렀다」 로 읽힌다.
|
|
258
|
+
const argOk = gotCall && JSON.stringify(tcalls[0]?.args ?? '').includes('config');
|
|
215
259
|
add({
|
|
216
260
|
id: 'tools',
|
|
217
261
|
label: '도구 호출',
|
|
218
262
|
status: gotCall ? (argOk ? 'ok' : 'warn') : 'no',
|
|
219
263
|
detail: gotCall
|
|
220
|
-
? `${tcalls[0]?.
|
|
264
|
+
? `${tcalls[0]?.name} 호출됨${argOk ? '' : ' — 인자가 부정확, 편집 신뢰성 작업이 더 필요합니다'}`
|
|
221
265
|
: tl.ok ? '도구를 안 부르고 글로만 답합니다' : serverMessage(tl),
|
|
222
266
|
ms: tl.ms,
|
|
223
267
|
});
|
|
@@ -227,12 +271,15 @@ export async function probe(conn, onStep = () => {}) {
|
|
|
227
271
|
if (gotCall) {
|
|
228
272
|
const tc = tcalls[0];
|
|
229
273
|
const callId = tc.id ?? 'call_1';
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
274
|
+
/*
|
|
275
|
+
* 되돌려 넣는 모양도 루프와 같은 함수로 짓는다 (adapter.js).
|
|
276
|
+
*
|
|
277
|
+
* 여기에 규격별 갈래를 손으로 적어 두면 규격이 하나 늘 때마다 이 자리가
|
|
278
|
+
* 조용히 틀린다 — Anthropic 에서는 도구 결과가 사람 차례로 가야 하는데
|
|
279
|
+
* `role:'tool'` 로 보내 「모르는 역할」 을 받고 있었다.
|
|
280
|
+
*/
|
|
281
|
+
const assistantMsg = assistantMessage(shape, { content: '', toolCalls: [tc] });
|
|
282
|
+
const toolMsg = toolMessage(shape, { callId, name: tc.name, content: '{"port": 7099}' });
|
|
236
283
|
const rt = await call({
|
|
237
284
|
...quiet,
|
|
238
285
|
timeout: 90000,
|
|
@@ -245,7 +292,7 @@ export async function probe(conn, onStep = () => {}) {
|
|
|
245
292
|
tools: [READ_TOOL],
|
|
246
293
|
maxTokens: 256,
|
|
247
294
|
});
|
|
248
|
-
const said = rt.ok ?
|
|
295
|
+
const said = rt.ok ? 읽기(rt).content : '';
|
|
249
296
|
add({
|
|
250
297
|
id: 'toolresult',
|
|
251
298
|
label: '도구 결과 되돌리기',
|
|
@@ -279,8 +326,20 @@ export async function probe(conn, onStep = () => {}) {
|
|
|
279
326
|
timeout: 90000,
|
|
280
327
|
});
|
|
281
328
|
if (!js.ok) break;
|
|
282
|
-
raw =
|
|
283
|
-
|
|
329
|
+
raw = 읽기(js).content ?? '';
|
|
330
|
+
/*
|
|
331
|
+
* **객체일 때만** 읽었다고 한다.
|
|
332
|
+
*
|
|
333
|
+
* 답 모양을 강제하는 칸이 없는 규격(Anthropic)에서는 이 물음에 그냥
|
|
334
|
+
* `21` 이라고 답한다. `JSON.parse('21')` 은 오류 없이 숫자 21 을 준다.
|
|
335
|
+
* 그런데 그다음 줄이 `'answer' in parsed` 라, 숫자에 `in` 을 써서
|
|
336
|
+
* **TypeError 로 진단 전체가 통째로 죽었다.** 스키마를 안 지킨 것은
|
|
337
|
+
* 「구조적 출력 안 됨」 이라고 적을 일이지 프로그램이 끝날 일이 아니다.
|
|
338
|
+
*/
|
|
339
|
+
try {
|
|
340
|
+
const 읽힌것 = JSON.parse(raw);
|
|
341
|
+
if (읽힌것 && typeof 읽힌것 === 'object' && !Array.isArray(읽힌것)) parsed = 읽힌것;
|
|
342
|
+
} catch { /* 글로만 답하는 서버 — 아래에서 '스키마를 안 지킴' 으로 적는다 */ }
|
|
284
343
|
}
|
|
285
344
|
const jsonOk = !!(parsed && 'answer' in parsed);
|
|
286
345
|
add({
|
|
@@ -316,7 +375,7 @@ export async function probe(conn, onStep = () => {}) {
|
|
|
316
375
|
maxTokens: 32,
|
|
317
376
|
timeout: 60000,
|
|
318
377
|
});
|
|
319
|
-
const 눈있음 = !!(눈.ok &&
|
|
378
|
+
const 눈있음 = !!(눈.ok && 읽기(눈).content);
|
|
320
379
|
add({
|
|
321
380
|
id: 'vision',
|
|
322
381
|
label: '그림 보기',
|
|
@@ -330,9 +389,18 @@ export async function probe(conn, onStep = () => {}) {
|
|
|
330
389
|
});
|
|
331
390
|
facts.vision = 눈있음;
|
|
332
391
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
392
|
+
/*
|
|
393
|
+
* 7. 추론 강도 조절 — 낮음/높음이 실제로 다른 결과를 내느냐.
|
|
394
|
+
*
|
|
395
|
+
* 상한에 걸리면 둘 다 같은 숫자가 나와 비교가 무의미해진다. 넉넉히 준다.
|
|
396
|
+
*
|
|
397
|
+
* Anthropic 규격은 여기서 상한을 더 크게 잡아야 한다. 그쪽은 강도를 말이
|
|
398
|
+
* 아니라 **출력 상한 안에서 나가는 예산**으로 주는데(adapter.js 의
|
|
399
|
+
* 생각예산), 1,500 으로는 답에 남길 자리를 빼고 나면 최소 예산 1,024 를
|
|
400
|
+
* 못 만든다. 그러면 생각이 아예 안 켜진 채로 두 번을 부르고, 이 칸은
|
|
401
|
+
* 「차이 없음」 이라고 적는다 — 켤 수 있는 모델에 대고 못 켠다고 적는 셈이다.
|
|
402
|
+
*/
|
|
403
|
+
const THINK_CAP = shape === 'anthropic' ? 8000 : 1500;
|
|
336
404
|
const seen = [];
|
|
337
405
|
for (const lv of ['low', 'high']) {
|
|
338
406
|
const r = await call({
|
|
@@ -342,13 +410,15 @@ export async function probe(conn, onStep = () => {}) {
|
|
|
342
410
|
timeout: 120000,
|
|
343
411
|
});
|
|
344
412
|
if (r.ok) {
|
|
345
|
-
const e =
|
|
413
|
+
const e = 읽기(r);
|
|
346
414
|
seen.push({
|
|
347
415
|
lv,
|
|
348
416
|
ms: r.ms,
|
|
349
417
|
thought: (e.thinking ?? '').length,
|
|
350
|
-
|
|
351
|
-
|
|
418
|
+
// 토큰 수와 끝난 까닭도 규격마다 이름이 다르다. 읽는 자리를 하나로 모은다 —
|
|
419
|
+
// 여기서 OpenAI 이름만 보면 Anthropic 은 늘 0 이고 늘 '안 잘림' 이 된다.
|
|
420
|
+
out: e.usage?.out ?? 0,
|
|
421
|
+
capped: e.stopped === 'length' || e.stopped === 'max_tokens',
|
|
352
422
|
});
|
|
353
423
|
} else {
|
|
354
424
|
seen.push({ lv, ms: r.ms, err: serverMessage(r) });
|
package/src/backend/quota.js
CHANGED
|
@@ -16,13 +16,37 @@
|
|
|
16
16
|
// `x-ratelimit-remaining-tokens` 를 쓰기도 하고 아예 안 주기도 한다.
|
|
17
17
|
// 그래서 **아는 이름만 읽고, 없으면 없다고 한다.** 없는 것을 0 으로 치면
|
|
18
18
|
// 화면에 "0 남음" 이 떠서, 멀쩡한데 다 썼다고 믿게 된다.
|
|
19
|
+
//
|
|
20
|
+
// Anthropic 은 `anthropic-` 을 앞에 달고, 남은 것을 **뒤에** 적는다
|
|
21
|
+
// (`anthropic-ratelimit-requests-remaining`). 그 이름을 몰라서, Claude 직통과
|
|
22
|
+
// Bedrock 게이트웨이에서는 이 줄이 **언제나 비어 있었다** — 서버는 매 응답에
|
|
23
|
+
// 남은 양을 실어 보내고 있었는데 우리만 못 읽었다. 그래서 그 두 자리에서는
|
|
24
|
+
// 아직도 429 를 맞아야만 알 수 있었다. 이 파일이 없애려던 바로 그 상황이다.
|
|
19
25
|
|
|
20
26
|
// 읽을 이름들. 앞에서부터 처음 있는 것 하나를 쓴다.
|
|
21
|
-
const 요청남음 = [
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const
|
|
27
|
+
const 요청남음 = [
|
|
28
|
+
'x-ratelimit-remaining-requests', 'ratelimit-remaining-requests', 'x-ratelimit-remaining',
|
|
29
|
+
'anthropic-ratelimit-requests-remaining',
|
|
30
|
+
];
|
|
31
|
+
const 토큰남음 = [
|
|
32
|
+
'x-ratelimit-remaining-tokens', 'ratelimit-remaining-tokens',
|
|
33
|
+
'anthropic-ratelimit-tokens-remaining',
|
|
34
|
+
// 입력·출력을 따로 세는 판도 있다. 둘 중 먼저 바닥나는 쪽이 곧 막히는 쪽이다.
|
|
35
|
+
'anthropic-ratelimit-input-tokens-remaining', 'anthropic-ratelimit-output-tokens-remaining',
|
|
36
|
+
];
|
|
37
|
+
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
|
+
/*
|
|
43
|
+
* 풀리는 때. Anthropic 은 초가 아니라 **날짜(RFC 3339)** 로 준다 —
|
|
44
|
+
* `언제풀리나` 가 Date.parse 로 받아 지금과의 차이를 초로 바꾼다.
|
|
45
|
+
*/
|
|
46
|
+
const 다시언제 = [
|
|
47
|
+
'retry-after', 'x-ratelimit-reset-requests', 'x-ratelimit-reset-tokens', 'ratelimit-reset',
|
|
48
|
+
'anthropic-ratelimit-requests-reset', 'anthropic-ratelimit-tokens-reset',
|
|
49
|
+
];
|
|
26
50
|
|
|
27
51
|
function 골라(머리, 이름들) {
|
|
28
52
|
if (!머리) return null;
|
package/src/backend/retry.js
CHANGED
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
// · 세 번까지. 그 뒤로는 사실대로 말한다. 열 번 두드리는 것은 할당량을 더 빨리
|
|
18
18
|
// 태우는 짓이다.
|
|
19
19
|
// · 400·401·403·404 는 다시 불러 봐야 같다. 한 번만 부르고 서버가 한 말을 보여 준다.
|
|
20
|
+
// 401 에 딱 하나 예외가 있는데, 그것도 여기가 아니라 backend/adapter.js 가 한다 —
|
|
21
|
+
// 열쇠받기(safety/authcmd.js)가 걸려 있으면 **같은 열쇠로 다시 부르는 것이 아니라**
|
|
22
|
+
// 새 열쇠를 받아서 부르는 것이라, 「불러 봐야 같다」 에 해당하지 않는다. 딱 한 번이다.
|
|
20
23
|
// · ECONNREFUSED 는 서버가 꺼진 것이고, 시간 초과는 5분을 또 기다릴 일이 아니다.
|
|
21
24
|
// 둘 다 안 부른다.
|
|
22
25
|
//
|
package/src/backend/scanui.js
CHANGED
|
@@ -67,7 +67,7 @@ export async function runScan(flags = {}) {
|
|
|
67
67
|
const wRun = Math.max(10, ...found.map((f) => width(f.runtime)));
|
|
68
68
|
for (const f of found) {
|
|
69
69
|
const 자리 = `${f.host}:${f.port}`;
|
|
70
|
-
const 규격 =
|
|
70
|
+
const 규격 = 규격이름(f.kind);
|
|
71
71
|
const 표시 = f.guessed ? c.gray(' (추정)') : '';
|
|
72
72
|
say(` ${c.hcyan('◆')} ${c.bold(pad(f.runtime, wRun))}${표시} ${c.gray(pad(자리, 22))}${c.gray(pad(규격, 14))}${c.gray(f.ms + 'ms')}`);
|
|
73
73
|
if (f.locked) {
|