Repository navigation
feat(client): add configure for SDK defaults - #158
Conversation
Add a process-wide configure(options=SDKOptions) API on the lifecycle singleton. Handlers read capture_content when they run, and an explicit True or False overrides the configured default.
|
bugbot run |
jeffdupont
left a comment
There was a problem hiding this comment.
Reviewed at 6be2bb0. All 2402 tests pass; the branch merges cleanly with current main and client tests still pass after the merge. The code itself is correct: all 12 effective_capture_content(content_override) call sites resolve in both blocking and stream paths, and the lock fires through ProviderHandler.__call__/.stream (tracking.py:201, :303, :315).
Must fix (spec and process gates, not code bugs)
1. configure and SDKOptions aren't on the approved API list (launchdarkly_ai_server/__init__.py)
api-surface/python/launchdarkly_ai_server.txt on monorepo main has neither (they'd sit next to config/ConfigInstance). TESTING.md §5.2 (around line 2226) says the monorepo + Name PR comes first and the SDK PR can't merge before its sync PR. CI passes today only because this repo has no api-surface/ copy yet; after the first sync it fails on both names for every open PR.
Fix: monorepo PR adding + configure / + SDKOptions (and the TS names), then merge this after the sync.
2. No spec and no TypeScript counterpart for a PII setting (monorepo TESTING.md §1.1 ~line 197, §3.14 ~line 1340)
TESTING.md doesn't mention configure/SDKOptions. §3.14 still says Python defaults capture_content=False; this PR changes it to None. §1.1 says an omitted option attaches false, which is only true while nothing is configured. No js-ai-sdk PR exists (searched configure, SDKOptions, captureContent, "process-wide").
Fix: spec PR covering precedence, the lock after first call and the shutdown() reset, updating §1.1/§3.14; then the JS PR.
3. ProviderHandler.capture_content is no longer assignable (types.py:177-182)
It's now a read-only property: h.capture_content = False raises AttributeError: property 'capture_content' of 'ProviderHandler' object has no setter. That's breaking for any caller that sets it, but the title is feat:, so release-please won't note it. (Nothing in this repo assigns it; external callers unverified.)
Fix: a 3-line @capture_content.setter writing _capture_content_override, or retitle as feat!:.
Should fix
4. The "configure turns content on" path is end-to-end tested for 1 of 6 handlers
Only openai-agents runs a handler under configure(True) and checks the span. The other five only check the wrapper forwards None, so e.g. passing content_override (falsy None) instead of the resolved bool in claude-messages _stream_impl would silently keep content off with no failing test. test_judge_gate_uses_the_effective_bool copies the expression from judges.py instead of calling run_judges, so it passes even if judges.py:188/:455 change.
Fix: one configure(True) + no-arg handler test per remaining package; replace the judge test with one that runs run_judges and asserts gen_ai.evaluation.explanation is set (pattern: test_conversation.py:216).
5. Provider packages crash on import with an older client (packages/*/pyproject.toml, e.g. openai-agents/pyproject.toml:6)
They depend on unpinned launchdarkly-ai-server and now import effective_capture_content. Upgrading only a provider package against client 0.3.0 gives ImportError. Pre-existing gap, but this PR hits all 6.
Fix: launchdarkly-ai-server>=<next client version> in the 6 provider pyprojects.
Nice to have
- Root
README.md:522and line 5 of the six provideragents.mdfiles still say content is off "unless the caller passescapture_content=True". Add one sentence linking to the client README "Process-wide defaults" section.
Verdict: fix 1, 2 and 3 first.
Assigning ProviderHandler.capture_content sets that handler's override. Streaming tests cover the process default on the other five handlers, run_judges checks the judge explanation, and the provider packages require launchdarkly-ai-server>=0.3.1.
|
@jeffdupont, thanks. The concrete items are in 3. 4. Each of the other five handlers has a streaming test that calls 5. The six provider packages now depend on 6. The root README and the six provider 1 and 2 are not changed here. They are monorepo gates, not behavior to invent on this branch:
Local result: 2409 passed, 11 skipped. ruff, format, and mypy are clean. |
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit dab2c44. Configure here.
|
@jeffdupont, the two remaining gates are in 1. 2. TESTING.md §3.9a specifies The js-ai-sdk Bugbot on |
<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
## Summary
Adds `launchdarkly-ai-opencode-agents`, a normal handler package with
`provides_for` `('*', 'agent')`. It reduces caller-supplied OpenCode
session events and does not depend on an OpenCode SDK.
`config().stream()` yields `chunk` then `done`. Content capture uses the
tri-state read from #158.
## Tests
`pytest` (2416 passed, 11 skipped), `ruff check`, `ruff format --check`,
and `mypy packages/*/src` were clean.
<!-- CURSOR_AGENT_PR_BODY_END -->
<div><a
href="https://cursor.com/agents/bc-f7e9aee4-24b4-5241-bfd4-dee3c9376b3f?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-web-light.png"><img
alt="Open in Web" width="114" height="28"
src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a
href="https://cursor.com/background-agent?bcId=bc-f7e9aee4-24b4-5241-bfd4-dee3c9376b3f&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img
alt="Open in Cursor" width="131" height="28"
src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
…ic API (#196) **Stacked on #195** (the API reports). ## Why The API reports make every public change visible, but they don't stop a breaking change from shipping in a minor or patch release. After 1.0 a breaking change has to be deliberate: declared, and released as a major version. ## What `scripts/api_breaking.py` uses griffe to compare the names in each public module's `__all__` with another git ref. The new **API compatibility** workflow runs it on every PR, and re-runs it when the title or labels change. - **On a PR (`pr`):** compares with the PR's base. A breaking change fails unless one of these is true: - the PR title declares it with `!` (`feat!: ...`, `fix(client)!: ...`), which makes release-please cut a major version (a minor one before 1.0.0); - an owner applied the **`api-compatible`** label because they judge the flagged change compatible. - **On the release PR (`release`):** for each package whose version changes in `.release-please-manifest.json`, compares the code with that package's last release tag (`<component>-<version>`). It fails on a breaking change unless the new version is a major one (a minor one before 1.0.0), or an owner applied `api-compatible` to the release PR. A package with no release tag yet is reported and skipped. - **What counts as breaking:** - from griffe: removed names, removed or moved parameters, new required parameters, changed defaults, changed attribute values; - added by the script: names dropped from `__all__`, and changed return types, which griffe doesn't flag. - **Experimental modules are skipped,** because an experimental feature may change in a minor release. ## Tested - **`tests/test_api_breaking.py` (23 tests)** uses a throwaway git repo with a tagged release. It covers: - each breaking kind, and additive changes that pass; - experimental modules being skipped; - the title and label rules; - release versions before and after 1.0, including 0.9.0 → 1.0.0; - a package with no release tag; - `api-compatible` passing an undeclared breaking release, and another label not passing it. The fixture isolates git from the developer's global config. - **Full suite:** `uv run pytest` gives 2579 passed, 14 skipped. `ruff check` and `ruff format --check` are clean, and `actionlint` passes on the workflow. - **Against real history:** - `pr` against `main`: no breaking changes, so it passes. - `pr` against `launchdarkly-ai-server-0.3.0`: 23 breaking changes. That's 14 names that moved to `experimental`, plus `capture_content`'s default changing from `False` to `None` in every provider factory, `create_handler` and `ProviderHandler`. It exits 1 with a plain title, and 0 with `!` or the label. ## The open release PR #166 Running `release` against #166 flags **6 provider packages**: - claude-agents, langchain-agents, openai-agents: 0.2.5 → 0.2.6 - claude-messages, langchain-messages, openai-messages: 0.2.4 → 0.2.5 Each changed `capture_content`'s default from `False` to `None` (#158) since its last release, but is getting only a patch bump. `client` (0.3.0 → 0.4.0) passes. If `None` means "use the default from `configure()`", callers see no difference. So the **`api-compatible` label now also passes the release check** (06050f1): an owner can judge a release PR compatible, the same way as any other PR. Verified on #166: it exits 1 without the label and 0 with it. The label now exists in this repo. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Overview** > Adds **CI enforcement** so undeclared public API breaks cannot ship in patch/minor releases. > > A new **API compatibility** GitHub Actions workflow runs on every PR (including title/label edits). For normal PRs it runs `scripts.api_breaking pr`, comparing stable `__all__` exports against the merge base via griffe (plus extra checks for `__all__` drops and return-type changes). CI fails unless the title uses conventional-commit `!` or an owner applies **`api-compatible`**. On **release-please** branches it runs `release`, comparing each bumped package to its last release tag and requiring a major (or pre-1.0 minor) bump when breaking changes slipped through. > > `api_report.resolve` is refactored to take a `ModulesCollection` so the breaking checker can reuse the same resolution logic. **`tests/test_api_breaking.py`** covers detection rules, title/label gates, semver alignment, experimental skips, and removed packages. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit a61cf9e. 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>
🤖 I have created a release *beep* *boop* --- <details><summary>launchdarkly-ai-server: 0.4.0</summary> ## [0.4.0](launchdarkly-ai-server-0.3.0...launchdarkly-ai-server-0.4.0) (2026-10-23) ### ⚠ BREAKING CHANGES * **evaluations:** keep the management API client internal ([#202](#202)) * **evaluations:** rename base uri to match spec ([#199](#199)) * **evaluations:** add test proving harness edge case matches spec ([#200](#200)) * **evaluations:** route handlers by provider and mode * **evaluations:** mark evaluations from code as experimental ([#157](#157)) ### Experimental * **evaluations:** add test proving harness edge case matches spec ([#200](#200)) ([fa7162e](fa7162e)) * **evaluations:** keep the management API client internal ([#202](#202)) ([17270ed](17270ed)) * **evaluations:** mark evaluations from code as experimental ([#157](#157)) ([1389765](1389765)) * **evaluations:** rename base uri to match spec ([#199](#199)) ([7d93f9c](7d93f9c)) * **evaluations:** route handlers by provider and mode ([f62591f](f62591f)) * **evaluations:** Set JSON as standard for output to pass to judge ([#203](#203)) ([ffc728b](ffc728b)) * **skills:** Correct the HTTP 403 advice now that server-side FDv2 is available to every account ([#178](#178)) ([b17e322](b17e322)) ### Features * **agui:** add a shared reader for handler streams ([#164](#164)) ([fc08dd5](fc08dd5)) * **AIC-3534:** add a TypeSafe Jev judge handler ([#149](#149)) ([1b41ea4](1b41ea4)) * **client:** add configure for SDK defaults ([#158](#158)) ([3736767](3736767)) * **langchain:** emit AG-UI text and tool events ([#163](#163)) ([ab5e9e1](ab5e9e1)) * **openai:** emit AG-UI text and tool events ([#161](#161)) ([2b1b2d4](2b1b2d4)) ### Bug Fixes * **AIC-3570:** re-resolve graph flags on every invoke and stream ([#187](#187)) ([8fdc4d4](8fdc4d4)) * **AIC-3571:** visit every child before its parent in reverse_traverse ([#189](#189)) ([87c73d9](87c73d9)) * **AIC-3572:** treat a variation without _ldMeta.enabled as disabled ([#184](#184)) ([7a0d260](7a0d260)) * **AIC-3574:** build the LaunchDarkly client off the event loop ([#185](#185)) ([bf03b6c](bf03b6c)) * **AIC-3576:** record deferred judge scores under the evaluated config ([#190](#190)) ([354d3f1](354d3f1)) * **AIC-3577:** send object tool results to the model as JSON ([#188](#188)) ([dead4bd](dead4bd)) * **AIC-3596:** name the [otel] extra in the missing-OTel warning ([#186](#186)) ([90ce7bf](90ce7bf)) * **client:** match a handler's provides_for after normalizing its mode ([f62591f](f62591f)) * give native graph spans their LaunchDarkly identity ([#121](#121)) ([e863b6d](e863b6d)) </details> <details><summary>launchdarkly-ai-python: 0.3.0</summary> ## [0.3.0](launchdarkly-ai-python-0.2.0...launchdarkly-ai-python-0.3.0) (2026-10-23) ### ⚠ BREAKING CHANGES * **evaluations:** mark evaluations from code as experimental ([#157](#157)) ### Experimental * **evaluations:** mark evaluations from code as experimental ([#157](#157)) ([1389765](1389765)) </details> <details><summary>launchdarkly-ai-claude-agents: 0.2.6</summary> ## [0.2.6](launchdarkly-ai-claude-agents-0.2.5...launchdarkly-ai-claude-agents-0.2.6) (2026-10-23) ### Features * **claude:** emit AG-UI text and tool events ([#165](#165)) ([3f3a0ce](3f3a0ce)) * **client:** add configure for SDK defaults ([#158](#158)) ([3736767](3736767)) ### Bug Fixes * **AIC-3571:** visit every child before its parent in reverse_traverse ([#189](#189)) ([87c73d9](87c73d9)) * **AIC-3577:** send object tool results to the model as JSON ([#188](#188)) ([dead4bd](dead4bd)) * **claude-agents:** fail the run when the CLI reports an API error as success ([#174](#174)) ([3d1a768](3d1a768)) * give native graph spans their LaunchDarkly identity ([#121](#121)) ([e863b6d](e863b6d)) </details> <details><summary>launchdarkly-ai-claude-messages: 0.2.5</summary> ## [0.2.5](launchdarkly-ai-claude-messages-0.2.4...launchdarkly-ai-claude-messages-0.2.5) (2026-10-23) ### Features * **claude:** emit AG-UI text and tool events ([#165](#165)) ([3f3a0ce](3f3a0ce)) * **client:** add configure for SDK defaults ([#158](#158)) ([3736767](3736767)) ### Bug Fixes * **AIC-3577:** send object tool results to the model as JSON ([#188](#188)) ([dead4bd](dead4bd)) </details> <details><summary>launchdarkly-ai-openai-agents: 0.2.6</summary> ## [0.2.6](launchdarkly-ai-openai-agents-0.2.5...launchdarkly-ai-openai-agents-0.2.6) (2026-10-23) ### Features * **client:** add configure for SDK defaults ([#158](#158)) ([3736767](3736767)) * **openai:** emit AG-UI text and tool events ([#161](#161)) ([2b1b2d4](2b1b2d4)) ### Bug Fixes * **AIC-3571:** visit every child before its parent in reverse_traverse ([#189](#189)) ([87c73d9](87c73d9)) * **AIC-3577:** send object tool results to the model as JSON ([#188](#188)) ([dead4bd](dead4bd)) * give native graph spans their LaunchDarkly identity ([#121](#121)) ([e863b6d](e863b6d)) </details> <details><summary>launchdarkly-ai-vercel-agents: 0.1.1</summary> ## [0.1.1](launchdarkly-ai-vercel-agents-0.1.0...launchdarkly-ai-vercel-agents-0.1.1) (2026-10-23) ### Features * **AIC-3408:** add Vercel AI SDK messages and agents adapters ([#103](#103)) ([64d679e](64d679e)) </details> <details><summary>launchdarkly-ai-litellm-agents: 0.1.1</summary> ## [0.1.1](launchdarkly-ai-litellm-agents-0.1.0...launchdarkly-ai-litellm-agents-0.1.1) (2026-10-23) ### Features * add LiteLLM messages and agents integrations ([#102](#102)) ([f2e9886](f2e9886)) </details> <details><summary>launchdarkly-ai-openai-messages: 0.2.5</summary> ## [0.2.5](launchdarkly-ai-openai-messages-0.2.4...launchdarkly-ai-openai-messages-0.2.5) (2026-10-23) ### Features * **client:** add configure for SDK defaults ([#158](#158)) ([3736767](3736767)) * **openai:** emit AG-UI text and tool events ([#161](#161)) ([2b1b2d4](2b1b2d4)) ### Bug Fixes * **AIC-3577:** send object tool results to the model as JSON ([#188](#188)) ([dead4bd](dead4bd)) </details> <details><summary>launchdarkly-ai-vercel-messages: 0.1.1</summary> ## [0.1.1](launchdarkly-ai-vercel-messages-0.1.0...launchdarkly-ai-vercel-messages-0.1.1) (2026-10-23) ### Features * **AIC-3408:** add Vercel AI SDK messages and agents adapters ([#103](#103)) ([64d679e](64d679e)) </details> <details><summary>launchdarkly-ai-litellm-messages: 0.1.1</summary> ## [0.1.1](launchdarkly-ai-litellm-messages-0.1.0...launchdarkly-ai-litellm-messages-0.1.1) (2026-10-23) ### Features * add LiteLLM messages and agents integrations ([#102](#102)) ([f2e9886](f2e9886)) </details> <details><summary>launchdarkly-ai-langchain-agents: 0.2.6</summary> ## [0.2.6](launchdarkly-ai-langchain-agents-0.2.5...launchdarkly-ai-langchain-agents-0.2.6) (2026-10-23) ### Features * **client:** add configure for SDK defaults ([#158](#158)) ([3736767](3736767)) * **langchain:** emit AG-UI text and tool events ([#163](#163)) ([ab5e9e1](ab5e9e1)) ### Bug Fixes * **AIC-3577:** send object tool results to the model as JSON ([#188](#188)) ([dead4bd](dead4bd)) * give native graph spans their LaunchDarkly identity ([#121](#121)) ([e863b6d](e863b6d)) </details> <details><summary>launchdarkly-ai-langchain-messages: 0.2.5</summary> ## [0.2.5](launchdarkly-ai-langchain-messages-0.2.4...launchdarkly-ai-langchain-messages-0.2.5) (2026-10-23) ### Features * **client:** add configure for SDK defaults ([#158](#158)) ([3736767](3736767)) * **langchain:** emit AG-UI text and tool events ([#163](#163)) ([ab5e9e1](ab5e9e1)) ### Bug Fixes * **AIC-3577:** send object tool results to the model as JSON ([#188](#188)) ([dead4bd](dead4bd)) </details> <details><summary>launchdarkly-ai-opencode-agents: 0.2.0</summary> ## [0.2.0](launchdarkly-ai-opencode-agents-0.1.0...launchdarkly-ai-opencode-agents-0.2.0) (2026-10-23) ### ⚠ BREAKING CHANGES * **opencode:** start and stop opencode serve in the handler ([#198](#198)) ### Features * **opencode-agents:** add an OpenCode handler ([#159](#159)) ([fc08e8c](fc08e8c)) * **opencode-agents:** emit the AG-UI text and tool subset ([#162](#162)) ([7677621](7677621)) * **opencode-agents:** open one OpenCode session per call ([#179](#179)) ([b225113](b225113)) * **opencode-agents:** render agent instruction templates ([#182](#182)) ([83175aa](83175aa)) * **opencode:** start and stop opencode serve in the handler ([#198](#198)) ([0748dcf](0748dcf)) ### Bug Fixes * **opencode-agents:** own the HTTP client ([#194](#194)) ([ed2d419](ed2d419)) * **opencode-agents:** skip judges and record structured history ([#177](#177)) ([6fadf1d](6fadf1d)) </details> <details><summary>launchdarkly-ai-typesafe: 0.1.1</summary> ## [0.1.1](launchdarkly-ai-typesafe-0.1.0...launchdarkly-ai-typesafe-0.1.1) (2026-10-23) ### Features * **AIC-3534:** add a TypeSafe Jev judge handler ([#149](#149)) ([1b41ea4](1b41ea4)) </details> --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
PyPI's latest launchdarkly-ai-server is 0.4.0, which has no parameter forwarding, so the handlers need a later release. >=0.4.1 holds whichever version release-please cuts next, the same convention as the >=0.3.1 bound added by #158. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@andrewklatzke @jeffdupont Here's the PR that gives users a process-level path to control whether the SDK includes content in o11y. They may want to continue doing it per-handler, but that could be error-prone if they're not careful.
Adds a process-wide
configure(options=SDKOptions)API for SDK behavior defaults. Handlers readcapture_contentwhen they run, and an explicitTrueorFalseon a factory or convenience wrapper overrides the configured default.init_clientstays the LaunchDarkly client initializer.SDKOptionsis the extension point for later process-wide defaults.Note
Overview
Adds
configure(options=SDKOptions(...))on the core client so apps can set process-wide behavior defaults separately frominit_client. The first field iscapture_content(still off by default);shutdown()resets options to built-ins.Handler factories and convenience wrappers now take
capture_content: bool | None = None.Noneresolves at invocation viaeffective_capture_content, so a handler built before startup can still followconfigure. ExplicitTrue/Falseoverrides the process default (includingFalseover a configuredTrue).ProviderHandler.capture_contentis a property that returns that effective bool (used for judge explanations too). Options are locked after the first__call__/streamunlessconfigureis called again with an identicalSDKOptions.All Tier 1 handler packages adopt the same pattern, bump
launchdarkly-ai-server>=0.3.1, and add tests for streaming/process defaults. Docs (TELEMETRY-CONTRACT, README,AGENTS.md) describe the new flow.Reviewed by Cursor Bugbot for commit dab2c44. Bugbot is set up for automated code reviews on this repo. Configure here.