🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@ai-sdk/openai

Package Overview
Dependencies
Maintainers
3
Versions
606
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ai-sdk/openai - npm Package Compare versions

Comparing version
3.0.81
to
3.0.82
+181
src/openai-stream-error.ts
import { APICallError } from '@ai-sdk/provider';
import type { ParseResult } from '@ai-sdk/provider-utils';
type StreamError = {
message: string;
code?: string | number | null;
type?: string | null;
frame: unknown;
};
export async function throwIfOpenAIStreamErrorBeforeOutput<T>({
stream,
getError,
isOutputChunk,
url,
requestBodyValues,
responseHeaders,
}: {
stream: ReadableStream<ParseResult<T>>;
getError: (chunk: T) => unknown | undefined;
isOutputChunk: (chunk: T) => boolean;
url: string;
requestBodyValues: unknown;
responseHeaders?: Record<string, string>;
}): Promise<ReadableStream<ParseResult<T>>> {
const [streamForEarlyError, streamForConsumer] = stream.tee();
const reader = streamForEarlyError.getReader();
try {
while (true) {
const result = await reader.read();
if (result.done) {
return streamForConsumer;
}
const chunk = result.value;
if (!chunk.success) {
return streamForConsumer;
}
const errorFrame = getError(chunk.value);
if (errorFrame != null) {
streamForConsumer.cancel().catch(() => {});
throw createOpenAIStreamError({
frame: errorFrame,
url,
requestBodyValues,
responseHeaders,
});
}
if (isOutputChunk(chunk.value)) {
return streamForConsumer;
}
}
} finally {
reader.cancel().catch(() => {});
reader.releaseLock();
}
}
function createOpenAIStreamError({
frame,
url,
requestBodyValues,
responseHeaders,
}: {
frame: unknown;
url: string;
requestBodyValues: unknown;
responseHeaders?: Record<string, string>;
}): APICallError {
const streamError = parseStreamError(frame);
return new APICallError({
message:
streamError?.message ??
'OpenAI stream failed before any output was generated',
url,
requestBodyValues,
statusCode: streamError == null ? 500 : getStatusCode(streamError),
responseHeaders,
responseBody: JSON.stringify(frame),
data: frame,
});
}
function parseStreamError(frame: unknown): StreamError | undefined {
const value = asRecord(frame);
if (value == null) {
return undefined;
}
if (value.type === 'response.failed') {
const response = asRecord(value.response);
const responseError = asRecord(response?.error);
return typeof responseError?.message === 'string'
? {
message: responseError.message,
code: getStringOrNumber(responseError.code),
type: 'response.failed',
frame,
}
: undefined;
}
const error = asRecord(value.error) ?? value;
return typeof error.message === 'string' &&
(asRecord(value.error) != null ||
typeof error.type === 'string' ||
'code' in error ||
'param' in error)
? {
message: error.message,
code: getStringOrNumber(error.code),
type: typeof error.type === 'string' ? error.type : undefined,
frame,
}
: undefined;
}
function getStatusCode(error: StreamError): number {
if (typeof error.code === 'number' && isHttpErrorStatusCode(error.code)) {
return error.code;
}
if (typeof error.code === 'string' && /^\d{3}$/.test(error.code)) {
const numericCode = Number(error.code);
if (isHttpErrorStatusCode(numericCode)) {
return numericCode;
}
}
const discriminator = [error.code, error.type]
.filter(value => typeof value === 'string' || typeof value === 'number')
.join(' ')
.toLowerCase();
if (
['insufficient_quota', 'rate_limit'].some(term =>
discriminator.includes(term),
)
) {
return 429;
}
if (discriminator.includes('authentication')) return 401;
if (discriminator.includes('permission')) return 403;
if (discriminator.includes('not_found')) return 404;
if (
['invalid', 'bad_request', 'context_length'].some(term =>
discriminator.includes(term),
)
) {
return 400;
}
if (discriminator.includes('overload')) return 503;
if (discriminator.includes('timeout')) return 504;
return 500;
}
function asRecord(value: unknown): Record<string, unknown> | undefined {
return typeof value === 'object' && value != null
? (value as Record<string, unknown>)
: undefined;
}
function getStringOrNumber(value: unknown): string | number | undefined {
return typeof value === 'string' || typeof value === 'number'
? value
: undefined;
}
function isHttpErrorStatusCode(value: number): boolean {
return Number.isInteger(value) && value >= 400 && value <= 599;
}
+16
-9

@@ -213,2 +213,17 @@ import * as _ai_sdk_provider from '@ai-sdk/provider';

} | {
type: "error";
sequence_number: number;
error: {
type: string;
code: string;
message: string;
param?: string | null | undefined;
};
} | {
type: "error";
sequence_number: number;
message: string;
code?: string | null | undefined;
param?: string | null | undefined;
} | {
type: "response.output_text.delta";

@@ -248,2 +263,3 @@ item_id: string;

type: "response.failed";
sequence_number: number;
response: {

@@ -656,11 +672,2 @@ error?: {

diff: string;
} | {
type: "error";
sequence_number: number;
error: {
type: string;
code: string;
message: string;
param?: string | null | undefined;
};
}>;

@@ -667,0 +674,0 @@ type OpenAIResponsesChunk = InferSchema<typeof openaiResponsesChunkSchema>;

@@ -213,2 +213,17 @@ import * as _ai_sdk_provider from '@ai-sdk/provider';

} | {
type: "error";
sequence_number: number;
error: {
type: string;
code: string;
message: string;
param?: string | null | undefined;
};
} | {
type: "error";
sequence_number: number;
message: string;
code?: string | null | undefined;
param?: string | null | undefined;
} | {
type: "response.output_text.delta";

@@ -248,2 +263,3 @@ item_id: string;

type: "response.failed";
sequence_number: number;
response: {

@@ -656,11 +672,2 @@ error?: {

diff: string;
} | {
type: "error";
sequence_number: number;
error: {
type: string;
code: string;
message: string;
param?: string | null | undefined;
};
}>;

@@ -667,0 +674,0 @@ type OpenAIResponsesChunk = InferSchema<typeof openaiResponsesChunkSchema>;

@@ -266,2 +266,17 @@ import { LanguageModelV3, LanguageModelV3CallOptions, LanguageModelV3GenerateResult, LanguageModelV3StreamResult, EmbeddingModelV3, ImageModelV3, TranscriptionModelV3CallOptions, TranscriptionModelV3, SpeechModelV3, JSONValue } from '@ai-sdk/provider';

} | {
type: "error";
sequence_number: number;
error: {
type: string;
code: string;
message: string;
param?: string | null | undefined;
};
} | {
type: "error";
sequence_number: number;
message: string;
code?: string | null | undefined;
param?: string | null | undefined;
} | {
type: "response.output_text.delta";

@@ -301,2 +316,3 @@ item_id: string;

type: "response.failed";
sequence_number: number;
response: {

@@ -709,11 +725,2 @@ error?: {

diff: string;
} | {
type: "error";
sequence_number: number;
error: {
type: string;
code: string;
message: string;
param?: string | null | undefined;
};
}>;

@@ -720,0 +727,0 @@ type OpenAIResponsesChunk = InferSchema<typeof openaiResponsesChunkSchema>;

@@ -266,2 +266,17 @@ import { LanguageModelV3, LanguageModelV3CallOptions, LanguageModelV3GenerateResult, LanguageModelV3StreamResult, EmbeddingModelV3, ImageModelV3, TranscriptionModelV3CallOptions, TranscriptionModelV3, SpeechModelV3, JSONValue } from '@ai-sdk/provider';

} | {
type: "error";
sequence_number: number;
error: {
type: string;
code: string;
message: string;
param?: string | null | undefined;
};
} | {
type: "error";
sequence_number: number;
message: string;
code?: string | null | undefined;
param?: string | null | undefined;
} | {
type: "response.output_text.delta";

@@ -301,2 +316,3 @@ item_id: string;

type: "response.failed";
sequence_number: number;
response: {

@@ -709,11 +725,2 @@ error?: {

diff: string;
} | {
type: "error";
sequence_number: number;
error: {
type: string;
code: string;
message: string;
param?: string | null | undefined;
};
}>;

@@ -720,0 +727,0 @@ type OpenAIResponsesChunk = InferSchema<typeof openaiResponsesChunkSchema>;

{
"name": "@ai-sdk/openai",
"version": "3.0.81",
"version": "3.0.82",
"license": "Apache-2.0",

@@ -40,3 +40,3 @@ "sideEffects": false,

"@ai-sdk/provider": "3.0.13",
"@ai-sdk/provider-utils": "4.0.36"
"@ai-sdk/provider-utils": "4.0.37"
},

@@ -43,0 +43,0 @@ "devDependencies": {

@@ -203,3 +203,3 @@ import {

case 'execution-denied':
contentValue = output.reason ?? 'Tool execution denied.';
contentValue = output.reason ?? 'Tool call execution denied.';
break;

@@ -206,0 +206,0 @@ case 'content':

@@ -18,3 +18,2 @@ import {

generateId,
isParsableJson,
parseProviderOptions,

@@ -27,2 +26,3 @@ postJsonToApi,

import { getOpenAILanguageModelCapabilities } from '../openai-language-model-capabilities';
import { throwIfOpenAIStreamErrorBeforeOutput } from '../openai-stream-error';
import {

@@ -419,7 +419,9 @@ convertOpenAIChatUsage,

const url = this.config.url({
path: '/chat/completions',
modelId: this.modelId,
});
const { responseHeaders, value: response } = await postJsonToApi({
url: this.config.url({
path: '/chat/completions',
modelId: this.modelId,
}),
url,
headers: combineHeaders(this.config.headers(), options.headers),

@@ -435,2 +437,11 @@ body,

const checkedResponse = await throwIfOpenAIStreamErrorBeforeOutput({
stream: response,
getError: chunk => ('error' in chunk ? chunk.error : undefined),
isOutputChunk: isOpenAIChatOutputChunk,
url,
requestBodyValues: body,
responseHeaders,
});
const toolCalls: Array<{

@@ -456,4 +467,4 @@ id: string;

return {
stream: response.pipeThrough(
const result = {
stream: checkedResponse.pipeThrough(
new TransformStream<

@@ -613,19 +624,2 @@ ParseResult<OpenAIChatChunk>,

}
// check if tool call is complete
// (some providers send the full tool call in one chunk):
if (isParsableJson(toolCall.function.arguments)) {
controller.enqueue({
type: 'tool-input-end',
id: toolCall.id,
});
controller.enqueue({
type: 'tool-call',
toolCallId: toolCall.id ?? generateId(),
toolName: toolCall.function.name,
input: toolCall.function.arguments,
});
toolCall.hasFinished = true;
}
}

@@ -654,22 +648,2 @@

});
// check if tool call is complete
if (
toolCall.function?.name != null &&
toolCall.function?.arguments != null &&
isParsableJson(toolCall.function.arguments)
) {
controller.enqueue({
type: 'tool-input-end',
id: toolCall.id,
});
controller.enqueue({
type: 'tool-call',
toolCallId: toolCall.id ?? generateId(),
toolName: toolCall.function.name,
input: toolCall.function.arguments,
});
toolCall.hasFinished = true;
}
}

@@ -697,2 +671,22 @@ }

// Finalize any unfinished tool calls. Tool calls are only
// finalized here (on stream end) to prevent premature
// execution when partial JSON is coincidentally parsable.
for (const toolCall of toolCalls) {
if (!toolCall.hasFinished) {
controller.enqueue({
type: 'tool-input-end',
id: toolCall.id,
});
controller.enqueue({
type: 'tool-call',
toolCallId: toolCall.id ?? generateId(),
toolName: toolCall.function.name,
input: toolCall.function.arguments,
});
toolCall.hasFinished = true;
}
}
controller.enqueue({

@@ -710,3 +704,21 @@ type: 'finish',

};
return result;
}
}
function isOpenAIChatOutputChunk(chunk: OpenAIChatChunk): boolean {
if ('error' in chunk) {
return false;
}
return chunk.choices.some(choice => {
const delta = choice.delta;
return (
(delta?.content != null && delta.content.length > 0) ||
(delta?.tool_calls != null && delta.tool_calls.length > 0) ||
(delta?.annotations != null && delta.annotations.length > 0)
);
});
}

@@ -21,2 +21,3 @@ import type {

import { openaiFailedResponseHandler } from '../openai-error';
import { throwIfOpenAIStreamErrorBeforeOutput } from '../openai-stream-error';
import {

@@ -228,7 +229,9 @@ convertOpenAICompletionUsage,

const url = this.config.url({
path: '/completions',
modelId: this.modelId,
});
const { responseHeaders, value: response } = await postJsonToApi({
url: this.config.url({
path: '/completions',
modelId: this.modelId,
}),
url,
headers: combineHeaders(this.config.headers(), options.headers),

@@ -244,2 +247,11 @@ body,

const checkedResponse = await throwIfOpenAIStreamErrorBeforeOutput({
stream: response,
getError: chunk => ('error' in chunk ? chunk.error : undefined),
isOutputChunk: isOpenAICompletionOutputChunk,
url,
requestBodyValues: body,
responseHeaders,
});
let finishReason: LanguageModelV3FinishReason = {

@@ -253,4 +265,4 @@ unified: 'other',

return {
stream: response.pipeThrough(
const result = {
stream: checkedResponse.pipeThrough(
new TransformStream<

@@ -339,3 +351,11 @@ ParseResult<OpenAICompletionChunk>,

};
return result;
}
}
function isOpenAICompletionOutputChunk(chunk: OpenAICompletionChunk): boolean {
return (
!('error' in chunk) && chunk.choices.some(choice => choice.text.length > 0)
);
}

@@ -737,3 +737,3 @@ import {

case 'execution-denied':
outputValue = output.reason ?? 'Tool execution denied.';
outputValue = output.reason ?? 'Tool call execution denied.';
break;

@@ -805,3 +805,3 @@ case 'json':

case 'execution-denied':
contentValue = output.reason ?? 'Tool execution denied.';
contentValue = output.reason ?? 'Tool call execution denied.';
break;

@@ -808,0 +808,0 @@ case 'json':

@@ -475,2 +475,26 @@ import type { JSONObject, JSONSchema7, JSONValue } from '@ai-sdk/provider';

// Captured from the Responses API when OpenAI returned an early
// insufficient_quota stream error after HTTP 200. This shape differs from the
// currently documented ResponseErrorEvent below.
const openaiResponsesNestedErrorChunkSchema = z.object({
type: z.literal('error'),
sequence_number: z.number(),
error: z.object({
type: z.string(),
code: z.string(),
message: z.string(),
param: z.string().nullish(),
}),
});
// Current OpenAI OpenAPI docs define ResponseErrorEvent with top-level
// code/message/param fields.
const openaiResponsesErrorChunkSchema = z.object({
type: z.literal('error'),
sequence_number: z.number(),
code: z.string().nullish(),
message: z.string(),
param: z.string().nullish(),
});
export const openaiResponsesChunkSchema = lazySchema(() =>

@@ -524,2 +548,3 @@ zodSchema(

type: z.literal('response.failed'),
sequence_number: z.number(),
response: z.object({

@@ -1039,12 +1064,4 @@ error: z

}),
z.object({
type: z.literal('error'),
sequence_number: z.number(),
error: z.object({
type: z.string(),
code: z.string(),
message: z.string(),
param: z.string().nullish(),
}),
}),
openaiResponsesNestedErrorChunkSchema,
openaiResponsesErrorChunkSchema,
z

@@ -1051,0 +1068,0 @@ .object({ type: z.string() })

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display