models.test.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import { expect, test } from "bun:test"
  2. import { CopilotModels } from "@opencode-ai/core/github-copilot/models"
  3. import { Model } from "@opencode-ai/core/model"
  4. import { Provider } from "@opencode-ai/core/provider"
  5. test("defensively syncs advertised Copilot models", async () => {
  6. const server = Bun.serve({
  7. port: 0,
  8. fetch: () =>
  9. Response.json({
  10. data: [
  11. {
  12. model_picker_enabled: true,
  13. id: "gpt-5",
  14. name: "GPT-5 remote",
  15. version: "gpt-5-2026-06-01",
  16. supported_endpoints: ["/responses"],
  17. billing: {
  18. token_prices: {
  19. batch_size: 0,
  20. default: { input_price: 10, output_price: 20, cache_price: 5 },
  21. },
  22. },
  23. capabilities: {
  24. family: "gpt",
  25. limits: {
  26. max_context_window_tokens: 200000,
  27. max_output_tokens: 16384,
  28. max_prompt_tokens: 180000,
  29. vision: {
  30. max_prompt_image_size: 10000000,
  31. max_prompt_images: 10,
  32. supported_media_types: ["image/png", "application/pdf"],
  33. },
  34. },
  35. supports: { tool_calls: true, vision: true, reasoning_effort: ["low", "high"] },
  36. },
  37. },
  38. {
  39. model_picker_enabled: true,
  40. id: "vision-only",
  41. name: "Vision only",
  42. version: "vision-only-2026-06-01",
  43. capabilities: {
  44. family: "vision",
  45. limits: {
  46. max_output_tokens: 16384,
  47. max_prompt_tokens: 180000,
  48. vision: {
  49. max_prompt_image_size: 10000000,
  50. max_prompt_images: 10,
  51. supported_media_types: ["image/png"],
  52. },
  53. },
  54. supports: { tool_calls: true, vision: true },
  55. },
  56. },
  57. {
  58. model_picker_enabled: false,
  59. id: "utility",
  60. name: "Utility",
  61. version: "utility-2026-06-01",
  62. capabilities: {
  63. family: "utility",
  64. limits: { max_output_tokens: 1000, max_prompt_tokens: 8000 },
  65. supports: { tool_calls: false },
  66. },
  67. },
  68. { model_picker_enabled: true, id: "incomplete" },
  69. ],
  70. }),
  71. })
  72. try {
  73. const existing = Model.Info.make({
  74. ...Model.Info.default(Provider.ID.githubCopilot, Model.ID.make("gpt-5")),
  75. modelID: Model.ID.make("gpt-5"),
  76. name: "GPT-5 local",
  77. })
  78. const stale = Model.Info.make({
  79. ...Model.Info.default(Provider.ID.githubCopilot, Model.ID.make("stale")),
  80. modelID: Model.ID.make("stale"),
  81. })
  82. const models = await CopilotModels.get(server.url.origin, {}, [existing, stale])
  83. const model = models.get(Model.ID.make("gpt-5"))
  84. expect(model?.name).toBe("GPT-5 local")
  85. expect(model?.settings).toMatchObject({ baseURL: server.url.origin, endpoint: "responses" })
  86. expect(model?.cost[0]).toMatchObject({ input: 0, output: 0, cache: { read: 0, write: 0 } })
  87. expect(model?.variants.map((variant) => variant.id)).toEqual([
  88. Model.VariantID.make("low"),
  89. Model.VariantID.make("high"),
  90. ])
  91. expect(model?.capabilities.input).toEqual(["text", "image", "pdf"])
  92. expect(models.get(Model.ID.make("vision-only"))?.capabilities.input).toEqual(["text", "image"])
  93. expect(models.get(Model.ID.make("utility"))?.enabled).toBe(false)
  94. expect(models.has(Model.ID.make("stale"))).toBe(false)
  95. expect(models.has(Model.ID.make("incomplete"))).toBe(false)
  96. } finally {
  97. await server.stop(true)
  98. }
  99. })