Skip to content
Draft
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
3 changes: 3 additions & 0 deletions apps/code/src/main/services/agent/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const sessionConfigSchema = z.object({
export type SessionConfig = z.infer<typeof sessionConfigSchema>;

// Start session input/output
const codexServiceTierSchema = z.enum(["standard", "fast", "flex"]);

export const startSessionInput = z.object({
taskId: z.string(),
Expand All @@ -50,6 +51,7 @@ export const startSessionInput = z.object({
customInstructions: z.string().max(2000).optional(),
effort: effortLevelSchema.optional(),
model: z.string().optional(),
serviceTier: codexServiceTierSchema.optional(),
jsonSchema: z.record(z.string(), z.unknown()).nullish(),
});

Expand Down Expand Up @@ -174,6 +176,7 @@ export const reconnectSessionInput = z.object({
permissionMode: z.string().optional(),
customInstructions: z.string().max(2000).optional(),
effort: effortLevelSchema.optional(),
serviceTier: codexServiceTierSchema.optional(),
jsonSchema: z.record(z.string(), z.unknown()).nullish(),
});

Expand Down
35 changes: 34 additions & 1 deletion apps/code/src/main/services/agent/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,8 @@ interface SessionConfig {
effort?: EffortLevel;
/** Model to use for the session (e.g. "claude-sonnet-4-6") */
model?: string;
/** Codex service tier, e.g. "standard" or "fast" */
serviceTier?: string;
/** JSON Schema for structured task output — when set, the agent gets a create_output tool */
jsonSchema?: Record<string, unknown> | null;
}
Expand Down Expand Up @@ -536,6 +538,7 @@ When creating pull requests, add the following footer at the end of the PR descr
customInstructions,
effort,
model,
serviceTier,
jsonSchema,
} = config;

Expand Down Expand Up @@ -599,6 +602,7 @@ When creating pull requests, add the following footer at the end of the PR descr
codexBinaryPath:
adapter === "codex" ? this.getCodexBinaryPath() : undefined,
model,
serviceTier,
instructions: adapter === "codex" ? systemPrompt.append : undefined,
onStructuredOutput: jsonSchema
? async (output) => {
Expand Down Expand Up @@ -1502,6 +1506,7 @@ For git operations while detached:
"customInstructions" in params ? params.customInstructions : undefined,
effort: "effort" in params ? params.effort : undefined,
model: "model" in params ? params.model : undefined,
serviceTier: "serviceTier" in params ? params.serviceTier : undefined,
jsonSchema: "jsonSchema" in params ? params.jsonSchema : undefined,
};
}
Expand Down Expand Up @@ -1767,10 +1772,38 @@ For git operations while detached:
currentValue: resolvedModelId,
options: modelOptions,
category: "model",
description: "Choose which model Claude should use",
description: `Choose which model ${adapter === "codex" ? "Codex" : "Claude"} should use`,
},
];

if (adapter === "codex") {
configOptions.push({
id: "service_tier",
name: "Speed",
type: "select",
currentValue: "standard",
options: [
{
value: "standard",
name: "Standard",
description: "Default Codex service tier",
},
{
value: "fast",
name: "Fast",
description: "Request Codex fast mode for lower latency",
},
{
value: "flex",
name: "Flex",
description: "Request Codex flex mode",
},
],
category: "service_tier",
description: "Choose the Codex service tier for new turns",
});
}

const effortOpts = getReasoningEffortOptions(adapter, resolvedModelId);
if (effortOpts) {
configOptions.push({
Expand Down
4 changes: 4 additions & 0 deletions apps/code/src/renderer/api/posthogClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ describe("PostHogAPIClient", () => {
adapter: "codex",
model: "gpt-5.4",
reasoningLevel: "high",
serviceTier: "fast",
});

expect(post).toHaveBeenCalledWith(
Expand All @@ -37,6 +38,7 @@ describe("PostHogAPIClient", () => {
runtime_adapter: "codex",
model: "gpt-5.4",
reasoning_effort: "high",
service_tier: "fast",
}),
}),
);
Expand Down Expand Up @@ -154,6 +156,7 @@ describe("PostHogAPIClient", () => {
adapter: "codex",
model: "gpt-5.4",
reasoningLevel: "high",
serviceTier: "fast",
initialPermissionMode: "auto",
}),
).resolves.toEqual({ id: "run-123", environment: "cloud" });
Expand All @@ -169,6 +172,7 @@ describe("PostHogAPIClient", () => {
runtime_adapter: "codex",
model: "gpt-5.4",
reasoning_effort: "high",
service_tier: "fast",
initial_permission_mode: "auto",
environment: "cloud",
}),
Expand Down
5 changes: 5 additions & 0 deletions apps/code/src/renderer/api/posthogClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,13 @@ export interface FinalizedTaskArtifactUpload {
}

type CloudRuntimeAdapter = "claude" | "codex";
type CloudRunServiceTier = "standard" | "fast" | "flex";

interface CloudRunOptions {
adapter?: CloudRuntimeAdapter;
model?: string;
reasoningLevel?: string;
serviceTier?: CloudRunServiceTier;
sandboxEnvironmentId?: string;
prAuthorshipMode?: PrAuthorshipMode;
runSource?: CloudRunSource;
Expand Down Expand Up @@ -233,6 +235,9 @@ function buildCloudRunRequestBody(
if (options?.sandboxEnvironmentId) {
body.sandbox_environment_id = options.sandboxEnvironmentId;
}
if (options?.serviceTier) {
body.service_tier = options.serviceTier;
}
if (options?.prAuthorshipMode) {
body.pr_authorship_mode = options.prAuthorshipMode;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export interface PromptInputProps {
enableCommands?: boolean;
// toolbar slots
modelSelector?: React.ReactElement | null | false;
speedSelector?: React.ReactElement | null | false;
reasoningSelector?: React.ReactElement | null | false;
historyButton?: React.ReactNode;
// prompt history provider
Expand Down Expand Up @@ -80,6 +81,7 @@ export const PromptInput = forwardRef<EditorHandle, PromptInputProps>(
enableBashMode = false,
enableCommands = true,
modelSelector,
speedSelector,
reasoningSelector,
historyButton,
getPromptHistory,
Expand Down Expand Up @@ -318,6 +320,7 @@ export const PromptInput = forwardRef<EditorHandle, PromptInputProps>(
/>
)}
{modelSelector && <span>{modelSelector}</span>}
{speedSelector && <span>{speedSelector}</span>}
{reasoningSelector && <span>{reasoningSelector}</span>}
{isBashMode && (
<Text className="font-mono text-(--blue-9) text-[13px]">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { SessionConfigOption } from "@agentclientprotocol/sdk";
import { Tooltip } from "@components/ui/Tooltip";
import { Lightning } from "@phosphor-icons/react";
import { Button } from "@posthog/quill";
import { flattenSelectOptions } from "../stores/sessionStore";

interface ServiceTierSelectorProps {
serviceTierOption?: SessionConfigOption;
onChange?: (value: string) => void;
disabled?: boolean;
}

export function ServiceTierSelector({
serviceTierOption,
onChange,
disabled,
}: ServiceTierSelectorProps) {
if (!serviceTierOption || serviceTierOption.type !== "select") {
return null;
}

const options = flattenSelectOptions(serviceTierOption.options);
const supportsFastMode = options.some((opt) => opt.value === "fast");
if (!supportsFastMode) return null;

const activeTier = serviceTierOption.currentValue;
const fastModeEnabled = activeTier === "fast";

const handleClick = () => {
onChange?.(fastModeEnabled ? "standard" : "fast");
};

return (
<Tooltip content="1.5x speed, increased usage" side="top">
<span className="inline-flex">
<Button
type="button"
variant="default"
size="sm"
disabled={disabled}
aria-label="Fast Mode"
aria-pressed={fastModeEnabled}
className={
fastModeEnabled
? "border-(--amber-7) bg-(--amber-3) text-amber-11 hover:bg-(--amber-4)"
: "text-muted-foreground"
}
onClick={handleClick}
>
<Lightning size={14} weight={fastModeEnabled ? "fill" : "regular"} />
</Button>
</span>
</Tooltip>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
useAdapterForTask,
useModeConfigOptionForTask,
usePendingPermissionsForTask,
useServiceTierConfigOptionForTask,
useThoughtLevelConfigOptionForTask,
} from "@features/sessions/stores/sessionStore";
import type { Plan } from "@features/sessions/types";
Expand Down Expand Up @@ -41,6 +42,7 @@ import { ModelSelector } from "./ModelSelector";
import { PlanStatusBar } from "./PlanStatusBar";
import { ReasoningLevelSelector } from "./ReasoningLevelSelector";
import { RawLogsView } from "./raw-logs/RawLogsView";
import { ServiceTierSelector } from "./ServiceTierSelector";

interface SessionViewProps {
events: AcpMessage[];
Expand Down Expand Up @@ -129,6 +131,7 @@ export function SessionView({
const pendingPermissions = usePendingPermissionsForTask(taskId);
const modeOption = useModeConfigOptionForTask(taskId);
const thoughtOption = useThoughtLevelConfigOptionForTask(taskId);
const serviceTierOption = useServiceTierConfigOptionForTask(taskId);
const adapter = useAdapterForTask(taskId);
const { allowBypassPermissions } = useSettingsStore();
const currentModeId = modeOption?.currentValue;
Expand Down Expand Up @@ -177,6 +180,18 @@ export function SessionView({
[taskId, thoughtOption],
);

const handleServiceTierChange = useCallback(
(value: string) => {
if (!taskId || !serviceTierOption) return;
getSessionService().setSessionConfigOption(
taskId,
serviceTierOption.id,
value,
);
},
[taskId, serviceTierOption],
);

const sessionId = taskId ?? "default";
const setContext = useDraftStore((s) => s.actions.setContext);
const requestFocus = useDraftStore((s) => s.actions.requestFocus);
Expand Down Expand Up @@ -645,6 +660,15 @@ export function SessionView({
/>
) : null
}
speedSelector={
adapter === "codex" && serviceTierOption ? (
<ServiceTierSelector
serviceTierOption={serviceTierOption}
onChange={handleServiceTierChange}
disabled={!isRunning}
/>
) : null
}
onBeforeSubmit={onBeforeSubmit}
onSubmit={handleSubmit}
onBashCommand={onBashCommand}
Expand Down
7 changes: 7 additions & 0 deletions apps/code/src/renderer/features/sessions/hooks/useSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,13 @@ export const useThoughtLevelConfigOptionForTask = (
return useConfigOptionForTask(taskId, "thought_level");
};

/** Get the service tier config option for a task */
export const useServiceTierConfigOptionForTask = (
taskId: string | undefined,
): SessionConfigOption | undefined => {
return useConfigOptionForTask(taskId, "service_tier");
};

/** Get the adapter type for a task */
export const useAdapterForTask = (
taskId: string | undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2489,6 +2489,14 @@ describe("SessionService", () => {
currentValue: "high",
options: [],
},
{
id: "service_tier",
name: "Speed",
type: "select",
category: "service_tier",
currentValue: "fast",
options: [],
},
],
}),
);
Expand Down Expand Up @@ -2553,6 +2561,7 @@ describe("SessionService", () => {
adapter: "codex",
model: "gpt-5.4",
reasoningLevel: "high",
serviceTier: "fast",
resumeFromRunId: "run-123",
}),
);
Expand Down
Loading
Loading