Add OpenRouter dedicated images API support
Fetches /api/v1/images/models in parallel with /models and merges results
into the model picker. Image-only models (e.g. Sourceful, Seedream, Flux
via this endpoint) were previously invisible since they don't appear in the
standard /models endpoint.
Models from the images API get usesImagesAPI=true and route through a new
generateImageAPIResponse() path in ChatViewModel that POSTs to /api/v1/images
with {model, prompt} instead of the chat completions endpoint. The response's
b64_json data is decoded and displayed via the existing GeneratedImagesView.
Cost is taken directly from the usage.cost field in the images API response
(USD per image) via a new rawCostUSD field on ChatResponse.Usage, bypassing
the token-based calculateCost() path used for chat models.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -801,6 +801,13 @@ Don't narrate future actions ("Let me...") - just use the tools.
|
||||
let bashActive = settings.bashEnabled
|
||||
let personalDataActive = settings.calendarEnabled || settings.remindersEnabled || settings.contactsEnabled || settings.locationMapsEnabled
|
||||
let researchAgentsActive = settings.agentsEnabled
|
||||
// Dedicated images API path (OpenRouter /images endpoint — separate from chat completions)
|
||||
if selectedModel?.capabilities.usesImagesAPI == true,
|
||||
let orProvider = provider as? OpenRouterProvider {
|
||||
generateImageAPIResponse(orProvider: orProvider, modelId: modelId, prompt: prompt)
|
||||
return
|
||||
}
|
||||
|
||||
let modelSupportTools = selectedModel?.capabilities.tools ?? false
|
||||
if modelSupportTools && (anytypeActive || bashActive || personalDataActive || researchAgentsActive || (mcpActive && !mcp.allowedFolders.isEmpty)) {
|
||||
generateAIResponseWithTools(provider: provider, modelId: modelId)
|
||||
@@ -1263,6 +1270,55 @@ Don't narrate future actions ("Let me...") - just use the tools.
|
||||
|
||||
// MARK: - AI Response with Tool Calls
|
||||
|
||||
// MARK: - Images API Generation
|
||||
|
||||
private func generateImageAPIResponse(orProvider: OpenRouterProvider, modelId: String, prompt: String) {
|
||||
isGenerating = true
|
||||
streamingTask?.cancel()
|
||||
streamingTask = Task {
|
||||
let startTime = Date()
|
||||
let assistantMessage = Message(
|
||||
role: .assistant,
|
||||
content: ThinkingVerbs.random(),
|
||||
tokens: nil,
|
||||
cost: nil,
|
||||
timestamp: Date(),
|
||||
attachments: nil,
|
||||
modelId: modelId,
|
||||
isStreaming: true
|
||||
)
|
||||
let messageId = assistantMessage.id
|
||||
messages.append(assistantMessage)
|
||||
|
||||
do {
|
||||
let response = try await orProvider.generateImage(model: modelId, prompt: prompt)
|
||||
let responseTime = Date().timeIntervalSince(startTime)
|
||||
|
||||
if let index = messages.firstIndex(where: { $0.id == messageId }) {
|
||||
messages[index].content = response.content
|
||||
messages[index].isStreaming = false
|
||||
messages[index].generatedImages = response.generatedImages
|
||||
messages[index].responseTime = responseTime
|
||||
|
||||
if let usage = response.usage {
|
||||
messages[index].tokens = usage.completionTokens
|
||||
let cost = usage.rawCostUSD
|
||||
messages[index].cost = cost
|
||||
sessionStats.addMessage(inputTokens: usage.promptTokens, outputTokens: usage.completionTokens, cost: cost)
|
||||
}
|
||||
}
|
||||
_ = detectGoodbyePhrase(in: "")
|
||||
} catch {
|
||||
if let index = messages.firstIndex(where: { $0.id == messageId }) {
|
||||
messages[index].content = "❌ Image generation failed: \(error.localizedDescription)"
|
||||
messages[index].isStreaming = false
|
||||
}
|
||||
Log.api.error("Images API error: \(error)")
|
||||
}
|
||||
isGenerating = false
|
||||
}
|
||||
}
|
||||
|
||||
private func generateAIResponseWithTools(provider: AIProvider, modelId: String) {
|
||||
let mcp = MCPService.shared
|
||||
Log.ui.info("generateAIResponseWithTools: model=\(modelId)")
|
||||
|
||||
Reference in New Issue
Block a user