ai-runtime-engine 1.1.0

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.
Files changed (269) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/LICENSE +15 -0
  3. package/README.md +489 -0
  4. package/dist/artifacts/artifacts.d.ts +28 -0
  5. package/dist/artifacts/artifacts.js +46 -0
  6. package/dist/benchmark/benchmark.d.ts +23 -0
  7. package/dist/benchmark/benchmark.js +40 -0
  8. package/dist/cli/cli.d.ts +6 -0
  9. package/dist/cli/cli.js +161 -0
  10. package/dist/cli/commands/cleanup.d.ts +47 -0
  11. package/dist/cli/commands/cleanup.js +96 -0
  12. package/dist/cli/commands/config.d.ts +8 -0
  13. package/dist/cli/commands/config.js +28 -0
  14. package/dist/cli/commands/doctor.d.ts +57 -0
  15. package/dist/cli/commands/doctor.js +86 -0
  16. package/dist/cli/commands/executions.d.ts +9 -0
  17. package/dist/cli/commands/executions.js +25 -0
  18. package/dist/cli/commands/info.d.ts +43 -0
  19. package/dist/cli/commands/info.js +53 -0
  20. package/dist/cli/commands/init.d.ts +5 -0
  21. package/dist/cli/commands/init.js +75 -0
  22. package/dist/cli/commands/inspect.d.ts +16 -0
  23. package/dist/cli/commands/inspect.js +60 -0
  24. package/dist/cli/commands/phase2.d.ts +22 -0
  25. package/dist/cli/commands/phase2.js +83 -0
  26. package/dist/cli/commands/route.d.ts +14 -0
  27. package/dist/cli/commands/route.js +49 -0
  28. package/dist/cli/commands/run.d.ts +11 -0
  29. package/dist/cli/commands/run.js +37 -0
  30. package/dist/cli/commands/setup.d.ts +34 -0
  31. package/dist/cli/commands/setup.js +104 -0
  32. package/dist/cli/commands/skills.d.ts +28 -0
  33. package/dist/cli/commands/skills.js +48 -0
  34. package/dist/cli/commands/test.d.ts +7 -0
  35. package/dist/cli/commands/test.js +29 -0
  36. package/dist/cli/context.d.ts +12 -0
  37. package/dist/cli/context.js +16 -0
  38. package/dist/cli/interactive/repl.d.ts +6 -0
  39. package/dist/cli/interactive/repl.js +45 -0
  40. package/dist/cli/interactive/session.d.ts +36 -0
  41. package/dist/cli/interactive/session.js +356 -0
  42. package/dist/cli/prompt.d.ts +6 -0
  43. package/dist/cli/prompt.js +18 -0
  44. package/dist/cli/render.d.ts +7 -0
  45. package/dist/cli/render.js +14 -0
  46. package/dist/comparison/analysis.d.ts +46 -0
  47. package/dist/comparison/analysis.js +177 -0
  48. package/dist/comparison/comparator.d.ts +46 -0
  49. package/dist/comparison/comparator.js +270 -0
  50. package/dist/comparison/comparison.d.ts +140 -0
  51. package/dist/comparison/comparison.js +9 -0
  52. package/dist/comparison/render.d.ts +7 -0
  53. package/dist/comparison/render.js +66 -0
  54. package/dist/config/defaults.d.ts +52 -0
  55. package/dist/config/defaults.js +56 -0
  56. package/dist/config/load.d.ts +17 -0
  57. package/dist/config/load.js +50 -0
  58. package/dist/config/providerDefaults.d.ts +17 -0
  59. package/dist/config/providerDefaults.js +61 -0
  60. package/dist/config/schema.d.ts +9 -0
  61. package/dist/config/schema.js +78 -0
  62. package/dist/context/budget.d.ts +13 -0
  63. package/dist/context/budget.js +17 -0
  64. package/dist/context/compiler.d.ts +61 -0
  65. package/dist/context/compiler.js +125 -0
  66. package/dist/context/tokens.d.ts +19 -0
  67. package/dist/context/tokens.js +38 -0
  68. package/dist/conversations/conversations.d.ts +38 -0
  69. package/dist/conversations/conversations.js +64 -0
  70. package/dist/core/capabilities/evidence.d.ts +40 -0
  71. package/dist/core/capabilities/evidence.js +102 -0
  72. package/dist/core/capabilities/overlay.d.ts +15 -0
  73. package/dist/core/capabilities/overlay.js +0 -0
  74. package/dist/core/capabilities/taxonomy.d.ts +19 -0
  75. package/dist/core/capabilities/taxonomy.js +25 -0
  76. package/dist/core/fallback/errors.d.ts +30 -0
  77. package/dist/core/fallback/errors.js +80 -0
  78. package/dist/core/fallback/fallback.d.ts +40 -0
  79. package/dist/core/fallback/fallback.js +82 -0
  80. package/dist/core/fallback/retryPolicy.d.ts +11 -0
  81. package/dist/core/fallback/retryPolicy.js +14 -0
  82. package/dist/core/health/health.d.ts +3 -0
  83. package/dist/core/health/health.js +5 -0
  84. package/dist/core/health/monitor.d.ts +23 -0
  85. package/dist/core/health/monitor.js +82 -0
  86. package/dist/core/policies/budget.d.ts +19 -0
  87. package/dist/core/policies/budget.js +37 -0
  88. package/dist/core/registry/builtinTasks.d.ts +8 -0
  89. package/dist/core/registry/builtinTasks.js +54 -0
  90. package/dist/core/registry/registry.d.ts +18 -0
  91. package/dist/core/registry/registry.js +33 -0
  92. package/dist/core/registry/taskRegistry.d.ts +15 -0
  93. package/dist/core/registry/taskRegistry.js +30 -0
  94. package/dist/core/router/confidence.d.ts +7 -0
  95. package/dist/core/router/confidence.js +20 -0
  96. package/dist/core/router/dimensions.d.ts +16 -0
  97. package/dist/core/router/dimensions.js +60 -0
  98. package/dist/core/router/executor.d.ts +16 -0
  99. package/dist/core/router/executor.js +25 -0
  100. package/dist/core/router/filter.d.ts +34 -0
  101. package/dist/core/router/filter.js +113 -0
  102. package/dist/core/router/normalize.d.ts +30 -0
  103. package/dist/core/router/normalize.js +119 -0
  104. package/dist/core/router/request.d.ts +4 -0
  105. package/dist/core/router/request.js +21 -0
  106. package/dist/core/router/router.d.ts +32 -0
  107. package/dist/core/router/router.js +195 -0
  108. package/dist/core/router/routingPrefs.d.ts +11 -0
  109. package/dist/core/router/routingPrefs.js +30 -0
  110. package/dist/core/router/scorer.d.ts +19 -0
  111. package/dist/core/router/scorer.js +50 -0
  112. package/dist/core/router/weights.d.ts +9 -0
  113. package/dist/core/router/weights.js +31 -0
  114. package/dist/core/validation/validator.d.ts +16 -0
  115. package/dist/core/validation/validator.js +33 -0
  116. package/dist/discovery/modelCatalog.d.ts +28 -0
  117. package/dist/discovery/modelCatalog.js +105 -0
  118. package/dist/discovery/openapi.d.ts +25 -0
  119. package/dist/discovery/openapi.js +76 -0
  120. package/dist/executions/checkpoint.d.ts +26 -0
  121. package/dist/executions/checkpoint.js +114 -0
  122. package/dist/executions/execution.d.ts +51 -0
  123. package/dist/executions/execution.js +8 -0
  124. package/dist/executions/store.d.ts +52 -0
  125. package/dist/executions/store.js +124 -0
  126. package/dist/generation/generateAdapter.d.ts +17 -0
  127. package/dist/generation/generateAdapter.js +30 -0
  128. package/dist/index.d.ts +147 -0
  129. package/dist/index.js +107 -0
  130. package/dist/learning/feedback.d.ts +9 -0
  131. package/dist/learning/feedback.js +18 -0
  132. package/dist/learning/learningStore.d.ts +68 -0
  133. package/dist/learning/learningStore.js +138 -0
  134. package/dist/learning/performanceStore.d.ts +27 -0
  135. package/dist/learning/performanceStore.js +0 -0
  136. package/dist/marketplace/presets.d.ts +24 -0
  137. package/dist/marketplace/presets.js +52 -0
  138. package/dist/mcp/mcp.d.ts +31 -0
  139. package/dist/mcp/mcp.js +54 -0
  140. package/dist/memory/bm25.d.ts +16 -0
  141. package/dist/memory/bm25.js +56 -0
  142. package/dist/memory/classifier.d.ts +14 -0
  143. package/dist/memory/classifier.js +17 -0
  144. package/dist/memory/memory.d.ts +80 -0
  145. package/dist/memory/memory.js +191 -0
  146. package/dist/orchestration/executor.d.ts +35 -0
  147. package/dist/orchestration/executor.js +65 -0
  148. package/dist/orchestration/orchestrator.d.ts +42 -0
  149. package/dist/orchestration/orchestrator.js +63 -0
  150. package/dist/orchestration/plan.d.ts +37 -0
  151. package/dist/orchestration/plan.js +70 -0
  152. package/dist/orchestration/planner.d.ts +29 -0
  153. package/dist/orchestration/planner.js +69 -0
  154. package/dist/plugin/ai.d.ts +82 -0
  155. package/dist/plugin/ai.js +167 -0
  156. package/dist/probing/probe.d.ts +25 -0
  157. package/dist/probing/probe.js +63 -0
  158. package/dist/providers/factory.d.ts +18 -0
  159. package/dist/providers/factory.js +54 -0
  160. package/dist/providers/httpClient.d.ts +34 -0
  161. package/dist/providers/httpClient.js +80 -0
  162. package/dist/providers/httpProvider.d.ts +49 -0
  163. package/dist/providers/httpProvider.js +135 -0
  164. package/dist/providers/mock/demo.d.ts +13 -0
  165. package/dist/providers/mock/demo.js +58 -0
  166. package/dist/providers/mock/mockProvider.d.ts +35 -0
  167. package/dist/providers/mock/mockProvider.js +121 -0
  168. package/dist/providers/mock/scenarios.d.ts +44 -0
  169. package/dist/providers/mock/scenarios.js +30 -0
  170. package/dist/providers/provider.d.ts +26 -0
  171. package/dist/providers/provider.js +11 -0
  172. package/dist/providers/wire/anthropicWire.d.ts +6 -0
  173. package/dist/providers/wire/anthropicWire.js +83 -0
  174. package/dist/providers/wire/openaiWire.d.ts +7 -0
  175. package/dist/providers/wire/openaiWire.js +81 -0
  176. package/dist/providers/wire/registry.d.ts +8 -0
  177. package/dist/providers/wire/registry.js +20 -0
  178. package/dist/providers/wire/types.d.ts +39 -0
  179. package/dist/providers/wire/types.js +24 -0
  180. package/dist/runtime/config.d.ts +31 -0
  181. package/dist/runtime/config.js +121 -0
  182. package/dist/runtime/context.d.ts +34 -0
  183. package/dist/runtime/context.js +11 -0
  184. package/dist/runtime/events.d.ts +99 -0
  185. package/dist/runtime/events.js +82 -0
  186. package/dist/runtime/host.d.ts +27 -0
  187. package/dist/runtime/host.js +7 -0
  188. package/dist/runtime/intent/classifier.d.ts +30 -0
  189. package/dist/runtime/intent/classifier.js +60 -0
  190. package/dist/runtime/intent/signals.d.ts +19 -0
  191. package/dist/runtime/intent/signals.js +46 -0
  192. package/dist/runtime/modes/availability.d.ts +11 -0
  193. package/dist/runtime/modes/availability.js +17 -0
  194. package/dist/runtime/modes/chat.d.ts +18 -0
  195. package/dist/runtime/modes/chat.js +67 -0
  196. package/dist/runtime/modes/modeResolver.d.ts +43 -0
  197. package/dist/runtime/modes/modeResolver.js +78 -0
  198. package/dist/runtime/policy.d.ts +72 -0
  199. package/dist/runtime/policy.js +59 -0
  200. package/dist/runtime/providerView.d.ts +62 -0
  201. package/dist/runtime/providerView.js +105 -0
  202. package/dist/runtime/routing.d.ts +26 -0
  203. package/dist/runtime/routing.js +65 -0
  204. package/dist/runtime/runtime.d.ts +191 -0
  205. package/dist/runtime/runtime.js +718 -0
  206. package/dist/runtime/types.d.ts +153 -0
  207. package/dist/runtime/types.js +9 -0
  208. package/dist/runtime/workspace/detectors.d.ts +15 -0
  209. package/dist/runtime/workspace/detectors.js +57 -0
  210. package/dist/runtime/workspace/workspace.d.ts +29 -0
  211. package/dist/runtime/workspace/workspace.js +116 -0
  212. package/dist/security/credentials.d.ts +26 -0
  213. package/dist/security/credentials.js +34 -0
  214. package/dist/security/redact.d.ts +16 -0
  215. package/dist/security/redact.js +57 -0
  216. package/dist/skills/builtins/fileAnalyzer.d.ts +7 -0
  217. package/dist/skills/builtins/fileAnalyzer.js +47 -0
  218. package/dist/skills/builtins/repositoryAnalyzer.d.ts +6 -0
  219. package/dist/skills/builtins/repositoryAnalyzer.js +47 -0
  220. package/dist/skills/discovery.d.ts +61 -0
  221. package/dist/skills/discovery.js +211 -0
  222. package/dist/skills/manifest.d.ts +30 -0
  223. package/dist/skills/manifest.js +75 -0
  224. package/dist/skills/registry.d.ts +15 -0
  225. package/dist/skills/registry.js +22 -0
  226. package/dist/skills/skill.d.ts +64 -0
  227. package/dist/skills/skill.js +8 -0
  228. package/dist/store/area.d.ts +54 -0
  229. package/dist/store/area.js +164 -0
  230. package/dist/store/paths.d.ts +15 -0
  231. package/dist/store/paths.js +48 -0
  232. package/dist/store/store.d.ts +59 -0
  233. package/dist/store/store.js +140 -0
  234. package/dist/telemetry/sinks/file.d.ts +12 -0
  235. package/dist/telemetry/sinks/file.js +28 -0
  236. package/dist/telemetry/telemetry.d.ts +36 -0
  237. package/dist/telemetry/telemetry.js +63 -0
  238. package/dist/tools/builtins/filesystem.d.ts +7 -0
  239. package/dist/tools/builtins/filesystem.js +53 -0
  240. package/dist/tools/builtins/git.d.ts +10 -0
  241. package/dist/tools/builtins/git.js +66 -0
  242. package/dist/tools/builtins/shell.d.ts +17 -0
  243. package/dist/tools/builtins/shell.js +91 -0
  244. package/dist/tools/jail.d.ts +12 -0
  245. package/dist/tools/jail.js +98 -0
  246. package/dist/tools/permissions.d.ts +25 -0
  247. package/dist/tools/permissions.js +24 -0
  248. package/dist/tools/registry.d.ts +10 -0
  249. package/dist/tools/registry.js +20 -0
  250. package/dist/tools/runner.d.ts +23 -0
  251. package/dist/tools/runner.js +64 -0
  252. package/dist/tools/tool.d.ts +53 -0
  253. package/dist/tools/tool.js +24 -0
  254. package/dist/tools/untrusted.d.ts +13 -0
  255. package/dist/tools/untrusted.js +30 -0
  256. package/dist/types.d.ts +460 -0
  257. package/dist/types.js +12 -0
  258. package/dist/util/clock.d.ts +6 -0
  259. package/dist/util/clock.js +4 -0
  260. package/dist/util/extractJson.d.ts +8 -0
  261. package/dist/util/extractJson.js +54 -0
  262. package/dist/verification/verify.d.ts +26 -0
  263. package/dist/verification/verify.js +67 -0
  264. package/docs/GUIDE.md +358 -0
  265. package/docs/README.md +21 -0
  266. package/docs/architecture.md +78 -0
  267. package/docs/router.md +376 -0
  268. package/docs/security.md +55 -0
  269. package/package.json +67 -0
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Human-readable rendering of a ComparisonResult for the terminal (REPL + one-shot). Pure: turns the
3
+ * structured result into lines. It never claims a verdict the ranking did not make — a judge verdict is
4
+ * labeled as opinion, and a report-only comparison shows no winner.
5
+ */
6
+ function candidateLine(c) {
7
+ const dot = c.ok ? '●' : '○';
8
+ const who = c.selected ? `${c.selected.providerId}/${c.selected.model}` : c.pin.provider ?? '?';
9
+ if (!c.ok)
10
+ return ` ${dot} ${c.label.padEnd(16)} ${who.padEnd(22)} [error: ${c.error?.category ?? 'failed'}]`;
11
+ const conf = c.confidence !== undefined ? `conf ${c.confidence.toFixed(2)}` : '';
12
+ const lat = c.latencyMs !== undefined ? `${c.latencyMs}ms` : '';
13
+ return ` ${dot} ${c.label.padEnd(16)} ${who.padEnd(22)} ${conf.padEnd(10)} ${lat}`;
14
+ }
15
+ export function renderComparison(result) {
16
+ const lines = [];
17
+ const okCount = result.candidates.filter((c) => c.ok).length;
18
+ lines.push(`Compared "${result.goal}" across ${result.usage.ran} model(s) — ${okCount} responded:`);
19
+ for (const c of result.candidates)
20
+ lines.push(candidateLine(c));
21
+ const { analysis, ranking } = result;
22
+ // Agreement.
23
+ if (analysis.unanimous) {
24
+ lines.push('Agreement: unanimous (all responses agree).');
25
+ }
26
+ else if (analysis.clusters.length > 1) {
27
+ lines.push(`Agreement: ${analysis.clusters.length} groups — ${analysis.clusters.map((cl) => `{${cl.labels.join(', ')}}`).join(' vs ')}`);
28
+ }
29
+ // Differences.
30
+ if (analysis.differences.length) {
31
+ lines.push('Differences:');
32
+ for (const d of analysis.differences.slice(0, 5))
33
+ lines.push(` ${d.a} ↔ ${d.b}: ${Math.round(d.similarity * 100)}% similar`);
34
+ }
35
+ // Contradictions (structured only).
36
+ if (analysis.contradictions.length) {
37
+ lines.push('Contradictions:');
38
+ for (const ct of analysis.contradictions)
39
+ lines.push(` field "${ct.field}": ${ct.values.map((v) => `${v.label}=${v.value}`).join(', ')}`);
40
+ }
41
+ // Missing information.
42
+ for (const m of analysis.missing) {
43
+ if (!m.points.length)
44
+ continue;
45
+ lines.push(`Only in ${m.label}:`);
46
+ for (const p of m.points.slice(0, 3))
47
+ lines.push(` - ${p.length > 100 ? p.slice(0, 97) + '…' : p}`);
48
+ }
49
+ // Verdict.
50
+ if (ranking.method === 'evidence') {
51
+ const top = ranking.ranked[0];
52
+ lines.push(ranking.winner ? `Verdict (evidence): ${ranking.winner} — ${whyFor(ranking, ranking.winner)}` : `Verdict (evidence): no candidate passed validation${top ? ` (best: ${top.label}, ${top.why})` : ''}`);
53
+ }
54
+ else if (ranking.method === 'judge') {
55
+ lines.push(`Verdict (judge opinion, not evidence): ${ranking.winner} — ${whyFor(ranking, ranking.winner)}`);
56
+ }
57
+ else {
58
+ lines.push('No verdict: report only (provide a validate() for evidence, or a judge model for an opinion).');
59
+ }
60
+ if (result.usage.note)
61
+ lines.push(`(${result.usage.note})`);
62
+ return lines;
63
+ }
64
+ function whyFor(ranking, label) {
65
+ return ranking.ranked.find((r) => r.label === label)?.why ?? '';
66
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Default weights, timeouts, and policy posture. Weights sum to 100 (capabilityFit 30 / quality 20 /
3
+ * reliability 15 / historicalSuccess 10 / latency 10 / cost 10 / userPreference 5). All are
4
+ * overridable via config.
5
+ */
6
+ import type { Evidence, ScoreWeights, Sensitivity, Strategy } from '../types.js';
7
+ import type { RouterConfig } from '../types.js';
8
+ export declare const DEFAULT_WEIGHTS: ScoreWeights;
9
+ export interface ResolvedPrivacy {
10
+ allowCloud: boolean;
11
+ allowLocal: boolean;
12
+ sensitiveDataAllowedOnCloud: boolean;
13
+ treatUnknownAsSensitive: boolean;
14
+ defaultSensitivity: Sensitivity;
15
+ }
16
+ export interface ResolvedConfig {
17
+ providers: RouterConfig['providers'];
18
+ strategy: Strategy;
19
+ weights: ScoreWeights;
20
+ timeoutMs: number;
21
+ maxFallbacks: number;
22
+ minEvidence: Evidence;
23
+ minConfidence: number;
24
+ privacy: ResolvedPrivacy;
25
+ telemetry: {
26
+ enabled: boolean;
27
+ sink: 'memory' | 'file';
28
+ storePrompts: false;
29
+ path?: string;
30
+ };
31
+ learning: {
32
+ enabled: boolean;
33
+ };
34
+ verification: {
35
+ enabled: boolean;
36
+ };
37
+ budget: {
38
+ maxCostUsd?: number;
39
+ maxCalls?: number;
40
+ };
41
+ policy: NonNullable<RouterConfig['policy']>;
42
+ tasks: NonNullable<RouterConfig['tasks']>;
43
+ }
44
+ export declare const DEFAULTS: {
45
+ strategy: Strategy;
46
+ timeoutMs: number;
47
+ maxFallbacks: number;
48
+ minEvidence: Evidence;
49
+ minConfidence: number;
50
+ };
51
+ /** Merge a user config over the defaults into a fully-resolved config the router can rely on. */
52
+ export declare function resolveConfig(cfg: RouterConfig): ResolvedConfig;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Default weights, timeouts, and policy posture. Weights sum to 100 (capabilityFit 30 / quality 20 /
3
+ * reliability 15 / historicalSuccess 10 / latency 10 / cost 10 / userPreference 5). All are
4
+ * overridable via config.
5
+ */
6
+ export const DEFAULT_WEIGHTS = {
7
+ capabilityFit: 30,
8
+ quality: 20,
9
+ reliability: 15,
10
+ historicalSuccess: 10,
11
+ latency: 10,
12
+ cost: 10,
13
+ userPreference: 5,
14
+ };
15
+ export const DEFAULTS = {
16
+ strategy: 'best',
17
+ timeoutMs: 90_000,
18
+ maxFallbacks: 4,
19
+ minEvidence: 'documented',
20
+ minConfidence: 0.7,
21
+ };
22
+ /** Merge a user config over the defaults into a fully-resolved config the router can rely on. */
23
+ export function resolveConfig(cfg) {
24
+ const privacy = {
25
+ allowCloud: cfg.privacy?.allowCloud ?? true,
26
+ allowLocal: cfg.privacy?.allowLocal ?? true,
27
+ sensitiveDataAllowedOnCloud: cfg.privacy?.sensitiveDataAllowedOnCloud ?? false,
28
+ treatUnknownAsSensitive: cfg.privacy?.treatUnknownAsSensitive ?? false,
29
+ defaultSensitivity: cfg.privacy?.defaultSensitivity ?? 'unknown',
30
+ };
31
+ const telemetry = {
32
+ enabled: cfg.telemetry?.enabled ?? true,
33
+ sink: cfg.telemetry?.sink ?? 'memory',
34
+ storePrompts: false,
35
+ ...(cfg.telemetry?.path !== undefined ? { path: cfg.telemetry.path } : {}),
36
+ };
37
+ return {
38
+ providers: cfg.providers,
39
+ strategy: cfg.strategy ?? DEFAULTS.strategy,
40
+ weights: { ...DEFAULT_WEIGHTS, ...(cfg.weights ?? {}) },
41
+ timeoutMs: cfg.defaults?.timeoutMs ?? DEFAULTS.timeoutMs,
42
+ maxFallbacks: cfg.defaults?.maxFallbacks ?? DEFAULTS.maxFallbacks,
43
+ minEvidence: cfg.defaults?.minEvidence ?? DEFAULTS.minEvidence,
44
+ minConfidence: cfg.defaults?.minConfidence ?? DEFAULTS.minConfidence,
45
+ privacy,
46
+ telemetry,
47
+ learning: { enabled: cfg.learning?.enabled ?? true },
48
+ verification: { enabled: cfg.verification?.enabled ?? false },
49
+ budget: {
50
+ ...(cfg.budget?.maxCostUsd !== undefined ? { maxCostUsd: cfg.budget.maxCostUsd } : {}),
51
+ ...(cfg.budget?.maxCalls !== undefined ? { maxCalls: cfg.budget.maxCalls } : {}),
52
+ },
53
+ policy: cfg.policy ?? {},
54
+ tasks: cfg.tasks ?? [],
55
+ };
56
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Load a RouterConfig from a YAML/JSON file and populate the environment from `.env` (dotenv). The
3
+ * config file carries only structure and env-var NAMES; secret values live in `.env` (gitignored).
4
+ * Resolution: defaults ← file ← env (env-var values are read later by the credential layer).
5
+ */
6
+ import type { RouterConfig } from '../types.js';
7
+ export declare function findConfigFile(cwd?: string): string | undefined;
8
+ /** Load config from an explicit path or the first default file found; returns an empty config if none. */
9
+ export declare function loadConfig(path?: string, cwd?: string): RouterConfig;
10
+ /**
11
+ * Remote configuration. Fetch and validate a config from an http(s) URL. The response is
12
+ * still run through the same zod validation, so a remote config can no more carry an inline secret
13
+ * than a local one.
14
+ */
15
+ export declare function loadRemoteConfig(url: string): Promise<RouterConfig>;
16
+ /** Load from a local path or an http(s) URL. */
17
+ export declare function loadConfigAsync(pathOrUrl?: string, cwd?: string): Promise<RouterConfig>;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Load a RouterConfig from a YAML/JSON file and populate the environment from `.env` (dotenv). The
3
+ * config file carries only structure and env-var NAMES; secret values live in `.env` (gitignored).
4
+ * Resolution: defaults ← file ← env (env-var values are read later by the credential layer).
5
+ */
6
+ import { existsSync, readFileSync } from 'node:fs';
7
+ import { resolve } from 'node:path';
8
+ import { parse as parseYaml } from 'yaml';
9
+ import { config as loadDotenv } from 'dotenv';
10
+ import { parseConfig } from './schema.js';
11
+ import { AIError } from '../core/fallback/errors.js';
12
+ const DEFAULT_FILES = ['ai-runtime.yaml', 'ai-runtime.yml', 'ai-runtime.json'];
13
+ export function findConfigFile(cwd = process.cwd()) {
14
+ for (const name of DEFAULT_FILES) {
15
+ const full = resolve(cwd, name);
16
+ if (existsSync(full))
17
+ return full;
18
+ }
19
+ return undefined;
20
+ }
21
+ /** Load config from an explicit path or the first default file found; returns an empty config if none. */
22
+ export function loadConfig(path, cwd = process.cwd()) {
23
+ loadDotenv({ quiet: true });
24
+ const file = path ? resolve(cwd, path) : findConfigFile(cwd);
25
+ if (!file || !existsSync(file))
26
+ return { providers: [] };
27
+ const text = readFileSync(file, 'utf8');
28
+ const raw = file.endsWith('.json') ? JSON.parse(text) : parseYaml(text);
29
+ return parseConfig(raw);
30
+ }
31
+ /**
32
+ * Remote configuration. Fetch and validate a config from an http(s) URL. The response is
33
+ * still run through the same zod validation, so a remote config can no more carry an inline secret
34
+ * than a local one.
35
+ */
36
+ export async function loadRemoteConfig(url) {
37
+ loadDotenv({ quiet: true });
38
+ const res = await fetch(url);
39
+ if (!res.ok)
40
+ throw new AIError(`failed to fetch remote config (HTTP ${res.status})`, { category: 'CONFIG' });
41
+ const text = await res.text();
42
+ const looksJson = url.endsWith('.json') || text.trimStart().startsWith('{');
43
+ return parseConfig(looksJson ? JSON.parse(text) : parseYaml(text));
44
+ }
45
+ /** Load from a local path or an http(s) URL. */
46
+ export async function loadConfigAsync(pathOrUrl, cwd = process.cwd()) {
47
+ if (pathOrUrl && /^https?:\/\//i.test(pathOrUrl))
48
+ return loadRemoteConfig(pathOrUrl);
49
+ return loadConfig(pathOrUrl, cwd);
50
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Per-vendor transport defaults — base URL, wire shape, privacy class, default models, and the
3
+ * conventional env var name for the key. Vendor knowledge lives here (config layer), never in core.
4
+ */
5
+ import type { PrivacyClass, ProviderKind } from '../types.js';
6
+ import type { WireShape } from '../providers/wire/types.js';
7
+ export interface ProviderDefault {
8
+ name: string;
9
+ baseUrl?: string;
10
+ wireShape: WireShape;
11
+ privacyClass: PrivacyClass;
12
+ defaultModels: string[];
13
+ apiKeyEnv?: string;
14
+ requiresKey: boolean;
15
+ supportsModelListing: boolean;
16
+ }
17
+ export declare const PROVIDER_DEFAULTS: Record<Exclude<ProviderKind, 'mock'>, ProviderDefault>;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Per-vendor transport defaults — base URL, wire shape, privacy class, default models, and the
3
+ * conventional env var name for the key. Vendor knowledge lives here (config layer), never in core.
4
+ */
5
+ export const PROVIDER_DEFAULTS = {
6
+ gemini: {
7
+ name: 'Google Gemini',
8
+ baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai',
9
+ wireShape: 'openai',
10
+ privacyClass: 'cloud',
11
+ defaultModels: ['gemini-3.6-flash', 'gemini-2.5-flash-lite'],
12
+ apiKeyEnv: 'GEMINI_API_KEY',
13
+ requiresKey: true,
14
+ supportsModelListing: true,
15
+ },
16
+ groq: {
17
+ name: 'Groq',
18
+ baseUrl: 'https://api.groq.com/openai/v1',
19
+ wireShape: 'openai',
20
+ privacyClass: 'cloud',
21
+ defaultModels: ['llama-3.3-70b'],
22
+ apiKeyEnv: 'GROQ_API_KEY',
23
+ requiresKey: true,
24
+ supportsModelListing: true,
25
+ },
26
+ anthropic: {
27
+ name: 'Anthropic',
28
+ baseUrl: 'https://api.anthropic.com',
29
+ wireShape: 'anthropic',
30
+ privacyClass: 'cloud',
31
+ defaultModels: ['claude-sonnet-5', 'claude-haiku-4-5'],
32
+ apiKeyEnv: 'ANTHROPIC_API_KEY',
33
+ requiresKey: true,
34
+ supportsModelListing: false,
35
+ },
36
+ ollama: {
37
+ name: 'Ollama (local)',
38
+ baseUrl: 'http://localhost:11434/v1',
39
+ wireShape: 'openai',
40
+ privacyClass: 'local',
41
+ defaultModels: ['llama3.1'],
42
+ requiresKey: false,
43
+ supportsModelListing: true,
44
+ },
45
+ 'openai-compatible': {
46
+ name: 'OpenAI-compatible',
47
+ wireShape: 'openai',
48
+ privacyClass: 'cloud',
49
+ defaultModels: [],
50
+ requiresKey: true,
51
+ supportsModelListing: true,
52
+ },
53
+ custom: {
54
+ name: 'Custom',
55
+ wireShape: 'openai',
56
+ privacyClass: 'cloud',
57
+ defaultModels: [],
58
+ requiresKey: true,
59
+ supportsModelListing: false,
60
+ },
61
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Config validation with zod. Runs at load time so a malformed config fails with an actionable
3
+ * message instead of a runtime 404 later. Provider blocks are `.strict()` — an unknown key (e.g. a
4
+ * stray inline `apiKey`) is rejected, which structurally prevents committing a raw secret in config.
5
+ */
6
+ import type { RouterConfig } from '../types.js';
7
+ /** Canonical strategy names. Exported for reuse by the runtime-config layer (no duplication). */
8
+ export declare const STRATEGIES: readonly ["best", "fastest", "cheapest", "highest-quality", "local-only", "cloud-only", "privacy-first", "provider-specific", "fallback-only"];
9
+ export declare function parseConfig(raw: unknown): RouterConfig;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Config validation with zod. Runs at load time so a malformed config fails with an actionable
3
+ * message instead of a runtime 404 later. Provider blocks are `.strict()` — an unknown key (e.g. a
4
+ * stray inline `apiKey`) is rejected, which structurally prevents committing a raw secret in config.
5
+ */
6
+ import { z } from 'zod';
7
+ import { AIError } from '../core/fallback/errors.js';
8
+ /** Canonical strategy names. Exported for reuse by the runtime-config layer (no duplication). */
9
+ export const STRATEGIES = ['best', 'fastest', 'cheapest', 'highest-quality', 'local-only', 'cloud-only', 'privacy-first', 'provider-specific', 'fallback-only'];
10
+ const EVIDENCE = ['unsupported', 'unknown', 'inferred', 'documented', 'verified'];
11
+ const GROUPS = ['input', 'output', 'intelligence', 'agent'];
12
+ const KINDS = ['openai-compatible', 'gemini', 'groq', 'anthropic', 'ollama', 'custom', 'mock'];
13
+ const KEY_LIKE = /^(sk-|gsk_|Bearer\s|[A-Za-z0-9_-]{40,}$)/;
14
+ const capabilityRequirement = z.object({
15
+ group: z.enum(GROUPS),
16
+ key: z.string().min(1),
17
+ minEvidence: z.enum(EVIDENCE).optional(),
18
+ weight: z.number().optional(),
19
+ });
20
+ const taskDefinition = z
21
+ .object({
22
+ id: z.string().min(1),
23
+ aliases: z.array(z.string()).optional(),
24
+ description: z.string().optional(),
25
+ required: z.array(capabilityRequirement),
26
+ preferred: z.array(capabilityRequirement).optional(),
27
+ minContextWindow: z.number().optional(),
28
+ output: z.object({ format: z.enum(['text', 'json', 'structured_output', 'code']), schema: z.unknown().optional() }).optional(),
29
+ defaultStrategy: z.enum(STRATEGIES).optional(),
30
+ privacy: z.object({ requireLocal: z.boolean().optional() }).optional(),
31
+ qualityFloor: z.enum(['frontier', 'strong', 'mid', 'small']).optional(),
32
+ })
33
+ .strict();
34
+ const providerConfig = z
35
+ .object({
36
+ id: z.string().min(1),
37
+ kind: z.enum(KINDS),
38
+ baseUrl: z.string().url().optional(),
39
+ apiKeyEnv: z
40
+ .string()
41
+ .optional()
42
+ .refine((v) => v === undefined || !KEY_LIKE.test(v), { message: 'apiKeyEnv must be an env-var NAME, not a key value' }),
43
+ enabled: z.boolean().optional(),
44
+ models: z.union([z.array(z.string()), z.literal('auto')]).optional(),
45
+ defaultModel: z.string().optional(),
46
+ capabilities: z
47
+ .object({ input: z.array(z.string()).optional(), output: z.array(z.string()).optional(), intelligence: z.array(z.string()).optional(), agent: z.array(z.string()).optional(), contextWindow: z.number().optional() })
48
+ .strict()
49
+ .optional(),
50
+ privacyClass: z.enum(['local', 'cloud']).optional(),
51
+ wireShape: z.enum(['openai', 'anthropic']).optional(),
52
+ headers: z.record(z.string()).optional(),
53
+ weightOverrides: z.record(z.number()).optional(),
54
+ })
55
+ .strict()
56
+ .refine((p) => !(['openai-compatible', 'custom'].includes(p.kind) && !p.baseUrl), { message: 'openai-compatible/custom providers require a baseUrl' });
57
+ const routerConfig = z
58
+ .object({
59
+ providers: z.array(providerConfig),
60
+ strategy: z.enum(STRATEGIES).optional(),
61
+ weights: z.record(z.number()).optional(),
62
+ defaults: z.object({ timeoutMs: z.number().optional(), maxFallbacks: z.number().optional(), minEvidence: z.enum(EVIDENCE).optional(), minConfidence: z.number().optional() }).strict().optional(),
63
+ privacy: z
64
+ .object({ allowCloud: z.boolean().optional(), allowLocal: z.boolean().optional(), sensitiveDataAllowedOnCloud: z.boolean().optional(), treatUnknownAsSensitive: z.boolean().optional(), defaultSensitivity: z.enum(['low', 'high', 'unknown']).optional() })
65
+ .strict()
66
+ .optional(),
67
+ telemetry: z.object({ enabled: z.boolean().optional(), sink: z.enum(['memory', 'file']).optional(), storePrompts: z.literal(false).optional(), path: z.string().optional() }).strict().optional(),
68
+ tasks: z.array(taskDefinition).optional(),
69
+ })
70
+ .strict();
71
+ export function parseConfig(raw) {
72
+ const result = routerConfig.safeParse(raw);
73
+ if (!result.success) {
74
+ const issues = result.error.issues.map((i) => ` - ${i.path.join('.') || '(root)'}: ${i.message}`).join('\n');
75
+ throw new AIError(`invalid ai-runtime config:\n${issues}`, { category: 'CONFIG' });
76
+ }
77
+ return result.data;
78
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Context-budget resolution. Governs the COMPILED CONTEXT size only (system prompt + assembled
3
+ * context) — distinct from provider input/output/total token usage, call count, and cost. Precedence:
4
+ * per-run > project config > env (AI_CONTEXT_MAX_TOKENS) > model-aware default.
5
+ */
6
+ export declare const DEFAULT_CONTEXT_TOKENS = 8000;
7
+ export interface ContextBudgetInputs {
8
+ perRun?: number;
9
+ config?: number;
10
+ env?: string;
11
+ modelDefault?: number;
12
+ }
13
+ export declare function resolveContextBudget(i: ContextBudgetInputs): number;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Context-budget resolution. Governs the COMPILED CONTEXT size only (system prompt + assembled
3
+ * context) — distinct from provider input/output/total token usage, call count, and cost. Precedence:
4
+ * per-run > project config > env (AI_CONTEXT_MAX_TOKENS) > model-aware default.
5
+ */
6
+ export const DEFAULT_CONTEXT_TOKENS = 8000;
7
+ function positive(n) {
8
+ return n !== undefined && Number.isFinite(n) && n > 0 ? n : undefined;
9
+ }
10
+ function fromEnv(raw) {
11
+ if (raw === undefined || raw.trim() === '')
12
+ return undefined;
13
+ return positive(Number(raw));
14
+ }
15
+ export function resolveContextBudget(i) {
16
+ return positive(i.perRun) ?? positive(i.config) ?? fromEnv(i.env) ?? positive(i.modelDefault) ?? DEFAULT_CONTEXT_TOKENS;
17
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * ContextCompiler — turns RuntimeContext-derived blocks into a compiled model context under a token
3
+ * budget. Pipeline: rank (by retention, then caller order) → dedupe → protect (critical kept verbatim)
4
+ * → compress/drop the rest to fit → assemble → validate (task-critical items survived). The output is a
5
+ * transient model input, NOT a runtime-state abstraction, and input blocks are never mutated
6
+ * (recoverability). Artifact blocks are inlined only when their full content fits; otherwise a compact
7
+ * reference is emitted.
8
+ */
9
+ import { TokenEstimator } from './tokens.js';
10
+ export type RetentionLevel = 'critical' | 'high' | 'normal' | 'discardable';
11
+ export interface ContextBlock {
12
+ id: string;
13
+ kind: string;
14
+ text: string;
15
+ retention: RetentionLevel;
16
+ /** Artifact blocks: lazily resolve full content when the compiler decides to inline it. */
17
+ resolveContent?: () => string;
18
+ /** Artifact blocks: the compact reference emitted when full content does not fit. */
19
+ reference?: string;
20
+ }
21
+ export interface CompiledBlock {
22
+ id: string;
23
+ kind: string;
24
+ retention: RetentionLevel;
25
+ text: string;
26
+ /** True when the block was truncated or replaced by a reference to fit the budget. */
27
+ compressed: boolean;
28
+ }
29
+ export interface CompileMetrics {
30
+ originalTokens: number;
31
+ compiledTokens: number;
32
+ budgetTokens: number;
33
+ ratio: number;
34
+ protectedCount: number;
35
+ retrievedCount: number;
36
+ compressedCount: number;
37
+ droppedCount: number;
38
+ /** True when protected (critical) content alone exceeded the budget. */
39
+ overBudget: boolean;
40
+ }
41
+ export interface ContextValidation {
42
+ ok: boolean;
43
+ checks: Array<{
44
+ name: string;
45
+ ok: boolean;
46
+ why?: string;
47
+ }>;
48
+ }
49
+ export interface CompiledContext {
50
+ system: string;
51
+ blocks: CompiledBlock[];
52
+ dropped: string[];
53
+ metrics: CompileMetrics;
54
+ validation: ContextValidation;
55
+ }
56
+ export interface CompileOptions {
57
+ budgetTokens: number;
58
+ estimator?: TokenEstimator;
59
+ separator?: string;
60
+ }
61
+ export declare function compileContext(blocks: ContextBlock[], opts: CompileOptions): CompiledContext;
@@ -0,0 +1,125 @@
1
+ /**
2
+ * ContextCompiler — turns RuntimeContext-derived blocks into a compiled model context under a token
3
+ * budget. Pipeline: rank (by retention, then caller order) → dedupe → protect (critical kept verbatim)
4
+ * → compress/drop the rest to fit → assemble → validate (task-critical items survived). The output is a
5
+ * transient model input, NOT a runtime-state abstraction, and input blocks are never mutated
6
+ * (recoverability). Artifact blocks are inlined only when their full content fits; otherwise a compact
7
+ * reference is emitted.
8
+ */
9
+ import { TokenEstimator } from './tokens.js';
10
+ const RANK = { critical: 0, high: 1, normal: 2, discardable: 3 };
11
+ const MIN_COMPRESS_TOKENS = 4; // below this, truncation is useless — drop instead
12
+ /** Extract identifier-like tokens (paths, file.ext, quoted strings, ALL_CAPS ids) for loss validation. */
13
+ function identifiers(text) {
14
+ const out = new Set();
15
+ for (const m of text.matchAll(/[A-Za-z0-9_./-]+\.[A-Za-z0-9]+/g))
16
+ out.add(m[0]); // file.ext / path.seg
17
+ for (const m of text.matchAll(/\b[A-Z][A-Z0-9_]{2,}\b/g))
18
+ out.add(m[0]); // ALL_CAPS_IDS
19
+ return [...out];
20
+ }
21
+ export function compileContext(blocks, opts) {
22
+ const estimator = opts.estimator ?? new TokenEstimator();
23
+ const sep = opts.separator ?? '\n\n';
24
+ const sepTokens = estimator.estimate(sep);
25
+ const budget = Math.max(0, opts.budgetTokens);
26
+ const ranked = [...blocks].map((b, i) => ({ b, i })).sort((x, y) => RANK[x.b.retention] - RANK[y.b.retention] || x.i - y.i);
27
+ // Each kept entry carries its original caller index so assembly restores caller order even when two
28
+ // blocks happen to share an id (assembly must never key on id — that would collapse/duplicate text).
29
+ const kept = [];
30
+ const dropped = [];
31
+ const seen = new Set();
32
+ let used = 0;
33
+ let compressedCount = 0;
34
+ let overBudget = false;
35
+ const keep = (order, block, tokens, sepCost) => {
36
+ kept.push({ order, block });
37
+ used += sepCost + tokens;
38
+ };
39
+ for (const { b, i } of ranked) {
40
+ const full = b.resolveContent ? b.resolveContent() : b.text;
41
+ const norm = full.trim();
42
+ if (norm && seen.has(norm)) {
43
+ dropped.push(b.id); // redundant duplicate
44
+ continue;
45
+ }
46
+ const sepCost = kept.length ? sepTokens : 0;
47
+ const fullTokens = estimator.estimate(full);
48
+ const remaining = budget - used - sepCost;
49
+ if (b.retention === 'critical') {
50
+ // Protected: kept verbatim even if it pushes past the budget.
51
+ keep(i, { id: b.id, kind: b.kind, retention: b.retention, text: full, compressed: false }, fullTokens, sepCost);
52
+ seen.add(norm);
53
+ if (used > budget)
54
+ overBudget = true;
55
+ continue;
56
+ }
57
+ if (fullTokens <= remaining) {
58
+ keep(i, { id: b.id, kind: b.kind, retention: b.retention, text: full, compressed: false }, fullTokens, sepCost);
59
+ seen.add(norm);
60
+ continue;
61
+ }
62
+ // Doesn't fit at full size.
63
+ if (b.retention === 'discardable') {
64
+ dropped.push(b.id);
65
+ continue;
66
+ }
67
+ // Artifact with a reference that fits → emit the reference.
68
+ if (b.resolveContent && b.reference && estimator.estimate(b.reference) <= remaining) {
69
+ keep(i, { id: b.id, kind: b.kind, retention: b.retention, text: b.reference, compressed: true }, estimator.estimate(b.reference), sepCost);
70
+ compressedCount += 1;
71
+ continue;
72
+ }
73
+ // Truncate to fit, if there's meaningful room; otherwise drop. Re-verify the truncated size fits —
74
+ // a drifted (sub-1) chars/token ratio can make even "…" exceed `remaining`.
75
+ if (remaining >= MIN_COMPRESS_TOKENS) {
76
+ const chars = estimator.charsForTokens(remaining) - 1;
77
+ const truncated = full.slice(0, Math.max(0, chars)) + '…';
78
+ const truncTokens = estimator.estimate(truncated);
79
+ if (chars > 0 && truncTokens <= remaining) {
80
+ keep(i, { id: b.id, kind: b.kind, retention: b.retention, text: truncated, compressed: true }, truncTokens, sepCost);
81
+ compressedCount += 1;
82
+ }
83
+ else {
84
+ dropped.push(b.id);
85
+ }
86
+ }
87
+ else {
88
+ dropped.push(b.id);
89
+ }
90
+ }
91
+ // Assemble in the caller's original order (rank drove inclusion, not layout). Sorted by original index,
92
+ // not keyed by id, so duplicate ids can never collapse or double a block.
93
+ const ordered = [...kept].sort((a, z) => a.order - z.order);
94
+ const system = ordered.map((k) => k.block.text).join(sep);
95
+ const keptBlocks = ordered.map((k) => k.block);
96
+ const originalTokens = blocks.reduce((s, b) => s + estimator.estimate(b.resolveContent ? b.resolveContent() : b.text), 0);
97
+ const compiledTokens = estimator.estimate(system);
98
+ const metrics = {
99
+ originalTokens,
100
+ compiledTokens,
101
+ budgetTokens: budget,
102
+ ratio: originalTokens ? compiledTokens / originalTokens : 1,
103
+ protectedCount: blocks.filter((b) => b.retention === 'critical').length,
104
+ retrievedCount: blocks.length,
105
+ compressedCount,
106
+ droppedCount: dropped.length,
107
+ overBudget,
108
+ };
109
+ return { system, blocks: keptBlocks, dropped, metrics, validation: validate(blocks, system, keptBlocks) };
110
+ }
111
+ /** Deterministic context-loss validation: critical blocks kept verbatim and their identifiers survived. */
112
+ function validate(input, system, kept) {
113
+ const checks = [];
114
+ const keptCritical = kept.filter((k) => k.retention === 'critical');
115
+ const critical = input.filter((b) => b.retention === 'critical');
116
+ // Validate against the text actually inlined for a critical block (resolved content when present).
117
+ const criticalText = (b) => (b.resolveContent ? b.resolveContent() : b.text);
118
+ const noneCompressed = keptCritical.every((k) => !k.compressed);
119
+ checks.push({ name: 'critical-verbatim', ok: noneCompressed, ...(noneCompressed ? {} : { why: 'a critical block was compressed' }) });
120
+ const allPresent = critical.every((b) => system.includes(criticalText(b).trim()));
121
+ checks.push({ name: 'critical-present', ok: allPresent, ...(allPresent ? {} : { why: 'a critical block is missing from the compiled context' }) });
122
+ const missingId = critical.flatMap((b) => identifiers(criticalText(b))).find((id) => !system.includes(id));
123
+ checks.push({ name: 'identifiers-preserved', ok: !missingId, ...(missingId ? { why: `identifier "${missingId}" was lost` } : {}) });
124
+ return { ok: checks.every((c) => c.ok), checks };
125
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Token ESTIMATION for context budgeting. These are heuristic estimates (≈ chars / charsPerToken),
3
+ * deliberately NOT conflated with provider-reported usage (which is authoritative and separate). The
4
+ * estimator can be calibrated over time from observed (chars, actualTokens) pairs via an EWMA, so the
5
+ * ratio drifts toward reality per deployment without ever claiming to be exact.
6
+ */
7
+ export declare class TokenEstimator {
8
+ private charsPerToken;
9
+ constructor(charsPerToken?: number);
10
+ /** Heuristic token estimate for a string. Always ≥ 0; a non-empty string estimates ≥ 1. */
11
+ estimate(text: string): number;
12
+ /** The chars→token ratio a token budget maps to (for compression sizing). */
13
+ charsForTokens(tokens: number): number;
14
+ /** Nudge the ratio toward an observed (chars, actualTokens) sample. Ignores degenerate samples. */
15
+ calibrate(chars: number, actualTokens: number): void;
16
+ ratio(): number;
17
+ }
18
+ /** Convenience: a one-off estimate with the default ratio. */
19
+ export declare function estimateTokens(text: string): number;