Skip to content

feat: expose the upstream provider on chat responses and error metadata - #55

Merged
ProJedi1234 merged 2 commits into
mainfrom
feat/response-provider
Sep 26, 2026
Merged

ProJedi1234 merged 2 commits into
mainfrom
feat/response-provider

Conversation

@ProJedi1234

@ProJedi1234 ProJedi1234 commented Sep 26, 2026 •

Copy link
Copy Markdown
Owner

Lets callers record which upstream provider (Groq, Cerebras, Baseten, ...) served an OpenRouter call. maia-server needs this before it can add a provider column to openrouter_calls.

Changes

  • ChatResponse.provider: String?: decodes OpenRouter's top-level provider string, following the existing StreamingDelta.provider. The field is optional, so responses without it still decode. OpenRouter's OpenAPI ChatResult schema doesn't list the field, but its mid-stream error chunk type does, and the streaming path already relies on it.
  • Error metadata fix: OpenRouterError.metadata could not survive real error bodies. OpenRouter types error.metadata as Record<string, unknown>. Moderation errors carry reasons: string[], guardrail blocks carry patterns: string[], and provider errors put the upstream's verbatim payload in raw, often as an object. ErrorDetail decoded metadata as [String: String], so any one of those values made the whole ErrorResponse decode throw. HTTPClient swallows that failure with try?, so the caller got "Unknown error occurred" and metadata == nil, and provider_name was unreachable. Metadata now decodes as arbitrary JSON and is flattened into the same [String: String]. Strings pass through, other values become compact sorted-key JSON text, and nulls are dropped. The public type is unchanged, so this is not a breaking change. Before the fix, both new tests failed with DecodingError.typeMismatch ... Path: error.metadata.reasons (and .attempt).

Audio transcription: no provider to expose

AudioTranscriptionResponse is unchanged. OpenRouter's OpenAPI spec for POST /audio/transcriptions (STTResponse) defines text, usage, and the verbose_json extras (task, language, duration, segments, words, confidence), with no provider field. The spec also documents no response headers for the endpoint. The only X- headers anywhere in the spec are request headers. I did not call the live endpoint to check for undocumented fields, because that spends a real key. If maia-server needs the transcription provider, confirming it will take one authorized live call, or a lookup through /generation, though that needs a generation id the transcription response doesn't return either.

Review guide

Read closely

  • Sources/OpenRouterKit/Errors/OpenRouterError.swift: the custom ErrorDetail.init(from:) and the metadataString flattening.

Skim

  • Sources/OpenRouterKit/Models/Chat/ChatResponse.swift: one optional stored property, synthesized Codable.
  • Tests/OpenRouterKitTests/ChatResponseProviderTests.swift: unit tests decode with and without provider, and a live integration test asserts that a real chat completion carries a non-empty provider. Both follow the UsageCostTests pattern.
  • Tests/OpenRouterKitTests/OpenRouterErrorTests.swift: two fixtures modeled on the documented provider-error and moderation-error shapes.
  • README.md: two short paragraphs.

Verification

  • swift build: clean.
  • swift test --parallel locally: 193 tests in 23 suites passed. Live integration suites were skipped because OPENROUTER_API_KEY is not set on the dev box.
  • CI integration job: runs the live suites with the repo secret, including the new provider test. That test is the live check that OpenRouter actually sends provider, since the OpenAPI schema doesn't list it.
  • swiftlint (ghcr.io/realm/swiftlint image): no violations in changed files. The only warnings are two that already existed in NIOHTTPClient.swift:114 and ChatRequestParametersTests.swift:567.

🤖 Generated with Claude Code

@ProJedi1234 ProJedi1234 added the enhancement New feature or request label Sep 26, 2026
@coderabbitai

coderabbitai Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: e4a5bcc7-b78a-4db3-a940-f4b84b42dba6

📥 Commits

Reviewing files that changed from the base of the PR and between 4d0d959 and ad7aeb3.

📒 Files selected for processing (5)
  • README.md
  • Sources/OpenRouterKit/Errors/OpenRouterError.swift
  • Sources/OpenRouterKit/Models/Chat/ChatResponse.swift
  • Tests/OpenRouterKitTests/ChatResponseProviderTests.swift
  • Tests/OpenRouterKitTests/OpenRouterErrorTests.swift

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

Chat responses now expose an optional upstream provider name. Error decoding now accepts structured metadata values and represents non-string, non-null values as compact JSON strings.

Changes

Response and Error Metadata

Layer / File(s) Summary
Chat response provider
Sources/OpenRouterKit/Models/Chat/ChatResponse.swift, Tests/OpenRouterKitTests/ChatResponseProviderTests.swift, README.md
ChatResponse adds an optional provider property. Tests cover decoding responses with and without the field. The README documents how to read the provider name.
Error metadata decoding
Sources/OpenRouterKit/Errors/OpenRouterError.swift, Tests/OpenRouterKitTests/OpenRouterErrorTests.swift, README.md
Error metadata decoding preserves strings, omits null values, and serializes other JSON values as compact, sorted-key JSON text. Tests cover nested objects, arrays, and scalar values. The README describes the metadata representation.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~12 minutes

Change: Feature

Merge Risk: ⚪ Minimal · up to ad7ae

The provider and error-metadata changes appear mergeable after normal checks.

Security Architecture Review

Security architecture risk: 🔵 Low · up to ad7ae

The new fields make more upstream data available to callers. No security failure is established, but applications should not treat provider names or error metadata as verified or safe to log without review.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The effective new exposure is to callers consuming decoded chat responses or errors. The supplied source does not establish a downstream logging, persistence, authorization, or cross-tenant sink.

Trust Boundaries and Controls

  • observed — Provider attribution and error metadata originate in decoded upstream responses. The new metadata conversion formats those values; it does not authenticate the provider name or redact raw metadata.

Hardening Proposals

  • proposed — Consumers that persist or log these fields should review raw error metadata for sensitive content and avoid using response-provided provider names as an authorization or billing authority without independent verification.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 7 functions across 4 files. (1 skipped: 1 … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main changes: exposing the upstream provider on chat responses and supporting provider information in error metadata.
Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 7 functions across 4 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A rabbit reads the provider name,
Then checks the error fields again.
Strings stay neat, and nulls depart,
JSON nests its bits with art.
I hop through tests and nibble hay,
Then share the docs along the way.

Comment @coderabbitai help to get the list of available commands.

ProJedi1234 and others added 2 commits September 26, 2026 18:01
OpenRouter puts a top-level `provider` string (Groq, Cerebras, ...) on
chat completion responses, naming the upstream that served the call.
`StreamingDelta` already decoded it, but `ChatResponse` did not, so
non-streaming callers had no way to record which provider they got.

The field is optional. OpenRouter's OpenAPI schema for ChatResult does
not list it, so responses that omit it still decode.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
OpenRouter types `error.metadata` as `Record<string, unknown>`.
Moderation errors carry `reasons: string[]`, guardrail blocks carry
`patterns: string[]`, and provider errors carry the upstream's verbatim
payload in `raw`, which is often an object. `ErrorDetail` decoded
metadata as `[String: String]`, so any one of those values made the
whole `ErrorResponse` decode throw. HTTPClient swallows that with
`try?`, so the caller got "Unknown error occurred", `metadata == nil`,
and no way to read `provider_name` short of parsing `rawResponseBody`.

Metadata now decodes as arbitrary JSON and is flattened into the
existing `[String: String]` shape. Strings pass through unchanged,
other values become compact sorted-key JSON text, and nulls are
dropped. The public type of `OpenRouterError.metadata` is unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
@ProJedi1234
ProJedi1234 force-pushed the feat/response-provider branch from ad7aeb3 to 4c042b2 Compare September 26, 2026 22:01
@ProJedi1234
ProJedi1234 merged commit 141c5f3 into main Sep 26, 2026
7 checks passed
@ProJedi1234
ProJedi1234 deleted the feat/response-provider branch September 26, 2026 22:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant