1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
//! Provider-neutral request types for the native runtime.
use serde::Serialize;
use supercode_interchange::ChatMessage;
/// A tool advertised to a model.
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct ToolSchema {
/// Tool name.
pub name: String,
/// Description the model uses to decide when to call it.
pub description: String,
/// JSON Schema for the tool's input object.
pub parameters: serde_json::Value,
}
impl ToolSchema {
/// Construct a tool schema.
pub fn new(
name: impl Into<String>,
description: impl Into<String>,
parameters: serde_json::Value,
) -> Self {
Self {
name: name.into(),
description: description.into(),
parameters,
}
}
}
/// A single model-completion request.
#[derive(Debug, Clone, PartialEq)]
pub struct ChatRequest {
/// Model id.
pub model: String,
/// Full conversation so far.
pub messages: Vec<ChatMessage>,
/// Tools to advertise (may be empty).
pub tools: Vec<ToolSchema>,
/// Optional sampling temperature.
pub temperature: Option<f32>,
/// Optional output token cap.
pub max_tokens: Option<u32>,
/// Reasoning/effort level, such as `"low"` or `"high"`.
pub effort: Option<String>,
/// Structured-output constraint sent as `response_format`.
pub response_format: Option<serde_json::Value>,
/// BP-13 (catalog D9 "Fast mode / service tiers"): the provider service
/// tier this request asks for (`"auto"`, `"priority"`, `"flex"`, …).
/// Sent verbatim as the OpenAI-compatible `service_tier` field; `None`
/// omits it, which is what every pre-BP-13 caller produced.
pub service_tier: Option<String>,
/// BP-13 (catalog D9 "Reasoning effort / thinking budgets"): a cap on
/// reasoning/thinking TOKENS for this request — Claude Code's
/// `MAX_THINKING_TOKENS`, the budget half of `effort`'s level half.
/// Sent as the unified `reasoning.max_tokens` field; `None` omits it.
pub thinking_budget: Option<u32>,
/// Arbitrary provider-native request fields.
pub extra_body: serde_json::Map<String, serde_json::Value>,
}
impl ChatRequest {
/// Construct a minimal request with a model and conversation.
pub fn new(model: impl Into<String>, messages: Vec<ChatMessage>) -> Self {
Self {
model: model.into(),
messages,
tools: Vec::new(),
temperature: None,
max_tokens: None,
effort: None,
response_format: None,
service_tier: None,
thinking_budget: None,
extra_body: serde_json::Map::new(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn minimal_request_has_no_optional_runtime_controls() {
let request = ChatRequest::new("example/model", vec![ChatMessage::user("hello")]);
assert_eq!(request.model, "example/model");
assert_eq!(request.messages.len(), 1);
assert!(request.tools.is_empty());
assert_eq!(request.temperature, None);
assert_eq!(request.max_tokens, None);
assert!(request.extra_body.is_empty());
}
#[test]
fn schema_constructor_preserves_provider_json() {
let parameters = serde_json::json!({"type": "object"});
let schema = ToolSchema::new("read", "Read a file", parameters.clone());
assert_eq!(schema.name, "read");
assert_eq!(schema.parameters, parameters);
}
}