models.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. import { Global } from "../global"
  2. import { Log } from "../util/log"
  3. import path from "path"
  4. import z from "zod"
  5. import { Installation } from "../installation"
  6. import { Flag } from "../flag/flag"
  7. import { lazy } from "@/util/lazy"
  8. // Try to import bundled snapshot (generated at build time)
  9. // Falls back to undefined in dev mode when snapshot doesn't exist
  10. /* @ts-ignore */
  11. export namespace ModelsDev {
  12. const log = Log.create({ service: "models.dev" })
  13. const filepath = path.join(Global.Path.cache, "models.json")
  14. export const Model = z.object({
  15. id: z.string(),
  16. name: z.string(),
  17. family: z.string().optional(),
  18. release_date: z.string(),
  19. attachment: z.boolean(),
  20. reasoning: z.boolean(),
  21. temperature: z.boolean(),
  22. tool_call: z.boolean(),
  23. interleaved: z
  24. .union([
  25. z.literal(true),
  26. z
  27. .object({
  28. field: z.enum(["reasoning_content", "reasoning_details"]),
  29. })
  30. .strict(),
  31. ])
  32. .optional(),
  33. cost: z
  34. .object({
  35. input: z.number(),
  36. output: z.number(),
  37. cache_read: z.number().optional(),
  38. cache_write: z.number().optional(),
  39. context_over_200k: z
  40. .object({
  41. input: z.number(),
  42. output: z.number(),
  43. cache_read: z.number().optional(),
  44. cache_write: z.number().optional(),
  45. })
  46. .optional(),
  47. })
  48. .optional(),
  49. limit: z.object({
  50. context: z.number(),
  51. input: z.number().optional(),
  52. output: z.number(),
  53. }),
  54. modalities: z
  55. .object({
  56. input: z.array(z.enum(["text", "audio", "image", "video", "pdf"])),
  57. output: z.array(z.enum(["text", "audio", "image", "video", "pdf"])),
  58. })
  59. .optional(),
  60. experimental: z.boolean().optional(),
  61. status: z.enum(["alpha", "beta", "deprecated"]).optional(),
  62. options: z.record(z.string(), z.any()),
  63. headers: z.record(z.string(), z.string()).optional(),
  64. provider: z.object({ npm: z.string() }).optional(),
  65. variants: z.record(z.string(), z.record(z.string(), z.any())).optional(),
  66. })
  67. export type Model = z.infer<typeof Model>
  68. export const Provider = z.object({
  69. api: z.string().optional(),
  70. name: z.string(),
  71. env: z.array(z.string()),
  72. id: z.string(),
  73. npm: z.string().optional(),
  74. models: z.record(z.string(), Model),
  75. })
  76. export type Provider = z.infer<typeof Provider>
  77. function url() {
  78. return Flag.OPENCODE_MODELS_URL || "https://models.dev"
  79. }
  80. export const Data = lazy(async () => {
  81. const file = Bun.file(Flag.OPENCODE_MODELS_PATH ?? filepath)
  82. const result = await file.json().catch(() => {})
  83. if (result) return result
  84. // @ts-ignore
  85. const snapshot = await import("./models-snapshot")
  86. .then((m) => m.snapshot as Record<string, unknown>)
  87. .catch(() => undefined)
  88. if (snapshot) return snapshot
  89. if (Flag.OPENCODE_DISABLE_MODELS_FETCH) return {}
  90. const json = await fetch(`${url()}/api.json`).then((x) => x.text())
  91. return JSON.parse(json)
  92. })
  93. export async function get() {
  94. const result = await Data()
  95. return result as Record<string, Provider>
  96. }
  97. export async function refresh() {
  98. const file = Bun.file(filepath)
  99. const result = await fetch(`${url()}/api.json`, {
  100. headers: {
  101. "User-Agent": Installation.USER_AGENT,
  102. },
  103. signal: AbortSignal.timeout(10 * 1000),
  104. }).catch((e) => {
  105. log.error("Failed to fetch models.dev", {
  106. error: e,
  107. })
  108. })
  109. if (result && result.ok) {
  110. await Bun.write(file, await result.text())
  111. ModelsDev.Data.reset()
  112. }
  113. }
  114. }
  115. if (!Flag.OPENCODE_DISABLE_MODELS_FETCH) {
  116. ModelsDev.refresh()
  117. setInterval(
  118. async () => {
  119. await ModelsDev.refresh()
  120. },
  121. 60 * 1000 * 60,
  122. ).unref()
  123. }