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.
- package/README.ko.md +6 -5
- package/README.md +6 -5
- package/package.json +60 -60
- package/src/acp/map.js +30 -25
- package/src/acp/serve.js +810 -810
- package/src/agent/budget.js +156 -156
- package/src/agent/commit.js +32 -9
- package/src/agent/compact.js +441 -420
- package/src/agent/effort.js +308 -308
- package/src/agent/evolve.js +263 -263
- package/src/agent/loop.js +1728 -1707
- package/src/agent/memory.js +156 -156
- package/src/agent/mention.js +210 -210
- package/src/agent/recall.js +22 -9
- package/src/agent/session.js +1056 -1049
- package/src/backend/adapter.js +1355 -1159
- package/src/backend/cachemark.js +223 -150
- package/src/backend/detect.js +327 -327
- package/src/backend/http.js +3 -1
- package/src/backend/mcp.js +15 -3
- package/src/backend/price.js +69 -6
- package/src/backend/probe.js +17 -3
- package/src/backend/quota.js +274 -250
- package/src/backend/tokens.js +25 -3
- package/src/backend/toolfit.js +352 -352
- package/src/backend/wire.js +715 -600
- package/src/commands.js +3184 -3135
- package/src/config.js +39 -0
- package/src/i18n/en.js +545 -544
- package/src/i18n/ja.js +498 -497
- package/src/i18n/ko.js +592 -591
- package/src/i18n/zh.js +498 -497
- package/src/oneshot.js +625 -620
- package/src/pack/tar.js +23 -1
- package/src/pack/zip.js +19 -1
- package/src/plugins/manage.js +85 -5
- package/src/repl.js +2616 -2602
- package/src/safety/audit.js +22 -3
- package/src/safety/guard.js +124 -9
- package/src/safety/network.js +43 -0
- package/src/tools/fastgrep.js +15 -0
- package/src/tools/fsutil.js +291 -265
- package/src/tools/index.js +40 -3
- package/src/tools/jobs.js +87 -6
- package/src/tools/label.js +52 -0
- package/src/tools/spawn.js +114 -0
- package/src/tools/verify.js +358 -358
- package/src/tools/webfetch.js +28 -3
- package/src/ui/export.js +26 -10
- package/src/ui/pick.js +115 -115
- package/src/ui/status.js +617 -613
package/src/backend/http.js
CHANGED
|
@@ -130,7 +130,9 @@ export async function 원시요청(url, { method = 'GET', headers = {}, body, ti
|
|
|
130
130
|
let 다음;
|
|
131
131
|
try { 다음 = new URL(loc, 지금); } catch { return { ...r, ms: Date.now() - started }; }
|
|
132
132
|
await r.버리기?.(); // 안 읽은 몸을 두고 다음 요청을 보내면 연결이 남는다.
|
|
133
|
-
|
|
133
|
+
// 훅이 async 여도 기다린다 — 이름을 실제로 풀어 보는 검사(webfetch.js)가
|
|
134
|
+
// 여기 붙는다. 안 기다리면 그 검사가 끝나기 전에 다음 홉이 나간다.
|
|
135
|
+
await 되돌림?.(다음);
|
|
134
136
|
// fetch 가 하는 대로: 303 은 GET 으로, POST 의 301·302 도 GET 으로, 307·308 은 그대로.
|
|
135
137
|
if (r.status === 303 || ((r.status === 301 || r.status === 302) && 방법 !== 'GET' && 방법 !== 'HEAD')) {
|
|
136
138
|
방법 = 'GET';
|
package/src/backend/mcp.js
CHANGED
|
@@ -27,6 +27,7 @@ import { spawn } from 'node:child_process';
|
|
|
27
27
|
import { existsSync, readFileSync } from 'node:fs';
|
|
28
28
|
import { join } from 'node:path';
|
|
29
29
|
import { VERSION } from '../version.js';
|
|
30
|
+
import { 열쇠환경인가 } from '../config.js';
|
|
30
31
|
|
|
31
32
|
// 붙는 데 이만큼 넘게 걸리면 포기한다. 시작이 느려지면 안 쓰게 된다.
|
|
32
33
|
export const 붙기제한 = 8000;
|
|
@@ -301,7 +302,7 @@ export class MCP서버 {
|
|
|
301
302
|
* 넘어가면 어디로 가는지 우리가 알 수 없다. 프로그램이 도는 데 꼭 필요한
|
|
302
303
|
* 것만 남긴다.
|
|
303
304
|
*/
|
|
304
|
-
function 깨끗한환경() {
|
|
305
|
+
export function 깨끗한환경() {
|
|
305
306
|
const 남길것 = ['PATH', 'Path', 'PATHEXT', 'HOME', 'USERPROFILE', 'TEMP', 'TMP', 'SystemRoot', 'windir', 'COMSPEC', 'LANG', 'LC_ALL', 'APPDATA', 'LOCALAPPDATA', 'ProgramFiles', 'ProgramData', 'NODE_PATH'];
|
|
306
307
|
const out = {};
|
|
307
308
|
for (const k of 남길것) if (process.env[k] != null) out[k] = process.env[k];
|
|
@@ -309,7 +310,7 @@ function 깨끗한환경() {
|
|
|
309
310
|
}
|
|
310
311
|
|
|
311
312
|
/**
|
|
312
|
-
* 게이트웨이
|
|
313
|
+
* 게이트웨이 열쇠**만** 뺀 환경. Bash 와 Jobs 가 자식에게 넘길 것.
|
|
313
314
|
*
|
|
314
315
|
* 위 깨끗한환경 은 남의 프로그램(MCP 서버)에 주는 것이라 통째로 씻는다.
|
|
315
316
|
* 여기는 다르다 — Bash 로 도는 것은 **사용자 제 프로젝트**다. PATH·NODE_ENV·
|
|
@@ -323,7 +324,18 @@ function 깨끗한환경() {
|
|
|
323
324
|
*/
|
|
324
325
|
export function 열쇠뺀환경(env = process.env) {
|
|
325
326
|
const out = { ...env };
|
|
326
|
-
|
|
327
|
+
/*
|
|
328
|
+
* 이름을 못 박지 않는다.
|
|
329
|
+
*
|
|
330
|
+
* 여기가 `delete out.DEEL_API_KEY` 한 줄이었다. 열쇠 이름은 **둘**인데
|
|
331
|
+
* (config.js 의 resolveKey), 그중 하나만 지운 것이다. 프로필 열쇠
|
|
332
|
+
* (`DEEL_KEY_PROD`)를 쓰는 사람은 `env` 한 줄로 그대로 샜다 — 그리고
|
|
333
|
+
* 그 방법을 우리 심사 명세가 권장으로 적어 뒀다.
|
|
334
|
+
*
|
|
335
|
+
* 무엇이 열쇠인지는 **읽는 자에게 묻는다**(열쇠환경인가). 이름이 늘면
|
|
336
|
+
* 거기만 고친다. 여기서 다시 적으면 반드시 한쪽이 낡는다.
|
|
337
|
+
*/
|
|
338
|
+
for (const k of Object.keys(out)) if (열쇠환경인가(k)) delete out[k];
|
|
327
339
|
return out;
|
|
328
340
|
}
|
|
329
341
|
|
package/src/backend/price.js
CHANGED
|
@@ -72,8 +72,26 @@ function 한칸읽기(열쇠, 값, 어디서) {
|
|
|
72
72
|
if (입 == null || 출 == null) {
|
|
73
73
|
return { 탈: `${어디서} 의 '${열쇠}' 요금이 숫자가 아닙니다 (입력·출력 둘 다 필요합니다)` };
|
|
74
74
|
}
|
|
75
|
+
/*
|
|
76
|
+
* ── 캐시 값은 **있으면 쓰고 없으면 안 지어낸다** ───────────────────
|
|
77
|
+
*
|
|
78
|
+
* 캐시로 읽힌 토큰은 정가로 안 받는다. 회사마다 다르지만 대개 정가의
|
|
79
|
+
* 한 자릿수 퍼센트고, 처음 써 넣을 때는 오히려 더 받는 곳도 있다.
|
|
80
|
+
* 그래서 이 둘을 정가로 세면 금액이 몇 배로 부풀고, 캐시를 켠 보람이
|
|
81
|
+
* 화면에서 사라진다 — 고쳐 놨는데 고친 표가 안 나는 셈이다.
|
|
82
|
+
*
|
|
83
|
+
* 그렇다고 배수를 여기 박지는 않는다. 이 파일이 요금을 안 박는 것과
|
|
84
|
+
* 같은 까닭이다(머리말). 모르면 정가로 세고 **모른다고 말한다** —
|
|
85
|
+
* 그 금액은 실제보다 **크다**. 큰 쪽으로 틀리는 것은 사람이 놀라고
|
|
86
|
+
* 끝나지만, 작은 쪽으로 틀리면 예산을 그 숫자로 잡는다.
|
|
87
|
+
*/
|
|
88
|
+
const 캐시읽기 = 값읽기(값?.캐시읽기 ?? 값?.cacheRead ?? 값?.cache_read);
|
|
89
|
+
const 캐시쓰기 = 값읽기(값?.캐시쓰기 ?? 값?.cacheWrite ?? 값?.cache_write);
|
|
75
90
|
const 기준 = 값?.기준 ?? 값?.asOf ?? null;
|
|
76
|
-
return {
|
|
91
|
+
return {
|
|
92
|
+
입력: 입, 출력: 출, 캐시읽기, 캐시쓰기,
|
|
93
|
+
기준: 기준 ? String(기준) : null, 어디서, 열쇠,
|
|
94
|
+
};
|
|
77
95
|
}
|
|
78
96
|
|
|
79
97
|
/**
|
|
@@ -141,12 +159,43 @@ export function 낡았나(기준, 이제 = Date.now()) {
|
|
|
141
159
|
*
|
|
142
160
|
* 요금을 모르면 null 이다. 여기서 0 을 돌려주면 부르는 쪽이 「$0.00」 을
|
|
143
161
|
* 찍게 되고, 그건 공짜라는 뜻이 되어 버린다.
|
|
162
|
+
*
|
|
163
|
+
* ── 「보낸 토큰」 이 규격마다 다른 것을 여기서 안 틀리게 ─────────────
|
|
164
|
+
*
|
|
165
|
+
* Anthropic 은 `input_tokens` 에 **캐시로 읽힌 것을 안 넣는다.** OpenAI 는
|
|
166
|
+
* `prompt_tokens` 가 캐시까지 **다 넣은 총계**다. 그래서 어느 한쪽에 맞춰
|
|
167
|
+
* 더하거나 빼면 다른 쪽에서 두 번 세거나 빠뜨린다. 이미 그 차이를 한 곳에서
|
|
168
|
+
* 흡수해 둔 값이 있다 — `usage.prompt` (backend/adapter.js 의 보낸토큰).
|
|
169
|
+
* 여기서는 그것을 총계로 보고, 캐시 몫을 빼서 정가로 낼 몫을 구한다.
|
|
170
|
+
*
|
|
171
|
+
* @param {{in?:number,out?:number,prompt?:number,cacheRead?:number,cacheWrite?:number}} 쓴것
|
|
172
|
+
* @param {object} 값 요금찾기() 가 준 것
|
|
173
|
+
* @returns {{달러:number,입력:number,출력:number,캐시:number,캐시모름:boolean}|null}
|
|
144
174
|
*/
|
|
145
|
-
export function 돈셈({ in: 입 = 0, out: 출 = 0 } = {}, 값) {
|
|
175
|
+
export function 돈셈({ in: 입 = 0, out: 출 = 0, prompt: 보냄 = 0, cacheRead: 읽음 = 0, cacheWrite: 씀 = 0 } = {}, 값) {
|
|
146
176
|
if (!값 || 값.없음 || 값.입력 == null || 값.출력 == null) return null;
|
|
147
|
-
const
|
|
148
|
-
const
|
|
149
|
-
|
|
177
|
+
const n = (v) => Math.max(0, Number(v) || 0);
|
|
178
|
+
const 읽 = n(읽음);
|
|
179
|
+
const 썼 = n(씀);
|
|
180
|
+
// prompt 가 있으면 그것이 총계다. 없는 자리(옛 기록·검사)는 in 을 총계로 본다.
|
|
181
|
+
const 총계 = n(보냄) || n(입);
|
|
182
|
+
const 정가몫 = Math.max(0, 총계 - 읽 - 썼);
|
|
183
|
+
|
|
184
|
+
const 캐시값있나 = 값.캐시읽기 != null || 값.캐시쓰기 != null;
|
|
185
|
+
const 읽기값 = 값.캐시읽기 ?? 값.입력;
|
|
186
|
+
const 쓰기값 = 값.캐시쓰기 ?? 값.입력;
|
|
187
|
+
|
|
188
|
+
const 정가돈 = 정가몫 / 단위 * 값.입력;
|
|
189
|
+
const 캐시돈 = (읽 / 단위 * 읽기값) + (썼 / 단위 * 쓰기값);
|
|
190
|
+
const 출돈 = n(출) / 단위 * 값.출력;
|
|
191
|
+
return {
|
|
192
|
+
달러: 정가돈 + 캐시돈 + 출돈,
|
|
193
|
+
입력: 정가돈,
|
|
194
|
+
출력: 출돈,
|
|
195
|
+
캐시: 캐시돈,
|
|
196
|
+
// 캐시를 쓴 적이 있는데 그 값을 모르면, 이 금액은 실제보다 **크다**.
|
|
197
|
+
캐시모름: (읽 + 썼) > 0 && !캐시값있나,
|
|
198
|
+
};
|
|
150
199
|
}
|
|
151
200
|
|
|
152
201
|
/**
|
|
@@ -190,7 +239,21 @@ export function 어디서온값(값) {
|
|
|
190
239
|
export function 요금적는법(모델, { 제공자 = null, 설정파일 = '.deel/config.json' } = {}) {
|
|
191
240
|
const 줄 = [
|
|
192
241
|
`요금을 모릅니다. ${설정파일} 에 100만 토큰당 달러로 적으면 셈합니다.`,
|
|
193
|
-
|
|
242
|
+
/*
|
|
243
|
+
* 예시에 `0` 을 쓰면 안 된다.
|
|
244
|
+
*
|
|
245
|
+
* `0` 은 **유효한 요금**이다. 안내를 그대로 복사해 붙이고 모델 이름만
|
|
246
|
+
* 바꾸면 값읽기() 가 그것을 정상 요금으로 받아, 그 뒤로 쓴 돈이 전부
|
|
247
|
+
* `$0.00` 으로 뜬다. 「모릅니다」 라고 말하려다 「공짜입니다」 라고
|
|
248
|
+
* 말하게 되는 셈이다 — 이 프로그램에서 제일 나쁜 종류의 고장이다.
|
|
249
|
+
*
|
|
250
|
+
* 그래서 자리표는 **숫자가 아닌 것**으로 둔다. 그대로 붙이면 값읽기가
|
|
251
|
+
* 못 읽고 여전히 「모릅니다」 라고 말한다.
|
|
252
|
+
*/
|
|
253
|
+
` "요금": { "${모델 || '모델이름'}": { "입력": <100만 입력토큰당 달러>, "출력": <100만 출력토큰당 달러>, "기준": "${new Date().toISOString().slice(0, 10)}" } }`,
|
|
254
|
+
// 캐시 값은 안 적어도 된다 — 적으면 금액이 정확해지고, 안 적으면 정가로
|
|
255
|
+
// 세서 실제보다 크게 나온다. 있는 줄을 몰라서 못 적는 일이 없게 적어 둔다.
|
|
256
|
+
' 캐시를 쓰는 창구면 `"캐시읽기": <달러>, "캐시쓰기": <달러>` 도 같이 적을 수 있습니다 (없으면 정가로 셉니다).',
|
|
194
257
|
];
|
|
195
258
|
if (제공자?.요금표주소) 줄.push(` 요금표: ${제공자.요금표주소}`);
|
|
196
259
|
return 줄;
|
package/src/backend/probe.js
CHANGED
|
@@ -215,6 +215,7 @@ export async function probe(conn, onStep = () => {}) {
|
|
|
215
215
|
});
|
|
216
216
|
let chunks = 0;
|
|
217
217
|
let firstMs = 0;
|
|
218
|
+
let 읽다실패 = null;
|
|
218
219
|
if (st.ok && st.res?.body) {
|
|
219
220
|
const t0 = Date.now();
|
|
220
221
|
try {
|
|
@@ -229,15 +230,28 @@ export async function probe(conn, onStep = () => {}) {
|
|
|
229
230
|
chunks += hits || (text.trim() ? 1 : 0);
|
|
230
231
|
}
|
|
231
232
|
reader.cancel().catch(() => {});
|
|
232
|
-
} catch {
|
|
233
|
+
} catch (e) {
|
|
234
|
+
/*
|
|
235
|
+
* 여기가 빈 catch 였다.
|
|
236
|
+
*
|
|
237
|
+
* 그러면 `getReader()` 가 던진 TypeError(본문이 우리가 기대한 꼴이
|
|
238
|
+
* 아니다)도 삼켜지고, 조각이 0개인 채로 아래에서 **「한 번에 옵니다」**
|
|
239
|
+
* 라고 적힌다. 우리 잘못이 서버 진단으로 둔갑하는 것이다 — 사람은
|
|
240
|
+
* 멀쩡한 창구를 스트리밍 안 되는 창구로 알고 쓴다.
|
|
241
|
+
*
|
|
242
|
+
* 끊긴 것(Abort)은 진짜로 예상한 일이라 그대로 넘긴다. 그 밖은 적는다.
|
|
243
|
+
*/
|
|
244
|
+
if (e?.name !== 'AbortError') 읽다실패 = String(e?.message ?? e).slice(0, 120);
|
|
245
|
+
}
|
|
233
246
|
}
|
|
234
247
|
add({
|
|
235
248
|
id: 'stream',
|
|
236
249
|
label: '스트리밍',
|
|
237
|
-
status: chunks > 2 ? 'ok' : st.ok ? 'warn' : 'no',
|
|
250
|
+
status: chunks > 2 ? 'ok' : (st.ok && !읽다실패) ? 'warn' : 'no',
|
|
238
251
|
detail: chunks > 2
|
|
239
252
|
? `조각 ${chunks}개, 첫 응답 ${firstMs}ms`
|
|
240
|
-
:
|
|
253
|
+
: 읽다실패 ? `흘러오는 것을 읽지 못했습니다 — ${읽다실패}`
|
|
254
|
+
: st.ok ? '한 번에 옵니다 — 화면은 스피너로 대체합니다' : serverMessage(st),
|
|
241
255
|
ms: st.ms,
|
|
242
256
|
});
|
|
243
257
|
facts.streaming = chunks > 2;
|