1
0
forked from boos/BoosAPI

feat(rust): wire downstream-key billing, add /v1/responses, ComfyUI shim

Previously every executor took `_state: AppState` (unused) and `check_auth`
discarded the matched downstream key id, so `usage_logs` was orphan code.
This change plumbs the key id from auth → handlers → executors → billing,
adds the OpenAI Responses API endpoint, and exposes shim aliases under
`/api/comfyui-agent/proxy/openai/*` so embedded ComfyUI's OpenAI nodes
flow through the same auth/conductor/billing pipeline.

- check_auth now returns Option<i64> (managed key id) instead of unit
- record_billing helper writes usage_logs after each successful upstream call
- extract_usage handles both chat-completions and responses usage shapes
- conductor wrappers record billing post-Success; stream variant scans
  chunks in reverse for the final usage event
- POST /v1/responses (stream + non-stream) via ProxyConductor
- shim aliases mounted on the same Router so auth/failover/billing work for free
- ComfyUIProcess injects BOOSAPI_BUILTIN_KEY env + --comfy-api-base on spawn
- bootstrap built-in `comfyui-agent` downstream key at startup (idempotent)
- get_auth_header in vendored ComfyUI falls back to BOOSAPI_BUILTIN_KEY env

Co-Authored-By: Kiro <noreply@kiro.dev>
This commit is contained in:
boos
2026-05-20 06:20:39 +08:00
parent 22751effd5
commit 9c0d4ec69f
7 changed files with 644 additions and 41 deletions
+15 -3
View File
@@ -70,9 +70,23 @@ pub async fn execute_chat_completions_stream(
_state: AppState,
route: UpstreamRoute,
body: Value,
) -> AppResult<Vec<String>> {
execute_streaming_request(route, "/chat/completions", body).await
}
/// Execute a streaming request against any upstream path (e.g. /responses).
/// Returns SSE `data: ...` lines verbatim.
pub async fn execute_streaming_request(
route: UpstreamRoute,
path_suffix: &str,
body: Value,
) -> AppResult<Vec<String>> {
let client = http_client();
let upstream_url = format!("{}/chat/completions", route.base_url.trim_end_matches('/'));
let upstream_url = format!(
"{}/{}",
route.base_url.trim_end_matches('/'),
path_suffix.trim_start_matches('/')
);
let resp = client
.post(&upstream_url)
@@ -94,7 +108,6 @@ pub async fn execute_chat_completions_stream(
)));
}
// Read the streaming response as chunks
let mut chunks = Vec::new();
let mut stream = resp.bytes_stream();
use futures::StreamExt;
@@ -102,7 +115,6 @@ pub async fn execute_chat_completions_stream(
match chunk_result {
Ok(bytes) => {
let text = String::from_utf8_lossy(&bytes).to_string();
// Parse SSE lines from the chunk
for line in text.lines() {
if line.starts_with("data: ") {
chunks.push(line.to_string());