@sayknow-cli/ai 0.2.7 → 0.3.0
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 +12 -0
- package/dist/types/auth-broker/client.d.ts +2 -1
- package/dist/types/auth-broker/remote-store.d.ts +3 -1
- package/dist/types/auth-broker/types.d.ts +6 -1
- package/dist/types/auth-broker/wire-schemas.d.ts +30 -0
- package/dist/types/auth-storage.d.ts +17 -0
- package/dist/types/provider-models/special.d.ts +3 -0
- package/dist/types/providers/anthropic.d.ts +15 -0
- package/dist/types/types.d.ts +1 -1
- package/dist/types/utils/oauth/glm-zcode.d.ts +71 -0
- package/dist/types/utils/oauth/types.d.ts +1 -1
- package/package.json +2 -2
- package/src/auth-broker/client.ts +15 -0
- package/src/auth-broker/remote-store.ts +20 -0
- package/src/auth-broker/server.ts +27 -0
- package/src/auth-broker/types.ts +12 -1
- package/src/auth-broker/wire-schemas.ts +17 -0
- package/src/auth-storage.ts +129 -11
- package/src/cli.ts +1 -0
- package/src/model-thinking.ts +11 -10
- package/src/models.json +58 -7
- package/src/provider-models/descriptors.ts +7 -1
- package/src/provider-models/openai-compat.ts +6 -0
- package/src/provider-models/special.ts +12 -0
- package/src/providers/anthropic.ts +82 -2
- package/src/stream.ts +1 -0
- package/src/types.ts +1 -0
- package/src/utils/oauth/glm-zcode.ts +433 -0
- package/src/utils/oauth/index.ts +10 -0
- package/src/utils/oauth/types.ts +1 -0
- package/src/utils.ts +11 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.7.1] - 2026-06-23
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Reworked the unofficial, opt-in `glm-zcode` provider to mirror how the ZCode desktop app actually reaches GLM: after the ZCode OAuth handshake it now auto-provisions a real Z.AI API key and calls `api.z.ai/api/anthropic` directly, instead of the `zcode.z.ai` coding-plan gateway that required an Aliyun captcha and a ZCode-JWT-bound plan entitlement. Requests also carry ZCode client source headers (`User-Agent: ZCode/<ver>`, `X-ZCode-Agent: glm`, plus platform/locale/timezone), so Z.AI recognizes the caller as the ZCode client (#1013, #1016, #1017).
|
|
10
|
+
|
|
11
|
+
## [0.7.0] - 2026-06-22
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Added the bundled `google-gemini-cli/gemini-3.5-flash` model entry so Gemini CLI users can select the model from the static registry (#965).
|
|
16
|
+
|
|
5
17
|
## [0.6.4] - 2026-06-20
|
|
6
18
|
|
|
7
19
|
### Fixed
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AuthCredential } from "../auth-storage";
|
|
2
|
-
import type { CredentialDisableResponse, CredentialRefreshResponse, CredentialUploadResponse, HealthzResponse, SnapshotResponse, SnapshotStreamEvent, UsageResponse } from "./types";
|
|
2
|
+
import type { CredentialDisableResponse, CredentialIfAbsentUploadResponse, CredentialRefreshResponse, CredentialUploadResponse, HealthzResponse, SnapshotResponse, SnapshotStreamEvent, UsageResponse } from "./types";
|
|
3
3
|
export interface AuthBrokerClientOptions {
|
|
4
4
|
/** Base URL (e.g. `https://broker.tailnet:8765`). Trailing slashes are trimmed. */
|
|
5
5
|
url: string;
|
|
@@ -63,4 +63,5 @@ export declare class AuthBrokerClient {
|
|
|
63
63
|
refreshCredential(id: number, signal?: AbortSignal): Promise<CredentialRefreshResponse>;
|
|
64
64
|
disableCredential(id: number, cause: string, signal?: AbortSignal): Promise<CredentialDisableResponse>;
|
|
65
65
|
uploadCredential(provider: string, credential: AuthCredential, signal?: AbortSignal): Promise<CredentialUploadResponse>;
|
|
66
|
+
uploadCredentialIfAbsent(provider: string, credential: AuthCredential, signal?: AbortSignal): Promise<CredentialIfAbsentUploadResponse>;
|
|
66
67
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AuthCredential, type AuthCredentialStore, type OAuthCredential, type StoredAuthCredential } from "../auth-storage";
|
|
1
|
+
import { type AuthCredential, type AuthCredentialIfAbsentResult, type AuthCredentialStore, type OAuthCredential, type StoredAuthCredential } from "../auth-storage";
|
|
2
2
|
import type { Provider } from "../types";
|
|
3
3
|
import type { UsageReport } from "../usage";
|
|
4
4
|
import type { OAuthCredentials } from "../utils/oauth/types";
|
|
@@ -44,6 +44,7 @@ export declare class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
44
44
|
}): Promise<void>;
|
|
45
45
|
replaceAuthCredentialsForProvider(_provider: string, _credentials: AuthCredential[]): StoredAuthCredential[];
|
|
46
46
|
upsertAuthCredentialForProvider(_provider: string, _credential: AuthCredential): StoredAuthCredential[];
|
|
47
|
+
upsertAuthCredentialForProviderIfAbsent(_provider: string, _credential: AuthCredential): AuthCredentialIfAbsentResult;
|
|
47
48
|
deleteAuthCredentialsForProvider(_provider: string, _disabledCause: string): void;
|
|
48
49
|
/**
|
|
49
50
|
* Upsert a single credential through the broker. The broker server is the
|
|
@@ -53,6 +54,7 @@ export declare class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
53
54
|
* any concurrent peer (refresh, generation bump) stays in sync.
|
|
54
55
|
*/
|
|
55
56
|
upsertAuthCredentialRemote(provider: string, credential: AuthCredential): Promise<StoredAuthCredential[]>;
|
|
57
|
+
upsertAuthCredentialRemoteIfAbsent(provider: string, credential: AuthCredential): Promise<AuthCredentialIfAbsentResult>;
|
|
56
58
|
/**
|
|
57
59
|
* Replace-all semantics: disable every active credential for the provider,
|
|
58
60
|
* then upload each of the new credentials. Used by API-key login so a new
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* clients use `access` tokens directly and call back to the broker when a
|
|
6
6
|
* credential expires or a 401 surfaces on a supposedly-fresh credential.
|
|
7
7
|
*/
|
|
8
|
-
import type { AuthCredential, AuthCredentialSnapshot, AuthCredentialSnapshotEntry } from "../auth-storage";
|
|
8
|
+
import type { AuthCredential, AuthCredentialIfAbsentReason, AuthCredentialSnapshot, AuthCredentialSnapshotEntry } from "../auth-storage";
|
|
9
9
|
import type { UsageReport } from "../usage";
|
|
10
10
|
/** GET /v1/healthz response body. */
|
|
11
11
|
export interface HealthzResponse {
|
|
@@ -56,6 +56,11 @@ export interface CredentialUploadRequest {
|
|
|
56
56
|
export interface CredentialUploadResponse {
|
|
57
57
|
entries: AuthCredentialSnapshotEntry[];
|
|
58
58
|
}
|
|
59
|
+
export interface CredentialIfAbsentUploadResponse {
|
|
60
|
+
inserted: boolean;
|
|
61
|
+
reason: AuthCredentialIfAbsentReason;
|
|
62
|
+
entries: AuthCredentialSnapshotEntry[];
|
|
63
|
+
}
|
|
59
64
|
/**
|
|
60
65
|
* SSE event kinds emitted on `GET /v1/snapshot/stream`. The same value is set
|
|
61
66
|
* as the SSE `event:` name (load-bearing for clients) **and** embedded as a
|
|
@@ -410,3 +410,33 @@ export declare const credentialUploadResponseSchema: z.ZodObject<{
|
|
|
410
410
|
identityKey: z.ZodNullable<z.ZodString>;
|
|
411
411
|
}, z.core.$strict>>;
|
|
412
412
|
}, z.core.$strict>;
|
|
413
|
+
export declare const credentialIfAbsentUploadResponseSchema: z.ZodObject<{
|
|
414
|
+
inserted: z.ZodBoolean;
|
|
415
|
+
reason: z.ZodEnum<{
|
|
416
|
+
inserted: "inserted";
|
|
417
|
+
"skipped-existing": "skipped-existing";
|
|
418
|
+
"skipped-existing-config": "skipped-existing-config";
|
|
419
|
+
"skipped-existing-env": "skipped-existing-env";
|
|
420
|
+
"skipped-existing-fallback": "skipped-existing-fallback";
|
|
421
|
+
"skipped-existing-runtime": "skipped-existing-runtime";
|
|
422
|
+
"skipped-invalid": "skipped-invalid";
|
|
423
|
+
}>;
|
|
424
|
+
entries: z.ZodArray<z.ZodObject<{
|
|
425
|
+
id: z.ZodNumber;
|
|
426
|
+
provider: z.ZodString;
|
|
427
|
+
credential: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
428
|
+
type: z.ZodLiteral<"oauth">;
|
|
429
|
+
access: z.ZodString;
|
|
430
|
+
expires: z.ZodNumber;
|
|
431
|
+
enterpriseUrl: z.ZodOptional<z.ZodString>;
|
|
432
|
+
projectId: z.ZodOptional<z.ZodString>;
|
|
433
|
+
email: z.ZodOptional<z.ZodString>;
|
|
434
|
+
accountId: z.ZodOptional<z.ZodString>;
|
|
435
|
+
refresh: z.ZodLiteral<"__remote__">;
|
|
436
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
437
|
+
type: z.ZodLiteral<"api_key">;
|
|
438
|
+
key: z.ZodString;
|
|
439
|
+
}, z.core.$strict>], "type">;
|
|
440
|
+
identityKey: z.ZodNullable<z.ZodString>;
|
|
441
|
+
}, z.core.$strict>>;
|
|
442
|
+
}, z.core.$strict>;
|
|
@@ -102,6 +102,19 @@ export interface AuthCredentialSnapshotEntry {
|
|
|
102
102
|
credential: SnapshotCredential;
|
|
103
103
|
identityKey: string | null;
|
|
104
104
|
}
|
|
105
|
+
export type AuthCredentialIfAbsentReason = "inserted" | "skipped-existing" | "skipped-existing-runtime" | "skipped-existing-config" | "skipped-existing-env" | "skipped-existing-fallback" | "skipped-invalid";
|
|
106
|
+
export interface AuthCredentialIfAbsentResult {
|
|
107
|
+
inserted: boolean;
|
|
108
|
+
reason: AuthCredentialIfAbsentReason;
|
|
109
|
+
provider: string;
|
|
110
|
+
entries: StoredAuthCredential[];
|
|
111
|
+
}
|
|
112
|
+
export interface AuthCredentialIfAbsentSnapshotResult {
|
|
113
|
+
inserted: boolean;
|
|
114
|
+
reason: AuthCredentialIfAbsentReason;
|
|
115
|
+
provider: string;
|
|
116
|
+
entries: AuthCredentialSnapshotEntry[];
|
|
117
|
+
}
|
|
105
118
|
/**
|
|
106
119
|
* Wire-shaped snapshot exported by {@link AuthStorage.exportSnapshot} and
|
|
107
120
|
* served by the auth-broker server on `GET /v1/snapshot`.
|
|
@@ -128,6 +141,7 @@ export interface AuthCredentialStore {
|
|
|
128
141
|
tryDisableAuthCredentialIfMatches(id: number, expectedData: string, disabledCause: string): boolean;
|
|
129
142
|
replaceAuthCredentialsForProvider(provider: string, credentials: AuthCredential[]): StoredAuthCredential[];
|
|
130
143
|
upsertAuthCredentialForProvider(provider: string, credential: AuthCredential): StoredAuthCredential[];
|
|
144
|
+
upsertAuthCredentialForProviderIfAbsent(provider: string, credential: AuthCredential): AuthCredentialIfAbsentResult;
|
|
131
145
|
deleteAuthCredentialsForProvider(provider: string, disabledCause: string): void;
|
|
132
146
|
getCache(key: string, options?: {
|
|
133
147
|
includeExpired?: boolean;
|
|
@@ -201,6 +215,7 @@ export interface AuthCredentialStore {
|
|
|
201
215
|
* post-write read path is consistent.
|
|
202
216
|
*/
|
|
203
217
|
upsertAuthCredentialRemote?(provider: string, credential: AuthCredential): Promise<StoredAuthCredential[]>;
|
|
218
|
+
upsertAuthCredentialRemoteIfAbsent?(provider: string, credential: AuthCredential): Promise<AuthCredentialIfAbsentResult>;
|
|
204
219
|
/**
|
|
205
220
|
* Optional async write hook for replace-all semantics (e.g. API-key login
|
|
206
221
|
* overwriting any previous keys for the same provider). When present,
|
|
@@ -417,6 +432,7 @@ export declare class AuthStorage {
|
|
|
417
432
|
* Set credential for a provider.
|
|
418
433
|
*/
|
|
419
434
|
set(provider: string, credential: AuthCredentialEntry): Promise<void>;
|
|
435
|
+
importCredentialIfAbsent(provider: string, credential: AuthCredential): Promise<AuthCredentialIfAbsentSnapshotResult>;
|
|
420
436
|
/**
|
|
421
437
|
* Remove credential for a provider.
|
|
422
438
|
*/
|
|
@@ -615,6 +631,7 @@ export declare class SqliteAuthCredentialStore implements AuthCredentialStore {
|
|
|
615
631
|
listAuthCredentials(provider?: string): StoredAuthCredential[];
|
|
616
632
|
replaceAuthCredentialsForProvider(provider: string, credentials: AuthCredential[]): StoredAuthCredential[];
|
|
617
633
|
upsertAuthCredentialForProvider(provider: string, credential: AuthCredential): StoredAuthCredential[];
|
|
634
|
+
upsertAuthCredentialForProviderIfAbsent(provider: string, credential: AuthCredential): AuthCredentialIfAbsentResult;
|
|
618
635
|
updateAuthCredential(id: number, credential: AuthCredential): void;
|
|
619
636
|
deleteAuthCredential(id: number, disabledCause: string): void;
|
|
620
637
|
/**
|
|
@@ -14,3 +14,6 @@ export declare function cursorModelManagerOptions(config?: CursorModelManagerCon
|
|
|
14
14
|
export interface ZaiModelManagerConfig {
|
|
15
15
|
}
|
|
16
16
|
export declare function zaiModelManagerOptions(_config?: ZaiModelManagerConfig): ModelManagerOptions<"anthropic-messages">;
|
|
17
|
+
export interface GlmZcodeModelManagerConfig {
|
|
18
|
+
}
|
|
19
|
+
export declare function glmZcodeModelManagerOptions(_config?: GlmZcodeModelManagerConfig): ModelManagerOptions<"anthropic-messages">;
|
|
@@ -9,9 +9,24 @@ export type AnthropicHeaderOptions = {
|
|
|
9
9
|
stream?: boolean;
|
|
10
10
|
modelHeaders?: Record<string, string>;
|
|
11
11
|
isCloudflareAiGateway?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Attach ZCode client "source" headers (User-Agent: ZCode/<ver>, X-Title,
|
|
14
|
+
* X-ZCode-Agent: glm, X-Platform, etc.) so api.z.ai recognizes the caller as
|
|
15
|
+
* the ZCode client, exactly like ZCode's `buildZCodeSourceHeaders` does for
|
|
16
|
+
* GLM providers. glm-zcode only.
|
|
17
|
+
*/
|
|
18
|
+
zcodeSourceHeaders?: boolean;
|
|
12
19
|
};
|
|
13
20
|
export declare function normalizeAnthropicBaseUrl(baseUrl?: string): string | undefined;
|
|
14
21
|
export declare function buildBetaHeader(baseBetas: string[], extraBetas: string[]): string;
|
|
22
|
+
/**
|
|
23
|
+
* Replicates ZCode's `buildZCodeSourceHeaders()` + GLM `X-ZCode-Agent` tag
|
|
24
|
+
* (host bundle `Bl` / `buildConnectivitySourceHeaders` for GLM providers), so
|
|
25
|
+
* api.z.ai sees skc's glm-zcode requests as the ZCode client. Dynamic values
|
|
26
|
+
* (platform/arch, locale, timezone, OS version) are resolved at runtime exactly
|
|
27
|
+
* as ZCode does; printable-ASCII-only and conditionally omitted when empty.
|
|
28
|
+
*/
|
|
29
|
+
export declare function buildZCodeSourceHeaders(): Record<string, string>;
|
|
15
30
|
export declare function buildAnthropicHeaders(options: AnthropicHeaderOptions): Record<string, string>;
|
|
16
31
|
type AnthropicCacheControl = {
|
|
17
32
|
type: "ephemeral";
|
package/dist/types/types.d.ts
CHANGED
|
@@ -48,7 +48,7 @@ export interface ThinkingConfig {
|
|
|
48
48
|
/** Provider-specific transport used to encode the selected effort. */
|
|
49
49
|
mode: ThinkingControlMode;
|
|
50
50
|
}
|
|
51
|
-
export type KnownProvider = "alibaba-coding-plan" | "amazon-bedrock" | "azure-openai" | "anthropic" | "google" | "google-gemini-cli" | "google-antigravity" | "google-vertex" | "openai" | "openai-codex" | "kimi-code" | "minimax-code" | "minimax-code-cn" | "github-copilot" | "fireworks" | "firepass" | "gitlab-duo" | "cursor" | "deepseek" | "xai" | "groq" | "cerebras" | "openrouter" | "kilo" | "vercel-ai-gateway" | "zai" | "mistral" | "minimax" | "opencode-go" | "opencode-zen" | "synthetic" | "cloudflare-ai-gateway" | "huggingface" | "litellm" | "moonshot" | "nvidia" | "nanogpt" | "ollama" | "ollama-cloud" | "qianfan" | "qwen-portal" | "together" | "venice" | "vllm" | "xiaomi" | "xiaomi-token-plan-sgp" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "zenmux" | "lm-studio";
|
|
51
|
+
export type KnownProvider = "alibaba-coding-plan" | "amazon-bedrock" | "azure-openai" | "anthropic" | "google" | "google-gemini-cli" | "google-antigravity" | "google-vertex" | "openai" | "openai-codex" | "kimi-code" | "minimax-code" | "minimax-code-cn" | "github-copilot" | "fireworks" | "firepass" | "gitlab-duo" | "cursor" | "deepseek" | "xai" | "groq" | "cerebras" | "openrouter" | "kilo" | "vercel-ai-gateway" | "zai" | "glm-zcode" | "mistral" | "minimax" | "opencode-go" | "opencode-zen" | "synthetic" | "cloudflare-ai-gateway" | "huggingface" | "litellm" | "moonshot" | "nvidia" | "nanogpt" | "ollama" | "ollama-cloud" | "qianfan" | "qwen-portal" | "together" | "venice" | "vllm" | "xiaomi" | "xiaomi-token-plan-sgp" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "zenmux" | "lm-studio";
|
|
52
52
|
export type Provider = KnownProvider | string;
|
|
53
53
|
import type { Effort } from "./model-thinking";
|
|
54
54
|
/** Token budgets for each thinking level (token-based providers only) */
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GLM ZCode OAuth flow (UNOFFICIAL, opt-in).
|
|
3
|
+
*
|
|
4
|
+
* Replicates how the ZCode desktop app turns a Z.AI login into usable GLM model
|
|
5
|
+
* access. This is NOT an official Z.AI OAuth client: it reuses ZCode's authorize
|
|
6
|
+
* page, broker, and a custom-protocol redirect. It may break at any time and may
|
|
7
|
+
* violate ZCode/Z.AI Terms of Service. Endpoints/client id are overridable via
|
|
8
|
+
* `ZCODE_OAUTH_*` environment variables.
|
|
9
|
+
*
|
|
10
|
+
* Verified end-to-end against the ZCode host bundle (`resolveZaiApiKey` /
|
|
11
|
+
* `resolveBizApiKey`) and live traffic:
|
|
12
|
+
* 1. Authorize: GET {authorize}?redirect_uri=zcode://oauth/callback&response_type=code&client_id=...&state=...
|
|
13
|
+
* (custom-protocol redirect → a CLI cannot catch it, so the user pastes the code/redirect URL)
|
|
14
|
+
* 2. Broker: POST {broker} { provider:"zai", code, redirect_uri, state }
|
|
15
|
+
* → { data: { token: <ZCode JWT>, zai: { access_token: <upstream Z.AI token> } } }
|
|
16
|
+
* 3. Business: POST {z/login} { token: <upstream Z.AI token> } → { data: { access_token: <business token> } }
|
|
17
|
+
* 4. Provision: with the business token, GET getCustomerInfo → default org/project,
|
|
18
|
+
* GET/POST .../api_keys (find/create a key named "zcode-api-key"),
|
|
19
|
+
* GET .../api_keys/copy/{id} → secretKey ⇒ a real Z.AI API key "{id}.{secret}".
|
|
20
|
+
*
|
|
21
|
+
* Credential mapping:
|
|
22
|
+
* - `access` = the provisioned **Z.AI API key** ("{id}.{secret}"). Model requests go to
|
|
23
|
+
* `https://api.z.ai/api/anthropic/v1/messages` with `Authorization: Bearer <key>`
|
|
24
|
+
* (exactly like a dashboard key) — NO zcode.z.ai gateway, NO captcha.
|
|
25
|
+
* - `refresh` = the upstream Z.AI OAuth access token (used to re-provision the key).
|
|
26
|
+
* The API key is long-lived, so `expires` is set far in the future.
|
|
27
|
+
*
|
|
28
|
+
* This provider must NEVER force `isOAuth=true`: the key is a plain Z.AI API key, and
|
|
29
|
+
* api.z.ai is not api.anthropic.com, so the Anthropic path already emits a plain bearer.
|
|
30
|
+
*/
|
|
31
|
+
import { OAuthCallbackFlow } from "./callback-server";
|
|
32
|
+
import type { OAuthController, OAuthCredentials } from "./types";
|
|
33
|
+
export declare const GLM_ZCODE_REFRESH_SKEW_MS: number;
|
|
34
|
+
/** Default endpoints / client id. Override via the matching `ZCODE_OAUTH_*` env vars. */
|
|
35
|
+
export declare const GLM_ZCODE_OAUTH_AUTHORIZE_URL = "https://chat.z.ai/api/oauth/authorize";
|
|
36
|
+
export declare const GLM_ZCODE_OAUTH_CLIENT_ID = "client_P8X5CMWmlaRO9gyO-KSqtg";
|
|
37
|
+
export declare const GLM_ZCODE_OAUTH_REDIRECT_URI = "zcode://oauth/callback";
|
|
38
|
+
export declare const GLM_ZCODE_OAUTH_BROKER_TOKEN_URL = "https://zcode.z.ai/api/v1/oauth/token";
|
|
39
|
+
export declare const GLM_ZCODE_ZAI_LOGIN_URL = "https://api.z.ai/api/auth/z/login";
|
|
40
|
+
export declare const GLM_ZCODE_USERINFO_URL = "https://chat.z.ai/api/oauth/userinfo";
|
|
41
|
+
/** Z.AI business API base (customer/org/project/api-key management). */
|
|
42
|
+
export declare const GLM_ZCODE_ZAI_API_BASE = "https://api.z.ai";
|
|
43
|
+
/** Model API base — the provisioned key is used here, exactly like a dashboard key. */
|
|
44
|
+
export declare const GLM_ZCODE_ANTHROPIC_BASE_URL = "https://api.z.ai/api/anthropic";
|
|
45
|
+
type FetchImpl = typeof globalThis.fetch;
|
|
46
|
+
/** Configured whenever a client id is available; the real ZCode client id ships as default. */
|
|
47
|
+
export declare function isGlmZcodeOAuthConfigured(): boolean;
|
|
48
|
+
export interface GlmZcodeOAuthFlowOptions {
|
|
49
|
+
fetch?: FetchImpl;
|
|
50
|
+
}
|
|
51
|
+
export declare class GlmZcodeOAuthFlow extends OAuthCallbackFlow {
|
|
52
|
+
#private;
|
|
53
|
+
constructor(ctrl: OAuthController, options?: GlmZcodeOAuthFlowOptions);
|
|
54
|
+
generateAuthUrl(state: string, redirectUri: string): Promise<{
|
|
55
|
+
url: string;
|
|
56
|
+
instructions?: string;
|
|
57
|
+
}>;
|
|
58
|
+
exchangeToken(code: string, state: string, redirectUri: string): Promise<OAuthCredentials>;
|
|
59
|
+
}
|
|
60
|
+
export declare function loginGlmZcode(ctrl: OAuthController, options?: GlmZcodeOAuthFlowOptions): Promise<OAuthCredentials>;
|
|
61
|
+
export interface GlmZcodeRefreshOptions {
|
|
62
|
+
signal?: AbortSignal;
|
|
63
|
+
fetch?: FetchImpl;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Re-provision the Z.AI API key from the stored upstream token. The key itself is
|
|
67
|
+
* long-lived, so this is rarely needed; if the upstream token has expired it fails
|
|
68
|
+
* loudly and the user must re-login.
|
|
69
|
+
*/
|
|
70
|
+
export declare function refreshGlmZcodeToken(credentials: OAuthCredentials, options?: AbortSignal | GlmZcodeRefreshOptions): Promise<OAuthCredentials>;
|
|
71
|
+
export {};
|
|
@@ -7,7 +7,7 @@ export type OAuthCredentials = {
|
|
|
7
7
|
email?: string;
|
|
8
8
|
accountId?: string;
|
|
9
9
|
};
|
|
10
|
-
export type OAuthProvider = "alibaba-coding-plan" | "anthropic" | "cerebras" | "cloudflare-ai-gateway" | "cursor" | "deepseek" | "fireworks" | "firepass" | "github-copilot" | "google-gemini-cli" | "google-antigravity" | "gitlab-duo" | "huggingface" | "kimi-code" | "kilo" | "kagi" | "litellm" | "lm-studio" | "minimax-code" | "minimax-code-cn" | "moonshot" | "nvidia" | "nanogpt" | "ollama" | "ollama-cloud" | "openai-codex" | "openai-codex-device" | "opencode-go" | "opencode-zen" | "parallel" | "perplexity" | "qianfan" | "qwen-portal" | "synthetic" | "tavily" | "together" | "venice" | "vercel-ai-gateway" | "vllm" | "xai" | "xiaomi" | "xiaomi-token-plan-sgp" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "zenmux" | "zai";
|
|
10
|
+
export type OAuthProvider = "alibaba-coding-plan" | "anthropic" | "cerebras" | "cloudflare-ai-gateway" | "cursor" | "deepseek" | "fireworks" | "firepass" | "github-copilot" | "google-gemini-cli" | "google-antigravity" | "gitlab-duo" | "huggingface" | "kimi-code" | "kilo" | "kagi" | "litellm" | "lm-studio" | "minimax-code" | "minimax-code-cn" | "moonshot" | "nvidia" | "nanogpt" | "ollama" | "ollama-cloud" | "openai-codex" | "openai-codex-device" | "opencode-go" | "opencode-zen" | "parallel" | "perplexity" | "qianfan" | "qwen-portal" | "synthetic" | "tavily" | "together" | "venice" | "vercel-ai-gateway" | "vllm" | "xai" | "glm-zcode" | "xiaomi" | "xiaomi-token-plan-sgp" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "zenmux" | "zai";
|
|
11
11
|
export type OAuthProviderId = OAuthProvider | (string & {});
|
|
12
12
|
export type OAuthPrompt = {
|
|
13
13
|
message: string;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@sayknow-cli/ai",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
6
|
"homepage": "https://github.com/jaybeyond/Sayknow_CLI",
|
|
7
7
|
"author": "jaybeyond",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@anthropic-ai/sdk": "^0.94.0",
|
|
45
45
|
"@bufbuild/protobuf": "^2.12.0",
|
|
46
|
-
"@sayknow-cli/utils": "0.
|
|
46
|
+
"@sayknow-cli/utils": "0.3.0",
|
|
47
47
|
"openai": "^6.36.0",
|
|
48
48
|
"partial-json": "^0.1.7",
|
|
49
49
|
"zod": "4.4.3"
|
|
@@ -11,6 +11,7 @@ import type { AuthCredential } from "../auth-storage";
|
|
|
11
11
|
import type {
|
|
12
12
|
CredentialDisableRequest,
|
|
13
13
|
CredentialDisableResponse,
|
|
14
|
+
CredentialIfAbsentUploadResponse,
|
|
14
15
|
CredentialRefreshResponse,
|
|
15
16
|
CredentialUploadRequest,
|
|
16
17
|
CredentialUploadResponse,
|
|
@@ -21,6 +22,7 @@ import type {
|
|
|
21
22
|
} from "./types";
|
|
22
23
|
import {
|
|
23
24
|
credentialDisableResponseSchema,
|
|
25
|
+
credentialIfAbsentUploadResponseSchema,
|
|
24
26
|
credentialRefreshResponseSchema,
|
|
25
27
|
credentialUploadResponseSchema,
|
|
26
28
|
healthzResponseSchema,
|
|
@@ -262,6 +264,19 @@ export class AuthBrokerClient {
|
|
|
262
264
|
}) as Promise<CredentialUploadResponse>;
|
|
263
265
|
}
|
|
264
266
|
|
|
267
|
+
async uploadCredentialIfAbsent(
|
|
268
|
+
provider: string,
|
|
269
|
+
credential: AuthCredential,
|
|
270
|
+
signal?: AbortSignal,
|
|
271
|
+
): Promise<CredentialIfAbsentUploadResponse> {
|
|
272
|
+
const body: CredentialUploadRequest = { provider, credential };
|
|
273
|
+
return this.#request("POST", "/v1/credential/if-absent", {
|
|
274
|
+
body,
|
|
275
|
+
schema: credentialIfAbsentUploadResponseSchema,
|
|
276
|
+
signal,
|
|
277
|
+
}) as Promise<CredentialIfAbsentUploadResponse>;
|
|
278
|
+
}
|
|
279
|
+
|
|
265
280
|
async #request<TSchema extends ZodType>(
|
|
266
281
|
method: "GET" | "POST",
|
|
267
282
|
path: string,
|
|
@@ -11,6 +11,7 @@ import { scheduler } from "node:timers/promises";
|
|
|
11
11
|
import { logger } from "@sayknow-cli/utils";
|
|
12
12
|
import {
|
|
13
13
|
type AuthCredential,
|
|
14
|
+
type AuthCredentialIfAbsentResult,
|
|
14
15
|
type AuthCredentialSnapshotEntry,
|
|
15
16
|
type AuthCredentialStore,
|
|
16
17
|
type OAuthCredential,
|
|
@@ -308,6 +309,15 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
308
309
|
);
|
|
309
310
|
}
|
|
310
311
|
|
|
312
|
+
upsertAuthCredentialForProviderIfAbsent(
|
|
313
|
+
_provider: string,
|
|
314
|
+
_credential: AuthCredential,
|
|
315
|
+
): AuthCredentialIfAbsentResult {
|
|
316
|
+
throw new Error(
|
|
317
|
+
"RemoteAuthCredentialStore is read-only on the client. Use `skc auth-broker login <provider>` to mutate credentials.",
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
311
321
|
deleteAuthCredentialsForProvider(_provider: string, _disabledCause: string): void {
|
|
312
322
|
throw new Error(
|
|
313
323
|
"RemoteAuthCredentialStore is read-only on the client. Use `skc auth-broker logout <provider>` to mutate credentials.",
|
|
@@ -328,6 +338,16 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
328
338
|
return this.listAuthCredentials(provider);
|
|
329
339
|
}
|
|
330
340
|
|
|
341
|
+
async upsertAuthCredentialRemoteIfAbsent(
|
|
342
|
+
provider: string,
|
|
343
|
+
credential: AuthCredential,
|
|
344
|
+
): Promise<AuthCredentialIfAbsentResult> {
|
|
345
|
+
const { inserted, reason, entries } = await this.#client.uploadCredentialIfAbsent(provider, credential);
|
|
346
|
+
this.#applyProviderEntries(provider, entries);
|
|
347
|
+
this.#maybeRefreshSnapshot("upload-if-absent");
|
|
348
|
+
return { inserted, reason, provider, entries: this.listAuthCredentials(provider) };
|
|
349
|
+
}
|
|
350
|
+
|
|
331
351
|
/**
|
|
332
352
|
* Replace-all semantics: disable every active credential for the provider,
|
|
333
353
|
* then upload each of the new credentials. Used by API-key login so a new
|
|
@@ -15,6 +15,7 @@ import { parseBind } from "../utils/parse-bind";
|
|
|
15
15
|
import { AuthBrokerRefresher, type AuthBrokerRefresherSchedule } from "./refresher";
|
|
16
16
|
import type {
|
|
17
17
|
CredentialDisableResponse,
|
|
18
|
+
CredentialIfAbsentUploadResponse,
|
|
18
19
|
CredentialRefreshResponse,
|
|
19
20
|
CredentialUploadResponse,
|
|
20
21
|
HealthzResponse,
|
|
@@ -592,6 +593,32 @@ export function startAuthBroker(opts: AuthBrokerServerOptions): AuthBrokerServer
|
|
|
592
593
|
const response: CredentialDisableResponse = { ok: true };
|
|
593
594
|
return json(200, response);
|
|
594
595
|
}
|
|
596
|
+
if (req.method === "POST" && pathname === "/v1/credential/if-absent") {
|
|
597
|
+
const parsed = await parseBody(req, credentialUploadRequestSchema);
|
|
598
|
+
if (!parsed.ok) return parsed.response;
|
|
599
|
+
const { provider, credential } = parsed.data;
|
|
600
|
+
try {
|
|
601
|
+
const result = await opts.storage.importCredentialIfAbsent(provider, credential);
|
|
602
|
+
logger.info("auth-broker credential import-if-absent", {
|
|
603
|
+
provider,
|
|
604
|
+
type: credential.type,
|
|
605
|
+
inserted: result.inserted,
|
|
606
|
+
reason: result.reason,
|
|
607
|
+
providerTotal: result.entries.length,
|
|
608
|
+
peer,
|
|
609
|
+
});
|
|
610
|
+
const response: CredentialIfAbsentUploadResponse = {
|
|
611
|
+
inserted: result.inserted,
|
|
612
|
+
reason: result.reason,
|
|
613
|
+
entries: result.entries,
|
|
614
|
+
};
|
|
615
|
+
return json(200, response);
|
|
616
|
+
} catch (error) {
|
|
617
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
618
|
+
logger.warn("auth-broker upload-if-absent failed", { provider, type: credential.type, peer });
|
|
619
|
+
return json(500, { error: message });
|
|
620
|
+
}
|
|
621
|
+
}
|
|
595
622
|
if (req.method === "POST" && pathname === "/v1/credential") {
|
|
596
623
|
const parsed = await parseBody(req, credentialUploadRequestSchema);
|
|
597
624
|
if (!parsed.ok) return parsed.response;
|
package/src/auth-broker/types.ts
CHANGED
|
@@ -6,7 +6,12 @@
|
|
|
6
6
|
* credential expires or a 401 surfaces on a supposedly-fresh credential.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
AuthCredential,
|
|
11
|
+
AuthCredentialIfAbsentReason,
|
|
12
|
+
AuthCredentialSnapshot,
|
|
13
|
+
AuthCredentialSnapshotEntry,
|
|
14
|
+
} from "../auth-storage";
|
|
10
15
|
import type { UsageReport } from "../usage";
|
|
11
16
|
|
|
12
17
|
/** GET /v1/healthz response body. */
|
|
@@ -68,6 +73,12 @@ export interface CredentialUploadResponse {
|
|
|
68
73
|
entries: AuthCredentialSnapshotEntry[];
|
|
69
74
|
}
|
|
70
75
|
|
|
76
|
+
export interface CredentialIfAbsentUploadResponse {
|
|
77
|
+
inserted: boolean;
|
|
78
|
+
reason: AuthCredentialIfAbsentReason;
|
|
79
|
+
entries: AuthCredentialSnapshotEntry[];
|
|
80
|
+
}
|
|
81
|
+
|
|
71
82
|
/**
|
|
72
83
|
* SSE event kinds emitted on `GET /v1/snapshot/stream`. The same value is set
|
|
73
84
|
* as the SSE `event:` name (load-bearing for clients) **and** embedded as a
|
|
@@ -185,6 +185,15 @@ export const credentialDisableResponseSchema = z
|
|
|
185
185
|
.strict();
|
|
186
186
|
|
|
187
187
|
// ─── Upload ────────────────────────────────────────────────────────────────
|
|
188
|
+
const credentialIfAbsentReasonSchema = z.enum([
|
|
189
|
+
"inserted",
|
|
190
|
+
"skipped-existing",
|
|
191
|
+
"skipped-existing-runtime",
|
|
192
|
+
"skipped-existing-config",
|
|
193
|
+
"skipped-existing-env",
|
|
194
|
+
"skipped-existing-fallback",
|
|
195
|
+
"skipped-invalid",
|
|
196
|
+
]);
|
|
188
197
|
|
|
189
198
|
export const credentialUploadRequestSchema = z
|
|
190
199
|
.object({
|
|
@@ -198,3 +207,11 @@ export const credentialUploadResponseSchema = z
|
|
|
198
207
|
entries: z.array(credentialSnapshotEntrySchema),
|
|
199
208
|
})
|
|
200
209
|
.strict();
|
|
210
|
+
|
|
211
|
+
export const credentialIfAbsentUploadResponseSchema = z
|
|
212
|
+
.object({
|
|
213
|
+
inserted: z.boolean(),
|
|
214
|
+
reason: credentialIfAbsentReasonSchema,
|
|
215
|
+
entries: z.array(credentialSnapshotEntrySchema),
|
|
216
|
+
})
|
|
217
|
+
.strict();
|