@oh-my-pi/pi-ai 18.2.4 → 18.2.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,23 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.2.6] - 2026-09-18
6
+
7
+ ### Fixed
8
+
9
+ - Fixed Anthropic prompt-cache head re-baselining on every memory recall refresh: the system breakpoint now anchors on the last stable segment instead of the volatile recall suffix, and the stable-system fingerprint ignores recall blocks, so a recall refresh re-bills only the suffix instead of the whole tools+system head.
10
+ - Fixed auth-broker client config resolution failing silently on Windows when reading the token file or `config.yml`; reads now use `node:fs` instead of `Bun.file`.
11
+
12
+ ## [18.2.5] - 2026-09-17
13
+
14
+ ### Added
15
+
16
+ - Added support for templating and custom base and authentication URLs in OAuth flows.
17
+
18
+ ### Fixed
19
+
20
+ - Fixed Anthropic prompt-cache breakpoints stalling when conversations include mid-conversation tool changes, preventing growing message tails from being unnecessarily re-billed as uncached input.
21
+
5
22
  ## [18.2.4] - 2026-09-17
6
23
 
7
24
  ### Added
@@ -48,7 +48,7 @@ export declare function postTokenRequest(request: CompiledOAuthRequest, standard
48
48
  response: Response;
49
49
  }>;
50
50
  /** Bearer GET declared by `userinfo`; failures leave identity fields unset. */
51
- export declare function applyUserinfo(userinfo: CompiledUserinfo | undefined, credentials: OAuthCredentials, context: RequestContext): Promise<OAuthCredentials>;
51
+ export declare function applyUserinfo(userinfo: CompiledUserinfo | undefined, credentials: OAuthCredentials, context: RequestContext, vars?: TemplateVars): Promise<OAuthCredentials>;
52
52
  /** Runs the rule's after-exchange / after-refresh hook, if declared. */
53
53
  export declare function applyAfterExchange(hook: string | undefined, credentials: OAuthCredentials, context: ExchangeContext): Promise<OAuthCredentials>;
54
54
  export declare function describeError(error: unknown): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-ai",
3
- "version": "18.2.4",
3
+ "version": "18.2.6",
4
4
  "description": "Unified LLM API with automatic model discovery and provider configuration",
5
5
  "keywords": [
6
6
  "ai",
@@ -128,11 +128,11 @@
128
128
  "fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
129
129
  },
130
130
  "dependencies": {
131
- "@oh-my-pi/omptype": "18.2.4",
132
- "@oh-my-pi/pi-catalog": "18.2.4",
133
- "@oh-my-pi/pi-natives": "18.2.4",
134
- "@oh-my-pi/pi-utils": "18.2.4",
135
- "@oh-my-pi/pi-wire": "18.2.4"
131
+ "@oh-my-pi/omptype": "18.2.6",
132
+ "@oh-my-pi/pi-catalog": "18.2.6",
133
+ "@oh-my-pi/pi-natives": "18.2.6",
134
+ "@oh-my-pi/pi-utils": "18.2.6",
135
+ "@oh-my-pi/pi-wire": "18.2.6"
136
136
  },
137
137
  "devDependencies": {
138
138
  "@types/bun": "^1.3.14"
@@ -4,6 +4,7 @@
4
4
  * token file → local SQLite) in one place so build-time tooling sees the same
5
5
  * credentials as the TUI.
6
6
  */
7
+ import * as fs from "node:fs/promises";
7
8
  import * as path from "node:path";
8
9
  import {
9
10
  $envExact,
@@ -60,7 +61,7 @@ async function defaultResolveConfigValue(config: string): Promise<string | undef
60
61
 
61
62
  async function readTokenFile(): Promise<string | null> {
62
63
  try {
63
- const raw = await Bun.file(getAuthBrokerTokenFilePath()).text();
64
+ const raw = await fs.readFile(getAuthBrokerTokenFilePath(), "utf8");
64
65
  const trimmed = raw.trim();
65
66
  return trimmed.length > 0 ? trimmed : null;
66
67
  } catch (err) {
@@ -99,7 +100,7 @@ async function readConfigYaml(agentDir: string): Promise<ConfigSnapshot> {
99
100
  for (const filename of MAIN_CONFIG_FILENAMES) {
100
101
  const configPath = path.join(agentDir, filename);
101
102
  try {
102
- const raw = await Bun.file(configPath).text();
103
+ const raw = await fs.readFile(configPath, "utf8");
103
104
  const parsed = YAML.parse(raw);
104
105
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
105
106
  const record = parsed as Record<string, unknown>;
@@ -121,7 +122,8 @@ export async function loadAuthBrokerAccountPool(): Promise<AuthBrokerAccountPool
121
122
 
122
123
  let parsed: unknown;
123
124
  try {
124
- parsed = await Bun.file(filePath).json();
125
+ const raw = await fs.readFile(filePath, "utf8");
126
+ parsed = JSON.parse(raw.charCodeAt(0) === 0xfeff ? raw.slice(1) : raw);
125
127
  } catch (error) {
126
128
  throw new AIError.ConfigurationError(`Unable to read OMP_AUTH_BROKER_ACCOUNT_POOL_FILE at ${filePath}`, {
127
129
  cause: error,
@@ -1563,7 +1563,9 @@ async function* iterateAnthropicEvents(
1563
1563
  let sawMessageStart = false;
1564
1564
  let sawMessageEnd = false;
1565
1565
 
1566
- for await (const sse of readSseEvents(response.body, signal)) {
1566
+ // Capture `raw` only when the diagnostic observer exists; otherwise the
1567
+ // per-frame wire-line array is pure token-path garbage.
1568
+ for await (const sse of readSseEvents(response.body, signal, onSseEvent ? { captureRaw: true } : undefined)) {
1567
1569
  notifyRawSseEvent(onSseEvent, sse);
1568
1570
  if (sse.event === "error") {
1569
1571
  throw createAnthropicSseStreamError(sse.data);
@@ -3952,8 +3954,17 @@ function applyPromptCaching(params: MessageCreateParamsStreaming, cacheControl?:
3952
3954
  const messageEnd = hasTrailingAssistantPad ? trailingIndex - 1 : trailingIndex;
3953
3955
 
3954
3956
  // A breakpoint caches every preceding byte, not only the decorated message.
3955
- // Once per-call or turn-scoped content appears, no later message can anchor a
3956
- // prefix reusable by the next request.
3957
+ // A per-call or turn-scoped message is rebuilt next request, so a prefix
3958
+ // spanning it cannot match — but only at its own position. Messages after
3959
+ // the mark are ordinary persisted history with stable bytes, so a later
3960
+ // breakpoint still matches everything after the mark. The cost is bounded
3961
+ // to re-billing the marked bytes themselves, not the growing tail.
3962
+ // Hence two anchors, not a truncation: the newest candidate at or before
3963
+ // the first per-call/turn-scoped message (when one exists) pins the
3964
+ // reusable prefix behind the mark, and the rolling tail candidates pin
3965
+ // the suffix after it. Turn-scoped `clear_at` messages are absent next
3966
+ // request, so they still truncate the decimation range (ordinals would
3967
+ // shift), but per-call marks no longer freeze the tail.
3957
3968
  let stableMessageEnd = messageEnd;
3958
3969
  for (let index = 0; index <= messageEnd; index++) {
3959
3970
  const message = params.messages[index];
@@ -3978,18 +3989,39 @@ function applyPromptCaching(params: MessageCreateParamsStreaming, cacheControl?:
3978
3989
  // Stable historical decimation checkpoint every 15 user turns (15th, 30th, 45th...)
3979
3990
  const decimationIndices = userIndices.filter((_, ordinal) => (ordinal + 1) % ANTHROPIC_DECIMATION_INTERVAL === 0);
3980
3991
 
3981
- // Collect up to 2 trailing candidates from the reusable prefix.
3992
+ // Collect up to 2 trailing candidates from the message tail, skipping
3993
+ // per-call messages, turn-scoped messages, and mid-conversation
3994
+ // tool-control messages. A per-call tail candidate is rebuilt next request
3995
+ // (fresh timestamps on appended probes, fresh redaction bytes), so a
3996
+ // breakpoint on it cannot match — it would spend the tail anchor on bytes
3997
+ // that never repeat while the persisted history behind it goes uncached.
3998
+ // Turn-scoped messages are absent next request for the same reason, and
3999
+ // tool controls reject cache_control outright. The walk starts at the
4000
+ // message tail (not the truncated prefix end) so the anchor advances every
4001
+ // turn; the sub-prefix candidate below covers the reusable region behind
4002
+ // a mark.
3982
4003
  const trailingCandidates: number[] = [];
3983
- for (let index = stableMessageEnd; index >= 0 && trailingCandidates.length < 2; index--) {
4004
+ for (let index = messageEnd; index >= 0 && trailingCandidates.length < 2; index--) {
3984
4005
  const message = params.messages[index];
3985
- if (!message) continue;
4006
+ if (!message || message.clear_at === "next_user_message" || isPerCallContextMessage(message)) continue;
4007
+ if (
4008
+ message.role === "system" &&
4009
+ typeof message.content !== "string" &&
4010
+ Array.isArray(message.content) &&
4011
+ message.content.length > 0 &&
4012
+ message.content.every(block => block.type === "tool_addition" || block.type === "tool_removal")
4013
+ ) {
4014
+ continue;
4015
+ }
3986
4016
  trailingCandidates.push(index);
3987
4017
  }
3988
-
3989
4018
  // Prioritize:
3990
4019
  // 1. Most recent trailing message
3991
4020
  // 2. Latest decimation checkpoints (newest first) to maintain stable long-context anchors
3992
- // 3. Second trailing message
4021
+ // 3. Newest message at or before the first per-call/turn-scoped mark, so a
4022
+ // volatile interior message costs only its own re-billed bytes instead
4023
+ // of invalidating the whole reusable prefix behind it
4024
+ // 4. Second trailing message
3993
4025
  const candidateIndices: number[] = [];
3994
4026
  if (trailingCandidates.length > 0) {
3995
4027
  candidateIndices.push(trailingCandidates[0]);
@@ -3999,6 +4031,9 @@ function applyPromptCaching(params: MessageCreateParamsStreaming, cacheControl?:
3999
4031
  candidateIndices.push(decimationIndices[i]);
4000
4032
  }
4001
4033
  }
4034
+ if (stableMessageEnd < messageEnd && stableMessageEnd >= 0 && !candidateIndices.includes(stableMessageEnd)) {
4035
+ candidateIndices.push(stableMessageEnd);
4036
+ }
4002
4037
  for (const index of trailingCandidates) {
4003
4038
  if (!candidateIndices.includes(index)) {
4004
4039
  candidateIndices.push(index);
@@ -4017,17 +4052,57 @@ function applyPromptCaching(params: MessageCreateParamsStreaming, cacheControl?:
4017
4052
  }
4018
4053
  }
4019
4054
 
4055
+ /**
4056
+ * Trailing system-prompt segments carrying per-turn volatile content (memory
4057
+ * recall blocks). They are rendered by the coding agent as their own
4058
+ * `systemPrompt` array elements and appended last, so on the wire they
4059
+ * normally form a volatile suffix after the stable prefix. The system cache
4060
+ * breakpoint anchors on the last stable segment instead of the array tail, so
4061
+ * a recall refresh re-bills only the suffix and the message tail for one turn
4062
+ * while the tools+stable-system prefix stays a cache hit. The fingerprint in
4063
+ * `planStableAnthropicSystem` is scoped the same way, so a recall-only change
4064
+ * no longer resets the tool/control baselines either.
4065
+ *
4066
+ * Only a genuinely trailing volatile run counts: a `before_agent_start`
4067
+ * extension override may append a stable policy block after the staged recall
4068
+ * block, and that block must stay fingerprinted stable (a change to it has to
4069
+ * re-baseline). A volatile block stranded mid-array still poisons the prefix
4070
+ * at its position — prefix caching is positional, so no classification can
4071
+ * save the bytes after it — but the stable tail is at least fingerprinted
4072
+ * instead of silently excluded.
4073
+ *
4074
+ * Detection is by our own markup, not model identity: recall blocks always
4075
+ * open with `<memories>`. Stable segments containing recalled text elsewhere
4076
+ * (e.g. quoted in conversation) are unaffected — only a leading tag counts.
4077
+ */
4078
+ const VOLATILE_SYSTEM_SEGMENT_MARKERS = ["<memories>"];
4079
+
4080
+ function stableSystemSuffixStart(systemBlocks: readonly AnthropicSystemBlock[]): number {
4081
+ let start = systemBlocks.length;
4082
+ while (start > 0) {
4083
+ const text = systemBlocks[start - 1]?.text ?? "";
4084
+ if (!VOLATILE_SYSTEM_SEGMENT_MARKERS.some(marker => text.startsWith(marker))) break;
4085
+ start--;
4086
+ }
4087
+ return start;
4088
+ }
4089
+
4020
4090
  /**
4021
4091
  * Anchor cache_control on the stable request head — the last (non-deferred)
4022
- * tool definition and the last system block. The canonical cache order is
4023
- * tools → system → messages, so a breakpoint on the final system block caches
4024
- * the entire tools+system prefix, and the extra tool breakpoint keeps the tool
4092
+ * tool definition and the last stable system block. The canonical cache order is
4093
+ * tools → system → messages, so a breakpoint on the final stable system block caches
4094
+ * the entire tools+stable-system prefix, and the extra tool breakpoint keeps the tool
4025
4095
  * definitions cached even when the system text changes. This guarantees the
4026
4096
  * large, unchanging head is a cache hit on every turn regardless of how the
4027
4097
  * message tail churns — the breakpoint placement first-party Anthropic clients
4028
4098
  * (Claude Code, Pi) use. Without it, the general API-key path anchors only the
4029
4099
  * moving message tail, so tail churn re-writes the whole head uncached.
4030
4100
  *
4101
+ * Volatile trailing segments (memory recall) sit after the breakpoint, so a
4102
+ * recall refresh re-bills only the suffix and the tail for one turn instead of
4103
+ * the whole head. When every system block is volatile there is no stable
4104
+ * boundary and the breakpoint stays on the array tail (previous behavior).
4105
+ *
4031
4106
  * Anthropic allows at most 4 cache breakpoints per request. At most one is
4032
4107
  * spent on tools and one on system here, leaving the remaining budget for
4033
4108
  * the message tail and historical decimation checkpoints in `applyPromptCaching`.
@@ -4063,9 +4138,28 @@ function applyHeadCaching(
4063
4138
  }
4064
4139
  }
4065
4140
 
4066
- if (systemBlocks && systemBlocks.length > 0 && !systemBlocks.some(block => block.cache_control != null)) {
4067
- const lastBlock = systemBlocks[systemBlocks.length - 1];
4068
- if (lastBlock) lastBlock.cache_control = cloneAnthropicCacheControl(cacheControl);
4141
+ if (systemBlocks && systemBlocks.length > 0) {
4142
+ // Anchor on the last stable block so a volatile recall suffix refresh
4143
+ // re-bills only the suffix, not the whole head. The skip-if-decorated
4144
+ // check applies only when there is no volatile suffix (previous
4145
+ // behavior): with a suffix present the boundary anchor is added
4146
+ // whenever the anchor block itself lacks a breakpoint, even if the
4147
+ // OAuth path pre-decorated its identity block — otherwise the only
4148
+ // system breakpoint sits before the stable prompt and a recall
4149
+ // refresh re-bills it. The message budget in `applyPromptCaching`
4150
+ // shrinks accordingly (4 minus head breakpoints). All-volatile falls
4151
+ // back to tail anchoring (previous behavior).
4152
+ const suffixStart = stableSystemSuffixStart(systemBlocks);
4153
+ if (suffixStart === systemBlocks.length) {
4154
+ if (!systemBlocks.some(block => block.cache_control != null)) {
4155
+ const lastBlock = systemBlocks[systemBlocks.length - 1];
4156
+ if (lastBlock) lastBlock.cache_control = cloneAnthropicCacheControl(cacheControl);
4157
+ }
4158
+ } else {
4159
+ const anchorIndex = suffixStart === 0 ? systemBlocks.length - 1 : suffixStart - 1;
4160
+ const anchor = systemBlocks[anchorIndex];
4161
+ if (anchor && anchor.cache_control == null) anchor.cache_control = cloneAnthropicCacheControl(cacheControl);
4162
+ }
4069
4163
  }
4070
4164
  }
4071
4165
 
@@ -4149,11 +4243,15 @@ function getAnthropicControlState(
4149
4243
  ): AnthropicControlState | undefined {
4150
4244
  if (!state) return undefined;
4151
4245
  const root = messages[0];
4246
+ // Key on the stable system prefix, not the full array: a volatile recall
4247
+ // suffix refresh must resolve the same baseline or the declared-tool,
4248
+ // effort, and control-transition state it preserves is lost with it.
4249
+ const stablePrefix = system?.slice(0, stableSystemSuffixStart(system)) ?? null;
4152
4250
  const fingerprint = String(
4153
4251
  Bun.hash(
4154
4252
  JSON.stringify([
4155
4253
  sessionId ?? "",
4156
- system?.map(block => block.text) ?? null,
4254
+ stablePrefix?.map(block => block.text) ?? null,
4157
4255
  root ? anthropicControlMessageProjection(root) : null,
4158
4256
  ]),
4159
4257
  ),
@@ -4199,14 +4297,16 @@ function syncAnthropicControlState(state: AnthropicControlState, messages: reado
4199
4297
  }
4200
4298
 
4201
4299
  /**
4202
- * Keep the top-level `system` array byte-stable across a session. The blocks
4203
- * captured on the first request are replayed verbatim (with the current
4204
- * request's cache breakpoints) while their text is unchanged. A text change
4300
+ * Keep the top-level `system` array byte-stable across a session. The stable
4301
+ * prefix captured on the first request is replayed verbatim (with the current
4302
+ * request's cache breakpoints) while its text is unchanged; the volatile
4303
+ * recall suffix always passes through current-turn. A stable-prefix change
4205
4304
  * re-baselines instead of duplicating the prompt as a mid-conversation
4206
4305
  * system message: omp's system prompt is one rendered segment that embeds
4207
4306
  * the tool roster, so replaying a second copy on every later request would
4208
4307
  * cost the full prompt again per change. The prefix rewrite is absorbed by
4209
- * `prefix_mismatch_behavior: "drop_block"` and one cache miss.
4308
+ * `prefix_mismatch_behavior: "drop_block"` and one cache miss, while a
4309
+ * recall-only change keeps the tool/control baselines intact.
4210
4310
  */
4211
4311
  function planStableAnthropicSystem(
4212
4312
  current: AnthropicSystemBlock[] | undefined,
@@ -4214,16 +4314,21 @@ function planStableAnthropicSystem(
4214
4314
  enabled: boolean,
4215
4315
  ): AnthropicSystemBlock[] | undefined {
4216
4316
  if (!state || !enabled) return current;
4217
- const fingerprint = JSON.stringify(current?.map(block => block.text) ?? null);
4317
+ const suffixStart = stableSystemSuffixStart(current ?? []);
4318
+ const fingerprint = JSON.stringify(current?.slice(0, suffixStart).map(block => block.text) ?? null);
4218
4319
  if (state.systemFingerprint !== fingerprint) {
4219
4320
  resetAnthropicControlState(state);
4220
4321
  state.systemFingerprint = fingerprint;
4221
- state.stableSystemBlocks = current?.map(block => ({ type: block.type, text: block.text }));
4222
- }
4223
- return state.stableSystemBlocks?.map((block, index) => {
4224
- const cacheControl = current?.[index]?.cache_control;
4225
- return cacheControl ? { ...block, cache_control: cloneAnthropicCacheControl(cacheControl) } : { ...block };
4226
- });
4322
+ state.stableSystemBlocks = current?.slice(0, suffixStart).map(block => ({ type: block.type, text: block.text }));
4323
+ }
4324
+ const stableReplay =
4325
+ state.stableSystemBlocks?.map((block, index) => {
4326
+ const cacheControl = current?.[index]?.cache_control;
4327
+ return cacheControl ? { ...block, cache_control: cloneAnthropicCacheControl(cacheControl) } : { ...block };
4328
+ }) ?? [];
4329
+ const suffix = current?.slice(suffixStart).map(block => ({ ...block })) ?? [];
4330
+ const replayed = [...stableReplay, ...suffix];
4331
+ return replayed.length > 0 ? replayed : undefined;
4227
4332
  }
4228
4333
 
4229
4334
  function anthropicToolDefinitionKey(tool: AnthropicWireTool): string {
@@ -267,10 +267,11 @@ export async function applyUserinfo(
267
267
  userinfo: CompiledUserinfo | undefined,
268
268
  credentials: OAuthCredentials,
269
269
  context: RequestContext,
270
+ vars: TemplateVars = {},
270
271
  ): Promise<OAuthCredentials> {
271
272
  if (!userinfo) return credentials;
272
273
  try {
273
- const response = await context.fetch(userinfo.url, {
274
+ const response = await context.fetch(template(userinfo.url, vars), {
274
275
  headers: { ...context.headers, Authorization: `Bearer ${credentials.access}` },
275
276
  signal: context.signal,
276
277
  });
@@ -93,6 +93,8 @@ export class DeclarativeOAuthCodeFlow extends OAuthCallbackFlow {
93
93
  #verifier = "";
94
94
  #clientId?: string;
95
95
  #clientSecret?: string;
96
+ #base?: string;
97
+ #auth?: string;
96
98
 
97
99
  constructor(
98
100
  ctrl: OAuthController,
@@ -123,6 +125,8 @@ export class DeclarativeOAuthCodeFlow extends OAuthCallbackFlow {
123
125
  const signal = this.ctrl.signal;
124
126
  this.#clientId = rule.clientId ? await resolveValue(rule.clientId, signal) : undefined;
125
127
  this.#clientSecret = rule.clientSecret ? await resolveValue(rule.clientSecret, signal) : undefined;
128
+ this.#base = rule.baseUrl ? (await resolveValue(rule.baseUrl, signal)).replace(/\/+$/, "") : undefined;
129
+ this.#auth = rule.authUrl ? (await resolveValue(rule.authUrl, signal)).replace(/\/+$/, "") : undefined;
126
130
  let challenge: string | undefined;
127
131
  if (rule.pkce) {
128
132
  const pkce = await generatePKCE();
@@ -136,6 +140,8 @@ export class DeclarativeOAuthCodeFlow extends OAuthCallbackFlow {
136
140
  scope,
137
141
  state,
138
142
  code_challenge: challenge,
143
+ base: this.#base,
144
+ auth: this.#auth,
139
145
  };
140
146
  const params = new URLSearchParams();
141
147
  if (rule.standardAuthorizeParams) {
@@ -150,7 +156,7 @@ export class DeclarativeOAuthCodeFlow extends OAuthCallbackFlow {
150
156
  if (state) params.set("state", state);
151
157
  }
152
158
  for (const key in rule.authorizeParams) params.set(key, template(rule.authorizeParams[key], vars));
153
- const authorizeUrl = await resolveValue(rule.authorizeUrl, signal);
159
+ const authorizeUrl = template(await resolveValue(rule.authorizeUrl, signal), vars);
154
160
  return { url: `${authorizeUrl}?${params.toString()}`, instructions: rule.instructions };
155
161
  }
156
162
 
@@ -185,6 +191,8 @@ export class DeclarativeOAuthCodeFlow extends OAuthCallbackFlow {
185
191
  code_verifier: rule.pkce ? this.#verifier : undefined,
186
192
  client_id: this.#clientId,
187
193
  client_secret: this.#clientSecret,
194
+ base: this.#base,
195
+ auth: this.#auth,
188
196
  };
189
197
  const context = { provider: this.#provider, fetch: this.#fetch, signal };
190
198
  const { body } = await postTokenRequest(
@@ -203,7 +211,7 @@ export class DeclarativeOAuthCodeFlow extends OAuthCallbackFlow {
203
211
  );
204
212
  throwIfCancelled(signal);
205
213
  let credentials = mapCredentials(rule.credential, body, this.#provider);
206
- credentials = await applyUserinfo(rule.userinfo, credentials, context);
214
+ credentials = await applyUserinfo(rule.userinfo, credentials, context, vars);
207
215
  throwIfCancelled(signal);
208
216
  return applyAfterExchange(rule.afterExchange, credentials, {
209
217
  provider: this.#provider,
@@ -31,7 +31,8 @@ async function loginClient(policy: CompiledAuthProvider, signal?: AbortSignal) {
31
31
  redirectUri: login.callback.redirectUri
32
32
  ? await resolveValue(login.callback.redirectUri, signal)
33
33
  : `http://${login.callback.hostname}:${login.callback.port}${login.callback.path}`,
34
- base: undefined,
34
+ base: login.baseUrl ? (await resolveValue(login.baseUrl, signal)).replace(/\/+$/, "") : undefined,
35
+ auth: login.authUrl ? (await resolveValue(login.authUrl, signal)).replace(/\/+$/, "") : undefined,
35
36
  };
36
37
  }
37
38
  if (login?.kind === "device-code") {
@@ -40,9 +41,10 @@ async function loginClient(policy: CompiledAuthProvider, signal?: AbortSignal) {
40
41
  clientSecret: undefined,
41
42
  redirectUri: undefined,
42
43
  base: login.baseUrl ? await resolveValue(login.baseUrl, signal) : undefined,
44
+ auth: undefined,
43
45
  };
44
46
  }
45
- return { clientId: undefined, clientSecret: undefined, redirectUri: undefined, base: undefined };
47
+ return { clientId: undefined, clientSecret: undefined, redirectUri: undefined, base: undefined, auth: undefined };
46
48
  }
47
49
 
48
50
  function createRequestRefresh(rule: RequestRefresh, policy: CompiledAuthProvider): Refresher {
@@ -66,6 +68,7 @@ function createRequestRefresh(rule: RequestRefresh, policy: CompiledAuthProvider
66
68
  client_secret: client.clientSecret,
67
69
  redirect_uri: client.redirectUri,
68
70
  base: client.base,
71
+ auth: client.auth,
69
72
  claude_code_sdk_version: claudeCodeSdkVersion,
70
73
  };
71
74
  const { body } = await postTokenRequest(