@ai-sdk/open-responses
Version:
The **[Open Responses provider](https://ai-sdk.dev/providers/ai-sdk-providers/open-responses)** for the [AI SDK](https://ai-sdk.dev/docs) contains language model support for [Open Responses](https://www.openresponses.org/) compatible APIs.
652 lines (644 loc) • 22.6 kB
JavaScript
// src/version.ts
var VERSION = true ? "1.0.16" : "0.0.0-test";
// src/open-responses-provider.ts
import {
NoSuchModelError
} from "@ai-sdk/provider";
import {
generateId,
withUserAgentSuffix
} from "@ai-sdk/provider-utils";
// src/responses/open-responses-language-model.ts
import {
combineHeaders,
createEventSourceResponseHandler,
createJsonErrorResponseHandler,
createJsonResponseHandler,
jsonSchema,
parseProviderOptions,
postJsonToApi
} from "@ai-sdk/provider-utils";
import { z as z3 } from "zod/v4";
// src/responses/convert-to-open-responses-input.ts
import { convertToBase64 } from "@ai-sdk/provider-utils";
async function convertToOpenResponsesInput({
prompt
}) {
var _a, _b, _c;
const input = [];
const warnings = [];
const systemMessages = [];
for (const { role, content } of prompt) {
switch (role) {
case "system": {
systemMessages.push(content);
break;
}
case "user": {
const userContent = [];
for (const part of content) {
switch (part.type) {
case "text": {
userContent.push({ type: "input_text", text: part.text });
break;
}
case "file": {
const mediaType = part.mediaType === "image/*" ? "image/jpeg" : part.mediaType;
if (part.mediaType.startsWith("image/")) {
userContent.push({
type: "input_image",
...part.data instanceof URL ? { image_url: part.data.toString() } : {
image_url: `data:${mediaType};base64,${convertToBase64(part.data)}`
}
});
} else if (part.data instanceof URL) {
userContent.push({
type: "input_file",
file_url: part.data.toString()
});
} else {
userContent.push({
type: "input_file",
filename: (_a = part.filename) != null ? _a : "data",
file_data: `data:${mediaType};base64,${convertToBase64(part.data)}`
});
}
break;
}
}
}
input.push({ type: "message", role: "user", content: userContent });
break;
}
case "assistant": {
const assistantContent = [];
const toolCalls = [];
for (const part of content) {
switch (part.type) {
case "text": {
assistantContent.push({ type: "output_text", text: part.text });
break;
}
case "tool-call": {
const argumentsValue = typeof part.input === "string" ? part.input : JSON.stringify(part.input);
toolCalls.push({
type: "function_call",
call_id: part.toolCallId,
name: part.toolName,
arguments: argumentsValue
});
break;
}
}
}
if (assistantContent.length > 0) {
input.push({
type: "message",
role: "assistant",
content: assistantContent
});
}
for (const toolCall of toolCalls) {
input.push(toolCall);
}
break;
}
case "tool": {
for (const part of content) {
if (part.type === "tool-result") {
const output = part.output;
let contentValue;
switch (output.type) {
case "text":
case "error-text":
contentValue = output.value;
break;
case "execution-denied":
contentValue = (_b = output.reason) != null ? _b : "Tool execution denied.";
break;
case "json":
case "error-json":
contentValue = JSON.stringify(output.value);
break;
case "content": {
const contentParts = [];
for (const item of output.value) {
switch (item.type) {
case "text": {
contentParts.push({
type: "input_text",
text: item.text
});
break;
}
case "image-data": {
contentParts.push({
type: "input_image",
image_url: `data:${item.mediaType};base64,${item.data}`
});
break;
}
case "image-url": {
contentParts.push({
type: "input_image",
image_url: item.url
});
break;
}
case "file-data": {
contentParts.push({
type: "input_file",
filename: (_c = item.filename) != null ? _c : "data",
file_data: `data:${item.mediaType};base64,${item.data}`
});
break;
}
default: {
warnings.push({
type: "other",
message: `unsupported tool content part type: ${item.type}`
});
break;
}
}
}
contentValue = contentParts;
break;
}
}
input.push({
type: "function_call_output",
call_id: part.toolCallId,
output: contentValue
});
}
}
break;
}
}
}
return {
input,
instructions: systemMessages.length > 0 ? systemMessages.join("\n") : void 0,
warnings
};
}
// src/responses/open-responses-api.ts
import { lazySchema, zodSchema } from "@ai-sdk/provider-utils";
import { z } from "zod/v4";
var openResponsesErrorSchema = lazySchema(
() => zodSchema(
z.object({
error: z.object({
message: z.string(),
type: z.string(),
param: z.string(),
code: z.string()
})
})
)
);
// src/responses/map-open-responses-finish-reason.ts
function mapOpenResponsesFinishReason({
finishReason,
hasToolCalls
}) {
switch (finishReason) {
case void 0:
case null:
return hasToolCalls ? "tool-calls" : "stop";
case "max_output_tokens":
return "length";
case "content_filter":
return "content-filter";
default:
return hasToolCalls ? "tool-calls" : "other";
}
}
// src/responses/open-responses-options.ts
import {
lazySchema as lazySchema2,
zodSchema as zodSchema2
} from "@ai-sdk/provider-utils";
import { z as z2 } from "zod/v4";
var openResponsesOptionsSchema = lazySchema2(
() => zodSchema2(
z2.object({
reasoningEffort: z2.enum(["none", "low", "medium", "high", "xhigh"]).nullish(),
/**
* Controls reasoning summary output from the model.
* Valid values: 'concise', 'detailed', 'auto'.
*/
reasoningSummary: z2.enum(["concise", "detailed", "auto"]).nullish()
})
)
);
// src/responses/open-responses-language-model.ts
var OpenResponsesLanguageModel = class {
constructor(modelId, config) {
this.specificationVersion = "v3";
this.supportedUrls = {
"image/*": [/^https?:\/\/.*$/]
};
this.modelId = modelId;
this.config = config;
}
get provider() {
return this.config.provider;
}
async getArgs({
maxOutputTokens,
temperature,
stopSequences,
topP,
topK,
presencePenalty,
frequencyPenalty,
seed,
prompt,
providerOptions,
tools,
toolChoice,
responseFormat
}) {
var _a;
const warnings = [];
if (stopSequences != null) {
warnings.push({ type: "unsupported", feature: "stopSequences" });
}
if (topK != null) {
warnings.push({ type: "unsupported", feature: "topK" });
}
if (seed != null) {
warnings.push({ type: "unsupported", feature: "seed" });
}
const {
input,
instructions,
warnings: inputWarnings
} = await convertToOpenResponsesInput({
prompt
});
warnings.push(...inputWarnings);
const functionTools = tools == null ? void 0 : tools.filter((tool) => tool.type === "function").map((tool) => ({
type: "function",
name: tool.name,
description: tool.description,
parameters: tool.inputSchema,
...tool.strict != null ? { strict: tool.strict } : {}
}));
const convertedToolChoice = toolChoice == null ? void 0 : toolChoice.type === "tool" ? { type: "function", name: toolChoice.toolName } : toolChoice.type;
const textFormat = (responseFormat == null ? void 0 : responseFormat.type) === "json" ? {
type: "json_schema",
...responseFormat.schema != null ? {
name: (_a = responseFormat.name) != null ? _a : "response",
description: responseFormat.description,
schema: responseFormat.schema,
strict: true
} : {}
} : void 0;
const openResponsesOptions = await parseProviderOptions({
provider: this.config.providerOptionsName,
providerOptions,
schema: openResponsesOptionsSchema
});
return {
body: {
model: this.modelId,
input,
instructions,
max_output_tokens: maxOutputTokens,
temperature,
top_p: topP,
presence_penalty: presencePenalty,
frequency_penalty: frequencyPenalty,
reasoning: (openResponsesOptions == null ? void 0 : openResponsesOptions.reasoningEffort) != null || (openResponsesOptions == null ? void 0 : openResponsesOptions.reasoningSummary) != null ? {
...(openResponsesOptions == null ? void 0 : openResponsesOptions.reasoningEffort) != null && {
effort: openResponsesOptions.reasoningEffort
},
...(openResponsesOptions == null ? void 0 : openResponsesOptions.reasoningSummary) != null && {
summary: openResponsesOptions.reasoningSummary
}
} : void 0,
tools: (functionTools == null ? void 0 : functionTools.length) ? functionTools : void 0,
tool_choice: convertedToolChoice,
...textFormat != null && { text: { format: textFormat } }
},
warnings
};
}
async doGenerate(options) {
var _a, _b, _c, _d, _e, _f;
const { body, warnings } = await this.getArgs(options);
const {
responseHeaders,
value: response,
rawValue: rawResponse
} = await postJsonToApi({
url: this.config.url,
headers: combineHeaders(this.config.headers(), options.headers),
body,
failedResponseHandler: createJsonErrorResponseHandler({
errorSchema: openResponsesErrorSchema,
errorToMessage: (error) => error.error.message
}),
successfulResponseHandler: createJsonResponseHandler(
// do not validate the response body, only apply types to the response body
jsonSchema(() => {
throw new Error("json schema not implemented");
})
),
abortSignal: options.abortSignal,
fetch: this.config.fetch
});
const content = [];
let hasToolCalls = false;
for (const part of response.output) {
switch (part.type) {
// TODO AI SDK 7 adjust reasoning in the specification to better support the reasoning structure from open responses.
case "reasoning": {
for (const contentPart of (_a = part.content) != null ? _a : []) {
content.push({
type: "reasoning",
text: contentPart.text
});
}
break;
}
case "message": {
for (const contentPart of part.content) {
content.push({
type: "text",
text: contentPart.text
});
}
break;
}
case "function_call": {
hasToolCalls = true;
content.push({
type: "tool-call",
toolCallId: part.call_id,
toolName: part.name,
input: part.arguments
});
break;
}
}
}
const usage = response.usage;
const inputTokens = usage == null ? void 0 : usage.input_tokens;
const cachedInputTokens = (_b = usage == null ? void 0 : usage.input_tokens_details) == null ? void 0 : _b.cached_tokens;
const outputTokens = usage == null ? void 0 : usage.output_tokens;
const reasoningTokens = (_c = usage == null ? void 0 : usage.output_tokens_details) == null ? void 0 : _c.reasoning_tokens;
return {
content,
finishReason: {
unified: mapOpenResponsesFinishReason({
finishReason: (_d = response.incomplete_details) == null ? void 0 : _d.reason,
hasToolCalls
}),
raw: (_f = (_e = response.incomplete_details) == null ? void 0 : _e.reason) != null ? _f : void 0
},
usage: {
inputTokens: {
total: inputTokens,
noCache: (inputTokens != null ? inputTokens : 0) - (cachedInputTokens != null ? cachedInputTokens : 0),
cacheRead: cachedInputTokens,
cacheWrite: void 0
},
outputTokens: {
total: outputTokens,
text: (outputTokens != null ? outputTokens : 0) - (reasoningTokens != null ? reasoningTokens : 0),
reasoning: reasoningTokens
},
raw: response.usage
},
request: { body },
response: {
id: response.id,
timestamp: new Date(response.created_at * 1e3),
modelId: response.model,
headers: responseHeaders,
body: rawResponse
},
providerMetadata: void 0,
warnings
};
}
async doStream(options) {
const { body, warnings } = await this.getArgs(options);
const { responseHeaders, value: response } = await postJsonToApi({
url: this.config.url,
headers: combineHeaders(this.config.headers(), options.headers),
body: {
...body,
stream: true
},
failedResponseHandler: createJsonErrorResponseHandler({
errorSchema: openResponsesErrorSchema,
errorToMessage: (error) => error.error.message
}),
// TODO consider validation
successfulResponseHandler: createEventSourceResponseHandler(z3.any()),
abortSignal: options.abortSignal,
fetch: this.config.fetch
});
const usage = {
inputTokens: {
total: void 0,
noCache: void 0,
cacheRead: void 0,
cacheWrite: void 0
},
outputTokens: {
total: void 0,
text: void 0,
reasoning: void 0
}
};
const updateUsage = (responseUsage) => {
var _a, _b;
if (!responseUsage) {
return;
}
const inputTokens = responseUsage.input_tokens;
const cachedInputTokens = (_a = responseUsage.input_tokens_details) == null ? void 0 : _a.cached_tokens;
const outputTokens = responseUsage.output_tokens;
const reasoningTokens = (_b = responseUsage.output_tokens_details) == null ? void 0 : _b.reasoning_tokens;
usage.inputTokens = {
total: inputTokens,
noCache: (inputTokens != null ? inputTokens : 0) - (cachedInputTokens != null ? cachedInputTokens : 0),
cacheRead: cachedInputTokens,
cacheWrite: void 0
};
usage.outputTokens = {
total: outputTokens,
text: (outputTokens != null ? outputTokens : 0) - (reasoningTokens != null ? reasoningTokens : 0),
reasoning: reasoningTokens
};
usage.raw = responseUsage;
};
let isActiveReasoning = false;
let hasToolCalls = false;
let finishReason = {
unified: "other",
raw: void 0
};
const toolCallsByItemId = {};
return {
stream: response.pipeThrough(
new TransformStream({
start(controller) {
controller.enqueue({ type: "stream-start", warnings });
},
transform(parseResult, controller) {
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
if (options.includeRawChunks) {
controller.enqueue({
type: "raw",
rawValue: parseResult.rawValue
});
}
if (!parseResult.success) {
controller.enqueue({ type: "error", error: parseResult.error });
return;
}
const chunk = parseResult.value;
if (chunk.type === "response.output_item.added" && chunk.item.type === "function_call") {
toolCallsByItemId[chunk.item.id] = {
toolName: chunk.item.name,
toolCallId: chunk.item.call_id,
arguments: chunk.item.arguments
};
} else if (chunk.type === "response.function_call_arguments.delta") {
const functionCallChunk = chunk;
const toolCall = (_a = toolCallsByItemId[functionCallChunk.item_id]) != null ? _a : toolCallsByItemId[functionCallChunk.item_id] = {};
toolCall.arguments = ((_b = toolCall.arguments) != null ? _b : "") + functionCallChunk.delta;
} else if (chunk.type === "response.function_call_arguments.done") {
const functionCallChunk = chunk;
const toolCall = (_c = toolCallsByItemId[functionCallChunk.item_id]) != null ? _c : toolCallsByItemId[functionCallChunk.item_id] = {};
toolCall.arguments = functionCallChunk.arguments;
} else if (chunk.type === "response.output_item.done" && chunk.item.type === "function_call") {
const toolCall = toolCallsByItemId[chunk.item.id];
const toolName = (_d = toolCall == null ? void 0 : toolCall.toolName) != null ? _d : chunk.item.name;
const toolCallId = (_e = toolCall == null ? void 0 : toolCall.toolCallId) != null ? _e : chunk.item.call_id;
const input = (_g = (_f = toolCall == null ? void 0 : toolCall.arguments) != null ? _f : chunk.item.arguments) != null ? _g : "";
controller.enqueue({
type: "tool-call",
toolCallId,
toolName,
input
});
hasToolCalls = true;
delete toolCallsByItemId[chunk.item.id];
} else if (chunk.type === "response.output_item.added" && chunk.item.type === "reasoning") {
controller.enqueue({
type: "reasoning-start",
id: chunk.item.id
});
isActiveReasoning = true;
} else if (chunk.type === "response.reasoning_text.delta") {
const reasoningChunk = chunk;
controller.enqueue({
type: "reasoning-delta",
id: reasoningChunk.item_id,
delta: reasoningChunk.delta
});
} else if (chunk.type === "response.output_item.done" && chunk.item.type === "reasoning") {
controller.enqueue({ type: "reasoning-end", id: chunk.item.id });
isActiveReasoning = false;
} else if (chunk.type === "response.output_item.added" && chunk.item.type === "message") {
controller.enqueue({ type: "text-start", id: chunk.item.id });
} else if (chunk.type === "response.output_text.delta") {
controller.enqueue({
type: "text-delta",
id: chunk.item_id,
delta: chunk.delta
});
} else if (chunk.type === "response.output_item.done" && chunk.item.type === "message") {
controller.enqueue({ type: "text-end", id: chunk.item.id });
} else if (chunk.type === "response.completed" || chunk.type === "response.incomplete") {
const reason = (_h = chunk.response.incomplete_details) == null ? void 0 : _h.reason;
finishReason = {
unified: mapOpenResponsesFinishReason({
finishReason: reason,
hasToolCalls
}),
raw: reason != null ? reason : void 0
};
updateUsage(chunk.response.usage);
} else if (chunk.type === "response.failed") {
finishReason = {
unified: "error",
raw: (_j = (_i = chunk.response.error) == null ? void 0 : _i.code) != null ? _j : chunk.response.status
};
updateUsage(chunk.response.usage);
}
},
flush(controller) {
if (isActiveReasoning) {
controller.enqueue({ type: "reasoning-end", id: "reasoning-0" });
}
controller.enqueue({
type: "finish",
finishReason,
usage,
providerMetadata: void 0
});
}
})
),
request: { body },
response: { headers: responseHeaders }
};
}
};
// src/open-responses-provider.ts
function createOpenResponses(options) {
const providerName = options.name;
const getHeaders = () => withUserAgentSuffix(
{
...options.apiKey ? {
Authorization: `Bearer ${options.apiKey}`
} : {},
...options.headers
},
`ai-sdk/open-responses/${VERSION}`
);
const createResponsesModel = (modelId) => {
return new OpenResponsesLanguageModel(modelId, {
provider: `${providerName}.responses`,
providerOptionsName: providerName,
headers: getHeaders,
url: options.url,
fetch: options.fetch,
generateId: () => generateId()
});
};
const createLanguageModel = (modelId) => {
if (new.target) {
throw new Error(
"The OpenAI model function cannot be called with the new keyword."
);
}
return createResponsesModel(modelId);
};
const provider = function(modelId) {
return createLanguageModel(modelId);
};
provider.specificationVersion = "v3";
provider.languageModel = createLanguageModel;
provider.embeddingModel = (modelId) => {
throw new NoSuchModelError({ modelId, modelType: "embeddingModel" });
};
provider.imageModel = (modelId) => {
throw new NoSuchModelError({ modelId, modelType: "imageModel" });
};
return provider;
}
export {
VERSION,
createOpenResponses
};
//# sourceMappingURL=index.mjs.map