Files
AI-Harness-Basic/src/model/openai.ts
2026-05-15 22:18:34 -04:00

136 lines
3.3 KiB
TypeScript

import OpenAI from "openai";
import type {
AgentMessage,
ModelClient,
ModelTurn,
ToolCall,
ToolDefinition
} from "../types.js";
import type { EventBus } from "../events.js";
export class OpenAIResponsesClient implements ModelClient {
private readonly client = new OpenAI();
constructor(
private readonly model = "gpt-5",
private readonly instructions?: string,
private readonly events?: EventBus
) {}
async respond(input: AgentMessage[], tools: ToolDefinition[]): Promise<ModelTurn> {
await this.events?.emit("model.request", {
model: this.model,
messages: input.length,
tools: tools.map((tool) => tool.name)
});
const response = await this.client.responses.create({
model: this.model,
instructions: this.instructions,
input,
tools: tools.map((tool) => ({
type: "function",
name: tool.name,
description: tool.description,
parameters: tool.parameters,
strict: true
}))
});
const toolCalls: ToolCall[] = response.output.flatMap((item) =>
item.type === "function_call"
? [
{
id: item.call_id,
name: item.name,
arguments: item.arguments
}
]
: []
);
const outputText = response.output_text ?? "";
await this.events?.emit("model.response", {
outputItems: response.output.length,
toolCalls: toolCalls.map((toolCall) => toolCall.name),
hasText: outputText.length > 0
});
return {
output: sanitizeOutputItems(response.output),
outputText,
toolCalls
};
}
async respondStream(
input: AgentMessage[],
tools: ToolDefinition[],
onTextDelta: (delta: string) => void
): Promise<ModelTurn> {
await this.events?.emit("model.request", {
model: this.model,
messages: input.length,
tools: tools.map((tool) => tool.name)
});
const stream = this.client.responses.stream({
model: this.model,
instructions: this.instructions,
input,
tools: tools.map((tool) => ({
type: "function",
name: tool.name,
description: tool.description,
parameters: tool.parameters,
strict: true
}))
});
for await (const event of stream) {
if (event.type === "response.output_text.delta") {
onTextDelta(event.delta);
}
}
const response = await stream.finalResponse();
const toolCalls: ToolCall[] = response.output.flatMap((item) =>
item.type === "function_call"
? [
{
id: item.call_id,
name: item.name,
arguments: item.arguments
}
]
: []
);
const outputText = response.output_text ?? "";
await this.events?.emit("model.response", {
outputItems: response.output.length,
toolCalls: toolCalls.map((toolCall) => toolCall.name),
hasText: outputText.length > 0
});
return {
output: sanitizeOutputItems(response.output),
outputText,
toolCalls
};
}
}
function sanitizeOutputItems(items: unknown[]): unknown[] {
return JSON.parse(
JSON.stringify(items, (key, value) => {
if (key === "parsed_arguments") {
return undefined;
}
return value;
})
) as unknown[];
}