feat(FN-3434): add Claude OAuth credential interop with verification and st
This merge lands four major features: the desktop app gains shell onboarding with remote mode support via a new `DesktopModeChooser` and `shell-settings` module (FN-3399); the dashboard gains full archived insights support with the `InsightsView` redesign and `useInsights` hook overhaul (FN-3315); C Fusion-Task-Id: FN-3434
This commit is contained in:
@@ -148,8 +148,8 @@ describe("wrapAuthStorageWithApiKeyProviders", () => {
|
||||
expect(await storage.getApiKey("openai-codex")).toBe("legacy-access-token");
|
||||
});
|
||||
|
||||
describe("Anthropic reclassification from OAuth to API key", () => {
|
||||
it("filters anthropic out of getOAuthProviders even when upstream reports it as OAuth", () => {
|
||||
describe("Anthropic provider classification", () => {
|
||||
it("keeps anthropic in getOAuthProviders when upstream reports it as OAuth", () => {
|
||||
const fusionAuth = makeAuthStorage();
|
||||
fusionAuth.getOAuthProviders = vi.fn(() => [
|
||||
{ id: "anthropic", name: "Anthropic" },
|
||||
@@ -161,11 +161,11 @@ describe("wrapAuthStorageWithApiKeyProviders", () => {
|
||||
const oauthProviders = wrapped.getOAuthProviders();
|
||||
|
||||
const oauthIds = oauthProviders.map((p) => p.id);
|
||||
expect(oauthIds).not.toContain("anthropic");
|
||||
expect(oauthIds).toContain("anthropic");
|
||||
expect(oauthIds).toContain("github-copilot");
|
||||
});
|
||||
|
||||
it("includes anthropic in getApiKeyProviders with correct display name", () => {
|
||||
it("does not duplicate anthropic in getApiKeyProviders when OAuth-backed", () => {
|
||||
const fusionAuth = makeAuthStorage();
|
||||
fusionAuth.getOAuthProviders = vi.fn(() => [
|
||||
{ id: "anthropic", name: "Anthropic" },
|
||||
@@ -176,8 +176,7 @@ describe("wrapAuthStorageWithApiKeyProviders", () => {
|
||||
const apiKeyProviders = wrapped.getApiKeyProviders();
|
||||
|
||||
const anthropic = apiKeyProviders.find((p) => p.id === "anthropic");
|
||||
expect(anthropic).toBeDefined();
|
||||
expect(anthropic!.name).toBe("Anthropic");
|
||||
expect(anthropic).toBeUndefined();
|
||||
});
|
||||
|
||||
it("stores anthropic credentials as api_key type", () => {
|
||||
|
||||
@@ -18,6 +18,13 @@ export function getCodexCliAuthPath(home = process.env.HOME || process.env.USERP
|
||||
return join(home, ".codex", "auth.json");
|
||||
}
|
||||
|
||||
export function getClaudeCodeCredentialPaths(home = process.env.HOME || process.env.USERPROFILE || homedir()): string[] {
|
||||
return [
|
||||
join(home, ".claude", ".credentials.json"),
|
||||
join(home, ".config", "claude", ".credentials.json"),
|
||||
];
|
||||
}
|
||||
|
||||
export function getLegacyAuthPaths(home = process.env.HOME || process.env.USERPROFILE || homedir()): string[] {
|
||||
return [
|
||||
join(home, ".pi", "agent", "auth.json"),
|
||||
|
||||
@@ -61,7 +61,7 @@ import {
|
||||
} from "./llama-cpp-extension.js";
|
||||
import { resolveSelfExtension } from "./self-extension.js";
|
||||
import { createReadOnlyAuthFileStorage, mergeAuthStorageReads, wrapAuthStorageWithApiKeyProviders } from "./provider-auth.js";
|
||||
import { getCodexCliAuthPath, getFusionAuthPath, getLegacyAuthPaths, getModelRegistryModelsPath, getPackageManagerAgentDir } from "./auth-paths.js";
|
||||
import { getClaudeCodeCredentialPaths, getCodexCliAuthPath, getFusionAuthPath, getLegacyAuthPaths, getModelRegistryModelsPath, getPackageManagerAgentDir } from "./auth-paths.js";
|
||||
import { resolveProject } from "../project-context.js";
|
||||
import { ensureBundledDependencyGraphPluginInstalled } from "../plugins/bundled-plugin-install.js";
|
||||
import { syncStartupModels } from "./startup-model-sync.js";
|
||||
@@ -424,6 +424,7 @@ export async function runDaemon(opts: DaemonOptions = {}) {
|
||||
const supplementalAuthStorage = createReadOnlyAuthFileStorage([
|
||||
...getLegacyAuthPaths(),
|
||||
getCodexCliAuthPath(),
|
||||
...getClaudeCodeCredentialPaths(),
|
||||
]);
|
||||
const mergedAuthStorage = mergeAuthStorageReads(authStorage, [supplementalAuthStorage]);
|
||||
const modelRegistry = ModelRegistry.create(mergedAuthStorage, getModelRegistryModelsPath());
|
||||
|
||||
@@ -37,7 +37,7 @@ import {
|
||||
import { promptForPort } from "./port-prompt.js";
|
||||
import { createReadOnlyProviderSettingsView } from "./provider-settings.js";
|
||||
import { createReadOnlyAuthFileStorage, mergeAuthStorageReads, wrapAuthStorageWithApiKeyProviders } from "./provider-auth.js";
|
||||
import { getCodexCliAuthPath, getFusionAuthPath, getLegacyAuthPaths, getModelRegistryModelsPath, getPackageManagerAgentDir } from "./auth-paths.js";
|
||||
import { getClaudeCodeCredentialPaths, getCodexCliAuthPath, getFusionAuthPath, getLegacyAuthPaths, getModelRegistryModelsPath, getPackageManagerAgentDir } from "./auth-paths.js";
|
||||
import { resolveProject } from "../project-context.js";
|
||||
import {
|
||||
ensureClaudeSkillsForAllProjectsOnStartup,
|
||||
@@ -1220,6 +1220,7 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
||||
const supplementalAuthStorage = createReadOnlyAuthFileStorage([
|
||||
...getLegacyAuthPaths(),
|
||||
getCodexCliAuthPath(),
|
||||
...getClaudeCodeCredentialPaths(),
|
||||
]);
|
||||
const mergedAuthStorage = mergeAuthStorageReads(authStorage, [supplementalAuthStorage]);
|
||||
const modelRegistry = ModelRegistry.create(mergedAuthStorage, getModelRegistryModelsPath());
|
||||
|
||||
@@ -41,18 +41,7 @@ interface ReadFallbackAuthStorage {
|
||||
|
||||
type StoredCredential = StoredAuthCredential;
|
||||
|
||||
/**
|
||||
* Provider IDs that should be treated as OAuth-backed by the upstream
|
||||
* pi-coding-agent AuthStorage but which Fusion reclassifies as API-key
|
||||
* providers. These IDs are stripped from getOAuthProviders() results so
|
||||
* the dashboard never offers a browser-based OAuth login for them.
|
||||
*/
|
||||
const OAUTH_TO_API_KEY_RECLASSIFICATIONS: ReadonlySet<string> = new Set([
|
||||
"anthropic",
|
||||
]);
|
||||
|
||||
const BUILT_IN_API_KEY_PROVIDERS: Array<{ id: string; name: string }> = [
|
||||
{ id: "anthropic", name: "Anthropic" },
|
||||
{ id: "brave", name: "Brave Search" },
|
||||
{ id: "kimi-coding", name: "Kimi" },
|
||||
{ id: "minimax", name: "Minimax" },
|
||||
@@ -90,7 +79,6 @@ export function wrapAuthStorageWithApiKeyProviders(
|
||||
getOAuthProviders: () =>
|
||||
mergedAuthStorage
|
||||
.getOAuthProviders()
|
||||
.filter((provider) => !OAUTH_TO_API_KEY_RECLASSIFICATIONS.has(provider.id))
|
||||
.map((provider) => ({ id: provider.id, name: provider.name })),
|
||||
hasAuth: (provider) => mergedAuthStorage.hasAuth(provider),
|
||||
login: (providerId, callbacks) =>
|
||||
@@ -100,12 +88,9 @@ export function wrapAuthStorageWithApiKeyProviders(
|
||||
),
|
||||
logout: (provider) => mergedAuthStorage.logout(provider),
|
||||
getApiKeyProviders: () => {
|
||||
// Use the reclassified (filtered) OAuth provider list so that providers
|
||||
// moved to API-key (e.g. anthropic) are not skipped by the OAuth dedup.
|
||||
const oauthProviderIds = new Set(
|
||||
mergedAuthStorage
|
||||
.getOAuthProviders()
|
||||
.filter((provider) => !OAUTH_TO_API_KEY_RECLASSIFICATIONS.has(provider.id))
|
||||
.map((provider) => provider.id),
|
||||
);
|
||||
const providers = new Map<string, string>();
|
||||
|
||||
@@ -41,7 +41,7 @@ import {
|
||||
import { promptForPort } from "./port-prompt.js";
|
||||
import { createReadOnlyProviderSettingsView } from "./provider-settings.js";
|
||||
import { createReadOnlyAuthFileStorage, mergeAuthStorageReads, wrapAuthStorageWithApiKeyProviders } from "./provider-auth.js";
|
||||
import { getCodexCliAuthPath, getFusionAuthPath, getLegacyAuthPaths, getModelRegistryModelsPath, getPackageManagerAgentDir } from "./auth-paths.js";
|
||||
import { getClaudeCodeCredentialPaths, getCodexCliAuthPath, getFusionAuthPath, getLegacyAuthPaths, getModelRegistryModelsPath, getPackageManagerAgentDir } from "./auth-paths.js";
|
||||
import { resolveProject } from "../project-context.js";
|
||||
import {
|
||||
ensureClaudeSkillsForAllProjectsOnStartup,
|
||||
@@ -487,6 +487,7 @@ export async function runServe(
|
||||
const supplementalAuthStorage = createReadOnlyAuthFileStorage([
|
||||
...getLegacyAuthPaths(),
|
||||
getCodexCliAuthPath(),
|
||||
...getClaudeCodeCredentialPaths(),
|
||||
]);
|
||||
const mergedAuthStorage = mergeAuthStorageReads(authStorage, [supplementalAuthStorage]);
|
||||
const modelRegistry = ModelRegistry.create(mergedAuthStorage, getModelRegistryModelsPath());
|
||||
|
||||
Reference in New Issue
Block a user