Skip to content
Merged
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/eleven-wombats-sin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ai-sdk/codemod': patch
---

feat(codemods): add codemods for providerMetadata key change
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ every file.
| `rename-tool-call-options-to-tool-execution-options` | Renames the `ToolCallOptions` type to `ToolExecutionOptions` |
| `rename-core-message-to-model-message` | Renames the `CoreMessage` type to `ModelMessage` |
| `rename-converttocoremessages-to-converttomodelmessages` | Renames `convertToCoreMessages` function to `convertToModelMessages` |
| `rename-vertex-provider-metadata-key` | Renames `google` to `vertex` in `providerMetadata` and `providerOptions` for Google Vertex files |
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add a note to the vertex key rename section that a codemod exists? could be good for other sections with codemods as well, so people immediately know.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will create a follow up PR for this


## AI SDK Core

Expand Down
1 change: 1 addition & 0 deletions packages/codemod/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ npx @ai-sdk/codemod v5/rename-format-stream-part .
| `v6/rename-mock-v2-to-v3` | Transforms v6/rename mock v2 to v3 |
| `v6/rename-text-embedding-to-embedding` | Transforms v6/rename text embedding to embedding |
| `v6/rename-tool-call-options-to-tool-execution-options` | Transforms v6/rename tool call options to tool execution options |
| `v6/rename-vertex-provider-metadata-key` | Transforms v6/rename vertex provider metadata key |

## CLI Options

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { createTransformer } from '../lib/create-transformer';

export default createTransformer((fileInfo, api, options, context) => {
const { j, root } = context;

// Only apply to files that import from @ai-sdk/google-vertex
const hasVertexImport =
root
.find(j.ImportDeclaration)
.filter(path => {
return (
path.node.source.type === 'StringLiteral' &&
(path.node.source.value === '@ai-sdk/google-vertex' ||
path.node.source.value === '@ai-sdk/google-vertex/edge')
);
})
.size() > 0;

if (!hasVertexImport) {
return;
}

// Helper to check if a node represents providerMetadata access
const isProviderMetadataAccess = (object: any): boolean => {
if (object.type === 'Identifier') {
return object.name === 'providerMetadata';
}
// Handle chained access like result.providerMetadata or event?.providerMetadata
if (
object.type === 'MemberExpression' ||
object.type === 'OptionalMemberExpression'
) {
const prop = object.property;
return prop.type === 'Identifier' && prop.name === 'providerMetadata';
}
return false;
};

// Helper to check if a node is inside a providerOptions object
const isInsideProviderOptions = (path: any): boolean => {
let current = path.parent;
while (current) {
if (current.node.type === 'ObjectExpression') {
const grandparent = current.parent;
if (
grandparent &&
(grandparent.node.type === 'Property' ||
grandparent.node.type === 'ObjectProperty')
) {
const key = grandparent.node.key;
if (key.type === 'Identifier' && key.name === 'providerOptions') {
return true;
}
}
}
current = current.parent;
}
return false;
};

// Transform providerMetadata?.google and providerMetadata.google
// Using MemberExpression (covers both optional and non-optional in jscodeshift)
root.find(j.MemberExpression).forEach(path => {
const property = path.node.property;
const object = path.node.object;

// Property must be 'google'
if (property.type !== 'Identifier' || property.name !== 'google') {
return;
}

// Check if accessing providerMetadata
if (isProviderMetadataAccess(object)) {
property.name = 'vertex';
context.hasChanges = true;
}
});

// Transform destructuring: const { google } = providerMetadata
// Also handles: const { google: metadata } = providerMetadata
root.find(j.VariableDeclarator).forEach(path => {
const id = path.node.id;
const init = path.node.init;

if (id.type !== 'ObjectPattern') {
return;
}

// Check if init is providerMetadata or something?.providerMetadata
let isFromProviderMetadata = false;
if (init) {
if (init.type === 'Identifier' && init.name === 'providerMetadata') {
isFromProviderMetadata = true;
} else if (
(init.type === 'MemberExpression' ||
init.type === 'OptionalMemberExpression') &&
init.property.type === 'Identifier' &&
init.property.name === 'providerMetadata'
) {
isFromProviderMetadata = true;
} else if (
init.type === 'LogicalExpression' &&
init.operator === '??' &&
init.left.type === 'MemberExpression' &&
init.left.property.type === 'Identifier' &&
init.left.property.name === 'providerMetadata'
) {
// Handle: const { google } = result.providerMetadata ?? {}
isFromProviderMetadata = true;
}
}

if (!isFromProviderMetadata) {
return;
}

// Find and rename 'google' property in destructuring
id.properties.forEach(prop => {
if (prop.type === 'ObjectProperty' || prop.type === 'Property') {
const key = prop.key;
if (key.type === 'Identifier' && key.name === 'google') {
key.name = 'vertex';
// If shorthand, also rename the value
if (
prop.shorthand &&
prop.value.type === 'Identifier' &&
prop.value.name === 'google'
) {
prop.value.name = 'vertex';
}
context.hasChanges = true;
}
}
});
});

// Transform providerOptions: { google: {...} } → providerOptions: { vertex: {...} }
root.find(j.Property).forEach(path => {
const key = path.node.key;

// Key must be 'google'
if (key.type !== 'Identifier' || key.name !== 'google') {
return;
}

// Check if this property is inside a providerOptions object
if (isInsideProviderOptions(path)) {
key.name = 'vertex';
context.hasChanges = true;
}
});

// Also handle ObjectProperty (for some AST variations)
root.find(j.ObjectProperty).forEach(path => {
const key = path.node.key;

if (key.type !== 'Identifier' || key.name !== 'google') {
return;
}

if (isInsideProviderOptions(path)) {
key.name = 'vertex';
context.hasChanges = true;
}
});
});
1 change: 1 addition & 0 deletions packages/codemod/src/lib/upgrade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ const bundle = [
'v6/rename-tool-call-options-to-tool-execution-options',
'v6/rename-core-message-to-model-message',
'v6/rename-converttocoremessages-to-converttomodelmessages',
'v6/rename-vertex-provider-metadata-key',
];

const log = debug('codemod:upgrade');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// @ts-nocheck
// This file uses @ai-sdk/google (NOT vertex) - should NOT be transformed
import { google } from '@ai-sdk/google';
import { generateText } from 'ai';

const result = await generateText({
model: google('gemini-2.5-flash'),
providerOptions: {
google: {
safetySettings: [],
},
},
prompt: 'Hello',
});

// These should stay as 'google' since we're using @ai-sdk/google
console.log(result.providerMetadata?.google?.safetyRatings);
const { google: metadata } = result.providerMetadata ?? {};

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// @ts-nocheck
// This file uses @ai-sdk/google (NOT vertex) - should NOT be transformed
import { google } from '@ai-sdk/google';
import { generateText } from 'ai';

const result = await generateText({
model: google('gemini-2.5-flash'),
providerOptions: {
google: {
safetySettings: [],
},
},
prompt: 'Hello',
});

// These should stay as 'google' since we're using @ai-sdk/google
console.log(result.providerMetadata?.google?.safetyRatings);
const { google: metadata } = result.providerMetadata ?? {};

Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// @ts-nocheck
import { vertex } from '@ai-sdk/google-vertex';
import { generateText, streamText } from 'ai';

// Case 1: Direct providerMetadata access with optional chaining
const result1 = await generateText({
model: vertex('gemini-2.5-flash'),
prompt: 'Hello',
});
console.log(result1.providerMetadata?.google?.safetyRatings);
console.log(result1.providerMetadata?.google?.groundingMetadata);
console.log(result1.providerMetadata?.google?.urlContextMetadata);
console.log(result1.providerMetadata?.google?.promptFeedback);
console.log(result1.providerMetadata?.google?.usageMetadata);

// Case 2: Non-optional access
const metadata = result1.providerMetadata.google;
const ratings = result1.providerMetadata.google.safetyRatings;

// Case 3: Destructuring from providerMetadata
const { google } = result1.providerMetadata ?? {};
const { google: vertexMeta } = result1.providerMetadata ?? {};

// Case 4: providerOptions input
const result2 = await generateText({
model: vertex('gemini-2.5-flash'),
providerOptions: {
google: {
safetySettings: [
{
category: 'HARM_CATEGORY_DANGEROUS_CONTENT',
threshold: 'BLOCK_LOW_AND_ABOVE',
},
],
},
},
prompt: 'Hello',
});

// Case 5: Streaming with finish event
const { stream } = await streamText({
model: vertex('gemini-2.5-flash'),
prompt: 'Hello',
});

for await (const event of stream) {
if (event.type === 'finish') {
console.log(event.providerMetadata?.google?.safetyRatings);
}
}

// Case 6: Content parts with thoughtSignature
const result3 = await generateText({
model: vertex('gemini-2.5-flash'),
prompt: 'Think step by step',
});

for (const part of result3.content) {
if (part.providerMetadata?.google?.thoughtSignature) {
console.log(part.providerMetadata.google.thoughtSignature);
}
}

// Case 7: Variable assignment
const providerMetadata = result1.providerMetadata;
const googleMeta = providerMetadata?.google;

// Case 8: Function that accesses providerMetadata
function logSafetyRatings(result: any) {
return result.providerMetadata?.google?.safetyRatings;
}

// Case 9: Nested providerOptions in larger config
const config = {
model: vertex('gemini-2.5-flash'),
providerOptions: {
google: {
thinkingConfig: {
thinkingBudget: 1024,
},
},
},
};
Loading
Loading