reasonix 0.0.4 → 0.0.5
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 +133 -58
- package/dist/cli/index.js +716 -54
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +96 -6
- package/dist/index.js +460 -20
- 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/cli/index.js
CHANGED
|
@@ -1,11 +1,264 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
DeepSeekClient
|
|
4
|
-
} from "./chunk-T2ODXAJP.js";
|
|
5
2
|
|
|
6
3
|
// src/cli/index.ts
|
|
7
4
|
import { Command } from "commander";
|
|
8
5
|
|
|
6
|
+
// src/client.ts
|
|
7
|
+
import { createParser } from "eventsource-parser";
|
|
8
|
+
|
|
9
|
+
// src/retry.ts
|
|
10
|
+
var DEFAULT_RETRYABLE_STATUSES = [408, 429, 500, 502, 503, 504];
|
|
11
|
+
async function fetchWithRetry(fetchFn, url, init, opts = {}) {
|
|
12
|
+
const maxAttempts = opts.maxAttempts ?? 4;
|
|
13
|
+
const initial = opts.initialBackoffMs ?? 500;
|
|
14
|
+
const cap = opts.maxBackoffMs ?? 1e4;
|
|
15
|
+
const retryable = new Set(opts.retryableStatuses ?? DEFAULT_RETRYABLE_STATUSES);
|
|
16
|
+
let lastError;
|
|
17
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
18
|
+
if (opts.signal?.aborted) throw new Error("aborted");
|
|
19
|
+
try {
|
|
20
|
+
const resp = await fetchFn(url, init);
|
|
21
|
+
if (resp.ok || !retryable.has(resp.status)) return resp;
|
|
22
|
+
if (attempt === maxAttempts - 1) return resp;
|
|
23
|
+
await resp.text().catch(() => void 0);
|
|
24
|
+
const waitMs = computeWait(attempt, initial, cap, resp.headers.get("Retry-After"));
|
|
25
|
+
opts.onRetry?.({ attempt: attempt + 1, reason: `http ${resp.status}`, waitMs });
|
|
26
|
+
await sleep(waitMs, opts.signal);
|
|
27
|
+
} catch (err) {
|
|
28
|
+
lastError = err;
|
|
29
|
+
if (isAbortError(err) || opts.signal?.aborted) throw err;
|
|
30
|
+
if (attempt === maxAttempts - 1) throw err;
|
|
31
|
+
const waitMs = computeWait(attempt, initial, cap, null);
|
|
32
|
+
opts.onRetry?.({
|
|
33
|
+
attempt: attempt + 1,
|
|
34
|
+
reason: `network: ${messageOf(err)}`,
|
|
35
|
+
waitMs
|
|
36
|
+
});
|
|
37
|
+
await sleep(waitMs, opts.signal);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
throw lastError ?? new Error("fetchWithRetry: loop exited unexpectedly");
|
|
41
|
+
}
|
|
42
|
+
function computeWait(attempt, initial, cap, retryAfter) {
|
|
43
|
+
if (retryAfter) {
|
|
44
|
+
const seconds = Number.parseFloat(retryAfter);
|
|
45
|
+
if (Number.isFinite(seconds) && seconds > 0) {
|
|
46
|
+
return Math.min(seconds * 1e3, cap);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const exp = initial * 2 ** attempt;
|
|
50
|
+
const jitter = exp * (0.75 + Math.random() * 0.5);
|
|
51
|
+
return Math.min(Math.max(jitter, 0), cap);
|
|
52
|
+
}
|
|
53
|
+
function sleep(ms, signal) {
|
|
54
|
+
if (ms <= 0) return Promise.resolve();
|
|
55
|
+
return new Promise((resolve2, reject) => {
|
|
56
|
+
const timer = setTimeout(resolve2, ms);
|
|
57
|
+
if (signal) {
|
|
58
|
+
const onAbort = () => {
|
|
59
|
+
clearTimeout(timer);
|
|
60
|
+
reject(new Error("aborted"));
|
|
61
|
+
};
|
|
62
|
+
if (signal.aborted) onAbort();
|
|
63
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
function isAbortError(err) {
|
|
68
|
+
if (!err || typeof err !== "object") return false;
|
|
69
|
+
const name = err.name;
|
|
70
|
+
return name === "AbortError";
|
|
71
|
+
}
|
|
72
|
+
function messageOf(err) {
|
|
73
|
+
if (err instanceof Error) return err.message;
|
|
74
|
+
try {
|
|
75
|
+
return String(err);
|
|
76
|
+
} catch {
|
|
77
|
+
return "unknown error";
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/client.ts
|
|
82
|
+
var Usage = class _Usage {
|
|
83
|
+
constructor(promptTokens = 0, completionTokens = 0, totalTokens = 0, promptCacheHitTokens = 0, promptCacheMissTokens = 0) {
|
|
84
|
+
this.promptTokens = promptTokens;
|
|
85
|
+
this.completionTokens = completionTokens;
|
|
86
|
+
this.totalTokens = totalTokens;
|
|
87
|
+
this.promptCacheHitTokens = promptCacheHitTokens;
|
|
88
|
+
this.promptCacheMissTokens = promptCacheMissTokens;
|
|
89
|
+
}
|
|
90
|
+
promptTokens;
|
|
91
|
+
completionTokens;
|
|
92
|
+
totalTokens;
|
|
93
|
+
promptCacheHitTokens;
|
|
94
|
+
promptCacheMissTokens;
|
|
95
|
+
get cacheHitRatio() {
|
|
96
|
+
const denom = this.promptCacheHitTokens + this.promptCacheMissTokens;
|
|
97
|
+
return denom > 0 ? this.promptCacheHitTokens / denom : 0;
|
|
98
|
+
}
|
|
99
|
+
static fromApi(raw) {
|
|
100
|
+
const u = raw ?? {};
|
|
101
|
+
return new _Usage(
|
|
102
|
+
u.prompt_tokens ?? 0,
|
|
103
|
+
u.completion_tokens ?? 0,
|
|
104
|
+
u.total_tokens ?? 0,
|
|
105
|
+
u.prompt_cache_hit_tokens ?? 0,
|
|
106
|
+
u.prompt_cache_miss_tokens ?? 0
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
var DeepSeekClient = class {
|
|
111
|
+
apiKey;
|
|
112
|
+
baseUrl;
|
|
113
|
+
timeoutMs;
|
|
114
|
+
retry;
|
|
115
|
+
_fetch;
|
|
116
|
+
constructor(opts = {}) {
|
|
117
|
+
const apiKey = opts.apiKey ?? process.env.DEEPSEEK_API_KEY;
|
|
118
|
+
if (!apiKey) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
"DEEPSEEK_API_KEY is not set. Put it in .env or pass apiKey to DeepSeekClient."
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
this.apiKey = apiKey;
|
|
124
|
+
this.baseUrl = (opts.baseUrl ?? process.env.DEEPSEEK_BASE_URL ?? "https://api.deepseek.com").replace(/\/+$/, "");
|
|
125
|
+
this.timeoutMs = opts.timeoutMs ?? 12e4;
|
|
126
|
+
this._fetch = opts.fetch ?? globalThis.fetch.bind(globalThis);
|
|
127
|
+
this.retry = opts.retry ?? {};
|
|
128
|
+
}
|
|
129
|
+
buildPayload(opts, stream) {
|
|
130
|
+
const payload = {
|
|
131
|
+
model: opts.model,
|
|
132
|
+
messages: opts.messages,
|
|
133
|
+
stream
|
|
134
|
+
};
|
|
135
|
+
if (opts.tools?.length) payload.tools = opts.tools;
|
|
136
|
+
if (opts.temperature !== void 0) payload.temperature = opts.temperature;
|
|
137
|
+
if (opts.maxTokens !== void 0) payload.max_tokens = opts.maxTokens;
|
|
138
|
+
if (opts.responseFormat) payload.response_format = opts.responseFormat;
|
|
139
|
+
return payload;
|
|
140
|
+
}
|
|
141
|
+
async chat(opts) {
|
|
142
|
+
const ctrl = new AbortController();
|
|
143
|
+
const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
|
|
144
|
+
const signal = opts.signal ?? ctrl.signal;
|
|
145
|
+
try {
|
|
146
|
+
const resp = await fetchWithRetry(
|
|
147
|
+
this._fetch,
|
|
148
|
+
`${this.baseUrl}/chat/completions`,
|
|
149
|
+
{
|
|
150
|
+
method: "POST",
|
|
151
|
+
headers: {
|
|
152
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
153
|
+
"Content-Type": "application/json"
|
|
154
|
+
},
|
|
155
|
+
body: JSON.stringify(this.buildPayload(opts, false)),
|
|
156
|
+
signal
|
|
157
|
+
},
|
|
158
|
+
{ ...this.retry, signal }
|
|
159
|
+
);
|
|
160
|
+
if (!resp.ok) {
|
|
161
|
+
throw new Error(`DeepSeek ${resp.status}: ${await resp.text()}`);
|
|
162
|
+
}
|
|
163
|
+
const data = await resp.json();
|
|
164
|
+
const choice = data.choices?.[0]?.message ?? {};
|
|
165
|
+
return {
|
|
166
|
+
content: choice.content ?? "",
|
|
167
|
+
reasoningContent: choice.reasoning_content ?? null,
|
|
168
|
+
toolCalls: choice.tool_calls ?? [],
|
|
169
|
+
usage: Usage.fromApi(data.usage),
|
|
170
|
+
raw: data
|
|
171
|
+
};
|
|
172
|
+
} finally {
|
|
173
|
+
clearTimeout(timer);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
async *stream(opts) {
|
|
177
|
+
const ctrl = new AbortController();
|
|
178
|
+
const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
|
|
179
|
+
const signal = opts.signal ?? ctrl.signal;
|
|
180
|
+
let resp;
|
|
181
|
+
try {
|
|
182
|
+
resp = await fetchWithRetry(
|
|
183
|
+
this._fetch,
|
|
184
|
+
`${this.baseUrl}/chat/completions`,
|
|
185
|
+
{
|
|
186
|
+
method: "POST",
|
|
187
|
+
headers: {
|
|
188
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
189
|
+
"Content-Type": "application/json",
|
|
190
|
+
Accept: "text/event-stream"
|
|
191
|
+
},
|
|
192
|
+
body: JSON.stringify(this.buildPayload(opts, true)),
|
|
193
|
+
signal
|
|
194
|
+
},
|
|
195
|
+
{ ...this.retry, signal }
|
|
196
|
+
);
|
|
197
|
+
} catch (err) {
|
|
198
|
+
clearTimeout(timer);
|
|
199
|
+
throw err;
|
|
200
|
+
}
|
|
201
|
+
if (!resp.ok || !resp.body) {
|
|
202
|
+
clearTimeout(timer);
|
|
203
|
+
throw new Error(`DeepSeek ${resp.status}: ${await resp.text().catch(() => "")}`);
|
|
204
|
+
}
|
|
205
|
+
const queue = [];
|
|
206
|
+
let done = false;
|
|
207
|
+
const parser = createParser({
|
|
208
|
+
onEvent: (ev) => {
|
|
209
|
+
if (!ev.data || ev.data === "[DONE]") {
|
|
210
|
+
done = true;
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
const json = JSON.parse(ev.data);
|
|
215
|
+
const delta = json.choices?.[0]?.delta ?? {};
|
|
216
|
+
const finishReason = json.choices?.[0]?.finish_reason ?? void 0;
|
|
217
|
+
const chunk = { raw: json, finishReason };
|
|
218
|
+
if (typeof delta.content === "string" && delta.content.length > 0) {
|
|
219
|
+
chunk.contentDelta = delta.content;
|
|
220
|
+
}
|
|
221
|
+
if (typeof delta.reasoning_content === "string" && delta.reasoning_content.length > 0) {
|
|
222
|
+
chunk.reasoningDelta = delta.reasoning_content;
|
|
223
|
+
}
|
|
224
|
+
if (Array.isArray(delta.tool_calls) && delta.tool_calls.length > 0) {
|
|
225
|
+
const tc = delta.tool_calls[0];
|
|
226
|
+
chunk.toolCallDelta = {
|
|
227
|
+
index: tc.index ?? 0,
|
|
228
|
+
id: tc.id,
|
|
229
|
+
name: tc.function?.name,
|
|
230
|
+
argumentsDelta: tc.function?.arguments
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
if (json.usage) {
|
|
234
|
+
chunk.usage = Usage.fromApi(json.usage);
|
|
235
|
+
}
|
|
236
|
+
queue.push(chunk);
|
|
237
|
+
} catch {
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
const reader = resp.body.getReader();
|
|
242
|
+
const decoder = new TextDecoder();
|
|
243
|
+
try {
|
|
244
|
+
while (true) {
|
|
245
|
+
if (queue.length > 0) {
|
|
246
|
+
yield queue.shift();
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (done) break;
|
|
250
|
+
const { value, done: streamDone } = await reader.read();
|
|
251
|
+
if (streamDone) break;
|
|
252
|
+
parser.feed(decoder.decode(value, { stream: true }));
|
|
253
|
+
}
|
|
254
|
+
while (queue.length > 0) yield queue.shift();
|
|
255
|
+
} finally {
|
|
256
|
+
clearTimeout(timer);
|
|
257
|
+
reader.releaseLock();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
9
262
|
// src/harvest.ts
|
|
10
263
|
function emptyPlanState() {
|
|
11
264
|
return { subgoals: [], hypotheses: [], uncertainties: [], rejectedPaths: [] };
|
|
@@ -96,6 +349,66 @@ function sanitizeArray(raw, maxItems, maxItemLen) {
|
|
|
96
349
|
return out;
|
|
97
350
|
}
|
|
98
351
|
|
|
352
|
+
// src/consistency.ts
|
|
353
|
+
var defaultSelector = (samples) => {
|
|
354
|
+
if (samples.length === 0) throw new Error("defaultSelector: samples is empty");
|
|
355
|
+
return samples.slice().sort((a, b) => {
|
|
356
|
+
const uDiff = a.planState.uncertainties.length - b.planState.uncertainties.length;
|
|
357
|
+
if (uDiff !== 0) return uDiff;
|
|
358
|
+
const aLen = a.response.content?.length ?? 0;
|
|
359
|
+
const bLen = b.response.content?.length ?? 0;
|
|
360
|
+
return aLen - bLen;
|
|
361
|
+
})[0];
|
|
362
|
+
};
|
|
363
|
+
async function runBranches(client, request, opts = {}) {
|
|
364
|
+
const budget = Math.max(1, opts.budget ?? 1);
|
|
365
|
+
const temperatures = resolveTemperatures(budget, opts.temperatures);
|
|
366
|
+
const selector = opts.selector ?? defaultSelector;
|
|
367
|
+
const samples = await Promise.all(
|
|
368
|
+
temperatures.map(async (temperature, index) => {
|
|
369
|
+
const response = await client.chat({ ...request, temperature });
|
|
370
|
+
const planState = await harvest(response.reasoningContent, client, opts.harvestOptions);
|
|
371
|
+
const sample = { index, temperature, response, planState };
|
|
372
|
+
try {
|
|
373
|
+
opts.onSampleDone?.(sample);
|
|
374
|
+
} catch {
|
|
375
|
+
}
|
|
376
|
+
return sample;
|
|
377
|
+
})
|
|
378
|
+
);
|
|
379
|
+
return { chosen: selector(samples), samples };
|
|
380
|
+
}
|
|
381
|
+
function aggregateBranchUsage(samples) {
|
|
382
|
+
let promptTokens = 0;
|
|
383
|
+
let completionTokens = 0;
|
|
384
|
+
let totalTokens = 0;
|
|
385
|
+
let promptCacheHitTokens = 0;
|
|
386
|
+
let promptCacheMissTokens = 0;
|
|
387
|
+
for (const s of samples) {
|
|
388
|
+
promptTokens += s.response.usage.promptTokens;
|
|
389
|
+
completionTokens += s.response.usage.completionTokens;
|
|
390
|
+
totalTokens += s.response.usage.totalTokens;
|
|
391
|
+
promptCacheHitTokens += s.response.usage.promptCacheHitTokens;
|
|
392
|
+
promptCacheMissTokens += s.response.usage.promptCacheMissTokens;
|
|
393
|
+
}
|
|
394
|
+
return {
|
|
395
|
+
promptTokens,
|
|
396
|
+
completionTokens,
|
|
397
|
+
totalTokens,
|
|
398
|
+
promptCacheHitTokens,
|
|
399
|
+
promptCacheMissTokens
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
function resolveTemperatures(budget, custom) {
|
|
403
|
+
if (custom && custom.length >= budget) return [...custom.slice(0, budget)];
|
|
404
|
+
if (budget === 1) return [0];
|
|
405
|
+
const out = [];
|
|
406
|
+
for (let i = 0; i < budget; i++) {
|
|
407
|
+
out.push(Number((i / (budget - 1)).toFixed(2)));
|
|
408
|
+
}
|
|
409
|
+
return out;
|
|
410
|
+
}
|
|
411
|
+
|
|
99
412
|
// src/memory.ts
|
|
100
413
|
import { createHash } from "crypto";
|
|
101
414
|
var ImmutablePrefix = class {
|
|
@@ -613,28 +926,73 @@ var CacheFirstLoop = class {
|
|
|
613
926
|
client;
|
|
614
927
|
prefix;
|
|
615
928
|
tools;
|
|
616
|
-
model;
|
|
617
929
|
maxToolIters;
|
|
618
|
-
stream;
|
|
619
|
-
harvestEnabled;
|
|
620
|
-
harvestOptions;
|
|
621
930
|
log = new AppendOnlyLog();
|
|
622
931
|
scratch = new VolatileScratch();
|
|
623
932
|
stats = new SessionStats();
|
|
624
933
|
repair;
|
|
934
|
+
// Mutable via configure() — slash commands in the TUI / library callers tweak
|
|
935
|
+
// these mid-session so users don't have to restart to try harvest or branch.
|
|
936
|
+
model;
|
|
937
|
+
stream;
|
|
938
|
+
harvestEnabled;
|
|
939
|
+
harvestOptions;
|
|
940
|
+
branchEnabled;
|
|
941
|
+
branchOptions;
|
|
625
942
|
_turn = 0;
|
|
943
|
+
_streamPreference;
|
|
626
944
|
constructor(opts) {
|
|
627
945
|
this.client = opts.client;
|
|
628
946
|
this.prefix = opts.prefix;
|
|
629
947
|
this.tools = opts.tools ?? new ToolRegistry();
|
|
630
948
|
this.model = opts.model ?? "deepseek-chat";
|
|
631
949
|
this.maxToolIters = opts.maxToolIters ?? 8;
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
950
|
+
if (typeof opts.branch === "number") {
|
|
951
|
+
this.branchOptions = { budget: opts.branch };
|
|
952
|
+
} else if (opts.branch && typeof opts.branch === "object") {
|
|
953
|
+
this.branchOptions = opts.branch;
|
|
954
|
+
} else {
|
|
955
|
+
this.branchOptions = {};
|
|
956
|
+
}
|
|
957
|
+
this.branchEnabled = (this.branchOptions.budget ?? 1) > 1;
|
|
958
|
+
const harvestForced = this.branchEnabled;
|
|
959
|
+
this.harvestEnabled = harvestForced || opts.harvest === true || typeof opts.harvest === "object" && opts.harvest !== null;
|
|
960
|
+
this.harvestOptions = typeof opts.harvest === "object" && opts.harvest !== null ? opts.harvest : this.branchOptions.harvestOptions ?? {};
|
|
961
|
+
this._streamPreference = opts.stream ?? true;
|
|
962
|
+
this.stream = this.branchEnabled ? false : this._streamPreference;
|
|
635
963
|
const allowedNames = /* @__PURE__ */ new Set([...this.prefix.toolSpecs.map((s) => s.function.name)]);
|
|
636
964
|
this.repair = new ToolCallRepair({ allowedToolNames: allowedNames });
|
|
637
965
|
}
|
|
966
|
+
/**
|
|
967
|
+
* Reconfigure model/harvest/branch/stream mid-session. The loop's log,
|
|
968
|
+
* scratch, and stats are preserved — only the per-turn behavior changes.
|
|
969
|
+
* Used by the TUI's slash commands and by library callers who want to
|
|
970
|
+
* flip a knob between turns.
|
|
971
|
+
*/
|
|
972
|
+
configure(opts) {
|
|
973
|
+
if (opts.model !== void 0) this.model = opts.model;
|
|
974
|
+
if (opts.stream !== void 0) this._streamPreference = opts.stream;
|
|
975
|
+
if (opts.branch !== void 0) {
|
|
976
|
+
if (typeof opts.branch === "number") {
|
|
977
|
+
this.branchOptions = { budget: opts.branch };
|
|
978
|
+
} else if (opts.branch && typeof opts.branch === "object") {
|
|
979
|
+
this.branchOptions = opts.branch;
|
|
980
|
+
} else {
|
|
981
|
+
this.branchOptions = {};
|
|
982
|
+
}
|
|
983
|
+
this.branchEnabled = (this.branchOptions.budget ?? 1) > 1;
|
|
984
|
+
}
|
|
985
|
+
if (opts.harvest !== void 0) {
|
|
986
|
+
const want = opts.harvest === true || typeof opts.harvest === "object" && opts.harvest !== null;
|
|
987
|
+
this.harvestEnabled = want || this.branchEnabled;
|
|
988
|
+
if (typeof opts.harvest === "object" && opts.harvest !== null) {
|
|
989
|
+
this.harvestOptions = opts.harvest;
|
|
990
|
+
}
|
|
991
|
+
} else if (this.branchEnabled) {
|
|
992
|
+
this.harvestEnabled = true;
|
|
993
|
+
}
|
|
994
|
+
this.stream = this.branchEnabled ? false : this._streamPreference;
|
|
995
|
+
}
|
|
638
996
|
buildMessages(pendingUser) {
|
|
639
997
|
const msgs = [...this.prefix.toMessages(), ...this.log.toMessages()];
|
|
640
998
|
if (pendingUser !== null) msgs.push({ role: "user", content: pendingUser });
|
|
@@ -651,8 +1009,85 @@ var CacheFirstLoop = class {
|
|
|
651
1009
|
let reasoningContent = "";
|
|
652
1010
|
let toolCalls = [];
|
|
653
1011
|
let usage = null;
|
|
1012
|
+
let branchSummary;
|
|
1013
|
+
let preHarvestedPlanState;
|
|
654
1014
|
try {
|
|
655
|
-
if (this.
|
|
1015
|
+
if (this.branchEnabled) {
|
|
1016
|
+
const budget = this.branchOptions.budget ?? 1;
|
|
1017
|
+
yield {
|
|
1018
|
+
turn: this._turn,
|
|
1019
|
+
role: "branch_start",
|
|
1020
|
+
content: "",
|
|
1021
|
+
branchProgress: {
|
|
1022
|
+
completed: 0,
|
|
1023
|
+
total: budget,
|
|
1024
|
+
latestIndex: -1,
|
|
1025
|
+
latestTemperature: -1,
|
|
1026
|
+
latestUncertainties: -1
|
|
1027
|
+
}
|
|
1028
|
+
};
|
|
1029
|
+
const queue = [];
|
|
1030
|
+
let waiter = null;
|
|
1031
|
+
const onSampleDone = (sample) => {
|
|
1032
|
+
if (waiter) {
|
|
1033
|
+
const w = waiter;
|
|
1034
|
+
waiter = null;
|
|
1035
|
+
w(sample);
|
|
1036
|
+
} else {
|
|
1037
|
+
queue.push(sample);
|
|
1038
|
+
}
|
|
1039
|
+
};
|
|
1040
|
+
const branchPromise = runBranches(
|
|
1041
|
+
this.client,
|
|
1042
|
+
{
|
|
1043
|
+
model: this.model,
|
|
1044
|
+
messages,
|
|
1045
|
+
tools: toolSpecs.length ? toolSpecs : void 0
|
|
1046
|
+
},
|
|
1047
|
+
{
|
|
1048
|
+
...this.branchOptions,
|
|
1049
|
+
harvestOptions: this.harvestOptions,
|
|
1050
|
+
onSampleDone
|
|
1051
|
+
}
|
|
1052
|
+
);
|
|
1053
|
+
for (let k = 0; k < budget; k++) {
|
|
1054
|
+
const sample = queue.shift() ?? await new Promise((resolve2) => {
|
|
1055
|
+
waiter = resolve2;
|
|
1056
|
+
});
|
|
1057
|
+
yield {
|
|
1058
|
+
turn: this._turn,
|
|
1059
|
+
role: "branch_progress",
|
|
1060
|
+
content: "",
|
|
1061
|
+
branchProgress: {
|
|
1062
|
+
completed: k + 1,
|
|
1063
|
+
total: budget,
|
|
1064
|
+
latestIndex: sample.index,
|
|
1065
|
+
latestTemperature: sample.temperature,
|
|
1066
|
+
latestUncertainties: sample.planState.uncertainties.length
|
|
1067
|
+
}
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
const result = await branchPromise;
|
|
1071
|
+
assistantContent = result.chosen.response.content;
|
|
1072
|
+
reasoningContent = result.chosen.response.reasoningContent ?? "";
|
|
1073
|
+
toolCalls = result.chosen.response.toolCalls;
|
|
1074
|
+
const agg = aggregateBranchUsage(result.samples);
|
|
1075
|
+
usage = new Usage(
|
|
1076
|
+
agg.promptTokens,
|
|
1077
|
+
agg.completionTokens,
|
|
1078
|
+
agg.totalTokens,
|
|
1079
|
+
agg.promptCacheHitTokens,
|
|
1080
|
+
agg.promptCacheMissTokens
|
|
1081
|
+
);
|
|
1082
|
+
preHarvestedPlanState = result.chosen.planState;
|
|
1083
|
+
branchSummary = summarizeBranch(result.chosen, result.samples);
|
|
1084
|
+
yield {
|
|
1085
|
+
turn: this._turn,
|
|
1086
|
+
role: "branch_done",
|
|
1087
|
+
content: "",
|
|
1088
|
+
branch: branchSummary
|
|
1089
|
+
};
|
|
1090
|
+
} else if (this.stream) {
|
|
656
1091
|
const callBuf = /* @__PURE__ */ new Map();
|
|
657
1092
|
for await (const chunk of this.client.stream({
|
|
658
1093
|
model: this.model,
|
|
@@ -712,17 +1147,13 @@ var CacheFirstLoop = class {
|
|
|
712
1147
|
};
|
|
713
1148
|
return;
|
|
714
1149
|
}
|
|
715
|
-
const turnStats = this.stats.record(
|
|
716
|
-
this._turn,
|
|
717
|
-
this.model,
|
|
718
|
-
usage ?? new (await import("./client-RIVGDOJP.js")).Usage()
|
|
719
|
-
);
|
|
1150
|
+
const turnStats = this.stats.record(this._turn, this.model, usage ?? new Usage());
|
|
720
1151
|
if (pendingUser !== null) {
|
|
721
1152
|
this.log.append({ role: "user", content: pendingUser });
|
|
722
1153
|
pendingUser = null;
|
|
723
1154
|
}
|
|
724
1155
|
this.scratch.reasoning = reasoningContent || null;
|
|
725
|
-
const planState = this.harvestEnabled ? await harvest(reasoningContent || null, this.client, this.harvestOptions) : emptyPlanState();
|
|
1156
|
+
const planState = preHarvestedPlanState ? preHarvestedPlanState : this.harvestEnabled ? await harvest(reasoningContent || null, this.client, this.harvestOptions) : emptyPlanState();
|
|
726
1157
|
const { calls: repairedCalls, report } = this.repair.process(
|
|
727
1158
|
toolCalls,
|
|
728
1159
|
reasoningContent || null
|
|
@@ -734,7 +1165,8 @@ var CacheFirstLoop = class {
|
|
|
734
1165
|
content: assistantContent,
|
|
735
1166
|
stats: turnStats,
|
|
736
1167
|
planState,
|
|
737
|
-
repair: report
|
|
1168
|
+
repair: report,
|
|
1169
|
+
branch: branchSummary
|
|
738
1170
|
};
|
|
739
1171
|
if (repairedCalls.length === 0) {
|
|
740
1172
|
yield { turn: this._turn, role: "done", content: assistantContent };
|
|
@@ -770,6 +1202,14 @@ var CacheFirstLoop = class {
|
|
|
770
1202
|
return msg;
|
|
771
1203
|
}
|
|
772
1204
|
};
|
|
1205
|
+
function summarizeBranch(chosen, samples) {
|
|
1206
|
+
return {
|
|
1207
|
+
budget: samples.length,
|
|
1208
|
+
chosenIndex: chosen.index,
|
|
1209
|
+
uncertainties: samples.map((s) => s.planState.uncertainties.length),
|
|
1210
|
+
temperatures: samples.map((s) => s.temperature)
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
773
1213
|
|
|
774
1214
|
// src/env.ts
|
|
775
1215
|
import { readFileSync } from "fs";
|
|
@@ -843,22 +1283,67 @@ var VERSION = "0.0.1";
|
|
|
843
1283
|
|
|
844
1284
|
// src/cli/commands/chat.tsx
|
|
845
1285
|
import { render } from "ink";
|
|
846
|
-
import React7, { useState as
|
|
1286
|
+
import React7, { useState as useState4 } from "react";
|
|
847
1287
|
|
|
848
1288
|
// src/cli/ui/App.tsx
|
|
849
1289
|
import { createWriteStream } from "fs";
|
|
850
|
-
import { Box as Box5, Static, useApp } from "ink";
|
|
851
|
-
import React5, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
1290
|
+
import { Box as Box5, Static, Text as Text5, useApp } from "ink";
|
|
1291
|
+
import React5, { useCallback, useEffect as useEffect2, useMemo, useRef, useState as useState2 } from "react";
|
|
852
1292
|
|
|
853
1293
|
// src/cli/ui/EventLog.tsx
|
|
854
1294
|
import { Box as Box2, Text as Text2 } from "ink";
|
|
855
|
-
import React2 from "react";
|
|
1295
|
+
import React2, { useEffect, useState } from "react";
|
|
856
1296
|
|
|
857
1297
|
// src/cli/ui/markdown.tsx
|
|
858
1298
|
import { Box, Text } from "ink";
|
|
859
1299
|
import React from "react";
|
|
1300
|
+
var SUPERSCRIPT = {
|
|
1301
|
+
"0": "\u2070",
|
|
1302
|
+
"1": "\xB9",
|
|
1303
|
+
"2": "\xB2",
|
|
1304
|
+
"3": "\xB3",
|
|
1305
|
+
"4": "\u2074",
|
|
1306
|
+
"5": "\u2075",
|
|
1307
|
+
"6": "\u2076",
|
|
1308
|
+
"7": "\u2077",
|
|
1309
|
+
"8": "\u2078",
|
|
1310
|
+
"9": "\u2079",
|
|
1311
|
+
"+": "\u207A",
|
|
1312
|
+
"-": "\u207B",
|
|
1313
|
+
n: "\u207F"
|
|
1314
|
+
};
|
|
1315
|
+
var SUBSCRIPT = {
|
|
1316
|
+
"0": "\u2080",
|
|
1317
|
+
"1": "\u2081",
|
|
1318
|
+
"2": "\u2082",
|
|
1319
|
+
"3": "\u2083",
|
|
1320
|
+
"4": "\u2084",
|
|
1321
|
+
"5": "\u2085",
|
|
1322
|
+
"6": "\u2086",
|
|
1323
|
+
"7": "\u2087",
|
|
1324
|
+
"8": "\u2088",
|
|
1325
|
+
"9": "\u2089",
|
|
1326
|
+
"+": "\u208A",
|
|
1327
|
+
"-": "\u208B"
|
|
1328
|
+
};
|
|
1329
|
+
function toSuperscript(s) {
|
|
1330
|
+
let out = "";
|
|
1331
|
+
for (const c of s) out += SUPERSCRIPT[c] ?? c;
|
|
1332
|
+
return out;
|
|
1333
|
+
}
|
|
1334
|
+
function toSubscript(s) {
|
|
1335
|
+
let out = "";
|
|
1336
|
+
for (const c of s) out += SUBSCRIPT[c] ?? c;
|
|
1337
|
+
return out;
|
|
1338
|
+
}
|
|
860
1339
|
function stripMath(s) {
|
|
861
|
-
return s.replace(/\\\(\s*/g, "").replace(/\s*\\\)/g, "").replace(/\\\[\s*/g, "\n").replace(/\s*\\\]/g, "\n").replace(
|
|
1340
|
+
return s.replace(/\\\(\s*/g, "").replace(/\s*\\\)/g, "").replace(/\\\[\s*/g, "\n").replace(/\s*\\\]/g, "\n").replace(
|
|
1341
|
+
/\\[dt]?frac\s*\{((?:[^{}]|\{[^{}]*\})+)\}\s*\{((?:[^{}]|\{[^{}]*\})+)\}/g,
|
|
1342
|
+
(_m, num, den) => `(${num.trim()})/(${den.trim()})`
|
|
1343
|
+
).replace(
|
|
1344
|
+
/\\binom\s*\{([^{}]+)\}\s*\{([^{}]+)\}/g,
|
|
1345
|
+
(_m, n, k) => `C(${n.trim()},${k.trim()})`
|
|
1346
|
+
).replace(/\\sqrt\s*\{([^{}]+)\}/g, (_m, g) => `\u221A(${g.trim()})`).replace(/\\boxed\s*\{([^{}]+)\}/g, (_m, g) => `\u3010${g.trim()}\u3011`).replace(/\\text\s*\{([^{}]+)\}/g, (_m, g) => g.trim()).replace(/\\overline\s*\{([^{}]+)\}/g, (_m, g) => `${g.trim()}\u0304`).replace(/\\hat\s*\{([^{}]+)\}/g, (_m, g) => `${g.trim()}\u0302`).replace(/\\vec\s*\{([^{}]+)\}/g, (_m, g) => `\u2192${g.trim()}`).replace(/\\cdot/g, "\xB7").replace(/\\times/g, "\xD7").replace(/\\div/g, "\xF7").replace(/\\pm/g, "\xB1").replace(/\\mp/g, "\u2213").replace(/\\leq/g, "\u2264").replace(/\\geq/g, "\u2265").replace(/\\neq/g, "\u2260").replace(/\\approx/g, "\u2248").replace(/\\in\b/g, "\u2208").replace(/\\notin\b/g, "\u2209").replace(/\\infty/g, "\u221E").replace(/\\sum\b/g, "\u03A3").replace(/\\prod\b/g, "\u03A0").replace(/\\int\b/g, "\u222B").replace(/\\alpha/g, "\u03B1").replace(/\\beta/g, "\u03B2").replace(/\\gamma/g, "\u03B3").replace(/\\delta/g, "\u03B4").replace(/\\theta/g, "\u03B8").replace(/\\lambda/g, "\u03BB").replace(/\\mu/g, "\u03BC").replace(/\\pi/g, "\u03C0").replace(/\\sigma/g, "\u03C3").replace(/\\phi/g, "\u03C6").replace(/\\omega/g, "\u03C9").replace(/\\implies\b/g, "\u21D2").replace(/\\iff\b/g, "\u21D4").replace(/\\to\b/g, "\u2192").replace(/\\rightarrow/g, "\u2192").replace(/\\Rightarrow/g, "\u21D2").replace(/\\leftarrow/g, "\u2190").replace(/\\Leftarrow/g, "\u21D0").replace(/\\ldots/g, "\u2026").replace(/\\cdots/g, "\u22EF").replace(/\\quad/g, " ").replace(/\\qquad/g, " ").replace(/\\,/g, " ").replace(/\\;/g, " ").replace(/\\!/g, "").replace(/\\\\/g, "\n").replace(/\^\{([\w+-]+)\}/g, (_m, g) => toSuperscript(g)).replace(/\^([0-9+\-n])/g, (_m, g) => toSuperscript(g)).replace(/_\{([\w+-]+)\}/g, (_m, g) => toSubscript(g)).replace(/_([0-9+\-])/g, (_m, g) => toSubscript(g)).replace(/\\[a-zA-Z]+\s*\{([^{}]+)\}\s*\{([^{}]+)\}/g, "($1)/($2)").replace(/\\[a-zA-Z]+\s*\{([^{}]+)\}/g, "$1").replace(/\\[a-zA-Z]+/g, "").replace(/[ \t]{2,}/g, " ");
|
|
862
1347
|
}
|
|
863
1348
|
var INLINE_RE = /(\*\*([^*\n]+?)\*\*|`([^`\n]+?)`|(?<![*\w])\*([^*\n]+?)\*(?!\w))/g;
|
|
864
1349
|
function InlineMd({ text }) {
|
|
@@ -1006,7 +1491,7 @@ var EventRow = React2.memo(function EventRow2({ event }) {
|
|
|
1006
1491
|
}
|
|
1007
1492
|
if (event.role === "assistant") {
|
|
1008
1493
|
if (event.streaming) return /* @__PURE__ */ React2.createElement(StreamingAssistant, { event });
|
|
1009
|
-
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "green" }, "assistant")), event.reasoning ? /* @__PURE__ */ React2.createElement(ReasoningBlock, { reasoning: event.reasoning }) : null, !isPlanStateEmpty(event.planState) ? /* @__PURE__ */ React2.createElement(PlanStateBlock, { planState: event.planState }) : null, event.text ? /* @__PURE__ */ React2.createElement(Markdown, { text: event.text }) : /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "(no content)"), event.stats ? /* @__PURE__ */ React2.createElement(StatsLine, { stats: event.stats }) : null, event.repair ? /* @__PURE__ */ React2.createElement(Text2, { color: "magenta" }, event.repair) : null);
|
|
1494
|
+
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "green" }, "assistant")), event.branch ? /* @__PURE__ */ React2.createElement(BranchBlock, { branch: event.branch }) : null, event.reasoning ? /* @__PURE__ */ React2.createElement(ReasoningBlock, { reasoning: event.reasoning }) : null, !isPlanStateEmpty(event.planState) ? /* @__PURE__ */ React2.createElement(PlanStateBlock, { planState: event.planState }) : null, event.text ? /* @__PURE__ */ React2.createElement(Markdown, { text: event.text }) : /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "(no content)"), event.stats ? /* @__PURE__ */ React2.createElement(StatsLine, { stats: event.stats }) : null, event.repair ? /* @__PURE__ */ React2.createElement(Text2, { color: "magenta" }, event.repair) : null);
|
|
1010
1495
|
}
|
|
1011
1496
|
if (event.role === "tool") {
|
|
1012
1497
|
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React2.createElement(Text2, { color: "yellow" }, `tool<${event.toolName ?? "?"}> \u2192`), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, " ", truncate(event.text, 400)));
|
|
@@ -1019,6 +1504,14 @@ var EventRow = React2.memo(function EventRow2({ event }) {
|
|
|
1019
1504
|
}
|
|
1020
1505
|
return /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, null, event.text));
|
|
1021
1506
|
});
|
|
1507
|
+
function BranchBlock({ branch }) {
|
|
1508
|
+
const per = branch.uncertainties.map((u, i) => {
|
|
1509
|
+
const marker = i === branch.chosenIndex ? "\u25B8" : " ";
|
|
1510
|
+
const t = (branch.temperatures[i] ?? 0).toFixed(1);
|
|
1511
|
+
return `${marker} #${i} T=${t} u=${u}`;
|
|
1512
|
+
}).join(" ");
|
|
1513
|
+
return /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, { color: "blue" }, "\u{1F500} branched ", /* @__PURE__ */ React2.createElement(Text2, { bold: true }, branch.budget), ` samples \u2192 picked #${branch.chosenIndex} `, /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, per)));
|
|
1514
|
+
}
|
|
1022
1515
|
function PlanStateBlock({ planState }) {
|
|
1023
1516
|
const lines = [];
|
|
1024
1517
|
if (planState.subgoals.length) lines.push(["subgoals", planState.subgoals]);
|
|
@@ -1033,10 +1526,29 @@ function ReasoningBlock({ reasoning }) {
|
|
|
1033
1526
|
const preview = flat.length <= max ? flat : `${flat.slice(0, max)}\u2026 (+${flat.length - max} chars)`;
|
|
1034
1527
|
return /* @__PURE__ */ React2.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React2.createElement(Text2, { dimColor: true, italic: true }, "\u21B3 thinking: ", preview));
|
|
1035
1528
|
}
|
|
1529
|
+
function Elapsed() {
|
|
1530
|
+
const [s, setS] = useState(0);
|
|
1531
|
+
useEffect(() => {
|
|
1532
|
+
const start = Date.now();
|
|
1533
|
+
const id = setInterval(() => setS(Math.floor((Date.now() - start) / 1e3)), 1e3);
|
|
1534
|
+
return () => clearInterval(id);
|
|
1535
|
+
}, []);
|
|
1536
|
+
const mm = String(Math.floor(s / 60)).padStart(2, "0");
|
|
1537
|
+
const ss = String(s % 60).padStart(2, "0");
|
|
1538
|
+
return /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, `${mm}:${ss}`);
|
|
1539
|
+
}
|
|
1036
1540
|
function StreamingAssistant({ event }) {
|
|
1541
|
+
if (event.branchProgress) {
|
|
1542
|
+
const p = event.branchProgress;
|
|
1543
|
+
if (p.completed === 0) {
|
|
1544
|
+
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "green" }, "assistant", " "), /* @__PURE__ */ React2.createElement(Text2, { color: "blue" }, "\u{1F500} launching ", p.total, " parallel samples (R1 thinking in parallel)\u2026", " "), /* @__PURE__ */ React2.createElement(Elapsed, null)), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, " ", "spread across T=0.0/0.5/1.0 \xB7 typical wait 30-90s for reasoner"));
|
|
1545
|
+
}
|
|
1546
|
+
const pct = Math.round(p.completed / p.total * 100);
|
|
1547
|
+
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "green" }, "assistant", " "), /* @__PURE__ */ React2.createElement(Text2, { color: "blue" }, "\u{1F500} branching ", p.completed, "/", p.total, " (", pct, "%)", " "), /* @__PURE__ */ React2.createElement(Elapsed, null)), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, " latest #", p.latestIndex, " T=", p.latestTemperature.toFixed(1), " u=", p.latestUncertainties, p.completed < p.total ? " \xB7 waiting for other samples\u2026" : " \xB7 selecting winner\u2026"));
|
|
1548
|
+
}
|
|
1037
1549
|
const tail = lastLine(event.text, 140);
|
|
1038
1550
|
const reasoningTail = event.reasoning ? lastLine(event.reasoning, 120) : "";
|
|
1039
|
-
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "green" }, "assistant", " "), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "(streaming \xB7 ", event.text.length, event.reasoning ? ` + think ${event.reasoning.length}` : "", " chars)")), reasoningTail ? /* @__PURE__ */ React2.createElement(Text2, { dimColor: true, italic: true }, "\u21B3 thinking: ", reasoningTail) : null, tail ? /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "\u25B8 ", tail) : /* @__PURE__ */ React2.createElement(Text2, { dimColor: true, italic: true }, " (waiting for first token\u2026)"));
|
|
1551
|
+
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "green" }, "assistant", " "), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "(streaming \xB7 ", event.text.length, event.reasoning ? ` + think ${event.reasoning.length}` : "", " chars)", " "), /* @__PURE__ */ React2.createElement(Elapsed, null)), reasoningTail ? /* @__PURE__ */ React2.createElement(Text2, { dimColor: true, italic: true }, "\u21B3 thinking: ", reasoningTail) : null, tail ? /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "\u25B8 ", tail) : /* @__PURE__ */ React2.createElement(Text2, { dimColor: true, italic: true }, " (waiting for first token\u2026)"));
|
|
1040
1552
|
}
|
|
1041
1553
|
function lastLine(s, maxChars) {
|
|
1042
1554
|
const flat = s.replace(/\s+/g, " ").trim();
|
|
@@ -1062,7 +1574,7 @@ function PromptInput({
|
|
|
1062
1574
|
disabled,
|
|
1063
1575
|
placeholder
|
|
1064
1576
|
}) {
|
|
1065
|
-
const effectivePlaceholder = disabled ? placeholder ?? "\u2026waiting for response\u2026" : placeholder ??
|
|
1577
|
+
const effectivePlaceholder = disabled ? placeholder ?? "\u2026waiting for response\u2026" : placeholder ?? "type a message, or /command";
|
|
1066
1578
|
return /* @__PURE__ */ React3.createElement(Box3, { borderStyle: "round", borderColor: disabled ? "gray" : "cyan", paddingX: 1 }, /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: disabled ? "gray" : "cyan" }, "you \u203A", " "), /* @__PURE__ */ React3.createElement(
|
|
1067
1579
|
TextInput,
|
|
1068
1580
|
{
|
|
@@ -1078,21 +1590,120 @@ function PromptInput({
|
|
|
1078
1590
|
// src/cli/ui/StatsPanel.tsx
|
|
1079
1591
|
import { Box as Box4, Text as Text4 } from "ink";
|
|
1080
1592
|
import React4 from "react";
|
|
1081
|
-
function StatsPanel({
|
|
1593
|
+
function StatsPanel({
|
|
1594
|
+
summary,
|
|
1595
|
+
model,
|
|
1596
|
+
prefixHash,
|
|
1597
|
+
harvestOn,
|
|
1598
|
+
branchBudget
|
|
1599
|
+
}) {
|
|
1082
1600
|
const hitPct = (summary.cacheHitRatio * 100).toFixed(1);
|
|
1083
1601
|
const hitColor = summary.cacheHitRatio >= 0.7 ? "green" : summary.cacheHitRatio >= 0.4 ? "yellow" : "red";
|
|
1084
|
-
|
|
1602
|
+
const branchOn = (branchBudget ?? 1) > 1;
|
|
1603
|
+
return /* @__PURE__ */ React4.createElement(Box4, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React4.createElement(Box4, { justifyContent: "space-between" }, /* @__PURE__ */ React4.createElement(Text4, null, /* @__PURE__ */ React4.createElement(Text4, { color: "cyan", bold: true }, "Reasonix"), /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, " \xB7 model "), /* @__PURE__ */ React4.createElement(Text4, { color: "yellow" }, model), /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, " \xB7 prefix "), /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, prefixHash), harvestOn ? /* @__PURE__ */ React4.createElement(Text4, { color: "magenta" }, " \xB7 harvest") : null, branchOn ? /* @__PURE__ */ React4.createElement(Text4, { color: "blue" }, " \xB7 branch", branchBudget) : null), /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "turns ", summary.turns, " \xB7 type /help")), /* @__PURE__ */ React4.createElement(Box4, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React4.createElement(Text4, null, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "cache hit "), /* @__PURE__ */ React4.createElement(Text4, { color: hitColor, bold: true }, hitPct, "%")), /* @__PURE__ */ React4.createElement(Text4, null, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "cost "), /* @__PURE__ */ React4.createElement(Text4, { color: "green" }, "$", summary.totalCostUsd.toFixed(6))), /* @__PURE__ */ React4.createElement(Text4, null, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "vs Claude "), /* @__PURE__ */ React4.createElement(Text4, null, "$", summary.claudeEquivalentUsd.toFixed(6))), /* @__PURE__ */ React4.createElement(Text4, null, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "saving "), /* @__PURE__ */ React4.createElement(Text4, { color: "green", bold: true }, summary.savingsVsClaudePct.toFixed(1), "%"))));
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
// src/cli/ui/slash.ts
|
|
1607
|
+
function parseSlash(text) {
|
|
1608
|
+
if (!text.startsWith("/")) return null;
|
|
1609
|
+
const parts = text.slice(1).trim().split(/\s+/);
|
|
1610
|
+
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
1611
|
+
if (!cmd) return null;
|
|
1612
|
+
return { cmd, args: parts.slice(1) };
|
|
1613
|
+
}
|
|
1614
|
+
function handleSlash(cmd, args, loop) {
|
|
1615
|
+
switch (cmd) {
|
|
1616
|
+
case "exit":
|
|
1617
|
+
case "quit":
|
|
1618
|
+
return { exit: true };
|
|
1619
|
+
case "clear":
|
|
1620
|
+
return { clear: true };
|
|
1621
|
+
case "help":
|
|
1622
|
+
case "?":
|
|
1623
|
+
return {
|
|
1624
|
+
info: [
|
|
1625
|
+
"Commands:",
|
|
1626
|
+
" /help this message",
|
|
1627
|
+
" /status show current settings",
|
|
1628
|
+
" /preset <fast|smart|max> one-tap presets \u2014 see below",
|
|
1629
|
+
" /model <id> deepseek-chat or deepseek-reasoner",
|
|
1630
|
+
" /harvest [on|off] Pillar 2: structured plan-state extraction",
|
|
1631
|
+
" /branch <N|off> run N parallel samples (N>=2), pick most confident",
|
|
1632
|
+
" /clear clear displayed history (log is kept)",
|
|
1633
|
+
" /exit quit",
|
|
1634
|
+
"",
|
|
1635
|
+
"Presets:",
|
|
1636
|
+
" fast deepseek-chat no harvest no branch ~1\xA2/100turns \u2190 default",
|
|
1637
|
+
" smart reasoner harvest ~10x cost, slower",
|
|
1638
|
+
" max reasoner harvest branch 3 ~30x cost, slowest"
|
|
1639
|
+
].join("\n")
|
|
1640
|
+
};
|
|
1641
|
+
case "status": {
|
|
1642
|
+
const branchBudget = loop.branchOptions.budget ?? 1;
|
|
1643
|
+
return {
|
|
1644
|
+
info: `model=${loop.model} harvest=${loop.harvestEnabled ? "on" : "off"} branch=${branchBudget > 1 ? branchBudget : "off"} stream=${loop.stream ? "on" : "off"}`
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
case "model": {
|
|
1648
|
+
const id = args[0];
|
|
1649
|
+
if (!id) return { info: "usage: /model <id> (try deepseek-chat or deepseek-reasoner)" };
|
|
1650
|
+
loop.configure({ model: id });
|
|
1651
|
+
return { info: `model \u2192 ${id}` };
|
|
1652
|
+
}
|
|
1653
|
+
case "harvest": {
|
|
1654
|
+
const arg = (args[0] ?? "").toLowerCase();
|
|
1655
|
+
const on = arg === "" ? !loop.harvestEnabled : arg === "on" || arg === "true" || arg === "1";
|
|
1656
|
+
loop.configure({ harvest: on });
|
|
1657
|
+
return { info: `harvest \u2192 ${loop.harvestEnabled ? "on" : "off"}` };
|
|
1658
|
+
}
|
|
1659
|
+
case "preset": {
|
|
1660
|
+
const name = (args[0] ?? "").toLowerCase();
|
|
1661
|
+
if (name === "fast" || name === "default") {
|
|
1662
|
+
loop.configure({ model: "deepseek-chat", harvest: false, branch: 1 });
|
|
1663
|
+
return { info: "preset \u2192 fast (deepseek-chat, no harvest, no branch)" };
|
|
1664
|
+
}
|
|
1665
|
+
if (name === "smart") {
|
|
1666
|
+
loop.configure({ model: "deepseek-reasoner", harvest: true, branch: 1 });
|
|
1667
|
+
return { info: "preset \u2192 smart (reasoner + harvest, ~10x cost vs fast)" };
|
|
1668
|
+
}
|
|
1669
|
+
if (name === "max" || name === "best") {
|
|
1670
|
+
loop.configure({ model: "deepseek-reasoner", harvest: true, branch: 3 });
|
|
1671
|
+
return {
|
|
1672
|
+
info: "preset \u2192 max (reasoner + harvest + branch3, ~30x cost vs fast, slowest)"
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1675
|
+
return { info: "usage: /preset <fast|smart|max>" };
|
|
1676
|
+
}
|
|
1677
|
+
case "branch": {
|
|
1678
|
+
const raw = (args[0] ?? "").toLowerCase();
|
|
1679
|
+
if (raw === "" || raw === "off" || raw === "0" || raw === "1") {
|
|
1680
|
+
loop.configure({ branch: 1 });
|
|
1681
|
+
return { info: "branch \u2192 off" };
|
|
1682
|
+
}
|
|
1683
|
+
const n = Number.parseInt(raw, 10);
|
|
1684
|
+
if (!Number.isFinite(n) || n < 2) {
|
|
1685
|
+
return { info: "usage: /branch <N> (N>=2, or 'off')" };
|
|
1686
|
+
}
|
|
1687
|
+
if (n > 8) {
|
|
1688
|
+
return { info: "branch budget capped at 8 to prevent runaway cost" };
|
|
1689
|
+
}
|
|
1690
|
+
loop.configure({ branch: n });
|
|
1691
|
+
return { info: `branch \u2192 ${n} (harvest auto-enabled; streaming disabled)` };
|
|
1692
|
+
}
|
|
1693
|
+
default:
|
|
1694
|
+
return { unknown: true, info: `unknown command: /${cmd} (try /help)` };
|
|
1695
|
+
}
|
|
1085
1696
|
}
|
|
1086
1697
|
|
|
1087
1698
|
// src/cli/ui/App.tsx
|
|
1088
1699
|
var FLUSH_INTERVAL_MS = 60;
|
|
1089
|
-
function App({ model, system, transcript, harvest: harvest2 }) {
|
|
1700
|
+
function App({ model, system, transcript, harvest: harvest2, branch }) {
|
|
1090
1701
|
const { exit } = useApp();
|
|
1091
|
-
const [historical, setHistorical] =
|
|
1092
|
-
const [streaming, setStreaming] =
|
|
1093
|
-
const [input, setInput] =
|
|
1094
|
-
const [busy, setBusy] =
|
|
1095
|
-
const [summary, setSummary] =
|
|
1702
|
+
const [historical, setHistorical] = useState2([]);
|
|
1703
|
+
const [streaming, setStreaming] = useState2(null);
|
|
1704
|
+
const [input, setInput] = useState2("");
|
|
1705
|
+
const [busy, setBusy] = useState2(false);
|
|
1706
|
+
const [summary, setSummary] = useState2({
|
|
1096
1707
|
turns: 0,
|
|
1097
1708
|
totalCostUsd: 0,
|
|
1098
1709
|
claudeEquivalentUsd: 0,
|
|
@@ -1103,7 +1714,7 @@ function App({ model, system, transcript, harvest: harvest2 }) {
|
|
|
1103
1714
|
if (transcript && !transcriptRef.current) {
|
|
1104
1715
|
transcriptRef.current = createWriteStream(transcript, { flags: "a" });
|
|
1105
1716
|
}
|
|
1106
|
-
|
|
1717
|
+
useEffect2(() => {
|
|
1107
1718
|
return () => {
|
|
1108
1719
|
transcriptRef.current?.end();
|
|
1109
1720
|
};
|
|
@@ -1113,10 +1724,10 @@ function App({ model, system, transcript, harvest: harvest2 }) {
|
|
|
1113
1724
|
if (loopRef.current) return loopRef.current;
|
|
1114
1725
|
const client = new DeepSeekClient();
|
|
1115
1726
|
const prefix = new ImmutablePrefix({ system });
|
|
1116
|
-
const l = new CacheFirstLoop({ client, prefix, model, harvest: harvest2 });
|
|
1727
|
+
const l = new CacheFirstLoop({ client, prefix, model, harvest: harvest2, branch });
|
|
1117
1728
|
loopRef.current = l;
|
|
1118
1729
|
return l;
|
|
1119
|
-
}, [model, system, harvest2]);
|
|
1730
|
+
}, [model, system, harvest2, branch]);
|
|
1120
1731
|
const prefixHash = loop.prefix.fingerprint;
|
|
1121
1732
|
const writeTranscript = useCallback((ev) => {
|
|
1122
1733
|
transcriptRef.current?.write(
|
|
@@ -1135,13 +1746,28 @@ function App({ model, system, transcript, harvest: harvest2 }) {
|
|
|
1135
1746
|
const text = raw.trim();
|
|
1136
1747
|
if (!text || busy) return;
|
|
1137
1748
|
setInput("");
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1749
|
+
const slash = parseSlash(text);
|
|
1750
|
+
if (slash) {
|
|
1751
|
+
const result = handleSlash(slash.cmd, slash.args, loop);
|
|
1752
|
+
if (result.exit) {
|
|
1753
|
+
transcriptRef.current?.end();
|
|
1754
|
+
exit();
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
if (result.clear) {
|
|
1758
|
+
setHistorical([]);
|
|
1759
|
+
return;
|
|
1760
|
+
}
|
|
1761
|
+
if (result.info) {
|
|
1762
|
+
setHistorical((prev) => [
|
|
1763
|
+
...prev,
|
|
1764
|
+
{
|
|
1765
|
+
id: `sys-${Date.now()}`,
|
|
1766
|
+
role: "info",
|
|
1767
|
+
text: result.info
|
|
1768
|
+
}
|
|
1769
|
+
]);
|
|
1770
|
+
}
|
|
1145
1771
|
return;
|
|
1146
1772
|
}
|
|
1147
1773
|
setHistorical((prev) => [...prev, { id: `u-${Date.now()}`, role: "user", text }]);
|
|
@@ -1172,6 +1798,23 @@ function App({ model, system, transcript, harvest: harvest2 }) {
|
|
|
1172
1798
|
if (ev.role === "assistant_delta") {
|
|
1173
1799
|
if (ev.content) contentBuf.current += ev.content;
|
|
1174
1800
|
if (ev.reasoningDelta) reasoningBuf.current += ev.reasoningDelta;
|
|
1801
|
+
} else if (ev.role === "branch_start") {
|
|
1802
|
+
setStreaming({
|
|
1803
|
+
id: assistantId,
|
|
1804
|
+
role: "assistant",
|
|
1805
|
+
text: "",
|
|
1806
|
+
streaming: true,
|
|
1807
|
+
branchProgress: ev.branchProgress
|
|
1808
|
+
});
|
|
1809
|
+
} else if (ev.role === "branch_progress") {
|
|
1810
|
+
setStreaming({
|
|
1811
|
+
id: assistantId,
|
|
1812
|
+
role: "assistant",
|
|
1813
|
+
text: "",
|
|
1814
|
+
streaming: true,
|
|
1815
|
+
branchProgress: ev.branchProgress
|
|
1816
|
+
});
|
|
1817
|
+
} else if (ev.role === "branch_done") {
|
|
1175
1818
|
} else if (ev.role === "assistant_final") {
|
|
1176
1819
|
flush();
|
|
1177
1820
|
const repairNote = ev.repair ? describeRepair(ev.repair) : "";
|
|
@@ -1184,6 +1827,7 @@ function App({ model, system, transcript, harvest: harvest2 }) {
|
|
|
1184
1827
|
text: ev.content || streamRef.text,
|
|
1185
1828
|
reasoning: streamRef.reasoning || void 0,
|
|
1186
1829
|
planState: ev.planState,
|
|
1830
|
+
branch: ev.branch,
|
|
1187
1831
|
stats: ev.stats,
|
|
1188
1832
|
repair: repairNote || void 0,
|
|
1189
1833
|
streaming: false
|
|
@@ -1217,7 +1861,19 @@ function App({ model, system, transcript, harvest: harvest2 }) {
|
|
|
1217
1861
|
},
|
|
1218
1862
|
[busy, exit, loop, writeTranscript]
|
|
1219
1863
|
);
|
|
1220
|
-
return /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column" }, /* @__PURE__ */ React5.createElement(
|
|
1864
|
+
return /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column" }, /* @__PURE__ */ React5.createElement(
|
|
1865
|
+
StatsPanel,
|
|
1866
|
+
{
|
|
1867
|
+
summary,
|
|
1868
|
+
model: loop.model,
|
|
1869
|
+
prefixHash,
|
|
1870
|
+
harvestOn: loop.harvestEnabled,
|
|
1871
|
+
branchBudget: loop.branchOptions.budget
|
|
1872
|
+
}
|
|
1873
|
+
), /* @__PURE__ */ React5.createElement(Static, { items: historical }, (item) => /* @__PURE__ */ React5.createElement(EventRow, { key: item.id, event: item })), streaming ? /* @__PURE__ */ React5.createElement(Box5, { marginY: 1 }, /* @__PURE__ */ React5.createElement(EventRow, { event: streaming })) : null, /* @__PURE__ */ React5.createElement(PromptInput, { value: input, onChange: setInput, onSubmit: handleSubmit, disabled: busy }), /* @__PURE__ */ React5.createElement(CommandStrip, null));
|
|
1874
|
+
}
|
|
1875
|
+
function CommandStrip() {
|
|
1876
|
+
return /* @__PURE__ */ React5.createElement(Box5, { paddingX: 2 }, /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "/help \xB7 /preset ", "<fast|smart|max>", " \xB7 /model \xB7 /harvest \xB7 /branch \xB7 /clear \xB7 /exit"));
|
|
1221
1877
|
}
|
|
1222
1878
|
function describeRepair(repair) {
|
|
1223
1879
|
const parts = [];
|
|
@@ -1228,12 +1884,12 @@ function describeRepair(repair) {
|
|
|
1228
1884
|
}
|
|
1229
1885
|
|
|
1230
1886
|
// src/cli/ui/Setup.tsx
|
|
1231
|
-
import { Box as Box6, Text as
|
|
1887
|
+
import { Box as Box6, Text as Text6, useApp as useApp2 } from "ink";
|
|
1232
1888
|
import TextInput2 from "ink-text-input";
|
|
1233
|
-
import React6, { useState as
|
|
1889
|
+
import React6, { useState as useState3 } from "react";
|
|
1234
1890
|
function Setup({ onReady }) {
|
|
1235
|
-
const [value, setValue] =
|
|
1236
|
-
const [error, setError] =
|
|
1891
|
+
const [value, setValue] = useState3("");
|
|
1892
|
+
const [error, setError] = useState3(null);
|
|
1237
1893
|
const { exit } = useApp2();
|
|
1238
1894
|
const handleSubmit = (raw) => {
|
|
1239
1895
|
const trimmed = raw.trim();
|
|
@@ -1254,7 +1910,7 @@ function Setup({ onReady }) {
|
|
|
1254
1910
|
}
|
|
1255
1911
|
onReady(trimmed);
|
|
1256
1912
|
};
|
|
1257
|
-
return /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React6.createElement(
|
|
1913
|
+
return /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React6.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text6, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React6.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React6.createElement(
|
|
1258
1914
|
TextInput2,
|
|
1259
1915
|
{
|
|
1260
1916
|
value,
|
|
@@ -1263,12 +1919,12 @@ function Setup({ onReady }) {
|
|
|
1263
1919
|
mask: "\u2022",
|
|
1264
1920
|
placeholder: "sk-..."
|
|
1265
1921
|
}
|
|
1266
|
-
)), error ? /* @__PURE__ */ React6.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(
|
|
1922
|
+
)), error ? /* @__PURE__ */ React6.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text6, { color: "red" }, error)) : value ? /* @__PURE__ */ React6.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "preview: ", redactKey(value))) : null, /* @__PURE__ */ React6.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "(Type /exit to abort.)")));
|
|
1267
1923
|
}
|
|
1268
1924
|
|
|
1269
1925
|
// src/cli/commands/chat.tsx
|
|
1270
1926
|
function Root({ initialKey, ...appProps }) {
|
|
1271
|
-
const [key, setKey] =
|
|
1927
|
+
const [key, setKey] = useState4(initialKey);
|
|
1272
1928
|
if (!key) {
|
|
1273
1929
|
return /* @__PURE__ */ React7.createElement(
|
|
1274
1930
|
Setup,
|
|
@@ -1287,7 +1943,8 @@ function Root({ initialKey, ...appProps }) {
|
|
|
1287
1943
|
model: appProps.model,
|
|
1288
1944
|
system: appProps.system,
|
|
1289
1945
|
transcript: appProps.transcript,
|
|
1290
|
-
harvest: appProps.harvest
|
|
1946
|
+
harvest: appProps.harvest,
|
|
1947
|
+
branch: appProps.branch
|
|
1291
1948
|
}
|
|
1292
1949
|
);
|
|
1293
1950
|
}
|
|
@@ -1397,12 +2054,17 @@ program.name("reasonix").description("DeepSeek-native agent framework \u2014 bui
|
|
|
1397
2054
|
program.command("chat").description("Interactive Ink TUI with live cache/cost panel.").option("-m, --model <id>", "DeepSeek model id", "deepseek-chat").option("-s, --system <prompt>", "System prompt (pinned in the immutable prefix)", DEFAULT_SYSTEM).option("--transcript <path>", "Write a JSONL transcript to this path").option(
|
|
1398
2055
|
"--harvest",
|
|
1399
2056
|
"Extract typed plan state from R1 reasoning (Pillar 2, adds a cheap V3 call per turn)"
|
|
2057
|
+
).option(
|
|
2058
|
+
"--branch <n>",
|
|
2059
|
+
"Self-consistency: run N parallel samples per turn and pick the most confident (disables streaming; enables harvest)",
|
|
2060
|
+
(v) => Number.parseInt(v, 10)
|
|
1400
2061
|
).action(async (opts) => {
|
|
1401
2062
|
await chatCommand({
|
|
1402
2063
|
model: opts.model,
|
|
1403
2064
|
system: opts.system,
|
|
1404
2065
|
transcript: opts.transcript,
|
|
1405
|
-
harvest: !!opts.harvest
|
|
2066
|
+
harvest: !!opts.harvest,
|
|
2067
|
+
branch: Number.isFinite(opts.branch) && opts.branch > 1 ? opts.branch : void 0
|
|
1406
2068
|
});
|
|
1407
2069
|
});
|
|
1408
2070
|
program.command("run <task>").description("Run a single task non-interactively, streaming output.").option("-m, --model <id>", "DeepSeek model id", "deepseek-chat").option("-s, --system <prompt>", "System prompt", DEFAULT_SYSTEM).action(async (task, opts) => {
|