| 381 | } |
| 382 | |
| 383 | function detectChanges(existing: ExistingModel | null, merged: MergedModel): Changes[] { |
| 384 | if (!existing) { |
| 385 | return []; |
| 386 | } |
| 387 | |
| 388 | const changes: Changes[] = []; |
| 389 | const epsilon = 0.001; |
| 390 | |
| 391 | const formatValue = (value: unknown): string => { |
| 392 | if (typeof value === "number") return formatNumber(value); |
| 393 | if (Array.isArray(value)) return `[${value.join(", ")}]`; |
| 394 | if (value === undefined) return "(none)"; |
| 395 | return String(value); |
| 396 | }; |
| 397 | |
| 398 | const compare = (field: string, oldValue: unknown, newValue: unknown) => { |
| 399 | const changed = field.startsWith("cost.") |
| 400 | ? ( |
| 401 | oldValue === undefined && newValue === undefined |
| 402 | ? false |
| 403 | : oldValue === undefined || newValue === undefined |
| 404 | ? true |
| 405 | : Math.abs((oldValue as number) - (newValue as number)) > epsilon |
| 406 | ) |
| 407 | : JSON.stringify(oldValue) !== JSON.stringify(newValue); |
| 408 | |
| 409 | if (changed) { |
| 410 | changes.push({ |
| 411 | field, |
| 412 | oldValue: formatValue(oldValue), |
| 413 | newValue: formatValue(newValue), |
| 414 | }); |
| 415 | } |
| 416 | }; |
| 417 | |
| 418 | compare("name", existing.name, merged.name); |
| 419 | compare("family", existing.family, merged.family); |
| 420 | compare("release_date", existing.release_date, merged.release_date); |
| 421 | compare("attachment", existing.attachment, merged.attachment); |
| 422 | compare("reasoning", existing.reasoning, merged.reasoning); |
| 423 | compare("structured_output", existing.structured_output, merged.structured_output); |
| 424 | compare("temperature", existing.temperature, merged.temperature); |
| 425 | compare("tool_call", existing.tool_call, merged.tool_call); |
| 426 | compare("open_weights", existing.open_weights, merged.open_weights); |
| 427 | compare("cost.input", existing.cost?.input, merged.cost?.input); |
| 428 | compare("cost.output", existing.cost?.output, merged.cost?.output); |
| 429 | compare("cost.cache_read", existing.cost?.cache_read, merged.cost?.cache_read); |
| 430 | compare("cost.cache_write", existing.cost?.cache_write, merged.cost?.cache_write); |
| 431 | compare("limit.context", existing.limit?.context, merged.limit.context); |
| 432 | compare("limit.output", existing.limit?.output, merged.limit.output); |
| 433 | compare("modalities.input", existing.modalities?.input, merged.modalities.input); |
| 434 | compare("modalities.output", existing.modalities?.output, merged.modalities.output); |
| 435 | |
| 436 | return changes; |
| 437 | } |
| 438 | |
| 439 | async function main() { |
| 440 | const args = process.argv.slice(2); |