pi2dsh 0.14.1 → 0.15.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.
@@ -4214,7 +4214,7 @@ function subscribeLifecycle(ctx, state) {
4214
4214
  for (const dispose of state.toolDisposers.values()) dispose();
4215
4215
  state.toolDisposers.clear();
4216
4216
  for (const provider of [...state.ownedProviderRoutes]) releaseSharedProviderRoute(state, provider);
4217
- state.eventBus.removeAllListeners();
4217
+ for (const off of state.eventBusOffs.splice(0)) off();
4218
4218
  }, "pi2dsh session shutdown");
4219
4219
  }
4220
4220
  async function flushPendingSessionStarts(ctx, state) {
@@ -4611,15 +4611,113 @@ function oauthStoreOf(state) {
4611
4611
  const COMPANION_ROUTE_PREFIX = "pi2dsh-companion/";
4612
4612
  const SHARED_HOST_STATE = /* @__PURE__ */ new WeakMap();
4613
4613
  /**
4614
- * The engine's own built-in OAuth directory entries, by object identity.
4615
- * They are login placeholders (no models, no transport), preloaded so
4616
- * `/login <provider>` works before any package is installed — and a package
4617
- * registering the SAME provider id owns the richer definition (transport,
4618
- * catalog, its own flow) and must supersede the placeholder in the shared
4619
- * ledger. Two PACKAGES colliding on one id still keep first-wins, matching
4620
- * the route-ownership rule.
4614
+ * Pi's provider ledger is layered, not flat (model-runtime.ts:742-786 at the
4615
+ * pinned upstream): a builtin base map, an extension overlay where defined
4616
+ * fields merge over the base (undefined fields expose the base — a package
4617
+ * overriding only baseUrl keeps the builtin OAuth flow), later registrations
4618
+ * overriding earlier ones, and unregistration restoring the builtin. These
4619
+ * helpers vendor that contract for the shared host ledger. One deliberate
4620
+ * adaptation: the overlay is slotted per PACKAGE and folded in package
4621
+ * first-registration order, because in Pi "last wins" is load order (each
4622
+ * session rebuilds the runtime so registration order is always load order),
4623
+ * while our host ledger accumulates across sessions — folding raw arrival
4624
+ * order would let a later agent's re-registration of package A overturn
4625
+ * package B for no Pi-semantic reason.
4621
4626
  */
4622
- const BUILTIN_PROVIDER_SEEDS = /* @__PURE__ */ new WeakSet();
4627
+ function mergeProviderRegistration(previous, config) {
4628
+ const effective = { ...previous };
4629
+ for (const [key, value] of Object.entries(config)) if (value !== void 0) effective[key] = value;
4630
+ return effective;
4631
+ }
4632
+ function overlayProviderConfig(base, overlay) {
4633
+ const merged = { ...base };
4634
+ for (const [key, value] of Object.entries(overlay)) if (value !== void 0) merged[key] = value;
4635
+ const baseModels = base?.models;
4636
+ if (overlay.models === void 0 && typeof overlay.baseUrl === "string" && Array.isArray(baseModels)) merged.models = baseModels.map((entry) => typeof entry === "object" && entry !== null ? {
4637
+ ...entry,
4638
+ baseUrl: overlay.baseUrl
4639
+ } : entry);
4640
+ return merged;
4641
+ }
4642
+ /** Structural fingerprint used to decide whether a recomposition actually
4643
+ * changed the canonical shape. Function identities differ on every factory
4644
+ * run (each agent instance registers fresh closures), so functions compare
4645
+ * by presence — behaviourally equivalent re-registrations keep the existing
4646
+ * canonical reference and the route built on it, exactly like today. */
4647
+ function providerFingerprint(config) {
4648
+ if (config === void 0) return "absent";
4649
+ return JSON.stringify(config, (_key, value) => typeof value === "function" ? "[function]" : value);
4650
+ }
4651
+ function recordPackageProviderRegistration(shared, packageName, name, value) {
4652
+ shared.providerPackages ??= /* @__PURE__ */ new Map();
4653
+ shared.providerPackageOrder ??= [];
4654
+ if (!shared.providerPackageOrder.includes(packageName)) shared.providerPackageOrder.push(packageName);
4655
+ let slots = shared.providerPackages.get(name);
4656
+ if (slots === void 0) {
4657
+ slots = /* @__PURE__ */ new Map();
4658
+ shared.providerPackages.set(name, slots);
4659
+ }
4660
+ slots.set(packageName, mergeProviderRegistration(slots.get(packageName), value));
4661
+ }
4662
+ /** Rebuild the canonical entry for one provider id from builtin + package
4663
+ * slots. Returns whether the composed shape changed (callers retire the
4664
+ * route on change so it is rebuilt from the new canonical). */
4665
+ function recomposeSharedProvider(shared, name) {
4666
+ const builtin = shared.providerBuiltins?.get(name);
4667
+ const slots = shared.providerPackages?.get(name);
4668
+ let canonical;
4669
+ if (slots === void 0 || slots.size === 0) canonical = builtin;
4670
+ else {
4671
+ canonical = { ...builtin ?? {} };
4672
+ for (const packageName of shared.providerPackageOrder ?? []) {
4673
+ const registration = slots.get(packageName);
4674
+ if (registration !== void 0) canonical = overlayProviderConfig(canonical, registration);
4675
+ }
4676
+ }
4677
+ if (!(providerFingerprint(shared.providers.get(name)) !== providerFingerprint(canonical))) return false;
4678
+ if (canonical === void 0) shared.providers.delete(name);
4679
+ else shared.providers.set(name, canonical);
4680
+ return true;
4681
+ }
4682
+ /** Force-retire a provider's route regardless of refcounts: the canonical
4683
+ * definition changed, so a route built on the old shape must not serve it. */
4684
+ function retireSharedProviderRoute(shared, name) {
4685
+ const route = shared.providerRouteDisposers.get(name);
4686
+ if (route === void 0) return;
4687
+ shared.providerRouteDisposers.delete(name);
4688
+ shared.providerRouteOwners.delete(name);
4689
+ route();
4690
+ }
4691
+ function sharedEventBusFor(shared, ownerAgent) {
4692
+ if (ownerAgent === void 0) {
4693
+ if (shared.hostEventBus === void 0) {
4694
+ shared.hostEventBus = new EventEmitter();
4695
+ shared.hostEventBus.setMaxListeners(0);
4696
+ }
4697
+ return shared.hostEventBus;
4698
+ }
4699
+ shared.agentEventBuses ??= /* @__PURE__ */ new WeakMap();
4700
+ let bus = shared.agentEventBuses.get(ownerAgent);
4701
+ if (bus === void 0) {
4702
+ bus = new EventEmitter();
4703
+ bus.setMaxListeners(0);
4704
+ shared.agentEventBuses.set(ownerAgent, bus);
4705
+ }
4706
+ return bus;
4707
+ }
4708
+ function sharedThemeFor(shared, ownerAgent) {
4709
+ if (ownerAgent === void 0) {
4710
+ shared.hostTheme ??= new Theme();
4711
+ return shared.hostTheme;
4712
+ }
4713
+ shared.agentThemes ??= /* @__PURE__ */ new WeakMap();
4714
+ let theme = shared.agentThemes.get(ownerAgent);
4715
+ if (theme === void 0) {
4716
+ theme = new Theme();
4717
+ shared.agentThemes.set(ownerAgent, theme);
4718
+ }
4719
+ return theme;
4720
+ }
4623
4721
  function sharedHostStateOf(ctx) {
4624
4722
  const key = ctx.root ?? ctx;
4625
4723
  let shared = SHARED_HOST_STATE.get(key);
@@ -5495,7 +5593,7 @@ function registerSharedProviderRoute(ctx, state, name, value) {
5495
5593
  return existing;
5496
5594
  }
5497
5595
  const canonical = shared.providers.get(name) ?? value;
5498
- shared.providers.set(name, canonical);
5596
+ if (!shared.providers.has(name)) shared.providers.set(name, canonical);
5499
5597
  const route = registerPiProviderRoute({
5500
5598
  llm: llmOf(ctx),
5501
5599
  ctx,
@@ -5550,31 +5648,27 @@ function createPiApi(ctx, state) {
5550
5648
  const name = typeof providerOrName === "string" ? providerOrName : String(providerOrName?.id ?? providerOrName?.name ?? "unnamed");
5551
5649
  const value = typeof providerOrName === "string" ? config ?? {} : providerOrName;
5552
5650
  state.providers.set(name, value);
5553
- const sharedExisting = state.shared.providers.get(name);
5554
- if (sharedExisting === void 0 || BUILTIN_PROVIDER_SEEDS.has(sharedExisting)) {
5555
- state.shared.providers.set(name, value);
5556
- if (sharedExisting !== void 0) {
5557
- const stale = state.shared.providerRouteDisposers.get(name);
5558
- if (stale !== void 0) {
5559
- state.shared.providerRouteDisposers.delete(name);
5560
- state.shared.providerRouteOwners.delete(name);
5561
- stale();
5562
- }
5563
- }
5564
- }
5565
- if (providerSupportsOAuth(value)) {
5651
+ recordPackageProviderRegistration(state.shared, state.packageName, name, value);
5652
+ if (recomposeSharedProvider(state.shared, name)) retireSharedProviderRoute(state.shared, name);
5653
+ const canonical = state.shared.providers.get(name) ?? value;
5654
+ if (providerSupportsOAuth(canonical)) {
5566
5655
  ensureLoginCommand(ctx, state);
5567
5656
  logger(ctx).info(`[pi2dsh] Pi provider ${JSON.stringify(name)} supports OAuth — log in with /login ${name}`);
5568
5657
  }
5569
- if (registerSharedProviderRoute(ctx, state, name, value) !== void 0) {
5658
+ if (registerSharedProviderRoute(ctx, state, name, canonical) !== void 0) {
5570
5659
  state.modelCatalog?.refresh();
5571
- if (providerCarriesTransport(value)) logger(ctx).info(`[pi2dsh] Pi provider ${JSON.stringify(name)} registered as a native DSH llm route`);
5572
- } else if (!providerSupportsOAuth(value)) logger(ctx).info(`[pi2dsh] recorded Pi provider ${JSON.stringify(name)}; model calls stay on DSH llm adapters`);
5573
- discoverProviderModels(ctx, state, name, value);
5660
+ if (providerCarriesTransport(canonical)) logger(ctx).info(`[pi2dsh] Pi provider ${JSON.stringify(name)} registered as a native DSH llm route`);
5661
+ } else if (!providerSupportsOAuth(canonical)) logger(ctx).info(`[pi2dsh] recorded Pi provider ${JSON.stringify(name)}; model calls stay on DSH llm adapters`);
5662
+ discoverProviderModels(ctx, state, name, canonical);
5574
5663
  },
5575
5664
  unregisterProvider(name) {
5576
5665
  state.providers.delete(name);
5577
5666
  releaseSharedProviderRoute(state, name);
5667
+ if ((state.shared.providerPackages?.get(name))?.delete(state.packageName) === true && recomposeSharedProvider(state.shared, name)) {
5668
+ retireSharedProviderRoute(state.shared, name);
5669
+ const restored = state.shared.providers.get(name);
5670
+ if (restored !== void 0) registerSharedProviderRoute(ctx, state, name, restored);
5671
+ }
5578
5672
  },
5579
5673
  registerMessageRenderer(customType, renderer) {
5580
5674
  state.messageRenderers.set(customType, renderer);
@@ -5687,7 +5781,11 @@ function createPiApi(ctx, state) {
5687
5781
  Promise.resolve(handler(data)).catch((error) => logger(ctx).warn(`[pi2dsh] package event ${channel} handler failed: ${String(error)}`));
5688
5782
  };
5689
5783
  state.eventBus.on(channel, safeHandler);
5690
- return () => state.eventBus.off(channel, safeHandler);
5784
+ const off = () => {
5785
+ state.eventBus.off(channel, safeHandler);
5786
+ };
5787
+ state.eventBusOffs.push(off);
5788
+ return off;
5691
5789
  }
5692
5790
  }
5693
5791
  };
@@ -5877,13 +5975,14 @@ async function applyPiPackage(ctx, options) {
5877
5975
  pendingSessionStarts: /* @__PURE__ */ new Map(),
5878
5976
  currentSystemPrompt: "",
5879
5977
  messageSource: `pi2dsh:${options.manifest.package.name}`,
5880
- eventBus: new EventEmitter(),
5978
+ eventBus: sharedEventBusFor(shared, ownerAgent),
5979
+ eventBusOffs: [],
5881
5980
  agentScope: new AsyncLocalStorage(),
5882
5981
  llmBridge: void 0,
5883
5982
  piAiRegistry: __createPiAiRuntimeRegistry(),
5884
5983
  subagentSessionFactory: void 0,
5885
5984
  bridge: new PiSessionBridge(),
5886
- theme: new Theme(),
5985
+ theme: sharedThemeFor(shared, ownerAgent),
5887
5986
  shortcuts: /* @__PURE__ */ new Map(),
5888
5987
  messageRenderers: /* @__PURE__ */ new Map(),
5889
5988
  entryRenderers: /* @__PURE__ */ new Map(),
@@ -5914,15 +6013,18 @@ async function applyPiPackage(ctx, options) {
5914
6013
  }, typeof ownerAgent?.id === "string" ? ownerAgent.id : void 0);
5915
6014
  subscribeLifecycle(ctx, state);
5916
6015
  subscribeInterceptors(ctx, state);
6016
+ shared.providerBuiltins ??= /* @__PURE__ */ new Map();
5917
6017
  for (const provider of builtinProviders()) {
5918
6018
  const config = {
5919
6019
  name: provider.name,
5920
6020
  baseUrl: provider.baseUrl,
5921
6021
  oauth: provider.auth.oauth
5922
6022
  };
5923
- BUILTIN_PROVIDER_SEEDS.add(config);
5924
6023
  state.providers.set(provider.id, config);
5925
- if (!shared.providers.has(provider.id)) shared.providers.set(provider.id, config);
6024
+ if (!shared.providerBuiltins.has(provider.id)) {
6025
+ shared.providerBuiltins.set(provider.id, config);
6026
+ recomposeSharedProvider(shared, provider.id);
6027
+ }
5926
6028
  }
5927
6029
  ensureLoginCommand(ctx, state);
5928
6030
  for (const [name, config] of [...state.providers]) try {
@@ -6073,7 +6175,7 @@ async function applyPiPackage(ctx, options) {
6073
6175
  remounts.set(state.packageName, async () => {
6074
6176
  state.extensionsReady = false;
6075
6177
  state.handlers.clear();
6076
- state.eventBus.removeAllListeners();
6178
+ for (const off of state.eventBusOffs.splice(0)) off();
6077
6179
  try {
6078
6180
  await registerPromptCommands(ctx, state, rootDir, options.manifest);
6079
6181
  await mountExtensions();
@@ -6102,6 +6204,6 @@ const runtimeInternals = {
6102
6204
  textBlocks
6103
6205
  };
6104
6206
  //#endregion
6105
- export { runtimeInternals as i, normalizeToolSchema as n, registerVisionCompanions as r, applyPiPackage as t };
6207
+ export { registerVisionCompanions as a, overlayProviderConfig as i, mergeProviderRegistration as n, runtimeInternals as o, normalizeToolSchema as r, applyPiPackage as t };
6106
6208
 
6107
- //# sourceMappingURL=runtime-BiD3SeGf.mjs.map
6209
+ //# sourceMappingURL=runtime-BN4E4OtI.mjs.map