pi-provider-litellm 3.0.0 → 3.0.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-provider-litellm",
3
- "version": "3.0.0",
3
+ "version": "3.0.1",
4
4
  "description": "LiteLLM proxy provider extension for Pi",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/index.ts CHANGED
@@ -1125,16 +1125,25 @@ async function refreshLiteLLM(
1125
1125
  return { ...credentials, access, expires: tokenExpiresAt(access, EXPIRE_TOKEN_IMMEDIATELY) };
1126
1126
  }
1127
1127
 
1128
+ async function configuredBaseUrl(
1129
+ definition: ProviderDefinition,
1130
+ ctx: { env(name: string): Promise<string | undefined> },
1131
+ credential?: ApiKeyCredential,
1132
+ ): Promise<string | undefined> {
1133
+ return (
1134
+ cleanConfig(credential?.env?.[ENV_BASE_URL]) ??
1135
+ cleanConfig(definition.baseUrl) ??
1136
+ (definition.useDefaultEnv ? cleanConfig(await ctx.env(ENV_BASE_URL)) : undefined)
1137
+ );
1138
+ }
1139
+
1128
1140
  async function resolveApiKeyAuth(
1129
1141
  definition: ProviderDefinition,
1130
1142
  ctx: { env(name: string): Promise<string | undefined> },
1131
1143
  credential?: ApiKeyCredential,
1132
1144
  executeHelpers = true,
1133
1145
  ) {
1134
- const baseUrl =
1135
- cleanConfig(credential?.env?.[ENV_BASE_URL]) ??
1136
- cleanConfig(definition.baseUrl) ??
1137
- (definition.useDefaultEnv ? cleanConfig(await ctx.env(ENV_BASE_URL)) : undefined);
1146
+ const baseUrl = await configuredBaseUrl(definition, ctx, credential);
1138
1147
  const stored = credential?.key
1139
1148
  ? resolveConfigValue(credential.key, { executeCommands: executeHelpers })?.trim()
1140
1149
  : undefined;
@@ -1209,6 +1218,7 @@ function createProviderAuth(
1209
1218
  definition: ProviderDefinition,
1210
1219
  clearOAuthRuntimeRoot?: () => void,
1211
1220
  onLogin?: () => void,
1221
+ oauthRuntimeRoot?: () => { apiKey: string; root: string } | undefined,
1212
1222
  ): ProviderAuth {
1213
1223
  function completeLogin<T extends Credential>(credential: T): T {
1214
1224
  onLogin?.();
@@ -1255,6 +1265,34 @@ function createProviderAuth(
1255
1265
  return fallback ? { type: "api_key", source: fallback } : undefined;
1256
1266
  },
1257
1267
  resolve: async ({ ctx, credential }) => {
1268
+ // Pi re-resolves auth through this api-key path whenever a caller passes an explicit
1269
+ // apiKey override — compaction and summarization both hand back the key they just
1270
+ // resolved — which bypasses the stored OAuth credential carrying the base URL. The
1271
+ // api-key path exports its root as env so it survives that round trip, but OAuth's
1272
+ // toAuth has no env to export, leaving an SSO session no record of its root. Read it
1273
+ // back from auth.json, the same fallback check() and seedModels() use, and export it
1274
+ // so the request carries the root rather than depending on in-memory state. auth.json is
1275
+ // shared, though: another Pi process logging in replaces the stored token while this one
1276
+ // still holds its own, so fall back to the root this process resolved for that exact
1277
+ // token. Only when nothing configures a root at all: a base URL that is set still
1278
+ // resolves, and still fails, on its own terms rather than silently rerouting.
1279
+ if (credential?.key && !(await configuredBaseUrl(definition, ctx, credential))) {
1280
+ const stored = readStoredCredential(definition.name, join(getAgentDir(), "auth.json"));
1281
+ const remembered = oauthRuntimeRoot?.();
1282
+ const root =
1283
+ stored?.type === "oauth" && stored.access === credential.key
1284
+ ? requireCredentialRoot(resolveCredentialRoot(definition, stored), definition.name)
1285
+ : remembered?.apiKey === credential.key
1286
+ ? remembered.root
1287
+ : undefined;
1288
+ if (root) {
1289
+ return {
1290
+ auth: { apiKey: credential.key, headers: resolveHeaders(definition), baseUrl: root },
1291
+ env: { [ENV_BASE_URL]: root },
1292
+ source: "OAuth",
1293
+ };
1294
+ }
1295
+ }
1258
1296
  clearOAuthRuntimeRoot?.();
1259
1297
  return resolveApiKeyAuth(definition, ctx, credential);
1260
1298
  },
@@ -1867,6 +1905,7 @@ export default async function (pi: ExtensionAPI): Promise<void> {
1867
1905
  definition,
1868
1906
  () => oauthRuntimeRoots.delete(definition.name),
1869
1907
  definition.name === PROVIDER_NAME ? resumeMcpDiscovery : undefined,
1908
+ () => oauthRuntimeRoots.get(definition.name),
1870
1909
  );
1871
1910
  if (auth.oauth) {
1872
1911
  if (definition.name === PROVIDER_NAME) {
@@ -169,13 +169,12 @@ function explicitLimit(value: number | undefined): number | undefined {
169
169
 
170
170
  const EXTENDED_LEVELS = THINKING_LEVEL_DEFINITIONS.map(([level]) => level);
171
171
 
172
- const LITELLM_LEVEL_FLAGS = {
173
- off: "supports_none_reasoning_effort",
174
- minimal: "supports_minimal_reasoning_effort",
175
- low: "supports_low_reasoning_effort",
176
- xhigh: "supports_xhigh_reasoning_effort",
177
- max: "supports_max_reasoning_effort",
178
- } as const;
172
+ // Derived from the level table rather than restated: a second hand-written copy
173
+ // silently dropped `supports_medium_reasoning_effort` and
174
+ // `supports_high_reasoning_effort`, so those router flags were never read.
175
+ const LITELLM_LEVEL_FLAGS = Object.fromEntries(THINKING_LEVEL_DEFINITIONS.map(([level, , flag]) => [level, flag])) as {
176
+ [Definition in (typeof THINKING_LEVEL_DEFINITIONS)[number] as Definition[0]]: Definition[2];
177
+ };
179
178
 
180
179
  function normalizeEffort(level: string): (typeof EXTENDED_LEVELS)[number] | undefined {
181
180
  const normalized = level === "none" ? "off" : level;
@@ -273,9 +272,10 @@ export const NO_TRANSMISSIBLE_LEVELS = {
273
272
 
274
273
  // Efforts the Responses API accepts. pi-ai passes an unmapped level through
275
274
  // verbatim and reads `thinkingLevelMap.off` as the disable value, so a Chat-shaped
276
- // map would emit `off` or `max` as an effort. `none` is the disable spelling —
277
- // pi-ai's own `openai/gpt-5.5` entry maps `off` to it.
278
- const RESPONSES_EFFORTS = new Set(["none", "minimal", "low", "medium", "high", "xhigh"]);
275
+ // map would emit `off` as an effort. `none` is the disable spelling — pi-ai's own
276
+ // `openai/gpt-5.5` entry maps `off` to it, and its `openai/gpt-5.6-*` entries map
277
+ // `max` to itself, so `max` is a real Responses effort and not a Chat-only value.
278
+ const RESPONSES_EFFORTS = new Set(["none", "minimal", "low", "medium", "high", "xhigh", "max"]);
279
279
 
280
280
  // A level map is only meaningful next to the compat that serializes it, and the
281
281
  // two used to travel separately: five call sites each decided whether to copy
@@ -5,8 +5,8 @@ export const THINKING_LEVEL_DEFINITIONS = [
5
5
  ["off", "none", "supports_none_reasoning_effort"],
6
6
  ["minimal", "minimal", "supports_minimal_reasoning_effort"],
7
7
  ["low", "low", "supports_low_reasoning_effort"],
8
- ["medium", "medium", undefined],
9
- ["high", "high", undefined],
8
+ ["medium", "medium", "supports_medium_reasoning_effort"],
9
+ ["high", "high", "supports_high_reasoning_effort"],
10
10
  ["xhigh", "xhigh", "supports_xhigh_reasoning_effort"],
11
11
  ["max", "max", "supports_max_reasoning_effort"],
12
12
  ] as const;
package/src/types.ts CHANGED
@@ -92,6 +92,8 @@ export interface ModelInfoEntry {
92
92
  supports_none_reasoning_effort?: boolean;
93
93
  supports_minimal_reasoning_effort?: boolean;
94
94
  supports_low_reasoning_effort?: boolean;
95
+ supports_medium_reasoning_effort?: boolean;
96
+ supports_high_reasoning_effort?: boolean;
95
97
  supports_xhigh_reasoning_effort?: boolean;
96
98
  supports_max_reasoning_effort?: boolean;
97
99
  supports_vision?: boolean;