3aaf0ad95e
`image_analyze` sends an image file to an OpenAI-compatible vision endpoint and returns the model's natural-language description. Complements `image_ocr` (which uses local tesseract for "what text is on this image"); `image_analyze` is for "what is this image about" — visual reasoning the local OCR engine can't do. Trust-boundary scope: **two-step opt-in only**. 1. The feature is gated by `[features] vision_model = true` — default `false`. 2. The tool needs a `[vision_model]` config block specifying `model` (with optional `api_key` / `base_url` — falls back to the main config api_key + the OpenAI base URL). Without both, the tool isn't registered, so no install fires a vision API call without explicit user setup. Workspace boundary: the tool rejects absolute paths and any `..` parent-dir traversal before any base64 encoding or HTTP call. Stateless — each call sends only the requested image + optional prompt; no session, no conversation history attached. Supports PNG, JPEG, GIF, WebP, and BMP inputs. **Billing**: each call hits the configured vision endpoint (OpenAI by default — `gpt-4o-mini` / `gpt-4o` family commonly configured). Users with their own deployments (Gemini, Claude Vision via OpenAI shim, local llama.cpp) can point `base_url` / `api_key` at the alternative. Tests cover the tool metadata (read-only capability, correct name), MIME-type detection across the supported formats and the unsupported-format rejection path, and the workspace-boundary checks (absolute paths and `..` traversal both reject before any API call). Skipped from the upstream PR: the `.github/workflows/sync-cnb.yml` rewrite, which v0.8.31 already addressed with the concurrency/scoped-push refactor; landing the older form would regress that commit. Resolved a clippy::collapsible_if in tool_setup.rs (the `if feature && let Some(cfg) = ...` form) to satisfy the workspace -D warnings gate. Harvested from PR #1467 by @MMMarcinho Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>