From 673a7064fe2ad3e1d72c448691cc57f4bbf70a77 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Fri, 24 Jul 2026 07:04:31 -0400 Subject: [PATCH] Validate completion reasoning effort --- docs/public/api-reference/fabro-api.yaml | 2 +- .../src/server/handler/completions.rs | 2 +- lib/apps/fabro-server/src/server/tests.rs | 21 ++++++++++++++++ .../create_completion_request_round_trip.rs | 25 +++++++++++++++++++ .../src/models/create-completion-request.ts | 5 +++- 5 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 lib/foundation/fabro-api/tests/create_completion_request_round_trip.rs diff --git a/docs/public/api-reference/fabro-api.yaml b/docs/public/api-reference/fabro-api.yaml index 519ee4188..e13097f1b 100644 --- a/docs/public/api-reference/fabro-api.yaml +++ b/docs/public/api-reference/fabro-api.yaml @@ -8498,7 +8498,7 @@ components: type: string description: Stop sequences. reasoning_effort: - type: string + $ref: "#/components/schemas/ReasoningEffort" description: Reasoning effort level. provider: type: string diff --git a/lib/apps/fabro-server/src/server/handler/completions.rs b/lib/apps/fabro-server/src/server/handler/completions.rs index b53e56d31..7afe6e943 100644 --- a/lib/apps/fabro-server/src/server/handler/completions.rs +++ b/lib/apps/fabro-server/src/server/handler/completions.rs @@ -109,7 +109,7 @@ async fn create_completion( } else { Some(req.stop_sequences) }, - reasoning_effort: req.reasoning_effort.as_deref().and_then(|s| s.parse().ok()), + reasoning_effort: req.reasoning_effort, speed: None, metadata: None, provider_options: req.provider_options, diff --git a/lib/apps/fabro-server/src/server/tests.rs b/lib/apps/fabro-server/src/server/tests.rs index 454455be0..641ea0b7b 100644 --- a/lib/apps/fabro-server/src/server/tests.rs +++ b/lib/apps/fabro-server/src/server/tests.rs @@ -15240,6 +15240,27 @@ async fn create_completion_missing_messages_returns_422() { assert_status!(response, StatusCode::UNPROCESSABLE_ENTITY).await; } +#[tokio::test] +async fn create_completion_invalid_reasoning_effort_returns_422() { + let app = test_app_with(); + + let req = Request::builder() + .method("POST") + .uri(api("/completions")) + .header("content-type", "application/json") + .body(Body::from( + serde_json::json!({ + "messages": [], + "reasoning_effort": "bogus" + }) + .to_string(), + )) + .unwrap(); + + let response = app.oneshot(req).await.unwrap(); + assert_status!(response, StatusCode::UNPROCESSABLE_ENTITY).await; +} + #[tokio::test] async fn create_completion_unknown_provider_returns_clear_error() { let app = test_app_with(); diff --git a/lib/foundation/fabro-api/tests/create_completion_request_round_trip.rs b/lib/foundation/fabro-api/tests/create_completion_request_round_trip.rs new file mode 100644 index 000000000..913947453 --- /dev/null +++ b/lib/foundation/fabro-api/tests/create_completion_request_round_trip.rs @@ -0,0 +1,25 @@ +use fabro_api::types::CreateCompletionRequest; +use fabro_model::ReasoningEffort; +use serde_json::json; + +#[test] +fn create_completion_request_reuses_canonical_reasoning_effort() { + let request: CreateCompletionRequest = serde_json::from_value(json!({ + "messages": [], + "reasoning_effort": "high" + })) + .unwrap(); + + let reasoning_effort: Option = request.reasoning_effort; + assert_eq!(reasoning_effort, Some(ReasoningEffort::High)); +} + +#[test] +fn create_completion_request_rejects_unknown_reasoning_effort() { + let result = serde_json::from_value::(json!({ + "messages": [], + "reasoning_effort": "bogus" + })); + + assert!(result.is_err()); +} diff --git a/lib/packages/fabro-api-client/src/models/create-completion-request.ts b/lib/packages/fabro-api-client/src/models/create-completion-request.ts index 923431e9b..836e20c0d 100644 --- a/lib/packages/fabro-api-client/src/models/create-completion-request.ts +++ b/lib/packages/fabro-api-client/src/models/create-completion-request.ts @@ -22,6 +22,9 @@ import type { CompletionToolChoice } from './completion-tool-choice'; // May contain unused imports in some cases // @ts-ignore import type { CompletionToolDefinition } from './completion-tool-definition'; +// May contain unused imports in some cases +// @ts-ignore +import type { ReasoningEffort } from './reasoning-effort'; export interface CreateCompletionRequest { /** @@ -56,7 +59,7 @@ export interface CreateCompletionRequest { /** * Reasoning effort level. */ - 'reasoning_effort'?: string; + 'reasoning_effort'?: ReasoningEffort; /** * Optional provider pin. */