| 205 | } |
| 206 | |
| 207 | export default function transform( |
| 208 | file: FileInfo, |
| 209 | api: API, |
| 210 | ): string | null | undefined { |
| 211 | const j = api.jscodeshift |
| 212 | const root = j(file.source) |
| 213 | const facts = collectImportFacts(j, root) |
| 214 | |
| 215 | // Bail out early if none of the target helpers are imported from |
| 216 | // `@tanstack/ai` — keeps the codemod a no-op on unrelated files that |
| 217 | // happen to use a `temperature`/`topP`/`maxTokens` key. |
| 218 | if (!facts.hasCoreHelper) { |
| 219 | return file.source |
| 220 | } |
| 221 | |
| 222 | let changed = 0 |
| 223 | const conflicts: Array<Conflict> = [] |
| 224 | |
| 225 | root |
| 226 | .find(j.CallExpression) |
| 227 | .filter((path) => { |
| 228 | const callee = path.node.callee |
| 229 | return callee.type === 'Identifier' && TARGET_CALLEES.has(callee.name) |
| 230 | }) |
| 231 | .forEach((path: ASTPath<CallExpression>) => { |
| 232 | const obj = firstObjectArg(path.node) |
| 233 | if (!obj) return |
| 234 | |
| 235 | // Only act if at least one root sampling prop is present. |
| 236 | const presentKeys = ROOT_SAMPLING_KEYS.filter((k) => findKey(obj, k)) |
| 237 | if (presentKeys.length === 0) return |
| 238 | |
| 239 | const line = path.node.loc?.start.line |
| 240 | const calleeName = |
| 241 | path.node.callee.type === 'Identifier' ? path.node.callee.name : 'call' |
| 242 | |
| 243 | // Resolve the provider from the adapter. Skip + report if we can't. |
| 244 | const provider = resolveProvider(obj) |
| 245 | if (!provider) { |
| 246 | conflicts.push({ |
| 247 | filePath: file.path, |
| 248 | line, |
| 249 | reason: `${calleeName}(): could not resolve a known provider adapter from the \`adapter\` property; left alone.`, |
| 250 | }) |
| 251 | return |
| 252 | } |
| 253 | |
| 254 | const renameMap = RENAME[provider] |
| 255 | const nested = PROVIDERS_WITH_NESTED_OPTIONS.has(provider) |
| 256 | |
| 257 | // Validate / locate modelOptions before mutating anything, so a |
| 258 | // conflict aborts the WHOLE call without a partial transform. |
| 259 | let modelOptionsObj: ObjectExpression | null = null |
| 260 | const modelOptionsProp = findKey(obj, 'modelOptions') |
| 261 | if (modelOptionsProp) { |
| 262 | if (modelOptionsProp.value.type !== 'ObjectExpression') { |
| 263 | conflicts.push({ |
| 264 | filePath: file.path, |