@caupulican/pi-adaptative 0.81.26 → 0.81.29

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 (286) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist/cli/args.d.ts.map +1 -1
  3. package/dist/cli/args.js +3 -2
  4. package/dist/cli/args.js.map +1 -1
  5. package/dist/core/agent-session-services.d.ts +4 -0
  6. package/dist/core/agent-session-services.d.ts.map +1 -1
  7. package/dist/core/agent-session-services.js +19 -2
  8. package/dist/core/agent-session-services.js.map +1 -1
  9. package/dist/core/agent-session.d.ts +30 -12
  10. package/dist/core/agent-session.d.ts.map +1 -1
  11. package/dist/core/agent-session.js +128 -36
  12. package/dist/core/agent-session.js.map +1 -1
  13. package/dist/core/auth-storage.d.ts +6 -0
  14. package/dist/core/auth-storage.d.ts.map +1 -1
  15. package/dist/core/auth-storage.js +35 -0
  16. package/dist/core/auth-storage.js.map +1 -1
  17. package/dist/core/autonomy/bounded-completion.d.ts.map +1 -1
  18. package/dist/core/autonomy/bounded-completion.js +59 -19
  19. package/dist/core/autonomy/bounded-completion.js.map +1 -1
  20. package/dist/core/autonomy/contracts.d.ts +2 -0
  21. package/dist/core/autonomy/contracts.d.ts.map +1 -1
  22. package/dist/core/autonomy/contracts.js.map +1 -1
  23. package/dist/core/autonomy/lane-tool-surface.d.ts +31 -0
  24. package/dist/core/autonomy/lane-tool-surface.d.ts.map +1 -0
  25. package/dist/core/autonomy/lane-tool-surface.js +96 -0
  26. package/dist/core/autonomy/lane-tool-surface.js.map +1 -0
  27. package/dist/core/autonomy/lane-tracker.d.ts +5 -0
  28. package/dist/core/autonomy/lane-tracker.d.ts.map +1 -1
  29. package/dist/core/autonomy/lane-tracker.js +14 -3
  30. package/dist/core/autonomy/lane-tracker.js.map +1 -1
  31. package/dist/core/background-lane-controller.d.ts +28 -4
  32. package/dist/core/background-lane-controller.d.ts.map +1 -1
  33. package/dist/core/background-lane-controller.js +237 -28
  34. package/dist/core/background-lane-controller.js.map +1 -1
  35. package/dist/core/billing-failover-controller.d.ts.map +1 -1
  36. package/dist/core/billing-failover-controller.js +1 -1
  37. package/dist/core/billing-failover-controller.js.map +1 -1
  38. package/dist/core/context/current-work-memory.d.ts +9 -0
  39. package/dist/core/context/current-work-memory.d.ts.map +1 -0
  40. package/dist/core/context/current-work-memory.js +61 -0
  41. package/dist/core/context/current-work-memory.js.map +1 -0
  42. package/dist/core/context/file-store-memory-provider.d.ts +9 -0
  43. package/dist/core/context/file-store-memory-provider.d.ts.map +1 -0
  44. package/dist/core/context/file-store-memory-provider.js +106 -0
  45. package/dist/core/context/file-store-memory-provider.js.map +1 -0
  46. package/dist/core/context/long-term-memory-trigger.d.ts +15 -0
  47. package/dist/core/context/long-term-memory-trigger.d.ts.map +1 -0
  48. package/dist/core/context/long-term-memory-trigger.js +39 -0
  49. package/dist/core/context/long-term-memory-trigger.js.map +1 -0
  50. package/dist/core/context/memory-prompt-block.d.ts +3 -1
  51. package/dist/core/context/memory-prompt-block.d.ts.map +1 -1
  52. package/dist/core/context/memory-prompt-block.js +20 -3
  53. package/dist/core/context/memory-prompt-block.js.map +1 -1
  54. package/dist/core/context/memory-prompt-budget.d.ts +17 -0
  55. package/dist/core/context/memory-prompt-budget.d.ts.map +1 -0
  56. package/dist/core/context/memory-prompt-budget.js +64 -0
  57. package/dist/core/context/memory-prompt-budget.js.map +1 -0
  58. package/dist/core/context/memory-provider-contract.d.ts.map +1 -1
  59. package/dist/core/context/memory-provider-contract.js +2 -5
  60. package/dist/core/context/memory-provider-contract.js.map +1 -1
  61. package/dist/core/context/memory-tier-composer.d.ts +27 -0
  62. package/dist/core/context/memory-tier-composer.d.ts.map +1 -0
  63. package/dist/core/context/memory-tier-composer.js +79 -0
  64. package/dist/core/context/memory-tier-composer.js.map +1 -0
  65. package/dist/core/context-gc.d.ts.map +1 -1
  66. package/dist/core/context-gc.js +2 -0
  67. package/dist/core/context-gc.js.map +1 -1
  68. package/dist/core/context-pipeline.d.ts +5 -3
  69. package/dist/core/context-pipeline.d.ts.map +1 -1
  70. package/dist/core/context-pipeline.js +15 -5
  71. package/dist/core/context-pipeline.js.map +1 -1
  72. package/dist/core/cost/cost-summary.d.ts +3 -1
  73. package/dist/core/cost/cost-summary.d.ts.map +1 -1
  74. package/dist/core/cost/cost-summary.js +3 -3
  75. package/dist/core/cost/cost-summary.js.map +1 -1
  76. package/dist/core/cost-guard.d.ts +17 -7
  77. package/dist/core/cost-guard.d.ts.map +1 -1
  78. package/dist/core/cost-guard.js +33 -10
  79. package/dist/core/cost-guard.js.map +1 -1
  80. package/dist/core/default-tool-surface.d.ts +9 -0
  81. package/dist/core/default-tool-surface.d.ts.map +1 -0
  82. package/dist/core/default-tool-surface.js +18 -0
  83. package/dist/core/default-tool-surface.js.map +1 -0
  84. package/dist/core/delegation/worker-result.d.ts.map +1 -1
  85. package/dist/core/delegation/worker-result.js +3 -0
  86. package/dist/core/delegation/worker-result.js.map +1 -1
  87. package/dist/core/delegation/worker-runner.d.ts +11 -5
  88. package/dist/core/delegation/worker-runner.d.ts.map +1 -1
  89. package/dist/core/delegation/worker-runner.js +77 -16
  90. package/dist/core/delegation/worker-runner.js.map +1 -1
  91. package/dist/core/extensions/loader.d.ts.map +1 -1
  92. package/dist/core/extensions/loader.js +15 -0
  93. package/dist/core/extensions/loader.js.map +1 -1
  94. package/dist/core/extensions/runner.d.ts +2 -0
  95. package/dist/core/extensions/runner.d.ts.map +1 -1
  96. package/dist/core/extensions/runner.js +7 -0
  97. package/dist/core/extensions/runner.js.map +1 -1
  98. package/dist/core/extensions/types.d.ts +9 -0
  99. package/dist/core/extensions/types.d.ts.map +1 -1
  100. package/dist/core/extensions/types.js.map +1 -1
  101. package/dist/core/failure-corpus.d.ts.map +1 -1
  102. package/dist/core/failure-corpus.js +2 -4
  103. package/dist/core/failure-corpus.js.map +1 -1
  104. package/dist/core/footer-data-provider.d.ts.map +1 -1
  105. package/dist/core/footer-data-provider.js +9 -1
  106. package/dist/core/footer-data-provider.js.map +1 -1
  107. package/dist/core/goals/goal-continuation-prompt.d.ts.map +1 -1
  108. package/dist/core/goals/goal-continuation-prompt.js +4 -3
  109. package/dist/core/goals/goal-continuation-prompt.js.map +1 -1
  110. package/dist/core/http-dispatcher.d.ts +12 -0
  111. package/dist/core/http-dispatcher.d.ts.map +1 -1
  112. package/dist/core/http-dispatcher.js +42 -4
  113. package/dist/core/http-dispatcher.js.map +1 -1
  114. package/dist/core/local-runtime-controller.d.ts +16 -0
  115. package/dist/core/local-runtime-controller.d.ts.map +1 -1
  116. package/dist/core/local-runtime-controller.js +107 -28
  117. package/dist/core/local-runtime-controller.js.map +1 -1
  118. package/dist/core/memory/memory-manager.d.ts +8 -3
  119. package/dist/core/memory/memory-manager.d.ts.map +1 -1
  120. package/dist/core/memory/memory-manager.js +27 -11
  121. package/dist/core/memory/memory-manager.js.map +1 -1
  122. package/dist/core/memory/memory-provider.d.ts +5 -1
  123. package/dist/core/memory/memory-provider.d.ts.map +1 -1
  124. package/dist/core/memory/memory-provider.js.map +1 -1
  125. package/dist/core/memory/providers/file-store.d.ts +3 -1
  126. package/dist/core/memory/providers/file-store.d.ts.map +1 -1
  127. package/dist/core/memory/providers/file-store.js +7 -2
  128. package/dist/core/memory/providers/file-store.js.map +1 -1
  129. package/dist/core/memory/providers/transcript-recall.d.ts +1 -0
  130. package/dist/core/memory/providers/transcript-recall.d.ts.map +1 -1
  131. package/dist/core/memory/providers/transcript-recall.js +4 -7
  132. package/dist/core/memory/providers/transcript-recall.js.map +1 -1
  133. package/dist/core/memory-controller.d.ts +36 -10
  134. package/dist/core/memory-controller.d.ts.map +1 -1
  135. package/dist/core/memory-controller.js +137 -21
  136. package/dist/core/memory-controller.js.map +1 -1
  137. package/dist/core/model-registry.d.ts +25 -0
  138. package/dist/core/model-registry.d.ts.map +1 -1
  139. package/dist/core/model-registry.js +91 -8
  140. package/dist/core/model-registry.js.map +1 -1
  141. package/dist/core/model-resolver.d.ts.map +1 -1
  142. package/dist/core/model-resolver.js +30 -11
  143. package/dist/core/model-resolver.js.map +1 -1
  144. package/dist/core/model-router-controller.d.ts.map +1 -1
  145. package/dist/core/model-router-controller.js +33 -29
  146. package/dist/core/model-router-controller.js.map +1 -1
  147. package/dist/core/model-selection-controller.d.ts +3 -0
  148. package/dist/core/model-selection-controller.d.ts.map +1 -1
  149. package/dist/core/model-selection-controller.js +8 -1
  150. package/dist/core/model-selection-controller.js.map +1 -1
  151. package/dist/core/models/local-runtime.d.ts +2 -0
  152. package/dist/core/models/local-runtime.d.ts.map +1 -1
  153. package/dist/core/models/local-runtime.js +10 -4
  154. package/dist/core/models/local-runtime.js.map +1 -1
  155. package/dist/core/models/perf-profile.d.ts.map +1 -1
  156. package/dist/core/models/perf-profile.js +95 -33
  157. package/dist/core/models/perf-profile.js.map +1 -1
  158. package/dist/core/models/runtime-arbiter.d.ts +11 -0
  159. package/dist/core/models/runtime-arbiter.d.ts.map +1 -1
  160. package/dist/core/models/runtime-arbiter.js +35 -13
  161. package/dist/core/models/runtime-arbiter.js.map +1 -1
  162. package/dist/core/profile-filter-controller.d.ts +11 -0
  163. package/dist/core/profile-filter-controller.d.ts.map +1 -1
  164. package/dist/core/profile-filter-controller.js +56 -11
  165. package/dist/core/profile-filter-controller.js.map +1 -1
  166. package/dist/core/profile-registry.d.ts +2 -2
  167. package/dist/core/profile-registry.d.ts.map +1 -1
  168. package/dist/core/profile-registry.js +58 -7
  169. package/dist/core/profile-registry.js.map +1 -1
  170. package/dist/core/reflection-controller.d.ts +9 -7
  171. package/dist/core/reflection-controller.d.ts.map +1 -1
  172. package/dist/core/reflection-controller.js +81 -12
  173. package/dist/core/reflection-controller.js.map +1 -1
  174. package/dist/core/research/research-runner.d.ts +2 -2
  175. package/dist/core/research/research-runner.d.ts.map +1 -1
  176. package/dist/core/research/research-runner.js +8 -6
  177. package/dist/core/research/research-runner.js.map +1 -1
  178. package/dist/core/resource-loader.d.ts +4 -2
  179. package/dist/core/resource-loader.d.ts.map +1 -1
  180. package/dist/core/resource-loader.js +11 -1
  181. package/dist/core/resource-loader.js.map +1 -1
  182. package/dist/core/runtime-builder.d.ts +30 -3
  183. package/dist/core/runtime-builder.d.ts.map +1 -1
  184. package/dist/core/runtime-builder.js +140 -40
  185. package/dist/core/runtime-builder.js.map +1 -1
  186. package/dist/core/sdk.d.ts +8 -7
  187. package/dist/core/sdk.d.ts.map +1 -1
  188. package/dist/core/sdk.js +38 -5
  189. package/dist/core/sdk.js.map +1 -1
  190. package/dist/core/security/secret-text.d.ts +3 -0
  191. package/dist/core/security/secret-text.d.ts.map +1 -0
  192. package/dist/core/security/secret-text.js +26 -0
  193. package/dist/core/security/secret-text.js.map +1 -0
  194. package/dist/core/settings-manager.d.ts +59 -17
  195. package/dist/core/settings-manager.d.ts.map +1 -1
  196. package/dist/core/settings-manager.js +300 -51
  197. package/dist/core/settings-manager.js.map +1 -1
  198. package/dist/core/skills.d.ts.map +1 -1
  199. package/dist/core/skills.js +1 -1
  200. package/dist/core/skills.js.map +1 -1
  201. package/dist/core/system-prompt-builder.d.ts +7 -0
  202. package/dist/core/system-prompt-builder.d.ts.map +1 -1
  203. package/dist/core/system-prompt-builder.js +23 -1
  204. package/dist/core/system-prompt-builder.js.map +1 -1
  205. package/dist/core/tool-gate-controller.d.ts +3 -0
  206. package/dist/core/tool-gate-controller.d.ts.map +1 -1
  207. package/dist/core/tool-gate-controller.js +20 -15
  208. package/dist/core/tool-gate-controller.js.map +1 -1
  209. package/dist/core/tool-selection/expected-utility.d.ts +51 -0
  210. package/dist/core/tool-selection/expected-utility.d.ts.map +1 -0
  211. package/dist/core/tool-selection/expected-utility.js +85 -0
  212. package/dist/core/tool-selection/expected-utility.js.map +1 -0
  213. package/dist/core/tool-selection/tool-performance-store.d.ts +82 -0
  214. package/dist/core/tool-selection/tool-performance-store.d.ts.map +1 -0
  215. package/dist/core/tool-selection/tool-performance-store.js +249 -0
  216. package/dist/core/tool-selection/tool-performance-store.js.map +1 -0
  217. package/dist/core/tool-selection/tool-selection-controller.d.ts +41 -0
  218. package/dist/core/tool-selection/tool-selection-controller.d.ts.map +1 -0
  219. package/dist/core/tool-selection/tool-selection-controller.js +199 -0
  220. package/dist/core/tool-selection/tool-selection-controller.js.map +1 -0
  221. package/dist/core/tools/delegate-status.d.ts +9 -0
  222. package/dist/core/tools/delegate-status.d.ts.map +1 -0
  223. package/dist/core/tools/delegate-status.js +52 -0
  224. package/dist/core/tools/delegate-status.js.map +1 -0
  225. package/dist/core/tools/delegate.d.ts +10 -0
  226. package/dist/core/tools/delegate.d.ts.map +1 -1
  227. package/dist/core/tools/delegate.js +36 -13
  228. package/dist/core/tools/delegate.js.map +1 -1
  229. package/dist/core/tools/run-toolkit-script.d.ts +3 -0
  230. package/dist/core/tools/run-toolkit-script.d.ts.map +1 -1
  231. package/dist/core/tools/run-toolkit-script.js +17 -7
  232. package/dist/core/tools/run-toolkit-script.js.map +1 -1
  233. package/dist/main.d.ts.map +1 -1
  234. package/dist/main.js +2 -0
  235. package/dist/main.js.map +1 -1
  236. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  237. package/dist/modes/interactive/components/footer.js +8 -1
  238. package/dist/modes/interactive/components/footer.js.map +1 -1
  239. package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  240. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  241. package/dist/modes/interactive/components/settings-selector.js +69 -29
  242. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  243. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  244. package/dist/modes/interactive/components/thinking-selector.js +2 -0
  245. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  246. package/dist/modes/interactive/config-backup.d.ts +7 -1
  247. package/dist/modes/interactive/config-backup.d.ts.map +1 -1
  248. package/dist/modes/interactive/config-backup.js +196 -32
  249. package/dist/modes/interactive/config-backup.js.map +1 -1
  250. package/dist/modes/interactive/interactive-mode.d.ts +1 -1
  251. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  252. package/dist/modes/interactive/interactive-mode.js +22 -28
  253. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  254. package/dist/modes/interactive/local-model-commands.d.ts.map +1 -1
  255. package/dist/modes/interactive/local-model-commands.js +2 -0
  256. package/dist/modes/interactive/local-model-commands.js.map +1 -1
  257. package/dist/modes/interactive/profile-menu-controller.d.ts +6 -4
  258. package/dist/modes/interactive/profile-menu-controller.d.ts.map +1 -1
  259. package/dist/modes/interactive/profile-menu-controller.js +248 -65
  260. package/dist/modes/interactive/profile-menu-controller.js.map +1 -1
  261. package/dist/modes/interactive/report-commands.d.ts.map +1 -1
  262. package/dist/modes/interactive/report-commands.js +8 -2
  263. package/dist/modes/interactive/report-commands.js.map +1 -1
  264. package/dist/modes/interactive/settings-selector-flow.d.ts.map +1 -1
  265. package/dist/modes/interactive/settings-selector-flow.js +8 -0
  266. package/dist/modes/interactive/settings-selector-flow.js.map +1 -1
  267. package/dist/modes/interactive/theme/theme-schema.json +1 -1
  268. package/dist/modes/interactive/theme/theme.d.ts +1 -1
  269. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  270. package/dist/modes/interactive/theme/theme.js +3 -1
  271. package/dist/modes/interactive/theme/theme.js.map +1 -1
  272. package/docs/extensions.md +32 -0
  273. package/docs/integration-sweep-builder-blueprint-2026-07-09.md +365 -0
  274. package/docs/integration-sweep-resume-2026-07-09.md +407 -0
  275. package/docs/resources.md +15 -7
  276. package/docs/settings.md +56 -5
  277. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  278. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  279. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  280. package/examples/extensions/preset.ts +2 -2
  281. package/examples/extensions/sandbox/package-lock.json +2 -2
  282. package/examples/extensions/sandbox/package.json +1 -1
  283. package/examples/extensions/with-deps/package-lock.json +2 -2
  284. package/examples/extensions/with-deps/package.json +1 -1
  285. package/npm-shrinkwrap.json +44 -35
  286. package/package.json +4 -4
@@ -1,6 +1,8 @@
1
1
  import { createAssistantMessageEventStream, } from "@caupulican/pi-ai";
2
2
  const PERF_EWMA_ALPHA = 0.3;
3
3
  const CHARS_PER_TOKEN_ESTIMATE = 4;
4
+ const DEFERRED_HEADERS_MAX_GAP_MS = 100;
5
+ const DEFERRED_HEADERS_MIN_REQUEST_MS = 1_000;
4
6
  export const DEFAULT_ADAPTIVE_STREAM_IDLE_CEILING_MS = 30 * 60 * 1000;
5
7
  export function isModelPerfProfile(value) {
6
8
  if (typeof value !== "object" || value === null || Array.isArray(value))
@@ -88,61 +90,121 @@ export function withModelPerfProfile(streamFn, recorder) {
88
90
  let responseHeadersAtMs;
89
91
  let firstTokenAtMs;
90
92
  const originalOnResponse = streamOptions?.onResponse;
91
- const inner = await streamFn(model, context, {
92
- ...streamOptions,
93
- onResponse: async (response, responseModel) => {
94
- responseHeadersAtMs ??= nowMs();
95
- await originalOnResponse?.(response, responseModel);
96
- },
97
- });
98
93
  const outer = createAssistantMessageEventStream();
94
+ let latest = emptyAssistantMessage(model);
95
+ let inner;
96
+ try {
97
+ inner = await streamFn(model, context, {
98
+ ...streamOptions,
99
+ onResponse: async (response, responseModel) => {
100
+ responseHeadersAtMs ??= nowMs();
101
+ await originalOnResponse?.(response, responseModel);
102
+ },
103
+ });
104
+ }
105
+ catch (error) {
106
+ outer.push(perfStreamFailure(latest, streamOptions?.signal?.aborted === true, error));
107
+ return outer;
108
+ }
99
109
  void (async () => {
100
110
  let terminalPushed = false;
101
- for await (const event of inner) {
102
- if (firstTokenAtMs === undefined && isFirstTokenEvent(event)) {
103
- firstTokenAtMs = nowMs();
111
+ try {
112
+ for await (const event of inner) {
113
+ latest = assistantMessageFromEvent(event);
114
+ if (firstTokenAtMs === undefined && isFirstTokenEvent(event)) {
115
+ firstTokenAtMs = nowMs();
116
+ }
117
+ if (event.type === "done") {
118
+ terminalPushed = true;
119
+ recordSuccessfulStreamSample({
120
+ completionTokens: event.message.usage.output,
121
+ doneAtMs: nowMs(),
122
+ firstTokenAtMs,
123
+ model,
124
+ nowIso,
125
+ promptTokens: event.message.usage.input + event.message.usage.cacheRead + event.message.usage.cacheWrite,
126
+ recorder,
127
+ requestStartedAtMs,
128
+ responseHeadersAtMs,
129
+ });
130
+ }
131
+ else if (event.type === "error") {
132
+ terminalPushed = true;
133
+ }
134
+ outer.push(event);
135
+ if (terminalPushed)
136
+ return;
104
137
  }
105
- if (event.type === "done") {
106
- terminalPushed = true;
107
- recordSuccessfulStreamSample({
108
- completionTokens: event.message.usage.output,
109
- doneAtMs: nowMs(),
110
- firstTokenAtMs,
111
- model,
112
- nowIso,
113
- promptTokens: event.message.usage.input,
114
- recorder,
115
- requestStartedAtMs,
116
- responseHeadersAtMs,
117
- });
118
- }
119
- else if (event.type === "error") {
120
- terminalPushed = true;
121
- }
122
- outer.push(event);
123
- if (terminalPushed)
124
- return;
138
+ outer.push(perfStreamFailure(latest, streamOptions?.signal?.aborted === true));
139
+ }
140
+ catch (error) {
141
+ outer.push(perfStreamFailure(latest, streamOptions?.signal?.aborted === true, error));
125
142
  }
126
- outer.end();
127
143
  })();
128
144
  return outer;
129
145
  };
130
146
  }
147
+ function assistantMessageFromEvent(event) {
148
+ if (event.type === "done")
149
+ return event.message;
150
+ if (event.type === "error")
151
+ return event.error;
152
+ return event.partial;
153
+ }
154
+ function emptyAssistantMessage(model) {
155
+ return {
156
+ role: "assistant",
157
+ content: [],
158
+ api: model.api,
159
+ provider: model.provider,
160
+ model: model.id,
161
+ usage: {
162
+ input: 0,
163
+ output: 0,
164
+ cacheRead: 0,
165
+ cacheWrite: 0,
166
+ totalTokens: 0,
167
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
168
+ },
169
+ stopReason: "stop",
170
+ timestamp: Date.now(),
171
+ };
172
+ }
173
+ function perfStreamFailure(latest, aborted, error) {
174
+ const stopReason = aborted ? "aborted" : "error";
175
+ const detail = error instanceof Error ? `: ${error.message}` : error === undefined ? "" : `: ${String(error)}`;
176
+ return {
177
+ type: "error",
178
+ reason: stopReason,
179
+ error: {
180
+ ...latest,
181
+ stopReason,
182
+ errorMessage: aborted
183
+ ? `stream aborted before terminal event${detail}`
184
+ : `stream ended without terminal event${detail}`,
185
+ },
186
+ };
187
+ }
131
188
  function recordSuccessfulStreamSample(input) {
132
189
  if (input.firstTokenAtMs === undefined)
133
190
  return;
134
191
  const modelKey = input.recorder.modelKey(input.model);
135
192
  if (!modelKey)
136
193
  return;
194
+ const requestToFirstTokenMs = input.firstTokenAtMs - input.requestStartedAtMs;
137
195
  const sample = {
138
196
  promptTokens: input.promptTokens,
139
197
  completionTokens: input.completionTokens,
140
- requestToFirstTokenMs: input.firstTokenAtMs - input.requestStartedAtMs,
198
+ requestToFirstTokenMs,
141
199
  firstTokenToDoneMs: input.doneAtMs - input.firstTokenAtMs,
142
200
  at: input.nowIso(),
143
201
  };
144
202
  if (input.responseHeadersAtMs !== undefined) {
145
- sample.headersToFirstTokenMs = input.firstTokenAtMs - input.responseHeadersAtMs;
203
+ const headersToFirstTokenMs = input.firstTokenAtMs - input.responseHeadersAtMs;
204
+ const headersWereEffectivelyDeferred = requestToFirstTokenMs >= DEFERRED_HEADERS_MIN_REQUEST_MS &&
205
+ headersToFirstTokenMs <= DEFERRED_HEADERS_MAX_GAP_MS;
206
+ if (!headersWereEffectivelyDeferred)
207
+ sample.headersToFirstTokenMs = headersToFirstTokenMs;
146
208
  }
147
209
  if (!hasUsableModelPerfSample(sample))
148
210
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"perf-profile.js","sourceRoot":"","sources":["../../../src/core/models/perf-profile.ts"],"names":[],"mappings":"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","sourcesContent":["import type { StreamFn, StreamIdleOptions } from \"@caupulican/pi-agent-core\";\nimport {\n\ttype Api,\n\ttype AssistantMessageEvent,\n\ttype Context,\n\tcreateAssistantMessageEventStream,\n\ttype Model,\n\ttype ProviderResponse,\n} from \"@caupulican/pi-ai\";\n\nconst PERF_EWMA_ALPHA = 0.3;\nconst CHARS_PER_TOKEN_ESTIMATE = 4;\nexport const DEFAULT_ADAPTIVE_STREAM_IDLE_CEILING_MS = 30 * 60 * 1000;\n\nexport interface ModelPerfProfile {\n\tprefillTokensPerSecond?: number;\n\tdecodeTokensPerSecond?: number;\n\tloadMs?: number;\n\tsamples: number;\n\tupdatedAt: string;\n}\n\nexport interface ModelPerfSample {\n\tpromptTokens?: number;\n\tcompletionTokens?: number;\n\theadersToFirstTokenMs?: number;\n\trequestToFirstTokenMs?: number;\n\tfirstTokenToDoneMs?: number;\n\tloadMs?: number;\n\tat?: string;\n}\n\nexport interface AdaptiveStreamIdleInput {\n\tbase: StreamIdleOptions;\n\tprofile?: ModelPerfProfile;\n\tpromptTokens: number;\n\tceilingMs?: number;\n\tlocalClass?: boolean;\n}\n\nexport interface ModelPerfProfileStreamRecorder {\n\tmodelKey: (model: Model<Api>) => string | undefined;\n\trecordSample: (modelKey: string, sample: ModelPerfSample) => void;\n\tnowMs?: () => number;\n\tnowIso?: () => string;\n}\n\nexport function isModelPerfProfile(value: unknown): value is ModelPerfProfile {\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) return false;\n\tconst record = value as Record<string, unknown>;\n\tconst samples = record.samples;\n\treturn (\n\t\t(record.prefillTokensPerSecond === undefined || isPositiveFiniteNumber(record.prefillTokensPerSecond)) &&\n\t\t(record.decodeTokensPerSecond === undefined || isPositiveFiniteNumber(record.decodeTokensPerSecond)) &&\n\t\t(record.loadMs === undefined || isPositiveFiniteNumber(record.loadMs)) &&\n\t\ttypeof samples === \"number\" &&\n\t\tNumber.isInteger(samples) &&\n\t\tsamples >= 0 &&\n\t\ttypeof record.updatedAt === \"string\"\n\t);\n}\n\nexport function hasUsableModelPerfSample(sample: ModelPerfSample): boolean {\n\treturn (\n\t\tprefillRateFromSample(sample) !== undefined ||\n\t\tdecodeRateFromSample(sample) !== undefined ||\n\t\tisPositiveFiniteNumber(sample.loadMs)\n\t);\n}\n\nexport function updateModelPerfProfile(\n\tcurrent: ModelPerfProfile | undefined,\n\tsample: ModelPerfSample,\n\tat: string = sample.at ?? new Date().toISOString(),\n): ModelPerfProfile | undefined {\n\tconst prefillRate = prefillRateFromSample(sample);\n\tconst decodeRate = decodeRateFromSample(sample);\n\tconst loadMs = isPositiveFiniteNumber(sample.loadMs) ? sample.loadMs : undefined;\n\tif (prefillRate === undefined && decodeRate === undefined && loadMs === undefined) return current;\n\n\tconst previousSamples = current?.samples ?? 0;\n\treturn {\n\t\t...(prefillRate !== undefined && {\n\t\t\tprefillTokensPerSecond: updateEwma(current?.prefillTokensPerSecond, prefillRate, previousSamples),\n\t\t}),\n\t\t...(prefillRate === undefined && current?.prefillTokensPerSecond !== undefined\n\t\t\t? { prefillTokensPerSecond: current.prefillTokensPerSecond }\n\t\t\t: {}),\n\t\t...(decodeRate !== undefined && {\n\t\t\tdecodeTokensPerSecond: updateEwma(current?.decodeTokensPerSecond, decodeRate, previousSamples),\n\t\t}),\n\t\t...(decodeRate === undefined && current?.decodeTokensPerSecond !== undefined\n\t\t\t? { decodeTokensPerSecond: current.decodeTokensPerSecond }\n\t\t\t: {}),\n\t\t...(loadMs !== undefined && { loadMs: updateEwma(current?.loadMs, loadMs, previousSamples) }),\n\t\t...(loadMs === undefined && current?.loadMs !== undefined ? { loadMs: current.loadMs } : {}),\n\t\tsamples: previousSamples + 1,\n\t\tupdatedAt: at,\n\t};\n}\n\nexport function resolveAdaptiveStreamIdleOptions(input: AdaptiveStreamIdleInput): Partial<StreamIdleOptions> {\n\tconst profile = input.profile;\n\tconst result: Partial<StreamIdleOptions> = {};\n\tconst localConnectDefaultMs = input.localClass\n\t\t? Math.max(input.base.connectMs, input.base.quietIdleMs)\n\t\t: input.base.connectMs;\n\tconst quietCeilingMs = Math.max(input.base.quietIdleMs, input.ceilingMs ?? DEFAULT_ADAPTIVE_STREAM_IDLE_CEILING_MS);\n\tconst connectCeilingMs = Math.max(localConnectDefaultMs, input.ceilingMs ?? DEFAULT_ADAPTIVE_STREAM_IDLE_CEILING_MS);\n\n\tconst expectedPrefillMs = expectedPrefillFromProfile(profile, input.promptTokens);\n\tif (expectedPrefillMs !== undefined) {\n\t\tconst quietIdleMs = adaptiveBound(input.base.quietIdleMs, quietCeilingMs, expectedPrefillMs);\n\t\tif (quietIdleMs > input.base.quietIdleMs) result.quietIdleMs = quietIdleMs;\n\t}\n\n\tif (input.localClass) {\n\t\tconst expectedConnectWorkMs =\n\t\t\texpectedPrefillMs !== undefined ? expectedPrefillMs + (profile?.loadMs ?? 0) : profile?.loadMs;\n\t\tconst connectMs =\n\t\t\texpectedConnectWorkMs !== undefined\n\t\t\t\t? adaptiveBound(localConnectDefaultMs, connectCeilingMs, expectedConnectWorkMs)\n\t\t\t\t: localConnectDefaultMs;\n\t\tif (connectMs > input.base.connectMs) result.connectMs = connectMs;\n\t}\n\n\treturn result;\n}\n\nexport function estimateContextPromptTokens(context: Context): number {\n\tconst serialized = JSON.stringify({\n\t\tsystemPrompt: context.systemPrompt,\n\t\tmessages: context.messages,\n\t\ttools: context.tools,\n\t});\n\tif (!serialized) return 0;\n\treturn Math.ceil(serialized.length / CHARS_PER_TOKEN_ESTIMATE);\n}\n\nexport function withModelPerfProfile(streamFn: StreamFn, recorder: ModelPerfProfileStreamRecorder): StreamFn {\n\treturn async (model, context, streamOptions) => {\n\t\tconst nowMs = recorder.nowMs ?? Date.now;\n\t\tconst nowIso = recorder.nowIso ?? (() => new Date().toISOString());\n\t\tconst requestStartedAtMs = nowMs();\n\t\tlet responseHeadersAtMs: number | undefined;\n\t\tlet firstTokenAtMs: number | undefined;\n\t\tconst originalOnResponse = streamOptions?.onResponse;\n\t\tconst inner = await streamFn(model, context, {\n\t\t\t...streamOptions,\n\t\t\tonResponse: async (response: ProviderResponse, responseModel: Model<Api>) => {\n\t\t\t\tresponseHeadersAtMs ??= nowMs();\n\t\t\t\tawait originalOnResponse?.(response, responseModel);\n\t\t\t},\n\t\t});\n\t\tconst outer = createAssistantMessageEventStream();\n\n\t\tvoid (async () => {\n\t\t\tlet terminalPushed = false;\n\t\t\tfor await (const event of inner) {\n\t\t\t\tif (firstTokenAtMs === undefined && isFirstTokenEvent(event)) {\n\t\t\t\t\tfirstTokenAtMs = nowMs();\n\t\t\t\t}\n\t\t\t\tif (event.type === \"done\") {\n\t\t\t\t\tterminalPushed = true;\n\t\t\t\t\trecordSuccessfulStreamSample({\n\t\t\t\t\t\tcompletionTokens: event.message.usage.output,\n\t\t\t\t\t\tdoneAtMs: nowMs(),\n\t\t\t\t\t\tfirstTokenAtMs,\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t\tnowIso,\n\t\t\t\t\t\tpromptTokens: event.message.usage.input,\n\t\t\t\t\t\trecorder,\n\t\t\t\t\t\trequestStartedAtMs,\n\t\t\t\t\t\tresponseHeadersAtMs,\n\t\t\t\t\t});\n\t\t\t\t} else if (event.type === \"error\") {\n\t\t\t\t\tterminalPushed = true;\n\t\t\t\t}\n\t\t\t\touter.push(event);\n\t\t\t\tif (terminalPushed) return;\n\t\t\t}\n\t\t\touter.end();\n\t\t})();\n\n\t\treturn outer;\n\t};\n}\n\nfunction recordSuccessfulStreamSample(input: {\n\tmodel: Model<Api>;\n\trecorder: ModelPerfProfileStreamRecorder;\n\trequestStartedAtMs: number;\n\tresponseHeadersAtMs: number | undefined;\n\tfirstTokenAtMs: number | undefined;\n\tdoneAtMs: number;\n\tpromptTokens: number;\n\tcompletionTokens: number;\n\tnowIso: () => string;\n}): void {\n\tif (input.firstTokenAtMs === undefined) return;\n\tconst modelKey = input.recorder.modelKey(input.model);\n\tif (!modelKey) return;\n\tconst sample: ModelPerfSample = {\n\t\tpromptTokens: input.promptTokens,\n\t\tcompletionTokens: input.completionTokens,\n\t\trequestToFirstTokenMs: input.firstTokenAtMs - input.requestStartedAtMs,\n\t\tfirstTokenToDoneMs: input.doneAtMs - input.firstTokenAtMs,\n\t\tat: input.nowIso(),\n\t};\n\tif (input.responseHeadersAtMs !== undefined) {\n\t\tsample.headersToFirstTokenMs = input.firstTokenAtMs - input.responseHeadersAtMs;\n\t}\n\tif (!hasUsableModelPerfSample(sample)) return;\n\ttry {\n\t\tinput.recorder.recordSample(modelKey, sample);\n\t} catch {\n\t\t// Perf profiling must never fail the user turn.\n\t}\n}\n\nfunction isFirstTokenEvent(event: AssistantMessageEvent): boolean {\n\treturn (\n\t\t(event.type === \"text_delta\" || event.type === \"thinking_delta\" || event.type === \"toolcall_delta\") &&\n\t\tevent.delta.length > 0\n\t);\n}\n\nfunction expectedPrefillFromProfile(profile: ModelPerfProfile | undefined, promptTokens: number): number | undefined {\n\tconst prefillRate = profile?.prefillTokensPerSecond;\n\tif (!profile || profile.samples < 1 || !prefillRate || promptTokens <= 0) return undefined;\n\tconst expectedPrefillMs = (promptTokens / prefillRate) * 1000;\n\treturn Number.isFinite(expectedPrefillMs) && expectedPrefillMs > 0 ? expectedPrefillMs : undefined;\n}\n\nfunction adaptiveBound(defaultMs: number, ceilingMs: number, expectedMs: number): number {\n\treturn Math.min(Math.ceil(Math.max(defaultMs, expectedMs * 3)), ceilingMs);\n}\n\nfunction prefillRateFromSample(sample: ModelPerfSample): number | undefined {\n\tconst durationMs = isPositiveFiniteNumber(sample.headersToFirstTokenMs)\n\t\t? sample.headersToFirstTokenMs\n\t\t: sample.requestToFirstTokenMs;\n\treturn tokensPerSecond(sample.promptTokens, durationMs);\n}\n\nfunction decodeRateFromSample(sample: ModelPerfSample): number | undefined {\n\treturn tokensPerSecond(sample.completionTokens, sample.firstTokenToDoneMs);\n}\n\nfunction tokensPerSecond(tokens: number | undefined, durationMs: number | undefined): number | undefined {\n\tif (!isPositiveFiniteNumber(tokens) || !isPositiveFiniteNumber(durationMs)) return undefined;\n\treturn tokens / (durationMs / 1000);\n}\n\nfunction updateEwma(previous: number | undefined, next: number, previousSamples: number): number {\n\tif (previous === undefined || previousSamples === 0) return next;\n\treturn previous * (1 - PERF_EWMA_ALPHA) + next * PERF_EWMA_ALPHA;\n}\n\nfunction isPositiveFiniteNumber(value: unknown): value is number {\n\treturn typeof value === \"number\" && Number.isFinite(value) && value > 0;\n}\n"]}
1
+ {"version":3,"file":"perf-profile.js","sourceRoot":"","sources":["../../../src/core/models/perf-profile.ts"],"names":[],"mappings":"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","sourcesContent":["import type { StreamFn, StreamIdleOptions } from \"@caupulican/pi-agent-core\";\nimport {\n\ttype Api,\n\ttype AssistantMessage,\n\ttype AssistantMessageEvent,\n\ttype Context,\n\tcreateAssistantMessageEventStream,\n\ttype Model,\n\ttype ProviderResponse,\n} from \"@caupulican/pi-ai\";\n\nconst PERF_EWMA_ALPHA = 0.3;\nconst CHARS_PER_TOKEN_ESTIMATE = 4;\nconst DEFERRED_HEADERS_MAX_GAP_MS = 100;\nconst DEFERRED_HEADERS_MIN_REQUEST_MS = 1_000;\nexport const DEFAULT_ADAPTIVE_STREAM_IDLE_CEILING_MS = 30 * 60 * 1000;\n\nexport interface ModelPerfProfile {\n\tprefillTokensPerSecond?: number;\n\tdecodeTokensPerSecond?: number;\n\tloadMs?: number;\n\tsamples: number;\n\tupdatedAt: string;\n}\n\nexport interface ModelPerfSample {\n\tpromptTokens?: number;\n\tcompletionTokens?: number;\n\theadersToFirstTokenMs?: number;\n\trequestToFirstTokenMs?: number;\n\tfirstTokenToDoneMs?: number;\n\tloadMs?: number;\n\tat?: string;\n}\n\nexport interface AdaptiveStreamIdleInput {\n\tbase: StreamIdleOptions;\n\tprofile?: ModelPerfProfile;\n\tpromptTokens: number;\n\tceilingMs?: number;\n\tlocalClass?: boolean;\n}\n\nexport interface ModelPerfProfileStreamRecorder {\n\tmodelKey: (model: Model<Api>) => string | undefined;\n\trecordSample: (modelKey: string, sample: ModelPerfSample) => void;\n\tnowMs?: () => number;\n\tnowIso?: () => string;\n}\n\nexport function isModelPerfProfile(value: unknown): value is ModelPerfProfile {\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) return false;\n\tconst record = value as Record<string, unknown>;\n\tconst samples = record.samples;\n\treturn (\n\t\t(record.prefillTokensPerSecond === undefined || isPositiveFiniteNumber(record.prefillTokensPerSecond)) &&\n\t\t(record.decodeTokensPerSecond === undefined || isPositiveFiniteNumber(record.decodeTokensPerSecond)) &&\n\t\t(record.loadMs === undefined || isPositiveFiniteNumber(record.loadMs)) &&\n\t\ttypeof samples === \"number\" &&\n\t\tNumber.isInteger(samples) &&\n\t\tsamples >= 0 &&\n\t\ttypeof record.updatedAt === \"string\"\n\t);\n}\n\nexport function hasUsableModelPerfSample(sample: ModelPerfSample): boolean {\n\treturn (\n\t\tprefillRateFromSample(sample) !== undefined ||\n\t\tdecodeRateFromSample(sample) !== undefined ||\n\t\tisPositiveFiniteNumber(sample.loadMs)\n\t);\n}\n\nexport function updateModelPerfProfile(\n\tcurrent: ModelPerfProfile | undefined,\n\tsample: ModelPerfSample,\n\tat: string = sample.at ?? new Date().toISOString(),\n): ModelPerfProfile | undefined {\n\tconst prefillRate = prefillRateFromSample(sample);\n\tconst decodeRate = decodeRateFromSample(sample);\n\tconst loadMs = isPositiveFiniteNumber(sample.loadMs) ? sample.loadMs : undefined;\n\tif (prefillRate === undefined && decodeRate === undefined && loadMs === undefined) return current;\n\n\tconst previousSamples = current?.samples ?? 0;\n\treturn {\n\t\t...(prefillRate !== undefined && {\n\t\t\tprefillTokensPerSecond: updateEwma(current?.prefillTokensPerSecond, prefillRate, previousSamples),\n\t\t}),\n\t\t...(prefillRate === undefined && current?.prefillTokensPerSecond !== undefined\n\t\t\t? { prefillTokensPerSecond: current.prefillTokensPerSecond }\n\t\t\t: {}),\n\t\t...(decodeRate !== undefined && {\n\t\t\tdecodeTokensPerSecond: updateEwma(current?.decodeTokensPerSecond, decodeRate, previousSamples),\n\t\t}),\n\t\t...(decodeRate === undefined && current?.decodeTokensPerSecond !== undefined\n\t\t\t? { decodeTokensPerSecond: current.decodeTokensPerSecond }\n\t\t\t: {}),\n\t\t...(loadMs !== undefined && { loadMs: updateEwma(current?.loadMs, loadMs, previousSamples) }),\n\t\t...(loadMs === undefined && current?.loadMs !== undefined ? { loadMs: current.loadMs } : {}),\n\t\tsamples: previousSamples + 1,\n\t\tupdatedAt: at,\n\t};\n}\n\nexport function resolveAdaptiveStreamIdleOptions(input: AdaptiveStreamIdleInput): Partial<StreamIdleOptions> {\n\tconst profile = input.profile;\n\tconst result: Partial<StreamIdleOptions> = {};\n\tconst localConnectDefaultMs = input.localClass\n\t\t? Math.max(input.base.connectMs, input.base.quietIdleMs)\n\t\t: input.base.connectMs;\n\tconst quietCeilingMs = Math.max(input.base.quietIdleMs, input.ceilingMs ?? DEFAULT_ADAPTIVE_STREAM_IDLE_CEILING_MS);\n\tconst connectCeilingMs = Math.max(localConnectDefaultMs, input.ceilingMs ?? DEFAULT_ADAPTIVE_STREAM_IDLE_CEILING_MS);\n\n\tconst expectedPrefillMs = expectedPrefillFromProfile(profile, input.promptTokens);\n\tif (expectedPrefillMs !== undefined) {\n\t\tconst quietIdleMs = adaptiveBound(input.base.quietIdleMs, quietCeilingMs, expectedPrefillMs);\n\t\tif (quietIdleMs > input.base.quietIdleMs) result.quietIdleMs = quietIdleMs;\n\t}\n\n\tif (input.localClass) {\n\t\tconst expectedConnectWorkMs =\n\t\t\texpectedPrefillMs !== undefined ? expectedPrefillMs + (profile?.loadMs ?? 0) : profile?.loadMs;\n\t\tconst connectMs =\n\t\t\texpectedConnectWorkMs !== undefined\n\t\t\t\t? adaptiveBound(localConnectDefaultMs, connectCeilingMs, expectedConnectWorkMs)\n\t\t\t\t: localConnectDefaultMs;\n\t\tif (connectMs > input.base.connectMs) result.connectMs = connectMs;\n\t}\n\n\treturn result;\n}\n\nexport function estimateContextPromptTokens(context: Context): number {\n\tconst serialized = JSON.stringify({\n\t\tsystemPrompt: context.systemPrompt,\n\t\tmessages: context.messages,\n\t\ttools: context.tools,\n\t});\n\tif (!serialized) return 0;\n\treturn Math.ceil(serialized.length / CHARS_PER_TOKEN_ESTIMATE);\n}\n\nexport function withModelPerfProfile(streamFn: StreamFn, recorder: ModelPerfProfileStreamRecorder): StreamFn {\n\treturn async (model, context, streamOptions) => {\n\t\tconst nowMs = recorder.nowMs ?? Date.now;\n\t\tconst nowIso = recorder.nowIso ?? (() => new Date().toISOString());\n\t\tconst requestStartedAtMs = nowMs();\n\t\tlet responseHeadersAtMs: number | undefined;\n\t\tlet firstTokenAtMs: number | undefined;\n\t\tconst originalOnResponse = streamOptions?.onResponse;\n\t\tconst outer = createAssistantMessageEventStream();\n\t\tlet latest = emptyAssistantMessage(model);\n\t\tlet inner: Awaited<ReturnType<StreamFn>>;\n\t\ttry {\n\t\t\tinner = await streamFn(model, context, {\n\t\t\t\t...streamOptions,\n\t\t\t\tonResponse: async (response: ProviderResponse, responseModel: Model<Api>) => {\n\t\t\t\t\tresponseHeadersAtMs ??= nowMs();\n\t\t\t\t\tawait originalOnResponse?.(response, responseModel);\n\t\t\t\t},\n\t\t\t});\n\t\t} catch (error) {\n\t\t\touter.push(perfStreamFailure(latest, streamOptions?.signal?.aborted === true, error));\n\t\t\treturn outer;\n\t\t}\n\n\t\tvoid (async () => {\n\t\t\tlet terminalPushed = false;\n\t\t\ttry {\n\t\t\t\tfor await (const event of inner) {\n\t\t\t\t\tlatest = assistantMessageFromEvent(event);\n\t\t\t\t\tif (firstTokenAtMs === undefined && isFirstTokenEvent(event)) {\n\t\t\t\t\t\tfirstTokenAtMs = nowMs();\n\t\t\t\t\t}\n\t\t\t\t\tif (event.type === \"done\") {\n\t\t\t\t\t\tterminalPushed = true;\n\t\t\t\t\t\trecordSuccessfulStreamSample({\n\t\t\t\t\t\t\tcompletionTokens: event.message.usage.output,\n\t\t\t\t\t\t\tdoneAtMs: nowMs(),\n\t\t\t\t\t\t\tfirstTokenAtMs,\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\tnowIso,\n\t\t\t\t\t\t\tpromptTokens:\n\t\t\t\t\t\t\t\tevent.message.usage.input + event.message.usage.cacheRead + event.message.usage.cacheWrite,\n\t\t\t\t\t\t\trecorder,\n\t\t\t\t\t\t\trequestStartedAtMs,\n\t\t\t\t\t\t\tresponseHeadersAtMs,\n\t\t\t\t\t\t});\n\t\t\t\t\t} else if (event.type === \"error\") {\n\t\t\t\t\t\tterminalPushed = true;\n\t\t\t\t\t}\n\t\t\t\t\touter.push(event);\n\t\t\t\t\tif (terminalPushed) return;\n\t\t\t\t}\n\t\t\t\touter.push(perfStreamFailure(latest, streamOptions?.signal?.aborted === true));\n\t\t\t} catch (error) {\n\t\t\t\touter.push(perfStreamFailure(latest, streamOptions?.signal?.aborted === true, error));\n\t\t\t}\n\t\t})();\n\n\t\treturn outer;\n\t};\n}\n\nfunction assistantMessageFromEvent(event: AssistantMessageEvent): AssistantMessage {\n\tif (event.type === \"done\") return event.message;\n\tif (event.type === \"error\") return event.error;\n\treturn event.partial;\n}\n\nfunction emptyAssistantMessage(model: Model<Api>): AssistantMessage {\n\treturn {\n\t\trole: \"assistant\",\n\t\tcontent: [],\n\t\tapi: model.api,\n\t\tprovider: model.provider,\n\t\tmodel: model.id,\n\t\tusage: {\n\t\t\tinput: 0,\n\t\t\toutput: 0,\n\t\t\tcacheRead: 0,\n\t\t\tcacheWrite: 0,\n\t\t\ttotalTokens: 0,\n\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t},\n\t\tstopReason: \"stop\",\n\t\ttimestamp: Date.now(),\n\t};\n}\n\nfunction perfStreamFailure(latest: AssistantMessage, aborted: boolean, error?: unknown): AssistantMessageEvent {\n\tconst stopReason = aborted ? \"aborted\" : \"error\";\n\tconst detail = error instanceof Error ? `: ${error.message}` : error === undefined ? \"\" : `: ${String(error)}`;\n\treturn {\n\t\ttype: \"error\",\n\t\treason: stopReason,\n\t\terror: {\n\t\t\t...latest,\n\t\t\tstopReason,\n\t\t\terrorMessage: aborted\n\t\t\t\t? `stream aborted before terminal event${detail}`\n\t\t\t\t: `stream ended without terminal event${detail}`,\n\t\t},\n\t};\n}\n\nfunction recordSuccessfulStreamSample(input: {\n\tmodel: Model<Api>;\n\trecorder: ModelPerfProfileStreamRecorder;\n\trequestStartedAtMs: number;\n\tresponseHeadersAtMs: number | undefined;\n\tfirstTokenAtMs: number | undefined;\n\tdoneAtMs: number;\n\tpromptTokens: number;\n\tcompletionTokens: number;\n\tnowIso: () => string;\n}): void {\n\tif (input.firstTokenAtMs === undefined) return;\n\tconst modelKey = input.recorder.modelKey(input.model);\n\tif (!modelKey) return;\n\tconst requestToFirstTokenMs = input.firstTokenAtMs - input.requestStartedAtMs;\n\tconst sample: ModelPerfSample = {\n\t\tpromptTokens: input.promptTokens,\n\t\tcompletionTokens: input.completionTokens,\n\t\trequestToFirstTokenMs,\n\t\tfirstTokenToDoneMs: input.doneAtMs - input.firstTokenAtMs,\n\t\tat: input.nowIso(),\n\t};\n\tif (input.responseHeadersAtMs !== undefined) {\n\t\tconst headersToFirstTokenMs = input.firstTokenAtMs - input.responseHeadersAtMs;\n\t\tconst headersWereEffectivelyDeferred =\n\t\t\trequestToFirstTokenMs >= DEFERRED_HEADERS_MIN_REQUEST_MS &&\n\t\t\theadersToFirstTokenMs <= DEFERRED_HEADERS_MAX_GAP_MS;\n\t\tif (!headersWereEffectivelyDeferred) sample.headersToFirstTokenMs = headersToFirstTokenMs;\n\t}\n\tif (!hasUsableModelPerfSample(sample)) return;\n\ttry {\n\t\tinput.recorder.recordSample(modelKey, sample);\n\t} catch {\n\t\t// Perf profiling must never fail the user turn.\n\t}\n}\n\nfunction isFirstTokenEvent(event: AssistantMessageEvent): boolean {\n\treturn (\n\t\t(event.type === \"text_delta\" || event.type === \"thinking_delta\" || event.type === \"toolcall_delta\") &&\n\t\tevent.delta.length > 0\n\t);\n}\n\nfunction expectedPrefillFromProfile(profile: ModelPerfProfile | undefined, promptTokens: number): number | undefined {\n\tconst prefillRate = profile?.prefillTokensPerSecond;\n\tif (!profile || profile.samples < 1 || !prefillRate || promptTokens <= 0) return undefined;\n\tconst expectedPrefillMs = (promptTokens / prefillRate) * 1000;\n\treturn Number.isFinite(expectedPrefillMs) && expectedPrefillMs > 0 ? expectedPrefillMs : undefined;\n}\n\nfunction adaptiveBound(defaultMs: number, ceilingMs: number, expectedMs: number): number {\n\treturn Math.min(Math.ceil(Math.max(defaultMs, expectedMs * 3)), ceilingMs);\n}\n\nfunction prefillRateFromSample(sample: ModelPerfSample): number | undefined {\n\tconst durationMs = isPositiveFiniteNumber(sample.headersToFirstTokenMs)\n\t\t? sample.headersToFirstTokenMs\n\t\t: sample.requestToFirstTokenMs;\n\treturn tokensPerSecond(sample.promptTokens, durationMs);\n}\n\nfunction decodeRateFromSample(sample: ModelPerfSample): number | undefined {\n\treturn tokensPerSecond(sample.completionTokens, sample.firstTokenToDoneMs);\n}\n\nfunction tokensPerSecond(tokens: number | undefined, durationMs: number | undefined): number | undefined {\n\tif (!isPositiveFiniteNumber(tokens) || !isPositiveFiniteNumber(durationMs)) return undefined;\n\treturn tokens / (durationMs / 1000);\n}\n\nfunction updateEwma(previous: number | undefined, next: number, previousSamples: number): number {\n\tif (previous === undefined || previousSamples === 0) return next;\n\treturn previous * (1 - PERF_EWMA_ALPHA) + next * PERF_EWMA_ALPHA;\n}\n\nfunction isPositiveFiniteNumber(value: unknown): value is number {\n\treturn typeof value === \"number\" && Number.isFinite(value) && value > 0;\n}\n"]}
@@ -18,18 +18,29 @@ export interface RuntimeLoadRequest {
18
18
  nowMs: number;
19
19
  minDwellMs?: number;
20
20
  pinActiveModel?: string;
21
+ /** Adapter identity for this request; set by RuntimeResidencyArbiter before planning. */
22
+ adapterId?: string;
23
+ /** When pinActiveModel is set, constrain that pin to this adapter when present. */
24
+ pinActiveAdapterId?: string;
21
25
  reservations?: RuntimeReservation[];
22
26
  recentEvictions?: RuntimeEvictionRecord[];
27
+ /** False performs admission/eviction only; the real model request owns cold loading. Default true. */
28
+ loadModel?: boolean;
23
29
  }
24
30
  export interface RuntimeReservation {
25
31
  model: string;
26
32
  bytes: number;
27
33
  priority: number;
34
+ /** Optional runtime identity; omitted means any adapter serving this model. */
35
+ adapterId?: string;
28
36
  }
29
37
  export interface RuntimeEvictionRecord {
30
38
  evicted: string;
31
39
  loaded: string;
32
40
  atMs: number;
41
+ /** Optional identities preserve anti-thrash separation across runtime adapters. */
42
+ evictedAdapterId?: string;
43
+ loadedAdapterId?: string;
33
44
  }
34
45
  export type RuntimeResidencyPlan = {
35
46
  status: "fits";
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-arbiter.d.ts","sourceRoot":"","sources":["../../../src/core/models/runtime-arbiter.ts"],"names":[],"mappings":"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","sourcesContent":["import type { OllamaRuntime, TransformersRuntime } from \"./local-runtime.ts\";\n\nexport type ResidencyControl = \"full\" | \"keep-alive\" | \"advisory\";\nexport type RuntimeRole = \"active\" | \"router\" | \"bench\" | \"probe\";\n\nexport interface RuntimeResidentModel {\n\tadapterId: string;\n\tmodel: string;\n\tbytes: number;\n\tlastUsedAtMs: number;\n\tpriority: number;\n\tpinned?: boolean;\n\tresidencyControl: ResidencyControl;\n}\n\nexport interface RuntimeLoadRequest {\n\tmodel: string;\n\tbytes: number;\n\trole: RuntimeRole;\n\tpriority: number;\n\tnowMs: number;\n\tminDwellMs?: number;\n\tpinActiveModel?: string;\n\treservations?: RuntimeReservation[];\n\trecentEvictions?: RuntimeEvictionRecord[];\n}\n\nexport interface RuntimeReservation {\n\tmodel: string;\n\tbytes: number;\n\tpriority: number;\n}\n\nexport interface RuntimeEvictionRecord {\n\tevicted: string;\n\tloaded: string;\n\tatMs: number;\n}\n\nexport type RuntimeResidencyPlan =\n\t| { status: \"fits\"; evict: RuntimeResidentModel[]; requiredBytes: number; budgetBytes: number }\n\t| { status: \"refuse\"; reason: string; evict: RuntimeResidentModel[]; requiredBytes: number; budgetBytes: number };\n\nexport interface RuntimeResidencyAdapter {\n\tid: string;\n\tresidencyControl: ResidencyControl;\n\tlist(): Promise<RuntimeResidentModel[]>;\n\tensureResident(model: string): Promise<void>;\n\trelease(model: string): Promise<void>;\n}\n\nexport class RuntimeResidencyArbiter {\n\tprivate readonly budgetBytes: number;\n\tprivate readonly adapters: Map<string, RuntimeResidencyAdapter>;\n\n\tconstructor(args: { budgetBytes: number; adapters: RuntimeResidencyAdapter[] }) {\n\t\tthis.budgetBytes = args.budgetBytes;\n\t\tthis.adapters = new Map(args.adapters.map((adapter) => [adapter.id, adapter]));\n\t}\n\n\tasync ensureResident(adapterId: string, request: RuntimeLoadRequest): Promise<RuntimeResidencyPlan> {\n\t\tconst adapter = this.adapters.get(adapterId);\n\t\tconst residents = (await Promise.all([...this.adapters.values()].map((entry) => entry.list()))).flat();\n\t\tif (!adapter) {\n\t\t\treturn {\n\t\t\t\tstatus: \"refuse\",\n\t\t\t\treason: \"unknown-adapter\",\n\t\t\t\tevict: [],\n\t\t\t\trequiredBytes: request.bytes,\n\t\t\t\tbudgetBytes: this.budgetBytes,\n\t\t\t};\n\t\t}\n\t\tconst plan = planRuntimeResidency({ budgetBytes: this.budgetBytes, residents, request });\n\t\tif (plan.status !== \"fits\") return plan;\n\t\tfor (const resident of plan.evict) {\n\t\t\tawait this.adapters.get(resident.adapterId)?.release(resident.model);\n\t\t}\n\t\tawait adapter.ensureResident(request.model);\n\t\treturn plan;\n\t}\n}\n\nexport class OllamaRuntimeResidencyAdapter implements RuntimeResidencyAdapter {\n\treadonly id: string;\n\treadonly residencyControl = \"keep-alive\" as const;\n\tprivate readonly runtime: Pick<OllamaRuntime, \"listResidentModels\" | \"ensureResident\" | \"releaseResident\">;\n\tprivate readonly nowMs: () => number;\n\n\tconstructor(\n\t\tid: string,\n\t\truntime: Pick<OllamaRuntime, \"listResidentModels\" | \"ensureResident\" | \"releaseResident\">,\n\t\toptions: { nowMs?: () => number } = {},\n\t) {\n\t\tthis.id = id;\n\t\tthis.runtime = runtime;\n\t\tthis.nowMs = options.nowMs ?? Date.now;\n\t}\n\n\tasync list(): Promise<RuntimeResidentModel[]> {\n\t\tconst now = this.nowMs();\n\t\treturn (await this.runtime.listResidentModels()).map((model) => ({\n\t\t\tadapterId: this.id,\n\t\t\tmodel: model.name,\n\t\t\tbytes: model.sizeBytes,\n\t\t\tlastUsedAtMs: now,\n\t\t\tpriority: 10,\n\t\t\tresidencyControl: this.residencyControl,\n\t\t}));\n\t}\n\n\tasync ensureResident(model: string): Promise<void> {\n\t\tconst result = await this.runtime.ensureResident(model);\n\t\tif (!result.ok) throw new Error(result.error);\n\t}\n\n\tasync release(model: string): Promise<void> {\n\t\tawait this.runtime.releaseResident(model);\n\t}\n}\n\nexport class TransformersRuntimeResidencyAdapter implements RuntimeResidencyAdapter {\n\treadonly id: string;\n\treadonly residencyControl = \"full\" as const;\n\tprivate readonly runtime: Pick<TransformersRuntime, \"detect\" | \"start\" | \"stop\">;\n\tprivate readonly model: string;\n\tprivate readonly bytes: number;\n\tprivate readonly nowMs: () => number;\n\n\tconstructor(\n\t\tid: string,\n\t\truntime: Pick<TransformersRuntime, \"detect\" | \"start\" | \"stop\">,\n\t\tmodel: string,\n\t\tbytes: number,\n\t\toptions: { nowMs?: () => number } = {},\n\t) {\n\t\tthis.id = id;\n\t\tthis.runtime = runtime;\n\t\tthis.model = model;\n\t\tthis.bytes = bytes;\n\t\tthis.nowMs = options.nowMs ?? Date.now;\n\t}\n\n\tasync list(): Promise<RuntimeResidentModel[]> {\n\t\tconst status = await this.runtime.detect();\n\t\tif (!status.serverUp) return [];\n\t\treturn [\n\t\t\t{\n\t\t\t\tadapterId: this.id,\n\t\t\t\tmodel: this.model,\n\t\t\t\tbytes: this.bytes,\n\t\t\t\tlastUsedAtMs: this.nowMs(),\n\t\t\t\tpriority: 10,\n\t\t\t\tresidencyControl: this.residencyControl,\n\t\t\t},\n\t\t];\n\t}\n\n\tasync ensureResident(_model: string): Promise<void> {\n\t\tconst result = await this.runtime.start();\n\t\tif (!result.started && result.reason !== \"already_running\") throw new Error(result.reason);\n\t}\n\n\tasync release(_model: string): Promise<void> {\n\t\tthis.runtime.stop();\n\t}\n}\n\nexport class AdvisoryRuntimeResidencyAdapter implements RuntimeResidencyAdapter {\n\treadonly id: string;\n\treadonly residencyControl = \"advisory\" as const;\n\tprivate readonly residents: RuntimeResidentModel[];\n\n\tconstructor(id: string, residents: Omit<RuntimeResidentModel, \"adapterId\" | \"residencyControl\">[]) {\n\t\tthis.id = id;\n\t\tthis.residents = residents.map((resident) => ({\n\t\t\t...resident,\n\t\t\tadapterId: id,\n\t\t\tresidencyControl: this.residencyControl,\n\t\t}));\n\t}\n\n\tasync list(): Promise<RuntimeResidentModel[]> {\n\t\treturn [...this.residents];\n\t}\n\n\tasync ensureResident(_model: string): Promise<void> {}\n\n\tasync release(_model: string): Promise<void> {}\n}\n\nexport function planRuntimeResidency(args: {\n\tbudgetBytes: number;\n\tresidents: readonly RuntimeResidentModel[];\n\trequest: RuntimeLoadRequest;\n}): RuntimeResidencyPlan {\n\tconst requestedModels = new Map<string, { bytes: number; priority: number }>();\n\trequestedModels.set(args.request.model, { bytes: args.request.bytes, priority: args.request.priority });\n\tfor (const reservation of args.request.reservations ?? []) {\n\t\tconst existing = requestedModels.get(reservation.model);\n\t\tif (!existing || reservation.bytes > existing.bytes) {\n\t\t\trequestedModels.set(reservation.model, { bytes: reservation.bytes, priority: reservation.priority });\n\t\t}\n\t}\n\n\tconst residentByModel = new Map(args.residents.map((resident) => [resident.model, resident]));\n\tconst residentBytes = args.residents.reduce((sum, resident) => sum + resident.bytes, 0);\n\tconst missingBytes = [...requestedModels].reduce((sum, [model, request]) => {\n\t\treturn residentByModel.has(model) ? sum : sum + request.bytes;\n\t}, 0);\n\tconst requiredBytes = residentBytes + missingBytes;\n\tif (requiredBytes <= args.budgetBytes) {\n\t\treturn { status: \"fits\", evict: [], requiredBytes, budgetBytes: args.budgetBytes };\n\t}\n\n\tif (hasPingPongRisk(args.request)) {\n\t\treturn { status: \"refuse\", reason: \"anti-thrash\", evict: [], requiredBytes, budgetBytes: args.budgetBytes };\n\t}\n\n\tlet projectedBytes = requiredBytes;\n\tconst evict: RuntimeResidentModel[] = [];\n\tconst candidates = args.residents\n\t\t.filter((resident) => !requestedModels.has(resident.model))\n\t\t.filter((resident) => !resident.pinned && resident.model !== args.request.pinActiveModel)\n\t\t.filter((resident) => resident.residencyControl !== \"advisory\")\n\t\t.filter(\n\t\t\t(resident) =>\n\t\t\t\targs.request.minDwellMs === undefined ||\n\t\t\t\targs.request.nowMs - resident.lastUsedAtMs >= args.request.minDwellMs,\n\t\t)\n\t\t.sort((left, right) => left.priority - right.priority || left.lastUsedAtMs - right.lastUsedAtMs);\n\tfor (const candidate of candidates) {\n\t\tevict.push(candidate);\n\t\tprojectedBytes -= candidate.bytes;\n\t\tif (projectedBytes <= args.budgetBytes) {\n\t\t\treturn { status: \"fits\", evict, requiredBytes, budgetBytes: args.budgetBytes };\n\t\t}\n\t}\n\treturn {\n\t\tstatus: \"refuse\",\n\t\treason: \"insufficient-evictable-memory\",\n\t\tevict,\n\t\trequiredBytes,\n\t\tbudgetBytes: args.budgetBytes,\n\t};\n}\n\nfunction hasPingPongRisk(request: RuntimeLoadRequest): boolean {\n\treturn (request.recentEvictions ?? []).some(\n\t\t(record) => record.evicted === request.model && request.nowMs - record.atMs <= (request.minDwellMs ?? 0),\n\t);\n}\n"]}
1
+ {"version":3,"file":"runtime-arbiter.d.ts","sourceRoot":"","sources":["../../../src/core/models/runtime-arbiter.ts"],"names":[],"mappings":"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","sourcesContent":["import type { OllamaRuntime, TransformersRuntime } from \"./local-runtime.ts\";\n\nexport type ResidencyControl = \"full\" | \"keep-alive\" | \"advisory\";\nexport type RuntimeRole = \"active\" | \"router\" | \"bench\" | \"probe\";\n\nexport interface RuntimeResidentModel {\n\tadapterId: string;\n\tmodel: string;\n\tbytes: number;\n\tlastUsedAtMs: number;\n\tpriority: number;\n\tpinned?: boolean;\n\tresidencyControl: ResidencyControl;\n}\n\nexport interface RuntimeLoadRequest {\n\tmodel: string;\n\tbytes: number;\n\trole: RuntimeRole;\n\tpriority: number;\n\tnowMs: number;\n\tminDwellMs?: number;\n\tpinActiveModel?: string;\n\t/** Adapter identity for this request; set by RuntimeResidencyArbiter before planning. */\n\tadapterId?: string;\n\t/** When pinActiveModel is set, constrain that pin to this adapter when present. */\n\tpinActiveAdapterId?: string;\n\treservations?: RuntimeReservation[];\n\trecentEvictions?: RuntimeEvictionRecord[];\n\t/** False performs admission/eviction only; the real model request owns cold loading. Default true. */\n\tloadModel?: boolean;\n}\n\nexport interface RuntimeReservation {\n\tmodel: string;\n\tbytes: number;\n\tpriority: number;\n\t/** Optional runtime identity; omitted means any adapter serving this model. */\n\tadapterId?: string;\n}\n\nexport interface RuntimeEvictionRecord {\n\tevicted: string;\n\tloaded: string;\n\tatMs: number;\n\t/** Optional identities preserve anti-thrash separation across runtime adapters. */\n\tevictedAdapterId?: string;\n\tloadedAdapterId?: string;\n}\n\nexport type RuntimeResidencyPlan =\n\t| { status: \"fits\"; evict: RuntimeResidentModel[]; requiredBytes: number; budgetBytes: number }\n\t| { status: \"refuse\"; reason: string; evict: RuntimeResidentModel[]; requiredBytes: number; budgetBytes: number };\n\nexport interface RuntimeResidencyAdapter {\n\tid: string;\n\tresidencyControl: ResidencyControl;\n\tlist(): Promise<RuntimeResidentModel[]>;\n\tensureResident(model: string): Promise<void>;\n\trelease(model: string): Promise<void>;\n}\n\nexport class RuntimeResidencyArbiter {\n\tprivate readonly budgetBytes: number;\n\tprivate readonly adapters: Map<string, RuntimeResidencyAdapter>;\n\n\tconstructor(args: { budgetBytes: number; adapters: RuntimeResidencyAdapter[] }) {\n\t\tthis.budgetBytes = args.budgetBytes;\n\t\tthis.adapters = new Map(args.adapters.map((adapter) => [adapter.id, adapter]));\n\t}\n\n\tasync ensureResident(adapterId: string, request: RuntimeLoadRequest): Promise<RuntimeResidencyPlan> {\n\t\tconst adapter = this.adapters.get(adapterId);\n\t\tconst residents = (await Promise.all([...this.adapters.values()].map((entry) => entry.list()))).flat();\n\t\tif (!adapter) {\n\t\t\treturn {\n\t\t\t\tstatus: \"refuse\",\n\t\t\t\treason: \"unknown-adapter\",\n\t\t\t\tevict: [],\n\t\t\t\trequiredBytes: request.bytes,\n\t\t\t\tbudgetBytes: this.budgetBytes,\n\t\t\t};\n\t\t}\n\t\tconst requestForAdapter: RuntimeLoadRequest = {\n\t\t\t...request,\n\t\t\tadapterId,\n\t\t\t...(request.pinActiveModel !== undefined && request.pinActiveAdapterId === undefined\n\t\t\t\t? { pinActiveAdapterId: adapterId }\n\t\t\t\t: {}),\n\t\t};\n\t\tconst plan = planRuntimeResidency({ budgetBytes: this.budgetBytes, residents, request: requestForAdapter });\n\t\tif (plan.status !== \"fits\") return plan;\n\t\tfor (const resident of plan.evict) {\n\t\t\tawait this.adapters.get(resident.adapterId)?.release(resident.model);\n\t\t}\n\t\tconst alreadyResident = residents.some(\n\t\t\t(resident) => resident.adapterId === adapterId && resident.model === request.model,\n\t\t);\n\t\tif (request.loadModel !== false && !alreadyResident) await adapter.ensureResident(request.model);\n\t\treturn plan;\n\t}\n}\n\nexport class OllamaRuntimeResidencyAdapter implements RuntimeResidencyAdapter {\n\treadonly id: string;\n\treadonly residencyControl = \"keep-alive\" as const;\n\tprivate readonly runtime: Pick<OllamaRuntime, \"listResidentModels\" | \"ensureResident\" | \"releaseResident\">;\n\tprivate readonly nowMs: () => number;\n\n\tconstructor(\n\t\tid: string,\n\t\truntime: Pick<OllamaRuntime, \"listResidentModels\" | \"ensureResident\" | \"releaseResident\">,\n\t\toptions: { nowMs?: () => number } = {},\n\t) {\n\t\tthis.id = id;\n\t\tthis.runtime = runtime;\n\t\tthis.nowMs = options.nowMs ?? Date.now;\n\t}\n\n\tasync list(): Promise<RuntimeResidentModel[]> {\n\t\tconst now = this.nowMs();\n\t\treturn (await this.runtime.listResidentModels()).map((model) => ({\n\t\t\tadapterId: this.id,\n\t\t\tmodel: model.name,\n\t\t\tbytes: model.sizeBytes,\n\t\t\tlastUsedAtMs: now,\n\t\t\tpriority: 10,\n\t\t\tresidencyControl: this.residencyControl,\n\t\t}));\n\t}\n\n\tasync ensureResident(model: string): Promise<void> {\n\t\tconst result = await this.runtime.ensureResident(model);\n\t\tif (!result.ok) throw new Error(result.error);\n\t}\n\n\tasync release(model: string): Promise<void> {\n\t\tawait this.runtime.releaseResident(model);\n\t}\n}\n\nexport class TransformersRuntimeResidencyAdapter implements RuntimeResidencyAdapter {\n\treadonly id: string;\n\treadonly residencyControl = \"full\" as const;\n\tprivate readonly runtime: Pick<TransformersRuntime, \"detect\" | \"start\" | \"stop\">;\n\tprivate readonly model: string;\n\tprivate readonly bytes: number;\n\tprivate readonly nowMs: () => number;\n\n\tconstructor(\n\t\tid: string,\n\t\truntime: Pick<TransformersRuntime, \"detect\" | \"start\" | \"stop\">,\n\t\tmodel: string,\n\t\tbytes: number,\n\t\toptions: { nowMs?: () => number } = {},\n\t) {\n\t\tthis.id = id;\n\t\tthis.runtime = runtime;\n\t\tthis.model = model;\n\t\tthis.bytes = bytes;\n\t\tthis.nowMs = options.nowMs ?? Date.now;\n\t}\n\n\tasync list(): Promise<RuntimeResidentModel[]> {\n\t\tconst status = await this.runtime.detect();\n\t\tif (!status.serverUp) return [];\n\t\treturn [\n\t\t\t{\n\t\t\t\tadapterId: this.id,\n\t\t\t\tmodel: this.model,\n\t\t\t\tbytes: this.bytes,\n\t\t\t\tlastUsedAtMs: this.nowMs(),\n\t\t\t\tpriority: 10,\n\t\t\t\tresidencyControl: this.residencyControl,\n\t\t\t},\n\t\t];\n\t}\n\n\tasync ensureResident(_model: string): Promise<void> {\n\t\tconst result = await this.runtime.start();\n\t\tif (!result.started && result.reason !== \"already_running\") throw new Error(result.reason);\n\t}\n\n\tasync release(_model: string): Promise<void> {\n\t\tthis.runtime.stop();\n\t}\n}\n\nexport class AdvisoryRuntimeResidencyAdapter implements RuntimeResidencyAdapter {\n\treadonly id: string;\n\treadonly residencyControl = \"advisory\" as const;\n\tprivate readonly residents: RuntimeResidentModel[];\n\n\tconstructor(id: string, residents: Omit<RuntimeResidentModel, \"adapterId\" | \"residencyControl\">[]) {\n\t\tthis.id = id;\n\t\tthis.residents = residents.map((resident) => ({\n\t\t\t...resident,\n\t\t\tadapterId: id,\n\t\t\tresidencyControl: this.residencyControl,\n\t\t}));\n\t}\n\n\tasync list(): Promise<RuntimeResidentModel[]> {\n\t\treturn [...this.residents];\n\t}\n\n\tasync ensureResident(_model: string): Promise<void> {}\n\n\tasync release(_model: string): Promise<void> {}\n}\n\nexport function planRuntimeResidency(args: {\n\tbudgetBytes: number;\n\tresidents: readonly RuntimeResidentModel[];\n\trequest: RuntimeLoadRequest;\n}): RuntimeResidencyPlan {\n\tconst requestedModels = new Map<string, { model: string; adapterId?: string; bytes: number; priority: number }>();\n\tconst addRequestedModel = (entry: { model: string; adapterId?: string; bytes: number; priority: number }): void => {\n\t\tconst key = `${entry.adapterId ?? \"*\"}\\0${entry.model}`;\n\t\tconst existing = requestedModels.get(key);\n\t\tif (!existing || entry.bytes > existing.bytes) requestedModels.set(key, entry);\n\t};\n\taddRequestedModel(args.request);\n\tfor (const reservation of args.request.reservations ?? []) {\n\t\taddRequestedModel({\n\t\t\t...reservation,\n\t\t\tadapterId:\n\t\t\t\treservation.adapterId ?? (reservation.model === args.request.model ? args.request.adapterId : undefined),\n\t\t});\n\t}\n\n\tconst isResidentForRequest = (\n\t\tresident: RuntimeResidentModel,\n\t\trequested: { model: string; adapterId?: string },\n\t): boolean =>\n\t\tresident.model === requested.model &&\n\t\t(requested.adapterId === undefined || resident.adapterId === requested.adapterId);\n\tconst residentBytes = args.residents.reduce((sum, resident) => sum + resident.bytes, 0);\n\tconst missingBytes = [...requestedModels.values()].reduce((sum, requested) => {\n\t\treturn args.residents.some((resident) => isResidentForRequest(resident, requested)) ? sum : sum + requested.bytes;\n\t}, 0);\n\tconst requiredBytes = residentBytes + missingBytes;\n\tif (requiredBytes <= args.budgetBytes) {\n\t\treturn { status: \"fits\", evict: [], requiredBytes, budgetBytes: args.budgetBytes };\n\t}\n\n\tif (hasPingPongRisk(args.request)) {\n\t\treturn { status: \"refuse\", reason: \"anti-thrash\", evict: [], requiredBytes, budgetBytes: args.budgetBytes };\n\t}\n\n\tlet projectedBytes = requiredBytes;\n\tconst evict: RuntimeResidentModel[] = [];\n\tconst candidates = args.residents\n\t\t.filter(\n\t\t\t(resident) => ![...requestedModels.values()].some((requested) => isResidentForRequest(resident, requested)),\n\t\t)\n\t\t.filter(\n\t\t\t(resident) =>\n\t\t\t\t!resident.pinned &&\n\t\t\t\t!(\n\t\t\t\t\tresident.model === args.request.pinActiveModel &&\n\t\t\t\t\t(args.request.pinActiveAdapterId === undefined || resident.adapterId === args.request.pinActiveAdapterId)\n\t\t\t\t),\n\t\t)\n\t\t.filter((resident) => resident.residencyControl !== \"advisory\")\n\t\t.filter(\n\t\t\t(resident) =>\n\t\t\t\targs.request.minDwellMs === undefined ||\n\t\t\t\targs.request.nowMs - resident.lastUsedAtMs >= args.request.minDwellMs,\n\t\t)\n\t\t.sort((left, right) => left.priority - right.priority || left.lastUsedAtMs - right.lastUsedAtMs);\n\tfor (const candidate of candidates) {\n\t\tevict.push(candidate);\n\t\tprojectedBytes -= candidate.bytes;\n\t\tif (projectedBytes <= args.budgetBytes) {\n\t\t\treturn { status: \"fits\", evict, requiredBytes, budgetBytes: args.budgetBytes };\n\t\t}\n\t}\n\treturn {\n\t\tstatus: \"refuse\",\n\t\treason: \"insufficient-evictable-memory\",\n\t\tevict,\n\t\trequiredBytes,\n\t\tbudgetBytes: args.budgetBytes,\n\t};\n}\n\nfunction hasPingPongRisk(request: RuntimeLoadRequest): boolean {\n\treturn (request.recentEvictions ?? []).some(\n\t\t(record) =>\n\t\t\trecord.evicted === request.model &&\n\t\t\t(request.adapterId === undefined ||\n\t\t\t\trecord.evictedAdapterId === undefined ||\n\t\t\t\trecord.evictedAdapterId === request.adapterId) &&\n\t\t\trequest.nowMs - record.atMs <= (request.minDwellMs ?? 0),\n\t);\n}\n"]}
@@ -17,13 +17,22 @@ export class RuntimeResidencyArbiter {
17
17
  budgetBytes: this.budgetBytes,
18
18
  };
19
19
  }
20
- const plan = planRuntimeResidency({ budgetBytes: this.budgetBytes, residents, request });
20
+ const requestForAdapter = {
21
+ ...request,
22
+ adapterId,
23
+ ...(request.pinActiveModel !== undefined && request.pinActiveAdapterId === undefined
24
+ ? { pinActiveAdapterId: adapterId }
25
+ : {}),
26
+ };
27
+ const plan = planRuntimeResidency({ budgetBytes: this.budgetBytes, residents, request: requestForAdapter });
21
28
  if (plan.status !== "fits")
22
29
  return plan;
23
30
  for (const resident of plan.evict) {
24
31
  await this.adapters.get(resident.adapterId)?.release(resident.model);
25
32
  }
26
- await adapter.ensureResident(request.model);
33
+ const alreadyResident = residents.some((resident) => resident.adapterId === adapterId && resident.model === request.model);
34
+ if (request.loadModel !== false && !alreadyResident)
35
+ await adapter.ensureResident(request.model);
27
36
  return plan;
28
37
  }
29
38
  }
@@ -115,17 +124,24 @@ export class AdvisoryRuntimeResidencyAdapter {
115
124
  }
116
125
  export function planRuntimeResidency(args) {
117
126
  const requestedModels = new Map();
118
- requestedModels.set(args.request.model, { bytes: args.request.bytes, priority: args.request.priority });
127
+ const addRequestedModel = (entry) => {
128
+ const key = `${entry.adapterId ?? "*"}\0${entry.model}`;
129
+ const existing = requestedModels.get(key);
130
+ if (!existing || entry.bytes > existing.bytes)
131
+ requestedModels.set(key, entry);
132
+ };
133
+ addRequestedModel(args.request);
119
134
  for (const reservation of args.request.reservations ?? []) {
120
- const existing = requestedModels.get(reservation.model);
121
- if (!existing || reservation.bytes > existing.bytes) {
122
- requestedModels.set(reservation.model, { bytes: reservation.bytes, priority: reservation.priority });
123
- }
135
+ addRequestedModel({
136
+ ...reservation,
137
+ adapterId: reservation.adapterId ?? (reservation.model === args.request.model ? args.request.adapterId : undefined),
138
+ });
124
139
  }
125
- const residentByModel = new Map(args.residents.map((resident) => [resident.model, resident]));
140
+ const isResidentForRequest = (resident, requested) => resident.model === requested.model &&
141
+ (requested.adapterId === undefined || resident.adapterId === requested.adapterId);
126
142
  const residentBytes = args.residents.reduce((sum, resident) => sum + resident.bytes, 0);
127
- const missingBytes = [...requestedModels].reduce((sum, [model, request]) => {
128
- return residentByModel.has(model) ? sum : sum + request.bytes;
143
+ const missingBytes = [...requestedModels.values()].reduce((sum, requested) => {
144
+ return args.residents.some((resident) => isResidentForRequest(resident, requested)) ? sum : sum + requested.bytes;
129
145
  }, 0);
130
146
  const requiredBytes = residentBytes + missingBytes;
131
147
  if (requiredBytes <= args.budgetBytes) {
@@ -137,8 +153,10 @@ export function planRuntimeResidency(args) {
137
153
  let projectedBytes = requiredBytes;
138
154
  const evict = [];
139
155
  const candidates = args.residents
140
- .filter((resident) => !requestedModels.has(resident.model))
141
- .filter((resident) => !resident.pinned && resident.model !== args.request.pinActiveModel)
156
+ .filter((resident) => ![...requestedModels.values()].some((requested) => isResidentForRequest(resident, requested)))
157
+ .filter((resident) => !resident.pinned &&
158
+ !(resident.model === args.request.pinActiveModel &&
159
+ (args.request.pinActiveAdapterId === undefined || resident.adapterId === args.request.pinActiveAdapterId)))
142
160
  .filter((resident) => resident.residencyControl !== "advisory")
143
161
  .filter((resident) => args.request.minDwellMs === undefined ||
144
162
  args.request.nowMs - resident.lastUsedAtMs >= args.request.minDwellMs)
@@ -159,6 +177,10 @@ export function planRuntimeResidency(args) {
159
177
  };
160
178
  }
161
179
  function hasPingPongRisk(request) {
162
- return (request.recentEvictions ?? []).some((record) => record.evicted === request.model && request.nowMs - record.atMs <= (request.minDwellMs ?? 0));
180
+ return (request.recentEvictions ?? []).some((record) => record.evicted === request.model &&
181
+ (request.adapterId === undefined ||
182
+ record.evictedAdapterId === undefined ||
183
+ record.evictedAdapterId === request.adapterId) &&
184
+ request.nowMs - record.atMs <= (request.minDwellMs ?? 0));
163
185
  }
164
186
  //# sourceMappingURL=runtime-arbiter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-arbiter.js","sourceRoot":"","sources":["../../../src/core/models/runtime-arbiter.ts"],"names":[],"mappings":"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","sourcesContent":["import type { OllamaRuntime, TransformersRuntime } from \"./local-runtime.ts\";\n\nexport type ResidencyControl = \"full\" | \"keep-alive\" | \"advisory\";\nexport type RuntimeRole = \"active\" | \"router\" | \"bench\" | \"probe\";\n\nexport interface RuntimeResidentModel {\n\tadapterId: string;\n\tmodel: string;\n\tbytes: number;\n\tlastUsedAtMs: number;\n\tpriority: number;\n\tpinned?: boolean;\n\tresidencyControl: ResidencyControl;\n}\n\nexport interface RuntimeLoadRequest {\n\tmodel: string;\n\tbytes: number;\n\trole: RuntimeRole;\n\tpriority: number;\n\tnowMs: number;\n\tminDwellMs?: number;\n\tpinActiveModel?: string;\n\treservations?: RuntimeReservation[];\n\trecentEvictions?: RuntimeEvictionRecord[];\n}\n\nexport interface RuntimeReservation {\n\tmodel: string;\n\tbytes: number;\n\tpriority: number;\n}\n\nexport interface RuntimeEvictionRecord {\n\tevicted: string;\n\tloaded: string;\n\tatMs: number;\n}\n\nexport type RuntimeResidencyPlan =\n\t| { status: \"fits\"; evict: RuntimeResidentModel[]; requiredBytes: number; budgetBytes: number }\n\t| { status: \"refuse\"; reason: string; evict: RuntimeResidentModel[]; requiredBytes: number; budgetBytes: number };\n\nexport interface RuntimeResidencyAdapter {\n\tid: string;\n\tresidencyControl: ResidencyControl;\n\tlist(): Promise<RuntimeResidentModel[]>;\n\tensureResident(model: string): Promise<void>;\n\trelease(model: string): Promise<void>;\n}\n\nexport class RuntimeResidencyArbiter {\n\tprivate readonly budgetBytes: number;\n\tprivate readonly adapters: Map<string, RuntimeResidencyAdapter>;\n\n\tconstructor(args: { budgetBytes: number; adapters: RuntimeResidencyAdapter[] }) {\n\t\tthis.budgetBytes = args.budgetBytes;\n\t\tthis.adapters = new Map(args.adapters.map((adapter) => [adapter.id, adapter]));\n\t}\n\n\tasync ensureResident(adapterId: string, request: RuntimeLoadRequest): Promise<RuntimeResidencyPlan> {\n\t\tconst adapter = this.adapters.get(adapterId);\n\t\tconst residents = (await Promise.all([...this.adapters.values()].map((entry) => entry.list()))).flat();\n\t\tif (!adapter) {\n\t\t\treturn {\n\t\t\t\tstatus: \"refuse\",\n\t\t\t\treason: \"unknown-adapter\",\n\t\t\t\tevict: [],\n\t\t\t\trequiredBytes: request.bytes,\n\t\t\t\tbudgetBytes: this.budgetBytes,\n\t\t\t};\n\t\t}\n\t\tconst plan = planRuntimeResidency({ budgetBytes: this.budgetBytes, residents, request });\n\t\tif (plan.status !== \"fits\") return plan;\n\t\tfor (const resident of plan.evict) {\n\t\t\tawait this.adapters.get(resident.adapterId)?.release(resident.model);\n\t\t}\n\t\tawait adapter.ensureResident(request.model);\n\t\treturn plan;\n\t}\n}\n\nexport class OllamaRuntimeResidencyAdapter implements RuntimeResidencyAdapter {\n\treadonly id: string;\n\treadonly residencyControl = \"keep-alive\" as const;\n\tprivate readonly runtime: Pick<OllamaRuntime, \"listResidentModels\" | \"ensureResident\" | \"releaseResident\">;\n\tprivate readonly nowMs: () => number;\n\n\tconstructor(\n\t\tid: string,\n\t\truntime: Pick<OllamaRuntime, \"listResidentModels\" | \"ensureResident\" | \"releaseResident\">,\n\t\toptions: { nowMs?: () => number } = {},\n\t) {\n\t\tthis.id = id;\n\t\tthis.runtime = runtime;\n\t\tthis.nowMs = options.nowMs ?? Date.now;\n\t}\n\n\tasync list(): Promise<RuntimeResidentModel[]> {\n\t\tconst now = this.nowMs();\n\t\treturn (await this.runtime.listResidentModels()).map((model) => ({\n\t\t\tadapterId: this.id,\n\t\t\tmodel: model.name,\n\t\t\tbytes: model.sizeBytes,\n\t\t\tlastUsedAtMs: now,\n\t\t\tpriority: 10,\n\t\t\tresidencyControl: this.residencyControl,\n\t\t}));\n\t}\n\n\tasync ensureResident(model: string): Promise<void> {\n\t\tconst result = await this.runtime.ensureResident(model);\n\t\tif (!result.ok) throw new Error(result.error);\n\t}\n\n\tasync release(model: string): Promise<void> {\n\t\tawait this.runtime.releaseResident(model);\n\t}\n}\n\nexport class TransformersRuntimeResidencyAdapter implements RuntimeResidencyAdapter {\n\treadonly id: string;\n\treadonly residencyControl = \"full\" as const;\n\tprivate readonly runtime: Pick<TransformersRuntime, \"detect\" | \"start\" | \"stop\">;\n\tprivate readonly model: string;\n\tprivate readonly bytes: number;\n\tprivate readonly nowMs: () => number;\n\n\tconstructor(\n\t\tid: string,\n\t\truntime: Pick<TransformersRuntime, \"detect\" | \"start\" | \"stop\">,\n\t\tmodel: string,\n\t\tbytes: number,\n\t\toptions: { nowMs?: () => number } = {},\n\t) {\n\t\tthis.id = id;\n\t\tthis.runtime = runtime;\n\t\tthis.model = model;\n\t\tthis.bytes = bytes;\n\t\tthis.nowMs = options.nowMs ?? Date.now;\n\t}\n\n\tasync list(): Promise<RuntimeResidentModel[]> {\n\t\tconst status = await this.runtime.detect();\n\t\tif (!status.serverUp) return [];\n\t\treturn [\n\t\t\t{\n\t\t\t\tadapterId: this.id,\n\t\t\t\tmodel: this.model,\n\t\t\t\tbytes: this.bytes,\n\t\t\t\tlastUsedAtMs: this.nowMs(),\n\t\t\t\tpriority: 10,\n\t\t\t\tresidencyControl: this.residencyControl,\n\t\t\t},\n\t\t];\n\t}\n\n\tasync ensureResident(_model: string): Promise<void> {\n\t\tconst result = await this.runtime.start();\n\t\tif (!result.started && result.reason !== \"already_running\") throw new Error(result.reason);\n\t}\n\n\tasync release(_model: string): Promise<void> {\n\t\tthis.runtime.stop();\n\t}\n}\n\nexport class AdvisoryRuntimeResidencyAdapter implements RuntimeResidencyAdapter {\n\treadonly id: string;\n\treadonly residencyControl = \"advisory\" as const;\n\tprivate readonly residents: RuntimeResidentModel[];\n\n\tconstructor(id: string, residents: Omit<RuntimeResidentModel, \"adapterId\" | \"residencyControl\">[]) {\n\t\tthis.id = id;\n\t\tthis.residents = residents.map((resident) => ({\n\t\t\t...resident,\n\t\t\tadapterId: id,\n\t\t\tresidencyControl: this.residencyControl,\n\t\t}));\n\t}\n\n\tasync list(): Promise<RuntimeResidentModel[]> {\n\t\treturn [...this.residents];\n\t}\n\n\tasync ensureResident(_model: string): Promise<void> {}\n\n\tasync release(_model: string): Promise<void> {}\n}\n\nexport function planRuntimeResidency(args: {\n\tbudgetBytes: number;\n\tresidents: readonly RuntimeResidentModel[];\n\trequest: RuntimeLoadRequest;\n}): RuntimeResidencyPlan {\n\tconst requestedModels = new Map<string, { bytes: number; priority: number }>();\n\trequestedModels.set(args.request.model, { bytes: args.request.bytes, priority: args.request.priority });\n\tfor (const reservation of args.request.reservations ?? []) {\n\t\tconst existing = requestedModels.get(reservation.model);\n\t\tif (!existing || reservation.bytes > existing.bytes) {\n\t\t\trequestedModels.set(reservation.model, { bytes: reservation.bytes, priority: reservation.priority });\n\t\t}\n\t}\n\n\tconst residentByModel = new Map(args.residents.map((resident) => [resident.model, resident]));\n\tconst residentBytes = args.residents.reduce((sum, resident) => sum + resident.bytes, 0);\n\tconst missingBytes = [...requestedModels].reduce((sum, [model, request]) => {\n\t\treturn residentByModel.has(model) ? sum : sum + request.bytes;\n\t}, 0);\n\tconst requiredBytes = residentBytes + missingBytes;\n\tif (requiredBytes <= args.budgetBytes) {\n\t\treturn { status: \"fits\", evict: [], requiredBytes, budgetBytes: args.budgetBytes };\n\t}\n\n\tif (hasPingPongRisk(args.request)) {\n\t\treturn { status: \"refuse\", reason: \"anti-thrash\", evict: [], requiredBytes, budgetBytes: args.budgetBytes };\n\t}\n\n\tlet projectedBytes = requiredBytes;\n\tconst evict: RuntimeResidentModel[] = [];\n\tconst candidates = args.residents\n\t\t.filter((resident) => !requestedModels.has(resident.model))\n\t\t.filter((resident) => !resident.pinned && resident.model !== args.request.pinActiveModel)\n\t\t.filter((resident) => resident.residencyControl !== \"advisory\")\n\t\t.filter(\n\t\t\t(resident) =>\n\t\t\t\targs.request.minDwellMs === undefined ||\n\t\t\t\targs.request.nowMs - resident.lastUsedAtMs >= args.request.minDwellMs,\n\t\t)\n\t\t.sort((left, right) => left.priority - right.priority || left.lastUsedAtMs - right.lastUsedAtMs);\n\tfor (const candidate of candidates) {\n\t\tevict.push(candidate);\n\t\tprojectedBytes -= candidate.bytes;\n\t\tif (projectedBytes <= args.budgetBytes) {\n\t\t\treturn { status: \"fits\", evict, requiredBytes, budgetBytes: args.budgetBytes };\n\t\t}\n\t}\n\treturn {\n\t\tstatus: \"refuse\",\n\t\treason: \"insufficient-evictable-memory\",\n\t\tevict,\n\t\trequiredBytes,\n\t\tbudgetBytes: args.budgetBytes,\n\t};\n}\n\nfunction hasPingPongRisk(request: RuntimeLoadRequest): boolean {\n\treturn (request.recentEvictions ?? []).some(\n\t\t(record) => record.evicted === request.model && request.nowMs - record.atMs <= (request.minDwellMs ?? 0),\n\t);\n}\n"]}
1
+ {"version":3,"file":"runtime-arbiter.js","sourceRoot":"","sources":["../../../src/core/models/runtime-arbiter.ts"],"names":[],"mappings":"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","sourcesContent":["import type { OllamaRuntime, TransformersRuntime } from \"./local-runtime.ts\";\n\nexport type ResidencyControl = \"full\" | \"keep-alive\" | \"advisory\";\nexport type RuntimeRole = \"active\" | \"router\" | \"bench\" | \"probe\";\n\nexport interface RuntimeResidentModel {\n\tadapterId: string;\n\tmodel: string;\n\tbytes: number;\n\tlastUsedAtMs: number;\n\tpriority: number;\n\tpinned?: boolean;\n\tresidencyControl: ResidencyControl;\n}\n\nexport interface RuntimeLoadRequest {\n\tmodel: string;\n\tbytes: number;\n\trole: RuntimeRole;\n\tpriority: number;\n\tnowMs: number;\n\tminDwellMs?: number;\n\tpinActiveModel?: string;\n\t/** Adapter identity for this request; set by RuntimeResidencyArbiter before planning. */\n\tadapterId?: string;\n\t/** When pinActiveModel is set, constrain that pin to this adapter when present. */\n\tpinActiveAdapterId?: string;\n\treservations?: RuntimeReservation[];\n\trecentEvictions?: RuntimeEvictionRecord[];\n\t/** False performs admission/eviction only; the real model request owns cold loading. Default true. */\n\tloadModel?: boolean;\n}\n\nexport interface RuntimeReservation {\n\tmodel: string;\n\tbytes: number;\n\tpriority: number;\n\t/** Optional runtime identity; omitted means any adapter serving this model. */\n\tadapterId?: string;\n}\n\nexport interface RuntimeEvictionRecord {\n\tevicted: string;\n\tloaded: string;\n\tatMs: number;\n\t/** Optional identities preserve anti-thrash separation across runtime adapters. */\n\tevictedAdapterId?: string;\n\tloadedAdapterId?: string;\n}\n\nexport type RuntimeResidencyPlan =\n\t| { status: \"fits\"; evict: RuntimeResidentModel[]; requiredBytes: number; budgetBytes: number }\n\t| { status: \"refuse\"; reason: string; evict: RuntimeResidentModel[]; requiredBytes: number; budgetBytes: number };\n\nexport interface RuntimeResidencyAdapter {\n\tid: string;\n\tresidencyControl: ResidencyControl;\n\tlist(): Promise<RuntimeResidentModel[]>;\n\tensureResident(model: string): Promise<void>;\n\trelease(model: string): Promise<void>;\n}\n\nexport class RuntimeResidencyArbiter {\n\tprivate readonly budgetBytes: number;\n\tprivate readonly adapters: Map<string, RuntimeResidencyAdapter>;\n\n\tconstructor(args: { budgetBytes: number; adapters: RuntimeResidencyAdapter[] }) {\n\t\tthis.budgetBytes = args.budgetBytes;\n\t\tthis.adapters = new Map(args.adapters.map((adapter) => [adapter.id, adapter]));\n\t}\n\n\tasync ensureResident(adapterId: string, request: RuntimeLoadRequest): Promise<RuntimeResidencyPlan> {\n\t\tconst adapter = this.adapters.get(adapterId);\n\t\tconst residents = (await Promise.all([...this.adapters.values()].map((entry) => entry.list()))).flat();\n\t\tif (!adapter) {\n\t\t\treturn {\n\t\t\t\tstatus: \"refuse\",\n\t\t\t\treason: \"unknown-adapter\",\n\t\t\t\tevict: [],\n\t\t\t\trequiredBytes: request.bytes,\n\t\t\t\tbudgetBytes: this.budgetBytes,\n\t\t\t};\n\t\t}\n\t\tconst requestForAdapter: RuntimeLoadRequest = {\n\t\t\t...request,\n\t\t\tadapterId,\n\t\t\t...(request.pinActiveModel !== undefined && request.pinActiveAdapterId === undefined\n\t\t\t\t? { pinActiveAdapterId: adapterId }\n\t\t\t\t: {}),\n\t\t};\n\t\tconst plan = planRuntimeResidency({ budgetBytes: this.budgetBytes, residents, request: requestForAdapter });\n\t\tif (plan.status !== \"fits\") return plan;\n\t\tfor (const resident of plan.evict) {\n\t\t\tawait this.adapters.get(resident.adapterId)?.release(resident.model);\n\t\t}\n\t\tconst alreadyResident = residents.some(\n\t\t\t(resident) => resident.adapterId === adapterId && resident.model === request.model,\n\t\t);\n\t\tif (request.loadModel !== false && !alreadyResident) await adapter.ensureResident(request.model);\n\t\treturn plan;\n\t}\n}\n\nexport class OllamaRuntimeResidencyAdapter implements RuntimeResidencyAdapter {\n\treadonly id: string;\n\treadonly residencyControl = \"keep-alive\" as const;\n\tprivate readonly runtime: Pick<OllamaRuntime, \"listResidentModels\" | \"ensureResident\" | \"releaseResident\">;\n\tprivate readonly nowMs: () => number;\n\n\tconstructor(\n\t\tid: string,\n\t\truntime: Pick<OllamaRuntime, \"listResidentModels\" | \"ensureResident\" | \"releaseResident\">,\n\t\toptions: { nowMs?: () => number } = {},\n\t) {\n\t\tthis.id = id;\n\t\tthis.runtime = runtime;\n\t\tthis.nowMs = options.nowMs ?? Date.now;\n\t}\n\n\tasync list(): Promise<RuntimeResidentModel[]> {\n\t\tconst now = this.nowMs();\n\t\treturn (await this.runtime.listResidentModels()).map((model) => ({\n\t\t\tadapterId: this.id,\n\t\t\tmodel: model.name,\n\t\t\tbytes: model.sizeBytes,\n\t\t\tlastUsedAtMs: now,\n\t\t\tpriority: 10,\n\t\t\tresidencyControl: this.residencyControl,\n\t\t}));\n\t}\n\n\tasync ensureResident(model: string): Promise<void> {\n\t\tconst result = await this.runtime.ensureResident(model);\n\t\tif (!result.ok) throw new Error(result.error);\n\t}\n\n\tasync release(model: string): Promise<void> {\n\t\tawait this.runtime.releaseResident(model);\n\t}\n}\n\nexport class TransformersRuntimeResidencyAdapter implements RuntimeResidencyAdapter {\n\treadonly id: string;\n\treadonly residencyControl = \"full\" as const;\n\tprivate readonly runtime: Pick<TransformersRuntime, \"detect\" | \"start\" | \"stop\">;\n\tprivate readonly model: string;\n\tprivate readonly bytes: number;\n\tprivate readonly nowMs: () => number;\n\n\tconstructor(\n\t\tid: string,\n\t\truntime: Pick<TransformersRuntime, \"detect\" | \"start\" | \"stop\">,\n\t\tmodel: string,\n\t\tbytes: number,\n\t\toptions: { nowMs?: () => number } = {},\n\t) {\n\t\tthis.id = id;\n\t\tthis.runtime = runtime;\n\t\tthis.model = model;\n\t\tthis.bytes = bytes;\n\t\tthis.nowMs = options.nowMs ?? Date.now;\n\t}\n\n\tasync list(): Promise<RuntimeResidentModel[]> {\n\t\tconst status = await this.runtime.detect();\n\t\tif (!status.serverUp) return [];\n\t\treturn [\n\t\t\t{\n\t\t\t\tadapterId: this.id,\n\t\t\t\tmodel: this.model,\n\t\t\t\tbytes: this.bytes,\n\t\t\t\tlastUsedAtMs: this.nowMs(),\n\t\t\t\tpriority: 10,\n\t\t\t\tresidencyControl: this.residencyControl,\n\t\t\t},\n\t\t];\n\t}\n\n\tasync ensureResident(_model: string): Promise<void> {\n\t\tconst result = await this.runtime.start();\n\t\tif (!result.started && result.reason !== \"already_running\") throw new Error(result.reason);\n\t}\n\n\tasync release(_model: string): Promise<void> {\n\t\tthis.runtime.stop();\n\t}\n}\n\nexport class AdvisoryRuntimeResidencyAdapter implements RuntimeResidencyAdapter {\n\treadonly id: string;\n\treadonly residencyControl = \"advisory\" as const;\n\tprivate readonly residents: RuntimeResidentModel[];\n\n\tconstructor(id: string, residents: Omit<RuntimeResidentModel, \"adapterId\" | \"residencyControl\">[]) {\n\t\tthis.id = id;\n\t\tthis.residents = residents.map((resident) => ({\n\t\t\t...resident,\n\t\t\tadapterId: id,\n\t\t\tresidencyControl: this.residencyControl,\n\t\t}));\n\t}\n\n\tasync list(): Promise<RuntimeResidentModel[]> {\n\t\treturn [...this.residents];\n\t}\n\n\tasync ensureResident(_model: string): Promise<void> {}\n\n\tasync release(_model: string): Promise<void> {}\n}\n\nexport function planRuntimeResidency(args: {\n\tbudgetBytes: number;\n\tresidents: readonly RuntimeResidentModel[];\n\trequest: RuntimeLoadRequest;\n}): RuntimeResidencyPlan {\n\tconst requestedModels = new Map<string, { model: string; adapterId?: string; bytes: number; priority: number }>();\n\tconst addRequestedModel = (entry: { model: string; adapterId?: string; bytes: number; priority: number }): void => {\n\t\tconst key = `${entry.adapterId ?? \"*\"}\\0${entry.model}`;\n\t\tconst existing = requestedModels.get(key);\n\t\tif (!existing || entry.bytes > existing.bytes) requestedModels.set(key, entry);\n\t};\n\taddRequestedModel(args.request);\n\tfor (const reservation of args.request.reservations ?? []) {\n\t\taddRequestedModel({\n\t\t\t...reservation,\n\t\t\tadapterId:\n\t\t\t\treservation.adapterId ?? (reservation.model === args.request.model ? args.request.adapterId : undefined),\n\t\t});\n\t}\n\n\tconst isResidentForRequest = (\n\t\tresident: RuntimeResidentModel,\n\t\trequested: { model: string; adapterId?: string },\n\t): boolean =>\n\t\tresident.model === requested.model &&\n\t\t(requested.adapterId === undefined || resident.adapterId === requested.adapterId);\n\tconst residentBytes = args.residents.reduce((sum, resident) => sum + resident.bytes, 0);\n\tconst missingBytes = [...requestedModels.values()].reduce((sum, requested) => {\n\t\treturn args.residents.some((resident) => isResidentForRequest(resident, requested)) ? sum : sum + requested.bytes;\n\t}, 0);\n\tconst requiredBytes = residentBytes + missingBytes;\n\tif (requiredBytes <= args.budgetBytes) {\n\t\treturn { status: \"fits\", evict: [], requiredBytes, budgetBytes: args.budgetBytes };\n\t}\n\n\tif (hasPingPongRisk(args.request)) {\n\t\treturn { status: \"refuse\", reason: \"anti-thrash\", evict: [], requiredBytes, budgetBytes: args.budgetBytes };\n\t}\n\n\tlet projectedBytes = requiredBytes;\n\tconst evict: RuntimeResidentModel[] = [];\n\tconst candidates = args.residents\n\t\t.filter(\n\t\t\t(resident) => ![...requestedModels.values()].some((requested) => isResidentForRequest(resident, requested)),\n\t\t)\n\t\t.filter(\n\t\t\t(resident) =>\n\t\t\t\t!resident.pinned &&\n\t\t\t\t!(\n\t\t\t\t\tresident.model === args.request.pinActiveModel &&\n\t\t\t\t\t(args.request.pinActiveAdapterId === undefined || resident.adapterId === args.request.pinActiveAdapterId)\n\t\t\t\t),\n\t\t)\n\t\t.filter((resident) => resident.residencyControl !== \"advisory\")\n\t\t.filter(\n\t\t\t(resident) =>\n\t\t\t\targs.request.minDwellMs === undefined ||\n\t\t\t\targs.request.nowMs - resident.lastUsedAtMs >= args.request.minDwellMs,\n\t\t)\n\t\t.sort((left, right) => left.priority - right.priority || left.lastUsedAtMs - right.lastUsedAtMs);\n\tfor (const candidate of candidates) {\n\t\tevict.push(candidate);\n\t\tprojectedBytes -= candidate.bytes;\n\t\tif (projectedBytes <= args.budgetBytes) {\n\t\t\treturn { status: \"fits\", evict, requiredBytes, budgetBytes: args.budgetBytes };\n\t\t}\n\t}\n\treturn {\n\t\tstatus: \"refuse\",\n\t\treason: \"insufficient-evictable-memory\",\n\t\tevict,\n\t\trequiredBytes,\n\t\tbudgetBytes: args.budgetBytes,\n\t};\n}\n\nfunction hasPingPongRisk(request: RuntimeLoadRequest): boolean {\n\treturn (request.recentEvictions ?? []).some(\n\t\t(record) =>\n\t\t\trecord.evicted === request.model &&\n\t\t\t(request.adapterId === undefined ||\n\t\t\t\trecord.evictedAdapterId === undefined ||\n\t\t\t\trecord.evictedAdapterId === request.adapterId) &&\n\t\t\trequest.nowMs - record.atMs <= (request.minDwellMs ?? 0),\n\t);\n}\n"]}
@@ -33,15 +33,26 @@ export interface ProfileFilterControllerDeps {
33
33
  /** Apply a thinking level (delegates to model-selection). */
34
34
  setThinkingLevel(level: ThinkingLevel): void;
35
35
  }
36
+ export interface ProfileFilterReloadSnapshot {
37
+ inertExtensionWarnings: string[];
38
+ profileDeniedExtensionCount: number;
39
+ profileControlledModel: boolean;
40
+ profileControlledThinking: boolean;
41
+ }
36
42
  export declare class ProfileFilterController {
37
43
  /** G12: extensions loaded but rendered fully inert by the profile's tools filter. */
38
44
  private _inertExtensionWarnings;
39
45
  /** Count of extensions withheld by the active resource profile (for the /context observation). */
40
46
  private _profileDeniedExtensionCount;
47
+ /** Whether the last applied profile generation, rather than the base session selection, owns the field. */
48
+ private _profileControlledModel;
49
+ private _profileControlledThinking;
41
50
  private readonly deps;
42
51
  constructor(deps: ProfileFilterControllerDeps);
43
52
  /** Inert-extension warnings tracked by the last {@link filterExtensionsForRuntime} pass. */
44
53
  getInertExtensionWarnings(): string[];
54
+ createReloadSnapshot(): ProfileFilterReloadSnapshot;
55
+ restoreReloadSnapshot(snapshot: ProfileFilterReloadSnapshot): void;
45
56
  /**
46
57
  * Resolve the active resource-profile tool allow/block filter from current settings.
47
58
  * Mirrors the construction-time derivation (settingsManager.getResourceProfileFilter("tools"))