@braintrust/pi-extension 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +126 -0
- package/package.json +71 -0
- package/src/client.ts +244 -0
- package/src/config.test.ts +313 -0
- package/src/config.ts +461 -0
- package/src/index.integration.test.ts +598 -0
- package/src/index.test.ts +409 -0
- package/src/index.ts +861 -0
- package/src/state.test.ts +131 -0
- package/src/state.ts +197 -0
- package/src/types.ts +179 -0
- package/src/utils.test.ts +163 -0
- package/src/utils.ts +384 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Braintrust
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# @braintrust/pi-extension
|
|
2
|
+
|
|
3
|
+
Braintrust extension for [pi](https://github.com/mariozechner/pi-coding-agent).
|
|
4
|
+
|
|
5
|
+
Today this extension automatically traces pi sessions, turns, model calls, and tool executions to Braintrust.
|
|
6
|
+
|
|
7
|
+
## What gets traced
|
|
8
|
+
|
|
9
|
+
- **Session spans**: one root span per pi session that actually produces at least one turn
|
|
10
|
+
- **Turn spans**: one span per user prompt / agent run
|
|
11
|
+
- **LLM spans**: one span per model response inside a turn
|
|
12
|
+
- **Tool spans**: one span per tool execution
|
|
13
|
+
|
|
14
|
+
Trace shape:
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
Session (task)
|
|
18
|
+
├── Turn 1 (task)
|
|
19
|
+
│ ├── anthropic/claude-sonnet-4 (llm)
|
|
20
|
+
│ │ ├── read: package.json (tool)
|
|
21
|
+
│ │ └── bash: pnpm test (tool)
|
|
22
|
+
│ └── anthropic/claude-sonnet-4 (llm)
|
|
23
|
+
└── Turn 2 (task)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
### From npm
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pi install npm:@braintrust/pi-extension
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### From this repo
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pi install .
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Or load it just for one run:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pi -e .
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Compatibility
|
|
47
|
+
|
|
48
|
+
This package supports the **last three stable pi versions**.
|
|
49
|
+
|
|
50
|
+
Our GitHub Actions compatibility job automatically resolves and tests the latest patch release from each of the last three stable pi minor versions, so new pi releases are picked up without manually updating the matrix.
|
|
51
|
+
|
|
52
|
+
## Quick start
|
|
53
|
+
|
|
54
|
+
Tracing is disabled by default.
|
|
55
|
+
|
|
56
|
+
Set these environment variables:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
export TRACE_TO_BRAINTRUST=true
|
|
60
|
+
export BRAINTRUST_API_KEY=sk-...
|
|
61
|
+
export BRAINTRUST_PROJECT=pi
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Then start pi normally.
|
|
65
|
+
|
|
66
|
+
In interactive mode, the footer shows a `Braintrust` status indicator while tracing is active, and a widget below the editor shows a shortened clickable trace link when available.
|
|
67
|
+
|
|
68
|
+
## Configuration
|
|
69
|
+
|
|
70
|
+
You can configure the extension with environment variables or JSON config files.
|
|
71
|
+
|
|
72
|
+
Config precedence is:
|
|
73
|
+
|
|
74
|
+
1. defaults
|
|
75
|
+
2. `~/.pi/agent/braintrust.json`
|
|
76
|
+
3. `.pi/braintrust.json`
|
|
77
|
+
4. environment variables
|
|
78
|
+
|
|
79
|
+
### Config file locations
|
|
80
|
+
|
|
81
|
+
- Global: `~/.pi/agent/braintrust.json`
|
|
82
|
+
- Project: `.pi/braintrust.json`
|
|
83
|
+
|
|
84
|
+
Example:
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"trace_to_braintrust": true,
|
|
89
|
+
"project": "pi",
|
|
90
|
+
"debug": true,
|
|
91
|
+
"additional_metadata": {
|
|
92
|
+
"team": "platform"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Supported settings
|
|
98
|
+
|
|
99
|
+
| Config key | Env var | Default |
|
|
100
|
+
|---|---|---|
|
|
101
|
+
| `trace_to_braintrust` | `TRACE_TO_BRAINTRUST` | `false` |
|
|
102
|
+
| `api_key` | `BRAINTRUST_API_KEY` | unset |
|
|
103
|
+
| `api_url` | `BRAINTRUST_API_URL` | `https://api.braintrust.dev` |
|
|
104
|
+
| `app_url` | `BRAINTRUST_APP_URL` | `https://www.braintrust.dev` |
|
|
105
|
+
| `org_name` | `BRAINTRUST_ORG_NAME` | unset |
|
|
106
|
+
| `project` | `BRAINTRUST_PROJECT` | `pi` |
|
|
107
|
+
| `debug` | `BRAINTRUST_DEBUG` | `false` |
|
|
108
|
+
| `additional_metadata` | `BRAINTRUST_ADDITIONAL_METADATA` | `{}` |
|
|
109
|
+
| `log_file` | `BRAINTRUST_LOG_FILE` | unset |
|
|
110
|
+
| `state_dir` | `BRAINTRUST_STATE_DIR` | `~/.pi/agent/state/braintrust-pi-extension` |
|
|
111
|
+
| `parent_span_id` | `PI_PARENT_SPAN_ID` | unset |
|
|
112
|
+
| `root_span_id` | `PI_ROOT_SPAN_ID` | unset |
|
|
113
|
+
|
|
114
|
+
## Notes
|
|
115
|
+
|
|
116
|
+
- Project config overrides global config.
|
|
117
|
+
- Environment variables override both config files.
|
|
118
|
+
- Session bookkeeping is stored in `~/.pi/agent/state/braintrust-pi-extension/` by default.
|
|
119
|
+
- Span delivery uses the Braintrust JavaScript SDK's built-in async/background flushing.
|
|
120
|
+
- If Braintrust is unavailable, pi should continue working normally.
|
|
121
|
+
- If `PI_PARENT_SPAN_ID` is set, the pi session span is attached under an existing Braintrust trace.
|
|
122
|
+
- `PI_ROOT_SPAN_ID` can be used when the parent span is not the trace root.
|
|
123
|
+
|
|
124
|
+
## Contributing
|
|
125
|
+
|
|
126
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for development setup, validation, and repository conventions.
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@braintrust/pi-extension",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Braintrust extension for pi. Includes automatic tracing for pi sessions, turns, LLM calls, and tool executions to Braintrust.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"braintrust",
|
|
7
|
+
"llm",
|
|
8
|
+
"observability",
|
|
9
|
+
"pi",
|
|
10
|
+
"pi-package",
|
|
11
|
+
"tracing"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/braintrustdata/braintrust-pi-extension"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"src",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"type": "module",
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"braintrust": "^3.7.0",
|
|
28
|
+
"valibot": "^1.3.1"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@mariozechner/pi-ai": "^0.64.0",
|
|
32
|
+
"@mariozechner/pi-coding-agent": "^0.64.0",
|
|
33
|
+
"@types/node": "^25.5.0",
|
|
34
|
+
"tsx": "^4.21.0",
|
|
35
|
+
"typescript": "^6.0.2",
|
|
36
|
+
"vite-plus": "^0.1.14",
|
|
37
|
+
"vitest": "^4.1.2"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"@mariozechner/pi-coding-agent": "*"
|
|
41
|
+
},
|
|
42
|
+
"devEngines": {
|
|
43
|
+
"packageManager": {
|
|
44
|
+
"name": "pnpm",
|
|
45
|
+
"version": ">=10.33.0",
|
|
46
|
+
"onFail": "error"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"engines": {
|
|
50
|
+
"npm": "please-use-pnpm",
|
|
51
|
+
"pnpm": ">=10.33.0",
|
|
52
|
+
"yarn": "please-use-pnpm"
|
|
53
|
+
},
|
|
54
|
+
"pi": {
|
|
55
|
+
"extensions": [
|
|
56
|
+
"./src/index.ts"
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
"scripts": {
|
|
60
|
+
"preinstall": "node -e \"const userAgent = process.env.npm_config_user_agent || ''; if (process.env.INIT_CWD === process.cwd() && !userAgent.includes('pnpm/')) { console.error('Use pnpm in this repo.'); process.exit(1); }\"",
|
|
61
|
+
"check": "vp check",
|
|
62
|
+
"fmt": "vp fmt",
|
|
63
|
+
"lint": "vp lint",
|
|
64
|
+
"pack": "vp pack",
|
|
65
|
+
"test": "vitest run",
|
|
66
|
+
"test:integration": "vitest run src/index.integration.test.ts",
|
|
67
|
+
"test:watch": "vitest",
|
|
68
|
+
"typecheck": "vp check",
|
|
69
|
+
"smoke": "tsx -e \"import('./src/index.ts')\""
|
|
70
|
+
}
|
|
71
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ERR_PERMALINK,
|
|
3
|
+
NOOP_SPAN_PERMALINK,
|
|
4
|
+
initLogger,
|
|
5
|
+
type Logger as BraintrustSdkLogger,
|
|
6
|
+
type Span as BraintrustSdkSpan,
|
|
7
|
+
} from "braintrust";
|
|
8
|
+
import type { Logger, TraceConfig } from "./types.ts";
|
|
9
|
+
import { toUnixSeconds } from "./utils.ts";
|
|
10
|
+
|
|
11
|
+
export type BraintrustSpanHandle = BraintrustSdkSpan;
|
|
12
|
+
|
|
13
|
+
export interface StartTraceSpanArgs {
|
|
14
|
+
spanId: string;
|
|
15
|
+
rootSpanId: string;
|
|
16
|
+
parentSpanId?: string;
|
|
17
|
+
name: string;
|
|
18
|
+
type: "task" | "llm" | "tool";
|
|
19
|
+
startedAt: number;
|
|
20
|
+
input?: unknown;
|
|
21
|
+
output?: unknown;
|
|
22
|
+
error?: string;
|
|
23
|
+
metadata?: Record<string, unknown>;
|
|
24
|
+
metrics?: Record<string, number | undefined>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface UpdateTraceSpanArgs {
|
|
28
|
+
id: string;
|
|
29
|
+
spanId?: string;
|
|
30
|
+
rootSpanId?: string;
|
|
31
|
+
input?: unknown;
|
|
32
|
+
output?: unknown;
|
|
33
|
+
error?: string;
|
|
34
|
+
metadata?: Record<string, unknown>;
|
|
35
|
+
metrics?: Record<string, number | undefined>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function compactRecord<T extends Record<string, unknown>>(value: T): Partial<T> {
|
|
39
|
+
return Object.fromEntries(
|
|
40
|
+
Object.entries(value).filter(([, entry]) => entry !== undefined),
|
|
41
|
+
) as Partial<T>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isUsablePermalink(url: string | undefined): url is string {
|
|
45
|
+
return Boolean(url && url !== NOOP_SPAN_PERMALINK && !url.startsWith(ERR_PERMALINK));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class BraintrustClient {
|
|
49
|
+
readonly config: TraceConfig;
|
|
50
|
+
readonly logger?: Logger;
|
|
51
|
+
sdkLogger?: BraintrustSdkLogger<true>;
|
|
52
|
+
initPromise?: Promise<void>;
|
|
53
|
+
|
|
54
|
+
constructor(config: TraceConfig, logger?: Logger) {
|
|
55
|
+
this.config = config;
|
|
56
|
+
this.logger = logger;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#ensureLogger(): BraintrustSdkLogger<true> {
|
|
60
|
+
if (this.sdkLogger) return this.sdkLogger;
|
|
61
|
+
|
|
62
|
+
if (this.config.apiUrl && !process.env.BRAINTRUST_API_URL) {
|
|
63
|
+
process.env.BRAINTRUST_API_URL = this.config.apiUrl;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this.sdkLogger = initLogger({
|
|
67
|
+
projectName: this.config.projectName,
|
|
68
|
+
apiKey: this.config.apiKey,
|
|
69
|
+
appUrl: this.config.appUrl,
|
|
70
|
+
orgName: this.config.orgName,
|
|
71
|
+
asyncFlush: true,
|
|
72
|
+
setCurrent: false,
|
|
73
|
+
debugLogLevel: this.config.debug ? "debug" : false,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return this.sdkLogger;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async initialize(): Promise<void> {
|
|
80
|
+
if (!this.initPromise) {
|
|
81
|
+
this.initPromise = this.#doInitialize();
|
|
82
|
+
}
|
|
83
|
+
return this.initPromise;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async #doInitialize(): Promise<void> {
|
|
87
|
+
if (!this.config.apiKey) {
|
|
88
|
+
throw new Error("BRAINTRUST_API_KEY is not set");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const sdkLogger = this.#ensureLogger();
|
|
92
|
+
await sdkLogger.id;
|
|
93
|
+
const project = await sdkLogger.project;
|
|
94
|
+
this.logger?.info("braintrust sdk logger initialized", {
|
|
95
|
+
appUrl: this.config.appUrl,
|
|
96
|
+
apiUrl: this.config.apiUrl,
|
|
97
|
+
project: project.name,
|
|
98
|
+
projectId: project.id,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
startSpan(args: StartTraceSpanArgs): BraintrustSpanHandle | undefined {
|
|
103
|
+
try {
|
|
104
|
+
const sdkLogger = this.#ensureLogger();
|
|
105
|
+
const span = sdkLogger.startSpan({
|
|
106
|
+
spanId: args.spanId,
|
|
107
|
+
parentSpanIds: args.parentSpanId
|
|
108
|
+
? {
|
|
109
|
+
spanId: args.parentSpanId,
|
|
110
|
+
rootSpanId: args.rootSpanId,
|
|
111
|
+
}
|
|
112
|
+
: undefined,
|
|
113
|
+
name: args.name,
|
|
114
|
+
type: args.type,
|
|
115
|
+
startTime: toUnixSeconds(args.startedAt),
|
|
116
|
+
event: compactRecord({
|
|
117
|
+
input: args.input,
|
|
118
|
+
output: args.output,
|
|
119
|
+
error: args.error,
|
|
120
|
+
metadata: args.metadata,
|
|
121
|
+
metrics: args.metrics,
|
|
122
|
+
}),
|
|
123
|
+
});
|
|
124
|
+
return span;
|
|
125
|
+
} catch (error) {
|
|
126
|
+
this.logger?.error("failed to start Braintrust span", {
|
|
127
|
+
error: String(error),
|
|
128
|
+
spanId: args.spanId,
|
|
129
|
+
parentSpanId: args.parentSpanId,
|
|
130
|
+
rootSpanId: args.rootSpanId,
|
|
131
|
+
name: args.name,
|
|
132
|
+
type: args.type,
|
|
133
|
+
});
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
logSpan(span: BraintrustSpanHandle | undefined, event: Omit<UpdateTraceSpanArgs, "id">): void {
|
|
139
|
+
if (!span) return;
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
span.log(
|
|
143
|
+
compactRecord({
|
|
144
|
+
input: event.input,
|
|
145
|
+
output: event.output,
|
|
146
|
+
error: event.error,
|
|
147
|
+
metadata: event.metadata,
|
|
148
|
+
metrics: event.metrics,
|
|
149
|
+
}),
|
|
150
|
+
);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
this.logger?.error("failed to log Braintrust span", {
|
|
153
|
+
error: String(error),
|
|
154
|
+
spanId: span.spanId,
|
|
155
|
+
rootSpanId: span.rootSpanId,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
endSpan(span: BraintrustSpanHandle | undefined, endedAt = Date.now()): void {
|
|
161
|
+
if (!span) return;
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
span.end({ endTime: toUnixSeconds(endedAt) });
|
|
165
|
+
} catch (error) {
|
|
166
|
+
this.logger?.error("failed to end Braintrust span", {
|
|
167
|
+
error: String(error),
|
|
168
|
+
spanId: span.spanId,
|
|
169
|
+
rootSpanId: span.rootSpanId,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
getSpanLink(span: BraintrustSpanHandle | undefined): string | undefined {
|
|
175
|
+
if (!span) return undefined;
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
const link = span.link();
|
|
179
|
+
return isUsablePermalink(link) ? link : undefined;
|
|
180
|
+
} catch (error) {
|
|
181
|
+
this.logger?.warn("failed to build Braintrust span link", {
|
|
182
|
+
error: String(error),
|
|
183
|
+
spanId: span.spanId,
|
|
184
|
+
rootSpanId: span.rootSpanId,
|
|
185
|
+
});
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async getSpanPermalink(span: BraintrustSpanHandle | undefined): Promise<string | undefined> {
|
|
191
|
+
if (!span) return undefined;
|
|
192
|
+
|
|
193
|
+
const link = this.getSpanLink(span);
|
|
194
|
+
if (link) return link;
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
const permalink = await span.permalink();
|
|
198
|
+
return isUsablePermalink(permalink) ? permalink : undefined;
|
|
199
|
+
} catch (error) {
|
|
200
|
+
this.logger?.warn("failed to build Braintrust span permalink", {
|
|
201
|
+
error: String(error),
|
|
202
|
+
spanId: span.spanId,
|
|
203
|
+
rootSpanId: span.rootSpanId,
|
|
204
|
+
});
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
updateSpan(args: UpdateTraceSpanArgs): void {
|
|
210
|
+
try {
|
|
211
|
+
const sdkLogger = this.#ensureLogger();
|
|
212
|
+
sdkLogger.updateSpan({
|
|
213
|
+
id: args.id,
|
|
214
|
+
span_id: args.spanId,
|
|
215
|
+
root_span_id: args.rootSpanId,
|
|
216
|
+
input: args.input,
|
|
217
|
+
output: args.output,
|
|
218
|
+
error: args.error,
|
|
219
|
+
metadata: args.metadata,
|
|
220
|
+
metrics: args.metrics,
|
|
221
|
+
});
|
|
222
|
+
} catch (error) {
|
|
223
|
+
this.logger?.error("failed to update Braintrust span", {
|
|
224
|
+
error: String(error),
|
|
225
|
+
id: args.id,
|
|
226
|
+
spanId: args.spanId,
|
|
227
|
+
rootSpanId: args.rootSpanId,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async flush(): Promise<void> {
|
|
233
|
+
const sdkLogger = this.sdkLogger;
|
|
234
|
+
if (!sdkLogger) return;
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
await sdkLogger.flush();
|
|
238
|
+
} catch (error) {
|
|
239
|
+
this.logger?.error("failed to flush Braintrust logs", {
|
|
240
|
+
error: String(error),
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|