kcode-codex-runtime-v2 0.1.3

Native dynamic-tool turns through the Codex app-server protocol
Documentation
# Documentation

Create a `CodexConfig`, optionally attach the sanitized model catalog that the
host has already verified, then open `Codex`.

## Standard text and dynamic-tool turns

Build an `AgentRequest` and add dynamic functions with `DynamicTool::new`.
Start it with `Codex::start_turn`, then iterate `AgentTurn::next_event`:

- handle each `AgentEvent::ProviderInput` as the exact client-to-Codex JSONL
  record;
- execute each `AgentEvent::ToolCall` in the application and return its
  matching `ToolResult` with `AgentTurn::respond`;
- consume `AgentEvent::Completed` as the terminal turn result.

```rust
use kcode_codex_runtime_v2::{
    AgentEvent, AgentRequest, Codex, CodexConfig, DynamicTool, ToolResult,
};
use serde_json::json;

# async fn example() -> Result<(), Box<dyn std::error::Error>> {
let codex = Codex::open(CodexConfig::default()).await?;
let mut request = AgentRequest::new("Use the supplied lookup when useful.", "gpt-5.4");
request.tools.push(DynamicTool::new(
    "lookup",
    "Look up one value",
    json!({
        "type": "object",
        "properties": {"key": {"type": "string"}},
        "required": ["key"]
    }),
));

let mut turn = codex.start_turn(request).await?;
while let Some(event) = turn.next_event().await {
    match event? {
        AgentEvent::ProviderInput(exact_jsonl) => {
            // Retain or inspect the exact outbound transport record.
            let _ = exact_jsonl;
        }
        AgentEvent::ToolCall(call) => {
            turn.respond(call.call_id, ToolResult::success("example value"))
                .await?;
        }
        AgentEvent::Completed(completed) => {
            println!("{}", completed.answer);
            break;
        }
    }
}
# Ok(())
# }
```

One dynamic function call represents one application operation. Codex may make
several calls in a model response. This crate exposes and completes dynamic
calls sequentially in server-request order.

Dynamic tools are declared only on fresh threads. Codex persists their
definitions with the thread and restores them during `thread/resume`.

## Image-analysis turns

`Codex::start_image_turn` is deliberately separate from the standard text
flow. It accepts typed, caller-owned bytes, creates a fresh ephemeral thread
with no dynamic functions, sends each image as an inline Base64 data URL, and
places the exact caller prompt last.

```rust
use kcode_codex_runtime_v2::{
    AgentEvent, Codex, CodexConfig, ImageInput, ImageMediaType,
    ImageTurnRequest,
};

# async fn example(png_bytes: Vec<u8>) -> Result<(), Box<dyn std::error::Error>> {
let codex = Codex::open(CodexConfig::default()).await?;
let image = ImageInput::new(ImageMediaType::Png, png_bytes)?;
let request = ImageTurnRequest::new(
    "Read every visible label and explain the diagram.",
    "gpt-5.4",
    vec![image],
);

let mut turn = codex.start_image_turn(request).await?;
while let Some(event) = turn.next_event().await {
    match event? {
        AgentEvent::ProviderInput(exact_jsonl) => {
            // The turn/start record contains the complete Base64 image.
            // Treat it as media-bearing data; do not copy it into a text journal.
            let _ = exact_jsonl;
        }
        AgentEvent::ToolCall(_) => unreachable!("image turns have no dynamic tools"),
        AgentEvent::Completed(completed) => {
            println!("{}", completed.answer);
            break;
        }
    }
}
# Ok(())
# }
```

Supported declared media types are PNG, JPEG, WebP, and GIF. Every image must
be nonempty. A turn accepts one to eight images and at most 20 MiB of image
bytes in aggregate. The runtime preserves image order and does not inspect,
decode, copy, or retain the bytes beyond the running turn.

The runtime applies `CodexConfig::base_instruction` to image turns as it does
to other fresh threads. Use an empty or deliberately minimal base instruction
when the supplied analysis prompt should be the only application-authored
instruction.

Dropping or cancelling `AgentTurn` terminates the owned app-server process.
The caller remains responsible for cancelling an image turn when its parent
operation is cancelled.

## Transport and result semantics

Every app-server process receives `tool_output_token_limit=180000`, overriding
the selected model's smaller default tool-output policy.

`AgentEvent::ProviderInput` exposes exact outbound JSONL rather than a friendly
rendering. This is the observable client-to-Codex boundary. It cannot claim to
show provider-side instructions or transformations that Codex does not expose.

For image turns, the `turn/start` record necessarily contains the full Base64
data URL. Consumers should retain the authoritative media object separately
and journal only bounded metadata such as its object identity, media type,
byte length, and hash.

`CompletedTurn::answer` remains ordinary prompt-directed text, and `usage`
contains the latest accounting Codex reported. General image understanding
does not guarantee OCR accuracy, bounding boxes, masks, coordinates,
segmentation, or deterministic annotations.