Skip to content

About

BAML-generated TypeScript, Python, Go and Rust SDKs for Alien's Platform and Manager APIs. Not an official Alien SDK.

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Alien SDK on BAML (reference implementation)

Built by Boundary as a reference for Alien; not an official Alien SDK.

One BAML project, four SDKs. The logic in baml/ is written once in BAML; baml generate turns it into a TypeScript, Python, Go and Rust package. Each package runs the same BAML code inside an embedded BAML runtime, so the four SDKs behave identically by construction. A thin hand-written facade per language adds client construction, idiomatic names and error conversion, and nothing else.

This is a reference repository, not a release: nothing here is published, and package names (@alien-baml/sdk, alien_sdk, example.com/alien-baml-sdk/go) are placeholders.

It covers:

  • Platform and Manager API operations imported from Alien's OpenAPI specs (specs/) by tools/openapi2baml: all 332: 279 Platform (every one Alien's TypeScript SDK would generate) and 53 Manager operations, in the shape docs/api-shape.md describes;
  • a setup-link helper that builds a customized AWS CloudFormation quick-create link from a server-provided template URL;
  • the decode rules that the regression tests of Alien's Platform Rust client check (unknown fields and enum values, optional success bodies, storedSecretInputIds presence, per-platform stacks, event states, roles), ported to baml/baml_src/ns_compat/ and run in all four languages;
  • a repro of two BAML-generated SDKs in one process (examples/coexistence/).

Not covered:

  • Alien's commands client (@alienplatform/commands, with CommandsClient and invoke, and the Rust alien-commands-client). The SDK has the operations it calls (commands.bootstrap on the Platform API, commands.createCommand and commands.getCommandStatus on the Manager), not the invoke flow built on them;
  • Alien's bindings (alien-bindings and its Node and Python adapters). docs/bindings/ compares the two adapters and the ways to produce them;
  • publishing and release: no package is published, and the package names are placeholders.

Layout

specs/                   Alien's OpenAPI specs, vendored (see specs/README.md)
tools/openapi2baml/      the OpenAPI → BAML importer (scripts/import.sh)
baml/                    the BAML project: baml.toml + baml_src/ns_*/ (ns_platform, ns_manager imported)
sdk/typescript/          generated/ (generated)  src/ (facade)        test/
sdk/python/              alien_sdk/ (facade; the generated SDK is alien_sdk/_generated/)  tests/
sdk/go/                  alienapi/ (generated)   alien/ (facade + tests)
sdk/rust/                alien_api/ (generated)  src/ (facade crate)  tests/
conformance/             language-neutral scenarios, stub server, fixtures
docs/                    API shape, errors, gaps, importer coverage, telemetry;
                         bindings/ compares Alien's Node and Python bindings adapters
bench/                   benchmark programs (ours and Alien's current SDKs); bench/run.sh measures
metrics/                 size, startup, per-call, large-page and Rust build measurements (written by bench/run.sh)
scripts/                 import.sh, generate.sh, verify.sh

Each generated SDK is named by package_name in baml/baml.toml (BAML's default is baml_sdk; these names are placeholders): the Python one lives inside the facade's package, as alien_sdk._generated, so installing alien-sdk writes no top-level package that another BAML-generated SDK would overwrite (gaps B4). To rename one, change it there and in the facade's imports of it (Python alien_sdk/_gen.py, the TypeScript and Go imports, the Rust path dependency in sdk/rust/Cargo.toml and its uses); each language's genops finds the generated package through the facade (so the Go facade's hand-written files import exactly one package of the module, the generated one). The generated directories are not checked in, nor are the facades' generated methods (sdk/python/alien_sdk/_operations.py, sdk/go/alien/operations.go, sdk/rust/src/operations.rs). Never edit them, nor the imported baml/baml_src/ns_platform/ and ns_manager/: change the importer or its config and run scripts/import.sh, or change the hand-written namespaces, then run scripts/generate.sh.

For coding agents, BAML's agent skill is checked in: .claude/skills/baml-core/ for Claude Code and .agents/skills/baml-core/ for Codex and OpenCode. It teaches the BAML language and CLI of the pinned toolchain, and baml agent install at the repository root writes it.

Requirements

Tested on macOS arm64 with Node 24.22, pnpm 12.14, Python 3.14, uv 0.13, Go 1.27 and Rust 1.96.

  • baml on PATH: the BAML wrapper (brew install boundaryml/baml/baml). The scripts install BAML 0.25.3-nightly.20261109.a through it
  • Node and pnpm
  • Python 3.10+ and uv
  • Go 1.24+ with cgo (CGO_ENABLED=1, the default on macOS); baml generate runs gofmt
  • Rust 1.91.1+

The scripts use BAML 0.25.3-nightly.20261109.a (BAML_PIN in scripts/lib.sh), which the manifests pin exactly (npm @boundaryml/baml-bridge, PyPI baml-bridge, Go github.com/boundaryml/baml-go, crates.io baml_bridge). It is a nightly build of BAML, not a numbered release: the imported types use @json attributes (BAML #5563), which no numbered release contains. PyPI spells the version 0.25.3.dev2026110900. The scripts keep all BAML state in the repository (.baml_home/, .cache/), never in ~/.baml.

Quickstart

git clone <this repository> alien-baml-sdk && cd alien-baml-sdk
scripts/generate.sh

generate.sh regenerates the four SDKs and builds the facades. Each example below builds a CloudFormation quick-create link, calls no API and prints:

https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/create/review?templateURL=https%3A%2F%2Fexample.com%2Fsetup.yaml&stackName=alien-setup&param_ExternalId=customer-0001

TypeScript. Save as sdk/typescript/quickstart.mjs, then run cd sdk/typescript && node quickstart.mjs.

import { cloudFormationLaunchUrl } from "@alien-baml/sdk"

console.log(cloudFormationLaunchUrl({
  templateUrl: "https://example.com/setup.yaml", region: "us-east-1",
  stackName: "alien-setup", parameters: { ExternalId: "customer-0001" },
}))

Python. Save as sdk/python/quickstart.py, then run cd sdk/python && uv run python quickstart.py.

import alien_sdk

print(alien_sdk.cloudformation_launch_url(alien_sdk.CloudFormationLaunch(
    template_url="https://example.com/setup.yaml", region="us-east-1",
    stack_name="alien-setup", parameters={"ExternalId": "customer-0001"})))

Go. Save as sdk/go/cmd/quickstart/main.go, then run cd sdk/go && go run ./cmd/quickstart. Go and Rust load BAML's runtime library at run time: the first program on a machine downloads it into ~/.baml/bridges/0.25.3-nightly.20261109.a/, and later ones load that copy. docs/runtime.md has how to provide it ahead of time.

package main

import (
	"context"
	"fmt"
	"log"

	"example.com/alien-baml-sdk/go/alien"
)

func main() {
	defer func() { // seals the telemetry recording
		if err := alien.Shutdown(); err != nil {
			log.Print(err)
		}
	}()
	url, err := alien.CloudFormationLaunchURL(context.Background(), alien.CloudFormationLaunch{
		TemplateUrl: "https://example.com/setup.yaml", Region: "us-east-1",
		StackName: "alien-setup", Parameters: map[string]string{"ExternalId": "customer-0001"},
	})
	if err != nil {
		panic(err)
	}
	fmt.Println(url)
}

Rust. Save as sdk/rust/examples/quickstart.rs, then run cd sdk/rust && cargo run --quiet --example quickstart. It prints nothing else: BAML's Rust bridge logs through the log crate (target baml_bridge) to whatever logger the program installs, such as env_logger (RUST_LOG=baml_bridge=debug shows how it finds and loads the runtime library) or a tracing subscriber with tracing-log, which tracing-subscriber enables by default. BAML_LOG does not change it.

#[tokio::main]
async fn main() -> Result<(), alien_sdk::Error> {
    let url = alien_sdk::cloudformation_launch_url(alien_sdk::CloudFormationLaunch::new(
        "https://example.com/setup.yaml".into(), "us-east-1".into(), "alien-setup".into(),
        [("ExternalId".to_string(), "customer-0001".to_string())].into_iter().collect(),
    )).await?;
    println!("{url}");
    Ok(())
}

Shutting down

Each SDK runs an embedded runtime, which seals its telemetry recording when it shuts down (Telemetry; with ALIEN_TELEMETRY=off none of this matters). Python and Rust shut the runtime down when the process exits normally, TypeScript when Node's event loop runs empty; Go never does it by itself. Call the SDK's shutdown last wherever that does not happen: process.exit(), a signal handler, an abort, a serverless platform ending the process, and every Go main (as above).

Language Call Automatic at
TypeScript await shutdown() Node's beforeExit; not on process.exit() or signals
Python alien_sdk.shutdown() interpreter exit (atexit); not on os._exit() or SIGTERM
Go defer alien.Shutdown() never
Rust alien_sdk::shutdown()? normal exit; not on signals or abort

SDK calls made after it fail. Details: docs/runtime.md.

Calling the Platform API

The client defaults to https://api.alien.dev. The examples above do not call it; these sketches show the shape of the API (the conformance suites run the same calls against a local stub server).

Every operation is <group>.<method> as in Alien's TypeScript SDK (docs/api-shape.md):

const alien = new Alien({ workspace: "my-workspace" })   // apiKey defaults to ALIEN_API_KEY, as in Alien's SDK
const manager = await alien.managers.get("mgr_...")
const page = await alien.deployments.list({ project: "my-project", limit: 10 })
const url = await alien.setup.launchUrlForProject("my-project", "us-east-1", "alien-setup", { ExternalId: "customer-0001" })
alien = alien_sdk.Alien(workspace="my-workspace")   # api_key defaults to ALIEN_API_KEY
manager = alien.managers.get("mgr_...")
page = alien.deployments.list(project="my-project", deployment_group="prod", limit=10)
page.next_cursor   # groups, methods, keywords and fields are snake_case
# Methods are typed (mypy and editors see each signature); a wrong keyword raises TypeError.
from alien_sdk.platform import CreateSetupLinkRequest   # request and response types, per API
request = CreateSetupLinkRequest.model_validate(form)   # camelCase keys (the API's, with the exceptions in docs/gaps.md#b27) or snake_case
client, err := alien.New(alien.Options{Workspace: "my-workspace"}) // API key from ALIEN_API_KEY
manager, err := client.Managers.Get(ctx, "mgr_...")
page, err := client.Deployments.List(ctx, &alien.ListDeploymentsParams{Project: alien.Ptr("my-project"), Limit: alien.Ptr[int64](10)})
use alien_sdk::{Alien, Options};

// The API key comes from ALIEN_API_KEY unless `api_key: Some(..)` is set;
// with neither, `new` fails with INVALID_REQUEST.
let alien = Alien::new(Options { workspace: Some("my-workspace".into()), ..Default::default() })?;
let manager = alien.managers().get("mgr_...").send().await?;
// Required arguments go to the method, optional ones to the builder's setters.
let page = alien.deployments().list().project("my-project").limit(10).send().await?;

Each client also takes a limit for each HTTP request, BAML's 5 minutes when unset: TypeScript timeoutMs, Python timeout= (seconds; None for no limit), Go Options.Timeout (a time.Duration), Rust Options::timeout (a Duration); a request that runs out fails with COMMUNICATION_ERROR (decision 9 in docs/api-shape.md).

Every call fails with one error type, AlienError (code, message, retryable, HTTP status, context). See docs/errors.md.

Keep the generated clients out of logs. The clients you create (Alien and AlienManager; in Go *alien.Client and *alien.ManagerClient) do not print the API key or the token. The generated clients they hold do:

  • Rust: none is reachable. Alien, AlienManager and every group and builder print without the key or token, so they are safe in dbg!, logs and #[tracing::instrument] arguments.
  • Go: %v, %+v and %#v of client.Platform show the API key, so log.Printf("%+v", client.Platform) logs it. json.Marshal of a client or of its generated client writes the key or token.
  • Python: a traceback that records local variables, as Sentry does by default, holds the API key (docs/errors.md).

Details: gaps B22 and B23.

Using it from another project

Each recipe uses the generated code, so run scripts/generate.sh first. The TypeScript and Python packages are copies: pack or install them again after regenerating. In your own project (/path/to/alien-baml-sdk is this repository):

  • TypeScript. Pack the package and install the tarball, as examples/coexistence/run.sh does: (cd /path/to/alien-baml-sdk/sdk/typescript && pnpm pack --pack-destination /path/to/packs), then pnpm add /path/to/packs/alien-baml-sdk-0.0.0.tgz (or npm install the same file). pnpm 12 fails the install over the install script of protobufjs, a dependency of the bridge that only prints a notice, unless your pnpm-workspace.yaml has allowBuilds: { protobufjs: false }.
  • Python. uv pip install /path/to/alien-baml-sdk/sdk/python (or pip install …) into your environment, then import alien_sdk.
  • Go. go mod edit -replace example.com/alien-baml-sdk/go=/path/to/alien-baml-sdk/sdk/go, then go get example.com/alien-baml-sdk/go/alien.
  • Rust. A path dependency: alien_sdk = { path = "/path/to/alien-baml-sdk/sdk/rust" }.

Each recipe brings the published BAML 0.25.3-nightly.20261109.a runtime package.

Telemetry

The BAML runtime records spans, timings and HTTP calls for every SDK function without instrumentation code. Telemetry is on by default (medium); ALIEN_TELEMETRY=off turns it off (the other levels are low, medium and high). Without a Boundary API key it writes local files under $BAML_HOME/btel/ (~/.baml by default), which nothing deletes, and makes no connection, unless the machine has a saved Boundary login (baml auth login): then it uploads with that login (gaps B3). It never records HTTP request or response bodies, which carry tokens (http_bodies = false in baml/baml.toml). A process that ends without shutting the runtime down leaves its recording unsealed: see Shutting down. The SDK never prints telemetry warnings: if the endpoint cannot be reached or rejects the key, telemetry is dropped silently. Details and the checks behind these statements: docs/telemetry.md.

Verifying

scripts/verify.sh --phase 2

Runs every check: tests the importer and checks that the imported BAML and the generated operation scenarios are current, runs baml check and baml test, regenerates and builds the four SDKs, runs all 883 conformance scenarios in all four languages, checks the shared setup fixture, runs the README quickstarts, the telemetry checks, the B4 coexistence repro and a secret scan, and prints a summary table. It exits non-zero on the first failure. --phase 1 leaves out the importer checks and runs only the 49 hand-written scenarios, not the 834 generated ones that call each operation (conformance/README.md).

Known gaps

docs/gaps.md lists what BAML could not do for this SDK, with a minimal repro and the workaround used here or the BAML pull request that fixes it, plus open questions for Alien.

Third-party material

specs/ is copied from alienplatform/alien at c5c3a30 (v3.3.43). baml/baml_src/ns_platform/, baml/baml_src/ns_manager/, conformance/scenarios/ops.* and docs/importer-coverage.md are generated from those specs, tools/openapi2baml/tests/fixtures/naming-oracle.json from Alien's TypeScript SDK, and baml/baml_src/ns_compat/ ports tests of Alien's Rust client. That material is Copyright 2026 Alien Software, Inc., under the FSL-1.1-Apache-2.0.

About

BAML-generated TypeScript, Python, Go and Rust SDKs for Alien's Platform and Manager APIs. Not an official Alien SDK.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages