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>
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import TypeaheadOverlay from "./typeahead-overlay.js";
|
||||
import {
|
||||
getAvailableModels,
|
||||
RECOMMENDED_MODELS,
|
||||
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";
|
||||
|
||||
@@ -16,39 +17,51 @@ import React, { useEffect, useState } from "react";
|
||||
*/
|
||||
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 () => {
|
||||
const models = await getAvailableModels();
|
||||
|
||||
// Split the list into recommended and “other” models.
|
||||
const recommended = RECOMMENDED_MODELS.filter((m) => models.includes(m));
|
||||
const others = models.filter((m) => !recommended.includes(m));
|
||||
|
||||
const ordered = [...recommended, ...others.sort()];
|
||||
|
||||
setItems(
|
||||
ordered.map((m) => ({
|
||||
label: recommended.includes(m) ? `⭐ ${m}` : m,
|
||||
value: m,
|
||||
})),
|
||||
);
|
||||
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
|
||||
@@ -58,10 +71,14 @@ export default function ModelOverlay({
|
||||
// available action is to dismiss the overlay (Esc or Enter).
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Always register input handling so hooks are called consistently.
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -91,13 +108,47 @@ export default function ModelOverlay({
|
||||
);
|
||||
}
|
||||
|
||||
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="Switch model"
|
||||
title="Select model"
|
||||
description={
|
||||
<Text>
|
||||
Current model: <Text color="greenBright">{currentModel}</Text>
|
||||
</Text>
|
||||
<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}
|
||||
|
||||
Reference in New Issue
Block a user