1import * as Effect from "effect/Effect";
2import * as Schema from "effect/Schema";
3import * as SchemaTransformation from "effect/SchemaTransformation";
4import { TrimmedNonEmptyString } from "./baseSchemas.ts";
5import { ProviderDriverKind } from "./providerInstance.ts";
6
7export const ProviderOptionDescriptorType = Schema.Literals(["select", "boolean"]);
8export type ProviderOptionDescriptorType = typeof ProviderOptionDescriptorType.Type;
9
10export const ProviderOptionChoice = Schema.Struct({
11 id: TrimmedNonEmptyString,
12 label: TrimmedNonEmptyString,
13 description: Schema.optional(TrimmedNonEmptyString),
14 isDefault: Schema.optional(Schema.Boolean),
15});
16export type ProviderOptionChoice = typeof ProviderOptionChoice.Type;
17
18const ProviderOptionDescriptorBase = {
19 id: TrimmedNonEmptyString,
20 label: TrimmedNonEmptyString,
21 description: Schema.optional(TrimmedNonEmptyString),
22} as const;
23
24export const SelectProviderOptionDescriptor = Schema.Struct({
25 ...ProviderOptionDescriptorBase,
26 type: Schema.Literal("select"),
27 options: Schema.Array(ProviderOptionChoice),
28 currentValue: Schema.optional(TrimmedNonEmptyString),
29 promptInjectedValues: Schema.optional(Schema.Array(TrimmedNonEmptyString)),
30 /** Marks the reasoning control when its id is an agent's own, such as an ACP `thought_level`. */
31 role: Schema.optional(Schema.Literal("reasoning")),
32});
33export type SelectProviderOptionDescriptor = typeof SelectProviderOptionDescriptor.Type;
34
35export const BooleanProviderOptionDescriptor = Schema.Struct({
36 ...ProviderOptionDescriptorBase,
37 type: Schema.Literal("boolean"),
38 currentValue: Schema.optional(Schema.Boolean),
39});
40export type BooleanProviderOptionDescriptor = typeof BooleanProviderOptionDescriptor.Type;
41
42export const ProviderOptionDescriptor = Schema.Union([
43 SelectProviderOptionDescriptor,
44 BooleanProviderOptionDescriptor,
45]);
46export type ProviderOptionDescriptor = typeof ProviderOptionDescriptor.Type;
47
48export const ProviderOptionSelectionValue = Schema.Union([TrimmedNonEmptyString, Schema.Boolean]);
49export type ProviderOptionSelectionValue = typeof ProviderOptionSelectionValue.Type;
50
51export const ProviderOptionSelection = Schema.Struct({
52 id: TrimmedNonEmptyString,
53 value: ProviderOptionSelectionValue,
54});
55export type ProviderOptionSelection = typeof ProviderOptionSelection.Type;
56
57const LegacyProviderOptionSelectionsObject = Schema.Record(Schema.String, Schema.Unknown);
58
59const ProviderOptionSelectionsFromLegacyObject = LegacyProviderOptionSelectionsObject.pipe(
60 Schema.decodeTo(
61 Schema.Array(ProviderOptionSelection),
62 SchemaTransformation.transformEffect({
63 decode: (record) => Effect.succeed(coerceLegacyOptionsObjectToArray(record)),
64 encode: (selections) => Effect.succeed(canonicalSelectionsToLegacyObject(selections)),
65 }),
66 ),
67);
68
69export const ProviderOptionSelections = Schema.Union([
70 Schema.Array(ProviderOptionSelection),
71 ProviderOptionSelectionsFromLegacyObject,
72]);
73export type ProviderOptionSelections = typeof ProviderOptionSelections.Type;
74
75function coerceLegacyOptionsObjectToArray(
76 record: Record<string, unknown>,
77): ReadonlyArray<ProviderOptionSelection> {
78 const entries: Array<ProviderOptionSelection> = [];
79 for (const [rawKey, rawValue] of Object.entries(record)) {
80 const id = typeof rawKey === "string" ? rawKey.trim() : "";
81 if (id.length === 0) continue;
82 if (typeof rawValue === "string") {
83 const trimmed = rawValue.trim();
84 if (trimmed.length > 0) entries.push({ id, value: trimmed });
85 } else if (typeof rawValue === "boolean") {
86 entries.push({ id, value: rawValue });
87 }
88 }
89 return entries;
90}
91
92function canonicalSelectionsToLegacyObject(
93 selections: ReadonlyArray<ProviderOptionSelection>,
94): Record<string, string | boolean> {
95 const out: Record<string, string | boolean> = {};
96 for (const { id, value } of selections) {
97 out[id] = value;
98 }
99 return out;
100}
101
102export const ModelCapabilities = Schema.Struct({
103 optionDescriptors: Schema.optional(Schema.Array(ProviderOptionDescriptor)),
104});
105export type ModelCapabilities = typeof ModelCapabilities.Type;
106
107export const CustomModelEntry = Schema.Struct({
108 slug: TrimmedNonEmptyString,
109 name: Schema.optional(TrimmedNonEmptyString),
110 capabilities: Schema.optional(ModelCapabilities),
111});
112export type CustomModelEntry = typeof CustomModelEntry.Type;
113
114export const CustomModelSetting = Schema.Union([Schema.String, CustomModelEntry]);
115export type CustomModelSetting = typeof CustomModelSetting.Type;
116
117const CODEX_DRIVER_KIND = ProviderDriverKind.make("codex");
118const CLAUDE_DRIVER_KIND = ProviderDriverKind.make("claudeAgent");
119const CURSOR_DRIVER_KIND = ProviderDriverKind.make("cursor");
120const GROK_DRIVER_KIND = ProviderDriverKind.make("grok");
121const FX_DRIVER_KIND = ProviderDriverKind.make("fx");
122const KIRO_DRIVER_KIND = ProviderDriverKind.make("kiro");
123const MUSE_DRIVER_KIND = ProviderDriverKind.make("muse");
124const PI_DRIVER_KIND = ProviderDriverKind.make("pi");
125const ACP_REGISTRY_DRIVER_KIND = ProviderDriverKind.make("acpRegistry");
126const OPENCODE_DRIVER_KIND = ProviderDriverKind.make("opencode");
127
128export const DEFAULT_MODEL = "gpt-6-astra";
129
130/** First model present in model/list wins; otherwise use Codex's isDefault. */
131export const PREFERRED_DEFAULT_CODEX_MODELS: ReadonlyArray<string> = [
132 DEFAULT_MODEL,
133 "gpt-5.6-sol",
134 "gpt-5.6-terra",
135];
136export const DEFAULT_TEXT_GENERATION_MODEL = "gpt-6-luna";
137/** Keep the official Antigravity session's current model. Never send this ID to ACP. */
138export const ANTIGRAVITY_DEFAULT_MODEL = "antigravity-default";
139export const DEFAULT_TEXT_GENERATION_REASONING_EFFORT = "low";
140
141/** Let Muse pick the account's default model. Never send this ID to Muse. */
142export const MUSE_DEFAULT_MODEL = "default";
143
144export const DEFAULT_MODEL_BY_PROVIDER: Partial<Record<ProviderDriverKind, string>> = {
145 [CODEX_DRIVER_KIND]: DEFAULT_MODEL,
146 [CLAUDE_DRIVER_KIND]: "claude-opus-5-5",
147 [CURSOR_DRIVER_KIND]: "auto",
148 // Product slug meaning the current session model, never an ACP model id.
149 [GROK_DRIVER_KIND]: "grok-build",
150 [FX_DRIVER_KIND]: "default",
151 // Keeps the Kiro session's current model; real models come from its `model` config option.
152 [KIRO_DRIVER_KIND]: "default",
153 [MUSE_DRIVER_KIND]: MUSE_DEFAULT_MODEL,
154 [ACP_REGISTRY_DRIVER_KIND]: "default",
155 // "default" defers to the user's own Pi settings.json model selection.
156 [PI_DRIVER_KIND]: "default",
157 [OPENCODE_DRIVER_KIND]: "openai/gpt-5",
158 [ProviderDriverKind.make("antigravity")]: ANTIGRAVITY_DEFAULT_MODEL,
159};
160
161export const DEFAULT_TEXT_GENERATION_MODEL_BY_PROVIDER: Partial<
162 Record<ProviderDriverKind, string>
163> = {
164 [CODEX_DRIVER_KIND]: DEFAULT_TEXT_GENERATION_MODEL,
165 [ProviderDriverKind.make("antigravity")]: ANTIGRAVITY_DEFAULT_MODEL,
166 [CLAUDE_DRIVER_KIND]: "claude-haiku-5-5",
167 [CURSOR_DRIVER_KIND]: "composer-2",
168 [OPENCODE_DRIVER_KIND]: "opencode/big-pickle",
169};
170
171export const MODEL_SLUG_ALIASES_BY_PROVIDER: Partial<
172 Record<ProviderDriverKind, Record<string, string>>
173> = {
174 [CODEX_DRIVER_KIND]: {
175 "gpt-5-codex": "gpt-5.4",
176 "5.4": "gpt-5.4",
177 "5.3": "gpt-5.3-codex",
178 "gpt-5.3": "gpt-5.3-codex",
179 "5.3-spark": "gpt-5.3-codex-spark",
180 "gpt-5.3-spark": "gpt-5.3-codex-spark",
181 },
182 [CLAUDE_DRIVER_KIND]: {},
183 [CURSOR_DRIVER_KIND]: {
184 composer: "composer-2",
185 "composer-1.5": "composer-1.5",
186 "composer-1": "composer-1.5",
187 "opus-4.6-thinking": "claude-opus-4-6",
188 "opus-4.6": "claude-opus-4-6",
189 "sonnet-4.6-thinking": "claude-sonnet-4-6",
190 "sonnet-4.6": "claude-sonnet-4-6",
191 "opus-4.5-thinking": "claude-opus-4-5",
192 "opus-4.5": "claude-opus-4-5",
193 },
194 [OPENCODE_DRIVER_KIND]: {},
195};
196
197export const PROVIDER_DISPLAY_NAMES: Partial<Record<ProviderDriverKind, string>> = {
198 [ProviderDriverKind.make("antigravity")]: "Antigravity",
199 [CODEX_DRIVER_KIND]: "Codex",
200 [CLAUDE_DRIVER_KIND]: "Claude",
201 [CURSOR_DRIVER_KIND]: "Cursor",
202 [GROK_DRIVER_KIND]: "Grok",
203 [FX_DRIVER_KIND]: "fx",
204 [KIRO_DRIVER_KIND]: "Kiro",
205 [MUSE_DRIVER_KIND]: "Muse Code",
206 [ACP_REGISTRY_DRIVER_KIND]: "ACP Registry",
207 [PI_DRIVER_KIND]: "Pi",
208 [OPENCODE_DRIVER_KIND]: "OpenCode",
209};