Add Codex JSON sessions and usage limits

This commit is contained in:
iris committed 2026-09-07 23:29:15 -04:00
1 parent 0862b47f76
commit 6a0202b1b5
14 files changed
+1173 -52

No files matched your search

+23 -22
View File
@@ -156,6 +156,9 @@ pub enum DriverKind {
/// The Claude Code CLI over stream-json. Named for the CLI specifically:
/// bare "claude" would suggest the credit-billed API, which this is not.
ClaudeCli,
/// The Codex CLI's `exec --json` JSONL stream. Each process is one turn;
/// the thread id it reports is resumed by the next process.
CodexCli,
}
impl DriverKind {
@@ -176,7 +179,7 @@ impl DriverKind {
pub fn max_image_edge(self) -> Option<u32> {
match self {
DriverKind::ClaudeCli => Some(1568),
DriverKind::Echo | DriverKind::LlamaCpp => None,
DriverKind::Echo | DriverKind::LlamaCpp | DriverKind::CodexCli => None,
}
}
@@ -199,6 +202,7 @@ impl DriverKind {
pub fn usage_provider(self) -> Option<&'static str> {
match self {
Self::ClaudeCli => Some(crate::usage::CLAUDE),
Self::CodexCli => Some(crate::usage::CODEX),
Self::Echo => Some(crate::usage::ECHO),
Self::LlamaCpp => None,
}
@@ -207,12 +211,13 @@ impl DriverKind {
/// The executable a provider of this kind runs when it names none.
///
/// Here rather than at each spawn site because it is not only the spawn
/// that runs it: `usage` runs the Claude CLI too, to have it refresh its
/// own OAuth token, and a default that disagreed with the driver's would
/// ask the wrong binary on a machine with two installs.
/// that runs it: `usage` runs provider CLIs too, so a default that
/// disagreed with the driver's would ask the wrong binary on a machine
/// with two installs.
pub fn default_program(self) -> &'static str {
match self {
Self::ClaudeCli => "claude",
Self::CodexCli => "codex",
Self::LlamaCpp => "llama-server",
// Echo is translated in-process; nothing is spawned for it.
Self::Echo => "echo",
@@ -222,9 +227,8 @@ impl DriverKind {
/// Whether the conversation exists outside this app, so that deleting the
/// session here does not end it.
///
/// The Claude Code CLI owns its own transcript and is resumable from it
/// whatever started it, so a session this app spawned is every bit as
/// recoverable as one it imported. Echo has nothing to keep, and a llama
/// Claude Code and Codex own their transcripts and can resume them
/// independently of this app. Echo has nothing to keep, and a llama
/// session's conversation is folded out of *this* app's transcript.
///
/// Asked before warning somebody that a deletion cannot be undone, which is
@@ -232,7 +236,7 @@ impl DriverKind {
/// be brought back, it spends the credibility the warning needs.
pub fn keeps_own_transcript(self) -> bool {
match self {
Self::ClaudeCli => true,
Self::ClaudeCli | Self::CodexCli => true,
Self::Echo | Self::LlamaCpp => false,
}
}
@@ -242,15 +246,15 @@ impl DriverKind {
///
/// Reported from here rather than decided on the phone, and asked of the
/// *kind* rather than branched on: the alternative is the session-type
/// `if` this app does not have anywhere else. `--effort` is the Claude
/// CLI's; a llama session's sampling is `params`, and echo does not think.
/// `if` this app does not have anywhere else. Coding CLIs take an effort
/// setting; a llama session's sampling is `params`, and echo does not think.
///
/// It matters more than a control that would simply do nothing, because
/// choosing a level stops the process -- so on a session that cannot use
/// one it is a button whose only effect is the cost.
pub fn takes_effort(self) -> bool {
match self {
Self::ClaudeCli => true,
Self::ClaudeCli | Self::CodexCli => true,
Self::Echo | Self::LlamaCpp => false,
}
}
@@ -283,20 +287,17 @@ pub struct SessionConfig {
pub model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cwd: Option<PathBuf>,
/// Claude permission mode chosen at spawn. Kept as a string because it is
/// passed straight to `--permission-mode` rather than interpreted here, so
/// the CLI stays the one authority on which modes exist.
/// Provider permission mode chosen at spawn. Kept as a string because the
/// driver maps it onto its CLI rather than shared code interpreting it.
#[serde(skip_serializing_if = "Option::is_none")]
pub permission_mode: Option<String>,
/// How hard the model thinks, passed straight to `--effort`. A string for
/// the same reason `permission_mode` is: the CLI owns which levels exist.
/// How hard the model thinks. A string for the same reason
/// `permission_mode` is: the CLI owns which levels exist.
///
/// Unlike the model and the mode, there is no control request that changes
/// one -- checked against 2.1.258, whose only two are `set_model` and
/// `set_permission_mode` -- so this is settled at launch and `None` means
/// whatever the CLI's own default is. That is a state the phone has to be
/// able to *choose*, not just start in, which is why it is an option
/// rather than a level with a default written here.
/// This is settled at launch and `None` means whatever the CLI's own
/// default is. That is a state the phone has to be able to *choose*, not
/// just start in, which is why it is an option rather than a level with a
/// default written here.
#[serde(skip_serializing_if = "Option::is_none")]
pub effort: Option<String>,
/// Settings the driver interprets, chosen at spawn.