# kcode-k1-codex-adapter
## Adapter API
use serde_json::Value;
use std::path::PathBuf;
#[derive(Clone, Debug)]
pub struct Config {
pub executable: PathBuf,
pub working_directory: String,
pub model: String,
pub reasoning_effort: Option<String>,
pub base_instructions: String,
pub tools: Vec<DynamicTool>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct DynamicTool {
pub name: String,
pub description: String,
pub input_schema: Value,
}
#[derive(Clone, Debug, PartialEq)]
pub struct ToolCall {
pub call_id: String,
pub name: String,
pub arguments: Value,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ToolResult {
pub success: bool,
pub output: String,
}
#[derive(Clone, Debug, PartialEq)]
pub enum Event {
TextDelta(String),
ToolCall(ToolCall),
Done,
Error(Error),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ErrorKind {
Busy,
Interrupted,
InvalidToolResult,
LaunchRejected,
Protocol,
Server,
Unavailable,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Error {
pub kind: ErrorKind,
pub message: String,
pub diagnostics: Vec<u8>,
}
impl std::fmt::Display for Error {}
impl std::error::Error for Error {}
#[derive(Clone)]
pub struct Adapter { /* private fields */ }
impl Adapter {
pub async fn open(config: Config) -> Result<Self, Error>;
pub async fn start_turn(
&self,
conversation_key: impl Into<String>,
input: impl Into<String>,
) -> Result<Turn, Error>;
pub fn diagnostics(&self) -> Vec<u8>;
}
pub struct Turn { /* private fields */ }
impl std::fmt::Debug for Turn {}
impl Turn {
pub async fn next_event(&mut self) -> Option<Event>;
pub async fn respond(
&self,
call_id: impl Into<String>,
result: ToolResult,
) -> Result<(), Error>;
}
`open` starts and initializes the child. A conversation key has at most one active turn; an overlapping `start_turn` returns `ErrorKind::Busy`. `next_event` yields provider events in order and ends in `Done` or `Error`; dropping a nonterminal turn abandons it. `respond` accepts only the exact pending call ID. `diagnostics` returns captured child stderr bytes. Dropping all adapter handles closes and reaps the child.
For ordinary conversation-sized requests, configuration validation and diagnostics copying are linear in their input size; async operations wait for the child and its app-server responses.
## Shim API
use std::{future::Future, pin::Pin};
pub const ASYNC_TOOL_ACKNOWLEDGEMENT: &str =
"The tool was launched asynchronously. Its result will not be available during this turn. Do not wait for or poll this call; continue the turn without its result. The result will be provided in the next user turn.";
pub type ToolLaunchFuture<'a> =
Pin<Box<dyn Future<Output = Result<(), String>> + Send + 'a>>;
pub trait ToolCallLauncher<B>: Send {
fn launch<'a>(&'a mut self, box_: &'a B) -> ToolLaunchFuture<'a>;
}
pub trait BoxCodec {
type Box: Clone;
fn tool_call_box(&mut self, call: &ToolCall) -> Self::Box;
fn box_text<'a>(&self, box_: &'a Self::Box) -> &'a str;
}
#[derive(Clone, Debug, PartialEq)]
pub enum ShimItem<B> {
Text(String),
Box(B),
}
#[derive(Clone, Debug, PartialEq)]
pub struct ShimOutput<B> {
pub items: Vec<ShimItem<B>>,
}
pub struct Shim<C: BoxCodec> { /* private fields */ }
impl<C: BoxCodec> Shim<C> {
pub fn new(
adapter: Adapter,
conversation_key: impl Into<String>,
codec: C,
launcher: Box<dyn ToolCallLauncher<C::Box>>,
) -> Self;
pub fn record_box(&mut self, box_: C::Box);
pub fn record_boxes(&mut self, boxes: impl IntoIterator<Item = C::Box>);
pub fn pending_box_count(&self) -> usize;
pub async fn infer(
&mut self,
input: impl Into<String>,
) -> Result<ShimOutput<C::Box>, Error>;
}
Use one `Shim` sequentially for one conversation; shims may share cloned adapters. `BoxCodec::tool_call_box` converts every dynamic call exactly once, and `box_text` supplies that box’s complete Codex-visible text.
For every dynamic call, `infer` first awaits `ToolCallLauncher::launch` with the converted canonical box. `Ok(())` means K1 accepted and started or queued the asynchronous tool, not that it completed. The launcher must return promptly. The shim then immediately answers that exact native call with `ASYNC_TOOL_ACKNOWLEDGEMENT` and continues the same Codex turn. Calls are launched sequentially in provider event order without a count limit, timer, batching heuristic, extra turn, or current-turn tool result.
A launcher `Err(String)` certifies that the tool was not launched and returns `ErrorKind::LaunchRejected`. A successful launch is not rolled back if acknowledgement or later inference fails; the consumer must preserve every accepted call and must not blindly retry or duplicate its effect.
Each `infer` drains one native turn through `Done` and returns only terminal, atomic `ShimOutput`. No partial output or cross-round suspension is exposed. `ShimOutput::items` preserves text and box event order; adjacent text deltas are coalesced. Failure or cancellation after accepted start poisons reuse, while a start rejection leaves the shim ready.
`record_box` and `record_boxes` append externally recorded boxes in order. Pending boxes prefix the next fresh turn before ordinary input and are removed only after start acceptance. Dynamic-call boxes are output only and are not queued automatically.
For ordinary conversation-sized inputs, rendering and `infer` bookkeeping are linear in emitted text, boxes, and calls; `infer` and each launch wait for their respective provider or consumer completion.