(req: CompletionRequest)
| 140 | |
| 141 | async isAvailable(): Promise<boolean> { |
| 142 | return !!this.apiKey; |
| 143 | } |
| 144 | |
| 145 | async listModels(): Promise<ModelInfo[]> { |
| 146 | if (!this.apiKey) return []; |
| 147 | return this.models; |
| 148 | } |
| 149 | |
| 150 | private convertMessages(messages: Message[]): OpenAI.Chat.ChatCompletionMessageParam[] { |
| 151 | return answerOrphanToolCalls(messages).map(m => { |
| 152 | if (m.role === 'tool') { |
| 153 | return { |
| 154 | role: 'tool', |
| 155 | tool_call_id: m.tool_call_id!, |
| 156 | content: m.content ?? '', |
| 157 | }; |
| 158 | } |
| 159 | if (m.role === 'assistant') { |
| 160 | const msg: OpenAI.Chat.ChatCompletionAssistantMessageParam = { |
| 161 | role: 'assistant', |
| 162 | content: m.content ?? null, |
| 163 | }; |
| 164 | if (m.tool_calls) { |
| 165 | msg.tool_calls = m.tool_calls.map((tc: any) => ({ |
| 166 | id: tc.id, |
| 167 | type: 'function' as const, |
| 168 | function: { name: tc.function.name, arguments: tc.function.arguments }, |
| 169 | })); |
| 170 | } |
| 171 | return msg; |
| 172 | } |
| 173 | return { role: m.role, content: m.content ?? '' } as OpenAI.Chat.ChatCompletionMessageParam; |
| 174 | }); |
| 175 | } |
| 176 | |
| 177 | async *complete(req: CompletionRequest): AsyncGenerator<StreamEvent> { |
| 178 | if (!this.client) { |
| 179 | yield { type: 'error', error: `No API key for ${this.name}` }; |
| 180 | return; |
| 181 | } |
| 182 | |
| 183 | const messages = this.convertMessages(req.messages); |
| 184 | const tools = req.tools?.map(t => ({ |
| 185 | type: 'function' as const, |
| 186 | function: t.function, |
| 187 | })); |
| 188 | |
| 189 | try { |
| 190 | // Optional sampling params — provider config may set these for local-server |
| 191 | // backends (LM Studio, llama.cpp) where the defaults cause repetition collapse. |
| 192 | // These map to the OpenAI chat completions API, which LM Studio honors. |
| 193 | const sampling: Record<string, number | undefined> = {}; |
| 194 | if (this.samplingOptions?.frequency_penalty != null) { |
| 195 | sampling.frequency_penalty = this.samplingOptions.frequency_penalty; |
| 196 | } |
| 197 | if (this.samplingOptions?.presence_penalty != null) { |
| 198 | sampling.presence_penalty = this.samplingOptions.presence_penalty; |
| 199 | } |
no test coverage detected