Switch to Pi#1
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 341785e421
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const host = optional("AGENT_SERVER_HOST", "127.0.0.1"); | ||
| const port = Number(optional("AGENT_SERVER_PORT", "4001")); | ||
| const token = process.env.AGENT_SERVER_TOKEN?.trim(); | ||
| const token = (process.env.AGENT_SERVER_TOKEN ?? process.env.APPX_AGENT_SERVER_TOKEN)?.trim(); |
There was a problem hiding this comment.
Use APPX token when AGENT_SERVER_TOKEN is empty
The token selection uses process.env.AGENT_SERVER_TOKEN ?? process.env.APPX_AGENT_SERVER_TOKEN, so an explicitly empty AGENT_SERVER_TOKEN (e.g. set to "" by environment templating) suppresses APPX_AGENT_SERVER_TOKEN and leaves token empty. In that case the auth middleware is skipped and all /v1/* routes become unauthenticated even though a valid APPX token is present, which is a security regression in deployments that rely on the fallback variable.
Useful? React with 👍 / 👎.
| extensionPaths: optionalList("PI_EXTENSION_PATHS"), | ||
| skillPaths: optionalList("PI_SKILL_PATHS"), | ||
| promptTemplatePaths: optionalList("PI_PROMPT_PATHS"), | ||
| themePaths: optionalList("PI_THEME_PATHS"), | ||
| noExtensions: truthy("PI_NO_EXTENSIONS"), | ||
| noSkills: truthy("PI_NO_SKILLS"), | ||
| noPromptTemplates: truthy("PI_NO_PROMPTS"), | ||
| noThemes: truthy("PI_NO_THEMES"), |
There was a problem hiding this comment.
Do we really need all of those? Can't we just point to project/.pi/ dir and resolve to skills and extensions there? What are the cases when we have skills from multiple paths?
| const gpt55ThinkingLevelMap: Partial<Record<ThinkingLevel, string | null>> = { | ||
| off: "none", | ||
| minimal: "minimal", | ||
| low: "low", | ||
| medium: "medium", | ||
| high: "high", | ||
| xhigh: "xhigh", | ||
| }; | ||
|
|
||
| const deepSeekV4ThinkingLevelMap: Partial<Record<ThinkingLevel, string | null>> = { | ||
| minimal: null, | ||
| low: null, | ||
| medium: null, | ||
| high: "high", | ||
| xhigh: "max", | ||
| }; |
There was a problem hiding this comment.
That's quite brittle hardcoding for a specific model. Are there libs that abstract that away?
There was a problem hiding this comment.
Can pull well-known LLMs data from https://github.com/earendil-works/pi/blob/main/packages/ai/src/models.generated.ts
There was a problem hiding this comment.
we'll prly need to split the routes into meaningful subdirs. It's getting harder to navigate them as they're lumped together
There was a problem hiding this comment.
That's the biggest change. Need to review carefully
There was a problem hiding this comment.
So this one apparently creates and manages runtimes for multi- setting
|
|
||
| function truthy(name: string): boolean { | ||
| const v = process.env[name]?.trim().toLowerCase(); | ||
| return v === "1" || v === "true" || v === "yes" || v === "on"; |
There was a problem hiding this comment.
That's a weird function. Why do we need support for all of those cases?
| | `GET` | `/v1/custom/providers` | List custom `models.json` providers without secrets | | ||
| | `PUT` | `/v1/custom/providers` | Create or update a custom provider | | ||
| | `DELETE` | `/v1/custom/providers/{provider}` | Remove a custom provider | |
There was a problem hiding this comment.
Is openorange considered a custom provider?
| `thinkingLevel` is one of `off`, `minimal`, `low`, `medium`, `high`, `xhigh`. | ||
| The runtime rejects changes while a session is streaming with HTTP `409`. | ||
| Pi clamps valid but unsupported thinking levels to the selected model's | ||
| supported set and returns the effective level in the response. |
There was a problem hiding this comment.
Thinking level is model-specific. Can we infer what thinking levels model expects using pi-ai? See earendil-works/pi#3208
There was a problem hiding this comment.
Well-known models are defined in https://github.com/earendil-works/pi/blob/main/packages/ai/src/models.generated.ts
| generated from it (see "Consuming from another app" below). | ||
|
|
||
| Mounted under `/v1`: | ||
| In `single` mode, all routes are mounted under `/v1`: |
There was a problem hiding this comment.
Isn't single mode just a trivial case of multi mode? Do we really need to hard-separate them?
| export type AgentAuthProviderRow = { | ||
| provider: string; | ||
| name: string; | ||
| configured: boolean; | ||
| credentialType?: "api_key" | "oauth"; | ||
| source?: "stored" | "runtime" | "environment" | "fallback" | "models_json_key" | "models_json_command"; | ||
| label?: string; | ||
| supportsApiKey: boolean; | ||
| supportsSubscription: boolean; | ||
| modelCount: number; | ||
| availableModelCount: number; | ||
| }; | ||
|
|
||
| export type AgentAuthPrompt = { | ||
| message: string; | ||
| placeholder?: string; | ||
| allowEmpty?: boolean; | ||
| }; | ||
|
|
||
| export type AgentOAuthFlowState = { | ||
| id: string; | ||
| provider: string; | ||
| providerName: string; | ||
| status: "starting" | "prompt" | "auth" | "waiting" | "complete" | "error" | "cancelled"; | ||
| authUrl?: string; | ||
| instructions?: string; | ||
| prompt?: AgentAuthPrompt; | ||
| progress: string[]; | ||
| error?: string; | ||
| expiresAt: string; | ||
| }; |
There was a problem hiding this comment.
I'd say auth and model config belongs at the project registry level (one level above runtime)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nto runtimes Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Split the HTTP layer so credential routes are served by a new
createCredentialsApp(credentials) factory that takes an
AgentCredentialsService, while session routes stay under
createSessionsApp(runtime). URL paths are unchanged — only the internal
mounting changes.
Changes:
- Add createCredentialsApp(credentials, options) in src/routes.ts that
mounts auth/*, custom/*, sessions/models, and healthz routes
- Remove credential routes from createSessionsApp, keeping only session
routes (sessions, sessions/{id}, prompt, abort, settings, extension-ui,
events SSE)
- Simplify CreateSessionsAppOptions to Record<string, never> (no more
credentialRoutes/sessionRoutes/healthRoute flags)
- Update server.ts to mount both apps: credentials at /v1 (always),
sessions at /v1 (single mode) or /v1/projects/:projectId (multi mode)
- Update openapi.ts to use registry-based approach and mirror the new
mounting structure
- Update test/server.test.ts: rebuild startServer helper to use
AgentRuntimeRegistry and mount both apps separately; update
project-scoped tests to use the new structure
All 41 tests pass. OpenAPI contract preserved: all 18 documented paths
still present.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…alsService Removes ~700 lines of duplicated credential code from AgentRuntime after Tasks 4–10 moved it to AgentCredentialsService. HTTP routes now reach credentials via createCredentialsApp; runtime.ts retains only session lifecycle, extension UI bridge, and resource loader construction. Deleted methods: - modelKey, defaultThinkingForModel, modelRow (now credentials.modelRow) - listModels, listAuthProviders - setProviderApiKey, removeProviderCredential, assertProviderId - customProviderApi - OAuth flow state machine: oauthFlowState, updateOAuthFlow, scheduleOAuthFlowCleanup, activeOAuthFlowForProvider, oauthLoginErrorMessage, waitForOAuthFlowUpdate, startProviderSubscriptionLogin, continueProviderSubscriptionLogin, getProviderSubscriptionLogin, cancelProviderSubscriptionLogin - readModelsJson, writeModelsJson - listCustomProviders, upsertCustomProvider, removeCustomProvider Deleted fields: pendingOAuthFlows, modelsJsonPath Deleted types: PendingOAuthFlow, CUSTOM_PROVIDER_APIS Updated: - sessionModelSettings calls credentials.modelRow - sessionModelDefaults calls credentials.defaultThinkingForModel - setSessionModelInternal calls credentials.defaultThinkingForModel Re-exported types from credentialsService: AgentModelRow, AgentAuthProviderRow, AgentAuthPrompt, AgentOAuthFlowState, AgentCustomProviderApi, AgentCustomProviderModel, AgentCustomProviderRow, UpsertCustomProviderRequest Test fix: LiteLLM test now instantiates AgentCredentialsService with thinking defaults from litellmRuntimeConfig before calling listModels. runtime.ts: 1241 → 749 lines (-492, ~40% reduction) All 41 tests pass. TypeScript compiles cleanly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pi switch refactor
Summary
Adds the Pi-backed Appx agent-server contract needed by the Appx Pi integration.
openai-completions,openai-responses, andanthropic-messagescompatible APIs.AgentRuntimeRegistryso one agent-server process can keep shared auth/model state while isolating session runtimes by Appx project directory.Why
Appx is moving from a single OpenCode backend toward Pi under the hood. The frontend and Appx proxy need a stable HTTP/SSE contract that hides provider transport differences, supports subscriptions/custom endpoints, and keeps project sessions isolated.
Validation
npm run buildpassed.npm testpassed, 28 tests.Notes
Pairs with appx-org/appx#2, which installs/proxies this service and adds the Pi UI.