@@ -4,12 +4,16 @@ import type {
44 ServerProvider ,
55 ServerProviderModel ,
66 ServerProviderAuth ,
7+ ServerProviderSlashCommand ,
78 ServerProviderState ,
89} from "@t3tools/contracts" ;
910import { Cache , Duration , Effect , Equal , Layer , Option , Result , Schema , Stream } from "effect" ;
1011import { ChildProcess , ChildProcessSpawner } from "effect/unstable/process" ;
1112import { decodeJsonResult } from "@t3tools/shared/schemaJson" ;
12- import { query as claudeQuery } from "@anthropic-ai/claude-agent-sdk" ;
13+ import {
14+ query as claudeQuery ,
15+ type SlashCommand as ClaudeSlashCommand ,
16+ } from "@anthropic-ai/claude-agent-sdk" ;
1317
1418import {
1519 buildServerProvider ,
@@ -340,6 +344,74 @@ function claudeAuthMetadata(input: {
340344
341345const CAPABILITIES_PROBE_TIMEOUT_MS = 8_000 ;
342346
347+ function nonEmptyProbeString ( value : string ) : string | undefined {
348+ const candidate = value . trim ( ) ;
349+ return candidate ? candidate : undefined ;
350+ }
351+
352+ function parseClaudeInitializationCommands (
353+ commands : ReadonlyArray < ClaudeSlashCommand > | undefined ,
354+ ) : ReadonlyArray < ServerProviderSlashCommand > {
355+ return dedupeSlashCommands (
356+ ( commands ?? [ ] ) . flatMap ( ( command ) => {
357+ const name = nonEmptyProbeString ( command . name ) ;
358+ if ( ! name ) {
359+ return [ ] ;
360+ }
361+
362+ const description = nonEmptyProbeString ( command . description ) ;
363+ const argumentHint = nonEmptyProbeString ( command . argumentHint ) ;
364+
365+ return [
366+ {
367+ name,
368+ ...( description ? { description } : { } ) ,
369+ ...( argumentHint ? { input : { hint : argumentHint } } : { } ) ,
370+ } satisfies ServerProviderSlashCommand ,
371+ ] ;
372+ } ) ,
373+ ) ;
374+ }
375+
376+ function dedupeSlashCommands (
377+ commands : ReadonlyArray < ServerProviderSlashCommand > ,
378+ ) : ReadonlyArray < ServerProviderSlashCommand > {
379+ const commandsByName = new Map < string , ServerProviderSlashCommand > ( ) ;
380+
381+ for ( const command of commands ) {
382+ const name = nonEmptyProbeString ( command . name ) ;
383+ if ( ! name ) {
384+ continue ;
385+ }
386+
387+ const key = name . toLowerCase ( ) ;
388+ const existing = commandsByName . get ( key ) ;
389+ if ( ! existing ) {
390+ commandsByName . set ( key , {
391+ ...command ,
392+ name,
393+ } ) ;
394+ continue ;
395+ }
396+
397+ commandsByName . set ( key , {
398+ ...existing ,
399+ ...( existing . description
400+ ? { }
401+ : command . description
402+ ? { description : command . description }
403+ : { } ) ,
404+ ...( existing . input ?. hint
405+ ? { }
406+ : command . input ?. hint
407+ ? { input : { hint : command . input . hint } }
408+ : { } ) ,
409+ } ) ;
410+ }
411+
412+ return [ ...commandsByName . values ( ) ] ;
413+ }
414+
343415/**
344416 * Probe account information by spawning a lightweight Claude Agent SDK
345417 * session and reading the initialization result.
@@ -361,13 +433,16 @@ const probeClaudeCapabilities = (binaryPath: string) => {
361433 pathToClaudeCodeExecutable : binaryPath ,
362434 abortController : abort ,
363435 maxTurns : 0 ,
364- settingSources : [ ] ,
436+ settingSources : [ "user" , "project" , "local" ] ,
365437 allowedTools : [ ] ,
366438 stderr : ( ) => { } ,
367439 } ,
368440 } ) ;
369441 const init = await q . initializationResult ( ) ;
370- return { subscriptionType : init . account ?. subscriptionType } ;
442+ return {
443+ subscriptionType : init . account ?. subscriptionType ,
444+ slashCommands : parseClaudeInitializationCommands ( init . commands ) ,
445+ } ;
371446 } ) . pipe (
372447 Effect . ensuring (
373448 Effect . sync ( ( ) => {
@@ -396,6 +471,9 @@ const runClaudeCommand = Effect.fn("runClaudeCommand")(function* (args: Readonly
396471
397472export const checkClaudeProviderStatus = Effect . fn ( "checkClaudeProviderStatus" ) ( function * (
398473 resolveSubscriptionType ?: ( binaryPath : string ) => Effect . Effect < string | undefined > ,
474+ resolveSlashCommands ?: (
475+ binaryPath : string ,
476+ ) => Effect . Effect < ReadonlyArray < ServerProviderSlashCommand > | undefined > ,
399477) : Effect . fn . Return <
400478 ServerProvider ,
401479 ServerSettingsError ,
@@ -491,6 +569,14 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")(
491569 } ) ;
492570 }
493571
572+ const slashCommands =
573+ ( resolveSlashCommands
574+ ? yield * resolveSlashCommands ( claudeSettings . binaryPath ) . pipe (
575+ Effect . orElseSucceed ( ( ) => undefined ) ,
576+ )
577+ : undefined ) ?? [ ] ;
578+ const dedupedSlashCommands = dedupeSlashCommands ( slashCommands ) ;
579+
494580 // ── Auth check + subscription detection ────────────────────────────
495581
496582 const authProbe = yield * runClaudeCommand ( [ "auth" , "status" ] ) . pipe (
@@ -525,6 +611,7 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")(
525611 enabled : claudeSettings . enabled ,
526612 checkedAt,
527613 models,
614+ slashCommands : dedupedSlashCommands ,
528615 probe : {
529616 installed : true ,
530617 version : parsedVersion ,
@@ -544,6 +631,7 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")(
544631 enabled : claudeSettings . enabled ,
545632 checkedAt,
546633 models,
634+ slashCommands : dedupedSlashCommands ,
547635 probe : {
548636 installed : true ,
549637 version : parsedVersion ,
@@ -561,6 +649,7 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")(
561649 enabled : claudeSettings . enabled ,
562650 checkedAt,
563651 models,
652+ slashCommands : dedupedSlashCommands ,
564653 probe : {
565654 installed : true ,
566655 version : parsedVersion ,
@@ -583,12 +672,18 @@ export const ClaudeProviderLive = Layer.effect(
583672 const subscriptionProbeCache = yield * Cache . make ( {
584673 capacity : 1 ,
585674 timeToLive : Duration . minutes ( 5 ) ,
586- lookup : ( binaryPath : string ) =>
587- probeClaudeCapabilities ( binaryPath ) . pipe ( Effect . map ( ( r ) => r ?. subscriptionType ) ) ,
675+ lookup : ( binaryPath : string ) => probeClaudeCapabilities ( binaryPath ) ,
588676 } ) ;
589677
590- const checkProvider = checkClaudeProviderStatus ( ( binaryPath ) =>
591- Cache . get ( subscriptionProbeCache , binaryPath ) ,
678+ const checkProvider = checkClaudeProviderStatus (
679+ ( binaryPath ) =>
680+ Cache . get ( subscriptionProbeCache , binaryPath ) . pipe (
681+ Effect . map ( ( probe ) => probe ?. subscriptionType ) ,
682+ ) ,
683+ ( binaryPath ) =>
684+ Cache . get ( subscriptionProbeCache , binaryPath ) . pipe (
685+ Effect . map ( ( probe ) => probe ?. slashCommands ) ,
686+ ) ,
592687 ) . pipe (
593688 Effect . provideService ( ServerSettingsService , serverSettings ) ,
594689 Effect . provideService ( ChildProcessSpawner . ChildProcessSpawner , spawner ) ,
0 commit comments