@oh-my-pi/pi-ai 18.2.2 → 18.2.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/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.2.3] - 2026-09-17
6
+
7
+ ### Added
8
+
9
+ - `stream()` and `streamSimple()` support asynchronous model header resolution for each request attempt, including authentication retries and cancellation.
10
+ - Provider login prompts can request masked entry with `secret: true`.
11
+
5
12
  ## [18.2.2] - 2026-09-16
6
13
 
7
14
  ### Added
@@ -1,5 +1,5 @@
1
1
  import type { ApiKeyResolver } from "./auth-retry.js";
2
- import type { OAuthAuthInfo, OAuthController, OAuthCredentials, OAuthProviderId } from "./registry/oauth/types.js";
2
+ import type { OAuthAuthInfo, OAuthController, OAuthCredentials, OAuthPrompt, OAuthProviderId } from "./registry/oauth/types.js";
3
3
  import type { Provider } from "./types.js";
4
4
  import type { ClientUsageIdentity, ClientUsageReport, ClientUsageSummary, CredentialRankingStrategy, ObservedUsageEntry, UsageHistoryEntry, UsageHistoryQuery, UsageLogger, UsageProvider, UsageReport } from "./usage.js";
5
5
  import { type CodexResetConsumeCode, type CodexResetCredit } from "./usage/openai-codex-reset.js";
@@ -800,7 +800,7 @@ export declare class AuthStorage {
800
800
  * Lower priority than {@link setRuntimeApiKey} so a CLI `--api-key`
801
801
  * still wins for the duration of a single invocation.
802
802
  */
803
- setConfigApiKey(provider: string, apiKey: string): void;
803
+ setConfigApiKey(provider: string, apiKeyConfig: string): void;
804
804
  /**
805
805
  * Remove a single config-sourced API key override.
806
806
  */
@@ -815,6 +815,13 @@ export declare class AuthStorage {
815
815
  * Used for custom provider keys from models.json.
816
816
  */
817
817
  setFallbackResolver(resolver: (provider: string) => string | undefined): void;
818
+ /**
819
+ * Install the host's async config-value resolver. Coding-agent uses this so
820
+ * every stored/config credential reference shares command caching,
821
+ * failure backoff, and process hardening even when AuthStorage was created
822
+ * independently and later attached to a registry.
823
+ */
824
+ setConfigValueResolver(resolver: (config: string) => Promise<string | undefined>): void;
818
825
  /**
819
826
  * Reload credentials from storage.
820
827
  */
@@ -935,10 +942,7 @@ export declare class AuthStorage {
935
942
  /** onAuth is required by auth-storage but optional in OAuthController */
936
943
  onAuth: (info: OAuthAuthInfo) => void;
937
944
  /** onPrompt is required for some providers (github-copilot, openai-codex) */
938
- onPrompt: (prompt: {
939
- message: string;
940
- placeholder?: string;
941
- }) => Promise<string>;
945
+ onPrompt: (prompt: OAuthPrompt) => Promise<string>;
942
946
  }): Promise<OAuthLoginIdentity | undefined>;
943
947
  /**
944
948
  * Logout from a provider.
@@ -1,12 +1,8 @@
1
1
  import type { FetchImpl } from "../../types.js";
2
- import type { OAuthController, OAuthCredentials } from "./types.js";
2
+ import type { OAuthController, OAuthCredentials, OAuthPrompt } from "./types.js";
3
3
  type GitHubCopilotLoginOptions = {
4
4
  onAuth: (url: string, instructions?: string) => void;
5
- onPrompt: (prompt: {
6
- message: string;
7
- placeholder?: string;
8
- allowEmpty?: boolean;
9
- }) => Promise<string>;
5
+ onPrompt: (prompt: OAuthPrompt) => Promise<string>;
10
6
  onProgress?: (message: string) => void;
11
7
  copilotIntegrationId?: unknown;
12
8
  signal?: AbortSignal;
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Kimi Code OAuth flow (device authorization grant)
3
3
  */
4
- export declare let getKimiCommonHeaders: () => Readonly<{
4
+ /** Lazily resolve the process-stable device headers used by Kimi requests. */
5
+ export declare const getKimiCommonHeaders: () => Readonly<{
5
6
  "User-Agent": `KimiCLI/${string}`;
6
7
  "X-Msh-Platform": "kimi_cli";
7
8
  "X-Msh-Version": string;
@@ -33,6 +33,8 @@ export type OAuthPrompt = {
33
33
  message: string;
34
34
  placeholder?: string;
35
35
  allowEmpty?: boolean;
36
+ /** Request masked entry from interactive hosts. Hosts that cannot hide input must reject the prompt. */
37
+ secret?: boolean;
36
38
  };
37
39
  export type OAuthAuthInfo = {
38
40
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-ai",
3
- "version": "18.2.2",
3
+ "version": "18.2.3",
4
4
  "description": "Unified LLM API with automatic model discovery and provider configuration",
5
5
  "keywords": [
6
6
  "ai",
@@ -124,11 +124,11 @@
124
124
  "fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
125
125
  },
126
126
  "dependencies": {
127
- "@oh-my-pi/omptype": "18.2.2",
128
- "@oh-my-pi/pi-catalog": "18.2.2",
129
- "@oh-my-pi/pi-natives": "18.2.2",
130
- "@oh-my-pi/pi-utils": "18.2.2",
131
- "@oh-my-pi/pi-wire": "18.2.2"
127
+ "@oh-my-pi/omptype": "18.2.3",
128
+ "@oh-my-pi/pi-catalog": "18.2.3",
129
+ "@oh-my-pi/pi-natives": "18.2.3",
130
+ "@oh-my-pi/pi-utils": "18.2.3",
131
+ "@oh-my-pi/pi-wire": "18.2.3"
132
132
  },
133
133
  "devDependencies": {
134
134
  "@types/bun": "^1.3.14"
@@ -26,6 +26,7 @@ import type {
26
26
  OAuthAuthInfo,
27
27
  OAuthController,
28
28
  OAuthCredentials,
29
+ OAuthPrompt,
29
30
  OAuthProvider,
30
31
  OAuthProviderId,
31
32
  } from "./registry/oauth/types";
@@ -1635,8 +1636,8 @@ export class AuthStorage {
1635
1636
  * Lower priority than {@link setRuntimeApiKey} so a CLI `--api-key`
1636
1637
  * still wins for the duration of a single invocation.
1637
1638
  */
1638
- setConfigApiKey(provider: string, apiKey: string): void {
1639
- this.#configOverrides.set(provider, apiKey);
1639
+ setConfigApiKey(provider: string, apiKeyConfig: string): void {
1640
+ this.#configOverrides.set(provider, apiKeyConfig);
1640
1641
  }
1641
1642
 
1642
1643
  /**
@@ -1661,6 +1662,15 @@ export class AuthStorage {
1661
1662
  setFallbackResolver(resolver: (provider: string) => string | undefined): void {
1662
1663
  this.#fallbackResolver = resolver;
1663
1664
  }
1665
+ /**
1666
+ * Install the host's async config-value resolver. Coding-agent uses this so
1667
+ * every stored/config credential reference shares command caching,
1668
+ * failure backoff, and process hardening even when AuthStorage was created
1669
+ * independently and later attached to a registry.
1670
+ */
1671
+ setConfigValueResolver(resolver: (config: string) => Promise<string | undefined>): void {
1672
+ this.#configValueResolver = resolver;
1673
+ }
1664
1674
 
1665
1675
  /**
1666
1676
  * Reload credentials from storage.
@@ -3169,7 +3179,7 @@ export class AuthStorage {
3169
3179
  /** onAuth is required by auth-storage but optional in OAuthController */
3170
3180
  onAuth: (info: OAuthAuthInfo) => void;
3171
3181
  /** onPrompt is required for some providers (github-copilot, openai-codex) */
3172
- onPrompt: (prompt: { message: string; placeholder?: string }) => Promise<string>;
3182
+ onPrompt: (prompt: OAuthPrompt) => Promise<string>;
3173
3183
  },
3174
3184
  ): Promise<OAuthLoginIdentity | undefined> {
3175
3185
  // Only paste-code providers (fixed non-loopback redirect, e.g. GitLab Duo
@@ -5960,8 +5970,8 @@ export class AuthStorage {
5960
5970
  }
5961
5971
 
5962
5972
  const configKey = this.#configOverrides.get(provider);
5963
- if (configKey) {
5964
- return configKey;
5973
+ if (configKey !== undefined) {
5974
+ return await this.#configValueResolver(configKey);
5965
5975
  }
5966
5976
 
5967
5977
  await this.#adoptExternalCredentialChanges();
@@ -6028,8 +6038,8 @@ export class AuthStorage {
6028
6038
  // honor it instead of forwarding an upstream OAuth token that the proxy
6029
6039
  // won't accept.
6030
6040
  const configKey = this.#configOverrides.get(provider);
6031
- if (configKey) {
6032
- return configKey;
6041
+ if (configKey !== undefined) {
6042
+ return await this.#configValueResolver(configKey);
6033
6043
  }
6034
6044
 
6035
6045
  // Precedence: a deliberate OAuth/login credential wins, then an explicit env var,
@@ -29,7 +29,7 @@ import {
29
29
  } from "../../providers/github-copilot-headers";
30
30
  import * as AIError from "../../error";
31
31
  import type { FetchImpl } from "../../types";
32
- import type { OAuthController, OAuthCredentials } from "./types";
32
+ import type { OAuthController, OAuthCredentials, OAuthPrompt } from "./types";
33
33
 
34
34
  const OPENCODE_CLIENT_ID = "Ov23li8tweQw6odWQebz";
35
35
  const COPILOT_CLI_CLIENT_ID = "Ov23ctDVkRmgkPke0Mmm";
@@ -55,7 +55,7 @@ const SLOW_DOWN_POLL_INTERVAL_MULTIPLIER = 1.4;
55
55
 
56
56
  type GitHubCopilotLoginOptions = {
57
57
  onAuth: (url: string, instructions?: string) => void;
58
- onPrompt: (prompt: { message: string; placeholder?: string; allowEmpty?: boolean }) => Promise<string>;
58
+ onPrompt: (prompt: OAuthPrompt) => Promise<string>;
59
59
  onProgress?: (message: string) => void;
60
60
  copilotIntegrationId?: unknown;
61
61
  signal?: AbortSignal;
@@ -6,7 +6,7 @@ import * as crypto from "node:crypto";
6
6
  import * as fs from "node:fs";
7
7
  import * as os from "node:os";
8
8
  import * as path from "node:path";
9
- import { getAgentDir } from "@oh-my-pi/pi-utils";
9
+ import { getAgentDir, once } from "@oh-my-pi/pi-utils";
10
10
  import packageJson from "../../../package.json" with { type: "json" };
11
11
 
12
12
  const DEVICE_ID_FILENAME = "kimi-device-id";
@@ -57,7 +57,8 @@ function sanitizeHeaderValue(value: string, fallback = ""): string {
57
57
  return sanitized || fallback;
58
58
  }
59
59
 
60
- export let getKimiCommonHeaders = () => {
60
+ /** Lazily resolve the process-stable device headers used by Kimi requests. */
61
+ export const getKimiCommonHeaders = once(() => {
61
62
  const headers = Object.freeze({
62
63
  "User-Agent": `KimiCLI/${packageJson.version}`,
63
64
  "X-Msh-Platform": "kimi_cli",
@@ -67,6 +68,5 @@ export let getKimiCommonHeaders = () => {
67
68
  "X-Msh-Os-Version": sanitizeHeaderValue(os.version(), "unknown"),
68
69
  "X-Msh-Device-Id": sanitizeHeaderValue(getDeviceId(), "unknown"),
69
70
  });
70
- getKimiCommonHeaders = () => headers;
71
71
  return headers;
72
- };
72
+ });
@@ -37,6 +37,8 @@ export type OAuthPrompt = {
37
37
  message: string;
38
38
  placeholder?: string;
39
39
  allowEmpty?: boolean;
40
+ /** Request masked entry from interactive hosts. Hosts that cannot hide input must reject the prompt. */
41
+ secret?: boolean;
40
42
  };
41
43
 
42
44
  export type OAuthAuthInfo = {
package/src/stream.ts CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  } from "@oh-my-pi/pi-catalog/model-thinking";
16
16
  import { providerEntries } from "@oh-my-pi/pi-catalog/compat/providers";
17
17
  import { CODEX_BASE_URL } from "@oh-my-pi/pi-catalog/wire/codex";
18
- import { $env, $pickenv, getProviderInFlightRoot, isEnoent, logger } from "@oh-my-pi/pi-utils";
18
+ import { $env, $pickenv, getProviderInFlightRoot, isEnoent, logger, untilAborted } from "@oh-my-pi/pi-utils";
19
19
  import { getCustomApi } from "./api-registry";
20
20
  import { createAuthRetryKeyState, isApiKeyResolver, resolveNextAuthRetryKey } from "./auth-retry";
21
21
  import * as AIError from "./error";
@@ -889,11 +889,40 @@ export function listProvidersWithEnvKey(): string[] {
889
889
  return Object.keys(serviceProviderMap);
890
890
  }
891
891
 
892
+ function withResolvedModelHeaders<TApi extends Api>(
893
+ model: Model<TApi>,
894
+ signal: AbortSignal | undefined,
895
+ run: (resolvedModel: Model<TApi>) => AssistantMessageEventStream,
896
+ ): AssistantMessageEventStream {
897
+ const resolveHeaders = model.resolveHeaders;
898
+ if (!resolveHeaders) return run(model);
899
+
900
+ const outer = new AssistantMessageEventStream();
901
+ void (async () => {
902
+ try {
903
+ const headers = await untilAborted(signal, () => resolveHeaders(signal));
904
+ signal?.throwIfAborted();
905
+ const inner = run({ ...model, resolveHeaders: undefined, headers: headers ? { ...headers } : undefined });
906
+ for await (const event of inner) {
907
+ outer.push(event);
908
+ if (outer.done) return;
909
+ }
910
+ if (!outer.done) outer.end(await inner.result());
911
+ } catch (error) {
912
+ outer.fail(error);
913
+ }
914
+ })();
915
+ return outer;
916
+ }
917
+
892
918
  export function stream<TApi extends Api>(
893
919
  model: Model<TApi>,
894
920
  context: Context,
895
921
  options?: OptionsForApi<TApi>,
896
922
  ): AssistantMessageEventStream {
923
+ if (model.resolveHeaders) {
924
+ return withResolvedModelHeaders(model, options?.signal, resolvedModel => stream(resolvedModel, context, options));
925
+ }
897
926
  if (!model.requiresGlyphTokenization) {
898
927
  return withThinkingLoopGuard(model, options, opts =>
899
928
  withProviderInFlightLimit(model, opts, () => streamDispatch(model, context, opts)),
@@ -1623,6 +1652,12 @@ function streamSimpleRequest<TApi extends Api>(
1623
1652
  return outer;
1624
1653
  }
1625
1654
 
1655
+ if (model.resolveHeaders) {
1656
+ return withResolvedModelHeaders(model, requestOptions.signal, resolvedModel =>
1657
+ streamSimpleRequest(resolvedModel, context, requestOptions),
1658
+ );
1659
+ }
1660
+
1626
1661
  // Pi-native transport short-circuits the per-provider dispatch entirely:
1627
1662
  // the gateway resolves provider + credential server-side, so we don't
1628
1663
  // need an `apiKey` from `getEnvApiKey` here — `options.apiKey` carries