| 76 | const decodeItem = Schema.decodeUnknownOption(item) |
| 77 | |
| 78 | function build(key: string, remote: SelectableItem, url: string, prev?: Model): Model { |
| 79 | const reasoning = |
| 80 | !!remote.capabilities.supports.adaptive_thinking || |
| 81 | !!remote.capabilities.supports.reasoning_effort?.length || |
| 82 | remote.capabilities.supports.max_thinking_budget !== undefined || |
| 83 | remote.capabilities.supports.min_thinking_budget !== undefined |
| 84 | const image = |
| 85 | (remote.capabilities.supports.vision ?? false) || |
| 86 | (remote.capabilities.limits.vision?.supported_media_types ?? []).some((item) => item.startsWith("image/")) |
| 87 | |
| 88 | const isMsgApi = remote.supported_endpoints?.includes("/v1/messages") |
| 89 | const prices = remote.billing?.token_prices |
| 90 | // Copilot prices are AIC per billing batch; OpenCode stores USD per million tokens. |
| 91 | const usdPerMillion = prices ? 10_000 / prices.batch_size : 0 |
| 92 | |
| 93 | const model: Model = { |
| 94 | id: key, |
| 95 | providerID: "github-copilot", |
| 96 | api: { |
| 97 | id: remote.id, |
| 98 | url: isMsgApi ? `${url}/v1` : url, |
| 99 | npm: isMsgApi ? "@ai-sdk/anthropic" : "@ai-sdk/github-copilot", |
| 100 | }, |
| 101 | // API response wins |
| 102 | status: "active", |
| 103 | limit: { |
| 104 | context: remote.capabilities.limits.max_context_window_tokens ?? remote.capabilities.limits.max_prompt_tokens, |
| 105 | input: remote.capabilities.limits.max_prompt_tokens, |
| 106 | output: remote.capabilities.limits.max_output_tokens, |
| 107 | }, |
| 108 | capabilities: { |
| 109 | temperature: prev?.capabilities.temperature ?? true, |
| 110 | reasoning: prev?.capabilities.reasoning ?? reasoning, |
| 111 | attachment: prev?.capabilities.attachment ?? true, |
| 112 | toolcall: remote.capabilities.supports.tool_calls, |
| 113 | input: { |
| 114 | text: true, |
| 115 | audio: false, |
| 116 | image, |
| 117 | video: false, |
| 118 | pdf: false, |
| 119 | }, |
| 120 | output: { |
| 121 | text: true, |
| 122 | audio: false, |
| 123 | image: false, |
| 124 | video: false, |
| 125 | pdf: false, |
| 126 | }, |
| 127 | interleaved: false, |
| 128 | }, |
| 129 | // existing wins |
| 130 | family: prev?.family ?? remote.capabilities.family, |
| 131 | name: prev?.name ?? remote.name, |
| 132 | cost: { |
| 133 | input: (prices?.default.input_price ?? 0) * usdPerMillion, |
| 134 | output: (prices?.default.output_price ?? 0) * usdPerMillion, |
| 135 | cache: { |