Validation probes for a route, in preference order. A managed route advertises every protocol in its provider profile, so an embeddings model resolves to a route that also lists chat/completions. The caller tries these in order and falls through to the next on a request-shape rejection, so such a model validates against `/v1/embeddings` even though the chat probe rejects it. Embeddings is ordered
(route: &ResolvedRoute)
| 380 | /// chat-capable route still validates against chat. Empty when the route |
| 381 | /// exposes no writable protocol. |
| 382 | fn validation_probes(route: &ResolvedRoute) -> Vec<ValidationProbe> { |
| 383 | let has = |protocol: &str| route.protocols.iter().any(|p| p == protocol); |
| 384 | let mut probes = Vec::new(); |
| 385 | |
| 386 | if has("openai_chat_completions") { |
| 387 | // Use max_completion_tokens (modern OpenAI parameter, required by GPT-5+) |
| 388 | // with max_tokens as fallback for legacy/self-hosted backends. |
| 389 | probes.push(ValidationProbe { |
| 390 | path: "/v1/chat/completions", |
| 391 | protocol: "openai_chat_completions", |
| 392 | body: bytes::Bytes::from_static( |
| 393 | br#"{"messages":[{"role":"user","content":"ping"}],"max_completion_tokens":32}"#, |
| 394 | ), |
| 395 | fallback_body: Some(bytes::Bytes::from_static( |
| 396 | br#"{"messages":[{"role":"user","content":"ping"}],"max_tokens":32}"#, |
| 397 | )), |
| 398 | }); |
| 399 | } |
| 400 | |
| 401 | if has("anthropic_messages") { |
| 402 | probes.push(ValidationProbe { |
| 403 | path: "/v1/messages", |
| 404 | protocol: "anthropic_messages", |
| 405 | body: bytes::Bytes::from_static( |
| 406 | br#"{"messages":[{"role":"user","content":"ping"}],"max_tokens":32}"#, |
| 407 | ), |
| 408 | fallback_body: None, |
| 409 | }); |
| 410 | } |
| 411 | |
| 412 | if has("openai_responses") { |
| 413 | probes.push(ValidationProbe { |
| 414 | path: "/v1/responses", |
| 415 | protocol: "openai_responses", |
| 416 | body: bytes::Bytes::from_static(br#"{"input":"ping","max_output_tokens":32}"#), |
| 417 | fallback_body: None, |
| 418 | }); |
| 419 | } |
| 420 | |
| 421 | if has("openai_completions") { |
| 422 | probes.push(ValidationProbe { |
| 423 | path: "/v1/completions", |
| 424 | protocol: "openai_completions", |
| 425 | body: bytes::Bytes::from_static(br#"{"prompt":"ping","max_tokens":32}"#), |
| 426 | fallback_body: None, |
| 427 | }); |
| 428 | } |
| 429 | |
| 430 | // Last so a chat-capable route prefers a chat probe, but an embeddings-only |
| 431 | // model still validates against its single writable endpoint. |
| 432 | if has("openai_embeddings") { |
| 433 | probes.push(ValidationProbe { |
| 434 | path: "/v1/embeddings", |
| 435 | protocol: "openai_embeddings", |
| 436 | body: bytes::Bytes::from_static(br#"{"input":"ping"}"#), |
| 437 | fallback_body: None, |
| 438 | }); |
| 439 | } |
no test coverage detected