reasonix 0.0.3 → 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 +811 -57
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +108 -6
- package/dist/index.js +487 -23
- 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 {
|
|
@@ -347,6 +660,74 @@ function repairTruncatedJson(input) {
|
|
|
347
660
|
}
|
|
348
661
|
}
|
|
349
662
|
|
|
663
|
+
// src/repair/flatten.ts
|
|
664
|
+
function analyzeSchema(schema) {
|
|
665
|
+
if (!schema) return { shouldFlatten: false, leafCount: 0, maxDepth: 0 };
|
|
666
|
+
let leafCount = 0;
|
|
667
|
+
let maxDepth = 0;
|
|
668
|
+
walk(schema, 0, (depth, isLeaf) => {
|
|
669
|
+
if (isLeaf) leafCount++;
|
|
670
|
+
if (depth > maxDepth) maxDepth = depth;
|
|
671
|
+
});
|
|
672
|
+
return {
|
|
673
|
+
shouldFlatten: leafCount > 10 || maxDepth > 2,
|
|
674
|
+
leafCount,
|
|
675
|
+
maxDepth
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
function flattenSchema(schema) {
|
|
679
|
+
const flatProps = {};
|
|
680
|
+
const required = [];
|
|
681
|
+
collect("", schema, flatProps, required, true);
|
|
682
|
+
return {
|
|
683
|
+
type: "object",
|
|
684
|
+
properties: flatProps,
|
|
685
|
+
required
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
function nestArguments(flatArgs) {
|
|
689
|
+
const out = {};
|
|
690
|
+
for (const [key, value] of Object.entries(flatArgs)) {
|
|
691
|
+
setByPath(out, key.split("."), value);
|
|
692
|
+
}
|
|
693
|
+
return out;
|
|
694
|
+
}
|
|
695
|
+
function walk(schema, depth, visit) {
|
|
696
|
+
if (schema.type === "object" && schema.properties) {
|
|
697
|
+
for (const child of Object.values(schema.properties)) {
|
|
698
|
+
walk(child, depth + 1, visit);
|
|
699
|
+
}
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
if (schema.type === "array" && schema.items) {
|
|
703
|
+
walk(schema.items, depth + 1, visit);
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
visit(depth, true);
|
|
707
|
+
}
|
|
708
|
+
function collect(prefix, schema, out, required, isRootRequired) {
|
|
709
|
+
if (schema.type === "object" && schema.properties) {
|
|
710
|
+
const requiredSet = new Set(schema.required ?? []);
|
|
711
|
+
for (const [key, child] of Object.entries(schema.properties)) {
|
|
712
|
+
const nextPrefix = prefix ? `${prefix}.${key}` : key;
|
|
713
|
+
const childRequired = isRootRequired && requiredSet.has(key);
|
|
714
|
+
collect(nextPrefix, child, out, required, childRequired);
|
|
715
|
+
}
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
out[prefix] = schema;
|
|
719
|
+
if (isRootRequired) required.push(prefix);
|
|
720
|
+
}
|
|
721
|
+
function setByPath(target, path, value) {
|
|
722
|
+
let cur = target;
|
|
723
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
724
|
+
const key = path[i];
|
|
725
|
+
if (typeof cur[key] !== "object" || cur[key] === null) cur[key] = {};
|
|
726
|
+
cur = cur[key];
|
|
727
|
+
}
|
|
728
|
+
cur[path[path.length - 1]] = value;
|
|
729
|
+
}
|
|
730
|
+
|
|
350
731
|
// src/repair/index.ts
|
|
351
732
|
var ToolCallRepair = class {
|
|
352
733
|
storm;
|
|
@@ -468,9 +849,20 @@ function round(n, digits) {
|
|
|
468
849
|
// src/tools.ts
|
|
469
850
|
var ToolRegistry = class {
|
|
470
851
|
_tools = /* @__PURE__ */ new Map();
|
|
852
|
+
_autoFlatten;
|
|
853
|
+
constructor(opts = {}) {
|
|
854
|
+
this._autoFlatten = opts.autoFlatten !== false;
|
|
855
|
+
}
|
|
471
856
|
register(def) {
|
|
472
857
|
if (!def.name) throw new Error("tool requires a name");
|
|
473
|
-
|
|
858
|
+
const internal = { ...def };
|
|
859
|
+
if (this._autoFlatten && def.parameters) {
|
|
860
|
+
const decision = analyzeSchema(def.parameters);
|
|
861
|
+
if (decision.shouldFlatten) {
|
|
862
|
+
internal.flatSchema = flattenSchema(def.parameters);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
this._tools.set(def.name, internal);
|
|
474
866
|
return this;
|
|
475
867
|
}
|
|
476
868
|
has(name) {
|
|
@@ -482,13 +874,17 @@ var ToolRegistry = class {
|
|
|
482
874
|
get size() {
|
|
483
875
|
return this._tools.size;
|
|
484
876
|
}
|
|
877
|
+
/** True if a registered tool's schema was flattened for the model. */
|
|
878
|
+
wasFlattened(name) {
|
|
879
|
+
return Boolean(this._tools.get(name)?.flatSchema);
|
|
880
|
+
}
|
|
485
881
|
specs() {
|
|
486
882
|
return [...this._tools.values()].map((t) => ({
|
|
487
883
|
type: "function",
|
|
488
884
|
function: {
|
|
489
885
|
name: t.name,
|
|
490
886
|
description: t.description ?? "",
|
|
491
|
-
parameters: t.parameters ?? { type: "object", properties: {} }
|
|
887
|
+
parameters: t.flatSchema ?? t.parameters ?? { type: "object", properties: {} }
|
|
492
888
|
}
|
|
493
889
|
}));
|
|
494
890
|
}
|
|
@@ -499,12 +895,15 @@ var ToolRegistry = class {
|
|
|
499
895
|
}
|
|
500
896
|
let args;
|
|
501
897
|
try {
|
|
502
|
-
args = typeof argumentsRaw === "string" ? argumentsRaw.trim() ? JSON.parse(argumentsRaw) : {} : argumentsRaw ?? {};
|
|
898
|
+
args = typeof argumentsRaw === "string" ? argumentsRaw.trim() ? JSON.parse(argumentsRaw) ?? {} : {} : argumentsRaw ?? {};
|
|
503
899
|
} catch (err) {
|
|
504
900
|
return JSON.stringify({
|
|
505
901
|
error: `invalid tool arguments JSON: ${err.message}`
|
|
506
902
|
});
|
|
507
903
|
}
|
|
904
|
+
if (tool.flatSchema && args && typeof args === "object" && hasDotKey(args)) {
|
|
905
|
+
args = nestArguments(args);
|
|
906
|
+
}
|
|
508
907
|
try {
|
|
509
908
|
const result = await tool.fn(args);
|
|
510
909
|
return typeof result === "string" ? result : JSON.stringify(result);
|
|
@@ -515,34 +914,85 @@ var ToolRegistry = class {
|
|
|
515
914
|
}
|
|
516
915
|
}
|
|
517
916
|
};
|
|
917
|
+
function hasDotKey(obj) {
|
|
918
|
+
for (const k of Object.keys(obj)) {
|
|
919
|
+
if (k.includes(".")) return true;
|
|
920
|
+
}
|
|
921
|
+
return false;
|
|
922
|
+
}
|
|
518
923
|
|
|
519
924
|
// src/loop.ts
|
|
520
925
|
var CacheFirstLoop = class {
|
|
521
926
|
client;
|
|
522
927
|
prefix;
|
|
523
928
|
tools;
|
|
524
|
-
model;
|
|
525
929
|
maxToolIters;
|
|
526
|
-
stream;
|
|
527
|
-
harvestEnabled;
|
|
528
|
-
harvestOptions;
|
|
529
930
|
log = new AppendOnlyLog();
|
|
530
931
|
scratch = new VolatileScratch();
|
|
531
932
|
stats = new SessionStats();
|
|
532
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;
|
|
533
942
|
_turn = 0;
|
|
943
|
+
_streamPreference;
|
|
534
944
|
constructor(opts) {
|
|
535
945
|
this.client = opts.client;
|
|
536
946
|
this.prefix = opts.prefix;
|
|
537
947
|
this.tools = opts.tools ?? new ToolRegistry();
|
|
538
948
|
this.model = opts.model ?? "deepseek-chat";
|
|
539
949
|
this.maxToolIters = opts.maxToolIters ?? 8;
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
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;
|
|
543
963
|
const allowedNames = /* @__PURE__ */ new Set([...this.prefix.toolSpecs.map((s) => s.function.name)]);
|
|
544
964
|
this.repair = new ToolCallRepair({ allowedToolNames: allowedNames });
|
|
545
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
|
+
}
|
|
546
996
|
buildMessages(pendingUser) {
|
|
547
997
|
const msgs = [...this.prefix.toMessages(), ...this.log.toMessages()];
|
|
548
998
|
if (pendingUser !== null) msgs.push({ role: "user", content: pendingUser });
|
|
@@ -559,8 +1009,85 @@ var CacheFirstLoop = class {
|
|
|
559
1009
|
let reasoningContent = "";
|
|
560
1010
|
let toolCalls = [];
|
|
561
1011
|
let usage = null;
|
|
1012
|
+
let branchSummary;
|
|
1013
|
+
let preHarvestedPlanState;
|
|
562
1014
|
try {
|
|
563
|
-
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) {
|
|
564
1091
|
const callBuf = /* @__PURE__ */ new Map();
|
|
565
1092
|
for await (const chunk of this.client.stream({
|
|
566
1093
|
model: this.model,
|
|
@@ -620,17 +1147,13 @@ var CacheFirstLoop = class {
|
|
|
620
1147
|
};
|
|
621
1148
|
return;
|
|
622
1149
|
}
|
|
623
|
-
const turnStats = this.stats.record(
|
|
624
|
-
this._turn,
|
|
625
|
-
this.model,
|
|
626
|
-
usage ?? new (await import("./client-RIVGDOJP.js")).Usage()
|
|
627
|
-
);
|
|
1150
|
+
const turnStats = this.stats.record(this._turn, this.model, usage ?? new Usage());
|
|
628
1151
|
if (pendingUser !== null) {
|
|
629
1152
|
this.log.append({ role: "user", content: pendingUser });
|
|
630
1153
|
pendingUser = null;
|
|
631
1154
|
}
|
|
632
1155
|
this.scratch.reasoning = reasoningContent || null;
|
|
633
|
-
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();
|
|
634
1157
|
const { calls: repairedCalls, report } = this.repair.process(
|
|
635
1158
|
toolCalls,
|
|
636
1159
|
reasoningContent || null
|
|
@@ -642,7 +1165,8 @@ var CacheFirstLoop = class {
|
|
|
642
1165
|
content: assistantContent,
|
|
643
1166
|
stats: turnStats,
|
|
644
1167
|
planState,
|
|
645
|
-
repair: report
|
|
1168
|
+
repair: report,
|
|
1169
|
+
branch: branchSummary
|
|
646
1170
|
};
|
|
647
1171
|
if (repairedCalls.length === 0) {
|
|
648
1172
|
yield { turn: this._turn, role: "done", content: assistantContent };
|
|
@@ -678,6 +1202,14 @@ var CacheFirstLoop = class {
|
|
|
678
1202
|
return msg;
|
|
679
1203
|
}
|
|
680
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
|
+
}
|
|
681
1213
|
|
|
682
1214
|
// src/env.ts
|
|
683
1215
|
import { readFileSync } from "fs";
|
|
@@ -751,22 +1283,67 @@ var VERSION = "0.0.1";
|
|
|
751
1283
|
|
|
752
1284
|
// src/cli/commands/chat.tsx
|
|
753
1285
|
import { render } from "ink";
|
|
754
|
-
import React7, { useState as
|
|
1286
|
+
import React7, { useState as useState4 } from "react";
|
|
755
1287
|
|
|
756
1288
|
// src/cli/ui/App.tsx
|
|
757
1289
|
import { createWriteStream } from "fs";
|
|
758
|
-
import { Box as Box5, Static, useApp } from "ink";
|
|
759
|
-
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";
|
|
760
1292
|
|
|
761
1293
|
// src/cli/ui/EventLog.tsx
|
|
762
1294
|
import { Box as Box2, Text as Text2 } from "ink";
|
|
763
|
-
import React2 from "react";
|
|
1295
|
+
import React2, { useEffect, useState } from "react";
|
|
764
1296
|
|
|
765
1297
|
// src/cli/ui/markdown.tsx
|
|
766
1298
|
import { Box, Text } from "ink";
|
|
767
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
|
+
}
|
|
768
1339
|
function stripMath(s) {
|
|
769
|
-
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, " ");
|
|
770
1347
|
}
|
|
771
1348
|
var INLINE_RE = /(\*\*([^*\n]+?)\*\*|`([^`\n]+?)`|(?<![*\w])\*([^*\n]+?)\*(?!\w))/g;
|
|
772
1349
|
function InlineMd({ text }) {
|
|
@@ -914,7 +1491,7 @@ var EventRow = React2.memo(function EventRow2({ event }) {
|
|
|
914
1491
|
}
|
|
915
1492
|
if (event.role === "assistant") {
|
|
916
1493
|
if (event.streaming) return /* @__PURE__ */ React2.createElement(StreamingAssistant, { event });
|
|
917
|
-
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);
|
|
918
1495
|
}
|
|
919
1496
|
if (event.role === "tool") {
|
|
920
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)));
|
|
@@ -927,6 +1504,14 @@ var EventRow = React2.memo(function EventRow2({ event }) {
|
|
|
927
1504
|
}
|
|
928
1505
|
return /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, null, event.text));
|
|
929
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
|
+
}
|
|
930
1515
|
function PlanStateBlock({ planState }) {
|
|
931
1516
|
const lines = [];
|
|
932
1517
|
if (planState.subgoals.length) lines.push(["subgoals", planState.subgoals]);
|
|
@@ -941,10 +1526,29 @@ function ReasoningBlock({ reasoning }) {
|
|
|
941
1526
|
const preview = flat.length <= max ? flat : `${flat.slice(0, max)}\u2026 (+${flat.length - max} chars)`;
|
|
942
1527
|
return /* @__PURE__ */ React2.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React2.createElement(Text2, { dimColor: true, italic: true }, "\u21B3 thinking: ", preview));
|
|
943
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
|
+
}
|
|
944
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
|
+
}
|
|
945
1549
|
const tail = lastLine(event.text, 140);
|
|
946
1550
|
const reasoningTail = event.reasoning ? lastLine(event.reasoning, 120) : "";
|
|
947
|
-
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)"));
|
|
948
1552
|
}
|
|
949
1553
|
function lastLine(s, maxChars) {
|
|
950
1554
|
const flat = s.replace(/\s+/g, " ").trim();
|
|
@@ -970,7 +1574,7 @@ function PromptInput({
|
|
|
970
1574
|
disabled,
|
|
971
1575
|
placeholder
|
|
972
1576
|
}) {
|
|
973
|
-
const effectivePlaceholder = disabled ? placeholder ?? "\u2026waiting for response\u2026" : placeholder ??
|
|
1577
|
+
const effectivePlaceholder = disabled ? placeholder ?? "\u2026waiting for response\u2026" : placeholder ?? "type a message, or /command";
|
|
974
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(
|
|
975
1579
|
TextInput,
|
|
976
1580
|
{
|
|
@@ -986,21 +1590,120 @@ function PromptInput({
|
|
|
986
1590
|
// src/cli/ui/StatsPanel.tsx
|
|
987
1591
|
import { Box as Box4, Text as Text4 } from "ink";
|
|
988
1592
|
import React4 from "react";
|
|
989
|
-
function StatsPanel({
|
|
1593
|
+
function StatsPanel({
|
|
1594
|
+
summary,
|
|
1595
|
+
model,
|
|
1596
|
+
prefixHash,
|
|
1597
|
+
harvestOn,
|
|
1598
|
+
branchBudget
|
|
1599
|
+
}) {
|
|
990
1600
|
const hitPct = (summary.cacheHitRatio * 100).toFixed(1);
|
|
991
1601
|
const hitColor = summary.cacheHitRatio >= 0.7 ? "green" : summary.cacheHitRatio >= 0.4 ? "yellow" : "red";
|
|
992
|
-
|
|
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
|
+
}
|
|
993
1696
|
}
|
|
994
1697
|
|
|
995
1698
|
// src/cli/ui/App.tsx
|
|
996
1699
|
var FLUSH_INTERVAL_MS = 60;
|
|
997
|
-
function App({ model, system, transcript, harvest: harvest2 }) {
|
|
1700
|
+
function App({ model, system, transcript, harvest: harvest2, branch }) {
|
|
998
1701
|
const { exit } = useApp();
|
|
999
|
-
const [historical, setHistorical] =
|
|
1000
|
-
const [streaming, setStreaming] =
|
|
1001
|
-
const [input, setInput] =
|
|
1002
|
-
const [busy, setBusy] =
|
|
1003
|
-
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({
|
|
1004
1707
|
turns: 0,
|
|
1005
1708
|
totalCostUsd: 0,
|
|
1006
1709
|
claudeEquivalentUsd: 0,
|
|
@@ -1011,7 +1714,7 @@ function App({ model, system, transcript, harvest: harvest2 }) {
|
|
|
1011
1714
|
if (transcript && !transcriptRef.current) {
|
|
1012
1715
|
transcriptRef.current = createWriteStream(transcript, { flags: "a" });
|
|
1013
1716
|
}
|
|
1014
|
-
|
|
1717
|
+
useEffect2(() => {
|
|
1015
1718
|
return () => {
|
|
1016
1719
|
transcriptRef.current?.end();
|
|
1017
1720
|
};
|
|
@@ -1021,10 +1724,10 @@ function App({ model, system, transcript, harvest: harvest2 }) {
|
|
|
1021
1724
|
if (loopRef.current) return loopRef.current;
|
|
1022
1725
|
const client = new DeepSeekClient();
|
|
1023
1726
|
const prefix = new ImmutablePrefix({ system });
|
|
1024
|
-
const l = new CacheFirstLoop({ client, prefix, model, harvest: harvest2 });
|
|
1727
|
+
const l = new CacheFirstLoop({ client, prefix, model, harvest: harvest2, branch });
|
|
1025
1728
|
loopRef.current = l;
|
|
1026
1729
|
return l;
|
|
1027
|
-
}, [model, system, harvest2]);
|
|
1730
|
+
}, [model, system, harvest2, branch]);
|
|
1028
1731
|
const prefixHash = loop.prefix.fingerprint;
|
|
1029
1732
|
const writeTranscript = useCallback((ev) => {
|
|
1030
1733
|
transcriptRef.current?.write(
|
|
@@ -1043,13 +1746,28 @@ function App({ model, system, transcript, harvest: harvest2 }) {
|
|
|
1043
1746
|
const text = raw.trim();
|
|
1044
1747
|
if (!text || busy) return;
|
|
1045
1748
|
setInput("");
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
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
|
+
}
|
|
1053
1771
|
return;
|
|
1054
1772
|
}
|
|
1055
1773
|
setHistorical((prev) => [...prev, { id: `u-${Date.now()}`, role: "user", text }]);
|
|
@@ -1080,6 +1798,23 @@ function App({ model, system, transcript, harvest: harvest2 }) {
|
|
|
1080
1798
|
if (ev.role === "assistant_delta") {
|
|
1081
1799
|
if (ev.content) contentBuf.current += ev.content;
|
|
1082
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") {
|
|
1083
1818
|
} else if (ev.role === "assistant_final") {
|
|
1084
1819
|
flush();
|
|
1085
1820
|
const repairNote = ev.repair ? describeRepair(ev.repair) : "";
|
|
@@ -1092,6 +1827,7 @@ function App({ model, system, transcript, harvest: harvest2 }) {
|
|
|
1092
1827
|
text: ev.content || streamRef.text,
|
|
1093
1828
|
reasoning: streamRef.reasoning || void 0,
|
|
1094
1829
|
planState: ev.planState,
|
|
1830
|
+
branch: ev.branch,
|
|
1095
1831
|
stats: ev.stats,
|
|
1096
1832
|
repair: repairNote || void 0,
|
|
1097
1833
|
streaming: false
|
|
@@ -1125,7 +1861,19 @@ function App({ model, system, transcript, harvest: harvest2 }) {
|
|
|
1125
1861
|
},
|
|
1126
1862
|
[busy, exit, loop, writeTranscript]
|
|
1127
1863
|
);
|
|
1128
|
-
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"));
|
|
1129
1877
|
}
|
|
1130
1878
|
function describeRepair(repair) {
|
|
1131
1879
|
const parts = [];
|
|
@@ -1136,12 +1884,12 @@ function describeRepair(repair) {
|
|
|
1136
1884
|
}
|
|
1137
1885
|
|
|
1138
1886
|
// src/cli/ui/Setup.tsx
|
|
1139
|
-
import { Box as Box6, Text as
|
|
1887
|
+
import { Box as Box6, Text as Text6, useApp as useApp2 } from "ink";
|
|
1140
1888
|
import TextInput2 from "ink-text-input";
|
|
1141
|
-
import React6, { useState as
|
|
1889
|
+
import React6, { useState as useState3 } from "react";
|
|
1142
1890
|
function Setup({ onReady }) {
|
|
1143
|
-
const [value, setValue] =
|
|
1144
|
-
const [error, setError] =
|
|
1891
|
+
const [value, setValue] = useState3("");
|
|
1892
|
+
const [error, setError] = useState3(null);
|
|
1145
1893
|
const { exit } = useApp2();
|
|
1146
1894
|
const handleSubmit = (raw) => {
|
|
1147
1895
|
const trimmed = raw.trim();
|
|
@@ -1162,7 +1910,7 @@ function Setup({ onReady }) {
|
|
|
1162
1910
|
}
|
|
1163
1911
|
onReady(trimmed);
|
|
1164
1912
|
};
|
|
1165
|
-
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(
|
|
1166
1914
|
TextInput2,
|
|
1167
1915
|
{
|
|
1168
1916
|
value,
|
|
@@ -1171,12 +1919,12 @@ function Setup({ onReady }) {
|
|
|
1171
1919
|
mask: "\u2022",
|
|
1172
1920
|
placeholder: "sk-..."
|
|
1173
1921
|
}
|
|
1174
|
-
)), 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.)")));
|
|
1175
1923
|
}
|
|
1176
1924
|
|
|
1177
1925
|
// src/cli/commands/chat.tsx
|
|
1178
1926
|
function Root({ initialKey, ...appProps }) {
|
|
1179
|
-
const [key, setKey] =
|
|
1927
|
+
const [key, setKey] = useState4(initialKey);
|
|
1180
1928
|
if (!key) {
|
|
1181
1929
|
return /* @__PURE__ */ React7.createElement(
|
|
1182
1930
|
Setup,
|
|
@@ -1195,7 +1943,8 @@ function Root({ initialKey, ...appProps }) {
|
|
|
1195
1943
|
model: appProps.model,
|
|
1196
1944
|
system: appProps.system,
|
|
1197
1945
|
transcript: appProps.transcript,
|
|
1198
|
-
harvest: appProps.harvest
|
|
1946
|
+
harvest: appProps.harvest,
|
|
1947
|
+
branch: appProps.branch
|
|
1199
1948
|
}
|
|
1200
1949
|
);
|
|
1201
1950
|
}
|
|
@@ -1305,12 +2054,17 @@ program.name("reasonix").description("DeepSeek-native agent framework \u2014 bui
|
|
|
1305
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(
|
|
1306
2055
|
"--harvest",
|
|
1307
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)
|
|
1308
2061
|
).action(async (opts) => {
|
|
1309
2062
|
await chatCommand({
|
|
1310
2063
|
model: opts.model,
|
|
1311
2064
|
system: opts.system,
|
|
1312
2065
|
transcript: opts.transcript,
|
|
1313
|
-
harvest: !!opts.harvest
|
|
2066
|
+
harvest: !!opts.harvest,
|
|
2067
|
+
branch: Number.isFinite(opts.branch) && opts.branch > 1 ? opts.branch : void 0
|
|
1314
2068
|
});
|
|
1315
2069
|
});
|
|
1316
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) => {
|