# 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,
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
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 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) -> 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.
Each `infer` starts one fresh native turn, immediately acknowledges every dynamic call once with the exact `ASYNC_TOOL_ACKNOWLEDGEMENT`, and drains that same turn through `Done`. It returns only terminal, atomic `ShimOutput`: no partial output and no cross-round suspension. `ShimOutput::items` preserves text and box event order; adjacent text deltas are coalesced.
`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. Failure or cancellation after accepted start poisons reuse; a start rejection leaves the shim ready.
For ordinary conversation-sized inputs, rendering and `infer` bookkeeping are linear in emitted text and boxes; `infer` waits for terminal completion.