reasonix 0.0.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/LICENSE +21 -0
- package/README.md +114 -0
- package/dist/chunk-XILYSYPT.js +261 -0
- package/dist/chunk-XILYSYPT.js.map +1 -0
- package/dist/cli/chunk-OSNTDDD6.js +262 -0
- package/dist/cli/chunk-OSNTDDD6.js.map +1 -0
- package/dist/cli/client-OWZXRMOE.js +10 -0
- package/dist/cli/client-OWZXRMOE.js.map +1 -0
- package/dist/cli/index.js +1089 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/client-4JTJKRDV.js +9 -0
- package/dist/client-4JTJKRDV.js.map +1 -0
- package/dist/index.d.ts +396 -0
- package/dist/index.js +716 -0
- package/dist/index.js.map +1 -0
- package/package.json +75 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,716 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DeepSeekClient,
|
|
3
|
+
Usage,
|
|
4
|
+
fetchWithRetry
|
|
5
|
+
} from "./chunk-XILYSYPT.js";
|
|
6
|
+
|
|
7
|
+
// src/harvest.ts
|
|
8
|
+
function emptyPlanState() {
|
|
9
|
+
return { subgoals: [], hypotheses: [], uncertainties: [], rejectedPaths: [] };
|
|
10
|
+
}
|
|
11
|
+
function isPlanStateEmpty(s) {
|
|
12
|
+
return s.subgoals.length === 0 && s.hypotheses.length === 0 && s.uncertainties.length === 0 && s.rejectedPaths.length === 0;
|
|
13
|
+
}
|
|
14
|
+
async function harvest(_reasoningContent) {
|
|
15
|
+
return emptyPlanState();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/memory.ts
|
|
19
|
+
import { createHash } from "crypto";
|
|
20
|
+
var ImmutablePrefix = class {
|
|
21
|
+
system;
|
|
22
|
+
toolSpecs;
|
|
23
|
+
fewShots;
|
|
24
|
+
constructor(opts) {
|
|
25
|
+
this.system = opts.system;
|
|
26
|
+
this.toolSpecs = Object.freeze([...opts.toolSpecs ?? []]);
|
|
27
|
+
this.fewShots = Object.freeze([...opts.fewShots ?? []]);
|
|
28
|
+
}
|
|
29
|
+
toMessages() {
|
|
30
|
+
return [{ role: "system", content: this.system }, ...this.fewShots.map((m) => ({ ...m }))];
|
|
31
|
+
}
|
|
32
|
+
tools() {
|
|
33
|
+
return this.toolSpecs.map((t) => structuredClone(t));
|
|
34
|
+
}
|
|
35
|
+
get fingerprint() {
|
|
36
|
+
const blob = JSON.stringify({
|
|
37
|
+
system: this.system,
|
|
38
|
+
tools: this.toolSpecs,
|
|
39
|
+
shots: this.fewShots
|
|
40
|
+
});
|
|
41
|
+
return createHash("sha256").update(blob).digest("hex").slice(0, 16);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
var AppendOnlyLog = class {
|
|
45
|
+
_entries = [];
|
|
46
|
+
append(message) {
|
|
47
|
+
if (!message || typeof message !== "object" || !("role" in message)) {
|
|
48
|
+
throw new Error(`invalid log entry: ${JSON.stringify(message)}`);
|
|
49
|
+
}
|
|
50
|
+
this._entries.push(message);
|
|
51
|
+
}
|
|
52
|
+
extend(messages) {
|
|
53
|
+
for (const m of messages) this.append(m);
|
|
54
|
+
}
|
|
55
|
+
get entries() {
|
|
56
|
+
return this._entries;
|
|
57
|
+
}
|
|
58
|
+
toMessages() {
|
|
59
|
+
return this._entries.map((e) => ({ ...e }));
|
|
60
|
+
}
|
|
61
|
+
get length() {
|
|
62
|
+
return this._entries.length;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
var VolatileScratch = class {
|
|
66
|
+
reasoning = null;
|
|
67
|
+
planState = null;
|
|
68
|
+
notes = [];
|
|
69
|
+
reset() {
|
|
70
|
+
this.reasoning = null;
|
|
71
|
+
this.planState = null;
|
|
72
|
+
this.notes = [];
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// src/repair/scavenge.ts
|
|
77
|
+
function scavengeToolCalls(reasoningContent, opts) {
|
|
78
|
+
if (!reasoningContent) return { calls: [], notes: [] };
|
|
79
|
+
const max = opts.maxCalls ?? 4;
|
|
80
|
+
const notes = [];
|
|
81
|
+
const out = [];
|
|
82
|
+
for (const candidate of iterateJsonObjects(reasoningContent)) {
|
|
83
|
+
if (out.length >= max) break;
|
|
84
|
+
const call = coerceToToolCall(candidate, opts.allowedNames);
|
|
85
|
+
if (call) {
|
|
86
|
+
out.push(call);
|
|
87
|
+
notes.push(`scavenged call: ${call.function.name}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return { calls: out, notes };
|
|
91
|
+
}
|
|
92
|
+
function* iterateJsonObjects(text) {
|
|
93
|
+
for (let i = 0; i < text.length; i++) {
|
|
94
|
+
if (text[i] !== "{") continue;
|
|
95
|
+
let depth = 0;
|
|
96
|
+
let inString = false;
|
|
97
|
+
let escaped = false;
|
|
98
|
+
for (let j = i; j < text.length; j++) {
|
|
99
|
+
const c = text[j];
|
|
100
|
+
if (escaped) {
|
|
101
|
+
escaped = false;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (inString) {
|
|
105
|
+
if (c === "\\") {
|
|
106
|
+
escaped = true;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (c === '"') inString = false;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (c === '"') inString = true;
|
|
113
|
+
else if (c === "{") depth++;
|
|
114
|
+
else if (c === "}") {
|
|
115
|
+
depth--;
|
|
116
|
+
if (depth === 0) {
|
|
117
|
+
yield text.slice(i, j + 1);
|
|
118
|
+
i = j;
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function coerceToToolCall(candidateJson, allowedNames) {
|
|
126
|
+
let parsed;
|
|
127
|
+
try {
|
|
128
|
+
parsed = JSON.parse(candidateJson);
|
|
129
|
+
} catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
133
|
+
if (typeof parsed.name === "string" && allowedNames.has(parsed.name)) {
|
|
134
|
+
const args = parsed.arguments;
|
|
135
|
+
return {
|
|
136
|
+
function: {
|
|
137
|
+
name: parsed.name,
|
|
138
|
+
arguments: typeof args === "string" ? args : JSON.stringify(args ?? {})
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (parsed.type === "function" && parsed.function && typeof parsed.function.name === "string" && allowedNames.has(parsed.function.name)) {
|
|
143
|
+
const args = parsed.function.arguments;
|
|
144
|
+
return {
|
|
145
|
+
type: "function",
|
|
146
|
+
function: {
|
|
147
|
+
name: parsed.function.name,
|
|
148
|
+
arguments: typeof args === "string" ? args : JSON.stringify(args ?? {})
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
if (typeof parsed.tool_name === "string" && allowedNames.has(parsed.tool_name)) {
|
|
153
|
+
return {
|
|
154
|
+
function: {
|
|
155
|
+
name: parsed.tool_name,
|
|
156
|
+
arguments: JSON.stringify(parsed.tool_args ?? {})
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/repair/storm.ts
|
|
164
|
+
var StormBreaker = class {
|
|
165
|
+
windowSize;
|
|
166
|
+
threshold;
|
|
167
|
+
recent = [];
|
|
168
|
+
constructor(windowSize = 6, threshold = 3) {
|
|
169
|
+
this.windowSize = windowSize;
|
|
170
|
+
this.threshold = threshold;
|
|
171
|
+
}
|
|
172
|
+
inspect(call) {
|
|
173
|
+
const sig = signature(call);
|
|
174
|
+
if (!sig) return { suppress: false };
|
|
175
|
+
const count = this.recent.reduce(
|
|
176
|
+
(n, [name, args]) => name === sig[0] && args === sig[1] ? n + 1 : n,
|
|
177
|
+
0
|
|
178
|
+
);
|
|
179
|
+
if (count >= this.threshold - 1) {
|
|
180
|
+
return {
|
|
181
|
+
suppress: true,
|
|
182
|
+
reason: `call-storm suppressed: ${sig[0]} called with identical args ${count + 1} times within window=${this.windowSize}`
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
this.recent.push(sig);
|
|
186
|
+
while (this.recent.length > this.windowSize) this.recent.shift();
|
|
187
|
+
return { suppress: false };
|
|
188
|
+
}
|
|
189
|
+
reset() {
|
|
190
|
+
this.recent.length = 0;
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
function signature(call) {
|
|
194
|
+
const name = call.function?.name;
|
|
195
|
+
if (!name) return null;
|
|
196
|
+
return [name, call.function?.arguments ?? ""];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/repair/truncation.ts
|
|
200
|
+
function repairTruncatedJson(input) {
|
|
201
|
+
const notes = [];
|
|
202
|
+
if (!input || !input.trim()) {
|
|
203
|
+
return { repaired: "{}", changed: input !== "{}", notes: ["empty input \u2192 {}"] };
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
JSON.parse(input);
|
|
207
|
+
return { repaired: input, changed: false, notes: [] };
|
|
208
|
+
} catch {
|
|
209
|
+
}
|
|
210
|
+
const stack = [];
|
|
211
|
+
let escaped = false;
|
|
212
|
+
let inString = false;
|
|
213
|
+
let lastSignificant = -1;
|
|
214
|
+
for (let i = 0; i < input.length; i++) {
|
|
215
|
+
const c = input[i];
|
|
216
|
+
if (!/\s/.test(c)) lastSignificant = i;
|
|
217
|
+
if (escaped) {
|
|
218
|
+
escaped = false;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (inString) {
|
|
222
|
+
if (c === "\\") {
|
|
223
|
+
escaped = true;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (c === '"') {
|
|
227
|
+
inString = false;
|
|
228
|
+
stack.pop();
|
|
229
|
+
}
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (c === '"') {
|
|
233
|
+
inString = true;
|
|
234
|
+
stack.push('"');
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (c === "{" || c === "[") stack.push(c);
|
|
238
|
+
else if (c === "}" || c === "]") stack.pop();
|
|
239
|
+
}
|
|
240
|
+
let s = input.slice(0, lastSignificant + 1);
|
|
241
|
+
if (/,$/.test(s)) {
|
|
242
|
+
s = s.replace(/,$/, "");
|
|
243
|
+
notes.push("trimmed trailing comma");
|
|
244
|
+
}
|
|
245
|
+
if (/"\s*:\s*$/.test(s)) {
|
|
246
|
+
s += " null";
|
|
247
|
+
notes.push("filled dangling key with null");
|
|
248
|
+
}
|
|
249
|
+
if (inString) {
|
|
250
|
+
s += '"';
|
|
251
|
+
stack.pop();
|
|
252
|
+
notes.push("closed unterminated string");
|
|
253
|
+
}
|
|
254
|
+
while (stack.length > 0) {
|
|
255
|
+
const top = stack.pop();
|
|
256
|
+
if (top === "{") s += "}";
|
|
257
|
+
else if (top === "[") s += "]";
|
|
258
|
+
else if (top === '"') s += '"';
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
JSON.parse(s);
|
|
262
|
+
return { repaired: s, changed: true, notes };
|
|
263
|
+
} catch (err) {
|
|
264
|
+
notes.push(`fallback to {}: ${err.message}`);
|
|
265
|
+
return { repaired: "{}", changed: true, notes };
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/repair/flatten.ts
|
|
270
|
+
function analyzeSchema(schema) {
|
|
271
|
+
if (!schema) return { shouldFlatten: false, leafCount: 0, maxDepth: 0 };
|
|
272
|
+
let leafCount = 0;
|
|
273
|
+
let maxDepth = 0;
|
|
274
|
+
walk(schema, 0, (depth, isLeaf) => {
|
|
275
|
+
if (isLeaf) leafCount++;
|
|
276
|
+
if (depth > maxDepth) maxDepth = depth;
|
|
277
|
+
});
|
|
278
|
+
return {
|
|
279
|
+
shouldFlatten: leafCount > 10 || maxDepth > 2,
|
|
280
|
+
leafCount,
|
|
281
|
+
maxDepth
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function flattenSchema(schema) {
|
|
285
|
+
const flatProps = {};
|
|
286
|
+
const required = [];
|
|
287
|
+
collect("", schema, flatProps, required, true);
|
|
288
|
+
return {
|
|
289
|
+
type: "object",
|
|
290
|
+
properties: flatProps,
|
|
291
|
+
required
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
function nestArguments(flatArgs) {
|
|
295
|
+
const out = {};
|
|
296
|
+
for (const [key, value] of Object.entries(flatArgs)) {
|
|
297
|
+
setByPath(out, key.split("."), value);
|
|
298
|
+
}
|
|
299
|
+
return out;
|
|
300
|
+
}
|
|
301
|
+
function walk(schema, depth, visit) {
|
|
302
|
+
if (schema.type === "object" && schema.properties) {
|
|
303
|
+
for (const child of Object.values(schema.properties)) {
|
|
304
|
+
walk(child, depth + 1, visit);
|
|
305
|
+
}
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (schema.type === "array" && schema.items) {
|
|
309
|
+
walk(schema.items, depth + 1, visit);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
visit(depth, true);
|
|
313
|
+
}
|
|
314
|
+
function collect(prefix, schema, out, required, isRootRequired) {
|
|
315
|
+
if (schema.type === "object" && schema.properties) {
|
|
316
|
+
const requiredSet = new Set(schema.required ?? []);
|
|
317
|
+
for (const [key, child] of Object.entries(schema.properties)) {
|
|
318
|
+
const nextPrefix = prefix ? `${prefix}.${key}` : key;
|
|
319
|
+
const childRequired = isRootRequired && requiredSet.has(key);
|
|
320
|
+
collect(nextPrefix, child, out, required, childRequired);
|
|
321
|
+
}
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
out[prefix] = schema;
|
|
325
|
+
if (isRootRequired) required.push(prefix);
|
|
326
|
+
}
|
|
327
|
+
function setByPath(target, path, value) {
|
|
328
|
+
let cur = target;
|
|
329
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
330
|
+
const key = path[i];
|
|
331
|
+
if (typeof cur[key] !== "object" || cur[key] === null) cur[key] = {};
|
|
332
|
+
cur = cur[key];
|
|
333
|
+
}
|
|
334
|
+
cur[path[path.length - 1]] = value;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// src/repair/index.ts
|
|
338
|
+
var ToolCallRepair = class {
|
|
339
|
+
storm;
|
|
340
|
+
opts;
|
|
341
|
+
constructor(opts) {
|
|
342
|
+
this.opts = opts;
|
|
343
|
+
this.storm = new StormBreaker(opts.stormWindow ?? 6, opts.stormThreshold ?? 3);
|
|
344
|
+
}
|
|
345
|
+
process(declaredCalls, reasoningContent) {
|
|
346
|
+
const report = {
|
|
347
|
+
scavenged: 0,
|
|
348
|
+
truncationsFixed: 0,
|
|
349
|
+
stormsBroken: 0,
|
|
350
|
+
notes: []
|
|
351
|
+
};
|
|
352
|
+
const scavenged = scavengeToolCalls(reasoningContent, {
|
|
353
|
+
allowedNames: this.opts.allowedToolNames,
|
|
354
|
+
maxCalls: this.opts.maxScavenge ?? 4
|
|
355
|
+
});
|
|
356
|
+
const seenSignatures = new Set(declaredCalls.map(signature2));
|
|
357
|
+
const merged = [...declaredCalls];
|
|
358
|
+
for (const sc of scavenged.calls) {
|
|
359
|
+
if (!seenSignatures.has(signature2(sc))) {
|
|
360
|
+
merged.push(sc);
|
|
361
|
+
report.scavenged++;
|
|
362
|
+
seenSignatures.add(signature2(sc));
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
report.notes.push(...scavenged.notes);
|
|
366
|
+
for (const call of merged) {
|
|
367
|
+
const args = call.function?.arguments ?? "";
|
|
368
|
+
const r = repairTruncatedJson(args);
|
|
369
|
+
if (r.changed) {
|
|
370
|
+
call.function.arguments = r.repaired;
|
|
371
|
+
report.truncationsFixed++;
|
|
372
|
+
report.notes.push(...r.notes.map((n) => `[${call.function.name}] ${n}`));
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
const filtered = [];
|
|
376
|
+
for (const call of merged) {
|
|
377
|
+
const verdict = this.storm.inspect(call);
|
|
378
|
+
if (verdict.suppress) {
|
|
379
|
+
report.stormsBroken++;
|
|
380
|
+
if (verdict.reason) report.notes.push(verdict.reason);
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
filtered.push(call);
|
|
384
|
+
}
|
|
385
|
+
return { calls: filtered, report };
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
function signature2(call) {
|
|
389
|
+
return `${call.function?.name ?? ""}::${call.function?.arguments ?? ""}`;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// src/telemetry.ts
|
|
393
|
+
var DEEPSEEK_PRICING = {
|
|
394
|
+
"deepseek-chat": { inputCacheHit: 0.07, inputCacheMiss: 0.27, output: 1.1 },
|
|
395
|
+
"deepseek-reasoner": { inputCacheHit: 0.14, inputCacheMiss: 0.55, output: 2.19 }
|
|
396
|
+
};
|
|
397
|
+
var CLAUDE_SONNET_PRICING = { input: 3, output: 15 };
|
|
398
|
+
function costUsd(model, usage) {
|
|
399
|
+
const p = DEEPSEEK_PRICING[model];
|
|
400
|
+
if (!p) return 0;
|
|
401
|
+
return (usage.promptCacheHitTokens * p.inputCacheHit + usage.promptCacheMissTokens * p.inputCacheMiss + usage.completionTokens * p.output) / 1e6;
|
|
402
|
+
}
|
|
403
|
+
function claudeEquivalentCost(usage) {
|
|
404
|
+
return (usage.promptTokens * CLAUDE_SONNET_PRICING.input + usage.completionTokens * CLAUDE_SONNET_PRICING.output) / 1e6;
|
|
405
|
+
}
|
|
406
|
+
var SessionStats = class {
|
|
407
|
+
turns = [];
|
|
408
|
+
record(turn, model, usage) {
|
|
409
|
+
const cost = costUsd(model, usage);
|
|
410
|
+
const stats = {
|
|
411
|
+
turn,
|
|
412
|
+
model,
|
|
413
|
+
usage,
|
|
414
|
+
cost,
|
|
415
|
+
cacheHitRatio: usage.cacheHitRatio
|
|
416
|
+
};
|
|
417
|
+
this.turns.push(stats);
|
|
418
|
+
return stats;
|
|
419
|
+
}
|
|
420
|
+
get totalCost() {
|
|
421
|
+
return this.turns.reduce((sum, t) => sum + t.cost, 0);
|
|
422
|
+
}
|
|
423
|
+
get totalClaudeEquivalent() {
|
|
424
|
+
return this.turns.reduce((sum, t) => sum + claudeEquivalentCost(t.usage), 0);
|
|
425
|
+
}
|
|
426
|
+
get savingsVsClaude() {
|
|
427
|
+
const c = this.totalClaudeEquivalent;
|
|
428
|
+
return c > 0 ? 1 - this.totalCost / c : 0;
|
|
429
|
+
}
|
|
430
|
+
get aggregateCacheHitRatio() {
|
|
431
|
+
let hit = 0;
|
|
432
|
+
let miss = 0;
|
|
433
|
+
for (const t of this.turns) {
|
|
434
|
+
hit += t.usage.promptCacheHitTokens;
|
|
435
|
+
miss += t.usage.promptCacheMissTokens;
|
|
436
|
+
}
|
|
437
|
+
const denom = hit + miss;
|
|
438
|
+
return denom > 0 ? hit / denom : 0;
|
|
439
|
+
}
|
|
440
|
+
summary() {
|
|
441
|
+
return {
|
|
442
|
+
turns: this.turns.length,
|
|
443
|
+
totalCostUsd: round(this.totalCost, 6),
|
|
444
|
+
claudeEquivalentUsd: round(this.totalClaudeEquivalent, 6),
|
|
445
|
+
savingsVsClaudePct: round(this.savingsVsClaude * 100, 2),
|
|
446
|
+
cacheHitRatio: round(this.aggregateCacheHitRatio, 4)
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
function round(n, digits) {
|
|
451
|
+
const f = 10 ** digits;
|
|
452
|
+
return Math.round(n * f) / f;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// src/tools.ts
|
|
456
|
+
var ToolRegistry = class {
|
|
457
|
+
_tools = /* @__PURE__ */ new Map();
|
|
458
|
+
register(def) {
|
|
459
|
+
if (!def.name) throw new Error("tool requires a name");
|
|
460
|
+
this._tools.set(def.name, def);
|
|
461
|
+
return this;
|
|
462
|
+
}
|
|
463
|
+
has(name) {
|
|
464
|
+
return this._tools.has(name);
|
|
465
|
+
}
|
|
466
|
+
get(name) {
|
|
467
|
+
return this._tools.get(name);
|
|
468
|
+
}
|
|
469
|
+
get size() {
|
|
470
|
+
return this._tools.size;
|
|
471
|
+
}
|
|
472
|
+
specs() {
|
|
473
|
+
return [...this._tools.values()].map((t) => ({
|
|
474
|
+
type: "function",
|
|
475
|
+
function: {
|
|
476
|
+
name: t.name,
|
|
477
|
+
description: t.description ?? "",
|
|
478
|
+
parameters: t.parameters ?? { type: "object", properties: {} }
|
|
479
|
+
}
|
|
480
|
+
}));
|
|
481
|
+
}
|
|
482
|
+
async dispatch(name, argumentsRaw) {
|
|
483
|
+
const tool = this._tools.get(name);
|
|
484
|
+
if (!tool) {
|
|
485
|
+
return JSON.stringify({ error: `unknown tool: ${name}` });
|
|
486
|
+
}
|
|
487
|
+
let args;
|
|
488
|
+
try {
|
|
489
|
+
args = typeof argumentsRaw === "string" ? argumentsRaw.trim() ? JSON.parse(argumentsRaw) : {} : argumentsRaw ?? {};
|
|
490
|
+
} catch (err) {
|
|
491
|
+
return JSON.stringify({
|
|
492
|
+
error: `invalid tool arguments JSON: ${err.message}`
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
try {
|
|
496
|
+
const result = await tool.fn(args);
|
|
497
|
+
return typeof result === "string" ? result : JSON.stringify(result);
|
|
498
|
+
} catch (err) {
|
|
499
|
+
return JSON.stringify({
|
|
500
|
+
error: `${err.name}: ${err.message}`
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
// src/loop.ts
|
|
507
|
+
var CacheFirstLoop = class {
|
|
508
|
+
client;
|
|
509
|
+
prefix;
|
|
510
|
+
tools;
|
|
511
|
+
model;
|
|
512
|
+
maxToolIters;
|
|
513
|
+
stream;
|
|
514
|
+
log = new AppendOnlyLog();
|
|
515
|
+
scratch = new VolatileScratch();
|
|
516
|
+
stats = new SessionStats();
|
|
517
|
+
repair;
|
|
518
|
+
_turn = 0;
|
|
519
|
+
constructor(opts) {
|
|
520
|
+
this.client = opts.client;
|
|
521
|
+
this.prefix = opts.prefix;
|
|
522
|
+
this.tools = opts.tools ?? new ToolRegistry();
|
|
523
|
+
this.model = opts.model ?? "deepseek-chat";
|
|
524
|
+
this.maxToolIters = opts.maxToolIters ?? 8;
|
|
525
|
+
this.stream = opts.stream ?? true;
|
|
526
|
+
const allowedNames = /* @__PURE__ */ new Set([...this.prefix.toolSpecs.map((s) => s.function.name)]);
|
|
527
|
+
this.repair = new ToolCallRepair({ allowedToolNames: allowedNames });
|
|
528
|
+
}
|
|
529
|
+
buildMessages(pendingUser) {
|
|
530
|
+
const msgs = [...this.prefix.toMessages(), ...this.log.toMessages()];
|
|
531
|
+
if (pendingUser !== null) msgs.push({ role: "user", content: pendingUser });
|
|
532
|
+
return msgs;
|
|
533
|
+
}
|
|
534
|
+
async *step(userInput) {
|
|
535
|
+
this._turn++;
|
|
536
|
+
this.scratch.reset();
|
|
537
|
+
let pendingUser = userInput;
|
|
538
|
+
const toolSpecs = this.prefix.tools();
|
|
539
|
+
for (let iter = 0; iter < this.maxToolIters; iter++) {
|
|
540
|
+
const messages = this.buildMessages(pendingUser);
|
|
541
|
+
let assistantContent = "";
|
|
542
|
+
let reasoningContent = "";
|
|
543
|
+
let toolCalls = [];
|
|
544
|
+
let usage = null;
|
|
545
|
+
try {
|
|
546
|
+
if (this.stream) {
|
|
547
|
+
const callBuf = /* @__PURE__ */ new Map();
|
|
548
|
+
for await (const chunk of this.client.stream({
|
|
549
|
+
model: this.model,
|
|
550
|
+
messages,
|
|
551
|
+
tools: toolSpecs.length ? toolSpecs : void 0
|
|
552
|
+
})) {
|
|
553
|
+
if (chunk.contentDelta) {
|
|
554
|
+
assistantContent += chunk.contentDelta;
|
|
555
|
+
yield {
|
|
556
|
+
turn: this._turn,
|
|
557
|
+
role: "assistant_delta",
|
|
558
|
+
content: chunk.contentDelta
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
if (chunk.reasoningDelta) {
|
|
562
|
+
reasoningContent += chunk.reasoningDelta;
|
|
563
|
+
yield {
|
|
564
|
+
turn: this._turn,
|
|
565
|
+
role: "assistant_delta",
|
|
566
|
+
content: "",
|
|
567
|
+
reasoningDelta: chunk.reasoningDelta
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
if (chunk.toolCallDelta) {
|
|
571
|
+
const d = chunk.toolCallDelta;
|
|
572
|
+
const cur = callBuf.get(d.index) ?? {
|
|
573
|
+
id: d.id,
|
|
574
|
+
type: "function",
|
|
575
|
+
function: { name: "", arguments: "" }
|
|
576
|
+
};
|
|
577
|
+
if (d.id) cur.id = d.id;
|
|
578
|
+
if (d.name) cur.function.name = (cur.function.name ?? "") + d.name;
|
|
579
|
+
if (d.argumentsDelta)
|
|
580
|
+
cur.function.arguments = (cur.function.arguments ?? "") + d.argumentsDelta;
|
|
581
|
+
callBuf.set(d.index, cur);
|
|
582
|
+
}
|
|
583
|
+
if (chunk.usage) usage = chunk.usage;
|
|
584
|
+
}
|
|
585
|
+
toolCalls = [...callBuf.values()];
|
|
586
|
+
} else {
|
|
587
|
+
const resp = await this.client.chat({
|
|
588
|
+
model: this.model,
|
|
589
|
+
messages,
|
|
590
|
+
tools: toolSpecs.length ? toolSpecs : void 0
|
|
591
|
+
});
|
|
592
|
+
assistantContent = resp.content;
|
|
593
|
+
reasoningContent = resp.reasoningContent ?? "";
|
|
594
|
+
toolCalls = resp.toolCalls;
|
|
595
|
+
usage = resp.usage;
|
|
596
|
+
}
|
|
597
|
+
} catch (err) {
|
|
598
|
+
yield {
|
|
599
|
+
turn: this._turn,
|
|
600
|
+
role: "error",
|
|
601
|
+
content: "",
|
|
602
|
+
error: err.message
|
|
603
|
+
};
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
const turnStats = this.stats.record(
|
|
607
|
+
this._turn,
|
|
608
|
+
this.model,
|
|
609
|
+
usage ?? new (await import("./client-4JTJKRDV.js")).Usage()
|
|
610
|
+
);
|
|
611
|
+
if (pendingUser !== null) {
|
|
612
|
+
this.log.append({ role: "user", content: pendingUser });
|
|
613
|
+
pendingUser = null;
|
|
614
|
+
}
|
|
615
|
+
this.scratch.reasoning = reasoningContent || null;
|
|
616
|
+
const planState = await harvest(reasoningContent || null);
|
|
617
|
+
const { calls: repairedCalls, report } = this.repair.process(
|
|
618
|
+
toolCalls,
|
|
619
|
+
reasoningContent || null
|
|
620
|
+
);
|
|
621
|
+
this.log.append(this.assistantMessage(assistantContent, repairedCalls));
|
|
622
|
+
yield {
|
|
623
|
+
turn: this._turn,
|
|
624
|
+
role: "assistant_final",
|
|
625
|
+
content: assistantContent,
|
|
626
|
+
stats: turnStats,
|
|
627
|
+
planState,
|
|
628
|
+
repair: report
|
|
629
|
+
};
|
|
630
|
+
if (repairedCalls.length === 0) {
|
|
631
|
+
yield { turn: this._turn, role: "done", content: assistantContent };
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
for (const call of repairedCalls) {
|
|
635
|
+
const name = call.function?.name ?? "";
|
|
636
|
+
const args = call.function?.arguments ?? "{}";
|
|
637
|
+
const result = await this.tools.dispatch(name, args);
|
|
638
|
+
this.log.append({
|
|
639
|
+
role: "tool",
|
|
640
|
+
tool_call_id: call.id ?? "",
|
|
641
|
+
name,
|
|
642
|
+
content: result
|
|
643
|
+
});
|
|
644
|
+
yield { turn: this._turn, role: "tool", content: result, toolName: name };
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
yield { turn: this._turn, role: "done", content: "[max_tool_iters reached]" };
|
|
648
|
+
}
|
|
649
|
+
async run(userInput, onEvent) {
|
|
650
|
+
let final = "";
|
|
651
|
+
for await (const ev of this.step(userInput)) {
|
|
652
|
+
onEvent?.(ev);
|
|
653
|
+
if (ev.role === "assistant_final") final = ev.content;
|
|
654
|
+
if (ev.role === "done") break;
|
|
655
|
+
}
|
|
656
|
+
return final;
|
|
657
|
+
}
|
|
658
|
+
assistantMessage(content, toolCalls) {
|
|
659
|
+
const msg = { role: "assistant", content };
|
|
660
|
+
if (toolCalls.length > 0) msg.tool_calls = toolCalls;
|
|
661
|
+
return msg;
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
// src/env.ts
|
|
666
|
+
import { readFileSync } from "fs";
|
|
667
|
+
import { resolve } from "path";
|
|
668
|
+
function loadDotenv(path = ".env") {
|
|
669
|
+
let raw;
|
|
670
|
+
try {
|
|
671
|
+
raw = readFileSync(resolve(process.cwd(), path), "utf8");
|
|
672
|
+
} catch {
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
676
|
+
const trimmed = line.trim();
|
|
677
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
678
|
+
const eq = trimmed.indexOf("=");
|
|
679
|
+
if (eq === -1) continue;
|
|
680
|
+
const key = trimmed.slice(0, eq).trim();
|
|
681
|
+
let value = trimmed.slice(eq + 1).trim();
|
|
682
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
683
|
+
value = value.slice(1, -1);
|
|
684
|
+
}
|
|
685
|
+
if (process.env[key] === void 0) process.env[key] = value;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// src/index.ts
|
|
690
|
+
var VERSION = "0.0.1";
|
|
691
|
+
export {
|
|
692
|
+
AppendOnlyLog,
|
|
693
|
+
CacheFirstLoop,
|
|
694
|
+
DeepSeekClient,
|
|
695
|
+
ImmutablePrefix,
|
|
696
|
+
SessionStats,
|
|
697
|
+
StormBreaker,
|
|
698
|
+
ToolCallRepair,
|
|
699
|
+
ToolRegistry,
|
|
700
|
+
Usage,
|
|
701
|
+
VERSION,
|
|
702
|
+
VolatileScratch,
|
|
703
|
+
analyzeSchema,
|
|
704
|
+
claudeEquivalentCost,
|
|
705
|
+
costUsd,
|
|
706
|
+
emptyPlanState,
|
|
707
|
+
fetchWithRetry,
|
|
708
|
+
flattenSchema,
|
|
709
|
+
harvest,
|
|
710
|
+
isPlanStateEmpty,
|
|
711
|
+
loadDotenv,
|
|
712
|
+
nestArguments,
|
|
713
|
+
repairTruncatedJson,
|
|
714
|
+
scavengeToolCalls
|
|
715
|
+
};
|
|
716
|
+
//# sourceMappingURL=index.js.map
|