reasonix 0.0.4 → 0.0.6
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.md +140 -58
- package/dist/cli/index.js +921 -70
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +138 -6
- package/dist/index.js +586 -32
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-Y7L6L5QS.js +0 -262
- package/dist/chunk-Y7L6L5QS.js.map +0 -1
- package/dist/cli/chunk-T2ODXAJP.js +0 -263
- package/dist/cli/chunk-T2ODXAJP.js.map +0 -1
- package/dist/cli/client-RIVGDOJP.js +0 -10
- package/dist/cli/client-RIVGDOJP.js.map +0 -1
- package/dist/client-KEA2D52Q.js +0 -9
- package/dist/client-KEA2D52Q.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,258 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
// src/client.ts
|
|
2
|
+
import { createParser } from "eventsource-parser";
|
|
3
|
+
|
|
4
|
+
// src/retry.ts
|
|
5
|
+
var DEFAULT_RETRYABLE_STATUSES = [408, 429, 500, 502, 503, 504];
|
|
6
|
+
async function fetchWithRetry(fetchFn, url, init, opts = {}) {
|
|
7
|
+
const maxAttempts = opts.maxAttempts ?? 4;
|
|
8
|
+
const initial = opts.initialBackoffMs ?? 500;
|
|
9
|
+
const cap = opts.maxBackoffMs ?? 1e4;
|
|
10
|
+
const retryable = new Set(opts.retryableStatuses ?? DEFAULT_RETRYABLE_STATUSES);
|
|
11
|
+
let lastError;
|
|
12
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
13
|
+
if (opts.signal?.aborted) throw new Error("aborted");
|
|
14
|
+
try {
|
|
15
|
+
const resp = await fetchFn(url, init);
|
|
16
|
+
if (resp.ok || !retryable.has(resp.status)) return resp;
|
|
17
|
+
if (attempt === maxAttempts - 1) return resp;
|
|
18
|
+
await resp.text().catch(() => void 0);
|
|
19
|
+
const waitMs = computeWait(attempt, initial, cap, resp.headers.get("Retry-After"));
|
|
20
|
+
opts.onRetry?.({ attempt: attempt + 1, reason: `http ${resp.status}`, waitMs });
|
|
21
|
+
await sleep(waitMs, opts.signal);
|
|
22
|
+
} catch (err) {
|
|
23
|
+
lastError = err;
|
|
24
|
+
if (isAbortError(err) || opts.signal?.aborted) throw err;
|
|
25
|
+
if (attempt === maxAttempts - 1) throw err;
|
|
26
|
+
const waitMs = computeWait(attempt, initial, cap, null);
|
|
27
|
+
opts.onRetry?.({
|
|
28
|
+
attempt: attempt + 1,
|
|
29
|
+
reason: `network: ${messageOf(err)}`,
|
|
30
|
+
waitMs
|
|
31
|
+
});
|
|
32
|
+
await sleep(waitMs, opts.signal);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
throw lastError ?? new Error("fetchWithRetry: loop exited unexpectedly");
|
|
36
|
+
}
|
|
37
|
+
function computeWait(attempt, initial, cap, retryAfter) {
|
|
38
|
+
if (retryAfter) {
|
|
39
|
+
const seconds = Number.parseFloat(retryAfter);
|
|
40
|
+
if (Number.isFinite(seconds) && seconds > 0) {
|
|
41
|
+
return Math.min(seconds * 1e3, cap);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const exp = initial * 2 ** attempt;
|
|
45
|
+
const jitter = exp * (0.75 + Math.random() * 0.5);
|
|
46
|
+
return Math.min(Math.max(jitter, 0), cap);
|
|
47
|
+
}
|
|
48
|
+
function sleep(ms, signal) {
|
|
49
|
+
if (ms <= 0) return Promise.resolve();
|
|
50
|
+
return new Promise((resolve2, reject) => {
|
|
51
|
+
const timer = setTimeout(resolve2, ms);
|
|
52
|
+
if (signal) {
|
|
53
|
+
const onAbort = () => {
|
|
54
|
+
clearTimeout(timer);
|
|
55
|
+
reject(new Error("aborted"));
|
|
56
|
+
};
|
|
57
|
+
if (signal.aborted) onAbort();
|
|
58
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function isAbortError(err) {
|
|
63
|
+
if (!err || typeof err !== "object") return false;
|
|
64
|
+
const name = err.name;
|
|
65
|
+
return name === "AbortError";
|
|
66
|
+
}
|
|
67
|
+
function messageOf(err) {
|
|
68
|
+
if (err instanceof Error) return err.message;
|
|
69
|
+
try {
|
|
70
|
+
return String(err);
|
|
71
|
+
} catch {
|
|
72
|
+
return "unknown error";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/client.ts
|
|
77
|
+
var Usage = class _Usage {
|
|
78
|
+
constructor(promptTokens = 0, completionTokens = 0, totalTokens = 0, promptCacheHitTokens = 0, promptCacheMissTokens = 0) {
|
|
79
|
+
this.promptTokens = promptTokens;
|
|
80
|
+
this.completionTokens = completionTokens;
|
|
81
|
+
this.totalTokens = totalTokens;
|
|
82
|
+
this.promptCacheHitTokens = promptCacheHitTokens;
|
|
83
|
+
this.promptCacheMissTokens = promptCacheMissTokens;
|
|
84
|
+
}
|
|
85
|
+
promptTokens;
|
|
86
|
+
completionTokens;
|
|
87
|
+
totalTokens;
|
|
88
|
+
promptCacheHitTokens;
|
|
89
|
+
promptCacheMissTokens;
|
|
90
|
+
get cacheHitRatio() {
|
|
91
|
+
const denom = this.promptCacheHitTokens + this.promptCacheMissTokens;
|
|
92
|
+
return denom > 0 ? this.promptCacheHitTokens / denom : 0;
|
|
93
|
+
}
|
|
94
|
+
static fromApi(raw) {
|
|
95
|
+
const u = raw ?? {};
|
|
96
|
+
return new _Usage(
|
|
97
|
+
u.prompt_tokens ?? 0,
|
|
98
|
+
u.completion_tokens ?? 0,
|
|
99
|
+
u.total_tokens ?? 0,
|
|
100
|
+
u.prompt_cache_hit_tokens ?? 0,
|
|
101
|
+
u.prompt_cache_miss_tokens ?? 0
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
var DeepSeekClient = class {
|
|
106
|
+
apiKey;
|
|
107
|
+
baseUrl;
|
|
108
|
+
timeoutMs;
|
|
109
|
+
retry;
|
|
110
|
+
_fetch;
|
|
111
|
+
constructor(opts = {}) {
|
|
112
|
+
const apiKey = opts.apiKey ?? process.env.DEEPSEEK_API_KEY;
|
|
113
|
+
if (!apiKey) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
"DEEPSEEK_API_KEY is not set. Put it in .env or pass apiKey to DeepSeekClient."
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
this.apiKey = apiKey;
|
|
119
|
+
this.baseUrl = (opts.baseUrl ?? process.env.DEEPSEEK_BASE_URL ?? "https://api.deepseek.com").replace(/\/+$/, "");
|
|
120
|
+
this.timeoutMs = opts.timeoutMs ?? 12e4;
|
|
121
|
+
this._fetch = opts.fetch ?? globalThis.fetch.bind(globalThis);
|
|
122
|
+
this.retry = opts.retry ?? {};
|
|
123
|
+
}
|
|
124
|
+
buildPayload(opts, stream) {
|
|
125
|
+
const payload = {
|
|
126
|
+
model: opts.model,
|
|
127
|
+
messages: opts.messages,
|
|
128
|
+
stream
|
|
129
|
+
};
|
|
130
|
+
if (opts.tools?.length) payload.tools = opts.tools;
|
|
131
|
+
if (opts.temperature !== void 0) payload.temperature = opts.temperature;
|
|
132
|
+
if (opts.maxTokens !== void 0) payload.max_tokens = opts.maxTokens;
|
|
133
|
+
if (opts.responseFormat) payload.response_format = opts.responseFormat;
|
|
134
|
+
return payload;
|
|
135
|
+
}
|
|
136
|
+
async chat(opts) {
|
|
137
|
+
const ctrl = new AbortController();
|
|
138
|
+
const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
|
|
139
|
+
const signal = opts.signal ?? ctrl.signal;
|
|
140
|
+
try {
|
|
141
|
+
const resp = await fetchWithRetry(
|
|
142
|
+
this._fetch,
|
|
143
|
+
`${this.baseUrl}/chat/completions`,
|
|
144
|
+
{
|
|
145
|
+
method: "POST",
|
|
146
|
+
headers: {
|
|
147
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
148
|
+
"Content-Type": "application/json"
|
|
149
|
+
},
|
|
150
|
+
body: JSON.stringify(this.buildPayload(opts, false)),
|
|
151
|
+
signal
|
|
152
|
+
},
|
|
153
|
+
{ ...this.retry, signal }
|
|
154
|
+
);
|
|
155
|
+
if (!resp.ok) {
|
|
156
|
+
throw new Error(`DeepSeek ${resp.status}: ${await resp.text()}`);
|
|
157
|
+
}
|
|
158
|
+
const data = await resp.json();
|
|
159
|
+
const choice = data.choices?.[0]?.message ?? {};
|
|
160
|
+
return {
|
|
161
|
+
content: choice.content ?? "",
|
|
162
|
+
reasoningContent: choice.reasoning_content ?? null,
|
|
163
|
+
toolCalls: choice.tool_calls ?? [],
|
|
164
|
+
usage: Usage.fromApi(data.usage),
|
|
165
|
+
raw: data
|
|
166
|
+
};
|
|
167
|
+
} finally {
|
|
168
|
+
clearTimeout(timer);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async *stream(opts) {
|
|
172
|
+
const ctrl = new AbortController();
|
|
173
|
+
const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
|
|
174
|
+
const signal = opts.signal ?? ctrl.signal;
|
|
175
|
+
let resp;
|
|
176
|
+
try {
|
|
177
|
+
resp = await fetchWithRetry(
|
|
178
|
+
this._fetch,
|
|
179
|
+
`${this.baseUrl}/chat/completions`,
|
|
180
|
+
{
|
|
181
|
+
method: "POST",
|
|
182
|
+
headers: {
|
|
183
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
184
|
+
"Content-Type": "application/json",
|
|
185
|
+
Accept: "text/event-stream"
|
|
186
|
+
},
|
|
187
|
+
body: JSON.stringify(this.buildPayload(opts, true)),
|
|
188
|
+
signal
|
|
189
|
+
},
|
|
190
|
+
{ ...this.retry, signal }
|
|
191
|
+
);
|
|
192
|
+
} catch (err) {
|
|
193
|
+
clearTimeout(timer);
|
|
194
|
+
throw err;
|
|
195
|
+
}
|
|
196
|
+
if (!resp.ok || !resp.body) {
|
|
197
|
+
clearTimeout(timer);
|
|
198
|
+
throw new Error(`DeepSeek ${resp.status}: ${await resp.text().catch(() => "")}`);
|
|
199
|
+
}
|
|
200
|
+
const queue = [];
|
|
201
|
+
let done = false;
|
|
202
|
+
const parser = createParser({
|
|
203
|
+
onEvent: (ev) => {
|
|
204
|
+
if (!ev.data || ev.data === "[DONE]") {
|
|
205
|
+
done = true;
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
const json = JSON.parse(ev.data);
|
|
210
|
+
const delta = json.choices?.[0]?.delta ?? {};
|
|
211
|
+
const finishReason = json.choices?.[0]?.finish_reason ?? void 0;
|
|
212
|
+
const chunk = { raw: json, finishReason };
|
|
213
|
+
if (typeof delta.content === "string" && delta.content.length > 0) {
|
|
214
|
+
chunk.contentDelta = delta.content;
|
|
215
|
+
}
|
|
216
|
+
if (typeof delta.reasoning_content === "string" && delta.reasoning_content.length > 0) {
|
|
217
|
+
chunk.reasoningDelta = delta.reasoning_content;
|
|
218
|
+
}
|
|
219
|
+
if (Array.isArray(delta.tool_calls) && delta.tool_calls.length > 0) {
|
|
220
|
+
const tc = delta.tool_calls[0];
|
|
221
|
+
chunk.toolCallDelta = {
|
|
222
|
+
index: tc.index ?? 0,
|
|
223
|
+
id: tc.id,
|
|
224
|
+
name: tc.function?.name,
|
|
225
|
+
argumentsDelta: tc.function?.arguments
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
if (json.usage) {
|
|
229
|
+
chunk.usage = Usage.fromApi(json.usage);
|
|
230
|
+
}
|
|
231
|
+
queue.push(chunk);
|
|
232
|
+
} catch {
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
const reader = resp.body.getReader();
|
|
237
|
+
const decoder = new TextDecoder();
|
|
238
|
+
try {
|
|
239
|
+
while (true) {
|
|
240
|
+
if (queue.length > 0) {
|
|
241
|
+
yield queue.shift();
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (done) break;
|
|
245
|
+
const { value, done: streamDone } = await reader.read();
|
|
246
|
+
if (streamDone) break;
|
|
247
|
+
parser.feed(decoder.decode(value, { stream: true }));
|
|
248
|
+
}
|
|
249
|
+
while (queue.length > 0) yield queue.shift();
|
|
250
|
+
} finally {
|
|
251
|
+
clearTimeout(timer);
|
|
252
|
+
reader.releaseLock();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
};
|
|
6
256
|
|
|
7
257
|
// src/harvest.ts
|
|
8
258
|
function emptyPlanState() {
|
|
@@ -94,6 +344,66 @@ function sanitizeArray(raw, maxItems, maxItemLen) {
|
|
|
94
344
|
return out;
|
|
95
345
|
}
|
|
96
346
|
|
|
347
|
+
// src/consistency.ts
|
|
348
|
+
var defaultSelector = (samples) => {
|
|
349
|
+
if (samples.length === 0) throw new Error("defaultSelector: samples is empty");
|
|
350
|
+
return samples.slice().sort((a, b) => {
|
|
351
|
+
const uDiff = a.planState.uncertainties.length - b.planState.uncertainties.length;
|
|
352
|
+
if (uDiff !== 0) return uDiff;
|
|
353
|
+
const aLen = a.response.content?.length ?? 0;
|
|
354
|
+
const bLen = b.response.content?.length ?? 0;
|
|
355
|
+
return aLen - bLen;
|
|
356
|
+
})[0];
|
|
357
|
+
};
|
|
358
|
+
async function runBranches(client, request, opts = {}) {
|
|
359
|
+
const budget = Math.max(1, opts.budget ?? 1);
|
|
360
|
+
const temperatures = resolveTemperatures(budget, opts.temperatures);
|
|
361
|
+
const selector = opts.selector ?? defaultSelector;
|
|
362
|
+
const samples = await Promise.all(
|
|
363
|
+
temperatures.map(async (temperature, index) => {
|
|
364
|
+
const response = await client.chat({ ...request, temperature });
|
|
365
|
+
const planState = await harvest(response.reasoningContent, client, opts.harvestOptions);
|
|
366
|
+
const sample = { index, temperature, response, planState };
|
|
367
|
+
try {
|
|
368
|
+
opts.onSampleDone?.(sample);
|
|
369
|
+
} catch {
|
|
370
|
+
}
|
|
371
|
+
return sample;
|
|
372
|
+
})
|
|
373
|
+
);
|
|
374
|
+
return { chosen: selector(samples), samples };
|
|
375
|
+
}
|
|
376
|
+
function aggregateBranchUsage(samples) {
|
|
377
|
+
let promptTokens = 0;
|
|
378
|
+
let completionTokens = 0;
|
|
379
|
+
let totalTokens = 0;
|
|
380
|
+
let promptCacheHitTokens = 0;
|
|
381
|
+
let promptCacheMissTokens = 0;
|
|
382
|
+
for (const s of samples) {
|
|
383
|
+
promptTokens += s.response.usage.promptTokens;
|
|
384
|
+
completionTokens += s.response.usage.completionTokens;
|
|
385
|
+
totalTokens += s.response.usage.totalTokens;
|
|
386
|
+
promptCacheHitTokens += s.response.usage.promptCacheHitTokens;
|
|
387
|
+
promptCacheMissTokens += s.response.usage.promptCacheMissTokens;
|
|
388
|
+
}
|
|
389
|
+
return {
|
|
390
|
+
promptTokens,
|
|
391
|
+
completionTokens,
|
|
392
|
+
totalTokens,
|
|
393
|
+
promptCacheHitTokens,
|
|
394
|
+
promptCacheMissTokens
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
function resolveTemperatures(budget, custom) {
|
|
398
|
+
if (custom && custom.length >= budget) return [...custom.slice(0, budget)];
|
|
399
|
+
if (budget === 1) return [0];
|
|
400
|
+
const out = [];
|
|
401
|
+
for (let i = 0; i < budget; i++) {
|
|
402
|
+
out.push(Number((i / (budget - 1)).toFixed(2)));
|
|
403
|
+
}
|
|
404
|
+
return out;
|
|
405
|
+
}
|
|
406
|
+
|
|
97
407
|
// src/memory.ts
|
|
98
408
|
import { createHash } from "crypto";
|
|
99
409
|
var ImmutablePrefix = class {
|
|
@@ -468,6 +778,93 @@ function signature2(call) {
|
|
|
468
778
|
return `${call.function?.name ?? ""}::${call.function?.arguments ?? ""}`;
|
|
469
779
|
}
|
|
470
780
|
|
|
781
|
+
// src/session.ts
|
|
782
|
+
import {
|
|
783
|
+
appendFileSync,
|
|
784
|
+
chmodSync,
|
|
785
|
+
existsSync,
|
|
786
|
+
mkdirSync,
|
|
787
|
+
readFileSync,
|
|
788
|
+
readdirSync,
|
|
789
|
+
statSync,
|
|
790
|
+
unlinkSync
|
|
791
|
+
} from "fs";
|
|
792
|
+
import { homedir } from "os";
|
|
793
|
+
import { dirname, join } from "path";
|
|
794
|
+
function sessionsDir() {
|
|
795
|
+
return join(homedir(), ".reasonix", "sessions");
|
|
796
|
+
}
|
|
797
|
+
function sessionPath(name) {
|
|
798
|
+
return join(sessionsDir(), `${sanitizeName(name)}.jsonl`);
|
|
799
|
+
}
|
|
800
|
+
function sanitizeName(name) {
|
|
801
|
+
const cleaned = name.replace(/[^\w\-\u4e00-\u9fa5]/g, "_").slice(0, 64);
|
|
802
|
+
return cleaned || "default";
|
|
803
|
+
}
|
|
804
|
+
function loadSessionMessages(name) {
|
|
805
|
+
const path = sessionPath(name);
|
|
806
|
+
if (!existsSync(path)) return [];
|
|
807
|
+
try {
|
|
808
|
+
const raw = readFileSync(path, "utf8");
|
|
809
|
+
const out = [];
|
|
810
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
811
|
+
const trimmed = line.trim();
|
|
812
|
+
if (!trimmed) continue;
|
|
813
|
+
try {
|
|
814
|
+
const msg = JSON.parse(trimmed);
|
|
815
|
+
if (msg && typeof msg === "object" && "role" in msg) out.push(msg);
|
|
816
|
+
} catch {
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
return out;
|
|
820
|
+
} catch {
|
|
821
|
+
return [];
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
function appendSessionMessage(name, message) {
|
|
825
|
+
const path = sessionPath(name);
|
|
826
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
827
|
+
appendFileSync(path, `${JSON.stringify(message)}
|
|
828
|
+
`, "utf8");
|
|
829
|
+
try {
|
|
830
|
+
chmodSync(path, 384);
|
|
831
|
+
} catch {
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
function listSessions() {
|
|
835
|
+
const dir = sessionsDir();
|
|
836
|
+
if (!existsSync(dir)) return [];
|
|
837
|
+
try {
|
|
838
|
+
const files = readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
|
|
839
|
+
return files.map((file) => {
|
|
840
|
+
const path = join(dir, file);
|
|
841
|
+
const stat = statSync(path);
|
|
842
|
+
const name = file.replace(/\.jsonl$/, "");
|
|
843
|
+
const messageCount = countLines(path);
|
|
844
|
+
return { name, path, size: stat.size, messageCount, mtime: stat.mtime };
|
|
845
|
+
}).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
846
|
+
} catch {
|
|
847
|
+
return [];
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
function deleteSession(name) {
|
|
851
|
+
const path = sessionPath(name);
|
|
852
|
+
try {
|
|
853
|
+
unlinkSync(path);
|
|
854
|
+
return true;
|
|
855
|
+
} catch {
|
|
856
|
+
return false;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
function countLines(path) {
|
|
860
|
+
try {
|
|
861
|
+
const raw = readFileSync(path, "utf8");
|
|
862
|
+
return raw.split(/\r?\n/).filter((l) => l.trim()).length;
|
|
863
|
+
} catch {
|
|
864
|
+
return 0;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
471
868
|
// src/telemetry.ts
|
|
472
869
|
var DEEPSEEK_PRICING = {
|
|
473
870
|
"deepseek-chat": { inputCacheHit: 0.07, inputCacheMiss: 0.27, output: 1.1 },
|
|
@@ -611,27 +1008,92 @@ var CacheFirstLoop = class {
|
|
|
611
1008
|
client;
|
|
612
1009
|
prefix;
|
|
613
1010
|
tools;
|
|
614
|
-
model;
|
|
615
1011
|
maxToolIters;
|
|
616
|
-
stream;
|
|
617
|
-
harvestEnabled;
|
|
618
|
-
harvestOptions;
|
|
619
1012
|
log = new AppendOnlyLog();
|
|
620
1013
|
scratch = new VolatileScratch();
|
|
621
1014
|
stats = new SessionStats();
|
|
622
1015
|
repair;
|
|
1016
|
+
// Mutable via configure() — slash commands in the TUI / library callers tweak
|
|
1017
|
+
// these mid-session so users don't have to restart to try harvest or branch.
|
|
1018
|
+
model;
|
|
1019
|
+
stream;
|
|
1020
|
+
harvestEnabled;
|
|
1021
|
+
harvestOptions;
|
|
1022
|
+
branchEnabled;
|
|
1023
|
+
branchOptions;
|
|
1024
|
+
sessionName;
|
|
1025
|
+
/** Number of messages that were pre-loaded from the session file. */
|
|
1026
|
+
resumedMessageCount;
|
|
623
1027
|
_turn = 0;
|
|
1028
|
+
_streamPreference;
|
|
624
1029
|
constructor(opts) {
|
|
625
1030
|
this.client = opts.client;
|
|
626
1031
|
this.prefix = opts.prefix;
|
|
627
1032
|
this.tools = opts.tools ?? new ToolRegistry();
|
|
628
1033
|
this.model = opts.model ?? "deepseek-chat";
|
|
629
1034
|
this.maxToolIters = opts.maxToolIters ?? 8;
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
1035
|
+
if (typeof opts.branch === "number") {
|
|
1036
|
+
this.branchOptions = { budget: opts.branch };
|
|
1037
|
+
} else if (opts.branch && typeof opts.branch === "object") {
|
|
1038
|
+
this.branchOptions = opts.branch;
|
|
1039
|
+
} else {
|
|
1040
|
+
this.branchOptions = {};
|
|
1041
|
+
}
|
|
1042
|
+
this.branchEnabled = (this.branchOptions.budget ?? 1) > 1;
|
|
1043
|
+
const harvestForced = this.branchEnabled;
|
|
1044
|
+
this.harvestEnabled = harvestForced || opts.harvest === true || typeof opts.harvest === "object" && opts.harvest !== null;
|
|
1045
|
+
this.harvestOptions = typeof opts.harvest === "object" && opts.harvest !== null ? opts.harvest : this.branchOptions.harvestOptions ?? {};
|
|
1046
|
+
this._streamPreference = opts.stream ?? true;
|
|
1047
|
+
this.stream = this.branchEnabled ? false : this._streamPreference;
|
|
633
1048
|
const allowedNames = /* @__PURE__ */ new Set([...this.prefix.toolSpecs.map((s) => s.function.name)]);
|
|
634
1049
|
this.repair = new ToolCallRepair({ allowedToolNames: allowedNames });
|
|
1050
|
+
this.sessionName = opts.session ?? null;
|
|
1051
|
+
if (this.sessionName) {
|
|
1052
|
+
const prior = loadSessionMessages(this.sessionName);
|
|
1053
|
+
for (const msg of prior) this.log.append(msg);
|
|
1054
|
+
this.resumedMessageCount = prior.length;
|
|
1055
|
+
} else {
|
|
1056
|
+
this.resumedMessageCount = 0;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
appendAndPersist(message) {
|
|
1060
|
+
this.log.append(message);
|
|
1061
|
+
if (this.sessionName) {
|
|
1062
|
+
try {
|
|
1063
|
+
appendSessionMessage(this.sessionName, message);
|
|
1064
|
+
} catch {
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
/**
|
|
1069
|
+
* Reconfigure model/harvest/branch/stream mid-session. The loop's log,
|
|
1070
|
+
* scratch, and stats are preserved — only the per-turn behavior changes.
|
|
1071
|
+
* Used by the TUI's slash commands and by library callers who want to
|
|
1072
|
+
* flip a knob between turns.
|
|
1073
|
+
*/
|
|
1074
|
+
configure(opts) {
|
|
1075
|
+
if (opts.model !== void 0) this.model = opts.model;
|
|
1076
|
+
if (opts.stream !== void 0) this._streamPreference = opts.stream;
|
|
1077
|
+
if (opts.branch !== void 0) {
|
|
1078
|
+
if (typeof opts.branch === "number") {
|
|
1079
|
+
this.branchOptions = { budget: opts.branch };
|
|
1080
|
+
} else if (opts.branch && typeof opts.branch === "object") {
|
|
1081
|
+
this.branchOptions = opts.branch;
|
|
1082
|
+
} else {
|
|
1083
|
+
this.branchOptions = {};
|
|
1084
|
+
}
|
|
1085
|
+
this.branchEnabled = (this.branchOptions.budget ?? 1) > 1;
|
|
1086
|
+
}
|
|
1087
|
+
if (opts.harvest !== void 0) {
|
|
1088
|
+
const want = opts.harvest === true || typeof opts.harvest === "object" && opts.harvest !== null;
|
|
1089
|
+
this.harvestEnabled = want || this.branchEnabled;
|
|
1090
|
+
if (typeof opts.harvest === "object" && opts.harvest !== null) {
|
|
1091
|
+
this.harvestOptions = opts.harvest;
|
|
1092
|
+
}
|
|
1093
|
+
} else if (this.branchEnabled) {
|
|
1094
|
+
this.harvestEnabled = true;
|
|
1095
|
+
}
|
|
1096
|
+
this.stream = this.branchEnabled ? false : this._streamPreference;
|
|
635
1097
|
}
|
|
636
1098
|
buildMessages(pendingUser) {
|
|
637
1099
|
const msgs = [...this.prefix.toMessages(), ...this.log.toMessages()];
|
|
@@ -649,8 +1111,85 @@ var CacheFirstLoop = class {
|
|
|
649
1111
|
let reasoningContent = "";
|
|
650
1112
|
let toolCalls = [];
|
|
651
1113
|
let usage = null;
|
|
1114
|
+
let branchSummary;
|
|
1115
|
+
let preHarvestedPlanState;
|
|
652
1116
|
try {
|
|
653
|
-
if (this.
|
|
1117
|
+
if (this.branchEnabled) {
|
|
1118
|
+
const budget = this.branchOptions.budget ?? 1;
|
|
1119
|
+
yield {
|
|
1120
|
+
turn: this._turn,
|
|
1121
|
+
role: "branch_start",
|
|
1122
|
+
content: "",
|
|
1123
|
+
branchProgress: {
|
|
1124
|
+
completed: 0,
|
|
1125
|
+
total: budget,
|
|
1126
|
+
latestIndex: -1,
|
|
1127
|
+
latestTemperature: -1,
|
|
1128
|
+
latestUncertainties: -1
|
|
1129
|
+
}
|
|
1130
|
+
};
|
|
1131
|
+
const queue = [];
|
|
1132
|
+
let waiter = null;
|
|
1133
|
+
const onSampleDone = (sample) => {
|
|
1134
|
+
if (waiter) {
|
|
1135
|
+
const w = waiter;
|
|
1136
|
+
waiter = null;
|
|
1137
|
+
w(sample);
|
|
1138
|
+
} else {
|
|
1139
|
+
queue.push(sample);
|
|
1140
|
+
}
|
|
1141
|
+
};
|
|
1142
|
+
const branchPromise = runBranches(
|
|
1143
|
+
this.client,
|
|
1144
|
+
{
|
|
1145
|
+
model: this.model,
|
|
1146
|
+
messages,
|
|
1147
|
+
tools: toolSpecs.length ? toolSpecs : void 0
|
|
1148
|
+
},
|
|
1149
|
+
{
|
|
1150
|
+
...this.branchOptions,
|
|
1151
|
+
harvestOptions: this.harvestOptions,
|
|
1152
|
+
onSampleDone
|
|
1153
|
+
}
|
|
1154
|
+
);
|
|
1155
|
+
for (let k = 0; k < budget; k++) {
|
|
1156
|
+
const sample = queue.shift() ?? await new Promise((resolve2) => {
|
|
1157
|
+
waiter = resolve2;
|
|
1158
|
+
});
|
|
1159
|
+
yield {
|
|
1160
|
+
turn: this._turn,
|
|
1161
|
+
role: "branch_progress",
|
|
1162
|
+
content: "",
|
|
1163
|
+
branchProgress: {
|
|
1164
|
+
completed: k + 1,
|
|
1165
|
+
total: budget,
|
|
1166
|
+
latestIndex: sample.index,
|
|
1167
|
+
latestTemperature: sample.temperature,
|
|
1168
|
+
latestUncertainties: sample.planState.uncertainties.length
|
|
1169
|
+
}
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
const result = await branchPromise;
|
|
1173
|
+
assistantContent = result.chosen.response.content;
|
|
1174
|
+
reasoningContent = result.chosen.response.reasoningContent ?? "";
|
|
1175
|
+
toolCalls = result.chosen.response.toolCalls;
|
|
1176
|
+
const agg = aggregateBranchUsage(result.samples);
|
|
1177
|
+
usage = new Usage(
|
|
1178
|
+
agg.promptTokens,
|
|
1179
|
+
agg.completionTokens,
|
|
1180
|
+
agg.totalTokens,
|
|
1181
|
+
agg.promptCacheHitTokens,
|
|
1182
|
+
agg.promptCacheMissTokens
|
|
1183
|
+
);
|
|
1184
|
+
preHarvestedPlanState = result.chosen.planState;
|
|
1185
|
+
branchSummary = summarizeBranch(result.chosen, result.samples);
|
|
1186
|
+
yield {
|
|
1187
|
+
turn: this._turn,
|
|
1188
|
+
role: "branch_done",
|
|
1189
|
+
content: "",
|
|
1190
|
+
branch: branchSummary
|
|
1191
|
+
};
|
|
1192
|
+
} else if (this.stream) {
|
|
654
1193
|
const callBuf = /* @__PURE__ */ new Map();
|
|
655
1194
|
for await (const chunk of this.client.stream({
|
|
656
1195
|
model: this.model,
|
|
@@ -710,29 +1249,26 @@ var CacheFirstLoop = class {
|
|
|
710
1249
|
};
|
|
711
1250
|
return;
|
|
712
1251
|
}
|
|
713
|
-
const turnStats = this.stats.record(
|
|
714
|
-
this._turn,
|
|
715
|
-
this.model,
|
|
716
|
-
usage ?? new (await import("./client-KEA2D52Q.js")).Usage()
|
|
717
|
-
);
|
|
1252
|
+
const turnStats = this.stats.record(this._turn, this.model, usage ?? new Usage());
|
|
718
1253
|
if (pendingUser !== null) {
|
|
719
|
-
this.
|
|
1254
|
+
this.appendAndPersist({ role: "user", content: pendingUser });
|
|
720
1255
|
pendingUser = null;
|
|
721
1256
|
}
|
|
722
1257
|
this.scratch.reasoning = reasoningContent || null;
|
|
723
|
-
const planState = this.harvestEnabled ? await harvest(reasoningContent || null, this.client, this.harvestOptions) : emptyPlanState();
|
|
1258
|
+
const planState = preHarvestedPlanState ? preHarvestedPlanState : this.harvestEnabled ? await harvest(reasoningContent || null, this.client, this.harvestOptions) : emptyPlanState();
|
|
724
1259
|
const { calls: repairedCalls, report } = this.repair.process(
|
|
725
1260
|
toolCalls,
|
|
726
1261
|
reasoningContent || null
|
|
727
1262
|
);
|
|
728
|
-
this.
|
|
1263
|
+
this.appendAndPersist(this.assistantMessage(assistantContent, repairedCalls));
|
|
729
1264
|
yield {
|
|
730
1265
|
turn: this._turn,
|
|
731
1266
|
role: "assistant_final",
|
|
732
1267
|
content: assistantContent,
|
|
733
1268
|
stats: turnStats,
|
|
734
1269
|
planState,
|
|
735
|
-
repair: report
|
|
1270
|
+
repair: report,
|
|
1271
|
+
branch: branchSummary
|
|
736
1272
|
};
|
|
737
1273
|
if (repairedCalls.length === 0) {
|
|
738
1274
|
yield { turn: this._turn, role: "done", content: assistantContent };
|
|
@@ -742,7 +1278,7 @@ var CacheFirstLoop = class {
|
|
|
742
1278
|
const name = call.function?.name ?? "";
|
|
743
1279
|
const args = call.function?.arguments ?? "{}";
|
|
744
1280
|
const result = await this.tools.dispatch(name, args);
|
|
745
|
-
this.
|
|
1281
|
+
this.appendAndPersist({
|
|
746
1282
|
role: "tool",
|
|
747
1283
|
tool_call_id: call.id ?? "",
|
|
748
1284
|
name,
|
|
@@ -768,14 +1304,22 @@ var CacheFirstLoop = class {
|
|
|
768
1304
|
return msg;
|
|
769
1305
|
}
|
|
770
1306
|
};
|
|
1307
|
+
function summarizeBranch(chosen, samples) {
|
|
1308
|
+
return {
|
|
1309
|
+
budget: samples.length,
|
|
1310
|
+
chosenIndex: chosen.index,
|
|
1311
|
+
uncertainties: samples.map((s) => s.planState.uncertainties.length),
|
|
1312
|
+
temperatures: samples.map((s) => s.temperature)
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
771
1315
|
|
|
772
1316
|
// src/env.ts
|
|
773
|
-
import { readFileSync } from "fs";
|
|
1317
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
774
1318
|
import { resolve } from "path";
|
|
775
1319
|
function loadDotenv(path = ".env") {
|
|
776
1320
|
let raw;
|
|
777
1321
|
try {
|
|
778
|
-
raw =
|
|
1322
|
+
raw = readFileSync2(resolve(process.cwd(), path), "utf8");
|
|
779
1323
|
} catch {
|
|
780
1324
|
return;
|
|
781
1325
|
}
|
|
@@ -794,15 +1338,15 @@ function loadDotenv(path = ".env") {
|
|
|
794
1338
|
}
|
|
795
1339
|
|
|
796
1340
|
// src/config.ts
|
|
797
|
-
import { chmodSync, mkdirSync, readFileSync as
|
|
798
|
-
import { homedir } from "os";
|
|
799
|
-
import { dirname, join } from "path";
|
|
1341
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "fs";
|
|
1342
|
+
import { homedir as homedir2 } from "os";
|
|
1343
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
800
1344
|
function defaultConfigPath() {
|
|
801
|
-
return
|
|
1345
|
+
return join2(homedir2(), ".reasonix", "config.json");
|
|
802
1346
|
}
|
|
803
1347
|
function readConfig(path = defaultConfigPath()) {
|
|
804
1348
|
try {
|
|
805
|
-
const raw =
|
|
1349
|
+
const raw = readFileSync3(path, "utf8");
|
|
806
1350
|
const parsed = JSON.parse(raw);
|
|
807
1351
|
if (parsed && typeof parsed === "object") return parsed;
|
|
808
1352
|
} catch {
|
|
@@ -810,10 +1354,10 @@ function readConfig(path = defaultConfigPath()) {
|
|
|
810
1354
|
return {};
|
|
811
1355
|
}
|
|
812
1356
|
function writeConfig(cfg, path = defaultConfigPath()) {
|
|
813
|
-
|
|
1357
|
+
mkdirSync2(dirname2(path), { recursive: true });
|
|
814
1358
|
writeFileSync(path, JSON.stringify(cfg, null, 2), "utf8");
|
|
815
1359
|
try {
|
|
816
|
-
|
|
1360
|
+
chmodSync2(path, 384);
|
|
817
1361
|
} catch {
|
|
818
1362
|
}
|
|
819
1363
|
}
|
|
@@ -850,24 +1394,34 @@ export {
|
|
|
850
1394
|
Usage,
|
|
851
1395
|
VERSION,
|
|
852
1396
|
VolatileScratch,
|
|
1397
|
+
aggregateBranchUsage,
|
|
853
1398
|
analyzeSchema,
|
|
1399
|
+
appendSessionMessage,
|
|
854
1400
|
claudeEquivalentCost,
|
|
855
1401
|
costUsd,
|
|
856
1402
|
defaultConfigPath,
|
|
1403
|
+
defaultSelector,
|
|
1404
|
+
deleteSession,
|
|
857
1405
|
emptyPlanState,
|
|
858
1406
|
fetchWithRetry,
|
|
859
1407
|
flattenSchema,
|
|
860
1408
|
harvest,
|
|
861
1409
|
isPlanStateEmpty,
|
|
862
1410
|
isPlausibleKey,
|
|
1411
|
+
listSessions,
|
|
863
1412
|
loadApiKey,
|
|
864
1413
|
loadDotenv,
|
|
1414
|
+
loadSessionMessages,
|
|
865
1415
|
nestArguments,
|
|
866
1416
|
readConfig,
|
|
867
1417
|
redactKey,
|
|
868
1418
|
repairTruncatedJson,
|
|
1419
|
+
runBranches,
|
|
1420
|
+
sanitizeName as sanitizeSessionName,
|
|
869
1421
|
saveApiKey,
|
|
870
1422
|
scavengeToolCalls,
|
|
1423
|
+
sessionPath,
|
|
1424
|
+
sessionsDir,
|
|
871
1425
|
writeConfig
|
|
872
1426
|
};
|
|
873
1427
|
//# sourceMappingURL=index.js.map
|