From d4662a2ee8bf8db4f59c234c65f9847cd1142d4d Mon Sep 17 00:00:00 2001 From: Julius Marminge <51714798+juliusmarminge@users.noreply.github.com> Date: Thu, 8 Oct 2026 14:53:13 -0700 Subject: [PATCH] refactor(provider-muse): move Muse Code into its own provider package Muse Code moves into @t3tools/provider-muse with the same layout as Pi: - ./settings: the instance settings schema, moved out of contracts. - ./client: label, Beta badge, settings form, and a plain-data glyph. Web and mobile draw it from the package instead of hand-drawn icons. - ./server: the driver and adapter factory the server registers. - ./testing: MSP launch helpers for the replay testkit and recorder. The Muse adapter and driver reach the server only through ProviderHost (cwd and attachment paths), and its text generation uses the provider-core ProviderTextGeneration type. The shared fake CLI test helper moves to provider-testing. Co-Authored-By: Claude Opus 5.5 --- apps/mobile/package.json | 1 + apps/mobile/src/components/ProviderIcon.tsx | 12 --- apps/mobile/src/lib/providerClients.ts | 3 +- apps/server/package.json | 1 + .../scripts/record-muse-msp-replay-fixture.ts | 7 +- .../Adapters/MuseAdapterV2.testkit.ts | 30 +++--- apps/server/src/provider/GrokProvider.test.ts | 2 +- apps/server/src/provider/builtInDrivers.ts | 2 +- .../ClaudeTextGeneration.test.ts | 2 +- .../CodexTextGeneration.test.ts | 2 +- .../textGeneration/GrokTextGeneration.test.ts | 2 +- apps/web/package.json | 1 + apps/web/src/components/Icons.tsx | 10 -- .../components/chat/ProviderInstanceIcon.tsx | 2 - .../components/settings/providerDriverMeta.ts | 9 +- package.json | 2 +- packages/contracts/src/settings.ts | 23 ----- packages/provider-muse/package.json | 41 ++++++++ packages/provider-muse/src/client.ts | 27 ++++++ packages/provider-muse/src/server.ts | 8 ++ .../provider-muse/src/server/adapter.test.ts | 19 ++-- .../provider-muse/src/server/adapter.ts | 33 +++---- .../provider-muse/src/server/driver.ts | 26 +++-- .../src/server/itemPresentation.test.ts | 4 +- .../src/server/itemPresentation.ts | 2 +- .../src/server/maintenance.test.ts | 2 +- .../provider-muse/src/server/maintenance.ts | 0 .../src/server/modelCatalog.test.ts | 6 +- .../provider-muse/src/server/modelCatalog.ts | 5 +- .../provider-muse/src/server/protocol.test.ts | 2 +- .../provider-muse/src/server/protocol.ts | 0 .../provider-muse/src/server/sdk.test.ts | 2 +- .../provider-muse/src/server/sdk.ts | 0 .../provider-muse/src/server/status.test.ts | 8 +- .../provider-muse/src/server/status.ts | 9 +- .../src/server/textGeneration.test.ts | 8 +- .../src/server/textGeneration.ts | 96 +++++++++---------- packages/provider-muse/src/settings.ts | 36 +++++++ packages/provider-muse/src/testing.ts | 14 +++ packages/provider-muse/tsconfig.json | 7 ++ packages/provider-testing/package.json | 5 + .../provider-testing/src}/fakeCli.ts | 0 pnpm-lock.yaml | 46 +++++++++ scripts/release-smoke.ts | 1 + 44 files changed, 324 insertions(+), 194 deletions(-) create mode 100644 packages/provider-muse/package.json create mode 100644 packages/provider-muse/src/client.ts create mode 100644 packages/provider-muse/src/server.ts rename apps/server/src/orchestration-v2/Adapters/MuseAdapterV2.test.ts => packages/provider-muse/src/server/adapter.test.ts (98%) rename apps/server/src/orchestration-v2/Adapters/MuseAdapterV2.ts => packages/provider-muse/src/server/adapter.ts (98%) rename apps/server/src/provider/Drivers/MuseDriver.ts => packages/provider-muse/src/server/driver.ts (91%) rename apps/server/src/orchestration-v2/Adapters/MuseItemPresentation.test.ts => packages/provider-muse/src/server/itemPresentation.test.ts (94%) rename apps/server/src/orchestration-v2/Adapters/MuseItemPresentation.ts => packages/provider-muse/src/server/itemPresentation.ts (98%) rename apps/server/src/provider/museMaintenance.test.ts => packages/provider-muse/src/server/maintenance.test.ts (99%) rename apps/server/src/provider/museMaintenance.ts => packages/provider-muse/src/server/maintenance.ts (100%) rename apps/server/src/provider/museModelCatalog.test.ts => packages/provider-muse/src/server/modelCatalog.test.ts (96%) rename apps/server/src/provider/museModelCatalog.ts => packages/provider-muse/src/server/modelCatalog.ts (97%) rename apps/server/src/provider/museProtocol.test.ts => packages/provider-muse/src/server/protocol.test.ts (91%) rename apps/server/src/provider/museProtocol.ts => packages/provider-muse/src/server/protocol.ts (100%) rename apps/server/src/provider/museSdk.test.ts => packages/provider-muse/src/server/sdk.test.ts (99%) rename apps/server/src/provider/museSdk.ts => packages/provider-muse/src/server/sdk.ts (100%) rename apps/server/src/provider/MuseProvider.test.ts => packages/provider-muse/src/server/status.test.ts (96%) rename apps/server/src/provider/MuseProvider.ts => packages/provider-muse/src/server/status.ts (94%) rename apps/server/src/textGeneration/MuseTextGeneration.test.ts => packages/provider-muse/src/server/textGeneration.test.ts (98%) rename apps/server/src/textGeneration/MuseTextGeneration.ts => packages/provider-muse/src/server/textGeneration.ts (83%) create mode 100644 packages/provider-muse/src/settings.ts create mode 100644 packages/provider-muse/src/testing.ts create mode 100644 packages/provider-muse/tsconfig.json rename {apps/server/src/testUtils => packages/provider-testing/src}/fakeCli.ts (100%) diff --git a/apps/mobile/package.json b/apps/mobile/package.json index 35fec0c2769a..08307ad65c2a 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -70,6 +70,7 @@ "@t3tools/mobile-review-diff-native": "file:./modules/t3-review-diff", "@t3tools/mobile-terminal-native": "file:./modules/t3-terminal", "@t3tools/provider-core": "workspace:*", + "@t3tools/provider-muse": "workspace:*", "@t3tools/provider-pi": "workspace:*", "@t3tools/shared": "workspace:*", "@tabler/icons-react-native": "^3.44.0", diff --git a/apps/mobile/src/components/ProviderIcon.tsx b/apps/mobile/src/components/ProviderIcon.tsx index 1675b15461d0..0645e6739818 100644 --- a/apps/mobile/src/components/ProviderIcon.tsx +++ b/apps/mobile/src/components/ProviderIcon.tsx @@ -120,18 +120,6 @@ export function ProviderIcon(props: ProviderIconProps) { ); } - if (props.provider === "muse") { - return ( - - - - ); - } - const packageIcon = getProviderClient(props.provider)?.icon; if (packageIcon) { const themeFill = (fill: { readonly light: string; readonly dark: string }) => diff --git a/apps/mobile/src/lib/providerClients.ts b/apps/mobile/src/lib/providerClients.ts index c8c1c05c0158..2d3ec1224992 100644 --- a/apps/mobile/src/lib/providerClients.ts +++ b/apps/mobile/src/lib/providerClients.ts @@ -1,9 +1,10 @@ import { isProviderDriverKind } from "@t3tools/contracts"; import { makeProviderClientRegistry } from "@t3tools/provider-core/client"; +import { museClient } from "@t3tools/provider-muse/client"; import { piClient } from "@t3tools/provider-pi/client"; /** The provider client definitions this mobile build ships. */ -const providerClients = makeProviderClientRegistry([piClient]); +const providerClients = makeProviderClientRegistry([museClient, piClient]); /** The client definition for a driver kind, or `undefined` for drivers drawn by hand. */ export function getProviderClient(driver: string | null | undefined) { diff --git a/apps/server/package.json b/apps/server/package.json index 7672fbf534e3..bb776149e4cd 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -42,6 +42,7 @@ "@opencode/protocol": "2.0.23", "@opencode/schema": "2.0.23", "@t3tools/provider-core": "workspace:*", + "@t3tools/provider-muse": "workspace:*", "@t3tools/provider-pi": "workspace:*", "diff": "8.0.3", "effect": "catalog:", diff --git a/apps/server/scripts/record-muse-msp-replay-fixture.ts b/apps/server/scripts/record-muse-msp-replay-fixture.ts index dde469487be0..a3cbfc6f9d14 100644 --- a/apps/server/scripts/record-muse-msp-replay-fixture.ts +++ b/apps/server/scripts/record-muse-msp-replay-fixture.ts @@ -33,8 +33,11 @@ import { MuseOrchestratorReplayHarness, } from "../src/orchestration-v2/Adapters/MuseAdapterV2.testkit.ts"; import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator"; -import { parseMuseVersion } from "../src/provider/museMaintenance.ts"; -import { makeMuseEnvironment, museServeArgs } from "../src/provider/museSdk.ts"; +import { + makeMuseEnvironment, + museServeArgs, + parseMuseVersion, +} from "@t3tools/provider-muse/testing"; import { provideDeterministicTestRuntime } from "../src/orchestration-v2/testkit/DeterministicRuntime.ts"; import { ORCHESTRATOR_REPLAY_FIXTURES } from "../src/orchestration-v2/testkit/fixtures/index.ts"; import { materializeFixtureInput } from "../src/orchestration-v2/testkit/fixtures/shared.ts"; diff --git a/apps/server/src/orchestration-v2/Adapters/MuseAdapterV2.testkit.ts b/apps/server/src/orchestration-v2/Adapters/MuseAdapterV2.testkit.ts index cce14853f6fd..f36d2c5959f5 100644 --- a/apps/server/src/orchestration-v2/Adapters/MuseAdapterV2.testkit.ts +++ b/apps/server/src/orchestration-v2/Adapters/MuseAdapterV2.testkit.ts @@ -16,33 +16,31 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { Connection, type DuplexTransport } from "@muse-code/sdk"; import { - MuseSettings, ProviderDriverKind, ProviderInstanceId, ProviderReplayEntry, type ProviderReplayEntry as ProviderReplayEntryType, type ProviderReplayTranscript, } from "@t3tools/contracts"; +import { MuseSettings } from "@t3tools/provider-muse/settings"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; import * as Schema from "effect/Schema"; -import * as ServerConfig from "../../config.ts"; +import { ProviderHost } from "@t3tools/provider-core/server/ProviderHost"; +import { layerTestProviderHost } from "@t3tools/provider-testing/host"; import { museInitializeParams, museServeArgs, type MuseSdkHost, type MuseSdkHostOptions, -} from "../../provider/museSdk.ts"; +} from "@t3tools/provider-muse/testing"; import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator"; import * as ProviderAdapterRegistry from "../ProviderAdapterRegistry.ts"; import * as ProviderContinuationRequests from "@t3tools/provider-core/server/continuationRequests"; -import { - makeReplayServerConfig, - type OrchestratorV2ProviderReplayHarness, -} from "../testkit/ProviderReplayHarness.ts"; -import { makeMuseAdapterV2 } from "./MuseAdapterV2.ts"; +import type { OrchestratorV2ProviderReplayHarness } from "../testkit/ProviderReplayHarness.ts"; +import { makeMuseAdapterV2 } from "@t3tools/provider-muse/server"; export const MUSE_PROVIDER_KIND = "muse"; export const MUSE_MSP_REPLAY_PROTOCOL = "muse.msp-jsonl"; @@ -448,10 +446,6 @@ export function layer(input: { /** Replay needs none; a live recording passes the host environment. */ readonly environment?: NodeJS.ProcessEnv; }) { - const layerServerConfig = Layer.effect( - ServerConfig.ServerConfig, - makeReplayServerConfig(`muse-${input.scenario}`).pipe(Effect.orDie), - ).pipe(Layer.provide(NodeServices.layer)); return ProviderAdapterRegistry.layerFromAdaptersEffect( Effect.gen(function* () { return [ @@ -460,7 +454,7 @@ export function layer(input: { settings: DEFAULT_MUSE_SETTINGS, environment: input.environment ?? {}, idAllocator: yield* IdAllocator.IdAllocatorV2, - serverConfig: yield* ServerConfig.ServerConfig, + host: yield* ProviderHost, fileSystem: yield* FileSystem.FileSystem, createHost: input.createHost, // Same queue the continuation worker drains when the fixture runs it. @@ -468,7 +462,15 @@ export function layer(input: { }), ]; }), - ).pipe(Layer.provide(Layer.mergeAll(layerServerConfig, NodeServices.layer, IdAllocator.layer))); + ).pipe( + Layer.provide( + Layer.mergeAll( + layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)), + NodeServices.layer, + IdAllocator.layer, + ), + ), + ); } export const MuseOrchestratorReplayHarness: OrchestratorV2ProviderReplayHarness< diff --git a/apps/server/src/provider/GrokProvider.test.ts b/apps/server/src/provider/GrokProvider.test.ts index f77ee10465e2..000176e9bb23 100644 --- a/apps/server/src/provider/GrokProvider.test.ts +++ b/apps/server/src/provider/GrokProvider.test.ts @@ -18,7 +18,7 @@ import { grokSlashCommandsFromInitialize, parseGrokModelsCliOutput, } from "./GrokProvider.ts"; -import { execScriptSource, writeFakeCli } from "../testUtils/fakeCli.ts"; +import { execScriptSource, writeFakeCli } from "@t3tools/provider-testing/fakeCli"; import { grokUsageResponseToLimits, readGrokAccount } from "./grokUsageLimits.ts"; const decodeGrokSettings = Schema.decodeSync(GrokSettings); diff --git a/apps/server/src/provider/builtInDrivers.ts b/apps/server/src/provider/builtInDrivers.ts index 08a403b70e04..1690a998c5f4 100644 --- a/apps/server/src/provider/builtInDrivers.ts +++ b/apps/server/src/provider/builtInDrivers.ts @@ -27,7 +27,7 @@ import { CodexDriver, type CodexDriverEnv } from "./Drivers/CodexDriver.ts"; import { CursorDriver, type CursorDriverEnv } from "./Drivers/CursorDriver.ts"; import { GrokDriver, type GrokDriverEnv } from "./Drivers/GrokDriver.ts"; import { OpenCodeDriver, type OpenCodeDriverEnv } from "./Drivers/OpenCodeDriver.ts"; -import { MuseDriver, type MuseDriverEnv } from "./Drivers/MuseDriver.ts"; +import { MuseDriver, type MuseDriverEnv } from "@t3tools/provider-muse/server"; import { PiDriver, type PiDriverEnv } from "@t3tools/provider-pi/server"; import type { AnyProviderDriver } from "@t3tools/provider-core/server/driver"; diff --git a/apps/server/src/textGeneration/ClaudeTextGeneration.test.ts b/apps/server/src/textGeneration/ClaudeTextGeneration.test.ts index 46e015123df2..29c0f950f7c8 100644 --- a/apps/server/src/textGeneration/ClaudeTextGeneration.test.ts +++ b/apps/server/src/textGeneration/ClaudeTextGeneration.test.ts @@ -21,7 +21,7 @@ import { import * as TextGeneration from "./TextGeneration.ts"; import { sanitizeThreadTitle } from "@t3tools/provider-core/server/textGenerationUtils"; import { makeClaudeTextGeneration } from "./ClaudeTextGeneration.ts"; -import { writeFakeCli } from "../testUtils/fakeCli.ts"; +import { writeFakeCli } from "@t3tools/provider-testing/fakeCli"; const decodeClaudeSettings = Schema.decodeSync(ClaudeSettings); const layerClaudeTextGenerationTest = ServerConfig.ServerConfig.layerTest(process.cwd(), { diff --git a/apps/server/src/textGeneration/CodexTextGeneration.test.ts b/apps/server/src/textGeneration/CodexTextGeneration.test.ts index fbfbe909f962..e991ddf17fdf 100644 --- a/apps/server/src/textGeneration/CodexTextGeneration.test.ts +++ b/apps/server/src/textGeneration/CodexTextGeneration.test.ts @@ -16,7 +16,7 @@ import { CodexSettings, ProviderInstanceId, TextGenerationError } from "@t3tools import * as ServerConfig from "../config.ts"; import * as TextGeneration from "./TextGeneration.ts"; import { makeCodexTextGeneration } from "./CodexTextGeneration.ts"; -import { writeFakeCli } from "../testUtils/fakeCli.ts"; +import { writeFakeCli } from "@t3tools/provider-testing/fakeCli"; const decodeCodexSettings = Schema.decodeSync(CodexSettings); const DEFAULT_TEST_MODEL_SELECTION = createModelSelection( diff --git a/apps/server/src/textGeneration/GrokTextGeneration.test.ts b/apps/server/src/textGeneration/GrokTextGeneration.test.ts index a577065913ab..8165683f62cf 100644 --- a/apps/server/src/textGeneration/GrokTextGeneration.test.ts +++ b/apps/server/src/textGeneration/GrokTextGeneration.test.ts @@ -16,7 +16,7 @@ import { GrokSettings, ProviderInstanceId } from "@t3tools/contracts"; import * as ServerConfig from "../config.ts"; import * as TextGeneration from "./TextGeneration.ts"; import { makeGrokTextGeneration } from "./GrokTextGeneration.ts"; -import { execScriptSource, writeFakeCli } from "../testUtils/fakeCli.ts"; +import { execScriptSource, writeFakeCli } from "@t3tools/provider-testing/fakeCli"; const decodeGrokSettings = Schema.decodeSync(GrokSettings); const __dirname = NodePath.dirname(NodeURL.fileURLToPath(import.meta.url)); diff --git a/apps/web/package.json b/apps/web/package.json index ab2ae7b35ea3..63a5f5c2ad35 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -29,6 +29,7 @@ "@t3tools/client-runtime": "workspace:*", "@t3tools/contracts": "workspace:*", "@t3tools/provider-core": "workspace:*", + "@t3tools/provider-muse": "workspace:*", "@t3tools/provider-pi": "workspace:*", "@t3tools/shared": "workspace:*", "@tanstack/react-pacer": "^0.19.4", diff --git a/apps/web/src/components/Icons.tsx b/apps/web/src/components/Icons.tsx index 66e170dacf61..7f38d65f2a14 100644 --- a/apps/web/src/components/Icons.tsx +++ b/apps/web/src/components/Icons.tsx @@ -815,13 +815,3 @@ export const ComputerUseAppIcon: Icon = (props) => { ); }; - -export const MuseIcon: Icon = (props) => ( - - - -); diff --git a/apps/web/src/components/chat/ProviderInstanceIcon.tsx b/apps/web/src/components/chat/ProviderInstanceIcon.tsx index ddfffd4fa9d3..e9e54575c910 100644 --- a/apps/web/src/components/chat/ProviderInstanceIcon.tsx +++ b/apps/web/src/components/chat/ProviderInstanceIcon.tsx @@ -8,7 +8,6 @@ import { ClaudeAI, CursorIcon, GrokIcon, - MuseIcon, Icon, OpenAI, OpenCodeIcon, @@ -29,7 +28,6 @@ const PROVIDER_ICON_BY_PROVIDER: Partial> = { [ProviderDriverKind.make("opencode")]: OpenCodeIcon, [ProviderDriverKind.make("cursor")]: CursorIcon, [ProviderDriverKind.make("grok")]: GrokIcon, - [ProviderDriverKind.make("muse")]: MuseIcon, [ProviderDriverKind.make("antigravity")]: AntigravityIcon, }; diff --git a/apps/web/src/components/settings/providerDriverMeta.ts b/apps/web/src/components/settings/providerDriverMeta.ts index 59b42d0f6751..c967100888c8 100644 --- a/apps/web/src/components/settings/providerDriverMeta.ts +++ b/apps/web/src/components/settings/providerDriverMeta.ts @@ -6,10 +6,10 @@ import { CursorSettings, GrokSettings, OpenCodeSettings, - MuseSettings, ProviderDriverKind, } from "@t3tools/contracts"; import { makeProviderClientRegistry } from "@t3tools/provider-core/client"; +import { museClient } from "@t3tools/provider-muse/client"; import { piClient } from "@t3tools/provider-pi/client"; /** The provider client definitions this web build ships, in presentation order. */ @@ -53,12 +53,7 @@ export const providerClients = makeProviderClientRegistry([ label: "Antigravity", settingsSchema: AntigravitySettings, }, - { - driverKind: ProviderDriverKind.make("muse"), - label: "Muse Code", - settingsSchema: MuseSettings, - badgeLabel: "Beta", - }, + museClient, piClient, { driverKind: ProviderDriverKind.make("acpRegistry"), diff --git a/package.json b/package.json index eece70e89ef5..a345b307273d 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "tc": "vp run -r --concurrency-limit 2 typecheck", "lint": "vp lint --report-unused-disable-directives", "knip": "knip --preprocessor ./scripts/knip-schemas.ts", - "knip:check": "knip --include files,dependencies --no-config-hints && knip --workspace apps/server --workspace apps/desktop --workspace apps/web --workspace packages/client-runtime --workspace packages/contracts --workspace packages/effect-acp --workspace packages/effect-codex-app-server --workspace packages/shared --workspace packages/ssh --workspace packages/tailscale --workspace packages/provider-core --workspace packages/provider-testing --workspace packages/provider-pi --exports --preprocessor ./scripts/knip-schemas.ts --no-config-hints", + "knip:check": "knip --include files,dependencies --no-config-hints && knip --workspace apps/server --workspace apps/desktop --workspace apps/web --workspace packages/client-runtime --workspace packages/contracts --workspace packages/effect-acp --workspace packages/effect-codex-app-server --workspace packages/shared --workspace packages/ssh --workspace packages/tailscale --workspace packages/provider-core --workspace packages/provider-testing --workspace packages/provider-pi --workspace packages/provider-muse --exports --preprocessor ./scripts/knip-schemas.ts --no-config-hints", "knip:production": "knip --production --preprocessor ./scripts/knip-schemas.ts", "lint:mobile": "node scripts/mobile-native-static-check.ts", "test": "vp run -r test", diff --git a/packages/contracts/src/settings.ts b/packages/contracts/src/settings.ts index c9b3c00136dc..e62f4ff578b7 100644 --- a/packages/contracts/src/settings.ts +++ b/packages/contracts/src/settings.ts @@ -840,29 +840,6 @@ export const AntigravitySettings = makeProviderSettingsSchema( ); export type AntigravitySettings = typeof AntigravitySettings.Type; -export const MuseSettings = makeProviderSettingsSchema( - { - enabled: Schema.Boolean.pipe( - Schema.withDecodingDefault(Effect.succeed(false)), - Schema.annotateKey({ providerSettingsForm: { hidden: true } }), - ), - binaryPath: makeBinaryPathSetting("muse").pipe( - Schema.annotateKey({ - title: "Binary path", - description: - "Path to the Muse Code binary on this environment. Sign in with muse login on that host.", - providerSettingsForm: { placeholder: "muse", clearWhenEmpty: "omit" }, - }), - ), - customModels: Schema.Array(CustomModelSetting).pipe( - Schema.withDecodingDefault(Effect.succeed([])), - Schema.annotateKey({ providerSettingsForm: { hidden: true } }), - ), - }, - { order: ["binaryPath"] }, -); -export type MuseSettings = typeof MuseSettings.Type; - export const AcpRegistryDistributionPreference = Schema.Literals(["auto", "binary", "npx", "uvx"]); export type AcpRegistryDistributionPreference = typeof AcpRegistryDistributionPreference.Type; diff --git a/packages/provider-muse/package.json b/packages/provider-muse/package.json new file mode 100644 index 000000000000..a59c6cd20c8d --- /dev/null +++ b/packages/provider-muse/package.json @@ -0,0 +1,41 @@ +{ + "name": "@t3tools/provider-muse", + "private": true, + "type": "module", + "exports": { + "./client": { + "types": "./src/client.ts", + "import": "./src/client.ts" + }, + "./settings": { + "types": "./src/settings.ts", + "import": "./src/settings.ts" + }, + "./server": { + "types": "./src/server.ts", + "import": "./src/server.ts" + }, + "./testing": { + "types": "./src/testing.ts", + "import": "./src/testing.ts" + } + }, + "scripts": { + "typecheck": "tsc --noEmit", + "test": "vp test run --config ../../vite.config.ts --dir ." + }, + "dependencies": { + "@muse-code/sdk": "1.4.2", + "@t3tools/contracts": "workspace:*", + "@t3tools/provider-core": "workspace:*", + "@t3tools/shared": "workspace:*", + "effect": "catalog:" + }, + "devDependencies": { + "@effect/platform-node": "catalog:", + "@effect/vitest": "catalog:", + "@t3tools/provider-testing": "workspace:*", + "@types/node": "catalog:", + "vite-plus": "catalog:" + } +} diff --git a/packages/provider-muse/src/client.ts b/packages/provider-muse/src/client.ts new file mode 100644 index 000000000000..d709e8689395 --- /dev/null +++ b/packages/provider-muse/src/client.ts @@ -0,0 +1,27 @@ +/** + * Muse Code's client definition: settings form, label, badge, and glyph. + * Browser- and React Native-safe. + * + * @module provider-muse/client + */ +import { ProviderDriverKind } from "@t3tools/contracts"; +import { defineProviderClient } from "@t3tools/provider-core/client"; + +import { MuseSettings } from "./settings.ts"; + +export const museClient = defineProviderClient({ + driverKind: ProviderDriverKind.make("muse"), + label: "Muse Code", + settingsSchema: MuseSettings, + badgeLabel: "Beta", + icon: { + viewBox: "0 0 150.4828 100", + fill: { light: "#171717", dark: "#e5e5e5" }, + paths: [ + { + d: "M107.9999847,0.0000013c-12.3391113,0-21.984787,9.2935991-30.7164001,21.0993118 C65.2847366,5.821527,55.2499847,0,43.2413788,0C18.7586193,0,0,31.8620682,0,65.5862122 C0,86.6896515,10.2095623,100,27.3103466,100c12.3080502,0,21.1600361-5.8025894,36.8965454-33.3103409 c0,0,6.5597382-11.5841255,11.0725021-19.563858c1.5813522,2.5531845,3.2431107,5.3001404,4.9964676,8.2535095 l7.379303,12.4137955C102.0298843,91.8478851,110.039032,100,124.5517273,100 c16.6595764,0,25.9310303-13.4923325,25.9310303-35.0344849 C150.4827576,29.6551723,131.301178,0.0000013,107.9999847,0.0000013z M52.2068977,59.2413788 c-12.7586212,20-17.1724167,24.4827576-24.2758656,24.4827576c-7.3103428,0-11.6551704-6.4178314-11.6551704-17.8620682 c0-24.4827576,12.2068977-49.5172424,26.7586231-49.5172424c7.880127,0,14.4653816,4.5510426,24.5522308,18.9913158 C58.0087967,50.0272293,52.2068977,59.2413788,52.2068977,59.2413788z M100.3597336,56.7235756L91.5367355,42.00877 c-2.3876724-3.8832092-4.6757278-7.451458-6.8838348-10.7230263 c7.951973-12.273531,14.5113449-18.3891907,22.3126144-18.3891907c16.2069016,0,29.1724243,23.8620682,29.1724243,53.1724091 c0,11.1724167-3.6593475,17.6551743-11.2413864,17.6551743 C117.6294937,83.7241364,114.1578674,78.924736,100.3597336,56.7235756z", + fillRule: "evenodd", + }, + ], + }, +}); diff --git a/packages/provider-muse/src/server.ts b/packages/provider-muse/src/server.ts new file mode 100644 index 000000000000..7100802d7e13 --- /dev/null +++ b/packages/provider-muse/src/server.ts @@ -0,0 +1,8 @@ +/** + * Muse Code's server entry: the driver the server registers, plus the + * adapter factory the replay testkit and fixture recorder build on. + * + * @module provider-muse/server + */ +export { MuseDriver, type MuseDriverEnv } from "./server/driver.ts"; +export { makeMuseAdapterV2, type MuseAdapterV2Options } from "./server/adapter.ts"; diff --git a/apps/server/src/orchestration-v2/Adapters/MuseAdapterV2.test.ts b/packages/provider-muse/src/server/adapter.test.ts similarity index 98% rename from apps/server/src/orchestration-v2/Adapters/MuseAdapterV2.test.ts rename to packages/provider-muse/src/server/adapter.test.ts index 77e12726a183..59f8adec4026 100644 --- a/apps/server/src/orchestration-v2/Adapters/MuseAdapterV2.test.ts +++ b/packages/provider-muse/src/server/adapter.test.ts @@ -5,7 +5,6 @@ import { MUSE_DEFAULT_MODEL, EnvironmentId, MessageId, - MuseSettings, NodeId, ProjectId, ProviderInstanceId, @@ -19,6 +18,7 @@ import { type OrchestrationV2AppThread, type OrchestrationV2ProviderThread, } from "@t3tools/contracts"; +import { MuseSettings } from "../settings.ts"; import * as DateTime from "effect/DateTime"; import * as Deferred from "effect/Deferred"; import * as Effect from "effect/Effect"; @@ -31,10 +31,11 @@ import * as Queue from "effect/Queue"; import * as Schema from "effect/Schema"; import * as Stream from "effect/Stream"; -import * as ServerConfig from "../../config.ts"; +import { ProviderHost } from "@t3tools/provider-core/server/ProviderHost"; +import { layerTestProviderHost } from "@t3tools/provider-testing/host"; import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession"; -import type { MuseItem } from "../../provider/museProtocol.ts"; -import type { MuseSdkHost } from "../../provider/museSdk.ts"; +import type { MuseItem } from "./protocol.ts"; +import type { MuseSdkHost } from "./sdk.ts"; import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator"; import { ProviderAdapterV2RuntimePolicy, @@ -42,14 +43,12 @@ import { type ProviderAdapterV2TurnInput, } from "@t3tools/provider-core/server/ProviderAdapter"; import type { ProviderContinuationRequest } from "@t3tools/provider-core/server/continuationRequests"; -import { makeMuseAdapterV2, type MuseAdapterV2Options } from "./MuseAdapterV2.ts"; +import { makeMuseAdapterV2, type MuseAdapterV2Options } from "./adapter.ts"; const testLayer = Layer.mergeAll( NodeServices.layer, IdAllocator.layer, - ServerConfig.layerTest(process.cwd(), { prefix: "t3-muse-v2-adapter-" }).pipe( - Layer.provide(NodeServices.layer), - ), + layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)), ); const MUSE_PROVIDER = ProviderDriverKind.make("muse"); const INSTANCE_ID = ProviderInstanceId.make("muse_work"); @@ -205,7 +204,7 @@ const makeHarness = Effect.fnUntraced(function* ( settings: museSettings, environment: { PATH: "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/fake/bin" }, idAllocator: yield* IdAllocator.IdAllocatorV2, - serverConfig: yield* ServerConfig.ServerConfig, + host: yield* ProviderHost, fileSystem: yield* FileSystem.FileSystem, createHost: async () => (hostCount++ === 0 ? fake.host : (replacement ?? fake).host), ...overrides, @@ -437,7 +436,7 @@ describe("MuseAdapterV2", () => { settings: museSettings, environment: { PATH: "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/fake/bin" }, idAllocator: yield* IdAllocator.IdAllocatorV2, - serverConfig: yield* ServerConfig.ServerConfig, + host: yield* ProviderHost, fileSystem: yield* FileSystem.FileSystem, createHost: async () => fake.host, }); diff --git a/apps/server/src/orchestration-v2/Adapters/MuseAdapterV2.ts b/packages/provider-muse/src/server/adapter.ts similarity index 98% rename from apps/server/src/orchestration-v2/Adapters/MuseAdapterV2.ts rename to packages/provider-muse/src/server/adapter.ts index 30d7073463a7..bf1699055534 100644 --- a/apps/server/src/orchestration-v2/Adapters/MuseAdapterV2.ts +++ b/packages/provider-muse/src/server/adapter.ts @@ -1,7 +1,6 @@ import { MspError, type SendUserTurnOptions } from "@muse-code/sdk"; import { MUSE_DEFAULT_MODEL, - type MuseSettings, ProviderDriverKind, type ModelSelection, type OrchestrationV2ConversationMessage, @@ -22,6 +21,7 @@ import { type RuntimeRequestId, type ServerProviderModel, } from "@t3tools/contracts"; +import type { MuseSettings } from "../settings.ts"; import { getModelSelectionStringOptionValue } from "@t3tools/shared/model"; import * as Cause from "effect/Cause"; import * as DateTime from "effect/DateTime"; @@ -35,14 +35,10 @@ import * as Semaphore from "effect/Semaphore"; import * as Scope from "effect/Scope"; import * as Stream from "effect/Stream"; -import { resolveAttachmentPath } from "../../attachmentStore.ts"; -import * as ServerConfig from "../../config.ts"; +import type { ProviderHostShape } from "@t3tools/provider-core/server/ProviderHost"; import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession"; import { buildRuntimeInstructions } from "@t3tools/provider-core/server/runtimeInstructions"; -import { - museModelCapabilities, - resolveMuseReasoningEffort, -} from "../../provider/museModelCatalog.ts"; +import { museModelCapabilities, resolveMuseReasoningEffort } from "./modelCatalog.ts"; import { MuseApproval, MuseCompactResult, @@ -60,14 +56,14 @@ import { museApprovalDecision, museApprovalOptions, type MuseItem, -} from "../../provider/museProtocol.ts"; +} from "./protocol.ts"; import { createMuseSdkHostEffect, museApprovalMode, type createMuseSdkHost, type MuseSdkHost, -} from "../../provider/museSdk.ts"; -import type { EventNdjsonLogger } from "../../provider/EventNdjsonLogger.ts"; +} from "./sdk.ts"; +import type { EventNdjsonLogger } from "@t3tools/provider-core/server/ProviderEventLoggers"; import { providerMessageTextWithAttachmentPaths, isProviderNativeImageAttachment, @@ -103,7 +99,7 @@ import { import type * as ProviderContinuationRequests from "@t3tools/provider-core/server/continuationRequests"; import { makeProviderFailure } from "@t3tools/provider-core/server/failure"; import { turnScopedSelectionTransition } from "@t3tools/provider-core/server/selectionTransition"; -import { museItemStatus, museToolPresentation } from "./MuseItemPresentation.ts"; +import { museItemStatus, museToolPresentation } from "./itemPresentation.ts"; const MUSE_PROVIDER = ProviderDriverKind.make("muse"); const isOpenSessionError = Schema.is(ProviderAdapterOpenSessionError); @@ -208,7 +204,7 @@ export interface MuseAdapterV2Options { readonly settings: MuseSettings; readonly environment: NodeJS.ProcessEnv; readonly idAllocator: IdAllocator.IdAllocatorV2["Service"]; - readonly serverConfig: ServerConfig.ServerConfig["Service"]; + readonly host: ProviderHostShape; readonly fileSystem: FileSystem.FileSystem; readonly modelCatalog?: Effect.Effect>; readonly createHost?: typeof createMuseSdkHost; @@ -293,7 +289,7 @@ export function makeMuseAdapterV2(options: MuseAdapterV2Options): ProviderAdapte const scope = yield* Effect.scope; // Muse rejects non-canonical workspace roots (for example macOS /tmp) and // reports canonical paths in approvals, so resolve symlinks once here. - const requestedCwd = input.runtimePolicy.cwd ?? options.serverConfig.cwd; + const requestedCwd = input.runtimePolicy.cwd ?? options.host.paths.cwd; const cwd = yield* options.fileSystem .realPath(requestedCwd) .pipe(Effect.orElseSucceed(() => requestedCwd)); @@ -1601,19 +1597,12 @@ export function makeMuseAdapterV2(options: MuseAdapterV2Options): ProviderAdapte const text = providerMessageTextWithAttachmentPaths({ text: message.text, attachments: message.attachments, - resolveAttachmentPath: (attachment) => - resolveAttachmentPath({ - attachmentsDir: options.serverConfig.attachmentsDir, - attachment, - }), + resolveAttachmentPath: options.host.resolveAttachmentPath, }); if (text) parts.push({ type: "text", text }); for (const attachment of message.attachments) if (isProviderNativeImageAttachment(attachment)) { - const path = resolveAttachmentPath({ - attachmentsDir: options.serverConfig.attachmentsDir, - attachment, - }); + const path = options.host.resolveAttachmentPath(attachment); if (!path) return yield* protocolError("Muse image attachment is missing"); const bytes = yield* options.fileSystem .readFile(path) diff --git a/apps/server/src/provider/Drivers/MuseDriver.ts b/packages/provider-muse/src/server/driver.ts similarity index 91% rename from apps/server/src/provider/Drivers/MuseDriver.ts rename to packages/provider-muse/src/server/driver.ts index 16bed1c3dab2..87f6b2818907 100644 --- a/apps/server/src/provider/Drivers/MuseDriver.ts +++ b/packages/provider-muse/src/server/driver.ts @@ -1,4 +1,5 @@ -import { MuseSettings, ProviderDriverKind } from "@t3tools/contracts"; +import { ProviderDriverKind } from "@t3tools/contracts"; +import { MuseSettings } from "../settings.ts"; import { HostProcessEnvironment } from "@t3tools/shared/hostProcess"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; @@ -8,18 +9,17 @@ import { HttpClient } from "effect/http"; import { ChildProcessSpawner } from "effect/process"; import { ProviderHost } from "@t3tools/provider-core/server/ProviderHost"; -import * as ServerConfig from "../../config.ts"; import { expandHomePath } from "@t3tools/provider-core/server/pathExpansion"; -import { makeMuseTextGeneration } from "../../textGeneration/MuseTextGeneration.ts"; -import { ProviderDriverError } from "../Errors.ts"; -import { makeMuseAdapterV2 } from "../../orchestration-v2/Adapters/MuseAdapterV2.ts"; +import { makeMuseTextGeneration } from "./textGeneration.ts"; +import { ProviderDriverError } from "@t3tools/provider-core/server/errors"; +import { makeMuseAdapterV2 } from "./adapter.ts"; import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator"; import * as ProviderContinuationRequests from "@t3tools/provider-core/server/continuationRequests"; -import { checkMuseProviderStatus, makePendingMuseProvider } from "../MuseProvider.ts"; -import * as ProviderEventLoggers from "../ProviderEventLoggers.ts"; +import { checkMuseProviderStatus, makePendingMuseProvider } from "./status.ts"; +import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers"; import { makeManagedServerProvider } from "@t3tools/provider-core/server/managedProvider"; -import { enrichMuseSnapshot, latestMuseVersion, museMaintenance } from "../museMaintenance.ts"; -import { makeMuseEnvironment } from "../museSdk.ts"; +import { enrichMuseSnapshot, latestMuseVersion, museMaintenance } from "./maintenance.ts"; +import { makeMuseEnvironment } from "./sdk.ts"; import { defaultProviderContinuationIdentity, type ProviderDriver, @@ -47,8 +47,7 @@ export type MuseDriverEnv = | FileSystem.FileSystem | HttpClient.HttpClient | Path.Path - | ProviderEventLoggers.ProviderEventLoggers - | ServerConfig.ServerConfig; + | ProviderEventLoggers.ProviderEventLoggers; export const MuseDriver: ProviderDriver = { driverKind: DRIVER_KIND, @@ -63,8 +62,7 @@ export const MuseDriver: ProviderDriver = { const httpClient = yield* HttpClient.HttpClient; const host = yield* ProviderHost; const eventLoggers = yield* ProviderEventLoggers.ProviderEventLoggers; - const serverConfig = yield* ServerConfig.ServerConfig; - const { cwd } = serverConfig; + const { cwd } = host.paths; const idAllocator = yield* IdAllocator.IdAllocatorV2; const continuationRequests = yield* ProviderContinuationRequests.ProviderContinuationRequests; const hostEnvironment = yield* HostProcessEnvironment; @@ -155,7 +153,7 @@ export const MuseDriver: ProviderDriver = { settings: effectiveConfig, environment: processEnvironment, idAllocator, - serverConfig, + host, fileSystem, modelCatalog, ...(eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {}), diff --git a/apps/server/src/orchestration-v2/Adapters/MuseItemPresentation.test.ts b/packages/provider-muse/src/server/itemPresentation.test.ts similarity index 94% rename from apps/server/src/orchestration-v2/Adapters/MuseItemPresentation.test.ts rename to packages/provider-muse/src/server/itemPresentation.test.ts index 7e4f489b5b00..59e48db95fd1 100644 --- a/apps/server/src/orchestration-v2/Adapters/MuseItemPresentation.test.ts +++ b/packages/provider-muse/src/server/itemPresentation.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "@effect/vitest"; -import type { MuseItem } from "../../provider/museProtocol.ts"; -import { museItemStatus, museToolPresentation } from "./MuseItemPresentation.ts"; +import type { MuseItem } from "./protocol.ts"; +import { museItemStatus, museToolPresentation } from "./itemPresentation.ts"; const item = (fields: Partial): MuseItem => ({ itemId: "native-item", diff --git a/apps/server/src/orchestration-v2/Adapters/MuseItemPresentation.ts b/packages/provider-muse/src/server/itemPresentation.ts similarity index 98% rename from apps/server/src/orchestration-v2/Adapters/MuseItemPresentation.ts rename to packages/provider-muse/src/server/itemPresentation.ts index 93df7761a3eb..e05490698bef 100644 --- a/apps/server/src/orchestration-v2/Adapters/MuseItemPresentation.ts +++ b/packages/provider-muse/src/server/itemPresentation.ts @@ -1,7 +1,7 @@ import type { OrchestrationV2TurnItem, OrchestrationV2TurnItemStatus } from "@t3tools/contracts"; import * as Schema from "effect/Schema"; -import type { MuseItem } from "../../provider/museProtocol.ts"; +import type { MuseItem } from "./protocol.ts"; const decodeToolArgs = Schema.decodeUnknownSync( Schema.fromJsonString(Schema.Record(Schema.String, Schema.Unknown)), diff --git a/apps/server/src/provider/museMaintenance.test.ts b/packages/provider-muse/src/server/maintenance.test.ts similarity index 99% rename from apps/server/src/provider/museMaintenance.test.ts rename to packages/provider-muse/src/server/maintenance.test.ts index 59d0868e768d..1ac4a48cae60 100644 --- a/apps/server/src/provider/museMaintenance.test.ts +++ b/packages/provider-muse/src/server/maintenance.test.ts @@ -11,7 +11,7 @@ import { museMaintenance, latestMuseVersion, parseMuseVersion, -} from "./museMaintenance.ts"; +} from "./maintenance.ts"; import { ProviderVersionCache, makeManualOnlyProviderMaintenanceCapabilities, diff --git a/apps/server/src/provider/museMaintenance.ts b/packages/provider-muse/src/server/maintenance.ts similarity index 100% rename from apps/server/src/provider/museMaintenance.ts rename to packages/provider-muse/src/server/maintenance.ts diff --git a/apps/server/src/provider/museModelCatalog.test.ts b/packages/provider-muse/src/server/modelCatalog.test.ts similarity index 96% rename from apps/server/src/provider/museModelCatalog.test.ts rename to packages/provider-muse/src/server/modelCatalog.test.ts index 2679be85e853..628628a161bd 100644 --- a/apps/server/src/provider/museModelCatalog.test.ts +++ b/packages/provider-muse/src/server/modelCatalog.test.ts @@ -1,14 +1,14 @@ import { describe, expect, it } from "@effect/vitest"; -import { MuseSettings } from "@t3tools/contracts"; +import { MuseSettings } from "../settings.ts"; import * as Effect from "effect/Effect"; import * as Schema from "effect/Schema"; -import type { MuseSdkHost } from "./museSdk.ts"; +import type { MuseSdkHost } from "./sdk.ts"; import { discoverMuseModels, museModelCapabilities, resolveMuseReasoningEffort, -} from "./museModelCatalog.ts"; +} from "./modelCatalog.ts"; const settings = Schema.decodeSync(MuseSettings)({}); const host = (catalog: Record): MuseSdkHost => ({ diff --git a/apps/server/src/provider/museModelCatalog.ts b/packages/provider-muse/src/server/modelCatalog.ts similarity index 97% rename from apps/server/src/provider/museModelCatalog.ts rename to packages/provider-muse/src/server/modelCatalog.ts index 7a75cf26f6d5..f390d062803c 100644 --- a/apps/server/src/provider/museModelCatalog.ts +++ b/packages/provider-muse/src/server/modelCatalog.ts @@ -1,9 +1,10 @@ import type { SendUserTurnOptions } from "@muse-code/sdk"; -import type { ModelCapabilities, MuseSettings, ServerProviderModel } from "@t3tools/contracts"; +import { ModelCapabilities, ServerProviderModel } from "@t3tools/contracts"; +import { MuseSettings } from "../settings.ts"; import { createModelCapabilities, getProviderOptionDescriptors } from "@t3tools/shared/model"; import * as Effect from "effect/Effect"; import * as Schema from "effect/Schema"; -import { createMuseSdkHost, createMuseSdkHostEffect, type MuseSdkHost } from "./museSdk.ts"; +import { createMuseSdkHost, createMuseSdkHostEffect, type MuseSdkHost } from "./sdk.ts"; type MuseReasoningEffort = NonNullable["reasoningEffort"]>; diff --git a/apps/server/src/provider/museProtocol.test.ts b/packages/provider-muse/src/server/protocol.test.ts similarity index 91% rename from apps/server/src/provider/museProtocol.test.ts rename to packages/provider-muse/src/server/protocol.test.ts index 479da19a4fdb..d583d84de495 100644 --- a/apps/server/src/provider/museProtocol.test.ts +++ b/packages/provider-muse/src/server/protocol.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vite-plus/test"; -import { museApprovalChoices } from "./museProtocol.ts"; +import { museApprovalChoices } from "./protocol.ts"; // Muse 1.4.3 shell approvals offer only `abort`; the muse_permission replay covers that case. describe("Muse approval choices", () => { diff --git a/apps/server/src/provider/museProtocol.ts b/packages/provider-muse/src/server/protocol.ts similarity index 100% rename from apps/server/src/provider/museProtocol.ts rename to packages/provider-muse/src/server/protocol.ts diff --git a/apps/server/src/provider/museSdk.test.ts b/packages/provider-muse/src/server/sdk.test.ts similarity index 99% rename from apps/server/src/provider/museSdk.test.ts rename to packages/provider-muse/src/server/sdk.test.ts index ff8de0c1abf9..8c92498ca517 100644 --- a/apps/server/src/provider/museSdk.test.ts +++ b/packages/provider-muse/src/server/sdk.test.ts @@ -9,7 +9,7 @@ import { makeMuseEnvironment, museApprovalMode, type MuseSdkHost, -} from "./museSdk.ts"; +} from "./sdk.ts"; vi.mock("@muse-code/sdk", () => ({ spawnMspConnection: vi.fn() })); diff --git a/apps/server/src/provider/museSdk.ts b/packages/provider-muse/src/server/sdk.ts similarity index 100% rename from apps/server/src/provider/museSdk.ts rename to packages/provider-muse/src/server/sdk.ts diff --git a/apps/server/src/provider/MuseProvider.test.ts b/packages/provider-muse/src/server/status.test.ts similarity index 96% rename from apps/server/src/provider/MuseProvider.test.ts rename to packages/provider-muse/src/server/status.test.ts index be7cafdbd6a1..8f7031de0ad7 100644 --- a/apps/server/src/provider/MuseProvider.test.ts +++ b/packages/provider-muse/src/server/status.test.ts @@ -1,15 +1,15 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { expect, it } from "@effect/vitest"; -import { MuseSettings } from "@t3tools/contracts"; +import { MuseSettings } from "../settings.ts"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Schema from "effect/Schema"; import { vi } from "vite-plus/test"; -import { writeFakeCli } from "../testUtils/fakeCli.ts"; -import type { MuseSdkHost } from "./museSdk.ts"; +import { writeFakeCli } from "@t3tools/provider-testing/fakeCli"; +import type { MuseSdkHost } from "./sdk.ts"; import { COMPACT_SLASH_COMMAND } from "@t3tools/provider-core/server/snapshotProbe"; -import { checkMuseProviderStatus } from "./MuseProvider.ts"; +import { checkMuseProviderStatus } from "./status.ts"; const settings = Schema.decodeSync(MuseSettings); const makeHost = (catalog: Record) => { diff --git a/apps/server/src/provider/MuseProvider.ts b/packages/provider-muse/src/server/status.ts similarity index 94% rename from apps/server/src/provider/MuseProvider.ts rename to packages/provider-muse/src/server/status.ts index 3d44431d0564..2cb4e5360ff2 100644 --- a/apps/server/src/provider/MuseProvider.ts +++ b/packages/provider-muse/src/server/status.ts @@ -1,4 +1,5 @@ -import type { MuseSettings, ServerProviderModel } from "@t3tools/contracts"; +import { ServerProviderModel } from "@t3tools/contracts"; +import { MuseSettings } from "../settings.ts"; import { resolveSpawnCommand } from "@t3tools/shared/shell"; import * as DateTime from "effect/DateTime"; import * as Effect from "effect/Effect"; @@ -6,9 +7,9 @@ import * as Option from "effect/Option"; import * as Result from "effect/Result"; import { ChildProcess } from "effect/process"; -import { createMuseSdkHost, makeMuseEnvironment } from "./museSdk.ts"; -import { parseMuseVersion } from "./museMaintenance.ts"; -import { discoverMuseModels, museModelCapabilities } from "./museModelCatalog.ts"; +import { createMuseSdkHost, makeMuseEnvironment } from "./sdk.ts"; +import { parseMuseVersion } from "./maintenance.ts"; +import { discoverMuseModels, museModelCapabilities } from "./modelCatalog.ts"; import { buildServerProvider, COMPACT_SLASH_COMMAND, diff --git a/apps/server/src/textGeneration/MuseTextGeneration.test.ts b/packages/provider-muse/src/server/textGeneration.test.ts similarity index 98% rename from apps/server/src/textGeneration/MuseTextGeneration.test.ts rename to packages/provider-muse/src/server/textGeneration.test.ts index 5f3be0548705..41e84dae23ec 100644 --- a/apps/server/src/textGeneration/MuseTextGeneration.test.ts +++ b/packages/provider-muse/src/server/textGeneration.test.ts @@ -3,19 +3,19 @@ import { expect, it } from "@effect/vitest"; import type { NotificationHandler } from "@muse-code/sdk"; import { MUSE_DEFAULT_MODEL, - MuseSettings, ProviderInstanceId, type ServerProviderModel, } from "@t3tools/contracts"; +import { MuseSettings } from "../settings.ts"; import { createModelSelection } from "@t3tools/shared/model"; import * as Effect from "effect/Effect"; import * as Fiber from "effect/Fiber"; import * as Schema from "effect/Schema"; import { vi } from "vite-plus/test"; -import type { MuseSdkHost } from "../provider/museSdk.ts"; -import { museModelCapabilities } from "../provider/museModelCatalog.ts"; -import { makeMuseTextGeneration } from "./MuseTextGeneration.ts"; +import type { MuseSdkHost } from "./sdk.ts"; +import { museModelCapabilities } from "./modelCatalog.ts"; +import { makeMuseTextGeneration } from "./textGeneration.ts"; const settings = Schema.decodeSync(MuseSettings)({ enabled: true }); const modelSelection = createModelSelection( diff --git a/apps/server/src/textGeneration/MuseTextGeneration.ts b/packages/provider-muse/src/server/textGeneration.ts similarity index 83% rename from apps/server/src/textGeneration/MuseTextGeneration.ts rename to packages/provider-muse/src/server/textGeneration.ts index e0d4f3f3afa5..8a13cf30260c 100644 --- a/apps/server/src/textGeneration/MuseTextGeneration.ts +++ b/packages/provider-muse/src/server/textGeneration.ts @@ -2,10 +2,10 @@ import { DEFAULT_TEXT_GENERATION_REASONING_EFFORT, MUSE_DEFAULT_MODEL, type ModelSelection, - type MuseSettings, type ServerProviderModel, TextGenerationError, } from "@t3tools/contracts"; +import type { MuseSettings } from "../settings.ts"; import { sanitizeBranchFragment, sanitizeFeatureBranchName } from "@t3tools/shared/git"; import { getModelSelectionStringOptionValue } from "@t3tools/shared/model"; import { extractJsonObject } from "@t3tools/shared/schemaJson"; @@ -14,13 +14,9 @@ import * as FileSystem from "effect/FileSystem"; import * as Option from "effect/Option"; import * as Schema from "effect/Schema"; -import { - createMuseSdkHost, - createMuseSdkHostEffect, - type MuseSdkHost, -} from "../provider/museSdk.ts"; -import { museModelCapabilities, resolveMuseReasoningEffort } from "../provider/museModelCatalog.ts"; -import type * as TextGeneration from "./TextGeneration.ts"; +import { createMuseSdkHost, createMuseSdkHostEffect, type MuseSdkHost } from "./sdk.ts"; +import { museModelCapabilities, resolveMuseReasoningEffort } from "./modelCatalog.ts"; +import type { ProviderTextGeneration } from "@t3tools/provider-core/server/textGeneration"; import { buildBranchNamePrompt, buildCommitMessagePrompt, @@ -73,7 +69,7 @@ const decodeUserInput = Schema.decodeUnknownSync(UserInputRequested); const encodeJson = Schema.encodeEffect(Schema.fromJsonString(Schema.Unknown)); const decodeJson = Schema.decodeEffect(Schema.fromJsonString(Schema.Unknown)); -type Operation = keyof TextGeneration.TextGeneration["Service"]; +type Operation = keyof ProviderTextGeneration; async function generateMuseText( host: MuseSdkHost, @@ -299,52 +295,56 @@ export const makeMuseTextGeneration = Effect.fn("makeMuseTextGeneration")(functi ); }); - const generateCommitMessage: TextGeneration.TextGeneration["Service"]["generateCommitMessage"] = - Effect.fn("MuseTextGeneration.generateCommitMessage")(function* (input) { - const generated = yield* runMuseJson({ - operation: "generateCommitMessage", - ...buildCommitMessagePrompt({ ...input, includeBranch: input.includeBranch === true }), - modelSelection: input.modelSelection, - }); - return { - subject: sanitizeCommitSubject(generated.subject), - body: generated.body.trim(), - ...("branch" in generated && typeof generated.branch === "string" - ? { branch: sanitizeFeatureBranchName(generated.branch) } - : {}), - }; + const generateCommitMessage: ProviderTextGeneration["generateCommitMessage"] = Effect.fn( + "MuseTextGeneration.generateCommitMessage", + )(function* (input) { + const generated = yield* runMuseJson({ + operation: "generateCommitMessage", + ...buildCommitMessagePrompt({ ...input, includeBranch: input.includeBranch === true }), + modelSelection: input.modelSelection, }); - const generatePrContent: TextGeneration.TextGeneration["Service"]["generatePrContent"] = - Effect.fn("MuseTextGeneration.generatePrContent")(function* (input) { - const generated = yield* runMuseJson({ - operation: "generatePrContent", - ...buildPrContentPrompt(input), - modelSelection: input.modelSelection, - }); - return { title: sanitizePrTitle(generated.title), body: generated.body.trim() }; + return { + subject: sanitizeCommitSubject(generated.subject), + body: generated.body.trim(), + ...("branch" in generated && typeof generated.branch === "string" + ? { branch: sanitizeFeatureBranchName(generated.branch) } + : {}), + }; + }); + const generatePrContent: ProviderTextGeneration["generatePrContent"] = Effect.fn( + "MuseTextGeneration.generatePrContent", + )(function* (input) { + const generated = yield* runMuseJson({ + operation: "generatePrContent", + ...buildPrContentPrompt(input), + modelSelection: input.modelSelection, }); - const generateBranchName: TextGeneration.TextGeneration["Service"]["generateBranchName"] = - Effect.fn("MuseTextGeneration.generateBranchName")(function* (input) { - const generated = yield* runMuseJson({ - operation: "generateBranchName", - ...buildBranchNamePrompt(input), - modelSelection: input.modelSelection, - }); - return { branch: sanitizeBranchFragment(generated.branch) }; + return { title: sanitizePrTitle(generated.title), body: generated.body.trim() }; + }); + const generateBranchName: ProviderTextGeneration["generateBranchName"] = Effect.fn( + "MuseTextGeneration.generateBranchName", + )(function* (input) { + const generated = yield* runMuseJson({ + operation: "generateBranchName", + ...buildBranchNamePrompt(input), + modelSelection: input.modelSelection, }); - const generateThreadTitle: TextGeneration.TextGeneration["Service"]["generateThreadTitle"] = - Effect.fn("MuseTextGeneration.generateThreadTitle")(function* (input) { - const generated = yield* runMuseJson({ - operation: "generateThreadTitle", - ...buildThreadTitlePrompt(input), - modelSelection: input.modelSelection, - }); - return { title: sanitizeThreadTitle(generated.title) }; + return { branch: sanitizeBranchFragment(generated.branch) }; + }); + const generateThreadTitle: ProviderTextGeneration["generateThreadTitle"] = Effect.fn( + "MuseTextGeneration.generateThreadTitle", + )(function* (input) { + const generated = yield* runMuseJson({ + operation: "generateThreadTitle", + ...buildThreadTitlePrompt(input), + modelSelection: input.modelSelection, }); + return { title: sanitizeThreadTitle(generated.title) }; + }); return { generateCommitMessage, generatePrContent, generateBranchName, generateThreadTitle, - } satisfies TextGeneration.TextGeneration["Service"]; + } satisfies ProviderTextGeneration; }); diff --git a/packages/provider-muse/src/settings.ts b/packages/provider-muse/src/settings.ts new file mode 100644 index 000000000000..85a781dc6608 --- /dev/null +++ b/packages/provider-muse/src/settings.ts @@ -0,0 +1,36 @@ +/** + * Muse Code instance settings. Shared by the server driver and the client + * settings form, so it holds only browser-safe schema code. + * + * @module provider-muse/settings + */ +import { + CustomModelSetting, + makeBinaryPathSetting, + makeProviderSettingsSchema, +} from "@t3tools/contracts"; +import * as Effect from "effect/Effect"; +import * as Schema from "effect/Schema"; + +export const MuseSettings = makeProviderSettingsSchema( + { + enabled: Schema.Boolean.pipe( + Schema.withDecodingDefault(Effect.succeed(false)), + Schema.annotateKey({ providerSettingsForm: { hidden: true } }), + ), + binaryPath: makeBinaryPathSetting("muse").pipe( + Schema.annotateKey({ + title: "Binary path", + description: + "Path to the Muse Code binary on this environment. Sign in with muse login on that host.", + providerSettingsForm: { placeholder: "muse", clearWhenEmpty: "omit" }, + }), + ), + customModels: Schema.Array(CustomModelSetting).pipe( + Schema.withDecodingDefault(Effect.succeed([])), + Schema.annotateKey({ providerSettingsForm: { hidden: true } }), + ), + }, + { order: ["binaryPath"] }, +); +export type MuseSettings = typeof MuseSettings.Type; diff --git a/packages/provider-muse/src/testing.ts b/packages/provider-muse/src/testing.ts new file mode 100644 index 000000000000..810388c5afe9 --- /dev/null +++ b/packages/provider-muse/src/testing.ts @@ -0,0 +1,14 @@ +/** + * Muse Code internals for replay tests and the fixture recorder: the MSP + * launch arguments, initialize parameters, environment, and version parser. + * + * @module provider-muse/testing + */ +export { + makeMuseEnvironment, + museInitializeParams, + museServeArgs, + type MuseSdkHost, + type MuseSdkHostOptions, +} from "./server/sdk.ts"; +export { parseMuseVersion } from "./server/maintenance.ts"; diff --git a/packages/provider-muse/tsconfig.json b/packages/provider-muse/tsconfig.json new file mode 100644 index 000000000000..374bac55202d --- /dev/null +++ b/packages/provider-muse/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "types": ["node"] + }, + "include": ["src"] +} diff --git a/packages/provider-testing/package.json b/packages/provider-testing/package.json index 35e3941ba87d..39c6c1f644aa 100644 --- a/packages/provider-testing/package.json +++ b/packages/provider-testing/package.json @@ -18,6 +18,10 @@ "./host": { "types": "./src/host.ts", "import": "./src/host.ts" + }, + "./fakeCli": { + "types": "./src/fakeCli.ts", + "import": "./src/fakeCli.ts" } }, "scripts": { @@ -28,6 +32,7 @@ "@effect/platform-node": "catalog:", "@t3tools/contracts": "workspace:*", "@t3tools/provider-core": "workspace:*", + "@t3tools/shared": "workspace:*", "effect": "catalog:" }, "devDependencies": { diff --git a/apps/server/src/testUtils/fakeCli.ts b/packages/provider-testing/src/fakeCli.ts similarity index 100% rename from apps/server/src/testUtils/fakeCli.ts rename to packages/provider-testing/src/fakeCli.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b948baba7c65..9316e7f35f53 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -356,6 +356,9 @@ importers: '@t3tools/provider-core': specifier: workspace:* version: link:../../packages/provider-core + '@t3tools/provider-muse': + specifier: workspace:* + version: link:../../packages/provider-muse '@t3tools/provider-pi': specifier: workspace:* version: link:../../packages/provider-pi @@ -603,6 +606,9 @@ importers: '@t3tools/provider-core': specifier: workspace:* version: link:../../packages/provider-core + '@t3tools/provider-muse': + specifier: workspace:* + version: link:../../packages/provider-muse '@t3tools/provider-pi': specifier: workspace:* version: link:../../packages/provider-pi @@ -733,6 +739,9 @@ importers: '@t3tools/provider-core': specifier: workspace:* version: link:../../packages/provider-core + '@t3tools/provider-muse': + specifier: workspace:* + version: link:../../packages/provider-muse '@t3tools/provider-pi': specifier: workspace:* version: link:../../packages/provider-pi @@ -1099,6 +1108,40 @@ importers: specifier: 1.0.0 version: 1.0.0(@types/node@24.12.4)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + packages/provider-muse: + dependencies: + '@muse-code/sdk': + specifier: 1.4.2 + version: 1.4.2(@types/node@24.12.4) + '@t3tools/contracts': + specifier: workspace:* + version: link:../contracts + '@t3tools/provider-core': + specifier: workspace:* + version: link:../provider-core + '@t3tools/shared': + specifier: workspace:* + version: link:../shared + effect: + specifier: 4.0.1 + version: 4.0.1(patch_hash=a33cba07c41f32374c2aaa86ea4a84d3ab9a872a78c94b65e929b19f8361856f) + devDependencies: + '@effect/platform-node': + specifier: 4.0.1 + version: 4.0.1(bufferutil@4.1.0)(effect@4.0.1(patch_hash=a33cba07c41f32374c2aaa86ea4a84d3ab9a872a78c94b65e929b19f8361856f))(redis@6.2.1)(utf-8-validate@6.0.6) + '@effect/vitest': + specifier: 4.0.1 + version: 4.0.1(patch_hash=359f6fb2f7b3ec145bb72208edb9034f02489791aa2491a55cdbd69bd56ee0d2)(@types/node@24.12.4)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.1(patch_hash=a33cba07c41f32374c2aaa86ea4a84d3ab9a872a78c94b65e929b19f8361856f))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + '@t3tools/provider-testing': + specifier: workspace:* + version: link:../provider-testing + '@types/node': + specifier: 24.12.4 + version: 24.12.4 + vite-plus: + specifier: 1.0.0 + version: 1.0.0(@types/node@24.12.4)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + packages/provider-pi: dependencies: '@t3tools/contracts': @@ -1141,6 +1184,9 @@ importers: '@t3tools/provider-core': specifier: workspace:* version: link:../provider-core + '@t3tools/shared': + specifier: workspace:* + version: link:../shared effect: specifier: 4.0.1 version: 4.0.1(patch_hash=a33cba07c41f32374c2aaa86ea4a84d3ab9a872a78c94b65e929b19f8361856f) diff --git a/scripts/release-smoke.ts b/scripts/release-smoke.ts index 8fb9ab883995..2f39d7a61e93 100644 --- a/scripts/release-smoke.ts +++ b/scripts/release-smoke.ts @@ -31,6 +31,7 @@ const workspaceFiles = [ "packages/provider-core/package.json", "packages/provider-testing/package.json", "packages/provider-pi/package.json", + "packages/provider-muse/package.json", "packages/effect-acp/package.json", "packages/effect-codex-app-server/package.json", "scripts/package.json",