Merge pull request #160 from frkr/feat/openroutertoolsconfig
config: make OpenRouter server-tool call limit configurable via config.json
6 files · +45 -3
M README.md +11 -1
| @@ -403,7 +403,17 @@ Opens a dialog with every persistent setting. `up`/`down` to navigate, `enter` o | |||
| 403 | 403 | - **auto-swarm** — let the main agent spawn background sub-agents in parallel via a built-in `swarm_spawn` tool. Off by default. When on, the tool is registered with the running agent, the system prompt gains a short addendum telling the model to delegate independent sub-tasks proactively, and zot watches every sub-agent the main agent spawns. As soon as the last sub-agent in a batch finishes its initial task, an `[auto-swarm update]` message is injected back into the chat with each agent's status / task / transcript tail, so the main agent can summarise the collective outcome. Flipping off mid-session removes the tool from the live agent and strips the addendum on the next turn — the model stops trying to delegate. See `/swarm` for the dashboard that lets you monitor, message, kill, or remove the spawned agents. | |
| 404 | 404 | - **auto-compact threshold** — choose `off`, `70%`, `80%`, `85%` (default), or `90%` of the model's advertised context window. The selected percentage controls automatic compaction before and after interactive turns and persists as `auto_compact_threshold`. `off` disables percentage-based triggers but keeps manual `/compact` and automatic recovery from context-window and payload-too-large responses. | |
| 405 | 405 | - **jail new sessions by default**: start every new agent with tools confined to its working directory. Off by default. The setting applies to interactive, print, JSON, RPC, and background-agent runs, persists as `jail_by_default`, and immediately updates the current interactive session. `/jail` and `/unjail` remain session-scoped overrides and do not change this default. | |
| 406 | - | - **OpenRouter Server Tools**: opt in to OpenRouter's beta web search, web fetch, advisor, subagent, fusion, image generation, datetime, and model-search tools. Off by default and available only with the OpenRouter provider. zot limits the remote loop to four server-tool calls per turn. `--no-tools` always disables them. Presets keep their own tool configuration and are not overridden by this setting. Server tools run remotely without zot's local tool confirmation dialog and may incur additional OpenRouter charges. | |
| 406 | + | - **OpenRouter Server Tools**: opt in to OpenRouter's beta web search, web fetch, advisor, subagent, fusion, image generation, datetime, and model-search tools. Off by default and available only with the OpenRouter provider. zot limits the remote loop to four server-tool calls per turn (adjustable via `openrouter_server_tool_call_limit` in `config.json`). `--no-tools` always disables them. Presets keep their own tool configuration and are not overridden by this setting. Server tools run remotely without zot's local tool confirmation dialog and may incur additional OpenRouter charges. | |
| 407 | + | ||
| 408 | + | **config.json example:** | |
| 409 | + | ||
| 410 | + | ```json | |
| 411 | + | { | |
| 412 | + | "openrouter_server_tools_enabled": true, | |
| 413 | + | "openrouter_server_tool_call_limit": 4 | |
| 414 | + | } | |
| 415 | + | ``` | |
| 416 | + | ||
| 407 | 417 | - **compact transcript rendering**: reduce visual chrome in the chat transcript. Tool calls render as a quiet header plus indented output instead of a bordered box, and sent messages render without padded background bubbles. Off by default. Changes apply immediately and persist to `config.json` as `compact_mode`. | |
| 408 | 418 | - **show loaded resources at startup**: list the active Zotfile agent (for `zot run`), loaded `AGENTS.md` paths, extensions, and user-installed skills in compact sections above the transcript. Built-in skills are omitted. Off by default. Changes apply immediately and persist to `config.json` as `show_instructions_at_startup`. | |
| 409 | 419 | - **TUI settings**: opens a sub-view for input layout and status placement. **Input style** can be `plain` (default prompt line), `lines` (separator lines above and below the input), or `block` (a user-bubble-style input block). **Status position** places model, usage, and working-directory information above or below the input. **Working spinner position** places the busy spinner above or below the input. Changes apply immediately and persist to `config.json` as `tui_input_style`, `tui_status_position`, and `tui_working_position` (`above_input` or `below_input` for the position fields). | |
| @@ -403,7 +403,17 @@ Opens a dialog with every persistent setting. `up`/`down` to navigate, `enter` o | |||||
| 403 | - **auto-swarm** — let the main agent spawn background sub-agents in parallel via a built-in `swarm_spawn` tool. Off by default. When on, the tool is registered with the running agent, the system prompt gains a short addendum telling the model to delegate independent sub-tasks proactively, and zot watches every sub-agent the main agent spawns. As soon as the last sub-agent in a batch finishes its initial task, an `[auto-swarm update]` message is injected back into the chat with each agent's status / task / transcript tail, so the main agent can summarise the collective outcome. Flipping off mid-session removes the tool from the live agent and strips the addendum on the next turn — the model stops trying to delegate. See `/swarm` for the dashboard that lets you monitor, message, kill, or remove the spawned agents. | 403 | - **auto-swarm** — let the main agent spawn background sub-agents in parallel via a built-in `swarm_spawn` tool. Off by default. When on, the tool is registered with the running agent, the system prompt gains a short addendum telling the model to delegate independent sub-tasks proactively, and zot watches every sub-agent the main agent spawns. As soon as the last sub-agent in a batch finishes its initial task, an `[auto-swarm update]` message is injected back into the chat with each agent's status / task / transcript tail, so the main agent can summarise the collective outcome. Flipping off mid-session removes the tool from the live agent and strips the addendum on the next turn — the model stops trying to delegate. See `/swarm` for the dashboard that lets you monitor, message, kill, or remove the spawned agents. | ||
| 404 | - **auto-compact threshold** — choose `off`, `70%`, `80%`, `85%` (default), or `90%` of the model's advertised context window. The selected percentage controls automatic compaction before and after interactive turns and persists as `auto_compact_threshold`. `off` disables percentage-based triggers but keeps manual `/compact` and automatic recovery from context-window and payload-too-large responses. | 404 | - **auto-compact threshold** — choose `off`, `70%`, `80%`, `85%` (default), or `90%` of the model's advertised context window. The selected percentage controls automatic compaction before and after interactive turns and persists as `auto_compact_threshold`. `off` disables percentage-based triggers but keeps manual `/compact` and automatic recovery from context-window and payload-too-large responses. | ||
| 405 | - **jail new sessions by default**: start every new agent with tools confined to its working directory. Off by default. The setting applies to interactive, print, JSON, RPC, and background-agent runs, persists as `jail_by_default`, and immediately updates the current interactive session. `/jail` and `/unjail` remain session-scoped overrides and do not change this default. | 405 | - **jail new sessions by default**: start every new agent with tools confined to its working directory. Off by default. The setting applies to interactive, print, JSON, RPC, and background-agent runs, persists as `jail_by_default`, and immediately updates the current interactive session. `/jail` and `/unjail` remain session-scoped overrides and do not change this default. | ||
| 406 | - | - **OpenRouter Server Tools**: opt in to OpenRouter's beta web search, web fetch, advisor, subagent, fusion, image generation, datetime, and model-search tools. Off by default and available only with the OpenRouter provider. zot limits the remote loop to four server-tool calls per turn. `--no-tools` always disables them. Presets keep their own tool configuration and are not overridden by this setting. Server tools run remotely without zot's local tool confirmation dialog and may incur additional OpenRouter charges. | 406 | + | - **OpenRouter Server Tools**: opt in to OpenRouter's beta web search, web fetch, advisor, subagent, fusion, image generation, datetime, and model-search tools. Off by default and available only with the OpenRouter provider. zot limits the remote loop to four server-tool calls per turn (adjustable via `openrouter_server_tool_call_limit` in `config.json`). `--no-tools` always disables them. Presets keep their own tool configuration and are not overridden by this setting. Server tools run remotely without zot's local tool confirmation dialog and may incur additional OpenRouter charges. |
| 407 | + | ||||
| 408 | + | **config.json example:** | |||
| 409 | + | ||||
| 410 | + | ```json | |||
| 411 | + | { | |||
| 412 | + | "openrouter_server_tools_enabled": true, | |||
| 413 | + | "openrouter_server_tool_call_limit": 4 | |||
| 414 | + | } | |||
| 415 | + | ``` | |||
| 416 | + | ||||
| 407 | - **compact transcript rendering**: reduce visual chrome in the chat transcript. Tool calls render as a quiet header plus indented output instead of a bordered box, and sent messages render without padded background bubbles. Off by default. Changes apply immediately and persist to `config.json` as `compact_mode`. | 417 | - **compact transcript rendering**: reduce visual chrome in the chat transcript. Tool calls render as a quiet header plus indented output instead of a bordered box, and sent messages render without padded background bubbles. Off by default. Changes apply immediately and persist to `config.json` as `compact_mode`. | ||
| 408 | - **show loaded resources at startup**: list the active Zotfile agent (for `zot run`), loaded `AGENTS.md` paths, extensions, and user-installed skills in compact sections above the transcript. Built-in skills are omitted. Off by default. Changes apply immediately and persist to `config.json` as `show_instructions_at_startup`. | 418 | - **show loaded resources at startup**: list the active Zotfile agent (for `zot run`), loaded `AGENTS.md` paths, extensions, and user-installed skills in compact sections above the transcript. Built-in skills are omitted. Off by default. Changes apply immediately and persist to `config.json` as `show_instructions_at_startup`. | ||
| 409 | - **TUI settings**: opens a sub-view for input layout and status placement. **Input style** can be `plain` (default prompt line), `lines` (separator lines above and below the input), or `block` (a user-bubble-style input block). **Status position** places model, usage, and working-directory information above or below the input. **Working spinner position** places the busy spinner above or below the input. Changes apply immediately and persist to `config.json` as `tui_input_style`, `tui_status_position`, and `tui_working_position` (`above_input` or `below_input` for the position fields). | 419 | - **TUI settings**: opens a sub-view for input layout and status placement. **Input style** can be `plain` (default prompt line), `lines` (separator lines above and below the input), or `block` (a user-bubble-style input block). **Status position** places model, usage, and working-directory information above or below the input. **Working spinner position** places the busy spinner above or below the input. Changes apply immediately and persist to `config.json` as `tui_input_style`, `tui_status_position`, and `tui_working_position` (`above_input` or `below_input` for the position fields). | ||
M packages/agent/build.go +14 -1
| @@ -988,11 +988,24 @@ func (r Resolved) NewAgent() *core.Agent { | |||
| 988 | 988 | a.Reasoning = r.Reasoning | |
| 989 | 989 | a.Temperature = r.Temperature | |
| 990 | 990 | if hasOpenRouterServerTools(r.ToolRegistry) { | |
| 991 | - | a.MaxToolCalls = tools.OpenRouterServerToolCallLimit | |
| 991 | + | a.MaxToolCalls = openRouterServerToolCallLimit() | |
| 992 | 992 | } | |
| 993 | 993 | return a | |
| 994 | 994 | } | |
| 995 | 995 | ||
| 996 | + | // openRouterServerToolCallLimit returns the configured call limit from | |
| 997 | + | // config.json, falling back to the built-in default (4). | |
| 998 | + | func openRouterServerToolCallLimit() int { | |
| 999 | + | cfg, err := LoadConfig() | |
| 1000 | + | if err != nil { | |
| 1001 | + | return tools.OpenRouterServerToolCallLimit | |
| 1002 | + | } | |
| 1003 | + | if cfg.OpenRouterServerToolCallLimit != nil && *cfg.OpenRouterServerToolCallLimit > 0 { | |
| 1004 | + | return *cfg.OpenRouterServerToolCallLimit | |
| 1005 | + | } | |
| 1006 | + | return tools.OpenRouterServerToolCallLimit | |
| 1007 | + | } | |
| 1008 | + | ||
| 996 | 1009 | // OpenRouterServerToolsEnabled reports the persisted preference. | |
| 997 | 1010 | // nil/missing and unreadable configuration fail closed to disabled. | |
| 998 | 1011 | func OpenRouterServerToolsEnabled() bool { | |
| @@ -988,11 +988,24 @@ func (r Resolved) NewAgent() *core.Agent { | |||||
| 988 | a.Reasoning = r.Reasoning | 988 | a.Reasoning = r.Reasoning | ||
| 989 | a.Temperature = r.Temperature | 989 | a.Temperature = r.Temperature | ||
| 990 | if hasOpenRouterServerTools(r.ToolRegistry) { | 990 | if hasOpenRouterServerTools(r.ToolRegistry) { | ||
| 991 | - | a.MaxToolCalls = tools.OpenRouterServerToolCallLimit | 991 | + | a.MaxToolCalls = openRouterServerToolCallLimit() |
| 992 | } | 992 | } | ||
| 993 | return a | 993 | return a | ||
| 994 | } | 994 | } | ||
| 995 | 995 | ||||
| 996 | + | // openRouterServerToolCallLimit returns the configured call limit from | |||
| 997 | + | // config.json, falling back to the built-in default (4). | |||
| 998 | + | func openRouterServerToolCallLimit() int { | |||
| 999 | + | cfg, err := LoadConfig() | |||
| 1000 | + | if err != nil { | |||
| 1001 | + | return tools.OpenRouterServerToolCallLimit | |||
| 1002 | + | } | |||
| 1003 | + | if cfg.OpenRouterServerToolCallLimit != nil && *cfg.OpenRouterServerToolCallLimit > 0 { | |||
| 1004 | + | return *cfg.OpenRouterServerToolCallLimit | |||
| 1005 | + | } | |||
| 1006 | + | return tools.OpenRouterServerToolCallLimit | |||
| 1007 | + | } | |||
| 1008 | + | ||||
| 996 | // OpenRouterServerToolsEnabled reports the persisted preference. | 1009 | // OpenRouterServerToolsEnabled reports the persisted preference. | ||
| 997 | // nil/missing and unreadable configuration fail closed to disabled. | 1010 | // nil/missing and unreadable configuration fail closed to disabled. | ||
| 998 | func OpenRouterServerToolsEnabled() bool { | 1011 | func OpenRouterServerToolsEnabled() bool { | ||
M packages/agent/cli.go +1 -0
| @@ -1271,6 +1271,7 @@ func runInteractive(ctx context.Context, args Args, version string) error { | |||
| 1271 | 1271 | AutoCompactThreshold: initialCfg.AutoCompactThreshold, | |
| 1272 | 1272 | JailByDefault: initialCfg.JailByDefault, | |
| 1273 | 1273 | OpenRouterServerToolsEnabled: initialCfg.OpenRouterServerToolsEnabled, | |
| 1274 | + | OpenRouterServerToolCallLimit: initialCfg.OpenRouterServerToolCallLimit, | |
| 1274 | 1275 | NoTools: args.NoTools, | |
| 1275 | 1276 | QuickModelShortcuts: quickModelShortcuts, | |
| 1276 | 1277 | RecursiveFileSuggest: initialCfg.RecursiveFileSuggest, | |
| @@ -1271,6 +1271,7 @@ func runInteractive(ctx context.Context, args Args, version string) error { | |||||
| 1271 | AutoCompactThreshold: initialCfg.AutoCompactThreshold, | 1271 | AutoCompactThreshold: initialCfg.AutoCompactThreshold, | ||
| 1272 | JailByDefault: initialCfg.JailByDefault, | 1272 | JailByDefault: initialCfg.JailByDefault, | ||
| 1273 | OpenRouterServerToolsEnabled: initialCfg.OpenRouterServerToolsEnabled, | 1273 | OpenRouterServerToolsEnabled: initialCfg.OpenRouterServerToolsEnabled, | ||
| 1274 | + | OpenRouterServerToolCallLimit: initialCfg.OpenRouterServerToolCallLimit, | |||
| 1274 | NoTools: args.NoTools, | 1275 | NoTools: args.NoTools, | ||
| 1275 | QuickModelShortcuts: quickModelShortcuts, | 1276 | QuickModelShortcuts: quickModelShortcuts, | ||
| 1276 | RecursiveFileSuggest: initialCfg.RecursiveFileSuggest, | 1277 | RecursiveFileSuggest: initialCfg.RecursiveFileSuggest, | ||
M packages/agent/config.go +4 -0
| @@ -73,6 +73,10 @@ type Config struct { | |||
| 73 | 73 | // Default off: users explicitly opt in from /settings. | |
| 74 | 74 | OpenRouterServerToolsEnabled *bool `json:"openrouter_server_tools_enabled,omitempty"` | |
| 75 | 75 | ||
| 76 | + | // OpenRouterServerToolCallLimit caps OpenRouter server-tool calls per turn | |
| 77 | + | // when server tools are enabled. nil/missing uses the built-in default (4). | |
| 78 | + | OpenRouterServerToolCallLimit *int `json:"openrouter_server_tool_call_limit,omitempty"` | |
| 79 | + | ||
| 76 | 80 | // RecursiveFileSuggest controls the @-mention file picker. When true | |
| 77 | 81 | // the picker fuzzy-searches the whole project tree below the working | |
| 78 | 82 | // directory; nil/missing/false keeps the default directory-by- | |
| @@ -73,6 +73,10 @@ type Config struct { | |||||
| 73 | // Default off: users explicitly opt in from /settings. | 73 | // Default off: users explicitly opt in from /settings. | ||
| 74 | OpenRouterServerToolsEnabled *bool `json:"openrouter_server_tools_enabled,omitempty"` | 74 | OpenRouterServerToolsEnabled *bool `json:"openrouter_server_tools_enabled,omitempty"` | ||
| 75 | 75 | ||||
| 76 | + | // OpenRouterServerToolCallLimit caps OpenRouter server-tool calls per turn | |||
| 77 | + | // when server tools are enabled. nil/missing uses the built-in default (4). | |||
| 78 | + | OpenRouterServerToolCallLimit *int `json:"openrouter_server_tool_call_limit,omitempty"` | |||
| 79 | + | ||||
| 76 | // RecursiveFileSuggest controls the @-mention file picker. When true | 80 | // RecursiveFileSuggest controls the @-mention file picker. When true | ||
| 77 | // the picker fuzzy-searches the whole project tree below the working | 81 | // the picker fuzzy-searches the whole project tree below the working | ||
| 78 | // directory; nil/missing/false keeps the default directory-by- | 82 | // directory; nil/missing/false keeps the default directory-by- | ||
M packages/agent/modes/interactive.go +5 -1
| @@ -76,6 +76,10 @@ type InteractiveConfig struct { | |||
| 76 | 76 | // nil means the default, off. | |
| 77 | 77 | OpenRouterServerToolsEnabled *bool | |
| 78 | 78 | ||
| 79 | + | // OpenRouterServerToolCallLimit mirrors the persisted call limit. | |
| 80 | + | // nil means the built-in default (4). | |
| 81 | + | OpenRouterServerToolCallLimit *int | |
| 82 | + | ||
| 79 | 83 | // NoTools preserves the launch-time promise that all tools stay disabled. | |
| 80 | 84 | NoTools bool | |
| 81 | 85 | ||
| @@ -6680,7 +6684,7 @@ func (i *Interactive) applyOpenRouterServerTools(active bool) { | |||
| 6680 | 6684 | for _, t := range tools.OpenRouterServerTools() { | |
| 6681 | 6685 | next[t.Name()] = t | |
| 6682 | 6686 | } | |
| 6683 | - | i.agent.MaxToolCalls = tools.OpenRouterServerToolCallLimit | |
| 6687 | + | i.agent.MaxToolCalls = tools.ResolveToolCallLimit(i.cfg.OpenRouterServerToolCallLimit) | |
| 6684 | 6688 | } | |
| 6685 | 6689 | i.agent.SetTools(next) | |
| 6686 | 6690 | } | |
| @@ -76,6 +76,10 @@ type InteractiveConfig struct { | |||||
| 76 | // nil means the default, off. | 76 | // nil means the default, off. | ||
| 77 | OpenRouterServerToolsEnabled *bool | 77 | OpenRouterServerToolsEnabled *bool | ||
| 78 | 78 | ||||
| 79 | + | // OpenRouterServerToolCallLimit mirrors the persisted call limit. | |||
| 80 | + | // nil means the built-in default (4). | |||
| 81 | + | OpenRouterServerToolCallLimit *int | |||
| 82 | + | ||||
| 79 | // NoTools preserves the launch-time promise that all tools stay disabled. | 83 | // NoTools preserves the launch-time promise that all tools stay disabled. | ||
| 80 | NoTools bool | 84 | NoTools bool | ||
| 81 | 85 | ||||
| @@ -6680,7 +6684,7 @@ func (i *Interactive) applyOpenRouterServerTools(active bool) { | |||||
| 6680 | for _, t := range tools.OpenRouterServerTools() { | 6684 | for _, t := range tools.OpenRouterServerTools() { | ||
| 6681 | next[t.Name()] = t | 6685 | next[t.Name()] = t | ||
| 6682 | } | 6686 | } | ||
| 6683 | - | i.agent.MaxToolCalls = tools.OpenRouterServerToolCallLimit | 6687 | + | i.agent.MaxToolCalls = tools.ResolveToolCallLimit(i.cfg.OpenRouterServerToolCallLimit) |
| 6684 | } | 6688 | } | ||
| 6685 | i.agent.SetTools(next) | 6689 | i.agent.SetTools(next) | ||
| 6686 | } | 6690 | } | ||
M packages/agent/tools/openrouter_server.go +10 -0
| @@ -13,6 +13,16 @@ import ( | |||
| 13 | 13 | // advertises OpenRouter server tools. | |
| 14 | 14 | const OpenRouterServerToolCallLimit = 4 | |
| 15 | 15 | ||
| 16 | + | // ResolveToolCallLimit returns n when it is a positive int, otherwise the | |
| 17 | + | // built-in default. This lets callers resolve a config.json *int without | |
| 18 | + | // importing the agent config package. | |
| 19 | + | func ResolveToolCallLimit(n *int) int { | |
| 20 | + | if n != nil && *n > 0 { | |
| 21 | + | return *n | |
| 22 | + | } | |
| 23 | + | return OpenRouterServerToolCallLimit | |
| 24 | + | } | |
| 25 | + | ||
| 16 | 26 | // OpenRouterServerToolNames lists every OpenRouter server tool that zot | |
| 17 | 27 | // can advertise on Chat Completions. Shell, bash, apply_patch, and | |
| 18 | 28 | // tool_search require Responses/Messages and are omitted here. | |
| @@ -13,6 +13,16 @@ import ( | |||||
| 13 | // advertises OpenRouter server tools. | 13 | // advertises OpenRouter server tools. | ||
| 14 | const OpenRouterServerToolCallLimit = 4 | 14 | const OpenRouterServerToolCallLimit = 4 | ||
| 15 | 15 | ||||
| 16 | + | // ResolveToolCallLimit returns n when it is a positive int, otherwise the | |||
| 17 | + | // built-in default. This lets callers resolve a config.json *int without | |||
| 18 | + | // importing the agent config package. | |||
| 19 | + | func ResolveToolCallLimit(n *int) int { | |||
| 20 | + | if n != nil && *n > 0 { | |||
| 21 | + | return *n | |||
| 22 | + | } | |||
| 23 | + | return OpenRouterServerToolCallLimit | |||
| 24 | + | } | |||
| 25 | + | ||||
| 16 | // OpenRouterServerToolNames lists every OpenRouter server tool that zot | 26 | // OpenRouterServerToolNames lists every OpenRouter server tool that zot | ||
| 17 | // can advertise on Chat Completions. Shell, bash, apply_patch, and | 27 | // can advertise on Chat Completions. Shell, bash, apply_patch, and | ||
| 18 | // tool_search require Responses/Messages and are omitted here. | 28 | // tool_search require Responses/Messages and are omitted here. | ||