@sayknow-cli/ai 0.2.7 → 0.3.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/CHANGELOG.md +18 -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/providers/mock.d.ts +2 -0
- package/dist/types/providers/openai-responses-shared.d.ts +16 -1
- package/dist/types/types.d.ts +9 -1
- package/dist/types/utils/json-parse.d.ts +8 -0
- 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/providers/mock.ts +3 -0
- package/src/providers/openai-codex-responses.ts +14 -2
- package/src/providers/openai-completions.ts +12 -1
- package/src/providers/openai-responses-shared.ts +43 -1
- package/src/stream.ts +1 -0
- package/src/types.ts +9 -0
- package/src/utils/json-parse.ts +18 -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,24 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.7.2] - 2026-06-24
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Reject truncated or incomplete streamed tool calls instead of executing them with partial arguments, so a cut-off tool-call payload fails fast rather than running against a mismatched schema.
|
|
10
|
+
|
|
11
|
+
## [0.7.1] - 2026-06-23
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- 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).
|
|
16
|
+
|
|
17
|
+
## [0.7.0] - 2026-06-22
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- 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).
|
|
22
|
+
|
|
5
23
|
## [0.6.4] - 2026-06-20
|
|
6
24
|
|
|
7
25
|
### 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";
|
|
@@ -60,6 +60,8 @@ export type MockContent = string | {
|
|
|
60
60
|
name: string;
|
|
61
61
|
/** Object form is preferred; strings are passed through verbatim. */
|
|
62
62
|
arguments: Record<string, unknown> | string;
|
|
63
|
+
/** Simulate a provider-flagged truncated call (cut off mid-arguments). */
|
|
64
|
+
incompleteArguments?: boolean;
|
|
63
65
|
};
|
|
64
66
|
/** One scripted response. */
|
|
65
67
|
export interface MockResponse {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type OpenAI from "openai";
|
|
2
2
|
import type { ResponseInput, ResponseInputContent, ResponseOutputItem } from "openai/resources/responses/responses";
|
|
3
|
-
import { type Api, type AssistantMessage, type ImageContent, type Model, type ServiceTier, type StopReason, type StreamOptions, type TextContent, type TextSignatureV1, type ToolResultMessage } from "../types";
|
|
3
|
+
import { type Api, type AssistantMessage, type ImageContent, type Model, type ServiceTier, type StopReason, type StreamOptions, type TextContent, type TextSignatureV1, type ToolCall, type ToolResultMessage } from "../types";
|
|
4
4
|
import type { AssistantMessageEventStream } from "../utils/event-stream";
|
|
5
5
|
export declare function encodeTextSignatureV1(id: string, phase?: TextSignatureV1["phase"]): string;
|
|
6
6
|
export declare function parseTextSignature(signature: string | undefined): {
|
|
@@ -44,6 +44,21 @@ export interface ProcessResponsesStreamOptions {
|
|
|
44
44
|
onOutputItemDone?: (item: ResponseOutputItem) => void;
|
|
45
45
|
}
|
|
46
46
|
export declare function processResponsesStream<TApi extends Api>(openaiStream: AsyncIterable<OpenAI.Responses.ResponseStreamEvent>, output: AssistantMessage, stream: AssistantMessageEventStream, model: Model<TApi>, options?: ProcessResponsesStreamOptions): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Mark tool-call blocks left incomplete by a length-truncated response so the
|
|
49
|
+
* agent loop rejects them instead of executing a best-effort partial parse.
|
|
50
|
+
*
|
|
51
|
+
* The universal signal is finalization: a call that never received its terminal
|
|
52
|
+
* `output_item.done` (passed in via `isFinalized`) was cut off mid-arguments.
|
|
53
|
+
* This covers both JSON function calls and raw-input custom tools without
|
|
54
|
+
* mis-flagging a *completed* custom tool whose raw input is not valid JSON. As a
|
|
55
|
+
* defensive secondary, a finalized JSON function call whose buffered arguments
|
|
56
|
+
* still don't parse (e.g. a misbehaving relay) is flagged too. No-op unless the
|
|
57
|
+
* turn stopped for length.
|
|
58
|
+
*
|
|
59
|
+
* Shared by both Responses providers (`openai-responses`, `openai-codex-responses`).
|
|
60
|
+
*/
|
|
61
|
+
export declare function flagTruncatedToolCalls(output: AssistantMessage, stopReason: StopReason, isFinalized: (block: ToolCall) => boolean): void;
|
|
47
62
|
export declare function mapOpenAIResponsesStopReason(status: OpenAI.Responses.ResponseStatus | undefined): StopReason;
|
|
48
63
|
/** Initial empty `AssistantMessage` that streaming providers accumulate into. */
|
|
49
64
|
export declare function createInitialResponsesAssistantMessage(api: Api, provider: string, modelId: string): AssistantMessage;
|
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) */
|
|
@@ -338,6 +338,14 @@ export interface ToolCall {
|
|
|
338
338
|
* JSON function tools.
|
|
339
339
|
*/
|
|
340
340
|
customWireName?: string;
|
|
341
|
+
/**
|
|
342
|
+
* Set when the provider detected the argument JSON was truncated — the model
|
|
343
|
+
* hit its output-token limit (or the response was otherwise cut short) before
|
|
344
|
+
* emitting a complete arguments object. The `arguments` field then holds a
|
|
345
|
+
* best-effort partial parse and must not be executed as-is; the agent loop
|
|
346
|
+
* rejects the call with a retryable error instead.
|
|
347
|
+
*/
|
|
348
|
+
incompleteArguments?: boolean;
|
|
341
349
|
}
|
|
342
350
|
export interface Usage {
|
|
343
351
|
/** Non-cached input tokens (matches the bucket the provider bills as new input). */
|
|
@@ -8,3 +8,11 @@ export declare function parseJsonWithRepair<T>(json: string): T;
|
|
|
8
8
|
* @returns Parsed object or empty object if parsing fails
|
|
9
9
|
*/
|
|
10
10
|
export declare function parseStreamingJson<T = Record<string, unknown>>(partialJson: string | undefined): T;
|
|
11
|
+
/**
|
|
12
|
+
* Whether a string is a complete, well-formed JSON document (strict parse, no
|
|
13
|
+
* repair). Used to distinguish a tool-call argument blob that finished cleanly
|
|
14
|
+
* from one that was cut off mid-stream (truncation). An empty / whitespace-only
|
|
15
|
+
* string is treated as complete: a tool invoked with no arguments legitimately
|
|
16
|
+
* streams an empty buffer and must not be flagged as truncated.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isCompleteJson(text: string | undefined): boolean;
|
|
@@ -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.1",
|
|
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.1",
|
|
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();
|