Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/quick-eagles-explain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ai-sdk/gateway': patch
---

fix(gateway): accept deprecated warnings in image, speech, transcription, and video responses
135 changes: 135 additions & 0 deletions packages/gateway/src/gateway-image-model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,16 @@
providerMetadata,
}: {
images?: string[];
<<<<<<< HEAD

Check failure on line 73 in packages/gateway/src/gateway-image-model.test.ts

View workflow job for this annotation

GitHub Actions / TypeScript

Merge conflict marker encountered.
warnings?: Array<{ type: 'other'; message: string }>;
=======

Check failure on line 75 in packages/gateway/src/gateway-image-model.test.ts

View workflow job for this annotation

GitHub Actions / TypeScript

Merge conflict marker encountered.
warnings?: Array<
| { type: 'unsupported'; feature: string; details?: string }
| { type: 'compatibility'; feature: string; details?: string }
| { type: 'deprecated'; setting: string; message: string }
| { type: 'other'; message: string }
>;
>>>>>>> 9c54a9f34c ([v6.0] fix(gateway): accept deprecated warnings in image, speech, transcription, and video responses (#16792))

Check failure on line 82 in packages/gateway/src/gateway-image-model.test.ts

View workflow job for this annotation

GitHub Actions / TypeScript

Merge conflict marker encountered.
providerMetadata?: Record<string, unknown>;
} = {}) {
server.urls['https://api.test.com/image-model'].response = {
Expand Down Expand Up @@ -285,6 +294,132 @@
expect(result.warnings).toEqual(mockWarnings);
});

<<<<<<< HEAD

Check failure on line 297 in packages/gateway/src/gateway-image-model.test.ts

View workflow job for this annotation

GitHub Actions / TypeScript

Merge conflict marker encountered.
=======

Check failure on line 298 in packages/gateway/src/gateway-image-model.test.ts

View workflow job for this annotation

GitHub Actions / TypeScript

Merge conflict marker encountered.
it('should map deprecated warnings to other warnings', async () => {
const mockWarnings = [
{
type: 'deprecated' as const,
setting: 'size',
message: 'Use `aspectRatio` instead.',
},
];

prepareJsonResponse({
images: ['base64-1'],
warnings: mockWarnings,
});

const result = await createTestModel().doGenerate({
prompt: 'Test prompt',
files: undefined,
mask: undefined,
n: 1,
size: undefined,
aspectRatio: undefined,
seed: undefined,
providerOptions: {},
});

expect(result.warnings).toEqual([
{ type: 'other', message: 'Use `aspectRatio` instead.' },
]);
});

it('should return unsupported warnings correctly', async () => {
const mockWarnings = [
{
type: 'unsupported' as const,
feature: 'size',
details:
'This model does not support the `size` option. Use `aspectRatio` instead.',
},
];

prepareJsonResponse({
images: ['base64-1'],
warnings: mockWarnings,
});

const result = await createTestModel().doGenerate({
prompt: 'Test prompt',
files: undefined,
mask: undefined,
n: 1,
size: '1024x1024',
aspectRatio: undefined,
seed: undefined,
providerOptions: {},
});

expect(result.warnings).toEqual(mockWarnings);
});

it('should return compatibility warnings correctly', async () => {
const mockWarnings = [
{
type: 'compatibility' as const,
feature: 'seed',
details: 'Seed support is approximate for this model.',
},
];

prepareJsonResponse({
images: ['base64-1'],
warnings: mockWarnings,
});

const result = await createTestModel().doGenerate({
prompt: 'Test prompt',
files: undefined,
mask: undefined,
n: 1,
size: undefined,
aspectRatio: undefined,
seed: 42,
providerOptions: {},
});

expect(result.warnings).toEqual(mockWarnings);
});

it('should handle mixed warning types', async () => {
const mockWarnings = [
{
type: 'unsupported' as const,
feature: 'size',
},
{
type: 'compatibility' as const,
feature: 'seed',
details: 'Approximate seed support.',
},
{
type: 'other' as const,
message: 'Rate limit approaching.',
},
];

prepareJsonResponse({
images: ['base64-1'],
warnings: mockWarnings,
});

const result = await createTestModel().doGenerate({
prompt: 'Test prompt',
files: undefined,
mask: undefined,
n: 1,
size: '1024x1024',
aspectRatio: undefined,
seed: 42,
providerOptions: {},
});

expect(result.warnings).toEqual(mockWarnings);
});

>>>>>>> 9c54a9f34c ([v6.0] fix(gateway): accept deprecated warnings in image, speech, transcription, and video responses (#16792))

Check failure on line 422 in packages/gateway/src/gateway-image-model.test.ts

View workflow job for this annotation

GitHub Actions / TypeScript

Merge conflict marker encountered.
it('should return empty warnings array when not provided', async () => {
prepareJsonResponse({
images: ['base64-1'],
Expand Down
28 changes: 27 additions & 1 deletion packages/gateway/src/gateway-image-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
type Resolvable,
} from '@ai-sdk/provider-utils';
import { z } from 'zod/v4';
import { mapGatewayWarnings } from './map-gateway-warnings';
import type { GatewayConfig } from './gateway-config';
import { asGatewayError } from './errors';
import { parseAuthMethod } from './errors/parse-auth-method';
Expand Down Expand Up @@ -78,7 +79,7 @@

return {
images: responseBody.images, // Always base64 strings from server
warnings: responseBody.warnings ?? [],
warnings: mapGatewayWarnings(responseBody.warnings),
providerMetadata:
responseBody.providerMetadata as ImageModelV2ProviderMetadata,
response: {
Expand Down Expand Up @@ -117,6 +118,31 @@
})
.catchall(z.unknown());

<<<<<<< HEAD

Check failure on line 121 in packages/gateway/src/gateway-image-model.ts

View workflow job for this annotation

GitHub Actions / TypeScript

Merge conflict marker encountered.
=======

Check failure on line 122 in packages/gateway/src/gateway-image-model.ts

View workflow job for this annotation

GitHub Actions / TypeScript

Merge conflict marker encountered.
const gatewayImageWarningSchema = z.discriminatedUnion('type', [
z.object({
type: z.literal('unsupported'),
feature: z.string(),
details: z.string().optional(),
}),
z.object({
type: z.literal('compatibility'),
feature: z.string(),
details: z.string().optional(),
}),
z.object({
type: z.literal('deprecated'),
setting: z.string(),
message: z.string(),
}),
z.object({
type: z.literal('other'),
message: z.string(),
}),
]);

>>>>>>> 9c54a9f34c ([v6.0] fix(gateway): accept deprecated warnings in image, speech, transcription, and video responses (#16792))

Check failure on line 145 in packages/gateway/src/gateway-image-model.ts

View workflow job for this annotation

GitHub Actions / TypeScript

Merge conflict marker encountered.
const gatewayImageUsageSchema = z.object({
inputTokens: z.number().nullish(),
outputTokens: z.number().nullish(),
Expand Down
140 changes: 140 additions & 0 deletions packages/gateway/src/gateway-speech-model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import type { SharedV3ProviderMetadata, SpeechModelV3 } from '@ai-sdk/provider';
import {
combineHeaders,
createJsonErrorResponseHandler,
createJsonResponseHandler,
postJsonToApi,
resolve,
type Resolvable,
} from '@ai-sdk/provider-utils';
import { z } from 'zod/v4';
import { mapGatewayWarnings } from './map-gateway-warnings';
import { asGatewayError } from './errors';
import { parseAuthMethod } from './errors/parse-auth-method';
import type { GatewayConfig } from './gateway-config';

export class GatewaySpeechModel implements SpeechModelV3 {
readonly specificationVersion = 'v3' as const;

constructor(
readonly modelId: string,
private readonly config: GatewayConfig & {
provider: string;
o11yHeaders: Resolvable<Record<string, string>>;
},
) {}

get provider(): string {
return this.config.provider;
}

async doGenerate({
text,
voice,
outputFormat,
instructions,
speed,
language,
providerOptions,
headers,
abortSignal,
}: Parameters<SpeechModelV3['doGenerate']>[0]): Promise<
Awaited<ReturnType<SpeechModelV3['doGenerate']>>
> {
const resolvedHeaders = await resolve(this.config.headers());
try {
const {
responseHeaders,
value: responseBody,
rawValue,
} = await postJsonToApi({
url: this.getUrl(),
headers: combineHeaders(
resolvedHeaders,
headers ?? {},
this.getModelConfigHeaders(),
await resolve(this.config.o11yHeaders),
),
body: {
text,
...(voice && { voice }),
...(outputFormat && { outputFormat }),
...(instructions && { instructions }),
...(speed != null && { speed }),
...(language && { language }),
...(providerOptions && { providerOptions }),
},
successfulResponseHandler: createJsonResponseHandler(
gatewaySpeechResponseSchema,
),
failedResponseHandler: createJsonErrorResponseHandler({
errorSchema: z.any(),
errorToMessage: data => data,
}),
...(abortSignal && { abortSignal }),
fetch: this.config.fetch,
});

return {
audio: responseBody.audio,
warnings: mapGatewayWarnings(responseBody.warnings),
providerMetadata:
responseBody.providerMetadata as SharedV3ProviderMetadata,
response: {
timestamp: new Date(),
modelId: this.modelId,
headers: responseHeaders,
body: rawValue,
},
};
} catch (error) {
throw await asGatewayError(
error,
await parseAuthMethod(resolvedHeaders ?? {}),
);
}
}

private getUrl() {
return `${this.config.baseURL}/speech-model`;
}

private getModelConfigHeaders() {
return {
'ai-speech-model-specification-version': '3',
'ai-model-id': this.modelId,
};
}
}

const providerMetadataEntrySchema = z.object({}).catchall(z.unknown());

const gatewaySpeechWarningSchema = z.discriminatedUnion('type', [
z.object({
type: z.literal('unsupported'),
feature: z.string(),
details: z.string().optional(),
}),
z.object({
type: z.literal('compatibility'),
feature: z.string(),
details: z.string().optional(),
}),
z.object({
type: z.literal('deprecated'),
setting: z.string(),
message: z.string(),
}),
z.object({
type: z.literal('other'),
message: z.string(),
}),
]);

const gatewaySpeechResponseSchema = z.object({
audio: z.string(),
warnings: z.array(gatewaySpeechWarningSchema).optional(),
providerMetadata: z
.record(z.string(), providerMetadataEntrySchema)
.optional(),
});
Loading
Loading