@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 +7 -0
- package/dist/types/auth-storage.d.ts +10 -6
- package/dist/types/registry/oauth/github-copilot.d.ts +2 -6
- package/dist/types/registry/oauth/kimi.d.ts +2 -1
- package/dist/types/registry/oauth/types.d.ts +2 -0
- package/package.json +6 -6
- package/src/auth-storage.ts +17 -7
- package/src/registry/oauth/github-copilot.ts +2 -2
- package/src/registry/oauth/kimi.ts +4 -4
- package/src/registry/oauth/types.ts +2 -0
- package/src/stream.ts +36 -1
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,
|
|
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
|
-
|
|
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.
|
|
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.
|
|
128
|
-
"@oh-my-pi/pi-catalog": "18.2.
|
|
129
|
-
"@oh-my-pi/pi-natives": "18.2.
|
|
130
|
-
"@oh-my-pi/pi-utils": "18.2.
|
|
131
|
-
"@oh-my-pi/pi-wire": "18.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"
|
package/src/auth-storage.ts
CHANGED
|
@@ -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,
|
|
1639
|
-
this.#configOverrides.set(provider,
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|