OpenPay (1 crate, 2174 LOC, 14 tests)
op-payouts Multi-rail payout engine. Visa Direct, Mastercard Send,
ACH credits (NACHA), Fedwire + SWIFT MT103 + ISO 20022
pacs.008, SEPA SCT + SCT Inst (mod-97 IBAN), UK FPS,
FedNow, RTP, PayPal, Wise. Crypto: USDC/USDT on
ETH/Polygon/Base/Solana + BTC + Lightning with real
ERC-20 transfer(address,uint256) calldata. Offline-pure
drivers (emit wire payloads — operators transmit).
Smart Byte (1 crate, 2605 LOC, 38 tests)
smart-byte-nostr Nostr adapter. NIP-01 canonical event id + BIP-340
Schnorr sign/verify; from-scratch bech32 + NIP-19 TLV
(npub/nsec/note/nprofile/nevent/nrelay); NIP-04 legacy
AES-256-CBC DMs; NIP-44 v2 HKDF-SHA256→ChaCha20+HMAC
with bucketed padding; NIP-17 gift-wrap (kind 1059) +
seal (kind 13) + rumor (kind 14); NIP-05/11/13/26/65.
Relay client message vocabulary (REQ/EVENT/CLOSE/AUTH/
EOSE/OK/NOTICE/CLOSED) + in-memory router.
EOC (1 crate, 2024 LOC, 30 tests)
eoc-streaming Token-by-token + tool-call streaming primitives.
SSE framing (data: lines); Anthropic event mapper
(message_start/content_block_*/message_delta/stop/ping);
OpenAI delta mapper; Vercel AI SDK mapper. Bounded-channel
backpressure with watermarks; CancelToken propagation;
Last-Event-ID resumable streams; per-stream joule meter.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
174 lines
6 KiB
Rust
174 lines
6 KiB
Rust
//! OpenAI Chat Completions streaming delta mapper.
|
|
//!
|
|
//! Reference frame shape:
|
|
//!
|
|
//! ```jsonc
|
|
//! {
|
|
//! "id": "chatcmpl-abc",
|
|
//! "object": "chat.completion.chunk",
|
|
//! "choices": [{
|
|
//! "index": 0,
|
|
//! "delta": { "role": "assistant", "content": "Hi" },
|
|
//! "finish_reason": null
|
|
//! }]
|
|
//! }
|
|
//! ```
|
|
//!
|
|
//! The terminating event is the literal SSE payload `[DONE]`, which the
|
|
//! caller should detect *before* invoking this mapper (it isn't valid
|
|
//! JSON). Tool calls are emitted as `delta.tool_calls[].function.{name,
|
|
//! arguments}` fragments; we emit `ToolCallStart` the first time a call
|
|
//! id is seen and `ToolCallDelta` for each argument fragment.
|
|
|
|
use serde_json::Value;
|
|
use std::collections::HashSet;
|
|
use std::sync::Mutex;
|
|
|
|
use crate::error::StreamResult;
|
|
use crate::stream::{Event, FinishReason, Role};
|
|
|
|
/// OpenAI → [`Event`] mapper.
|
|
///
|
|
/// Holds a small amount of state to distinguish the first appearance of
|
|
/// a tool call (which gets a `ToolCallStart`) from subsequent argument
|
|
/// fragments (`ToolCallDelta`).
|
|
#[derive(Debug, Default)]
|
|
pub struct OpenAiMapper {
|
|
seen_tool_ids: Mutex<HashSet<String>>,
|
|
started: Mutex<bool>,
|
|
}
|
|
|
|
impl OpenAiMapper {
|
|
/// Construct a fresh mapper.
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
/// Map a parsed OpenAI chunk (the JSON object after `data: `).
|
|
pub fn map(&self, data: &str) -> StreamResult<Vec<Event>> {
|
|
if data.trim() == "[DONE]" {
|
|
return Ok(vec![Event::MessageStop {
|
|
reason: FinishReason::EndTurn,
|
|
}]);
|
|
}
|
|
let v: Value = serde_json::from_str(data)?;
|
|
let id = v.get("id").and_then(|s| s.as_str()).map(String::from);
|
|
let mut out = Vec::new();
|
|
|
|
let mut started_guard = self.started.lock().map_err(poisoned)?;
|
|
if !*started_guard {
|
|
*started_guard = true;
|
|
out.push(Event::MessageStart {
|
|
id: id.clone(),
|
|
role: Role::Assistant,
|
|
});
|
|
}
|
|
drop(started_guard);
|
|
|
|
let choices = v.get("choices").and_then(|c| c.as_array());
|
|
let Some(choices) = choices else {
|
|
return Ok(out);
|
|
};
|
|
for choice in choices {
|
|
let index = choice
|
|
.get("index")
|
|
.and_then(|i| i.as_u64())
|
|
.unwrap_or(0) as u32;
|
|
if let Some(delta) = choice.get("delta") {
|
|
if let Some(content) = delta.get("content").and_then(|c| c.as_str())
|
|
&& !content.is_empty()
|
|
{
|
|
out.push(Event::TextDelta {
|
|
index,
|
|
delta: content.to_string(),
|
|
});
|
|
}
|
|
if let Some(calls) = delta.get("tool_calls").and_then(|c| c.as_array()) {
|
|
for call in calls {
|
|
let call_idx = call
|
|
.get("index")
|
|
.and_then(|i| i.as_u64())
|
|
.unwrap_or(0) as u32;
|
|
let call_id = call
|
|
.get("id")
|
|
.and_then(|s| s.as_str())
|
|
.unwrap_or_default()
|
|
.to_string();
|
|
let function = call.get("function");
|
|
let name = function
|
|
.and_then(|f| f.get("name"))
|
|
.and_then(|s| s.as_str())
|
|
.unwrap_or_default()
|
|
.to_string();
|
|
let args = function
|
|
.and_then(|f| f.get("arguments"))
|
|
.and_then(|s| s.as_str())
|
|
.unwrap_or_default()
|
|
.to_string();
|
|
if !call_id.is_empty() {
|
|
let mut seen = self.seen_tool_ids.lock().map_err(poisoned)?;
|
|
if !seen.contains(&call_id) {
|
|
seen.insert(call_id.clone());
|
|
out.push(Event::ToolCallStart {
|
|
index: call_idx,
|
|
id: call_id.clone(),
|
|
name: name.clone(),
|
|
});
|
|
}
|
|
drop(seen);
|
|
}
|
|
if !args.is_empty() {
|
|
out.push(Event::ToolCallDelta {
|
|
index: call_idx,
|
|
id: call_id,
|
|
arguments_delta: args,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if let Some(reason) = choice.get("finish_reason").and_then(|r| r.as_str()) {
|
|
out.push(Event::MessageStop {
|
|
reason: map_reason(reason),
|
|
});
|
|
}
|
|
}
|
|
Ok(out)
|
|
}
|
|
}
|
|
|
|
fn map_reason(s: &str) -> FinishReason {
|
|
match s {
|
|
"stop" => FinishReason::EndTurn,
|
|
"length" => FinishReason::MaxTokens,
|
|
"tool_calls" | "function_call" => FinishReason::ToolUse,
|
|
"content_filter" => FinishReason::ContentFilter,
|
|
other => FinishReason::Other(other.to_string()),
|
|
}
|
|
}
|
|
|
|
fn poisoned<T>(_: T) -> crate::error::StreamError {
|
|
crate::error::StreamError::Backend("mapper state poisoned".into())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn maps_content_delta() {
|
|
let m = OpenAiMapper::new();
|
|
let frame =
|
|
r#"{"id":"x","choices":[{"index":0,"delta":{"content":"Hi"},"finish_reason":null}]}"#;
|
|
let out = m.map(frame).unwrap();
|
|
assert!(matches!(out[0], Event::MessageStart { .. }));
|
|
assert!(matches!(out[1], Event::TextDelta { .. }));
|
|
}
|
|
|
|
#[test]
|
|
fn maps_done() {
|
|
let m = OpenAiMapper::new();
|
|
let out = m.map("[DONE]").unwrap();
|
|
assert!(matches!(out[0], Event::MessageStop { .. }));
|
|
}
|
|
}
|