Return provider token accounting with every successful core one-shot AI result.
Current state
crates/bds-core/src/engine/ai.rs::run_one_shot parses choices[0].message.content and ignores the response usage object.
The six one-shot operations already work: taxonomy analysis, image analysis, post analysis, language detection, post translation, and media translation.
A Diesel migration now adds nullable chat_messages.token_usage_input and chat_messages.token_usage_output beside the existing cache counters.
specs/ai.allium, UsageReturnedWithSuccessfulResult, is normative.
Required behavior
Every successful one-shot call returns its existing operation result plus a usage value with nullable:
input_tokens
output_tokens
cache_read_tokens
cache_write_tokens
Normalize OpenAI-compatible fields exactly as bDS2 does:
Missing usage or missing individual counters produces None for those counters and does not fail an otherwise valid AI result.
One-shot calls do not create a chat conversation and do not persist ChatMessage rows. Persisting normalized counters on assistant chat messages belongs to the extension chat implementation; the schema fields are present for that future work.
Implementation notes
Keep one shared usage parser at the HTTP response boundary and reuse it for all six operations.
Choose the smallest response-shape change that lets all callers access the usage without duplicating fields/parsing.
Update UI and engine callers without changing their current visible result behavior.
Follow red/green TDD.
Compare with ../bDS2/lib/bds/ai/openai_compatible_runtime.ex, one_shot.ex, and chat.ex when the spec is silent.
Acceptance criteria
Tests cover a complete usage object, a partial usage object, no usage object, and malformed/non-numeric counters.
All six operations return normalized usage while retaining their current payloads.
Existing callers continue to work and do not persist one-shot usage in chat tables.
Migration tests prove input, output, cache-read, and cache-write chat columns are nullable and writable.
allium check specs/*.allium, cargo fmt --all -- --check, cargo build --workspace, and cargo test --workspace pass.
## Goal
Return provider token accounting with every successful core one-shot AI result.
## Current state
- `crates/bds-core/src/engine/ai.rs::run_one_shot` parses `choices[0].message.content` and ignores the response `usage` object.
- The six one-shot operations already work: taxonomy analysis, image analysis, post analysis, language detection, post translation, and media translation.
- A Diesel migration now adds nullable `chat_messages.token_usage_input` and `chat_messages.token_usage_output` beside the existing cache counters.
- `specs/ai.allium`, `UsageReturnedWithSuccessfulResult`, is normative.
## Required behavior
Every successful one-shot call returns its existing operation result plus a usage value with nullable:
- `input_tokens`
- `output_tokens`
- `cache_read_tokens`
- `cache_write_tokens`
Normalize OpenAI-compatible fields exactly as bDS2 does:
- `usage.prompt_tokens` -> `input_tokens`
- `usage.completion_tokens` -> `output_tokens`
- `usage.prompt_tokens_details.cached_tokens` -> `cache_read_tokens`
- `usage.completion_tokens_details.cached_tokens` -> `cache_write_tokens`
Missing usage or missing individual counters produces `None` for those counters and does not fail an otherwise valid AI result.
One-shot calls do not create a chat conversation and do not persist `ChatMessage` rows. Persisting normalized counters on assistant chat messages belongs to the extension chat implementation; the schema fields are present for that future work.
## Implementation notes
- Keep one shared usage parser at the HTTP response boundary and reuse it for all six operations.
- Choose the smallest response-shape change that lets all callers access the usage without duplicating fields/parsing.
- Update UI and engine callers without changing their current visible result behavior.
- Follow red/green TDD.
- Compare with `../bDS2/lib/bds/ai/openai_compatible_runtime.ex`, `one_shot.ex`, and `chat.ex` when the spec is silent.
## Acceptance criteria
- Tests cover a complete usage object, a partial usage object, no usage object, and malformed/non-numeric counters.
- All six operations return normalized usage while retaining their current payloads.
- Existing callers continue to work and do not persist one-shot usage in chat tables.
- Migration tests prove input, output, cache-read, and cache-write chat columns are nullable and writable.
- `allium check specs/*.allium`, `cargo fmt --all -- --check`, `cargo build --workspace`, and `cargo test --workspace` pass.
run_one_shot now returns (OneShotResponse, TokenUsage); one shared parse_token_usage at the HTTP response boundary normalizes prompt_tokens -> input_tokens, completion_tokens -> output_tokens, prompt_tokens_details.cached_tokens -> cache_read_tokens, completion_tokens_details.cached_tokens -> cache_write_tokens. Missing or non-numeric counters stay null without failing the result.
All six one-shot operations return usage through the single call path; engine and UI callers destructure and ignore it, so visible behavior is unchanged and nothing is persisted to chat tables.
Tests: parser unit tests for complete/partial/absent/malformed usage; the six per-operation tests assert normalized usage from mocked responses. The existing migration test already proves all four chat_messages token columns are nullable and writable.
allium check, cargo fmt --check, cargo build --workspace, cargo test --workspace all pass.
Implemented in 33929fd.
- `run_one_shot` now returns `(OneShotResponse, TokenUsage)`; one shared `parse_token_usage` at the HTTP response boundary normalizes `prompt_tokens` -> `input_tokens`, `completion_tokens` -> `output_tokens`, `prompt_tokens_details.cached_tokens` -> `cache_read_tokens`, `completion_tokens_details.cached_tokens` -> `cache_write_tokens`. Missing or non-numeric counters stay null without failing the result.
- All six one-shot operations return usage through the single call path; engine and UI callers destructure and ignore it, so visible behavior is unchanged and nothing is persisted to chat tables.
- Tests: parser unit tests for complete/partial/absent/malformed usage; the six per-operation tests assert normalized usage from mocked responses. The existing migration test already proves all four chat_messages token columns are nullable and writable.
- `allium check`, `cargo fmt --check`, `cargo build --workspace`, `cargo test --workspace` all pass.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Goal
Return provider token accounting with every successful core one-shot AI result.
Current state
crates/bds-core/src/engine/ai.rs::run_one_shotparseschoices[0].message.contentand ignores the responseusageobject.chat_messages.token_usage_inputandchat_messages.token_usage_outputbeside the existing cache counters.specs/ai.allium,UsageReturnedWithSuccessfulResult, is normative.Required behavior
Every successful one-shot call returns its existing operation result plus a usage value with nullable:
input_tokensoutput_tokenscache_read_tokenscache_write_tokensNormalize OpenAI-compatible fields exactly as bDS2 does:
usage.prompt_tokens->input_tokensusage.completion_tokens->output_tokensusage.prompt_tokens_details.cached_tokens->cache_read_tokensusage.completion_tokens_details.cached_tokens->cache_write_tokensMissing usage or missing individual counters produces
Nonefor those counters and does not fail an otherwise valid AI result.One-shot calls do not create a chat conversation and do not persist
ChatMessagerows. Persisting normalized counters on assistant chat messages belongs to the extension chat implementation; the schema fields are present for that future work.Implementation notes
../bDS2/lib/bds/ai/openai_compatible_runtime.ex,one_shot.ex, andchat.exwhen the spec is silent.Acceptance criteria
allium check specs/*.allium,cargo fmt --all -- --check,cargo build --workspace, andcargo test --workspacepass.Implemented in
33929fd.run_one_shotnow returns(OneShotResponse, TokenUsage); one sharedparse_token_usageat the HTTP response boundary normalizesprompt_tokens->input_tokens,completion_tokens->output_tokens,prompt_tokens_details.cached_tokens->cache_read_tokens,completion_tokens_details.cached_tokens->cache_write_tokens. Missing or non-numeric counters stay null without failing the result.allium check,cargo fmt --check,cargo build --workspace,cargo test --workspaceall pass.