feat: add configurable transport and codex websocket session caching

This commit is contained in:
Mario Zechner 2026-02-13 23:41:49 +01:00
parent 9537919a49
commit a26a9cfabd
15 changed files with 580 additions and 4 deletions

View file

@ -2,6 +2,15 @@
## [Unreleased]
### Added
- Added `transport` setting (`"sse"`, `"websocket"`, `"auto"`) to `/settings` and `settings.json` for providers that support multiple transports (currently `openai-codex` via OpenAI Codex Responses).
### Changed
- Interactive mode now applies transport changes immediately to the active agent session.
- Settings migration now maps legacy `websockets: boolean` to the new `transport` setting.
## [0.52.11] - 2026-02-13
### Added

View file

@ -150,7 +150,7 @@ Type `/` in the editor to trigger commands. [Extensions](#extensions) can regist
| `/login`, `/logout` | OAuth authentication |
| `/model` | Switch models |
| `/scoped-models` | Enable/disable models for Ctrl+P cycling |
| `/settings` | Thinking level, theme, message delivery |
| `/settings` | Thinking level, theme, message delivery, transport |
| `/resume` | Pick from previous sessions |
| `/new` | Start a new session |
| `/name <name>` | Set session display name |
@ -193,7 +193,7 @@ Submit messages while the agent is working:
- **Escape** aborts and restores queued messages to editor
- **Alt+Up** retrieves queued messages back to editor
Configure delivery in [settings](docs/settings.md): `steeringMode` and `followUpMode` can be `"one-at-a-time"` (default, waits for response) or `"all"` (delivers all queued at once).
Configure delivery in [settings](docs/settings.md): `steeringMode` and `followUpMode` can be `"one-at-a-time"` (default, waits for response) or `"all"` (delivers all queued at once). `transport` selects provider transport preference (`"sse"`, `"websocket"`, or `"auto"`) for providers that support multiple transports.
---

View file

@ -41,7 +41,7 @@ Edit directly or use `/settings` for common options.
| `theme` | string | `"dark"` | Theme name (`"dark"`, `"light"`, or custom) |
| `quietStartup` | boolean | `false` | Hide startup header |
| `collapseChangelog` | boolean | `false` | Show condensed changelog after updates |
| `doubleEscapeAction` | string | `"tree"` | Action for double-escape: `"tree"` or `"fork"` |
| `doubleEscapeAction` | string | `"tree"` | Action for double-escape: `"tree"`, `"fork"`, or `"none"` |
| `editorPaddingX` | number | `0` | Horizontal padding for input editor (0-3) |
| `autocompleteMaxVisible` | number | `5` | Max visible items in autocomplete dropdown (3-20) |
| `showHardwareCursor` | boolean | `false` | Show terminal cursor |
@ -98,12 +98,14 @@ When a provider requests a retry delay longer than `maxDelayMs` (e.g., Google's
|---------|------|---------|-------------|
| `steeringMode` | string | `"one-at-a-time"` | How steering messages are sent: `"all"` or `"one-at-a-time"` |
| `followUpMode` | string | `"one-at-a-time"` | How follow-up messages are sent: `"all"` or `"one-at-a-time"` |
| `transport` | string | `"sse"` | Preferred transport for providers that support multiple transports: `"sse"`, `"websocket"`, or `"auto"` |
### Terminal & Images
| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `terminal.showImages` | boolean | `true` | Show images in terminal (if supported) |
| `terminal.clearOnShrink` | boolean | `false` | Clear empty rows when content shrinks (can cause flicker) |
| `images.autoResize` | boolean | `true` | Resize images to 2000x2000 max |
| `images.blockImages` | boolean | `false` | Block all images from being sent to LLM |

View file

@ -300,6 +300,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
},
steeringMode: settingsManager.getSteeringMode(),
followUpMode: settingsManager.getFollowUpMode(),
transport: settingsManager.getTransport(),
thinkingBudgets: settingsManager.getThinkingBudgets(),
maxRetryDelayMs: settingsManager.getRetrySettings().maxDelayMs,
getApiKey: async (provider) => {

View file

@ -1,3 +1,4 @@
import type { Transport } from "@mariozechner/pi-ai";
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
import { dirname, join } from "path";
import { CONFIG_DIR_NAME, getAgentDir } from "../config.js";
@ -40,6 +41,8 @@ export interface MarkdownSettings {
codeBlockIndent?: string; // default: " "
}
export type TransportSetting = Transport;
/**
* Package source for npm/git packages.
* - String form: load all resources from the package
@ -60,6 +63,7 @@ export interface Settings {
defaultProvider?: string;
defaultModel?: string;
defaultThinkingLevel?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
transport?: TransportSetting; // default: "sse"
steeringMode?: "all" | "one-at-a-time";
followUpMode?: "all" | "one-at-a-time";
theme?: string;
@ -188,6 +192,12 @@ export class SettingsManager {
delete settings.queueMode;
}
// Migrate legacy websockets boolean -> transport enum
if (!("transport" in settings) && typeof settings.websockets === "boolean") {
settings.transport = settings.websockets ? "websocket" : "sse";
delete settings.websockets;
}
// Migrate old skills object format to new array format
if (
"skills" in settings &&
@ -433,6 +443,16 @@ export class SettingsManager {
this.save();
}
getTransport(): TransportSetting {
return this.settings.transport ?? "sse";
}
setTransport(transport: TransportSetting): void {
this.globalSettings.transport = transport;
this.markModified("transport");
this.save();
}
getCompactionEnabled(): boolean {
return this.settings.compaction?.enabled ?? true;
}

View file

@ -1,4 +1,5 @@
import type { ThinkingLevel } from "@mariozechner/pi-agent-core";
import type { Transport } from "@mariozechner/pi-ai";
import {
Container,
getCapabilities,
@ -29,6 +30,7 @@ export interface SettingsConfig {
enableSkillCommands: boolean;
steeringMode: "all" | "one-at-a-time";
followUpMode: "all" | "one-at-a-time";
transport: Transport;
thinkingLevel: ThinkingLevel;
availableThinkingLevels: ThinkingLevel[];
currentTheme: string;
@ -51,6 +53,7 @@ export interface SettingsCallbacks {
onEnableSkillCommandsChange: (enabled: boolean) => void;
onSteeringModeChange: (mode: "all" | "one-at-a-time") => void;
onFollowUpModeChange: (mode: "all" | "one-at-a-time") => void;
onTransportChange: (transport: Transport) => void;
onThinkingLevelChange: (level: ThinkingLevel) => void;
onThemeChange: (theme: string) => void;
onThemePreview?: (theme: string) => void;
@ -162,6 +165,13 @@ export class SettingsSelectorComponent extends Container {
currentValue: config.followUpMode,
values: ["one-at-a-time", "all"],
},
{
id: "transport",
label: "Transport",
description: "Preferred transport for providers that support multiple transports",
currentValue: config.transport,
values: ["sse", "websocket", "auto"],
},
{
id: "hide-thinking",
label: "Hide thinking",
@ -354,6 +364,9 @@ export class SettingsSelectorComponent extends Container {
case "follow-up-mode":
callbacks.onFollowUpModeChange(newValue as "all" | "one-at-a-time");
break;
case "transport":
callbacks.onTransportChange(newValue as Transport);
break;
case "hide-thinking":
callbacks.onHideThinkingBlockChange(newValue === "true");
break;

View file

@ -3034,6 +3034,7 @@ export class InteractiveMode {
enableSkillCommands: this.settingsManager.getEnableSkillCommands(),
steeringMode: this.session.steeringMode,
followUpMode: this.session.followUpMode,
transport: this.settingsManager.getTransport(),
thinkingLevel: this.session.thinkingLevel,
availableThinkingLevels: this.session.getAvailableThinkingLevels(),
currentTheme: this.settingsManager.getTheme() || "dark",
@ -3076,6 +3077,10 @@ export class InteractiveMode {
onFollowUpModeChange: (mode) => {
this.session.setFollowUpMode(mode);
},
onTransportChange: (transport) => {
this.settingsManager.setTransport(transport);
this.session.agent.setTransport(transport);
},
onThinkingLevelChange: (level) => {
this.session.setThinkingLevel(level);
this.footer.invalidate();