zot/examples/extensions/weather
patriceckhart 74709a0bd9 feat(extensions): phase 2 — extension-defined tools
Extensions can now register tools the LLM calls directly. The model
sees them in its tool list alongside the built-ins (read, write,
edit, bash, skill); when it invokes one, zot routes the tool_call
to the owning extension subprocess and feeds the tool_result back.

Wire format additions (internal/extproto):
  ext -> host:
    register_tool {name, description, schema}
    ready                                       # all initial regs flushed
    tool_result {id, content[], is_error}       # reply to a tool_call
  host -> ext:
    tool_call {id, name, args}                  # raw json args from the model

Manager (internal/agent/extensions):
  - tracks per-extension RegisterToolFromExt frames
  - validates schemas parse as JSON before registering (bad schema
    skipped + logged, doesn't crash zot)
  - toolIndex map for O(1) lookup
  - WaitForReady(grace): blocks per extension on its readyCh until
    a ready frame arrives or the grace expires; called once after
    Discover so the agent's tool registry is built against the
    final set
  - Tools() / HasTool() / InvokeTool() public surface
  - readLoop closes readyCh on stdout EOF so a wedged extension
    doesn't permanently block WaitForReady

extensionTool (internal/agent/extensions/tool.go):
  implements core.Tool. Execute() round-trips through
  Manager.InvokeTool with a 60s default timeout, decodes
  base64 image blocks, surfaces extension+tool name in
  ToolResult.Details for the renderer.

internal/agent/build.go:
  - new ExtensionToolSource interface (declared here to avoid the
    build->extensions->core import cycle) + ExtensionToolInfo
    mirror of extensions.ToolInfo
  - Resolved.MergeExtensionTools(): folds extension tools into
    ToolRegistry, re-renders the system prompt's tool summary
    with both built-in and extension tools listed
  - Resolved gains private bookkeeping fields so the rebuild
    works without re-running Resolve

internal/agent/cli.go:
  extension manager built BEFORE the agent in interactive mode
  so MergeExtensionTools can fire before NewAgent. Same in
  buildAgent + buildAgentFor closures so login / model-switch
  rebuilds also include extension tools. extToolAdapter bridges
  *extensions.Manager to ExtensionToolSource.

internal/agent/rpc.go:
  extension lifecycle now also runs in `zot rpc` mode. Notify and
  Display from extensions surface as `ext_notify` / `ext_display`
  events on the rpc stream so any consumer can react.

pkg/zotext (Go SDK):
  - ToolHandler, ToolResult, ToolContent types
  - Tool(name, desc, schema, fn) registration method
  - TextResult / TextErrorResult / Image / ImageBytes constructors
  - Run() now also flushes register_tool frames + a final ready
    sentinel after the last registration

examples/extensions/weather: working Go example registering one
tool. Deterministic fake weather (sha1 of city -> temp + cond) so
the demo is repeatable. Plus README explaining how to install.

Tests:
  internal/agent/extensions/tool_test.go: spawns a mock /bin/sh
  extension that registers a tool, sends ready, and echoes tool
  calls. Verifies registration timing, lookup via HasTool/Tools,
  invoke roundtrip via InvokeTool.

End-to-end verified against live anthropic backend:
  prompt: "What is the weather in Berlin?"
  -> [tool_call] weather({"city":"Berlin"})
  -> [tool_result] Berlin: 16°C, fog (deterministic fake)
  -> reply: "Berlin is 16°C."

Docs/extensions.md updated with phase 2 wire format, the new SDK
tool API, and the weather example.
2026-04-19 14:46:32 +02:00
..
extension.json feat(extensions): phase 2 — extension-defined tools 2026-04-19 14:46:32 +02:00
main.go feat(extensions): phase 2 — extension-defined tools 2026-04-19 14:46:32 +02:00
README.md feat(extensions): phase 2 — extension-defined tools 2026-04-19 14:46:32 +02:00

weather — example zot extension (Go, tool-providing)

Demonstrates the tool registration half of the extension protocol (phase 2). The extension exposes a weather(city) tool that the LLM can invoke; the result is fake-but-deterministic so repeated calls for the same city return the same thing.

Build

cd examples/extensions/weather
go build -o weather .

Install

zot ext install .

Copies the directory (manifest + binary) into $ZOT_HOME/extensions/weather/. zot picks it up the next time you launch the TUI and registers the tool with the agent.

Use

In zot, ask:

  • "What's the weather in Berlin?"
  • "Compare the weather in Tokyo and Reykjavik."

The model decides on its own to call the weather tool because the description tells it what the tool is for. You don't need to invoke anything by hand.

Add the leading slash to your /help table

The tool also shows up in the system prompt's tool list because zot folds extension tools into the agent's registry at startup.

See also

  • examples/extensions/hello — slash-command extension (Go SDK)
  • examples/extensions/clock — slash-command extension (Node, no SDK)
  • docs/extensions.md — full protocol reference