@carboncode/cli 0.1.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/LICENSE +21 -0
- package/LICENSES/DeepSeek-Reasonix-MIT.txt +21 -0
- package/README.md +109 -0
- package/README.zh-CN.md +91 -0
- package/THIRD_PARTY_NOTICES.md +14 -0
- package/dashboard/app.css +3233 -0
- package/dashboard/dist/app.js +30444 -0
- package/dashboard/dist/app.js.map +1 -0
- package/dashboard/dist/vendor-hljs.css +10 -0
- package/dashboard/dist/vendor-uplot.css +1 -0
- package/dashboard/index.html +19 -0
- package/data/deepseek-tokenizer.json.gz +0 -0
- package/dist/cli/acp-35C4ME6Y.js +711 -0
- package/dist/cli/acp-35C4ME6Y.js.map +1 -0
- package/dist/cli/chat-A6UJDPGV.js +51 -0
- package/dist/cli/chat-A6UJDPGV.js.map +1 -0
- package/dist/cli/chunk-2425HK6U.js +54 -0
- package/dist/cli/chunk-2425HK6U.js.map +1 -0
- package/dist/cli/chunk-25T6CVUP.js +172 -0
- package/dist/cli/chunk-25T6CVUP.js.map +1 -0
- package/dist/cli/chunk-2UQP6H6T.js +31 -0
- package/dist/cli/chunk-2UQP6H6T.js.map +1 -0
- package/dist/cli/chunk-3OAR6NVL.js +96 -0
- package/dist/cli/chunk-3OAR6NVL.js.map +1 -0
- package/dist/cli/chunk-3T6VBZCL.js +54 -0
- package/dist/cli/chunk-3T6VBZCL.js.map +1 -0
- package/dist/cli/chunk-4IBIPQVB.js +153 -0
- package/dist/cli/chunk-4IBIPQVB.js.map +1 -0
- package/dist/cli/chunk-4MQ3VURH.js +3106 -0
- package/dist/cli/chunk-4MQ3VURH.js.map +1 -0
- package/dist/cli/chunk-4TVNJWMA.js +11619 -0
- package/dist/cli/chunk-4TVNJWMA.js.map +1 -0
- package/dist/cli/chunk-4VR6XF4P.js +341 -0
- package/dist/cli/chunk-4VR6XF4P.js.map +1 -0
- package/dist/cli/chunk-5QCB62C4.js +25319 -0
- package/dist/cli/chunk-5QCB62C4.js.map +1 -0
- package/dist/cli/chunk-6OWJV3YW.js +390 -0
- package/dist/cli/chunk-6OWJV3YW.js.map +1 -0
- package/dist/cli/chunk-7EO27TB3.js +130 -0
- package/dist/cli/chunk-7EO27TB3.js.map +1 -0
- package/dist/cli/chunk-7L2WTRNU.js +308 -0
- package/dist/cli/chunk-7L2WTRNU.js.map +1 -0
- package/dist/cli/chunk-BHTZFEYE.js +47 -0
- package/dist/cli/chunk-BHTZFEYE.js.map +1 -0
- package/dist/cli/chunk-BSGCXZQN.js +343 -0
- package/dist/cli/chunk-BSGCXZQN.js.map +1 -0
- package/dist/cli/chunk-BSINVTTL.js +464 -0
- package/dist/cli/chunk-BSINVTTL.js.map +1 -0
- package/dist/cli/chunk-CPKCNHRR.js +323 -0
- package/dist/cli/chunk-CPKCNHRR.js.map +1 -0
- package/dist/cli/chunk-CXVWUPA3.js +96 -0
- package/dist/cli/chunk-CXVWUPA3.js.map +1 -0
- package/dist/cli/chunk-D5NFKRGO.js +160 -0
- package/dist/cli/chunk-D5NFKRGO.js.map +1 -0
- package/dist/cli/chunk-ECHSFYOY.js +109 -0
- package/dist/cli/chunk-ECHSFYOY.js.map +1 -0
- package/dist/cli/chunk-FEZK652I.js +3644 -0
- package/dist/cli/chunk-FEZK652I.js.map +1 -0
- package/dist/cli/chunk-GALC45Q2.js +696 -0
- package/dist/cli/chunk-GALC45Q2.js.map +1 -0
- package/dist/cli/chunk-IAUOP25G.js +2984 -0
- package/dist/cli/chunk-IAUOP25G.js.map +1 -0
- package/dist/cli/chunk-ILJOIQ5W.js +163 -0
- package/dist/cli/chunk-ILJOIQ5W.js.map +1 -0
- package/dist/cli/chunk-IX6XI2RG.js +225 -0
- package/dist/cli/chunk-IX6XI2RG.js.map +1 -0
- package/dist/cli/chunk-J5BYPUB5.js +62795 -0
- package/dist/cli/chunk-J5BYPUB5.js.map +1 -0
- package/dist/cli/chunk-J5XJHLWM.js +55 -0
- package/dist/cli/chunk-J5XJHLWM.js.map +1 -0
- package/dist/cli/chunk-JKGYMRX5.js +101 -0
- package/dist/cli/chunk-JKGYMRX5.js.map +1 -0
- package/dist/cli/chunk-JMBMLOBP.js +26 -0
- package/dist/cli/chunk-JMBMLOBP.js.map +1 -0
- package/dist/cli/chunk-LN3B5PMX.js +128 -0
- package/dist/cli/chunk-LN3B5PMX.js.map +1 -0
- package/dist/cli/chunk-M2UFZUX3.js +635 -0
- package/dist/cli/chunk-M2UFZUX3.js.map +1 -0
- package/dist/cli/chunk-PJS34556.js +809 -0
- package/dist/cli/chunk-PJS34556.js.map +1 -0
- package/dist/cli/chunk-QJG7OF27.js +655 -0
- package/dist/cli/chunk-QJG7OF27.js.map +1 -0
- package/dist/cli/chunk-QVC75MR3.js +232 -0
- package/dist/cli/chunk-QVC75MR3.js.map +1 -0
- package/dist/cli/chunk-S2KIUQKQ.js +378 -0
- package/dist/cli/chunk-S2KIUQKQ.js.map +1 -0
- package/dist/cli/chunk-S4XVGLRW.js +499 -0
- package/dist/cli/chunk-S4XVGLRW.js.map +1 -0
- package/dist/cli/chunk-T5TQ4NDT.js +190 -0
- package/dist/cli/chunk-T5TQ4NDT.js.map +1 -0
- package/dist/cli/chunk-TH756VLN.js +1924 -0
- package/dist/cli/chunk-TH756VLN.js.map +1 -0
- package/dist/cli/chunk-TUK7OWJA.js +51 -0
- package/dist/cli/chunk-TUK7OWJA.js.map +1 -0
- package/dist/cli/chunk-U4IJVG32.js +363 -0
- package/dist/cli/chunk-U4IJVG32.js.map +1 -0
- package/dist/cli/chunk-UI66BH6D.js +624 -0
- package/dist/cli/chunk-UI66BH6D.js.map +1 -0
- package/dist/cli/chunk-VPMBGAND.js +53 -0
- package/dist/cli/chunk-VPMBGAND.js.map +1 -0
- package/dist/cli/chunk-WLHH3OSR.js +522 -0
- package/dist/cli/chunk-WLHH3OSR.js.map +1 -0
- package/dist/cli/chunk-WRN65TRD.js +908 -0
- package/dist/cli/chunk-WRN65TRD.js.map +1 -0
- package/dist/cli/chunk-X53B3JIX.js +34320 -0
- package/dist/cli/chunk-X53B3JIX.js.map +1 -0
- package/dist/cli/chunk-XJ5SRLKK.js +50 -0
- package/dist/cli/chunk-XJ5SRLKK.js.map +1 -0
- package/dist/cli/chunk-YZSXRGFH.js +54 -0
- package/dist/cli/chunk-YZSXRGFH.js.map +1 -0
- package/dist/cli/code-4TUTAGO5.js +163 -0
- package/dist/cli/code-4TUTAGO5.js.map +1 -0
- package/dist/cli/commands-KMOZEYCF.js +356 -0
- package/dist/cli/commands-KMOZEYCF.js.map +1 -0
- package/dist/cli/commit-DTFA56VQ.js +292 -0
- package/dist/cli/commit-DTFA56VQ.js.map +1 -0
- package/dist/cli/desktop-7N3MHNBD.js +1274 -0
- package/dist/cli/desktop-7N3MHNBD.js.map +1 -0
- package/dist/cli/devtools-HW3WDT3Q.js +91 -0
- package/dist/cli/devtools-HW3WDT3Q.js.map +1 -0
- package/dist/cli/diff-E5OWTF4C.js +165 -0
- package/dist/cli/diff-E5OWTF4C.js.map +1 -0
- package/dist/cli/doctor-IEJQRJMN.js +27 -0
- package/dist/cli/doctor-IEJQRJMN.js.map +1 -0
- package/dist/cli/events-4625EGXI.js +340 -0
- package/dist/cli/events-4625EGXI.js.map +1 -0
- package/dist/cli/index.js +3536 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/mcp-PDI2PDLG.js +277 -0
- package/dist/cli/mcp-PDI2PDLG.js.map +1 -0
- package/dist/cli/mcp-browse-OSPXOFPZ.js +178 -0
- package/dist/cli/mcp-browse-OSPXOFPZ.js.map +1 -0
- package/dist/cli/mcp-inspect-QRFVTHMF.js +148 -0
- package/dist/cli/mcp-inspect-QRFVTHMF.js.map +1 -0
- package/dist/cli/package.json +3 -0
- package/dist/cli/prompt-3CDII3UO.js +16 -0
- package/dist/cli/prompt-3CDII3UO.js.map +1 -0
- package/dist/cli/prune-sessions-KZX4SXKW.js +44 -0
- package/dist/cli/prune-sessions-KZX4SXKW.js.map +1 -0
- package/dist/cli/replay-HYOSRQIV.js +291 -0
- package/dist/cli/replay-HYOSRQIV.js.map +1 -0
- package/dist/cli/run-2ZHADOUP.js +220 -0
- package/dist/cli/run-2ZHADOUP.js.map +1 -0
- package/dist/cli/server-X75PAZG5.js +3572 -0
- package/dist/cli/server-X75PAZG5.js.map +1 -0
- package/dist/cli/sessions-POOZA5CQ.js +120 -0
- package/dist/cli/sessions-POOZA5CQ.js.map +1 -0
- package/dist/cli/setup-YLPFI3OH.js +618 -0
- package/dist/cli/setup-YLPFI3OH.js.map +1 -0
- package/dist/cli/stats-NXJ3TO2D.js +16 -0
- package/dist/cli/stats-NXJ3TO2D.js.map +1 -0
- package/dist/cli/update-ZUO5MKQ6.js +15 -0
- package/dist/cli/update-ZUO5MKQ6.js.map +1 -0
- package/dist/cli/version-NXXWE3WN.js +33 -0
- package/dist/cli/version-NXXWE3WN.js.map +1 -0
- package/dist/index.d.ts +2523 -0
- package/dist/index.js +15408 -0
- package/dist/index.js.map +1 -0
- package/package.json +112 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire as __cr } from 'node:module'; if (typeof globalThis.require === 'undefined') { globalThis.require = __cr(import.meta.url); }
|
|
3
|
+
|
|
4
|
+
// src/tokenizer.ts
|
|
5
|
+
import { existsSync, readFileSync } from "fs";
|
|
6
|
+
import { createRequire } from "module";
|
|
7
|
+
import { dirname, join } from "path";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
import { gunzipSync } from "zlib";
|
|
10
|
+
function buildByteToChar() {
|
|
11
|
+
const result = new Array(256);
|
|
12
|
+
const bs = [];
|
|
13
|
+
for (let b = 33; b <= 126; b++) bs.push(b);
|
|
14
|
+
for (let b = 161; b <= 172; b++) bs.push(b);
|
|
15
|
+
for (let b = 174; b <= 255; b++) bs.push(b);
|
|
16
|
+
const cs = bs.slice();
|
|
17
|
+
let n = 0;
|
|
18
|
+
for (let b = 0; b < 256; b++) {
|
|
19
|
+
if (!bs.includes(b)) {
|
|
20
|
+
bs.push(b);
|
|
21
|
+
cs.push(256 + n);
|
|
22
|
+
n++;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
for (let i = 0; i < bs.length; i++) {
|
|
26
|
+
result[bs[i]] = String.fromCodePoint(cs[i]);
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
var cached = null;
|
|
31
|
+
function resolveDataPath() {
|
|
32
|
+
if (process.env.REASONIX_TOKENIZER_PATH) return process.env.REASONIX_TOKENIZER_PATH;
|
|
33
|
+
const candidates = [];
|
|
34
|
+
try {
|
|
35
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
36
|
+
candidates.push(join(here, "..", "data", "deepseek-tokenizer.json.gz"));
|
|
37
|
+
candidates.push(join(here, "..", "..", "data", "deepseek-tokenizer.json.gz"));
|
|
38
|
+
} catch {
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const req = createRequire(import.meta.url);
|
|
42
|
+
candidates.push(
|
|
43
|
+
join(dirname(req.resolve("reasonix/package.json")), "data", "deepseek-tokenizer.json.gz")
|
|
44
|
+
);
|
|
45
|
+
} catch {
|
|
46
|
+
}
|
|
47
|
+
for (const p of candidates) {
|
|
48
|
+
if (existsSync(p)) return p;
|
|
49
|
+
}
|
|
50
|
+
return candidates[0] ?? join(process.cwd(), "data", "deepseek-tokenizer.json.gz");
|
|
51
|
+
}
|
|
52
|
+
function loadTokenizer() {
|
|
53
|
+
if (cached) return cached;
|
|
54
|
+
const buf = readFileSync(resolveDataPath());
|
|
55
|
+
const json = gunzipSync(buf).toString("utf8");
|
|
56
|
+
const data = JSON.parse(json);
|
|
57
|
+
const mergeRank = /* @__PURE__ */ new Map();
|
|
58
|
+
for (let i = 0; i < data.model.merges.length; i++) {
|
|
59
|
+
mergeRank.set(data.model.merges[i], i);
|
|
60
|
+
}
|
|
61
|
+
const splitRegexes = [];
|
|
62
|
+
for (const p of data.pre_tokenizer.pretokenizers) {
|
|
63
|
+
if (p.type === "Split") {
|
|
64
|
+
splitRegexes.push(new RegExp(p.pattern.Regex, "gu"));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const addedMap = /* @__PURE__ */ new Map();
|
|
68
|
+
const addedContents = [];
|
|
69
|
+
for (const t of data.added_tokens) {
|
|
70
|
+
if (!t.special) {
|
|
71
|
+
addedMap.set(t.content, t.id);
|
|
72
|
+
addedContents.push(t.content);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
addedContents.sort((a, b) => b.length - a.length);
|
|
76
|
+
const addedPattern = addedContents.length ? new RegExp(addedContents.map(escapeRegex).join("|"), "g") : null;
|
|
77
|
+
cached = {
|
|
78
|
+
vocab: data.model.vocab,
|
|
79
|
+
mergeRank,
|
|
80
|
+
splitRegexes,
|
|
81
|
+
byteToChar: buildByteToChar(),
|
|
82
|
+
addedPattern,
|
|
83
|
+
addedMap
|
|
84
|
+
};
|
|
85
|
+
return cached;
|
|
86
|
+
}
|
|
87
|
+
function escapeRegex(s) {
|
|
88
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
89
|
+
}
|
|
90
|
+
function applySplit(chunks, re) {
|
|
91
|
+
const out = [];
|
|
92
|
+
for (const chunk of chunks) {
|
|
93
|
+
if (!chunk) continue;
|
|
94
|
+
re.lastIndex = 0;
|
|
95
|
+
let last = 0;
|
|
96
|
+
for (const m of chunk.matchAll(re)) {
|
|
97
|
+
const idx = m.index ?? 0;
|
|
98
|
+
if (idx > last) out.push(chunk.slice(last, idx));
|
|
99
|
+
if (m[0].length > 0) out.push(m[0]);
|
|
100
|
+
last = idx + m[0].length;
|
|
101
|
+
}
|
|
102
|
+
if (last < chunk.length) out.push(chunk.slice(last));
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
function byteLevelEncode(s, byteToChar) {
|
|
107
|
+
const bytes = new TextEncoder().encode(s);
|
|
108
|
+
let out = "";
|
|
109
|
+
for (let i = 0; i < bytes.length; i++) out += byteToChar[bytes[i]];
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
function bpeEncode(piece, mergeRank) {
|
|
113
|
+
if (piece.length <= 1) return piece ? [piece] : [];
|
|
114
|
+
let word = Array.from(piece);
|
|
115
|
+
while (true) {
|
|
116
|
+
let bestIdx = -1;
|
|
117
|
+
let bestRank = Number.POSITIVE_INFINITY;
|
|
118
|
+
for (let i = 0; i < word.length - 1; i++) {
|
|
119
|
+
const pair = `${word[i]} ${word[i + 1]}`;
|
|
120
|
+
const rank = mergeRank.get(pair);
|
|
121
|
+
if (rank !== void 0 && rank < bestRank) {
|
|
122
|
+
bestRank = rank;
|
|
123
|
+
bestIdx = i;
|
|
124
|
+
if (rank === 0) break;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (bestIdx < 0) break;
|
|
128
|
+
word = [
|
|
129
|
+
...word.slice(0, bestIdx),
|
|
130
|
+
word[bestIdx] + word[bestIdx + 1],
|
|
131
|
+
...word.slice(bestIdx + 2)
|
|
132
|
+
];
|
|
133
|
+
if (word.length === 1) break;
|
|
134
|
+
}
|
|
135
|
+
return word;
|
|
136
|
+
}
|
|
137
|
+
function encode(text) {
|
|
138
|
+
if (!text) return [];
|
|
139
|
+
const t = loadTokenizer();
|
|
140
|
+
const ids = [];
|
|
141
|
+
const process2 = (segment) => {
|
|
142
|
+
if (!segment) return;
|
|
143
|
+
let chunks = [segment];
|
|
144
|
+
for (const re of t.splitRegexes) chunks = applySplit(chunks, re);
|
|
145
|
+
for (const chunk of chunks) {
|
|
146
|
+
if (!chunk) continue;
|
|
147
|
+
const byteLevel = byteLevelEncode(chunk, t.byteToChar);
|
|
148
|
+
const pieces = bpeEncode(byteLevel, t.mergeRank);
|
|
149
|
+
for (const p of pieces) {
|
|
150
|
+
const id = t.vocab[p];
|
|
151
|
+
if (id !== void 0) ids.push(id);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
if (t.addedPattern) {
|
|
156
|
+
t.addedPattern.lastIndex = 0;
|
|
157
|
+
let last = 0;
|
|
158
|
+
for (const m of text.matchAll(t.addedPattern)) {
|
|
159
|
+
const idx = m.index ?? 0;
|
|
160
|
+
if (idx > last) process2(text.slice(last, idx));
|
|
161
|
+
const id = t.addedMap.get(m[0]);
|
|
162
|
+
if (id !== void 0) ids.push(id);
|
|
163
|
+
last = idx + m[0].length;
|
|
164
|
+
}
|
|
165
|
+
if (last < text.length) process2(text.slice(last));
|
|
166
|
+
} else {
|
|
167
|
+
process2(text);
|
|
168
|
+
}
|
|
169
|
+
return ids;
|
|
170
|
+
}
|
|
171
|
+
function countTokens(text) {
|
|
172
|
+
return encode(text).length;
|
|
173
|
+
}
|
|
174
|
+
var DEFAULT_BOUNDED_TOKENIZE_CHARS = 2 * 1024;
|
|
175
|
+
function countTokensBounded(text, maxChars = DEFAULT_BOUNDED_TOKENIZE_CHARS) {
|
|
176
|
+
if (text.length === 0) return 0;
|
|
177
|
+
const cap = Math.floor(maxChars);
|
|
178
|
+
if (cap > 0 && text.length <= cap) return countTokens(text);
|
|
179
|
+
if (cap <= 0) return Math.max(1, Math.ceil(text.length * 0.3));
|
|
180
|
+
const headChars = Math.ceil(cap / 2);
|
|
181
|
+
const tailChars = Math.floor(cap / 2);
|
|
182
|
+
const head = text.slice(0, headChars);
|
|
183
|
+
const tail = tailChars > 0 ? text.slice(-tailChars) : "";
|
|
184
|
+
const sampleChars = head.length + tail.length;
|
|
185
|
+
const sampleTokens = countTokens(head) + countTokens(tail);
|
|
186
|
+
const ratio = sampleChars > 0 ? sampleTokens / sampleChars : 0.3;
|
|
187
|
+
return Math.max(1, Math.ceil(text.length * ratio));
|
|
188
|
+
}
|
|
189
|
+
var BOS = "<\uFF5Cbegin\u2581of\u2581sentence\uFF5C>";
|
|
190
|
+
var EOS = "<\uFF5Cend\u2581of\u2581sentence\uFF5C>";
|
|
191
|
+
var USER_SP = "<\uFF5CUser\uFF5C>";
|
|
192
|
+
var ASSISTANT_SP = "<\uFF5CAssistant\uFF5C>";
|
|
193
|
+
var THINK_START = "<think>";
|
|
194
|
+
var THINK_END = "</think>";
|
|
195
|
+
var DSML = "\uFF5CDSML\uFF5C";
|
|
196
|
+
var TC_BEGIN = `<${DSML}tool_calls>`;
|
|
197
|
+
var TC_END = `</${DSML}tool_calls>`;
|
|
198
|
+
var INVOKE_BEGIN = `<${DSML}invoke name="`;
|
|
199
|
+
var INVOKE_END = `</${DSML}invoke>`;
|
|
200
|
+
var PARAM_TEMPLATE = `<${DSML}parameter name="{key}" string="{is_str}">{value}</${DSML}parameter>`;
|
|
201
|
+
var TOOL_RESULT_TEMPLATE = "<tool_result>{content}</tool_result>";
|
|
202
|
+
var toolsTemplateCache = /* @__PURE__ */ new WeakMap();
|
|
203
|
+
function renderTools(tools) {
|
|
204
|
+
const cached2 = toolsTemplateCache.get(tools);
|
|
205
|
+
if (cached2 !== void 0) return cached2;
|
|
206
|
+
const schemas = tools.map((t) => {
|
|
207
|
+
const fn = t.function ?? t;
|
|
208
|
+
return JSON.stringify(fn);
|
|
209
|
+
}).join("\n");
|
|
210
|
+
const rendered = `## Tools
|
|
211
|
+
|
|
212
|
+
You have access to a set of tools to help answer the user's question. You can invoke tools by writing a "<${DSML}tool_calls>" block like the following:
|
|
213
|
+
|
|
214
|
+
<${DSML}tool_calls>
|
|
215
|
+
<${DSML}invoke name="$TOOL_NAME">
|
|
216
|
+
<${DSML}parameter name="$PARAMETER_NAME" string="true|false">$PARAMETER_VALUE</${DSML}parameter>
|
|
217
|
+
...
|
|
218
|
+
</${DSML}invoke>
|
|
219
|
+
<${DSML}invoke name="$TOOL_NAME2">
|
|
220
|
+
...
|
|
221
|
+
</${DSML}invoke>
|
|
222
|
+
</${DSML}tool_calls>
|
|
223
|
+
|
|
224
|
+
String parameters should be specified as is and set \`string="true"\`. For all other types (numbers, booleans, arrays, objects), pass the value in JSON format and set \`string="false"\`.
|
|
225
|
+
|
|
226
|
+
If thinking_mode is enabled (triggered by ${THINK_START}), you MUST output your complete reasoning inside ${THINK_START}...${THINK_END} BEFORE any tool calls or final response.
|
|
227
|
+
|
|
228
|
+
Otherwise, output directly after ${THINK_END} with tool calls or final response.
|
|
229
|
+
|
|
230
|
+
### Available Tool Schemas
|
|
231
|
+
|
|
232
|
+
${schemas}
|
|
233
|
+
|
|
234
|
+
You MUST strictly follow the above defined tool name and parameter schemas to invoke tool calls.`;
|
|
235
|
+
toolsTemplateCache.set(tools, rendered);
|
|
236
|
+
return rendered;
|
|
237
|
+
}
|
|
238
|
+
function encodeArgumentsToDsml(argsJson) {
|
|
239
|
+
let args;
|
|
240
|
+
try {
|
|
241
|
+
args = JSON.parse(argsJson);
|
|
242
|
+
} catch {
|
|
243
|
+
args = { arguments: argsJson };
|
|
244
|
+
}
|
|
245
|
+
return Object.entries(args).map(
|
|
246
|
+
([k, v]) => PARAM_TEMPLATE.replace("{key}", k).replace("{is_str}", typeof v === "string" ? "true" : "false").replace("{value}", typeof v === "string" ? v : JSON.stringify(v))
|
|
247
|
+
).join("\n");
|
|
248
|
+
}
|
|
249
|
+
function renderToolCallsDsml(toolCalls) {
|
|
250
|
+
const invokes = toolCalls.map((tc) => {
|
|
251
|
+
const name = tc.function?.name ?? "";
|
|
252
|
+
const argsJson = tc.function?.arguments ?? "{}";
|
|
253
|
+
return `${INVOKE_BEGIN + name}">
|
|
254
|
+
${encodeArgumentsToDsml(argsJson)}
|
|
255
|
+
${INVOKE_END}`;
|
|
256
|
+
}).join("\n");
|
|
257
|
+
return `
|
|
258
|
+
|
|
259
|
+
${TC_BEGIN}
|
|
260
|
+
${invokes}
|
|
261
|
+
${TC_END}`;
|
|
262
|
+
}
|
|
263
|
+
function mergeToolMessages(messages) {
|
|
264
|
+
const merged = [];
|
|
265
|
+
for (const msg of messages) {
|
|
266
|
+
const role = msg.role ?? "user";
|
|
267
|
+
if (role === "tool") {
|
|
268
|
+
const toolBlock = TOOL_RESULT_TEMPLATE.replace("{content}", msg.content ?? "");
|
|
269
|
+
const last = merged[merged.length - 1];
|
|
270
|
+
if (last && last.role === "user" && Array.isArray(last._toolBlocks) && Array.isArray(last._textParts)) {
|
|
271
|
+
last._toolBlocks.push(toolBlock);
|
|
272
|
+
last.content = `${last._textParts.join("\n\n")}
|
|
273
|
+
|
|
274
|
+
${last._toolBlocks.join("\n")}`.replace(
|
|
275
|
+
/^\n\n/,
|
|
276
|
+
""
|
|
277
|
+
);
|
|
278
|
+
} else {
|
|
279
|
+
merged.push({
|
|
280
|
+
role: "user",
|
|
281
|
+
content: toolBlock,
|
|
282
|
+
_textParts: [],
|
|
283
|
+
_toolBlocks: [toolBlock]
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
} else if (role === "user") {
|
|
287
|
+
const text = msg.content ?? "";
|
|
288
|
+
const last = merged[merged.length - 1];
|
|
289
|
+
if (last && last.role === "user" && Array.isArray(last._toolBlocks) && Array.isArray(last._textParts)) {
|
|
290
|
+
last._textParts.push(text);
|
|
291
|
+
last.content = `${last._textParts.join("\n\n")}
|
|
292
|
+
|
|
293
|
+
${last._toolBlocks.join("\n\n")}`.replace(
|
|
294
|
+
/^\n\n/,
|
|
295
|
+
""
|
|
296
|
+
);
|
|
297
|
+
} else {
|
|
298
|
+
merged.push({
|
|
299
|
+
...msg,
|
|
300
|
+
role: "user",
|
|
301
|
+
content: text,
|
|
302
|
+
_textParts: [text],
|
|
303
|
+
_toolBlocks: []
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
} else {
|
|
307
|
+
merged.push({ ...msg });
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
for (const m of merged) {
|
|
311
|
+
m._textParts = void 0;
|
|
312
|
+
m._toolBlocks = void 0;
|
|
313
|
+
}
|
|
314
|
+
return merged;
|
|
315
|
+
}
|
|
316
|
+
function dropThinkingMessages(messages) {
|
|
317
|
+
let lastUserIdx = -1;
|
|
318
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
319
|
+
const role = messages[i].role;
|
|
320
|
+
if (role === "user" || role === "developer") {
|
|
321
|
+
lastUserIdx = i;
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (lastUserIdx < 0) return messages;
|
|
326
|
+
const result = [];
|
|
327
|
+
for (let i = 0; i < messages.length; i++) {
|
|
328
|
+
const msg = messages[i];
|
|
329
|
+
if (i < lastUserIdx && msg.role === "developer") continue;
|
|
330
|
+
if (i < lastUserIdx && msg.role === "assistant") {
|
|
331
|
+
result.push({ ...msg, reasoning_content: null });
|
|
332
|
+
} else {
|
|
333
|
+
result.push(msg);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return result;
|
|
337
|
+
}
|
|
338
|
+
function formatDeepSeekPrompt(messages, drop_thinking = false) {
|
|
339
|
+
if (messages.length === 0) return ASSISTANT_SP + THINK_END;
|
|
340
|
+
let msgs = messages;
|
|
341
|
+
if (drop_thinking) {
|
|
342
|
+
msgs = dropThinkingMessages(msgs);
|
|
343
|
+
}
|
|
344
|
+
const merged = mergeToolMessages(msgs);
|
|
345
|
+
let prompt = BOS;
|
|
346
|
+
for (let i = 0; i < merged.length; i++) {
|
|
347
|
+
const msg = merged[i];
|
|
348
|
+
const role = msg.role ?? "user";
|
|
349
|
+
const nextRole = i + 1 < merged.length ? merged[i + 1].role ?? "user" : null;
|
|
350
|
+
if (role === "system") {
|
|
351
|
+
prompt += msg.content ?? "";
|
|
352
|
+
} else if (role === "user") {
|
|
353
|
+
prompt += USER_SP + (msg.content ?? "");
|
|
354
|
+
if (nextRole === "assistant" || nextRole === null) {
|
|
355
|
+
prompt += ASSISTANT_SP + THINK_END;
|
|
356
|
+
}
|
|
357
|
+
} else if (role === "assistant") {
|
|
358
|
+
if (msg.reasoning_content) {
|
|
359
|
+
prompt += THINK_START + msg.reasoning_content + THINK_END;
|
|
360
|
+
}
|
|
361
|
+
if (msg.content) prompt += msg.content;
|
|
362
|
+
const tcs = msg.tool_calls;
|
|
363
|
+
if (Array.isArray(tcs) && tcs.length > 0) {
|
|
364
|
+
prompt += renderToolCallsDsml(tcs);
|
|
365
|
+
}
|
|
366
|
+
prompt += EOS;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return prompt;
|
|
370
|
+
}
|
|
371
|
+
function estimateConversationTokens(messages, drop_thinking = false) {
|
|
372
|
+
if (messages.length === 0) return 0;
|
|
373
|
+
return countTokensBounded(formatDeepSeekPrompt(messages, drop_thinking));
|
|
374
|
+
}
|
|
375
|
+
function estimateRequestTokens(messages, toolSpecs, drop_thinking = false) {
|
|
376
|
+
let total = estimateConversationTokens(messages, drop_thinking);
|
|
377
|
+
if (toolSpecs && toolSpecs.length > 0) {
|
|
378
|
+
total += countTokensBounded(renderTools(toolSpecs));
|
|
379
|
+
}
|
|
380
|
+
return total;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export {
|
|
384
|
+
resolveDataPath,
|
|
385
|
+
countTokens,
|
|
386
|
+
countTokensBounded,
|
|
387
|
+
estimateConversationTokens,
|
|
388
|
+
estimateRequestTokens
|
|
389
|
+
};
|
|
390
|
+
//# sourceMappingURL=chunk-6OWJV3YW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/tokenizer.ts"],"sourcesContent":["/** Encode-only DeepSeek V4 tokenizer port. Applies V4 chat template so token count tracks API `prompt_tokens`. */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { gunzipSync } from \"node:zlib\";\n\ninterface AddedToken {\n id: number;\n content: string;\n special: boolean;\n normalized: boolean;\n}\n\ninterface SplitPretokenizer {\n type: \"Split\";\n pattern: { Regex: string };\n behavior: \"Isolated\" | \"Removed\" | string;\n invert: boolean;\n}\n\ninterface ByteLevelPretokenizer {\n type: \"ByteLevel\";\n add_prefix_space: boolean;\n trim_offsets: boolean;\n use_regex: boolean;\n}\n\ntype Pretokenizer = SplitPretokenizer | ByteLevelPretokenizer;\n\ninterface TokenizerData {\n added_tokens: AddedToken[];\n pre_tokenizer: {\n type: \"Sequence\";\n pretokenizers: Pretokenizer[];\n };\n model: {\n type: \"BPE\";\n vocab: Record<string, number>;\n merges: string[];\n };\n}\n\ninterface LoadedTokenizer {\n vocab: Record<string, number>;\n mergeRank: Map<string, number>;\n splitRegexes: RegExp[];\n byteToChar: string[];\n /** Non-special added tokens only — special tokens in user text tokenize byte-by-byte (HF default). */\n addedPattern: RegExp | null;\n addedMap: Map<string, number>;\n}\n\n/** GPT-2 byte→unicode map; lets byte-level BPE vocab serialize as readable JSON strings. */\nfunction buildByteToChar(): string[] {\n const result: string[] = new Array(256);\n const bs: number[] = [];\n for (let b = 33; b <= 126; b++) bs.push(b);\n for (let b = 161; b <= 172; b++) bs.push(b);\n for (let b = 174; b <= 255; b++) bs.push(b);\n const cs = bs.slice();\n let n = 0;\n for (let b = 0; b < 256; b++) {\n if (!bs.includes(b)) {\n bs.push(b);\n cs.push(256 + n);\n n++;\n }\n }\n for (let i = 0; i < bs.length; i++) {\n result[bs[i]!] = String.fromCodePoint(cs[i]!);\n }\n return result;\n}\n\nlet cached: LoadedTokenizer | null = null;\n\n/** Two ../data candidates needed: dist/index.js AND dist/cli/index.js resolve to different roots. */\nexport function resolveDataPath(): string {\n if (process.env.REASONIX_TOKENIZER_PATH) return process.env.REASONIX_TOKENIZER_PATH;\n const candidates: string[] = [];\n try {\n const here = dirname(fileURLToPath(import.meta.url));\n candidates.push(join(here, \"..\", \"data\", \"deepseek-tokenizer.json.gz\"));\n candidates.push(join(here, \"..\", \"..\", \"data\", \"deepseek-tokenizer.json.gz\"));\n } catch {\n /* import.meta.url unavailable — skip to the package resolution step. */\n }\n try {\n const req = createRequire(import.meta.url);\n candidates.push(\n join(dirname(req.resolve(\"reasonix/package.json\")), \"data\", \"deepseek-tokenizer.json.gz\"),\n );\n } catch {\n /* Not installed as `reasonix/` — the earlier candidates still may hit. */\n }\n for (const p of candidates) {\n if (existsSync(p)) return p;\n }\n // Nothing exists — return the first candidate anyway so readFileSync\n // surfaces a concrete path in the ENOENT message (better than silent miss).\n return candidates[0] ?? join(process.cwd(), \"data\", \"deepseek-tokenizer.json.gz\");\n}\n\nfunction loadTokenizer(): LoadedTokenizer {\n if (cached) return cached;\n const buf = readFileSync(resolveDataPath());\n const json = gunzipSync(buf).toString(\"utf8\");\n const data = JSON.parse(json) as TokenizerData;\n\n const mergeRank = new Map<string, number>();\n for (let i = 0; i < data.model.merges.length; i++) {\n mergeRank.set(data.model.merges[i]!, i);\n }\n\n const splitRegexes: RegExp[] = [];\n for (const p of data.pre_tokenizer.pretokenizers) {\n if (p.type === \"Split\") {\n // All three Split rules use Isolated — matches become their own\n // pre-tokens and so do the in-between stretches. The ByteLevel\n // stage in the Sequence does no extra splitting here\n // (use_regex:false), so our 3 Split regexes are the whole story.\n splitRegexes.push(new RegExp(p.pattern.Regex, \"gu\"));\n }\n }\n\n const addedMap = new Map<string, number>();\n const addedContents: string[] = [];\n for (const t of data.added_tokens) {\n if (!t.special) {\n addedMap.set(t.content, t.id);\n addedContents.push(t.content);\n }\n }\n // Longest-first ensures greedy matching doesn't lose a longer token\n // to a shorter prefix (e.g. `<think>` before `<`).\n addedContents.sort((a, b) => b.length - a.length);\n const addedPattern = addedContents.length\n ? new RegExp(addedContents.map(escapeRegex).join(\"|\"), \"g\")\n : null;\n\n cached = {\n vocab: data.model.vocab,\n mergeRank,\n splitRegexes,\n byteToChar: buildByteToChar(),\n addedPattern,\n addedMap,\n };\n return cached;\n}\n\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction applySplit(chunks: string[], re: RegExp): string[] {\n const out: string[] = [];\n for (const chunk of chunks) {\n if (!chunk) continue;\n // Reset lastIndex — reusing a /g regex across matchAll iterations\n // is safe (matchAll internally advances), but across different\n // input strings we want a clean start.\n re.lastIndex = 0;\n let last = 0;\n for (const m of chunk.matchAll(re)) {\n const idx = m.index ?? 0;\n if (idx > last) out.push(chunk.slice(last, idx));\n if (m[0].length > 0) out.push(m[0]);\n last = idx + m[0].length;\n }\n if (last < chunk.length) out.push(chunk.slice(last));\n }\n return out;\n}\n\n/** UTF-8 bytes of `s`, each mapped to its byte-level visible char. */\nfunction byteLevelEncode(s: string, byteToChar: string[]): string {\n const bytes = new TextEncoder().encode(s);\n let out = \"\";\n for (let i = 0; i < bytes.length; i++) out += byteToChar[bytes[i]!];\n return out;\n}\n\nfunction bpeEncode(piece: string, mergeRank: Map<string, number>): string[] {\n if (piece.length <= 1) return piece ? [piece] : [];\n let word: string[] = Array.from(piece);\n while (true) {\n let bestIdx = -1;\n let bestRank = Number.POSITIVE_INFINITY;\n for (let i = 0; i < word.length - 1; i++) {\n const pair = `${word[i]} ${word[i + 1]}`;\n const rank = mergeRank.get(pair);\n if (rank !== undefined && rank < bestRank) {\n bestRank = rank;\n bestIdx = i;\n if (rank === 0) break; // 0 is already the best possible\n }\n }\n if (bestIdx < 0) break;\n word = [\n ...word.slice(0, bestIdx),\n word[bestIdx]! + word[bestIdx + 1]!,\n ...word.slice(bestIdx + 2),\n ];\n if (word.length === 1) break;\n }\n return word;\n}\n\nexport function encode(text: string): number[] {\n if (!text) return [];\n const t = loadTokenizer();\n const ids: number[] = [];\n\n const process = (segment: string) => {\n if (!segment) return;\n let chunks: string[] = [segment];\n for (const re of t.splitRegexes) chunks = applySplit(chunks, re);\n for (const chunk of chunks) {\n if (!chunk) continue;\n const byteLevel = byteLevelEncode(chunk, t.byteToChar);\n const pieces = bpeEncode(byteLevel, t.mergeRank);\n for (const p of pieces) {\n const id = t.vocab[p];\n // If not in vocab we silently skip: shouldn't happen for\n // byte-level BPE (every single byte has its own vocab entry),\n // but if a future tokenizer update breaks that invariant we'd\n // rather under-count than throw from a UI gauge.\n if (id !== undefined) ids.push(id);\n }\n }\n };\n\n if (t.addedPattern) {\n t.addedPattern.lastIndex = 0;\n let last = 0;\n for (const m of text.matchAll(t.addedPattern)) {\n const idx = m.index ?? 0;\n if (idx > last) process(text.slice(last, idx));\n const id = t.addedMap.get(m[0]);\n if (id !== undefined) ids.push(id);\n last = idx + m[0].length;\n }\n if (last < text.length) process(text.slice(last));\n } else {\n process(text);\n }\n return ids;\n}\n\nexport function countTokens(text: string): number {\n return encode(text).length;\n}\n\nexport const DEFAULT_BOUNDED_TOKENIZE_CHARS = 2 * 1024;\n\nexport function countTokensBounded(\n text: string,\n maxChars = DEFAULT_BOUNDED_TOKENIZE_CHARS,\n): number {\n if (text.length === 0) return 0;\n const cap = Math.floor(maxChars);\n if (cap > 0 && text.length <= cap) return countTokens(text);\n if (cap <= 0) return Math.max(1, Math.ceil(text.length * 0.3));\n\n const headChars = Math.ceil(cap / 2);\n const tailChars = Math.floor(cap / 2);\n const head = text.slice(0, headChars);\n const tail = tailChars > 0 ? text.slice(-tailChars) : \"\";\n const sampleChars = head.length + tail.length;\n const sampleTokens = countTokens(head) + countTokens(tail);\n const ratio = sampleChars > 0 ? sampleTokens / sampleChars : 0.3;\n return Math.max(1, Math.ceil(text.length * ratio));\n}\n\nconst BOS = \"<|begin▁of▁sentence|>\";\nconst EOS = \"<|end▁of▁sentence|>\";\nconst USER_SP = \"<|User|>\";\nconst ASSISTANT_SP = \"<|Assistant|>\";\nconst THINK_START = \"<think>\";\nconst THINK_END = \"</think>\";\n\nconst DSML = \"|DSML|\";\nconst TC_BEGIN = `<${DSML}tool_calls>`;\nconst TC_END = `</${DSML}tool_calls>`;\nconst INVOKE_BEGIN = `<${DSML}invoke name=\"`;\nconst INVOKE_END = `</${DSML}invoke>`;\nconst PARAM_TEMPLATE = `<${DSML}parameter name=\"{key}\" string=\"{is_str}\">{value}</${DSML}parameter>`;\nconst TOOL_RESULT_TEMPLATE = \"<tool_result>{content}</tool_result>\";\n\n/** Keyed by `ImmutablePrefix._toolSpecs` identity — stable for the prefix's lifetime. */\nconst toolsTemplateCache = new WeakMap<ReadonlyArray<unknown>, string>();\n\nfunction renderTools(tools: ReadonlyArray<unknown>): string {\n const cached = toolsTemplateCache.get(tools);\n if (cached !== undefined) return cached;\n\n const schemas = tools\n .map((t) => {\n const fn = (t as { function?: unknown }).function ?? t;\n return JSON.stringify(fn);\n })\n .join(\"\\n\");\n const rendered = `## Tools\\n\\nYou have access to a set of tools to help answer the user's question. You can invoke tools by writing a \\\"<${DSML}tool_calls>\" block like the following:\\n\\n<${DSML}tool_calls>\\n<${DSML}invoke name=\"$TOOL_NAME\">\\n<${DSML}parameter name=\"$PARAMETER_NAME\" string=\"true|false\">$PARAMETER_VALUE</${DSML}parameter>\\n...\\n</${DSML}invoke>\\n<${DSML}invoke name=\"$TOOL_NAME2\">\\n...\\n</${DSML}invoke>\\n</${DSML}tool_calls>\\n\\nString parameters should be specified as is and set \\`string=\"true\"\\`. For all other types (numbers, booleans, arrays, objects), pass the value in JSON format and set \\`string=\"false\"\\`.\\n\\nIf thinking_mode is enabled (triggered by ${THINK_START}), you MUST output your complete reasoning inside ${THINK_START}...${THINK_END} BEFORE any tool calls or final response.\\n\\nOtherwise, output directly after ${THINK_END} with tool calls or final response.\\n\\n### Available Tool Schemas\\n\\n${schemas}\\n\\nYou MUST strictly follow the above defined tool name and parameter schemas to invoke tool calls.`;\n\n toolsTemplateCache.set(tools, rendered);\n return rendered;\n}\n\ninterface ToolCall {\n function?: { name?: string; arguments?: string };\n [k: string]: unknown;\n}\n\ninterface V4Message {\n role?: string;\n content?: string | null;\n tool_calls?: ToolCall[];\n tool_call_id?: string;\n reasoning_content?: string | null;\n _toolBlocks?: string[];\n _textParts?: string[];\n}\n\nfunction encodeArgumentsToDsml(argsJson: string): string {\n let args: Record<string, unknown>;\n try {\n args = JSON.parse(argsJson) as Record<string, unknown>;\n } catch {\n args = { arguments: argsJson };\n }\n return Object.entries(args)\n .map(([k, v]) =>\n PARAM_TEMPLATE.replace(\"{key}\", k)\n .replace(\"{is_str}\", typeof v === \"string\" ? \"true\" : \"false\")\n .replace(\"{value}\", typeof v === \"string\" ? v : JSON.stringify(v)),\n )\n .join(\"\\n\");\n}\n\nfunction renderToolCallsDsml(toolCalls: ToolCall[]): string {\n const invokes = toolCalls\n .map((tc) => {\n const name = tc.function?.name ?? \"\";\n const argsJson = tc.function?.arguments ?? \"{}\";\n return `${INVOKE_BEGIN + name}\">\\n${encodeArgumentsToDsml(argsJson)}\\n${INVOKE_END}`;\n })\n .join(\"\\n\");\n return `\\n\\n${TC_BEGIN}\\n${invokes}\\n${TC_END}`;\n}\n\nfunction mergeToolMessages(messages: V4Message[]): V4Message[] {\n const merged: V4Message[] = [];\n for (const msg of messages) {\n const role = msg.role ?? \"user\";\n if (role === \"tool\") {\n const toolBlock = TOOL_RESULT_TEMPLATE.replace(\"{content}\", msg.content ?? \"\");\n const last = merged[merged.length - 1];\n if (\n last &&\n last.role === \"user\" &&\n Array.isArray(last._toolBlocks) &&\n Array.isArray(last._textParts)\n ) {\n last._toolBlocks.push(toolBlock);\n last.content = `${last._textParts.join(\"\\n\\n\")}\\n\\n${last._toolBlocks.join(\"\\n\")}`.replace(\n /^\\n\\n/,\n \"\",\n );\n } else {\n merged.push({\n role: \"user\",\n content: toolBlock,\n _textParts: [],\n _toolBlocks: [toolBlock],\n });\n }\n } else if (role === \"user\") {\n const text = msg.content ?? \"\";\n const last = merged[merged.length - 1];\n if (\n last &&\n last.role === \"user\" &&\n Array.isArray(last._toolBlocks) &&\n Array.isArray(last._textParts)\n ) {\n last._textParts.push(text);\n last.content =\n `${last._textParts.join(\"\\n\\n\")}\\n\\n${last._toolBlocks.join(\"\\n\\n\")}`.replace(\n /^\\n\\n/,\n \"\",\n );\n } else {\n merged.push({\n ...msg,\n role: \"user\",\n content: text,\n _textParts: [text],\n _toolBlocks: [],\n });\n }\n } else {\n merged.push({ ...msg });\n }\n }\n for (const m of merged) {\n m._textParts = undefined;\n m._toolBlocks = undefined;\n }\n return merged;\n}\n\n/** Drop `reasoning_content` from assistant messages before the last user/developer message. Matches Python `_drop_thinking_messages`. */\nfunction dropThinkingMessages(messages: V4Message[]): V4Message[] {\n let lastUserIdx = -1;\n for (let i = messages.length - 1; i >= 0; i--) {\n const role = messages[i]!.role;\n if (role === \"user\" || role === \"developer\") {\n lastUserIdx = i;\n break;\n }\n }\n if (lastUserIdx < 0) return messages;\n\n // Match Python `_drop_thinking_messages`:\n // - developer messages before lastUserIdx are dropped entirely\n // - assistant messages before lastUserIdx keep content & tool_calls\n // but have reasoning_content stripped\n const result: V4Message[] = [];\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i]!;\n if (i < lastUserIdx && msg.role === \"developer\") continue;\n if (i < lastUserIdx && msg.role === \"assistant\") {\n result.push({ ...msg, reasoning_content: null });\n } else {\n result.push(msg);\n }\n }\n return result;\n}\n\n/** Apply DeepSeek V4 chat template. Matches `encoding_dsv4.py`: tool results merged into user messages, assistant tool_calls in DSML, generation suffix appended. */\nexport function formatDeepSeekPrompt(\n messages: Array<{\n role?: string;\n content?: string | null;\n tool_calls?: unknown;\n tool_call_id?: string;\n reasoning_content?: string | null;\n }>,\n drop_thinking = false,\n): string {\n if (messages.length === 0) return ASSISTANT_SP + THINK_END;\n\n let msgs = messages as V4Message[];\n if (drop_thinking) {\n msgs = dropThinkingMessages(msgs);\n }\n const merged = mergeToolMessages(msgs);\n\n let prompt = BOS;\n\n for (let i = 0; i < merged.length; i++) {\n const msg = merged[i]!;\n const role = msg.role ?? \"user\";\n const nextRole = i + 1 < merged.length ? (merged[i + 1]!.role ?? \"user\") : null;\n\n if (role === \"system\") {\n prompt += msg.content ?? \"\";\n } else if (role === \"user\") {\n prompt += USER_SP + (msg.content ?? \"\");\n if (nextRole === \"assistant\" || nextRole === null) {\n prompt += ASSISTANT_SP + THINK_END;\n }\n } else if (role === \"assistant\") {\n if (msg.reasoning_content) {\n prompt += THINK_START + msg.reasoning_content + THINK_END;\n }\n if (msg.content) prompt += msg.content;\n const tcs = msg.tool_calls;\n if (Array.isArray(tcs) && tcs.length > 0) {\n prompt += renderToolCallsDsml(tcs);\n }\n prompt += EOS;\n }\n }\n\n return prompt;\n}\n\n/** Token-count the FULL conversation as the API would see it: wraps messages in V4 chat template, then encodes once. */\nexport function estimateConversationTokens(\n messages: Array<{\n role?: string;\n content?: string | null;\n tool_calls?: unknown;\n tool_call_id?: string;\n reasoning_content?: string | null;\n }>,\n drop_thinking = false,\n): number {\n if (messages.length === 0) return 0;\n return countTokensBounded(formatDeepSeekPrompt(messages, drop_thinking));\n}\n\n/** Total request tokens (messages + tool specs) as the API counts them. Tool specs rendered via V4 TOOLS_TEMPLATE and added to message token count. */\nexport function estimateRequestTokens(\n messages: Array<{\n role?: string;\n content?: string | null;\n tool_calls?: unknown;\n tool_call_id?: string;\n reasoning_content?: string | null;\n }>,\n toolSpecs?: ReadonlyArray<unknown> | null,\n drop_thinking = false,\n): number {\n let total = estimateConversationTokens(messages, drop_thinking);\n if (toolSpecs && toolSpecs.length > 0) {\n total += countTokensBounded(renderTools(toolSpecs));\n }\n return total;\n}\n\n/** Exposed for tests — resets the lazy-load singleton. */\nexport function _resetForTests(): void {\n cached = null;\n}\n"],"mappings":"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","names":["process","cached"]}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire as __cr } from 'node:module'; if (typeof globalThis.require === 'undefined') { globalThis.require = __cr(import.meta.url); }
|
|
3
|
+
import {
|
|
4
|
+
VERSION,
|
|
5
|
+
compareVersions,
|
|
6
|
+
detectInstallSource,
|
|
7
|
+
detectNpmInstallPrefix,
|
|
8
|
+
getLatestVersion
|
|
9
|
+
} from "./chunk-LN3B5PMX.js";
|
|
10
|
+
|
|
11
|
+
// src/cli/commands/update.ts
|
|
12
|
+
import { spawn } from "child_process";
|
|
13
|
+
var MANUAL_UPDATE_COMMANDS = [
|
|
14
|
+
"npm install -g @carboncode/cli@latest",
|
|
15
|
+
"bun add -g @carboncode/cli",
|
|
16
|
+
"pnpm add -g @carboncode/cli@latest",
|
|
17
|
+
"yarn global add @carboncode/cli@latest"
|
|
18
|
+
];
|
|
19
|
+
function planUpdate(input) {
|
|
20
|
+
const diff = compareVersions(input.current, input.latest);
|
|
21
|
+
if (diff > 0) {
|
|
22
|
+
return {
|
|
23
|
+
action: "newer-local",
|
|
24
|
+
message: `current (${input.current}) is newer than the published ${input.latest} \u2014 nothing to do.`
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (diff === 0) {
|
|
28
|
+
return { action: "up-to-date", message: `carboncode ${input.current} is up to date.` };
|
|
29
|
+
}
|
|
30
|
+
if (input.installSource === "npx") {
|
|
31
|
+
return {
|
|
32
|
+
action: "npx-hint",
|
|
33
|
+
message: [
|
|
34
|
+
`carboncode ${input.latest} is available.`,
|
|
35
|
+
"you're running via npx \u2014 the next `npx @carboncode/cli ...` launch will auto-fetch",
|
|
36
|
+
"the latest (npx caches packages for a short window). to force a refresh",
|
|
37
|
+
"sooner, clear the cache: `npm cache clean --force`."
|
|
38
|
+
].join("\n")
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (input.installSource === "unknown") {
|
|
42
|
+
return {
|
|
43
|
+
action: "manual-hint",
|
|
44
|
+
message: [
|
|
45
|
+
`carboncode ${input.latest} is available, but the install source could not be determined automatically.`,
|
|
46
|
+
"run one of these manually based on how you installed carboncode:",
|
|
47
|
+
...MANUAL_UPDATE_COMMANDS.map((c) => ` ${c}`)
|
|
48
|
+
].join("\n")
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const command = buildUpdateCommand(input.installSource, input.npmPrefix ?? null);
|
|
52
|
+
return {
|
|
53
|
+
action: "run-install",
|
|
54
|
+
message: `upgrading carboncode ${input.current} \u2192 ${input.latest} (via ${input.installSource})`,
|
|
55
|
+
command
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function buildUpdateCommand(source, npmPrefix) {
|
|
59
|
+
switch (source) {
|
|
60
|
+
case "npm":
|
|
61
|
+
return npmPrefix ? ["npm", "--prefix", npmPrefix, "install", "-g", "@carboncode/cli@latest"] : ["npm", "install", "-g", "@carboncode/cli@latest"];
|
|
62
|
+
case "bun":
|
|
63
|
+
return ["bun", "add", "-g", "@carboncode/cli"];
|
|
64
|
+
case "pnpm":
|
|
65
|
+
return ["pnpm", "add", "-g", "@carboncode/cli@latest"];
|
|
66
|
+
case "yarn":
|
|
67
|
+
return ["yarn", "global", "add", "@carboncode/cli@latest"];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function defaultSpawn(argv) {
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
const child = spawn(argv[0], argv.slice(1), {
|
|
73
|
+
stdio: "inherit",
|
|
74
|
+
shell: process.platform === "win32"
|
|
75
|
+
});
|
|
76
|
+
child.once("error", reject);
|
|
77
|
+
child.once("exit", (code) => resolve(code ?? 1));
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
async function updateCommand(opts = {}) {
|
|
81
|
+
const write = opts.write ?? ((m) => process.stdout.write(m));
|
|
82
|
+
const exit = opts.exit ?? ((c) => process.exit(c));
|
|
83
|
+
const fetchLatest = opts.fetchLatest ?? (() => getLatestVersion({ force: true }));
|
|
84
|
+
const detectSource = opts.detectSource ?? (() => detectInstallSource());
|
|
85
|
+
const detectPrefix = opts.detectPrefix ?? (() => detectNpmInstallPrefix());
|
|
86
|
+
const doSpawn = opts.spawnInstall ?? defaultSpawn;
|
|
87
|
+
write(`current: carboncode ${VERSION}
|
|
88
|
+
`);
|
|
89
|
+
const latest = await fetchLatest();
|
|
90
|
+
if (!latest) {
|
|
91
|
+
write("could not reach registry.npmjs.org \u2014 check your network.\n");
|
|
92
|
+
exit(1);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
write(`latest: carboncode ${latest}
|
|
96
|
+
`);
|
|
97
|
+
const installSource = detectSource();
|
|
98
|
+
const npmPrefix = installSource === "npm" ? detectPrefix() : null;
|
|
99
|
+
const plan = planUpdate({ current: VERSION, latest, installSource, npmPrefix });
|
|
100
|
+
write(`
|
|
101
|
+
${plan.message}
|
|
102
|
+
`);
|
|
103
|
+
if (plan.action === "manual-hint") {
|
|
104
|
+
exit(1);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (plan.action !== "run-install" || !plan.command) return;
|
|
108
|
+
if (opts.dryRun) {
|
|
109
|
+
write(`(dry run) would run: ${plan.command.join(" ")}
|
|
110
|
+
`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
write(`
|
|
114
|
+
running: ${plan.command.join(" ")}
|
|
115
|
+
`);
|
|
116
|
+
const code = await doSpawn(plan.command);
|
|
117
|
+
if (code !== 0) {
|
|
118
|
+
write(`
|
|
119
|
+
${plan.command[0]} exited with code ${code}. upgrade did not complete.
|
|
120
|
+
`);
|
|
121
|
+
exit(code);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export {
|
|
126
|
+
MANUAL_UPDATE_COMMANDS,
|
|
127
|
+
planUpdate,
|
|
128
|
+
updateCommand
|
|
129
|
+
};
|
|
130
|
+
//# sourceMappingURL=chunk-7EO27TB3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/cli/commands/update.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport {\n type InstallSource,\n VERSION,\n compareVersions,\n detectInstallSource,\n detectNpmInstallPrefix,\n getLatestVersion,\n} from \"../../version.js\";\n\nexport type UpdateAction =\n | \"up-to-date\"\n | \"newer-local\"\n | \"npx-hint\"\n | \"manual-hint\"\n | \"run-install\";\n\nexport interface UpdatePlan {\n action: UpdateAction;\n /** Human-readable summary; the CLI prints this verbatim. */\n message: string;\n command?: string[];\n}\n\nexport interface PlanUpdateInput {\n current: string;\n latest: string;\n installSource: InstallSource;\n /** Pin npm to this prefix so nvm/fnm can't redirect the install. */\n npmPrefix?: string | null;\n}\n\nexport const MANUAL_UPDATE_COMMANDS: readonly string[] = [\n \"npm install -g @carboncode/cli@latest\",\n \"bun add -g @carboncode/cli\",\n \"pnpm add -g @carboncode/cli@latest\",\n \"yarn global add @carboncode/cli@latest\",\n];\n\n/** Pure decision — split out so tests don't need to spawn child processes or hit the network. */\nexport function planUpdate(input: PlanUpdateInput): UpdatePlan {\n const diff = compareVersions(input.current, input.latest);\n if (diff > 0) {\n return {\n action: \"newer-local\",\n message: `current (${input.current}) is newer than the published ${input.latest} — nothing to do.`,\n };\n }\n if (diff === 0) {\n return { action: \"up-to-date\", message: `carboncode ${input.current} is up to date.` };\n }\n if (input.installSource === \"npx\") {\n return {\n action: \"npx-hint\",\n message: [\n `carboncode ${input.latest} is available.`,\n \"you're running via npx — the next `npx @carboncode/cli ...` launch will auto-fetch\",\n \"the latest (npx caches packages for a short window). to force a refresh\",\n \"sooner, clear the cache: `npm cache clean --force`.\",\n ].join(\"\\n\"),\n };\n }\n if (input.installSource === \"unknown\") {\n return {\n action: \"manual-hint\",\n message: [\n `carboncode ${input.latest} is available, but the install source could not be determined automatically.`,\n \"run one of these manually based on how you installed carboncode:\",\n ...MANUAL_UPDATE_COMMANDS.map((c) => ` ${c}`),\n ].join(\"\\n\"),\n };\n }\n const command = buildUpdateCommand(input.installSource, input.npmPrefix ?? null);\n return {\n action: \"run-install\",\n message: `upgrading carboncode ${input.current} → ${input.latest} (via ${input.installSource})`,\n command,\n };\n}\n\nfunction buildUpdateCommand(\n source: Exclude<InstallSource, \"npx\" | \"unknown\">,\n npmPrefix: string | null,\n): string[] {\n switch (source) {\n case \"npm\":\n return npmPrefix\n ? [\"npm\", \"--prefix\", npmPrefix, \"install\", \"-g\", \"@carboncode/cli@latest\"]\n : [\"npm\", \"install\", \"-g\", \"@carboncode/cli@latest\"];\n case \"bun\":\n return [\"bun\", \"add\", \"-g\", \"@carboncode/cli\"];\n case \"pnpm\":\n return [\"pnpm\", \"add\", \"-g\", \"@carboncode/cli@latest\"];\n case \"yarn\":\n return [\"yarn\", \"global\", \"add\", \"@carboncode/cli@latest\"];\n }\n}\n\nexport interface UpdateCommandOptions {\n /** Skip spawning the package manager; print the decision only. */\n dryRun?: boolean;\n /** Test seam: override the registry lookup. Returns null = offline. */\n fetchLatest?: () => Promise<string | null>;\n /** Test seam: override the install-source detector. */\n detectSource?: () => InstallSource;\n /** Test seam: override the npm prefix detector. */\n detectPrefix?: () => string | null;\n /** Test seam: override the spawner. Must return exit code. */\n spawnInstall?: (argv: string[]) => Promise<number>;\n /** Test seam: stdout writer. */\n write?: (msg: string) => void;\n /** Test seam: process exit — tests don't want to tear down vitest. */\n exit?: (code: number) => void;\n}\n\nfunction defaultSpawn(argv: string[]): Promise<number> {\n return new Promise((resolve, reject) => {\n // `shell: true` on Windows is what lets `npm` resolve to `npm.cmd`\n // without routing through our `prepareSpawn` helper. The args here\n // are literal strings under our control — no user input flows in,\n // so injection is not a concern. Avoiding `prepareSpawn` keeps\n // this command free of a dep on the shell tools module.\n const child = spawn(argv[0]!, argv.slice(1), {\n stdio: \"inherit\",\n shell: process.platform === \"win32\",\n });\n child.once(\"error\", reject);\n child.once(\"exit\", (code) => resolve(code ?? 1));\n });\n}\n\nexport async function updateCommand(opts: UpdateCommandOptions = {}): Promise<void> {\n const write = opts.write ?? ((m: string) => process.stdout.write(m));\n const exit = opts.exit ?? ((c: number) => process.exit(c));\n const fetchLatest = opts.fetchLatest ?? (() => getLatestVersion({ force: true }));\n const detectSource = opts.detectSource ?? (() => detectInstallSource());\n const detectPrefix = opts.detectPrefix ?? (() => detectNpmInstallPrefix());\n const doSpawn = opts.spawnInstall ?? defaultSpawn;\n\n write(`current: carboncode ${VERSION}\\n`);\n const latest = await fetchLatest();\n if (!latest) {\n write(\"could not reach registry.npmjs.org — check your network.\\n\");\n exit(1);\n return;\n }\n write(`latest: carboncode ${latest}\\n`);\n\n const installSource = detectSource();\n const npmPrefix = installSource === \"npm\" ? detectPrefix() : null;\n const plan = planUpdate({ current: VERSION, latest, installSource, npmPrefix });\n write(`\\n${plan.message}\\n`);\n\n if (plan.action === \"manual-hint\") {\n exit(1);\n return;\n }\n if (plan.action !== \"run-install\" || !plan.command) return;\n if (opts.dryRun) {\n write(`(dry run) would run: ${plan.command.join(\" \")}\\n`);\n return;\n }\n write(`\\nrunning: ${plan.command.join(\" \")}\\n`);\n const code = await doSpawn(plan.command);\n if (code !== 0) {\n write(`\\n${plan.command[0]} exited with code ${code}. upgrade did not complete.\\n`);\n exit(code);\n }\n}\n"],"mappings":"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","names":[]}
|