reasonix 0.0.4 → 0.0.6

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