Skip to content

feat(client)!: add result variant and value to the feature_flag span event - #186

Merged
apucacao merged 13 commits into
mainfrom
ag/ff-result-variant
Oct 29, 2026
Merged

apucacao merged 13 commits into
mainfrom
ag/ff-result-variant

Conversation

@apucacao

@apucacao apucacao commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Runs made through the turnkey SDK don't show up on an AI Config's monitoring traces tab. Turnkey writes the feature_flag span event without feature_flag.result.value. Since launchdarkly/observability#2553, a trace search for a flag key only scans hours where a feature_flag event with a value was recorded, so turnkey runs are never found. The LaunchDarkly observability plugins always send the value (the SDK tracing hook with includeValue: true).

This adds two attributes to the event setLdSpanAttributes writes:

  • feature_flag.result.variant: the variation key. The OpenTelemetry feature-flag conventions make it conditionally required when the provider has variants.
  • feature_flag.result.value: with content capture on, the evaluated variation, serialized with JSON.stringify exactly as TracingHook does. With capture off, the variation key, JSON-encoded like any flag value (for example "opus"), so the prompt is never sent unless capture is on.

What changed

  • extractVariation returns the raw variation it already receives from client.variation. invoke, stream, inline judges and graph nodes pass it to handlers as variables.__ldValue, next to __ld.
  • Deferred judges get the value too. JudgeTask has a new optional field, judgeValue: the judge variation exactly as the LaunchDarkly SDK returned it. buildJudgeTasks fills it, and runJudge passes it on under the same capture check as the inline path. It is plain JSON, so a task still survives structured clone, and it adds no new kind of content, since the task already carries judgeConfig and actualOutput.
  • The launchdarkly.graph root span gets the value too. graph().invoke() and graph().stream() share one code path, which now passes the graph flag's raw variation to the root span. The root has no single handler, so it follows the process-wide content capture default, which is off today. With the default off, the root keeps the JSON-encoded variation key.
  • The value is deliberately not added to TrackData. TrackData is the payload of every $ld:ai:* track event, and the value includes the prompt.
  • The core passes the raw variation only to handlers with captureContent on. executeAndTrack and executeAndStream set __ldValue only when the handler's captureContent is true, the same check judge reasoning uses, so the raw config never reaches a capture-off handler. That check, and the graph root's, go through one function, captureContentFor, which returns a handler's own captureContent or the process-wide default when there is no handler or it states none. This uses the existing captureContent handler option; there is no new setting.
  • setLdSpanAttributes writes the variant when the variation key is non-empty. For the value it writes the raw variation's JSON when __ldValue is present, otherwise the JSON-encoded variation key, and leaves it off when there is neither (native-graph spans).
  • The only public API change is the optional JudgeTask.judgeValue field. setLdSpanAttributes keeps its signature and the handlers are unchanged.
  • AGENTS.md describes the new attributes, including the deferred judge and graph root cases.

vercelEvaluate has no raw variation at hand, so its value is always the variation key.

Testing

  • New tests: the core passes __ldValue only to a capture-on handler, for both executeAndTrack and executeAndStream; the value is JSON.stringify of the raw variation when present, the JSON-encoded variation key otherwise, and absent with neither; __ldValue reaches the handler and stays out of TrackData and track payloads.
  • New tests: a JudgeTask built from a judge variation carries it as judgeValue through structured clone, and its runJudge run writes the raw variation as feature_flag.result.value with capture on and the variation key with capture off. The launchdarkly.graph root writes the variation key with the default off and the raw graph variation with it on, for both invoke and stream.
  • Updated the four handler tests that assert the root event's exact attributes (claude-agents, langchain-agents, langchain-messages, openai-agents).
  • Ran the handler, graph, span, tracking and utils tests in every package, the full client test suite, tsc --noEmit in every package, api:check, api:surface and biome.
  • End to end: with this build installed, a real agent run's trace appeared on the AI Config monitoring tab on catamorphic, where it hadn't before.

The cross-SDK spec change is in launchdarkly/ai-sdks-monorepo#113.


Note

Overview
Turnkey SDK runs were missing from AI Config monitoring traces because the root feature_flag event lacked feature_flag.result.value, which observability now requires to correlate traces with a flag. This PR adds feature_flag.result.variant (served variation key, omitted when empty) and feature_flag.result.value on the event setLdSpanAttributes writes, aligned with LaunchDarkly OTel TracingHook serialization.

extractVariation now returns the raw SDK value, threaded through invoke/stream, graph nodes, and judges as optional evaluatedValue. The core exposes it to handlers only as variables.__ldValue when captureContentFor(handler) is true (handler captureContent or process default); otherwise the event gets JSON-encoded variation key only. Raw variation never lands in TrackData or track payloads. Deferred judges store it on optional JudgeTask.judgeValue; the launchdarkly.graph root uses the graph flag raw value only when the process-wide capture default is on.

captureContentFor centralizes that gate. Handler package tests and AGENTS.md are updated; the only public API addition is JudgeTask.judgeValue.

Reviewed by Cursor Bugbot for commit 869636a. Bugbot is set up for automated code reviews on this repo. Configure here.

extractVariation now returns the raw variation it already gets from client.variation, and invoke, stream, inline judges and graph nodes hand it to handlers as variables.__ldValue, next to __ld. It is kept out of TrackData on purpose: TrackData is the payload of every $ld:ai:* track event, and the value includes the prompt.
…vent

AI Config monitoring only finds traces whose feature_flag event carries feature_flag.result.value, which the LaunchDarkly observability plugins always send. Write the variation key as feature_flag.result.variant (conditionally required by the OpenTelemetry feature-flag conventions) and the evaluated value as feature_flag.result.value, serialized with JSON.stringify exactly like TracingHook with includeValue. Both are omitted when there is nothing to write, for example on native-graph spans.
@apucacao
apucacao marked this pull request as ready for review October 28, 2026 19:39
The raw variation holds the prompt, so writing it on every run's span event
bypassed the content-capture opt-in. setLdSpanAttributes now takes a
captureContent argument (default false). With capture on and a raw variation
present, feature_flag.result.value is its JSON, as before. Otherwise it is the
JSON-encoded variation key, and it is omitted when there is neither.

Every handler passes the capture setting it already uses for content
attributes, as does vercelEvaluate. Native graph and client graph spans carry
no raw variation, so they keep the default.
@apucacao
apucacao requested a review from a team as a code owner October 28, 2026 20:01
Move the content-capture gate for feature_flag.result.value out of the
handlers and into executeAndTrack/executeAndStream. The core now sets
variables.__ldValue only when the handler that runs has captureContent on
(ProviderHandler.captureContent, the same gate judge reasoning uses), so the
raw config never reaches a capture-off handler. Config runs, judges and graph
nodes all go through these two functions.

setLdSpanAttributes goes back to its original signature, and the handler call
sites and the API report match main again. The helper writes the raw
variation's JSON when __ldValue is present, the JSON-encoded variation key
otherwise, and nothing when there is neither.
@apucacao
apucacao requested a review from jeffdupont October 28, 2026 20:20

@jeffdupont jeffdupont left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, and it matches the updated #113 spec. Gating on handler.captureContent in executeAndTrack / executeAndStream keeps the raw config away from capture-off handlers, and since createHandlerInternal (utils.ts:74) sets that property for every handler, it is a single check. The fallback also closes the vercelEvaluate / JudgeTask gap from the first version.

  • One handler-level capture-on test. The four handler tests only assert the capture-off value (JSON.stringify('variation-a')). §1.5 asks handler tests to cover capture on too. A single test that goes through config().invoke with createXHandler({ captureContent: true }) and asserts result.value === JSON.stringify(rawVariation) would cover the wiring from start to finish.
  • Serialization question. gonfalon's flag Monitoring Traces tab groups chart series by the exact feature_flag.result.value string, matched against JSON.stringify(variation.value) (static/ld/components/feature-monitoring/hooks/useVariationChartConfig.ts:8,45-49). JS already matches with capture on. The capture-off "variation-a" fallback matches no variation, so if AI Config Monitoring uses that chart, those traces land in an unlabeled series. Do you know which UI reads result.value for AI Configs? Not blocking.

Happy to approve once the capture-on test is in.

Run config().invoke with a handler built by createHandler with captureContent
on and check that the root feature_flag event's feature_flag.result.value is
the JSON of the raw variation extractVariation returned.
@apucacao
apucacao requested a review from jeffdupont October 29, 2026 02:57

@jeffdupont jeffdupont left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bda9e7b adds the end-to-end capture-on test I asked for: it goes through config().invoke with capture on and asserts the raw variation as the value. Approving.

This matches launchdarkly/ai-sdks-monorepo#113. setLdSpanAttributes writes feature_flag.result.variant only for a non-empty variation key. It writes feature_flag.result.value as JSON.stringify of the raw variation, which is how the node-server-sdk-otel TracingHook writes it. The core sets __ldValue only when handler.captureContent is true. Otherwise the value is the JSON-encoded key, and it is left off when there is neither. The event is still written only by the shared helper on the root span. The tests cover capture on (client.test.ts), capture off and unset (tracking.test.ts, for track and stream), and the case with neither (utils.test.ts). The change is additive. extractVariation gains a return field, and no public type or API report changes, so API compatibility passes.

On parity with python-ai-sdk#240, the span output agrees, with two differences in how it gets there. First, JS gates capture in the core and Python gates it in the helper. Second, a judge run from a JudgeTask writes the raw value in Python but only the key here, because the JS JudgeTask path has no raw variation. Both stay within the spec.

….result.value

A deferred judge (buildJudgeTasks, then runJudge in a worker) wrote the
variation key as feature_flag.result.value even when the judge handler
captures content, because the task had no raw variation to pass. JudgeTask
now carries it as judgeValue, and runJudge hands it to the core as the
evaluated value, which writes it only for a capture-on handler.
…tion

captureContentFor returns a handler's own captureContent when it states one,
otherwise the process-wide default (off). The evaluated-variation gate in
executeAndTrack and executeAndStream now goes through it, so a later
process-wide setting changes one place. Behavior is unchanged.
…on the graph root

The launchdarkly.graph span wrote the variation key as feature_flag.result.value
even when content capture is on, because the raw graph variation never reached
it. buildGraph now returns the variation it fetched, and graph().stream (which
invoke() walks) passes it as __ldValue when captureContentFor(undefined) is
true. The root has no single handler, so it follows the process-wide default.
@apucacao
apucacao requested a review from jeffdupont October 29, 2026 15:47
@apucacao apucacao changed the title feat(client): add result variant and value to the feature_flag span event feat(client)!: add result variant and value to the feature_flag span event Oct 29, 2026

@jeffdupont jeffdupont left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since my approval at bda9e7b: 8398f4e puts the judge variation on JudgeTask.judgeValue, and runJudge gates it with the judge's handler. That closes the deferred-judge gap I mentioned, and the structuredClone test is a good touch. 4ad0f34 gives the launchdarkly.graph root the graph variation under the process default. de89d4a routes both through captureContentFor. The main merge had no conflict resolutions.

About the red API compatibility runs: they come from 8398f4e. JudgeTask is public, so the new field changed ai-server.api.md, and the check counts any changed declaration as breaking. The rename to feat(client)! at 16:34 made it pass, and the PR is clean now. Adding an optional field doesn't break anyone, though. If you'd rather not have a BREAKING CHANGES entry in the changelog, the api-compatible label on a title without ! is the more accurate route. Your call.

One thing to settle with #190. I merged #190 onto this branch locally. It merges with no conflicts, but leaves two captureContentFor functions. The one in content.ts keeps the hard-coded false default, and tracking.ts and graph.ts still import it. The one in sdk-options.ts follows configure(), and judges.ts imports it. Handlers built with createHandler are fine, because #190 makes captureContent a getter that always returns a boolean. A plain function handler, and the graph root, would still send only the key while the default is on. A quick vitest probe on the merged tree confirms it for the plain function, and the full suite still passes, because graph.test.ts mocks content.js. Whichever of the two merges second should delete the content.ts version, point tracking.ts and graph.ts at sdk-options.ts, and use configure() in the graph test instead of the mock. A test with a plain function handler under the built-in default would keep it fixed.

Nit: executeAndTrack and executeAndStream spread the caller's variables before adding __ldValue. A capture-off handler therefore writes a caller-supplied variables.__ldValue. Overwriting or deleting it the way __ld is handled would close that.

executeAndTrack and executeAndStream spread the caller's variables
before adding __ldValue, so a capture-off handler still received a
caller-supplied __ldValue and wrote it as feature_flag.result.value.
Both now build the handler's variables in one place that sets __ldValue
only for a capture-on handler and removes it otherwise, as __ld is
already overwritten.
#111 replaced createHandler's positional stream and captureContent
arguments with an options object, and the positional form now throws.
The two client tests this branch added still used it.
@apucacao
apucacao requested a review from jeffdupont October 29, 2026 19:55
@apucacao
apucacao merged commit 8dbe2ec into main Oct 29, 2026
12 checks passed
@apucacao
apucacao deleted the ag/ff-result-variant branch October 29, 2026 20:29
@github-actions github-actions Bot mentioned this pull request Oct 29, 2026
apucacao added a commit that referenced this pull request Oct 29, 2026
…ure() (#190)

Fixes [AIC-3703](https://launchdarkly.atlassian.net/browse/AIC-3703).
The spec change for TESTING.md §3.14 and Appendix A.8 (`createHandler`
returning a new function) is in launchdarkly/ai-sdks-monorepo#113.

BEGIN_COMMIT_OVERRIDE
feat(client)!: capture conversation content on spans by default and add
configure()

BREAKING CHANGE: conversation content is now captured on spans by
default. To keep it off, call configure({ options: { captureContent:
false } }) once at startup.

fix(client): return a new handler from createHandler instead of changing
the function passed in

BREAKING CHANGE: 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.
END_COMMIT_OVERRIDE

## Why

The cross-SDK spec (TESTING.md §3.9a) says conversation content is
captured on spans by default and that a process-wide `configure()` sets
that default, with TypeScript marked pending. The Python SDK already
works this way. This SDK did the opposite: every handler factory
defaulted `captureContent` to `false`, evaluation spans had their own
`false` default, and there was no `configure()`. This PR brings the
TypeScript SDK in line with the spec and with Python.

## What changed

- New `configure({ options })` and `SDKOptions` (`{ captureContent?:
boolean }`, built-in default `true`) exported from
`@launchdarkly/ai-server`. The options live on the lifecycle singleton,
not on `initClient`. `configure` can replace them until the first AI
invocation. Every call to a handler built with `createHandler` or a
factory counts, whether the SDK makes it or your code calls `await
handler(config, input)` directly, as does the start of a handler's
stream, an SDK call to a plain function handler, and a `vercelEvaluate`
call. After that, identical options are accepted and different ones
throw. This matches Python, where every handler call latches.
`shutdown()` restores the built-in defaults, even when no client was
ever initialized.
- `createHandler` now returns a new function that latches `configure`
and then calls the one you passed, with the same `this` and arguments,
returning its result. It keeps the original function's own properties,
so wrapping a handler again keeps its `stream` and `providers`. The
options object, `createHandler(providesFor, fn, { stream,
captureContent, providers })`, is unchanged. It no longer returns the
function it was given, and that function no longer carries
`providesFor`, so code that calls `createHandler(p, fn)` and keeps using
`fn` must use the returned handler instead. When the `captureContent`
option is omitted and the function is already a handler with an explicit
override, the new handler keeps that override; an unset handler stays
unset and keeps following `configure()`. An omitted `providers` option
likewise keeps the wrapped handler's list, and a passed one replaces it
on the new handler only.
- `handler.captureContent` on anything built with `createHandler` is now
resolved when it is read: the handler's explicit `true` or `false` if it
has one, otherwise the configured default, otherwise `true`. Assigning
to it sets the handler's override and leaves the process default alone.
- Every handler factory (claude-agents, claude-messages,
langchain-agents, langchain-messages, litellm-agents, litellm-messages,
openai-agents, openai-messages, typesafe, vercel-agents,
vercel-messages) leaves the option unset when it is omitted and resolves
it at the start of each run and stream, so a handler built before
`configure()` follows it. Convenience wrappers forward an omitted option
as unset. `vercelEvaluate` follows the same rule.
- With #186 merged, `feature_flag.result.value` also goes through this
resolver. #186 added a second `captureContentFor` in `content.ts` with a
fixed `false` default; it is deleted, and `tracking.ts` and `graph.ts`
now use the one in `sdk-options.ts`. So the graph root and plain
function handlers (not built with `createHandler`) follow `configure()`
and its built-in `true`, and write the raw variation by default.
- Judge explanations and the spans the evaluations runner writes use the
same resolver. A plain function judge or generation handler with no flag
follows the process default.
- The content helpers no longer throw when a handler passes a text part
with no text. That could only happen with capture on, so it only
mattered once capture became the default.
- Docs (root AGENTS.md, package agents.md files, client, claude-agents,
litellm-messages and vercel-messages READMEs) now describe the
resolution order instead of "off by default". The client README has a
new "Conversation content on spans" section, with migration notes for
both breaking changes. AGENTS.md and the root README no longer say
`createHandler` returns the same function. The API report gains
`configure` and `SDKOptions`, both additions already listed as joining
in `api-surface/ai-server.txt`.

## Behaviour change

If you never set `captureContent`, your spans will now include
conversation content: prompts and system instructions, model output,
tool definitions, tool arguments and results, judge explanations, and
the same fields on offline evaluation spans. The full variation is also
exported as `feature_flag.result.value`, on handler spans and on the
graph root, because #186 gates it on content capture, which now resolves
to `true` by default. Model names, token counts, timings and tool names
were already exported and are unchanged.

`createHandler` also changed: it returns a new function, and the
function you pass in is left without `providesFor`. Use the returned
handler.

## How to opt out

For the whole process, once at startup, before the first AI call:

```ts
import { configure } from '@launchdarkly/ai-server';

configure({ options: { captureContent: false } });
```

For one handler, pass `captureContent: false` to its factory, for
example `createOpenAIMessagesHandler({ captureContent: false })`. An
explicit `true` on a factory still captures when the process default is
`false`.

## Testing

- New client tests for `configure`: default on, read at run time for a
handler built earlier, replacement before the first invocation, explicit
`false` and `true` overrides in both directions, the property setter,
rejection after an invocation and acceptance of identical options,
latching on a plain function call through the SDK, on a direct `await
handler(...)` call (as soon as it starts, and when wrapped twice), on a
handler stream that is never iterated, and on a streamed SDK call,
`shutdown()` restoring the default, `captureContent` on `initClient`
being ignored with a warning, and `runJudges` writing
`gen_ai.evaluation.explanation` only when the judge's effective flag is
true.
- Re-wrapping tests: `createHandler(q, createHandler(p, fn, {
captureContent: false })).captureContent` stays `false` under a
configured `true`, a re-wrapped unset handler still follows a later
`configure()`, and `runJudges` omits the explanation for a re-wrapped
judge that kept an explicit `false`.
- The deferred `runJudge(task, handlers)` path writes
`gen_ai.evaluation.explanation` only when the judge's effective flag is
true: built-in default, explicit `false`, `configure` turning it off,
and explicit `true` over a configured `false`.
- `inspectConfig` reads a variation without latching `configure`.
- `openaiMessages` and `claudeAgents`: an explicit `captureContent:
false` beats a configured `true`, and an omitted option is forwarded as
unset, so the handler follows `configure()`.
- `createHandler` tests check that the returned handler forwards `this`
and arguments, returns the original result, latches `configure` when
called directly, keeps properties attached to the wrapped function, and
keeps or replaces `providers` on a re-wrap.
- `vercelEvaluate` tests check that a call latches `configure` (also
when the model call fails), that identical options are still accepted
afterwards, and that a call rejected for a disabled config does not
latch.
- Evaluation span tests now check that the transcript is exported under
the built-in default, removed by `configure` or an explicit `false`,
kept by an explicit `true` over a configured `false`, and that a handler
built before `configure` follows it.
- Each handler package has tests for: on by default, explicit `false`,
`configure` turning it off, a handler built before `configure`, and
explicit `true` over a configured `false`.
- `feature_flag.result.value`: a plain function handler under the
built-in default gets the raw variation through `executeAndTrack` and
through `config().invoke`, and gets the variation key once `configure`
turns capture off. The graph root writes the raw graph variation under
the built-in default and under a configured `true`, and the variation
key under a configured `false`. The graph suite drives this with
`configure()` and `resetSdkOptions()` instead of mocking the resolver.
These tests fail against the old fixed `false`. #186's tests, including
the caller-supplied `__ldValue` one, still pass.
- `yarn test`, `yarn typecheck`, `yarn code:check`, `yarn lint:pkg`,
`yarn api:update` (no report change), `yarn api:check`, `yarn
api:surface`, `yarn test:api-surface`, `yarn test:api-breaking`, `yarn
check:packaging` and `scripts/api-breaking.mjs pr` all pass locally.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Overview**
> Aligns the TypeScript SDK with the cross-SDK spec and Python:
**conversation content on OpenTelemetry spans is on by default**, and
integrators can set process-wide defaults with new **`configure({
options: { captureContent } })`**.
> 
> **Content capture** no longer defaults to `false` on every handler
factory. Unset `captureContent` follows `configure()` (built-in
**`true`**), resolved **when each run starts** so handlers created
before `configure()` still pick up the default. Per-handler
`captureContent: false` (or convenience-wrapper overrides) still wins.
The same gate applies to judge **`gen_ai.evaluation.explanation`**,
offline evaluation runner spans, and **`feature_flag.result.value`**
(full variation when capture is on, variation key when off).
**`shutdown()`** restores built-in defaults and clears the
post-invocation lock on `configure`.
> 
> **`createHandler`** now returns a **new wrapper** that carries
`providesFor`, latches the first AI call, and forwards to the function
you pass in—the original function is **not** mutated and must not be
used for routing. Handler **`captureContent`** is a runtime getter with
an assignable override.
> 
> Docs, examples, and tests across the client and all provider packages
were updated for the new defaults and resolution order.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
ede7625. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->




[AIC-3703]:
https://launchdarkly.atlassian.net/browse/AIC-3703?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants