kcode-k1-codex-adapter 0.1.0

Concrete multiplexed Codex app-server adapter and per-conversation K1 shim
Documentation
# 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.