@zhivex-ai/openai 0.13.7 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ export { OpenAIAgentsClient } from "./agents.js";
7
7
  import { toJSONSchema, z } from "zod";
8
8
  import { OpenAILiveModel, isOpenAILiveModel } from "./live.js";
9
9
  import { createOpenAIImageGenerationModel, normalizeOpenAIImageGenerationCall, normalizeOpenAIImageGenerationPartialImage } from "./image-generation.js";
10
- import { groundedCapabilities, supportsOpenAIModernResponses, isOpenAIAstraModel, isOpenAISolLunaModel, modelCapabilities, supportsOpenAIApplyPatchAndSkills, supportsOpenAIShell } from "./capabilities.js";
10
+ import { groundedCapabilities, supportsOpenAIModernResponses, assertOpenAIModelRequestSupported, resolveOpenAIModelProfile, modelCapabilities } from "./capabilities.js";
11
11
  import { imageInputToDataUrl, ConfigurationError, ProviderHTTPError, ProviderResponseTooLargeError, ProviderToolCallError, assertTrustedEndpoint, resolveAudioResponseLimits, openWebSocketConnection, hostedTool, providerDataPart, UnsupportedFeatureError, createProviderAdapter, isCallableToolDefinition, isHostedToolDefinition, normalizeFinishReason, streamSSE, streamChatCompletions, toolResultPayload, withRetry, withResponseRetry, withTimeoutSignal } from "@zhivex-ai/core/provider";
12
12
  const MIB = 1024 * 1024;
13
13
  const DEFAULT_HOSTED_IMAGE_EVENT_BYTES = 32 * MIB;
@@ -270,8 +270,7 @@ const responsesImageGenerationConfig = (tools, limits) => {
270
270
  : {})
271
271
  };
272
272
  };
273
- const assertResponsesToolsSupported = (modelId, tools) => {
274
- const currentCapabilities = modelCapabilities(modelId).agentCapabilities;
273
+ const assertResponsesToolsSupported = (modelId, tools, currentCapabilities) => {
275
274
  for (const definition of Object.values(tools ?? {})) {
276
275
  const type = isCallableToolDefinition(definition) ? openAILocalResponsesToolType(definition) : definition.type;
277
276
  if (type === "tool_search" && !currentCapabilities?.toolSearch) {
@@ -283,22 +282,22 @@ const assertResponsesToolsSupported = (modelId, tools) => {
283
282
  if (type === "computer" && !currentCapabilities?.computerUse) {
284
283
  throw new UnsupportedFeatureError(`Provider "openai" model "${modelId}" does not support the Responses computer tool.`);
285
284
  }
286
- if (type === "shell" && !supportsOpenAIShell(modelId)) {
285
+ if (type === "shell" && !currentCapabilities?.shell) {
287
286
  throw new UnsupportedFeatureError(`Provider "openai" model "${modelId}" does not support the Responses ${type} tool.`);
288
287
  }
289
- if (type === "apply_patch" && !supportsOpenAIApplyPatchAndSkills(modelId)) {
288
+ if (type === "apply_patch" && !currentCapabilities?.applyPatch) {
290
289
  throw new UnsupportedFeatureError(`Provider "openai" model "${modelId}" does not support the Responses ${type} tool.`);
291
290
  }
292
291
  if (type === "skill" && !currentCapabilities?.skills) {
293
292
  throw new UnsupportedFeatureError(`Provider "openai" model "${modelId}" does not support the Responses skills tool.`);
294
293
  }
295
- if (type === "programmatic_tool_calling" && !supportsOpenAIModernResponses(modelId)) {
294
+ if (type === "programmatic_tool_calling" && !currentCapabilities?.programmaticToolCalling) {
296
295
  throw new UnsupportedFeatureError(`Provider "openai" model "${modelId}" does not support Programmatic Tool Calling.`);
297
296
  }
298
297
  }
299
298
  };
300
- const assertOpenAIResponsesOptionsSupported = (modelId, options) => {
301
- if (options.multi_agent?.enabled && !supportsOpenAIModernResponses(modelId)) {
299
+ const assertOpenAIResponsesOptionsSupported = (modelId, options, currentCapabilities) => {
300
+ if (options.multi_agent?.enabled && !currentCapabilities?.multiAgent) {
302
301
  throw new UnsupportedFeatureError(`Provider "openai" model "${modelId}" does not support Multi-agent.`);
303
302
  }
304
303
  };
@@ -934,7 +933,7 @@ const addTokenUsage = (left, right) => {
934
933
  return right;
935
934
  if (!right)
936
935
  return left;
937
- const sum = (a, b) => a === undefined && b === undefined ? undefined : (a ?? 0) + (b ?? 0);
936
+ const sum = (a, b) => a === undefined || b === undefined ? undefined : a + b;
938
937
  return {
939
938
  inputTokens: sum(left.inputTokens, right.inputTokens),
940
939
  cachedInputTokens: sum(left.cachedInputTokens, right.cachedInputTokens),
@@ -1382,7 +1381,8 @@ const streamGenerateResult = async function* (result) {
1382
1381
  type: "finish",
1383
1382
  finishReason: result.finishReason,
1384
1383
  providerFinishReason: result.providerFinishReason,
1385
- usage: result.usage
1384
+ usage: result.usage,
1385
+ providerRequestCount: result.providerRequestCount
1386
1386
  };
1387
1387
  };
1388
1388
  const extractSources = (value) => {
@@ -1419,45 +1419,43 @@ class OpenAILanguageModel {
1419
1419
  responseLimits;
1420
1420
  provider = "openai";
1421
1421
  capabilities;
1422
- constructor(modelId, apiKey, baseURL, fetcher, responseLimits) {
1422
+ constructor(modelId, apiKey, baseURL, fetcher, responseLimits, capabilityOptions) {
1423
1423
  this.modelId = modelId;
1424
1424
  this.apiKey = apiKey;
1425
1425
  this.baseURL = baseURL;
1426
1426
  this.fetcher = fetcher;
1427
1427
  this.responseLimits = responseLimits;
1428
- this.capabilities = { ...modelCapabilities(modelId), toolHistory: "json" };
1428
+ const base = modelCapabilities(modelId, capabilityOptions.unknownModelCapabilities);
1429
+ const override = (capabilityOptions.modelCapabilities && Object.hasOwn(capabilityOptions.modelCapabilities, modelId) ? capabilityOptions.modelCapabilities[modelId] : undefined);
1430
+ this.capabilities = { ...base, ...override,
1431
+ agentCapabilities: { ...base.agentCapabilities, ...override?.agentCapabilities }, toolHistory: "json" };
1429
1432
  }
1430
1433
  usesResponsesAPI(input, options) {
1431
- if (isOpenAISolLunaModel(this.modelId)) {
1432
- const raw = options.bodyOptions;
1433
- const effort = input.reasoning?.effort ?? raw.reasoning?.effort ?? raw.reasoning_effort;
1434
- if (effort !== undefined && !["none", "low", "medium", "high", "xhigh", "max"].includes(String(effort))) {
1435
- throw new UnsupportedFeatureError(`GPT-6 Sol/Luna does not support reasoning effort "${effort}".`);
1436
- }
1437
- if (effort !== "none") {
1438
- if (input.temperature !== undefined || raw.temperature !== undefined || raw.top_p !== undefined || raw.top_logprobs !== undefined ||
1439
- (options.apiMode === "chat" && raw.logprobs !== undefined) || raw.include?.includes("message.output_text.logprobs")) {
1440
- throw new UnsupportedFeatureError('GPT-6 Sol/Luna sampling and logprobs controls require reasoning effort "none".');
1441
- }
1442
- if (options.apiMode === "chat" && Object.keys(input.tools ?? {}).length) {
1443
- throw new UnsupportedFeatureError('GPT-6 Sol/Luna tool calling with reasoning requires the Responses API; Chat Completions requires effort "none".');
1444
- }
1445
- }
1434
+ if (!this.capabilities.tools && (Object.keys(input.tools ?? {}).length || input.messages.some(message => message.parts.some(part => part.type === "tool-call" || part.type === "tool-result")))) {
1435
+ throw new UnsupportedFeatureError(`OpenAI model "${this.modelId}" has no declared tool support.`);
1446
1436
  }
1447
- if (isOpenAIAstraModel(this.modelId)) {
1448
- const raw = options.bodyOptions;
1449
- const effort = input.reasoning?.effort ?? raw.reasoning?.effort ?? raw.reasoning_effort;
1450
- if (effort !== undefined && !["low", "medium", "high", "xhigh", "max"].includes(String(effort))) {
1451
- throw new UnsupportedFeatureError(`GPT-6 Astra does not support reasoning effort "${effort}".`);
1452
- }
1453
- if (input.temperature !== undefined || raw.temperature !== undefined || raw.top_p !== undefined || raw.top_logprobs !== undefined ||
1454
- (options.apiMode === "chat" && raw.logprobs !== undefined) || raw.include?.includes("message.output_text.logprobs")) {
1455
- throw new UnsupportedFeatureError("GPT-6 Astra does not support the requested sampling or logprobs controls.");
1456
- }
1457
- if (options.apiMode === "chat" && Object.keys(input.tools ?? {}).length) {
1458
- throw new UnsupportedFeatureError("GPT-6 Astra tool calling requires the Responses API.");
1459
- }
1437
+ if (!this.capabilities.vision && input.messages.some(message => message.parts.some(part => part.type === "image"))) {
1438
+ throw new UnsupportedFeatureError(`OpenAI model "${this.modelId}" has no declared vision support.`);
1460
1439
  }
1440
+ const rawFormat = options.bodyOptions.response_format;
1441
+ const rawText = options.bodyOptions.text;
1442
+ const formatTypes = [rawFormat?.type, rawText?.format?.type];
1443
+ if (!this.capabilities.structuredOutput && (input.structuredOutput?.mode === "native" || formatTypes.includes("json_schema"))) {
1444
+ throw new UnsupportedFeatureError(`OpenAI model "${this.modelId}" has no declared native structured output support.`);
1445
+ }
1446
+ if (!this.capabilities.jsonMode && formatTypes.includes("json_object")) {
1447
+ throw new UnsupportedFeatureError(`OpenAI model "${this.modelId}" has no declared JSON mode support.`);
1448
+ }
1449
+ if (!this.capabilities.parallelToolCalls && options.bodyOptions.parallel_tool_calls === true) {
1450
+ throw new UnsupportedFeatureError(`OpenAI model "${this.modelId}" has no declared parallel tool calling support.`);
1451
+ }
1452
+ if (!this.capabilities.reasoning && (input.reasoning || options.bodyOptions.reasoning || options.bodyOptions.reasoning_effort)) {
1453
+ throw new UnsupportedFeatureError(`OpenAI model "${this.modelId}" has no declared reasoning support.`);
1454
+ }
1455
+ if (!this.capabilities.toolChoice && (input.toolChoice !== undefined || options.bodyOptions.tool_choice !== undefined)) {
1456
+ throw new UnsupportedFeatureError(`OpenAI model "${this.modelId}" has no declared tool choice support.`);
1457
+ }
1458
+ assertOpenAIModelRequestSupported(this.modelId, input, options);
1461
1459
  const requiresResponses = hasResponsesOnlyTools(input.tools) || options.multiAgentEnabled;
1462
1460
  if (options.apiMode === "chat") {
1463
1461
  if (requiresResponses) {
@@ -1465,12 +1463,12 @@ class OpenAILanguageModel {
1465
1463
  }
1466
1464
  return false;
1467
1465
  }
1468
- return options.apiMode === "responses" || requiresResponses || supportsOpenAIModernResponses(this.modelId);
1466
+ return options.apiMode === "responses" || requiresResponses || resolveOpenAIModelProfile(this.modelId).defaultApi === "responses";
1469
1467
  }
1470
1468
  async generateViaResponses(input, signal, options) {
1471
1469
  const responseBodyOptions = openAIResponsesBodyOptions(options.bodyOptions, this.modelId);
1472
- assertResponsesToolsSupported(this.modelId, input.tools);
1473
- assertOpenAIResponsesOptionsSupported(this.modelId, responseBodyOptions);
1470
+ assertResponsesToolsSupported(this.modelId, input.tools, this.capabilities.agentCapabilities);
1471
+ assertOpenAIResponsesOptionsSupported(this.modelId, responseBodyOptions, this.capabilities.agentCapabilities);
1474
1472
  const previousResponse = responseBodyOptions.store === false ? undefined : getProviderResponseId(input.messages);
1475
1473
  const messages = previousResponse && previousResponse.index < input.messages.length - 1
1476
1474
  ? input.messages.slice(previousResponse.index + 1)
@@ -1478,75 +1476,119 @@ class OpenAILanguageModel {
1478
1476
  let nextPreviousResponseId = previousResponse?.responseId;
1479
1477
  let nextInput = messages.length ? toResponsesInput(messages, input.toolResultFormat, input.messages) : [];
1480
1478
  let accumulatedUsage;
1479
+ const ptc = hasProgrammaticToolCalling(input.tools);
1480
+ const requestLimit = Math.min(input.maxProviderRequests ?? 8, 8);
1481
+ if (!Number.isSafeInteger(requestLimit) || requestLimit < 1)
1482
+ throw new ConfigurationError("maxProviderRequests must be a positive integer.");
1483
+ let providerRequestCount = 0;
1484
+ let usageComplete = true;
1485
+ const failure = (diagnosticCode, cause) => new ProviderToolCallError({
1486
+ provider: "openai", transport: "responses", diagnosticCode,
1487
+ reason: cause instanceof ProviderToolCallError ? cause.reason : "response_failed",
1488
+ retryable: false, effectsPossible: providerRequestCount > 0,
1489
+ confirmedUsage: accumulatedUsage, usageComplete, providerRequestCount
1490
+ });
1481
1491
  let statelessInternalOutputs = [];
1482
- for (let continuation = 0; continuation < 8; continuation += 1) {
1483
- const response = await withResponseRetry(() => this.fetcher(`${this.baseURL}/responses`, {
1484
- method: "POST",
1485
- headers: options.headers,
1486
- signal,
1487
- body: JSON.stringify({
1488
- ...responseBodyOptions,
1489
- model: this.modelId,
1490
- ...(nextPreviousResponseId ? { previous_response_id: nextPreviousResponseId } : {}),
1491
- ...(nextInput.length ? { input: nextInput } : {}),
1492
- tools: mapResponsesTools(input.tools),
1493
- ...(input.toolChoice ? { tool_choice: mapResponsesToolChoice(input.toolChoice) } : {}),
1494
- text: mapResponsesStructuredOutput(input),
1495
- temperature: input.temperature,
1496
- max_output_tokens: input.maxTokens,
1497
- ...mapResponsesReasoning(input, responseBodyOptions.reasoning)
1498
- })
1499
- }), { ...input, abortSignal: signal }, "OpenAI");
1500
- const json = await parseJson(response);
1501
- accumulatedUsage = addTokenUsage(accumulatedUsage, mapResponsesUsage(json.usage));
1502
- const assistantMessage = parseResponsesAssistantMessage(json, options.multiAgentEnabled, localResponsesTools(input.tools), this.responseLimits.toolCallArgumentChars);
1503
- const currentOutput = Array.isArray(json.output)
1504
- ? json.output
1505
- : [];
1506
- const completeStatelessOutput = [...statelessInternalOutputs, ...currentOutput];
1507
- if (responseBodyOptions.store === false && completeStatelessOutput.length) {
1508
- assistantMessage.parts = assistantMessage.parts.filter((part) => part.type !== "provider-data" ||
1509
- part.provider !== "openai" ||
1510
- !part.data ||
1511
- typeof part.data !== "object" ||
1512
- part.data.type !== "responses_output");
1513
- assistantMessage.parts.push(providerDataPart("openai", {
1514
- type: "responses_output",
1515
- items: completeStatelessOutput
1516
- }));
1517
- }
1518
- const hasToolCalls = assistantMessage.parts.some((part) => part.type === "tool-call");
1519
- const hasRefusal = (json.output ?? []).some((item) => item?.type === "message" &&
1520
- (item.content ?? []).some((content) => content?.type === "refusal"));
1521
- const hasFinalMessage = (json.output ?? []).some((item) => item?.type === "message" &&
1522
- (!options.multiAgentEnabled || (item.agent?.agent_name === "/root" && item.phase === "final_answer")));
1523
- const shouldContinueProgram = hasProgrammaticToolCalling(input.tools) &&
1524
- json.status === "completed" &&
1525
- !hasToolCalls &&
1526
- !hasFinalMessage &&
1527
- (json.output ?? []).some((item) => item?.type === "program" || item?.type === "program_output");
1528
- if (!shouldContinueProgram) {
1529
- return {
1530
- messages: [assistantMessage],
1531
- text: extractMessageText(assistantMessage),
1532
- audio: extractAudioOutputs(assistantMessage),
1533
- images: extractResponsesImageOutputs(json, responsesImageGenerationConfig(input.tools)?.outputFormat),
1534
- finishReason: normalizeResponsesFinishReason(json.status, hasToolCalls, hasRefusal),
1535
- providerFinishReason: json.status,
1536
- usage: accumulatedUsage,
1537
- rawResponse: json
1538
- };
1539
- }
1540
- if (responseBodyOptions.store === false) {
1541
- statelessInternalOutputs = completeStatelessOutput;
1542
- nextInput = [...nextInput, ...currentOutput];
1543
- }
1544
- else {
1545
- nextPreviousResponseId = json.id;
1546
- nextInput = [];
1492
+ try {
1493
+ for (let continuation = 0; continuation < requestLimit; continuation += 1) {
1494
+ const remainingOutputTokens = input.maxTokens === undefined ? undefined
1495
+ : input.maxTokens - (accumulatedUsage?.outputTokens ?? 0);
1496
+ if (ptc && remainingOutputTokens !== undefined && remainingOutputTokens < 1)
1497
+ throw failure("OPENAI_PTC_OUTPUT_LIMIT");
1498
+ if (ptc) {
1499
+ signal?.throwIfAborted();
1500
+ providerRequestCount++;
1501
+ usageComplete = false;
1502
+ }
1503
+ const response = await withResponseRetry(() => this.fetcher(`${this.baseURL}/responses`, {
1504
+ method: "POST",
1505
+ headers: options.headers,
1506
+ signal,
1507
+ body: JSON.stringify({
1508
+ ...responseBodyOptions,
1509
+ model: this.modelId,
1510
+ ...(nextPreviousResponseId ? { previous_response_id: nextPreviousResponseId } : {}),
1511
+ ...(nextInput.length ? { input: nextInput } : {}),
1512
+ tools: mapResponsesTools(input.tools),
1513
+ ...(input.toolChoice ? { tool_choice: mapResponsesToolChoice(input.toolChoice) } : {}),
1514
+ text: mapResponsesStructuredOutput(input) ?? responseBodyOptions.text,
1515
+ temperature: input.temperature,
1516
+ max_output_tokens: ptc ? remainingOutputTokens : input.maxTokens,
1517
+ ...mapResponsesReasoning(input, responseBodyOptions.reasoning)
1518
+ })
1519
+ }), { ...input, abortSignal: signal, ...(ptc ? { maxRetries: 0 } : {}) }, "OpenAI");
1520
+ const json = await parseJson(response);
1521
+ const reportedUsage = mapResponsesUsage(json.usage);
1522
+ if (ptc && (!reportedUsage || ![reportedUsage.inputTokens, reportedUsage.outputTokens, reportedUsage.totalTokens].every(value => typeof value === "number" && Number.isSafeInteger(value) && value >= 0) || reportedUsage.totalTokens < reportedUsage.inputTokens + reportedUsage.outputTokens || Object.values(reportedUsage).some(value => value !== undefined && (!Number.isSafeInteger(value) || value < 0)))) {
1523
+ throw failure("OPENAI_PTC_USAGE_UNKNOWN");
1524
+ }
1525
+ const nextUsage = addTokenUsage(accumulatedUsage, reportedUsage);
1526
+ if (ptc && Object.values(nextUsage ?? {}).some(value => value !== undefined && !Number.isSafeInteger(value)))
1527
+ throw failure("OPENAI_PTC_USAGE_UNKNOWN");
1528
+ accumulatedUsage = nextUsage;
1529
+ if (ptc)
1530
+ usageComplete = true;
1531
+ if (ptc && input.maxTokens !== undefined && accumulatedUsage.outputTokens > input.maxTokens)
1532
+ throw failure("OPENAI_PTC_OUTPUT_LIMIT");
1533
+ const assistantMessage = parseResponsesAssistantMessage(json, options.multiAgentEnabled, localResponsesTools(input.tools), this.responseLimits.toolCallArgumentChars);
1534
+ const currentOutput = Array.isArray(json.output)
1535
+ ? json.output
1536
+ : [];
1537
+ const completeStatelessOutput = [...statelessInternalOutputs, ...currentOutput];
1538
+ if (responseBodyOptions.store === false && completeStatelessOutput.length) {
1539
+ assistantMessage.parts = assistantMessage.parts.filter((part) => part.type !== "provider-data" ||
1540
+ part.provider !== "openai" ||
1541
+ !part.data ||
1542
+ typeof part.data !== "object" ||
1543
+ part.data.type !== "responses_output");
1544
+ assistantMessage.parts.push(providerDataPart("openai", {
1545
+ type: "responses_output",
1546
+ items: completeStatelessOutput
1547
+ }));
1548
+ }
1549
+ const hasToolCalls = assistantMessage.parts.some((part) => part.type === "tool-call");
1550
+ const hasRefusal = (json.output ?? []).some((item) => item?.type === "message" &&
1551
+ (item.content ?? []).some((content) => content?.type === "refusal"));
1552
+ const hasFinalMessage = (json.output ?? []).some((item) => item?.type === "message" &&
1553
+ (!options.multiAgentEnabled || (item.agent?.agent_name === "/root" && item.phase === "final_answer")));
1554
+ const shouldContinueProgram = hasProgrammaticToolCalling(input.tools) &&
1555
+ json.status === "completed" &&
1556
+ !hasToolCalls &&
1557
+ !hasFinalMessage &&
1558
+ (json.output ?? []).some((item) => item?.type === "program" || item?.type === "program_output");
1559
+ if (!shouldContinueProgram) {
1560
+ return {
1561
+ messages: [assistantMessage],
1562
+ text: extractMessageText(assistantMessage),
1563
+ audio: extractAudioOutputs(assistantMessage),
1564
+ images: extractResponsesImageOutputs(json, responsesImageGenerationConfig(input.tools)?.outputFormat),
1565
+ finishReason: normalizeResponsesFinishReason(json.status, hasToolCalls, hasRefusal),
1566
+ providerFinishReason: json.status,
1567
+ usage: accumulatedUsage,
1568
+ ...(ptc ? { providerRequestCount } : {}),
1569
+ rawResponse: json
1570
+ };
1571
+ }
1572
+ if (responseBodyOptions.store === false) {
1573
+ statelessInternalOutputs = completeStatelessOutput;
1574
+ nextInput = [...nextInput, ...currentOutput];
1575
+ }
1576
+ else {
1577
+ nextPreviousResponseId = json.id;
1578
+ nextInput = [];
1579
+ }
1547
1580
  }
1581
+ throw failure("OPENAI_PTC_REQUEST_LIMIT");
1582
+ }
1583
+ catch (error) {
1584
+ if (!ptc)
1585
+ throw error;
1586
+ if (error instanceof ProviderToolCallError && error.providerRequestCount !== undefined)
1587
+ throw error;
1588
+ // Never replay a dispatched program; the receipt distinguishes a confirmed
1589
+ // prefix from a failed/uncertain request and intentionally omits raw payloads.
1590
+ throw failure(error instanceof ProviderToolCallError ? error.diagnosticCode : "OPENAI_PTC_REQUEST_FAILED", error);
1548
1591
  }
1549
- throw new ProviderHTTPError("OpenAI Programmatic Tool Calling exceeded 8 internal continuations.", 500);
1550
1592
  }
1551
1593
  async generate(input) {
1552
1594
  const { signal, cleanup } = getRequestOptions(input);
@@ -1565,7 +1607,7 @@ class OpenAILanguageModel {
1565
1607
  messages: mapMessages(input.messages, input.toolResultFormat),
1566
1608
  tools: mapTools(input.tools),
1567
1609
  ...(input.toolChoice ? { tool_choice: mapToolChoice(input.toolChoice) } : {}),
1568
- response_format: mapStructuredOutput(input),
1610
+ response_format: mapStructuredOutput(input) ?? options.bodyOptions.response_format,
1569
1611
  temperature: input.temperature,
1570
1612
  ...(input.reasoning ? {} : { max_tokens: input.maxTokens }),
1571
1613
  ...mapChatReasoning(input),
@@ -1594,19 +1636,18 @@ class OpenAILanguageModel {
1594
1636
  const options = resolveOpenAILanguageRequestOptions(input.providerOptions, this.apiKey);
1595
1637
  if (this.usesResponsesAPI(input, options)) {
1596
1638
  const responseBodyOptions = openAIResponsesBodyOptions(options.bodyOptions, this.modelId);
1597
- assertResponsesToolsSupported(this.modelId, input.tools);
1598
- assertOpenAIResponsesOptionsSupported(this.modelId, responseBodyOptions);
1639
+ assertResponsesToolsSupported(this.modelId, input.tools, this.capabilities.agentCapabilities);
1640
+ assertOpenAIResponsesOptionsSupported(this.modelId, responseBodyOptions, this.capabilities.agentCapabilities);
1599
1641
  const { signal, cleanup } = getRequestOptions(input);
1600
1642
  if (hasProgrammaticToolCalling(input.tools)) {
1601
- const result = await this.generateViaResponses(input, signal, options);
1602
- return (async function* () {
1603
- try {
1604
- yield* streamGenerateResult(result);
1605
- }
1606
- finally {
1607
- cleanup();
1608
- }
1609
- })();
1643
+ try {
1644
+ const result = await this.generateViaResponses(input, signal, options);
1645
+ return streamGenerateResult(result);
1646
+ }
1647
+ finally {
1648
+ // PTC is fully materialized before an iterator is returned.
1649
+ cleanup();
1650
+ }
1610
1651
  }
1611
1652
  const previousResponse = responseBodyOptions.store === false ? undefined : getProviderResponseId(input.messages);
1612
1653
  const messages = previousResponse && previousResponse.index < input.messages.length - 1
@@ -1623,7 +1664,7 @@ class OpenAILanguageModel {
1623
1664
  ...(messages.length ? { input: toResponsesInput(messages, input.toolResultFormat, input.messages) } : {}),
1624
1665
  tools: mapResponsesTools(input.tools),
1625
1666
  ...(input.toolChoice ? { tool_choice: mapResponsesToolChoice(input.toolChoice) } : {}),
1626
- text: mapResponsesStructuredOutput(input),
1667
+ text: mapResponsesStructuredOutput(input) ?? responseBodyOptions.text,
1627
1668
  temperature: input.temperature,
1628
1669
  max_output_tokens: input.maxTokens,
1629
1670
  ...mapResponsesReasoning(input, responseBodyOptions.reasoning),
@@ -1652,7 +1693,7 @@ class OpenAILanguageModel {
1652
1693
  messages: mapMessages(input.messages, input.toolResultFormat),
1653
1694
  tools: mapTools(input.tools),
1654
1695
  ...(input.toolChoice ? { tool_choice: mapToolChoice(input.toolChoice) } : {}),
1655
- response_format: mapStructuredOutput(input),
1696
+ response_format: mapStructuredOutput(input) ?? options.bodyOptions.response_format,
1656
1697
  temperature: input.temperature,
1657
1698
  ...(input.reasoning ? {} : { max_tokens: input.maxTokens }),
1658
1699
  ...mapChatReasoning(input),
@@ -1751,7 +1792,7 @@ export const createOpenAI = (options = {}) => {
1751
1792
  languageModel: (modelId) => {
1752
1793
  if (modelId.startsWith("gpt-live-"))
1753
1794
  throw new UnsupportedFeatureError("GPT-Live is a voice model; use openai.realtimeModel(modelId).");
1754
- return new OpenAILanguageModel(modelId, apiKey, baseURL, fetcher, responseLimits);
1795
+ return new OpenAILanguageModel(modelId, apiKey, baseURL, fetcher, responseLimits, options);
1755
1796
  },
1756
1797
  embeddingModel: (modelId) => new OpenAIEmbeddingModel(modelId, apiKey, baseURL, fetcher),
1757
1798
  transcriptionModel: (modelId) => new OpenAITranscriptionModel(modelId, apiKey, baseURL, fetcher, responseLimits),