@averyyy/pi-coding-agent 1.0.0-piclient.1 → 1.0.1-piclient.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +32 -7
  3. package/dist/bundle/chunks/{anthropic-messages-UID2PALB.js → anthropic-messages-OH6PVFDC.js} +3 -3
  4. package/dist/bundle/chunks/{azure-openai-responses-WK37QQ3X.js → azure-openai-responses-TXH53UDQ.js} +1 -1
  5. package/dist/bundle/chunks/bedrock-converse-stream.js +1 -1
  6. package/dist/bundle/chunks/{chunk-Z772OFUN.js → chunk-4ILNOXGR.js} +1 -1
  7. package/dist/bundle/chunks/{chunk-LDPPAHPJ.js → chunk-6RH3AHQA.js} +2 -2
  8. package/dist/bundle/chunks/{chunk-GIUN2MS4.js → chunk-ADXZE3T6.js} +1 -1
  9. package/dist/bundle/chunks/{chunk-Y5O5BZGX.js → chunk-AMZHOOYQ.js} +5 -5
  10. package/dist/bundle/chunks/{chunk-2LGHNI5Y.js → chunk-B5YEEMN6.js} +43 -44
  11. package/dist/bundle/chunks/chunk-BWQQ7T6C.js +2 -0
  12. package/dist/bundle/chunks/{chunk-7JGR3GZN.js → chunk-F42GKQ7R.js} +1 -1
  13. package/dist/bundle/chunks/chunk-F5ZABL6Z.js +20 -0
  14. package/dist/bundle/chunks/{chunk-CWVWZD3J.js → chunk-JTLRGL3E.js} +1 -1
  15. package/dist/bundle/chunks/{chunk-NEUJZQL3.js → chunk-KDCQ3NIW.js} +4 -4
  16. package/dist/bundle/chunks/chunk-NDRYPTF3.js +82 -0
  17. package/dist/bundle/chunks/{chunk-OSKPX3Z5.js → chunk-PXGUCLIP.js} +1 -1
  18. package/dist/bundle/chunks/{chunk-KYK7L7MP.js → chunk-TQURZFKG.js} +2 -2
  19. package/dist/bundle/chunks/{chunk-6D7LHLKG.js → chunk-WGS2XTDX.js} +1 -1
  20. package/dist/bundle/chunks/chunk-WZXBGVMV.js +2 -0
  21. package/dist/bundle/chunks/{cli-JE36AHMI.js → cli-PWPIOM3F.js} +2 -2
  22. package/dist/bundle/chunks/{cloudflare-workers-ai-system-one-NPQN263E.js → cloudflare-workers-ai-system-one-GOPEBKZD.js} +1 -1
  23. package/dist/bundle/chunks/codemode-worker.js +24 -3
  24. package/dist/bundle/chunks/easter-egg-3d-SZ6NEDZW.js +2 -0
  25. package/dist/bundle/chunks/{execute-UX73LR4F.js → execute-7OKZOXCP.js} +25 -4
  26. package/dist/bundle/chunks/{google-generative-ai-OHRSHIE7.js → google-generative-ai-XQU2LTWM.js} +1 -1
  27. package/dist/bundle/chunks/{google-vertex-KXEB3MVO.js → google-vertex-F6Q2CN55.js} +1 -1
  28. package/dist/bundle/chunks/{mistral-conversations-DKVFNQL6.js → mistral-conversations-XAJWHQFR.js} +1 -1
  29. package/dist/bundle/chunks/node-N5BSDOA5.js +16 -0
  30. package/dist/bundle/chunks/openai-chatgpt.js +1 -1
  31. package/dist/bundle/chunks/{openai-codex-responses-WVERB76N.js → openai-codex-responses-3EHOMZYM.js} +1 -1
  32. package/dist/bundle/chunks/{openai-completions-JXDDPZ23.js → openai-completions-QC2IQHSC.js} +1 -1
  33. package/dist/bundle/chunks/{openai-responses-WDT74L23.js → openai-responses-JPLD2GJS.js} +1 -1
  34. package/dist/bundle/chunks/{runtime-MPXPM7VL.js → runtime-XC47QHT2.js} +1 -1
  35. package/dist/bundle/chunks/{typesafe-system-one-A4CZTQWZ.js → typesafe-system-one-7DJTDUKY.js} +1 -1
  36. package/dist/bundle/chunks/virtual-modules-TA3HGAAA.js +2 -0
  37. package/dist/bundle/cli-runtime.js +1 -1
  38. package/dist/bundle/index.js +1 -1
  39. package/dist/bundle/rpc-entry.js +1 -1
  40. package/dist/cli/args.d.ts.map +1 -1
  41. package/dist/cli/args.js +4 -1
  42. package/dist/cli/args.js.map +1 -1
  43. package/dist/config.d.ts.map +1 -1
  44. package/dist/config.js +3 -0
  45. package/dist/config.js.map +1 -1
  46. package/dist/core/agent-session.d.ts.map +1 -1
  47. package/dist/core/agent-session.js +30 -3
  48. package/dist/core/agent-session.js.map +1 -1
  49. package/dist/core/cache-warmer.d.ts.map +1 -1
  50. package/dist/core/cache-warmer.js +11 -3
  51. package/dist/core/cache-warmer.js.map +1 -1
  52. package/dist/core/export-html/tool-renderer.d.ts +3 -3
  53. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  54. package/dist/core/export-html/tool-renderer.js +3 -3
  55. package/dist/core/export-html/tool-renderer.js.map +1 -1
  56. package/dist/core/extensions/index.d.ts +1 -1
  57. package/dist/core/extensions/index.d.ts.map +1 -1
  58. package/dist/core/extensions/index.js.map +1 -1
  59. package/dist/core/extensions/loader.d.ts.map +1 -1
  60. package/dist/core/extensions/loader.js +5 -0
  61. package/dist/core/extensions/loader.js.map +1 -1
  62. package/dist/core/extensions/runner.d.ts +3 -1
  63. package/dist/core/extensions/runner.d.ts.map +1 -1
  64. package/dist/core/extensions/runner.js +6 -0
  65. package/dist/core/extensions/runner.js.map +1 -1
  66. package/dist/core/extensions/types.d.ts +9 -0
  67. package/dist/core/extensions/types.d.ts.map +1 -1
  68. package/dist/core/extensions/types.js.map +1 -1
  69. package/dist/core/mcp-servers.d.ts +6 -0
  70. package/dist/core/mcp-servers.d.ts.map +1 -1
  71. package/dist/core/mcp-servers.js +11 -0
  72. package/dist/core/mcp-servers.js.map +1 -1
  73. package/dist/core/model-resolver.js +2 -2
  74. package/dist/core/model-resolver.js.map +1 -1
  75. package/dist/core/model-runtime.d.ts +16 -0
  76. package/dist/core/model-runtime.d.ts.map +1 -1
  77. package/dist/core/model-runtime.js +325 -31
  78. package/dist/core/model-runtime.js.map +1 -1
  79. package/dist/core/pi-server-client.d.ts +21 -1
  80. package/dist/core/pi-server-client.d.ts.map +1 -1
  81. package/dist/core/pi-server-client.js +308 -22
  82. package/dist/core/pi-server-client.js.map +1 -1
  83. package/dist/core/pi-server-protocol.d.ts +12 -1
  84. package/dist/core/pi-server-protocol.d.ts.map +1 -1
  85. package/dist/core/pi-server-protocol.js +1 -0
  86. package/dist/core/pi-server-protocol.js.map +1 -1
  87. package/dist/core/sdk.d.ts.map +1 -1
  88. package/dist/core/sdk.js +51 -18
  89. package/dist/core/sdk.js.map +1 -1
  90. package/dist/core/tools/renderers/index.d.ts +2 -2
  91. package/dist/core/tools/renderers/index.d.ts.map +1 -1
  92. package/dist/core/tools/renderers/index.js.map +1 -1
  93. package/dist/extensions/mcp/cli.d.ts.map +1 -1
  94. package/dist/extensions/mcp/cli.js +3 -0
  95. package/dist/extensions/mcp/cli.js.map +1 -1
  96. package/dist/extensions/mcp/config.d.ts +15 -3
  97. package/dist/extensions/mcp/config.d.ts.map +1 -1
  98. package/dist/extensions/mcp/config.js +46 -10
  99. package/dist/extensions/mcp/config.js.map +1 -1
  100. package/dist/extensions/mcp/index.d.ts +4 -1
  101. package/dist/extensions/mcp/index.d.ts.map +1 -1
  102. package/dist/extensions/mcp/index.js +80 -45
  103. package/dist/extensions/mcp/index.js.map +1 -1
  104. package/dist/extensions/mcp/oauth.d.ts +4 -1
  105. package/dist/extensions/mcp/oauth.d.ts.map +1 -1
  106. package/dist/extensions/mcp/oauth.js +54 -10
  107. package/dist/extensions/mcp/oauth.js.map +1 -1
  108. package/dist/extensions/mcp/runtime.d.ts.map +1 -1
  109. package/dist/extensions/mcp/runtime.js +1 -0
  110. package/dist/extensions/mcp/runtime.js.map +1 -1
  111. package/dist/extensions/mcp/tools.d.ts +3 -1
  112. package/dist/extensions/mcp/tools.d.ts.map +1 -1
  113. package/dist/extensions/mcp/tools.js +19 -13
  114. package/dist/extensions/mcp/tools.js.map +1 -1
  115. package/dist/extensions/mcp/ui.d.ts.map +1 -1
  116. package/dist/extensions/mcp/ui.js +8 -4
  117. package/dist/extensions/mcp/ui.js.map +1 -1
  118. package/dist/index.d.ts +1 -1
  119. package/dist/index.d.ts.map +1 -1
  120. package/dist/index.js.map +1 -1
  121. package/dist/main.d.ts.map +1 -1
  122. package/dist/main.js +24 -17
  123. package/dist/main.js.map +1 -1
  124. package/dist/modes/interactive/components/armin.d.ts +3 -0
  125. package/dist/modes/interactive/components/armin.d.ts.map +1 -1
  126. package/dist/modes/interactive/components/armin.js +7 -5
  127. package/dist/modes/interactive/components/armin.js.map +1 -1
  128. package/dist/modes/interactive/components/auth-url.d.ts +14 -0
  129. package/dist/modes/interactive/components/auth-url.d.ts.map +1 -0
  130. package/dist/modes/interactive/components/auth-url.js +37 -0
  131. package/dist/modes/interactive/components/auth-url.js.map +1 -0
  132. package/dist/modes/interactive/components/easter-egg-3d.d.ts +104 -0
  133. package/dist/modes/interactive/components/easter-egg-3d.d.ts.map +1 -0
  134. package/dist/modes/interactive/components/{pi-logo-animation.js → easter-egg-3d.js} +127 -74
  135. package/dist/modes/interactive/components/easter-egg-3d.js.map +1 -0
  136. package/dist/modes/interactive/components/easter-egg-3d.lazy.d.ts +6 -0
  137. package/dist/modes/interactive/components/easter-egg-3d.lazy.d.ts.map +1 -0
  138. package/dist/modes/interactive/components/easter-egg-3d.lazy.js +23 -0
  139. package/dist/modes/interactive/components/easter-egg-3d.lazy.js.map +1 -0
  140. package/dist/modes/interactive/components/index.d.ts +0 -1
  141. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  142. package/dist/modes/interactive/components/index.js +0 -1
  143. package/dist/modes/interactive/components/index.js.map +1 -1
  144. package/dist/modes/interactive/components/login-dialog.d.ts +2 -0
  145. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  146. package/dist/modes/interactive/components/login-dialog.js +11 -5
  147. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  148. package/dist/modes/interactive/components/tool-execution.d.ts +6 -18
  149. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  150. package/dist/modes/interactive/components/tool-execution.js +22 -45
  151. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  152. package/dist/modes/interactive/interactive-mode.d.ts +2 -2
  153. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  154. package/dist/modes/interactive/interactive-mode.js +15 -17
  155. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  156. package/dist/package-manager-cli.d.ts.map +1 -1
  157. package/dist/package-manager-cli.js +12 -0
  158. package/dist/package-manager-cli.js.map +1 -1
  159. package/dist/utils/image-convert.d.ts +12 -0
  160. package/dist/utils/image-convert.d.ts.map +1 -1
  161. package/dist/utils/image-convert.js +45 -6
  162. package/dist/utils/image-convert.js.map +1 -1
  163. package/docs/cli.md +2 -0
  164. package/docs/codemode.md +1 -1
  165. package/docs/extensions.md +4 -0
  166. package/docs/keybindings.md +1 -1
  167. package/docs/mcp.md +23 -1
  168. package/docs/models.md +2 -2
  169. package/docs/quickstart.md +16 -2
  170. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  171. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  172. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  173. package/examples/extensions/gondolin/package-lock.json +2 -2
  174. package/examples/extensions/gondolin/package.json +1 -1
  175. package/examples/extensions/sandbox/package-lock.json +2 -2
  176. package/examples/extensions/sandbox/package.json +1 -1
  177. package/examples/extensions/with-deps/package-lock.json +2 -2
  178. package/examples/extensions/with-deps/package.json +1 -1
  179. package/package.json +13 -14
  180. package/dist/bundle/chunks/chunk-DTD7JQ7Y.js +0 -42
  181. package/dist/bundle/chunks/chunk-OI6YFHHK.js +0 -82
  182. package/dist/bundle/chunks/chunk-SQXKOUF6.js +0 -2
  183. package/dist/bundle/chunks/node-O3RIBJRH.js +0 -16
  184. package/dist/bundle/chunks/pi-logo-animation-APS4JS6B.js +0 -2
  185. package/dist/bundle/chunks/virtual-modules-XLXFDFIX.js +0 -2
  186. package/dist/modes/interactive/components/daxnuts.d.ts +0 -23
  187. package/dist/modes/interactive/components/daxnuts.d.ts.map +0 -1
  188. package/dist/modes/interactive/components/daxnuts.js +0 -140
  189. package/dist/modes/interactive/components/daxnuts.js.map +0 -1
  190. package/dist/modes/interactive/components/pi-logo-animation.d.ts +0 -68
  191. package/dist/modes/interactive/components/pi-logo-animation.d.ts.map +0 -1
  192. package/dist/modes/interactive/components/pi-logo-animation.js.map +0 -1
  193. package/dist/modes/interactive/components/pi-logo-animation.lazy.d.ts +0 -7
  194. package/dist/modes/interactive/components/pi-logo-animation.lazy.d.ts.map +0 -1
  195. package/dist/modes/interactive/components/pi-logo-animation.lazy.js +0 -12
  196. package/dist/modes/interactive/components/pi-logo-animation.lazy.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { dirname, join } from "node:path";
2
- import { clampThinkingLevel, createModels, lazyStream, ModelsError, normalizeContext, } from "@earendil-works/pi-ai";
2
+ import { clampThinkingLevel, createModels, isModelType, lazyStream, ModelsError, normalizeContext, } from "@earendil-works/pi-ai";
3
3
  import * as builtinProviderCatalog from "@earendil-works/pi-ai/providers/all";
4
4
  import { assertChatModel, assertClassifierModel, assertImageModel, classifierErrorResult, imageErrorResult, } from "@earendil-works/pi-ai/utils/model-operations";
5
5
  import { getAgentDir } from "../config.js";
@@ -7,6 +7,7 @@ import { operationSignal, raceWithAbortSignal } from "../utils/abort.js";
7
7
  import { AuthStorage as DefaultAuthStorage } from "./auth-storage.js";
8
8
  import { ModelConfig } from "./model-config.js";
9
9
  import { FileModelsStore, InMemoryCodingAgentModelsStore } from "./models-store.js";
10
+ import { cancelDeferredPiServer, classifyPiServer, fetchDeferredPiServer, fetchPiServerModels, generateImagesPiServer, streamPiServer, streamRawPiServer, } from "./pi-server-client.js";
10
11
  import { composeModelProvider, configuredRequestAuthStatus, resolveCompatibilityRequestConfig, resolveConfiguredModelHeaders, validateExtensionProvider, } from "./provider-composer.js";
11
12
  import { withRemoteCatalog } from "./remote-catalog-provider.js";
12
13
  import { RuntimeCredentials } from "./runtime-credentials.js";
@@ -43,6 +44,7 @@ export class ModelRuntime {
43
44
  models;
44
45
  credentials;
45
46
  defaultBuiltins;
47
+ nativeCatalogProviders;
46
48
  builtins = new Map();
47
49
  nativeExtensionProviders = new Map();
48
50
  extensionProviders = new Map();
@@ -51,6 +53,19 @@ export class ModelRuntime {
51
53
  compositionErrors = new Map();
52
54
  modelsPath;
53
55
  modelNetworkEnabled;
56
+ piServerMode = process.env.PI_SERVER_MODE === "true";
57
+ remoteCatalog = {
58
+ models: [],
59
+ available: [],
60
+ };
61
+ remoteProviderCapabilities = [];
62
+ remoteCatalogError;
63
+ registrationRefresh = Promise.resolve();
64
+ registrationRefreshErrors = new Map();
65
+ remoteCatalogRefreshSeq = 0;
66
+ remoteCatalogLoaded = false;
67
+ localOperationAvailable = [];
68
+ remoteProjection;
54
69
  config;
55
70
  snapshot = {
56
71
  all: [],
@@ -64,11 +79,12 @@ export class ModelRuntime {
64
79
  providerAvailabilitySeq = new Map();
65
80
  availabilityError;
66
81
  credentialOperations = new Map();
67
- constructor(credentials, config, modelsPath, modelsStore, providers, modelNetworkEnabled) {
82
+ constructor(credentials, config, modelsPath, modelsStore, providers, modelNetworkEnabled, nativeCatalogProviders) {
68
83
  this.credentials = credentials;
69
84
  this.config = config;
70
85
  this.modelsPath = modelsPath;
71
86
  this.modelNetworkEnabled = modelNetworkEnabled;
87
+ this.nativeCatalogProviders = nativeCatalogProviders;
72
88
  this.defaultBuiltins = new Map(providers.map((provider) => [provider.id, provider]));
73
89
  for (const [providerId, provider] of this.defaultBuiltins)
74
90
  this.builtins.set(providerId, provider);
@@ -84,12 +100,11 @@ export class ModelRuntime {
84
100
  ? new FileModelsStore(options.modelsStorePath ?? join(dirname(modelsPath), "models-store.json"))
85
101
  : new InMemoryCodingAgentModelsStore());
86
102
  const builtinModelDataGeneratedAt = builtinProviderCatalog.getBuiltinModelDataGeneratedAt();
87
- const providers = builtinProviderCatalog
88
- .builtinProviders()
89
- .map((provider) => provider.refreshModels !== undefined
103
+ const builtinProviders = builtinProviderCatalog.builtinProviders();
104
+ const providers = builtinProviders.map((provider) => provider.refreshModels !== undefined
90
105
  ? provider
91
106
  : withRemoteCatalog(provider, options.catalogBaseUrl, builtinModelDataGeneratedAt));
92
- const runtime = new ModelRuntime(credentials, config, modelsPath, modelsStore, providers, process.env.PI_OFFLINE === undefined);
107
+ const runtime = new ModelRuntime(credentials, config, modelsPath, modelsStore, providers, process.env.PI_OFFLINE === undefined, new Set(builtinProviders.filter((provider) => provider.refreshModels !== undefined).map((provider) => provider.id)));
93
108
  runtime.configureRadiusProviders();
94
109
  runtime.rebuildProviders();
95
110
  const refreshFromNetwork = runtime.modelNetworkEnabled && options.allowModelNetwork === true;
@@ -102,7 +117,12 @@ export class ModelRuntime {
102
117
  : options.signal;
103
118
  try {
104
119
  if (options.refreshOnCreate !== false) {
105
- await runtime.refresh({ allowNetwork: refreshFromNetwork, signal });
120
+ const result = await runtime.refresh({ allowNetwork: refreshFromNetwork, signal });
121
+ const remoteError = result.errors.get("pi-server");
122
+ if (remoteError)
123
+ throw remoteError;
124
+ if (runtime.piServerMode && result.aborted)
125
+ signal?.throwIfAborted();
106
126
  }
107
127
  }
108
128
  finally {
@@ -174,6 +194,7 @@ export class ModelRuntime {
174
194
  this.updateModelSnapshot();
175
195
  }
176
196
  updateModelSnapshot() {
197
+ this.remoteProjection = undefined;
177
198
  const all = [...this.models.getModels()];
178
199
  this.snapshot = {
179
200
  ...this.snapshot,
@@ -183,8 +204,8 @@ export class ModelRuntime {
183
204
  }
184
205
  async runAvailabilityRefresh(seq, errorSeq, signal) {
185
206
  const providers = this.models.getProviders();
186
- const [available, checks, credentials] = await Promise.all([
187
- this.models.getAvailable(undefined, { signal }),
207
+ const [allAvailable, checks, credentials] = await Promise.all([
208
+ this.models.getAllAvailable(undefined, { signal }),
188
209
  Promise.all(providers.map(async (provider) => [
189
210
  provider.id,
190
211
  await this.models.checkAuth(provider.id, { signal }),
@@ -193,13 +214,15 @@ export class ModelRuntime {
193
214
  ]);
194
215
  if (seq !== this.availabilityRefreshSeq)
195
216
  return;
217
+ this.localOperationAvailable = allAvailable;
218
+ this.remoteProjection = undefined;
196
219
  const auth = new Map(checks);
197
220
  const configuredProviders = new Set(checks
198
221
  .filter((entry) => entry[1] !== undefined)
199
222
  .map(([providerId]) => providerId));
200
223
  this.snapshot = {
201
224
  all: [...this.models.getModels()],
202
- available: [...available],
225
+ available: allAvailable.filter((model) => isModelType(model, "chat")),
203
226
  configuredProviders,
204
227
  storedProviders: new Set(credentials.map((entry) => entry.providerId)),
205
228
  auth,
@@ -228,14 +251,20 @@ export class ModelRuntime {
228
251
  this.providerAvailabilitySeq.set(providerId, providerSeq);
229
252
  const errorSeq = ++this.availabilityErrorSeq;
230
253
  try {
231
- const [available, auth, credential] = await Promise.all([
232
- this.models.getAvailable(providerId, { signal }),
254
+ const [allAvailable, auth, credential] = await Promise.all([
255
+ this.models.getAllAvailable(providerId, { signal }),
233
256
  this.models.checkAuth(providerId, { signal }),
234
257
  this.credentials.read(providerId, { signal }),
235
258
  ]);
236
259
  signal.throwIfAborted();
237
260
  if (this.providerAvailabilitySeq.get(providerId) !== providerSeq)
238
261
  return;
262
+ this.localOperationAvailable = [
263
+ ...this.localOperationAvailable.filter((model) => model.provider !== providerId),
264
+ ...allAvailable,
265
+ ];
266
+ this.remoteProjection = undefined;
267
+ const available = allAvailable.filter((model) => isModelType(model, "chat"));
239
268
  const configuredProviders = new Set(this.snapshot.configuredProviders);
240
269
  const storedProviders = new Set(this.snapshot.storedProviders);
241
270
  const authByProvider = new Map(this.snapshot.auth);
@@ -276,36 +305,167 @@ export class ModelRuntime {
276
305
  }
277
306
  }
278
307
  getProviders() {
308
+ if (this.piServerMode) {
309
+ const ids = new Set([
310
+ ...this.models.getProviders().map((provider) => provider.id),
311
+ ...this.getAllModels().map((model) => model.provider),
312
+ ]);
313
+ return [...ids].flatMap((id) => this.getProvider(id) ?? []);
314
+ }
279
315
  return this.models.getProviders();
280
316
  }
281
317
  getProvider(providerId) {
282
- return this.models.getProvider(providerId);
318
+ const local = this.models.getProvider(providerId);
319
+ if (!this.piServerMode)
320
+ return local;
321
+ const catalog = this.getAllModels(providerId);
322
+ if (!local && catalog.length === 0)
323
+ return undefined;
324
+ const capabilities = this.remoteProviderCapabilities.find((provider) => provider.id === providerId);
325
+ return {
326
+ ...local,
327
+ id: providerId,
328
+ name: local?.name ?? providerId,
329
+ auth: local?.auth ?? {},
330
+ getModels: () => this.getModels(providerId),
331
+ getAllModels: () => this.getAllModels(providerId),
332
+ stream: (model, context, options) => this.stream(model, context, options),
333
+ streamSimple: (model, context, options) => this.streamSimple(model, context, options),
334
+ generateImages: local?.generateImages || catalog.some((model) => isModelType(model, "image"))
335
+ ? (model, context, options) => this.generateImages(model, context, options)
336
+ : undefined,
337
+ classify: local?.classify || catalog.some((model) => isModelType(model, "classifier"))
338
+ ? (model, context, options) => this.classify(model, context, options)
339
+ : undefined,
340
+ fetchDeferred: local?.fetchDeferred || capabilities?.fetchDeferred
341
+ ? (model, handle, options) => this.streamDeferred(model, handle, options)
342
+ : undefined,
343
+ cancelDeferred: local?.cancelDeferred || capabilities?.cancelDeferred
344
+ ? (model, handle, options) => this.cancelDeferred(model, handle, options)
345
+ : undefined,
346
+ };
283
347
  }
284
348
  getModels(providerId) {
349
+ if (this.piServerMode)
350
+ return this.getModelsOfType("chat", providerId);
285
351
  return this.models.getModels(providerId);
286
352
  }
287
353
  getModel(providerId, modelId) {
354
+ if (this.piServerMode)
355
+ return this.getModelOfType("chat", providerId, modelId);
288
356
  return this.models.getModel(providerId, modelId);
289
357
  }
290
358
  getModelsOfType(type, providerId) {
359
+ if (this.piServerMode)
360
+ return this.getRemoteModels(false, providerId).filter((model) => isModelType(model, type));
291
361
  return this.models.getModelsOfType(type, providerId);
292
362
  }
293
363
  getModelOfType(type, providerId, modelId) {
364
+ if (this.piServerMode)
365
+ return this.getModelsOfType(type, providerId).find((model) => model.id === modelId);
294
366
  return this.models.getModelOfType(type, providerId, modelId);
295
367
  }
296
368
  getAllModels(providerId) {
369
+ if (this.piServerMode)
370
+ return this.getRemoteModels(false, providerId);
297
371
  return this.models.getAllModels(providerId);
298
372
  }
299
- getAvailableOfType(type, providerId, options) {
373
+ async getAvailableOfType(type, providerId, options) {
374
+ if (this.piServerMode) {
375
+ options?.signal?.throwIfAborted();
376
+ await raceWithAbortSignal(this.registrationRefresh, options?.signal);
377
+ if (!this.remoteCatalogLoaded)
378
+ await this.refreshRemoteCatalog(options?.signal);
379
+ return this.getRemoteModels(true, providerId).filter((model) => isModelType(model, type));
380
+ }
300
381
  return this.models.getAvailableOfType(type, providerId, options);
301
382
  }
302
- getAllAvailable(providerId, options) {
383
+ async getAllAvailable(providerId, options) {
384
+ if (this.piServerMode) {
385
+ options?.signal?.throwIfAborted();
386
+ await raceWithAbortSignal(this.registrationRefresh, options?.signal);
387
+ if (!this.remoteCatalogLoaded)
388
+ await this.refreshRemoteCatalog(options?.signal);
389
+ return this.getRemoteModels(true, providerId);
390
+ }
303
391
  return this.models.getAllAvailable(providerId, options);
304
392
  }
393
+ getRemoteModels(available, providerId) {
394
+ if (this.remoteProjection) {
395
+ const models = available ? this.remoteProjection.available : this.remoteProjection.models;
396
+ return providerId === undefined ? models : models.filter((model) => model.provider === providerId);
397
+ }
398
+ const catalog = new Map(this.remoteCatalog.models.map((model) => [`${model.type ?? "chat"}\0${model.provider}\0${model.id}`, model]));
399
+ for (const model of this.localOperationAvailable) {
400
+ const key = `${model.type ?? "chat"}\0${model.provider}\0${model.id}`;
401
+ if (!catalog.has(key))
402
+ catalog.set(key, model);
403
+ }
404
+ const localAvailableKeys = new Set(this.localOperationAvailable.map((model) => `${model.type ?? "chat"}\0${model.provider}\0${model.id}`));
405
+ for (const id of this.nativeCatalogProviders) {
406
+ for (const model of this.models.getAllModels(id)) {
407
+ const key = `${model.type ?? "chat"}\0${model.provider}\0${model.id}`;
408
+ if (!catalog.has(key) || localAvailableKeys.has(key))
409
+ catalog.set(key, model);
410
+ }
411
+ }
412
+ const availableKeys = new Set(this.remoteCatalog.available.map((model) => `${model.type ?? "chat"}\0${model.provider}\0${model.id}`));
413
+ for (const model of this.localOperationAvailable) {
414
+ availableKeys.add(`${model.type ?? "chat"}\0${model.provider}\0${model.id}`);
415
+ }
416
+ const overlayProviders = new Set([...this.config.getProviderIds(), ...this.getRegisteredProviderIds()]);
417
+ for (const id of overlayProviders) {
418
+ const native = this.nativeExtensionProviders.get(id);
419
+ const base = native ?? this.builtins.get(id);
420
+ const baseModels = native
421
+ ? (native.getAllModels?.() ?? native.getModels())
422
+ : [...catalog.values()].filter((model) => model.provider === id);
423
+ const provider = composeModelProvider(id, base && {
424
+ ...base,
425
+ getModels: () => baseModels.filter((model) => isModelType(model, "chat")),
426
+ getAllModels: () => baseModels,
427
+ }, this.config, this.extensionProviders.get(id));
428
+ for (const [key, model] of catalog)
429
+ if (model.provider === id)
430
+ catalog.delete(key);
431
+ for (const model of provider.getAllModels?.() ?? provider.getModels()) {
432
+ catalog.set(`${model.type ?? "chat"}\0${model.provider}\0${model.id}`, model);
433
+ }
434
+ }
435
+ const models = [...catalog.values()];
436
+ const availableProviders = new Set(models
437
+ .filter((model) => availableKeys.has(`${model.type ?? "chat"}\0${model.provider}\0${model.id}`))
438
+ .map((model) => model.provider));
439
+ const virtualModels = [...this.virtualModels.values()].flatMap((models) => [...models.values()].map((entry) => entry.model));
440
+ const virtualIds = new Set(virtualModels.map((model) => `${model.provider}\0${model.id}`));
441
+ const physical = models.filter((model) => !isModelType(model, "chat") || !virtualIds.has(`${model.provider}\0${model.id}`));
442
+ const eligibleVirtual = virtualModels.filter((model) => !models.some((entry) => entry.provider === model.provider) || availableProviders.has(model.provider));
443
+ this.remoteProjection = {
444
+ models: [...physical, ...virtualModels],
445
+ available: [
446
+ ...physical.filter((model) => availableKeys.has(`${model.type ?? "chat"}\0${model.provider}\0${model.id}`)),
447
+ ...eligibleVirtual,
448
+ ],
449
+ };
450
+ return this.getRemoteModels(available, providerId);
451
+ }
452
+ async refreshRemoteCatalog(signal) {
453
+ const seq = ++this.remoteCatalogRefreshSeq;
454
+ const catalog = await fetchPiServerModels({ signal });
455
+ if (seq !== this.remoteCatalogRefreshSeq)
456
+ return;
457
+ this.remoteCatalog = catalog;
458
+ this.remoteProviderCapabilities = catalog.providers;
459
+ this.remoteCatalogLoaded = true;
460
+ this.remoteCatalogError = undefined;
461
+ this.remoteProjection = undefined;
462
+ }
305
463
  async checkAuth(providerId, options) {
306
464
  return this.models.checkAuth(providerId, options);
307
465
  }
308
466
  async getAvailable(providerId, options) {
467
+ if (this.piServerMode)
468
+ return this.getAvailableOfType("chat", providerId, options);
309
469
  if (providerId) {
310
470
  const errorSeq = ++this.availabilityErrorSeq;
311
471
  try {
@@ -325,6 +485,8 @@ export class ModelRuntime {
325
485
  return this.snapshot.available;
326
486
  }
327
487
  getAvailableSnapshot() {
488
+ if (this.piServerMode)
489
+ return this.getRemoteModels(true).filter((model) => isModelType(model, "chat"));
328
490
  return this.snapshot.available;
329
491
  }
330
492
  getError() {
@@ -337,6 +499,10 @@ export class ModelRuntime {
337
499
  }
338
500
  if (this.availabilityError)
339
501
  errors.push(`Availability refresh: ${this.availabilityError}`);
502
+ if (this.remoteCatalogError)
503
+ errors.push(`pi-server model catalog: ${this.remoteCatalogError}`);
504
+ for (const [providerId, error] of this.registrationRefreshErrors)
505
+ errors.push(`Provider "${providerId}" refresh: ${error}`);
340
506
  return errors.length > 0 ? errors.join("\n\n") : undefined;
341
507
  }
342
508
  getRegisteredProviderConfig(providerId) {
@@ -359,6 +525,8 @@ export class ModelRuntime {
359
525
  return this.isUsingOAuth(providerId) && this.models.getProvider(providerId)?.auth.oauth?.isSubscription === true;
360
526
  }
361
527
  hasConfiguredAuth(providerId) {
528
+ if (this.piServerMode)
529
+ return this.getRemoteModels(true, providerId).length > 0;
362
530
  return this.snapshot.configuredProviders.has(providerId);
363
531
  }
364
532
  async getAuth(providerOrModel, overrides = {}) {
@@ -475,11 +643,52 @@ export class ModelRuntime {
475
643
  },
476
644
  };
477
645
  }
646
+ async prepareRemoteRequest(model, options) {
647
+ if (options?.fetch !== undefined)
648
+ throw new ModelsError("stream", "pi-server does not support custom fetch implementations");
649
+ if (options &&
650
+ "transformPreparedStreamOptions" in options &&
651
+ options.transformPreparedStreamOptions !== undefined)
652
+ throw new ModelsError("stream", "pi-server does not support client prepared stream option transforms");
653
+ const resolution = this.models.getProvider(model.provider)
654
+ ? await this.getAuth(model, { apiKey: options?.apiKey, env: options?.env, signal: options?.signal })
655
+ : undefined;
656
+ let headers = mergeHeaders(mergeHeaders(model.headers, resolution?.auth.headers), options?.headers);
657
+ const { transformHeaders, ...remoteOptions } = options ?? {};
658
+ if (transformHeaders)
659
+ headers = await transformHeaders(headers ?? {});
660
+ return {
661
+ model: resolution?.auth.baseUrl ? { ...model, baseUrl: resolution.auth.baseUrl } : model,
662
+ options: {
663
+ ...remoteOptions,
664
+ apiKey: options?.apiKey ?? resolution?.auth.apiKey,
665
+ headers,
666
+ env: resolution?.env || options?.env ? { ...resolution?.env, ...options?.env } : undefined,
667
+ },
668
+ };
669
+ }
670
+ async prepareStreamRequest(model, options) {
671
+ const { transformPreparedStreamOptions, ...authOptions } = options ?? {};
672
+ const prepared = await this.prepareRequest(model, authOptions);
673
+ return {
674
+ provider: prepared.provider,
675
+ model: prepared.model,
676
+ options: transformPreparedStreamOptions
677
+ ? await transformPreparedStreamOptions(prepared.options, prepared.model)
678
+ : prepared.options,
679
+ };
680
+ }
478
681
  stream(model, context, options) {
479
682
  const transcript = normalizeContext(context);
480
683
  return lazyStream(model, async () => {
481
684
  assertChatModel(model);
482
- const prepared = await this.prepareRequest(model, options);
685
+ if (this.piServerMode) {
686
+ if (isVirtualModel(model))
687
+ throw new ModelsError("stream", `Virtual model ${model.provider}/${model.id} must be routed before streaming`);
688
+ const prepared = await this.prepareRemoteRequest(model, options);
689
+ return streamRawPiServer(prepared.model, transcript, prepared.options);
690
+ }
691
+ const prepared = await this.prepareStreamRequest(model, options);
483
692
  return prepared.provider.stream(prepared.model, transcript, prepared.options);
484
693
  });
485
694
  }
@@ -509,7 +718,11 @@ export class ModelRuntime {
509
718
  }
510
719
  return lazyStream(model, async () => {
511
720
  assertChatModel(model);
512
- const prepared = await this.prepareRequest(model, options);
721
+ if (this.piServerMode) {
722
+ const prepared = await this.prepareRemoteRequest(model, options);
723
+ return streamPiServer(prepared.model, transcript, prepared.options);
724
+ }
725
+ const prepared = await this.prepareStreamRequest(model, options);
513
726
  return prepared.provider.streamSimple(prepared.model, transcript, prepared.options);
514
727
  });
515
728
  }
@@ -519,7 +732,11 @@ export class ModelRuntime {
519
732
  streamDeferred(model, handle, options) {
520
733
  return lazyStream(model, async () => {
521
734
  assertChatModel(model);
522
- const prepared = await this.prepareRequest(model, options);
735
+ if (this.piServerMode) {
736
+ const prepared = await this.prepareRemoteRequest(model, options);
737
+ return fetchDeferredPiServer(prepared.model, handle, prepared.options);
738
+ }
739
+ const prepared = await this.prepareStreamRequest(model, options);
523
740
  if (!prepared.provider.fetchDeferred) {
524
741
  throw new ModelsError("provider", `Provider ${model.provider} does not support deferred responses`);
525
742
  }
@@ -531,6 +748,11 @@ export class ModelRuntime {
531
748
  }
532
749
  async cancelDeferred(model, handle, options) {
533
750
  assertChatModel(model);
751
+ if (this.piServerMode) {
752
+ const prepared = await this.prepareRemoteRequest(model, options);
753
+ await cancelDeferredPiServer(prepared.model, handle, prepared.options);
754
+ return;
755
+ }
534
756
  const prepared = await this.prepareRequest(model, options);
535
757
  if (!prepared.provider.cancelDeferred) {
536
758
  throw new ModelsError("provider", `Provider ${model.provider} does not support deferred responses`);
@@ -540,6 +762,10 @@ export class ModelRuntime {
540
762
  async generateImages(model, context, options) {
541
763
  try {
542
764
  assertImageModel(model);
765
+ if (this.piServerMode) {
766
+ const prepared = await this.prepareRemoteRequest(model, options);
767
+ return await generateImagesPiServer(prepared.model, context, prepared.options);
768
+ }
543
769
  const prepared = await this.prepareRequest(model, options);
544
770
  if (!prepared.provider.generateImages) {
545
771
  throw new ModelsError("provider", `Provider ${model.provider} does not support image generation`);
@@ -553,6 +779,10 @@ export class ModelRuntime {
553
779
  async classify(model, context, options) {
554
780
  try {
555
781
  assertClassifierModel(model);
782
+ if (this.piServerMode) {
783
+ const prepared = await this.prepareRemoteRequest(model, options);
784
+ return await classifyPiServer(prepared.model, context, prepared.options);
785
+ }
556
786
  const prepared = await this.prepareRequest(model, options);
557
787
  if (!prepared.provider.classify) {
558
788
  throw new ModelsError("provider", `Provider ${model.provider} does not support classification`);
@@ -579,6 +809,60 @@ export class ModelRuntime {
579
809
  });
580
810
  }
581
811
  async refresh(options = {}) {
812
+ if (options.signal?.aborted)
813
+ return { aborted: true, errors: new Map() };
814
+ try {
815
+ await raceWithAbortSignal(this.registrationRefresh, options.signal);
816
+ }
817
+ catch (error) {
818
+ if (options.signal?.aborted)
819
+ return { aborted: true, errors: new Map() };
820
+ throw error;
821
+ }
822
+ if (this.piServerMode) {
823
+ this.config = await ModelConfig.load(this.modelsPath);
824
+ this.configureRadiusProviders();
825
+ this.rebuildProviders();
826
+ let result = await this.models.refresh({ ...options, allowNetwork: false });
827
+ const nativeProviders = [
828
+ ...new Set([...this.nativeCatalogProviders, ...this.nativeExtensionProviders.keys()]),
829
+ ].filter((id) => options.providers === undefined || options.providers.includes(id));
830
+ if ((options.allowNetwork ?? this.modelNetworkEnabled) && nativeProviders.length > 0) {
831
+ const nativeResult = await this.models.refresh({
832
+ ...options,
833
+ providers: nativeProviders,
834
+ allowNetwork: true,
835
+ });
836
+ result = {
837
+ aborted: result.aborted || nativeResult.aborted,
838
+ errors: new Map([...result.errors, ...nativeResult.errors]),
839
+ };
840
+ }
841
+ const errors = new Map(result.errors);
842
+ try {
843
+ await this.queueAvailabilityRefresh(options.signal);
844
+ }
845
+ catch (error) {
846
+ if (!options.signal?.aborted)
847
+ errors.set("availability", error instanceof Error ? error : new Error(String(error)));
848
+ }
849
+ try {
850
+ if (!options.signal?.aborted)
851
+ await this.refreshRemoteCatalog(options.signal);
852
+ }
853
+ catch (error) {
854
+ if (!options.signal?.aborted) {
855
+ const remoteError = error instanceof Error ? error : new Error(String(error));
856
+ this.remoteCatalogError = remoteError.message;
857
+ errors.set("pi-server", remoteError);
858
+ }
859
+ }
860
+ for (const id of this.registrationRefreshErrors.keys()) {
861
+ if (!errors.has(id) && (options.providers === undefined || options.providers.includes(id)))
862
+ this.registrationRefreshErrors.delete(id);
863
+ }
864
+ return { aborted: result.aborted || options.signal?.aborted === true, errors };
865
+ }
582
866
  this.config = await ModelConfig.load(this.modelsPath);
583
867
  this.configureRadiusProviders();
584
868
  if (options.providers) {
@@ -593,12 +877,7 @@ export class ModelRuntime {
593
877
  ...options,
594
878
  allowNetwork: options.allowNetwork ?? this.modelNetworkEnabled,
595
879
  };
596
- // Published pi-ai builds before ModelsStore returned void and accepted a provider ID.
597
- // The fallback keeps source-mode CLI tests working without rebuilding workspace dependencies.
598
- const result = (await this.models.refresh(refreshOptions)) ?? {
599
- aborted: refreshOptions.signal?.aborted ?? false,
600
- errors: new Map(),
601
- };
880
+ const result = await this.models.refresh(refreshOptions);
602
881
  const errors = new Map(result.errors);
603
882
  this.updateModelSnapshot();
604
883
  if (options.providers) {
@@ -623,6 +902,21 @@ export class ModelRuntime {
623
902
  }
624
903
  return { aborted: result.aborted || (options.signal?.aborted ?? false), errors };
625
904
  }
905
+ queueRegistrationRefresh(providerId) {
906
+ this.registrationRefresh = this.registrationRefresh
907
+ .then(async () => {
908
+ const result = await this.models.refresh({ allowNetwork: false, providers: [providerId] });
909
+ const error = result.errors.get(providerId);
910
+ if (error)
911
+ throw error;
912
+ this.updateModelSnapshot();
913
+ await this.refreshProviderAvailability(providerId, operationSignal());
914
+ this.registrationRefreshErrors.delete(providerId);
915
+ })
916
+ .catch((error) => {
917
+ this.registrationRefreshErrors.set(providerId, error instanceof Error ? error.message : String(error));
918
+ });
919
+ }
626
920
  registerNativeProvider(provider) {
627
921
  if (!provider.id.trim())
628
922
  throw new Error("Provider id must not be empty.");
@@ -631,7 +925,7 @@ export class ModelRuntime {
631
925
  this.recomposeProvider(provider.id);
632
926
  this.updateModelSnapshot();
633
927
  this.markProvisionallyConfigured(provider.id, configuredRequestAuthStatus(this.config.getProvider(provider.id), undefined), provider.auth.oauth && !provider.auth.apiKey ? "oauth" : "api_key");
634
- void this.refresh({ allowNetwork: false });
928
+ this.queueRegistrationRefresh(provider.id);
635
929
  }
636
930
  /**
637
931
  * Mark a newly registered provider as configured when it has a stored credential or a configured
@@ -670,14 +964,14 @@ export class ModelRuntime {
670
964
  this.recomposeProvider(providerId);
671
965
  this.updateModelSnapshot();
672
966
  this.markProvisionallyConfigured(providerId, configuredRequestAuthStatus(this.config.getProvider(providerId), effective), effective.oauth && !effective.apiKey ? "oauth" : "api_key");
673
- void this.refresh({ allowNetwork: false });
967
+ this.queueRegistrationRefresh(providerId);
674
968
  }
675
969
  unregisterProvider(providerId) {
676
970
  this.extensionProviders.delete(providerId);
677
971
  this.nativeExtensionProviders.delete(providerId);
678
972
  this.recomposeProvider(providerId);
679
973
  this.updateModelSnapshot();
680
- void this.refresh({ allowNetwork: false });
974
+ this.queueRegistrationRefresh(providerId);
681
975
  }
682
976
  /**
683
977
  * Register a virtual model under `definition.provider`, which may also list physical models or
@@ -688,7 +982,7 @@ export class ModelRuntime {
688
982
  const { provider: providerId, id } = definition;
689
983
  if (!providerId.trim() || !id.trim())
690
984
  throw new Error("Virtual model provider and id must not be empty.");
691
- const existing = this.models.getModel(providerId, id);
985
+ const existing = this.getModel(providerId, id);
692
986
  if (existing && !isVirtualModel(existing)) {
693
987
  throw new Error(`Virtual model ${providerId}/${id} conflicts with a physical model.`);
694
988
  }
@@ -703,7 +997,7 @@ export class ModelRuntime {
703
997
  this.snapshot = { ...this.snapshot, auth, configuredProviders };
704
998
  }
705
999
  this.updateModelSnapshot();
706
- void this.refresh({ allowNetwork: false });
1000
+ this.queueRegistrationRefresh(providerId);
707
1001
  }
708
1002
  unregisterVirtualModel(providerId, id) {
709
1003
  const models = this.virtualModels.get(providerId);
@@ -713,7 +1007,7 @@ export class ModelRuntime {
713
1007
  this.virtualModels.delete(providerId);
714
1008
  this.recomposeProvider(providerId);
715
1009
  this.updateModelSnapshot();
716
- void this.refresh({ allowNetwork: false });
1010
+ this.queueRegistrationRefresh(providerId);
717
1011
  }
718
1012
  /**
719
1013
  * Ask a virtual model's router for the model and thinking level of one request. The router must
@@ -751,7 +1045,7 @@ export class ModelRuntime {
751
1045
  }
752
1046
  /** A catalog chat model that is not virtual. */
753
1047
  getPhysicalModel(providerId, modelId) {
754
- const model = this.models.getModel(providerId, modelId);
1048
+ const model = this.getModel(providerId, modelId);
755
1049
  return model && !isVirtualModel(model) ? model : undefined;
756
1050
  }
757
1051
  }