Files
llmx/codex-cli/src/components/model-overlay.tsx
Daniel Nakov eafbc75612 feat: support multiple providers via Responses-Completion transformation (#247)
https://github.com/user-attachments/assets/9ecb51be-fa65-4e99-8512-abb898dda569

Implemented it as a transformation between Responses API and Completion
API so that it supports existing providers that implement the Completion
API and minimizes the changes needed to the codex repo.

---------

Co-authored-by: Thibault Sottiaux <tibo@openai.com>
Co-authored-by: Fouad Matin <169186268+fouad-openai@users.noreply.github.com>
Co-authored-by: Fouad Matin <fouad@openai.com>
2025-04-20 20:59:34 -07:00

160 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import TypeaheadOverlay from "./typeahead-overlay.js";
import {
getAvailableModels,
RECOMMENDED_MODELS as _RECOMMENDED_MODELS,
} from "../utils/model-utils.js";
import { providers } from "../utils/providers.js";
import { Box, Text, useInput } from "ink";
import React, { useEffect, useState } from "react";
/**
* Props for <ModelOverlay>.
*
* When `hasLastResponse` is true the user has already received at least one
* assistant response in the current session which means switching models is no
* longer supported the overlay should therefore show an error and only allow
* the user to close it.
*/
type Props = {
currentModel: string;
currentProvider?: string;
hasLastResponse: boolean;
onSelect: (model: string) => void;
onSelectProvider?: (provider: string) => void;
onExit: () => void;
};
export default function ModelOverlay({
currentModel,
currentProvider = "openai",
hasLastResponse,
onSelect,
onSelectProvider,
onExit,
}: Props): JSX.Element {
const [items, setItems] = useState<Array<{ label: string; value: string }>>(
[],
);
const [providerItems, _setProviderItems] = useState<
Array<{ label: string; value: string }>
>(Object.values(providers).map((p) => ({ label: p.name, value: p.name })));
const [mode, setMode] = useState<"model" | "provider">("model");
const [isLoading, setIsLoading] = useState<boolean>(true);
// This effect will run when the provider changes to update the model list
useEffect(() => {
setIsLoading(true);
(async () => {
try {
const models = await getAvailableModels(currentProvider);
// Convert the models to the format needed by TypeaheadOverlay
setItems(
models.map((m) => ({
label: m,
value: m,
})),
);
} catch (error) {
// Silently handle errors - remove console.error
// console.error("Error loading models:", error);
} finally {
setIsLoading(false);
}
})();
}, [currentProvider]);
// ---------------------------------------------------------------------------
// If the conversation already contains a response we cannot change the model
// anymore because the backend requires a consistent model across the entire
// run. In that scenario we replace the regular typeahead picker with a
// simple message instructing the user to start a new chat. The only
// available action is to dismiss the overlay (Esc or Enter).
// ---------------------------------------------------------------------------
// Register input handling for switching between model and provider selection
useInput((_input, key) => {
if (hasLastResponse && (key.escape || key.return)) {
onExit();
} else if (!hasLastResponse) {
if (key.tab) {
setMode(mode === "model" ? "provider" : "model");
}
}
});
if (hasLastResponse) {
return (
<Box
flexDirection="column"
borderStyle="round"
borderColor="gray"
width={80}
>
<Box paddingX={1}>
<Text bold color="red">
Unable to switch model
</Text>
</Box>
<Box paddingX={1}>
<Text>
You can only pick a model before the assistant sends its first
response. To use a different model please start a new chat.
</Text>
</Box>
<Box paddingX={1}>
<Text dimColor>press esc or enter to close</Text>
</Box>
</Box>
);
}
if (mode === "provider") {
return (
<TypeaheadOverlay
title="Select provider"
description={
<Box flexDirection="column">
<Text>
Current provider:{" "}
<Text color="greenBright">{currentProvider}</Text>
</Text>
<Text dimColor>press tab to switch to model selection</Text>
</Box>
}
initialItems={providerItems}
currentValue={currentProvider}
onSelect={(provider) => {
if (onSelectProvider) {
onSelectProvider(provider);
// Immediately switch to model selection so user can pick a model for the new provider
setMode("model");
}
}}
onExit={onExit}
/>
);
}
return (
<TypeaheadOverlay
title="Select model"
description={
<Box flexDirection="column">
<Text>
Current model: <Text color="greenBright">{currentModel}</Text>
</Text>
<Text>
Current provider: <Text color="greenBright">{currentProvider}</Text>
</Text>
{isLoading && <Text color="yellow">Loading models...</Text>}
<Text dimColor>press tab to switch to provider selection</Text>
</Box>
}
initialItems={items}
currentValue={currentModel}
onSelect={onSelect}
onExit={onExit}
/>
);
}