Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
267cebf
design doc WIP
aaronvg Aug 1, 2026
7d698fd
BEP draft: AI functions and agents — docs, working reference, notes
aaronvg Aug 3, 2026
5317dbb
bep try again
aaronvg Aug 3, 2026
da937a5
add more files
aaronvg Aug 5, 2026
3dbd98d
new bep
aaronvg Aug 7, 2026
ef03373
second bep
aaronvg Aug 7, 2026
b48041c
claude code
aaronvg Aug 7, 2026
d49b029
Merge remote-tracking branch 'origin/canary' into aaron/custom-llm-pr…
aaronvg Aug 7, 2026
4c9bc5e
mcp stuff
aaronvg Aug 7, 2026
89db5de
some publish thing
aaronvg Aug 9, 2026
95b2bcc
clean up slop
aaronvg Aug 9, 2026
15e7bbc
feat(ai): builtin ai/provider packages + @spec desugar for LLM functions
aaronvg Aug 9, 2026
fac38ce
test(ai): accept snapshot churn from builtin packages; fmt tools field
aaronvg Aug 9, 2026
e587133
fix(syntax): ClientField::value() returns the full unquoted shorthand
aaronvg Aug 9, 2026
2f64f97
Merge remote-tracking branch 'origin/canary' into aaron/custom-llm-pr…
aaronvg Aug 9, 2026
d6c9f89
ci: fix PR 4352 gate failures — java Object-method escape, baselines
aaronvg Aug 9, 2026
cbba09c
chore(clippy): use method path over redundant closure in tools lowering
aaronvg Aug 9, 2026
180b90a
ci: human-readable size-gate baselines; trim whitespace in concepts page
aaronvg Aug 9, 2026
bfabceb
chore(clippy): box FunctionDeclBody::Llm after tools field growth
aaronvg Aug 9, 2026
d04a16b
ci: bump packed-program size ceilings for the builtin ai stdlib
aaronvg Aug 9, 2026
955cacb
fix(ai): address multi-agent review findings on the @spec desugar
aaronvg Aug 9, 2026
ad8ac3a
chore(clippy): hoist the fill helper above statements in LlmFunctionBody
aaronvg Aug 9, 2026
8328d57
feat(ai): pure client construction, RoundRobin wrapper, mcp -> ai.mcp
aaronvg Aug 9, 2026
7c33919
feat(ai)!: single-path LLM functions — every function runs the Agent …
aaronvg Aug 9, 2026
d7e10f3
test/fmt: single-path follow-ups — AST tests, fmt client expressions
aaronvg Aug 10, 2026
ee15fee
feat(ai): streaming — StreamingClient, ai.Stream, $stream companion
aaronvg Aug 10, 2026
becb35a
fix(codegen)!: keep LLM functions in generated SDKs; finish fixture m…
aaronvg Aug 10, 2026
38382ad
refactor(ast)!: delete the legacy client/retry/LLM-call synthesis
aaronvg Aug 10, 2026
deca27c
feat(ai)!: host-language streaming — $stream returns baml.llm.Stream
aaronvg Aug 10, 2026
136d817
test: regenerate snapshots for the single-path stream type
aaronvg Aug 10, 2026
e2e46de
fix(sdk-tests): restore BAML stream collectors; go $parse surface; fmt
aaronvg Aug 10, 2026
055a76d
fix(openai): resolve api_key/base_url env at request time by NAME
aaronvg Aug 10, 2026
6258927
test(sdk): re-record streaming SSE fixtures in Responses format
aaronvg Aug 10, 2026
0e9dea2
fix(ci): stale LSP hover expectation; drop orphaned snapshot
aaronvg Aug 10, 2026
6da6d0f
test: keep the stdlib out of user-project bytecode snapshots
aaronvg Aug 10, 2026
4a7b1f7
fix(ai): runner-level failures are not blind-replayable; unbreak carg…
aaronvg Aug 10, 2026
de8fbd5
fix: review findings — client(...) parse, Java accessors, SSE validator
aaronvg Aug 10, 2026
dfe0c45
chore: rustfmt + accept describe_command snapshots
aaronvg Aug 10, 2026
662c37e
fix(sdk_tests): revive the C# phase10 streaming fixture on the new path
aaronvg Aug 10, 2026
8809f0e
fix(lsp2): update the companion-listing test to the current companion…
aaronvg Aug 10, 2026
285fa1f
chore: drop the orphaned baml_src/mcp.snap
aaronvg Aug 10, 2026
732bdd8
Merge origin/canary into aaron/custom-llm-providers-v5
aaronvg Aug 10, 2026
9707f32
fix(_plan): make mount/unmount round-trip through the registry
aaronvg Aug 10, 2026
1a972ad
perf(sdkgen-ts): ship inlined bytecode as base64, not a decimal array
aaronvg Aug 10, 2026
2511cb7
chore: sort the base64 dependency
aaronvg Aug 10, 2026
412966b
Merge remote-tracking branch 'origin/canary' into aaron/custom-llm-pr…
aaronvg Aug 10, 2026
43a4157
Merge origin/canary; regenerate snapshots for the rvalue-sinking fix
aaronvg Aug 10, 2026
ec5b331
refactor(ai): give the ai package the namespace tree the BEP specifies
aaronvg Aug 10, 2026
e9d48d5
fix: two probes that the ai namespace move invalidated
aaronvg Aug 10, 2026
b798e93
fix(lsp tests): migrate fixtures to the ai.clients namespace
aaronvg Aug 10, 2026
a415615
Fix streaming
aaronvg Aug 11, 2026
01ab40d
chore: keep plan drafts out of the PR
aaronvg Aug 11, 2026
b013a8d
fix: refresh generated stream migration fixtures
aaronvg Aug 11, 2026
f80c22f
fix: avoid generated prompt data accessor
aaronvg Aug 11, 2026
84699b8
more fixes
aaronvg Aug 11, 2026
1090caf
test: restore ai stream migration coverage
aaronvg Aug 11, 2026
4d09fd2
test: refresh bytecode snapshots
aaronvg Aug 11, 2026
ebaf0ab
fix errors
aaronvg Aug 11, 2026
06d01f9
test: refresh ai std snapshots
aaronvg Aug 11, 2026
938f2cb
ci: retry macos bridge test
aaronvg Aug 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
9 changes: 6 additions & 3 deletions baml_language/.cargo/size-gate.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,17 @@ max_file_bytes = "22.5 MiB"
kind = "pack"
policy.max_delta_pct = 3.0

# Bumped ~3% above the Aug 2026 baselines when the builtin ai/provider
# stdlib packages landed (PR #4352): the packed program carries their
# bytecode (~700 KB).
[artifacts.packed-program.platform.aarch64-apple-darwin]
max_file_bytes = "13.6 MiB"
max_file_bytes = "14.3 MiB"

[artifacts.packed-program.platform.x86_64-unknown-linux-gnu]
max_file_bytes = "17.4 MiB"
max_file_bytes = "18.3 MiB"

[artifacts.packed-program.platform.x86_64-pc-windows-msvc]
max_file_bytes = "14.5 MiB"
max_file_bytes = "15.3 MiB"

[artifacts.packed-program.pack]
cli_package = "baml_cli"
Expand Down
4 changes: 2 additions & 2 deletions baml_language/.ci/size-gate/aarch64-apple-darwin.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ stripped_bytes = "20.3 MiB"
gzip_bytes = "9.7 MiB"

[artifacts.packed-program]
file_bytes = "13.2 MiB"
gzip_bytes = "6.1 MiB"
file_bytes = "13.8 MiB"
gzip_bytes = "6.3 MiB"
4 changes: 2 additions & 2 deletions baml_language/.ci/size-gate/x86_64-pc-windows-msvc.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ stripped_bytes = "21.9 MiB"
gzip_bytes = "9.9 MiB"

[artifacts.packed-program]
file_bytes = "14.1 MiB"
gzip_bytes = "6.2 MiB"
file_bytes = "14.8 MiB"
gzip_bytes = "6.4 MiB"
4 changes: 2 additions & 2 deletions baml_language/.ci/size-gate/x86_64-unknown-linux-gnu.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ stripped_bytes = "26.1 MiB"
gzip_bytes = "11.1 MiB"

[artifacts.packed-program]
file_bytes = "16.9 MiB"
gzip_bytes = "7.0 MiB"
file_bytes = "17.7 MiB"
gzip_bytes = "7.2 MiB"
1 change: 1 addition & 0 deletions baml_language/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion baml_language/crates/baml_base/src/core_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ impl TypePath {
Self(vec![name])
}

/// Build a `TypePath` from a compile-time dotted literal like `"baml.llm.Client"`.
/// Build a `TypePath` from a compile-time dotted literal like `"ai.Prompt"`.
/// Use only at synthetic construction sites; runtime input should come from
/// already-segmented data (e.g., parser tokens).
pub fn from_dotted(s: &str) -> Self {
Expand Down
10 changes: 9 additions & 1 deletion baml_language/crates/baml_base/src/qualified_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::Name;
/// Prefix used for standard library items in qualified names.
///
/// All `Namespace::BamlStd` and standard `Namespace::Builtin` items
/// are displayed with this prefix (e.g., `baml.llm.call_llm_function`).
/// are displayed with this prefix (e.g., `baml.prompt.render_output_format`).
pub const BAML_STD_PREFIX: &str = "baml.";

/// FQN (as a display string) of the recursive `json` type alias defined in
Expand All @@ -22,6 +22,14 @@ pub const BAML_STD_PREFIX: &str = "baml.";
/// recursion for output-format rendering, SAP streaming, and Python codegen.
pub const BAML_JSON_JSON: &str = "baml.json.json";

/// Canonical host-facing typed AI stream. Compiler synthesis, engine handle
/// lifting, and SDK generators must share this identity rather than carrying
/// independent string literals.
pub const AI_STREAM_STREAM: &str = "ai.stream.Stream";

/// Canonical nominal end-of-stream marker used by AI streams.
pub const AI_STREAM_DONE: &str = "ai.stream.Done";

/// A qualified name that unambiguously identifies an item.
///
/// Combines a namespace (where the item lives) with a name (what the item
Expand Down
33 changes: 33 additions & 0 deletions baml_language/crates/baml_builtins2/baml_std/ai/journal.baml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// The Journal: the append-only typed record of one run.
//
// The event types themselves live in root.events, and the content blocks they
// carry live in root.content. Journal stays at the package root because it is
// one of the nine names the BEP's flow diagram names directly.
//
// Mirrors _planv2/pages/02_guides/04_the_journal.md.

// The COMPLETE record. Repair attempts, feedback, failures — everything that
// happened is an event.
class Journal {
log: root.events.Event[],

function entries(self) -> root.events.Event[] {
self.log
}

// A journal for one run of a spec, with RunStarted appended.
function new<Out>(spec: FunctionSpec<Out>) -> Journal {
Journal {
log: [
root.events.RunStarted { spec_name: spec.name(), arguments: spec.arguments() },
],
}
}

// The driving runner is the only writer; clients and tools never append.
function append_all(self, events: root.events.Event[]) -> void {
for (let e in events) {
self.log.push(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
// Reliability composes at the client boundary: wrapper clients.
// Mirrors _planv2/pages/02_guides/03_clients/04_reliability.md.

// The default judgment: a request the provider rejected does not become
// valid by resending.
function default_retry_judgment(f: root.errors.Failure) -> bool {
match (f) {
let r: root.errors.Refused => false,
let i: root.errors.InvalidRequest => false,
let p: root.errors.ParseFailed => false,
_ => true,
}
}

class Backoff {
initial_ms: int,
multiplier: int,
max_ms: int,

function new(initial_ms: int = 250, multiplier: int = 2, max_ms: int = 10000) -> Backoff {
Backoff { initial_ms: initial_ms, multiplier: multiplier, max_ms: max_ms }
}

// The delay before attempt N (first retry is attempt 1). A
// root.errors.RateLimited.retry_after_ms hint overrides the computed value.
function delay_ms(self, attempt: int, f: root.errors.Failure) -> int {
let hinted = match (f) {
let r: root.errors.RateLimited => r.retry_after_ms,
_ => null,
};
if let h: int = hinted {
h
} else {
let d = self.initial_ms;
let i = 1;
while (i < attempt) {
d = d * self.multiplier;
i = i + 1;
}
if (d > self.max_ms) {
self.max_ms
} else {
d
}
}
}
}

class Retry {
inner: root.Client,
max_attempts: int,
retry_if: (root.errors.Failure) -> bool throws never,
backoff: Backoff,

function new(
inner: root.Client,
max_attempts: int = 3,
retry_if: ((root.errors.Failure) -> bool throws never)? = null,
backoff: Backoff? = null,
) -> Retry {
Retry {
inner: inner,
max_attempts: max_attempts,
retry_if: retry_if ?? default_retry_judgment,
backoff: backoff ?? Backoff.new(),
}
}

function _attempt(self, input: root.ModelTurnInput, remaining: int) -> root.ModelTurn {
self.inner.invoke(input) catch_all (e) {
let f: root.errors.Failure => {
// the safety gate is not overridable; the judgment is
if (
remaining > 1
&& f.retry_safety() == root.errors.RetrySafety.Safe
&& self.retry_if(f)
) {
let attempt = self.max_attempts - remaining + 1;
let _ = baml.sys.sleep(
baml.time.Duration.from_milliseconds(self.backoff.delay_ms(attempt, f)),
) catch_all (se) {
_ => null,
};
self._attempt(input, remaining - 1)
} else {
throw e
}
},
_ => throw e, // untyped failures are never retried
}
}

implements root.Client {
function id(self) -> string {
self.inner.id()
}

function invoke(self, input: root.ModelTurnInput) -> root.ModelTurn {
self._attempt(input, self.max_attempts) catch_all (e) {
_ => throw root.errors.normalize(e),
}
}
}
}

// Rotates invocations across members. Replaces the legacy round-robin
// provider: distribution is a client-boundary concern, exactly like Retry
// and Fallback. The counter mutates on the shared value, so every call
// site holding this client participates in one rotation.
class RoundRobin {
members: root.Client[],
counter: int,

function new(members: root.Client[]) -> RoundRobin {
if (members.length() == 0) {
throw baml.errors.InvalidArgument { message: "RoundRobin needs at least one member" }
}
RoundRobin { members: members, counter: 0 }
}

implements root.Client {
function id(self) -> string {
if let c: root.Client = self.members.at(0) {
c.id()
} else {
"round-robin"
}
}

function invoke(self, input: root.ModelTurnInput) -> root.ModelTurn {
{
let index = self.counter % self.members.length();
self.counter = self.counter + 1;
match (self.members.at(index)) {
let c: root.Client => c.invoke(input),
null => {
throw baml.errors.InvalidArgument { message: "RoundRobin needs at least one member" }
},
}
} catch_all (e) {
_ => throw root.errors.normalize(e),
}
}
}
}

class Fallback {
members: root.Client[],

function _try(self, input: root.ModelTurnInput, index: int) -> root.ModelTurn {
match (self.members.at(index)) {
let c: root.Client => {
c.invoke(input) catch_all (e) {
let f: root.errors.Failure => {
if (
index + 1 < self.members.length()
&& f.retry_safety() == root.errors.RetrySafety.Safe
) {
self._try(input, index + 1)
} else {
throw e
}
},
_ => throw e,
}
},
null => {
throw baml.errors.InvalidArgument { message: "Fallback needs at least one member" }
},
}
}

implements root.Client {
function id(self) -> string {
// NOTE: `?.method()` on an interface-typed optional trips a VM bug;
// if-let is the reliable form.
if let c: root.Client = self.members.at(0) {
c.id()
} else {
"fallback"
}
}

function invoke(self, input: root.ModelTurnInput) -> root.ModelTurn {
self._try(input, 0) catch_all (e) {
_ => throw root.errors.normalize(e),
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Canonical assistant content: what a model turn produced, normalized away
// from any one provider's envelope. Clients build these; the journal records
// them inside root.events.AssistantMessage.
//
// Mirrors _planv2/pages/04_reference/01_api.md (the `ai.content` namespace).

// A model turn is structured blocks, never a bare string and never an HTTP
// envelope.
class Text {
text: string,
}

class Reasoning {
summary: string,
}

class ToolUse {
id: string,
name: string,
args: map<string, unknown>,
}

type Block = Text | Reasoning | ToolUse;

// Why the model stopped. `ToolUse` means the turn ended with tool calls the
// runner must execute before the next turn.
enum StopReason {
Complete,
ToolUse,
MaxTokens,
Refused,
}
Loading
Loading