mirror of
https://github.com/hanzoai/agent.git
synced 2026-09-11 21:08:51 +00:00
The four routes were written at the literal /v1/agent, which is fine for the standalone daemon and wrong for anyone else: a host whose router already spends that address had no way to take this surface at all. It now composes off one prefix — MountAt(app, prefix, ...) registers there, Mount(app, ...) is MountAt at DefaultPrefix, so the daemon is unchanged and the host says where. A prefix that is not an absolute path is refused at mount rather than registering routes nothing can reach, and a trailing slash is the same address. Nothing in the round reads the prefix, so the doc comments that named the old literal now name the route.
152 lines
5.6 KiB
Go
152 lines
5.6 KiB
Go
package agent
|
|
|
|
import (
|
|
"encoding/json"
|
|
"sort"
|
|
"strings"
|
|
|
|
openai "github.com/hanzoai/go-openai"
|
|
)
|
|
|
|
// Preset is one named agent type in the preset LIBRARY — a first-class, extensible
|
|
// catalog the round is created from and the presets route lists.
|
|
// It frames a tool-calling round: the system prompt that instructs the model, the
|
|
// builtin tool defs offered alongside the caller's and the org's registered tools,
|
|
// and whether the model's tool calls are EXECUTED server-side (registry Dispatch →
|
|
// actions) or handed back to the client as ops (a graph/UI mutation the server
|
|
// cannot perform). Adding an agent type is one Register call — no other change.
|
|
type Preset struct {
|
|
ID string `json:"id"`
|
|
Title string `json:"title"`
|
|
SystemPrompt string `json:"systemPrompt"`
|
|
BuiltinTools []openai.Tool `json:"-"`
|
|
// ServerExecuted gates the tool-call split: when true, a call the tool
|
|
// registry knows is dispatched server-side and reported in actions; when
|
|
// false the round is advisory — every call is returned as an op for the
|
|
// client to apply.
|
|
ServerExecuted bool `json:"serverExecuted"`
|
|
}
|
|
|
|
// DefaultPreset is used when a request omits preset (and its capability alias).
|
|
const DefaultPreset = "graph"
|
|
|
|
// presets is the library. Seeded with graph + create below; a host extends it by
|
|
// calling Register before Mount.
|
|
var presets = map[string]Preset{}
|
|
|
|
// Register adds (or replaces) a preset in the library. Call from init() or before
|
|
// Mount. Replacing an existing id is allowed so a host can re-skin a builtin.
|
|
func Register(p Preset) {
|
|
presets[strings.TrimSpace(p.ID)] = p
|
|
}
|
|
|
|
// Presets returns every registered preset, sorted by ID for a stable listing.
|
|
func Presets() []Preset {
|
|
out := make([]Preset, 0, len(presets))
|
|
for _, p := range presets {
|
|
out = append(out, p)
|
|
}
|
|
sort.Slice(out, func(i, j int) bool { return out[i].ID < out[j].ID })
|
|
return out
|
|
}
|
|
|
|
// presetFor resolves a preset by id, defaulting an empty id to DefaultPreset. ok
|
|
// is false for an unknown id so the handler answers 400 rather than guessing.
|
|
func presetFor(id string) (Preset, bool) {
|
|
id = strings.TrimSpace(id)
|
|
if id == "" {
|
|
id = DefaultPreset
|
|
}
|
|
p, ok := presets[id]
|
|
return p, ok
|
|
}
|
|
|
|
// toolDef builds an OpenAI function tool from a JSON-Schema string. The schema is
|
|
// held as json.RawMessage so it reaches the model verbatim.
|
|
func toolDef(name, desc, schema string) openai.Tool {
|
|
return openai.Tool{
|
|
Type: openai.ToolTypeFunction,
|
|
Function: &openai.FunctionDefinition{
|
|
Name: name,
|
|
Description: desc,
|
|
Parameters: json.RawMessage(schema),
|
|
},
|
|
}
|
|
}
|
|
|
|
func init() {
|
|
Register(Preset{
|
|
ID: "graph",
|
|
Title: "Studio Graph Copilot",
|
|
ServerExecuted: false,
|
|
SystemPrompt: strings.TrimSpace(`
|
|
You are Hanzo Studio Copilot. You read and mutate a node workflow graph
|
|
(LiteGraph / ComfyUI-compatible). The user describes an outcome; you respond with
|
|
tool calls that describe the graph operations to apply. You never execute the
|
|
graph yourself — the Studio client applies each operation and re-queues the graph.
|
|
Reference nodes you create by the id you give them in add_node. Prefer the fewest
|
|
operations that achieve the request; explain briefly in text when helpful.`),
|
|
BuiltinTools: []openai.Tool{
|
|
toolDef("add_node", "Add a node to the graph.", `{
|
|
"type":"object",
|
|
"properties":{
|
|
"id":{"type":"string","description":"caller label to reference this node in later ops"},
|
|
"type":{"type":"string","description":"node class, e.g. KSampler"},
|
|
"pos":{"type":"array","items":{"type":"number"},"description":"[x,y] canvas position"}
|
|
},
|
|
"required":["type"]
|
|
}`),
|
|
toolDef("set_widget", "Set a widget value on a node.", `{
|
|
"type":"object",
|
|
"properties":{
|
|
"node_id":{"type":"string"},
|
|
"name":{"type":"string","description":"widget name"},
|
|
"value":{}
|
|
},
|
|
"required":["node_id","name","value"]
|
|
}`),
|
|
toolDef("set_prompt", "Set the prompt text on a node.", `{
|
|
"type":"object",
|
|
"properties":{"node_id":{"type":"string"},"text":{"type":"string"}},
|
|
"required":["node_id","text"]
|
|
}`),
|
|
toolDef("connect", "Connect an output slot to an input slot.", `{
|
|
"type":"object",
|
|
"properties":{
|
|
"from":{"type":"string","description":"source node id"},
|
|
"from_slot":{"description":"output slot index or name"},
|
|
"to":{"type":"string","description":"target node id"},
|
|
"to_slot":{"description":"input slot index or name"}
|
|
},
|
|
"required":["from","to"]
|
|
}`),
|
|
toolDef("move_node", "Move a node to a new position.", `{
|
|
"type":"object",
|
|
"properties":{"node_id":{"type":"string"},"pos":{"type":"array","items":{"type":"number"}}},
|
|
"required":["node_id","pos"]
|
|
}`),
|
|
toolDef("delete_node", "Remove a node from the graph.", `{
|
|
"type":"object",
|
|
"properties":{"node_id":{"type":"string"}},
|
|
"required":["node_id"]
|
|
}`),
|
|
toolDef("layout", "Auto-arrange the graph.", `{"type":"object","properties":{}}`),
|
|
toolDef("queue", "Queue the graph for execution.", `{"type":"object","properties":{}}`),
|
|
},
|
|
})
|
|
|
|
Register(Preset{
|
|
ID: "create",
|
|
Title: "Product & Fashion Create",
|
|
ServerExecuted: true,
|
|
SystemPrompt: strings.TrimSpace(`
|
|
You are Hanzo Create, a fashion and product render assistant. You help the user
|
|
generate, edit, and refine product and fashion imagery. Use the tools available to
|
|
this workspace — the org's connected render and MCP services — to run and iterate
|
|
on renders. Call a tool when it advances the request; describe the result briefly.`),
|
|
// No builtin tools: the callable set is the org's registered MCP/registry
|
|
// tools, resolved per request and dispatched server-side.
|
|
BuiltinTools: nil,
|
|
})
|
|
}
|