Add OpenAI platform: live model fetching with enrichment (provider 1)
The 4th registry row: id "openai", OPENAI_API_KEY env > auth.json "openai" scope (login picker/paste/validation all registry-generic — zero TUI changes needed, pinned by the picker test), base https://api.openai.com/v1 with KIGI_OPENAI_BASE_URL override, Responses dialect via the new PlatformWireApi spec field, enrichment-backed metadata (wire_serves_metadata=false). OpenAI's GET /v1/models returns bare ids and is polluted with tts/whisper/embeddings entries: the listing is restricted to enrichment-known TOOL-CALLING models (review caught that membership alone admitted models.dev-known embeddings models, which would 400 on every agentic request; dropped ids are debug-logged for launch-day diagnosability). Context windows, effort menus, display names, and thinking capability come from the enrichment pipeline — wiremock e2e pins the full contract: polluted live listing + models.dev → one Responses-backed chat model with a 400k documented context window. Responses max-effort wiring (closes the P0c-1 debt): canonical effort rides a CreateResponseWrapper sidecar and patch_reasoning_effort writes it onto the serialized body at both send sites (all seven levels pinned, xhigh/max distinct, summary preserved); normalize_effort_echo drops echoes async-openai's typed enum cannot represent at both the non-stream and SSE parse seams; the dead typed to_responses_api converter is deleted. Kimi/moonshot stay byte-identical (ChatCompletions untouched, wire_api maps to the same default; kimi wire tests green). kimi-import now recognizes ANY registry platform host as built-in (was hardcoded moonshot), covering openai and future rows.
This commit is contained in:
@@ -70,6 +70,9 @@ fn deserialize_response_event(data: &str) -> Result<rs::ResponseStreamEvent> {
|
||||
Err(first_err) => {
|
||||
// Try sanitizing: parse as Value, strip unknown tools, retry.
|
||||
if let Ok(mut value) = serde_json::from_str::<serde_json::Value>(data) {
|
||||
// A `max` reasoning-effort echo is unrepresentable in the
|
||||
// typed enum; drop it so the event parses.
|
||||
kigi_sampling_types::normalize_effort_echo(&mut value);
|
||||
// Strip tools that async_openai's rs::Tool can't deserialize
|
||||
// (e.g., xAI-specific "x_search"). Instead of maintaining a
|
||||
// hardcoded allowlist, try deserializing each tool entry —
|
||||
@@ -1028,6 +1031,7 @@ impl SamplingClient {
|
||||
// it in post-serialize. This is the last surviving piece of the
|
||||
// old raw_output machinery.
|
||||
kigi_sampling_types::patch_reasoning_text_types(&mut request_body);
|
||||
kigi_sampling_types::patch_reasoning_effort(&mut request_body, request.reasoning_effort);
|
||||
let http_request = self.post(self.endpoint("responses")).json(&request_body);
|
||||
|
||||
let response = http_request.send().await.map_err(|e| {
|
||||
@@ -1074,7 +1078,16 @@ impl SamplingClient {
|
||||
});
|
||||
}
|
||||
|
||||
let response_obj = serde_json::from_slice::<rs::Response>(&bytes).map_err(|e| {
|
||||
let mut response_value =
|
||||
serde_json::from_slice::<serde_json::Value>(&bytes).map_err(|e| {
|
||||
let raw_body = String::from_utf8_lossy(&bytes);
|
||||
tracing::error!(error = %e, raw_body = %raw_body, "Response body is not JSON");
|
||||
SamplingError::Serialization(e)
|
||||
})?;
|
||||
// A `max` effort echo is unrepresentable in the typed enum — drop it
|
||||
// rather than failing the whole response.
|
||||
kigi_sampling_types::normalize_effort_echo(&mut response_value);
|
||||
let response_obj = serde_json::from_value::<rs::Response>(response_value).map_err(|e| {
|
||||
let raw_body = String::from_utf8_lossy(&bytes);
|
||||
tracing::error!(
|
||||
error = %e,
|
||||
@@ -1156,6 +1169,7 @@ impl SamplingClient {
|
||||
}
|
||||
}
|
||||
kigi_sampling_types::patch_reasoning_text_types(&mut request_body);
|
||||
kigi_sampling_types::patch_reasoning_effort(&mut request_body, request.reasoning_effort);
|
||||
// Fresh per attempt so signals never leak across retries; `None`
|
||||
// (check disabled) sends no header and does no peek work per event.
|
||||
let doom_loop = self
|
||||
@@ -1687,6 +1701,7 @@ impl SamplingClient {
|
||||
let responses_request: rs::CreateResponse = (&request).into();
|
||||
|
||||
let mut wrapper = CreateResponseWrapper::new(responses_request);
|
||||
wrapper.reasoning_effort = request.reasoning_effort;
|
||||
wrapper.x_kigi_conv_id = x_kigi_conv_id;
|
||||
wrapper.x_kigi_req_id = x_kigi_req_id;
|
||||
wrapper.x_kigi_session_id = x_kigi_session_id;
|
||||
@@ -1720,6 +1735,7 @@ impl SamplingClient {
|
||||
let responses_request: rs::CreateResponse = (&request).into();
|
||||
|
||||
let mut wrapper = CreateResponseWrapper::new(responses_request);
|
||||
wrapper.reasoning_effort = request.reasoning_effort;
|
||||
wrapper.x_kigi_conv_id = x_kigi_conv_id;
|
||||
wrapper.x_kigi_req_id = x_kigi_req_id;
|
||||
wrapper.x_kigi_session_id = x_kigi_session_id;
|
||||
|
||||
Reference in New Issue
Block a user