Repository navigation
fix: pass AI Config model parameters through to every provider handler - #73
Conversation
|
bugbot run |
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>
6a1c4cf to
1f14632
Compare
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>
… forward it verbatim Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
bugbot run |
…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>
|
bugbot run |
jeffdupont
left a comment
There was a problem hiding this comment.
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_keybecomesapiKey, which ChatOpenAI and ChatAnthropic use.anthropic_api_url,configuration,client_options,timeout,max_retriesand Bedrockcredentials/region/endpoint_hostalso 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,executableandallowDangerouslySkipPermissionscan 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_tokensstays snake_case, but the Claude Agent SDK readsbudgetTokens, so the thinking budget is probably ignored. The same goes forcontext_management[].compact_thresholdin 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,normalizeModelParametersandpickForwardedModelParametersare 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.
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>
|
bugbot run |
jeffdupont
left a comment
There was a problem hiding this comment.
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:
-
Claude Agents spreads the whole bag into
query()options (thread 1, GA blocker). No change.claude-agents/src/handler.ts:997and:1124still passcamelizeModelParameters(normalizeModelParameters(...))with no allowlist, socwd,env,permissionMode,pathToClaudeCodeExecutableandallowDangerouslySkipPermissionscan all come from a config. -
LangChain constructor args (thread 2). No change.
langchain-messages/src/handler.ts:175, and the same in langchain-agents, still spread the camelized bag, soapi_key→apiKey,anthropic_api_urlandclient_optionsreach the constructors. -
OpenAI Agents forwards
extraHeaders/extraBody/extraQuery. Bugbot flagged this on the current head (openai-agents/src/handler.ts:510):buildModelSettingscamelizes the whole bag intoModelSettings, so a config can inject request headers or body fields on every model call. Same class as the Vercelheadersbug; 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.
…ard connection or credential keys
…andler allowlists
|
bugbot 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.
|
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 532a0ee. Configure here.
`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>
🤖 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>
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.parameterscomes 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.max_turns,top_p), the providers' own names and what the UI writes.thinking.budget_tokensasbudgetTokens, and OpenAI Agents getscontext_management[].compact_thresholdascompactThreshold.max_tokenstomaxOutputTokens.headersandproviderOptionsare not forwarded: call-level headers replace the provider'sAuthorizationheader, and the AI Gateway reads credentials and routing fromproviderOptions.vercelEvaluateno longer readsmodel.parameters.maxTurns, as the Python native graphs do.🤖 Generated with Claude Code
Note
Overview
AI Config
model.parametersare 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.effortand Chat Completions token limits → Responsesmax_output_tokens. Agent/LangChain/Vercel paths camelize top-level keys (max_turns→maxTurns) and map nested shapes where SDKs differ (e.g. Claude Agentsthinking.budget_tokens→budgetTokens, OpenAI AgentsmaxTurnsonRunner.run). Native graphs forward each node’s settings like the Python SDK.Docs warn integrators not to spread
config.model.parametersinto LangChain constructors. Coverage addsnever-forwardedfixtures, extensive unit tests, and wire-level tests with real SDK + stubbedfetch.Reviewed by Cursor Bugbot for commit 532a0ee. Bugbot is set up for automated code reviews on this repo. Configure here.