Skip to content

fix!: pass AI Config model parameters through to every provider handler - #107

Merged
jeffdupont merged 63 commits into
mainfrom
alexis/forward-model-parameters
Oct 23, 2026
Merged

jeffdupont merged 63 commits into
mainfrom
alexis/forward-model-parameters

Conversation

@apucacao

@apucacao apucacao commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Every time I ask "how would I use the turnkey SDK here?", I hear about how model params aren't currently being passed to models. This PR fixes that.

Not entirely sure about the implementation though: we ended up definiting the list of params supported by each of the underlying SDKs, because I couldn't find a better export from those packages, and iterating on types felt harder than the explicit list. The downside is that if/when things change, we'd need to update the code. But presumably we already need to do that for our model parameter schemas in Gonfalon.


Model parameters set in an AI Config were silently ignored by most handlers. Every handler now passes them through to the provider.

  • Each handler forwards exactly the list for that handler in the cross-SDK spec (ai-sdks-monorepo feat: emit gen_ai.conversation.id #42, TESTING.md 1.12), the same lists the JS SDK uses. Any other key is dropped. A test per handler sets one key at a time and checks the forwarded set equals the spec list.
  • Never forwarded, by any handler: credentials, endpoints and connection settings (base URL, region, headers, timeouts, retries, HTTP clients), raw request bags (extra_*, model_kwargs), remote MCP servers, the Claude Agent SDK's host-process settings (cli_path, env, cwd, add_dirs, permission_mode, sessions, hooks, callbacks), and keys that set data location or retention, server-side state, identity, runtime wiring or API shape, extra prompt content, or safety configuration (for example store, truncation, metadata, user, include, system, guardrails).
  • invoke and stream forward the same keys, and so do each handler and its native graph.
  • A key the provider expects as an object (thinking, reasoning, text, ...) is dropped when the config sets it to anything else.
  • A nested value is rebuilt to the shape the provider SDK declares, and dropped when it is malformed: thinking needs a known type (and budget_tokens for enabled), Anthropic tool_choice and cache_control need a known type, OpenAI reasoning keeps effort and summary, OpenAI Agents tool_choice is a string only, and Anthropic output_config keeps only effort.
  • Structured output stays with the handler's outputFormat: output_config.format and Claude Agents output_format are not forwarded, so a config cannot ask the provider to enforce a second schema.
  • A test finds every handler module under packages/ and fails if one has no forwarded-keys list, so a new handler package must declare one.
  • Configs use snake_case (max_turns, top_p), the providers' own names and what the UI writes.
  • Every Python provider SDK takes snake_case, so all handlers pass keys through as-is.
  • The TypeScript SDK converts to camelCase for its agent and LangChain handlers only.

Release

This is a breaking change. A setting in an AI Config's model.parameters that every handler used to ignore now reaches the provider, so a config that already sets, say, temperature behaves differently after upgrading. The API check also flags two removed handler-module names (OWNED_PARAMETERS in the Vercel handlers) and a new required argument on OpenCode session_events. Before 1.0 this ships as a minor release.

BREAKING CHANGE: handlers now forward the AI Config's model parameters to the provider, limited to each handler's list in TESTING.md 1.12. Configs that set parameters which used to be ignored now take effect.

🤖 Generated with Claude Code


Note

Overview
Breaking: AI Config model.parameters now reach provider SDKs instead of being ignored. Handlers filter through per-provider allowlists (cross-SDK TESTING.md §1.12), shared model_parameters() / select_forwarded_parameters() in launchdarkly-ai-server, and rebuild or drop nested shapes (thinking, Anthropic tool_choice/output_config, LiteLLM reasoning, etc.).

Security: Credentials, endpoints, request-injection bags (extra_*, model_kwargs), Claude Agent host settings (cli_path, env, permission_mode, …), and similar keys are explicitly excluded; drift and probe tests lock the classification to each SDK’s fields.

Coverage: Claude Agents/Messages, LangChain Agents/Messages (OpenAI/Anthropic/Bedrock), and LiteLLM Agents—including native graph paths—merge filtered params into ClaudeAgentOptions, Messages API calls, LangChain constructors, and ModelSettings. Package deps bump to launchdarkly-ai-server>=0.4.1.

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

model.parameters is a free-form dict the LaunchDarkly UI writes for
provider tuning values, including max_turns for agent turn caps. Every
Python handler except claude-messages ignored it entirely, and
claude-messages read only max_tokens. In particular there was no way
to cap an agent's turns: claude-agents never set
ClaudeAgentOptions.max_turns and openai-agents never passed max_turns
to Runner.run.

Adds a shared launchdarkly_ai_server.model_parameters(config) helper
that returns model.parameters as a fresh dict (or {} when absent/not
a mapping), and never reads model.custom. Applies it at every provider
call site across all six handler packages, including streaming and
native-graph paths. Keys are forwarded as-is (snake_case, matching
both the UI and every Python provider SDK here); no allowlisting and
no case conversion.

Handler-owned keys (model, messages/input, system/instructions, tools,
etc., decided per call site) are always removed from the forwarded
dict before merging with the handler's own kwargs, so a config value
can never override what the handler itself sets. claude-messages keeps
its max_tokens-defaults-to-1024 behaviour exactly.

LangChain's handlers already forwarded model.parameters through a
local _model_constructor_kwargs helper; swapped that to use the new
shared helper for consistency.

Verified: uv run pytest (1332 passed, 11 skipped), uv run mypy
packages/*/src, uv run ruff check ., uv run ruff format --check .

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@apucacao apucacao changed the title fix(client): forward model.parameters to every provider handler fix: pass AI Config model parameters through to every provider handler Sep 24, 2026
@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread packages/claude-agents/src/launchdarkly_ai_claude_agents/handler.py
…ccepts

model.parameters is a free-form dict the LaunchDarkly UI writes, offering
keys that make sense across providers but that no single provider SDK
accepts in full. Forwarding it unfiltered (added in a prior commit on this
branch) raised TypeError before any request for several real configs:
ClaudeAgentOptions has no temperature/top_p/top_k/max_tokens/
stop_sequences/tool_choice/metadata; the OpenAI Responses API has no
max_tokens/frequency_penalty/presence_penalty/seed/n/stop/response_format/
logit_bias/logprobs/max_completion_tokens/audio/modalities/prediction;
Anthropic's Messages API has no top-level effort.

Adds launchdarkly_ai_server.parameter_forwarding, a shared filter used by
every handler: accept-sets are derived once from the live provider type
(inspect.signature for plain methods, dataclasses.fields for
ClaudeAgentOptions/ModelSettings, pydantic model_fields + aliases for the
LangChain chat models), never hand-listed, so an SDK's own drift is picked
up automatically. transport/escape-hatch keys (extra_headers, extra_query,
extra_body, timeout, extra_*) are stripped unconditionally regardless of
what a signature accepts, and each call site may additionally exclude keys
the API accepts but that would break the handler because the handler
itself already decides that behaviour: stream/stream_options/background/
conversation/prompt for the OpenAI Responses API, stream for Anthropic
Messages. Everything else the provider accepts is forwarded, including
keys that are not strictly generation settings (store, user,
safety_identifier, prompt_cache_key, prompt_cache_retention, include,
context_management, metadata, service_tier, instructions, moderation).

Two renames, applied before the accept filter: openai-messages maps
max_tokens/max_completion_tokens to the Responses API's max_output_tokens
(explicit max_output_tokens wins, then max_completion_tokens, then
max_tokens); claude-messages maps a top-level effort into
output_config.effort, unless the config already sets its own
output_config.effort, which wins.

Applied at every call site across all six handlers, including streaming
and native-graph paths. claude-agents/openai-agents dataclass accept-sets
are computed once at module load from the real SDK types, independent of
each package's per-call importlib.import_module mocking in tests.
LangChain's per-provider pydantic accept-sets are cached per class with
functools.cache, since the constructor class is only known once a provider
branch resolves langchain_openai/_anthropic/_aws.

Updated the existing tests whose old assertions relied on unfiltered
pass-through of a key the real SDK does not accept (tools forwarded to
ChatOpenAI's constructor in two langchain-agents/messages tests) to give
the mocked provider class an accurate model_fields shape and to reflect
the value now being dropped.

Verified: uv run pytest (1383 passed, 11 skipped), uv run mypy
packages/*/src, uv run ruff check ., uv run ruff format --check ..
uv.lock unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

…warded-key lists

Every handler decided which model.parameters keys to forward by introspecting the
live provider SDK at runtime: inspect.signature for the two Anthropic/OpenAI methods,
dataclasses.fields for ClaudeAgentOptions/ModelSettings, pydantic model_fields plus
aliases for the three LangChain chat models. Every competitor SDK (LiteLLM, Vercel AI
SDK, Braintrust, LangChain) and this SDK's own TypeScript port use a written-down list
instead, so replaces the derivation with one: each handler now carries a literal
frozenset of the keys it forwards, generated once from the same introspection and
pasted in as a reviewable list, next to its existing handler-owned and excluded sets.

Deletes accepted_parameter_keys_from_signature/_from_dataclass/_from_pydantic_model,
_alias_strings, is_transport_parameter, strip_transport_parameters, and
filter_forwardable_parameters from packages/client's parameter_forwarding module now
that nothing calls them. In their place, select_forwarded_parameters(params, keys) is
the one shared piece left: keep the params keys that are on a handler's own forwarded
list, drop everything else.

Reproduces every handler's existing forwarded/owned/excluded classification exactly,
with one deliberate addition applied consistently: client/connection configuration
(API keys, base URLs, organization ids, HTTP clients, default headers/query, proxies,
timeouts, retry counts) is now always excluded, even when a provider's own accept-set
would otherwise let it through. This newly excludes, per handler:

- langchain-messages / langchain-agents (ChatOpenAI): api_key, openai_api_key,
  base_url, openai_api_base, organization, openai_organization, openai_proxy, client,
  async_client, root_client, root_async_client, http_client, http_async_client,
  http_socket_options, default_headers, default_query, max_retries, request_timeout
- langchain-messages / langchain-agents (ChatAnthropic): anthropic_api_key, api_key,
  anthropic_api_url, base_url, anthropic_proxy, default_request_timeout, max_retries,
  default_headers
- langchain-messages / langchain-agents (ChatBedrockConverse, opt-in dependency):
  bedrock_api_key, api_key, aws_access_key_id, aws_secret_access_key,
  aws_session_token, credentials_profile_name, endpoint_url, base_url, client,
  bedrock_client, config, default_headers, max_retries
- openai-agents (ModelSettings): retry

timeout/extra_headers/extra_query/extra_body/extra_args were already excluded by the
prior transport-prefix rule on every handler that has them; they are now named
explicitly in each handler's own excluded set instead of matched by a shared prefix
check, since nothing here is derived at runtime any more.

Adds one drift test per handler package (test_parameter_forwarding.py) that reads the
real provider SDK's signature/dataclass/model_fields and asserts every parameter it
accepts is classified in exactly one of forwarded, handler-owned, or excluded, so an
SDK addition nobody has classified fails loudly by name, and so does a stale list
entry. The LangChain ChatBedrockConverse test skips itself when langchain-aws (an
opt-in dependency) is not installed, matching this package's existing Bedrock test
pattern.

Updates langchain-messages' TestModelParametersReachTheWire test, which relied on
forwarding http_async_client/api_key through model.parameters to mock the outgoing
HTTP call: it now intercepts ChatOpenAI's own default httpx client builder instead, so
the mock transport is reached without any config value ever naming an HTTP client or a
key. Adds TestConnectionConfigIsNeverForwarded to both langchain-messages and
langchain-agents, asserting api_key/base_url from model.parameters never reach the
ChatOpenAI/ChatAnthropic constructor kwargs.

Verified: uv run --frozen pytest (1385 passed, 15 skipped), uv run --frozen mypy
packages/*/src, uv run --frozen ruff check ., uv run --frozen ruff format --check .,
uv.lock unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread packages/langchain-agents/src/launchdarkly_ai_langchain_agents/handler.py Outdated
…eters

They are the same constructor field as `model`, under its field name or its
alias. The handler always sets `model` itself, so a config value for either
collided with the resolved model name or overrode it. Classify both as
handler-owned per model class, and have the drift tests read that set from
the handler instead of a test-local copy.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

@apucacao
apucacao marked this pull request as ready for review September 25, 2026 14:48

@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.

Reviewed against the 1.0 GA plan. The fix is needed: parameters set in an AI Config were being ignored, and that should be fixed before 1.0. Tests pass at d51199d (make test: 1391 passed, 15 skipped, exit 0). The branch merges cleanly onto current main (fee904a), and the merged tree passes too: 1491 passed, make typecheck clean.

Four things I think need settling before merge, because each one changes the public interface and can't be changed after 1.0:

  1. Claude Agents forwards host-process settings, not just model settings. A config can set cli_path, env, cwd, add_dirs, permission_mode, settings, setting_sources, plugins and sandbox. I reproduced it: a config with cli_path: /tmp/attacker-binary makes the SDK launch that file as its agent process (claude_agent_sdk/_internal/transport/subprocess_cli.py:225), and env: {ANTHROPIC_BASE_URL: ...} points the customer's API key at another host (:434). That turns "can edit an AI Config" into "can run code on the customer's server." Inline comment below.
  2. Two LangChain keys get around the exclusion list. model_kwargs carries extra_headers / extra_query into the ChatOpenAI request payload (reproduced with _get_request_payload), though both are listed as excluded. mcp_servers (ChatAnthropic) would let a config attach a remote MCP server; I haven't tested that one.
  3. Two new public names. model_parameters and select_forwarded_parameters are in the root __all__, but only the provider packages use them. Same point as on #121 with make_graph_track_data: they belong in the internal group, or the 1.0 surface trim has to remove them again.
  4. Python and JS don't match (js #73). The two messages handlers agree. In the agent and LangChain handlers, JS forwards the whole camelCased bag with no allowlist, so api_key becomes apiKey, which ChatOpenAI and ChatAnthropic use. I'll leave a note there too. The monorepo has no spec for forwarding these parameters, and open monorepo #10 says "do not rename or filter parameter keys," which contradicts both PRs. One spec in TESTING.md would settle it for both languages.

Smaller points, not blocking:

  • output_format (Claude Messages) and response_id / starting_after / text_format (OpenAI) are forwarded on stream but not invoke, so one config can behave differently depending on the call.
  • OpenAI Responses forwards reasoning but not reasoning_effort. If the UI writes reasoning_effort, it's dropped silently, as effort was before your rename. I haven't checked what the UI writes.
  • The loops that remove handler-owned keys never find anything, since those keys aren't on the forwarded lists. They're harmless, just dead code.
  • In native graphs only the root node's max_turns is used.

Removing _HAS_ANTHROPIC is fine; nothing on main reads it.

Comment thread packages/claude-agents/src/launchdarkly_ai_claude_agents/handler.py
Comment thread packages/langchain-agents/src/launchdarkly_ai_langchain_agents/handler.py Outdated
Comment thread packages/langchain-agents/src/launchdarkly_ai_langchain_agents/handler.py Outdated
Comment thread packages/client/src/launchdarkly_ai_server/__init__.py Outdated
Comment thread packages/claude-messages/src/launchdarkly_ai_claude_messages/handler.py Outdated
@apucacao

apucacao commented Oct 2, 2026

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread packages/claude-agents/src/launchdarkly_ai_claude_agents/handler.py
Comment thread packages/langchain-agents/src/launchdarkly_ai_langchain_agents/handler.py Outdated
Comment thread packages/langchain-agents/src/launchdarkly_ai_langchain_agents/handler.py Outdated
…d_parameters from the package root

Only the provider packages in this repo use these helpers, and anything in
__all__ becomes public API at 1.0. Callers now import them from the modules
that define them: launchdarkly_ai_server.utils and
launchdarkly_ai_server.parameter_forwarding.
@apucacao

apucacao commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

…rameters

ClaudeAgentOptions also configures the host process the SDK launches: which
binary runs, its environment, working directory, file access, permission
mode, settings, plugins, sandbox, and session state. Forwarding those from a
config let anyone who can edit an AI Config launch their own binary or point
the API key at another host.

The forwarded list is now max_turns, max_thinking_tokens, thinking, effort,
max_budget_usd, fallback_model, output_format and betas. Every other field is
excluded, with the reason next to it.

The loops that popped handler-owned keys are removed: none of those keys is
on the forwarded list, so the filter already drops them.
model_kwargs is merged straight into the request payload, so it carried
extra_headers and extra_query past the exclusions. mcp_servers let a config
attach a remote MCP server that then receives the conversation. region_name
and inference_geo move where data is processed. Bedrock's
additional_model_request_fields is another unfiltered request bag.

All of those are now excluded, along with LangChain's own runtime fields
(callbacks, cache, tags, metadata, streaming mode, message format, ...),
which configure how LangChain runs in this process rather than the request.

_model_constructor_kwargs now requires its forwarded list, and the dead pop
of tools for Bedrock is removed.
output_format is only accepted by messages.stream, so a config with it
behaved differently depending on the call. It is now dropped on both paths;
output_config, which both accept, carries the output format. One forwarded
list now serves both calls.

inference_geo is excluded too: it sets the region inference runs in.

The loops that popped handler-owned keys are removed, since none of those
keys is on the forwarded list.
apucacao and others added 4 commits October 21, 2026 19:50
ChatAnthropic sends thinking to the Messages API as written, and the handler
checked only that it was an object, so {"budget_tokens": 1024} or
{"type": "enabled"} with no budget reached the API and was rejected there.
The JS SDK already drops a thinking without a string type.

thinking is now rebuilt from the variants the Messages API takes (enabled
with budget_tokens, adaptive, disabled), keeping only their sub-keys, the
same shapes the Claude Messages handler uses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Data residency (inference_geo) and container state (container,
reuse_last_container) are deployment settings, not generation settings, and
section 1.12 lists them as never forwarded. inference_geo was filed under
endpoints and the container keys were missing, so no test checked that a
handler drops them. inference_geo now sits under data location and
retention, and container, reuse_last_container and previous_response_id
under server-side state, matching the spec's categories.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rameters

The TypeSafe judge handler landed on main without a forwarded-key list, so the
guard that every handler module declares one fails. system_one takes the state,
the questions and the model name; its other arguments are runtime wiring, so
the list is empty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@apucacao
apucacao requested a review from jeffdupont October 22, 2026 21:01

@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.

Changing my approval to request changes. I found this after reading the full spec in ai-sdks-monorepo#42.

output_config is forwarded with only a mapping check (claude-messages/.../handler.py:67 and :81, and the LangChain ChatAnthropic list), so output_config.format reaches the API. That field is Anthropic's structured-output JSON schema. The handler owns structured output through outputFormat (the schema instruction at handler.py:330). So a config with outputFormat set to schema A and output_config.format set to schema B gets back JSON in the shape of B.

I've asked in mono#42 for output_config to keep only effort. Once that lands, rebuilding it here as {effort}, and flipping the tests that assert it passes through, would close this. Everything from my earlier threads still looks good.

apucacao and others added 3 commits October 22, 2026 19:25
output_config.format is Anthropic's structured-output schema. The handlers
own structured output through outputFormat, so a config that also set
output_config.format got a reply shaped by a second schema. Claude Messages
(invoke and stream) and LangChain ChatAnthropic now rebuild output_config as
{"effort": ...} and drop it when effort is missing or not a string.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed output

The handler puts the config's outputFormat schema in the system prompt. A
model.parameters.output_format with another schema was forwarded to
ClaudeAgentOptions, so the prompt asked for one shape while the SDK enforced
another. output_format is now excluded on the handler and the native graph,
which share the same list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@apucacao apucacao changed the title fix: pass AI Config model parameters through to every provider handler fix!: pass AI Config model parameters through to every provider handler Oct 22, 2026
@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread packages/openai-agents/src/launchdarkly_ai_openai_agents/handler.py
Comment thread packages/langchain-agents/src/launchdarkly_ai_langchain_agents/handler.py Outdated
apucacao and others added 2 commits October 22, 2026 19:41
A mistyped effort or summary made Reasoning(...) raise while the Agent was
built, so a config that used to run failed. It is malformed, so it is now
dropped like any other malformed nested value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ChatAnthropic takes stop as the alias of stop_sequences. A config that set
both passed both spellings of one field. stop is now renamed, and
stop_sequences wins when both are set, as for ChatBedrockConverse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@apucacao

Copy link
Copy Markdown
Contributor Author

@jeffdupont fixed, following the #42 change:

  • output_config (06317e3): Claude Messages rebuilds it as {"effort": ...} on invoke and stream, and drops it when effort is missing or not a string, so format never reaches the API. Both LangChain packages do the same for ChatAnthropic, and a test checks a real ChatAnthropic._get_request_payload carries only {"effort": "low"}. The pass-through tests are flipped.
  • Claude Agents output_format (65e4cf3): same problem, since the handler owns structured output through outputFormat in the system prompt. It's now excluded on the handler and native graph, and the cross-SDK probe asserts it's dropped. feat: emit gen_ai.conversation.id #42 has the matching spec change.

Two other things changed since your approval:

  • main gained the public API check (chore(AIC-3500): fail CI on an undeclared breaking change to the public API #196), and it flagged this PR: the Vercel handlers' OWNED_PARAMETERS are gone (replaced by the forwarded lists) and OpenCode session_events takes the forwarded parameters. Since configs that set parameters now behave differently anyway, I marked the PR fix!: with a BREAKING CHANGE: footer, which ships a minor before 1.0.
  • main also added the TypeSafe judge handler, so it now declares an empty forwarded list (e2d65f9): system_one takes the state, the questions and the model name only.

Bugbot also found two crashes on the latest push, both fixed:

  • A reasoning with a mistyped effort or summary made Reasoning(...) raise while the Agent was built, in openai-agents and litellm-agents. It's now dropped as malformed (defb53f).
  • ChatAnthropic got both stop and stop_sequences when a config set both, though they're one field. stop is now renamed and stop_sequences wins, which answers the precedence question you raised on Oct 12 and matches what JS already does (5031a8d). feat: emit gen_ai.conversation.id #42 says so in the ChatAnthropic row (64177ad).

@apucacao
apucacao requested a review from jeffdupont October 22, 2026 23:45
Eleven handler packages now import launchdarkly_ai_server.parameter_forwarding
and utils.model_parameters, which no released client has (PyPI's latest is
0.4.0). This change is breaking, so release-please ships the client as 0.5.0,
and each handler now requires it instead of a bare or >=0.3.1 range that would
fail at import.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3e11f4f. Configure here.

Comment thread packages/claude-agents/pyproject.toml Outdated
apucacao and others added 2 commits October 23, 2026 11:19
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>
@apucacao

Copy link
Copy Markdown
Contributor Author

@jeffdupont one follow-up after your approval: point 2 from your Oct 12 review (a lower bound on launchdarkly-ai-server) had slipped. The >=0.3.1 on six handlers came from #158, and the five newer handler packages had no bound at all, while all eleven import parameter_forwarding and utils.model_parameters, which no released client has (PyPI is at 0.4.0). Each of the eleven now requires launchdarkly-ai-server>=0.4.1 (718594e), the same latest-plus-a-patch convention #158 used, so it holds whichever version release-please cuts next. TypeSafe and the ai meta-package import neither module, so they're unchanged. I also merged main again so the branch's release manifest matches PyPI.

@jeffdupont
jeffdupont merged commit e3afb3f into main Oct 23, 2026
9 checks passed
@jeffdupont
jeffdupont deleted the alexis/forward-model-parameters branch October 23, 2026 16:25
@github-actions github-actions Bot mentioned this pull request Oct 23, 2026
jeffdupont pushed a commit to launchdarkly/js-ai-sdk that referenced this pull request Oct 23, 2026
The JS and Python SDKs have to forward the same `model.parameters` keys
before 1.0. Jeff's review of launchdarkly/python-ai-sdk#107 found that
the lists from #73 differ from Python's. The shared lists are now in the
cross-SDK spec (launchdarkly/ai-sdks-monorepo#42, TESTING.md §1.12), and
Python is moving to them too. This PR makes each JS handler forward
exactly those lists, minus the keys the spec marks as not supported in
TypeScript.

What changed:

- Claude Messages: stops forwarding `container`, `inference_geo` and
`metadata`. `output_config` keeps only `effort` (a top-level `effort`
still moves into it, and an explicit `output_config.effort` wins).
`output_config.format` is dropped, because structured output belongs to
the config's `outputFormat`: forwarding it let a config ask for one
schema in `outputFormat` while the API enforced another, and made
`stream` return JSON.
- OpenAI Messages: stops forwarding `context_management`, `include`,
`instructions`, `metadata`, `moderation`, `prompt_cache_retention`,
`safety_identifier`, `store`, `truncation` and `user`, on both invoke
and stream. `max_tool_calls` is not forwarded: the spec marks it not
supported in TypeScript, because the `openai` package's
`responses.create` has no such parameter.
- OpenAI Agents (handler and native graph): stops forwarding
`context_management`, `prompt_cache_retention`, `store` and
`truncation`. `tool_choice` is forwarded only as a string.
- LiteLLM Agents (handler and native graph): stops forwarding `store`.
`reasoning` keeps only `effort`, because the Chat Completions model
sends it as `reasoning_effort` and has no field for `summary`.
`tool_choice` is forwarded only as a string.
- Claude Agents (handler and native graph): stops forwarding
`output_format`, for the same reason as `output_config.format`: the
handler applies the config's `outputFormat` itself. `thinking` is kept
only with a type the Claude Agent SDK knows, an `enabled` thinking needs
a numeric budget, and `display` is kept only as `summarized` or
`omitted`.
- Vercel (messages and agents): a `reasoning` object is read as its
`effort`, and any other non-string `reasoning` is dropped. `tool_choice`
is forwarded only as a string, since the AI SDK reads any other value as
a call to a named tool.
- LangChain ChatOpenAI (both LangChain packages and the LangGraph native
graph's default model): stops forwarding `audio`, `disable_streaming`,
`metadata`, `modalities`, `output_version`, `prompt_cache_retention`,
`reasoning`, `stream_usage`, `streaming`, `tags`, `use_responses_api`,
`user`, `verbose` and `zdr_enabled`. `prompt_cache_key` is still
forwarded: the spec marks it not supported in Python only, where
`ChatOpenAI` has no such field.
- LangChain ChatAnthropic: stops forwarding `context_management`,
`disable_streaming`, `inference_geo`, `metadata`, `output_version`,
`stream_usage`, `streaming`, `tags` and `verbose`. It now takes `stop`
as `stopSequences` and `max_tokens_to_sample` as `maxTokens`, because
`ChatAnthropic` has neither field under those names. `outputConfig`
keeps only `effort`, as in Claude Messages.
- LangChain ChatBedrockConverse: stops forwarding `disable_streaming`,
`guardrail_config`, `metadata`, `output_version`, `stream_usage`,
`streaming`, `supports_tool_choice_values`, `tags` and `verbose`.
- LiteLLM Messages and TypeSafe: no change. LiteLLM Messages already
forwarded the spec's list, and TypeSafe forwards no `model.parameters`
key.

Following the spec, nested values with the wrong shape are dropped
instead of sent. Examples are a `thinking` without a `type`, a
`reasoning` that is not an object, a `tool_choice` of the wrong type, or
an `output_config` without a string `effort`. OpenAI Messages
`reasoning` keeps only `effort` and `summary`, as OpenAI Agents already
did.

Testing:

- Each handler and native graph has a test that the forwarded keys are
exactly the spec's list, and, where the handler has both, that invoke
and stream forward the same keys and values. This now includes LiteLLM
Messages and LiteLLM Agents. TypeSafe has a test that it forwards no
`model.parameters` key at all.
- Every package's never-forward bag is built from one shared key list in
`packages/client/src/__tests__/never-forwarded-keys.ts`, which covers
every never-forward category in the spec, including `user`, `metadata`,
`inference_geo` and `container`. Each package's
`__tests__/never-forwarded.ts` adds only the keys its own table leaves
out, so the bags can't drift apart.
- New tests cover dropping malformed nested values,
`output_config.format` on invoke and stream, and `output_format` in
Claude Agents.
- Each changed package's tests and `tsc --noEmit`, and `biome check .`,
pass locally.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Overview**
> Aligns every JS provider handler with **TESTING.md §1.12** so
`model.parameters` forwards the same generation-related keys as the
Python SDK, and drops everything else (credentials, retention, metadata,
wiring, etc.).
> 
> **Allowlists are tightened across packages:** OpenAI Messages/Agents
stop forwarding retention and attribution fields (`store`, `user`,
`context_management`, …); Claude Messages keeps `output_config` to `{
effort }` only and strips `format`; Claude Agents no longer forwards
`output_format` (structured output stays on config `outputFormat`);
LangChain constructors lose base-model wiring keys and narrow
ChatAnthropic `outputConfig`; LiteLLM/Vercel agents drop `store` and
non-string `tool_choice`; Vercel maps object `reasoning` to a string
`effort`. **Malformed nested values** (`thinking`, `tool_choice`,
`reasoning`, typed Anthropic objects) are dropped instead of passed
through.
> 
> **Tests:** Shared `createNeverForwarded()` in `packages/client`
replaces duplicated never-forward lists; each handler/native graph gets
§1.12 canonical-key tests and invoke/stream parity checks where
applicable.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
0dd95ab. 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>
jeffdupont pushed a commit that referenced this pull request Oct 23, 2026
…host settings (#201)

## Summary

This is the Python side of the core half of
launchdarkly/ai-sdks-monorepo#59 (TESTING.md §2.x "Claude Agents Handler
— host options and built-in tool set"). The TypeScript side is
launchdarkly/js-ai-sdk#135.

- **`tools=[]` when a config attaches no `NativeTool`** (§2.x.2). This
reverses 50e82fd. With the key omitted, every Python run got the full
Claude Code built-in set (`Read`, `Bash`, `Skill` and the rest), and the
read-only tools in it need no permission. A config now turns a built-in
on by attaching its sentinel, which is what TypeScript has always done.
User-defined tools still go through the in-process MCP server and
`allowed_tools`, whatever `tools` holds.
- **`create_claude_agents_handler(*, capture_content=None, cwd=None,
setting_sources=None)`** (§2.x.1). Both options are forwarded to every
`query()` on the blocking and streaming paths.
- `setting_sources` not given sends `[]`. The Agent SDK reads `None` as
"load every source", which put the host's plugins, skills, hooks,
permission rules and `CLAUDE.md` into every run (AIC-3584).
  - `cwd` is sent only when given.
  - `capture_content=None` still follows `configure()`.
- A config can't set or override either option: since #107 the handler
forwards only allowlisted `model.parameters`, and both keys are on the
excluded list. A test pins that.
- **The wrappers and the native adapter take both options too**,
matching launchdarkly/js-ai-sdk#135:
- `claude_agents()` and `claude_graph()` take `cwd` / `setting_sources`
keyword-only and build the handler with them. Neither reaches `config()`
/ `graph()`.
- `to_claude_agents()` reads them from `opts` and sends
`_host_query_options(...)` to every node's `query()`, root and
sub-agents. It already sent `tools=[]` with no native tools, and a test
now pins that too.
  - All four entry points default to `setting_sources=[]` and no `cwd`.
- **The README has a migration note.** `[]` also drops the settings-file
`env` (`ANTHROPIC_BASE_URL`, `CLAUDE_CODE_USE_BEDROCK` / `_VERTEX`,
proxy variables), `apiKeyHelper` and `model`. A deployment that reaches
Bedrock, Vertex or a gateway through `~/.claude/settings.json` needs
`setting_sources=["user"]` or those variables in the process
environment.

## Breaking

Two changes in runtime behaviour:
- A run has no Claude Code built-ins unless its config attaches them.
- A run loads no host Claude Code settings unless the application passes
`setting_sources`.

This needs to land before the 1.0 freeze, because changing either
default after 1.0 would be breaking.

## Tests

- `TestBuiltinsSurviveAnEmptyToolList` is replaced. Its "omits the key"
test asserted the behaviour this PR reverses.
- `TestHostOptionsAndBuiltinToolSet` adds nine tests, each run on both
the invoke and stream paths.
- `TestToClaudeAgentsHostIsolation` adds four, including root and leaf
for the default and for given values.
- `TestConvenienceExportHostOptions` and `TestClaudeGraphHostOptions`
add two each: the default, and given values reaching `query()` but not
`config()` / `graph()`.
- The API report is regenerated for the three new signatures.

Checks:
- `uv run pytest`: every claude-agents test passes. The one failure in
the full suite is `opencode-agents`
`test_a_fake_opencode_is_started_spoken_to_and_stopped`, a macOS
`/private/var` vs `/var` temp-path mismatch already on `main`.
- `make lint`, `make format-check` and `make typecheck` are clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Overview**
> **Breaking:** Python Claude agent runs no longer inherit the Agent
SDK’s implicit defaults for built-in tools or host Claude Code settings.
> 
> Runs now pass **`tools=[]`** when the flag config attaches no
`NativeTool` sentinel, which turns off Claude Code built-ins (`Read`,
`Bash`, etc.) unless you explicitly enable them via `ClaudeRead`,
`ClaudeBash`, and similar in `tool_handlers`. User-defined tools still
use the in-process MCP server unchanged.
> 
> **Host isolation:** `create_claude_agents_handler`, `claude_agents`,
`claude_graph`, and `to_claude_agents` gain app-only **`cwd`** and
**`setting_sources`** (via `_host_query_options`), forwarded on every
`query()` (invoke, stream, and graph nodes). Default
**`setting_sources=[]`** stops loading `~/.claude/settings.json`,
project `CLAUDE.md`, plugins, hooks, and settings-file `env`
(Bedrock/Vertex gateways may break until you opt in or set process env).
Flag **`model.parameters` cannot override** these options.
> 
> README/agents.md document migration; tests replace the old “omit
`tools`” expectation and add coverage for host options and built-in tool
sets across invoke/stream/graph paths.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
bcbde98. 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>
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