@code-yeongyu/senpi 2026.7.14 → 2026.7.16-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +143 -0
- package/README.md +6 -6
- package/dist/bun/cli.js +2 -0
- package/dist/bun/cli.js.map +1 -1
- package/dist/cli/args.js +1 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/list-models.d.ts +2 -2
- package/dist/cli/list-models.d.ts.map +1 -1
- package/dist/cli/list-models.js +3 -3
- package/dist/cli/list-models.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +3 -0
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +4 -3
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +14 -5
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +8 -4
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +81 -30
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-providers.js +1 -1
- package/dist/core/auth-providers.js.map +1 -1
- package/dist/core/auth-storage.d.ts +32 -87
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +158 -221
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/cache-stats.d.ts +2 -2
- package/dist/core/cache-stats.d.ts.map +1 -1
- package/dist/core/cache-stats.js +1 -1
- package/dist/core/cache-stats.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts +1 -1
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/dynamic-prompt/build.d.ts.map +1 -1
- package/dist/core/dynamic-prompt/build.js +6 -1
- package/dist/core/dynamic-prompt/build.js.map +1 -1
- package/dist/core/extensions/builtin/bash-timeout/timeout.js +2 -2
- package/dist/core/extensions/builtin/bash-timeout/timeout.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.d.ts.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.js +22 -13
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/runtime-session.d.ts +1 -1
- package/dist/core/extensions/builtin/terminal/runtime-session.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/runtime-session.js +3 -2
- package/dist/core/extensions/builtin/terminal/runtime-session.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +8 -2
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +1 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +3 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +15 -2
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/model-config.d.ts +589 -0
- package/dist/core/model-config.d.ts.map +1 -0
- package/dist/core/model-config.js +288 -0
- package/dist/core/model-config.js.map +1 -0
- package/dist/core/model-registry.d.ts +17 -146
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +69 -943
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts +8 -5
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +19 -18
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/model-runtime.d.ts +81 -0
- package/dist/core/model-runtime.d.ts.map +1 -0
- package/dist/core/model-runtime.js +442 -0
- package/dist/core/model-runtime.js.map +1 -0
- package/dist/core/models-store.d.ts +17 -0
- package/dist/core/models-store.d.ts.map +1 -0
- package/dist/core/models-store.js +46 -0
- package/dist/core/models-store.js.map +1 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +7 -2
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/provider-composer.d.ts +64 -0
- package/dist/core/provider-composer.d.ts.map +1 -0
- package/dist/core/provider-composer.js +406 -0
- package/dist/core/provider-composer.js.map +1 -0
- package/dist/core/provider-display-names.d.ts.map +1 -1
- package/dist/core/provider-display-names.js +2 -1
- package/dist/core/provider-display-names.js.map +1 -1
- package/dist/core/radius.d.ts +2 -0
- package/dist/core/radius.d.ts.map +1 -0
- package/dist/core/radius.js +2 -0
- package/dist/core/radius.js.map +1 -0
- package/dist/core/remote-catalog-provider.d.ts +5 -0
- package/dist/core/remote-catalog-provider.d.ts.map +1 -0
- package/dist/core/remote-catalog-provider.js +83 -0
- package/dist/core/remote-catalog-provider.js.map +1 -0
- package/dist/core/runtime-credentials.d.ts +15 -0
- package/dist/core/runtime-credentials.d.ts.map +1 -0
- package/dist/core/runtime-credentials.js +35 -0
- package/dist/core/runtime-credentials.js.map +1 -0
- package/dist/core/sdk.d.ts +11 -20
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +61 -89
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/skills.js +1 -1
- package/dist/core/skills.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +1 -11
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +10 -13
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +22 -10
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/footer.js +1 -1
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts +4 -8
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +14 -12
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts +13 -4
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +84 -41
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts +4 -4
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +14 -25
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +5 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +155 -129
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +70 -8
- package/dist/package-manager-cli.js.map +1 -1
- package/docs/custom-provider.md +8 -11
- package/docs/extensions.md +78 -555
- package/docs/models.md +4 -0
- package/docs/packages.md +1 -0
- package/docs/providers.md +23 -11
- package/docs/sdk.md +79 -105
- package/docs/usage.md +1 -0
- package/examples/AGENTS.md +34 -0
- package/examples/extensions/README.md +1 -0
- package/examples/extensions/custom-provider-anthropic/index.ts +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/kimi-deferred-tools.ts +61 -0
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/sdk/02-custom-model.ts +5 -8
- package/examples/sdk/09-api-keys-and-oauth.ts +13 -31
- package/examples/sdk/12-full-control.ts +8 -13
- package/examples/sdk/README.md +14 -18
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +32 -0
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +3 -3
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/local-module-loader.ts +2 -2
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/rewrite-imports.ts +4 -4
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/render.ts +32 -32
- package/node_modules/@earendil-works/pi-agent-core/package.json +4 -4
- package/node_modules/@earendil-works/pi-ai/README.md +80 -25
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js +23 -42
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.js +10 -6
- package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/lazy.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/lazy.js +9 -10
- package/node_modules/@earendil-works/pi-ai/dist/api/lazy.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.d.ts +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js +4 -3
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.d.ts +2 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +54 -5
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js +23 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.d.ts +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js +24 -6
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/pi-messages.d.ts +97 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/pi-messages.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/pi-messages.js +307 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/pi-messages.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/pi-messages.lazy.d.ts +3 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/pi-messages.lazy.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/pi-messages.lazy.js +3 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/pi-messages.lazy.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/credential-store.d.ts +2 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/credential-store.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/credential-store.js +3 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/credential-store.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/helpers.d.ts +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/helpers.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/helpers.js +4 -3
- package/node_modules/@earendil-works/pi-ai/dist/auth/helpers.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/anthropic.d.ts +9 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/anthropic.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/{utils → auth}/oauth/anthropic.js +51 -116
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/anthropic.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/device-code.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/device-code.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/github-copilot.d.ts +6 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/github-copilot.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/{utils → auth}/oauth/github-copilot.js +18 -77
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/github-copilot.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/load.d.ts +23 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/load.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/load.js +49 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/load.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/oauth-page.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/oauth-page.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/openai-codex.d.ts +9 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/openai-codex.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/{utils → auth}/oauth/openai-codex.js +61 -155
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/openai-codex.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/pkce.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/pkce.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/radius.d.ts +16 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/radius.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/radius.js +316 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/radius.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/xai.d.ts +6 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/xai.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/xai.js +189 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/xai.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/resolve.d.ts +2 -4
- package/node_modules/@earendil-works/pi-ai/dist/auth/resolve.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/resolve.js +9 -7
- package/node_modules/@earendil-works/pi-ai/dist/auth/resolve.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/types.d.ts +49 -10
- package/node_modules/@earendil-works/pi-ai/dist/auth/types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/types.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/bun-oauth.d.ts +3 -0
- package/node_modules/@earendil-works/pi-ai/dist/bun-oauth.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/bun-oauth.js +17 -0
- package/node_modules/@earendil-works/pi-ai/dist/bun-oauth.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/cli.js +58 -76
- package/node_modules/@earendil-works/pi-ai/dist/cli.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/compat/extension-oauth-types.d.ts +42 -0
- package/node_modules/@earendil-works/pi-ai/dist/compat/extension-oauth-types.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/compat/extension-oauth-types.js +2 -0
- package/node_modules/@earendil-works/pi-ai/dist/compat/extension-oauth-types.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/compat.d.ts +2 -0
- package/node_modules/@earendil-works/pi-ai/dist/compat.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/compat.js +49 -22
- package/node_modules/@earendil-works/pi-ai/dist/compat.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/images-models.d.ts +4 -2
- package/node_modules/@earendil-works/pi-ai/dist/images-models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/images-models.js +5 -4
- package/node_modules/@earendil-works/pi-ai/dist/images-models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/index.d.ts +3 -1
- package/node_modules/@earendil-works/pi-ai/dist/index.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/index.js +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/models-store.d.ts +25 -0
- package/node_modules/@earendil-works/pi-ai/dist/models-store.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/models-store.js +16 -0
- package/node_modules/@earendil-works/pi-ai/dist/models-store.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/models.d.ts +69 -30
- package/node_modules/@earendil-works/pi-ai/dist/models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +328 -120
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/models.js +217 -31
- package/node_modules/@earendil-works/pi-ai/dist/models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/oauth.d.ts +2 -1
- package/node_modules/@earendil-works/pi-ai/dist/oauth.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/oauth.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/oauth.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/all.d.ts +11 -6
- package/node_modules/@earendil-works/pi-ai/dist/providers/all.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/all.js +3 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/all.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js +51 -10
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.js +4 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-auth.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-auth.js +13 -20
- package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-auth.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-stream.d.ts +8 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-stream.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-stream.js +21 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-stream.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-workers-ai.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-workers-ai.js +2 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-workers-ai.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.js +11 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.d.ts +1 -6
- package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.js +1 -2
- package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/google-vertex.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/google-vertex.js +56 -5
- package/node_modules/@earendil-works/pi-ai/dist/providers/google-vertex.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.models.d.ts +66 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.models.js +42 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/moonshotai-cn.models.d.ts +36 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/moonshotai-cn.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/moonshotai-cn.models.js +19 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/moonshotai-cn.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/moonshotai.models.d.ts +36 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/moonshotai.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/moonshotai.models.js +19 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/moonshotai.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.d.ts +57 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.js +19 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts +42 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js +175 -139
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/radius-config.d.ts +26 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/radius-config.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/radius-config.js +59 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/radius-config.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/radius.d.ts +9 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/radius.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/radius.js +54 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/radius.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.d.ts +84 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js +74 -2
- package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xai.d.ts +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xai.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xai.js +15 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/xai.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xai.models.d.ts +6 -114
- package/node_modules/@earendil-works/pi-ai/dist/providers/xai.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xai.models.js +3 -92
- package/node_modules/@earendil-works/pi-ai/dist/providers/xai.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/context-transformer.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/context-transformer.js +9 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/context-transformer.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/index.d.ts +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/index.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/index.js +6 -2
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/index.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/coerce-parameters.d.ts +8 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/coerce-parameters.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/coerce-parameters.js +92 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/coerce-parameters.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/format.d.ts +6 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/format.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/format.js +54 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/format.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/index.d.ts +4 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/index.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/index.js +4 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/index.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-match.d.ts +7 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-match.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-match.js +16 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-match.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-tag-scanner.d.ts +19 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-tag-scanner.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-tag-scanner.js +217 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-tag-scanner.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/parse.d.ts +4 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/parse.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/parse.js +46 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/parse.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/stream-boundary.d.ts +13 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/stream-boundary.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/stream-boundary.js +144 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/stream-boundary.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/stream.d.ts +5 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/stream.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/stream.js +208 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/stream.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/tool-resolver.d.ts +4 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/tool-resolver.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/tool-resolver.js +17 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/tool-resolver.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/xml-entities.d.ts +5 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/xml-entities.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/xml-entities.js +34 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/xml-entities.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/types.d.ts +3 -1
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/types.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/types.d.ts +12 -5
- package/node_modules/@earendil-works/pi-ai/dist/types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/types.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +6 -1
- package/node_modules/@earendil-works/pi-pty/native/prebuilds/darwin-arm64/senpi_pty.darwin-arm64.node +0 -0
- package/node_modules/@earendil-works/pi-pty/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/utils.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/utils.js +22 -4
- package/node_modules/@earendil-works/pi-tui/dist/utils.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +6 -6
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/anthropic.d.ts +0 -27
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/anthropic.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/anthropic.js.map +0 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.js.map +0 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.d.ts +0 -32
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.js.map +0 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/index.d.ts +0 -58
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/index.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/index.js +0 -122
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/index.js.map +0 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/load.d.ts +0 -5
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/load.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/load.js +0 -22
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/load.js.map +0 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/oauth-page.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/oauth-page.js.map +0 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/openai-codex.d.ts +0 -45
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/openai-codex.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/openai-codex.js.map +0 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/pkce.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/pkce.js.map +0 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/types.d.ts +0 -64
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/types.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/types.js +0 -2
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/types.js.map +0 -1
- /package/node_modules/@earendil-works/pi-ai/dist/{utils → auth}/oauth/device-code.d.ts +0 -0
- /package/node_modules/@earendil-works/pi-ai/dist/{utils → auth}/oauth/device-code.js +0 -0
- /package/node_modules/@earendil-works/pi-ai/dist/{utils → auth}/oauth/oauth-page.d.ts +0 -0
- /package/node_modules/@earendil-works/pi-ai/dist/{utils → auth}/oauth/oauth-page.js +0 -0
- /package/node_modules/@earendil-works/pi-ai/dist/{utils → auth}/oauth/pkce.d.ts +0 -0
- /package/node_modules/@earendil-works/pi-ai/dist/{utils → auth}/oauth/pkce.js +0 -0
package/docs/extensions.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
>
|
|
1
|
+
> pi can create extensions. Ask it to build one for your use case.
|
|
2
2
|
|
|
3
3
|
# Extensions
|
|
4
4
|
|
|
5
|
-
Extensions are TypeScript modules that extend
|
|
5
|
+
Extensions are TypeScript modules that extend pi's behavior. They can subscribe to lifecycle events, register custom tools callable by the LLM, add commands, and more.
|
|
6
6
|
|
|
7
|
-
> **Placement for /reload:** Put extensions in `~/.
|
|
7
|
+
> **Placement for /reload:** Put extensions in `~/.pi/agent/extensions/` (global) or `.pi/extensions/` (project-local) for auto-discovery. Use `pi -e ./path.ts` only for quick tests. Extensions in auto-discovered locations can be hot-reloaded with `/reload`.
|
|
8
8
|
|
|
9
9
|
**Key capabilities:**
|
|
10
10
|
- **Custom tools** - Register tools the LLM can call via `pi.registerTool()`
|
|
@@ -38,11 +38,6 @@ See [examples/extensions/](../examples/extensions/) for working implementations.
|
|
|
38
38
|
- [Events](#events)
|
|
39
39
|
- [Lifecycle Overview](#lifecycle-overview)
|
|
40
40
|
- [Resource Events](#resource-events)
|
|
41
|
-
- [Builtin Hooks](#builtin-hooks)
|
|
42
|
-
- [Hook setup workflow](#hook-setup-workflow)
|
|
43
|
-
- [Hook input and output](#hook-input-and-output)
|
|
44
|
-
- [Common hook recipes](#common-hook-recipes)
|
|
45
|
-
- [Hook troubleshooting](#hook-troubleshooting)
|
|
46
41
|
- [Session Events](#session-events)
|
|
47
42
|
- [Agent Events](#agent-events)
|
|
48
43
|
- [Model Events](#model-events)
|
|
@@ -60,10 +55,10 @@ See [examples/extensions/](../examples/extensions/) for working implementations.
|
|
|
60
55
|
|
|
61
56
|
## Quick Start
|
|
62
57
|
|
|
63
|
-
Create `~/.
|
|
58
|
+
Create `~/.pi/agent/extensions/my-extension.ts`:
|
|
64
59
|
|
|
65
60
|
```typescript
|
|
66
|
-
import type { ExtensionAPI } from "@
|
|
61
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
67
62
|
import { Type } from "typebox";
|
|
68
63
|
|
|
69
64
|
export default function (pi: ExtensionAPI) {
|
|
@@ -108,21 +103,21 @@ export default function (pi: ExtensionAPI) {
|
|
|
108
103
|
Test with `--extension` (or `-e`) flag:
|
|
109
104
|
|
|
110
105
|
```bash
|
|
111
|
-
|
|
106
|
+
pi -e ./my-extension.ts
|
|
112
107
|
```
|
|
113
108
|
|
|
114
109
|
## Extension Locations
|
|
115
110
|
|
|
116
111
|
> **Security:** Extensions run with your full system permissions and can execute arbitrary code. Only install from sources you trust.
|
|
117
112
|
|
|
118
|
-
Extensions are auto-discovered from trusted locations. Project-local `.
|
|
113
|
+
Extensions are auto-discovered from trusted locations. Project-local `.pi/extensions` entries load only after the project is trusted.
|
|
119
114
|
|
|
120
115
|
| Location | Scope |
|
|
121
116
|
|----------|-------|
|
|
122
|
-
| `~/.
|
|
123
|
-
| `~/.
|
|
124
|
-
| `.
|
|
125
|
-
| `.
|
|
117
|
+
| `~/.pi/agent/extensions/*.ts` | Global (all projects) |
|
|
118
|
+
| `~/.pi/agent/extensions/*/index.ts` | Global (subdirectory) |
|
|
119
|
+
| `.pi/extensions/*.ts` | Project-local |
|
|
120
|
+
| `.pi/extensions/*/index.ts` | Project-local (subdirectory) |
|
|
126
121
|
|
|
127
122
|
Additional paths via `settings.json`:
|
|
128
123
|
|
|
@@ -139,20 +134,20 @@ Additional paths via `settings.json`:
|
|
|
139
134
|
}
|
|
140
135
|
```
|
|
141
136
|
|
|
142
|
-
To share extensions via npm or git as
|
|
137
|
+
To share extensions via npm or git as pi packages, see [packages.md](packages.md).
|
|
143
138
|
|
|
144
139
|
## Available Imports
|
|
145
140
|
|
|
146
141
|
| Package | Purpose |
|
|
147
142
|
|---------|---------|
|
|
148
|
-
| `@
|
|
143
|
+
| `@earendil-works/pi-coding-agent` | Extension types (`ExtensionAPI`, `ExtensionContext`, events) |
|
|
149
144
|
| `typebox` | Schema definitions for tool parameters |
|
|
150
145
|
| `@earendil-works/pi-ai` | AI utilities (`StringEnum` for Google-compatible enums) |
|
|
151
146
|
| `@earendil-works/pi-tui` | TUI components for custom rendering |
|
|
152
147
|
|
|
153
148
|
npm dependencies work too. Add a `package.json` next to your extension (or in a parent directory), run `npm install`, and imports from `node_modules/` are resolved automatically.
|
|
154
149
|
|
|
155
|
-
For distributed
|
|
150
|
+
For distributed pi packages installed with `pi install` (npm or git), runtime deps must be in `dependencies`. Package installation uses production installs (`npm install --omit=dev`) by default, so `devDependencies` are not available at runtime; when `npmCommand` is configured, git packages use plain `install` for compatibility with wrappers.
|
|
156
151
|
|
|
157
152
|
Node.js built-ins (`node:fs`, `node:path`, etc.) are also available.
|
|
158
153
|
|
|
@@ -161,7 +156,7 @@ Node.js built-ins (`node:fs`, `node:path`, etc.) are also available.
|
|
|
161
156
|
An extension exports a default factory function that receives `ExtensionAPI`. The factory can be synchronous or asynchronous:
|
|
162
157
|
|
|
163
158
|
```typescript
|
|
164
|
-
import type { ExtensionAPI } from "@
|
|
159
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
165
160
|
|
|
166
161
|
export default function (pi: ExtensionAPI) {
|
|
167
162
|
// Subscribe to events
|
|
@@ -183,14 +178,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
183
178
|
|
|
184
179
|
Extensions are loaded via [jiti](https://github.com/unjs/jiti), so TypeScript works without compilation.
|
|
185
180
|
|
|
186
|
-
If the factory returns a `Promise`,
|
|
181
|
+
If the factory returns a `Promise`, pi awaits it before continuing startup. That means async initialization completes before `session_start`, before `resources_discover`, and before provider registrations queued via `pi.registerProvider()` are flushed.
|
|
187
182
|
|
|
188
183
|
### Async factory functions
|
|
189
184
|
|
|
190
185
|
Use an async factory for one-time startup work such as fetching remote configuration or dynamically discovering available models.
|
|
191
186
|
|
|
192
187
|
```typescript
|
|
193
|
-
import type { ExtensionAPI } from "@
|
|
188
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
194
189
|
|
|
195
190
|
export default async function (pi: ExtensionAPI) {
|
|
196
191
|
const response = await fetch("http://localhost:1234/v1/models");
|
|
@@ -220,7 +215,7 @@ export default async function (pi: ExtensionAPI) {
|
|
|
220
215
|
}
|
|
221
216
|
```
|
|
222
217
|
|
|
223
|
-
This pattern makes the fetched models available during normal startup and to `
|
|
218
|
+
This pattern makes the fetched models available during normal startup and to `pi --list-models`.
|
|
224
219
|
|
|
225
220
|
### Long-lived resources and shutdown
|
|
226
221
|
|
|
@@ -233,14 +228,14 @@ Defer background resource startup until `session_start` or the command/tool/even
|
|
|
233
228
|
**Single file** - simplest, for small extensions:
|
|
234
229
|
|
|
235
230
|
```
|
|
236
|
-
~/.
|
|
231
|
+
~/.pi/agent/extensions/
|
|
237
232
|
└── my-extension.ts
|
|
238
233
|
```
|
|
239
234
|
|
|
240
235
|
**Directory with index.ts** - for multi-file extensions:
|
|
241
236
|
|
|
242
237
|
```
|
|
243
|
-
~/.
|
|
238
|
+
~/.pi/agent/extensions/
|
|
244
239
|
└── my-extension/
|
|
245
240
|
├── index.ts # Entry point (exports default function)
|
|
246
241
|
├── tools.ts # Helper module
|
|
@@ -250,7 +245,7 @@ Defer background resource startup until `session_start` or the command/tool/even
|
|
|
250
245
|
**Package with dependencies** - for extensions that need npm packages:
|
|
251
246
|
|
|
252
247
|
```
|
|
253
|
-
~/.
|
|
248
|
+
~/.pi/agent/extensions/
|
|
254
249
|
└── my-extension/
|
|
255
250
|
├── package.json # Declares dependencies and entry points
|
|
256
251
|
├── package-lock.json
|
|
@@ -280,7 +275,7 @@ Run `npm install` in the extension directory, then imports from `node_modules/`
|
|
|
280
275
|
### Lifecycle Overview
|
|
281
276
|
|
|
282
277
|
```
|
|
283
|
-
|
|
278
|
+
pi starts
|
|
284
279
|
│
|
|
285
280
|
├─► project_trust (user/global and CLI extensions only, before project resources load)
|
|
286
281
|
├─► session_start { reason: "startup" }
|
|
@@ -356,7 +351,7 @@ exit (Ctrl+C, Ctrl+D, SIGHUP, SIGTERM)
|
|
|
356
351
|
|
|
357
352
|
#### project_trust
|
|
358
353
|
|
|
359
|
-
Fired before
|
|
354
|
+
Fired before pi decides whether to trust a project with dynamic configs (`.pi` or `.agents/skills`). It runs during startup and when session replacement (for example `/resume`) enters a cwd whose trust has not been resolved in the current process. Only user/global extensions and CLI `-e` extensions participate; project-local extensions are not loaded until after trust is resolved.
|
|
360
355
|
|
|
361
356
|
```typescript
|
|
362
357
|
pi.on("project_trust", async (event, ctx) => {
|
|
@@ -369,7 +364,7 @@ pi.on("project_trust", async (event, ctx) => {
|
|
|
369
364
|
});
|
|
370
365
|
```
|
|
371
366
|
|
|
372
|
-
A `project_trust` handler must return `{ trusted: "yes" | "no" | "undecided" }`. A user/global or CLI extension that returns `"yes"` or `"no"` owns the decision; the first yes/no decision wins and suppresses the built-in trust prompt. Use `remember: true` to persist a yes/no decision; otherwise it applies only to the current process. Return `"undecided"` to let later handlers or the built-in trust flow decide. Check `ctx.hasUI` before prompting. If no handler returns yes/no, normal trust resolution continues: saved `trust.json` decisions apply first, then `defaultProjectTrust` controls whether
|
|
367
|
+
A `project_trust` handler must return `{ trusted: "yes" | "no" | "undecided" }`. A user/global or CLI extension that returns `"yes"` or `"no"` owns the decision; the first yes/no decision wins and suppresses the built-in trust prompt. Use `remember: true` to persist a yes/no decision; otherwise it applies only to the current process. Return `"undecided"` to let later handlers or the built-in trust flow decide. Check `ctx.hasUI` before prompting. If no handler returns yes/no, normal trust resolution continues: saved `trust.json` decisions apply first, then `defaultProjectTrust` controls whether pi asks, trusts, or declines by default.
|
|
373
368
|
|
|
374
369
|
### Resource Events
|
|
375
370
|
|
|
@@ -390,451 +385,6 @@ pi.on("resources_discover", async (event, _ctx) => {
|
|
|
390
385
|
});
|
|
391
386
|
```
|
|
392
387
|
|
|
393
|
-
### Builtin Hooks
|
|
394
|
-
|
|
395
|
-
The builtin `hooks` extension runs trusted command hooks from JSON config files. It is intended for local guardrails and migration from Claude-style command hooks, while keeping the extension API as the primary senpi customization surface.
|
|
396
|
-
|
|
397
|
-
Default senpi hook files are discovered from:
|
|
398
|
-
|
|
399
|
-
| Source | Timing | Notes |
|
|
400
|
-
|--------|--------|-------|
|
|
401
|
-
| `~/.senpi/agent/hooks.json` | pre-session | Global hook config. |
|
|
402
|
-
| Global package `pi.hooks` or `hooks/*.json` | pre-session | Package hook config loaded after the global hook file. |
|
|
403
|
-
| `.senpi/hooks.json` | pre-session | Project hook config, loaded after project trust. |
|
|
404
|
-
| Project package `pi.hooks` or `hooks/*.json` | pre-session | Package hook config loaded after the project hook file. |
|
|
405
|
-
| Temporary package sources and SDK/host-provided `new DefaultResourceLoader({ additionalHookPaths })` | pre-session | Package/plugin hook JSON files resolved before `session_start`. |
|
|
406
|
-
| `resources_discover` `hookPaths` | runtime | Late sources. They can affect later hook events in the current runtime and `SessionStart` on `/reload` or the next session. |
|
|
407
|
-
|
|
408
|
-
#### Hook setup workflow
|
|
409
|
-
|
|
410
|
-
Use builtin hooks when you already have Claude/Codex-style JSON hook configs or when you want a small shell-script guardrail. Use a TypeScript extension instead when the behavior needs rich senpi APIs, UI prompts, custom tools, long-lived state, or non-command handlers.
|
|
411
|
-
|
|
412
|
-
For a project-local hook:
|
|
413
|
-
|
|
414
|
-
1. Create `.senpi/hooks.json`.
|
|
415
|
-
2. Put command scripts under `.senpi/hooks/` or another project path.
|
|
416
|
-
3. Start `senpi` in that project.
|
|
417
|
-
4. Run `/hooks list` and copy the real hook id.
|
|
418
|
-
5. Run `/hooks trust <id>` once for each command hook you want to execute.
|
|
419
|
-
6. Trigger the event, then use `/hooks diagnostics` if it did not run.
|
|
420
|
-
|
|
421
|
-
For a global hook, use `~/.senpi/agent/hooks.json` and a stable global script path. For team-shared hooks, prefer a senpi package or plugin manifest so the scripts and hook JSON move together.
|
|
422
|
-
|
|
423
|
-
Trust is tied to the command hook's canonical hash. Changing the command text, `commandWindows`, timeout, matcher, or status message makes the hook untrusted again until you review and trust the new hash. Disabling a hook with `/hooks disable <id>` keeps the trust entry but skips execution; `/hooks enable <id>` re-enables it.
|
|
424
|
-
|
|
425
|
-
Recommended first check after adding a hook:
|
|
426
|
-
|
|
427
|
-
```text
|
|
428
|
-
/hooks list
|
|
429
|
-
/hooks diagnostics
|
|
430
|
-
```
|
|
431
|
-
|
|
432
|
-
`/hooks list` should show the source, matcher, trust state, disabled state, and redacted command preview. `/hooks diagnostics` should be empty or contain only diagnostics you intentionally accept.
|
|
433
|
-
|
|
434
|
-
#### Minimal project hook
|
|
435
|
-
|
|
436
|
-
Create `.senpi/hooks.json`:
|
|
437
|
-
|
|
438
|
-
```json
|
|
439
|
-
{
|
|
440
|
-
"hooks": {
|
|
441
|
-
"PreToolUse": [
|
|
442
|
-
{
|
|
443
|
-
"matcher": "bash",
|
|
444
|
-
"hooks": [
|
|
445
|
-
{
|
|
446
|
-
"type": "command",
|
|
447
|
-
"command": "node .senpi/hooks/log-pretool.mjs",
|
|
448
|
-
"timeout": 10,
|
|
449
|
-
"statusMessage": "Checking bash command"
|
|
450
|
-
}
|
|
451
|
-
]
|
|
452
|
-
}
|
|
453
|
-
]
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
```
|
|
457
|
-
|
|
458
|
-
Create `.senpi/hooks/log-pretool.mjs`:
|
|
459
|
-
|
|
460
|
-
```javascript
|
|
461
|
-
let stdin = "";
|
|
462
|
-
process.stdin.setEncoding("utf8");
|
|
463
|
-
process.stdin.on("data", (chunk) => {
|
|
464
|
-
stdin += chunk;
|
|
465
|
-
});
|
|
466
|
-
process.stdin.on("end", () => {
|
|
467
|
-
const input = JSON.parse(stdin);
|
|
468
|
-
const toolInput = input.toolInput ?? input.tool_input ?? {};
|
|
469
|
-
const command = String(toolInput.command ?? "");
|
|
470
|
-
if (command.includes("rm -rf")) {
|
|
471
|
-
process.stdout.write(JSON.stringify({
|
|
472
|
-
hookSpecificOutput: {
|
|
473
|
-
hookEventName: "PreToolUse",
|
|
474
|
-
permissionDecision: "deny",
|
|
475
|
-
permissionDecisionReason: "Refusing destructive shell command"
|
|
476
|
-
}
|
|
477
|
-
}));
|
|
478
|
-
return;
|
|
479
|
-
}
|
|
480
|
-
process.stdout.write(JSON.stringify({
|
|
481
|
-
hookSpecificOutput: {
|
|
482
|
-
hookEventName: "PreToolUse",
|
|
483
|
-
permissionDecision: "allow"
|
|
484
|
-
}
|
|
485
|
-
}));
|
|
486
|
-
});
|
|
487
|
-
```
|
|
488
|
-
|
|
489
|
-
Start senpi in the project and inspect hooks:
|
|
490
|
-
|
|
491
|
-
```bash
|
|
492
|
-
senpi
|
|
493
|
-
/hooks list
|
|
494
|
-
/hooks trust hk_example_PreToolUse_0_0
|
|
495
|
-
```
|
|
496
|
-
|
|
497
|
-
The real hook id is shown by `/hooks list`. A command hook must be trusted before it runs. Project hook trust is writable only when the project is trusted.
|
|
498
|
-
|
|
499
|
-
Exact command grammar:
|
|
500
|
-
|
|
501
|
-
```text
|
|
502
|
-
Usage: /hooks [list|diagnostics|trust <id>|disable <id>|enable <id>|reload]
|
|
503
|
-
```
|
|
504
|
-
|
|
505
|
-
`/hooks list` shows executable hooks, ids, trust state, disabled state, matcher, status message, and a redacted command preview. `/hooks diagnostics` shows malformed, unsupported, untrusted, and skipped hook details. `/hooks reload` runs the normal extension/resource reload flow.
|
|
506
|
-
|
|
507
|
-
#### Command handler JSON
|
|
508
|
-
|
|
509
|
-
Only command handlers are executable in builtin hooks v1:
|
|
510
|
-
|
|
511
|
-
```json
|
|
512
|
-
{
|
|
513
|
-
"type": "command",
|
|
514
|
-
"command": "node ./hooks/check.mjs",
|
|
515
|
-
"commandWindows": "node .\\hooks\\check.mjs",
|
|
516
|
-
"timeout": 30,
|
|
517
|
-
"statusMessage": "Running hook"
|
|
518
|
-
}
|
|
519
|
-
```
|
|
520
|
-
|
|
521
|
-
`command` is required and runs through the host shell with the hook input JSON on stdin. `commandWindows` or `command_windows` can override the command on Windows. `timeout` is seconds and defaults to 600. `statusMessage` is display text: while the hook command runs during `PreToolUse`/`PostToolUse`, the TUI shows it in the live `Running PreToolUse/PostToolUse hook: ...` status row (hooks without one show their sanitized command text instead).
|
|
522
|
-
|
|
523
|
-
Hook commands inherit a minimal environment (`PATH`, home/user/shell/temp variables, and Windows shell basics). Hooks loaded through the plugin manifest helper also receive `PLUGIN_ROOT`, `PLUGIN_DATA`, `CLAUDE_PLUGIN_ROOT`, and `CLAUDE_PLUGIN_DATA`. Every command receives `SENPI_HOOK_SOURCE` and `SENPI_HOOK_EVENT`. Do not put API keys or tokens in hook config, command text, status messages, stdout, stderr, or diagnostics. Read secrets inside the command from your own secret store and avoid echoing them.
|
|
524
|
-
|
|
525
|
-
#### Event map
|
|
526
|
-
|
|
527
|
-
| Event | senpi source | Supported behavior |
|
|
528
|
-
|-------|--------------|--------------------|
|
|
529
|
-
| `SessionStart` | `session_start` | Runs on startup/reload/new/resume/fork. `additionalContext` is recorded as hidden hook context. Decisions are diagnostics only. |
|
|
530
|
-
| `UserPromptSubmit` | `input` plus `before_agent_start` | Can block the prompt. `additionalContext` is injected as hidden context for the turn. `systemMessage` appends to the turn system prompt. |
|
|
531
|
-
| `PreToolUse` | `tool_call` | Can deny/block, ask (represented as block), allow/approve, mutate `updatedInput` only with `permissionDecision: "allow"`, and add context for the matching tool result. |
|
|
532
|
-
| `PostToolUse` | `tool_result` | Can block by replacing the tool result with an error, replace tool output with `updatedToolOutput`, and append `additionalContext`. |
|
|
533
|
-
| `PreCompact` | `session_before_compact` | Can block/cancel compaction with `decision: "block"` or exit code 2. `additionalContext` and `customInstructions` are diagnostic-only. |
|
|
534
|
-
| `PostCompact` | `session_compact` | Runs after accepted compactions. Output fields are currently diagnostic-only. |
|
|
535
|
-
| `Stop` | `agent_end` | Can block by sending a follow-up prompt from `additionalContext` or `reason`. Reentry is capped at eight blocks per turn. |
|
|
536
|
-
|
|
537
|
-
Matchers are strings. For tool events, matchers match tool names. For lifecycle events, `*`, event names, reasons such as `startup`, `reload`, `manual`, or `overflow`, comma/pipe-separated literals, and JavaScript regular expressions are supported.
|
|
538
|
-
|
|
539
|
-
Command stdout may be empty or JSON. Universal JSON fields include `decision`, `reason`, `additionalContext`, `continue`, `stopReason`, `suppressOutput`, `systemMessage`, and `hookSpecificOutput`. `hookSpecificOutput.hookEventName` must match the current event when present. Exit code `2` is treated as a block decision with stderr as the reason.
|
|
540
|
-
|
|
541
|
-
#### Hook input and output
|
|
542
|
-
|
|
543
|
-
Every command receives one JSON object on stdin. The object always includes `event` and `cwd`, and includes session/transcript fields when senpi has them:
|
|
544
|
-
|
|
545
|
-
| Field | Meaning |
|
|
546
|
-
|-------|---------|
|
|
547
|
-
| `event` | Hook event name, such as `PreToolUse` or `UserPromptSubmit`. |
|
|
548
|
-
| `cwd` | Current project working directory. |
|
|
549
|
-
| `session_id` | Session id when available. |
|
|
550
|
-
| `transcript_path` | Session transcript path when available. |
|
|
551
|
-
|
|
552
|
-
Tool events include both senpi-style camelCase and Claude-compatible snake_case fields:
|
|
553
|
-
|
|
554
|
-
```json
|
|
555
|
-
{
|
|
556
|
-
"event": "PreToolUse",
|
|
557
|
-
"toolName": "bash",
|
|
558
|
-
"toolInput": { "command": "npm test" },
|
|
559
|
-
"tool_name": "bash",
|
|
560
|
-
"tool_input": { "command": "npm test" },
|
|
561
|
-
"cwd": "/repo"
|
|
562
|
-
}
|
|
563
|
-
```
|
|
564
|
-
|
|
565
|
-
Prompt hooks receive the raw prompt:
|
|
566
|
-
|
|
567
|
-
```json
|
|
568
|
-
{
|
|
569
|
-
"event": "UserPromptSubmit",
|
|
570
|
-
"prompt": "summarize this repo",
|
|
571
|
-
"cwd": "/repo"
|
|
572
|
-
}
|
|
573
|
-
```
|
|
574
|
-
|
|
575
|
-
Output must be valid JSON if you want senpi to apply a decision. Empty stdout means no decision. Prefer event-specific output inside `hookSpecificOutput`:
|
|
576
|
-
|
|
577
|
-
```json
|
|
578
|
-
{
|
|
579
|
-
"hookSpecificOutput": {
|
|
580
|
-
"hookEventName": "PreToolUse",
|
|
581
|
-
"permissionDecision": "deny",
|
|
582
|
-
"permissionDecisionReason": "Do not run destructive commands"
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
```
|
|
586
|
-
|
|
587
|
-
`PreToolUse` supports `permissionDecision: "allow" | "deny" | "ask"`. `updatedInput` is applied only when `permissionDecision` is `"allow"`. `additionalContext` is saved as hidden context for the active tool result.
|
|
588
|
-
|
|
589
|
-
```json
|
|
590
|
-
{
|
|
591
|
-
"hookSpecificOutput": {
|
|
592
|
-
"hookEventName": "PreToolUse",
|
|
593
|
-
"permissionDecision": "allow",
|
|
594
|
-
"updatedInput": { "command": "npm test -- --runInBand" },
|
|
595
|
-
"additionalContext": "The hook serialized this test command."
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
```
|
|
599
|
-
|
|
600
|
-
`UserPromptSubmit` can block or add context, but it cannot replace the prompt through Claude/Codex-compatible fields:
|
|
601
|
-
|
|
602
|
-
```json
|
|
603
|
-
{
|
|
604
|
-
"decision": "block",
|
|
605
|
-
"reason": "Please include an issue number before starting release work."
|
|
606
|
-
}
|
|
607
|
-
```
|
|
608
|
-
|
|
609
|
-
`PostToolUse` can replace visible tool output with `updatedToolOutput` and can add `additionalContext`:
|
|
610
|
-
|
|
611
|
-
```json
|
|
612
|
-
{
|
|
613
|
-
"hookSpecificOutput": {
|
|
614
|
-
"hookEventName": "PostToolUse",
|
|
615
|
-
"updatedToolOutput": "Output redacted by local hook.",
|
|
616
|
-
"additionalContext": "The raw tool output was replaced because it matched the secret scanner."
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
```
|
|
620
|
-
|
|
621
|
-
`Stop` can ask senpi to continue with a follow-up prompt by returning `decision: "block"` plus `additionalContext` or `reason`. senpi caps repeated Stop follow-ups at eight per turn to prevent loops.
|
|
622
|
-
|
|
623
|
-
#### Common hook recipes
|
|
624
|
-
|
|
625
|
-
Block dangerous shell commands:
|
|
626
|
-
|
|
627
|
-
```javascript
|
|
628
|
-
let stdin = "";
|
|
629
|
-
process.stdin.setEncoding("utf8");
|
|
630
|
-
process.stdin.on("data", (chunk) => {
|
|
631
|
-
stdin += chunk;
|
|
632
|
-
});
|
|
633
|
-
process.stdin.on("end", () => {
|
|
634
|
-
const input = JSON.parse(stdin);
|
|
635
|
-
const toolInput = input.toolInput ?? input.tool_input ?? {};
|
|
636
|
-
const command = String(toolInput.command ?? "");
|
|
637
|
-
|
|
638
|
-
if (/\brm\s+-rf\b/.test(command)) {
|
|
639
|
-
process.stdout.write(JSON.stringify({
|
|
640
|
-
hookSpecificOutput: {
|
|
641
|
-
hookEventName: "PreToolUse",
|
|
642
|
-
permissionDecision: "deny",
|
|
643
|
-
permissionDecisionReason: "Blocked rm -rf"
|
|
644
|
-
}
|
|
645
|
-
}));
|
|
646
|
-
}
|
|
647
|
-
});
|
|
648
|
-
```
|
|
649
|
-
|
|
650
|
-
Add per-prompt project context:
|
|
651
|
-
|
|
652
|
-
```json
|
|
653
|
-
{
|
|
654
|
-
"hooks": {
|
|
655
|
-
"UserPromptSubmit": [
|
|
656
|
-
{
|
|
657
|
-
"hooks": [
|
|
658
|
-
{
|
|
659
|
-
"type": "command",
|
|
660
|
-
"command": "node .senpi/hooks/prompt-context.mjs",
|
|
661
|
-
"statusMessage": "Loading prompt context"
|
|
662
|
-
}
|
|
663
|
-
]
|
|
664
|
-
}
|
|
665
|
-
]
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
```
|
|
669
|
-
|
|
670
|
-
```javascript
|
|
671
|
-
process.stdin.resume();
|
|
672
|
-
process.stdin.on("end", () => {
|
|
673
|
-
process.stdout.write(JSON.stringify({
|
|
674
|
-
hookSpecificOutput: {
|
|
675
|
-
hookEventName: "UserPromptSubmit",
|
|
676
|
-
additionalContext: "Prefer the repository's AGENTS.md rules over generic defaults."
|
|
677
|
-
}
|
|
678
|
-
}));
|
|
679
|
-
});
|
|
680
|
-
```
|
|
681
|
-
|
|
682
|
-
Stop when a final answer needs a required follow-up:
|
|
683
|
-
|
|
684
|
-
```javascript
|
|
685
|
-
process.stdin.resume();
|
|
686
|
-
process.stdin.on("end", () => {
|
|
687
|
-
process.stdout.write(JSON.stringify({
|
|
688
|
-
decision: "block",
|
|
689
|
-
hookSpecificOutput: {
|
|
690
|
-
hookEventName: "Stop",
|
|
691
|
-
additionalContext: "Before finishing, run the focused regression test and summarize the result."
|
|
692
|
-
}
|
|
693
|
-
}));
|
|
694
|
-
});
|
|
695
|
-
```
|
|
696
|
-
|
|
697
|
-
Distribute hooks from a package:
|
|
698
|
-
|
|
699
|
-
```json
|
|
700
|
-
{
|
|
701
|
-
"name": "team-senpi-hooks",
|
|
702
|
-
"pi": {
|
|
703
|
-
"hooks": ["./hooks/pretool.json"]
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
```
|
|
707
|
-
|
|
708
|
-
```json
|
|
709
|
-
{
|
|
710
|
-
"hooks": {
|
|
711
|
-
"PreToolUse": [
|
|
712
|
-
{
|
|
713
|
-
"matcher": "bash|edit|write",
|
|
714
|
-
"hooks": [
|
|
715
|
-
{
|
|
716
|
-
"type": "command",
|
|
717
|
-
"command": "node ./hooks/check.mjs",
|
|
718
|
-
"statusMessage": "Running team hook"
|
|
719
|
-
}
|
|
720
|
-
]
|
|
721
|
-
}
|
|
722
|
-
]
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
```
|
|
726
|
-
|
|
727
|
-
Package hook paths are resolved relative to the package root. If you need plugin-root environment variables, use a `.codex-plugin/plugin.json` manifest and `${PLUGIN_ROOT}` in the command.
|
|
728
|
-
|
|
729
|
-
#### Plugin hooks
|
|
730
|
-
|
|
731
|
-
Plugin hook manifests are package integration inputs. Installed senpi packages can expose hook JSON files through `package.json` under `pi.hooks` or by placing JSON files under `hooks/`; those sources are discovered before `session_start` in global, project, or temporary package scope.
|
|
732
|
-
|
|
733
|
-
Plugin hook manifests live at `.codex-plugin/plugin.json` under the plugin root. A manifest can point to hook JSON files:
|
|
734
|
-
|
|
735
|
-
```json
|
|
736
|
-
{
|
|
737
|
-
"name": "example-hooks-plugin",
|
|
738
|
-
"hooks": "./hooks/pretool.json"
|
|
739
|
-
}
|
|
740
|
-
```
|
|
741
|
-
|
|
742
|
-
It can also inline hook config directly:
|
|
743
|
-
|
|
744
|
-
```json
|
|
745
|
-
{
|
|
746
|
-
"name": "inline-hooks-plugin",
|
|
747
|
-
"hooks": {
|
|
748
|
-
"hooks": {
|
|
749
|
-
"SessionStart": [
|
|
750
|
-
{
|
|
751
|
-
"matcher": "startup|reload",
|
|
752
|
-
"hooks": [
|
|
753
|
-
{
|
|
754
|
-
"type": "command",
|
|
755
|
-
"command": "node ${PLUGIN_ROOT}/hooks/session-start.mjs"
|
|
756
|
-
}
|
|
757
|
-
]
|
|
758
|
-
}
|
|
759
|
-
]
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
```
|
|
764
|
-
|
|
765
|
-
`hooks` may also be an array mixing paths, nested arrays, and inline hook objects. Hook paths must stay inside the plugin root. The manifest helper also understands a plugin default hook file at `hooks/hooks.json` unless the host disables default plugin hooks. Plugin commands that reference `${PLUGIN_ROOT}`, `$PLUGIN_ROOT`, or `%PLUGIN_ROOT%` must resolve to existing files inside the plugin root.
|
|
766
|
-
|
|
767
|
-
SDK hosts can still provide explicit pre-session hook sources:
|
|
768
|
-
|
|
769
|
-
```typescript
|
|
770
|
-
const loader = new DefaultResourceLoader({
|
|
771
|
-
cwd,
|
|
772
|
-
agentDir,
|
|
773
|
-
additionalHookPaths: ["/absolute/path/to/package-hooks.json"],
|
|
774
|
-
});
|
|
775
|
-
```
|
|
776
|
-
|
|
777
|
-
Pre-session sources are visible to `SessionStart` on initial startup.
|
|
778
|
-
|
|
779
|
-
#### Runtime hook sources
|
|
780
|
-
|
|
781
|
-
Extensions can return late hook paths from `resources_discover`:
|
|
782
|
-
|
|
783
|
-
```typescript
|
|
784
|
-
import { join } from "node:path";
|
|
785
|
-
import type { ExtensionAPI } from "@code-yeongyu/senpi";
|
|
786
|
-
|
|
787
|
-
export default function (pi: ExtensionAPI) {
|
|
788
|
-
pi.on("resources_discover", (event, ctx) => {
|
|
789
|
-
if (event.reason !== "reload") return {};
|
|
790
|
-
return {
|
|
791
|
-
hookPaths: [join(ctx.cwd, ".senpi", "generated-hooks.json")],
|
|
792
|
-
};
|
|
793
|
-
});
|
|
794
|
-
}
|
|
795
|
-
```
|
|
796
|
-
|
|
797
|
-
These paths are runtime sources. They can affect later hook events in the current runtime, but their `SessionStart` hooks do not run for the already-started initial session. On `/hooks reload` or `/reload`, runtime `SessionStart` hooks are visible to the reloaded session. If a runtime source contains `SessionStart`, senpi records a diagnostic: `Runtime SessionStart hooks are loaded for reload or the next session only.`
|
|
798
|
-
|
|
799
|
-
#### Not yet supported
|
|
800
|
-
|
|
801
|
-
The builtin hooks implementation intentionally does not claim full Claude hook coverage. Unsupported shapes produce warnings or diagnostics and are not executed.
|
|
802
|
-
|
|
803
|
-
| Area | Not yet supported |
|
|
804
|
-
|------|-------------------|
|
|
805
|
-
| Events | `PermissionRequest`, `PermissionDenied`, `SubagentStart`, `SubagentStop`, `Notification`, `Setup`, `UserPromptExpansion`, `PostToolUseFailure`, `PostToolBatch`, `TaskCreated`, `TaskCompleted`, `StopFailure`, `TeammateIdle`, `InstructionsLoaded`, `ConfigChange`, `CwdChanged`, `FileChanged`, `WorktreeCreate`, `WorktreeRemove`, `MessageDisplay`, `SessionEnd`, `Elicitation`, `ElicitationResult`. |
|
|
806
|
-
| Handler types | `prompt`, `agent`, `http`, and `mcp_tool`. |
|
|
807
|
-
| Command forms | Exec-form commands, object-valued `command`, separate `args`, `shell`, and command handlers with `async: true`. |
|
|
808
|
-
| Command fields | `if`, `asyncRewake`, `terminalSequence`, and `continueOnBlock`. |
|
|
809
|
-
| Prompt mutation | UserPromptSubmit prompt replacement fields such as `prompt`, `updatedPrompt`, and `replacementPrompt`. |
|
|
810
|
-
| Async behavior | Claude-style async hooks and `asyncRewake`. |
|
|
811
|
-
| PreCompact mutation | `customInstructions` does not change compaction in builtin hooks v1. |
|
|
812
|
-
|
|
813
|
-
#### omo-codex migration notes
|
|
814
|
-
|
|
815
|
-
For omo-codex or Claude-style hook configs, migrate the lowest-risk checks first:
|
|
816
|
-
|
|
817
|
-
- Keep existing `PreToolUse`, `PostToolUse`, `UserPromptSubmit`, `SessionStart`, `PreCompact`, `PostCompact`, and `Stop` command hooks if they use shell command strings.
|
|
818
|
-
- Move `http`, `mcp_tool`, `prompt`, and `agent` hooks to senpi extensions or tools.
|
|
819
|
-
- Replace `if` conditions with logic inside the command script or with matcher strings for tool/lifecycle selection.
|
|
820
|
-
- Treat `Notification`, subagent/task/teammate/worktree events, and prompt replacement as deferred; document them in plugin README files instead of assuming execution.
|
|
821
|
-
- When the host uses the plugin manifest helper, use `PLUGIN_ROOT` and `PLUGIN_DATA` for plugin-relative scripts and state. Keep secrets out of manifests and hook JSON.
|
|
822
|
-
|
|
823
|
-
#### Hook troubleshooting
|
|
824
|
-
|
|
825
|
-
If a hook does not run:
|
|
826
|
-
|
|
827
|
-
1. Run `/hooks list` and confirm the hook is present, enabled, and trusted.
|
|
828
|
-
2. Run `/hooks diagnostics` and check for malformed JSON, unsupported fields, untrusted hashes, skipped runtime `SessionStart`, or plugin containment errors.
|
|
829
|
-
3. Confirm the matcher matches the event. Tool events match tool names such as `bash`, `edit`, `write`, `Bash`, `Edit`, and `Write`; lifecycle events match reasons such as `startup`, `reload`, `manual`, and `overflow`.
|
|
830
|
-
4. Confirm the command path works from the project cwd. For plugin commands using `${PLUGIN_ROOT}`, the resolved file must exist inside the plugin root.
|
|
831
|
-
5. Keep stdout as valid JSON or empty. Human-readable logs should go to stderr, and secret-bearing logs should be avoided entirely.
|
|
832
|
-
6. After editing hook JSON or scripts, run `/hooks reload`, then `/hooks list` again. If the trusted hash changed, trust the hook again.
|
|
833
|
-
|
|
834
|
-
If a hook runs but has no effect, check the event support table. Some fields are diagnostic-only by design, such as `PreCompact` `additionalContext` and `customInstructions`, `PostCompact` output fields, and unsupported prompt replacement fields for `UserPromptSubmit`.
|
|
835
|
-
|
|
836
|
-
If a hook times out, increase `timeout` in seconds or make the script observe stdin and exit quickly. The default timeout is 600 seconds. Invalid timeout values such as `0`, negative numbers, `NaN`, or infinity are rejected and the command is not started.
|
|
837
|
-
|
|
838
388
|
### Session Events
|
|
839
389
|
|
|
840
390
|
See [Session Format](session-format.md) for session storage internals and the SessionManager API.
|
|
@@ -878,7 +428,7 @@ pi.on("session_before_switch", async (event, ctx) => {
|
|
|
878
428
|
});
|
|
879
429
|
```
|
|
880
430
|
|
|
881
|
-
After a successful switch or new-session action,
|
|
431
|
+
After a successful switch or new-session action, pi emits `session_shutdown` for the old extension instance, reloads and rebinds extensions for the new session, then emits `session_start` with `reason: "new" | "resume"` and `previousSessionFile`.
|
|
882
432
|
Do cleanup work in `session_shutdown`, then reestablish any in-memory state in `session_start`.
|
|
883
433
|
|
|
884
434
|
#### session_before_fork
|
|
@@ -895,7 +445,7 @@ pi.on("session_before_fork", async (event, ctx) => {
|
|
|
895
445
|
});
|
|
896
446
|
```
|
|
897
447
|
|
|
898
|
-
After a successful fork or clone,
|
|
448
|
+
After a successful fork or clone, pi emits `session_shutdown` for the old extension instance, reloads and rebinds extensions for the new session, then emits `session_start` with `reason: "fork"` and `previousSessionFile`.
|
|
899
449
|
Do cleanup work in `session_shutdown`, then reestablish any in-memory state in `session_start`.
|
|
900
450
|
|
|
901
451
|
#### session_before_compact / session_compact
|
|
@@ -972,7 +522,7 @@ pi.on("before_agent_start", async (event, ctx) => {
|
|
|
972
522
|
// event.systemPrompt - current chained system prompt for this handler
|
|
973
523
|
// (includes changes from earlier before_agent_start handlers)
|
|
974
524
|
// event.systemPromptOptions - structured options used to build the system prompt
|
|
975
|
-
// .customPrompt - any custom system prompt (from --system-prompt or custom templates)
|
|
525
|
+
// .customPrompt - any custom system prompt (from --system-prompt, SYSTEM.md, or custom templates)
|
|
976
526
|
// .selectedTools - tools currently active in the prompt
|
|
977
527
|
// .toolSnippets - one-line descriptions for each tool
|
|
978
528
|
// .promptGuidelines - custom guideline bullets
|
|
@@ -994,7 +544,7 @@ pi.on("before_agent_start", async (event, ctx) => {
|
|
|
994
544
|
});
|
|
995
545
|
```
|
|
996
546
|
|
|
997
|
-
The `systemPromptOptions` field gives extensions access to the same structured data
|
|
547
|
+
The `systemPromptOptions` field gives extensions access to the same structured data Pi uses to build the system prompt. This lets you inspect what Pi has loaded — custom prompts, guidelines, tool snippets, context files, skills — without re-discovering resources or re-parsing flags. Use it when your extension needs to make deep, informed changes to the system prompt while respecting user-provided configuration.
|
|
998
548
|
|
|
999
549
|
Inside `before_agent_start`, `event.systemPrompt` and `ctx.getSystemPrompt()` both reflect the chained system prompt as of the current handler. Later `before_agent_start` handlers can still modify it again.
|
|
1000
550
|
|
|
@@ -1122,7 +672,7 @@ Runs once per provider request; retries reuse the same headers rather than re-fi
|
|
|
1122
672
|
|
|
1123
673
|
Fired after the provider-specific payload is built, right before the request is sent. Handlers run in extension load order. Returning `undefined` keeps the payload unchanged. Returning any other value replaces the payload for later handlers and for the actual request.
|
|
1124
674
|
|
|
1125
|
-
This hook can rewrite provider-level system instructions or remove them entirely. Those payload-level changes are not reflected by `ctx.getSystemPrompt()`, which reports
|
|
675
|
+
This hook can rewrite provider-level system instructions or remove them entirely. Those payload-level changes are not reflected by `ctx.getSystemPrompt()`, which reports Pi's system prompt string rather than the final serialized provider payload.
|
|
1126
676
|
|
|
1127
677
|
```typescript
|
|
1128
678
|
pi.on("before_provider_request", (event, ctx) => {
|
|
@@ -1195,7 +745,7 @@ Use this to update extension UI when `pi.setThinkingLevel()`, model changes, or
|
|
|
1195
745
|
|
|
1196
746
|
Fired after `tool_execution_start`, before the tool executes. **Can block.** Use `isToolCallEventType` to narrow and get typed inputs.
|
|
1197
747
|
|
|
1198
|
-
Before `tool_call` runs,
|
|
748
|
+
Before `tool_call` runs, pi waits for previously emitted Agent events to finish draining through `AgentSession`. This means `ctx.sessionManager` is up to date through the current assistant tool-calling message.
|
|
1199
749
|
|
|
1200
750
|
In the default parallel tool execution mode, sibling tool calls from the same assistant message are preflighted sequentially, then executed concurrently. `tool_call` is not guaranteed to see sibling tool results from that same assistant message in `ctx.sessionManager`.
|
|
1201
751
|
|
|
@@ -1208,7 +758,7 @@ Behavior guarantees:
|
|
|
1208
758
|
- Return values from `tool_call` only control blocking via `{ block: true, reason?: string }`
|
|
1209
759
|
|
|
1210
760
|
```typescript
|
|
1211
|
-
import { isToolCallEventType } from "@
|
|
761
|
+
import { isToolCallEventType } from "@earendil-works/pi-coding-agent";
|
|
1212
762
|
|
|
1213
763
|
pi.on("tool_call", async (event, ctx) => {
|
|
1214
764
|
// event.toolName - "bash", "read", "write", "edit", etc.
|
|
@@ -1244,7 +794,7 @@ export type MyToolInput = Static<typeof myToolSchema>;
|
|
|
1244
794
|
Use `isToolCallEventType` with explicit type parameters:
|
|
1245
795
|
|
|
1246
796
|
```typescript
|
|
1247
|
-
import { isToolCallEventType } from "@
|
|
797
|
+
import { isToolCallEventType } from "@earendil-works/pi-coding-agent";
|
|
1248
798
|
import type { MyToolInput } from "my-extension";
|
|
1249
799
|
|
|
1250
800
|
pi.on("tool_call", (event) => {
|
|
@@ -1268,7 +818,7 @@ In parallel tool mode, `tool_result` and `tool_execution_end` may interleave in
|
|
|
1268
818
|
Use `ctx.signal` for nested async work inside the handler. This lets Esc cancel model calls, `fetch()`, and other abort-aware operations started by the extension.
|
|
1269
819
|
|
|
1270
820
|
```typescript
|
|
1271
|
-
import { isBashToolResult } from "@
|
|
821
|
+
import { isBashToolResult } from "@earendil-works/pi-coding-agent";
|
|
1272
822
|
|
|
1273
823
|
pi.on("tool_result", async (event, ctx) => {
|
|
1274
824
|
// event.toolName, event.toolCallId, event.input
|
|
@@ -1296,7 +846,7 @@ pi.on("tool_result", async (event, ctx) => {
|
|
|
1296
846
|
Fired when user executes `!` or `!!` commands. **Can intercept.**
|
|
1297
847
|
|
|
1298
848
|
```typescript
|
|
1299
|
-
import { createLocalBashOperations } from "@
|
|
849
|
+
import { createLocalBashOperations } from "@earendil-works/pi-coding-agent";
|
|
1300
850
|
|
|
1301
851
|
pi.on("user_bash", (event, ctx) => {
|
|
1302
852
|
// event.command - the bash command
|
|
@@ -1306,7 +856,7 @@ pi.on("user_bash", (event, ctx) => {
|
|
|
1306
856
|
// Option 1: Provide custom operations (e.g., SSH)
|
|
1307
857
|
return { operations: remoteBashOps };
|
|
1308
858
|
|
|
1309
|
-
// Option 2: Wrap
|
|
859
|
+
// Option 2: Wrap pi's built-in local bash backend
|
|
1310
860
|
const local = createLocalBashOperations();
|
|
1311
861
|
return {
|
|
1312
862
|
operations: {
|
|
@@ -1439,7 +989,7 @@ Use this for abort-aware nested work started by extension handlers, for example:
|
|
|
1439
989
|
- file or process helpers that accept `AbortSignal`
|
|
1440
990
|
|
|
1441
991
|
`ctx.signal` is typically defined during active turn events such as `tool_call`, `tool_result`, `message_update`, and `turn_end`.
|
|
1442
|
-
It is usually `undefined` in idle or non-turn contexts such as session events, extension commands, and shortcuts fired while
|
|
992
|
+
It is usually `undefined` in idle or non-turn contexts such as session events, extension commands, and shortcuts fired while pi is idle.
|
|
1443
993
|
|
|
1444
994
|
```typescript
|
|
1445
995
|
pi.on("tool_result", async (event, ctx) => {
|
|
@@ -1460,7 +1010,7 @@ Control flow helpers. `ctx.isIdle()` is false while Pi is processing an agent ru
|
|
|
1460
1010
|
|
|
1461
1011
|
### ctx.shutdown()
|
|
1462
1012
|
|
|
1463
|
-
Request a graceful shutdown of
|
|
1013
|
+
Request a graceful shutdown of pi.
|
|
1464
1014
|
|
|
1465
1015
|
- **Interactive mode:** Deferred until the agent becomes idle (after processing all queued steering and follow-up messages).
|
|
1466
1016
|
- **RPC mode:** Deferred until the next idle state (after completing the current command response, when waiting for the next command).
|
|
@@ -1476,19 +1026,6 @@ pi.on("tool_call", (event, ctx) => {
|
|
|
1476
1026
|
});
|
|
1477
1027
|
```
|
|
1478
1028
|
|
|
1479
|
-
### ctx.updateToolHookStatus()
|
|
1480
|
-
|
|
1481
|
-
Only available on the context passed to `tool_call` and `tool_result` handlers. Replaces the label of that handler's live `Running PreToolUse/PostToolUse hook: ...` status row in the TUI, so long-running hooks can report what they are doing. Messages are truncated to 79 characters; calls after the handler finished are ignored.
|
|
1482
|
-
|
|
1483
|
-
```typescript
|
|
1484
|
-
pi.on("tool_result", async (event, ctx) => {
|
|
1485
|
-
ctx.updateToolHookStatus?.("Checking comments");
|
|
1486
|
-
await runCommentChecker(event);
|
|
1487
|
-
ctx.updateToolHookStatus?.("Collecting diagnostics");
|
|
1488
|
-
await collectDiagnostics(event);
|
|
1489
|
-
});
|
|
1490
|
-
```
|
|
1491
|
-
|
|
1492
1029
|
### ctx.getContextUsage()
|
|
1493
1030
|
|
|
1494
1031
|
Returns current context usage for the active model. Uses last assistant usage when available, then estimates tokens for trailing messages.
|
|
@@ -1518,7 +1055,7 @@ ctx.compact({
|
|
|
1518
1055
|
|
|
1519
1056
|
### ctx.getSystemPrompt()
|
|
1520
1057
|
|
|
1521
|
-
Returns
|
|
1058
|
+
Returns Pi's current system prompt string.
|
|
1522
1059
|
|
|
1523
1060
|
- During `before_agent_start`, this reflects chained system-prompt changes made so far for the current turn.
|
|
1524
1061
|
- It does not include later `context` message mutations.
|
|
@@ -1538,7 +1075,7 @@ Command handlers receive `ExtensionCommandContext`, which extends `ExtensionCont
|
|
|
1538
1075
|
|
|
1539
1076
|
### ctx.getSystemPromptOptions()
|
|
1540
1077
|
|
|
1541
|
-
Returns the base inputs
|
|
1078
|
+
Returns the base inputs Pi currently uses to build the system prompt.
|
|
1542
1079
|
|
|
1543
1080
|
```typescript
|
|
1544
1081
|
const options = ctx.getSystemPromptOptions();
|
|
@@ -1661,7 +1198,7 @@ Options:
|
|
|
1661
1198
|
To discover available sessions, use the static `SessionManager.list()` or `SessionManager.listAll()` methods:
|
|
1662
1199
|
|
|
1663
1200
|
```typescript
|
|
1664
|
-
import { SessionManager } from "@
|
|
1201
|
+
import { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
1665
1202
|
|
|
1666
1203
|
pi.registerCommand("switch", {
|
|
1667
1204
|
description: "Switch to another session",
|
|
@@ -1755,7 +1292,7 @@ Tools run with `ExtensionContext`, so they cannot call `ctx.reload()` directly.
|
|
|
1755
1292
|
Example tool the LLM can call to trigger reload:
|
|
1756
1293
|
|
|
1757
1294
|
```typescript
|
|
1758
|
-
import type { ExtensionAPI } from "@
|
|
1295
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
1759
1296
|
import { Type } from "typebox";
|
|
1760
1297
|
|
|
1761
1298
|
export default function (pi: ExtensionAPI) {
|
|
@@ -1873,7 +1410,7 @@ pi.sendUserMessage("What is 2+2?");
|
|
|
1873
1410
|
// With content array (text + images)
|
|
1874
1411
|
pi.sendUserMessage([
|
|
1875
1412
|
{ type: "text", text: "Describe this image:" },
|
|
1876
|
-
{ type: "image",
|
|
1413
|
+
{ type: "image", source: { type: "base64", mediaType: "image/png", data: "..." } },
|
|
1877
1414
|
]);
|
|
1878
1415
|
|
|
1879
1416
|
// During streaming - must specify delivery mode
|
|
@@ -1948,7 +1485,7 @@ Labels persist in the session and survive restarts. Use them to mark important p
|
|
|
1948
1485
|
|
|
1949
1486
|
Register a command.
|
|
1950
1487
|
|
|
1951
|
-
If multiple extensions register the same command name,
|
|
1488
|
+
If multiple extensions register the same command name, pi keeps them all and assigns numeric invocation suffixes in load order, for example `/review:1` and `/review:2`.
|
|
1952
1489
|
|
|
1953
1490
|
```typescript
|
|
1954
1491
|
pi.registerCommand("stats", {
|
|
@@ -2102,41 +1639,6 @@ Typical `sourceInfo.source` values:
|
|
|
2102
1639
|
- `sdk` for tools passed via `createAgentSession({ customTools })`
|
|
2103
1640
|
- extension source metadata for tools registered by extensions
|
|
2104
1641
|
|
|
2105
|
-
### pi.executeTool(name, params, options?)
|
|
2106
|
-
|
|
2107
|
-
Execute an active tool from extension code using the same wrapped pipeline as a model-dispatched tool call.
|
|
2108
|
-
|
|
2109
|
-
```typescript
|
|
2110
|
-
const result = await pi.executeTool("read", { path: "README.md" }, {
|
|
2111
|
-
onUpdate: (partial) => {
|
|
2112
|
-
// Forward partial AgentToolResult updates into your parent tool UI.
|
|
2113
|
-
},
|
|
2114
|
-
});
|
|
2115
|
-
```
|
|
2116
|
-
|
|
2117
|
-
`params` are prepared and validated against the target tool schema before `tool_call` handlers run. If validation fails, `executeTool` rejects with `ExecuteToolError` code `invalid_params`, the tool is not executed, and no `tool_call` hook fires.
|
|
2118
|
-
|
|
2119
|
-
Only active tools can run. Registered but inactive tools reject with code `inactive_tool`; unknown names reject with code `unknown_tool`. Both errors include `toolName` and `activeTools`.
|
|
2120
|
-
|
|
2121
|
-
When pre-tool hooks block, `executeTool` rejects with code `blocked` and the hook reason. When hooks mutate `event.input`, the executed tool sees the mutated arguments. `tool_result` hooks can replace the returned content, details, or error state before the promise resolves.
|
|
2122
|
-
|
|
2123
|
-
The optional `signal` and `onUpdate` are forwarded directly to the tool execution callback. Bridge-invoked calls emit `tool_call` and `tool_result` extension events, but do not emit `tool_execution_start`, `tool_execution_update`, or `tool_execution_end` UI events; they are subcalls of the parent extension/tool UI.
|
|
2124
|
-
|
|
2125
|
-
The SDK exports `ExecuteToolOptions<TDetails>`, `ExecuteToolResult<TDetails>`, `ExecuteToolUpdateCallback<TDetails>`, `ExecuteToolError`, and `ExecuteToolErrorCode`. `ExecuteToolResult<TDetails>` is the normal `AgentToolResult<TDetails>` returned by the wrapped tool after `tool_result` hooks have run.
|
|
2126
|
-
|
|
2127
|
-
```typescript
|
|
2128
|
-
try {
|
|
2129
|
-
const result = await pi.executeTool("bash", { command: "pwd" });
|
|
2130
|
-
ctx.ui.notify(result.content);
|
|
2131
|
-
} catch (error) {
|
|
2132
|
-
if (error instanceof ExecuteToolError && error.code === "blocked") {
|
|
2133
|
-
ctx.ui.notify(error.message, "warning");
|
|
2134
|
-
} else {
|
|
2135
|
-
throw error;
|
|
2136
|
-
}
|
|
2137
|
-
}
|
|
2138
|
-
```
|
|
2139
|
-
|
|
2140
1642
|
### pi.setModel(model)
|
|
2141
1643
|
|
|
2142
1644
|
Set the current model. Returns `false` if no API key is available for the model. See [models.md](models.md) for configuring custom models.
|
|
@@ -2175,7 +1677,7 @@ Register or override a model provider dynamically. Useful for proxies, custom en
|
|
|
2175
1677
|
|
|
2176
1678
|
Calls made during the extension factory function are queued and applied once the runner initialises. Calls made after that — for example from a command handler following a user setup flow — take effect immediately without requiring a `/reload`.
|
|
2177
1679
|
|
|
2178
|
-
|
|
1680
|
+
Dynamic providers can implement `refreshModels`. Pi calls it during model refresh, publishes the returned list synchronously through the provider, and passes the canonical credential/store/network/signal context. The extension decides whether to persist the catalog through `context.store`; live servers such as llama.cpp can ignore it.
|
|
2179
1681
|
|
|
2180
1682
|
```typescript
|
|
2181
1683
|
// Register a new provider with custom models
|
|
@@ -2197,6 +1699,26 @@ pi.registerProvider("my-proxy", {
|
|
|
2197
1699
|
]
|
|
2198
1700
|
});
|
|
2199
1701
|
|
|
1702
|
+
// Register a live llama.cpp catalog without persisting discovered models
|
|
1703
|
+
pi.registerProvider("llama.cpp", {
|
|
1704
|
+
baseUrl: "http://localhost:8080/v1",
|
|
1705
|
+
apiKey: "local",
|
|
1706
|
+
api: "openai-completions",
|
|
1707
|
+
async refreshModels({ signal }) {
|
|
1708
|
+
const response = await fetch("http://localhost:8080/v1/models", { signal });
|
|
1709
|
+
const { data } = await response.json();
|
|
1710
|
+
return data.map(({ id }) => ({
|
|
1711
|
+
id,
|
|
1712
|
+
name: id,
|
|
1713
|
+
reasoning: false,
|
|
1714
|
+
input: ["text"],
|
|
1715
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
1716
|
+
contextWindow: 128000,
|
|
1717
|
+
maxTokens: 16384
|
|
1718
|
+
}));
|
|
1719
|
+
}
|
|
1720
|
+
});
|
|
1721
|
+
|
|
2200
1722
|
// Override baseUrl for an existing provider (keeps all models)
|
|
2201
1723
|
pi.registerProvider("anthropic", {
|
|
2202
1724
|
baseUrl: "https://proxy.example.com"
|
|
@@ -2234,6 +1756,7 @@ pi.registerProvider("corporate-ai", {
|
|
|
2234
1756
|
- `headers` - Custom headers to include in requests.
|
|
2235
1757
|
- `authHeader` - If true, adds `Authorization: Bearer` header automatically.
|
|
2236
1758
|
- `models` - Array of model definitions. If provided, replaces all existing models for this provider. Model definitions can set `baseUrl` to override the provider endpoint for that model.
|
|
1759
|
+
- `refreshModels` - Async dynamic discovery callback. Its returned models replace extension-provided models. Use the scoped `context.store` only when results should persist.
|
|
2237
1760
|
- `oauth` - OAuth provider config for `/login` support. When provided, the provider appears in the login menu.
|
|
2238
1761
|
- `streamSimple` - Custom streaming implementation for non-standard APIs.
|
|
2239
1762
|
|
|
@@ -2309,7 +1832,7 @@ Pass the real target file path to `withFileMutationQueue()`, not the raw user ar
|
|
|
2309
1832
|
Queue the entire mutation window on that target path. That includes read-modify-write logic, not just the final write.
|
|
2310
1833
|
|
|
2311
1834
|
```typescript
|
|
2312
|
-
import { withFileMutationQueue } from "@
|
|
1835
|
+
import { withFileMutationQueue } from "@earendil-works/pi-coding-agent";
|
|
2313
1836
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2314
1837
|
import { dirname, resolve } from "node:path";
|
|
2315
1838
|
|
|
@@ -2405,7 +1928,7 @@ async execute(toolCallId, params) {
|
|
|
2405
1928
|
|
|
2406
1929
|
**Important:** Use `StringEnum` from `@earendil-works/pi-ai` for string enums. `Type.Union`/`Type.Literal` doesn't work with Google's API.
|
|
2407
1930
|
|
|
2408
|
-
**Argument preparation:** `prepareArguments(args)` is optional. If defined, it runs before schema validation and before `execute()`. Use it to mimic an older accepted input shape when
|
|
1931
|
+
**Argument preparation:** `prepareArguments(args)` is optional. If defined, it runs before schema validation and before `execute()`. Use it to mimic an older accepted input shape when pi resumes an older session whose stored tool call arguments no longer match the current schema. Return the object you want validated against `parameters`. Keep the public schema strict. Do not add deprecated compatibility fields to `parameters` just to keep old resumed sessions working.
|
|
2409
1932
|
|
|
2410
1933
|
Example: an older session may contain an `edit` tool call with top-level `oldText` and `newText`, while the current schema only accepts `edits: [{ oldText, newText }]`.
|
|
2411
1934
|
|
|
@@ -2458,13 +1981,13 @@ Extensions can override built-in tools (`read`, `bash`, `edit`, `write`, `grep`,
|
|
|
2458
1981
|
|
|
2459
1982
|
```bash
|
|
2460
1983
|
# Extension's read tool replaces built-in read
|
|
2461
|
-
|
|
1984
|
+
pi -e ./tool-override.ts
|
|
2462
1985
|
```
|
|
2463
1986
|
|
|
2464
1987
|
Alternatively, use `--no-builtin-tools` to start without any built-in tools while keeping extension tools enabled:
|
|
2465
1988
|
```bash
|
|
2466
1989
|
# No built-in tools, only extension tools
|
|
2467
|
-
|
|
1990
|
+
pi --no-builtin-tools -e ./my-extension.ts
|
|
2468
1991
|
```
|
|
2469
1992
|
|
|
2470
1993
|
See [examples/extensions/tool-override.ts](../examples/extensions/tool-override.ts) for a complete example that overrides `read` with logging and access control.
|
|
@@ -2489,7 +2012,7 @@ Built-in tool implementations:
|
|
|
2489
2012
|
Built-in tools support pluggable operations for delegating to remote systems (SSH, containers, etc.):
|
|
2490
2013
|
|
|
2491
2014
|
```typescript
|
|
2492
|
-
import { createReadTool, createBashTool, type ReadOperations } from "@
|
|
2015
|
+
import { createReadTool, createBashTool, type ReadOperations } from "@earendil-works/pi-coding-agent";
|
|
2493
2016
|
|
|
2494
2017
|
// Create tool with custom operations
|
|
2495
2018
|
const remoteRead = createReadTool(cwd, {
|
|
@@ -2515,12 +2038,12 @@ pi.registerTool({
|
|
|
2515
2038
|
|
|
2516
2039
|
**Operations interfaces:** `ReadOperations`, `WriteOperations`, `EditOperations`, `BashOperations`, `LsOperations`, `GrepOperations`, `FindOperations`
|
|
2517
2040
|
|
|
2518
|
-
For `user_bash`, extensions can reuse
|
|
2041
|
+
For `user_bash`, extensions can reuse pi's local shell backend via `createLocalBashOperations()` instead of reimplementing local process spawning, shell resolution, and process-tree termination.
|
|
2519
2042
|
|
|
2520
2043
|
The bash tool also supports a spawn hook to adjust the command, cwd, or env before execution:
|
|
2521
2044
|
|
|
2522
2045
|
```typescript
|
|
2523
|
-
import { createBashTool } from "@
|
|
2046
|
+
import { createBashTool } from "@earendil-works/pi-coding-agent";
|
|
2524
2047
|
|
|
2525
2048
|
const bashTool = createBashTool(cwd, {
|
|
2526
2049
|
spawnHook: ({ command, cwd, env }) => ({
|
|
@@ -2550,7 +2073,7 @@ import {
|
|
|
2550
2073
|
formatSize, // Human-readable size (e.g., "50KB", "1.5MB")
|
|
2551
2074
|
DEFAULT_MAX_BYTES, // 50KB
|
|
2552
2075
|
DEFAULT_MAX_LINES, // 2000
|
|
2553
|
-
} from "@
|
|
2076
|
+
} from "@earendil-works/pi-coding-agent";
|
|
2554
2077
|
|
|
2555
2078
|
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
2556
2079
|
const output = await runCommand();
|
|
@@ -2633,7 +2156,6 @@ pi.registerTool({
|
|
|
2633
2156
|
- `lastComponent` - the previously returned component for that slot, if any
|
|
2634
2157
|
- `invalidate()` - request a rerender of this tool row
|
|
2635
2158
|
- `toolCallId`, `cwd`, `executionStarted`, `argsComplete`, `isPartial`, `expanded`, `showImages`, `isError`
|
|
2636
|
-
- `imageProtocol` - optional for compatibility with older hosts; when present it is `"kitty"`, `"iterm2"`, or `null` when the terminal cannot render inline images. With an active protocol, the host owns native image rendering and protocol fallbacks for image result blocks, so custom renderers can suppress duplicate image indicators.
|
|
2637
2159
|
|
|
2638
2160
|
Use `context.state` for cross-slot shared state. Keep slot-local caches on the returned component instance when you want to reuse and mutate the same component across renders.
|
|
2639
2161
|
|
|
@@ -2687,7 +2209,7 @@ If a slot intentionally has no visible content, return an empty `Component` such
|
|
|
2687
2209
|
Use `keyHint()` to display keybinding hints that respect the active keybinding configuration:
|
|
2688
2210
|
|
|
2689
2211
|
```typescript
|
|
2690
|
-
import { keyHint } from "@
|
|
2212
|
+
import { keyHint } from "@earendil-works/pi-coding-agent";
|
|
2691
2213
|
|
|
2692
2214
|
renderResult(result, { expanded }, theme, context) {
|
|
2693
2215
|
let text = theme.fg("success", "✓ Done");
|
|
@@ -2992,7 +2514,7 @@ ctx.ui.setFooter((tui, theme) => ({
|
|
|
2992
2514
|
ctx.ui.setFooter(undefined); // Restore built-in footer
|
|
2993
2515
|
|
|
2994
2516
|
// Terminal title
|
|
2995
|
-
ctx.ui.setTitle("
|
|
2517
|
+
ctx.ui.setTitle("pi - my-project");
|
|
2996
2518
|
|
|
2997
2519
|
// Editor text
|
|
2998
2520
|
ctx.ui.setEditorText("Prefill text");
|
|
@@ -3165,7 +2687,7 @@ See [tui.md](tui.md) for the full `OverlayOptions` and `OverlayHandle` API and [
|
|
|
3165
2687
|
Replace the main input editor with a custom implementation (vim mode, emacs mode, etc.):
|
|
3166
2688
|
|
|
3167
2689
|
```typescript
|
|
3168
|
-
import { CustomEditor, type ExtensionAPI } from "@
|
|
2690
|
+
import { CustomEditor, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3169
2691
|
import { matchesKey } from "@earendil-works/pi-tui";
|
|
3170
2692
|
|
|
3171
2693
|
class VimEditor extends CustomEditor {
|
|
@@ -3275,7 +2797,7 @@ theme.strikethrough(text)
|
|
|
3275
2797
|
For syntax highlighting in custom tool renderers:
|
|
3276
2798
|
|
|
3277
2799
|
```typescript
|
|
3278
|
-
import { highlightCode, getLanguageFromPath } from "@
|
|
2800
|
+
import { highlightCode, getLanguageFromPath } from "@earendil-works/pi-coding-agent";
|
|
3279
2801
|
|
|
3280
2802
|
// Highlight code with explicit language
|
|
3281
2803
|
const highlighted = highlightCode("const x = 1;", "typescript", theme);
|
|
@@ -3368,6 +2890,7 @@ All examples in [examples/extensions/](../examples/extensions/).
|
|
|
3368
2890
|
| `interactive-shell.ts` | Persistent shell session | `on("user_bash")` |
|
|
3369
2891
|
| `sandbox/` | Sandboxed tool execution | Tool operations |
|
|
3370
2892
|
| `gondolin/` | Route built-in tools and `!` commands into a Gondolin micro-VM | Tool operations, built-in tool overrides, `on("user_bash")` |
|
|
2893
|
+
| `subagent/` | Spawn sub-agents | `registerTool`, `exec` |
|
|
3371
2894
|
| **Games** |||
|
|
3372
2895
|
| `snake.ts` | Snake game | `registerCommand`, `ui.custom`, keyboard handling |
|
|
3373
2896
|
| `space-invaders.ts` | Space Invaders game | `registerCommand`, `ui.custom` |
|