diff --git a/.changeset/add-zai-glm-provider.md b/.changeset/add-zai-glm-provider.md new file mode 100644 index 00000000..6075d505 --- /dev/null +++ b/.changeset/add-zai-glm-provider.md @@ -0,0 +1,6 @@ +--- +"@open-codesign/shared": minor +"@open-codesign/providers": patch +--- + +Add Z.ai (GLM Coding Plan) as a built-in provider with support for GLM-5.1, GLM-5-Turbo, GLM-4.7, and GLM-4.5-Air models via the Anthropic-compatible API endpoint. diff --git a/packages/providers/src/validate.ts b/packages/providers/src/validate.ts index 0336b283..89ca98f9 100644 --- a/packages/providers/src/validate.ts +++ b/packages/providers/src/validate.ts @@ -67,6 +67,19 @@ function endpoint(provider: SupportedOnboardingProvider, baseUrl?: string): Prov headers: () => ({}), }; } + case 'zai': { + const root = baseUrl ? normalizeValidateBaseUrl(baseUrl) : 'https://api.z.ai/api/anthropic'; + return { + url: `${root}/v1/models`, + headers: (apiKey) => { + const auth: Record = { + 'x-api-key': apiKey, + 'anthropic-version': '2023-06-01', + }; + return withClaudeCodeIdentity('anthropic', baseUrl, auth); + }, + }; + } } } diff --git a/packages/shared/src/config.test.ts b/packages/shared/src/config.test.ts index 129396be..8852be94 100644 --- a/packages/shared/src/config.test.ts +++ b/packages/shared/src/config.test.ts @@ -499,3 +499,46 @@ describe('provider capability helpers', () => { expect(caps.modelDiscoveryMode).toBe('manual'); }); }); + +describe('zai builtin provider', () => { + it('has a well-formed zai entry in BUILTIN_PROVIDERS', () => { + const zai = BUILTIN_PROVIDERS.zai; + expect(zai).toBeDefined(); + expect(zai.id).toBe('zai'); + expect(zai.name).toBe('Z.ai (GLM Coding Plan)'); + expect(zai.builtin).toBe(true); + expect(zai.wire).toBe('anthropic'); + expect(zai.baseUrl).toBe('https://api.z.ai/api/anthropic'); + expect(zai.defaultModel).toBe('glm-5.1'); + expect(zai.modelsHint).toEqual(['glm-5.1', 'glm-5-turbo', 'glm-4.7', 'glm-4.5-air']); + expect(zai.requiresApiKey).toBe(true); + }); + + it('zai capabilities are set for static-hint discovery with reasoning', () => { + const caps = resolveProviderCapabilities('zai', BUILTIN_PROVIDERS.zai); + expect(caps.supportsKeyless).toBe(false); + expect(caps.supportsModelsEndpoint).toBe(false); + expect(caps.supportsReasoning).toBe(true); + expect(caps.modelDiscoveryMode).toBe('static-hint'); + }); + + it('zai is included in SUPPORTED_ONBOARDING_PROVIDERS', () => { + expect(SUPPORTED_ONBOARDING_PROVIDERS).toContain('zai'); + }); + + it('detects z.ai anthropic URL as anthropic wire', () => { + expect(detectWireFromBaseUrl('https://api.z.ai/api/anthropic')).toBe('anthropic'); + }); + + it('accepts zai as active provider in v3 config', () => { + const parsed = ConfigV3Schema.parse({ + version: 3, + activeProvider: 'zai', + activeModel: 'glm-5.1', + secrets: { zai: { ciphertext: 'plain:sk-test', mask: 'sk-***test' } }, + providers: { zai: BUILTIN_PROVIDERS.zai }, + }); + expect(parsed.activeProvider).toBe('zai'); + expect(parsed.activeModel).toBe('glm-5.1'); + }); +}); diff --git a/packages/shared/src/config.ts b/packages/shared/src/config.ts index 12aac774..4e34b3bb 100644 --- a/packages/shared/src/config.ts +++ b/packages/shared/src/config.ts @@ -21,6 +21,7 @@ export const SUPPORTED_ONBOARDING_PROVIDERS = [ 'openai', 'openrouter', 'ollama', + 'zai', ] as const; export type SupportedOnboardingProvider = (typeof SUPPORTED_ONBOARDING_PROVIDERS)[number]; @@ -294,6 +295,23 @@ export const BUILTIN_PROVIDERS: Readonly