@wrongstack/core 0.24.0 → 0.31.1

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.
@@ -142,6 +142,21 @@ declare function createToolOutputSerializer(opts?: ToolOutputSerializerOptions):
142
142
  /**
143
143
  * Shared token estimation with JSON.stringify caching.
144
144
  * Avoids repeated stringification of tool input objects.
145
+ *
146
+ * ## Calibration
147
+ *
148
+ * `estimateRequestTokens` uses a fixed 3.5 chars/token heuristic — a
149
+ * conservative overestimate that prevents underestimation but reduces
150
+ * accuracy. After each API call, call `recordActualUsage()` with the
151
+ * real `usage.input` from the provider response. The module maintains a
152
+ * rolling average of `actual / estimated` ratio (EWM, α=0.3) and
153
+ * applies it to subsequent calls via `estimateRequestTokensCalibrated`.
154
+ *
155
+ * Calibration is per-module (shared across all callers), which is
156
+ * sufficient: the chars/token ratio is a property of the tokenizer,
157
+ * not the model. Uncalibrated calls (before any samples, or when
158
+ * `recordActualUsage` is not called) fall back to the uncalibrated
159
+ * estimate so nothing breaks.
145
160
  */
146
161
  /**
147
162
  * Estimate tokens for a tool_use block input.
@@ -190,6 +205,47 @@ declare function estimateRequestTokens(messages: unknown, systemPrompt: unknown,
190
205
  description?: string;
191
206
  inputSchema: unknown;
192
207
  }[]): RequestTokenBreakdown;
208
+ /**
209
+ * Record the actual API input token count after a provider call so
210
+ * `estimateRequestTokensCalibrated` can self-correct on subsequent calls.
211
+ *
212
+ * Prefer passing `estimatedInputTokens` explicitly (the calibrated pre-flight
213
+ * estimate from the middleware) — this avoids race conditions when other code
214
+ * also calls `estimateRequestTokens` between the pre-flight and this call
215
+ * (e.g. audit logging in agent.ts).
216
+ *
217
+ * When `estimatedInputTokens` is omitted, falls back to `_cal.prevEst`
218
+ * for backward compatibility with callers that don't have the pre-flight value.
219
+ */
220
+ declare function recordActualUsage(actualInputTokens: number, estimatedInputTokens?: number): void;
221
+ /**
222
+ * Returns the current calibration state. Exposed for debugging and
223
+ * tests — not needed by normal callers.
224
+ */
225
+ declare function getCalibrationState(): {
226
+ ratio: number;
227
+ count: number;
228
+ calibrated: boolean;
229
+ };
230
+ /**
231
+ * Like `estimateRequestTokens` but applies the rolling calibration factor
232
+ * so context pressure readings converge on reality within a few iterations.
233
+ *
234
+ * Before any `recordActualUsage` samples are collected, returns the same
235
+ * result as `estimateRequestTokens` (ratio = 1.0, no distortion).
236
+ * After `MIN_SAMPLES_FOR_CALIBRATION` samples, applies the calibrated
237
+ * multiplier capped to the range [0.5, 1.5] as a sanity bound.
238
+ */
239
+ declare function estimateRequestTokensCalibrated(messages: unknown, systemPrompt: unknown, tools: {
240
+ name: string;
241
+ description?: string;
242
+ inputSchema: unknown;
243
+ }[]): RequestTokenBreakdown;
244
+ /**
245
+ * Resets calibration state. Primarily for tests that run in the same
246
+ * process and need a clean slate between suites.
247
+ */
248
+ declare function resetCalibration(): void;
193
249
 
194
250
  interface MessageRepairReport {
195
251
  changed: boolean;
@@ -254,6 +310,25 @@ interface CompileFail {
254
310
  }
255
311
  declare function compileUserRegex(pattern: string, flags: string): CompileResult | CompileFail;
256
312
 
313
+ /**
314
+ * Glob pattern → concrete file path expansion.
315
+ *
316
+ * Supports: *, **, ?, [...]
317
+ * Does NOT support brace expansion {a,b}.
318
+ *
319
+ * Returns the input as-is if it contains no glob metacharacters.
320
+ * On Windows, both / and \ are accepted as path separators.
321
+ */
322
+ /**
323
+ * Resolve `pattern` to the set of concrete file paths it matches.
324
+ * Literal paths (no glob chars) are returned as-is.
325
+ *
326
+ * @example
327
+ * await expandGlob('src/**\/*.ts') // → ['src/a.ts', 'src/b/c.ts', ...]
328
+ * await expandGlob('foo.txt') // → ['foo.txt']
329
+ */
330
+ declare function expandGlob(pattern: string): Promise<string[]>;
331
+
257
332
  /**
258
333
  * Attempt to close an incomplete JSON object string by auto-closing braces
259
334
  * and completing any unclosed double-quoted string values.
@@ -296,4 +371,4 @@ declare function completePartialObject(s: string): string;
296
371
  */
297
372
  declare function mergeModelsPayload(base: ModelsDevPayload, overlay: ModelsDevPayload): ModelsDevPayload;
298
373
 
299
- export { type AtomicWriteOptions, type BuildChildEnvOptions, type CompileFail, type CompileResult, type MessageRepairReport, type MessageRepairResult, type NewlineStyle, type RequestTokenBreakdown, type SafeParseResult, type ToolOutputSerializerOptions, type UnifiedDiffOptions, type ValidationError, type ValidationResult, atomicWrite, buildChildEnv, color, compileGlob, compileUserRegex, completePartialObject, createToolOutputSerializer, detectNewlineStyle, ensureDir, estimateRequestTokens, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, formatTodosList, matchAny, matchGlob, mergeModelsPayload, normalizeToLf, repairToolUseAdjacency, safeParse, safeStringify, sanitizeJsonString, stripAnsi, toStyle, unifiedDiff, validateAgainstSchema };
374
+ export { type AtomicWriteOptions, type BuildChildEnvOptions, type CompileFail, type CompileResult, type MessageRepairReport, type MessageRepairResult, type NewlineStyle, type RequestTokenBreakdown, type SafeParseResult, type ToolOutputSerializerOptions, type UnifiedDiffOptions, type ValidationError, type ValidationResult, atomicWrite, buildChildEnv, color, compileGlob, compileUserRegex, completePartialObject, createToolOutputSerializer, detectNewlineStyle, ensureDir, estimateRequestTokens, estimateRequestTokensCalibrated, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, expandGlob, formatTodosList, getCalibrationState, matchAny, matchGlob, mergeModelsPayload, normalizeToLf, recordActualUsage, repairToolUseAdjacency, resetCalibration, safeParse, safeStringify, sanitizeJsonString, stripAnsi, toStyle, unifiedDiff, validateAgainstSchema };
@@ -1,6 +1,7 @@
1
1
  import { randomBytes, createHash } from 'crypto';
2
2
  import * as fs from 'fs/promises';
3
3
  import * as path2 from 'path';
4
+ import { isAbsolute, resolve } from 'path';
4
5
  import * as os from 'os';
5
6
 
6
7
  // src/utils/atomic-write.ts
@@ -25,8 +26,8 @@ async function atomicWrite(targetPath, content, opts = {}) {
25
26
  }
26
27
  let mode;
27
28
  try {
28
- const stat2 = await fs.stat(targetPath);
29
- mode = stat2.mode & 511;
29
+ const stat3 = await fs.stat(targetPath);
30
+ mode = stat3.mode & 511;
30
31
  } catch {
31
32
  mode = opts.mode;
32
33
  }
@@ -63,7 +64,7 @@ async function renameWithRetry(from, to) {
63
64
  if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
64
65
  throw err;
65
66
  }
66
- await new Promise((resolve2) => setTimeout(resolve2, delays[i]));
67
+ await new Promise((resolve3) => setTimeout(resolve3, delays[i]));
67
68
  }
68
69
  }
69
70
  throw lastErr;
@@ -657,6 +658,17 @@ function createToolOutputSerializer(opts = {}) {
657
658
 
658
659
  // src/utils/token-estimate.ts
659
660
  var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
661
+ var _cal = {
662
+ ratio: 1,
663
+ // current calibration multiplier (actual / estimated)
664
+ count: 0,
665
+ // number of samples recorded
666
+ prevEst: 0,
667
+ // estimated tokens from the most recent estimateRequestTokens call
668
+ /** EWM α — higher = faster adaptation, more volatile */
669
+ alpha: 0.3
670
+ };
671
+ var MIN_SAMPLES_FOR_CALIBRATION = 3;
660
672
  var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
661
673
  var ESTIMATE_CACHE_MAX_SIZE = 1e4;
662
674
  function getCachedEstimate(key, compute) {
@@ -729,13 +741,53 @@ function estimateRequestTokens(messages, systemPrompt, tools) {
729
741
  for (const t of tools) {
730
742
  toolsTokens += estimateToolDefTokens(t);
731
743
  }
744
+ const total = messagesTokens + systemTokens + toolsTokens;
745
+ _cal.prevEst = total;
732
746
  return {
733
747
  messages: messagesTokens,
734
748
  systemPrompt: systemTokens,
735
749
  tools: toolsTokens,
736
- total: messagesTokens + systemTokens + toolsTokens
750
+ total
751
+ };
752
+ }
753
+ function recordActualUsage(actualInputTokens, estimatedInputTokens) {
754
+ if (actualInputTokens <= 0) return;
755
+ const est = estimatedInputTokens ?? _cal.prevEst;
756
+ if (est <= 0) return;
757
+ const sampleRatio = actualInputTokens / est;
758
+ if (_cal.count === 0) {
759
+ _cal.ratio = sampleRatio;
760
+ } else {
761
+ _cal.ratio = _cal.alpha * sampleRatio + (1 - _cal.alpha) * _cal.ratio;
762
+ }
763
+ _cal.ratio = Math.min(1.5, Math.max(0.5, _cal.ratio));
764
+ _cal.count++;
765
+ }
766
+ function getCalibrationState() {
767
+ return {
768
+ ratio: _cal.ratio,
769
+ count: _cal.count,
770
+ calibrated: _cal.count >= MIN_SAMPLES_FOR_CALIBRATION
737
771
  };
738
772
  }
773
+ function estimateRequestTokensCalibrated(messages, systemPrompt, tools) {
774
+ const result = estimateRequestTokens(messages, systemPrompt, tools);
775
+ if (_cal.count >= MIN_SAMPLES_FOR_CALIBRATION) {
776
+ const safeRatio = Math.min(1.5, Math.max(0.5, _cal.ratio));
777
+ return {
778
+ messages: Math.round(result.messages * safeRatio),
779
+ systemPrompt: Math.round(result.systemPrompt * safeRatio),
780
+ tools: Math.round(result.tools * safeRatio),
781
+ total: Math.round(result.total * safeRatio)
782
+ };
783
+ }
784
+ return result;
785
+ }
786
+ function resetCalibration() {
787
+ _cal.ratio = 1;
788
+ _cal.count = 0;
789
+ _cal.prevEst = 0;
790
+ }
739
791
 
740
792
  // src/utils/message-invariants.ts
741
793
  function repairToolUseAdjacency(messages) {
@@ -958,6 +1010,120 @@ function compileUserRegex(pattern, flags) {
958
1010
  };
959
1011
  }
960
1012
  }
1013
+ var GLOB_CHARS = /* @__PURE__ */ new Set(["*", "?", "["]);
1014
+ var IS_WINDOWS = process.platform === "win32";
1015
+ var SEP = IS_WINDOWS ? "\\" : "/";
1016
+ function isGlob(p) {
1017
+ for (const c of p) {
1018
+ if (GLOB_CHARS.has(c)) return true;
1019
+ }
1020
+ return false;
1021
+ }
1022
+ function globToRegex(pat) {
1023
+ let i = 0, re = "^";
1024
+ while (i < pat.length) {
1025
+ const c = pat[i];
1026
+ if (c === "*") {
1027
+ if (pat[i + 1] === "*") {
1028
+ re += ".*";
1029
+ i += 2;
1030
+ if (pat[i] === "/") i++;
1031
+ } else {
1032
+ re += "[^/\\\\]*";
1033
+ i++;
1034
+ }
1035
+ } else if (c === "?") {
1036
+ re += "[^/\\\\]";
1037
+ i++;
1038
+ } else if (c === "[") {
1039
+ let cls = "[";
1040
+ i++;
1041
+ if (pat[i] === "!" || pat[i] === "^") {
1042
+ cls += "^";
1043
+ i++;
1044
+ }
1045
+ while (i < pat.length && pat[i] !== "]") {
1046
+ const ch = pat[i] ?? "";
1047
+ if (ch === "\\") cls += "\\\\";
1048
+ else if (ch === "]" || ch === "^") cls += `\\${ch}`;
1049
+ else cls += ch;
1050
+ i++;
1051
+ }
1052
+ cls += "]";
1053
+ re += cls;
1054
+ i++;
1055
+ } else {
1056
+ re += c.replace(/[.+^${}()|\\]/g, "\\$&");
1057
+ i++;
1058
+ }
1059
+ }
1060
+ return new RegExp(re + "$");
1061
+ }
1062
+ function baseDir(pat) {
1063
+ let i = pat.length - 1;
1064
+ while (i >= 0 && !GLOB_CHARS.has(pat[i]) && pat[i] !== SEP && pat[i] !== "/") i--;
1065
+ const cut = i >= 0 ? pat.lastIndexOf(SEP, i) : pat.lastIndexOf("/", i);
1066
+ return cut < 0 ? "." : pat.slice(0, cut);
1067
+ }
1068
+ async function expandGlob(pattern) {
1069
+ if (!isGlob(pattern)) return [pattern];
1070
+ const results = /* @__PURE__ */ new Set();
1071
+ const abs = isAbsolute(pattern);
1072
+ const base = abs ? baseDir(pattern) : baseDir(pattern);
1073
+ const relPat = base === "." ? pattern : pattern.slice(base.length + 1);
1074
+ async function walk2(dir, pat) {
1075
+ let entries;
1076
+ try {
1077
+ entries = await fs.readdir(dir);
1078
+ } catch {
1079
+ return;
1080
+ }
1081
+ const firstGlob = pat.search(/[*?[\[]/);
1082
+ if (firstGlob < 0) {
1083
+ const re = globToRegex(pat);
1084
+ for (const e of entries) {
1085
+ if (re.test(e)) {
1086
+ const full = `${dir}${SEP}${e}`;
1087
+ results.add(abs ? resolve(full) : full);
1088
+ }
1089
+ }
1090
+ return;
1091
+ }
1092
+ const before = pat.slice(0, firstGlob);
1093
+ const rest = pat.slice(firstGlob);
1094
+ if (before.endsWith("**")) {
1095
+ await walk2(dir, rest);
1096
+ for (const e of entries) {
1097
+ const full = `${dir}${SEP}${e}`;
1098
+ try {
1099
+ const stat3 = await fs.stat(full);
1100
+ if (stat3.isDirectory()) await walk2(full, rest);
1101
+ } catch {
1102
+ }
1103
+ }
1104
+ } else if (before === "") {
1105
+ const re = globToRegex(rest);
1106
+ for (const e of entries) {
1107
+ if (re.test(e)) {
1108
+ const full = `${dir}${SEP}${e}`;
1109
+ results.add(abs ? resolve(full) : full);
1110
+ }
1111
+ }
1112
+ } else {
1113
+ const seg = before.replace(/[*?[\]]/g, "").replace(/\/$/, "");
1114
+ if (entries.includes(seg)) {
1115
+ const full = `${dir}${SEP}${seg}`;
1116
+ try {
1117
+ const stat3 = await fs.stat(full);
1118
+ if (stat3.isDirectory()) await walk2(full, rest);
1119
+ } catch {
1120
+ }
1121
+ }
1122
+ }
1123
+ }
1124
+ await walk2(base === "." ? "." : base, relPat);
1125
+ return [...results];
1126
+ }
961
1127
 
962
1128
  // src/utils/json-repair.ts
963
1129
  function completePartialObject(s) {
@@ -1111,6 +1277,6 @@ function stripUndefined(obj) {
1111
1277
  return out;
1112
1278
  }
1113
1279
 
1114
- export { atomicWrite, buildChildEnv, color, compileGlob, compileUserRegex, completePartialObject, createToolOutputSerializer, detectNewlineStyle, ensureDir, estimateRequestTokens, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, formatTodosList, matchAny, matchGlob, mergeModelsPayload, normalizeToLf, projectHash, repairToolUseAdjacency, resolveWstackPaths, safeParse, safeStringify, sanitizeJsonString, stripAnsi, toStyle, unifiedDiff, validateAgainstSchema };
1280
+ export { atomicWrite, buildChildEnv, color, compileGlob, compileUserRegex, completePartialObject, createToolOutputSerializer, detectNewlineStyle, ensureDir, estimateRequestTokens, estimateRequestTokensCalibrated, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, expandGlob, formatTodosList, getCalibrationState, matchAny, matchGlob, mergeModelsPayload, normalizeToLf, projectHash, recordActualUsage, repairToolUseAdjacency, resetCalibration, resolveWstackPaths, safeParse, safeStringify, sanitizeJsonString, stripAnsi, toStyle, unifiedDiff, validateAgainstSchema };
1115
1281
  //# sourceMappingURL=index.js.map
1116
1282
  //# sourceMappingURL=index.js.map