Skip to content

enhancement(agent): add returnRunResult option to agent.asTool() to expose RunResult and interruptions #249

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
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
5 changes: 5 additions & 0 deletions .changeset/cuddly-poets-refuse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@openai/agents-core': patch
---

Add `returnRunResult` option to `agent.asTool()` to expose full RunResult, including interruptions such as approvals.
1 change: 1 addition & 0 deletions docs/src/content/docs/guides/tools.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ Under the hood the SDK:
- Creates a function tool with a single `input` parameter.
- Runs the sub‑agent with that input when the tool is called.
- Returns either the last message or the output extracted by `customOutputExtractor`.
- If `returnRunResult` is set, returns the full `RunResult` so nested interruptions can be inspected.

---

Expand Down
21 changes: 20 additions & 1 deletion examples/basic/tool-use-behavior.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { z } from 'zod';
import { Agent, run, tool } from '@openai/agents';
import { Agent, run, tool, RunContext, RunResult } from '@openai/agents';

const Weather = z.object({
city: z.string(),
Expand Down Expand Up @@ -46,6 +46,19 @@ const agent3 = new Agent({
tools: [getWeatherTool, saySomethingTool],
});

const agentWithRunResult = new Agent({
name: 'Tool agent with RunResult',
instructions,
toolUseBehavior: { stopAtToolNames: ['get_weather'] },
outputType: Weather,
tools: [getWeatherTool, saySomethingTool],
});

const weatherToolWithRunResult = agentWithRunResult.asTool({
toolName: 'weather_summary',
returnRunResult: true,
});

async function main() {
const input = 'What is the weather in San Francisco?';
const result = await run(agent, input);
Expand All @@ -65,6 +78,12 @@ async function main() {
const finalOutput3 = result3.finalOutput;
// The weather in San Francisco is sunny. Thanks for asking!
console.log(finalOutput3);

const result4 = (await weatherToolWithRunResult.invoke(
new RunContext(),
JSON.stringify({ input }),
)) as RunResult<any, Agent<any, any>>;
console.log('agentWithRunResult.newItems:', result4.newItems);
}

main();
12 changes: 11 additions & 1 deletion examples/docs/tools/agentsAsTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,17 @@ const summarizerTool = summarizer.asTool({
toolDescription: 'Generate a concise summary of the supplied text.',
});

const echoAgent = new Agent({
name: 'Echo',
instructions: 'Repeat whatever the user says.',
});

const echoTool = echoAgent.asTool({
toolName: 'echo_text',
returnRunResult: true,
});

const mainAgent = new Agent({
name: 'Research assistant',
tools: [summarizerTool],
tools: [summarizerTool, echoTool],
});
21 changes: 19 additions & 2 deletions packages/agents-core/src/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,8 +444,22 @@ export class Agent<
customOutputExtractor?: (
output: RunResult<TContext, Agent<TContext, any>>,
) => string | Promise<string>;
}): FunctionTool {
const { toolName, toolDescription, customOutputExtractor } = options;
/**
* If true, the invoked tool will return the {@link RunResult} of the agent
* run instead of just the extracted output text.
*/
returnRunResult?: boolean;
}): FunctionTool<
TContext,
any,
string | RunResult<TContext, Agent<TContext, any>>
> {
const {
toolName,
toolDescription,
customOutputExtractor,
returnRunResult,
} = options;
return tool({
name: toolName ?? toFunctionToolName(this.name),
description: toolDescription ?? '',
Expand All @@ -469,6 +483,9 @@ export class Agent<
const result = await runner.run(this, data.input, {
context: context?.context,
});
if (returnRunResult) {
return result as RunResult<TContext, Agent<TContext, any>>;
}
if (typeof customOutputExtractor === 'function') {
return customOutputExtractor(result as any);
}
Expand Down
67 changes: 66 additions & 1 deletion packages/agents-core/test/agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import { Agent } from '../src/agent';
import { RunContext } from '../src/runContext';
import { Handoff, handoff } from '../src/handoff';
import { z } from 'zod/v3';
import { JsonSchemaDefinition, setDefaultModelProvider } from '../src';
import {
JsonSchemaDefinition,
setDefaultModelProvider,
RunResult,
} from '../src';
import { FakeModelProvider } from './stubs';

describe('Agent', () => {
Expand Down Expand Up @@ -198,6 +202,67 @@ describe('Agent', () => {
const result1 = agent.processFinalOutput('{"message": "Hi, how are you?"}');
expect(result1).toEqual({ message: 'Hi, how are you?' });
});

it('should return a RunResult when returnRunResult option is true', async () => {
const agent = new Agent({
name: 'Test Agent',
instructions: 'You do tests.',
});
const tool = agent.asTool({ returnRunResult: true });
setDefaultModelProvider(new FakeModelProvider());
const result = await tool.invoke({} as any, '{"input":"hello"}');
expect(result).toBeInstanceOf(RunResult);
expect((result as RunResult<any, Agent<any, any>>).finalOutput).toBe(
'Hello World',
);
});

it('should expose finalOutput with stopAtToolNames using returnRunResult', async () => {
setDefaultModelProvider(new FakeModelProvider());

const subAgent = new Agent({
name: 'Echo',
instructions: 'Repeat what the user says.',
});

const queryTool = subAgent.asTool({
toolName: 'query_action_logs',
toolDescription: 'Echo tool that represents a long-running tool',
});

const outerAgent = new Agent({
name: 'Parent',
instructions: 'Use the tool then stop.',
tools: [queryTool],
toolUseBehavior: { stopAtToolNames: ['query_action_logs'] },
});

const outerToolDefault = outerAgent.asTool({
toolName: 'get_action_logs',
});

const outerToolWithRunResult = outerAgent.asTool({
toolName: 'get_action_logs_rich',
returnRunResult: true,
});

const input = JSON.stringify({ input: 'hello' });

const resultDefault = await outerToolDefault.invoke(
new RunContext({}),
input,
);
expect(resultDefault).toBe('Hello World');

const resultWithRunResult = await outerToolWithRunResult.invoke(
new RunContext({}),
input,
);
expect(
(resultWithRunResult as RunResult<any, Agent<any, any>>).finalOutput,
).toBe('Hello World');
});

it('should process final output (json schema)', async () => {
const agent = new Agent({
name: 'Test Agent',
Expand Down