Switch to agent-server#2
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6aa47e1fed
ℹ️ 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".
| if (res.status === 401) { | ||
| window.location.href = '/login'; | ||
| throw new Error('Unauthorized'); |
There was a problem hiding this comment.
Gate login redirect on Appx auth header
This new Pi API client redirects to /login on every HTTP 401, but /api/agent/* responses can also return 401 from the upstream agent backend (for example, backend token mismatch or provider-side auth errors) while the Appx session is still valid. Because the redirect ignores X-Appx-Auth: required, users get logged out for non-session failures and can enter a misleading re-login loop instead of seeing the actual agent error.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Sounds like a valid concern
| APPX_HOST=$APPX_HOST | ||
| APPX_DATA=$APPX_DATA | ||
| APPX_PORT=$APPX_PORT | ||
| APPX_AGENT_SERVER_URL=http://127.0.0.1:4001 |
There was a problem hiding this comment.
Persist configured agent-server URL in bootstrap env
The bootstrap template always writes APPX_AGENT_SERVER_URL=http://127.0.0.1:4001, which overrides any non-default value a deployer intended for first-time setup. In installations where agent-server runs on a different host/port, bootstrap completes with an unusable proxy target until the env file is manually corrected, so the generated config should honor the chosen/runtime value instead of hardcoding the default.
Useful? React with 👍 / 👎.
| if [ ! -d /home/appx-agent/.pi ] && [ -d /home/opencode/.pi ]; then | ||
| cp -a /home/opencode/.pi /home/appx-agent/.pi |
There was a problem hiding this comment.
Leftover mention of opencode in [ -d /home/opencode/.pi ] and cp -a /home/opencode/.pi
| if id -u opencode >/dev/null 2>&1; then | ||
| pkill -u opencode -f '(^|/)agent-server( |$)|agent-server/dist/server\.js' 2>/dev/null || true |
There was a problem hiding this comment.
some more opencode leftovers
| AGENT_SERVER_DIR="${AGENT_SERVER_DIR:-}" | ||
| if [ -z "$AGENT_SERVER_DIR" ] && [ -d "$REPO_DIR/../agent-server" ]; then | ||
| AGENT_SERVER_DIR="$(cd "$REPO_DIR/../agent-server" && pwd)" | ||
| fi | ||
|
|
||
| if [ -n "$OPENCODE_NPM_VERSION" ] && [ "$CURRENT" = "$OPENCODE_NPM_VERSION" ]; then | ||
| echo "opencode already at $OPENCODE_NPM_VERSION" | ||
| if [ -n "$AGENT_SERVER_DIR" ] && [ -f "$AGENT_SERVER_DIR/package.json" ]; then | ||
| echo "installing agent-server from $AGENT_SERVER_DIR..." | ||
| ( | ||
| cd "$AGENT_SERVER_DIR" | ||
| npm ci | ||
| npm run build | ||
| npm install -g . | ||
| ) | ||
| ln -sf "$NODE_BIN_DIR/agent-server" /usr/local/bin/agent-server | ||
| echo "agent-server installed: /usr/local/bin/agent-server" | ||
| else | ||
| echo "installing opencode${OPENCODE_NPM_VERSION:+ $OPENCODE_NPM_VERSION} via npm..." | ||
| npm install -g "opencode-ai@${OPENCODE_NPM_VERSION:-latest}" | ||
| ln -sf "$NODE_BIN_DIR/opencode" /usr/local/bin/opencode | ||
| echo "opencode installed: $(/usr/local/bin/opencode --version 2>/dev/null)" | ||
| echo "agent-server repo not found; clone appx-org/agent-server next to appx or set AGENT_SERVER_DIR" |
There was a problem hiding this comment.
That's obviously brittle. Long term want to get it from npm registry
| @@ -1,3 +1,2 @@ | |||
| DROP INDEX IF EXISTS idx_assigned_port_unique; | |||
| ALTER TABLE projects DROP COLUMN assigned_port; | |||
| ALTER TABLE projects DROP COLUMN opencode_project_id; | |||
There was a problem hiding this comment.
Why we drop the opencode column without adding pi column? Where do we get info from?
There was a problem hiding this comment.
To use Codex here, create a Codex account and connect to github.
There was a problem hiding this comment.
Cool idea! Really like how we can reduce attack surface by leveraging pi extensions
There was a problem hiding this comment.
We'll extend this template with references and existing app examples in the future
|
Is there now just one Pi server for all apps created from agent-server? @codex |
|
To use Codex here, create a Codex account and connect to github. |
There was a problem hiding this comment.
What does this file do?
There was a problem hiding this comment.
Not really sure why we need it if agent-server is always in loopback (on same server as all apps)
There was a problem hiding this comment.
This is reverse proxy to route calls made on appx UI to agent-server backend
| api.HandleFunc("GET /api/settings/api-key", handleGetAPIKeyStatus(a.Store)) | ||
| api.HandleFunc("PUT /api/settings/api-key", handleSetAPIKey(a.Store, oc)) | ||
| api.HandleFunc("DELETE /api/settings/api-key", handleDeleteAPIKey(a.Store, oc)) |
There was a problem hiding this comment.
How do we handle API key in pi now?
| } | ||
| agentServerURL := rcfg.AgentServerURL | ||
| if agentServerURL == "" { | ||
| agentServerURL = "http://127.0.0.1:4001" |
There was a problem hiding this comment.
hardcoded - want to pass by env var
| api.Handle("GET /api/agent/{agentPath...}", agentGlobalProxy) | ||
| api.Handle("POST /api/agent/{agentPath...}", agentGlobalProxy) | ||
| api.Handle("PUT /api/agent/{agentPath...}", agentGlobalProxy) | ||
| api.Handle("DELETE /api/agent/{agentPath...}", agentGlobalProxy) | ||
| agentProxy := agentServerProxyHandler(pm, agentServerURL, rcfg.AgentServerToken) | ||
| api.Handle("GET /api/projects/{id}/agent/{agentPath...}", agentProxy) | ||
| api.Handle("POST /api/projects/{id}/agent/{agentPath...}", agentProxy) | ||
| api.Handle("PATCH /api/projects/{id}/agent/{agentPath...}", agentProxy) | ||
| api.Handle("DELETE /api/projects/{id}/agent/{agentPath...}", agentProxy) |
There was a problem hiding this comment.
Are those APIs mirroring agent-server?
There was a problem hiding this comment.
yes, for reverse proxy
|
|
||
| // OpenCode health endpoint — registered on the outer mux before the /api/opencode/ | ||
| // proxy so the more-specific pattern takes precedence. Protected by auth middleware. | ||
| mux.Handle("GET /api/opencode/health", a.Middleware(http.HandlerFunc(handleOpenCodeHealth(oc)))) |
There was a problem hiding this comment.
We should prly have an endpoint for health of agent-server in case it gets stuck without failing and systemd doesn't restart it (there are no healthchecks in systemd afaik)
| export interface AgentAuthProvider { | ||
| 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; | ||
| } | ||
|
|
There was a problem hiding this comment.
All of those interfaces and agent-server UI in general should probably live in a dedicated repo (as part of agent-server?)
| export type PiSessionInfo = { | ||
| id: string; | ||
| createdAt: string; | ||
| firstMessage: string; | ||
| messageCount: number; | ||
| }; | ||
|
|
||
| export type ThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh'; | ||
|
|
||
| export type PiAgentModel = { | ||
| provider: string; | ||
| id: string; | ||
| name: string; | ||
| api: string; | ||
| reasoning: boolean; | ||
| available: boolean; | ||
| input: Array<'text' | 'image'>; | ||
| contextWindow: number; | ||
| maxTokens: number; | ||
| defaultThinkingLevel?: ThinkingLevel; | ||
| }; |
There was a problem hiding this comment.
Similar comment - it's best if backend and frontend for interacting with agents live in one place (e.g. agent-server) rather than be separated in two different repos. It's harder to keep them in sync in two different repos
There was a problem hiding this comment.
I don't think we have anything like that in any of the apps so far. I'm not sure but it looks like it's stepping into agent-server area of responsibility
There was a problem hiding this comment.
It turns SSE stream from agent-server into renderable UiMessage
There was a problem hiding this comment.
That got quite bigger. Need to investigate
| CURRENT=$(/usr/local/bin/opencode --version 2>/dev/null || echo "") | ||
| AGENT_SERVER_DIR="${AGENT_SERVER_DIR:-}" | ||
| if [ -z "$AGENT_SERVER_DIR" ] && [ -d "$REPO_DIR/../agent-server" ]; then | ||
| AGENT_SERVER_DIR="$(cd "$REPO_DIR/../agent-server" && pwd)" |
There was a problem hiding this comment.
I would actually pass agent-server dir via environment variables similarly to how we pass some other vars (I'll look for reference)
| const thinkingLabels: Record<ThinkingLevel, string> = { | ||
| off: 'Off', | ||
| minimal: 'Minimal', | ||
| low: 'Low', | ||
| medium: 'Medium', | ||
| high: 'High', | ||
| xhigh: 'X-high', | ||
| }; |
There was a problem hiding this comment.
those are model-specific
| const styles: Record<string, React.CSSProperties> = { | ||
| container: { | ||
| flex: 1, | ||
| display: 'flex', | ||
| flexDirection: 'column', | ||
| minHeight: 0, | ||
| overflow: 'hidden', | ||
| }, |
There was a problem hiding this comment.
We should use tailwind css
| agentProjectIDHeader = "X-Appx-Project-Id" | ||
| agentProjectNameHeader = "X-Appx-Project-Name" | ||
| agentProjectDirHeader = "X-Appx-Project-Dir" |
There was a problem hiding this comment.
Probably need only "X-Appx-Project-Id" header, others are kind of redundant and should be passed once when creating the project
Summary
Switches Appx's default agent path from legacy OpenCode to Pi through the separate
agent-serverservice.agent-serverwith trusted project id/name/directory headers..piharness files: prompt, guardrails extension, settings, and egress skill helper.Why
The old Appx flow was tied to one OpenCode backend. Pi gives Appx provider-flexible model selection, subscription auth, custom provider support, and project-local extensions, but Appx still needs a single browser contract and isolated sessions per project.
Validation
npm exec -- tsc -bpassed locally.task buildpassed.task testpassed.appx.serviceactive after installing the rebuilt binary.200using the configured sslip host header.Known Follow-Up
task lintstill reports pre-existing React hook lint errors inEgressRequestDock.tsxandOpenCodeStatus.tsx; this PR does not change those files' hook patterns.Dependency
Pairs with appx-org/agent-server#1, which provides project-scoped Pi runtimes and provider/model APIs.