models.ts 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. import { Global } from "../global"
  2. import { Log } from "../util/log"
  3. import path from "path"
  4. import z from "zod"
  5. import { data } from "./models-macro" with { type: "macro" }
  6. import { Installation } from "../installation"
  7. import { Flag } from "../flag/flag"
  8. export namespace ModelsDev {
  9. const log = Log.create({ service: "models.dev" })
  10. const filepath = path.join(Global.Path.cache, "models.json")
  11. export const Model = z.object({
  12. id: z.string(),
  13. name: z.string(),
  14. family: z.string().optional(),
  15. release_date: z.string(),
  16. attachment: z.boolean(),
  17. reasoning: z.boolean(),
  18. temperature: z.boolean(),
  19. tool_call: z.boolean(),
  20. interleaved: z
  21. .union([
  22. z.literal(true),
  23. z
  24. .object({
  25. field: z.enum(["reasoning_content", "reasoning_details"]),
  26. })
  27. .strict(),
  28. ])
  29. .optional(),
  30. cost: z
  31. .object({
  32. input: z.number(),
  33. output: z.number(),
  34. cache_read: z.number().optional(),
  35. cache_write: z.number().optional(),
  36. context_over_200k: z
  37. .object({
  38. input: z.number(),
  39. output: z.number(),
  40. cache_read: z.number().optional(),
  41. cache_write: z.number().optional(),
  42. })
  43. .optional(),
  44. })
  45. .optional(),
  46. limit: z.object({
  47. context: z.number(),
  48. output: z.number(),
  49. }),
  50. modalities: z
  51. .object({
  52. input: z.array(z.enum(["text", "audio", "image", "video", "pdf"])),
  53. output: z.array(z.enum(["text", "audio", "image", "video", "pdf"])),
  54. })
  55. .optional(),
  56. experimental: z.boolean().optional(),
  57. status: z.enum(["alpha", "beta", "deprecated"]).optional(),
  58. options: z.record(z.string(), z.any()),
  59. headers: z.record(z.string(), z.string()).optional(),
  60. provider: z.object({ npm: z.string() }).optional(),
  61. })
  62. export type Model = z.infer<typeof Model>
  63. export const Provider = z.object({
  64. api: z.string().optional(),
  65. name: z.string(),
  66. env: z.array(z.string()),
  67. id: z.string(),
  68. npm: z.string().optional(),
  69. models: z.record(z.string(), Model),
  70. })
  71. export type Provider = z.infer<typeof Provider>
  72. export async function get() {
  73. refresh()
  74. const file = Bun.file(filepath)
  75. const result = await file.json().catch(() => {})
  76. if (result) return result as Record<string, Provider>
  77. const json = await data()
  78. return JSON.parse(json) as Record<string, Provider>
  79. }
  80. export async function refresh() {
  81. if (Flag.OPENCODE_DISABLE_MODELS_FETCH) return
  82. const file = Bun.file(filepath)
  83. log.info("refreshing", {
  84. file,
  85. })
  86. const result = await fetch("https://models.dev/api.json", {
  87. headers: {
  88. "User-Agent": Installation.USER_AGENT,
  89. "x-opencode-client": Flag.OPENCODE_CLIENT,
  90. },
  91. signal: AbortSignal.timeout(10 * 1000),
  92. }).catch((e) => {
  93. log.error("Failed to fetch models.dev", {
  94. error: e,
  95. })
  96. })
  97. if (result && result.ok) await Bun.write(file, await result.text())
  98. }
  99. }
  100. setInterval(() => ModelsDev.refresh(), 60 * 1000 * 60).unref()