🛟 Updated.Unverified
13af864 parent: 58ba549 modified
.tickets/issues/0067-enable-disable-reasoning-mode.yaml +13 -0 | @@ -11,6 +11,7 @@ labels: | ||
| 11 | 11 | - kind::feature |
| 12 | 12 | related: |
| 13 | 13 | - 62 |
| 14 | + - 76 | |
| 14 | 15 | body: | |
| 15 | 16 | Question (user, 2026-10-06): can mm turn a model's reasoning mode on or |
| 16 | 17 | off? |
| @@ -193,3 +194,15 @@ comments: | ||
| 193 | 194 | `reasoning_effort` (Ollama) — per-server spelling left to the user. |
| 194 | 195 | A `thinking: false` key is still worth it for one spelling across |
| 195 | 196 | providers (it would reuse `withoutReasoning`) and for `/think`. |
| 197 | + - id: 2 | |
| 198 | + author: | |
| 199 | + name: claude | |
| 200 | + email: noreply@anthropic.com | |
| 201 | + createdAt: 2026-10-06T16:00:00.000Z | |
| 202 | + body: | | |
| 203 | + Ticket 76 is done on branch `feature/genkit-v1.13.1`: once merged, | |
| 204 | + `sampling.chat_template_kwargs: {enable_thinking: false}` (dmr, | |
| 205 | + llamacpp, llmman) and `sampling.reasoning_effort: none` (ollama) turn | |
| 206 | + the reasoning off for every turn — documented in reference/configuration | |
| 207 | + and explanation/thinking-models (EN+FR). What this ticket still adds: | |
| 208 | + one provider-independent `thinking: false` key and `/think`. | |
| @@ -11,6 +11,7 @@ labels: | |||
| 11 | - kind::feature | 11 | - kind::feature |
| 12 | related: | 12 | related: |
| 13 | - 62 | 13 | - 62 |
| 14 | + - 76 | ||
| 14 | body: | | 15 | body: | |
| 15 | Question (user, 2026-10-06): can mm turn a model's reasoning mode on or | 16 | Question (user, 2026-10-06): can mm turn a model's reasoning mode on or |
| 16 | off? | 17 | off? |
| @@ -193,3 +194,15 @@ comments: | |||
| 193 | `reasoning_effort` (Ollama) — per-server spelling left to the user. | 194 | `reasoning_effort` (Ollama) — per-server spelling left to the user. |
| 194 | A `thinking: false` key is still worth it for one spelling across | 195 | A `thinking: false` key is still worth it for one spelling across |
| 195 | providers (it would reuse `withoutReasoning`) and for `/think`. | 196 | providers (it would reuse `withoutReasoning`) and for `/think`. |
| 197 | + - id: 2 | ||
| 198 | + author: | ||
| 199 | + name: claude | ||
| 200 | + email: noreply@anthropic.com | ||
| 201 | + createdAt: 2026-10-06T16:00:00.000Z | ||
| 202 | + body: | | ||
| 203 | + Ticket 76 is done on branch `feature/genkit-v1.13.1`: once merged, | ||
| 204 | + `sampling.chat_template_kwargs: {enable_thinking: false}` (dmr, | ||
| 205 | + llamacpp, llmman) and `sampling.reasoning_effort: none` (ollama) turn | ||
| 206 | + the reasoning off for every turn — documented in reference/configuration | ||
| 207 | + and explanation/thinking-models (EN+FR). What this ticket still adds: | ||
| 208 | + one provider-independent `thinking: false` key and `/think`. | ||
added
.tickets/issues/0076-upgrade-genkit-go-to-v1-13-1.yaml +207 -0 | new file mode 100644 | ||
| @@ -0,0 +1,207 @@ | ||
| 1 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | |
| 2 | +id: 76 | |
| 3 | +title: Upgrade Genkit Go to v1.13.1 | |
| 4 | +state: open | |
| 5 | +author: | |
| 6 | + name: k33g | |
| 7 | + email: ph.charriere@gmail.com | |
| 8 | +createdAt: 2026-10-06T14:30:00.000Z | |
| 9 | +updatedAt: 2026-10-06T16:00:00.000Z | |
| 10 | +labels: | |
| 11 | + - kind::chore | |
| 12 | + - priority::medium | |
| 13 | +epic: Providers (LLM Engine) | |
| 14 | +related: | |
| 15 | + - 67 | |
| 16 | +body: | | |
| 17 | + Bump `github.com/firebase/genkit/go` from v1.10.0 (current go.mod) to | |
| 18 | + v1.13.1 (latest, 2026-09-03). | |
| 19 | + | |
| 20 | + ## Why | |
| 21 | + | |
| 22 | + Since v1.12.0, compat_oai's `ModelGenerator.WithConfig` keeps the | |
| 23 | + `sampling` keys `openai.ChatCompletionNewParams` does not declare and | |
| 24 | + sends them as JSON extras (`SetExtraFields`, plugins/compat_oai/ | |
| 25 | + generate.go ~l.231). In v1.10.0 they are silently dropped (JSON round | |
| 26 | + trip through `base.MapToStruct`). That makes provider-specific keys | |
| 27 | + usable from the YAML, first of all | |
| 28 | + `chat_template_kwargs: {enable_thinking: false}` for the llama.cpp | |
| 29 | + family (dmr, llamacpp, llmman) — see ticket 67. | |
| 30 | + | |
| 31 | + ## The trap (found in a scratch copy on 2026-10-06) | |
| 32 | + | |
| 33 | + Bumping alone BREAKS requests that carry an unknown key: | |
| 34 | + | |
| 35 | + - In v1.13.1 `OpenAICompatible.DefineModel` is deprecated and returns an | |
| 36 | + UNREGISTERED model. mm discards its result | |
| 37 | + (internal/engine/openai_compat.go:189), so the call is a no-op. | |
| 38 | + - The model actually used is then resolved on the fly by the plugin | |
| 39 | + (`ResolveAction` -> `newSDKModel`), with the strict reflected SDK config | |
| 40 | + schema: `config: Additional property chat_template_kwargs is not | |
| 41 | + allowed`, and the whole request fails (v1.10.0 only dropped the key). | |
| 42 | + - `ModelOptions.ConfigSchema` does not help: it is set on the model | |
| 43 | + nobody registered. | |
| 44 | + | |
| 45 | + Fix proven in the scratch copy: | |
| 46 | + | |
| 47 | + genkit.RegisterAction(g, client.DefineModel(p.name, b.Model, ai.ModelOptions{ | |
| 48 | + Label: b.Model, | |
| 49 | + Supports: &compat_oai.Multimodal, | |
| 50 | + }).(*ai.ModelAction)) | |
| 51 | + | |
| 52 | + `DefineModel`'s model takes its config untyped: no validation, unknown | |
| 53 | + keys ride to the wire. Alternative to evaluate: the non-deprecated | |
| 54 | + `client.NewModel(id, opts)` + `genkit.RegisterAction`, which validates | |
| 55 | + against the strict schema unless an explicit permissive `ConfigSchema` | |
| 56 | + is given (it would reject unknown keys otherwise). | |
| 57 | + | |
| 58 | + ## Measured in the scratch copy | |
| 59 | + | |
| 60 | + - `go get github.com/firebase/genkit/go@v1.13.1` + `go mod tidy`: go | |
| 61 | + stays `1.26.0`; also bumps google/dotprompt, invopop/jsonschema | |
| 62 | + v0.14.0, goccy/go-yaml v1.19.2, buger/jsonparser v1.1.2, adds | |
| 63 | + go.yaml.in/yaml/v4; openai-go stays v1.8.2. | |
| 64 | + - `go build`, `go vet`, `go test ./...` green (the skills shipped-YAML | |
| 65 | + test failed only because the copy had no YAML files). | |
| 66 | + - Recording server: the request carries | |
| 67 | + `"chat_template_kwargs": {"enable_thinking": false}` AND mm's own | |
| 68 | + middleware fields (`return_progress`, `stream_options`). | |
| 69 | + - Live, DMR qwen3.5-4b, `-p "What is 17*23?"`, sampling | |
| 70 | + `chat_template_kwargs.enable_thinking: false`: v1.10.0 = 4 s, a 💭 | |
| 71 | + reasoning block, 391; v1.13.1 + RegisterAction = under 1 s, no | |
| 72 | + reasoning, 391. | |
| 73 | + | |
| 74 | + ## Side effects to handle | |
| 75 | + | |
| 76 | + - Genkit now logs `INFO Genkit initialized env=prod plugins=[dmr] …` on | |
| 77 | + stderr at start-up: silence it (slog level/handler), like the ACP SDK's | |
| 78 | + "connection closed" log in internal/ask. | |
| 79 | + - Not re-checked after the bump, each relying on Genkit internals: | |
| 80 | + - internal/engine/reasoning.go — the tap that reads | |
| 81 | + `reasoning_content` off the wire because v1.10.0 dropped it; v1.13.1 | |
| 82 | + reads `reasoning_content` / `reasoning` itself (generate.go ~l.548, | |
| 83 | + ~l.824): possible double display, or the tap becomes redundant. | |
| 84 | + - internal/engine/toolargs.go — tool-argument decoding workarounds. | |
| 85 | + - internal/engine/usage.go — the comment saying sampling cannot carry | |
| 86 | + the no-reasoning field becomes false (summary path can stay as is). | |
| 87 | + - tool order (ticket 53, tests/tool-order), the watchdog, the | |
| 88 | + non-streaming retry, `max_tokens` cut-off handling. | |
| 89 | + - ollama and llmman providers, ACP and window modes, MCP tools. | |
| 90 | + - Docs: providers / thinking-models explanations, configuration | |
| 91 | + reference (`sampling` passes extra keys), INSTALL/README if they quote | |
| 92 | + the Genkit version; ebook chapter on the engine if it shows | |
| 93 | + `DefineModel`. | |
| 94 | +tasks: | |
| 95 | + - id: 1 | |
| 96 | + title: "Branch from main after feature/yolo is merged; go get github.com/firebase/genkit/go@v1.13.1 + go mod tidy" | |
| 97 | + state: closed | |
| 98 | + priority: none | |
| 99 | + author: | |
| 100 | + name: k33g | |
| 101 | + email: ph.charriere@gmail.com | |
| 102 | + createdAt: 2026-10-06T14:30:00.000Z | |
| 103 | + updatedAt: 2026-10-06T16:00:00.000Z | |
| 104 | + - id: 2 | |
| 105 | + title: "Failing test first: a sampling key unknown to the OpenAI SDK (chat_template_kwargs) reaches the request body (httptest server)" | |
| 106 | + state: closed | |
| 107 | + priority: none | |
| 108 | + author: | |
| 109 | + name: k33g | |
| 110 | + email: ph.charriere@gmail.com | |
| 111 | + createdAt: 2026-10-06T14:30:00.000Z | |
| 112 | + updatedAt: 2026-10-06T16:00:00.000Z | |
| 113 | + - id: 3 | |
| 114 | + title: "Register the model explicitly (RegisterAction + DefineModel, or NewModel with a permissive ConfigSchema) — decide which" | |
| 115 | + state: closed | |
| 116 | + priority: none | |
| 117 | + author: | |
| 118 | + name: k33g | |
| 119 | + email: ph.charriere@gmail.com | |
| 120 | + createdAt: 2026-10-06T14:30:00.000Z | |
| 121 | + updatedAt: 2026-10-06T16:00:00.000Z | |
| 122 | + - id: 4 | |
| 123 | + title: "Re-check reasoning.go (Genkit now reads reasoning_content itself), toolargs.go, usage.go against v1.13.1; remove what became redundant" | |
| 124 | + state: closed | |
| 125 | + priority: none | |
| 126 | + author: | |
| 127 | + name: k33g | |
| 128 | + email: ph.charriere@gmail.com | |
| 129 | + createdAt: 2026-10-06T14:30:00.000Z | |
| 130 | + updatedAt: 2026-10-06T16:00:00.000Z | |
| 131 | + - id: 5 | |
| 132 | + title: "Silence Genkit's 'INFO Genkit initialized' start-up log" | |
| 133 | + state: closed | |
| 134 | + priority: none | |
| 135 | + author: | |
| 136 | + name: k33g | |
| 137 | + email: ph.charriere@gmail.com | |
| 138 | + createdAt: 2026-10-06T14:30:00.000Z | |
| 139 | + updatedAt: 2026-10-06T16:00:00.000Z | |
| 140 | + - id: 6 | |
| 141 | + title: "Live checks: dmr, llamacpp, llmman, ollama; line, window, ACP; tools, MCP, /compact, tool-order proxy (tests/tool-order)" | |
| 142 | + state: open | |
| 143 | + priority: none | |
| 144 | + author: | |
| 145 | + name: k33g | |
| 146 | + email: ph.charriere@gmail.com | |
| 147 | + createdAt: 2026-10-06T14:30:00.000Z | |
| 148 | + updatedAt: 2026-10-06T16:00:00.000Z | |
| 149 | + - id: 7 | |
| 150 | + title: "Docs EN+FR: sampling extra keys (configuration reference), providers / thinking-models explanations; .memory summary (Genkit version)" | |
| 151 | + state: closed | |
| 152 | + priority: none | |
| 153 | + author: | |
| 154 | + name: k33g | |
| 155 | + email: ph.charriere@gmail.com | |
| 156 | + createdAt: 2026-10-06T14:30:00.000Z | |
| 157 | + updatedAt: 2026-10-06T16:00:00.000Z | |
| 158 | +comments: | |
| 159 | + - id: 1 | |
| 160 | + author: | |
| 161 | + name: claude | |
| 162 | + email: noreply@anthropic.com | |
| 163 | + createdAt: 2026-10-06T16:00:00.000Z | |
| 164 | + body: | | |
| 165 | + **Done on branch `feature/genkit-v1.13.1` (from main 58ba549), uncommitted.** | |
| 166 | + | |
| 167 | + - go.mod: genkit v1.13.1 (+ the indirect bumps listed in the body; | |
| 168 | + mailru/easyjson and wk8/go-ordered-map dropped, pb33f/ordered-map | |
| 169 | + and go.yaml.in/yaml/v4 added). | |
| 170 | + - Task 3 decision: `genkit.RegisterAction(g, client.DefineModel(...))` | |
| 171 | + (`//nolint:staticcheck`, explained). NewModel rejected: it converts | |
| 172 | + the map config to `openai.ChatCompletionNewParams` BEFORE the model | |
| 173 | + function, so the extras are lost (measured round trip). | |
| 174 | + - TWO regressions the bump brought, found by tests/live, both fixed: | |
| 175 | + 1. Genkit now hands reasoning-only chunks to the streaming callback; | |
| 176 | + mm's `emit("")` closed the 💭 block on each → one block per token | |
| 177 | + in the line mode (live, qwen3.5-4b). Fix: `onlyReasoning(chunk)` | |
| 178 | + skips them (the tap still displays). Test needs a FLUSHED stream | |
| 179 | + (`TestReasoningStreamsAsOneBlock`): written at once, the tap reads | |
| 180 | + everything before the callback and the bug hides. | |
| 181 | + 2. Genkit now appends the reasoning as a reasoning PART of the final | |
| 182 | + message → it would enter mm's history/session file and go back to | |
| 183 | + the server as `reasoning_content`. Fix: `dropReasoning(resp)` in | |
| 184 | + the WrapModel hook (covers tool-loop calls too). | |
| 185 | + `TestReasoningStaysOutOfTheHistory`. | |
| 186 | + - Genkit's `INFO Genkit initialized` silenced: `quietGenkit()` = | |
| 187 | + `logger.SetLevel(slog.LevelWarn)` before Init (GENKIT_LOG_LEVEL wins). | |
| 188 | + - toolargs.go: Genkit still says "could not parse tool args" — unchanged. | |
| 189 | + usage.go: comment corrected. reasoning.go tap kept (display + | |
| 190 | + watchdog heartbeat, one road). | |
| 191 | + - Tests: `TestSamplingKeysUnknownToTheSDKReachTheServer` (failed first | |
| 192 | + with the schema error), the two reasoning tests, `TestOpenKeepsGenkitQuiet`. | |
| 193 | + `go test ./...` green, vet clean. | |
| 194 | + - Live: DMR mellum2 + bash; DMR qwen3.5-4b thinking shown as ONE block, | |
| 195 | + `chat_template_kwargs` off via sampling (no reasoning, 391); | |
| 196 | + two-turn REPL with sessions: session file holds text parts only; | |
| 197 | + llmman Qwen3.5-4B thinking + bash + second thought block; ACP | |
| 198 | + (scripted client): stdout pure JSON-RPC, 149 thought chunks forming | |
| 199 | + one coherent text, answer 391, no INFO line on stderr; tests/tool-order | |
| 200 | + session.py two ACP turns end_turn; /compact with summaryMaxTokens 300 | |
| 201 | + succeeds. | |
| 202 | + - NOT checked live: ollama and llamacpp servers (not running on the | |
| 203 | + host), the window mode, the tool-order proxy measurement, MCP tools. | |
| 204 | + - Docs EN+FR: reference/configuration (`sampling` extra keys), | |
| 205 | + explanation/thinking-models (Genkit 1.13 delivery, how to turn | |
| 206 | + reasoning off through sampling). | |
| 207 | + | |
| new file mode 100644 | |||
| @@ -0,0 +1,207 @@ | |||
| 1 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | ||
| 2 | +id: 76 | ||
| 3 | +title: Upgrade Genkit Go to v1.13.1 | ||
| 4 | +state: open | ||
| 5 | +author: | ||
| 6 | + name: k33g | ||
| 7 | + email: ph.charriere@gmail.com | ||
| 8 | +createdAt: 2026-10-06T14:30:00.000Z | ||
| 9 | +updatedAt: 2026-10-06T16:00:00.000Z | ||
| 10 | +labels: | ||
| 11 | + - kind::chore | ||
| 12 | + - priority::medium | ||
| 13 | +epic: Providers (LLM Engine) | ||
| 14 | +related: | ||
| 15 | + - 67 | ||
| 16 | +body: | | ||
| 17 | + Bump `github.com/firebase/genkit/go` from v1.10.0 (current go.mod) to | ||
| 18 | + v1.13.1 (latest, 2026-09-03). | ||
| 19 | + | ||
| 20 | + ## Why | ||
| 21 | + | ||
| 22 | + Since v1.12.0, compat_oai's `ModelGenerator.WithConfig` keeps the | ||
| 23 | + `sampling` keys `openai.ChatCompletionNewParams` does not declare and | ||
| 24 | + sends them as JSON extras (`SetExtraFields`, plugins/compat_oai/ | ||
| 25 | + generate.go ~l.231). In v1.10.0 they are silently dropped (JSON round | ||
| 26 | + trip through `base.MapToStruct`). That makes provider-specific keys | ||
| 27 | + usable from the YAML, first of all | ||
| 28 | + `chat_template_kwargs: {enable_thinking: false}` for the llama.cpp | ||
| 29 | + family (dmr, llamacpp, llmman) — see ticket 67. | ||
| 30 | + | ||
| 31 | + ## The trap (found in a scratch copy on 2026-10-06) | ||
| 32 | + | ||
| 33 | + Bumping alone BREAKS requests that carry an unknown key: | ||
| 34 | + | ||
| 35 | + - In v1.13.1 `OpenAICompatible.DefineModel` is deprecated and returns an | ||
| 36 | + UNREGISTERED model. mm discards its result | ||
| 37 | + (internal/engine/openai_compat.go:189), so the call is a no-op. | ||
| 38 | + - The model actually used is then resolved on the fly by the plugin | ||
| 39 | + (`ResolveAction` -> `newSDKModel`), with the strict reflected SDK config | ||
| 40 | + schema: `config: Additional property chat_template_kwargs is not | ||
| 41 | + allowed`, and the whole request fails (v1.10.0 only dropped the key). | ||
| 42 | + - `ModelOptions.ConfigSchema` does not help: it is set on the model | ||
| 43 | + nobody registered. | ||
| 44 | + | ||
| 45 | + Fix proven in the scratch copy: | ||
| 46 | + | ||
| 47 | + genkit.RegisterAction(g, client.DefineModel(p.name, b.Model, ai.ModelOptions{ | ||
| 48 | + Label: b.Model, | ||
| 49 | + Supports: &compat_oai.Multimodal, | ||
| 50 | + }).(*ai.ModelAction)) | ||
| 51 | + | ||
| 52 | + `DefineModel`'s model takes its config untyped: no validation, unknown | ||
| 53 | + keys ride to the wire. Alternative to evaluate: the non-deprecated | ||
| 54 | + `client.NewModel(id, opts)` + `genkit.RegisterAction`, which validates | ||
| 55 | + against the strict schema unless an explicit permissive `ConfigSchema` | ||
| 56 | + is given (it would reject unknown keys otherwise). | ||
| 57 | + | ||
| 58 | + ## Measured in the scratch copy | ||
| 59 | + | ||
| 60 | + - `go get github.com/firebase/genkit/go@v1.13.1` + `go mod tidy`: go | ||
| 61 | + stays `1.26.0`; also bumps google/dotprompt, invopop/jsonschema | ||
| 62 | + v0.14.0, goccy/go-yaml v1.19.2, buger/jsonparser v1.1.2, adds | ||
| 63 | + go.yaml.in/yaml/v4; openai-go stays v1.8.2. | ||
| 64 | + - `go build`, `go vet`, `go test ./...` green (the skills shipped-YAML | ||
| 65 | + test failed only because the copy had no YAML files). | ||
| 66 | + - Recording server: the request carries | ||
| 67 | + `"chat_template_kwargs": {"enable_thinking": false}` AND mm's own | ||
| 68 | + middleware fields (`return_progress`, `stream_options`). | ||
| 69 | + - Live, DMR qwen3.5-4b, `-p "What is 17*23?"`, sampling | ||
| 70 | + `chat_template_kwargs.enable_thinking: false`: v1.10.0 = 4 s, a 💭 | ||
| 71 | + reasoning block, 391; v1.13.1 + RegisterAction = under 1 s, no | ||
| 72 | + reasoning, 391. | ||
| 73 | + | ||
| 74 | + ## Side effects to handle | ||
| 75 | + | ||
| 76 | + - Genkit now logs `INFO Genkit initialized env=prod plugins=[dmr] …` on | ||
| 77 | + stderr at start-up: silence it (slog level/handler), like the ACP SDK's | ||
| 78 | + "connection closed" log in internal/ask. | ||
| 79 | + - Not re-checked after the bump, each relying on Genkit internals: | ||
| 80 | + - internal/engine/reasoning.go — the tap that reads | ||
| 81 | + `reasoning_content` off the wire because v1.10.0 dropped it; v1.13.1 | ||
| 82 | + reads `reasoning_content` / `reasoning` itself (generate.go ~l.548, | ||
| 83 | + ~l.824): possible double display, or the tap becomes redundant. | ||
| 84 | + - internal/engine/toolargs.go — tool-argument decoding workarounds. | ||
| 85 | + - internal/engine/usage.go — the comment saying sampling cannot carry | ||
| 86 | + the no-reasoning field becomes false (summary path can stay as is). | ||
| 87 | + - tool order (ticket 53, tests/tool-order), the watchdog, the | ||
| 88 | + non-streaming retry, `max_tokens` cut-off handling. | ||
| 89 | + - ollama and llmman providers, ACP and window modes, MCP tools. | ||
| 90 | + - Docs: providers / thinking-models explanations, configuration | ||
| 91 | + reference (`sampling` passes extra keys), INSTALL/README if they quote | ||
| 92 | + the Genkit version; ebook chapter on the engine if it shows | ||
| 93 | + `DefineModel`. | ||
| 94 | +tasks: | ||
| 95 | + - id: 1 | ||
| 96 | + title: "Branch from main after feature/yolo is merged; go get github.com/firebase/genkit/go@v1.13.1 + go mod tidy" | ||
| 97 | + state: closed | ||
| 98 | + priority: none | ||
| 99 | + author: | ||
| 100 | + name: k33g | ||
| 101 | + email: ph.charriere@gmail.com | ||
| 102 | + createdAt: 2026-10-06T14:30:00.000Z | ||
| 103 | + updatedAt: 2026-10-06T16:00:00.000Z | ||
| 104 | + - id: 2 | ||
| 105 | + title: "Failing test first: a sampling key unknown to the OpenAI SDK (chat_template_kwargs) reaches the request body (httptest server)" | ||
| 106 | + state: closed | ||
| 107 | + priority: none | ||
| 108 | + author: | ||
| 109 | + name: k33g | ||
| 110 | + email: ph.charriere@gmail.com | ||
| 111 | + createdAt: 2026-10-06T14:30:00.000Z | ||
| 112 | + updatedAt: 2026-10-06T16:00:00.000Z | ||
| 113 | + - id: 3 | ||
| 114 | + title: "Register the model explicitly (RegisterAction + DefineModel, or NewModel with a permissive ConfigSchema) — decide which" | ||
| 115 | + state: closed | ||
| 116 | + priority: none | ||
| 117 | + author: | ||
| 118 | + name: k33g | ||
| 119 | + email: ph.charriere@gmail.com | ||
| 120 | + createdAt: 2026-10-06T14:30:00.000Z | ||
| 121 | + updatedAt: 2026-10-06T16:00:00.000Z | ||
| 122 | + - id: 4 | ||
| 123 | + title: "Re-check reasoning.go (Genkit now reads reasoning_content itself), toolargs.go, usage.go against v1.13.1; remove what became redundant" | ||
| 124 | + state: closed | ||
| 125 | + priority: none | ||
| 126 | + author: | ||
| 127 | + name: k33g | ||
| 128 | + email: ph.charriere@gmail.com | ||
| 129 | + createdAt: 2026-10-06T14:30:00.000Z | ||
| 130 | + updatedAt: 2026-10-06T16:00:00.000Z | ||
| 131 | + - id: 5 | ||
| 132 | + title: "Silence Genkit's 'INFO Genkit initialized' start-up log" | ||
| 133 | + state: closed | ||
| 134 | + priority: none | ||
| 135 | + author: | ||
| 136 | + name: k33g | ||
| 137 | + email: ph.charriere@gmail.com | ||
| 138 | + createdAt: 2026-10-06T14:30:00.000Z | ||
| 139 | + updatedAt: 2026-10-06T16:00:00.000Z | ||
| 140 | + - id: 6 | ||
| 141 | + title: "Live checks: dmr, llamacpp, llmman, ollama; line, window, ACP; tools, MCP, /compact, tool-order proxy (tests/tool-order)" | ||
| 142 | + state: open | ||
| 143 | + priority: none | ||
| 144 | + author: | ||
| 145 | + name: k33g | ||
| 146 | + email: ph.charriere@gmail.com | ||
| 147 | + createdAt: 2026-10-06T14:30:00.000Z | ||
| 148 | + updatedAt: 2026-10-06T16:00:00.000Z | ||
| 149 | + - id: 7 | ||
| 150 | + title: "Docs EN+FR: sampling extra keys (configuration reference), providers / thinking-models explanations; .memory summary (Genkit version)" | ||
| 151 | + state: closed | ||
| 152 | + priority: none | ||
| 153 | + author: | ||
| 154 | + name: k33g | ||
| 155 | + email: ph.charriere@gmail.com | ||
| 156 | + createdAt: 2026-10-06T14:30:00.000Z | ||
| 157 | + updatedAt: 2026-10-06T16:00:00.000Z | ||
| 158 | +comments: | ||
| 159 | + - id: 1 | ||
| 160 | + author: | ||
| 161 | + name: claude | ||
| 162 | + email: noreply@anthropic.com | ||
| 163 | + createdAt: 2026-10-06T16:00:00.000Z | ||
| 164 | + body: | | ||
| 165 | + **Done on branch `feature/genkit-v1.13.1` (from main 58ba549), uncommitted.** | ||
| 166 | + | ||
| 167 | + - go.mod: genkit v1.13.1 (+ the indirect bumps listed in the body; | ||
| 168 | + mailru/easyjson and wk8/go-ordered-map dropped, pb33f/ordered-map | ||
| 169 | + and go.yaml.in/yaml/v4 added). | ||
| 170 | + - Task 3 decision: `genkit.RegisterAction(g, client.DefineModel(...))` | ||
| 171 | + (`//nolint:staticcheck`, explained). NewModel rejected: it converts | ||
| 172 | + the map config to `openai.ChatCompletionNewParams` BEFORE the model | ||
| 173 | + function, so the extras are lost (measured round trip). | ||
| 174 | + - TWO regressions the bump brought, found by tests/live, both fixed: | ||
| 175 | + 1. Genkit now hands reasoning-only chunks to the streaming callback; | ||
| 176 | + mm's `emit("")` closed the 💭 block on each → one block per token | ||
| 177 | + in the line mode (live, qwen3.5-4b). Fix: `onlyReasoning(chunk)` | ||
| 178 | + skips them (the tap still displays). Test needs a FLUSHED stream | ||
| 179 | + (`TestReasoningStreamsAsOneBlock`): written at once, the tap reads | ||
| 180 | + everything before the callback and the bug hides. | ||
| 181 | + 2. Genkit now appends the reasoning as a reasoning PART of the final | ||
| 182 | + message → it would enter mm's history/session file and go back to | ||
| 183 | + the server as `reasoning_content`. Fix: `dropReasoning(resp)` in | ||
| 184 | + the WrapModel hook (covers tool-loop calls too). | ||
| 185 | + `TestReasoningStaysOutOfTheHistory`. | ||
| 186 | + - Genkit's `INFO Genkit initialized` silenced: `quietGenkit()` = | ||
| 187 | + `logger.SetLevel(slog.LevelWarn)` before Init (GENKIT_LOG_LEVEL wins). | ||
| 188 | + - toolargs.go: Genkit still says "could not parse tool args" — unchanged. | ||
| 189 | + usage.go: comment corrected. reasoning.go tap kept (display + | ||
| 190 | + watchdog heartbeat, one road). | ||
| 191 | + - Tests: `TestSamplingKeysUnknownToTheSDKReachTheServer` (failed first | ||
| 192 | + with the schema error), the two reasoning tests, `TestOpenKeepsGenkitQuiet`. | ||
| 193 | + `go test ./...` green, vet clean. | ||
| 194 | + - Live: DMR mellum2 + bash; DMR qwen3.5-4b thinking shown as ONE block, | ||
| 195 | + `chat_template_kwargs` off via sampling (no reasoning, 391); | ||
| 196 | + two-turn REPL with sessions: session file holds text parts only; | ||
| 197 | + llmman Qwen3.5-4B thinking + bash + second thought block; ACP | ||
| 198 | + (scripted client): stdout pure JSON-RPC, 149 thought chunks forming | ||
| 199 | + one coherent text, answer 391, no INFO line on stderr; tests/tool-order | ||
| 200 | + session.py two ACP turns end_turn; /compact with summaryMaxTokens 300 | ||
| 201 | + succeeds. | ||
| 202 | + - NOT checked live: ollama and llamacpp servers (not running on the | ||
| 203 | + host), the window mode, the tool-order proxy measurement, MCP tools. | ||
| 204 | + - Docs EN+FR: reference/configuration (`sampling` extra keys), | ||
| 205 | + explanation/thinking-models (Genkit 1.13 delivery, how to turn | ||
| 206 | + reasoning off through sampling). | ||
| 207 | + | ||
added
.tickets/issues/0077-ebook-chapter-on-mcp.yaml +131 -0 | new file mode 100644 | ||
| @@ -0,0 +1,131 @@ | ||
| 1 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | |
| 2 | +id: 77 | |
| 3 | +title: "Ebook: a chapter on MCP" | |
| 4 | +state: open | |
| 5 | +author: | |
| 6 | + name: k33g | |
| 7 | + email: ph.charriere@gmail.com | |
| 8 | +createdAt: 2026-10-06T18:00:00.000Z | |
| 9 | +updatedAt: 2026-10-06T18:00:00.000Z | |
| 10 | +labels: | |
| 11 | + - documentation | |
| 12 | + - priority::low | |
| 13 | +body: | | |
| 14 | + The ebook (« Créer un agent de code pas à pas », `ebook/`, French | |
| 15 | + AsciiDoc, built with the ebook-builder skill) never covers the MCP client, | |
| 16 | + and its conclusion (ebook/chapters/99-conclusion.adoc, "Ce qu'il faudrait | |
| 17 | + faire ensuite") still lists MCP as future work — stale since the client | |
| 18 | + landed. Found while updating the book for yolo + Genkit 1.13 | |
| 19 | + (2026-10-06). | |
| 20 | + | |
| 21 | + ## What exists to describe (verified 2026-10-06) | |
| 22 | + | |
| 23 | + - `internal/mcp` (~1,600 lines with internal/tools/mcp.go): | |
| 24 | + - `client.go` — `Client` interface, `ServerClient`, `Connect`, | |
| 25 | + `ConnectAll` (discovery, warnings), `CloseAll`, the `Tool` model. | |
| 26 | + - `http_client.go` / `http_wire.go` — Streamable HTTP: `initialize` → | |
| 27 | + `notifications/initialized` → `Mcp-Session-Id` on every POST, session | |
| 28 | + lost → re-initialise once; JSON or SSE answers; bearer / API-key | |
| 29 | + auth headers from environment variables (decision recorded | |
| 30 | + 2026-09-23, branch feature/mcp-gateway-session). | |
| 31 | + - `tool_wrapper.go` — each MCP tool becomes a Genkit tool named | |
| 32 | + `<server>_<tool>` (`SanitizeToolName`), collisions with built-in | |
| 33 | + tools refused (`IsBuiltinTool`, `CheckCollisions`), required params | |
| 34 | + checked, output truncated to maxOutput, loop detector fed, the two | |
| 35 | + paths (evented / terminal) like every built-in tool. | |
| 36 | + - Permissions: `mcp.permissions` (default `ask`) and per-server | |
| 37 | + `permissions`; since the yolo work `mustAsk` = `ask` OR `yolo: false` | |
| 38 | + (consent package — ties in with chapter 11). | |
| 39 | + - `main.go` — MCP assembly at start-up, banner warnings, and | |
| 40 | + `MCP_GATEWAY_URL` (the Docker Sandboxes gateway, sbx injects it). | |
| 41 | + - **stdio transport is NOT implemented**: `Connect` returns "stdio | |
| 42 | + transport for MCP is not yet implemented - use HTTP transport", although | |
| 43 | + several comments (config.go `MCP`, tool_wrapper.go) say "stdio or HTTP". | |
| 44 | + The chapter must describe HTTP only (or this ticket waits for stdio); | |
| 45 | + the misleading comments are worth fixing on the way. | |
| 46 | + - The ACP façade receives `mcpServers` in `session/new` and ignores them | |
| 47 | + explicitly (confirmed 2026-10-06: internal/acp/acp.go:247 logs | |
| 48 | + `mcpServers=N (ignored)`): MCP servers come from mm's YAML, never from | |
| 49 | + the editor. | |
| 50 | + - User docs already exist and are the factual base (EN+FR): tutorials/ | |
| 51 | + mcp-getting-started, how-to/mcp-use-{context7,github,wikipedia}, | |
| 52 | + reference/mcp-{configuration,authentication,available-tools}, | |
| 53 | + explanation/mcp-{architecture,decisions}; demo/ has MCP-GITHUB*.md, | |
| 54 | + MCP_SERVERS_GUIDE.md, MCP_SBX_FIX.md. NB: docs/en/reference/ | |
| 55 | + mcp-configuration.md is a stale copy of configuration.md with no MCP | |
| 56 | + section (the FR one is the real MCP reference) — fix or note. | |
| 57 | + | |
| 58 | + ## Proposal | |
| 59 | + | |
| 60 | + New chapter `ebook/chapters/14-mcp.adoc`, after 13 (window mode) and | |
| 61 | + before the conclusion — MCP builds on the tool machinery (ch 04), the | |
| 62 | + event sink (ch 11) and the permission rules (ch 11 `consent`). Same | |
| 63 | + structure as the other chapters: « À quoi on répond », the code layer | |
| 64 | + by layer, « Pourquoi », « Ce qu'on retient ». Suggested beats: | |
| 65 | + 1. Why an agent with five tools would want more without recompiling — | |
| 66 | + and the conclusion's own question: does it need them? | |
| 67 | + 2. The protocol in one exchange (initialize, tools/list, tools/call) and | |
| 68 | + why mm hand-rolls the HTTP client. | |
| 69 | + 3. A session that survives: Mcp-Session-Id, re-initialise on loss (the | |
| 70 | + gateway incident of 2026-09-23 as the story). | |
| 71 | + 4. Wrapping: one MCP tool = one Genkit tool, names, collisions, the | |
| 72 | + contracts it keeps (truncation, loop detector, text failures). | |
| 73 | + 5. Trust: `permissions: ask` by default, how `yolo` combines with it. | |
| 74 | + 6. Docker Sandboxes: MCP_GATEWAY_URL. | |
| 75 | + 7. What is not there (stdio) and why. | |
| 76 | +tasks: | |
| 77 | + - id: 1 | |
| 78 | + title: "Verify the facts against the code at writing time (stdio status, ACP mcpServers, permissions/yolo, gateway)" | |
| 79 | + state: open | |
| 80 | + priority: none | |
| 81 | + author: | |
| 82 | + name: k33g | |
| 83 | + email: ph.charriere@gmail.com | |
| 84 | + createdAt: 2026-10-06T18:00:00.000Z | |
| 85 | + updatedAt: 2026-10-06T18:00:00.000Z | |
| 86 | + - id: 2 | |
| 87 | + title: "Write ebook/chapters/14-mcp.adoc (FR, one physical line per paragraph, Latin-1 in SVG text) and include it in book.adoc" | |
| 88 | + state: open | |
| 89 | + priority: none | |
| 90 | + author: | |
| 91 | + name: k33g | |
| 92 | + email: ph.charriere@gmail.com | |
| 93 | + createdAt: 2026-10-06T18:00:00.000Z | |
| 94 | + updatedAt: 2026-10-06T18:00:00.000Z | |
| 95 | + - id: 3 | |
| 96 | + title: "Diagram (SVG + drawio pair in ebook/images/): mm ↔ MCP server over HTTP, the wrapped tool in the loop" | |
| 97 | + state: open | |
| 98 | + priority: none | |
| 99 | + author: | |
| 100 | + name: k33g | |
| 101 | + email: ph.charriere@gmail.com | |
| 102 | + createdAt: 2026-10-06T18:00:00.000Z | |
| 103 | + updatedAt: 2026-10-06T18:00:00.000Z | |
| 104 | + - id: 4 | |
| 105 | + title: "Update the conclusion (drop 'MCP' from future work) and the introduction's chapter list if it has one" | |
| 106 | + state: open | |
| 107 | + priority: none | |
| 108 | + author: | |
| 109 | + name: k33g | |
| 110 | + email: ph.charriere@gmail.com | |
| 111 | + createdAt: 2026-10-06T18:00:00.000Z | |
| 112 | + updatedAt: 2026-10-06T18:00:00.000Z | |
| 113 | + - id: 5 | |
| 114 | + title: "Fix the misleading 'stdio or HTTP' comments in the code (config.go, tool_wrapper.go) — or implement stdio first" | |
| 115 | + state: open | |
| 116 | + priority: none | |
| 117 | + author: | |
| 118 | + name: k33g | |
| 119 | + email: ph.charriere@gmail.com | |
| 120 | + createdAt: 2026-10-06T18:00:00.000Z | |
| 121 | + updatedAt: 2026-10-06T18:00:00.000Z | |
| 122 | + - id: 6 | |
| 123 | + title: "Rebuild the book (bash .claude/skills/ebook-builder/scripts/build_ebook.sh ebook): 0 warnings" | |
| 124 | + state: open | |
| 125 | + priority: none | |
| 126 | + author: | |
| 127 | + name: k33g | |
| 128 | + email: ph.charriere@gmail.com | |
| 129 | + createdAt: 2026-10-06T18:00:00.000Z | |
| 130 | + updatedAt: 2026-10-06T18:00:00.000Z | |
| 131 | +comments: [] | |
| new file mode 100644 | |||
| @@ -0,0 +1,131 @@ | |||
| 1 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | ||
| 2 | +id: 77 | ||
| 3 | +title: "Ebook: a chapter on MCP" | ||
| 4 | +state: open | ||
| 5 | +author: | ||
| 6 | + name: k33g | ||
| 7 | + email: ph.charriere@gmail.com | ||
| 8 | +createdAt: 2026-10-06T18:00:00.000Z | ||
| 9 | +updatedAt: 2026-10-06T18:00:00.000Z | ||
| 10 | +labels: | ||
| 11 | + - documentation | ||
| 12 | + - priority::low | ||
| 13 | +body: | | ||
| 14 | + The ebook (« Créer un agent de code pas à pas », `ebook/`, French | ||
| 15 | + AsciiDoc, built with the ebook-builder skill) never covers the MCP client, | ||
| 16 | + and its conclusion (ebook/chapters/99-conclusion.adoc, "Ce qu'il faudrait | ||
| 17 | + faire ensuite") still lists MCP as future work — stale since the client | ||
| 18 | + landed. Found while updating the book for yolo + Genkit 1.13 | ||
| 19 | + (2026-10-06). | ||
| 20 | + | ||
| 21 | + ## What exists to describe (verified 2026-10-06) | ||
| 22 | + | ||
| 23 | + - `internal/mcp` (~1,600 lines with internal/tools/mcp.go): | ||
| 24 | + - `client.go` — `Client` interface, `ServerClient`, `Connect`, | ||
| 25 | + `ConnectAll` (discovery, warnings), `CloseAll`, the `Tool` model. | ||
| 26 | + - `http_client.go` / `http_wire.go` — Streamable HTTP: `initialize` → | ||
| 27 | + `notifications/initialized` → `Mcp-Session-Id` on every POST, session | ||
| 28 | + lost → re-initialise once; JSON or SSE answers; bearer / API-key | ||
| 29 | + auth headers from environment variables (decision recorded | ||
| 30 | + 2026-09-23, branch feature/mcp-gateway-session). | ||
| 31 | + - `tool_wrapper.go` — each MCP tool becomes a Genkit tool named | ||
| 32 | + `<server>_<tool>` (`SanitizeToolName`), collisions with built-in | ||
| 33 | + tools refused (`IsBuiltinTool`, `CheckCollisions`), required params | ||
| 34 | + checked, output truncated to maxOutput, loop detector fed, the two | ||
| 35 | + paths (evented / terminal) like every built-in tool. | ||
| 36 | + - Permissions: `mcp.permissions` (default `ask`) and per-server | ||
| 37 | + `permissions`; since the yolo work `mustAsk` = `ask` OR `yolo: false` | ||
| 38 | + (consent package — ties in with chapter 11). | ||
| 39 | + - `main.go` — MCP assembly at start-up, banner warnings, and | ||
| 40 | + `MCP_GATEWAY_URL` (the Docker Sandboxes gateway, sbx injects it). | ||
| 41 | + - **stdio transport is NOT implemented**: `Connect` returns "stdio | ||
| 42 | + transport for MCP is not yet implemented - use HTTP transport", although | ||
| 43 | + several comments (config.go `MCP`, tool_wrapper.go) say "stdio or HTTP". | ||
| 44 | + The chapter must describe HTTP only (or this ticket waits for stdio); | ||
| 45 | + the misleading comments are worth fixing on the way. | ||
| 46 | + - The ACP façade receives `mcpServers` in `session/new` and ignores them | ||
| 47 | + explicitly (confirmed 2026-10-06: internal/acp/acp.go:247 logs | ||
| 48 | + `mcpServers=N (ignored)`): MCP servers come from mm's YAML, never from | ||
| 49 | + the editor. | ||
| 50 | + - User docs already exist and are the factual base (EN+FR): tutorials/ | ||
| 51 | + mcp-getting-started, how-to/mcp-use-{context7,github,wikipedia}, | ||
| 52 | + reference/mcp-{configuration,authentication,available-tools}, | ||
| 53 | + explanation/mcp-{architecture,decisions}; demo/ has MCP-GITHUB*.md, | ||
| 54 | + MCP_SERVERS_GUIDE.md, MCP_SBX_FIX.md. NB: docs/en/reference/ | ||
| 55 | + mcp-configuration.md is a stale copy of configuration.md with no MCP | ||
| 56 | + section (the FR one is the real MCP reference) — fix or note. | ||
| 57 | + | ||
| 58 | + ## Proposal | ||
| 59 | + | ||
| 60 | + New chapter `ebook/chapters/14-mcp.adoc`, after 13 (window mode) and | ||
| 61 | + before the conclusion — MCP builds on the tool machinery (ch 04), the | ||
| 62 | + event sink (ch 11) and the permission rules (ch 11 `consent`). Same | ||
| 63 | + structure as the other chapters: « À quoi on répond », the code layer | ||
| 64 | + by layer, « Pourquoi », « Ce qu'on retient ». Suggested beats: | ||
| 65 | + 1. Why an agent with five tools would want more without recompiling — | ||
| 66 | + and the conclusion's own question: does it need them? | ||
| 67 | + 2. The protocol in one exchange (initialize, tools/list, tools/call) and | ||
| 68 | + why mm hand-rolls the HTTP client. | ||
| 69 | + 3. A session that survives: Mcp-Session-Id, re-initialise on loss (the | ||
| 70 | + gateway incident of 2026-09-23 as the story). | ||
| 71 | + 4. Wrapping: one MCP tool = one Genkit tool, names, collisions, the | ||
| 72 | + contracts it keeps (truncation, loop detector, text failures). | ||
| 73 | + 5. Trust: `permissions: ask` by default, how `yolo` combines with it. | ||
| 74 | + 6. Docker Sandboxes: MCP_GATEWAY_URL. | ||
| 75 | + 7. What is not there (stdio) and why. | ||
| 76 | +tasks: | ||
| 77 | + - id: 1 | ||
| 78 | + title: "Verify the facts against the code at writing time (stdio status, ACP mcpServers, permissions/yolo, gateway)" | ||
| 79 | + state: open | ||
| 80 | + priority: none | ||
| 81 | + author: | ||
| 82 | + name: k33g | ||
| 83 | + email: ph.charriere@gmail.com | ||
| 84 | + createdAt: 2026-10-06T18:00:00.000Z | ||
| 85 | + updatedAt: 2026-10-06T18:00:00.000Z | ||
| 86 | + - id: 2 | ||
| 87 | + title: "Write ebook/chapters/14-mcp.adoc (FR, one physical line per paragraph, Latin-1 in SVG text) and include it in book.adoc" | ||
| 88 | + state: open | ||
| 89 | + priority: none | ||
| 90 | + author: | ||
| 91 | + name: k33g | ||
| 92 | + email: ph.charriere@gmail.com | ||
| 93 | + createdAt: 2026-10-06T18:00:00.000Z | ||
| 94 | + updatedAt: 2026-10-06T18:00:00.000Z | ||
| 95 | + - id: 3 | ||
| 96 | + title: "Diagram (SVG + drawio pair in ebook/images/): mm ↔ MCP server over HTTP, the wrapped tool in the loop" | ||
| 97 | + state: open | ||
| 98 | + priority: none | ||
| 99 | + author: | ||
| 100 | + name: k33g | ||
| 101 | + email: ph.charriere@gmail.com | ||
| 102 | + createdAt: 2026-10-06T18:00:00.000Z | ||
| 103 | + updatedAt: 2026-10-06T18:00:00.000Z | ||
| 104 | + - id: 4 | ||
| 105 | + title: "Update the conclusion (drop 'MCP' from future work) and the introduction's chapter list if it has one" | ||
| 106 | + state: open | ||
| 107 | + priority: none | ||
| 108 | + author: | ||
| 109 | + name: k33g | ||
| 110 | + email: ph.charriere@gmail.com | ||
| 111 | + createdAt: 2026-10-06T18:00:00.000Z | ||
| 112 | + updatedAt: 2026-10-06T18:00:00.000Z | ||
| 113 | + - id: 5 | ||
| 114 | + title: "Fix the misleading 'stdio or HTTP' comments in the code (config.go, tool_wrapper.go) — or implement stdio first" | ||
| 115 | + state: open | ||
| 116 | + priority: none | ||
| 117 | + author: | ||
| 118 | + name: k33g | ||
| 119 | + email: ph.charriere@gmail.com | ||
| 120 | + createdAt: 2026-10-06T18:00:00.000Z | ||
| 121 | + updatedAt: 2026-10-06T18:00:00.000Z | ||
| 122 | + - id: 6 | ||
| 123 | + title: "Rebuild the book (bash .claude/skills/ebook-builder/scripts/build_ebook.sh ebook): 0 warnings" | ||
| 124 | + state: open | ||
| 125 | + priority: none | ||
| 126 | + author: | ||
| 127 | + name: k33g | ||
| 128 | + email: ph.charriere@gmail.com | ||
| 129 | + createdAt: 2026-10-06T18:00:00.000Z | ||
| 130 | + updatedAt: 2026-10-06T18:00:00.000Z | ||
| 131 | +comments: [] | ||
modified
demo-llmman/agent.llmman.yaml +4 -0 | @@ -42,6 +42,8 @@ mmConfigs: | ||
| 42 | 42 | # an agent follows its asker's yolo, not this one. Override for one run: AGENT_YOLO=false. |
| 43 | 43 | yolo: false |
| 44 | 44 | |
| 45 | + | |
| 46 | + | |
| 45 | 47 | # The `bash` tool. |
| 46 | 48 | # `false` removes it: the model can still read and edit files (editTools) and load skills, |
| 47 | 49 | # but never runs a command. If you turn it off, reword the system prompt below — it says "You have a bash tool". |
| @@ -189,6 +191,8 @@ sampling: | ||
| 189 | 191 | parallel_tool_calls: false |
| 190 | 192 | top_p: 0.9 |
| 191 | 193 | max_tokens: 4096 |
| 194 | + chat_template_kwargs: | |
| 195 | + enable_thinking: true | |
| 192 | 196 | |
| 193 | 197 | # Silence allowed between two tokens once the answer has started. |
| 194 | 198 | watchdogTimeout: 3m |
| @@ -42,6 +42,8 @@ mmConfigs: | |||
| 42 | # an agent follows its asker's yolo, not this one. Override for one run: AGENT_YOLO=false. | 42 | # an agent follows its asker's yolo, not this one. Override for one run: AGENT_YOLO=false. |
| 43 | yolo: false | 43 | yolo: false |
| 44 | 44 | ||
| 45 | + | ||
| 46 | + | ||
| 45 | # The `bash` tool. | 47 | # The `bash` tool. |
| 46 | # `false` removes it: the model can still read and edit files (editTools) and load skills, | 48 | # `false` removes it: the model can still read and edit files (editTools) and load skills, |
| 47 | # but never runs a command. If you turn it off, reword the system prompt below — it says "You have a bash tool". | 49 | # but never runs a command. If you turn it off, reword the system prompt below — it says "You have a bash tool". |
| @@ -189,6 +191,8 @@ sampling: | |||
| 189 | parallel_tool_calls: false | 191 | parallel_tool_calls: false |
| 190 | top_p: 0.9 | 192 | top_p: 0.9 |
| 191 | max_tokens: 4096 | 193 | max_tokens: 4096 |
| 194 | + chat_template_kwargs: | ||
| 195 | + enable_thinking: true | ||
| 192 | 196 | ||
| 193 | # Silence allowed between two tokens once the answer has started. | 197 | # Silence allowed between two tokens once the answer has started. |
| 194 | watchdogTimeout: 3m | 198 | watchdogTimeout: 3m |
modified
docs/en/README.md +1 -0 | @@ -16,6 +16,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr) | ||
| 16 | 16 | - [Ask another mm agent from your session](how-to/ask-another-agent.md) — `mmConfigs`, `/ask-<name>`, `/ask-with-context-<name>` |
| 17 | 17 | - [Ask another agent from a skill or a script](how-to/ask-another-agent-from-a-skill.md) — `"$MM_BIN" --ask <agent>`, `--ask-with-context`, the demo skills |
| 18 | 18 | - [Use mini-me from an editor (ACP)](how-to/use-from-an-editor.md) |
| 19 | +- [Turn a model's reasoning off](how-to/turn-reasoning-off.md) — `chat_template_kwargs` or `reasoning_effort` in `sampling`, per server | |
| 19 | 20 | - [Confirm tool calls before they run](how-to/confirm-tool-calls.md) — `yolo: false`, the four answers, `AGENT_YOLO`, who decides with `mmConfigs` |
| 20 | 21 | - [Run mini-me in a Docker Sandbox](how-to/run-in-a-docker-sandbox.md) — the template and the two kits under `docker-sbx/`, window mode and editor |
| 21 | 22 | - [Run mini-me with the v3 sandbox kits](how-to/run-with-sandbox-kits-v3.md) — the same two kits for the kit specification v3, under `docker-sbx-v3/` |
| @@ -16,6 +16,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr) | |||
| 16 | - [Ask another mm agent from your session](how-to/ask-another-agent.md) — `mmConfigs`, `/ask-<name>`, `/ask-with-context-<name>` | 16 | - [Ask another mm agent from your session](how-to/ask-another-agent.md) — `mmConfigs`, `/ask-<name>`, `/ask-with-context-<name>` |
| 17 | - [Ask another agent from a skill or a script](how-to/ask-another-agent-from-a-skill.md) — `"$MM_BIN" --ask <agent>`, `--ask-with-context`, the demo skills | 17 | - [Ask another agent from a skill or a script](how-to/ask-another-agent-from-a-skill.md) — `"$MM_BIN" --ask <agent>`, `--ask-with-context`, the demo skills |
| 18 | - [Use mini-me from an editor (ACP)](how-to/use-from-an-editor.md) | 18 | - [Use mini-me from an editor (ACP)](how-to/use-from-an-editor.md) |
| 19 | +- [Turn a model's reasoning off](how-to/turn-reasoning-off.md) — `chat_template_kwargs` or `reasoning_effort` in `sampling`, per server | ||
| 19 | - [Confirm tool calls before they run](how-to/confirm-tool-calls.md) — `yolo: false`, the four answers, `AGENT_YOLO`, who decides with `mmConfigs` | 20 | - [Confirm tool calls before they run](how-to/confirm-tool-calls.md) — `yolo: false`, the four answers, `AGENT_YOLO`, who decides with `mmConfigs` |
| 20 | - [Run mini-me in a Docker Sandbox](how-to/run-in-a-docker-sandbox.md) — the template and the two kits under `docker-sbx/`, window mode and editor | 21 | - [Run mini-me in a Docker Sandbox](how-to/run-in-a-docker-sandbox.md) — the template and the two kits under `docker-sbx/`, window mode and editor |
| 21 | - [Run mini-me with the v3 sandbox kits](how-to/run-with-sandbox-kits-v3.md) — the same two kits for the kit specification v3, under `docker-sbx-v3/` | 22 | - [Run mini-me with the v3 sandbox kits](how-to/run-with-sandbox-kits-v3.md) — the same two kits for the kit specification v3, under `docker-sbx-v3/` |
modified
docs/en/explanation/providers.md +1 -1 | @@ -18,7 +18,7 @@ | ||
| 18 | 18 | |
| 19 | 19 | **llmman is a third registry entry, with its own probe.** [llmman](https://github.com/llmmanorg/llmman) (`llmman serve`, port 17434) is a daemon in front of one `llama-server` per loaded model, and speaks several APIs; `mm` uses only its OpenAI one. Everything llama.cpp-specific passes through it — prompt progress, reasoning, `enable_thinking: false` for summaries — so it gets the same treatment as `llamacpp`. Its `/props`, though, is a stub (`n_ctx` 4096, no chat template, measured on llmman 0.1.498), so the probe reads the served window from `/api/ps` instead, for the configured model, once it is loaded. The trained context from `/api/show` is not used: `LLMMAN_CONTEXT_LENGTH` can serve less, and a window too large means compression that never triggers before the server refuses the prompt. A model missing from `/v1/models` is only a warning: llmman pulls it on the first request. |
| 20 | 20 | |
| 21 | -**Ollama is a fourth entry, and shows why each server keeps its own.** Ollama's `/v1` looks like the others, but measured on Ollama 0.35.0 it differs in three ways. Reasoning streams as `delta.reasoning`, which the tap already reads. No prompt progress comes back, so `return_progress` is not sent. And `chat_template_kwargs` is ignored: the summary of a compaction reasoned until its token limit and came back empty; `reasoning_effort: "none"` is what Ollama obeys, and with it the same compaction succeeded. That is why how to turn reasoning off is a per-provider field, not something implied by "is llama.cpp". Like llmman, Ollama reports the served window on `/api/ps` once the model is loaded (the two share that reading); unlike llmman, it does not pull a missing model, so the probe's warning says `ollama pull`. | |
| 21 | +**Ollama is a fourth entry, and shows why each server keeps its own.** Ollama's `/v1` looks like the others, but measured on Ollama 0.35.0 it differs in three ways. Reasoning streams as `delta.reasoning`, which the tap already reads. No prompt progress comes back, so `return_progress` is not sent. And `chat_template_kwargs` is ignored: the summary of a compaction reasoned until its token limit and came back empty; `reasoning_effort: "none"` is what Ollama obeys, and with it the same compaction succeeded. That is why how to turn reasoning off is a per-provider field, not something implied by "is llama.cpp". `mm` uses it for summaries only; for every turn, the user writes the same server-specific key in `sampling`, which forwards keys the OpenAI API does not know since Genkit 1.12 (see [How to turn a model's reasoning off](../how-to/turn-reasoning-off.md)). Like llmman, Ollama reports the served window on `/api/ps` once the model is loaded (the two share that reading); unlike llmman, it does not pull a missing model, so the probe's warning says `ollama pull`. | |
| 22 | 22 | |
| 23 | 23 | **Errors are rewritten in the server's own words.** A stack of wrapped messages is noise; "tool calls need llama-server started with --jinja" is the whole diagnosis. The provider recognises connection refusals, the missing `--jinja` flag, 401/403, 404 with a pull or alias hint, 429 and 5xx. Unknown errors come back unchanged: a raw message beats a wrong hint. |
| 24 | 24 | |
| @@ -18,7 +18,7 @@ | |||
| 18 | 18 | ||
| 19 | **llmman is a third registry entry, with its own probe.** [llmman](https://github.com/llmmanorg/llmman) (`llmman serve`, port 17434) is a daemon in front of one `llama-server` per loaded model, and speaks several APIs; `mm` uses only its OpenAI one. Everything llama.cpp-specific passes through it — prompt progress, reasoning, `enable_thinking: false` for summaries — so it gets the same treatment as `llamacpp`. Its `/props`, though, is a stub (`n_ctx` 4096, no chat template, measured on llmman 0.1.498), so the probe reads the served window from `/api/ps` instead, for the configured model, once it is loaded. The trained context from `/api/show` is not used: `LLMMAN_CONTEXT_LENGTH` can serve less, and a window too large means compression that never triggers before the server refuses the prompt. A model missing from `/v1/models` is only a warning: llmman pulls it on the first request. | 19 | **llmman is a third registry entry, with its own probe.** [llmman](https://github.com/llmmanorg/llmman) (`llmman serve`, port 17434) is a daemon in front of one `llama-server` per loaded model, and speaks several APIs; `mm` uses only its OpenAI one. Everything llama.cpp-specific passes through it — prompt progress, reasoning, `enable_thinking: false` for summaries — so it gets the same treatment as `llamacpp`. Its `/props`, though, is a stub (`n_ctx` 4096, no chat template, measured on llmman 0.1.498), so the probe reads the served window from `/api/ps` instead, for the configured model, once it is loaded. The trained context from `/api/show` is not used: `LLMMAN_CONTEXT_LENGTH` can serve less, and a window too large means compression that never triggers before the server refuses the prompt. A model missing from `/v1/models` is only a warning: llmman pulls it on the first request. |
| 20 | 20 | ||
| 21 | -**Ollama is a fourth entry, and shows why each server keeps its own.** Ollama's `/v1` looks like the others, but measured on Ollama 0.35.0 it differs in three ways. Reasoning streams as `delta.reasoning`, which the tap already reads. No prompt progress comes back, so `return_progress` is not sent. And `chat_template_kwargs` is ignored: the summary of a compaction reasoned until its token limit and came back empty; `reasoning_effort: "none"` is what Ollama obeys, and with it the same compaction succeeded. That is why how to turn reasoning off is a per-provider field, not something implied by "is llama.cpp". Like llmman, Ollama reports the served window on `/api/ps` once the model is loaded (the two share that reading); unlike llmman, it does not pull a missing model, so the probe's warning says `ollama pull`. | 21 | +**Ollama is a fourth entry, and shows why each server keeps its own.** Ollama's `/v1` looks like the others, but measured on Ollama 0.35.0 it differs in three ways. Reasoning streams as `delta.reasoning`, which the tap already reads. No prompt progress comes back, so `return_progress` is not sent. And `chat_template_kwargs` is ignored: the summary of a compaction reasoned until its token limit and came back empty; `reasoning_effort: "none"` is what Ollama obeys, and with it the same compaction succeeded. That is why how to turn reasoning off is a per-provider field, not something implied by "is llama.cpp". `mm` uses it for summaries only; for every turn, the user writes the same server-specific key in `sampling`, which forwards keys the OpenAI API does not know since Genkit 1.12 (see [How to turn a model's reasoning off](../how-to/turn-reasoning-off.md)). Like llmman, Ollama reports the served window on `/api/ps` once the model is loaded (the two share that reading); unlike llmman, it does not pull a missing model, so the probe's warning says `ollama pull`. |
| 22 | 22 | ||
| 23 | **Errors are rewritten in the server's own words.** A stack of wrapped messages is noise; "tool calls need llama-server started with --jinja" is the whole diagnosis. The provider recognises connection refusals, the missing `--jinja` flag, 401/403, 404 with a pull or alias hint, 429 and 5xx. Unknown errors come back unchanged: a raw message beats a wrong hint. | 23 | **Errors are rewritten in the server's own words.** A stack of wrapped messages is noise; "tool calls need llama-server started with --jinja" is the whole diagnosis. The provider recognises connection refusals, the missing `--jinja` flag, 401/403, 404 with a pull or alias hint, 429 and 5xx. Unknown errors come back unchanged: a raw message beats a wrong hint. |
| 24 | 24 | ||
modified
docs/en/explanation/thinking-models.md +3 -1 | @@ -30,6 +30,8 @@ So `mm` reads the reasoning one layer below Genkit: a middleware on the HTTP cli | ||
| 30 | 30 | |
| 31 | 31 | This is why `showThinking: false` turns off the *display* and nothing else. The reasoning is always read, because reading it is what keeps the turn alive. |
| 32 | 32 | |
| 33 | +Since Genkit 1.13 the plugin does forward reasoning: as reasoning-only chunks to the streaming handler, and as a reasoning part of the final message. `mm` keeps its own reading anyway — one road for the display and the heartbeat whatever Genkit version or server — and neutralises the two new deliveries. A chunk that carries nothing but reasoning is skipped by `mm`'s handler: handled as an empty answer chunk, each one closed the thought block, and the line mode printed one `💭` block per token. The reasoning part of the final message is removed before Genkit or `mm` keeps the message: left in, it would go back to the server as the assistant message's `reasoning_content` on the next model call. | |
| 34 | + | |
| 33 | 35 | ## Where it goes |
| 34 | 36 | |
| 35 | 37 | | Front end | What you see | |
| @@ -46,6 +48,6 @@ Three properties hold everywhere: | ||
| 46 | 48 | |
| 47 | 49 | ## What this is not |
| 48 | 50 | |
| 49 | -It is not a "reasoning effort" setting: `mm` asks for nothing and configures nothing on the model side. A model thinks or it does not, and `mm` shows what arrives. | |
| 51 | +It is not a "reasoning effort" setting: `mm` has no key of its own for it, and shows what arrives. The server can still be asked not to reason, through `sampling`, whose keys unknown to the OpenAI API are sent as they are (since Genkit 1.12): `chat_template_kwargs: {enable_thinking: false}` for a llama.cpp server (`dmr`, `llamacpp`, `llmman`), `reasoning_effort: none` for Ollama. Measured with Qwen3.5 4B on Docker Model Runner: the same answer in under a second, without the four seconds of reasoning. `mm` does this by itself, per server, for the summary of a compaction only. See [How to turn a model's reasoning off](../how-to/turn-reasoning-off.md). | |
| 50 | 52 | |
| 51 | 53 | It is not a parser. Some models instead wrap their reasoning in `<think>` tags inside `content`. Those arrive as ordinary answer text and `mm` shows them as such — the two shapes would need different treatment, and only the field is handled today. |
| @@ -30,6 +30,8 @@ So `mm` reads the reasoning one layer below Genkit: a middleware on the HTTP cli | |||
| 30 | 30 | ||
| 31 | This is why `showThinking: false` turns off the *display* and nothing else. The reasoning is always read, because reading it is what keeps the turn alive. | 31 | This is why `showThinking: false` turns off the *display* and nothing else. The reasoning is always read, because reading it is what keeps the turn alive. |
| 32 | 32 | ||
| 33 | +Since Genkit 1.13 the plugin does forward reasoning: as reasoning-only chunks to the streaming handler, and as a reasoning part of the final message. `mm` keeps its own reading anyway — one road for the display and the heartbeat whatever Genkit version or server — and neutralises the two new deliveries. A chunk that carries nothing but reasoning is skipped by `mm`'s handler: handled as an empty answer chunk, each one closed the thought block, and the line mode printed one `💭` block per token. The reasoning part of the final message is removed before Genkit or `mm` keeps the message: left in, it would go back to the server as the assistant message's `reasoning_content` on the next model call. | ||
| 34 | + | ||
| 33 | ## Where it goes | 35 | ## Where it goes |
| 34 | 36 | ||
| 35 | | Front end | What you see | | 37 | | Front end | What you see | |
| @@ -46,6 +48,6 @@ Three properties hold everywhere: | |||
| 46 | 48 | ||
| 47 | ## What this is not | 49 | ## What this is not |
| 48 | 50 | ||
| 49 | -It is not a "reasoning effort" setting: `mm` asks for nothing and configures nothing on the model side. A model thinks or it does not, and `mm` shows what arrives. | 51 | +It is not a "reasoning effort" setting: `mm` has no key of its own for it, and shows what arrives. The server can still be asked not to reason, through `sampling`, whose keys unknown to the OpenAI API are sent as they are (since Genkit 1.12): `chat_template_kwargs: {enable_thinking: false}` for a llama.cpp server (`dmr`, `llamacpp`, `llmman`), `reasoning_effort: none` for Ollama. Measured with Qwen3.5 4B on Docker Model Runner: the same answer in under a second, without the four seconds of reasoning. `mm` does this by itself, per server, for the summary of a compaction only. See [How to turn a model's reasoning off](../how-to/turn-reasoning-off.md). |
| 50 | 52 | ||
| 51 | It is not a parser. Some models instead wrap their reasoning in `<think>` tags inside `content`. Those arrive as ordinary answer text and `mm` shows them as such — the two shapes would need different treatment, and only the field is handled today. | 53 | It is not a parser. Some models instead wrap their reasoning in `<think>` tags inside `content`. Those arrive as ordinary answer text and `mm` shows them as such — the two shapes would need different treatment, and only the field is handled today. |
added
docs/en/how-to/turn-reasoning-off.md +54 -0 | new file mode 100644 | ||
| @@ -0,0 +1,54 @@ | ||
| 1 | +# How to turn a model's reasoning off | |
| 2 | + | |
| 3 | +A thinking model (Qwen3.5, Qwen3.x, gpt-oss…) reasons before it answers. On a simple question that costs seconds and tokens for nothing. This guide shows how to ask the server not to reason, for every turn. It assumes you already run `mm` with a YAML configuration file and a thinking model. | |
| 4 | + | |
| 5 | +## Steps | |
| 6 | + | |
| 7 | +1. Find your provider in the configuration file (`provider:`). | |
| 8 | + | |
| 9 | +2. Add the server's own switch under `sampling`. | |
| 10 | + | |
| 11 | + For `dmr`, `llamacpp` or `llmman` — all llama.cpp underneath: | |
| 12 | + | |
| 13 | + ```yaml | |
| 14 | + sampling: | |
| 15 | + temperature: 0.0 | |
| 16 | + max_tokens: 4096 | |
| 17 | + chat_template_kwargs: | |
| 18 | + enable_thinking: false | |
| 19 | + ``` | |
| 20 | + | |
| 21 | + For `ollama`: | |
| 22 | + | |
| 23 | + ```yaml | |
| 24 | + sampling: | |
| 25 | + temperature: 0.0 | |
| 26 | + max_tokens: 4096 | |
| 27 | + reasoning_effort: none | |
| 28 | + ``` | |
| 29 | + | |
| 30 | + The keys you write are merged with the built-in ones (`temperature`, `top_p`, `max_tokens`): only the switch needs adding. | |
| 31 | + | |
| 32 | +3. Ask a question and compare. With reasoning on, the line mode prints a dim `💭` block before the `🤖` answer; with it off, the answer comes straight away: | |
| 33 | + | |
| 34 | + ``` | |
| 35 | + $ mm -p "What is 17*23? Answer with the number only." agent.yaml | |
| 36 | + | |
| 37 | + 🤖 391⚙ 0 command(s) | |
| 38 | + 391 | |
| 39 | + ``` | |
| 40 | + | |
| 41 | + Measured with Qwen3.5 4B on Docker Model Runner: under a second, where the same question took four seconds of reasoning. | |
| 42 | + | |
| 43 | +## Variants | |
| 44 | + | |
| 45 | +- **Hide the reasoning but let the model think.** `showThinking: false` removes the `💭` block, the window's `‣ thinking` entry and the editor's thought chunks. The model still reasons — same time, same tokens. | |
| 46 | +- **The model ignores the switch.** `enable_thinking` is a variable of the model's chat template: a template that does not read it (a model without a thinking mode, or with another variable name) is not affected. Check the model card for the variable its template reads. | |
| 47 | +- **Turn it off for one run.** Keep two configuration files, or start the server itself without reasoning (for llama-server, see its `--reasoning-budget` option). | |
| 48 | +- **Compaction summaries.** Nothing to do: `mm` already asks the server not to reason for the summary of `/compact` and of the automatic compression, whatever `sampling` says. | |
| 49 | + | |
| 50 | +## See also | |
| 51 | + | |
| 52 | +- The `sampling` key and what it forwards: [configuration reference](../reference/configuration.md). | |
| 53 | +- Where reasoning comes from and why `mm` reads it itself: [Thinking models](../explanation/thinking-models.md). | |
| 54 | +- Why each server is told in its own words: [Providers](../explanation/providers.md). | |
| new file mode 100644 | |||
| @@ -0,0 +1,54 @@ | |||
| 1 | +# How to turn a model's reasoning off | ||
| 2 | + | ||
| 3 | +A thinking model (Qwen3.5, Qwen3.x, gpt-oss…) reasons before it answers. On a simple question that costs seconds and tokens for nothing. This guide shows how to ask the server not to reason, for every turn. It assumes you already run `mm` with a YAML configuration file and a thinking model. | ||
| 4 | + | ||
| 5 | +## Steps | ||
| 6 | + | ||
| 7 | +1. Find your provider in the configuration file (`provider:`). | ||
| 8 | + | ||
| 9 | +2. Add the server's own switch under `sampling`. | ||
| 10 | + | ||
| 11 | + For `dmr`, `llamacpp` or `llmman` — all llama.cpp underneath: | ||
| 12 | + | ||
| 13 | + ```yaml | ||
| 14 | + sampling: | ||
| 15 | + temperature: 0.0 | ||
| 16 | + max_tokens: 4096 | ||
| 17 | + chat_template_kwargs: | ||
| 18 | + enable_thinking: false | ||
| 19 | + ``` | ||
| 20 | + | ||
| 21 | + For `ollama`: | ||
| 22 | + | ||
| 23 | + ```yaml | ||
| 24 | + sampling: | ||
| 25 | + temperature: 0.0 | ||
| 26 | + max_tokens: 4096 | ||
| 27 | + reasoning_effort: none | ||
| 28 | + ``` | ||
| 29 | + | ||
| 30 | + The keys you write are merged with the built-in ones (`temperature`, `top_p`, `max_tokens`): only the switch needs adding. | ||
| 31 | + | ||
| 32 | +3. Ask a question and compare. With reasoning on, the line mode prints a dim `💭` block before the `🤖` answer; with it off, the answer comes straight away: | ||
| 33 | + | ||
| 34 | + ``` | ||
| 35 | + $ mm -p "What is 17*23? Answer with the number only." agent.yaml | ||
| 36 | + | ||
| 37 | + 🤖 391⚙ 0 command(s) | ||
| 38 | + 391 | ||
| 39 | + ``` | ||
| 40 | + | ||
| 41 | + Measured with Qwen3.5 4B on Docker Model Runner: under a second, where the same question took four seconds of reasoning. | ||
| 42 | + | ||
| 43 | +## Variants | ||
| 44 | + | ||
| 45 | +- **Hide the reasoning but let the model think.** `showThinking: false` removes the `💭` block, the window's `‣ thinking` entry and the editor's thought chunks. The model still reasons — same time, same tokens. | ||
| 46 | +- **The model ignores the switch.** `enable_thinking` is a variable of the model's chat template: a template that does not read it (a model without a thinking mode, or with another variable name) is not affected. Check the model card for the variable its template reads. | ||
| 47 | +- **Turn it off for one run.** Keep two configuration files, or start the server itself without reasoning (for llama-server, see its `--reasoning-budget` option). | ||
| 48 | +- **Compaction summaries.** Nothing to do: `mm` already asks the server not to reason for the summary of `/compact` and of the automatic compression, whatever `sampling` says. | ||
| 49 | + | ||
| 50 | +## See also | ||
| 51 | + | ||
| 52 | +- The `sampling` key and what it forwards: [configuration reference](../reference/configuration.md). | ||
| 53 | +- Where reasoning comes from and why `mm` reads it itself: [Thinking models](../explanation/thinking-models.md). | ||
| 54 | +- Why each server is told in its own words: [Providers](../explanation/providers.md). | ||
modified
docs/en/reference/configuration.md +1 -1 | @@ -22,7 +22,7 @@ | ||
| 22 | 22 | | `bashTool` | bool | `true` | Declare `bash`. `false` removes it: the model can still read and edit files (`editTools`) and load skills, but never runs a command. Reword a system prompt that still announces a bash tool. | |
| 23 | 23 | | `editTools` | bool | `true` | Declare `read_file`, `write_file`, `edit_file`. `false` leaves `bash` and `read_skill` only. | |
| 24 | 24 | | `fetchContentTool` | bool | `false` | Declare `fetch_content`, which fetches web pages as Markdown. Off by default: it is the only built-in tool that reaches the network. | |
| 25 | -| `sampling` | map | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Generation settings, OpenAI API key names, passed to the server. The shipped `agent.yaml` adds `parallel_tool_calls: false`. `max_tokens` also bounds one tool call: a `write_file` of a long document (or a thinking model's reasoning plus the call) can hit it, and the call is then cut off. mm hands it back to the model twice, asking for smaller pieces, then stops with `the model hit max_tokens (N) while writing a tool call`; raise the value if that happens. With streaming, mm also asks the server for token counts (`stream_options.include_usage`). | | |
| 25 | +| `sampling` | map | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Generation settings, OpenAI API key names, passed to the server. A key the OpenAI API does not declare is sent as it is, at the top level of the request — for example `chat_template_kwargs: {enable_thinking: false}`, which tells a llama.cpp server (`dmr`, `llamacpp`, `llmman`) not to reason; `reasoning_effort: none` does the same on Ollama. `messages`, `tools`, `tool_choice` (and the legacy `functions`, `function_call`) are ignored: Genkit builds them from the conversation. The shipped `agent.yaml` adds `parallel_tool_calls: false`. `max_tokens` also bounds one tool call: a `write_file` of a long document (or a thinking model's reasoning plus the call) can hit it, and the call is then cut off. mm hands it back to the model twice, asking for smaller pieces, then stops with `the model hit max_tokens (N) while writing a tool call`; raise the value if that happens. With streaming, mm also asks the server for token counts (`stream_options.include_usage`). | | |
| 26 | 26 | | `watchdogTimeout` | duration | `20s` | Silence tolerated between two streamed tokens before the connection is declared hung. Doubled for summary requests. | |
| 27 | 27 | | `firstTokenTimeout` | duration | `3m` | Silence tolerated before a model call's first token, while the server reads the prompt (every round trip of a turn has one). Never shorter than `watchdogTimeout`; must be >= 0. With a llama.cpp server (`dmr`, `llamacpp`, `llmman` — whose local GGUF models run in llama-server) mm asks for `return_progress`: each progress report restarts this clock, and the spinner shows `Reading the prompt 12.0k/25.3k tokens`. Ollama sends no progress: this one wait covers the whole prompt (and the model's loading). On a timeout the watchdog says `no first token from the server after …` (before the first token) or `connection hang detected after …` (after). | |
| 28 | 28 | | `bashTimeout` | duration | `30s` | How long one `bash` command may run before it is killed; the tool then answers `[error: <bashTimeout> timeout exceeded]`. Must be > 0. The watchdog keeps counting while a command runs (ticket 57): a command longer than `watchdogTimeout` ends the turn, so raise both together. | |
| @@ -22,7 +22,7 @@ | |||
| 22 | | `bashTool` | bool | `true` | Declare `bash`. `false` removes it: the model can still read and edit files (`editTools`) and load skills, but never runs a command. Reword a system prompt that still announces a bash tool. | | 22 | | `bashTool` | bool | `true` | Declare `bash`. `false` removes it: the model can still read and edit files (`editTools`) and load skills, but never runs a command. Reword a system prompt that still announces a bash tool. | |
| 23 | | `editTools` | bool | `true` | Declare `read_file`, `write_file`, `edit_file`. `false` leaves `bash` and `read_skill` only. | | 23 | | `editTools` | bool | `true` | Declare `read_file`, `write_file`, `edit_file`. `false` leaves `bash` and `read_skill` only. | |
| 24 | | `fetchContentTool` | bool | `false` | Declare `fetch_content`, which fetches web pages as Markdown. Off by default: it is the only built-in tool that reaches the network. | | 24 | | `fetchContentTool` | bool | `false` | Declare `fetch_content`, which fetches web pages as Markdown. Off by default: it is the only built-in tool that reaches the network. | |
| 25 | -| `sampling` | map | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Generation settings, OpenAI API key names, passed to the server. The shipped `agent.yaml` adds `parallel_tool_calls: false`. `max_tokens` also bounds one tool call: a `write_file` of a long document (or a thinking model's reasoning plus the call) can hit it, and the call is then cut off. mm hands it back to the model twice, asking for smaller pieces, then stops with `the model hit max_tokens (N) while writing a tool call`; raise the value if that happens. With streaming, mm also asks the server for token counts (`stream_options.include_usage`). | | 25 | +| `sampling` | map | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Generation settings, OpenAI API key names, passed to the server. A key the OpenAI API does not declare is sent as it is, at the top level of the request — for example `chat_template_kwargs: {enable_thinking: false}`, which tells a llama.cpp server (`dmr`, `llamacpp`, `llmman`) not to reason; `reasoning_effort: none` does the same on Ollama. `messages`, `tools`, `tool_choice` (and the legacy `functions`, `function_call`) are ignored: Genkit builds them from the conversation. The shipped `agent.yaml` adds `parallel_tool_calls: false`. `max_tokens` also bounds one tool call: a `write_file` of a long document (or a thinking model's reasoning plus the call) can hit it, and the call is then cut off. mm hands it back to the model twice, asking for smaller pieces, then stops with `the model hit max_tokens (N) while writing a tool call`; raise the value if that happens. With streaming, mm also asks the server for token counts (`stream_options.include_usage`). | |
| 26 | | `watchdogTimeout` | duration | `20s` | Silence tolerated between two streamed tokens before the connection is declared hung. Doubled for summary requests. | | 26 | | `watchdogTimeout` | duration | `20s` | Silence tolerated between two streamed tokens before the connection is declared hung. Doubled for summary requests. | |
| 27 | | `firstTokenTimeout` | duration | `3m` | Silence tolerated before a model call's first token, while the server reads the prompt (every round trip of a turn has one). Never shorter than `watchdogTimeout`; must be >= 0. With a llama.cpp server (`dmr`, `llamacpp`, `llmman` — whose local GGUF models run in llama-server) mm asks for `return_progress`: each progress report restarts this clock, and the spinner shows `Reading the prompt 12.0k/25.3k tokens`. Ollama sends no progress: this one wait covers the whole prompt (and the model's loading). On a timeout the watchdog says `no first token from the server after …` (before the first token) or `connection hang detected after …` (after). | | 27 | | `firstTokenTimeout` | duration | `3m` | Silence tolerated before a model call's first token, while the server reads the prompt (every round trip of a turn has one). Never shorter than `watchdogTimeout`; must be >= 0. With a llama.cpp server (`dmr`, `llamacpp`, `llmman` — whose local GGUF models run in llama-server) mm asks for `return_progress`: each progress report restarts this clock, and the spinner shows `Reading the prompt 12.0k/25.3k tokens`. Ollama sends no progress: this one wait covers the whole prompt (and the model's loading). On a timeout the watchdog says `no first token from the server after …` (before the first token) or `connection hang detected after …` (after). | |
| 28 | | `bashTimeout` | duration | `30s` | How long one `bash` command may run before it is killed; the tool then answers `[error: <bashTimeout> timeout exceeded]`. Must be > 0. The watchdog keeps counting while a command runs (ticket 57): a command longer than `watchdogTimeout` ends the turn, so raise both together. | | 28 | | `bashTimeout` | duration | `30s` | How long one `bash` command may run before it is killed; the tool then answers `[error: <bashTimeout> timeout exceeded]`. Must be > 0. The watchdog keeps counting while a command runs (ticket 57): a command longer than `watchdogTimeout` ends the turn, so raise both together. | |
modified
docs/en/reference/mcp-configuration.md +1 -1 | @@ -21,7 +21,7 @@ | ||
| 21 | 21 | | `skillsDir` | string | `skills` | Directory holding the skills, as `<name>.md` files or `<name>/SKILL.md` directories. A relative path is resolved from the directory `mm` is started in (terminal mode, or ACP mode without a config file), or next to the config file (ACP mode with one); the banner warns with the absolute path when nothing is found there. | |
| 22 | 22 | | `bashTool` | bool | `true` | Declare `bash`. `false` removes it: the model can still read and edit files (`editTools`) and load skills, but never runs a command. Reword a system prompt that still announces a bash tool. | |
| 23 | 23 | | `editTools` | bool | `true` | Declare `read_file`, `write_file`, `edit_file`. `false` leaves `bash` and `read_skill` only. | |
| 24 | -| `sampling` | map | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Generation settings, OpenAI API key names, passed to the server. The shipped `agent.yaml` adds `parallel_tool_calls: false`. | | |
| 24 | +| `sampling` | map | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Generation settings, OpenAI API key names, passed to the server. A key the OpenAI API does not declare is sent as it is (for example `chat_template_kwargs`). The shipped `agent.yaml` adds `parallel_tool_calls: false`. | | |
| 25 | 25 | | `watchdogTimeout` | duration | `20s` | Silence tolerated between two streamed tokens before the connection is declared hung. Doubled for summary requests. | |
| 26 | 26 | | `context` | map | see below | Automatic history compression. | |
| 27 | 27 | | `sessions` | map | see below | Persistent, per-project sessions. | |
| @@ -21,7 +21,7 @@ | |||
| 21 | | `skillsDir` | string | `skills` | Directory holding the skills, as `<name>.md` files or `<name>/SKILL.md` directories. A relative path is resolved from the directory `mm` is started in (terminal mode, or ACP mode without a config file), or next to the config file (ACP mode with one); the banner warns with the absolute path when nothing is found there. | | 21 | | `skillsDir` | string | `skills` | Directory holding the skills, as `<name>.md` files or `<name>/SKILL.md` directories. A relative path is resolved from the directory `mm` is started in (terminal mode, or ACP mode without a config file), or next to the config file (ACP mode with one); the banner warns with the absolute path when nothing is found there. | |
| 22 | | `bashTool` | bool | `true` | Declare `bash`. `false` removes it: the model can still read and edit files (`editTools`) and load skills, but never runs a command. Reword a system prompt that still announces a bash tool. | | 22 | | `bashTool` | bool | `true` | Declare `bash`. `false` removes it: the model can still read and edit files (`editTools`) and load skills, but never runs a command. Reword a system prompt that still announces a bash tool. | |
| 23 | | `editTools` | bool | `true` | Declare `read_file`, `write_file`, `edit_file`. `false` leaves `bash` and `read_skill` only. | | 23 | | `editTools` | bool | `true` | Declare `read_file`, `write_file`, `edit_file`. `false` leaves `bash` and `read_skill` only. | |
| 24 | -| `sampling` | map | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Generation settings, OpenAI API key names, passed to the server. The shipped `agent.yaml` adds `parallel_tool_calls: false`. | | 24 | +| `sampling` | map | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Generation settings, OpenAI API key names, passed to the server. A key the OpenAI API does not declare is sent as it is (for example `chat_template_kwargs`). The shipped `agent.yaml` adds `parallel_tool_calls: false`. | |
| 25 | | `watchdogTimeout` | duration | `20s` | Silence tolerated between two streamed tokens before the connection is declared hung. Doubled for summary requests. | | 25 | | `watchdogTimeout` | duration | `20s` | Silence tolerated between two streamed tokens before the connection is declared hung. Doubled for summary requests. | |
| 26 | | `context` | map | see below | Automatic history compression. | | 26 | | `context` | map | see below | Automatic history compression. | |
| 27 | | `sessions` | map | see below | Persistent, per-project sessions. | | 27 | | `sessions` | map | see below | Persistent, per-project sessions. | |
modified
docs/fr/README.md +1 -0 | @@ -16,6 +16,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https: | ||
| 16 | 16 | - [Interroger un autre agent mm depuis votre session](how-to/ask-another-agent.md) — `mmConfigs`, `/ask-<nom>`, `/ask-with-context-<nom>` |
| 17 | 17 | - [Interroger un autre agent depuis une skill ou un script](how-to/ask-another-agent-from-a-skill.md) — `"$MM_BIN" --ask <agent>`, `--ask-with-context`, les skills de démo |
| 18 | 18 | - [Utiliser mini-me depuis un éditeur (ACP)](how-to/use-from-an-editor.md) |
| 19 | +- [Couper le raisonnement d'un modèle](how-to/turn-reasoning-off.md) — `chat_template_kwargs` ou `reasoning_effort` dans `sampling`, selon le serveur | |
| 19 | 20 | - [Confirmer les appels d'outils avant leur exécution](how-to/confirm-tool-calls.md) — `yolo: false`, les quatre réponses, `AGENT_YOLO`, qui décide avec `mmConfigs` |
| 20 | 21 | - [Faire tourner mini-me dans un Docker Sandbox](how-to/run-in-a-docker-sandbox.md) — le template et les deux kits sous `docker-sbx/`, mode fenêtre et éditeur |
| 21 | 22 | - [Faire tourner mini-me avec les kits sandbox v3](how-to/run-with-sandbox-kits-v3.md) — les deux mêmes kits pour la spécification de kit v3, sous `docker-sbx-v3/` |
| @@ -16,6 +16,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https: | |||
| 16 | - [Interroger un autre agent mm depuis votre session](how-to/ask-another-agent.md) — `mmConfigs`, `/ask-<nom>`, `/ask-with-context-<nom>` | 16 | - [Interroger un autre agent mm depuis votre session](how-to/ask-another-agent.md) — `mmConfigs`, `/ask-<nom>`, `/ask-with-context-<nom>` |
| 17 | - [Interroger un autre agent depuis une skill ou un script](how-to/ask-another-agent-from-a-skill.md) — `"$MM_BIN" --ask <agent>`, `--ask-with-context`, les skills de démo | 17 | - [Interroger un autre agent depuis une skill ou un script](how-to/ask-another-agent-from-a-skill.md) — `"$MM_BIN" --ask <agent>`, `--ask-with-context`, les skills de démo |
| 18 | - [Utiliser mini-me depuis un éditeur (ACP)](how-to/use-from-an-editor.md) | 18 | - [Utiliser mini-me depuis un éditeur (ACP)](how-to/use-from-an-editor.md) |
| 19 | +- [Couper le raisonnement d'un modèle](how-to/turn-reasoning-off.md) — `chat_template_kwargs` ou `reasoning_effort` dans `sampling`, selon le serveur | ||
| 19 | - [Confirmer les appels d'outils avant leur exécution](how-to/confirm-tool-calls.md) — `yolo: false`, les quatre réponses, `AGENT_YOLO`, qui décide avec `mmConfigs` | 20 | - [Confirmer les appels d'outils avant leur exécution](how-to/confirm-tool-calls.md) — `yolo: false`, les quatre réponses, `AGENT_YOLO`, qui décide avec `mmConfigs` |
| 20 | - [Faire tourner mini-me dans un Docker Sandbox](how-to/run-in-a-docker-sandbox.md) — le template et les deux kits sous `docker-sbx/`, mode fenêtre et éditeur | 21 | - [Faire tourner mini-me dans un Docker Sandbox](how-to/run-in-a-docker-sandbox.md) — le template et les deux kits sous `docker-sbx/`, mode fenêtre et éditeur |
| 21 | - [Faire tourner mini-me avec les kits sandbox v3](how-to/run-with-sandbox-kits-v3.md) — les deux mêmes kits pour la spécification de kit v3, sous `docker-sbx-v3/` | 22 | - [Faire tourner mini-me avec les kits sandbox v3](how-to/run-with-sandbox-kits-v3.md) — les deux mêmes kits pour la spécification de kit v3, sous `docker-sbx-v3/` |
modified
docs/fr/explanation/providers.md +1 -1 | @@ -18,7 +18,7 @@ | ||
| 18 | 18 | |
| 19 | 19 | **llmman est une troisième entrée du registre, avec sa propre sonde.** [llmman](https://github.com/llmmanorg/llmman) (`llmman serve`, port 17434) est un démon placé devant un `llama-server` par modèle chargé, et parle plusieurs API ; `mm` n'utilise que celle d'OpenAI. Tout ce qui est propre à llama.cpp la traverse — avancement de la lecture du prompt, raisonnement, `enable_thinking: false` pour les résumés — d'où le même traitement que `llamacpp`. Son `/props`, en revanche, est un bouchon (`n_ctx` 4096, pas de chat template, mesuré sur llmman 0.1.498) : la sonde lit donc la fenêtre servie sur `/api/ps`, pour le modèle configuré, une fois qu'il est chargé. Le contexte d'entraînement de `/api/show` n'est pas utilisé : `LLMMAN_CONTEXT_LENGTH` peut servir moins, et une fenêtre trop grande donne une compression qui ne se déclenche jamais avant que le serveur refuse le prompt. Un modèle absent de `/v1/models` n'est qu'un avertissement : llmman le télécharge à la première requête. |
| 20 | 20 | |
| 21 | -**Ollama est une quatrième entrée, et montre pourquoi chaque serveur garde la sienne.** Le `/v1` d'Ollama ressemble aux autres, mais, mesuré sur Ollama 0.35.0, il diffère sur trois points. Le raisonnement arrive dans `delta.reasoning`, que le tap lit déjà. Aucun avancement de lecture du prompt ne revient, donc `return_progress` n'est pas envoyé. Et `chat_template_kwargs` est ignoré : le résumé d'une compaction a raisonné jusqu'à sa limite de tokens et est revenu vide ; c'est `reasoning_effort: "none"` qu'Ollama respecte, et avec lui la même compaction a réussi. Voilà pourquoi la façon de couper le raisonnement est un champ par fournisseur, et non une conséquence de « est llama.cpp ». Comme llmman, Ollama rapporte la fenêtre servie sur `/api/ps` une fois le modèle chargé (les deux partagent cette lecture) ; contrairement à llmman, il ne télécharge pas un modèle absent, d'où l'avertissement de la sonde qui dit `ollama pull`. | |
| 21 | +**Ollama est une quatrième entrée, et montre pourquoi chaque serveur garde la sienne.** Le `/v1` d'Ollama ressemble aux autres, mais, mesuré sur Ollama 0.35.0, il diffère sur trois points. Le raisonnement arrive dans `delta.reasoning`, que le tap lit déjà. Aucun avancement de lecture du prompt ne revient, donc `return_progress` n'est pas envoyé. Et `chat_template_kwargs` est ignoré : le résumé d'une compaction a raisonné jusqu'à sa limite de tokens et est revenu vide ; c'est `reasoning_effort: "none"` qu'Ollama respecte, et avec lui la même compaction a réussi. Voilà pourquoi la façon de couper le raisonnement est un champ par fournisseur, et non une conséquence de « est llama.cpp ». `mm` ne s'en sert que pour les résumés ; pour chaque tour, l'utilisateur écrit la même clé propre au serveur dans `sampling`, qui transmet depuis Genkit 1.12 les clés que l'API OpenAI ne connaît pas (voir [Couper le raisonnement d'un modèle](../how-to/turn-reasoning-off.md)). Comme llmman, Ollama rapporte la fenêtre servie sur `/api/ps` une fois le modèle chargé (les deux partagent cette lecture) ; contrairement à llmman, il ne télécharge pas un modèle absent, d'où l'avertissement de la sonde qui dit `ollama pull`. | |
| 22 | 22 | |
| 23 | 23 | **Les erreurs sont réécrites dans les mots du serveur.** Une pile de messages imbriqués est du bruit ; « tool calls need llama-server started with --jinja » est tout le diagnostic. Le fournisseur reconnaît les refus de connexion, l'option `--jinja` manquante, les 401/403, les 404 avec une indication de pull ou d'alias, les 429 et les 5xx. Les erreurs inconnues reviennent inchangées : un message brut vaut mieux qu'une mauvaise piste. |
| 24 | 24 | |
| @@ -18,7 +18,7 @@ | |||
| 18 | 18 | ||
| 19 | **llmman est une troisième entrée du registre, avec sa propre sonde.** [llmman](https://github.com/llmmanorg/llmman) (`llmman serve`, port 17434) est un démon placé devant un `llama-server` par modèle chargé, et parle plusieurs API ; `mm` n'utilise que celle d'OpenAI. Tout ce qui est propre à llama.cpp la traverse — avancement de la lecture du prompt, raisonnement, `enable_thinking: false` pour les résumés — d'où le même traitement que `llamacpp`. Son `/props`, en revanche, est un bouchon (`n_ctx` 4096, pas de chat template, mesuré sur llmman 0.1.498) : la sonde lit donc la fenêtre servie sur `/api/ps`, pour le modèle configuré, une fois qu'il est chargé. Le contexte d'entraînement de `/api/show` n'est pas utilisé : `LLMMAN_CONTEXT_LENGTH` peut servir moins, et une fenêtre trop grande donne une compression qui ne se déclenche jamais avant que le serveur refuse le prompt. Un modèle absent de `/v1/models` n'est qu'un avertissement : llmman le télécharge à la première requête. | 19 | **llmman est une troisième entrée du registre, avec sa propre sonde.** [llmman](https://github.com/llmmanorg/llmman) (`llmman serve`, port 17434) est un démon placé devant un `llama-server` par modèle chargé, et parle plusieurs API ; `mm` n'utilise que celle d'OpenAI. Tout ce qui est propre à llama.cpp la traverse — avancement de la lecture du prompt, raisonnement, `enable_thinking: false` pour les résumés — d'où le même traitement que `llamacpp`. Son `/props`, en revanche, est un bouchon (`n_ctx` 4096, pas de chat template, mesuré sur llmman 0.1.498) : la sonde lit donc la fenêtre servie sur `/api/ps`, pour le modèle configuré, une fois qu'il est chargé. Le contexte d'entraînement de `/api/show` n'est pas utilisé : `LLMMAN_CONTEXT_LENGTH` peut servir moins, et une fenêtre trop grande donne une compression qui ne se déclenche jamais avant que le serveur refuse le prompt. Un modèle absent de `/v1/models` n'est qu'un avertissement : llmman le télécharge à la première requête. |
| 20 | 20 | ||
| 21 | -**Ollama est une quatrième entrée, et montre pourquoi chaque serveur garde la sienne.** Le `/v1` d'Ollama ressemble aux autres, mais, mesuré sur Ollama 0.35.0, il diffère sur trois points. Le raisonnement arrive dans `delta.reasoning`, que le tap lit déjà. Aucun avancement de lecture du prompt ne revient, donc `return_progress` n'est pas envoyé. Et `chat_template_kwargs` est ignoré : le résumé d'une compaction a raisonné jusqu'à sa limite de tokens et est revenu vide ; c'est `reasoning_effort: "none"` qu'Ollama respecte, et avec lui la même compaction a réussi. Voilà pourquoi la façon de couper le raisonnement est un champ par fournisseur, et non une conséquence de « est llama.cpp ». Comme llmman, Ollama rapporte la fenêtre servie sur `/api/ps` une fois le modèle chargé (les deux partagent cette lecture) ; contrairement à llmman, il ne télécharge pas un modèle absent, d'où l'avertissement de la sonde qui dit `ollama pull`. | 21 | +**Ollama est une quatrième entrée, et montre pourquoi chaque serveur garde la sienne.** Le `/v1` d'Ollama ressemble aux autres, mais, mesuré sur Ollama 0.35.0, il diffère sur trois points. Le raisonnement arrive dans `delta.reasoning`, que le tap lit déjà. Aucun avancement de lecture du prompt ne revient, donc `return_progress` n'est pas envoyé. Et `chat_template_kwargs` est ignoré : le résumé d'une compaction a raisonné jusqu'à sa limite de tokens et est revenu vide ; c'est `reasoning_effort: "none"` qu'Ollama respecte, et avec lui la même compaction a réussi. Voilà pourquoi la façon de couper le raisonnement est un champ par fournisseur, et non une conséquence de « est llama.cpp ». `mm` ne s'en sert que pour les résumés ; pour chaque tour, l'utilisateur écrit la même clé propre au serveur dans `sampling`, qui transmet depuis Genkit 1.12 les clés que l'API OpenAI ne connaît pas (voir [Couper le raisonnement d'un modèle](../how-to/turn-reasoning-off.md)). Comme llmman, Ollama rapporte la fenêtre servie sur `/api/ps` une fois le modèle chargé (les deux partagent cette lecture) ; contrairement à llmman, il ne télécharge pas un modèle absent, d'où l'avertissement de la sonde qui dit `ollama pull`. |
| 22 | 22 | ||
| 23 | **Les erreurs sont réécrites dans les mots du serveur.** Une pile de messages imbriqués est du bruit ; « tool calls need llama-server started with --jinja » est tout le diagnostic. Le fournisseur reconnaît les refus de connexion, l'option `--jinja` manquante, les 401/403, les 404 avec une indication de pull ou d'alias, les 429 et les 5xx. Les erreurs inconnues reviennent inchangées : un message brut vaut mieux qu'une mauvaise piste. | 23 | **Les erreurs sont réécrites dans les mots du serveur.** Une pile de messages imbriqués est du bruit ; « tool calls need llama-server started with --jinja » est tout le diagnostic. Le fournisseur reconnaît les refus de connexion, l'option `--jinja` manquante, les 401/403, les 404 avec une indication de pull ou d'alias, les 429 et les 5xx. Les erreurs inconnues reviennent inchangées : un message brut vaut mieux qu'une mauvaise piste. |
| 24 | 24 | ||
modified
docs/fr/explanation/thinking-models.md +3 -1 | @@ -30,6 +30,8 @@ Et **le battement du chien de garde**, qui est la partie sérieuse. `mm` annule | ||
| 30 | 30 | |
| 31 | 31 | D'où le fait que `showThinking: false` coupe l'*affichage* et rien d'autre. Le raisonnement est toujours lu, parce que le lire est ce qui garde le tour en vie. |
| 32 | 32 | |
| 33 | +Depuis Genkit 1.13, le greffon transmet bien le raisonnement : sous forme de chunks qui ne contiennent que du raisonnement, passés au gestionnaire de streaming, et sous forme d'une partie de raisonnement dans le message final. `mm` garde malgré tout sa propre lecture — une seule voie pour l'affichage et le battement, quels que soient la version de Genkit et le serveur — et neutralise ces deux nouvelles livraisons. Un chunk qui ne contient que du raisonnement est ignoré par le gestionnaire de `mm` : traité comme un morceau de réponse vide, chacun fermait le bloc de pensée, et le mode ligne affichait un bloc `💭` par token. La partie de raisonnement du message final est retirée avant que Genkit ou `mm` ne conserve le message : laissée en place, elle repartirait vers le serveur comme `reasoning_content` du message de l'assistant au prochain appel du modèle. | |
| 34 | + | |
| 33 | 35 | ## Où il va |
| 34 | 36 | |
| 35 | 37 | | Façade | Ce que vous voyez | |
| @@ -46,6 +48,6 @@ Trois propriétés valent partout : | ||
| 46 | 48 | |
| 47 | 49 | ## Ce que ce n'est pas |
| 48 | 50 | |
| 49 | -Ce n'est pas un réglage d'« effort de raisonnement » : `mm` ne demande rien et ne configure rien côté modèle. Un modèle réfléchit ou non, et `mm` affiche ce qui arrive. | |
| 51 | +Ce n'est pas un réglage d'« effort de raisonnement » : `mm` n'a pas de clé à lui pour cela, et affiche ce qui arrive. On peut tout de même demander au serveur de ne pas raisonner, via `sampling`, dont les clés inconnues de l'API OpenAI sont transmises telles quelles (depuis Genkit 1.12) : `chat_template_kwargs: {enable_thinking: false}` pour un serveur llama.cpp (`dmr`, `llamacpp`, `llmman`), `reasoning_effort: none` pour Ollama. Mesuré avec Qwen3.5 4B sur Docker Model Runner : la même réponse en moins d'une seconde, sans les quatre secondes de raisonnement. `mm` le fait de lui-même, serveur par serveur, pour le seul résumé d'une compaction. Voir [Couper le raisonnement d'un modèle](../how-to/turn-reasoning-off.md). | |
| 50 | 52 | |
| 51 | 53 | Ce n'est pas un analyseur. Certains modèles encadrent plutôt leur raisonnement de balises `<think>` à l'intérieur de `content`. Celles-là arrivent comme du texte de réponse ordinaire et `mm` les affiche comme tel — les deux formes demanderaient des traitements différents, et seul le champ est géré aujourd'hui. |
| @@ -30,6 +30,8 @@ Et **le battement du chien de garde**, qui est la partie sérieuse. `mm` annule | |||
| 30 | 30 | ||
| 31 | D'où le fait que `showThinking: false` coupe l'*affichage* et rien d'autre. Le raisonnement est toujours lu, parce que le lire est ce qui garde le tour en vie. | 31 | D'où le fait que `showThinking: false` coupe l'*affichage* et rien d'autre. Le raisonnement est toujours lu, parce que le lire est ce qui garde le tour en vie. |
| 32 | 32 | ||
| 33 | +Depuis Genkit 1.13, le greffon transmet bien le raisonnement : sous forme de chunks qui ne contiennent que du raisonnement, passés au gestionnaire de streaming, et sous forme d'une partie de raisonnement dans le message final. `mm` garde malgré tout sa propre lecture — une seule voie pour l'affichage et le battement, quels que soient la version de Genkit et le serveur — et neutralise ces deux nouvelles livraisons. Un chunk qui ne contient que du raisonnement est ignoré par le gestionnaire de `mm` : traité comme un morceau de réponse vide, chacun fermait le bloc de pensée, et le mode ligne affichait un bloc `💭` par token. La partie de raisonnement du message final est retirée avant que Genkit ou `mm` ne conserve le message : laissée en place, elle repartirait vers le serveur comme `reasoning_content` du message de l'assistant au prochain appel du modèle. | ||
| 34 | + | ||
| 33 | ## Où il va | 35 | ## Où il va |
| 34 | 36 | ||
| 35 | | Façade | Ce que vous voyez | | 37 | | Façade | Ce que vous voyez | |
| @@ -46,6 +48,6 @@ Trois propriétés valent partout : | |||
| 46 | 48 | ||
| 47 | ## Ce que ce n'est pas | 49 | ## Ce que ce n'est pas |
| 48 | 50 | ||
| 49 | -Ce n'est pas un réglage d'« effort de raisonnement » : `mm` ne demande rien et ne configure rien côté modèle. Un modèle réfléchit ou non, et `mm` affiche ce qui arrive. | 51 | +Ce n'est pas un réglage d'« effort de raisonnement » : `mm` n'a pas de clé à lui pour cela, et affiche ce qui arrive. On peut tout de même demander au serveur de ne pas raisonner, via `sampling`, dont les clés inconnues de l'API OpenAI sont transmises telles quelles (depuis Genkit 1.12) : `chat_template_kwargs: {enable_thinking: false}` pour un serveur llama.cpp (`dmr`, `llamacpp`, `llmman`), `reasoning_effort: none` pour Ollama. Mesuré avec Qwen3.5 4B sur Docker Model Runner : la même réponse en moins d'une seconde, sans les quatre secondes de raisonnement. `mm` le fait de lui-même, serveur par serveur, pour le seul résumé d'une compaction. Voir [Couper le raisonnement d'un modèle](../how-to/turn-reasoning-off.md). |
| 50 | 52 | ||
| 51 | Ce n'est pas un analyseur. Certains modèles encadrent plutôt leur raisonnement de balises `<think>` à l'intérieur de `content`. Celles-là arrivent comme du texte de réponse ordinaire et `mm` les affiche comme tel — les deux formes demanderaient des traitements différents, et seul le champ est géré aujourd'hui. | 53 | Ce n'est pas un analyseur. Certains modèles encadrent plutôt leur raisonnement de balises `<think>` à l'intérieur de `content`. Celles-là arrivent comme du texte de réponse ordinaire et `mm` les affiche comme tel — les deux formes demanderaient des traitements différents, et seul le champ est géré aujourd'hui. |
added
docs/fr/how-to/turn-reasoning-off.md +54 -0 | new file mode 100644 | ||
| @@ -0,0 +1,54 @@ | ||
| 1 | +# Couper le raisonnement d'un modèle | |
| 2 | + | |
| 3 | +Un modèle à raisonnement (Qwen3.5, Qwen3.x, gpt-oss…) réfléchit avant de répondre. Sur une question simple, cela coûte des secondes et des tokens pour rien. Ce guide montre comment demander au serveur de ne pas raisonner, à chaque tour. Il suppose que vous lancez déjà `mm` avec un fichier de configuration YAML et un modèle à raisonnement. | |
| 4 | + | |
| 5 | +## Étapes | |
| 6 | + | |
| 7 | +1. Repérez votre fournisseur dans le fichier de configuration (`provider:`). | |
| 8 | + | |
| 9 | +2. Ajoutez l'interrupteur propre au serveur sous `sampling`. | |
| 10 | + | |
| 11 | + Pour `dmr`, `llamacpp` ou `llmman` — tous trois llama.cpp en dessous : | |
| 12 | + | |
| 13 | + ```yaml | |
| 14 | + sampling: | |
| 15 | + temperature: 0.0 | |
| 16 | + max_tokens: 4096 | |
| 17 | + chat_template_kwargs: | |
| 18 | + enable_thinking: false | |
| 19 | + ``` | |
| 20 | + | |
| 21 | + Pour `ollama` : | |
| 22 | + | |
| 23 | + ```yaml | |
| 24 | + sampling: | |
| 25 | + temperature: 0.0 | |
| 26 | + max_tokens: 4096 | |
| 27 | + reasoning_effort: none | |
| 28 | + ``` | |
| 29 | + | |
| 30 | + Les clés que vous écrivez s'ajoutent à celles par défaut (`temperature`, `top_p`, `max_tokens`) : seul l'interrupteur est à ajouter. | |
| 31 | + | |
| 32 | +3. Posez une question et comparez. Raisonnement actif, le mode ligne affiche un bloc `💭` grisé avant la réponse `🤖` ; coupé, la réponse arrive directement : | |
| 33 | + | |
| 34 | + ``` | |
| 35 | + $ mm -p "What is 17*23? Answer with the number only." agent.yaml | |
| 36 | + | |
| 37 | + 🤖 391⚙ 0 command(s) | |
| 38 | + 391 | |
| 39 | + ``` | |
| 40 | + | |
| 41 | + Mesuré avec Qwen3.5 4B sur Docker Model Runner : moins d'une seconde, là où la même question prenait quatre secondes de raisonnement. | |
| 42 | + | |
| 43 | +## Variantes | |
| 44 | + | |
| 45 | +- **Masquer le raisonnement mais laisser le modèle réfléchir.** `showThinking: false` retire le bloc `💭`, l'entrée `‣ thinking` de la fenêtre et les morceaux de pensée envoyés à l'éditeur. Le modèle raisonne quand même — même durée, mêmes tokens. | |
| 46 | +- **Le modèle ignore l'interrupteur.** `enable_thinking` est une variable du template de chat du modèle : un template qui ne la lit pas (un modèle sans mode de raisonnement, ou qui utilise un autre nom de variable) n'est pas concerné. Vérifiez sur la fiche du modèle la variable que lit son template. | |
| 47 | +- **Le couper pour une seule exécution.** Gardez deux fichiers de configuration, ou démarrez le serveur lui-même sans raisonnement (pour llama-server, voir son option `--reasoning-budget`). | |
| 48 | +- **Résumés de compaction.** Rien à faire : `mm` demande déjà au serveur de ne pas raisonner pour le résumé de `/compact` et de la compression automatique, quoi que dise `sampling`. | |
| 49 | + | |
| 50 | +## Voir aussi | |
| 51 | + | |
| 52 | +- La clé `sampling` et ce qu'elle transmet : [référence de la configuration](../reference/configuration.md). | |
| 53 | +- D'où vient le raisonnement et pourquoi `mm` le lit lui-même : [Modèles à raisonnement](../explanation/thinking-models.md). | |
| 54 | +- Pourquoi chaque serveur est prévenu dans ses propres termes : [Fournisseurs](../explanation/providers.md). | |
| new file mode 100644 | |||
| @@ -0,0 +1,54 @@ | |||
| 1 | +# Couper le raisonnement d'un modèle | ||
| 2 | + | ||
| 3 | +Un modèle à raisonnement (Qwen3.5, Qwen3.x, gpt-oss…) réfléchit avant de répondre. Sur une question simple, cela coûte des secondes et des tokens pour rien. Ce guide montre comment demander au serveur de ne pas raisonner, à chaque tour. Il suppose que vous lancez déjà `mm` avec un fichier de configuration YAML et un modèle à raisonnement. | ||
| 4 | + | ||
| 5 | +## Étapes | ||
| 6 | + | ||
| 7 | +1. Repérez votre fournisseur dans le fichier de configuration (`provider:`). | ||
| 8 | + | ||
| 9 | +2. Ajoutez l'interrupteur propre au serveur sous `sampling`. | ||
| 10 | + | ||
| 11 | + Pour `dmr`, `llamacpp` ou `llmman` — tous trois llama.cpp en dessous : | ||
| 12 | + | ||
| 13 | + ```yaml | ||
| 14 | + sampling: | ||
| 15 | + temperature: 0.0 | ||
| 16 | + max_tokens: 4096 | ||
| 17 | + chat_template_kwargs: | ||
| 18 | + enable_thinking: false | ||
| 19 | + ``` | ||
| 20 | + | ||
| 21 | + Pour `ollama` : | ||
| 22 | + | ||
| 23 | + ```yaml | ||
| 24 | + sampling: | ||
| 25 | + temperature: 0.0 | ||
| 26 | + max_tokens: 4096 | ||
| 27 | + reasoning_effort: none | ||
| 28 | + ``` | ||
| 29 | + | ||
| 30 | + Les clés que vous écrivez s'ajoutent à celles par défaut (`temperature`, `top_p`, `max_tokens`) : seul l'interrupteur est à ajouter. | ||
| 31 | + | ||
| 32 | +3. Posez une question et comparez. Raisonnement actif, le mode ligne affiche un bloc `💭` grisé avant la réponse `🤖` ; coupé, la réponse arrive directement : | ||
| 33 | + | ||
| 34 | + ``` | ||
| 35 | + $ mm -p "What is 17*23? Answer with the number only." agent.yaml | ||
| 36 | + | ||
| 37 | + 🤖 391⚙ 0 command(s) | ||
| 38 | + 391 | ||
| 39 | + ``` | ||
| 40 | + | ||
| 41 | + Mesuré avec Qwen3.5 4B sur Docker Model Runner : moins d'une seconde, là où la même question prenait quatre secondes de raisonnement. | ||
| 42 | + | ||
| 43 | +## Variantes | ||
| 44 | + | ||
| 45 | +- **Masquer le raisonnement mais laisser le modèle réfléchir.** `showThinking: false` retire le bloc `💭`, l'entrée `‣ thinking` de la fenêtre et les morceaux de pensée envoyés à l'éditeur. Le modèle raisonne quand même — même durée, mêmes tokens. | ||
| 46 | +- **Le modèle ignore l'interrupteur.** `enable_thinking` est une variable du template de chat du modèle : un template qui ne la lit pas (un modèle sans mode de raisonnement, ou qui utilise un autre nom de variable) n'est pas concerné. Vérifiez sur la fiche du modèle la variable que lit son template. | ||
| 47 | +- **Le couper pour une seule exécution.** Gardez deux fichiers de configuration, ou démarrez le serveur lui-même sans raisonnement (pour llama-server, voir son option `--reasoning-budget`). | ||
| 48 | +- **Résumés de compaction.** Rien à faire : `mm` demande déjà au serveur de ne pas raisonner pour le résumé de `/compact` et de la compression automatique, quoi que dise `sampling`. | ||
| 49 | + | ||
| 50 | +## Voir aussi | ||
| 51 | + | ||
| 52 | +- La clé `sampling` et ce qu'elle transmet : [référence de la configuration](../reference/configuration.md). | ||
| 53 | +- D'où vient le raisonnement et pourquoi `mm` le lit lui-même : [Modèles à raisonnement](../explanation/thinking-models.md). | ||
| 54 | +- Pourquoi chaque serveur est prévenu dans ses propres termes : [Fournisseurs](../explanation/providers.md). | ||
modified
docs/fr/reference/configuration.md +1 -1 | @@ -22,7 +22,7 @@ | ||
| 22 | 22 | | `bashTool` | booléen | `true` | Déclare `bash`. `false` le retire : le modèle peut encore lire et modifier des fichiers (`editTools`) et charger des skills, mais n'exécute jamais de commande. Reformulez un prompt système qui annonce encore un outil bash. | |
| 23 | 23 | | `editTools` | booléen | `true` | Déclare `read_file`, `write_file`, `edit_file`. `false` ne laisse que `bash` et `read_skill`. | |
| 24 | 24 | | `fetchContentTool` | booléen | `false` | Déclare `fetch_content`, qui récupère des pages web en Markdown. Désactivé par défaut : c'est le seul outil intégré qui accède au réseau. | |
| 25 | -| `sampling` | dictionnaire | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Paramètres de génération, noms de l'API OpenAI, transmis au serveur. L'`agent.yaml` livré ajoute `parallel_tool_calls: false`. `max_tokens` borne aussi un appel d'outil : un `write_file` de long document (ou le raisonnement d'un modèle « thinking » plus l'appel) peut l'atteindre, et l'appel est alors coupé. mm le rend deux fois au modèle en lui demandant de procéder par morceaux, puis s'arrête avec `the model hit max_tokens (N) while writing a tool call` ; augmentez la valeur si cela arrive. En streaming, mm demande aussi au serveur le compte des tokens (`stream_options.include_usage`). | | |
| 25 | +| `sampling` | dictionnaire | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Paramètres de génération, noms de l'API OpenAI, transmis au serveur. Une clé que l'API OpenAI ne déclare pas est envoyée telle quelle, au premier niveau de la requête — par exemple `chat_template_kwargs: {enable_thinking: false}`, qui demande à un serveur llama.cpp (`dmr`, `llamacpp`, `llmman`) de ne pas raisonner ; `reasoning_effort: none` fait de même avec Ollama. `messages`, `tools`, `tool_choice` (et les anciens `functions`, `function_call`) sont ignorées : Genkit les construit à partir de la conversation. L'`agent.yaml` livré ajoute `parallel_tool_calls: false`. `max_tokens` borne aussi un appel d'outil : un `write_file` de long document (ou le raisonnement d'un modèle « thinking » plus l'appel) peut l'atteindre, et l'appel est alors coupé. mm le rend deux fois au modèle en lui demandant de procéder par morceaux, puis s'arrête avec `the model hit max_tokens (N) while writing a tool call` ; augmentez la valeur si cela arrive. En streaming, mm demande aussi au serveur le compte des tokens (`stream_options.include_usage`). | | |
| 26 | 26 | | `watchdogTimeout` | durée | `20s` | Silence toléré entre deux tokens diffusés avant de déclarer la connexion bloquée. Doublé pour les requêtes de résumé. | |
| 27 | 27 | | `firstTokenTimeout` | durée | `3m` | Silence toléré avant le premier token d'un appel au modèle, pendant que le serveur lit le prompt (chaque aller-retour d'un tour en a un). Jamais plus court que `watchdogTimeout` ; doit être >= 0. Avec un serveur llama.cpp (`dmr`, `llamacpp`, `llmman` — dont les modèles GGUF locaux tournent dans llama-server), mm demande `return_progress` : chaque rapport d'avancement relance ce délai, et le spinner affiche `Reading the prompt 12.0k/25.3k tokens`. Ollama n'envoie pas d'avancement : cette seule attente couvre tout le prompt (et le chargement du modèle). À l'expiration, le watchdog dit `no first token from the server after …` (avant le premier token) ou `connection hang detected after …` (après). | |
| 28 | 28 | | `bashTimeout` | durée | `30s` | Durée maximale d'une commande `bash` avant qu'elle soit tuée ; l'outil répond alors `[error: <bashTimeout> timeout exceeded]`. Doit être > 0. Le watchdog continue de compter pendant qu'une commande tourne (ticket 57) : une commande plus longue que `watchdogTimeout` termine le tour, donc relevez les deux ensemble. | |
| @@ -22,7 +22,7 @@ | |||
| 22 | | `bashTool` | booléen | `true` | Déclare `bash`. `false` le retire : le modèle peut encore lire et modifier des fichiers (`editTools`) et charger des skills, mais n'exécute jamais de commande. Reformulez un prompt système qui annonce encore un outil bash. | | 22 | | `bashTool` | booléen | `true` | Déclare `bash`. `false` le retire : le modèle peut encore lire et modifier des fichiers (`editTools`) et charger des skills, mais n'exécute jamais de commande. Reformulez un prompt système qui annonce encore un outil bash. | |
| 23 | | `editTools` | booléen | `true` | Déclare `read_file`, `write_file`, `edit_file`. `false` ne laisse que `bash` et `read_skill`. | | 23 | | `editTools` | booléen | `true` | Déclare `read_file`, `write_file`, `edit_file`. `false` ne laisse que `bash` et `read_skill`. | |
| 24 | | `fetchContentTool` | booléen | `false` | Déclare `fetch_content`, qui récupère des pages web en Markdown. Désactivé par défaut : c'est le seul outil intégré qui accède au réseau. | | 24 | | `fetchContentTool` | booléen | `false` | Déclare `fetch_content`, qui récupère des pages web en Markdown. Désactivé par défaut : c'est le seul outil intégré qui accède au réseau. | |
| 25 | -| `sampling` | dictionnaire | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Paramètres de génération, noms de l'API OpenAI, transmis au serveur. L'`agent.yaml` livré ajoute `parallel_tool_calls: false`. `max_tokens` borne aussi un appel d'outil : un `write_file` de long document (ou le raisonnement d'un modèle « thinking » plus l'appel) peut l'atteindre, et l'appel est alors coupé. mm le rend deux fois au modèle en lui demandant de procéder par morceaux, puis s'arrête avec `the model hit max_tokens (N) while writing a tool call` ; augmentez la valeur si cela arrive. En streaming, mm demande aussi au serveur le compte des tokens (`stream_options.include_usage`). | | 25 | +| `sampling` | dictionnaire | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Paramètres de génération, noms de l'API OpenAI, transmis au serveur. Une clé que l'API OpenAI ne déclare pas est envoyée telle quelle, au premier niveau de la requête — par exemple `chat_template_kwargs: {enable_thinking: false}`, qui demande à un serveur llama.cpp (`dmr`, `llamacpp`, `llmman`) de ne pas raisonner ; `reasoning_effort: none` fait de même avec Ollama. `messages`, `tools`, `tool_choice` (et les anciens `functions`, `function_call`) sont ignorées : Genkit les construit à partir de la conversation. L'`agent.yaml` livré ajoute `parallel_tool_calls: false`. `max_tokens` borne aussi un appel d'outil : un `write_file` de long document (ou le raisonnement d'un modèle « thinking » plus l'appel) peut l'atteindre, et l'appel est alors coupé. mm le rend deux fois au modèle en lui demandant de procéder par morceaux, puis s'arrête avec `the model hit max_tokens (N) while writing a tool call` ; augmentez la valeur si cela arrive. En streaming, mm demande aussi au serveur le compte des tokens (`stream_options.include_usage`). | |
| 26 | | `watchdogTimeout` | durée | `20s` | Silence toléré entre deux tokens diffusés avant de déclarer la connexion bloquée. Doublé pour les requêtes de résumé. | | 26 | | `watchdogTimeout` | durée | `20s` | Silence toléré entre deux tokens diffusés avant de déclarer la connexion bloquée. Doublé pour les requêtes de résumé. | |
| 27 | | `firstTokenTimeout` | durée | `3m` | Silence toléré avant le premier token d'un appel au modèle, pendant que le serveur lit le prompt (chaque aller-retour d'un tour en a un). Jamais plus court que `watchdogTimeout` ; doit être >= 0. Avec un serveur llama.cpp (`dmr`, `llamacpp`, `llmman` — dont les modèles GGUF locaux tournent dans llama-server), mm demande `return_progress` : chaque rapport d'avancement relance ce délai, et le spinner affiche `Reading the prompt 12.0k/25.3k tokens`. Ollama n'envoie pas d'avancement : cette seule attente couvre tout le prompt (et le chargement du modèle). À l'expiration, le watchdog dit `no first token from the server after …` (avant le premier token) ou `connection hang detected after …` (après). | | 27 | | `firstTokenTimeout` | durée | `3m` | Silence toléré avant le premier token d'un appel au modèle, pendant que le serveur lit le prompt (chaque aller-retour d'un tour en a un). Jamais plus court que `watchdogTimeout` ; doit être >= 0. Avec un serveur llama.cpp (`dmr`, `llamacpp`, `llmman` — dont les modèles GGUF locaux tournent dans llama-server), mm demande `return_progress` : chaque rapport d'avancement relance ce délai, et le spinner affiche `Reading the prompt 12.0k/25.3k tokens`. Ollama n'envoie pas d'avancement : cette seule attente couvre tout le prompt (et le chargement du modèle). À l'expiration, le watchdog dit `no first token from the server after …` (avant le premier token) ou `connection hang detected after …` (après). | |
| 28 | | `bashTimeout` | durée | `30s` | Durée maximale d'une commande `bash` avant qu'elle soit tuée ; l'outil répond alors `[error: <bashTimeout> timeout exceeded]`. Doit être > 0. Le watchdog continue de compter pendant qu'une commande tourne (ticket 57) : une commande plus longue que `watchdogTimeout` termine le tour, donc relevez les deux ensemble. | | 28 | | `bashTimeout` | durée | `30s` | Durée maximale d'une commande `bash` avant qu'elle soit tuée ; l'outil répond alors `[error: <bashTimeout> timeout exceeded]`. Doit être > 0. Le watchdog continue de compter pendant qu'une commande tourne (ticket 57) : une commande plus longue que `watchdogTimeout` termine le tour, donc relevez les deux ensemble. | |
modified
go.mod +7 -7 | @@ -3,7 +3,7 @@ module mm | ||
| 3 | 3 | go 1.26.0 |
| 4 | 4 | |
| 5 | 5 | require ( |
| 6 | - github.com/firebase/genkit/go v1.10.0 | |
| 6 | + github.com/firebase/genkit/go v1.13.1 | |
| 7 | 7 | github.com/gdamore/tcell/v2 v2.13.10 |
| 8 | 8 | github.com/openai/openai-go v1.8.2 |
| 9 | 9 | github.com/sashabaranov/go-openai v1.42.1 |
| @@ -19,28 +19,28 @@ require ( | ||
| 19 | 19 | github.com/gdamore/encoding v1.0.1 // indirect |
| 20 | 20 | github.com/go-json-experiment/json v0.0.0-20260623181947-01eb4420fa68 // indirect |
| 21 | 21 | github.com/lucasb-eyer/go-colorful v1.4.0 // indirect |
| 22 | + github.com/pb33f/ordered-map/v2 v2.3.1 // indirect | |
| 22 | 23 | github.com/rivo/uniseg v0.4.7 // indirect |
| 24 | + go.yaml.in/yaml/v4 v4.0.0-rc.4 // indirect | |
| 23 | 25 | golang.org/x/term v0.46.0 // indirect |
| 24 | 26 | ) |
| 25 | 27 | |
| 26 | 28 | require ( |
| 27 | 29 | github.com/bahlo/generic-list-go v0.2.0 // indirect |
| 28 | - github.com/buger/jsonparser v1.1.1 // indirect | |
| 30 | + github.com/buger/jsonparser v1.1.2 // indirect | |
| 29 | 31 | github.com/coder/acp-go-sdk v0.13.5 |
| 30 | 32 | github.com/coder/websocket v1.8.14 // indirect |
| 31 | 33 | github.com/go-logr/logr v1.4.3 // indirect |
| 32 | 34 | github.com/go-logr/stdr v1.2.2 // indirect |
| 33 | - github.com/goccy/go-yaml v1.17.1 // indirect | |
| 34 | - github.com/google/dotprompt/go v0.0.0-20251014011017-8d056e027254 // indirect | |
| 35 | + github.com/goccy/go-yaml v1.19.2 // indirect | |
| 36 | + github.com/google/dotprompt/go v0.0.0-20260708220100-73beb993ac95 // indirect | |
| 35 | 37 | github.com/google/uuid v1.6.0 // indirect |
| 36 | - github.com/invopop/jsonschema v0.13.0 // indirect | |
| 37 | - github.com/mailru/easyjson v0.9.0 // indirect | |
| 38 | + github.com/invopop/jsonschema v0.14.0 // indirect | |
| 38 | 39 | github.com/mbleigh/raymond v0.0.0-20250414171441-6b3a58ab9e0a // indirect |
| 39 | 40 | github.com/tidwall/gjson v1.18.0 // indirect |
| 40 | 41 | github.com/tidwall/match v1.1.1 // indirect |
| 41 | 42 | github.com/tidwall/pretty v1.2.1 // indirect |
| 42 | 43 | github.com/tidwall/sjson v1.2.5 // indirect |
| 43 | - github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect | |
| 44 | 44 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect |
| 45 | 45 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect |
| 46 | 46 | github.com/xeipuuv/gojsonschema v1.2.0 // indirect |
| @@ -3,7 +3,7 @@ module mm | |||
| 3 | go 1.26.0 | 3 | go 1.26.0 |
| 4 | 4 | ||
| 5 | require ( | 5 | require ( |
| 6 | - github.com/firebase/genkit/go v1.10.0 | 6 | + github.com/firebase/genkit/go v1.13.1 |
| 7 | github.com/gdamore/tcell/v2 v2.13.10 | 7 | github.com/gdamore/tcell/v2 v2.13.10 |
| 8 | github.com/openai/openai-go v1.8.2 | 8 | github.com/openai/openai-go v1.8.2 |
| 9 | github.com/sashabaranov/go-openai v1.42.1 | 9 | github.com/sashabaranov/go-openai v1.42.1 |
| @@ -19,28 +19,28 @@ require ( | |||
| 19 | github.com/gdamore/encoding v1.0.1 // indirect | 19 | github.com/gdamore/encoding v1.0.1 // indirect |
| 20 | github.com/go-json-experiment/json v0.0.0-20260623181947-01eb4420fa68 // indirect | 20 | github.com/go-json-experiment/json v0.0.0-20260623181947-01eb4420fa68 // indirect |
| 21 | github.com/lucasb-eyer/go-colorful v1.4.0 // indirect | 21 | github.com/lucasb-eyer/go-colorful v1.4.0 // indirect |
| 22 | + github.com/pb33f/ordered-map/v2 v2.3.1 // indirect | ||
| 22 | github.com/rivo/uniseg v0.4.7 // indirect | 23 | github.com/rivo/uniseg v0.4.7 // indirect |
| 24 | + go.yaml.in/yaml/v4 v4.0.0-rc.4 // indirect | ||
| 23 | golang.org/x/term v0.46.0 // indirect | 25 | golang.org/x/term v0.46.0 // indirect |
| 24 | ) | 26 | ) |
| 25 | 27 | ||
| 26 | require ( | 28 | require ( |
| 27 | github.com/bahlo/generic-list-go v0.2.0 // indirect | 29 | github.com/bahlo/generic-list-go v0.2.0 // indirect |
| 28 | - github.com/buger/jsonparser v1.1.1 // indirect | 30 | + github.com/buger/jsonparser v1.1.2 // indirect |
| 29 | github.com/coder/acp-go-sdk v0.13.5 | 31 | github.com/coder/acp-go-sdk v0.13.5 |
| 30 | github.com/coder/websocket v1.8.14 // indirect | 32 | github.com/coder/websocket v1.8.14 // indirect |
| 31 | github.com/go-logr/logr v1.4.3 // indirect | 33 | github.com/go-logr/logr v1.4.3 // indirect |
| 32 | github.com/go-logr/stdr v1.2.2 // indirect | 34 | github.com/go-logr/stdr v1.2.2 // indirect |
| 33 | - github.com/goccy/go-yaml v1.17.1 // indirect | 35 | + github.com/goccy/go-yaml v1.19.2 // indirect |
| 34 | - github.com/google/dotprompt/go v0.0.0-20251014011017-8d056e027254 // indirect | 36 | + github.com/google/dotprompt/go v0.0.0-20260708220100-73beb993ac95 // indirect |
| 35 | github.com/google/uuid v1.6.0 // indirect | 37 | github.com/google/uuid v1.6.0 // indirect |
| 36 | - github.com/invopop/jsonschema v0.13.0 // indirect | 38 | + github.com/invopop/jsonschema v0.14.0 // indirect |
| 37 | - github.com/mailru/easyjson v0.9.0 // indirect | ||
| 38 | github.com/mbleigh/raymond v0.0.0-20250414171441-6b3a58ab9e0a // indirect | 39 | github.com/mbleigh/raymond v0.0.0-20250414171441-6b3a58ab9e0a // indirect |
| 39 | github.com/tidwall/gjson v1.18.0 // indirect | 40 | github.com/tidwall/gjson v1.18.0 // indirect |
| 40 | github.com/tidwall/match v1.1.1 // indirect | 41 | github.com/tidwall/match v1.1.1 // indirect |
| 41 | github.com/tidwall/pretty v1.2.1 // indirect | 42 | github.com/tidwall/pretty v1.2.1 // indirect |
| 42 | github.com/tidwall/sjson v1.2.5 // indirect | 43 | github.com/tidwall/sjson v1.2.5 // indirect |
| 43 | - github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect | ||
| 44 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect | 44 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect |
| 45 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect | 45 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect |
| 46 | github.com/xeipuuv/gojsonschema v1.2.0 // indirect | 46 | github.com/xeipuuv/gojsonschema v1.2.0 // indirect |
modified
go.sum +20 -14 | @@ -1,7 +1,7 @@ | ||
| 1 | 1 | github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= |
| 2 | 2 | github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= |
| 3 | -github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= | |
| 4 | -github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= | |
| 3 | +github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk= | |
| 4 | +github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= | |
| 5 | 5 | github.com/coder/acp-go-sdk v0.13.5 h1:LI9jq5xon7xslaYlnoktvTVyDlE37yIk2daT7N9ASYk= |
| 6 | 6 | github.com/coder/acp-go-sdk v0.13.5/go.mod h1:yKzM/3R9uELp4+nBAwwtkS0aN1FOFjo11CNPy37yFko= |
| 7 | 7 | github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= |
| @@ -9,8 +9,8 @@ github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6p | ||
| 9 | 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
| 10 | 10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= |
| 11 | 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
| 12 | -github.com/firebase/genkit/go v1.10.0 h1:kOu3MKfgqRPk9yYHg2HFoCg8VWzcHJtfRyQw7OuYqMs= | |
| 13 | -github.com/firebase/genkit/go v1.10.0/go.mod h1:AzmlJrm+2PjSrLnBHwY0uTbRC/GsazMa0JYpBrVf18E= | |
| 12 | +github.com/firebase/genkit/go v1.13.1 h1:6fgQ0ogxG+SIgtYQD/yf8T0+39lacioB5voFdFYk1tI= | |
| 13 | +github.com/firebase/genkit/go v1.13.1/go.mod h1:nWewix7d2O+oikJ035XPmY4mrO2phXwmk6NekKmaDLU= | |
| 14 | 14 | github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw= |
| 15 | 15 | github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo= |
| 16 | 16 | github.com/gdamore/tcell/v2 v2.13.10 h1:Afs3JKt83HnhuUKdZ3MnxUgOqQRWftj5JyDqv1LLynA= |
| @@ -22,28 +22,28 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= | ||
| 22 | 22 | github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= |
| 23 | 23 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= |
| 24 | 24 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= |
| 25 | -github.com/goccy/go-yaml v1.17.1 h1:LI34wktB2xEE3ONG/2Ar54+/HJVBriAGJ55PHls4YuY= | |
| 26 | -github.com/goccy/go-yaml v1.17.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= | |
| 27 | -github.com/google/dotprompt/go v0.0.0-20251014011017-8d056e027254 h1:okN800+zMJOGHLJCgry+OGzhhtH6YrjQh1rluHmOacE= | |
| 28 | -github.com/google/dotprompt/go v0.0.0-20251014011017-8d056e027254/go.mod h1:k8cjJAQWc//ac/bMnzItyOFbfT01tgRTZGgxELCuxEQ= | |
| 25 | +github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= | |
| 26 | +github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= | |
| 27 | +github.com/google/dotprompt/go v0.0.0-20260708220100-73beb993ac95 h1:SJdnmyOaT+kZNcUR+a1y2+Oa51j2ctCjYbtexaiXN68= | |
| 28 | +github.com/google/dotprompt/go v0.0.0-20260708220100-73beb993ac95/go.mod h1:dnlL7KrFwJ7s8EJdsAp1WdLqOalJq1Sx2jWZnQkhFXs= | |
| 29 | 29 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= |
| 30 | 30 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= |
| 31 | 31 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= |
| 32 | 32 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= |
| 33 | -github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= | |
| 34 | -github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= | |
| 33 | +github.com/invopop/jsonschema v0.14.0 h1:MHQqLhvpNUZfw+hM3AZDYK7jxO8FZoQeQM77g8iyZjg= | |
| 34 | +github.com/invopop/jsonschema v0.14.0/go.mod h1:ygm6C2EaVNMBDPpaPlnOA2pFAxBnxGjFlMZABxm9n2I= | |
| 35 | 35 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= |
| 36 | 36 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= |
| 37 | 37 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= |
| 38 | 38 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= |
| 39 | 39 | github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4= |
| 40 | 40 | github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= |
| 41 | -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= | |
| 42 | -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= | |
| 43 | 41 | github.com/mbleigh/raymond v0.0.0-20250414171441-6b3a58ab9e0a h1:v2cBA3xWKv2cIOVhnzX/gNgkNXqiHfUgJtA3r61Hf7A= |
| 44 | 42 | github.com/mbleigh/raymond v0.0.0-20250414171441-6b3a58ab9e0a/go.mod h1:Y6ghKH+ZijXn5d9E7qGGZBmjitx7iitZdQiIW97EpTU= |
| 45 | 43 | github.com/openai/openai-go v1.8.2 h1:UqSkJ1vCOPUpz9Ka5tS0324EJFEuOvMc+lA/EarJWP8= |
| 46 | 44 | github.com/openai/openai-go v1.8.2/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y= |
| 45 | +github.com/pb33f/ordered-map/v2 v2.3.1 h1:5319HDO0aw4DA4gzi+zv4FXU9UlSs3xGZ40wcP1nBjY= | |
| 46 | +github.com/pb33f/ordered-map/v2 v2.3.1/go.mod h1:qxFQgd0PkVUtOMCkTapqotNgzRhMPL7VvaHKbd1HnmQ= | |
| 47 | 47 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
| 48 | 48 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
| 49 | 49 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= |
| @@ -66,8 +66,6 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= | ||
| 66 | 66 | github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= |
| 67 | 67 | github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= |
| 68 | 68 | github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= |
| 69 | -github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= | |
| 70 | -github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= | |
| 71 | 69 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= |
| 72 | 70 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= |
| 73 | 71 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= |
| @@ -96,10 +94,14 @@ go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKr | ||
| 96 | 94 | go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= |
| 97 | 95 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= |
| 98 | 96 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= |
| 97 | +go.yaml.in/yaml/v4 v4.0.0-rc.4 h1:UP4+v6fFrBIb1l934bDl//mmnoIZEDK0idg1+AIvX5U= | |
| 98 | +go.yaml.in/yaml/v4 v4.0.0-rc.4/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= | |
| 99 | 99 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= |
| 100 | 100 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= |
| 101 | 101 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= |
| 102 | 102 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= |
| 103 | +golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c= | |
| 104 | +golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o= | |
| 103 | 105 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= |
| 104 | 106 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= |
| 105 | 107 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= |
| @@ -109,6 +111,8 @@ golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= | ||
| 109 | 111 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
| 110 | 112 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
| 111 | 113 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
| 114 | +golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= | |
| 115 | +golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= | |
| 112 | 116 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
| 113 | 117 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
| 114 | 118 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
| @@ -133,6 +137,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm | ||
| 133 | 137 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= |
| 134 | 138 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= |
| 135 | 139 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= |
| 140 | +golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= | |
| 141 | +golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= | |
| 136 | 142 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
| 137 | 143 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
| 138 | 144 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= |
| @@ -1,7 +1,7 @@ | |||
| 1 | github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= | 1 | github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= |
| 2 | github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= | 2 | github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= |
| 3 | -github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= | 3 | +github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk= |
| 4 | -github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= | 4 | +github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= |
| 5 | github.com/coder/acp-go-sdk v0.13.5 h1:LI9jq5xon7xslaYlnoktvTVyDlE37yIk2daT7N9ASYk= | 5 | github.com/coder/acp-go-sdk v0.13.5 h1:LI9jq5xon7xslaYlnoktvTVyDlE37yIk2daT7N9ASYk= |
| 6 | github.com/coder/acp-go-sdk v0.13.5/go.mod h1:yKzM/3R9uELp4+nBAwwtkS0aN1FOFjo11CNPy37yFko= | 6 | github.com/coder/acp-go-sdk v0.13.5/go.mod h1:yKzM/3R9uELp4+nBAwwtkS0aN1FOFjo11CNPy37yFko= |
| 7 | github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= | 7 | github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= |
| @@ -9,8 +9,8 @@ github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6p | |||
| 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
| 10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | 10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= |
| 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
| 12 | -github.com/firebase/genkit/go v1.10.0 h1:kOu3MKfgqRPk9yYHg2HFoCg8VWzcHJtfRyQw7OuYqMs= | 12 | +github.com/firebase/genkit/go v1.13.1 h1:6fgQ0ogxG+SIgtYQD/yf8T0+39lacioB5voFdFYk1tI= |
| 13 | -github.com/firebase/genkit/go v1.10.0/go.mod h1:AzmlJrm+2PjSrLnBHwY0uTbRC/GsazMa0JYpBrVf18E= | 13 | +github.com/firebase/genkit/go v1.13.1/go.mod h1:nWewix7d2O+oikJ035XPmY4mrO2phXwmk6NekKmaDLU= |
| 14 | github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw= | 14 | github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw= |
| 15 | github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo= | 15 | github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo= |
| 16 | github.com/gdamore/tcell/v2 v2.13.10 h1:Afs3JKt83HnhuUKdZ3MnxUgOqQRWftj5JyDqv1LLynA= | 16 | github.com/gdamore/tcell/v2 v2.13.10 h1:Afs3JKt83HnhuUKdZ3MnxUgOqQRWftj5JyDqv1LLynA= |
| @@ -22,28 +22,28 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= | |||
| 22 | github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= | 22 | github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= |
| 23 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= | 23 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= |
| 24 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= | 24 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= |
| 25 | -github.com/goccy/go-yaml v1.17.1 h1:LI34wktB2xEE3ONG/2Ar54+/HJVBriAGJ55PHls4YuY= | 25 | +github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= |
| 26 | -github.com/goccy/go-yaml v1.17.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= | 26 | +github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= |
| 27 | -github.com/google/dotprompt/go v0.0.0-20251014011017-8d056e027254 h1:okN800+zMJOGHLJCgry+OGzhhtH6YrjQh1rluHmOacE= | 27 | +github.com/google/dotprompt/go v0.0.0-20260708220100-73beb993ac95 h1:SJdnmyOaT+kZNcUR+a1y2+Oa51j2ctCjYbtexaiXN68= |
| 28 | -github.com/google/dotprompt/go v0.0.0-20251014011017-8d056e027254/go.mod h1:k8cjJAQWc//ac/bMnzItyOFbfT01tgRTZGgxELCuxEQ= | 28 | +github.com/google/dotprompt/go v0.0.0-20260708220100-73beb993ac95/go.mod h1:dnlL7KrFwJ7s8EJdsAp1WdLqOalJq1Sx2jWZnQkhFXs= |
| 29 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= | 29 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= |
| 30 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= | 30 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= |
| 31 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= | 31 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= |
| 32 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | 32 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= |
| 33 | -github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= | 33 | +github.com/invopop/jsonschema v0.14.0 h1:MHQqLhvpNUZfw+hM3AZDYK7jxO8FZoQeQM77g8iyZjg= |
| 34 | -github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= | 34 | +github.com/invopop/jsonschema v0.14.0/go.mod h1:ygm6C2EaVNMBDPpaPlnOA2pFAxBnxGjFlMZABxm9n2I= |
| 35 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= | 35 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= |
| 36 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= | 36 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= |
| 37 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | 37 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= |
| 38 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | 38 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= |
| 39 | github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4= | 39 | github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4= |
| 40 | github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= | 40 | github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= |
| 41 | -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= | ||
| 42 | -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= | ||
| 43 | github.com/mbleigh/raymond v0.0.0-20250414171441-6b3a58ab9e0a h1:v2cBA3xWKv2cIOVhnzX/gNgkNXqiHfUgJtA3r61Hf7A= | 41 | github.com/mbleigh/raymond v0.0.0-20250414171441-6b3a58ab9e0a h1:v2cBA3xWKv2cIOVhnzX/gNgkNXqiHfUgJtA3r61Hf7A= |
| 44 | github.com/mbleigh/raymond v0.0.0-20250414171441-6b3a58ab9e0a/go.mod h1:Y6ghKH+ZijXn5d9E7qGGZBmjitx7iitZdQiIW97EpTU= | 42 | github.com/mbleigh/raymond v0.0.0-20250414171441-6b3a58ab9e0a/go.mod h1:Y6ghKH+ZijXn5d9E7qGGZBmjitx7iitZdQiIW97EpTU= |
| 45 | github.com/openai/openai-go v1.8.2 h1:UqSkJ1vCOPUpz9Ka5tS0324EJFEuOvMc+lA/EarJWP8= | 43 | github.com/openai/openai-go v1.8.2 h1:UqSkJ1vCOPUpz9Ka5tS0324EJFEuOvMc+lA/EarJWP8= |
| 46 | github.com/openai/openai-go v1.8.2/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y= | 44 | github.com/openai/openai-go v1.8.2/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y= |
| 45 | +github.com/pb33f/ordered-map/v2 v2.3.1 h1:5319HDO0aw4DA4gzi+zv4FXU9UlSs3xGZ40wcP1nBjY= | ||
| 46 | +github.com/pb33f/ordered-map/v2 v2.3.1/go.mod h1:qxFQgd0PkVUtOMCkTapqotNgzRhMPL7VvaHKbd1HnmQ= | ||
| 47 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | 47 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
| 48 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | 48 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
| 49 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= | 49 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= |
| @@ -66,8 +66,6 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= | |||
| 66 | github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= | 66 | github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= |
| 67 | github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= | 67 | github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= |
| 68 | github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= | 68 | github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= |
| 69 | -github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= | ||
| 70 | -github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= | ||
| 71 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= | 69 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= |
| 72 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= | 70 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= |
| 73 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= | 71 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= |
| @@ -96,10 +94,14 @@ go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKr | |||
| 96 | go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= | 94 | go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= |
| 97 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= | 95 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= |
| 98 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= | 96 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= |
| 97 | +go.yaml.in/yaml/v4 v4.0.0-rc.4 h1:UP4+v6fFrBIb1l934bDl//mmnoIZEDK0idg1+AIvX5U= | ||
| 98 | +go.yaml.in/yaml/v4 v4.0.0-rc.4/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= | ||
| 99 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | 99 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= |
| 100 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | 100 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= |
| 101 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= | 101 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= |
| 102 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= | 102 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= |
| 103 | +golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c= | ||
| 104 | +golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o= | ||
| 103 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | 105 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= |
| 104 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | 106 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= |
| 105 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= | 107 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= |
| @@ -109,6 +111,8 @@ golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= | |||
| 109 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | 111 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
| 110 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | 112 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
| 111 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | 113 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
| 114 | +golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= | ||
| 115 | +golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= | ||
| 112 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | 116 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
| 113 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | 117 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
| 114 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | 118 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
| @@ -133,6 +137,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm | |||
| 133 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | 137 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= |
| 134 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= | 138 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= |
| 135 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= | 139 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= |
| 140 | +golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= | ||
| 141 | +golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= | ||
| 136 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | 142 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
| 137 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | 143 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
| 138 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= | 144 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= |
modified
internal/engine/engine.go +7 -0 | @@ -332,6 +332,7 @@ func (e *Engine) Generate(ctx context.Context, messages []*ai.Message, tools []a | ||
| 332 | 332 | }) |
| 333 | 333 | } |
| 334 | 334 | } |
| 335 | + dropReasoning(resp) | |
| 335 | 336 | return resp, err |
| 336 | 337 | }, |
| 337 | 338 | // The tool is about to write to the screen: we forget the |
| @@ -450,6 +451,12 @@ func (e *Engine) Generate(ctx context.Context, messages []*ai.Message, tools []a | ||
| 450 | 451 | if guard.composing && !wasComposing { |
| 451 | 452 | spinner.Start("Composing a tool call") |
| 452 | 453 | } |
| 454 | + // Since Genkit 1.13 a chunk may carry nothing but reasoning. The | |
| 455 | + // tap has already shown it (reasoning.go); handed to emit, its | |
| 456 | + // empty text would close the thought block after every token. | |
| 457 | + if onlyReasoning(chunk) { | |
| 458 | + return nil | |
| 459 | + } | |
| 453 | 460 | emit(chunk.Text()) |
| 454 | 461 | return nil |
| 455 | 462 | }), |
| @@ -332,6 +332,7 @@ func (e *Engine) Generate(ctx context.Context, messages []*ai.Message, tools []a | |||
| 332 | }) | 332 | }) |
| 333 | } | 333 | } |
| 334 | } | 334 | } |
| 335 | + dropReasoning(resp) | ||
| 335 | return resp, err | 336 | return resp, err |
| 336 | }, | 337 | }, |
| 337 | // The tool is about to write to the screen: we forget the | 338 | // The tool is about to write to the screen: we forget the |
| @@ -450,6 +451,12 @@ func (e *Engine) Generate(ctx context.Context, messages []*ai.Message, tools []a | |||
| 450 | if guard.composing && !wasComposing { | 451 | if guard.composing && !wasComposing { |
| 451 | spinner.Start("Composing a tool call") | 452 | spinner.Start("Composing a tool call") |
| 452 | } | 453 | } |
| 454 | + // Since Genkit 1.13 a chunk may carry nothing but reasoning. The | ||
| 455 | + // tap has already shown it (reasoning.go); handed to emit, its | ||
| 456 | + // empty text would close the thought block after every token. | ||
| 457 | + if onlyReasoning(chunk) { | ||
| 458 | + return nil | ||
| 459 | + } | ||
| 453 | emit(chunk.Text()) | 460 | emit(chunk.Text()) |
| 454 | return nil | 461 | return nil |
| 455 | }), | 462 | }), |
modified
internal/engine/openai_compat.go +22 -2 | @@ -4,6 +4,7 @@ import ( | ||
| 4 | 4 | "context" |
| 5 | 5 | "encoding/json" |
| 6 | 6 | "fmt" |
| 7 | + "log/slog" | |
| 7 | 8 | "net/http" |
| 8 | 9 | "os" |
| 9 | 10 | "strings" |
| @@ -12,6 +13,7 @@ import ( | ||
| 12 | 13 | "mm/internal/config" |
| 13 | 14 | |
| 14 | 15 | "github.com/firebase/genkit/go/ai" |
| 16 | + "github.com/firebase/genkit/go/core/logger" | |
| 15 | 17 | "github.com/firebase/genkit/go/genkit" |
| 16 | 18 | "github.com/firebase/genkit/go/plugins/compat_oai" |
| 17 | 19 | "github.com/openai/openai-go/option" |
| @@ -185,14 +187,32 @@ func (p *openaiCompat) Open(ctx context.Context, b Backend) (*genkit.Genkit, str | ||
| 185 | 187 | // request asks for the token counts it would otherwise lack (usage.go). |
| 186 | 188 | Opts: []option.RequestOption{option.WithMiddleware(streamOptions(p.progress, p.thinkingOff), reasoningTap)}, |
| 187 | 189 | } |
| 190 | + quietGenkit() | |
| 188 | 191 | g := genkit.Init(ctx, genkit.WithPlugins(client)) |
| 189 | - client.DefineModel(p.name, b.Model, ai.ModelOptions{ | |
| 192 | + // Registered explicitly: since Genkit 1.13 DefineModel returns an | |
| 193 | + // UNREGISTERED model, and an unregistered name is resolved by the plugin | |
| 194 | + // with the OpenAI SDK's strict config schema, which rejects any sampling | |
| 195 | + // key the SDK does not declare — chat_template_kwargs, the llama.cpp way | |
| 196 | + // to turn reasoning off, first of all ("Additional property … is not | |
| 197 | + // allowed"). DefineModel's model takes its config untyped and sends such | |
| 198 | + // keys as JSON extras; NewModel, its non-deprecated successor, converts | |
| 199 | + // the config to the SDK struct first and loses them. Ticket 76. | |
| 200 | + genkit.RegisterAction(g, client.DefineModel(p.name, b.Model, ai.ModelOptions{ //nolint:staticcheck // see above: the only constructor that keeps provider-specific sampling keys | |
| 190 | 201 | Label: b.Model, |
| 191 | 202 | Supports: &compat_oai.Multimodal, // enables Tools, Multiturn, SystemRole… |
| 192 | - }) | |
| 203 | + }).(*ai.ModelAction)) | |
| 193 | 204 | return g, p.name + "/" + b.Model, nil |
| 194 | 205 | } |
| 195 | 206 | |
| 207 | +// quietGenkit keeps Genkit's informational log lines off the screen: since | |
| 208 | +// 1.13 Init logs "INFO Genkit initialized env=prod plugins=[dmr] …" on | |
| 209 | +// stderr, which lands in the middle of the terminal's output and of the | |
| 210 | +// window's banner. Warnings still show. GENKIT_LOG_LEVEL, when set, wins: | |
| 211 | +// Init applies it after this call. | |
| 212 | +func quietGenkit() { | |
| 213 | + logger.SetLevel(slog.LevelWarn) | |
| 214 | +} | |
| 215 | + | |
| 196 | 216 | // Probe: reachability on /models — the one endpoint every OpenAI-compatible |
| 197 | 217 | // server has, and the one the fallback logic already relied on — then whatever |
| 198 | 218 | // the server-specific probe adds. The config's context window always wins over |
| @@ -4,6 +4,7 @@ import ( | |||
| 4 | "context" | 4 | "context" |
| 5 | "encoding/json" | 5 | "encoding/json" |
| 6 | "fmt" | 6 | "fmt" |
| 7 | + "log/slog" | ||
| 7 | "net/http" | 8 | "net/http" |
| 8 | "os" | 9 | "os" |
| 9 | "strings" | 10 | "strings" |
| @@ -12,6 +13,7 @@ import ( | |||
| 12 | "mm/internal/config" | 13 | "mm/internal/config" |
| 13 | 14 | ||
| 14 | "github.com/firebase/genkit/go/ai" | 15 | "github.com/firebase/genkit/go/ai" |
| 16 | + "github.com/firebase/genkit/go/core/logger" | ||
| 15 | "github.com/firebase/genkit/go/genkit" | 17 | "github.com/firebase/genkit/go/genkit" |
| 16 | "github.com/firebase/genkit/go/plugins/compat_oai" | 18 | "github.com/firebase/genkit/go/plugins/compat_oai" |
| 17 | "github.com/openai/openai-go/option" | 19 | "github.com/openai/openai-go/option" |
| @@ -185,14 +187,32 @@ func (p *openaiCompat) Open(ctx context.Context, b Backend) (*genkit.Genkit, str | |||
| 185 | // request asks for the token counts it would otherwise lack (usage.go). | 187 | // request asks for the token counts it would otherwise lack (usage.go). |
| 186 | Opts: []option.RequestOption{option.WithMiddleware(streamOptions(p.progress, p.thinkingOff), reasoningTap)}, | 188 | Opts: []option.RequestOption{option.WithMiddleware(streamOptions(p.progress, p.thinkingOff), reasoningTap)}, |
| 187 | } | 189 | } |
| 190 | + quietGenkit() | ||
| 188 | g := genkit.Init(ctx, genkit.WithPlugins(client)) | 191 | g := genkit.Init(ctx, genkit.WithPlugins(client)) |
| 189 | - client.DefineModel(p.name, b.Model, ai.ModelOptions{ | 192 | + // Registered explicitly: since Genkit 1.13 DefineModel returns an |
| 193 | + // UNREGISTERED model, and an unregistered name is resolved by the plugin | ||
| 194 | + // with the OpenAI SDK's strict config schema, which rejects any sampling | ||
| 195 | + // key the SDK does not declare — chat_template_kwargs, the llama.cpp way | ||
| 196 | + // to turn reasoning off, first of all ("Additional property … is not | ||
| 197 | + // allowed"). DefineModel's model takes its config untyped and sends such | ||
| 198 | + // keys as JSON extras; NewModel, its non-deprecated successor, converts | ||
| 199 | + // the config to the SDK struct first and loses them. Ticket 76. | ||
| 200 | + genkit.RegisterAction(g, client.DefineModel(p.name, b.Model, ai.ModelOptions{ //nolint:staticcheck // see above: the only constructor that keeps provider-specific sampling keys | ||
| 190 | Label: b.Model, | 201 | Label: b.Model, |
| 191 | Supports: &compat_oai.Multimodal, // enables Tools, Multiturn, SystemRole… | 202 | Supports: &compat_oai.Multimodal, // enables Tools, Multiturn, SystemRole… |
| 192 | - }) | 203 | + }).(*ai.ModelAction)) |
| 193 | return g, p.name + "/" + b.Model, nil | 204 | return g, p.name + "/" + b.Model, nil |
| 194 | } | 205 | } |
| 195 | 206 | ||
| 207 | +// quietGenkit keeps Genkit's informational log lines off the screen: since | ||
| 208 | +// 1.13 Init logs "INFO Genkit initialized env=prod plugins=[dmr] …" on | ||
| 209 | +// stderr, which lands in the middle of the terminal's output and of the | ||
| 210 | +// window's banner. Warnings still show. GENKIT_LOG_LEVEL, when set, wins: | ||
| 211 | +// Init applies it after this call. | ||
| 212 | +func quietGenkit() { | ||
| 213 | + logger.SetLevel(slog.LevelWarn) | ||
| 214 | +} | ||
| 215 | + | ||
| 196 | // Probe: reachability on /models — the one endpoint every OpenAI-compatible | 216 | // Probe: reachability on /models — the one endpoint every OpenAI-compatible |
| 197 | // server has, and the one the fallback logic already relied on — then whatever | 217 | // server has, and the one the fallback logic already relied on — then whatever |
| 198 | // the server-specific probe adds. The config's context window always wins over | 218 | // the server-specific probe adds. The config's context window always wins over |
modified
internal/engine/provider_test.go +13 -0 | @@ -9,6 +9,7 @@ package engine | ||
| 9 | 9 | import ( |
| 10 | 10 | "context" |
| 11 | 11 | "encoding/json" |
| 12 | + "log/slog" | |
| 12 | 13 | "net/http" |
| 13 | 14 | "net/http/httptest" |
| 14 | 15 | "os" |
| @@ -18,6 +19,8 @@ import ( | ||
| 18 | 19 | |
| 19 | 20 | "mm/internal/config" |
| 20 | 21 | |
| 22 | + "github.com/firebase/genkit/go/core/logger" | |
| 23 | + | |
| 21 | 24 | "github.com/firebase/genkit/go/ai" |
| 22 | 25 | ) |
| 23 | 26 | |
| @@ -534,3 +537,13 @@ func TestProbeOllama(t *testing.T) { | ||
| 534 | 537 | t.Errorf("missing model: %+v, want one warning naming ollama pull, window unknown", info) |
| 535 | 538 | } |
| 536 | 539 | } |
| 540 | + | |
| 541 | +// Opening a provider leaves Genkit at warning level: its "Genkit initialized" | |
| 542 | +// INFO line would land in the terminal's output and the window's banner. | |
| 543 | +func TestOpenKeepsGenkitQuiet(t *testing.T) { | |
| 544 | + t.Setenv("GENKIT_LOG_LEVEL", "") | |
| 545 | + _ = engineOn(t, "http://127.0.0.1:1/v1") | |
| 546 | + if got := logger.GetLevel(); got < slog.LevelWarn { | |
| 547 | + t.Errorf("Genkit log level = %v after Open, want warn or above", got) | |
| 548 | + } | |
| 549 | +} | |
| @@ -9,6 +9,7 @@ package engine | |||
| 9 | import ( | 9 | import ( |
| 10 | "context" | 10 | "context" |
| 11 | "encoding/json" | 11 | "encoding/json" |
| 12 | + "log/slog" | ||
| 12 | "net/http" | 13 | "net/http" |
| 13 | "net/http/httptest" | 14 | "net/http/httptest" |
| 14 | "os" | 15 | "os" |
| @@ -18,6 +19,8 @@ import ( | |||
| 18 | 19 | ||
| 19 | "mm/internal/config" | 20 | "mm/internal/config" |
| 20 | 21 | ||
| 22 | + "github.com/firebase/genkit/go/core/logger" | ||
| 23 | + | ||
| 21 | "github.com/firebase/genkit/go/ai" | 24 | "github.com/firebase/genkit/go/ai" |
| 22 | ) | 25 | ) |
| 23 | 26 | ||
| @@ -534,3 +537,13 @@ func TestProbeOllama(t *testing.T) { | |||
| 534 | t.Errorf("missing model: %+v, want one warning naming ollama pull, window unknown", info) | 537 | t.Errorf("missing model: %+v, want one warning naming ollama pull, window unknown", info) |
| 535 | } | 538 | } |
| 536 | } | 539 | } |
| 540 | + | ||
| 541 | +// Opening a provider leaves Genkit at warning level: its "Genkit initialized" | ||
| 542 | +// INFO line would land in the terminal's output and the window's banner. | ||
| 543 | +func TestOpenKeepsGenkitQuiet(t *testing.T) { | ||
| 544 | + t.Setenv("GENKIT_LOG_LEVEL", "") | ||
| 545 | + _ = engineOn(t, "http://127.0.0.1:1/v1") | ||
| 546 | + if got := logger.GetLevel(); got < slog.LevelWarn { | ||
| 547 | + t.Errorf("Genkit log level = %v after Open, want warn or above", got) | ||
| 548 | + } | ||
| 549 | +} | ||
modified
internal/engine/reasoning.go +38 -0 | @@ -5,8 +5,10 @@ import ( | ||
| 5 | 5 | "encoding/json" |
| 6 | 6 | "io" |
| 7 | 7 | "net/http" |
| 8 | + "slices" | |
| 8 | 9 | "strings" |
| 9 | 10 | |
| 11 | + "github.com/firebase/genkit/go/ai" | |
| 10 | 12 | "github.com/openai/openai-go/option" |
| 11 | 13 | ) |
| 12 | 14 | |
| @@ -34,6 +36,42 @@ import ( | ||
| 34 | 36 | // is carried on the REQUEST CONTEXT rather than in a package variable — so |
| 35 | 37 | // two turns running at once cannot cross their reasoning. |
| 36 | 38 | |
| 39 | +// dropReasoning removes the reasoning parts from a model response, in place. | |
| 40 | +// Since Genkit 1.13 the plugin reads `reasoning_content` itself and puts it | |
| 41 | +// in the response as a reasoning part (ticket 76). Kept, it would land in the | |
| 42 | +// history — Genkit builds the next model call of the turn from it, mm keeps | |
| 43 | +// it for the next question and the session file — and go back to the server | |
| 44 | +// as the assistant message's `reasoning_content`: the model would read its | |
| 45 | +// own thoughts, and pay for them in context. The reasoning is shown through | |
| 46 | +// the tap above; it never belongs to the conversation. A nil response, or one | |
| 47 | +// without a message, is left alone. | |
| 48 | +// | |
| 49 | +// resp, err := next(ctx, params) | |
| 50 | +// dropReasoning(resp) | |
| 51 | +func dropReasoning(resp *ai.ModelResponse) { | |
| 52 | + if resp == nil || resp.Message == nil { | |
| 53 | + return | |
| 54 | + } | |
| 55 | + resp.Message.Content = slices.DeleteFunc(resp.Message.Content, func(p *ai.Part) bool { | |
| 56 | + return p.IsReasoning() | |
| 57 | + }) | |
| 58 | +} | |
| 59 | + | |
| 60 | +// onlyReasoning reports whether a streamed chunk carries reasoning and | |
| 61 | +// nothing else — no text, no tool call. Genkit 1.13 started handing such | |
| 62 | +// chunks to the streaming callback; earlier versions dropped them. | |
| 63 | +func onlyReasoning(chunk *ai.ModelResponseChunk) bool { | |
| 64 | + if chunk == nil || len(chunk.Content) == 0 { | |
| 65 | + return false | |
| 66 | + } | |
| 67 | + for _, p := range chunk.Content { | |
| 68 | + if !p.IsReasoning() { | |
| 69 | + return false | |
| 70 | + } | |
| 71 | + } | |
| 72 | + return true | |
| 73 | +} | |
| 74 | + | |
| 37 | 75 | // reasonKey is the context key carrying the current turn's listener. |
| 38 | 76 | type reasonKey struct{} |
| 39 | 77 | |
| @@ -5,8 +5,10 @@ import ( | |||
| 5 | "encoding/json" | 5 | "encoding/json" |
| 6 | "io" | 6 | "io" |
| 7 | "net/http" | 7 | "net/http" |
| 8 | + "slices" | ||
| 8 | "strings" | 9 | "strings" |
| 9 | 10 | ||
| 11 | + "github.com/firebase/genkit/go/ai" | ||
| 10 | "github.com/openai/openai-go/option" | 12 | "github.com/openai/openai-go/option" |
| 11 | ) | 13 | ) |
| 12 | 14 | ||
| @@ -34,6 +36,42 @@ import ( | |||
| 34 | // is carried on the REQUEST CONTEXT rather than in a package variable — so | 36 | // is carried on the REQUEST CONTEXT rather than in a package variable — so |
| 35 | // two turns running at once cannot cross their reasoning. | 37 | // two turns running at once cannot cross their reasoning. |
| 36 | 38 | ||
| 39 | +// dropReasoning removes the reasoning parts from a model response, in place. | ||
| 40 | +// Since Genkit 1.13 the plugin reads `reasoning_content` itself and puts it | ||
| 41 | +// in the response as a reasoning part (ticket 76). Kept, it would land in the | ||
| 42 | +// history — Genkit builds the next model call of the turn from it, mm keeps | ||
| 43 | +// it for the next question and the session file — and go back to the server | ||
| 44 | +// as the assistant message's `reasoning_content`: the model would read its | ||
| 45 | +// own thoughts, and pay for them in context. The reasoning is shown through | ||
| 46 | +// the tap above; it never belongs to the conversation. A nil response, or one | ||
| 47 | +// without a message, is left alone. | ||
| 48 | +// | ||
| 49 | +// resp, err := next(ctx, params) | ||
| 50 | +// dropReasoning(resp) | ||
| 51 | +func dropReasoning(resp *ai.ModelResponse) { | ||
| 52 | + if resp == nil || resp.Message == nil { | ||
| 53 | + return | ||
| 54 | + } | ||
| 55 | + resp.Message.Content = slices.DeleteFunc(resp.Message.Content, func(p *ai.Part) bool { | ||
| 56 | + return p.IsReasoning() | ||
| 57 | + }) | ||
| 58 | +} | ||
| 59 | + | ||
| 60 | +// onlyReasoning reports whether a streamed chunk carries reasoning and | ||
| 61 | +// nothing else — no text, no tool call. Genkit 1.13 started handing such | ||
| 62 | +// chunks to the streaming callback; earlier versions dropped them. | ||
| 63 | +func onlyReasoning(chunk *ai.ModelResponseChunk) bool { | ||
| 64 | + if chunk == nil || len(chunk.Content) == 0 { | ||
| 65 | + return false | ||
| 66 | + } | ||
| 67 | + for _, p := range chunk.Content { | ||
| 68 | + if !p.IsReasoning() { | ||
| 69 | + return false | ||
| 70 | + } | ||
| 71 | + } | ||
| 72 | + return true | ||
| 73 | +} | ||
| 74 | + | ||
| 37 | // reasonKey is the context key carrying the current turn's listener. | 75 | // reasonKey is the context key carrying the current turn's listener. |
| 38 | type reasonKey struct{} | 76 | type reasonKey struct{} |
| 39 | 77 | ||
modified
internal/engine/reasoning_test.go +77 -0 | @@ -2,13 +2,20 @@ package engine | ||
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | 4 | "context" |
| 5 | + "encoding/json" | |
| 6 | + "fmt" | |
| 5 | 7 | "io" |
| 6 | 8 | "net/http" |
| 7 | 9 | "net/http/httptest" |
| 8 | 10 | "strings" |
| 9 | 11 | "sync" |
| 10 | 12 | "testing" |
| 13 | + "time" | |
| 11 | 14 | |
| 15 | + "mm/internal/config" | |
| 16 | + "mm/internal/ui" | |
| 17 | + | |
| 18 | + "github.com/firebase/genkit/go/ai" | |
| 12 | 19 | "github.com/openai/openai-go/option" |
| 13 | 20 | ) |
| 14 | 21 | |
| @@ -160,3 +167,73 @@ func TestWithReasoningIgnoresANilListener(t *testing.T) { | ||
| 160 | 167 | } |
| 161 | 168 | |
| 162 | 169 | var _ = option.WithMiddleware(reasoningTap) // the middleware still fits the option |
| 170 | + | |
| 171 | +// The model never reads its own thoughts back: Genkit 1.13 puts the streamed | |
| 172 | +// reasoning into the final message as a reasoning part, and the history that | |
| 173 | +// part would land in is sent back as `reasoning_content` on the next request | |
| 174 | +// (ticket 76). The returned history carries none, and neither does the next | |
| 175 | +// request. | |
| 176 | +func TestReasoningStaysOutOfTheHistory(t *testing.T) { | |
| 177 | + srv, bodies := recordingLlama(t, func(w http.ResponseWriter) { | |
| 178 | + fmt.Fprint(w, sseChunkLine(`{"role":"assistant","reasoning_content":"let me think"}`, "")) | |
| 179 | + fmt.Fprint(w, sseChunkLine(`{"content":"391"}`, "")) | |
| 180 | + fmt.Fprint(w, sseChunkLine(`{}`, "stop")) | |
| 181 | + fmt.Fprint(w, "data: [DONE]\n\n") | |
| 182 | + }) | |
| 183 | + e := engineOn(t, srv.URL+"/v1") | |
| 184 | + | |
| 185 | + resp, history, err := e.Generate(context.Background(), []*ai.Message{ai.NewUserTextMessage("17*23?")}, nil) | |
| 186 | + if err != nil { | |
| 187 | + t.Fatalf("Generate: %v", err) | |
| 188 | + } | |
| 189 | + for _, m := range append(history, resp.Message) { | |
| 190 | + for _, p := range m.Content { | |
| 191 | + if p.IsReasoning() { | |
| 192 | + t.Fatalf("a %s message keeps a reasoning part: %q", m.Role, p.Text) | |
| 193 | + } | |
| 194 | + } | |
| 195 | + } | |
| 196 | + | |
| 197 | + if _, _, err := e.Generate(context.Background(), append(history, ai.NewUserTextMessage("and 17*24?")), nil); err != nil { | |
| 198 | + t.Fatalf("second Generate: %v", err) | |
| 199 | + } | |
| 200 | + got := bodies() | |
| 201 | + if b, _ := json.Marshal(got[len(got)-1]["messages"]); strings.Contains(string(b), "reasoning_content") { | |
| 202 | + t.Errorf("the next request sends the reasoning back: %s", b) | |
| 203 | + } | |
| 204 | +} | |
| 205 | + | |
| 206 | +// One thought block per thought: Genkit 1.13 hands the streaming callback | |
| 207 | +// the chunks that carry nothing but reasoning, and each of them used to close | |
| 208 | +// the 💭 block the tap had just opened — one block per token on screen | |
| 209 | +// (ticket 76). The tap shows the reasoning; those chunks must not end it. | |
| 210 | +func TestReasoningStreamsAsOneBlock(t *testing.T) { | |
| 211 | + srv, _ := recordingLlama(t, func(w http.ResponseWriter) { | |
| 212 | + // One chunk at a time, as a server streams them: written together, | |
| 213 | + // the tap reads them all before Genkit's callback runs and the bug | |
| 214 | + // does not show. | |
| 215 | + for _, word := range []string{"The", " user", " asks"} { | |
| 216 | + fmt.Fprint(w, sseChunkLine(fmt.Sprintf(`{"reasoning_content":%q}`, word), "")) | |
| 217 | + w.(http.Flusher).Flush() | |
| 218 | + time.Sleep(20 * time.Millisecond) | |
| 219 | + } | |
| 220 | + fmt.Fprint(w, sseChunkLine(`{"content":"391"}`, "")) | |
| 221 | + fmt.Fprint(w, sseChunkLine(`{}`, "stop")) | |
| 222 | + fmt.Fprint(w, "data: [DONE]\n\n") | |
| 223 | + }) | |
| 224 | + e := engineOn(t, srv.URL+"/v1") | |
| 225 | + var out strings.Builder | |
| 226 | + oldOut, oldShow := ui.Out, config.Cfg.ShowThinking | |
| 227 | + ui.Out, config.Cfg.ShowThinking = &out, true | |
| 228 | + t.Cleanup(func() { ui.Out, config.Cfg.ShowThinking = oldOut, oldShow }) | |
| 229 | + | |
| 230 | + if _, _, err := e.Generate(context.Background(), []*ai.Message{ai.NewUserTextMessage("17*23?")}, nil); err != nil { | |
| 231 | + t.Fatalf("Generate: %v", err) | |
| 232 | + } | |
| 233 | + if n := strings.Count(out.String(), "💭"); n != 1 { | |
| 234 | + t.Errorf("%d thought blocks, want 1:\n%q", n, out.String()) | |
| 235 | + } | |
| 236 | + if !strings.Contains(out.String(), "The user asks") { | |
| 237 | + t.Errorf("the reasoning is not shown in one piece:\n%q", out.String()) | |
| 238 | + } | |
| 239 | +} | |
| @@ -2,13 +2,20 @@ package engine | |||
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | "context" | 4 | "context" |
| 5 | + "encoding/json" | ||
| 6 | + "fmt" | ||
| 5 | "io" | 7 | "io" |
| 6 | "net/http" | 8 | "net/http" |
| 7 | "net/http/httptest" | 9 | "net/http/httptest" |
| 8 | "strings" | 10 | "strings" |
| 9 | "sync" | 11 | "sync" |
| 10 | "testing" | 12 | "testing" |
| 13 | + "time" | ||
| 11 | 14 | ||
| 15 | + "mm/internal/config" | ||
| 16 | + "mm/internal/ui" | ||
| 17 | + | ||
| 18 | + "github.com/firebase/genkit/go/ai" | ||
| 12 | "github.com/openai/openai-go/option" | 19 | "github.com/openai/openai-go/option" |
| 13 | ) | 20 | ) |
| 14 | 21 | ||
| @@ -160,3 +167,73 @@ func TestWithReasoningIgnoresANilListener(t *testing.T) { | |||
| 160 | } | 167 | } |
| 161 | 168 | ||
| 162 | var _ = option.WithMiddleware(reasoningTap) // the middleware still fits the option | 169 | var _ = option.WithMiddleware(reasoningTap) // the middleware still fits the option |
| 170 | + | ||
| 171 | +// The model never reads its own thoughts back: Genkit 1.13 puts the streamed | ||
| 172 | +// reasoning into the final message as a reasoning part, and the history that | ||
| 173 | +// part would land in is sent back as `reasoning_content` on the next request | ||
| 174 | +// (ticket 76). The returned history carries none, and neither does the next | ||
| 175 | +// request. | ||
| 176 | +func TestReasoningStaysOutOfTheHistory(t *testing.T) { | ||
| 177 | + srv, bodies := recordingLlama(t, func(w http.ResponseWriter) { | ||
| 178 | + fmt.Fprint(w, sseChunkLine(`{"role":"assistant","reasoning_content":"let me think"}`, "")) | ||
| 179 | + fmt.Fprint(w, sseChunkLine(`{"content":"391"}`, "")) | ||
| 180 | + fmt.Fprint(w, sseChunkLine(`{}`, "stop")) | ||
| 181 | + fmt.Fprint(w, "data: [DONE]\n\n") | ||
| 182 | + }) | ||
| 183 | + e := engineOn(t, srv.URL+"/v1") | ||
| 184 | + | ||
| 185 | + resp, history, err := e.Generate(context.Background(), []*ai.Message{ai.NewUserTextMessage("17*23?")}, nil) | ||
| 186 | + if err != nil { | ||
| 187 | + t.Fatalf("Generate: %v", err) | ||
| 188 | + } | ||
| 189 | + for _, m := range append(history, resp.Message) { | ||
| 190 | + for _, p := range m.Content { | ||
| 191 | + if p.IsReasoning() { | ||
| 192 | + t.Fatalf("a %s message keeps a reasoning part: %q", m.Role, p.Text) | ||
| 193 | + } | ||
| 194 | + } | ||
| 195 | + } | ||
| 196 | + | ||
| 197 | + if _, _, err := e.Generate(context.Background(), append(history, ai.NewUserTextMessage("and 17*24?")), nil); err != nil { | ||
| 198 | + t.Fatalf("second Generate: %v", err) | ||
| 199 | + } | ||
| 200 | + got := bodies() | ||
| 201 | + if b, _ := json.Marshal(got[len(got)-1]["messages"]); strings.Contains(string(b), "reasoning_content") { | ||
| 202 | + t.Errorf("the next request sends the reasoning back: %s", b) | ||
| 203 | + } | ||
| 204 | +} | ||
| 205 | + | ||
| 206 | +// One thought block per thought: Genkit 1.13 hands the streaming callback | ||
| 207 | +// the chunks that carry nothing but reasoning, and each of them used to close | ||
| 208 | +// the 💭 block the tap had just opened — one block per token on screen | ||
| 209 | +// (ticket 76). The tap shows the reasoning; those chunks must not end it. | ||
| 210 | +func TestReasoningStreamsAsOneBlock(t *testing.T) { | ||
| 211 | + srv, _ := recordingLlama(t, func(w http.ResponseWriter) { | ||
| 212 | + // One chunk at a time, as a server streams them: written together, | ||
| 213 | + // the tap reads them all before Genkit's callback runs and the bug | ||
| 214 | + // does not show. | ||
| 215 | + for _, word := range []string{"The", " user", " asks"} { | ||
| 216 | + fmt.Fprint(w, sseChunkLine(fmt.Sprintf(`{"reasoning_content":%q}`, word), "")) | ||
| 217 | + w.(http.Flusher).Flush() | ||
| 218 | + time.Sleep(20 * time.Millisecond) | ||
| 219 | + } | ||
| 220 | + fmt.Fprint(w, sseChunkLine(`{"content":"391"}`, "")) | ||
| 221 | + fmt.Fprint(w, sseChunkLine(`{}`, "stop")) | ||
| 222 | + fmt.Fprint(w, "data: [DONE]\n\n") | ||
| 223 | + }) | ||
| 224 | + e := engineOn(t, srv.URL+"/v1") | ||
| 225 | + var out strings.Builder | ||
| 226 | + oldOut, oldShow := ui.Out, config.Cfg.ShowThinking | ||
| 227 | + ui.Out, config.Cfg.ShowThinking = &out, true | ||
| 228 | + t.Cleanup(func() { ui.Out, config.Cfg.ShowThinking = oldOut, oldShow }) | ||
| 229 | + | ||
| 230 | + if _, _, err := e.Generate(context.Background(), []*ai.Message{ai.NewUserTextMessage("17*23?")}, nil); err != nil { | ||
| 231 | + t.Fatalf("Generate: %v", err) | ||
| 232 | + } | ||
| 233 | + if n := strings.Count(out.String(), "💭"); n != 1 { | ||
| 234 | + t.Errorf("%d thought blocks, want 1:\n%q", n, out.String()) | ||
| 235 | + } | ||
| 236 | + if !strings.Contains(out.String(), "The user asks") { | ||
| 237 | + t.Errorf("the reasoning is not shown in one piece:\n%q", out.String()) | ||
| 238 | + } | ||
| 239 | +} | ||
modified
internal/engine/summary_test.go +31 -0 | @@ -11,6 +11,8 @@ import ( | ||
| 11 | 11 | "sync" |
| 12 | 12 | "testing" |
| 13 | 13 | |
| 14 | + "mm/internal/config" | |
| 15 | + | |
| 14 | 16 | "github.com/firebase/genkit/go/ai" |
| 15 | 17 | ) |
| 16 | 18 | |
| @@ -167,3 +169,32 @@ func TestStreamOptionsWithoutThinkingOllama(t *testing.T) { | ||
| 167 | 169 | t.Errorf("a request that chose was changed: %s", got) |
| 168 | 170 | } |
| 169 | 171 | } |
| 172 | + | |
| 173 | +// A sampling key the OpenAI SDK does not declare reaches the server as is: | |
| 174 | +// `chat_template_kwargs` is how a llama.cpp server is told, per request, not | |
| 175 | +// to reason — the YAML must be able to say it (ticket 76). | |
| 176 | +func TestSamplingKeysUnknownToTheSDKReachTheServer(t *testing.T) { | |
| 177 | + srv, bodies := recordingLlama(t, goodAnswer) | |
| 178 | + e := engineOn(t, srv.URL+"/v1") | |
| 179 | + old := config.Cfg.Sampling | |
| 180 | + t.Cleanup(func() { config.Cfg.Sampling = old }) | |
| 181 | + config.Cfg.Sampling = map[string]any{ | |
| 182 | + "temperature": 0.0, | |
| 183 | + "chat_template_kwargs": map[string]any{"enable_thinking": false}, | |
| 184 | + } | |
| 185 | + | |
| 186 | + if _, _, err := e.Generate(context.Background(), []*ai.Message{ai.NewUserTextMessage("hi")}, nil); err != nil { | |
| 187 | + t.Fatalf("Generate: %v", err) | |
| 188 | + } | |
| 189 | + got := bodies() | |
| 190 | + if len(got) != 1 { | |
| 191 | + t.Fatalf("%d request(s), want 1", len(got)) | |
| 192 | + } | |
| 193 | + kw, _ := got[0]["chat_template_kwargs"].(map[string]any) | |
| 194 | + if v, set := kw["enable_thinking"]; !set || v != false { | |
| 195 | + t.Errorf("chat_template_kwargs = %v, want {enable_thinking: false}", got[0]["chat_template_kwargs"]) | |
| 196 | + } | |
| 197 | + if got[0]["stream_options"] == nil { | |
| 198 | + t.Error("mm's own middleware field stream_options is gone") | |
| 199 | + } | |
| 200 | +} | |
| @@ -11,6 +11,8 @@ import ( | |||
| 11 | "sync" | 11 | "sync" |
| 12 | "testing" | 12 | "testing" |
| 13 | 13 | ||
| 14 | + "mm/internal/config" | ||
| 15 | + | ||
| 14 | "github.com/firebase/genkit/go/ai" | 16 | "github.com/firebase/genkit/go/ai" |
| 15 | ) | 17 | ) |
| 16 | 18 | ||
| @@ -167,3 +169,32 @@ func TestStreamOptionsWithoutThinkingOllama(t *testing.T) { | |||
| 167 | t.Errorf("a request that chose was changed: %s", got) | 169 | t.Errorf("a request that chose was changed: %s", got) |
| 168 | } | 170 | } |
| 169 | } | 171 | } |
| 172 | + | ||
| 173 | +// A sampling key the OpenAI SDK does not declare reaches the server as is: | ||
| 174 | +// `chat_template_kwargs` is how a llama.cpp server is told, per request, not | ||
| 175 | +// to reason — the YAML must be able to say it (ticket 76). | ||
| 176 | +func TestSamplingKeysUnknownToTheSDKReachTheServer(t *testing.T) { | ||
| 177 | + srv, bodies := recordingLlama(t, goodAnswer) | ||
| 178 | + e := engineOn(t, srv.URL+"/v1") | ||
| 179 | + old := config.Cfg.Sampling | ||
| 180 | + t.Cleanup(func() { config.Cfg.Sampling = old }) | ||
| 181 | + config.Cfg.Sampling = map[string]any{ | ||
| 182 | + "temperature": 0.0, | ||
| 183 | + "chat_template_kwargs": map[string]any{"enable_thinking": false}, | ||
| 184 | + } | ||
| 185 | + | ||
| 186 | + if _, _, err := e.Generate(context.Background(), []*ai.Message{ai.NewUserTextMessage("hi")}, nil); err != nil { | ||
| 187 | + t.Fatalf("Generate: %v", err) | ||
| 188 | + } | ||
| 189 | + got := bodies() | ||
| 190 | + if len(got) != 1 { | ||
| 191 | + t.Fatalf("%d request(s), want 1", len(got)) | ||
| 192 | + } | ||
| 193 | + kw, _ := got[0]["chat_template_kwargs"].(map[string]any) | ||
| 194 | + if v, set := kw["enable_thinking"]; !set || v != false { | ||
| 195 | + t.Errorf("chat_template_kwargs = %v, want {enable_thinking: false}", got[0]["chat_template_kwargs"]) | ||
| 196 | + } | ||
| 197 | + if got[0]["stream_options"] == nil { | ||
| 198 | + t.Error("mm's own middleware field stream_options is gone") | ||
| 199 | + } | ||
| 200 | +} | ||
modified
internal/engine/usage.go +6 -3 | @@ -44,9 +44,12 @@ import ( | ||
| 44 | 44 | // {"enable_thinking": false}` the same 300 tokens were the summary. On a real |
| 45 | 45 | // session (18k tokens of history, Qwen3.8-27B) the reasoning ate the whole |
| 46 | 46 | // summaryMaxTokens and every compaction failed with "empty summary". Notes |
| 47 | -// need no reasoning. It cannot go in the sampling config either: compat_oai | |
| 48 | -// maps the config onto openai.ChatCompletionNewParams, which has no such | |
| 49 | -// field. How to say it depends on the server (thinkingOff below): llama.cpp | |
| 47 | +// need no reasoning. It is added here, per request, rather than in the | |
| 48 | +// sampling config: the summary needs it whatever the YAML says, and the | |
| 49 | +// field's name depends on the server. (Until Genkit 1.12 the sampling config | |
| 50 | +// could not carry it at all — compat_oai mapped it onto | |
| 51 | +// openai.ChatCompletionNewParams, which has no such field, and dropped it; | |
| 52 | +// since then unknown keys travel as JSON extras, ticket 76.) How to say it depends on the server (thinkingOff below): llama.cpp | |
| 50 | 53 | // reads the template variable, and a template without it ignores it; Ollama |
| 51 | 54 | // ignores chat_template_kwargs entirely (measured 2026-10-01, Ollama 0.35.0, |
| 52 | 55 | // qwen3.5:2b: it reasoned through the whole budget) and obeys |
| @@ -44,9 +44,12 @@ import ( | |||
| 44 | // {"enable_thinking": false}` the same 300 tokens were the summary. On a real | 44 | // {"enable_thinking": false}` the same 300 tokens were the summary. On a real |
| 45 | // session (18k tokens of history, Qwen3.8-27B) the reasoning ate the whole | 45 | // session (18k tokens of history, Qwen3.8-27B) the reasoning ate the whole |
| 46 | // summaryMaxTokens and every compaction failed with "empty summary". Notes | 46 | // summaryMaxTokens and every compaction failed with "empty summary". Notes |
| 47 | -// need no reasoning. It cannot go in the sampling config either: compat_oai | 47 | +// need no reasoning. It is added here, per request, rather than in the |
| 48 | -// maps the config onto openai.ChatCompletionNewParams, which has no such | 48 | +// sampling config: the summary needs it whatever the YAML says, and the |
| 49 | -// field. How to say it depends on the server (thinkingOff below): llama.cpp | 49 | +// field's name depends on the server. (Until Genkit 1.12 the sampling config |
| 50 | +// could not carry it at all — compat_oai mapped it onto | ||
| 51 | +// openai.ChatCompletionNewParams, which has no such field, and dropped it; | ||
| 52 | +// since then unknown keys travel as JSON extras, ticket 76.) How to say it depends on the server (thinkingOff below): llama.cpp | ||
| 50 | // reads the template variable, and a template without it ignores it; Ollama | 53 | // reads the template variable, and a template without it ignores it; Ollama |
| 51 | // ignores chat_template_kwargs entirely (measured 2026-10-01, Ollama 0.35.0, | 54 | // ignores chat_template_kwargs entirely (measured 2026-10-01, Ollama 0.35.0, |
| 52 | // qwen3.5:2b: it reasoned through the whole budget) and obeys | 55 | // qwen3.5:2b: it reasoned through the whole budget) and obeys |
modified
internal/ui/ui.go +7 -4 | @@ -58,7 +58,8 @@ type Diff struct { | ||
| 58 | 58 | } |
| 59 | 59 | |
| 60 | 60 | // Sink receives the events of a turn. nil (the default) means the terminal |
| 61 | -// front end: the tools print themselves and nobody asks for permission. | |
| 61 | +// front end: the tools print themselves, and ask — when they ask at all — | |
| 62 | +// through the line mode's prompt (consent.Terminal). | |
| 62 | 63 | type Sink interface { |
| 63 | 64 | // Text is a chunk of the model's streamed answer. |
| 64 | 65 | Text(text string) |
| @@ -69,9 +70,11 @@ type Sink interface { | ||
| 69 | 70 | Reasoning(text string) |
| 70 | 71 | // ToolStart announces a call before anything runs (ACP: status "pending"). |
| 71 | 72 | ToolStart(ev ToolEvent) |
| 72 | - // Allow asks whether the call may run. The terminal front end has no | |
| 73 | - // gatekeeper and would always say yes; a client shows the user a dialog. | |
| 74 | - // ctx is the turn's context: a cancelled turn abandons the question. | |
| 73 | + // Allow asks the user whether the call may run — a dialog, a prompt in | |
| 74 | + // the box — remembering session-wide answers. Whether to ask at all is | |
| 75 | + // decided BEFORE, by the caller: consent.Gate (yolo) for the built-in | |
| 76 | + // tools, the MCP wrapper for its own. ctx is the turn's context: a | |
| 77 | + // cancelled turn abandons the question. | |
| 75 | 78 | Allow(ctx context.Context, ev ToolEvent) bool |
| 76 | 79 | // ToolRunning reports that the call was allowed and is executing. |
| 77 | 80 | ToolRunning(id string) |
| @@ -58,7 +58,8 @@ type Diff struct { | |||
| 58 | } | 58 | } |
| 59 | 59 | ||
| 60 | // Sink receives the events of a turn. nil (the default) means the terminal | 60 | // Sink receives the events of a turn. nil (the default) means the terminal |
| 61 | -// front end: the tools print themselves and nobody asks for permission. | 61 | +// front end: the tools print themselves, and ask — when they ask at all — |
| 62 | +// through the line mode's prompt (consent.Terminal). | ||
| 62 | type Sink interface { | 63 | type Sink interface { |
| 63 | // Text is a chunk of the model's streamed answer. | 64 | // Text is a chunk of the model's streamed answer. |
| 64 | Text(text string) | 65 | Text(text string) |
| @@ -69,9 +70,11 @@ type Sink interface { | |||
| 69 | Reasoning(text string) | 70 | Reasoning(text string) |
| 70 | // ToolStart announces a call before anything runs (ACP: status "pending"). | 71 | // ToolStart announces a call before anything runs (ACP: status "pending"). |
| 71 | ToolStart(ev ToolEvent) | 72 | ToolStart(ev ToolEvent) |
| 72 | - // Allow asks whether the call may run. The terminal front end has no | 73 | + // Allow asks the user whether the call may run — a dialog, a prompt in |
| 73 | - // gatekeeper and would always say yes; a client shows the user a dialog. | 74 | + // the box — remembering session-wide answers. Whether to ask at all is |
| 74 | - // ctx is the turn's context: a cancelled turn abandons the question. | 75 | + // decided BEFORE, by the caller: consent.Gate (yolo) for the built-in |
| 76 | + // tools, the MCP wrapper for its own. ctx is the turn's context: a | ||
| 77 | + // cancelled turn abandons the question. | ||
| 75 | Allow(ctx context.Context, ev ToolEvent) bool | 78 | Allow(ctx context.Context, ev ToolEvent) bool |
| 76 | // ToolRunning reports that the call was allowed and is executing. | 79 | // ToolRunning reports that the call was allowed and is executing. |
| 77 | ToolRunning(id string) | 80 | ToolRunning(id string) |