# pi-perplexity ## Stack - TypeScript extension for `@mariozechner/pi-coding-agent`; loaded directly via jiti, no build artifact. - Node >=18.14.1 runtime APIs only: `fetch`, `crypto.randomUUID()`, `Buffer.from(..., "base64url")`, `Intl`. - Pi-bundled packages live in `peerDependencies` with `"*"`: `@mariozechner/pi-tui`, `@mariozechner/pi-ai`, `@sinclair/typebox`. ## Commands ```bash npm run typecheck npm test node --import @mariozechner/jiti/register --input-type=module -e "import('./src/index.ts').then((m)=>{ const entry = m.default?.default ?? m.default; if (typeof entry !== 'function') throw new Error('default export missing'); })" ``` ## File-Scoped Commands | Task | Command | |------|---------| | Single test file | `npm run test:build && node --test --experimental-test-module-mocks ".tmp-test/test/search/stream.test.js"` | | Live E2E opt-in | `PI_PERPLEXITY_E2E=1 npm test` | ## Structure - `src/index.ts` — extension entry and `perplexity_search` tool definition. - `src/auth/` — JWT extraction, email OTP login, token storage. - `src/search/` — Perplexity SSE request, stream merge, result formatting, models. - `src/render/` — TUI renderers for tool calls and results. - `src/commands/` — slash commands such as login/config. - `test/` — automated Node test suite; `.tmp-test/` is generated by `npm run test:build`. - `local_tests/` — manual/debug scripts and captured dumps; do not treat as automated coverage. - `docs/design-decisions.md` — rationale for non-obvious behavior. ## Extension Conventions - Entry point exports a default factory from `src/index.ts`. - Tool name is `perplexity_search`. - Tool `execute` signature is `(toolCallId, params, signal, onUpdate, ctx)`. - Tool results use `{ content: [{ type: "text", text }], details: { ... } }`. - String enum params use `StringEnum` from `@mariozechner/pi-ai`; `Type.Union([Type.Literal(...)])` breaks Google models. - Stream/API types stay loose: all reverse-engineered event fields are optional. ## Perplexity Protocol Gotchas - Endpoint: `POST https://www.perplexity.ai/rest/sse/perplexity_ask`. - Required Perplexity constants live in `src/constants.ts`. - Searches default to `is_incognito: true` to avoid polluting user history. - The response is `data:` JSON lines with `[DONE]`, not a fully standard SSE stream. - Events are incremental snapshots: shallow-merge top level, merge blocks by `intended_usage`, splice markdown via `chunk_starting_offset`, accumulate sources. - Completion is `event.final === true` or `event.status === "COMPLETED"`. - Answer extraction order: markdown blocks → `ask_text` blocks → event text. - Source extraction order: `web_results` block → `sources_list`; deduplicate by URL. ## Auth Gotchas - Auth tries macOS app token first via `defaults read ai.perplexity.mac authToken`, unless `PI_AUTH_NO_BORROW=1`. - Email OTP fallback uses `ctx.ui.input()`. - Tokens are stored at `~/.config/pi-perplexity/auth.json` with mode `0600`. - Stored tokens have no expiry tracking; `loadToken()` returns any cached token and expiry surfaces as 401/403 at request time. - AUTH errors preserve the cached token; users explicitly clear/re-auth with `/perplexity-login --force`. ## Boundaries ### Always - Pass `AbortSignal` through to network calls. - Return user-readable error text from tool execution instead of throwing raw errors. - Keep runtime dependency-free; use platform APIs for HTTP, SSE parsing, JWT decoding, and UUIDs. ### Ask First - Changing Perplexity request headers, model IDs, or auth flow behavior. - Adding package dependencies or generated build artifacts. - Deleting captured local test dumps or token-related files. ### Never - Never commit secrets, credentials, `.env`, cached auth tokens, or live Perplexity response dumps containing private data. - Never clear cached auth automatically on transient 401/403 responses.