[codex] Add Pi provider integration - #2831

Closed
AmbitiousRealism2025 wants to merge 3 commits into
pingdotgg:mainfrom
AmbitiousRealism2025:codex/pi-provider
Closed

[codex] Add Pi provider integration#2831
AmbitiousRealism2025 wants to merge 3 commits into
pingdotgg:mainfrom
AmbitiousRealism2025:codex/pi-provider

Conversation

@AmbitiousRealism2025

@AmbitiousRealism2025AmbitiousRealism2025 commented May 27, 2026

Copy link
Copy Markdown

Summary

Adds Pi as a selectable provider in T3 Code using Pi's local CLI/RPC integration rather than treating it as a generic API provider. The adapter discovers and uses the existing local Pi configuration, surfaces configured Pi models in the selector, supports Pi slash-command metadata in provider discovery, and routes prompts through Pi sessions.

Follow-up fixes in this branch also make Pi prompt timeouts activity-based for long tool-heavy turns and map Pi tool execution events into T3 provider runtime tool lifecycle events so the Work Log can show progress while Pi is using tools.

Implementation Notes

  • Adds Pi provider settings, driver metadata, provider registry wiring, and UI picker metadata.
  • Adds Pi RPC helpers for local model/config discovery, prompt execution, text delta streaming, and tool result extraction.
  • Adds a Pi provider adapter and text generation layer that use existing ~/.pi/agent configuration.
  • Keeps Pi-specific behavior isolated behind the existing provider adapter/runtime contracts.
  • Preserves existing provider behavior for Claude, Codex, OpenCode, Cursor, and ACP providers.

Validation

  • bun install
  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test
  • bunx vitest run src/provider/Layers/PiRpc.test.ts
  • Local Pi RPC smoke against existing ~/.pi/agent config
  • Manual local T3 Code verification with Pi selectable, Pi models visible, basic Pi prompt completing, and Pi tool events observed during a bash-tool prompt

Notes / Risks

  • Pi is a CLI/TUI-backed provider integration, so maintainability depends on Pi RPC event compatibility.
  • Slash-command parity is documented in PI_PARITY.md; commands that are inherently Pi-native are surfaced/documented rather than reimplemented as separate T3 UI features.
  • Long Pi turns are bounded by idle timeout after activity, not by total wall-clock duration, to avoid killing healthy multi-tool Pi runs.

Note

Add Pi provider integration with session management, streaming, and text generation

  • Registers a new pi provider driver kind in contracts, settings, and the built-in driver registry, removing it from the "Coming soon" list in the settings UI.
  • Adds PiRpc.ts with utilities to invoke the Pi CLI as a child process and communicate via newline-delimited JSON RPC, supporting streaming events, abort signals, and per-activity timeouts.
  • Adds PiAdapter.ts implementing the full ProviderAdapterShape for Pi: session lifecycle, streaming assistant text and tool events, attachment handling, turn interruption, and in-memory session state.
  • Adds PiTextGeneration.ts for Pi-backed commit messages, PR content, branch names, and thread titles via the RPC prompt path.
  • Adds PiProvider.ts for provider status checks, model discovery via pi --version and Pi RPC inventory, and slash command surfacing.
  • Risk: Pi integration depends on an external binary resolved from PI_CODING_AGENT_DIR or ~/.pi/agent; misconfiguration or binary absence will surface as probe errors at status-check time.
📊 Macroscope summarized 9c1c57e. 17 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

@coderabbitai

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 275c764f-0fd0-455c-b361-f827f608b4fc

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels May 27, 2026
Comment on lines +149 to +201
readonly resetTimeoutOnActivity?: boolean | undefined;
}): Promise<PiRpcRunResult> {
return await new Promise((resolve, reject) => {
const child = spawn(input.binaryPath, ["--mode", "rpc", ...(input.args ?? [])], {
cwd: input.cwd,
env: input.environment ?? process.env,
shell: process.platform === "win32",
stdio: ["pipe", "pipe", "pipe"],
});

const responses = new Map<string, PiRpcLine>();
const pending = new Set(input.commands.map((command) => command.id));
const events: PiRpcLine[] = [];
let stderr = "";
let stdoutBuffer = "";
let agentEnded = false;
let requestedClose = false;
let settled = false;

const finishIfReady = () => {
if (pending.size > 0) return;
if (input.waitForAgentEnd === true && !agentEnded) return;
if (requestedClose) return;
requestedClose = true;
child.stdin.end();
};

const rejectOnce = (error: Error) => {
if (settled) return;
settled = true;
clearTimeout(timer);
child.kill("SIGTERM");
reject(error);
};

const timeoutMs = input.timeoutMs ?? 120_000;
let timer: ReturnType<typeof setTimeout>;
const resetTimer = () => {
clearTimeout(timer);
// @effect-diagnostics-next-line globalTimers:off
timer = setTimeout(() => {
rejectOnce(new Error(`Timed out waiting for Pi RPC. Stderr: ${stderr.trim()}`));
}, timeoutMs);
};

// @effect-diagnostics-next-line globalTimers:off
timer = setTimeout(() => {
rejectOnce(new Error(`Timed out waiting for Pi RPC. Stderr: ${stderr.trim()}`));
}, timeoutMs);

input.signal?.addEventListener("abort", () => {
rejectOnce(new Error("Pi RPC request was aborted."));
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 LowLayers/PiRpc.ts:149

When an already-aborted AbortSignal is passed, the abort listener is never triggered, so the function spawns the child process and runs to completion or timeout instead of immediately rejecting. Consider checking input.signal?.aborted before spawning and rejecting early if already aborted.

+ if (input.signal?.aborted) {+ rejectOnce(new Error("Pi RPC request was aborted."));+ return;+ }+
input.signal?.addEventListener("abort", () => {
rejectOnce(new Error("Pi RPC request was aborted."));
});
🤖 Copy this AI Prompt to have your agent fix this:
In file apps/server/src/provider/Layers/PiRpc.ts around lines 149-201:
When an already-aborted `AbortSignal` is passed, the `abort` listener is never triggered, so the function spawns the child process and runs to completion or timeout instead of immediately rejecting. Consider checking `input.signal?.aborted` before spawning and rejecting early if already aborted.
Evidence trail:
apps/server/src/provider/Layers/PiRpc.ts lines 139-201 at REVIEWED_COMMIT: no `signal.aborted` check before `spawn()` on line 152; `addEventListener('abort', ...)` on line 199 won't fire for already-aborted signals per Node.js EventTarget spec. Node.js v22+ docs confirm `addEventListener` does not retroactively fire: https://nodejs.org/api/events.html (events.addAbortListener section).

operation: "startSession",
issue: `Expected provider '${PROVIDER}' but received '${input.provider}'.`,
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MediumLayers/PiAdapter.ts:285

When startSession replaces an existing session at lines 286-290, it aborts the old session and deletes it from sessions but never emits a session.exited event. This differs from stopSession which emits the event, so downstream consumers cannot track the lifecycle transition when sessions are replaced.

🤖 Copy this AI Prompt to have your agent fix this:
In file apps/server/src/provider/Layers/PiAdapter.ts around line 285:
When `startSession` replaces an existing session at lines 286-290, it aborts the old session and deletes it from `sessions` but never emits a `session.exited` event. This differs from `stopSession` which emits the event, so downstream consumers cannot track the lifecycle transition when sessions are replaced.
Evidence trail:
apps/server/src/provider/Layers/PiAdapter.ts lines 286-290 (startSession replaces existing session without emitting event), lines 686-700 (stopSession emits session.exited event after similar abort+delete), lines 759-761 (streamEvents exposes runtimeEvents queue to consumers).

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@AmbitiousRealism2025
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

[codex] Add Pi provider integration - #2831

Closed
AmbitiousRealism2025 wants to merge 3 commits into
pingdotgg:mainfrom
AmbitiousRealism2025:codex/pi-provider
Closed

[codex] Add Pi provider integration#2831
AmbitiousRealism2025 wants to merge 3 commits into
pingdotgg:mainfrom
AmbitiousRealism2025:codex/pi-provider

Conversation

@AmbitiousRealism2025

@AmbitiousRealism2025AmbitiousRealism2025 commented May 27, 2026

Copy link
Copy Markdown

Summary

Adds Pi as a selectable provider in T3 Code using Pi's local CLI/RPC integration rather than treating it as a generic API provider. The adapter discovers and uses the existing local Pi configuration, surfaces configured Pi models in the selector, supports Pi slash-command metadata in provider discovery, and routes prompts through Pi sessions.

Follow-up fixes in this branch also make Pi prompt timeouts activity-based for long tool-heavy turns and map Pi tool execution events into T3 provider runtime tool lifecycle events so the Work Log can show progress while Pi is using tools.

Implementation Notes

  • Adds Pi provider settings, driver metadata, provider registry wiring, and UI picker metadata.
  • Adds Pi RPC helpers for local model/config discovery, prompt execution, text delta streaming, and tool result extraction.
  • Adds a Pi provider adapter and text generation layer that use existing ~/.pi/agent configuration.
  • Keeps Pi-specific behavior isolated behind the existing provider adapter/runtime contracts.
  • Preserves existing provider behavior for Claude, Codex, OpenCode, Cursor, and ACP providers.

Validation

  • bun install
  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test
  • bunx vitest run src/provider/Layers/PiRpc.test.ts
  • Local Pi RPC smoke against existing ~/.pi/agent config
  • Manual local T3 Code verification with Pi selectable, Pi models visible, basic Pi prompt completing, and Pi tool events observed during a bash-tool prompt

Notes / Risks

  • Pi is a CLI/TUI-backed provider integration, so maintainability depends on Pi RPC event compatibility.
  • Slash-command parity is documented in PI_PARITY.md; commands that are inherently Pi-native are surfaced/documented rather than reimplemented as separate T3 UI features.
  • Long Pi turns are bounded by idle timeout after activity, not by total wall-clock duration, to avoid killing healthy multi-tool Pi runs.

Note

Add Pi provider integration with session management, streaming, and text generation

  • Registers a new pi provider driver kind in contracts, settings, and the built-in driver registry, removing it from the "Coming soon" list in the settings UI.
  • Adds PiRpc.ts with utilities to invoke the Pi CLI as a child process and communicate via newline-delimited JSON RPC, supporting streaming events, abort signals, and per-activity timeouts.
  • Adds PiAdapter.ts implementing the full ProviderAdapterShape for Pi: session lifecycle, streaming assistant text and tool events, attachment handling, turn interruption, and in-memory session state.
  • Adds PiTextGeneration.ts for Pi-backed commit messages, PR content, branch names, and thread titles via the RPC prompt path.
  • Adds PiProvider.ts for provider status checks, model discovery via pi --version and Pi RPC inventory, and slash command surfacing.
  • Risk: Pi integration depends on an external binary resolved from PI_CODING_AGENT_DIR or ~/.pi/agent; misconfiguration or binary absence will surface as probe errors at status-check time.
📊 Macroscope summarized 9c1c57e. 17 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

@coderabbitai

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 275c764f-0fd0-455c-b361-f827f608b4fc

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels May 27, 2026
Comment on lines +149 to +201
readonly resetTimeoutOnActivity?: boolean | undefined;
}): Promise<PiRpcRunResult> {
return await new Promise((resolve, reject) => {
const child = spawn(input.binaryPath, ["--mode", "rpc", ...(input.args ?? [])], {
cwd: input.cwd,
env: input.environment ?? process.env,
shell: process.platform === "win32",
stdio: ["pipe", "pipe", "pipe"],
});

const responses = new Map<string, PiRpcLine>();
const pending = new Set(input.commands.map((command) => command.id));
const events: PiRpcLine[] = [];
let stderr = "";
let stdoutBuffer = "";
let agentEnded = false;
let requestedClose = false;
let settled = false;

const finishIfReady = () => {
if (pending.size > 0) return;
if (input.waitForAgentEnd === true && !agentEnded) return;
if (requestedClose) return;
requestedClose = true;
child.stdin.end();
};

const rejectOnce = (error: Error) => {
if (settled) return;
settled = true;
clearTimeout(timer);
child.kill("SIGTERM");
reject(error);
};

const timeoutMs = input.timeoutMs ?? 120_000;
let timer: ReturnType<typeof setTimeout>;
const resetTimer = () => {
clearTimeout(timer);
// @effect-diagnostics-next-line globalTimers:off
timer = setTimeout(() => {
rejectOnce(new Error(`Timed out waiting for Pi RPC. Stderr: ${stderr.trim()}`));
}, timeoutMs);
};

// @effect-diagnostics-next-line globalTimers:off
timer = setTimeout(() => {
rejectOnce(new Error(`Timed out waiting for Pi RPC. Stderr: ${stderr.trim()}`));
}, timeoutMs);

input.signal?.addEventListener("abort", () => {
rejectOnce(new Error("Pi RPC request was aborted."));
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 LowLayers/PiRpc.ts:149

When an already-aborted AbortSignal is passed, the abort listener is never triggered, so the function spawns the child process and runs to completion or timeout instead of immediately rejecting. Consider checking input.signal?.aborted before spawning and rejecting early if already aborted.

+ if (input.signal?.aborted) {+ rejectOnce(new Error("Pi RPC request was aborted."));+ return;+ }+
input.signal?.addEventListener("abort", () => {
rejectOnce(new Error("Pi RPC request was aborted."));
});
🤖 Copy this AI Prompt to have your agent fix this:
In file apps/server/src/provider/Layers/PiRpc.ts around lines 149-201:
When an already-aborted `AbortSignal` is passed, the `abort` listener is never triggered, so the function spawns the child process and runs to completion or timeout instead of immediately rejecting. Consider checking `input.signal?.aborted` before spawning and rejecting early if already aborted.
Evidence trail:
apps/server/src/provider/Layers/PiRpc.ts lines 139-201 at REVIEWED_COMMIT: no `signal.aborted` check before `spawn()` on line 152; `addEventListener('abort', ...)` on line 199 won't fire for already-aborted signals per Node.js EventTarget spec. Node.js v22+ docs confirm `addEventListener` does not retroactively fire: https://nodejs.org/api/events.html (events.addAbortListener section).

operation: "startSession",
issue: `Expected provider '${PROVIDER}' but received '${input.provider}'.`,
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MediumLayers/PiAdapter.ts:285

When startSession replaces an existing session at lines 286-290, it aborts the old session and deletes it from sessions but never emits a session.exited event. This differs from stopSession which emits the event, so downstream consumers cannot track the lifecycle transition when sessions are replaced.

🤖 Copy this AI Prompt to have your agent fix this:
In file apps/server/src/provider/Layers/PiAdapter.ts around line 285:
When `startSession` replaces an existing session at lines 286-290, it aborts the old session and deletes it from `sessions` but never emits a `session.exited` event. This differs from `stopSession` which emits the event, so downstream consumers cannot track the lifecycle transition when sessions are replaced.
Evidence trail:
apps/server/src/provider/Layers/PiAdapter.ts lines 286-290 (startSession replaces existing session without emitting event), lines 686-700 (stopSession emits session.exited event after similar abort+delete), lines 759-761 (streamEvents exposes runtimeEvents queue to consumers).

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@AmbitiousRealism2025
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[codex] Add Pi provider integration - #2831

Closed
AmbitiousRealism2025 wants to merge 3 commits into
pingdotgg:mainfrom
AmbitiousRealism2025:codex/pi-provider
Closed

[codex] Add Pi provider integration#2831
AmbitiousRealism2025 wants to merge 3 commits into
pingdotgg:mainfrom
AmbitiousRealism2025:codex/pi-provider

Conversation

@AmbitiousRealism2025

@AmbitiousRealism2025AmbitiousRealism2025 commented May 27, 2026

Copy link
Copy Markdown

Summary

Adds Pi as a selectable provider in T3 Code using Pi's local CLI/RPC integration rather than treating it as a generic API provider. The adapter discovers and uses the existing local Pi configuration, surfaces configured Pi models in the selector, supports Pi slash-command metadata in provider discovery, and routes prompts through Pi sessions.

Follow-up fixes in this branch also make Pi prompt timeouts activity-based for long tool-heavy turns and map Pi tool execution events into T3 provider runtime tool lifecycle events so the Work Log can show progress while Pi is using tools.

Implementation Notes

  • Adds Pi provider settings, driver metadata, provider registry wiring, and UI picker metadata.
  • Adds Pi RPC helpers for local model/config discovery, prompt execution, text delta streaming, and tool result extraction.
  • Adds a Pi provider adapter and text generation layer that use existing ~/.pi/agent configuration.
  • Keeps Pi-specific behavior isolated behind the existing provider adapter/runtime contracts.
  • Preserves existing provider behavior for Claude, Codex, OpenCode, Cursor, and ACP providers.

Validation

  • bun install
  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test
  • bunx vitest run src/provider/Layers/PiRpc.test.ts
  • Local Pi RPC smoke against existing ~/.pi/agent config
  • Manual local T3 Code verification with Pi selectable, Pi models visible, basic Pi prompt completing, and Pi tool events observed during a bash-tool prompt

Notes / Risks

  • Pi is a CLI/TUI-backed provider integration, so maintainability depends on Pi RPC event compatibility.
  • Slash-command parity is documented in PI_PARITY.md; commands that are inherently Pi-native are surfaced/documented rather than reimplemented as separate T3 UI features.
  • Long Pi turns are bounded by idle timeout after activity, not by total wall-clock duration, to avoid killing healthy multi-tool Pi runs.

Note

Add Pi provider integration with session management, streaming, and text generation

  • Registers a new pi provider driver kind in contracts, settings, and the built-in driver registry, removing it from the "Coming soon" list in the settings UI.
  • Adds PiRpc.ts with utilities to invoke the Pi CLI as a child process and communicate via newline-delimited JSON RPC, supporting streaming events, abort signals, and per-activity timeouts.
  • Adds PiAdapter.ts implementing the full ProviderAdapterShape for Pi: session lifecycle, streaming assistant text and tool events, attachment handling, turn interruption, and in-memory session state.
  • Adds PiTextGeneration.ts for Pi-backed commit messages, PR content, branch names, and thread titles via the RPC prompt path.
  • Adds PiProvider.ts for provider status checks, model discovery via pi --version and Pi RPC inventory, and slash command surfacing.
  • Risk: Pi integration depends on an external binary resolved from PI_CODING_AGENT_DIR or ~/.pi/agent; misconfiguration or binary absence will surface as probe errors at status-check time.
📊 Macroscope summarized 9c1c57e. 17 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

@coderabbitai

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 275c764f-0fd0-455c-b361-f827f608b4fc

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels May 27, 2026
Comment on lines +149 to +201
readonly resetTimeoutOnActivity?: boolean | undefined;
}): Promise<PiRpcRunResult> {
return await new Promise((resolve, reject) => {
const child = spawn(input.binaryPath, ["--mode", "rpc", ...(input.args ?? [])], {
cwd: input.cwd,
env: input.environment ?? process.env,
shell: process.platform === "win32",
stdio: ["pipe", "pipe", "pipe"],
});

const responses = new Map<string, PiRpcLine>();
const pending = new Set(input.commands.map((command) => command.id));
const events: PiRpcLine[] = [];
let stderr = "";
let stdoutBuffer = "";
let agentEnded = false;
let requestedClose = false;
let settled = false;

const finishIfReady = () => {
if (pending.size > 0) return;
if (input.waitForAgentEnd === true && !agentEnded) return;
if (requestedClose) return;
requestedClose = true;
child.stdin.end();
};

const rejectOnce = (error: Error) => {
if (settled) return;
settled = true;
clearTimeout(timer);
child.kill("SIGTERM");
reject(error);
};

const timeoutMs = input.timeoutMs ?? 120_000;
let timer: ReturnType<typeof setTimeout>;
const resetTimer = () => {
clearTimeout(timer);
// @effect-diagnostics-next-line globalTimers:off
timer = setTimeout(() => {
rejectOnce(new Error(`Timed out waiting for Pi RPC. Stderr: ${stderr.trim()}`));
}, timeoutMs);
};

// @effect-diagnostics-next-line globalTimers:off
timer = setTimeout(() => {
rejectOnce(new Error(`Timed out waiting for Pi RPC. Stderr: ${stderr.trim()}`));
}, timeoutMs);

input.signal?.addEventListener("abort", () => {
rejectOnce(new Error("Pi RPC request was aborted."));
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 LowLayers/PiRpc.ts:149

When an already-aborted AbortSignal is passed, the abort listener is never triggered, so the function spawns the child process and runs to completion or timeout instead of immediately rejecting. Consider checking input.signal?.aborted before spawning and rejecting early if already aborted.

+ if (input.signal?.aborted) {+ rejectOnce(new Error("Pi RPC request was aborted."));+ return;+ }+
input.signal?.addEventListener("abort", () => {
rejectOnce(new Error("Pi RPC request was aborted."));
});
🤖 Copy this AI Prompt to have your agent fix this:
In file apps/server/src/provider/Layers/PiRpc.ts around lines 149-201:
When an already-aborted `AbortSignal` is passed, the `abort` listener is never triggered, so the function spawns the child process and runs to completion or timeout instead of immediately rejecting. Consider checking `input.signal?.aborted` before spawning and rejecting early if already aborted.
Evidence trail:
apps/server/src/provider/Layers/PiRpc.ts lines 139-201 at REVIEWED_COMMIT: no `signal.aborted` check before `spawn()` on line 152; `addEventListener('abort', ...)` on line 199 won't fire for already-aborted signals per Node.js EventTarget spec. Node.js v22+ docs confirm `addEventListener` does not retroactively fire: https://nodejs.org/api/events.html (events.addAbortListener section).

operation: "startSession",
issue: `Expected provider '${PROVIDER}' but received '${input.provider}'.`,
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MediumLayers/PiAdapter.ts:285

When startSession replaces an existing session at lines 286-290, it aborts the old session and deletes it from sessions but never emits a session.exited event. This differs from stopSession which emits the event, so downstream consumers cannot track the lifecycle transition when sessions are replaced.

🤖 Copy this AI Prompt to have your agent fix this:
In file apps/server/src/provider/Layers/PiAdapter.ts around line 285:
When `startSession` replaces an existing session at lines 286-290, it aborts the old session and deletes it from `sessions` but never emits a `session.exited` event. This differs from `stopSession` which emits the event, so downstream consumers cannot track the lifecycle transition when sessions are replaced.
Evidence trail:
apps/server/src/provider/Layers/PiAdapter.ts lines 286-290 (startSession replaces existing session without emitting event), lines 686-700 (stopSession emits session.exited event after similar abort+delete), lines 759-761 (streamEvents exposes runtimeEvents queue to consumers).

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@AmbitiousRealism2025
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[codex] Add Pi provider integration - #2831

Closed
AmbitiousRealism2025 wants to merge 3 commits into
pingdotgg:mainfrom
AmbitiousRealism2025:codex/pi-provider
Closed

[codex] Add Pi provider integration#2831
AmbitiousRealism2025 wants to merge 3 commits into
pingdotgg:mainfrom
AmbitiousRealism2025:codex/pi-provider

Conversation

@AmbitiousRealism2025

@AmbitiousRealism2025AmbitiousRealism2025 commented May 27, 2026

Copy link
Copy Markdown

Summary

Adds Pi as a selectable provider in T3 Code using Pi's local CLI/RPC integration rather than treating it as a generic API provider. The adapter discovers and uses the existing local Pi configuration, surfaces configured Pi models in the selector, supports Pi slash-command metadata in provider discovery, and routes prompts through Pi sessions.

Follow-up fixes in this branch also make Pi prompt timeouts activity-based for long tool-heavy turns and map Pi tool execution events into T3 provider runtime tool lifecycle events so the Work Log can show progress while Pi is using tools.

Implementation Notes

  • Adds Pi provider settings, driver metadata, provider registry wiring, and UI picker metadata.
  • Adds Pi RPC helpers for local model/config discovery, prompt execution, text delta streaming, and tool result extraction.
  • Adds a Pi provider adapter and text generation layer that use existing ~/.pi/agent configuration.
  • Keeps Pi-specific behavior isolated behind the existing provider adapter/runtime contracts.
  • Preserves existing provider behavior for Claude, Codex, OpenCode, Cursor, and ACP providers.

Validation

  • bun install
  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test
  • bunx vitest run src/provider/Layers/PiRpc.test.ts
  • Local Pi RPC smoke against existing ~/.pi/agent config
  • Manual local T3 Code verification with Pi selectable, Pi models visible, basic Pi prompt completing, and Pi tool events observed during a bash-tool prompt

Notes / Risks

  • Pi is a CLI/TUI-backed provider integration, so maintainability depends on Pi RPC event compatibility.
  • Slash-command parity is documented in PI_PARITY.md; commands that are inherently Pi-native are surfaced/documented rather than reimplemented as separate T3 UI features.
  • Long Pi turns are bounded by idle timeout after activity, not by total wall-clock duration, to avoid killing healthy multi-tool Pi runs.

Note

Add Pi provider integration with session management, streaming, and text generation

  • Registers a new pi provider driver kind in contracts, settings, and the built-in driver registry, removing it from the "Coming soon" list in the settings UI.
  • Adds PiRpc.ts with utilities to invoke the Pi CLI as a child process and communicate via newline-delimited JSON RPC, supporting streaming events, abort signals, and per-activity timeouts.
  • Adds PiAdapter.ts implementing the full ProviderAdapterShape for Pi: session lifecycle, streaming assistant text and tool events, attachment handling, turn interruption, and in-memory session state.
  • Adds PiTextGeneration.ts for Pi-backed commit messages, PR content, branch names, and thread titles via the RPC prompt path.
  • Adds PiProvider.ts for provider status checks, model discovery via pi --version and Pi RPC inventory, and slash command surfacing.
  • Risk: Pi integration depends on an external binary resolved from PI_CODING_AGENT_DIR or ~/.pi/agent; misconfiguration or binary absence will surface as probe errors at status-check time.
📊 Macroscope summarized 9c1c57e. 17 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

@coderabbitai

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 275c764f-0fd0-455c-b361-f827f608b4fc

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels May 27, 2026
Comment on lines +149 to +201
readonly resetTimeoutOnActivity?: boolean | undefined;
}): Promise<PiRpcRunResult> {
return await new Promise((resolve, reject) => {
const child = spawn(input.binaryPath, ["--mode", "rpc", ...(input.args ?? [])], {
cwd: input.cwd,
env: input.environment ?? process.env,
shell: process.platform === "win32",
stdio: ["pipe", "pipe", "pipe"],
});

const responses = new Map<string, PiRpcLine>();
const pending = new Set(input.commands.map((command) => command.id));
const events: PiRpcLine[] = [];
let stderr = "";
let stdoutBuffer = "";
let agentEnded = false;
let requestedClose = false;
let settled = false;

const finishIfReady = () => {
if (pending.size > 0) return;
if (input.waitForAgentEnd === true && !agentEnded) return;
if (requestedClose) return;
requestedClose = true;
child.stdin.end();
};

const rejectOnce = (error: Error) => {
if (settled) return;
settled = true;
clearTimeout(timer);
child.kill("SIGTERM");
reject(error);
};

const timeoutMs = input.timeoutMs ?? 120_000;
let timer: ReturnType<typeof setTimeout>;
const resetTimer = () => {
clearTimeout(timer);
// @effect-diagnostics-next-line globalTimers:off
timer = setTimeout(() => {
rejectOnce(new Error(`Timed out waiting for Pi RPC. Stderr: ${stderr.trim()}`));
}, timeoutMs);
};

// @effect-diagnostics-next-line globalTimers:off
timer = setTimeout(() => {
rejectOnce(new Error(`Timed out waiting for Pi RPC. Stderr: ${stderr.trim()}`));
}, timeoutMs);

input.signal?.addEventListener("abort", () => {
rejectOnce(new Error("Pi RPC request was aborted."));
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 LowLayers/PiRpc.ts:149

When an already-aborted AbortSignal is passed, the abort listener is never triggered, so the function spawns the child process and runs to completion or timeout instead of immediately rejecting. Consider checking input.signal?.aborted before spawning and rejecting early if already aborted.

+ if (input.signal?.aborted) {+ rejectOnce(new Error("Pi RPC request was aborted."));+ return;+ }+
input.signal?.addEventListener("abort", () => {
rejectOnce(new Error("Pi RPC request was aborted."));
});
🤖 Copy this AI Prompt to have your agent fix this:
In file apps/server/src/provider/Layers/PiRpc.ts around lines 149-201:
When an already-aborted `AbortSignal` is passed, the `abort` listener is never triggered, so the function spawns the child process and runs to completion or timeout instead of immediately rejecting. Consider checking `input.signal?.aborted` before spawning and rejecting early if already aborted.
Evidence trail:
apps/server/src/provider/Layers/PiRpc.ts lines 139-201 at REVIEWED_COMMIT: no `signal.aborted` check before `spawn()` on line 152; `addEventListener('abort', ...)` on line 199 won't fire for already-aborted signals per Node.js EventTarget spec. Node.js v22+ docs confirm `addEventListener` does not retroactively fire: https://nodejs.org/api/events.html (events.addAbortListener section).

operation: "startSession",
issue: `Expected provider '${PROVIDER}' but received '${input.provider}'.`,
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MediumLayers/PiAdapter.ts:285

When startSession replaces an existing session at lines 286-290, it aborts the old session and deletes it from sessions but never emits a session.exited event. This differs from stopSession which emits the event, so downstream consumers cannot track the lifecycle transition when sessions are replaced.

🤖 Copy this AI Prompt to have your agent fix this:
In file apps/server/src/provider/Layers/PiAdapter.ts around line 285:
When `startSession` replaces an existing session at lines 286-290, it aborts the old session and deletes it from `sessions` but never emits a `session.exited` event. This differs from `stopSession` which emits the event, so downstream consumers cannot track the lifecycle transition when sessions are replaced.
Evidence trail:
apps/server/src/provider/Layers/PiAdapter.ts lines 286-290 (startSession replaces existing session without emitting event), lines 686-700 (stopSession emits session.exited event after similar abort+delete), lines 759-761 (streamEvents exposes runtimeEvents queue to consumers).

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@AmbitiousRealism2025
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

[codex] Add Pi provider integration - #2831

Closed
AmbitiousRealism2025 wants to merge 3 commits into
pingdotgg:mainfrom
AmbitiousRealism2025:codex/pi-provider
Closed

[codex] Add Pi provider integration#2831
AmbitiousRealism2025 wants to merge 3 commits into
pingdotgg:mainfrom
AmbitiousRealism2025:codex/pi-provider

Conversation

@AmbitiousRealism2025

@AmbitiousRealism2025AmbitiousRealism2025 commented May 27, 2026

Copy link
Copy Markdown

Summary

Adds Pi as a selectable provider in T3 Code using Pi's local CLI/RPC integration rather than treating it as a generic API provider. The adapter discovers and uses the existing local Pi configuration, surfaces configured Pi models in the selector, supports Pi slash-command metadata in provider discovery, and routes prompts through Pi sessions.

Follow-up fixes in this branch also make Pi prompt timeouts activity-based for long tool-heavy turns and map Pi tool execution events into T3 provider runtime tool lifecycle events so the Work Log can show progress while Pi is using tools.

Implementation Notes

  • Adds Pi provider settings, driver metadata, provider registry wiring, and UI picker metadata.
  • Adds Pi RPC helpers for local model/config discovery, prompt execution, text delta streaming, and tool result extraction.
  • Adds a Pi provider adapter and text generation layer that use existing ~/.pi/agent configuration.
  • Keeps Pi-specific behavior isolated behind the existing provider adapter/runtime contracts.
  • Preserves existing provider behavior for Claude, Codex, OpenCode, Cursor, and ACP providers.

Validation

  • bun install
  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test
  • bunx vitest run src/provider/Layers/PiRpc.test.ts
  • Local Pi RPC smoke against existing ~/.pi/agent config
  • Manual local T3 Code verification with Pi selectable, Pi models visible, basic Pi prompt completing, and Pi tool events observed during a bash-tool prompt

Notes / Risks

  • Pi is a CLI/TUI-backed provider integration, so maintainability depends on Pi RPC event compatibility.
  • Slash-command parity is documented in PI_PARITY.md; commands that are inherently Pi-native are surfaced/documented rather than reimplemented as separate T3 UI features.
  • Long Pi turns are bounded by idle timeout after activity, not by total wall-clock duration, to avoid killing healthy multi-tool Pi runs.

Note

Add Pi provider integration with session management, streaming, and text generation

  • Registers a new pi provider driver kind in contracts, settings, and the built-in driver registry, removing it from the "Coming soon" list in the settings UI.
  • Adds PiRpc.ts with utilities to invoke the Pi CLI as a child process and communicate via newline-delimited JSON RPC, supporting streaming events, abort signals, and per-activity timeouts.
  • Adds PiAdapter.ts implementing the full ProviderAdapterShape for Pi: session lifecycle, streaming assistant text and tool events, attachment handling, turn interruption, and in-memory session state.
  • Adds PiTextGeneration.ts for Pi-backed commit messages, PR content, branch names, and thread titles via the RPC prompt path.
  • Adds PiProvider.ts for provider status checks, model discovery via pi --version and Pi RPC inventory, and slash command surfacing.
  • Risk: Pi integration depends on an external binary resolved from PI_CODING_AGENT_DIR or ~/.pi/agent; misconfiguration or binary absence will surface as probe errors at status-check time.
📊 Macroscope summarized 9c1c57e. 17 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

@coderabbitai

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 275c764f-0fd0-455c-b361-f827f608b4fc

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels May 27, 2026
Comment on lines +149 to +201
readonly resetTimeoutOnActivity?: boolean | undefined;
}): Promise<PiRpcRunResult> {
return await new Promise((resolve, reject) => {
const child = spawn(input.binaryPath, ["--mode", "rpc", ...(input.args ?? [])], {
cwd: input.cwd,
env: input.environment ?? process.env,
shell: process.platform === "win32",
stdio: ["pipe", "pipe", "pipe"],
});

const responses = new Map<string, PiRpcLine>();
const pending = new Set(input.commands.map((command) => command.id));
const events: PiRpcLine[] = [];
let stderr = "";
let stdoutBuffer = "";
let agentEnded = false;
let requestedClose = false;
let settled = false;

const finishIfReady = () => {
if (pending.size > 0) return;
if (input.waitForAgentEnd === true && !agentEnded) return;
if (requestedClose) return;
requestedClose = true;
child.stdin.end();
};

const rejectOnce = (error: Error) => {
if (settled) return;
settled = true;
clearTimeout(timer);
child.kill("SIGTERM");
reject(error);
};

const timeoutMs = input.timeoutMs ?? 120_000;
let timer: ReturnType<typeof setTimeout>;
const resetTimer = () => {
clearTimeout(timer);
// @effect-diagnostics-next-line globalTimers:off
timer = setTimeout(() => {
rejectOnce(new Error(`Timed out waiting for Pi RPC. Stderr: ${stderr.trim()}`));
}, timeoutMs);
};

// @effect-diagnostics-next-line globalTimers:off
timer = setTimeout(() => {
rejectOnce(new Error(`Timed out waiting for Pi RPC. Stderr: ${stderr.trim()}`));
}, timeoutMs);

input.signal?.addEventListener("abort", () => {
rejectOnce(new Error("Pi RPC request was aborted."));
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 LowLayers/PiRpc.ts:149

When an already-aborted AbortSignal is passed, the abort listener is never triggered, so the function spawns the child process and runs to completion or timeout instead of immediately rejecting. Consider checking input.signal?.aborted before spawning and rejecting early if already aborted.

+ if (input.signal?.aborted) {+ rejectOnce(new Error("Pi RPC request was aborted."));+ return;+ }+
input.signal?.addEventListener("abort", () => {
rejectOnce(new Error("Pi RPC request was aborted."));
});
🤖 Copy this AI Prompt to have your agent fix this:
In file apps/server/src/provider/Layers/PiRpc.ts around lines 149-201:
When an already-aborted `AbortSignal` is passed, the `abort` listener is never triggered, so the function spawns the child process and runs to completion or timeout instead of immediately rejecting. Consider checking `input.signal?.aborted` before spawning and rejecting early if already aborted.
Evidence trail:
apps/server/src/provider/Layers/PiRpc.ts lines 139-201 at REVIEWED_COMMIT: no `signal.aborted` check before `spawn()` on line 152; `addEventListener('abort', ...)` on line 199 won't fire for already-aborted signals per Node.js EventTarget spec. Node.js v22+ docs confirm `addEventListener` does not retroactively fire: https://nodejs.org/api/events.html (events.addAbortListener section).

operation: "startSession",
issue: `Expected provider '${PROVIDER}' but received '${input.provider}'.`,
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MediumLayers/PiAdapter.ts:285

When startSession replaces an existing session at lines 286-290, it aborts the old session and deletes it from sessions but never emits a session.exited event. This differs from stopSession which emits the event, so downstream consumers cannot track the lifecycle transition when sessions are replaced.

🤖 Copy this AI Prompt to have your agent fix this:
In file apps/server/src/provider/Layers/PiAdapter.ts around line 285:
When `startSession` replaces an existing session at lines 286-290, it aborts the old session and deletes it from `sessions` but never emits a `session.exited` event. This differs from `stopSession` which emits the event, so downstream consumers cannot track the lifecycle transition when sessions are replaced.
Evidence trail:
apps/server/src/provider/Layers/PiAdapter.ts lines 286-290 (startSession replaces existing session without emitting event), lines 686-700 (stopSession emits session.exited event after similar abort+delete), lines 759-761 (streamEvents exposes runtimeEvents queue to consumers).

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@AmbitiousRealism2025
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[codex] Add Pi provider integration - #2831

Closed
AmbitiousRealism2025 wants to merge 3 commits into
pingdotgg:mainfrom
AmbitiousRealism2025:codex/pi-provider
Closed

[codex] Add Pi provider integration#2831
AmbitiousRealism2025 wants to merge 3 commits into
pingdotgg:mainfrom
AmbitiousRealism2025:codex/pi-provider

Conversation

@AmbitiousRealism2025

@AmbitiousRealism2025AmbitiousRealism2025 commented May 27, 2026

Copy link
Copy Markdown

Summary

Adds Pi as a selectable provider in T3 Code using Pi's local CLI/RPC integration rather than treating it as a generic API provider. The adapter discovers and uses the existing local Pi configuration, surfaces configured Pi models in the selector, supports Pi slash-command metadata in provider discovery, and routes prompts through Pi sessions.

Follow-up fixes in this branch also make Pi prompt timeouts activity-based for long tool-heavy turns and map Pi tool execution events into T3 provider runtime tool lifecycle events so the Work Log can show progress while Pi is using tools.

Implementation Notes

  • Adds Pi provider settings, driver metadata, provider registry wiring, and UI picker metadata.
  • Adds Pi RPC helpers for local model/config discovery, prompt execution, text delta streaming, and tool result extraction.
  • Adds a Pi provider adapter and text generation layer that use existing ~/.pi/agent configuration.
  • Keeps Pi-specific behavior isolated behind the existing provider adapter/runtime contracts.
  • Preserves existing provider behavior for Claude, Codex, OpenCode, Cursor, and ACP providers.

Validation

  • bun install
  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test
  • bunx vitest run src/provider/Layers/PiRpc.test.ts
  • Local Pi RPC smoke against existing ~/.pi/agent config
  • Manual local T3 Code verification with Pi selectable, Pi models visible, basic Pi prompt completing, and Pi tool events observed during a bash-tool prompt

Notes / Risks

  • Pi is a CLI/TUI-backed provider integration, so maintainability depends on Pi RPC event compatibility.
  • Slash-command parity is documented in PI_PARITY.md; commands that are inherently Pi-native are surfaced/documented rather than reimplemented as separate T3 UI features.
  • Long Pi turns are bounded by idle timeout after activity, not by total wall-clock duration, to avoid killing healthy multi-tool Pi runs.

Note

Add Pi provider integration with session management, streaming, and text generation

  • Registers a new pi provider driver kind in contracts, settings, and the built-in driver registry, removing it from the "Coming soon" list in the settings UI.
  • Adds PiRpc.ts with utilities to invoke the Pi CLI as a child process and communicate via newline-delimited JSON RPC, supporting streaming events, abort signals, and per-activity timeouts.
  • Adds PiAdapter.ts implementing the full ProviderAdapterShape for Pi: session lifecycle, streaming assistant text and tool events, attachment handling, turn interruption, and in-memory session state.
  • Adds PiTextGeneration.ts for Pi-backed commit messages, PR content, branch names, and thread titles via the RPC prompt path.
  • Adds PiProvider.ts for provider status checks, model discovery via pi --version and Pi RPC inventory, and slash command surfacing.
  • Risk: Pi integration depends on an external binary resolved from PI_CODING_AGENT_DIR or ~/.pi/agent; misconfiguration or binary absence will surface as probe errors at status-check time.
📊 Macroscope summarized 9c1c57e. 17 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

@coderabbitai

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 275c764f-0fd0-455c-b361-f827f608b4fc

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels May 27, 2026
Comment on lines +149 to +201
readonly resetTimeoutOnActivity?: boolean | undefined;
}): Promise<PiRpcRunResult> {
return await new Promise((resolve, reject) => {
const child = spawn(input.binaryPath, ["--mode", "rpc", ...(input.args ?? [])], {
cwd: input.cwd,
env: input.environment ?? process.env,
shell: process.platform === "win32",
stdio: ["pipe", "pipe", "pipe"],
});

const responses = new Map<string, PiRpcLine>();
const pending = new Set(input.commands.map((command) => command.id));
const events: PiRpcLine[] = [];
let stderr = "";
let stdoutBuffer = "";
let agentEnded = false;
let requestedClose = false;
let settled = false;

const finishIfReady = () => {
if (pending.size > 0) return;
if (input.waitForAgentEnd === true && !agentEnded) return;
if (requestedClose) return;
requestedClose = true;
child.stdin.end();
};

const rejectOnce = (error: Error) => {
if (settled) return;
settled = true;
clearTimeout(timer);
child.kill("SIGTERM");
reject(error);
};

const timeoutMs = input.timeoutMs ?? 120_000;
let timer: ReturnType<typeof setTimeout>;
const resetTimer = () => {
clearTimeout(timer);
// @effect-diagnostics-next-line globalTimers:off
timer = setTimeout(() => {
rejectOnce(new Error(`Timed out waiting for Pi RPC. Stderr: ${stderr.trim()}`));
}, timeoutMs);
};

// @effect-diagnostics-next-line globalTimers:off
timer = setTimeout(() => {
rejectOnce(new Error(`Timed out waiting for Pi RPC. Stderr: ${stderr.trim()}`));
}, timeoutMs);

input.signal?.addEventListener("abort", () => {
rejectOnce(new Error("Pi RPC request was aborted."));
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 LowLayers/PiRpc.ts:149

When an already-aborted AbortSignal is passed, the abort listener is never triggered, so the function spawns the child process and runs to completion or timeout instead of immediately rejecting. Consider checking input.signal?.aborted before spawning and rejecting early if already aborted.

+ if (input.signal?.aborted) {+ rejectOnce(new Error("Pi RPC request was aborted."));+ return;+ }+
input.signal?.addEventListener("abort", () => {
rejectOnce(new Error("Pi RPC request was aborted."));
});
🤖 Copy this AI Prompt to have your agent fix this:
In file apps/server/src/provider/Layers/PiRpc.ts around lines 149-201:
When an already-aborted `AbortSignal` is passed, the `abort` listener is never triggered, so the function spawns the child process and runs to completion or timeout instead of immediately rejecting. Consider checking `input.signal?.aborted` before spawning and rejecting early if already aborted.
Evidence trail:
apps/server/src/provider/Layers/PiRpc.ts lines 139-201 at REVIEWED_COMMIT: no `signal.aborted` check before `spawn()` on line 152; `addEventListener('abort', ...)` on line 199 won't fire for already-aborted signals per Node.js EventTarget spec. Node.js v22+ docs confirm `addEventListener` does not retroactively fire: https://nodejs.org/api/events.html (events.addAbortListener section).

operation: "startSession",
issue: `Expected provider '${PROVIDER}' but received '${input.provider}'.`,
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MediumLayers/PiAdapter.ts:285

When startSession replaces an existing session at lines 286-290, it aborts the old session and deletes it from sessions but never emits a session.exited event. This differs from stopSession which emits the event, so downstream consumers cannot track the lifecycle transition when sessions are replaced.

🤖 Copy this AI Prompt to have your agent fix this:
In file apps/server/src/provider/Layers/PiAdapter.ts around line 285:
When `startSession` replaces an existing session at lines 286-290, it aborts the old session and deletes it from `sessions` but never emits a `session.exited` event. This differs from `stopSession` which emits the event, so downstream consumers cannot track the lifecycle transition when sessions are replaced.
Evidence trail:
apps/server/src/provider/Layers/PiAdapter.ts lines 286-290 (startSession replaces existing session without emitting event), lines 686-700 (stopSession emits session.exited event after similar abort+delete), lines 759-761 (streamEvents exposes runtimeEvents queue to consumers).

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@AmbitiousRealism2025
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[codex] Add Pi provider integration - #2831

Closed
AmbitiousRealism2025 wants to merge 3 commits into
pingdotgg:mainfrom
AmbitiousRealism2025:codex/pi-provider
Closed

[codex] Add Pi provider integration#2831
AmbitiousRealism2025 wants to merge 3 commits into
pingdotgg:mainfrom
AmbitiousRealism2025:codex/pi-provider

Conversation

@AmbitiousRealism2025

@AmbitiousRealism2025AmbitiousRealism2025 commented May 27, 2026

Copy link
Copy Markdown

Summary

Adds Pi as a selectable provider in T3 Code using Pi's local CLI/RPC integration rather than treating it as a generic API provider. The adapter discovers and uses the existing local Pi configuration, surfaces configured Pi models in the selector, supports Pi slash-command metadata in provider discovery, and routes prompts through Pi sessions.

Follow-up fixes in this branch also make Pi prompt timeouts activity-based for long tool-heavy turns and map Pi tool execution events into T3 provider runtime tool lifecycle events so the Work Log can show progress while Pi is using tools.

Implementation Notes

  • Adds Pi provider settings, driver metadata, provider registry wiring, and UI picker metadata.
  • Adds Pi RPC helpers for local model/config discovery, prompt execution, text delta streaming, and tool result extraction.
  • Adds a Pi provider adapter and text generation layer that use existing ~/.pi/agent configuration.
  • Keeps Pi-specific behavior isolated behind the existing provider adapter/runtime contracts.
  • Preserves existing provider behavior for Claude, Codex, OpenCode, Cursor, and ACP providers.

Validation

  • bun install
  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test
  • bunx vitest run src/provider/Layers/PiRpc.test.ts
  • Local Pi RPC smoke against existing ~/.pi/agent config
  • Manual local T3 Code verification with Pi selectable, Pi models visible, basic Pi prompt completing, and Pi tool events observed during a bash-tool prompt

Notes / Risks

  • Pi is a CLI/TUI-backed provider integration, so maintainability depends on Pi RPC event compatibility.
  • Slash-command parity is documented in PI_PARITY.md; commands that are inherently Pi-native are surfaced/documented rather than reimplemented as separate T3 UI features.
  • Long Pi turns are bounded by idle timeout after activity, not by total wall-clock duration, to avoid killing healthy multi-tool Pi runs.

Note

Add Pi provider integration with session management, streaming, and text generation

  • Registers a new pi provider driver kind in contracts, settings, and the built-in driver registry, removing it from the "Coming soon" list in the settings UI.
  • Adds PiRpc.ts with utilities to invoke the Pi CLI as a child process and communicate via newline-delimited JSON RPC, supporting streaming events, abort signals, and per-activity timeouts.
  • Adds PiAdapter.ts implementing the full ProviderAdapterShape for Pi: session lifecycle, streaming assistant text and tool events, attachment handling, turn interruption, and in-memory session state.
  • Adds PiTextGeneration.ts for Pi-backed commit messages, PR content, branch names, and thread titles via the RPC prompt path.
  • Adds PiProvider.ts for provider status checks, model discovery via pi --version and Pi RPC inventory, and slash command surfacing.
  • Risk: Pi integration depends on an external binary resolved from PI_CODING_AGENT_DIR or ~/.pi/agent; misconfiguration or binary absence will surface as probe errors at status-check time.
📊 Macroscope summarized 9c1c57e. 17 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

@coderabbitai

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 275c764f-0fd0-455c-b361-f827f608b4fc

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels May 27, 2026
Comment on lines +149 to +201
readonly resetTimeoutOnActivity?: boolean | undefined;
}): Promise<PiRpcRunResult> {
return await new Promise((resolve, reject) => {
const child = spawn(input.binaryPath, ["--mode", "rpc", ...(input.args ?? [])], {
cwd: input.cwd,
env: input.environment ?? process.env,
shell: process.platform === "win32",
stdio: ["pipe", "pipe", "pipe"],
});

const responses = new Map<string, PiRpcLine>();
const pending = new Set(input.commands.map((command) => command.id));
const events: PiRpcLine[] = [];
let stderr = "";
let stdoutBuffer = "";
let agentEnded = false;
let requestedClose = false;
let settled = false;

const finishIfReady = () => {
if (pending.size > 0) return;
if (input.waitForAgentEnd === true && !agentEnded) return;
if (requestedClose) return;
requestedClose = true;
child.stdin.end();
};

const rejectOnce = (error: Error) => {
if (settled) return;
settled = true;
clearTimeout(timer);
child.kill("SIGTERM");
reject(error);
};

const timeoutMs = input.timeoutMs ?? 120_000;
let timer: ReturnType<typeof setTimeout>;
const resetTimer = () => {
clearTimeout(timer);
// @effect-diagnostics-next-line globalTimers:off
timer = setTimeout(() => {
rejectOnce(new Error(`Timed out waiting for Pi RPC. Stderr: ${stderr.trim()}`));
}, timeoutMs);
};

// @effect-diagnostics-next-line globalTimers:off
timer = setTimeout(() => {
rejectOnce(new Error(`Timed out waiting for Pi RPC. Stderr: ${stderr.trim()}`));
}, timeoutMs);

input.signal?.addEventListener("abort", () => {
rejectOnce(new Error("Pi RPC request was aborted."));
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 LowLayers/PiRpc.ts:149

When an already-aborted AbortSignal is passed, the abort listener is never triggered, so the function spawns the child process and runs to completion or timeout instead of immediately rejecting. Consider checking input.signal?.aborted before spawning and rejecting early if already aborted.

+ if (input.signal?.aborted) {+ rejectOnce(new Error("Pi RPC request was aborted."));+ return;+ }+
input.signal?.addEventListener("abort", () => {
rejectOnce(new Error("Pi RPC request was aborted."));
});
🤖 Copy this AI Prompt to have your agent fix this:
In file apps/server/src/provider/Layers/PiRpc.ts around lines 149-201:
When an already-aborted `AbortSignal` is passed, the `abort` listener is never triggered, so the function spawns the child process and runs to completion or timeout instead of immediately rejecting. Consider checking `input.signal?.aborted` before spawning and rejecting early if already aborted.
Evidence trail:
apps/server/src/provider/Layers/PiRpc.ts lines 139-201 at REVIEWED_COMMIT: no `signal.aborted` check before `spawn()` on line 152; `addEventListener('abort', ...)` on line 199 won't fire for already-aborted signals per Node.js EventTarget spec. Node.js v22+ docs confirm `addEventListener` does not retroactively fire: https://nodejs.org/api/events.html (events.addAbortListener section).

operation: "startSession",
issue: `Expected provider '${PROVIDER}' but received '${input.provider}'.`,
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MediumLayers/PiAdapter.ts:285

When startSession replaces an existing session at lines 286-290, it aborts the old session and deletes it from sessions but never emits a session.exited event. This differs from stopSession which emits the event, so downstream consumers cannot track the lifecycle transition when sessions are replaced.

🤖 Copy this AI Prompt to have your agent fix this:
In file apps/server/src/provider/Layers/PiAdapter.ts around line 285:
When `startSession` replaces an existing session at lines 286-290, it aborts the old session and deletes it from `sessions` but never emits a `session.exited` event. This differs from `stopSession` which emits the event, so downstream consumers cannot track the lifecycle transition when sessions are replaced.
Evidence trail:
apps/server/src/provider/Layers/PiAdapter.ts lines 286-290 (startSession replaces existing session without emitting event), lines 686-700 (stopSession emits session.exited event after similar abort+delete), lines 759-761 (streamEvents exposes runtimeEvents queue to consumers).

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@AmbitiousRealism2025
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

[codex] Add Pi provider integration - #2831

Closed
AmbitiousRealism2025 wants to merge 3 commits into
pingdotgg:mainfrom
AmbitiousRealism2025:codex/pi-provider
Closed

[codex] Add Pi provider integration#2831
AmbitiousRealism2025 wants to merge 3 commits into
pingdotgg:mainfrom
AmbitiousRealism2025:codex/pi-provider

Conversation

@AmbitiousRealism2025

@AmbitiousRealism2025AmbitiousRealism2025 commented May 27, 2026

Copy link
Copy Markdown

Summary

Adds Pi as a selectable provider in T3 Code using Pi's local CLI/RPC integration rather than treating it as a generic API provider. The adapter discovers and uses the existing local Pi configuration, surfaces configured Pi models in the selector, supports Pi slash-command metadata in provider discovery, and routes prompts through Pi sessions.

Follow-up fixes in this branch also make Pi prompt timeouts activity-based for long tool-heavy turns and map Pi tool execution events into T3 provider runtime tool lifecycle events so the Work Log can show progress while Pi is using tools.

Implementation Notes

  • Adds Pi provider settings, driver metadata, provider registry wiring, and UI picker metadata.
  • Adds Pi RPC helpers for local model/config discovery, prompt execution, text delta streaming, and tool result extraction.
  • Adds a Pi provider adapter and text generation layer that use existing ~/.pi/agent configuration.
  • Keeps Pi-specific behavior isolated behind the existing provider adapter/runtime contracts.
  • Preserves existing provider behavior for Claude, Codex, OpenCode, Cursor, and ACP providers.

Validation

  • bun install
  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test
  • bunx vitest run src/provider/Layers/PiRpc.test.ts
  • Local Pi RPC smoke against existing ~/.pi/agent config
  • Manual local T3 Code verification with Pi selectable, Pi models visible, basic Pi prompt completing, and Pi tool events observed during a bash-tool prompt

Notes / Risks

  • Pi is a CLI/TUI-backed provider integration, so maintainability depends on Pi RPC event compatibility.
  • Slash-command parity is documented in PI_PARITY.md; commands that are inherently Pi-native are surfaced/documented rather than reimplemented as separate T3 UI features.
  • Long Pi turns are bounded by idle timeout after activity, not by total wall-clock duration, to avoid killing healthy multi-tool Pi runs.

Note

Add Pi provider integration with session management, streaming, and text generation

  • Registers a new pi provider driver kind in contracts, settings, and the built-in driver registry, removing it from the "Coming soon" list in the settings UI.
  • Adds PiRpc.ts with utilities to invoke the Pi CLI as a child process and communicate via newline-delimited JSON RPC, supporting streaming events, abort signals, and per-activity timeouts.
  • Adds PiAdapter.ts implementing the full ProviderAdapterShape for Pi: session lifecycle, streaming assistant text and tool events, attachment handling, turn interruption, and in-memory session state.
  • Adds PiTextGeneration.ts for Pi-backed commit messages, PR content, branch names, and thread titles via the RPC prompt path.
  • Adds PiProvider.ts for provider status checks, model discovery via pi --version and Pi RPC inventory, and slash command surfacing.
  • Risk: Pi integration depends on an external binary resolved from PI_CODING_AGENT_DIR or ~/.pi/agent; misconfiguration or binary absence will surface as probe errors at status-check time.
📊 Macroscope summarized 9c1c57e. 17 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

@coderabbitai

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 275c764f-0fd0-455c-b361-f827f608b4fc

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels May 27, 2026
Comment on lines +149 to +201
readonly resetTimeoutOnActivity?: boolean | undefined;
}): Promise<PiRpcRunResult> {
return await new Promise((resolve, reject) => {
const child = spawn(input.binaryPath, ["--mode", "rpc", ...(input.args ?? [])], {
cwd: input.cwd,
env: input.environment ?? process.env,
shell: process.platform === "win32",
stdio: ["pipe", "pipe", "pipe"],
});

const responses = new Map<string, PiRpcLine>();
const pending = new Set(input.commands.map((command) => command.id));
const events: PiRpcLine[] = [];
let stderr = "";
let stdoutBuffer = "";
let agentEnded = false;
let requestedClose = false;
let settled = false;

const finishIfReady = () => {
if (pending.size > 0) return;
if (input.waitForAgentEnd === true && !agentEnded) return;
if (requestedClose) return;
requestedClose = true;
child.stdin.end();
};

const rejectOnce = (error: Error) => {
if (settled) return;
settled = true;
clearTimeout(timer);
child.kill("SIGTERM");
reject(error);
};

const timeoutMs = input.timeoutMs ?? 120_000;
let timer: ReturnType<typeof setTimeout>;
const resetTimer = () => {
clearTimeout(timer);
// @effect-diagnostics-next-line globalTimers:off
timer = setTimeout(() => {
rejectOnce(new Error(`Timed out waiting for Pi RPC. Stderr: ${stderr.trim()}`));
}, timeoutMs);
};

// @effect-diagnostics-next-line globalTimers:off
timer = setTimeout(() => {
rejectOnce(new Error(`Timed out waiting for Pi RPC. Stderr: ${stderr.trim()}`));
}, timeoutMs);

input.signal?.addEventListener("abort", () => {
rejectOnce(new Error("Pi RPC request was aborted."));
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 LowLayers/PiRpc.ts:149

When an already-aborted AbortSignal is passed, the abort listener is never triggered, so the function spawns the child process and runs to completion or timeout instead of immediately rejecting. Consider checking input.signal?.aborted before spawning and rejecting early if already aborted.

+ if (input.signal?.aborted) {+ rejectOnce(new Error("Pi RPC request was aborted."));+ return;+ }+
input.signal?.addEventListener("abort", () => {
rejectOnce(new Error("Pi RPC request was aborted."));
});
🤖 Copy this AI Prompt to have your agent fix this:
In file apps/server/src/provider/Layers/PiRpc.ts around lines 149-201:
When an already-aborted `AbortSignal` is passed, the `abort` listener is never triggered, so the function spawns the child process and runs to completion or timeout instead of immediately rejecting. Consider checking `input.signal?.aborted` before spawning and rejecting early if already aborted.
Evidence trail:
apps/server/src/provider/Layers/PiRpc.ts lines 139-201 at REVIEWED_COMMIT: no `signal.aborted` check before `spawn()` on line 152; `addEventListener('abort', ...)` on line 199 won't fire for already-aborted signals per Node.js EventTarget spec. Node.js v22+ docs confirm `addEventListener` does not retroactively fire: https://nodejs.org/api/events.html (events.addAbortListener section).

operation: "startSession",
issue: `Expected provider '${PROVIDER}' but received '${input.provider}'.`,
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MediumLayers/PiAdapter.ts:285

When startSession replaces an existing session at lines 286-290, it aborts the old session and deletes it from sessions but never emits a session.exited event. This differs from stopSession which emits the event, so downstream consumers cannot track the lifecycle transition when sessions are replaced.

🤖 Copy this AI Prompt to have your agent fix this:
In file apps/server/src/provider/Layers/PiAdapter.ts around line 285:
When `startSession` replaces an existing session at lines 286-290, it aborts the old session and deletes it from `sessions` but never emits a `session.exited` event. This differs from `stopSession` which emits the event, so downstream consumers cannot track the lifecycle transition when sessions are replaced.
Evidence trail:
apps/server/src/provider/Layers/PiAdapter.ts lines 286-290 (startSession replaces existing session without emitting event), lines 686-700 (stopSession emits session.exited event after similar abort+delete), lines 759-761 (streamEvents exposes runtimeEvents queue to consumers).

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@AmbitiousRealism2025