@arnilo/prism-provider-zai 0.0.5 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -1
- package/README.md +17 -10
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/models.d.ts +52 -2
- package/dist/models.js +174 -5
- package/dist/provider.d.ts +10 -1
- package/dist/provider.js +104 -13
- package/dist/thinking.d.ts +26 -0
- package/dist/thinking.js +76 -6
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,19 @@ All notable changes to @arnilo/prism-provider-zai will be documented in this fil
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [
|
|
8
|
+
## [0.0.6] - 2026-07-19
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Caller-gated `listZaiModels()` / `mapZaiModel()` (OpenAI-compatible `GET /models`).
|
|
13
|
+
- Official `clear_thinking` + Preserved Thinking (`reasoning_content` replay).
|
|
14
|
+
- Featured catalog refreshed to official GLM-5.x / 4.7 / 4.6 / 4.5 ids.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Default base URL is official international `https://api.z.ai/api/paas/v4`.
|
|
19
|
+
- Docs aligned to official thinking / reasoning_effort / tool_stream fields (removed obsolete compat docs names).
|
|
20
|
+
- Resolved `thinking` / `reasoning_effort` / `tool_stream` win over raw compat spreads.
|
|
9
21
|
|
|
10
22
|
## [0.0.5] - 2026-07-16
|
|
11
23
|
|
package/README.md
CHANGED
|
@@ -1,25 +1,32 @@
|
|
|
1
1
|
# @arnilo/prism-provider-zai
|
|
2
2
|
|
|
3
|
-
Z.AI GLM provider package for Prism.
|
|
3
|
+
Z.AI GLM Chat Completions provider package for Prism.
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
|
-
import { createZaiProviderPackage } from "@arnilo/prism-provider-zai";
|
|
6
|
+
import { createZaiProviderPackage, listZaiModels } from "@arnilo/prism-provider-zai";
|
|
7
7
|
|
|
8
8
|
api.registerProviderPackage(createZaiProviderPackage({ apiKey: "fake-zai-key" }));
|
|
9
|
+
|
|
10
|
+
// Optional caller-gated discovery (never runs during setup):
|
|
11
|
+
const models = await listZaiModels({ apiKey: "fake-zai-key" });
|
|
12
|
+
api.registerProviderPackage(createZaiProviderPackage({ apiKey: "fake-zai-key", models }));
|
|
9
13
|
```
|
|
10
14
|
|
|
11
15
|
Exports:
|
|
12
|
-
- `createZaiProviderPackage()`
|
|
13
|
-
- `
|
|
14
|
-
- `
|
|
15
|
-
- `
|
|
16
|
+
- `createZaiProviderPackage()` / `createZaiProvider()`
|
|
17
|
+
- `defineZaiModel()` / `zaiModels` (featured offline bootstrap)
|
|
18
|
+
- `listZaiModels()` / `mapZaiModel()` (caller-gated OpenAI-compatible `GET /models`)
|
|
19
|
+
- `zaiThinking` / `zaiReasoningEffort` / `zaiToolStream` / `zaiClearThinking` / `zaiPreserveThinking`
|
|
20
|
+
- `ZAI_DEFAULT_BASE_URL` (`https://api.z.ai/api/paas/v4`)
|
|
16
21
|
|
|
17
22
|
Security defaults:
|
|
18
23
|
- No network calls during import, setup, build, or default tests.
|
|
19
24
|
- No automatic environment, file, keychain, or shell credential lookup.
|
|
20
|
-
- API keys are resolved per request from caller-supplied values or resolvers.
|
|
25
|
+
- API keys are resolved per request from caller-supplied values or resolvers and redacted from errors.
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
- `
|
|
27
|
+
Thinking / cache:
|
|
28
|
+
- Official body fields: `thinking` (`{ type: "enabled" | "disabled", clear_thinking? }`), `reasoning_effort` (GLM-5.2+), `tool_stream` (GLM-4.6+).
|
|
29
|
+
- Per-turn `options.compat` wins over `model.compat`.
|
|
30
|
+
- Preserved Thinking: set `clear_thinking: false` (and optionally `preserveThinking: true`) so prior thinking is replayed as `reasoning_content`.
|
|
31
|
+
- `kind: "implicit"` — GLM context caching is automatic; no explicit cache payload. `prompt_tokens_details.cached_tokens` / `cache_write_tokens` map to usage.
|
|
25
32
|
- Provider-owned headers (`content-type`, `authorization`) win over caller headers.
|
package/dist/index.d.ts
CHANGED
|
@@ -7,5 +7,6 @@ export interface ZaiProviderPackageOptions {
|
|
|
7
7
|
readonly models?: readonly ModelConfig[];
|
|
8
8
|
}
|
|
9
9
|
export declare function createZaiProviderPackage(options?: ZaiProviderPackageOptions): ProviderPackage;
|
|
10
|
-
export { defineZaiModel, zaiModels, type ZaiModelConfig } from "./models.js";
|
|
11
|
-
export { createZaiProvider, zaiBody, zaiEvents, type ZaiProviderOptions } from "./provider.js";
|
|
10
|
+
export { defineZaiModel, listZaiModels, mapZaiModel, zaiModels, type ListZaiModelsOptions, type ZaiModelConfig, type ZaiModelEntry, } from "./models.js";
|
|
11
|
+
export { createZaiProvider, toZaiMessage, zaiBody, zaiEvents, ZAI_DEFAULT_BASE_URL, type ZaiProviderOptions, } from "./provider.js";
|
|
12
|
+
export { zaiClearThinking, zaiPreserveThinking, zaiReasoningEffort, zaiThinking, zaiToolStream, } from "./thinking.js";
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ export function createZaiProviderPackage(options = {}) {
|
|
|
15
15
|
},
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
|
-
export { defineZaiModel, zaiModels } from "./models.js";
|
|
19
|
-
export { createZaiProvider, zaiBody, zaiEvents } from "./provider.js";
|
|
18
|
+
export { defineZaiModel, listZaiModels, mapZaiModel, zaiModels, } from "./models.js";
|
|
19
|
+
export { createZaiProvider, toZaiMessage, zaiBody, zaiEvents, ZAI_DEFAULT_BASE_URL, } from "./provider.js";
|
|
20
|
+
export { zaiClearThinking, zaiPreserveThinking, zaiReasoningEffort, zaiThinking, zaiToolStream, } from "./thinking.js";
|
|
20
21
|
//# sourceMappingURL=index.js.map
|
package/dist/models.d.ts
CHANGED
|
@@ -1,11 +1,61 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type CredentialValueSource, type JsonObject, type ModelConfig } from "@arnilo/prism";
|
|
2
2
|
export interface ZaiModelConfig extends Omit<ModelConfig, "provider" | "compat"> {
|
|
3
3
|
readonly provider?: "zai";
|
|
4
4
|
readonly compat?: JsonObject & {
|
|
5
|
+
/** Official deep-thinking switch (`boolean` or `{ type, clear_thinking? }`). */
|
|
5
6
|
readonly thinking?: boolean | JsonObject;
|
|
7
|
+
/** Official GLM-5.2+ effort: max | xhigh | high | medium | low | minimal | none. */
|
|
6
8
|
readonly reasoning_effort?: string;
|
|
9
|
+
/** Official GLM-4.6+ streaming tool-call arguments. */
|
|
7
10
|
readonly tool_stream?: boolean;
|
|
11
|
+
/** Official nested `thinking.clear_thinking` (also accepted at compat root). */
|
|
12
|
+
readonly clear_thinking?: boolean;
|
|
13
|
+
/** Prism-local: replay prior thinking as `reasoning_content` when not clearing. */
|
|
14
|
+
readonly preserveThinking?: boolean;
|
|
8
15
|
};
|
|
9
16
|
}
|
|
17
|
+
export interface ListZaiModelsOptions {
|
|
18
|
+
readonly apiKey?: CredentialValueSource;
|
|
19
|
+
readonly fetch?: typeof fetch;
|
|
20
|
+
/** Defaults to official international `https://api.z.ai/api/paas/v4`. */
|
|
21
|
+
readonly baseUrl?: string;
|
|
22
|
+
readonly signal?: AbortSignal;
|
|
23
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
24
|
+
readonly provider?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Sparse OpenAI-compatible `/models` entry. Z.AI does not publish a first-class
|
|
28
|
+
* list-models API page; this helper follows the OpenAI-compatible convention
|
|
29
|
+
* used by the Chat Completions base (`GET {baseUrl}/models`). Prefer featured
|
|
30
|
+
* `zaiModels` (docs-verified) when discovery is unavailable.
|
|
31
|
+
* @see https://docs.z.ai/api-reference/llm/chat-completion
|
|
32
|
+
* @see https://docs.z.ai/guides/overview/overview
|
|
33
|
+
*/
|
|
34
|
+
export interface ZaiModelEntry {
|
|
35
|
+
readonly id: string;
|
|
36
|
+
readonly object?: string;
|
|
37
|
+
readonly created?: number;
|
|
38
|
+
readonly owned_by?: string;
|
|
39
|
+
}
|
|
10
40
|
export declare function defineZaiModel(config: ZaiModelConfig): ModelConfig;
|
|
11
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Caller-gated Z.AI model discovery via OpenAI-compatible `GET /models`.
|
|
43
|
+
* Never invoked by `createZaiProviderPackage` — hosts call this and pass results
|
|
44
|
+
* via `models:` (or register themselves). Official docs list model codes on the
|
|
45
|
+
* Chat Completions page / overview; use featured `zaiModels` as offline bootstrap.
|
|
46
|
+
*/
|
|
47
|
+
export declare function listZaiModels(options?: ListZaiModelsOptions): Promise<ModelConfig[]>;
|
|
48
|
+
/**
|
|
49
|
+
* Map a sparse OpenAI-compatible `/models` entry to Prism `ModelConfig`.
|
|
50
|
+
* Limits / thinking defaults are inferred from official Chat Completions model codes.
|
|
51
|
+
*/
|
|
52
|
+
export declare function mapZaiModel(entry: ZaiModelEntry, options?: {
|
|
53
|
+
readonly provider?: string;
|
|
54
|
+
}): ModelConfig;
|
|
55
|
+
/**
|
|
56
|
+
* Featured offline bootstrap aliases — official Chat Completions model codes from
|
|
57
|
+
* https://docs.z.ai/api-reference/llm/chat-completion and overview context sizes.
|
|
58
|
+
* Refresh live ids via `listZaiModels()` when the OpenAI-compatible list endpoint
|
|
59
|
+
* is available to the account.
|
|
60
|
+
*/
|
|
61
|
+
export declare const zaiModels: readonly [ModelConfig, ModelConfig, ModelConfig, ModelConfig, ModelConfig, ModelConfig, ModelConfig];
|
package/dist/models.js
CHANGED
|
@@ -1,20 +1,189 @@
|
|
|
1
|
+
import { redactSecrets, resolveCredentialValue, } from "@arnilo/prism";
|
|
2
|
+
import { readBoundedResponseText } from "@arnilo/prism/providers/transport";
|
|
1
3
|
export function defineZaiModel(config) {
|
|
2
|
-
return {
|
|
4
|
+
return {
|
|
5
|
+
...config,
|
|
6
|
+
provider: "zai",
|
|
7
|
+
capabilities: {
|
|
8
|
+
input: ["text"],
|
|
9
|
+
output: ["text"],
|
|
10
|
+
reasoning: true,
|
|
11
|
+
tools: true,
|
|
12
|
+
streaming: true,
|
|
13
|
+
structuredOutput: "json_schema",
|
|
14
|
+
...config.capabilities,
|
|
15
|
+
},
|
|
16
|
+
cache: config.cache ?? { kind: "implicit" },
|
|
17
|
+
};
|
|
3
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Caller-gated Z.AI model discovery via OpenAI-compatible `GET /models`.
|
|
21
|
+
* Never invoked by `createZaiProviderPackage` — hosts call this and pass results
|
|
22
|
+
* via `models:` (or register themselves). Official docs list model codes on the
|
|
23
|
+
* Chat Completions page / overview; use featured `zaiModels` as offline bootstrap.
|
|
24
|
+
*/
|
|
25
|
+
export async function listZaiModels(options = {}) {
|
|
26
|
+
const provider = options.provider ?? "zai";
|
|
27
|
+
const baseUrl = (options.baseUrl ?? "https://api.z.ai/api/paas/v4").replace(/\/$/, "");
|
|
28
|
+
const token = await resolveCredentialValue(options.apiKey, { provider, name: "apiKey" });
|
|
29
|
+
const response = await (options.fetch ?? fetch)(`${baseUrl}/models`, {
|
|
30
|
+
method: "GET",
|
|
31
|
+
headers: { ...options.headers, ...(token ? { authorization: `Bearer ${token}` } : {}) },
|
|
32
|
+
signal: options.signal,
|
|
33
|
+
});
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
const body = await readBoundedResponseText(response, { secrets: [token] });
|
|
36
|
+
throw new Error(`Z.AI model discovery failed: ${response.status} ${redactSecrets(body, [token])}`);
|
|
37
|
+
}
|
|
38
|
+
const payload = (await response.json());
|
|
39
|
+
if (!Array.isArray(payload.data))
|
|
40
|
+
throw new Error("Z.AI model discovery response missing data array");
|
|
41
|
+
return payload.data.map((entry) => mapZaiModel(entry, { provider }));
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Map a sparse OpenAI-compatible `/models` entry to Prism `ModelConfig`.
|
|
45
|
+
* Limits / thinking defaults are inferred from official Chat Completions model codes.
|
|
46
|
+
*/
|
|
47
|
+
export function mapZaiModel(entry, options = {}) {
|
|
48
|
+
if (!entry || typeof entry.id !== "string" || entry.id.length === 0) {
|
|
49
|
+
throw new Error("Z.AI model entry missing id");
|
|
50
|
+
}
|
|
51
|
+
const id = entry.id;
|
|
52
|
+
const limits = limitsForZaiModel(id);
|
|
53
|
+
const reasoning = looksLikeReasoningModel(id);
|
|
54
|
+
return defineZaiModel({
|
|
55
|
+
provider: options.provider ?? "zai",
|
|
56
|
+
model: id,
|
|
57
|
+
displayName: id,
|
|
58
|
+
capabilities: {
|
|
59
|
+
input: looksLikeVisionModel(id) ? ["text", "image"] : ["text"],
|
|
60
|
+
output: ["text"],
|
|
61
|
+
reasoning,
|
|
62
|
+
tools: true,
|
|
63
|
+
streaming: true,
|
|
64
|
+
structuredOutput: "json_schema",
|
|
65
|
+
},
|
|
66
|
+
limits,
|
|
67
|
+
cache: { kind: "implicit" },
|
|
68
|
+
compat: cleanJson({
|
|
69
|
+
...thinkingDefaultsForModel(id),
|
|
70
|
+
zai: cleanJson({
|
|
71
|
+
owned_by: entry.owned_by,
|
|
72
|
+
created: entry.created,
|
|
73
|
+
}),
|
|
74
|
+
}),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Featured offline bootstrap aliases — official Chat Completions model codes from
|
|
79
|
+
* https://docs.z.ai/api-reference/llm/chat-completion and overview context sizes.
|
|
80
|
+
* Refresh live ids via `listZaiModels()` when the OpenAI-compatible list endpoint
|
|
81
|
+
* is available to the account.
|
|
82
|
+
*/
|
|
4
83
|
export const zaiModels = [
|
|
84
|
+
defineZaiModel({
|
|
85
|
+
model: "glm-5.2",
|
|
86
|
+
displayName: "GLM-5.2",
|
|
87
|
+
limits: { contextWindow: 1_000_000, maxOutputTokens: 128_000 },
|
|
88
|
+
cache: { kind: "implicit" },
|
|
89
|
+
compat: { thinking: true, reasoning_effort: "max", tool_stream: true },
|
|
90
|
+
}),
|
|
91
|
+
defineZaiModel({
|
|
92
|
+
model: "glm-5.1",
|
|
93
|
+
displayName: "GLM-5.1",
|
|
94
|
+
limits: { contextWindow: 200_000, maxOutputTokens: 128_000 },
|
|
95
|
+
cache: { kind: "implicit" },
|
|
96
|
+
compat: { thinking: true, tool_stream: true },
|
|
97
|
+
}),
|
|
98
|
+
defineZaiModel({
|
|
99
|
+
model: "glm-5",
|
|
100
|
+
displayName: "GLM-5",
|
|
101
|
+
limits: { contextWindow: 200_000, maxOutputTokens: 128_000 },
|
|
102
|
+
cache: { kind: "implicit" },
|
|
103
|
+
compat: { thinking: true, tool_stream: true },
|
|
104
|
+
}),
|
|
105
|
+
defineZaiModel({
|
|
106
|
+
model: "glm-5-turbo",
|
|
107
|
+
displayName: "GLM-5-Turbo",
|
|
108
|
+
limits: { contextWindow: 200_000, maxOutputTokens: 128_000 },
|
|
109
|
+
cache: { kind: "implicit" },
|
|
110
|
+
compat: { thinking: true, tool_stream: true },
|
|
111
|
+
}),
|
|
5
112
|
defineZaiModel({
|
|
6
113
|
model: "glm-4.7",
|
|
7
114
|
displayName: "GLM-4.7",
|
|
8
|
-
limits: { contextWindow:
|
|
115
|
+
limits: { contextWindow: 200_000, maxOutputTokens: 128_000 },
|
|
116
|
+
cache: { kind: "implicit" },
|
|
117
|
+
// Official: GLM-4.7 forced thinking when enabled; tool_stream supported (4.6+).
|
|
118
|
+
compat: { thinking: true, tool_stream: true },
|
|
119
|
+
}),
|
|
120
|
+
defineZaiModel({
|
|
121
|
+
model: "glm-4.6",
|
|
122
|
+
displayName: "GLM-4.6",
|
|
123
|
+
limits: { contextWindow: 200_000, maxOutputTokens: 128_000 },
|
|
9
124
|
cache: { kind: "implicit" },
|
|
10
|
-
compat: { thinking: true,
|
|
125
|
+
compat: { thinking: true, tool_stream: true },
|
|
11
126
|
}),
|
|
12
127
|
defineZaiModel({
|
|
13
128
|
model: "glm-4.5",
|
|
14
129
|
displayName: "GLM-4.5",
|
|
15
|
-
limits: { contextWindow: 128_000, maxOutputTokens:
|
|
130
|
+
limits: { contextWindow: 128_000, maxOutputTokens: 96_000 },
|
|
16
131
|
cache: { kind: "implicit" },
|
|
17
|
-
compat: { thinking: true
|
|
132
|
+
compat: { thinking: true },
|
|
18
133
|
}),
|
|
19
134
|
];
|
|
135
|
+
function thinkingDefaultsForModel(modelId) {
|
|
136
|
+
const id = modelId.toLowerCase();
|
|
137
|
+
if (!looksLikeReasoningModel(id))
|
|
138
|
+
return {};
|
|
139
|
+
const compat = { thinking: true };
|
|
140
|
+
if (supportsToolStream(id))
|
|
141
|
+
compat.tool_stream = true;
|
|
142
|
+
// Official: reasoning_effort only for GLM-5.2+.
|
|
143
|
+
if (supportsReasoningEffort(id))
|
|
144
|
+
compat.reasoning_effort = "max";
|
|
145
|
+
return compat;
|
|
146
|
+
}
|
|
147
|
+
function limitsForZaiModel(modelId) {
|
|
148
|
+
const id = modelId.toLowerCase();
|
|
149
|
+
if (id.includes("glm-5.2") || id.includes("glm-5-2")) {
|
|
150
|
+
return { contextWindow: 1_000_000, maxOutputTokens: 128_000 };
|
|
151
|
+
}
|
|
152
|
+
if (id.includes("glm-5") || id.includes("glm-4.7") || id.includes("glm-4.6")) {
|
|
153
|
+
return { contextWindow: 200_000, maxOutputTokens: 128_000 };
|
|
154
|
+
}
|
|
155
|
+
if (id.includes("glm-4.5v")) {
|
|
156
|
+
return { contextWindow: 64_000, maxOutputTokens: 16_000 };
|
|
157
|
+
}
|
|
158
|
+
if (id.includes("glm-4.5")) {
|
|
159
|
+
return { contextWindow: 128_000, maxOutputTokens: 96_000 };
|
|
160
|
+
}
|
|
161
|
+
if (id.includes("glm-4-32b") || id.includes("128k")) {
|
|
162
|
+
return { contextWindow: 128_000, maxOutputTokens: 16_000 };
|
|
163
|
+
}
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
function looksLikeReasoningModel(modelId) {
|
|
167
|
+
const id = modelId.toLowerCase();
|
|
168
|
+
return (id.includes("glm-5")
|
|
169
|
+
|| id.includes("glm-4.7")
|
|
170
|
+
|| id.includes("glm-4.6")
|
|
171
|
+
|| id.includes("glm-4.5"));
|
|
172
|
+
}
|
|
173
|
+
function looksLikeVisionModel(modelId) {
|
|
174
|
+
const id = modelId.toLowerCase();
|
|
175
|
+
return id.includes("glm-5v") || id.includes("glm-4.6v") || id.includes("glm-4.5v") || id.endsWith("v");
|
|
176
|
+
}
|
|
177
|
+
function supportsToolStream(modelId) {
|
|
178
|
+
const id = modelId.toLowerCase();
|
|
179
|
+
// Official: tool_stream supported by GLM-4.6 and above.
|
|
180
|
+
return id.includes("glm-5") || id.includes("glm-4.7") || id.includes("glm-4.6");
|
|
181
|
+
}
|
|
182
|
+
function supportsReasoningEffort(modelId) {
|
|
183
|
+
const id = modelId.toLowerCase();
|
|
184
|
+
return id.includes("glm-5.2") || id.includes("glm-5-2");
|
|
185
|
+
}
|
|
186
|
+
function cleanJson(value) {
|
|
187
|
+
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
|
|
188
|
+
}
|
|
20
189
|
//# sourceMappingURL=models.js.map
|
package/dist/provider.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import type { AIProvider, JsonObject, ProviderEvent, ProviderRequest } from "@arnilo/prism";
|
|
1
|
+
import type { AIProvider, JsonObject, Message, ModelConfig, ProviderEvent, ProviderRequest } from "@arnilo/prism";
|
|
2
2
|
import { type CredentialValueSource } from "@arnilo/prism";
|
|
3
|
+
/** Official international Chat Completions base (China `open.bigmodel.cn` remains overridable). */
|
|
4
|
+
export declare const ZAI_DEFAULT_BASE_URL = "https://api.z.ai/api/paas/v4";
|
|
3
5
|
export interface ZaiProviderOptions {
|
|
4
6
|
readonly id?: string;
|
|
5
7
|
readonly baseUrl?: string;
|
|
@@ -9,3 +11,10 @@ export interface ZaiProviderOptions {
|
|
|
9
11
|
export declare function createZaiProvider(options?: ZaiProviderOptions): AIProvider;
|
|
10
12
|
export declare function zaiBody(request: ProviderRequest): JsonObject;
|
|
11
13
|
export declare function zaiEvents(body: ReadableStream<Uint8Array>, signal?: AbortSignal): AsyncIterable<ProviderEvent>;
|
|
14
|
+
/**
|
|
15
|
+
* Serialize Prism messages for Z.AI Chat Completions.
|
|
16
|
+
* Prior thinking blocks become `reasoning_content` when Preserved Thinking is active;
|
|
17
|
+
* otherwise they are dropped (never flattened into visible text).
|
|
18
|
+
* @see https://docs.z.ai/guides/capabilities/thinking-mode
|
|
19
|
+
*/
|
|
20
|
+
export declare function toZaiMessage(message: Message, model: ModelConfig, preserveThinking?: boolean): JsonObject;
|
package/dist/provider.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { assertStructuredOutputRequestSupported, providerDone, providerError, providerTextDelta, providerThinkingDelta, providerToolCall, providerToolCallDelta, providerUsage, resolveCredentialValue, toolCallContent } from "@arnilo/prism";
|
|
2
|
-
import { applyOpenAIChatStructuredOutput, mapOpenAIChatUsage,
|
|
1
|
+
import { assertStructuredOutputRequestSupported, providerDone, providerError, providerTextDelta, providerThinkingDelta, providerToolCall, providerToolCallDelta, providerUsage, resolveCredentialValue, toolCallContent, } from "@arnilo/prism";
|
|
2
|
+
import { applyOpenAIChatStructuredOutput, mapOpenAIChatUsage, serializeOpenAITool, } from "@arnilo/prism/providers/openai";
|
|
3
3
|
import { parseJsonObjectArguments, readBoundedResponseText, readSseData, } from "@arnilo/prism/providers/transport";
|
|
4
|
-
import { zaiReasoningEffort, zaiThinking, zaiToolStream } from "./thinking.js";
|
|
4
|
+
import { zaiPreserveThinking, zaiReasoningEffort, zaiThinking, zaiToolStream, } from "./thinking.js";
|
|
5
|
+
/** Official international Chat Completions base (China `open.bigmodel.cn` remains overridable). */
|
|
6
|
+
export const ZAI_DEFAULT_BASE_URL = "https://api.z.ai/api/paas/v4";
|
|
5
7
|
export function createZaiProvider(options = {}) {
|
|
6
8
|
const id = options.id ?? "zai";
|
|
7
|
-
const baseUrl = (options.baseUrl ??
|
|
9
|
+
const baseUrl = (options.baseUrl ?? ZAI_DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
8
10
|
return {
|
|
9
11
|
id,
|
|
10
12
|
async *generate(request) {
|
|
@@ -15,7 +17,11 @@ export function createZaiProvider(options = {}) {
|
|
|
15
17
|
try {
|
|
16
18
|
const response = await (options.fetch ?? fetch)(`${baseUrl}/chat/completions`, {
|
|
17
19
|
method: "POST",
|
|
18
|
-
headers: {
|
|
20
|
+
headers: {
|
|
21
|
+
...request.options?.headers,
|
|
22
|
+
"content-type": "application/json",
|
|
23
|
+
...(token ? { authorization: `Bearer ${token}` } : {}),
|
|
24
|
+
},
|
|
19
25
|
body: JSON.stringify(zaiBody(request)),
|
|
20
26
|
signal: request.signal,
|
|
21
27
|
});
|
|
@@ -35,18 +41,21 @@ export function createZaiProvider(options = {}) {
|
|
|
35
41
|
export function zaiBody(request) {
|
|
36
42
|
assertStructuredOutputRequestSupported(request.model, request.options);
|
|
37
43
|
const { maxTokens, ...parameters } = request.model.parameters ?? {};
|
|
44
|
+
const compatRest = stripZaiManagedCompat(request.options?.compat);
|
|
45
|
+
const preserveThinking = zaiPreserveThinking(request);
|
|
38
46
|
const body = {
|
|
39
47
|
model: request.model.model,
|
|
40
|
-
messages: request.messages.map((message) =>
|
|
48
|
+
messages: request.messages.map((message) => toZaiMessage(message, request.model, preserveThinking)),
|
|
41
49
|
tools: request.tools?.map(serializeOpenAITool),
|
|
42
50
|
stream: true,
|
|
43
|
-
tool_stream: zaiToolStream(request),
|
|
44
|
-
thinking: zaiThinking(request),
|
|
45
|
-
reasoning_effort: zaiReasoningEffort(request),
|
|
46
51
|
...parameters,
|
|
47
52
|
max_tokens: maxTokens ?? request.model.limits?.maxOutputTokens,
|
|
48
|
-
...
|
|
53
|
+
...compatRest,
|
|
49
54
|
...request.options?.extra,
|
|
55
|
+
// Resolved official fields win over raw compat/extra escape hatches.
|
|
56
|
+
thinking: zaiThinking(request),
|
|
57
|
+
reasoning_effort: zaiReasoningEffort(request),
|
|
58
|
+
tool_stream: zaiToolStream(request),
|
|
50
59
|
};
|
|
51
60
|
applyOpenAIChatStructuredOutput(body, request.options?.structuredOutput);
|
|
52
61
|
return clean(body);
|
|
@@ -58,11 +67,12 @@ export async function* zaiEvents(body, signal) {
|
|
|
58
67
|
if (data === "[DONE]")
|
|
59
68
|
break;
|
|
60
69
|
const chunk = JSON.parse(data);
|
|
61
|
-
usage = mapOpenAIChatUsage(chunk.usage) ?? usage;
|
|
62
70
|
if (chunk.usage) {
|
|
63
71
|
const mapped = mapOpenAIChatUsage(chunk.usage);
|
|
64
|
-
if (mapped)
|
|
72
|
+
if (mapped) {
|
|
73
|
+
usage = mapped;
|
|
65
74
|
yield providerUsage(mapped);
|
|
75
|
+
}
|
|
66
76
|
}
|
|
67
77
|
for (const choice of chunk.choices ?? []) {
|
|
68
78
|
const delta = choice.delta ?? {};
|
|
@@ -77,7 +87,12 @@ export async function* zaiEvents(body, signal) {
|
|
|
77
87
|
current.name = tool.function?.name ?? current.name;
|
|
78
88
|
current.argumentsText += tool.function?.arguments ?? "";
|
|
79
89
|
tools.set(index, current);
|
|
80
|
-
yield providerToolCallDelta({
|
|
90
|
+
yield providerToolCallDelta({
|
|
91
|
+
index,
|
|
92
|
+
id: tool.id,
|
|
93
|
+
name: tool.function?.name,
|
|
94
|
+
argumentsText: tool.function?.arguments,
|
|
95
|
+
});
|
|
81
96
|
}
|
|
82
97
|
}
|
|
83
98
|
}
|
|
@@ -88,6 +103,82 @@ export async function* zaiEvents(body, signal) {
|
|
|
88
103
|
}
|
|
89
104
|
yield providerDone(usage);
|
|
90
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Serialize Prism messages for Z.AI Chat Completions.
|
|
108
|
+
* Prior thinking blocks become `reasoning_content` when Preserved Thinking is active;
|
|
109
|
+
* otherwise they are dropped (never flattened into visible text).
|
|
110
|
+
* @see https://docs.z.ai/guides/capabilities/thinking-mode
|
|
111
|
+
*/
|
|
112
|
+
export function toZaiMessage(message, model, preserveThinking = false) {
|
|
113
|
+
const capabilities = model.capabilities ?? {};
|
|
114
|
+
const thinkingParts = message.content.filter((part) => part.type === "thinking");
|
|
115
|
+
const reasoningContent = preserveThinking && thinkingParts.length > 0
|
|
116
|
+
? thinkingParts.map((part) => part.text).join("\n")
|
|
117
|
+
: undefined;
|
|
118
|
+
if (message.role === "tool") {
|
|
119
|
+
const result = message.content.find((part) => part.type === "tool_result");
|
|
120
|
+
return {
|
|
121
|
+
role: "tool",
|
|
122
|
+
tool_call_id: result?.toolCallId ?? "",
|
|
123
|
+
content: result ? JSON.stringify(result.result ?? result.error ?? null) : "",
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (message.role === "assistant") {
|
|
127
|
+
const toolCalls = message.content.filter((part) => part.type === "tool_call");
|
|
128
|
+
const textParts = message.content.filter((part) => part.type === "text");
|
|
129
|
+
if (toolCalls.length > 0) {
|
|
130
|
+
return clean({
|
|
131
|
+
role: "assistant",
|
|
132
|
+
content: textParts.map((part) => part.text).join("\n") || null,
|
|
133
|
+
tool_calls: toolCalls.map((call) => ({
|
|
134
|
+
id: call.id,
|
|
135
|
+
type: "function",
|
|
136
|
+
function: { name: call.name, arguments: JSON.stringify(call.arguments) },
|
|
137
|
+
})),
|
|
138
|
+
reasoning_content: reasoningContent,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const content = [];
|
|
143
|
+
for (const part of message.content) {
|
|
144
|
+
if (part.type === "text") {
|
|
145
|
+
content.push({ type: "text", text: part.text });
|
|
146
|
+
}
|
|
147
|
+
else if (part.type === "thinking") {
|
|
148
|
+
// Handled via reasoning_content when preserving; otherwise dropped.
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
else if (part.type === "image") {
|
|
152
|
+
if (!capabilities.input?.includes("image")) {
|
|
153
|
+
throw new Error("Z.AI request includes image but model does not declare image input capability");
|
|
154
|
+
}
|
|
155
|
+
const url = part.url ?? (part.data ? `data:${part.mimeType ?? "image/png"};base64,${part.data}` : undefined);
|
|
156
|
+
if (!url)
|
|
157
|
+
throw new Error("Z.AI image block missing url or data");
|
|
158
|
+
content.push({ type: "image_url", image_url: { url } });
|
|
159
|
+
}
|
|
160
|
+
else if (part.type === "audio" || part.type === "file" || part.type === "document") {
|
|
161
|
+
throw new Error(`Z.AI Chat Completions does not support ${part.type} content blocks`);
|
|
162
|
+
}
|
|
163
|
+
else if (part.type === "tool_call") {
|
|
164
|
+
throw new Error("Z.AI assistant tool_call blocks must be the only content on the message");
|
|
165
|
+
}
|
|
166
|
+
else if (part.type === "tool_result") {
|
|
167
|
+
throw new Error("Z.AI tool_result blocks must appear in role=tool messages");
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (content.length === 1 && content[0].type === "text") {
|
|
171
|
+
return clean({ role: message.role, content: content[0].text, reasoning_content: reasoningContent });
|
|
172
|
+
}
|
|
173
|
+
return clean({ role: message.role, content, reasoning_content: reasoningContent });
|
|
174
|
+
}
|
|
175
|
+
/** Drop Prism-managed compat keys so they are not double-emitted / overwrite resolved fields. */
|
|
176
|
+
function stripZaiManagedCompat(compat) {
|
|
177
|
+
if (!compat)
|
|
178
|
+
return {};
|
|
179
|
+
const { thinking: _thinking, reasoning_effort: _reasoningEffort, reasoningEffort: _reasoningEffortCamel, tool_stream: _toolStream, clear_thinking: _clearThinking, clearThinking: _clearThinkingCamel, preserveThinking: _preserveThinking, preserve_thinking: _preserveThinkingSnake, ...rest } = compat;
|
|
180
|
+
return rest;
|
|
181
|
+
}
|
|
91
182
|
function clean(value) {
|
|
92
183
|
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
|
|
93
184
|
}
|
package/dist/thinking.d.ts
CHANGED
|
@@ -1,4 +1,30 @@
|
|
|
1
1
|
import type { JsonObject, ProviderRequest } from "@arnilo/prism";
|
|
2
|
+
/**
|
|
3
|
+
* Map Prism compat → official Z.AI `thinking` body object.
|
|
4
|
+
* @see https://docs.z.ai/guides/capabilities/thinking
|
|
5
|
+
* @see https://docs.z.ai/api-reference/llm/chat-completion
|
|
6
|
+
*/
|
|
2
7
|
export declare function zaiThinking(request: ProviderRequest): JsonObject | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Official `reasoning_effort` (GLM-5.2+). Request `options.compat` wins over model defaults.
|
|
10
|
+
* Allowed: max | xhigh | high | medium | low | minimal | none.
|
|
11
|
+
* @see https://docs.z.ai/guides/capabilities/thinking
|
|
12
|
+
*/
|
|
3
13
|
export declare function zaiReasoningEffort(request: ProviderRequest): string | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Official `tool_stream` (GLM-4.6+). Request wins over model defaults.
|
|
16
|
+
* @see https://docs.z.ai/guides/capabilities/stream-tool
|
|
17
|
+
*/
|
|
4
18
|
export declare function zaiToolStream(request: ProviderRequest): boolean | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Official nested `thinking.clear_thinking` (default true on the wire).
|
|
21
|
+
* When false, prior `reasoning_content` must be replayed for Preserved Thinking.
|
|
22
|
+
* @see https://docs.z.ai/guides/capabilities/thinking-mode
|
|
23
|
+
*/
|
|
24
|
+
export declare function zaiClearThinking(request: ProviderRequest): boolean | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Prism-local: when true (and clear_thinking is not true), replay prior thinking
|
|
27
|
+
* blocks as assistant `reasoning_content`. Official Preserved Thinking also requires
|
|
28
|
+
* `thinking.clear_thinking: false`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function zaiPreserveThinking(request: ProviderRequest): boolean;
|
package/dist/thinking.js
CHANGED
|
@@ -1,17 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map Prism compat → official Z.AI `thinking` body object.
|
|
3
|
+
* @see https://docs.z.ai/guides/capabilities/thinking
|
|
4
|
+
* @see https://docs.z.ai/api-reference/llm/chat-completion
|
|
5
|
+
*/
|
|
1
6
|
export function zaiThinking(request) {
|
|
2
7
|
const value = request.options?.compat?.thinking ?? request.model.compat?.thinking;
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
const clearThinking = zaiClearThinking(request);
|
|
9
|
+
if (value === false || request.options?.cacheRetention === "none") {
|
|
10
|
+
return cleanThinking({ type: "disabled", clear_thinking: clearThinking });
|
|
11
|
+
}
|
|
12
|
+
if (value && typeof value === "object") {
|
|
13
|
+
return cleanThinking({
|
|
14
|
+
...value,
|
|
15
|
+
...(clearThinking !== undefined ? { clear_thinking: clearThinking } : {}),
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
if (value === true) {
|
|
19
|
+
return cleanThinking({ type: "enabled", clear_thinking: clearThinking });
|
|
20
|
+
}
|
|
21
|
+
// Hosts may set only `clear_thinking` / preserve flags without an explicit thinking switch.
|
|
22
|
+
if (clearThinking !== undefined) {
|
|
23
|
+
return cleanThinking({ type: "enabled", clear_thinking: clearThinking });
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
8
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Official `reasoning_effort` (GLM-5.2+). Request `options.compat` wins over model defaults.
|
|
29
|
+
* Allowed: max | xhigh | high | medium | low | minimal | none.
|
|
30
|
+
* @see https://docs.z.ai/guides/capabilities/thinking
|
|
31
|
+
*/
|
|
9
32
|
export function zaiReasoningEffort(request) {
|
|
10
|
-
const effort = request.options?.compat?.reasoning_effort
|
|
33
|
+
const effort = request.options?.compat?.reasoning_effort
|
|
34
|
+
?? request.options?.compat?.reasoningEffort
|
|
35
|
+
?? request.model.compat?.reasoning_effort;
|
|
11
36
|
return typeof effort === "string" ? effort : undefined;
|
|
12
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Official `tool_stream` (GLM-4.6+). Request wins over model defaults.
|
|
40
|
+
* @see https://docs.z.ai/guides/capabilities/stream-tool
|
|
41
|
+
*/
|
|
13
42
|
export function zaiToolStream(request) {
|
|
14
43
|
const value = request.options?.compat?.tool_stream ?? request.model.compat?.tool_stream;
|
|
15
44
|
return typeof value === "boolean" ? value : undefined;
|
|
16
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Official nested `thinking.clear_thinking` (default true on the wire).
|
|
48
|
+
* When false, prior `reasoning_content` must be replayed for Preserved Thinking.
|
|
49
|
+
* @see https://docs.z.ai/guides/capabilities/thinking-mode
|
|
50
|
+
*/
|
|
51
|
+
export function zaiClearThinking(request) {
|
|
52
|
+
const fromThinkingObject = readClearThinkingFromObject(request.options?.compat?.thinking ?? request.model.compat?.thinking);
|
|
53
|
+
if (fromThinkingObject !== undefined)
|
|
54
|
+
return fromThinkingObject;
|
|
55
|
+
const value = request.options?.compat?.clear_thinking
|
|
56
|
+
?? request.options?.compat?.clearThinking
|
|
57
|
+
?? request.model.compat?.clear_thinking;
|
|
58
|
+
return typeof value === "boolean" ? value : undefined;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Prism-local: when true (and clear_thinking is not true), replay prior thinking
|
|
62
|
+
* blocks as assistant `reasoning_content`. Official Preserved Thinking also requires
|
|
63
|
+
* `thinking.clear_thinking: false`.
|
|
64
|
+
*/
|
|
65
|
+
export function zaiPreserveThinking(request) {
|
|
66
|
+
const clear = zaiClearThinking(request);
|
|
67
|
+
if (clear === true)
|
|
68
|
+
return false;
|
|
69
|
+
const value = request.options?.compat?.preserveThinking
|
|
70
|
+
?? request.options?.compat?.preserve_thinking
|
|
71
|
+
?? request.model.compat?.preserveThinking
|
|
72
|
+
?? request.model.compat?.preserve_thinking;
|
|
73
|
+
if (typeof value === "boolean")
|
|
74
|
+
return value;
|
|
75
|
+
// clear_thinking:false implies preserved thinking even without an explicit preserve flag.
|
|
76
|
+
return clear === false;
|
|
77
|
+
}
|
|
78
|
+
function readClearThinkingFromObject(value) {
|
|
79
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
80
|
+
return undefined;
|
|
81
|
+
const clear = value.clear_thinking ?? value.clearThinking;
|
|
82
|
+
return typeof clear === "boolean" ? clear : undefined;
|
|
83
|
+
}
|
|
84
|
+
function cleanThinking(value) {
|
|
85
|
+
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
|
|
86
|
+
}
|
|
17
87
|
//# sourceMappingURL=thinking.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arnilo/prism-provider-zai",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "Z.AI provider package for Prism.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"pack:dry-run": "npm pack --dry-run"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
|
-
"@arnilo/prism": "0.0.
|
|
28
|
+
"@arnilo/prism": "0.0.6"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@arnilo/prism": "file:../.."
|