@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 +5 -0
- package/extensions/credential.ts +40 -3
- package/extensions/provider.ts +24 -2
- package/package.json +1 -1
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
|
package/extensions/credential.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
package/extensions/provider.ts
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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",
|