Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions src/functions/api.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::collections::HashSet;

use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use serde_json::Value;
Expand Down Expand Up @@ -29,6 +31,8 @@ pub struct Function {
#[serde(default)]
pub tags: Option<Vec<String>>,
#[serde(default)]
pub function_schema: Option<serde_json::Value>,
#[serde(default)]
pub metadata: Option<serde_json::Value>,
#[serde(default)]
pub created: Option<String>,
Expand Down Expand Up @@ -298,6 +302,47 @@ pub async fn list_functions_page(
parse_function_list_page(raw)
}

/// List a project's functions using the public API's cursor contract.
pub async fn list_all_functions(client: &ApiClient, project_id: &str) -> Result<Vec<Function>> {
let mut query = FunctionListQuery {
project_id: Some(project_id.to_string()),
..Default::default()
};
let mut functions = Vec::new();
let mut cursors = HashSet::new();

loop {
let page = list_functions_page(client, &query).await?;
if query.snapshot.is_none() {
query.snapshot = page.snapshot;
}
functions.extend(
page.objects
.into_iter()
.map(serde_json::from_value)
.collect::<std::result::Result<Vec<_>, _>>()
.context("unexpected function response shape")?,
);
let Some(cursor) = page.next_cursor else {
break;
};
if !cursors.insert(cursor.clone()) {
anyhow::bail!("function pagination returned a repeated cursor");
}
query.cursor = Some(cursor);
}

Ok(functions)
}

pub async fn create_function(client: &ApiClient, body: &Value) -> Result<Function> {
client.post("/v1/function", body).await
}

pub async fn replace_function(client: &ApiClient, body: &Value) -> Result<Function> {
client.put("/v1/function", body).await
}

fn parse_function_list_page(raw: Value) -> Result<FunctionListPage> {
let objects = raw
.get("objects")
Expand Down
3 changes: 3 additions & 0 deletions src/functions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1116,6 +1116,7 @@ mod tests {
prompt_data: Some(serde_json::json!({"parser": {"choice": ["a", "b"]}})),
function_data: Some(serde_json::json!({"type": "prompt"})),
tags: None,
function_schema: None,
metadata: None,
created: None,
_xact_id: None,
Expand All @@ -1136,6 +1137,7 @@ mod tests {
prompt_data: None,
function_data: None,
tags: None,
function_schema: None,
metadata: None,
created: None,
_xact_id: None,
Expand Down Expand Up @@ -1163,6 +1165,7 @@ mod tests {
prompt_data: None,
function_data: None,
tags: None,
function_schema: None,
metadata: None,
created: None,
_xact_id: None,
Expand Down
20 changes: 20 additions & 0 deletions src/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,26 @@ impl ApiClient {
parse_json_response(response, "POST", path).await
}

pub async fn put<T: DeserializeOwned, B: Serialize>(&self, path: &str, body: &B) -> Result<T> {
let url = self.url(path);
let response = self
.http
.put(&url)
.bearer_auth(&self.api_key)
.json(body)
.send()
.await
.context("request failed")?;

if !response.status().is_success() {
let status = response.status();
let body = response.text().await.unwrap_or_default();
return Err(HttpError { status, body }.into());
}

parse_json_response(response, "PUT", path).await
}

pub async fn patch<T: DeserializeOwned, B: Serialize>(
&self,
path: &str,
Expand Down
53 changes: 30 additions & 23 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mod functions;
mod http;
mod init;
mod js_runner;
mod observability;
mod profiles;
mod project_context;
mod projects;
Expand Down Expand Up @@ -60,35 +61,36 @@ const HELP_TEMPLATE: &str = "\
{before-help}{about} - {usage}

Core
init Initialize .bt config directory and files
login Log in to Braintrust
logout Remove a saved Braintrust login
profiles Manage saved Braintrust login profiles
switch Switch org and project context
view View logs, traces, and spans
init Initialize .bt config directory and files
login Log in to Braintrust
logout Remove a saved Braintrust login
profiles Manage saved Braintrust login profiles
switch Switch org and project context
view View logs, traces, and spans

Projects & resources
projects Manage projects
topics Inspect and control Topics automation
prompts Manage prompts
functions Manage functions (tools, scorers, and more)
tools Manage tools
scorers Manage scorers
experiments Manage experiments
environments Manage deployment environments
projects Manage projects
observability Manage active observability tools
topics Inspect and control Topics automation
prompts Manage prompts
functions Manage functions (tools, scorers, and more)
tools Manage tools
scorers Manage scorers
experiments Manage experiments
environments Manage deployment environments

Data & evaluation
datasets Manage datasets
eval Run eval files
sql Run SQL queries against Braintrust
sync Synchronize project logs between Braintrust and local NDJSON files
datasets Manage datasets
eval Run eval files
sql Run SQL queries against Braintrust
sync Synchronize project logs between Braintrust and local NDJSON files

Additional
docs Manage workflow docs for coding agents
trace Manage coding-agent tracing
setup Configure Braintrust setup flows (deprecated: use curl -fsSL https://braintrust.dev/wizard/setup.sh | sh)
status Show current identity, org, and project context
update Update bt in-place
docs Manage workflow docs for coding agents
trace Manage coding-agent tracing
setup Configure Braintrust setup flows (deprecated: use curl -fsSL https://braintrust.dev/wizard/setup.sh | sh)
status Show current identity, org, and project context
update Update bt in-place

Flags
--profile <PROFILE> Use a saved login profile [env: BRAINTRUST_PROFILE]
Expand Down Expand Up @@ -149,6 +151,8 @@ enum Commands {
Eval(CLIArgs<eval::EvalArgs>),
/// Manage projects
Projects(CLIArgs<projects::ProjectsArgs>),
/// Manage active observability tools
Observability(CLIArgs<observability::ObservabilityArgs>),
/// Inspect and control Topics automation
Topics(CLIArgs<topics::TopicsArgs>),
/// Manage datasets
Expand Down Expand Up @@ -198,6 +202,7 @@ impl Commands {
#[cfg(unix)]
Commands::Eval(cmd) => &cmd.base,
Commands::Projects(cmd) => &cmd.base,
Commands::Observability(cmd) => &cmd.base,
Commands::Topics(cmd) => &cmd.base,
Commands::Datasets(cmd) => &cmd.base,
Commands::Environments(cmd) => &cmd.base,
Expand Down Expand Up @@ -229,6 +234,7 @@ impl Commands {
#[cfg(unix)]
Commands::Eval(cmd) => &mut cmd.base,
Commands::Projects(cmd) => &mut cmd.base,
Commands::Observability(cmd) => &mut cmd.base,
Commands::Datasets(cmd) => &mut cmd.base,
Commands::Environments(cmd) => &mut cmd.base,
Commands::Topics(cmd) => &mut cmd.base,
Expand Down Expand Up @@ -363,6 +369,7 @@ fn try_main() -> Result<()> {
#[cfg(unix)]
Commands::Eval(cmd) => eval::run(cmd.base, cmd.args).await?,
Commands::Projects(cmd) => projects::run(cmd.base, cmd.args).await?,
Commands::Observability(cmd) => observability::run(cmd.base, cmd.args).await?,
Commands::Datasets(cmd) => datasets::run(cmd.base, cmd.args).await?,
Commands::Environments(cmd) => environments::run(cmd.base, cmd.args).await?,
Commands::Topics(cmd) => topics::run(cmd.base, cmd.args).await?,
Expand Down
Loading
Loading