calfkit 0.5.2__tar.gz → 0.5.4__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.
Files changed (363) hide show
  1. calfkit-0.5.4/.release-please-manifest.json +3 -0
  2. {calfkit-0.5.2 → calfkit-0.5.4}/CHANGELOG.md +14 -0
  3. {calfkit-0.5.2 → calfkit-0.5.4}/PKG-INFO +51 -34
  4. {calfkit-0.5.2 → calfkit-0.5.4}/README.md +48 -33
  5. {calfkit-0.5.2 → calfkit-0.5.4}/ROADMAP.md +1 -0
  6. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/cli/__init__.py +5 -2
  7. calfkit-0.5.4/calfkit/cli/_loader.py +165 -0
  8. calfkit-0.5.4/calfkit/cli/_run.py +123 -0
  9. calfkit-0.5.4/calfkit/cli/run.py +117 -0
  10. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/cli/topics.py +4 -74
  11. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/client/client.py +26 -5
  12. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/models/state.py +16 -0
  13. calfkit-0.5.4/calfkit/nodes/_projection.py +260 -0
  14. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/nodes/agent.py +24 -4
  15. calfkit-0.5.4/docs/agent-pov-projection.md +381 -0
  16. calfkit-0.5.4/docs/cli.md +214 -0
  17. calfkit-0.5.4/examples/multi_agent_panel/README.md +29 -0
  18. calfkit-0.5.4/examples/multi_agent_panel/panel.py +38 -0
  19. calfkit-0.5.4/examples/multi_agent_panel/run.py +47 -0
  20. calfkit-0.5.4/examples/multi_agent_panel/service.py +23 -0
  21. {calfkit-0.5.2 → calfkit-0.5.4}/pyproject.toml +8 -4
  22. {calfkit-0.5.2 → calfkit-0.5.4}/tests/provisioning_cli_nodes.py +10 -0
  23. calfkit-0.5.4/tests/test_agent_loop_integration.py +410 -0
  24. calfkit-0.5.4/tests/test_client_author.py +94 -0
  25. calfkit-0.5.4/tests/test_projection.py +602 -0
  26. calfkit-0.5.4/tests/test_projection_coverage.py +124 -0
  27. calfkit-0.5.4/tests/test_projection_review_fixes.py +84 -0
  28. calfkit-0.5.4/tests/test_run_cli.py +200 -0
  29. calfkit-0.5.4/tests/test_run_loader.py +179 -0
  30. calfkit-0.5.4/tests/test_run_serve.py +247 -0
  31. calfkit-0.5.4/tests/test_state_stamping.py +117 -0
  32. calfkit-0.5.2/.release-please-manifest.json +0 -3
  33. {calfkit-0.5.2 → calfkit-0.5.4}/.github/CODEOWNERS +0 -0
  34. {calfkit-0.5.2 → calfkit-0.5.4}/.github/dependabot.yml +0 -0
  35. {calfkit-0.5.2 → calfkit-0.5.4}/.github/workflows/build.yml +0 -0
  36. {calfkit-0.5.2 → calfkit-0.5.4}/.github/workflows/code-checks.yml +0 -0
  37. {calfkit-0.5.2 → calfkit-0.5.4}/.github/workflows/kafka-integration.yml +0 -0
  38. {calfkit-0.5.2 → calfkit-0.5.4}/.github/workflows/mcp-e2e.yml +0 -0
  39. {calfkit-0.5.2 → calfkit-0.5.4}/.github/workflows/release.yml +0 -0
  40. {calfkit-0.5.2 → calfkit-0.5.4}/.github/workflows/security.yml +0 -0
  41. {calfkit-0.5.2 → calfkit-0.5.4}/.github/workflows/test.yml +0 -0
  42. {calfkit-0.5.2 → calfkit-0.5.4}/.gitignore +0 -0
  43. {calfkit-0.5.2 → calfkit-0.5.4}/LICENSE +0 -0
  44. {calfkit-0.5.2 → calfkit-0.5.4}/Makefile +0 -0
  45. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/__init__.py +0 -0
  46. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_protocol.py +0 -0
  47. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_types.py +0 -0
  48. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/__init__.py +0 -0
  49. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/LICENSE +0 -0
  50. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/__init__.py +0 -0
  51. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/__main__.py +0 -0
  52. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/_a2a.py +0 -0
  53. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/_agent_graph.py +0 -0
  54. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/_cli/__init__.py +0 -0
  55. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/_cli/web.py +0 -0
  56. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/_function_schema.py +0 -0
  57. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/_griffe.py +0 -0
  58. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/_instrumentation.py +0 -0
  59. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/_json_schema.py +0 -0
  60. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/_mcp.py +0 -0
  61. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/_otel_messages.py +0 -0
  62. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/_output.py +0 -0
  63. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/_parts_manager.py +0 -0
  64. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/_run_context.py +0 -0
  65. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/_system_prompt.py +0 -0
  66. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/_thinking_part.py +0 -0
  67. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/_tool_manager.py +0 -0
  68. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/_utils.py +0 -0
  69. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/ag_ui.py +0 -0
  70. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/agent/__init__.py +0 -0
  71. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/agent/abstract.py +0 -0
  72. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/agent/wrapper.py +0 -0
  73. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/builtin_tools.py +0 -0
  74. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/common_tools/__init__.py +0 -0
  75. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/common_tools/duckduckgo.py +0 -0
  76. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/common_tools/exa.py +0 -0
  77. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/common_tools/tavily.py +0 -0
  78. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/direct.py +0 -0
  79. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/durable_exec/__init__.py +0 -0
  80. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/durable_exec/dbos/__init__.py +0 -0
  81. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/durable_exec/dbos/_agent.py +0 -0
  82. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/durable_exec/dbos/_fastmcp_toolset.py +0 -0
  83. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/durable_exec/dbos/_mcp.py +0 -0
  84. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/durable_exec/dbos/_mcp_server.py +0 -0
  85. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/durable_exec/dbos/_model.py +0 -0
  86. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/durable_exec/dbos/_utils.py +0 -0
  87. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/__init__.py +0 -0
  88. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/_agent.py +0 -0
  89. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/_cache_policies.py +0 -0
  90. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/_function_toolset.py +0 -0
  91. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/_mcp_server.py +0 -0
  92. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/_model.py +0 -0
  93. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/_toolset.py +0 -0
  94. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/durable_exec/prefect/_types.py +0 -0
  95. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/__init__.py +0 -0
  96. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_agent.py +0 -0
  97. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_dynamic_toolset.py +0 -0
  98. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_fastmcp_toolset.py +0 -0
  99. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_function_toolset.py +0 -0
  100. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_logfire.py +0 -0
  101. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_mcp.py +0 -0
  102. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_mcp_server.py +0 -0
  103. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_model.py +0 -0
  104. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_run_context.py +0 -0
  105. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_toolset.py +0 -0
  106. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/durable_exec/temporal/_workflow.py +0 -0
  107. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/embeddings/__init__.py +0 -0
  108. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/embeddings/base.py +0 -0
  109. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/embeddings/cohere.py +0 -0
  110. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/embeddings/google.py +0 -0
  111. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/embeddings/instrumented.py +0 -0
  112. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/embeddings/openai.py +0 -0
  113. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/embeddings/result.py +0 -0
  114. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/embeddings/sentence_transformers.py +0 -0
  115. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/embeddings/settings.py +0 -0
  116. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/embeddings/test.py +0 -0
  117. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/embeddings/voyageai.py +0 -0
  118. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/embeddings/wrapper.py +0 -0
  119. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/exceptions.py +0 -0
  120. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/ext/__init__.py +0 -0
  121. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/ext/aci.py +0 -0
  122. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/ext/langchain.py +0 -0
  123. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/format_prompt.py +0 -0
  124. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/mcp.py +0 -0
  125. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/messages.py +0 -0
  126. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/models/__init__.py +0 -0
  127. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/models/anthropic.py +0 -0
  128. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/models/bedrock.py +0 -0
  129. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/models/cerebras.py +0 -0
  130. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/models/cohere.py +0 -0
  131. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/models/fallback.py +0 -0
  132. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/models/function.py +0 -0
  133. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/models/gemini.py +0 -0
  134. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/models/google.py +0 -0
  135. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/models/groq.py +0 -0
  136. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/models/huggingface.py +0 -0
  137. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/models/instrumented.py +0 -0
  138. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/models/mcp_sampling.py +0 -0
  139. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/models/mistral.py +0 -0
  140. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/models/openai.py +0 -0
  141. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/models/openrouter.py +0 -0
  142. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/models/outlines.py +0 -0
  143. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/models/test.py +0 -0
  144. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/models/wrapper.py +0 -0
  145. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/models/xai.py +0 -0
  146. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/output.py +0 -0
  147. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/profiles/__init__.py +0 -0
  148. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/profiles/amazon.py +0 -0
  149. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/profiles/anthropic.py +0 -0
  150. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/profiles/cohere.py +0 -0
  151. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/profiles/deepseek.py +0 -0
  152. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/profiles/google.py +0 -0
  153. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/profiles/grok.py +0 -0
  154. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/profiles/groq.py +0 -0
  155. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/profiles/harmony.py +0 -0
  156. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/profiles/meta.py +0 -0
  157. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/profiles/mistral.py +0 -0
  158. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/profiles/moonshotai.py +0 -0
  159. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/profiles/openai.py +0 -0
  160. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/profiles/qwen.py +0 -0
  161. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/profiles/zai.py +0 -0
  162. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/__init__.py +0 -0
  163. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/alibaba.py +0 -0
  164. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/anthropic.py +0 -0
  165. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/azure.py +0 -0
  166. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/bedrock.py +0 -0
  167. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/cerebras.py +0 -0
  168. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/cohere.py +0 -0
  169. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/deepseek.py +0 -0
  170. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/fireworks.py +0 -0
  171. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/gateway.py +0 -0
  172. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/github.py +0 -0
  173. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/google.py +0 -0
  174. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/google_gla.py +0 -0
  175. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/google_vertex.py +0 -0
  176. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/grok.py +0 -0
  177. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/groq.py +0 -0
  178. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/heroku.py +0 -0
  179. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/huggingface.py +0 -0
  180. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/litellm.py +0 -0
  181. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/mistral.py +0 -0
  182. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/moonshotai.py +0 -0
  183. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/nebius.py +0 -0
  184. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/ollama.py +0 -0
  185. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/openai.py +0 -0
  186. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/openrouter.py +0 -0
  187. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/outlines.py +0 -0
  188. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/ovhcloud.py +0 -0
  189. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/sambanova.py +0 -0
  190. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/sentence_transformers.py +0 -0
  191. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/together.py +0 -0
  192. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/vercel.py +0 -0
  193. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/voyageai.py +0 -0
  194. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/providers/xai.py +0 -0
  195. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/py.typed +0 -0
  196. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/result.py +0 -0
  197. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/retries.py +0 -0
  198. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/run.py +0 -0
  199. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/settings.py +0 -0
  200. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/tools.py +0 -0
  201. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/toolsets/__init__.py +0 -0
  202. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/toolsets/_dynamic.py +0 -0
  203. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/toolsets/abstract.py +0 -0
  204. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/toolsets/approval_required.py +0 -0
  205. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/toolsets/combined.py +0 -0
  206. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/toolsets/external.py +0 -0
  207. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/toolsets/fastmcp.py +0 -0
  208. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/toolsets/filtered.py +0 -0
  209. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/toolsets/function.py +0 -0
  210. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/toolsets/prefixed.py +0 -0
  211. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/toolsets/prepared.py +0 -0
  212. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/toolsets/renamed.py +0 -0
  213. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/toolsets/wrapper.py +0 -0
  214. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/ui/__init__.py +0 -0
  215. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/ui/_adapter.py +0 -0
  216. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/ui/_event_stream.py +0 -0
  217. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/ui/_messages_builder.py +0 -0
  218. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/ui/_web/__init__.py +0 -0
  219. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/ui/_web/api.py +0 -0
  220. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/ui/_web/app.py +0 -0
  221. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/ui/ag_ui/__init__.py +0 -0
  222. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/ui/ag_ui/_adapter.py +0 -0
  223. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/ui/ag_ui/_event_stream.py +0 -0
  224. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/ui/ag_ui/app.py +0 -0
  225. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/ui/vercel_ai/__init__.py +0 -0
  226. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/ui/vercel_ai/_adapter.py +0 -0
  227. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/ui/vercel_ai/_event_stream.py +0 -0
  228. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/ui/vercel_ai/_models.py +0 -0
  229. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/ui/vercel_ai/_utils.py +0 -0
  230. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/ui/vercel_ai/request_types.py +0 -0
  231. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/ui/vercel_ai/response_types.py +0 -0
  232. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/pydantic_ai/usage.py +0 -0
  233. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/_vendor/vendor.txt +0 -0
  234. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/cli/mcp.py +0 -0
  235. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/client/__init__.py +0 -0
  236. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/client/base.py +0 -0
  237. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/client/deserialize.py +0 -0
  238. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/client/invocation_handle.py +0 -0
  239. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/client/middleware.py +0 -0
  240. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/client/node_result.py +0 -0
  241. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/client/reply_dispatcher.py +0 -0
  242. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/exceptions.py +0 -0
  243. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/mcp/__init__.py +0 -0
  244. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/mcp/_adapt.py +0 -0
  245. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/mcp/_bridge.py +0 -0
  246. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/mcp/_codegen.py +0 -0
  247. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/mcp/_config.py +0 -0
  248. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/mcp/_dedup.py +0 -0
  249. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/mcp/_factory.py +0 -0
  250. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/mcp/_server.py +0 -0
  251. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/mcp/_session.py +0 -0
  252. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/mcp/_testing.py +0 -0
  253. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/mcp/_tool_def.py +0 -0
  254. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/mcp/exceptions.py +0 -0
  255. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/mcp/mcp.schema.json +0 -0
  256. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/models/__init__.py +0 -0
  257. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/models/actions.py +0 -0
  258. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/models/envelope.py +0 -0
  259. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/models/node_schema.py +0 -0
  260. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/models/payload.py +0 -0
  261. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/models/session_context.py +0 -0
  262. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/models/tool_context.py +0 -0
  263. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/nodes/__init__.py +0 -0
  264. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/nodes/base.py +0 -0
  265. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/nodes/consumer.py +0 -0
  266. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/nodes/node.py +0 -0
  267. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/nodes/tool.py +0 -0
  268. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/providers/__init__.py +0 -0
  269. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/providers/pydantic_ai/__init__.py +0 -0
  270. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/providers/pydantic_ai/anthropic.py +0 -0
  271. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/providers/pydantic_ai/model_client.py +0 -0
  272. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/providers/pydantic_ai/openai.py +0 -0
  273. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/provisioning/__init__.py +0 -0
  274. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/provisioning/config.py +0 -0
  275. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/provisioning/provisioner.py +0 -0
  276. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/worker/__init__.py +0 -0
  277. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/worker/lifecycle.py +0 -0
  278. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/worker/worker.py +0 -0
  279. {calfkit-0.5.2 → calfkit-0.5.4}/calfkit/worker/worker_config.py +0 -0
  280. {calfkit-0.5.2 → calfkit-0.5.4}/codecov.yml +0 -0
  281. {calfkit-0.5.2 → calfkit-0.5.4}/docs/calfkit-v1-design.md +0 -0
  282. {calfkit-0.5.2 → calfkit-0.5.4}/docs/calfkit-v1-dx-review.md +0 -0
  283. {calfkit-0.5.2 → calfkit-0.5.4}/docs/deps-as-dict-refactor.md +0 -0
  284. {calfkit-0.5.2 → calfkit-0.5.4}/docs/fire-and-forget-emit.md +0 -0
  285. {calfkit-0.5.2 → calfkit-0.5.4}/docs/hooks-design.md +0 -0
  286. {calfkit-0.5.2 → calfkit-0.5.4}/docs/mcp-discovery-rpc-design.md +0 -0
  287. {calfkit-0.5.2 → calfkit-0.5.4}/docs/mcp-json-schema-reference-design.md +0 -0
  288. {calfkit-0.5.2 → calfkit-0.5.4}/docs/mcp-overview.md +0 -0
  289. {calfkit-0.5.2 → calfkit-0.5.4}/docs/topic-provisioning.md +0 -0
  290. {calfkit-0.5.2 → calfkit-0.5.4}/docs/worker-lifecycle.md +0 -0
  291. {calfkit-0.5.2 → calfkit-0.5.4}/examples/__init__.py +0 -0
  292. {calfkit-0.5.2 → calfkit-0.5.4}/examples/deprecated/agent_dispatcher.py +0 -0
  293. {calfkit-0.5.2 → calfkit-0.5.4}/examples/deprecated/chat_node.py +0 -0
  294. {calfkit-0.5.2 → calfkit-0.5.4}/examples/deprecated/chat_repl_cli.py +0 -0
  295. {calfkit-0.5.2 → calfkit-0.5.4}/examples/deprecated/invoke_agent.py +0 -0
  296. {calfkit-0.5.2 → calfkit-0.5.4}/examples/deprecated/router_node.py +0 -0
  297. {calfkit-0.5.2 → calfkit-0.5.4}/examples/deprecated/tool_nodes.py +0 -0
  298. {calfkit-0.5.2 → calfkit-0.5.4}/examples/quickstart/agent_service.py +0 -0
  299. {calfkit-0.5.2 → calfkit-0.5.4}/examples/quickstart/emit.py +0 -0
  300. {calfkit-0.5.2 → calfkit-0.5.4}/examples/quickstart/invoke.py +0 -0
  301. {calfkit-0.5.2 → calfkit-0.5.4}/examples/quickstart/weather_sink.py +0 -0
  302. {calfkit-0.5.2 → calfkit-0.5.4}/examples/quickstart/weather_tool.py +0 -0
  303. {calfkit-0.5.2 → calfkit-0.5.4}/examples/quickstart_mcp/README.md +0 -0
  304. {calfkit-0.5.2 → calfkit-0.5.4}/examples/quickstart_mcp/agent_service.py +0 -0
  305. {calfkit-0.5.2 → calfkit-0.5.4}/examples/quickstart_mcp/everything_schemas.py +0 -0
  306. {calfkit-0.5.2 → calfkit-0.5.4}/examples/quickstart_mcp/invoke.py +0 -0
  307. {calfkit-0.5.2 → calfkit-0.5.4}/examples/quickstart_mcp/shared.py +0 -0
  308. {calfkit-0.5.2 → calfkit-0.5.4}/examples/quickstart_mcp/tools_service.py +0 -0
  309. {calfkit-0.5.2 → calfkit-0.5.4}/examples/rpc_worker.py +0 -0
  310. {calfkit-0.5.2 → calfkit-0.5.4}/examples/topic_provisioning.py +0 -0
  311. {calfkit-0.5.2 → calfkit-0.5.4}/release-please-config.json +0 -0
  312. {calfkit-0.5.2 → calfkit-0.5.4}/tests/__init__.py +0 -0
  313. {calfkit-0.5.2 → calfkit-0.5.4}/tests/conftest.py +0 -0
  314. {calfkit-0.5.2 → calfkit-0.5.4}/tests/integration/__init__.py +0 -0
  315. {calfkit-0.5.2 → calfkit-0.5.4}/tests/integration/test_agent_output_types.py +0 -0
  316. {calfkit-0.5.2 → calfkit-0.5.4}/tests/integration/test_agent_workers.py +0 -0
  317. {calfkit-0.5.2 → calfkit-0.5.4}/tests/integration/test_topic_provisioning.py +0 -0
  318. {calfkit-0.5.2 → calfkit-0.5.4}/tests/mcp/__init__.py +0 -0
  319. {calfkit-0.5.2 → calfkit-0.5.4}/tests/mcp/conftest.py +0 -0
  320. {calfkit-0.5.2 → calfkit-0.5.4}/tests/mcp/e2e/__init__.py +0 -0
  321. {calfkit-0.5.2 → calfkit-0.5.4}/tests/mcp/e2e/test_real_mcp_server.py +0 -0
  322. {calfkit-0.5.2 → calfkit-0.5.4}/tests/mcp/test_adapt.py +0 -0
  323. {calfkit-0.5.2 → calfkit-0.5.4}/tests/mcp/test_baseline_schema_only_dispatch.py +0 -0
  324. {calfkit-0.5.2 → calfkit-0.5.4}/tests/mcp/test_bridge.py +0 -0
  325. {calfkit-0.5.2 → calfkit-0.5.4}/tests/mcp/test_bridge_integration.py +0 -0
  326. {calfkit-0.5.2 → calfkit-0.5.4}/tests/mcp/test_cli.py +0 -0
  327. {calfkit-0.5.2 → calfkit-0.5.4}/tests/mcp/test_codegen.py +0 -0
  328. {calfkit-0.5.2 → calfkit-0.5.4}/tests/mcp/test_config.py +0 -0
  329. {calfkit-0.5.2 → calfkit-0.5.4}/tests/mcp/test_dedup.py +0 -0
  330. {calfkit-0.5.2 → calfkit-0.5.4}/tests/mcp/test_exceptions.py +0 -0
  331. {calfkit-0.5.2 → calfkit-0.5.4}/tests/mcp/test_factory.py +0 -0
  332. {calfkit-0.5.2 → calfkit-0.5.4}/tests/mcp/test_schema.py +0 -0
  333. {calfkit-0.5.2 → calfkit-0.5.4}/tests/mcp/test_server.py +0 -0
  334. {calfkit-0.5.2 → calfkit-0.5.4}/tests/mcp/test_session.py +0 -0
  335. {calfkit-0.5.2 → calfkit-0.5.4}/tests/mcp/test_testing.py +0 -0
  336. {calfkit-0.5.2 → calfkit-0.5.4}/tests/mcp/test_tool_def.py +0 -0
  337. {calfkit-0.5.2 → calfkit-0.5.4}/tests/mcp/test_worker_integration.py +0 -0
  338. {calfkit-0.5.2 → calfkit-0.5.4}/tests/providers.py +0 -0
  339. {calfkit-0.5.2 → calfkit-0.5.4}/tests/provisioning_cli_import_boom.py +0 -0
  340. {calfkit-0.5.2 → calfkit-0.5.4}/tests/test_co_tenant_tool_isolation.py +0 -0
  341. {calfkit-0.5.2 → calfkit-0.5.4}/tests/test_concurrent_tool_calls.py +0 -0
  342. {calfkit-0.5.2 → calfkit-0.5.4}/tests/test_consumer.py +0 -0
  343. {calfkit-0.5.2 → calfkit-0.5.4}/tests/test_fire_and_forget.py +0 -0
  344. {calfkit-0.5.2 → calfkit-0.5.4}/tests/test_gates.py +0 -0
  345. {calfkit-0.5.2 → calfkit-0.5.4}/tests/test_headers.py +0 -0
  346. {calfkit-0.5.2 → calfkit-0.5.4}/tests/test_instructions.py +0 -0
  347. {calfkit-0.5.2 → calfkit-0.5.4}/tests/test_lifecycle.py +0 -0
  348. {calfkit-0.5.2 → calfkit-0.5.4}/tests/test_lifecycle_e2e.py +0 -0
  349. {calfkit-0.5.2 → calfkit-0.5.4}/tests/test_lifecycle_resource_fields.py +0 -0
  350. {calfkit-0.5.2 → calfkit-0.5.4}/tests/test_lifecycle_resource_injection.py +0 -0
  351. {calfkit-0.5.2 → calfkit-0.5.4}/tests/test_lifecycle_review_fixes.py +0 -0
  352. {calfkit-0.5.2 → calfkit-0.5.4}/tests/test_model_settings.py +0 -0
  353. {calfkit-0.5.2 → calfkit-0.5.4}/tests/test_overrides.py +0 -0
  354. {calfkit-0.5.2 → calfkit-0.5.4}/tests/test_provisioning.py +0 -0
  355. {calfkit-0.5.2 → calfkit-0.5.4}/tests/test_provisioning_cli.py +0 -0
  356. {calfkit-0.5.2 → calfkit-0.5.4}/tests/test_provisioning_client.py +0 -0
  357. {calfkit-0.5.2 → calfkit-0.5.4}/tests/test_provisioning_worker.py +0 -0
  358. {calfkit-0.5.2 → calfkit-0.5.4}/tests/test_reply_dispatcher_ttl.py +0 -0
  359. {calfkit-0.5.2 → calfkit-0.5.4}/tests/test_serializable.py +0 -0
  360. {calfkit-0.5.2 → calfkit-0.5.4}/tests/test_skip_if_no_kafka_marker.py +0 -0
  361. {calfkit-0.5.2 → calfkit-0.5.4}/tests/test_tool_errors.py +0 -0
  362. {calfkit-0.5.2 → calfkit-0.5.4}/tests/test_worker_lifecycle.py +0 -0
  363. {calfkit-0.5.2 → calfkit-0.5.4}/tests/utils.py +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.5.4"
3
+ }
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.4](https://github.com/calf-ai/calfkit-sdk/compare/v0.5.3...v0.5.4) (2026-06-06)
4
+
5
+
6
+ ### Features
7
+
8
+ * agent-POV message-history projection for multi-agent channels ([#154](https://github.com/calf-ai/calfkit-sdk/issues/154)) ([#185](https://github.com/calf-ai/calfkit-sdk/issues/185)) ([86e69c5](https://github.com/calf-ai/calfkit-sdk/commit/86e69c5436369d088c8cf02122135adecda43759))
9
+
10
+ ## [0.5.3](https://github.com/calf-ai/calfkit-sdk/compare/v0.5.2...v0.5.3) (2026-06-06)
11
+
12
+
13
+ ### Features
14
+
15
+ * CLI add `calfkit run` to run nodes without Worker boilerplate ([#181](https://github.com/calf-ai/calfkit-sdk/issues/181)) ([6c1e38e](https://github.com/calf-ai/calfkit-sdk/commit/6c1e38e006f1481016dc132e502fa4eeb734d9ef))
16
+
3
17
  ## [0.5.2](https://github.com/calf-ai/calfkit-sdk/compare/v0.5.1...v0.5.2) (2026-06-06)
4
18
 
5
19
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: calfkit
3
- Version: 0.5.2
3
+ Version: 0.5.4
4
4
  Summary: Build AI workflows and agents as fully-distributed and event-driven microservices.
5
5
  Project-URL: Homepage, https://github.com/calf-ai/calfkit-sdk
6
6
  Project-URL: Repository, https://github.com/calf-ai/calfkit-sdk
@@ -36,9 +36,11 @@ Requires-Dist: typing-inspection>=0.4.0
36
36
  Requires-Dist: uuid-utils>=0.14.0
37
37
  Provides-Extra: cli
38
38
  Requires-Dist: typer>=0.12; extra == 'cli'
39
+ Requires-Dist: watchfiles>=0.21; extra == 'cli'
39
40
  Provides-Extra: dev
40
41
  Provides-Extra: mcp-codegen
41
42
  Requires-Dist: typer>=0.12; extra == 'mcp-codegen'
43
+ Requires-Dist: watchfiles>=0.21; extra == 'mcp-codegen'
42
44
  Description-Content-Type: text/markdown
43
45
 
44
46
  # 🐮 Calfkit SDK
@@ -100,9 +102,11 @@ Calfkit is a Python SDK that builds event-stream agents out-the-box. You get the
100
102
  ### 1. Install
101
103
 
102
104
  ```bash
103
- pip install calfkit
105
+ pip install "calfkit[cli]"
104
106
  ```
105
107
 
108
+ The `[cli]` extra adds the `calfkit` command used below to run nodes during development. (The library itself is just `pip install calfkit`.)
109
+
106
110
  <br>
107
111
 
108
112
  ### 2. Start a Calfkit Broker
@@ -140,10 +144,11 @@ You will be provided a Calfkit broker API to deploy your agents instead of setti
140
144
  > created on first use. Most hardened/managed brokers have that **disabled**, in
141
145
  > which case producers and consumers silently stall on a missing topic. Calfkit
142
146
  > ships an **EXPERIMENTAL, opt-in** topic provisioner (off by default) for the
143
- > dev/CI case: `Client.connect("localhost:9092", provisioning=ProvisioningConfig(enabled=True))`.
144
- > It is a development convenience (`replication_factor=1`, no ACLs) — **review it
145
- > before production**, where topic creation is typically ops-governed. See
146
- > [`docs/topic-provisioning.md`](docs/topic-provisioning.md).
147
+ > dev/CI case — pass `--provision` to `calfkit run` below (or
148
+ > `Client.connect("localhost:9092", provisioning=ProvisioningConfig(enabled=True))`
149
+ > in code). It is a development convenience (`replication_factor=1`, no ACLs) —
150
+ > **review it before production**, where topic creation is typically ops-governed.
151
+ > See [`docs/topic-provisioning.md`](docs/topic-provisioning.md).
147
152
 
148
153
  <br>
149
154
 
@@ -153,30 +158,19 @@ Define and deploy a tool as an independent service. Tools are not owned by or co
153
158
 
154
159
  ```python
155
160
  # weather_tool.py
156
- import asyncio
157
161
  from calfkit.nodes import agent_tool
158
- from calfkit.client import Client
159
- from calfkit.worker import Worker
160
162
 
161
163
  # Define a tool — the @agent_tool decorator turns any function into a deployable tool node
162
164
  @agent_tool
163
165
  def get_weather(location: str) -> str:
164
166
  """Get the current weather at a location"""
165
167
  return f"It's sunny in {location}"
166
-
167
- async def main():
168
- client = Client.connect("localhost:9092") # Connect to Kafka broker
169
- worker = Worker(client, nodes=[get_weather]) # Initialize a worker with the tool node
170
- await worker.run() # (Blocking call) Deploy the service to start serving traffic
171
-
172
- if __name__ == "__main__":
173
- asyncio.run(main())
174
168
  ```
175
169
 
176
- Run the file to deploy the tool service:
170
+ Deploy the tool as a service. `calfkit run` points at a `module:attr` target and starts a worker for you — no `Client`/`Worker` wiring required:
177
171
 
178
172
  ```shell
179
- python weather_tool.py
173
+ calfkit run weather_tool:get_weather
180
174
  ```
181
175
 
182
176
  <br>
@@ -187,11 +181,8 @@ Deploy the agent as its own service. The `Agent` handles LLM chat, tool orchestr
187
181
 
188
182
  ```python
189
183
  # agent_service.py
190
- import asyncio
191
184
  from calfkit.nodes import Agent
192
185
  from calfkit.providers import OpenAIResponsesModelClient
193
- from calfkit.client import Client
194
- from calfkit.worker import Worker
195
186
  from weather_tool import get_weather # Import the tool definition (reusable)
196
187
 
197
188
  agent = Agent(
@@ -201,14 +192,6 @@ agent = Agent(
201
192
  model_client=OpenAIResponsesModelClient(model_name="gpt-5.4-nano"),
202
193
  tools=[get_weather], # Register tool definitions with the agent
203
194
  )
204
-
205
- async def main():
206
- client = Client.connect("localhost:9092") # Connect to Kafka broker
207
- worker = Worker(client, nodes=[agent]) # Initialize a worker with the agent node
208
- await worker.run() # (Blocking call) Deploy the service to start serving traffic
209
-
210
- if __name__ == "__main__":
211
- asyncio.run(main())
212
195
  ```
213
196
 
214
197
  Set your OpenAI API key:
@@ -217,10 +200,10 @@ Set your OpenAI API key:
217
200
  export OPENAI_API_KEY=sk-...
218
201
  ```
219
202
 
220
- Run the file to deploy the agent service:
203
+ Deploy the agent as its own service (run it alongside the tool service from step 3):
221
204
 
222
205
  ```shell
223
- python agent_service.py
206
+ calfkit run agent_service:agent
224
207
  ```
225
208
 
226
209
  <br>
@@ -240,7 +223,7 @@ async def main():
240
223
  # Send a request and await the response
241
224
  result = await client.execute_node(
242
225
  "What's the weather in Tokyo?",
243
- "agent.input", # The topic the agent subscribes to
226
+ "weather_agent.input", # The topic the agent subscribes to
244
227
  )
245
228
  print(f"Assistant: {result.output}")
246
229
 
@@ -256,6 +239,34 @@ python invoke.py
256
239
 
257
240
  <br>
258
241
 
242
+ ### Deploying to production
243
+
244
+ `calfkit run` is a **development** convenience — it imports your module and starts a worker for you (and `--reload` restarts it on edits). For production, deploy each node with an explicit `Worker` so startup, scaling, and topic governance stay under your control:
245
+
246
+ ```python
247
+ # serve_tool.py — deploy the tool as its own service
248
+ import asyncio
249
+ from calfkit.client import Client
250
+ from calfkit.worker import Worker
251
+ from weather_tool import get_weather
252
+
253
+ async def main():
254
+ client = Client.connect("localhost:9092") # Connect to Kafka broker
255
+ worker = Worker(client, nodes=[get_weather]) # One service per node
256
+ await worker.run() # (Blocking) serve until stopped
257
+
258
+ if __name__ == "__main__":
259
+ asyncio.run(main())
260
+ ```
261
+
262
+ ```shell
263
+ python serve_tool.py
264
+ ```
265
+
266
+ See the **[CLI reference](docs/cli.md)** for every `calfkit run` flag (`--host`, `--provision`, `--reload`, `--app-dir`, …) and the other `calfkit` commands (`mcp`, `topics`).
267
+
268
+ <br>
269
+
259
270
  ### Structured Outputs (Optional)
260
271
 
261
272
  Agents can be deployed with a `final_output_type` to enforce structured output from the LLM. The output is type-safe and deserialized automatically on the client side.
@@ -310,6 +321,8 @@ result = await client.execute_node(
310
321
  )
311
322
  ```
312
323
 
324
+ The same `message_history` can carry turns from *multiple* agents — see [`examples/multi_agent_panel/`](examples/multi_agent_panel/) for a multi-agent discussion over one shared transcript.
325
+
313
326
  **Runtime dependency injection** — pass runtime data to tools via the `deps` parameter:
314
327
 
315
328
  ```python
@@ -468,7 +481,11 @@ See [`docs/mcp-overview.md`](docs/mcp-overview.md) for the quickstart, deploymen
468
481
 
469
482
  ## Documentation
470
483
 
471
- Full documentation is coming soon. In the meantime, this README serves as the primary reference for getting started with Calfkit.
484
+ Full documentation is coming soon. In the meantime, this README serves as the primary reference for getting started with Calfkit. Deeper guides live in [`docs/`](docs/):
485
+
486
+ - [CLI reference](docs/cli.md) — `calfkit run`, `mcp`, and `topics` commands
487
+ - [MCP adaptor](docs/mcp-overview.md) — expose MCP servers as calfkit tools
488
+ - [Topic provisioning](docs/topic-provisioning.md) — experimental dev topic auto-creation
472
489
 
473
490
  Deep-dive guides:
474
491
 
@@ -57,9 +57,11 @@ Calfkit is a Python SDK that builds event-stream agents out-the-box. You get the
57
57
  ### 1. Install
58
58
 
59
59
  ```bash
60
- pip install calfkit
60
+ pip install "calfkit[cli]"
61
61
  ```
62
62
 
63
+ The `[cli]` extra adds the `calfkit` command used below to run nodes during development. (The library itself is just `pip install calfkit`.)
64
+
63
65
  <br>
64
66
 
65
67
  ### 2. Start a Calfkit Broker
@@ -97,10 +99,11 @@ You will be provided a Calfkit broker API to deploy your agents instead of setti
97
99
  > created on first use. Most hardened/managed brokers have that **disabled**, in
98
100
  > which case producers and consumers silently stall on a missing topic. Calfkit
99
101
  > ships an **EXPERIMENTAL, opt-in** topic provisioner (off by default) for the
100
- > dev/CI case: `Client.connect("localhost:9092", provisioning=ProvisioningConfig(enabled=True))`.
101
- > It is a development convenience (`replication_factor=1`, no ACLs) — **review it
102
- > before production**, where topic creation is typically ops-governed. See
103
- > [`docs/topic-provisioning.md`](docs/topic-provisioning.md).
102
+ > dev/CI case — pass `--provision` to `calfkit run` below (or
103
+ > `Client.connect("localhost:9092", provisioning=ProvisioningConfig(enabled=True))`
104
+ > in code). It is a development convenience (`replication_factor=1`, no ACLs) —
105
+ > **review it before production**, where topic creation is typically ops-governed.
106
+ > See [`docs/topic-provisioning.md`](docs/topic-provisioning.md).
104
107
 
105
108
  <br>
106
109
 
@@ -110,30 +113,19 @@ Define and deploy a tool as an independent service. Tools are not owned by or co
110
113
 
111
114
  ```python
112
115
  # weather_tool.py
113
- import asyncio
114
116
  from calfkit.nodes import agent_tool
115
- from calfkit.client import Client
116
- from calfkit.worker import Worker
117
117
 
118
118
  # Define a tool — the @agent_tool decorator turns any function into a deployable tool node
119
119
  @agent_tool
120
120
  def get_weather(location: str) -> str:
121
121
  """Get the current weather at a location"""
122
122
  return f"It's sunny in {location}"
123
-
124
- async def main():
125
- client = Client.connect("localhost:9092") # Connect to Kafka broker
126
- worker = Worker(client, nodes=[get_weather]) # Initialize a worker with the tool node
127
- await worker.run() # (Blocking call) Deploy the service to start serving traffic
128
-
129
- if __name__ == "__main__":
130
- asyncio.run(main())
131
123
  ```
132
124
 
133
- Run the file to deploy the tool service:
125
+ Deploy the tool as a service. `calfkit run` points at a `module:attr` target and starts a worker for you — no `Client`/`Worker` wiring required:
134
126
 
135
127
  ```shell
136
- python weather_tool.py
128
+ calfkit run weather_tool:get_weather
137
129
  ```
138
130
 
139
131
  <br>
@@ -144,11 +136,8 @@ Deploy the agent as its own service. The `Agent` handles LLM chat, tool orchestr
144
136
 
145
137
  ```python
146
138
  # agent_service.py
147
- import asyncio
148
139
  from calfkit.nodes import Agent
149
140
  from calfkit.providers import OpenAIResponsesModelClient
150
- from calfkit.client import Client
151
- from calfkit.worker import Worker
152
141
  from weather_tool import get_weather # Import the tool definition (reusable)
153
142
 
154
143
  agent = Agent(
@@ -158,14 +147,6 @@ agent = Agent(
158
147
  model_client=OpenAIResponsesModelClient(model_name="gpt-5.4-nano"),
159
148
  tools=[get_weather], # Register tool definitions with the agent
160
149
  )
161
-
162
- async def main():
163
- client = Client.connect("localhost:9092") # Connect to Kafka broker
164
- worker = Worker(client, nodes=[agent]) # Initialize a worker with the agent node
165
- await worker.run() # (Blocking call) Deploy the service to start serving traffic
166
-
167
- if __name__ == "__main__":
168
- asyncio.run(main())
169
150
  ```
170
151
 
171
152
  Set your OpenAI API key:
@@ -174,10 +155,10 @@ Set your OpenAI API key:
174
155
  export OPENAI_API_KEY=sk-...
175
156
  ```
176
157
 
177
- Run the file to deploy the agent service:
158
+ Deploy the agent as its own service (run it alongside the tool service from step 3):
178
159
 
179
160
  ```shell
180
- python agent_service.py
161
+ calfkit run agent_service:agent
181
162
  ```
182
163
 
183
164
  <br>
@@ -197,7 +178,7 @@ async def main():
197
178
  # Send a request and await the response
198
179
  result = await client.execute_node(
199
180
  "What's the weather in Tokyo?",
200
- "agent.input", # The topic the agent subscribes to
181
+ "weather_agent.input", # The topic the agent subscribes to
201
182
  )
202
183
  print(f"Assistant: {result.output}")
203
184
 
@@ -213,6 +194,34 @@ python invoke.py
213
194
 
214
195
  <br>
215
196
 
197
+ ### Deploying to production
198
+
199
+ `calfkit run` is a **development** convenience — it imports your module and starts a worker for you (and `--reload` restarts it on edits). For production, deploy each node with an explicit `Worker` so startup, scaling, and topic governance stay under your control:
200
+
201
+ ```python
202
+ # serve_tool.py — deploy the tool as its own service
203
+ import asyncio
204
+ from calfkit.client import Client
205
+ from calfkit.worker import Worker
206
+ from weather_tool import get_weather
207
+
208
+ async def main():
209
+ client = Client.connect("localhost:9092") # Connect to Kafka broker
210
+ worker = Worker(client, nodes=[get_weather]) # One service per node
211
+ await worker.run() # (Blocking) serve until stopped
212
+
213
+ if __name__ == "__main__":
214
+ asyncio.run(main())
215
+ ```
216
+
217
+ ```shell
218
+ python serve_tool.py
219
+ ```
220
+
221
+ See the **[CLI reference](docs/cli.md)** for every `calfkit run` flag (`--host`, `--provision`, `--reload`, `--app-dir`, …) and the other `calfkit` commands (`mcp`, `topics`).
222
+
223
+ <br>
224
+
216
225
  ### Structured Outputs (Optional)
217
226
 
218
227
  Agents can be deployed with a `final_output_type` to enforce structured output from the LLM. The output is type-safe and deserialized automatically on the client side.
@@ -267,6 +276,8 @@ result = await client.execute_node(
267
276
  )
268
277
  ```
269
278
 
279
+ The same `message_history` can carry turns from *multiple* agents — see [`examples/multi_agent_panel/`](examples/multi_agent_panel/) for a multi-agent discussion over one shared transcript.
280
+
270
281
  **Runtime dependency injection** — pass runtime data to tools via the `deps` parameter:
271
282
 
272
283
  ```python
@@ -425,7 +436,11 @@ See [`docs/mcp-overview.md`](docs/mcp-overview.md) for the quickstart, deploymen
425
436
 
426
437
  ## Documentation
427
438
 
428
- Full documentation is coming soon. In the meantime, this README serves as the primary reference for getting started with Calfkit.
439
+ Full documentation is coming soon. In the meantime, this README serves as the primary reference for getting started with Calfkit. Deeper guides live in [`docs/`](docs/):
440
+
441
+ - [CLI reference](docs/cli.md) — `calfkit run`, `mcp`, and `topics` commands
442
+ - [MCP adaptor](docs/mcp-overview.md) — expose MCP servers as calfkit tools
443
+ - [Topic provisioning](docs/topic-provisioning.md) — experimental dev topic auto-creation
429
444
 
430
445
  Deep-dive guides:
431
446
 
@@ -7,6 +7,7 @@ An index of potential features and changes under consideration for Calf SDK. Eac
7
7
  - [Deps-as-dict refactor](docs/deps-as-dict-refactor.md) — `ctx.deps`/`result.deps` are plain dicts and `correlation_id` is a top-level context attribute (closes #144; breaking)
8
8
  - [Fire-and-forget emit](docs/fire-and-forget-emit.md) — true one-way Client.emit_to_node; nullable callback terminal; opt-in reply TTL (closes #132)
9
9
  - [Topic Provisioning](docs/topic-provisioning.md) — EXPERIMENTAL opt-in `ProvisioningConfig` to best-effort create Kafka topics for dev/CI (off by default; review for prod)
10
+ - [Agent-POV History Projection](docs/agent-pov-projection.md) — always-on POV projection over `message_history` + `ModelResponse.name` stamping for shared-channel multi-agent; portable content-prefix attribution (PR #185; closes #154)
10
11
 
11
12
  ## Proposed
12
13
 
@@ -1,8 +1,9 @@
1
1
  """calfkit top-level CLI entry point.
2
2
 
3
3
  Mounts subcommands as typer sub-apps: ``mcp`` (with ``mcp codegen`` /
4
- ``mcp schema``) and ``topics`` (with ``topics provision``). Future
5
- subcommands land alongside via the same mounting pattern.
4
+ ``mcp schema``) and ``topics`` (with ``topics provision``), plus the
5
+ top-level ``run`` command. Future subcommands land alongside via the same
6
+ mounting pattern.
6
7
 
7
8
  Invoked via the ``calfkit`` console script registered in pyproject.toml's
8
9
  ``[project.scripts]``.
@@ -27,11 +28,13 @@ def _build_app() -> Any:
27
28
  raise SystemExit("The calfkit CLI requires the 'cli' optional extra. Install with: pip install calfkit[cli]") from e
28
29
 
29
30
  from calfkit.cli.mcp import app as mcp_app
31
+ from calfkit.cli.run import run as run_command
30
32
  from calfkit.cli.topics import app as topics_app
31
33
 
32
34
  app = typer.Typer(name="calfkit", help="Calfkit SDK command-line tools.", no_args_is_help=True)
33
35
  app.add_typer(mcp_app, name="mcp")
34
36
  app.add_typer(topics_app, name="topics")
37
+ app.command(name="run")(run_command)
35
38
  return app
36
39
 
37
40
 
@@ -0,0 +1,165 @@
1
+ """Shared node loader for the calfkit CLI.
2
+
3
+ Resolves ``module:attr`` specs into a flat list of node objects. Used by both
4
+ ``calfkit topics provision`` (``--nodes module:attr``) and ``calfkit run``
5
+ (positional ``module:attr`` targets).
6
+
7
+ Each ``attr`` may resolve to a single node or an iterable of nodes; iterables
8
+ are expanded. ``McpServer`` instances are treated as scalars even though they
9
+ are iterable (their ``__iter__`` yields tool defs) so a directly-resolved MCP
10
+ server is preserved rather than splatted.
11
+
12
+ Requires the ``cli`` optional extra (typer). If typer is not installed, the
13
+ import raises with a clear remediation message rather than silently failing.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import importlib
19
+ import os
20
+ import sys
21
+ from collections.abc import Iterable
22
+ from typing import Any
23
+
24
+ try:
25
+ import typer
26
+ except ImportError as e: # pragma: no cover -- exercised manually
27
+ raise ImportError("the calfkit CLI requires the 'cli' optional extra. Install with: pip install calfkit[cli]") from e
28
+
29
+
30
+ def resolve_specs(specs: list[str], *, app_dir: str | None = None, source_label: str = "target") -> list[Any]:
31
+ """Resolve ``module:attr`` specs into a flat list of resolved objects.
32
+
33
+ Each ``attr`` may be a single object or an iterable of objects; iterables
34
+ (other than ``str``/``bytes`` and ``McpServer``) are expanded. Results are
35
+ concatenated in the order the specs were supplied.
36
+
37
+ Args:
38
+ specs: ``module:attr`` strings.
39
+ app_dir: Optional directory inserted at ``sys.path[0]`` before importing
40
+ so targets are resolvable relative to it (e.g. the project root the
41
+ user runs the command from). The path is absolutised; duplicates are
42
+ not re-inserted.
43
+ source_label: How to name the spec source in error messages (e.g.
44
+ ``"target"`` for ``calfkit run`` or ``"--nodes"`` for
45
+ ``calfkit topics provision``).
46
+
47
+ Raises:
48
+ typer.Exit: (code 2) on a malformed spec, an import failure, or a
49
+ missing attribute.
50
+ """
51
+ from calfkit.mcp._server import McpServer
52
+
53
+ if app_dir is not None:
54
+ abs_dir = os.path.abspath(app_dir)
55
+ if abs_dir not in sys.path:
56
+ sys.path.insert(0, abs_dir)
57
+
58
+ resolved: list[Any] = []
59
+ for spec in specs:
60
+ module_path, sep, attr = spec.partition(":")
61
+ if not sep or not module_path or not attr:
62
+ typer.echo(
63
+ f"Error: {source_label} must be in 'module:attr' form, got {spec!r}.",
64
+ err=True,
65
+ )
66
+ raise typer.Exit(2)
67
+ try:
68
+ module = importlib.import_module(module_path)
69
+ except (ModuleNotFoundError, ImportError) as e:
70
+ # The module (or one of its imports) does not exist / cannot load.
71
+ typer.echo(f"Error: cannot import module {module_path!r}: {e}", err=True)
72
+ raise typer.Exit(2) from e
73
+ except Exception as e: # noqa: BLE001 -- side-effect code at import time failed
74
+ # The module exists but its top-level code raised when executed.
75
+ typer.echo(
76
+ f"Error: module {module_path!r} raised during import (side-effect code failed): {e}",
77
+ err=True,
78
+ )
79
+ raise typer.Exit(2) from e
80
+ try:
81
+ obj = getattr(module, attr)
82
+ except AttributeError as e:
83
+ typer.echo(f"Error: module {module_path!r} has no attribute {attr!r}.", err=True)
84
+ raise typer.Exit(2) from e
85
+
86
+ # A single node has ``subscribe_topics`` but is not itself a plain
87
+ # iterable of nodes. Strings/bytes are iterable too; an McpServer is
88
+ # iterable (its ``__iter__`` yields tool defs) but must be kept whole.
89
+ # Treat all three as scalars; expand everything else iterable.
90
+ if isinstance(obj, (str, bytes, McpServer)) or not isinstance(obj, Iterable):
91
+ resolved.append(obj)
92
+ else:
93
+ resolved.extend(obj)
94
+ return resolved
95
+
96
+
97
+ def validate_nodes(objs: list[Any], *, source_label: str = "target") -> None:
98
+ """Ensure every object is a node or an ``McpServer``.
99
+
100
+ A node is duck-typed by the attributes the worker wiring relies on
101
+ (``subscribe_topics`` and ``_return_topic``). ``McpServer`` instances are
102
+ allowed through unconditionally — the worker expands them into per-tool
103
+ bridges at startup.
104
+
105
+ Args:
106
+ objs: Resolved objects to validate.
107
+ source_label: How to name the spec source in error messages.
108
+
109
+ Raises:
110
+ typer.Exit: (code 2) if any object is neither a node nor an McpServer.
111
+ """
112
+ from calfkit.mcp._server import McpServer
113
+
114
+ for obj in objs:
115
+ if isinstance(obj, McpServer):
116
+ continue
117
+ missing = [attr for attr in ("subscribe_topics", "_return_topic") if not hasattr(obj, attr)]
118
+ if missing:
119
+ typer.echo(
120
+ f"Error: resolved object {obj!r} is not a node "
121
+ f"(missing {', '.join(missing)}). {source_label} must point at a "
122
+ "BaseNodeDef or McpServer (or an iterable of them).",
123
+ err=True,
124
+ )
125
+ raise typer.Exit(2)
126
+
127
+
128
+ def load_nodes(targets: list[str], *, app_dir: str | None = None, source_label: str = "target") -> list[Any]:
129
+ """Resolve, validate, and de-duplicate ``targets`` into a non-empty node list.
130
+
131
+ The composition used by ``calfkit run`` for both the inline (no-reload) path
132
+ and the parent-side pre-flight before the reload supervisor starts. Raising
133
+ on an empty result means a misconfigured run fails fast and loud (exit 2)
134
+ instead of starting an idle worker.
135
+
136
+ Raises:
137
+ typer.Exit: (code 2) on a bad spec, import failure, non-node object, or
138
+ if ``targets`` resolve to zero nodes.
139
+ """
140
+ objs = resolve_specs(targets, app_dir=app_dir, source_label=source_label)
141
+ validate_nodes(objs, source_label=source_label)
142
+ nodes = dedupe_by_node_id(objs)
143
+ if not nodes:
144
+ typer.echo("Error: no nodes resolved from the given target(s).", err=True)
145
+ raise typer.Exit(2)
146
+ return nodes
147
+
148
+
149
+ def dedupe_by_node_id(objs: list[Any]) -> list[Any]:
150
+ """Drop duplicate nodes by ``node_id``, preserving first-seen order.
151
+
152
+ Objects without a ``node_id`` (e.g. ``McpServer``) are keyed by identity so
153
+ they are always retained.
154
+ """
155
+ seen: set[Any] = set()
156
+ out: list[Any] = []
157
+ for obj in objs:
158
+ key = getattr(obj, "node_id", None)
159
+ if key is None:
160
+ key = id(obj)
161
+ if key in seen:
162
+ continue
163
+ seen.add(key)
164
+ out.append(obj)
165
+ return out