Skip to content

Add Codex AppServer Pi provider#92

Merged
glittercowboy merged 4 commits intomainfrom
codex/codex-appserver-provider
Apr 29, 2026
Merged

Add Codex AppServer Pi provider#92
glittercowboy merged 4 commits intomainfrom
codex/codex-appserver-provider

Conversation

@glittercowboy
Copy link
Copy Markdown
Contributor

@glittercowboy glittercowboy commented Apr 29, 2026

Adds codex-appserver as a Pi provider. Preserves Claude SDK persistent session/cache prompt behavior from 9524290 and routes Codex dynamic tool calls through Pi-owned tool execution.

Dependencies:

  • protocol-go v0.27.0 is tagged and used by this branch.

Summary by CodeRabbit

  • New Features

    • Added a Codex app-server provider with GPT model support and streaming integration.
    • Tasks can now select a provider; a sensible default is applied when none is specified.
  • Bug Fixes

    • Clearer error reporting when an unknown provider is encountered.
  • Tests

    • New unit and smoke tests covering provider behavior and integration.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 29, 2026

📝 Walkthrough

Walkthrough

Adds a new Pi extension provider "codex-appserver" that runs a codex app-server child process over newline-delimited JSON-RPC, threads a configurable provider value (defaulting to "claude-cli") from task context through the Pi executor, and includes tests and a smoke test; also bumps a Go dependency.

Changes

Cohort / File(s) Summary
Dependency
go.mod
Bumped github.com/gsd-build/protocol-go from v0.26.0v0.27.0.
Pi executor & provider plumbing
internal/pi/executor.go, internal/session/actor.go, internal/session/actor_test.go
Added ProviderOrDefault helper; NewExecutor and runPiExecutor now pass a normalized provider (default "claude-cli") from task context instead of a hardcoded string; exit-error handling refined; tests added for provider propagation and defaulting.
Codex provider implementation
internal/pi/extension/codex-appserver-provider.ts
New provider that spawns codex app-server, manages newline-delimited JSON-RPC requests/responses with per-request promises and 90s timeout, parses streaming events into Pi assistant events, bridges tool calls (start/delta/end + toolUse), handles cancellation and process exit, and registers under codex-appserver / openai-responses.
Provider registration
internal/pi/extension/index.ts
Registers the new codex-appserver provider during extension initialization.
Tests & smoke test
internal/pi/extension/codex-appserver-provider.test.mjs, internal/pi/extension/codex-appserver-smoke.mjs
Unit test for model definitions, dynamic tool mapping, and output stamping; smoke test runs pi in RPC mode, responds to extension UI request, validates observed runtime flags and agent behavior.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant PI as PI Executor
    participant Codex as Codex App-Server
    participant Tool as Tool Handler

    Client->>PI: start turn (model, provider, tools)
    PI->>Codex: spawn process, open JSON-RPC (stdin/stdout)
    PI->>Codex: send turn/request (with model, tools)
    
    Codex->>PI: emit text_start
    PI->>Client: stream text_start

    Codex->>PI: emit text_delta
    PI->>Client: stream text_delta

    Codex->>PI: emit tool_call_start
    PI->>Tool: emit toolcall_start and run tool (ask_user_questions)
    Tool-->>PI: return tool result
    PI->>Codex: deliver tool result
    Codex->>PI: emit tool_call_end / toolUse
    PI->>Client: stream toolUse

    Codex->>PI: emit token_usage
    PI->>Client: stream usage

    Codex->>PI: emit done
    PI->>Client: finalize stream

    Client->>PI: abort
    PI->>Codex: send turn/interrupt
    Codex->>PI: acknowledge interrupt
    PI->>Client: close stream
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 I hopped into streams with a curious cheer,

Codex replied in newline-JSON ear,
Tools tapped and answered, bridges made neat,
Provider defaulted, the turn stayed complete,
Hooray — new hops, new runs, let's greet! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add Codex AppServer Pi provider' directly and clearly describes the main change—adding a new Pi provider for Codex AppServer. It is concise, specific, and accurately summarizes the primary objective of the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/codex-appserver-provider

Review rate limit: 4/10 reviews remaining, refill in 31 minutes and 35 seconds.

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
internal/session/actor.go (1)

619-622: Prefer a single source of truth for provider defaulting.

"claude-cli" fallback is now duplicated here and in internal/pi/executor.go. Consider centralizing defaulting in one layer to avoid drift.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/session/actor.go` around lines 619 - 622, Centralize the
`"claude-cli"` default by adding a shared helper ProviderOrDefault(provider
string) that does strings.TrimSpace and returns "claude-cli" when empty, then
replace the local logic in actor.go (the provider :=
strings.TrimSpace(tc.Provider) / if provider == "" block) with provider =
ProviderOrDefault(tc.Provider) and update internal/pi/executor.go to call the
same ProviderOrDefault so there is a single source of truth for provider
defaulting.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/pi/extension/codex-appserver-provider.ts`:
- Around line 274-319: The initPromise IIFE can stay rejected forever if
codexRequest("initialize") or codexRequest("thread/start") throws, so wrap the
async initialization block in a try/catch that performs the same cleanup done in
the codex.on("close") handler (set initialized = false; initPromise = null;
codex = null; rl = null; threadId = null; activeTurnId = null; pendingBridge =
null; abort/kill the codex process if started; end any activeRun stream with an
error) and then rethrow the error so callers see the failure; this ensures
initPromise is cleared on error and subsequent calls to the function will retry
initialization.
- Around line 349-353: streamCodexAppServer currently overwrites the single
global activeRun, risking cross-routing when streams overlap; add a guard at the
start of streamCodexAppServer to detect an existing activeRun (created by
createAssistantMessageEventStream/codexOutputForModel) and either reject/throw
(or return an error stream) instead of proceeding, then ensure activeRun is
cleared on stream termination (on end/error/close handlers) so subsequent calls
can set activeRun safely; update the function to check activeRun before
assigning, and add cleanup setting activeRun = null in the stream's completion
and error handlers to prevent state corruption.
- Around line 282-297: The Codex process close handler currently leaves pending
RPC promises/timers alive; update the codex.on("close", ...) block to actively
reject and cleanup all pending RPCs: iterate the pendingBridge (and any other
pending requests collection), for each pending request call its reject callback
with a clear Error (e.g. "Codex process exited") and clear any associated
timeout timers, then remove entries from pendingBridge; also set pendingBridge =
null (and any other pending maps) after cleanup so no further resolution
happens; keep the existing activeRun shutdown logic but ensure pending RPCs are
rejected before nulling pendingBridge/initPromise.

In `@internal/pi/extension/codex-appserver-smoke.mjs`:
- Around line 57-61: The smoke test currently checks only camelCase event fields
(event.toolName) in the conditional branches that set flags.sawToolStart and
flags.sawToolEnd; update the conditions in the event handlers (the two if blocks
referencing event.type === "tool_execution_start" / "tool_execution_end") to
accept either camelCase or snake_case by checking both event.toolName ||
event.tool_name (or equivalent) so the flags are set when the payload uses
either key form.

---

Nitpick comments:
In `@internal/session/actor.go`:
- Around line 619-622: Centralize the `"claude-cli"` default by adding a shared
helper ProviderOrDefault(provider string) that does strings.TrimSpace and
returns "claude-cli" when empty, then replace the local logic in actor.go (the
provider := strings.TrimSpace(tc.Provider) / if provider == "" block) with
provider = ProviderOrDefault(tc.Provider) and update internal/pi/executor.go to
call the same ProviderOrDefault so there is a single source of truth for
provider defaulting.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2b48550b-f8bd-4b36-ba9f-83792eecb6be

📥 Commits

Reviewing files that changed from the base of the PR and between 96494d9 and 78d8d2b.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (8)
  • go.mod
  • internal/pi/executor.go
  • internal/pi/extension/codex-appserver-provider.test.mjs
  • internal/pi/extension/codex-appserver-provider.ts
  • internal/pi/extension/codex-appserver-smoke.mjs
  • internal/pi/extension/index.ts
  • internal/session/actor.go
  • internal/session/actor_test.go

Comment thread internal/pi/extension/codex-appserver-provider.ts
Comment thread internal/pi/extension/codex-appserver-provider.ts
Comment thread internal/pi/extension/codex-appserver-provider.ts
Comment thread internal/pi/extension/codex-appserver-smoke.mjs Outdated
@glittercowboy glittercowboy force-pushed the codex/codex-appserver-provider branch from d96c248 to 48c8cca Compare April 29, 2026 18:36
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
internal/session/actor.go (1)

619-619: Consider centralizing provider normalization in one layer.

Line 619 normalizes with pi.ProviderOrDefault(...), and pi.NewExecutor(...) also normalizes. Keeping normalization only in the executor reduces drift risk.

♻️ Minimal refactor
-	provider := pi.ProviderOrDefault(tc.Provider)
@@
-		Provider:           provider,
+		Provider:           tc.Provider,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/session/actor.go` at line 619, The provider normalization is
duplicated: remove the call to pi.ProviderOrDefault at the call site and rely on
pi.NewExecutor to normalize consistently; specifically, stop assigning provider
:= pi.ProviderOrDefault(tc.Provider) and instead pass the raw tc.Provider (or
tc.Provider value) into NewExecutor (or any place that expects normalization),
so only pi.NewExecutor performs normalization and you avoid drift between
pi.ProviderOrDefault and pi.NewExecutor.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@internal/session/actor.go`:
- Line 619: The provider normalization is duplicated: remove the call to
pi.ProviderOrDefault at the call site and rely on pi.NewExecutor to normalize
consistently; specifically, stop assigning provider :=
pi.ProviderOrDefault(tc.Provider) and instead pass the raw tc.Provider (or
tc.Provider value) into NewExecutor (or any place that expects normalization),
so only pi.NewExecutor performs normalization and you avoid drift between
pi.ProviderOrDefault and pi.NewExecutor.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7d19c889-d378-4de7-b107-614d5ec26737

📥 Commits

Reviewing files that changed from the base of the PR and between 78d8d2b and 48c8cca.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (8)
  • go.mod
  • internal/pi/executor.go
  • internal/pi/extension/codex-appserver-provider.test.mjs
  • internal/pi/extension/codex-appserver-provider.ts
  • internal/pi/extension/codex-appserver-smoke.mjs
  • internal/pi/extension/index.ts
  • internal/session/actor.go
  • internal/session/actor_test.go
✅ Files skipped from review due to trivial changes (2)
  • go.mod
  • internal/pi/extension/codex-appserver-provider.test.mjs
🚧 Files skipped from review as they are similar to previous changes (3)
  • internal/pi/extension/index.ts
  • internal/pi/extension/codex-appserver-smoke.mjs
  • internal/pi/extension/codex-appserver-provider.ts

@glittercowboy glittercowboy force-pushed the codex/codex-appserver-provider branch from 48c8cca to c6a2ced Compare April 29, 2026 18:44
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
internal/session/actor_test.go (1)

256-287: Add a whitespace-only provider case.

ProviderOrDefault trims whitespace, but this test only covers the empty-string path. A " " case would lock in the defaulting behavior and catch regressions where whitespace is forwarded literally.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/session/actor_test.go` around lines 256 - 287, Add a whitespace-only
provider test case to ensure ProviderOrDefault treats strings like "   " as
empty and falls back to the default; update
TestActorPiExecutorDefaultsEmptyProviderToClaudeCli (or add a new test) to set
the provider to a whitespace string (e.g., t.Setenv("FAKE_PI_PROVIDER", "   ")
or equivalent used by testPiOptions), run NewActor and actor.runPiExecutor as in
the existing test, then assert the produced args (readNulArgsFile / argIndex
checks) still yield "--provider" value "claude-cli"; this verifies
ProviderOrDefault trimming behavior and prevents forwarding literal whitespace
provider values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@internal/session/actor_test.go`:
- Around line 256-287: Add a whitespace-only provider test case to ensure
ProviderOrDefault treats strings like "   " as empty and falls back to the
default; update TestActorPiExecutorDefaultsEmptyProviderToClaudeCli (or add a
new test) to set the provider to a whitespace string (e.g.,
t.Setenv("FAKE_PI_PROVIDER", "   ") or equivalent used by testPiOptions), run
NewActor and actor.runPiExecutor as in the existing test, then assert the
produced args (readNulArgsFile / argIndex checks) still yield "--provider" value
"claude-cli"; this verifies ProviderOrDefault trimming behavior and prevents
forwarding literal whitespace provider values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f0fe0948-3cd7-4f39-aff4-0329eedec1ec

📥 Commits

Reviewing files that changed from the base of the PR and between 48c8cca and c6a2ced.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (8)
  • go.mod
  • internal/pi/executor.go
  • internal/pi/extension/codex-appserver-provider.test.mjs
  • internal/pi/extension/codex-appserver-provider.ts
  • internal/pi/extension/codex-appserver-smoke.mjs
  • internal/pi/extension/index.ts
  • internal/session/actor.go
  • internal/session/actor_test.go
✅ Files skipped from review due to trivial changes (2)
  • go.mod
  • internal/pi/extension/codex-appserver-provider.test.mjs
🚧 Files skipped from review as they are similar to previous changes (4)
  • internal/pi/extension/index.ts
  • internal/pi/extension/codex-appserver-smoke.mjs
  • internal/session/actor.go
  • internal/pi/extension/codex-appserver-provider.ts

@glittercowboy glittercowboy merged commit 80b8323 into main Apr 29, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant