mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-19 22:01:43 +00:00
feat(foundry): implement provider credential management (Claude, Codex)
Add credential extraction, injection, and UI for managing Claude and Codex OAuth credentials in sandbox environments. Credentials are stored per-user in the user actor, injected on task owner swap, and periodically re-extracted to capture token refreshes. Frontend account settings show provider sign-in status. Changes: - User actor: new userProviderCredentials table with upsert/get actions - Task workspace: extract/inject provider credentials, integrate with owner swap and polling - App snapshot: include provider credential status (anthropic/openai booleans) - Frontend: new Providers section in account settings Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
3895e34bdb
commit
c1a4895303
14 changed files with 481 additions and 11 deletions
|
|
@ -29,6 +29,9 @@ const signInRoute = createRoute({
|
|||
getParentRoute: () => rootRoute,
|
||||
path: "/signin",
|
||||
component: SignInRoute,
|
||||
validateSearch: (search: Record<string, unknown>): { error?: string } => ({
|
||||
error: typeof search.error === "string" ? search.error : undefined,
|
||||
}),
|
||||
});
|
||||
|
||||
const accountRoute = createRoute({
|
||||
|
|
@ -150,6 +153,7 @@ function IndexRoute() {
|
|||
|
||||
function SignInRoute() {
|
||||
const snapshot = useMockAppSnapshot();
|
||||
const { error } = signInRoute.useSearch();
|
||||
if (!isMockFrontendClient && isAppSnapshotBootstrapping(snapshot)) {
|
||||
return <AppLoadingScreen label="Restoring Foundry session..." />;
|
||||
}
|
||||
|
|
@ -157,7 +161,7 @@ function SignInRoute() {
|
|||
return <IndexRoute />;
|
||||
}
|
||||
|
||||
return <MockSignInPage />;
|
||||
return <MockSignInPage error={error} />;
|
||||
}
|
||||
|
||||
function AccountRoute() {
|
||||
|
|
|
|||
|
|
@ -188,10 +188,16 @@ function MemberRow({ member }: { member: FoundryOrganizationMember }) {
|
|||
);
|
||||
}
|
||||
|
||||
export function MockSignInPage() {
|
||||
const AUTH_ERROR_MESSAGES: Record<string, string> = {
|
||||
please_restart_the_process: "Sign-in failed. Please try again.",
|
||||
state_mismatch: "Sign-in session expired. Please try again.",
|
||||
};
|
||||
|
||||
export function MockSignInPage({ error }: { error?: string }) {
|
||||
const client = useMockAppClient();
|
||||
const navigate = useNavigate();
|
||||
const t = useFoundryTokens();
|
||||
const errorMessage = error ? (AUTH_ERROR_MESSAGES[error] ?? `Sign-in error: ${error}`) : undefined;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -253,6 +259,25 @@ export function MockSignInPage() {
|
|||
Connect your GitHub account to get started.
|
||||
</p>
|
||||
|
||||
{errorMessage && (
|
||||
<p
|
||||
style={{
|
||||
fontSize: "13px",
|
||||
fontWeight: 500,
|
||||
color: "#f85149",
|
||||
margin: "0 0 16px 0",
|
||||
lineHeight: 1.5,
|
||||
padding: "10px 14px",
|
||||
background: "rgba(248, 81, 73, 0.1)",
|
||||
borderRadius: "8px",
|
||||
width: "100%",
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
{errorMessage}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* GitHub sign-in button */}
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -1172,6 +1197,8 @@ export function MockAccountSettingsPage() {
|
|||
</div>
|
||||
</SettingsContentSection>
|
||||
|
||||
<ProviderCredentialsSection />
|
||||
|
||||
<SettingsContentSection title="Sessions" description="Manage your active sessions across devices.">
|
||||
<SettingsRow label="Current session" description="This device — signed in via GitHub OAuth." />
|
||||
</SettingsContentSection>
|
||||
|
|
@ -1222,6 +1249,48 @@ export function MockAccountSettingsPage() {
|
|||
);
|
||||
}
|
||||
|
||||
function ProviderCredentialsSection() {
|
||||
const snapshot = useMockAppSnapshot();
|
||||
const t = useFoundryTokens();
|
||||
const providerCredentials = snapshot.providerCredentials ?? { anthropic: false, openai: false };
|
||||
|
||||
const providers = [
|
||||
{
|
||||
key: "anthropic" as const,
|
||||
label: "Claude",
|
||||
description: "Anthropic's Claude AI assistant.",
|
||||
signedIn: providerCredentials.anthropic,
|
||||
},
|
||||
{
|
||||
key: "openai" as const,
|
||||
label: "Codex",
|
||||
description: "OpenAI's Codex coding agent.",
|
||||
signedIn: providerCredentials.openai,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<SettingsContentSection title="Provider Credentials" description="Sign in to AI providers to use them in your tasks.">
|
||||
{providers.map((provider) => (
|
||||
<SettingsRow
|
||||
key={provider.key}
|
||||
label={provider.label}
|
||||
description={provider.signedIn ? "Signed in" : "Not signed in"}
|
||||
action={
|
||||
provider.signedIn ? (
|
||||
<span style={{ ...badgeStyle(t, "rgba(52, 211, 153, 0.12)", "rgb(52, 211, 153)"), fontSize: "10px" }}>Connected</span>
|
||||
) : (
|
||||
<button type="button" style={secondaryButtonStyle(t)}>
|
||||
Sign in
|
||||
</button>
|
||||
)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</SettingsContentSection>
|
||||
);
|
||||
}
|
||||
|
||||
function AppearanceSection() {
|
||||
const { colorMode, setColorMode } = useColorMode();
|
||||
const t = useFoundryTokens();
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ const EMPTY_APP_SNAPSHOT: FoundryAppSnapshot = {
|
|||
skippedAt: null,
|
||||
},
|
||||
},
|
||||
providerCredentials: { anthropic: false, openai: false },
|
||||
users: [],
|
||||
organizations: [],
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue