@pwguler/pi-pengepul-provider 0.5.1 → 0.5.3

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/README.md CHANGED
@@ -141,6 +141,11 @@ box:
141
141
  }
142
142
  ```
143
143
 
144
+ `env` is read off the credential by this provider, not by pi core: a provider
145
+ that brings its own `resolve()` has to hand the values back, and pengepul does.
146
+ Re-add them after `/login pengepul` — pi replaces the whole credential on login,
147
+ so a key rotation drops the block and, with it, the tier.
148
+
144
149
  Idle warming covers gaps up to 30 minutes, pi's own safety limit, and pi only
145
150
  triggers it when the expected saving clears a threshold of its own — a small
146
151
  prompt is left to expire rather than refreshed. The long tier is what carries a
@@ -10,7 +10,7 @@
10
10
  * override, then the credential, then the loopback default pengepul binds to.
11
11
  */
12
12
 
13
- import { API_KEY_ENV } from "./config.ts"
13
+ import { API_KEY_ENV, RELAY_BASE_ENV } from "./config.ts"
14
14
  import { normalizeRootBaseUrl } from "./dialect.ts"
15
15
  import { DEFAULT_RELAY_BASE } from "./models.ts"
16
16
 
@@ -20,6 +20,8 @@ export interface PengepulCredential {
20
20
  type?: unknown
21
21
  key?: unknown
22
22
  baseUrl?: unknown
23
+ /** Provider-scoped environment values pi stores alongside the key. */
24
+ env?: unknown
23
25
  }
24
26
 
25
27
  function nonEmptyString(value: unknown): string | undefined {
@@ -28,9 +30,18 @@ function nonEmptyString(value: unknown): string | undefined {
28
30
  return trimmed === "" ? undefined : trimmed
29
31
  }
30
32
 
31
- /** The relay base stored on the credential, when the user set one. */
33
+ /**
34
+ * The relay base stored on the credential, when the user set one.
35
+ *
36
+ * `baseUrl` is where the stored credential keeps it. The credential pi hands
37
+ * the network phase of a refresh is not the stored one: pi rebuilds it from
38
+ * `resolve()` as `{ type, key, env }`, so the base only survives as the
39
+ * `PENGEPUL_BASE_URL` entry `resolve()` writes into `env`. Missing that
40
+ * fallback sends the catalog fetch to loopback - which on any machine but the
41
+ * relay's is some other listener, or nothing.
42
+ */
32
43
  export function credentialRelayBase(credential: PengepulCredential | undefined): string | undefined {
33
- return nonEmptyString(credential?.baseUrl)
44
+ return nonEmptyString(credential?.baseUrl) ?? credentialEnv(credential)?.[RELAY_BASE_ENV]
34
45
  }
35
46
 
36
47
  /** The API key stored on the credential, when the user set one. */
@@ -38,6 +49,32 @@ export function credentialApiKey(credential: PengepulCredential | undefined): st
38
49
  return nonEmptyString(credential?.key)
39
50
  }
40
51
 
52
+ /**
53
+ * The provider-scoped environment the credential carries, when it holds any.
54
+ *
55
+ * pi core fills an `AuthResult.env` from here only for providers that bring no
56
+ * `resolve()` of their own; this provider brings one, so the values have to be
57
+ * passed through or they never reach a request. They are how a user sets
58
+ * `PI_CACHE_RETENTION=long` for the relay without exporting it in every shell
59
+ * that starts pi, and losing them is silent: the cache quietly falls back to
60
+ * the five-minute tier and re-bills whole prefixes.
61
+ *
62
+ * Non-string values are dropped rather than stringified — an env var is a
63
+ * string, and a nested object here is a typo, not a value.
64
+ */
65
+ export function credentialEnv(
66
+ credential: PengepulCredential | undefined,
67
+ ): Record<string, string> | undefined {
68
+ const raw = credential?.env
69
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return undefined
70
+
71
+ const env: Record<string, string> = {}
72
+ for (const [name, value] of Object.entries(raw)) {
73
+ if (typeof value === "string") env[name] = value
74
+ }
75
+ return Object.keys(env).length > 0 ? env : undefined
76
+ }
77
+
41
78
  export interface RelayBaseSources {
42
79
  /** The `PENGEPUL_BASE_URL` environment value. */
43
80
  environment?: string | undefined
@@ -30,6 +30,7 @@ import { anthropicMessagesApi, lazyStream, openAICompletionsApi } from "@earendi
30
30
  import { API_KEY_ENV, RELAY_BASE_ENV } from "./config.ts"
31
31
  import {
32
32
  credentialApiKey,
33
+ credentialEnv,
33
34
  credentialRelayBase,
34
35
  resolveApiKey,
35
36
  resolveRelayBase,
@@ -211,9 +212,30 @@ export function createPengepulProvider(options: PengepulProviderOptions): Provid
211
212
  resolve: async (input): Promise<AuthResult | undefined> => {
212
213
  const resolved = resolveKey(input.credential)
213
214
  if (!resolved) return undefined
215
+ const credential = input.credential as PengepulCredential | undefined
216
+ const relayBase = credentialRelayBase(credential)
217
+ const credentialVars = credentialEnv(credential)
218
+ const env =
219
+ credentialVars || relayBase
220
+ ? { ...credentialVars, ...(relayBase ? { [RELAY_BASE_ENV]: relayBase } : {}) }
221
+ : undefined
214
222
  // No `baseUrl` here on purpose: pi would apply it to every model and
215
- // collapse the two dialect base URLs into one.
216
- return { auth: { apiKey: resolved.key }, source: resolved.source }
223
+ // collapse the two dialect base URLs into one. The relay base rides
224
+ // in `env` instead, because the network phase of a refresh gets a
225
+ // credential pi rebuilds from this result, and `env` is the only
226
+ // field of ours that survives the rebuild.
227
+ //
228
+ // The credential's own `env` is here because pi only reads it off the credential itself
229
+ // when the provider brings no `resolve()` of its own — this one does,
230
+ // so a value like PI_CACHE_RETENTION=long is dropped unless it is
231
+ // handed back. Dropping it is silent and costs money: the cache falls
232
+ // back to the five-minute tier and re-bills the prefix on every
233
+ // return.
234
+ return {
235
+ auth: { apiKey: resolved.key },
236
+ ...(env ? { env } : {}),
237
+ source: resolved.source,
238
+ }
217
239
  },
218
240
  },
219
241
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pwguler/pi-pengepul-provider",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "type": "module",
5
5
  "description": "pi custom provider for pengepul, a relay that pools your Claude/Codex subscriptions. Key and relay base live in auth.json; both native wires (Anthropic Messages, OpenAI Chat Completions) come from one catalog.",
6
6
  "license": "MIT",