| 1 | import { ServerProviderModel } from "@t3tools/contracts"; |
| 2 | import { MuseSettings } from "../settings.ts"; |
| 3 | import { resolveSpawnCommand } from "@t3tools/shared/shell"; |
| 4 | import * as DateTime from "effect/DateTime"; |
| 5 | import * as Effect from "effect/Effect"; |
| 6 | import * as Option from "effect/Option"; |
| 7 | import * as Result from "effect/Result"; |
| 8 | import * as ChildProcess from "effect/process/ChildProcess"; |
| 9 | |
| 10 | import { createMuseSdkHost, makeMuseEnvironment } from "./sdk.ts"; |
| 11 | import { parseMuseVersion } from "./maintenance.ts"; |
| 12 | import { discoverMuseModels, museModelCapabilities } from "./modelCatalog.ts"; |
| 13 | import { |
| 14 | buildServerProvider, |
| 15 | COMPACT_SLASH_COMMAND, |
| 16 | DEFAULT_TIMEOUT_MS, |
| 17 | isCommandMissingCause, |
| 18 | providerModelsFromSettings, |
| 19 | spawnAndCollect, |
| 20 | type ProviderProbeResult, |
| 21 | } from "@t3tools/provider-core/server/snapshotProbe"; |
| 22 | |
| 23 | const MUSE_PRESENTATION = { |
| 24 | displayName: "Muse Code", |
| 25 | showInteractionModeToggle: false, |
| 26 | reportsContextWindow: true, |
| 27 | // Muse has no native "accept edits" mode, so T3 offers only the two it maps |
| 28 | // directly: :ask-me with promptUnmatched, and :unrestricted with allowAll. |
| 29 | supportedRuntimeModes: ["approval-required", "full-access"], |
| 30 | } as const; |
| 31 | |
| 32 | const FALLBACK_CAPABILITIES = museModelCapabilities(); |
| 33 | |
| 34 | export const makePendingMuseProvider = Effect.fn("makePendingMuseProvider")(function* ( |
| 35 | settings: MuseSettings, |
| 36 | ) { |
| 37 | return buildServerProvider({ |
| 38 | presentation: MUSE_PRESENTATION, |
| 39 | enabled: settings.enabled, |
| 40 | checkedAt: DateTime.formatIso(yield* DateTime.now), |
| 41 | models: providerModelsFromSettings([], settings.customModels, FALLBACK_CAPABILITIES), |
| 42 | slashCommands: settings.enabled ? [COMPACT_SLASH_COMMAND] : [], |
| 43 | probe: { |
| 44 | installed: false, |
| 45 | version: null, |
| 46 | status: "warning", |
| 47 | auth: { status: "unknown" }, |
| 48 | message: settings.enabled |
| 49 | ? "Checking Muse Code CLI availability..." |
| 50 | : "Muse Code is disabled in T3 Code settings.", |
| 51 | }, |
| 52 | }); |
| 53 | }); |
| 54 | |
| 55 | export const checkMuseProviderStatus = Effect.fn("checkMuseProviderStatus")(function* ( |
| 56 | settings: MuseSettings, |
| 57 | environment?: NodeJS.ProcessEnv, |
| 58 | cwd?: string, |
| 59 | createHost: typeof createMuseSdkHost = createMuseSdkHost, |
| 60 | ) { |
| 61 | if (!settings.enabled) return yield* makePendingMuseProvider(settings); |
| 62 | const checkedAt = DateTime.formatIso(yield* DateTime.now); |
| 63 | // The driver passes a prepared environment; direct callers get the host's without META_API_KEY. |
| 64 | const museEnvironment = environment ?? makeMuseEnvironment(); |
| 65 | const snapshot = (probe: ProviderProbeResult, models: ReadonlyArray<ServerProviderModel> = []) => |
| 66 | buildServerProvider({ |
| 67 | presentation: MUSE_PRESENTATION, |
| 68 | enabled: true, |
| 69 | checkedAt, |
| 70 | models: providerModelsFromSettings(models, settings.customModels, FALLBACK_CAPABILITIES), |
| 71 | slashCommands: [COMPACT_SLASH_COMMAND], |
| 72 | probe, |
| 73 | }); |
| 74 | const versionResult = yield* Effect.gen(function* () { |
| 75 | const spawnCommand = yield* resolveSpawnCommand(settings.binaryPath, ["--version"], { |
| 76 | env: museEnvironment, |
| 77 | }); |
| 78 | return yield* spawnAndCollect( |
| 79 | settings.binaryPath, |
| 80 | ChildProcess.make(spawnCommand.command, spawnCommand.args, { |
| 81 | env: museEnvironment, |
| 82 | shell: spawnCommand.shell, |
| 83 | ...(cwd ? { cwd } : {}), |
| 84 | }), |
| 85 | ); |
| 86 | }).pipe(Effect.timeoutOption(DEFAULT_TIMEOUT_MS), Effect.result); |
| 87 | |
| 88 | if (Result.isFailure(versionResult)) { |
| 89 | const missing = isCommandMissingCause(versionResult.failure); |
| 90 | return snapshot({ |
| 91 | installed: !missing, |
| 92 | version: null, |
| 93 | status: "error", |
| 94 | auth: { status: "unknown" }, |
| 95 | message: missing |
| 96 | ? "Muse Code CLI (`muse`) was not found. Install Muse Code and run `muse login` on this T3 server host." |
| 97 | : "Failed to execute Muse Code CLI. Check its binary path on this T3 server host.", |
| 98 | }); |
| 99 | } |
| 100 | if (Option.isNone(versionResult.success)) { |
| 101 | return snapshot({ |
| 102 | installed: true, |
| 103 | version: null, |
| 104 | status: "error", |
| 105 | auth: { status: "unknown" }, |
| 106 | message: "Muse Code CLI version check timed out.", |
| 107 | }); |
| 108 | } |
| 109 | const versionOutput = versionResult.success.value; |
| 110 | const version = parseMuseVersion(`${versionOutput.stdout}\n${versionOutput.stderr}`); |
| 111 | if (versionOutput.code !== 0) { |
| 112 | return snapshot({ |
| 113 | installed: true, |
| 114 | version, |
| 115 | status: "error", |
| 116 | auth: { status: "unknown" }, |
| 117 | message: "Muse Code CLI is installed but failed to run.", |
| 118 | }); |
| 119 | } |
| 120 | |
| 121 | const catalog = yield* discoverMuseModels(settings, museEnvironment, cwd, createHost).pipe( |
| 122 | Effect.scoped, |
| 123 | Effect.timeoutOption(12_000), |
| 124 | Effect.result, |
| 125 | ); |
| 126 | if (Result.isFailure(catalog) || Option.isNone(catalog.success)) { |
| 127 | return snapshot({ |
| 128 | installed: true, |
| 129 | version, |
| 130 | status: "error", |
| 131 | auth: { status: "unknown" }, |
| 132 | message: |
| 133 | "Muse Code SDK could not read the model catalog. Check your Muse installation and run `muse login` on this T3 server host.", |
| 134 | }); |
| 135 | } |
| 136 | const models = catalog.success.value; |
| 137 | // MSP has no auth signal, and model/list answers from Muse's cache even when logged out. |
| 138 | // Auth failures surface on the first turn instead. |
| 139 | return snapshot( |
| 140 | { |
| 141 | installed: true, |
| 142 | version, |
| 143 | auth: { status: "unknown" }, |
| 144 | ...(models.length > 0 |
| 145 | ? { status: "ready" } |
| 146 | : { |
| 147 | status: "warning", |
| 148 | message: |
| 149 | "Muse Code returned no models. Run `muse login` on this T3 server host and refresh its status.", |
| 150 | }), |
| 151 | }, |
| 152 | models, |
| 153 | ); |
| 154 | }); |