feat(ai): builtin ai/provider packages + @spec desugar for LLM functions - #4352
Conversation
Flue-style user-facing BEP in _plan/pages (introduction, guides, examples, advanced, appendix), a compiling+tested reference implementation in _plan/ai_agents (typed Session<T, X>, policies, journal, 30 offline tests, live OpenAI loop), and reference_notes.md recording toolchain bugs and spec issues found while building it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Move the BEP phase-1 reference implementation from _planv2/baml_src into builtin stdlib packages (ai, openai, anthropic, google, claude_code, mcp), reachable from any project without a root. prefix. Compiler: an LLM function with a backtick prompt and a "provider/model" client string gets a synthesized <Fn>$spec companion; `Fn@spec(args)` is new postfix sugar for it and builds the bound, unrun ai.FunctionSpec<Out>. A new `tools: [fn, ...]` field switches the function's direct call to the ai runner (ai.Agent<Out>.new(client = client).run(Fn@spec(...)).value with a compiler-injected ai.Client? override param) and opts out of the legacy baml.llm companions. FunctionSpec.default_client is a lazy thunk so spec construction never touches credentials. MIR lower_lambda now disambiguates same-span lambda scopes by owner (mirrors build_tagged_body_closure), fixing capture resolution for companions that share the parent's source ranges. _planv2/baml_src keeps fixtures/tests/how-tos/live smokes, rewritten against the builtins and PlanTrip@spec. Offline: 25/25 pass. Live smokes pass for openai (default-client path), anthropic, google, claude_code, mcp_tools, and dynamic MCP attach. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The whole-program codegen snapshots (test_06_codegen, baml_src bytecode) list every stdlib function, so the six new packages churn all of them; the diffs are the new ai/provider/mcp listings plus block reordering, with no user-code changes. New per-namespace snapshots appear for ai.errors / ai.internal, and prompt_tag_runtime gains the PtRenderPerson $spec companion. One diagnostics snapshot picks up the new "'client', 'tools' and 'prompt'" wording. baml_fmt learns the LLM-body `tools` field (any field order, canonical client/tools/prompt output); `@spec` prints verbatim via the existing unknown-expression fallback. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The accessor truncated `client openai/gpt-4o-mini` to its first WORD, so the unquoted form resolved the provider prefix alone — previously a guaranteed E0003, and with the openai builtin package now a silent misresolution. Concatenating the value tokens makes the unquoted shorthand behave exactly like the quoted form; the shorthand_clients LSP fixture now expects no diagnostics. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
⏭️ Performance benchmarks were skippedPerf benchmarks (CodSpeed) are opt-in on pull requests — they no longer run on every push. They always run automatically after merge to To run them on this PR, do any of the following, then push a commit (or re-run CI):
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe pull request adds typed AI runtimes, provider clients, MCP support, compiler support for ChangesAI agent runtime
Estimated code review effort: 5 (Critical) | ~180 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Note
Due to the large number of review comments, Critical severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
baml_language/crates/baml_compiler_parser/src/parser.rs (1)
4241-4253: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winDo not classify
client(...)as an LLM directive.
clientlexes asTokenKind::Client. Line 4244 acceptsclient(...)as an LLM directive because the excluded-token set does not includeTokenKind::LParen. A regular function body that calls a parameter namedclientthen enters LLM-body parsing and fails.Add
TokenKind::LParento this excluded-token set. Add a parser unit test for a regular body containingclient(...).Proposed fix
TokenKind::Dot | TokenKind::Equals | TokenKind::Comma | TokenKind::RParen + | TokenKind::LParenAs per coding guidelines, “
**/*.rs: Prefer writing Rust unit tests over integration tests where possible”.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/crates/baml_compiler_parser/src/parser.rs` around lines 4241 - 4253, The LLM-directive detection around TokenKind::Client incorrectly classifies regular client(...) calls because LParen is not excluded. Add TokenKind::LParen to the matches exclusion in the relevant parser logic, and add a Rust parser unit test covering a regular body that invokes client(...).Source: Coding guidelines
baml_language/crates/baml_fmt/src/ast/declarations.rs (1)
739-745: 🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick winThe formatter drops the
toolsfield.
LlmFunctionBody::printemitsclient,prompt, andtype_builder, but neverself.tools. Formatting a function that declarestoolsdeletes the field from the source. The deletion is not cosmetic:lower_functionkeys spec mode on the presence oftools, so the formatted file compiles down the legacybaml.llmpath instead of theai.Agentpath. The struct doc on Line 656 states the canonical order is client, tools, prompt, so print must include it.Add a formatter snapshot for an LLM function with
toolsso the round trip is covered.🐛 Proposed fix to print the `tools` field
+ if let Some(tools) = &self.tools { + printer.print_standalone_with_trivia(tools, inner_indent); + printer.print_newline(); + } + printer.print_standalone_with_trivia(&self.prompt, inner_indent); printer.print_newline();🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/crates/baml_fmt/src/ast/declarations.rs` around lines 739 - 745, Update LlmFunctionBody::print to emit self.tools between self.client and self.prompt, preserving the documented canonical order and existing trivia/newline handling. Add a formatter snapshot covering an LLM function declaring tools to verify the field survives formatting and round trips correctly.
🟠 Major comments (32)
baml_language/crates/sys_ops/src/lib.rs-2168-2180 (1)
2168-2180: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winWire the
start_processoperation and process classes throughwith_sys_instance.
with_sys_instancerewires onlybaml_sys_exec,baml_sys_shell, andbaml_sys_sleep. A customIoNamespaceSysinstalled this way still usesDefaultIoOps::baml_sys_exec, and the newIoClassSysProcess/IoClassSysProcessLineStreamclasses are missing from sys glue andSysOpsfields, sobaml.sys.start_process(...)stays unreachable/unsupported under the builder.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/crates/sys_ops/src/lib.rs` around lines 2168 - 2180, Update with_sys_instance and the related sys glue to route start_process through the configured IoNamespaceSys instead of DefaultIoOps::start_process. Add IoClassSysProcess and IoClassSysProcessLineStream to the sys glue and corresponding SysOps fields, then wire their operations so baml.sys.start_process(...) works with custom instances.baml_language/_planv2/pages/02_guides/01_functions/03_calling_functions.md-18-31 (1)
18-31: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winRemove the stale “not implemented” caveat.
The PR objective states that direct-call lowering through
ai.Agentis implemented. Lines 18-27 document that lowering, but Lines 30-31 tell users to expand calls manually. Delete or update the caveat so the guide matches the compiler behavior.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/_planv2/pages/02_guides/01_functions/03_calling_functions.md` around lines 18 - 31, The calling-functions guide still says direct-call lowering is not implemented; remove or revise the caveat following the `PlanTrip` desugared example so it states that plain calls are lowered automatically and no manual expansion is required.baml_language/_plan/pages/02_guides/02_sessions.md-114-120 (1)
114-120: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winDo not send the first message twice.
In the no-snapshot branch,
msgbecomestrip_requestand is then sent again at Line 120. The initial prompt and the transcript therefore contain the same input. Use either the function argument orsend()for the first turn.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/_plan/pages/02_guides/02_sessions.md` around lines 114 - 120, Update handle_turn so the no-snapshot branch does not pass msg as trip_request and then send it again; initialize the session without the first-turn message in that branch, while preserving the existing send(msg) call and snapshot-resume behavior.baml_language/_planv2/pages/02_guides/01_functions/02_tools.md-61-100 (1)
61-100: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winQualify tool-error behavior by
on_errormode.The guide says validation failures and thrown tool errors are never application exceptions, but later says
Raisemakes a tool failure throwToolFailedError. State explicitly thatReportconverts failures to tool results, whileRaisepropagates them after journaling. Clarify whether invalid arguments follow the same mode.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/_planv2/pages/02_guides/01_functions/02_tools.md` around lines 61 - 100, Update the “Argument validation,” “Tool errors are data,” and “Tool failure policy” sections to qualify behavior by on_error mode: Report converts validation and tool failures into tool results for the model, while Raise journals the failure and propagates ToolFailedError. Explicitly state whether invalid arguments follow this same Report/Raise behavior, without changing the documented default.baml_language/_plan/pages/02_guides/02_sessions.md-31-46 (1)
31-46: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winUse one
send()and journal-timing contract.
Session.sendqueues data, whileSession.runappends the corresponding event to the journal. The two guides currently describe queue admission and journal append as immediate and policy-controlled.
baml_language/_plan/pages/02_guides/02_sessions.md#L31-L46: document thatsend()queues andrun()journals before policy processing.baml_language/_plan/pages/02_guides/04_steering.md#L27-L35: distinguish queue admission, journal append, and model injection.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/_plan/pages/02_guides/02_sessions.md` around lines 31 - 46, The session documentation must consistently describe the timing contract: in baml_language/_plan/pages/02_guides/02_sessions.md lines 31-46, update the send flow to state that Session.send queues the message and Session.run appends the corresponding event to the journal before policy processing, rather than implying immediate or policy-controlled journaling; in baml_language/_plan/pages/04_steering.md lines 27-35, explicitly distinguish queue admission, journal append, and model injection while preserving their respective timing and policy behavior.baml_language/_plan/pages/02_guides/02_sessions.md-95-109 (1)
95-109: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy liftDefine one snapshot and resume-policy contract.
The sessions guide presents
$resumeas sufficient by itself, while the configuration guide requires the caller to provide$policyfor resumed sessions.
baml_language/_plan/pages/02_guides/02_sessions.md#L95-L109: show$policywhen required, or qualify the claim that the snapshot contains everything needed.baml_language/_plan/pages/02_guides/03_configuration.md#L75-L81: state whether the default policy is restored automatically and whether custom policies require$policy.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/_plan/pages/02_guides/02_sessions.md` around lines 95 - 109, Define one consistent snapshot/resume policy contract across both guides: in baml_language/_plan/pages/02_guides/02_sessions.md lines 95-109, either include $policy in the resume example when required or qualify the statement that snapshots contain everything needed; in baml_language/_plan/pages/02_guides/03_configuration.md lines 75-81, explicitly state whether the default policy is restored automatically and when resumed sessions require $policy, especially for custom policies.baml_language/_plan/pages/02_guides/06_tools.md-52-58 (1)
52-58: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winRespect
ToolErrorModein the error guarantee.At Lines 52-58, the page says a tool throw never crashes the agent and always becomes a tool result.
baml_language/_planv2/pages/02_guides/02_specs_and_runners/02_the_default_runner.mdstates thatRaisemode throwsToolFailedErrorafter recordingToolFailed. Qualify this recipe asReportmode or document both modes.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/_plan/pages/02_guides/06_tools.md` around lines 52 - 58, Update the tool-error guidance in the page’s “throw inside a tool” section to qualify the behavior by ToolErrorMode: document that Report mode converts failures into tool results, while Raise mode propagates ToolFailedError after recording the failure. Keep the recommendation to handle total failure at the agent call site.baml_language/_planv2/pages/02_guides/02_specs_and_runners/01_specs.md-28-31 (1)
28-31: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winDocument lazy default-client resolution consistently.
The pages describe eager resolution, but the PR objective states that
FunctionSpec.default_clientis lazy.
baml_language/_planv2/pages/02_guides/02_specs_and_runners/01_specs.md#L28-L31: describe the default client as lazy rather than resolved during spec creation.baml_language/_planv2/pages/02_guides/02_specs_and_runners/01_specs.md#L44-L52: document whendefault_clientresolves and when credential errors occur.baml_language/_planv2/pages/02_guides/03_clients/01_choosing_a_model.md#L41-L42: remove the eager-validation claim.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/_planv2/pages/02_guides/02_specs_and_runners/01_specs.md` around lines 28 - 31, Update baml_language/_planv2/pages/02_guides/02_specs_and_runners/01_specs.md lines 28-31 to describe FunctionSpec.default_client as lazy rather than resolved during spec creation; update lines 44-52 to state when it resolves and when credential errors occur; update baml_language/_planv2/pages/02_guides/03_clients/01_choosing_a_model.md lines 41-42 to remove the claim that client validation is eager.baml_language/_plan/pages/02_guides/05_models.md-39-50 (1)
39-50: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy liftThe documentation trees define incompatible
Clientinterfaces.Choose the phase-1 public contract and update every affected statement.
baml_language/_plan/pages/02_guides/05_models.md#L39-L50: align the interface declaration with the selected contract.baml_language/_plan/pages/02_guides/05_models.md#L100-L105: align the implementation instructions with the selected contract.baml_language/_planv2/pages/02_guides/03_clients/02_the_client_interface.md#L5-L10: align the public interface declaration with the selected contract.baml_language/_planv2/pages/02_guides/03_clients/02_the_client_interface.md#L26-L30: align the rendering and parsing visibility statement with the selected contract.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/_plan/pages/02_guides/05_models.md` around lines 39 - 50, Adopt one phase-1 public Client contract consistently across all affected documentation. In baml_language/_plan/pages/02_guides/05_models.md lines 39-50, update the Client interface; in lines 100-105, align the implementation instructions. In baml_language/_planv2/pages/02_guides/03_clients/02_the_client_interface.md lines 5-10, update the public interface declaration, and in lines 26-30, make the rendering and parsing visibility statement match that same contract.baml_language/_plan/pages/02_guides/10_policies.md-119-135 (1)
119-135: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy liftKeep refolded policy state in
SessionStateor derive it from the journal.The refold contract rebuilds
SessionState, but these examples keep replay-sensitive state on the policy object. A resume or policy replacement can retain stale values or fail to restore them.
baml_language/_plan/pages/02_guides/10_policies.md#L119-L135: Store the accumulated cost inSessionState, or derive it fromJournal, instead of mutatingself.spent.baml_language/_plan/pages/02_guides/10_policies.md#L145-L170: Store held tool calls inSessionState, or rebuild them deterministically from journal entries, instead of mutatingself.held.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/_plan/pages/02_guides/10_policies.md` around lines 119 - 135, The policy examples must keep replay-sensitive state in SessionState or derive it deterministically from Journal. In baml_language/_plan/pages/02_guides/10_policies.md lines 119-135, update WithBudget so accumulated cost is not mutated in self.spent; in lines 145-170, update the held-tool-call policy so state is not mutated in self.held. Ensure both values are restored correctly during refolding, resume, and policy replacement.baml_language/_plan/pages/02_guides/10_policies.md-155-158 (1)
155-158: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winConsume a held tool call when its approval is processed.
Line 156 returns the held
RunToolwithout removing it. A secondPermissionGrantedevent with the samecall_idemits the same command again. The runner then executes the tool again.Remove the held entry for both approval outcomes before returning. Ignore later approval events for that
call_id.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/_plan/pages/02_guides/10_policies.md` around lines 155 - 158, Update the PermissionGranted and PermissionDenied handling to consume and remove the held entry for the event’s call_id before producing the command. Ensure a later approval for the same call_id finds no held tool and emits no duplicate execution, while preserving the existing denied failure command behavior.baml_language/_planv2/baml_src/tests/loop.baml-101-126 (1)
101-126: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winProvide a third tool-use turn before expecting
StepBudgetExceeded.The initial model call is uncounted. After the second tool turn, the loop has
steps == 2and issues a third model call.ScriptedClientthen throws its out-of-turnsInvalidArgumenterror instead of the expected budget error.Keep
max_steps = 2and add a third tool-use turn. The next call attempt will then exceed the budget.Proposed test fix
ai.ModelTurn { content: [ai.ToolUse { id: "c2", name: "search_flights", args: call_args }], stop_reason: ai.StopReason.ToolUse, usage: null, }, + ai.ModelTurn { + content: [ai.ToolUse { id: "c3", name: "search_flights", args: call_args }], + stop_reason: ai.StopReason.ToolUse, + usage: null, + },🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/_planv2/baml_src/tests/loop.baml` around lines 101 - 126, Add a third ai.ModelTurn with ToolUse to the scripted client in the “step budget throws typed” test, keeping max_steps = 2 and the existing tool-use pattern. This ensures the third model call is available and the following call attempt produces ai.errors.StepBudgetExceeded instead of ScriptedClient’s out-of-turns error.baml_language/_planv2/pages/04_reference/01_api.md-138-173 (1)
138-173: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winKeep phase-2 media APIs out of the phase-1 reference.
The implemented
ContentBlockunion hasText,Reasoning, andToolUseonly.Promptprovidesrender_text; it does not providerenderorInstructionPart[]. The future-phases page also assigns media outputs to phase 2.
baml_language/_planv2/pages/04_reference/01_api.md#L138-L173: RemoveMedia,InstructionPart, andPrompt.renderfrom the phase-1 API reference.baml_language/_planv2/pages/04_reference/02_events.md#L44-L44: RemoveMedialowering from theAssistantMessagerendering rule.baml_language/_planv2/readme.md#L80-L86: Remove media instruction parts andMediafrom the phase-1 API tree.Based on the upstream
ai/journal.bamlandai/spec.bamlcontracts, these APIs are not implemented in phase 1.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/_planv2/pages/04_reference/01_api.md` around lines 138 - 173, Remove phase-2 media APIs from the phase-1 reference: in baml_language/_planv2/pages/04_reference/01_api.md lines 138-173, remove Media, InstructionPart, and Prompt.render while retaining the implemented Text, Reasoning, ToolUse, and Prompt.render_text contracts; in baml_language/_planv2/pages/04_reference/02_events.md line 44, remove Media lowering from the AssistantMessage rendering rule; and in baml_language/_planv2/readme.md lines 80-86, remove media instruction parts and Media from the phase-1 API tree.baml_language/_planv2/publish.py-139-140 (1)
139-140: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winSet a timeout for the publish request.
urlopen(req)can block indefinitely when the server or network stalls. Pass a finite timeout so the publishing workflow fails predictably.Proposed fix
- with urllib.request.urlopen(req) as resp: + with urllib.request.urlopen(req, timeout=30) as resp:🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/_planv2/publish.py` around lines 139 - 140, Update the urllib.request.urlopen call in the publish request flow to pass a finite timeout value, ensuring stalled network operations fail predictably while preserving the existing response decoding and printing behavior.baml_language/_planv2/pages/04_reference/01_api.md-30-44 (1)
30-44: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winDocument
default_clientas a lazy resolver.
FunctionSpec.default_clientis a() -> Client throws unknownthunk. It is not an eagerly resolvedClient.Update the field type and state that spec creation does not access credentials or environment variables. The current reference tells callers to expect failures at spec creation, but the implementation defers them until the runner resolves the client.
Based on the upstream
baml_language/crates/baml_builtins2/baml_std/ai/spec.bamlcontract,default_clientis lazy.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/_planv2/pages/04_reference/01_api.md` around lines 30 - 44, Update the FunctionSpec.default_client documentation to describe it as a lazy () -> Client throws unknown resolver rather than an eagerly resolved Client. State that spec creation does not access credentials or environment variables, and that unknown-prefix or missing-credential failures occur when the runner resolves the client.baml_language/_planv2/pages/02_guides/03_clients/04_reliability.md-133-134 (1)
133-134: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winRedact provider bodies before logging.
The example logs
raw_bodydirectly. A provider error body can contain prompt data, user data, or credentials. Log a bounded, redacted diagnostic, or require explicit opt-in for raw response logging.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/_planv2/pages/02_guides/03_clients/04_reliability.md` around lines 133 - 134, Update the error handling example around the provider rejection log to avoid emitting i.raw_body directly. Log only a bounded, redacted diagnostic by default, or gate raw response logging behind explicit opt-in, while preserving the existing status and throw behavior.baml_language/_planv2/pages/03_how_to/01_retry_a_failed_parse_with_feedback.md-31-38 (1)
31-38: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winCommit the complete event set for failed attempts.
The journal contract records a
Usageevent for each model turn. This branch appends onlyAssistantMessageandUserMessage, so failed attempts are missing from usage totals and event callbacks. Append the same events as the default runner, or narrow the helper's documented contract.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/_planv2/pages/03_how_to/01_retry_a_failed_parse_with_feedback.md` around lines 31 - 38, Update the failed-attempt branch in turn_with_feedback to append the complete event set for the model turn, including its Usage event, matching the default runner’s journal and callback behavior. Preserve the existing AssistantMessage, correction UserMessage, and retry flow while ensuring usage totals include failed attempts.baml_language/_planv2/pages/02_guides/04_the_journal.md-28-35 (1)
28-35: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winResolve the
UserMessagepersistence contradiction.Lines [16-22] say correction requests are committed events, and
baml_language/_planv2/pages/03_how_to/01_retry_a_failed_parse_with_feedback.mdappends the correction request to the journal. The table says parse repair usesUserMessageephemerally. For the documented implementation, state that parse repair appendsUserMessageso replay and observers see the correction.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/_planv2/pages/02_guides/04_the_journal.md` around lines 28 - 35, Update the UserMessage entry in the journal event table to state that parse-repair correction requests are appended as committed journal events, while preserving the existing custom-runner behavior. Ensure the description reflects that replay and observers can see these correction messages rather than treating them as ephemeral.baml_language/_planv2/pages/03_how_to/04_observe_a_run_with_on_event.md-29-35 (1)
29-35: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winDo not log raw tool outputs by default.
The callback logs
ToolCompleted.outputdirectly at Line [33]. Tool results can contain user data, secrets, or large payloads. Redact or truncate the output, or mark raw logging as an explicit opt-in.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/_planv2/pages/03_how_to/04_observe_a_run_with_on_event.md` around lines 29 - 35, Update log_event’s ToolCompleted branch so it does not log done.output raw by default; apply the project’s existing redaction or truncation utility, or require an explicit opt-in before including raw output, while preserving the completion event and tool ID in the log.baml_language/_planv2/pages/03_how_to/01_retry_a_failed_parse_with_feedback.md-21-28 (1)
21-28: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy liftHandle tool turns before parsing terminal text.
c.invokecan return aModelTurnwithStopReason.ToolUse. In that case,terminal_text()is null, so this helper parses an empty string and sends a correction request instead of executing the tools. Add the tool-dispatch loop, or reject specs with tools before invoking the client.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/_planv2/pages/03_how_to/01_retry_a_failed_parse_with_feedback.md` around lines 21 - 28, Handle tool-use ModelTurn results from c.invoke before calling baml.sap.parse<Out>; either dispatch the returned tools and continue the invocation loop until a terminal response is available, or reject specs with configured tools before invoking the client. Ensure terminal_text() is parsed only after tool turns are handled, rather than treating null as an empty candidate.baml_language/_planv2/pages/02_guides/03_clients/04_reliability.md-49-53 (1)
49-53: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winUse the constructors shown by the runtime fixtures.
The supplied reliability fixture uses
ai.Retry.new(...),ai.Backoff.new(...), andai.Fallback { ... }. This page usesai.clients.Retry, an unqualifiedBackoff, and a different constructor shape. A copied example will not match the current public API. Align Lines [49-53] and [91-96] with the fixture syntax, or update both the fixture and runtime together.Also applies to: 91-96
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/_planv2/pages/02_guides/03_clients/04_reliability.md` around lines 49 - 53, Update the reliability guide examples around the Retry and fallback declarations to use the runtime fixture’s public constructors: ai.Retry.new(...), ai.Backoff.new(...), and the ai.Fallback { ... } shape. Apply the same syntax consistently to both referenced examples, or update the corresponding fixture and runtime API together if the guide is intended to define a new API.baml_language/_planv2/pages/02_guides/03_clients/05_the_built_in_clients.md-556-560 (1)
556-560: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy liftKeep retry safety consistent for state-changing Claude Code and MCP runs.
The documentation states both that all failures are
Safeand that state-changing tools can complete external effects before a failure whileUnknownclassification is not implemented. This permitsRetryorFallbackto replay non-idempotent actions.
baml_language/_planv2/pages/02_guides/03_clients/05_the_built_in_clients.md#L556-L560: implementUnknownclassification or disable retries for clients with state-changing harness or MCP tools.baml_language/_planv2/pages/02_guides/03_clients/04_reliability.md#L40-L42: qualify theSafestatement so it excludes clients that can perform external side effects.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/_planv2/pages/02_guides/03_clients/05_the_built_in_clients.md` around lines 556 - 560, Update baml_language/_planv2/pages/02_guides/03_clients/05_the_built_in_clients.md lines 556-560 to implement Unknown failure classification for clients with state-changing harness_tools or MCP tools, or explicitly disable Retry and Fallback for those clients; remove the contradictory “not yet implemented” behavior. Update baml_language/_planv2/pages/02_guides/03_clients/04_reliability.md lines 40-42 to qualify that failures are Safe only when no external side effects can occur.baml_language/_planv2/pages/03_how_to/06_use_mcp_tools_with_any_client.md-21-38 (1)
21-38: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winDo not document pending call desugaring as current executable syntax.
Both examples use a
tools:surface that the supplied context says the reference implementation does not yet support.
baml_language/_planv2/pages/03_how_to/06_use_mcp_tools_with_any_client.md#L21-L38: show the manualToolbox.new(conn.tools())form first, or label the expression form as planned.baml_language/_planv2/pages/03_how_to/05_attach_mcp_servers_to_claude_code.md#L76-L96: bindattach_mcp_tool(client)in the manual spec, or label[attach_mcp]as future syntax.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/_planv2/pages/03_how_to/06_use_mcp_tools_with_any_client.md` around lines 21 - 38, Update baml_language/_planv2/pages/03_how_to/06_use_mcp_tools_with_any_client.md lines 21-38 to show the manual Toolbox.new(conn.tools()) form first, or explicitly label the tools: expression as planned syntax; update baml_language/_planv2/pages/03_how_to/05_attach_mcp_servers_to_claude_code.md lines 76-96 to bind attach_mcp_tool(client) in the manual spec, or label [attach_mcp] as future syntax. Ensure neither example presents unsupported pending syntax as current executable code.baml_language/crates/baml_builtins2/baml_std/anthropic/ns_internal/messages.baml-192-202 (1)
192-202: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
base_urlredirects the API key to an arbitrary host.
base_urloverrides the host, and thex-api-keyheader is attached unconditionally. A caller that setsbase_urlto any other origin sends the Anthropic credential to that origin. The override is a legitimate feature for gateways and proxies, so the risk is a posture gap rather than an exploit. Two mitigations are worth considering: requirehttpsfor a non-defaultbase_url, and document thatbase_urlis a trusted-input field.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/crates/baml_builtins2/baml_std/anthropic/ns_internal/messages.baml` around lines 192 - 202, Update the request construction around base_url so non-default overrides are accepted only when they use HTTPS, while preserving the default Anthropic URL and existing gateway/proxy support. Validate the URL scheme before attaching the unconditional x-api-key header, and document base_url as requiring trusted input.baml_language/crates/baml_builtins2/baml_std/ai/tools.baml-15-28 (1)
15-28: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winThe comment promises schema validation that
calldoes not perform.Lines 15-17 state that the raw handler receives "the validated-by-schema argument map". The
raw_handlerbranch at Lines 26-28 returnsrh(args)directly. Nothing validatesargsagainstself.input_schema. The handler branch below does validate, becausereflect.call_anychecks the signature.Raw tools are the MCP and OpenAPI path, so the arguments come from model output and reach an external server unchecked. Either validate
argsagainstinput_schemabefore dispatch, or correct the comment to state that the raw handler owns validation.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/crates/baml_builtins2/baml_std/ai/tools.baml` around lines 15 - 28, Update the raw_handler path in Tool.call so args is validated against self.input_schema before dispatching to rh, preserving the documented validated-by-schema contract and rejecting invalid model-generated arguments before reaching external servers.baml_language/_plan/ai_agents/baml_src/ns_ai/session.baml-95-167 (1)
95-167: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winNothing bounds the number of model calls in the reference session. The runner delegates the entire step budget to the policy, and one shipped middleware emits
CallModelwithout charging a step. The two sites together allow an unbounded sequence of paid model calls.
baml_language/_plan/ai_agents/baml_src/ns_ai/session.baml#L95-L167: add a runner-side cap onCallModelexecutions insiderun(), so the loop terminates even when a policy never charges a step.baml_language/_plan/ai_agents/baml_src/ns_ai/policy.baml#L184-L194: route the steering flush through the inner policy instead of emitting a bareCallModel, soToolLoop.next_callincrementsst.stepsand enforcesmax_steps.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/_plan/ai_agents/baml_src/ns_ai/session.baml` around lines 95 - 167, The session runner in run() needs an independent cap on CallModel executions; add runner-side counting and termination when the configured model-call budget is exhausted, while preserving normal command processing. In baml_language/_plan/ai_agents/baml_src/ns_ai/session.baml lines 95-167, update the CallModel branch to enforce this bound. In baml_language/_plan/ai_agents/baml_src/ns_ai/policy.baml lines 184-194, route the steering flush through the inner policy’s next_call path instead of emitting a bare CallModel so ToolLoop.next_call increments st.steps and enforces max_steps.baml_language/crates/baml_builtins2/baml_std/ai/wrappers.baml-26-46 (1)
26-46: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winClamp the
retry_after_mshint tomax_ms.The hint path returns
hunchanged.max_msbounds only the computed backoff. A provider that returns a largeretry_after_mstherefore blocks the caller for that full duration, andBackoff.max_msgives no protection. The value comes from an external response, so it must be bounded. Also reject negative values.🐛 Proposed fix
if let h: int = hinted { - h + if (h < 0) { + 0 + } else if (h > self.max_ms) { + self.max_ms + } else { + h + } } else {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/crates/baml_builtins2/baml_std/ai/wrappers.baml` around lines 26 - 46, Update Backoff.delay_ms so the retry_after_ms hint is bounded by self.max_ms and never returns a negative delay. Preserve the computed backoff behavior for cases without a hint, and apply the same non-negative, maximum-bound contract to the hinted value.baml_language/crates/baml_builtins2/baml_std/ai/journal.baml-85-99 (1)
85-99: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
entries()returns the mutable backing list and breaks the append-only invariant.The comment at Line 94 states that the driving runner is the only writer.
entries()returnsself.logitself, so any caller can push events directly and bypassappend_all. The same aliasing also means a caller that holds the result sees later appends, which makes length-based scans (such as the futility scan inai/runner.baml) depend on mutation timing. Return a copy.🛡️ Proposed fix
function entries(self) -> Event[] { - self.log + self.log.map((e) -> { e }) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/crates/baml_builtins2/baml_std/ai/journal.baml` around lines 85 - 99, Update Journal.entries() to return a copy of self.log rather than the mutable backing list. Preserve append-only ownership through append_all() and ensure callers receive a snapshot that does not reflect later appends.baml_language/crates/baml_builtins2/baml_std/claude_code/ns_internal/cli.baml-277-296 (1)
277-296: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winUndefined stdin contract when
keep_stdin_openis unset. The newProcessOptions.keep_stdin_openoption decides whether a writable stdin pipe exists, but neither the declaration nor the call site defines the behavior ofwrite_stdinandclose_stdinwhen the option is absent. The Claude Code client depends on that undefined behavior.
baml_language/crates/baml_builtins2/baml_std/claude_code/ns_internal/cli.baml#L277-L296: setkeep_stdin_open: trueif the intent is to deliver EOF throughclose_stdin, or remove theclose_stdincall and itscatch_all.baml_language/crates/baml_builtins2/baml_std/baml/ns_sys/sys.baml#L89-L97: document whetherwrite_stdinandclose_stdinthrowroot.errors.Ioor act as no-ops whenkeep_stdin_openis absent orfalse, and state whether the child inherits the parent's stdin in that case.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/crates/baml_builtins2/baml_std/claude_code/ns_internal/cli.baml` around lines 277 - 296, The stdin contract is undefined when ProcessOptions.keep_stdin_open is absent or false. In baml_language/crates/baml_builtins2/baml_std/claude_code/ns_internal/cli.baml lines 277-296, set keep_stdin_open to true if the Claude Code client should deliver EOF via close_stdin; otherwise remove the close_stdin call and its catch_all. In baml_language/crates/baml_builtins2/baml_std/baml/ns_sys/sys.baml lines 89-97, document whether write_stdin and close_stdin throw root.errors.Io or no-op when stdin is not kept open, and whether the child inherits the parent stdin.baml_language/crates/baml_builtins2/baml_std/claude_code/ns_internal/cli.baml-99-104 (1)
99-104: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winEscape tool names and descriptions before embedding them in JSON text.
catalog.pushbuilds a JSON object by string interpolation.t.nameandt.descriptionare inserted directly between quotes. A description that contains",\, or a newline produces invalid JSON in the prompt. The model then cannot parse the catalog reliably. Build the object as a map and serialize it withbaml.json.stringify, as the code already does fort.input_schema.The same pattern appears at Lines 20-22, where
c.idandc.nameare interpolated into a JSON tool-call line.🛠️ Proposed fix for the catalog entry
for (let t in tb.list()) { - catalog.push( - `{"name": "${t.name}", "description": "${t.description}", "parameters": ${baml.json.stringify(baml.json.to_json(t.input_schema))}}`, - ); + let entry: map<string, unknown> = {}; + let _ = entry.set("name", t.name); + let _ = entry.set("description", t.description); + let _ = entry.set("parameters", t.input_schema); + catalog.push(baml.json.stringify(baml.json.to_json(entry))); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/crates/baml_builtins2/baml_std/claude_code/ns_internal/cli.baml` around lines 99 - 104, Update the catalog construction in the loop over tb.list() to build each entry as a map and serialize the complete object with baml.json.stringify, rather than interpolating t.name and t.description into JSON text; preserve the existing input_schema serialization. Apply the same change to the tool-call line using c.id and c.name so those values are safely escaped before embedding in JSON.baml_language/crates/baml_builtins2/baml_std/openai/ns_internal/responses.baml-148-154 (1)
148-154: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winGuard the tool-argument decode.
item.argumentsis model-generated text.baml.json.from_stringis called withoutcatch_all, so malformed arguments throw an untyped decode error out ofopenai_parseand out ofinvoke. The Gemini and Claude Code clients both guard the same decode and fall back to an empty map. Align the OpenAI client, or throwai.errors.ParseFailedso the runner sees a typed failure.🛠️ Proposed fix
let args: map<string, unknown> = {}; if let s: string = item.arguments { - args = baml.json.from_string<map<string, unknown>>(s); + args = baml.json.from_string<map<string, unknown>>(s) catch_all (e) { + _ => { + let empty: map<string, unknown> = {}; + empty + }, + }; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/crates/baml_builtins2/baml_std/openai/ns_internal/responses.baml` around lines 148 - 154, Guard the baml.json.from_string call in the openai_parse tool-argument handling for item.arguments, matching the Gemini and Claude Code clients by using catch_all and falling back to an empty map for malformed model-generated JSON, or convert failures into ai.errors.ParseFailed so invoke receives a typed error.baml_language/crates/baml_builtins2/baml_std/mcp/mcp.baml-71-83 (1)
71-83: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winClose the child process if the initialize handshake fails.
start_processsucceeds, thenconn._request("initialize", init)runs._requestthrows on a read failure, on stream EOF, or on a JSON-RPC error object._sendalso throws on a write failure. In every one of those paths the spawned server process is never closed, so the child leaks for the lifetime of the program. Wrap the handshake and close the process before rethrowing.🛠️ Proposed fix
let conn = McpConnection { server_name: name, p: p, next_id: 1 }; let init: map<string, unknown> = {}; let _ = init.set("protocolVersion", "2025-06-18"); let caps: map<string, unknown> = {}; let _ = init.set("capabilities", caps); let info: map<string, unknown> = {}; let _ = info.set("name", "baml-mcp"); let _ = info.set("version", "0.1"); let _ = init.set("clientInfo", info); - let _ = conn._request("initialize", init); - conn._send(rpc_line(null, "notifications/initialized", null)); + let _ = conn._request("initialize", init) catch_all (e) { + _ => { + conn.close(); + throw e + }, + }; + conn._send(rpc_line(null, "notifications/initialized", null)) catch_all (e) { + _ => { + conn.close(); + throw e + }, + }; conn🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/crates/baml_builtins2/baml_std/mcp/mcp.baml` around lines 71 - 83, Update the handshake flow in the McpConnection constructor/startup method around conn._request("initialize", init) and conn._send(...) to catch any failure from initialization or the initialized notification, close the spawned child process, then rethrow the original error; return conn unchanged only after both operations succeed.
Binary size checks passed✅ 7 passed
Generated by |
- sdkgen_java: escape method names that collide with java.lang.Object's final methods (wait/notify/notifyAll/getClass) with the same `$` suffix as keywords — `baml.sys.Process.wait` generated `wait()` which cannot override the final `Object.wait()`. Runtime binding keeps the BAML name. - markdown whitelist: allow the _plan/ and _planv2/ BEP planning trees (the validate-markdown hook checks all files once any .md changes). - size gate: update packed-program baselines (+~700 KB, the six new builtin stdlib packages' bytecode). - accept bytecode_format display snapshots (stdlib listing grew). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@baml_language/sdks/java/sdkgen_java/src/emit.rs`:
- Line 786: Update the accessor-name generation in render_class to use
java_method_identifier(...) for generated getter/setter method names, while
retaining java_identifier(...) for field and reference names. Add an emitter
unit test covering a property named wait (and the java.lang.Object final-method
names) to verify generated SDK code compiles without accessor conflicts.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 883daee3-aed9-441f-b2b4-17019850b7e6
⛔ Files ignored due to path filters (3)
baml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded_unoptimized.snapis excluded by!**/*.snapbaml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_textual.snapis excluded by!**/*.snap
📒 Files selected for processing (6)
baml_language/.ci/size-gate/aarch64-apple-darwin.tomlbaml_language/.ci/size-gate/x86_64-pc-windows-msvc.tomlbaml_language/.ci/size-gate/x86_64-unknown-linux-gnu.tomlbaml_language/.markdown-whitelistbaml_language/sdks/java/sdkgen_java/src/emit.rsbaml_language/sdks/java/sdkgen_java/src/routing.rs
What
Ships the BEP phase-1 AI runtime as builtin stdlib packages and implements the
@specdesugar for LLM functions.Builtin packages
The
_planv2/baml_srcreference implementation moves intocrates/baml_builtins2/baml_std/as six root packages, reachable from any project with noroot.prefix and no files to copy:ai— the public surface:FunctionSpec,Prompt,Tool/Toolbox,Journal+ events,ModelTurn/Client, theAgentrunner,Retry/Fallbackwrappers,ai.errorsopenai,anthropic,google— provider clients (Responses / Messages / generateContent)claude_code— the Claude Code CLI as a harness clientmcp— MCP servers as ordinary journaled toolsTwo intentional changes landed during the move:
FunctionSpec.default_clientis now a lazy thunk (() -> Client throws unknown) so building a spec never touches credentials, andAgent.runselects the client viamatchon the nullable override.@specdesugarAn LLM function with a backtick prompt and a
"provider/model"client string gets a compiler-synthesized<Fn>$speccompanion; new postfix sugarFn@spec(args)references it:The new
tools:field (list of function references, each normalized throughai.tool(...)) switches the function to spec mode: its direct call desugars toai.Agent<Out>.new(client = client).run(Fn@spec(...)).value, withclient: ai.Client? = nullas a compiler-injected override parameter, and the legacybaml.llmcompanions ($render_prompt,$build_request*,$stream,$parse*) are not generated. Functions withouttools:keep the legacy direct-call path unchanged and additionally get the$speccompanion.A
tools:field on a function that is not spec-eligible (Jinja prompt or unknown provider prefix) is a compile error.Compiler details
toolsfield in LLM bodies (optional colon, arbitrary expression), postfix@spec(newSPEC_EXPRnode), plus tighter LLM-body detection (prompt(/prompt.and friends no longer misclassify expression bodies).$specbody is built while the CST backtick is in hand; the prompt template re-lowers as a plain template string inside a closure withctxbound toai.internal.SpecCtx, so${ctx.output_format}substitutes the render-time argument and interp diagnostics keep real spans.lower_lambdanow disambiguates same-span lambda scopes by owner (mirrorsbuild_tagged_body_closure), fixing capture resolution for companions that share the parent's source ranges.toolsfield support;@specprints verbatim._planv2/baml_srcKeeps only fixtures, tests, how-tos, and live smokes, rewritten against the builtins;
plan_trip_spec(...)is replaced by the realPlanTripLLM function +PlanTrip@spec(...).Testing
_planv2offline: 25/25 pass (includes a new direct-call desugar test).infisical run --env=test):live_openai()(exercises the lazy default-client path),live_anthropic(),live_google(),live_claude_code(),live_mcp_tools(),live_claude_code_dynamic_mcp()— all complete a real tool loop and return a typedItinerary/ echo reply.cargo test -p baml_tests(alltest_06_codegensnapshot churn is the stdlib listing growing by the new packages; reviewed and accepted), plus parser/AST/MIR/HIR/TIR/emit/engine/CLI/surface suites.🤖 Generated with Claude Code
Note
High Risk
Large stdlib and execution-model swap (journal-based runner vs legacy LLM orchestration) affects all spec-mode LLM calls, streaming FFI, and binary size; provider clients and tool/MCP paths are security- and reliability-sensitive.
Overview
Replaces the monolithic
baml.llmstdlib (orchestratingClient/PrimitiveClient, Jinja path, legacy$stream) with a BEP-styleaipackage and separate provider builtins (openai,anthropic,google,claude_code), all embedded inbaml_builtins2and registered inALL.The new surface centers on
FunctionSpec, structuralai.Prompt,Journal+ events,ModelTurn/Client,Agentrunner (tool loop, repair, budgets), typed failures, Retry / Fallback / RoundRobin, one-turn streaming (TurnStream,ai.stream.Stream,from_spec), MCP over stdio, and client wrappers in BAML rather than Rust orchestration. Built-in HTTP clients lower prompts and journals to each provider’s wire format (plus Claude Code CLI and Anthropic/OpenAI/Google streaming decoders).baml.promptholds prompt/output-format helpers; legacy provider option schemas move tosys_llm_typesfor Rust-only compatibility. SAP parsing cache and stream parse hooks move frombaml.llmintobaml.sap.baml.sysgainsstart_process, line-oriented stdout, and stdin control for MCP and harness clients.Shared
AI_STREAM_*constants are added for host/SDK identity. Packed-program size gates and CI baselines rise ~3% for the new bytecode. CLIbaml describesnapshots switch frombaml.llmtobaml.prompt.Reviewed by Cursor Bugbot for commit 938f2cb. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by CodeRabbit
New Features
@specsyntax for reusable AI function specifications.Documentation
Bug Fixes
Tests