ai-consultants 3.1.1 → 3.2.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.2.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,13 @@ curl -fsSL https://raw.githubusercontent.com/matteoscurati/ai-consultants/main/s
786
787
 
787
788
  ## Changelog
788
789
 
790
+ ### v3.2.0
791
+ - **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.
792
+ - **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.
793
+ - **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`).
794
+ - **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`.
795
+ - **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.
796
+
789
797
  ### v3.1.1
790
798
  - **`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
799
 
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
- # AI Consultants v3.1.1
1
+ # AI Consultants v3.2.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.2.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)
@@ -444,7 +444,7 @@ parallel and returns the coverage union.
444
444
 
445
445
  | Consultant | Premium | Standard | Economy |
446
446
  |------------|---------|----------|---------|
447
- | Claude | claude-opus-4-8 | claude-sonnet-4-6 | claude-haiku-4-5 |
447
+ | Claude | claude-opus-5 | claude-sonnet-4-6 | claude-haiku-4-5 |
448
448
  | Gemini | Gemini 3.1 Pro (High) | Gemini 3.5 Flash (High) | Gemini 3.5 Flash (Low) |
449
449
  | Codex | gpt-5.5 | gpt-5.4 | gpt-5.4-nano |
450
450
  | Mistral | mistral-large-3 | mistral-medium-latest | devstral-small-2 |
@@ -546,7 +546,13 @@ with `--set`). Rewrites create a private timestamped backup. Use `--advanced` to
546
546
  review every parameter or `--dry-run` to preview a redacted result.
547
547
  Auto-selected `*_USE_API` values are marked `# ai-consultants:auto`, allowing a
548
548
  later run to adapt when a CLI or credential changes. Environment variables,
549
- `--set`, and unmarked values remain explicit user choices.
549
+ `--set`, and unmarked values remain explicit user choices, except for the exact
550
+ historical generated Claude default described below.
551
+ Managed model defaults use `# ai-consultants:default`; `configure` upgrades the
552
+ historical unmarked `CLAUDE_MODEL=claude-opus-4-8` default to Opus 5. To keep
553
+ 4.8 intentionally, run
554
+ `ai-consultants configure --set CLAUDE_MODEL=claude-opus-4-8`; explicit model
555
+ overrides are stored with `# ai-consultants:pin`.
550
556
 
551
557
  Enter credentials through `--interactive`/`--advanced` or export them before the
552
558
  run; avoid passing API keys through `--set`, where the shell may retain them in
@@ -563,6 +569,7 @@ DEFAULT_STRATEGY=coverage # coverage | compare_only | majority | risk_averse
563
569
  ENABLE_SMART_ROUTING=true # Auto-select consultants by question category
564
570
  MAX_SESSION_COST=1.00 # USD budget cap (paired with ENABLE_BUDGET_LIMIT=true to enforce)
565
571
  KIMI_MODEL=kimi-code/k3 # Pin the Kimi consultant to K3
572
+ CLAUDE_API_MAX_TOKENS=16384 # Shared thinking + visible-output budget in API mode
566
573
  ```
567
574
 
568
575
  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.2.0
9
9
  ---
10
10
 
11
- # AI Consultants v3.1.1 - AI Expert Panel
11
+ # AI Consultants v3.2.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
 
@@ -1,4 +1,4 @@
1
- # Cost Rates - AI Consultants v3.1.1
1
+ # Cost Rates - AI Consultants v3.2.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 |
@@ -1,10 +1,10 @@
1
1
  {
2
- "version": "3.1.1",
3
- "updated": "2026-07-16",
2
+ "version": "3.2.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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-consultants",
3
- "version": "3.1.1",
3
+ "version": "3.2.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,6 +252,11 @@ 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
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.2.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
@@ -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"