@moxxy/plugin-provider-zai 0.21.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/LICENSE +21 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -0
- package/dist/models.d.ts +16 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +31 -0
- package/dist/models.js.map +1 -0
- package/package.json +66 -0
- package/src/index.baseurl.test.ts +107 -0
- package/src/index.test.ts +74 -0
- package/src/index.ts +79 -0
- package/src/models.ts +35 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Moxxy (moxxy.ai)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { glmModels } from './models.js';
|
|
2
|
+
export { glmModels };
|
|
3
|
+
/**
|
|
4
|
+
* z.ai in "API key" mode: the standard, pay-as-you-go endpoint, which speaks
|
|
5
|
+
* the OpenAI Chat Completions protocol. Reuses the shared
|
|
6
|
+
* {@link defineOpenAICompatProvider} with the vendor slug + base URL + GLM
|
|
7
|
+
* catalog forced on (so usage stats, provider events and error context
|
|
8
|
+
* attribute to `zai`, not `openai`).
|
|
9
|
+
*
|
|
10
|
+
* `resolveApiKey` refuses an absent/empty key rather than letting
|
|
11
|
+
* {@link OpenAIProvider} fall back to `process.env.OPENAI_API_KEY` — the
|
|
12
|
+
* baseURL is pinned to api.z.ai, so that fallback would silently ship the
|
|
13
|
+
* user's OpenAI credential to a third-party host on a misconfigured provider.
|
|
14
|
+
*/
|
|
15
|
+
export declare const zaiProviderDef: import("@moxxy/sdk").ProviderDef;
|
|
16
|
+
/**
|
|
17
|
+
* z.ai in "plan" mode: the GLM Coding Plan, billed against a subscription and
|
|
18
|
+
* served over an Anthropic Messages-compatible endpoint (the same one Claude
|
|
19
|
+
* Code targets). Reuses {@link AnthropicProvider} with the GLM catalog and the
|
|
20
|
+
* z.ai base URL — only the credential + endpoint differ from the plain
|
|
21
|
+
* `anthropic` provider. No `validateKey`: z.ai's Anthropic endpoint exposes no
|
|
22
|
+
* free model-list probe, and the plan key is proven by the first request.
|
|
23
|
+
*/
|
|
24
|
+
export declare const zaiCodingPlanProviderDef: import("@moxxy/sdk").ProviderDef;
|
|
25
|
+
export declare const zaiPlugin: import("@moxxy/sdk").Plugin;
|
|
26
|
+
export default zaiPlugin;
|
|
27
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,OAAO,EAAE,SAAS,EAAE,CAAC;AAQrB;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc,kCAazB,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,kCAsBnC,CAAC;AAEH,eAAO,MAAM,SAAS,6BAIpB,CAAC;AAEH,eAAe,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { defineProvider, definePlugin } from '@moxxy/sdk';
|
|
2
|
+
import { defineOpenAICompatProvider, pickOpenAICompatConfig } from '@moxxy/plugin-provider-openai';
|
|
3
|
+
import { AnthropicProvider } from '@moxxy/plugin-provider-anthropic';
|
|
4
|
+
import { glmModels } from './models.js';
|
|
5
|
+
export { glmModels };
|
|
6
|
+
/** Pay-as-you-go OpenAI-compatible endpoint (standard z.ai API key). */
|
|
7
|
+
const ZAI_OPENAI_BASE_URL = 'https://api.z.ai/api/paas/v4';
|
|
8
|
+
/** GLM Coding Plan — Anthropic Messages-compatible endpoint (the one Claude Code uses). */
|
|
9
|
+
const ZAI_ANTHROPIC_BASE_URL = 'https://api.z.ai/api/anthropic';
|
|
10
|
+
const ZAI_DEFAULT_MODEL = 'glm-4.6';
|
|
11
|
+
/**
|
|
12
|
+
* z.ai in "API key" mode: the standard, pay-as-you-go endpoint, which speaks
|
|
13
|
+
* the OpenAI Chat Completions protocol. Reuses the shared
|
|
14
|
+
* {@link defineOpenAICompatProvider} with the vendor slug + base URL + GLM
|
|
15
|
+
* catalog forced on (so usage stats, provider events and error context
|
|
16
|
+
* attribute to `zai`, not `openai`).
|
|
17
|
+
*
|
|
18
|
+
* `resolveApiKey` refuses an absent/empty key rather than letting
|
|
19
|
+
* {@link OpenAIProvider} fall back to `process.env.OPENAI_API_KEY` — the
|
|
20
|
+
* baseURL is pinned to api.z.ai, so that fallback would silently ship the
|
|
21
|
+
* user's OpenAI credential to a third-party host on a misconfigured provider.
|
|
22
|
+
*/
|
|
23
|
+
export const zaiProviderDef = defineOpenAICompatProvider({
|
|
24
|
+
name: 'zai',
|
|
25
|
+
baseURL: ZAI_OPENAI_BASE_URL,
|
|
26
|
+
defaultModel: ZAI_DEFAULT_MODEL,
|
|
27
|
+
models: glmModels,
|
|
28
|
+
resolveApiKey: (cfg) => {
|
|
29
|
+
if (!cfg.apiKey)
|
|
30
|
+
throw new Error('zai requires an API key (set the zai provider apiKey or ZAI_API_KEY)');
|
|
31
|
+
return cfg.apiKey;
|
|
32
|
+
},
|
|
33
|
+
auth: {
|
|
34
|
+
kind: 'apiKey',
|
|
35
|
+
hint: 'z.ai API key (pay-as-you-go) from https://z.ai/manage-apikey/apikey-list',
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
/**
|
|
39
|
+
* z.ai in "plan" mode: the GLM Coding Plan, billed against a subscription and
|
|
40
|
+
* served over an Anthropic Messages-compatible endpoint (the same one Claude
|
|
41
|
+
* Code targets). Reuses {@link AnthropicProvider} with the GLM catalog and the
|
|
42
|
+
* z.ai base URL — only the credential + endpoint differ from the plain
|
|
43
|
+
* `anthropic` provider. No `validateKey`: z.ai's Anthropic endpoint exposes no
|
|
44
|
+
* free model-list probe, and the plan key is proven by the first request.
|
|
45
|
+
*/
|
|
46
|
+
export const zaiCodingPlanProviderDef = defineProvider({
|
|
47
|
+
name: 'zai-coding-plan',
|
|
48
|
+
models: [...glmModels],
|
|
49
|
+
createClient: (config) => {
|
|
50
|
+
const cfg = pickOpenAICompatConfig(config);
|
|
51
|
+
// Refuse to construct without a key: AnthropicProvider falls back to
|
|
52
|
+
// process.env.ANTHROPIC_API_KEY when apiKey is absent, while baseURL is
|
|
53
|
+
// pinned to z.ai's Anthropic endpoint — forwarding an empty key would
|
|
54
|
+
// silently exfiltrate the user's real Anthropic credential to api.z.ai.
|
|
55
|
+
if (!cfg.apiKey)
|
|
56
|
+
throw new Error('zai-coding-plan requires an API key (set the zai-coding-plan provider apiKey)');
|
|
57
|
+
return new AnthropicProvider({
|
|
58
|
+
apiKey: cfg.apiKey,
|
|
59
|
+
name: 'zai-coding-plan',
|
|
60
|
+
baseURL: cfg.baseURL ?? ZAI_ANTHROPIC_BASE_URL,
|
|
61
|
+
defaultModel: cfg.defaultModel ?? ZAI_DEFAULT_MODEL,
|
|
62
|
+
models: glmModels,
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
auth: {
|
|
66
|
+
kind: 'apiKey',
|
|
67
|
+
hint: 'z.ai GLM Coding Plan key (Anthropic-compatible endpoint, like Claude Code)',
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
export const zaiPlugin = definePlugin({
|
|
71
|
+
name: '@moxxy/plugin-provider-zai',
|
|
72
|
+
version: '0.0.0',
|
|
73
|
+
providers: [zaiProviderDef, zaiCodingPlanProviderDef],
|
|
74
|
+
});
|
|
75
|
+
export default zaiPlugin;
|
|
76
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,0BAA0B,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACnG,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,wEAAwE;AACxE,MAAM,mBAAmB,GAAG,8BAA8B,CAAC;AAC3D,2FAA2F;AAC3F,MAAM,sBAAsB,GAAG,gCAAgC,CAAC;AAChE,MAAM,iBAAiB,GAAG,SAAS,CAAC;AAEpC;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,0BAA0B,CAAC;IACvD,IAAI,EAAE,KAAK;IACX,OAAO,EAAE,mBAAmB;IAC5B,YAAY,EAAE,iBAAiB;IAC/B,MAAM,EAAE,SAAS;IACjB,aAAa,EAAE,CAAC,GAAG,EAAE,EAAE;QACrB,IAAI,CAAC,GAAG,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,sEAAsE,CAAC,CAAC;QACzG,OAAO,GAAG,CAAC,MAAM,CAAC;IACpB,CAAC;IACD,IAAI,EAAE;QACJ,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,0EAA0E;KACjF;CACF,CAAC,CAAC;AAEH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,cAAc,CAAC;IACrD,IAAI,EAAE,iBAAiB;IACvB,MAAM,EAAE,CAAC,GAAG,SAAS,CAAC;IACtB,YAAY,EAAE,CAAC,MAAM,EAAE,EAAE;QACvB,MAAM,GAAG,GAAG,sBAAsB,CAAC,MAAM,CAAC,CAAC;QAC3C,qEAAqE;QACrE,wEAAwE;QACxE,sEAAsE;QACtE,wEAAwE;QACxE,IAAI,CAAC,GAAG,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,+EAA+E,CAAC,CAAC;QAClH,OAAO,IAAI,iBAAiB,CAAC;YAC3B,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,IAAI,EAAE,iBAAiB;YACvB,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,sBAAsB;YAC9C,YAAY,EAAE,GAAG,CAAC,YAAY,IAAI,iBAAiB;YACnD,MAAM,EAAE,SAAS;SAClB,CAAC,CAAC;IACL,CAAC;IACD,IAAI,EAAE;QACJ,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,4EAA4E;KACnF;CACF,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,SAAS,GAAG,YAAY,CAAC;IACpC,IAAI,EAAE,4BAA4B;IAClC,OAAO,EAAE,OAAO;IAChB,SAAS,EAAE,CAAC,cAAc,EAAE,wBAAwB,CAAC;CACtD,CAAC,CAAC;AAEH,eAAe,SAAS,CAAC"}
|
package/dist/models.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ModelDescriptor } from '@moxxy/sdk';
|
|
2
|
+
/**
|
|
3
|
+
* z.ai (Zhipu) GLM model catalog, as of 2026-06. Shared by both the
|
|
4
|
+
* pay-as-you-go `zai` provider (OpenAI-compatible endpoint) and the
|
|
5
|
+
* `zai-coding-plan` provider (Anthropic-compatible endpoint) — the GLM model
|
|
6
|
+
* ids are identical across both surfaces; only the transport/billing differ.
|
|
7
|
+
*
|
|
8
|
+
* Numbers are the documented limits as of June 2026 (verify against
|
|
9
|
+
* https://docs.z.ai/guides/llm). GLM-5.2 is the coding-first flagship with a
|
|
10
|
+
* usable 1M-token context (released 2026-06-13); the GLM-4.5 tier tops out at
|
|
11
|
+
* 128k. Only the `-v` (vision) variants accept image input. An unlisted model
|
|
12
|
+
* id still streams fine — it's passed straight through to the endpoint; the
|
|
13
|
+
* catalog only drives context-window budgets and capability gating.
|
|
14
|
+
*/
|
|
15
|
+
export declare const glmModels: ReadonlyArray<ModelDescriptor>;
|
|
16
|
+
//# sourceMappingURL=models.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,SAAS,EAAE,aAAa,CAAC,eAAe,CAmBpD,CAAC"}
|
package/dist/models.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* z.ai (Zhipu) GLM model catalog, as of 2026-06. Shared by both the
|
|
3
|
+
* pay-as-you-go `zai` provider (OpenAI-compatible endpoint) and the
|
|
4
|
+
* `zai-coding-plan` provider (Anthropic-compatible endpoint) — the GLM model
|
|
5
|
+
* ids are identical across both surfaces; only the transport/billing differ.
|
|
6
|
+
*
|
|
7
|
+
* Numbers are the documented limits as of June 2026 (verify against
|
|
8
|
+
* https://docs.z.ai/guides/llm). GLM-5.2 is the coding-first flagship with a
|
|
9
|
+
* usable 1M-token context (released 2026-06-13); the GLM-4.5 tier tops out at
|
|
10
|
+
* 128k. Only the `-v` (vision) variants accept image input. An unlisted model
|
|
11
|
+
* id still streams fine — it's passed straight through to the endpoint; the
|
|
12
|
+
* catalog only drives context-window budgets and capability gating.
|
|
13
|
+
*/
|
|
14
|
+
export const glmModels = [
|
|
15
|
+
// GLM-5 family: coding-first frontier. 5.2 carries a usable 1M context.
|
|
16
|
+
// These are reasoning models; the shared OpenAIProvider already streams their
|
|
17
|
+
// `reasoning_content` deltas, so supportsReasoning gates reasoning_effort +
|
|
18
|
+
// reasoning-stream surfacing on (without it the capability is dead upstream).
|
|
19
|
+
{ id: 'glm-5.2', contextWindow: 1_000_000, maxOutputTokens: 128_000, supportsTools: true, supportsStreaming: true, supportsReasoning: true },
|
|
20
|
+
{ id: 'glm-5.1', contextWindow: 200_000, maxOutputTokens: 128_000, supportsTools: true, supportsStreaming: true, supportsReasoning: true },
|
|
21
|
+
{ id: 'glm-5', contextWindow: 200_000, maxOutputTokens: 128_000, supportsTools: true, supportsStreaming: true, supportsReasoning: true },
|
|
22
|
+
// GLM-4.6: prior flagship, 200k context, strong agentic-coding scores.
|
|
23
|
+
{ id: 'glm-4.6', contextWindow: 200_000, maxOutputTokens: 128_000, supportsTools: true, supportsStreaming: true, supportsReasoning: true },
|
|
24
|
+
// GLM-4.5 tier: general (4.5), lightweight (-air), free/fast (-flash).
|
|
25
|
+
{ id: 'glm-4.5', contextWindow: 131_072, maxOutputTokens: 98_304, supportsTools: true, supportsStreaming: true },
|
|
26
|
+
{ id: 'glm-4.5-air', contextWindow: 131_072, maxOutputTokens: 98_304, supportsTools: true, supportsStreaming: true },
|
|
27
|
+
{ id: 'glm-4.5-flash', contextWindow: 131_072, maxOutputTokens: 98_304, supportsTools: true, supportsStreaming: true },
|
|
28
|
+
// Vision-capable variant.
|
|
29
|
+
{ id: 'glm-4.5v', contextWindow: 65_536, maxOutputTokens: 16_384, supportsTools: true, supportsStreaming: true, supportsImages: true },
|
|
30
|
+
];
|
|
31
|
+
//# sourceMappingURL=models.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models.js","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,SAAS,GAAmC;IACvD,wEAAwE;IACxE,8EAA8E;IAC9E,4EAA4E;IAC5E,8EAA8E;IAC9E,EAAE,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,eAAe,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE;IAC5I,EAAE,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE;IAC1I,EAAE,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE;IAExI,uEAAuE;IACvE,EAAE,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE;IAE1I,uEAAuE;IACvE,EAAE,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE;IAChH,EAAE,EAAE,EAAE,aAAa,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE;IACpH,EAAE,EAAE,EAAE,eAAe,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE;IAEtH,0BAA0B;IAC1B,EAAE,EAAE,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE;CACvI,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@moxxy/plugin-provider-zai",
|
|
3
|
+
"version": "0.21.1",
|
|
4
|
+
"description": "z.ai (Zhipu GLM) LLMProvider plugin for moxxy. Ships two providers: `zai` (pay-as-you-go, OpenAI-compatible endpoint) and `zai-coding-plan` (GLM Coding Plan, Anthropic-compatible endpoint).",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"moxxy",
|
|
7
|
+
"agent",
|
|
8
|
+
"provider",
|
|
9
|
+
"zai",
|
|
10
|
+
"zhipu",
|
|
11
|
+
"glm",
|
|
12
|
+
"llm"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://moxxy.ai",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/moxxy-ai/moxxy/issues"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/moxxy-ai/moxxy.git",
|
|
21
|
+
"directory": "packages/plugin-provider-zai"
|
|
22
|
+
},
|
|
23
|
+
"author": "Michal Makowski <michal.makowski97@gmail.com>",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"type": "module",
|
|
29
|
+
"main": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"import": "./dist/index.js"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"dist",
|
|
39
|
+
"src"
|
|
40
|
+
],
|
|
41
|
+
"moxxy": {
|
|
42
|
+
"plugin": {
|
|
43
|
+
"entry": "./dist/index.js",
|
|
44
|
+
"kind": "provider"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@moxxy/plugin-provider-anthropic": "0.21.1",
|
|
49
|
+
"@moxxy/plugin-provider-openai": "0.21.1",
|
|
50
|
+
"@moxxy/sdk": "0.21.1"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/node": "^22.10.0",
|
|
54
|
+
"typescript": "^5.7.3",
|
|
55
|
+
"vitest": "^2.1.8",
|
|
56
|
+
"zod": "^3.24.0",
|
|
57
|
+
"@moxxy/tsconfig": "0.0.0",
|
|
58
|
+
"@moxxy/vitest-preset": "0.0.0"
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "tsc -p tsconfig.json",
|
|
62
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
63
|
+
"test": "vitest run",
|
|
64
|
+
"clean": "rm -rf dist .turbo"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import type { DefineOpenAICompatProviderSpec } from '@moxxy/plugin-provider-openai';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The whole point of this plugin is that traffic (and the z.ai key) goes to
|
|
6
|
+
* api.z.ai — the OpenAI-compatible endpoint for `zai`, the Anthropic-compatible
|
|
7
|
+
* endpoint for `zai-coding-plan` — and NEVER to openai.com / anthropic.com. The
|
|
8
|
+
* resolved base URLs are private inside the underlying SDK clients, so a
|
|
9
|
+
* refactor that dropped `ZAI_OPENAI_BASE_URL` / `ZAI_ANTHROPIC_BASE_URL` (or
|
|
10
|
+
* routed one mode through the wrong vendor) would ship the z.ai credential to
|
|
11
|
+
* the wrong host and still pass the rest of the suite.
|
|
12
|
+
*
|
|
13
|
+
* These tests pin the wiring at zai's own trust boundary: they mock zai's two
|
|
14
|
+
* direct workspace deps and assert that the z.ai base URLs (and slug/catalog)
|
|
15
|
+
* reach the construction call. The real `pickOpenAICompatConfig` narrowing is
|
|
16
|
+
* preserved so the `zai-coding-plan` config-handling stays exercised.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const ZAI_OPENAI_BASE_URL = 'https://api.z.ai/api/paas/v4';
|
|
20
|
+
const ZAI_ANTHROPIC_BASE_URL = 'https://api.z.ai/api/anthropic';
|
|
21
|
+
|
|
22
|
+
/** Captured spec from the (mocked) defineOpenAICompatProvider call. */
|
|
23
|
+
let compatSpec: DefineOpenAICompatProviderSpec | undefined;
|
|
24
|
+
/** Captured config from the (mocked) AnthropicProvider construction. */
|
|
25
|
+
let anthropicConfig: Record<string, unknown> | undefined;
|
|
26
|
+
|
|
27
|
+
vi.mock('@moxxy/plugin-provider-openai', async (importActual) => {
|
|
28
|
+
const actual = await importActual<typeof import('@moxxy/plugin-provider-openai')>();
|
|
29
|
+
return {
|
|
30
|
+
...actual,
|
|
31
|
+
// Capture the spec zai hands the shared factory, and return a tiny stub
|
|
32
|
+
// ProviderDef so importing index.ts doesn't construct a real SDK client.
|
|
33
|
+
defineOpenAICompatProvider: (spec: DefineOpenAICompatProviderSpec) => {
|
|
34
|
+
compatSpec = spec;
|
|
35
|
+
return {
|
|
36
|
+
name: spec.name,
|
|
37
|
+
models: [...spec.models],
|
|
38
|
+
createClient: () => ({ name: spec.name, models: spec.models }),
|
|
39
|
+
...(spec.auth ? { auth: spec.auth } : {}),
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
vi.mock('@moxxy/plugin-provider-anthropic', () => ({
|
|
46
|
+
// Spy class: record the config zai passes, expose name/models like the real
|
|
47
|
+
// provider so any later assertions still see a usable shape.
|
|
48
|
+
AnthropicProvider: class {
|
|
49
|
+
name: string;
|
|
50
|
+
models: unknown;
|
|
51
|
+
constructor(config: Record<string, unknown>) {
|
|
52
|
+
anthropicConfig = config;
|
|
53
|
+
this.name = typeof config.name === 'string' ? config.name : 'anthropic';
|
|
54
|
+
this.models = config.models;
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
describe('@moxxy/plugin-provider-zai base-URL pinning', () => {
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
compatSpec = undefined;
|
|
62
|
+
anthropicConfig = undefined;
|
|
63
|
+
vi.resetModules();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
afterEach(() => {
|
|
67
|
+
vi.resetModules();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('`zai` is wired to the z.ai OpenAI-compatible endpoint, not openai.com', async () => {
|
|
71
|
+
const { zaiProviderDef } = await import('./index.js');
|
|
72
|
+
expect(zaiProviderDef.name).toBe('zai');
|
|
73
|
+
// The slug + base URL + default model + catalog reach the shared factory.
|
|
74
|
+
expect(compatSpec).toBeDefined();
|
|
75
|
+
expect(compatSpec?.name).toBe('zai');
|
|
76
|
+
expect(compatSpec?.baseURL).toBe(ZAI_OPENAI_BASE_URL);
|
|
77
|
+
expect(compatSpec?.baseURL).not.toContain('openai.com');
|
|
78
|
+
expect(compatSpec?.defaultModel).toBe('glm-4.6');
|
|
79
|
+
expect(compatSpec?.models.map((m) => m.id)).toContain('glm-5.2');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('`zai-coding-plan` constructs AnthropicProvider against the z.ai Anthropic endpoint, not anthropic.com', async () => {
|
|
83
|
+
const { zaiCodingPlanProviderDef } = await import('./index.js');
|
|
84
|
+
const client = zaiCodingPlanProviderDef.createClient({ apiKey: 'plan-key' });
|
|
85
|
+
expect(client.name).toBe('zai-coding-plan');
|
|
86
|
+
expect(anthropicConfig).toBeDefined();
|
|
87
|
+
expect(anthropicConfig?.name).toBe('zai-coding-plan');
|
|
88
|
+
expect(anthropicConfig?.apiKey).toBe('plan-key');
|
|
89
|
+
expect(anthropicConfig?.baseURL).toBe(ZAI_ANTHROPIC_BASE_URL);
|
|
90
|
+
expect(String(anthropicConfig?.baseURL)).not.toContain('anthropic.com');
|
|
91
|
+
expect(anthropicConfig?.defaultModel).toBe('glm-4.6');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('`zai-coding-plan` config may override the base URL but a wrong-typed override falls back to the z.ai endpoint', async () => {
|
|
95
|
+
const { zaiCodingPlanProviderDef } = await import('./index.js');
|
|
96
|
+
|
|
97
|
+
// A valid string override is honored (self-hosted / proxy).
|
|
98
|
+
zaiCodingPlanProviderDef.createClient({ apiKey: 'k', baseURL: 'https://proxy.internal/anthropic' });
|
|
99
|
+
expect(anthropicConfig?.baseURL).toBe('https://proxy.internal/anthropic');
|
|
100
|
+
|
|
101
|
+
// A wrong-typed baseURL is dropped by pickOpenAICompatConfig and must fall
|
|
102
|
+
// back to the pinned z.ai endpoint — never leak through as a bogus value.
|
|
103
|
+
anthropicConfig = undefined;
|
|
104
|
+
zaiCodingPlanProviderDef.createClient({ apiKey: 'k', baseURL: { not: 'a string' } } as Record<string, unknown>);
|
|
105
|
+
expect(anthropicConfig?.baseURL).toBe(ZAI_ANTHROPIC_BASE_URL);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { zaiPlugin, zaiProviderDef, zaiCodingPlanProviderDef, glmModels } from './index.js';
|
|
3
|
+
|
|
4
|
+
describe('@moxxy/plugin-provider-zai', () => {
|
|
5
|
+
it('registers the two GLM providers under the right names', () => {
|
|
6
|
+
expect(zaiPlugin.providers?.map((p) => p.name)).toEqual(['zai', 'zai-coding-plan']);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('advertises the GLM catalog (including the 1M-context glm-5.2 flagship)', () => {
|
|
10
|
+
expect(zaiProviderDef.models).toEqual(glmModels);
|
|
11
|
+
expect(zaiCodingPlanProviderDef.models).toEqual(glmModels);
|
|
12
|
+
const flagship = glmModels.find((m) => m.id === 'glm-5.2');
|
|
13
|
+
expect(flagship).toMatchObject({ contextWindow: 1_000_000 });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('createClient stamps the vendor slug so usage/errors attribute to z.ai, not openai/anthropic', () => {
|
|
17
|
+
const api = zaiProviderDef.createClient({ apiKey: 'test-key' });
|
|
18
|
+
expect(api.name).toBe('zai');
|
|
19
|
+
expect(api.models).toEqual(glmModels);
|
|
20
|
+
|
|
21
|
+
const plan = zaiCodingPlanProviderDef.createClient({ apiKey: 'test-key' });
|
|
22
|
+
expect(plan.name).toBe('zai-coding-plan');
|
|
23
|
+
expect(plan.models).toEqual(glmModels);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('narrows the untyped registry config — wrong-typed fields do not crash createClient', () => {
|
|
27
|
+
// The registry hands createClient a `Record<string, unknown>`; the old
|
|
28
|
+
// blanket `config as OpenAIProviderConfig` would smuggle wrong-typed
|
|
29
|
+
// fields straight into the client. The runtime pick must drop non-string
|
|
30
|
+
// apiKey/baseURL/defaultModel (falling back to the z.ai defaults) rather
|
|
31
|
+
// than forward garbage, while still producing a usable client.
|
|
32
|
+
const messy: Record<string, unknown> = {
|
|
33
|
+
apiKey: 'test-key',
|
|
34
|
+
baseURL: { not: 'a string' },
|
|
35
|
+
defaultModel: ['nope'],
|
|
36
|
+
extraneous: 'ignored',
|
|
37
|
+
};
|
|
38
|
+
const api = zaiProviderDef.createClient(messy);
|
|
39
|
+
expect(api.name).toBe('zai');
|
|
40
|
+
expect(api.models).toEqual(glmModels);
|
|
41
|
+
expect(typeof api.stream).toBe('function');
|
|
42
|
+
|
|
43
|
+
const plan = zaiCodingPlanProviderDef.createClient(messy);
|
|
44
|
+
expect(plan.name).toBe('zai-coding-plan');
|
|
45
|
+
expect(plan.models).toEqual(glmModels);
|
|
46
|
+
expect(typeof plan.stream).toBe('function');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('refuses to construct without a key (no env-fallback credential exfiltration to api.z.ai)', () => {
|
|
50
|
+
// baseURL is pinned to api.z.ai for both modes; if createClient forwarded
|
|
51
|
+
// an absent key, the underlying providers fall back to
|
|
52
|
+
// ANTHROPIC_API_KEY / OPENAI_API_KEY and ship the user's real credential to
|
|
53
|
+
// the third-party host. The guard must throw instead.
|
|
54
|
+
for (const empty of [{}, { apiKey: undefined }, { apiKey: '' }, { apiKey: 123 }] as Record<string, unknown>[]) {
|
|
55
|
+
expect(() => zaiProviderDef.createClient(empty)).toThrow(/api key/i);
|
|
56
|
+
expect(() => zaiCodingPlanProviderDef.createClient(empty)).toThrow(/api key/i);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('exposes apiKey auth descriptors for both modes', () => {
|
|
61
|
+
expect(zaiProviderDef.auth).toMatchObject({ kind: 'apiKey' });
|
|
62
|
+
expect(zaiCodingPlanProviderDef.auth).toMatchObject({ kind: 'apiKey' });
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('the GLM-5 family + glm-4.6 advertise supportsReasoning so reasoning is requested', () => {
|
|
66
|
+
// Reasoning is gated upstream on descriptor.supportsReasoning; without it
|
|
67
|
+
// ProviderRequest.reasoning is always undefined for these reasoning models
|
|
68
|
+
// and the streamed reasoning summary the OpenAIProvider plumbs is never
|
|
69
|
+
// surfaced.
|
|
70
|
+
for (const id of ['glm-5.2', 'glm-5.1', 'glm-5', 'glm-4.6']) {
|
|
71
|
+
expect(glmModels.find((m) => m.id === id)?.supportsReasoning, id).toBe(true);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { defineProvider, definePlugin } from '@moxxy/sdk';
|
|
2
|
+
import { defineOpenAICompatProvider, pickOpenAICompatConfig } from '@moxxy/plugin-provider-openai';
|
|
3
|
+
import { AnthropicProvider } from '@moxxy/plugin-provider-anthropic';
|
|
4
|
+
import { glmModels } from './models.js';
|
|
5
|
+
|
|
6
|
+
export { glmModels };
|
|
7
|
+
|
|
8
|
+
/** Pay-as-you-go OpenAI-compatible endpoint (standard z.ai API key). */
|
|
9
|
+
const ZAI_OPENAI_BASE_URL = 'https://api.z.ai/api/paas/v4';
|
|
10
|
+
/** GLM Coding Plan — Anthropic Messages-compatible endpoint (the one Claude Code uses). */
|
|
11
|
+
const ZAI_ANTHROPIC_BASE_URL = 'https://api.z.ai/api/anthropic';
|
|
12
|
+
const ZAI_DEFAULT_MODEL = 'glm-4.6';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* z.ai in "API key" mode: the standard, pay-as-you-go endpoint, which speaks
|
|
16
|
+
* the OpenAI Chat Completions protocol. Reuses the shared
|
|
17
|
+
* {@link defineOpenAICompatProvider} with the vendor slug + base URL + GLM
|
|
18
|
+
* catalog forced on (so usage stats, provider events and error context
|
|
19
|
+
* attribute to `zai`, not `openai`).
|
|
20
|
+
*
|
|
21
|
+
* `resolveApiKey` refuses an absent/empty key rather than letting
|
|
22
|
+
* {@link OpenAIProvider} fall back to `process.env.OPENAI_API_KEY` — the
|
|
23
|
+
* baseURL is pinned to api.z.ai, so that fallback would silently ship the
|
|
24
|
+
* user's OpenAI credential to a third-party host on a misconfigured provider.
|
|
25
|
+
*/
|
|
26
|
+
export const zaiProviderDef = defineOpenAICompatProvider({
|
|
27
|
+
name: 'zai',
|
|
28
|
+
baseURL: ZAI_OPENAI_BASE_URL,
|
|
29
|
+
defaultModel: ZAI_DEFAULT_MODEL,
|
|
30
|
+
models: glmModels,
|
|
31
|
+
resolveApiKey: (cfg) => {
|
|
32
|
+
if (!cfg.apiKey) throw new Error('zai requires an API key (set the zai provider apiKey or ZAI_API_KEY)');
|
|
33
|
+
return cfg.apiKey;
|
|
34
|
+
},
|
|
35
|
+
auth: {
|
|
36
|
+
kind: 'apiKey',
|
|
37
|
+
hint: 'z.ai API key (pay-as-you-go) from https://z.ai/manage-apikey/apikey-list',
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* z.ai in "plan" mode: the GLM Coding Plan, billed against a subscription and
|
|
43
|
+
* served over an Anthropic Messages-compatible endpoint (the same one Claude
|
|
44
|
+
* Code targets). Reuses {@link AnthropicProvider} with the GLM catalog and the
|
|
45
|
+
* z.ai base URL — only the credential + endpoint differ from the plain
|
|
46
|
+
* `anthropic` provider. No `validateKey`: z.ai's Anthropic endpoint exposes no
|
|
47
|
+
* free model-list probe, and the plan key is proven by the first request.
|
|
48
|
+
*/
|
|
49
|
+
export const zaiCodingPlanProviderDef = defineProvider({
|
|
50
|
+
name: 'zai-coding-plan',
|
|
51
|
+
models: [...glmModels],
|
|
52
|
+
createClient: (config) => {
|
|
53
|
+
const cfg = pickOpenAICompatConfig(config);
|
|
54
|
+
// Refuse to construct without a key: AnthropicProvider falls back to
|
|
55
|
+
// process.env.ANTHROPIC_API_KEY when apiKey is absent, while baseURL is
|
|
56
|
+
// pinned to z.ai's Anthropic endpoint — forwarding an empty key would
|
|
57
|
+
// silently exfiltrate the user's real Anthropic credential to api.z.ai.
|
|
58
|
+
if (!cfg.apiKey) throw new Error('zai-coding-plan requires an API key (set the zai-coding-plan provider apiKey)');
|
|
59
|
+
return new AnthropicProvider({
|
|
60
|
+
apiKey: cfg.apiKey,
|
|
61
|
+
name: 'zai-coding-plan',
|
|
62
|
+
baseURL: cfg.baseURL ?? ZAI_ANTHROPIC_BASE_URL,
|
|
63
|
+
defaultModel: cfg.defaultModel ?? ZAI_DEFAULT_MODEL,
|
|
64
|
+
models: glmModels,
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
auth: {
|
|
68
|
+
kind: 'apiKey',
|
|
69
|
+
hint: 'z.ai GLM Coding Plan key (Anthropic-compatible endpoint, like Claude Code)',
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export const zaiPlugin = definePlugin({
|
|
74
|
+
name: '@moxxy/plugin-provider-zai',
|
|
75
|
+
version: '0.0.0',
|
|
76
|
+
providers: [zaiProviderDef, zaiCodingPlanProviderDef],
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
export default zaiPlugin;
|
package/src/models.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ModelDescriptor } from '@moxxy/sdk';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* z.ai (Zhipu) GLM model catalog, as of 2026-06. Shared by both the
|
|
5
|
+
* pay-as-you-go `zai` provider (OpenAI-compatible endpoint) and the
|
|
6
|
+
* `zai-coding-plan` provider (Anthropic-compatible endpoint) — the GLM model
|
|
7
|
+
* ids are identical across both surfaces; only the transport/billing differ.
|
|
8
|
+
*
|
|
9
|
+
* Numbers are the documented limits as of June 2026 (verify against
|
|
10
|
+
* https://docs.z.ai/guides/llm). GLM-5.2 is the coding-first flagship with a
|
|
11
|
+
* usable 1M-token context (released 2026-06-13); the GLM-4.5 tier tops out at
|
|
12
|
+
* 128k. Only the `-v` (vision) variants accept image input. An unlisted model
|
|
13
|
+
* id still streams fine — it's passed straight through to the endpoint; the
|
|
14
|
+
* catalog only drives context-window budgets and capability gating.
|
|
15
|
+
*/
|
|
16
|
+
export const glmModels: ReadonlyArray<ModelDescriptor> = [
|
|
17
|
+
// GLM-5 family: coding-first frontier. 5.2 carries a usable 1M context.
|
|
18
|
+
// These are reasoning models; the shared OpenAIProvider already streams their
|
|
19
|
+
// `reasoning_content` deltas, so supportsReasoning gates reasoning_effort +
|
|
20
|
+
// reasoning-stream surfacing on (without it the capability is dead upstream).
|
|
21
|
+
{ id: 'glm-5.2', contextWindow: 1_000_000, maxOutputTokens: 128_000, supportsTools: true, supportsStreaming: true, supportsReasoning: true },
|
|
22
|
+
{ id: 'glm-5.1', contextWindow: 200_000, maxOutputTokens: 128_000, supportsTools: true, supportsStreaming: true, supportsReasoning: true },
|
|
23
|
+
{ id: 'glm-5', contextWindow: 200_000, maxOutputTokens: 128_000, supportsTools: true, supportsStreaming: true, supportsReasoning: true },
|
|
24
|
+
|
|
25
|
+
// GLM-4.6: prior flagship, 200k context, strong agentic-coding scores.
|
|
26
|
+
{ id: 'glm-4.6', contextWindow: 200_000, maxOutputTokens: 128_000, supportsTools: true, supportsStreaming: true, supportsReasoning: true },
|
|
27
|
+
|
|
28
|
+
// GLM-4.5 tier: general (4.5), lightweight (-air), free/fast (-flash).
|
|
29
|
+
{ id: 'glm-4.5', contextWindow: 131_072, maxOutputTokens: 98_304, supportsTools: true, supportsStreaming: true },
|
|
30
|
+
{ id: 'glm-4.5-air', contextWindow: 131_072, maxOutputTokens: 98_304, supportsTools: true, supportsStreaming: true },
|
|
31
|
+
{ id: 'glm-4.5-flash', contextWindow: 131_072, maxOutputTokens: 98_304, supportsTools: true, supportsStreaming: true },
|
|
32
|
+
|
|
33
|
+
// Vision-capable variant.
|
|
34
|
+
{ id: 'glm-4.5v', contextWindow: 65_536, maxOutputTokens: 16_384, supportsTools: true, supportsStreaming: true, supportsImages: true },
|
|
35
|
+
];
|