Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion apps/server/src/codexAppServerManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -819,7 +819,7 @@ describe("resolveCodexModelForAccount", () => {
planType: "plus",
sparkEnabled: false,
}),
).toBe("gpt-5.5");
).toBe("gpt-5.6-sol");
});

it("keeps spark for supported plans", () => {
Expand Down Expand Up @@ -1394,6 +1394,7 @@ describe("CodexAppServerManager discovery", () => {
name: "GPT-5.6 Sol",
supportedReasoningEfforts: ["low", "medium", "high", "xhigh", "max", "ultra"],
defaultReasoningEffort: "low",
isDefault: true,
additionalSpeedTiers: ["fast"],
},
},
Expand All @@ -1404,6 +1405,7 @@ describe("CodexAppServerManager discovery", () => {
name: "GPT-5.6 Sol",
supported_reasoning_efforts: ["low", "medium", "high", "xhigh", "max", "ultra"],
default_reasoning_effort: "low",
is_default: true,
additional_speed_tiers: ["fast"],
},
},
Expand Down Expand Up @@ -1459,6 +1461,7 @@ describe("CodexAppServerManager discovery", () => {
{
slug: "gpt-5.6-sol",
name: "GPT-5.6 Sol",
isDefault: true,
supportedReasoningEfforts: [
{ value: "low" },
{ value: "medium" },
Expand Down
7 changes: 6 additions & 1 deletion apps/server/src/codexAppServerManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import readline from "node:readline";

import {
ApprovalRequestId,
DEFAULT_MODEL_BY_PROVIDER,
EventId,
type ProviderComposerCapabilities,
ProviderItemId,
Expand Down Expand Up @@ -242,7 +243,7 @@ const RECOVERABLE_THREAD_RESUME_ERROR_SNIPPETS = [
"unknown thread",
"does not exist",
];
const CODEX_DEFAULT_MODEL = "gpt-5.5";
const CODEX_DEFAULT_MODEL = DEFAULT_MODEL_BY_PROVIDER.codex;
const CODEX_SPARK_MODEL = "gpt-5.3-codex-spark";
const CODEX_SPARK_DISABLED_PLAN_TYPES = new Set<CodexPlanType>(["free", "go", "plus"]);
const CODEX_DISCOVERY_SESSION_IDLE_MS = 10 * 60 * 1000;
Expand Down Expand Up @@ -3423,6 +3424,8 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
this.readString(model, "defaultReasoningEffort") ??
this.readString(model, "default_reasoning_effort");
const trimmedDefaultReasoningEffort = defaultReasoningEffort?.trim();
const isDefault = this.readFirstBoolean(model, ["isDefault", "is_default"]);
const description = this.readString(model, "description")?.trim();
const additionalSpeedTiers =
this.readArray(model, "additionalSpeedTiers") ??
this.readArray(model, "additional_speed_tiers") ??
Expand All @@ -3445,6 +3448,8 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
{
slug: trimmedSlug,
name: trimmedName,
...(description ? { description } : {}),
...(isDefault === true ? { isDefault: true as const } : {}),
...(supportedReasoningEfforts.length > 0 ? { supportedReasoningEfforts } : {}),
...(trimmedDefaultReasoningEffort &&
supportedReasoningEfforts.some(
Expand Down
33 changes: 32 additions & 1 deletion apps/server/src/provider/Layers/ClaudeAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import path from "node:path";
import * as NodeServices from "@effect/platform-node/NodeServices";
import type {
Options as ClaudeQueryOptions,
ModelInfo,
PermissionMode,
PermissionResult,
SDKControlGetContextUsageResponse,
Expand Down Expand Up @@ -339,13 +340,27 @@ describe("ClaudeAdapterLive", () => {

it.effect("uses the configured Claude executable for pre-session model discovery", () => {
const harness = makeHarness();
(harness.query as { supportedModels: () => Promise<ModelInfo[]> }).supportedModels =
async () => [
{
value: "opus[1m]",
resolvedModel: "claude-opus-4-8[1m]",
displayName: "Claude Opus 4.8 (1M context)",
description: "Complex agentic coding",
supportsEffort: true,
supportedEffortLevels: ["low", "medium", "high", "xhigh", "max"],
supportsAdaptiveThinking: true,
supportsFastMode: true,
supportsAutoMode: false,
},
];
return Effect.gen(function* () {
const adapter = yield* ClaudeAdapter;
if (!adapter.listModels) {
return assert.fail("Claude adapter should support model discovery.");
}

yield* adapter.listModels({
const result = yield* adapter.listModels({
provider: "claudeAgent",
cwd: "/tmp/claude-model-discovery",
binaryPath: "/managed/claude-models",
Expand All @@ -356,6 +371,22 @@ describe("ClaudeAdapterLive", () => {
"/managed/claude-models",
);
assert.equal(harness.query.closeCalls, 1);
assert.deepEqual(result.models, [
{
slug: "opus[1m]",
name: "Claude Opus 4.8 (1M context)",
resolvedModel: "claude-opus-4-8[1m]",
description: "Complex agentic coding",
supportedReasoningEfforts: [
{ value: "low", label: "Low" },
{ value: "medium", label: "Medium" },
{ value: "high", label: "High" },
{ value: "xhigh", label: "Extra High" },
{ value: "max", label: "Max" },
],
supportsFastMode: true,
},
]);
}).pipe(
Effect.provideService(Random.Random, makeDeterministicRandomService()),
Effect.provide(harness.layer),
Expand Down
24 changes: 22 additions & 2 deletions apps/server/src/provider/Layers/ClaudeAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import {
type ProviderListSkillsResult,
type ProviderListAgentsResult,
type ProviderListModelsResult,
type ProviderModelDescriptor,
getAgentMentionAliases,
} from "@synara/contracts";
import {
Expand Down Expand Up @@ -299,6 +300,25 @@ function mapSupportedCommands(commands: SlashCommand[]): ProviderListCommandsRes
};
}

function mapClaudeModelInfo(model: ModelInfo): ProviderModelDescriptor {
const resolvedModel = model.resolvedModel?.trim();
const description = model.description.trim();
const supportedReasoningEfforts = model.supportedEffortLevels?.map((effort) => ({
value: effort,
label:
effort === "xhigh" ? "Extra High" : `${effort.charAt(0).toUpperCase()}${effort.slice(1)}`,
}));
return {
slug: model.value,
name: model.displayName,
...(resolvedModel ? { resolvedModel } : {}),
...(model.value === "default" ? { isDefault: true as const } : {}),
...(description ? { description } : {}),
...(supportedReasoningEfforts?.length ? { supportedReasoningEfforts } : {}),
...(model.supportsFastMode !== undefined ? { supportsFastMode: model.supportsFastMode } : {}),
};
}

function neverResolvingUserMessageStream(): AsyncIterable<SDKUserMessage> {
return {
[Symbol.asyncIterator](): AsyncIterator<SDKUserMessage> {
Expand Down Expand Up @@ -3748,7 +3768,7 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) {
.supportedModels()
.then((models) => {
modelsCache.set(modelCacheKey, {
models: models.map((m) => ({ slug: m.value, name: m.displayName })),
models: models.map(mapClaudeModelInfo),
source: "sdk",
cached: false,
});
Expand Down Expand Up @@ -4327,7 +4347,7 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) {
})().catch(() => undefined);
const models = await tempQuery.supportedModels();
return {
models: models.map((model) => ({ slug: model.value, name: model.displayName })),
models: models.map(mapClaudeModelInfo),
source: "sdk",
cached: false,
};
Expand Down
8 changes: 8 additions & 0 deletions apps/server/src/provider/Layers/GrokAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ describe("GrokAdapter runtime event scoping", () => {
object: "model",
aliases: ["grok-code-fast", "grok-code-fast-1", "grok-build-0.1", "ignored-alias"],
},
{
id: "grok-4.5",
object: "model",
aliases: ["grok-4.5-latest", "grok-build-latest"],
},
{ id: "grok-code-fast-1-0825", object: "model" },
{ id: "grok-4.3", object: "model" },
{ id: " " },
Expand All @@ -106,6 +111,9 @@ describe("GrokAdapter runtime event scoping", () => {
{ slug: "grok-build-0.1", name: "Grok Build 0.1" },
{ slug: "grok-code-fast", name: "Grok Code Fast" },
{ slug: "grok-code-fast-1", name: "Grok Code Fast 1" },
{ slug: "grok-4.5", name: "Grok 4.5" },
{ slug: "grok-4.5-latest", name: "Grok 4.5" },
{ slug: "grok-build-latest", name: "Grok Build Latest" },
{ slug: "grok-code-fast-1-0825", name: "Grok Code Fast 1 0825" },
]);
});
Expand Down
14 changes: 13 additions & 1 deletion apps/server/src/provider/Layers/GrokAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,12 @@ function parseGrokResume(raw: unknown): { sessionId: string } | undefined {
}

function formatGrokModelName(slug: string): string {
if (slug === "grok-build-latest") {
return "Grok Build Latest";
}
if (slug === "grok-4.5" || slug === "grok-4.5-latest") {
return "Grok 4.5";
}
if (slug === "grok-build-0.1") {
return "Grok Build 0.1";
}
Expand All @@ -431,7 +437,13 @@ function formatGrokModelName(slug: string): string {
}

function isGrokBuildApiModelSlug(slug: string): boolean {
return slug === "grok-build-0.1" || /^grok-code-fast(?:-\d+(?:-\d+)?)?$/u.test(slug);
return (
slug === "grok-build-latest" ||
slug === "grok-4.5" ||
slug === "grok-4.5-latest" ||
slug === "grok-build-0.1" ||
/^grok-code-fast(?:-\d+(?:-\d+)?)?$/u.test(slug)
);
}

function readXaiModelAliases(rawModel: Record<string, unknown>): string[] {
Expand Down
5 changes: 4 additions & 1 deletion apps/web/src/appSettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ describe("getAppModelOptions", () => {
const options = getAppModelOptions("codex", ["custom/internal-model"]);

expect(options.map((option) => option.slug)).toEqual([
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
"gpt-5.5",
"gpt-5.4",
"gpt-5.4-mini",
Expand Down Expand Up @@ -223,7 +226,7 @@ describe("resolveAppModelSelection", () => {
},
"",
),
).toBe("gpt-5.5");
).toBe("gpt-5.6-sol");
});

it("resolves display names through the shared resolver", () => {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/ChatView.browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3265,7 +3265,7 @@ describe("ChatView timeline estimator parity (full app)", () => {
expect(
useComposerDraftStore.getState().draftsByThreadId[THREAD_ID]?.modelSelectionByProvider
.codex,
).toMatchObject({ provider: "codex", model: "gpt-5.5" });
).toMatchObject({ provider: "codex", model: "gpt-5.6-sol" });
});
expect(document.querySelector('[data-slot="menu-popup"]')).toBeNull();

Expand Down
29 changes: 16 additions & 13 deletions apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ import {
ProviderInteractionMode,
RuntimeMode,
} from "@synara/contracts";
import { getModelCapabilities, normalizeModelSlug } from "@synara/shared/model";
import {
getModelCapabilities,
getRecommendedDefaultModelSelection,
normalizeModelSlug,
} from "@synara/shared/model";
import { resolveTailUserMessageEditTarget } from "@synara/shared/conversationEdit";
import { threadExportBlockedReason } from "@synara/shared/threadExport";
import { buildTemporaryWorktreeBranchName } from "@synara/shared/git";
Expand Down Expand Up @@ -1674,10 +1678,8 @@ export default function ChatView({
? buildLocalDraftThread(
threadId,
draftThread,
fallbackDraftProject?.defaultModelSelection ?? {
provider: "codex",
model: DEFAULT_MODEL_BY_PROVIDER.codex,
},
fallbackDraftProject?.defaultModelSelection ??
getRecommendedDefaultModelSelection("codex")!,
localDraftError,
)
: undefined,
Expand Down Expand Up @@ -2380,14 +2382,6 @@ export default function ChatView({
openCodeDynamicModelsQuery.data,
piDynamicModelsQuery.data,
]);
const { modelOptions: composerModelOptions, selectedModel } = useEffectiveComposerModelState({
threadId,
selectedProvider,
threadModelSelection: activeThread?.modelSelection,
projectModelSelection: activeProject?.defaultModelSelection,
customModelsByProvider,
availableModelOptionsByProvider: modelOptionsByProvider,
});
const runtimeModelsByProvider = useMemo(
() => ({
claudeAgent: claudeDynamicModelsQuery.data?.models ?? [],
Expand All @@ -2412,6 +2406,15 @@ export default function ChatView({
piDynamicModelsQuery.data?.models,
],
);
const { modelOptions: composerModelOptions, selectedModel } = useEffectiveComposerModelState({
threadId,
selectedProvider,
threadModelSelection: activeThread?.modelSelection,
projectModelSelection: activeProject?.defaultModelSelection,
customModelsByProvider,
availableModelOptionsByProvider: modelOptionsByProvider,
runtimeModelOptionsByProvider: runtimeModelsByProvider,
});
const providerModelsQueryByProvider = {
claudeAgent: claudeDynamicModelsQuery,
codex: codexDynamicModelsQuery,
Expand Down
13 changes: 3 additions & 10 deletions apps/web/src/components/DiffPanel.logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,8 @@
// Exports: resolveDiffPanelThread, diff view source helpers
// Depends on: ChatView.logic draft-thread normalization.

import {
DEFAULT_MODEL_BY_PROVIDER,
type ModelSelection,
type ThreadId,
type TurnId,
} from "@synara/contracts";
import { type ModelSelection, type ThreadId, type TurnId } from "@synara/contracts";
import { getRecommendedDefaultModelSelection } from "@synara/shared/model";
import type { FileDiffMetadata } from "@pierre/diffs/react";

import type { DraftThreadState } from "../composerDraftStore";
Expand Down Expand Up @@ -59,10 +55,7 @@ export function resolveDiffPanelThread(input: {
return buildLocalDraftThread(
input.threadId,
input.draftThread,
input.fallbackModelSelection ?? {
provider: "codex",
model: DEFAULT_MODEL_BY_PROVIDER.codex,
},
input.fallbackModelSelection ?? getRecommendedDefaultModelSelection("codex")!,
null,
);
}
Expand Down
10 changes: 2 additions & 8 deletions apps/web/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ import {
type ServerLocalServerProcess,
} from "@synara/contracts";
import { isGenericChatThreadTitle } from "@synara/shared/chatThreads";
import { getDefaultModel } from "@synara/shared/model";
import { getDefaultModel, getRecommendedDefaultModelSelection } from "@synara/shared/model";
import { pluralize } from "@synara/shared/text";
import { localServerAddressLabel, localServerMatchesRun } from "@synara/shared/localServers";
import { resolveThreadWorkspaceCwd } from "@synara/shared/threadEnvironment";
Expand Down Expand Up @@ -2922,16 +2922,10 @@ export default function Sidebar() {
throw new Error("The target project could not be resolved.");
}

const providerDefaultModel = getDefaultModel(provider);
const modelSelection =
activeProject.defaultModelSelection?.provider === provider
? activeProject.defaultModelSelection
: providerDefaultModel
? {
provider,
model: providerDefaultModel,
}
: null;
: getRecommendedDefaultModelSelection(provider);
if (!modelSelection) {
throw new Error("Select a Pi model before importing a Pi thread.");
}
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/chat/TraitsPicker.browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ describe("TraitsPicker (Codex)", () => {

it("shows Fast in the trigger label when fast mode is active", async () => {
await using _ = await mountCodexPicker({
options: { fastMode: true },
options: { reasoningEffort: "medium", fastMode: true },
});

await vi.waitFor(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ describe("getComposerProviderState", () => {
it("preserves a stored runtime Codex effort for dispatch before discovery resolves", () => {
const state = getComposerProviderState({
provider: "codex",
model: "gpt-5.6-sol",
model: "gpt-5.7-sol",
prompt: "",
modelOptions: {
codex: {
Expand Down
Loading
Loading