Skip to content

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

Merged
apucacao merged 24 commits into
mainfrom
alexis/forward-max-turns
Oct 7, 2026
Merged

apucacao merged 24 commits into
mainfrom
alexis/forward-max-turns

Conversation

@apucacao

@apucacao apucacao commented Sep 18, 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, limited to an allowlist of model and run settings per handler. Unknown keys are dropped.

  • model.parameters comes from the AI Config, so no handler forwards credentials, base URLs or other endpoint and connection settings, timeouts or retries, request header, body or query overrides, remote MCP servers, or Claude Agents host-process settings (cwd, env, permissionMode, the executable path, and so on), in any spelling. Each handler has a test that a bag containing all of them produces options with none of them.
  • Configs use snake_case (max_turns, top_p), the providers' own names and what the UI writes.
  • Anthropic and OpenAI clients take snake_case, so those handlers pass keys through as-is.
  • Agent SDKs and LangChain read camelCase, so those handlers convert top-level keys. If both spellings are set, snake_case wins.
  • Nested values are passed as written, except where the SDK reads a different shape: Claude Agents gets thinking.budget_tokens as budgetTokens, and OpenAI Agents gets context_management[].compact_threshold as compactThreshold.
  • The Vercel AI SDK handlers map keys onto the AI SDK's own call settings, such as max_tokens to maxOutputTokens. headers and providerOptions are not forwarded: call-level headers replace the provider's Authorization header, and the AI Gateway reads credentials and routing from providerOptions. vercelEvaluate no longer reads model.parameters.
  • The Claude Agents and OpenAI Agents native graphs forward each node's settings, plus maxTurns, as the Python native graphs do.

🤖 Generated with Claude Code


Note

Overview
AI Config model.parameters are now applied across all provider handlers instead of being ignored or blindly spread into SDK constructors.

Shared helpers in @launchdarkly/ai-server (normalizeModelParameters, pickForwardedModelParameters, camelizeModelParameters) normalize the config bag and pick only allowlisted generation/run settings per handler. Credentials, endpoints, headers, model_kwargs / providerData, remote MCP, and Claude Agents host-process options are never forwarded; handler-owned fields (model, messages/input, tools, streaming flags) still win at merge time.

Per-stack behavior: raw Anthropic/OpenAI message handlers keep snake_case on the wire, with renames like UI effort → output_config.effort and Chat Completions token limits → Responses max_output_tokens. Agent/LangChain/Vercel paths camelize top-level keys (max_turns → maxTurns) and map nested shapes where SDKs differ (e.g. Claude Agents thinking.budget_tokens → budgetTokens, OpenAI Agents maxTurns on Runner.run). Native graphs forward each node’s settings like the Python SDK.

Docs warn integrators not to spread config.model.parameters into LangChain constructors. Coverage adds never-forwarded fixtures, extensive unit tests, and wire-level tests with real SDK + stubbed fetch.

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

@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 and others added 4 commits September 22, 2026 10:37
buildQueryOptions already accepted an unused extra options bag; the call
site never filled it. An AI Config could set a turn cap or agent-run
tuning knobs and this handler silently dropped them, so an agent driven
through this SDK ran with no budget at all.

Now config.model.parameters is checked against the Options keys the
Claude Agent SDK actually accepts (maxTurns, maxThinkingTokens,
maxBudgetUsd, effort, fallbackModel, thinking) and only those are
forwarded. Anything else, including temperature and max_tokens which
this SDK has no equivalent for, is ignored rather than passed through.
No default is added, so a config that sets nothing behaves exactly as
before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The handler built responses.create()/responses.stream() calls as only
{model, input, tools}, so a customer who set temperature, top_p, or
max_output_tokens in an AI Config had it silently dropped on every
call, blocking and streaming alike.

config.model.parameters is now checked against an explicit allowlist
of ResponseCreateParams keys the Responses API actually accepts
(temperature, top_p, max_output_tokens, top_logprobs,
parallel_tool_calls, reasoning, truncation, metadata, store,
service_tier, prompt_cache_key, safety_identifier), and only those are
forwarded, on every call site: the initial turn, the tool-loop
follow-up turn, and both branches of the streaming path. The allowlist
is spread first so model, input, tools, and previous_response_id -
computed by the handler itself - always win, so a parameters bag can
never smuggle those back in. A config that sets nothing produces {},
so the request sent is unchanged from before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The handler only ever read config.model.parameters.max_tokens; every
other Anthropic Messages API knob a customer set in an AI Config
(temperature, top_p, top_k, stop_sequences, thinking, and the rest of
MessageCreateParamsBase) was silently dropped.

Now an explicit allowlist, taken from the SDK's own
MessageCreateParamsBase type, is forwarded from model.parameters at
both call sites (the blocking tool loop and the streaming tool loop).
The allowlist excludes model, messages, tools, system, max_tokens, and
stream, so a config value can never override what the handler already
decided for those. max_tokens keeps its existing behaviour and 1024
default unchanged.

A config that sets no parameters, or an empty parameters object,
produces the exact same call as before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Runner.run already accepts maxTurns and the Agents SDK's ModelSettings
tunes temperature, topP, and similar knobs, but the handler never read
either from config.model.parameters. An agent driven through this SDK
ran with no turn budget at all, and a customer's temperature/topP/etc.
settings silently did nothing.

Now config.model.parameters is checked against the ModelSettings keys
the Agents SDK's Agent constructor actually accepts (temperature, topP,
frequencyPenalty, presencePenalty, maxTokens, toolChoice,
parallelToolCalls, truncation, store, promptCacheRetention) and only
those are forwarded as modelSettings. maxTurns is read separately and
passed to Runner.run's options, since it caps the agentic loop rather
than tuning a model call. Both the blocking and streaming call paths
are covered. Anything else in model.parameters is ignored rather than
forwarded. No default is added, so a config that sets nothing behaves
exactly as before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@apucacao
apucacao force-pushed the alexis/forward-max-turns branch from 6a1c4cf to 1f14632 Compare September 22, 2026 14:39
@apucacao apucacao changed the title fix(claude-agents): forward maxTurns and model.parameters to query() fix: forward model.parameters to every provider handler Sep 22, 2026
apucacao and others added 3 commits September 22, 2026 10:51
Extracts the duplicated `buildModelParameterOptions` / `buildModelSettings`
picker loop (claude-agents, claude-messages, openai-messages, openai-agents)
into `pickForwardedModelParameters`, and the repeated
`config.model?.parameters && typeof ... === 'object' ? ... : {}` guard
(langchain-messages, langchain-agents, langchain-agents/native-graph) into
`normalizeModelParameters`. Both live in packages/client and are exported
from its public index.

Each handler keeps its own FORWARDED_*_KEYS allowlist next to the request
shape it maps onto; the shared picker only owns the loop. openai-agents
keeps collapsing an empty result to `undefined` itself. The LangChain
handlers keep spreading the whole parameters object, unchanged, since their
chat models take an open-ended options bag.

No behaviour change: same keys picked, same spread order, same undefined vs
{} results. Adds unit tests for both new helpers in packages/client.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…bag through

The four allowlist handlers (claude-agents, claude-messages,
openai-messages, openai-agents) each maintained a hand-picked
FORWARDED_*_KEYS list and a pickForwardedModelParameters call to filter
config.model.parameters before forwarding it to the provider SDK. The
allowlists silently dropped valid settings and needed updating whenever
a provider added a new tunable. The LaunchDarkly UI already constrains
which keys a customer can save, so an unsupported key reaching the
provider is an acceptable error, not something this SDK needs to guard.

Replace every allowlist with normalizeModelParameters, the same
pass-through helper the LangChain handlers already use, and delete
pickForwardedModelParameters from @launchdarkly/ai-server now that no
caller is left.

While making every call site spread parameters first, found three
places where a handler-owned key was set with a conditional spread
(`...(cond ? { key } : {})`) instead of an explicit key: when the
handler's own condition was false, the key was never set, so a
customer's model.parameters could smuggle a value through unopposed.
Fixed by always setting the key explicitly, to undefined when absent:
claude-messages' `system`/`tools`, claude-agents' `systemPrompt`, and
openai-messages' `tools`/`previous_response_id`/`text` (structured
output).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
buildQueryOptions took a single `extra` bag that carried both the
customer's model.parameters and this handler's own internal flags
(includePartialMessages). Two different trust levels in one untyped
argument, with the safe ordering only holding by accident of how the
call site happened to build the literal.

Split them: `modelParameters` is required, documented as untrusted, and
spread first so every handler-owned key overrides it. `internalOptions`
carries the handler's own flags and is spread last so it wins over
everything. A reader of the signature can now see which is which.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…dlers

The LaunchDarkly UI saves model.parameters keys in snake_case, the wire
name every provider SDK expects (max_turns, top_p, ...). claude-agents,
openai-agents, and both langchain handlers wrap JavaScript frameworks
that only read camelCase option names, so a snake_case key like
max_turns silently vanished instead of reaching the framework: no
error, the value just never took effect.

Add camelizeModelParameters (packages/client/src/utils.ts), converting
top-level snake_case keys to camelCase generated from the key itself.
Nested values (e.g. thinking.budget_tokens) pass through untouched,
and when both spellings of a key are set the snake_case one wins,
deterministically regardless of key order.

Apply it at every call site in the four affected handlers:
claude-agents (query() options, both the blocking and streaming
paths), openai-agents (ModelSettings and buildMaxTurns for
Runner.run), and both langchain handlers' default model
constructors, including langchain-agents' native-graph.ts.

claude-messages and openai-messages wrap raw provider clients that
already read snake_case themselves, so they are unchanged.

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

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@apucacao apucacao changed the title fix: pass model.parameters through 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.

apucacao and others added 4 commits September 24, 2026 11:55
…r request types

The four framework handlers correctly forward the whole model.parameters bag
via normalizeModelParameters, since their frameworks ignore keys they do not
recognize. The two handlers that wrap a raw provider client (claude-messages,
openai-messages) do not have that safety net: an unsupported key reaches the
wire and the provider returns 400.

Restores pickForwardedModelParameters (previously dropped in cf9a611) as a
shared, exported helper: pick the subset of parameters whose keys appear in
an explicit allowlist, drop the rest silently. The two raw-client handlers
use it next to a compiler-checked allowlist derived from each SDK's own
request type, so the list cannot silently drift.

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

This handler wraps the raw OpenAI SDK's responses.create / responses.stream,
which reject an unrecognized field with a 400. The LaunchDarkly UI offers the
Chat Completions parameter set, so a config that sets max_tokens,
frequency_penalty, presence_penalty, seed, n, stop, response_format,
logit_bias, logprobs, max_completion_tokens, audio, modalities, or prediction
used to reach the API verbatim and would now break the run.

FORWARDED_MODEL_PARAMETER_KEYS lists every ResponseCreateParamsBase key this
handler forwards. A compile-time exhaustiveness check classifies every other
key on that type as handler-owned (input, model, previous_response_id, text,
tools) or excluded as transport/plumbing, with a reason in the comment above
the list; a key the SDK adds and this file does not classify fails to build,
naming the key.

buildModelParameterOptions also renames the two Chat Completions token-limit
spellings the UI offers, max_tokens and max_completion_tokens, to the
Responses API's max_output_tokens, before filtering. Precedence when a config
sets more than one spelling: an explicit max_output_tokens wins, then
max_completion_tokens, then max_tokens.

Applied at every responses.create / responses.stream call site: the initial
blocking turn, the blocking tool-loop turn, and both branches of the
streaming turn. Behaviour is unchanged when model.parameters is absent or
empty.

Updates the handler.test.ts case that asserted an unrecognized key reached
the request to assert the opposite, and adds coverage for the accepted-key
path, the dropped-key path, both renames, and precedence. Adds
wire.test.ts, which does not mock the openai package and instead stubs
global fetch to assert on the literal JSON body responses.create sends.

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

This handler wraps the raw Anthropic SDK's messages.create / messages.stream,
which reject an unrecognized field with a 400. The LaunchDarkly UI offers a
top-level effort setting that the Messages API only accepts nested under
output_config, so a config that set it used to reach the API verbatim and
would now break the run.

FORWARDED_MODEL_PARAMETER_KEYS lists every MessageCreateParamsBase key this
handler forwards. A compile-time exhaustiveness check classifies every other
key on that type as handler-owned (model, messages, system, tools,
max_tokens) or excluded as transport/plumbing (stream, since this handler
selects streaming by choosing between messages.create and messages.stream,
not by setting a field), with the reason in the comment above the list; a key
the SDK adds and this file does not classify fails to build, naming the key.

buildModelParameterOptions also moves a top-level effort into
output_config.effort before filtering. An output_config.effort already
present in the config wins over the renamed value. The handler does not set
output_config itself anywhere (outputFormat is folded into the system prompt
instead), so there is no handler-owned output_config to protect here.

Applied at both call sites inside the tool loop shared by the blocking and
streaming paths. Keeps max_tokens ?? 1024. Behaviour is unchanged when
model.parameters is absent or empty.

Updates the two handler.test.ts cases that asserted an unrecognized key
reached the request to assert the opposite, and adds coverage for the
effort rename, the explicit-output_config.effort-wins case, and the merge
with an output_config that also sets other fields. Adds wire.test.ts, which
does not mock @anthropic-ai/sdk and instead stubs global fetch to assert on
the literal JSON body messages.create sends.

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

Orchestrator follow-up: TypeScript and Python must apply the same exclusion
rule, and the previous excluded set went too far. Excluding stream_options,
store, safety_identifier, prompt_cache_key, prompt_cache_retention, include,
context_management, conversation, prompt, and user was based on "is this a
generation setting", not "would this break the handler" — the latter is the
actual contract: forward everything the API accepts unless setting it
produces a broken or missing result.

Moves store, user, safety_identifier, prompt_cache_key,
prompt_cache_retention, include, and context_management from excluded to
forwarded. The excluded set is now exactly five keys, matching the shared
rule:
- stream, stream_options: the handler chooses streaming itself by calling
  responses.create() vs responses.stream().
- background: the call returns before the output exists, so the handler
  gets no result.
- conversation, prompt: supply server-side state/templates that conflict
  with the input the handler builds itself.

The compile-time exhaustiveness check against ResponseCreateParamsBase still
passes with the narrowed OpenAIExcludedKeys union (verified by removing
'user' from FORWARDED_MODEL_PARAMETER_KEYS and confirming tsc names it).

claude-messages needed no change: its excluded set was already exactly
{ stream }, which is what the shared rule requires for the Messages API.
Refreshed its doc comment to state the same "would break the handler" rule
explicitly, no behavior change.

Updates handler.test.ts to add coverage for the seven now-forwarded keys and
to keep asserting the five still-excluded keys are dropped. Extends
wire.test.ts with one case proving `store` reaches the literal wire request
while `background` is dropped from it.

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.

Parity note alongside my review of python #107 (launchdarkly/python-ai-sdk#107). The two messages handlers match Python: allowlists, the effort → output_config and max_tokens → max_output_tokens renames, and unknown keys dropped. The agent and LangChain handlers don't match. Python allowlists every handler. Here the whole model.parameters bag is camelCased and spread in with no filtering, so:

  • LangChain (modelConstructorArgs): api_key becomes apiKey, which ChatOpenAI and ChatAnthropic use. anthropic_api_url, configuration, client_options, timeout, max_retries and Bedrock credentials / region / endpoint_host also reach the constructors. Spreading the raw bag predates this PR, but camelCasing is what makes the snake_case spellings the UI writes take effect.
  • Claude Agents (buildQueryOptions): cwd, env, permissionMode, extraArgs, pathToClaudeCodeExecutable, executable and allowDangerouslySkipPermissions can all come from a config. The Python PR has the same problem with its allowlist; details are in my inline comment there.
  • Nested keys aren't converted: thinking.budget_tokens stays snake_case, but the Claude Agent SDK reads budgetTokens, so the thinking budget is probably ignored. The same goes for context_management[].compact_threshold in openai-agents. I haven't confirmed either at runtime.
  • Native graphs: neither the claude-agents nor the openai-agents native graph forwards parameters or maxTurns; Python's do.
  • New public names: camelizeModelParameters, normalizeModelParameters and pickForwardedModelParameters are exported from the client root (index.ts:87,93,97). For the 1.0 surface they belong in the internal group.

The monorepo has no spec for forwarding these parameters, and open monorepo #10 says not to rename or filter keys. I'd suggest agreeing one rule in TESTING.md (allowlist per handler, connection and host keys never forwarded) and landing both PRs against it.

Comment thread packages/claude-agents/src/handler.ts
Comment thread packages/langchain-messages/src/handler.ts Outdated
apucacao and others added 3 commits October 2, 2026 16:29
The handler spread model.parameters into generateText/streamText as
saved, but the AI SDK only reads its own camelCase call settings and
drops everything else without an error. A config's max_tokens, top_p,
stop_sequences and the like never reached the model; only single-word
keys such as temperature did.

The handler now renames max_tokens / max_completion_tokens /
max_output_tokens to maxOutputTokens and reasoning_effort to reasoning,
camelizes the rest of the top-level keys, and forwards only the AI SDK
call settings (LanguageModelCallOptions and RequestOptions, minus
abortSignal, plus toolChoice and providerOptions). The allowlist is
checked at compile time against the AI SDK's own types, and replaces
the old denylist of handler-owned names.

vercelEvaluate also forwards the three request fields
experimental_evaluate accepts (maxRetries, headers, providerOptions)
from model.parameters; options passed to vercelEvaluate win.

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

Same problem as vercel-messages: the ToolLoopAgent settings were built
by spreading model.parameters as saved, so snake_case keys such as
max_tokens and top_p were dropped by the AI SDK. The single-agent
handler and every native-graph node now go through the same mapping
and compile-time-checked allowlist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@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/handler.ts
Comment thread packages/langchain-agents/src/handler.ts Outdated
Comment thread packages/openai-agents/src/handler.ts Outdated

@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/handler.ts
Comment thread packages/langchain-agents/src/handler.ts Outdated

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

Requesting changes for the security issues below. Re-reviewed at 4241d75. yarn build passes, and yarn test passes: exit 0, 1238 tests across the workspaces.

New since my review: the Vercel parameter mapping (d2a52ae, 4241d75). The rename and camelize logic is right, and the compile-time check against the AI SDK's own types is a good guard. The tests catch the fix: reverting buildModelParameterOptions to pass the raw bag through fails 7 vercel-messages tests and 6 vercel-agents tests.

GA blocker in the new code: headers is on the allowlist, so a config can replace the customer's API key. FORWARDED_MODEL_PARAMETER_KEYS includes headers (vercel-messages/src/model-parameters.ts, and the same list in vercel-agents), and the AI SDK lets call-level headers win over the provider's. I probed it through buildModelParameterOptions into generateText. With the provider built with apiKey: 'CUSTOMER' and a config that sets headers: { Authorization: 'Bearer ATTACKER' }, the request went out with authorization: Bearer ATTACKER, on both @ai-sdk/openai and the Vercel Gateway. This is the same issue as python #103's extra_headers. FORWARDED_EVALUATE_PARAMETER_KEYS forwards headers to experimental_evaluate too (from the code; I didn't probe it). Could headers move to the excluded keys in all three lists? providerOptions deserves a look too, since some providers accept routing and credential settings in it. I haven't tested that.

Repro (a vitest file in packages/vercel-messages/src/, run at 4241d754; it passes, meaning the config's header won):

import { it, expect } from 'vitest';
import { generateText } from 'ai';
import { createOpenAI } from '@ai-sdk/openai';
import { buildModelParameterOptions } from './model-parameters.js';

it('a config header replaces the customer API key', async () => {
  const seen: Record<string, string>[] = [];
  const fetch = async (_url: unknown, init: RequestInit) => {
    seen.push(Object.fromEntries(new Headers(init.headers).entries()));
    return new Response(JSON.stringify({ error: { message: 'x' } }), { status: 401 });
  };
  const provider = createOpenAI({ apiKey: 'CUSTOMER', fetch: fetch as typeof globalThis.fetch });
  const opts = buildModelParameterOptions({ headers: { Authorization: 'Bearer ATTACKER' } } as never);
  await generateText({ model: provider('gpt-4o'), prompt: 'hi', maxRetries: 0, ...opts }).catch(() => {});
  expect(seen[0]?.authorization).toBe('Bearer ATTACKER');
});

The handler spreads these options into generateText / streamText (vercel-messages/src/handler.ts:280, :329). Inverting that assertion makes a good regression test once headers is excluded.

Still open, no reply on either thread:

  1. Claude Agents spreads the whole bag into query() options (thread 1, GA blocker). No change. claude-agents/src/handler.ts:997 and :1124 still pass camelizeModelParameters(normalizeModelParameters(...)) with no allowlist, so cwd, env, permissionMode, pathToClaudeCodeExecutable and allowDangerouslySkipPermissions can all come from a config.

  2. LangChain constructor args (thread 2). No change. langchain-messages/src/handler.ts:175, and the same in langchain-agents, still spread the camelized bag, so api_key → apiKey, anthropic_api_url and client_options reach the constructors.

  3. OpenAI Agents forwards extraHeaders / extraBody / extraQuery. Bugbot flagged this on the current head (openai-agents/src/handler.ts:510): buildModelSettings camelizes the whole bag into ModelSettings, so a config can inject request headers or body fields on every model call. Same class as the Vercel headers bug; I haven't probed it.

openai-agents has no allowlist at all. Python #107 allowlists every handler, so those three JS handlers are where the two SDKs disagree. Nested keys (thinking.budget_tokens) are still left in snake_case, which I raised in my first review.

There's no TESTING.md section for parameter forwarding. python #107 needs the same, so one shared spec section listing the excluded keys (credentials, base URLs, headers, host-process settings) would keep both languages aligned.

…meout from model.parameters

Call-level headers win over the provider's own, so a config that set
headers.Authorization replaced the customer's API key on @ai-sdk/openai
and the AI Gateway. providerOptions carries Gateway credentials (byok)
and routing (order, only, models). maxRetries and timeout are client
policy. vercelEvaluate no longer reads model.parameters at all, since
nothing it accepts is left.
… model.parameters

The whole camelized bag was spread into query() options, so a config
could set cwd, env, permissionMode, pathToClaudeCodeExecutable,
allowDangerouslySkipPermissions, mcpServers and every other host-process
setting. Only maxTurns, maxThinkingTokens, thinking, effort, maxBudgetUsd,
fallbackModel, outputFormat and betas are forwarded now. thinking is
rebuilt as the SDK reads it, so budget_tokens arrives as budgetTokens.

The native graph forwards the same allowlist per node, as the Python
native graph does.
… from model.parameters

The whole camelized bag became the Agent's ModelSettings, so a config
could set providerData, which the Agents SDK merges into the request and
reads extraHeaders, extraBody and extraQuery from. Only generation
settings are forwarded now, and maxTurns goes to Runner.run alone.
reasoning, text and context_management entries are rebuilt in the SDK's
shapes; compact_threshold becomes compactThreshold and unknown entry
keys are dropped. A top-level verbosity becomes text.verbosity.

The native graph forwards each node's settings, and the root's maxTurns
to Runner.run, as the Python native graph does.
…or fields

Camelizing the whole bag turned api_key, anthropic_api_url,
client_options, configuration, model_kwargs and Bedrock credentials,
region and endpoint_host into working ChatOpenAI, ChatAnthropic and
ChatBedrockConverse constructor options. Each class now takes only its
generation settings, mirroring the Python SDK's lists without
model_kwargs, mcp_servers or region_name. ChatAnthropic gets a top-level
effort as outputConfig.effort, and ChatOpenAI its service_tier spelling.

The LangGraph native graph's default ChatOpenAI uses the same list.
@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.

…edrockConverse

It is merged into the Bedrock request body as written, so it is request
injection, like invocationKwargs. The never-forwarded test fixture in
every package now includes it.
@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.

✅ 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 532a0ee. Configure here.

@apucacao
apucacao merged commit 993225f into main Oct 7, 2026
10 checks passed
@apucacao
apucacao deleted the alexis/forward-max-turns branch October 7, 2026 16:28
@github-actions github-actions Bot mentioned this pull request Oct 7, 2026
XieX added a commit that referenced this pull request Oct 7, 2026
`main` added `camelizeModelParameters`, `normalizeModelParameters` and
`pickForwardedModelParameters` to the package root (#73). Add them to the
root-export snapshot and regenerate the root API report, so the snapshot
test and `api:check` pass after the merge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
andrewklatzke pushed a commit that referenced this pull request Oct 9, 2026
🤖 I have created a release *beep* *boop*
---


<details><summary>@launchdarkly/ai-claude-agents: 0.4.0</summary>

##
[0.4.0](https://github.com/launchdarkly/js-ai-sdk/compare/@launchdarkly/ai-claude-agents-0.3.0...@launchdarkly/ai-claude-agents-0.4.0)
(2026-10-09)


### Features

* **AIC-3211:** record each graph node as its own tracking event
([#91](#91))
([2df2923](2df2923))
* **AIC-3495:** record which public helpers an application calls
([#115](#115))
([aeb4019](aeb4019))
* **build:** build the AI SDK like the other LaunchDarkly JS SDKs so
CommonJS apps can load it (2/4)
([#77](#77))
([2cda755](2cda755))


### Bug Fixes

* **claude-agents:** forward only an allowlist of query() options from
model.parameters
([37bd8a7](37bd8a7))
* **deps:** depend on the @launchdarkly/ai-server release each package
needs ([#119](#119))
([9c2a56b](9c2a56b))
* **handlers:** camelize snake_case model.parameters for framework
handlers
([75c6287](75c6287))
* **langchain:** stop forwarding additionalModelRequestFields to
ChatBedrockConverse
([f6268fd](f6268fd))
* pass AI Config model parameters through to every provider handler
([#73](#73))
([993225f](993225f))


### Dependencies

* The following workspace dependencies were updated
  * dependencies
    * @launchdarkly/ai-server bumped from ^0.3.0 to ^0.4.0
</details>

<details><summary>@launchdarkly/ai-claude-messages: 0.4.0</summary>

##
[0.4.0](https://github.com/launchdarkly/js-ai-sdk/compare/@launchdarkly/ai-claude-messages-0.3.0...@launchdarkly/ai-claude-messages-0.4.0)
(2026-10-09)


### Features

* **AIC-3495:** record which public helpers an application calls
([#115](#115))
([aeb4019](aeb4019))
* **build:** build the AI SDK like the other LaunchDarkly JS SDKs so
CommonJS apps can load it (2/4)
([#77](#77))
([2cda755](2cda755))
* **claude-messages:** forward only Messages API keys from
model.parameters
([9a1819e](9a1819e))


### Bug Fixes

* **deps:** depend on the @launchdarkly/ai-server release each package
needs ([#119](#119))
([9c2a56b](9c2a56b))
* **handlers:** camelize snake_case model.parameters for framework
handlers
([75c6287](75c6287))
* **langchain:** stop forwarding additionalModelRequestFields to
ChatBedrockConverse
([f6268fd](f6268fd))
* **openai-messages:** only exclude model.parameters keys that would
break the handler
([39e342d](39e342d))
* pass AI Config model parameters through to every provider handler
([#73](#73))
([993225f](993225f))
* **SEC-9507:** authorize message-handler tools from the active
variation ([#90](#90))
([c16fe93](c16fe93))


### Dependencies

* The following workspace dependencies were updated
  * dependencies
    * @launchdarkly/ai-server bumped from ^0.3.0 to ^0.4.0
</details>

<details><summary>@launchdarkly/ai-langchain-agents: 0.4.0</summary>

##
[0.4.0](https://github.com/launchdarkly/js-ai-sdk/compare/@launchdarkly/ai-langchain-agents-0.3.0...@launchdarkly/ai-langchain-agents-0.4.0)
(2026-10-09)


### Features

* **AIC-3211:** record each graph node as its own tracking event
([#91](#91))
([2df2923](2df2923))
* **AIC-3495:** record which public helpers an application calls
([#115](#115))
([aeb4019](aeb4019))
* **build:** build the AI SDK like the other LaunchDarkly JS SDKs so
CommonJS apps can load it (2/4)
([#77](#77))
([2cda755](2cda755))


### Bug Fixes

* **deps:** depend on the @launchdarkly/ai-server release each package
needs ([#119](#119))
([9c2a56b](9c2a56b))
* **handlers:** camelize snake_case model.parameters for framework
handlers
([75c6287](75c6287))
* **langchain:** forward only a per-model-class allowlist of constructor
fields
([0990a51](0990a51))
* **langchain:** stop forwarding additionalModelRequestFields to
ChatBedrockConverse
([f6268fd](f6268fd))
* pass AI Config model parameters through to every provider handler
([#73](#73))
([993225f](993225f))


### Dependencies

* The following workspace dependencies were updated
  * dependencies
    * @launchdarkly/ai-server bumped from ^0.3.0 to ^0.4.0
</details>

<details><summary>@launchdarkly/ai-langchain-messages: 0.4.0</summary>

##
[0.4.0](https://github.com/launchdarkly/js-ai-sdk/compare/@launchdarkly/ai-langchain-messages-0.3.0...@launchdarkly/ai-langchain-messages-0.4.0)
(2026-10-09)


### Features

* **AIC-3495:** record which public helpers an application calls
([#115](#115))
([aeb4019](aeb4019))
* **build:** build the AI SDK like the other LaunchDarkly JS SDKs so
CommonJS apps can load it (2/4)
([#77](#77))
([2cda755](2cda755))


### Bug Fixes

* **deps:** depend on the @launchdarkly/ai-server release each package
needs ([#119](#119))
([9c2a56b](9c2a56b))
* **handlers:** camelize snake_case model.parameters for framework
handlers
([75c6287](75c6287))
* **langchain:** forward only a per-model-class allowlist of constructor
fields
([0990a51](0990a51))
* **langchain:** stop forwarding additionalModelRequestFields to
ChatBedrockConverse
([f6268fd](f6268fd))
* pass AI Config model parameters through to every provider handler
([#73](#73))
([993225f](993225f))
* **SEC-9507:** authorize message-handler tools from the active
variation ([#90](#90))
([c16fe93](c16fe93))


### Dependencies

* The following workspace dependencies were updated
  * dependencies
    * @launchdarkly/ai-server bumped from ^0.3.0 to ^0.4.0
</details>

<details><summary>@launchdarkly/ai-node: 0.3.0</summary>

##
[0.3.0](https://github.com/launchdarkly/js-ai-sdk/compare/@launchdarkly/ai-node-0.2.0...@launchdarkly/ai-node-0.3.0)
(2026-10-09)


### Experimental

* **skills:** add experimental Agent Skills under
`@launchdarkly/ai-server/experimental`, re-exported from
`@launchdarkly/ai-node/experimental`
([a242659](a242659))


### Features

* **build:** build the AI SDK like the other LaunchDarkly JS SDKs so
CommonJS apps can load it (2/4)
([#77](#77))
([2cda755](2cda755))
* **build:** dual ESM/CommonJS output via tsup
([d8e62e4](d8e62e4))
* **client:** `initClient(client, options)` accepts telemetry options
for a pre-initialized client
([a242659](a242659))
* **client:** warn about unrecognized `initClient` options instead of
ignoring them silently
([a242659](a242659))


### Bug Fixes

* **client:** `shutdownTelemetry()` releases the OpenTelemetry globals
the SDK registered, so a later `initClient` exports spans again
([a242659](a242659))
* **client:** a repeat `initClient(client)` returns the client already
set instead of replacing it
([a242659](a242659))
* **client:** an `initClient` that fails, or that `shutdown()`
interrupts, closes the client it built, and a failure is no longer
cached, so the next call retries
([a242659](a242659))
* **deps:** depend on the @launchdarkly/ai-server release each package
needs ([#119](#119))
([9c2a56b](9c2a56b))


### Dependencies

* The following workspace dependencies were updated
  * dependencies
    * @launchdarkly/ai-server bumped from ^0.3.0 to ^0.4.0
</details>

<details><summary>@launchdarkly/ai-openai-agents: 0.4.0</summary>

##
[0.4.0](https://github.com/launchdarkly/js-ai-sdk/compare/@launchdarkly/ai-openai-agents-0.3.0...@launchdarkly/ai-openai-agents-0.4.0)
(2026-10-09)


### Features

* **AIC-3210:** support streaming responses from agent graph nodes
([#84](#84))
([24671e8](24671e8))
* **AIC-3211:** record each graph node as its own tracking event
([#91](#91))
([2df2923](2df2923))
* **AIC-3495:** record which public helpers an application calls
([#115](#115))
([aeb4019](aeb4019))
* **build:** build the AI SDK like the other LaunchDarkly JS SDKs so
CommonJS apps can load it (2/4)
([#77](#77))
([2cda755](2cda755))


### Bug Fixes

* **deps:** depend on the @launchdarkly/ai-server release each package
needs ([#119](#119))
([9c2a56b](9c2a56b))
* **handlers:** camelize snake_case model.parameters for framework
handlers
([75c6287](75c6287))
* **langchain:** stop forwarding additionalModelRequestFields to
ChatBedrockConverse
([f6268fd](f6268fd))
* **openai-agents:** forward only an allowlist of ModelSettings fields
from model.parameters
([398e935](398e935))
* pass AI Config model parameters through to every provider handler
([#73](#73))
([993225f](993225f))
* **telemetry:** report OpenAI tool arguments as an object, not a JSON
string ([#68](#68))
([d614791](d614791))


### Dependencies

* The following workspace dependencies were updated
  * dependencies
    * @launchdarkly/ai-server bumped from ^0.3.0 to ^0.4.0
</details>

<details><summary>@launchdarkly/ai-openai-messages: 0.4.0</summary>

##
[0.4.0](https://github.com/launchdarkly/js-ai-sdk/compare/@launchdarkly/ai-openai-messages-0.3.0...@launchdarkly/ai-openai-messages-0.4.0)
(2026-10-09)


### Features

* **AIC-3495:** record which public helpers an application calls
([#115](#115))
([aeb4019](aeb4019))
* **build:** build the AI SDK like the other LaunchDarkly JS SDKs so
CommonJS apps can load it (2/4)
([#77](#77))
([2cda755](2cda755))
* **openai-messages:** forward only Responses API keys from
model.parameters
([5c51dc9](5c51dc9))


### Bug Fixes

* **deps:** depend on the @launchdarkly/ai-server release each package
needs ([#119](#119))
([9c2a56b](9c2a56b))
* **handlers:** camelize snake_case model.parameters for framework
handlers
([75c6287](75c6287))
* **langchain:** stop forwarding additionalModelRequestFields to
ChatBedrockConverse
([f6268fd](f6268fd))
* **openai-messages:** only exclude model.parameters keys that would
break the handler
([39e342d](39e342d))
* pass AI Config model parameters through to every provider handler
([#73](#73))
([993225f](993225f))
* **SEC-9507:** authorize message-handler tools from the active
variation ([#90](#90))
([c16fe93](c16fe93))
* **telemetry:** report OpenAI tool arguments as an object, not a JSON
string ([#68](#68))
([d614791](d614791))


### Dependencies

* The following workspace dependencies were updated
  * dependencies
    * @launchdarkly/ai-server bumped from ^0.3.0 to ^0.4.0
</details>

<details><summary>@launchdarkly/ai-otel: 0.2.0</summary>

##
[0.2.0](https://github.com/launchdarkly/js-ai-sdk/compare/@launchdarkly/ai-otel-0.1.1...@launchdarkly/ai-otel-0.2.0)
(2026-10-09)


### Features

* **build:** build the AI SDK like the other LaunchDarkly JS SDKs so
CommonJS apps can load it (2/4)
([#77](#77))
([2cda755](2cda755))
* **build:** dual ESM/CommonJS output via tsup
([d8e62e4](d8e62e4))
</details>

<details><summary>@launchdarkly/ai-server: 0.4.0</summary>

##
[0.4.0](https://github.com/launchdarkly/js-ai-sdk/compare/@launchdarkly/ai-server-0.3.0...@launchdarkly/ai-server-0.4.0)
(2026-10-09)


### Experimental

* **skills:** add experimental Agent Skills under
`@launchdarkly/ai-server/experimental`, re-exported from
`@launchdarkly/ai-node/experimental`
([a242659](a242659))


### Features

* **AIC-3210:** support streaming responses from agent graph nodes
([#84](#84))
([24671e8](24671e8))
* **AIC-3211:** record each graph node as its own tracking event
([#91](#91))
([2df2923](2df2923))
* **AIC-3416:** say the real reason when a bundler breaks an optional
import [3/4]
([#78](#78))
([7ee4d5a](7ee4d5a))
* **AIC-3495:** record which public helpers an application calls
([#115](#115))
([aeb4019](aeb4019))
* **build:** build the AI SDK like the other LaunchDarkly JS SDKs so
CommonJS apps can load it (2/4)
([#77](#77))
([2cda755](2cda755))
* **client:** `initClient(client, options)` accepts telemetry options
for a pre-initialized client
([a242659](a242659))
* **client:** restore pickForwardedModelParameters for closed provider
request types
([f7f85d0](f7f85d0))
* **client:** warn about unrecognized `initClient` options instead of
ignoring them silently
([a242659](a242659))


### Bug Fixes

* **client:** `shutdownTelemetry()` releases the OpenTelemetry globals
the SDK registered, so a later `initClient` exports spans again
([a242659](a242659))
* **client:** a repeat `initClient(client)` returns the client already
set instead of replacing it
([a242659](a242659))
* **client:** an `initClient` that fails, or that `shutdown()`
interrupts, closes the client it built, and a failure is no longer
cached, so the next call retries
([a242659](a242659))
* **handlers:** camelize snake_case model.parameters for framework
handlers
([75c6287](75c6287))
* **judges:** a judge's own config must not fail the run it grades
([#17](#17))
([6e85cac](6e85cac))
* pass AI Config model parameters through to every provider handler
([#73](#73))
([993225f](993225f))
* **vercel-messages:** map model.parameters onto AI SDK call settings
([d2a52ae](d2a52ae))
* **vercel:** tag vercelEvaluate spans with run identity
([#100](#100))
([d32bede](d32bede))
</details>

<details><summary>@launchdarkly/ai-vercel-agents: 0.2.0</summary>

##
[0.2.0](https://github.com/launchdarkly/js-ai-sdk/compare/@launchdarkly/ai-vercel-agents-0.1.0...@launchdarkly/ai-vercel-agents-0.2.0)
(2026-10-09)


### Features

* **AIC-3408:** add Vercel AI SDK messages and agents adapters
([#83](#83))
([7d08c9c](7d08c9c))
* **AIC-3495:** record which public helpers an application calls
([#115](#115))
([aeb4019](aeb4019))


### Bug Fixes

* **deps:** depend on the @launchdarkly/ai-server release each package
needs ([#119](#119))
([9c2a56b](9c2a56b))
* **langchain:** stop forwarding additionalModelRequestFields to
ChatBedrockConverse
([f6268fd](f6268fd))
* pass AI Config model parameters through to every provider handler
([#73](#73))
([993225f](993225f))
* **vercel-agents:** map model.parameters onto ToolLoopAgent call
settings
([4241d75](4241d75))
* **vercel:** never forward headers, providerOptions, maxRetries or
timeout from model.parameters
([0a616fb](0a616fb))
* **vercel:** send image content as AI SDK file parts
([#99](#99))
([2856abc](2856abc))


### Dependencies

* The following workspace dependencies were updated
  * dependencies
    * @launchdarkly/ai-server bumped from ^0.3.0 to ^0.4.0
</details>

<details><summary>@launchdarkly/ai-vercel-messages: 0.2.0</summary>

##
[0.2.0](https://github.com/launchdarkly/js-ai-sdk/compare/@launchdarkly/ai-vercel-messages-0.1.0...@launchdarkly/ai-vercel-messages-0.2.0)
(2026-10-09)


### Features

* **AIC-3408:** add Vercel AI SDK messages and agents adapters
([#83](#83))
([7d08c9c](7d08c9c))
* **AIC-3495:** record which public helpers an application calls
([#115](#115))
([aeb4019](aeb4019))


### Bug Fixes

* **deps:** depend on the @launchdarkly/ai-server release each package
needs ([#119](#119))
([9c2a56b](9c2a56b))
* **langchain:** stop forwarding additionalModelRequestFields to
ChatBedrockConverse
([f6268fd](f6268fd))
* pass AI Config model parameters through to every provider handler
([#73](#73))
([993225f](993225f))
* **vercel-messages:** map model.parameters onto AI SDK call settings
([d2a52ae](d2a52ae))
* **vercel:** never forward headers, providerOptions, maxRetries or
timeout from model.parameters
([0a616fb](0a616fb))
* **vercel:** send image content as AI SDK file parts
([#99](#99))
([2856abc](2856abc))
* **vercel:** tag vercelEvaluate spans with run identity
([#100](#100))
([d32bede](d32bede))


### Dependencies

* The following workspace dependencies were updated
  * dependencies
    * @launchdarkly/ai-server bumped from ^0.3.0 to ^0.4.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**
> **Release Please monorepo cut** that bumps published versions in
`.release-please-manifest.json`, each package’s `package.json` /
`src/version.ts`, and adds matching **CHANGELOG** sections (including
first changelogs for the Vercel packages). Provider packages now depend
on **`@launchdarkly/ai-server` ^0.4.0**.
> 
> The documented release for **`@launchdarkly/ai-server` 0.4.0** (and
aligned provider bumps) includes **dual ESM + CommonJS builds**, **agent
graph node streaming**, **per-node tracking events** and **public-helper
usage telemetry**, **`initClient` / `shutdownTelemetry` lifecycle
fixes**, and **experimental Agent Skills** under
`@launchdarkly/ai-server/experimental`. Cross-cutting fixes in the notes
cover **AI Config `model.parameters` forwarding** (allowlists,
snake_case → camelCase), **message-handler tool authorization
(SEC-9507)**, and **Vercel adapter** parameter mapping and multimodal
content handling.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
4acc72c. 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