diff --git a/apps/mobile/package.json b/apps/mobile/package.json
index bdb4c8ebfbca..e91ec70d0ba1 100644
--- a/apps/mobile/package.json
+++ b/apps/mobile/package.json
@@ -73,14 +73,6 @@
"@t3tools/mobile-review-diff-native": "file:./modules/t3-review-diff",
"@t3tools/mobile-source-text": "file:./modules/t3-source-text",
"@t3tools/mobile-terminal-native": "file:./modules/t3-terminal",
- "@t3tools/provider-acp-registry": "workspace:*",
- "@t3tools/provider-core": "workspace:*",
- "@t3tools/provider-cursor": "workspace:*",
- "@t3tools/provider-fx": "workspace:*",
- "@t3tools/provider-grok": "workspace:*",
- "@t3tools/provider-muse": "workspace:*",
- "@t3tools/provider-opencode": "workspace:*",
- "@t3tools/provider-pi": "workspace:*",
"@t3tools/shared": "workspace:*",
"@tabler/icons-react-native": "^3.44.0",
"clsx": "^2.1.1",
diff --git a/apps/mobile/src/components/ComposerModelControl.tsx b/apps/mobile/src/components/ComposerModelControl.tsx
index 431cf61b1ec6..e422cfabd459 100644
--- a/apps/mobile/src/components/ComposerModelControl.tsx
+++ b/apps/mobile/src/components/ComposerModelControl.tsx
@@ -70,7 +70,7 @@ export function ComposerModelControl(props: {
/>
)
}
- label={label}
+ label={props.modelOption?.shortName ?? label}
iconOnly={props.iconOnly}
suffix={
summary ? (
diff --git a/apps/mobile/src/components/ProviderIcon.tsx b/apps/mobile/src/components/ProviderIcon.tsx
index 248594ab8655..1fe46085d9e8 100644
--- a/apps/mobile/src/components/ProviderIcon.tsx
+++ b/apps/mobile/src/components/ProviderIcon.tsx
@@ -10,7 +10,7 @@ import { useId, useState } from "react";
import { resolveOfficialAcpRegistryIconUrl } from "@t3tools/contracts";
import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider";
import { AppText as Text } from "./ui/AppText";
-import { getProviderClient } from "../lib/providerClients";
+import { getProviderClient } from "@t3tools/client-runtime/provider-clients";
type ProviderIconProps = {
readonly provider: string | null | undefined;
diff --git a/apps/mobile/src/features/threads/thread-handoff-row.tsx b/apps/mobile/src/features/threads/thread-handoff-row.tsx
index 4cb04fa6b3ec..7e9698c057aa 100644
--- a/apps/mobile/src/features/threads/thread-handoff-row.tsx
+++ b/apps/mobile/src/features/threads/thread-handoff-row.tsx
@@ -2,6 +2,7 @@ import * as Dialog from "../../components/ui/Dialog";
import { useAtomValue } from "@effect/atom-react";
import { scopeThreadRef } from "@t3tools/client-runtime/environment";
import { resolveHandoffEndpoints, type HandoffEndpoints } from "@t3tools/client-runtime/handoff";
+import { resolveModelShortName } from "@t3tools/client-runtime/provider-clients";
import {
normalizeProviderAccentColor,
resolveProviderInstanceDisplayName,
@@ -136,7 +137,7 @@ function HandoffEndpoint(props: {
const provider = props.providers.find((candidate) => candidate.instanceId === props.instanceId);
const model = provider?.models.find((candidate) => candidate.slug === props.model);
const label =
- model?.shortName ??
+ (model ? resolveModelShortName(model, provider?.driver) : undefined) ??
model?.name ??
props.model ??
(provider ? resolveProviderInstanceDisplayName(provider) : props.instanceId);
diff --git a/apps/mobile/src/lib/modelOptions.ts b/apps/mobile/src/lib/modelOptions.ts
index c36c63a4f145..930d25beb73e 100644
--- a/apps/mobile/src/lib/modelOptions.ts
+++ b/apps/mobile/src/lib/modelOptions.ts
@@ -11,15 +11,17 @@ import type {
RuntimeMode,
ServerConfig as T3ServerConfig,
} from "@t3tools/contracts";
-import { resolveProjectProviderInstanceEnabled } from "@t3tools/contracts";
+import { isProviderDriverKind, resolveProjectProviderInstanceEnabled } from "@t3tools/contracts";
import {
buildExplicitProviderOptionSelectionsFromDescriptors,
getProviderOptionDescriptors,
} from "@t3tools/shared/model";
+import { resolveModelShortName } from "@t3tools/client-runtime/provider-clients";
export type ModelOption = {
readonly key: string;
readonly label: string;
+ readonly shortName?: string;
readonly subtitle: string;
readonly providerKey: string;
readonly providerLabel: string;
@@ -319,9 +321,11 @@ export function buildModelOptions(
models.sort((a, b) => (order.get(a.slug) ?? Infinity) - (order.get(b.slug) ?? Infinity));
for (const model of models) {
const key = `${provider.instanceId}:${model.slug}`;
+ const shortName = resolveModelShortName(model, provider.driver);
options.set(key, {
key,
label: model.name,
+ ...(shortName ? { shortName } : {}),
subtitle: model.subProvider ?? "",
providerKey: provider.instanceId,
providerLabel,
@@ -384,9 +388,13 @@ export function buildModelOptions(
displayName: provider?.displayName ?? instanceConfig?.displayName,
instanceId: fallbackModelSelection.instanceId,
});
+ const shortName = model
+ ? resolveModelShortName(model, isProviderDriverKind(providerDriver) ? providerDriver : null)
+ : undefined;
options.set(key, {
key,
label: model?.name ?? fallbackModelSelection.model,
+ ...(shortName ? { shortName } : {}),
subtitle: model?.subProvider ?? "",
providerKey: fallbackModelSelection.instanceId,
providerLabel,
diff --git a/apps/mobile/src/lib/providerClients.ts b/apps/mobile/src/lib/providerClients.ts
deleted file mode 100644
index 2a62b5fdc46f..000000000000
--- a/apps/mobile/src/lib/providerClients.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { isProviderDriverKind } from "@t3tools/contracts";
-import { acpRegistryClient } from "@t3tools/provider-acp-registry/client";
-import { makeProviderClientRegistry } from "@t3tools/provider-core/client";
-import { cursorClient } from "@t3tools/provider-cursor/client";
-import { fxClient } from "@t3tools/provider-fx/client";
-import { grokClient } from "@t3tools/provider-grok/client";
-import { museClient } from "@t3tools/provider-muse/client";
-import { openCodeClient } from "@t3tools/provider-opencode/client";
-import { piClient } from "@t3tools/provider-pi/client";
-
-const providerClients = makeProviderClientRegistry([
- cursorClient,
- grokClient,
- fxClient,
- museClient,
- openCodeClient,
- piClient,
- acpRegistryClient,
-]);
-
-export function getProviderClient(driver: string | null | undefined) {
- return isProviderDriverKind(driver) ? providerClients.get(driver) : undefined;
-}
diff --git a/apps/web/package.json b/apps/web/package.json
index 9ab88beb3d7f..de75da24e380 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -34,14 +34,7 @@
"@pierre/trees": "1.0.0-beta.6",
"@t3tools/client-runtime": "workspace:*",
"@t3tools/contracts": "workspace:*",
- "@t3tools/provider-acp-registry": "workspace:*",
"@t3tools/provider-core": "workspace:*",
- "@t3tools/provider-cursor": "workspace:*",
- "@t3tools/provider-fx": "workspace:*",
- "@t3tools/provider-grok": "workspace:*",
- "@t3tools/provider-muse": "workspace:*",
- "@t3tools/provider-opencode": "workspace:*",
- "@t3tools/provider-pi": "workspace:*",
"@t3tools/shared": "workspace:*",
"@tanstack/react-pacer": "^0.19.4",
"@tanstack/react-router": "^1.160.2",
diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx
index f0b6877c42e7..5369f2158e6e 100644
--- a/apps/web/src/components/ChatView.tsx
+++ b/apps/web/src/components/ChatView.tsx
@@ -1628,7 +1628,7 @@ export default function ChatView(props: ChatViewProps) {
(model) => model.slug === providerSubagentModelSlug,
);
const providerSubagentModelLabel = providerSubagentCatalogModel
- ? getTriggerDisplayModelName(providerSubagentCatalogModel)
+ ? getTriggerDisplayModelName(providerSubagentCatalogModel, selectedProviderEntry?.driverKind)
: formatModelSlugName(activeThread?.modelSelection.model ?? "");
const providerSubagentEffortLabel =
activeThread === undefined
diff --git a/apps/web/src/components/chat/ContextWindowMeter.logic.test.ts b/apps/web/src/components/chat/ContextWindowMeter.logic.test.ts
index 670ccd541bdb..e8d08570707b 100644
--- a/apps/web/src/components/chat/ContextWindowMeter.logic.test.ts
+++ b/apps/web/src/components/chat/ContextWindowMeter.logic.test.ts
@@ -99,7 +99,7 @@ describe("resolveContextWindowModelDisplayName", () => {
},
modelOptionsByInstance,
),
- ).toBe("5.6 Sol");
+ ).toBe("GPT-5.6 Sol");
});
});
diff --git a/apps/web/src/components/chat/ContextWindowMeter.logic.ts b/apps/web/src/components/chat/ContextWindowMeter.logic.ts
index c6bcfba0b00a..5a0911a10a05 100644
--- a/apps/web/src/components/chat/ContextWindowMeter.logic.ts
+++ b/apps/web/src/components/chat/ContextWindowMeter.logic.ts
@@ -8,7 +8,7 @@ import {
resolveSelectableProviderInstanceEntry,
type ProviderInstanceEntry,
} from "../../providerInstances";
-import { getTriggerDisplayModelName, type ModelEsque } from "./providerIconUtils";
+import { getDisplayModelName, type ModelEsque } from "./providerIconUtils";
// Fallback TTL: Claude's 1h cache plus slack.
const CLAUDE_RESUME_COMPACTION_FALLBACK_MS = 70 * 60_000;
@@ -96,7 +96,7 @@ export function resolveContextWindowModelDisplayName(
.get(selection.instanceId)
?.find((model) => model.slug === selection.model);
- return selectedModel ? getTriggerDisplayModelName(selectedModel) : selection.model;
+ return selectedModel ? getDisplayModelName(selectedModel) : selection.model;
}
export function formatContextWindowCompactionMessage(
diff --git a/apps/web/src/components/chat/ModelListRow.tsx b/apps/web/src/components/chat/ModelListRow.tsx
index 5c35d78e3886..1d318443e089 100644
--- a/apps/web/src/components/chat/ModelListRow.tsx
+++ b/apps/web/src/components/chat/ModelListRow.tsx
@@ -1,11 +1,7 @@
import { type ProviderDriverKind, type ProviderInstanceId } from "@t3tools/contracts";
import { memo } from "react";
import { CheckIcon, StarIcon } from "lucide-react";
-import {
- getDisplayModelName,
- getProviderRowLabel,
- getTriggerDisplayModelLabel,
-} from "./providerIconUtils";
+import { getDisplayModelName, getProviderRowLabel } from "./providerIconUtils";
import { ComboboxItem } from "../ui/combobox";
import { Button } from "../ui/button";
import { Badge } from "../ui/badge";
@@ -19,7 +15,6 @@ export const ModelListRow = memo(function ModelListRow(props: {
index: number;
slug: string;
name: string;
- shortName?: string | undefined;
subProvider?: string | undefined;
instanceId: ProviderInstanceId;
driverKind: ProviderDriverKind;
@@ -30,8 +25,6 @@ export const ModelListRow = memo(function ModelListRow(props: {
isFavorite: boolean;
isSelected: boolean;
showProvider: boolean;
- preferShortName?: boolean;
- useTriggerLabel?: boolean;
showNewBadge?: boolean;
unavailable?: boolean;
jumpLabel?: string | null;
@@ -41,7 +34,6 @@ export const ModelListRow = memo(function ModelListRow(props: {
const model = {
slug: props.slug,
name: props.name,
- shortName: props.shortName,
subProvider: props.subProvider,
};
const providerLabel = getProviderRowLabel(props.providerDisplayName, props.subProvider);
@@ -61,12 +53,7 @@ export const ModelListRow = memo(function ModelListRow(props: {
- {props.useTriggerLabel
- ? getTriggerDisplayModelLabel(model)
- : getDisplayModelName(
- model,
- props.preferShortName ? { preferShortName: true } : undefined,
- )}
+ {getDisplayModelName(model)}
{props.showNewBadge ? (
2 ? `, ${selectedEntries.length - 2} more` : ""}`
: undefined;
const allModelNames = selectedEntries
- ? selectedEntries.map((selection) => selection.label).join(", ") || "Choose models"
+ ? selectedEntries.map((selection) => selection.fullLabel).join(", ") || "Choose models"
: undefined;
const triggerTooltipContent = shortcutLabel
? `${props.triggerLabel ?? allModelNames ?? triggerLabel} · ${shortcutLabel}`
@@ -193,7 +191,9 @@ export const ProviderModelPicker = memo(function ProviderModelPicker(props: {
candidate.slug === model);
const label =
providerModel !== undefined
- ? getTriggerDisplayModelName(providerModel)
+ ? getTriggerDisplayModelName(providerModel, entry?.driverKind)
: model !== undefined && model.length > 0
? model
: (entry?.displayName ?? props.instanceId);
diff --git a/apps/web/src/components/chat/providerIconUtils.ts b/apps/web/src/components/chat/providerIconUtils.ts
index 9ab57d4f3d70..3640faefa5c3 100644
--- a/apps/web/src/components/chat/providerIconUtils.ts
+++ b/apps/web/src/components/chat/providerIconUtils.ts
@@ -1,3 +1,6 @@
+import type { ProviderDriverKind } from "@t3tools/contracts";
+import { resolveModelShortName } from "@t3tools/client-runtime/provider-clients";
+
export type ModelEsque = {
slug: string;
name: string;
@@ -47,10 +50,10 @@ export function getProviderRowLabel(
return `${providerDisplayName} · ${sub}`;
}
-export function getTriggerDisplayModelName(model: ModelEsque): string {
- return getDisplayModelName(model, { preferShortName: true });
-}
-
-export function getTriggerDisplayModelLabel(model: ModelEsque): string {
- return getTriggerDisplayModelName(model);
+export function getTriggerDisplayModelName(
+ model: ModelEsque,
+ driverKind: ProviderDriverKind | null | undefined,
+): string {
+ const shortName = resolveModelShortName(model, driverKind);
+ return getDisplayModelName({ ...model, shortName }, { preferShortName: true });
}
diff --git a/apps/web/src/components/guest/GuestPage.browser.test.tsx b/apps/web/src/components/guest/GuestPage.browser.test.tsx
index f3668478edb8..bd29b183299b 100644
--- a/apps/web/src/components/guest/GuestPage.browser.test.tsx
+++ b/apps/web/src/components/guest/GuestPage.browser.test.tsx
@@ -246,7 +246,7 @@ describe("GuestPage", () => {
expect(screen.container.querySelector('[aria-label="Fork from this response"]')).toBeNull();
expect(socket).not.toHaveBeenCalled();
// The composer names the thread's model, as the owner's does, with nothing to pick.
- await expect.element(screen.getByLabelText("Model: 5.4")).toBeVisible();
+ await expect.element(screen.getByLabelText("Model: GPT-5.4")).toBeVisible();
});
it("sends attached files with the message, as form data", async () => {
diff --git a/apps/web/src/components/guest/GuestPage.tsx b/apps/web/src/components/guest/GuestPage.tsx
index 2314fab42d18..e860920692ff 100644
--- a/apps/web/src/components/guest/GuestPage.tsx
+++ b/apps/web/src/components/guest/GuestPage.tsx
@@ -49,7 +49,7 @@ import { PierreEntryIcon } from "../chat/PierreEntryIcon";
import { GuestMessageName, guestBubbleClassName } from "../chat/guestMessageStyle";
import { MessagesTimeline } from "../chat/MessagesTimeline";
import { ProviderInstanceIcon } from "../chat/ProviderInstanceIcon";
-import { getTriggerDisplayModelName } from "../chat/providerIconUtils";
+import { getDisplayModelName, getTriggerDisplayModelName } from "../chat/providerIconUtils";
import { ScrollToEndButton } from "../chat/TimelineOverlays";
import type { TimelineRow } from "../chat/timelineRowContext";
import { useTimelineFollow } from "../chat/useTimelineFollow";
@@ -557,11 +557,12 @@ function UnplacedGuestMessage({
/** The thread's model, labeled as the owner's composer labels it, with no way to change it. */
function GuestModelLabel({ model }: { readonly model: GuestThreadModel }) {
- const label = getTriggerDisplayModelName({ slug: model.name, ...model });
+ const label = getTriggerDisplayModelName({ slug: model.name, ...model }, model.driver);
+ const fullLabel = getDisplayModelName({ slug: model.name, ...model });
return (
}
- aria-label={`Model: ${label}`}
+ aria-label={`Model: ${fullLabel}`}
className="pointer-events-none min-w-0 shrink"
>
{model.driver === null ? null : (
diff --git a/apps/web/src/components/settings/providerDriverMeta.ts b/apps/web/src/components/settings/providerDriverMeta.ts
index 552fc0d2867e..3b7f0ff6bb25 100644
--- a/apps/web/src/components/settings/providerDriverMeta.ts
+++ b/apps/web/src/components/settings/providerDriverMeta.ts
@@ -1,42 +1 @@
-import {
- AntigravitySettings,
- ClaudeSettings,
- CodexSettings,
- ProviderDriverKind,
-} from "@t3tools/contracts";
-import { acpRegistryClient } from "@t3tools/provider-acp-registry/client";
-import { makeProviderClientRegistry } from "@t3tools/provider-core/client";
-import { cursorClient } from "@t3tools/provider-cursor/client";
-import { fxClient } from "@t3tools/provider-fx/client";
-import { grokClient } from "@t3tools/provider-grok/client";
-import { museClient } from "@t3tools/provider-muse/client";
-import { openCodeClient } from "@t3tools/provider-opencode/client";
-import { piClient } from "@t3tools/provider-pi/client";
-
-export const providerClients = makeProviderClientRegistry([
- {
- driverKind: ProviderDriverKind.make("codex"),
- label: "Codex",
- acpRegistryAgentIds: ["codex-acp"],
- settingsSchema: CodexSettings,
- },
- {
- driverKind: ProviderDriverKind.make("claudeAgent"),
- label: "Claude",
- acpRegistryAgentIds: ["claude-acp"],
- settingsSchema: ClaudeSettings,
- },
- cursorClient,
- grokClient,
- fxClient,
- openCodeClient,
- {
- driverKind: ProviderDriverKind.make("antigravity"),
- label: "Antigravity",
- acpRegistryAgentIds: ["antigravity-acp"],
- settingsSchema: AntigravitySettings,
- },
- museClient,
- piClient,
- acpRegistryClient,
-]);
+export { providerClients } from "@t3tools/client-runtime/provider-clients";
diff --git a/apps/web/src/components/sidebar/SidebarThreadRow.tsx b/apps/web/src/components/sidebar/SidebarThreadRow.tsx
index 449be6dff7aa..b087e3d1e548 100644
--- a/apps/web/src/components/sidebar/SidebarThreadRow.tsx
+++ b/apps/web/src/components/sidebar/SidebarThreadRow.tsx
@@ -104,7 +104,7 @@ import { ProjectFavicon, type ProjectFaviconProject } from "../ProjectFavicon";
import { ThreadSearchMatchExcerpt } from "../ThreadSearchMatch";
import { makeWorkspaceFileDropHandlers } from "../chat/workspaceFileDrop";
import { ProviderInstanceIcon } from "../chat/ProviderInstanceIcon";
-import { getTriggerDisplayModelLabel } from "../chat/providerIconUtils";
+import { getTriggerDisplayModelName } from "../chat/providerIconUtils";
import { shouldShowInstanceBadge, type ProviderInstanceEntry } from "../../providerInstances";
import { useThreadRunningTerminalIds } from "../../state/terminalSessions";
import { InlineButton } from "../ui/button";
@@ -771,7 +771,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP
(model) => model.slug === thread.modelSelection.model,
);
const modelLabel = selectedModel
- ? getTriggerDisplayModelLabel(selectedModel)
+ ? getTriggerDisplayModelName(selectedModel, providerEntry?.driverKind)
: thread.modelSelection.model;
const isRemote = thread.environmentId !== props.currentEnvironmentId;
@@ -1602,7 +1602,7 @@ export const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props
(model) => model.slug === thread.modelSelection.model,
);
const modelLabel = selectedModel
- ? getTriggerDisplayModelLabel(selectedModel)
+ ? getTriggerDisplayModelName(selectedModel, providerEntry?.driverKind)
: thread.modelSelection.model;
const runningTerminalIds = useThreadRunningTerminalIds({
environmentId: thread.environmentId,
diff --git a/apps/web/src/modelSelection.ts b/apps/web/src/modelSelection.ts
index 9028dd4e39da..db52028c397e 100644
--- a/apps/web/src/modelSelection.ts
+++ b/apps/web/src/modelSelection.ts
@@ -31,6 +31,7 @@ import {
NO_PROVIDER_MODEL_SELECTION,
} from "./providerInstances";
import { sortModelsForProviderInstance } from "./modelOrdering";
+import { resolveModelShortName } from "@t3tools/client-runtime/provider-clients";
export const MAX_CUSTOM_MODEL_LENGTH = 256;
const DEFAULT_TEXT_GENERATION_INSTANCE_ID = ProviderInstanceId.make("codex");
@@ -85,13 +86,17 @@ function appendUnavailableDynamicModelSelection(
return [...options, { slug, name: slug, isCustom: false, isUnavailable: true }];
}
-function toAppModelOption(model: ServerProvider["models"][number]): AppModelOption {
+function toAppModelOption(
+ model: ServerProvider["models"][number],
+ driverKind: ProviderDriverKind,
+): AppModelOption {
const option: AppModelOption = {
slug: model.slug,
name: model.name,
isCustom: model.isCustom,
};
- if (model.shortName) option.shortName = model.shortName;
+ const shortName = resolveModelShortName(model, driverKind);
+ if (shortName) option.shortName = shortName;
if (model.subProvider) option.subProvider = model.subProvider;
if (model.aliases) option.aliases = model.aliases;
if (model.badge) option.badge = model.badge;
@@ -159,7 +164,7 @@ function getAppModelOptions(
// Server custom-model snapshots can lag removals; rebuild custom rows from settings.
const options: AppModelOption[] = rawModels
.filter((model) => !model.isCustom)
- .map(toAppModelOption);
+ .map((model) => toAppModelOption(model, provider));
const seen = new Set(options.map((option) => option.slug));
const builtInModelSlugs = new Set(
Arr.filterMap(getProviderModels(providers, provider), (model) =>
@@ -175,7 +180,13 @@ function getAppModelOptions(
}
seen.add(entry.slug);
- options.push({ slug: entry.slug, name: entry.name, isCustom: true });
+ const shortName = resolveModelShortName(entry, provider);
+ options.push({
+ slug: entry.slug,
+ name: entry.name,
+ isCustom: true,
+ ...(shortName ? { shortName } : {}),
+ });
}
const preferences = readInstanceModelPreferences(settings, defaultInstanceId);
@@ -195,7 +206,7 @@ export function getAppModelOptionsForInstance(
): AppModelOption[] {
const options: AppModelOption[] = entry.models
.filter((model) => !model.isCustom)
- .map(toAppModelOption);
+ .map((model) => toAppModelOption(model, entry.driverKind));
const seen = new Set(options.map((option) => option.slug));
const builtInModelSlugs = new Set(
Arr.filterMap(entry.models, (model) =>
@@ -210,7 +221,13 @@ export function getAppModelOptionsForInstance(
}
seen.add(custom.slug);
- options.push({ slug: custom.slug, name: custom.name, isCustom: true });
+ const shortName = resolveModelShortName(custom, entry.driverKind);
+ options.push({
+ slug: custom.slug,
+ name: custom.name,
+ isCustom: true,
+ ...(shortName ? { shortName } : {}),
+ });
}
const preferences = readInstanceModelPreferences(settings, entry.instanceId);
diff --git a/packages/client-runtime/package.json b/packages/client-runtime/package.json
index 03779b627eea..9719f5936078 100644
--- a/packages/client-runtime/package.json
+++ b/packages/client-runtime/package.json
@@ -478,6 +478,10 @@
"./model-traits": {
"types": "./src/modelTraits.ts",
"default": "./src/modelTraits.ts"
+ },
+ "./provider-clients": {
+ "types": "./src/providerClients.ts",
+ "default": "./src/providerClients.ts"
}
},
"scripts": {
@@ -487,6 +491,14 @@
"dependencies": {
"@pierre/diffs": "catalog:",
"@t3tools/contracts": "workspace:*",
+ "@t3tools/provider-acp-registry": "workspace:*",
+ "@t3tools/provider-core": "workspace:*",
+ "@t3tools/provider-cursor": "workspace:*",
+ "@t3tools/provider-fx": "workspace:*",
+ "@t3tools/provider-grok": "workspace:*",
+ "@t3tools/provider-muse": "workspace:*",
+ "@t3tools/provider-opencode": "workspace:*",
+ "@t3tools/provider-pi": "workspace:*",
"@t3tools/shared": "workspace:*",
"@t3tools/source-control-core": "workspace:*",
"@t3tools/source-control-definitions": "workspace:*",
diff --git a/packages/client-runtime/src/providerClients.ts b/packages/client-runtime/src/providerClients.ts
new file mode 100644
index 000000000000..37cd15209614
--- /dev/null
+++ b/packages/client-runtime/src/providerClients.ts
@@ -0,0 +1,60 @@
+import {
+ AntigravitySettings,
+ ClaudeSettings,
+ CodexSettings,
+ isProviderDriverKind,
+ ProviderDriverKind,
+} from "@t3tools/contracts";
+import { acpRegistryClient } from "@t3tools/provider-acp-registry/client";
+import { makeProviderClientRegistry } from "@t3tools/provider-core/client";
+import { modelShortName } from "@t3tools/provider-core/modelShortName";
+import { cursorClient } from "@t3tools/provider-cursor/client";
+import { fxClient } from "@t3tools/provider-fx/client";
+import { grokClient } from "@t3tools/provider-grok/client";
+import { museClient } from "@t3tools/provider-muse/client";
+import { openCodeClient } from "@t3tools/provider-opencode/client";
+import { piClient } from "@t3tools/provider-pi/client";
+
+export const providerClients = makeProviderClientRegistry([
+ {
+ driverKind: ProviderDriverKind.make("codex"),
+ label: "Codex",
+ modelFamilies: ["GPT-", "GPT "],
+ acpRegistryAgentIds: ["codex-acp"],
+ settingsSchema: CodexSettings,
+ },
+ {
+ driverKind: ProviderDriverKind.make("claudeAgent"),
+ label: "Claude",
+ modelFamilies: ["Claude "],
+ acpRegistryAgentIds: ["claude-acp"],
+ settingsSchema: ClaudeSettings,
+ },
+ cursorClient,
+ grokClient,
+ fxClient,
+ openCodeClient,
+ {
+ driverKind: ProviderDriverKind.make("antigravity"),
+ label: "Antigravity",
+ modelFamilies: ["Gemini "],
+ acpRegistryAgentIds: ["antigravity-acp"],
+ settingsSchema: AntigravitySettings,
+ },
+ museClient,
+ piClient,
+ acpRegistryClient,
+]);
+
+export function getProviderClient(driver: string | null | undefined) {
+ return isProviderDriverKind(driver) ? providerClients.get(driver) : undefined;
+}
+
+export function resolveModelShortName(
+ model: { readonly name: string; readonly shortName?: string | undefined },
+ driverKind: ProviderDriverKind | null | undefined,
+): string | undefined {
+ if (model.shortName) return model.shortName;
+ const families = driverKind ? providerClients.get(driverKind)?.modelFamilies : undefined;
+ return modelShortName(model.name, families ?? []);
+}
diff --git a/packages/client-runtime/src/state/subagentDisplay.ts b/packages/client-runtime/src/state/subagentDisplay.ts
index d4d0db34e2ac..295e557fc8ff 100644
--- a/packages/client-runtime/src/state/subagentDisplay.ts
+++ b/packages/client-runtime/src/state/subagentDisplay.ts
@@ -6,6 +6,7 @@ import type {
} from "@t3tools/contracts";
import { formatModelSlugName, resolveSelectableModel } from "@t3tools/shared/model";
import { fileBasename } from "@t3tools/shared/path";
+import { resolveModelShortName } from "../providerClients.ts";
import { isTerminalSubagentStatus } from "./subagentRuntime.ts";
export function subagentGroupSummary(
@@ -70,7 +71,7 @@ export function resolveSubagentMetadata(input: {
: model;
const catalogModel = input.provider?.models.find((candidate) => candidate.slug === slug);
const reportedLabel = catalogModel
- ? catalogModel.shortName || catalogModel.name
+ ? (resolveModelShortName(catalogModel, input.provider?.driver) ?? catalogModel.name)
: model
? formatModelSlugName(model)
: "Not reported";
diff --git a/packages/provider-core/package.json b/packages/provider-core/package.json
index c45de9a9b149..ab93310316ac 100644
--- a/packages/provider-core/package.json
+++ b/packages/provider-core/package.json
@@ -7,6 +7,10 @@
"types": "./src/client.ts",
"import": "./src/client.ts"
},
+ "./modelShortName": {
+ "types": "./src/modelShortName.ts",
+ "import": "./src/modelShortName.ts"
+ },
"./server/adapterDriver": {
"types": "./src/server/adapterDriver.ts",
"import": "./src/server/adapterDriver.ts"
diff --git a/packages/provider-core/src/client.ts b/packages/provider-core/src/client.ts
index cba77d33a6ab..646c5e1871ef 100644
--- a/packages/provider-core/src/client.ts
+++ b/packages/provider-core/src/client.ts
@@ -36,6 +36,8 @@ export interface ProviderClientDefinition {
readonly driverKind: ProviderDriverKind;
readonly label: string;
readonly icon?: ProviderIcon;
+ /** Model-name family prefixes the provider's own icon already shows, e.g. "Claude ". */
+ readonly modelFamilies?: ReadonlyArray;
readonly settingsSchema: ProviderSettingsSchema;
readonly environmentFields?: ReadonlyArray;
readonly hasDefaultInstance?: boolean;
diff --git a/packages/provider-core/src/modelShortName.test.ts b/packages/provider-core/src/modelShortName.test.ts
new file mode 100644
index 000000000000..b58e2b32e935
--- /dev/null
+++ b/packages/provider-core/src/modelShortName.test.ts
@@ -0,0 +1,18 @@
+import { describe, expect, it } from "vite-plus/test";
+
+import { modelShortName } from "./modelShortName.ts";
+
+describe("modelShortName", () => {
+ it.each([
+ ["Claude Opus 5.5", ["Claude "], "Opus 5.5"],
+ ["GPT-6.1-Sol", ["GPT-"], "6.1 Sol"],
+ ["GPT 6.0 Astra", ["GPT-", "GPT "], "6.0 Astra"],
+ ["Grok 4 Pro", [], undefined],
+ ["Claude Opus 5.5", [], "Opus 5.5"],
+ ["NVIDIA: Nemotron 3 Super", [], "Nemotron 3 Super"],
+ ["GPT-5.5", ["GPT-"], undefined],
+ ["Gemini 3.1 Pro", ["Gemini "], "3.1 Pro"],
+ ] as const)("gives %s on families %j the short name %s", (name, families, expected) => {
+ expect(modelShortName(name, families)).toBe(expected);
+ });
+});
diff --git a/packages/provider-core/src/modelShortName.ts b/packages/provider-core/src/modelShortName.ts
new file mode 100644
index 000000000000..1be593e6eb85
--- /dev/null
+++ b/packages/provider-core/src/modelShortName.ts
@@ -0,0 +1,71 @@
+const FAMILY_PREFIXES = ["Claude ", "GPT-", "GPT ", "Gemini ", "Muse ", "Grok "] as const;
+
+// Words that describe a variant within a family rather than name a model. Off the
+// family's native provider there is no icon saying which family "4 Pro" belongs to.
+const VARIANT_WORDS = new Set([
+ "pro",
+ "flash",
+ "mini",
+ "max",
+ "lite",
+ "nano",
+ "ultra",
+ "plus",
+ "fast",
+ "turbo",
+ "preview",
+ "latest",
+ "thinking",
+ "reasoning",
+ "build",
+ "code",
+ "image",
+ "vision",
+ "audio",
+ "live",
+ "custom",
+ "tools",
+ "exp",
+ "experimental",
+ "chat",
+ "tts",
+ "stt",
+ "non",
+ "transcribe",
+]);
+
+const VERSION = /^v?\d+(?:\.\d+)*[a-z]?$/i;
+
+const VENDOR_PREFIX = /^[A-Z][\w.&\- ]{0,24}: (?=\S)/;
+
+export function modelShortName(
+ name: string,
+ nativeFamilies: ReadonlyArray,
+): string | undefined {
+ const unprefixed = name.replace(VENDOR_PREFIX, "");
+ const short =
+ withoutFamily(unprefixed, nativeFamilies) ?? (namesModel(unprefixed) ? unprefixed : name);
+ return short === name ? undefined : short;
+}
+
+function withoutFamily(name: string, nativeFamilies: ReadonlyArray): string | undefined {
+ const prefix = FAMILY_PREFIXES.find((candidate) => name.startsWith(candidate));
+ if (!prefix) return undefined;
+ const rest = name.slice(prefix.length).trim();
+ const keep = nativeFamilies.includes(prefix)
+ ? nonVersionWords(rest).length > 0
+ : namesModel(rest);
+ if (!keep) return undefined;
+ return /\s/.test(rest) ? rest : rest.replaceAll("-", " ");
+}
+
+function nonVersionWords(text: string): ReadonlyArray {
+ return text
+ .replace(/\(.*?\)/g, " ")
+ .split(/[\s-]+/)
+ .filter((word) => word.length > 0 && !VERSION.test(word));
+}
+
+function namesModel(text: string): boolean {
+ return nonVersionWords(text).some((word) => !VARIANT_WORDS.has(word.toLowerCase()));
+}
diff --git a/packages/provider-cursor/src/client.ts b/packages/provider-cursor/src/client.ts
index 158133d54c82..720b19de9542 100644
--- a/packages/provider-cursor/src/client.ts
+++ b/packages/provider-cursor/src/client.ts
@@ -6,6 +6,7 @@ import { CursorSettings } from "./settings.ts";
export const cursorClient = defineProviderClient({
driverKind: ProviderDriverKind.make("cursor"),
label: "Cursor",
+ modelFamilies: ["Grok "],
acpRegistryAgentIds: ["cursor"],
settingsSchema: CursorSettings,
environmentFields: [
diff --git a/packages/provider-grok/src/client.ts b/packages/provider-grok/src/client.ts
index 42a4e7fbc177..d9df069b68d8 100644
--- a/packages/provider-grok/src/client.ts
+++ b/packages/provider-grok/src/client.ts
@@ -6,6 +6,7 @@ import { GrokSettings } from "./settings.ts";
export const grokClient = defineProviderClient({
driverKind: ProviderDriverKind.make("grok"),
label: "Grok",
+ modelFamilies: ["Grok "],
acpRegistryAgentIds: ["grok-build"],
settingsSchema: GrokSettings,
icon: {
diff --git a/packages/provider-muse/src/client.ts b/packages/provider-muse/src/client.ts
index 2d35baf20a9c..ff2d1a1ac100 100644
--- a/packages/provider-muse/src/client.ts
+++ b/packages/provider-muse/src/client.ts
@@ -6,6 +6,7 @@ import { MuseSettings } from "./settings.ts";
export const museClient = defineProviderClient({
driverKind: ProviderDriverKind.make("muse"),
label: "Muse Code",
+ modelFamilies: ["Muse "],
settingsSchema: MuseSettings,
icon: {
viewBox: "0 0 287.56 191",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ae138a46cd08..0f997881d1ba 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -579,30 +579,6 @@ importers:
'@t3tools/mobile-terminal-native':
specifier: file:./modules/t3-terminal
version: file:apps/mobile/modules/t3-terminal
- '@t3tools/provider-acp-registry':
- specifier: workspace:*
- version: link:../../packages/provider-acp-registry
- '@t3tools/provider-core':
- specifier: workspace:*
- version: link:../../packages/provider-core
- '@t3tools/provider-cursor':
- specifier: workspace:*
- version: link:../../packages/provider-cursor
- '@t3tools/provider-fx':
- specifier: workspace:*
- version: link:../../packages/provider-fx
- '@t3tools/provider-grok':
- specifier: workspace:*
- version: link:../../packages/provider-grok
- '@t3tools/provider-muse':
- specifier: workspace:*
- version: link:../../packages/provider-muse
- '@t3tools/provider-opencode':
- specifier: workspace:*
- version: link:../../packages/provider-opencode
- '@t3tools/provider-pi':
- specifier: workspace:*
- version: link:../../packages/provider-pi
'@t3tools/shared':
specifier: workspace:*
version: link:../../packages/shared
@@ -1061,30 +1037,9 @@ importers:
'@t3tools/contracts':
specifier: workspace:*
version: link:../../packages/contracts
- '@t3tools/provider-acp-registry':
- specifier: workspace:*
- version: link:../../packages/provider-acp-registry
'@t3tools/provider-core':
specifier: workspace:*
version: link:../../packages/provider-core
- '@t3tools/provider-cursor':
- specifier: workspace:*
- version: link:../../packages/provider-cursor
- '@t3tools/provider-fx':
- specifier: workspace:*
- version: link:../../packages/provider-fx
- '@t3tools/provider-grok':
- specifier: workspace:*
- version: link:../../packages/provider-grok
- '@t3tools/provider-muse':
- specifier: workspace:*
- version: link:../../packages/provider-muse
- '@t3tools/provider-opencode':
- specifier: workspace:*
- version: link:../../packages/provider-opencode
- '@t3tools/provider-pi':
- specifier: workspace:*
- version: link:../../packages/provider-pi
'@t3tools/shared':
specifier: workspace:*
version: link:../../packages/shared
@@ -1444,6 +1399,30 @@ importers:
'@t3tools/contracts':
specifier: workspace:*
version: link:../contracts
+ '@t3tools/provider-acp-registry':
+ specifier: workspace:*
+ version: link:../provider-acp-registry
+ '@t3tools/provider-core':
+ specifier: workspace:*
+ version: link:../provider-core
+ '@t3tools/provider-cursor':
+ specifier: workspace:*
+ version: link:../provider-cursor
+ '@t3tools/provider-fx':
+ specifier: workspace:*
+ version: link:../provider-fx
+ '@t3tools/provider-grok':
+ specifier: workspace:*
+ version: link:../provider-grok
+ '@t3tools/provider-muse':
+ specifier: workspace:*
+ version: link:../provider-muse
+ '@t3tools/provider-opencode':
+ specifier: workspace:*
+ version: link:../provider-opencode
+ '@t3tools/provider-pi':
+ specifier: workspace:*
+ version: link:../provider-pi
'@t3tools/shared':
specifier: workspace:*
version: link:../shared