pi-python-core 0.10.0__tar.gz → 0.10.1__tar.gz
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.
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/PKG-INFO +2 -1
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/README.md +1 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/README.zh-CN.md +1 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/COVERAGE.md +1 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/verification.json +37 -25
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/docs/PLUGINS.md +56 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/docs/PLUGIN_DEVELOPMENT.md +1 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/docs/zh/PLUGINS.md +56 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/docs/zh/PLUGIN_DEVELOPMENT.md +1 -0
- pi_python_core-0.10.1/examples/memory_demo.py +62 -0
- pi_python_core-0.10.1/examples/plugins/memory/plugin.py +138 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/pyproject.toml +1 -1
- pi_python_core-0.10.1/src/pi_python/_version.py +1 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/plugins/__init__.py +19 -3
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/plugins/_compose.py +15 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_examples.py +1 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_plugins.py +50 -0
- pi_python_core-0.10.0/src/pi_python/_version.py +0 -1
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/.gitignore +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/LICENSE +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/NOTICE +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/__init__.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/baseline.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/contracts/release-candidate.example.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/contracts/release-candidate.schema.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/contracts/upstream-baseline.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/fixtures/C01-text.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/fixtures/C02-tool.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/fixtures/C03-sequential.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/fixtures/C04-parallel.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/fixtures/C05-one-sequential.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/fixtures/C06-one-failure.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/fixtures/C07-unknown.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/fixtures/C08-prepare.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/fixtures/C09-before-error.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/fixtures/C09-block.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/fixtures/C09-replace.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/fixtures/C10-length.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/fixtures/C11-transform.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/fixtures/C12-system.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/fixtures/C13-steering.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/fixtures/C14-followup-all.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/fixtures/C14-followup-one.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/fixtures/C15-prepare.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/fixtures/C16-continue.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/fixtures/C16-end.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/fixtures/C16-mixed.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/fixtures/C16-terminate.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/fixtures/C19-abort-stream.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/fixtures/C19-abort-tools.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/fixtures/C24-provider-error.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/plugin-cases.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/plugin-fixtures/prompts/notes.txt +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/plugin-fixtures/prompts/plan.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/plugin-fixtures/prompts/review.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/plugin-fixtures/prompts/sub/nested.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/plugin-fixtures/prompts/summarize.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/plugin-fixtures/skills/.hidden/SKILL.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/plugin-fixtures/skills/alpha/SKILL.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/plugin-fixtures/skills/alpha/references/notes.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/plugin-fixtures/skills/beta/SKILL.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/plugin-fixtures/skills/broken/SKILL.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/plugin-fixtures/skills/delta/SKILL.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/plugin-fixtures/skills/gamma/SKILL.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/plugin-fixtures/skills/longdesc/SKILL.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/plugin-fixtures/skills/loose.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/plugin-fixtures/skills/nested/group/epsilon/SKILL.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/plugin-fixtures/skills/noname/SKILL.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/plugin-fixtures/skills/plain.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/plugin-fixtures/skills/zeta/SKILL.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/plugin-fixtures/skills/zeta/sub/eta/SKILL.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/anthropic-oauth.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/anthropic-request-cache-long.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/anthropic-request-cache-none.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/anthropic-request-replay.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/anthropic-request-tools-image.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/anthropic-text.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/anthropic-thinking.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/anthropic-tool.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/codex-request-tools-image.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/codex-text.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/completions-compat-strict.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/completions-deepseek.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/completions-format-ant-ling.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/completions-format-baseten.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/completions-format-string-thinking.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/completions-format-together.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/completions-format-zai.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/completions-midconvo-tools.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/completions-moonshot-usage.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/completions-openai-off.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/completions-openrouter.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/completions-qwen-length.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/completions-reasoning-content.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/completions-request-replay.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/completions-text.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/completions-tool-stream.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/openai-incomplete.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/openai-request-cache-long.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/openai-request-cache-none.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/openai-request-reasoning.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/openai-request-replay.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/openai-request-tools-image.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/openai-text.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/openai-thinking-backfill.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/openai-thinking.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/openai-tool.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/proxy-text.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/proxy-thinking-tool.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/session-anthropic-collapse-budget.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/session-anthropic-native-tools.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/session-anthropic-oauth-effort.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/session-anthropic-redefinition.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/session-anthropic-server-fallback.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/session-anthropic-thinking-off.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/session-codex-tool-search.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/session-openai-additional-tools.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/session-openai-collapse.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/session-openai-explicit-cache-long.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/session-openai-explicit-cache-none.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/provider-fixtures/session-openai-removal.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/recovery-cases.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/release.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C01-text.python.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C01-text.upstream.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C02-tool.python.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C02-tool.upstream.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C03-sequential.python.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C03-sequential.upstream.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C04-parallel.python.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C04-parallel.upstream.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C05-one-sequential.python.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C05-one-sequential.upstream.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C06-one-failure.python.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C06-one-failure.upstream.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C07-unknown.python.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C07-unknown.upstream.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C08-prepare.python.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C08-prepare.upstream.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C09-before-error.python.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C09-before-error.upstream.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C09-block.python.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C09-block.upstream.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C09-replace.python.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C09-replace.upstream.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C10-length.python.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C10-length.upstream.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C11-transform.python.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C11-transform.upstream.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C12-system.python.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C12-system.upstream.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C13-steering.python.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C13-steering.upstream.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C14-followup-all.python.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C14-followup-all.upstream.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C14-followup-one.python.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C14-followup-one.upstream.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C15-prepare.python.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C15-prepare.upstream.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C16-continue.python.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C16-continue.upstream.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C16-end.python.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C16-end.upstream.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C16-mixed.python.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C16-mixed.upstream.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C16-terminate.python.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C16-terminate.upstream.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C19-abort-stream.python.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C19-abort-stream.upstream.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C19-abort-tools.python.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C19-abort-tools.upstream.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C24-provider-error.python.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/C24-provider-error.upstream.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/artifacts.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/benchmark.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/bootstrap.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/candidate-live-noop.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/conformance.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/linux-3.11.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/linux-3.12.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/linux-3.13.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/linux-3.14.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/linux-install-hash-repair.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/live-0.5-regression.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/live-0.5-session-claude.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/live-0.5-session-codex.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/live-claude-reasoning-replay.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/live-final-matrix.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/live-initial.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/live-loop-initial.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/live-providers.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/live-reasoning-replay.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/live-refresh.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/live-regression-0.4-deepseek.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/live-regression-0.4-first-run.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/live-regression-0.4.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/live-session-claude.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/live-session-codex.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/live-websocket-cached-recheck.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/live-websocket-cached.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/live-websocket-final.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/plugins.upstream.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/provider-conformance.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/recovery-conformance.upstream.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/runtime-dependencies.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/source-verification.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.1.0/artifacts.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.1.0/linux-3.11.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.1.0/linux-3.12.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.1.0/linux-3.13.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.1.0/verification.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.2.0/artifacts.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.2.0/linux-3.11.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.2.0/linux-3.12.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.2.0/linux-3.13.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.2.0/verification.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.3.0/against-0.4-fixtures.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.3.0/artifacts.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.3.0/linux-3.11.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.3.0/linux-3.12.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.3.0/linux-3.13.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.3.0/provider-conformance.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.3.0/verification.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.4.0/artifacts.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.4.0/linux-3.11.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.4.0/linux-3.12.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.4.0/linux-3.13.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.4.0/verification.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.5.0/artifacts.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.5.0/linux-3.11.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.5.0/linux-3.12.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.5.0/linux-3.13.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.5.0/verification.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.6.0/artifacts.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.6.0/linux-3.11.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.6.0/linux-3.12.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.6.0/linux-3.13.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.6.0/verification.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.7.0/artifacts.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.7.0/linux-3.11.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.7.0/linux-3.12.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.7.0/linux-3.13.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.7.0/linux-3.14.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.7.0/verification.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.8.0/artifacts.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.8.0/linux-3.11.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.8.0/linux-3.12.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.8.0/linux-3.13.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.8.0/linux-3.14.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/v0.8.0/verification.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/results/websocket-conformance.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/runner.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/websocket-fixtures/codex-continuation-lost-state.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/compat/websocket-fixtures/codex-continuation-tool-loop.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/docs/API.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/docs/CODING_AGENTS.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/docs/CONCEPTS.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/docs/IMPLEMENTATION.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/docs/MCP_INTERACTION.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/docs/PROVIDERS.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/docs/history/IMPLEMENTATION-0.3.0.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/docs/history/IMPLEMENTATION-0.4.0.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/docs/history/IMPLEMENTATION-0.5.0.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/docs/history/IMPLEMENTATION-0.6.0.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/docs/history/IMPLEMENTATION-0.7.0.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/docs/history/IMPLEMENTATION-0.8.0.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/docs/zh/API.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/docs/zh/CODING_AGENTS.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/docs/zh/CONCEPTS.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/docs/zh/MCP_HOST_VERIFICATION.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/docs/zh/MCP_INTERACTION.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/docs/zh/PROVIDERS.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/examples/in_memory.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/examples/local_model.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/examples/mcp_interactive.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/examples/mcp_interactive_server.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/examples/mcp_tools.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/examples/plugin_demo.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/examples/plugins/lab_tools/agents/reviewer.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/examples/plugins/lab_tools/ops.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/examples/plugins/lab_tools/plugin.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/examples/plugins/lab_tools/prompts/dedup.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/examples/plugins/lab_tools/skills/dedup-window/SKILL.md +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/examples/provider_chat.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/examples/quickstart.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/examples/recovery.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/examples/save_restore.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/examples/subagent.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/__init__.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/_mcp_host.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/_mcp_interaction.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/_mcp_process.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/_mcp_sampling.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/agent.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/cancellation.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/data/models.json +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/errors.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/estimate.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/events.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/features.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/function_tools.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/hooks.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/limits.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/loop.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/lowlevel.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/mcp.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/messages.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/models.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/plugins/_frontmatter.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/plugins/_resources.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/plugins/_subagents.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/provider.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/providers/__init__.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/providers/anthropic.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/providers/common.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/providers/completions.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/providers/oauth.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/providers/openai.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/providers/transport.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/proxy.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/py.typed +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/queues.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/recovery.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/run.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/stream.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/sync.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/tasks.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/testing.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/tools.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/src/pi_python/transcript.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/servers/mcp_interactive_server.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/servers/mcp_process_server.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/servers/mcp_server.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_completions.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_conformance.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_contract_v3.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_control.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_extended_core.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_features.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_function_tools.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_lifecycle_contracts.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_loop.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_mcp.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_mcp_delta.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_mcp_host.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_mcp_interaction.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_mcp_process.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_messages.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_models.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_nested_agents.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_oauth.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_plugin_conformance.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_providers.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_recovery.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_recovery_conformance.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_recovery_helpers.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_release.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_session_changes.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_sync.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_tasks.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_tools.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_typeddict_annotations.py +0 -0
- {pi_python_core-0.10.0 → pi_python_core-0.10.1}/tests/test_websocket_continuation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pi-python-core
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.1
|
|
4
4
|
Summary: An embeddable asyncio agent core with opt-in Claude and OpenAI providers
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -99,6 +99,7 @@ To try it offline first: `python examples/quickstart.py`.
|
|
|
99
99
|
| Use MCP tools | `async with connect_stdio(...) as tools`, or `connect_http(url)` for a remote server | [mcp_tools](examples/mcp_tools.py) |
|
|
100
100
|
| Let one agent call another | Wrap the sub-agent as a tool; cancellation propagates down. Plugins can also define subagents in Markdown | [subagent](examples/subagent.py) |
|
|
101
101
|
| Package and share capabilities | A plugin bundles tools, instructions, hooks, skills, prompt templates, subagents and MCP servers; load it by name or path with `load_plugins` and build the agent with `plugins.agent(...)` | [plugin_demo](examples/plugin_demo.py) |
|
|
102
|
+
| Remember across conversations | Not built in, as in Pi's core; an example plugin saves facts to a file and puts them in the system prompt, ready to copy and adapt | [memory_demo](examples/memory_demo.py) |
|
|
102
103
|
| Intervene mid-run | `steer` injects guidance, `follow_up` queues the next task, `abort` cancels at any time (callable from any thread) | |
|
|
103
104
|
| Context full, service errors | `is_context_overflow` and `is_retryable_error` tell you why, `continue_run()` retries, `transform_context` compacts | [recovery](examples/recovery.py) |
|
|
104
105
|
| Save and restore conversations | `encode_messages` / `decode_messages`; your application decides where to store them | [save_restore](examples/save_restore.py) |
|
|
@@ -65,6 +65,7 @@ To try it offline first: `python examples/quickstart.py`.
|
|
|
65
65
|
| Use MCP tools | `async with connect_stdio(...) as tools`, or `connect_http(url)` for a remote server | [mcp_tools](examples/mcp_tools.py) |
|
|
66
66
|
| Let one agent call another | Wrap the sub-agent as a tool; cancellation propagates down. Plugins can also define subagents in Markdown | [subagent](examples/subagent.py) |
|
|
67
67
|
| Package and share capabilities | A plugin bundles tools, instructions, hooks, skills, prompt templates, subagents and MCP servers; load it by name or path with `load_plugins` and build the agent with `plugins.agent(...)` | [plugin_demo](examples/plugin_demo.py) |
|
|
68
|
+
| Remember across conversations | Not built in, as in Pi's core; an example plugin saves facts to a file and puts them in the system prompt, ready to copy and adapt | [memory_demo](examples/memory_demo.py) |
|
|
68
69
|
| Intervene mid-run | `steer` injects guidance, `follow_up` queues the next task, `abort` cancels at any time (callable from any thread) | |
|
|
69
70
|
| Context full, service errors | `is_context_overflow` and `is_retryable_error` tell you why, `continue_run()` retries, `transform_context` compacts | [recovery](examples/recovery.py) |
|
|
70
71
|
| Save and restore conversations | `encode_messages` / `decode_messages`; your application decides where to store them | [save_restore](examples/save_restore.py) |
|
|
@@ -65,6 +65,7 @@ agent = Agent(provider=llm, model=llm.model("qwen3:8b", context_window=40960), t
|
|
|
65
65
|
| 用 MCP 工具 | `async with connect_stdio(...) as tools`;远程服务器用 `connect_http(url)` | [mcp_tools](examples/mcp_tools.py) |
|
|
66
66
|
| 让一个 agent 调用另一个 | 把子 agent 包成工具,取消会一路传下去;插件也可以用 Markdown 定义子 agent | [subagent](examples/subagent.py) |
|
|
67
67
|
| 打包和分享能力 | 插件把工具、说明、钩子、技能、提示模板、子 agent 和 MCP 服务器打成一组;用 `load_plugins` 按名字或路径加载,再用 `plugins.agent(...)` 构造 agent | [plugin_demo](examples/plugin_demo.py) |
|
|
68
|
+
| 跨对话记住信息 | 与 Pi 的核心一样不内置;示例插件把事实存进文件,再放进系统提示,可以复制后修改 | [memory_demo](examples/memory_demo.py) |
|
|
68
69
|
| 中途干预 | `steer` 插入指导,`follow_up` 排队后续任务,`abort` 随时取消(任何线程都可以调用) | |
|
|
69
70
|
| 上下文满了、服务出错 | `is_context_overflow`、`is_retryable_error` 判断原因,`continue_run()` 重试,`transform_context` 压缩 | [recovery](examples/recovery.py) |
|
|
70
71
|
| 保存与恢复对话 | `encode_messages` / `decode_messages`,应用决定存在哪里 | [save_restore](examples/save_restore.py) |
|
|
@@ -133,6 +133,7 @@ CI 第一次在 GitHub 上运行,Linux 和 macOS 全部通过,另外发现
|
|
|
133
133
|
- 上游由命令行程序发现和加载 package(`pi install`、`settings.json`、项目信任)。本库没有应用程序,由应用代码指定加载哪些插件,不自动发现。已安装的插件通过 entry point 组 `pi_python.plugins` 注册,对应 `pi install npm:...`。
|
|
134
134
|
- 插件代码是 Python 的 `setup(api)`,对应 Pi 扩展的工厂函数。`PluginAPI` 只保留与界面无关的部分(工具、系统提示、钩子、事件、MCP),没有命令、快捷键、渲染器等终端界面接口;另加了 `service`(应用提供的对象)、`options`(按插件分开的设置)、`add_check`(自检)和 `on_close`。
|
|
135
135
|
- 插件处理函数出错时交给 `on_error`,默认写日志;上游交给 `emitError`,显示在界面上。`before_tool_call` 与上游一样,出错时让这次调用失败。应用自己的钩子保持核心行为,不做隔离。
|
|
136
|
+
- 插件注册处理函数时检查它能否接收该钩子传入的位置参数(`subscribe` 为事件一个参数,`add_check` 和 `on_close` 没有参数),不能就报 `ConfigurationError`,加载停止(0.10.1 加入)。上游没有这一步:TypeScript 在编译时检查类型,JavaScript 调用时多出的参数会被忽略。Python 会在每次调用时抛出 `TypeError`,插件处理函数的错误又只写日志,不检查的话这样的处理函数会悄悄失效。应用自己的钩子不做这项检查。
|
|
136
137
|
- 模型用 `read_skill` 工具读取技能(上游让模型用通用的 `read` 工具)。它只能读技能目录里的文件,单个文件不超过 256 KiB。
|
|
137
138
|
- 子 agent 在同一进程、同一事件循环里运行(上游为每个子 agent 启动一个 pi 子进程),使用主 agent 的 Provider 和合并后的全部钩子。上游的子进程会加载同样的扩展,所以插件的处理函数同样作用于子 agent,这一点与上游一致。不指定模型时,子 agent 用调用那一刻主 agent 的模型(上游从设置里取默认模型)。没有上游的 `agentScope`、`cwd` 参数和项目级 agent 的确认步骤。
|
|
138
139
|
- MCP:工具名为 `mcp__<服务器>__<工具>`,但工具名里的 `-` 保留(上游换成 `_`)。字符串里只展开 `${PLUGIN_ROOT}`、`${PYTHON}` 和环境变量,前两个是本库新增;不支持 `!命令`。不支持 OAuth、`exposure` / `toolExposure` 和单次请求超时,这些设置会被忽略并给出警告。新增 `pi_python.mcp.connect_http`,连接 streamable HTTP 服务器;在 MCP SDK 1.10、1.30 和 2.3 上测过。
|
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
"--extra",
|
|
16
16
|
"providers",
|
|
17
17
|
"--extra",
|
|
18
|
-
"mcp"
|
|
18
|
+
"mcp-interactive"
|
|
19
19
|
],
|
|
20
20
|
"exit_code": 0,
|
|
21
|
-
"seconds": 0.
|
|
21
|
+
"seconds": 0.02251266618259251,
|
|
22
22
|
"stdout": "",
|
|
23
|
-
"stderr": "Resolved
|
|
23
|
+
"stderr": "Resolved 60 packages in 2ms\nAudited 57 packages in 8ms\n"
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
26
|
"command": [
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"scripts"
|
|
36
36
|
],
|
|
37
37
|
"exit_code": 0,
|
|
38
|
-
"seconds": 0.
|
|
38
|
+
"seconds": 0.0649345419369638,
|
|
39
39
|
"stdout": "All checks passed!\n",
|
|
40
40
|
"stderr": ""
|
|
41
41
|
},
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
"scripts"
|
|
54
54
|
],
|
|
55
55
|
"exit_code": 0,
|
|
56
|
-
"seconds": 0.
|
|
57
|
-
"stdout": "
|
|
56
|
+
"seconds": 0.024958166992291808,
|
|
57
|
+
"stdout": "110 files already formatted\n",
|
|
58
58
|
"stderr": ""
|
|
59
59
|
},
|
|
60
60
|
{
|
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
"src/pi_python"
|
|
66
66
|
],
|
|
67
67
|
"exit_code": 0,
|
|
68
|
-
"seconds": 0.
|
|
69
|
-
"stdout": "Success: no issues found in
|
|
68
|
+
"seconds": 0.5702685830183327,
|
|
69
|
+
"stdout": "Success: no issues found in 43 source files\n",
|
|
70
70
|
"stderr": ""
|
|
71
71
|
},
|
|
72
72
|
{
|
|
@@ -77,8 +77,8 @@
|
|
|
77
77
|
"-q"
|
|
78
78
|
],
|
|
79
79
|
"exit_code": 0,
|
|
80
|
-
"seconds":
|
|
81
|
-
"stdout": "........................................................................ [
|
|
80
|
+
"seconds": 149.19938791706227,
|
|
81
|
+
"stdout": "........................................................................ [ 9%]\n........................................................................ [ 19%]\n........................................................................ [ 28%]\n........................................................................ [ 38%]\n........................................................................ [ 47%]\n........................................................................ [ 57%]\n........................................................................ [ 67%]\n...............................................ss....................... [ 76%]\n........................................................................ [ 86%]\n........................................................................ [ 95%]\n................................ [100%]\n750 passed, 2 skipped in 148.30s (0:02:28)\n",
|
|
82
82
|
"stderr": ""
|
|
83
83
|
},
|
|
84
84
|
{
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"examples/in_memory.py"
|
|
90
90
|
],
|
|
91
91
|
"exit_code": 0,
|
|
92
|
-
"seconds": 0.
|
|
92
|
+
"seconds": 0.6022720420733094,
|
|
93
93
|
"stdout": "The telemetry contains 3 complete records.\n",
|
|
94
94
|
"stderr": ""
|
|
95
95
|
},
|
|
@@ -104,8 +104,8 @@
|
|
|
104
104
|
"test/agent.test.ts"
|
|
105
105
|
],
|
|
106
106
|
"exit_code": 0,
|
|
107
|
-
"seconds": 1.
|
|
108
|
-
"stdout": "\n> @earendil-works/pi-agent-core@1.0.0 test\n> vitest --run test/agent-loop.test.ts test/agent.test.ts\n\n\n RUN v4.1.11 /Users/daidong/Documents/gitrepos/pi-python/reference/pi/packages/agent\n\n\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\n\n Test Files 2 passed (2)\n Tests 77 passed (77)\n Start at
|
|
107
|
+
"seconds": 1.9349422920495272,
|
|
108
|
+
"stdout": "\n> @earendil-works/pi-agent-core@1.0.0 test\n> vitest --run test/agent-loop.test.ts test/agent.test.ts\n\n\n RUN v4.1.11 /Users/daidong/Documents/gitrepos/pi-python/reference/pi/packages/agent\n\n\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\n\n Test Files 2 passed (2)\n Tests 77 passed (77)\n Start at 12:01:38\n Duration 1.40s (transform 916ms, setup 0ms, import 2.00s, tests 281ms, environment 0ms)\n\n",
|
|
109
109
|
"stderr": ""
|
|
110
110
|
},
|
|
111
111
|
{
|
|
@@ -121,8 +121,8 @@
|
|
|
121
121
|
"test/openai-codex-stream.test.ts"
|
|
122
122
|
],
|
|
123
123
|
"exit_code": 0,
|
|
124
|
-
"seconds": 1.
|
|
125
|
-
"stdout": "\n> @earendil-works/pi-ai@1.0.0 test\n> vitest --run test/transcript-tool-changes.test.ts test/validation.test.ts test/anthropic-mid-conversation-effort.test.ts test/openai-codex-stream.test.ts\n\n\n RUN v4.1.11 /Users/daidong/Documents/gitrepos/pi-python/reference/pi/packages/ai\n\n\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\n\n Test Files 4 passed (4)\n Tests 70 passed (70)\n Start at
|
|
124
|
+
"seconds": 1.3067305411677808,
|
|
125
|
+
"stdout": "\n> @earendil-works/pi-ai@1.0.0 test\n> vitest --run test/transcript-tool-changes.test.ts test/validation.test.ts test/anthropic-mid-conversation-effort.test.ts test/openai-codex-stream.test.ts\n\n\n RUN v4.1.11 /Users/daidong/Documents/gitrepos/pi-python/reference/pi/packages/ai\n\n\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\n\n Test Files 4 passed (4)\n Tests 70 passed (70)\n Start at 12:01:39\n Duration 992ms (transform 1.03s, setup 0ms, import 1.86s, tests 252ms, environment 0ms)\n\n",
|
|
126
126
|
"stderr": ""
|
|
127
127
|
},
|
|
128
128
|
{
|
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
"scripts/conformance.py"
|
|
134
134
|
],
|
|
135
135
|
"exit_code": 0,
|
|
136
|
-
"seconds":
|
|
136
|
+
"seconds": 5.7676790410187095,
|
|
137
137
|
"stdout": "C01-text: passed []\nC02-tool: passed []\nC03-sequential: passed []\nC04-parallel: passed []\nC05-one-sequential: passed []\nC06-one-failure: passed []\nC07-unknown: passed []\nC08-prepare: passed []\nC09-before-error: passed []\nC09-block: passed []\nC09-replace: passed []\nC10-length: passed []\nC11-transform: passed []\nC12-system: passed []\nC13-steering: passed []\nC14-followup-all: passed []\nC14-followup-one: passed []\nC15-prepare: passed []\nC16-continue: passed []\nC16-end: passed []\nC16-mixed: passed []\nC16-terminate: passed []\nC19-abort-stream: passed []\nC19-abort-tools: passed []\nC24-provider-error: passed []\n",
|
|
138
138
|
"stderr": ""
|
|
139
139
|
},
|
|
@@ -145,7 +145,7 @@
|
|
|
145
145
|
"scripts/provider_conformance.py"
|
|
146
146
|
],
|
|
147
147
|
"exit_code": 0,
|
|
148
|
-
"seconds":
|
|
148
|
+
"seconds": 15.009326208150014,
|
|
149
149
|
"stdout": "anthropic-oauth.json passed\nanthropic-request-cache-long.json passed\nanthropic-request-cache-none.json passed\nanthropic-request-replay.json passed\nanthropic-request-tools-image.json passed\nanthropic-text.json passed\nanthropic-thinking.json passed\nanthropic-tool.json passed\ncodex-request-tools-image.json passed\ncodex-text.json passed\ncompletions-compat-strict.json passed\ncompletions-deepseek.json passed\ncompletions-format-ant-ling.json passed\ncompletions-format-baseten.json passed\ncompletions-format-string-thinking.json passed\ncompletions-format-together.json passed\ncompletions-format-zai.json passed\ncompletions-midconvo-tools.json passed\ncompletions-moonshot-usage.json passed\ncompletions-openai-off.json passed\ncompletions-openrouter.json passed\ncompletions-qwen-length.json passed\ncompletions-reasoning-content.json passed\ncompletions-request-replay.json passed\ncompletions-text.json passed\ncompletions-tool-stream.json passed\nopenai-incomplete.json passed\nopenai-request-cache-long.json passed\nopenai-request-cache-none.json passed\nopenai-request-reasoning.json passed\nopenai-request-replay.json passed\nopenai-request-tools-image.json passed\nopenai-text.json passed\nopenai-thinking-backfill.json passed\nopenai-thinking.json passed\nopenai-tool.json passed\nproxy-text.json passed\nproxy-thinking-tool.json passed\nsession-anthropic-collapse-budget.json passed\nsession-anthropic-native-tools.json passed\nsession-anthropic-oauth-effort.json passed\nsession-anthropic-redefinition.json passed\nsession-anthropic-server-fallback.json passed\nsession-anthropic-thinking-off.json passed\nsession-codex-tool-search.json passed\nsession-openai-additional-tools.json passed\nsession-openai-collapse.json passed\nsession-openai-explicit-cache-long.json passed\nsession-openai-explicit-cache-none.json passed\nsession-openai-removal.json passed\n",
|
|
150
150
|
"stderr": ""
|
|
151
151
|
},
|
|
@@ -157,7 +157,7 @@
|
|
|
157
157
|
"scripts/websocket_conformance.py"
|
|
158
158
|
],
|
|
159
159
|
"exit_code": 0,
|
|
160
|
-
"seconds": 0.
|
|
160
|
+
"seconds": 0.9335567080415785,
|
|
161
161
|
"stdout": "codex-continuation-lost-state.json passed\ncodex-continuation-tool-loop.json passed\n",
|
|
162
162
|
"stderr": ""
|
|
163
163
|
},
|
|
@@ -169,7 +169,7 @@
|
|
|
169
169
|
"scripts/recovery_conformance.py"
|
|
170
170
|
],
|
|
171
171
|
"exit_code": 0,
|
|
172
|
-
"seconds": 0.
|
|
172
|
+
"seconds": 0.628341041970998,
|
|
173
173
|
"stdout": "recovery classifiers: 44/44 match upstream \n",
|
|
174
174
|
"stderr": ""
|
|
175
175
|
},
|
|
@@ -181,8 +181,8 @@
|
|
|
181
181
|
"scripts/plugin_conformance.py"
|
|
182
182
|
],
|
|
183
183
|
"exit_code": 0,
|
|
184
|
-
"seconds":
|
|
185
|
-
"stdout": "....... [100%]\n7 passed in 0.
|
|
184
|
+
"seconds": 1.306608916958794,
|
|
185
|
+
"stdout": "....... [100%]\n7 passed in 0.74s\n",
|
|
186
186
|
"stderr": ""
|
|
187
187
|
},
|
|
188
188
|
{
|
|
@@ -193,10 +193,22 @@
|
|
|
193
193
|
"examples/plugin_demo.py"
|
|
194
194
|
],
|
|
195
195
|
"exit_code": 0,
|
|
196
|
-
"seconds": 0.
|
|
196
|
+
"seconds": 0.576676958007738,
|
|
197
197
|
"stdout": "loaded: lab_tools 0.1.0\ncheck: counts_duplicates passed\nprompt: Find duplicate events in events.csv using a 30 second window. Read the dedup-window skill first.\ntools used: ['read_skill', 'subagent']\nanswer: The reviewer found 2 duplicate rows.\n",
|
|
198
198
|
"stderr": ""
|
|
199
199
|
},
|
|
200
|
+
{
|
|
201
|
+
"command": [
|
|
202
|
+
"uv",
|
|
203
|
+
"run",
|
|
204
|
+
"python",
|
|
205
|
+
"examples/mcp_interactive.py"
|
|
206
|
+
],
|
|
207
|
+
"exit_code": 0,
|
|
208
|
+
"seconds": 1.8445827078539878,
|
|
209
|
+
"stdout": "Model task=demo-run request=1 profile=default attempt=1 usage=None\nModel task=demo-run request=2 profile=default attempt=1 usage=None\nModel task=demo-run request=3 profile=json attempt=1 usage={'input': 4, 'output': 3, 'cacheRead': 1}\nModel task=demo-run request=4 profile=text attempt=1 usage={'input': 4, 'output': 3, 'cacheRead': 1}\nForm from business: Choose output for report\nDemo user chooses: short, confirmed\n{\"label\": \"report\", \"model_reply\": \"[report] [ready]\", \"action\": \"accept\", \"selection\": {\"style\": \"short\", \"confirmed\": true}, \"auxiliary\": {\"json\": {\"ok\": true}, \"text\": \"Ready\"}}\n",
|
|
210
|
+
"stderr": ""
|
|
211
|
+
},
|
|
200
212
|
{
|
|
201
213
|
"command": [
|
|
202
214
|
"uv",
|
|
@@ -206,7 +218,7 @@
|
|
|
206
218
|
"--help"
|
|
207
219
|
],
|
|
208
220
|
"exit_code": 0,
|
|
209
|
-
"seconds": 0.
|
|
221
|
+
"seconds": 0.6522026660386473,
|
|
210
222
|
"stdout": "usage: provider_chat.py [-h]\n --provider {anthropic,openai,openai-codex,openai-chatgpt,deepseek}\n --model MODEL [--prompt PROMPT]\n [--credential-file CREDENTIAL_FILE] [--login]\n [--device]\n [--thinking {off,minimal,low,medium,high,xhigh,max}]\n [--transport {sse,websocket,websocket-cached,auto}]\n [--session-id SESSION_ID]\n\nExplicit API/OAuth example. Invoke --help; never imported by the core.\n\noptions:\n -h, --help show this help message and exit\n --provider {anthropic,openai,openai-codex,openai-chatgpt,deepseek}\n --model MODEL Model ID enabled for this account\n --prompt PROMPT\n --credential-file CREDENTIAL_FILE\n App-owned OAuth file (never reuse another client file)\n --login\n --device Codex device authorization instead of browser callback\n --thinking {off,minimal,low,medium,high,xhigh,max}\n --transport {sse,websocket,websocket-cached,auto}\n --session-id SESSION_ID\n Required for websocket-cached\n",
|
|
211
223
|
"stderr": ""
|
|
212
224
|
},
|
|
@@ -216,9 +228,9 @@
|
|
|
216
228
|
"build"
|
|
217
229
|
],
|
|
218
230
|
"exit_code": 0,
|
|
219
|
-
"seconds": 1.
|
|
231
|
+
"seconds": 1.0952744169626385,
|
|
220
232
|
"stdout": "",
|
|
221
|
-
"stderr": "Building source distribution...\nBuilding wheel from source distribution...\nSuccessfully built dist/pi_python_core-0.
|
|
233
|
+
"stderr": "Building source distribution...\nBuilding wheel from source distribution...\nSuccessfully built dist/pi_python_core-0.10.1.tar.gz\nSuccessfully built dist/pi_python_core-0.10.1-py3-none-any.whl\n"
|
|
222
234
|
}
|
|
223
235
|
],
|
|
224
236
|
"status": "passed"
|
|
@@ -311,6 +311,62 @@ Several plugins can handle the same hook. Handlers run in this order: your own h
|
|
|
311
311
|
|
|
312
312
|
When a plugin's handler raises an exception or returns the wrong type, a `PluginFailure` report goes to the `on_error` callback of `load_plugins`, which may be sync or async and logs the failure by default. That handler's contribution is skipped and the other handlers still run, so one faulty plugin does not stop the agent. The exception is `before_tool_call`, where an error fails that tool call, as in Pi. Your own hooks keep the core behavior described in the [API](API.md#hooks).
|
|
313
313
|
|
|
314
|
+
A handler that cannot accept the arguments its hook passes, such as a `transform_context` handler written as `def inject(messages)` instead of `def inject(messages, cancel)`, is rejected when it is registered: `api.on` raises `ConfigurationError` naming the expected arguments, and loading stops. The same check applies to `subscribe` (one argument, the event), `add_check` and `on_close` (no arguments). Otherwise such a handler would fail on every call and only be logged.
|
|
315
|
+
|
|
316
|
+
## Calling a model from a plugin
|
|
317
|
+
|
|
318
|
+
The application owns providers and credentials, so a plugin has no model of its own. A plugin that needs a model call, for example to summarize or to merge notes, asks for the Provider as the `"provider"` service and reads the model name from its options:
|
|
319
|
+
|
|
320
|
+
```python
|
|
321
|
+
from pi_python import Agent, ToolContext
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def setup(api):
|
|
325
|
+
provider = api.service("provider")
|
|
326
|
+
|
|
327
|
+
async def summarize(text: str, context: ToolContext) -> str:
|
|
328
|
+
"""Summarize a text in three sentences.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
text: The text to summarize.
|
|
332
|
+
"""
|
|
333
|
+
writer = Agent(
|
|
334
|
+
provider=provider,
|
|
335
|
+
model=api.options.get("model") or context.agent_context.model,
|
|
336
|
+
system_prompt="Summarize the text in three sentences.",
|
|
337
|
+
)
|
|
338
|
+
result = await context.run_agent(writer, text) # aborting the run aborts this call
|
|
339
|
+
reply = "".join(getattr(block, "text", "") for block in result.messages[-1].content)
|
|
340
|
+
if result.status != "completed" or not reply:
|
|
341
|
+
raise RuntimeError(f"The summary failed: {result.status}")
|
|
342
|
+
return reply
|
|
343
|
+
|
|
344
|
+
api.add_tool(summarize)
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
The application passes `services={"provider": provider}`, and `options={"summary": {"model": "claude-haiku-4-5"}}` to use a cheaper model for the plugin named `summary`. Without the option, the call uses the model of the agent that called the tool. Inside a tool, run the call with `context.run_agent`, so that aborting the main run also aborts it. When the model call is optional, use `api.service("provider", default=None)` and say in the plugin's documentation what happens without it.
|
|
348
|
+
|
|
349
|
+
## Memory across conversations
|
|
350
|
+
|
|
351
|
+
Neither this library nor Pi's core has built-in memory. Pi's application remembers through instruction files it reads into the system prompt (`AGENTS.md`), saved sessions and compaction. What is worth remembering, and where to keep it, differs between applications. A plugin can add memory with two parts: tools that save facts to storage the application chooses, and a `transform_context` handler that puts the saved facts into every request.
|
|
352
|
+
|
|
353
|
+
[examples/plugins/memory](../examples/plugins/memory) does this in one file, and [examples/memory_demo.py](../examples/memory_demo.py) runs it offline:
|
|
354
|
+
|
|
355
|
+
```python
|
|
356
|
+
async with load_plugins(
|
|
357
|
+
["./examples/plugins/memory"],
|
|
358
|
+
services={"provider": provider}, # optional: merges facts when the list is full
|
|
359
|
+
options={"memory": {"path": "memory.json", "max_facts": 50}},
|
|
360
|
+
) as plugins:
|
|
361
|
+
agent = plugins.agent(provider=provider, model="claude-sonnet-5-5")
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
- The model saves a fact with `remember` and removes one with `forget`. The facts are kept in the JSON file.
|
|
365
|
+
- Before every request, the facts become the `memory` section of the system prompt (`SystemMessage.sections`). The stored conversation does not change. The section changes only when the facts do, so the cached prompt prefix stays valid in between.
|
|
366
|
+
- When a new fact would make the list longer than `max_facts`, the plugin asks a model to merge the list, as in [Calling a model from a plugin](#calling-a-model-from-a-plugin). Without a provider, the oldest facts are dropped.
|
|
367
|
+
|
|
368
|
+
Copy the directory and change what your application needs: what is worth saving, retrieval when there are too many facts to list, or a database in place of the file. The example assumes that one process writes the file at a time.
|
|
369
|
+
|
|
314
370
|
## Differences from Pi
|
|
315
371
|
|
|
316
372
|
Pi's command-line application finds and loads packages; this library has no application, so your code names the plugins to load. The other differences:
|
|
@@ -258,6 +258,7 @@ install. Do not claim packaging is verified merely because directory loading wor
|
|
|
258
258
|
| Async work tied to plugin lifetime | Create `scope = api.task_scope()` after registering resources it uses. Await `scope.run(client.fetch(...), cancel=context.cancel)` in an async tool with a `ToolContext` parameter. Declare `task-scope-v1` if using it. |
|
|
259
259
|
| Worker thread needs a loop-bound connection | Host creates a `LoopPortal` on the connection's loop and passes it as a service. The synchronous tool uses `portal.call(async_function, ...)`. See [API](API.md#owned-tasks-and-calls-from-worker-threads). |
|
|
260
260
|
| A hook | Register `@api.on("before_tool_call")`; return `False` to block. Use [documented hook signatures](API.md#hooks), and test the blocked path. |
|
|
261
|
+
| Model call inside the plugin | Ask for `provider = api.service("provider")`; take the model from `api.options.get("model")`, else `context.agent_context.model`. In an async tool, run `await context.run_agent(Agent(provider=provider, model=...), text)`. See [Calling a model from a plugin](PLUGINS.md#calling-a-model-from-a-plugin). |
|
|
261
262
|
| Subagent | Add `agents/reviewer.md` with `name`, `description`, optional `tools`/`model`, and a system-prompt body. See the [lab_tools example](../examples/plugins/lab_tools). |
|
|
262
263
|
| MCP server | Add `mcp.json` or call `api.add_mcp_server`. Install `[mcp]`; specify required servers/tools in readiness. Use the [interactive workflow](MCP_INTERACTION.md) for host model/form callbacks. |
|
|
263
264
|
|
|
@@ -300,6 +300,62 @@ async with load_plugins(["sandbox-plugin"], options=options, strict=True) as plu
|
|
|
300
300
|
|
|
301
301
|
插件的处理函数抛出异常或返回了错误的类型时,一份 `PluginFailure` 报告交给 `load_plugins` 的 `on_error` 回调;回调可以是普通函数或 async 函数,默认写进日志。这个处理函数的结果被跳过,其他处理函数照常运行,所以一个有问题的插件不会让 agent 停下。`before_tool_call` 例外:和 Pi 一样,那里的错误会让这次工具调用失败。你自己的钩子保持核心原来的行为,见 [API](API.md#钩子)。
|
|
302
302
|
|
|
303
|
+
处理函数接收不了钩子传入的参数时,注册那一刻就会被拒绝。例如把 `transform_context` 的处理函数写成 `def inject(messages)`,少了 `cancel`:`api.on` 抛出 `ConfigurationError`,写明应接收哪些参数,加载随之停止。`subscribe`(一个参数,即事件)、`add_check` 和 `on_close`(没有参数)也做同样的检查。如果不在注册时检查,这样的处理函数每次调用都会失败,却只留下一行日志。
|
|
304
|
+
|
|
305
|
+
## 在插件里调用模型
|
|
306
|
+
|
|
307
|
+
Provider 和凭据归应用所有,插件自己没有模型。插件需要调用模型时,比如做摘要或合并笔记,通过 `"provider"` 服务取得 Provider,从自己的 options 里读模型名:
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
from pi_python import Agent, ToolContext
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def setup(api):
|
|
314
|
+
provider = api.service("provider")
|
|
315
|
+
|
|
316
|
+
async def summarize(text: str, context: ToolContext) -> str:
|
|
317
|
+
"""Summarize a text in three sentences.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
text: The text to summarize.
|
|
321
|
+
"""
|
|
322
|
+
writer = Agent(
|
|
323
|
+
provider=provider,
|
|
324
|
+
model=api.options.get("model") or context.agent_context.model,
|
|
325
|
+
system_prompt="Summarize the text in three sentences.",
|
|
326
|
+
)
|
|
327
|
+
result = await context.run_agent(writer, text) # 中止主运行时,这次调用一起中止
|
|
328
|
+
reply = "".join(getattr(block, "text", "") for block in result.messages[-1].content)
|
|
329
|
+
if result.status != "completed" or not reply:
|
|
330
|
+
raise RuntimeError(f"The summary failed: {result.status}")
|
|
331
|
+
return reply
|
|
332
|
+
|
|
333
|
+
api.add_tool(summarize)
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
应用传入 `services={"provider": provider}`;想让名为 `summary` 的插件用便宜一些的模型,再传 `options={"summary": {"model": "claude-haiku-4-5"}}`。不给这个选项时,用调用这个工具的 agent 的模型。在工具里用 `context.run_agent` 运行这次调用,这样中止主运行时它也会中止。模型调用可有可无时,用 `api.service("provider", default=None)`,并在插件文档里写明没有 provider 时会怎样。
|
|
337
|
+
|
|
338
|
+
## 跨对话的记忆
|
|
339
|
+
|
|
340
|
+
本库和 Pi 的核心都没有内置记忆。Pi 的应用靠三样东西记住信息:读进系统提示的说明文件(`AGENTS.md`)、保存的会话和上下文压缩。哪些信息值得记、存在哪里,各个应用差别很大。插件可以用两部分加上记忆:把事实存进应用指定位置的工具,以及一个 `transform_context` 处理函数,在每次请求时把已存的事实放进去。
|
|
341
|
+
|
|
342
|
+
[examples/plugins/memory](../../examples/plugins/memory) 用一个文件实现了这种做法,[examples/memory_demo.py](../../examples/memory_demo.py) 离线运行它:
|
|
343
|
+
|
|
344
|
+
```python
|
|
345
|
+
async with load_plugins(
|
|
346
|
+
["./examples/plugins/memory"],
|
|
347
|
+
services={"provider": provider}, # 可选:列表满了时用来合并事实
|
|
348
|
+
options={"memory": {"path": "memory.json", "max_facts": 50}},
|
|
349
|
+
) as plugins:
|
|
350
|
+
agent = plugins.agent(provider=provider, model="claude-sonnet-5-5")
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
- 模型用 `remember` 保存一条事实,用 `forget` 删除一条。事实保存在 JSON 文件里。
|
|
354
|
+
- 每次请求之前,这些事实成为系统提示里名为 `memory` 的一段(`SystemMessage.sections`)。保存的对话不变。只有事实变化时这一段才变,所以两次变化之间,提示缓存的前缀一直有效。
|
|
355
|
+
- 新事实会让列表超过 `max_facts` 时,插件请模型合并列表,做法见[在插件里调用模型](#在插件里调用模型)。没有 provider 时,删掉最早的事实。
|
|
356
|
+
|
|
357
|
+
复制这个目录,再按应用的需要修改:哪些信息值得存,事实太多、无法全部列出时怎样检索,或者用数据库代替文件。这个示例假定同一时间只有一个进程写这个文件。
|
|
358
|
+
|
|
303
359
|
## 与 Pi 的差别
|
|
304
360
|
|
|
305
361
|
Pi 的命令行程序负责查找和加载 package;本库没有应用程序,所以要加载哪些插件由你的代码指定。其他差别:
|
|
@@ -222,6 +222,7 @@ Windows 下把 `.venv-installed/bin/python` 换成 `.venv-installed/Scripts/pyth
|
|
|
222
222
|
| 与插件生命周期绑定的异步工作 | 先注册所用资源的清理,再创建 `scope = api.task_scope()`。带 `ToolContext` 参数的异步工具中,用 `await scope.run(client.fetch(...), cancel=context.cancel)` 调用。使用时声明 `task-scope-v1`。 |
|
|
223
223
|
| 工作线程访问绑定事件循环的连接 | 宿主在连接所属循环创建 `LoopPortal`,通过 service 传入。同步工具用 `portal.call(async_function, ...)`。见 [API](API.md)。 |
|
|
224
224
|
| 钩子 | 注册 `@api.on("before_tool_call")`,返回 `False` 阻止调用。按[文档签名](API.md)实现,并验证被阻止的路径。 |
|
|
225
|
+
| 插件内调用模型 | 用 `provider = api.service("provider")` 取得 Provider;模型取 `api.options.get("model")`,没有时用 `context.agent_context.model`。在异步工具里执行 `await context.run_agent(Agent(provider=provider, model=...), text)`。见[在插件里调用模型](PLUGINS.md#在插件里调用模型)。 |
|
|
225
226
|
| 子 Agent | 添加 `agents/reviewer.md`,包含 `name`、`description`、可选 `tools`/`model` 和系统提示正文。见 [lab_tools 示例](../../examples/plugins/lab_tools)。 |
|
|
226
227
|
| MCP 服务 | 添加 `mcp.json` 或调用 `api.add_mcp_server`。安装 `[mcp]`,并在 readiness 中要求对应服务和工具。宿主模型或表单回调按 [MCP 交互流程](MCP_INTERACTION.md)配置。 |
|
|
227
228
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Long-term memory with a plugin: a fact saved in one conversation is used in the next.
|
|
2
|
+
|
|
3
|
+
The plugin in examples/plugins/memory keeps facts in a JSON file and puts them in the
|
|
4
|
+
system prompt of every request. Each conversation below opens its own plugin set, as a
|
|
5
|
+
new process would. The model is scripted so this runs offline.
|
|
6
|
+
|
|
7
|
+
python examples/memory_demo.py
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import json
|
|
12
|
+
import tempfile
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from pi_python import AssistantMessage, ScriptedProvider, ToolCall
|
|
16
|
+
from pi_python.plugins import load_plugins
|
|
17
|
+
|
|
18
|
+
PLUGIN = Path(__file__).parent / "plugins" / "memory"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def call(tool: str, **args) -> AssistantMessage:
|
|
22
|
+
return AssistantMessage([ToolCall(f"call-{tool}", tool, args)], "tool_use")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def conversation(provider: ScriptedProvider, options: dict, text: str) -> None:
|
|
26
|
+
# The plugin merges facts with a model call when its list is full; it gets the
|
|
27
|
+
# Provider as a service, so the application decides which models plugins may use.
|
|
28
|
+
async with load_plugins([PLUGIN], services={"provider": provider}, options=options) as plugins:
|
|
29
|
+
agent = plugins.agent(provider=provider, system_prompt="You are a project assistant.")
|
|
30
|
+
result = await agent.prompt(text)
|
|
31
|
+
assert result.status == "completed", result.errors
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async def main() -> None:
|
|
35
|
+
path = Path(tempfile.mkdtemp()) / "memory.json"
|
|
36
|
+
options = {"memory": {"path": str(path), "max_facts": 2}}
|
|
37
|
+
|
|
38
|
+
first = ScriptedProvider(
|
|
39
|
+
[call("remember", fact="The paper deadline is Friday."), AssistantMessage.text("Noted.")]
|
|
40
|
+
)
|
|
41
|
+
await conversation(first, options, "Remember: the paper deadline is Friday.")
|
|
42
|
+
print("saved:", json.loads(path.read_text(encoding="utf-8"))["facts"])
|
|
43
|
+
|
|
44
|
+
second = ScriptedProvider(
|
|
45
|
+
[
|
|
46
|
+
call("remember", fact="The paper deadline moved to Monday."),
|
|
47
|
+
call("remember", fact="Use the ACM template."),
|
|
48
|
+
# The list would hold 3 facts, more than max_facts, so the plugin asks the
|
|
49
|
+
# model to merge them; this reply is that merge.
|
|
50
|
+
AssistantMessage.text("The paper deadline is Monday.\nUse the ACM template."),
|
|
51
|
+
AssistantMessage.text("Noted: Monday, ACM template."),
|
|
52
|
+
]
|
|
53
|
+
)
|
|
54
|
+
await conversation(second, options, "The deadline moved to Monday. Also use the ACM template.")
|
|
55
|
+
system = second.requests[0].messages[0]
|
|
56
|
+
print("memory in the next conversation's system prompt:")
|
|
57
|
+
print(" " + system.sections["memory"].replace("\n", "\n "))
|
|
58
|
+
print("after merging:", json.loads(path.read_text(encoding="utf-8"))["facts"])
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
if __name__ == "__main__":
|
|
62
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""An example plugin: facts saved in one conversation are known in the next.
|
|
2
|
+
|
|
3
|
+
The facts live in the JSON file named by the `path` option. The model saves a fact with
|
|
4
|
+
`remember` and removes a wrong or outdated one with `forget`. Before every model request,
|
|
5
|
+
the facts become the "memory" section of the system prompt; the stored conversation is
|
|
6
|
+
not changed. The section changes only when the facts do, so the cached prompt prefix
|
|
7
|
+
stays valid between changes.
|
|
8
|
+
|
|
9
|
+
When a new fact would make the list longer than `max_facts` (default 50), `remember`
|
|
10
|
+
asks a model to merge the list. That call uses the Provider the application passes as
|
|
11
|
+
the "provider" service, with the model named by the `model` option, or else the calling
|
|
12
|
+
agent's model. Without a provider, or when merging does not shorten the list, the oldest
|
|
13
|
+
facts are dropped.
|
|
14
|
+
|
|
15
|
+
The plugin does not decide what is worth remembering beyond what the model saves, and
|
|
16
|
+
it assumes one process writes the file at a time. Adapt both to your application.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import asyncio
|
|
20
|
+
import json
|
|
21
|
+
import os
|
|
22
|
+
import re
|
|
23
|
+
import tempfile
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
from pi_python import Agent, ConfigurationError, SystemMessage, ToolContext
|
|
27
|
+
|
|
28
|
+
__version__ = "0.1.0"
|
|
29
|
+
|
|
30
|
+
MERGE_PROMPT = (
|
|
31
|
+
"You maintain a list of saved facts. Merge facts that say the same thing and drop"
|
|
32
|
+
" facts that a later fact makes outdated. Reply with the remaining facts, one per"
|
|
33
|
+
" line, and nothing else."
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def setup(api):
|
|
38
|
+
if "path" not in api.options:
|
|
39
|
+
raise ConfigurationError(
|
|
40
|
+
f"The memory plugin needs a file: options={{{api.name!r}: {{'path': ...}}}}"
|
|
41
|
+
)
|
|
42
|
+
path = Path(api.options["path"]).expanduser()
|
|
43
|
+
max_facts = api.options.get("max_facts", 50)
|
|
44
|
+
if type(max_facts) is not int or max_facts < 1:
|
|
45
|
+
raise ConfigurationError("max_facts must be a positive integer")
|
|
46
|
+
provider = api.service("provider", default=None)
|
|
47
|
+
facts: list[str] = load(path)
|
|
48
|
+
lock = asyncio.Lock() # tool calls can run in parallel
|
|
49
|
+
|
|
50
|
+
async def shorten(updated: list[str], context: ToolContext) -> list[str]:
|
|
51
|
+
if provider is not None:
|
|
52
|
+
merger = Agent(
|
|
53
|
+
provider=provider,
|
|
54
|
+
model=api.options.get("model") or context.agent_context.model,
|
|
55
|
+
system_prompt=MERGE_PROMPT,
|
|
56
|
+
)
|
|
57
|
+
# run_agent ties the call to this tool call: aborting the run aborts it.
|
|
58
|
+
result = await context.run_agent(merger, "\n".join(updated))
|
|
59
|
+
reply = "".join(getattr(b, "text", "") for b in result.messages[-1].content)
|
|
60
|
+
merged = [re.sub(r"^\s*(?:[-*]|\d+[.)])\s*", "", line) for line in reply.splitlines()]
|
|
61
|
+
merged = [line for line in merged if line]
|
|
62
|
+
if result.status == "completed" and 0 < len(merged) < len(updated):
|
|
63
|
+
updated = merged
|
|
64
|
+
return updated[-max_facts:]
|
|
65
|
+
|
|
66
|
+
async def remember(fact: str, context: ToolContext) -> str:
|
|
67
|
+
"""Save a fact that should still be known in later conversations.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
fact: One short, self-contained statement, such as "The paper deadline is Friday".
|
|
71
|
+
"""
|
|
72
|
+
fact = " ".join(fact.split())
|
|
73
|
+
if not fact:
|
|
74
|
+
return "Nothing saved: the fact is empty."
|
|
75
|
+
async with lock:
|
|
76
|
+
if fact in facts:
|
|
77
|
+
return f"Already saved as fact {facts.index(fact) + 1}."
|
|
78
|
+
updated = [*facts, fact]
|
|
79
|
+
if len(updated) > max_facts:
|
|
80
|
+
updated = await shorten(updated, context)
|
|
81
|
+
save(path, updated)
|
|
82
|
+
facts[:] = updated
|
|
83
|
+
return f"Saved. Facts in memory: {len(facts)}."
|
|
84
|
+
|
|
85
|
+
async def forget(number: int) -> str:
|
|
86
|
+
"""Remove a saved fact that is wrong or outdated.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
number: The fact's number in the memory section of the system prompt.
|
|
90
|
+
"""
|
|
91
|
+
async with lock:
|
|
92
|
+
if not 1 <= number <= len(facts):
|
|
93
|
+
return f"There is no fact {number}. Facts in memory: {len(facts)}."
|
|
94
|
+
removed = facts[number - 1]
|
|
95
|
+
updated = facts[: number - 1] + facts[number:]
|
|
96
|
+
save(path, updated) # change the list only after the file is written
|
|
97
|
+
facts[:] = updated
|
|
98
|
+
return f"Removed: {removed}"
|
|
99
|
+
|
|
100
|
+
api.add_tool(remember)
|
|
101
|
+
api.add_tool(forget)
|
|
102
|
+
api.add_system_prompt(
|
|
103
|
+
"When the user states a fact or preference that should be known in later"
|
|
104
|
+
" conversations, save it with remember. Remove saved facts that turn out wrong"
|
|
105
|
+
" with forget."
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
@api.on("transform_context")
|
|
109
|
+
def show_memory(messages, cancel):
|
|
110
|
+
# The messages are a copy for this request; changing them changes only what the
|
|
111
|
+
# model receives. Agents from a plugin set have a leading system message.
|
|
112
|
+
if not facts or not messages or not isinstance(messages[0], SystemMessage):
|
|
113
|
+
return None
|
|
114
|
+
listed = "\n".join(f"{i}. {fact}" for i, fact in enumerate(facts, 1))
|
|
115
|
+
messages[0].sections["memory"] = (
|
|
116
|
+
"Facts saved in earlier conversations, numbered for forget:\n" + listed
|
|
117
|
+
)
|
|
118
|
+
return messages
|
|
119
|
+
|
|
120
|
+
api.add_check(lambda: path.parent.is_dir(), "directory_exists")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def load(path: Path) -> list[str]:
|
|
124
|
+
if not path.exists():
|
|
125
|
+
return []
|
|
126
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
127
|
+
facts = data.get("facts") if isinstance(data, dict) else None
|
|
128
|
+
if not isinstance(facts, list) or not all(isinstance(f, str) for f in facts):
|
|
129
|
+
raise ConfigurationError(f"{path}: facts must be a list of strings")
|
|
130
|
+
return facts
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def save(path: Path, facts: list[str]) -> None:
|
|
134
|
+
"""Write atomically, so a crash never leaves half a file."""
|
|
135
|
+
fd, temporary = tempfile.mkstemp(dir=path.parent, prefix=f".{path.name}.")
|
|
136
|
+
with os.fdopen(fd, "w", encoding="utf-8") as file:
|
|
137
|
+
json.dump({"facts": facts}, file, ensure_ascii=False, indent=2)
|
|
138
|
+
os.replace(temporary, path)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.10.1"
|