create-loopengine 0.1.16 → 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 +95 -2
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: {
|
|
@@ -524,6 +533,13 @@ async function handleHttpToolPost(req: IncomingMessage, res: ServerResponse, age
|
|
|
524
533
|
return
|
|
525
534
|
}
|
|
526
535
|
|
|
536
|
+
// Resolved *before* createHttpTool writes anything to disk. Reading it
|
|
537
|
+
// after would risk a cold import of the freshly-written tools/index.ts
|
|
538
|
+
// (folder-form agents don't cache config.tools) — which already contains
|
|
539
|
+
// the new tool by then, so the ...currentTools, tool below would append
|
|
540
|
+
// it a second time.
|
|
541
|
+
const currentTools = entry.config.tools ?? (await loadDefaultTools(entry.config))
|
|
542
|
+
|
|
527
543
|
let tool: ToolDefinition
|
|
528
544
|
try {
|
|
529
545
|
const result = await createHttpTool(agentName, parsed.value)
|
|
@@ -534,7 +550,6 @@ async function handleHttpToolPost(req: IncomingMessage, res: ServerResponse, age
|
|
|
534
550
|
return
|
|
535
551
|
}
|
|
536
552
|
|
|
537
|
-
const currentTools = entry.config.tools ?? (await loadDefaultTools(entry.config))
|
|
538
553
|
updateAgent(agentName, { config: { tools: [...currentTools, tool] } })
|
|
539
554
|
|
|
540
555
|
if (parsed.decision) {
|
|
@@ -556,6 +571,72 @@ async function handleHttpToolPost(req: IncomingMessage, res: ServerResponse, age
|
|
|
556
571
|
res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify({ tool: describeTool(tool) }))
|
|
557
572
|
}
|
|
558
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
|
+
|
|
559
640
|
// Backs GET /composio/connections and GET /composio/tools — not scoped
|
|
560
641
|
// under /agents/:name/ like the routes above, deliberately: which apps
|
|
561
642
|
// are connected and what they offer isn't a property of any one agent,
|
|
@@ -2010,6 +2091,18 @@ const server = createServer(async (req, res) => {
|
|
|
2010
2091
|
return
|
|
2011
2092
|
}
|
|
2012
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
|
+
|
|
2013
2106
|
// Backs the Skills tab's edit form — see skills-admin.ts for why
|
|
2014
2107
|
// :skillId is restricted to a flat (non-nested) id.
|
|
2015
2108
|
const skillMatch = pathname.match(/^\/agents\/([^/]+)\/skills\/([^/]+)$/)
|