fixo-cli 1.0.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 (303) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +530 -0
  3. package/dist/agent/agent-client.d.ts +108 -0
  4. package/dist/agent/agent-client.d.ts.map +1 -0
  5. package/dist/agent/agent-client.js +1247 -0
  6. package/dist/agent/agent-client.js.map +1 -0
  7. package/dist/agent/agent-pool.d.ts +20 -0
  8. package/dist/agent/agent-pool.d.ts.map +1 -0
  9. package/dist/agent/agent-pool.js +217 -0
  10. package/dist/agent/agent-pool.js.map +1 -0
  11. package/dist/agent/background-awareness.d.ts +55 -0
  12. package/dist/agent/background-awareness.d.ts.map +1 -0
  13. package/dist/agent/background-awareness.js +104 -0
  14. package/dist/agent/background-awareness.js.map +1 -0
  15. package/dist/agent/command-parser.d.ts +33 -0
  16. package/dist/agent/command-parser.d.ts.map +1 -0
  17. package/dist/agent/command-parser.js +120 -0
  18. package/dist/agent/command-parser.js.map +1 -0
  19. package/dist/agent/context-budget.d.ts +91 -0
  20. package/dist/agent/context-budget.d.ts.map +1 -0
  21. package/dist/agent/context-budget.js +219 -0
  22. package/dist/agent/context-budget.js.map +1 -0
  23. package/dist/agent/conversation.d.ts +190 -0
  24. package/dist/agent/conversation.d.ts.map +1 -0
  25. package/dist/agent/conversation.js +547 -0
  26. package/dist/agent/conversation.js.map +1 -0
  27. package/dist/agent/hooks.d.ts +72 -0
  28. package/dist/agent/hooks.d.ts.map +1 -0
  29. package/dist/agent/hooks.js +214 -0
  30. package/dist/agent/hooks.js.map +1 -0
  31. package/dist/agent/mcp-bridge.d.ts +13 -0
  32. package/dist/agent/mcp-bridge.d.ts.map +1 -0
  33. package/dist/agent/mcp-bridge.js +86 -0
  34. package/dist/agent/mcp-bridge.js.map +1 -0
  35. package/dist/agent/mcp-client.d.ts +24 -0
  36. package/dist/agent/mcp-client.d.ts.map +1 -0
  37. package/dist/agent/mcp-client.js +146 -0
  38. package/dist/agent/mcp-client.js.map +1 -0
  39. package/dist/agent/mcp-manager.d.ts +13 -0
  40. package/dist/agent/mcp-manager.d.ts.map +1 -0
  41. package/dist/agent/mcp-manager.js +84 -0
  42. package/dist/agent/mcp-manager.js.map +1 -0
  43. package/dist/agent/mcp-registry.d.ts +45 -0
  44. package/dist/agent/mcp-registry.d.ts.map +1 -0
  45. package/dist/agent/mcp-registry.js +98 -0
  46. package/dist/agent/mcp-registry.js.map +1 -0
  47. package/dist/agent/orchestrator.d.ts +14 -0
  48. package/dist/agent/orchestrator.d.ts.map +1 -0
  49. package/dist/agent/orchestrator.js +118 -0
  50. package/dist/agent/orchestrator.js.map +1 -0
  51. package/dist/agent/parser-adapter.d.ts +120 -0
  52. package/dist/agent/parser-adapter.d.ts.map +1 -0
  53. package/dist/agent/parser-adapter.js +265 -0
  54. package/dist/agent/parser-adapter.js.map +1 -0
  55. package/dist/agent/parsers/imports.d.ts +11 -0
  56. package/dist/agent/parsers/imports.d.ts.map +1 -0
  57. package/dist/agent/parsers/imports.js +94 -0
  58. package/dist/agent/parsers/imports.js.map +1 -0
  59. package/dist/agent/parsers/shell.d.ts +23 -0
  60. package/dist/agent/parsers/shell.d.ts.map +1 -0
  61. package/dist/agent/parsers/shell.js +200 -0
  62. package/dist/agent/parsers/shell.js.map +1 -0
  63. package/dist/agent/parsers/symbols.d.ts +17 -0
  64. package/dist/agent/parsers/symbols.d.ts.map +1 -0
  65. package/dist/agent/parsers/symbols.js +103 -0
  66. package/dist/agent/parsers/symbols.js.map +1 -0
  67. package/dist/agent/permissions.d.ts +65 -0
  68. package/dist/agent/permissions.d.ts.map +1 -0
  69. package/dist/agent/permissions.js +219 -0
  70. package/dist/agent/permissions.js.map +1 -0
  71. package/dist/agent/predictive-gate.d.ts +69 -0
  72. package/dist/agent/predictive-gate.d.ts.map +1 -0
  73. package/dist/agent/predictive-gate.js +128 -0
  74. package/dist/agent/predictive-gate.js.map +1 -0
  75. package/dist/agent/provider-cooldown.d.ts +144 -0
  76. package/dist/agent/provider-cooldown.d.ts.map +1 -0
  77. package/dist/agent/provider-cooldown.js +300 -0
  78. package/dist/agent/provider-cooldown.js.map +1 -0
  79. package/dist/agent/providers-manager.d.ts +109 -0
  80. package/dist/agent/providers-manager.d.ts.map +1 -0
  81. package/dist/agent/providers-manager.js +464 -0
  82. package/dist/agent/providers-manager.js.map +1 -0
  83. package/dist/agent/repo-map.d.ts +6 -0
  84. package/dist/agent/repo-map.d.ts.map +1 -0
  85. package/dist/agent/repo-map.js +221 -0
  86. package/dist/agent/repo-map.js.map +1 -0
  87. package/dist/agent/retry.d.ts +103 -0
  88. package/dist/agent/retry.d.ts.map +1 -0
  89. package/dist/agent/retry.js +276 -0
  90. package/dist/agent/retry.js.map +1 -0
  91. package/dist/agent/search/index.d.ts +61 -0
  92. package/dist/agent/search/index.d.ts.map +1 -0
  93. package/dist/agent/search/index.js +314 -0
  94. package/dist/agent/search/index.js.map +1 -0
  95. package/dist/agent/single-agent.d.ts +76 -0
  96. package/dist/agent/single-agent.d.ts.map +1 -0
  97. package/dist/agent/single-agent.js +697 -0
  98. package/dist/agent/single-agent.js.map +1 -0
  99. package/dist/agent/skills.d.ts +22 -0
  100. package/dist/agent/skills.d.ts.map +1 -0
  101. package/dist/agent/skills.js +139 -0
  102. package/dist/agent/skills.js.map +1 -0
  103. package/dist/agent/stream-glue.d.ts +85 -0
  104. package/dist/agent/stream-glue.d.ts.map +1 -0
  105. package/dist/agent/stream-glue.js +120 -0
  106. package/dist/agent/stream-glue.js.map +1 -0
  107. package/dist/agent/subagent.d.ts +72 -0
  108. package/dist/agent/subagent.d.ts.map +1 -0
  109. package/dist/agent/subagent.js +193 -0
  110. package/dist/agent/subagent.js.map +1 -0
  111. package/dist/agent/telemetry.d.ts +192 -0
  112. package/dist/agent/telemetry.d.ts.map +1 -0
  113. package/dist/agent/telemetry.js +400 -0
  114. package/dist/agent/telemetry.js.map +1 -0
  115. package/dist/agent/tokenizer.d.ts +42 -0
  116. package/dist/agent/tokenizer.d.ts.map +1 -0
  117. package/dist/agent/tokenizer.js +107 -0
  118. package/dist/agent/tokenizer.js.map +1 -0
  119. package/dist/agent/tool-executor.d.ts +289 -0
  120. package/dist/agent/tool-executor.d.ts.map +1 -0
  121. package/dist/agent/tool-executor.js +2519 -0
  122. package/dist/agent/tool-executor.js.map +1 -0
  123. package/dist/agent/web-impl.d.ts +2 -0
  124. package/dist/agent/web-impl.d.ts.map +1 -0
  125. package/dist/agent/web-impl.js +34 -0
  126. package/dist/agent/web-impl.js.map +1 -0
  127. package/dist/agent/web.d.ts +8 -0
  128. package/dist/agent/web.d.ts.map +1 -0
  129. package/dist/agent/web.js +8 -0
  130. package/dist/agent/web.js.map +1 -0
  131. package/dist/agent/worker-agent.d.ts +27 -0
  132. package/dist/agent/worker-agent.d.ts.map +1 -0
  133. package/dist/agent/worker-agent.js +503 -0
  134. package/dist/agent/worker-agent.js.map +1 -0
  135. package/dist/config.d.ts +162 -0
  136. package/dist/config.d.ts.map +1 -0
  137. package/dist/config.js +138 -0
  138. package/dist/config.js.map +1 -0
  139. package/dist/context/fixo-md-watcher.d.ts +42 -0
  140. package/dist/context/fixo-md-watcher.d.ts.map +1 -0
  141. package/dist/context/fixo-md-watcher.js +126 -0
  142. package/dist/context/fixo-md-watcher.js.map +1 -0
  143. package/dist/context/fixo-md.d.ts +50 -0
  144. package/dist/context/fixo-md.d.ts.map +1 -0
  145. package/dist/context/fixo-md.js +118 -0
  146. package/dist/context/fixo-md.js.map +1 -0
  147. package/dist/context/todo.d.ts +65 -0
  148. package/dist/context/todo.d.ts.map +1 -0
  149. package/dist/context/todo.js +194 -0
  150. package/dist/context/todo.js.map +1 -0
  151. package/dist/git/git-manager.d.ts +33 -0
  152. package/dist/git/git-manager.d.ts.map +1 -0
  153. package/dist/git/git-manager.js +293 -0
  154. package/dist/git/git-manager.js.map +1 -0
  155. package/dist/git/git-ops.d.ts +10 -0
  156. package/dist/git/git-ops.d.ts.map +1 -0
  157. package/dist/git/git-ops.js +131 -0
  158. package/dist/git/git-ops.js.map +1 -0
  159. package/dist/index.d.ts +3 -0
  160. package/dist/index.d.ts.map +1 -0
  161. package/dist/index.js +352 -0
  162. package/dist/index.js.map +1 -0
  163. package/dist/indexer.d.ts +30 -0
  164. package/dist/indexer.d.ts.map +1 -0
  165. package/dist/indexer.js +273 -0
  166. package/dist/indexer.js.map +1 -0
  167. package/dist/lsp/lsp-client.d.ts +24 -0
  168. package/dist/lsp/lsp-client.d.ts.map +1 -0
  169. package/dist/lsp/lsp-client.js +205 -0
  170. package/dist/lsp/lsp-client.js.map +1 -0
  171. package/dist/lsp/lsp-manager.d.ts +17 -0
  172. package/dist/lsp/lsp-manager.d.ts.map +1 -0
  173. package/dist/lsp/lsp-manager.js +154 -0
  174. package/dist/lsp/lsp-manager.js.map +1 -0
  175. package/dist/lsp/lsp-pre-save.d.ts +137 -0
  176. package/dist/lsp/lsp-pre-save.d.ts.map +1 -0
  177. package/dist/lsp/lsp-pre-save.js +245 -0
  178. package/dist/lsp/lsp-pre-save.js.map +1 -0
  179. package/dist/lsp/syntax-fallback.d.ts +83 -0
  180. package/dist/lsp/syntax-fallback.d.ts.map +1 -0
  181. package/dist/lsp/syntax-fallback.js +275 -0
  182. package/dist/lsp/syntax-fallback.js.map +1 -0
  183. package/dist/model-outcomes.d.ts +12 -0
  184. package/dist/model-outcomes.d.ts.map +1 -0
  185. package/dist/model-outcomes.js +46 -0
  186. package/dist/model-outcomes.js.map +1 -0
  187. package/dist/planner.d.ts +32 -0
  188. package/dist/planner.d.ts.map +1 -0
  189. package/dist/planner.js +163 -0
  190. package/dist/planner.js.map +1 -0
  191. package/dist/project-memory.d.ts +29 -0
  192. package/dist/project-memory.d.ts.map +1 -0
  193. package/dist/project-memory.js +349 -0
  194. package/dist/project-memory.js.map +1 -0
  195. package/dist/review.d.ts +2 -0
  196. package/dist/review.d.ts.map +1 -0
  197. package/dist/review.js +61 -0
  198. package/dist/review.js.map +1 -0
  199. package/dist/runtime/background-jobs.d.ts +97 -0
  200. package/dist/runtime/background-jobs.d.ts.map +1 -0
  201. package/dist/runtime/background-jobs.js +331 -0
  202. package/dist/runtime/background-jobs.js.map +1 -0
  203. package/dist/runtime/credential-vault.d.ts +124 -0
  204. package/dist/runtime/credential-vault.d.ts.map +1 -0
  205. package/dist/runtime/credential-vault.js +184 -0
  206. package/dist/runtime/credential-vault.js.map +1 -0
  207. package/dist/runtime/loop-trap.d.ts +197 -0
  208. package/dist/runtime/loop-trap.d.ts.map +1 -0
  209. package/dist/runtime/loop-trap.js +420 -0
  210. package/dist/runtime/loop-trap.js.map +1 -0
  211. package/dist/runtime/policy.d.ts +15 -0
  212. package/dist/runtime/policy.d.ts.map +1 -0
  213. package/dist/runtime/policy.js +60 -0
  214. package/dist/runtime/policy.js.map +1 -0
  215. package/dist/runtime/redaction.d.ts +66 -0
  216. package/dist/runtime/redaction.d.ts.map +1 -0
  217. package/dist/runtime/redaction.js +155 -0
  218. package/dist/runtime/redaction.js.map +1 -0
  219. package/dist/runtime/session-snapshots.d.ts +76 -0
  220. package/dist/runtime/session-snapshots.d.ts.map +1 -0
  221. package/dist/runtime/session-snapshots.js +166 -0
  222. package/dist/runtime/session-snapshots.js.map +1 -0
  223. package/dist/runtime/staging.d.ts +205 -0
  224. package/dist/runtime/staging.d.ts.map +1 -0
  225. package/dist/runtime/staging.js +526 -0
  226. package/dist/runtime/staging.js.map +1 -0
  227. package/dist/runtime/task-session.d.ts +95 -0
  228. package/dist/runtime/task-session.d.ts.map +1 -0
  229. package/dist/runtime/task-session.js +263 -0
  230. package/dist/runtime/task-session.js.map +1 -0
  231. package/dist/runtime/worktree.d.ts +55 -0
  232. package/dist/runtime/worktree.d.ts.map +1 -0
  233. package/dist/runtime/worktree.js +175 -0
  234. package/dist/runtime/worktree.js.map +1 -0
  235. package/dist/setup-wizard.d.ts +8 -0
  236. package/dist/setup-wizard.d.ts.map +1 -0
  237. package/dist/setup-wizard.js +73 -0
  238. package/dist/setup-wizard.js.map +1 -0
  239. package/dist/shared/content.d.ts +43 -0
  240. package/dist/shared/content.d.ts.map +1 -0
  241. package/dist/shared/content.js +61 -0
  242. package/dist/shared/content.js.map +1 -0
  243. package/dist/shared/types.d.ts +217 -0
  244. package/dist/shared/types.d.ts.map +1 -0
  245. package/dist/shared/types.js +3 -0
  246. package/dist/shared/types.js.map +1 -0
  247. package/dist/test-runner.d.ts +5 -0
  248. package/dist/test-runner.d.ts.map +1 -0
  249. package/dist/test-runner.js +42 -0
  250. package/dist/test-runner.js.map +1 -0
  251. package/dist/types.d.ts +85 -0
  252. package/dist/types.d.ts.map +1 -0
  253. package/dist/types.js +2 -0
  254. package/dist/types.js.map +1 -0
  255. package/dist/ui/ascii.d.ts +23 -0
  256. package/dist/ui/ascii.d.ts.map +1 -0
  257. package/dist/ui/ascii.js +45 -0
  258. package/dist/ui/ascii.js.map +1 -0
  259. package/dist/ui/colors.d.ts +111 -0
  260. package/dist/ui/colors.d.ts.map +1 -0
  261. package/dist/ui/colors.js +166 -0
  262. package/dist/ui/colors.js.map +1 -0
  263. package/dist/ui/image-attach.d.ts +27 -0
  264. package/dist/ui/image-attach.d.ts.map +1 -0
  265. package/dist/ui/image-attach.js +100 -0
  266. package/dist/ui/image-attach.js.map +1 -0
  267. package/dist/ui/index.d.ts +18 -0
  268. package/dist/ui/index.d.ts.map +1 -0
  269. package/dist/ui/index.js +18 -0
  270. package/dist/ui/index.js.map +1 -0
  271. package/dist/ui/markdown-stream.d.ts +91 -0
  272. package/dist/ui/markdown-stream.d.ts.map +1 -0
  273. package/dist/ui/markdown-stream.js +524 -0
  274. package/dist/ui/markdown-stream.js.map +1 -0
  275. package/dist/ui/plan-renderer.d.ts +36 -0
  276. package/dist/ui/plan-renderer.d.ts.map +1 -0
  277. package/dist/ui/plan-renderer.js +79 -0
  278. package/dist/ui/plan-renderer.js.map +1 -0
  279. package/dist/ui/prompt.d.ts +11 -0
  280. package/dist/ui/prompt.d.ts.map +1 -0
  281. package/dist/ui/prompt.js +1960 -0
  282. package/dist/ui/prompt.js.map +1 -0
  283. package/dist/ui/render-primitives.d.ts +117 -0
  284. package/dist/ui/render-primitives.d.ts.map +1 -0
  285. package/dist/ui/render-primitives.js +322 -0
  286. package/dist/ui/render-primitives.js.map +1 -0
  287. package/dist/ui/render.d.ts +133 -0
  288. package/dist/ui/render.d.ts.map +1 -0
  289. package/dist/ui/render.js +547 -0
  290. package/dist/ui/render.js.map +1 -0
  291. package/dist/ui/session-header.d.ts +30 -0
  292. package/dist/ui/session-header.d.ts.map +1 -0
  293. package/dist/ui/session-header.js +74 -0
  294. package/dist/ui/session-header.js.map +1 -0
  295. package/dist/workspace-guard.d.ts +68 -0
  296. package/dist/workspace-guard.d.ts.map +1 -0
  297. package/dist/workspace-guard.js +168 -0
  298. package/dist/workspace-guard.js.map +1 -0
  299. package/dist/workspace-lock.d.ts +27 -0
  300. package/dist/workspace-lock.d.ts.map +1 -0
  301. package/dist/workspace-lock.js +95 -0
  302. package/dist/workspace-lock.js.map +1 -0
  303. package/package.json +63 -0
@@ -0,0 +1,144 @@
1
+ /**
2
+ * provider-cooldown.ts — Per-provider rate-limit and failure tracker.
3
+ *
4
+ * Tracks a per-provider failure history with a backoff schedule that
5
+ * grows exponentially on repeated 4xx/5xx errors and resets to zero on
6
+ * the next successful response. Used by `agent-client.ts` to:
7
+ *
8
+ * 1. Short-circuit new requests while a provider is in cooldown.
9
+ * 2. Surface cooldown state to higher layers so they can pick an
10
+ * alternative model/provider without re-trying the same doomed
11
+ * endpoint.
12
+ *
13
+ * All state is process-local and in-memory. Cooldowns are reset on
14
+ * process restart; this is intentional — there is no value in
15
+ * persisting transient rate-limit state across CLI invocations.
16
+ *
17
+ * Backoff schedule:
18
+ * - 429 / 402 / 403 (rate-limit family) → 30s, 60s, 120s, 240s, 300s cap
19
+ * - 5xx → 10s, 20s, 40s, 80s, 120s cap
20
+ * - Other retryable status → 15s, 30s, 60s, 120s, 180s cap
21
+ * - Non-retryable (4xx other) → no cooldown, just record
22
+ */
23
+ /** Buckets that share a backoff schedule. */
24
+ type BackoffFamily = 'rate_limit' | 'server_error' | 'other_retryable' | 'none';
25
+ /** Status code → backoff family. Exported for testability. */
26
+ export declare function classifyStatus(status: number): BackoffFamily;
27
+ /** Public cooldown state for a single provider. */
28
+ export interface ProviderCooldownEntry {
29
+ providerId: string;
30
+ consecutiveFailures: number;
31
+ lastFailureAt: number;
32
+ cooldownUntil: number;
33
+ lastErrorStatus: number | null;
34
+ lastErrorMessage?: string;
35
+ totalRequests: number;
36
+ totalFailures: number;
37
+ totalRateLimited: number;
38
+ /** Rolling success rate over the last `successWindowSize` requests, 0..1. */
39
+ successRate: number;
40
+ }
41
+ /** Result of a `suggestNext` lookup. */
42
+ export interface NextCandidate<T> {
43
+ id: string;
44
+ available: boolean;
45
+ cooldownMs: number;
46
+ payload: T;
47
+ }
48
+ export declare class ProviderInCooldownError extends Error {
49
+ readonly providerId: string;
50
+ readonly cooldownMs: number;
51
+ readonly until: number;
52
+ constructor(providerId: string, cooldownMs: number, until: number);
53
+ }
54
+ export declare class ProviderCooldownManager {
55
+ private readonly entries;
56
+ private readonly windowSize;
57
+ constructor(windowSize?: number);
58
+ /** Records a successful response from `providerId`. Resets failure state. */
59
+ recordSuccess(providerId: string, now?: number): void;
60
+ /**
61
+ * Records a failure. `status = 0` represents a network-layer error
62
+ * that did not produce an HTTP response. Returns the cooldown
63
+ * duration (ms) that the entry will be unavailable for, or 0 if
64
+ * the family is non-retryable.
65
+ */
66
+ recordFailure(providerId: string, status: number, message?: string, now?: number): number;
67
+ /**
68
+ * Returns true if the provider is not currently cooling down.
69
+ * Does not throw; callers that want strict "wait or fail" behaviour
70
+ * should use `assertAvailable` instead.
71
+ */
72
+ isAvailable(providerId: string, now?: number): boolean;
73
+ /** Returns the remaining cooldown in ms; 0 if available. */
74
+ getCooldownMs(providerId: string, now?: number): number;
75
+ /**
76
+ * Throws a `ProviderInCooldownError` if the provider is currently
77
+ * unavailable; returns silently otherwise.
78
+ */
79
+ assertAvailable(providerId: string, now?: number): void;
80
+ /**
81
+ * Returns a snapshot of the public cooldown state for `providerId`,
82
+ * or `undefined` if no requests have been recorded yet.
83
+ */
84
+ getEntry(providerId: string): ProviderCooldownEntry | undefined;
85
+ /** Returns snapshots for every tracked provider. */
86
+ getAll(): ProviderCooldownEntry[];
87
+ /**
88
+ * Picks the first available provider from `candidates`. If none is
89
+ * available, returns the one with the smallest remaining cooldown
90
+ * (the "least-bad" fallback so the caller can surface an informative
91
+ * error to the user). Returns `null` only when `candidates` is empty.
92
+ */
93
+ suggestNext<T>(candidates: ReadonlyArray<{
94
+ id: string;
95
+ } & T>, now?: number): NextCandidate<T> | null;
96
+ /** Clears state for one provider (or all providers if omitted). */
97
+ reset(providerId?: string): void;
98
+ /**
99
+ * Record a per-provider quality signal. The signal is a tuple of
100
+ * `(durationMs, resultCount)` — the search provider chain uses
101
+ * this to bias its next pick toward whichever provider is
102
+ * currently responding fastest *and* returning the most results.
103
+ *
104
+ * The implementation is deliberately minimal: a rolling
105
+ * per-provider EMA of `durationMs` and `resultCount`, exposed
106
+ * via {@link getQuality}. Future work can fold this into the
107
+ * `suggestNext` candidate ordering.
108
+ */
109
+ recordQuality(providerId: string, durationMs: number, resultCount: number, now?: number): void;
110
+ /** Read the per-provider quality snapshot, or null when no
111
+ * samples have been recorded. */
112
+ getQuality(providerId: string): {
113
+ samples: number;
114
+ avgDurationMs: number;
115
+ avgResultCount: number;
116
+ lastUpdatedAt: number;
117
+ } | null;
118
+ private getOrCreate;
119
+ private computeCooldownMs;
120
+ private pushRecent;
121
+ private toPublic;
122
+ }
123
+ /**
124
+ * Process-wide singleton used by `AgentClient`. Importing this rather
125
+ * than constructing a new instance ensures that all retry loops share
126
+ * the same provider health view across the lifetime of a single CLI
127
+ * invocation.
128
+ */
129
+ export declare const providerCooldown: ProviderCooldownManager;
130
+ /**
131
+ * Module-level helper used by the search chain. Routes a
132
+ * non-2xx response into the cooldown manager so the
133
+ * future picks avoid this provider until the cooldown
134
+ * expires.
135
+ */
136
+ export declare function recordProviderError(providerId: string, status: number, message: string): void;
137
+ /**
138
+ * Module-level helper for the search chain. Records a
139
+ * `(durationMs, resultCount)` sample; the manager maintains
140
+ * a rolling EMA and exposes it via `providerCooldown.getQuality`.
141
+ */
142
+ export declare function recordProviderQuality(providerId: string, durationMs: number, resultCount: number): void;
143
+ export {};
144
+ //# sourceMappingURL=provider-cooldown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-cooldown.d.ts","sourceRoot":"","sources":["../../src/agent/provider-cooldown.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,300 @@
1
+ /**
2
+ * provider-cooldown.ts — Per-provider rate-limit and failure tracker.
3
+ *
4
+ * Tracks a per-provider failure history with a backoff schedule that
5
+ * grows exponentially on repeated 4xx/5xx errors and resets to zero on
6
+ * the next successful response. Used by `agent-client.ts` to:
7
+ *
8
+ * 1. Short-circuit new requests while a provider is in cooldown.
9
+ * 2. Surface cooldown state to higher layers so they can pick an
10
+ * alternative model/provider without re-trying the same doomed
11
+ * endpoint.
12
+ *
13
+ * All state is process-local and in-memory. Cooldowns are reset on
14
+ * process restart; this is intentional — there is no value in
15
+ * persisting transient rate-limit state across CLI invocations.
16
+ *
17
+ * Backoff schedule:
18
+ * - 429 / 402 / 403 (rate-limit family) → 30s, 60s, 120s, 240s, 300s cap
19
+ * - 5xx → 10s, 20s, 40s, 80s, 120s cap
20
+ * - Other retryable status → 15s, 30s, 60s, 120s, 180s cap
21
+ * - Non-retryable (4xx other) → no cooldown, just record
22
+ */
23
+ const ONE_SECOND_MS = 1_000;
24
+ const ONE_MINUTE_MS = 60 * ONE_SECOND_MS;
25
+ /** Status code → backoff family. Exported for testability. */
26
+ export function classifyStatus(status) {
27
+ if (status === 429)
28
+ return 'rate_limit';
29
+ if (status >= 500 && status < 600)
30
+ return 'server_error';
31
+ if (status === 408 || status === 425 || status === 502 || status === 503 || status === 504) {
32
+ return 'server_error';
33
+ }
34
+ if (status >= 400 && status < 500) {
35
+ // Other 4xx — treated as a hard failure, no cooldown.
36
+ return 'none';
37
+ }
38
+ // Network-layer errors surface with status=0; treat as retryable-but-mild.
39
+ if (status === 0)
40
+ return 'other_retryable';
41
+ return 'none';
42
+ }
43
+ /** Per-family backoff schedule, in milliseconds. Index = consecutive failure count. */
44
+ const BACKOFF_SCHEDULES = {
45
+ rate_limit: [30_000, 60_000, 120_000, 240_000, 300_000],
46
+ server_error: [10_000, 20_000, 40_000, 80_000, 120_000],
47
+ other_retryable: [15_000, 30_000, 60_000, 120_000, 180_000],
48
+ };
49
+ const MAX_COOLDOWN_MS = 5 * ONE_MINUTE_MS;
50
+ export class ProviderInCooldownError extends Error {
51
+ providerId;
52
+ cooldownMs;
53
+ until;
54
+ constructor(providerId, cooldownMs, until) {
55
+ super(`Provider '${providerId}' is in cooldown for ${cooldownMs}ms ` +
56
+ `(until ${new Date(until).toISOString()})`);
57
+ this.name = 'ProviderInCooldownError';
58
+ this.providerId = providerId;
59
+ this.cooldownMs = cooldownMs;
60
+ this.until = until;
61
+ }
62
+ }
63
+ const SUCCESS_WINDOW = 100;
64
+ export class ProviderCooldownManager {
65
+ entries = new Map();
66
+ windowSize;
67
+ constructor(windowSize = SUCCESS_WINDOW) {
68
+ this.windowSize = Math.max(1, windowSize);
69
+ }
70
+ /** Records a successful response from `providerId`. Resets failure state. */
71
+ recordSuccess(providerId, now = Date.now()) {
72
+ const entry = this.getOrCreate(providerId);
73
+ entry.consecutiveFailures = 0;
74
+ entry.cooldownUntil = 0;
75
+ entry.lastErrorStatus = null;
76
+ entry.lastErrorMessage = undefined;
77
+ entry.totalRequests += 1;
78
+ this.pushRecent(entry, 1);
79
+ void now; // accepted for symmetry / testability
80
+ }
81
+ /**
82
+ * Records a failure. `status = 0` represents a network-layer error
83
+ * that did not produce an HTTP response. Returns the cooldown
84
+ * duration (ms) that the entry will be unavailable for, or 0 if
85
+ * the family is non-retryable.
86
+ */
87
+ recordFailure(providerId, status, message, now = Date.now()) {
88
+ const entry = this.getOrCreate(providerId);
89
+ entry.totalRequests += 1;
90
+ entry.totalFailures += 1;
91
+ entry.lastFailureAt = now;
92
+ entry.lastErrorStatus = status;
93
+ entry.lastErrorMessage = message;
94
+ this.pushRecent(entry, 0);
95
+ const family = classifyStatus(status);
96
+ if (family === 'none') {
97
+ // Hard failure: reset the consecutive counter so a future
98
+ // retryable error starts from zero.
99
+ entry.consecutiveFailures = 0;
100
+ entry.cooldownUntil = 0;
101
+ return 0;
102
+ }
103
+ entry.consecutiveFailures += 1;
104
+ if (status === 429)
105
+ entry.totalRateLimited += 1;
106
+ const cooldownMs = this.computeCooldownMs(family, entry.consecutiveFailures);
107
+ entry.cooldownUntil = now + cooldownMs;
108
+ return cooldownMs;
109
+ }
110
+ /**
111
+ * Returns true if the provider is not currently cooling down.
112
+ * Does not throw; callers that want strict "wait or fail" behaviour
113
+ * should use `assertAvailable` instead.
114
+ */
115
+ isAvailable(providerId, now = Date.now()) {
116
+ const entry = this.entries.get(providerId);
117
+ if (!entry)
118
+ return true;
119
+ if (entry.cooldownUntil === 0)
120
+ return true;
121
+ return entry.cooldownUntil <= now;
122
+ }
123
+ /** Returns the remaining cooldown in ms; 0 if available. */
124
+ getCooldownMs(providerId, now = Date.now()) {
125
+ const entry = this.entries.get(providerId);
126
+ if (!entry || entry.cooldownUntil === 0)
127
+ return 0;
128
+ const remaining = entry.cooldownUntil - now;
129
+ return remaining > 0 ? remaining : 0;
130
+ }
131
+ /**
132
+ * Throws a `ProviderInCooldownError` if the provider is currently
133
+ * unavailable; returns silently otherwise.
134
+ */
135
+ assertAvailable(providerId, now = Date.now()) {
136
+ const ms = this.getCooldownMs(providerId, now);
137
+ if (ms > 0) {
138
+ const until = now + ms;
139
+ throw new ProviderInCooldownError(providerId, ms, until);
140
+ }
141
+ }
142
+ /**
143
+ * Returns a snapshot of the public cooldown state for `providerId`,
144
+ * or `undefined` if no requests have been recorded yet.
145
+ */
146
+ getEntry(providerId) {
147
+ const entry = this.entries.get(providerId);
148
+ if (!entry)
149
+ return undefined;
150
+ return this.toPublic(providerId, entry);
151
+ }
152
+ /** Returns snapshots for every tracked provider. */
153
+ getAll() {
154
+ return Array.from(this.entries.entries())
155
+ .map(([id, entry]) => this.toPublic(id, entry))
156
+ .sort((a, b) => b.totalRequests - a.totalRequests);
157
+ }
158
+ /**
159
+ * Picks the first available provider from `candidates`. If none is
160
+ * available, returns the one with the smallest remaining cooldown
161
+ * (the "least-bad" fallback so the caller can surface an informative
162
+ * error to the user). Returns `null` only when `candidates` is empty.
163
+ */
164
+ suggestNext(candidates, now = Date.now()) {
165
+ if (candidates.length === 0)
166
+ return null;
167
+ let leastBad = null;
168
+ for (const c of candidates) {
169
+ const cooldownMs = this.getCooldownMs(c.id, now);
170
+ if (cooldownMs === 0) {
171
+ return { id: c.id, available: true, cooldownMs: 0, payload: c };
172
+ }
173
+ if (!leastBad || cooldownMs < leastBad.cooldownMs) {
174
+ leastBad = { id: c.id, available: false, cooldownMs, payload: c };
175
+ }
176
+ }
177
+ return leastBad;
178
+ }
179
+ /** Clears state for one provider (or all providers if omitted). */
180
+ reset(providerId) {
181
+ if (providerId === undefined) {
182
+ this.entries.clear();
183
+ }
184
+ else {
185
+ this.entries.delete(providerId);
186
+ }
187
+ }
188
+ /**
189
+ * Record a per-provider quality signal. The signal is a tuple of
190
+ * `(durationMs, resultCount)` — the search provider chain uses
191
+ * this to bias its next pick toward whichever provider is
192
+ * currently responding fastest *and* returning the most results.
193
+ *
194
+ * The implementation is deliberately minimal: a rolling
195
+ * per-provider EMA of `durationMs` and `resultCount`, exposed
196
+ * via {@link getQuality}. Future work can fold this into the
197
+ * `suggestNext` candidate ordering.
198
+ */
199
+ recordQuality(providerId, durationMs, resultCount, now = Date.now()) {
200
+ const entry = this.getOrCreate(providerId);
201
+ const prev = entry.quality;
202
+ const alpha = 0.4; // smoothing factor
203
+ const dur = Math.max(0, durationMs);
204
+ const count = Math.max(0, resultCount);
205
+ entry.quality = {
206
+ samples: prev.samples + 1,
207
+ avgDurationMs: prev.samples === 0
208
+ ? dur
209
+ : prev.avgDurationMs * (1 - alpha) + dur * alpha,
210
+ avgResultCount: prev.samples === 0
211
+ ? count
212
+ : prev.avgResultCount * (1 - alpha) + count * alpha,
213
+ lastUpdatedAt: now,
214
+ };
215
+ }
216
+ /** Read the per-provider quality snapshot, or null when no
217
+ * samples have been recorded. */
218
+ getQuality(providerId) {
219
+ const entry = this.entries.get(providerId);
220
+ if (!entry || entry.quality.samples === 0)
221
+ return null;
222
+ return { ...entry.quality };
223
+ }
224
+ // ──── internals ────────────────────────────────────────────────
225
+ getOrCreate(providerId) {
226
+ let entry = this.entries.get(providerId);
227
+ if (!entry) {
228
+ entry = {
229
+ consecutiveFailures: 0,
230
+ lastFailureAt: 0,
231
+ cooldownUntil: 0,
232
+ lastErrorStatus: null,
233
+ lastErrorMessage: undefined,
234
+ totalRequests: 0,
235
+ totalFailures: 0,
236
+ totalRateLimited: 0,
237
+ recent: [],
238
+ quality: { samples: 0, avgDurationMs: 0, avgResultCount: 0, lastUpdatedAt: 0 },
239
+ };
240
+ this.entries.set(providerId, entry);
241
+ }
242
+ return entry;
243
+ }
244
+ computeCooldownMs(family, consecutive) {
245
+ if (family === 'none')
246
+ return 0;
247
+ const schedule = BACKOFF_SCHEDULES[family];
248
+ const idx = Math.min(consecutive - 1, schedule.length - 1);
249
+ if (idx < 0)
250
+ return 0;
251
+ return Math.min(schedule[idx], MAX_COOLDOWN_MS);
252
+ }
253
+ pushRecent(entry, value) {
254
+ entry.recent.push(value);
255
+ if (entry.recent.length > this.windowSize) {
256
+ entry.recent.shift();
257
+ }
258
+ }
259
+ toPublic(providerId, entry) {
260
+ const successes = entry.recent.reduce((acc, v) => acc + v, 0);
261
+ const successRate = entry.recent.length === 0 ? 1 : successes / entry.recent.length;
262
+ return {
263
+ providerId,
264
+ consecutiveFailures: entry.consecutiveFailures,
265
+ lastFailureAt: entry.lastFailureAt,
266
+ cooldownUntil: entry.cooldownUntil,
267
+ lastErrorStatus: entry.lastErrorStatus,
268
+ lastErrorMessage: entry.lastErrorMessage,
269
+ totalRequests: entry.totalRequests,
270
+ totalFailures: entry.totalFailures,
271
+ totalRateLimited: entry.totalRateLimited,
272
+ successRate,
273
+ };
274
+ }
275
+ }
276
+ /**
277
+ * Process-wide singleton used by `AgentClient`. Importing this rather
278
+ * than constructing a new instance ensures that all retry loops share
279
+ * the same provider health view across the lifetime of a single CLI
280
+ * invocation.
281
+ */
282
+ export const providerCooldown = new ProviderCooldownManager();
283
+ /**
284
+ * Module-level helper used by the search chain. Routes a
285
+ * non-2xx response into the cooldown manager so the
286
+ * future picks avoid this provider until the cooldown
287
+ * expires.
288
+ */
289
+ export function recordProviderError(providerId, status, message) {
290
+ providerCooldown.recordFailure(providerId, status, message);
291
+ }
292
+ /**
293
+ * Module-level helper for the search chain. Records a
294
+ * `(durationMs, resultCount)` sample; the manager maintains
295
+ * a rolling EMA and exposes it via `providerCooldown.getQuality`.
296
+ */
297
+ export function recordProviderQuality(providerId, durationMs, resultCount) {
298
+ providerCooldown.recordQuality(providerId, durationMs, resultCount);
299
+ }
300
+ //# sourceMappingURL=provider-cooldown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-cooldown.js","sourceRoot":"","sources":["../../src/agent/provider-cooldown.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,109 @@
1
+ import { type ProviderCredential } from '../runtime/credential-vault.js';
2
+ export interface ProviderDefinition {
3
+ /** Short ID used as the key in providers.json */
4
+ name: string;
5
+ /** Human-readable display name */
6
+ displayName: string;
7
+ /** Base URL for the provider's API (OpenAI-compatible unless noted) */
8
+ baseUrl: string;
9
+ /** Whether this provider uses the standard OpenAI /chat/completions format */
10
+ openAICompat: boolean;
11
+ /** Example model IDs for the picker */
12
+ models: string[];
13
+ /** Whether an API key is required (some providers allow anonymous use) */
14
+ requiresKey: boolean;
15
+ /** Docs / sign-up URL for the provider */
16
+ docsUrl: string;
17
+ }
18
+ export declare const PROVIDER_REGISTRY: ProviderDefinition[];
19
+ /**
20
+ * On-disk cache of the live model list returned by each provider's
21
+ * `/models` endpoint. The cache is per-provider and bounded by
22
+ * `MODELS_CACHE_TTL_MS`. Live fetches refresh the entry; stale or
23
+ * missing entries fall back to the registry `models[]` array with
24
+ * a `registry-fallback` source tag so the UI can show an
25
+ * `[unverified]` hint.
26
+ */
27
+ export interface ProviderModelsCacheEntry {
28
+ models: string[];
29
+ fetchedAt: string;
30
+ source: 'live' | 'registry-fallback';
31
+ }
32
+ export declare const ProvidersManager: {
33
+ /** List all connected providers with masked keys. */
34
+ list(): Array<{
35
+ name: string;
36
+ displayName: string;
37
+ maskedKey: string;
38
+ addedAt: string;
39
+ }>;
40
+ /** Add or update a provider API key. */
41
+ add(name: string, apiKey: string, note?: string): void;
42
+ /** Remove a provider key. Returns true if removed, false if not found. */
43
+ remove(name: string): boolean;
44
+ /** Get the raw API key for a provider, or null if not configured. */
45
+ getKey(name: string): string | null;
46
+ /** Get the base URL and key for a provider — for direct bypass. */
47
+ getDirectConfig(name: string): {
48
+ apiKey: string;
49
+ baseUrl: string;
50
+ displayName: string;
51
+ } | null;
52
+ /**
53
+ * Run `fn` with a direct provider's full credential
54
+ * ({@link ProviderCredential}) handed in via a scoped
55
+ * callback. The credential is sourced from the in-memory
56
+ * {@link ProviderKeyVault} and is only reachable inside `fn`.
57
+ *
58
+ * Use this in preference to `getDirectConfig(name)` whenever
59
+ * possible — it keeps the raw API key out of return values,
60
+ * error stacks, and exception payloads.
61
+ */
62
+ withDirectCredential<T>(name: string, fn: (cred: ProviderCredential & {
63
+ openAICompat: boolean;
64
+ }) => Promise<T> | T): Promise<T | null>;
65
+ /**
66
+ * Populate the {@link ProviderKeyVault} from the on-disk
67
+ * providers store. Idempotent: re-running it is safe and
68
+ * only refreshes entries for providers that have keys on
69
+ * disk. Called automatically on first direct-config access.
70
+ */
71
+ hydrateVault(): void;
72
+ /**
73
+ * Drop the cached vault singleton. Used by the
74
+ * `/fixo providers:reset` slash command and by tests.
75
+ */
76
+ resetVault(): void;
77
+ /** Check if a provider key is configured. */
78
+ has(name: string): boolean;
79
+ /** Get provider definition by name. */
80
+ getDefinition(name: string): ProviderDefinition | undefined;
81
+ /** Get all registered provider definitions. */
82
+ getAllDefinitions(): ProviderDefinition[];
83
+ /**
84
+ * Read the cached model list for `name`. Returns `null` when no
85
+ * entry exists or when the entry is stale (older than
86
+ * {@link MODELS_CACHE_TTL_MS}). Synchronous + read-only so the
87
+ * `/model` picker can call it inline without awaiting.
88
+ */
89
+ getCachedModels(name: string): ProviderModelsCacheEntry | null;
90
+ /**
91
+ * Fetch the live model list from the provider's `/models`
92
+ * endpoint and persist it to the on-disk cache. Routes through
93
+ * the credential vault so the raw API key never escapes into a
94
+ * wider stack frame.
95
+ *
96
+ * Resolution order:
97
+ * 1. live fetch (success → cache + return `source: 'live'`).
98
+ * 2. fresh cache hit (within TTL) → `source: 'cache'`.
99
+ * 3. registry `models[]` fallback → `source: 'registry-fallback'`.
100
+ *
101
+ * Never throws — failure modes degrade through the layers above.
102
+ */
103
+ fetchRemoteModels(name: string): Promise<{
104
+ models: string[];
105
+ source: "live" | "cache" | "registry-fallback";
106
+ fetchedAt: string;
107
+ }>;
108
+ };
109
+ //# sourceMappingURL=providers-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providers-manager.d.ts","sourceRoot":"","sources":["../../src/agent/providers-manager.ts"],"names":[],"mappings":"AAkBA,OAAO,EAIL,KAAK,kBAAkB,EACxB,MAAM,gCAAgC,CAAC;AAIxC,MAAM,WAAW,kBAAkB;IACjC,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,YAAY,EAAE,OAAO,CAAC;IACtB,uCAAuC;IACvC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,0EAA0E;IAC1E,WAAW,EAAE,OAAO,CAAC;IACrB,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,eAAO,MAAM,iBAAiB,EAAE,kBAAkB,EAiIjD,CAAC;AAiDF;;;;;;;GAOG;AACH,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,GAAG,mBAAmB,CAAC;CACtC;AAuED,eAAO,MAAM,gBAAgB;IAC3B,qDAAqD;YAC7C,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAaxF,wCAAwC;cAC9B,MAAM,UAAU,MAAM,SAAS,MAAM,GAAG,IAAI;IAsBtD,0EAA0E;iBAC7D,MAAM,GAAG,OAAO;IAS7B,qEAAqE;iBACxD,MAAM,GAAG,MAAM,GAAG,IAAI;IAKnC,mEAAmE;0BAC7C,MAAM,GAAG;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAkB9F;;;;;;;;;OASG;yBACwB,CAAC,QACpB,MAAM,MACR,CAAC,IAAI,EAAE,kBAAkB,GAAG;QAAE,YAAY,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAC3E,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAWpB;;;;;OAKG;oBACa,IAAI;IAcpB;;;OAGG;kBACW,IAAI;IAIlB,6CAA6C;cACnC,MAAM,GAAG,OAAO;IAK1B,uCAAuC;wBACnB,MAAM,GAAG,kBAAkB,GAAG,SAAS;IAI3D,+CAA+C;yBAC1B,kBAAkB,EAAE;IAIzC;;;;;OAKG;0BACmB,MAAM,GAAG,wBAAwB,GAAG,IAAI;IAU9D;;;;;;;;;;;;OAYG;4BAC2B,MAAM,GAAG,OAAO,CAAC;QAC7C,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,mBAAmB,CAAC;QAC/C,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CA2DH,CAAC"}