Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
"test:assert": "playwright test"
},
"dependencies": {
"@earendil-works/pi-ai": "1.0.0",
"@earendil-works/pi-durable": "1.0.0",
"@earendil-works/pi-ai": "1.2.0",
"@earendil-works/pi-durable": "1.2.0",
"@sentry/cloudflare": "file:../../packed/sentry-cloudflare-packed.tgz",
"agents": "0.26.0"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export class Assistant extends Agent<Env> {
// The manual span should nest under the SDK's `execute_tool` span.
execute: async args =>
Sentry.startSpan({ name: 'resolve-weather', attributes: { 'weather.city': args.city } }, () => ({
content: [{ type: 'text', text: `It is 21 degrees and sunny in ${args.city}.` }],
output: [{ type: 'text', text: `It is 21 degrees and sunny in ${args.city}.` }],
})),
}),
defineTool({
Expand All @@ -67,7 +67,7 @@ export class Assistant extends Agent<Env> {
execute: async args => {
const marker = `crashed:${args.job}`;
if (await this.ctx.storage.get(marker)) {
return { content: [{ type: 'text', text: `Step of job ${args.job} completed.` }] };
return { output: [{ type: 'text', text: `Step of job ${args.job} completed.` }] };
}
await this.ctx.storage.put(marker, true);
await this.ctx.storage.sync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
"test:assert": "OPENROUTER_API_KEY=$E2E_OPENROUTER_API_KEY playwright test"
},
"dependencies": {
"@earendil-works/chord": "1.0.0",
"@earendil-works/pi-ai": "1.0.0",
"@earendil-works/pi-durable": "1.0.0",
"@earendil-works/chord": "1.2.0",
"@earendil-works/pi-ai": "1.2.0",
"@earendil-works/pi-durable": "1.2.0",
"@sentry/node": "file:../../packed/sentry-node-packed.tgz"
},
"devDependencies": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const tools = [
// The manual span should nest under the SDK's `execute_tool` span.
execute: async args =>
Sentry.startSpan({ name: 'resolve-weather', attributes: { 'weather.city': args.city } }, () => ({
content: [{ type: 'text', text: `It is 21 degrees and sunny in ${args.city}.` }],
output: [{ type: 'text', text: `It is 21 degrees and sunny in ${args.city}.` }],
})),
}),
defineTool({
Expand All @@ -60,7 +60,7 @@ const tools = [
try {
writeFileSync(`${dataDir}crashed-${args.job}`, 'crashed once', { flag: 'wx' });
} catch {
return { content: [{ type: 'text', text: `Step of job ${args.job} completed.` }] };
return { output: [{ type: 'text', text: `Step of job ${args.job} completed.` }] };
}
process.exit(CRASH_EXIT_CODE);
},
Expand All @@ -80,14 +80,14 @@ const tools = [
const request = { type: 'input', content: args.task, requestId: `delegate:${api.taskId}` };
const settled = await (await child.submit(request, toolContext)).wait(toolContext);
if (settled.status !== 'done') {
return { isError: true, content: [{ type: 'text', text: `Subagent ended: ${settled.reason}` }] };
return { isError: true, output: [{ type: 'text', text: `Subagent ended: ${settled.reason}` }] };
}
const answer = await api.commit(tx => tx.entry(AssistantEntry, settled.answer), toolContext);
const text = (answer?.model?.[0]?.content ?? [])
.filter(part => part.type === 'text')
.map(part => part.text)
.join('');
return { content: [{ type: 'text', text }] };
return { output: [{ type: 'text', text }] };
},
}),
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ registry.install(
await new Promise((_, reject) =>
toolContext.abortSignal.addEventListener('abort', () => reject(new Error('tool aborted'))),
);
return { content: [] };
return { output: [] };
},
}),
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ registry.install(
tx => tx.createConversation({ ownership: { kind: 'task', taskId: api.taskId } }),
toolContext,
);
return { content: [{ type: 'text', text: 'Subagent created.' }] };
return { output: [{ type: 'text', text: 'Subagent created.' }] };
},
}),
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ registry.install(
const settled = await (
await child.submit({ type: 'input', content: args.task }, toolContext)
).wait(toolContext);
return { content: [{ type: 'text', text: await answerText(api, settled, toolContext) }] };
return { output: [{ type: 'text', text: await answerText(api, settled, toolContext) }] };
},
}),
defineTool({
Expand All @@ -89,14 +89,14 @@ registry.install(
const settled = await (
await child.submit({ type: 'input', content: 'Forked task.' }, toolContext)
).wait(toolContext);
return { content: [{ type: 'text', text: await answerText(api, settled, toolContext) }] };
return { output: [{ type: 'text', text: await answerText(api, settled, toolContext) }] };
},
}),
defineTool({
name: 'inner',
description: 'A plain tool.',
parameters: Type.Object({}),
execute: async () => ({ content: [{ type: 'text', text: 'INNER RESULT' }] }),
execute: async () => ({ output: [{ type: 'text', text: 'INNER RESULT' }] }),
}),
defineTool({
name: 'batch',
Expand All @@ -105,7 +105,7 @@ registry.install(
execute: async (_args, api, toolContext) => {
const inner = (await api.agent(toolContext)).tools.find(tool => tool.name === 'inner');
await inner.execute({}, api, toolContext);
return { content: [{ type: 'text', text: 'OUTER RESULT' }] };
return { output: [{ type: 'text', text: 'OUTER RESULT' }] };
},
}),
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ registry.install(
name: 'secret',
description: 'Returns a secret.',
parameters: Type.Object({}),
execute: async () => ({ content: [{ type: 'text', text: 'original secret value' }] }),
execute: async () => ({ output: [{ type: 'text', text: 'original secret value' }] }),
}),
defineTool({
name: 'fail_now',
Expand All @@ -87,9 +87,7 @@ registry.install(
hooks: [
hook(ToolTask, {
afterTool: (call, result) =>
call.name === 'secret'
? { ...result, content: [{ type: 'text', text: 'redacted by afterTool' }] }
: undefined,
call.name === 'secret' ? { ...result, output: [{ type: 'text', text: 'redacted by afterTool' }] } : undefined,
}),
],
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ registry.install(
name: 'get_weather',
description: 'Get the current weather for a city.',
parameters: Type.Object({ city: Type.String() }),
execute: async args => ({ content: [{ type: 'text', text: `It is 21 degrees and sunny in ${args.city}.` }] }),
execute: async args => ({ output: [{ type: 'text', text: `It is 21 degrees and sunny in ${args.city}.` }] }),
}),
defineTool({
name: 'broken',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runn
// instead, guarded by the `min: 22` skip below.
const PI_DURABLE_DEPENDENCIES = {
additionalDependencies: {
'@earendil-works/pi-durable': '^1.0.0',
'@earendil-works/pi-ai': '^1.0.0',
'@earendil-works/chord': '^1.0.0',
'@earendil-works/pi-durable': '^1.2.0',
'@earendil-works/pi-ai': '^1.2.0',
'@earendil-works/chord': '^1.2.0',
},
};

Expand Down
2 changes: 1 addition & 1 deletion packages/server-utils/src/ai/pi-durable/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export function instrumentTool(tool: PiTool, runs: PiRuns, options: GenAiOptions
call.endTimestamp = timestampInSeconds();
if (!calls) {
// No result entry follows a call outside a tool task phase, nor a nested call.
finishToolCall(call, { content: result?.content, isError: result?.isError });
finishToolCall(call, { content: result?.output ?? result?.content, isError: result?.isError });
}
return result;
} catch (error) {
Expand Down
3 changes: 3 additions & 0 deletions packages/server-utils/src/ai/pi-durable/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ export interface PiModels {
}

export interface PiToolExecutionResult {
/** The content blocks the tool returns for the model, in pi-durable 1.2.0 and later. */
output?: unknown[];
/** The content blocks the tool returns for the model, in pi-durable versions before 1.2.0. */
content?: unknown[];
isError?: boolean;
}
Expand Down
11 changes: 11 additions & 0 deletions packages/server-utils/test/ai/lib/tracing/pi-durable.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,17 @@ describe('instrumentPiDurableHarnessOptions', () => {
});
});

it('reads the result of a call outside a tool phase from `output`', async () => {
const tool: PiTool = {
name: 'get_weather',
execute: async () => ({ output: [{ type: 'text', text: 'sunny' }] }),
};

await instrumentTool(tool, createRuns(), {}).execute({}, { callId: 'call_1' }, undefined);

expect(spanToJSON(endedSpans[0]!).attributes[GEN_AI_TOOL_CALL_RESULT]).toBe('sunny');
});

it.each([
['no content blocks', []],
['an empty text block', [{ type: 'text', text: '' }]],
Expand Down
Loading