From 4704076e6602fcea4bdc742c1203a5f0fd35679c Mon Sep 17 00:00:00 2001 From: 1bcMax Date: Mon, 27 Jul 2026 16:01:36 -0700 Subject: [PATCH] feat: consume the published brand-numbers artifact MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Marketing numbers here were hand-maintained and had drifted from the catalog. They now regenerate from blockrun.ai/brand/numbers.json via markers, and CI fails offline against a committed snapshot when they disagree. scripts/sync-brand-numbers.mjs is vendored byte-for-byte from BlockRunAI/blockrun:brand/ — a package would mean a dependency bump in every repo, and several consumers have no package manifest at all. blockrun CI compares the copies, so this one cannot quietly fall behind. Frontmatter takes literals rather than markers: an HTML comment there is part of the YAML string, not invisible. --- .github/workflows/brand-numbers.yml | 24 ++ README.md | 2 +- brand-numbers.json | 34 +++ ...nalyze-connection-error-custom-provider.md | 2 +- ...auth-vision-provider-falls-back-to-auto.md | 2 +- ...one-endpoint-gpt-claude-gemini-deepseek.md | 6 +- .../04-pay-per-call-llm-no-api-keys-hermes.md | 4 +- scripts/sync-brand-numbers.mjs | 270 ++++++++++++++++++ .../skills/clawrouter/SKILL.md | 22 +- 9 files changed, 350 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/brand-numbers.yml create mode 100644 brand-numbers.json create mode 100644 scripts/sync-brand-numbers.mjs diff --git a/.github/workflows/brand-numbers.yml b/.github/workflows/brand-numbers.yml new file mode 100644 index 0000000..c1f63f9 --- /dev/null +++ b/.github/workflows/brand-numbers.yml @@ -0,0 +1,24 @@ +name: Brand numbers + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +# Fails when a marketing number in this repo disagrees with brand-numbers.json. +# +# --check is deliberately OFFLINE. It compares against the committed snapshot +# and never fetches, so a blockrun.ai deploy in progress cannot fail this repo's +# CI. Pulling a newer artifact is a separate, deliberate act: +# +# node scripts/sync-brand-numbers.mjs --refresh +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + - run: node scripts/sync-brand-numbers.mjs --check diff --git a/README.md b/README.md index 2faf018..d5de6ae 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # hermes-plugin-clawrouter -ClawRouter for [Hermes](https://github.com/NousResearch/hermes-agent) — 55+ LLMs from 9 providers, x402 USDC micropayments, smart routing. One local proxy, one wallet, no API keys. +ClawRouter for [Hermes](https://github.com/NousResearch/hermes-agent) — 66 LLMs from 9 providers, x402 USDC micropayments, smart routing. One local proxy, one wallet, no API keys. Current catalog headliners: Claude Fable 5 / Opus 4.8 / Sonnet 5, GPT-5.6 (Terra / Sol / Luna), Gemini 3.1 Pro / 3.5 Flash, Grok 4.5, DeepSeek V4 Pro, GLM-5.2, Kimi K2.7, MiniMax M3 — plus 8 free NVIDIA-hosted models that cost nothing to run. Image, video, and web-search tools bill through the same wallet. diff --git a/brand-numbers.json b/brand-numbers.json new file mode 100644 index 0000000..312505d --- /dev/null +++ b/brand-numbers.json @@ -0,0 +1,34 @@ +{ + "$schema": "https://blockrun.ai/brand/numbers.schema.json", + "version": 1, + "models": { + "chatVisible": 66, + "totalVisible": 86, + "free": 8, + "freeWithheld": 17, + "image": 8, + "video": 5, + "music": 1, + "speech": 5, + "soundfx": 1, + "withFallback": 44, + "withFallbackAllEntries": 75 + }, + "clawrouter": { + "dimensions": 15, + "tiers": 4, + "profiles": 4, + "aliases": 202 + }, + "mcp": { + "tools": 19 + }, + "chains": { + "rpc": 40 + }, + "savings": { + "baselineModel": "anthropic/claude-opus-5", + "ecoVsBaselinePct": 98, + "autoVsBaselinePct": 87 + } +} diff --git a/docs/01-vision-analyze-connection-error-custom-provider.md b/docs/01-vision-analyze-connection-error-custom-provider.md index 9842028..910be39 100644 --- a/docs/01-vision-analyze-connection-error-custom-provider.md +++ b/docs/01-vision-analyze-connection-error-custom-provider.md @@ -145,7 +145,7 @@ dominates latency exactly as before. *If you'd rather not run and maintain your own local proxy, a ready-made option is [ClawRouter](https://github.com/BlockRunAI/ClawRouter) — a one-command Hermes plugin -that serves a local OpenAI-compatible endpoint and gives you 55+ models — many +that serves a local OpenAI-compatible endpoint and gives you 66 models — many vision-capable — behind it. It's one way to implement Fix 2; the diagnosis above stands regardless of which proxy you use.* diff --git a/docs/02-oauth-vision-provider-falls-back-to-auto.md b/docs/02-oauth-vision-provider-falls-back-to-auto.md index d6b8d3e..a08314d 100644 --- a/docs/02-oauth-vision-provider-falls-back-to-auto.md +++ b/docs/02-oauth-vision-provider-falls-back-to-auto.md @@ -122,7 +122,7 @@ avoiding the broken OAuth branch. *If you'd rather not assemble and maintain your own proxy, [ClawRouter](https://github.com/BlockRunAI/ClawRouter) is a ready-made Hermes plugin -that exposes 55+ models (MiniMax included) as a single local `api_key` provider — one +that exposes 66 models (MiniMax included) as a single local `api_key` provider — one way to implement the robust setup above. The diagnosis holds regardless of which proxy you use; track the upstream fix in [hermes-agent#38685](https://github.com/NousResearch/hermes-agent/issues/38685).* diff --git a/docs/03-one-endpoint-gpt-claude-gemini-deepseek.md b/docs/03-one-endpoint-gpt-claude-gemini-deepseek.md index 17f5103..83ea8a2 100644 --- a/docs/03-one-endpoint-gpt-claude-gemini-deepseek.md +++ b/docs/03-one-endpoint-gpt-claude-gemini-deepseek.md @@ -1,6 +1,6 @@ --- title: "Run GPT-5, Claude, Gemini and DeepSeek in Nous Hermes From One Endpoint" -description: "Stop wiring a separate provider, key and OAuth flow for every model in Hermes Agent. Point Hermes at one OpenAI-compatible gateway and switch between 55+ models from the /model picker." +description: "Stop wiring a separate provider, key and OAuth flow for every model in Hermes Agent. Point Hermes at one OpenAI-compatible gateway and switch between 66 models from the /model picker." keywords: - hermes multiple llm providers - hermes one endpoint all models @@ -76,7 +76,7 @@ blockrun/xai/grok-4.5 blockrun/minimax/minimax-m3 blockrun/moonshot/kimi-k3 blockrun/deepseek/deepseek-v4-pro -…46 curated entries (55+ models via the gateway) +…46 curated entries (66 models via the gateway) ``` Set the model to `blockrun/auto` and the gateway's router picks a model per request @@ -167,4 +167,4 @@ Z.AI/GLM, NVIDIA-hosted open models, and more — plus a few free tiers. *Last reviewed against Hermes Agent v0.18.x. The single-endpoint pattern is provider- agnostic; ClawRouter is the implementation that adds non-custodial pay-per-call -billing and 55+ models behind the one URL.* +billing and 66 models behind the one URL.* diff --git a/docs/04-pay-per-call-llm-no-api-keys-hermes.md b/docs/04-pay-per-call-llm-no-api-keys-hermes.md index 2450b47..d220927 100644 --- a/docs/04-pay-per-call-llm-no-api-keys-hermes.md +++ b/docs/04-pay-per-call-llm-no-api-keys-hermes.md @@ -1,6 +1,6 @@ --- title: "Pay-Per-Call LLM Access for Hermes Agents — No API Keys, No Subscriptions" -description: "Funding and rotating an API key for every model your Hermes agent uses is a chore and a liability. Here's how to give Hermes keyless, pay-per-call access to 55+ models with USDC micropayments from a wallet you control." +description: "Funding and rotating an API key for every model your Hermes agent uses is a chore and a liability. Here's how to give Hermes keyless, pay-per-call access to 66 models with USDC micropayments from a wallet you control." keywords: - hermes no api key llm - hermes pay per use llm @@ -49,7 +49,7 @@ Applied to LLM access, that means: [ClawRouter](https://github.com/BlockRunAI/ClawRouter) implements this for Hermes. It runs a local OpenAI-compatible gateway that signs an x402 payment for each upstream -call and routes to 55+ models: +call and routes to 66 models: ```bash curl -fsSL https://raw.githubusercontent.com/BlockRunAI/ClawRouter-Hermes/main/scripts/install.sh | bash diff --git a/scripts/sync-brand-numbers.mjs b/scripts/sync-brand-numbers.mjs new file mode 100644 index 0000000..c3717f4 --- /dev/null +++ b/scripts/sync-brand-numbers.mjs @@ -0,0 +1,270 @@ +#!/usr/bin/env node +/** + * Sync marketing numbers from BlockRun's canonical brand artifact. + * + * This file is copied byte-for-byte into every public repo as + * scripts/sync-brand-numbers.mjs. It is a copy rather than an npm package on + * purpose: a package would mean 37 dependency bumps, and several consuming + * repos have no package.json at all. Zero dependencies, plain Node. + * + * node scripts/sync-brand-numbers.mjs rewrite markers in place + * node scripts/sync-brand-numbers.mjs --check exit 1 on drift, write nothing + * node scripts/sync-brand-numbers.mjs --refresh re-fetch the artifact first + * + * --check NEVER touches the network. PR CI must be deterministic and offline: + * if it fetched, a deploy in progress would fail every repo in the org at once. + * Freshness is the fan-out job's problem, not the pull request's. + * + * Markers look like: 66 + * and wrap the WHOLE token, so a badge URL, its alt text and the prose number + * can all regenerate from one key. + */ +import { existsSync, lstatSync, readFileSync, writeFileSync, readdirSync } from "node:fs"; +import { join, relative, extname } from "node:path"; + +const ROOT = process.cwd(); +const SNAPSHOT = join(ROOT, "brand-numbers.json"); +// ORIGIN is tried first because it IS the truth — the mirror can only ever be +// as fresh as the last time someone refreshed it. The mirror exists so a repo +// can still sync while blockrun.ai is down, not to front the origin. +// +// The mirror is awesome-blockrun's own brand-numbers.json: that repo consumes +// the artifact like every other, and its snapshot doubles as the org's copy. +// One file, one role per repo, nothing to keep in step by hand. +const ORIGIN = "https://blockrun.ai/brand/numbers.json"; +const MIRROR = + "https://raw.githubusercontent.com/BlockRunAI/awesome-blockrun/main/brand-numbers.json"; + +const argv = new Set(process.argv.slice(2)); +const check = argv.has("--check"); +const refresh = argv.has("--refresh"); + +const SKIP_DIRS = new Set([ + "node_modules", ".git", "dist", "build", "out", ".next", "coverage", + "vendor", "target", "__pycache__", ".venv", "venv", +]); +// .txt is here for llms.txt, which is a first-class marketing surface: it is +// what agents read to find out what BlockRun serves. Scanning other .txt files +// costs a read and changes nothing — only files with markers are ever written. +const TEXT_EXT = new Set([".md", ".mdx", ".txt"]); + +/* ── 1. numbers ──────────────────────────────────────────────────────────── */ + +async function loadNumbers() { + if (!refresh) { + try { + return JSON.parse(readFileSync(SNAPSHOT, "utf8")); + } catch { + fail( + `no brand-numbers.json in ${ROOT}\n` + + ` run with --refresh once to seed it from ${ORIGIN}`, + ); + } + } + for (const url of [ORIGIN, MIRROR]) { + try { + const res = await fetch(url, { signal: AbortSignal.timeout(10_000) }); + if (!res.ok) continue; + const json = await res.json(); + writeFileSync(SNAPSHOT, `${JSON.stringify(json, null, 2)}\n`); + return json; + } catch { + /* try the next source */ + } + } + fail(`could not refresh from ${MIRROR} or ${ORIGIN}`); +} + +/** Flatten nested numbers into dotted keys, ignoring $comment / rationale prose. */ +function flatten(obj, prefix = "") { + return Object.entries(obj).flatMap(([k, v]) => { + if (k.startsWith("$")) return []; + const key = `${prefix}${k}`; + if (v && typeof v === "object" && !Array.isArray(v)) return flatten(v, `${key}.`); + if (v === null) return []; + return [[key, v]]; + }); +} + +/* ── 2. renderers ────────────────────────────────────────────────────────── */ + +/** + * How a key becomes text. Default is the bare value. + * + * A marker may carry an `@modifier` — `` — which + * selects a renderer without changing which number is looked up. The modifier + * is what makes a key reusable: the same mcp.tools appears as a shields badge + * at the top of a README and as a bare "19 tools" in a table two screens down, + * and one marker still keeps the badge URL, its alt text and the label in step. + * + * Renderers are registered under the FULL marker name so a badge's label is + * written out rather than guessed from the key. + */ +const badge = (label) => (n) => + `${n} ${label}`; + +const RENDER = { + "mcp.tools@badge": badge("tools"), + "models.totalVisible@badge": badge("models"), + "models.chatVisible@badge": badge("models"), +}; +const render = (marker, value) => (RENDER[marker] ?? String)(value); + +/** `mcp.tools@badge` looks up `mcp.tools`. Unmodified markers are unaffected. */ +const keyOf = (marker) => marker.split("@")[0]; + +/* ── 3. marker rewriting ─────────────────────────────────────────────────── */ + +const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +const OPEN_ANY = //g; +const CLOSE_ANY = //g; + +/** Byte ranges of fenced code blocks — markers inside them are documentation. */ +function fencedRanges(text) { + const ranges = []; + const fence = /^(\s*)(`{3,}|~{3,})[^\n]*$/gm; + let open = null; + for (let m; (m = fence.exec(text)); ) { + if (open === null) open = m.index; + else { + ranges.push([open, m.index + m[0].length]); + open = null; + } + } + return ranges; +} + +function syncFile(file, numbers, problems) { + const before = readFileSync(file, "utf8"); + const rel = relative(ROOT, file); + const fenced = fencedRanges(before); + const inFence = (i) => fenced.some(([a, b]) => i >= a && i < b); + const known = new Map(numbers); + const used = new Set(); + + // Markers actually present, so a file is only ever rewritten for what it uses + // and an @modifier is carried through to the renderer verbatim. + const markers = new Set(); + // A marker naming a key that does not exist is an error, never a silent + // no-op: a typo'd marker would otherwise sit there looking synced forever. + for (const [re, shown] of [ + [OPEN_ANY, (n) => ``], + [CLOSE_ANY, (n) => ``], + ]) { + for (const m of before.matchAll(re)) { + if (inFence(m.index)) continue; + markers.add(m[1]); + if (!known.has(keyOf(m[1]))) problems.push(`${rel}: unknown key ${shown(m[1])}`); + } + } + + let after = before; + for (const marker of markers) { + const key = keyOf(marker); + if (!known.has(key)) continue; + const value = known.get(key); + const pair = new RegExp( + `()([\\s\\S]*?)()`, + "g", + ); + after = after.replace(pair, (whole, open, inner, close, offset) => { + if (inFence(offset)) return whole; + // Nesting means the closing tag of an inner marker would be consumed by + // the outer one. Refuse rather than produce mangled output. + if (/`, "g"))] + .filter((m) => !inFence(m.index)).length; + const closes = [...before.matchAll(new RegExp(``, "g"))] + .filter((m) => !inFence(m.index)).length; + if (opens !== closes) problems.push(`${rel}: unbalanced marker br:${marker} (${opens} open, ${closes} close)`); + } + + return { before, after, changed: before !== after, used }; +} + +/* ── 4. walk ─────────────────────────────────────────────────────────────── */ + +function* walk(dir) { + for (const name of readdirSync(dir)) { + if (SKIP_DIRS.has(name)) continue; + const p = join(dir, name); + // lstat, not stat: a symlinked directory is reached by its real path or not + // at all. blockrun's docs/ -> awesome-blockrun/docs is exactly the case that + // matters — following it would edit a submodule's files behind the skip + // below, and a link pointing at an ancestor would recurse forever. + const s = lstatSync(p); + if (s.isSymbolicLink()) continue; + if (s.isDirectory()) { + // A nested repo is a submodule or vendored checkout: it carries its own + // brand-numbers.json and syncs itself. Rewriting its markers from THIS + // repo's snapshot would dirty a submodule nobody asked us to touch, and + // would report drift that belongs to another repo's CI. + if (existsSync(join(p, ".git"))) continue; + yield* walk(p); + } else if (TEXT_EXT.has(extname(name))) yield p; + } +} + +function fail(msg) { + console.error(`brand-numbers: ${msg}`); + process.exit(1); +} + +/* ── 5. run ──────────────────────────────────────────────────────────────── */ + +const raw = await loadNumbers(); +const numbers = flatten(raw); +const problems = []; +const drifted = []; +const everUsed = new Set(); + +for (const file of walk(ROOT)) { + const { before, after, changed, used } = syncFile(file, numbers, problems); + used.forEach((k) => everUsed.add(k)); + if (!changed) continue; + drifted.push({ file: relative(ROOT, file), before, after }); + if (!check) writeFileSync(file, after); +} + +if (problems.length) { + for (const p of problems) console.error(` ${p}`); + fail(`${problems.length} marker problem(s)`); +} + +if (check) { + if (drifted.length === 0) { + console.log(`brand-numbers: up to date (${everUsed.size} keys in use)`); + process.exit(0); + } + console.error("brand-numbers: these files disagree with brand-numbers.json\n"); + for (const { file, before, after } of drifted) { + const b = before.split("\n"); + const a = after.split("\n"); + for (let i = 0; i < Math.max(b.length, a.length); i++) { + if (b[i] !== a[i]) { + console.error(` ${file}:${i + 1}`); + console.error(` - ${(b[i] ?? "").trim()}`); + console.error(` + ${(a[i] ?? "").trim()}`); + } + } + } + console.error( + "\n fix with: node scripts/sync-brand-numbers.mjs && git commit -am 'chore: sync brand numbers'", + ); + process.exit(1); +} + +console.log( + drifted.length + ? `brand-numbers: updated ${drifted.length} file(s)` + : `brand-numbers: already up to date (${everUsed.size} keys in use)`, +); diff --git a/src/clawrouter_hermes/skills/clawrouter/SKILL.md b/src/clawrouter_hermes/skills/clawrouter/SKILL.md index 3c1fb77..10ded6d 100644 --- a/src/clawrouter_hermes/skills/clawrouter/SKILL.md +++ b/src/clawrouter_hermes/skills/clawrouter/SKILL.md @@ -1,6 +1,6 @@ --- name: clawrouter -description: Hosted-gateway LLM router — save 67% on inference costs. A local proxy that forwards each request to the blockrun.ai gateway, which routes to the cheapest capable model across 55+ models from OpenAI, Anthropic, Google, DeepSeek, xAI, NVIDIA, and more. 8 free NVIDIA models included. Also exposes realtime market data (global stocks, crypto, FX, commodities), Twitter/X intelligence, and prediction-market data across Polymarket, Kalshi, Limitless, Opinion, Predict.Fun, dFlow + UMA oracle resolution + wallet identity & clustering as built-in agent tools. Not a local-inference tool — prompts are sent to the blockrun.ai gateway. +description: Hosted-gateway LLM router — save 87% on inference costs. A local proxy that forwards each request to the blockrun.ai gateway, which routes to the cheapest capable model across 66 models from OpenAI, Anthropic, Google, DeepSeek, xAI, NVIDIA, and more. 8 free NVIDIA models included. Also exposes realtime market data (global stocks, crypto, FX, commodities), Twitter/X intelligence, and prediction-market data across Polymarket, Kalshi, Limitless, Opinion, Predict.Fun, dFlow + UMA oracle resolution + wallet identity & clustering as built-in agent tools. Not a local-inference tool — prompts are sent to the blockrun.ai gateway. triggers: - "clawrouter" - "claw router" @@ -65,7 +65,7 @@ basics through common OS package managers when available. Then in a Hermes chat session: -- Pick model `blockrun/auto` for smart routing across 55+ models. +- Pick model `blockrun/auto` for smart routing across 66 models. - `/clawrouter wallet` — show address + USDC balance - `/clawrouter stats` — proxy usage - `/clawrouter status` — proxy health @@ -76,7 +76,7 @@ Wallet lives at `~/.openclaw/blockrun/mnemonic` (shared with the upstream TS CLI --- -Hosted-gateway LLM router that saves 67% on inference costs by forwarding each request to the blockrun.ai gateway, which picks the cheapest model capable of handling it across 55+ models from 9 providers (8 free NVIDIA models). All billing flows through one USDC wallet; you do not hold provider API keys. +Hosted-gateway LLM router that saves 87% on inference costs by forwarding each request to the blockrun.ai gateway, which picks the cheapest model capable of handling it across 66 models from 9 providers (8 free NVIDIA models). All billing flows through one USDC wallet; you do not hold provider API keys. **This is not a local-inference tool.** ClawRouter is a thin local proxy. Your prompts are sent over HTTPS to the blockrun.ai gateway for model execution. If your workload requires inference that never leaves your machine, use a local runtime like Ollama — ClawRouter is not the right tool for that use case. @@ -149,16 +149,22 @@ openclaw models set openai/gpt-4o ClawRouter classifies each request into one of four tiers: -- **SIMPLE** (40% of traffic) — factual lookups, greetings, translations → Gemini Flash ($0.60/M, 99% savings) -- **MEDIUM** (30%) — summaries, explanations, data extraction → DeepSeek Chat ($0.42/M, 99% savings) -- **COMPLEX** (20%) — code generation, multi-step analysis → Claude Opus ($75/M, best quality) -- **REASONING** (10%) — proofs, formal logic, multi-step math → o3 ($8/M, 89% savings) +- **SIMPLE** — factual lookups, greetings, translations → gemini-2.5-flash ($0.30/$2.50) +- **MEDIUM** — summaries, explanations, data extraction → kimi-k2.7 ($0.95/$4.00) +- **COMPLEX** — code generation, multi-step analysis → gemini-3.1-pro ($2/$12) +- **REASONING** — proofs, formal logic, multi-step math → grok-4-1-fast-reasoning ($0.20/$0.50) + +Prices are per 1M input/output tokens, on the default `auto` profile. Per-tier +savings percentages are deliberately not quoted: the published figure is blended +across a stated workload mix, and a per-tier number invites comparison against a +baseline nobody wrote down. See +[savings-mix.json](https://github.com/BlockRunAI/blockrun/blob/main/src/brand/savings-mix.json). Rules handle ~80% of requests in <1ms. Only ambiguous queries hit the LLM classifier (~$0.00003 per classification). ## Available Models -55+ models including: claude-fable-5, claude-opus-5, claude-opus-4.8, claude-opus-4.7, claude-sonnet-5, claude-sonnet-4.6, gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna, gpt-5.5, gpt-5.4, gemini-3.1-pro, gemini-3.5-flash, grok-4.5, grok-4.3, grok-build-0.1, deepseek-v4-pro, deepseek-chat, glm-5.2, kimi-k3, minimax-m3, qwen3.7-max, and the curated free models (mistral-large-3-675b, qwen3-next-80b-a3b-instruct, seed-oss-36b, nemotron-3-nano-omni-30b-a3b-reasoning [vision], mistral-nemotron, step-3.7-flash, nemotron-nano-9b-v2, nemotron-nano-12b-v2-vl [vision]). +66 models including: claude-fable-5, claude-opus-5, claude-opus-4.8, claude-opus-4.7, claude-sonnet-5, claude-sonnet-4.6, gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna, gpt-5.5, gpt-5.4, gemini-3.1-pro, gemini-3.5-flash, grok-4.5, grok-4.3, grok-build-0.1, deepseek-v4-pro, deepseek-chat, glm-5.2, kimi-k3, minimax-m3, qwen3.7-max, and the curated free models (mistral-large-3-675b, qwen3-next-80b-a3b-instruct, seed-oss-36b, nemotron-3-nano-omni-30b-a3b-reasoning [vision], mistral-nemotron, step-3.7-flash, nemotron-nano-9b-v2, nemotron-nano-12b-v2-vl [vision]). ## Built-in Agent Tools