Skip to content

Commit a4af22e

Browse files
committed
Update PR #2519
1 parent 6d0956a commit a4af22e

29 files changed

Lines changed: 584 additions & 720 deletions

‎integrations/deepseek-harness/README.md‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ To override the defaults, add the following entry to the profile's
3030
```
3131
3232
The optional configuration fields are `apiKey`, `projectName`, `metadata`,
33-
`orgName`, and `appUrl`. `metadata` is added to every top-level user-turn trace,
34-
and `projectName` defaults to `DeepSeek Harness`. You can set `apiKey`
35-
under **Settings > Plugins** in Harness; it is declared as a secret field so
36-
the settings UI handles it as a credential. A configured `apiKey` takes
37-
precedence over the `BRAINTRUST_API_KEY` environment variable.
33+
`orgName`, `appUrl`, and `captureAttachments`. `metadata` is added to every
34+
top-level user-turn trace, and `projectName` defaults to `DeepSeek Harness`.
35+
You can set `apiKey` under **Settings > Plugins** in Harness; it is declared as
36+
a secret field so the settings UI handles it as a credential. A configured
37+
`apiKey` takes precedence over the `BRAINTRUST_API_KEY` environment variable.
38+
Set `captureAttachments: true` to upload Harness images as attachments; when it
39+
is omitted, the `BRAINTRUST_CAPTURE_ATTACHMENTS` environment variable decides.

‎integrations/deepseek-harness/src/index.test.ts‎

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,16 @@ const mock = vi.hoisted(() => {
4545
}
4646

4747
const logger = {
48-
loggingState: { _internalCaptureAttachmentsEnabled: vi.fn(() => true) },
4948
startSpan: (args: Record<PropertyKey, unknown>) => new MockSpan(args),
5049
flush: vi.fn(async () => undefined),
5150
};
5251
const initLogger = vi.fn(() => logger);
52+
const captureAttachmentsEnabled = vi.fn(() => true);
5353

5454
return {
5555
Attachment: MockAttachment,
5656
attachments,
57+
captureAttachmentsEnabled,
5758
initLogger,
5859
logger,
5960
reset() {
@@ -62,15 +63,14 @@ const mock = vi.hoisted(() => {
6263
spans.length = 0;
6364
initLogger.mockClear();
6465
logger.flush.mockClear();
65-
logger.loggingState._internalCaptureAttachmentsEnabled.mockReturnValue(
66-
true,
67-
);
66+
captureAttachmentsEnabled.mockReturnValue(true);
6867
},
6968
spans,
7069
};
7170
});
7271

7372
vi.mock("braintrust", () => ({
73+
_internalCaptureAttachmentsEnabled: mock.captureAttachmentsEnabled,
7474
Attachment: mock.Attachment,
7575
initLogger: mock.initLogger,
7676
NOOP_SPAN: {},
@@ -152,6 +152,14 @@ describe("DeepSeek Harness plugin", () => {
152152
expect(ConfigSchema.dict?.apiKey?.meta.role).toBe("secret");
153153
});
154154

155+
test("passes the attachment capture setting to its logger", () => {
156+
createContext({ captureAttachments: true });
157+
158+
expect(mock.initLogger).toHaveBeenCalledWith(
159+
expect.objectContaining({ captureAttachments: true }),
160+
);
161+
});
162+
155163
test("only logs the supported tool definition fields", async () => {
156164
const harness = createContext();
157165
const stream = harness.listener("llm/stream")(
@@ -235,9 +243,7 @@ describe("DeepSeek Harness plugin", () => {
235243
test.each([true, false])(
236244
"processes Harness images with capture=%s",
237245
async (captureAttachments) => {
238-
mock.logger.loggingState._internalCaptureAttachmentsEnabled.mockReturnValue(
239-
captureAttachments,
240-
);
246+
mock.captureAttachmentsEnabled.mockReturnValue(captureAttachments);
241247
const harness = createContext();
242248
const currentSession = session();
243249
const image = {
@@ -281,26 +287,27 @@ describe("DeepSeek Harness plugin", () => {
281287
);
282288
await harness.listener("session/flush")(currentSession);
283289

284-
if (!captureAttachments) {
290+
let expectedImagePart: Record<string, unknown>;
291+
if (captureAttachments) {
292+
expect(harness.readImage).toHaveBeenCalledOnce();
293+
expect(harness.readImage).toHaveBeenCalledWith(image);
294+
expect(mock.attachments).toHaveLength(1);
295+
expect(mock.attachments[0]?.params).toMatchObject({
296+
filename: "diagram.png",
297+
contentType: "image/png",
298+
});
299+
expect(new Uint8Array(mock.attachments[0]?.params.data ?? [])).toEqual(
300+
new Uint8Array([1, 2, 3]),
301+
);
302+
expectedImagePart = {
303+
type: "image_url",
304+
image_url: { url: mock.attachments[0] },
305+
};
306+
} else {
285307
expect(harness.readImage).not.toHaveBeenCalled();
286308
expect(mock.attachments).toHaveLength(0);
287-
return;
309+
expectedImagePart = { type: "image", attachment: image };
288310
}
289-
expect(harness.readImage).toHaveBeenCalledOnce();
290-
expect(harness.readImage).toHaveBeenCalledWith(image);
291-
expect(mock.attachments).toHaveLength(1);
292-
expect(mock.attachments[0]?.params).toMatchObject({
293-
filename: "diagram.png",
294-
contentType: "image/png",
295-
});
296-
expect(new Uint8Array(mock.attachments[0]?.params.data ?? [])).toEqual(
297-
new Uint8Array([1, 2, 3]),
298-
);
299-
300-
const expectedImagePart = {
301-
type: "image_url",
302-
image_url: { url: mock.attachments[0] },
303-
};
304311
const turn = mock.spans.find(
305312
(span) => span.args.name === "deepseek_harness.turn",
306313
);

‎integrations/deepseek-harness/src/index.ts‎

Lines changed: 46 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type {} from "@deepseek-ai/dsh-session";
77
import type {} from "@deepseek-ai/dsh-tools";
88
import z from "@deepseek-ai/schemastery";
99
import {
10+
_internalCaptureAttachmentsEnabled,
1011
Attachment,
1112
initLogger,
1213
NOOP_SPAN,
@@ -49,6 +50,11 @@ export interface Config {
4950
orgName?: string;
5051
/** Optional Braintrust deployment URL. */
5152
appUrl?: string;
53+
/**
54+
* Capture inline Harness images as attachments.
55+
* Falls back to BRAINTRUST_CAPTURE_ATTACHMENTS when omitted.
56+
*/
57+
captureAttachments?: boolean;
5258
}
5359

5460
export const Config: z<Config> = z.object({
@@ -57,6 +63,7 @@ export const Config: z<Config> = z.object({
5763
metadata: z.dict(z.any()),
5864
orgName: z.string(),
5965
appUrl: z.string(),
66+
captureAttachments: z.boolean(),
6067
});
6168

6269
type TokenMetrics = Record<string, number> & {
@@ -112,56 +119,53 @@ async function normalizeBlocks(
112119
blocks: readonly HarnessContentBlock[],
113120
resolveImage: (
114121
ref: HarnessImageAttachmentRef,
115-
) => Promise<Attachment | null | undefined>,
122+
) => Promise<Attachment | undefined>,
116123
): Promise<unknown> {
117124
if (blocks.length === 0) return "";
118125
if (blocks.every((block) => block.type === "text")) {
119126
return contentText(blocks);
120127
}
121-
return (
122-
await Promise.all(
123-
blocks.map(async (block) => {
124-
switch (block.type) {
125-
case "text":
126-
return { type: "text", text: block.text ?? "" };
127-
case "reasoning":
128-
return { type: "reasoning", text: block.text ?? "" };
129-
case "tool-call":
130-
return {
131-
type: "tool_call",
132-
id: block.id,
133-
name: block.name,
134-
arguments: block.arguments,
135-
};
136-
case "tool-result":
137-
return {
138-
type: "tool_result",
139-
tool_call_id: block.toolCallId,
140-
content: await normalizeBlocks(block.content ?? [], resolveImage),
141-
...(block.isError ? { is_error: true } : {}),
142-
};
143-
case "image": {
144-
const attachment = block.attachment
145-
? await resolveImage(block.attachment)
146-
: undefined;
147-
if (attachment === null) return undefined;
148-
return attachment
149-
? { type: "image_url", image_url: { url: attachment } }
150-
: { type: "image", attachment: block.attachment };
151-
}
152-
default:
153-
return { type: block.type };
128+
return Promise.all(
129+
blocks.map(async (block) => {
130+
switch (block.type) {
131+
case "text":
132+
return { type: "text", text: block.text ?? "" };
133+
case "reasoning":
134+
return { type: "reasoning", text: block.text ?? "" };
135+
case "tool-call":
136+
return {
137+
type: "tool_call",
138+
id: block.id,
139+
name: block.name,
140+
arguments: block.arguments,
141+
};
142+
case "tool-result":
143+
return {
144+
type: "tool_result",
145+
tool_call_id: block.toolCallId,
146+
content: await normalizeBlocks(block.content ?? [], resolveImage),
147+
...(block.isError ? { is_error: true } : {}),
148+
};
149+
case "image": {
150+
const attachment = block.attachment
151+
? await resolveImage(block.attachment)
152+
: undefined;
153+
return attachment
154+
? { type: "image_url", image_url: { url: attachment } }
155+
: { type: "image", attachment: block.attachment };
154156
}
155-
}),
156-
)
157-
).filter((block) => block !== undefined);
157+
default:
158+
return { type: block.type };
159+
}
160+
}),
161+
);
158162
}
159163

160164
async function normalizeMessage(
161165
message: HarnessMessage,
162166
resolveImage: (
163167
ref: HarnessImageAttachmentRef,
164-
) => Promise<Attachment | null | undefined>,
168+
) => Promise<Attachment | undefined>,
165169
): Promise<Record<string, unknown>> {
166170
const toolResult = message.content.find(
167171
(block) => block.type === "tool-result",
@@ -202,7 +206,7 @@ async function normalizeInput(
202206
options: HarnessGenerateOptions,
203207
resolveImage: (
204208
ref: HarnessImageAttachmentRef,
205-
) => Promise<Attachment | null | undefined>,
209+
) => Promise<Attachment | undefined>,
206210
): Promise<unknown[]> {
207211
return [
208212
...(options.system ? [{ role: "system", content: options.system }] : []),
@@ -376,6 +380,7 @@ export function apply(ctx: Context, config: Config = {}): void {
376380
projectName: config.projectName ?? DEFAULT_PROJECT_NAME,
377381
...(config.orgName ? { orgName: config.orgName } : {}),
378382
...(config.appUrl ? { appUrl: config.appUrl } : {}),
383+
captureAttachments: config.captureAttachments,
379384
setCurrent: false,
380385
});
381386
const turns = new Map<string, Map<number, TurnState>>();
@@ -396,9 +401,8 @@ export function apply(ctx: Context, config: Config = {}): void {
396401

397402
const resolveImage = async (
398403
ref: HarnessImageAttachmentRef,
399-
): Promise<Attachment | null | undefined> => {
400-
if (!logger.loggingState._internalCaptureAttachmentsEnabled(logger))
401-
return null;
404+
): Promise<Attachment | undefined> => {
405+
if (!_internalCaptureAttachmentsEnabled(logger)) return undefined;
402406
const attachmentStore = (
403407
ctx as Context & { attachments?: HarnessAttachmentStore }
404408
).attachments;

‎integrations/openai-agents-js/src/attachment-capture.test.ts‎

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,7 @@ it.each([true, false])(
3737
name: type,
3838
groupId: null,
3939
};
40-
const readData = vi.fn(() => "AQID");
41-
const audio = {
42-
get data() {
43-
return readData();
44-
},
45-
format: "pcm",
46-
};
40+
const audio = { data: "AQID", format: "pcm" };
4741
const span: AgentsSpan = {
4842
type: "trace.span",
4943
traceId: type,
@@ -86,7 +80,6 @@ it.each([true, false])(
8680
),
8781
).toBe(true);
8882
if (!captureAttachments) {
89-
expect(readData).not.toHaveBeenCalled();
9083
expect(decode).not.toHaveBeenCalled();
9184
expect(JSON.stringify(rows)).not.toContain("AQID");
9285
}

0 commit comments

Comments
 (0)