1use napi_derive::napi;
4use serde::{Deserialize, Serialize};
5
6use openclaw_providers::{
7 CompletionRequest, CompletionResponse, ContentBlock, Message, MessageContent, Role, StopReason,
8 Tool as ProviderTool,
9};
10
11#[napi(object)]
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct JsMessage {
15 pub role: String,
17 pub content: String,
19 pub tool_use_id: Option<String>,
21 pub tool_name: Option<String>,
23}
24
25#[napi(object)]
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct JsCompletionRequest {
29 pub model: String,
31 pub messages: Vec<JsMessage>,
33 pub system: Option<String>,
35 pub max_tokens: u32,
37 pub temperature: Option<f64>,
39 pub stop: Option<Vec<String>>,
41 pub tools: Option<Vec<JsTool>>,
43}
44
45#[napi(object)]
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct JsTool {
49 pub name: String,
51 pub description: String,
53 pub input_schema: serde_json::Value,
55}
56
57#[napi(object)]
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct JsCompletionResponse {
61 pub id: String,
63 pub model: String,
65 pub content: String,
67 pub stop_reason: Option<String>,
69 pub tool_calls: Option<Vec<JsToolCall>>,
71 pub usage: JsTokenUsage,
73}
74
75#[napi(object)]
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct JsToolCall {
79 pub id: String,
81 pub name: String,
83 pub input: serde_json::Value,
85}
86
87#[napi(object)]
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct JsTokenUsage {
91 pub input_tokens: u32,
93 pub output_tokens: u32,
95 pub cache_read_tokens: Option<u32>,
97 pub cache_write_tokens: Option<u32>,
99}
100
101#[napi(object)]
103#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct JsStreamChunk {
105 pub chunk_type: String,
108 pub delta: Option<String>,
110 pub index: Option<u32>,
112 pub stop_reason: Option<String>,
114}
115
116#[must_use]
120pub fn convert_js_message(msg: &JsMessage) -> Message {
121 let role = match msg.role.as_str() {
122 "user" => Role::User,
123 "assistant" => Role::Assistant,
124 "system" => Role::System,
125 "tool" => Role::Tool,
126 _ => Role::User,
127 };
128
129 if role == Role::Tool {
131 Message {
132 role,
133 content: MessageContent::Blocks(vec![ContentBlock::ToolResult {
134 tool_use_id: msg.tool_use_id.clone().unwrap_or_default(),
135 content: msg.content.clone(),
136 is_error: None,
137 }]),
138 }
139 } else {
140 Message {
141 role,
142 content: MessageContent::Text(msg.content.clone()),
143 }
144 }
145}
146
147#[must_use]
149pub fn convert_js_tool(tool: &JsTool) -> ProviderTool {
150 ProviderTool {
151 name: tool.name.clone(),
152 description: tool.description.clone(),
153 input_schema: tool.input_schema.clone(),
154 }
155}
156
157pub fn convert_request(req: JsCompletionRequest) -> CompletionRequest {
159 CompletionRequest {
160 model: req.model,
161 messages: req.messages.iter().map(convert_js_message).collect(),
162 system: req.system,
163 max_tokens: req.max_tokens,
164 temperature: req.temperature.map_or(1.0, |t| t as f32),
165 stop: req.stop,
166 tools: req
167 .tools
168 .map(|tools| tools.iter().map(convert_js_tool).collect()),
169 }
170}
171
172#[must_use]
174pub fn convert_response(resp: CompletionResponse) -> JsCompletionResponse {
175 let content = resp
177 .content
178 .iter()
179 .filter_map(|block| {
180 if let ContentBlock::Text { text } = block {
181 Some(text.as_str())
182 } else {
183 None
184 }
185 })
186 .collect::<Vec<_>>()
187 .join("");
188
189 let tool_calls: Vec<JsToolCall> = resp
191 .content
192 .iter()
193 .filter_map(|block| {
194 if let ContentBlock::ToolUse { id, name, input } = block {
195 Some(JsToolCall {
196 id: id.clone(),
197 name: name.clone(),
198 input: input.clone(),
199 })
200 } else {
201 None
202 }
203 })
204 .collect();
205
206 let stop_reason = resp.stop_reason.map(|sr| match sr {
207 StopReason::EndTurn => "end_turn".to_string(),
208 StopReason::MaxTokens => "max_tokens".to_string(),
209 StopReason::StopSequence => "stop_sequence".to_string(),
210 StopReason::ToolUse => "tool_use".to_string(),
211 });
212
213 JsCompletionResponse {
214 id: resp.id,
215 model: resp.model,
216 content,
217 stop_reason,
218 tool_calls: if tool_calls.is_empty() {
219 None
220 } else {
221 Some(tool_calls)
222 },
223 usage: JsTokenUsage {
224 input_tokens: resp.usage.input_tokens as u32,
225 output_tokens: resp.usage.output_tokens as u32,
226 cache_read_tokens: resp.usage.cache_read_tokens.map(|v| v as u32),
227 cache_write_tokens: resp.usage.cache_write_tokens.map(|v| v as u32),
228 },
229 }
230}