Files
hzhang 3e02a73c05 feat: read agent_id from ctx (SDK now plumbs it)
Plexum-sdk-go now unpacks `_meta.agent_id` from host's MCP tools/call
frames into the ctx. Plugin reads it via plugin.AgentIDFromContext
and uses it for backend api-key resolution (agentKeys map lookup +
bearer token) and HF on_call pre-check.

config.defaultAgentID demoted from "v1 stop-gap for the missing SDK
plumbing" to "fallback for operator-driven plugin-call". Normal turn
loops carry the real caller id through ctx now; defaultAgentID only
fires on host-driven dispatch paths.

Also: log every CallTool dispatch with {tool, agent_id} at info level
so operators can see which agent is hitting which tool without
debug-level chatter.

E2E in sim: PLEXUM_ECHO_FORCE_TOOL_USE drives echo provider →
dialectic_list_topics dispatched via host → plugin sees
agent_id=test-agent in ctx + logs it correctly.
2026-06-03 12:54:53 +01:00

106 lines
2.8 KiB
Go

// plexum-dialectic-plugin — Plexum-side Dialectic plugin.
//
// Ports Dialectic.OpenclawPlugin to the Plexum SDK: 8 dialectic_* tools
// over HTTP against Dialectic.Backend. Lazy activation (no background
// services — purely tool-call driven).
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
sdkplugin "git.hangman-lab.top/hzhang/Plexum-sdk-go/plugin"
dialcfg "git.hangman-lab.top/zhi/Dialectic.PlexumPlugin/internal/config"
"git.hangman-lab.top/zhi/Dialectic.PlexumPlugin/internal/tools"
)
var Version = "0.1.0"
type dialecticPlugin struct {
host sdkplugin.HostAPI
cfg dialcfg.Resolved
deps tools.Deps
}
func (p *dialecticPlugin) Manifest() sdkplugin.Manifest {
return manifestFromDisk()
}
func (p *dialecticPlugin) Init(ctx context.Context, host sdkplugin.HostAPI) error {
p.host = host
profileRoot := os.Getenv("PLEXUM_PROFILE_ROOT")
if profileRoot == "" {
home, _ := os.UserHomeDir()
profileRoot = filepath.Join(home, ".plexum")
}
raw, err := dialcfg.Load(profileRoot)
if err != nil {
return fmt.Errorf("load dialectic config: %w", err)
}
p.cfg = dialcfg.Resolve(raw)
host.Log("info", "dialectic plugin initialized", map[string]any{
"version": Version,
"backend": p.cfg.BackendURL,
"default_agent_id": p.cfg.DefaultAgentID,
"agent_keys_count": len(p.cfg.AgentKeys),
"has_default_key": p.cfg.APIKey != "",
})
p.deps = tools.Deps{
Config: p.cfg,
Host: host,
AgentIDFromCtx: func(ctx context.Context) string {
// Host attaches the caller agent id via tools/call
// `_meta.agent_id`; SDK unpacks it into ctx.
if id := sdkplugin.AgentIDFromContext(ctx); id != "" {
return id
}
// Fallback for host paths that don't carry an agent
// (CLI plugin-call against this plugin from an operator
// debugging the deployment). Empty when not set.
return p.cfg.DefaultAgentID
},
}
return nil
}
func (p *dialecticPlugin) CallTool(ctx context.Context, name string, input json.RawMessage) (sdkplugin.ToolResult, error) {
p.host.Log("info", "tools/call dispatched", map[string]any{
"tool": name,
"agent_id": sdkplugin.AgentIDFromContext(ctx),
})
return tools.Dispatch(ctx, p.deps, name, input)
}
func manifestFromDisk() sdkplugin.Manifest {
exe, err := os.Executable()
if err == nil {
raw, err := os.ReadFile(filepath.Join(filepath.Dir(exe), "manifest.json"))
if err == nil {
var m sdkplugin.Manifest
if err := json.Unmarshal(raw, &m); err == nil && m.Name != "" {
return m
}
}
}
return sdkplugin.Manifest{
Name: "dialectic",
Version: Version,
Activation: sdkplugin.ActivationLazy,
Executable: "plexum-dialectic-plugin",
}
}
func main() {
if err := sdkplugin.Serve(&dialecticPlugin{}); err != nil && !errors.Is(err, context.Canceled) {
fmt.Fprintf(os.Stderr, "plexum-dialectic-plugin: %v\n", err)
os.Exit(1)
}
}