stackone-ai 2.10.1__tar.gz → 3.0.0__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 (186) hide show
  1. stackone_ai-3.0.0/.gitignore +23 -0
  2. {stackone_ai-2.10.1 → stackone_ai-3.0.0}/CHANGELOG.md +102 -0
  3. stackone_ai-3.0.0/MIGRATION.md +444 -0
  4. stackone_ai-3.0.0/PKG-INFO +463 -0
  5. stackone_ai-3.0.0/README.md +425 -0
  6. {stackone_ai-2.10.1 → stackone_ai-3.0.0}/pyproject.toml +20 -35
  7. stackone_ai-3.0.0/stackone_ai/__init__.py +40 -0
  8. stackone_ai-3.0.0/stackone_ai/tools.py +1765 -0
  9. stackone_ai-3.0.0/stackone_ai/toolset.py +1246 -0
  10. stackone_ai-3.0.0/stackone_ai/types.py +181 -0
  11. stackone_ai-3.0.0/tests/vectors/README.md +144 -0
  12. stackone_ai-2.10.1/.claude/rules/development-workflow.md +0 -63
  13. stackone_ai-2.10.1/.claude/rules/examples-standards.md +0 -75
  14. stackone_ai-2.10.1/.claude/rules/git-workflow.md +0 -97
  15. stackone_ai-2.10.1/.claude/rules/nix-workflow.md +0 -84
  16. stackone_ai-2.10.1/.claude/rules/no-relative-imports.md +0 -27
  17. stackone_ai-2.10.1/.claude/rules/package-installation.md +0 -59
  18. stackone_ai-2.10.1/.claude/rules/release-please-standards.md +0 -63
  19. stackone_ai-2.10.1/.claude/rules/uv-scripts.md +0 -66
  20. stackone_ai-2.10.1/.cursor/rules/development-workflow.mdc +0 -1
  21. stackone_ai-2.10.1/.cursor/rules/examples-standards.mdc +0 -1
  22. stackone_ai-2.10.1/.cursor/rules/git-workflow.mdc +0 -1
  23. stackone_ai-2.10.1/.cursor/rules/no-relative-imports.mdc +0 -1
  24. stackone_ai-2.10.1/.cursor/rules/package-installation.mdc +0 -1
  25. stackone_ai-2.10.1/.cursor/rules/release-please-standards.mdc +0 -1
  26. stackone_ai-2.10.1/.cursor/rules/uv-scripts.mdc +0 -1
  27. stackone_ai-2.10.1/.env.example +0 -6
  28. stackone_ai-2.10.1/.envrc +0 -2
  29. stackone_ai-2.10.1/.github/actions/setup-nix/action.yaml +0 -50
  30. stackone_ai-2.10.1/.github/dependabot.yaml +0 -46
  31. stackone_ai-2.10.1/.github/workflows/ci.yaml +0 -100
  32. stackone_ai-2.10.1/.github/workflows/nix-flake-update.yaml +0 -63
  33. stackone_ai-2.10.1/.github/workflows/nix-flake.yaml +0 -35
  34. stackone_ai-2.10.1/.github/workflows/release.yaml +0 -66
  35. stackone_ai-2.10.1/.gitignore +0 -24
  36. stackone_ai-2.10.1/.gitleaks.toml +0 -9
  37. stackone_ai-2.10.1/.gitmodules +0 -3
  38. stackone_ai-2.10.1/.mcp.json +0 -12
  39. stackone_ai-2.10.1/.release-please-config.json +0 -15
  40. stackone_ai-2.10.1/.release-please-manifest.json +0 -3
  41. stackone_ai-2.10.1/CLAUDE.md +0 -106
  42. stackone_ai-2.10.1/PKG-INFO +0 -541
  43. stackone_ai-2.10.1/README.md +0 -506
  44. stackone_ai-2.10.1/examples/auth_management.py +0 -103
  45. stackone_ai-2.10.1/examples/crewai_integration.py +0 -69
  46. stackone_ai-2.10.1/examples/langchain_integration.py +0 -69
  47. stackone_ai-2.10.1/examples/langgraph_integration.py +0 -62
  48. stackone_ai-2.10.1/examples/openai_integration.py +0 -113
  49. stackone_ai-2.10.1/examples/pydantic_ai_integration.py +0 -53
  50. stackone_ai-2.10.1/examples/search_tools.py +0 -171
  51. stackone_ai-2.10.1/examples/test_examples.py +0 -61
  52. stackone_ai-2.10.1/flake.lock +0 -230
  53. stackone_ai-2.10.1/flake.nix +0 -153
  54. stackone_ai-2.10.1/justfile +0 -47
  55. stackone_ai-2.10.1/scripts/benchmark_search.py +0 -134
  56. stackone_ai-2.10.1/stackone_ai/__init__.py +0 -26
  57. stackone_ai-2.10.1/stackone_ai/constants.py +0 -8
  58. stackone_ai-2.10.1/stackone_ai/feedback/__init__.py +0 -5
  59. stackone_ai-2.10.1/stackone_ai/feedback/tool.py +0 -226
  60. stackone_ai-2.10.1/stackone_ai/integrations/__init__.py +0 -20
  61. stackone_ai-2.10.1/stackone_ai/integrations/langgraph.py +0 -86
  62. stackone_ai-2.10.1/stackone_ai/local_search.py +0 -170
  63. stackone_ai-2.10.1/stackone_ai/models.py +0 -685
  64. stackone_ai-2.10.1/stackone_ai/semantic_search.py +0 -205
  65. stackone_ai-2.10.1/stackone_ai/toolset.py +0 -1341
  66. stackone_ai-2.10.1/stackone_ai/utils/__init__.py +0 -1
  67. stackone_ai-2.10.1/stackone_ai/utils/normalize.py +0 -17
  68. stackone_ai-2.10.1/stackone_ai/utils/tfidf_index.py +0 -242
  69. stackone_ai-2.10.1/tests/conftest.py +0 -113
  70. stackone_ai-2.10.1/tests/mocks/serve.ts +0 -134
  71. stackone_ai-2.10.1/tests/test_agent_tools.py +0 -541
  72. stackone_ai-2.10.1/tests/test_feedback.py +0 -397
  73. stackone_ai-2.10.1/tests/test_fetch_tools.py +0 -682
  74. stackone_ai-2.10.1/tests/test_integrations_langgraph.py +0 -217
  75. stackone_ai-2.10.1/tests/test_integrations_pydantic_ai.py +0 -183
  76. stackone_ai-2.10.1/tests/test_local_search.py +0 -254
  77. stackone_ai-2.10.1/tests/test_models.py +0 -962
  78. stackone_ai-2.10.1/tests/test_semantic_search.py +0 -1039
  79. stackone_ai-2.10.1/tests/test_tfidf_index.py +0 -452
  80. stackone_ai-2.10.1/tests/test_tool_calling.py +0 -536
  81. stackone_ai-2.10.1/tests/test_toolset.py +0 -356
  82. stackone_ai-2.10.1/uv.lock +0 -5881
  83. stackone_ai-2.10.1/vendor/stackone-ai-node/.claude/rules/development-workflow.md +0 -29
  84. stackone_ai-2.10.1/vendor/stackone-ai-node/.claude/rules/file-operations.md +0 -134
  85. stackone_ai-2.10.1/vendor/stackone-ai-node/.claude/rules/git-workflow.md +0 -97
  86. stackone_ai-2.10.1/vendor/stackone-ai-node/.claude/rules/pnpm-usage.md +0 -54
  87. stackone_ai-2.10.1/vendor/stackone-ai-node/.claude/rules/typescript-patterns.md +0 -246
  88. stackone_ai-2.10.1/vendor/stackone-ai-node/.claude/rules/typescript-testing.md +0 -132
  89. stackone_ai-2.10.1/vendor/stackone-ai-node/.claude/skills/orama-integration/SKILL.md +0 -25
  90. stackone_ai-2.10.1/vendor/stackone-ai-node/.cursor/rules/development-workflow.mdc +0 -1
  91. stackone_ai-2.10.1/vendor/stackone-ai-node/.cursor/rules/file-operations.mdc +0 -1
  92. stackone_ai-2.10.1/vendor/stackone-ai-node/.cursor/rules/git-workflow.mdc +0 -1
  93. stackone_ai-2.10.1/vendor/stackone-ai-node/.cursor/rules/orama-integration.mdc +0 -1
  94. stackone_ai-2.10.1/vendor/stackone-ai-node/.cursor/rules/pnpm-usage.mdc +0 -1
  95. stackone_ai-2.10.1/vendor/stackone-ai-node/.cursor/rules/typescript-patterns.mdc +0 -1
  96. stackone_ai-2.10.1/vendor/stackone-ai-node/.cursor/rules/typescript-testing.mdc +0 -1
  97. stackone_ai-2.10.1/vendor/stackone-ai-node/.envrc +0 -1
  98. stackone_ai-2.10.1/vendor/stackone-ai-node/.github/CODEOWNERS +0 -6
  99. stackone_ai-2.10.1/vendor/stackone-ai-node/.github/actions/setup-nix/action.yaml +0 -19
  100. stackone_ai-2.10.1/vendor/stackone-ai-node/.github/workflows/check-title.yaml +0 -25
  101. stackone_ai-2.10.1/vendor/stackone-ai-node/.github/workflows/ci.yaml +0 -87
  102. stackone_ai-2.10.1/vendor/stackone-ai-node/.github/workflows/claude.yaml +0 -39
  103. stackone_ai-2.10.1/vendor/stackone-ai-node/.github/workflows/dry-publish.yaml +0 -17
  104. stackone_ai-2.10.1/vendor/stackone-ai-node/.github/workflows/nix-flake.yaml +0 -33
  105. stackone_ai-2.10.1/vendor/stackone-ai-node/.github/workflows/release.yaml +0 -52
  106. stackone_ai-2.10.1/vendor/stackone-ai-node/.gitignore +0 -183
  107. stackone_ai-2.10.1/vendor/stackone-ai-node/.gitleaks.toml +0 -11
  108. stackone_ai-2.10.1/vendor/stackone-ai-node/.mcp.json +0 -12
  109. stackone_ai-2.10.1/vendor/stackone-ai-node/.oxfmtrc.jsonc +0 -7
  110. stackone_ai-2.10.1/vendor/stackone-ai-node/.oxlintrc.jsonc +0 -179
  111. stackone_ai-2.10.1/vendor/stackone-ai-node/AGENTS.md +0 -1
  112. stackone_ai-2.10.1/vendor/stackone-ai-node/CHANGELOG.md +0 -373
  113. stackone_ai-2.10.1/vendor/stackone-ai-node/CLAUDE.md +0 -26
  114. stackone_ai-2.10.1/vendor/stackone-ai-node/LICENSE +0 -201
  115. stackone_ai-2.10.1/vendor/stackone-ai-node/README.md +0 -614
  116. stackone_ai-2.10.1/vendor/stackone-ai-node/examples/README.md +0 -295
  117. stackone_ai-2.10.1/vendor/stackone-ai-node/examples/ai-sdk-integration.test.ts +0 -52
  118. stackone_ai-2.10.1/vendor/stackone-ai-node/examples/ai-sdk-integration.ts +0 -52
  119. stackone_ai-2.10.1/vendor/stackone-ai-node/examples/anthropic-integration.ts +0 -63
  120. stackone_ai-2.10.1/vendor/stackone-ai-node/examples/claude-agent-sdk-integration.test.ts +0 -140
  121. stackone_ai-2.10.1/vendor/stackone-ai-node/examples/claude-agent-sdk-integration.ts +0 -88
  122. stackone_ai-2.10.1/vendor/stackone-ai-node/examples/fetch-tools-debug.ts +0 -297
  123. stackone_ai-2.10.1/vendor/stackone-ai-node/examples/fetch-tools.test.ts +0 -102
  124. stackone_ai-2.10.1/vendor/stackone-ai-node/examples/fetch-tools.ts +0 -82
  125. stackone_ai-2.10.1/vendor/stackone-ai-node/examples/meta-tools.ts +0 -280
  126. stackone_ai-2.10.1/vendor/stackone-ai-node/examples/openai-integration.test.ts +0 -72
  127. stackone_ai-2.10.1/vendor/stackone-ai-node/examples/openai-integration.ts +0 -69
  128. stackone_ai-2.10.1/vendor/stackone-ai-node/examples/openai-responses-integration.test.ts +0 -64
  129. stackone_ai-2.10.1/vendor/stackone-ai-node/examples/openai-responses-integration.ts +0 -63
  130. stackone_ai-2.10.1/vendor/stackone-ai-node/examples/package.json +0 -30
  131. stackone_ai-2.10.1/vendor/stackone-ai-node/examples/tanstack-ai-integration.test.ts +0 -77
  132. stackone_ai-2.10.1/vendor/stackone-ai-node/examples/tanstack-ai-integration.ts +0 -83
  133. stackone_ai-2.10.1/vendor/stackone-ai-node/examples/tsconfig.json +0 -16
  134. stackone_ai-2.10.1/vendor/stackone-ai-node/flake.lock +0 -61
  135. stackone_ai-2.10.1/vendor/stackone-ai-node/flake.nix +0 -49
  136. stackone_ai-2.10.1/vendor/stackone-ai-node/knip.config.ts +0 -21
  137. stackone_ai-2.10.1/vendor/stackone-ai-node/lefthook.yaml +0 -22
  138. stackone_ai-2.10.1/vendor/stackone-ai-node/mocks/handlers.example-api.ts +0 -59
  139. stackone_ai-2.10.1/vendor/stackone-ai-node/mocks/handlers.mcp.ts +0 -35
  140. stackone_ai-2.10.1/vendor/stackone-ai-node/mocks/handlers.openai.ts +0 -214
  141. stackone_ai-2.10.1/vendor/stackone-ai-node/mocks/handlers.stackone-ai.ts +0 -97
  142. stackone_ai-2.10.1/vendor/stackone-ai-node/mocks/handlers.stackone-rpc.ts +0 -85
  143. stackone_ai-2.10.1/vendor/stackone-ai-node/mocks/handlers.ts +0 -13
  144. stackone_ai-2.10.1/vendor/stackone-ai-node/mocks/handlers.utils.ts +0 -30
  145. stackone_ai-2.10.1/vendor/stackone-ai-node/mocks/mcp-server.ts +0 -275
  146. stackone_ai-2.10.1/vendor/stackone-ai-node/mocks/node.ts +0 -4
  147. stackone_ai-2.10.1/vendor/stackone-ai-node/package.json +0 -118
  148. stackone_ai-2.10.1/vendor/stackone-ai-node/pnpm-lock.yaml +0 -4506
  149. stackone_ai-2.10.1/vendor/stackone-ai-node/pnpm-workspace.yaml +0 -66
  150. stackone_ai-2.10.1/vendor/stackone-ai-node/src/consts.ts +0 -20
  151. stackone_ai-2.10.1/vendor/stackone-ai-node/src/feedback.test.ts +0 -300
  152. stackone_ai-2.10.1/vendor/stackone-ai-node/src/feedback.ts +0 -230
  153. stackone_ai-2.10.1/vendor/stackone-ai-node/src/headers.test.ts +0 -69
  154. stackone_ai-2.10.1/vendor/stackone-ai-node/src/headers.ts +0 -46
  155. stackone_ai-2.10.1/vendor/stackone-ai-node/src/index.ts +0 -29
  156. stackone_ai-2.10.1/vendor/stackone-ai-node/src/mcp-client.test.ts +0 -68
  157. stackone_ai-2.10.1/vendor/stackone-ai-node/src/mcp-client.ts +0 -55
  158. stackone_ai-2.10.1/vendor/stackone-ai-node/src/requestBuilder.test.ts +0 -778
  159. stackone_ai-2.10.1/vendor/stackone-ai-node/src/requestBuilder.ts +0 -377
  160. stackone_ai-2.10.1/vendor/stackone-ai-node/src/rpc-client.test.ts +0 -122
  161. stackone_ai-2.10.1/vendor/stackone-ai-node/src/rpc-client.ts +0 -107
  162. stackone_ai-2.10.1/vendor/stackone-ai-node/src/schema.ts +0 -64
  163. stackone_ai-2.10.1/vendor/stackone-ai-node/src/tool.test-d.ts +0 -53
  164. stackone_ai-2.10.1/vendor/stackone-ai-node/src/tool.test.ts +0 -1453
  165. stackone_ai-2.10.1/vendor/stackone-ai-node/src/tool.ts +0 -743
  166. stackone_ai-2.10.1/vendor/stackone-ai-node/src/toolsets.test-d.ts +0 -44
  167. stackone_ai-2.10.1/vendor/stackone-ai-node/src/toolsets.test.ts +0 -712
  168. stackone_ai-2.10.1/vendor/stackone-ai-node/src/toolsets.ts +0 -558
  169. stackone_ai-2.10.1/vendor/stackone-ai-node/src/types.ts +0 -203
  170. stackone_ai-2.10.1/vendor/stackone-ai-node/src/utils/array.test.ts +0 -48
  171. stackone_ai-2.10.1/vendor/stackone-ai-node/src/utils/array.ts +0 -13
  172. stackone_ai-2.10.1/vendor/stackone-ai-node/src/utils/error-stackone-api.test.ts +0 -208
  173. stackone_ai-2.10.1/vendor/stackone-ai-node/src/utils/error-stackone-api.ts +0 -130
  174. stackone_ai-2.10.1/vendor/stackone-ai-node/src/utils/error-stackone.test.ts +0 -15
  175. stackone_ai-2.10.1/vendor/stackone-ai-node/src/utils/error-stackone.ts +0 -9
  176. stackone_ai-2.10.1/vendor/stackone-ai-node/src/utils/tfidf-index.test.ts +0 -206
  177. stackone_ai-2.10.1/vendor/stackone-ai-node/src/utils/tfidf-index.ts +0 -192
  178. stackone_ai-2.10.1/vendor/stackone-ai-node/src/utils/try-import.test.ts +0 -23
  179. stackone_ai-2.10.1/vendor/stackone-ai-node/src/utils/try-import.ts +0 -25
  180. stackone_ai-2.10.1/vendor/stackone-ai-node/src/utils/type.ts +0 -4
  181. stackone_ai-2.10.1/vendor/stackone-ai-node/tsconfig.json +0 -28
  182. stackone_ai-2.10.1/vendor/stackone-ai-node/tsdown.config.ts +0 -20
  183. stackone_ai-2.10.1/vendor/stackone-ai-node/vitest.config.ts +0 -46
  184. stackone_ai-2.10.1/vendor/stackone-ai-node/vitest.setup.ts +0 -5
  185. {stackone_ai-2.10.1 → stackone_ai-3.0.0}/LICENSE +0 -0
  186. {stackone_ai-2.10.1 → stackone_ai-3.0.0}/stackone_ai/py.typed +0 -0
@@ -0,0 +1,23 @@
1
+ .env
2
+ .env.*
3
+ !.env.example
4
+ .venv
5
+
6
+ .pytest_cache
7
+ .python-version
8
+ __pycache__
9
+ .coverage
10
+ coverage/
11
+
12
+ .DS_Store
13
+
14
+
15
+ *.egg-info
16
+ dist/
17
+ build/
18
+
19
+ # Node tooling for the MCP mock server
20
+ node_modules/
21
+
22
+ # Hypothesis test database
23
+ .hypothesis/
@@ -1,5 +1,107 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.0.0](https://github.com/StackOneHQ/stackone-ai-python/compare/stackone-ai-v2.10.1...stackone-ai-v3.0.0) (2026-10-29)
4
+
5
+
6
+ ### ⚠ BREAKING CHANGES
7
+
8
+ * one search tool and one execute tool spanning the account scope (ENG-3175) ([#231](https://github.com/StackOneHQ/stackone-ai-python/issues/231))
9
+ * StackOneRpcTool, MCP_PARAM_STYLE, ParameterLocation, validate_method, is_json_content_type and filename_from_content_disposition are removed. Every tool fetch_tools() returns is a StackOneMcpTool that executes over MCP tools/call on the endpoint and account that listed it, with its arguments sent unchanged; nothing posts to /actions/rpc.
10
+ * **tools:** StackOneTool.execute() no longer makes HTTP requests. On the base class it raises StackOneError, so a hand-built tool must override execute().
11
+ * **types:** ExecuteConfig is extra="forbid" and keeps only name, headers and timeout. Passing method, url, body_type or parameter_locations raises a pydantic ValidationError.
12
+ * **toolset:** StackOneToolSet.execute() and tool.execute() return the server's {isError, result, defenderMetadata?, policyMetadata?} object rather than the unwrapped payload or the /actions/rpc body, so result["data"] becomes result["result"]["data"].
13
+ * **tools:** a file action returns {download_url, expires_at, file} as `result` instead of {content, content_type, status_code, headers, file_name}, and raises StackOneAPIError with status 501 when no link can be issued. Tools.execute_openai_tool_calls() no longer base64-encodes bytes.
14
+ * **toolset:** the /mcp URL no longer carries ?param-style=flat_prefixed, so per-action tools from fetch_tools() take whichever argument style the server serves by default instead of flat, prefixed keys such as body_variables.
15
+ * **tools:** StackOneMcpTool(headers=...) is now optional and means extra request headers, not the full header set. The SDK always sets Authorization, x-account-id and User-Agent itself, after them.
16
+ * **tools:** to_openai_function() no longer derives `required` from the per-property nullable markers. It emits the served root `required` list verbatim, in the served order, and omits it when absent or empty, so a hand-built ToolParameters without `required` now has no required fields.
17
+ * **toolset:** fetch_tools(), openai(), langchain() and pydantic_ai() include the stackone_submit_feedback tool, once, whenever the server serves it.
18
+ * **tools:** a top-level `headers_<name>` argument is dropped unless the tool's schema declares that property, and always for Authorization, x-account-id and User-Agent. A declared flat `headers_<name>` property no longer admits the same name inside a nested `headers` object. An open `headers` object, as `*_execute_action` serves it, forwards any other header, so StackOneToolSet.execute(..., {"headers": {...}}) passes host-set headers through where it previously dropped them all. A top-level `headers` argument that isn't a plain object is dropped too, unless the schema declares `headers` itself as a non-object field, in which case it is an ordinary argument sent as given. A declared flat `headers_<name>` argument whose value is a list or dict is dropped as well; only a string, number or boolean can be a header value.
19
+ * **tools:** when more than one account serves a tool name, Tools.get_tool() returns the first one listed (listings merge in sorted account order) rather than the last.
20
+ * Python 3.10 is no longer supported. stackone-ai requires Python 3.11 or later.
21
+ * the `stackone-ai[mcp]` extra no longer exists. mcp is a core dependency, so install plain `stackone-ai`.
22
+ * langchain-core is no longer installed with stackone-ai. StackOneTool.to_langchain(), Tools.to_langchain() and StackOneToolSet.langchain() need the `stackone-ai[langchain]` extra, which requires langchain-core 0.3.36 or later, and raise an ImportError that names it without it.
23
+ * stackone_ai.models, stackone_ai.constants and stackone_ai.utils no longer exist. Import StackOneTool and Tools from stackone_ai.tools; ToolParameters, ExecuteConfig, DEFAULT_BASE_URL and the errors from stackone_ai.types; or any public name from stackone_ai. ToolDefinition and DEFAULT_HYBRID_ALPHA are removed.
24
+ * **toolset:** client-side search is removed: SearchConfig, SearchMode, SearchTool, SemanticSearchClient, SemanticSearchResult, SemanticSearchResponse and SemanticSearchError, the `search=` constructor argument, search_tools(), search_action_names(), get_search_tool(), semantic_client, the tool_search and tool_execute meta tools, and the bm25s and numpy dependencies. StackOneToolSet.search() runs the server's *_search_actions tools instead.
25
+ * stackone_ai.integrations, with to_tool_node(), to_tool_executor(), bind_model_with_tools() and create_react_agent(), is removed. Pass tools.to_langchain() to LangGraph's own ToolNode and bind_tools().
26
+ * **tools:** the tool_feedback tool, stackone_ai.feedback, create_feedback_tool() and the implicit feedback_* execution options are removed, and StackOneTool.execute() and call() no longer take options=. Use StackOneToolSet.submit_feedback() and a search hit's session_id instead.
27
+ * **toolset:** StackOneToolSet.execute() takes (action_id, arguments=None, *, account_ids=None, session_id=None) instead of a meta tool name with arguments as a JSON string or dict. arguments must be a dict in the nested form an action's example_request shows, and a failure raises (ToolsetConfigError, ToolArgumentsError, ToolsetLoadError or StackOneAPIError) instead of returning {"error": ...}.
28
+ * **tools:** to_openai_function(), to_langchain() and to_pydantic_ai_tool() give the model the served JSON Schema instead of only type, description, enum and a shallow copy of items and properties, so format, pattern, default, bounds, oneOf/anyOf and nested required reach it. to_langchain() sets that schema as args_schema instead of a pydantic model built from it.
29
+ * **toolset:** openai(), langchain() and pydantic_ai() no longer take mode=. Set tool_mode="search_execute" on StackOneToolSet to give a model the server's search and execute tools.
30
+ * **toolset:** StackOneToolSet(account_id="") raises ToolsetConfigError instead of being treated as no account, which now lists every active account, so an account id read from an environment variable that is set but empty raises. An empty or non-string entry in account_ids, set_accounts() or execute={"account_ids": [...]} raises too, instead of being sent with no x-account-id.
31
+ * **toolset:** account_ids, set_accounts() and execute={"account_ids": ...} accept only a list of strings. A tuple or any other non-list, which 2.10.1 accepted, raises ToolsetConfigError; pass list(ids) instead.
32
+ * removed StackOneTool.connector property and Tools.get_connectors().
33
+
34
+ ### Features
35
+
36
+ * export the feedback types and StackOneMcpTool, and take any sequence of tool names ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
37
+ * one search tool and one execute tool spanning the account scope (ENG-3175) ([#231](https://github.com/StackOneHQ/stackone-ai-python/issues/231)) ([691d13a](https://github.com/StackOneHQ/stackone-ai-python/commit/691d13a9c2021bcd6d1645bce7a489d0e705b2fc))
38
+ * retry HTTP 429 on every request, honouring Retry-After, and raise a 429 that outlasts the retries or the timeout instead of skipping the account ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
39
+ * **toolset:** add a headers option sent on every request ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
40
+ * **toolset:** add action_run_id to submit_feedback(), and send feedback through the lowest account id ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
41
+ * **toolset:** link calls with session_id and add submit_feedback() ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
42
+ * **toolset:** read STACKONE_BASE_URL ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
43
+ * **toolset:** send x-end-user-id for non-shared accounts ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
44
+ * **toolset:** skip non-shared accounts during discovery unless include_non_shared ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
45
+ * **toolset:** tag each search hit with its account_id and apply top_k to the merged ranking ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
46
+ * **toolset:** warn when STACKONE_ACCOUNT_ID is set but ignored ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
47
+ * **tools:** raise ToolArgumentsError, a StackOneError and a ValueError, for unusable tool arguments ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
48
+
49
+
50
+ ### Bug Fixes
51
+
52
+ * **tools:** build every adapter from the first tool of each name ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
53
+ * **tools:** emit the served required list in served order ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
54
+ * **toolset:** accept account ids only as a list ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
55
+ * **toolset:** cache the healthy accounts when one fails, and retry a failed one after 30 seconds ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
56
+ * **toolset:** chain a failed end-user lookup onto the 400 it was looking up for ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
57
+ * **toolset:** hold a provider lookup's miss for the failed-account window in execute() ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
58
+ * **toolset:** learn a failed account's provider with one GET /accounts before execute() refuses ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
59
+ * **toolset:** look up a non-shared account's end user when the API asks for it ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
60
+ * **toolset:** move a pinned action_id and session_id to the end of the arguments ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
61
+ * **toolset:** name shared accounts in the STACKONE_ACCOUNT_ID warning ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
62
+ * **toolset:** raise the shared error when every account fails, and keep each failure ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
63
+ * **toolset:** raise when every active account on the key is non-shared and not opted in ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
64
+ * **toolset:** re-list only the failed accounts that could serve the action in execute() ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
65
+ * **toolset:** refuse an action whose connector is linked on more than one account ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
66
+ * **toolset:** refuse to execute while a matching account failed to list ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
67
+ * **toolset:** reject an empty or non-string account id ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
68
+ * **toolset:** route stackone_submit_feedback over MCP and list it once ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
69
+ * **toolset:** send feedback once, through the first account ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
70
+ * **tools:** guard every header argument by its own declaration ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
71
+ * **tools:** keep a retried 429 on record while another request in the exchange is answered ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
72
+ * **tools:** report an MCP timeout as a timeout ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
73
+ * **tools:** return structuredContent from a tools/call result with no text ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
74
+ * **tools:** return the first listing from get_tool(), as Node does ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
75
+ * **tools:** send tools/call without relisting the catalog first ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
76
+ * **tools:** write nested header values as Node writes them ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
77
+ * **types:** declare ToolParameters.required so a hand-built tool type-checks ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
78
+
79
+
80
+ ### Code Refactoring
81
+
82
+ * execute every tool over MCP tools/call ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
83
+ * MCP-only toolset with account discovery, search/execute, and hardened security ([#199](https://github.com/StackOneHQ/stackone-ai-python/issues/199)) ([e53edf0](https://github.com/StackOneHQ/stackone-ai-python/commit/e53edf0c7cc1da6cc5376df9ca27c6b845597e22))
84
+ * remove stackone_ai.integrations ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
85
+ * split stackone_ai.models into stackone_ai.tools and stackone_ai.types ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
86
+ * **toolset:** remove client-side search ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
87
+ * **toolset:** remove mode= from openai(), langchain() and pydantic_ai() ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
88
+ * **toolset:** return every result exactly as the server wrote it ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
89
+ * **toolset:** run an action by id with execute() ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
90
+ * **toolset:** stop pinning param-style=flat_prefixed on the MCP endpoint ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
91
+ * **tools:** pass the served schema through to every adapter ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
92
+ * **tools:** remove the client-side feedback tool ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
93
+ * **tools:** remove the HTTP executor from the base StackOneTool ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
94
+ * **tools:** return a download link from file actions ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
95
+ * **tools:** treat StackOneMcpTool headers as extra headers ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
96
+ * **types:** reduce ExecuteConfig to name, headers and timeout ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
97
+
98
+
99
+ ### Build System
100
+
101
+ * make langchain-core an optional dependency ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
102
+ * make mcp a core dependency and remove the mcp extra ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
103
+ * require Python 3.11 or later ([049abf5](https://github.com/StackOneHQ/stackone-ai-python/commit/049abf548c7f5448a89e7999e95336980d23afc3))
104
+
3
105
  ## [2.10.1](https://github.com/StackOneHQ/stackone-ai-python/compare/stackone-ai-v2.10.0...stackone-ai-v2.10.1) (2026-07-28)
4
106
 
5
107
 
@@ -0,0 +1,444 @@
1
+ # Migrating from 2.x to 3.0
2
+
3
+ 3.0 is a thin client over StackOne's MCP endpoint. Every tool call goes over MCP
4
+ `tools/call`, and the only other request the SDK makes is `GET /accounts`, to find
5
+ your linked accounts. Search runs on the server. Anything MCP does not support has
6
+ been removed rather than kept on a second transport.
7
+
8
+ Work through the sections that apply to you. Each one gives the 2.x code and its 3.0
9
+ replacement.
10
+
11
+ - [Installation](#installation)
12
+ - [Imports](#imports)
13
+ - [Search and execute](#search-and-execute)
14
+ - [Results](#results)
15
+ - [Tool arguments and headers](#tool-arguments-and-headers)
16
+ - [Base URL](#base-url)
17
+ - [Accounts](#accounts)
18
+ - [Feedback](#feedback)
19
+ - [Schemas given to a model](#schemas-given-to-a-model)
20
+ - [Hand-built tools and `ExecuteConfig`](#hand-built-tools-and-executeconfig)
21
+ - [LangGraph helpers](#langgraph-helpers)
22
+
23
+ ## Installation
24
+
25
+ - **Python 3.11 or later** is required. 3.10 is no longer supported.
26
+ - **`mcp` is a core dependency.** The `stackone-ai[mcp]` extra no longer exists.
27
+ - **`langchain-core` is now optional.** `to_langchain()` and `toolset.langchain()`
28
+ need the `langchain` extra and raise an `ImportError` that says so without it.
29
+ - `bm25s` and `numpy` are no longer dependencies, because search now runs on the server.
30
+
31
+ ```bash
32
+ # 2.x
33
+ uv add 'stackone-ai[mcp]'
34
+
35
+ # 3.0: add the extras for the frameworks you use
36
+ uv add stackone-ai
37
+ uv add 'stackone-ai[langchain]' # LangChain and LangGraph
38
+ uv add 'stackone-ai[pydantic-ai]' # Pydantic AI
39
+ ```
40
+
41
+ ## Imports
42
+
43
+ `stackone_ai.models` is split into `stackone_ai.tools` and `stackone_ai.types`. Anything
44
+ public can also be imported from the package root.
45
+
46
+ ```python
47
+ # 2.x
48
+ from stackone_ai.models import ExecuteConfig, StackOneTool, ToolParameters, Tools
49
+ from stackone_ai.toolset import ToolsetConfigError
50
+
51
+ # 3.0
52
+ from stackone_ai import ExecuteConfig, StackOneTool, ToolParameters, Tools, ToolsetConfigError
53
+ ```
54
+
55
+ `ToolsetError`, `ToolsetConfigError` and `ToolsetLoadError` now subclass `StackOneError`,
56
+ so `except StackOneError` catches everything the SDK raises. Existing
57
+ `except ToolsetError` clauses behave as before.
58
+
59
+ Unusable tool arguments raise the new `ToolArgumentsError`, before any request: a JSON
60
+ string that does not parse or does not parse to an object, an argument that cannot be
61
+ encoded as JSON, or `tool.call()` given both positional and keyword arguments, or more
62
+ than one positional argument. Raised by `tool.execute()`, `tool.call()` and
63
+ `toolset.execute()` alike. It subclasses both `StackOneError` and `ValueError`, which is
64
+ what 2.x raised, so existing `except ValueError` clauses still catch it.
65
+
66
+ These names have been removed and have no direct replacement:
67
+
68
+ | Removed | Use instead |
69
+ |---|---|
70
+ | `SearchConfig`, `SearchMode`, `SearchTool`, `SemanticSearchClient`, `SemanticSearchResult`, `SemanticSearchResponse`, `SemanticSearchError` | `toolset.search()`. See [Search and execute](#search-and-execute) |
71
+ | `stackone_ai.feedback`, `create_feedback_tool` | `toolset.submit_feedback()`. See [Feedback](#feedback) |
72
+ | `stackone_ai.integrations` | LangGraph's own APIs. See [LangGraph helpers](#langgraph-helpers) |
73
+ | `ToolDefinition` | `StackOneTool` fields: `name`, `description`, `parameters` |
74
+ | `ParameterLocation`, `validate_method` | Nothing. `ExecuteConfig` no longer has a method or parameter locations |
75
+ | `StackOneTool.connector`, `Tools.get_connectors()` | `fetch_tools(providers=[...])` to filter by provider. A provider name can contain `_` (`browser_linkedin`), so do not split the tool name |
76
+
77
+ Some names were public on `main` for a while before 3.0 but never in a release:
78
+ `StackOneRpcTool`, `MCP_PARAM_STYLE`, `is_json_content_type` and
79
+ `filename_from_content_disposition`. They are gone as well.
80
+
81
+ ## Search and execute
82
+
83
+ Client-side search is gone: the BM25/TF-IDF index, the semantic search client, the
84
+ `search=` constructor argument, and the `tool_search`/`tool_execute` meta tools. The
85
+ server's `stackone_search_actions` tool now does the search, and `toolset.execute()`
86
+ runs an action by id through the server's `stackone_execute_action` tool. Each is one
87
+ call spanning every account in scope.
88
+
89
+ ```python
90
+ # 2.x
91
+ toolset = StackOneToolSet(search={"method": "auto"})
92
+ tools = toolset.search_tools("list employees", top_k=5)
93
+ result = tools[0].execute({"query_page_size": 25})
94
+
95
+ names = toolset.search_action_names("time off requests", top_k=5)
96
+
97
+ # 3.0
98
+ toolset = StackOneToolSet()
99
+ hits = toolset.search("list employees", top_k=5) # a list of action dicts, best first
100
+ hit = hits[0]
101
+ result = toolset.execute(
102
+ hit["action_id"],
103
+ {"query": {"page_size": 25}}, # the nested form in hit["input_schema"]
104
+ account_ids=[hit["available_accounts"][0]["id"]],
105
+ session_id=hit.get("session_id"),
106
+ )
107
+ ```
108
+
109
+ `search()` returns the server's hits: dicts carrying `action_id`, `provider` and
110
+ `available_accounts` (each `{"id", "name"}`), plus `description`, `similarity_score`,
111
+ `input_schema` and `example_request` when the server sends them, and the search's
112
+ `session_id`. `top_k` caps the whole result, as in 2.x; the same action linked on two
113
+ accounts is one hit naming both. When an action's connector is linked on more than one
114
+ account in scope, the server refuses to pick one and `execute()` raises
115
+ `StackOneAPIError` unless `account_ids` names one, such as
116
+ `[hit["available_accounts"][0]["id"]]`.
117
+
118
+ `toolset.execute()` has a new signature. It used to take a meta tool name, such as
119
+ `"tool_execute"`, with arguments as a JSON string or a dict. It now takes
120
+ `(action_id, arguments=None, *, account_ids=None, session_id=None)`, and `arguments`
121
+ must be a dict. It raises instead of returning `{"error": ...}`: `ToolsetConfigError`
122
+ before any request when `action_id`, `arguments` or `session_id` is malformed,
123
+ `ToolArgumentsError` when the arguments cannot be encoded as JSON, `ToolsetLoadError` when
124
+ the server did not serve `stackone_execute_action` (the project's policy disables
125
+ search_execute), and `StackOneAPIError` when the action fails, including when the server
126
+ rejects the arguments or the action id.
127
+
128
+ To give a model the search and execute tools, set the tool mode on the toolset.
129
+ `openai()`, `langchain()` and `pydantic_ai()` no longer take `mode=`.
130
+
131
+ ```python
132
+ # 2.x
133
+ toolset = StackOneToolSet(search={"method": "auto"})
134
+ openai_tools = toolset.openai(mode="search_and_execute")
135
+ # ...for each tool call the model makes:
136
+ toolset.execute(call.function.name, call.function.arguments)
137
+
138
+ # 3.0: stackone_search_actions and stackone_execute_action, served by the server and
139
+ # spanning every account in scope (at most 50), with stackone_list_accounts for several
140
+ toolset = StackOneToolSet(tool_mode="search_execute")
141
+ tools = toolset.fetch_tools()
142
+ openai_tools = tools.to_openai()
143
+ # ...then run the model's tool calls and get the messages to send back:
144
+ messages.extend(tools.execute_openai_tool_calls(message.tool_calls))
145
+ ```
146
+
147
+ For a single account the server serves that account's own pair,
148
+ `<provider>_<account>_search_actions` and `_execute_action`; the SDK presents them as
149
+ `stackone_search_actions` and `stackone_execute_action`, so code and prompts use the same
150
+ names for one account or many. A scope of more than 50 accounts raises
151
+ `ToolsetConfigError` before any request.
152
+
153
+ `get_search_tool()` and `semantic_client` are removed. Call `toolset.search()` instead.
154
+
155
+ ## Results
156
+
157
+ **Every tool returns the server's result as the server wrote it.** For an action,
158
+ that is `{"isError": False, "result": ..., "defenderMetadata"?, "policyMetadata"?}`.
159
+ Search results are bare JSON. This applies to `tool.execute()`, `tool.call()` and
160
+ `toolset.execute()`.
161
+
162
+ ```python
163
+ tool = toolset.fetch_tools(account_ids=[account_id]).get_tool("hibob_list_employees")
164
+
165
+ # 2.x
166
+ employees = tool.execute({})["data"]
167
+
168
+ # 3.0
169
+ employees = tool.execute({})["result"]["data"]
170
+ ```
171
+
172
+ A result with `isError` set raises `StackOneAPIError`, which carries the status from the
173
+ payload in `status_code` and the body in `response_body`.
174
+
175
+ **Integers above 2^53 keep their exact value.** This SDK parses the server's JSON
176
+ itself, so it is not limited to the integer precision of IEEE 754 doubles; the Node
177
+ SDK's result can differ for a field that size, because its JSON parser has already
178
+ rounded it by the time the SDK sees it.
179
+
180
+ **File actions return a download link, not bytes.** The SDK does not follow the link.
181
+ When no link can be issued, the call raises `StackOneAPIError` with `status_code` 501.
182
+
183
+ ```python
184
+ # 2.x
185
+ result = download.execute({"id": "file-id"})
186
+ with open(result["file_name"] or "download.bin", "wb") as f:
187
+ f.write(result["content"])
188
+
189
+ # 3.0
190
+ from pathlib import Path
191
+
192
+ import httpx
193
+
194
+ link = download.execute({"id": "file-id"})["result"]
195
+ # {"download_url": ..., "expires_at": ..., "file": {"name", "content_type", "content_length"}}
196
+ name = Path(link["file"]["name"] or "download.bin").name # chosen by the provider: keep only the basename
197
+ Path(name).write_bytes(httpx.get(link["download_url"]).content)
198
+ ```
199
+
200
+ ## Tool arguments and headers
201
+
202
+ **Arguments are sent exactly as given**, as `tools/call` arguments. The SDK no longer
203
+ splits flat `path_`/`query_`/`body_` keys into a request envelope, because the server
204
+ maps them itself.
205
+
206
+ **`fetch_tools()` tools have the server's own argument shape.** In 2.x the SDK asked
207
+ the server for the flat, prefixed style (`?param-style=flat_prefixed`). That request is
208
+ gone, so the argument names are whatever the server serves. Read them from
209
+ `tool.parameters.properties` rather than hard-coding them:
210
+
211
+ ```python
212
+ # 2.x
213
+ tool.execute({"body_variables": {"first": 25}})
214
+
215
+ # 3.0: use the keys the schema names, for example
216
+ print(tool.parameters.properties)
217
+ tool.execute({"body": {"variables": {"first": 25}}})
218
+ ```
219
+
220
+ **Header arguments are allowlisted.** A header argument is an entry of a `headers`
221
+ object argument, or a top-level `headers_<name>` argument. Each one is forwarded only
222
+ if the tool's schema declares it in the same form: under `headers.properties`, or as a
223
+ `headers_<name>` property. An open `headers` object, `"type": "object"` with no
224
+ `properties` and `additionalProperties` not `false`, declares every name; without
225
+ `"type": "object"`, or with `additionalProperties: false` and no `properties`, it
226
+ declares none. `Authorization`, `x-account-id`, `User-Agent` and `x-end-user-id` are
227
+ never forwarded, even when declared, because the SDK sets them itself. A dropped header argument is
228
+ logged as a warning, except a null value, which is omitted silently. Every other
229
+ argument is sent unchanged.
230
+
231
+ A top-level `headers` argument that isn't a plain object is dropped with a warning,
232
+ unless the schema declares `headers` itself as a non-object field, in which case it's
233
+ an ordinary argument that happens to be named `headers`: it skips header filtering and is
234
+ sent as given, subject to the same JSON-value check as every other argument. A
235
+ `headers_<name>` argument is dropped with a warning when its value is a list or dict.
236
+
237
+ `stackone_execute_action` serves an open `headers` object, so `toolset.execute()` passes your
238
+ own headers on to the action, with the exception of those four:
239
+
240
+ ```python
241
+ toolset.execute("linear_list_comments", {"headers": {"x-request-id": "abc"}})
242
+ ```
243
+
244
+ ## Base URL
245
+
246
+ **`STACKONE_BASE_URL` is now read.** `base_url` still takes precedence when given;
247
+ otherwise the SDK now falls back to `STACKONE_BASE_URL` before the default,
248
+ `https://api.stackone.com`. An empty `base_url` argument or an empty
249
+ `STACKONE_BASE_URL` is treated as not set, not as a literal empty host.
250
+
251
+ ## Accounts
252
+
253
+ **With no account id, the SDK discovers your accounts.** In 2.x, calling
254
+ `fetch_tools()` with no account listed tools without an `x-account-id`. In 3.0 it asks
255
+ `GET /accounts` and lists the catalog of every active shared account (non-shared ones
256
+ too with `include_non_shared=True`). If you have many
257
+ accounts, pass `account_id=`, `account_ids=` or call `set_accounts()` so the SDK does
258
+ not fetch every catalog.
259
+
260
+ **Discovery skips non-shared accounts.** An account with `shared: false` belongs to a
261
+ single end user, so discovery leaves it out and logs a warning naming it; when every
262
+ active account is non-shared it raises `ToolsetConfigError` instead. Pass its id, or
263
+ construct the toolset with `include_non_shared=True`, to use it.
264
+
265
+ **A non-shared account's end user is sent for you.** The API requires every MCP request
266
+ for a non-shared account to carry the account's end-user id in `x-end-user-id`. Whenever
267
+ the SDK calls `GET /accounts` (discovery, or `fetch_accounts()`), it records the
268
+ `origin_username` of each account with `shared: false` and sends it as `x-end-user-id` on
269
+ every request for that account. With explicit account ids it makes no `GET /accounts` up
270
+ front: the first request for a non-shared account is refused, and the SDK then calls
271
+ `GET /accounts` once and retries it with the end user it records. Call
272
+ `toolset.fetch_accounts()` once first to skip that round trip.
273
+
274
+ **`STACKONE_ACCOUNT_ID` is not read.** Pass the account id as `account_id=` or
275
+ `execute={"account_ids": [...]}`. A toolset constructed with neither while
276
+ `STACKONE_ACCOUNT_ID` is set to a non-empty value logs a warning that it is ignored.
277
+
278
+ **An empty account id raises.** `StackOneToolSet(account_id="")` raises
279
+ `ToolsetConfigError`, where it used to be treated as no account at all. An account id
280
+ read from an environment variable that is set but empty now raises too, rather than
281
+ quietly reaching every active shared account:
282
+
283
+ ```python
284
+ # Raises when STACKONE_ACCOUNT_ID is set to ""
285
+ toolset = StackOneToolSet(account_id=os.getenv("STACKONE_ACCOUNT_ID"))
286
+
287
+ # Falls back to discovery only when the variable is unset or empty, deliberately
288
+ toolset = StackOneToolSet(account_id=os.getenv("STACKONE_ACCOUNT_ID") or None)
289
+ ```
290
+
291
+ An empty or non-string entry in `account_ids`, `set_accounts()` or
292
+ `execute={"account_ids": [...]}` raises `ToolsetConfigError` as well, and so does
293
+ passing a tuple (or any other non-list) there: pass a list, `list(ids)`, instead. A falsy entry
294
+ (`None`, `0`, `""`) used to produce an unscoped request with no `x-account-id`; a
295
+ truthy non-string entry used to be sent as the header's value.
296
+
297
+ **`get_tool()` returns the first of any duplicates.** When two accounts serve the same
298
+ tool name, `Tools.get_tool()` now returns the first one listed, where 2.x returned the
299
+ last. Listings are merged in sorted account order, and a warning names the clashing
300
+ tools. `execute_openai_tool_calls()`, `to_openai()`, `to_langchain()` and
301
+ `to_pydantic_ai()` use the same one: each builds one tool per name. Pass `account_ids`
302
+ to choose the account yourself.
303
+
304
+ **A failing account is skipped, and left out for 30 seconds.** In a multi-account scope, an
305
+ account whose listing fails with a non-429 error is skipped with a warning and left out of
306
+ the cached catalog for 30 seconds, unless every account fails: then the accounts' shared
307
+ `StackOneAPIError` is raised when they all failed with one status, and otherwise a
308
+ `ToolsetLoadError` with each account's error in `failures`. A 429 that outlasts its retries
309
+ is never skipped — it aborts the whole call, even when other accounts are healthy, since
310
+ it is the key's rate limit rather than that account's failure. A single-account scope
311
+ raises its failure rather than skipping or caching it. `execute()` does not wait the 30
312
+ seconds for an account that could serve the action, one whose provider is the action's
313
+ connector or unknown after one `GET /accounts` to learn it: it lists it again straight
314
+ away, and refuses to run the action on another account while it still fails.
315
+
316
+ ## Feedback
317
+
318
+ The client-side `tool_feedback` tool, `create_feedback_tool()` and the implicit
319
+ `feedback_*` execution options have been removed. `execute()` and `call()` no longer
320
+ take `options=`.
321
+
322
+ ```python
323
+ # 2.x
324
+ feedback_tool = toolset.fetch_tools(actions=["tool_*"]).get_tool("tool_feedback")
325
+ feedback_tool.call(feedback="Worked well", account_id="acc_123", tool_names=["workday_list_workers"])
326
+ tool.execute({"id": "1"}, options={"feedback_session_id": "chat-42"})
327
+
328
+ # 3.0
329
+ hit = toolset.search("list workers")[0]
330
+ toolset.execute(hit["action_id"], session_id=hit.get("session_id"))
331
+ toolset.submit_feedback(
332
+ "positive",
333
+ [hit["action_id"]],
334
+ feedback="Worked well",
335
+ session_id=hit.get("session_id"),
336
+ )
337
+ ```
338
+
339
+ When feedback is enabled for your project, the server also serves a
340
+ `stackone_submit_feedback` tool. **`fetch_tools()`, `openai()`, `langchain()` and
341
+ `pydantic_ai()` include it**, once, however many accounts are linked. If you don't want
342
+ a model to call it, filter it out:
343
+
344
+ ```python
345
+ from stackone_ai import Tools
346
+
347
+ tools = Tools([t for t in toolset.fetch_tools() if t.name != "stackone_submit_feedback"])
348
+ ```
349
+
350
+ `submit_feedback()` raises `ToolsetLoadError` when feedback is not enabled. It calls the
351
+ tool once, on the lowest account id in scope, and sends `action_run_id` when given.
352
+
353
+ ## Schemas given to a model
354
+
355
+ **`to_openai_function()` passes the served schema through.** 2.x kept only `type`,
356
+ `description`, `enum` and a shallow copy of `items`/`properties`. Now `format`,
357
+ `pattern`, `default`, bounds, `oneOf`/`anyOf` and nested `required` reach the model as
358
+ the server served them. `to_langchain()` and `to_pydantic_ai_tool()` hand over the
359
+ same schema.
360
+
361
+ **`required` is the served list.** 2.x derived `required` from each property's
362
+ internal `nullable` marker, which made every property of a hand-built tool with no
363
+ markers required. 3.0 emits the schema's own `required` list, in the order the server
364
+ sent it, and leaves it out when that list is missing or empty. Give a hand-built tool
365
+ its `required` list directly:
366
+
367
+ ```python
368
+ # 2.x: required, because "nullable" is absent
369
+ ToolParameters(type="object", properties={"id": {"type": "string"}})
370
+
371
+ # 3.0
372
+ ToolParameters(type="object", properties={"id": {"type": "string"}}, required=["id"])
373
+ ```
374
+
375
+ ## Hand-built tools and `ExecuteConfig`
376
+
377
+ **`StackOneTool` no longer makes HTTP requests.** Its `execute()` raises `StackOneError`
378
+ on the base class, so a hand-built tool must override it. Tools from `fetch_tools()` are
379
+ `StackOneMcpTool` instances, which execute over MCP. `_api_key` is now optional on the
380
+ base class.
381
+
382
+ ```python
383
+ # 2.x
384
+ tool = StackOneTool(
385
+ description="Get an employee",
386
+ parameters=ToolParameters(type="object", properties={"id": {"type": "string"}}),
387
+ _execute_config=ExecuteConfig(
388
+ name="get_employee", method="GET", url="https://api.example.com/employees/{id}",
389
+ parameter_locations={"id": ParameterLocation.PATH},
390
+ ),
391
+ _api_key="...",
392
+ )
393
+
394
+ # 3.0
395
+ class GetEmployee(StackOneTool):
396
+ def __init__(self) -> None:
397
+ super().__init__(
398
+ description="Get an employee",
399
+ parameters=ToolParameters(type="object", properties={"id": {"type": "string"}}, required=["id"]),
400
+ _execute_config=ExecuteConfig(name="get_employee"),
401
+ )
402
+
403
+ def execute(self, arguments=None):
404
+ args = json.loads(arguments) if isinstance(arguments, str) else dict(arguments or {})
405
+ return httpx.get(f"https://api.example.com/employees/{args['id']}").json()
406
+
407
+ tool = GetEmployee()
408
+ ```
409
+
410
+ Define `__init__` on the subclass, as above. `StackOneTool` is a pydantic model, so a
411
+ type checker gives a subclass without its own `__init__` one built from the model's
412
+ fields, which rejects `_execute_config` even though the call works at runtime.
413
+
414
+ **`ExecuteConfig` keeps only `name`, `headers` and `timeout`**, and rejects any other
415
+ field. Passing `method`, `url`, `body_type` or `parameter_locations` raises a pydantic
416
+ `ValidationError` rather than being silently ignored.
417
+
418
+ **`StackOneMcpTool(headers=...)` adds headers; it does not replace them.** The SDK
419
+ always sets `Authorization`, `x-account-id` and `User-Agent` itself, after your headers,
420
+ and drops any case variant of those names you pass. An `x-end-user-id` you pass is sent as
421
+ given. `headers` is optional.
422
+
423
+ **`StackOneToolSet(headers=...)` is new.** It sends extra headers on every request the
424
+ toolset makes, `GET /accounts` included, with the same rule: `Authorization`, `x-account-id`
425
+ and `User-Agent` are ignored with a warning, and `x-end-user-id` is passed through unless
426
+ `GET /accounts` reported the account's end user.
427
+
428
+ ## LangGraph helpers
429
+
430
+ `stackone_ai.integrations` has been removed. Pass the LangChain tools to LangGraph
431
+ directly:
432
+
433
+ ```python
434
+ # 2.x
435
+ from stackone_ai.integrations import to_tool_node
436
+ node = to_tool_node(tools)
437
+
438
+ # 3.0
439
+ from langgraph.prebuilt import ToolNode
440
+ node = ToolNode(tools.to_langchain())
441
+ ```
442
+
443
+ See [examples/langgraph_integration.py](examples/langgraph_integration.py) for a
444
+ complete agent.