2.4.1 #7
@@ -360,7 +360,6 @@ Don't narrate future actions ("Let me...") - just use the tools.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func startAutoContinue() {
|
func startAutoContinue() {
|
||||||
showSystemMessage("↩ Continuing…")
|
|
||||||
silentContinuePrompt = "Please continue from where you left off."
|
silentContinuePrompt = "Please continue from where you left off."
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
generateAIResponse(to: "", attachments: nil)
|
generateAIResponse(to: "", attachments: nil)
|
||||||
@@ -1475,9 +1474,8 @@ Don't narrate future actions ("Let me...") - just use the tools.
|
|||||||
if finalContent.isEmpty {
|
if finalContent.isEmpty {
|
||||||
// Some models (observed with Qwen via OpenRouter) stop calling tools
|
// Some models (observed with Qwen via OpenRouter) stop calling tools
|
||||||
// after a long tool-call chain but return no summarizing text at all.
|
// after a long tool-call chain but return no summarizing text at all.
|
||||||
// Surface a placeholder and nudge a follow-up turn instead of a silent blank bubble.
|
// Silently nudge a follow-up turn instead of showing a placeholder bubble.
|
||||||
finishedWithEmptyContent = true
|
finishedWithEmptyContent = true
|
||||||
finalContent = "[No response from the model — retrying]"
|
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -1566,9 +1564,7 @@ Don't narrate future actions ("Let me...") - just use the tools.
|
|||||||
// If this was the last iteration, note it
|
// If this was the last iteration, note it
|
||||||
if iteration == maxIterations - 1 {
|
if iteration == maxIterations - 1 {
|
||||||
hitIterationLimit = true // We're exiting with pending tool calls
|
hitIterationLimit = true // We're exiting with pending tool calls
|
||||||
finalContent = response.content.isEmpty
|
finalContent = response.content
|
||||||
? "[Tool loop reached maximum iterations]"
|
|
||||||
: response.content
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1577,41 +1573,57 @@ Don't narrate future actions ("Let me...") - just use the tools.
|
|||||||
wasCancelled = true
|
wasCancelled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we hit the iteration limit or the model returned no text at all, silently
|
||||||
|
// nudge a follow-up turn instead of showing a placeholder/blank bubble.
|
||||||
|
let willAutoContinue = (hitIterationLimit || finishedWithEmptyContent) && !wasCancelled
|
||||||
|
|
||||||
// Display the final response as an assistant message
|
// Display the final response as an assistant message
|
||||||
let responseTime = Date().timeIntervalSince(startTime)
|
let responseTime = Date().timeIntervalSince(startTime)
|
||||||
let assistantMessage = Message(
|
if willAutoContinue && finalContent.isEmpty {
|
||||||
role: .assistant,
|
// Nothing worth showing yet — still record usage/cost for this turn.
|
||||||
content: finalContent,
|
if let usage = totalUsage, let model = selectedModel {
|
||||||
tokens: totalUsage?.completionTokens,
|
let hasPricing = model.pricing.prompt > 0 || model.pricing.completion > 0
|
||||||
cost: nil,
|
let cost: Double? = hasPricing ? calculateCost(usage: usage, pricing: model.pricing) : nil
|
||||||
timestamp: Date(),
|
sessionStats.addMessage(
|
||||||
attachments: nil,
|
inputTokens: usage.promptTokens,
|
||||||
responseTime: responseTime,
|
outputTokens: usage.completionTokens,
|
||||||
wasInterrupted: wasCancelled,
|
cost: cost
|
||||||
modelId: modelId,
|
)
|
||||||
generatedImages: finalImages.isEmpty ? nil : finalImages
|
|
||||||
)
|
|
||||||
messages.append(assistantMessage)
|
|
||||||
|
|
||||||
// Calculate cost
|
|
||||||
if let usage = totalUsage, let model = selectedModel {
|
|
||||||
let hasPricing = model.pricing.prompt > 0 || model.pricing.completion > 0
|
|
||||||
let cost: Double? = hasPricing ? calculateCost(usage: usage, pricing: model.pricing) : nil
|
|
||||||
if let index = messages.lastIndex(where: { $0.id == assistantMessage.id }) {
|
|
||||||
messages[index].cost = cost
|
|
||||||
}
|
}
|
||||||
sessionStats.addMessage(
|
} else {
|
||||||
inputTokens: usage.promptTokens,
|
let assistantMessage = Message(
|
||||||
outputTokens: usage.completionTokens,
|
role: .assistant,
|
||||||
cost: cost
|
content: finalContent,
|
||||||
|
tokens: totalUsage?.completionTokens,
|
||||||
|
cost: nil,
|
||||||
|
timestamp: Date(),
|
||||||
|
attachments: nil,
|
||||||
|
responseTime: responseTime,
|
||||||
|
wasInterrupted: wasCancelled,
|
||||||
|
modelId: modelId,
|
||||||
|
generatedImages: finalImages.isEmpty ? nil : finalImages
|
||||||
)
|
)
|
||||||
|
messages.append(assistantMessage)
|
||||||
|
|
||||||
|
// Calculate cost
|
||||||
|
if let usage = totalUsage, let model = selectedModel {
|
||||||
|
let hasPricing = model.pricing.prompt > 0 || model.pricing.completion > 0
|
||||||
|
let cost: Double? = hasPricing ? calculateCost(usage: usage, pricing: model.pricing) : nil
|
||||||
|
if let index = messages.lastIndex(where: { $0.id == assistantMessage.id }) {
|
||||||
|
messages[index].cost = cost
|
||||||
|
}
|
||||||
|
sessionStats.addMessage(
|
||||||
|
inputTokens: usage.promptTokens,
|
||||||
|
outputTokens: usage.completionTokens,
|
||||||
|
cost: cost
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isGenerating = false
|
isGenerating = false
|
||||||
streamingTask = nil
|
streamingTask = nil
|
||||||
|
|
||||||
// If we hit the iteration limit, or the model returned no text at all, nudge a follow-up turn
|
if willAutoContinue {
|
||||||
if (hitIterationLimit || finishedWithEmptyContent) && !wasCancelled {
|
|
||||||
startAutoContinue()
|
startAutoContinue()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user