mirror of
https://github.com/patriceckhart/zot.git
synced 2026-06-28 06:13:42 +02:00
Single Go module, four top-level packages under packages/. Import
paths become github.com/patriceckhart/zot/packages/<name>; downstream
consumers can depend on individual packages without pulling the rest.
Layout:
packages/provider/ LLM clients + catalog
packages/provider/auth/ credential store + OAuth + login server
packages/core/ agent loop, sessions, cost
packages/tui/ terminal toolkit + chat view
packages/agent/ CLI wiring, system prompt
extensions/ extproto/ modes/ tools/ skills/ swarm/
sdk/ (was pkg/zotcore, package renamed zotcore -> sdk)
ext/ (was pkg/zotext, package renamed zotext -> ext)
internal/ and pkg/ removed. The internal/assets logo moved into
packages/provider/auth/assets.
Public Go SDK identifiers renamed:
pkg/zotcore (package zotcore) -> packages/agent/sdk (package sdk)
pkg/zotext (package zotext) -> packages/agent/ext (package ext)
This breaks Go-based extensions and embedders; the JSON wire protocol
for extensions and RPC is unchanged, so non-Go extensions, already-
built extension binaries, and zot rpc consumers are unaffected.
Docs, examples, and the built-in write-zot-extension skill updated
for the new paths and identifiers. Shadow-bug fixes in code samples
(ext := ext.New -> e := ext.New).
82 lines
2.5 KiB
Go
82 lines
2.5 KiB
Go
package extensions
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func writeMockToolExtension(t *testing.T, root string) {
|
|
t.Helper()
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("mock uses /bin/sh; skip on windows")
|
|
}
|
|
dir := filepath.Join(root, "tool-mock")
|
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Shell-script extension: hello, register one tool, ready, then
|
|
// loop on stdin echoing back tool_call args as tool_result text.
|
|
script := `#!/bin/sh
|
|
printf '%s\n' '{"type":"hello","name":"tool-mock","version":"0.1","capabilities":["tools"]}'
|
|
printf '%s\n' '{"type":"register_tool","name":"echo","description":"echo back the args","schema":{"type":"object","properties":{"msg":{"type":"string"}}}}'
|
|
printf '%s\n' '{"type":"ready"}'
|
|
while IFS= read -r line; do
|
|
case "$line" in
|
|
*'"type":"tool_call"'*)
|
|
id=$(printf '%s' "$line" | sed -n 's/.*"id":"\([^"]*\)".*/\1/p')
|
|
printf '%s\n' "{\"type\":\"tool_result\",\"id\":\"$id\",\"content\":[{\"type\":\"text\",\"text\":\"echoed\"}]}"
|
|
;;
|
|
*'"type":"shutdown"'*)
|
|
printf '%s\n' '{"type":"shutdown_ack"}'
|
|
exit 0
|
|
;;
|
|
esac
|
|
done
|
|
`
|
|
if err := os.WriteFile(filepath.Join(dir, "run.sh"), []byte(script), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
mfb, _ := json.Marshal(map[string]any{"name": "tool-mock", "exec": "./run.sh"})
|
|
if err := os.WriteFile(filepath.Join(dir, "extension.json"), mfb, 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestManagerToolRegistrationAndInvoke(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
writeMockToolExtension(t, filepath.Join(tmp, "extensions"))
|
|
|
|
mgr := New(tmp, "", "0.0.0", "anthropic", "opus", &stubHooks{})
|
|
if errs := mgr.Discover(context.Background()); len(errs) > 0 {
|
|
t.Fatalf("discover errs: %v", errs)
|
|
}
|
|
defer mgr.Stop(2 * time.Second)
|
|
|
|
// Wait for ready (the script sends it right after register_tool).
|
|
mgr.WaitForReady(time.Second)
|
|
|
|
tools := mgr.Tools()
|
|
if len(tools) != 1 || tools[0].Name != "echo" {
|
|
t.Fatalf("expected one tool 'echo', got %#v", tools)
|
|
}
|
|
if !mgr.HasTool("echo") {
|
|
t.Fatal("HasTool(\"echo\") = false")
|
|
}
|
|
|
|
resp, err := mgr.InvokeTool(context.Background(), "echo", json.RawMessage(`{"msg":"hi"}`), 2*time.Second)
|
|
if err != nil {
|
|
t.Fatalf("invoke: %v", err)
|
|
}
|
|
if resp.IsError {
|
|
t.Errorf("expected success, got is_error=true")
|
|
}
|
|
if len(resp.Content) != 1 || resp.Content[0].Type != "text" || resp.Content[0].Text != "echoed" {
|
|
t.Errorf("unexpected content: %#v", resp.Content)
|
|
}
|
|
}
|