136 lines
3.3 KiB
TypeScript
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[];
|
|
}
|