pi-hypercharm-provider 1.3.8 → 1.3.9

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.
@@ -1,6 +1,112 @@
1
1
  {
2
2
  "format": 1,
3
- "entries": {},
3
+ "entries": {
4
+ "topology/peer-seq": {
5
+ "key": "topology/peer-seq",
6
+ "value": {
7
+ "format": 1,
8
+ "next": 1
9
+ },
10
+ "version": 1,
11
+ "updatedAt": 1788269765843,
12
+ "updatedBy": {
13
+ "id": "session:01a05d2e-8a1c-7bba-96fe-6e9eb4d7c42e",
14
+ "name": "main",
15
+ "kind": "main",
16
+ "sessionId": "01a05d2e-8a1c-7bba-96fe-6e9eb4d7c42e"
17
+ }
18
+ },
19
+ "topology/hosts/276282166ed2e87cc2e85abfc95904809b77ae8c5dd6d44b4ab841d33b333b8a": {
20
+ "key": "topology/hosts/276282166ed2e87cc2e85abfc95904809b77ae8c5dd6d44b4ab841d33b333b8a",
21
+ "value": {
22
+ "format": 1,
23
+ "id": "session:01a05d2e-8a1c-7bba-96fe-6e9eb4d7c42e",
24
+ "rootId": "session:01a05d2e-8a1c-7bba-96fe-6e9eb4d7c42e",
25
+ "identity": {
26
+ "id": "session:01a05d2e-8a1c-7bba-96fe-6e9eb4d7c42e",
27
+ "name": "main",
28
+ "kind": "main",
29
+ "sessionId": "01a05d2e-8a1c-7bba-96fe-6e9eb4d7c42e"
30
+ },
31
+ "startedAt": 1788269765777,
32
+ "updatedAt": 1788270826370,
33
+ "expiresAt": 1788270841370
34
+ },
35
+ "version": 324,
36
+ "updatedAt": 1788270826371,
37
+ "updatedBy": {
38
+ "id": "session:01a05d2e-8a1c-7bba-96fe-6e9eb4d7c42e",
39
+ "name": "main",
40
+ "kind": "main",
41
+ "sessionId": "01a05d2e-8a1c-7bba-96fe-6e9eb4d7c42e"
42
+ }
43
+ },
44
+ "sessions/01a05d2e-8a1c-7bba-96fe-6e9eb4d7c42e": {
45
+ "key": "sessions/01a05d2e-8a1c-7bba-96fe-6e9eb4d7c42e",
46
+ "value": {
47
+ "id": "session:01a05d2e-8a1c-7bba-96fe-6e9eb4d7c42e",
48
+ "name": "PHP-1",
49
+ "label": "PHP-1",
50
+ "kind": "peer",
51
+ "status": "running",
52
+ "runner": "pi",
53
+ "transport": "host",
54
+ "cwd": "/Users/monotykamary/VCS/working-remote/open-source/pi-hypercharm-provider",
55
+ "sessionId": "01a05d2e-8a1c-7bba-96fe-6e9eb4d7c42e",
56
+ "model": "hypercharm/glm-5.3-flash",
57
+ "thinking": "high",
58
+ "startedAt": 1788269765777,
59
+ "updatedAt": 1788270826370,
60
+ "pendingMessages": false,
61
+ "local": false
62
+ },
63
+ "version": 324,
64
+ "updatedAt": 1788270826372,
65
+ "updatedBy": {
66
+ "id": "session:01a05d2e-8a1c-7bba-96fe-6e9eb4d7c42e",
67
+ "name": "main",
68
+ "kind": "main",
69
+ "sessionId": "01a05d2e-8a1c-7bba-96fe-6e9eb4d7c42e"
70
+ }
71
+ },
72
+ "topology/participants/276282166ed2e87cc2e85abfc95904809b77ae8c5dd6d44b4ab841d33b333b8a": {
73
+ "key": "topology/participants/276282166ed2e87cc2e85abfc95904809b77ae8c5dd6d44b4ab841d33b333b8a",
74
+ "value": {
75
+ "format": 1,
76
+ "id": "session:01a05d2e-8a1c-7bba-96fe-6e9eb4d7c42e",
77
+ "kind": "root",
78
+ "rootId": "session:01a05d2e-8a1c-7bba-96fe-6e9eb4d7c42e",
79
+ "ownerHostId": "session:01a05d2e-8a1c-7bba-96fe-6e9eb4d7c42e",
80
+ "ownerIdentityId": "session:01a05d2e-8a1c-7bba-96fe-6e9eb4d7c42e",
81
+ "name": "main",
82
+ "status": "running",
83
+ "runner": "pi",
84
+ "transport": "host",
85
+ "capabilities": [
86
+ "steer",
87
+ "followUp",
88
+ "fabric"
89
+ ],
90
+ "cwd": "/Users/monotykamary/VCS/working-remote/open-source/pi-hypercharm-provider",
91
+ "sessionId": "01a05d2e-8a1c-7bba-96fe-6e9eb4d7c42e",
92
+ "model": "hypercharm/glm-5.3-flash",
93
+ "thinking": "high",
94
+ "startedAt": 1788269765777,
95
+ "updatedAt": 1788270826370,
96
+ "pendingMessages": false,
97
+ "controlProtocol": "v1",
98
+ "label": "PHP-1"
99
+ },
100
+ "version": 324,
101
+ "updatedAt": 1788270826373,
102
+ "updatedBy": {
103
+ "id": "session:01a05d2e-8a1c-7bba-96fe-6e9eb4d7c42e",
104
+ "name": "main",
105
+ "kind": "main",
106
+ "sessionId": "01a05d2e-8a1c-7bba-96fe-6e9eb4d7c42e"
107
+ }
108
+ }
109
+ },
4
110
  "versions": {
5
111
  "topology/hosts/e9758aa8c95305a8d60a8aa2c20fd53195591d479b2460e1273457793d3f1fec": 18,
6
112
  "sessions/019fe2c8-1dd2-7435-9fa1-fa664831df18": 17,
@@ -13,7 +119,11 @@
13
119
  "topology/participants/3195ffccdcff6cb767344b2d1af6c5f3e1e3e4a9906a81129ffaf24352769d2a": 217,
14
120
  "topology/hosts/b98efd3cf26df4c69c6deae014475c4c0c080b1d0e5ef1cb0ebcc8dca7501c85": 388,
15
121
  "sessions/01a00efe-4e23-7b2b-8583-2e8493780899": 387,
16
- "topology/participants/b98efd3cf26df4c69c6deae014475c4c0c080b1d0e5ef1cb0ebcc8dca7501c85": 388
122
+ "topology/participants/b98efd3cf26df4c69c6deae014475c4c0c080b1d0e5ef1cb0ebcc8dca7501c85": 388,
123
+ "topology/peer-seq": 1,
124
+ "topology/hosts/276282166ed2e87cc2e85abfc95904809b77ae8c5dd6d44b4ab841d33b333b8a": 324,
125
+ "sessions/01a05d2e-8a1c-7bba-96fe-6e9eb4d7c42e": 324,
126
+ "topology/participants/276282166ed2e87cc2e85abfc95904809b77ae8c5dd6d44b4ab841d33b333b8a": 324
17
127
  },
18
128
  "tombstoneOrder": [
19
129
  "sessions/019fe2c8-1dd2-7435-9fa1-fa664831df18",
package/README.md CHANGED
@@ -18,6 +18,7 @@ _Hyperoptimized coding models — DeepSeek, GLM, Kimi, Qwen, MiniMax, Gemma, GPT
18
18
  - **31+ AI Models** including DeepSeek V4 Flash/Pro, GLM 5/5.1, Kimi K2.5/K2.6, Qwen3.6/3.7, MiniMax M2.7, Gemma 4, GPT-OSS, and Llama
19
19
  - **DeepSeek Native Thinking** — Uses the `deepseek` thinking format for Charm Hyper requests, with native `reasoning_effort` on models that publish levels
20
20
  - **OpenAI-compatible API** via Charm Hyper's `/v1/chat/completions` endpoint
21
+ - **OAuth Device Flow** — sign in with `/login` under the `hypercharm` provider (independent of the official provider's `hyper` registration)
21
22
  - **Official Catalog Sync** from Charm's typed `/v1/provider` endpoint, matching `@charmland/pi-hyper-provider`
22
23
  - **Reasoning Models** with provider-published on/off states and exact effort levels
23
24
  - **Attachment Support** for models the official catalog marks as attachment-capable
@@ -181,13 +182,14 @@ Non-interactive toggles:
181
182
 
182
183
  The HyperCharm API key can be configured in multiple ways (resolved in this order):
183
184
 
184
- 1. **`auth.json`** (recommended) — Add to `~/.pi/agent/auth.json`:
185
+ 1. **OAuth** — Run `pi`, send `/login`, and pick **HyperCharm**. This uses Hyper's device flow (open the verification URL, enter the code) and stores a refreshable OAuth credential under the `hypercharm` provider fully independent of the official `@charmland/pi-hyper-provider` (`hyper`) registration, so both extensions can be installed side by side.
186
+ 2. **`auth.json`** (recommended for API keys) — Add to `~/.pi/agent/auth.json`:
185
187
  ```json
186
188
  { "hypercharm": { "type": "api_key", "key": "your-api-key" } }
187
189
  ```
188
190
  The `key` field supports literal values, env var names, and shell commands (prefix with `!`). See [pi's auth file docs](https://github.com/badlogic/pi-mono) for details.
189
- 2. **Runtime override** — Use the `--api-key` CLI flag
190
- 3. **Environment variable** — Set `HYPERCHARM_API_KEY`
191
+ 3. **Runtime override** — Use the `--api-key` CLI flag
192
+ 4. **Environment variable** — Set `HYPERCHARM_API_KEY`
191
193
 
192
194
  Get your API key from [hyper.charm.land](https://hyper.charm.land).
193
195
 
@@ -217,7 +219,8 @@ Model metadata matches Charm's official [`@charmland/pi-hyper-provider`](https:/
217
219
  - `thinkingFormat: "deepseek"`, which maps Pi thinking levels onto Hyper's `thinking` envelope
218
220
  - `reasoning_effort` only when the catalog publishes concrete level names
219
221
  - `maxTokensField: "max_tokens"` and `supportsStore: false`
220
- - zero `cacheWrite`, because Hyper reports discounted cached-output pricing, not cache-write cost
222
+ - `cacheRead` from `cost_per_1m_out_cached` (discounted cached-output price) and `cacheWrite` from `cost_per_1m_in_cached`, exactly as the official provider maps them
223
+ - `/v1/credits` balance accepted in either hypercredits (`balance`) or USD (`balance_usd`, converted at the observed 20 hc = $1 rate)
221
224
 
222
225
  `patch.json` is reserved only for a verified provider regression and is currently empty.
223
226
 
package/index.ts CHANGED
@@ -73,11 +73,15 @@
73
73
  * /hypercharm-status reset
74
74
  *
75
75
  * Usage:
76
- * # Option 1: Store in auth.json (recommended)
76
+ * # Option 1: OAuth run pi, send /login, and pick "HyperCharm"
77
+ * # (device flow; provider id "hypercharm", distinct from the official
78
+ * # @charmland/pi-hyper-provider registration "hyper")
79
+ *
80
+ * # Option 2: Store in auth.json
77
81
  * # Add to ~/.pi/agent/auth.json:
78
82
  * # "hypercharm": { "type": "api_key", "key": "your-api-key" }
79
83
  *
80
- * # Option 2: Set as environment variable
84
+ * # Option 3: Set as environment variable
81
85
  * export HYPERCHARM_API_KEY=your-api-key
82
86
  *
83
87
  * # Run pi with the extension
@@ -90,6 +94,7 @@
90
94
 
91
95
  import { clampThinkingLevel, streamOpenAICompletions } from "@earendil-works/pi-ai/compat";
92
96
  import type { AssistantMessageEventStream, SimpleStreamOptions } from "@earendil-works/pi-ai/compat";
97
+ import { USER_AGENT, loginHypercharm, refreshHypercharmToken } from "./oauth";
93
98
  import { getAgentDir, type ExtensionAPI, type ExtensionContext, type ModelRegistry } from "@earendil-works/pi-coding-agent";
94
99
  import modelsData from "./models.json" with { type: "json" };
95
100
  import customModelsData from "./custom-models.json" with { type: "json" };
@@ -302,7 +307,7 @@ function transformApiModel(apiModel: any): JsonModel | null {
302
307
  async function fetchLiveModels(apiKey: string, signal?: AbortSignal): Promise<JsonModel[] | null> {
303
308
  try {
304
309
  const response = await fetch(MODELS_URL, {
305
- headers: { Authorization: `Bearer ${apiKey}` },
310
+ headers: { Authorization: `Bearer ${apiKey}`, "User-Agent": USER_AGENT },
306
311
  signal: signal ? AbortSignal.any([AbortSignal.timeout(LIVE_FETCH_TIMEOUT_MS), signal]) : AbortSignal.timeout(LIVE_FETCH_TIMEOUT_MS),
307
312
  });
308
313
  if (!response.ok) return null;
@@ -634,7 +639,7 @@ let metaFetched = false;
634
639
  async function fetchJsonGet(url: string, apiKey: string, signal?: AbortSignal): Promise<any | null> {
635
640
  try {
636
641
  const response = await fetch(url, {
637
- headers: { Authorization: `Bearer ${apiKey}` },
642
+ headers: { Authorization: `Bearer ${apiKey}`, "User-Agent": USER_AGENT },
638
643
  signal: signal
639
644
  ? AbortSignal.any([AbortSignal.timeout(ACCOUNT_FETCH_TIMEOUT_MS), signal])
640
645
  : AbortSignal.timeout(ACCOUNT_FETCH_TIMEOUT_MS),
@@ -656,9 +661,12 @@ function refreshCredits(apiKey: string | undefined, signal: AbortSignal | undefi
656
661
  try {
657
662
  const data = await fetchJsonGet(`${BASE_URL}/credits`, apiKey, signal);
658
663
  if (data === null) return;
659
- const balance = data?.balance;
660
- if (typeof balance === "number" && Number.isFinite(balance)) {
661
- account.balance = balance;
664
+ // /credits can report hypercredits ("balance") or USD ("balance_usd",
665
+ // USD-billed accounts). Handle both at the observed 20 hc = $1 rate so a
666
+ // server-side unit switch can never silently freeze the balance readout.
667
+ const rawBalance = typeof data?.balance === "number" ? data.balance : typeof data?.balance_usd === "number" ? data.balance_usd * 20 : undefined;
668
+ if (typeof rawBalance === "number" && Number.isFinite(rawBalance)) {
669
+ account.balance = rawBalance;
662
670
  }
663
671
  } finally {
664
672
  creditsInFlight = null;
@@ -1007,6 +1015,12 @@ function makeProviderConfig(models: JsonModel[] = currentModels) {
1007
1015
  api: "hypercharm",
1008
1016
  models,
1009
1017
  streamSimple: streamHypercharm,
1018
+ oauth: {
1019
+ name: "HyperCharm",
1020
+ login: (callbacks) => loginHypercharm(callbacks),
1021
+ refreshToken: (credentials, signal) => refreshHypercharmToken(credentials, signal),
1022
+ getApiKey: (credentials) => String(credentials.access ?? ""),
1023
+ },
1010
1024
  };
1011
1025
  }
1012
1026
 
package/oauth.ts ADDED
@@ -0,0 +1,192 @@
1
+ /**
2
+ * HyperCharm OAuth device flow (provider id "hypercharm").
3
+ *
4
+ * Mirrors the device flow from @charmland/pi-hyper-provider against
5
+ * hyper.charm.land, but registers under our own provider id ("hypercharm",
6
+ * oauth display name "HyperCharm") so it never collides with the official
7
+ * provider's "hyper" registration when both extensions are installed.
8
+ *
9
+ * Note: both providers register the device under the same name
10
+ * (`Pi (<hostname>)`), so /v1/devices entries are indistinguishable per host
11
+ * when the official provider is also signed in — the status widget's OAuth
12
+ * days-left readout may match either session.
13
+ */
14
+
15
+ import { hostname } from "os";
16
+ import type { OAuthCredentials, OAuthDeviceCodeInfo, OAuthLoginCallbacks } from "@earendil-works/pi-ai";
17
+ import pkg from "./package.json" with { type: "json" };
18
+
19
+ const BASE_URL = "https://hyper.charm.land";
20
+ const OAUTH_FETCH_TIMEOUT_MS = 30_000;
21
+ const DEFAULT_POLL_INTERVAL_SECONDS = 5;
22
+ const MIN_POLL_INTERVAL_MS = 1_000;
23
+ const SLOW_DOWN_INCREMENT_MS = 5_000;
24
+ const TOKEN_EXPIRY_BUFFER_MS = 30_000;
25
+ const CANCEL_MESSAGE = "Login cancelled";
26
+ const TIMEOUT_MESSAGE = "HyperCharm device flow timed out";
27
+
28
+ export const USER_AGENT = `pi-hypercharm-provider/${(pkg as { version?: string }).version ?? "0.0.0"}`;
29
+
30
+ function deviceName(): string {
31
+ const host = hostname();
32
+ return host ? `Pi (${host})` : "Pi";
33
+ }
34
+
35
+ function hyperHeaders(extra: Record<string, string> = {}): Record<string, string> {
36
+ return { "Content-Type": "application/json", "User-Agent": USER_AGENT, ...extra };
37
+ }
38
+
39
+ async function oauthFetch(url: string, init: RequestInit, signal?: AbortSignal): Promise<Response> {
40
+ const timeout = AbortSignal.timeout(OAUTH_FETCH_TIMEOUT_MS);
41
+ return fetch(url, { ...init, signal: signal ? AbortSignal.any([signal, timeout]) : timeout });
42
+ }
43
+
44
+ async function abortableSleep(ms: number, signal?: AbortSignal): Promise<void> {
45
+ if (signal?.aborted) throw new Error(CANCEL_MESSAGE);
46
+ return new Promise((resolve, reject) => {
47
+ const timer = setTimeout(resolve, ms);
48
+ signal?.addEventListener(
49
+ "abort",
50
+ () => {
51
+ clearTimeout(timer);
52
+ reject(new Error(CANCEL_MESSAGE));
53
+ },
54
+ { once: true },
55
+ );
56
+ });
57
+ }
58
+
59
+ interface DeviceAuthResponse {
60
+ device_code: string;
61
+ expires_in: number;
62
+ user_code: string;
63
+ verification_url: string;
64
+ interval?: number;
65
+ }
66
+
67
+ type DevicePollResponse =
68
+ | { refresh_token: string; team_id?: string; team_name?: string; user_id?: string }
69
+ | { error: string; error_description?: string };
70
+
71
+ interface TokenExchangeResponse {
72
+ access_token: string;
73
+ refresh_token: string;
74
+ expires_in?: number;
75
+ expires_at?: number;
76
+ }
77
+
78
+ async function initiateDeviceAuth(signal?: AbortSignal): Promise<DeviceAuthResponse> {
79
+ const res = await oauthFetch(`${BASE_URL}/device/auth`, {
80
+ method: "POST",
81
+ headers: hyperHeaders(),
82
+ body: JSON.stringify({ device_name: deviceName() }),
83
+ signal,
84
+ });
85
+ if (!res.ok) throw new Error(`Hyper device auth failed: HTTP ${res.status}`);
86
+ const payload = await res.json();
87
+ if (
88
+ typeof payload?.device_code !== "string" ||
89
+ typeof payload?.user_code !== "string" ||
90
+ typeof payload?.verification_url !== "string" ||
91
+ typeof payload?.expires_in !== "number"
92
+ ) {
93
+ throw new Error("Hyper device auth response is invalid");
94
+ }
95
+ return payload;
96
+ }
97
+
98
+ async function pollDeviceAuth(
99
+ auth: DeviceAuthResponse,
100
+ signal?: AbortSignal,
101
+ ): Promise<{ refresh_token: string; team_name?: string }> {
102
+ const deadline = Date.now() + auth.expires_in * 1000;
103
+ let intervalMs = Math.max(MIN_POLL_INTERVAL_MS, (auth.interval ?? DEFAULT_POLL_INTERVAL_SECONDS) * 1000);
104
+
105
+ // Server-specified cadence starts after the first wait (device codes are
106
+ // never ready immediately; polling sooner only earns slow_down responses).
107
+ await abortableSleep(Math.min(intervalMs, Math.max(0, deadline - Date.now())), signal);
108
+
109
+ while (Date.now() < deadline) {
110
+ if (signal?.aborted) throw new Error(CANCEL_MESSAGE);
111
+ const res = await fetch(`${BASE_URL}/device/auth/${encodeURIComponent(auth.device_code)}`, {
112
+ headers: hyperHeaders(),
113
+ signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(OAUTH_FETCH_TIMEOUT_MS)]) : AbortSignal.timeout(OAUTH_FETCH_TIMEOUT_MS),
114
+ });
115
+ if (!res.ok) throw new Error(`Hyper device poll failed: HTTP ${res.status}`);
116
+ const payload = await res.json();
117
+
118
+ if (typeof payload?.refresh_token === "string") {
119
+ return { refresh_token: payload.refresh_token, team_name: payload.team_name };
120
+ }
121
+ if (payload?.error === "authorization_pending") {
122
+ // fall through to the interval sleep
123
+ } else if (payload?.error === "slow_down") {
124
+ intervalMs = Math.max(MIN_POLL_INTERVAL_MS, intervalMs + SLOW_DOWN_INCREMENT_MS);
125
+ } else {
126
+ throw new Error(`Hyper device authorization failed: ${payload?.error_description ?? payload?.error ?? "unknown error"}`);
127
+ }
128
+
129
+ const remainingMs = deadline - Date.now();
130
+ if (remainingMs <= 0) break;
131
+ await abortableSleep(Math.min(intervalMs, remainingMs), signal);
132
+ }
133
+ throw new Error(TIMEOUT_MESSAGE);
134
+ }
135
+
136
+ async function exchangeRefreshToken(refreshToken: string, signal?: AbortSignal): Promise<TokenExchangeResponse> {
137
+ const res = await fetch(`${BASE_URL}/token/exchange`, {
138
+ method: "POST",
139
+ headers: hyperHeaders(),
140
+ body: JSON.stringify({ refresh_token: refreshToken }),
141
+ signal,
142
+ });
143
+ if (!res.ok) {
144
+ const err = new Error(`Hyper token exchange failed: HTTP ${res.status}`);
145
+ if (res.status === 401) {
146
+ throw new Error("Your Hyper session is no longer valid. Run /login and re-authenticate with HyperCharm.", { cause: err });
147
+ }
148
+ throw err;
149
+ }
150
+ const payload = (await res.json()) as TokenExchangeResponse;
151
+ if (typeof payload?.access_token !== "string") throw new Error("Hyper token exchange response is invalid");
152
+ return payload;
153
+ }
154
+
155
+ function tokenExpiresAtMs(token: TokenExchangeResponse): number {
156
+ const now = Date.now();
157
+ const expiresAt = typeof token.expires_in === "number" ? now + token.expires_in * 1000 : Number(token.expires_at) * 1000;
158
+ if (!Number.isFinite(expiresAt) || expiresAt <= now) {
159
+ throw new Error("Hyper token exchange response contains an expired token expiry");
160
+ }
161
+ return expiresAt - Math.min(TOKEN_EXPIRY_BUFFER_MS, Math.floor((expiresAt - now) / 2));
162
+ }
163
+
164
+ function toCredentials(token: TokenExchangeResponse, fallbackRefresh: string, teamName?: string): OAuthCredentials {
165
+ return {
166
+ type: "oauth",
167
+ refresh: token.refresh_token || fallbackRefresh,
168
+ access: token.access_token,
169
+ expires: tokenExpiresAtMs(token),
170
+ ...(teamName ? { teamName } : {}),
171
+ };
172
+ }
173
+
174
+ /** Device-flow login for the "hypercharm" provider (/login flow). */
175
+ export async function loginHypercharm(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
176
+ const deviceAuth = await initiateDeviceAuth(callbacks.signal);
177
+ callbacks.onDeviceCode({
178
+ userCode: deviceAuth.user_code,
179
+ verificationUri: deviceAuth.verification_url,
180
+ intervalSeconds: deviceAuth.interval,
181
+ expiresInSeconds: deviceAuth.expires_in,
182
+ });
183
+ const devicePoll = await pollDeviceAuth(deviceAuth, callbacks.signal);
184
+ const token = await exchangeRefreshToken(devicePoll.refresh_token, callbacks.signal);
185
+ return toCredentials(token, devicePoll.refresh_token, devicePoll.team_name);
186
+ }
187
+
188
+ /** Refresh an expired HyperCharm OAuth credential via the token exchange endpoint. */
189
+ export async function refreshHypercharmToken(credential: OAuthCredentials, signal?: AbortSignal): Promise<OAuthCredentials> {
190
+ const teamName = typeof credential.teamName === "string" && credential.teamName.trim() ? credential.teamName : undefined;
191
+ return toCredentials(await exchangeRefreshToken(credential.refresh, signal), credential.refresh, teamName);
192
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-hypercharm-provider",
3
- "version": "1.3.8",
3
+ "version": "1.3.9",
4
4
  "description": "HyperCharm provider extension for pi - Access DeepSeek, GLM, Kimi, Qwen, MiniMax, Gemma, and GPT-OSS models through the Charm Hyper API",
5
5
  "type": "module",
6
6
  "main": "index.ts",