1+ import * as OS from "node:os" ;
12import type {
23 ClaudeSettings ,
34 ModelCapabilities ,
@@ -6,7 +7,19 @@ import type {
67 ServerProviderAuth ,
78 ServerProviderState ,
89} from "@t3tools/contracts" ;
9- import { Cache , Duration , Effect , Equal , Layer , Option , Result , Schema , Stream } from "effect" ;
10+ import {
11+ Cache ,
12+ Duration ,
13+ Effect ,
14+ Equal ,
15+ FileSystem ,
16+ Layer ,
17+ Option ,
18+ Path ,
19+ Result ,
20+ Schema ,
21+ Stream ,
22+ } from "effect" ;
1023import { ChildProcess , ChildProcessSpawner } from "effect/unstable/process" ;
1124import { decodeJsonResult } from "@t3tools/shared/schemaJson" ;
1225import { query as claudeQuery } from "@anthropic-ai/claude-agent-sdk" ;
@@ -36,6 +49,12 @@ const DEFAULT_CLAUDE_MODEL_CAPABILITIES: ModelCapabilities = {
3649} ;
3750
3851const PROVIDER = "claudeAgent" as const ;
52+ const ZAI_ANTHROPIC_BASE_URL = "https://api.z.ai/api/anthropic" ;
53+ const DEFAULT_CLAUDE_GLM_MODEL_MAPPING = {
54+ opus : "glm-4.7" ,
55+ sonnet : "glm-4.7" ,
56+ haiku : "glm-4.5-air" ,
57+ } as const ;
3958const BUILT_IN_MODELS : ReadonlyArray < ServerProviderModel > = [
4059 {
4160 slug : "claude-opus-4-6" ,
@@ -92,6 +111,107 @@ const BUILT_IN_MODELS: ReadonlyArray<ServerProviderModel> = [
92111 } ,
93112] ;
94113
114+ interface ClaudeGlmIntegration {
115+ readonly hasAuthToken : boolean ;
116+ readonly opusModel : string ;
117+ readonly sonnetModel : string ;
118+ readonly haikuModel : string ;
119+ }
120+
121+ function normalizeUrl ( value : string | undefined ) : string | undefined {
122+ const trimmed = value ?. trim ( ) ;
123+ return trimmed ? trimmed . replace ( / \/ + $ / g, "" ) . toLowerCase ( ) : undefined ;
124+ }
125+
126+ function asPlainRecord ( value : unknown ) : Record < string , unknown > | undefined {
127+ return typeof value === "object" && value !== null && ! globalThis . Array . isArray ( value )
128+ ? ( value as Record < string , unknown > )
129+ : undefined ;
130+ }
131+
132+ function asTrimmedString ( value : unknown ) : string | undefined {
133+ return typeof value === "string" && value . trim ( ) . length > 0 ? value . trim ( ) : undefined ;
134+ }
135+
136+ function readClaudeGlmIntegrationFromEnv (
137+ env : Record < string , string | undefined > ,
138+ ) : ClaudeGlmIntegration | undefined {
139+ if ( normalizeUrl ( env . ANTHROPIC_BASE_URL ) !== normalizeUrl ( ZAI_ANTHROPIC_BASE_URL ) ) {
140+ return undefined ;
141+ }
142+
143+ return {
144+ hasAuthToken : Boolean ( asTrimmedString ( env . ANTHROPIC_AUTH_TOKEN ) ) ,
145+ opusModel :
146+ asTrimmedString ( env . ANTHROPIC_DEFAULT_OPUS_MODEL ) ?? DEFAULT_CLAUDE_GLM_MODEL_MAPPING . opus ,
147+ sonnetModel :
148+ asTrimmedString ( env . ANTHROPIC_DEFAULT_SONNET_MODEL ) ??
149+ DEFAULT_CLAUDE_GLM_MODEL_MAPPING . sonnet ,
150+ haikuModel :
151+ asTrimmedString ( env . ANTHROPIC_DEFAULT_HAIKU_MODEL ) ?? DEFAULT_CLAUDE_GLM_MODEL_MAPPING . haiku ,
152+ } ;
153+ }
154+
155+ function buildClaudeModels (
156+ integration : ClaudeGlmIntegration | undefined ,
157+ ) : ReadonlyArray < ServerProviderModel > {
158+ if ( ! integration ) {
159+ return BUILT_IN_MODELS ;
160+ }
161+
162+ return BUILT_IN_MODELS . map ( ( model ) => {
163+ let mappedModel : string | undefined ;
164+ switch ( model . slug ) {
165+ case "claude-opus-4-6" :
166+ mappedModel = integration . opusModel ;
167+ break ;
168+ case "claude-sonnet-4-6" :
169+ mappedModel = integration . sonnetModel ;
170+ break ;
171+ case "claude-haiku-4-5" :
172+ mappedModel = integration . haikuModel ;
173+ break ;
174+ }
175+
176+ return mappedModel ? { ...model , name : `${ model . name } (${ mappedModel } )` } : model ;
177+ } ) ;
178+ }
179+
180+ export const readClaudeGlmIntegration = Effect . fn ( "readClaudeGlmIntegration" ) ( function * ( ) {
181+ const fileSystem = yield * FileSystem . FileSystem ;
182+ const path = yield * Path . Path ;
183+ const settingsPath = path . join ( OS . homedir ( ) , ".claude" , "settings.json" ) ;
184+ const content = yield * fileSystem
185+ . readFileString ( settingsPath )
186+ . pipe ( Effect . orElseSucceed ( ( ) => undefined ) ) ;
187+
188+ const fileEnv = ( ( ) => {
189+ if ( ! content ) {
190+ return { } as Record < string , string | undefined > ;
191+ }
192+ try {
193+ const parsed = JSON . parse ( content ) as unknown ;
194+ const envRecord = asPlainRecord ( asPlainRecord ( parsed ) ?. env ) ;
195+ if ( ! envRecord ) {
196+ return { } as Record < string , string | undefined > ;
197+ }
198+ return Object . fromEntries (
199+ Object . entries ( envRecord ) . flatMap ( ( [ key , value ] ) => {
200+ const stringValue = asTrimmedString ( value ) ;
201+ return stringValue ? [ [ key , stringValue ] ] : [ ] ;
202+ } ) ,
203+ ) as Record < string , string | undefined > ;
204+ } catch {
205+ return { } as Record < string , string | undefined > ;
206+ }
207+ } ) ( ) ;
208+
209+ return readClaudeGlmIntegrationFromEnv ( {
210+ ...fileEnv ,
211+ ...process . env ,
212+ } ) ;
213+ } ) ;
214+
95215export function getClaudeModelCapabilities ( model : string | null | undefined ) : ModelCapabilities {
96216 const slug = model ?. trim ( ) ;
97217 return (
@@ -446,15 +566,22 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")(
446566) : Effect . fn . Return <
447567 ServerProvider ,
448568 ServerSettingsError ,
449- ChildProcessSpawner . ChildProcessSpawner | ServerSettingsService
569+ | ChildProcessSpawner . ChildProcessSpawner
570+ | FileSystem . FileSystem
571+ | Path . Path
572+ | ServerSettingsService
450573> {
451574 const claudeSettings = yield * Effect . service ( ServerSettingsService ) . pipe (
452575 Effect . flatMap ( ( service ) => service . getSettings ) ,
453576 Effect . map ( ( settings ) => settings . providers . claudeAgent ) ,
454577 ) ;
578+ const glmIntegration = yield * readClaudeGlmIntegration ( ) . pipe (
579+ Effect . orElseSucceed ( ( ) => undefined ) ,
580+ ) ;
455581 const checkedAt = new Date ( ) . toISOString ( ) ;
582+ const displayName = glmIntegration ? "Claude / GLM" : "Claude" ;
456583 const models = providerModelsFromSettings (
457- BUILT_IN_MODELS ,
584+ buildClaudeModels ( glmIntegration ) ,
458585 PROVIDER ,
459586 claudeSettings . customModels ,
460587 DEFAULT_CLAUDE_MODEL_CAPABILITIES ,
@@ -466,6 +593,7 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")(
466593 enabled : false ,
467594 checkedAt,
468595 models,
596+ displayName,
469597 probe : {
470598 installed : false ,
471599 version : null ,
@@ -488,6 +616,7 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")(
488616 enabled : claudeSettings . enabled ,
489617 checkedAt,
490618 models,
619+ displayName,
491620 probe : {
492621 installed : ! isCommandMissingCause ( error ) ,
493622 version : null ,
@@ -506,6 +635,7 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")(
506635 enabled : claudeSettings . enabled ,
507636 checkedAt,
508637 models,
638+ displayName,
509639 probe : {
510640 installed : true ,
511641 version : null ,
@@ -526,6 +656,7 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")(
526656 enabled : claudeSettings . enabled ,
527657 checkedAt,
528658 models,
659+ displayName,
529660 probe : {
530661 installed : true ,
531662 version : parsedVersion ,
@@ -538,6 +669,41 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")(
538669 } ) ;
539670 }
540671
672+ if ( glmIntegration ) {
673+ return buildServerProvider ( {
674+ provider : PROVIDER ,
675+ enabled : claudeSettings . enabled ,
676+ checkedAt,
677+ models,
678+ displayName,
679+ probe : glmIntegration . hasAuthToken
680+ ? {
681+ installed : true ,
682+ version : parsedVersion ,
683+ status : "ready" ,
684+ auth : {
685+ status : "authenticated" ,
686+ type : "apiKey" ,
687+ label : "Z.AI GLM Plan" ,
688+ } ,
689+ message :
690+ "Configured to use Z.AI's Anthropic-compatible endpoint. Claude model tiers map to GLM models from your Claude settings." ,
691+ }
692+ : {
693+ installed : true ,
694+ version : parsedVersion ,
695+ status : "error" ,
696+ auth : {
697+ status : "unauthenticated" ,
698+ type : "apiKey" ,
699+ label : "Z.AI GLM Plan" ,
700+ } ,
701+ message :
702+ "Configured to use Z.AI's Anthropic-compatible endpoint, but ANTHROPIC_AUTH_TOKEN is missing." ,
703+ } ,
704+ } ) ;
705+ }
706+
541707 // ── Auth check + subscription detection ────────────────────────────
542708
543709 const authProbe = yield * runClaudeCommand ( [ "auth" , "status" ] ) . pipe (
@@ -574,6 +740,7 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")(
574740 enabled : claudeSettings . enabled ,
575741 checkedAt,
576742 models : resolvedModels ,
743+ displayName,
577744 probe : {
578745 installed : true ,
579746 version : parsedVersion ,
@@ -593,6 +760,7 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")(
593760 enabled : claudeSettings . enabled ,
594761 checkedAt,
595762 models : resolvedModels ,
763+ displayName,
596764 probe : {
597765 installed : true ,
598766 version : parsedVersion ,
@@ -610,6 +778,7 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")(
610778 enabled : claudeSettings . enabled ,
611779 checkedAt,
612780 models : resolvedModels ,
781+ displayName,
613782 probe : {
614783 installed : true ,
615784 version : parsedVersion ,
@@ -627,6 +796,8 @@ export const ClaudeProviderLive = Layer.effect(
627796 ClaudeProvider ,
628797 Effect . gen ( function * ( ) {
629798 const serverSettings = yield * ServerSettingsService ;
799+ const fileSystem = yield * FileSystem . FileSystem ;
800+ const path = yield * Path . Path ;
630801 const spawner = yield * ChildProcessSpawner . ChildProcessSpawner ;
631802
632803 const subscriptionProbeCache = yield * Cache . make ( {
@@ -640,6 +811,8 @@ export const ClaudeProviderLive = Layer.effect(
640811 Cache . get ( subscriptionProbeCache , binaryPath ) ,
641812 ) . pipe (
642813 Effect . provideService ( ServerSettingsService , serverSettings ) ,
814+ Effect . provideService ( FileSystem . FileSystem , fileSystem ) ,
815+ Effect . provideService ( Path . Path , path ) ,
643816 Effect . provideService ( ChildProcessSpawner . ChildProcessSpawner , spawner ) ,
644817 ) ;
645818
0 commit comments