1import type * as ScriptProgress from "@t3tools/provider-core/server/ScriptProgress";
2import { ProviderDriverKind } from "@t3tools/contracts";
3import { MuseSettings } from "../settings.ts";
4import * as HostProcess from "@t3tools/shared/HostProcess";
5import * as Effect from "effect/Effect";
6import * as FileSystem from "effect/FileSystem";
7import * as Path from "effect/Path";
8import * as Schema from "effect/Schema";
9import * as HttpClient from "effect/http/HttpClient";
10import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
11
12import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
13import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
14import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
15import { expandHomePath } from "@t3tools/provider-core/server/pathExpansion";
16import { makeMuseTextGeneration } from "./textGeneration.ts";
17import { ProviderDriverError } from "@t3tools/provider-core/server/errors";
18import { makeMuseAdapterV2 } from "./adapter.ts";
19import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
20import * as ProviderContinuationRequests from "@t3tools/provider-core/server/ProviderContinuationRequests";
21import { checkMuseProviderStatus, makePendingMuseProvider } from "./status.ts";
22import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
23import { makeManagedServerProvider } from "@t3tools/provider-core/server/managedProvider";
24import { enrichMuseSnapshot, latestMuseVersion, museMaintenance } from "./maintenance.ts";
25import { makeMuseEnvironment } from "./sdk.ts";
26import {
27 defaultProviderContinuationIdentity,
28 type ProviderDriver,
29 type ProviderInstance,
30} from "@t3tools/provider-core/server/driver";
31import { mergeProviderInstanceEnvironment } from "@t3tools/provider-core/server/instanceEnvironment";
32import {
33 makeCachedProviderMaintenanceResolution,
34 resolveProviderMaintenanceCapabilitiesEffect,
35} from "@t3tools/provider-core/server/maintenanceResolver";
36import {
37 haveProviderSnapshotSettingsChanged,
38 makeProviderSnapshotSettingsSource,
39 type ProviderSnapshotSettings,
40} from "@t3tools/provider-core/server/snapshotSettings";
41import { withInstanceIdentity } from "@t3tools/provider-core/server/instanceIdentity";
42
43const DRIVER_KIND = ProviderDriverKind.make("muse");
44const decodeMuseSettings = Schema.decodeSync(MuseSettings);
45
46export type MuseDriverEnv =
47 | IdAllocator.IdAllocatorV2
48 | ScriptProgress.ScriptProgress
49 | McpProviderSessions.McpProviderSessions
50 | ProviderHost.ProviderHost
51 | ChildProcessSpawner.ChildProcessSpawner
52 | FileSystem.FileSystem
53 | HttpClient.HttpClient
54 | ProviderLatestVersions.ProviderLatestVersions
55 | Path.Path
56 | ProviderEventLoggers.ProviderEventLoggers;
57
58export const MuseDriver: ProviderDriver<MuseSettings, MuseDriverEnv> = {
59 driverKind: DRIVER_KIND,
60 metadata: { displayName: "Muse Code", supportsMultipleInstances: true },
61 configSchema: MuseSettings,
62 defaultConfig: () => decodeMuseSettings({}),
63 create: ({ instanceId, displayName, accentColor, environment, enabled, config }) =>
64 Effect.gen(function* () {
65 const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
66 const fileSystem = yield* FileSystem.FileSystem;
67 const path = yield* Path.Path;
68 const httpClient = yield* HttpClient.HttpClient;
69 const latestVersions = yield* ProviderLatestVersions.ProviderLatestVersions;
70 const host = yield* ProviderHost.ProviderHost;
71 const eventLoggers = yield* ProviderEventLoggers.ProviderEventLoggers;
72 const { cwd } = host.paths;
73 const continuationRequests = yield* ProviderContinuationRequests.ProviderContinuationRequests;
74 const hostEnvironment = yield* HostProcess.Environment;
75 // Drop an inherited META_API_KEY so Muse uses its login; an instance value still wins.
76 const processEnvironment = yield* mergeProviderInstanceEnvironment(
77 environment,
78 makeMuseEnvironment(hostEnvironment),
79 );
80 const effectiveConfig = {
81 ...config,
82 enabled,
83 binaryPath: expandHomePath(config.binaryPath, yield* HostProcess.HomeDirectory),
84 } satisfies MuseSettings;
85 const continuationIdentity = defaultProviderContinuationIdentity({
86 driverKind: DRIVER_KIND,
87 instanceId,
88 });
89 const stampIdentity = withInstanceIdentity({
90 instanceId,
91 driverKind: DRIVER_KIND,
92 displayName,
93 accentColor,
94 continuationGroupKey: continuationIdentity.continuationKey,
95 });
96 const snapshotSettings = yield* makeProviderSnapshotSettingsSource(effectiveConfig);
97 const resolveInstallation = yield* makeCachedProviderMaintenanceResolution(
98 resolveProviderMaintenanceCapabilitiesEffect(museMaintenance, {
99 binaryPath: effectiveConfig.binaryPath,
100 env: processEnvironment,
101 }).pipe(
102 Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
103 Effect.provideService(FileSystem.FileSystem, fileSystem),
104 Effect.provideService(Path.Path, path),
105 ),
106 );
107 const resolveMaintenance = (options?: { readonly fresh?: boolean }) =>
108 Effect.gen(function* () {
109 const capabilities = yield* resolveInstallation(options);
110 // The maintenance runner requests fresh capabilities around an explicit update
111 // and needs the native target version to verify that the command actually upgraded.
112 const latestVersion = options?.fresh
113 ? yield* latestMuseVersion(processEnvironment, { fresh: true }).pipe(
114 Effect.provideService(HttpClient.HttpClient, httpClient),
115 Effect.provideService(
116 ProviderLatestVersions.ProviderLatestVersions,
117 latestVersions,
118 ),
119 )
120 : undefined;
121 return latestVersion !== undefined ? { ...capabilities, latestVersion } : capabilities;
122 });
123 const snapshot = yield* makeManagedServerProvider<ProviderSnapshotSettings<MuseSettings>>({
124 resolveMaintenance,
125 getSettings: snapshotSettings.getSettings,
126 streamSettings: snapshotSettings.streamSettings,
127 haveSettingsChanged: haveProviderSnapshotSettingsChanged,
128 initialSnapshot: (settings) =>
129 makePendingMuseProvider(settings.provider).pipe(Effect.map(stampIdentity)),
130 checkProvider: checkMuseProviderStatus(effectiveConfig, processEnvironment, cwd).pipe(
131 Effect.map(stampIdentity),
132 Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
133 Effect.provideService(FileSystem.FileSystem, fileSystem),
134 Effect.provideService(Path.Path, path),
135 ),
136 enrichSnapshot: ({ settings, snapshot: currentSnapshot, publishSnapshot }) =>
137 resolveMaintenance().pipe(
138 Effect.flatMap((maintenanceCapabilities) =>
139 enrichMuseSnapshot({
140 snapshot: currentSnapshot,
141 maintenanceCapabilities,
142 enableProviderUpdateChecks: settings.enableProviderUpdateChecks,
143 environment: processEnvironment,
144 }),
145 ),
146 Effect.provideService(HttpClient.HttpClient, httpClient),
147 Effect.provideService(ProviderLatestVersions.ProviderLatestVersions, latestVersions),
148 Effect.flatMap(publishSnapshot),
149 ),
150 }).pipe(
151 Effect.mapError(
152 (cause) =>
153 new ProviderDriverError({
154 driver: DRIVER_KIND,
155 instanceId,
156 detail: "Failed to build Muse Code snapshot.",
157 cause,
158 }),
159 ),
160 );
161 const modelCatalog = snapshot.getSnapshot.pipe(Effect.map((current) => current.models));
162 const orchestrationAdapter = yield* makeMuseAdapterV2({
163 instanceId,
164 settings: effectiveConfig,
165 environment: processEnvironment,
166 modelCatalog,
167 ...(eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {}),
168 continuationRequests,
169 });
170 const textGeneration = yield* makeMuseTextGeneration(effectiveConfig, {
171 environment: processEnvironment,
172 modelCatalog,
173 });
174 return {
175 instanceId,
176 driverKind: DRIVER_KIND,
177 continuationIdentity,
178 displayName,
179 accentColor,
180 enabled,
181 snapshot,
182 orchestrationAdapter,
183 textGeneration,
184 } satisfies ProviderInstance;
185 }),
186};