| 1 | import type { SendUserTurnOptions } from "@muse-code/sdk"; |
| 2 | import { ModelCapabilities, ServerProviderModel } from "@t3tools/contracts"; |
| 3 | import { MuseSettings } from "../settings.ts"; |
| 4 | import { createModelCapabilities, getProviderOptionDescriptors } from "@t3tools/shared/model"; |
| 5 | import * as Effect from "effect/Effect"; |
| 6 | import * as Schema from "effect/Schema"; |
| 7 | import { createMuseSdkHost, createMuseSdkHostEffect, type MuseSdkHost } from "./sdk.ts"; |
| 8 | |
| 9 | type MuseReasoningEffort = NonNullable<SendUserTurnOptions<never>["reasoningEffort"]>; |
| 10 | |
| 11 | const REASONING_EFFORT_LABELS = { |
| 12 | none: "None", |
| 13 | minimal: "Minimal", |
| 14 | low: "Low", |
| 15 | medium: "Medium", |
| 16 | high: "High", |
| 17 | xhigh: "Extra High", |
| 18 | max: "Max", |
| 19 | ultra: "Ultra", |
| 20 | } satisfies Record<MuseReasoningEffort, string>; |
| 21 | |
| 22 | const isMuseReasoningEffort = (tier: string): tier is MuseReasoningEffort => |
| 23 | Object.hasOwn(REASONING_EFFORT_LABELS, tier); |
| 24 | |
| 25 | /** Used when Muse does not report a model's efforts (custom slugs, older hosts, "unknown"). */ |
| 26 | const FALLBACK_EFFORTS = ["low", "medium", "high", "xhigh", "max"]; |
| 27 | /** Muse's own CLI default. */ |
| 28 | const FALLBACK_DEFAULT_EFFORT = "high"; |
| 29 | |
| 30 | const ModelCatalogEntry = Schema.Struct({ |
| 31 | modelId: Schema.NonEmptyString, |
| 32 | displayLabel: Schema.String, |
| 33 | providerId: Schema.String, |
| 34 | isDefault: Schema.Boolean, |
| 35 | variants: Schema.optional(Schema.Union([Schema.Array(Schema.String), Schema.Literal("unknown")])), |
| 36 | defaultReasoningEffort: Schema.optional(Schema.String), |
| 37 | reasoningEffortVariants: Schema.optional( |
| 38 | Schema.Array( |
| 39 | Schema.Struct({ tier: Schema.String, description: Schema.optional(Schema.String) }), |
| 40 | ), |
| 41 | ), |
| 42 | }); |
| 43 | const decodeModelList = Schema.decodeUnknownEffect( |
| 44 | Schema.Struct({ providerId: Schema.String, models: Schema.Array(ModelCatalogEntry) }), |
| 45 | ); |
| 46 | |
| 47 | /** Builds the reasoning picker from a `model/list` row, or the fallback when called without one. */ |
| 48 | export function museModelCapabilities( |
| 49 | model?: Pick< |
| 50 | typeof ModelCatalogEntry.Type, |
| 51 | "variants" | "defaultReasoningEffort" | "reasoningEffortVariants" |
| 52 | >, |
| 53 | ): ModelCapabilities { |
| 54 | const reported = model?.variants !== undefined && model.variants !== "unknown"; |
| 55 | const efforts = (reported ? model.variants : FALLBACK_EFFORTS).filter(isMuseReasoningEffort); |
| 56 | const preferredDefault = reported ? model?.defaultReasoningEffort : FALLBACK_DEFAULT_EFFORT; |
| 57 | const defaultValue = efforts.find((tier) => tier === preferredDefault) ?? efforts[0]; |
| 58 | if (!defaultValue) return createModelCapabilities({ optionDescriptors: [] }); |
| 59 | return createModelCapabilities({ |
| 60 | optionDescriptors: [ |
| 61 | { |
| 62 | id: "reasoningEffort", |
| 63 | label: "Reasoning", |
| 64 | type: "select", |
| 65 | currentValue: defaultValue, |
| 66 | options: efforts.map((id) => { |
| 67 | const description = model?.reasoningEffortVariants?.find( |
| 68 | (variant) => variant.tier === id, |
| 69 | )?.description; |
| 70 | return { |
| 71 | id, |
| 72 | label: REASONING_EFFORT_LABELS[id], |
| 73 | ...(id === defaultValue ? { isDefault: true } : {}), |
| 74 | ...(description ? { description } : {}), |
| 75 | }; |
| 76 | }), |
| 77 | }, |
| 78 | ], |
| 79 | }); |
| 80 | } |
| 81 | |
| 82 | /** Apply the advertised model choices to saved, remembered and implicit efforts at dispatch. */ |
| 83 | export function resolveMuseReasoningEffort( |
| 84 | capabilities: ModelCapabilities | null | undefined, |
| 85 | effort: string | undefined, |
| 86 | ): string | undefined { |
| 87 | if (!capabilities) return effort; |
| 88 | const descriptor = getProviderOptionDescriptors({ |
| 89 | caps: capabilities, |
| 90 | selections: effort ? [{ id: "reasoningEffort", value: effort }] : undefined, |
| 91 | }).find((descriptor) => descriptor.id === "reasoningEffort"); |
| 92 | if (descriptor?.type !== "select") return undefined; |
| 93 | return descriptor.options.find((option) => option.id === descriptor.currentValue)?.id; |
| 94 | } |
| 95 | |
| 96 | class MuseCatalogError extends Schema.TaggedError<MuseCatalogError>()("MuseCatalogError", { |
| 97 | detail: Schema.String, |
| 98 | }) {} |
| 99 | |
| 100 | export const discoverMuseModels = Effect.fn("discoverMuseModels")(function* ( |
| 101 | settings: MuseSettings, |
| 102 | environment: NodeJS.ProcessEnv | undefined, |
| 103 | cwd?: string, |
| 104 | createHost: typeof createMuseSdkHost = createMuseSdkHost, |
| 105 | ) { |
| 106 | const host = yield* Effect.acquireRelease( |
| 107 | createMuseSdkHostEffect( |
| 108 | { |
| 109 | binaryPath: settings.binaryPath, |
| 110 | ...(environment ? { environment } : {}), |
| 111 | ...(cwd ? { cwd } : {}), |
| 112 | readOnly: true, |
| 113 | startupTimeoutMs: 8_000, |
| 114 | }, |
| 115 | createHost, |
| 116 | ), |
| 117 | (host: MuseSdkHost) => Effect.promise(() => host.close()), |
| 118 | { interruptible: true }, |
| 119 | ); |
| 120 | const result = yield* Effect.tryPromise(() => host.connection.request("model/list", {})); |
| 121 | const catalog = yield* decodeModelList(result); |
| 122 | if (catalog.providerId !== "meta") { |
| 123 | return yield* new MuseCatalogError({ detail: "Muse returned a catalog for another provider." }); |
| 124 | } |
| 125 | const seen = new Set<string>(); |
| 126 | return catalog.models.flatMap((model): ServerProviderModel[] => { |
| 127 | if (model.providerId !== "meta" || seen.has(model.modelId)) return []; |
| 128 | seen.add(model.modelId); |
| 129 | return [ |
| 130 | { |
| 131 | slug: model.modelId, |
| 132 | name: model.displayLabel.trim() || model.modelId, |
| 133 | isCustom: false, |
| 134 | isDefault: model.isDefault, |
| 135 | capabilities: museModelCapabilities(model), |
| 136 | }, |
| 137 | ]; |
| 138 | }); |
| 139 | }); |