Skip to content

Commit 40ef2d5

Browse files
authored
fix(frontend): auto-select credentials correctly in old builder (#11815)
## Changes 🏗️ On the **Old Builder**, when running an agent... ### Before <img width="800" height="614" alt="Screenshot 2026-01-21 at 21 27 05" src="https://github.com/user-attachments/assets/a3b2ec17-597f-44d2-9130-9e7931599c38" /> Credentials are there, but it is not recognising them, you need to click on them to be selected ### After <img width="1029" height="728" alt="Screenshot 2026-01-21 at 21 26 47" src="https://github.com/user-attachments/assets/c6e83846-6048-439e-919d-6807674f2d5a" /> It uses the new credentials UI and correctly auto-selects existing ones. ### Other Fixed a small timezone display glitch on the new library view. ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] Run agent in old builder - [x] Credentials are auto-selected and using the new collapsed system credentials UI
1 parent b714c0c commit 40ef2d5

File tree

4 files changed

+192
-68
lines changed

4 files changed

+192
-68
lines changed

autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/components/agent-run-draft-view.tsx

Lines changed: 126 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
"use client";
2-
import React, { useCallback, useEffect, useMemo, useState } from "react";
2+
import React, {
3+
useCallback,
4+
useContext,
5+
useEffect,
6+
useMemo,
7+
useState,
8+
} from "react";
39

410
import {
511
CredentialsMetaInput,
12+
CredentialsType,
613
GraphExecutionID,
714
GraphMeta,
815
LibraryAgentPreset,
@@ -29,14 +36,19 @@ import {
2936
} from "@/components/__legacy__/ui/icons";
3037
import { Input } from "@/components/__legacy__/ui/input";
3138
import { Button } from "@/components/atoms/Button/Button";
32-
import { CredentialsInput } from "@/components/contextual/CredentialsInput/CredentialsInput";
39+
import { CredentialsGroupedView } from "@/components/contextual/CredentialsInput/components/CredentialsGroupedView/CredentialsGroupedView";
40+
import {
41+
findSavedCredentialByProviderAndType,
42+
findSavedUserCredentialByProviderAndType,
43+
} from "@/components/contextual/CredentialsInput/components/CredentialsGroupedView/helpers";
3344
import { InformationTooltip } from "@/components/molecules/InformationTooltip/InformationTooltip";
3445
import {
3546
useToast,
3647
useToastOnFail,
3748
} from "@/components/molecules/Toast/use-toast";
3849
import { humanizeCronExpression } from "@/lib/cron-expression-utils";
3950
import { cn, isEmpty } from "@/lib/utils";
51+
import { CredentialsProvidersContext } from "@/providers/agent-credentials/credentials-provider";
4052
import { ClockIcon, CopyIcon, InfoIcon } from "@phosphor-icons/react";
4153
import { CalendarClockIcon, Trash2Icon } from "lucide-react";
4254

@@ -90,6 +102,7 @@ export function AgentRunDraftView({
90102
const api = useBackendAPI();
91103
const { toast } = useToast();
92104
const toastOnFail = useToastOnFail();
105+
const allProviders = useContext(CredentialsProvidersContext);
93106

94107
const [inputValues, setInputValues] = useState<Record<string, any>>({});
95108
const [inputCredentials, setInputCredentials] = useState<
@@ -128,6 +141,77 @@ export function AgentRunDraftView({
128141
() => graph.credentials_input_schema.properties,
129142
[graph],
130143
);
144+
const credentialFields = useMemo(
145+
function getCredentialFields() {
146+
return Object.entries(agentCredentialsInputFields);
147+
},
148+
[agentCredentialsInputFields],
149+
);
150+
const requiredCredentials = useMemo(
151+
function getRequiredCredentials() {
152+
return new Set(
153+
(graph.credentials_input_schema?.required as string[]) || [],
154+
);
155+
},
156+
[graph.credentials_input_schema?.required],
157+
);
158+
159+
useEffect(
160+
function initializeDefaultCredentials() {
161+
if (!allProviders) return;
162+
if (!graph.credentials_input_schema?.properties) return;
163+
if (requiredCredentials.size === 0) return;
164+
165+
setInputCredentials(function updateCredentials(currentCreds) {
166+
const next = { ...currentCreds };
167+
let didAdd = false;
168+
169+
for (const key of requiredCredentials) {
170+
if (next[key]) continue;
171+
const schema = graph.credentials_input_schema.properties[key];
172+
if (!schema) continue;
173+
174+
const providerNames = schema.credentials_provider || [];
175+
const credentialTypes = schema.credentials_types || [];
176+
const requiredScopes = schema.credentials_scopes;
177+
178+
const userCredential = findSavedUserCredentialByProviderAndType(
179+
providerNames,
180+
credentialTypes,
181+
requiredScopes,
182+
allProviders,
183+
);
184+
185+
const savedCredential =
186+
userCredential ||
187+
findSavedCredentialByProviderAndType(
188+
providerNames,
189+
credentialTypes,
190+
requiredScopes,
191+
allProviders,
192+
);
193+
194+
if (!savedCredential) continue;
195+
196+
next[key] = {
197+
id: savedCredential.id,
198+
provider: savedCredential.provider,
199+
type: savedCredential.type as CredentialsType,
200+
title: savedCredential.title,
201+
};
202+
didAdd = true;
203+
}
204+
205+
if (!didAdd) return currentCreds;
206+
return next;
207+
});
208+
},
209+
[
210+
allProviders,
211+
graph.credentials_input_schema?.properties,
212+
requiredCredentials,
213+
],
214+
);
131215

132216
const [allRequiredInputsAreSet, missingInputs] = useMemo(() => {
133217
const nonEmptyInputs = new Set(
@@ -145,18 +229,35 @@ export function AgentRunDraftView({
145229
);
146230
return [isSuperset, difference];
147231
}, [agentInputSchema.required, inputValues]);
148-
const [allCredentialsAreSet, missingCredentials] = useMemo(() => {
149-
const availableCredentials = new Set(Object.keys(inputCredentials));
150-
const allCredentials = new Set(Object.keys(agentCredentialsInputFields));
151-
// Backwards-compatible implementation of isSupersetOf and difference
152-
const isSuperset = Array.from(allCredentials).every((item) =>
153-
availableCredentials.has(item),
154-
);
155-
const difference = Array.from(allCredentials).filter(
156-
(item) => !availableCredentials.has(item),
157-
);
158-
return [isSuperset, difference];
159-
}, [agentCredentialsInputFields, inputCredentials]);
232+
const [allCredentialsAreSet, missingCredentials] = useMemo(
233+
function getCredentialStatus() {
234+
const missing = Array.from(requiredCredentials).filter((key) => {
235+
const cred = inputCredentials[key];
236+
return !cred || !cred.id;
237+
});
238+
return [missing.length === 0, missing];
239+
},
240+
[requiredCredentials, inputCredentials],
241+
);
242+
function addChangedCredentials(prev: Set<keyof LibraryAgentPresetUpdatable>) {
243+
const next = new Set(prev);
244+
next.add("credentials");
245+
return next;
246+
}
247+
248+
function handleCredentialChange(key: string, value?: CredentialsMetaInput) {
249+
setInputCredentials(function updateInputCredentials(currentCreds) {
250+
const next = { ...currentCreds };
251+
if (value === undefined) {
252+
delete next[key];
253+
return next;
254+
}
255+
next[key] = value;
256+
return next;
257+
});
258+
setChangedPresetAttributes(addChangedCredentials);
259+
}
260+
160261
const notifyMissingInputs = useCallback(
161262
(needPresetName: boolean = true) => {
162263
const allMissingFields = (
@@ -649,35 +750,6 @@ export function AgentRunDraftView({
649750
</>
650751
)}
651752

652-
{/* Credentials inputs */}
653-
{Object.entries(agentCredentialsInputFields).map(
654-
([key, inputSubSchema]) => (
655-
<CredentialsInput
656-
key={key}
657-
schema={{ ...inputSubSchema, discriminator: undefined }}
658-
selectedCredentials={
659-
inputCredentials[key] ?? inputSubSchema.default
660-
}
661-
onSelectCredentials={(value) => {
662-
setInputCredentials((obj) => {
663-
const newObj = { ...obj };
664-
if (value === undefined) {
665-
delete newObj[key];
666-
return newObj;
667-
}
668-
return {
669-
...obj,
670-
[key]: value,
671-
};
672-
});
673-
setChangedPresetAttributes((prev) =>
674-
prev.add("credentials"),
675-
);
676-
}}
677-
/>
678-
),
679-
)}
680-
681753
{/* Regular inputs */}
682754
{Object.entries(agentInputFields).map(([key, inputSubSchema]) => (
683755
<RunAgentInputs
@@ -695,6 +767,17 @@ export function AgentRunDraftView({
695767
data-testid={`agent-input-${key}`}
696768
/>
697769
))}
770+
771+
{/* Credentials inputs */}
772+
{credentialFields.length > 0 && (
773+
<CredentialsGroupedView
774+
credentialFields={credentialFields}
775+
requiredCredentials={requiredCredentials}
776+
inputCredentials={inputCredentials}
777+
inputValues={inputValues}
778+
onCredentialChange={handleCredentialChange}
779+
/>
780+
)}
698781
</CardContent>
699782
</Card>
700783
</div>

autogpt_platform/frontend/src/components/contextual/CredentialsInput/components/CredentialsGroupedView/helpers.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { CredentialsProvidersContextType } from "@/providers/agent-credentials/credentials-provider";
2-
import { getSystemCredentials } from "../../helpers";
2+
import { filterSystemCredentials, getSystemCredentials } from "../../helpers";
33

44
export type CredentialField = [string, any];
55

@@ -208,3 +208,42 @@ export function findSavedCredentialByProviderAndType(
208208

209209
return undefined;
210210
}
211+
212+
export function findSavedUserCredentialByProviderAndType(
213+
providerNames: string[],
214+
credentialTypes: string[],
215+
requiredScopes: string[] | undefined,
216+
allProviders: CredentialsProvidersContextType | null,
217+
): SavedCredential | undefined {
218+
for (const providerName of providerNames) {
219+
const providerData = allProviders?.[providerName];
220+
if (!providerData) continue;
221+
222+
const userCredentials = filterSystemCredentials(
223+
providerData.savedCredentials ?? [],
224+
);
225+
226+
const matchingCredentials: SavedCredential[] = [];
227+
228+
for (const credential of userCredentials) {
229+
const typeMatches =
230+
credentialTypes.length === 0 ||
231+
credentialTypes.includes(credential.type);
232+
const scopesMatch = hasRequiredScopes(credential, requiredScopes);
233+
234+
if (!typeMatches) continue;
235+
if (!scopesMatch) continue;
236+
237+
matchingCredentials.push(credential as SavedCredential);
238+
}
239+
240+
if (matchingCredentials.length === 1) {
241+
return matchingCredentials[0];
242+
}
243+
if (matchingCredentials.length > 1) {
244+
return undefined;
245+
}
246+
}
247+
248+
return undefined;
249+
}

autogpt_platform/frontend/src/components/contextual/CredentialsInput/useCredentialsInput.ts

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -98,39 +98,36 @@ export function useCredentialsInput({
9898

9999
// Auto-select the first available credential on initial mount
100100
// Once a user has made a selection, we don't override it
101-
useEffect(() => {
102-
if (readOnly) return;
103-
if (!credentials || !("savedCredentials" in credentials)) return;
104-
105-
// If already selected, don't auto-select
106-
if (selectedCredential?.id) return;
101+
useEffect(
102+
function autoSelectCredential() {
103+
if (readOnly) return;
104+
if (!credentials || !("savedCredentials" in credentials)) return;
105+
if (selectedCredential?.id) return;
107106

108-
// Only attempt auto-selection once
109-
if (hasAttemptedAutoSelect.current) return;
110-
hasAttemptedAutoSelect.current = true;
107+
const savedCreds = credentials.savedCredentials;
108+
if (savedCreds.length === 0) return;
111109

112-
// If optional, don't auto-select (user can choose "None")
113-
if (isOptional) return;
110+
if (hasAttemptedAutoSelect.current) return;
111+
hasAttemptedAutoSelect.current = true;
114112

115-
const savedCreds = credentials.savedCredentials;
113+
if (isOptional) return;
116114

117-
// Auto-select the first credential if any are available
118-
if (savedCreds.length > 0) {
119115
const cred = savedCreds[0];
120116
onSelectCredential({
121117
id: cred.id,
122118
type: cred.type,
123119
provider: credentials.provider,
124120
title: (cred as any).title,
125121
});
126-
}
127-
}, [
128-
credentials,
129-
selectedCredential?.id,
130-
readOnly,
131-
isOptional,
132-
onSelectCredential,
133-
]);
122+
},
123+
[
124+
credentials,
125+
selectedCredential?.id,
126+
readOnly,
127+
isOptional,
128+
onSelectCredential,
129+
],
130+
);
134131

135132
if (
136133
!credentials ||

autogpt_platform/frontend/src/lib/timezone-utils.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,14 @@ export function getTimezoneDisplayName(timezone: string): string {
106106
const parts = timezone.split("/");
107107
const city = parts[parts.length - 1].replace(/_/g, " ");
108108
const abbr = getTimezoneAbbreviation(timezone);
109-
return abbr ? `${city} (${abbr})` : city;
109+
if (abbr && abbr !== timezone) {
110+
return `${city} (${abbr})`;
111+
}
112+
// If abbreviation is same as timezone or not found, show timezone with underscores replaced
113+
const timezoneDisplay = timezone.replace(/_/g, " ");
114+
return `${city} (${timezoneDisplay})`;
110115
} catch {
111-
return timezone;
116+
return timezone.replace(/_/g, " ");
112117
}
113118
}
114119

0 commit comments

Comments
 (0)