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/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 {
@@ -534,9 +844,20 @@ function round(n, digits) {
534
844
  // src/tools.ts
535
845
  var ToolRegistry = class {
536
846
  _tools = /* @__PURE__ */ new Map();
847
+ _autoFlatten;
848
+ constructor(opts = {}) {
849
+ this._autoFlatten = opts.autoFlatten !== false;
850
+ }
537
851
  register(def) {
538
852
  if (!def.name) throw new Error("tool requires a name");
539
- this._tools.set(def.name, def);
853
+ const internal = { ...def };
854
+ if (this._autoFlatten && def.parameters) {
855
+ const decision = analyzeSchema(def.parameters);
856
+ if (decision.shouldFlatten) {
857
+ internal.flatSchema = flattenSchema(def.parameters);
858
+ }
859
+ }
860
+ this._tools.set(def.name, internal);
540
861
  return this;
541
862
  }
542
863
  has(name) {
@@ -548,13 +869,17 @@ var ToolRegistry = class {
548
869
  get size() {
549
870
  return this._tools.size;
550
871
  }
872
+ /** True if a registered tool's schema was flattened for the model. */
873
+ wasFlattened(name) {
874
+ return Boolean(this._tools.get(name)?.flatSchema);
875
+ }
551
876
  specs() {
552
877
  return [...this._tools.values()].map((t) => ({
553
878
  type: "function",
554
879
  function: {
555
880
  name: t.name,
556
881
  description: t.description ?? "",
557
- parameters: t.parameters ?? { type: "object", properties: {} }
882
+ parameters: t.flatSchema ?? t.parameters ?? { type: "object", properties: {} }
558
883
  }
559
884
  }));
560
885
  }
@@ -565,12 +890,15 @@ var ToolRegistry = class {
565
890
  }
566
891
  let args;
567
892
  try {
568
- args = typeof argumentsRaw === "string" ? argumentsRaw.trim() ? JSON.parse(argumentsRaw) : {} : argumentsRaw ?? {};
893
+ args = typeof argumentsRaw === "string" ? argumentsRaw.trim() ? JSON.parse(argumentsRaw) ?? {} : {} : argumentsRaw ?? {};
569
894
  } catch (err) {
570
895
  return JSON.stringify({
571
896
  error: `invalid tool arguments JSON: ${err.message}`
572
897
  });
573
898
  }
899
+ if (tool.flatSchema && args && typeof args === "object" && hasDotKey(args)) {
900
+ args = nestArguments(args);
901
+ }
574
902
  try {
575
903
  const result = await tool.fn(args);
576
904
  return typeof result === "string" ? result : JSON.stringify(result);
@@ -581,34 +909,85 @@ var ToolRegistry = class {
581
909
  }
582
910
  }
583
911
  };
912
+ function hasDotKey(obj) {
913
+ for (const k of Object.keys(obj)) {
914
+ if (k.includes(".")) return true;
915
+ }
916
+ return false;
917
+ }
584
918
 
585
919
  // src/loop.ts
586
920
  var CacheFirstLoop = class {
587
921
  client;
588
922
  prefix;
589
923
  tools;
590
- model;
591
924
  maxToolIters;
592
- stream;
593
- harvestEnabled;
594
- harvestOptions;
595
925
  log = new AppendOnlyLog();
596
926
  scratch = new VolatileScratch();
597
927
  stats = new SessionStats();
598
928
  repair;
929
+ // Mutable via configure() — slash commands in the TUI / library callers tweak
930
+ // these mid-session so users don't have to restart to try harvest or branch.
931
+ model;
932
+ stream;
933
+ harvestEnabled;
934
+ harvestOptions;
935
+ branchEnabled;
936
+ branchOptions;
599
937
  _turn = 0;
938
+ _streamPreference;
600
939
  constructor(opts) {
601
940
  this.client = opts.client;
602
941
  this.prefix = opts.prefix;
603
942
  this.tools = opts.tools ?? new ToolRegistry();
604
943
  this.model = opts.model ?? "deepseek-chat";
605
944
  this.maxToolIters = opts.maxToolIters ?? 8;
606
- this.stream = opts.stream ?? true;
607
- this.harvestEnabled = opts.harvest === true || typeof opts.harvest === "object" && opts.harvest !== null;
608
- this.harvestOptions = typeof opts.harvest === "object" && opts.harvest !== null ? opts.harvest : {};
945
+ if (typeof opts.branch === "number") {
946
+ this.branchOptions = { budget: opts.branch };
947
+ } else if (opts.branch && typeof opts.branch === "object") {
948
+ this.branchOptions = opts.branch;
949
+ } else {
950
+ this.branchOptions = {};
951
+ }
952
+ this.branchEnabled = (this.branchOptions.budget ?? 1) > 1;
953
+ const harvestForced = this.branchEnabled;
954
+ this.harvestEnabled = harvestForced || opts.harvest === true || typeof opts.harvest === "object" && opts.harvest !== null;
955
+ this.harvestOptions = typeof opts.harvest === "object" && opts.harvest !== null ? opts.harvest : this.branchOptions.harvestOptions ?? {};
956
+ this._streamPreference = opts.stream ?? true;
957
+ this.stream = this.branchEnabled ? false : this._streamPreference;
609
958
  const allowedNames = /* @__PURE__ */ new Set([...this.prefix.toolSpecs.map((s) => s.function.name)]);
610
959
  this.repair = new ToolCallRepair({ allowedToolNames: allowedNames });
611
960
  }
961
+ /**
962
+ * Reconfigure model/harvest/branch/stream mid-session. The loop's log,
963
+ * scratch, and stats are preserved — only the per-turn behavior changes.
964
+ * Used by the TUI's slash commands and by library callers who want to
965
+ * flip a knob between turns.
966
+ */
967
+ configure(opts) {
968
+ if (opts.model !== void 0) this.model = opts.model;
969
+ if (opts.stream !== void 0) this._streamPreference = opts.stream;
970
+ if (opts.branch !== void 0) {
971
+ if (typeof opts.branch === "number") {
972
+ this.branchOptions = { budget: opts.branch };
973
+ } else if (opts.branch && typeof opts.branch === "object") {
974
+ this.branchOptions = opts.branch;
975
+ } else {
976
+ this.branchOptions = {};
977
+ }
978
+ this.branchEnabled = (this.branchOptions.budget ?? 1) > 1;
979
+ }
980
+ if (opts.harvest !== void 0) {
981
+ const want = opts.harvest === true || typeof opts.harvest === "object" && opts.harvest !== null;
982
+ this.harvestEnabled = want || this.branchEnabled;
983
+ if (typeof opts.harvest === "object" && opts.harvest !== null) {
984
+ this.harvestOptions = opts.harvest;
985
+ }
986
+ } else if (this.branchEnabled) {
987
+ this.harvestEnabled = true;
988
+ }
989
+ this.stream = this.branchEnabled ? false : this._streamPreference;
990
+ }
612
991
  buildMessages(pendingUser) {
613
992
  const msgs = [...this.prefix.toMessages(), ...this.log.toMessages()];
614
993
  if (pendingUser !== null) msgs.push({ role: "user", content: pendingUser });
@@ -625,8 +1004,85 @@ var CacheFirstLoop = class {
625
1004
  let reasoningContent = "";
626
1005
  let toolCalls = [];
627
1006
  let usage = null;
1007
+ let branchSummary;
1008
+ let preHarvestedPlanState;
628
1009
  try {
629
- if (this.stream) {
1010
+ if (this.branchEnabled) {
1011
+ const budget = this.branchOptions.budget ?? 1;
1012
+ yield {
1013
+ turn: this._turn,
1014
+ role: "branch_start",
1015
+ content: "",
1016
+ branchProgress: {
1017
+ completed: 0,
1018
+ total: budget,
1019
+ latestIndex: -1,
1020
+ latestTemperature: -1,
1021
+ latestUncertainties: -1
1022
+ }
1023
+ };
1024
+ const queue = [];
1025
+ let waiter = null;
1026
+ const onSampleDone = (sample) => {
1027
+ if (waiter) {
1028
+ const w = waiter;
1029
+ waiter = null;
1030
+ w(sample);
1031
+ } else {
1032
+ queue.push(sample);
1033
+ }
1034
+ };
1035
+ const branchPromise = runBranches(
1036
+ this.client,
1037
+ {
1038
+ model: this.model,
1039
+ messages,
1040
+ tools: toolSpecs.length ? toolSpecs : void 0
1041
+ },
1042
+ {
1043
+ ...this.branchOptions,
1044
+ harvestOptions: this.harvestOptions,
1045
+ onSampleDone
1046
+ }
1047
+ );
1048
+ for (let k = 0; k < budget; k++) {
1049
+ const sample = queue.shift() ?? await new Promise((resolve2) => {
1050
+ waiter = resolve2;
1051
+ });
1052
+ yield {
1053
+ turn: this._turn,
1054
+ role: "branch_progress",
1055
+ content: "",
1056
+ branchProgress: {
1057
+ completed: k + 1,
1058
+ total: budget,
1059
+ latestIndex: sample.index,
1060
+ latestTemperature: sample.temperature,
1061
+ latestUncertainties: sample.planState.uncertainties.length
1062
+ }
1063
+ };
1064
+ }
1065
+ const result = await branchPromise;
1066
+ assistantContent = result.chosen.response.content;
1067
+ reasoningContent = result.chosen.response.reasoningContent ?? "";
1068
+ toolCalls = result.chosen.response.toolCalls;
1069
+ const agg = aggregateBranchUsage(result.samples);
1070
+ usage = new Usage(
1071
+ agg.promptTokens,
1072
+ agg.completionTokens,
1073
+ agg.totalTokens,
1074
+ agg.promptCacheHitTokens,
1075
+ agg.promptCacheMissTokens
1076
+ );
1077
+ preHarvestedPlanState = result.chosen.planState;
1078
+ branchSummary = summarizeBranch(result.chosen, result.samples);
1079
+ yield {
1080
+ turn: this._turn,
1081
+ role: "branch_done",
1082
+ content: "",
1083
+ branch: branchSummary
1084
+ };
1085
+ } else if (this.stream) {
630
1086
  const callBuf = /* @__PURE__ */ new Map();
631
1087
  for await (const chunk of this.client.stream({
632
1088
  model: this.model,
@@ -686,17 +1142,13 @@ var CacheFirstLoop = class {
686
1142
  };
687
1143
  return;
688
1144
  }
689
- const turnStats = this.stats.record(
690
- this._turn,
691
- this.model,
692
- usage ?? new (await import("./client-KEA2D52Q.js")).Usage()
693
- );
1145
+ const turnStats = this.stats.record(this._turn, this.model, usage ?? new Usage());
694
1146
  if (pendingUser !== null) {
695
1147
  this.log.append({ role: "user", content: pendingUser });
696
1148
  pendingUser = null;
697
1149
  }
698
1150
  this.scratch.reasoning = reasoningContent || null;
699
- const planState = this.harvestEnabled ? await harvest(reasoningContent || null, this.client, this.harvestOptions) : emptyPlanState();
1151
+ const planState = preHarvestedPlanState ? preHarvestedPlanState : this.harvestEnabled ? await harvest(reasoningContent || null, this.client, this.harvestOptions) : emptyPlanState();
700
1152
  const { calls: repairedCalls, report } = this.repair.process(
701
1153
  toolCalls,
702
1154
  reasoningContent || null
@@ -708,7 +1160,8 @@ var CacheFirstLoop = class {
708
1160
  content: assistantContent,
709
1161
  stats: turnStats,
710
1162
  planState,
711
- repair: report
1163
+ repair: report,
1164
+ branch: branchSummary
712
1165
  };
713
1166
  if (repairedCalls.length === 0) {
714
1167
  yield { turn: this._turn, role: "done", content: assistantContent };
@@ -744,6 +1197,14 @@ var CacheFirstLoop = class {
744
1197
  return msg;
745
1198
  }
746
1199
  };
1200
+ function summarizeBranch(chosen, samples) {
1201
+ return {
1202
+ budget: samples.length,
1203
+ chosenIndex: chosen.index,
1204
+ uncertainties: samples.map((s) => s.planState.uncertainties.length),
1205
+ temperatures: samples.map((s) => s.temperature)
1206
+ };
1207
+ }
747
1208
 
748
1209
  // src/env.ts
749
1210
  import { readFileSync } from "fs";
@@ -826,10 +1287,12 @@ export {
826
1287
  Usage,
827
1288
  VERSION,
828
1289
  VolatileScratch,
1290
+ aggregateBranchUsage,
829
1291
  analyzeSchema,
830
1292
  claudeEquivalentCost,
831
1293
  costUsd,
832
1294
  defaultConfigPath,
1295
+ defaultSelector,
833
1296
  emptyPlanState,
834
1297
  fetchWithRetry,
835
1298
  flattenSchema,
@@ -842,6 +1305,7 @@ export {
842
1305
  readConfig,
843
1306
  redactKey,
844
1307
  repairTruncatedJson,
1308
+ runBranches,
845
1309
  saveApiKey,
846
1310
  scavengeToolCalls,
847
1311
  writeConfig