stackone-ai 2.8.0__tar.gz → 2.9.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. stackone_ai-2.9.1/.release-please-manifest.json +3 -0
  2. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/CHANGELOG.md +15 -0
  3. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/PKG-INFO +81 -7
  4. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/README.md +78 -6
  5. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/examples/pydantic_ai_integration.py +12 -24
  6. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/pyproject.toml +2 -1
  7. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/stackone_ai/__init__.py +1 -1
  8. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/stackone_ai/models.py +112 -6
  9. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/stackone_ai/toolset.py +56 -8
  10. stackone_ai-2.9.1/tests/test_integrations_pydantic_ai.py +183 -0
  11. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/tests/test_tool_calling.py +186 -1
  12. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/uv.lock +6 -2
  13. stackone_ai-2.8.0/.release-please-manifest.json +0 -3
  14. stackone_ai-2.8.0/vendor/stackone-ai-node/.git +0 -1
  15. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/.claude/rules/development-workflow.md +0 -0
  16. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/.claude/rules/examples-standards.md +0 -0
  17. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/.claude/rules/git-workflow.md +0 -0
  18. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/.claude/rules/nix-workflow.md +0 -0
  19. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/.claude/rules/no-relative-imports.md +0 -0
  20. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/.claude/rules/package-installation.md +0 -0
  21. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/.claude/rules/release-please-standards.md +0 -0
  22. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/.claude/rules/uv-scripts.md +0 -0
  23. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/.cursor/rules/development-workflow.mdc +0 -0
  24. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/.cursor/rules/examples-standards.mdc +0 -0
  25. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/.cursor/rules/git-workflow.mdc +0 -0
  26. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/.cursor/rules/no-relative-imports.mdc +0 -0
  27. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/.cursor/rules/package-installation.mdc +0 -0
  28. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/.cursor/rules/release-please-standards.mdc +0 -0
  29. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/.cursor/rules/uv-scripts.mdc +0 -0
  30. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/.env.example +0 -0
  31. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/.envrc +0 -0
  32. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/.github/actions/setup-nix/action.yaml +0 -0
  33. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/.github/dependabot.yaml +0 -0
  34. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/.github/workflows/ci.yaml +0 -0
  35. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/.github/workflows/nix-flake-update.yaml +0 -0
  36. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/.github/workflows/nix-flake.yaml +0 -0
  37. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/.github/workflows/release.yaml +0 -0
  38. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/.gitignore +0 -0
  39. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/.gitleaks.toml +0 -0
  40. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/.gitmodules +0 -0
  41. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/.mcp.json +0 -0
  42. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/.release-please-config.json +0 -0
  43. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/CLAUDE.md +0 -0
  44. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/LICENSE +0 -0
  45. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/examples/auth_management.py +0 -0
  46. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/examples/crewai_integration.py +0 -0
  47. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/examples/langchain_integration.py +0 -0
  48. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/examples/langgraph_integration.py +0 -0
  49. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/examples/openai_integration.py +0 -0
  50. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/examples/search_tools.py +0 -0
  51. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/examples/test_examples.py +0 -0
  52. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/flake.lock +0 -0
  53. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/flake.nix +0 -0
  54. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/justfile +0 -0
  55. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/scripts/benchmark_search.py +0 -0
  56. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/stackone_ai/constants.py +0 -0
  57. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/stackone_ai/feedback/__init__.py +0 -0
  58. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/stackone_ai/feedback/tool.py +0 -0
  59. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/stackone_ai/integrations/__init__.py +0 -0
  60. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/stackone_ai/integrations/langgraph.py +0 -0
  61. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/stackone_ai/local_search.py +0 -0
  62. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/stackone_ai/py.typed +0 -0
  63. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/stackone_ai/semantic_search.py +0 -0
  64. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/stackone_ai/utils/__init__.py +0 -0
  65. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/stackone_ai/utils/normalize.py +0 -0
  66. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/stackone_ai/utils/tfidf_index.py +0 -0
  67. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/tests/conftest.py +0 -0
  68. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/tests/mocks/serve.ts +0 -0
  69. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/tests/test_agent_tools.py +0 -0
  70. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/tests/test_feedback.py +0 -0
  71. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/tests/test_fetch_tools.py +0 -0
  72. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/tests/test_integrations_langgraph.py +0 -0
  73. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/tests/test_local_search.py +0 -0
  74. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/tests/test_models.py +0 -0
  75. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/tests/test_semantic_search.py +0 -0
  76. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/tests/test_tfidf_index.py +0 -0
  77. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/tests/test_toolset.py +0 -0
  78. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/.claude/rules/development-workflow.md +0 -0
  79. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/.claude/rules/file-operations.md +0 -0
  80. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/.claude/rules/git-workflow.md +0 -0
  81. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/.claude/rules/pnpm-usage.md +0 -0
  82. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/.claude/rules/typescript-patterns.md +0 -0
  83. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/.claude/rules/typescript-testing.md +0 -0
  84. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/.claude/skills/orama-integration/SKILL.md +0 -0
  85. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/.cursor/rules/development-workflow.mdc +0 -0
  86. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/.cursor/rules/file-operations.mdc +0 -0
  87. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/.cursor/rules/git-workflow.mdc +0 -0
  88. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/.cursor/rules/orama-integration.mdc +0 -0
  89. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/.cursor/rules/pnpm-usage.mdc +0 -0
  90. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/.cursor/rules/typescript-patterns.mdc +0 -0
  91. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/.cursor/rules/typescript-testing.mdc +0 -0
  92. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/.envrc +0 -0
  93. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/.github/CODEOWNERS +0 -0
  94. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/.github/actions/setup-nix/action.yaml +0 -0
  95. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/.github/workflows/check-title.yaml +0 -0
  96. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/.github/workflows/ci.yaml +0 -0
  97. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/.github/workflows/claude.yaml +0 -0
  98. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/.github/workflows/dry-publish.yaml +0 -0
  99. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/.github/workflows/nix-flake.yaml +0 -0
  100. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/.github/workflows/release.yaml +0 -0
  101. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/.gitignore +0 -0
  102. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/.gitleaks.toml +0 -0
  103. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/.mcp.json +0 -0
  104. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/.oxfmtrc.jsonc +0 -0
  105. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/.oxlintrc.jsonc +0 -0
  106. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/AGENTS.md +0 -0
  107. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/CHANGELOG.md +0 -0
  108. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/CLAUDE.md +0 -0
  109. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/LICENSE +0 -0
  110. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/README.md +0 -0
  111. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/examples/README.md +0 -0
  112. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/examples/ai-sdk-integration.test.ts +0 -0
  113. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/examples/ai-sdk-integration.ts +0 -0
  114. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/examples/anthropic-integration.ts +0 -0
  115. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/examples/claude-agent-sdk-integration.test.ts +0 -0
  116. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/examples/claude-agent-sdk-integration.ts +0 -0
  117. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/examples/fetch-tools-debug.ts +0 -0
  118. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/examples/fetch-tools.test.ts +0 -0
  119. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/examples/fetch-tools.ts +0 -0
  120. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/examples/meta-tools.ts +0 -0
  121. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/examples/openai-integration.test.ts +0 -0
  122. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/examples/openai-integration.ts +0 -0
  123. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/examples/openai-responses-integration.test.ts +0 -0
  124. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/examples/openai-responses-integration.ts +0 -0
  125. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/examples/package.json +0 -0
  126. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/examples/tanstack-ai-integration.test.ts +0 -0
  127. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/examples/tanstack-ai-integration.ts +0 -0
  128. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/examples/tsconfig.json +0 -0
  129. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/flake.lock +0 -0
  130. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/flake.nix +0 -0
  131. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/knip.config.ts +0 -0
  132. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/lefthook.yaml +0 -0
  133. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/mocks/handlers.example-api.ts +0 -0
  134. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/mocks/handlers.mcp.ts +0 -0
  135. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/mocks/handlers.openai.ts +0 -0
  136. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/mocks/handlers.stackone-ai.ts +0 -0
  137. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/mocks/handlers.stackone-rpc.ts +0 -0
  138. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/mocks/handlers.ts +0 -0
  139. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/mocks/handlers.utils.ts +0 -0
  140. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/mocks/mcp-server.ts +0 -0
  141. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/mocks/node.ts +0 -0
  142. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/package.json +0 -0
  143. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/pnpm-lock.yaml +0 -0
  144. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/pnpm-workspace.yaml +0 -0
  145. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/consts.ts +0 -0
  146. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/feedback.test.ts +0 -0
  147. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/feedback.ts +0 -0
  148. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/headers.test.ts +0 -0
  149. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/headers.ts +0 -0
  150. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/index.ts +0 -0
  151. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/mcp-client.test.ts +0 -0
  152. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/mcp-client.ts +0 -0
  153. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/requestBuilder.test.ts +0 -0
  154. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/requestBuilder.ts +0 -0
  155. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/rpc-client.test.ts +0 -0
  156. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/rpc-client.ts +0 -0
  157. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/schema.ts +0 -0
  158. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/tool.test-d.ts +0 -0
  159. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/tool.test.ts +0 -0
  160. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/tool.ts +0 -0
  161. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/toolsets.test-d.ts +0 -0
  162. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/toolsets.test.ts +0 -0
  163. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/toolsets.ts +0 -0
  164. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/types.ts +0 -0
  165. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/utils/array.test.ts +0 -0
  166. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/utils/array.ts +0 -0
  167. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/utils/error-stackone-api.test.ts +0 -0
  168. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/utils/error-stackone-api.ts +0 -0
  169. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/utils/error-stackone.test.ts +0 -0
  170. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/utils/error-stackone.ts +0 -0
  171. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/utils/tfidf-index.test.ts +0 -0
  172. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/utils/tfidf-index.ts +0 -0
  173. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/utils/try-import.test.ts +0 -0
  174. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/utils/try-import.ts +0 -0
  175. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/src/utils/type.ts +0 -0
  176. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/tsconfig.json +0 -0
  177. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/tsdown.config.ts +0 -0
  178. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/vitest.config.ts +0 -0
  179. {stackone_ai-2.8.0 → stackone_ai-2.9.1}/vendor/stackone-ai-node/vitest.setup.ts +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "2.9.1"
3
+ }
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.9.1](https://github.com/StackOneHQ/stackone-ai-python/compare/stackone-ai-v2.9.0...stackone-ai-v2.9.1) (2026-06-03)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **models:** handle binary file downloads in tool execution ([#190](https://github.com/StackOneHQ/stackone-ai-python/issues/190)) ([8983c68](https://github.com/StackOneHQ/stackone-ai-python/commit/8983c68dd2fdbdbdd52af531a9a9f1de6d72e1b8))
9
+ * **search:** make ToolsetConfigError actionable and fix misleading docs ([#188](https://github.com/StackOneHQ/stackone-ai-python/issues/188)) ([d332c27](https://github.com/StackOneHQ/stackone-ai-python/commit/d332c27d2f7b827e7ca043f60c37cb0831a92361))
10
+
11
+ ## [2.9.0](https://github.com/StackOneHQ/stackone-ai-python/compare/stackone-ai-v2.8.0...stackone-ai-v2.9.0) (2026-04-30)
12
+
13
+
14
+ ### Features
15
+
16
+ * **integrations:** add native Pydantic AI support ([#184](https://github.com/StackOneHQ/stackone-ai-python/issues/184)) ([8216ab6](https://github.com/StackOneHQ/stackone-ai-python/commit/8216ab6c0776b0753cf3e68461bf5e8a9b044b06))
17
+
3
18
  ## [2.8.0](https://github.com/StackOneHQ/stackone-ai-python/compare/stackone-ai-v2.7.0...stackone-ai-v2.8.0) (2026-04-20)
4
19
 
5
20
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stackone-ai
3
- Version: 2.8.0
3
+ Version: 2.9.1
4
4
  Summary: agents performing actions on your SaaS
5
5
  Author-email: StackOne <support@stackone.com>
6
6
  License-File: LICENSE
@@ -29,6 +29,8 @@ Requires-Dist: pydantic-ai>=1.83.0; extra == 'examples'
29
29
  Requires-Dist: python-dotenv>=1.0.1; extra == 'examples'
30
30
  Provides-Extra: mcp
31
31
  Requires-Dist: mcp>=1.3.0; extra == 'mcp'
32
+ Provides-Extra: pydantic-ai
33
+ Requires-Dist: pydantic-ai-slim<2.0.0,>=1.83.0; extra == 'pydantic-ai'
32
34
  Description-Content-Type: text/markdown
33
35
 
34
36
  # StackOne AI SDK
@@ -59,6 +61,7 @@ StackOne AI provides a unified interface for accessing various SaaS tools throug
59
61
  - LangChain Tools
60
62
  - CrewAI Tools
61
63
  - LangGraph Tool Node
64
+ - Pydantic AI Toolset
62
65
 
63
66
  ## Requirements
64
67
 
@@ -146,6 +149,35 @@ tools = toolset.fetch_tools(providers=["hibob"])
146
149
  - Glob pattern: `["*_list_employees"]` matches all tools ending with `_list_employees`
147
150
  - Provider prefix: `["workday_*"]` matches all Workday tools
148
151
 
152
+ ## File Downloads
153
+
154
+ Actions that return a file — e.g. `googledrive_unified_download_file`, `documents_download_file`, any `*_unified_download_file` — resolve to **raw bytes plus metadata**, not parsed JSON. The SDK decides this from the response `Content-Type`: a JSON content type is parsed as before; anything else is treated as a file download. This applies to both `tool.execute()` and `tool.call()`.
155
+
156
+ ```python
157
+ tools = toolset.fetch_tools(actions=["googledrive_*"], account_ids=[account_id])
158
+ download = tools.get_tool("googledrive_unified_download_file")
159
+
160
+ result = download.execute({"id": "file-id"})
161
+
162
+ # `result` is a dict describing the file — write the bytes straight to disk:
163
+ with open(result["file_name"] or "download.bin", "wb") as f:
164
+ f.write(result["content"])
165
+ ```
166
+
167
+ The returned dict:
168
+
169
+ | Key | Type | Description |
170
+ | -------------- | ------------- | -------------------------------------------------------------------------------------------- |
171
+ | `content` | `bytes` | Raw file bytes. **Not JSON-serializable** — see the caveat below. |
172
+ | `content_type` | `str` | The file's MIME type (e.g. `application/pdf`), or `application/octet-stream` if unspecified. |
173
+ | `status_code` | `int` | HTTP status of the download response. |
174
+ | `headers` | `dict` | Response headers. |
175
+ | `file_name` | `str \| None` | Filename from the `Content-Disposition` header (handles RFC 5987 `filename*`), else `None`. |
176
+
177
+ > **Caveat:** `content` holds raw bytes, which are not JSON-serializable. If you forward tool results to an LLM — or anywhere that re-serializes them to JSON — handle or strip the `content` key (for example, base64-encode it on the LLM-facing path).
178
+
179
+ JSON responses are unchanged: any action returning `application/json` (or a `…+json` type) is parsed and returned as a dict exactly as before.
180
+
149
181
  ## Implicit Feedback (Beta)
150
182
 
151
183
  The Python SDK can emit implicit behavioral feedback to LangSmith so you can triage low-quality tool results without manually tagging runs.
@@ -228,6 +260,46 @@ for tool_call in response.tool_calls:
228
260
 
229
261
  </details>
230
262
 
263
+ <details>
264
+ <summary>Pydantic AI Integration</summary>
265
+
266
+ StackOne tools convert to Pydantic AI `Tool` instances via `.to_pydantic_ai()`, parallel to `.to_openai()` and `.to_langchain()`:
267
+
268
+ Prerequisites:
269
+
270
+ ```bash
271
+ pip install 'stackone-ai[pydantic-ai]'
272
+ ```
273
+
274
+ ```python
275
+ import os
276
+ from pydantic_ai import Agent
277
+ from stackone_ai import StackOneToolSet
278
+
279
+ toolset = StackOneToolSet()
280
+ tools = toolset.fetch_tools(
281
+ actions=["workday_list_workers", "workday_get_worker"],
282
+ account_ids=[os.environ["STACKONE_ACCOUNT_ID"]],
283
+ ).to_pydantic_ai()
284
+
285
+ agent = Agent("openai:gpt-5.4", tools=tools)
286
+ result = agent.run_sync("List the first 5 employees")
287
+ print(result.output)
288
+ ```
289
+
290
+ For the full catalog (or the meta search/execute tools), use the `.pydantic_ai()` method on `StackOneToolSet` — parallel to `.openai()` / `.langchain()`:
291
+
292
+ ```python
293
+ toolset = StackOneToolSet()
294
+ tools = toolset.pydantic_ai(account_ids=[os.environ["STACKONE_ACCOUNT_ID"]])
295
+
296
+ # For agent-driven discovery, enable search on the constructor:
297
+ # toolset = StackOneToolSet(search={"method": "auto"})
298
+ # tools = toolset.pydantic_ai(mode="search_and_execute")
299
+ ```
300
+
301
+ </details>
302
+
231
303
  <details>
232
304
  <summary>LangGraph Integration</summary>
233
305
 
@@ -354,15 +426,15 @@ Search for tools using natural language queries. Works with both semantic (cloud
354
426
 
355
427
  ```python
356
428
  import os
429
+ from stackone_ai import StackOneToolSet
357
430
 
358
- # Get a callable search tool
359
- toolset = StackOneToolSet()
431
+ # Get a callable search tool — search must be enabled on the toolset
432
+ toolset = StackOneToolSet(search={"method": "auto"})
360
433
  account_id = os.getenv("STACKONE_ACCOUNT_ID")
361
- all_tools = toolset.fetch_tools(account_ids=[account_id])
362
434
  search_tool = toolset.get_search_tool()
363
435
 
364
- # Search for relevant tools — returns a Tools collection
365
- tools = search_tool("manage employees", top_k=5)
436
+ # Search for relevant tools — returns a Tools collection scoped to the account
437
+ tools = search_tool("manage employees", top_k=5, account_ids=[account_id])
366
438
 
367
439
  # Execute a discovered tool directly
368
440
  tools[0](limit=10)
@@ -376,7 +448,9 @@ Discover tools using natural language instead of exact names. Queries like "onbo
376
448
  import os
377
449
  from stackone_ai import StackOneToolSet
378
450
 
379
- toolset = StackOneToolSet()
451
+ # Search must be enabled on the constructor — pass `search={}` for defaults,
452
+ # or set a backend / top_k explicitly.
453
+ toolset = StackOneToolSet(search={"method": "auto"})
380
454
 
381
455
  # Search by intent — returns Tools collection ready for any framework
382
456
  account_id = os.getenv("STACKONE_ACCOUNT_ID")
@@ -26,6 +26,7 @@ StackOne AI provides a unified interface for accessing various SaaS tools throug
26
26
  - LangChain Tools
27
27
  - CrewAI Tools
28
28
  - LangGraph Tool Node
29
+ - Pydantic AI Toolset
29
30
 
30
31
  ## Requirements
31
32
 
@@ -113,6 +114,35 @@ tools = toolset.fetch_tools(providers=["hibob"])
113
114
  - Glob pattern: `["*_list_employees"]` matches all tools ending with `_list_employees`
114
115
  - Provider prefix: `["workday_*"]` matches all Workday tools
115
116
 
117
+ ## File Downloads
118
+
119
+ Actions that return a file — e.g. `googledrive_unified_download_file`, `documents_download_file`, any `*_unified_download_file` — resolve to **raw bytes plus metadata**, not parsed JSON. The SDK decides this from the response `Content-Type`: a JSON content type is parsed as before; anything else is treated as a file download. This applies to both `tool.execute()` and `tool.call()`.
120
+
121
+ ```python
122
+ tools = toolset.fetch_tools(actions=["googledrive_*"], account_ids=[account_id])
123
+ download = tools.get_tool("googledrive_unified_download_file")
124
+
125
+ result = download.execute({"id": "file-id"})
126
+
127
+ # `result` is a dict describing the file — write the bytes straight to disk:
128
+ with open(result["file_name"] or "download.bin", "wb") as f:
129
+ f.write(result["content"])
130
+ ```
131
+
132
+ The returned dict:
133
+
134
+ | Key | Type | Description |
135
+ | -------------- | ------------- | -------------------------------------------------------------------------------------------- |
136
+ | `content` | `bytes` | Raw file bytes. **Not JSON-serializable** — see the caveat below. |
137
+ | `content_type` | `str` | The file's MIME type (e.g. `application/pdf`), or `application/octet-stream` if unspecified. |
138
+ | `status_code` | `int` | HTTP status of the download response. |
139
+ | `headers` | `dict` | Response headers. |
140
+ | `file_name` | `str \| None` | Filename from the `Content-Disposition` header (handles RFC 5987 `filename*`), else `None`. |
141
+
142
+ > **Caveat:** `content` holds raw bytes, which are not JSON-serializable. If you forward tool results to an LLM — or anywhere that re-serializes them to JSON — handle or strip the `content` key (for example, base64-encode it on the LLM-facing path).
143
+
144
+ JSON responses are unchanged: any action returning `application/json` (or a `…+json` type) is parsed and returned as a dict exactly as before.
145
+
116
146
  ## Implicit Feedback (Beta)
117
147
 
118
148
  The Python SDK can emit implicit behavioral feedback to LangSmith so you can triage low-quality tool results without manually tagging runs.
@@ -195,6 +225,46 @@ for tool_call in response.tool_calls:
195
225
 
196
226
  </details>
197
227
 
228
+ <details>
229
+ <summary>Pydantic AI Integration</summary>
230
+
231
+ StackOne tools convert to Pydantic AI `Tool` instances via `.to_pydantic_ai()`, parallel to `.to_openai()` and `.to_langchain()`:
232
+
233
+ Prerequisites:
234
+
235
+ ```bash
236
+ pip install 'stackone-ai[pydantic-ai]'
237
+ ```
238
+
239
+ ```python
240
+ import os
241
+ from pydantic_ai import Agent
242
+ from stackone_ai import StackOneToolSet
243
+
244
+ toolset = StackOneToolSet()
245
+ tools = toolset.fetch_tools(
246
+ actions=["workday_list_workers", "workday_get_worker"],
247
+ account_ids=[os.environ["STACKONE_ACCOUNT_ID"]],
248
+ ).to_pydantic_ai()
249
+
250
+ agent = Agent("openai:gpt-5.4", tools=tools)
251
+ result = agent.run_sync("List the first 5 employees")
252
+ print(result.output)
253
+ ```
254
+
255
+ For the full catalog (or the meta search/execute tools), use the `.pydantic_ai()` method on `StackOneToolSet` — parallel to `.openai()` / `.langchain()`:
256
+
257
+ ```python
258
+ toolset = StackOneToolSet()
259
+ tools = toolset.pydantic_ai(account_ids=[os.environ["STACKONE_ACCOUNT_ID"]])
260
+
261
+ # For agent-driven discovery, enable search on the constructor:
262
+ # toolset = StackOneToolSet(search={"method": "auto"})
263
+ # tools = toolset.pydantic_ai(mode="search_and_execute")
264
+ ```
265
+
266
+ </details>
267
+
198
268
  <details>
199
269
  <summary>LangGraph Integration</summary>
200
270
 
@@ -321,15 +391,15 @@ Search for tools using natural language queries. Works with both semantic (cloud
321
391
 
322
392
  ```python
323
393
  import os
394
+ from stackone_ai import StackOneToolSet
324
395
 
325
- # Get a callable search tool
326
- toolset = StackOneToolSet()
396
+ # Get a callable search tool — search must be enabled on the toolset
397
+ toolset = StackOneToolSet(search={"method": "auto"})
327
398
  account_id = os.getenv("STACKONE_ACCOUNT_ID")
328
- all_tools = toolset.fetch_tools(account_ids=[account_id])
329
399
  search_tool = toolset.get_search_tool()
330
400
 
331
- # Search for relevant tools — returns a Tools collection
332
- tools = search_tool("manage employees", top_k=5)
401
+ # Search for relevant tools — returns a Tools collection scoped to the account
402
+ tools = search_tool("manage employees", top_k=5, account_ids=[account_id])
333
403
 
334
404
  # Execute a discovered tool directly
335
405
  tools[0](limit=10)
@@ -343,7 +413,9 @@ Discover tools using natural language instead of exact names. Queries like "onbo
343
413
  import os
344
414
  from stackone_ai import StackOneToolSet
345
415
 
346
- toolset = StackOneToolSet()
416
+ # Search must be enabled on the constructor — pass `search={}` for defaults,
417
+ # or set a backend / top_k explicitly.
418
+ toolset = StackOneToolSet(search={"method": "auto"})
347
419
 
348
420
  # Search by intent — returns Tools collection ready for any framework
349
421
  account_id = os.getenv("STACKONE_ACCOUNT_ID")
@@ -3,11 +3,13 @@
3
3
  ```bash
4
4
  uv run examples/pydantic_ai_integration.py
5
5
  ```
6
+
7
+ Install with `pip install 'stackone-ai[pydantic-ai]'` (or
8
+ `pip install 'stackone-ai[examples]'` to run this file).
6
9
  """
7
10
 
8
11
  from __future__ import annotations
9
12
 
10
- import json
11
13
  import os
12
14
 
13
15
  try:
@@ -18,31 +20,15 @@ except ModuleNotFoundError:
18
20
  pass
19
21
 
20
22
  try:
21
- from pydantic_ai import Agent, Tool
23
+ from pydantic_ai import Agent
22
24
  except ImportError:
23
- print("Install pydantic-ai to run this example: pip install pydantic-ai")
25
+ print("Install pydantic-ai to run this example: pip install 'stackone-ai[pydantic-ai]'")
24
26
  raise SystemExit(1) from None
25
27
 
26
28
  from stackone_ai import StackOneToolSet
27
29
 
28
30
 
29
- def _to_pydantic_ai_tool(stackone_tool) -> Tool:
30
- """Convert a StackOneTool to a Pydantic AI Tool with the proper JSON schema."""
31
- params_schema = stackone_tool.to_openai_function()["function"]["parameters"]
32
-
33
- def execute(**kwargs: object) -> str:
34
- return json.dumps(stackone_tool.execute(kwargs))
35
-
36
- return Tool.from_schema(
37
- execute,
38
- name=stackone_tool.name,
39
- description=stackone_tool.description,
40
- json_schema=params_schema,
41
- )
42
-
43
-
44
31
  def pydantic_ai_integration() -> None:
45
- account_id = os.getenv("STACKONE_ACCOUNT_ID")
46
32
  for var in ["STACKONE_API_KEY", "STACKONE_ACCOUNT_ID", "OPENAI_API_KEY"]:
47
33
  if not os.getenv(var):
48
34
  print(f"Set {var} to run this example.")
@@ -51,12 +37,14 @@ def pydantic_ai_integration() -> None:
51
37
  toolset = StackOneToolSet()
52
38
  tools = toolset.fetch_tools(
53
39
  actions=["workday_list_workers", "workday_get_worker", "workday_get_current_user"],
54
- account_ids=[account_id],
55
- )
56
- pydantic_tools = [_to_pydantic_ai_tool(t) for t in tools]
57
- print(f"Loaded {len(pydantic_tools)} tools for Pydantic AI.")
40
+ account_ids=[os.environ["STACKONE_ACCOUNT_ID"]],
41
+ ).to_pydantic_ai()
58
42
 
59
- agent = Agent("openai:gpt-5.4", system_prompt="You are a helpful HR assistant.", tools=pydantic_tools)
43
+ agent = Agent(
44
+ "openai:gpt-5.4",
45
+ system_prompt="You are a helpful HR assistant.",
46
+ tools=tools,
47
+ )
60
48
  result = agent.run_sync("List the first 5 employees")
61
49
  print(f"Result:\n{result.output}")
62
50
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "stackone-ai"
3
- version = "2.8.0"
3
+ version = "2.9.1"
4
4
  description = "agents performing actions on your SaaS"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -37,6 +37,7 @@ packages = ["stackone_ai"]
37
37
 
38
38
  [project.optional-dependencies]
39
39
  mcp = ["mcp>=1.3.0"]
40
+ pydantic-ai = ["pydantic-ai-slim>=1.83.0,<2.0.0"]
40
41
  examples = [
41
42
  "crewai>=0.102.0",
42
43
  "langchain-openai>=1.1.14",
@@ -23,4 +23,4 @@ __all__ = [
23
23
  "SemanticSearchResponse",
24
24
  "SemanticSearchError",
25
25
  ]
26
- __version__ = "2.8.0"
26
+ __version__ = "2.9.1"
@@ -3,16 +3,20 @@ from __future__ import annotations
3
3
  import base64
4
4
  import json
5
5
  import logging
6
+ import re
6
7
  from collections.abc import Sequence
7
8
  from datetime import datetime, timezone
8
9
  from enum import Enum
9
- from typing import Annotated, Any, ClassVar, TypeAlias, cast
10
- from urllib.parse import quote
10
+ from typing import TYPE_CHECKING, Annotated, Any, ClassVar, TypeAlias, cast
11
+ from urllib.parse import quote, unquote
11
12
 
12
13
  import httpx
13
14
  from langchain_core.tools import BaseTool
14
15
  from pydantic import BaseModel, BeforeValidator, Field, PrivateAttr
15
16
 
17
+ if TYPE_CHECKING:
18
+ from pydantic_ai.tools import Tool as PydanticAITool
19
+
16
20
  # Type aliases for common types
17
21
  JsonDict: TypeAlias = dict[str, Any]
18
22
  Headers: TypeAlias = dict[str, str]
@@ -54,6 +58,47 @@ def validate_method(v: str) -> str:
54
58
  return method
55
59
 
56
60
 
61
+ def _is_json_content_type(content_type: str) -> bool:
62
+ """Whether a response body should be parsed as JSON based on its Content-Type.
63
+
64
+ Only genuine JSON media types are parsed (``application/json`` and structured
65
+ suffixes such as ``application/problem+json``). Anything else - including a
66
+ missing Content-Type - is treated as opaque content (a file download), so the
67
+ raw bytes are returned instead of being force-decoded as UTF-8/JSON. This mirrors
68
+ how the StackOne generated SDKs default unknown bodies to ``application/octet-stream``.
69
+ """
70
+ media_type = content_type.split(";", 1)[0].strip().lower()
71
+ return media_type == "application/json" or media_type.endswith("+json")
72
+
73
+
74
+ def _filename_from_content_disposition(value: str | None) -> str | None:
75
+ """Extract the filename from a Content-Disposition header value, if present.
76
+
77
+ Handles both the plain ``filename="example.pdf"`` form and the RFC 5987 extended
78
+ ``filename*=UTF-8''example%20file.pdf`` form (which takes precedence when present).
79
+ The extended form is percent-decoded using its declared charset (RFC 5987 permits
80
+ both ``UTF-8`` and ``ISO-8859-1``); an unknown or empty charset falls back to UTF-8.
81
+ """
82
+ if not value:
83
+ return None
84
+ extended = re.search(r"filename\*\s*=\s*([^']*)'[^']*'([^;]+)", value, re.IGNORECASE)
85
+ if extended:
86
+ charset = extended.group(1).strip() or "utf-8"
87
+ encoded = extended.group(2).strip().strip('"')
88
+ try:
89
+ return unquote(encoded, encoding=charset, errors="replace") or None
90
+ except LookupError:
91
+ # Unrecognised charset label - decode as UTF-8 rather than failing.
92
+ return unquote(encoded, encoding="utf-8", errors="replace") or None
93
+ quoted = re.search(r'filename\s*=\s*"([^"]*)"', value, re.IGNORECASE)
94
+ if quoted:
95
+ return quoted.group(1).strip() or None
96
+ bare = re.search(r"filename\s*=\s*([^;]+)", value, re.IGNORECASE)
97
+ if bare:
98
+ return bare.group(1).strip().strip('"') or None
99
+ return None
100
+
101
+
57
102
  class ExecuteConfig(BaseModel):
58
103
  """Configuration for executing a tool against an API endpoint"""
59
104
 
@@ -203,7 +248,14 @@ class StackOneTool(BaseModel):
203
248
  options: Execution options (e.g. feedback metadata)
204
249
 
205
250
  Returns:
206
- API response as dict
251
+ For JSON responses, the parsed API response as a dict.
252
+
253
+ For file downloads (any non-JSON Content-Type, e.g. a
254
+ ``documents_download_file`` action), a dict describing the file:
255
+ ``{"content": <bytes>, "content_type": str, "status_code": int,
256
+ "headers": dict, "file_name": str | None}``. Note ``content`` holds
257
+ the raw bytes and is therefore not JSON-serializable - callers that
258
+ re-serialize tool results (e.g. for an LLM) should handle this key.
207
259
 
208
260
  Raises:
209
261
  StackOneAPIError: If the API request fails
@@ -254,9 +306,23 @@ class StackOneTool(BaseModel):
254
306
  response_status = response.status_code
255
307
  response.raise_for_status()
256
308
 
257
- result = response.json()
258
- result_payload = cast(JsonDict, result) if isinstance(result, dict) else {"result": result}
259
- return result_payload
309
+ content_type = response.headers.get("content-type", "")
310
+ if _is_json_content_type(content_type):
311
+ result = response.json()
312
+ result_payload = cast(JsonDict, result) if isinstance(result, dict) else {"result": result}
313
+ return result_payload
314
+
315
+ # Non-JSON bodies are file downloads (e.g. documents_download_file), which the
316
+ # API serves as raw binary with the file's own MIME type and a Content-Disposition
317
+ # header. Return the bytes plus metadata rather than forcing a JSON/UTF-8 decode.
318
+ # The shape mirrors the StackOne generated SDKs' download response.
319
+ return {
320
+ "content": response.content,
321
+ "content_type": content_type or "application/octet-stream",
322
+ "status_code": response.status_code,
323
+ "headers": dict(response.headers),
324
+ "file_name": _filename_from_content_disposition(response.headers.get("content-disposition")),
325
+ }
260
326
 
261
327
  except json.JSONDecodeError as exc:
262
328
  status = "error"
@@ -467,6 +533,36 @@ class StackOneTool(BaseModel):
467
533
 
468
534
  return StackOneLangChainTool()
469
535
 
536
+ def to_pydantic_ai_tool(self) -> PydanticAITool:
537
+ """Convert this tool to a Pydantic AI ``Tool``.
538
+
539
+ Requires ``stackone-ai[pydantic-ai]`` (installs ``pydantic-ai-slim``).
540
+
541
+ Returns:
542
+ A ``pydantic_ai.tools.Tool`` ready to pass to ``Agent(tools=[...])``.
543
+ """
544
+ try:
545
+ from pydantic_ai.tools import Tool
546
+ except ImportError as e:
547
+ raise ImportError(
548
+ "Install `pydantic-ai-slim` (or `stackone-ai[pydantic-ai]`) "
549
+ "to use the Pydantic AI integration."
550
+ ) from e
551
+
552
+ openai_function = self.to_openai_function()
553
+ json_schema = openai_function["function"]["parameters"]
554
+ parent_tool = self
555
+
556
+ def implementation(**kwargs: Any) -> Any:
557
+ return parent_tool.execute(kwargs)
558
+
559
+ return Tool.from_schema(
560
+ function=implementation,
561
+ name=self.name,
562
+ description=self.description,
563
+ json_schema=json_schema,
564
+ )
565
+
470
566
  def set_account_id(self, account_id: str | None) -> None:
471
567
  """Set the account ID for this tool
472
568
 
@@ -577,3 +673,13 @@ class Tools:
577
673
  Sequence of tools in LangChain format
578
674
  """
579
675
  return [tool.to_langchain() for tool in self.tools]
676
+
677
+ def to_pydantic_ai(self) -> list[PydanticAITool]:
678
+ """Convert all tools to Pydantic AI ``Tool`` instances.
679
+
680
+ Requires ``stackone-ai[pydantic-ai]`` (installs ``pydantic-ai-slim``).
681
+
682
+ Returns:
683
+ List of ``pydantic_ai.tools.Tool`` ready to pass to ``Agent(tools=[...])``.
684
+ """
685
+ return [tool.to_pydantic_ai_tool() for tool in self.tools]
@@ -11,7 +11,7 @@ import threading
11
11
  from collections.abc import Coroutine, Sequence
12
12
  from dataclasses import dataclass
13
13
  from importlib import metadata
14
- from typing import Any, Literal, TypedDict, TypeVar
14
+ from typing import TYPE_CHECKING, Any, Literal, TypedDict, TypeVar
15
15
 
16
16
  from pydantic import BaseModel, Field, PrivateAttr, ValidationError, field_validator
17
17
 
@@ -32,6 +32,9 @@ from stackone_ai.semantic_search import (
32
32
  )
33
33
  from stackone_ai.utils.normalize import _normalize_action_name
34
34
 
35
+ if TYPE_CHECKING:
36
+ from pydantic_ai.tools import Tool as PydanticAITool
37
+
35
38
  logger = logging.getLogger("stackone.tools")
36
39
 
37
40
  SearchMode = Literal["auto", "semantic", "local"]
@@ -642,13 +645,14 @@ class StackOneToolSet:
642
645
 
643
646
  Example::
644
647
 
645
- toolset = StackOneToolSet()
648
+ toolset = StackOneToolSet(search={"method": "auto"})
646
649
  search_tool = toolset.get_search_tool()
647
650
  tools = search_tool("manage employee records", account_ids=["acc-123"])
648
651
  """
649
652
  if self._search_config is None:
650
653
  raise ToolsetConfigError(
651
- "Search is disabled. Initialize StackOneToolSet with a search config to enable."
654
+ "Search is disabled. Pass search={} (or search={'method': 'auto'}) to "
655
+ "StackOneToolSet(...) to enable. See README 'Search Tool' for options."
652
656
  )
653
657
 
654
658
  config: SearchConfig = {**self._search_config}
@@ -661,7 +665,8 @@ class StackOneToolSet:
661
665
  """Build tool_search + tool_execute tools scoped to this toolset."""
662
666
  if self._search_config is None:
663
667
  raise ToolsetConfigError(
664
- "Search is disabled. Initialize StackOneToolSet with a search config to enable."
668
+ "Search is disabled. Pass search={} (or search={'method': 'auto'}) to "
669
+ "StackOneToolSet(...) to enable. See README 'Search Tool' for options."
665
670
  )
666
671
 
667
672
  if account_ids:
@@ -710,8 +715,8 @@ class StackOneToolSet:
710
715
  toolset = StackOneToolSet()
711
716
  tools = toolset.openai()
712
717
 
713
- # Meta tools for agent-driven discovery
714
- toolset = StackOneToolSet()
718
+ # Meta tools for agent-driven discovery — search must be enabled
719
+ toolset = StackOneToolSet(search={"method": "auto"})
715
720
  tools = toolset.openai(mode="search_and_execute")
716
721
  """
717
722
  effective_account_ids = account_ids or (
@@ -752,6 +757,47 @@ class StackOneToolSet:
752
757
 
753
758
  return self.fetch_tools(account_ids=effective_account_ids).to_langchain()
754
759
 
760
+ def pydantic_ai(
761
+ self,
762
+ *,
763
+ mode: Literal["search_and_execute"] | None = None,
764
+ account_ids: list[str] | None = None,
765
+ ) -> list[PydanticAITool]:
766
+ """Get tools as Pydantic AI ``Tool`` instances.
767
+
768
+ Args:
769
+ mode: Tool mode.
770
+ ``None`` (default): fetch all tools and convert to Pydantic AI tools.
771
+ ``"search_and_execute"``: return two meta tools (tool_search + tool_execute)
772
+ that let the LLM discover and execute tools on-demand.
773
+ account_ids: Account IDs to scope tools. Overrides the ``execute``
774
+ config from the constructor.
775
+
776
+ Returns:
777
+ List of Pydantic AI ``Tool`` objects ready to pass to ``Agent(tools=...)``.
778
+
779
+ Requires ``stackone-ai[pydantic-ai]`` (installs ``pydantic-ai-slim``).
780
+
781
+ Examples::
782
+
783
+ # All tools
784
+ toolset = StackOneToolSet()
785
+ tools = toolset.pydantic_ai()
786
+ agent = Agent("openai:gpt-5.4", tools=tools)
787
+
788
+ # Meta tools for agent-driven discovery — search must be enabled
789
+ toolset = StackOneToolSet(search={"method": "auto"})
790
+ tools = toolset.pydantic_ai(mode="search_and_execute")
791
+ """
792
+ effective_account_ids = account_ids or (
793
+ self._execute_config.get("account_ids") if self._execute_config else None
794
+ )
795
+
796
+ if mode == "search_and_execute":
797
+ return self._build_tools(account_ids=effective_account_ids).to_pydantic_ai()
798
+
799
+ return self.fetch_tools(account_ids=effective_account_ids).to_pydantic_ai()
800
+
755
801
  def execute(
756
802
  self,
757
803
  tool_name: str,
@@ -886,7 +932,8 @@ class StackOneToolSet:
886
932
  """
887
933
  if self._search_config is None:
888
934
  raise ToolsetConfigError(
889
- "Search is disabled. Initialize StackOneToolSet with a search config to enable."
935
+ "Search is disabled. Pass search={} (or search={'method': 'auto'}) to "
936
+ "StackOneToolSet(...) to enable. See README 'Search Tool' for options."
890
937
  )
891
938
 
892
939
  # Merge constructor defaults with per-call overrides
@@ -1039,7 +1086,8 @@ class StackOneToolSet:
1039
1086
  """
1040
1087
  if self._search_config is None:
1041
1088
  raise ToolsetConfigError(
1042
- "Search is disabled. Initialize StackOneToolSet with search config to enable."
1089
+ "Search is disabled. Pass search={} (or search={'method': 'auto'}) to "
1090
+ "StackOneToolSet(...) to enable. See README 'Search Tool' for options."
1043
1091
  )
1044
1092
 
1045
1093
  # Merge constructor defaults with per-call overrides