fix(paseo-agent): polish provider settings UI

Keep provider instance keys stable for credentials and model addressing while allowing display-name edits.
This commit is contained in:
Mohamed Boudra
2026-07-02 15:15:37 +02:00
parent 6a2b5ea5ea
commit 7cc47701ad
21 changed files with 576 additions and 149 deletions
+8 -9
View File
@@ -17,14 +17,11 @@ type PaseoAgentDaemonClient = Pick<
interface ApiKeyProviderInput {
catalogId: string;
name: string;
apiKey: string;
models?: string[];
}
interface OAuthProviderInput {
catalogId: string;
name: string;
}
interface ExpectedProvider {
@@ -54,11 +51,9 @@ export async function addApiKeyProvider(page: Page, provider: ApiKeyProviderInpu
await page.getByTestId(`paseo-agent-catalog-select-${provider.catalogId}`).click();
await expect(page.getByTestId("paseo-agent-provider-form")).toBeVisible();
await page.getByLabel("Provider name").fill(provider.name);
await expect(page.getByLabel("Provider name")).toHaveCount(0);
await expect(page.getByLabel("Models")).toHaveCount(0);
await page.getByLabel("API key").fill(provider.apiKey);
if (provider.models) {
await page.getByLabel("Models").fill(provider.models.join("\n"));
}
await page.getByRole("button", { name: "Save provider", exact: true }).click();
await expect(page.getByTestId("paseo-agent-provider-form")).toHaveCount(0);
@@ -72,9 +67,13 @@ export async function startOAuthProviderSignIn(
await page.getByRole("button", { name: "Add model provider", exact: true }).click();
await expect(page.getByTestId("paseo-agent-provider-picker")).toBeVisible();
await page.getByTestId(`paseo-agent-catalog-select-${provider.catalogId}`).click();
await expect(page.getByTestId("paseo-agent-provider-form")).toBeVisible();
await page.getByLabel("Provider name").fill(provider.name);
await expect(page.getByTestId("paseo-agent-provider-form")).toHaveCount(0);
await expect(page.getByTestId("paseo-agent-oauth-sign-in")).toBeVisible();
await expect(page.getByLabel("Provider name")).toHaveCount(0);
await expect(
page.getByRole("button", { name: "Sign in with browser", exact: true }),
).toBeVisible();
await page.getByRole("button", { name: "Use a code instead", exact: true }).click();
await expect(page.getByTestId("paseo-agent-oauth-user-code")).toBeVisible();
await expect(page.getByTestId("paseo-agent-oauth-verification-link")).toBeVisible();
@@ -1,3 +1,4 @@
import { expect } from "@playwright/test";
import { test } from "./fixtures";
import {
addApiKeyProvider,
@@ -8,8 +9,11 @@ import {
startOAuthProviderSignIn,
} from "./helpers/paseo-agent";
const OPENROUTER_PROVIDER = "phase-e-openrouter-ui";
const CHATGPT_PROVIDER = "phase-e-chatgpt-ui";
const CLOSE_PROVIDER = "phase-e-close-ui";
const DEFAULT_OPENROUTER_PROVIDER = "OpenRouter";
const DEFAULT_CHATGPT_PROVIDER = "ChatGPT";
const RENAME_PROVIDER = "phase-e-rename-ui";
test.describe("Paseo Agent provider configuration", () => {
const providerNamesToCleanup = new Set<string>();
@@ -20,34 +24,82 @@ test.describe("Paseo Agent provider configuration", () => {
});
test("adds an OpenRouter model provider from Settings", async ({ page }) => {
providerNamesToCleanup.add(OPENROUTER_PROVIDER);
providerNamesToCleanup.add(DEFAULT_OPENROUTER_PROVIDER);
await openPaseoAgentSettings(page);
await addApiKeyProvider(page, {
catalogId: "openrouter",
name: OPENROUTER_PROVIDER,
apiKey: "sk-or-phase-e-write-only",
models: ["openai/gpt-4o-mini", "anthropic/claude-3.7-sonnet"],
});
await expectModelProviderListed(page, {
name: OPENROUTER_PROVIDER,
name: DEFAULT_OPENROUTER_PROVIDER,
providerLabel: "OpenRouter",
modelCount: 2,
modelCount: 0,
auth: "Connected",
});
});
test("adds an API-key provider without prompting for a provider name", async ({ page }) => {
providerNamesToCleanup.add(DEFAULT_OPENROUTER_PROVIDER);
await openPaseoAgentSettings(page);
await page.getByRole("button", { name: "Add model provider", exact: true }).click();
await page.getByTestId("paseo-agent-catalog-select-openrouter").click();
await expect(page.getByTestId("paseo-agent-provider-form")).toBeVisible();
await expect(page.getByLabel("Provider name")).toHaveCount(0);
await expect(page.getByLabel("Models")).toHaveCount(0);
await page.getByLabel("API key").fill("sk-or-default-name");
await page.getByRole("button", { name: "Save provider", exact: true }).click();
await expectModelProviderListed(page, {
name: DEFAULT_OPENROUTER_PROVIDER,
providerLabel: "OpenRouter",
modelCount: 0,
auth: "Connected",
});
});
test("starts a ChatGPT sign-in from Settings", async ({ page }) => {
providerNamesToCleanup.add(CHATGPT_PROVIDER);
providerNamesToCleanup.add(DEFAULT_CHATGPT_PROVIDER);
await openPaseoAgentSettings(page);
await startOAuthProviderSignIn(page, {
catalogId: "chatgpt",
name: CHATGPT_PROVIDER,
});
});
test("starts OAuth sign-in without prompting for a provider name", async ({ page }) => {
providerNamesToCleanup.add(DEFAULT_CHATGPT_PROVIDER);
await openPaseoAgentSettings(page);
await page.getByRole("button", { name: "Add model provider", exact: true }).click();
await page.getByTestId("paseo-agent-catalog-select-chatgpt").click();
await expect(page.getByTestId("paseo-agent-provider-form")).toHaveCount(0);
await expect(page.getByLabel("Provider name")).toHaveCount(0);
});
test("shows user-facing provider picker rows", async ({ page }) => {
await openPaseoAgentSettings(page);
await page.getByRole("button", { name: "Add model provider", exact: true }).click();
const picker = page.getByTestId("paseo-agent-provider-picker");
await expect(
page
.getByTestId("paseo-agent-catalog-entry-openrouter")
.getByText("API key", { exact: true }),
).toBeVisible();
await expect(page.getByTestId("paseo-agent-catalog-icon-openrouter")).toBeVisible();
await expect(
page.getByTestId("paseo-agent-catalog-entry-chatgpt").getByText("Sign in", { exact: true }),
).toBeVisible();
await expect(page.getByTestId("paseo-agent-catalog-icon-chatgpt")).toBeVisible();
await expect(picker.getByText("openai-codex-responses")).toHaveCount(0);
await expect(picker.getByText(/\b\d+ models?\b/)).toHaveCount(0);
});
test("shows a stored ChatGPT login as a read-only model provider row", async ({ page }) => {
providerNamesToCleanup.add(CHATGPT_PROVIDER);
@@ -61,4 +113,41 @@ test.describe("Paseo Agent provider configuration", () => {
auth: "Connected",
});
});
test("renames a configured provider row without changing its stored credential key", async ({
page,
}) => {
providerNamesToCleanup.add(RENAME_PROVIDER);
await seedChatGptProvider(RENAME_PROVIDER);
await openPaseoAgentSettings(page);
await page.getByTestId(`paseo-agent-provider-rename-${RENAME_PROVIDER}`).click();
await expect(page.getByTestId("paseo-agent-provider-rename-form")).toBeVisible();
await page.getByLabel("Provider name").fill("Work account");
await page.getByRole("button", { name: "Save", exact: true }).click();
await expectModelProviderListed(page, {
name: "Work account",
providerLabel: "ChatGPT",
modelCount: 1,
auth: "Connected",
});
});
test("closes the Paseo Agent settings sheet after providers load", async ({ page }) => {
providerNamesToCleanup.add(CLOSE_PROVIDER);
await seedChatGptProvider(CLOSE_PROVIDER);
await openPaseoAgentSettings(page);
await expectModelProviderListed(page, {
name: CLOSE_PROVIDER,
providerLabel: "ChatGPT",
modelCount: 1,
auth: "Connected",
});
await page.getByLabel("Close", { exact: true }).click();
await expect(page.getByTestId("paseo-agent-settings-sheet")).toHaveCount(0);
});
});
@@ -0,0 +1,20 @@
import Svg, { Path } from "react-native-svg";
interface OpenRouterIconProps {
size?: number;
color?: string;
}
export function OpenRouterIcon({ size = 16, color = "currentColor" }: OpenRouterIconProps) {
return (
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
<Path
d="M3 7H15.5L21 12L15.5 17H3M10 3V21M15 7V17"
stroke={color}
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
/>
</Svg>
);
}
@@ -9,6 +9,7 @@ import {
getPaseoAgentApiKeyAuth,
getPaseoAgentOAuthAuth,
isPaseoAgentCatalogEntrySupported,
nextPaseoAgentProviderName,
parsePaseoAgentModelIds,
paseoAgentAuthBadge,
paseoAgentProviderLabel,
@@ -95,6 +96,21 @@ describe("paseo-agent-settings-sheet-model", () => {
).toEqual(["alpha/fast", "beta/steady", "gamma/deep"]);
});
it("uses the catalog label for the first provider instance", () => {
expect(nextPaseoAgentProviderName(catalogEntry({ label: "Acme Cloud" }), [])).toBe(
"Acme Cloud",
);
});
it("numbers later provider instances from the catalog label", () => {
expect(
nextPaseoAgentProviderName(catalogEntry({ label: "Acme Cloud" }), [
providerConfig({ name: "Acme Cloud" }),
providerConfig({ name: "Acme Cloud (1)" }),
]),
).toBe("Acme Cloud (2)");
});
it("builds a generic provider payload with a trimmed explicit key", () => {
expect(
createPaseoAgentProviderInput({
@@ -93,6 +93,24 @@ export function parsePaseoAgentModelIds(raw: string): string[] {
return ids;
}
export function nextPaseoAgentProviderName(
entry: PaseoAgentCatalogEntry,
providers: RedactedPaseoAgentProviderConfig[],
): string {
const baseName = entry.label.trim() || entry.id;
const existingNames = new Set(providers.map((provider) => provider.name));
if (!existingNames.has(baseName)) {
return baseName;
}
for (let index = 1; ; index += 1) {
const candidate = `${baseName} (${index})`;
if (!existingNames.has(candidate)) {
return candidate;
}
}
}
export function createPaseoAgentProviderInput(input: {
entry: PaseoAgentCatalogEntry;
name: string;
@@ -400,24 +400,19 @@ describe("PaseoAgentSettingsSheet", () => {
renderSheet();
fireEvent.click(screen.getByRole("button", { name: "Add model provider" }));
fireEvent.click(screen.getByTestId("paseo-agent-catalog-select-catalog-alpha"));
fireEvent.change(screen.getByLabelText("Provider name"), {
target: { value: "alpha-main" },
});
expect(screen.queryByLabelText("Provider name")).toBeNull();
expect(screen.queryByLabelText("Models")).toBeNull();
fireEvent.change(screen.getByLabelText("API key"), {
target: { value: " alpha-secret " },
});
fireEvent.change(screen.getByLabelText("Models"), {
target: { value: "alpha/fast\nbeta/steady" },
});
fireEvent.click(screen.getByRole("button", { name: "Save provider" }));
await waitFor(() => {
expect(hookState.current.setProvider).toHaveBeenCalledWith({
name: "alpha-main",
name: "Catalog Alpha",
providerType: "catalog-alpha",
options: {
apiKey: "alpha-secret",
models: [{ id: "alpha/fast" }, { id: "beta/steady" }],
},
} satisfies PaseoAgentSetProviderInput);
});
@@ -433,7 +428,7 @@ describe("PaseoAgentSettingsSheet", () => {
await waitFor(() => {
expect(hookState.current.setProvider).toHaveBeenCalledWith({
name: "catalog-alpha",
name: "Catalog Alpha",
providerType: "catalog-alpha",
options: {
apiKey: "$ALPHA_API_KEY",
@@ -470,7 +465,7 @@ describe("PaseoAgentSettingsSheet", () => {
await waitFor(() => {
expect(openExternalUrls).toEqual(["https://login.example.test/oauth/authorize?state=abc"]);
expect(hookState.current.startOAuth).toHaveBeenCalledWith("catalog-login", "browser");
expect(hookState.current.startOAuth).toHaveBeenCalledWith("Catalog Login", "browser");
});
expect(screen.getByText("https://login.example.test/oauth/authorize?state=abc")).toBeTruthy();
expect(screen.getByTestId("paseo-agent-oauth-url").getAttribute("href")).toBe(
@@ -512,14 +507,14 @@ describe("PaseoAgentSettingsSheet", () => {
await waitFor(() => {
expect(hookState.current.setProvider).toHaveBeenCalledWith({
name: "catalog-login",
name: "Catalog Login",
providerType: "catalog-login",
options: {
models: [{ id: "alpha-fast", label: "Alpha Fast" }],
},
} satisfies PaseoAgentSetProviderInput);
expect(hookState.current.startOAuth).toHaveBeenCalledWith("catalog-login", "device_code");
expect(hookState.current.completeOAuth).toHaveBeenCalledWith("catalog-login");
expect(hookState.current.startOAuth).toHaveBeenCalledWith("Catalog Login", "device_code");
expect(hookState.current.completeOAuth).toHaveBeenCalledWith("Catalog Login");
});
});
@@ -540,7 +535,7 @@ describe("PaseoAgentSettingsSheet", () => {
fireEvent.click(screen.getByTestId("paseo-agent-catalog-select-catalog-login"));
const actionButtons = screen
.getByTestId("paseo-agent-provider-form")
.getByTestId("paseo-agent-oauth-sign-in")
.querySelectorAll("button");
expect(Array.from(actionButtons).map((button) => button.textContent)).toEqual([
"Providers",
@@ -21,6 +21,7 @@ import { useHostRuntimeSnapshot } from "@/runtime/host-runtime";
import type {
PaseoAgentOAuthCompleteResult,
PaseoAgentOAuthStartResult,
PaseoAgentRenameProviderInput,
PaseoAgentSetProviderInput,
} from "@/hooks/use-paseo-agent-providers";
import type { Theme } from "@/styles/theme";
@@ -32,7 +33,7 @@ import {
getPaseoAgentApiKeyAuth,
getPaseoAgentOAuthAuth,
isPaseoAgentCatalogEntrySupported,
parsePaseoAgentModelIds,
nextPaseoAgentProviderName,
paseoAgentAuthBadge,
paseoAgentProviderLabel,
preferredPaseoAgentOAuthMode,
@@ -56,9 +57,12 @@ type AddSheetState =
| {
kind: "form";
entry: PaseoAgentCatalogEntry;
initialName?: string;
lockName: boolean;
name: string;
returnToPicker: boolean;
}
| {
kind: "rename";
provider: RedactedPaseoAgentProviderConfig;
};
type OAuthState =
@@ -80,17 +84,6 @@ type OAuthState =
authorization: PaseoAgentOAuthStartResult["authorization"];
};
interface ProviderNameAndModelsFieldsProps {
entry: PaseoAgentCatalogEntry;
name: string;
models: string;
resetKey: number;
lockName: boolean;
hasCatalogModels: boolean;
onNameChange: (value: string) => void;
onModelsChange: (value: string) => void;
}
interface ApiKeyFieldsProps {
auth: PaseoAgentApiKeyAuthManifest;
apiKey: string;
@@ -146,8 +139,14 @@ function modelCountLabel(count: number): string {
return count === 1 ? "1 model" : `${count} models`;
}
function catalogModelSummary(entry: PaseoAgentCatalogEntry): string {
return entry.models.length > 0 ? modelCountLabel(entry.models.length) : "Custom models";
function catalogAuthSummary(entry: PaseoAgentCatalogEntry): string | null {
if (getPaseoAgentOAuthAuth(entry)) {
return "Sign in";
}
if (getPaseoAgentApiKeyAuth(entry)) {
return "API key";
}
return null;
}
function hasKnownProviderIcon(iconName: string | undefined): iconName is string {
@@ -205,19 +204,22 @@ function ProviderRow({
catalogEntry,
isFirst,
onReauth,
onRename,
}: {
provider: RedactedPaseoAgentProviderConfig;
catalogEntry: PaseoAgentCatalogEntry | undefined;
isFirst: boolean;
onReauth: (provider: RedactedPaseoAgentProviderConfig, entry: PaseoAgentCatalogEntry) => void;
onRename: (provider: RedactedPaseoAgentProviderConfig) => void;
}) {
const modelLabel = modelCountLabel(provider.models.length);
const authBadge = paseoAgentAuthBadge(provider.auth);
const providerLabel = paseoAgentProviderLabel(provider, catalogEntry);
const providerName = provider.displayName ?? provider.name;
const canReauth = Boolean(catalogEntry && isPaseoAgentCatalogEntrySupported(catalogEntry));
const accessibilityLabel = authBadge
? `${provider.name}, ${providerLabel}, ${modelLabel}, ${authBadge.label}`
: `${provider.name}, ${providerLabel}, ${modelLabel}`;
? `${providerName}, ${providerLabel}, ${modelLabel}, ${authBadge.label}`
: `${providerName}, ${providerLabel}, ${modelLabel}`;
const rowStyle = useMemo(() => [styles.providerRow, !isFirst && styles.rowBorder], [isFirst]);
const handleReauth = useCallback(() => {
@@ -225,6 +227,9 @@ function ProviderRow({
onReauth(provider, catalogEntry);
}
}, [catalogEntry, onReauth, provider]);
const handleRename = useCallback(() => {
onRename(provider);
}, [onRename, provider]);
return (
<View
@@ -237,13 +242,21 @@ function ProviderRow({
<View style={provider.available ? styles.dotAvailable : styles.dotMuted} />
<View style={styles.providerText}>
<Text style={styles.providerName} numberOfLines={1}>
{provider.name}
{providerName}
</Text>
<Text style={styles.providerMeta} numberOfLines={1}>
{providerLabel} · {modelLabel}
</Text>
</View>
{authBadge ? <StatusBadge label={authBadge.label} variant={authBadge.variant} /> : null}
<Button
variant="outline"
size="xs"
onPress={handleRename}
testID={`paseo-agent-provider-rename-${provider.name}`}
>
Rename
</Button>
{canReauth ? (
<Button
variant="outline"
@@ -268,6 +281,7 @@ function CatalogEntryRow({
onSelect: (entry: PaseoAgentCatalogEntry) => void;
}) {
const supported = isPaseoAgentCatalogEntrySupported(entry);
const authSummary = catalogAuthSummary(entry);
const rowStyle = useMemo(
() => [styles.catalogRow, !isFirst && styles.rowBorder, !supported && styles.disabledRow],
[isFirst, supported],
@@ -282,16 +296,18 @@ function CatalogEntryRow({
accessibilityLabel={entry.label}
testID={`paseo-agent-catalog-entry-${entry.id}`}
>
<View style={styles.catalogIcon}>
<View style={styles.catalogIcon} testID={`paseo-agent-catalog-icon-${entry.id}`}>
<CatalogEntryIcon entry={entry} />
</View>
<View style={styles.catalogText}>
<Text style={styles.providerName} numberOfLines={1}>
{entry.label}
</Text>
<Text style={styles.providerMeta} numberOfLines={1}>
{entry.api} · {catalogModelSummary(entry)}
</Text>
{authSummary ? (
<Text style={styles.providerMeta} numberOfLines={1}>
{authSummary}
</Text>
) : null}
{entry.docsUrl ? (
<ExternalLink
href={entry.docsUrl}
@@ -438,59 +454,6 @@ function OAuthAuthorizationPanel({
);
}
function ProviderNameAndModelsFields({
entry,
name,
models,
resetKey,
lockName,
hasCatalogModels,
onNameChange,
onModelsChange,
}: ProviderNameAndModelsFieldsProps) {
const modelInputStyle = useMemo(() => [styles.formInput, styles.modelsInput], []);
return (
<>
<Text style={styles.formLabel}>Provider name</Text>
<AdaptiveTextInput
testID="paseo-agent-provider-name"
accessibilityLabel="Provider name"
initialValue={name}
resetKey={`paseo-agent-provider-name-${resetKey}`}
value={name}
onChangeText={onNameChange}
placeholder={entry.id}
editable={!lockName}
autoCapitalize="none"
autoCorrect={false}
style={styles.formInput}
/>
{hasCatalogModels ? (
<Text style={styles.formHint}>{modelCountLabel(entry.models.length)} from the catalog</Text>
) : (
<>
<Text style={styles.formLabel}>Models</Text>
<AdaptiveTextInput
testID="paseo-agent-models"
accessibilityLabel="Models"
initialValue={models}
resetKey={`paseo-agent-models-${resetKey}`}
value={models}
onChangeText={onModelsChange}
placeholder="provider/model-id"
multiline
autoCapitalize="none"
autoCorrect={false}
style={modelInputStyle}
/>
<Text style={styles.formHint}>One model id per line, or comma-separated.</Text>
</>
)}
</>
);
}
function ApiKeyFields({ auth, apiKey, resetKey, onApiKeyChange }: ApiKeyFieldsProps) {
const envHint = `Leave blank to use $${auth.envVar} on the host.`;
const apiKeyHint = auth.hint ?? "Stored on the host and never shown again.";
@@ -638,8 +601,7 @@ function ProviderFormActions({
function PaseoAgentProviderFormSheet({
entry,
visible,
initialName,
lockName,
name,
onBack,
onClose,
setProvider,
@@ -649,8 +611,7 @@ function PaseoAgentProviderFormSheet({
}: {
entry: PaseoAgentCatalogEntry | null;
visible: boolean;
initialName: string | undefined;
lockName: boolean;
name: string | undefined;
onBack: () => void;
onClose: () => void;
setProvider: (
@@ -660,9 +621,7 @@ function PaseoAgentProviderFormSheet({
completeOAuth: (name: string) => Promise<PaseoAgentOAuthCompleteResult>;
preferredOAuthMode: PaseoAgentOAuthMode;
}) {
const [name, setName] = useState("");
const [apiKey, setApiKey] = useState("");
const [models, setModels] = useState("");
const [error, setError] = useState<string | null>(null);
const [saving, setSaving] = useState(false);
const [oauthState, setOAuthState] = useState<OAuthState>({ status: "idle" });
@@ -670,15 +629,13 @@ function PaseoAgentProviderFormSheet({
useEffect(() => {
if (visible && entry) {
setName(initialName ?? entry.id);
setApiKey("");
setModels("");
setError(null);
setSaving(false);
setOAuthState({ status: "idle" });
bumpResetKey();
}
}, [entry, initialName, visible]);
}, [entry, name, visible]);
const header = useMemo<SheetHeader>(
() => ({
@@ -689,11 +646,8 @@ function PaseoAgentProviderFormSheet({
);
const apiKeyAuth = entry ? getPaseoAgentApiKeyAuth(entry) : null;
const oauthAuth = entry ? getPaseoAgentOAuthAuth(entry) : null;
const trimmedName = name.trim();
const modelIds = useMemo(() => parsePaseoAgentModelIds(models), [models]);
const hasCatalogModels = Boolean(entry && entry.models.length > 0);
const hasConfiguredModels = hasCatalogModels || modelIds.length > 0;
const canSubmit = Boolean(entry && trimmedName.length > 0 && hasConfiguredModels && !saving);
const trimmedName = name?.trim() ?? "";
const canSubmit = Boolean(entry && trimmedName.length > 0 && !saving);
const activeAuthorization =
oauthState.status === "authorizing" ||
oauthState.status === "completing" ||
@@ -712,7 +666,6 @@ function PaseoAgentProviderFormSheet({
entry,
name: trimmedName,
apiKey,
modelIds: hasCatalogModels ? undefined : modelIds,
}),
)
.then(() => {
@@ -724,7 +677,7 @@ function PaseoAgentProviderFormSheet({
setError(describeError(err, "Failed to save provider"));
})
.finally(() => setSaving(false));
}, [apiKey, canSubmit, entry, hasCatalogModels, modelIds, onClose, setProvider, trimmedName]);
}, [apiKey, canSubmit, entry, onClose, setProvider, trimmedName]);
const handleStartOAuth = useCallback(
(mode: PaseoAgentOAuthMode) => {
@@ -737,7 +690,6 @@ function PaseoAgentProviderFormSheet({
createPaseoAgentProviderInput({
entry,
name: trimmedName,
modelIds: hasCatalogModels ? undefined : modelIds,
}),
)
.then(() => startOAuth(trimmedName, mode))
@@ -762,7 +714,7 @@ function PaseoAgentProviderFormSheet({
})
.finally(() => setSaving(false));
},
[canSubmit, entry, hasCatalogModels, modelIds, setProvider, startOAuth, trimmedName],
[canSubmit, entry, setProvider, startOAuth, trimmedName],
);
const handleCompleteOAuth = useCallback(() => {
@@ -807,20 +759,9 @@ function PaseoAgentProviderFormSheet({
onClose={onClose}
desktopMaxWidth={480}
snapPoints={FORM_SNAP_POINTS}
testID="paseo-agent-provider-form"
testID={oauthAuth ? "paseo-agent-oauth-sign-in" : "paseo-agent-provider-form"}
>
<View style={styles.formGroup}>
<ProviderNameAndModelsFields
entry={entry}
name={name}
models={models}
resetKey={resetKey}
lockName={lockName}
hasCatalogModels={hasCatalogModels}
onNameChange={setName}
onModelsChange={setModels}
/>
{apiKeyAuth ? (
<ApiKeyFields
auth={apiKeyAuth}
@@ -873,6 +814,103 @@ function PaseoAgentProviderFormSheet({
);
}
function PaseoAgentProviderRenameSheet({
provider,
visible,
onClose,
renameProvider,
}: {
provider: RedactedPaseoAgentProviderConfig | null;
visible: boolean;
onClose: () => void;
renameProvider: (
input: PaseoAgentRenameProviderInput,
) => Promise<RedactedPaseoAgentProviderConfig | null>;
}) {
const [displayName, setDisplayName] = useState("");
const [error, setError] = useState<string | null>(null);
const [saving, setSaving] = useState(false);
const [resetKey, bumpResetKey] = useReducer((key: number) => key + 1, 0);
useEffect(() => {
if (visible && provider) {
setDisplayName(provider.displayName ?? provider.name);
setError(null);
setSaving(false);
bumpResetKey();
}
}, [provider, visible]);
const header = useMemo<SheetHeader>(() => ({ title: "Rename provider" }), []);
const trimmedDisplayName = displayName.trim();
const canSave = Boolean(provider && trimmedDisplayName.length > 0 && !saving);
const handleSave = useCallback(() => {
if (!provider || !canSave) return;
setError(null);
setSaving(true);
void renameProvider({ name: provider.name, displayName: trimmedDisplayName })
.then(() => {
onClose();
return undefined;
})
.catch((err: unknown) => {
setError(describeError(err, "Failed to rename provider"));
})
.finally(() => setSaving(false));
}, [canSave, onClose, provider, renameProvider, trimmedDisplayName]);
if (!provider) {
return null;
}
return (
<AdaptiveModalSheet
header={header}
visible={visible}
onClose={onClose}
desktopMaxWidth={420}
snapPoints={FORM_SNAP_POINTS}
testID="paseo-agent-provider-rename-form"
>
<View style={styles.formGroup}>
<Text style={styles.formLabel}>Provider name</Text>
<AdaptiveTextInput
testID="paseo-agent-provider-display-name"
accessibilityLabel="Provider name"
initialValue={displayName}
resetKey={`paseo-agent-provider-display-name-${resetKey}`}
value={displayName}
onChangeText={setDisplayName}
autoCapitalize="none"
autoCorrect={false}
style={styles.formInput}
/>
{error ? (
<Text style={styles.errorText} testID="paseo-agent-provider-rename-error">
{error}
</Text>
) : null}
<View style={styles.formActions}>
<Button variant="secondary" size="sm" onPress={onClose} disabled={saving}>
Cancel
</Button>
<Button
variant="default"
size="sm"
onPress={handleSave}
disabled={!canSave}
loading={saving}
testID="paseo-agent-provider-rename-submit"
>
{saving ? "Saving..." : "Save"}
</Button>
</View>
</View>
</AdaptiveModalSheet>
);
}
export function PaseoAgentSettingsSheet({
serverId,
visible,
@@ -890,6 +928,7 @@ export function PaseoAgentSettingsSheet({
setProvider,
startOAuth,
completeOAuth,
renameProvider,
} = usePaseoAgentProviders(serverId);
const activeConnection = useHostRuntimeSnapshot(serverId)?.activeConnection ?? null;
const preferredOAuthMode = preferredPaseoAgentOAuthMode(activeConnection);
@@ -911,21 +950,31 @@ export function PaseoAgentSettingsSheet({
const handleOpenPicker = useCallback(() => setAddSheet({ kind: "picker" }), []);
const handleCloseSubSheet = useCallback(() => setAddSheet({ kind: "closed" }), []);
const handleSelectEntry = useCallback((entry: PaseoAgentCatalogEntry) => {
setAddSheet({ kind: "form", entry, lockName: false, returnToPicker: true });
}, []);
const handleSelectEntry = useCallback(
(entry: PaseoAgentCatalogEntry) => {
setAddSheet({
kind: "form",
entry,
name: nextPaseoAgentProviderName(entry, providers),
returnToPicker: true,
});
},
[providers],
);
const handleReauth = useCallback(
(provider: RedactedPaseoAgentProviderConfig, entry: PaseoAgentCatalogEntry) => {
setAddSheet({
kind: "form",
entry,
initialName: provider.name,
lockName: true,
name: provider.name,
returnToPicker: false,
});
},
[],
);
const handleRename = useCallback((provider: RedactedPaseoAgentProviderConfig) => {
setAddSheet({ kind: "rename", provider });
}, []);
const handleBackFromForm = useCallback(() => {
setAddSheet((current) =>
current.kind === "form" && current.returnToPicker ? { kind: "picker" } : { kind: "closed" },
@@ -983,6 +1032,7 @@ export function PaseoAgentSettingsSheet({
catalogEntry={catalogById.get(provider.providerType)}
isFirst={index === 0}
onReauth={handleReauth}
onRename={handleRename}
/>
))}
</View>
@@ -1015,8 +1065,7 @@ export function PaseoAgentSettingsSheet({
<PaseoAgentProviderFormSheet
entry={addSheet.kind === "form" ? addSheet.entry : null}
visible={addSheet.kind === "form"}
initialName={addSheet.kind === "form" ? addSheet.initialName : undefined}
lockName={addSheet.kind === "form" ? addSheet.lockName : false}
name={addSheet.kind === "form" ? addSheet.name : undefined}
onBack={handleBackFromForm}
onClose={handleCloseSubSheet}
setProvider={setProvider}
@@ -1025,6 +1074,14 @@ export function PaseoAgentSettingsSheet({
preferredOAuthMode={preferredOAuthMode}
/>
) : null}
{supported && catalogSupported ? (
<PaseoAgentProviderRenameSheet
provider={addSheet.kind === "rename" ? addSheet.provider : null}
visible={addSheet.kind === "rename"}
onClose={handleCloseSubSheet}
renameProvider={renameProvider}
/>
) : null}
</>
);
}
@@ -1151,10 +1208,6 @@ const styles = StyleSheet.create((theme) => ({
borderColor: theme.colors.border,
fontSize: theme.fontSize.sm,
},
modelsInput: {
minHeight: 80,
textAlignVertical: "top",
},
authorizationBox: {
gap: theme.spacing[2],
borderRadius: theme.borderRadius.lg,
@@ -16,6 +16,16 @@ describe("resolveProviderIconName", () => {
expect(resolveProviderIconName("paseo")).toEqual({ kind: "builtin", id: "paseo" });
});
it("resolves Paseo Agent model-provider catalog icon names", () => {
expect(resolveProviderIconName("openai")).toEqual({ kind: "builtin", id: "openai" });
expect(resolveProviderIconName("openrouter")).toEqual({
kind: "builtin",
id: "openrouter",
});
expect(resolveProviderIconName("kimi")).toEqual({ kind: "catalog", id: "kimi" });
expect(resolveProviderIconName("opencode")).toEqual({ kind: "builtin", id: "opencode" });
});
it("returns the catalog identifier for ACP catalog provider ids that ship an icon", () => {
expect(resolveProviderIconName("amp-acp")).toEqual({ kind: "catalog", id: "amp-acp" });
expect(resolveProviderIconName("gemini")).toEqual({ kind: "catalog", id: "gemini" });
@@ -6,6 +6,7 @@ import { CodexIcon } from "@/components/icons/codex-icon";
import { CopilotIcon } from "@/components/icons/copilot-icon";
import { MiniMaxIcon } from "@/components/icons/minimax-icon";
import { OpenCodeIcon } from "@/components/icons/opencode-icon";
import { OpenRouterIcon } from "@/components/icons/openrouter-icon";
import { OmpIcon } from "@/components/icons/omp-icon";
import { PaseoLogo } from "@/components/icons/paseo-logo";
import { PiIcon } from "@/components/icons/pi-icon";
@@ -26,7 +27,9 @@ const BUILTIN_PROVIDER_ICONS: Record<string, ProviderIconComponent> = {
kiro: PackagePlus,
minimax: MiniMaxIcon as unknown as ProviderIconComponent,
omp: OmpIcon as unknown as ProviderIconComponent,
openai: CodexIcon as unknown as ProviderIconComponent,
opencode: OpenCodeIcon as unknown as ProviderIconComponent,
openrouter: OpenRouterIcon as unknown as ProviderIconComponent,
paseo: PaseoLogo as unknown as ProviderIconComponent,
pi: PiIcon as unknown as ProviderIconComponent,
};
@@ -20,7 +20,7 @@ export function ProviderSettingsHost() {
}
if (provider === PASEO_AGENT_PROVIDER) {
return <PaseoAgentSettingsSheet serverId={serverId} visible onClose={handleClose} />;
return <PaseoAgentSettingsSheet serverId={serverId} visible={visible} onClose={handleClose} />;
}
return (
@@ -5,6 +5,7 @@ import type {
PaseoAgentCatalogEntry,
PaseoAgentOAuthCompleteResponse,
PaseoAgentOAuthStartResponse,
PaseoAgentRenameProviderRequest,
PaseoAgentSetProviderRequest,
RedactedPaseoAgentProviderConfig,
} from "@getpaseo/protocol/messages";
@@ -27,6 +28,10 @@ function describeQueryError(error: unknown): string | null {
}
export type PaseoAgentSetProviderInput = Omit<PaseoAgentSetProviderRequest, "type" | "requestId">;
export type PaseoAgentRenameProviderInput = Omit<
PaseoAgentRenameProviderRequest,
"type" | "requestId"
>;
export type PaseoAgentOAuthStartResult = PaseoAgentOAuthStartResponse["payload"];
export type PaseoAgentOAuthCompleteResult = PaseoAgentOAuthCompleteResponse["payload"];
@@ -44,6 +49,9 @@ interface UsePaseoAgentProvidersResult {
setProvider: (
input: PaseoAgentSetProviderInput,
) => Promise<RedactedPaseoAgentProviderConfig | null>;
renameProvider: (
input: PaseoAgentRenameProviderInput,
) => Promise<RedactedPaseoAgentProviderConfig | null>;
startOAuth: (name: string, mode?: string) => Promise<PaseoAgentOAuthStartResult>;
completeOAuth: (name: string) => Promise<PaseoAgentOAuthCompleteResult>;
}
@@ -120,6 +128,28 @@ export function usePaseoAgentProviders(serverId: string | null): UsePaseoAgentPr
[setProviderAsync],
);
const renameProviderMutation = useMutation({
mutationFn: async (input: PaseoAgentRenameProviderInput) => {
if (!client) {
throw new Error(hostDisconnectedMessage);
}
const result = await client.renamePaseoAgentProvider(input);
if (!result.success) {
throw new Error(result.error ?? saveProviderFailedMessage);
}
return result.provider;
},
onSuccess: () => {
void queryClient.invalidateQueries({ queryKey });
},
});
const { mutateAsync: renameProviderAsync } = renameProviderMutation;
const renameProvider = useCallback(
(input: PaseoAgentRenameProviderInput) => renameProviderAsync(input),
[renameProviderAsync],
);
const startOAuthMutation = useMutation({
mutationFn: async (input: { name: string; mode?: string }) => {
if (!client) {
@@ -173,6 +203,7 @@ export function usePaseoAgentProviders(serverId: string | null): UsePaseoAgentPr
catalogError,
refresh,
setProvider,
renameProvider,
startOAuth,
completeOAuth,
};
+17
View File
@@ -103,6 +103,8 @@ import type {
PaseoAgentOAuthCredential,
PaseoAgentOAuthStartResponse,
PaseoAgentOAuthStoreCredentialResponse,
PaseoAgentRenameProviderRequest,
PaseoAgentRenameProviderResponse,
PaseoAgentRemoveProviderResponse,
PaseoAgentSetProviderRequest,
PaseoAgentSetProviderResponse,
@@ -3905,6 +3907,7 @@ export class DaemonClient {
type: "config.paseo_agent.set_provider.request",
name: input.name,
providerType: input.providerType,
...(input.displayName ? { displayName: input.displayName } : {}),
options: input.options,
},
timeout: 30000,
@@ -3925,6 +3928,20 @@ export class DaemonClient {
});
}
async renamePaseoAgentProvider(
input: Omit<PaseoAgentRenameProviderRequest, "type" | "requestId"> & { requestId?: string },
): Promise<PaseoAgentRenameProviderResponse["payload"]> {
return this.sendNamespacedCorrelatedSessionRequest({
requestId: input.requestId,
message: {
type: "config.paseo_agent.rename_provider.request",
name: input.name,
displayName: input.displayName,
},
timeout: 30000,
});
}
async startPaseoAgentOAuth(
name: string,
options?: string | { mode?: string; requestId?: string },
@@ -228,6 +228,35 @@ describe("Paseo Agent config RPC schemas", () => {
expect(JSON.stringify(parsed)).not.toContain("refresh-token");
});
test("parses provider rename request and response with display name", () => {
const request = SessionInboundMessageSchema.parse({
type: "config.paseo_agent.rename_provider.request",
requestId: "req-rename-provider",
name: "subscription",
displayName: "Work account",
});
const response = SessionOutboundMessageSchema.parse({
type: "config.paseo_agent.rename_provider.response",
payload: {
requestId: "req-rename-provider",
success: true,
provider: {
name: "subscription",
displayName: "Work account",
providerType: "chatgpt",
models: [{ id: "gpt-5.4-mini" }],
auth: { kind: "oauth", configured: true, source: "stored" },
available: true,
error: null,
},
error: null,
},
});
expect(request.displayName).toBe("Work account");
expect(response.payload.provider?.displayName).toBe("Work account");
});
test("parses ChatGPT provider config separately from credential storage", () => {
const parsed = SessionInboundMessageSchema.parse({
type: "config.paseo_agent.set_provider.request",
+25
View File
@@ -2004,6 +2004,7 @@ export const PaseoAgentProviderAuthStateSchema = z
export const RedactedPaseoAgentProviderConfigSchema = z
.object({
name: z.string().min(1),
displayName: z.string().min(1).optional(),
providerType: PaseoAgentProviderTypeSchema,
baseUrl: z.string().optional(),
api: z.string().optional(),
@@ -2028,6 +2029,7 @@ export const PaseoAgentSetProviderRequestSchema = z.object({
type: z.literal("config.paseo_agent.set_provider.request"),
requestId: z.string(),
name: z.string().trim().min(1),
displayName: z.string().trim().min(1).optional(),
providerType: PaseoAgentProviderTypeSchema,
options: PaseoAgentSetProviderOptionsSchema,
});
@@ -2038,6 +2040,13 @@ export const PaseoAgentRemoveProviderRequestSchema = z.object({
name: z.string().trim().min(1),
});
export const PaseoAgentRenameProviderRequestSchema = z.object({
type: z.literal("config.paseo_agent.rename_provider.request"),
requestId: z.string(),
name: z.string().trim().min(1),
displayName: z.string().trim().min(1),
});
export const PaseoAgentOAuthStartRequestSchema = z.object({
type: z.literal("config.paseo_agent.oauth.start.request"),
requestId: z.string(),
@@ -2213,6 +2222,7 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
PaseoAgentGetCatalogRequestSchema,
PaseoAgentSetProviderRequestSchema,
PaseoAgentRemoveProviderRequestSchema,
PaseoAgentRenameProviderRequestSchema,
PaseoAgentOAuthStartRequestSchema,
PaseoAgentOAuthCompleteRequestSchema,
PaseoAgentOAuthStoreCredentialRequestSchema,
@@ -4056,6 +4066,16 @@ export const PaseoAgentRemoveProviderResponseSchema = z.object({
}),
});
export const PaseoAgentRenameProviderResponseSchema = z.object({
type: z.literal("config.paseo_agent.rename_provider.response"),
payload: z.object({
requestId: z.string(),
success: z.boolean(),
provider: RedactedPaseoAgentProviderConfigSchema.nullable(),
error: z.string().nullable(),
}),
});
const PaseoAgentOAuthStartAuthorizationSchema = z
.object({
kind: z.string().min(1),
@@ -4500,6 +4520,7 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
PaseoAgentGetCatalogResponseSchema,
PaseoAgentSetProviderResponseSchema,
PaseoAgentRemoveProviderResponseSchema,
PaseoAgentRenameProviderResponseSchema,
PaseoAgentOAuthStartResponseSchema,
PaseoAgentOAuthCompleteResponseSchema,
PaseoAgentOAuthStoreCredentialResponseSchema,
@@ -4647,6 +4668,9 @@ export type PaseoAgentSetProviderResponse = z.infer<typeof PaseoAgentSetProvider
export type PaseoAgentRemoveProviderResponse = z.infer<
typeof PaseoAgentRemoveProviderResponseSchema
>;
export type PaseoAgentRenameProviderResponse = z.infer<
typeof PaseoAgentRenameProviderResponseSchema
>;
export type PaseoAgentOAuthStartResponse = z.infer<typeof PaseoAgentOAuthStartResponseSchema>;
export type PaseoAgentOAuthCompleteResponse = z.infer<typeof PaseoAgentOAuthCompleteResponseSchema>;
export type PaseoAgentOAuthStoreCredentialResponse = z.infer<
@@ -4732,6 +4756,7 @@ export type PaseoAgentGetProvidersRequest = z.infer<typeof PaseoAgentGetProvider
export type PaseoAgentGetCatalogRequest = z.infer<typeof PaseoAgentGetCatalogRequestSchema>;
export type PaseoAgentSetProviderRequest = z.infer<typeof PaseoAgentSetProviderRequestSchema>;
export type PaseoAgentRemoveProviderRequest = z.infer<typeof PaseoAgentRemoveProviderRequestSchema>;
export type PaseoAgentRenameProviderRequest = z.infer<typeof PaseoAgentRenameProviderRequestSchema>;
export type PaseoAgentOAuthStartRequest = z.infer<typeof PaseoAgentOAuthStartRequestSchema>;
export type PaseoAgentOAuthCompleteRequest = z.infer<typeof PaseoAgentOAuthCompleteRequestSchema>;
export type PaseoAgentOAuthStoreCredentialRequest = z.infer<
@@ -5,7 +5,9 @@ export const BUILTIN_PROVIDER_ICON_NAMES = [
"kiro",
"minimax",
"omp",
"openai",
"opencode",
"openrouter",
"paseo",
"pi",
];
@@ -30,6 +30,7 @@ export const PASEO_AGENT_PROVIDER_CATALOG = [
id: "openrouter",
piProvider: "openrouter",
label: "OpenRouter",
iconName: "openrouter",
auth: { kind: "api_key", envVar: "OPENROUTER_API_KEY" },
defaultModels: false,
},
@@ -43,12 +44,14 @@ export const PASEO_AGENT_PROVIDER_CATALOG = [
id: "kimi",
piProvider: "kimi-coding",
label: "Kimi Coding Plan",
iconName: "kimi",
auth: { kind: "api_key", envVar: "KIMI_API_KEY" },
},
{
id: "opencode-go",
piProvider: "opencode-go",
label: "OpenCode Go",
iconName: "opencode",
auth: { kind: "api_key", envVar: "OPENCODE_API_KEY" },
},
] as const satisfies readonly PaseoAgentCatalogRef[];
@@ -321,6 +321,41 @@ describe("PaseoAgentConfigService", () => {
expect(JSON.stringify(providers)).not.toContain("refresh-token");
});
test("renames the provider display name without moving stored OAuth credentials", () => {
const service = new PaseoAgentConfigService({
paseoHome: home,
logger: createTestLogger(),
});
service.setProvider({
name: "subscription",
providerType: "chatgpt",
options: {},
});
service.storeOAuthCredential("subscription", {
type: "oauth",
access: "access-token",
refresh: "refresh-token",
expires: 123,
});
const provider = service.renameProvider("subscription", "Work account");
expect(provider).toMatchObject({
name: "subscription",
displayName: "Work account",
auth: { kind: "oauth", configured: true, source: "stored" },
available: true,
});
const stored = JSON.parse(
readFileSync(paseoAgentAuthStoragePath({ PASEO_HOME: home }), "utf8"),
);
expect(Object.keys(stored)).toEqual(["subscription"]);
expect(stored.subscription).toMatchObject({
type: "oauth",
refresh: "refresh-token",
});
});
test("reports OAuth as missing or needing attention for absent and mismatched credentials", () => {
const service = new PaseoAgentConfigService({
paseoHome: home,
@@ -40,6 +40,7 @@ interface PaseoAgentConfigServiceOptions {
interface SetProviderInput {
name: string;
providerType: string;
displayName?: string;
options: {
apiKey?: string;
baseUrl?: string;
@@ -170,6 +171,9 @@ function redactedProviders(
available: auth.configured,
error: null,
};
if (entry.displayName) {
provider.displayName = entry.displayName;
}
provider.baseUrl = settings.baseUrl;
provider.api = settings.api;
return provider;
@@ -223,6 +227,9 @@ export class PaseoAgentConfigService {
...current.providers,
[input.name]: {
type: catalogEntry.id,
...((input.displayName ?? current.providers?.[input.name]?.displayName)
? { displayName: input.displayName ?? current.providers?.[input.name]?.displayName }
: {}),
options: providerOptionsForPersist(input.options, catalogEntry),
},
},
@@ -231,6 +238,27 @@ export class PaseoAgentConfigService {
return this.requireRedactedProvider(next, input.name);
}
renameProvider(name: string, displayName: string): RedactedPaseoAgentProviderConfig {
const trimmedDisplayName = displayName.trim();
const next = this.updateConfig((current) => {
const provider = current.providers?.[name];
if (!provider) {
throw new Error(`Paseo Agent provider '${name}' is not configured.`);
}
return PaseoAgentConfigSchema.parse({
...current,
providers: {
...current.providers,
[name]: {
...provider,
displayName: trimmedDisplayName,
},
},
});
});
return this.requireRedactedProvider(next, name);
}
removeProvider(name: string): boolean {
let removed = false;
this.updateConfig((current) => {
@@ -154,6 +154,21 @@ describe("resolvePaseoAgentCatalogAuth", () => {
restoreEnvVars(previousValues);
}
});
it("assigns icon names to every Paseo Agent catalog entry", () => {
const iconNames = new Map(
PASEO_AGENT_PROVIDER_CATALOG.map((entry) => [entry.id, entry.iconName]),
);
expect(iconNames).toEqual(
new Map([
["openrouter", "openrouter"],
["chatgpt", "openai"],
["kimi", "kimi"],
["opencode-go", "opencode"],
]),
);
});
});
describe("listPaseoAgentModels", () => {
@@ -45,6 +45,7 @@ const PaseoAgentProviderOptionsSchema = z
const PaseoAgentModelProviderSchema = z
.object({
type: z.string().min(1),
displayName: z.string().min(1).optional(),
options: PaseoAgentProviderOptionsSchema.default({}),
})
.strict();
+38
View File
@@ -1808,6 +1808,8 @@ export class Session {
return this.handlePaseoAgentSetProviderRequest(msg);
case "config.paseo_agent.remove_provider.request":
return this.handlePaseoAgentRemoveProviderRequest(msg);
case "config.paseo_agent.rename_provider.request":
return this.handlePaseoAgentRenameProviderRequest(msg);
case "config.paseo_agent.oauth.start.request":
return this.handlePaseoAgentOAuthStartRequest(msg);
case "config.paseo_agent.oauth.complete.request":
@@ -1931,6 +1933,7 @@ export class Session {
try {
const provider = this.createPaseoAgentConfigService().setProvider({
name: msg.name,
displayName: msg.displayName,
providerType: msg.providerType,
options: msg.options,
});
@@ -1993,6 +1996,41 @@ export class Session {
}
}
private async handlePaseoAgentRenameProviderRequest(
msg: Extract<SessionInboundMessage, { type: "config.paseo_agent.rename_provider.request" }>,
): Promise<void> {
try {
const provider = this.createPaseoAgentConfigService().renameProvider(
msg.name,
msg.displayName,
);
await this.refreshPaseoAgentRuntimeSnapshot();
this.emit({
type: "config.paseo_agent.rename_provider.response",
payload: {
requestId: msg.requestId,
success: true,
provider,
error: null,
},
});
} catch (error) {
this.sessionLogger.error(
{ err: error, providerName: msg.name },
"Failed to rename Paseo Agent provider",
);
this.emit({
type: "config.paseo_agent.rename_provider.response",
payload: {
requestId: msg.requestId,
success: false,
provider: null,
error: getErrorMessage(error),
},
});
}
}
private async handlePaseoAgentOAuthStartRequest(
msg: Extract<SessionInboundMessage, { type: "config.paseo_agent.oauth.start.request" }>,
): Promise<void> {