Showing preview only (9,698K chars total). Download the full file or copy to clipboard to get everything.
Repository: badlogic/pi-mono
Branch: main
Commit: c2a42e3215fd
Files: 708
Total size: 9.1 MB
Directory structure:
gitextract_w7t9d370/
├── .gitattributes
├── .github/
│ ├── APPROVED_CONTRIBUTORS
│ ├── APPROVED_CONTRIBUTORS.vacation
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug.yml
│ │ ├── config.yml
│ │ └── contribution.yml
│ └── workflows/
│ ├── approve-contributor.yml
│ ├── build-binaries.yml
│ ├── ci.yml
│ ├── oss-weekend-issues.yml
│ └── pr-gate.yml
├── .gitignore
├── .husky/
│ └── pre-commit
├── .pi/
│ ├── extensions/
│ │ ├── diff.ts
│ │ ├── files.ts
│ │ ├── prompt-url-widget.ts
│ │ ├── redraws.ts
│ │ └── tps.ts
│ ├── git/
│ │ └── .gitignore
│ ├── npm/
│ │ └── .gitignore
│ └── prompts/
│ ├── cl.md
│ ├── is.md
│ └── pr.md
├── AGENTS.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── biome.json
├── package.json
├── packages/
│ ├── agent/
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── agent-loop.ts
│ │ │ ├── agent.ts
│ │ │ ├── index.ts
│ │ │ ├── proxy.ts
│ │ │ └── types.ts
│ │ ├── test/
│ │ │ ├── agent-loop.test.ts
│ │ │ ├── agent.test.ts
│ │ │ ├── bedrock-models.test.ts
│ │ │ ├── bedrock-utils.ts
│ │ │ ├── e2e.test.ts
│ │ │ └── utils/
│ │ │ ├── calculate.ts
│ │ │ └── get-current-time.ts
│ │ ├── tsconfig.build.json
│ │ └── vitest.config.ts
│ ├── ai/
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── bedrock-provider.d.ts
│ │ ├── bedrock-provider.js
│ │ ├── package.json
│ │ ├── scripts/
│ │ │ ├── generate-models.ts
│ │ │ └── generate-test-image.ts
│ │ ├── src/
│ │ │ ├── api-registry.ts
│ │ │ ├── bedrock-provider.ts
│ │ │ ├── cli.ts
│ │ │ ├── env-api-keys.ts
│ │ │ ├── index.ts
│ │ │ ├── models.generated.ts
│ │ │ ├── models.ts
│ │ │ ├── oauth.ts
│ │ │ ├── providers/
│ │ │ │ ├── amazon-bedrock.ts
│ │ │ │ ├── anthropic.ts
│ │ │ │ ├── azure-openai-responses.ts
│ │ │ │ ├── github-copilot-headers.ts
│ │ │ │ ├── google-gemini-cli.ts
│ │ │ │ ├── google-shared.ts
│ │ │ │ ├── google-vertex.ts
│ │ │ │ ├── google.ts
│ │ │ │ ├── mistral.ts
│ │ │ │ ├── openai-codex-responses.ts
│ │ │ │ ├── openai-completions.ts
│ │ │ │ ├── openai-responses-shared.ts
│ │ │ │ ├── openai-responses.ts
│ │ │ │ ├── register-builtins.ts
│ │ │ │ ├── simple-options.ts
│ │ │ │ └── transform-messages.ts
│ │ │ ├── stream.ts
│ │ │ ├── types.ts
│ │ │ └── utils/
│ │ │ ├── event-stream.ts
│ │ │ ├── hash.ts
│ │ │ ├── json-parse.ts
│ │ │ ├── oauth/
│ │ │ │ ├── anthropic.ts
│ │ │ │ ├── github-copilot.ts
│ │ │ │ ├── google-antigravity.ts
│ │ │ │ ├── google-gemini-cli.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── oauth-page.ts
│ │ │ │ ├── openai-codex.ts
│ │ │ │ ├── pkce.ts
│ │ │ │ └── types.ts
│ │ │ ├── overflow.ts
│ │ │ ├── sanitize-unicode.ts
│ │ │ ├── typebox-helpers.ts
│ │ │ └── validation.ts
│ │ ├── test/
│ │ │ ├── abort.test.ts
│ │ │ ├── anthropic-oauth.test.ts
│ │ │ ├── anthropic-tool-name-normalization.test.ts
│ │ │ ├── azure-utils.ts
│ │ │ ├── bedrock-models.test.ts
│ │ │ ├── bedrock-utils.ts
│ │ │ ├── cache-retention.test.ts
│ │ │ ├── context-overflow.test.ts
│ │ │ ├── cross-provider-handoff.test.ts
│ │ │ ├── empty.test.ts
│ │ │ ├── github-copilot-anthropic.test.ts
│ │ │ ├── github-copilot-oauth.test.ts
│ │ │ ├── google-gemini-cli-claude-thinking-header.test.ts
│ │ │ ├── google-gemini-cli-empty-stream.test.ts
│ │ │ ├── google-gemini-cli-retry-delay.test.ts
│ │ │ ├── google-shared-gemini3-unsigned-tool-call.test.ts
│ │ │ ├── google-shared-image-tool-result-routing.test.ts
│ │ │ ├── google-thinking-signature.test.ts
│ │ │ ├── google-tool-call-missing-args.test.ts
│ │ │ ├── google-vertex-api-key-resolution.test.ts
│ │ │ ├── image-tool-result.test.ts
│ │ │ ├── interleaved-thinking.test.ts
│ │ │ ├── lazy-module-load.test.ts
│ │ │ ├── oauth.ts
│ │ │ ├── openai-codex-stream.test.ts
│ │ │ ├── openai-completions-tool-choice.test.ts
│ │ │ ├── openai-completions-tool-result-images.test.ts
│ │ │ ├── openai-responses-reasoning-replay-e2e.test.ts
│ │ │ ├── openai-responses-tool-result-images.test.ts
│ │ │ ├── responseid.test.ts
│ │ │ ├── stream.test.ts
│ │ │ ├── supports-xhigh.test.ts
│ │ │ ├── tokens.test.ts
│ │ │ ├── tool-call-id-normalization.test.ts
│ │ │ ├── tool-call-without-result.test.ts
│ │ │ ├── total-tokens.test.ts
│ │ │ ├── transform-messages-copilot-openai-to-anthropic.test.ts
│ │ │ ├── unicode-surrogate.test.ts
│ │ │ ├── validation.test.ts
│ │ │ ├── xhigh.test.ts
│ │ │ └── zen.test.ts
│ │ ├── tsconfig.build.json
│ │ └── vitest.config.ts
│ ├── coding-agent/
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── docs/
│ │ │ ├── compaction.md
│ │ │ ├── custom-provider.md
│ │ │ ├── development.md
│ │ │ ├── extensions.md
│ │ │ ├── json.md
│ │ │ ├── keybindings.md
│ │ │ ├── models.md
│ │ │ ├── packages.md
│ │ │ ├── prompt-templates.md
│ │ │ ├── providers.md
│ │ │ ├── rpc.md
│ │ │ ├── sdk.md
│ │ │ ├── session.md
│ │ │ ├── settings.md
│ │ │ ├── shell-aliases.md
│ │ │ ├── skills.md
│ │ │ ├── terminal-setup.md
│ │ │ ├── termux.md
│ │ │ ├── themes.md
│ │ │ ├── tmux.md
│ │ │ ├── tree.md
│ │ │ ├── tui.md
│ │ │ └── windows.md
│ │ ├── examples/
│ │ │ ├── README.md
│ │ │ ├── extensions/
│ │ │ │ ├── README.md
│ │ │ │ ├── antigravity-image-gen.ts
│ │ │ │ ├── auto-commit-on-exit.ts
│ │ │ │ ├── bash-spawn-hook.ts
│ │ │ │ ├── bookmark.ts
│ │ │ │ ├── built-in-tool-renderer.ts
│ │ │ │ ├── claude-rules.ts
│ │ │ │ ├── commands.ts
│ │ │ │ ├── confirm-destructive.ts
│ │ │ │ ├── custom-compaction.ts
│ │ │ │ ├── custom-footer.ts
│ │ │ │ ├── custom-header.ts
│ │ │ │ ├── custom-provider-anthropic/
│ │ │ │ │ ├── .gitignore
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── package.json
│ │ │ │ ├── custom-provider-gitlab-duo/
│ │ │ │ │ ├── .gitignore
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── package.json
│ │ │ │ │ └── test.ts
│ │ │ │ ├── custom-provider-qwen-cli/
│ │ │ │ │ ├── .gitignore
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── package.json
│ │ │ │ ├── dirty-repo-guard.ts
│ │ │ │ ├── doom-overlay/
│ │ │ │ │ ├── .gitignore
│ │ │ │ │ ├── README.md
│ │ │ │ │ ├── doom/
│ │ │ │ │ │ ├── build/
│ │ │ │ │ │ │ ├── doom.js
│ │ │ │ │ │ │ └── doom.wasm
│ │ │ │ │ │ ├── build.sh
│ │ │ │ │ │ └── doomgeneric_pi.c
│ │ │ │ │ ├── doom-component.ts
│ │ │ │ │ ├── doom-engine.ts
│ │ │ │ │ ├── doom-keys.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── wad-finder.ts
│ │ │ │ ├── dynamic-resources/
│ │ │ │ │ ├── SKILL.md
│ │ │ │ │ ├── dynamic.json
│ │ │ │ │ ├── dynamic.md
│ │ │ │ │ └── index.ts
│ │ │ │ ├── dynamic-tools.ts
│ │ │ │ ├── event-bus.ts
│ │ │ │ ├── file-trigger.ts
│ │ │ │ ├── git-checkpoint.ts
│ │ │ │ ├── handoff.ts
│ │ │ │ ├── hello.ts
│ │ │ │ ├── inline-bash.ts
│ │ │ │ ├── input-transform.ts
│ │ │ │ ├── interactive-shell.ts
│ │ │ │ ├── mac-system-theme.ts
│ │ │ │ ├── message-renderer.ts
│ │ │ │ ├── minimal-mode.ts
│ │ │ │ ├── modal-editor.ts
│ │ │ │ ├── model-status.ts
│ │ │ │ ├── notify.ts
│ │ │ │ ├── overlay-qa-tests.ts
│ │ │ │ ├── overlay-test.ts
│ │ │ │ ├── permission-gate.ts
│ │ │ │ ├── pirate.ts
│ │ │ │ ├── plan-mode/
│ │ │ │ │ ├── README.md
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── utils.ts
│ │ │ │ ├── preset.ts
│ │ │ │ ├── protected-paths.ts
│ │ │ │ ├── provider-payload.ts
│ │ │ │ ├── qna.ts
│ │ │ │ ├── question.ts
│ │ │ │ ├── questionnaire.ts
│ │ │ │ ├── rainbow-editor.ts
│ │ │ │ ├── reload-runtime.ts
│ │ │ │ ├── rpc-demo.ts
│ │ │ │ ├── sandbox/
│ │ │ │ │ ├── .gitignore
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── package.json
│ │ │ │ ├── send-user-message.ts
│ │ │ │ ├── session-name.ts
│ │ │ │ ├── shutdown-command.ts
│ │ │ │ ├── snake.ts
│ │ │ │ ├── space-invaders.ts
│ │ │ │ ├── ssh.ts
│ │ │ │ ├── status-line.ts
│ │ │ │ ├── subagent/
│ │ │ │ │ ├── README.md
│ │ │ │ │ ├── agents/
│ │ │ │ │ │ ├── planner.md
│ │ │ │ │ │ ├── reviewer.md
│ │ │ │ │ │ ├── scout.md
│ │ │ │ │ │ └── worker.md
│ │ │ │ │ ├── agents.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── prompts/
│ │ │ │ │ ├── implement-and-review.md
│ │ │ │ │ ├── implement.md
│ │ │ │ │ └── scout-and-plan.md
│ │ │ │ ├── summarize.ts
│ │ │ │ ├── system-prompt-header.ts
│ │ │ │ ├── timed-confirm.ts
│ │ │ │ ├── titlebar-spinner.ts
│ │ │ │ ├── todo.ts
│ │ │ │ ├── tool-override.ts
│ │ │ │ ├── tools.ts
│ │ │ │ ├── trigger-compact.ts
│ │ │ │ ├── truncated-tool.ts
│ │ │ │ ├── widget-placement.ts
│ │ │ │ └── with-deps/
│ │ │ │ ├── .gitignore
│ │ │ │ ├── index.ts
│ │ │ │ └── package.json
│ │ │ ├── rpc-extension-ui.ts
│ │ │ └── sdk/
│ │ │ ├── 01-minimal.ts
│ │ │ ├── 02-custom-model.ts
│ │ │ ├── 03-custom-prompt.ts
│ │ │ ├── 04-skills.ts
│ │ │ ├── 05-tools.ts
│ │ │ ├── 06-extensions.ts
│ │ │ ├── 07-context-files.ts
│ │ │ ├── 08-prompt-templates.ts
│ │ │ ├── 09-api-keys-and-oauth.ts
│ │ │ ├── 10-settings.ts
│ │ │ ├── 11-sessions.ts
│ │ │ ├── 12-full-control.ts
│ │ │ └── README.md
│ │ ├── package.json
│ │ ├── scripts/
│ │ │ └── migrate-sessions.sh
│ │ ├── src/
│ │ │ ├── bun/
│ │ │ │ ├── cli.ts
│ │ │ │ └── register-bedrock.ts
│ │ │ ├── cli/
│ │ │ │ ├── args.ts
│ │ │ │ ├── config-selector.ts
│ │ │ │ ├── file-processor.ts
│ │ │ │ ├── initial-message.ts
│ │ │ │ ├── list-models.ts
│ │ │ │ └── session-picker.ts
│ │ │ ├── cli.ts
│ │ │ ├── config.ts
│ │ │ ├── core/
│ │ │ │ ├── agent-session.ts
│ │ │ │ ├── auth-storage.ts
│ │ │ │ ├── bash-executor.ts
│ │ │ │ ├── compaction/
│ │ │ │ │ ├── branch-summarization.ts
│ │ │ │ │ ├── compaction.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── utils.ts
│ │ │ │ ├── defaults.ts
│ │ │ │ ├── diagnostics.ts
│ │ │ │ ├── event-bus.ts
│ │ │ │ ├── exec.ts
│ │ │ │ ├── export-html/
│ │ │ │ │ ├── ansi-to-html.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── template.css
│ │ │ │ │ ├── template.html
│ │ │ │ │ ├── template.js
│ │ │ │ │ └── tool-renderer.ts
│ │ │ │ ├── extensions/
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── loader.ts
│ │ │ │ │ ├── runner.ts
│ │ │ │ │ ├── types.ts
│ │ │ │ │ └── wrapper.ts
│ │ │ │ ├── footer-data-provider.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── keybindings.ts
│ │ │ │ ├── messages.ts
│ │ │ │ ├── model-registry.ts
│ │ │ │ ├── model-resolver.ts
│ │ │ │ ├── package-manager.ts
│ │ │ │ ├── prompt-templates.ts
│ │ │ │ ├── resolve-config-value.ts
│ │ │ │ ├── resource-loader.ts
│ │ │ │ ├── sdk.ts
│ │ │ │ ├── session-manager.ts
│ │ │ │ ├── settings-manager.ts
│ │ │ │ ├── skills.ts
│ │ │ │ ├── slash-commands.ts
│ │ │ │ ├── system-prompt.ts
│ │ │ │ ├── timings.ts
│ │ │ │ └── tools/
│ │ │ │ ├── bash.ts
│ │ │ │ ├── edit-diff.ts
│ │ │ │ ├── edit.ts
│ │ │ │ ├── file-mutation-queue.ts
│ │ │ │ ├── find.ts
│ │ │ │ ├── grep.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── ls.ts
│ │ │ │ ├── path-utils.ts
│ │ │ │ ├── read.ts
│ │ │ │ ├── truncate.ts
│ │ │ │ └── write.ts
│ │ │ ├── index.ts
│ │ │ ├── main.ts
│ │ │ ├── migrations.ts
│ │ │ ├── modes/
│ │ │ │ ├── index.ts
│ │ │ │ ├── interactive/
│ │ │ │ │ ├── components/
│ │ │ │ │ │ ├── armin.ts
│ │ │ │ │ │ ├── assistant-message.ts
│ │ │ │ │ │ ├── bash-execution.ts
│ │ │ │ │ │ ├── bordered-loader.ts
│ │ │ │ │ │ ├── branch-summary-message.ts
│ │ │ │ │ │ ├── compaction-summary-message.ts
│ │ │ │ │ │ ├── config-selector.ts
│ │ │ │ │ │ ├── countdown-timer.ts
│ │ │ │ │ │ ├── custom-editor.ts
│ │ │ │ │ │ ├── custom-message.ts
│ │ │ │ │ │ ├── daxnuts.ts
│ │ │ │ │ │ ├── diff.ts
│ │ │ │ │ │ ├── dynamic-border.ts
│ │ │ │ │ │ ├── extension-editor.ts
│ │ │ │ │ │ ├── extension-input.ts
│ │ │ │ │ │ ├── extension-selector.ts
│ │ │ │ │ │ ├── footer.ts
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ ├── keybinding-hints.ts
│ │ │ │ │ │ ├── login-dialog.ts
│ │ │ │ │ │ ├── model-selector.ts
│ │ │ │ │ │ ├── oauth-selector.ts
│ │ │ │ │ │ ├── scoped-models-selector.ts
│ │ │ │ │ │ ├── session-selector-search.ts
│ │ │ │ │ │ ├── session-selector.ts
│ │ │ │ │ │ ├── settings-selector.ts
│ │ │ │ │ │ ├── show-images-selector.ts
│ │ │ │ │ │ ├── skill-invocation-message.ts
│ │ │ │ │ │ ├── theme-selector.ts
│ │ │ │ │ │ ├── thinking-selector.ts
│ │ │ │ │ │ ├── tool-execution.ts
│ │ │ │ │ │ ├── tree-selector.ts
│ │ │ │ │ │ ├── user-message-selector.ts
│ │ │ │ │ │ ├── user-message.ts
│ │ │ │ │ │ └── visual-truncate.ts
│ │ │ │ │ ├── interactive-mode.ts
│ │ │ │ │ └── theme/
│ │ │ │ │ ├── dark.json
│ │ │ │ │ ├── light.json
│ │ │ │ │ ├── theme-schema.json
│ │ │ │ │ └── theme.ts
│ │ │ │ ├── print-mode.ts
│ │ │ │ └── rpc/
│ │ │ │ ├── jsonl.ts
│ │ │ │ ├── rpc-client.ts
│ │ │ │ ├── rpc-mode.ts
│ │ │ │ └── rpc-types.ts
│ │ │ └── utils/
│ │ │ ├── changelog.ts
│ │ │ ├── child-process.ts
│ │ │ ├── clipboard-image.ts
│ │ │ ├── clipboard-native.ts
│ │ │ ├── clipboard.ts
│ │ │ ├── exif-orientation.ts
│ │ │ ├── frontmatter.ts
│ │ │ ├── git.ts
│ │ │ ├── image-convert.ts
│ │ │ ├── image-resize.ts
│ │ │ ├── mime.ts
│ │ │ ├── photon.ts
│ │ │ ├── shell.ts
│ │ │ ├── sleep.ts
│ │ │ └── tools-manager.ts
│ │ ├── test/
│ │ │ ├── agent-session-auto-compaction-queue.test.ts
│ │ │ ├── agent-session-branching.test.ts
│ │ │ ├── agent-session-compaction.test.ts
│ │ │ ├── agent-session-concurrent.test.ts
│ │ │ ├── agent-session-dynamic-provider.test.ts
│ │ │ ├── agent-session-dynamic-tools.test.ts
│ │ │ ├── agent-session-model-switch-thinking.test.ts
│ │ │ ├── agent-session-retry.test.ts
│ │ │ ├── agent-session-tree-navigation.test.ts
│ │ │ ├── args.test.ts
│ │ │ ├── auth-storage.test.ts
│ │ │ ├── bash-close-hang-windows.test.ts
│ │ │ ├── block-images.test.ts
│ │ │ ├── clipboard-image-bmp-conversion.test.ts
│ │ │ ├── clipboard-image.test.ts
│ │ │ ├── compaction-extensions-example.test.ts
│ │ │ ├── compaction-extensions.test.ts
│ │ │ ├── compaction-serialization.test.ts
│ │ │ ├── compaction-summary-reasoning.test.ts
│ │ │ ├── compaction-thinking-model.test.ts
│ │ │ ├── compaction.test.ts
│ │ │ ├── extensions-discovery.test.ts
│ │ │ ├── extensions-input-event.test.ts
│ │ │ ├── extensions-runner.test.ts
│ │ │ ├── file-mutation-queue.test.ts
│ │ │ ├── fixtures/
│ │ │ │ ├── assistant-message-with-thinking-code.json
│ │ │ │ ├── before-compaction.jsonl
│ │ │ │ ├── empty-agent/
│ │ │ │ │ └── .gitkeep
│ │ │ │ ├── empty-cwd/
│ │ │ │ │ └── .gitkeep
│ │ │ │ ├── large-session.jsonl
│ │ │ │ ├── skills/
│ │ │ │ │ ├── consecutive-hyphens/
│ │ │ │ │ │ └── SKILL.md
│ │ │ │ │ ├── disable-model-invocation/
│ │ │ │ │ │ └── SKILL.md
│ │ │ │ │ ├── invalid-name-chars/
│ │ │ │ │ │ └── SKILL.md
│ │ │ │ │ ├── invalid-yaml/
│ │ │ │ │ │ └── SKILL.md
│ │ │ │ │ ├── long-name/
│ │ │ │ │ │ └── SKILL.md
│ │ │ │ │ ├── missing-description/
│ │ │ │ │ │ └── SKILL.md
│ │ │ │ │ ├── multiline-description/
│ │ │ │ │ │ └── SKILL.md
│ │ │ │ │ ├── name-mismatch/
│ │ │ │ │ │ └── SKILL.md
│ │ │ │ │ ├── nested/
│ │ │ │ │ │ └── child-skill/
│ │ │ │ │ │ └── SKILL.md
│ │ │ │ │ ├── no-frontmatter/
│ │ │ │ │ │ └── SKILL.md
│ │ │ │ │ ├── root-skill-preferred/
│ │ │ │ │ │ ├── SKILL.md
│ │ │ │ │ │ └── nested-child/
│ │ │ │ │ │ └── SKILL.md
│ │ │ │ │ ├── unknown-field/
│ │ │ │ │ │ └── SKILL.md
│ │ │ │ │ └── valid-skill/
│ │ │ │ │ └── SKILL.md
│ │ │ │ └── skills-collision/
│ │ │ │ ├── first/
│ │ │ │ │ └── calendar/
│ │ │ │ │ └── SKILL.md
│ │ │ │ └── second/
│ │ │ │ └── calendar/
│ │ │ │ └── SKILL.md
│ │ │ ├── footer-data-provider.test.ts
│ │ │ ├── footer-width.test.ts
│ │ │ ├── frontmatter.test.ts
│ │ │ ├── git-ssh-url.test.ts
│ │ │ ├── git-update.test.ts
│ │ │ ├── image-processing.test.ts
│ │ │ ├── initial-message.test.ts
│ │ │ ├── interactive-mode-status.test.ts
│ │ │ ├── keybindings-migration.test.ts
│ │ │ ├── model-registry.test.ts
│ │ │ ├── model-resolver.test.ts
│ │ │ ├── package-command-paths.test.ts
│ │ │ ├── package-manager-ssh.test.ts
│ │ │ ├── package-manager.test.ts
│ │ │ ├── path-utils.test.ts
│ │ │ ├── plan-mode-utils.test.ts
│ │ │ ├── prompt-templates.test.ts
│ │ │ ├── resource-loader.test.ts
│ │ │ ├── rpc-example.ts
│ │ │ ├── rpc-jsonl.test.ts
│ │ │ ├── rpc.test.ts
│ │ │ ├── sdk-codex-cache-probe-tool-loop.ts
│ │ │ ├── sdk-skills.test.ts
│ │ │ ├── session-info-modified-timestamp.test.ts
│ │ │ ├── session-manager/
│ │ │ │ ├── build-context.test.ts
│ │ │ │ ├── custom-session-id.test.ts
│ │ │ │ ├── file-operations.test.ts
│ │ │ │ ├── labels.test.ts
│ │ │ │ ├── migration.test.ts
│ │ │ │ ├── save-entry.test.ts
│ │ │ │ └── tree-traversal.test.ts
│ │ │ ├── session-selector-path-delete.test.ts
│ │ │ ├── session-selector-rename.test.ts
│ │ │ ├── session-selector-search.test.ts
│ │ │ ├── settings-manager-bug.test.ts
│ │ │ ├── settings-manager.test.ts
│ │ │ ├── skills.test.ts
│ │ │ ├── streaming-render-debug.ts
│ │ │ ├── system-prompt.test.ts
│ │ │ ├── test-theme-colors.ts
│ │ │ ├── tool-execution-component.test.ts
│ │ │ ├── tools.test.ts
│ │ │ ├── tree-selector.test.ts
│ │ │ ├── truncate-to-width.test.ts
│ │ │ └── utilities.ts
│ │ ├── tsconfig.build.json
│ │ ├── tsconfig.examples.json
│ │ └── vitest.config.ts
│ ├── mom/
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── dev.sh
│ │ ├── docker.sh
│ │ ├── docs/
│ │ │ ├── artifacts-server.md
│ │ │ ├── events.md
│ │ │ ├── new.md
│ │ │ ├── sandbox.md
│ │ │ ├── slack-bot-minimal-guide.md
│ │ │ └── v86.md
│ │ ├── package.json
│ │ ├── scripts/
│ │ │ └── migrate-timestamps.ts
│ │ ├── src/
│ │ │ ├── agent.ts
│ │ │ ├── context.ts
│ │ │ ├── download.ts
│ │ │ ├── events.ts
│ │ │ ├── log.ts
│ │ │ ├── main.ts
│ │ │ ├── sandbox.ts
│ │ │ ├── slack.ts
│ │ │ ├── store.ts
│ │ │ └── tools/
│ │ │ ├── attach.ts
│ │ │ ├── bash.ts
│ │ │ ├── edit.ts
│ │ │ ├── index.ts
│ │ │ ├── read.ts
│ │ │ ├── truncate.ts
│ │ │ └── write.ts
│ │ └── tsconfig.build.json
│ ├── pods/
│ │ ├── README.md
│ │ ├── docs/
│ │ │ ├── gml-4.5.md
│ │ │ ├── gpt-oss.md
│ │ │ ├── implementation-plan.md
│ │ │ ├── kimi-k2.md
│ │ │ ├── models.md
│ │ │ ├── plan.md
│ │ │ └── qwen3-coder.md
│ │ ├── package.json
│ │ ├── scripts/
│ │ │ ├── model_run.sh
│ │ │ └── pod_setup.sh
│ │ ├── src/
│ │ │ ├── cli.ts
│ │ │ ├── commands/
│ │ │ │ ├── models.ts
│ │ │ │ ├── pods.ts
│ │ │ │ └── prompt.ts
│ │ │ ├── config.ts
│ │ │ ├── index.ts
│ │ │ ├── model-configs.ts
│ │ │ ├── models.json
│ │ │ ├── ssh.ts
│ │ │ └── types.ts
│ │ └── tsconfig.build.json
│ ├── tui/
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── autocomplete.ts
│ │ │ ├── components/
│ │ │ │ ├── box.ts
│ │ │ │ ├── cancellable-loader.ts
│ │ │ │ ├── editor.ts
│ │ │ │ ├── image.ts
│ │ │ │ ├── input.ts
│ │ │ │ ├── loader.ts
│ │ │ │ ├── markdown.ts
│ │ │ │ ├── select-list.ts
│ │ │ │ ├── settings-list.ts
│ │ │ │ ├── spacer.ts
│ │ │ │ ├── text.ts
│ │ │ │ └── truncated-text.ts
│ │ │ ├── editor-component.ts
│ │ │ ├── fuzzy.ts
│ │ │ ├── index.ts
│ │ │ ├── keybindings.ts
│ │ │ ├── keys.ts
│ │ │ ├── kill-ring.ts
│ │ │ ├── stdin-buffer.ts
│ │ │ ├── terminal-image.ts
│ │ │ ├── terminal.ts
│ │ │ ├── tui.ts
│ │ │ ├── undo-stack.ts
│ │ │ └── utils.ts
│ │ ├── test/
│ │ │ ├── autocomplete.test.ts
│ │ │ ├── bug-regression-isimageline-startswith-bug.test.ts
│ │ │ ├── chat-simple.ts
│ │ │ ├── editor.test.ts
│ │ │ ├── fuzzy.test.ts
│ │ │ ├── image-test.ts
│ │ │ ├── input.test.ts
│ │ │ ├── key-tester.ts
│ │ │ ├── keys.test.ts
│ │ │ ├── markdown.test.ts
│ │ │ ├── overlay-non-capturing.test.ts
│ │ │ ├── overlay-options.test.ts
│ │ │ ├── overlay-short-content.test.ts
│ │ │ ├── regression-regional-indicator-width.test.ts
│ │ │ ├── select-list.test.ts
│ │ │ ├── stdin-buffer.test.ts
│ │ │ ├── terminal-image.test.ts
│ │ │ ├── test-themes.ts
│ │ │ ├── truncated-text.test.ts
│ │ │ ├── tui-overlay-style-leak.test.ts
│ │ │ ├── tui-render.test.ts
│ │ │ ├── viewport-overwrite-repro.ts
│ │ │ ├── virtual-terminal.ts
│ │ │ └── wrap-ansi.test.ts
│ │ ├── tsconfig.build.json
│ │ └── vitest.config.ts
│ └── web-ui/
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── example/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── app.css
│ │ │ ├── custom-messages.ts
│ │ │ └── main.ts
│ │ ├── tsconfig.json
│ │ └── vite.config.ts
│ ├── package.json
│ ├── scripts/
│ │ └── count-prompt-tokens.ts
│ ├── src/
│ │ ├── ChatPanel.ts
│ │ ├── app.css
│ │ ├── components/
│ │ │ ├── AgentInterface.ts
│ │ │ ├── AttachmentTile.ts
│ │ │ ├── ConsoleBlock.ts
│ │ │ ├── CustomProviderCard.ts
│ │ │ ├── ExpandableSection.ts
│ │ │ ├── Input.ts
│ │ │ ├── MessageEditor.ts
│ │ │ ├── MessageList.ts
│ │ │ ├── Messages.ts
│ │ │ ├── ProviderKeyInput.ts
│ │ │ ├── SandboxedIframe.ts
│ │ │ ├── StreamingMessageContainer.ts
│ │ │ ├── ThinkingBlock.ts
│ │ │ ├── message-renderer-registry.ts
│ │ │ └── sandbox/
│ │ │ ├── ArtifactsRuntimeProvider.ts
│ │ │ ├── AttachmentsRuntimeProvider.ts
│ │ │ ├── ConsoleRuntimeProvider.ts
│ │ │ ├── FileDownloadRuntimeProvider.ts
│ │ │ ├── RuntimeMessageBridge.ts
│ │ │ ├── RuntimeMessageRouter.ts
│ │ │ └── SandboxRuntimeProvider.ts
│ │ ├── dialogs/
│ │ │ ├── ApiKeyPromptDialog.ts
│ │ │ ├── AttachmentOverlay.ts
│ │ │ ├── CustomProviderDialog.ts
│ │ │ ├── ModelSelector.ts
│ │ │ ├── PersistentStorageDialog.ts
│ │ │ ├── ProvidersModelsTab.ts
│ │ │ ├── SessionListDialog.ts
│ │ │ └── SettingsDialog.ts
│ │ ├── index.ts
│ │ ├── prompts/
│ │ │ └── prompts.ts
│ │ ├── storage/
│ │ │ ├── app-storage.ts
│ │ │ ├── backends/
│ │ │ │ └── indexeddb-storage-backend.ts
│ │ │ ├── store.ts
│ │ │ ├── stores/
│ │ │ │ ├── custom-providers-store.ts
│ │ │ │ ├── provider-keys-store.ts
│ │ │ │ ├── sessions-store.ts
│ │ │ │ └── settings-store.ts
│ │ │ └── types.ts
│ │ ├── tools/
│ │ │ ├── artifacts/
│ │ │ │ ├── ArtifactElement.ts
│ │ │ │ ├── ArtifactPill.ts
│ │ │ │ ├── Console.ts
│ │ │ │ ├── DocxArtifact.ts
│ │ │ │ ├── ExcelArtifact.ts
│ │ │ │ ├── GenericArtifact.ts
│ │ │ │ ├── HtmlArtifact.ts
│ │ │ │ ├── ImageArtifact.ts
│ │ │ │ ├── MarkdownArtifact.ts
│ │ │ │ ├── PdfArtifact.ts
│ │ │ │ ├── SvgArtifact.ts
│ │ │ │ ├── TextArtifact.ts
│ │ │ │ ├── artifacts-tool-renderer.ts
│ │ │ │ ├── artifacts.ts
│ │ │ │ └── index.ts
│ │ │ ├── extract-document.ts
│ │ │ ├── index.ts
│ │ │ ├── javascript-repl.ts
│ │ │ ├── renderer-registry.ts
│ │ │ ├── renderers/
│ │ │ │ ├── BashRenderer.ts
│ │ │ │ ├── CalculateRenderer.ts
│ │ │ │ ├── DefaultRenderer.ts
│ │ │ │ └── GetCurrentTimeRenderer.ts
│ │ │ └── types.ts
│ │ └── utils/
│ │ ├── attachment-utils.ts
│ │ ├── auth-token.ts
│ │ ├── format.ts
│ │ ├── i18n.ts
│ │ ├── model-discovery.ts
│ │ ├── proxy-utils.ts
│ │ └── test-sessions.ts
│ ├── tsconfig.build.json
│ └── tsconfig.json
├── pi-mono.code-workspace
├── pi-test.sh
├── scripts/
│ ├── browser-smoke-entry.ts
│ ├── build-binaries.sh
│ ├── check-browser-smoke.mjs
│ ├── cost.ts
│ ├── oss-weekend.mjs
│ ├── release.mjs
│ ├── session-transcripts.ts
│ └── sync-versions.js
├── test.sh
├── tsconfig.base.json
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
# Default to LF for text files across the repo
* text=auto eol=lf
# Windows scripts should keep CRLF
*.bat text eol=crlf
*.cmd text eol=crlf
*.ps1 text eol=crlf
# Shell scripts should keep LF
*.sh text eol=lf
# Common binary assets
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.webp binary
*.ico binary
*.pdf binary
*.zip binary
*.gz binary
*.woff binary
*.woff2 binary
================================================
FILE: .github/APPROVED_CONTRIBUTORS
================================================
# GitHub handles of users approved to submit PRs
# One handle per line (without @)
# Add new contributors by commenting lgtm on their issue
barapa
alasano
aadishv
airtonix
aliou
aos
austinm911
banteg
ben-vargas
butelo
can1357
CarlosGtrz
cau1k
cmf
crcatala
Cursivez
cv
dannote
default-anton
dnouri
DronNick
enisdenjo
ferologics
fightbulc
ghoulr
gnattu
HACKE-RC
hewliyang
hjanuschka
iamd3vil
jblwilliams
joshp123
jsinge97
justram
kaofelix
kiliman
kim0
lockmeister
LukeFost
lukele
m-box-mr
marckrenn
markusylisiurunen
mcinteerj
melihmucuk
mitsuhiko
mrexodia
nathyong
nickseelert
nicobailon
ninlds
ogulcancelik
patrick-kidger
paulbettner
Perlence
pjtf93
prateekmedia
prathamdby
ribelo
richardgill
robinwander
ronyrus
roshanasingh4
scutifer
skuridin
steipete
svkozak
tallshort
theBucky
thomasmhr
tiagoefreitas
timolins
tmustier
tudoroancea
unexge
vaayne
VaclavSynacek
vsabavat
w-winter
Whamp
WismutHansen
XesGaDeus
yevhen
badlogictest
terrorobe
zedrdave
mrud
toorusr
andresaraujo
lightningRalf
williballenthin
masonc15
4h9fbZ
haoqixu
Graffioh
charles-cooper
emanuelst
juanibiapina
liby
pasky
odysseus0
giuseppeg
michaelpersonal
academo
PriNova
semtexzv
jasonish
markusn
SamFold
Soleone
virtuald
NateSmyth
7Sageer
MatthieuBizien
sumeet
marchellodev
vedang
lucemia
mcollina
lajarre
smithbm2316
drewburr
gordonhwc
deybhayden
tintinweb
asoules
zhahaoyu
in0vik
jtac
yzhg1983
smcllns
dmmulroy
zmberber
================================================
FILE: .github/APPROVED_CONTRIBUTORS.vacation
================================================
# GitHub handles of users approved to submit PRs
# One handle per line (without @)
# Add new contributors by commenting lgtm on their issue
aadishv
airtonix
aliou
aos
austinm911
banteg
ben-vargas
butelo
can1357
CarlosGtrz
cau1k
cmf
crcatala
Cursivez
cv
dannote
default-anton
dnouri
DronNick
enisdenjo
ferologics
fightbulc
ghoulr
gnattu
HACKE-RC
hewliyang
hjanuschka
iamd3vil
jblwilliams
joshp123
jsinge97
justram
kaofelix
kiliman
kim0
lockmeister
LukeFost
lukele
m-box-mr
marckrenn
markusylisiurunen
mcinteerj
melihmucuk
mitsuhiko
mrexodia
nathyong
nickseelert
nicobailon
ninlds
ogulcancelik
patrick-kidger
paulbettner
Perlence
pjtf93
prateekmedia
prathamdby
ribelo
richardgill
robinwander
ronyrus
roshanasingh4
scutifer
skuridin
steipete
svkozak
tallshort
theBucky
thomasmhr
tiagoefreitas
timolins
tmustier
tudoroancea
unexge
vaayne
VaclavSynacek
vsabavat
w-winter
Whamp
WismutHansen
XesGaDeus
yevhen
badlogictest
terrorobe
zedrdave
mrud
toorusr
andresaraujo
lightningRalf
williballenthin
masonc15
4h9fbZ
haoqixu
Graffioh
charles-cooper
emanuelst
juanibiapina
liby
pasky
odysseus0
giuseppeg
michaelpersonal
academo
PriNova
semtexzv
jasonish
markusn
SamFold
Soleone
virtuald
NateSmyth
7Sageer
MatthieuBizien
sumeet
marchellodev
vedang
================================================
FILE: .github/ISSUE_TEMPLATE/bug.yml
================================================
name: Bug Report
description: Report something that's broken
labels: ["bug"]
body:
- type: textarea
id: description
attributes:
label: What happened?
description: Be specific. Include error messages if any.
validations:
required: true
- type: textarea
id: repro
attributes:
label: Steps to reproduce
description: Minimal steps to trigger the bug.
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected behavior
validations:
required: false
- type: input
id: version
attributes:
label: Version
description: e.g. 0.49.0
validations:
required: false
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: Questions
url: https://discord.com/invite/3cU7Bz4UPx
about: Ask questions on Discord instead of opening an issue
================================================
FILE: .github/ISSUE_TEMPLATE/contribution.yml
================================================
name: Contribution Proposal
description: Propose a change or feature (required for new contributors before submitting a PR)
labels: []
body:
- type: markdown
attributes:
value: |
**Before you start:** Read [CONTRIBUTING.md](https://github.com/badlogic/pi-mono/blob/main/CONTRIBUTING.md).
Keep this short. If it doesn't fit on one screen, it's too long. Write in your own voice.
- type: textarea
id: what
attributes:
label: What do you want to change?
description: Be specific and concise.
validations:
required: true
- type: textarea
id: why
attributes:
label: Why?
description: What problem does this solve?
validations:
required: true
- type: textarea
id: how
attributes:
label: How? (optional)
description: Brief technical approach if you have one in mind.
validations:
required: false
================================================
FILE: .github/workflows/approve-contributor.yml
================================================
name: Approve Contributor
on:
issue_comment:
types: [created]
jobs:
approve:
if: ${{ !github.event.issue.pull_request }}
runs-on: ubuntu-latest
permissions:
contents: write
issues: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.repository.default_branch }}
- name: Add contributor to approved list
id: update
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const issueAuthor = context.payload.issue.user.login;
const commenter = context.payload.comment.user.login;
const commentBody = context.payload.comment.body || '';
const approvedFile = '.github/APPROVED_CONTRIBUTORS';
if (!/^\s*lgtm\b/i.test(commentBody)) {
console.log('Comment does not match lgtm');
core.setOutput('status', 'skipped');
return;
}
try {
const { data: permissionLevel } = await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username: commenter
});
if (!['admin', 'write'].includes(permissionLevel.permission)) {
console.log(`${commenter} does not have write access`);
core.setOutput('status', 'skipped');
return;
}
} catch (error) {
console.log(`${commenter} does not have collaborator access`);
core.setOutput('status', 'skipped');
return;
}
let content = fs.readFileSync(approvedFile, 'utf8');
const approvedList = content
.split('\n')
.map(line => line.trim().toLowerCase())
.filter(line => line && !line.startsWith('#'));
if (approvedList.includes(issueAuthor.toLowerCase())) {
console.log(`${issueAuthor} is already approved`);
core.setOutput('status', 'already');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `@${issueAuthor} is already in the approved contributors list.`
});
return;
}
content = content.trimEnd() + '\n' + issueAuthor + '\n';
fs.writeFileSync(approvedFile, content);
console.log(`Added ${issueAuthor} to approved contributors`);
core.setOutput('status', 'added');
- name: Commit and push
if: steps.update.outputs.status == 'added'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add .github/APPROVED_CONTRIBUTORS
git diff --staged --quiet || git commit -m "chore: approve contributor ${{ github.event.issue.user.login }}"
git push
- name: Comment on issue
if: steps.update.outputs.status == 'added'
uses: actions/github-script@v7
with:
script: |
const issueAuthor = context.payload.issue.user.login;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `@${issueAuthor} has been added to the approved contributors list. You can now submit PRs. Thanks for contributing!`
});
================================================
FILE: .github/workflows/build-binaries.yml
================================================
name: Build Binaries
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
tag:
description: 'Tag to build (e.g., v0.12.0)'
required: true
type: string
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
env:
RELEASE_TAG: ${{ github.event.inputs.tag || github.ref_name }}
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ env.RELEASE_TAG }}
- name: Setup Bun
uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.1
with:
bun-version: 1.2.20
- name: Setup Node.js
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
node-version: '22'
registry-url: 'https://registry.npmjs.org'
- name: Build binaries
run: ./scripts/build-binaries.sh
- name: Extract changelog for this version
id: changelog
run: |
VERSION="${RELEASE_TAG}"
VERSION="${VERSION#v}" # Remove 'v' prefix
# Extract changelog section for this version
cd packages/coding-agent
awk "/^## \[${VERSION}\]/{flag=1; next} /^## \[/{flag=0} flag" CHANGELOG.md > /tmp/release-notes.md
# If empty, use a default message
if [ ! -s /tmp/release-notes.md ]; then
echo "Release ${VERSION}" > /tmp/release-notes.md
fi
- name: Create GitHub Release and upload binaries
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
cd packages/coding-agent/binaries
# Create release with changelog notes (or update if exists)
gh release create "${RELEASE_TAG}" \
--title "${RELEASE_TAG}" \
--notes-file /tmp/release-notes.md \
pi-darwin-arm64.tar.gz \
pi-darwin-x64.tar.gz \
pi-linux-x64.tar.gz \
pi-linux-arm64.tar.gz \
pi-windows-x64.zip \
2>/dev/null || \
gh release upload "${RELEASE_TAG}" \
pi-darwin-arm64.tar.gz \
pi-darwin-x64.tar.gz \
pi-linux-x64.tar.gz \
pi-linux-arm64.tar.gz \
pi-windows-x64.zip \
--clobber
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
build-check-test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev fd-find ripgrep
sudo ln -s $(which fdfind) /usr/local/bin/fd
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Check
run: npm run check
- name: Test
run: npm test
================================================
FILE: .github/workflows/oss-weekend-issues.yml
================================================
name: OSS Weekend Issues
on:
issues:
types: [opened]
jobs:
close-issues-during-weekend:
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
steps:
- name: Close new issues during OSS weekend
uses: actions/github-script@v7
with:
script: |
const issueAuthor = context.payload.issue.user.login;
const defaultBranch = context.payload.repository.default_branch;
if (issueAuthor.endsWith('[bot]') || issueAuthor === 'dependabot[bot]') {
console.log(`Skipping bot: ${issueAuthor}`);
return;
}
async function getPermission(username) {
try {
const { data: permissionLevel } = await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username,
});
return permissionLevel.permission;
} catch {
return null;
}
}
async function getTextFile(path) {
const { data: fileContent } = await github.rest.repos.getContent({
owner: context.repo.owner,
repo: context.repo.repo,
path,
ref: defaultBranch,
});
if (!('content' in fileContent) || typeof fileContent.content !== 'string') {
throw new Error(`Expected file content for ${path}`);
}
return Buffer.from(fileContent.content, 'base64').toString('utf8');
}
const permission = await getPermission(issueAuthor);
if (['admin', 'maintain', 'write'].includes(permission)) {
console.log(`${issueAuthor} is a collaborator with ${permission} access`);
return;
}
let weekendState;
try {
weekendState = JSON.parse(await getTextFile('.github/oss-weekend.json'));
} catch (error) {
if (error && typeof error === 'object' && 'status' in error && error.status === 404) {
console.log('OSS weekend is not active');
return;
}
throw error;
}
if (!weekendState?.active) {
console.log('OSS weekend is not active');
return;
}
const reopenDate = weekendState.reopensOnText || weekendState.reopensOn || 'after the weekend';
const discordUrl = weekendState.discordUrl || 'https://discord.com/invite/3cU7Bz4UPx';
const message = [
`Hi @${issueAuthor}, thanks for opening an issue.`,
'',
`OSS weekend is active until ${reopenDate}, so new issues are being auto-closed for now.`,
'',
`Please reopen or submit this issue again after ${reopenDate}. For support, join [Discord](${discordUrl}).`,
].join('\n');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: message,
});
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
state: 'closed',
});
================================================
FILE: .github/workflows/pr-gate.yml
================================================
name: PR Gate
on:
pull_request_target:
types: [opened]
jobs:
check-contributor:
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
pull-requests: write
steps:
- name: Check if contributor is approved
uses: actions/github-script@v7
with:
script: |
const prAuthor = context.payload.pull_request.user.login;
const defaultBranch = context.payload.repository.default_branch;
if (prAuthor.endsWith('[bot]') || prAuthor === 'dependabot[bot]') {
console.log(`Skipping bot: ${prAuthor}`);
return;
}
async function getPermission(username) {
try {
const { data: permissionLevel } = await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username,
});
return permissionLevel.permission;
} catch {
return null;
}
}
async function getTextFile(path) {
const { data: fileContent } = await github.rest.repos.getContent({
owner: context.repo.owner,
repo: context.repo.repo,
path,
ref: defaultBranch,
});
if (!('content' in fileContent) || typeof fileContent.content !== 'string') {
throw new Error(`Expected file content for ${path}`);
}
return Buffer.from(fileContent.content, 'base64').toString('utf8');
}
async function closePullRequest(message) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
body: message,
});
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
state: 'closed',
});
}
const permission = await getPermission(prAuthor);
if (['admin', 'maintain', 'write'].includes(permission)) {
console.log(`${prAuthor} is a collaborator with ${permission} access`);
return;
}
const approvedContent = await getTextFile('.github/APPROVED_CONTRIBUTORS');
const approvedList = approvedContent
.split('\n')
.map(line => line.trim().toLowerCase())
.filter(line => line && !line.startsWith('#'));
const isApprovedContributor = approvedList.includes(prAuthor.toLowerCase());
let weekendState = null;
try {
weekendState = JSON.parse(await getTextFile('.github/oss-weekend.json'));
} catch (error) {
if (!(error && typeof error === 'object' && 'status' in error && error.status === 404)) {
throw error;
}
}
if (weekendState?.active && isApprovedContributor) {
console.log(`${prAuthor} is approved, but OSS weekend is active`);
const reopenDate = weekendState.reopensOnText || weekendState.reopensOn || 'after the weekend';
const discordUrl = weekendState.discordUrl || 'https://discord.com/invite/3cU7Bz4UPx';
const message = [
`Hi @${prAuthor}, thanks for the PR.`,
'',
`OSS weekend is active until ${reopenDate}, so external PRs are being paused for now.`,
'',
'You are already on the approved contributors list, so you can resubmit this PR after the weekend without reapproval.',
'',
`This PR will be closed automatically. For support, join [Discord](${discordUrl}).`,
].join('\n');
await closePullRequest(message);
return;
}
if (isApprovedContributor) {
console.log(`${prAuthor} is in the approved contributors list`);
return;
}
console.log(`${prAuthor} is not approved, closing PR`);
const message = [
`Hi @${prAuthor}, thanks for your interest in contributing!`,
'',
'We ask new contributors to open an issue first before submitting a PR. This helps us discuss the approach and avoid wasted effort.',
'',
'**Next steps:**',
'1. Open an issue describing what you want to change and why (keep it concise, write in your human voice, AI slop will be closed)',
'2. Once a maintainer approves with `lgtm`, you\'ll be added to the approved contributors list',
'3. Then you can submit your PR',
'',
`This PR will be closed automatically. See https://github.com/${context.repo.owner}/${context.repo.repo}/blob/${defaultBranch}/CONTRIBUTING.md for more details.`,
].join('\n');
await closePullRequest(message);
================================================
FILE: .gitignore
================================================
node_modules/
dist/
*.log
.DS_Store
*.tsbuildinfo
# packages/*/node_modules/
packages/*/dist/
packages/*/dist-chrome/
packages/*/dist-firefox/
# Environment
.env
# Editor files
.vscode/
.zed/
.idea/
*.swp
*.swo
*~
# Package specific
.npm/
coverage/
.nyc_output/
.pi_config/
tui-debug.log
compaction-results/
.opencode/
syntax.jsonl
out.jsonl
pi-*.html
out.html
packages/coding-agent/binaries/
todo.md
================================================
FILE: .husky/pre-commit
================================================
#!/bin/sh
# Get list of staged files before running check
STAGED_FILES=$(git diff --cached --name-only)
# Run the check script (formatting, linting, and type checking)
echo "Running formatting, linting, and type checking..."
npm run check
if [ $? -ne 0 ]; then
echo "❌ Checks failed. Please fix the errors before committing."
exit 1
fi
RUN_BROWSER_SMOKE=0
for file in $STAGED_FILES; do
case "$file" in
packages/ai/*|packages/web-ui/*|package.json|package-lock.json)
RUN_BROWSER_SMOKE=1
break
;;
esac
done
if [ $RUN_BROWSER_SMOKE -eq 1 ]; then
echo "Running browser smoke check..."
npm run check:browser-smoke
if [ $? -ne 0 ]; then
echo "❌ Browser smoke check failed."
exit 1
fi
fi
# Restage files that were previously staged and may have been modified by formatting
for file in $STAGED_FILES; do
if [ -f "$file" ]; then
git add "$file"
fi
done
echo "✅ All pre-commit checks passed!"
================================================
FILE: .pi/extensions/diff.ts
================================================
/**
* Diff Extension
*
* /diff command shows modified/deleted/new files from git status and opens
* the selected file in VS Code's diff view.
*/
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { DynamicBorder } from "@mariozechner/pi-coding-agent";
import { Container, Key, matchesKey, type SelectItem, SelectList, Text } from "@mariozechner/pi-tui";
interface FileInfo {
status: string;
statusLabel: string;
file: string;
}
export default function (pi: ExtensionAPI) {
pi.registerCommand("diff", {
description: "Show git changes and open in VS Code diff view",
handler: async (_args, ctx) => {
if (!ctx.hasUI) {
ctx.ui.notify("No UI available", "error");
return;
}
// Get changed files from git status
const result = await pi.exec("git", ["status", "--porcelain"], { cwd: ctx.cwd });
if (result.code !== 0) {
ctx.ui.notify(`git status failed: ${result.stderr}`, "error");
return;
}
if (!result.stdout || !result.stdout.trim()) {
ctx.ui.notify("No changes in working tree", "info");
return;
}
// Parse git status output
// Format: XY filename (where XY is two-letter status, then space, then filename)
const lines = result.stdout.split("\n");
const files: FileInfo[] = [];
for (const line of lines) {
if (line.length < 4) continue; // Need at least "XY f"
const status = line.slice(0, 2);
const file = line.slice(2).trimStart();
// Translate status codes to short labels
let statusLabel: string;
if (status.includes("M")) statusLabel = "M";
else if (status.includes("A")) statusLabel = "A";
else if (status.includes("D")) statusLabel = "D";
else if (status.includes("?")) statusLabel = "?";
else if (status.includes("R")) statusLabel = "R";
else if (status.includes("C")) statusLabel = "C";
else statusLabel = status.trim() || "~";
files.push({ status: statusLabel, statusLabel, file });
}
if (files.length === 0) {
ctx.ui.notify("No changes found", "info");
return;
}
const WINDOWS_UNSAFE_CMD_CHARS_RE = /[&|<>^%\r\n]/;
const quoteCmdArg = (value: string) => `"${value.replace(/"/g, '""')}"`;
const openWithCode = async (file: string) => {
if (process.platform === "win32") {
if (WINDOWS_UNSAFE_CMD_CHARS_RE.test(file)) {
ctx.ui.notify(
`Refusing to open ${file}: path contains Windows cmd metacharacters (& | < > ^ % or newline).`,
"error",
);
return null;
}
const commandLine = `code -g ${quoteCmdArg(file)}`;
return pi.exec("cmd", ["/d", "/s", "/c", commandLine], { cwd: ctx.cwd });
}
return pi.exec("code", ["-g", file], { cwd: ctx.cwd });
};
const openSelected = async (fileInfo: FileInfo): Promise<void> => {
try {
// Open in VS Code diff view.
// For untracked files, git difftool won't work, so fall back to just opening the file.
if (fileInfo.status === "?") {
const openResult = await openWithCode(fileInfo.file);
if (!openResult) return;
if (openResult.code !== 0) {
const openStderr = openResult.stderr.trim();
ctx.ui.notify(
`Failed to open ${fileInfo.file} (exit ${openResult.code})${openStderr ? `: ${openStderr}` : ""}`,
"error",
);
}
return;
}
const diffResult = await pi.exec("git", ["difftool", "-y", "--tool=vscode", fileInfo.file], {
cwd: ctx.cwd,
});
if (diffResult.code !== 0) {
const diffStderr = diffResult.stderr.trim();
ctx.ui.notify(
`Failed to show diff with vscode for ${fileInfo.file} (exit ${diffResult.code})${diffStderr ? `: ${diffStderr}` : ""}`,
"error",
);
ctx.ui.notify(
"Troubleshooting: check git difftool config (e.g. `git config --get difftool.vscode.cmd`).",
"info",
);
const openResult = await openWithCode(fileInfo.file);
if (!openResult) return;
if (openResult.code !== 0) {
const openStderr = openResult.stderr.trim();
ctx.ui.notify(
`Failed to open ${fileInfo.file} (exit ${openResult.code})${openStderr ? `: ${openStderr}` : ""}`,
"error",
);
}
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
ctx.ui.notify(`Failed to open ${fileInfo.file}: ${message}`, "error");
}
};
// Show file picker with SelectList
await ctx.ui.custom<void>((tui, theme, _kb, done) => {
const container = new Container();
// Top border
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
// Title
container.addChild(new Text(theme.fg("accent", theme.bold(" Select file to diff")), 0, 0));
// Build select items with colored status
const items: SelectItem[] = files.map((f) => {
let statusColor: string;
switch (f.status) {
case "M":
statusColor = theme.fg("warning", f.status);
break;
case "A":
statusColor = theme.fg("success", f.status);
break;
case "D":
statusColor = theme.fg("error", f.status);
break;
case "?":
statusColor = theme.fg("muted", f.status);
break;
default:
statusColor = theme.fg("dim", f.status);
}
return {
value: f,
label: `${statusColor} ${f.file}`,
};
});
const visibleRows = Math.min(files.length, 15);
let currentIndex = 0;
const selectList = new SelectList(items, visibleRows, {
selectedPrefix: (t) => theme.fg("accent", t),
selectedText: (t) => t, // Keep existing colors
description: (t) => theme.fg("muted", t),
scrollInfo: (t) => theme.fg("dim", t),
noMatch: (t) => theme.fg("warning", t),
});
selectList.onSelect = (item) => {
void openSelected(item.value as FileInfo);
};
selectList.onCancel = () => done();
selectList.onSelectionChange = (item) => {
currentIndex = items.indexOf(item);
};
container.addChild(selectList);
// Help text
container.addChild(
new Text(theme.fg("dim", " ↑↓ navigate • ←→ page • enter open • esc close"), 0, 0),
);
// Bottom border
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
return {
render: (w) => container.render(w),
invalidate: () => container.invalidate(),
handleInput: (data) => {
// Add paging with left/right
if (matchesKey(data, Key.left)) {
// Page up - clamp to 0
currentIndex = Math.max(0, currentIndex - visibleRows);
selectList.setSelectedIndex(currentIndex);
} else if (matchesKey(data, Key.right)) {
// Page down - clamp to last
currentIndex = Math.min(items.length - 1, currentIndex + visibleRows);
selectList.setSelectedIndex(currentIndex);
} else {
selectList.handleInput(data);
}
tui.requestRender();
},
};
});
},
});
}
================================================
FILE: .pi/extensions/files.ts
================================================
/**
* Files Extension
*
* /files command lists all files the model has read/written/edited in the active session branch,
* coalesced by path and sorted newest first. Selecting a file opens it in VS Code.
*/
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { DynamicBorder } from "@mariozechner/pi-coding-agent";
import { Container, Key, matchesKey, type SelectItem, SelectList, Text } from "@mariozechner/pi-tui";
interface FileEntry {
path: string;
operations: Set<"read" | "write" | "edit">;
lastTimestamp: number;
}
type FileToolName = "read" | "write" | "edit";
export default function (pi: ExtensionAPI) {
pi.registerCommand("files", {
description: "Show files read/written/edited in this session",
handler: async (_args, ctx) => {
if (!ctx.hasUI) {
ctx.ui.notify("No UI available", "error");
return;
}
// Get the current branch (path from leaf to root)
const branch = ctx.sessionManager.getBranch();
// First pass: collect tool calls (id -> {path, name}) from assistant messages
const toolCalls = new Map<string, { path: string; name: FileToolName; timestamp: number }>();
for (const entry of branch) {
if (entry.type !== "message") continue;
const msg = entry.message;
if (msg.role === "assistant" && Array.isArray(msg.content)) {
for (const block of msg.content) {
if (block.type === "toolCall") {
const name = block.name;
if (name === "read" || name === "write" || name === "edit") {
const path = block.arguments?.path;
if (path && typeof path === "string") {
toolCalls.set(block.id, { path, name, timestamp: msg.timestamp });
}
}
}
}
}
}
// Second pass: match tool results to get the actual execution timestamp
const fileMap = new Map<string, FileEntry>();
for (const entry of branch) {
if (entry.type !== "message") continue;
const msg = entry.message;
if (msg.role === "toolResult") {
const toolCall = toolCalls.get(msg.toolCallId);
if (!toolCall) continue;
const { path, name } = toolCall;
const timestamp = msg.timestamp;
const existing = fileMap.get(path);
if (existing) {
existing.operations.add(name);
if (timestamp > existing.lastTimestamp) {
existing.lastTimestamp = timestamp;
}
} else {
fileMap.set(path, {
path,
operations: new Set([name]),
lastTimestamp: timestamp,
});
}
}
}
if (fileMap.size === 0) {
ctx.ui.notify("No files read/written/edited in this session", "info");
return;
}
// Sort by most recent first
const files = Array.from(fileMap.values()).sort((a, b) => b.lastTimestamp - a.lastTimestamp);
const WINDOWS_UNSAFE_CMD_CHARS_RE = /[&|<>^%\r\n]/;
const quoteCmdArg = (value: string) => `"${value.replace(/"/g, '""')}"`;
const openWithCode = async (path: string) => {
if (process.platform === "win32") {
if (WINDOWS_UNSAFE_CMD_CHARS_RE.test(path)) {
ctx.ui.notify(
`Refusing to open ${path}: path contains Windows cmd metacharacters (& | < > ^ % or newline).`,
"error",
);
return null;
}
const commandLine = `code -g ${quoteCmdArg(path)}`;
return pi.exec("cmd", ["/d", "/s", "/c", commandLine], { cwd: ctx.cwd });
}
return pi.exec("code", ["-g", path], { cwd: ctx.cwd });
};
const openSelected = async (file: FileEntry): Promise<void> => {
try {
const openResult = await openWithCode(file.path);
if (!openResult) return;
if (openResult.code !== 0) {
const openStderr = openResult.stderr.trim();
ctx.ui.notify(
`Failed to open ${file.path} (exit ${openResult.code})${openStderr ? `: ${openStderr}` : ""}`,
"error",
);
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
ctx.ui.notify(`Failed to open ${file.path}: ${message}`, "error");
}
};
// Show file picker with SelectList
await ctx.ui.custom<void>((tui, theme, _kb, done) => {
const container = new Container();
// Top border
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
// Title
container.addChild(new Text(theme.fg("accent", theme.bold(" Select file to open")), 0, 0));
// Build select items with colored operations
const items: SelectItem[] = files.map((f) => {
const ops: string[] = [];
if (f.operations.has("read")) ops.push(theme.fg("muted", "R"));
if (f.operations.has("write")) ops.push(theme.fg("success", "W"));
if (f.operations.has("edit")) ops.push(theme.fg("warning", "E"));
const opsLabel = ops.join("");
return {
value: f,
label: `${opsLabel} ${f.path}`,
};
});
const visibleRows = Math.min(files.length, 15);
let currentIndex = 0;
const selectList = new SelectList(items, visibleRows, {
selectedPrefix: (t) => theme.fg("accent", t),
selectedText: (t) => t, // Keep existing colors
description: (t) => theme.fg("muted", t),
scrollInfo: (t) => theme.fg("dim", t),
noMatch: (t) => theme.fg("warning", t),
});
selectList.onSelect = (item) => {
void openSelected(item.value as FileEntry);
};
selectList.onCancel = () => done();
selectList.onSelectionChange = (item) => {
currentIndex = items.indexOf(item);
};
container.addChild(selectList);
// Help text
container.addChild(
new Text(theme.fg("dim", " ↑↓ navigate • ←→ page • enter open • esc close"), 0, 0),
);
// Bottom border
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
return {
render: (w) => container.render(w),
invalidate: () => container.invalidate(),
handleInput: (data) => {
// Add paging with left/right
if (matchesKey(data, Key.left)) {
// Page up - clamp to 0
currentIndex = Math.max(0, currentIndex - visibleRows);
selectList.setSelectedIndex(currentIndex);
} else if (matchesKey(data, Key.right)) {
// Page down - clamp to last
currentIndex = Math.min(items.length - 1, currentIndex + visibleRows);
selectList.setSelectedIndex(currentIndex);
} else {
selectList.handleInput(data);
}
tui.requestRender();
},
};
});
},
});
}
================================================
FILE: .pi/extensions/prompt-url-widget.ts
================================================
import { DynamicBorder, type ExtensionAPI, type ExtensionContext } from "@mariozechner/pi-coding-agent";
import { Container, Text } from "@mariozechner/pi-tui";
const PR_PROMPT_PATTERN = /^\s*You are given one or more GitHub PR URLs:\s*(\S+)/im;
const ISSUE_PROMPT_PATTERN = /^\s*Analyze GitHub issue\(s\):\s*(\S+)/im;
type PromptMatch = {
kind: "pr" | "issue";
url: string;
};
type GhMetadata = {
title?: string;
author?: {
login?: string;
name?: string | null;
};
};
function extractPromptMatch(prompt: string): PromptMatch | undefined {
const prMatch = prompt.match(PR_PROMPT_PATTERN);
if (prMatch?.[1]) {
return { kind: "pr", url: prMatch[1].trim() };
}
const issueMatch = prompt.match(ISSUE_PROMPT_PATTERN);
if (issueMatch?.[1]) {
return { kind: "issue", url: issueMatch[1].trim() };
}
return undefined;
}
async function fetchGhMetadata(
pi: ExtensionAPI,
kind: PromptMatch["kind"],
url: string,
): Promise<GhMetadata | undefined> {
const args =
kind === "pr" ? ["pr", "view", url, "--json", "title,author"] : ["issue", "view", url, "--json", "title,author"];
try {
const result = await pi.exec("gh", args);
if (result.code !== 0 || !result.stdout) return undefined;
return JSON.parse(result.stdout) as GhMetadata;
} catch {
return undefined;
}
}
function formatAuthor(author?: GhMetadata["author"]): string | undefined {
if (!author) return undefined;
const name = author.name?.trim();
const login = author.login?.trim();
if (name && login) return `${name} (@${login})`;
if (login) return `@${login}`;
if (name) return name;
return undefined;
}
export default function promptUrlWidgetExtension(pi: ExtensionAPI) {
const setWidget = (ctx: ExtensionContext, match: PromptMatch, title?: string, authorText?: string) => {
ctx.ui.setWidget("prompt-url", (_tui, thm) => {
const titleText = title ? thm.fg("accent", title) : thm.fg("accent", match.url);
const authorLine = authorText ? thm.fg("muted", authorText) : undefined;
const urlLine = thm.fg("dim", match.url);
const lines = [titleText];
if (authorLine) lines.push(authorLine);
lines.push(urlLine);
const container = new Container();
container.addChild(new DynamicBorder((s: string) => thm.fg("muted", s)));
container.addChild(new Text(lines.join("\n"), 1, 0));
return container;
});
};
const applySessionName = (ctx: ExtensionContext, match: PromptMatch, title?: string) => {
const label = match.kind === "pr" ? "PR" : "Issue";
const trimmedTitle = title?.trim();
const fallbackName = `${label}: ${match.url}`;
const desiredName = trimmedTitle ? `${label}: ${trimmedTitle} (${match.url})` : fallbackName;
const currentName = pi.getSessionName()?.trim();
if (!currentName) {
pi.setSessionName(desiredName);
return;
}
if (currentName === match.url || currentName === fallbackName) {
pi.setSessionName(desiredName);
}
};
pi.on("before_agent_start", async (event, ctx) => {
if (!ctx.hasUI) return;
const match = extractPromptMatch(event.prompt);
if (!match) {
return;
}
setWidget(ctx, match);
applySessionName(ctx, match);
void fetchGhMetadata(pi, match.kind, match.url).then((meta) => {
const title = meta?.title?.trim();
const authorText = formatAuthor(meta?.author);
setWidget(ctx, match, title, authorText);
applySessionName(ctx, match, title);
});
});
pi.on("session_switch", async (_event, ctx) => {
rebuildFromSession(ctx);
});
const getUserText = (content: string | { type: string; text?: string }[] | undefined): string => {
if (!content) return "";
if (typeof content === "string") return content;
return (
content
.filter((block): block is { type: "text"; text: string } => block.type === "text")
.map((block) => block.text)
.join("\n") ?? ""
);
};
const rebuildFromSession = (ctx: ExtensionContext) => {
if (!ctx.hasUI) return;
const entries = ctx.sessionManager.getEntries();
const lastMatch = [...entries].reverse().find((entry) => {
if (entry.type !== "message" || entry.message.role !== "user") return false;
const text = getUserText(entry.message.content);
return !!extractPromptMatch(text);
});
const content =
lastMatch?.type === "message" && lastMatch.message.role === "user" ? lastMatch.message.content : undefined;
const text = getUserText(content);
const match = text ? extractPromptMatch(text) : undefined;
if (!match) {
ctx.ui.setWidget("prompt-url", undefined);
return;
}
setWidget(ctx, match);
applySessionName(ctx, match);
void fetchGhMetadata(pi, match.kind, match.url).then((meta) => {
const title = meta?.title?.trim();
const authorText = formatAuthor(meta?.author);
setWidget(ctx, match, title, authorText);
applySessionName(ctx, match, title);
});
};
pi.on("session_start", async (_event, ctx) => {
rebuildFromSession(ctx);
});
}
================================================
FILE: .pi/extensions/redraws.ts
================================================
/**
* Redraws Extension
*
* Exposes /tui to show TUI redraw stats.
*/
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { Text } from "@mariozechner/pi-tui";
export default function (pi: ExtensionAPI) {
pi.registerCommand("tui", {
description: "Show TUI stats",
handler: async (_args, ctx) => {
if (!ctx.hasUI) return;
let redraws = 0;
await ctx.ui.custom<void>((tui, _theme, _keybindings, done) => {
redraws = tui.fullRedraws;
done(undefined);
return new Text("", 0, 0);
});
ctx.ui.notify(`TUI full redraws: ${redraws}`, "info");
},
});
}
================================================
FILE: .pi/extensions/tps.ts
================================================
import type { AssistantMessage } from "@mariozechner/pi-ai";
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
function isAssistantMessage(message: unknown): message is AssistantMessage {
if (!message || typeof message !== "object") return false;
const role = (message as { role?: unknown }).role;
return role === "assistant";
}
export default function (pi: ExtensionAPI) {
let agentStartMs: number | null = null;
pi.on("agent_start", () => {
agentStartMs = Date.now();
});
pi.on("agent_end", (event, ctx) => {
if (!ctx.hasUI) return;
if (agentStartMs === null) return;
const elapsedMs = Date.now() - agentStartMs;
agentStartMs = null;
if (elapsedMs <= 0) return;
let input = 0;
let output = 0;
let cacheRead = 0;
let cacheWrite = 0;
let totalTokens = 0;
for (const message of event.messages) {
if (!isAssistantMessage(message)) continue;
input += message.usage.input || 0;
output += message.usage.output || 0;
cacheRead += message.usage.cacheRead || 0;
cacheWrite += message.usage.cacheWrite || 0;
totalTokens += message.usage.totalTokens || 0;
}
if (output <= 0) return;
const elapsedSeconds = elapsedMs / 1000;
const tokensPerSecond = output / elapsedSeconds;
const message = `TPS ${tokensPerSecond.toFixed(1)} tok/s. out ${output.toLocaleString()}, in ${input.toLocaleString()}, cache r/w ${cacheRead.toLocaleString()}/${cacheWrite.toLocaleString()}, total ${totalTokens.toLocaleString()}, ${elapsedSeconds.toFixed(1)}s`;
ctx.ui.notify(message, "info");
});
}
================================================
FILE: .pi/git/.gitignore
================================================
*
!.gitignore
================================================
FILE: .pi/npm/.gitignore
================================================
*
!.gitignore
================================================
FILE: .pi/prompts/cl.md
================================================
---
description: Audit changelog entries before release
---
Audit changelog entries for all commits since the last release.
## Process
1. **Find the last release tag:**
```bash
git tag --sort=-version:refname | head -1
```
2. **List all commits since that tag:**
```bash
git log <tag>..HEAD --oneline
```
3. **Read each package's [Unreleased] section:**
- packages/ai/CHANGELOG.md
- packages/tui/CHANGELOG.md
- packages/coding-agent/CHANGELOG.md
4. **For each commit, check:**
- Skip: changelog updates, doc-only changes, release housekeeping
- Skip: changes to generated model catalogs (for example `packages/ai/src/models.generated.ts`) unless accompanied by an intentional product-facing change in non-generated source/docs.
- Determine which package(s) the commit affects (use `git show <hash> --stat`)
- Verify a changelog entry exists in the affected package(s)
- For external contributions (PRs), verify format: `Description ([#N](url) by [@user](url))`
5. **Cross-package duplication rule:**
Changes in `ai`, `agent` or `tui` that affect end users should be duplicated to `coding-agent` changelog, since coding-agent is the user-facing package that depends on them.
6. **Add New Features section after changelog fixes:**
- Insert a `### New Features` section at the start of `## [Unreleased]` in `packages/coding-agent/CHANGELOG.md`.
- Propose the top new features to the user for confirmation before writing them.
- Link to relevant docs and sections whenever possible.
7. **Report:**
- List commits with missing entries
- List entries that need cross-package duplication
- Add any missing entries directly
## Changelog Format Reference
Sections (in order):
- `### Breaking Changes` - API changes requiring migration
- `### Added` - New features
- `### Changed` - Changes to existing functionality
- `### Fixed` - Bug fixes
- `### Removed` - Removed features
Attribution:
- Internal: `Fixed foo ([#123](https://github.com/badlogic/pi-mono/issues/123))`
- External: `Added bar ([#456](https://github.com/badlogic/pi-mono/pull/456) by [@user](https://github.com/user))`
================================================
FILE: .pi/prompts/is.md
================================================
---
description: Analyze GitHub issues (bugs or feature requests)
---
Analyze GitHub issue(s): $ARGUMENTS
For each issue:
1. Add the `inprogress` label to the issue via GitHub CLI before analysis starts. If adding the label fails, report that explicitly and continue.
2. Read the issue in full, including all comments and linked issues/PRs.
3. Do not trust analysis written in the issue. Independently verify behavior and derive your own analysis from the code and execution path.
4. **For bugs**:
- Ignore any root cause analysis in the issue (likely wrong)
- Read all related code files in full (no truncation)
- Trace the code path and identify the actual root cause
- Propose a fix
5. **For feature requests**:
- Do not trust implementation proposals in the issue without verification
- Read all related code files in full (no truncation)
- Propose the most concise implementation approach
- List affected files and changes needed
Do NOT implement unless explicitly asked. Analyze and propose only.
================================================
FILE: .pi/prompts/pr.md
================================================
---
description: Review PRs from URLs with structured issue and code analysis
---
You are given one or more GitHub PR URLs: $@
For each PR URL, do the following in order:
1. Add the `inprogress` label to the PR via GitHub CLI before analysis starts. If adding the label fails, report that explicitly and continue.
2. Read the PR page in full. Include description, all comments, all commits, and all changed files.
3. Identify any linked issues referenced in the PR body, comments, commit messages, or cross links. Read each issue in full, including all comments.
4. Analyze the PR diff. Read all relevant code files in full with no truncation from the current main branch and compare against the diff. Do not fetch PR file blobs unless a file is missing on main or the diff context is insufficient. Include related code paths that are not in the diff but are required to validate behavior.
5. Check for a changelog entry in the relevant `packages/*/CHANGELOG.md` files. Report whether an entry exists. If missing, state that a changelog entry is required before merge and that you will add it if the user decides to merge. Follow the changelog format rules in AGENTS.md. Verify:
- Entry uses correct section (`### Breaking Changes`, `### Added`, `### Fixed`, etc.)
- External contributions include PR link and author: `Fixed foo ([#123](https://github.com/badlogic/pi-mono/pull/123) by [@user](https://github.com/user))`
- Breaking changes are in `### Breaking Changes`, not just `### Fixed`
6. Check if packages/coding-agent/README.md, packages/coding-agent/docs/*.md, packages/coding-agent/examples/**/*.md require modification. This is usually the case when existing features have been changed, or new features have been added.
7. Provide a structured review with these sections:
- Good: solid choices or improvements
- Bad: concrete issues, regressions, missing tests, or risks
- Ugly: subtle or high impact problems
8. Add Questions or Assumptions if anything is unclear.
9. Add Change summary and Tests.
Output format per PR:
PR: <url>
Changelog:
- ...
Good:
- ...
Bad:
- ...
Ugly:
- ...
Questions or Assumptions:
- ...
Change summary:
- ...
Tests:
- ...
If no issues are found, say so under Bad and Ugly.
================================================
FILE: AGENTS.md
================================================
# Development Rules
## First Message
If the user did not give you a concrete task in their first message,
read README.md, then ask which module(s) to work on. Based on the answer, read the relevant README.md files in parallel.
- packages/ai/README.md
- packages/tui/README.md
- packages/agent/README.md
- packages/coding-agent/README.md
- packages/mom/README.md
- packages/pods/README.md
- packages/web-ui/README.md
## Code Quality
- No `any` types unless absolutely necessary
- Check node_modules for external API type definitions instead of guessing
- **NEVER use inline imports** - no `await import("./foo.js")`, no `import("pkg").Type` in type positions, no dynamic imports for types. Always use standard top-level imports.
- NEVER remove or downgrade code to fix type errors from outdated dependencies; upgrade the dependency instead
- Always ask before removing functionality or code that appears to be intentional
- Never hardcode key checks with, eg. `matchesKey(keyData, "ctrl+x")`. All keybindings must be configurable. Add default to matching object (`DEFAULT_EDITOR_KEYBINDINGS` or `DEFAULT_APP_KEYBINDINGS`)
## Commands
- After code changes (not documentation changes): `npm run check` (get full output, no tail). Fix all errors, warnings, and infos before committing.
- Note: `npm run check` does not run tests.
- NEVER run: `npm run dev`, `npm run build`, `npm test`
- Only run specific tests if user instructs: `npx tsx ../../node_modules/vitest/dist/cli.js --run test/specific.test.ts`
- Run tests from the package root, not the repo root.
- If you create or modify a test file, you MUST run that test file and iterate until it passes.
- When writing tests, run them, identify issues in either the test or implementation, and iterate until fixed.
- NEVER commit unless user asks
## GitHub Issues
When reading issues:
- Always read all comments on the issue
- Use this command to get everything in one call:
```bash
gh issue view <number> --json title,body,comments,labels,state
```
## OSS Weekend
- If the user says `enable OSS weekend mode until X`, run `node scripts/oss-weekend.mjs --mode=close --end-date=YYYY-MM-DD --git` with the requested end date
- If the user says `end OSS weekend mode`, run `node scripts/oss-weekend.mjs --mode=open --git`
- The script updates `README.md`, `packages/coding-agent/README.md`, and `.github/oss-weekend.json`
- With `--git`, the script stages only those OSS weekend files, commits them, and pushes them
- During OSS weekend, `.github/workflows/oss-weekend-issues.yml` auto-closes new issues from non-maintainers, and `.github/workflows/pr-gate.yml` auto-closes PRs from approved non-maintainers with the weekend message
When creating issues:
- Add `pkg:*` labels to indicate which package(s) the issue affects
- Available labels: `pkg:agent`, `pkg:ai`, `pkg:coding-agent`, `pkg:mom`, `pkg:pods`, `pkg:tui`, `pkg:web-ui`
- If an issue spans multiple packages, add all relevant labels
When posting issue/PR comments:
- Write the full comment to a temp file and use `gh issue comment --body-file` or `gh pr comment --body-file`
- Never pass multi-line markdown directly via `--body` in shell commands
- Preview the exact comment text before posting
- Post exactly one final comment unless the user explicitly asks for multiple comments
- If a comment is malformed, delete it immediately, then post one corrected comment
- Keep comments concise, technical, and in the user's tone
When closing issues via commit:
- Include `fixes #<number>` or `closes #<number>` in the commit message
- This automatically closes the issue when the commit is merged
## PR Workflow
- Analyze PRs without pulling locally first
- If the user approves: create a feature branch, pull PR, rebase on main, apply adjustments, commit, merge into main, push, close PR, and leave a comment in the user's tone
- You never open PRs yourself. We work in feature branches until everything is according to the user's requirements, then merge into main, and push.
## Tools
- GitHub CLI for issues/PRs
- Add package labels to issues/PRs: pkg:agent, pkg:ai, pkg:coding-agent, pkg:mom, pkg:pods, pkg:tui, pkg:web-ui
## Testing pi Interactive Mode with tmux
To test pi's TUI in a controlled terminal environment:
```bash
# Create tmux session with specific dimensions
tmux new-session -d -s pi-test -x 80 -y 24
# Start pi from source
tmux send-keys -t pi-test "cd /Users/badlogic/workspaces/pi-mono && ./pi-test.sh" Enter
# Wait for startup, then capture output
sleep 3 && tmux capture-pane -t pi-test -p
# Send input
tmux send-keys -t pi-test "your prompt here" Enter
# Send special keys
tmux send-keys -t pi-test Escape
tmux send-keys -t pi-test C-o # ctrl+o
# Cleanup
tmux kill-session -t pi-test
```
## Style
- Keep answers short and concise
- No emojis in commits, issues, PR comments, or code
- No fluff or cheerful filler text
- Technical prose only, be kind but direct (e.g., "Thanks @user" not "Thanks so much @user!")
## Changelog
Location: `packages/*/CHANGELOG.md` (each package has its own)
### Format
Use these sections under `## [Unreleased]`:
- `### Breaking Changes` - API changes requiring migration
- `### Added` - New features
- `### Changed` - Changes to existing functionality
- `### Fixed` - Bug fixes
- `### Removed` - Removed features
### Rules
- Before adding entries, read the full `[Unreleased]` section to see which subsections already exist
- New entries ALWAYS go under `## [Unreleased]` section
- Append to existing subsections (e.g., `### Fixed`), do not create duplicates
- NEVER modify already-released version sections (e.g., `## [0.12.2]`)
- Each version section is immutable once released
### Attribution
- **Internal changes (from issues)**: `Fixed foo bar ([#123](https://github.com/badlogic/pi-mono/issues/123))`
- **External contributions**: `Added feature X ([#456](https://github.com/badlogic/pi-mono/pull/456) by [@username](https://github.com/username))`
## Adding a New LLM Provider (packages/ai)
Adding a new provider requires changes across multiple files:
### 1. Core Types (`packages/ai/src/types.ts`)
- Add API identifier to `Api` type union (e.g., `"bedrock-converse-stream"`)
- Create options interface extending `StreamOptions`
- Add mapping to `ApiOptionsMap`
- Add provider name to `KnownProvider` type union
### 2. Provider Implementation (`packages/ai/src/providers/`)
Create provider file exporting:
- `stream<Provider>()` function returning `AssistantMessageEventStream`
- `streamSimple<Provider>()` for `SimpleStreamOptions` mapping
- Provider-specific options interface
- Message/tool conversion functions
- Response parsing emitting standardized events (`text`, `tool_call`, `thinking`, `usage`, `stop`)
### 3. Provider Exports and Lazy Registration
- Add a package subpath export in `packages/ai/package.json` pointing at `./dist/providers/<provider>.js`
- Add `export type` re-exports in `packages/ai/src/index.ts` for provider option types that should remain available from the root entry
- Register the provider in `packages/ai/src/providers/register-builtins.ts` via lazy loader wrappers, do not statically import provider implementation modules there
- Add credential detection in `packages/ai/src/env-api-keys.ts`
### 4. Model Generation (`packages/ai/scripts/generate-models.ts`)
- Add logic to fetch/parse models from provider source
- Map to standardized `Model` interface
### 5. Tests (`packages/ai/test/`)
Add provider to: `stream.test.ts`, `tokens.test.ts`, `abort.test.ts`, `empty.test.ts`, `context-overflow.test.ts`, `image-limits.test.ts`, `unicode-surrogate.test.ts`, `tool-call-without-result.test.ts`, `image-tool-result.test.ts`, `total-tokens.test.ts`, `cross-provider-handoff.test.ts`.
For `cross-provider-handoff.test.ts`, add at least one provider/model pair. If the provider exposes multiple model families (for example GPT and Claude), add at least one pair per family.
For non-standard auth, create utility (e.g., `bedrock-utils.ts`) with credential detection.
### 6. Coding Agent (`packages/coding-agent/`)
- `src/core/model-resolver.ts`: Add default model ID to `DEFAULT_MODELS`
- `src/cli/args.ts`: Add env var documentation
- `README.md`: Add provider setup instructions
### 7. Documentation
- `packages/ai/README.md`: Add to providers table, document options/auth, add env vars
- `packages/ai/CHANGELOG.md`: Add entry under `## [Unreleased]`
## Releasing
**Lockstep versioning**: All packages always share the same version number. Every release updates all packages together.
**Version semantics** (no major releases):
- `patch`: Bug fixes and new features
- `minor`: API breaking changes
### Steps
1. **Update CHANGELOGs**: Ensure all changes since last release are documented in the `[Unreleased]` section of each affected package's CHANGELOG.md
2. **Run release script**:
```bash
npm run release:patch # Fixes and additions
npm run release:minor # API breaking changes
```
The script handles: version bump, CHANGELOG finalization, commit, tag, publish, and adding new `[Unreleased]` sections.
## **CRITICAL** Tool Usage Rules **CRITICAL**
- NEVER use sed/cat to read a file or a range of a file. Always use the read tool (use offset + limit for ranged reads).
- You MUST read every file you modify in full before editing.
## **CRITICAL** Git Rules for Parallel Agents **CRITICAL**
Multiple agents may work on different files in the same worktree simultaneously. You MUST follow these rules:
### Committing
- **ONLY commit files YOU changed in THIS session**
- ALWAYS include `fixes #<number>` or `closes #<number>` in the commit message when there is a related issue or PR
- NEVER use `git add -A` or `git add .` - these sweep up changes from other agents
- ALWAYS use `git add <specific-file-paths>` listing only files you modified
- Before committing, run `git status` and verify you are only staging YOUR files
- Track which files you created/modified/deleted during the session
### Forbidden Git Operations
These commands can destroy other agents' work:
- `git reset --hard` - destroys uncommitted changes
- `git checkout .` - destroys uncommitted changes
- `git clean -fd` - deletes untracked files
- `git stash` - stashes ALL changes including other agents' work
- `git add -A` / `git add .` - stages other agents' uncommitted work
- `git commit --no-verify` - bypasses required checks and is never allowed
### Safe Workflow
```bash
# 1. Check status first
git status
# 2. Add ONLY your specific files
git add packages/ai/src/providers/transform-messages.ts
git add packages/ai/CHANGELOG.md
# 3. Commit
git commit -m "fix(ai): description"
# 4. Push (pull --rebase if needed, but NEVER reset/checkout)
git pull --rebase && git push
```
### If Rebase Conflicts Occur
- Resolve conflicts in YOUR files only
- If conflict is in a file you didn't modify, abort and ask the user
- NEVER force push
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to pi
Thanks for wanting to contribute! This guide exists to save both of us time.
## The One Rule
**You must understand your code.** If you can't explain what your changes do and how they interact with the rest of the system, your PR will be closed.
Using AI to write code is fine. You can gain understanding by interrogating an agent with access to the codebase until you grasp all edge cases and effects of your changes. What's not fine is submitting agent-generated slop without that understanding.
If you use an agent, run it from the `pi-mono` root directory so it picks up `AGENTS.md` automatically. Your agent must follow the rules and guidelines in that file.
## First-Time Contributors
We use an approval gate for new contributors:
1. Open an issue describing what you want to change and why
2. Keep it concise (if it doesn't fit on one screen, it's too long)
3. Write in your own voice, at least for the intro
4. A maintainer will comment `lgtm` if approved
5. Once approved, you can submit PRs
This exists because AI makes it trivial to generate plausible-looking but low-quality contributions. The issue step lets us filter early.
## Before Submitting a PR
```bash
npm run check # must pass with no errors
./test.sh # must pass
```
Do not edit `CHANGELOG.md`. Changelog entries are added by maintainers.
If you're adding a new provider to `packages/ai`, see `AGENTS.md` for required tests.
## Philosophy
pi's core is minimal. If your feature doesn't belong in the core, it should be an extension. PRs that bloat the core will likely be rejected.
## Questions?
Open an issue or ask on [Discord](https://discord.com/invite/nKXTsAcmbT).
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2025 Mario Zechner
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
<p align="center">
<a href="https://shittycodingagent.ai">
<img src="https://shittycodingagent.ai/logo.svg" alt="pi logo" width="128">
</a>
</p>
<p align="center">
<a href="https://discord.com/invite/3cU7Bz4UPx"><img alt="Discord" src="https://img.shields.io/badge/discord-community-5865F2?style=flat-square&logo=discord&logoColor=white" /></a>
<a href="https://github.com/badlogic/pi-mono/actions/workflows/ci.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/badlogic/pi-mono/ci.yml?style=flat-square&branch=main" /></a>
</p>
<p align="center">
<a href="https://pi.dev">pi.dev</a> domain graciously donated by
<br /><br />
<a href="https://exe.dev"><img src="packages/coding-agent/docs/images/exy.png" alt="Exy mascot" width="48" /><br />exe.dev</a>
</p>
# Pi Monorepo
> **Looking for the pi coding agent?** See **[packages/coding-agent](packages/coding-agent)** for installation and usage.
Tools for building AI agents and managing LLM deployments.
## Packages
| Package | Description |
|---------|-------------|
| **[@mariozechner/pi-ai](packages/ai)** | Unified multi-provider LLM API (OpenAI, Anthropic, Google, etc.) |
| **[@mariozechner/pi-agent-core](packages/agent)** | Agent runtime with tool calling and state management |
| **[@mariozechner/pi-coding-agent](packages/coding-agent)** | Interactive coding agent CLI |
| **[@mariozechner/pi-mom](packages/mom)** | Slack bot that delegates messages to the pi coding agent |
| **[@mariozechner/pi-tui](packages/tui)** | Terminal UI library with differential rendering |
| **[@mariozechner/pi-web-ui](packages/web-ui)** | Web components for AI chat interfaces |
| **[@mariozechner/pi-pods](packages/pods)** | CLI for managing vLLM deployments on GPU pods |
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines and [AGENTS.md](AGENTS.md) for project-specific rules (for both humans and agents).
## Development
```bash
npm install # Install all dependencies
npm run build # Build all packages
npm run check # Lint, format, and type check
./test.sh # Run tests (skips LLM-dependent tests without API keys)
./pi-test.sh # Run pi from sources (must be run from repo root)
```
> **Note:** `npm run check` requires `npm run build` to be run first. The web-ui package uses `tsc` which needs compiled `.d.ts` files from dependencies.
## License
MIT
================================================
FILE: biome.json
================================================
{
"$schema": "https://biomejs.dev/schemas/2.3.5/schema.json",
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"style": {
"noNonNullAssertion": "off",
"useConst": "error",
"useNodejsImportProtocol": "off"
},
"suspicious": {
"noExplicitAny": "off",
"noControlCharactersInRegex": "off",
"noEmptyInterface": "off"
}
}
},
"formatter": {
"enabled": true,
"formatWithErrors": false,
"indentStyle": "tab",
"indentWidth": 3,
"lineWidth": 120
},
"files": {
"includes": [
"packages/*/src/**/*.ts",
"packages/*/test/**/*.ts",
"packages/coding-agent/examples/**/*.ts",
"packages/web-ui/src/**/*.ts",
"packages/web-ui/example/**/*.ts",
"!**/node_modules/**/*",
"!**/test-sessions.ts",
"!**/models.generated.ts",
"!packages/web-ui/src/app.css",
"!packages/mom/data/**/*",
"!!**/node_modules"
]
}
}
================================================
FILE: package.json
================================================
{
"name": "pi-monorepo",
"private": true,
"type": "module",
"workspaces": [
"packages/*",
"packages/web-ui/example",
"packages/coding-agent/examples/extensions/with-deps",
"packages/coding-agent/examples/extensions/custom-provider-anthropic",
"packages/coding-agent/examples/extensions/custom-provider-gitlab-duo",
"packages/coding-agent/examples/extensions/custom-provider-qwen-cli"
],
"scripts": {
"clean": "npm run clean --workspaces",
"build": "cd packages/tui && npm run build && cd ../ai && npm run build && cd ../agent && npm run build && cd ../coding-agent && npm run build && cd ../mom && npm run build && cd ../web-ui && npm run build && cd ../pods && npm run build",
"dev": "concurrently --names \"ai,agent,coding-agent,mom,web-ui,tui\" --prefix-colors \"cyan,yellow,red,white,green,magenta\" \"cd packages/ai && npm run dev\" \"cd packages/agent && npm run dev\" \"cd packages/coding-agent && npm run dev\" \"cd packages/mom && npm run dev\" \"cd packages/web-ui && npm run dev\" \"cd packages/tui && npm run dev\"",
"dev:tsc": "concurrently --names \"ai,web-ui\" --prefix-colors \"cyan,green\" \"cd packages/ai && npm run dev:tsc\" \"cd packages/web-ui && npm run dev:tsc\"",
"check": "biome check --write --error-on-warnings . && tsgo --noEmit && npm run check:browser-smoke && cd packages/web-ui && npm run check",
"check:browser-smoke": "node scripts/check-browser-smoke.mjs",
"test": "npm run test --workspaces --if-present",
"version:patch": "npm version patch -ws --no-git-tag-version && node scripts/sync-versions.js && shx rm -rf node_modules packages/*/node_modules package-lock.json && npm install",
"version:minor": "npm version minor -ws --no-git-tag-version && node scripts/sync-versions.js && shx rm -rf node_modules packages/*/node_modules package-lock.json && npm install",
"version:major": "npm version major -ws --no-git-tag-version && node scripts/sync-versions.js && shx rm -rf node_modules packages/*/node_modules package-lock.json && npm install",
"version:set": "npm version -ws",
"prepublishOnly": "npm run clean && npm run build && npm run check",
"publish": "npm run prepublishOnly && npm publish -ws --access public",
"publish:dry": "npm run prepublishOnly && npm publish -ws --access public --dry-run",
"release:patch": "node scripts/release.mjs patch",
"release:minor": "node scripts/release.mjs minor",
"release:major": "node scripts/release.mjs major",
"prepare": "husky"
},
"devDependencies": {
"@biomejs/biome": "2.3.5",
"@types/node": "^22.10.5",
"@typescript/native-preview": "7.0.0-dev.20260120.1",
"concurrently": "^9.2.1",
"husky": "^9.1.7",
"tsx": "^4.20.3",
"typescript": "^5.9.2",
"shx": "^0.4.0"
},
"engines": {
"node": ">=20.0.0"
},
"version": "0.0.3",
"dependencies": {
"@mariozechner/jiti": "^2.6.5",
"@mariozechner/pi-coding-agent": "^0.30.2",
"get-east-asian-width": "^1.4.0"
},
"overrides": {
"rimraf": "6.1.2",
"fast-xml-parser": "5.3.8",
"gaxios": {
"rimraf": "6.1.2"
}
}
}
================================================
FILE: packages/agent/CHANGELOG.md
================================================
# Changelog
## [Unreleased]
## [0.61.0] - 2026-03-20
## [0.60.0] - 2026-03-18
## [0.59.0] - 2026-03-17
## [0.58.4] - 2026-03-16
### Fixed
- Fixed steering messages to wait until the current assistant message's tool-call batch fully finishes instead of skipping pending tool calls.
## [0.58.3] - 2026-03-15
## [0.58.2] - 2026-03-15
## [0.58.1] - 2026-03-14
## [0.58.0] - 2026-03-14
### Added
- Added `beforeToolCall` and `afterToolCall` hooks to `AgentOptions` and `AgentLoopConfig` for preflight blocking and post-execution tool result mutation.
### Changed
- Added configurable tool execution mode to `Agent` and `agentLoop` via `toolExecution: "parallel" | "sequential"`, with `parallel` as the default. Parallel mode preflights tool calls sequentially, executes allowed tools concurrently, and emits final tool results in assistant source order.
## [0.57.1] - 2026-03-07
## [0.57.0] - 2026-03-07
## [0.56.3] - 2026-03-06
## [0.56.2] - 2026-03-05
## [0.56.1] - 2026-03-05
## [0.56.0] - 2026-03-04
## [0.55.4] - 2026-03-02
## [0.55.3] - 2026-02-27
## [0.55.2] - 2026-02-27
## [0.55.1] - 2026-02-26
## [0.55.0] - 2026-02-24
## [0.54.2] - 2026-02-23
## [0.54.1] - 2026-02-22
## [0.54.0] - 2026-02-19
## [0.53.1] - 2026-02-19
## [0.53.0] - 2026-02-17
## [0.52.12] - 2026-02-13
### Added
- Added `transport` to `AgentOptions` and `AgentLoopConfig` forwarding, allowing stream transport preference (`"sse"`, `"websocket"`, `"auto"`) to flow into provider calls.
## [0.52.11] - 2026-02-13
## [0.52.10] - 2026-02-12
## [0.52.9] - 2026-02-08
## [0.52.8] - 2026-02-07
## [0.52.7] - 2026-02-06
### Fixed
- Fixed `continue()` to resume queued steering/follow-up messages when context currently ends in an assistant message, and preserved one-at-a-time steering ordering during assistant-tail resumes ([#1312](https://github.com/badlogic/pi-mono/pull/1312) by [@ferologics](https://github.com/ferologics))
## [0.52.6] - 2026-02-05
## [0.52.5] - 2026-02-05
## [0.52.4] - 2026-02-05
## [0.52.3] - 2026-02-05
## [0.52.2] - 2026-02-05
## [0.52.1] - 2026-02-05
## [0.52.0] - 2026-02-05
## [0.51.6] - 2026-02-04
## [0.51.5] - 2026-02-04
## [0.51.4] - 2026-02-03
## [0.51.3] - 2026-02-03
## [0.51.2] - 2026-02-03
## [0.51.1] - 2026-02-02
## [0.51.0] - 2026-02-01
## [0.50.9] - 2026-02-01
## [0.50.8] - 2026-02-01
### Added
- Added `maxRetryDelayMs` option to `AgentOptions` to cap server-requested retry delays. Passed through to the underlying stream function. ([#1123](https://github.com/badlogic/pi-mono/issues/1123))
## [0.50.7] - 2026-01-31
## [0.50.6] - 2026-01-30
## [0.50.5] - 2026-01-30
## [0.50.3] - 2026-01-29
## [0.50.2] - 2026-01-29
## [0.50.1] - 2026-01-26
## [0.50.0] - 2026-01-26
## [0.49.3] - 2026-01-22
## [0.49.2] - 2026-01-19
## [0.49.1] - 2026-01-18
## [0.49.0] - 2026-01-17
## [0.48.0] - 2026-01-16
## [0.47.0] - 2026-01-16
## [0.46.0] - 2026-01-15
## [0.45.7] - 2026-01-13
## [0.45.6] - 2026-01-13
## [0.45.5] - 2026-01-13
## [0.45.4] - 2026-01-13
## [0.45.3] - 2026-01-13
## [0.45.2] - 2026-01-13
## [0.45.1] - 2026-01-13
## [0.45.0] - 2026-01-13
## [0.44.0] - 2026-01-12
## [0.43.0] - 2026-01-11
## [0.42.5] - 2026-01-11
## [0.42.4] - 2026-01-10
## [0.42.3] - 2026-01-10
## [0.42.2] - 2026-01-10
## [0.42.1] - 2026-01-09
## [0.42.0] - 2026-01-09
## [0.41.0] - 2026-01-09
## [0.40.1] - 2026-01-09
## [0.40.0] - 2026-01-08
## [0.39.1] - 2026-01-08
## [0.39.0] - 2026-01-08
## [0.38.0] - 2026-01-08
### Added
- `thinkingBudgets` option on `Agent` and `AgentOptions` to customize token budgets per thinking level ([#529](https://github.com/badlogic/pi-mono/pull/529) by [@melihmucuk](https://github.com/melihmucuk))
## [0.37.8] - 2026-01-07
## [0.37.7] - 2026-01-07
## [0.37.6] - 2026-01-06
## [0.37.5] - 2026-01-06
## [0.37.4] - 2026-01-06
## [0.37.3] - 2026-01-06
### Added
- `sessionId` option on `Agent` to forward session identifiers to LLM providers for session-based caching.
## [0.37.2] - 2026-01-05
## [0.37.1] - 2026-01-05
## [0.37.0] - 2026-01-05
### Fixed
- `minimal` thinking level now maps to `minimal` reasoning effort instead of being treated as `low`.
## [0.36.0] - 2026-01-05
## [0.35.0] - 2026-01-05
## [0.34.2] - 2026-01-04
## [0.34.1] - 2026-01-04
## [0.34.0] - 2026-01-04
## [0.33.0] - 2026-01-04
## [0.32.3] - 2026-01-03
## [0.32.2] - 2026-01-03
## [0.32.1] - 2026-01-03
## [0.32.0] - 2026-01-03
### Breaking Changes
- **Queue API replaced with steer/followUp**: The `queueMessage()` method has been split into two methods with different delivery semantics ([#403](https://github.com/badlogic/pi-mono/issues/403)):
- `steer(msg)`: Interrupts the agent mid-run. Delivered after current tool execution, skips remaining tools.
- `followUp(msg)`: Waits until the agent finishes. Delivered only when there are no more tool calls or steering messages.
- **Queue mode renamed**: `queueMode` option renamed to `steeringMode`. Added new `followUpMode` option. Both control whether messages are delivered one-at-a-time or all at once.
- **AgentLoopConfig callbacks renamed**: `getQueuedMessages` split into `getSteeringMessages` and `getFollowUpMessages`.
- **Agent methods renamed**:
- `queueMessage()` → `steer()` and `followUp()`
- `clearMessageQueue()` → `clearSteeringQueue()`, `clearFollowUpQueue()`, `clearAllQueues()`
- `setQueueMode()`/`getQueueMode()` → `setSteeringMode()`/`getSteeringMode()` and `setFollowUpMode()`/`getFollowUpMode()`
### Fixed
- `prompt()` and `continue()` now throw if called while the agent is already streaming, preventing race conditions and corrupted state. Use `steer()` or `followUp()` to queue messages during streaming, or `await` the previous call.
## [0.31.1] - 2026-01-02
## [0.31.0] - 2026-01-02
### Breaking Changes
- **Transport abstraction removed**: `ProviderTransport`, `AppTransport`, and `AgentTransport` interface have been removed. Use the `streamFn` option directly for custom streaming implementations.
- **Agent options renamed**:
- `transport` → removed (use `streamFn` instead)
- `messageTransformer` → `convertToLlm`
- `preprocessor` → `transformContext`
- **`AppMessage` renamed to `AgentMessage`**: All references to `AppMessage` have been renamed to `AgentMessage` for consistency.
- **`CustomMessages` renamed to `CustomAgentMessages`**: The declaration merging interface has been renamed.
- **`UserMessageWithAttachments` and `Attachment` types removed**: Attachment handling is now the responsibility of the `convertToLlm` function.
- **Agent loop moved from `@mariozechner/pi-ai`**: The `agentLoop`, `agentLoopContinue`, and related types have moved to this package. Import from `@mariozechner/pi-agent-core` instead.
### Added
- `streamFn` option on `Agent` for custom stream implementations. Default uses `streamSimple` from pi-ai.
- `streamProxy()` utility function for browser apps that need to proxy LLM calls through a backend server. Replaces the removed `AppTransport`.
- `getApiKey` option for dynamic API key resolution (useful for expiring OAuth tokens like GitHub Copilot).
- `agentLoop()` and `agentLoopContinue()` low-level functions for running the agent loop without the `Agent` class wrapper.
- New exported types: `AgentLoopConfig`, `AgentContext`, `AgentTool`, `AgentToolResult`, `AgentToolUpdateCallback`, `StreamFn`.
### Changed
- `Agent` constructor now has all options optional (empty options use defaults).
- `queueMessage()` is now synchronous (no longer returns a Promise).
================================================
FILE: packages/agent/README.md
================================================
# @mariozechner/pi-agent-core
Stateful agent with tool execution and event streaming. Built on `@mariozechner/pi-ai`.
## Installation
```bash
npm install @mariozechner/pi-agent-core
```
## Quick Start
```typescript
import { Agent } from "@mariozechner/pi-agent-core";
import { getModel } from "@mariozechner/pi-ai";
const agent = new Agent({
initialState: {
systemPrompt: "You are a helpful assistant.",
model: getModel("anthropic", "claude-sonnet-4-20250514"),
},
});
agent.subscribe((event) => {
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
// Stream just the new text chunk
process.stdout.write(event.assistantMessageEvent.delta);
}
});
await agent.prompt("Hello!");
```
## Core Concepts
### AgentMessage vs LLM Message
The agent works with `AgentMessage`, a flexible type that can include:
- Standard LLM messages (`user`, `assistant`, `toolResult`)
- Custom app-specific message types via declaration merging
LLMs only understand `user`, `assistant`, and `toolResult`. The `convertToLlm` function bridges this gap by filtering and transforming messages before each LLM call.
### Message Flow
```
AgentMessage[] → transformContext() → AgentMessage[] → convertToLlm() → Message[] → LLM
(optional) (required)
```
1. **transformContext**: Prune old messages, inject external context
2. **convertToLlm**: Filter out UI-only messages, convert custom types to LLM format
## Event Flow
The agent emits events for UI updates. Understanding the event sequence helps build responsive interfaces.
### prompt() Event Sequence
When you call `prompt("Hello")`:
```
prompt("Hello")
├─ agent_start
├─ turn_start
├─ message_start { message: userMessage } // Your prompt
├─ message_end { message: userMessage }
├─ message_start { message: assistantMessage } // LLM starts responding
├─ message_update { message: partial... } // Streaming chunks
├─ message_update { message: partial... }
├─ message_end { message: assistantMessage } // Complete response
├─ turn_end { message, toolResults: [] }
└─ agent_end { messages: [...] }
```
### With Tool Calls
If the assistant calls tools, the loop continues:
```
prompt("Read config.json")
├─ agent_start
├─ turn_start
├─ message_start/end { userMessage }
├─ message_start { assistantMessage with toolCall }
├─ message_update...
├─ message_end { assistantMessage }
├─ tool_execution_start { toolCallId, toolName, args }
├─ tool_execution_update { partialResult } // If tool streams
├─ tool_execution_end { toolCallId, result }
├─ message_start/end { toolResultMessage }
├─ turn_end { message, toolResults: [toolResult] }
│
├─ turn_start // Next turn
├─ message_start { assistantMessage } // LLM responds to tool result
├─ message_update...
├─ message_end
├─ turn_end
└─ agent_end
```
Tool execution mode is configurable:
- `parallel` (default): preflight tool calls sequentially, execute allowed tools concurrently, emit final `tool_execution_end` and `toolResult` messages in assistant source order
- `sequential`: execute tool calls one by one, matching the historical behavior
The `beforeToolCall` hook runs after `tool_execution_start` and validated argument parsing. It can block execution. The `afterToolCall` hook runs after tool execution finishes and before `tool_execution_end` and final tool result message events are emitted.
When you use the `Agent` class, assistant `message_end` processing is treated as a barrier before tool preflight begins. That means `beforeToolCall` sees agent state that already includes the assistant message that requested the tool call.
### continue() Event Sequence
`continue()` resumes from existing context without adding a new message. Use it for retries after errors.
```typescript
// After an error, retry from current state
await agent.continue();
```
The last message in context must be `user` or `toolResult` (not `assistant`).
### Event Types
| Event | Description |
|-------|-------------|
| `agent_start` | Agent begins processing |
| `agent_end` | Agent completes with all new messages |
| `turn_start` | New turn begins (one LLM call + tool executions) |
| `turn_end` | Turn completes with assistant message and tool results |
| `message_start` | Any message begins (user, assistant, toolResult) |
| `message_update` | **Assistant only.** Includes `assistantMessageEvent` with delta |
| `message_end` | Message completes |
| `tool_execution_start` | Tool begins |
| `tool_execution_update` | Tool streams progress |
| `tool_execution_end` | Tool completes |
## Agent Options
```typescript
const agent = new Agent({
// Initial state
initialState: {
systemPrompt: string,
model: Model<any>,
thinkingLevel: "off" | "minimal" | "low" | "medium" | "high" | "xhigh",
tools: AgentTool<any>[],
messages: AgentMessage[],
},
// Convert AgentMessage[] to LLM Message[] (required for custom message types)
convertToLlm: (messages) => messages.filter(...),
// Transform context before convertToLlm (for pruning, compaction)
transformContext: async (messages, signal) => pruneOldMessages(messages),
// Steering mode: "one-at-a-time" (default) or "all"
steeringMode: "one-at-a-time",
// Follow-up mode: "one-at-a-time" (default) or "all"
followUpMode: "one-at-a-time",
// Custom stream function (for proxy backends)
streamFn: streamProxy,
// Session ID for provider caching
sessionId: "session-123",
// Dynamic API key resolution (for expiring OAuth tokens)
getApiKey: async (provider) => refreshToken(),
// Tool execution mode: "parallel" (default) or "sequential"
toolExecution: "parallel",
// Preflight each tool call after args are validated. Can block execution.
beforeToolCall: async ({ toolCall, args, context }) => {
if (toolCall.name === "bash") {
return { block: true, reason: "bash is disabled" };
}
},
// Postprocess each tool result before final tool events are emitted.
afterToolCall: async ({ toolCall, result, isError, context }) => {
if (!isError) {
return { details: { ...result.details, audited: true } };
}
},
// Custom thinking budgets for token-based providers
thinkingBudgets: {
minimal: 128,
low: 512,
medium: 1024,
high: 2048,
},
});
```
## Agent State
```typescript
interface AgentState {
systemPrompt: string;
model: Model<any>;
thinkingLevel: ThinkingLevel;
tools: AgentTool<any>[];
messages: AgentMessage[];
isStreaming: boolean;
streamMessage: AgentMessage | null; // Current partial during streaming
pendingToolCalls: Set<string>;
error?: string;
}
```
Access via `agent.state`. During streaming, `streamMessage` contains the partial assistant message.
## Methods
### Prompting
```typescript
// Text prompt
await agent.prompt("Hello");
// With images
await agent.prompt("What's in this image?", [
{ type: "image", data: base64Data, mimeType: "image/jpeg" }
]);
// AgentMessage directly
await agent.prompt({ role: "user", content: "Hello", timestamp: Date.now() });
// Continue from current context (last message must be user or toolResult)
await agent.continue();
```
### State Management
```typescript
agent.setSystemPrompt("New prompt");
agent.setModel(getModel("openai", "gpt-4o"));
agent.setThinkingLevel("medium");
agent.setTools([myTool]);
agent.setToolExecution("sequential");
agent.setBeforeToolCall(async ({ toolCall }) => undefined);
agent.setAfterToolCall(async ({ toolCall, result }) => undefined);
agent.replaceMessages(newMessages);
agent.appendMessage(message);
agent.clearMessages();
agent.reset(); // Clear everything
```
### Session and Thinking Budgets
```typescript
agent.sessionId = "session-123";
agent.thinkingBudgets = {
minimal: 128,
low: 512,
medium: 1024,
high: 2048,
};
```
### Control
```typescript
agent.abort(); // Cancel current operation
await agent.waitForIdle(); // Wait for completion
```
### Events
```typescript
const unsubscribe = agent.subscribe((event) => {
console.log(event.type);
});
unsubscribe();
```
## Steering and Follow-up
Steering messages let you interrupt the agent while tools are running. Follow-up messages let you queue work after the agent would otherwise stop.
```typescript
agent.setSteeringMode("one-at-a-time");
agent.setFollowUpMode("one-at-a-time");
// While agent is running tools
agent.steer({
role: "user",
content: "Stop! Do this instead.",
timestamp: Date.now(),
});
// After the agent finishes its current work
agent.followUp({
role: "user",
content: "Also summarize the result.",
timestamp: Date.now(),
});
const steeringMode = agent.getSteeringMode();
const followUpMode = agent.getFollowUpMode();
agent.clearSteeringQueue();
agent.clearFollowUpQueue();
agent.clearAllQueues();
```
Use clearSteeringQueue, clearFollowUpQueue, or clearAllQueues to drop queued messages.
When steering messages are detected after a turn completes:
1. All tool calls from the current assistant message have already finished
2. Steering messages are injected
3. The LLM responds on the next turn
Follow-up messages are checked only when there are no more tool calls and no steering messages. If any are queued, they are injected and another turn runs.
## Custom Message Types
Extend `AgentMessage` via declaration merging:
```typescript
declare module "@mariozechner/pi-agent-core" {
interface CustomAgentMessages {
notification: { role: "notification"; text: string; timestamp: number };
}
}
// Now valid
const msg: AgentMessage = { role: "notification", text: "Info", timestamp: Date.now() };
```
Handle custom types in `convertToLlm`:
```typescript
const agent = new Agent({
convertToLlm: (messages) => messages.flatMap(m => {
if (m.role === "notification") return []; // Filter out
return [m];
}),
});
```
## Tools
Define tools using `AgentTool`:
```typescript
import { Type } from "@sinclair/typebox";
const readFileTool: AgentTool = {
name: "read_file",
label: "Read File", // For UI display
description: "Read a file's contents",
parameters: Type.Object({
path: Type.String({ description: "File path" }),
}),
execute: async (toolCallId, params, signal, onUpdate) => {
const content = await fs.readFile(params.path, "utf-8");
// Optional: stream progress
onUpdate?.({ content: [{ type: "text", text: "Reading..." }], details: {} });
return {
content: [{ type: "text", text: content }],
details: { path: params.path, size: content.length },
};
},
};
agent.setTools([readFileTool]);
```
### Error Handling
**Throw an error** when a tool fails. Do not return error messages as content.
```typescript
execute: async (toolCallId, params, signal, onUpdate) => {
if (!fs.existsSync(params.path)) {
throw new Error(`File not found: ${params.path}`);
}
// Return content only on success
return { content: [{ type: "text", text: "..." }] };
}
```
Thrown errors are caught by the agent and reported to the LLM as tool errors with `isError: true`.
## Proxy Usage
For browser apps that proxy through a backend:
```typescript
import { Agent, streamProxy } from "@mariozechner/pi-agent-core";
const agent = new Agent({
streamFn: (model, context, options) =>
streamProxy(model, context, {
...options,
authToken: "...",
proxyUrl: "https://your-server.com",
}),
});
```
## Low-Level API
For direct control without the Agent class:
```typescript
import { agentLoop, agentLoopContinue } from "@mariozechner/pi-agent-core";
const context: AgentContext = {
systemPrompt: "You are helpful.",
messages: [],
tools: [],
};
const config: AgentLoopConfig = {
model: getModel("openai", "gpt-4o"),
convertToLlm: (msgs) => msgs.filter(m => ["user", "assistant", "toolResult"].includes(m.role)),
toolExecution: "parallel",
beforeToolCall: async ({ toolCall, args, context }) => undefined,
afterToolCall: async ({ toolCall, result, isError, context }) => undefined,
};
const userMessage = { role: "user", content: "Hello", timestamp: Date.now() };
for await (const event of agentLoop([userMessage], context, config)) {
console.log(event.type);
}
// Continue from existing context
for await (const event of agentLoopContinue(context, config)) {
console.log(event.type);
}
```
These low-level streams are observational. They preserve event order, but they do not wait for your async event handling to settle before later producer phases continue. If you need message processing to act as a barrier before tool preflight, use the `Agent` class instead of raw `agentLoop()` or `agentLoopContinue()`.
## License
MIT
================================================
FILE: packages/agent/package.json
================================================
{
"name": "@mariozechner/pi-agent-core",
"version": "0.61.0",
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist",
"README.md"
],
"scripts": {
"clean": "shx rm -rf dist",
"build": "tsgo -p tsconfig.build.json",
"dev": "tsgo -p tsconfig.build.json --watch --preserveWatchOutput",
"test": "vitest --run",
"prepublishOnly": "npm run clean && npm run build"
},
"dependencies": {
"@mariozechner/pi-ai": "^0.61.0"
},
"keywords": [
"ai",
"agent",
"llm",
"transport",
"state-management"
],
"author": "Mario Zechner",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/badlogic/pi-mono.git",
"directory": "packages/agent"
},
"engines": {
"node": ">=20.0.0"
},
"devDependencies": {
"@types/node": "^24.3.0",
"typescript": "^5.7.3",
"vitest": "^3.2.4"
}
}
================================================
FILE: packages/agent/src/agent-loop.ts
================================================
/**
* Agent loop that works with AgentMessage throughout.
* Transforms to Message[] only at the LLM call boundary.
*/
import {
type AssistantMessage,
type Context,
EventStream,
streamSimple,
type ToolResultMessage,
validateToolArguments,
} from "@mariozechner/pi-ai";
import type {
AgentContext,
AgentEvent,
AgentLoopConfig,
AgentMessage,
AgentTool,
AgentToolCall,
AgentToolResult,
StreamFn,
} from "./types.js";
export type AgentEventSink = (event: AgentEvent) => Promise<void> | void;
/**
* Start an agent loop with a new prompt message.
* The prompt is added to the context and events are emitted for it.
*/
export function agentLoop(
prompts: AgentMessage[],
context: AgentContext,
config: AgentLoopConfig,
signal?: AbortSignal,
streamFn?: StreamFn,
): EventStream<AgentEvent, AgentMessage[]> {
const stream = createAgentStream();
void runAgentLoop(
prompts,
context,
config,
async (event) => {
stream.push(event);
},
signal,
streamFn,
).then((messages) => {
stream.end(messages);
});
return stream;
}
/**
* Continue an agent loop from the current context without adding a new message.
* Used for retries - context already has user message or tool results.
*
* **Important:** The last message in context must convert to a `user` or `toolResult` message
* via `convertToLlm`. If it doesn't, the LLM provider will reject the request.
* This cannot be validated here since `convertToLlm` is only called once per turn.
*/
export function agentLoopContinue(
context: AgentContext,
config: AgentLoopConfig,
signal?: AbortSignal,
streamFn?: StreamFn,
): EventStream<AgentEvent, AgentMessage[]> {
if (context.messages.length === 0) {
throw new Error("Cannot continue: no messages in context");
}
if (context.messages[context.messages.length - 1].role === "assistant") {
throw new Error("Cannot continue from message role: assistant");
}
const stream = createAgentStream();
void runAgentLoopContinue(
context,
config,
async (event) => {
stream.push(event);
},
signal,
streamFn,
).then((messages) => {
stream.end(messages);
});
return stream;
}
export async function runAgentLoop(
prompts: AgentMessage[],
context: AgentContext,
config: AgentLoopConfig,
emit: AgentEventSink,
signal?: AbortSignal,
streamFn?: StreamFn,
): Promise<AgentMessage[]> {
const newMessages: AgentMessage[] = [...prompts];
const currentContext: AgentContext = {
...context,
messages: [...context.messages, ...prompts],
};
await emit({ type: "agent_start" });
await emit({ type: "turn_start" });
for (const prompt of prompts) {
await emit({ type: "message_start", message: prompt });
await emit({ type: "message_end", message: prompt });
}
await runLoop(currentContext, newMessages, config, signal, emit, streamFn);
return newMessages;
}
export async function runAgentLoopContinue(
context: AgentContext,
config: AgentLoopConfig,
emit: AgentEventSink,
signal?: AbortSignal,
streamFn?: StreamFn,
): Promise<AgentMessage[]> {
if (context.messages.length === 0) {
throw new Error("Cannot continue: no messages in context");
}
if (context.messages[context.messages.length - 1].role === "assistant") {
throw new Error("Cannot continue from message role: assistant");
}
const newMessages: AgentMessage[] = [];
const currentContext: AgentContext = { ...context };
await emit({ type: "agent_start" });
await emit({ type: "turn_start" });
await runLoop(currentContext, newMessages, config, signal, emit, streamFn);
return newMessages;
}
function createAgentStream(): EventStream<AgentEvent, AgentMessage[]> {
return new EventStream<AgentEvent, AgentMessage[]>(
(event: AgentEvent) => event.type === "agent_end",
(event: AgentEvent) => (event.type === "agent_end" ? event.messages : []),
);
}
/**
* Main loop logic shared by agentLoop and agentLoopContinue.
*/
async function runLoop(
currentContext: AgentContext,
newMessages: AgentMessage[],
config: AgentLoopConfig,
signal: AbortSignal | undefined,
emit: AgentEventSink,
streamFn?: StreamFn,
): Promise<void> {
let firstTurn = true;
// Check for steering messages at start (user may have typed while waiting)
let pendingMessages: AgentMessage[] = (await config.getSteeringMessages?.()) || [];
// Outer loop: continues when queued follow-up messages arrive after agent would stop
while (true) {
let hasMoreToolCalls = true;
// Inner loop: process tool calls and steering messages
while (hasMoreToolCalls || pendingMessages.length > 0) {
if (!firstTurn) {
await emit({ type: "turn_start" });
} else {
firstTurn = false;
}
// Process pending messages (inject before next assistant response)
if (pendingMessages.length > 0) {
for (const message of pendingMessages) {
await emit({ type: "message_start", message });
await emit({ type: "message_end", message });
currentContext.messages.push(message);
newMessages.push(message);
}
pendingMessages = [];
}
// Stream assistant response
const message = await streamAssistantResponse(currentContext, config, signal, emit, streamFn);
newMessages.push(message);
if (message.stopReason === "error" || message.stopReason === "aborted") {
await emit({ type: "turn_end", message, toolResults: [] });
await emit({ type: "agent_end", messages: newMessages });
return;
}
// Check for tool calls
const toolCalls = message.content.filter((c) => c.type === "toolCall");
hasMoreToolCalls = toolCalls.length > 0;
const toolResults: ToolResultMessage[] = [];
if (hasMoreToolCalls) {
toolResults.push(...(await executeToolCalls(currentContext, message, config, signal, emit)));
for (const result of toolResults) {
currentContext.messages.push(result);
newMessages.push(result);
}
}
await emit({ type: "turn_end", message, toolResults });
pendingMessages = (await config.getSteeringMessages?.()) || [];
}
// Agent would stop here. Check for follow-up messages.
const followUpMessages = (await config.getFollowUpMessages?.()) || [];
if (followUpMessages.length > 0) {
// Set as pending so inner loop processes them
pendingMessages = followUpMessages;
continue;
}
// No more messages, exit
break;
}
await emit({ type: "agent_end", messages: newMessages });
}
/**
* Stream an assistant response from the LLM.
* This is where AgentMessage[] gets transformed to Message[] for the LLM.
*/
async function streamAssistantResponse(
context: AgentContext,
config: AgentLoopConfig,
signal: AbortSignal | undefined,
emit: AgentEventSink,
streamFn?: StreamFn,
): Promise<AssistantMessage> {
// Apply context transform if configured (AgentMessage[] → AgentMessage[])
let messages = context.messages;
if (config.transformContext) {
messages = await config.transformContext(messages, signal);
}
// Convert to LLM-compatible messages (AgentMessage[] → Message[])
const llmMessages = await config.convertToLlm(messages);
// Build LLM context
const llmContext: Context = {
systemPrompt: context.systemPrompt,
messages: llmMessages,
tools: context.tools,
};
const streamFunction = streamFn || streamSimple;
// Resolve API key (important for expiring tokens)
const resolvedApiKey =
(config.getApiKey ? await config.getApiKey(config.model.provider) : undefined) || config.apiKey;
const response = await streamFunction(config.model, llmContext, {
...config,
apiKey: resolvedApiKey,
signal,
});
let partialMessage: AssistantMessage | null = null;
let addedPartial = false;
for await (const event of response) {
switch (event.type) {
case "start":
partialMessage = event.partial;
context.messages.push(partialMessage);
addedPartial = true;
await emit({ type: "message_start", message: { ...partialMessage } });
break;
case "text_start":
case "text_delta":
case "text_end":
case "thinking_start":
case "thinking_delta":
case "thinking_end":
case "toolcall_start":
case "toolcall_delta":
case "toolcall_end":
if (partialMessage) {
partialMessage = event.partial;
context.messages[context.messages.length - 1] = partialMessage;
await emit({
type: "message_update",
assistantMessageEvent: event,
message: { ...partialMessage },
});
}
break;
case "done":
case "error": {
const finalMessage = await response.result();
if (addedPartial) {
context.messages[context.messages.length - 1] = finalMessage;
} else {
context.messages.push(finalMessage);
}
if (!addedPartial) {
await emit({ type: "message_start", message: { ...finalMessage } });
}
await emit({ type: "message_end", message: finalMessage });
return finalMessage;
}
}
}
const finalMessage = await response.result();
if (addedPartial) {
context.messages[context.messages.length - 1] = finalMessage;
} else {
context.messages.push(finalMessage);
await emit({ type: "message_start", message: { ...finalMessage } });
}
await emit({ type: "message_end", message: finalMessage });
return finalMessage;
}
/**
* Execute tool calls from an assistant message.
*/
async function executeToolCalls(
currentContext: AgentContext,
assistantMessage: AssistantMessage,
config: AgentLoopConfig,
signal: AbortSignal | undefined,
emit: AgentEventSink,
): Promise<ToolResultMessage[]> {
const toolCalls = assistantMessage.content.filter((c) => c.type === "toolCall");
if (config.toolExecution === "sequential") {
return executeToolCallsSequential(currentContext, assistantMessage, toolCalls, config, signal, emit);
}
return executeToolCallsParallel(currentContext, assistantMessage, toolCalls, config, signal, emit);
}
async function executeToolCallsSequential(
currentContext: AgentContext,
assistantMessage: AssistantMessage,
toolCalls: AgentToolCall[],
config: AgentLoopConfig,
signal: AbortSignal | undefined,
emit: AgentEventSink,
): Promise<ToolResultMessage[]> {
const results: ToolResultMessage[] = [];
for (const toolCall of toolCalls) {
await emit({
type: "tool_execution_start",
toolCallId: toolCall.id,
toolName: toolCall.name,
args: toolCall.arguments,
});
const preparation = await prepareToolCall(currentContext, assistantMessage, toolCall, config, signal);
if (preparation.kind === "immediate") {
results.push(await emitToolCallOutcome(toolCall, preparation.result, preparation.isError, emit));
} else {
const executed = await executePreparedToolCall(preparation, signal, emit);
results.push(
await finalizeExecutedToolCall(
currentContext,
assistantMessage,
preparation,
executed,
config,
signal,
emit,
),
);
}
}
return results;
}
async function executeToolCallsParallel(
currentContext: AgentContext,
assistantMessage: AssistantMessage,
toolCalls: AgentToolCall[],
config: AgentLoopConfig,
signal: AbortSignal | undefined,
emit: AgentEventSink,
): Promise<ToolResultMessage[]> {
const results: ToolResultMessage[] = [];
const runnableCalls: PreparedToolCall[] = [];
for (const toolCall of toolCalls) {
await emit({
type: "tool_execution_start",
toolCallId: toolCall.id,
toolName: toolCall.name,
args: toolCall.arguments,
});
const preparation = await prepareToolCall(currentContext, assistantMessage, toolCall, config, signal);
if (preparation.kind === "immediate") {
results.push(await emitToolCallOutcome(toolCall, preparation.result, preparation.isError, emit));
} else {
runnableCalls.push(preparation);
}
}
const runningCalls = runnableCalls.map((prepared) => ({
prepared,
execution: executePreparedToolCall(prepared, signal, emit),
}));
for (const running of runningCalls) {
const executed = await running.execution;
results.push(
await finalizeExecutedToolCall(
currentContext,
assistantMessage,
running.prepared,
executed,
config,
signal,
emit,
),
);
}
return results;
}
type PreparedToolCall = {
kind: "prepared";
toolCall: AgentToolCall;
tool: AgentTool<any>;
args: unknown;
};
type ImmediateToolCallOutcome = {
kind: "immediate";
result: AgentToolResult<any>;
isError: boolean;
};
type ExecutedToolCallOutcome = {
result: AgentToolResult<any>;
isError: boolean;
};
async function prepareToolCall(
currentContext: AgentContext,
assistantMessage: AssistantMessage,
toolCall: AgentToolCall,
config: AgentLoopConfig,
signal: AbortSignal | undefined,
): Promise<PreparedToolCall | ImmediateToolCallOutcome> {
const tool = currentContext.tools?.find((t) => t.name === toolCall.name);
if (!tool) {
return {
kind: "immediate",
result: createErrorToolResult(`Tool ${toolCall.name} not found`),
isError: true,
};
}
try {
const validatedArgs = validateToolArguments(tool, toolCall);
if (config.beforeToolCall) {
const beforeResult = await config.beforeToolCall(
{
assistantMessage,
toolCall,
args: validatedArgs,
context: currentContext,
},
signal,
);
if (beforeResult?.block) {
return {
kind: "immediate",
result: createErrorToolResult(beforeResult.reason || "Tool execution was blocked"),
isError: true,
};
}
}
return {
kind: "prepared",
toolCall,
tool,
args: validatedArgs,
};
} catch (error) {
return {
kind: "immediate",
result: createErrorToolResult(error instanceof Error ? error.message : String(error)),
isError: true,
};
}
}
async function executePreparedToolCall(
prepared: PreparedToolCall,
signal: AbortSignal | undefined,
emit: AgentEventSink,
): Promise<ExecutedToolCallOutcome> {
const updateEvents: Promise<void>[] = [];
try {
const result = await prepared.tool.execute(
prepared.toolCall.id,
prepared.args as never,
signal,
(partialResult) => {
updateEvents.push(
Promise.resolve(
emit({
type: "tool_execution_update",
toolCallId: prepared.toolCall.id,
toolName: prepared.toolCall.name,
args: prepared.toolCall.arguments,
partialResult,
}),
),
);
},
);
await Promise.all(updateEvents);
return { result, isError: false };
} catch (error) {
await Promise.all(updateEvents);
return {
result: createErrorToolResult(error instanceof Error ? error.message : String(error)),
isError: true,
};
}
}
async function finalizeExecutedToolCall(
currentContext: AgentContext,
assistantMessage: AssistantMessage,
prepared: PreparedToolCall,
executed: ExecutedToolCallOutcome,
config: AgentLoopConfig,
signal: AbortSignal | undefined,
emit: AgentEventSink,
): Promise<ToolResultMessage> {
let result = executed.result;
let isError = executed.isError;
if (config.afterToolCall) {
const afterResult = await config.afterToolCall(
{
assistantMessage,
toolCall: prepared.toolCall,
args: prepared.args,
result,
isError,
context: currentContext,
},
signal,
);
if (afterResult) {
result = {
content: afterResult.content ?? result.content,
details: afterResult.details ?? result.details,
};
isError = afterResult.isError ?? isError;
}
}
return await emitToolCallOutcome(prepared.toolCall, result, isError, emit);
}
function createErrorToolResult(message: string): AgentToolResult<any> {
return {
content: [{ type: "text", text: message }],
details: {},
};
}
async function emitToolCallOutcome(
toolCall: AgentToolCall,
result: AgentToolResult<any>,
isError: boolean,
emit: AgentEventSink,
): Promise<ToolResultMessage> {
await emit({
type: "tool_execution_end",
toolCallId: toolCall.id,
toolName: toolCall.name,
result,
isError,
});
const toolResultMessage: ToolResultMessage = {
role: "toolResult",
toolCallId: toolCall.id,
toolName: toolCall.name,
content: result.content,
details: result.details,
isError,
timestamp: Date.now(),
};
await emit({ type: "message_start", message: toolResultMessage });
await emit({ type: "message_end", message: toolResultMessage });
return toolResultMessage;
}
================================================
FILE: packages/agent/src/agent.ts
================================================
/**
* Agent class that uses the agent-loop directly.
* No transport abstraction - calls streamSimple via the loop.
*/
import {
getModel,
type ImageContent,
type Message,
type Model,
type SimpleStreamOptions,
streamSimple,
type TextContent,
type ThinkingBudgets,
type Transport,
} from "@mariozechner/pi-ai";
import { runAgentLoop, runAgentLoopContinue } from "./agent-loop.js";
import type {
AfterToolCallContext,
AfterToolCallResult,
AgentContext,
AgentEvent,
AgentLoopConfig,
AgentMessage,
AgentState,
AgentTool,
BeforeToolCallContext,
BeforeToolCallResult,
StreamFn,
ThinkingLevel,
ToolExecutionMode,
} from "./types.js";
/**
* Default convertToLlm: Keep only LLM-compatible messages, convert attachments.
*/
function defaultConvertToLlm(messages: AgentMessage[]): Message[] {
return messages.filter((m) => m.role === "user" || m.role === "assistant" || m.role === "toolResult");
}
export interface AgentOptions {
initialState?: Partial<AgentState>;
/**
* Converts AgentMessage[] to LLM-compatible Message[] before each LLM call.
* Default filters to user/assistant/toolResult and converts attachments.
*/
convertToLlm?: (messages: AgentMessage[]) => Message[] | Promise<Message[]>;
/**
* Optional transform applied to context before convertToLlm.
* Use for context pruning, injecting external context, etc.
*/
transformContext?: (messages: AgentMessage[], signal?: AbortSignal) => Promise<AgentMessage[]>;
/**
* Steering mode: "all" = send all steering messages at once, "one-at-a-time" = one per turn
*/
steeringMode?: "all" | "one-at-a-time";
/**
* Follow-up mode: "all" = send all follow-up messages at once, "one-at-a-time" = one per turn
*/
followUpMode?: "all" | "one-at-a-time";
/**
* Custom stream function (for proxy backends, etc.). Default uses streamSimple.
*/
streamFn?: StreamFn;
/**
* Optional session identifier forwarded to LLM providers.
* Used by providers that support session-based caching (e.g., OpenAI Codex).
*/
sessionId?: string;
/**
* Resolves an API key dynamically for each LLM call.
* Useful for expiring tokens (e.g., GitHub Copilot OAuth).
*/
getApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;
/**
* Inspect or replace provider payloads before they are sent.
*/
onPayload?: SimpleStreamOptions["onPayload"];
/**
* Custom token budgets for thinking levels (token-based providers only).
*/
thinkingBudgets?: ThinkingBudgets;
/**
* Preferred transport for providers that support multiple transports.
*/
transport?: Transport;
/**
* Maximum delay in milliseconds to wait for a retry when the server requests a long wait.
* If the server's requested delay exceeds this value, the request fails immediately,
* allowing higher-level retry logic to handle it with user visibility.
* Default: 60000 (60 seconds). Set to 0 to disable the cap.
*/
maxRetryDelayMs?: number;
/** Tool execution mode. Default: "parallel" */
toolExecution?: ToolExecutionMode;
/** Called before a tool is executed, after arguments have been validated. */
beforeToolCall?: (context: BeforeToolCallContext, signal?: AbortSignal) => Promise<BeforeToolCallResult | undefined>;
/** Called after a tool finishes executing, before final tool events are emitted. */
afterToolCall?: (context: AfterToolCallContext, signal?: AbortSignal) => Promise<AfterToolCallResult | undefined>;
}
export class Agent {
private _state: AgentState = {
systemPrompt: "",
model: getModel("google", "gemini-2.5-flash-lite-preview-06-17"),
thinkingLevel: "off",
tools: [],
messages: [],
isStreaming: false,
streamMessage: null,
pendingToolCalls: new Set<string>(),
error: undefined,
};
private listeners = new Set<(e: AgentEvent) => void>();
private abortController?: AbortController;
private convertToLlm: (messages: AgentMessage[]) => Message[] | Promise<Message[]>;
private transformContext?: (messages: AgentMessage[], signal?: AbortSignal) => Promise<AgentMessage[]>;
private steeringQueue: AgentMessage[] = [];
private followUpQueue: AgentMessage[] = [];
private steeringMode: "all" | "one-at-a-time";
private followUpMode: "all" | "one-at-a-time";
public streamFn: StreamFn;
private _sessionId?: string;
public getApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;
private _onPayload?: SimpleStreamOptions["onPayload"];
private runningPrompt?: Promise<void>;
private resolveRunningPrompt?: () => void;
private _thinkingBudgets?: ThinkingBudgets;
private _transport: Transport;
private _maxRetryDelayMs?: number;
private _toolExecution: ToolExecutionMode;
private _beforeToolCall?: (
context: BeforeToolCallContext,
signal?: AbortSignal,
) => Promise<BeforeToolCallResult | undefined>;
private _afterToolCall?: (
context: AfterToolCallContext,
signal?: AbortSignal,
) => Promise<AfterToolCallResult | undefined>;
constructor(opts: AgentOptions = {}) {
this._state = { ...this._state, ...opts.initialState };
this.convertToLlm = opts.convertToLlm || defaultConvertToLlm;
this.transformContext = opts.transformContext;
this.steeringMode = opts.steeringMode || "one-at-a-time";
this.followUpMode = opts.followUpMode || "one-at-a-time";
this.streamFn = opts.streamFn || streamSimple;
this._sessionId = opts.sessionId;
this.getApiKey = opts.getApiKey;
this._onPayload = opts.onPayload;
this._thinkingBudgets = opts.thinkingBudgets;
this._transport = opts.transport ?? "sse";
this._maxRetryDelayMs = opts.maxRetryDelayMs;
this._toolExecution = opts.toolExecution ?? "parallel";
this._beforeToolCall = opts.beforeToolCall;
this._afterToolCall = opts.afterToolCall;
}
/**
* Get the current session ID used for provider caching.
*/
get sessionId(): string | undefined {
return this._sessionId;
}
/**
* Set the session ID for provider caching.
* Call this when switching sessions (new session, branch, resume).
*/
set sessionId(value: string | undefined) {
this._sessionId = value;
}
/**
* Get the current thinking budgets.
*/
get thinkingBudgets(): ThinkingBudgets | undefined {
return this._thinkingBudgets;
}
/**
* Set custom thinking budgets for token-based providers.
*/
set thinkingBudgets(value: ThinkingBudgets | undefined) {
this._thinkingBudgets = value;
}
/**
* Get the current preferred transport.
*/
get transport(): Transport {
return this._transport;
}
/**
* Set the preferred transport.
*/
setTransport(value: Transport) {
this._transport = value;
}
/**
* Get the current max retry delay in milliseconds.
*/
get maxRetryDelayMs(): number | undefined {
return this._maxRetryDelayMs;
}
/**
* Set the maximum delay to wait for server-requested retries.
* Set to 0 to disable the cap.
*/
set maxRetryDelayMs(value: number | undefined) {
this._maxRetryDelayMs = value;
}
get toolExecution(): ToolExecutionMode {
return this._toolExecution;
}
setToolExecution(value: ToolExecutionMode) {
this._toolExecution = value;
}
setBeforeToolCall(
value:
| ((context: BeforeToolCallContext, signal?: AbortSignal) => Promise<BeforeToolCallResult | undefined>)
| undefined,
) {
this._beforeToolCall = value;
}
setAfterToolCall(
value:
| ((context: AfterToolCallContext, signal?: AbortSignal) => Promise<AfterToolCallResult | undefined>)
| undefined,
) {
this._afterToolCall = value;
}
get state(): AgentState {
return this._state;
}
subscribe(fn: (e: AgentEvent) => void): () => void {
this.listeners.add(fn);
return () => this.listeners.delete(fn);
}
// State mutators
setSystemPrompt(v: string) {
this._state.systemPrompt = v;
}
setModel(m: Model<any>) {
this._state.model = m;
}
setThinkingLevel(l: ThinkingLevel) {
this._state.thinkingLevel = l;
}
setSteeringMode(mode: "all" | "one-at-a-time") {
this.steeringMode = mode;
}
getSteeringMode(): "all" | "one-at-a-time" {
return this.steeringMode;
}
setFollowUpMode(mode: "all" | "one-at-a-time") {
this.followUpMode = mode;
}
getFollowUpMode(): "all" | "one-at-a-time" {
return this.followUpMode;
}
setTools(t: AgentTool<any>[]) {
this._state.tools = t;
}
replaceMessages(ms: AgentMessage[]) {
this._state.messages = ms.slice();
}
appendMessage(m: AgentMessage) {
this._state.messages = [...this._state.messages, m];
}
/**
* Queue a steering message while the agent is running.
* Delivered after the current assistant turn finishes executing its tool calls,
* before the next LLM call.
*/
steer(m: AgentMessage) {
this.steeringQueue.push(m);
}
/**
* Queue a follow-up message to be processed after the agent finishes.
* Delivered only when agent has no more tool calls or steering messages.
*/
followUp(m: AgentMessage) {
this.followUpQueue.push(m);
}
clearSteeringQueue() {
this.steeringQueue = [];
}
clearFollowUpQueue() {
this.followUpQueue = [];
}
clearAllQueues() {
this.steeringQueue = [];
this.followUpQueue = [];
}
hasQueuedMessages(): boolean {
return this.steeringQueue.length > 0 || this.followUpQueue.length > 0;
}
private dequeueSteeringMessages(): AgentMessage[] {
if (this.steeringMode === "one-at-a-time") {
if (this.steeringQueue.length > 0) {
const first = this.steeringQueue[0];
this.steeringQueue = this.steeringQueue.slice(1);
return [first];
}
return [];
}
const steering = this.steeringQueue.slice();
this.steeringQueue = [];
return steering;
}
private dequeueFollowUpMessages(): AgentMessage[] {
if (this.followUpMode === "one-at-a-time") {
if (this.followUpQueue.length > 0) {
const first = this.followUpQueue[0];
this.followUpQueue = this.followUpQueue.slice(1);
return [first];
}
return [];
}
const followUp = this.followUpQueue.slice();
this.followUpQueue = [];
return followUp;
}
clearMessages() {
this._state.messages = [];
}
abort() {
this.abortController?.abort();
}
waitForIdle(): Promise<void> {
return this.runningPrompt ?? Promise.resolve();
}
reset() {
this._state.messages = [];
this._state.isStreaming = false;
this._state.streamMessage = null;
this._state.pendingToolCalls = new Set<string>();
this._state.error = undefined;
this.steeringQueue = [];
this.followUpQueue = [];
}
/** Send a prompt with an AgentMessage */
async prompt(message: AgentMessage | AgentMessage[]): Promise<void>;
async prompt(input: string, images?: ImageContent[]): Promise<void>;
async prompt(input: string | AgentMessage | AgentMessage[], images?: ImageContent[]) {
if (this._state.isStreaming) {
throw new Error(
"Agent is already processing a prompt. Use steer() or followUp() to queue messages, or wait for completion.",
);
}
const model = this._state.model;
if (!model) throw new Error("No model configured");
let msgs: AgentMessage[];
if (Array.isArray(input)) {
msgs = input;
} else if (typeof input === "string") {
const content: Array<TextContent | ImageContent> = [{ type: "text", text: input }];
if (images && images.length > 0) {
content.push(...images);
}
msgs = [
{
role: "user",
content,
timestamp: Date.now(),
},
];
} else {
msgs = [input];
}
await this._runLoop(msgs);
}
/**
* Continue from current context (used for retries and resuming queued messages).
*/
async continue() {
if (this._state.isStreaming) {
throw new Error("Agent is already processing. Wait for completion before continuing.");
}
const messages = this._state.messages;
if (messages.length === 0) {
throw new Error("No messages to continue from");
}
if (messages[messages.length - 1].role === "assistant") {
const queuedSteering = this.dequeueSteeringMessages();
if (queuedSteering.length > 0) {
await this._runLoop(queuedSteering, { skipInitialSteeringPoll: true });
return;
}
const queuedFollowUp = this.dequeueFollowUpMessages();
if (queuedFollowUp.length > 0) {
await this._runLoop(queuedFollowUp);
return;
}
throw new Error("Cannot continue from message role: assistant");
}
await this._runLoop(undefined);
}
private _processLoopEvent(event: AgentEvent): void {
switch (event.type) {
case "message_start":
this._state.streamMessage = event.message;
break;
case "message_update":
this._state.streamMessage = event.message;
break;
case "message_end":
this._state.streamMessage = null;
this.appendMessage(event.message);
break;
case "tool_execution_start": {
const pendingToolCalls = new Set(this._state.pendingToolCalls);
pendingToolCalls.add(event.toolCallId);
this._state.pendingToolCalls = pendingToolCalls;
break;
}
case "tool_execution_end": {
const pendingToolCalls = new Set(this._state.pendingToolCalls);
pendingToolCalls.delete(event.toolCallId);
this._state.pendingToolCalls = pendingToolCalls;
break;
}
case "turn_end":
if (event.message.role === "assistant" && (event.message as any).errorMessage) {
this._state.error = (event.message as any).errorMessage;
}
break;
case "agent_end":
this._state.isStreaming = false;
this._state.streamMessage = null;
break;
}
this.emit(event);
}
/**
* Run the agent loop.
* If messages are provided, starts a new conversation turn with those messages.
* Otherwise, continues from existing context.
*/
private async _runLoop(messages?: AgentMessage[], options?: { skipInitialSteeringPoll?: boolean }) {
const model = this._state.model;
if (!model) throw new Error("No model configured");
this.runningPrompt = new Promise<void>((resolve) => {
this.resolveRunningPrompt = resolve;
});
this.abortController = new AbortController();
this._state.isStreaming = true;
this._state.streamMessage = null;
this._state.error = undefined;
const reasoning = this._state.thinkingLevel === "off" ? undefined : this._state.thinkingLevel;
const context: AgentContext = {
systemPrompt: this._state.systemPrompt,
messages: this._state.messages.slice(),
tools: this._state.tools,
};
let skipInitialSteeringPoll = options?.skipInitialSteeringPoll === true;
const config: AgentLoopConfig = {
model,
reasoning,
sessionId: this._sessionId,
onPayload: this._onPayload,
transport: this._transport,
thinkingBudgets: this._thinkingBudgets,
maxRetryDelayMs: this._maxRetryDelayMs,
toolExecution: this._toolExecution,
beforeToolCall: this._beforeToolCall,
afterToolCall: this._afterToolCall,
convertToLlm: this.convertToLlm,
transformContext: this.transformContext,
getApiKey: this.getApiKey,
getSteeringMessages: async () => {
if (skipInitialSteeringPoll) {
skipInitialSteeringPoll = false;
return [];
}
return this.dequeueSteeringMessages();
},
getFollowUpMessages: async () => this.dequeueFollowUpMessages(),
};
try {
if (messages) {
await runAgentLoop(
messages,
context,
config,
async (event) => this._processLoopEvent(event),
this.abortController.signal,
this.streamFn,
);
} else {
await runAgentLoopContinue(
context,
config,
async (event) => this._processLoopEvent(event),
this.abortController.signal,
this.streamFn,
);
}
} catch (err: any) {
const errorMsg: AgentMessage = {
role: "assistant",
content: [{ type: "text", text: "" }],
api: model.api,
provider: model.provider,
model: model.id,
usage: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
stopReason: this.abortController?.signal.aborted ? "aborted" : "error",
errorMessage: err?.message || String(err),
timestamp: Date.now(),
} as AgentMessage;
this.appendMessage(errorMsg);
this._state.error = err?.message || String(err);
this.emit({ type: "agent_end", messages: [errorMsg] });
} finally {
this._state.isStreaming = false;
this._state.streamMessage = null;
this._state.pendingToolCalls = new Set<string>();
this.abortController = undefined;
this.resolveRunningPrompt?.();
this.runningPrompt = undefined;
this.resolveRunningPrompt = undefined;
}
}
private emit(e: AgentEvent) {
for (const listener of this.listeners) {
listener(e);
}
}
}
================================================
FILE: packages/agent/src/index.ts
================================================
// Core Agent
export * from "./agent.js";
// Loop functions
export * from "./agent-loop.js";
// Proxy utilities
export * from "./proxy.js";
// Types
export * from "./types.js";
================================================
FILE: packages/agent/src/proxy.ts
================================================
/**
* Proxy stream function for apps that route LLM calls through a server.
* The server manages auth and proxies requests to LLM providers.
*/
// Internal import for JSON parsing utility
import {
type AssistantMessage,
type AssistantMessageEvent,
type Context,
EventStream,
type Model,
parseStreamingJson,
type SimpleStreamOptions,
type StopReason,
type ToolCall,
} from "@mariozechner/pi-ai";
// Create stream class matching ProxyMessageEventStream
class ProxyMessageEventStream extends EventStream<AssistantMessageEvent, AssistantMessage> {
constructor() {
super(
(event) => event.type === "done" || event.type === "error",
(event) => {
if (event.type === "done") return event.message;
if (event.type === "error") return event.error;
throw new Error("Unexpected event type");
},
);
}
}
/**
* Proxy event types - server sends these with partial field stripped to reduce bandwidth.
*/
export type ProxyAssistantMessageEvent =
| { type: "start" }
| { type: "text_start"; contentIndex: number }
| { type: "text_delta"; contentIndex: number; delta: string }
| { type: "text_end"; contentIndex: number; contentSignature?: string }
| { type: "thinking_start"; contentIndex: number }
| { type: "thinking_delta"; contentIndex: number; delta: string }
| { type: "thinking_end"; contentIndex: number; contentSignature?: string }
| { type: "toolcall_start"; contentIndex: number; id: string; toolName: string }
| { type: "toolcall_delta"; contentIndex: number; delta: string }
| { type: "toolcall_end"; contentIndex: number }
| {
type: "done";
reason: Extract<StopReason, "stop" | "length" | "toolUse">;
usage: AssistantMessage["usage"];
}
| {
type: "error";
reason: Extract<StopReason, "aborted" | "error">;
errorMessage?: string;
usage: AssistantMessage["usage"];
};
export interface ProxyStreamOptions extends SimpleStreamOptions {
/** Auth token for the proxy server */
authToken: string;
/** Proxy server URL (e.g., "https://genai.example.com") */
proxyUrl: string;
}
/**
* Stream function that proxies through a server instead of calling LLM providers directly.
* The server strips the partial field from delta events to reduce bandwidth.
* We reconstruct the partial message client-side.
*
* Use this as the `streamFn` option when creating an Agent that needs to go through a proxy.
*
* @example
* ```typescript
* const agent = new Agent({
* streamFn: (model, context, options) =>
* streamProxy(model, context, {
* ...options,
* authToken: await getAuthToken(),
* proxyUrl: "https://genai.example.com",
* }),
* });
* ```
*/
export function streamProxy(model: Model<any>, context: Context, options: ProxyStreamOptions): ProxyMessageEventStream {
const stream = new ProxyMessageEventStream();
(async () => {
// Initialize the partial message that we'll build up from events
const partial: AssistantMessage = {
role: "assistant",
stopReason: "stop",
content: [],
api: model.api,
provider: model.provider,
model: model.id,
usage: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
timestamp: Date.now(),
};
let reader: ReadableStreamDefaultReader<Uint8Array> | undefined;
const abortHandler = () => {
if (reader) {
reader.cancel("Request aborted by user").catch(() => {});
}
};
if (options.signal) {
options.signal.addEventListener("abort", abortHandler);
}
try {
const response = await fetch(`${options.proxyUrl}/api/stream`, {
method: "POST",
headers: {
Authorization: `Bearer ${options.authToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model,
context,
options: {
temperature: options.temperature,
maxTokens: options.maxTokens,
reasoning: options.reasoning,
},
}),
signal: options.signal,
});
if (!response.ok) {
let errorMessage = `Proxy error: ${response.status} ${response.statusText}`;
try {
const errorData = (await response.json()) as { error?: string };
if (errorData.error) {
errorMessage = `Proxy error: ${errorData.error}`;
}
} catch {
// Couldn't parse error response
}
throw new Error(errorMessage);
}
reader = response.body!.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
if (options.signal?.aborted) {
throw new Error("Request aborted by user");
}
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() || "";
for (const line of lines) {
if (line.startsWith("data: ")) {
const data = line.slice(6).trim();
if (data) {
const proxyEvent = JSON.parse(data) as ProxyAssistantMessageEvent;
const event = processProxyEvent(proxyEvent, partial);
if (event) {
stream.push(event);
}
}
}
}
}
if (options.signal?.aborted) {
throw new Error("Request aborted by user");
}
stream.end();
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
const reason = options.signal?.aborted ? "aborted" : "error";
partial.stopReason = reason;
partial.errorMessage = errorMessage;
stream.push({
type: "error",
reason,
error: partial,
});
stream.end();
} finally {
if (options.signal) {
options.signal.removeEventListener("abort", abortHandler);
}
}
})();
return stream;
}
/**
* Process a proxy event and update the partial message.
*/
function processProxyEvent(
proxyEvent: ProxyAssistantMessageEvent,
partial: AssistantMessage,
): AssistantMessageEvent | undefined {
switch (proxyEvent.type) {
case "start":
return { type: "start", partial };
case "text_start":
partial.content[proxyEvent.contentIndex] = { type: "text", text: "" };
return { type: "text_start", contentIndex: proxyEvent.contentIndex, partial };
case "text_delta": {
const content = partial.content[proxyEvent.contentIndex];
if (content?.type === "text") {
content.text += proxyEvent.delta;
return {
type: "text_delta",
contentIndex: proxyEvent.contentIndex,
delta: proxyEvent.delta,
partial,
};
}
throw new Error("Received text_delta for non-text content");
}
case "text_end": {
const content = partial.content[proxyEvent.contentIndex];
if (content?.type === "text") {
content.textSignature = proxyEvent.contentSignature;
return {
type: "text_end",
contentIndex: proxyEvent.contentIndex,
content: content.text,
partial,
};
}
throw new Error("Received text_end for non-text content");
}
case "thinking_start":
partial.content[proxyEvent.contentIndex] = { type: "thinking", thinking: "" };
return { type: "thinking_start", contentIndex: proxyEvent.contentIndex, partial };
case "thinking_delta": {
const content = partial.content[proxyEvent.contentIndex];
if (content?.type === "thinking") {
content.thinking += proxyEvent.delta;
return {
type: "thinking_delta",
contentIndex: proxyEvent.contentIndex,
delta: proxyEvent.delta,
partial,
};
}
throw new Error("Received thinking_delta for non-thinking content");
}
case "thinking_end": {
const content = partial.content[proxyEvent.contentIndex];
if (content?.type === "thinking") {
content.thinkingSignature = proxyEvent.contentSignature;
return {
type: "thinking_end",
contentIndex: proxyEvent.contentIndex,
content: content.thinking,
partial,
};
}
throw new Error("Received thinking_end for non-thinking content");
}
case "toolcall_start":
partial.content[proxyEvent.contentIndex] = {
type: "toolCall",
id: proxyEvent.id,
name: proxyEvent.toolName,
arguments: {},
partialJson: "",
} satisfies ToolCall & { partialJson: string } as ToolCall;
return { type: "toolcall_start", contentIndex: proxyEvent.contentIndex, partial };
case "toolcall_delta": {
const content = partial.content[proxyEvent.contentIndex];
if (content?.type === "toolCall") {
(content as any).partialJson += proxyEvent.delta;
content.arguments = parseStreamingJson((content as any).partialJson) || {};
partial.content[proxyEvent.contentIndex] = { ...content }; // Trigger reactivity
return {
type: "toolcall_delta",
contentIndex: proxyEvent.contentIndex,
delta: proxyEvent.delta,
partial,
};
}
throw new Error("Received toolcall_delta for non-toolCall content");
}
case "toolcall_end": {
const content = partial.content[proxyEvent.contentIndex];
if (content?.type === "toolCall") {
delete (content as any).partialJson;
return {
type: "toolcall_end",
contentIndex: proxyEvent.contentIndex,
toolCall: content,
partial,
};
}
return undefined;
}
case "done":
partial.stopReason = proxyEvent.reason;
partial.usage = proxyEvent.usage;
return { type: "done", reason: proxyEvent.reason, message: partial };
case "error":
partial.stopReason = proxyEvent.reason;
partial.errorMessage = proxyEvent.errorMessage;
partial.usage = proxyEvent.usage;
return { type: "error", reason: proxyEvent.reason, error: partial };
default: {
const _exhaustiveCheck: never = proxyEvent;
console.warn(`Unhandled proxy event type: ${(proxyEvent as any).type}`);
return undefined;
}
}
}
================================================
FILE: packages/agent/src/types.ts
================================================
import type {
AssistantMessage,
AssistantMessageEvent,
ImageContent,
Message,
Model,
SimpleStreamOptions,
streamSimple,
TextContent,
Tool,
ToolResultMessage,
} from "@mariozechner/pi-ai";
import type { Static, TSchema } from "@sinclair/typebox";
/**
* Stream function used by the agent loop.
*
* Contract:
* - Must not throw or return a rejected promise for request/model/runtime failures.
* - Must return an AssistantMessageEventStream.
* - Failures must be encoded in the returned stream via protocol events and a
* final AssistantMessage with stopReason "error" or "aborted" and errorMessage.
*/
export type StreamFn = (
...args: Parameters<typeof streamSimple>
) => ReturnType<typeof streamSimple> | Promise<ReturnType<typeof streamSimple>>;
/**
* Configuration for how tool calls from a single assistant message are executed.
*
* - "sequential": each tool call is prepared, executed, and finalized before the next one starts.
* - "parallel": tool calls are prepared sequentially, then allowed tools execute concurrently.
* Final tool results are still emitted in assistant source order.
*/
export type ToolExecutionMode = "sequential" | "parallel";
/** A single tool call content block emitted by an assistant message. */
export type AgentToolCall = Extract<AssistantMessage["content"][number], { type: "toolCall" }>;
/**
* Result returned from `beforeToolCall`.
*
* Returning `{ block: true }` prevents the tool from executing. The loop emits an error tool result instead.
* `reason` becomes the text shown in that error result. If omitted, a default blocked message is used.
*/
export interface BeforeToolCallResult {
block?: boolean;
reason?: string;
}
/**
* Partial override returned from `afterToolCall`.
*
* Merge semantics are field-by-field:
* - `content`: if provided, replaces the tool result content array in full
* - `details`: if provided, replaces the tool result details value in full
* - `isError`: if provided, replaces the tool result error flag
*
* Omitted fields keep the original executed tool result values.
* There is no deep merge for `content` or `details`.
*/
export interface AfterToolCallResult {
content?: (TextContent | ImageContent)[];
details?: unknown;
isError?: boolean;
}
/** Context passed to `beforeToolCall`. */
export interface BeforeToolCallContext {
/** The assistant message that requested the tool call. */
assistantMessage: AssistantMessage;
/** The raw tool call block from `assistantMessage.content`. */
toolCall: AgentToolCall;
/** Validated tool arguments for the target tool schema. */
args: unknown;
/** Current agent context at the time the tool call is prepared. */
context: AgentContext;
}
/** Context passed to `afterToolCall`. */
export interface AfterToolCallContext {
/** The assistant message that requested the tool call. */
assistantMessage: AssistantMessage;
/** The raw tool call block from `assistantMessage.content`. */
toolCall: AgentToolCall;
/** Validated tool arguments for the target tool schema. */
args: unknown;
/** The executed tool result before any `afterToolCall` overrides are applied. */
result: AgentToolResult<any>;
/** Whether the executed tool result is currently treated as an error. */
isError: boolean;
/** Current agent context at the time the tool call is finalized. */
context: AgentContext;
}
export interface AgentLoopConfig extends SimpleStreamOptions {
model: Model<any>;
/**
* Converts AgentMessage[] to LLM-compatible Message[] before each LLM call.
*
* Each AgentMessage must be converted to a UserMessage, AssistantMessage, or ToolResultMessage
* that the LLM can understand. AgentMessages that cannot be converted (e.g., UI-only notifications,
* status messages) should be filtered out.
*
* Contract: must not throw or reject. Return a safe fallback value instead.
* Throwing interrupts the low-level agent loop without producing a normal event sequence.
*
* @example
* ```typescript
* convertToLlm: (messages) => messages.flatMap(m => {
* if (m.role === "custom") {
* // Convert custom message to user message
* return [{ role: "user", content: m.content, timestamp: m.timestamp }];
* }
* if (m.role === "notification") {
* // Filter out UI-only messages
* return [];
* }
* // Pass through standard LLM messages
* return [m];
* })
* ```
*/
convertToLlm: (messages: AgentMessage[]) => Message[] | Promise<Message[]>;
/**
* Optional transform applied to the context before `convertToLlm`.
*
* Use this for operations that work at the AgentMessage level:
* - Context window management (pruning old messages)
* - Injecting context from external sources
*
* Contract: must not throw or reject. Return the original messages or another
* safe fallback value instead.
*
* @example
* ```typescript
* transformContext: async (messages) => {
* if (estimateTokens(messages) > MAX_TOKENS) {
* return pruneOldMessages(messages);
* }
* return messages;
* }
* ```
*/
transformContext?: (messages: AgentMessage[], signal?: AbortSignal) => Promise<AgentMessage[]>;
/**
* Resolves an API key dynamically for each LLM call.
*
* Useful for short-lived OAuth tokens (e.g., GitHub Copilot) that may expire
* during long-running tool execution phases.
*
* Contract: must not throw or reject. Return undefined when no key is available.
*/
getApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;
/**
* Returns steering messages to inject into the conversation mid-run.
*
* Called after the current assistant turn finishes executing its tool calls.
* If messages are returned, they are added to the context before the next LLM call.
* Tool calls from the current assistant message are not skipped.
*
* Use this for "steering" the agent while it's working.
*
* Contract: must not throw or reject. Return [] when no steering messages are available.
*/
getSteeringMessages?: () => Promise<AgentMessage[]>;
/**
* Returns follow-up messages to process after the agent would otherwise stop.
*
* Called when the agent has no more tool calls and no steering messages.
* If messages are returned, they're added to the context and the agent
* continues with another turn.
*
* Use this for follow-up messages that should wait until the agent finishes.
*
* Contract: must not throw or reject. Return [] when no follow-up messages are available.
*/
getFollowUpMessages?: () => Promise<AgentMessage[]>;
/**
* Tool execution mode.
* - "sequential": execute tool calls one by one
* - "parallel": preflight tool calls sequentially, then execute allowed tools concurrently
*
* Default: "parallel"
*/
toolExecution?: ToolExecutionMode;
/**
* Called before a tool is executed, after arguments have been validated.
*
* Return `{ block: true }` to prevent execution. The loop emits an error tool result instead.
* The hook receives the agent abort signal and is responsible for honoring it.
*/
beforeToolCall?: (context: BeforeToolCallContext, signal?: AbortSignal) => Promise<BeforeToolCallResult | undefined>;
/**
* Called after a tool finishes executing, before final tool events are emitted.
*
* Return an `AfterToolCallResult` to override parts of the executed tool result:
* - `content` replaces the full content array
* - `details` replaces the full details payload
* - `isError` replaces the error flag
*
* Any omitted fields keep their original values. No deep merge is performed.
* The hook receives the agent abort signal and is responsible for honoring it.
*/
afterToolCall?: (context: AfterToolCallContext, signal?: AbortSignal) => Promise<AfterToolCallResult | undefined>;
}
/**
* Thinking/reasoning level for models that support it.
* Note: "xhigh" is only supported by OpenAI gpt-5.1-codex-max, gpt-5.2, gpt-5.2-codex, gpt-5.3, and gpt-5.3-codex models.
*/
export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
/**
* Extensible interface for custom app messages.
* Apps can extend via declaration merging:
*
* @example
* ```typescript
* declare module "@mariozechner/agent" {
* interface CustomAgentMessages {
* artifact: ArtifactMessage;
* notification: NotificationMessage;
* }
* }
* ```
*/
export interface CustomAgentMessages {
// Empty by default - apps extend via declaration merging
}
/**
* AgentMessage: Union of LLM messages + custom messages.
* This abstraction allows apps to add custom message types while maintaining
* type safety and compatibility with the base LLM messages.
*/
export type AgentMessage = Message | CustomAgentMessages[keyof CustomAgentMessages];
/**
* Agent state containing all configuration and conversation data.
*/
export interface AgentState {
systemPrompt: string;
model: Model<any>;
thinkingLevel: ThinkingLevel;
tools: AgentTool<any>[];
messages: AgentMessage[]; // Can include attachments + custom message types
isStreaming: boolean;
streamMessage: AgentMessage | null;
pendingToolCalls: Set<string>;
error?: string;
}
export interface AgentToolResult<T> {
// Content blocks supporting text and images
content: (TextContent | ImageContent)[];
// Details to be displayed in a UI or logged
details: T;
}
// Callback for streaming tool execution updates
export type AgentToolUpdateCallback<T = any> = (partialResult: AgentToolResult<T>) => void;
// AgentTool extends Tool but adds the execute function
export interface AgentTool<TParameters extends TSchema = TSchema, TDetails = any> extends Tool<TParameters> {
// A human-readable label for the tool to be displayed in UI
label: string;
execute: (
toolCallId: string,
params: Static<TParameters>,
signal?: AbortSignal,
onUpdate?: AgentToolUpdateCallback<TDetails>,
) => Promise<AgentToolResult<TDetails>>;
}
// AgentContext is like Context but uses AgentTool
export interface AgentContext {
systemPrompt: string;
messages: AgentMessage[];
tools?: AgentTool<any>[];
}
/**
* Events emitted by the Agent for UI updates.
* These events provide fine-grained lifecycle information for messages, turns, and tool executions.
*/
export type AgentEvent =
// Agent lifecycle
| { type: "agent_start" }
| { type: "agent_end"; messages: AgentMessage[] }
// Turn lifecycle - a turn is one assistant response + any tool calls/results
| { type: "turn_start" }
| { type: "turn_end"; message: AgentMessage; toolResults: ToolResultMessage[] }
// Message lifecycle - emitted for user, assistant, and toolResult messages
| { type: "message_start"; message: AgentMessage }
// Only emitted for assistant messages during streaming
| { type: "message_update"; message: AgentMessage; assistantMessageEvent: AssistantMessageEvent }
| { type: "message_end"; message: AgentMessage }
// Tool execution lifecycle
| { type: "tool_execution_start"; toolCallId: string; toolName: string; args: any }
| { type: "tool_execution_update"; toolCallId: string; toolName: string; args: any; partialResult: any }
| { type: "tool_execution_end"; toolCallId: string; toolName: string; result: any; isError: boolean };
================================================
FILE: packages/agent/test/agent-loop.test.ts
================================================
import {
type AssistantMessage,
type AssistantMessageEvent,
EventStream,
type Message,
type Model,
type UserMessage,
} from "@mariozechner/pi-ai";
import { Type } from "@sinclair/typebox";
import { describe, expect, it } from "vitest";
import { agentLoop, agentLoopContinue } from "../src/agent-loop.js";
import type { AgentContext, AgentEvent, AgentLoopConfig, AgentMessage, AgentTool } from "../src/types.js";
// Mock stream for testing - mimics MockAssistantStream
class MockAssistantStream extends EventStream<AssistantMessageEvent, AssistantMessage> {
constructor() {
super(
(event) => event.type === "done" || event.type === "error",
(event) => {
if (event.type === "done") return event.message;
if (event.type === "error") return event.error;
throw new Error("Unexpected event type");
},
);
}
}
function createUsage() {
return {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
};
}
function createModel(): Model<"openai-responses"> {
return {
id: "mock",
name: "mock",
api: "openai-responses",
provider: "openai",
baseUrl: "https://example.invalid",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 8192,
maxTokens: 2048,
};
}
function createAssistantMessage(
content: AssistantMessage["content"],
stopReason: AssistantMessage["stopReason"] = "stop",
): AssistantMessage {
return {
role: "assistant",
content,
api: "openai-responses",
provider: "openai",
model: "mock",
usage: createUsage(),
stopReason,
timestamp: Date.now(),
};
}
function createUserMessage(text: string): UserMessage {
return {
role: "user",
content: text,
timestamp: Date.now(),
};
}
// Simple identity converter for tests - just passes through standard messages
function identityConverter(messages: AgentMessage[]): Message[] {
return messages.filter((m) => m.role === "user" || m.role === "assistant" || m.role === "toolResult") as Message[];
}
describe("agentLoop with AgentMessage", () => {
it("should emit events with AgentMessage types", async () => {
const context: AgentContext = {
systemPrompt: "You are helpful.",
messages: [],
tools: [],
};
const userPrompt: AgentMessage = createUserMessage("Hello");
const config: AgentLoopConfig = {
model: createModel(),
convertToLlm: identityConverter,
};
const streamFn = () => {
const stream = new MockAssistantStream();
queueMicrotask(() => {
const message = createAssistantMessage([{ type: "text", text: "Hi there!" }]);
stream.push({ type: "done", reason: "stop", message });
});
return stream;
};
const events: AgentEvent[] = [];
const stream = agentLoop([userPrompt], context, config, undefined, streamFn);
for await (const event of stream) {
events.push(event);
}
const messages = await stream.result();
// Should have user message and assistant message
expect(messages.length).toBe(2);
expect(messages[0].role).toBe("user");
expect(messages[1].role).toBe("assistant");
// Verify event sequence
const eventTypes = events.map((e) => e.type);
expect(eventTypes).toContain("agent_start");
expect(eventTypes).toContain("turn_start");
expect(eventTypes).toContain("message_start");
expect(eventTypes).toContain("message_end");
expect(eventTypes).toContain("turn_end");
expect(eventTypes).toContain("agent_end");
});
it("should handle custom message types via convertToLlm", async () => {
// Create a custom message type
interface CustomNotification {
role: "notification";
text: string;
timestamp: number;
}
const notification: CustomNotification = {
role: "notification",
text: "This is a notification",
timestamp: Date.now(),
};
const context: AgentContext = {
systemPrompt: "You are helpful.",
messages: [notification as unknown as AgentMessage], // Custom message in context
tools: [],
};
const userPrompt: AgentMessage = createUserMessage("Hello");
let convertedMessages: Message[] = [];
const config: AgentLoopConfig = {
model: createModel(),
convertToLlm: (messages) => {
// Filter out notifications, convert rest
convertedMessages = messages
.filter((m) => (m as { role: string }).role !== "notification")
.filter((m) => m.role === "user" || m.role === "assistant" || m.role === "toolResult") as Message[];
return convertedMessages;
},
};
const streamFn = () => {
const stream = new MockAssistantStream();
queueMicrotask(() => {
const message = createAssistantMessage([{ type: "text", text: "Response" }]);
stream.push({ type: "done", reason: "stop", message });
});
return stream;
};
const events: AgentEvent[] = [];
const stream = agentLoop([userPrompt], context, config, undefined, streamFn);
for await (const event of stream) {
events.push(event);
}
// The notification should have been filtered out in convertToLlm
expect(convertedMessages.length).toBe(1); // Only user message
expect(convertedMessages[0].role).toBe("user");
});
it("should apply transformContext before convertToLlm", async () => {
const context: AgentContext = {
systemPrompt: "You are helpful.",
messages: [
createUserMessage("old message 1"),
createAssistantMessage([{ type: "text", text: "old response 1" }]),
createUserMessage("old message 2"),
createAssistantMessage([{ type: "text", text: "old response 2" }]),
],
tools: [],
};
const userPrompt: AgentMessage = createUserMessage("new message");
let transformedMessages: AgentMessage[] = [];
let convertedMessages: Message[] = [];
const config: AgentLoopConfig = {
model: createModel(),
transformContext: async (messages) => {
// Keep only last 2 messages (prune old ones)
transformedMessages = messages.slice(-2);
return transformedMessages;
},
convertToLlm: (messages) => {
convertedMessages = messages.filter(
(m) => m.role === "user" || m.role === "assistant" || m.role === "toolResult",
) as Message[];
return convertedMessages;
},
};
const streamFn = () => {
const stream = new MockAssistantStream();
queueMicrotask(() => {
const message = createAssistantMessage([{ type: "text", text: "Response" }]);
stream.push({ type: "done", reason: "stop", message });
});
return stream;
};
const stream = agentLoop([userPrompt], context, config, undefined, streamFn);
for await (const _ of stream) {
// consume
}
// transformContext should have been called first, keeping only last 2
expect(transformedMessages.length).toBe(2);
// Then convertToLlm receives the pruned messages
expect(convertedMessages.length).toBe(2);
});
it("should handle tool calls and results", async () => {
const toolSchema = Type.Object({ value: Type.String() });
const executed: string[] = [];
const tool: AgentTool<typeof toolSchema, { value: string }> = {
name: "echo",
label: "Echo",
description: "Echo tool",
parameters: toolSchema,
async execute(_toolCallId, params) {
executed.push(params.value);
return {
content: [{ type: "text", text: `echoed: ${params.value}` }],
details: { value: params.value },
};
},
};
const context: AgentContext = {
systemPrompt: "",
messages: [],
tools: [tool],
};
const userPrompt: AgentMessage = createUserMessage("echo something");
const config: AgentLoopConfig = {
model: createModel(),
convertToLlm: identityConverter,
};
let callIndex = 0;
const streamFn = () => {
const stream = new MockAssistantStream();
queueMicrotask(() => {
if (callIndex === 0) {
// First call: return tool call
const message = createAssistantMessage(
[{ type: "toolCall", id: "tool-1", name: "echo", arguments: { value: "hello" } }],
"toolUse",
);
stream.push({ type: "done", reason: "toolUse", message });
} else {
// Second call: return final response
const message = createAssistantMessage([{ type: "text", text: "done" }]);
stream.push({ type: "done", reason: "stop", message });
}
callIndex++;
});
return stream;
};
const events: AgentEvent[] = [];
const stream = agentLoop([userPrompt], context, config, undefined, streamFn);
for await (const event of stream) {
events.push(event);
}
// Tool should have been executed
expect(executed).toEqual(["hello"]);
// Should have tool execution events
const toolStart = events.find((e) => e.type === "tool_execution_start");
const toolEnd = events.find((e) => e.type === "tool_execution_end");
expect(toolStart).toBeDefined();
expect(toolEnd).toBeDefined();
if (toolEnd?.type === "tool_execution_end") {
expect(toolEnd.isError).toBe(false);
}
});
it("should execute tool calls in parallel and emit tool results in source order", async () => {
const toolSchema = Type.Object({ value: Type.String() });
let firstResolved = false;
let parallelObserved = false;
let releaseFirst: (() => void) | undefined;
const firstDone = new Promise<void>((resolve) => {
releaseFirst = resolve;
});
const tool: AgentTool<typeof toolSchema, { value: string }> = {
name: "echo",
label: "Echo",
description: "Echo tool",
parameters: toolSchema,
async execute(_toolCallId, params) {
if (params.value === "first") {
await firstDone;
firstResolved = true;
}
if (params.value === "second" && !firstResolved) {
parallelObserved = true;
}
return {
content: [{ type: "text", text: `echoed: ${params.value}` }],
details: { value: params.value },
};
},
};
const context: AgentContext = {
systemPrompt: "",
messages: [],
tools: [tool],
};
const userPrompt: AgentMessage = createUserMessage("echo both");
const config: AgentLoopConfig = {
model: createModel(),
convertToLlm: identityConverter,
toolExecution: "parallel",
};
let callIndex = 0;
const stream = agentLoop([userPrompt], context, config, undefined, () => {
const mockStream = new MockAssistantStream();
queueMicrotask(() => {
if (callIndex === 0) {
const message = createAssistantMessage(
[
{ type: "toolCall", id: "tool-1", name: "echo", arguments: { value: "first" } },
{ type: "toolCall", id: "tool-2", name: "echo", arguments: { value: "second" } },
],
"toolUse",
);
mockStream.push({ type: "done", reason: "toolUse", message });
setTimeout(() => releaseFirst?.(), 20);
} else {
const message = createAssistantMessage([{ type: "text", text: "done" }]);
mockStream.push({ type: "done", reason: "stop", message });
}
callIndex++;
});
return mockStream;
});
const events: AgentEvent[] = [];
for await (const event of stream) {
events.push(event);
}
const toolResultIds = events.flatMap((event) => {
if (event.type !== "message_end" || event.message.role !== "toolResult") {
return [];
}
return [event.message.toolCallId];
});
expect(parallelObserved).toBe(true);
expect(toolResultIds).toEqual(["tool-1", "tool-2"]);
});
it("should inject queued messages after all tool calls complete", async () => {
const toolSchema = Type.Object({ value: Type.String() });
const executed: string[] = [];
const tool: AgentTool<typeof toolSchema, { value: string }> = {
name: "echo",
label: "Echo",
description: "Echo tool",
parameters: toolSchema,
async execute(_toolCallId, params) {
executed.push(params.value);
return {
content: [{ type: "text", text: `ok:${params.value}` }],
details: { value: params.value },
};
},
};
const context: AgentContext = {
systemPrompt: "",
messages: [],
tools: [tool],
};
const userPrompt: AgentMessage = createUserMessage("start");
const queuedUserMessage: AgentMessage = createUserMessage("interrupt");
let queuedDelivered = false;
let callIndex = 0;
let sawInterruptInContext = false;
const config: AgentLoopConfig = {
model: createModel(),
convertToLlm: identityConverter,
toolExecution: "sequential",
getSteeringMessages: async () => {
// Return steering message after tool execution has started.
if (executed.length >= 1 && !queuedDelivered) {
queuedDelivered = true;
return [queuedUserMessage];
}
return [];
},
};
const events: AgentEvent[] = [];
const stream = agentLoop([userPrompt], context, config, undefined, (_model, ctx, _options) => {
// Check if interrupt message is in context on second call
if (callIndex === 1) {
sawInterruptInContext = ctx.messages.some(
(m) => m.role === "user" && typeof m.content === "string" && m.content === "interrupt",
);
}
const mockStream = new MockAssistantStream();
queueMicrotask(() => {
if (callIndex === 0) {
// First call: return two tool calls
const message = createAssistantMessage(
[
{ type: "toolCall", id: "tool-1", name: "echo", arguments: { value: "first" } },
{ type: "toolCall", id: "tool-2", name: "echo", arguments: { value: "second" } },
],
"toolUse",
);
mockStream.push({ type: "done", reason: "toolUse", message });
} else {
// Second call: return final response
const message = createAssistantMessage([{ type: "text", text: "done" }]);
mockStream.push({ type: "done", reason: "stop", message });
}
callIndex++;
});
return mockStream;
});
for await (const event of stream) {
events.push(event);
}
// Both tools should execute before steering is injected
expect(executed).toEqual(["first", "second"]);
const toolEnds = events.filter(
(e): e is Extract<AgentEvent, { type: "tool_execution_end" }> => e.type === "tool_execution_end",
);
expect(toolEnds.length).toBe(2);
expect(toolEnds[0].isError).toBe(false);
expect(toolEnds[1].isError).toBe(false);
// Queued message should appear in events after both tool result messages
const eventSequence = events.flatMap((event) => {
if (event.type !== "message_start") return [];
if (event.message.role === "toolResult") return [`tool:${event.message.toolCallId}`];
if (event.message.role === "user" && typeof event.message.content === "string") {
return [event.message.content];
}
return [];
});
expect(eventSequence).toContain("interrupt");
expect(eventSequence.indexOf("tool:tool-1")).toBeLessThan(eventSequence.indexOf("interrupt"));
expect(eventSequence.indexOf("tool:tool-2")).toBeLessThan(eventSequence.indexOf("interrupt"));
// Interrupt message should be in context when second LLM call is made
expect(sawInterruptInContext).toBe(true);
});
});
describe("agentLoopContinue with AgentMessage", () => {
it("should throw when context has no messages", () => {
const context: AgentContext = {
systemPrompt: "You are helpful.",
messages: [],
tools: [],
};
const config: AgentLoopConfig = {
model: createModel(),
convertToLlm: identityConverter,
};
expect(() => agentLoopContinue(context, config)).toThrow("Cannot continue: no messages in context");
});
it("should continue from existing context without emitting user message events", async () => {
const userMessage: AgentMessage = createUserMessage("Hello");
const context: AgentContext = {
systemPrompt: "You are helpful.",
messages: [userMessage],
tools: [],
};
const config: AgentLoopConfig = {
model: createModel(),
convertToLlm: identityConverter,
};
const streamFn = () => {
const stream = new MockAssistantStream();
queueMicrotask(() => {
const message = createAssistantMessage([{ type: "text", text: "Response" }]);
stream.push({ type: "done", reason: "stop", message });
});
return stream;
};
const events: AgentEvent[] = [];
const stream = agentLoopContinue(context, config, undefined, streamFn);
for await (const event of stream) {
events.push(event);
}
const messages = await stream.result();
// Should only return the new assistant message (not the existing user message)
expect(messages.length).toBe(1);
expect(messages[0].role).toBe("assistant");
// Should NOT have user message events (that's the key difference from agentLoop)
const messageEndEvents = events.filter((e) => e.type === "message_end");
expect(messageEndEvents.length).toBe(1);
expect((messageEndEvents[0] as any).message.role).toBe("assistant");
});
it("should allow custom message types as last message (caller responsibility)", async () => {
// Custom message that will be converted to user message by convertToLlm
interface CustomMessage {
role: "custom";
text: string;
timestamp: number;
}
const customMessage: CustomMessage = {
role: "custom",
text: "Hook content",
timestamp: Date.now(),
};
const context: AgentContext = {
systemPrompt: "You are helpful.",
messages: [customMessage as unknown as AgentMessage],
tools: [],
};
const config: AgentLoopConfig = {
model: createModel(),
convertToLlm: (messages) => {
// Convert custom to user message
return messages
.map((m) => {
if ((m as any).role === "custom") {
return {
role: "user" as const,
content: (m as any).text,
timestamp: m.timestamp,
};
}
return m;
})
.filter((m) => m.role === "user" || m.role === "assistant" || m.role === "toolResult") as Message[];
},
};
const streamFn = () => {
const stream = new MockAssistantStream();
queueMicrotask(() => {
const message = createAssistantMessage([{ type: "text", text: "Response to custom message" }]);
stream.push({ type: "done", reason: "stop", message });
});
return stream;
};
// Should not throw - the custom message will be converted to user message
const stream = agentLoopContinue(context, config, undefined, streamFn);
const events: AgentEvent[] = [];
for await (const event of stream) {
events.push(event);
}
const messages = await stream.result();
expect(messages.length).toBe(1);
expect(messages[0].role).toBe("assistant");
});
});
================================================
FILE: packages/agent/test/agent.test.ts
================================================
import { type AssistantMessage, type AssistantMessageEvent, EventStream, getModel } from "@mariozechner/pi-ai";
import { describe, expect, it } from "vitest";
import { Agent } from "../src/index.js";
// Mock stream that mimics AssistantMessageEventStream
class MockAssistantStream extends EventStream<AssistantMessageEvent, AssistantMessage> {
constructor() {
super(
(event) => event.type === "done" || event.type === "error",
(event) => {
if (event.type === "done") return event.message;
if (event.type === "error") return event.error;
throw new Error("Unexpected event type");
},
);
}
}
function createAssistantMessage(text: string): AssistantMessage {
return {
role: "assistant",
content: [{ type: "text", text }],
api: "openai-responses",
provider: "openai",
model: "mock",
usage: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
stopReason: "stop",
timestamp: Date.now(),
};
}
describe("Agent", () => {
it("should create an agent instance with default state", () => {
const agent = new Agent();
expect(agent.state).toBeDefined();
expect(agent.state.systemPrompt).toBe("");
expect(agent.state.model).toBeDefined();
expect(agent.state.thinkingLevel).toBe("off");
expect(agent.state.tools).toEqual([]);
expect(agent.state.messages).toEqual([]);
expect(agent.state.isStreaming).toBe(false);
expect(agent.state.streamMessage).toBe(null);
expect(agent.state.pendingToolCalls).toEqual(new Set());
expect(agent.state.error).toBeUndefined();
});
it("should create an agent instance with custom initial state", () => {
const customModel = getModel("openai", "gpt-4o-mini");
const agent = new Agent({
initialState: {
systemPrompt: "You are a helpful assistant.",
model: customModel,
thinkingLevel: "low",
},
});
expect(agent.state.systemPrompt).toBe("You are a helpful assistant.");
expect(agent.state.model).toBe(customModel);
expect(agent.state.thinkingLevel).toBe("low");
});
it("should subscribe to events", () => {
const agent = new Agent();
let eventCount = 0;
const unsubscribe = agent.subscribe((_event) => {
eventCount++;
});
// No initial event on subscribe
expect(eventCount).toBe(0);
// State mutators don't emit events
agent.setSystemPrompt("Test prompt");
expect(eventCount).toBe(0);
expect(agent.state.systemPrompt).toBe("Test prompt");
// Unsubscribe should work
unsubscribe();
agent.setSystemPrompt("Another prompt");
expect(eventCount).toBe(0); // Should not increase
});
it("should update state with mutators", () => {
const agent = new Agent();
// Test setSystemPrompt
agent.setSystemPrompt("Custom prompt");
expect(agent.state.systemPrompt).toBe("Custom prompt");
// Test setModel
const newModel = getModel("google", "gemini-2.5-flash");
agent.setModel(newModel);
expect(agent.state.model).toBe(newModel);
// Test setThinkingLevel
agent.setThinkingLevel("high");
expect(agent.state.thinkingLevel).toBe("high");
// Test setTools
const tools = [{ name: "test", description: "test tool" } as any];
agent.setTools(tools);
expect(agent.state.tools).toBe(tools);
// Test replaceMessages
const messages = [{ role: "user" as const, content: "Hello", timestamp: Date.now() }];
agent.replaceMessages(messages);
expect(agent.state.messages).toEqual(messages);
expect(agent.state.messages).not.toBe(messages); // Should be a copy
// Test appendMessage
const newMessage = { role: "assistant" as const, content: [{ type: "text" as const, text: "Hi" }] };
agent.appendMessage(newMessage as any);
expect(agent.state.messages).toHaveLength(2);
expect(agent.state.messages[1]).toBe(newMessage);
// Test clearMessages
agent.clearMessages();
expect(agent.state.messages).toEqual([]);
});
it("should support steering message queue", async () => {
const agent = new Agent();
const message = { role: "user" as const, content: "Steering message", timestamp: Date.now() };
agent.steer(message);
// The message is queued but not yet in state.messages
expect(agent.state.messages).not.toContainEqual(message);
});
it("should support follow-up message queue", async () => {
const agent = new Agent();
const message = { role: "user" as const, content: "Follow-up message", timestamp: Date.now() };
agent.followUp(message);
// The message is queued but not yet in state.messages
expect(agent.state.messages).not.toContainEqual(message);
});
it("should handle abort controller", () => {
const agent = new Agent();
// Should not throw even if nothing is running
expect(() => agent.abort()).not.toThrow();
});
it("should throw when prompt() called while streaming", async () => {
let abortSignal: AbortSignal | undefined;
const agent = new Agent({
// Use a stream function that responds to abort
streamFn: (_model, _context, options) => {
abortSignal = options?.signal;
const stream = new MockAssistantStream();
queueMicrotask(() => {
stream.push({ type: "start", partial: createAssistantMessage("") });
// Check abort signal periodically
const checkAbort = () => {
if (abortSignal?.aborted) {
stream.push({ type: "error", reason: "aborted", error: createAssistantMessage("Aborted") });
} else {
setTimeout(checkAbort, 5);
}
};
checkAbort();
});
return stream;
},
});
// Start first prompt (don't await, it will block until abort)
const firstPrompt = agent.prompt("First message");
// Wait a tick for isStreaming to be set
await new Promise((resolve) => setTimeout(resolve, 10));
expect(agent.state.isStreaming).toBe(true);
// Second prompt should reject
await expect(agent.prompt("Second message")).rejects.toThrow(
"Agent is already processing a prompt. Use steer() or followUp() to queue messages, or wait for completion.",
);
// Cleanup - abort to stop the stream
agent.abort();
await firstPrompt.catch(() => {}); // Ignore abort error
});
it("should throw when continue() called while streaming", async () => {
let abortSignal: AbortSignal | undefined;
const agent = new Agent({
streamFn: (_model, _context, options) => {
abortSignal = options?.signal;
const stream = new MockAssistantStream();
queueMicrotask(() => {
stream.push({ type: "start", partial: createAssistantMessage("") });
const checkAbort = () => {
if (abortSignal?.aborted) {
stream.push({ type: "error", reason: "aborted", error: createAssistantMessage("Aborted") });
} else {
setTimeout(checkAbort, 5);
}
};
checkAbort();
});
return stream;
},
});
// Start first prompt
const firstPrompt = agent.prompt("First message");
await new Promise((resolve) => setTimeout(resolve, 10));
expect(agent.state.isStreaming).toBe(true);
// continue() should reject
await expect(agent.continue()).rejects.toThrow(
"Agent is already processing. Wait for completion before continuing.",
);
// Cleanup
agent.abort();
await firstPrompt.catch(() => {});
});
it("continue() should process queued follow-up messages after an assistant turn", async () => {
const agent = new Agent({
streamFn: () => {
const stream = new MockAssistantStream();
queueMicrotask(() => {
stream.push({ type: "done", reason: "stop", message: createAssistantMessage("Processed") });
});
return stream;
},
});
agent.replaceMessages([
{
role: "user",
content: [{ type: "text", text: "Initial" }],
timestamp: Date.now() - 10,
},
createAssistantMessage("Initial response"),
]);
agent.followUp({
role: "user",
content: [{ type: "text", text: "Queued follow-up" }],
timestamp: Date.now(),
});
await expect(agent.continue()).resolves.toBeUndefined();
const hasQueuedFollowUp = agent.state.messages.some((message) => {
if (message.role !== "user") return false;
if (typeof message.content === "string") return message.content === "Queued follow-up";
return message.content.some((part) => part.type === "text" && part.text === "Queued follow-up");
});
expect(hasQueuedFollowUp).toBe(true);
expect(agent.state.messages[agent.state.messages.length - 1].role).toBe("assistant");
});
it("continue() should keep one-at-a-time steering semantics from assistant tail", async () => {
let responseCount = 0;
const agent = new Agent({
streamFn: () => {
const stream = new MockAssistantStream();
responseCount++;
queueMicrotask(() => {
stream.push({
type: "done",
reason: "stop",
message: createAssistantMessage(`Processed ${responseCount}`),
});
});
return stream;
},
});
agent.replaceMessages([
{
role: "user",
content: [{ type: "text", text: "Initial" }],
timestamp: Date.now() - 10,
},
createAssistantMessage("Initial response"),
]);
agent.steer({
role: "user",
content: [{ type: "text", text: "Steering 1" }],
timestamp: Date.now(),
});
agent.steer({
role: "user",
content: [{ type: "text", text: "Steering 2" }],
timestamp: Date.now() + 1,
});
await expect(agent.continue()).resolves.toBeUndefined();
const recentMessages = agent.state.messages.slice(-4);
expect(recentMessages.map((m) => m.role)).toEqual(["user", "assistant", "user", "assistant"]);
expect(responseCount).toBe(2);
});
it("forwards sessionId to streamFn options", async () => {
let receivedSessionId: string | undefined;
const agent = new Agent({
sessionId: "session-abc",
streamFn: (_model, _context, options) => {
receivedSessionId = options?.sessionId;
const stream = new MockAssistantStream();
queueMicrotask(() => {
const message = createAssistantMessage("ok");
stream.push({ type: "done", reason: "stop", message });
});
return stream;
},
});
await agent.prompt("hello");
expect(receivedSessionId).toBe("session-abc");
// Test setter
agent.sessionId = "session-def";
expect(agent.sessionId).toBe("session-def");
await agent.prompt("hello again");
expect(receivedSessionId).toBe("session-def");
});
});
================================================
FILE: packages/agent/test/bedrock-models.test.ts
================================================
/**
* A test suite to ensure Amazon Bedrock models work correctly with the agent loop.
*
* Some Bedrock models don't support all features (e.g., reasoning signatures).
* This test suite verifies that the agent loop works with various Bedrock models.
*
* This test suite is not enabled by default unless AWS credentials and
* `BEDROCK_EXTENSIVE_MODEL_TEST` environment variables are set.
*
* You can run this test suite with:
* ```bash
* $ AWS_REGION=us-east-1 BEDROCK_EXTENSIVE_MODEL_TEST=1 AWS_PROFILE=pi npm test -- ./test/bedrock-models.test.ts
* ```
*
* ## Known Issues by Category
*
* 1. **Inference Profile Required**: Some models require an inference profile ARN instead of on-demand.
* 2. **Invalid Model ID**: Model identifiers that don't exist in the current region.
* 3. **Max Tokens Exceeded**: Model's maxTokens in our config exceeds the actual limit.
* 4. **No Reasoning in User Messages**: Model rejects reasoning content when replayed in conversation.
* 5. **Invalid Signature Format**: Model validates signature format (Anthropic newer models).
*/
import type { AssistantMessage } from "@mariozechner/pi-ai";
import { getModels } from "@mariozechner/pi-ai";
import { describe, expect, it } from "vitest";
import { Agent } from "../src/index.js";
import { hasBedrockCredentials } from "./bedrock-utils.js";
// =============================================================================
// Known Issue Categories
// =============================================================================
/** Models that require inference profile ARN (not available on-demand in us-east-1) */
const REQUIRES_INFERENCE_PROFILE = new Set([
"anthropic.claude-3-5-haiku-20241022-v1:0",
"anthropic.claude-3-5-sonnet-20241022-v2:0",
"anthropic.claude-3-opus-20240229-v1:0",
"meta.llama3-1-70b-instruct-v1:0",
"meta.llama3-1-8b-instruct-v1:0",
]);
/** Models with invalid identifiers (not available in us-east-1 or don't exist) */
const INVALID_MODEL_ID = new Set([
"deepseek.v3-v1:0",
"eu.anthropic.claude-haiku-4-5-20251001-v1:0",
"eu.anthropic.claude-opus-4-5-20251101-v1:0",
"eu.anthropic.claude-sonnet-4-5-20250929-v1:0",
"qwen.qwen3-235b-a22b-2507-v1:0",
"qwen.qwen3-coder-480b-a35b-v1:0",
]);
/** Models where our maxTokens config exceeds the model's actual limit */
const MAX_TOKENS_EXCEEDED = new Set([
"us.meta.llama4-maverick-17b-instruct-v1:0",
"us.meta.llama4-scout-17b-instruct-v1:0",
]);
/**
* Models that reject reasoning content in user messages (when replaying conversation).
* These work for multi-turn but fail when synthetic thinking is injected.
*/
const NO_REASONING_IN_USER_MESSAGES = new Set([
// Mistral models
"mistral.ministral-3-14b-instruct",
"mistral.ministral-3-8b-instruct",
"mistral.mistral-large-2402-v1:0",
"mistral.voxtral-mini-3b-2507",
"mistral.voxtral-small-24b-2507",
// Nvidia models
"nvidia.nemotron-nano-12b-v2",
"nvidia.nemotron-nano-9b-v2",
// Qwen models
"qwen.qwen3-coder-30b-a3b-v1:0",
// Amazon Nova models
"us.amazon.nova-lite-v1:0",
"us.amazon.nova-micro-v1:0",
"us.amazon.nova-premier-v1:0",
"us.amazon.nova-pro-v1:0",
// Meta Llama models
"us.meta.llama3-2-11b-instruct-v1:0",
"us.meta.llama3-2-1b-instruct-v1:0",
"us.meta.llama3-2-3b-instruct-v1:0",
"us.meta.llama3-2-90b-instruct-v1:0",
"us.meta.llama3-3-70b-instruct-v1:0",
// DeepSeek
"us.deepseek.r1-v1:0",
// Older Anthropic models
"anthropic.claude-3-5-sonnet-20240620-v1:0",
"anthropic.claude-3-haiku-20240307-v1:0",
"anthropic.claude-3-sonnet-20240229-v1:0",
// Cohere models
"cohere.command-r-plus-v1:0",
"cohere.command-r-v1:0",
// Google models
"google.gemma-3-27b-it",
"google.gemma-3-4b-it",
// Non-Anthropic models that don't support signatures (now handled by omitting signature)
// but still reject reasoning content in user messages
"global.amazon.nova-2-lite-v1:0",
"minimax.minimax-m2",
"moonshot.kimi-k2-thinking",
"openai.gpt-oss-120b-1:0",
"openai.gpt-oss-20b-1:0",
"openai.gpt-oss-safeguard-120b",
"openai.gpt-oss-safeguard-20b",
"qwen.qwen3-32b-v1:0",
"qwen.qwen3-next-80b-a3b",
"qwen.qwen3-vl-235b-a22b",
]);
/**
* Models that validate signature format (Anthropic newer models).
* These work for multi-turn but fail when synthetic/invalid signature is injected.
*/
const VALIDATES_SIGNATURE_FORMAT = new Set([
"global.anthropic.claude-haiku-4-5-20251001-v1:0",
"global.anthropic.claude-opus-4-5-20251101-v1:0",
"global.anthropic.claude-sonnet-4-20250514-v1:0",
"global.anthropic.claude-sonnet-4-5-20250929-v1:0",
"us.anthropic.claude-3-7-sonnet-20250219-v1:0",
"us.anthropic.claude-opus-4-1-20250805-v1:0",
"us.anthropic.claude-opus-4-20250514-v1:0",
]);
/**
* DeepSeek R1 fails multi-turn because it rejects reasoning in the replayed assistant message.
*/
const REJECTS_REASONING_ON_REPLAY = new Set(["us.deepseek.r1-v1:0"]);
// =============================================================================
// Helper Functions
// =============================================================================
function isModelUnavailable(modelId: string): boolean {
return REQUIRES_INFERENCE_PROFILE.has(modelId) || INVALID_MODEL_ID.has(modelId) || MAX_TOKENS_EXCEEDED.has(modelId);
}
function failsMultiTurnWithThinking(modelId: string): boolean {
return REJECTS_REASONING_ON_REPLAY.has(modelId);
}
function failsSyntheticSignature(modelId: string): boolean {
return NO_REASONING_IN_USER_MESSAGES.has(modelId) || VALIDATES_SIGNATURE_FORMAT.has(modelId);
}
// =============================================================================
// Tests
// =============================================================================
describe("Amazon Bedrock Models - Agent Loop", () => {
const shouldRunExtensiveTests = hasBedrockCredentials() && process.env.BEDROCK_EXTENSIVE_MODEL_TEST;
// Get all Amazon Bedrock models
const allBedrockModels = getModels("amazon-bedrock");
if (shouldRunExtensiveTests) {
for (const model of allBedrockModels) {
const modelId = model.id;
describe(`Model: ${modelId}`, () => {
// Skip entirely unavailable models
const unavailable = isModelUnavailable(modelId);
it.skipIf(unavailable)("should handle basic text prompt", { timeout: 60_000 }, async () => {
const agent = new Agent({
initialState: {
systemPrompt: "You are a helpful assistant. Be extremely concise.",
model,
thinkingLevel: "off",
tools: [],
},
});
await agent.prompt("Reply with exactly: 'OK'");
if (agent.state.error) {
throw new Error(`Basic prompt error: ${agent.state.error}`);
}
expect(agent.state.isStreaming).toBe(false);
expect(agent.state.messages.length).toBe(2);
const assistantMessage = agent.state.messages[1];
if (assistantMessage.role !== "assistant") throw new Error("Expected assistant message");
console.log(`${modelId}: OK`);
});
// Skip if model is unavailable or known to fail multi-turn with thinking
const skipMultiTurn = unavailable || failsMultiTurnWithThinking(modelId);
it.skipIf(skipMultiTurn)(
"should handle multi-turn conversation with thinking content in history",
{ timeout: 120_000 },
async () => {
const agent = new Agent({
initialState: {
systemPrompt: "You are a helpful assistant. Be extremely concise.",
model,
thinkingLevel: "medium",
tools: [],
},
});
// First turn
await agent.prompt("My name is Alice.");
if (agent.state.error) {
throw new Error(`First turn error: ${agent.state.error}`);
}
// Second turn - this should replay the first assistant message which may contain thinking
await agent.prompt("What is my name?");
if (agent.state.error) {
throw new Error(`Second turn error: ${agent.state.error}`);
}
expect(agent.state.messages.length).toBe(4);
console.log(`${modelId}: multi-turn OK`);
},
);
// Skip if model is unavailable or known to fail synthetic signature
const skipSynthetic = unavailable || failsSyntheticSignature(modelId);
it.skipIf(skipSynthetic)(
"should handle conversation with synthetic thinking signature in history",
{ timeout: 60_000 },
async () => {
const agent = new Agent({
initialState: {
systemPrompt: "You are a helpful assistant. Be extremely concise.",
model,
thinkingLevel: "off",
tools: [],
},
});
// Inject a message with a thinking block that has a signature
const syntheticAssistantMessage: AssistantMessage = {
role: "assistant",
content: [
{
type: "thinking",
thinking: "I need to remember the user's name.",
thinkingSignature: "synthetic-signature-123",
},
{ type: "text", text: "Nice to meet you, Alice!" },
],
api: "bedrock-converse-stream",
provider: "amazon-bedrock",
model: modelId,
usage: {
input: 10,
output: 20,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 30,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
stopReason: "stop",
timestamp: Date.now(),
};
agent.replaceMessages([
{ role: "user", content: "My name is Alice.", timestamp: Date.now() },
syntheticAssistantMessage,
]);
await agent.prompt("What is my name?");
if (agent.state.error) {
throw new Error(`Synthetic signature error: ${agent.state.error}`);
}
expect(agent.state.messages.length).toBe(4);
console.log(`${modelId}: synthetic signature OK`);
},
);
});
}
} else {
it.skip("skipped - set AWS credentials and BEDROCK_EXTENSIVE_MODEL_TEST=1 to run", () => {});
}
});
================================================
FILE: packages/agent/test/bedrock-utils.ts
================================================
/**
* Utility functions for Amazon Bedrock tests
*/
/**
* Check if any valid AWS credentials are configured for Bedrock.
* Returns true if any of the following are set:
* - AWS_PROFILE (named profile from ~/.aws/credentials)
* - AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY (IAM keys)
* - AWS_BEARER_TOKEN_BEDROCK (Bedrock API key)
*/
export function hasBedrockCredentials(): boolean {
return !!(
process.env.AWS_PROFILE ||
(process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) ||
process.env.AWS_BEARER_TOKEN_BEDROCK
);
}
================================================
FILE: packages/agent/test/e2e.test.ts
================================================
import type { AssistantMessage, Model, ToolResultMessage, UserMessage } from "@mariozechner/pi-ai";
import { getModel } from "@mariozechner/pi-ai";
import { describe, expect, it } from "vitest";
import { Agent } from "../src/index.js";
import { hasBedrockCredentials } from "./bedrock-utils.js";
import { calculateTool } from "./utils/calculate.js";
delete process.env.ANTHROPIC_OAUTH_TOKEN;
async function basicPrompt(model: Model<any>) {
const agent = new Agent({
initialState: {
systemPrompt: "You are a helpful assistant. Keep your responses concise.",
model,
thinkingLevel: "off",
tools: [],
},
});
await agent.prompt("What is 2+2? Answer with just the number.");
expect(agent.state.isStreaming).toBe(false);
expect(agent.state.messages.length).toBe(2);
expect(agent.state.messages[0].role).toBe("user");
expect(agent.state.messages[1].role).toBe("assistant");
const assistantMessage = agent.state.messages[1];
if (assistantMessage.role !== "assistant") throw new Error("Expected assistant message");
expect(assistantMessage.content.length).toBeGreaterThan(0);
const textContent = assistantMessage.content.find((c) => c.type === "text");
expect(textContent).toBeDefined();
if (textContent?.type !== "text") throw new Error("Expected text content");
expect(textContent.text).toContain("4");
}
async function toolExecution(model: Model<any>) {
const agent = new Agent({
initialState: {
systemPrompt: "You are a helpful assistant. Always use the calculator tool for math.",
model,
thinkingLevel: "off",
tools: [calculateTool],
},
});
await agent.prompt("Calculate 123 * 456 using the calculator tool.");
expect(agent.state.isStreaming).toBe(false);
expect(agent.state.messages.length).toBeGreaterThanOrEqual(3);
const toolResultMsg = agent.state.messages.find((m) => m.role === "toolResult");
expect(toolResultMsg).toBeDefined();
if (toolResultMsg?.role !== "toolResult") throw new Error("Expected tool result message");
const textContent =
toolResultMsg.content
?.filter((c) => c.type === "text")
.map((c: any) => c.text)
.join("\n") || "";
expect(textContent).toBeDefined();
const expectedResult = 123 * 456;
expect(textContent).toContain(String(expectedResult));
const finalMessage = agent.state.messages[agent.state.messages.length - 1];
if (finalMessage.role !== "assistant") throw new Error("Expected final assistant message");
const finalText = finalMessage.content.find((c) => c.type === "text");
expect(finalText).toBeDefined();
if (finalText?.type !== "text") throw new Error("Expected text content");
// Check for number with or without comma formatting
const hasNumber =
finalText.text.includes(String(expectedResult)) ||
finalText.text.includes("56,088") ||
finalText.text.includes("56088");
expect(hasNumber).toBe(true);
}
async function abortExecution(model: Model<any>) {
const agent = new Agent({
initialState: {
systemPrompt: "You are a helpful assistant.",
model,
thinkingLevel: "off",
tools: [calculateTool],
},
});
const promptPromise = agent.prompt("Calculate 100 * 200, then 300 * 400, then sum the results.");
setTimeout(() => {
agent.abort();
}, 100);
await promptPromise;
expect(agent.state.isStreaming).toBe(false);
expect(agent.state.messages.length).toBeGreaterThanOrEqual(2);
const lastMessage = agent.state.messages[agent.state.messages.length - 1];
if (lastMessage.role !== "assistant") throw new Error("Expected assistant message");
expect(lastMessage.stopReason).toBe("aborted");
expect(lastMessage.errorMessage).toBeDefined();
expect(agent.state.error).toBeDefined();
expect(agent.state.error).toBe(lastMessage.errorMessage);
}
async function stateUpdates(model: Model<any>) {
const agent = new Agent({
initialState: {
systemPrompt: "You are a helpful assistant.",
model,
thinkingLevel: "off",
tools: [],
},
});
const events: Array<string> = [];
agent.subscribe((event) => {
events.push(event.type);
});
await agent.prompt("Count from 1 to 5.");
// Should have received lifecycle events
expect(events).toContain("agent_start");
expect(events).toContain("agent_end");
expect(events).toContain("message_start");
expect(events).toContain("message_end");
// May have message_update events during streaming
const hasMessageUpdates = events.some((e) => e === "message_update");
expect(hasMessageUpdates).toBe(true);
// Check final state
expect(agent.state.isStreaming).toBe(false);
expect(agent.state.messages.length).toBe(2); // User message + assistant response
}
async function multiTurnConversation(model: Model<any>) {
const agent = new Agent({
initialState: {
systemPrompt: "You are a helpful assistant.",
model,
thinkingLevel: "off",
tools: [],
},
});
await agent.prompt("My name is Alice.");
expect(agent.state.messages.length).toBe(2);
await agent.prompt("What is my name?");
expect(agent.state.messages.length).toBe(4);
const lastMessage = agent.state.messages[3];
if (lastMessage.role !== "assistant") throw new Error("Expected assistant message");
const lastText = lastMessage.content.find((c) => c.type === "text");
if (lastText?.type !== "text") throw new Error("Expected text content");
expect(lastText.text.toLowerCase()).toContain("alice");
}
describe("Agent E2E Tests", () => {
describe.skipIf(!process.env.GEMINI_API_KEY)("Google Provider (gemini-2.5-flash)", () => {
const model = getModel("google", "gemini-2.5-flash");
it("should handle basic text prompt", async () => {
await basicPrompt(model);
});
it("should execute tools correctly", async () => {
await toolExecution(model);
});
it("should handle abort during execution", async () => {
await abortExecution(model);
});
it("should emit state updates during streaming", async () => {
await stateUpdates(model);
});
it("should maintain context across multiple turns", async () => {
await multiTurnConversation(model);
});
});
describe.skipIf(!process.env.OPENAI_API_KEY)("OpenAI Provider (gpt-4o-mini)", () => {
const model = getModel("openai", "gpt-4o-mini");
it("should handle basic text prompt", async () => {
await basicPrompt(model);
});
it("should execute tools correctly", async () => {
await toolExecution(model);
});
it("should handle abort during execution", async () => {
await abortExecution(model);
});
it("should emit state updates during streaming", async () => {
await stateUpdates(model);
});
it("should maintain context across multiple turns", async () => {
await multiTurnConversation(model);
});
});
describe.skipIf(!process.env.ANTHROPIC_API_KEY)("Anthropic Provider (claude-haiku-4-5)", () => {
const model = getModel("anthropic", "claude-haiku-4-5");
it("should handle basic text prompt", async () => {
await basicPrompt(model);
});
it("should execute tools correctly", async () => {
await toolExecution(model);
});
it("should handle abort during execution", async () => {
await abortExecution(model);
});
it("should emit state updates during streaming", async () => {
await stateUpdates(model);
});
it("should maintain context across multiple turns", async () => {
await multiTurnConversation(model);
});
});
describe.skipIf(!process.env.XAI_API_KEY)("xAI Provider (grok-3)", () => {
const model = getModel("xai", "grok-3");
it("should handle basic text prompt", async () => {
await basicPrompt(model);
});
it("should execute tools correctly", async () => {
await toolExecution(model);
});
it("should handle abort during execution", async () => {
await abortExecution(model);
});
it("should emit state updates during streaming", async () => {
await stateUpdates(model);
});
it("should maintain context across multiple turns", async () => {
await multiTurnConversation(model);
});
});
describe.skipIf(!process.env.GROQ_API_KEY)("Groq Provider (openai/gpt-oss-20b)", () => {
const model = getModel("groq", "openai/gpt-oss-20b");
it("should handle basic text prompt", async () => {
await basicPrompt(model);
});
it("should execute tools correctly", async () => {
await toolExecution(model);
});
it("should handle abort during execution", async () => {
await abortExecution(model);
});
it("should emit state updates during streaming", async () => {
await stateUpdates(model);
});
it("should maintain context across multiple turns", async () => {
await multiTurnConversation(model);
});
});
/*describe.skipIf(!process.env.CEREBRAS_API_KEY)("Cerebras Provider (gpt-oss-120b)", () => {
const model = getModel("cerebras", "gpt-oss-120b");
it("should handle basic text prompt", async () => {
await basicPrompt(model);
});
it("should execute tools correctly", async () => {
await toolExecution(model);
});
it("should handle abort during execution", async () => {
await abortExecution(model);
});
it("should emit state updates during streaming", async () => {
await stateUpdates(model);
});
it("should maintain context across multiple turns", async () => {
await multiTurnConversation(model);
});
});*/
describe.skipIf(!process.env.ZAI_API_KEY)("zAI Provider (glm-4.5-air)", () => {
const model = getModel("zai", "glm-4.5-air");
it("should handle basic text prompt", async () => {
await basicPrompt(model);
});
it("should execute tools correctly", async () => {
await toolExecution(model);
});
it("should handle abort during execution", async () => {
await abortExecution(model);
});
it("should emit state updates during streaming", async () => {
await stateUpdates(model);
});
it("should maintain context across multiple turns", async () => {
await multiTurnConversation(model);
});
});
describe.skipIf(!hasBedrockCredentials())("Amazon Bedrock Provider (claude-sonnet-4-5)", () => {
const model = getModel("amazon-bedrock", "global.anthropic.claude-sonnet-4-5-20250929-v1:0");
it("should handle basic text prompt", async () => {
await basicPrompt(model);
});
it("should execute tools correctly", async () => {
await toolExecution(model);
});
it("should handle abort during execution", async () => {
await abortExecution(model);
});
it("should emit state updates during streaming", async () => {
await stateUpdates(model);
});
it("should maintain context across multiple turns", async () => {
await multiTurnConversation(model);
});
});
});
describe("Agent.continue()", () => {
describe("validation", () => {
it("should throw when no messages in context", async () => {
const agent = new Agent({
initialState: {
systemPrompt: "Test",
model: getModel("openai", "gpt-5.4"),
},
});
await expect(agent.continue()).rejects.toThrow("No messages to continue from");
});
it("should throw when last message is assistant", async () => {
const agent = new Agent({
initialState: {
systemPrompt: "Test",
model: getModel("openai", "gpt-5.4"),
},
});
const assistantMessage: AssistantMessage = {
role: "assistant",
content: [{ type: "text", text: "Hello" }],
api: "openai-responses",
provider: "openai",
model: "gpt-5.4",
usage: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
stopReason: "stop",
timestamp: Date.now(),
};
agent.replaceMessages([assistantMessage]);
await expect(agent.continue()).rejects.toThrow("Cannot continue from message role: assistant");
});
});
describe.skipIf(!process.env.OPENAI_API_KEY)("continue from user message", () => {
const model = getModel("openai", "gpt-5.4");
it("should continue and get response when last message is user", async () => {
const agent = new Agent({
initialState: {
systemPrompt: "You are a helpful assistant. Follow instructions exactly.",
model,
thinkingLevel: "off",
tools: [],
},
});
// Manually add a user message without calling prompt()
const userMessage: UserMessage = {
role: "user",
content: [{ type: "text", text: "Say exactly: HELLO WORLD" }],
timestamp: Date.now(),
};
agent.replaceMessages([userMessage]);
// Continue from the user message
await agent.continue();
expect(agent.state.isStreaming).toBe(false);
expect(agent.state.messages.length).toBe(2);
expect(agent.state.messages[0].role).toBe("user");
expect(agent.state.messages[1].role).toBe("assistant");
const assistantMsg = agent.state.messages[1] as AssistantMessage;
const textContent = assistantMsg.content.find((c) => c.type === "text");
expect(textContent).toBeDefined();
if (textContent?.type === "text") {
expect(textContent.text.toUpperCase()).toContain("HELLO WORLD");
}
});
});
describe.skipIf(!process.env.OPENAI_
gitextract_w7t9d370/ ├── .gitattributes ├── .github/ │ ├── APPROVED_CONTRIBUTORS │ ├── APPROVED_CONTRIBUTORS.vacation │ ├── ISSUE_TEMPLATE/ │ │ ├── bug.yml │ │ ├── config.yml │ │ └── contribution.yml │ └── workflows/ │ ├── approve-contributor.yml │ ├── build-binaries.yml │ ├── ci.yml │ ├── oss-weekend-issues.yml │ └── pr-gate.yml ├── .gitignore ├── .husky/ │ └── pre-commit ├── .pi/ │ ├── extensions/ │ │ ├── diff.ts │ │ ├── files.ts │ │ ├── prompt-url-widget.ts │ │ ├── redraws.ts │ │ └── tps.ts │ ├── git/ │ │ └── .gitignore │ ├── npm/ │ │ └── .gitignore │ └── prompts/ │ ├── cl.md │ ├── is.md │ └── pr.md ├── AGENTS.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── biome.json ├── package.json ├── packages/ │ ├── agent/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── agent-loop.ts │ │ │ ├── agent.ts │ │ │ ├── index.ts │ │ │ ├── proxy.ts │ │ │ └── types.ts │ │ ├── test/ │ │ │ ├── agent-loop.test.ts │ │ │ ├── agent.test.ts │ │ │ ├── bedrock-models.test.ts │ │ │ ├── bedrock-utils.ts │ │ │ ├── e2e.test.ts │ │ │ └── utils/ │ │ │ ├── calculate.ts │ │ │ └── get-current-time.ts │ │ ├── tsconfig.build.json │ │ └── vitest.config.ts │ ├── ai/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── bedrock-provider.d.ts │ │ ├── bedrock-provider.js │ │ ├── package.json │ │ ├── scripts/ │ │ │ ├── generate-models.ts │ │ │ └── generate-test-image.ts │ │ ├── src/ │ │ │ ├── api-registry.ts │ │ │ ├── bedrock-provider.ts │ │ │ ├── cli.ts │ │ │ ├── env-api-keys.ts │ │ │ ├── index.ts │ │ │ ├── models.generated.ts │ │ │ ├── models.ts │ │ │ ├── oauth.ts │ │ │ ├── providers/ │ │ │ │ ├── amazon-bedrock.ts │ │ │ │ ├── anthropic.ts │ │ │ │ ├── azure-openai-responses.ts │ │ │ │ ├── github-copilot-headers.ts │ │ │ │ ├── google-gemini-cli.ts │ │ │ │ ├── google-shared.ts │ │ │ │ ├── google-vertex.ts │ │ │ │ ├── google.ts │ │ │ │ ├── mistral.ts │ │ │ │ ├── openai-codex-responses.ts │ │ │ │ ├── openai-completions.ts │ │ │ │ ├── openai-responses-shared.ts │ │ │ │ ├── openai-responses.ts │ │ │ │ ├── register-builtins.ts │ │ │ │ ├── simple-options.ts │ │ │ │ └── transform-messages.ts │ │ │ ├── stream.ts │ │ │ ├── types.ts │ │ │ └── utils/ │ │ │ ├── event-stream.ts │ │ │ ├── hash.ts │ │ │ ├── json-parse.ts │ │ │ ├── oauth/ │ │ │ │ ├── anthropic.ts │ │ │ │ ├── github-copilot.ts │ │ │ │ ├── google-antigravity.ts │ │ │ │ ├── google-gemini-cli.ts │ │ │ │ ├── index.ts │ │ │ │ ├── oauth-page.ts │ │ │ │ ├── openai-codex.ts │ │ │ │ ├── pkce.ts │ │ │ │ └── types.ts │ │ │ ├── overflow.ts │ │ │ ├── sanitize-unicode.ts │ │ │ ├── typebox-helpers.ts │ │ │ └── validation.ts │ │ ├── test/ │ │ │ ├── abort.test.ts │ │ │ ├── anthropic-oauth.test.ts │ │ │ ├── anthropic-tool-name-normalization.test.ts │ │ │ ├── azure-utils.ts │ │ │ ├── bedrock-models.test.ts │ │ │ ├── bedrock-utils.ts │ │ │ ├── cache-retention.test.ts │ │ │ ├── context-overflow.test.ts │ │ │ ├── cross-provider-handoff.test.ts │ │ │ ├── empty.test.ts │ │ │ ├── github-copilot-anthropic.test.ts │ │ │ ├── github-copilot-oauth.test.ts │ │ │ ├── google-gemini-cli-claude-thinking-header.test.ts │ │ │ ├── google-gemini-cli-empty-stream.test.ts │ │ │ ├── google-gemini-cli-retry-delay.test.ts │ │ │ ├── google-shared-gemini3-unsigned-tool-call.test.ts │ │ │ ├── google-shared-image-tool-result-routing.test.ts │ │ │ ├── google-thinking-signature.test.ts │ │ │ ├── google-tool-call-missing-args.test.ts │ │ │ ├── google-vertex-api-key-resolution.test.ts │ │ │ ├── image-tool-result.test.ts │ │ │ ├── interleaved-thinking.test.ts │ │ │ ├── lazy-module-load.test.ts │ │ │ ├── oauth.ts │ │ │ ├── openai-codex-stream.test.ts │ │ │ ├── openai-completions-tool-choice.test.ts │ │ │ ├── openai-completions-tool-result-images.test.ts │ │ │ ├── openai-responses-reasoning-replay-e2e.test.ts │ │ │ ├── openai-responses-tool-result-images.test.ts │ │ │ ├── responseid.test.ts │ │ │ ├── stream.test.ts │ │ │ ├── supports-xhigh.test.ts │ │ │ ├── tokens.test.ts │ │ │ ├── tool-call-id-normalization.test.ts │ │ │ ├── tool-call-without-result.test.ts │ │ │ ├── total-tokens.test.ts │ │ │ ├── transform-messages-copilot-openai-to-anthropic.test.ts │ │ │ ├── unicode-surrogate.test.ts │ │ │ ├── validation.test.ts │ │ │ ├── xhigh.test.ts │ │ │ └── zen.test.ts │ │ ├── tsconfig.build.json │ │ └── vitest.config.ts │ ├── coding-agent/ │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── docs/ │ │ │ ├── compaction.md │ │ │ ├── custom-provider.md │ │ │ ├── development.md │ │ │ ├── extensions.md │ │ │ ├── json.md │ │ │ ├── keybindings.md │ │ │ ├── models.md │ │ │ ├── packages.md │ │ │ ├── prompt-templates.md │ │ │ ├── providers.md │ │ │ ├── rpc.md │ │ │ ├── sdk.md │ │ │ ├── session.md │ │ │ ├── settings.md │ │ │ ├── shell-aliases.md │ │ │ ├── skills.md │ │ │ ├── terminal-setup.md │ │ │ ├── termux.md │ │ │ ├── themes.md │ │ │ ├── tmux.md │ │ │ ├── tree.md │ │ │ ├── tui.md │ │ │ └── windows.md │ │ ├── examples/ │ │ │ ├── README.md │ │ │ ├── extensions/ │ │ │ │ ├── README.md │ │ │ │ ├── antigravity-image-gen.ts │ │ │ │ ├── auto-commit-on-exit.ts │ │ │ │ ├── bash-spawn-hook.ts │ │ │ │ ├── bookmark.ts │ │ │ │ ├── built-in-tool-renderer.ts │ │ │ │ ├── claude-rules.ts │ │ │ │ ├── commands.ts │ │ │ │ ├── confirm-destructive.ts │ │ │ │ ├── custom-compaction.ts │ │ │ │ ├── custom-footer.ts │ │ │ │ ├── custom-header.ts │ │ │ │ ├── custom-provider-anthropic/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.ts │ │ │ │ │ └── package.json │ │ │ │ ├── custom-provider-gitlab-duo/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── package.json │ │ │ │ │ └── test.ts │ │ │ │ ├── custom-provider-qwen-cli/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.ts │ │ │ │ │ └── package.json │ │ │ │ ├── dirty-repo-guard.ts │ │ │ │ ├── doom-overlay/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── README.md │ │ │ │ │ ├── doom/ │ │ │ │ │ │ ├── build/ │ │ │ │ │ │ │ ├── doom.js │ │ │ │ │ │ │ └── doom.wasm │ │ │ │ │ │ ├── build.sh │ │ │ │ │ │ └── doomgeneric_pi.c │ │ │ │ │ ├── doom-component.ts │ │ │ │ │ ├── doom-engine.ts │ │ │ │ │ ├── doom-keys.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── wad-finder.ts │ │ │ │ ├── dynamic-resources/ │ │ │ │ │ ├── SKILL.md │ │ │ │ │ ├── dynamic.json │ │ │ │ │ ├── dynamic.md │ │ │ │ │ └── index.ts │ │ │ │ ├── dynamic-tools.ts │ │ │ │ ├── event-bus.ts │ │ │ │ ├── file-trigger.ts │ │ │ │ ├── git-checkpoint.ts │ │ │ │ ├── handoff.ts │ │ │ │ ├── hello.ts │ │ │ │ ├── inline-bash.ts │ │ │ │ ├── input-transform.ts │ │ │ │ ├── interactive-shell.ts │ │ │ │ ├── mac-system-theme.ts │ │ │ │ ├── message-renderer.ts │ │ │ │ ├── minimal-mode.ts │ │ │ │ ├── modal-editor.ts │ │ │ │ ├── model-status.ts │ │ │ │ ├── notify.ts │ │ │ │ ├── overlay-qa-tests.ts │ │ │ │ ├── overlay-test.ts │ │ │ │ ├── permission-gate.ts │ │ │ │ ├── pirate.ts │ │ │ │ ├── plan-mode/ │ │ │ │ │ ├── README.md │ │ │ │ │ ├── index.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── preset.ts │ │ │ │ ├── protected-paths.ts │ │ │ │ ├── provider-payload.ts │ │ │ │ ├── qna.ts │ │ │ │ ├── question.ts │ │ │ │ ├── questionnaire.ts │ │ │ │ ├── rainbow-editor.ts │ │ │ │ ├── reload-runtime.ts │ │ │ │ ├── rpc-demo.ts │ │ │ │ ├── sandbox/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── index.ts │ │ │ │ │ └── package.json │ │ │ │ ├── send-user-message.ts │ │ │ │ ├── session-name.ts │ │ │ │ ├── shutdown-command.ts │ │ │ │ ├── snake.ts │ │ │ │ ├── space-invaders.ts │ │ │ │ ├── ssh.ts │ │ │ │ ├── status-line.ts │ │ │ │ ├── subagent/ │ │ │ │ │ ├── README.md │ │ │ │ │ ├── agents/ │ │ │ │ │ │ ├── planner.md │ │ │ │ │ │ ├── reviewer.md │ │ │ │ │ │ ├── scout.md │ │ │ │ │ │ └── worker.md │ │ │ │ │ ├── agents.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── prompts/ │ │ │ │ │ ├── implement-and-review.md │ │ │ │ │ ├── implement.md │ │ │ │ │ └── scout-and-plan.md │ │ │ │ ├── summarize.ts │ │ │ │ ├── system-prompt-header.ts │ │ │ │ ├── timed-confirm.ts │ │ │ │ ├── titlebar-spinner.ts │ │ │ │ ├── todo.ts │ │ │ │ ├── tool-override.ts │ │ │ │ ├── tools.ts │ │ │ │ ├── trigger-compact.ts │ │ │ │ ├── truncated-tool.ts │ │ │ │ ├── widget-placement.ts │ │ │ │ └── with-deps/ │ │ │ │ ├── .gitignore │ │ │ │ ├── index.ts │ │ │ │ └── package.json │ │ │ ├── rpc-extension-ui.ts │ │ │ └── sdk/ │ │ │ ├── 01-minimal.ts │ │ │ ├── 02-custom-model.ts │ │ │ ├── 03-custom-prompt.ts │ │ │ ├── 04-skills.ts │ │ │ ├── 05-tools.ts │ │ │ ├── 06-extensions.ts │ │ │ ├── 07-context-files.ts │ │ │ ├── 08-prompt-templates.ts │ │ │ ├── 09-api-keys-and-oauth.ts │ │ │ ├── 10-settings.ts │ │ │ ├── 11-sessions.ts │ │ │ ├── 12-full-control.ts │ │ │ └── README.md │ │ ├── package.json │ │ ├── scripts/ │ │ │ └── migrate-sessions.sh │ │ ├── src/ │ │ │ ├── bun/ │ │ │ │ ├── cli.ts │ │ │ │ └── register-bedrock.ts │ │ │ ├── cli/ │ │ │ │ ├── args.ts │ │ │ │ ├── config-selector.ts │ │ │ │ ├── file-processor.ts │ │ │ │ ├── initial-message.ts │ │ │ │ ├── list-models.ts │ │ │ │ └── session-picker.ts │ │ │ ├── cli.ts │ │ │ ├── config.ts │ │ │ ├── core/ │ │ │ │ ├── agent-session.ts │ │ │ │ ├── auth-storage.ts │ │ │ │ ├── bash-executor.ts │ │ │ │ ├── compaction/ │ │ │ │ │ ├── branch-summarization.ts │ │ │ │ │ ├── compaction.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── defaults.ts │ │ │ │ ├── diagnostics.ts │ │ │ │ ├── event-bus.ts │ │ │ │ ├── exec.ts │ │ │ │ ├── export-html/ │ │ │ │ │ ├── ansi-to-html.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── template.css │ │ │ │ │ ├── template.html │ │ │ │ │ ├── template.js │ │ │ │ │ └── tool-renderer.ts │ │ │ │ ├── extensions/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── loader.ts │ │ │ │ │ ├── runner.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── wrapper.ts │ │ │ │ ├── footer-data-provider.ts │ │ │ │ ├── index.ts │ │ │ │ ├── keybindings.ts │ │ │ │ ├── messages.ts │ │ │ │ ├── model-registry.ts │ │ │ │ ├── model-resolver.ts │ │ │ │ ├── package-manager.ts │ │ │ │ ├── prompt-templates.ts │ │ │ │ ├── resolve-config-value.ts │ │ │ │ ├── resource-loader.ts │ │ │ │ ├── sdk.ts │ │ │ │ ├── session-manager.ts │ │ │ │ ├── settings-manager.ts │ │ │ │ ├── skills.ts │ │ │ │ ├── slash-commands.ts │ │ │ │ ├── system-prompt.ts │ │ │ │ ├── timings.ts │ │ │ │ └── tools/ │ │ │ │ ├── bash.ts │ │ │ │ ├── edit-diff.ts │ │ │ │ ├── edit.ts │ │ │ │ ├── file-mutation-queue.ts │ │ │ │ ├── find.ts │ │ │ │ ├── grep.ts │ │ │ │ ├── index.ts │ │ │ │ ├── ls.ts │ │ │ │ ├── path-utils.ts │ │ │ │ ├── read.ts │ │ │ │ ├── truncate.ts │ │ │ │ └── write.ts │ │ │ ├── index.ts │ │ │ ├── main.ts │ │ │ ├── migrations.ts │ │ │ ├── modes/ │ │ │ │ ├── index.ts │ │ │ │ ├── interactive/ │ │ │ │ │ ├── components/ │ │ │ │ │ │ ├── armin.ts │ │ │ │ │ │ ├── assistant-message.ts │ │ │ │ │ │ ├── bash-execution.ts │ │ │ │ │ │ ├── bordered-loader.ts │ │ │ │ │ │ ├── branch-summary-message.ts │ │ │ │ │ │ ├── compaction-summary-message.ts │ │ │ │ │ │ ├── config-selector.ts │ │ │ │ │ │ ├── countdown-timer.ts │ │ │ │ │ │ ├── custom-editor.ts │ │ │ │ │ │ ├── custom-message.ts │ │ │ │ │ │ ├── daxnuts.ts │ │ │ │ │ │ ├── diff.ts │ │ │ │ │ │ ├── dynamic-border.ts │ │ │ │ │ │ ├── extension-editor.ts │ │ │ │ │ │ ├── extension-input.ts │ │ │ │ │ │ ├── extension-selector.ts │ │ │ │ │ │ ├── footer.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── keybinding-hints.ts │ │ │ │ │ │ ├── login-dialog.ts │ │ │ │ │ │ ├── model-selector.ts │ │ │ │ │ │ ├── oauth-selector.ts │ │ │ │ │ │ ├── scoped-models-selector.ts │ │ │ │ │ │ ├── session-selector-search.ts │ │ │ │ │ │ ├── session-selector.ts │ │ │ │ │ │ ├── settings-selector.ts │ │ │ │ │ │ ├── show-images-selector.ts │ │ │ │ │ │ ├── skill-invocation-message.ts │ │ │ │ │ │ ├── theme-selector.ts │ │ │ │ │ │ ├── thinking-selector.ts │ │ │ │ │ │ ├── tool-execution.ts │ │ │ │ │ │ ├── tree-selector.ts │ │ │ │ │ │ ├── user-message-selector.ts │ │ │ │ │ │ ├── user-message.ts │ │ │ │ │ │ └── visual-truncate.ts │ │ │ │ │ ├── interactive-mode.ts │ │ │ │ │ └── theme/ │ │ │ │ │ ├── dark.json │ │ │ │ │ ├── light.json │ │ │ │ │ ├── theme-schema.json │ │ │ │ │ └── theme.ts │ │ │ │ ├── print-mode.ts │ │ │ │ └── rpc/ │ │ │ │ ├── jsonl.ts │ │ │ │ ├── rpc-client.ts │ │ │ │ ├── rpc-mode.ts │ │ │ │ └── rpc-types.ts │ │ │ └── utils/ │ │ │ ├── changelog.ts │ │ │ ├── child-process.ts │ │ │ ├── clipboard-image.ts │ │ │ ├── clipboard-native.ts │ │ │ ├── clipboard.ts │ │ │ ├── exif-orientation.ts │ │ │ ├── frontmatter.ts │ │ │ ├── git.ts │ │ │ ├── image-convert.ts │ │ │ ├── image-resize.ts │ │ │ ├── mime.ts │ │ │ ├── photon.ts │ │ │ ├── shell.ts │ │ │ ├── sleep.ts │ │ │ └── tools-manager.ts │ │ ├── test/ │ │ │ ├── agent-session-auto-compaction-queue.test.ts │ │ │ ├── agent-session-branching.test.ts │ │ │ ├── agent-session-compaction.test.ts │ │ │ ├── agent-session-concurrent.test.ts │ │ │ ├── agent-session-dynamic-provider.test.ts │ │ │ ├── agent-session-dynamic-tools.test.ts │ │ │ ├── agent-session-model-switch-thinking.test.ts │ │ │ ├── agent-session-retry.test.ts │ │ │ ├── agent-session-tree-navigation.test.ts │ │ │ ├── args.test.ts │ │ │ ├── auth-storage.test.ts │ │ │ ├── bash-close-hang-windows.test.ts │ │ │ ├── block-images.test.ts │ │ │ ├── clipboard-image-bmp-conversion.test.ts │ │ │ ├── clipboard-image.test.ts │ │ │ ├── compaction-extensions-example.test.ts │ │ │ ├── compaction-extensions.test.ts │ │ │ ├── compaction-serialization.test.ts │ │ │ ├── compaction-summary-reasoning.test.ts │ │ │ ├── compaction-thinking-model.test.ts │ │ │ ├── compaction.test.ts │ │ │ ├── extensions-discovery.test.ts │ │ │ ├── extensions-input-event.test.ts │ │ │ ├── extensions-runner.test.ts │ │ │ ├── file-mutation-queue.test.ts │ │ │ ├── fixtures/ │ │ │ │ ├── assistant-message-with-thinking-code.json │ │ │ │ ├── before-compaction.jsonl │ │ │ │ ├── empty-agent/ │ │ │ │ │ └── .gitkeep │ │ │ │ ├── empty-cwd/ │ │ │ │ │ └── .gitkeep │ │ │ │ ├── large-session.jsonl │ │ │ │ ├── skills/ │ │ │ │ │ ├── consecutive-hyphens/ │ │ │ │ │ │ └── SKILL.md │ │ │ │ │ ├── disable-model-invocation/ │ │ │ │ │ │ └── SKILL.md │ │ │ │ │ ├── invalid-name-chars/ │ │ │ │ │ │ └── SKILL.md │ │ │ │ │ ├── invalid-yaml/ │ │ │ │ │ │ └── SKILL.md │ │ │ │ │ ├── long-name/ │ │ │ │ │ │ └── SKILL.md │ │ │ │ │ ├── missing-description/ │ │ │ │ │ │ └── SKILL.md │ │ │ │ │ ├── multiline-description/ │ │ │ │ │ │ └── SKILL.md │ │ │ │ │ ├── name-mismatch/ │ │ │ │ │ │ └── SKILL.md │ │ │ │ │ ├── nested/ │ │ │ │ │ │ └── child-skill/ │ │ │ │ │ │ └── SKILL.md │ │ │ │ │ ├── no-frontmatter/ │ │ │ │ │ │ └── SKILL.md │ │ │ │ │ ├── root-skill-preferred/ │ │ │ │ │ │ ├── SKILL.md │ │ │ │ │ │ └── nested-child/ │ │ │ │ │ │ └── SKILL.md │ │ │ │ │ ├── unknown-field/ │ │ │ │ │ │ └── SKILL.md │ │ │ │ │ └── valid-skill/ │ │ │ │ │ └── SKILL.md │ │ │ │ └── skills-collision/ │ │ │ │ ├── first/ │ │ │ │ │ └── calendar/ │ │ │ │ │ └── SKILL.md │ │ │ │ └── second/ │ │ │ │ └── calendar/ │ │ │ │ └── SKILL.md │ │ │ ├── footer-data-provider.test.ts │ │ │ ├── footer-width.test.ts │ │ │ ├── frontmatter.test.ts │ │ │ ├── git-ssh-url.test.ts │ │ │ ├── git-update.test.ts │ │ │ ├── image-processing.test.ts │ │ │ ├── initial-message.test.ts │ │ │ ├── interactive-mode-status.test.ts │ │ │ ├── keybindings-migration.test.ts │ │ │ ├── model-registry.test.ts │ │ │ ├── model-resolver.test.ts │ │ │ ├── package-command-paths.test.ts │ │ │ ├── package-manager-ssh.test.ts │ │ │ ├── package-manager.test.ts │ │ │ ├── path-utils.test.ts │ │ │ ├── plan-mode-utils.test.ts │ │ │ ├── prompt-templates.test.ts │ │ │ ├── resource-loader.test.ts │ │ │ ├── rpc-example.ts │ │ │ ├── rpc-jsonl.test.ts │ │ │ ├── rpc.test.ts │ │ │ ├── sdk-codex-cache-probe-tool-loop.ts │ │ │ ├── sdk-skills.test.ts │ │ │ ├── session-info-modified-timestamp.test.ts │ │ │ ├── session-manager/ │ │ │ │ ├── build-context.test.ts │ │ │ │ ├── custom-session-id.test.ts │ │ │ │ ├── file-operations.test.ts │ │ │ │ ├── labels.test.ts │ │ │ │ ├── migration.test.ts │ │ │ │ ├── save-entry.test.ts │ │ │ │ └── tree-traversal.test.ts │ │ │ ├── session-selector-path-delete.test.ts │ │ │ ├── session-selector-rename.test.ts │ │ │ ├── session-selector-search.test.ts │ │ │ ├── settings-manager-bug.test.ts │ │ │ ├── settings-manager.test.ts │ │ │ ├── skills.test.ts │ │ │ ├── streaming-render-debug.ts │ │ │ ├── system-prompt.test.ts │ │ │ ├── test-theme-colors.ts │ │ │ ├── tool-execution-component.test.ts │ │ │ ├── tools.test.ts │ │ │ ├── tree-selector.test.ts │ │ │ ├── truncate-to-width.test.ts │ │ │ └── utilities.ts │ │ ├── tsconfig.build.json │ │ ├── tsconfig.examples.json │ │ └── vitest.config.ts │ ├── mom/ │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── dev.sh │ │ ├── docker.sh │ │ ├── docs/ │ │ │ ├── artifacts-server.md │ │ │ ├── events.md │ │ │ ├── new.md │ │ │ ├── sandbox.md │ │ │ ├── slack-bot-minimal-guide.md │ │ │ └── v86.md │ │ ├── package.json │ │ ├── scripts/ │ │ │ └── migrate-timestamps.ts │ │ ├── src/ │ │ │ ├── agent.ts │ │ │ ├── context.ts │ │ │ ├── download.ts │ │ │ ├── events.ts │ │ │ ├── log.ts │ │ │ ├── main.ts │ │ │ ├── sandbox.ts │ │ │ ├── slack.ts │ │ │ ├── store.ts │ │ │ └── tools/ │ │ │ ├── attach.ts │ │ │ ├── bash.ts │ │ │ ├── edit.ts │ │ │ ├── index.ts │ │ │ ├── read.ts │ │ │ ├── truncate.ts │ │ │ └── write.ts │ │ └── tsconfig.build.json │ ├── pods/ │ │ ├── README.md │ │ ├── docs/ │ │ │ ├── gml-4.5.md │ │ │ ├── gpt-oss.md │ │ │ ├── implementation-plan.md │ │ │ ├── kimi-k2.md │ │ │ ├── models.md │ │ │ ├── plan.md │ │ │ └── qwen3-coder.md │ │ ├── package.json │ │ ├── scripts/ │ │ │ ├── model_run.sh │ │ │ └── pod_setup.sh │ │ ├── src/ │ │ │ ├── cli.ts │ │ │ ├── commands/ │ │ │ │ ├── models.ts │ │ │ │ ├── pods.ts │ │ │ │ └── prompt.ts │ │ │ ├── config.ts │ │ │ ├── index.ts │ │ │ ├── model-configs.ts │ │ │ ├── models.json │ │ │ ├── ssh.ts │ │ │ └── types.ts │ │ └── tsconfig.build.json │ ├── tui/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── autocomplete.ts │ │ │ ├── components/ │ │ │ │ ├── box.ts │ │ │ │ ├── cancellable-loader.ts │ │ │ │ ├── editor.ts │ │ │ │ ├── image.ts │ │ │ │ ├── input.ts │ │ │ │ ├── loader.ts │ │ │ │ ├── markdown.ts │ │ │ │ ├── select-list.ts │ │ │ │ ├── settings-list.ts │ │ │ │ ├── spacer.ts │ │ │ │ ├── text.ts │ │ │ │ └── truncated-text.ts │ │ │ ├── editor-component.ts │ │ │ ├── fuzzy.ts │ │ │ ├── index.ts │ │ │ ├── keybindings.ts │ │ │ ├── keys.ts │ │ │ ├── kill-ring.ts │ │ │ ├── stdin-buffer.ts │ │ │ ├── terminal-image.ts │ │ │ ├── terminal.ts │ │ │ ├── tui.ts │ │ │ ├── undo-stack.ts │ │ │ └── utils.ts │ │ ├── test/ │ │ │ ├── autocomplete.test.ts │ │ │ ├── bug-regression-isimageline-startswith-bug.test.ts │ │ │ ├── chat-simple.ts │ │ │ ├── editor.test.ts │ │ │ ├── fuzzy.test.ts │ │ │ ├── image-test.ts │ │ │ ├── input.test.ts │ │ │ ├── key-tester.ts │ │ │ ├── keys.test.ts │ │ │ ├── markdown.test.ts │ │ │ ├── overlay-non-capturing.test.ts │ │ │ ├── overlay-options.test.ts │ │ │ ├── overlay-short-content.test.ts │ │ │ ├── regression-regional-indicator-width.test.ts │ │ │ ├── select-list.test.ts │ │ │ ├── stdin-buffer.test.ts │ │ │ ├── terminal-image.test.ts │ │ │ ├── test-themes.ts │ │ │ ├── truncated-text.test.ts │ │ │ ├── tui-overlay-style-leak.test.ts │ │ │ ├── tui-render.test.ts │ │ │ ├── viewport-overwrite-repro.ts │ │ │ ├── virtual-terminal.ts │ │ │ └── wrap-ansi.test.ts │ │ ├── tsconfig.build.json │ │ └── vitest.config.ts │ └── web-ui/ │ ├── CHANGELOG.md │ ├── README.md │ ├── example/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── index.html │ │ ├── package.json │ │ ├── src/ │ │ │ ├── app.css │ │ │ ├── custom-messages.ts │ │ │ └── main.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── package.json │ ├── scripts/ │ │ └── count-prompt-tokens.ts │ ├── src/ │ │ ├── ChatPanel.ts │ │ ├── app.css │ │ ├── components/ │ │ │ ├── AgentInterface.ts │ │ │ ├── AttachmentTile.ts │ │ │ ├── ConsoleBlock.ts │ │ │ ├── CustomProviderCard.ts │ │ │ ├── ExpandableSection.ts │ │ │ ├── Input.ts │ │ │ ├── MessageEditor.ts │ │ │ ├── MessageList.ts │ │ │ ├── Messages.ts │ │ │ ├── ProviderKeyInput.ts │ │ │ ├── SandboxedIframe.ts │ │ │ ├── StreamingMessageContainer.ts │ │ │ ├── ThinkingBlock.ts │ │ │ ├── message-renderer-registry.ts │ │ │ └── sandbox/ │ │ │ ├── ArtifactsRuntimeProvider.ts │ │ │ ├── AttachmentsRuntimeProvider.ts │ │ │ ├── ConsoleRuntimeProvider.ts │ │ │ ├── FileDownloadRuntimeProvider.ts │ │ │ ├── RuntimeMessageBridge.ts │ │ │ ├── RuntimeMessageRouter.ts │ │ │ └── SandboxRuntimeProvider.ts │ │ ├── dialogs/ │ │ │ ├── ApiKeyPromptDialog.ts │ │ │ ├── AttachmentOverlay.ts │ │ │ ├── CustomProviderDialog.ts │ │ │ ├── ModelSelector.ts │ │ │ ├── PersistentStorageDialog.ts │ │ │ ├── ProvidersModelsTab.ts │ │ │ ├── SessionListDialog.ts │ │ │ └── SettingsDialog.ts │ │ ├── index.ts │ │ ├── prompts/ │ │ │ └── prompts.ts │ │ ├── storage/ │ │ │ ├── app-storage.ts │ │ │ ├── backends/ │ │ │ │ └── indexeddb-storage-backend.ts │ │ │ ├── store.ts │ │ │ ├── stores/ │ │ │ │ ├── custom-providers-store.ts │ │ │ │ ├── provider-keys-store.ts │ │ │ │ ├── sessions-store.ts │ │ │ │ └── settings-store.ts │ │ │ └── types.ts │ │ ├── tools/ │ │ │ ├── artifacts/ │ │ │ │ ├── ArtifactElement.ts │ │ │ │ ├── ArtifactPill.ts │ │ │ │ ├── Console.ts │ │ │ │ ├── DocxArtifact.ts │ │ │ │ ├── ExcelArtifact.ts │ │ │ │ ├── GenericArtifact.ts │ │ │ │ ├── HtmlArtifact.ts │ │ │ │ ├── ImageArtifact.ts │ │ │ │ ├── MarkdownArtifact.ts │ │ │ │ ├── PdfArtifact.ts │ │ │ │ ├── SvgArtifact.ts │ │ │ │ ├── TextArtifact.ts │ │ │ │ ├── artifacts-tool-renderer.ts │ │ │ │ ├── artifacts.ts │ │ │ │ └── index.ts │ │ │ ├── extract-document.ts │ │ │ ├── index.ts │ │ │ ├── javascript-repl.ts │ │ │ ├── renderer-registry.ts │ │ │ ├── renderers/ │ │ │ │ ├── BashRenderer.ts │ │ │ │ ├── CalculateRenderer.ts │ │ │ │ ├── DefaultRenderer.ts │ │ │ │ └── GetCurrentTimeRenderer.ts │ │ │ └── types.ts │ │ └── utils/ │ │ ├── attachment-utils.ts │ │ ├── auth-token.ts │ │ ├── format.ts │ │ ├── i18n.ts │ │ ├── model-discovery.ts │ │ ├── proxy-utils.ts │ │ └── test-sessions.ts │ ├── tsconfig.build.json │ └── tsconfig.json ├── pi-mono.code-workspace ├── pi-test.sh ├── scripts/ │ ├── browser-smoke-entry.ts │ ├── build-binaries.sh │ ├── check-browser-smoke.mjs │ ├── cost.ts │ ├── oss-weekend.mjs │ ├── release.mjs │ ├── session-transcripts.ts │ └── sync-versions.js ├── test.sh ├── tsconfig.base.json └── tsconfig.json
Showing preview only (426K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (4718 symbols across 424 files)
FILE: .pi/extensions/diff.ts
type FileInfo (line 12) | interface FileInfo {
FILE: .pi/extensions/files.ts
type FileEntry (line 12) | interface FileEntry {
type FileToolName (line 18) | type FileToolName = "read" | "write" | "edit";
FILE: .pi/extensions/prompt-url-widget.ts
constant PR_PROMPT_PATTERN (line 4) | const PR_PROMPT_PATTERN = /^\s*You are given one or more GitHub PR URLs:...
constant ISSUE_PROMPT_PATTERN (line 5) | const ISSUE_PROMPT_PATTERN = /^\s*Analyze GitHub issue\(s\):\s*(\S+)/im;
type PromptMatch (line 7) | type PromptMatch = {
type GhMetadata (line 12) | type GhMetadata = {
function extractPromptMatch (line 20) | function extractPromptMatch(prompt: string): PromptMatch | undefined {
function fetchGhMetadata (line 34) | async function fetchGhMetadata(
function formatAuthor (line 51) | function formatAuthor(author?: GhMetadata["author"]): string | undefined {
function promptUrlWidgetExtension (line 61) | function promptUrlWidgetExtension(pi: ExtensionAPI) {
FILE: .pi/extensions/tps.ts
function isAssistantMessage (line 4) | function isAssistantMessage(message: unknown): message is AssistantMessa...
FILE: packages/agent/src/agent-loop.ts
type AgentEventSink (line 25) | type AgentEventSink = (event: AgentEvent) => Promise<void> | void;
function agentLoop (line 31) | function agentLoop(
function agentLoopContinue (line 64) | function agentLoopContinue(
function runAgentLoop (line 95) | async function runAgentLoop(
function runAgentLoopContinue (line 120) | async function runAgentLoopContinue(
function createAgentStream (line 145) | function createAgentStream(): EventStream<AgentEvent, AgentMessage[]> {
function runLoop (line 155) | async function runLoop(
function streamAssistantResponse (line 238) | async function streamAssistantResponse(
function executeToolCalls (line 336) | async function executeToolCalls(
function executeToolCallsSequential (line 350) | async function executeToolCallsSequential(
function executeToolCallsParallel (line 390) | async function executeToolCallsParallel(
type PreparedToolCall (line 440) | type PreparedToolCall = {
type ImmediateToolCallOutcome (line 447) | type ImmediateToolCallOutcome = {
type ExecutedToolCallOutcome (line 453) | type ExecutedToolCallOutcome = {
function prepareToolCall (line 458) | async function prepareToolCall(
function executePreparedToolCall (line 509) | async function executePreparedToolCall(
function finalizeExecutedToolCall (line 546) | async function finalizeExecutedToolCall(
function createErrorToolResult (line 582) | function createErrorToolResult(message: string): AgentToolResult<any> {
function emitToolCallOutcome (line 589) | async function emitToolCallOutcome(
FILE: packages/agent/src/agent.ts
function defaultConvertToLlm (line 37) | function defaultConvertToLlm(messages: AgentMessage[]): Message[] {
type AgentOptions (line 41) | interface AgentOptions {
class Agent (line 116) | class Agent {
method constructor (line 156) | constructor(opts: AgentOptions = {}) {
method sessionId (line 177) | get sessionId(): string | undefined {
method sessionId (line 185) | set sessionId(value: string | undefined) {
method thinkingBudgets (line 192) | get thinkingBudgets(): ThinkingBudgets | undefined {
method thinkingBudgets (line 199) | set thinkingBudgets(value: ThinkingBudgets | undefined) {
method transport (line 206) | get transport(): Transport {
method setTransport (line 213) | setTransport(value: Transport) {
method maxRetryDelayMs (line 220) | get maxRetryDelayMs(): number | undefined {
method maxRetryDelayMs (line 228) | set maxRetryDelayMs(value: number | undefined) {
method toolExecution (line 232) | get toolExecution(): ToolExecutionMode {
method setToolExecution (line 236) | setToolExecution(value: ToolExecutionMode) {
method setBeforeToolCall (line 240) | setBeforeToolCall(
method setAfterToolCall (line 248) | setAfterToolCall(
method state (line 256) | get state(): AgentState {
method subscribe (line 260) | subscribe(fn: (e: AgentEvent) => void): () => void {
method setSystemPrompt (line 266) | setSystemPrompt(v: string) {
method setModel (line 270) | setModel(m: Model<any>) {
method setThinkingLevel (line 274) | setThinkingLevel(l: ThinkingLevel) {
method setSteeringMode (line 278) | setSteeringMode(mode: "all" | "one-at-a-time") {
method getSteeringMode (line 282) | getSteeringMode(): "all" | "one-at-a-time" {
method setFollowUpMode (line 286) | setFollowUpMode(mode: "all" | "one-at-a-time") {
method getFollowUpMode (line 290) | getFollowUpMode(): "all" | "one-at-a-time" {
method setTools (line 294) | setTools(t: AgentTool<any>[]) {
method replaceMessages (line 298) | replaceMessages(ms: AgentMessage[]) {
method appendMessage (line 302) | appendMessage(m: AgentMessage) {
method steer (line 311) | steer(m: AgentMessage) {
method followUp (line 319) | followUp(m: AgentMessage) {
method clearSteeringQueue (line 323) | clearSteeringQueue() {
method clearFollowUpQueue (line 327) | clearFollowUpQueue() {
method clearAllQueues (line 331) | clearAllQueues() {
method hasQueuedMessages (line 336) | hasQueuedMessages(): boolean {
method dequeueSteeringMessages (line 340) | private dequeueSteeringMessages(): AgentMessage[] {
method dequeueFollowUpMessages (line 355) | private dequeueFollowUpMessages(): AgentMessage[] {
method clearMessages (line 370) | clearMessages() {
method abort (line 374) | abort() {
method waitForIdle (line 378) | waitForIdle(): Promise<void> {
method reset (line 382) | reset() {
method prompt (line 395) | async prompt(input: string | AgentMessage | AgentMessage[], images?: I...
method continue (line 431) | async continue() {
method _processLoopEvent (line 459) | private _processLoopEvent(event: AgentEvent): void {
method _runLoop (line 508) | private async _runLoop(messages?: AgentMessage[], options?: { skipInit...
method emit (line 608) | private emit(e: AgentEvent) {
FILE: packages/agent/src/proxy.ts
class ProxyMessageEventStream (line 20) | class ProxyMessageEventStream extends EventStream<AssistantMessageEvent,...
method constructor (line 21) | constructor() {
type ProxyAssistantMessageEvent (line 36) | type ProxyAssistantMessageEvent =
type ProxyStreamOptions (line 59) | interface ProxyStreamOptions extends SimpleStreamOptions {
function streamProxy (line 85) | function streamProxy(model: Model<any>, context: Context, options: Proxy...
function processProxyEvent (line 211) | function processProxyEvent(
FILE: packages/agent/src/types.ts
type StreamFn (line 24) | type StreamFn = (
type ToolExecutionMode (line 35) | type ToolExecutionMode = "sequential" | "parallel";
type AgentToolCall (line 38) | type AgentToolCall = Extract<AssistantMessage["content"][number], { type...
type BeforeToolCallResult (line 46) | interface BeforeToolCallResult {
type AfterToolCallResult (line 62) | interface AfterToolCallResult {
type BeforeToolCallContext (line 69) | interface BeforeToolCallContext {
type AfterToolCallContext (line 81) | interface AfterToolCallContext {
type AgentLoopConfig (line 96) | interface AgentLoopConfig extends SimpleStreamOptions {
type ThinkingLevel (line 220) | type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xh...
type CustomAgentMessages (line 236) | interface CustomAgentMessages {
type AgentMessage (line 245) | type AgentMessage = Message | CustomAgentMessages[keyof CustomAgentMessa...
type AgentState (line 250) | interface AgentState {
type AgentToolResult (line 262) | interface AgentToolResult<T> {
type AgentToolUpdateCallback (line 270) | type AgentToolUpdateCallback<T = any> = (partialResult: AgentToolResult<...
type AgentTool (line 273) | interface AgentTool<TParameters extends TSchema = TSchema, TDetails = an...
type AgentContext (line 285) | interface AgentContext {
type AgentEvent (line 295) | type AgentEvent =
FILE: packages/agent/test/agent-loop.test.ts
class MockAssistantStream (line 15) | class MockAssistantStream extends EventStream<AssistantMessageEvent, Ass...
method constructor (line 16) | constructor() {
function createUsage (line 28) | function createUsage() {
function createModel (line 39) | function createModel(): Model<"openai-responses"> {
function createAssistantMessage (line 54) | function createAssistantMessage(
function createUserMessage (line 70) | function createUserMessage(text: string): UserMessage {
function identityConverter (line 79) | function identityConverter(messages: AgentMessage[]): Message[] {
type CustomNotification (line 133) | interface CustomNotification {
method execute (line 247) | async execute(_toolCallId, params) {
method execute (line 324) | async execute(_toolCallId, params) {
method execute (line 399) | async execute(_toolCallId, params) {
type CustomMessage (line 558) | interface CustomMessage {
FILE: packages/agent/test/agent.test.ts
class MockAssistantStream (line 6) | class MockAssistantStream extends EventStream<AssistantMessageEvent, Ass...
method constructor (line 7) | constructor() {
function createAssistantMessage (line 19) | function createAssistantMessage(text: string): AssistantMessage {
FILE: packages/agent/test/bedrock-models.test.ts
constant REQUIRES_INFERENCE_PROFILE (line 35) | const REQUIRES_INFERENCE_PROFILE = new Set([
constant INVALID_MODEL_ID (line 44) | const INVALID_MODEL_ID = new Set([
constant MAX_TOKENS_EXCEEDED (line 54) | const MAX_TOKENS_EXCEEDED = new Set([
constant NO_REASONING_IN_USER_MESSAGES (line 63) | const NO_REASONING_IN_USER_MESSAGES = new Set([
constant VALIDATES_SIGNATURE_FORMAT (line 116) | const VALIDATES_SIGNATURE_FORMAT = new Set([
constant REJECTS_REASONING_ON_REPLAY (line 129) | const REJECTS_REASONING_ON_REPLAY = new Set(["us.deepseek.r1-v1:0"]);
function isModelUnavailable (line 135) | function isModelUnavailable(modelId: string): boolean {
function failsMultiTurnWithThinking (line 139) | function failsMultiTurnWithThinking(modelId: string): boolean {
function failsSyntheticSignature (line 143) | function failsSyntheticSignature(modelId: string): boolean {
FILE: packages/agent/test/bedrock-utils.ts
function hasBedrockCredentials (line 12) | function hasBedrockCredentials(): boolean {
FILE: packages/agent/test/e2e.test.ts
function basicPrompt (line 10) | async function basicPrompt(model: Model<any>) {
function toolExecution (line 37) | async function toolExecution(model: Model<any>) {
function abortExecution (line 78) | async function abortExecution(model: Model<any>) {
function stateUpdates (line 107) | async function stateUpdates(model: Model<any>) {
function multiTurnConversation (line 139) | async function multiTurnConversation(model: Model<any>) {
FILE: packages/agent/test/utils/calculate.ts
type CalculateResult (line 4) | interface CalculateResult extends AgentToolResult<undefined> {
function calculate (line 9) | function calculate(expression: string): CalculateResult {
type CalculateParams (line 22) | type CalculateParams = Static<typeof calculateSchema>;
FILE: packages/agent/test/utils/get-current-time.ts
type GetCurrentTimeResult (line 4) | interface GetCurrentTimeResult extends AgentToolResult<{ utcTimestamp: n...
function getCurrentTime (line 6) | async function getCurrentTime(timezone?: string): Promise<GetCurrentTime...
type GetCurrentTimeParams (line 36) | type GetCurrentTimeParams = Static<typeof getCurrentTimeSchema>;
FILE: packages/ai/scripts/generate-models.ts
type ModelsDevModel (line 12) | interface ModelsDevModel {
type AiGatewayModel (line 35) | interface AiGatewayModel {
constant COPILOT_STATIC_HEADERS (line 49) | const COPILOT_STATIC_HEADERS = {
constant AI_GATEWAY_MODELS_URL (line 56) | const AI_GATEWAY_MODELS_URL = "https://ai-gateway.vercel.sh/v1";
constant AI_GATEWAY_BASE_URL (line 57) | const AI_GATEWAY_BASE_URL = "https://ai-gateway.vercel.sh";
function fetchOpenRouterModels (line 59) | async function fetchOpenRouterModels(): Promise<Model<any>[]> {
function fetchAiGatewayModels (line 117) | async function fetchAiGatewayModels(): Promise<Model<any>[]> {
function loadModelsDevData (line 175) | async function loadModelsDevData(): Promise<Model<any>[]> {
function generateModels (line 640) | async function generateModels() {
FILE: packages/ai/src/api-registry.ts
type ApiStreamFunction (line 11) | type ApiStreamFunction = (
type ApiStreamSimpleFunction (line 17) | type ApiStreamSimpleFunction = (
type ApiProvider (line 23) | interface ApiProvider<TApi extends Api = Api, TOptions extends StreamOpt...
type ApiProviderInternal (line 29) | interface ApiProviderInternal {
type RegisteredApiProvider (line 35) | type RegisteredApiProvider = {
function wrapStream (line 42) | function wrapStream<TApi extends Api, TOptions extends StreamOptions>(
function wrapStreamSimple (line 54) | function wrapStreamSimple<TApi extends Api>(
function registerApiProvider (line 66) | function registerApiProvider<TApi extends Api, TOptions extends StreamOp...
function getApiProvider (line 80) | function getApiProvider(api: Api): ApiProviderInternal | undefined {
function getApiProviders (line 84) | function getApiProviders(): ApiProviderInternal[] {
function unregisterApiProviders (line 88) | function unregisterApiProviders(sourceId: string): void {
function clearApiProviders (line 96) | function clearApiProviders(): void {
FILE: packages/ai/src/cli.ts
constant AUTH_FILE (line 8) | const AUTH_FILE = "auth.json";
constant PROVIDERS (line 9) | const PROVIDERS = getOAuthProviders();
function prompt (line 11) | function prompt(rl: ReturnType<typeof createInterface>, question: string...
function loadAuth (line 15) | function loadAuth(): Record<string, { type: "oauth" } & OAuthCredentials> {
function saveAuth (line 24) | function saveAuth(auth: Record<string, { type: "oauth" } & OAuthCredenti...
function login (line 28) | async function login(providerId: OAuthProviderId): Promise<void> {
function main (line 61) | async function main(): Promise<void> {
FILE: packages/ai/src/env-api-keys.ts
type DynamicImport (line 6) | type DynamicImport = (specifier: string) => Promise<unknown>;
constant NODE_FS_SPECIFIER (line 9) | const NODE_FS_SPECIFIER = "node:" + "fs";
constant NODE_OS_SPECIFIER (line 10) | const NODE_OS_SPECIFIER = "node:" + "os";
constant NODE_PATH_SPECIFIER (line 11) | const NODE_PATH_SPECIFIER = "node:" + "path";
function hasVertexAdcCredentials (line 30) | function hasVertexAdcCredentials(): boolean {
function getEnvApiKey (line 65) | function getEnvApiKey(provider: any): string | undefined {
FILE: packages/ai/src/models.generated.ts
constant MODELS (line 6) | const MODELS = {
FILE: packages/ai/src/models.ts
type ModelApi (line 15) | type ModelApi<
function getModel (line 20) | function getModel<TProvider extends KnownProvider, TModelId extends keyo...
function getProviders (line 28) | function getProviders(): KnownProvider[] {
function getModels (line 32) | function getModels<TProvider extends KnownProvider>(
function calculateCost (line 39) | function calculateCost<TApi extends Api>(model: Model<TApi>, usage: Usag...
function supportsXhigh (line 55) | function supportsXhigh<TApi extends Api>(model: Model<TApi>): boolean {
function modelsAreEqual (line 71) | function modelsAreEqual<TApi extends Api>(
FILE: packages/ai/src/providers/amazon-bedrock.ts
type BedrockOptions (line 48) | interface BedrockOptions extends StreamOptions {
type Block (line 60) | type Block = (TextContent | ThinkingContent | ToolCall) & { index?: numb...
function handleContentBlockStart (line 263) | function handleContentBlockStart(
function handleContentBlockDelta (line 286) | function handleContentBlockDelta(
function handleMetadata (line 344) | function handleMetadata(
function handleContentBlockStop (line 359) | function handleContentBlockStop(
function supportsAdaptiveThinking (line 388) | function supportsAdaptiveThinking(modelId: string): boolean {
function mapThinkingLevelToEffort (line 397) | function mapThinkingLevelToEffort(
function resolveCacheRetention (line 420) | function resolveCacheRetention(cacheRetention?: CacheRetention): CacheRe...
function supportsPromptCaching (line 441) | function supportsPromptCaching(model: Model<"bedrock-converse-stream">):...
function supportsThinkingSignature (line 464) | function supportsThinkingSignature(model: Model<"bedrock-converse-stream...
function buildSystemPrompt (line 469) | function buildSystemPrompt(
function normalizeToolCallId (line 488) | function normalizeToolCallId(id: string): string {
function convertMessages (line 493) | function convertMessages(
function convertToolConfig (line 652) | function convertToolConfig(
function mapStopReason (line 683) | function mapStopReason(reason: string | undefined): StopReason {
function buildAdditionalModelRequestFields (line 698) | function buildAdditionalModelRequestFields(
function createImageBlock (line 743) | function createImageBlock(mimeType: string, data: string) {
FILE: packages/ai/src/providers/anthropic.ts
function resolveCacheRetention (line 39) | function resolveCacheRetention(cacheRetention?: CacheRetention): CacheRe...
function getCacheControl (line 49) | function getCacheControl(
function convertContentBlocks (line 106) | function convertContentBlocks(content: (TextContent | ImageContent)[]):
type AnthropicEffort (line 155) | type AnthropicEffort = "low" | "medium" | "high" | "max";
type AnthropicOptions (line 157) | interface AnthropicOptions extends StreamOptions {
function mergeHeaders (line 189) | function mergeHeaders(...headerSources: (Record<string, string> | undefi...
type Block (line 262) | type Block = (ThinkingContent | TextContent | (ToolCall & { partialJson:...
function supportsAdaptiveThinking (line 446) | function supportsAdaptiveThinking(modelId: string): boolean {
function mapThinkingLevelToEffort (line 460) | function mapThinkingLevelToEffort(level: SimpleStreamOptions["reasoning"...
function isOAuthToken (line 518) | function isOAuthToken(apiKey: string): boolean {
function createClient (line 522) | function createClient(
function buildParams (line 607) | function buildParams(
function normalizeToolCallId (line 693) | function normalizeToolCallId(id: string): string {
function convertMessages (line 697) | function convertMessages(
function convertTools (line 862) | function convertTools(tools: Tool[], isOAuthToken: boolean): Anthropic.M...
function mapStopReason (line 880) | function mapStopReason(reason: Anthropic.Messages.StopReason | string): ...
FILE: packages/ai/src/providers/azure-openai-responses.ts
constant DEFAULT_AZURE_API_VERSION (line 18) | const DEFAULT_AZURE_API_VERSION = "v1";
constant AZURE_TOOL_CALL_PROVIDERS (line 19) | const AZURE_TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "op...
function parseDeploymentNameMap (line 21) | function parseDeploymentNameMap(value: string | undefined): Map<string, ...
function resolveDeploymentName (line 34) | function resolveDeploymentName(model: Model<"azure-openai-responses">, o...
type AzureOpenAIResponsesOptions (line 43) | interface AzureOpenAIResponsesOptions extends StreamOptions {
function normalizeAzureBaseUrl (line 142) | function normalizeAzureBaseUrl(baseUrl: string): string {
function buildDefaultBaseUrl (line 146) | function buildDefaultBaseUrl(resourceName: string): string {
function resolveAzureConfig (line 150) | function resolveAzureConfig(
function createClient (line 181) | function createClient(model: Model<"azure-openai-responses">, apiKey: st...
function buildParams (line 208) | function buildParams(
FILE: packages/ai/src/providers/github-copilot-headers.ts
function inferCopilotInitiator (line 5) | function inferCopilotInitiator(messages: Message[]): "user" | "agent" {
function hasCopilotVisionInput (line 11) | function hasCopilotVisionInput(messages: Message[]): boolean {
function buildCopilotDynamicHeaders (line 23) | function buildCopilotDynamicHeaders(params: {
FILE: packages/ai/src/providers/google-gemini-cli.ts
type GoogleThinkingLevel (line 39) | type GoogleThinkingLevel = "THINKING_LEVEL_UNSPECIFIED" | "MINIMAL" | "L...
type GoogleGeminiCliOptions (line 41) | interface GoogleGeminiCliOptions extends StreamOptions {
constant DEFAULT_ENDPOINT (line 60) | const DEFAULT_ENDPOINT = "https://cloudcode-pa.googleapis.com";
constant ANTIGRAVITY_DAILY_ENDPOINT (line 61) | const ANTIGRAVITY_DAILY_ENDPOINT = "https://daily-cloudcode-pa.sandbox.g...
constant ANTIGRAVITY_AUTOPUSH_ENDPOINT (line 62) | const ANTIGRAVITY_AUTOPUSH_ENDPOINT = "https://autopush-cloudcode-pa.san...
constant ANTIGRAVITY_ENDPOINT_FALLBACKS (line 63) | const ANTIGRAVITY_ENDPOINT_FALLBACKS = [
constant GEMINI_CLI_HEADERS (line 69) | const GEMINI_CLI_HEADERS = {
constant DEFAULT_ANTIGRAVITY_VERSION (line 80) | const DEFAULT_ANTIGRAVITY_VERSION = "1.18.4";
function getAntigravityHeaders (line 82) | function getAntigravityHeaders() {
constant ANTIGRAVITY_SYSTEM_INSTRUCTION (line 90) | const ANTIGRAVITY_SYSTEM_INSTRUCTION =
constant MAX_RETRIES (line 100) | const MAX_RETRIES = 3;
constant BASE_DELAY_MS (line 101) | const BASE_DELAY_MS = 1000;
constant MAX_EMPTY_STREAM_RETRIES (line 102) | const MAX_EMPTY_STREAM_RETRIES = 2;
constant EMPTY_STREAM_BASE_DELAY_MS (line 103) | const EMPTY_STREAM_BASE_DELAY_MS = 500;
constant CLAUDE_THINKING_BETA_HEADER (line 104) | const CLAUDE_THINKING_BETA_HEADER = "interleaved-thinking-2025-05-14";
function extractRetryDelay (line 115) | function extractRetryDelay(errorText: string, response?: Response | Head...
function needsClaudeThinkingBetaHeader (line 206) | function needsClaudeThinkingBetaHeader(model: Model<"google-gemini-cli">...
function isGemini3ProModel (line 210) | function isGemini3ProModel(modelId: string): boolean {
function isGemini3FlashModel (line 214) | function isGemini3FlashModel(modelId: string): boolean {
function isGemini3Model (line 218) | function isGemini3Model(modelId: string): boolean {
function isRetryableError (line 225) | function isRetryableError(status: number, errorText: string): boolean {
function extractErrorMessage (line 236) | function extractErrorMessage(errorText: string): string {
function sleep (line 251) | function sleep(ms: number, signal?: AbortSignal): Promise<void> {
type CloudCodeAssistRequest (line 265) | interface CloudCodeAssistRequest {
type CloudCodeAssistResponseChunk (line 289) | interface CloudCodeAssistResponseChunk {
function buildRequest (line 863) | function buildRequest(
type ClampedThinkingLevel (line 947) | type ClampedThinkingLevel = Exclude<ThinkingLevel, "xhigh">;
function getGeminiCliThinkingLevel (line 949) | function getGeminiCliThinkingLevel(effort: ClampedThinkingLevel, modelId...
FILE: packages/ai/src/providers/google-shared.ts
type GoogleApiType (line 10) | type GoogleApiType = "google-generative-ai" | "google-gemini-cli" | "goo...
function isThinkingPart (line 27) | function isThinkingPart(part: Pick<Part, "thought" | "thoughtSignature">...
function retainThoughtSignature (line 40) | function retainThoughtSignature(existing: string | undefined, incoming: ...
constant SKIP_THOUGHT_SIGNATURE (line 51) | const SKIP_THOUGHT_SIGNATURE = "skip_thought_signature_validator";
function isValidThoughtSignature (line 53) | function isValidThoughtSignature(signature: string | undefined): boolean {
function resolveThoughtSignature (line 62) | function resolveThoughtSignature(isSameProviderAndModel: boolean, signat...
function requiresToolCallId (line 69) | function requiresToolCallId(modelId: string): boolean {
function getGeminiMajorVersion (line 73) | function getGeminiMajorVersion(modelId: string): number | undefined {
function supportsMultimodalFunctionResponse (line 79) | function supportsMultimodalFunctionResponse(modelId: string): boolean {
function convertMessages (line 90) | function convertMessages<T extends GoogleApiType>(model: Model<T>, conte...
function convertTools (line 250) | function convertTools(
function mapToolChoice (line 269) | function mapToolChoice(choice: string): FunctionCallingConfigMode {
function mapStopReason (line 285) | function mapStopReason(reason: FinishReason): StopReason {
function mapStopReasonString (line 317) | function mapStopReasonString(reason: string): StopReason {
FILE: packages/ai/src/providers/google-vertex.ts
type GoogleVertexOptions (line 36) | interface GoogleVertexOptions extends StreamOptions {
constant API_VERSION (line 47) | const API_VERSION = "v1";
constant THINKING_LEVEL_MAP (line 49) | const THINKING_LEVEL_MAP: Record<GoogleThinkingLevel, ThinkingLevel> = {
function createClient (line 326) | function createClient(
function createClientWithApiKey (line 349) | function createClientWithApiKey(
function resolveApiKey (line 370) | function resolveApiKey(options?: GoogleVertexOptions): string | undefined {
function isPlaceholderApiKey (line 378) | function isPlaceholderApiKey(apiKey: string): boolean {
function resolveProject (line 382) | function resolveProject(options?: GoogleVertexOptions): string {
function resolveLocation (line 392) | function resolveLocation(options?: GoogleVertexOptions): string {
function buildParams (line 400) | function buildParams(
type ClampedThinkingLevel (line 457) | type ClampedThinkingLevel = Exclude<PiThinkingLevel, "xhigh">;
function isGemini3ProModel (line 459) | function isGemini3ProModel(model: Model<"google-generative-ai">): boolean {
function isGemini3FlashModel (line 463) | function isGemini3FlashModel(model: Model<"google-generative-ai">): bool...
function getGemini3ThinkingLevel (line 467) | function getGemini3ThinkingLevel(
function getGoogleBudget (line 493) | function getGoogleBudget(
FILE: packages/ai/src/providers/google.ts
type GoogleOptions (line 36) | interface GoogleOptions extends StreamOptions {
function createClient (line 314) | function createClient(
function buildParams (line 334) | function buildParams(
type ClampedThinkingLevel (line 392) | type ClampedThinkingLevel = Exclude<ThinkingLevel, "xhigh">;
function isGemini3ProModel (line 394) | function isGemini3ProModel(model: Model<"google-generative-ai">): boolean {
function isGemini3FlashModel (line 398) | function isGemini3FlashModel(model: Model<"google-generative-ai">): bool...
function getGemini3ThinkingLevel (line 402) | function getGemini3ThinkingLevel(
function getGoogleBudget (line 428) | function getGoogleBudget(
FILE: packages/ai/src/providers/mistral.ts
constant MISTRAL_TOOL_CALL_ID_LENGTH (line 33) | const MISTRAL_TOOL_CALL_ID_LENGTH = 9;
constant MAX_MISTRAL_ERROR_BODY_CHARS (line 34) | const MAX_MISTRAL_ERROR_BODY_CHARS = 4000;
type MistralOptions (line 39) | interface MistralOptions extends StreamOptions {
function createOutput (line 124) | function createOutput(model: Model<"mistral-conversations">): AssistantM...
function createMistralToolCallIdNormalizer (line 144) | function createMistralToolCallIdNormalizer(): (id: string) => string {
function deriveMistralToolCallId (line 166) | function deriveMistralToolCallId(id: string, attempt: number): string {
function formatMistralError (line 176) | function formatMistralError(error: unknown): string {
function truncateErrorText (line 190) | function truncateErrorText(text: string, maxChars: number): string {
function safeJsonStringify (line 195) | function safeJsonStringify(value: unknown): string {
function buildRequestOptions (line 204) | function buildRequestOptions(model: Model<"mistral-conversations">, opti...
function buildChatPayload (line 226) | function buildChatPayload(
function consumeChatStream (line 254) | async function consumeChatStream(
function toFunctionTools (line 437) | function toFunctionTools(tools: Tool[]): Array<FunctionTool & { type: "f...
function toChatMessages (line 449) | function toChatMessages(messages: Message[], supportsImages: boolean): C...
function buildToolResultText (line 536) | function buildToolResultText(text: string, hasImages: boolean, supportsI...
function mapToolChoice (line 557) | function mapToolChoice(
function mapChatStopReason (line 570) | function mapChatStopReason(reason: string | null): StopReason {
FILE: packages/ai/src/providers/openai-codex-responses.ts
type DynamicImport (line 7) | type DynamicImport = (specifier: string) => Promise<unknown>;
constant NODE_OS_SPECIFIER (line 10) | const NODE_OS_SPECIFIER = "node:" + "os";
constant DEFAULT_CODEX_BASE_URL (line 37) | const DEFAULT_CODEX_BASE_URL = "https://chatgpt.com/backend-api";
constant JWT_CLAIM_PATH (line 38) | const JWT_CLAIM_PATH = "https://api.openai.com/auth" as const;
constant MAX_RETRIES (line 39) | const MAX_RETRIES = 3;
constant BASE_DELAY_MS (line 40) | const BASE_DELAY_MS = 1000;
constant CODEX_TOOL_CALL_PROVIDERS (line 41) | const CODEX_TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "op...
constant CODEX_RESPONSE_STATUSES (line 43) | const CODEX_RESPONSE_STATUSES = new Set<CodexResponseStatus>([
type OpenAICodexResponsesOptions (line 56) | interface OpenAICodexResponsesOptions extends StreamOptions {
type CodexResponseStatus (line 62) | type CodexResponseStatus = "completed" | "incomplete" | "failed" | "canc...
type RequestBody (line 64) | interface RequestBody {
function isRetryableError (line 85) | function isRetryableError(status: number, errorText: string): boolean {
function sleep (line 92) | function sleep(ms: number, signal?: AbortSignal): Promise<void> {
function buildRequestBody (line 296) | function buildRequestBody(
function clampReasoningEffort (line 336) | function clampReasoningEffort(modelId: string, effort: string): string {
function resolveCodexUrl (line 345) | function resolveCodexUrl(baseUrl?: string): string {
function resolveCodexWebSocketUrl (line 353) | function resolveCodexWebSocketUrl(baseUrl?: string): string {
function processStream (line 364) | async function processStream(
function normalizeCodexStatus (line 402) | function normalizeCodexStatus(status: unknown): CodexResponseStatus | un...
constant OPENAI_BETA_RESPONSES_WEBSOCKETS (line 458) | const OPENAI_BETA_RESPONSES_WEBSOCKETS = "responses_websockets=2026-02-06";
constant SESSION_WEBSOCKET_CACHE_TTL_MS (line 459) | const SESSION_WEBSOCKET_CACHE_TTL_MS = 5 * 60 * 1000;
type WebSocketEventType (line 461) | type WebSocketEventType = "open" | "message" | "error" | "close";
type WebSocketListener (line 462) | type WebSocketListener = (event: unknown) => void;
type WebSocketLike (line 464) | interface WebSocketLike {
type CachedWebSocketConnection (line 471) | interface CachedWebSocketConnection {
type WebSocketConstructor (line 479) | type WebSocketConstructor = new (
function getWebSocketConstructor (line 484) | function getWebSocketConstructor(): WebSocketConstructor | null {
function headersToRecord (line 490) | function headersToRecord(headers: Headers): Record<string, string> {
function getWebSocketReadyState (line 498) | function getWebSocketReadyState(socket: WebSocketLike): number | undefin...
function isWebSocketReusable (line 503) | function isWebSocketReusable(socket: WebSocketLike): boolean {
function closeWebSocketSilently (line 509) | function closeWebSocketSilently(socket: WebSocketLike, code = 1000, reas...
function scheduleSessionWebSocketExpiry (line 515) | function scheduleSessionWebSocketExpiry(sessionId: string, entry: Cached...
function connectWebSocket (line 526) | async function connectWebSocket(url: string, headers: Headers, signal?: ...
function acquireWebSocket (line 588) | async function acquireWebSocket(
function extractWebSocketError (line 664) | function extractWebSocketError(event: unknown): Error {
function extractWebSocketCloseError (line 674) | function extractWebSocketCloseError(event: unknown): Error {
function decodeWebSocketData (line 685) | async function decodeWebSocketData(data: unknown): Promise<string | null> {
function processWebSocketStream (line 793) | async function processWebSocketStream(
function parseErrorResponse (line 825) | async function parseErrorResponse(response: Response): Promise<{ message...
function extractAccountId (line 856) | function extractAccountId(token: string): string {
function createCodexRequestId (line 869) | function createCodexRequestId(): string {
function buildBaseCodexHeaders (line 876) | function buildBaseCodexHeaders(
function buildSSEHeaders (line 894) | function buildSSEHeaders(
function buildWebSocketHeaders (line 913) | function buildWebSocketHeaders(
FILE: packages/ai/src/providers/openai-completions.ts
function hasToolHistory (line 41) | function hasToolHistory(messages: Message[]): boolean {
type OpenAICompletionsOptions (line 55) | interface OpenAICompletionsOptions extends StreamOptions {
function createClient (line 325) | function createClient(
function buildParams (line 363) | function buildParams(model: Model<"openai-completions">, context: Contex...
function mapReasoningEffort (line 441) | function mapReasoningEffort(
function maybeAddOpenRouterAnthropicCacheControl (line 448) | function maybeAddOpenRouterAnthropicCacheControl(
function convertMessages (line 481) | function convertMessages(
function convertTools (line 708) | function convertTools(
function parseChunkUsage (line 724) | function parseChunkUsage(
function mapStopReason (line 752) | function mapStopReason(reason: ChatCompletionChunk.Choice["finish_reason...
function detectCompat (line 783) | function detectCompat(model: Model<"openai-completions">): Required<Open...
function getCompat (line 840) | function getCompat(model: Model<"openai-completions">): Required<OpenAIC...
FILE: packages/ai/src/providers/openai-responses-shared.ts
function encodeTextSignatureV1 (line 40) | function encodeTextSignatureV1(id: string, phase?: TextSignatureV1["phas...
function parseTextSignature (line 46) | function parseTextSignature(
type OpenAIResponsesStreamOptions (line 66) | interface OpenAIResponsesStreamOptions {
type ConvertResponsesMessagesOptions (line 74) | interface ConvertResponsesMessagesOptions {
type ConvertResponsesToolsOptions (line 78) | interface ConvertResponsesToolsOptions {
function convertResponsesMessages (line 86) | function convertResponsesMessages<TApi extends Api>(
function convertResponsesTools (line 261) | function convertResponsesTools(tools: Tool[], options?: ConvertResponses...
function processResponsesStream (line 276) | async function processResponsesStream<TApi extends Api>(
function mapStopReason (line 488) | function mapStopReason(status: OpenAI.Responses.ResponseStatus | undefin...
FILE: packages/ai/src/providers/openai-responses.ts
constant OPENAI_TOOL_CALL_PROVIDERS (line 21) | const OPENAI_TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "o...
function resolveCacheRetention (line 27) | function resolveCacheRetention(cacheRetention?: CacheRetention): CacheRe...
function getPromptCacheRetention (line 41) | function getPromptCacheRetention(baseUrl: string, cacheRetention: CacheR...
type OpenAIResponsesOptions (line 52) | interface OpenAIResponsesOptions extends StreamOptions {
function createClient (line 149) | function createClient(
function buildParams (line 187) | function buildParams(model: Model<"openai-responses">, context: Context,...
function getServiceTierCostMultiplier (line 242) | function getServiceTierCostMultiplier(serviceTier: ResponseCreateParamsS...
function applyServiceTierPricing (line 253) | function applyServiceTierPricing(usage: Usage, serviceTier: ResponseCrea...
FILE: packages/ai/src/providers/register-builtins.ts
type LazyProviderModule (line 24) | interface LazyProviderModule<
type AnthropicProviderModule (line 37) | interface AnthropicProviderModule {
type AzureOpenAIResponsesProviderModule (line 42) | interface AzureOpenAIResponsesProviderModule {
type GoogleProviderModule (line 47) | interface GoogleProviderModule {
type GoogleGeminiCliProviderModule (line 52) | interface GoogleGeminiCliProviderModule {
type GoogleVertexProviderModule (line 57) | interface GoogleVertexProviderModule {
type MistralProviderModule (line 62) | interface MistralProviderModule {
type OpenAICodexResponsesProviderModule (line 67) | interface OpenAICodexResponsesProviderModule {
type OpenAICompletionsProviderModule (line 72) | interface OpenAICompletionsProviderModule {
type OpenAIResponsesProviderModule (line 77) | interface OpenAIResponsesProviderModule {
type BedrockProviderModule (line 82) | interface BedrockProviderModule {
function setBedrockProviderModule (line 131) | function setBedrockProviderModule(module: BedrockProviderModule): void {
function forwardStream (line 138) | function forwardStream(target: AssistantMessageEventStream, source: Asyn...
function createLazyLoadErrorMessage (line 147) | function createLazyLoadErrorMessage<TApi extends Api>(model: Model<TApi>...
function createLazyStream (line 168) | function createLazyStream<TApi extends Api, TOptions extends StreamOptio...
function createLazySimpleStream (line 189) | function createLazySimpleStream<
function loadAnthropicProviderModule (line 212) | function loadAnthropicProviderModule(): Promise<
function loadAzureOpenAIResponsesProviderModule (line 225) | function loadAzureOpenAIResponsesProviderModule(): Promise<
function loadGoogleProviderModule (line 238) | function loadGoogleProviderModule(): Promise<
function loadGoogleGeminiCliProviderModule (line 251) | function loadGoogleGeminiCliProviderModule(): Promise<
function loadGoogleVertexProviderModule (line 264) | function loadGoogleVertexProviderModule(): Promise<
function loadMistralProviderModule (line 277) | function loadMistralProviderModule(): Promise<
function loadOpenAICodexResponsesProviderModule (line 290) | function loadOpenAICodexResponsesProviderModule(): Promise<
function loadOpenAICompletionsProviderModule (line 303) | function loadOpenAICompletionsProviderModule(): Promise<
function loadOpenAIResponsesProviderModule (line 316) | function loadOpenAIResponsesProviderModule(): Promise<
function loadBedrockProviderModule (line 329) | function loadBedrockProviderModule(): Promise<
function registerBuiltInApiProviders (line 366) | function registerBuiltInApiProviders(): void {
function resetApiProviders (line 428) | function resetApiProviders(): void {
FILE: packages/ai/src/providers/simple-options.ts
function buildBaseOptions (line 3) | function buildBaseOptions(model: Model<Api>, options?: SimpleStreamOptio...
function clampReasoning (line 18) | function clampReasoning(effort: ThinkingLevel | undefined): Exclude<Thin...
function adjustMaxTokensForThinking (line 22) | function adjustMaxTokensForThinking(
FILE: packages/ai/src/providers/transform-messages.ts
function transformMessages (line 8) | function transformMessages<TApi extends Api>(
FILE: packages/ai/src/stream.ts
function resolveApiProvider (line 17) | function resolveApiProvider(api: Api) {
function stream (line 25) | function stream<TApi extends Api>(
function complete (line 34) | async function complete<TApi extends Api>(
function streamSimple (line 43) | function streamSimple<TApi extends Api>(
function completeSimple (line 52) | async function completeSimple<TApi extends Api>(
FILE: packages/ai/src/types.ts
type KnownApi (line 5) | type KnownApi =
type Api (line 17) | type Api = KnownApi | (string & {});
type KnownProvider (line 19) | type KnownProvider =
type Provider (line 43) | type Provider = KnownProvider | string;
type ThinkingLevel (line 45) | type ThinkingLevel = "minimal" | "low" | "medium" | "high" | "xhigh";
type ThinkingBudgets (line 48) | interface ThinkingBudgets {
type CacheRetention (line 56) | type CacheRetention = "none" | "short" | "long";
type Transport (line 58) | type Transport = "sse" | "websocket" | "auto";
type StreamOptions (line 60) | interface StreamOptions {
type ProviderStreamOptions (line 108) | type ProviderStreamOptions = StreamOptions & Record<string, unknown>;
type SimpleStreamOptions (line 111) | interface SimpleStreamOptions extends StreamOptions {
type StreamFunction (line 125) | type StreamFunction<TApi extends Api = Api, TOptions extends StreamOptio...
type TextSignatureV1 (line 131) | interface TextSignatureV1 {
type TextContent (line 137) | interface TextContent {
type ThinkingContent (line 143) | interface ThinkingContent {
type ImageContent (line 153) | interface ImageContent {
type ToolCall (line 159) | interface ToolCall {
type Usage (line 167) | interface Usage {
type StopReason (line 182) | type StopReason = "stop" | "length" | "toolUse" | "error" | "aborted";
type UserMessage (line 184) | interface UserMessage {
type AssistantMessage (line 190) | interface AssistantMessage {
type ToolResultMessage (line 203) | interface ToolResultMessage<TDetails = any> {
type Message (line 213) | type Message = UserMessage | AssistantMessage | ToolResultMessage;
type Tool (line 217) | interface Tool<TParameters extends TSchema = TSchema> {
type Context (line 223) | interface Context {
type AssistantMessageEvent (line 237) | type AssistantMessageEvent =
type OpenAICompletionsCompat (line 255) | interface OpenAICompletionsCompat {
type OpenAIResponsesCompat (line 285) | interface OpenAIResponsesCompat {
type OpenRouterRouting (line 294) | interface OpenRouterRouting {
type VercelGatewayRouting (line 306) | interface VercelGatewayRouting {
type Model (line 314) | interface Model<TApi extends Api> {
FILE: packages/ai/src/utils/event-stream.ts
class EventStream (line 4) | class EventStream<T, R = T> implements AsyncIterable<T> {
method constructor (line 11) | constructor(
method push (line 20) | push(event: T): void {
method end (line 37) | end(result?: R): void {
method result (line 63) | result(): Promise<R> {
method [Symbol.asyncIterator] (line 49) | async *[Symbol.asyncIterator](): AsyncIterator<T> {
class AssistantMessageEventStream (line 68) | class AssistantMessageEventStream extends EventStream<AssistantMessageEv...
method constructor (line 69) | constructor() {
function createAssistantMessageEventStream (line 85) | function createAssistantMessageEventStream(): AssistantMessageEventStream {
FILE: packages/ai/src/utils/hash.ts
function shortHash (line 2) | function shortHash(str: string): string {
FILE: packages/ai/src/utils/json-parse.ts
function parseStreamingJson (line 10) | function parseStreamingJson<T = any>(partialJson: string | undefined): T {
FILE: packages/ai/src/utils/oauth/anthropic.ts
type CallbackServerInfo (line 13) | type CallbackServerInfo = {
type NodeApis (line 20) | type NodeApis = {
constant CLIENT_ID (line 28) | const CLIENT_ID = decode("OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZj...
constant AUTHORIZE_URL (line 29) | const AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
constant TOKEN_URL (line 30) | const TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
constant CALLBACK_HOST (line 31) | const CALLBACK_HOST = "127.0.0.1";
constant CALLBACK_PORT (line 32) | const CALLBACK_PORT = 53692;
constant CALLBACK_PATH (line 33) | const CALLBACK_PATH = "/callback";
constant REDIRECT_URI (line 34) | const REDIRECT_URI = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
constant SCOPES (line 35) | const SCOPES =
function getNodeApis (line 37) | async function getNodeApis(): Promise<NodeApis> {
function parseAuthorizationInput (line 51) | function parseAuthorizationInput(input: string): { code?: string; state?...
function formatErrorDetails (line 81) | function formatErrorDetails(error: unknown): string {
function startCallbackServer (line 98) | async function startCallbackServer(expectedState: string): Promise<Callb...
function postJson (line 169) | async function postJson(url: string, body: Record<string, string | numbe...
function exchangeAuthorizationCode (line 189) | async function exchangeAuthorizationCode(
function loginAnthropic (line 230) | async function loginAnthropic(options: {
function refreshAnthropicToken (line 348) | async function refreshAnthropicToken(refreshToken: string): Promise<OAut...
method login (line 386) | async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
method refreshToken (line 395) | async refreshToken(credentials: OAuthCredentials): Promise<OAuthCredenti...
method getApiKey (line 399) | getApiKey(credentials: OAuthCredentials): string {
FILE: packages/ai/src/utils/oauth/github-copilot.ts
type CopilotCredentials (line 9) | type CopilotCredentials = OAuthCredentials & {
constant CLIENT_ID (line 14) | const CLIENT_ID = decode("SXYxLmI1MDdhMDhjODdlY2ZlOTg=");
constant COPILOT_HEADERS (line 16) | const COPILOT_HEADERS = {
constant INITIAL_POLL_INTERVAL_MULTIPLIER (line 23) | const INITIAL_POLL_INTERVAL_MULTIPLIER = 1.2;
constant SLOW_DOWN_POLL_INTERVAL_MULTIPLIER (line 24) | const SLOW_DOWN_POLL_INTERVAL_MULTIPLIER = 1.4;
type DeviceCodeResponse (line 26) | type DeviceCodeResponse = {
type DeviceTokenSuccessResponse (line 34) | type DeviceTokenSuccessResponse = {
type DeviceTokenErrorResponse (line 40) | type DeviceTokenErrorResponse = {
function normalizeDomain (line 46) | function normalizeDomain(input: string): string | null {
function getUrls (line 57) | function getUrls(domain: string): {
function getBaseUrlFromToken (line 74) | function getBaseUrlFromToken(token: string): string | null {
function getGitHubCopilotBaseUrl (line 83) | function getGitHubCopilotBaseUrl(token?: string, enterpriseDomain?: stri...
function fetchJson (line 94) | async function fetchJson(url: string, init: RequestInit): Promise<unknow...
function startDeviceFlow (line 103) | async function startDeviceFlow(domain: string): Promise<DeviceCodeRespon...
function abortableSleep (line 150) | function abortableSleep(ms: number, signal?: AbortSignal): Promise<void> {
function pollForGitHubAccessToken (line 170) | async function pollForGitHubAccessToken(
function refreshGitHubCopilotToken (line 241) | async function refreshGitHubCopilotToken(
function enableGitHubCopilotModel (line 279) | async function enableGitHubCopilotModel(token: string, modelId: string, ...
function enableAllGitHubCopilotModels (line 305) | async function enableAllGitHubCopilotModels(
function loginGitHubCopilot (line 327) | async function loginGitHubCopilot(options: {
method login (line 372) | async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
method refreshToken (line 381) | async refreshToken(credentials: OAuthCredentials): Promise<OAuthCredenti...
method getApiKey (line 386) | getApiKey(credentials: OAuthCredentials): string {
method modifyModels (line 390) | modifyModels(models: Model<Api>[], credentials: OAuthCredentials): Model...
FILE: packages/ai/src/utils/oauth/google-antigravity.ts
type AntigravityCredentials (line 14) | type AntigravityCredentials = OAuthCredentials & {
constant CLIENT_ID (line 28) | const CLIENT_ID = decode(
constant CLIENT_SECRET (line 31) | const CLIENT_SECRET = decode("R09DU1BYLUs1OEZXUjQ4NkxkTEoxbUxCOHNYQzR6Nn...
constant REDIRECT_URI (line 32) | const REDIRECT_URI = "http://localhost:51121/oauth-callback";
constant SCOPES (line 35) | const SCOPES = [
constant AUTH_URL (line 43) | const AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
constant TOKEN_URL (line 44) | const TOKEN_URL = "https://oauth2.googleapis.com/token";
constant DEFAULT_PROJECT_ID (line 47) | const DEFAULT_PROJECT_ID = "rising-fact-p41fc";
type CallbackServerInfo (line 49) | type CallbackServerInfo = {
function getNodeCreateServer (line 58) | async function getNodeCreateServer(): Promise<typeof import("node:http")...
function startCallbackServer (line 67) | async function startCallbackServer(): Promise<CallbackServerInfo> {
function parseRedirectUrl (line 128) | function parseRedirectUrl(input: string): { code?: string; state?: strin...
type LoadCodeAssistPayload (line 144) | interface LoadCodeAssistPayload {
function discoverProject (line 153) | async function discoverProject(accessToken: string, onProgress?: (messag...
function getUserEmail (line 213) | async function getUserEmail(accessToken: string): Promise<string | undef...
function refreshAntigravityToken (line 234) | async function refreshAntigravityToken(refreshToken: string, projectId: ...
function loginAntigravity (line 273) | async function loginAntigravity(
method login (line 437) | async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
method refreshToken (line 441) | async refreshToken(credentials: OAuthCredentials): Promise<OAuthCredenti...
method getApiKey (line 449) | getApiKey(credentials: OAuthCredentials): string {
FILE: packages/ai/src/utils/oauth/google-gemini-cli.ts
type GeminiCredentials (line 14) | type GeminiCredentials = OAuthCredentials & {
constant CLIENT_ID (line 27) | const CLIENT_ID = decode(
constant CLIENT_SECRET (line 30) | const CLIENT_SECRET = decode("R09DU1BYLTR1SGdNUG0tMW83U2stZ2VWNkN1NWNsWE...
constant REDIRECT_URI (line 31) | const REDIRECT_URI = "http://localhost:8085/oauth2callback";
constant SCOPES (line 32) | const SCOPES = [
constant AUTH_URL (line 37) | const AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
constant TOKEN_URL (line 38) | const TOKEN_URL = "https://oauth2.googleapis.com/token";
constant CODE_ASSIST_ENDPOINT (line 39) | const CODE_ASSIST_ENDPOINT = "https://cloudcode-pa.googleapis.com";
type CallbackServerInfo (line 41) | type CallbackServerInfo = {
function getNodeCreateServer (line 50) | async function getNodeCreateServer(): Promise<typeof import("node:http")...
function startCallbackServer (line 59) | async function startCallbackServer(): Promise<CallbackServerInfo> {
function parseRedirectUrl (line 120) | function parseRedirectUrl(input: string): { code?: string; state?: strin...
type LoadCodeAssistPayload (line 136) | interface LoadCodeAssistPayload {
type LongRunningOperationResponse (line 145) | interface LongRunningOperationResponse {
constant TIER_FREE (line 154) | const TIER_FREE = "free-tier";
constant TIER_LEGACY (line 155) | const TIER_LEGACY = "legacy-tier";
constant TIER_STANDARD (line 156) | const TIER_STANDARD = "standard-tier";
type GoogleRpcErrorResponse (line 158) | interface GoogleRpcErrorResponse {
function wait (line 167) | function wait(ms: number): Promise<void> {
function getDefaultTier (line 174) | function getDefaultTier(allowedTiers?: Array<{ id?: string; isDefault?: ...
function isVpcScAffectedUser (line 180) | function isVpcScAffectedUser(payload: unknown): boolean {
function pollOperation (line 191) | async function pollOperation(
function discoverProject (line 224) | async function discoverProject(accessToken: string, onProgress?: (messag...
function getUserEmail (line 355) | async function getUserEmail(accessToken: string): Promise<string | undef...
function refreshGoogleCloudToken (line 376) | async function refreshGoogleCloudToken(refreshToken: string, projectId: ...
function loginGeminiCli (line 415) | async function loginGeminiCli(
method login (line 579) | async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
method refreshToken (line 583) | async refreshToken(credentials: OAuthCredentials): Promise<OAuthCredenti...
method getApiKey (line 591) | getApiKey(credentials: OAuthCredentials): string {
FILE: packages/ai/src/utils/oauth/index.ts
constant BUILT_IN_OAUTH_PROVIDERS (line 42) | const BUILT_IN_OAUTH_PROVIDERS: OAuthProviderInterface[] = [
function getOAuthProvider (line 57) | function getOAuthProvider(id: OAuthProviderId): OAuthProviderInterface |...
function registerOAuthProvider (line 64) | function registerOAuthProvider(provider: OAuthProviderInterface): void {
function unregisterOAuthProvider (line 74) | function unregisterOAuthProvider(id: string): void {
function resetOAuthProviders (line 86) | function resetOAuthProviders(): void {
function getOAuthProviders (line 96) | function getOAuthProviders(): OAuthProviderInterface[] {
function getOAuthProviderInfoList (line 103) | function getOAuthProviderInfoList(): OAuthProviderInfo[] {
function refreshOAuthToken (line 119) | async function refreshOAuthToken(
function getOAuthApiKey (line 137) | async function getOAuthApiKey(
FILE: packages/ai/src/utils/oauth/oauth-page.ts
constant LOGO_SVG (line 1) | const LOGO_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8...
function escapeHtml (line 3) | function escapeHtml(value: string): string {
function renderPage (line 12) | function renderPage(options: { title: string; heading: string; message: ...
function oauthSuccessHtml (line 94) | function oauthSuccessHtml(message: string): string {
function oauthErrorHtml (line 102) | function oauthErrorHtml(message: string, details?: string): string {
FILE: packages/ai/src/utils/oauth/openai-codex.ts
constant CLIENT_ID (line 24) | const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
constant AUTHORIZE_URL (line 25) | const AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize";
constant TOKEN_URL (line 26) | const TOKEN_URL = "https://auth.openai.com/oauth/token";
constant REDIRECT_URI (line 27) | const REDIRECT_URI = "http://localhost:1455/auth/callback";
constant SCOPE (line 28) | const SCOPE = "openid profile email offline_access";
constant JWT_CLAIM_PATH (line 29) | const JWT_CLAIM_PATH = "https://api.openai.com/auth";
type TokenSuccess (line 31) | type TokenSuccess = { type: "success"; access: string; refresh: string; ...
type TokenFailure (line 32) | type TokenFailure = { type: "failed" };
type TokenResult (line 33) | type TokenResult = TokenSuccess | TokenFailure;
type JwtPayload (line 35) | type JwtPayload = {
function createState (line 42) | function createState(): string {
function parseAuthorizationInput (line 49) | function parseAuthorizationInput(input: string): { code?: string; state?...
function decodeJwt (line 79) | function decodeJwt(token: string): JwtPayload | null {
function exchangeAuthorizationCode (line 91) | async function exchangeAuthorizationCode(
function refreshAccessToken (line 133) | async function refreshAccessToken(refreshToken: string): Promise<TokenRe...
function createAuthorizationFlow (line 174) | async function createAuthorizationFlow(
type OAuthServerInfo (line 195) | type OAuthServerInfo = {
function startLocalOAuthServer (line 201) | function startLocalOAuthServer(state: string): Promise<OAuthServerInfo> {
function getAccountId (line 282) | function getAccountId(accessToken: string): string | null {
function loginOpenAICodex (line 300) | async function loginOpenAICodex(options: {
function refreshOpenAICodexToken (line 410) | async function refreshOpenAICodexToken(refreshToken: string): Promise<OA...
method login (line 434) | async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
method refreshToken (line 443) | async refreshToken(credentials: OAuthCredentials): Promise<OAuthCredenti...
method getApiKey (line 447) | getApiKey(credentials: OAuthCredentials): string {
FILE: packages/ai/src/utils/oauth/pkce.ts
function base64urlEncode (line 9) | function base64urlEncode(bytes: Uint8Array): string {
function generatePKCE (line 21) | async function generatePKCE(): Promise<{ verifier: string; challenge: st...
FILE: packages/ai/src/utils/oauth/types.ts
type OAuthCredentials (line 3) | type OAuthCredentials = {
type OAuthProviderId (line 10) | type OAuthProviderId = string;
type OAuthProvider (line 13) | type OAuthProvider = OAuthProviderId;
type OAuthPrompt (line 15) | type OAuthPrompt = {
type OAuthAuthInfo (line 21) | type OAuthAuthInfo = {
type OAuthLoginCallbacks (line 26) | interface OAuthLoginCallbacks {
type OAuthProviderInterface (line 34) | interface OAuthProviderInterface {
type OAuthProviderInfo (line 55) | interface OAuthProviderInfo {
FILE: packages/ai/src/utils/overflow.ts
constant OVERFLOW_PATTERNS (line 27) | const OVERFLOW_PATTERNS = [
function isContextOverflow (line 92) | function isContextOverflow(message: AssistantMessage, contextWindow?: nu...
function getOverflowPatterns (line 121) | function getOverflowPatterns(): RegExp[] {
FILE: packages/ai/src/utils/sanitize-unicode.ts
function sanitizeSurrogates (line 21) | function sanitizeSurrogates(text: string): string {
FILE: packages/ai/src/utils/typebox-helpers.ts
function StringEnum (line 14) | function StringEnum<T extends readonly string[]>(
FILE: packages/ai/src/utils/validation.ts
function canUseRuntimeCodegen (line 14) | function canUseRuntimeCodegen(): boolean {
function validateToolCall (line 49) | function validateToolCall(tools: Tool[], toolCall: ToolCall): any {
function validateToolArguments (line 64) | function validateToolArguments(tool: Tool, toolCall: ToolCall): any {
FILE: packages/ai/test/abort.test.ts
type StreamOptionsWithExtras (line 6) | type StreamOptionsWithExtras = StreamOptions & Record<string, unknown>;
function testAbortSignal (line 18) | async function testAbortSignal<TApi extends Api>(llm: Model<TApi>, optio...
function testImmediateAbort (line 62) | async function testImmediateAbort<TApi extends Api>(llm: Model<TApi>, op...
function testAbortThenNewMessage (line 75) | async function testAbortThenNewMessage<TApi extends Api>(llm: Model<TApi...
FILE: packages/ai/test/anthropic-oauth.test.ts
function jsonResponse (line 4) | function jsonResponse(body: unknown, status: number = 200): Response {
function getUrl (line 13) | function getUrl(input: unknown): string {
function getJsonBody (line 26) | function getJsonBody(init?: RequestInit): Record<string, string> {
FILE: packages/ai/test/azure-utils.ts
function parseDeploymentNameMap (line 5) | function parseDeploymentNameMap(value: string | undefined): Map<string, ...
function hasAzureOpenAICredentials (line 18) | function hasAzureOpenAICredentials(): boolean {
function resolveAzureDeploymentName (line 24) | function resolveAzureDeploymentName(modelId: string): string | undefined {
FILE: packages/ai/test/bedrock-utils.ts
function hasBedrockCredentials (line 12) | function hasBedrockCredentials(): boolean {
FILE: packages/ai/test/context-overflow.test.ts
constant LOREM_IPSUM (line 35) | const LOREM_IPSUM = `Lorem ipsum dolor sit amet, consectetur adipiscing ...
function generateOverflowContent (line 39) | function generateOverflowContent(contextWindow: number): string {
type OverflowResult (line 46) | interface OverflowResult {
function testContextOverflow (line 57) | async function testContextOverflow(model: Model<any>, apiKey: string): P...
function logResult (line 87) | function logResult(result: OverflowResult) {
FILE: packages/ai/test/cross-provider-handoff.test.ts
type ProviderModelPair (line 46) | interface ProviderModelPair {
constant PROVIDER_MODEL_PAIRS (line 53) | const PROVIDER_MODEL_PAIRS: ProviderModelPair[] = [
type CachedContext (line 110) | interface CachedContext {
function getApiKey (line 122) | async function getApiKey(provider: string): Promise<string | undefined> {
function hasApiKey (line 131) | function hasApiKey(provider: string): boolean {
function hasAnyApiKey (line 141) | function hasAnyApiKey(): boolean {
function dumpFailurePayload (line 145) | function dumpFailurePayload(params: { label: string; error: string; payl...
function generateContext (line 161) | async function generateContext(
FILE: packages/ai/test/empty.test.ts
type StreamOptionsWithExtras (line 6) | type StreamOptionsWithExtras = StreamOptions & Record<string, unknown>;
function testEmptyMessage (line 22) | async function testEmptyMessage<TApi extends Api>(llm: Model<TApi>, opti...
function testEmptyStringMessage (line 47) | async function testEmptyStringMessage<TApi extends Api>(llm: Model<TApi>...
function testWhitespaceOnlyMessage (line 72) | async function testWhitespaceOnlyMessage<TApi extends Api>(llm: Model<TA...
function testEmptyAssistantMessage (line 97) | async function testEmptyAssistantMessage<TApi extends Api>(llm: Model<TA...
FILE: packages/ai/test/github-copilot-anthropic.test.ts
method [Symbol.asyncIterator] (line 12) | async *[Symbol.asyncIterator]() {
class FakeAnthropic (line 30) | class FakeAnthropic {
method constructor (line 31) | constructor(opts: Record<string, unknown>) {
FILE: packages/ai/test/github-copilot-oauth.test.ts
function jsonResponse (line 4) | function jsonResponse(body: unknown, status: number = 200): Response {
function getUrl (line 13) | function getUrl(input: unknown): string {
FILE: packages/ai/test/google-gemini-cli-claude-thinking-header.test.ts
method start (line 24) | start(controller) {
FILE: packages/ai/test/google-gemini-cli-empty-stream.test.ts
method start (line 15) | start(controller) {
method start (line 40) | start(controller) {
FILE: packages/ai/test/google-shared-gemini3-unsigned-tool-call.test.ts
constant SKIP_THOUGHT_SIGNATURE (line 5) | const SKIP_THOUGHT_SIGNATURE = "skip_thought_signature_validator";
function makeGemini3Model (line 7) | function makeGemini3Model(id = "gemini-3-pro-preview"): Model<"google-ge...
FILE: packages/ai/test/google-shared-image-tool-result-routing.test.ts
function makeModel (line 5) | function makeModel<TApi extends "google-generative-ai" | "google-gemini-...
function makeContext (line 24) | function makeContext(model: { api: string; provider: string; id: string ...
FILE: packages/ai/test/google-tool-call-missing-args.test.ts
method start (line 48) | start(controller) {
FILE: packages/ai/test/google-vertex-api-key-resolution.test.ts
class GoogleGenAI (line 8) | class GoogleGenAI {
method constructor (line 28) | constructor(config: Record<string, unknown>) {
FILE: packages/ai/test/image-tool-result.test.ts
type StreamOptionsWithExtras (line 9) | type StreamOptionsWithExtras = StreamOptions & Record<string, unknown>;
function handleToolWithImageResult (line 32) | async function handleToolWithImageResult<TApi extends Api>(model: Model<...
function handleToolWithTextAndImageResult (line 120) | async function handleToolWithTextAndImageResult<TApi extends Api>(
FILE: packages/ai/test/interleaved-thinking.test.ts
type CalculatorOperation (line 24) | type CalculatorOperation = "add" | "subtract" | "multiply" | "divide";
type CalculatorArguments (line 26) | type CalculatorArguments = {
function asCalculatorArguments (line 32) | function asCalculatorArguments(args: ToolCall["arguments"]): CalculatorA...
function evaluateCalculatorCall (line 50) | function evaluateCalculatorCall(toolCall: ToolCall): number {
function assertSecondToolCallWithInterleavedThinking (line 64) | async function assertSecondToolCallWithInterleavedThinking<TApi extends ...
FILE: packages/ai/test/lazy-module-load.test.ts
constant SDK_SPECIFIERS (line 12) | const SDK_SPECIFIERS = [
type ProbeResult (line 20) | type ProbeResult = {
function runProbe (line 24) | function runProbe(action: string): ProbeResult {
FILE: packages/ai/test/oauth.ts
constant AUTH_PATH (line 14) | const AUTH_PATH = join(homedir(), ".pi", "agent", "auth.json");
type ApiKeyCredential (line 16) | type ApiKeyCredential = {
type OAuthCredentialEntry (line 21) | type OAuthCredentialEntry = {
type AuthCredential (line 25) | type AuthCredential = ApiKeyCredential | OAuthCredentialEntry;
type AuthStorage (line 27) | type AuthStorage = Record<string, AuthCredential>;
function loadAuthStorage (line 29) | function loadAuthStorage(): AuthStorage {
function saveAuthStorage (line 41) | function saveAuthStorage(storage: AuthStorage): void {
function resolveApiKey (line 58) | async function resolveApiKey(provider: string): Promise<string | undefin...
FILE: packages/ai/test/openai-codex-stream.test.ts
function mockToken (line 21) | function mockToken(): string {
function buildSSEPayload (line 29) | function buildSSEPayload({
method start (line 120) | start(controller) {
method start (line 196) | start(controller) {
method start (line 255) | start(controller) {
method start (line 349) | start(controller) {
method start (line 450) | start(controller) {
method start (line 546) | start(controller) {
FILE: packages/ai/test/openai-completions-tool-choice.test.ts
class FakeOpenAI (line 23) | class FakeOpenAI {
method [Symbol.asyncIterator] (line 29) | async *[Symbol.asyncIterator]() {
FILE: packages/ai/test/openai-completions-tool-result-images.test.ts
function buildToolResult (line 38) | function buildToolResult(toolCallId: string, timestamp: number): ToolRes...
FILE: packages/ai/test/openai-responses-tool-result-images.test.ts
type StreamOptionsWithExtras (line 12) | type StreamOptionsWithExtras = StreamOptions & Record<string, unknown>;
function isRecord (line 27) | function isRecord(value: unknown): value is Record<string, unknown> {
function isResponsePayload (line 31) | function isResponsePayload(value: unknown): value is { input: unknown[] } {
function isFunctionCallOutputItem (line 35) | function isFunctionCallOutputItem(
function isInputTextItem (line 41) | function isInputTextItem(value: unknown): value is { type: "input_text";...
function isInputImageItem (line 45) | function isInputImageItem(value: unknown): value is { type: "input_image...
function verifyToolResultImagesStayInFunctionCallOutput (line 49) | async function verifyToolResultImagesStayInFunctionCallOutput<TApi exten...
FILE: packages/ai/test/responseid.test.ts
type StreamOptionsWithExtras (line 8) | type StreamOptionsWithExtras = StreamOptions & Record<string, unknown>;
function expectResponseId (line 18) | async function expectResponseId<TApi extends Api>(model: Model<TApi>, op...
FILE: packages/ai/test/stream.test.ts
type StreamOptionsWithExtras (line 11) | type StreamOptionsWithExtras = StreamOptions & Record<string, unknown>;
function basicTextGeneration (line 48) | async function basicTextGeneration<TApi extends Api>(model: Model<TApi>,...
function handleToolCall (line 77) | async function handleToolCall<TApi extends Api>(model: Model<TApi>, opti...
function handleStreaming (line 155) | async function handleStreaming<TApi extends Api>(model: Model<TApi>, opt...
function handleThinking (line 185) | async function handleThinking<TApi extends Api>(model: Model<TApi>, opti...
function handleImage (line 222) | async function handleImage<TApi extends Api>(model: Model<TApi>, options...
function multiTurn (line 269) | async function multiTurn<TApi extends Api>(model: Model<TApi>, options?:...
FILE: packages/ai/test/tokens.test.ts
type StreamOptionsWithExtras (line 6) | type StreamOptionsWithExtras = StreamOptions & Record<string, unknown>;
function testTokensOnAbort (line 22) | async function testTokensOnAbort<TApi extends Api>(llm: Model<TApi>, opt...
FILE: packages/ai/test/tool-call-id-normalization.test.ts
function buildPrefilledMessages (line 190) | function buildPrefilledMessages(): Message[] {
FILE: packages/ai/test/tool-call-without-result.test.ts
type StreamOptionsWithExtras (line 7) | type StreamOptionsWithExtras = StreamOptions & Record<string, unknown>;
function testToolCallWithoutResult (line 34) | async function testToolCallWithoutResult<TApi extends Api>(model: Model<...
FILE: packages/ai/test/total-tokens.test.ts
type StreamOptionsWithExtras (line 20) | type StreamOptionsWithExtras = StreamOptions & Record<string, unknown>;
constant LONG_SYSTEM_PROMPT (line 37) | const LONG_SYSTEM_PROMPT = `You are a helpful assistant. Be concise in y...
function testTotalTokensWithCache (line 49) | async function testTotalTokensWithCache<TApi extends Api>(
function logUsage (line 88) | function logUsage(label: string, usage: Usage) {
function assertTotalTokensEqualsComponents (line 97) | function assertTotalTokensEqualsComponents(usage: Usage) {
FILE: packages/ai/test/transform-messages-copilot-openai-to-anthropic.test.ts
function anthropicNormalizeToolCallId (line 6) | function anthropicNormalizeToolCallId(
function makeCopilotClaudeModel (line 14) | function makeCopilotClaudeModel(): Model<"anthropic-messages"> {
FILE: packages/ai/test/unicode-surrogate.test.ts
type StreamOptionsWithExtras (line 7) | type StreamOptionsWithExtras = StreamOptions & Record<string, unknown>;
function testEmojiInToolResults (line 37) | async function testEmojiInToolResults<TApi extends Api>(llm: Model<TApi>...
function testRealWorldLinkedInData (line 124) | async function testRealWorldLinkedInData<TApi extends Api>(llm: Model<TA...
function testUnpairedHighSurrogate (line 213) | async function testUnpairedHighSurrogate<TApi extends Api>(llm: Model<TA...
FILE: packages/ai/test/xhigh.test.ts
function makeContext (line 6) | function makeContext(): Context {
FILE: packages/coding-agent/examples/extensions/antigravity-image-gen.ts
constant PROVIDER (line 36) | const PROVIDER = "google-antigravity";
constant ASPECT_RATIOS (line 38) | const ASPECT_RATIOS = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", ...
type AspectRatio (line 40) | type AspectRatio = (typeof ASPECT_RATIOS)[number];
constant DEFAULT_MODEL (line 42) | const DEFAULT_MODEL = "gemini-3-pro-image";
constant DEFAULT_ASPECT_RATIO (line 43) | const DEFAULT_ASPECT_RATIO: AspectRatio = "1:1";
constant DEFAULT_SAVE_MODE (line 44) | const DEFAULT_SAVE_MODE = "none";
constant SAVE_MODES (line 46) | const SAVE_MODES = ["none", "project", "global", "custom"] as const;
type SaveMode (line 47) | type SaveMode = (typeof SAVE_MODES)[number];
constant ANTIGRAVITY_ENDPOINT (line 49) | const ANTIGRAVITY_ENDPOINT = "https://daily-cloudcode-pa.sandbox.googlea...
constant DEFAULT_ANTIGRAVITY_VERSION (line 51) | const DEFAULT_ANTIGRAVITY_VERSION = "1.18.3";
constant ANTIGRAVITY_HEADERS (line 53) | const ANTIGRAVITY_HEADERS = {
constant IMAGE_SYSTEM_INSTRUCTION (line 63) | const IMAGE_SYSTEM_INSTRUCTION =
constant TOOL_PARAMS (line 66) | const TOOL_PARAMS = Type.Object({
type ToolParams (line 82) | type ToolParams = Static<typeof TOOL_PARAMS>;
type CloudCodeAssistRequest (line 84) | interface CloudCodeAssistRequest {
type CloudCodeAssistResponseChunk (line 104) | interface CloudCodeAssistResponseChunk {
type Content (line 131) | interface Content {
type Part (line 136) | interface Part {
type ParsedCredentials (line 144) | interface ParsedCredentials {
type ExtensionConfig (line 149) | interface ExtensionConfig {
type SaveConfig (line 154) | interface SaveConfig {
function parseOAuthCredentials (line 159) | function parseOAuthCredentials(raw: string): ParsedCredentials {
function readConfigFile (line 172) | function readConfigFile(path: string): ExtensionConfig {
function loadConfig (line 185) | function loadConfig(cwd: string): ExtensionConfig {
function resolveSaveConfig (line 192) | function resolveSaveConfig(params: ToolParams, cwd: string): SaveConfig {
function imageExtension (line 222) | function imageExtension(mimeType: string): string {
function saveImage (line 230) | async function saveImage(base64Data: string, mimeType: string, outputDir...
function buildRequest (line 242) | function buildRequest(prompt: string, model: string, projectId: string, ...
function parseSseForImage (line 274) | async function parseSseForImage(
function getCredentials (line 343) | async function getCredentials(ctx: {
function antigravityImageGen (line 353) | function antigravityImageGen(pi: ExtensionAPI) {
FILE: packages/coding-agent/examples/extensions/built-in-tool-renderer.ts
method execute (line 41) | async execute(toolCallId, params, signal, onUpdate) {
method renderCall (line 45) | renderCall(args, theme) {
method renderResult (line 57) | renderResult(result, { expanded, isPartial }, theme) {
method execute (line 100) | async execute(toolCallId, params, signal, onUpdate) {
method renderCall (line 104) | renderCall(args, theme) {
method renderResult (line 114) | renderResult(result, { expanded, isPartial }, theme) {
method execute (line 159) | async execute(toolCallId, params, signal, onUpdate) {
method renderCall (line 163) | renderCall(args, theme) {
method renderResult (line 169) | renderResult(result, { expanded, isPartial }, theme) {
method execute (line 223) | async execute(toolCallId, params, signal, onUpdate) {
method renderCall (line 227) | renderCall(args, theme) {
method renderResult (line 235) | renderResult(result, { isPartial }, theme) {
FILE: packages/coding-agent/examples/extensions/claude-rules.ts
function findMarkdownFiles (line 27) | function findMarkdownFiles(dir: string, basePath: string = ""): string[] {
function claudeRulesExtension (line 49) | function claudeRulesExtension(pi: ExtensionAPI) {
FILE: packages/coding-agent/examples/extensions/commands.ts
function commandsExtension (line 15) | function commandsExtension(pi: ExtensionAPI) {
FILE: packages/coding-agent/examples/extensions/custom-footer.ts
method invalidate (line 29) | invalidate() {}
method render (line 30) | render(width: number): string[] {
FILE: packages/coding-agent/examples/extensions/custom-header.ts
function getPiMascot (line 13) | function getPiMascot(theme: Theme): string[] {
method render (line 53) | render(_width: number): string[] {
method invalidate (line 59) | invalidate() {}
FILE: packages/coding-agent/examples/extensions/custom-provider-anthropic/index.ts
constant CLIENT_ID (line 53) | const CLIENT_ID = decode("OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZj...
constant AUTHORIZE_URL (line 54) | const AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
constant TOKEN_URL (line 55) | const TOKEN_URL = "https://console.anthropic.com/v1/oauth/token";
constant REDIRECT_URI (line 56) | const REDIRECT_URI = "https://console.anthropic.com/oauth/code/callback";
constant SCOPES (line 57) | const SCOPES = "org:create_api_key user:profile user:inference";
function generatePKCE (line 59) | async function generatePKCE(): Promise<{ verifier: string; challenge: st...
function loginAnthropic (line 78) | async function loginAnthropic(callbacks: OAuthLoginCallbacks): Promise<O...
function refreshAnthropicToken (line 127) | async function refreshAnthropicToken(credentials: OAuthCredentials): Pro...
function isOAuthToken (line 180) | function isOAuthToken(apiKey: string): boolean {
function sanitizeSurrogates (line 184) | function sanitizeSurrogates(text: string): string {
function convertContentBlocks (line 188) | function convertContentBlocks(
function convertMessages (line 217) | function convertMessages(messages: Message[], isOAuth: boolean, _tools?:...
function convertTools (line 307) | function convertTools(tools: Tool[], isOAuth: boolean): any[] {
function mapStopReason (line 319) | function mapStopReason(reason: string): StopReason {
function streamCustomAnthropic (line 334) | function streamCustomAnthropic(
FILE: packages/coding-agent/examples/extensions/custom-provider-gitlab-duo/index.ts
constant GITLAB_COM_URL (line 30) | const GITLAB_COM_URL = "https://gitlab.com";
constant AI_GATEWAY_URL (line 31) | const AI_GATEWAY_URL = "https://cloud.gitlab.com";
constant ANTHROPIC_PROXY_URL (line 32) | const ANTHROPIC_PROXY_URL = `${AI_GATEWAY_URL}/ai/v1/proxy/anthropic/`;
constant OPENAI_PROXY_URL (line 33) | const OPENAI_PROXY_URL = `${AI_GATEWAY_URL}/ai/v1/proxy/openai/v1`;
constant BUNDLED_CLIENT_ID (line 35) | const BUNDLED_CLIENT_ID = "da4edff2e6ebd2bc3208611e2768bc1c1dd7be791dc5f...
constant OAUTH_SCOPES (line 36) | const OAUTH_SCOPES = ["api"];
constant REDIRECT_URI (line 37) | const REDIRECT_URI = "http://127.0.0.1:8080/callback";
constant DIRECT_ACCESS_TTL (line 38) | const DIRECT_ACCESS_TTL = 25 * 60 * 1000;
type Backend (line 44) | type Backend = "anthropic" | "openai";
type GitLabModel (line 46) | interface GitLabModel {
constant MODELS (line 58) | const MODELS: GitLabModel[] = [
constant MODEL_MAP (line 129) | const MODEL_MAP = new Map(MODELS.map((m) => [m.id, m]));
type DirectAccessToken (line 135) | interface DirectAccessToken {
function getDirectAccessToken (line 143) | async function getDirectAccessToken(gitlabAccessToken: string): Promise<...
function invalidateDirectAccessToken (line 170) | function invalidateDirectAccessToken() {
function generatePKCE (line 178) | async function generatePKCE(): Promise<{ verifier: string; challenge: st...
function loginGitLab (line 193) | async function loginGitLab(callbacks: OAuthLoginCallbacks): Promise<OAut...
function refreshGitLabToken (line 237) | async function refreshGitLabToken(credentials: OAuthCredentials): Promis...
function streamGitLabDuo (line 266) | function streamGitLabDuo(
FILE: packages/coding-agent/examples/extensions/custom-provider-gitlab-duo/test.ts
constant MODEL_MAP (line 17) | const MODEL_MAP = new Map(MODELS.map((m) => [m.id, m]));
function main (line 19) | async function main() {
FILE: packages/coding-agent/examples/extensions/custom-provider-qwen-cli/index.ts
constant QWEN_DEVICE_CODE_ENDPOINT (line 19) | const QWEN_DEVICE_CODE_ENDPOINT = "https://chat.qwen.ai/api/v1/oauth2/de...
constant QWEN_TOKEN_ENDPOINT (line 20) | const QWEN_TOKEN_ENDPOINT = "https://chat.qwen.ai/api/v1/oauth2/token";
constant QWEN_CLIENT_ID (line 21) | const QWEN_CLIENT_ID = "f0304373b74a44d2b584a3fb70ca9e56";
constant QWEN_SCOPE (line 22) | const QWEN_SCOPE = "openid profile email model.completion";
constant QWEN_GRANT_TYPE (line 23) | const QWEN_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
constant QWEN_DEFAULT_BASE_URL (line 24) | const QWEN_DEFAULT_BASE_URL = "https://dashscope.aliyuncs.com/compatible...
constant QWEN_POLL_INTERVAL_MS (line 25) | const QWEN_POLL_INTERVAL_MS = 2000;
function generatePKCE (line 31) | async function generatePKCE(): Promise<{ verifier: string; challenge: st...
type DeviceCodeResponse (line 54) | interface DeviceCodeResponse {
type TokenResponse (line 63) | interface TokenResponse {
function abortableSleep (line 71) | function abortableSleep(ms: number, signal?: AbortSignal): Promise<void> {
function startDeviceFlow (line 89) | async function startDeviceFlow(): Promise<{ deviceCode: DeviceCodeRespon...
function pollForToken (line 126) | async function pollForToken(
function loginQwen (line 213) | async function loginQwen(callbacks: OAuthLoginCallbacks): Promise<OAuthC...
function refreshQwenToken (line 244) | async function refreshQwenToken(credentials: OAuthCredentials): Promise<...
function getQwenBaseUrl (line 281) | function getQwenBaseUrl(resourceUrl?: string): string {
FILE: packages/coding-agent/examples/extensions/dirty-repo-guard.ts
function checkDirtyRepo (line 10) | async function checkDirtyRepo(
FILE: packages/coding-agent/examples/extensions/doom-overlay/doom-component.ts
function renderHalfBlock (line 13) | function renderHalfBlock(
class DoomOverlayComponent (line 47) | class DoomOverlayComponent implements Component {
method constructor (line 56) | constructor(tui: TUI, engine: DoomEngine, onExit: () => void, resume =...
method startGameLoop (line 70) | private startGameLoop(): void {
method handleInput (line 83) | handleInput(data: string): void {
method render (line 104) | render(width: number): string[] {
method invalidate (line 124) | invalidate(): void {}
method dispose (line 126) | dispose(): void {
FILE: packages/coding-agent/examples/extensions/doom-overlay/doom-engine.ts
type DoomModule (line 10) | interface DoomModule {
class DoomEngine (line 27) | class DoomEngine {
method constructor (line 35) | constructor(wadPath: string) {
method width (line 39) | get width(): number {
method height (line 43) | get height(): number {
method init (line 47) | async init(): Promise<void> {
method initDoom (line 102) | private initDoom(): void {
method tick (line 133) | tick(): void {
method getFrameRGBA (line 142) | getFrameRGBA(): Uint8Array {
method pushKey (line 165) | pushKey(pressed: boolean, key: number): void {
method isInitialized (line 170) | isInitialized(): boolean {
FILE: packages/coding-agent/examples/extensions/doom-overlay/doom-keys.ts
function mapKeyToDoom (line 43) | function mapKeyToDoom(data: string): number[] {
FILE: packages/coding-agent/examples/extensions/doom-overlay/doom/build/doom.js
function locateFile (line 8) | function locateFile(path){if(Module["locateFile"]){return Module["locate...
function updateMemoryViews (line 8) | function updateMemoryViews(){var b=wasmMemory.buffer;Module["HEAP8"]=HEA...
function preRun (line 8) | function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="func...
function initRuntime (line 8) | function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!...
function postRun (line 8) | function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="f...
function getUniqueRunDependency (line 8) | function getUniqueRunDependency(id){return id}
function addRunDependency (line 8) | function addRunDependency(id){runDependencies++;Module["monitorRunDepend...
function removeRunDependency (line 8) | function removeRunDependency(id){runDependencies--;Module["monitorRunDep...
function abort (line 8) | function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";...
function findWasmBinary (line 8) | function findWasmBinary(){return locateFile("doom.wasm")}
function getBinarySync (line 8) | function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return...
function getWasmBinary (line 8) | async function getWasmBinary(binaryFile){if(!wasmBinary){try{var respons...
function instantiateArrayBuffer (line 8) | async function instantiateArrayBuffer(binaryFile,imports){try{var binary...
function instantiateAsync (line 8) | async function instantiateAsync(binary,binaryFile,imports){if(!binary&&t...
function getWasmImports (line 8) | function getWasmImports(){return{env:wasmImports,wasi_snapshot_preview1:...
function createWasm (line 8) | async function createWasm(){function receiveInstance(instance,module){wa...
class ExitStatus (line 8) | class ExitStatus{name="ExitStatus";constructor(status){this.message=`Pro...
method constructor (line 8) | constructor(status){this.message=`Program terminated with exit(${statu...
function getValue (line 8) | function getValue(ptr,type="i8"){if(type.endsWith("*"))type="*";switch(t...
function setValue (line 8) | function setValue(ptr,value,type="i8"){if(type.endsWith("*"))type="*";sw...
function trim (line 8) | function trim(arr){var start=0;for(;start<arr.length;start++){if(arr[sta...
method init (line 8) | init(){}
method shutdown (line 8) | shutdown(){}
method register (line 8) | register(dev,ops){TTY.ttys[dev]={input:[],output:[],ops};FS.registerDevi...
method open (line 8) | open(stream){var tty=TTY.ttys[stream.node.rdev];if(!tty){throw new FS.Er...
method close (line 8) | close(stream){stream.tty.ops.fsync(stream.tty)}
method fsync (line 8) | fsync(stream){stream.tty.ops.fsync(stream.tty)}
method read (line 8) | read(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.ge...
method write (line 8) | write(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.p...
method get_char (line 8) | get_char(tty){return FS_stdin_getChar()}
method put_char (line 8) | put_char(tty,val){if(val===null||val===10){out(UTF8ArrayToString(tty.out...
method fsync (line 8) | fsync(tty){if(tty.output?.length>0){out(UTF8ArrayToString(tty.output));t...
method ioctl_tcgets (line 8) | ioctl_tcgets(tty){return{c_iflag:25856,c_oflag:5,c_cflag:191,c_lflag:353...
method ioctl_tcsets (line 8) | ioctl_tcsets(tty,optional_actions,data){return 0}
method ioctl_tiocgwinsz (line 8) | ioctl_tiocgwinsz(tty){return[24,80]}
method put_char (line 8) | put_char(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.out...
method fsync (line 8) | fsync(tty){if(tty.output?.length>0){err(UTF8ArrayToString(tty.output));t...
method mount (line 8) | mount(mount){return MEMFS.createNode(null,"/",16895,0)}
method createNode (line 8) | createNode(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){...
method getFileDataAsTypedArray (line 8) | getFileDataAsTypedArray(node){if(!node.contents)return new Uint8Array(0)...
method expandFileStorage (line 8) | expandFileStorage(node,newCapacity){var prevCapacity=node.contents?node....
method resizeFileStorage (line 8) | resizeFileStorage(node,newSize){if(node.usedBytes==newSize)return;if(new...
method getattr (line 8) | getattr(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr...
method setattr (line 8) | setattr(node,attr){for(const key of["mode","atime","mtime","ctime"]){if(...
method lookup (line 8) | lookup(parent,name){throw MEMFS.doesNotExistError}
method mknod (line 8) | mknod(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)}
method rename (line 8) | rename(old_node,new_dir,new_name){var new_node;try{new_node=FS.lookupNod...
method unlink (line 8) | unlink(parent,name){delete parent.contents[name];parent.ctime=parent.mti...
method rmdir (line 8) | rmdir(parent,name){var node=FS.lookupNode(parent,name);for(var i in node...
method readdir (line 8) | readdir(node){return[".","..",...Object.keys(node.contents)]}
method symlink (line 8) | symlink(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname...
method readlink (line 8) | readlink(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}ret...
method read (line 8) | read(stream,buffer,offset,length,position){var contents=stream.node.cont...
method write (line 8) | write(stream,buffer,offset,length,position,canOwn){if(buffer.buffer===HE...
method llseek (line 8) | llseek(stream,offset,whence){var position=offset;if(whence===1){position...
method mmap (line 8) | mmap(stream,length,position,prot,flags){if(!FS.isFile(stream.node.mode))...
method msync (line 8) | msync(stream,buffer,offset,length,mmapFlags){MEMFS.stream_ops.write(stre...
function processData (line 8) | function processData(byteArray){function finish(byteArray){preFinish?.()...
method constructor (line 8) | constructor(errno){this.errno=errno}
method object (line 8) | get object(){return this.node}
method object (line 8) | set object(val){this.node=val}
method isRead (line 8) | get isRead(){return(this.flags&2097155)!==1}
method isWrite (line 8) | get isWrite(){return(this.flags&2097155)!==0}
method isAppend (line 8) | get isAppend(){return this.flags&1024}
method flags (line 8) | get flags(){return this.shared.flags}
method flags (line 8) | set flags(val){this.shared.flags=val}
method position (line 8) | get position(){return this.shared.position}
method position (line 8) | set position(val){this.shared.position=val}
method constructor (line 8) | constructor(parent,name,mode,rdev){if(!parent){parent=this}this.parent=p...
method read (line 8) | get read(){return(this.mode&this.readMode)===this.readMode}
method read (line 8) | set read(val){val?this.mode|=this.readMode:this.mode&=~this.readMode}
method write (line 8) | get write(){return(this.mode&this.writeMode)===this.writeMode}
method write (line 8) | set write(val){val?this.mode|=this.writeMode:this.mode&=~this.writeMode}
method isFolder (line 8) | get isFolder(){return FS.isDir(this.mode)}
method isDevice (line 8) | get isDevice(){return FS.isChrdev(this.mode)}
method lookupPath (line 8) | lookupPath(path,opts={}){if(!path){throw new FS.ErrnoError(44)}opts.foll...
method getPath (line 8) | getPath(node){var path;while(true){if(FS.isRoot(node)){var mount=node.mo...
method hashName (line 8) | hashName(parentid,name){var hash=0;for(var i=0;i<name.length;i++){hash=(...
method hashAddNode (line 8) | hashAddNode(node){var hash=FS.hashName(node.parent.id,node.name);node.na...
method hashRemoveNode (line 8) | hashRemoveNode(node){var hash=FS.hashName(node.parent.id,node.name);if(F...
method lookupNode (line 8) | lookupNode(parent,name){var errCode=FS.mayLookup(parent);if(errCode){thr...
method createNode (line 8) | createNode(parent,name,mode,rdev){var node=new FS.FSNode(parent,name,mod...
method destroyNode (line 8) | destroyNode(node){FS.hashRemoveNode(node)}
method isRoot (line 8) | isRoot(node){return node===node.parent}
method isMountpoint (line 8) | isMountpoint(node){return!!node.mounted}
method isFile (line 8) | isFile(mode){return(mode&61440)===32768}
method isDir (line 8) | isDir(mode){return(mode&61440)===16384}
method isLink (line 8) | isLink(mode){return(mode&61440)===40960}
method isChrdev (line 8) | isChrdev(mode){return(mode&61440)===8192}
method isBlkdev (line 8) | isBlkdev(mode){return(mode&61440)===24576}
method isFIFO (line 8) | isFIFO(mode){return(mode&61440)===4096}
method isSocket (line 8) | isSocket(mode){return(mode&49152)===49152}
method flagsToPermissionString (line 8) | flagsToPermissionString(flag){var perms=["r","w","rw"][flag&3];if(flag&5...
method nodePermissions (line 8) | nodePermissions(node,perms){if(FS.ignorePermissions){return 0}if(perms.i...
method mayLookup (line 8) | mayLookup(dir){if(!FS.isDir(dir.mode))return 54;var errCode=FS.nodePermi...
method mayCreate (line 8) | mayCreate(dir,name){if(!FS.isDir(dir.mode)){return 54}try{var node=FS.lo...
method mayDelete (line 8) | mayDelete(dir,name,isdir){var node;try{node=FS.lookupNode(dir,name)}catc...
method mayOpen (line 8) | mayOpen(node,flags){if(!node){return 44}if(FS.isLink(node.mode)){return ...
method checkOpExists (line 8) | checkOpExists(op,err){if(!op){throw new FS.ErrnoError(err)}return op}
method nextfd (line 8) | nextfd(){for(var fd=0;fd<=FS.MAX_OPEN_FDS;fd++){if(!FS.streams[fd]){retu...
method getStreamChecked (line 8) | getStreamChecked(fd){var stream=FS.getStream(fd);if(!stream){throw new F...
method createStream (line 8) | createStream(stream,fd=-1){stream=Object.assign(new FS.FSStream,stream);...
method closeStream (line 8) | closeStream(fd){FS.streams[fd]=null}
method dupStream (line 8) | dupStream(origStream,fd=-1){var stream=FS.createStream(origStream,fd);st...
method doSetAttr (line 8) | doSetAttr(stream,node,attr){var setattr=stream?.stream_ops.setattr;var a...
method open (line 8) | open(stream){var device=FS.getDevice(stream.node.rdev);stream.stream_ops...
method llseek (line 8) | llseek(){throw new FS.ErrnoError(70)}
method registerDevice (line 8) | registerDevice(dev,ops){FS.devices[dev]={stream_ops:ops}}
method getMounts (line 8) | getMounts(mount){var mounts=[];var check=[mount];while(check.length){var...
method syncfs (line 8) | syncfs(populate,callback){if(typeof populate=="function"){callback=popul...
method mount (line 8) | mount(type,opts,mountpoint){var root=mountpoint==="/";var pseudo=!mountp...
method unmount (line 8) | unmount(mountpoint){var lookup=FS.lookupPath(mountpoint,{follow_mount:fa...
method lookup (line 8) | lookup(parent,name){return parent.node_ops.lookup(parent,name)}
method mknod (line 8) | mknod(path,mode,dev){var lookup=FS.lookupPath(path,{parent:true});var pa...
method statfs (line 8) | statfs(path){return FS.statfsNode(FS.lookupPath(path,{follow:true}).node)}
method statfsStream (line 8) | statfsStream(stream){return FS.statfsNode(stream.node)}
method statfsNode (line 8) | statfsNode(node){var rtn={bsize:4096,frsize:4096,blocks:1e6,bfree:5e5,ba...
method create (line 8) | create(path,mode=438){mode&=4095;mode|=32768;return FS.mknod(path,mode,0)}
method mkdir (line 8) | mkdir(path,mode=511){mode&=511|512;mode|=16384;return FS.mknod(path,mode...
method mkdirTree (line 8) | mkdirTree(path,mode){var dirs=path.split("/");var d="";for(var dir of di...
method mkdev (line 8) | mkdev(path,mode,dev){if(typeof dev=="undefined"){dev=mode;mode=438}mode|...
method symlink (line 8) | symlink(oldpath,newpath){if(!PATH_FS.resolve(oldpath)){throw new FS.Errn...
method rename (line 8) | rename(old_path,new_path){var old_dirname=PATH.dirname(old_path);var new...
method rmdir (line 8) | rmdir(path){var lookup=FS.lookupPath(path,{parent:true});var parent=look...
method readdir (line 8) | readdir(path){var lookup=FS.lookupPath(path,{follow:true});var node=look...
method unlink (line 8) | unlink(path){var lookup=FS.lookupPath(path,{parent:true});var parent=loo...
method readlink (line 8) | readlink(path){var lookup=FS.lookupPath(path);var link=lookup.node;if(!l...
method stat (line 8) | stat(path,dontFollow){var lookup=FS.lookupPath(path,{follow:!dontFollow}...
method fstat (line 8) | fstat(fd){var stream=FS.getStreamChecked(fd);var node=stream.node;var ge...
method lstat (line 8) | lstat(path){return FS.stat(path,true)}
method doChmod (line 8) | doChmod(stream,node,mode,dontFollow){FS.doSetAttr(stream,node,{mode:mode...
method chmod (line 8) | chmod(path,mode,dontFollow){var node;if(typeof path=="string"){var looku...
method lchmod (line 8) | lchmod(path,mode){FS.chmod(path,mode,true)}
method fchmod (line 8) | fchmod(fd,mode){var stream=FS.getStreamChecked(fd);FS.doChmod(stream,str...
method doChown (line 8) | doChown(stream,node,dontFollow){FS.doSetAttr(stream,node,{timestamp:Date...
method chown (line 8) | chown(path,uid,gid,dontFollow){var node;if(typeof path=="string"){var lo...
method lchown (line 8) | lchown(path,uid,gid){FS.chown(path,uid,gid,true)}
method fchown (line 8) | fchown(fd,uid,gid){var stream=FS.getStreamChecked(fd);FS.doChown(stream,...
method doTruncate (line 8) | doTruncate(stream,node,len){if(FS.isDir(node.mode)){throw new FS.ErrnoEr...
method truncate (line 8) | truncate(path,len){if(len<0){throw new FS.ErrnoError(28)}var node;if(typ...
method ftruncate (line 8) | ftruncate(fd,len){var stream=FS.getStreamChecked(fd);if(len<0||(stream.f...
method utime (line 8) | utime(path,atime,mtime){var lookup=FS.lookupPath(path,{follow:true});var...
method open (line 8) | open(path,flags,mode=438){if(path===""){throw new FS.ErrnoError(44)}flag...
method close (line 8) | close(stream){if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(stre...
method isClosed (line 8) | isClosed(stream){return stream.fd===null}
method llseek (line 8) | llseek(stream,offset,whence){if(FS.isClosed(stream)){throw new FS.ErrnoE...
method read (line 8) | read(stream,buffer,offset,length,position){if(length<0||position<0){thro...
method write (line 8) | write(stream,buffer,offset,length,position,canOwn){if(length<0||position...
method mmap (line 8) | mmap(stream,length,position,prot,flags){if((prot&2)!==0&&(flags&2)===0&&...
method msync (line 8) | msync(stream,buffer,offset,length,mmapFlags){if(!stream.stream_ops.msync...
method ioctl (line 8) | ioctl(stream,cmd,arg){if(!stream.stream_ops.ioctl){throw new FS.ErrnoErr...
method readFile (line 8) | readFile(path,opts={}){opts.flags=opts.flags||0;opts.encoding=opts.encod...
method writeFile (line 8) | writeFile(path,data,opts={}){opts.flags=opts.flags||577;var stream=FS.op...
method chdir (line 8) | chdir(path){var lookup=FS.lookupPath(path,{follow:true});if(lookup.node=...
method createDefaultDirectories (line 8) | createDefaultDirectories(){FS.mkdir("/tmp");FS.mkdir("/home");FS.mkdir("...
method createDefaultDevices (line 8) | createDefaultDevices(){FS.mkdir("/dev");FS.registerDevice(FS.makedev(1,3...
method createSpecialDirectories (line 8) | createSpecialDirectories(){FS.mkdir("/proc");var proc_self=FS.mkdir("/pr...
method createStandardStreams (line 8) | createStandardStreams(input,output,error){if(input){FS.createDevice("/de...
method staticInit (line 8) | staticInit(){FS.nameTable=new Array(4096);FS.mount(MEMFS,{},"/");FS.crea...
method init (line 8) | init(input,output,error){FS.initialized=true;input??=Module["stdin"];out...
method quit (line 8) | quit(){FS.initialized=false;for(var stream of FS.streams){if(stream){FS....
method findObject (line 8) | findObject(path,dontResolveLastLink){var ret=FS.analyzePath(path,dontRes...
method analyzePath (line 8) | analyzePath(path,dontResolveLastLink){try{var lookup=FS.lookupPath(path,...
method createPath (line 8) | createPath(parent,path,canRead,canWrite){parent=typeof parent=="string"?...
method createFile (line 8) | createFile(parent,name,properties,canRead,canWrite){var path=PATH.join2(...
method createDataFile (line 8) | createDataFile(parent,name,data,canRead,canWrite,canOwn){var path=name;i...
method createDevice (line 8) | createDevice(parent,name,input,output){var path=PATH.join2(typeof parent...
method forceLoadFile (line 8) | forceLoadFile(obj){if(obj.isDevice||obj.isFolder||obj.link||obj.contents...
method createLazyFile (line 8) | createLazyFile(parent,name,url,canRead,canWrite){class LazyUint8Array{le...
method calculateAt (line 8) | calculateAt(dirfd,path,allowEmpty){if(PATH.isAbs(path)){return path}var ...
method writeStat (line 8) | writeStat(buf,stat){HEAP32[buf>>2]=stat.dev;HEAP32[buf+4>>2]=stat.mode;H...
method writeStatFs (line 8) | writeStatFs(buf,stats){HEAP32[buf+4>>2]=stats.bsize;HEAP32[buf+40>>2]=st...
method doMsync (line 8) | doMsync(addr,stream,len,flags,offset){if(!FS.isFile(stream.node.mode)){t...
method getStreamFromFD (line 8) | getStreamFromFD(fd){var stream=FS.getStreamChecked(fd);return stream}
method getStr (line 8) | getStr(ptr){var ret=UTF8ToString(ptr);return ret}
function ___syscall_fcntl64 (line 8) | function ___syscall_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;try...
function ___syscall_ioctl (line 8) | function ___syscall_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;try{va...
function ___syscall_mkdirat (line 8) | function ___syscall_mkdirat(dirfd,path,mode){try{path=SYSCALLS.getStr(pa...
function ___syscall_openat (line 8) | function ___syscall_openat(dirfd,path,flags,varargs){SYSCALLS.varargs=va...
function ___syscall_renameat (line 8) | function ___syscall_renameat(olddirfd,oldpath,newdirfd,newpath){try{oldp...
function ___syscall_rmdir (line 8) | function ___syscall_rmdir(path){try{path=SYSCALLS.getStr(path);FS.rmdir(...
function ___syscall_unlinkat (line 8) | function ___syscall_unlinkat(dirfd,path,flags){try{path=SYSCALLS.getStr(...
function _fd_close (line 8) | function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.cl...
function _fd_read (line 8) | function _fd_read(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamF...
function _fd_seek (line 8) | function _fd_seek(fd,offset,whence,newOffset){offset=bigintToI53Checked(...
function _fd_write (line 8) | function _fd_write(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStream...
function convertReturnValue (line 8) | function convertReturnValue(ret){if(returnType==="string"){return UTF8To...
function onDone (line 8) | function onDone(ret){if(stack!==0)stackRestore(stack);return convertRetu...
function run (line 8) | function run(){if(runDependencies>0){dependenciesFulfilled=run;return}pr...
FILE: packages/coding-agent/examples/extensions/doom-overlay/doom/doomgeneric_pi.c
function EMSCRIPTEN_KEEPALIVE (line 22) | EMSCRIPTEN_KEEPALIVE
function EMSCRIPTEN_KEEPALIVE (line 26) | EMSCRIPTEN_KEEPALIVE
function EMSCRIPTEN_KEEPALIVE (line 29) | EMSCRIPTEN_KEEPALIVE
function EMSCRIPTEN_KEEPALIVE (line 33) | EMSCRIPTEN_KEEPALIVE
function DG_Init (line 43) | void DG_Init(void) {
function DG_DrawFrame (line 47) | void DG_DrawFrame(void) {
function DG_SleepMs (line 51) | void DG_SleepMs(uint32_t ms) {
function DG_GetTicksMs (line 56) | uint32_t DG_GetTicksMs(void) {
function DG_GetKey (line 60) | int DG_GetKey(int *pressed, unsigned char *key) {
function DG_SetWindowTitle (line 70) | void DG_SetWindowTitle(const char *title) {
FILE: packages/coding-agent/examples/extensions/doom-overlay/wad-finder.ts
constant BUNDLED_WAD (line 7) | const BUNDLED_WAD = join(__dirname, "doom1.wad");
constant WAD_URL (line 8) | const WAD_URL = "https://distro.ibiblio.org/slitaz/sources/packages/d/do...
constant DEFAULT_WAD_PATHS (line 10) | const DEFAULT_WAD_PATHS = ["./doom1.wad", "./DOOM1.WAD", "~/doom1.wad", ...
function findWadFile (line 12) | function findWadFile(customPath?: string): string | null {
function ensureWadFile (line 34) | async function ensureWadFile(): Promise<string | null> {
FILE: packages/coding-agent/examples/extensions/dynamic-tools.ts
constant ECHO_PARAMS (line 13) | const ECHO_PARAMS = Type.Object({
function normalizeToolName (line 17) | function normalizeToolName(input: string): string | undefined {
function dynamicToolsExtension (line 24) | function dynamicToolsExtension(pi: ExtensionAPI) {
FILE: packages/coding-agent/examples/extensions/handoff.ts
constant SYSTEM_PROMPT (line 19) | const SYSTEM_PROMPT = `You are a context transfer assistant. Given a con...
FILE: packages/coding-agent/examples/extensions/hello.ts
method execute (line 17) | async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
FILE: packages/coding-agent/examples/extensions/interactive-shell.ts
constant DEFAULT_INTERACTIVE_COMMANDS (line 27) | const DEFAULT_INTERACTIVE_COMMANDS = [
function getInteractiveCommands (line 100) | function getInteractiveCommands(): string[] {
function isInteractiveCommand (line 109) | function isInteractiveCommand(command: string): boolean {
FILE: packages/coding-agent/examples/extensions/mac-system-theme.ts
function isDarkMode (line 14) | async function isDarkMode(): Promise<boolean> {
FILE: packages/coding-agent/examples/extensions/minimal-mode.ts
function shortenPath (line 35) | function shortenPath(path: string): string {
function createBuiltInTools (line 46) | function createBuiltInTools(cwd: string) {
function getBuiltInTools (line 58) | function getBuiltInTools(cwd: string) {
method execute (line 78) | async execute(toolCallId, params, signal, onUpdate, ctx) {
method renderCall (line 83) | renderCall(args, theme) {
method renderResult (line 97) | renderResult(result, { expanded }, theme) {
method execute (line 125) | async execute(toolCallId, params, signal, onUpdate, ctx) {
method renderCall (line 130) | renderCall(args, theme) {
method renderResult (line 138) | renderResult(result, { expanded }, theme) {
method execute (line 174) | async execute(toolCallId, params, signal, onUpdate, ctx) {
method renderCall (line 179) | renderCall(args, theme) {
method renderResult (line 188) | renderResult(result, { expanded }, theme) {
method execute (line 216) | async execute(toolCallId, params, signal, onUpdate, ctx) {
method renderCall (line 221) | renderCall(args, theme) {
method renderResult (line 228) | renderResult(result, { expanded }, theme) {
method execute (line 261) | async execute(toolCallId, params, signal, onUpdate, ctx) {
method renderCall (line 266) | renderCall(args, theme) {
method renderResult (line 280) | renderResult(result, { expanded }, theme) {
method execute (line 319) | async execute(toolCallId, params, signal, onUpdate, ctx) {
method renderCall (line 324) | renderCall(args, theme) {
method renderResult (line 342) | renderResult(result, { expanded }, theme) {
method execute (line 381) | async execute(toolCallId, params, signal, onUpdate, ctx) {
method renderCall (line 386) | renderCall(args, theme) {
method renderResult (line 398) | renderResult(result, { expanded }, theme) {
FILE: packages/coding-agent/examples/extensions/modal-editor.ts
constant NORMAL_KEYS (line 16) | const NORMAL_KEYS: Record<string, string | null> = {
class ModalEditor (line 28) | class ModalEditor extends CustomEditor {
method handleInput (line 31) | handleInput(data: string): void {
method render (line 67) | render(width: number): string[] {
FILE: packages/coding-agent/examples/extensions/notify.ts
function windowsToastScript (line 13) | function windowsToastScript(title: string, body: string): string {
function notifyOSC777 (line 26) | function notifyOSC777(title: string, body: string): void {
function notifyOSC99 (line 30) | function notifyOSC99(title: string, body: string): void {
function notifyWindows (line 36) | function notifyWindows(title: string, body: string): void {
function notify (line 41) | function notify(title: string, body: string): void {
FILE: packages/coding-agent/examples/extensions/overlay-qa-tests.ts
function sleep (line 300) | function sleep(ms: number): Promise<void> {
method constructor (line 306) | constructor(protected theme: Theme) {}
method box (line 308) | protected box(lines: string[], width: number, title?: string): string[] {
method invalidate (line 327) | invalidate(): void {}
method dispose (line 328) | dispose(): void {}
class AnchorTestComponent (line 332) | class AnchorTestComponent extends BaseOverlay {
method constructor (line 333) | constructor(
method handleInput (line 341) | handleInput(data: string): void {
method render (line 351) | render(width: number): string[] {
class MarginTestComponent (line 370) | class MarginTestComponent extends BaseOverlay {
method constructor (line 371) | constructor(
method handleInput (line 379) | handleInput(data: string): void {
method render (line 387) | render(width: number): string[] {
class StackOverlayComponent (line 405) | class StackOverlayComponent extends BaseOverlay {
method constructor (line 406) | constructor(
method handleInput (line 415) | handleInput(data: string): void {
method render (line 421) | render(width: number): string[] {
class StreamingOverflowComponent (line 448) | class StreamingOverflowComponent extends BaseOverlay {
method constructor (line 456) | constructor(
method startProcess (line 465) | private startProcess(): void {
method handleInput (line 523) | handleInput(data: string): void {
method render (line 536) | render(width: number): string[] {
method dispose (line 574) | dispose(): void {
class EdgeTestComponent (line 581) | class EdgeTestComponent extends BaseOverlay {
method constructor (line 582) | constructor(
method handleInput (line 589) | handleInput(data: string): void {
method render (line 595) | render(width: number): string[] {
class PercentTestComponent (line 616) | class PercentTestComponent extends BaseOverlay {
method constructor (line 617) | constructor(
method handleInput (line 625) | handleInput(data: string): void {
method render (line 633) | render(width: number): string[] {
class MaxHeightTestComponent (line 651) | class MaxHeightTestComponent extends BaseOverlay {
method constructor (line 652) | constructor(
method handleInput (line 659) | handleInput(data: string): void {
method render (line 665) | render(width: number): string[] {
class SidepanelComponent (line 686) | class SidepanelComponent extends BaseOverlay {
method constructor (line 690) | constructor(
method handleInput (line 698) | handleInput(data: string): void {
method render (line 713) | render(width: number): string[] {
class AnimationDemoComponent (line 747) | class AnimationDemoComponent extends BaseOverlay {
method constructor (line 754) | constructor(
method startAnimation (line 763) | private startAnimation(): void {
method handleInput (line 781) | handleInput(data: string): void {
method render (line 788) | render(width: number): string[] {
method dispose (line 830) | dispose(): void {
function hslToRgb (line 839) | function hslToRgb(h: number, s: number, l: number): [number, number, num...
class ToggleDemoComponent (line 862) | class ToggleDemoComponent extends BaseOverlay {
method constructor (line 866) | constructor(
method handleInput (line 874) | handleInput(data: string): void {
method render (line 895) | render(width: number): string[] {
class PassiveDemoController (line 924) | class PassiveDemoController extends BaseOverlay {
method constructor (line 933) | constructor(
method handleInput (line 952) | handleInput(data: string): void {
method render (line 965) | render(width: number): string[] {
method cleanup (line 985) | private cleanup(): void {
method dispose (line 994) | override dispose(): void {
class TimerPanel (line 999) | class TimerPanel extends BaseOverlay {
method tick (line 1002) | tick(): void {
method render (line 1006) | render(width: number): string[] {
class FocusDemoController (line 1017) | class FocusDemoController extends BaseOverlay {
method constructor (line 1022) | constructor(
method cycleFocus (line 1051) | private cycleFocus(): void {
method close (line 1064) | private close(): void {
method handleInput (line 1071) | handleInput(data: string): void {
method render (line 1079) | render(width: number): string[] {
method dispose (line 1102) | override dispose(): void {
class FocusPanel (line 1107) | class FocusPanel extends BaseOverlay {
method constructor (line 1111) | constructor(
method handleInput (line 1122) | handleInput(data: string): void {
method render (line 1130) | render(width: number): string[] {
class StreamingInputController (line 1157) | class StreamingInputController extends BaseOverlay {
method constructor (line 1165) | constructor(
method cycleFocus (line 1209) | private cycleFocus(): void {
method close (line 1229) | private close(): void {
method handleInput (line 1240) | handleInput(data: string): void {
method render (line 1248) | render(width: number): string[] {
method dispose (line 1283) | override dispose(): void {
class StreamingInputPanel (line 1288) | class StreamingInputPanel implements Component {
method constructor (line 1293) | constructor(
method handleInput (line 1303) | handleInput(data: string): void {
method render (line 1315) | render(width: number): string[] {
method invalidate (line 1347) | invalidate(): void {}
FILE: packages/coding-agent/examples/extensions/overlay-test.ts
class OverlayTestComponent (line 31) | class OverlayTestComponent implements Focusable {
method constructor (line 45) | constructor(
method handleInput (line 50) | handleInput(data: string): void {
method render (line 84) | render(_width: number): string[] {
method invalidate (line 148) | invalidate(): void {}
method dispose (line 149) | dispose(): void {}
FILE: packages/coding-agent/examples/extensions/pirate.ts
function pirateExtension (line 15) | function pirateExtension(pi: ExtensionAPI) {
FILE: packages/coding-agent/examples/extensions/plan-mode/index.ts
constant PLAN_MODE_TOOLS (line 22) | const PLAN_MODE_TOOLS = ["read", "bash", "grep", "find", "ls", "question...
constant NORMAL_MODE_TOOLS (line 23) | const NORMAL_MODE_TOOLS = ["read", "bash", "edit", "write"];
function isAssistantMessage (line 26) | function isAssistantMessage(m: AgentMessage): m is AssistantMessage {
function getTextContent (line 31) | function getTextContent(message: AssistantMessage): string {
function planModeExtension (line 38) | function planModeExtension(pi: ExtensionAPI): void {
FILE: packages/coding-agent/examples/extensions/plan-mode/utils.ts
constant DESTRUCTIVE_PATTERNS (line 7) | const DESTRUCTIVE_PATTERNS = [
constant SAFE_PATTERNS (line 44) | const SAFE_PATTERNS = [
function isSafeCommand (line 97) | function isSafeCommand(command: string): boolean {
type TodoItem (line 103) | interface TodoItem {
function cleanStepText (line 109) | function cleanStepText(text: string): string {
function extractTodoItems (line 129) | function extractTodoItems(message: string): TodoItem[] {
function extractDoneSteps (line 152) | function extractDoneSteps(message: string): number[] {
function markCompletedSteps (line 161) | function markCompletedSteps(text: string, items: TodoItem[]): number {
FILE: packages/coding-agent/examples/extensions/preset.ts
type Preset (line 48) | interface Preset {
type PresetsConfig (line 61) | interface PresetsConfig {
function loadPresets (line 69) | function loadPresets(cwd: string): PresetsConfig {
function presetExtension (line 100) | function presetExtension(pi: ExtensionAPI) {
FILE: packages/coding-agent/examples/extensions/qna.ts
constant SYSTEM_PROMPT (line 14) | const SYSTEM_PROMPT = `You are a question extractor. Given text from a c...
FILE: packages/coding-agent/examples/extensions/question.ts
type OptionWithDesc (line 11) | interface OptionWithDesc {
type DisplayOption (line 16) | type DisplayOption = OptionWithDesc & { isOther?: boolean };
type QuestionDetails (line 18) | interface QuestionDetails {
function question (line 36) | function question(pi: ExtensionAPI) {
FILE: packages/coding-agent/examples/extensions/questionnaire.ts
type QuestionOption (line 13) | interface QuestionOption {
type RenderOption (line 19) | type RenderOption = QuestionOption & { isOther?: boolean };
type Question (line 21) | interface Question {
type Answer (line 29) | interface Answer {
type QuestionnaireResult (line 37) | interface QuestionnaireResult {
function errorResult (line 66) | function errorResult(
function questionnaire (line 76) | function questionnaire(pi: ExtensionAPI) {
FILE: packages/coding-agent/examples/extensions/rainbow-editor.ts
constant COLORS (line 10) | const COLORS: [number, number, number][] = [
constant RESET (line 19) | const RESET = "\x1b[0m";
function brighten (line 21) | function brighten(rgb: [number, number, number], factor: number): string {
function colorize (line 26) | function colorize(text: string, shinePos: number): string {
class RainbowEditor (line 44) | class RainbowEditor extends CustomEditor {
method hasUltrathink (line 48) | private hasUltrathink(): boolean {
method startAnimation (line 52) | private startAnimation(): void {
method stopAnimation (line 60) | private stopAnimation(): void {
method handleInput (line 67) | handleInput(data: string): void {
method render (line 76) | render(width: number): string[] {
FILE: packages/coding-agent/examples/extensions/reload-runtime.ts
method execute (line 29) | async execute() {
FILE: packages/coding-agent/examples/extensions/sandbox/index.ts
type SandboxConfig (line 47) | interface SandboxConfig extends SandboxRuntimeConfig {
constant DEFAULT_CONFIG (line 51) | const DEFAULT_CONFIG: SandboxConfig = {
function loadConfig (line 75) | function loadConfig(cwd: string): SandboxConfig {
function deepMerge (line 101) | function deepMerge(base: SandboxConfig, overrides: Partial<SandboxConfig...
function createSandboxedBashOps (line 128) | function createSandboxedBashOps(): BashOperations {
method execute (line 213) | async execute(id, params, signal, onUpdate, _ctx) {
FILE: packages/coding-agent/examples/extensions/shutdown-command.ts
method execute (line 26) | async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
method execute (line 47) | async execute(_toolCallId, params, _signal, onUpdate, ctx) {
FILE: packages/coding-agent/examples/extensions/snake.ts
constant GAME_WIDTH (line 8) | const GAME_WIDTH = 40;
constant GAME_HEIGHT (line 9) | const GAME_HEIGHT = 15;
constant TICK_MS (line 10) | const TICK_MS = 100;
type Direction (line 12) | type Direction = "up" | "down" | "left" | "right";
type Point (line 13) | type Point = { x: number; y: number };
type GameState (line 15) | interface GameState {
function createInitialState (line 25) | function createInitialState(): GameState {
function spawnFood (line 43) | function spawnFood(snake: Point[]): Point {
class SnakeComponent (line 54) | class SnakeComponent {
method constructor (line 66) | constructor(
method startGame (line 90) | private startGame(): void {
method tick (line 100) | private tick(): void {
method handleInput (line 150) | handleInput(data: string): void {
method invalidate (line 203) | invalidate(): void {
method render (line 207) | render(width: number): string[] {
method padLine (line 292) | private padLine(line: string, width: number): string {
method dispose (line 299) | dispose(): void {
constant SNAKE_SAVE_TYPE (line 307) | const SNAKE_SAVE_TYPE = "snake-save";
FILE: packages/coding-agent/examples/extensions/space-invaders.ts
constant GAME_WIDTH (line 9) | const GAME_WIDTH = 60;
constant GAME_HEIGHT (line 10) | const GAME_HEIGHT = 24;
constant TICK_MS (line 11) | const TICK_MS = 50;
constant PLAYER_Y (line 12) | const PLAYER_Y = GAME_HEIGHT - 2;
constant ALIEN_ROWS (line 13) | const ALIEN_ROWS = 5;
constant ALIEN_COLS (line 14) | const ALIEN_COLS = 11;
constant ALIEN_START_Y (line 15) | const ALIEN_START_Y = 2;
type Point (line 17) | type Point = { x: number; y: number };
type Bullet (line 19) | interface Bullet extends Point {
type Alien (line 23) | interface Alien extends Point {
type Shield (line 28) | interface Shield {
type GameState (line 33) | interface GameState {
type KeyState (line 50) | interface KeyState {
function createShields (line 56) | function createShields(): Shield[] {
function createAliens (line 72) | function createAliens(): Alien[] {
function createInitialState (line 88) | function createInitialState(highScore = 0, level = 1): GameState {
class SpaceInvadersComponent (line 107) | class SpaceInvadersComponent {
method constructor (line 125) | constructor(
method startGame (line 144) | private startGame(): void {
method tick (line 154) | private tick(): void {
method moveAliens (line 208) | private moveAliens(): void {
method alienShoot (line 252) | private alienShoot(): void {
method checkCollisions (line 273) | private checkCollisions(): void {
method handleInput (line 320) | handleInput(data: string): void {
method invalidate (line 378) | invalidate(): void {
method render (line 382) | render(width: number): string[] {
method padLine (line 511) | private padLine(line: string, width: number): string {
method dispose (line 517) | dispose(): void {
constant INVADERS_SAVE_TYPE (line 525) | const INVADERS_SAVE_TYPE = "space-invaders-save";
FILE: packages/coding-agent/examples/extensions/ssh.ts
function sshExec (line 29) | function sshExec(remote: string, command: string): Promise<Buffer> {
function createRemoteReadOps (line 47) | function createRemoteReadOps(remote: string, remoteCwd: string, localCwd...
function createRemoteWriteOps (line 64) | function createRemoteWriteOps(remote: string, remoteCwd: string, localCw...
function createRemoteEditOps (line 75) | function createRemoteEditOps(remote: string, remoteCwd: string, localCwd...
function createRemoteBashOps (line 81) | function createRemoteBashOps(remote: string, remoteCwd: string, localCwd...
method execute (line 130) | async execute(id, params, signal, onUpdate, _ctx) {
method execute (line 144) | async execute(id, params, signal, onUpdate, _ctx) {
method execute (line 158) | async execute(id, params, signal, onUpdate, _ctx) {
method execute (line 172) | async execute(id, params, signal, onUpdate, _ctx) {
FILE: packages/coding-agent/examples/extensions/subagent/agents.ts
type AgentScope (line 9) | type AgentScope = "user" | "project" | "both";
type AgentConfig (line 11) | interface AgentConfig {
type AgentDiscoveryResult (line 21) | interface AgentDiscoveryResult {
function loadAgentsFromDir (line 26) | function loadAgentsFromDir(dir: string, source: "user" | "project"): Age...
function isDirectory (line 77) | function isDirectory(p: string): boolean {
function findNearestProjectAgentsDir (line 85) | function findNearestProjectAgentsDir(cwd: string): string | null {
function discoverAgents (line 97) | function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryR...
function formatAgentList (line 118) | function formatAgentList(agents: AgentConfig[], maxItems: number): { tex...
FILE: packages/coding-agent/examples/extensions/subagent/index.ts
constant MAX_PARALLEL_TASKS (line 27) | const MAX_PARALLEL_TASKS = 8;
constant MAX_CONCURRENCY (line 28) | const MAX_CONCURRENCY = 4;
constant COLLAPSED_ITEM_COUNT (line 29) | const COLLAPSED_ITEM_COUNT = 10;
function formatTokens (line 31) | function formatTokens(count: number): string {
function formatUsageStats (line 38) | function formatUsageStats(
function formatToolCall (line 64) | function formatToolCall(
type UsageStats (line 132) | interface UsageStats {
type SingleResult (line 142) | interface SingleResult {
type SubagentDetails (line 156) | interface SubagentDetails {
function getFinalOutput (line 163) | function getFinalOutput(messages: Message[]): string {
type DisplayItem (line 175) | type DisplayItem = { type: "text"; text: string } | { type: "toolCall"; ...
function getDisplayItems (line 177) | function getDisplayItems(messages: Message[]): DisplayItem[] {
function mapWithConcurrencyLimit (line 190) | async function mapWithConcurrencyLimit<TIn, TOut>(
function writePromptToTempFile (line 210) | async function writePromptToTempFile(agentName: string, prompt: string):...
type OnUpdateCallback (line 220) | type OnUpdateCallback = (partial: AgentToolResult<SubagentDetails>) => v...
function runSingleAgent (line 222) | async function runSingleAgent(
method execute (line 422) | async execute(_toolCallId, params, signal, onUpdate, ctx) {
method renderCall (line 651) | renderCall(args, theme) {
method renderResult (line 695) | renderResult(result, { expanded }, theme) {
FILE: packages/coding-agent/examples/extensions/summarize.ts
type ContentBlock (line 6) | type ContentBlock = {
type SessionEntry (line 13) | type SessionEntry = {
FILE: packages/coding-agent/examples/extensions/titlebar-spinner.ts
constant BRAILLE_FRAMES (line 14) | const BRAILLE_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
function getBaseTitle (line 16) | function getBaseTitle(pi: ExtensionAPI): string {
function stopAnimation (line 26) | function stopAnimation(ctx: ExtensionContext) {
function startAnimation (line 35) | function startAnimation(ctx: ExtensionContext) {
FILE: packages/coding-agent/examples/extensions/todo.ts
type Todo (line 18) | interface Todo {
type TodoDetails (line 24) | interface TodoDetails {
class TodoListComponent (line 40) | class TodoListComponent {
method constructor (line 47) | constructor(todos: Todo[], theme: Theme, onClose: () => void) {
method handleInput (line 53) | handleInput(data: string): void {
method render (line 59) | render(width: number): string[] {
method invalidate (line 99) | invalidate(): void {
method execute (line 144) | async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
method renderCall (line 223) | renderCall(args, theme) {
method renderResult (line 230) | renderResult(result, { expanded }, theme) {
FILE: packages/coding-agent/examples/extensions/tool-override.ts
constant LOG_FILE (line 30) | const LOG_FILE = join(getAgentDir(), "read-access.log");
constant BLOCKED_PATTERNS (line 33) | const BLOCKED_PATTERNS = [
function isBlockedPath (line 43) | function isBlockedPath(path: string): boolean {
function logAccess (line 47) | async function logAccess(path: string, allowed: boolean, reason?: string) {
method execute (line 76) | async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
FILE: packages/coding-agent/examples/extensions/tools.ts
type ToolsState (line 17) | interface ToolsState {
function toolsExtension (line 21) | function toolsExtension(pi: ExtensionAPI) {
FILE: packages/coding-agent/examples/extensions/trigger-compact.ts
constant COMPACT_THRESHOLD_TOKENS (line 3) | const COMPACT_THRESHOLD_TOKENS = 100_000;
FILE: packages/coding-agent/examples/extensions/truncated-tool.ts
type RgDetails (line 39) | interface RgDetails {
method execute (line 56) | async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
method renderCall (line 138) | renderCall(args, theme) {
method renderResult (line 151) | renderResult(result, { expanded, isPartial }, theme) {
FILE: packages/coding-agent/examples/extensions/widget-placement.ts
function widgetPlacementExtension (line 9) | function widgetPlacementExtension(pi: ExtensionAPI) {
FILE: packages/coding-agent/examples/rpc-extension-ui.ts
constant GREEN (line 29) | const GREEN = "\x1b[32m";
constant YELLOW (line 30) | const YELLOW = "\x1b[33m";
constant BLUE (line 31) | const BLUE = "\x1b[34m";
constant MAGENTA (line 32) | const MAGENTA = "\x1b[35m";
constant RED (line 33) | const RED = "\x1b[31m";
constant DIM (line 34) | const DIM = "\x1b[2m";
constant BOLD (line 35) | const BOLD = "\x1b[1m";
constant RESET (line 36) | const RESET = "\x1b[0m";
type ExtensionUIRequest (line 42) | interface ExtensionUIRequest {
class OutputLog (line 63) | class OutputLog implements Component {
method setVisibleLines (line 68) | setVisibleLines(n: number): void {
method append (line 72) | append(line: string): void {
method appendRaw (line 79) | appendRaw(text: string): void {
method invalidate (line 87) | invalidate(): void {}
method render (line 89) | render(width: number): string[] {
class LoadingIndicator (line 100) | class LoadingIndicator implements Component {
method start (line 105) | start(tui: TUI): void {
method stop (line 114) | stop(): void {
method invalidate (line 121) | invalidate(): void {}
method render (line 123) | render(_width: number): string[] {
class PromptInput (line 132) | class PromptInput implements Component {
method constructor (line 136) | constructor() {
method handleInput (line 140) | handleInput(data: string): void {
method invalidate (line 148) | invalidate(): void {
method render (line 152) | render(width: number): string[] {
class SelectDialog (line 161) | class SelectDialog implements Component {
method constructor (line 167) | constructor(title: string, options: string[]) {
method handleInput (line 181) | handleInput(data: string): void {
method invalidate (line 185) | invalidate(): void {
method render (line 189) | render(width: number): string[] {
class InputDialog (line 198) | class InputDialog implements Component {
method constructor (line 203) | constructor(title: string, prefill?: string) {
method onSubmit (line 209) | set onSubmit(fn: ((value: string) => void) | undefined) {
method onEscape (line 213) | set onEscape(fn: (() => void) | undefined) {
method inputComponent (line 217) | get inputComponent(): Input {
method handleInput (line 221) | handleInput(data: string): void {
method invalidate (line 229) | invalidate(): void {
method render (line 233) | render(width: number): string[] {
function main (line 246) | async function main() {
FILE: packages/coding-agent/src/cli/args.ts
type Mode (line 10) | type Mode = "text" | "json" | "rpc";
type Args (line 12) | interface Args {
constant VALID_THINKING_LEVELS (line 50) | const VALID_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high"...
function isValidThinkingLevel (line 52) | function isValidThinkingLevel(level: string): level is ThinkingLevel {
function parseArgs (line 56) | function parseArgs(args: string[], extensionFlags?: Map<string, { type: ...
function printHelp (line 182) | function printHelp(): void {
FILE: packages/coding-agent/src/cli/config-selector.ts
type ConfigSelectorOptions (line 11) | interface ConfigSelectorOptions {
function selectConfig (line 19) | async function selectConfig(options: ConfigSelectorOptions): Promise<voi...
FILE: packages/coding-agent/src/cli/file-processor.ts
type ProcessedFiles (line 13) | interface ProcessedFiles {
type ProcessFileOptions (line 18) | interface ProcessFileOptions {
function processFileArguments (line 24) | async function processFileArguments(fileArgs: string[], options?: Proces...
FILE: packages/coding-agent/src/cli/initial-message.ts
type InitialMessageInput (line 4) | interface InitialMessageInput {
type InitialMessageResult (line 11) | interface InitialMessageResult {
function buildInitialMessage (line 20) | function buildInitialMessage({
FILE: packages/coding-agent/src/cli/list-models.ts
function formatTokenCount (line 12) | function formatTokenCount(count: number): string {
function listModels (line 27) | async function listModels(modelRegistry: ModelRegistry, searchPattern?: ...
FILE: packages/coding-agent/src/cli/session-picker.ts
type SessionsLoader (line 10) | type SessionsLoader = (onProgress?: SessionListProgress) => Promise<Sess...
function selectSession (line 13) | async function selectSession(
FILE: packages/coding-agent/src/config.ts
type InstallMethod (line 27) | type InstallMethod = "bun-binary" | "npm" | "pnpm" | "yarn" | "bun" | "u...
function detectInstallMethod (line 29) | function detectInstallMethod(): InstallMethod {
function getUpdateInstruction (line 52) | function getUpdateInstruction(packageName: string): string {
function getPackageDir (line 80) | function getPackageDir(): string {
function getThemesDir (line 111) | function getThemesDir(): string {
function getExportTemplateDir (line 127) | function getExportTemplateDir(): string {
function getPackageJsonPath (line 137) | function getPackageJsonPath(): string {
function getReadmePath (line 142) | function getReadmePath(): string {
function getDocsPath (line 147) | function getDocsPath(): string {
function getExamplesPath (line 152) | function getExamplesPath(): string {
function getChangelogPath (line 157) | function getChangelogPath(): string {
constant APP_NAME (line 167) | const APP_NAME: string = pkg.piConfig?.name || "pi";
constant CONFIG_DIR_NAME (line 168) | const CONFIG_DIR_NAME: string = pkg.piConfig?.configDir || ".pi";
constant VERSION (line 169) | const VERSION: string = pkg.version;
constant ENV_AGENT_DIR (line 172) | const ENV_AGENT_DIR = `${APP_NAME.toUpperCase()}_CODING_AGENT_DIR`;
constant DEFAULT_SHARE_VIEWER_URL (line 174) | const DEFAULT_SHARE_VIEWER_URL = "https://pi.dev/session/";
function getShareViewerUrl (line 177) | function getShareViewerUrl(gistId: string): string {
function getAgentDir (line 187) | function getAgentDir(): string {
function getCustomThemesDir (line 199) | function getCustomThemesDir(): string {
function getModelsPath (line 204) | function getModelsPath(): string {
function getAuthPath (line 209) | function getAuthPath(): string {
function getSettingsPath (line 214) | function getSettingsPath(): string {
function getToolsDir (line 219) | function getToolsDir(): string {
function getBinDir (line 224) | function getBinDir(): string {
function getPromptsDir (line 229) | function getPromptsDir(): string {
function getSessionsDir (line 234) | function getSessionsDir(): string {
function getDebugLogPath (line 239) | function getDebugLogPath(): string {
FILE: packages/coding-agent/src/core/agent-session.ts
type ParsedSkillBlock (line 88) | interface ParsedSkillBlock {
function parseSkillBlock (line 99) | function parseSkillBlock(text: string): ParsedSkillBlock | null {
type AgentSessionEvent (line 111) | type AgentSessionEvent =
type AgentSessionEventListener (line 125) | type AgentSessionEventListener = (event: AgentSessionEvent) => void;
type AgentSessionConfig (line 131) | interface AgentSessionConfig {
type ExtensionBindings (line 152) | interface ExtensionBindings {
type PromptOptions (line 160) | interface PromptOptions {
type ModelCycleResult (line 172) | interface ModelCycleResult {
type SessionStats (line 180) | interface SessionStats {
constant THINKING_LEVELS (line 203) | const THINKING_LEVELS: ThinkingLevel[] = ["off", "minimal", "low", "medi...
constant THINKING_LEVELS_WITH_XHIGH (line 206) | const THINKING_LEVELS_WITH_XHIGH: ThinkingLevel[] = ["off", "minimal", "...
class AgentSession (line 212) | class AgentSession {
method constructor (line 277) | constructor(config: AgentSessionConfig) {
method modelRegistry (line 302) | get modelRegistry(): ModelRegistry {
method _installAgentToolHooks (line 314) | private _installAgentToolHooks(): void {
method _emit (line 370) | private _emit(event: AgentSessionEvent): void {
method _createRetryPromiseForAgentEnd (line 397) | private _createRetryPromiseForAgentEnd(event: AgentEvent): void {
method _findLastAssistantInMessages (line 417) | private _findLastAssistantInMessages(messages: AgentMessage[]): Assist...
method _processAgentEvent (line 427) | private async _processAgentEvent(event: AgentEvent): Promise<void> {
method _resolveRetry (line 514) | private _resolveRetry(): void {
method _getUserMessageText (line 523) | private _getUserMessageText(message: Message): string {
method _findLastAssistantMessage (line 532) | private _findLastAssistantMessage(): AssistantMessage | undefined {
method _emitExtensionEvent (line 544) | private async _emitExtensionEvent(event: AgentEvent): Promise<void> {
method subscribe (line 621) | subscribe(listener: AgentSessionEventListener): () => void {
method _disconnectFromAgent (line 638) | private _disconnectFromAgent(): void {
method _reconnectToAgent (line 649) | private _reconnectToAgent(): void {
method dispose (line 658) | dispose(): void {
method state (line 668) | get state(): AgentState {
method model (line 673) | get model(): Model<any> | undefined {
method thinkingLevel (line 678) | get thinkingLevel(): ThinkingLevel {
method isStreaming (line 683) | get isStreaming(): boolean {
method systemPrompt (line 688) | get systemPrompt(): string {
method retryAttempt (line 693) | get retryAttempt(): number {
method getActiveToolNames (line 701) | getActiveToolNames(): string[] {
method getAllTools (line 708) | getAllTools(): ToolInfo[] {
method setActiveToolsByName (line 722) | setActiveToolsByName(toolNames: string[]): void {
method isCompacting (line 740) | get isCompacting(): boolean {
method messages (line 749) | get messages(): AgentMessage[] {
method steeringMode (line 754) | get steeringMode(): "all" | "one-at-a-time" {
method followUpMode (line 759) | get followUpMode(): "all" | "one-at-a-time" {
method sessionFile (line 764) | get sessionFile(): string | undefined {
method sessionId (line 769) | get sessionId(): string {
method sessionName (line 774) | get sessionName(): string | undefined {
method scopedModels (line 779) | get scopedModels(): ReadonlyArray<{ model: Model<any>; thinkingLevel?:...
method setScopedModels (line 784) | setScopedModels(scopedModels: Array<{ model: Model<any>; thinkingLevel...
method promptTemplates (line 789) | get promptTemplates(): ReadonlyArray<PromptTemplate> {
method _normalizePromptSnippet (line 793) | private _normalizePromptSnippet(text: string | undefined): string | un...
method _normalizePromptGuidelines (line 802) | private _normalizePromptGuidelines(guidelines: string[] | undefined): ...
method _rebuildSystemPrompt (line 817) | private _rebuildSystemPrompt(toolNames: string[]): string {
method prompt (line 865) | async prompt(text: string, options?: PromptOptions): Promise<void> {
method _tryExecuteExtensionCommand (line 1009) | private async _tryExecuteExtensionCommand(text: string): Promise<boole...
method _expandSkillCommand (line 1042) | private _expandSkillCommand(text: string): string {
method steer (line 1076) | async steer(text: string, images?: ImageContent[]): Promise<void> {
method followUp (line 1096) | async followUp(text: string, images?: ImageContent[]): Promise<void> {
method _queueSteer (line 1112) | private async _queueSteer(text: string, images?: ImageContent[]): Prom...
method _queueFollowUp (line 1128) | private async _queueFollowUp(text: string, images?: ImageContent[]): P...
method _throwIfExtensionCommand (line 1144) | private _throwIfExtensionCommand(text: string): void {
method sendCustomMessage (line 1170) | async sendCustomMessage<T = unknown>(
method sendUserMessage (line 1212) | async sendUserMessage(
method clearQueue (line 1250) | clearQueue(): { steering: string[]; followUp: string[] } {
method pendingMessageCount (line 1260) | get pendingMessageCount(): number {
method getSteeringMessages (line 1265) | getSteeringMessages(): readonly string[] {
method getFollowUpMessages (line 1270) | getFollowUpMessages(): readonly string[] {
method resourceLoader (line 1274) | get resourceLoader(): ResourceLoader {
method abort (line 1281) | async abort(): Promise<void> {
method newSession (line 1295) | async newSession(options?: {
method _emitModelSelect (line 1351) | private async _emitModelSelect(
method setModel (line 1371) | async setModel(model: Model<any>): Promise<void> {
method cycleModel (line 1395) | async cycleModel(direction: "forward" | "backward" = "forward"): Promi...
method _getScopedModelsWithApiKey (line 1402) | private async _getScopedModelsWithApiKey(): Promise<Array<{ model: Mod...
method _cycleScopedModel (line 1424) | private async _cycleScopedModel(direction: "forward" | "backward"): Pr...
method _cycleAvailableModel (line 1453) | private async _cycleAvailableModel(direction: "forward" | "backward"):...
method setThinkingLevel (line 1492) | setThinkingLevel(level: ThinkingLevel): void {
method cycleThinkingLevel (line 1513) | cycleThinkingLevel(): ThinkingLevel | undefined {
method getAvailableThinkingLevels (line 1529) | getAvailableThinkingLevels(): ThinkingLevel[] {
method supportsXhighThinking (line 1537) | supportsXhighThinking(): boolean {
method supportsThinking (line 1544) | supportsThinking(): boolean {
method _getThinkingLevelForModelSwitch (line 1548) | private _getThinkingLevelForModelSwitch(explicitLevel?: ThinkingLevel)...
method _clampThinkingLevel (line 1558) | private _clampThinkingLevel(level: ThinkingLevel, availableLevels: Thi...
method setSteeringMode (line 1584) | setSteeringMode(mode: "all" | "one-at-a-time"): void {
method setFollowUpMode (line 1593) | setFollowUpMode(mode: "all" | "one-at-a-time"): void {
method compact (line 1607) | async compact(customInstructions?: string): Promise<CompactionResult> {
method abortCompaction (line 1720) | abortCompaction(): void {
method abortBranchSummary (line 1728) | abortBranchSummary(): void {
method _checkCompaction (line 1743) | private async _checkCompaction(assistantMessage: AssistantMessage, ski...
method _runAutoCompaction (line 1825) | private async _runAutoCompaction(reason: "overflow" | "threshold", wil...
method setAutoCompactionEnabled (line 1968) | setAutoCompactionEnabled(enabled: boolean): void {
method autoCompactionEnabled (line 1973) | get autoCompactionEnabled(): boolean {
method bindExtensions (line 1977) | async bindExtensions(bindings: ExtensionBindings): Promise<void> {
method extendResourcesFromExtensions (line 1998) | private async extendResourcesFromExtensions(reason: "startup" | "reloa...
method buildExtensionResourcePaths (line 2023) | private buildExtensionResourcePaths(entries: Array<{ path: string; ext...
method getExtensionSourceLabel (line 2042) | private getExtensionSourceLabel(extensionPath: string): string {
method _applyExtensionBindings (line 2051) | private _applyExtensionBindings(runner: ExtensionRunner): void {
method _refreshCurrentModelFromRegistry (line 2061) | private _refreshCurrentModelFromRegistry(): void {
method _bindExtensionCore (line 2075) | private _bindExtensionCore(runner: ExtensionRunner): void {
method _refreshToolRegistry (line 2196) | private _refreshToolRegistry(options?: { activeToolNames?: string[]; i...
method _buildRuntime (line 2250) | private _buildRuntime(options: {
method reload (line 2303) | async reload(): Promise<void> {
method _isRetryableError (line 2334) | private _isRetryableError(message: AssistantMessage): boolean {
method _handleRetryableError (line 2352) | private async _handleRetryableError(message: AssistantMessage): Promis...
method abortRetry (line 2431) | abortRetry(): void {
method waitForRetry (line 2441) | private async waitForRetry(): Promise<void> {
method isRetrying (line 2448) | get isRetrying(): boolean {
method autoRetryEnabled (line 2453) | get autoRetryEnabled(): boolean {
method setAutoRetryEnabled (line 2460) | setAutoRetryEnabled(enabled: boolean): void {
method executeBash (line 2476) | async executeBash(
method recordBashResult (line 2509) | recordBashResult(command: string, result: BashResult, options?: { excl...
method abortBash (line 2538) | abortBash(): void {
method isBashRunning (line 2543) | get isBashRunning(): boolean {
method hasPendingBashMessages (line 2548) | get hasPendingBashMessages(): boolean {
method _flushPendingBashMessages (line 2556) | private _flushPendingBashMessages(): void {
method switchSession (line 2580) | async switchSession(sessionPath: string): Promise<boolean> {
method setSessionName (line 2657) | setSessionName(name: string): void {
method fork (line 2670) | async fork(entryId: string): Promise<{ selectedText: string; cancelled...
method navigateTree (line 2740) | async navigateTree(
method getUserMessagesForForking (line 2925) | getUserMessagesForForking(): Array<{ entryId: string; text: string }> {
method _extractUserMessageText (line 2942) | private _extractUserMessageText(content: string | Array<{ type: string...
method getSessionStats (line 2956) | getSessionStats(): SessionStats {
method getContextUsage (line 3000) | getContextUsage(): ContextUsage | undefined {
method exportToHtml (line 3051) | async exportToHtml(outputPath?: string): Promise<string> {
method exportToJsonl (line 3076) | exportToJsonl(outputPath?: string): string {
method importFromJsonl (line 3112) | async importFromJsonl(inputPath: string): Promise<boolean> {
method getLastAssistantText (line 3141) | getLastAssistantText(): string | undefined {
method hasExtensionHandlers (line 3172) | hasExtensionHandlers(eventType: string): boolean {
method extensionRunner (line 3179) | get extensionRunner(): ExtensionRunner | undefined {
FILE: packages/coding-agent/src/core/auth-storage.ts
type ApiKeyCredential (line 22) | type ApiKeyCredential = {
type OAuthCredential (line 27) | type OAuthCredential = {
type AuthCredential (line 31) | type AuthCredential = ApiKeyCredential | OAuthCredential;
type AuthStorageData (line 33) | type AuthStorageData = Record<string, AuthCredential>;
type LockResult (line 35) | type LockResult<T> = {
type AuthStorageBackend (line 40) | interface AuthStorageBackend {
class FileAuthStorageBackend (line 45) | class FileAuthStorageBackend implements AuthStorageBackend {
method constructor (line 46) | constructor(private authPath: string = join(getAgentDir(), "auth.json"...
method ensureParentDir (line 48) | private ensureParentDir(): void {
method ensureFileExists (line 55) | private ensureFileExists(): void {
method acquireLockSyncWithRetry (line 62) | private acquireLockSyncWithRetry(path: string): () => void {
method withLock (line 89) | withLock<T>(fn: (current: string | undefined) => LockResult<T>): T {
method withLockAsync (line 110) | async withLockAsync<T>(fn: (current: string | undefined) => Promise<Lo...
class InMemoryAuthStorageBackend (line 161) | class InMemoryAuthStorageBackend implements AuthStorageBackend {
method withLock (line 164) | withLock<T>(fn: (current: string | undefined) => LockResult<T>): T {
method withLockAsync (line 172) | async withLockAsync<T>(fn: (current: string | undefined) => Promise<Lo...
class AuthStorage (line 184) | class AuthStorage {
method constructor (line 191) | private constructor(private storage: AuthStorageBackend) {
method create (line 195) | static create(authPath?: string): AuthStorage {
method fromStorage (line 199) | static fromStorage(storage: AuthStorageBackend): AuthStorage {
method inMemory (line 203) | static inMemory(data: AuthStorageData = {}): AuthStorage {
method setRuntimeApiKey (line 213) | setRuntimeApiKey(provider: string, apiKey: string): void {
method removeRuntimeApiKey (line 220) | removeRuntimeApiKey(provider: string): void {
method setFallbackResolver (line 228) | setFallbackResolver(resolver: (provider: string) => string | undefined...
method recordError (line 232) | private recordError(error: unknown): void {
method parseStorageData (line 237) | private parseStorageData(content: string | undefined): AuthStorageData {
method reload (line 247) | reload(): void {
method persistProviderChange (line 262) | private persistProviderChange(provider: string, credential: AuthCreden...
method get (line 286) | get(provider: string): AuthCredential | undefined {
method set (line 293) | set(provider: string, credential: AuthCredential): void {
method remove (line 301) | remove(provider: string): void {
method list (line 309) | list(): string[] {
method has (line 316) | has(provider: string): boolean {
method hasAuth (line 324) | hasAuth(provider: string): boolean {
method getAll (line 335) | getAll(): AuthStorageData {
method drainErrors (line 339) | drainErrors(): Error[] {
method login (line 348) | async login(providerId: OAuthProviderId, callbacks: OAuthLoginCallback...
method logout (line 361) | logout(provider: string): void {
method refreshOAuthTokenWithLock (line 369) | private async refreshOAuthTokenWithLock(
method getApiKey (line 424) | async getApiKey(providerId: string): Promise<string | undefined> {
method getOAuthProviders (line 486) | getOAuthProviders() {
FILE: packages/coding-agent/src/core/bash-executor.ts
type BashExecutorOptions (line 22) | interface BashExecutorOptions {
type BashResult (line 29) | interface BashResult {
function executeBash (line 59) | function executeBash(command: string, options?: BashExecutorOptions): Pr...
function executeBashWithOperations (line 67) | async function executeBashWithOperations(
FILE: packages/coding-agent/src/core/compaction/branch-summarization.ts
type BranchSummaryResult (line 33) | interface BranchSummaryResult {
type BranchSummaryDetails (line 42) | interface BranchSummaryDetails {
type BranchPreparation (line 49) | interface BranchPreparation {
type CollectEntriesResult (line 58) | interface CollectEntriesResult {
type GenerateBranchSummaryOptions (line 65) | interface GenerateBranchSummaryOptions {
function collectEntriesForBranchSummary (line 96) | function collectEntriesForBranchSummary(
function getMessageFromEntry (line 144) | function getMessageFromEntry(entry: SessionEntry): AgentMessage | undefi...
function prepareBranchEntries (line 183) | function prepareBranchEntries(entries: SessionEntry[], tokenBudget: numb...
constant BRANCH_SUMMARY_PREAMBLE (line 241) | const BRANCH_SUMMARY_PREAMBLE = `The user explored a different conversat...
constant BRANCH_SUMMARY_PROMPT (line 246) | const BRANCH_SUMMARY_PROMPT = `Create a structured summary of this conve...
function generateBranchSummary (line 281) | async function generateBranchSummary(
FILE: packages/coding-agent/src/core/compaction/compaction.ts
type CompactionDetails (line 33) | interface CompactionDetails {
function extractFileOperations (line 41) | function extractFileOperations(
function getMessageFromEntry (line 79) | function getMessageFromEntry(entry: SessionEntry): AgentMessage | undefi...
type CompactionResult (line 96) | interface CompactionResult<T = unknown> {
type CompactionSettings (line 108) | interface CompactionSettings {
constant DEFAULT_COMPACTION_SETTINGS (line 114) | const DEFAULT_COMPACTION_SETTINGS: CompactionSettings = {
function calculateContextTokens (line 128) | function calculateContextTokens(usage: Usage): number {
function getAssistantUsage (line 136) | function getAssistantUsage(msg: AgentMessage): Usage | undefined {
function getLastAssistantUsage (line 149) | function getLastAssistantUsage(entries: SessionEntry[]): Usage | undefin...
type ContextUsageEstimate (line 160) | interface ContextUsageEstimate {
function getLastAssistantUsageInfo (line 167) | function getLastAssistantUsageInfo(messages: AgentMessage[]): { usage: U...
function estimateContextTokens (line 179) | function estimateContextTokens(messages: AgentMessage[]): ContextUsageEs...
function shouldCompact (line 212) | function shouldCompact(contextTokens: number, contextWindow: number, set...
function estimateTokens (line 225) | function estimateTokens(message: AgentMessage): number {
function findValidCutPoints (line 292) | function findValidCutPoints(entries: SessionEntry[], startIndex: number,...
function findTurnStartIndex (line 337) | function findTurnStartIndex(entries: SessionEntry[], entryIndex: number,...
type CutPointResult (line 354) | interface CutPointResult {
function findCutPoint (line 379) | function findCutPoint(
constant SUMMARIZATION_PROMPT (line 447) | const SUMMARIZATION_PROMPT = `The messages above are a conversation to s...
constant UPDATE_SUMMARIZATION_PROMPT (line 480) | const UPDATE_SUMMARIZATION_PROMPT = `The messages above are NEW conversa...
function generateSummary (line 523) | async function generateSummary(
type CompactionPreparation (line 586) | interface CompactionPreparation {
function prepareCompaction (line 604) | function prepareCompaction(
constant TURN_PREFIX_SUMMARIZATION_PROMPT (line 690) | const TURN_PREFIX_SUMMARIZATION_PROMPT = `This is the PREFIX of a turn t...
function compact (line 712) | async function compact(
function generateTurnPrefixSummary (line 783) | async function generateTurnPrefixSummary(
FILE: packages/coding-agent/src/core/compaction/utils.ts
type FileOperations (line 12) | interface FileOperations {
function createFileOps (line 18) | function createFileOps(): FileOperations {
function extractFileOpsFromMessage (line 29) | function extractFileOpsFromMessage(message: AgentMessage, fileOps: FileO...
function computeFileLists (line 62) | function computeFileLists(fileOps: FileOperations): { readFiles: string[...
function formatFileOperations (line 72) | function formatFileOperations(readFiles: string[], modifiedFiles: string...
constant TOOL_RESULT_MAX_CHARS (line 89) | const TOOL_RESULT_MAX_CHARS = 2000;
function truncateForSummary (line 95) | function truncateForSummary(text: string, maxChars: number): string {
function serializeConversation (line 109) | function serializeConversation(messages: Message[]): string {
constant SUMMARIZATION_SYSTEM_PROMPT (line 168) | const SUMMARIZATION_SYSTEM_PROMPT = `You are a context summarization ass...
FILE: packages/coding-agent/src/core/defaults.ts
constant DEFAULT_THINKING_LEVEL (line 3) | const DEFAULT_THINKING_LEVEL: ThinkingLevel = "medium";
FILE: packages/coding-agent/src/core/diagnostics.ts
type ResourceCollision (line 1) | interface ResourceCollision {
type ResourceDiagnostic (line 10) | interface ResourceDiagnostic {
FILE: packages/coding-agent/src/core/event-bus.ts
type EventBus (line 3) | interface EventBus {
type EventBusController (line 8) | interface EventBusController extends EventBus {
function createEventBus (line 12) | function createEventBus(): EventBusController {
FILE: packages/coding-agent/src/core/exec.ts
type ExecOptions (line 11) | interface ExecOptions {
type ExecResult (line 23) | interface ExecResult {
function execCommand (line 34) | async function execCommand(
FILE: packages/coding-agent/src/core/export-html/ansi-to-html.ts
constant ANSI_COLORS (line 15) | const ANSI_COLORS = [
function color256ToHex (line 37) | function color256ToHex(index: number): string {
function escapeHtml (line 63) | function escapeHtml(text: string): string {
type TextStyle (line 72) | interface TextStyle {
function createEmptyStyle (line 81) | function createEmptyStyle(): TextStyle {
function styleToInlineCSS (line 92) | function styleToInlineCSS(style: TextStyle): string {
function hasStyle (line 103) | function hasStyle(style: TextStyle): boolean {
function applySgrCode (line 110) | function applySgrCode(params: number[], style: TextStyle): void {
constant ANSI_REGEX (line 193) | const ANSI_REGEX = /\x1b\[([\d;]*)m/g;
function ansiToHtml (line 198) | function ansiToHtml(text: string): string {
function ansiLinesToHtml (line 256) | function ansiLinesToHtml(lines: string[]): string {
FILE: packages/coding-agent/src/core/export-html/index.ts
type ToolHtmlRenderer (line 14) | interface ToolHtmlRenderer {
type RenderedToolHtml (line 27) | interface RenderedToolHtml {
type ExportOptions (line 33) | interface ExportOptions {
function parseColor (line 41) | function parseColor(color: string): { r: number; g: number; b: number } ...
function getLuminance (line 62) | function getLuminance(r: number, g: number, b: number): number {
function adjustBrightness (line 71) | function adjustBrightness(color: string, factor: number): string {
function deriveExportColors (line 79) | function deriveExportColors(baseColor: string): { pageBg: string; cardBg...
function generateThemeVars (line 109) | function generateThemeVars(themeName?: string): string {
type SessionData (line 128) | interface SessionData {
function generateHtml (line 141) | function generateHtml(sessionData: SessionData, themeName?: string): str...
constant BUILTIN_TOOLS (line 175) | const BUILTIN_TOOLS = new Set(["bash", "read", "write", "edit", "ls", "f...
function preRenderCustomTools (line 180) | function preRenderCustomTools(
function exportSessionToHtml (line 227) | async function exportSessionToHtml(
function exportFromFile (line 279) | async function exportFromFile(inputPath: string, options?: ExportOptions...
FILE: packages/coding-agent/src/core/export-html/template.js
function buildTree (line 73) | function buildTree() {
function buildActivePathIds (line 116) | function buildActivePathIds(targetId) {
function getPath (line 133) | function getPath(targetId) {
function findNewestLeaf (line 155) | function findNewestLeaf(nodeId) {
function flattenTree (line 183) | function flattenTree(roots, activePathIds) {
function buildTreePrefix (line 258) | function buildTreePrefix(flatNode) {
function hasTextContent (line 295) | function hasTextContent(content) {
function extractContent (line 305) | function extractContent(content) {
function getSearchableText (line 316) | function getSearchableText(entry, label) {
function filterNodes (line 352) | function filterNodes(flatNodes, currentLeafId) {
function recalculateVisualStructure (line 416) | function recalculateVisualStructure(filteredNodes, allFlatNodes) {
function shortenPath (line 542) | function shortenPath(p) {
function formatToolCall (line 555) | function formatToolCall(name, args) {
function escapeHtml (line 591) | function escapeHtml(text) {
function truncate (line 600) | function truncate(s, maxLen = 100) {
function getTreeNodeDisplayHtml (line 608) | function getTreeNodeDisplayHtml(entry, label) {
function renderTree (line 672) | function renderTree() {
function forceTreeRerender (line 749) | function forceTreeRerender() {
function formatTokens (line 758) | function formatTokens(count) {
function formatTimestamp (line 765) | function formatTimestamp(ts) {
function replaceTabs (line 771) | function replaceTabs(text) {
function str (line 776) | function str(value) {
function getLanguageFromPath (line 782) | function getLanguageFromPath(filePath) {
function findToolResult (line 796) | function findToolResult(toolCallId) {
function formatExpandableOutput (line 807) | function formatExpandableOutput(text, maxLines, lang) {
function renderToolCall (line 863) | function renderToolCall(call) {
function buildShareUrl (line 1038) | function buildShareUrl(entryId) {
function copyToClipboard (line 1066) | async function copyToClipboard(text, button) {
function renderCopyLinkButton (line 1107) | function renderCopyLinkButton(entryId) {
function renderEntry (line 1116) | function renderEntry(entry) {
function computeStats (line 1229) | function computeStats(entryList) {
function renderHeader (line 1272) | function renderHeader() {
function renderEntryToNode (line 1366) | function renderEntryToNode(entry) {
function navigateTo (line 1387) | function navigateTo(targetId, scrollMode = 'target', scrollToEntryId = n...
function escapeHtmlTags (line 1446) | function escapeHtmlTags(text) {
method code (line 1456) | code(token) {
method text (line 1477) | text(token) {
method codespan (line 1481) | codespan(token) {
function safeMarkedParse (line 1488) | function safeMarkedParse(text) {
function isMobileLayout (line 1517) | function isMobileLayout() {
function getSidebarBounds (line 1521) | function getSidebarBounds() {
function clampSidebarWidth (line 1532) | function clampSidebarWidth(width) {
function applySidebarWidth (line 1537) | function applySidebarWidth(width) {
function loadSidebarWidth (line 1541) | function loadSidebarWidth() {
function saveSidebarWidth (line 1552) | function saveSidebarWidth(width) {
function setupSidebarResize (line 1560) | function setupSidebarResize() {
FILE: packages/coding-agent/src/core/export-html/tool-renderer.ts
type ToolHtmlRendererDeps (line 13) | interface ToolHtmlRendererDeps {
type ToolHtmlRenderer (line 22) | interface ToolHtmlRenderer {
function createToolHtmlRenderer (line 40) | function createToolHtmlRenderer(deps: ToolHtmlRendererDeps): ToolHtmlRen...
FILE: packages/coding-agent/src/core/extensions/loader.ts
constant VIRTUAL_MODULES (line 42) | const VIRTUAL_MODULES: Record<string, unknown> = {
function getAliases (line 58) | function getAliases(): Record<string, string> {
constant UNICODE_SPACES (line 88) | const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
function normalizeUnicodeSpaces (line 90) | function normalizeUnicodeSpaces(str: string): string {
function expandPath (line 94) | function expandPath(p: string): string {
function resolvePath (line 105) | function resolvePath(extPath: string, cwd: string): string {
type HandlerFn (line 113) | type HandlerFn = (...args: unknown[]) => Promise<unknown>;
function createExtensionRuntime (line 119) | function createExtensionRuntime(): ExtensionRuntime {
function createExtensionAPI (line 160) | function createExtensionAPI(
function loadExtensionModule (line 287) | async function loadExtensionModule(extensionPath: string) {
function createExtension (line 304) | function createExtension(extensionPath: string, resolvedPath: string): E...
function loadExtension (line 317) | async function loadExtension(
function loadExtensionFromFactory (line 345) | async function loadExtensionFromFactory(
function loadExtensions (line 361) | async function loadExtensions(paths: string[], cwd: string, eventBus?: E...
type PiManifest (line 387) | interface PiManifest {
function readPiManifest (line 394) | function readPiManifest(packageJsonPath: string): PiManifest | null {
function isExtensionFile (line 407) | function isExtensionFile(name: string): boolean {
function resolveExtensionEntries (line 420) | function resolveExtensionEntries(dir: string): string[] | null {
function discoverExtensionsInDir (line 462) | function discoverExtensionsInDir(dir: string): string[] {
function discoverAndLoadExtensions (line 499) | async function discoverAndLoadExtensions(
FILE: packages/coding-agent/src/core/extensions/runner.ts
constant RESERVED_KEYBINDINGS_FOR_EXTENSION_CONFLICTS (line 56) | const RESERVED_KEYBINDINGS_FOR_EXTENSION_CONFLICTS = [
type BuiltInKeyBindings (line 76) | type BuiltInKeyBindings = Partial<Record<KeyId, { keybinding: string; re...
type BeforeAgentStartCombinedResult (line 96) | interface BeforeAgentStartCombinedResult {
type RunnerEmitEvent (line 105) | type RunnerEmitEvent = Exclude<
type SessionBeforeEvent (line 117) | type SessionBeforeEvent = Extract<
type SessionBeforeEventResult (line 122) | type SessionBeforeEventResult =
type RunnerEmitResult (line 128) | type RunnerEmitResult<TEvent extends RunnerEmitEvent> = TEvent extends {...
type ExtensionErrorListener (line 138) | type ExtensionErrorListener = (error: ExtensionError) => void;
type NewSessionHandler (line 140) | type NewSessionHandler = (options?: {
type ForkHandler (line 145) | type ForkHandler = (entryId: string) => Promise<{ cancelled: boolean }>;
type NavigateTreeHandler (line 147) | type NavigateTreeHandler = (
type SwitchSessionHandler (line 152) | type SwitchSessionHandler = (sessionPath: string) => Promise<{ cancelled...
type ReloadHandler (line 154) | type ReloadHandler = () => Promise<void>;
type ShutdownHandler (line 156) | type ShutdownHandler = () => void;
function emitSessionShutdownEvent (line 162) | async function emitSessionShutdownEvent(extensionRunner: ExtensionRunner...
method theme (line 190) | get theme() {
class ExtensionRunner (line 200) | class ExtensionRunner {
method constructor (line 225) | constructor(
method bindCore (line 240) | bindCore(
method bindCommandContext (line 311) | bindCommandContext(actions?: ExtensionCommandContextActions): void {
method setUIContext (line 330) | setUIContext(uiContext?: ExtensionUIContext): void {
method getUIContext (line 334) | getUIContext(): ExtensionUIContext {
method hasUI (line 338) | hasUI(): boolean {
method getExtensionPaths (line 342) | getExtensionPaths(): string[] {
method getAllRegisteredTools (line 347) | getAllRegisteredTools(): RegisteredTool[] {
method getToolDefinition (line 360) | getToolDefinition(toolName: string): RegisteredTool["definition"] | un...
method getFlags (line 370) | getFlags(): Map<string, ExtensionFlag> {
method setFlagValue (line 382) | setFlagValue(name: string, value: boolean | string): void {
method getFlagValues (line 386) | getFlagValues(): Map<string, boolean | string> {
method getShortcuts (line 390) | getShortcuts(resolvedKeybindings: KeybindingsConfig): Map<KeyId, Exten...
method getShortcutDiagnostics (line 435) | getShortcutDiagnostics(): ResourceDiagnostic[] {
method onError (line 439) | onError(listener: ExtensionErrorListener): () => void {
method emitError (line 444) | emitError(error: ExtensionError): void {
method hasHandlers (line 450) | hasHandlers(eventType: string): boolean {
method getMessageRenderer (line 460) | getMessageRenderer(customType: string): MessageRenderer | undefined {
method getRegisteredCommands (line 470) | getRegisteredCommands(reserved?: Set<string>): RegisteredCommand[] {
method getCommandDiagnostics (line 503) | getCommandDiagnostics(): ResourceDiagnostic[] {
method getRegisteredCommandsWithPaths (line 507) | getRegisteredCommandsWithPaths(): Array<{ command: RegisteredCommand; ...
method getCommand (line 517) | getCommand(name: string): RegisteredCommand | undefined {
method shutdown (line 531) | shutdown(): void {
method createContext (line 539) | createContext(): ExtensionContext {
method createCommandContext (line 560) | createCommandContext(): ExtensionCommandContext {
method isSessionBeforeEvent (line 572) | private isSessionBeforeEvent(event: RunnerEmitEvent): event is Session...
method emit (line 581) | async emit<TEvent extends RunnerEmitEvent>(event: TEvent): Promise<Run...
method emitToolResult (line 615) | async emitToolResult(event: ToolResultEvent): Promise<ToolResultEventR...
method emitToolCall (line 665) | async emitToolCall(event: ToolCallEvent): Promise<ToolCallEventResult ...
method emitUserBash (line 688) | async emitUserBash(event: UserBashEvent): Promise<UserBashEventResult ...
method emitContext (line 717) | async emitContext(messages: AgentMessage[]): Promise<AgentMessage[]> {
method emitBeforeProviderRequest (line 749) | async emitBeforeProviderRequest(payload: unknown): Promise<unknown> {
method emitBeforeAgentStart (line 783) | async emitBeforeAgentStart(
method emitResourcesDiscover (line 840) | async emitResourcesDiscover(
method emitInput (line 889) | async emitInput(text: string, images: ImageContent[] | undefined, sour...
FILE: packages/coding-agent/src/core/extensions/types.ts
type ExtensionUIDialogOptions (line 84) | interface ExtensionUIDialogOptions {
type WidgetPlacement (line 92) | type WidgetPlacement = "aboveEditor" | "belowEditor";
type ExtensionWidgetOptions (line 95) | interface ExtensionWidgetOptions {
type TerminalInputHandler (line 101) | type TerminalInputHandler = (data: string) => { consume?: boolean; data?...
type ExtensionUIContext (line 107) | interface ExtensionUIContext {
type ContextUsage (line 244) | interface ContextUsage {
type CompactOptions (line 252) | interface CompactOptions {
type ExtensionContext (line 261) | interface ExtensionContext {
type ExtensionCommandContext (line 294) | interface ExtensionCommandContext extends ExtensionContext {
type ToolRenderResultOptions (line 325) | interface ToolRenderResultOptions {
type ToolDefinition (line 335) | interface ToolDefinition<TParams extends TSchema = TSchema, TDetails = u...
type ResourcesDiscoverEvent (line 374) | interface ResourcesDiscoverEvent {
type ResourcesDiscoverResult (line 381) | interface ResourcesDiscoverResult {
type SessionDirectoryEvent (line 392) | interface SessionDirectoryEvent {
type SessionStartEvent (line 398) | interface SessionStartEvent {
type SessionBeforeSwitchEvent (line 403) | interface SessionBeforeSwitchEvent {
type SessionSwitchEvent (line 410) | interface SessionSwitchEvent {
type SessionBeforeForkEvent (line 417) | interface SessionBeforeForkEvent {
type SessionForkEvent (line 423) | interface SessionForkEvent {
type SessionBeforeCompactEvent (line 429) | interface SessionBeforeCompactEvent {
type SessionCompactEvent (line 438) | interface SessionCompactEvent {
type SessionShutdownEvent (line 445) | interface SessionShutdownEvent {
type TreePreparation (line 450) | interface TreePreparation {
type SessionBeforeTreeEvent (line 465) | interface SessionBeforeTreeEvent {
type SessionTreeEvent (line 472) | interface SessionTreeEvent {
type SessionEvent (line 480) | type SessionEvent =
type ContextEvent (line 498) | interface ContextEvent {
type BeforeProviderRequestEvent (line 504) | interface BeforeProviderRequestEvent {
type BeforeAgentStartEvent (line 510) | interface BeforeAgentStartEvent {
type AgentStartEvent (line 518) | interface AgentStartEvent {
type AgentEndEvent (line 523) | interface AgentEndEvent {
type TurnStartEvent (line 529) | interface TurnStartEvent {
type TurnEndEvent (line 536) | interface TurnEndEvent {
type MessageStartEvent (line 544) | interface MessageStartEvent {
type MessageUpdateEvent (line 550) | interface MessageUpdateEvent {
type MessageEndEvent (line 557) | interface MessageEndEvent {
type ToolExecutionStartEvent (line 563) | interface ToolExecutionStartEvent {
type ToolExecutionUpdateEvent (line 571) | interface ToolExecutionUpdateEvent {
type ToolExecutionEndEvent (line 580) | interface ToolExecutionEndEvent {
type ModelSelectSource (line 592) | type ModelSelectSource = "set" | "cycle" | "restore";
type ModelSelectEvent (line 595) | interface ModelSelectEvent {
type UserBashEvent (line 607) | interface UserBashEvent {
type InputSource (line 622) | type InputSource = "interactive" | "rpc" | "extension";
type InputEvent (line 625) | interface InputEvent {
type InputEventResult (line 636) | type InputEventResult =
type ToolCallEventBase (line 645) | interface ToolCallEventBase {
type BashToolCallEvent (line 650) | interface BashToolCallEvent extends ToolCallEventBase {
type ReadToolCallEvent (line 655) | interface ReadToolCallEvent extends ToolCallEventBase {
type EditToolCallEvent (line 660) | interface EditToolCallEvent extends ToolCallEventBase {
type WriteToolCallEvent (line 665) | interface WriteToolCallEvent extends ToolCallEventBase {
type GrepToolCallEvent (line 670) | interface GrepToolCallEvent extends ToolCallEventBase {
type FindToolCallEvent (line 675) | interface FindToolCallEvent extends ToolCallEventBase {
type LsToolCallEvent (line 680) | interface LsToolCallEvent extends ToolCallEventBase {
type CustomToolCallEvent (line 685) | interface CustomToolCallEvent extends ToolCallEventBase {
type ToolCallEvent (line 691) | type ToolCallEvent =
type ToolResultEventBase (line 701) | interface ToolResultEventBase {
type BashToolResultEvent (line 709) | interface BashToolResultEvent extends ToolResultEventBase {
type ReadToolResultEvent (line 714) | interface ReadToolResultEvent extends ToolResultEventBase {
type EditToolResultEvent (line 719) | interface EditToolResultEvent extends ToolResultEventBase {
type WriteToolResultEvent (line 724) | interface WriteToolResultEvent extends ToolResultEventBase {
type GrepToolResultEvent (line 729) | interface GrepToolResultEvent extends ToolResultEventBase {
type FindToolResultEvent (line 734) | interface FindToolResultEvent extends ToolResultEventBase {
type LsToolResultEvent (line 739) | interface LsToolResultEvent extends ToolResultEventBase {
type CustomToolResultEvent (line 744) | interface CustomToolResultEvent extends ToolResultEventBase {
type ToolResultEvent (line 750) | type ToolResultEvent =
function isBashToolResult (line 761) | function isBashToolResult(e: ToolResultEvent): e is BashToolResultEvent {
function isReadToolResult (line 764) | function isReadToolResult(e: ToolResultEvent): e is ReadToolResultEvent {
function isEditToolResult (line 767) | function isEditToolResult(e: ToolResultEvent): e is EditToolResultEvent {
function isWriteToolResult (line 770) | function isWriteToolResult(e: ToolResultEvent): e is WriteToolResultEvent {
function isGrepToolResult (line 773) | function isGrepToolResult(e: ToolResultEvent): e is GrepToolResultEvent {
function isFindToolResult (line 776) | function isFindToolResult(e: ToolResultEvent): e is FindToolResultEvent {
function isLsToolResult (line 779) | function isLsToolResult(e: ToolResultEvent): e is LsToolResultEvent {
function isToolCallEventType (line 814) | function isToolCallEventType(toolName: string, event: ToolCallEvent): bo...
type ExtensionEvent (line 819) | type ExtensionEvent =
type ContextEventResult (line 845) | interface ContextEventResult {
type BeforeProviderRequestEventResult (line 849) | type BeforeProviderRequestEventResult = unknown;
type ToolCallEventResult (line 851) | interface ToolCallEventResult {
type UserBashEventResult (line 857) | interface UserBashEventResult {
type ToolResultEventResult (line 864) | interface ToolResultEventResult {
type BeforeAgentStartEventResult (line 870) | interface BeforeAgentStartEventResult {
type SessionDirectoryResult (line 876) | interface SessionDirectoryResult {
type SessionDirectoryHandler (line 882) | type SessionDirectoryHandler = (
type SessionBeforeSwitchResult (line 886) | interface SessionBeforeSwitchResult {
type SessionBeforeForkResult (line 890) | interface SessionBeforeForkResult {
type SessionBeforeCompactResult (line 895) | interface SessionBeforeCompactResult {
type SessionBeforeTreeResult (line 900) | interface SessionBeforeTreeResult {
type MessageRenderOptions (line 918) | interface MessageRenderOptions {
type MessageRenderer (line 922) | type MessageRenderer<T = unknown> = (
type RegisteredCommand (line 932) | interface RegisteredCommand {
type ExtensionHandler (line 945) | type ExtensionHandler<E, R = undefined> = (event: E, ctx: ExtensionConte...
type ExtensionAPI (line 950) | interface ExtensionAPI {
type ProviderConfig (line 1183) | interface ProviderConfig {
type ProviderModelConfig (line 1214) | interface ProviderModelConfig {
type ExtensionFactory (line 1238) | type ExtensionFactory = (pi: ExtensionAPI) => void | Promise<void>;
type RegisteredTool (line 1244) | interface RegisteredTool {
type ExtensionFlag (line 1249) | interface ExtensionFlag {
type ExtensionShortcut (line 1257) | interface ExtensionShortcut {
type HandlerFn (line 1264) | type HandlerFn = (...args: unknown[]) => Promise<unknown>;
type SendMessageHandler (line 1266) | type SendMessageHandler = <T = unknown>(
type SendUserMessageHandler (line 1271) | type SendUserMessageHandler = (
type AppendEntryHandler (line 1276) | type AppendEntryHandler = <T = unknown>(customType: string, data?: T) =>...
type SetSessionNameHandler (line 1278) | type SetSessionNameHandler = (name: string) => void;
type GetSessionNameHandler (line 1280) | type GetSessionNameHandler = () => string | undefined;
type GetActiveToolsHandler (line 1282) | type GetActiveToolsHandler = () => string[];
type ToolInfo (line 1285) | type ToolInfo = Pick<ToolDefinition, "name" | "description" | "parameter...
type GetAllToolsHandler (line 1287) | type GetAllToolsHandler = () => ToolInfo[];
type GetCommandsHandler (line 1289) | type GetCommandsHandler = () => SlashCommandInfo[];
type SetActiveToolsHandler (line 1291) | type SetActiveToolsHandler = (toolNames: string[]) => void;
type RefreshToolsHandler (line 1293) | type RefreshToolsHandler = () => void;
type SetModelHandler (line 1295) | type SetModelHandler = (model: Model<any>) => Promise<boolean>;
type GetThinkingLevelHandler (line 1297) | type GetThinkingLevelHandler = () => ThinkingLevel;
type SetThinkingLevelHandler (line 1299) | type SetThinkingLevelHandler = (level: ThinkingLevel) => void;
type SetLabelHandler (line 1301) | type SetLabelHandler = (entryId: string, label: string | undefined) => v...
type ExtensionRuntimeState (line 1307) | interface ExtensionRuntimeState {
type ExtensionActions (line 1325) | interface ExtensionActions {
type ExtensionContextActions (line 1346) | interface ExtensionContextActions {
type ExtensionCommandContextActions (line 1361) | interface ExtensionCommandContextActions {
type ExtensionRuntime (line 1380) | interface ExtensionRuntime extends ExtensionRuntimeState, ExtensionActio...
type Extension (line 1383) | interface Extension {
type LoadExtensionsResult (line 1395) | interface LoadExtensionsResult {
type ExtensionError (line 1406) | interface ExtensionError {
FILE: packages/coding-agent/src/core/extensions/wrapper.ts
function wrapRegisteredTool (line 16) | function wrapRegisteredTool(registeredTool: RegisteredTool, runner: Exte...
function wrapRegisteredTools (line 32) | function wrapRegisteredTools(registeredTools: RegisteredTool[], runner: ...
FILE: packages/coding-agent/src/core/footer-data-provider.ts
type GitPaths (line 5) | type GitPaths = {
function findGitPaths (line 15) | function findGitPaths(): GitPaths | null {
function resolveBranchWithGitSync (line 50) | function resolveBranchWithGitSync(repoDir: string): string | null {
function resolveBranchWithGitAsync (line 61) | function resolveBranchWithGitAsync(repoDir: string): Promise<string | nu...
class FooterDataProvider (line 86) | class FooterDataProvider {
method constructor (line 101) | constructor() {
method getGitBranch (line 107) | getGitBranch(): string | null {
method getExtensionStatuses (line 115) | getExtensionStatuses(): ReadonlyMap<string, string> {
method onBranchChange (line 120) | onBranchChange(callback: () => void): () => void {
method setExtensionStatus (line 126) | setExtensionStatus(key: string, text: string | undefined): void {
method clearExtensionStatuses (line 135) | clearExtensionStatuses(): void {
method getAvailableProviderCount (line 140) | getAvailableProviderCount(): number {
method setAvailableProviderCount (line 145) | setAvailableProviderCount(count: number): void {
method dispose (line 150) | dispose(): void {
method notifyBranchChange (line 167) | private notifyBranchChange(): void {
method scheduleRefresh (line 171) | private scheduleRefresh(): void {
method refreshGitBranchAsync (line 182) | private async refreshGitBranchAsync(): Promise<void> {
method resolveGitBranchSync (line 208) | private resolveGitBranchSync(): string | null {
method resolveGitBranchAsync (line 222) | private async resolveGitBranchAsync(): Promise<string | null> {
method setupGitWatcher (line 238) | private setupGitWatcher(): void {
type ReadonlyFooterDataProvider (line 270) | type ReadonlyFooterDataProvider = Pick<
FILE: packages/coding-agent/src/core/keybindings.ts
type AppKeybindings (line 13) | interface AppKeybindings {
type AppKeybinding (line 42) | type AppKeybinding = keyof AppKeybindings;
type Keybindings (line 45) | interface Keybindings extends AppKeybindings {}
constant KEYBINDINGS (line 48) | const KEYBINDINGS = {
constant KEYBINDING_NAME_MIGRATIONS (line 126) | const KEYBINDING_NAME_MIGRATIONS = {
function isRecord (line 186) | function isRecord(value: unknown): value is Record<string, unknown> {
function isLegacyKeybindingName (line 190) | function isLegacyKeybindingName(key: string): key is keyof typeof KEYBIN...
function toKeybindingsConfig (line 194) | function toKeybindingsConfig(value: unknown): KeybindingsConfig {
function migrateKeybindingNames (line 210) | function migrateKeybindingNames(rawConfig: Record<string, unknown>): {
function orderKeybindingsConfig (line 232) | function orderKeybindingsConfig(config: Record<string, unknown>): Record...
function loadRawConfig (line 250) | function loadRawConfig(path: string): Record<string, unknown> | undefined {
function migrateKeybindingsConfigFile (line 260) | function migrateKeybindingsConfigFile(agentDir: string = getAgentDir()):...
class KeybindingsManager (line 272) | class KeybindingsManager extends TuiKeybindingsManager {
method constructor (line 275) | constructor(userBindings: KeybindingsConfig = {}, configPath?: string) {
method create (line 280) | static create(agentDir: string = getAgentDir()): KeybindingsManager {
method reload (line 286) | reload(): void {
method getEffectiveConfig (line 291) | getEffectiveConfig(): KeybindingsConfig {
method loadFromFile (line 295) | private static loadFromFile(path: string): KeybindingsConfig {
FILE: packages/coding-agent/src/core/messages.ts
constant COMPACTION_SUMMARY_PREFIX (line 11) | const COMPACTION_SUMMARY_PREFIX = `The conversation history before this ...
constant COMPACTION_SUMMARY_SUFFIX (line 16) | const COMPACTION_SUMMARY_SUFFIX = `
constant BRANCH_SUMMARY_PREFIX (line 19) | const BRANCH_SUMMARY_PREFIX = `The following is a summary of a branch th...
constant BRANCH_SUMMARY_SUFFIX (line 24) | const BRANCH_SUMMARY_SUFFIX = `</summary>`;
type BashExecutionMessage (line 29) | interface BashExecutionMessage {
type CustomMessage (line 46) | interface CustomMessage<T = unknown> {
type BranchSummaryMessage (line 55) | interface BranchSummaryMessage {
type CompactionSummaryMessage (line 62) | interface CompactionSummaryMessage {
type CustomAgentMessages (line 71) | interface CustomAgentMessages {
function bashExecutionToText (line 82) | function bashExecutionToText(msg: BashExecutionMessage): string {
function createBranchSummaryMessage (line 100) | function createBranchSummaryMessage(summary: string, fromId: string, tim...
function createCompactionSummaryMessage (line 109) | function createCompactionSummaryMessage(
function createCustomMessage (line 123) | function createCustomMessage(
function convertToLlm (line 148) | function convertToLlm(messages: AgentMessage[]): Message[] {
FILE: packages/coding-agent/src/core/model-registry.ts
type ModelOverride (line 125) | type ModelOverride = Static<typeof ModelOverrideSchema>;
type ModelsConfig (line 144) | type ModelsConfig = Static<typeof ModelsConfigSchema>;
type ProviderOverride (line 147) | interface ProviderOverride {
type CustomModelsResult (line 155) | interface CustomModelsResult {
function emptyCustomModelsResult (line 164) | function emptyCustomModelsResult(error?: string): CustomModelsResult {
function mergeCompat (line 168) | function mergeCompat(
function applyModelOverride (line 203) | function applyModelOverride(model: Model<Api>, override: ModelOverride):...
class ModelRegistry (line 241) | class ModelRegistry {
method constructor (line 247) | constructor(
method refresh (line 267) | refresh(): void {
method getError (line 285) | getError(): string | undefined {
method loadModels (line 289) | private loadModels(): void {
method loadBuiltInModels (line 318) | private loadBuiltInModels(
method mergeCustomModels (line 353) | private mergeCustomModels(builtInModels: Model<Api>[], customModels: M...
method loadCustomModels (line 366) | private loadCustomModels(modelsJsonPath: string): CustomModelsResult {
method validateConfig (line 422) | private validateConfig(config: ModelsConfig): void {
method parseModels (line 465) | private parseModels(config: ModelsConfig): Model<Api>[] {
method getAll (line 523) | getAll(): Model<Api>[] {
method getAvailable (line 531) | getAvailable(): Model<Api>[] {
method find (line 538) | find(provider: string, modelId: string): Model<Api> | undefined {
method getApiKey (line 545) | async getApiKey(model: Model<Api>): Promise<string | undefined> {
method getApiKeyForProvider (line 552) | async getApiKeyForProvider(provider: string): Promise<string | undefin...
method isUsingOAuth (line 559) | isUsingOAuth(model: Model<Api>): boolean {
method registerProvider (line 571) | registerProvider(providerName: string, config: ProviderConfigInput): v...
method unregisterProvider (line 586) | unregisterProvider(providerName: string): void {
method validateProviderConfig (line 593) | private validateProviderConfig(providerName: string, config: ProviderC...
method applyProviderConfig (line 617) | private applyProviderConfig(providerName: string, config: ProviderConf...
type ProviderConfigInput (line 707) | interface ProviderConfigInput {
FILE: packages/coding-agent/src/core/model-resolver.ts
type ScopedModel (line 40) | interface ScopedModel {
function isAlias (line 50) | function isAlias(id: string): boolean {
function findExactModelReferenceMatch (line 64) | function findExactModelReferenceMatch(
function tryMatchModel (line 112) | function tryMatchModel(modelPattern: string, availableModels: Model<Api>...
type ParsedModelResult (line 144) | interface ParsedModelResult {
function buildFallbackModel (line 151) | function buildFallbackModel(provider: string, modelId: string, available...
function parseModelPattern (line 180) | function parseModelPattern(
function resolveModelScope (line 246) | async function resolveModelScope(patterns: string[], modelRegistry: Mode...
type ResolveCliModelResult (line 306) | interface ResolveCliModelResult {
function resolveCliModel (line 328) | function resolveCliModel(options: {
type InitialModelResult (line 460) | interface InitialModelResult {
function findInitialModel (line 474) | async function findInitialModel(options: {
function restoreModelFromSession (line 559) | async function restoreModelFromSession(
FILE: packages/coding-agent/src/core/package-manager.ts
constant NETWORK_TIMEOUT_MS (line 12) | const NETWORK_TIMEOUT_MS = 10000;
constant UPDATE_CHECK_CONCURRENCY (line 13) | const UPDATE_CHECK_CONCURRENCY = 4;
function isOfflineModeEnabled (line 15) | function isOfflineModeEnabled(): boolean {
type PathMetadata (line 21) | interface PathMetadata {
type ResolvedResource (line 28) | interface ResolvedResource {
type ResolvedPaths (line 34) | interface ResolvedPaths {
type MissingSourceAction (line 41) | type MissingSourceAction = "install" | "skip" | "error";
type ProgressEvent (line 43) | interface ProgressEvent {
type ProgressCallback (line 50) | type ProgressCallback = (event: ProgressEvent) => void;
type PackageUpdate (line 52) | interface PackageUpdate {
type PackageManager (line 59) | interface PackageManager {
type PackageManagerOptions (line 74) | interface PackageManagerOptions {
type SourceScope (line 80) | type SourceScope = "user" | "project" | "temporary";
type NpmSource (line 82) | type NpmSource = {
type LocalSource (line 89) | type LocalSource = {
type ParsedSource (line 94) | type ParsedSource = NpmSource | GitSource | LocalSource;
type PiManifest (line 96) | interface PiManifest {
type ResourceAccumulator (line 103) | interface ResourceAccumulator {
type PackageFilter (line 110) | interface PackageFilter {
type ResourceType (line 117) | type ResourceType = "extensions" | "skills" | "prompts" | "themes";
constant RESOURCE_TYPES (line 119) | const RESOURCE_TYPES: ResourceType[] = ["extensions", "skills", "prompts...
constant FILE_PATTERNS (line 121) | const FILE_PATTERNS: Record<ResourceType, RegExp> = {
constant IGNORE_FILE_NAMES (line 128) | const IGNORE_FILE_NAMES = [".gitignore", ".ignore", ".fdignore"];
type IgnoreMatcher (line 130) | type IgnoreMatcher = ReturnType<typeof ignore>;
function toPosixPath (line 132) | function toPosixPath(p: string): string {
function getHomeDir (line 136) | function getHomeDir(): string {
function prefixIgnorePattern (line 140) | function prefixIgnorePattern(line: string, prefix: string): string | null {
function addIgnoreRules (line 163) | function addIgnoreRules(ig: IgnoreMatcher, dir: string, rootDir: string)...
function isPattern (line 183) | function isPattern(s: string): boolean {
function splitPatterns (line 187) | function splitPatterns(entries: string[]): { plain: string[]; patterns: ...
function collectFiles (line 200) | function collectFiles(
function collectSkillEntries (line 251) | function collectSkillEntries(
function collectAutoSkillEntries (line 305) | function collectAutoSkillEntries(dir: string, includeRootFiles = true): ...
function findGitRepoRoot (line 309) | function findGitRepoRoot(startDir: string): string | null {
function collectAncestorAgentsSkillDirs (line 323) | function collectAncestorAgentsSkillDirs(startDir: string): string[] {
function collectAutoPromptEntries (line 344) | function collectAutoPromptEntries(dir: string): string[] {
function collectAutoThemeEntries (line 381) | function collectAutoThemeEntries(dir: string): string[] {
function readPiManifestFile (line 418) | function readPiManifestFile(packageJsonPath: string): PiManifest | null {
function resolveExtensionEntries (line 428) | function resolveExtensionEntries(dir: string): string[] | null {
function collectAutoExtensionEntries (line 458) | function collectAutoExtensionEntries(dir: string): string[] {
function collectResourceFiles (line 516) | function collectResourceFiles(dir: string, resourceType: ResourceType): ...
function matchesAnyPattern (line 526) | function matchesAnyPattern(filePath: string, patterns: string[], baseDir...
function normalizeExactPattern (line 554) | function normalizeExactPattern(pattern: string): string {
function matchesAnyExactPattern (line 559) | function matchesAnyExactPattern(filePath: string, patterns: string[], ba...
function getOverridePatterns (line 579) | function getOverridePatterns(entries: string[]): string[] {
function isEnabledByOverrides (line 583) | function isEnabledByOverrides(filePath: string, patterns: string[], base...
function applyPatterns (line 610) | function applyPatterns(allPaths: string[], patterns: string[], baseDir: ...
class DefaultPackageManager (line 658) | class DefaultPackageManager implements PackageManager {
method constructor (line 666) | constructor(options: PackageManagerOptions) {
method setProgressCallback (line 672) | setProgressCallback(callback: ProgressCallback | undefined): void {
method addSourceToSettings (line 676) | addSourceToSettings(source: string, options?: { local?: boolean }): bo...
method removeSourceFromSettings (line 695) | removeSourceFromSettings(source: string, options?: { local?: boolean }...
method getInstalledPath (line 713) | getInstalledPath(source: string, scope: "user" | "project"): string | ...
method emitProgress (line 731) | private emitProgress(event: ProgressEvent): void {
method withProgress (line 735) | private async withProgress(
method resolve (line 752) | async resolve(onMissing?: (source: string) => Promise<MissingSourceAct...
method resolveExtensionSources (line 806) | async resolveExtensionSources(
method install (line 817) | async install(source: string, options?: { local?: boolean }): Promise<...
method remove (line 840) | async remove(source: string, options?: { local?: boolean }): Promise<v...
method update (line 859) | async update(source?: string): Promise<void> {
method updateSourceForScope (line 876) | private async updateSourceForScope(source: string, scope: SourceScope)...
method checkForAvailableUpdates (line 897) | async checkForAvailableUpdates(): Promise<PackageUpdate[]> {
method resolvePackageSources (line 962) | private async resolvePackageSources(
method resolveLocalExtensionSource (line 1022) | private resolveLocalExtensionSource(
method installParsedSource (line 1053) | private async installParsedSource(parsed: ParsedSource, scope: SourceS...
method getPackageSourceString (line 1064) | private getPackageSourceString(pkg: PackageSource): string {
method getSourceMatchKeyForInput (line 1068) | private getSourceMatchKeyForInput(source: string): string {
method getSourceMatchKeyForSettings (line 1079) | private getSourceMatchKeyForSettings(source: string, scope: SourceScop...
method packageSourcesMatch (line 1091) | private packageSourcesMatch(existing: PackageSource, inputSource: stri...
method normalizePackageSourceForSettings (line 1097) | private normalizePackageSourceForSettings(source: string, scope: Sourc...
method parseSource (line 1108) | private parseSource(source: string): ParsedSource {
method installedNpmMatchesPinnedVersion (line 1141) | private async installedNpmMatchesPinnedVersion(source: NpmSource, inst...
method npmHasAvailableUpdate (line 1155) | private async npmHasAvailableUpdate(source: NpmSource, installedPath: ...
method getInstalledNpmVersion (line 1173) | private getInstalledNpmVersion(installedPath: string): string | undefi...
method getLatestNpmVersion (line 1185) | private async getLatestNpmVersion(packageName: string): Promise<string> {
method gitHasAvailableUpdate (line 1194) | private async gitHasAvailableUpdate(installedPath: string): Promise<bo...
method getRemoteGitHead (line 1211) | private async getRemoteGitHead(installedPath: string): Promise<string> {
method getGitUpstreamRef (line 1229) | private async getGitUpstreamRef(installedPath: string): Promise<string...
method runGitRemoteCommand (line 1246) | private runGitRemoteCommand(installedPath: string, args: string[]): Pr...
method runWithConcurrency (line 1256) | private async runWithConcurrency<T>(tasks: Array<() => Promise<T>>, li...
method getPackageIdentity (line 1286) | private getPackageIdentity(source: string, scope?: SourceScope): string {
method dedupePackages (line 1306) | private dedupePackages(
method parseNpmSpec (line 1329) | private parseNpmSpec(spec: string): { name: string; version?: string } {
method getNpmCommand (line 1339) | private getNpmCommand(): { command: string; args: string[] } {
method runNpmCommand (line 1351) | private async runNpmCommand(args: string[], options?: { cwd?: string }...
method runNpmCommandSync (line 1356) | private runNpmCommandSync(args: string[]): string {
method installNpm (line 1361) | private async installNpm(source: NpmSource, scope: SourceScope, tempor...
method uninstallNpm (line 1371) | private async uninstallNpm(source: NpmSource, scope: SourceScope): Pro...
method installGit (line 1383) | private async installGit(source: GitSource, scope: SourceScope): Promi...
method updateGit (line 1404) | private async updateGit(source: GitSource, scope: SourceScope): Promis...
method refreshTemporaryGitSource (line 1431) | private async refreshTemporaryGitSource(source: GitSource, sourceStr: ...
method removeGit (line 1444) | private async removeGit(source: GitSource, scope: SourceScope): Promis...
method pruneEmptyGitParents (line 1451) | private pruneEmptyGitParents(targetDir: string, installRoot: string | ...
method ensureNpmProject (line 1473) | private ensureNpmProject(installRoot: string): void {
method ensureGitIgnore (line 1485) | private ensureGitIgnore(dir: string): void {
method getNpmInstallRoot (line 1495) | private getNpmInstallRoot(scope: SourceScope, temporary: boolean): str...
method getGlobalNpmRoot (line 1505) | private getGlobalNpmRoot(): string {
method getNpmInstallPath (line 1517) | private getNpmInstallPath(source: NpmSource, scope: SourceScope): stri...
method getGitInstallPath (line 1527) | private getGitInstallPath(source: GitSource, scope: SourceScope): stri...
method getGitInstallRoot (line 1537) | private getGitInstallRoot(scope: SourceScope): string | undefined {
method getTemporaryDir (line 1547) | private getTemporaryDir(prefix: string, suffix?: string): string {
method getBaseDirForScope (line 1555) | private getBaseDirForScope(scope: SourceScope): string {
method resolvePath (line 1565) | private resolvePath(input: string): string {
method resolvePathFromBase (line 1573) | private resolvePathFromBase(input: string, baseDir: string): string {
method collectPackageResources (line 1581) | private collectPackageResources(
method collectDefaultResources (line 1630) | private collectDefaultResources(
method applyPackageFilter (line 1652) | private applyPackageFilter(
method collectManifestFiles (line 1683) | private collectManifestFiles(
method readPiManifest (line 1705) | private readPiManifest(packageRoot: string): PiManifest | null {
method addManifestEntries (line 1720) | private addManifestEntries(
method collectFilesFromManifestEntries (line 1740) | private collectFilesFromManifestEntries(entries: string[], root: strin...
method resolveLocalEntries (line 1746) | private resolveLocalEntries(
method addAutoDiscoveredResources (line 1769) | private addAutoDiscoveredResources(
method collectFilesFromPaths (line 1895) | private collectFilesFromPaths(paths: string[], resourceType: ResourceT...
method getTargetMap (line 1914) | private getTargetMap(
method addResource (line 1932) | private addResource(
method createAccumulator (line 1944) | private createAccumulator(): ResourceAccumulator {
method toResolvedPaths (line 1953) | private toResolvedPaths(accumulator: ResourceAccumulator): ResolvedPat...
method runCommandCapture (line 1970) | private runCommandCapture(
method runCommand (line 2018) | private runCommand(command: string, args: string[], options?: { cwd?: ...
method runCommandSync (line 2036) | private runCommandSync(command: string, args: string[]): string {
FILE: packages/coding-agent/src/core/prompt-templates.ts
type PromptTemplate (line 10) | interface PromptTemplate {
function parseCommandArgs (line 22) | function parseCommandArgs(argsString: string): string[] {
function substituteArgs (line 66) | function substituteArgs(content: string, args: string[]): string {
function loadTemplateFromFile (line 102) | function loadTemplateFromFile(filePath: string, source: string, sourceLa...
function loadTemplatesFromDir (line 138) | function loadTemplatesFromDir(dir: string, source: string, sourceLabel: ...
type LoadPromptTemplatesOptions (line 177) | interface LoadPromptTemplatesOptions {
function normalizePath (line 188) | function normalizePath(input: string): string {
function resolvePromptPath (line 196) | function resolvePromptPath(p: string, cwd: string): string {
function buildPathSourceLabel (line 201) | function buildPathSourceLabel(p: string): string {
function loadPromptTemplates (line 212) | function loadPromptTemplates(options: LoadPromptTemplatesOptions = {}): ...
function expandPromptTemplate (line 285) | function expandPromptTemplate(text: string, templates: PromptTemplate[])...
FILE: packages/coding-agent/src/core/resolve-config-value.ts
function resolveConfigValue (line 17) | function resolveConfigValue(config: string): string | undefined {
function executeWithConfiguredShell (line 25) | function executeWithConfiguredShell(command: string): { executed: boolea...
function executeWithDefaultShell (line 55) | function executeWithDefaultShell(command: string): string | undefined {
function executeCommand (line 68) | function executeCommand(commandConfig: string): string | undefined {
function resolveHeaders (line 89) | function resolveHeaders(headers: Record<string, string> | undefined): Re...
function clearConfigValueCache (line 102) | function clearConfigValueCache(): void {
FILE: packages/coding-agent/src/core/resource-loader.ts
type ResourceExtensionPaths (line 21) | interface ResourceExtensionPaths {
type ResourceLoader (line 27) | interface ResourceLoader {
function resolvePromptInput (line 40) | function resolvePromptInput(input: string | undefined, description: stri...
function loadContextFileFromDir (line 57) | function loadContextFileFromDir(dir: string): { path: string; content: s...
function loadProjectContextFiles (line 75) | function loadProjectContextFiles(
type DefaultResourceLoaderOptions (line 114) | interface DefaultResourceLoaderOptions {
class DefaultResourceLoader (line 150) | class DefaultResourceLoader implements ResourceLoader {
method constructor (line 201) | constructor(options: DefaultResourceLoaderOptions) {
method getExtensions (line 245) | getExtensions(): LoadExtensionsResult {
method getSkills (line 249) | getSkills(): { skills: Skill[]; diagnostics: ResourceDiagnostic[] } {
method getPrompts (line 253) | getPrompts(): { prompts: PromptTemplate[]; diagnostics: ResourceDiagno...
method getThemes (line 257) | getThemes(): { themes: Theme[]; diagnostics: ResourceDiagnostic[] } {
method getAgentsFiles (line 261) | getAgentsFiles(): { agentsFiles: Array<{ path: string; content: string...
method getSystemPrompt (line 265) | getSystemPrompt(): string | undefined {
method getAppendSystemPrompt (line 269) | getAppendSystemPrompt(): string[] {
method getPathMetadata (line 273) | getPathMetadata(): Map<string, PathMetadata> {
method extendResources (line 277) | extendResources(paths: ResourceExtensionPaths): void {
method reload (line 307) | async reload(): Promise<void> {
method normalizeExtensionPaths (line 438) | private normalizeExtensionPaths(
method updateSkillsFromPaths (line 447) | private updateSkillsFromPaths(
method updatePromptsFromPaths (line 474) | private updatePromptsFromPaths(
method updateThemesFromPaths (line 502) | private updateTh
Copy disabled (too large)
Download .json
Condensed preview — 708 files, each showing path, character count, and a content snippet. Download the .json file for the full structured content (10,591K chars).
[
{
"path": ".gitattributes",
"chars": 382,
"preview": "# Default to LF for text files across the repo\n* text=auto eol=lf\n\n# Windows scripts should keep CRLF\n*.bat text eol=crl"
},
{
"path": ".github/APPROVED_CONTRIBUTORS",
"chars": 1391,
"preview": "# GitHub handles of users approved to submit PRs\n# One handle per line (without @)\n# Add new contributors by commenting "
},
{
"path": ".github/APPROVED_CONTRIBUTORS.vacation",
"chars": 1235,
"preview": "# GitHub handles of users approved to submit PRs\n# One handle per line (without @)\n# Add new contributors by commenting "
},
{
"path": ".github/ISSUE_TEMPLATE/bug.yml",
"chars": 703,
"preview": "name: Bug Report\ndescription: Report something that's broken\nlabels: [\"bug\"]\nbody:\n - type: textarea\n id: descriptio"
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 174,
"preview": "blank_issues_enabled: false\ncontact_links:\n - name: Questions\n url: https://discord.com/invite/3cU7Bz4UPx\n about:"
},
{
"path": ".github/ISSUE_TEMPLATE/contribution.yml",
"chars": 925,
"preview": "name: Contribution Proposal\ndescription: Propose a change or feature (required for new contributors before submitting a "
},
{
"path": ".github/workflows/approve-contributor.yml",
"chars": 3691,
"preview": "name: Approve Contributor\n\non:\n issue_comment:\n types: [created]\n\njobs:\n approve:\n if: ${{ !github.event.issue.p"
},
{
"path": ".github/workflows/build-binaries.yml",
"chars": 2355,
"preview": "name: Build Binaries\n\non:\n push:\n tags:\n - 'v*'\n workflow_dispatch:\n inputs:\n tag:\n description"
},
{
"path": ".github/workflows/ci.yml",
"chars": 853,
"preview": "name: CI\n\non:\n push:\n branches: [main]\n pull_request:\n branches: [main]\n\nconcurrency:\n group: ci-${{ github.ref"
},
{
"path": ".github/workflows/oss-weekend-issues.yml",
"chars": 3508,
"preview": "name: OSS Weekend Issues\n\non:\n issues:\n types: [opened]\n\njobs:\n close-issues-during-weekend:\n runs-on: ubuntu-la"
},
{
"path": ".github/workflows/pr-gate.yml",
"chars": 5287,
"preview": "name: PR Gate\n\non:\n pull_request_target:\n types: [opened]\n\njobs:\n check-contributor:\n runs-on: ubuntu-latest\n "
},
{
"path": ".gitignore",
"chars": 404,
"preview": "node_modules/\ndist/\n*.log\n.DS_Store\n*.tsbuildinfo\n# packages/*/node_modules/\npackages/*/dist/\npackages/*/dist-chrome/\npa"
},
{
"path": ".husky/pre-commit",
"chars": 943,
"preview": "#!/bin/sh\n\n# Get list of staged files before running check\nSTAGED_FILES=$(git diff --cached --name-only)\n\n# Run the chec"
},
{
"path": ".pi/extensions/diff.ts",
"chars": 6946,
"preview": "/**\n * Diff Extension\n *\n * /diff command shows modified/deleted/new files from git status and opens\n * the selected fil"
},
{
"path": ".pi/extensions/files.ts",
"chars": 6387,
"preview": "/**\n * Files Extension\n *\n * /files command lists all files the model has read/written/edited in the active session bran"
},
{
"path": ".pi/extensions/prompt-url-widget.ts",
"chars": 4872,
"preview": "import { DynamicBorder, type ExtensionAPI, type ExtensionContext } from \"@mariozechner/pi-coding-agent\";\nimport { Contai"
},
{
"path": ".pi/extensions/redraws.ts",
"chars": 605,
"preview": "/**\n * Redraws Extension\n *\n * Exposes /tui to show TUI redraw stats.\n */\n\nimport type { ExtensionAPI } from \"@mariozech"
},
{
"path": ".pi/extensions/tps.ts",
"chars": 1552,
"preview": "import type { AssistantMessage } from \"@mariozechner/pi-ai\";\nimport type { ExtensionAPI } from \"@mariozechner/pi-coding-"
},
{
"path": ".pi/git/.gitignore",
"chars": 14,
"preview": "*\n!.gitignore\n"
},
{
"path": ".pi/npm/.gitignore",
"chars": 14,
"preview": "*\n!.gitignore\n"
},
{
"path": ".pi/prompts/cl.md",
"chars": 2151,
"preview": "---\ndescription: Audit changelog entries before release\n---\nAudit changelog entries for all commits since the last relea"
},
{
"path": ".pi/prompts/is.md",
"chars": 1032,
"preview": "---\ndescription: Analyze GitHub issues (bugs or feature requests)\n---\nAnalyze GitHub issue(s): $ARGUMENTS\n\nFor each issu"
},
{
"path": ".pi/prompts/pr.md",
"chars": 2232,
"preview": "---\ndescription: Review PRs from URLs with structured issue and code analysis\n---\nYou are given one or more GitHub PR UR"
},
{
"path": "AGENTS.md",
"chars": 10893,
"preview": "# Development Rules\n\n## First Message\nIf the user did not give you a concrete task in their first message,\nread README.m"
},
{
"path": "CONTRIBUTING.md",
"chars": 1685,
"preview": "# Contributing to pi\n\nThanks for wanting to contribute! This guide exists to save both of us time.\n\n## The One Rule\n\n**Y"
},
{
"path": "LICENSE",
"chars": 1069,
"preview": "MIT License\n\nCopyright (c) 2025 Mario Zechner\n\nPermission is hereby granted, free of charge, to any person obtaining a c"
},
{
"path": "README.md",
"chars": 2437,
"preview": "<p align=\"center\">\n <a href=\"https://shittycodingagent.ai\">\n <img src=\"https://shittycodingagent.ai/logo.svg\" alt=\"p"
},
{
"path": "biome.json",
"chars": 895,
"preview": "{\n\t\"$schema\": \"https://biomejs.dev/schemas/2.3.5/schema.json\",\n\t\"linter\": {\n\t\t\"enabled\": true,\n\t\t\"rules\": {\n\t\t\t\"recommen"
},
{
"path": "package.json",
"chars": 3036,
"preview": "{\n\t\"name\": \"pi-monorepo\",\n\t\"private\": true,\n\t\"type\": \"module\",\n\t\"workspaces\": [\n\t\t\"packages/*\",\n\t\t\"packages/web-ui/examp"
},
{
"path": "packages/agent/CHANGELOG.md",
"chars": 7540,
"preview": "# Changelog\n\n## [Unreleased]\n\n## [0.61.0] - 2026-03-20\n\n## [0.60.0] - 2026-03-18\n\n## [0.59.0] - 2026-03-17\n\n## [0.58.4] "
},
{
"path": "packages/agent/README.md",
"chars": 12792,
"preview": "# @mariozechner/pi-agent-core\n\nStateful agent with tool execution and event streaming. Built on `@mariozechner/pi-ai`.\n\n"
},
{
"path": "packages/agent/package.json",
"chars": 986,
"preview": "{\n\t\"name\": \"@mariozechner/pi-agent-core\",\n\t\"version\": \"0.61.0\",\n\t\"description\": \"General-purpose agent with transport ab"
},
{
"path": "packages/agent/src/agent-loop.ts",
"chars": 16084,
"preview": "/**\n * Agent loop that works with AgentMessage throughout.\n * Transforms to Message[] only at the LLM call boundary.\n */"
},
{
"path": "packages/agent/src/agent.ts",
"chars": 16481,
"preview": "/**\n * Agent class that uses the agent-loop directly.\n * No transport abstraction - calls streamSimple via the loop.\n */"
},
{
"path": "packages/agent/src/index.ts",
"chars": 177,
"preview": "// Core Agent\nexport * from \"./agent.js\";\n// Loop functions\nexport * from \"./agent-loop.js\";\n// Proxy utilities\nexport *"
},
{
"path": "packages/agent/src/proxy.ts",
"chars": 9677,
"preview": "/**\n * Proxy stream function for apps that route LLM calls through a server.\n * The server manages auth and proxies requ"
},
{
"path": "packages/agent/src/types.ts",
"chars": 11205,
"preview": "import type {\n\tAssistantMessage,\n\tAssistantMessageEvent,\n\tImageContent,\n\tMessage,\n\tModel,\n\tSimpleStreamOptions,\n\tstreamS"
},
{
"path": "packages/agent/test/agent-loop.test.ts",
"chars": 18219,
"preview": "import {\n\ttype AssistantMessage,\n\ttype AssistantMessageEvent,\n\tEventStream,\n\ttype Message,\n\ttype Model,\n\ttype UserMessag"
},
{
"path": "packages/agent/test/agent.test.ts",
"chars": 10255,
"preview": "import { type AssistantMessage, type AssistantMessageEvent, EventStream, getModel } from \"@mariozechner/pi-ai\";\nimport {"
},
{
"path": "packages/agent/test/bedrock-models.test.ts",
"chars": 9850,
"preview": "/**\n * A test suite to ensure Amazon Bedrock models work correctly with the agent loop.\n *\n * Some Bedrock models don't "
},
{
"path": "packages/agent/test/bedrock-utils.ts",
"chars": 552,
"preview": "/**\n * Utility functions for Amazon Bedrock tests\n */\n\n/**\n * Check if any valid AWS credentials are configured for Bedr"
},
{
"path": "packages/agent/test/e2e.test.ts",
"chars": 15115,
"preview": "import type { AssistantMessage, Model, ToolResultMessage, UserMessage } from \"@mariozechner/pi-ai\";\nimport { getModel } "
},
{
"path": "packages/agent/test/utils/calculate.ts",
"chars": 1059,
"preview": "import { type Static, Type } from \"@sinclair/typebox\";\nimport type { AgentTool, AgentToolResult } from \"../../src/types."
},
{
"path": "packages/agent/test/utils/get-current-time.ts",
"chars": 1507,
"preview": "import { type Static, Type } from \"@sinclair/typebox\";\nimport type { AgentTool, AgentToolResult } from \"../../src/types."
},
{
"path": "packages/agent/tsconfig.build.json",
"chars": 209,
"preview": "{\n\t\"extends\": \"../../tsconfig.base.json\",\n\t\"compilerOptions\": {\n\t\t\"outDir\": \"./dist\",\n\t\t\"rootDir\": \"./src\"\n\t},\n\t\"include"
},
{
"path": "packages/agent/vitest.config.ts",
"chars": 184,
"preview": "import { defineConfig } from \"vitest/config\";\n\nexport default defineConfig({\n\ttest: {\n\t\tglobals: true,\n\t\tenvironment: \"n"
},
{
"path": "packages/ai/CHANGELOG.md",
"chars": 57304,
"preview": "# Changelog\n\n## [Unreleased]\n\n## [0.61.0] - 2026-03-20\n\n### Added\n\n- Added `gpt-5.4-mini` model support for the `openai-"
},
{
"path": "packages/ai/README.md",
"chars": 46383,
"preview": "# @mariozechner/pi-ai\n\nUnified LLM API with automatic model discovery, provider configuration, token and cost tracking, "
},
{
"path": "packages/ai/bedrock-provider.d.ts",
"chars": 44,
"preview": "export * from \"./dist/bedrock-provider.js\";\n"
},
{
"path": "packages/ai/bedrock-provider.js",
"chars": 44,
"preview": "export * from \"./dist/bedrock-provider.js\";\n"
},
{
"path": "packages/ai/package.json",
"chars": 3019,
"preview": "{\n\t\"name\": \"@mariozechner/pi-ai\",\n\t\"version\": \"0.61.0\",\n\t\"description\": \"Unified LLM API with automatic model discovery "
},
{
"path": "packages/ai/scripts/generate-models.ts",
"chars": 46353,
"preview": "#!/usr/bin/env tsx\n\nimport { writeFileSync } from \"fs\";\nimport { join, dirname } from \"path\";\nimport { fileURLToPath } f"
},
{
"path": "packages/ai/scripts/generate-test-image.ts",
"chars": 943,
"preview": "#!/usr/bin/env tsx\n\nimport { createCanvas } from \"canvas\";\nimport { writeFileSync } from \"fs\";\nimport { join, dirname } "
},
{
"path": "packages/ai/src/api-registry.ts",
"chars": 2563,
"preview": "import type {\n\tApi,\n\tAssistantMessageEventStream,\n\tContext,\n\tModel,\n\tSimpleStreamOptions,\n\tStreamFunction,\n\tStreamOption"
},
{
"path": "packages/ai/src/bedrock-provider.ts",
"chars": 165,
"preview": "import { streamBedrock, streamSimpleBedrock } from \"./providers/amazon-bedrock.js\";\n\nexport const bedrockProviderModule "
},
{
"path": "packages/ai/src/cli.ts",
"chars": 3864,
"preview": "#!/usr/bin/env node\n\nimport { existsSync, readFileSync, writeFileSync } from \"fs\";\nimport { createInterface } from \"read"
},
{
"path": "packages/ai/src/env-api-keys.ts",
"chars": 5021,
"preview": "// NEVER convert to top-level imports - breaks browser/Vite builds (web-ui)\nlet _existsSync: typeof import(\"node:fs\").ex"
},
{
"path": "packages/ai/src/index.ts",
"chars": 1408,
"preview": "export type { Static, TSchema } from \"@sinclair/typebox\";\nexport { Type } from \"@sinclair/typebox\";\n\nexport * from \"./ap"
},
{
"path": "packages/ai/src/models.generated.ts",
"chars": 351346,
"preview": "// This file is auto-generated by scripts/generate-models.ts\n// Do not edit manually - run 'npm run generate-models' to "
},
{
"path": "packages/ai/src/models.ts",
"chars": 2888,
"preview": "import { MODELS } from \"./models.generated.js\";\nimport type { Api, KnownProvider, Model, Usage } from \"./types.js\";\n\ncon"
},
{
"path": "packages/ai/src/oauth.ts",
"chars": 40,
"preview": "export * from \"./utils/oauth/index.js\";\n"
},
{
"path": "packages/ai/src/providers/amazon-bedrock.ts",
"chars": 24929,
"preview": "import {\n\tBedrockRuntimeClient,\n\ttype BedrockRuntimeClientConfig,\n\tStopReason as BedrockStopReason,\n\ttype Tool as Bedroc"
},
{
"path": "packages/ai/src/providers/anthropic.ts",
"chars": 27709,
"preview": "import Anthropic from \"@anthropic-ai/sdk\";\nimport type {\n\tContentBlockParam,\n\tMessageCreateParamsStreaming,\n\tMessagePara"
},
{
"path": "packages/ai/src/providers/azure-openai-responses.ts",
"chars": 7991,
"preview": "import { AzureOpenAI } from \"openai\";\nimport type { ResponseCreateParamsStreaming } from \"openai/resources/responses/res"
},
{
"path": "packages/ai/src/providers/github-copilot-headers.ts",
"chars": 1184,
"preview": "import type { Message } from \"../types.js\";\n\n// Copilot expects X-Initiator to indicate whether the request is user-init"
},
{
"path": "packages/ai/src/providers/google-gemini-cli.ts",
"chars": 30059,
"preview": "/**\n * Google Gemini CLI / Antigravity provider.\n * Shared implementation for both google-gemini-cli and google-antigrav"
},
{
"path": "packages/ai/src/providers/google-shared.ts",
"chars": 12071,
"preview": "/**\n * Shared utilities for Google Generative AI and Google Cloud Code Assist providers.\n */\n\nimport { type Content, Fin"
},
{
"path": "packages/ai/src/providers/google-vertex.ts",
"chars": 14987,
"preview": "import {\n\ttype GenerateContentConfig,\n\ttype GenerateContentParameters,\n\tGoogleGenAI,\n\ttype ThinkingConfig,\n\tThinkingLeve"
},
{
"path": "packages/ai/src/providers/google.ts",
"chars": 13308,
"preview": "import {\n\ttype GenerateContentConfig,\n\ttype GenerateContentParameters,\n\tGoogleGenAI,\n\ttype ThinkingConfig,\n} from \"@goog"
},
{
"path": "packages/ai/src/providers/mistral.ts",
"chars": 18594,
"preview": "import { Mistral } from \"@mistralai/mistralai\";\nimport type { RequestOptions } from \"@mistralai/mistralai/lib/sdks.js\";\n"
},
{
"path": "packages/ai/src/providers/openai-codex-responses.ts",
"chars": 28858,
"preview": "import type * as NodeOs from \"node:os\";\nimport type { Tool as OpenAITool, ResponseInput, ResponseStreamEvent } from \"ope"
},
{
"path": "packages/ai/src/providers/openai-completions.ts",
"chars": 29486,
"preview": "import OpenAI from \"openai\";\nimport type {\n\tChatCompletionAssistantMessageParam,\n\tChatCompletionChunk,\n\tChatCompletionCo"
},
{
"path": "packages/ai/src/providers/openai-responses-shared.ts",
"chars": 17347,
"preview": "import type OpenAI from \"openai\";\nimport type {\n\tTool as OpenAITool,\n\tResponseCreateParamsStreaming,\n\tResponseFunctionCa"
},
{
"path": "packages/ai/src/providers/openai-responses.ts",
"chars": 7867,
"preview": "import OpenAI from \"openai\";\nimport type { ResponseCreateParamsStreaming } from \"openai/resources/responses/responses.js"
},
{
"path": "packages/ai/src/providers/register-builtins.ts",
"chars": 15628,
"preview": "import { clearApiProviders, registerApiProvider } from \"../api-registry.js\";\nimport type {\n\tApi,\n\tAssistantMessage,\n\tAss"
},
{
"path": "packages/ai/src/providers/simple-options.ts",
"chars": 1511,
"preview": "import type { Api, Model, SimpleStreamOptions, StreamOptions, ThinkingBudgets, ThinkingLevel } from \"../types.js\";\n\nexpo"
},
{
"path": "packages/ai/src/providers/transform-messages.ts",
"chars": 5675,
"preview": "import type { Api, AssistantMessage, Message, Model, ToolCall, ToolResultMessage } from \"../types.js\";\n\n/**\n * Normalize"
},
{
"path": "packages/ai/src/stream.ts",
"chars": 1486,
"preview": "import \"./providers/register-builtins.js\";\n\nimport { getApiProvider } from \"./api-registry.js\";\nimport type {\n\tApi,\n\tAss"
},
{
"path": "packages/ai/src/types.ts",
"chars": 12043,
"preview": "import type { AssistantMessageEventStream } from \"./utils/event-stream.js\";\n\nexport type { AssistantMessageEventStream }"
},
{
"path": "packages/ai/src/utils/event-stream.ts",
"chars": 2300,
"preview": "import type { AssistantMessage, AssistantMessageEvent } from \"../types.js\";\n\n// Generic event stream class for async ite"
},
{
"path": "packages/ai/src/utils/hash.ts",
"chars": 540,
"preview": "/** Fast deterministic hash to shorten long strings */\nexport function shortHash(str: string): string {\n\tlet h1 = 0xdead"
},
{
"path": "packages/ai/src/utils/json-parse.ts",
"chars": 803,
"preview": "import { parse as partialParse } from \"partial-json\";\n\n/**\n * Attempts to parse potentially incomplete JSON during strea"
},
{
"path": "packages/ai/src/utils/oauth/anthropic.ts",
"chars": 11632,
"preview": "/**\n * Anthropic OAuth flow (Claude Pro/Max)\n *\n * NOTE: This module uses Node.js http.createServer for the OAuth callba"
},
{
"path": "packages/ai/src/utils/oauth/github-copilot.ts",
"chars": 11801,
"preview": "/**\n * GitHub Copilot OAuth flow\n */\n\nimport { getModels } from \"../../models.js\";\nimport type { Api, Model } from \"../."
},
{
"path": "packages/ai/src/utils/oauth/google-antigravity.ts",
"chars": 13284,
"preview": "/**\n * Antigravity OAuth flow (Gemini 3, Claude, GPT-OSS via Google Cloud)\n * Uses different OAuth credentials than goog"
},
{
"path": "packages/ai/src/utils/oauth/google-gemini-cli.ts",
"chars": 17561,
"preview": "/**\n * Gemini CLI OAuth flow (Google Cloud Code Assist)\n * Standard Gemini models only (gemini-2.0-flash, gemini-2.5-*)\n"
},
{
"path": "packages/ai/src/utils/oauth/index.ts",
"chars": 4961,
"preview": "/**\n * OAuth credential management for AI providers.\n *\n * This module handles login, token refresh, and credential stor"
},
{
"path": "packages/ai/src/utils/oauth/oauth-page.ts",
"chars": 3115,
"preview": "const LOGO_SVG = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 800 800\" aria-hidden=\"true\"><path fill=\"#fff\" fil"
},
{
"path": "packages/ai/src/utils/oauth/openai-codex.ts",
"chars": 13097,
"preview": "/**\n * OpenAI Codex (ChatGPT OAuth) flow\n *\n * NOTE: This module uses Node.js crypto and http for the OAuth callback.\n *"
},
{
"path": "packages/ai/src/utils/oauth/pkce.ts",
"chars": 997,
"preview": "/**\n * PKCE utilities using Web Crypto API.\n * Works in both Node.js 20+ and browsers.\n */\n\n/**\n * Encode bytes as base6"
},
{
"path": "packages/ai/src/utils/oauth/types.ts",
"chars": 1622,
"preview": "import type { Api, Model } from \"../../types.js\";\n\nexport type OAuthCredentials = {\n\trefresh: string;\n\taccess: string;\n\t"
},
{
"path": "packages/ai/src/utils/overflow.ts",
"chars": 5777,
"preview": "import type { AssistantMessage } from \"../types.js\";\n\n/**\n * Regex patterns to detect context overflow errors from diffe"
},
{
"path": "packages/ai/src/utils/sanitize-unicode.ts",
"chars": 1161,
"preview": "/**\n * Removes unpaired Unicode surrogate characters from a string.\n *\n * Unpaired surrogates (high surrogates 0xD800-0x"
},
{
"path": "packages/ai/src/utils/typebox-helpers.ts",
"chars": 799,
"preview": "import { type TUnsafe, Type } from \"@sinclair/typebox\";\n\n/**\n * Creates a string enum schema compatible with Google's AP"
},
{
"path": "packages/ai/src/utils/validation.ts",
"chars": 2938,
"preview": "import AjvModule from \"ajv\";\nimport addFormatsModule from \"ajv-formats\";\n\n// Handle both default and named exports\nconst"
},
{
"path": "packages/ai/test/abort.test.ts",
"chars": 8883,
"preview": "import { describe, expect, it } from \"vitest\";\nimport { getModel } from \"../src/models.js\";\nimport { complete, stream } "
},
{
"path": "packages/ai/test/anthropic-oauth.test.ts",
"chars": 3191,
"preview": "import { afterEach, describe, expect, it, vi } from \"vitest\";\nimport { loginAnthropic, refreshAnthropicToken } from \"../"
},
{
"path": "packages/ai/test/anthropic-tool-name-normalization.test.ts",
"chars": 6575,
"preview": "import { Type } from \"@sinclair/typebox\";\nimport { describe, expect, it } from \"vitest\";\nimport { getModel } from \"../sr"
},
{
"path": "packages/ai/test/azure-utils.ts",
"chars": 950,
"preview": "/**\n * Utility functions for Azure OpenAI tests\n */\n\nfunction parseDeploymentNameMap(value: string | undefined): Map<str"
},
{
"path": "packages/ai/test/bedrock-models.test.ts",
"chars": 2634,
"preview": "/**\n * A test suite to ensure all configured Amazon Bedrock models are usable.\n *\n * This is here to make sure we got co"
},
{
"path": "packages/ai/test/bedrock-utils.ts",
"chars": 552,
"preview": "/**\n * Utility functions for Amazon Bedrock tests\n */\n\n/**\n * Check if any valid AWS credentials are configured for Bedr"
},
{
"path": "packages/ai/test/cache-retention.test.ts",
"chars": 9884,
"preview": "import { afterEach, beforeEach, describe, expect, it } from \"vitest\";\nimport { getModel } from \"../src/models.js\";\nimpor"
},
{
"path": "packages/ai/test/context-overflow.test.ts",
"chars": 28609,
"preview": "/**\n * Test context overflow error handling across providers.\n *\n * Context overflow occurs when the input (prompt + his"
},
{
"path": "packages/ai/test/cross-provider-handoff.test.ts",
"chars": 14544,
"preview": "/**\n * Cross-Provider Handoff Test\n *\n * Tests that contexts generated by one provider/model can be consumed by another."
},
{
"path": "packages/ai/test/empty.test.ts",
"chars": 24485,
"preview": "import { describe, expect, it } from \"vitest\";\nimport { getModel } from \"../src/models.js\";\nimport { complete } from \".."
},
{
"path": "packages/ai/test/github-copilot-anthropic.test.ts",
"chars": 3414,
"preview": "import { describe, expect, it, vi } from \"vitest\";\nimport { getModel } from \"../src/models.js\";\nimport type { Context } "
},
{
"path": "packages/ai/test/github-copilot-oauth.test.ts",
"chars": 6007,
"preview": "import { afterEach, describe, expect, it, vi } from \"vitest\";\nimport { loginGitHubCopilot } from \"../src/utils/oauth/git"
},
{
"path": "packages/ai/test/google-gemini-cli-claude-thinking-header.test.ts",
"chars": 2890,
"preview": "import { afterEach, describe, expect, it, vi } from \"vitest\";\nimport { streamGoogleGeminiCli } from \"../src/providers/go"
},
{
"path": "packages/ai/test/google-gemini-cli-empty-stream.test.ts",
"chars": 2638,
"preview": "import { afterEach, describe, expect, it, vi } from \"vitest\";\nimport { streamGoogleGeminiCli } from \"../src/providers/go"
},
{
"path": "packages/ai/test/google-gemini-cli-retry-delay.test.ts",
"chars": 1645,
"preview": "import { afterEach, describe, expect, it, vi } from \"vitest\";\nimport { extractRetryDelay } from \"../src/providers/google"
},
{
"path": "packages/ai/test/google-shared-gemini3-unsigned-tool-call.test.ts",
"chars": 4988,
"preview": "import { describe, expect, it } from \"vitest\";\nimport { convertMessages } from \"../src/providers/google-shared.js\";\nimpo"
},
{
"path": "packages/ai/test/google-shared-image-tool-result-routing.test.ts",
"chars": 4127,
"preview": "import { describe, expect, it } from \"vitest\";\nimport { convertMessages } from \"../src/providers/google-shared.js\";\nimpo"
},
{
"path": "packages/ai/test/google-thinking-signature.test.ts",
"chars": 1739,
"preview": "import { describe, expect, it } from \"vitest\";\nimport { isThinkingPart, retainThoughtSignature } from \"../src/providers/"
},
{
"path": "packages/ai/test/google-tool-call-missing-args.test.ts",
"chars": 2652,
"preview": "import { Type } from \"@sinclair/typebox\";\nimport { afterEach, describe, expect, it, vi } from \"vitest\";\nimport { streamG"
},
{
"path": "packages/ai/test/google-vertex-api-key-resolution.test.ts",
"chars": 3418,
"preview": "import { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\n\nconst googleGenAiMock = vi.hoisted(() => ({\n\t"
},
{
"path": "packages/ai/test/image-tool-result.test.ts",
"chars": 16862,
"preview": "import { readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { Type } from \"@sinclair/typebox\";\nimpo"
},
{
"path": "packages/ai/test/interleaved-thinking.test.ts",
"chars": 5308,
"preview": "import { Type } from \"@sinclair/typebox\";\nimport { describe, expect, it } from \"vitest\";\nimport { getEnvApiKey } from \"."
},
{
"path": "packages/ai/test/lazy-module-load.test.ts",
"chars": 3069,
"preview": "import { spawnSync } from \"node:child_process\";\nimport { createRequire } from \"node:module\";\nimport { dirname, resolve }"
},
{
"path": "packages/ai/test/oauth.ts",
"chars": 2512,
"preview": "/**\n * Test helper for resolving API keys from ~/.pi/agent/auth.json\n *\n * Supports both API key and OAuth credentials.\n"
},
{
"path": "packages/ai/test/openai-codex-stream.test.ts",
"chars": 20032,
"preview": "import { mkdtempSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { afte"
},
{
"path": "packages/ai/test/openai-completions-tool-choice.test.ts",
"chars": 6328,
"preview": "import { Type } from \"@sinclair/typebox\";\nimport { beforeEach, describe, expect, it, vi } from \"vitest\";\nimport { getMod"
},
{
"path": "packages/ai/test/openai-completions-tool-result-images.test.ts",
"chars": 2740,
"preview": "import { describe, expect, it } from \"vitest\";\nimport { getModel } from \"../src/models.js\";\nimport { convertMessages } f"
},
{
"path": "packages/ai/test/openai-responses-reasoning-replay-e2e.test.ts",
"chars": 9398,
"preview": "import { Type } from \"@sinclair/typebox\";\nimport { describe, expect, it } from \"vitest\";\nimport { getModel } from \"../sr"
},
{
"path": "packages/ai/test/openai-responses-tool-result-images.test.ts",
"chars": 7336,
"preview": "import { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:u"
},
{
"path": "packages/ai/test/responseid.test.ts",
"chars": 5650,
"preview": "import { describe, expect, it } from \"vitest\";\nimport { getModel } from \"../src/models.js\";\nimport { complete } from \".."
},
{
"path": "packages/ai/test/stream.test.ts",
"chars": 46564,
"preview": "import { Type } from \"@sinclair/typebox\";\nimport { type ChildProcess, execSync, spawn } from \"child_process\";\nimport { r"
},
{
"path": "packages/ai/test/supports-xhigh.test.ts",
"chars": 970,
"preview": "import { describe, expect, it } from \"vitest\";\nimport { getModel, supportsXhigh } from \"../src/models.js\";\n\ndescribe(\"su"
},
{
"path": "packages/ai/test/tokens.test.ts",
"chars": 10554,
"preview": "import { describe, expect, it } from \"vitest\";\nimport { getModel } from \"../src/models.js\";\nimport { stream } from \"../s"
},
{
"path": "packages/ai/test/tool-call-id-normalization.test.ts",
"chars": 9045,
"preview": "/**\n * Tool Call ID Normalization Tests\n *\n * Tests that tool call IDs from OpenAI Responses API (github-copilot, openai"
},
{
"path": "packages/ai/test/tool-call-without-result.test.ts",
"chars": 11901,
"preview": "import { Type } from \"@sinclair/typebox\";\nimport { describe, expect, it } from \"vitest\";\nimport { getModel } from \"../sr"
},
{
"path": "packages/ai/test/total-tokens.test.ts",
"chars": 23731,
"preview": "/**\n * Test totalTokens field across all providers.\n *\n * totalTokens represents the total number of tokens processed by"
},
{
"path": "packages/ai/test/transform-messages-copilot-openai-to-anthropic.test.ts",
"chars": 3544,
"preview": "import { describe, expect, it } from \"vitest\";\nimport { transformMessages } from \"../src/providers/transform-messages.js"
},
{
"path": "packages/ai/test/unicode-surrogate.test.ts",
"chars": 24706,
"preview": "import { Type } from \"@sinclair/typebox\";\nimport { describe, expect, it } from \"vitest\";\nimport { getModel } from \"../sr"
},
{
"path": "packages/ai/test/validation.test.ts",
"chars": 1175,
"preview": "import { Type } from \"@sinclair/typebox\";\nimport { afterEach, describe, expect, it, vi } from \"vitest\";\nimport type { To"
},
{
"path": "packages/ai/test/xhigh.test.ts",
"chars": 2288,
"preview": "import { describe, expect, it } from \"vitest\";\nimport { getModel } from \"../src/models.js\";\nimport { stream } from \"../s"
},
{
"path": "packages/ai/test/zen.test.ts",
"chars": 850,
"preview": "import { describe, expect, it } from \"vitest\";\nimport { MODELS } from \"../src/models.generated.js\";\nimport { complete } "
},
{
"path": "packages/ai/tsconfig.build.json",
"chars": 208,
"preview": "{\n\t\"extends\": \"../../tsconfig.base.json\",\n\t\"compilerOptions\": {\n\t\t\"outDir\": \"./dist\",\n\t\t\"rootDir\": \"./src\"\n\t},\n\t\"include"
},
{
"path": "packages/ai/vitest.config.ts",
"chars": 190,
"preview": "import { defineConfig } from 'vitest/config';\n\nexport default defineConfig({\n test: {\n globals: true,\n environmen"
},
{
"path": "packages/coding-agent/.gitignore",
"chars": 12,
"preview": "*.bun-build\n"
},
{
"path": "packages/coding-agent/CHANGELOG.md",
"chars": 256954,
"preview": "# Changelog\n\n## [Unreleased]\n\n## [0.61.0] - 2026-03-20\n\n### New Features\n\n- Namespaced keybinding ids and a unified keyb"
},
{
"path": "packages/coding-agent/README.md",
"chars": 22573,
"preview": "<p align=\"center\">\n <a href=\"https://shittycodingagent.ai\">\n <img src=\"https://shittycodingagent.ai/logo.svg\" alt=\"p"
},
{
"path": "packages/coding-agent/docs/compaction.md",
"chars": 14863,
"preview": "# Compaction & Branch Summarization\n\nLLMs have limited context windows. When conversations grow too long, pi uses compac"
},
{
"path": "packages/coding-agent/docs/custom-provider.md",
"chars": 18821,
"preview": "# Custom Providers\n\nExtensions can register custom model providers via `pi.registerProvider()`. This enables:\n\n- **Proxi"
},
{
"path": "packages/coding-agent/docs/development.md",
"chars": 1327,
"preview": "# Development\n\nSee [AGENTS.md](../../../AGENTS.md) for additional guidelines.\n\n## Setup\n\n```bash\ngit clone https://githu"
},
{
"path": "packages/coding-agent/docs/extensions.md",
"chars": 71664,
"preview": "> pi can create extensions. Ask it to build one for your use case.\n\n# Extensions\n\nExtensions are TypeScript modules that"
},
{
"path": "packages/coding-agent/docs/json.md",
"chars": 2953,
"preview": "# JSON Event Stream Mode\n\n```bash\npi --mode json \"Your prompt\"\n```\n\nOutputs all session events as JSON lines to stdout. "
},
{
"path": "packages/coding-agent/docs/keybindings.md",
"chars": 7186,
"preview": "# Keybindings\n\nAll keyboard shortcuts can be customized via `~/.pi/agent/keybindings.json`. Each action can be bound to "
},
{
"path": "packages/coding-agent/docs/models.md",
"chars": 10111,
"preview": "# Custom Models\n\nAdd custom providers and models (Ollama, vLLM, LM Studio, proxies) via `~/.pi/agent/models.json`.\n\n## T"
},
{
"path": "packages/coding-agent/docs/packages.md",
"chars": 7543,
"preview": "> pi can help you create pi packages. Ask it to bundle your extensions, skills, prompt templates, or themes.\n\n# Pi Packa"
},
{
"path": "packages/coding-agent/docs/prompt-templates.md",
"chars": 1855,
"preview": "> pi can create prompt templates. Ask it to build one for your workflow.\n\n# Prompt Templates\n\nPrompt templates are Markd"
},
{
"path": "packages/coding-agent/docs/providers.md",
"chars": 6498,
"preview": "# Providers\n\nPi supports subscription-based providers via OAuth and API key providers via environment variables or auth "
},
{
"path": "packages/coding-agent/docs/rpc.md",
"chars": 33778,
"preview": "# RPC Mode\n\nRPC mode enables headless operation of the coding agent via a JSON protocol over stdin/stdout. This is usefu"
},
{
"path": "packages/coding-agent/docs/sdk.md",
"chars": 28500,
"preview": "> pi can help you use the SDK. Ask it to build an integration for your use case.\n\n# SDK\n\nThe SDK provides programmatic a"
},
{
"path": "packages/coding-agent/docs/session.md",
"chars": 14249,
"preview": "# Session File Format\n\nSessions are stored as JSONL (JSON Lines) files. Each line is a JSON object with a `type` field. "
},
{
"path": "packages/coding-agent/docs/settings.md",
"chars": 7668,
"preview": "# Settings\n\nPi uses JSON settings files with project settings overriding global settings.\n\n| Location | Scope |\n|-------"
},
{
"path": "packages/coding-agent/docs/shell-aliases.md",
"chars": 356,
"preview": "# Shell Aliases\n\nPi runs bash in non-interactive mode (`bash -c`), which doesn't expand aliases by default.\n\nTo enable y"
},
{
"path": "packages/coding-agent/docs/skills.md",
"chars": 6218,
"preview": "> pi can create skills. Ask it to build one for your use case.\n\n# Skills\n\nSkills are self-contained capability packages "
},
{
"path": "packages/coding-agent/docs/terminal-setup.md",
"chars": 3501,
"preview": "# Terminal Setup\n\nPi uses the [Kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol/) for reliable"
},
{
"path": "packages/coding-agent/docs/termux.md",
"chars": 3185,
"preview": "# Termux (Android) Setup\n\nPi runs on Android via [Termux](https://termux.dev/), a terminal emulator and Linux environmen"
},
{
"path": "packages/coding-agent/docs/themes.md",
"chars": 7930,
"preview": "> pi can create themes. Ask it to build one for your setup.\n\n# Themes\n\nThemes are JSON files that define colors for the "
},
{
"path": "packages/coding-agent/docs/tmux.md",
"chars": 1806,
"preview": "# tmux Setup\n\nPi works inside tmux, but tmux strips modifier information from certain keys by default. Without configura"
},
{
"path": "packages/coding-agent/docs/tree.md",
"chars": 7332,
"preview": "# Session Tree Navigation\n\nThe `/tree` command provides tree-based navigation of the session history.\n\n## Overview\n\nSess"
},
{
"path": "packages/coding-agent/docs/tui.md",
"chars": 27853,
"preview": "> pi can create TUI components. Ask it to build one for your use case.\n\n# TUI Components\n\nExtensions and custom tools ca"
},
{
"path": "packages/coding-agent/docs/windows.md",
"chars": 394,
"preview": "# Windows Setup\n\nPi requires a bash shell on Windows. Checked locations (in order):\n\n1. Custom path from `~/.pi/agent/se"
},
{
"path": "packages/coding-agent/examples/README.md",
"chars": 906,
"preview": "# Examples\n\nExample code for pi-coding-agent SDK and extensions.\n\n## Directories\n\n### [sdk/](sdk/)\nProgrammatic usage vi"
},
{
"path": "packages/coding-agent/examples/extensions/README.md",
"chars": 9050,
"preview": "# Extension Examples\n\nExample extensions for pi-coding-agent.\n\n## Usage\n\n```bash\n# Load an extension with --extension fl"
},
{
"path": "packages/coding-agent/examples/extensions/antigravity-image-gen.ts",
"chars": 12750,
"preview": "/**\n * Antigravity Image Generation\n *\n * Generates images via Google Antigravity's image models (gemini-3-pro-image, im"
},
{
"path": "packages/coding-agent/examples/extensions/auto-commit-on-exit.ts",
"chars": 1569,
"preview": "/**\n * Auto-Commit on Exit Extension\n *\n * Automatically commits changes when the agent exits.\n * Uses the last assistan"
},
{
"path": "packages/coding-agent/examples/extensions/bash-spawn-hook.ts",
"chars": 692,
"preview": "/**\n * Bash Spawn Hook Example\n *\n * Adjusts command, cwd, and env before execution.\n *\n * Usage:\n * pi -e ./bash-spaw"
},
{
"path": "packages/coding-agent/examples/extensions/bookmark.ts",
"chars": 1553,
"preview": "/**\n * Entry bookmarking example.\n *\n * Shows setLabel to mark entries with labels for easy navigation in /tree.\n * Labe"
},
{
"path": "packages/coding-agent/examples/extensions/built-in-tool-renderer.ts",
"chars": 8048,
"preview": "/**\n * Built-in Tool Renderer Example - Custom rendering for built-in tools\n *\n * Demonstrates how to override the rende"
},
{
"path": "packages/coding-agent/examples/extensions/claude-rules.ts",
"chars": 2486,
"preview": "/**\n * Claude Rules Extension\n *\n * Scans the project's .claude/rules/ folder for rule files and lists them\n * in the sy"
},
{
"path": "packages/coding-agent/examples/extensions/commands.ts",
"chars": 2559,
"preview": "/**\n * Commands Extension\n *\n * Demonstrates the pi.getCommands() API by providing a /commands command\n * that lists all"
},
{
"path": "packages/coding-agent/examples/extensions/confirm-destructive.ts",
"chars": 1678,
"preview": "/**\n * Confirm Destructive Actions Extension\n *\n * Prompts for confirmation before destructive session actions (clear, s"
},
{
"path": "packages/coding-agent/examples/extensions/custom-compaction.ts",
"chars": 4073,
"preview": "/**\n * Custom Compaction Extension\n *\n * Replaces the default compaction behavior with a full summary of the entire cont"
},
{
"path": "packages/coding-agent/examples/extensions/custom-footer.ts",
"chars": 2139,
"preview": "/**\n * Custom Footer Extension - demonstrates ctx.ui.setFooter()\n *\n * footerData exposes data not otherwise accessible:"
},
{
"path": "packages/coding-agent/examples/extensions/custom-header.ts",
"chars": 2391,
"preview": "/**\n * Custom Header Extension\n *\n * Demonstrates ctx.ui.setHeader() for replacing the built-in header\n * (logo + keybin"
},
{
"path": "packages/coding-agent/examples/extensions/custom-provider-anthropic/.gitignore",
"chars": 14,
"preview": "node_modules/\n"
},
{
"path": "packages/coding-agent/examples/extensions/custom-provider-anthropic/index.ts",
"chars": 19152,
"preview": "/**\n * Custom Provider Example\n *\n * Demonstrates registering a custom provider with:\n * - Custom API identifier (\"custo"
},
{
"path": "packages/coding-agent/examples/extensions/custom-provider-anthropic/package.json",
"chars": 376,
"preview": "{\n \"name\": \"pi-extension-custom-provider-anthropic\",\n \"private\": true,\n \"version\": \"1.12.0\",\n \"type\": \"module\",\n \"s"
},
{
"path": "packages/coding-agent/examples/extensions/custom-provider-gitlab-duo/.gitignore",
"chars": 14,
"preview": "node_modules/\n"
},
{
"path": "packages/coding-agent/examples/extensions/custom-provider-gitlab-duo/index.ts",
"chars": 10743,
"preview": "/**\n * GitLab Duo Provider Extension\n *\n * Provides access to GitLab Duo AI models (Claude and GPT) through GitLab's AI "
},
{
"path": "packages/coding-agent/examples/extensions/custom-provider-gitlab-duo/package.json",
"chars": 317,
"preview": "{\n \"name\": \"pi-extension-custom-provider-gitlab-duo\",\n \"private\": true,\n \"version\": \"1.12.0\",\n \"type\": \"module\",\n \""
},
{
"path": "packages/coding-agent/examples/extensions/custom-provider-gitlab-duo/test.ts",
"chars": 2688,
"preview": "/**\n * Test script for GitLab Duo extension\n * Run: npx tsx test.ts [model-id] [--thinking]\n *\n * Examples:\n * npx tsx"
},
{
"path": "packages/coding-agent/examples/extensions/custom-provider-qwen-cli/.gitignore",
"chars": 14,
"preview": "node_modules/\n"
},
{
"path": "packages/coding-agent/examples/extensions/custom-provider-qwen-cli/index.ts",
"chars": 10114,
"preview": "/**\n * Qwen CLI Provider Extension\n *\n * Provides access to Qwen models via OAuth authentication with chat.qwen.ai.\n * U"
},
{
"path": "packages/coding-agent/examples/extensions/custom-provider-qwen-cli/package.json",
"chars": 315,
"preview": "{\n \"name\": \"pi-extension-custom-provider-qwen-cli\",\n \"private\": true,\n \"version\": \"1.11.0\",\n \"type\": \"module\",\n \"sc"
},
{
"path": "packages/coding-agent/examples/extensions/dirty-repo-guard.ts",
"chars": 1477,
"preview": "/**\n * Dirty Repo Guard Extension\n *\n * Prevents session changes when there are uncommitted git changes.\n * Useful to en"
},
{
"path": "packages/coding-agent/examples/extensions/doom-overlay/.gitignore",
"chars": 41,
"preview": "# Auto-downloaded on first run\ndoom1.wad\n"
},
{
"path": "packages/coding-agent/examples/extensions/doom-overlay/README.md",
"chars": 1320,
"preview": "# DOOM Overlay Demo\n\nPlay DOOM as an overlay in pi. Demonstrates that the overlay system can handle real-time game rende"
},
{
"path": "packages/coding-agent/examples/extensions/doom-overlay/doom/build/doom.js",
"chars": 64607,
"preview": "var createDoomModule = (() => {\n var _scriptName = typeof document != 'undefined' ? document.currentScript?.src : undef"
},
{
"path": "packages/coding-agent/examples/extensions/doom-overlay/doom/build.sh",
"chars": 3362,
"preview": "#!/usr/bin/env bash\n# Build DOOM for pi-doom using doomgeneric and Emscripten\n\nset -e\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BA"
},
{
"path": "packages/coding-agent/examples/extensions/doom-overlay/doom/doomgeneric_pi.c",
"chars": 1709,
"preview": "/**\n * pi-doom platform implementation for doomgeneric\n *\n * Minimal implementation - no sound, just framebuffer and inp"
},
{
"path": "packages/coding-agent/examples/extensions/doom-overlay/doom-component.ts",
"chars": 3637,
"preview": "/**\n * DOOM Component for overlay mode\n *\n * Renders DOOM frames using half-block characters (▀) with 24-bit color.\n * H"
},
{
"path": "packages/coding-agent/examples/extensions/doom-overlay/doom-engine.ts",
"chars": 4914,
"preview": "/**\n * DOOM Engine - WebAssembly wrapper for doomgeneric\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport"
},
{
"path": "packages/coding-agent/examples/extensions/doom-overlay/doom-keys.ts",
"chars": 3493,
"preview": "/**\n * DOOM key codes (from doomkeys.h)\n */\nexport const DoomKeys = {\n\tKEY_RIGHTARROW: 0xae,\n\tKEY_LEFTARROW: 0xac,\n\tKEY_"
},
{
"path": "packages/coding-agent/examples/extensions/doom-overlay/index.ts",
"chars": 2107,
"preview": "/**\n * DOOM Overlay Demo - Play DOOM as an overlay\n *\n * Usage: pi --extension ./examples/extensions/doom-overlay\n *\n * "
},
{
"path": "packages/coding-agent/examples/extensions/doom-overlay/wad-finder.ts",
"chars": 1552,
"preview": "import { existsSync, writeFileSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileUR"
},
{
"path": "packages/coding-agent/examples/extensions/dynamic-resources/SKILL.md",
"chars": 177,
"preview": "---\nname: dynamic-resources\ndescription: Example skill loaded from resources_discover\n---\n\n# Dynamic Resources Skill\n\nTh"
},
{
"path": "packages/coding-agent/examples/extensions/dynamic-resources/dynamic.json",
"chars": 2034,
"preview": "{\n\t\"$schema\": \"https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme"
}
]
// ... and 508 more files (download for full content)
About this extraction
This page contains the full source code of the badlogic/pi-mono GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 708 files (9.1 MB), approximately 2.4M tokens, and a symbol index with 4718 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.