@earendil-works/pi-coding-agent 0.82.1 → 0.84.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (311) hide show
  1. package/CHANGELOG.md +243 -0
  2. package/README.md +6 -1
  3. package/dist/cli/args.d.ts +2 -0
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +27 -1
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/cli/config-selector.d.ts.map +1 -1
  8. package/dist/cli/config-selector.js +2 -2
  9. package/dist/cli/config-selector.js.map +1 -1
  10. package/dist/cli/credential-print.d.ts +23 -0
  11. package/dist/cli/credential-print.d.ts.map +1 -0
  12. package/dist/cli/credential-print.js +117 -0
  13. package/dist/cli/credential-print.js.map +1 -0
  14. package/dist/cli/experimental/auth.d.ts +16 -0
  15. package/dist/cli/experimental/auth.d.ts.map +1 -0
  16. package/dist/cli/experimental/auth.js +13 -0
  17. package/dist/cli/experimental/auth.js.map +1 -0
  18. package/dist/cli/experimental/cli.d.ts +6 -0
  19. package/dist/cli/experimental/cli.d.ts.map +1 -0
  20. package/dist/cli/experimental/cli.js +5 -0
  21. package/dist/cli/experimental/cli.js.map +1 -0
  22. package/dist/cli/experimental/command-options.d.ts +17 -0
  23. package/dist/cli/experimental/command-options.d.ts.map +1 -0
  24. package/dist/cli/experimental/command-options.js +35 -0
  25. package/dist/cli/experimental/command-options.js.map +1 -0
  26. package/dist/cli/experimental/command.d.ts +63 -0
  27. package/dist/cli/experimental/command.d.ts.map +1 -0
  28. package/dist/cli/experimental/command.js +130 -0
  29. package/dist/cli/experimental/command.js.map +1 -0
  30. package/dist/cli/experimental/commands/client.d.ts +13 -0
  31. package/dist/cli/experimental/commands/client.d.ts.map +1 -0
  32. package/dist/cli/experimental/commands/client.js +25 -0
  33. package/dist/cli/experimental/commands/client.js.map +1 -0
  34. package/dist/cli/experimental/commands/pi.d.ts +15 -0
  35. package/dist/cli/experimental/commands/pi.d.ts.map +1 -0
  36. package/dist/cli/experimental/commands/pi.js +28 -0
  37. package/dist/cli/experimental/commands/pi.js.map +1 -0
  38. package/dist/cli/experimental/commands/server.d.ts +13 -0
  39. package/dist/cli/experimental/commands/server.d.ts.map +1 -0
  40. package/dist/cli/experimental/commands/server.js +25 -0
  41. package/dist/cli/experimental/commands/server.js.map +1 -0
  42. package/dist/cli/experimental/transport-address.d.ts +10 -0
  43. package/dist/cli/experimental/transport-address.d.ts.map +1 -0
  44. package/dist/cli/experimental/transport-address.js +38 -0
  45. package/dist/cli/experimental/transport-address.js.map +1 -0
  46. package/dist/cli/list-models.d.ts +1 -1
  47. package/dist/cli/list-models.d.ts.map +1 -1
  48. package/dist/cli/list-models.js +2 -2
  49. package/dist/cli/list-models.js.map +1 -1
  50. package/dist/cli/startup-ui.d.ts +1 -1
  51. package/dist/cli/startup-ui.d.ts.map +1 -1
  52. package/dist/cli/startup-ui.js +2 -2
  53. package/dist/cli/startup-ui.js.map +1 -1
  54. package/dist/cli.d.ts.map +1 -1
  55. package/dist/cli.js +1 -0
  56. package/dist/cli.js.map +1 -1
  57. package/dist/client/index.d.ts +3 -0
  58. package/dist/client/index.d.ts.map +1 -0
  59. package/dist/client/index.js +3 -0
  60. package/dist/client/index.js.map +1 -0
  61. package/dist/client/remote-session.d.ts +53 -0
  62. package/dist/client/remote-session.d.ts.map +1 -0
  63. package/dist/client/remote-session.js +340 -0
  64. package/dist/client/remote-session.js.map +1 -0
  65. package/dist/client/transcript.d.ts +12 -0
  66. package/dist/client/transcript.d.ts.map +1 -0
  67. package/dist/client/transcript.js +98 -0
  68. package/dist/client/transcript.js.map +1 -0
  69. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  70. package/dist/core/agent-session-runtime.js +3 -0
  71. package/dist/core/agent-session-runtime.js.map +1 -1
  72. package/dist/core/agent-session-services.d.ts +1 -0
  73. package/dist/core/agent-session-services.d.ts.map +1 -1
  74. package/dist/core/agent-session-services.js +1 -0
  75. package/dist/core/agent-session-services.js.map +1 -1
  76. package/dist/core/agent-session.d.ts +2 -11
  77. package/dist/core/agent-session.d.ts.map +1 -1
  78. package/dist/core/agent-session.js +85 -76
  79. package/dist/core/agent-session.js.map +1 -1
  80. package/dist/core/auth-storage.d.ts +15 -9
  81. package/dist/core/auth-storage.d.ts.map +1 -1
  82. package/dist/core/auth-storage.js +159 -38
  83. package/dist/core/auth-storage.js.map +1 -1
  84. package/dist/core/extensions/index.d.ts +1 -1
  85. package/dist/core/extensions/index.d.ts.map +1 -1
  86. package/dist/core/extensions/index.js.map +1 -1
  87. package/dist/core/extensions/loader.d.ts.map +1 -1
  88. package/dist/core/extensions/loader.js +43 -20
  89. package/dist/core/extensions/loader.js.map +1 -1
  90. package/dist/core/extensions/runner.d.ts +3 -1
  91. package/dist/core/extensions/runner.d.ts.map +1 -1
  92. package/dist/core/extensions/runner.js +10 -0
  93. package/dist/core/extensions/runner.js.map +1 -1
  94. package/dist/core/extensions/types.d.ts +28 -3
  95. package/dist/core/extensions/types.d.ts.map +1 -1
  96. package/dist/core/extensions/types.js.map +1 -1
  97. package/dist/core/footer-data-provider.d.ts +10 -0
  98. package/dist/core/footer-data-provider.d.ts.map +1 -1
  99. package/dist/core/footer-data-provider.js +1 -1
  100. package/dist/core/footer-data-provider.js.map +1 -1
  101. package/dist/core/http-dispatcher.d.ts.map +1 -1
  102. package/dist/core/http-dispatcher.js +5 -0
  103. package/dist/core/http-dispatcher.js.map +1 -1
  104. package/dist/core/keybindings.d.ts +36 -4
  105. package/dist/core/keybindings.d.ts.map +1 -1
  106. package/dist/core/model-config.d.ts +29 -5
  107. package/dist/core/model-config.d.ts.map +1 -1
  108. package/dist/core/model-config.js +4 -0
  109. package/dist/core/model-config.js.map +1 -1
  110. package/dist/core/model-registry.d.ts +5 -3
  111. package/dist/core/model-registry.d.ts.map +1 -1
  112. package/dist/core/model-registry.js +13 -10
  113. package/dist/core/model-registry.js.map +1 -1
  114. package/dist/core/model-resolver.d.ts +4 -3
  115. package/dist/core/model-resolver.d.ts.map +1 -1
  116. package/dist/core/model-resolver.js +41 -10
  117. package/dist/core/model-resolver.js.map +1 -1
  118. package/dist/core/model-runtime.d.ts +30 -13
  119. package/dist/core/model-runtime.d.ts.map +1 -1
  120. package/dist/core/model-runtime.js +231 -73
  121. package/dist/core/model-runtime.js.map +1 -1
  122. package/dist/core/models-store.d.ts +12 -7
  123. package/dist/core/models-store.d.ts.map +1 -1
  124. package/dist/core/models-store.js +83 -14
  125. package/dist/core/models-store.js.map +1 -1
  126. package/dist/core/package-manager.d.ts +4 -1
  127. package/dist/core/package-manager.d.ts.map +1 -1
  128. package/dist/core/package-manager.js +76 -42
  129. package/dist/core/package-manager.js.map +1 -1
  130. package/dist/core/pi-manifest.d.ts +8 -0
  131. package/dist/core/pi-manifest.d.ts.map +1 -0
  132. package/dist/core/pi-manifest.js +25 -0
  133. package/dist/core/pi-manifest.js.map +1 -0
  134. package/dist/core/provider-composer.d.ts +4 -1
  135. package/dist/core/provider-composer.d.ts.map +1 -1
  136. package/dist/core/provider-composer.js +36 -15
  137. package/dist/core/provider-composer.js.map +1 -1
  138. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  139. package/dist/core/remote-catalog-provider.js +73 -67
  140. package/dist/core/remote-catalog-provider.js.map +1 -1
  141. package/dist/core/resource-loader.d.ts +15 -0
  142. package/dist/core/resource-loader.d.ts.map +1 -1
  143. package/dist/core/resource-loader.js +64 -10
  144. package/dist/core/resource-loader.js.map +1 -1
  145. package/dist/core/runtime-credentials.d.ts +5 -5
  146. package/dist/core/runtime-credentials.d.ts.map +1 -1
  147. package/dist/core/runtime-credentials.js +11 -8
  148. package/dist/core/runtime-credentials.js.map +1 -1
  149. package/dist/core/session-manager.d.ts.map +1 -1
  150. package/dist/core/session-manager.js +3 -1
  151. package/dist/core/session-manager.js.map +1 -1
  152. package/dist/core/settings-manager.d.ts +12 -0
  153. package/dist/core/settings-manager.d.ts.map +1 -1
  154. package/dist/core/settings-manager.js +40 -16
  155. package/dist/core/settings-manager.js.map +1 -1
  156. package/dist/core/tools/find.d.ts +3 -0
  157. package/dist/core/tools/find.d.ts.map +1 -1
  158. package/dist/core/tools/find.js +11 -18
  159. package/dist/core/tools/find.js.map +1 -1
  160. package/dist/core/tools/read.d.ts.map +1 -1
  161. package/dist/core/tools/read.js +1 -1
  162. package/dist/core/tools/read.js.map +1 -1
  163. package/dist/extensions/llama/index.d.ts.map +1 -1
  164. package/dist/extensions/llama/index.js +11 -2
  165. package/dist/extensions/llama/index.js.map +1 -1
  166. package/dist/extensions/llama/provider.d.ts.map +1 -1
  167. package/dist/extensions/llama/provider.js +22 -8
  168. package/dist/extensions/llama/provider.js.map +1 -1
  169. package/dist/index.d.ts +4 -4
  170. package/dist/index.d.ts.map +1 -1
  171. package/dist/index.js +1 -1
  172. package/dist/index.js.map +1 -1
  173. package/dist/main.d.ts.map +1 -1
  174. package/dist/main.js +57 -5
  175. package/dist/main.js.map +1 -1
  176. package/dist/modes/index.d.ts +1 -0
  177. package/dist/modes/index.d.ts.map +1 -1
  178. package/dist/modes/index.js.map +1 -1
  179. package/dist/modes/interactive/components/assistant-message.d.ts +5 -2
  180. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  181. package/dist/modes/interactive/components/assistant-message.js +13 -4
  182. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  183. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  184. package/dist/modes/interactive/components/custom-editor.js +7 -0
  185. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  186. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  187. package/dist/modes/interactive/components/footer.js +1 -1
  188. package/dist/modes/interactive/components/footer.js.map +1 -1
  189. package/dist/modes/interactive/components/markdown-transform.d.ts +3 -0
  190. package/dist/modes/interactive/components/markdown-transform.d.ts.map +1 -0
  191. package/dist/modes/interactive/components/markdown-transform.js +19 -0
  192. package/dist/modes/interactive/components/markdown-transform.js.map +1 -0
  193. package/dist/modes/interactive/components/mermaid.d.ts +11 -0
  194. package/dist/modes/interactive/components/mermaid.d.ts.map +1 -0
  195. package/dist/modes/interactive/components/mermaid.js +75 -0
  196. package/dist/modes/interactive/components/mermaid.js.map +1 -0
  197. package/dist/modes/interactive/components/model-selector.d.ts +1 -1
  198. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  199. package/dist/modes/interactive/components/model-selector.js +20 -5
  200. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  201. package/dist/modes/interactive/components/scoped-models-selector.d.ts +4 -0
  202. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  203. package/dist/modes/interactive/components/scoped-models-selector.js +26 -0
  204. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  205. package/dist/modes/interactive/components/settings-selector.d.ts +8 -2
  206. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  207. package/dist/modes/interactive/components/settings-selector.js +30 -0
  208. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  209. package/dist/modes/interactive/components/user-message.d.ts +3 -1
  210. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  211. package/dist/modes/interactive/components/user-message.js +9 -2
  212. package/dist/modes/interactive/components/user-message.js.map +1 -1
  213. package/dist/modes/interactive/interactive-mode.d.ts +33 -2
  214. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  215. package/dist/modes/interactive/interactive-mode.js +443 -144
  216. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  217. package/dist/modes/interactive/theme/dark.json +1 -0
  218. package/dist/modes/interactive/theme/light.json +1 -0
  219. package/dist/modes/interactive/theme/theme-controller.d.ts +3 -0
  220. package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
  221. package/dist/modes/interactive/theme/theme-controller.js +10 -1
  222. package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
  223. package/dist/modes/interactive/theme/theme-schema.json +5 -1
  224. package/dist/modes/interactive/theme/theme.d.ts +2 -2
  225. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  226. package/dist/modes/interactive/theme/theme.js +13 -3
  227. package/dist/modes/interactive/theme/theme.js.map +1 -1
  228. package/dist/modes/json-event.d.ts +28 -0
  229. package/dist/modes/json-event.d.ts.map +1 -0
  230. package/dist/modes/json-event.js +12 -0
  231. package/dist/modes/json-event.js.map +1 -0
  232. package/dist/modes/print-mode.d.ts.map +1 -1
  233. package/dist/modes/print-mode.js +12 -2
  234. package/dist/modes/print-mode.js.map +1 -1
  235. package/dist/modes/rpc/rpc-client.d.ts +5 -4
  236. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  237. package/dist/modes/rpc/rpc-client.js.map +1 -1
  238. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  239. package/dist/modes/rpc/rpc-mode.js +17 -3
  240. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  241. package/dist/package-manager-cli.d.ts.map +1 -1
  242. package/dist/package-manager-cli.js +11 -9
  243. package/dist/package-manager-cli.js.map +1 -1
  244. package/dist/rpc-entry.d.ts.map +1 -1
  245. package/dist/rpc-entry.js +1 -0
  246. package/dist/rpc-entry.js.map +1 -1
  247. package/dist/utils/abort.d.ts +5 -0
  248. package/dist/utils/abort.d.ts.map +1 -0
  249. package/dist/utils/abort.js +48 -0
  250. package/dist/utils/abort.js.map +1 -0
  251. package/dist/utils/clipboard.d.ts +1 -1
  252. package/dist/utils/clipboard.d.ts.map +1 -1
  253. package/dist/utils/clipboard.js +22 -2
  254. package/dist/utils/clipboard.js.map +1 -1
  255. package/dist/utils/management-http.d.ts +23 -0
  256. package/dist/utils/management-http.d.ts.map +1 -0
  257. package/dist/utils/management-http.js +50 -0
  258. package/dist/utils/management-http.js.map +1 -0
  259. package/dist/utils/paths.d.ts +3 -0
  260. package/dist/utils/paths.d.ts.map +1 -1
  261. package/dist/utils/paths.js +23 -1
  262. package/dist/utils/paths.js.map +1 -1
  263. package/dist/utils/tool-result-images.d.ts +19 -0
  264. package/dist/utils/tool-result-images.d.ts.map +1 -0
  265. package/dist/utils/tool-result-images.js +45 -0
  266. package/dist/utils/tool-result-images.js.map +1 -0
  267. package/dist/utils/tools-manager.d.ts.map +1 -1
  268. package/dist/utils/tools-manager.js +4 -6
  269. package/dist/utils/tools-manager.js.map +1 -1
  270. package/dist/utils/version-check.d.ts +4 -0
  271. package/dist/utils/version-check.d.ts.map +1 -1
  272. package/dist/utils/version-check.js +20 -2
  273. package/dist/utils/version-check.js.map +1 -1
  274. package/docs/compaction.md +2 -2
  275. package/docs/custom-provider.md +16 -8
  276. package/docs/extensions.md +30 -4
  277. package/docs/json.md +19 -14
  278. package/docs/keybindings.md +38 -10
  279. package/docs/models.md +24 -3
  280. package/docs/providers.md +2 -0
  281. package/docs/quickstart.md +2 -0
  282. package/docs/rpc.md +13 -11
  283. package/docs/sdk.md +21 -2
  284. package/docs/security.md +1 -1
  285. package/docs/session-format.md +2 -0
  286. package/docs/settings.md +4 -1
  287. package/docs/themes.md +5 -3
  288. package/docs/usage.md +7 -0
  289. package/examples/extensions/custom-compaction.ts +1 -16
  290. package/examples/extensions/custom-provider-anthropic/index.ts +10 -3
  291. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  292. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  293. package/examples/extensions/custom-provider-gitlab-duo/index.ts +2 -1
  294. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  295. package/examples/extensions/doom-overlay/wad-finder.ts +7 -3
  296. package/examples/extensions/gondolin/package-lock.json +2 -2
  297. package/examples/extensions/gondolin/package.json +1 -1
  298. package/examples/extensions/handoff.ts +2 -11
  299. package/examples/extensions/qna.ts +3 -7
  300. package/examples/extensions/sandbox/package-lock.json +2 -2
  301. package/examples/extensions/sandbox/package.json +1 -1
  302. package/examples/extensions/summarize.ts +7 -18
  303. package/examples/extensions/with-deps/package-lock.json +2 -2
  304. package/examples/extensions/with-deps/package.json +1 -1
  305. package/examples/rpc-extension-ui.ts +11 -2
  306. package/examples/sdk/02-custom-model.ts +1 -2
  307. package/examples/sdk/09-api-keys-and-oauth.ts +1 -1
  308. package/examples/sdk/12-full-control.ts +3 -1
  309. package/examples/sdk/README.md +1 -1
  310. package/npm-shrinkwrap.json +70 -26
  311. package/package.json +14 -7
@@ -2,12 +2,26 @@ import { dirname, join } from "node:path";
2
2
  import { createModels, lazyStream, ModelsError, } from "@earendil-works/pi-ai";
3
3
  import * as builtinProviderCatalog from "@earendil-works/pi-ai/providers/all";
4
4
  import { getAgentDir } from "../config.js";
5
+ import { operationSignal, raceWithAbortSignal } from "../utils/abort.js";
5
6
  import { AuthStorage as DefaultAuthStorage } from "./auth-storage.js";
6
7
  import { ModelConfig } from "./model-config.js";
7
8
  import { FileModelsStore, InMemoryCodingAgentModelsStore } from "./models-store.js";
8
9
  import { composeModelProvider, configuredRequestAuthStatus, resolveCompatibilityRequestConfig, resolveConfiguredModelHeaders, validateExtensionProvider, } from "./provider-composer.js";
9
10
  import { withRemoteCatalog } from "./remote-catalog-provider.js";
10
11
  import { RuntimeCredentials } from "./runtime-credentials.js";
12
+ /** Credentials changed successfully, but the local model/auth snapshot could not be synchronized. */
13
+ export class CredentialSynchronizationError extends Error {
14
+ providerId;
15
+ operation;
16
+ credential;
17
+ constructor(providerId, operation, credential, options) {
18
+ super(`Credential ${operation} committed for ${providerId}, but local synchronization failed`, options);
19
+ this.name = "CredentialSynchronizationError";
20
+ this.providerId = providerId;
21
+ this.operation = operation;
22
+ this.credential = credential;
23
+ }
24
+ }
11
25
  function mergeHeaders(base, override) {
12
26
  if (!base && !override)
13
27
  return undefined;
@@ -41,8 +55,11 @@ export class ModelRuntime {
41
55
  storedProviders: new Set(),
42
56
  auth: new Map(),
43
57
  };
44
- availabilityRefresh;
58
+ availabilityRefreshSeq = 0;
59
+ availabilityErrorSeq = 0;
60
+ providerAvailabilitySeq = new Map();
45
61
  availabilityError;
62
+ credentialOperations = new Map();
46
63
  constructor(credentials, config, modelsPath, modelsStore, providers, modelNetworkEnabled) {
47
64
  this.credentials = credentials;
48
65
  this.config = config;
@@ -72,12 +89,15 @@ export class ModelRuntime {
72
89
  runtime.configureRadiusProviders();
73
90
  runtime.rebuildProviders();
74
91
  const refreshFromNetwork = runtime.modelNetworkEnabled && options.allowModelNetwork === true;
75
- const controller = refreshFromNetwork ? new AbortController() : undefined;
76
- const timeout = controller
77
- ? setTimeout(() => controller.abort(), options.modelRefreshTimeoutMs ?? 15_000)
78
- : undefined;
92
+ const controller = refreshFromNetwork && options.modelRefreshTimeoutMs !== undefined ? new AbortController() : undefined;
93
+ const timeout = controller ? setTimeout(() => controller.abort(), options.modelRefreshTimeoutMs) : undefined;
94
+ const signal = controller
95
+ ? options.signal
96
+ ? AbortSignal.any([options.signal, controller.signal])
97
+ : controller.signal
98
+ : options.signal;
79
99
  try {
80
- await runtime.refresh({ allowNetwork: refreshFromNetwork, signal: controller?.signal });
100
+ await runtime.refresh({ allowNetwork: refreshFromNetwork, signal });
81
101
  }
82
102
  finally {
83
103
  if (timeout)
@@ -149,16 +169,18 @@ export class ModelRuntime {
149
169
  available: all.filter((model) => this.snapshot.configuredProviders.has(model.provider)),
150
170
  };
151
171
  }
152
- async runAvailabilityRefresh() {
172
+ async runAvailabilityRefresh(seq, errorSeq, signal) {
153
173
  const providers = this.models.getProviders();
154
174
  const [available, checks, credentials] = await Promise.all([
155
- this.models.getAvailable(),
175
+ this.models.getAvailable(undefined, { signal }),
156
176
  Promise.all(providers.map(async (provider) => [
157
177
  provider.id,
158
- await this.models.checkAuth(provider.id),
178
+ await this.models.checkAuth(provider.id, { signal }),
159
179
  ])),
160
- this.credentials.list(),
180
+ this.credentials.list({ signal }),
161
181
  ]);
182
+ if (seq !== this.availabilityRefreshSeq)
183
+ return;
162
184
  const auth = new Map(checks);
163
185
  const configuredProviders = new Set(checks
164
186
  .filter((entry) => entry[1] !== undefined)
@@ -170,28 +192,76 @@ export class ModelRuntime {
170
192
  storedProviders: new Set(credentials.map((entry) => entry.providerId)),
171
193
  auth,
172
194
  };
173
- this.availabilityError = undefined;
195
+ if (errorSeq === this.availabilityErrorSeq)
196
+ this.availabilityError = undefined;
174
197
  }
175
- queueAvailabilityRefresh(after) {
176
- const refresh = (after ?? Promise.resolve()).catch(() => { }).then(() => this.runAvailabilityRefresh());
177
- const recorded = refresh.catch((error) => {
178
- this.availabilityError = error instanceof Error ? error.message : String(error);
198
+ queueAvailabilityRefresh(signal) {
199
+ const seq = ++this.availabilityRefreshSeq;
200
+ for (const [providerId, providerSeq] of this.providerAvailabilitySeq) {
201
+ this.providerAvailabilitySeq.set(providerId, providerSeq + 1);
202
+ }
203
+ const errorSeq = ++this.availabilityErrorSeq;
204
+ const effectiveSignal = operationSignal(signal);
205
+ return this.runAvailabilityRefresh(seq, errorSeq, effectiveSignal).catch((error) => {
206
+ if (errorSeq === this.availabilityErrorSeq && !effectiveSignal.aborted) {
207
+ this.availabilityError = error instanceof Error ? error.message : String(error);
208
+ }
179
209
  throw error;
180
210
  });
181
- const tracked = recorded.finally(() => {
182
- if (this.availabilityRefresh === tracked)
183
- this.availabilityRefresh = undefined;
184
- });
185
- this.availabilityRefresh = tracked;
186
- return tracked;
187
211
  }
188
- /** Coalesce concurrent readers onto the pending refresh. */
189
- refreshAvailability() {
190
- return this.availabilityRefresh ?? this.queueAvailabilityRefresh(undefined);
191
- }
192
- /** Mutations must not observe an in-flight refresh started before them. */
193
- forceRefreshAvailability() {
194
- return this.queueAvailabilityRefresh(this.availabilityRefresh);
212
+ async refreshProviderAvailability(providerId, signal) {
213
+ // Invalidate any full availability pass that started before this credential change.
214
+ ++this.availabilityRefreshSeq;
215
+ const providerSeq = (this.providerAvailabilitySeq.get(providerId) ?? 0) + 1;
216
+ this.providerAvailabilitySeq.set(providerId, providerSeq);
217
+ const errorSeq = ++this.availabilityErrorSeq;
218
+ try {
219
+ const [available, auth, credential] = await Promise.all([
220
+ this.models.getAvailable(providerId, { signal }),
221
+ this.models.checkAuth(providerId, { signal }),
222
+ this.credentials.read(providerId, { signal }),
223
+ ]);
224
+ signal.throwIfAborted();
225
+ if (this.providerAvailabilitySeq.get(providerId) !== providerSeq)
226
+ return;
227
+ const configuredProviders = new Set(this.snapshot.configuredProviders);
228
+ const storedProviders = new Set(this.snapshot.storedProviders);
229
+ const authByProvider = new Map(this.snapshot.auth);
230
+ if (auth) {
231
+ configuredProviders.add(providerId);
232
+ authByProvider.set(providerId, auth);
233
+ }
234
+ else {
235
+ configuredProviders.delete(providerId);
236
+ authByProvider.delete(providerId);
237
+ }
238
+ if (credential)
239
+ storedProviders.add(providerId);
240
+ else
241
+ storedProviders.delete(providerId);
242
+ const all = [...this.models.getModels()];
243
+ const availableById = new Map([...this.snapshot.available.filter((model) => model.provider !== providerId), ...available].map((model) => [
244
+ `${model.provider}\0${model.id}`,
245
+ model,
246
+ ]));
247
+ this.snapshot = {
248
+ all,
249
+ available: all.flatMap((model) => availableById.get(`${model.provider}\0${model.id}`) ?? []),
250
+ configuredProviders,
251
+ storedProviders,
252
+ auth: authByProvider,
253
+ };
254
+ if (errorSeq === this.availabilityErrorSeq)
255
+ this.availabilityError = undefined;
256
+ }
257
+ catch (error) {
258
+ if (this.providerAvailabilitySeq.get(providerId) === providerSeq &&
259
+ errorSeq === this.availabilityErrorSeq &&
260
+ !signal.aborted) {
261
+ this.availabilityError = error instanceof Error ? error.message : String(error);
262
+ }
263
+ throw error;
264
+ }
195
265
  }
196
266
  getProviders() {
197
267
  return this.models.getProviders();
@@ -205,24 +275,26 @@ export class ModelRuntime {
205
275
  getModel(providerId, modelId) {
206
276
  return this.models.getModel(providerId, modelId);
207
277
  }
208
- async checkAuth(providerId) {
209
- return this.models.checkAuth(providerId);
278
+ async checkAuth(providerId, options) {
279
+ return this.models.checkAuth(providerId, options);
210
280
  }
211
- async getAvailable(providerId) {
281
+ async getAvailable(providerId, options) {
212
282
  if (providerId) {
213
- if (this.availabilityRefresh) {
214
- await this.availabilityRefresh;
215
- return this.snapshot.available.filter((model) => model.provider === providerId);
216
- }
283
+ const errorSeq = ++this.availabilityErrorSeq;
217
284
  try {
218
- return await this.models.getAvailable(providerId);
285
+ const available = await this.models.getAvailable(providerId, options);
286
+ if (errorSeq === this.availabilityErrorSeq)
287
+ this.availabilityError = undefined;
288
+ return available;
219
289
  }
220
290
  catch (error) {
221
- this.availabilityError = error instanceof Error ? error.message : String(error);
291
+ if (errorSeq === this.availabilityErrorSeq && !options?.signal?.aborted) {
292
+ this.availabilityError = error instanceof Error ? error.message : String(error);
293
+ }
222
294
  throw error;
223
295
  }
224
296
  }
225
- await this.refreshAvailability();
297
+ await this.queueAvailabilityRefresh(options?.signal);
226
298
  return this.snapshot.available;
227
299
  }
228
300
  getAvailableSnapshot() {
@@ -256,6 +328,9 @@ export class ModelRuntime {
256
328
  isUsingOAuth(providerId) {
257
329
  return this.snapshot.auth.get(providerId)?.type === "oauth";
258
330
  }
331
+ isUsingSubscription(providerId) {
332
+ return this.isUsingOAuth(providerId) && this.models.getProvider(providerId)?.auth.oauth?.isSubscription === true;
333
+ }
259
334
  hasConfiguredAuth(providerId) {
260
335
  return this.snapshot.configuredProviders.has(providerId);
261
336
  }
@@ -274,26 +349,62 @@ export class ModelRuntime {
274
349
  },
275
350
  };
276
351
  }
277
- async setRuntimeApiKey(providerId, apiKey, refreshOptions = {}) {
278
- this.credentials.setRuntimeApiKey(providerId, apiKey);
279
- const auth = new Map(this.snapshot.auth).set(providerId, { type: "api_key", source: "runtime API key" });
280
- const configuredProviders = new Set(this.snapshot.configuredProviders).add(providerId);
281
- const storedProviders = new Set(this.snapshot.storedProviders).add(providerId);
282
- this.snapshot = {
283
- ...this.snapshot,
284
- auth,
285
- configuredProviders,
286
- storedProviders,
287
- available: this.snapshot.all.filter((model) => configuredProviders.has(model.provider)),
288
- };
289
- await this.refresh(refreshOptions);
352
+ enqueueCredentialOperation(providerId, signal, task) {
353
+ const previous = this.credentialOperations.get(providerId) ?? Promise.resolve();
354
+ let markStarted;
355
+ const started = new Promise((resolve) => {
356
+ markStarted = resolve;
357
+ });
358
+ const operation = (async () => {
359
+ await previous.catch(() => { });
360
+ signal.throwIfAborted();
361
+ markStarted?.();
362
+ return task();
363
+ })();
364
+ const tail = operation.catch(() => { });
365
+ this.credentialOperations.set(providerId, tail);
366
+ void tail.then(() => {
367
+ if (this.credentialOperations.get(providerId) === tail)
368
+ this.credentialOperations.delete(providerId);
369
+ });
370
+ return raceWithAbortSignal(started, signal).then(() => operation);
371
+ }
372
+ async synchronizeCredentialState(providerId, operation, credential, signal) {
373
+ try {
374
+ signal.throwIfAborted();
375
+ this.recomposeProvider(providerId);
376
+ const compositionError = this.compositionErrors.get(providerId);
377
+ if (compositionError)
378
+ throw new Error(compositionError);
379
+ const result = await this.models.refresh({ allowNetwork: false, providers: [providerId], signal });
380
+ if (result.aborted)
381
+ signal.throwIfAborted();
382
+ const refreshError = result.errors.get(providerId);
383
+ if (refreshError)
384
+ throw refreshError;
385
+ this.updateModelSnapshot();
386
+ await this.refreshProviderAvailability(providerId, signal);
387
+ }
388
+ catch (cause) {
389
+ throw new CredentialSynchronizationError(providerId, operation, credential, { cause });
390
+ }
391
+ }
392
+ setRuntimeApiKey(providerId, apiKey, options = {}) {
393
+ const signal = operationSignal(options.signal);
394
+ return this.enqueueCredentialOperation(providerId, signal, async () => {
395
+ this.credentials.setRuntimeApiKey(providerId, apiKey);
396
+ await this.synchronizeCredentialState(providerId, "setRuntimeApiKey", { type: "api_key", key: apiKey }, signal);
397
+ });
290
398
  }
291
- async removeRuntimeApiKey(providerId) {
292
- this.credentials.removeRuntimeApiKey(providerId);
293
- await this.refresh({ allowNetwork: this.modelNetworkEnabled });
399
+ removeRuntimeApiKey(providerId, options = {}) {
400
+ const signal = operationSignal(options.signal);
401
+ return this.enqueueCredentialOperation(providerId, signal, async () => {
402
+ this.credentials.removeRuntimeApiKey(providerId);
403
+ await this.synchronizeCredentialState(providerId, "removeRuntimeApiKey", undefined, signal);
404
+ });
294
405
  }
295
- listCredentials() {
296
- return this.credentials.list();
406
+ listCredentials(options) {
407
+ return this.credentials.list(options);
297
408
  }
298
409
  getProviderAuthStatus(providerId) {
299
410
  if (this.credentials.hasRuntimeApiKey(providerId))
@@ -310,10 +421,15 @@ export class ModelRuntime {
310
421
  const provider = this.models.getProvider(model.provider);
311
422
  if (!provider)
312
423
  throw new ModelsError("provider", `Unknown provider: ${model.provider}`);
313
- const resolution = await this.getAuth(model, { apiKey: options?.apiKey, env: options?.env });
424
+ const resolution = await this.getAuth(model, {
425
+ apiKey: options?.apiKey,
426
+ env: options?.env,
427
+ signal: options?.signal,
428
+ });
314
429
  if (!resolution)
315
430
  throw new ModelsError("auth", `Provider is not configured: ${model.provider}`);
316
- const { transformHeaders, ...providerOptions } = options ?? {};
431
+ const { transformHeaders, ...rawProviderOptions } = options ?? {};
432
+ const providerOptions = rawProviderOptions;
317
433
  let headers = mergeHeaders(resolution.auth.headers, providerOptions.headers);
318
434
  if (transformHeaders)
319
435
  headers = await transformHeaders(headers ?? {});
@@ -349,21 +465,48 @@ export class ModelRuntime {
349
465
  completeSimple(model, context, options) {
350
466
  return this.streamSimple(model, context, options).result();
351
467
  }
352
- async login(providerId, type, interaction) {
353
- const credential = await this.models.login(providerId, type, interaction);
354
- await this.refresh({ allowNetwork: this.modelNetworkEnabled });
355
- return credential;
468
+ async fetchDeferred(model, handle, options) {
469
+ return lazyStream(model, async () => {
470
+ const prepared = await this.prepareRequest(model, options);
471
+ if (!prepared.provider.fetchDeferred) {
472
+ throw new ModelsError("provider", `Provider ${model.provider} does not support deferred responses`);
473
+ }
474
+ return prepared.provider.fetchDeferred(prepared.model, handle, prepared.options);
475
+ }).result();
476
+ }
477
+ async cancelDeferred(model, handle, options) {
478
+ const prepared = await this.prepareRequest(model, options);
479
+ if (!prepared.provider.cancelDeferred) {
480
+ throw new ModelsError("provider", `Provider ${model.provider} does not support deferred responses`);
481
+ }
482
+ await prepared.provider.cancelDeferred(prepared.model, handle, prepared.options);
483
+ }
484
+ login(providerId, type, interaction) {
485
+ const signal = operationSignal(interaction.signal);
486
+ return this.enqueueCredentialOperation(providerId, signal, async () => {
487
+ const credential = await this.models.login(providerId, type, { ...interaction, signal });
488
+ await this.synchronizeCredentialState(providerId, "login", credential, signal);
489
+ return credential;
490
+ });
356
491
  }
357
- async logout(providerId) {
358
- await this.models.logout(providerId);
359
- // Reset credential-dependent compatibility projections before the unconfigured provider is skipped by refresh.
360
- this.recomposeProvider(providerId);
361
- await this.refresh({ allowNetwork: this.modelNetworkEnabled });
492
+ logout(providerId, options = {}) {
493
+ const signal = operationSignal(options.signal);
494
+ return this.enqueueCredentialOperation(providerId, signal, async () => {
495
+ await this.models.logout(providerId, { signal });
496
+ await this.synchronizeCredentialState(providerId, "logout", undefined, signal);
497
+ });
362
498
  }
363
499
  async refresh(options = {}) {
364
500
  this.config = await ModelConfig.load(this.modelsPath);
365
501
  this.configureRadiusProviders();
366
- this.rebuildProviders();
502
+ if (options.providers) {
503
+ for (const providerId of new Set(options.providers))
504
+ this.recomposeProvider(providerId);
505
+ this.updateModelSnapshot();
506
+ }
507
+ else {
508
+ this.rebuildProviders();
509
+ }
367
510
  const refreshOptions = {
368
511
  ...options,
369
512
  allowNetwork: options.allowNetwork ?? this.modelNetworkEnabled,
@@ -374,14 +517,29 @@ export class ModelRuntime {
374
517
  aborted: refreshOptions.signal?.aborted ?? false,
375
518
  errors: new Map(),
376
519
  };
520
+ const errors = new Map(result.errors);
377
521
  this.updateModelSnapshot();
378
- try {
379
- await this.forceRefreshAvailability();
522
+ if (options.providers) {
523
+ await Promise.all([...new Set(options.providers)].map(async (providerId) => {
524
+ try {
525
+ await this.refreshProviderAvailability(providerId, operationSignal(options.signal));
526
+ }
527
+ catch (error) {
528
+ if (!options.signal?.aborted) {
529
+ errors.set(providerId, error instanceof Error ? error : new Error(String(error)));
530
+ }
531
+ }
532
+ }));
380
533
  }
381
- catch {
382
- // Availability errors are recorded by forceRefreshAvailability; refreshed models remain usable.
534
+ else {
535
+ try {
536
+ await this.queueAvailabilityRefresh(options.signal);
537
+ }
538
+ catch {
539
+ // Availability errors are recorded by the latest pass; refreshed models remain usable.
540
+ }
383
541
  }
384
- return result;
542
+ return { aborted: result.aborted || (options.signal?.aborted ?? false), errors };
385
543
  }
386
544
  registerNativeProvider(provider) {
387
545
  if (!provider.id.trim())