Skip to content

Commit ad764a4

Browse files
committed
feat: new agent adding function, and new support for adding agents from the url with url params
1 parent 55e801d commit ad764a4

File tree

1 file changed

+193
-150
lines changed

1 file changed

+193
-150
lines changed

src/routes/(app)/templates/create/+page.svelte

Lines changed: 193 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import * as Table from '$lib/components/ui/table/index.js';
3030
3131
import { cn } from '$lib/utils';
32-
import { idAsKey, type PublicRegistryAgent } from '$lib/threads';
32+
import { idAsKey, type PublicRegistryAgent, type Registry } from '$lib/threads';
3333
import { Session } from '$lib/session.svelte';
3434
import { tools } from '$lib/mcptools';
3535
@@ -69,6 +69,177 @@
6969
import CopyButton from '$lib/components/copy-button.svelte';
7070
import Pip from '$lib/components/pip.svelte';
7171
import OptionField from './OptionField.svelte';
72+
import { page } from '$app/state';
73+
74+
function sourceToRegistryId(source: AgentSource): RegistryAgentIdentifier['registrySourceId'] {
75+
switch (source) {
76+
case 'local':
77+
return { type: 'local' };
78+
79+
case 'marketplace':
80+
return { type: 'marketplace' };
81+
82+
case 'linked':
83+
return { type: 'linked', linkedServerId: 'default' };
84+
}
85+
}
86+
87+
const addAgent = async (name: string, source: any, version: string) => {
88+
try {
89+
const existingCount = $formData.agents.filter((a) => a.id.name === name).length;
90+
const registrySourceId = sourceToRegistryId(source as AgentSource);
91+
const detailed = await ctx.server
92+
.lookupAgent({ name, version, registrySourceId })
93+
.catch((e) => {
94+
toast.error(`${e}`);
95+
console.error(e);
96+
return null;
97+
});
98+
if (detailed) {
99+
try {
100+
$formData.agents.push({
101+
id: {
102+
name,
103+
version,
104+
registrySourceId
105+
},
106+
name: name + (existingCount > 0 ? `-${existingCount}` : ''),
107+
description: '',
108+
providerType: 'local',
109+
provider: {
110+
runtime: Object.keys(detailed.registryAgent.runtimes)[0] as any,
111+
remote_request: {
112+
maxCost: { type: 'micro_coral', amount: 1000 },
113+
serverSource: { type: 'servers', servers: [] }
114+
}
115+
},
116+
customToolAccess: new Set(),
117+
blocking: false,
118+
options: {}
119+
});
120+
detailedAgent = null;
121+
$formData.agents = $formData.agents;
122+
selectedAgent = $formData.agents.length - 1;
123+
} catch (error) {
124+
console.error('Failed to add agent:', error);
125+
}
126+
}
127+
} catch (error) {
128+
console.error('Failed to lookup agent:', error);
129+
}
130+
};
131+
132+
const AGENT_REGEX = /^(marketplace|linked|local):(.+?)@(\d+\.\d+\.\d+)$/;
133+
const agentsQuery = page.url.searchParams.get('agents');
134+
135+
type AgentSource = 'marketplace' | 'linked' | 'local';
136+
let parsedAgents: ParsedAgent[] = [];
137+
138+
onMount(async () => {
139+
try {
140+
const result = parseAgentsQuery(agentsQuery);
141+
parsedAgents = result.agents;
142+
143+
for (const agent of parsedAgents) {
144+
console.log(
145+
'following url instructions to add agent: ' +
146+
agent.name +
147+
'@' +
148+
agent.version +
149+
' from ' +
150+
agent.source +
151+
' '
152+
);
153+
await addAgent(agent.name, agent.source, agent.version);
154+
}
155+
} catch (err) {
156+
console.error('Failed to parse agents:', err);
157+
}
158+
});
159+
interface ParsedAgent {
160+
source: AgentSource;
161+
name: string;
162+
version: string;
163+
raw: string;
164+
}
165+
166+
function parseAgentsQuery(query: string | null) {
167+
if (!query) return { agents: [], errors: [] as string[] };
168+
169+
const agentsFromQuery: ParsedAgent[] = [];
170+
const errors: string[] = [];
171+
172+
for (const raw of query.split(',')) {
173+
const trimmed = raw.trim();
174+
if (!trimmed) continue;
175+
176+
const match = trimmed.match(AGENT_REGEX);
177+
178+
if (!match) {
179+
errors.push(`Invalid agent format: "${trimmed}"`);
180+
continue;
181+
}
182+
183+
const [, source, name, version] = match;
184+
185+
agentsFromQuery.push({
186+
source: source as AgentSource,
187+
name: name ?? '',
188+
version: version ?? '',
189+
raw: trimmed
190+
});
191+
}
192+
193+
return { agents: agentsFromQuery, errors };
194+
}
195+
196+
let lastDeletedAgent: {
197+
agent: any;
198+
index: number;
199+
} | null = $state(null);
200+
201+
const removeAgent = (index: number) => {
202+
if (index < 0 || index >= $formData.agents.length) return;
203+
204+
const agent = $formData.agents[index];
205+
206+
lastDeletedAgent = {
207+
agent,
208+
index
209+
};
210+
211+
$formData.agents.splice(index, 1);
212+
$formData.agents = $formData.agents;
213+
214+
// Maintain selection invariants
215+
if (selectedAgent !== null) {
216+
if (selectedAgent === index) {
217+
selectedAgent = 0;
218+
} else if (selectedAgent > index) {
219+
selectedAgent--;
220+
}
221+
}
222+
223+
toast(`Agent "${lastDeletedAgent.agent.name}" deleted`, {
224+
action: {
225+
label: 'Undo',
226+
onClick: restoreAgent
227+
}
228+
});
229+
};
230+
231+
const restoreAgent = () => {
232+
if (!lastDeletedAgent) return;
233+
234+
$formData.agents.splice(lastDeletedAgent.index, 0, lastDeletedAgent.agent);
235+
236+
$formData.agents = $formData.agents;
237+
toast.success('Agent "' + lastDeletedAgent.agent.name + '" restored');
238+
239+
selectedAgent = lastDeletedAgent.index;
240+
241+
lastDeletedAgent = null;
242+
};
72243
73244
type CreateSessionRequest = NonNullable<
74245
operations['createSession']['requestBody']
@@ -88,6 +259,9 @@
88259
let currentTab = $state('agent');
89260
90261
let sendingForm = $state(false);
262+
263+
let catalogsLoaded = $derived(Object.keys(ctx.server.catalogs).length > 0);
264+
91265
// svelte-ignore state_referenced_locally
92266
let form = superForm(defaults(zod4(formSchema)), {
93267
SPA: true,
@@ -110,7 +284,6 @@
110284
try {
111285
sendingForm = true;
112286
const body = await asJson;
113-
// console.log({ body });
114287
const res = await ctx.server.api.POST('/api/v1/sessions/{namespace}', {
115288
params: {
116289
path: { namespace: ctx.server.namespace }
@@ -358,121 +531,6 @@
358531
359532
const isMobile = new IsMobile();
360533
361-
const addAgent = async (agent: any) => {
362-
const catalog = Object.values(ctx.server.catalogs).at(0);
363-
364-
try {
365-
if (!agent) {
366-
throw new Error('No agents found in registry');
367-
}
368-
369-
if (!catalog) {
370-
throw new Error('Catalog failed to load');
371-
}
372-
373-
if (!Array.isArray(agent.versions) || agent.versions.length === 0) {
374-
throw new Error('Agent has no available versions');
375-
}
376-
377-
// Resolve detailed catalog info for this agent + version
378-
const detailed = await ctx.server.lookupAgent({
379-
name: agent.name,
380-
version: agent.versions[0],
381-
registrySourceId: { ...catalog.identifier }
382-
});
383-
384-
if (!detailed?.registryAgent?.runtimes) {
385-
throw new Error('Agent runtimes are missing from catalog');
386-
}
387-
388-
const runtimes = Object.keys(detailed.registryAgent.runtimes);
389-
if (runtimes.length === 0) {
390-
throw new Error('Agent has no supported runtimes');
391-
}
392-
393-
const runtime = runtimes[0] as any;
394-
395-
const existingCount = $formData.agents.filter((a) => a.id.name === agent.name).length;
396-
selectedAgent = null;
397-
398-
$formData.agents.push({
399-
id: {
400-
name: agent.name,
401-
version: agent.versions[0],
402-
registrySourceId: { ...catalog.identifier }
403-
},
404-
name: agent.name + (existingCount > 0 ? `-${existingCount}` : ''),
405-
description: '',
406-
provider: {
407-
remote_request: {
408-
maxCost: { type: 'micro_coral', amount: 1000 },
409-
serverSource: { type: 'servers', servers: [] }
410-
},
411-
runtime
412-
},
413-
providerType: 'local',
414-
customToolAccess: new Set(),
415-
blocking: false,
416-
options: {}
417-
});
418-
419-
detailedAgent = null;
420-
$formData.agents = $formData.agents;
421-
selectedAgent = $formData.agents.length - 1;
422-
} catch (err) {
423-
const message = err instanceof Error ? err.message : 'An unexpected error occurred';
424-
toast.error(message);
425-
}
426-
};
427-
428-
let lastDeletedAgent: {
429-
agent: any;
430-
index: number;
431-
} | null = $state(null);
432-
433-
const removeAgent = (index: number) => {
434-
if (index < 0 || index >= $formData.agents.length) return;
435-
436-
const agent = $formData.agents[index];
437-
438-
lastDeletedAgent = {
439-
agent,
440-
index
441-
};
442-
443-
$formData.agents.splice(index, 1);
444-
$formData.agents = $formData.agents;
445-
446-
// Maintain selection invariants
447-
if (selectedAgent !== null) {
448-
if (selectedAgent === index) {
449-
selectedAgent = 0;
450-
} else if (selectedAgent > index) {
451-
selectedAgent--;
452-
}
453-
}
454-
455-
toast(`Agent "${lastDeletedAgent.agent.name}" deleted`, {
456-
action: {
457-
label: 'Undo',
458-
onClick: restoreAgent
459-
}
460-
});
461-
};
462-
463-
const restoreAgent = () => {
464-
if (!lastDeletedAgent) return;
465-
466-
$formData.agents.splice(lastDeletedAgent.index, 0, lastDeletedAgent.agent);
467-
468-
$formData.agents = $formData.agents;
469-
toast.success('Agent "' + lastDeletedAgent.agent.name + '" restored');
470-
471-
selectedAgent = lastDeletedAgent.index;
472-
473-
lastDeletedAgent = null;
474-
};
475-
476534
const UNGROUPED = '__ungrouped';
477535
478536
let groupedOptions = $derived(
@@ -664,19 +722,24 @@
664722
<Command.Root>
665723
<Command.Input placeholder="Search agents..." />
666724
<Command.List>
667-
{#each Object.values(ctx.server.catalogs).map((catalog) => catalog) as catalog}
668-
<Command.Group heading={`${catalog.identifier.type}`}>
669-
{#each Object.values(ctx.server.catalogs).flatMap( (catalog) => Object.values(catalog.agents) ) as agent}
725+
{#each Object.values(ctx.server.catalogs) as catalog}
726+
<Command.Group heading={catalog.identifier.type}>
727+
{#each Object.values(catalog.agents) as agent}
670728
<HoverCard.Root>
671-
<HoverCard.Trigger class="m-0"
672-
><Command.Item
673-
class=" w-full cursor-pointer border-b px-4 py-2"
674-
onSelect={() => addAgent(agent)}
729+
<HoverCard.Trigger class="m-0">
730+
<Command.Item
731+
class="w-full cursor-pointer border-b px-4 py-2"
732+
onSelect={() =>
733+
addAgent(
734+
agent.name,
735+
catalog.identifier.type,
736+
agent.versions[0]!
737+
)}
675738
>
676739
<span class="grow">{agent.name}</span>
677-
<!-- <IconHeartRegular /> -->
678-
</Command.Item></HoverCard.Trigger
679-
>
740+
</Command.Item>
741+
</HoverCard.Trigger>
742+
680743
<HoverCard.Content
681744
side="right"
682745
class="max-w-1/2 min-w-full whitespace-pre-wrap"
@@ -787,29 +850,9 @@
787850
{#if $formData.agents.length !== 0}
788851
<Graph agents={$formData.agents} groups={$formData.groups} bind:selectedAgent />
789852
{:else}
790-
<Card.Root class="m-auto w-1/4">
791-
<Card.Header>
792-
<Card.Title>Session creator</Card.Title>
793-
</Card.Header>
794-
<Card.Content class="flex flex-col gap-2 text-sm ">
795-
<span>Sessions let agents coordinate.</span>
796-
797-
<span>Agents appear as nodes in a graph.</span>
798-
799-
<span>Connections represent agent groups.</span>
800-
</Card.Content>
801-
<Card.Footer>
802-
<Button
803-
class="grow {selectedAgent !== null &&
804-
$formData.agents.length > selectedAgent
805-
? ''
806-
: 'bg-accent/90'} w-fit truncate "
807-
onclick={addAgent}
808-
>
809-
<span>Add an agent</span>
810-
</Button>
811-
</Card.Footer>
812-
</Card.Root>
853+
<p>
854+
No agents added yet. Use the "Add agents" menu to add agents to your session.
855+
</p>
813856
{/if}
814857
</Tabs.Content>
815858
</Tabs.Root>

0 commit comments

Comments
 (0)