Skip to content
Draft
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
60 changes: 59 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,59 @@ jobs:
timeout-minutes: 10
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Set up pinned Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.3.14
- name: Build and validate plugins
run: make test

js-daemon-client:
name: JavaScript daemon client (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 10
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Set up pinned Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.3.14
- name: Test RPC transport contracts
run: bun test src/runtime/js-daemon-client/tests

opencode-compatibility:
name: OpenCode package (${{ matrix.peer-version }})
strategy:
fail-fast: false
matrix:
peer-version: ["1.0.0", "latest"]
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Set up pinned Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.3.14
- name: Build validated tarball
run: make validate-opencode
- name: Install package with OpenCode peers
env:
PEER_VERSION: ${{ matrix.peer-version }}
run: |
set -euo pipefail
mkdir install-test
npm install --prefix install-test --no-audit --no-fund \
./dist/opencode/braintrust-trace-opencode-*.tgz \
"@opencode-ai/plugin@${PEER_VERSION}" \
"@opencode-ai/sdk@${PEER_VERSION}"
node -e 'import(process.argv[1]).then((m) => { if (typeof m.default !== "function") process.exit(1) })' \
"$PWD/install-test/node_modules/@braintrust/trace-opencode/dist/index.js"

daemon:
name: Daemon (${{ matrix.os }})
strategy:
Expand All @@ -48,11 +98,18 @@ jobs:
rustup default stable
rustup component add clippy rustfmt
- name: Install latest coding agents
run: npm install --prefix "${{ runner.temp }}/coding-agents" --no-save --no-package-lock --no-audit --no-fund --cache "${{ runner.temp }}/npm-cache" @openai/codex@latest @anthropic-ai/claude-code@latest
run: npm install --prefix "${{ runner.temp }}/coding-agents" --no-save --no-package-lock --no-audit --no-fund --cache "${{ runner.temp }}/npm-cache" @openai/codex@latest @anthropic-ai/claude-code@latest opencode-ai@latest
- name: Set up pinned Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.3.14
- name: Build OpenCode plugin for integration harness
run: make build-opencode
- name: Report coding-agent versions
run: |
npm exec --prefix "${{ runner.temp }}/coding-agents" -- codex --version
npm exec --prefix "${{ runner.temp }}/coding-agents" -- claude --version
npm exec --prefix "${{ runner.temp }}/coding-agents" -- opencode --version
- name: Check formatting
if: runner.os == 'Linux'
run: cargo fmt --manifest-path bt-daemon/Cargo.toml -- --check
Expand All @@ -66,6 +123,7 @@ jobs:
BT_AGENT_INGEST_MODE: mock
CODEX_BIN: ${{ runner.temp }}/coding-agents/node_modules/.bin/codex${{ matrix.agent_suffix }}
CLAUDE_BIN: ${{ runner.temp }}/coding-agents/node_modules/.bin/claude${{ matrix.agent_suffix }}
OPENCODE_BIN: ${{ runner.temp }}/coding-agents/node_modules/.bin/opencode${{ matrix.agent_suffix }}
run: cargo test --manifest-path bt-daemon/Cargo.toml --all-features --locked --test agent_integration -- --ignored --nocapture --test-threads=1
- name: Lint daemon
run: cargo clippy --manifest-path bt-daemon/Cargo.toml --all-targets --all-features --locked -- -D warnings
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ DIST := dist
# name would shadow it in recipe subshells.
BUILD_RULES := $(addprefix build-,$(PLUGINS))
PUBLISH_RULES := $(addprefix publish-,$(PLUGINS))
VALIDATE_RULES := $(addprefix validate-,$(PLUGINS))

.PHONY: build test publish clean $(BUILD_RULES) $(PUBLISH_RULES)
.PHONY: build test publish clean $(BUILD_RULES) $(VALIDATE_RULES) $(PUBLISH_RULES)

build: $(BUILD_RULES)

Expand All @@ -38,6 +39,10 @@ test: build
src/plugins/$$p/validate.sh "$(DIST)/$$p"; \
done

$(VALIDATE_RULES): validate-%: build-%
@echo "==> validate $*"
@src/plugins/$*/validate.sh "$(DIST)/$*"

# Deploy every plugin named in the PUBLISH_TARGETS env var map. Fails if unset.
publish:
@scripts/publish.sh
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ For further instructions, see the instructions for your desired coding agent
|-------------|-------------------------|
| Claude Code | [braintrustdata/braintrust-claude-plugin](https://github.com/braintrustdata/braintrust-claude-plugin) |
| Codex | [braintrustdata/braintrust-codex-plugin](https://github.com/braintrustdata/braintrust-codex-plugin) |
| OpenCode | npm: [`@braintrust/trace-opencode`](https://www.npmjs.com/package/@braintrust/trace-opencode) |

## Development & releasing

Expand Down
9 changes: 8 additions & 1 deletion bt-daemon/src/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ impl Session {
pub fn spawn(
session_id: String,
source: String,
plugin_version: Option<String>,
journal: JournalWriter,
replay: Vec<Envelope>,
translators: Arc<Registry>,
Expand All @@ -57,6 +58,7 @@ impl Session {
let actor = SessionActor {
session_id: session_id.clone(),
source: source.clone(),
plugin_version,
translators,
sink_factory,
counters: counters.clone(),
Expand Down Expand Up @@ -164,6 +166,7 @@ async fn hydrate_transcript_snapshot(env: &mut Envelope) {
struct SessionActor {
session_id: String,
source: String,
plugin_version: Option<String>,
translators: Arc<Registry>,
sink_factory: Arc<dyn SinkFactory>,
counters: Arc<Counters>,
Expand All @@ -175,7 +178,11 @@ struct SessionActor {
impl SessionActor {
async fn run(self, mut rx: mpsc::UnboundedReceiver<SessionMsg>) {
let mut translator = self.translators.create(&self.source, &self.session_id);
let mut sink = match self.sink_factory.create(&self.session_id, &self.source) {
let mut sink = match self.sink_factory.create(
&self.session_id,
&self.source,
self.plugin_version.as_deref(),
) {
Ok(s) => s,
Err(e) => {
self.set_error(format!("sink init failed: {e}"));
Expand Down
1 change: 1 addition & 0 deletions bt-daemon/src/journal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ pub fn envelope_from_redacted(r: RedactedEnvelope) -> Envelope {
Envelope {
source: r.source,
source_version: r.source_version,
plugin_version: r.plugin_version,
session_id: r.session_id,
event: r.event,
ts_ms: r.ts_ms,
Expand Down
7 changes: 6 additions & 1 deletion bt-daemon/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ pub async fn run_hook(
let env = Envelope {
source: args.source.clone(),
source_version: args.source_version.clone(),
plugin_version: None,
session_id,
event,
ts_ms: now_ms(),
Expand Down Expand Up @@ -607,7 +608,11 @@ impl ImportProcessor {
Some(live) => live,
None => {
let translator = self.opts.translators.create(&env.source, &sid);
let sink = self.opts.sink_factory.create(&sid, &env.source)?;
let sink = self.opts.sink_factory.create(
&sid,
&env.source,
env.plugin_version.as_deref(),
)?;
self.sessions.insert(
sid.clone(),
ImportLive {
Expand Down
1 change: 1 addition & 0 deletions bt-daemon/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ impl Daemon {
let session = Session::spawn(
env.session_id.clone(),
env.source.clone(),
env.plugin_version.clone(),
journal,
replay,
self.translators.clone(),
Expand Down
9 changes: 7 additions & 2 deletions bt-daemon/src/sink/braintrust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,17 @@ impl BraintrustSinkFactory {
}

impl SinkFactory for BraintrustSinkFactory {
fn create(&self, _session_id: &str, source: &str) -> anyhow::Result<Box<dyn Sink>> {
fn create(
&self,
_session_id: &str,
source: &str,
plugin_version: Option<&str>,
) -> anyhow::Result<Box<dyn Sink>> {
Ok(Box::new(BraintrustSink {
cache: self.cache.clone(),
default_api_url: self.default_api_url.clone(),
default_app_url: self.default_app_url.clone(),
version: self.version.clone(),
version: plugin_version.unwrap_or(&self.version).to_string(),
source: source.to_string(),
creds: None,
urls: None,
Expand Down
7 changes: 6 additions & 1 deletion bt-daemon/src/sink/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ pub struct DebugSinkFactory {
}

impl SinkFactory for DebugSinkFactory {
fn create(&self, session_id: &str, _source: &str) -> anyhow::Result<Box<dyn Sink>> {
fn create(
&self,
session_id: &str,
_source: &str,
_plugin_version: Option<&str>,
) -> anyhow::Result<Box<dyn Sink>> {
std::fs::create_dir_all(&self.dir)?;
let path = self.dir.join(format!("{}.ndjson", sanitize(session_id)));
let file = OpenOptions::new().create(true).append(true).open(&path)?;
Expand Down
12 changes: 9 additions & 3 deletions bt-daemon/src/sink/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,14 @@ pub trait Sink: Send {
}
}

/// Builds a sink per session. `source` is the agent id (e.g. `codex`), used by
/// the Braintrust sink to stamp `context.span_origin`.
/// Builds a sink per session. `source` and `plugin_version` identify the
/// instrumentation that captured the events and are used to stamp
/// `context.span_origin` centrally.
pub trait SinkFactory: Send + Sync {
fn create(&self, session_id: &str, source: &str) -> anyhow::Result<Box<dyn Sink>>;
fn create(
&self,
session_id: &str,
source: &str,
plugin_version: Option<&str>,
) -> anyhow::Result<Box<dyn Sink>>;
}
1 change: 1 addition & 0 deletions bt-daemon/src/transcript_import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,7 @@ fn envelope(
Envelope {
source: source.into(),
source_version,
plugin_version: None,
session_id: session_id.into(),
event: event.into(),
ts_ms,
Expand Down
5 changes: 4 additions & 1 deletion bt-daemon/src/translate/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ mod claude;
mod codex;
mod debug;
mod git;
mod opencode;

pub use claude::ClaudeTranslatorFactory;
pub use codex::CodexTranslatorFactory;
pub use debug::DebugTranslatorFactory;
pub use opencode::OpenCodeTranslatorFactory;

use crate::wire::{Envelope, SessionConfig};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -122,7 +124,8 @@ impl Registry {
let mut r = Registry::debug_only();
let git = Arc::new(git::GitMetadataCache::default());
r.register(Box::new(ClaudeTranslatorFactory::new(git.clone())));
r.register(Box::new(CodexTranslatorFactory::new(git)));
r.register(Box::new(CodexTranslatorFactory::new(git.clone())));
r.register(Box::new(OpenCodeTranslatorFactory::new(git)));
r
}

Expand Down
Loading
Loading