ai-consultants 3.1.1 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.env.example CHANGED
@@ -144,9 +144,10 @@ KIMI_TIMEOUT=180
144
144
  KIMI_CMD=kimi
145
145
 
146
146
  # Claude - The Synthesizer
147
- CLAUDE_MODEL=claude-opus-4-8
147
+ CLAUDE_MODEL=claude-opus-5 # ai-consultants:default
148
148
  CLAUDE_TIMEOUT=240
149
149
  CLAUDE_CMD=claude
150
+ CLAUDE_API_MAX_TOKENS=16384
150
151
 
151
152
  # Qwen3 - The Analyst (CLI/API switchable v2.7)
152
153
  QWEN3_MODEL=qwen3.7-max
package/CLAUDE.md CHANGED
@@ -6,7 +6,7 @@ AI Consultants is a multi-model coverage system that queries up to 11 AI consult
6
6
 
7
7
  **Self-Exclusion**: The invoking agent is automatically excluded from the panel to prevent self-consultation. Claude Code won't query Claude, Codex CLI won't query Codex, etc.
8
8
 
9
- **Version**: 3.1.1
9
+ **Version**: 3.3.0
10
10
 
11
11
  ## Distribution
12
12
 
@@ -368,7 +368,7 @@ Three tiers of models are available for each consultant, configurable via `apply
368
368
 
369
369
  | Tier | Description | Example Models |
370
370
  |------|-------------|----------------|
371
- | **premium** | Latest flagship models | claude-opus-4-8, Gemini 3.1 Pro (High), gpt-5.5 |
371
+ | **premium** | Latest flagship models | claude-opus-5, Gemini 3.1 Pro (High), gpt-5.5 |
372
372
  | **standard** | Good quality at reasonable cost | claude-sonnet-4-6, Gemini 3.5 Flash (High), gpt-5.4 |
373
373
  | **economy** | Optimized for speed and low cost | claude-haiku-4-5, Gemini 3.5 Flash (Low), gpt-5.4-nano |
374
374
 
@@ -400,12 +400,12 @@ Three new presets leverage the model tiers:
400
400
  ./scripts/consult_all.sh --preset fast "quick syntax question"
401
401
  ```
402
402
 
403
- ### Premium Model Defaults (June 2026)
403
+ ### Premium Model Defaults (July 2026)
404
404
  All consultants now use premium models by default:
405
405
 
406
406
  | Consultant | Default Model |
407
407
  |------------|---------------|
408
- | Claude | claude-opus-4-8 |
408
+ | Claude | claude-opus-5 |
409
409
  | Gemini | Gemini 3.1 Pro (High) (via agy CLI) |
410
410
  | Codex | gpt-5.5 |
411
411
  | Mistral | mistral-large-3 |
@@ -613,7 +613,8 @@ for f in scripts/*.sh scripts/lib/*.sh; do bash -n "$f" && echo "OK: $f"; done
613
613
  | `ENABLE_SMART_ROUTING` | false | Intelligent routing by category |
614
614
  | `ENABLE_COST_TRACKING` | true | Track costs |
615
615
  | `MAX_SESSION_COST` | 1.00 | Max budget ($) |
616
- | `CLAUDE_MODEL` | claude-opus-4-8 | Claude model (v2.5) |
616
+ | `CLAUDE_MODEL` | claude-opus-5 | Claude model (v2.5) |
617
+ | `CLAUDE_API_MAX_TOKENS` | 16384 | Claude API thinking + visible-output budget |
617
618
  | `GEMINI_MODEL` | Gemini 3.1 Pro (High) | Gemini agy CLI model (v2.15) |
618
619
  | `GEMINI_API_MODEL` | gemini-3.1-pro-preview | Gemini API-mode model ID (v2.15) |
619
620
  | `CODEX_MODEL` | gpt-5.5 | Codex model (v2.5) |
@@ -786,6 +787,19 @@ curl -fsSL https://raw.githubusercontent.com/matteoscurati/ai-consultants/main/s
786
787
 
787
788
  ## Changelog
788
789
 
790
+ ### v3.3.0
791
+ - **Grok and Kimi compatibility is now established from capabilities, not inferred from an installed binary or its version string.** `grok_cli_supports_required_interface` validates the complete headless parser surface inside the same isolated HOME/CWD boundary used for dispatch, while `grok_cli_exposes_requested_model` verifies both usable authentication and the exact `GROK_MODEL` in the CLI inventory (`scripts/query_grok.sh:85-154`). Kimi similarly checks prompt mode, `stream-json`, the provider inventory, and `KIMI_MODEL` before any request (`scripts/query_kimi.sh:45-79`). Compatible future and vendor-custom versions therefore continue to work without a release-specific allowlist; an interface that cannot enforce the adapter's contract fails before a model call.
792
+ - **The observed CLI version is provenance only.** Successful Grok and Kimi responses add `metadata.cli_version` and `metadata.cli_compatibility="capability-probed"`; the shared schema documents both fields without making either required for other transports (`scripts/query_grok.sh:319-336`, `scripts/query_kimi.sh:126-138`, `scripts/lib/schema.json:169-180`). This deliberately does not introduce a blessed-version range: the version is useful for diagnosis, while compatibility is decided by the exercised command, output, and model surfaces.
793
+ - **Optional capabilities remain optional.** Grok's `--no-auto-update` is used when the help surface advertises it, but its absence does not reject an otherwise compatible CLI. The capability probe itself uses `--help` and model/provider inventory commands; it does not send a consultation. Grok preserves its existing narrowly classified API fallback when the required CLI route is unavailable, while Kimi continues to fail closed because it has no API transport.
794
+ - **Regression coverage pins the compatibility contract rather than today's binaries.** Grok's suite accepts an alternate compatible version, rejects an incomplete interface before dispatch, checks requested-model availability, and preserves the post-launch/API-fallback boundary (32 checks). Kimi's suite covers an alternate compatible version, an incomplete interface, and a missing requested model (8 checks). Full release gate: 17/17 suites; core suite: 299/299; ShellCheck green. Installed Grok 0.2.114 and Kimi 0.27.0 also passed no-model-call probes.
795
+
796
+ ### v3.2.0
797
+ - **Claude premium upgraded from Opus 4.8 to Opus 5.** The default `CLAUDE_MODEL`, premium/max/best tier, standalone adapter fallback, configuration examples, model table, and cost catalog now resolve to the canonical `claude-opus-5` ID (`scripts/config.sh:274`, `scripts/query_claude.sh:29`). Pricing remains $5/$25 per million tokens; `claude-opus-4-8` stays in the legacy catalog for historical responses and intentional pins.
798
+ - **Opus 5 adaptive thinking is accounted for end to end.** Claude CLI mode now requests the JSON result envelope, extracts visible `.result`, aggregates provider usage across `modelUsage`, and persists the provider's exact `costUSD` (`scripts/query_claude.sh:72-126`, `scripts/lib/common.sh:904-980`, `scripts/lib/costs.sh:307-354`). This fixes the review finding that pricing only the visible answer undercounted hidden thinking and cache charges. The implementation deliberately prefers provider cost over reconstructing cache pricing from token totals.
799
+ - **Anthropic API responses no longer disappear when thinking precedes text.** `parse_anthropic_response` selects every `type=text` block rather than assuming `.content[0].text` (`scripts/lib/api.sh:325-343`). The API token budget is now the configurable `CLAUDE_API_MAX_TOKENS=16384`, shared by adaptive thinking and visible output; `stop_reason=max_tokens` fails closed instead of admitting a partial answer into synthesis (`scripts/lib/api_query.sh:94-153`).
800
+ - **Historical generated defaults migrate with provenance.** Before this release, `init` copied `CLAUDE_MODEL=claude-opus-4-8` without distinguishing a generated default from a user pin, so the new runtime fallback was unreachable for existing installations. `configure` now upgrades that exact historical value to Opus 5 and writes `# ai-consultants:default`; model values supplied by environment, interactive input, or `--set` receive `# ai-consultants:pin` and survive later rewrites (`scripts/configure.sh:183-291`, `scripts/configure.sh:357-420`). An intentionally retained 4.8 value should be reasserted once with `--set CLAUDE_MODEL=claude-opus-4-8`.
801
+ - **Verification expanded around all four review findings.** `test_query_claude.sh` exercises CLI JSON extraction, measured usage, provider cost persistence, and session billing. `test_api_transport.sh` covers thinking-before-text, the 16,384-token request default, invalid budgets, and truncation rejection; `test_configure.sh` covers managed migration and persistent pins. Full gate: 17/17 suites; core suite: 299/299; shellcheck green. The real maintainer config was not rewritten during implementation and remained on 4.8 until the release installation step.
802
+
789
803
  ### v3.1.1
790
804
  - **`doctor` no longer aborts at the first missing enabled consultant.** The v3.1.0 tag workflow exposed the pre-existing `set -e` interaction when Grok became CLI-first and the clean Ubuntu runner had no `grok` binary: the main JSON diagnostic exited before emitting output. Missing CLI/API configuration checks now add their issue and continue to the final summary, which still exits unhealthy. `test_doctor.sh` stubs the complete CLI roster, rejects empty JSON output, and includes a dedicated missing-Grok regression. Full gate: 16/16 suites; shellcheck green.
791
805
 
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
- # AI Consultants v3.1.1
1
+ # AI Consultants v3.3.0
2
2
 
3
3
  > **Coverage, not a single guess.** A panel of up to 11 frontier models from different vendors fans out on your question in parallel and hands you the *union* of what they collectively see — the risks, edge cases, and approaches a single model misses.
4
4
 
5
- [![Version](https://img.shields.io/badge/version-3.1.1-blue.svg)](https://github.com/matteoscurati/ai-consultants)
5
+ [![Version](https://img.shields.io/badge/version-3.3.0-blue.svg)](https://github.com/matteoscurati/ai-consultants)
6
6
  [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
7
7
  [![Claude Code Skill](https://img.shields.io/badge/Claude%20Code-Skill-orange.svg)](https://docs.anthropic.com/en/docs/claude-code/skills)
8
8
  [![GitHub stars](https://img.shields.io/github/stars/matteoscurati/ai-consultants?style=social)](https://github.com/matteoscurati/ai-consultants)
@@ -388,7 +388,10 @@ to the xAI API only when the CLI is missing, cannot launch, or has no usable
388
388
  authentication and `GROK_API_KEY` is configured; post-launch request failures
389
389
  are surfaced without a silent API charge. Qwen3 and MiniMax can also switch
390
390
  from their CLI to API transport. Gemini, Codex, Claude, and Mistral are
391
- CLI/API switchable.
391
+ CLI/API switchable. Grok and Kimi compatibility is capability-probed before
392
+ dispatch: the requested model, headless arguments, and structured-output
393
+ surface must be available. Any CLI version is accepted when those checks pass;
394
+ the observed version is response provenance only.
392
395
 
393
396
  ### API-Only Consultants
394
397
 
@@ -444,7 +447,7 @@ parallel and returns the coverage union.
444
447
 
445
448
  | Consultant | Premium | Standard | Economy |
446
449
  |------------|---------|----------|---------|
447
- | Claude | claude-opus-4-8 | claude-sonnet-4-6 | claude-haiku-4-5 |
450
+ | Claude | claude-opus-5 | claude-sonnet-4-6 | claude-haiku-4-5 |
448
451
  | Gemini | Gemini 3.1 Pro (High) | Gemini 3.5 Flash (High) | Gemini 3.5 Flash (Low) |
449
452
  | Codex | gpt-5.5 | gpt-5.4 | gpt-5.4-nano |
450
453
  | Mistral | mistral-large-3 | mistral-medium-latest | devstral-small-2 |
@@ -546,7 +549,13 @@ with `--set`). Rewrites create a private timestamped backup. Use `--advanced` to
546
549
  review every parameter or `--dry-run` to preview a redacted result.
547
550
  Auto-selected `*_USE_API` values are marked `# ai-consultants:auto`, allowing a
548
551
  later run to adapt when a CLI or credential changes. Environment variables,
549
- `--set`, and unmarked values remain explicit user choices.
552
+ `--set`, and unmarked values remain explicit user choices, except for the exact
553
+ historical generated Claude default described below.
554
+ Managed model defaults use `# ai-consultants:default`; `configure` upgrades the
555
+ historical unmarked `CLAUDE_MODEL=claude-opus-4-8` default to Opus 5. To keep
556
+ 4.8 intentionally, run
557
+ `ai-consultants configure --set CLAUDE_MODEL=claude-opus-4-8`; explicit model
558
+ overrides are stored with `# ai-consultants:pin`.
550
559
 
551
560
  Enter credentials through `--interactive`/`--advanced` or export them before the
552
561
  run; avoid passing API keys through `--set`, where the shell may retain them in
@@ -563,6 +572,7 @@ DEFAULT_STRATEGY=coverage # coverage | compare_only | majority | risk_averse
563
572
  ENABLE_SMART_ROUTING=true # Auto-select consultants by question category
564
573
  MAX_SESSION_COST=1.00 # USD budget cap (paired with ENABLE_BUDGET_LIMIT=true to enforce)
565
574
  KIMI_MODEL=kimi-code/k3 # Pin the Kimi consultant to K3
575
+ CLAUDE_API_MAX_TOKENS=16384 # Shared thinking + visible-output budget in API mode
566
576
  ```
567
577
 
568
578
  Full reference: [`references/configuration.md`](references/configuration.md). Copy-paste workflows: [`docs/RECIPES.md`](docs/RECIPES.md). For category-aware preset suggestions: `ai-consultants doctor --suggest-preset --question "..."`.
package/SKILL.md CHANGED
@@ -5,10 +5,10 @@ license: MIT
5
5
  compatibility: Requires bash, jq, and at least 2 AI CLI tools (agy, codex, vibe, etc.). macOS and Linux.
6
6
  metadata:
7
7
  author: matteoscurati
8
- version: 3.1.1
8
+ version: 3.3.0
9
9
  ---
10
10
 
11
- # AI Consultants v3.1.1 - AI Expert Panel
11
+ # AI Consultants v3.3.0 - AI Expert Panel
12
12
 
13
13
  **Coverage, not a single guess.** Convene a panel of AI "consultants" from different vendors for coding questions: they fan out in parallel and you get the *union* of what they collectively see — the risks, edge cases, and approaches a single model misses. Each consultant has a **configurable persona** that decorrelates its analysis.
14
14
 
@@ -45,6 +45,11 @@ Configuration (presets, strategies, features, personas, API keys) can be managed
45
45
 
46
46
  **CLI/API Mode**: Gemini, Codex, Claude, Mistral, Qwen, Grok, and MiniMax can switch between CLI and API mode via `*_USE_API` environment variables. Grok is CLI-first on Grok Build with `grok-4.5` and falls back to the xAI API when the CLI cannot run. Gemini auto-selects API mode when `GEMINI_API_KEY` is set (no `agy` install needed) and the CLI otherwise.
47
47
 
48
+ Grok and Kimi are compatible by capability rather than by CLI version. Before
49
+ dispatch, their adapters verify the required headless arguments, requested
50
+ model, and structured-output surface. Compatible version changes are accepted;
51
+ the observed version is recorded only as response provenance.
52
+
48
53
  **Self-Exclusion**: The invoking agent is automatically excluded from the panel. When invoked from Claude Code, Claude is excluded; when invoked from Codex CLI, Codex is excluded, etc.
49
54
 
50
55
  ## Requirements
@@ -1,4 +1,4 @@
1
- # Cost Rates - AI Consultants v3.1.1
1
+ # Cost Rates - AI Consultants v3.3.0
2
2
 
3
3
  This page documents the per-token rates used by the cost tracking system.
4
4
 
@@ -26,7 +26,7 @@ Since v2.5, models are organized into three tiers. Use `apply_model_tier()` or `
26
26
  | gpt-5.5 | Premium | $0.005 | Codex |
27
27
  | gpt-5.4 | Standard | $0.0025 | Codex |
28
28
  | gpt-5.4-nano | Economy | $0.0002 | Codex |
29
- | claude-opus-4-8 | Premium | $0.005 | Claude |
29
+ | claude-opus-5 | Premium | $0.005 | Claude |
30
30
  | claude-sonnet-4-6 | Standard | $0.003 | Claude |
31
31
  | claude-haiku-4-5 | Economy | $0.001 | Claude |
32
32
  | mistral-large-3 | Premium | $0.002 | Mistral |
@@ -59,7 +59,7 @@ Since v2.5, models are organized into three tiers. Use `apply_model_tier()` or `
59
59
  | gpt-5.5 | Premium | $0.030 | Codex |
60
60
  | gpt-5.4 | Standard | $0.015 | Codex |
61
61
  | gpt-5.4-nano | Economy | $0.00125 | Codex |
62
- | claude-opus-4-8 | Premium | $0.025 | Claude |
62
+ | claude-opus-5 | Premium | $0.025 | Claude |
63
63
  | claude-sonnet-4-6 | Standard | $0.015 | Claude |
64
64
  | claude-haiku-4-5 | Economy | $0.005 | Claude |
65
65
  | mistral-large-3 | Premium | $0.006 | Mistral |
@@ -37,6 +37,8 @@ The schema is defined in `scripts/lib/schema.json` following JSON Schema Draft-0
37
37
  "tokens_output": 300,
38
38
  "latency_ms": 2345,
39
39
  "model_version": "gemini-3.1-pro-preview",
40
+ "cli_version": "0.27.0",
41
+ "cli_compatibility": "capability-probed",
40
42
  "timestamp": "2024-01-14T12:34:56Z"
41
43
  }
42
44
  }
@@ -209,6 +211,8 @@ Present only if Multi-Agent Debate is enabled (round >= 2):
209
211
  | `tokens_output` | integer | Completion tokens, when the provider reported them (API mode only) |
210
212
  | `latency_ms` | integer | Response time in ms |
211
213
  | `model_version` | string | Exact model version |
214
+ | `cli_version` | string | Observed CLI version for provenance; never a compatibility gate |
215
+ | `cli_compatibility` | string | Capability-probe result: `capability-probed` or `incompatible` |
212
216
  | `timestamp` | string | ISO 8601 timestamp |
213
217
 
214
218
  ## Enum Values
@@ -1,10 +1,10 @@
1
1
  {
2
- "version": "3.1.1",
3
- "updated": "2026-07-16",
2
+ "version": "3.3.0",
3
+ "updated": "2026-07-25",
4
4
  "models": {
5
5
  "_comment_units": "All rates are USD per 1,000 tokens (per-1K). lib/costs.sh computes cost as (token_count / 1000) * rate, so every value here MUST be a per-1K rate = (provider $/1M price) / 1000. Do NOT paste $/MTok figures directly.",
6
- "_comment_premium": "Premium tier models - Jun 2026 (v2.17.0)",
7
- "claude-opus-4-8": {
6
+ "_comment_premium": "Premium tier models - Jul 2026",
7
+ "claude-opus-5": {
8
8
  "input": 0.005,
9
9
  "output": 0.025
10
10
  },
@@ -137,6 +137,10 @@
137
137
  "output": 0.001
138
138
  },
139
139
  "_comment_legacy": "Legacy models for backwards compatibility",
140
+ "claude-opus-4-8": {
141
+ "input": 0.005,
142
+ "output": 0.025
143
+ },
140
144
  "claude-opus-4-7": {
141
145
  "input": 0.005,
142
146
  "output": 0.025
@@ -331,8 +335,8 @@
331
335
  "qwen3.8-max-preview"
332
336
  ],
333
337
  "consultant_fallbacks": {
334
- "_comment": "Premium defaults - Updated Jun 2026 (v2.17.0)",
335
- "claude": "claude-opus-4-8",
338
+ "_comment": "Premium defaults - Updated Jul 2026",
339
+ "claude": "claude-opus-5",
336
340
  "gemini": "Gemini 3.1 Pro (High)",
337
341
  "codex": "gpt-5.5",
338
342
  "mistral": "mistral-large-3",
@@ -347,7 +351,7 @@
347
351
  "model_tiers": {
348
352
  "_comment": "Model tiers for apply_model_tier() function",
349
353
  "premium": {
350
- "claude": "claude-opus-4-8",
354
+ "claude": "claude-opus-5",
351
355
  "gemini": "Gemini 3.1 Pro (High)",
352
356
  "codex": "gpt-5.5",
353
357
  "mistral": "mistral-large-3",
@@ -0,0 +1,96 @@
1
+ # Release v3.2.0
2
+
3
+ **Date:** 2026-07-25
4
+ **Type:** Minor — Claude moves to Opus 5 with adaptive-thinking-safe transports and billing
5
+ **Previous:** v3.1.1
6
+
7
+ ## Highlights
8
+
9
+ - **Claude now uses `claude-opus-5` as its premium default** across direct configuration, model tiers, documentation, and cost tracking.
10
+ - **Claude CLI costs now come directly from the provider**, including adaptive thinking and cache charges that are absent from the visible answer.
11
+ - **Anthropic API thinking blocks are handled correctly**, and responses truncated by the shared thinking/output budget are rejected instead of synthesized as complete.
12
+ - **Existing generated Opus 4.8 defaults migrate to Opus 5**, while explicit model pins are recorded and preserved.
13
+ - **Opus 4.8 remains supported** for historical responses and intentional overrides.
14
+
15
+ ## What's New
16
+
17
+ ### Opus 5 premium default
18
+
19
+ The Claude consultant, premium/max/best quality tier, standalone adapter fallback, examples, and cost catalog now use the canonical `claude-opus-5` model ID. Standard and economy tiers remain on Claude Sonnet 4.6 and Haiku 4.5.
20
+
21
+ Opus 5 retains the previous $5 per million input tokens and $25 per million output tokens. The legacy Opus 4.8 rates remain available so historical sessions and pinned installations continue to be priced correctly.
22
+
23
+ ### Exact Claude CLI usage and cost
24
+
25
+ Claude CLI mode now requests its JSON result envelope. AI Consultants extracts:
26
+
27
+ - the visible answer from `.result`;
28
+ - measured input, cache, and output usage from `modelUsage`;
29
+ - the provider-reported `costUSD`.
30
+
31
+ The exact provider cost takes precedence over local token-rate reconstruction. This includes adaptive-thinking and cache charges even though those tokens do not appear in the visible answer.
32
+
33
+ ### Adaptive-thinking-safe API mode
34
+
35
+ Anthropic responses may place a thinking block before one or more visible text blocks. The parser now collects every visible `type=text` block instead of assuming the first content block contains the answer.
36
+
37
+ The new `CLAUDE_API_MAX_TOKENS` setting defaults to 16,384 tokens. Anthropic counts adaptive thinking and visible output against this shared budget. If the provider returns `stop_reason=max_tokens`, the consultation fails as truncated rather than entering synthesis as a complete response.
38
+
39
+ ### Managed configuration migration
40
+
41
+ Older `ai-consultants init` templates wrote the then-current Opus 4.8 default as an ordinary value. Running `ai-consultants configure` now recognizes that exact historical generated default, upgrades it to Opus 5, and records managed provenance.
42
+
43
+ Explicit model choices are written with an `# ai-consultants:pin` marker and survive subsequent configuration refreshes.
44
+
45
+ ## Breaking Changes
46
+
47
+ None — this release is backwards-compatible. Opus 4.8 remains selectable and priced.
48
+
49
+ If `CLAUDE_MODEL=claude-opus-4-8` was an intentional unmarked override rather than the generated default, reassert it once after upgrading:
50
+
51
+ ```bash
52
+ ai-consultants configure --set CLAUDE_MODEL=claude-opus-4-8
53
+ ```
54
+
55
+ ## Upgrade Guide
56
+
57
+ ```bash
58
+ # If installed via git clone
59
+ cd ~/.claude/skills/ai-consultants && git pull
60
+
61
+ # If installed via curl | bash
62
+ curl -fsSL https://raw.githubusercontent.com/matteoscurati/ai-consultants/main/scripts/install.sh | bash
63
+
64
+ # If used through npx
65
+ npx ai-consultants@latest "your question"
66
+ ```
67
+
68
+ Refresh persistent configuration so a historical generated Opus 4.8 value migrates:
69
+
70
+ ```bash
71
+ ai-consultants configure
72
+ ai-consultants doctor
73
+ ```
74
+
75
+ API users may override the shared thinking/output budget if needed:
76
+
77
+ ```bash
78
+ ai-consultants configure --set CLAUDE_API_MAX_TOKENS=32768
79
+ ```
80
+
81
+ ## Verification
82
+
83
+ - Full repository gate: 17/17 suites
84
+ - Core suite: 299/299 checks
85
+ - Claude CLI JSON/cost regression suite: 6/6 checks
86
+ - Anthropic transport regression suite: 48/48 checks
87
+ - Shellcheck and release-surface validation
88
+ - Live local Claude CLI JSON envelope verified with `claude-opus-5`
89
+
90
+ ## Commits
91
+
92
+ - `3963c3c` feat: upgrade Claude consultant to Opus 5
93
+
94
+ ## Contributors
95
+
96
+ - Matteo Scurati
@@ -0,0 +1,80 @@
1
+ # Release v3.3.0
2
+
3
+ **Date:** 2026-07-29
4
+ **Type:** Minor — capability-based Grok and Kimi CLI compatibility
5
+ **Previous:** v3.2.0
6
+
7
+ ## Highlights
8
+
9
+ - Grok and Kimi now prove CLI compatibility from the interface they actually need, not from a version string.
10
+ - Compatible future and vendor-custom CLI versions are accepted without a release-specific allowlist.
11
+ - Incomplete interfaces and missing requested models are rejected before a model request begins.
12
+ - Responses expose the observed CLI version as provenance alongside the capability-probe result.
13
+
14
+ ## What's New
15
+
16
+ ### Capability-based CLI compatibility
17
+
18
+ Before dispatch, the Grok adapter validates its complete headless argument
19
+ surface, strict sandbox controls, structured output, authentication state, and
20
+ the requested model. The probe runs inside an isolated environment and does not
21
+ send a consultation.
22
+
23
+ Kimi now validates prompt mode, `stream-json`, provider inventory, and the exact
24
+ configured model before dispatch. A CLI with a different version number remains
25
+ compatible when these capabilities are present; a CLI that cannot satisfy the
26
+ contract fails before a model call.
27
+
28
+ Grok's `--no-auto-update` flag remains optional. It is enabled when the installed
29
+ CLI advertises it and omitted otherwise, so a useful automation control does not
30
+ become an accidental version constraint.
31
+
32
+ ### Diagnostic provenance
33
+
34
+ Successful Grok and Kimi CLI responses now include:
35
+
36
+ | Field | Meaning |
37
+ |---|---|
38
+ | `metadata.cli_version` | The version reported by the CLI, retained for diagnosis and provenance |
39
+ | `metadata.cli_compatibility` | `capability-probed` when the required interface passed |
40
+
41
+ These fields are additive. Existing response consumers and non-CLI transports
42
+ do not need to provide them.
43
+
44
+ ### Verification
45
+
46
+ - Full repository gate: 17/17 suites
47
+ - Core suite: 299/299 checks
48
+ - Grok adapter suite: 32/32 checks
49
+ - Kimi adapter suite: 8/8 checks
50
+ - ShellCheck: green
51
+ - Installed Grok 0.2.114 and Kimi 0.27.0: no-model-call capability probes passed
52
+
53
+ ## Breaking Changes
54
+
55
+ None — this release is backwards-compatible. No configuration changes are
56
+ required.
57
+
58
+ An installed Grok or Kimi CLI that lacks the adapter's required command,
59
+ structured-output, or requested-model surface will now be rejected before
60
+ dispatch instead of failing later during a consultation.
61
+
62
+ ## Upgrade Guide
63
+
64
+ ```bash
65
+ # npx (no install)
66
+ npx ai-consultants@latest "your question"
67
+
68
+ # git clone install
69
+ cd ~/.claude/skills/ai-consultants && git pull
70
+
71
+ # curl | bash install
72
+ curl -fsSL https://raw.githubusercontent.com/matteoscurati/ai-consultants/main/scripts/install.sh | bash
73
+ ```
74
+
75
+ No configuration changes are required. If an adapter reports an incompatible
76
+ CLI, update that CLI and run `ai-consultants doctor` before retrying.
77
+
78
+ ## Commits
79
+
80
+ - `c7bad95` Harden CLI capability checks
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-consultants",
3
- "version": "3.1.1",
3
+ "version": "3.3.0",
4
4
  "description": "A harness for every question - a panel of up to 11 AI models that picks its own deliberation strategy per question (convergence, adversarial verification, tournament) and checks its work before answering",
5
5
  "bin": {
6
6
  "ai-consultants": "bin/ai-consultants"
@@ -54,6 +54,11 @@ ai-consultants configure --dry-run
54
54
  Use the hidden interactive credential prompts (or exported environment variables)
55
55
  for API keys; command-line arguments may be visible in shell history and process
56
56
  inspection.
57
+ Model defaults carry an `# ai-consultants:default` marker. On rewrite,
58
+ `configure` upgrades the historical unmarked
59
+ `CLAUDE_MODEL=claude-opus-4-8` generated default to Opus 5. An explicit model
60
+ override supplied with `--set` is stored with `# ai-consultants:pin`, so
61
+ `--set CLAUDE_MODEL=claude-opus-4-8` keeps the legacy model intentionally.
57
62
  The exhaustive parameter contract is [`.env.example`](../.env.example); the
58
63
  configurator derives its accepted keys from that template, while
59
64
  [`scripts/config.sh`](../scripts/config.sh) remains the runtime source of truth.
@@ -235,7 +240,8 @@ ENABLE_DEEPSEEK=false
235
240
  ```bash
236
241
  GEMINI_MODEL=Gemini 3.1 Pro (High) # agy CLI display name; API mode uses GEMINI_API_MODEL
237
242
  CODEX_MODEL=gpt-5.5
238
- CLAUDE_MODEL=claude-opus-4-8
243
+ CLAUDE_MODEL=claude-opus-5
244
+ CLAUDE_API_MAX_TOKENS=16384 # API only: adaptive thinking + visible output
239
245
  MISTRAL_MODEL=mistral-large-3
240
246
  CURSOR_MODEL=composer-2.5
241
247
  KIMI_MODEL=kimi-code/k3
@@ -246,16 +252,26 @@ DEEPSEEK_MODEL=deepseek-v4-pro
246
252
  MINIMAX_MODEL=MiniMax-M2.7
247
253
  ```
248
254
 
255
+ Anthropic counts adaptive thinking and visible response tokens against the same
256
+ `CLAUDE_API_MAX_TOKENS` limit. The default is 16,384; a response that reaches
257
+ that limit is rejected as truncated instead of entering synthesis as a complete
258
+ answer.
259
+
249
260
  `GROK_MODEL` is passed explicitly to `grok -m`. The default transport is the
250
261
  headless Grok Build CLI using a private `--prompt-file` inside an isolated,
251
262
  tool-free strict sandbox. If the CLI is missing, cannot execute, or has no
252
263
  usable authentication, the same query and model fall back to `GROK_API_URL`
253
264
  when `GROK_API_KEY` is set. Failures after a request launches (including
254
265
  timeouts, model errors, and empty output) are returned without API fallback.
255
- Set `GROK_USE_API=true` only to force the API path.
266
+ Set `GROK_USE_API=true` only to force the API path. The adapter accepts any CLI
267
+ version that exposes the complete required headless interface and requested
268
+ model; `metadata.cli_version` is provenance, not a version pin.
256
269
 
257
270
  `KIMI_MODEL` is passed directly to `kimi --model`, so `kimi-code/k3` overrides
258
- any older default stored in the user's Kimi CLI configuration.
271
+ any older default stored in the user's Kimi CLI configuration. Kimi CLI
272
+ compatibility is also capability-probed: prompt mode, `stream-json`, provider
273
+ inventory, and the requested model must be present. No numeric CLI version is
274
+ used as a gate.
259
275
 
260
276
  ## Model Quality Tiers (v2.5)
261
277
 
package/scripts/config.sh CHANGED
@@ -271,7 +271,8 @@ KIMI_CMD="${KIMI_CMD:-kimi}"
271
271
  # CLAUDE CONFIGURATION - The Synthesizer (v2.2)
272
272
  # =============================================================================
273
273
 
274
- CLAUDE_MODEL="${CLAUDE_MODEL:-claude-opus-4-8}"
274
+ CLAUDE_MODEL="${CLAUDE_MODEL:-claude-opus-5}"
275
+ CLAUDE_API_MAX_TOKENS="${CLAUDE_API_MAX_TOKENS:-16384}"
275
276
  CLAUDE_TIMEOUT_SECONDS="${CLAUDE_TIMEOUT:-240}"
276
277
  CLAUDE_CMD="${CLAUDE_CMD:-claude}"
277
278
 
@@ -581,7 +582,7 @@ get_model_for_tier() {
581
582
  case "$tier" in
582
583
  premium|max|best)
583
584
  case "$consultant" in
584
- claude) echo "claude-opus-4-8" ;;
585
+ claude) echo "claude-opus-5" ;;
585
586
  gemini) echo "Gemini 3.1 Pro (High)" ;;
586
587
  codex) echo "gpt-5.5" ;;
587
588
  mistral) echo "mistral-large-3" ;;
@@ -778,4 +779,4 @@ EOF
778
779
  # VERSION
779
780
  # =============================================================================
780
781
 
781
- AI_CONSULTANTS_VERSION="3.1.1"
782
+ AI_CONSULTANTS_VERSION="3.3.0"
@@ -181,6 +181,25 @@ set_value() {
181
181
  }
182
182
 
183
183
  AUTO_MARKER="# ai-consultants:auto"
184
+ DEFAULT_MARKER="# ai-consultants:default"
185
+ PIN_MARKER="# ai-consultants:pin"
186
+
187
+ has_value_marker() {
188
+ local file="$1" key="$2" marker="$3" line normalized
189
+ [[ -r "$file" ]] || return 1
190
+ while IFS= read -r line || [[ -n "$line" ]]; do
191
+ line="${line%$'\r'}"
192
+ [[ "$line" =~ ^[[:space:]]*# ]] && continue
193
+ normalized="${line#"${line%%[![:space:]]*}"}"
194
+ if [[ "$normalized" =~ ^export[[:space:]]+ ]]; then
195
+ normalized="${normalized#export}"
196
+ normalized="${normalized#"${normalized%%[![:space:]]*}"}"
197
+ fi
198
+ [[ "$normalized" == "$key="* ]] || continue
199
+ [[ "$normalized" == *"$marker"* ]] && return 0
200
+ done < "$file"
201
+ return 1
202
+ }
184
203
 
185
204
  is_auto_managed_value() {
186
205
  local file="$1" key="$2" line normalized raw="" seen=false
@@ -239,13 +258,33 @@ set_auto_value() {
239
258
  # Merge existing persistent values first, then current environment overrides.
240
259
  while IFS= read -r key; do
241
260
  if [[ -f "$OUTPUT_FILE" ]] && value=$(read_value "$OUTPUT_FILE" "$key"); then
242
- set_value "$key" "$value"
261
+ if has_value_marker "$OUTPUT_FILE" "$key" "$DEFAULT_MARKER"; then
262
+ set_value "$key" "$value $DEFAULT_MARKER"
263
+ elif has_value_marker "$OUTPUT_FILE" "$key" "$PIN_MARKER"; then
264
+ set_value "$key" "$value $PIN_MARKER"
265
+ else
266
+ set_value "$key" "$value"
267
+ fi
243
268
  fi
244
269
  if [[ -n "${!key+x}" ]]; then
245
- set_value "$key" "${!key}"
270
+ if [[ "$key" == *_MODEL ]]; then
271
+ set_value "$key" "${!key} $PIN_MARKER"
272
+ else
273
+ set_value "$key" "${!key}"
274
+ fi
246
275
  fi
247
276
  done < <(list_parameters)
248
277
 
278
+ # Before model defaults had provenance markers, `init` copied the then-current
279
+ # Claude default as an ordinary-looking value. Refresh that exact historical
280
+ # default when configure rewrites the file. A real legacy pin is preserved by
281
+ # `--set CLAUDE_MODEL=claude-opus-4-8`, which records the pin marker.
282
+ if [[ "$(read_value "$WORK_FILE" CLAUDE_MODEL 2>/dev/null || true)" == "claude-opus-4-8" ]] \
283
+ && ! has_value_marker "$WORK_FILE" CLAUDE_MODEL "$PIN_MARKER"; then
284
+ set_value CLAUDE_MODEL "claude-opus-5 $DEFAULT_MARKER"
285
+ echo "Migrated managed Claude default: claude-opus-4-8 -> claude-opus-5" >&2
286
+ fi
287
+
249
288
  configure_cli_only() {
250
289
  local enable_key="$1" cmd_key="$2" cmd enabled=false
251
290
  cmd=$(effective_input_value "$cmd_key")
@@ -327,7 +366,11 @@ prompt_value() {
327
366
  else
328
367
  read -r -p "$key [$current]: " reply
329
368
  if [[ -n "$reply" ]]; then
330
- set_value "$key" "$reply"
369
+ if [[ "$key" == *_MODEL ]]; then
370
+ set_value "$key" "$reply $PIN_MARKER"
371
+ else
372
+ set_value "$key" "$reply"
373
+ fi
331
374
  fi
332
375
  fi
333
376
  }
@@ -368,7 +411,11 @@ fi
368
411
 
369
412
  # Explicit command-line overrides have final precedence.
370
413
  for ((i=0; i<${#SET_KEYS[@]}; i++)); do
371
- set_value "${SET_KEYS[$i]}" "${SET_VALUES[$i]}"
414
+ if [[ "${SET_KEYS[$i]}" == *_MODEL ]]; then
415
+ set_value "${SET_KEYS[$i]}" "${SET_VALUES[$i]} $PIN_MARKER"
416
+ else
417
+ set_value "${SET_KEYS[$i]}" "${SET_VALUES[$i]}"
418
+ fi
372
419
  done
373
420
 
374
421
  enabled_count=0
@@ -249,7 +249,7 @@ build_openai_request() {
249
249
  build_anthropic_request() {
250
250
  local prompt="$1"
251
251
  local model="${2:-claude-sonnet-4-6}"
252
- local max_tokens="${3:-4096}"
252
+ local max_tokens="${3:-16384}"
253
253
 
254
254
  jq -n \
255
255
  --arg model "$model" \
@@ -327,7 +327,11 @@ parse_anthropic_response() {
327
327
  local response="$1"
328
328
 
329
329
  local content
330
- content=$(echo "$response" | jq -r '.content[0].text // empty' 2>/dev/null)
330
+ # Opus 5 may put a thinking block before the visible text block. Select
331
+ # every text block instead of assuming content[0] is user-visible output.
332
+ content=$(echo "$response" | jq -r \
333
+ '[.content[]? | select(.type == "text") | (.text // "")] | join("\n")' \
334
+ 2>/dev/null)
331
335
 
332
336
  if [[ -n "$content" && "$content" != "null" ]]; then
333
337
  echo "$content"
@@ -94,7 +94,14 @@ run_api_mode_query() {
94
94
  if [[ -n "$effort" ]]; then
95
95
  log_warn "[$consultant_name] $effort_var is not supported on the 'anthropic' wire format and is ignored."
96
96
  fi
97
- request_body=$(build_anthropic_request "$query" "$model")
97
+ local max_tokens_var api_max_tokens
98
+ max_tokens_var="$(to_upper "$consultant_name")_API_MAX_TOKENS"
99
+ api_max_tokens="${!max_tokens_var:-16384}"
100
+ if ! [[ "$api_max_tokens" =~ ^[1-9][0-9]*$ ]]; then
101
+ log_error "[$consultant_name] $max_tokens_var must be a positive integer (got: $api_max_tokens)"
102
+ return 1
103
+ fi
104
+ request_body=$(build_anthropic_request "$query" "$model" "$api_max_tokens")
98
105
  auth_style="anthropic"
99
106
  ;;
100
107
  qwen)
@@ -134,6 +141,16 @@ run_api_mode_query() {
134
141
  local raw_response
135
142
  raw_response=$(cat "$temp_response")
136
143
 
144
+ # Anthropic counts adaptive thinking and visible output against the same
145
+ # max_tokens budget. A max_tokens stop is therefore an incomplete answer,
146
+ # not a successful partial response suitable for synthesis.
147
+ if [[ "$api_format" == "anthropic" ]] \
148
+ && [[ "$(jq -r '.stop_reason // empty' <<<"$raw_response" 2>/dev/null)" == "max_tokens" ]]; then
149
+ log_error "[$consultant_name] API response was truncated at ${api_max_tokens:-the configured} max tokens"
150
+ rm -f "$temp_response"
151
+ return 1
152
+ fi
153
+
137
154
  local parsed_content
138
155
  case "$api_format" in
139
156
  google_ai)
@@ -902,6 +902,7 @@ resolve_response_tokens() {
902
902
 
903
903
  # Build metadata JSON for responses
904
904
  # Usage: build_response_metadata <latency_ms> <model> [error_msg] [tokens] [source]
905
+ # [tokens_input] [tokens_output] [provider_cost_usd]
905
906
  #
906
907
  # tokens_used was hardcoded to 0 until v2.25, which made every session's cost
907
908
  # report read $0.00 regardless of what was actually spent (calculate_session_cost
@@ -915,6 +916,7 @@ build_response_metadata() {
915
916
  local source="${5:-unknown}"
916
917
  local tokens_in="${6:-}"
917
918
  local tokens_out="${7:-}"
919
+ local provider_cost="${8:-}"
918
920
 
919
921
  jq -n \
920
922
  --argjson latency "$latency" \
@@ -925,9 +927,11 @@ build_response_metadata() {
925
927
  --arg source "$source" \
926
928
  --arg t_in "$tokens_in" \
927
929
  --arg t_out "$tokens_out" \
930
+ --arg provider_cost "$provider_cost" \
928
931
  '{tokens_used: $tokens, tokens_source: $source, latency_ms: $latency, model_version: $model, timestamp: $timestamp}
929
932
  + (if $t_in != "" then {tokens_input: ($t_in | tonumber)} else {} end)
930
933
  + (if $t_out != "" then {tokens_output: ($t_out | tonumber)} else {} end)
934
+ + (if $provider_cost != "" then {provider_cost_usd: ($provider_cost | tonumber)} else {} end)
931
935
  + (if $error != "" then {error: $error} else {} end)'
932
936
  }
933
937
 
@@ -943,15 +947,14 @@ build_structured_response() {
943
947
  local tokens_source="${7:-unknown}"
944
948
  local tokens_in="${8:-}"
945
949
  local tokens_out="${9:-}"
946
- local tokens_in="${8:-}"
947
- local tokens_out="${9:-}"
950
+ local provider_cost="${10:-}"
948
951
 
949
952
  jq -n \
950
953
  --arg consultant "$consultant" \
951
954
  --arg model "$model" \
952
955
  --arg persona "$persona" \
953
956
  --argjson inner "$inner_json" \
954
- --argjson metadata "$(build_response_metadata "$latency" "$model" "" "$tokens" "$tokens_source" "$tokens_in" "$tokens_out")" \
957
+ --argjson metadata "$(build_response_metadata "$latency" "$model" "" "$tokens" "$tokens_source" "$tokens_in" "$tokens_out" "$provider_cost")" \
955
958
  '{consultant: $consultant, model: $model, persona: $persona, response: $inner.response, confidence: $inner.confidence, metadata: $metadata}'
956
959
  }
957
960
 
@@ -967,13 +970,14 @@ build_fallback_response() {
967
970
  local tokens_source="${7:-unknown}"
968
971
  local tokens_in="${8:-}"
969
972
  local tokens_out="${9:-}"
973
+ local provider_cost="${10:-}"
970
974
 
971
975
  jq -n \
972
976
  --arg consultant "$consultant" \
973
977
  --arg model "$model" \
974
978
  --arg persona "$persona" \
975
979
  --arg response "$response_text" \
976
- --argjson metadata "$(build_response_metadata "$latency" "$model" "" "$tokens" "$tokens_source" "$tokens_in" "$tokens_out")" \
980
+ --argjson metadata "$(build_response_metadata "$latency" "$model" "" "$tokens" "$tokens_source" "$tokens_in" "$tokens_out" "$provider_cost")" \
977
981
  '{consultant: $consultant, model: $model, persona: $persona,
978
982
  response: {summary: "Unstructured response - see detailed", detailed: $response, approach: "unknown", pros: [], cons: [], caveats: ["Unstructured output from consultant"]},
979
983
  confidence: {score: 5, reasoning: "Confidence not provided by consultant", uncertainty_factors: ["Non-standard response format"]},
@@ -306,11 +306,8 @@ _billable_response_files() {
306
306
  # Calculate total session cost from responses
307
307
  # Usage: calculate_session_cost <responses_dir>
308
308
  #
309
- # Prefers the provider's own prompt/completion split when it was recorded
310
- # (API mode). The 60/40 guess is a last resort for CLI mode, which has no
311
- # split to record: consultations are large-context/short-reply, so applying it
312
- # to a measured total materially overstates cost when output rates exceed
313
- # input rates.
309
+ # Prefers an exact provider cost when recorded (Claude CLI), then the provider's
310
+ # prompt/completion split (API mode), and only then the legacy 60/40 guess.
314
311
  calculate_session_cost() {
315
312
  local responses_dir="$1"
316
313
  local total_cost=0
@@ -318,6 +315,13 @@ calculate_session_cost() {
318
315
 
319
316
  while IFS= read -r f; do
320
317
  [[ -n "$f" ]] || continue
318
+ local provider_cost
319
+ provider_cost=$(jq -r '.metadata.provider_cost_usd // empty' "$f" 2>/dev/null)
320
+ if [[ "$provider_cost" =~ ^[0-9]+([.][0-9]+)?$ ]]; then
321
+ total_cost=$(echo "scale=6; $total_cost + $provider_cost" | bc)
322
+ continue
323
+ fi
324
+
321
325
  local model input_tokens output_tokens
322
326
  model=$(jq -r '.model // "default"' "$f" 2>/dev/null)
323
327
  input_tokens=$(jq -r '.metadata.tokens_input // empty' "$f" 2>/dev/null)
@@ -338,6 +342,11 @@ calculate_session_cost() {
338
342
  total_cost=$(echo "scale=6; $total_cost + $cost" | bc)
339
343
  done < <(_billable_response_files "$responses_dir")
340
344
 
345
+ # Provider-reported costs may carry fewer decimal places than locally
346
+ # estimated values. Normalize the public result to the existing 6-place
347
+ # contract without changing its numeric value.
348
+ total_cost=$(echo "scale=6; $total_cost / 1" | bc)
349
+
341
350
  # bc drops the leading zero on sub-1 values (".115000"); restore it as
342
351
  # estimate_query_cost does, so callers can compare numerically.
343
352
  [[ "$total_cost" == .* ]] && total_cost="0$total_cost"
@@ -351,8 +360,9 @@ calculate_session_cost() {
351
360
  #
352
361
  # Two things can make the number less than it looks:
353
362
  # - credit-billed models contribute 0 because they have no per-token price;
354
- # - CLI-mode consultants have no provider token count, so their share is a
355
- # 4-chars-per-token approximation (metadata.tokens_source = "estimated").
363
+ # - CLI-mode consultants without provider usage have a 4-chars-per-token
364
+ # approximation (metadata.tokens_source = "estimated"). Claude CLI is the
365
+ # exception: its JSON envelope supplies measured usage and exact cost.
356
366
  # Both are stated rather than folded silently into a confident-looking total.
357
367
  #
358
368
  # Rescans the directory rather than reading state left by
@@ -167,6 +167,15 @@
167
167
  "type": "string",
168
168
  "description": "Exact model version string"
169
169
  },
170
+ "cli_version": {
171
+ "type": "string",
172
+ "description": "Observed consultant CLI version for provenance only; compatibility is not decided from this string."
173
+ },
174
+ "cli_compatibility": {
175
+ "type": "string",
176
+ "enum": ["capability-probed", "incompatible"],
177
+ "description": "Result of checking the CLI's required command, model, and structured-output capabilities before dispatch."
178
+ },
170
179
  "timestamp": {
171
180
  "type": "string",
172
181
  "format": "date-time",
@@ -4,9 +4,10 @@
4
4
  # Usage: ./query_claude.sh "question" [context_file] [output_file]
5
5
  #
6
6
  # Environment variables:
7
- # CLAUDE_MODEL - Model to use (default: claude-opus-4-8)
7
+ # CLAUDE_MODEL - Model to use (default: claude-opus-5)
8
8
  # CLAUDE_TIMEOUT - Timeout in seconds (default: 240)
9
9
  # CLAUDE_USE_API - Use API mode instead of CLI (default: false)
10
+ # CLAUDE_API_MAX_TOKENS - API thinking + visible-output budget (default: 16384)
10
11
  # ANTHROPIC_API_KEY - API key for API mode
11
12
  # ENABLE_PERSONA - Enable "The Synthesizer" persona (default: true)
12
13
 
@@ -25,7 +26,7 @@ OUTPUT_FILE="${3:-/tmp/claude_response.json}"
25
26
  ENABLE_PERSONA="${ENABLE_PERSONA:-true}"
26
27
  CONSULTANT_NAME="Claude"
27
28
  CLAUDE_CMD="${CLAUDE_CMD:-claude}"
28
- MODEL_USED="${CLAUDE_MODEL:-claude-opus-4-8}"
29
+ MODEL_USED="${CLAUDE_MODEL:-claude-opus-5}"
29
30
 
30
31
  # --- Build query ---
31
32
  FULL_QUERY=$(build_full_query "$QUERY" "$CONTEXT_FILE")
@@ -69,12 +70,13 @@ else
69
70
  log_api_mode_status "claude"
70
71
  check_command "$CLAUDE_CMD" "Claude CLI" "Visit https://docs.anthropic.com/en/docs/claude-code" || exit 1
71
72
 
72
- # Claude CLI uses --print for non-interactive mode
73
+ # JSON output exposes provider-measured usage and cost. In particular,
74
+ # output_tokens includes adaptive thinking that is absent from .result.
73
75
  echo "$FULL_QUERY" | run_query \
74
76
  "Claude" \
75
77
  "$TEMP_OUTPUT" \
76
78
  "$CLAUDE_TIMEOUT_SECONDS" \
77
- "$CLAUDE_CMD" --print --model "$MODEL_USED"
79
+ "$CLAUDE_CMD" --print --model "$MODEL_USED" --output-format json
78
80
 
79
81
  exit_code=$?
80
82
  fi
@@ -88,16 +90,45 @@ PERSONA_NAME=$(get_persona_name "$CONSULTANT_NAME")
88
90
 
89
91
  # --- Post-processing: wrap in full schema using shared helpers ---
90
92
  if [[ $exit_code -eq 0 && -f "$TEMP_OUTPUT" && -s "$TEMP_OUTPUT" ]]; then
91
- RAW_RESPONSE=$(cat "$TEMP_OUTPUT")
92
- read -r _TOK _TOK_SRC _TOK_IN _TOK_OUT <<< "$(resolve_response_tokens "$FULL_QUERY" "$RAW_RESPONSE")"
93
+ PROVIDER_COST=""
94
+ if ! is_api_mode "claude"; then
95
+ CLI_ENVELOPE=$(cat "$TEMP_OUTPUT")
96
+ if ! echo "$CLI_ENVELOPE" | jq -e \
97
+ '.type == "result" and (.result | type == "string")' >/dev/null 2>&1; then
98
+ rm -f "$TEMP_OUTPUT"
99
+ build_error_response "$CONSULTANT_NAME" "$MODEL_USED" "$PERSONA_NAME" \
100
+ "Claude CLI returned an invalid JSON result envelope" "$LATENCY_MS" > "$OUTPUT_FILE"
101
+ cat "$OUTPUT_FILE"
102
+ exit 1
103
+ fi
104
+ RAW_RESPONSE=$(echo "$CLI_ENVELOPE" | jq -r '.result')
105
+ _TOK_IN=$(echo "$CLI_ENVELOPE" | jq -r \
106
+ '([.modelUsage[]?
107
+ | (.inputTokens // 0)
108
+ + (.cacheCreationInputTokens // 0)
109
+ + (.cacheReadInputTokens // 0)] | add)
110
+ // ((.usage.input_tokens // 0)
111
+ + (.usage.cache_creation_input_tokens // 0)
112
+ + (.usage.cache_read_input_tokens // 0))')
113
+ _TOK_OUT=$(echo "$CLI_ENVELOPE" | jq -r \
114
+ '([.modelUsage[]? | (.outputTokens // 0)] | add)
115
+ // (.usage.output_tokens // 0)')
116
+ _TOK=$((_TOK_IN + _TOK_OUT))
117
+ _TOK_SRC="measured"
118
+ PROVIDER_COST=$(echo "$CLI_ENVELOPE" | jq -r \
119
+ '[.modelUsage[]?.costUSD | numbers] | add // empty')
120
+ else
121
+ RAW_RESPONSE=$(cat "$TEMP_OUTPUT")
122
+ read -r _TOK _TOK_SRC _TOK_IN _TOK_OUT <<< "$(resolve_response_tokens "$FULL_QUERY" "$RAW_RESPONSE")"
123
+ fi
93
124
  clear_api_token_split
94
125
  rm -f "$TEMP_OUTPUT"
95
126
 
96
127
  # Try to parse as structured JSON
97
128
  if echo "$RAW_RESPONSE" | jq -e '.response.summary' > /dev/null 2>&1; then
98
- build_structured_response "$CONSULTANT_NAME" "$MODEL_USED" "$PERSONA_NAME" "$RAW_RESPONSE" "$LATENCY_MS" "$_TOK" "$_TOK_SRC" "$_TOK_IN" "$_TOK_OUT" > "$OUTPUT_FILE"
129
+ build_structured_response "$CONSULTANT_NAME" "$MODEL_USED" "$PERSONA_NAME" "$RAW_RESPONSE" "$LATENCY_MS" "$_TOK" "$_TOK_SRC" "$_TOK_IN" "$_TOK_OUT" "$PROVIDER_COST" > "$OUTPUT_FILE"
99
130
  else
100
- build_fallback_response "$CONSULTANT_NAME" "$MODEL_USED" "$PERSONA_NAME" "$RAW_RESPONSE" "$LATENCY_MS" "$_TOK" "$_TOK_SRC" "$_TOK_IN" "$_TOK_OUT" > "$OUTPUT_FILE"
131
+ build_fallback_response "$CONSULTANT_NAME" "$MODEL_USED" "$PERSONA_NAME" "$RAW_RESPONSE" "$LATENCY_MS" "$_TOK" "$_TOK_SRC" "$_TOK_IN" "$_TOK_OUT" "$PROVIDER_COST" > "$OUTPUT_FILE"
101
132
  fi
102
133
  else
103
134
  rm -f "$TEMP_OUTPUT"
@@ -36,6 +36,9 @@ START_TIME=$(get_timestamp_ms)
36
36
  TEMP_OUTPUT=$(mktemp)
37
37
  GROK_RUNTIME_DIR=""
38
38
  TRANSPORT="cli"
39
+ GROK_CLI_VERSION=""
40
+ GROK_CLI_COMPATIBILITY=""
41
+ GROK_SUPPORTS_NO_AUTO_UPDATE=false
39
42
  exit_code=1
40
43
 
41
44
  cleanup() {
@@ -67,6 +70,93 @@ run_grok_api() {
67
70
  fi
68
71
  }
69
72
 
73
+ observe_grok_cli_version() {
74
+ local version
75
+ version=$("$GROK_CMD" --version 2>/dev/null | head -1 || true)
76
+ if [[ "$version" =~ ^grok[[:space:]]+([^[:space:]]+) ]]; then
77
+ printf '%s\n' "${BASH_REMATCH[1]}"
78
+ elif [[ -n "$version" ]]; then
79
+ printf '%s\n' "$version"
80
+ else
81
+ printf 'unknown\n'
82
+ fi
83
+ }
84
+
85
+ grok_cli_supports_required_interface() {
86
+ local probe_home="$1"
87
+ local probe_grok_home="$2"
88
+ local probe_workspace="$3"
89
+ local help flag
90
+
91
+ help=$(env HOME="$probe_home" GROK_HOME="$probe_grok_home" \
92
+ "$GROK_CMD" --help 2>&1 || true)
93
+ for flag in \
94
+ --prompt-file --model --cwd --output-format --no-plan --no-subagents \
95
+ --no-memory --disable-web-search --max-turns --permission-mode \
96
+ --sandbox --tools --deny --verbatim; do
97
+ grep -q -- "$flag" <<< "$help" || return 1
98
+ done
99
+ grep -Eq '^[[:space:]]+models([[:space:]]|$)' <<< "$help" || return 1
100
+
101
+ # Exercise the complete headless argument surface under --help. This checks
102
+ # parser compatibility without starting a session or sending a prompt.
103
+ env HOME="$probe_home" GROK_HOME="$probe_grok_home" \
104
+ "$GROK_CMD" \
105
+ --prompt-file /dev/null \
106
+ -m "$GROK_MODEL" \
107
+ --cwd "$probe_workspace" \
108
+ --output-format plain \
109
+ --no-plan \
110
+ --no-subagents \
111
+ --no-memory \
112
+ --disable-web-search \
113
+ --max-turns 1 \
114
+ --permission-mode dontAsk \
115
+ --sandbox strict \
116
+ --tools "" \
117
+ --deny Bash \
118
+ --deny Edit \
119
+ --deny Read \
120
+ --deny Grep \
121
+ --deny MCPTool \
122
+ --deny WebFetch \
123
+ --deny WebSearch \
124
+ --verbatim \
125
+ --help >/dev/null 2>&1 || return 1
126
+
127
+ if grep -q -- '--no-auto-update' <<< "$help"; then
128
+ GROK_SUPPORTS_NO_AUTO_UPDATE=true
129
+ else
130
+ GROK_SUPPORTS_NO_AUTO_UPDATE=false
131
+ fi
132
+ GROK_CLI_COMPATIBILITY="capability-probed"
133
+ }
134
+
135
+ grok_cli_exposes_requested_model() {
136
+ local isolated_home="$1"
137
+ local isolated_grok_home="$2"
138
+ local models
139
+
140
+ if ! models=$(env HOME="$isolated_home" GROK_HOME="$isolated_grok_home" \
141
+ "$GROK_CMD" models 2>&1); then
142
+ printf '%s\n' "$models"
143
+ return 1
144
+ fi
145
+ grep -Fq "You are logged in with grok.com." <<< "$models" || {
146
+ printf '%s\n' "$models"
147
+ return 1
148
+ }
149
+ awk -v requested="$GROK_MODEL" '
150
+ {
151
+ line = $0
152
+ sub(/^[[:space:]*]+/, "", line)
153
+ split(line, fields, /[[:space:]]+/)
154
+ if (fields[1] == requested) found = 1
155
+ }
156
+ END { exit(found ? 0 : 1) }
157
+ ' <<< "$models"
158
+ }
159
+
70
160
  grok_cli_is_unavailable() {
71
161
  local cli_exit_code="$1"
72
162
  local error_file="$2"
@@ -85,7 +175,7 @@ grok_cli_is_unavailable() {
85
175
  # model errors, timeouts, empty output, or other post-launch failures here;
86
176
  # those must surface instead of silently creating a billable API request.
87
177
  grep -Eiq \
88
- 'not authenticated|authentication( is)? required|authentication failed|unauthorized|run .?grok login|please .*log ?in|no (valid )?(credentials|access token)|missing .*credential|token.*expired|(^|[^0-9])401([^0-9]|$)|permission denied|cannot execute|exec format error|no such file or directory|failed to (start|launch|spawn)|could not (start|launch|spawn)' \
178
+ 'not authenticated|authentication( is)? required|authentication failed|unauthorized|run .?grok login|please .*log ?in|no (valid )?(credentials|access token)|missing .*credential|token.*expired|(^|[^0-9])401([^0-9]|$)|permission denied|cannot execute|exec format error|no such file or directory|failed to (start|launch|spawn)|could not (start|launch|spawn)|lacks the required capabilities|does not expose the requested model' \
89
179
  "$error_file"
90
180
  }
91
181
 
@@ -128,51 +218,66 @@ else
128
218
  fi
129
219
  fi
130
220
 
131
- # Official Grok Build headless contract:
132
- # --prompt-file avoids argv size/process-list exposure, -m pins the
133
- # model, and plain output contains the final assistant text.
134
- # dontAsk + strict sandbox make every unapproved tool fail closed.
135
- GROK_ARGS=(
136
- env
137
- HOME="$isolated_home"
138
- GROK_HOME="$isolated_grok_home"
139
- "$GROK_CMD"
140
- --prompt-file "$prompt_file"
141
- -m "$GROK_MODEL"
142
- --cwd "$isolated_workspace"
143
- --output-format plain
144
- --no-plan
145
- --no-subagents
146
- --no-memory
147
- --disable-web-search
148
- --max-turns 1
149
- --permission-mode dontAsk
150
- --sandbox strict
151
- --tools ""
152
- --deny Bash
153
- --deny Edit
154
- --deny Read
155
- --deny Grep
156
- --deny MCPTool
157
- --deny WebFetch
158
- --deny WebSearch
159
- --verbatim
160
- )
161
-
162
- # Newer Grok Build releases document --no-auto-update for automation;
163
- # keep compatibility with older installed builds that do not expose it.
164
- if "$GROK_CMD" --help 2>&1 | grep -q -- '--no-auto-update'; then
165
- GROK_ARGS+=("--no-auto-update")
166
- fi
167
-
168
- if run_query \
169
- "$CONSULTANT_NAME" \
170
- "$TEMP_OUTPUT" \
171
- "$GROK_TIMEOUT_SECONDS" \
172
- "${GROK_ARGS[@]}" </dev/null; then
173
- exit_code=0
221
+ GROK_CLI_VERSION=$(observe_grok_cli_version)
222
+ if ! grok_cli_supports_required_interface \
223
+ "$isolated_home" "$isolated_grok_home" "$isolated_workspace"; then
224
+ GROK_CLI_COMPATIBILITY="incompatible"
225
+ printf 'Grok Build CLI lacks the required capabilities\n' > "${TEMP_OUTPUT}.err"
226
+ log_warn "[$CONSULTANT_NAME] Grok Build CLI is capability-incompatible (reported version: $GROK_CLI_VERSION)"
227
+ exit_code=69
228
+ elif ! grok_cli_exposes_requested_model \
229
+ "$isolated_home" "$isolated_grok_home" > "${TEMP_OUTPUT}.err"; then
230
+ printf 'Grok Build CLI does not expose the requested model %s\n' \
231
+ "$GROK_MODEL" >> "${TEMP_OUTPUT}.err"
232
+ log_warn "[$CONSULTANT_NAME] Grok Build CLI does not expose $GROK_MODEL"
233
+ exit_code=69
174
234
  else
175
- exit_code=$?
235
+ # Official Grok Build headless contract:
236
+ # --prompt-file avoids argv size/process-list exposure, -m pins the
237
+ # model, and plain output contains the final assistant text.
238
+ # dontAsk + strict sandbox make every unapproved tool fail closed.
239
+ GROK_ARGS=(
240
+ env
241
+ HOME="$isolated_home"
242
+ GROK_HOME="$isolated_grok_home"
243
+ "$GROK_CMD"
244
+ --prompt-file "$prompt_file"
245
+ -m "$GROK_MODEL"
246
+ --cwd "$isolated_workspace"
247
+ --output-format plain
248
+ --no-plan
249
+ --no-subagents
250
+ --no-memory
251
+ --disable-web-search
252
+ --max-turns 1
253
+ --permission-mode dontAsk
254
+ --sandbox strict
255
+ --tools ""
256
+ --deny Bash
257
+ --deny Edit
258
+ --deny Read
259
+ --deny Grep
260
+ --deny MCPTool
261
+ --deny WebFetch
262
+ --deny WebSearch
263
+ --verbatim
264
+ )
265
+
266
+ # --no-auto-update is a useful optional capability, not a version
267
+ # requirement. Compatible older or vendor-custom builds may omit it.
268
+ if [[ "$GROK_SUPPORTS_NO_AUTO_UPDATE" == "true" ]]; then
269
+ GROK_ARGS+=("--no-auto-update")
270
+ fi
271
+
272
+ if run_query \
273
+ "$CONSULTANT_NAME" \
274
+ "$TEMP_OUTPUT" \
275
+ "$GROK_TIMEOUT_SECONDS" \
276
+ "${GROK_ARGS[@]}" </dev/null; then
277
+ exit_code=0
278
+ else
279
+ exit_code=$?
280
+ fi
176
281
  fi
177
282
  else
178
283
  log_warn "[$CONSULTANT_NAME] Grok Build CLI not found: $GROK_CMD"
@@ -214,7 +319,17 @@ fi
214
319
  # Preserve which route actually answered without changing the shared schema.
215
320
  if [[ -s "$OUTPUT_FILE" ]]; then
216
321
  response_tmp=$(mktemp)
217
- if jq --arg transport "$TRANSPORT" '.metadata.transport = $transport' \
322
+ if jq --arg transport "$TRANSPORT" \
323
+ --arg cli_version "$GROK_CLI_VERSION" \
324
+ --arg cli_compatibility "$GROK_CLI_COMPATIBILITY" '
325
+ .metadata.transport = $transport |
326
+ if $transport == "cli" and $cli_version != "" then
327
+ .metadata.cli_version = $cli_version |
328
+ .metadata.cli_compatibility = $cli_compatibility
329
+ else
330
+ .
331
+ end
332
+ ' \
218
333
  "$OUTPUT_FILE" > "$response_tmp"; then
219
334
  mv "$response_tmp" "$OUTPUT_FILE"
220
335
  else
@@ -40,9 +40,43 @@ START_TIME=$(get_timestamp_ms)
40
40
 
41
41
  # --- Execution ---
42
42
  TEMP_OUTPUT=$(mktemp)
43
+ KIMI_CLI_VERSION=""
44
+
45
+ observe_kimi_cli_version() {
46
+ local version
47
+ version=$("$KIMI_CMD" --version 2>/dev/null | head -1 || true)
48
+ printf '%s\n' "${version:-unknown}"
49
+ }
50
+
51
+ kimi_cli_supports_required_interface() {
52
+ local help providers flag
53
+
54
+ help=$("$KIMI_CMD" --help 2>&1 || true)
55
+ for flag in --model --prompt --output-format; do
56
+ grep -q -- "$flag" <<< "$help" || return 1
57
+ done
58
+ grep -Eq '^[[:space:]]+provider([[:space:]]|$)' <<< "$help" || return 1
59
+
60
+ # Check the complete prompt-mode parser without running a model request.
61
+ "$KIMI_CMD" \
62
+ --model "$KIMI_MODEL" \
63
+ --prompt "" \
64
+ --output-format stream-json \
65
+ --help >/dev/null 2>&1 || return 1
66
+
67
+ providers=$("$KIMI_CMD" provider list --json 2>/dev/null || true)
68
+ jq -e --arg model "$KIMI_MODEL" '
69
+ .models[$model] != null
70
+ ' <<< "$providers" >/dev/null 2>&1
71
+ }
43
72
 
44
73
  # Check CLI prerequisite
45
74
  check_command "$KIMI_CMD" "Kimi CLI" "curl -L code.kimi.com/install.sh | bash" || exit 1
75
+ KIMI_CLI_VERSION=$(observe_kimi_cli_version)
76
+ kimi_cli_supports_required_interface || {
77
+ log_error "[Kimi] Kimi CLI lacks the required capabilities for model $KIMI_MODEL (reported version: $KIMI_CLI_VERSION)"
78
+ exit 1
79
+ }
46
80
 
47
81
  # Kimi takes the prompt as the -p argument (it does NOT read stdin). The old
48
82
  # `--quiet --input-format text` were NOT real flags on kimi 0.23.6 — commander
@@ -89,5 +123,17 @@ PERSONA_NAME=$(get_persona_name "$CONSULTANT_NAME")
89
123
  process_consultant_response "$CONSULTANT_NAME" "$MODEL_USED" "$PERSONA_NAME" \
90
124
  "$TEMP_OUTPUT" "$OUTPUT_FILE" "$exit_code" "$LATENCY_MS" "" "$FULL_QUERY"
91
125
 
126
+ if [[ -s "$OUTPUT_FILE" ]]; then
127
+ response_tmp=$(mktemp)
128
+ if jq --arg cli_version "$KIMI_CLI_VERSION" '
129
+ .metadata.cli_version = $cli_version |
130
+ .metadata.cli_compatibility = "capability-probed"
131
+ ' "$OUTPUT_FILE" > "$response_tmp"; then
132
+ mv "$response_tmp" "$OUTPUT_FILE"
133
+ else
134
+ rm -f "$response_tmp"
135
+ fi
136
+ fi
137
+
92
138
  cat "$OUTPUT_FILE"
93
139
  exit $exit_code