package tool import ( "context" llm "gitea.stevedudenhoeffer.com/steve/majordomo/llm" ) // AgentSession is the live-run handle a SessionToolFactory receives. // It is implemented by the executors (agentexec / skillexec / scaddy's // adapter) on top of majordomo's agent loop and exposes the one mid-run // mutation session tools need: feeding content back into the running // conversation. // // Why an interface (vs the concrete agent type): legacy agentkit handed the // factory a *agentkit.Agent so tools could call agent.AttachImages. // majordomo's *agent.Agent is deliberately immutable mid-run — message // injection happens through the run-scoped steer mailbox // (agent.WithSteer). A narrow interface lets each executor implement // AttachImages over its own steer queue without skilltools importing // the agent package, and keeps session tools testable with a two-line // fake. type AgentSession interface { // AttachImages queues a user-role message (text plus image parts) // for injection into the conversation before the agent's next // step. Used by tools that produce visual feedback the model must // see on its following turn (e.g. scaddy's rendered OpenSCAD // previews). Safe to call from inside a tool handler; the message // lands after the current step's tool results. AttachImages(text string, images ...llm.ImagePart) } // SessionToolFactory builds per-session tools that close over the live // agent session. Called by the executor after the agent is constructed // but before it runs. See Invocation.SessionToolFactory for the // rationale (static ExtraTools cannot reach the running agent). type SessionToolFactory func(session AgentSession) SessionTools // SessionTools carries per-session tools plus optional post-run and // teardown hooks. It replaces legacy agentkit's SessionTools with the same // three-field shape, re-based on majordomo types. type SessionTools struct { // Tools to add to the agent's toolbox for this run only. Tools []llm.Tool // PostRun, if set, is called after the agent run completes // (successfully or not). It receives the full run transcript (the // agent Result's Messages — also populated on partial results from // agent.ErrMaxSteps / agent.ErrToolLoop), the agent's text output, // and the run error, so the hook can decide whether to attempt // artifact production on partial success (e.g. scaddy ships the // latest SCAD even when the step budget ran out). The returned // PostRunResult is attached to the executor's run result. Errors // inside PostRun must be handled by the hook itself — the executor // logs a nil return but never fails the run over it; the agent's // output is the source of truth. // // Why a transcript slice (vs the live agent): the consumers only // ever read the message history (thought-chain transcripts); the // majordomo agent exposes that on Result, not on the Agent. PostRun func(ctx context.Context, transcript []llm.Message, output string, runErr error) *PostRunResult // Cleanup, if set, is deferred by the executor immediately after // the factory returns. Called even if the run fails or PostRun // panics. Use for temp directory removal, closing file handles, // etc. Cleanup func() } // PostRunResult carries artifacts produced by the PostRun hook. // Attached to the executor's run result so callers (Discord command // handlers, HTTP API responses) can inspect and deliver the artifacts. // // Why a separate struct (vs returning artifacts inline): post- // processing may produce multiple typed artifacts (PNGs, STLs, SCAD // source) that the delivery layer classifies and routes differently. // A flat []Artifact + arbitrary Metadata covers the known use cases // without over-specifying the shape. type PostRunResult struct { // Artifacts are files produced during post-processing // (e.g., rendered PNGs, STL files, SCAD source). Artifacts []Artifact // Metadata is arbitrary key-value data the delivery layer can // use for formatting (e.g., iteration count, model name, notes). Metadata map[string]any } // Artifact is a named binary blob produced by post-run processing. // // Why: the delivery layer needs name + type + bytes to classify // each artifact (PNG → embed image, STL → filetransfer upload, // SCAD → paste upload). A struct with these three fields is the // minimal viable description. type Artifact struct { Name string // e.g., "model.stl", "preview_iso.png" MimeType string // e.g., "model/stl", "image/png" Data []byte }