pi-python-core 0.10.1__tar.gz → 0.10.2__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.1 → pi_python_core-0.10.2}/PKG-INFO +1 -1
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/COVERAGE.md +14 -1
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/docs/API.md +32 -3
- pi_python_core-0.10.2/docs/APPLICATION_TOOLS_IMPLEMENTATION.md +76 -0
- pi_python_core-0.10.2/docs/releases/0.10.2.md +52 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/docs/zh/API.md +32 -3
- pi_python_core-0.10.2/examples/capability_composition.py +149 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/pyproject.toml +1 -1
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/__init__.py +2 -0
- pi_python_core-0.10.2/src/pi_python/_version.py +1 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/agent.py +53 -4
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/events.py +3 -4
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/features.py +1 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/loop.py +36 -6
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/messages.py +57 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/run.py +91 -11
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/tools.py +39 -2
- pi_python_core-0.10.2/tests/test_application_compatibility.py +207 -0
- pi_python_core-0.10.2/tests/test_application_tools.py +508 -0
- pi_python_core-0.10.2/tests/test_capability_composition.py +59 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_examples.py +1 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_features.py +7 -1
- pi_python_core-0.10.1/src/pi_python/_version.py +0 -1
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/.gitignore +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/LICENSE +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/NOTICE +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/README.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/README.zh-CN.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/__init__.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/baseline.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/contracts/release-candidate.example.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/contracts/release-candidate.schema.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/contracts/upstream-baseline.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/fixtures/C01-text.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/fixtures/C02-tool.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/fixtures/C03-sequential.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/fixtures/C04-parallel.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/fixtures/C05-one-sequential.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/fixtures/C06-one-failure.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/fixtures/C07-unknown.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/fixtures/C08-prepare.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/fixtures/C09-before-error.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/fixtures/C09-block.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/fixtures/C09-replace.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/fixtures/C10-length.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/fixtures/C11-transform.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/fixtures/C12-system.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/fixtures/C13-steering.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/fixtures/C14-followup-all.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/fixtures/C14-followup-one.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/fixtures/C15-prepare.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/fixtures/C16-continue.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/fixtures/C16-end.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/fixtures/C16-mixed.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/fixtures/C16-terminate.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/fixtures/C19-abort-stream.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/fixtures/C19-abort-tools.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/fixtures/C24-provider-error.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/plugin-cases.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/plugin-fixtures/prompts/notes.txt +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/plugin-fixtures/prompts/plan.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/plugin-fixtures/prompts/review.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/plugin-fixtures/prompts/sub/nested.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/plugin-fixtures/prompts/summarize.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/plugin-fixtures/skills/.hidden/SKILL.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/plugin-fixtures/skills/alpha/SKILL.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/plugin-fixtures/skills/alpha/references/notes.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/plugin-fixtures/skills/beta/SKILL.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/plugin-fixtures/skills/broken/SKILL.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/plugin-fixtures/skills/delta/SKILL.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/plugin-fixtures/skills/gamma/SKILL.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/plugin-fixtures/skills/longdesc/SKILL.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/plugin-fixtures/skills/loose.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/plugin-fixtures/skills/nested/group/epsilon/SKILL.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/plugin-fixtures/skills/noname/SKILL.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/plugin-fixtures/skills/plain.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/plugin-fixtures/skills/zeta/SKILL.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/plugin-fixtures/skills/zeta/sub/eta/SKILL.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/anthropic-oauth.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/anthropic-request-cache-long.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/anthropic-request-cache-none.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/anthropic-request-replay.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/anthropic-request-tools-image.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/anthropic-text.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/anthropic-thinking.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/anthropic-tool.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/codex-request-tools-image.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/codex-text.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/completions-compat-strict.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/completions-deepseek.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/completions-format-ant-ling.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/completions-format-baseten.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/completions-format-string-thinking.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/completions-format-together.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/completions-format-zai.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/completions-midconvo-tools.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/completions-moonshot-usage.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/completions-openai-off.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/completions-openrouter.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/completions-qwen-length.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/completions-reasoning-content.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/completions-request-replay.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/completions-text.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/completions-tool-stream.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/openai-incomplete.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/openai-request-cache-long.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/openai-request-cache-none.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/openai-request-reasoning.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/openai-request-replay.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/openai-request-tools-image.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/openai-text.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/openai-thinking-backfill.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/openai-thinking.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/openai-tool.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/proxy-text.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/proxy-thinking-tool.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/session-anthropic-collapse-budget.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/session-anthropic-native-tools.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/session-anthropic-oauth-effort.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/session-anthropic-redefinition.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/session-anthropic-server-fallback.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/session-anthropic-thinking-off.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/session-codex-tool-search.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/session-openai-additional-tools.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/session-openai-collapse.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/session-openai-explicit-cache-long.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/session-openai-explicit-cache-none.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/provider-fixtures/session-openai-removal.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/recovery-cases.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/release.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C01-text.python.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C01-text.upstream.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C02-tool.python.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C02-tool.upstream.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C03-sequential.python.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C03-sequential.upstream.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C04-parallel.python.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C04-parallel.upstream.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C05-one-sequential.python.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C05-one-sequential.upstream.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C06-one-failure.python.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C06-one-failure.upstream.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C07-unknown.python.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C07-unknown.upstream.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C08-prepare.python.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C08-prepare.upstream.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C09-before-error.python.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C09-before-error.upstream.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C09-block.python.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C09-block.upstream.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C09-replace.python.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C09-replace.upstream.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C10-length.python.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C10-length.upstream.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C11-transform.python.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C11-transform.upstream.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C12-system.python.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C12-system.upstream.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C13-steering.python.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C13-steering.upstream.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C14-followup-all.python.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C14-followup-all.upstream.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C14-followup-one.python.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C14-followup-one.upstream.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C15-prepare.python.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C15-prepare.upstream.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C16-continue.python.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C16-continue.upstream.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C16-end.python.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C16-end.upstream.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C16-mixed.python.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C16-mixed.upstream.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C16-terminate.python.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C16-terminate.upstream.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C19-abort-stream.python.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C19-abort-stream.upstream.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C19-abort-tools.python.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C19-abort-tools.upstream.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C24-provider-error.python.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/C24-provider-error.upstream.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/artifacts.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/benchmark.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/bootstrap.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/candidate-live-noop.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/conformance.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/linux-3.11.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/linux-3.12.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/linux-3.13.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/linux-3.14.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/linux-install-hash-repair.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/live-0.5-regression.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/live-0.5-session-claude.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/live-0.5-session-codex.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/live-claude-reasoning-replay.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/live-final-matrix.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/live-initial.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/live-loop-initial.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/live-providers.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/live-reasoning-replay.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/live-refresh.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/live-regression-0.4-deepseek.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/live-regression-0.4-first-run.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/live-regression-0.4.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/live-session-claude.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/live-session-codex.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/live-websocket-cached-recheck.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/live-websocket-cached.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/live-websocket-final.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/plugins.upstream.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/provider-conformance.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/recovery-conformance.upstream.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/runtime-dependencies.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/source-verification.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.1.0/artifacts.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.1.0/linux-3.11.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.1.0/linux-3.12.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.1.0/linux-3.13.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.1.0/verification.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.2.0/artifacts.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.2.0/linux-3.11.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.2.0/linux-3.12.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.2.0/linux-3.13.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.2.0/verification.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.3.0/against-0.4-fixtures.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.3.0/artifacts.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.3.0/linux-3.11.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.3.0/linux-3.12.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.3.0/linux-3.13.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.3.0/provider-conformance.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.3.0/verification.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.4.0/artifacts.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.4.0/linux-3.11.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.4.0/linux-3.12.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.4.0/linux-3.13.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.4.0/verification.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.5.0/artifacts.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.5.0/linux-3.11.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.5.0/linux-3.12.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.5.0/linux-3.13.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.5.0/verification.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.6.0/artifacts.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.6.0/linux-3.11.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.6.0/linux-3.12.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.6.0/linux-3.13.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.6.0/verification.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.7.0/artifacts.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.7.0/linux-3.11.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.7.0/linux-3.12.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.7.0/linux-3.13.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.7.0/linux-3.14.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.7.0/verification.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.8.0/artifacts.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.8.0/linux-3.11.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.8.0/linux-3.12.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.8.0/linux-3.13.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.8.0/linux-3.14.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/v0.8.0/verification.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/verification.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/results/websocket-conformance.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/runner.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/websocket-fixtures/codex-continuation-lost-state.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/compat/websocket-fixtures/codex-continuation-tool-loop.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/docs/CODING_AGENTS.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/docs/CONCEPTS.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/docs/IMPLEMENTATION.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/docs/MCP_INTERACTION.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/docs/PLUGINS.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/docs/PLUGIN_DEVELOPMENT.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/docs/PROVIDERS.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/docs/history/IMPLEMENTATION-0.3.0.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/docs/history/IMPLEMENTATION-0.4.0.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/docs/history/IMPLEMENTATION-0.5.0.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/docs/history/IMPLEMENTATION-0.6.0.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/docs/history/IMPLEMENTATION-0.7.0.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/docs/history/IMPLEMENTATION-0.8.0.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/docs/zh/CODING_AGENTS.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/docs/zh/CONCEPTS.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/docs/zh/MCP_HOST_VERIFICATION.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/docs/zh/MCP_INTERACTION.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/docs/zh/PLUGINS.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/docs/zh/PLUGIN_DEVELOPMENT.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/docs/zh/PROVIDERS.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/examples/in_memory.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/examples/local_model.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/examples/mcp_interactive.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/examples/mcp_interactive_server.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/examples/mcp_tools.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/examples/memory_demo.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/examples/plugin_demo.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/examples/plugins/lab_tools/agents/reviewer.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/examples/plugins/lab_tools/ops.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/examples/plugins/lab_tools/plugin.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/examples/plugins/lab_tools/prompts/dedup.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/examples/plugins/lab_tools/skills/dedup-window/SKILL.md +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/examples/plugins/memory/plugin.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/examples/provider_chat.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/examples/quickstart.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/examples/recovery.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/examples/save_restore.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/examples/subagent.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/_mcp_host.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/_mcp_interaction.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/_mcp_process.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/_mcp_sampling.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/cancellation.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/data/models.json +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/errors.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/estimate.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/function_tools.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/hooks.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/limits.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/lowlevel.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/mcp.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/models.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/plugins/__init__.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/plugins/_compose.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/plugins/_frontmatter.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/plugins/_resources.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/plugins/_subagents.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/provider.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/providers/__init__.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/providers/anthropic.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/providers/common.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/providers/completions.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/providers/oauth.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/providers/openai.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/providers/transport.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/proxy.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/py.typed +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/queues.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/recovery.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/stream.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/sync.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/tasks.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/testing.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/src/pi_python/transcript.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/servers/mcp_interactive_server.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/servers/mcp_process_server.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/servers/mcp_server.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_completions.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_conformance.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_contract_v3.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_control.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_extended_core.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_function_tools.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_lifecycle_contracts.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_loop.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_mcp.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_mcp_delta.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_mcp_host.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_mcp_interaction.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_mcp_process.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_messages.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_models.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_nested_agents.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_oauth.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_plugin_conformance.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_plugins.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_providers.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_recovery.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_recovery_conformance.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_recovery_helpers.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_release.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_session_changes.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_sync.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_tasks.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_tools.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_typeddict_annotations.py +0 -0
- {pi_python_core-0.10.1 → pi_python_core-0.10.2}/tests/test_websocket_continuation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 验收映射
|
|
2
2
|
|
|
3
|
-
首期测试证明的是本文列出的文本和工具行为,不是完整 Pi 或模型智能。
|
|
3
|
+
首期测试证明的是本文列出的文本和工具行为,不是完整 Pi 或模型智能。C01–C29 是设计中的用例组,不是 29 个单独测试。具体测试总数与命令见 [验证记录](results/verification.json),逐 fixture 结果见 [差分记录](results/conformance.json)。
|
|
4
4
|
|
|
5
5
|
| 用例组 | Python 行为证据 | 上游证据 / 分类 |
|
|
6
6
|
|---|---|---|
|
|
@@ -32,6 +32,19 @@
|
|
|
32
32
|
| C26 release 候选 | test_C26_noop_duplicate_and_synthetic_upgrade 及参数化拒绝测试 | 合成接口演练;独立远端校验脚本另存实际记录 |
|
|
33
33
|
| C27 wheel 安装 | Linux 容器内断网安装、示例、全部 pytest | 实际组合见 linux-3.11/3.12/3.13.json |
|
|
34
34
|
|
|
35
|
+
## D9:应用工具批次与能力包最小接入
|
|
36
|
+
|
|
37
|
+
2026-10-16 设计修订的新增合同由 `tests/test_application_tools.py`(C28)和 `tests/test_capability_composition.py`(C29)覆盖。它们是 Python 新增合同,不算作与上游直接一致。
|
|
38
|
+
|
|
39
|
+
| 用例组 | Python 行为证据 | 分类 |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| C28 应用工具批次 | 零模型请求;来源与空轮次事件编解码;共享准备、验证、钩子、预算、取消和超时;忙碌互斥;订阅故障保留结果;配置快照;可见性与历史无伪造调用 | intentional_difference,D9 |
|
|
42
|
+
| C29 普通 Python 能力组合 | 同一工具的两种调用方式;名称冲突拒绝;状态与配置隔离;否决不可覆盖;运行完成不掩盖领域验证失败 | intentional_difference,D9;`examples/capability_composition.py` 无网络示例 |
|
|
43
|
+
|
|
44
|
+
兼容性修订:按用户要求,工具事件的 `origin/source` 改为显式 `Agent(include_tool_origin=True)` 才输出,默认不新增键;两种调用入口都遵守此选项。工具上下文和执行结果通过只读属性提供来源,不改变原有 dataclass 字段及 `asdict` 输出。应用批次历史仍保存来源。`tests/test_application_compatibility.py` 对照 v0.10.1 的字段集合,检查旧解析器、整数轮次及自定义消息转换器;新接口接入时可能的订阅失败和消息被过滤也有明确断言。这是对设计草案中“所有工具事件都携带来源”的兼容性调整,不是将缺失来源默认为已记录。
|
|
45
|
+
|
|
46
|
+
本次沿用现行版本的默认无预算上限、已知取消与显式未知结果的区分,以及可选扩展能力,不把它们按早期草案回退。跨 run 预算、领域效果、科研判断内化和新的包加载器不在本次实现范围内。上述旧验证文件记录的是各自运行时的版本,不自动成为 D9 的验证证据。
|
|
47
|
+
|
|
35
48
|
## 有意差异与范围外行为
|
|
36
49
|
|
|
37
50
|
D1–D8 保持原设计的含义。D1 严格输入;D2 执行前钩子不能通过原地修改参数改变调用;D3 资源预算;D4 Python 取消及未知结果;D5 状态复制;D6 输出检查;D7 诊断、原始结果和完整流校验;D8 文本与工具子集。每项有独立 Python 断言,不以 skip 代替验收。
|
|
@@ -6,11 +6,12 @@ Import the public types from `pi_python`. The minimum Python version is 3.11, an
|
|
|
6
6
|
|
|
7
7
|
## Agent and run results
|
|
8
8
|
|
|
9
|
-
`Agent(provider=..., model="mock" or ModelInfo(...), options={}, system_prompt="", tools=[], messages=[], hooks=Hooks(), limits=RunLimits(), execution_mode="parallel", steering_mode="one_at_a_time", follow_up_mode="one_at_a_time")`. If there is neither a `provider` nor a default stream set with `set_default_stream_fn`, `prompt` raises `ConfigurationError` immediately.
|
|
9
|
+
`Agent(provider=..., model="mock" or ModelInfo(...), options={}, system_prompt="", tools=[], messages=[], hooks=Hooks(), limits=RunLimits(), execution_mode="parallel", steering_mode="one_at_a_time", follow_up_mode="one_at_a_time", include_tool_origin=False)`. If there is neither a `provider` nor a default stream set with `set_default_stream_fn`, `prompt` raises `ConfigurationError` immediately.
|
|
10
10
|
|
|
11
11
|
| Method | Behavior |
|
|
12
12
|
|---|---|
|
|
13
13
|
| `await prompt(str_or_message_or_list)` | Appends the input and runs; returns a `RunResult` |
|
|
14
|
+
| `await execute_tools(invocations, source=None)` | Runs a nonempty `list[ToolInvocation]` without requesting a model; returns `RunResult` |
|
|
14
15
|
| `await continue_run()` | Continues from a valid user/tool-result history, or consumes input queued after the final answer; if the last answer failed or was cancelled, retries that turn (see "Recovering from failures"); never replays past tools |
|
|
15
16
|
| `prompt_sync(...)` / `continue_run_sync()` | Blocking versions for plain scripts; Ctrl+C cancels the current run; see "Using it from plain scripts" |
|
|
16
17
|
| `steer(message)` | Accepts guidance at the next safe boundary; also takes a string |
|
|
@@ -22,12 +23,40 @@ Import the public types from `pi_python`. The minimum Python version is 3.11, an
|
|
|
22
23
|
| `clear_queues(steering=True, follow_up=True)` | Explicitly clears the given queues |
|
|
23
24
|
| `await aclose()` | Requests cancellation and waits for this instance's cleanup; does not close a shared Provider |
|
|
24
25
|
|
|
25
|
-
One instance does not accept overlapping `prompt` / `continue_run` calls; the second call raises `AgentBusyError` immediately. `async with Agent(...)` is supported. `state` returns a defensive copy; changing it does not change the history.
|
|
26
|
+
One instance does not accept overlapping `prompt` / `continue_run` / `execute_tools` calls; the second call raises `AgentBusyError` immediately. `async with Agent(...)` is supported. `state` returns a defensive copy; changing it does not change the history.
|
|
26
27
|
|
|
27
28
|
`RunResult` contains `status` (`completed` / `failed` / `cancelled` / `limit_reached`), the `messages` added by this run, the accumulated numeric `usage`, `stop_reason`, `errors`, `tool_outcomes`, `reconciliation_required`, `cleanup_complete`, and the number of items left in each queue. Usage only sums the top-level numeric fields the Provider reports; it does not estimate cost.
|
|
28
29
|
|
|
29
30
|
`tool_outcomes` records, in call order, the raw arguments, the prepared arguments, `raw_result` and the final `result`. `execution_status` is `not_started`, `running`, `succeeded`, `failed`, `cancelled` or `unknown`. `cancelled` means the tool was cancelled or timed out; `unknown` only comes from a `ToolOutcomeUnknownError` that the tool raised itself. If execution succeeded but the output check or the after-hook failed, the status is still `succeeded` and the final result is an error. No error triggers an automatic retry.
|
|
30
31
|
|
|
32
|
+
## Application-selected tools and capability composition
|
|
33
|
+
|
|
34
|
+
Use `require_features(["application-tool-batches-v1"])` to check that an installed build supports this contract.
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from pi_python import ToolInvocation
|
|
38
|
+
|
|
39
|
+
result = await agent.execute_tools(
|
|
40
|
+
[ToolInvocation("check_sample", {"value": -1})], source="sample-checks@1"
|
|
41
|
+
)
|
|
42
|
+
# Inspect tool_outcomes here; completed does not mean a domain check passed.
|
|
43
|
+
await agent.prompt("Interpret the application check results.")
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`ToolInvocation(name, arguments)` accepts a registered tool name and a JSON object. Core assigns a unique call ID. Unknown names and schema failures become per-call error results. Empty batches and invalid invocation envelopes are rejected before a run starts. No Provider is required for this entry point.
|
|
47
|
+
|
|
48
|
+
Each batch is an independent run. It shares argument preparation, schema validation, tool hooks, execution order/concurrency, output checks, tool-call budgets, deadlines and cancellation ownership with model-selected calls. The entire batch is refused if it exceeds `max_tool_calls`. It does not invoke model preparation/conversion hooks or `finish_turn`, consume either input queue, or follow a tool's `terminate` flag into a model loop. Tools and configuration stay fixed during the batch; explicit configuration updates made during it wait until the next run (or a later idle `update_config`), in submission order. Task-wide budgets across several runs belong to the application.
|
|
49
|
+
|
|
50
|
+
Tool-event payloads keep their previous field sets by default. Set `Agent(include_tool_origin=True)` to add `origin` (`"application"` or `"model"`) and `source` (the caller's diagnostic string, or None) to tool events, for either entry point. This option is captured at run start. `ToolContext` and `ToolOutcome` expose provenance as read-only properties, not dataclass fields: `dataclasses.asdict()` keeps the previous shape and does not include provenance. Read `.origin` / `.source` explicitly or use the batch history record when storing it. Model-selected calls always have `source=None`. A source label is not an authenticated identity.
|
|
51
|
+
|
|
52
|
+
Existing model runs retain integer `turn_id` values and do not create `application_tool_batch` messages. Application events have `turn_id=None`, because no model turn occurred. Code that computes with turn IDs or stores them in integer-only columns must handle this before adopting `execute_tools` or reading its event logs. An exception from a critical subscriber can fail the run; None itself is valid in the new event codec. Critical end subscribers finish before the Agent becomes idle, subject to the existing cancellation and delivery-failure rules.
|
|
53
|
+
|
|
54
|
+
History gets one `CustomMessage(custom_type="application_tool_batch", data=...)`, including accepted calls that failed, were cancelled or never started. It contains each call's ID, name, original/prepared arguments, origin/source, execution status, error flag/code and final visible content. It never invents an assistant tool call or an unmatched `ToolResultMessage`. Raw results, structured content and local details stay in `tool_outcomes`; they are not automatically sent to the model. On a subsequent model request, remaining built-in batch messages are converted after the application's `convert_to_llm` hook into explicitly labeled application-result text. Other custom types still require an application converter. This default visibility policy is not a sandbox: application hooks can deliberately expose other fields.
|
|
55
|
+
|
|
56
|
+
**Custom-converter compatibility:** no converter is needed for built-in batch messages. If an application already supplies `transform_context` or `convert_to_llm`, it should preserve these messages for core to convert, or explicitly convert them itself. Rejecting unknown custom types will fail a subsequent model run; filtering all custom messages may silently remove the feedback. These cases arise when adopting the new entry point or importing its history, not in ordinary pre-existing model history. The `application_tool_batch` type name is now reserved for the built-in record; an application previously using that same custom name must review its converter and payload.
|
|
57
|
+
|
|
58
|
+
The [offline composition example](../examples/capability_composition.py) uses the same tool with model-selected and application-fixed calls. Plain Python objects supply instruction sections, tools, hooks and validation. The application owns their state and lifecycle, rejects duplicate instruction names, and composes hooks with explicit order and veto rules. Core rejects duplicate tool names. Configuration is recorded in an application-converted `capability_configuration` message; core does not interpret package versions or load a new package format. The example ends with `status="completed"` but `domain_passed=False`: execution success is separate from domain acceptance.
|
|
59
|
+
|
|
31
60
|
## Provider
|
|
32
61
|
|
|
33
62
|
```python
|
|
@@ -122,7 +151,7 @@ Every hook can be a sync or an async function. Contexts and arguments are passed
|
|
|
122
151
|
| `prepare_request(context, cancel)` | Returns a `TurnUpdate` or None; called before every model request |
|
|
123
152
|
| `prepare_next_turn(context, cancel)` | Returns a `TurnUpdate` or None; called from the second turn on |
|
|
124
153
|
| `transform_context(messages, cancel)` | Returns the message list used for the model request; runs before convert |
|
|
125
|
-
| `convert_to_llm(messages)` | Returns the message list that can be sent to the model; must handle or explicitly filter out `
|
|
154
|
+
| `convert_to_llm(messages)` | Returns the message list that can be sent to the model; must handle or explicitly filter out custom types other than the built-in `application_tool_batch` |
|
|
126
155
|
| `before_tool_call(call, args, context)` | True / None allows, False blocks, or return a `ToolResult` to use without executing; if the hook raises, that call gets an error result (`hook_error`) and the run continues, as in Pi |
|
|
127
156
|
| `after_tool_call(call, result, context)` | Returns a full `ToolResult`, a partial `ToolResultUpdate`, or None |
|
|
128
157
|
| `finish_turn(context, cancel)` | Returns `"continue"`, `"end"` or None |
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# 应用直接执行工具:实现与验证
|
|
2
|
+
|
|
3
|
+
这次检查的是:应用能否不经过模型决策,直接执行已注册工具,同时保留与模型调用相同的执行规则。现在两种入口共用工具执行路径,应用调用不会伪装成模型调用;无网络示例也验证了“运行完成,但领域检查未通过”。这些结果支持开始在应用侧组合具体能力包,不代表领域方法或科研判断已经实现。
|
|
4
|
+
|
|
5
|
+
## 新设计如何分工
|
|
6
|
+
|
|
7
|
+
- **机械 core** 管模型请求、工具执行、参数验证、钩子、事件、预算和取消,不判断领域结论是否正确。
|
|
8
|
+
- **领域能力包** 提供知识、工具、固定步骤和验证规则。应用用普通 Python 代码组合它们,拥有状态与生命周期,并解决名称和规则冲突。
|
|
9
|
+
- **基础模型** 使用这些知识处理具体任务。哪些步骤由模型选择、哪些由应用固定执行,由应用明确决定。
|
|
10
|
+
|
|
11
|
+
新增的 `Agent.execute_tools([ToolInvocation(...)], source=...)` 让应用可以执行固定检查,而不必先让模型选择工具。它与模型调用共用参数准备、执行前后钩子、输出验证、并发限制、工具预算和取消清理。每批是独立运行;跨批次总预算仍由应用管理。
|
|
12
|
+
|
|
13
|
+
例如,[组合示例](../examples/capability_composition.py) 检查数值 `-1` 是否达到应用规定的下限 `0`。模型可以选择调用检查工具;应用也可以先执行同一个工具,再把结果交给模型解释。检查程序正常运行,所以运行状态是 `completed`;数值没有达标,所以应用读取的领域验证结果仍是 `False`。
|
|
14
|
+
|
|
15
|
+
## 记录如何保持可解释
|
|
16
|
+
|
|
17
|
+
工具上下文和执行结果通过只读属性提供 `origin` 与 `source`,分别表示调用来源和应用自报的诊断标签,不改变原有 dataclass 序列化字段。工具事件默认不新增这两个键,只有显式设置 `Agent(include_tool_origin=True)` 才输出。来源标签不代表认证身份。模型调用标为 `model`,应用调用标为 `application`;应用批次历史始终保存来源。
|
|
18
|
+
|
|
19
|
+
应用批次不请求模型,不产生模型轮次,也不消费指导或后续输入队列。历史保存一条内置的 `application_tool_batch` 自定义消息,包括各调用的参数、来源、执行状态和整理后的可见内容。后续请求将它转成明确标注的应用结果文本,不制造 assistant 工具调用或悬空工具结果。原始结果和本地详情留在执行结果中,不自动发给模型。
|
|
20
|
+
|
|
21
|
+
工具名冲突由 core 拒绝。指令段冲突、多个钩子的调用顺序和否决规则由示例中的应用组合器处理。两个能力包实例的可变状态彼此独立。本次没有新增领域包加载器,也没有改动现有插件机制。
|
|
22
|
+
|
|
23
|
+
## 旧应用的兼容性
|
|
24
|
+
|
|
25
|
+
按用户要求,来源信息不再默认扩展工具事件或 dataclass 序列化结构。专门的测试使用 v0.10.1 的字段集合,验证严格检查字段的旧消费者仍能走完模型工具循环。来源仍可通过属性显式读取,需要事件记录时再开启选项。
|
|
26
|
+
|
|
27
|
+
只使用原有 `prompt/continue_run` 且不导入新批次历史的应用,不会收到空轮次或新批次消息。采用 `execute_tools` 后则需要检查接入边界:监听器对 `turn_id=None` 做整数运算会导致订阅失败;已有消息转换器若拒绝新类型,会使后续模型运行失败,若过滤所有自定义消息,则会静默丢掉检查结果。没有自定义转换器时,core 可直接处理内置批次消息;有转换器时,原样保留这类消息也可交给 core 处理。相关成功与失败情况均有测试,但未运行所有实际上层应用的集成测试。
|
|
28
|
+
|
|
29
|
+
## 验证结果与边界
|
|
30
|
+
|
|
31
|
+
兼容性修订后的本机全量测试 **798 项通过、2 项跳过**。C28、C29 及兼容性定向测试共 **47 项通过**,覆盖零模型请求、来源与消息编解码、共享执行规则、配置隔离、忙碌互斥、取消与未退出工具、订阅故障和领域验证失败。两项跳过来自既有插件测试:它们只适用于不支持 `meta` 的旧版 SDK,不计入通过数。
|
|
32
|
+
|
|
33
|
+
实际运行上游和 Python 后,**25 组核心差分全部通过**。新增入口属于 Python 合同 D9,不计作上游直接一致。构建后的 wheel 已在仓库外的独立环境离线安装;43 项应用批次与兼容性测试及组合示例通过。
|
|
34
|
+
|
|
35
|
+
本报告记录发布准备前的本地验证,环境为 macOS ARM64、Python 3.14。该轮没有重新运行 Linux/HPC、多版本矩阵或真实模型调用;已把新增示例和安装包测试接入现有 CI。0.10.2 的接入说明见[发布说明](releases/0.10.2.md),远端 CI 结果以对应工作流为准。示例只证明接口能够组合,不测量领域效果,也不包含“把科研判断内化进 PiPilot”。
|
|
36
|
+
|
|
37
|
+
## 方法与核查附录
|
|
38
|
+
|
|
39
|
+
### 设计依据
|
|
40
|
+
|
|
41
|
+
实际采用 `/Users/daidong/Documents/rp-writings/research-ideas/pi-python-core-design/` 下的 2026-10-16 修订,而不是代码仓库中同名的旧副本:
|
|
42
|
+
|
|
43
|
+
- `DESIGN.md`:“应用直接执行工具”“领域能力包的最小接入约定”。
|
|
44
|
+
- `DEVELOPMENT-PLAN.md`:C28、C29。
|
|
45
|
+
- `UPSTREAM-COMPATIBILITY.md`:D9。
|
|
46
|
+
- `README.md`、`BOOTSTRAP-PROMPT.md`、`DOMAIN-CAPABILITY-PACKS.md`:范围和分工。
|
|
47
|
+
|
|
48
|
+
现有发布版本已经扩展了早期草案的范围。本次保留已有 Provider、插件、同步接口,以及默认无预算上限、已知取消与显式未知结果的区分,没有按旧草案回退它们。上述实现验证使用未发布的 0.10.1 开发构建,没有升级上游基线。随后经用户确认兼容性方案,安排以 0.10.2 提交、推送和发布;这一授权不构成对所有上层应用的兼容性保证。
|
|
49
|
+
|
|
50
|
+
### 实现入口
|
|
51
|
+
|
|
52
|
+
- [Agent API](zh/API.md#应用直接执行工具与能力包组合):参数、来源、可见性和生命周期合同。
|
|
53
|
+
- `src/pi_python/agent.py`:公开入口和运行互斥。
|
|
54
|
+
- `src/pi_python/loop.py`、`run.py`:共用执行器、批次预算、运行收尾和历史记录。
|
|
55
|
+
- `src/pi_python/messages.py`、`events.py`、`tools.py`:内置消息转换、空轮次事件与调用来源。
|
|
56
|
+
- [兼容性测试](../tests/test_application_compatibility.py)、[C28 测试](../tests/test_application_tools.py)、[C29 测试](../tests/test_capability_composition.py)、[验收映射](../compat/COVERAGE.md#d9应用工具批次与能力包最小接入)。
|
|
57
|
+
- 能力检查:`require_features(["application-tool-batches-v1"])`。
|
|
58
|
+
|
|
59
|
+
### 实际执行的检查
|
|
60
|
+
|
|
61
|
+
| 检查 | 命令或方式 | 结果 |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| 新合同及兼容性 | `.venv/bin/pytest -q tests/test_application_tools.py tests/test_application_compatibility.py tests/test_capability_composition.py` | 47 通过 |
|
|
64
|
+
| 全量测试 | `.venv/bin/pytest -q -ra` | 798 通过,2 跳过 |
|
|
65
|
+
| 静态检查 | `.venv/bin/ruff check src tests examples compat scripts` | 通过 |
|
|
66
|
+
| 格式检查 | `.venv/bin/ruff format --check src tests examples compat scripts` | 114 文件通过 |
|
|
67
|
+
| 类型检查 | `.venv/bin/mypy src/pi_python` | 43 源文件通过 |
|
|
68
|
+
| 补丁检查 | `git diff --check` | 通过 |
|
|
69
|
+
| 实际上游对照 | `.venv/bin/python scripts/conformance.py` | 25 组通过,无差异 |
|
|
70
|
+
| 构建 | `uv build --out-dir /tmp/pi-python-optional-origin-dist` | sdist、wheel 成功 |
|
|
71
|
+
| 独立安装 | 在前一轮建立的独立环境中重新安装本轮 wheel:`uv pip install --python /tmp/pi-python-execute-tools-installed/bin/python --offline --reinstall-package pi-python-core /tmp/pi-python-optional-origin-dist/pi_python_core-0.10.1-py3-none-any.whl` | 成功 |
|
|
72
|
+
| 安装包行为 | 从 `/tmp` 使用独立环境运行组合示例和 `pytest -q -o pythonpath= <仓库>/tests/test_application_tools.py <仓库>/tests/test_application_compatibility.py` | 示例输出 `run=completed; domain_passed=False`;43 测试通过 |
|
|
73
|
+
|
|
74
|
+
独立安装的导入路径确认为 `/tmp/pi-python-execute-tools-installed/lib/python3.14/site-packages/pi_python/__init__.py`,不是源码目录。本轮全量测试和构建原始输出保留在 `/tmp/pi-python-optional-origin-*.log`;这些是临时日志,不是持久化存储保证。
|
|
75
|
+
|
|
76
|
+
补充清理边界测试时,首版测试在释放工具后立即调用 `aclose()`,此时清理回调尚未执行,触发了既有的 `CleanupTimeoutError`。测试改为有界等待实际清理完成后关闭,并检查迟到的成功不能改写已提交记录;修正后新合同和全量测试均重新通过。
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# 0.10.2:应用直接执行工具,来源记录可选输出
|
|
2
|
+
|
|
3
|
+
本版本新增 `Agent.execute_tools()`,让应用直接执行已注册工具,不必先请求模型决定调用。两种入口共用工具执行与清理路径;旧工具事件默认保持原有字段集合。
|
|
4
|
+
|
|
5
|
+
## 新增能力
|
|
6
|
+
|
|
7
|
+
- `ToolInvocation(name, arguments)` 与 `await agent.execute_tools(invocations, source=None)`:独立工具批次,无模型请求,共用参数准备、schema 验证、执行前后钩子、并发限制、预算、超时和取消清理。
|
|
8
|
+
- 应用结果通过内置 `application_tool_batch` 自定义消息记录;不伪造 assistant 工具调用,不生成没有对应调用的 `ToolResultMessage`。
|
|
9
|
+
- `ToolContext.origin/source` 与 `ToolOutcome.origin/source` 只读属性,用于区分模型选择与应用指定的调用。来源标签是诊断信息,不是认证身份。
|
|
10
|
+
- `Agent(include_tool_origin=True)` 可将来源加入工具事件;默认关闭,两种入口都遵守该选项。
|
|
11
|
+
- `require_features(["application-tool-batches-v1"])` 可检查是否支持此合同。
|
|
12
|
+
- 无网络能力包组合示例,演示同一个工具的两种调用方式,以及“运行 completed,但领域验证失败”。没有新增包加载器或领域判断引擎。
|
|
13
|
+
|
|
14
|
+
## 兼容性与接入注意事项
|
|
15
|
+
|
|
16
|
+
### 沿用旧接口
|
|
17
|
+
|
|
18
|
+
原有 `prompt/continue_run` 保持整数轮次 ID,不自动生成应用批次消息。工具事件默认不新增 `origin/source`;`ToolContext` 和 `ToolOutcome` 的 dataclass 字段及 `dataclasses.asdict()` 输出结构保持不变。来源信息需要显式读取属性,或从批次历史、开启来源输出后的事件中保存。
|
|
19
|
+
|
|
20
|
+
### 采用新接口或读取新记录
|
|
21
|
+
|
|
22
|
+
1. 应用批次没有模型轮次,其事件的 `turn_id=None`。监听器、数据库字段和事件解析器需要允许这种情况;对 None 做整数运算会出错,关键订阅者失败可能使运行失败。
|
|
23
|
+
2. 没有自定义消息转换器时,core 自动转换内置批次消息。已有 `transform_context/convert_to_llm` 应保留该消息供 core 转换,或自行明确转换。拒绝未知类型可能导致后续模型运行失败;过滤全部自定义消息可能静默丢掉工具反馈。
|
|
24
|
+
3. `application_tool_batch` 是内置消息类型名;已有同名自定义类型的应用应检查其数据与转换规则。
|
|
25
|
+
4. 每批预算只约束当前运行;跨多次运行的任务总预算和领域验收由应用负责。`completed` 不代表领域验证通过。
|
|
26
|
+
|
|
27
|
+
本版本没有更改默认资源上限、现有插件加载机制或上游参照基线。上述接入边界已经说明,并经项目负责人确认后安排发布;这不等于已验证所有实际上层应用。
|
|
28
|
+
|
|
29
|
+
## 验证
|
|
30
|
+
|
|
31
|
+
本地 macOS ARM64 / Python 3.14:全量测试 798 通过、2 跳过;C28、C29 与兼容性定向测试 47 通过;25 组实际上游核心差分通过。Ruff、格式与 mypy 检查通过。两项跳过只适用于不支持 `meta` 的旧版 MCP SDK,不计入通过数。
|
|
32
|
+
|
|
33
|
+
0.10.2 的 wheel 与源码包通过 `twine check --strict`。wheel 已在仓库外的独立 Python 3.11 环境中安装,版本一致性、组合示例和 43 项应用批次及兼容性测试通过。
|
|
34
|
+
|
|
35
|
+
发布由 `v0.10.2` 标签触发,CI 验证和构建成功后才上传 PyPI。没有以模拟示例宣称真实模型、HPC 或领域效果已经验证。
|
|
36
|
+
|
|
37
|
+
## 使用与文档
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from pi_python import ToolInvocation
|
|
41
|
+
|
|
42
|
+
result = await agent.execute_tools(
|
|
43
|
+
[ToolInvocation("check_sample", {"value": -1})], source="sample-checks@1"
|
|
44
|
+
)
|
|
45
|
+
# 根据 result.tool_outcomes 验收,而不是仅检查 result.status。
|
|
46
|
+
await agent.prompt("解释应用检查的结果。")
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
- [中文 API](../zh/API.md#应用直接执行工具与能力包组合)
|
|
50
|
+
- [English API](../API.md#application-selected-tools-and-capability-composition)
|
|
51
|
+
- [无网络组合示例](../../examples/capability_composition.py)
|
|
52
|
+
- [实现与本地验证记录](../APPLICATION_TOOLS_IMPLEMENTATION.md)
|
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
## Agent 与运行结果
|
|
8
8
|
|
|
9
|
-
`Agent(provider=..., model="mock" 或 ModelInfo(...), options={}, system_prompt="", tools=[], messages=[], hooks=Hooks(), limits=RunLimits(), execution_mode="parallel", steering_mode="one_at_a_time", follow_up_mode="one_at_a_time")`。既没有 `provider` 也没有用 `set_default_stream_fn` 设置默认流时,`prompt` 直接抛 `ConfigurationError`。
|
|
9
|
+
`Agent(provider=..., model="mock" 或 ModelInfo(...), options={}, system_prompt="", tools=[], messages=[], hooks=Hooks(), limits=RunLimits(), execution_mode="parallel", steering_mode="one_at_a_time", follow_up_mode="one_at_a_time", include_tool_origin=False)`。既没有 `provider` 也没有用 `set_default_stream_fn` 设置默认流时,`prompt` 直接抛 `ConfigurationError`。
|
|
10
10
|
|
|
11
11
|
| 方法 | 行为 |
|
|
12
12
|
|---|---|
|
|
13
13
|
| `await prompt(str_or_message_or_list)` | 追加输入并运行,返回 `RunResult` |
|
|
14
|
+
| `await execute_tools(invocations, source=None)` | 执行非空 `list[ToolInvocation]`,不请求模型,返回 `RunResult` |
|
|
14
15
|
| `await continue_run()` | 从合法的用户/工具结果历史继续,或消费最终回答之后的排队输入;最后一条回答失败或被取消时,重试这一轮(见“从失败中恢复”);不重放历史工具 |
|
|
15
16
|
| `prompt_sync(...)` / `continue_run_sync()` | 普通脚本里的阻塞版本,按 Ctrl+C 会取消本次运行;见“在普通脚本里使用” |
|
|
16
17
|
| `steer(message)` | 在下一安全边界接纳指导;也接受字符串 |
|
|
@@ -22,12 +23,40 @@
|
|
|
22
23
|
| `clear_queues(steering=True, follow_up=True)` | 显式清除指定队列 |
|
|
23
24
|
| `await aclose()` | 请求取消并等待本实例的清理,不关闭共享 Provider |
|
|
24
25
|
|
|
25
|
-
同一实例不接受重叠的 `prompt` / `continue_run`,第二次调用立即抛 `AgentBusyError`。支持 `async with Agent(...)`。`state` 返回防御性复制;修改它不会改变历史。
|
|
26
|
+
同一实例不接受重叠的 `prompt` / `continue_run` / `execute_tools`,第二次调用立即抛 `AgentBusyError`。支持 `async with Agent(...)`。`state` 返回防御性复制;修改它不会改变历史。
|
|
26
27
|
|
|
27
28
|
`RunResult` 包含 `status`(`completed` / `failed` / `cancelled` / `limit_reached`)、本次新增 `messages`、数值用量累计 `usage`、`stop_reason`、`errors`、`tool_outcomes`、`reconciliation_required`、`cleanup_complete` 和两个队列的剩余数量。用量只累加 Provider 报告的顶层数值字段;不推算费用。
|
|
28
29
|
|
|
29
30
|
`tool_outcomes` 按调用顺序记录原始参数、准备后参数、`raw_result` 和最终 `result`。`execution_status` 是 `not_started`、`running`、`succeeded`、`failed`、`cancelled`、`unknown`。`cancelled` 表示工具被取消或超时;`unknown` 只来自工具主动抛出的 `ToolOutcomeUnknownError`。执行成功但输出检查或后置钩子失败时,状态仍为 `succeeded`,最终结果为错误。所有错误都不触发自动重试。
|
|
30
31
|
|
|
32
|
+
## 应用直接执行工具与能力包组合
|
|
33
|
+
|
|
34
|
+
可用 `require_features(["application-tool-batches-v1"])` 检查已安装版本是否支持此合同。
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from pi_python import ToolInvocation
|
|
38
|
+
|
|
39
|
+
result = await agent.execute_tools(
|
|
40
|
+
[ToolInvocation("check_sample", {"value": -1})], source="sample-checks@1"
|
|
41
|
+
)
|
|
42
|
+
# 在这里检查 tool_outcomes;completed 不等于领域检查通过。
|
|
43
|
+
await agent.prompt("解释应用执行的检查结果。")
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`ToolInvocation(name, arguments)` 接收已注册工具名和 JSON 对象参数,core 分配唯一调用 ID。未知工具名、schema 验证失败成为该调用的错误结果。空批次和不合法的调用对象在运行开始前拒绝。这个入口不要求配置 Provider。
|
|
47
|
+
|
|
48
|
+
每个批次占用一次独立运行,与模型调用共用参数准备、schema 验证、工具钩子、执行顺序与并发、输出检查、工具预算、超时和取消清理。超过 `max_tool_calls` 时整批不执行。它不调用模型准备或转换钩子,不调用 `finish_turn`,不消费两类输入队列,也不根据工具的 `terminate` 字段启动模型循环。批次的工具与配置保持固定;期间提交的配置更新按提交顺序留到下次运行前应用,或在之后空闲时的 `update_config` 中先应用。跨多次运行的任务总预算由应用管理。
|
|
49
|
+
|
|
50
|
+
工具事件默认保持原有字段集合。显式设置 `Agent(include_tool_origin=True)` 后,两种入口的工具事件才增加 `origin`(`"application"` 或 `"model"`)及 `source`(调用者提供的诊断字符串或 None)。该选项在每次运行开始时固定。`ToolContext` 和 `ToolOutcome` 通过只读属性提供来源,不新增 dataclass 字段;`dataclasses.asdict()` 保持旧结构,不包含来源信息。需要持久化来源时,显式读取 `.origin` / `.source`,或保存批次历史记录。模型调用的 `source` 始终为 None。来源标签不是认证身份。
|
|
51
|
+
|
|
52
|
+
原有模型运行仍使用整数 `turn_id`,也不会产生 `application_tool_batch` 消息。应用批次事件的 `turn_id=None`,因为没有发生模型轮次。在采用 `execute_tools` 或读取其事件日志前,对轮次做整数运算或使用仅允许整数的数据库列的应用需要适配。None 本身在新事件编解码器中合法,但关键订阅者抛错可能使运行失败。正常情况下,关键结束订阅完成后实例才空闲;取消和订阅失败仍遵守既有规则。
|
|
53
|
+
|
|
54
|
+
历史追加一条 `CustomMessage(custom_type="application_tool_batch", data=...)`,记录已接纳调用,包括失败、取消和未执行的调用。每项包含 ID、工具名、原始及准备后参数、来源、执行状态、错误标志与代码、整理后的可见内容。不伪造 assistant 调用,不产生悬空 `ToolResultMessage`。原始结果、结构化内容和本地详情留在 `tool_outcomes`,不自动送模型。后续模型请求中,应用的 `convert_to_llm` 钩子执行后,剩余的内置批次消息自动转成明确标注“应用执行结果”的文本。其他自定义类型仍需应用转换。默认可见性规则不是隔离环境;应用钩子仍可主动公开其他字段。
|
|
55
|
+
|
|
56
|
+
**已有转换器的兼容性:** 内置批次消息不要求应用编写转换器。若已有 `transform_context` 或 `convert_to_llm`,应保留这类消息交给 core 转换,或自行明确转换。拒绝未知自定义类型会导致后续模型运行失败;过滤所有自定义消息则可能静默丢掉检查结果。这些情况发生在采用新接口或导入其历史之后,普通旧模型历史不会新增这类消息。`application_tool_batch` 现在是内置记录使用的类型名;旧应用若恰好已使用同名自定义类型,需要检查转换器和数据格式。
|
|
57
|
+
|
|
58
|
+
[无网络组合示例](../../examples/capability_composition.py) 在同一个工具上演示模型选择调用和应用固定调用。普通 Python 对象提供指令段、工具、钩子和验证方法。应用拥有状态与生命周期,拒绝重复指令段名,并明确组合钩子的顺序与否决规则;core 拒绝重复工具名。配置以 `capability_configuration` 自定义消息记录,由应用转换;core 不解释包版本,也不新增包加载格式。示例最终得到 `status="completed"`,但 `domain_passed=False`,保留执行完成与领域验收的区别。
|
|
59
|
+
|
|
31
60
|
## Provider
|
|
32
61
|
|
|
33
62
|
```python
|
|
@@ -110,7 +139,7 @@ def search_papers(query: str, year: int | None = None, limit: int = 10) -> list[
|
|
|
110
139
|
| `prepare_request(context, cancel)` | 返回 `TurnUpdate` 或 None,在每次模型请求前调用 |
|
|
111
140
|
| `prepare_next_turn(context, cancel)` | 返回 `TurnUpdate` 或 None,从第二轮开始调用 |
|
|
112
141
|
| `transform_context(messages, cancel)` | 返回模型请求使用的消息列表,先于 convert |
|
|
113
|
-
| `convert_to_llm(messages)` |
|
|
142
|
+
| `convert_to_llm(messages)` | 返回可送模型的消息列表,必须处理或显式过滤除内置 `application_tool_batch` 外的自定义消息类型 |
|
|
114
143
|
| `before_tool_call(call, args, context)` | True / None 允许,False 阻止,或返回无需执行的 `ToolResult`;抛异常时该调用得到错误结果(`hook_error`),运行继续,与 Pi 相同 |
|
|
115
144
|
| `after_tool_call(call, result, context)` | 返回完整 `ToolResult`、部分 `ToolResultUpdate` 或 None |
|
|
116
145
|
| `finish_turn(context, cancel)` | 返回 `"continue"`、`"end"` 或 None |
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Offline C29 example: application-owned knowledge, tools, hooks and validation.
|
|
2
|
+
|
|
3
|
+
This toy check demonstrates composition, not the effectiveness of domain knowledge.
|
|
4
|
+
Run with: python examples/capability_composition.py
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import json
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
|
|
11
|
+
from pi_python import (
|
|
12
|
+
Agent,
|
|
13
|
+
AssistantMessage,
|
|
14
|
+
ConfigurationError,
|
|
15
|
+
CustomMessage,
|
|
16
|
+
Hooks,
|
|
17
|
+
ScriptedProvider,
|
|
18
|
+
SystemMessage,
|
|
19
|
+
Tool,
|
|
20
|
+
ToolCall,
|
|
21
|
+
ToolInvocation,
|
|
22
|
+
ToolResult,
|
|
23
|
+
UserMessage,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class CheckPack:
|
|
29
|
+
"""A plain Python object owned by one application session, not a plugin loader."""
|
|
30
|
+
|
|
31
|
+
name: str = "sample-checks"
|
|
32
|
+
version: str = "1"
|
|
33
|
+
minimum: int = 0
|
|
34
|
+
calls: list[int] = field(default_factory=list)
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def instructions(self):
|
|
38
|
+
return {
|
|
39
|
+
self.name: "Check the sample before interpreting it. A normal run is not validation."
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
def before(self, call, args, context):
|
|
43
|
+
# Applications explicitly combine vetoes; a later allow cannot erase a block.
|
|
44
|
+
return args.get("value") != 999
|
|
45
|
+
|
|
46
|
+
async def check(self, args, context):
|
|
47
|
+
value = args["value"]
|
|
48
|
+
self.calls.append(value)
|
|
49
|
+
passed = value >= self.minimum
|
|
50
|
+
return ToolResult.text(
|
|
51
|
+
f"sample={value}; domain_passed={passed}",
|
|
52
|
+
structured_content={"domain_passed": passed},
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def tools(self):
|
|
56
|
+
return [
|
|
57
|
+
Tool(
|
|
58
|
+
"check_sample",
|
|
59
|
+
"Check a sample against the application rule",
|
|
60
|
+
{
|
|
61
|
+
"type": "object",
|
|
62
|
+
"properties": {"value": {"type": "integer"}},
|
|
63
|
+
"required": ["value"],
|
|
64
|
+
"additionalProperties": False,
|
|
65
|
+
},
|
|
66
|
+
self.check,
|
|
67
|
+
)
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def compose(packs, provider, mode):
|
|
72
|
+
"""Resolve instruction names and hook order here; Agent checks tool-name conflicts."""
|
|
73
|
+
sections, tools, metadata = {}, [], []
|
|
74
|
+
for pack in packs:
|
|
75
|
+
for name, text in pack.instructions.items():
|
|
76
|
+
if name in sections:
|
|
77
|
+
raise ConfigurationError(f"Duplicate instruction section: {name}")
|
|
78
|
+
sections[name] = text
|
|
79
|
+
tools.extend(pack.tools())
|
|
80
|
+
metadata.append({"name": pack.name, "version": pack.version, "minimum": pack.minimum})
|
|
81
|
+
|
|
82
|
+
def before(call, args, context):
|
|
83
|
+
for pack in packs: # explicit order, stop at the first veto
|
|
84
|
+
if pack.before(call, args, context) is False:
|
|
85
|
+
return False
|
|
86
|
+
return True
|
|
87
|
+
|
|
88
|
+
def convert(messages):
|
|
89
|
+
return [
|
|
90
|
+
UserMessage("Application capability configuration: " + json.dumps(m.data))
|
|
91
|
+
if isinstance(m, CustomMessage) and m.custom_type == "capability_configuration"
|
|
92
|
+
else m # core handles application_tool_batch, not arbitrary custom types
|
|
93
|
+
for m in messages
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
return Agent(
|
|
97
|
+
provider=provider,
|
|
98
|
+
tools=tools,
|
|
99
|
+
messages=[
|
|
100
|
+
SystemMessage(sections=sections),
|
|
101
|
+
CustomMessage("capability_configuration", {"packs": metadata, "mode": mode}),
|
|
102
|
+
],
|
|
103
|
+
hooks=Hooks(before_tool_call=before, convert_to_llm=convert),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
async def compare():
|
|
108
|
+
model_pack, application_pack = CheckPack(), CheckPack()
|
|
109
|
+
model_provider = ScriptedProvider(
|
|
110
|
+
[
|
|
111
|
+
AssistantMessage([ToolCall("model-check", "check_sample", {"value": -1})], "tool_use"),
|
|
112
|
+
AssistantMessage.text("The check failed."),
|
|
113
|
+
]
|
|
114
|
+
)
|
|
115
|
+
application_provider = ScriptedProvider([AssistantMessage.text("The fixed check failed.")])
|
|
116
|
+
async with compose([model_pack], model_provider, "model") as model_agent:
|
|
117
|
+
model_result = await model_agent.prompt("Check sample -1 and interpret it.")
|
|
118
|
+
async with compose([application_pack], application_provider, "application") as app_agent:
|
|
119
|
+
batch = await app_agent.execute_tools(
|
|
120
|
+
[ToolInvocation("check_sample", {"value": -1})], source="sample-checks@1"
|
|
121
|
+
)
|
|
122
|
+
assert not application_provider.requests
|
|
123
|
+
# Domain validation uses the independent tool result, not run status or model prose.
|
|
124
|
+
domain_passed = batch.tool_outcomes[0].result.structured_content["domain_passed"]
|
|
125
|
+
interpretation = await app_agent.prompt(
|
|
126
|
+
"Interpret the fixed check; do not claim it passed."
|
|
127
|
+
)
|
|
128
|
+
assert model_result.status == batch.status == interpretation.status == "completed"
|
|
129
|
+
assert domain_passed is False
|
|
130
|
+
assert model_pack.calls == application_pack.calls == [-1]
|
|
131
|
+
assert model_pack.calls is not application_pack.calls
|
|
132
|
+
assert CheckPack().calls == []
|
|
133
|
+
return {
|
|
134
|
+
"model": model_result,
|
|
135
|
+
"application": batch,
|
|
136
|
+
"domain_passed": domain_passed,
|
|
137
|
+
"model_requests": len(model_provider.requests),
|
|
138
|
+
"application_requests": len(application_provider.requests),
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
async def main():
|
|
143
|
+
result = await compare()
|
|
144
|
+
print(f"run={result['application'].status}; domain_passed={result['domain_passed']}")
|
|
145
|
+
print("Both paths use the same tool; the application batch made zero model requests.")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
if __name__ == "__main__":
|
|
149
|
+
asyncio.run(main())
|
|
@@ -64,6 +64,7 @@ from .recovery import (
|
|
|
64
64
|
)
|
|
65
65
|
from .tools import (
|
|
66
66
|
Tool,
|
|
67
|
+
ToolInvocation,
|
|
67
68
|
ToolContext,
|
|
68
69
|
ToolExecutor,
|
|
69
70
|
ToolResult,
|
|
@@ -151,6 +152,7 @@ __all__ = [
|
|
|
151
152
|
"is_recoverable_length",
|
|
152
153
|
"is_retryable_error",
|
|
153
154
|
"retry_delay",
|
|
155
|
+
"ToolInvocation",
|
|
154
156
|
"ToolContext",
|
|
155
157
|
"ToolExecutor",
|
|
156
158
|
"ToolResult",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.10.2"
|
|
@@ -9,6 +9,7 @@ import asyncio
|
|
|
9
9
|
from copy import copy, deepcopy
|
|
10
10
|
from dataclasses import dataclass
|
|
11
11
|
from typing import Any, Callable
|
|
12
|
+
from uuid import uuid4
|
|
12
13
|
|
|
13
14
|
from .cancellation import CancelToken
|
|
14
15
|
from .errors import (
|
|
@@ -29,6 +30,7 @@ from .messages import (
|
|
|
29
30
|
TextContent,
|
|
30
31
|
Message,
|
|
31
32
|
SystemMessage,
|
|
33
|
+
ToolCall,
|
|
32
34
|
UserMessage,
|
|
33
35
|
validate_history,
|
|
34
36
|
validate_json,
|
|
@@ -37,7 +39,7 @@ from .provider import Provider, DefaultProvider, FunctionProvider, has_default_s
|
|
|
37
39
|
from .queues import MessageQueues
|
|
38
40
|
from .run import CALLER_CANCELLED, Run, RunResult, merge
|
|
39
41
|
from .sync import run_sync
|
|
40
|
-
from .tools import Tool
|
|
42
|
+
from .tools import Tool, ToolInvocation
|
|
41
43
|
from .transcript import current_system_message
|
|
42
44
|
|
|
43
45
|
|
|
@@ -78,9 +80,13 @@ class Agent:
|
|
|
78
80
|
execution_mode: str = "parallel",
|
|
79
81
|
steering_mode: str = "one_at_a_time",
|
|
80
82
|
follow_up_mode: str = "one_at_a_time",
|
|
83
|
+
include_tool_origin: bool = False,
|
|
81
84
|
):
|
|
82
85
|
if execution_mode not in {"parallel", "sequential"}:
|
|
83
86
|
raise ConfigurationError("Invalid execution mode")
|
|
87
|
+
if type(include_tool_origin) is not bool:
|
|
88
|
+
raise ConfigurationError("include_tool_origin must be a boolean")
|
|
89
|
+
self.include_tool_origin = include_tool_origin
|
|
84
90
|
self._queues = MessageQueues(steering_mode, follow_up_mode)
|
|
85
91
|
if provider is not None and stream_fn is not None:
|
|
86
92
|
raise ConfigurationError("Pass provider or stream_fn, not both")
|
|
@@ -204,6 +210,10 @@ class Agent:
|
|
|
204
210
|
if self._running:
|
|
205
211
|
self._updates.append(update)
|
|
206
212
|
else:
|
|
213
|
+
# Preserve submission order after an application batch deferred updates.
|
|
214
|
+
for pending in self._updates:
|
|
215
|
+
merge(self._defaults, pending)
|
|
216
|
+
self._updates.clear()
|
|
207
217
|
merge(self._defaults, update)
|
|
208
218
|
|
|
209
219
|
@staticmethod
|
|
@@ -332,6 +342,31 @@ class Agent:
|
|
|
332
342
|
raise AgentBusyError("Agent already running")
|
|
333
343
|
return await self._start(self._input(message))
|
|
334
344
|
|
|
345
|
+
async def execute_tools(
|
|
346
|
+
self, invocations: list[ToolInvocation], *, source: str | None = None
|
|
347
|
+
) -> RunResult:
|
|
348
|
+
"""Execute a nonempty batch without a model request or consuming queued input.
|
|
349
|
+
|
|
350
|
+
Shares the model tool path, limits and cancellation ownership. Results are
|
|
351
|
+
recorded as application feedback, never as assistant/tool-result pairs.
|
|
352
|
+
``source`` is caller-supplied diagnostic metadata, not an identity claim.
|
|
353
|
+
"""
|
|
354
|
+
self._usable()
|
|
355
|
+
if self._running:
|
|
356
|
+
raise AgentBusyError("Agent already running")
|
|
357
|
+
validate_history(self._messages)
|
|
358
|
+
if not isinstance(invocations, list) or not invocations:
|
|
359
|
+
raise ConfigurationError("execute_tools requires a nonempty list of ToolInvocation")
|
|
360
|
+
if source is not None and not isinstance(source, str):
|
|
361
|
+
raise ConfigurationError("source must be a string or None")
|
|
362
|
+
calls = []
|
|
363
|
+
for invocation in invocations:
|
|
364
|
+
if not isinstance(invocation, ToolInvocation):
|
|
365
|
+
raise ConfigurationError("Expected ToolInvocation")
|
|
366
|
+
invocation.__post_init__()
|
|
367
|
+
calls.append(ToolCall(str(uuid4()), invocation.name, deepcopy(invocation.arguments)))
|
|
368
|
+
return await self._start([], application_calls=calls, source=source)
|
|
369
|
+
|
|
335
370
|
def prompt_sync(
|
|
336
371
|
self, message: str | Message | list[Message], images: list[ImageContent] | None = None
|
|
337
372
|
) -> RunResult:
|
|
@@ -369,18 +404,32 @@ class Agent:
|
|
|
369
404
|
return await self._start([])
|
|
370
405
|
|
|
371
406
|
async def _start(
|
|
372
|
-
self,
|
|
407
|
+
self,
|
|
408
|
+
pending: list[Message],
|
|
409
|
+
skip_initial_steering: bool = False,
|
|
410
|
+
*,
|
|
411
|
+
application_calls: list[ToolCall] | None = None,
|
|
412
|
+
source: str | None = None,
|
|
373
413
|
) -> RunResult:
|
|
374
|
-
if
|
|
414
|
+
if (
|
|
415
|
+
application_calls is None
|
|
416
|
+
and isinstance(self.provider, DefaultProvider)
|
|
417
|
+
and not has_default_stream()
|
|
418
|
+
):
|
|
375
419
|
raise ConfigurationError(
|
|
376
420
|
"No model provider: pass Agent(provider=...) or call set_default_stream_fn(...)"
|
|
377
421
|
)
|
|
422
|
+
for update in self._updates:
|
|
423
|
+
merge(self._defaults, update)
|
|
424
|
+
self._updates.clear()
|
|
378
425
|
self._running = True
|
|
379
426
|
self._idle.clear()
|
|
380
427
|
self._settled.clear()
|
|
381
428
|
self._last_error = None
|
|
382
429
|
self._events.failed = False
|
|
383
|
-
run = self._run = Run(
|
|
430
|
+
run = self._run = Run(
|
|
431
|
+
self, skip_initial_steering, application_calls=application_calls, source=source
|
|
432
|
+
)
|
|
384
433
|
run.loop = asyncio.get_running_loop()
|
|
385
434
|
# Driver starts with a checkpoint so cancellation before scheduling still finalizes.
|
|
386
435
|
run.driver = asyncio.create_task(self._drive(run, pending))
|
|
@@ -16,7 +16,7 @@ from .messages import validate_json
|
|
|
16
16
|
class Event:
|
|
17
17
|
type: str
|
|
18
18
|
run_id: str
|
|
19
|
-
turn_id: int
|
|
19
|
+
turn_id: int | None
|
|
20
20
|
sequence: int
|
|
21
21
|
call_id: str | None = None
|
|
22
22
|
data: dict[str, Any] = field(default_factory=dict)
|
|
@@ -50,8 +50,7 @@ def decode_event(value: str) -> Event:
|
|
|
50
50
|
if (
|
|
51
51
|
type(e.sequence) is not int
|
|
52
52
|
or e.sequence < 1
|
|
53
|
-
or type(e.turn_id) is not int
|
|
54
|
-
or e.turn_id < 0
|
|
53
|
+
or (e.turn_id is not None and (type(e.turn_id) is not int or e.turn_id < 0))
|
|
55
54
|
):
|
|
56
55
|
raise MessageValidationError("Invalid event sequence or turn")
|
|
57
56
|
if e.call_id is not None and not isinstance(e.call_id, str):
|
|
@@ -85,7 +84,7 @@ class EventDispatcher:
|
|
|
85
84
|
self,
|
|
86
85
|
kind: str,
|
|
87
86
|
run_id: str,
|
|
88
|
-
turn_id: int,
|
|
87
|
+
turn_id: int | None,
|
|
89
88
|
data: dict[str, Any],
|
|
90
89
|
call_id: str | None,
|
|
91
90
|
await_owned: Callable,
|