Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 61 additions & 61 deletions internal/config/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,43 +48,43 @@ var essential = map[string]bool{
// common holds the settings people actually revisit. Everything not listed
// here or above is treated as advanced.
var common = map[string]bool{
"model.temperature": true,
"model.max_tokens": true,
"model.context_window": true,
"model.reasoning_effort": true,
"model.auxiliary": true,
"agent.max_turns": true,
"agent.personality": true,
"agent.system_prompt_extra": true,
"agent.timezone": true,
"tools.approval_mode": true,
"tools.web_search.provider": true,
"tools.web_search.api_key": true,
"terminal.backend": true,
"terminal.cwd": true,
"terminal.timeout": true,
"memory.memory_enabled": true,
"memory.user_profile_enabled": true,
"rag.embed_model": true,
"rag.embed_provider": true,
"rag.rerank_mode": true,
"rag.per_user": true,
"skills.enabled": true,
"skills.auto_create": true,
"cron.enabled": true,
"cron.timezone": true,
"gateway.enabled": true,
"gateway.telegram.enabled": true,
"gateway.discord.enabled": true,
"mcp.enabled": true,
"compression.enabled": true,
"streaming.enabled": true,
"delegation.enabled": true,
"display.show_reasoning": true,
"display.tool_progress": true,
"display.max_live_reasoning_chars": true,
"logging.level": true,
"server.host": true,
"model.temperature": true,
"model.max_tokens": true,
"model.context_window": true,
"model.reasoning_effort": true,
"model.auxiliary": true,
"agent.max_turns": true,
"agent.personality": true,
"agent.system_prompt_extra": true,
"agent.timezone": true,
"tools.approval_mode": true,
"tools.web_search.provider": true,
"tools.web_search.api_key": true,
"terminal.backend": true,
"terminal.cwd": true,
"terminal.timeout": true,
"memory.memory_enabled": true,
"memory.user_profile_enabled": true,
"rag.embed_model": true,
"rag.embed_provider": true,
"rag.rerank_mode": true,
"rag.per_user": true,
"skills.enabled": true,
"skills.auto_create": true,
"cron.enabled": true,
"cron.timezone": true,
"gateway.enabled": true,
"gateway.telegram.enabled": true,
"gateway.discord.enabled": true,
"mcp.enabled": true,
"compression.enabled": true,
"streaming.enabled": true,
"delegation.enabled": true,
"display.show_reasoning": true,
"display.tool_progress": true,
"display.max_live_reasoning_chars": true,
"logging.level": true,
"server.host": true,
}

func tierFor(path string) string {
Expand Down Expand Up @@ -115,34 +115,34 @@ var enums = map[string][]string{
"session_reset.mode": {"never", "idle", "daily"},
"display.theme": {"system", "light", "dark"},
"logging.level": {"debug", "info", "warn", "error"},
"agent.reasoning_effort": {"none", "low", "medium", "high"},
"model.reasoning_effort": {"none", "low", "medium", "high"},
"tools.web_search.provider": {"browser", "brave", "tavily", "searxng", "none"},
}

var help = map[string]string{
"model.default": "Model id as your provider spells it, e.g. anthropic/claude-sonnet-4.5.",
"model.provider": "Which entry under providers to call.",
"model.auxiliary": "Cheaper model used for summarising and other background work.",
"model.context_window": "Used to decide when to compact; set it to match your model.",
"database.driver": "sqlite for a single node, postgres when you share state.",
"database.dsn": "sqlite: a file path. postgres: postgres://user:pass@host:5432/db?sslmode=disable",
"server.auth_token": "Leave empty to keep the dashboard open — sensible behind a private network.",
"server.host": "0.0.0.0 exposes it on every interface; 127.0.0.1 keeps it local.",
"agent.workspace": "The only directory file tools may read or write.",
"agent.system_prompt_extra": "Appended to the system prompt on every turn.",
"tools.toolset": "Preset deciding which tools reach the model.",
"tools.approval_mode": "auto runs mutating tools directly; deny blocks them.",
"rag.rerank_mode": "How to reorder results: llm (an auxiliary model scores them), api (an external reranker), or off.",
"rag.embed_model": "The embedding model for indexing and search, e.g. text-embedding-3-small.",
"rag.per_user": "Keep a separate memory per chat user (Discord/Telegram), so the agent can recall topics and facts about each specific person. Stores cross-conversation data about individuals; off by default.",
"compression.threshold": "Fraction of the context window that triggers automatic compaction.",
"terminal.backend": "local runs on this machine; docker and ssh sandbox it elsewhere.",
"memory.memory_enabled": "Lets the agent store durable facts between sessions.",
"skills.auto_create": "Allows the agent to write new skills on its own.",
"osint.google_cookie": "Optional. A logged-in Google Cookie header enables osint_google to resolve an email to its public profile. ToS-sensitive; uses your own session. Leave empty to disable.",
"display.show_reasoning": "Stream and show model reasoning/thinking in the dashboard (and TUI). Off skips emitting reasoning events so long thinking traces never hit the UI.",
"display.tool_progress": "Show live tool progress lines while a tool runs.",
"model.default": "Model id as your provider spells it, e.g. anthropic/claude-sonnet-4.5.",
"model.provider": "Which entry under providers to call.",
"model.auxiliary": "Cheaper model used for summarising and other background work.",
"model.context_window": "Used to decide when to compact; set it to match your model.",
"model.reasoning_effort": "Official provider-native reasoning value for this model. Leave empty for the provider default. Custom endpoints send the selected value as reasoning_effort.",
"agent.reasoning_effort": "Fallback reasoning value when a turn does not set one. Official adapters only send values the active model accepts.",
"database.driver": "sqlite for a single node, postgres when you share state.",
"database.dsn": "sqlite: a file path. postgres: postgres://user:pass@host:5432/db?sslmode=disable",
"server.auth_token": "Leave empty to keep the dashboard open — sensible behind a private network.",
"server.host": "0.0.0.0 exposes it on every interface; 127.0.0.1 keeps it local.",
"agent.workspace": "The only directory file tools may read or write.",
"agent.system_prompt_extra": "Appended to the system prompt on every turn.",
"tools.toolset": "Preset deciding which tools reach the model.",
"tools.approval_mode": "auto runs mutating tools directly; deny blocks them.",
"rag.rerank_mode": "How to reorder results: llm (an auxiliary model scores them), api (an external reranker), or off.",
"rag.embed_model": "The embedding model for indexing and search, e.g. text-embedding-3-small.",
"rag.per_user": "Keep a separate memory per chat user (Discord/Telegram), so the agent can recall topics and facts about each specific person. Stores cross-conversation data about individuals; off by default.",
"compression.threshold": "Fraction of the context window that triggers automatic compaction.",
"terminal.backend": "local runs on this machine; docker and ssh sandbox it elsewhere.",
"memory.memory_enabled": "Lets the agent store durable facts between sessions.",
"skills.auto_create": "Allows the agent to write new skills on its own.",
"osint.google_cookie": "Optional. A logged-in Google Cookie header enables osint_google to resolve an email to its public profile. ToS-sensitive; uses your own session. Leave empty to disable.",
"display.show_reasoning": "Stream and show model reasoning/thinking in the dashboard (and TUI). Off skips emitting reasoning events so long thinking traces never hit the UI.",
"display.tool_progress": "Show live tool progress lines while a tool runs.",
"display.max_live_reasoning_chars": "Max characters of reasoning kept in the browser while a turn streams (trailing window). Prevents tab freezes on long thinking. Default 48000. 0 = unlimited. Full text is still saved server-side and restored after the turn.",
}

Expand Down
9 changes: 1 addition & 8 deletions internal/llm/anthropic.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,14 +194,7 @@ func (c *anthropicClient) buildBody(req Request, stream bool) map[string]any {
}
}
}
switch strings.ToLower(req.ReasoningEffort) {
case "low":
body["thinking"] = map[string]any{"type": "enabled", "budget_tokens": 2048}
case "medium":
body["thinking"] = map[string]any{"type": "enabled", "budget_tokens": 8192}
case "high":
body["thinking"] = map[string]any{"type": "enabled", "budget_tokens": 16384}
}
mergeEncoded(body, OfficialReasoning(c.opts.Kind, c.opts.BaseURL, req.Model).Encode(req.ReasoningEffort))
if _, ok := body["thinking"]; ok {
// Thinking requires headroom beyond the budget.
if maxTokens < 16384 {
Expand Down
8 changes: 6 additions & 2 deletions internal/llm/codex.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,12 @@ func (c *codexClient) buildBody(req Request, stream bool) map[string]any {
if req.Temperature > 0 {
body["temperature"] = req.Temperature
}
if e := strings.ToLower(req.ReasoningEffort); e != "" && e != "none" {
body["reasoning"] = map[string]any{"effort": e}
if enc := OfficialReasoning("openai", c.opts.BaseURL, req.Model).Encode(req.ReasoningEffort); !enc.Omit {
if e, ok := enc.Body["reasoning_effort"]; ok {
body["reasoning"] = map[string]any{"effort": e}
} else {
mergeEncoded(body, enc)
}
}
if len(req.Tools) > 0 {
tools := make([]map[string]any, 0, len(req.Tools))
Expand Down
30 changes: 3 additions & 27 deletions internal/llm/gemini.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,35 +375,11 @@ func (c *geminiClient) endpoint(model, method string, stream bool) string {
// use thinkingBudget token counts. includeThoughts requests thought summaries
// when the endpoint exposes them (not all reverse proxies return thought text).
func geminiThinkingConfig(model, effort string) map[string]any {
e := strings.ToLower(strings.TrimSpace(effort))
if e == "" {
return nil
}
useLevel := geminiModelUsesThinkingLevel(model)
switch e {
case "none":
if useLevel {
return map[string]any{"thinkingLevel": "MINIMAL", "includeThoughts": false}
}
return map[string]any{"thinkingBudget": 0}
case "low":
if useLevel {
return map[string]any{"thinkingLevel": "LOW", "includeThoughts": true}
}
return map[string]any{"thinkingBudget": 2048, "includeThoughts": true}
case "medium":
if useLevel {
return map[string]any{"thinkingLevel": "MEDIUM", "includeThoughts": true}
}
return map[string]any{"thinkingBudget": 8192, "includeThoughts": true}
case "high":
if useLevel {
return map[string]any{"thinkingLevel": "HIGH", "includeThoughts": true}
}
return map[string]any{"thinkingBudget": 24576, "includeThoughts": true}
default:
enc := OfficialReasoning("gemini", "", model).Encode(effort)
if enc.Omit || len(enc.GeminiThinking) == 0 {
return nil
}
return enc.GeminiThinking
}

func geminiModelUsesThinkingLevel(model string) bool {
Expand Down
9 changes: 1 addition & 8 deletions internal/llm/openai.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,14 +189,7 @@ func (c *openAIClient) buildBody(req Request, stream bool) map[string]any {
body["parallel_tool_calls"] = false
}
}
if e := strings.ToLower(req.ReasoningEffort); e != "" && e != "none" {
// OpenAI uses reasoning_effort; OpenRouter accepts a reasoning object.
body["reasoning_effort"] = e
if strings.Contains(c.opts.BaseURL, "openrouter.ai") {
delete(body, "reasoning_effort")
body["reasoning"] = map[string]any{"effort": e}
}
}
mergeEncoded(body, OfficialReasoning(c.opts.Kind, c.opts.BaseURL, req.Model).Encode(req.ReasoningEffort))
for k, v := range req.Extra {
body[k] = v
}
Expand Down
Loading
Loading