openai / openai/codex

VS Code Sessions: surface live Codex thread status and remaining usage from existing app-server events

Open
#38,883 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

app-server enhancement extension rate-limits session
Dominant language
Rust
Stars
125k
Forks
19.5k
PR merge metrics
PR metrics pending

Description

What variant of Codex are you using?

IDE Extension (VS Code) + Codex app-server

What feature would you like to see?

I would like the Codex VS Code Sessions sidebar to surface the live state of each Codex session and optionally show the remaining Codex usage limits.

This is related to #38759, but after inspecting the current open-source openai/codex implementation, the required backend status contract appears to already exist in codex app-server.

The remaining work appears to be primarily in the VS Code extension: map the existing Codex thread state into VS Code's session status/badge UI.

Desired Sessions sidebar states

Each Codex session should visibly distinguish:

  • Working
  • Waiting for input
  • Waiting for approval
  • Idle
  • Completed
  • Failed / Error
  • Interrupted

for example:

Working · 5h 74% left · week 43% left

This is related to #38759.

Existing Codex protocol already supports the required state

After inspecting the current open-source openai/codex implementation, the required backend status contract appears to already exist in codex app-server.

ThreadStatus currently supports:

export type ThreadStatus =
  | { type: "notLoaded" }
  | { type: "idle" }
  | { type: "systemError" }
  | {
      type: "active";
      activeFlags: Array<ThreadActiveFlag>;
    };

with:

export type ThreadActiveFlag =
  | "waitingOnApproval"
  | "waitingOnUserInput";

The app-server also emits:

thread/status/changed

with:

export type ThreadStatusChangedNotification = {
  threadId: string;
  status: ThreadStatus;
};

thread/list already returns the current Thread.status, so the Sessions sidebar can initialize each item immediately and then update it incrementally through thread/status/changed.

Proposed VS Code mapping
Codex state VS Code state Display badge
active with no flags InProgress Working
active + waitingOnUserInput NeedsInput Waiting for input
active + waitingOnApproval NeedsInput Waiting for approval
systemError Failed Error
idle + last turn completed Completed Completed
idle + last turn failed Failed Failed
idle neutral Idle
last turn interrupted neutral Interrupted
notLoaded neutral / previous terminal state No live activity

Terminal state should not be inferred from ThreadStatus::Idle alone.

Codex separately exposes turn outcome:

export type TurnStatus =
  | "completed"
  | "interrupted"
  | "failed"
  | "inProgress";

The extension can therefore combine:

thread/status/changed -> live activity
turn/completed        -> terminal outcome

This should make the Sessions sidebar fully event-driven without polling or introducing another backend status model.

Additional information

Additional information

Reference TypeScript implementation

Because the VS Code extension UI code is not currently included in the public Codex repository, this is intended as a reference implementation for the internal extension-side Sessions controller.

import * as vscode from "vscode";

type ThreadActiveFlag =
  | "waitingOnApproval"
  | "waitingOnUserInput";

type ThreadStatus =
  | { type: "notLoaded" }
  | { type: "idle" }
  | { type: "systemError" }
  | {
      type: "active";
      activeFlags: ThreadActiveFlag[];
    };

type TurnStatus =
  | "completed"
  | "interrupted"
  | "failed"
  | "inProgress";

interface SessionPresentation {
  status?: vscode.ChatSessionStatus;
  badge?: string;
  tooltip: string;
}

function mapCodexSessionStatus(
  threadStatus: ThreadStatus,
  lastTurnStatus?: TurnStatus,
): SessionPresentation {
  if (threadStatus.type === "active") {
    if (
      threadStatus.activeFlags.includes("waitingOnApproval")
    ) {
      return {
        status: vscode.ChatSessionStatus.NeedsInput,
        badge: "Waiting for approval",
        tooltip: "Codex is waiting for an approval.",
      };
    }

    if (
      threadStatus.activeFlags.includes("waitingOnUserInput")
    ) {
      return {
        status: vscode.ChatSessionStatus.NeedsInput,
        badge: "Waiting for input",
        tooltip: "Codex is waiting for your input.",
      };
    }

    return {
      status: vscode.ChatSessionStatus.InProgress,
      badge: "Working",
      tooltip: "Codex is currently working.",
    };
  }

  if (threadStatus.type === "systemError") {
    return {
      status: vscode.ChatSessionStatus.Failed,
      badge: "Error",
      tooltip: "The Codex session encountered a system error.",
    };
  }

  switch (lastTurnStatus) {
    case "completed":
      return {
        status: vscode.ChatSessionStatus.Completed,
        badge: "Completed",
        tooltip: "The most recent Codex turn completed.",
      };

    case "failed":
      return {
        status: vscode.ChatSessionStatus.Failed,
        badge: "Failed",
        tooltip: "The most recent Codex turn failed.",
      };

    case "interrupted":
      return {
        badge: "Interrupted",
        tooltip: "The most recent Codex turn was interrupted.",
      };
  }

  if (threadStatus.type === "idle") {
    return {
      badge: "Idle",
      tooltip: "This Codex session is loaded but has no active turn.",
    };
  }

  return {
    tooltip: "This Codex session is not currently loaded.",
  };
}
Event-driven session updates

Keep the latest terminal turn outcome per thread:

const lastTurnStatusByThread =
  new Map<string, TurnStatus>();

Then process the existing app-server events:

function handleServerNotification(
  method: string,
  params: unknown,
): void {
  switch (method) {
    case "thread/status/changed": {
      const event = params as {
        threadId: string;
        status: ThreadStatus;
      };

      const thread = threads.get(event.threadId);
      const item = sessionItems.get(event.threadId);

      if (!thread || !item) {
        return;
      }

      thread.status = event.status;

      if (event.status.type === "active") {
        lastTurnStatusByThread.delete(event.threadId);
      }

      updateSessionItem(item, thread);
      return;
    }

    case "turn/completed": {
      const event = params as {
        threadId: string;
        turn: {
          status: TurnStatus;
        };
      };

      lastTurnStatusByThread.set(
        event.threadId,
        event.turn.status,
      );

      const thread = threads.get(event.threadId);
      const item = sessionItems.get(event.threadId);

      if (thread && item) {
        updateSessionItem(item, thread);
      }

      return;
    }
  }
}

Update the corresponding VS Code session item:

function updateSessionItem(
  item: vscode.ChatSessionItem,
  thread: {
    id: string;
    status: ThreadStatus;
  },
): void {
  const presentation = mapCodexSessionStatus(
    thread.status,
    lastTurnStatusByThread.get(thread.id),
  );

  item.status = presentation.status;
  item.badge = presentation.badge;
  item.tooltip = presentation.tooltip;

  sessionController.items.add(item);
}
Remaining 5-hour / weekly usage

Codex already exposes account rate-limit information through the app-server.

A rate-limit window contains:

export type RateLimitWindow = {
  usedPercent: number;
  windowDurationMins: number | null;
  resetsAt: number | null;
};

Remaining usage can be calculated as:

function remainingPercent(
  window: RateLimitWindow,
): number {
  return Math.max(
    0,
    Math.min(100, 100 - window.usedPercent),
  );
}

The current limits can be fetched through:

account/rateLimits/read

and refreshed when receiving:

account/rateLimits/updated

A compact renderer could be:

function rateLimitLabel(
  window: RateLimitWindow,
): string {
  const remaining = Math.round(
    remainingPercent(window),
  );

  if (window.windowDurationMins === 300) {
    return `5h ${remaining}% left`;
  }

  if (
    window.windowDurationMins ===
    7 * 24 * 60
  ) {
    return `week ${remaining}% left`;
  }

  return `${remaining}% left`;
}

Then the Sessions UI could use:

item.description = [
  rateLimits.primary &&
    rateLimitLabel(rateLimits.primary),
  rateLimits.secondary &&
    rateLimitLabel(rateLimits.secondary),
]
  .filter(Boolean)
  .join(" · ");

Result:

Working
5h 74% left · week 43% left

I would use:

  • badge for the per-session runtime state
  • description for account-wide remaining usage
  • tooltip for state details and reset timestamps

The usage limits are account-wide rather than specific to a single thread, so they should not be represented as the session's main state.

Proposed event flow
thread/list
    |
    +--> initial Thread.status
    |
    v
VS Code Sessions sidebar
    |
    +-- thread/status/changed
    |      +-- Working
    |      +-- Waiting for input
    |      +-- Waiting for approval
    |      +-- Idle
    |      `-- Error
    |
    +-- turn/completed
    |      +-- Completed
    |      +-- Failed
    |      `-- Interrupted
    |
    `-- account/rateLimits/updated
           `-- refresh remaining usage

The main finding is that the open-source Codex app-server already provides the necessary status and rate-limit signals. The remaining work appears to be wiring those existing events into the VS Code Sessions UI rather than introducing another extension-specific status model.

Related: #38759

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the internal VS Code Sessions controller described in the issue and verify whether its code is available, since the extension UI is not in the public repository. Trace thread/list, thread/status/changed, turn/completed, and account/rateLimits/updated, then confirm that session badges and account-wide usage labels update for each proposed state.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust, typescript, vscode
Domain
developer-experience, frontend, tooling
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.