Repository navigation
fix(AIC-3592): make native LangGraph hand-offs complete - #192
Merged
Merged
Conversation
toLangGraph failed every turn that handed off. The hand-off Command now carries a ToolMessage for its call, the tools node declares its hand-off targets as ends, a node no longer runs again after it hands off (single- and multi-child), only the first hand-off in a message routes, and parallel_tool_calls is no longer passed to bindTools. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 29, 2026
…t key Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A hand-off target can be on another provider. Live Whodunit run on 29 Oct: a Bedrock target with no tools rejected toolUse/toolResult blocks, and an OpenAI target rejected Claude reasoning_content. History from before a node was entered is now plain text: reasoning dropped, tool calls and results as lines. The node's own tool loop stays native. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the output Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…MessageChunk Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jeffdupont
marked this pull request as ready for review
October 29, 2026 19:25
aknight-ld
approved these changes
Oct 30, 2026
jeffdupont
added a commit
to launchdarkly/python-ai-sdk
that referenced
this pull request
Oct 30, 2026
## Summary Fixes [AIC-3592](https://launchdarkly.atlassian.net/browse/AIC-3592) (1.0 blocker) in Python. `to_lang_graph` failed every turn that handed off: the Whodunit app's `check --engine langgraph` failed 30 of 30 graph turns with `ChatBedrockConverse._converse_params() got an unexpected keyword argument 'parallelToolCalls'`. Implements the new §2.x.3 bullets in launchdarkly/ai-sdks-monorepo#118. In `packages/langchain-agents/src/launchdarkly_ai_langchain_agents/native_graph.py`: - **`parallel_tool_calls` is no longer passed to `bind_tools`.** This is the Bedrock error. The first-hand-off rule below replaces it. - **The hand-off `Command` answers its tool call.** It now adds a `ToolMessage` (`Handed off to <target>.`) with the call's id. The id comes from `InjectedToolCallId`, so the hand-off schema is now a small pydantic model (a dict schema skips the injection). `pydantic>=2.13` is declared, as in the other packages that import it. - **The routing node does not run again after a hand-off.** The static `tools -> node` edge is replaced by a conditional edge that goes back to the node only when no hand-off routed. This applies to single-child and multi-child nodes. - **Only the first hand-off in a message routes**, as `graph()` does. A later one returns `Ignored: only the first transfer in a turn routes.` and does not track `$ld:ai:graph:handoff_success`. - **A hand-off target sees provider-neutral text history.** Messages already in the state when a node is entered are rewritten for its model: reasoning and other non-text blocks dropped, each assistant message's tool calls appended as `[Tool call] <name> <args>` lines, the tool results after it folded into one user message of `[Tool result] <name>: <text>` lines in call order. Human and system messages pass unchanged. The node's own tool loop after entry stays native. A normal tool call in the same message as a hand-off still runs, in the same tools step, and the target receives its result (decision 1A on the ticket). ## Tests `TestNativeHandoffRealStateGraph` runs six cases on the real `StateGraph` with a mock model: a multi-child hand-off, a single-child hand-off, a normal call plus a hand-off, two hand-offs, no `parallel_tool_calls`, and a node's own tool loop staying native. Each hand-off case asserts the exact messages the target receives (no tool role, tool calls, or reasoning blocks). `TestToNeutralHistory` unit-tests the rewrite on one input covering every rule (reasoning, null text, an empty assistant, an unmatched result, call order, nested and non-ASCII args, a message chunk, an image in a human message); the TypeScript test uses the same input and expects the same output. Local: `make lint`, `make format-check`, `make typecheck`, `make test` (3718 passed, 18 skipped) all pass. The live Bedrock run is under "Live check". TypeScript: launchdarkly/js-ai-sdk#192. Go is out of scope for 1.0. ## Live check Passed on 29 Oct: js-ai-sdk#192 at `0a0dd22`, Whodunit `check --engine langgraph` on Bedrock, 15 of 15 graph lines, 0 errors. Bedrock end-to-end in CI: deferred to Q4 follow-up. 🤖 Generated with [Claude Code](https://claude.com/claude-code) [AIC-3592]: https://launchdarkly.atlassian.net/browse/AIC-3592?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Overview** > Fixes broken **LangGraph hand-offs** in `to_lang_graph` (AIC-3592), including the Bedrock failure from passing `parallel_tool_calls` to `bind_tools`. Hand-off routing now matches `graph()`: only the **first** `transfer_to_*` call in a turn navigates; extras return an ignored message. > > Hand-off **`Command`** updates now include a **`ToolMessage`** that answers the transfer tool call (via **`InjectedToolCallId`** on a small Pydantic `_HandoffArgs` schema). **`pydantic>=2.13`** is added as a direct dependency. After tools run, a **conditional edge** stops the routing node from executing again when a hand-off fired, instead of always looping back through `_tools → node`. > > When a node is **entered** (not continuing its own tool loop), prior conversation is rewritten with **`_to_neutral_history`**: reasoning/non-text blocks dropped, tool calls and results flattened to plain `[Tool call]` / `[Tool result]` text so downstream agents on other providers never see native tool or reasoning payloads. > > **Tests** add `TestNativeHandoffRealStateGraph` (six StateGraph scenarios) and `TestToNeutralHistory` aligned with the TypeScript rewrite fixture. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit cee3051. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Merged
jeffdupont
added a commit
that referenced
this pull request
Oct 30, 2026
Steps 1 and 3 of [AIC-3673](https://launchdarkly.atlassian.net/browse/AIC-3673). The Python half is launchdarkly/python-ai-sdk#242. ## What this does - **`release-as: 1.0.0`** on all 14 packages in `release-please-config.json`. The `node-workspace` plugin updates the internal dependency ranges. - **Publish jobs for `litellm-agents` and `litellm-messages`.** They had none, so `@launchdarkly/ai-litellm-*-0.2.0` was tagged but never published. Both are also added to the manual-publish choices. `changelog-sections` is left alone: #181 (merged) removed the evaluations Experimental rows when it promoted Evaluations from Code to core. ##⚠️ npm setup: trusted publishers still to confirm for 5 of 14 packages The 26 Oct release run ([40393827352](https://github.com/launchdarkly/js-ai-sdk/actions/runs/40393827352)) failed 3 publish jobs, and the litellm packages have never had a job: | Package | On npm | Failure | |---|---|---| | `@launchdarkly/ai-typesafe` | 0.0.0 placeholder | `E404 … PUT @launchdarkly/ai-typesafe`: the package doesn't exist, and OIDC can't create it | | `@launchdarkly/ai-vercel-agents` | 0.0.0 placeholder | `E403 OIDC permission denied` | | `@launchdarkly/ai-vercel-messages` | 0.0.0 placeholder | `E403 OIDC permission denied` | | `@launchdarkly/ai-litellm-agents` | 0.0.0 placeholder | no publish job (added here) | | `@launchdarkly/ai-litellm-messages` | 0.0.0 placeholder | no publish job (added here) | Before the release PR merges, an npm org admin needs to: 1. ~~Publish a 0.0.0 placeholder for `ai-typesafe`, `ai-litellm-agents` and `ai-litellm-messages`.~~ Done: all three are on npm at 0.0.0 (checked 29 Oct). 2. Add a trusted publisher (`launchdarkly/js-ai-sdk`, workflow `release-please.yml`) to all five. Otherwise 1.0.0 ships 9 of 14 packages. ## Merge this last Merge this only after the items below are done. Anything merged before the release PR (#171) merges ships in 1.0.0, and a `!` change merged after 1.0 forces 2.0. - [x] #181 feat(evaluations)!: promote Evaluations from Code to core. After it, `@launchdarkly/ai-server/experimental` exports nothing. - [x] #180 fix(SEC-9507)!: dispatch tools only from the active variation's config.tools - [x] #183 fix(claude-agents)!: honour max_tokens and disable thinking by default - [x] #111 feat(AIC-3439)!: wildcard handlers accept only named providers (merged 29 Oct) - [x] npm placeholders (above) - [ ] Trusted publishers on all five packages (above; needs an npm org admin to confirm) - [ ] #192 fix(AIC-3592): make native LangGraph hand-offs complete (ready for review; spec launchdarkly/ai-sdks-monorepo#118) - [ ] #194 fix(AIC-3573): never send a NativeTool to the model as a function tool (ready for review; spec launchdarkly/ai-sdks-monorepo#116) - [ ] #190 feat(client)!: capture conversation content by default and add configure() (approved; breaking, so it must land before #171) - [ ] #196 test(AIC-3716): run the OpenAI native graph with a tool on the real SDK (test only; optional for the release) Not in 1.0: #187 (Shadow Requests, closed). 1.0 ships with nothing experimental. ## After merging 1. Confirm the regenerated #171 lists 1.0.0 for all 14 packages and its CI is green, then merge it. 2. Install each package from npm into an empty project and import its entry points, in both ESM and CJS (AIC-3673 step 5). 3. Open a PR removing `release-as` straight away. ## Checks - The workflow YAML parses: 14 per-package publish jobs plus release-please and the canary. The new jobs are copies of `release-typesafe`. - No local build was run: this is config only, made through the GitHub API. 🤖 Generated with [Claude Code](https://claude.com/claude-code) [AIC-3673]: https://launchdarkly.atlassian.net/browse/AIC-3673?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Overview** > Prepares the monorepo for a coordinated **1.0.0** npm release by setting **`release-as: "1.0.0"`** on all 14 workspaces in `release-please-config.json` (the `node-workspace` plugin will align internal dependency ranges when the release PR lands). > > The **`release-please`** workflow now fully covers **`litellm-agents`** and **`litellm-messages`**: they appear in manual `workflow_dispatch` package choices, expose release-please job outputs, and get dedicated **`release-litellm-*`** publish jobs (build, attest, npm publish) matching the other packages—closing the gap where LiteLLM tags could be created without a publish step. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit af882d4. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: andrewklatzke <aklatzke@launchdarkly.com>
jeffdupont
pushed a commit
that referenced
this pull request
Oct 30, 2026
🤖 I have created a release *beep* *boop* --- <details><summary>@launchdarkly/ai-claude-agents: 1.0.0</summary> ## [1.0.0](https://github.com/launchdarkly/js-ai-sdk/compare/@launchdarkly/ai-claude-agents-0.5.0...@launchdarkly/ai-claude-agents-1.0.0) (2026-10-30) ### ⚠ BREAKING CHANGES * **client:** conversation content is now captured on spans by default. To keep it off, call configure({ options: { captureContent: false } }) once at startup. * **client:** createHandler returns a new wrapper function rather than adding providesFor to the function you pass and returning that same function. Use the handler createHandler returns; the function you passed in no longer carries providesFor. * **client:** add result variant and value to the feature_flag span event ([#186](#186)) * **SEC-9507:** enable Claude native tools only when the active variation attaches them * **claude-agents:** honour the config's max_tokens and disable thinking unless the config asks ([#183](#183)) * **skills:** rename FDv2SkillStore to LaunchDarklySkillStore ([#172](#172)) * **AIC-3598:** type toolHandlers on the handler contract; declare @langchain/anthropic ([#170](#170)) ### Features * **client:** add result variant and value to the feature_flag span event ([#186](#186)) ([8dbe2ec](8dbe2ec)) * **client:** capture conversation content on spans by default and add configure() ([d9c7fc3](d9c7fc3)) * **skills:** give Claude Agents runs the skills attached to their variation ([#136](#136)) ([df16ba0](df16ba0)) * **skills:** rename FDv2SkillStore to LaunchDarklySkillStore ([#172](#172)) ([d3e7c5f](d3e7c5f)) ### Bug Fixes * **AIC-3598:** type toolHandlers on the handler contract; declare @langchain/anthropic ([#170](#170)) ([c6c00fc](c6c00fc)) * **claude-agents:** honour the config's max_tokens and disable thinking unless the config asks ([#183](#183)) ([f58ce21](f58ce21)) * **client:** return a new handler from createHandler instead of changing the function passed in ([d9c7fc3](d9c7fc3)) * **SEC-9507:** dispatch litellm-messages tool calls only to tools offered for the request ([6926e8a](6926e8a)) * **SEC-9507:** enable Claude native tools only when the active variation attaches them ([6926e8a](6926e8a)) * **SEC-9507:** pass handlers only the tools attached to the active variation ([6926e8a](6926e8a)) ### Dependencies * The following workspace dependencies were updated * dependencies * @launchdarkly/ai-server bumped from ^0.5.0 to ^1.0.0 </details> <details><summary>@launchdarkly/ai-claude-messages: 1.0.0</summary> ## [1.0.0](https://github.com/launchdarkly/js-ai-sdk/compare/@launchdarkly/ai-claude-messages-0.5.0...@launchdarkly/ai-claude-messages-1.0.0) (2026-10-30) ### ⚠ BREAKING CHANGES * **client:** conversation content is now captured on spans by default. To keep it off, call configure({ options: { captureContent: false } }) once at startup. * **client:** createHandler returns a new wrapper function rather than adding providesFor to the function you pass and returning that same function. Use the handler createHandler returns; the function you passed in no longer carries providesFor. * **AIC-3598:** type toolHandlers on the handler contract; declare @langchain/anthropic ([#170](#170)) ### Features * **client:** capture conversation content on spans by default and add configure() ([d9c7fc3](d9c7fc3)) ### Bug Fixes * **AIC-3573:** never send a NativeTool to the model as a function tool ([#194](#194)) ([ec43b85](ec43b85)) * **AIC-3598:** type toolHandlers on the handler contract; declare @langchain/anthropic ([#170](#170)) ([c6c00fc](c6c00fc)) * **client:** return a new handler from createHandler instead of changing the function passed in ([d9c7fc3](d9c7fc3)) ### Dependencies * The following workspace dependencies were updated * dependencies * @launchdarkly/ai-server bumped from ^0.5.0 to ^1.0.0 </details> <details><summary>@launchdarkly/ai-langchain-agents: 1.0.0</summary> ## [1.0.0](https://github.com/launchdarkly/js-ai-sdk/compare/@launchdarkly/ai-langchain-agents-0.5.0...@launchdarkly/ai-langchain-agents-1.0.0) (2026-10-30) ### ⚠ BREAKING CHANGES * **client:** conversation content is now captured on spans by default. To keep it off, call configure({ options: { captureContent: false } }) once at startup. * **client:** createHandler returns a new wrapper function rather than adding providesFor to the function you pass and returning that same function. Use the handler createHandler returns; the function you passed in no longer carries providesFor. * **client:** add result variant and value to the feature_flag span event ([#186](#186)) * **AIC-3439:** let wildcard handlers accept only named providers ([#111](#111)) * **AIC-3598:** type toolHandlers on the handler contract; declare @langchain/anthropic ([#170](#170)) ### Features * **AIC-3439:** let wildcard handlers accept only named providers ([#111](#111)) ([9371975](9371975)) * **client:** add result variant and value to the feature_flag span event ([#186](#186)) ([8dbe2ec](8dbe2ec)) * **client:** capture conversation content on spans by default and add configure() ([d9c7fc3](d9c7fc3)) ### Bug Fixes * **AIC-3573:** never send a NativeTool to the model as a function tool ([#194](#194)) ([ec43b85](ec43b85)) * **AIC-3592:** make native LangGraph hand-offs complete ([#192](#192)) ([025f2ce](025f2ce)) * **AIC-3598:** type toolHandlers on the handler contract; declare @langchain/anthropic ([#170](#170)) ([c6c00fc](c6c00fc)) * **client:** return a new handler from createHandler instead of changing the function passed in ([d9c7fc3](d9c7fc3)) ### Dependencies * The following workspace dependencies were updated * dependencies * @launchdarkly/ai-server bumped from ^0.5.0 to ^1.0.0 </details> <details><summary>@launchdarkly/ai-langchain-messages: 1.0.0</summary> ## [1.0.0](https://github.com/launchdarkly/js-ai-sdk/compare/@launchdarkly/ai-langchain-messages-0.5.0...@launchdarkly/ai-langchain-messages-1.0.0) (2026-10-30) ### ⚠ BREAKING CHANGES * **client:** conversation content is now captured on spans by default. To keep it off, call configure({ options: { captureContent: false } }) once at startup. * **client:** createHandler returns a new wrapper function rather than adding providesFor to the function you pass and returning that same function. Use the handler createHandler returns; the function you passed in no longer carries providesFor. * **client:** add result variant and value to the feature_flag span event ([#186](#186)) * **AIC-3439:** let wildcard handlers accept only named providers ([#111](#111)) * **AIC-3686:** rename the LangChain and OpenAI messages factories to *MessagesHandler ([#173](#173)) ### Features * **AIC-3439:** let wildcard handlers accept only named providers ([#111](#111)) ([9371975](9371975)) * **AIC-3686:** rename the LangChain and OpenAI messages factories to *MessagesHandler ([#173](#173)) ([15a0d37](15a0d37)) * **client:** add result variant and value to the feature_flag span event ([#186](#186)) ([8dbe2ec](8dbe2ec)) * **client:** capture conversation content on spans by default and add configure() ([d9c7fc3](d9c7fc3)) ### Bug Fixes * **AIC-3573:** never send a NativeTool to the model as a function tool ([#194](#194)) ([ec43b85](ec43b85)) * **AIC-3726:** realign three usage IDs with the 1.0 factory names ([#197](#197)) ([fc83c7a](fc83c7a)) * **client:** return a new handler from createHandler instead of changing the function passed in ([d9c7fc3](d9c7fc3)) ### Dependencies * The following workspace dependencies were updated * dependencies * @launchdarkly/ai-server bumped from ^0.5.0 to ^1.0.0 </details> <details><summary>@launchdarkly/ai-litellm-agents: 1.0.0</summary> ## [1.0.0](https://github.com/launchdarkly/js-ai-sdk/compare/@launchdarkly/ai-litellm-agents-0.2.0...@launchdarkly/ai-litellm-agents-1.0.0) (2026-10-30) ### ⚠ BREAKING CHANGES * **client:** conversation content is now captured on spans by default. To keep it off, call configure({ options: { captureContent: false } }) once at startup. * **client:** createHandler returns a new wrapper function rather than adding providesFor to the function you pass and returning that same function. Use the handler createHandler returns; the function you passed in no longer carries providesFor. ### Features * **client:** capture conversation content on spans by default and add configure() ([d9c7fc3](d9c7fc3)) ### Bug Fixes * **AIC-3573:** never send a NativeTool to the model as a function tool ([#194](#194)) ([ec43b85](ec43b85)) * **client:** return a new handler from createHandler instead of changing the function passed in ([d9c7fc3](d9c7fc3)) ### Dependencies * The following workspace dependencies were updated * dependencies * @launchdarkly/ai-server bumped from ^0.5.0 to ^1.0.0 </details> <details><summary>@launchdarkly/ai-litellm-messages: 1.0.0</summary> ## [1.0.0](https://github.com/launchdarkly/js-ai-sdk/compare/@launchdarkly/ai-litellm-messages-0.2.0...@launchdarkly/ai-litellm-messages-1.0.0) (2026-10-30) ### ⚠ BREAKING CHANGES * **client:** conversation content is now captured on spans by default. To keep it off, call configure({ options: { captureContent: false } }) once at startup. * **client:** createHandler returns a new wrapper function rather than adding providesFor to the function you pass and returning that same function. Use the handler createHandler returns; the function you passed in no longer carries providesFor. * **SEC-9507:** enable Claude native tools only when the active variation attaches them * **AIC-3598:** type toolHandlers on the handler contract; declare @langchain/anthropic ([#170](#170)) ### Features * **client:** capture conversation content on spans by default and add configure() ([d9c7fc3](d9c7fc3)) ### Bug Fixes * **AIC-3573:** never send a NativeTool to the model as a function tool ([#194](#194)) ([ec43b85](ec43b85)) * **AIC-3598:** type toolHandlers on the handler contract; declare @langchain/anthropic ([#170](#170)) ([c6c00fc](c6c00fc)) * **client:** return a new handler from createHandler instead of changing the function passed in ([d9c7fc3](d9c7fc3)) * **SEC-9507:** dispatch litellm-messages tool calls only to tools offered for the request ([6926e8a](6926e8a)) * **SEC-9507:** enable Claude native tools only when the active variation attaches them ([6926e8a](6926e8a)) * **SEC-9507:** pass handlers only the tools attached to the active variation ([6926e8a](6926e8a)) ### Dependencies * The following workspace dependencies were updated * dependencies * @launchdarkly/ai-server bumped from ^0.5.0 to ^1.0.0 </details> <details><summary>@launchdarkly/ai-node: 1.0.0</summary> ## [1.0.0](https://github.com/launchdarkly/js-ai-sdk/compare/@launchdarkly/ai-node-0.4.0...@launchdarkly/ai-node-1.0.0) (2026-10-30) ### ⚠ BREAKING CHANGES * **evaluations:** promote Evaluations from Code to core ([#181](#181)) * **AIC-3686:** rename the LangChain and OpenAI messages factories to *MessagesHandler ([#173](#173)) * **skills:** rename FDv2SkillStore to LaunchDarklySkillStore ([#172](#172)) ### Features * **AIC-3686:** rename the LangChain and OpenAI messages factories to *MessagesHandler ([#173](#173)) ([15a0d37](15a0d37)) * **evaluations:** promote Evaluations from Code to core ([#181](#181)) ([676a2ed](676a2ed)) * **skills:** rename FDv2SkillStore to LaunchDarklySkillStore ([#172](#172)) ([d3e7c5f](d3e7c5f)) ### Dependencies * The following workspace dependencies were updated * dependencies * @launchdarkly/ai-server bumped from ^0.5.0 to ^1.0.0 </details> <details><summary>@launchdarkly/ai-openai-agents: 1.0.0</summary> ## [1.0.0](https://github.com/launchdarkly/js-ai-sdk/compare/@launchdarkly/ai-openai-agents-0.5.0...@launchdarkly/ai-openai-agents-1.0.0) (2026-10-30) ### ⚠ BREAKING CHANGES * **client:** conversation content is now captured on spans by default. To keep it off, call configure({ options: { captureContent: false } }) once at startup. * **client:** createHandler returns a new wrapper function rather than adding providesFor to the function you pass and returning that same function. Use the handler createHandler returns; the function you passed in no longer carries providesFor. * **client:** add result variant and value to the feature_flag span event ([#186](#186)) * **AIC-3687:** rename createOpenAIAgentHandler to createOpenAIAgentsHandler ([#174](#174)) ### Features * **AIC-3687:** rename createOpenAIAgentHandler to createOpenAIAgentsHandler ([#174](#174)) ([cfba4d2](cfba4d2)) * **client:** add result variant and value to the feature_flag span event ([#186](#186)) ([8dbe2ec](8dbe2ec)) * **client:** capture conversation content on spans by default and add configure() ([d9c7fc3](d9c7fc3)) ### Bug Fixes * **AIC-3573:** never send a NativeTool to the model as a function tool ([#194](#194)) ([ec43b85](ec43b85)) * **AIC-3726:** realign three usage IDs with the 1.0 factory names ([#197](#197)) ([fc83c7a](fc83c7a)) * **client:** return a new handler from createHandler instead of changing the function passed in ([d9c7fc3](d9c7fc3)) ### Dependencies * The following workspace dependencies were updated * dependencies * @launchdarkly/ai-server bumped from ^0.5.0 to ^1.0.0 </details> <details><summary>@launchdarkly/ai-openai-messages: 1.0.0</summary> ## [1.0.0](https://github.com/launchdarkly/js-ai-sdk/compare/@launchdarkly/ai-openai-messages-0.5.0...@launchdarkly/ai-openai-messages-1.0.0) (2026-10-30) ### ⚠ BREAKING CHANGES * **client:** conversation content is now captured on spans by default. To keep it off, call configure({ options: { captureContent: false } }) once at startup. * **client:** createHandler returns a new wrapper function rather than adding providesFor to the function you pass and returning that same function. Use the handler createHandler returns; the function you passed in no longer carries providesFor. * **AIC-3686:** rename the LangChain and OpenAI messages factories to *MessagesHandler ([#173](#173)) * **AIC-3598:** type toolHandlers on the handler contract; declare @langchain/anthropic ([#170](#170)) ### Features * **AIC-3686:** rename the LangChain and OpenAI messages factories to *MessagesHandler ([#173](#173)) ([15a0d37](15a0d37)) * **client:** capture conversation content on spans by default and add configure() ([d9c7fc3](d9c7fc3)) ### Bug Fixes * **AIC-3573:** never send a NativeTool to the model as a function tool ([#194](#194)) ([ec43b85](ec43b85)) * **AIC-3598:** type toolHandlers on the handler contract; declare @langchain/anthropic ([#170](#170)) ([c6c00fc](c6c00fc)) * **AIC-3726:** realign three usage IDs with the 1.0 factory names ([#197](#197)) ([fc83c7a](fc83c7a)) * **client:** return a new handler from createHandler instead of changing the function passed in ([d9c7fc3](d9c7fc3)) ### Dependencies * The following workspace dependencies were updated * dependencies * @launchdarkly/ai-server bumped from ^0.5.0 to ^1.0.0 </details> <details><summary>@launchdarkly/ai-otel: 1.0.0</summary> ## [1.0.0](https://github.com/launchdarkly/js-ai-sdk/compare/@launchdarkly/ai-otel-0.3.0...@launchdarkly/ai-otel-1.0.0) (2026-10-30) ### ⚠ BREAKING CHANGES * **client:** spans carry launchdarkly.project_id instead of highlight.project_id ### Bug Fixes * **client:** stamp launchdarkly.project_id on the telemetry resource ([#176](#176)) ([93fb412](93fb412)) </details> <details><summary>@launchdarkly/ai-server: 1.0.0</summary> ## [1.0.0](https://github.com/launchdarkly/js-ai-sdk/compare/@launchdarkly/ai-server-0.5.0...@launchdarkly/ai-server-1.0.0) (2026-10-30) ### ⚠ BREAKING CHANGES * **client:** conversation content is now captured on spans by default. To keep it off, call configure({ options: { captureContent: false } }) once at startup. * **client:** createHandler returns a new wrapper function rather than adding providesFor to the function you pass and returning that same function. Use the handler createHandler returns; the function you passed in no longer carries providesFor. * **client:** add result variant and value to the feature_flag span event ([#186](#186)) * **AIC-3439:** let wildcard handlers accept only named providers ([#111](#111)) * **SEC-9507:** enable Claude native tools only when the active variation attaches them * **evaluations:** export each offline row as one trace ([#175](#175)) * **evaluations:** promote Evaluations from Code to core ([#181](#181)) * **evaluations:** remove default pass rate threshold ([#184](#184)) * **client:** spans carry launchdarkly.project_id instead of highlight.project_id * **AIC-3687:** rename createOpenAIAgentHandler to createOpenAIAgentsHandler ([#174](#174)) * **AIC-3686:** rename the LangChain and OpenAI messages factories to *MessagesHandler ([#173](#173)) * **skills:** rename FDv2SkillStore to LaunchDarklySkillStore ([#172](#172)) * **AIC-3598:** type toolHandlers on the handler contract; declare @langchain/anthropic ([#170](#170)) ### Features * **AIC-3439:** let wildcard handlers accept only named providers ([#111](#111)) ([9371975](9371975)) * **AIC-3686:** rename the LangChain and OpenAI messages factories to *MessagesHandler ([#173](#173)) ([15a0d37](15a0d37)) * **AIC-3687:** rename createOpenAIAgentHandler to createOpenAIAgentsHandler ([#174](#174)) ([cfba4d2](cfba4d2)) * **client:** add result variant and value to the feature_flag span event ([#186](#186)) ([8dbe2ec](8dbe2ec)) * **client:** capture conversation content on spans by default and add configure() ([d9c7fc3](d9c7fc3)) * **client:** point judge spans at the run they graded ([#200](#200)) ([ba9c936](ba9c936)) * **evaluations:** export each offline row as one trace ([#175](#175)) ([05a7eb1](05a7eb1)) * **evaluations:** promote Evaluations from Code to core ([#181](#181)) ([676a2ed](676a2ed)) * **evaluations:** remove default pass rate threshold ([#184](#184)) ([f3a3e37](f3a3e37)) * **skills:** give Claude Agents runs the skills attached to their variation ([#136](#136)) ([df16ba0](df16ba0)) * **skills:** rename FDv2SkillStore to LaunchDarklySkillStore ([#172](#172)) ([d3e7c5f](d3e7c5f)) ### Bug Fixes * **AIC-3573:** never send a NativeTool to the model as a function tool ([#194](#194)) ([ec43b85](ec43b85)) * **AIC-3598:** type toolHandlers on the handler contract; declare @langchain/anthropic ([#170](#170)) ([c6c00fc](c6c00fc)) * **AIC-3601:** stop multi-edge routing text from asking a router for a final answer ([#177](#177)) ([10cce54](10cce54)) * **client:** return a new handler from createHandler instead of changing the function passed in ([d9c7fc3](d9c7fc3)) * **client:** stamp launchdarkly.project_id on the telemetry resource ([#176](#176)) ([93fb412](93fb412)) * **evaluations:** update stale test import ([#189](#189)) ([f4d8083](f4d8083)) * **evaluations:** warn once when a scorer returns an invalid score ([#199](#199)) ([f43c888](f43c888)) * **graph:** report a node's own judge scores on its node_done stream event ([#182](#182)) ([1230f02](1230f02)) * **SEC-9507:** dispatch litellm-messages tool calls only to tools offered for the request ([6926e8a](6926e8a)) * **SEC-9507:** enable Claude native tools only when the active variation attaches them ([6926e8a](6926e8a)) * **SEC-9507:** pass handlers only the tools attached to the active variation ([6926e8a](6926e8a)) </details> <details><summary>@launchdarkly/ai-typesafe: 1.0.0</summary> ## [1.0.0](https://github.com/launchdarkly/js-ai-sdk/compare/@launchdarkly/ai-typesafe-0.2.0...@launchdarkly/ai-typesafe-1.0.0) (2026-10-30) ### ⚠ BREAKING CHANGES * **client:** conversation content is now captured on spans by default. To keep it off, call configure({ options: { captureContent: false } }) once at startup. * **client:** createHandler returns a new wrapper function rather than adding providesFor to the function you pass and returning that same function. Use the handler createHandler returns; the function you passed in no longer carries providesFor. ### Features * **client:** capture conversation content on spans by default and add configure() ([d9c7fc3](d9c7fc3)) ### Bug Fixes * **client:** return a new handler from createHandler instead of changing the function passed in ([d9c7fc3](d9c7fc3)) ### Dependencies * The following workspace dependencies were updated * dependencies * @launchdarkly/ai-server bumped from ^0.5.0 to ^1.0.0 </details> <details><summary>@launchdarkly/ai-vercel-agents: 1.0.0</summary> ## [1.0.0](https://github.com/launchdarkly/js-ai-sdk/compare/@launchdarkly/ai-vercel-agents-0.3.0...@launchdarkly/ai-vercel-agents-1.0.0) (2026-10-30) ### ⚠ BREAKING CHANGES * **client:** conversation content is now captured on spans by default. To keep it off, call configure({ options: { captureContent: false } }) once at startup. * **client:** createHandler returns a new wrapper function rather than adding providesFor to the function you pass and returning that same function. Use the handler createHandler returns; the function you passed in no longer carries providesFor. ### Features * **client:** capture conversation content on spans by default and add configure() ([d9c7fc3](d9c7fc3)) ### Bug Fixes * **AIC-3573:** never send a NativeTool to the model as a function tool ([#194](#194)) ([ec43b85](ec43b85)) * **client:** return a new handler from createHandler instead of changing the function passed in ([d9c7fc3](d9c7fc3)) ### Dependencies * The following workspace dependencies were updated * dependencies * @launchdarkly/ai-server bumped from ^0.5.0 to ^1.0.0 </details> <details><summary>@launchdarkly/ai-vercel-messages: 1.0.0</summary> ## [1.0.0](https://github.com/launchdarkly/js-ai-sdk/compare/@launchdarkly/ai-vercel-messages-0.3.0...@launchdarkly/ai-vercel-messages-1.0.0) (2026-10-30) ### ⚠ BREAKING CHANGES * **client:** conversation content is now captured on spans by default. To keep it off, call configure({ options: { captureContent: false } }) once at startup. * **client:** createHandler returns a new wrapper function rather than adding providesFor to the function you pass and returning that same function. Use the handler createHandler returns; the function you passed in no longer carries providesFor. ### Features * **client:** capture conversation content on spans by default and add configure() ([d9c7fc3](d9c7fc3)) ### Bug Fixes * **AIC-3573:** never send a NativeTool to the model as a function tool ([#194](#194)) ([ec43b85](ec43b85)) * **client:** return a new handler from createHandler instead of changing the function passed in ([d9c7fc3](d9c7fc3)) ### Dependencies * The following workspace dependencies were updated * dependencies * @launchdarkly/ai-server bumped from ^0.5.0 to ^1.0.0 </details> --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Overview** > This **release-please** PR cuts **1.0.0** for every workspace package (`@launchdarkly/ai-server`, `@launchdarkly/ai-node`, provider adapters, `@launchdarkly/ai-otel`, `@launchdarkly/ai-typesafe`) by updating `.release-please-manifest.json`, each `package.json` / `src/version.ts`, and adding **1.0.0** changelog sections. Provider packages bump their `@launchdarkly/ai-server` dependency to **^1.0.0**. > > The release notes bundled here flag **breaking** upgrades integrators must handle: **`configure()`** with **conversation content on spans by default** (opt out via `captureContent: false`); **`createHandler`** now returns a **new wrapper** instead of mutating the passed function; renamed factories (**`*MessagesHandler`**, **`createOpenAIAgentsHandler`**); **`LaunchDarklySkillStore`** (was `FDv2SkillStore`); **Evaluations promoted to core** with offline export/trace and threshold behavior changes; telemetry **`launchdarkly.project_id`** (was `highlight.project_id`); richer **`feature_flag`** span events; and **SEC-9507** tool scoping (native tools and handlers only see tools on the active variation). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit f87c9a6. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes AIC-3592 (1.0 blocker) in TypeScript.
toLangGraphfailed every turn that handed off: the Whodunit app'scheck --engine langgraphfailed 30 of 30 graph turns with "Node whodunit-manor-butler is not reachable". Implements the new §2.x.3 bullets in launchdarkly/ai-sdks-monorepo#118.In
packages/langchain-agents/src/native-graph.ts:Commandanswers its tool call. It now adds aToolMessage(Handed off to <target>.) with the call's id, so the target's model call never sees an unansweredtool_use.ends, so LangGraph accepts theCommandtarget. This is the "not reachable" error.tools -> nodeedge is replaced by a conditional edge that goes back to the node only when no hand-off routed. This applies to single-child and multi-child nodes.graph()does. A later one returnsIgnored: only the first transfer in a turn routes.and does not track$ld:ai:graph:handoff_success.parallel_tool_callsis no longer passed tobindTools. Bedrock Converse rejects it; the first-hand-off rule replaces it.[Tool call] <name> <args>lines, the tool results after it folded into one user message of[Tool result] <name>: <text>lines in call order. Human and system messages pass unchanged. The node's own tool loop after entry stays native.A normal tool call in the same message as a hand-off still runs, in the same tools step, and the target receives its result (decision 1A on the ticket).
Tests
native-graph.shallow.test.tsruns six new cases on the realStateGraphwith a mock model: a multi-child hand-off, a single-child hand-off, a normal call plus a hand-off, two hand-offs, noparallel_tool_calls, and a node's own tool loop staying native. Each hand-off case asserts the exact messages the target receives (no tool role, tool calls, or reasoning blocks). AtoNeutralHistoryunit test covers every rewrite rule on the same input as the Python test, with the same expected output. The helper is exported fromnative-graph.tsfor that test only; the package index does not re-export it (api:checkpasses). The unused_textand the duplicate final-output extraction now usecontentText. The mocked executor test now asserts theToolMessagein theCommandupdate.Local:
yarn code:check,yarn typecheck,yarn api:check,yarn testall pass. The live Bedrock run is under "Live check".Python: launchdarkly/python-ai-sdk#246. Go is out of scope for 1.0.
Live check
Passed on 29 Oct at head
0a0dd22: Whodunitcheck --engine langgraphon Bedrock completed 15 of 15 graph lines with 0 errors (the run atb528263completed 9 of 16, with thetoolConfigandreasoning_contenterrors). Reachability 5/5, verdict format 6/6. The later commitae7a03echanges only the test file.Bedrock end-to-end in CI: deferred to Q4 follow-up.
🤖 Generated with Claude Code
Note
Overview
Fixes broken LangGraph hand-offs in
toLangGraphthat caused unreachable target nodes and failed multi-agent runs (e.g. Whodunit on Bedrock).Hand-off tools now return a
Commandthat includes aToolMessageanswering the transfer call, registerToolNodeendsfor validgototargets, and use conditional edges so the routing node does not run again after a successful hand-off. Only the firsttransfer_to_*in a turn routes; later ones return an ignored message.parallel_tool_callsis no longer passed tobindTools(Bedrock compatibility); first-wins routing replaces it.When a node is entered via hand-off, prior conversation is passed through new
toNeutralHistory(reasoning/non-text stripped, tool calls/results as plain[Tool call]/[Tool result]lines); the node’s own tool loop after entry stays native.Adds real
StateGraphintegration tests for hand-off scenarios and atoNeutralHistoryunit test aligned with Python; updates the mocked hand-off executor test for theCommandmessage update.Reviewed by Cursor Bugbot for commit ae7a03e. Bugbot is set up for automated code reviews on this repo. Configure here.