Skip to content

Commit 6dda155

Browse files
feat(designer): Foundry Agent Service httpClient migration, version picker, and bug fixes (#8886)
* refactor(designer): replace raw fetch() with IHttpClient in foundryAgentService Replace the custom foundryRequest wrapper (raw fetch + AbortController) with the shared IHttpClient abstraction used by all other services. This gives Foundry API calls consistent retry (axios-retry), error handling, and request patterns. - Add getHttpClient() to ICognitiveServiceService and BaseCognitiveServiceService - Refactor all foundryAgentService functions to accept IHttpClient as first param - Use httpClient.get/post with noAuth:true and custom Bearer headers - Update callers in designer and designer-v2 (useCognitiveService, foundryUpdates) - Update all tests to mock IHttpClient instead of globalThis.fetch Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(designer-ui): remove Tools section from FoundryAgentDetails panel Remove the Tools label, summary text, toolsList style, and related tests from the Foundry agent inline details component. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor(designer-ui): move "Edit in foundry portal" link below agent dropdown - Remove portal link rendering from FoundryAgentDetails component - Add FoundryPortalLink component in parametersTab (designer + designer-v2) - Change label to lowercase "Edit in foundry portal" - Fix duplicate NavigateIcon export; re-export useFoundryAgentDetailsStyles - Update tests to cover buildFoundryPortalUrl instead of removed UI Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(designer): add Foundry agent version picker, httpClient refactor, and post-save refresh - Add version picker dropdown to FoundryAgentDetails with auto-select of latest version on first load and stored version restoration - Add listFoundryAgentVersions API with data-plane + portal BFF fallback and resilient response parsing (extractVersionsData) - Add foundryAgentVersionNumber internal parameter to agent loop manifest - Add useFoundryAgentVersions React Query hook in both designer packages - Wire version change to update model, instructions, and portal link - Add post-save version refresh: flushPendingFoundryUpdates tracks flushed nodes, onFlushed callback invalidates versions cache - Refactor CognitiveServiceService: replace getHttpClient() getter with readonly httpClient property to match standard service pattern - Add 35 foundry service tests, 9 UI tests, and foundryUpdates tests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(designer): sync version number and instructions to workflow params on initial load On first agent load, the version number and system instructions were not written to the workflow parameters, causing validation errors (empty system message content). The initial-load effect now writes both foundryAgentVersionNumber and syncs instructions into the messages parameter alongside user instructions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(designer): prevent infinite update loop and uncontrolled dropdown warning - Use useRef flag for one-shot initial version sync instead of state guard that could race with batched Redux dispatches - Add value and selectedOptions props to disabled version dropdown to prevent uncontrolled-to-controlled transition Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(designer): fix consumeVersionRefresh race condition and simplify Foundry code - Add needsVersionRefresh() to check flag without consuming it - Only consume flag after confirming new version differs from stored - Extract shared helpers: useFoundryConnectionResourceId, getFoundryServiceContext - Simplify foundryAgentService query params and extractVersionsData - Update tests for needsVersionRefresh behavior in both designer packages Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(designer): remove unnecessary Foundry API call on version change, fix ReDoS and portal URL fallback - Version selection no longer queues a Foundry agent update API call - Portal URL omits ?version= when no version is selected (defaults to latest) - Fix polynomial regex in buildProjectEndpointFromResourceId (greedy [^/]+) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(designer): persist Foundry version change to workflow parameters The version picker dropdown was not updating the serialized workflow when changing versions. Two issues were fixed: 1. Type mismatch in handleVersionSelect: strict equality failed when the API returned version as a number but the dropdown provided a string. Added String() coercion for the comparison. 2. Stale preservedValue blocking serialization: dispatchParamUpdate updated the value segments but left preservedValue intact. The serializer checks preservedValue first, so it returned the old version number. Now clears preservedValue on every programmatic parameter update. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(designer): prevent generic agent UI flash for Foundry nodes When clicking a Foundry agent node, the agentModelType parameter is populated asynchronously via connection loading. Until it resolves, isAgentServiceConnection is false and the generic agent UI briefly renders before switching to the Foundry-specific view. Added isFoundryAgentPending check: if the node already has a foundryAgentId value, show the filtered Foundry-aware UI immediately instead of the generic fallback, eliminating the visual flash. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(designer): add tests for Foundry agent fixes - Add helpers.spec.ts: tests for isAgentConnectorAndAgentServiceModel covering Foundry params, missing model type, non-agent connector - Add dispatchParamUpdate.spec.ts: tests for preservedValue clearing and String coercion in parameter updates - Enhance foundryAgentDetails.spec.tsx: add handleVersionSelect logic tests for string/numeric version type coercion Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(designer): prevent Foundry pending state when switching to non-Foundry connection isFoundryAgentPending now checks that agentModelType is truly empty (not yet loaded) before returning true. Previously, switching from a Foundry connection to a non-Foundry connection left foundryAgentId with a stale value, causing the Foundry-filtered UI to persist and hide normal agent parameters. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(designer): consume version refresh flag unconditionally Move consumeVersionRefresh() before the version comparison check so the flag is always cleared after versions refetch. Previously, if a Foundry save didn't produce a new version number, the flag leaked and the effect would re-fire on every render. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(logic-apps-shared): replace polynomial regex with iterative slash trimming Replace /\/+$/ regex in normalizeEndpoint with a while-loop using endsWith/slice to avoid ReDoS on strings with many trailing slashes. Flagged by GitHub Advanced Security code scanning. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor(logic-apps-shared): remove portal fallback for foundry versions Simplify listFoundryAgentVersions by removing the fallback to the legacy Portal BFF endpoint. The service now relies exclusively on the standardized Data Plane API. - Remove projectResourceId parameter from listFoundryAgentVersions signature - Remove fallback logic and tests - Update call sites in designer and designer-v2 hooks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 2b1b914 commit 6dda155

File tree

24 files changed

+1513
-560
lines changed

24 files changed

+1513
-560
lines changed

Localize/lang/strings.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@
168168
"1nODUD": "Hide functions",
169169
"1nvvw1": "Enter the value of the Authorization header",
170170
"1pjO9s": "Hide source schema",
171+
"1r967W": "Edit in foundry portal",
171172
"1r9ljA": "Enter a valid URI.",
172173
"1tmN2o": "Workflow version",
173174
"1uGBLP": "5",
@@ -680,7 +681,6 @@
680681
"CyI9Au": "Dismiss",
681682
"CyT8H7": "Accepts 'Number', 'Integer', and 'Decimal' types.",
682683
"CypYLs": "Insert a new step between {parentName} and {childName}",
683-
"Cz5vTr": "Edit in Foundry Portal",
684684
"Czt6YV": "at {times}",
685685
"D+Ptnq": "Returns the path and query from a URI",
686686
"D/xTXV": "Show source schema",
@@ -1875,6 +1875,7 @@
18751875
"_1nODUD.comment": "Label to close Functions list",
18761876
"_1nvvw1.comment": "Raw Value Placeholder Text",
18771877
"_1pjO9s.comment": "Label to close source schema toolbox",
1878+
"_1r967W.comment": "Link to edit agent in Foundry Portal",
18781879
"_1r9ljA.comment": "Error validation message for URIs",
18791880
"_1tmN2o.comment": "Workflow version text",
18801881
"_1uGBLP.comment": "Hour of the day",
@@ -2387,7 +2388,6 @@
23872388
"_CyI9Au.comment": "Button text to dismiss the deprecation toast",
23882389
"_CyT8H7.comment": "Explains that numerical type allows three different number types",
23892390
"_CypYLs.comment": "Tooltip for the button to add a new step (action or branch)",
2390-
"_Cz5vTr.comment": "Link to edit agent in Foundry Portal",
23912391
"_Czt6YV.comment": "Recurrence schedule description at times",
23922392
"_D+Ptnq.comment": "Label for description of custom uriPathAndQuery Function",
23932393
"_D/xTXV.comment": "Label to open source schema toolbox",
@@ -3756,7 +3756,6 @@
37563756
"_haeWoU.comment": "Error message when splitOn cannot be evaluated",
37573757
"_hbOvB4.comment": "Dislike button text for suggested flow",
37583758
"_hbmiUp.comment": "Placeholder text for date picker",
3759-
"_hbwavm.comment": "Foundry agents version display",
37603759
"_hdN+aw.comment": "Description for host field",
37613760
"_hesDPs.comment": "Milliseconds",
37623761
"_hflWi6.comment": "Description for trimByteOrderMark function",
@@ -3942,6 +3941,7 @@
39423941
"_lbq5E1.comment": "description of upload content transfer setting",
39433942
"_lciYKh.comment": "Loading message for the MCP server workflows field",
39443943
"_lckgnb.comment": "Required collection parameter to apply reverse function on",
3944+
"_ld530c.comment": "Placeholder while agent versions load",
39453945
"_ldBi4y.comment": "Aria label for the toggle filters button",
39463946
"_ldn/IC.comment": "Test button",
39473947
"_leA1MT.comment": "Tooltip for URL endpoint connection parameter",
@@ -4395,6 +4395,7 @@
43954395
"_ur+ZvW.comment": "The tab label for the monitoring connections tab on the configure template wizard",
43964396
"_ur3P27.comment": "Suggest to the user to try a conversion function instead",
43974397
"_urAHv1.comment": "Hour of the day",
4398+
"_urJyNX.comment": "Placeholder for version dropdown",
43984399
"_urmf+A.comment": "Message informing that target element cannot be removed",
43994400
"_usCZ7R.comment": "Time zone value ",
44004401
"_uwuGU0.comment": "Time zone value ",
@@ -4972,7 +4973,6 @@
49724973
"haeWoU": "Failed to evaluate outputs because splitOn {splitOn} cannot be evaluated. As a result, this operation's outputs might not be correctly visible in subsequent actions",
49734974
"hbOvB4": "This isn't what I'm looking for",
49744975
"hbmiUp": "Select a date...",
4975-
"hbwavm": "Agents (v2)",
49764976
"hdN+aw": "Specifies which Logic App sku the template supports (e.g., Standard, Consumption).",
49774977
"hesDPs": "{count} Milliseconds",
49784978
"hflWi6": "Removes Byte Order Mark (BOM) characters from the beginning of strings or binary content.",
@@ -5158,6 +5158,7 @@
51585158
"lbq5E1": "Large messages may be split up into smaller requests to the connector to allow large message upload. More details can be found at http://aka.ms/logicapps-chunk#upload-content-in-chunks",
51595159
"lciYKh": "Loading workflows...",
51605160
"lckgnb": "Required. The collection to reverse.",
5161+
"ld530c": "Loading versions...",
51615162
"ldBi4y": "Toggle filters",
51625163
"ldn/IC": "Test",
51635164
"leA1MT": "The endpoint of the Cosmos DB account",
@@ -5611,6 +5612,7 @@
56115612
"ur+ZvW": "Connections",
56125613
"ur3P27": "Try using a Conversion function such as: {conversionFunctions}",
56135614
"urAHv1": "4",
5615+
"urJyNX": "Select a version",
56145616
"urmf+A": "Target schema element can't be deleted.",
56155617
"usCZ7R": "(UTC+11:00) Magadan",
56165618
"uwuGU0": "(UTC+06:30) Yangon (Rangoon)",

apps/Standalone/src/designer/app/AzureLogicAppsDesigner/DesignerCommandBar.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import {
4343
flushPendingFoundryUpdates,
4444
} from '@microsoft/logic-apps-designer';
4545
import { useMemo } from 'react';
46-
import { useMutation } from '@tanstack/react-query';
46+
import { useMutation, useQueryClient } from '@tanstack/react-query';
4747
import { useDispatch, useSelector } from 'react-redux';
4848
import LogicAppsIcon from '../../../assets/logicapp.svg';
4949
import { environment } from '../../../environments/environment';
@@ -100,6 +100,7 @@ export const DesignerCommandBar = ({
100100
}) => {
101101
const dispatch = useDispatch<AppDispatch>();
102102
const isCopilotReady = useNodesInitialized();
103+
const queryClient = useQueryClient();
103104
const { isLoading: isSaving, mutate: saveWorkflowMutate } = useMutation(async () => {
104105
const designerState = DesignerStore.getState();
105106
const serializedWorkflow = await serializeBJSWorkflow(designerState, {
@@ -135,7 +136,9 @@ export const DesignerCommandBar = ({
135136
const customCodeFilesWithData = getCustomCodeFilesWithData(designerState.customCode);
136137

137138
if (!hasParametersErrors) {
138-
await flushPendingFoundryUpdates().catch(console.error);
139+
await flushPendingFoundryUpdates(() => {
140+
queryClient.invalidateQueries({ queryKey: ['foundryAgentVersions'] });
141+
}).catch(console.error);
139142
await saveWorkflow(serializedWorkflow, customCodeFilesWithData, () => dispatch(resetDesignerDirtyState(undefined)));
140143
if (Object.keys(serializedWorkflow?.definition?.triggers ?? {}).length > 0) {
141144
updateCallbackUrl(designerState, dispatch);

apps/Standalone/src/designer/app/AzureLogicAppsDesigner/DesignerCommandBarV2.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ import {
5050
flushPendingFoundryUpdates,
5151
} from '@microsoft/logic-apps-designer-v2';
5252
import { useEffect, useMemo, useState } from 'react';
53-
import { useMutation } from '@tanstack/react-query';
53+
import { useMutation, useQueryClient } from '@tanstack/react-query';
5454
import { useDispatch, useSelector } from 'react-redux';
5555
import { environment } from '../../../environments/environment';
5656
import { isSuccessResponse } from './Services/HttpClient';
@@ -160,6 +160,7 @@ export const DesignerCommandBar = ({
160160

161161
const dispatch = useDispatch<AppDispatch>();
162162
const isCopilotReady = useNodesInitialized();
163+
const queryClient = useQueryClient();
163164
const { isLoading: isSaving, mutate: saveWorkflowMutate } = useMutation(async (autoSave?: boolean) => {
164165
try {
165166
setAutoSaving(autoSave ?? false);
@@ -197,7 +198,9 @@ export const DesignerCommandBar = ({
197198
const customCodeFilesWithData = getCustomCodeFilesWithData(designerState.customCode);
198199

199200
if (!hasParametersErrors || autoSave) {
200-
await flushPendingFoundryUpdates().catch(console.error);
201+
await flushPendingFoundryUpdates(() => {
202+
queryClient.invalidateQueries({ queryKey: ['foundryAgentVersions'] });
203+
}).catch(console.error);
201204
await saveWorkflow(serializedWorkflow, customCodeFilesWithData, () => dispatch(resetDesignerDirtyState(undefined)), autoSave);
202205
if (Object.keys(serializedWorkflow?.definition?.triggers ?? {}).length > 0) {
203206
updateCallbackUrl(designerState, dispatch);

libs/designer-ui/src/lib/foundryagentdetails/__test__/foundryAgentDetails.spec.tsx

Lines changed: 139 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import * as React from 'react';
22
import * as renderer from 'react-test-renderer';
33
import { IntlProvider } from 'react-intl';
44
import { describe, vi, it, expect } from 'vitest';
5-
import { FoundryAgentDetails } from '../index';
6-
import type { FoundryAgent, FoundryModel } from '@microsoft/logic-apps-shared';
5+
import { FoundryAgentDetails, buildFoundryPortalUrl } from '../index';
6+
import type { FoundryAgent, FoundryAgentVersion, FoundryModel } from '@microsoft/logic-apps-shared';
77

88
function renderWithIntl(component: React.ReactElement) {
99
return renderer.create(<IntlProvider locale="en">{component}</IntlProvider>);
@@ -27,68 +27,173 @@ describe('FoundryAgentDetails', () => {
2727
{ id: 'gpt-35-turbo', name: 'GPT-3.5 Turbo' },
2828
];
2929

30+
const baseVersions: FoundryAgentVersion[] = [
31+
{
32+
id: 'agent-1:3',
33+
name: 'agent-1',
34+
version: '3',
35+
description: '',
36+
created_at: 1772564608,
37+
metadata: {},
38+
object: 'agent.version',
39+
definition: { kind: 'prompt', model: 'gpt-4', instructions: 'v3 instructions' },
40+
},
41+
{
42+
id: 'agent-1:2',
43+
name: 'agent-1',
44+
version: '2',
45+
description: '',
46+
created_at: 1770322055,
47+
metadata: {},
48+
object: 'agent.version',
49+
definition: { kind: 'prompt', model: 'gpt-35-turbo', instructions: 'v2 instructions' },
50+
},
51+
];
52+
3053
it('should render without crashing', () => {
3154
const tree = renderWithIntl(
3255
<FoundryAgentDetails agent={baseAgent} models={baseModels} onModelChange={vi.fn()} onInstructionsChange={vi.fn()} />
3356
);
3457
expect(tree.toJSON()).toBeTruthy();
3558
});
3659

37-
it('should render version text', () => {
60+
it('should render with versions dropdown when versions are provided', () => {
3861
const tree = renderWithIntl(
39-
<FoundryAgentDetails agent={baseAgent} models={baseModels} onModelChange={vi.fn()} onInstructionsChange={vi.fn()} />
62+
<FoundryAgentDetails
63+
agent={baseAgent}
64+
models={baseModels}
65+
onModelChange={vi.fn()}
66+
onInstructionsChange={vi.fn()}
67+
versions={baseVersions}
68+
selectedVersion="3"
69+
onVersionChange={vi.fn()}
70+
/>
4071
);
4172
const json = tree.toJSON() as renderer.ReactTestRendererJSON;
4273
expect(json.children).toBeTruthy();
4374
expect(json.children!.length).toBeGreaterThan(0);
4475
});
4576

46-
it('should render tools summary as "None" when no tools', () => {
47-
const tree = renderWithIntl(
48-
<FoundryAgentDetails agent={baseAgent} models={baseModels} onModelChange={vi.fn()} onInstructionsChange={vi.fn()} />
49-
);
50-
expect(tree.toJSON()).toBeTruthy();
51-
});
52-
53-
it('should render portal link when projectResourceId is provided', () => {
54-
const resourceId = '/subscriptions/sub-1/resourceGroups/rg-1/providers/Microsoft.CognitiveServices/accounts/acct-1/projects/proj-1';
77+
it('should render with numeric version values from the API (runtime type mismatch)', () => {
78+
// The Foundry API may return version as a number despite the TypeScript interface declaring string.
79+
// The component must handle this gracefully via String() coercion.
80+
const numericVersions = baseVersions.map((v) => ({ ...v, version: Number(v.version) as unknown as string }));
5581
const tree = renderWithIntl(
5682
<FoundryAgentDetails
5783
agent={baseAgent}
5884
models={baseModels}
5985
onModelChange={vi.fn()}
6086
onInstructionsChange={vi.fn()}
61-
projectResourceId={resourceId}
87+
versions={numericVersions}
88+
selectedVersion="3"
89+
onVersionChange={vi.fn()}
6290
/>
6391
);
64-
const root = tree.root;
65-
// Find the Link element (rendered as an anchor with href)
66-
const links = root.findAll(
67-
(node) => node.props.href && typeof node.props.href === 'string' && node.props.href.startsWith('https://ai.azure.com/')
68-
);
69-
expect(links.length).toBeGreaterThan(0);
70-
expect(links[0].props.href).toContain('agent-1');
92+
const json = tree.toJSON() as renderer.ReactTestRendererJSON;
93+
expect(json.children).toBeTruthy();
94+
expect(json.children!.length).toBeGreaterThan(0);
7195
});
7296

73-
it('should not render portal link when projectResourceId is missing', () => {
97+
it('should render disabled version dropdown when no versions available', () => {
7498
const tree = renderWithIntl(
75-
<FoundryAgentDetails agent={baseAgent} models={baseModels} onModelChange={vi.fn()} onInstructionsChange={vi.fn()} />
99+
<FoundryAgentDetails agent={baseAgent} models={baseModels} onModelChange={vi.fn()} onInstructionsChange={vi.fn()} versions={[]} />
76100
);
77-
const root = tree.root;
78-
const links = root.findAll(
79-
(node) => node.props.href && typeof node.props.href === 'string' && node.props.href.startsWith('https://ai.azure.com/')
80-
);
81-
expect(links.length).toBe(0);
101+
expect(tree.toJSON()).toBeTruthy();
82102
});
83103

84-
it('should render agent with tools summary', () => {
85-
const agentWithTools: FoundryAgent = {
86-
...baseAgent,
87-
tools: [{ type: 'code_interpreter' }, { type: 'file_search' }],
88-
};
104+
it('should show loading placeholder when versions are loading', () => {
89105
const tree = renderWithIntl(
90-
<FoundryAgentDetails agent={agentWithTools} models={baseModels} onModelChange={vi.fn()} onInstructionsChange={vi.fn()} />
106+
<FoundryAgentDetails
107+
agent={baseAgent}
108+
models={baseModels}
109+
onModelChange={vi.fn()}
110+
onInstructionsChange={vi.fn()}
111+
versionsLoading={true}
112+
/>
91113
);
92114
expect(tree.toJSON()).toBeTruthy();
93115
});
94116
});
117+
118+
describe('handleVersionSelect logic (String coercion)', () => {
119+
// Extracted logic from handleVersionSelect to verify the String() coercion fix
120+
// works for both string and numeric version values from the API.
121+
function findVersion(versions: FoundryAgentVersion[], optionValue: string): FoundryAgentVersion | undefined {
122+
return versions.find((v) => String(v.version) === optionValue);
123+
}
124+
125+
const versions: FoundryAgentVersion[] = [
126+
{
127+
id: 'a:8',
128+
name: 'a',
129+
version: '8',
130+
description: '',
131+
created_at: 0,
132+
metadata: {},
133+
object: 'agent.version',
134+
definition: { kind: 'prompt', model: 'gpt-4', instructions: '' },
135+
},
136+
{
137+
id: 'a:7',
138+
name: 'a',
139+
version: '7',
140+
description: '',
141+
created_at: 0,
142+
metadata: {},
143+
object: 'agent.version',
144+
definition: { kind: 'prompt', model: 'gpt-4', instructions: '' },
145+
},
146+
];
147+
148+
it('should find version when version field is a string', () => {
149+
const result = findVersion(versions, '8');
150+
expect(result).toBeDefined();
151+
expect(result!.id).toBe('a:8');
152+
});
153+
154+
it('should find version when API returns version as a number (runtime type mismatch)', () => {
155+
const numericVersions = versions.map((v) => ({ ...v, version: Number(v.version) as unknown as string }));
156+
const result = findVersion(numericVersions, '8');
157+
expect(result).toBeDefined();
158+
expect(result!.id).toBe('a:8');
159+
});
160+
161+
it('should return undefined for non-existent version', () => {
162+
expect(findVersion(versions, '99')).toBeUndefined();
163+
});
164+
165+
it('should return undefined for empty optionValue', () => {
166+
expect(findVersion(versions, '')).toBeUndefined();
167+
});
168+
});
169+
170+
describe('buildFoundryPortalUrl', () => {
171+
it('should return a portal URL when given a valid project resource ID', () => {
172+
const resourceId = '/subscriptions/sub-1/resourceGroups/rg-1/providers/Microsoft.CognitiveServices/accounts/acct-1/projects/proj-1';
173+
const url = buildFoundryPortalUrl(resourceId, 'agent-1');
174+
expect(url).toContain('https://ai.azure.com/');
175+
expect(url).toContain('agent-1');
176+
});
177+
178+
it('should return undefined when projectResourceId is missing', () => {
179+
expect(buildFoundryPortalUrl(undefined, 'agent-1')).toBeUndefined();
180+
});
181+
182+
it('should return undefined for an invalid resource ID', () => {
183+
expect(buildFoundryPortalUrl('/subscriptions/sub-1/resourceGroups/rg-1', 'agent-1')).toBeUndefined();
184+
});
185+
186+
it('should use versionNumber as the version query param when provided', () => {
187+
const resourceId = '/subscriptions/sub-1/resourceGroups/rg-1/providers/Microsoft.CognitiveServices/accounts/acct-1/projects/proj-1';
188+
const url = buildFoundryPortalUrl(resourceId, 'agent-1', '5');
189+
expect(url).toContain('?version=5');
190+
expect(url).not.toContain('version=2');
191+
});
192+
193+
it('should omit version query param when versionNumber is not provided', () => {
194+
const resourceId = '/subscriptions/sub-1/resourceGroups/rg-1/providers/Microsoft.CognitiveServices/accounts/acct-1/projects/proj-1';
195+
const url = buildFoundryPortalUrl(resourceId, 'agent-1');
196+
expect(url).not.toContain('?version=');
197+
expect(url).toContain('/build/agents/agent-1/build');
198+
});
199+
});

0 commit comments

Comments
 (0)