@hyav/pi-provider 0.1.5 → 0.1.6

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  This file is the authoritative user-facing release history for `@hyav/pi-provider`.
4
4
 
5
+ ## 0.1.6 - 2026-08-24
6
+
7
+ - Prevent Charm Hyper model-refresh warnings when no credentials are configured by registering OAuth-capable Providers without unresolved optional environment API keys and clearing stale API-key configuration after `/reload`; configured environment and stored API keys remain supported.
8
+
5
9
  ## 0.1.5 - 2026-08-24
6
10
 
7
11
  - Skip network model-catalog refreshes for Providers whose environment-backed API key is not configured and which have no resolved stored credential, retaining cached models without Pi refresh warnings.
@@ -61,9 +61,10 @@ function getStartupBridge(pi: ExtensionAPI): StartupBridge {
61
61
  );
62
62
  }
63
63
 
64
- function emitRegistration(pi: ExtensionAPI, envelope: AdapterRegistrationEnvelope): void {
64
+ function emitRegistration(pi: ExtensionAPI, envelope: AdapterRegistrationEnvelope, onSessionStart?: () => void): void {
65
65
  pi.events.emit(PI_PROVIDER_ADAPTER_EVENT, envelope);
66
66
  pi.on("session_start", () => {
67
+ onSessionStart?.();
67
68
  pi.events.emit(PI_PROVIDER_ADAPTER_EVENT, envelope);
68
69
  });
69
70
  }
@@ -92,17 +93,26 @@ function createAdapterExtension<TAdapter extends ProviderAdapter | StatusAdapter
92
93
  // Host runs in a different Pi module context and cannot use this
93
94
  // extension's module-local state. The factory is retained so a Host
94
95
  // loaded later can recreate the adapter with its configured runtime.
95
- registerProviderAdapter(pi, providerAdapter, bridge.dependencies, {}, modelDrafts);
96
- emitRegistration(pi, {
97
- version: PI_PROVIDER_ADAPTER_PROTOCOL_VERSION,
98
- kind: "provider",
99
- id,
100
- token,
101
- adapter: providerAdapter,
102
- factory: createAdapter,
103
- startupDependencies: bridge.dependencies,
104
- modelDrafts,
105
- });
96
+ const registeredProvider = registerProviderAdapter(pi, providerAdapter, bridge.dependencies, {}, modelDrafts);
97
+ emitRegistration(
98
+ pi,
99
+ {
100
+ version: PI_PROVIDER_ADAPTER_PROTOCOL_VERSION,
101
+ kind: "provider",
102
+ id,
103
+ token,
104
+ adapter: providerAdapter,
105
+ factory: createAdapter,
106
+ startupDependencies: bridge.dependencies,
107
+ modelDrafts,
108
+ },
109
+ // During extension loading Pi can only remove queued registrations;
110
+ // repeat OAuth-only replacement after binding so /reload clears a
111
+ // raw environment key retained by Pi's merge semantics.
112
+ registeredProvider.apiKey === undefined
113
+ ? () => registerProviderAdapter(pi, providerAdapter, bridge.dependencies, {}, modelDrafts)
114
+ : undefined,
115
+ );
106
116
  return;
107
117
  }
108
118
  if (kind === "status") {
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI, ProviderConfig } from "@earendil-works/pi-coding-agent";
1
+ import type { ProviderConfig } from "@earendil-works/pi-coding-agent";
2
2
  import { validateProviderModelDrafts } from "./adapter-validation.ts";
3
3
  import { applyOfficialModelCosts, findOfficialMeta, type OfficialModelMeta } from "./official-pricing.ts";
4
4
  import { resolvePricingDetails } from "./pricing-adjustments.ts";
@@ -17,6 +17,11 @@ import type {
17
17
  const DEFAULT_CONTEXT_WINDOW = 128_000;
18
18
  const DEFAULT_MAX_TOKENS = 16_384;
19
19
 
20
+ type ProviderRegistrationApi = {
21
+ registerProvider(name: string, config: ProviderConfig): void;
22
+ unregisterProvider?(name: string): void;
23
+ };
24
+
20
25
  function finiteNonNegative(value: unknown): number {
21
26
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
22
27
  }
@@ -233,12 +238,23 @@ function replaceModels(target: ProviderModel[], source: ProviderModel[]): Provid
233
238
  return target;
234
239
  }
235
240
 
236
- function lacksCatalogRefreshCredential(apiKey: string, context: ProviderRefreshContext): boolean {
237
- if (context.allowNetwork !== true || context.credential !== undefined) return false;
241
+ function hasMissingEnvironmentReference(apiKey: string): boolean {
238
242
  const environmentNames = getEnvironmentReferences(apiKey);
239
243
  return environmentNames.length > 0 && environmentNames.some((name) => !process.env[name]);
240
244
  }
241
245
 
246
+ function lacksCatalogRefreshCredential(apiKey: string, context: ProviderRefreshContext): boolean {
247
+ return context.allowNetwork === true && context.credential === undefined && hasMissingEnvironmentReference(apiKey);
248
+ }
249
+
250
+ function shouldOmitOptionalApiKey(adapter: ProviderAdapter, runtime: PiProviderDependencies): boolean {
251
+ return (
252
+ adapter.provider.oauth !== undefined &&
253
+ hasMissingEnvironmentReference(adapter.provider.apiKey) &&
254
+ runtime.readStoredCredential(adapter.id)?.type !== "api_key"
255
+ );
256
+ }
257
+
242
258
  /**
243
259
  * Register a normalized Provider before the Host has assembled its final
244
260
  * registry. The original drafts remain attached to the adapter so a Host in a
@@ -276,6 +292,10 @@ export function prepareProviderRegistration(
276
292
 
277
293
  const { models: _draftModels, refreshModels: originalRefresh, ...providerMetadata } = adapter.provider;
278
294
  const registeredProvider: ProviderConfig = { ...providerMetadata, models };
295
+ // Pi 0.84.2 resolves a declared environment API key before it can skip an
296
+ // unauthenticated catalog refresh. Register OAuth-only until the optional
297
+ // key exists, while preserving API-key credentials already stored by Pi.
298
+ if (shouldOmitOptionalApiKey(adapter, runtime)) delete registeredProvider.apiKey;
279
299
  if (originalRefresh) {
280
300
  registeredProvider.refreshModels = async (options: ProviderRefreshContext) => {
281
301
  // Pi may ask every dynamic Provider to refresh. Keep the current catalog
@@ -329,7 +349,7 @@ export function cancelDeferredProviderRegistrations(providers: readonly Provider
329
349
  }
330
350
 
331
351
  export function refreshProviderRegistrations(
332
- pi: Pick<ExtensionAPI, "registerProvider">,
352
+ pi: ProviderRegistrationApi,
333
353
  providers: readonly ProviderAdapter[],
334
354
  runtime: PiProviderDependencies,
335
355
  officialPricing: Record<string, OfficialModelMeta>,
@@ -348,13 +368,16 @@ export function refreshProviderRegistrations(
348
368
  }
349
369
 
350
370
  export function registerProviderAdapter(
351
- pi: Pick<ExtensionAPI, "registerProvider">,
371
+ pi: ProviderRegistrationApi,
352
372
  adapter: ProviderAdapter,
353
373
  runtime: PiProviderDependencies,
354
374
  officialPricing: Record<string, OfficialModelMeta> = {},
355
375
  modelDrafts?: ProviderModelDraft[],
356
376
  ): ProviderConfig {
357
377
  const registeredProvider = prepareProviderRegistration(adapter, runtime, officialPricing, modelDrafts);
378
+ // Pi merges re-registrations, so omission alone cannot clear a raw API key
379
+ // left by the previous extension instance during /reload.
380
+ if (registeredProvider.apiKey === undefined) pi.unregisterProvider?.(adapter.id);
358
381
  pi.registerProvider(adapter.id, registeredProvider);
359
382
  return registeredProvider;
360
383
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyav/pi-provider",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Provider extension toolkit for Pi to integrate and manage custom LLM providers with dynamic models, request tuners, and account status.",
5
5
  "author": "hyav",
6
6
  "license": "MIT",