create-loopengine 0.1.17 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/template/adapters/http.ts +88 -1
package/package.json
CHANGED
|
@@ -36,9 +36,14 @@ import {
|
|
|
36
36
|
synthesizeCreateModelCall,
|
|
37
37
|
editAgentFile,
|
|
38
38
|
createHttpTool,
|
|
39
|
+
updateHttpTool,
|
|
40
|
+
readHttpToolSpec,
|
|
41
|
+
listEditableHttpToolNames,
|
|
39
42
|
HttpToolNameError,
|
|
40
43
|
HttpToolExistsError,
|
|
41
44
|
HttpToolIndexShapeError,
|
|
45
|
+
HttpToolNotFoundError,
|
|
46
|
+
HttpToolNotEditableError,
|
|
42
47
|
type HttpToolSpec,
|
|
43
48
|
AgentEditNotSupportedError,
|
|
44
49
|
AgentFileNotFoundError,
|
|
@@ -290,6 +295,10 @@ function describeTool(t: { name: string; description: string; safe?: boolean; in
|
|
|
290
295
|
|
|
291
296
|
async function describeAgent(entry: RegistryEntry): Promise<Record<string, unknown>> {
|
|
292
297
|
const { config } = entry
|
|
298
|
+
// Which localTools the Tools tab's Edit button applies to — only the
|
|
299
|
+
// ones with a saved spec (loopengine's own sidecar spec file), i.e.
|
|
300
|
+
// only tools this same admin UI generated in the first place.
|
|
301
|
+
const editableHttpToolNames = new Set(listEditableHttpToolNames(config.name))
|
|
293
302
|
// Kept separate (not just concatenated into one `tools` array — though
|
|
294
303
|
// that's still returned too, below, for Overview's own at-a-glance
|
|
295
304
|
// count) so the "Tools" tab can show where each one actually comes
|
|
@@ -346,7 +355,7 @@ async function describeAgent(entry: RegistryEntry): Promise<Record<string, unkno
|
|
|
346
355
|
// schema here — never executed, so the console-prompt fallback its
|
|
347
356
|
// own doc comment mentions never applies to this call.
|
|
348
357
|
systemTools: [...systemTools, createAskUserTool({ agent: config.name })].map(describeTool),
|
|
349
|
-
localTools: localTools.map(describeTool),
|
|
358
|
+
localTools: localTools.map((t) => ({ ...describeTool(t), httpToolEditable: editableHttpToolNames.has(t.name) })),
|
|
350
359
|
agentAsTools: agentAsTools.map(describeTool),
|
|
351
360
|
gatewayTools: gatewayTools.map(describeTool),
|
|
352
361
|
permissions: {
|
|
@@ -562,6 +571,72 @@ async function handleHttpToolPost(req: IncomingMessage, res: ServerResponse, age
|
|
|
562
571
|
res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify({ tool: describeTool(tool) }))
|
|
563
572
|
}
|
|
564
573
|
|
|
574
|
+
// Backs the Tools tab's Edit button on a Local tool: GET populates the
|
|
575
|
+
// form from the spec createHttpTool/updateHttpTool saved alongside the
|
|
576
|
+
// generated file, PUT saves it back. 404s for any tool without a saved
|
|
577
|
+
// spec (hand-written, or created before this sidecar existed) — same
|
|
578
|
+
// signal describeAgent's own httpToolEditable flag uses to decide
|
|
579
|
+
// whether to show the button at all, so this only ever rejects a request
|
|
580
|
+
// the UI itself shouldn't have been able to send.
|
|
581
|
+
function handleHttpToolGet(res: ServerResponse, agentName: string, toolName: string): void {
|
|
582
|
+
if (!getEntry(agentName)) {
|
|
583
|
+
res.writeHead(404, { 'content-type': 'application/json' }).end(JSON.stringify({ error: `unknown agent '${agentName}'` }))
|
|
584
|
+
return
|
|
585
|
+
}
|
|
586
|
+
const spec = readHttpToolSpec(agentName, toolName)
|
|
587
|
+
if (!spec) {
|
|
588
|
+
res.writeHead(404, { 'content-type': 'application/json' }).end(
|
|
589
|
+
JSON.stringify({ error: `No admin-created HTTP tool named '${toolName}' for '${agentName}' — hand-written tools aren't editable through this form.` }),
|
|
590
|
+
)
|
|
591
|
+
return
|
|
592
|
+
}
|
|
593
|
+
res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify(spec))
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
async function handleHttpToolPut(req: IncomingMessage, res: ServerResponse, agentName: string, toolName: string): Promise<void> {
|
|
597
|
+
const entry = getEntry(agentName)
|
|
598
|
+
if (!entry) {
|
|
599
|
+
res.writeHead(404, { 'content-type': 'application/json' }).end(JSON.stringify({ error: `unknown agent '${agentName}'` }))
|
|
600
|
+
return
|
|
601
|
+
}
|
|
602
|
+
const body = await readJsonBody(req)
|
|
603
|
+
const parsed = parseHttpToolBody(body)
|
|
604
|
+
if (!parsed.ok) {
|
|
605
|
+
res.writeHead(400, { 'content-type': 'application/json' }).end(JSON.stringify({ error: parsed.error }))
|
|
606
|
+
return
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Same ordering reasoning as handleHttpToolPost above: resolved before
|
|
610
|
+
// updateHttpTool touches disk.
|
|
611
|
+
const currentTools = entry.config.tools ?? (await loadDefaultTools(entry.config))
|
|
612
|
+
|
|
613
|
+
let tool: ToolDefinition
|
|
614
|
+
try {
|
|
615
|
+
const result = await updateHttpTool(agentName, toolName, parsed.value)
|
|
616
|
+
tool = result.tool
|
|
617
|
+
} catch (err) {
|
|
618
|
+
const status =
|
|
619
|
+
err instanceof HttpToolNotFoundError || err instanceof HttpToolNotEditableError
|
|
620
|
+
? 409
|
|
621
|
+
: err instanceof HttpToolNameError || err instanceof HttpToolIndexShapeError
|
|
622
|
+
? 400
|
|
623
|
+
: 500
|
|
624
|
+
res.writeHead(status, { 'content-type': 'application/json' }).end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }))
|
|
625
|
+
return
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Replaces the edited tool in place (same array position, same
|
|
629
|
+
// reference identity for everything else) rather than the create
|
|
630
|
+
// path's append — an edit doesn't seed a new actauth rule either,
|
|
631
|
+
// unlike creation: the tool's permission is unchanged by this, still
|
|
632
|
+
// governed by whatever rule (if any) creation seeded or an operator
|
|
633
|
+
// added since, editable separately via the Actauth tab.
|
|
634
|
+
const nextTools = currentTools.map((t) => (t.name === toolName ? tool : t))
|
|
635
|
+
updateAgent(agentName, { config: { tools: nextTools } })
|
|
636
|
+
|
|
637
|
+
res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify({ tool: describeTool(tool) }))
|
|
638
|
+
}
|
|
639
|
+
|
|
565
640
|
// Backs GET /composio/connections and GET /composio/tools — not scoped
|
|
566
641
|
// under /agents/:name/ like the routes above, deliberately: which apps
|
|
567
642
|
// are connected and what they offer isn't a property of any one agent,
|
|
@@ -2016,6 +2091,18 @@ const server = createServer(async (req, res) => {
|
|
|
2016
2091
|
return
|
|
2017
2092
|
}
|
|
2018
2093
|
|
|
2094
|
+
// Backs the Tools tab's Edit button on a Local tool — GET to populate
|
|
2095
|
+
// the form, PUT to save (see handleHttpToolGet/Put above).
|
|
2096
|
+
const httpToolItemMatch = pathname.match(/^\/agents\/([^/]+)\/tools\/http\/([^/]+)$/)
|
|
2097
|
+
if (httpToolItemMatch && req.method === 'GET') {
|
|
2098
|
+
handleHttpToolGet(res, decodeURIComponent(httpToolItemMatch[1]), decodeURIComponent(httpToolItemMatch[2]))
|
|
2099
|
+
return
|
|
2100
|
+
}
|
|
2101
|
+
if (httpToolItemMatch && req.method === 'PUT') {
|
|
2102
|
+
await handleHttpToolPut(req, res, decodeURIComponent(httpToolItemMatch[1]), decodeURIComponent(httpToolItemMatch[2]))
|
|
2103
|
+
return
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2019
2106
|
// Backs the Skills tab's edit form — see skills-admin.ts for why
|
|
2020
2107
|
// :skillId is restricted to a flat (non-nested) id.
|
|
2021
2108
|
const skillMatch = pathname.match(/^\/agents\/([^/]+)\/skills\/([^/]+)$/)
|