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
7 changes: 7 additions & 0 deletions apps/web/src/components/Icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ import React, { type SVGProps, useId } from "react";
import { cn } from "~/lib/utils";
export type Icon = React.FC<SVGProps<SVGSVGElement>>;

export const UltrafastIcon: Icon = (props) => (
<svg viewBox="0 0 24 24" fill="currentColor" {...props}>
<path d="m17 2-10 12h7l-1 8 10-12h-7l1-8Z" opacity="0.4" />
<path d="m11 2-10 12h7l-1 8 10-12h-7l1-8Z" />
</svg>
);

export const FinderIcon: Icon = (props) => (
<svg viewBox="0 0 24 24" fill="none" {...props}>
<rect x="2" y="2" width="20" height="20" rx="4" fill="#36A9F5" />
Expand Down
78 changes: 75 additions & 3 deletions apps/web/src/components/chat/SubagentTooltipContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ import type {
OrchestrationProjectShell,
ServerProvider,
ProviderDriverKind,
ProviderInstanceId,
OrchestrationV2Subagent,
} from "@t3tools/contracts";
import {
resolveSubagentMetadata,
subagentDetailPreview,
} from "@t3tools/client-runtime/state/subagent-display";
import { getModelSelectionStringOptionValue, resolveSelectableModel } from "@t3tools/shared/model";
import { getTraitsSpeedDisplay, TraitsSpeedIcon } from "./TraitsSpeed";
import type { ReactNode } from "react";
import {
BotIcon,
Expand All @@ -29,13 +33,17 @@ import { deriveProviderInstanceEntries, shouldShowInstanceBadge } from "../../pr
export function SubagentTooltipContent(props: {
title: string;
model: string | null;
providerInstanceId: ProviderInstanceId;
origin: OrchestrationV2Subagent["origin"];
provider?: ServerProvider | undefined;
/** The environment's instances; with several accounts on one provider, the card names this one. */
providers?: ReadonlyArray<ServerProvider> | undefined;
driver?: ProviderDriverKind | undefined;
elapsed?: ReactNode;
parentThread?: Pick<OrchestrationV2ThreadShell, "projectId" | "worktreePath"> | undefined;
childThread?: Pick<OrchestrationV2ThreadShell, "branch" | "worktreePath"> | undefined;
childThread?:
| Pick<OrchestrationV2ThreadShell, "branch" | "worktreePath" | "modelSelection">
| undefined;
parentProject?: Pick<OrchestrationProjectShell, "workspaceRoot"> | undefined;
childProject?: Pick<OrchestrationProjectShell, "id" | "title" | "workspaceRoot"> | undefined;
status: OrchestrationV2TurnItemStatus;
Expand All @@ -44,6 +52,59 @@ export function SubagentTooltipContent(props: {
}) {
const { modelLabel, workspace: metadata } = resolveSubagentMetadata(props);
const preview = subagentDetailPreview(props);
const model = props.model?.trim();
const modelSlug = props.provider
? resolveSelectableModel(props.provider.driver, model, props.provider.models)
: model;
const providerModel = props.provider?.models.find((candidate) => candidate.slug === modelSlug);
const childSelection = props.childThread?.modelSelection;
const childModel = props.provider
? (resolveSelectableModel(
props.provider.driver,
childSelection?.model,
props.provider.models,
) ?? childSelection?.model.trim())
: childSelection?.model.trim();
const provider = props.provider;
const matchingSelection =
props.origin === "app_owned" &&
childModel === (modelSlug ?? model) &&
childSelection?.instanceId === props.providerInstanceId
? childSelection
: undefined;
const effort = ["reasoningEffort", "effort", "reasoning", "variant"]
.map((id) => getModelSelectionStringOptionValue(matchingSelection, id))
.find(Boolean);
const speed = provider
? providerModel?.capabilities?.optionDescriptors
?.map((descriptor) => {
const saved = matchingSelection?.options?.find((option) => option.id === descriptor.id);
if (
descriptor.id === "fastMode" &&
descriptor.type === "boolean" &&
typeof saved?.value === "boolean"
) {
return getTraitsSpeedDisplay(provider.driver, {
...descriptor,
currentValue: saved.value,
});
}
if (
provider.driver === "codex" &&
descriptor.id === "serviceTier" &&
descriptor.type === "select" &&
typeof saved?.value === "string" &&
descriptor.options.some((option) => option.id === saved.value)
) {
return getTraitsSpeedDisplay(provider.driver, {
...descriptor,
currentValue: saved.value,
});
}
return null;
})
.find(Boolean)
: undefined;
const driver = props.provider?.driver ?? props.driver;
const entries = deriveProviderInstanceEntries(props.providers ?? []);
const entry = entries.find((candidate) => candidate.instanceId === props.provider?.instanceId);
Expand Down Expand Up @@ -75,8 +136,19 @@ export function SubagentTooltipContent(props: {
) : (
<BotIcon className="size-3 shrink-0" />
)}
<span className="min-w-0 truncate text-foreground/75">
{showInstanceBadge ? `${modelLabel} · ${entry.displayName}` : modelLabel}
<span className="inline-flex min-w-0 items-center gap-1 text-foreground/75">
<span className="min-w-0 truncate">
{showInstanceBadge ? `${modelLabel} · ${entry.displayName}` : modelLabel}
</span>
{effort || speed?.speedIcon ? (
Comment thread
Bil0000 marked this conversation as resolved.
<span className="inline-flex shrink-0 items-center gap-1">
{effort ? " · " : null}
{speed?.speedIcon && provider ? (
<TraitsSpeedIcon provider={provider.driver} speedIcon={speed.speedIcon} size="xs" />
) : null}
{effort}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</span>
) : null}
</span>
</div>
<div className="flex min-w-0 items-center justify-between gap-4">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { act, cloneElement, type ReactElement, type ReactNode } from "react";
import { create, type ReactTestRenderer } from "react-test-renderer";
import { EnvironmentId, ThreadId, type OrchestrationV2ContextTransfer } from "@t3tools/contracts";
import { create, type ReactTestInstance, type ReactTestRenderer } from "react-test-renderer";
import {
EnvironmentId,
ThreadId,
type ModelSelection,
type ProviderOptionDescriptor,
type OrchestrationV2ContextTransfer,
} from "@t3tools/contracts";
import * as DateTime from "effect/DateTime";
import { afterEach, expect, it, vi } from "vite-plus/test";

Expand Down Expand Up @@ -196,7 +202,11 @@ it("shows readable models and only differing workspace details in agent tooltips
worktreePath: null as string | null,
branch: null as string | null,
title: "Worker",
modelSelection: { instanceId: "codex", model: "gpt-5.4" },
modelSelection: {
instanceId: "codex",
model: "gpt-5.4",
options: [{ id: "reasoningEffort", value: "high" }] as ModelSelection["options"],
},
lineage: { parentThreadId: "parent", relationshipToParent: "subagent" },
};
state.projects = [
Expand Down Expand Up @@ -231,6 +241,7 @@ it("shows readable models and only differing workspace details in agent tooltips
{
id: "agent",
childThreadId: "child",
origin: "app_owned",
driver: "codex",
providerInstanceId: "codex",
title: "Worker",
Expand All @@ -253,12 +264,24 @@ it("shows readable models and only differing workspace details in agent tooltips
await act(async () => {
renderer = create(panel);
});
const text = () =>
renderer.root
.findAll((node) => typeof node.type === "string")
.flatMap((node) => node.children.filter((child) => typeof child === "string"))
.join("");
const text = (visibleOnly = false) => {
const read = (node: ReactTestInstance | string): string => {
if (typeof node === "string") return node;
if (visibleOnly && node.props.className === "sr-only") return "";
return node.children.map(read).join("");
};
return read(renderer.root);
};
expect(text()).toContain("My GPT · high");
state.projection = {
...projection,
subagents: [{ ...projection.subagents[0], origin: "provider_native" }],
};
await act(async () => renderer.update(cloneElement(panel)));
expect(text()).toContain("My GPT");
expect(text()).not.toContain("My GPT · high");
state.projection = projection;
await act(async () => renderer.update(cloneElement(panel)));
expect(text()).not.toContain("Tokens");
expect(text()).not.toContain("Open subagent");
expect(text()).not.toContain("Project");
Expand All @@ -279,6 +302,11 @@ it("shows readable models and only differing workspace details in agent tooltips
};
await act(async () => renderer.update(cloneElement(panel)));
expect(text()).toContain(expected);
if (!model?.trim() || model === "gpt-5.5" || model === "custom/model-v1") {
expect(text()).not.toContain(" · high");
} else {
expect(text()).toContain(`${expected} · high`);
}
expect(text()).not.toContain("Unknown");
if (!model?.trim()) expect(text()).not.toContain("My GPT");
}
Expand All @@ -302,6 +330,179 @@ it("shows readable models and only differing workspace details in agent tooltips
expect(text()).not.toContain("My GPT");
}

state.projection = projection;
for (const [options, expected] of [
[[{ id: "effort", value: "max" }], " · max"],
[[{ id: "reasoning", value: "low" }], " · low"],
[[{ id: "variant", value: "high" }], " · high"],
[[{ id: "reasoningEffort", value: "none" }], " · none"],
[[{ id: "reasoningEffort", value: true }], ""],
[[{ id: "serviceTier", value: "fast" }], ""],
[[], ""],
[undefined, ""],
] as const) {
child.modelSelection.options = options;
state.shells = [{ environmentId: "test", source: { ...child } }];
await act(async () => renderer.update(cloneElement(panel)));
expect(text()).toContain(`My GPT${expected}`);
if (!expected) expect(text()).not.toContain("My GPT ·");
}
const speedConfig = state.configs.get("test");
const serviceTier: ProviderOptionDescriptor = {
id: "serviceTier",
label: "Service Tier",
type: "select",
currentValue: "priority",
options: [
{ id: "default", label: "Standard", isDefault: true },
{ id: "priority", label: "Fast" },
{ id: "ultrafast", label: "Ultrafast" },
{ id: "flex", label: "Flex" },
],
};
const fastMode: ProviderOptionDescriptor = {
id: "fastMode",
label: "Fast Mode",
type: "boolean",
currentValue: true,
};
for (const [driver, descriptor, value, iconLabel] of [
["codex", serviceTier, "default", ""],
["codex", serviceTier, "priority", "Fast mode on"],
["codex", serviceTier, "ultrafast", "Ultrafast mode on"],
["codex", serviceTier, "flex", ""],
["codex", serviceTier, "unknown", ""],
[
"codex",
{ ...serviceTier, options: serviceTier.options.filter(({ id }) => id !== "ultrafast") },
"ultrafast",
"",
],
["codex", serviceTier, true, ""],
["codex", serviceTier, undefined, ""],
["claudeAgent", fastMode, true, "Fast mode on"],
["claudeAgent", fastMode, false, ""],
["cursor", fastMode, true, "Fast mode on"],
["cursor", fastMode, false, ""],
["opencode", fastMode, true, "Fast mode on"],
["cursor", fastMode, "true", ""],
["cursor", serviceTier, "priority", ""],
] as const) {
state.configs.set("test", {
providers: [
{
instanceId: "codex",
driver,
models: [
{
slug: "gpt-5.4",
name: "My GPT",
capabilities: { optionDescriptors: [descriptor] },
},
],
},
],
});
child.modelSelection.options = [
{ id: "reasoningEffort", value: "high" },
...(value === undefined ? [] : [{ id: descriptor.id, value }]),
];
state.shells = [{ environmentId: "test", source: { ...child } }];
await act(async () => renderer.update(cloneElement(panel)));
expect(text(true)).toContain("My GPT · high");
expect(text(true)).not.toMatch(/Fast|Ultrafast|Normal|Standard|Flex/);
expect(text()).toContain(`My GPT · ${iconLabel}high`);
if (!iconLabel) expect(text()).not.toContain("mode on");
child.modelSelection.options = child.modelSelection.options.filter(
({ id }) => id !== "reasoningEffort",
);
state.shells = [{ environmentId: "test", source: { ...child } }];
await act(async () => renderer.update(cloneElement(panel)));
expect(text()).not.toContain(" · high");
expect(text(true)).not.toMatch(/Fast|Ultrafast|Normal|Standard|Flex/);
if (iconLabel) expect(text()).toContain(iconLabel);
else expect(text()).not.toContain("mode on");
state.projection = {
...projection,
subagents: [{ ...projection.subagents[0], origin: "provider_native" }],
};
await act(async () => renderer.update(cloneElement(panel)));
expect(text()).not.toMatch(/Fast|Ultrafast|Normal|Standard|Flex| · high/);
state.projection = projection;
for (const modelSelection of [
{ ...child.modelSelection, instanceId: "other" },
{ ...child.modelSelection, model: "gpt-5.5" },
]) {
state.shells = [{ environmentId: "test", source: { ...child, modelSelection } }];
await act(async () => renderer.update(cloneElement(panel)));
expect(text()).not.toMatch(/Fast|Ultrafast|Normal|Standard|Flex| · high/);
}
}
state.configs.set("test", {
providers: [
{
instanceId: "codex",
driver: "codex",
displayName: "Work account",
models: [
{
slug: "gpt-5.4",
name: "My GPT",
capabilities: { optionDescriptors: [serviceTier] },
},
],
},
{
instanceId: "codex_personal",
driver: "codex",
displayName: "Personal account",
models: [],
},
],
});
child.modelSelection.options = [
{ id: "reasoningEffort", value: "high" },
{ id: "serviceTier", value: "priority" },
];
state.shells = [{ environmentId: "test", source: { ...child } }];
await act(async () => renderer.update(cloneElement(panel)));
expect(text(true)).toContain("My GPT · Work account · high");
expect(text()).toContain("My GPT · Work account · Fast mode onhigh");
expect(text()).not.toContain("Personal account");
state.configs.set("test", speedConfig);
child.modelSelection.options = [{ id: "reasoningEffort", value: "high" }];
state.shells = [
{
environmentId: "test",
source: { ...child, modelSelection: { ...child.modelSelection, instanceId: "other" } },
},
];
await act(async () => renderer.update(cloneElement(panel)));
expect(text()).not.toContain("My GPT ·");
const config = state.configs.get("test");
state.configs.clear();
await act(async () => renderer.update(cloneElement(panel)));
expect(text()).toContain("GPT-5.4");
expect(text()).not.toContain("GPT-5.4 ·");
state.shells = [{ environmentId: "test", source: { ...child } }];
await act(async () => renderer.update(cloneElement(panel)));
expect(text()).toContain("GPT-5.4 · high");
state.configs.set("test", config);
for (const model of ["custom/model-v1", "custom/model-v2"]) {
state.projection = {
...projection,
subagents: [{ ...projection.subagents[0], model }],
};
state.shells = [
{
environmentId: "test",
source: { ...child, modelSelection: { ...child.modelSelection, model: "custom/model-v1" } },
},
];
await act(async () => renderer.update(cloneElement(panel)));
expect(text().includes(`${model} · high`)).toBe(model === "custom/model-v1");
}

state.projection = {
...projection,
subagents: [
Expand Down Expand Up @@ -364,6 +565,7 @@ it("shows readable models and only differing workspace details in agent tooltips
state.configs.clear();
await act(async () => renderer.update(cloneElement(panel)));
expect(text()).toContain("GPT-5.4");
expect(text()).not.toContain("GPT-5.4 ·");
expect(text()).not.toContain("gpt-5.4");
expect(text()).not.toContain("Project");
expect(text()).not.toContain("Workspace");
Expand Down
Loading
Loading