Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/auto-create-release-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh pr checks "${{ github.ref_name }}" --watch --required
gh pr checks "${{ github.ref_name }}" --watch
- name: Merge pull request
env:
Expand Down
1 change: 1 addition & 0 deletions Core/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ let package = Package(
.product(name: "GitHubCopilotService", package: "Tool"),
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "Status", package: "Tool"),
.product(name: "Logger", package: "Tool"),
]
),

Expand Down
32 changes: 27 additions & 5 deletions Core/Sources/ChatService/ChatService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public protocol ChatServiceType {
references: [ConversationAttachedReference],
model: String?,
modelProviderName: String?,
reasoningEffort: String?,
agentMode: Bool,
customChatModeId: String?,
userLanguage: String?,
Expand Down Expand Up @@ -363,6 +364,7 @@ public final class ChatService: ChatServiceType, ObservableObject {
references: [ConversationAttachedReference],
model: String? = nil,
modelProviderName: String? = nil,
reasoningEffort: String? = nil,
agentMode: Bool = false,
customChatModeId: String? = nil,
userLanguage: String? = nil,
Expand Down Expand Up @@ -469,6 +471,7 @@ public final class ChatService: ChatServiceType, ObservableObject {
references: references,
model: model,
modelProviderName: modelProviderName,
reasoningEffort: reasoningEffort,
agentMode: agentMode,
customChatModeId: customChatModeId,
userLanguage: userLanguage,
Expand Down Expand Up @@ -500,6 +503,7 @@ public final class ChatService: ChatServiceType, ObservableObject {
references: [ConversationAttachedReference],
model: String? = nil,
modelProviderName: String? = nil,
reasoningEffort: String? = nil,
agentMode: Bool = false,
customChatModeId: String? = nil,
userLanguage: String? = nil,
Expand All @@ -526,6 +530,7 @@ public final class ChatService: ChatServiceType, ObservableObject {
references: references,
model: model,
modelProviderName: modelProviderName,
reasoningEffort: reasoningEffort,
agentMode: agentMode,
customChatModeId: customChatModeId,
userLanguage: userLanguage,
Expand All @@ -537,7 +542,10 @@ public final class ChatService: ChatServiceType, ObservableObject {
await memory.mutateHistory { history in
if let index = history.firstIndex(where: { $0.id == response.turnId && $0.role.isAssistant }) {
history[index].modelName = response.modelName
let modelProviderName = response.modelInfo?.providerName ?? response.modelProviderName
history[index].modelProviderName = modelProviderName
history[index].billingMultiplier = response.billingMultiplier
history[index].reasoningEffort = response.modelInfo?.reasoningEffort

self.saveChatMessageToStorage(history[index])
}
Expand Down Expand Up @@ -998,6 +1006,16 @@ public final class ChatService: ChatServiceType, ObservableObject {
}
}

private func strippingRequestIDs(from message: String) -> String {
// "Request ID:" always appears before "GitHub Request ID:", so cutting at the first
// occurrence removes both along with the preceding separator (". " or " | ")
guard let range = message.range(of: "Request ID:", options: .caseInsensitive) else {
return message
}
return String(message[..<range.lowerBound])
.trimmingCharacters(in: CharacterSet(charactersIn: " |\t\n\r"))
}

private func handleProgressEnd(token: String, progress: ConversationProgressEnd) {
guard let workDoneToken = activeRequestId, workDoneToken == token else { return }

Expand All @@ -1011,12 +1029,13 @@ public final class ChatService: ChatServiceType, ObservableObject {
Task {
let selectedModel = lastUserRequest?.model
let selectedModelProviderName = lastUserRequest?.modelProviderName

let isBYOK = selectedModel != nil && selectedModelProviderName != nil

var errorMessageText: String
if let selectedModel = selectedModel, let selectedModelProviderName = selectedModelProviderName {
errorMessageText = "You've reached your quota limit for your BYOK model \(selectedModel). Please check with \(selectedModelProviderName) for more information."
} else {
errorMessageText = CLSError.message
errorMessageText = strippingRequestIDs(from: CLSError.message)
}

await Status.shared
Expand All @@ -1026,10 +1045,12 @@ public final class ChatService: ChatServiceType, ObservableObject {
chatTabID: chatTabInfo.id,
panelMessages: [.init(type: .error, title: String(CLSError.code ?? 0), message: errorMessageText, location: .Panel)]
)
// will persist in resetongoingRequest()
await memory.appendMessage(errorMessage)

if let lastUserRequest,

// TBB messages mention "AI Credits" or "additional overages" — no fallback for TBB
let isTBB = !isBYOK && (CLSError.message.contains("AI Credits") || CLSError.message.contains("additional overages"))
if !isTBB,
let lastUserRequest,
let currentUserPlan = await Status.shared.currentUserPlan(),
currentUserPlan != "free" {
guard let fallbackModel = CopilotModelManager.getFallbackLLM(
Expand All @@ -1051,6 +1072,7 @@ public final class ChatService: ChatServiceType, ObservableObject {
}
return
}
resetOngoingRequest(with: .error)
}
} else if CLSError.code == 400 && CLSError.message.contains("model is not supported") {
Task {
Expand Down
18 changes: 15 additions & 3 deletions Core/Sources/ConversationTab/Chat.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ public struct DisplayedChatMessage: Equatable {
public var turnStatus: ChatMessage.TurnStatus? = nil
public let requestType: RequestType
public var modelName: String? = nil
public var modelProviderName: String? = nil
public var billingMultiplier: Float? = nil
public var reasoningEffort: String? = nil

public init(
id: String,
Expand All @@ -60,7 +62,9 @@ public struct DisplayedChatMessage: Equatable {
turnStatus: ChatMessage.TurnStatus? = nil,
requestType: RequestType,
modelName: String? = nil,
billingMultiplier: Float? = nil
modelProviderName: String? = nil,
billingMultiplier: Float? = nil,
reasoningEffort: String? = nil
) {
self.id = id
self.role = role
Expand All @@ -80,7 +84,9 @@ public struct DisplayedChatMessage: Equatable {
self.turnStatus = turnStatus
self.requestType = requestType
self.modelName = modelName
self.modelProviderName = modelProviderName
self.billingMultiplier = billingMultiplier
self.reasoningEffort = reasoningEffort
}
}

Expand Down Expand Up @@ -739,6 +745,7 @@ struct Chat {
let selectedModelFamily = selectedModel?.modelFamily ?? CopilotModelManager.getDefaultChatModel(
scope: AppState.shared.modelScope()
)?.modelFamily
let reasoningEffort = selectedModel.flatMap { AppState.shared.effectiveReasoningEffort(for: $0) }
let agentMode = AppState.shared.isAgentModeEnabled()
let selectedAgentSubMode = AppState.shared.getSelectedAgentSubMode()
let shouldAttachImages = selectedModel?.supportVision ?? CopilotModelManager.getDefaultChatModel(
Expand Down Expand Up @@ -775,6 +782,7 @@ struct Chat {
references: references,
model: selectedModelFamily,
modelProviderName: selectedModel?.providerName,
reasoningEffort: reasoningEffort,
agentMode: agentMode,
customChatModeId: selectedAgentSubMode,
userLanguage: chatResponseLocale
Expand Down Expand Up @@ -834,6 +842,7 @@ struct Chat {
references: references,
model: selectedModelFamily,
modelProviderName: selectedModel?.providerName,
reasoningEffort: selectedModel.flatMap { AppState.shared.effectiveReasoningEffort(for: $0) },
agentMode: agentMode,
customChatModeId: selectedAgentSubMode,
userLanguage: chatResponseLocale
Expand Down Expand Up @@ -1079,7 +1088,9 @@ struct Chat {
turnStatus: message.turnStatus,
requestType: message.requestType,
modelName: message.modelName,
billingMultiplier: message.billingMultiplier
modelProviderName: message.modelProviderName,
billingMultiplier: message.billingMultiplier,
reasoningEffort: message.reasoningEffort
))

return all
Expand Down Expand Up @@ -1328,14 +1339,15 @@ struct Chat {
// TODO: if we need to switch to agent mode or keep the current mode
let selectedAgentSubMode = AppState.shared.getSelectedAgentSubMode()

return .run { _ in
return .run { _ in
try await service.send(
UUID().uuidString,
content: message,
skillSet: skillSet,
references: references,
model: selectedModelFamily,
modelProviderName: selectedModel?.providerName,
reasoningEffort: selectedModel.flatMap { AppState.shared.effectiveReasoningEffort(for: $0) },
agentMode: agentMode,
customChatModeId: selectedAgentSubMode,
userLanguage: chatResponseLocale
Expand Down
12 changes: 8 additions & 4 deletions Core/Sources/ConversationTab/ChatPanel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ private let r: Double = 4
public struct ChatPanel: View {
@Perception.Bindable var chat: StoreOf<Chat>
@Namespace var inputAreaNamespace
@ObservedObject private var rateLimitNotifier = RateLimitNotifierImpl.shared
@ObservedObject private var warningManager = WarningStateManager.shared

public var body: some View {
WithPerceptionTracking {
Expand Down Expand Up @@ -56,9 +56,13 @@ public struct ChatPanel: View {
}
}

if let warning = rateLimitNotifier.currentWarning {
RateLimitWarningBanner(message: warning.message) {
rateLimitNotifier.dismissWarning()
if let warning = warningManager.currentWarning {
WarningBanner(
message: warning.message,
severity: warning.severity,
actions: warning.actions
) {
warningManager.dismissWarning()
}
.scaledPadding(.horizontal, 24)
.scaledPadding(.vertical, 8)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,11 @@ struct ModeAndModelPicker: View {
selectedModel = nil
}
} else {
if let fresh = freshModel, let current = currentModel,
fresh.supportsReasoningEffortLevel != current.supportsReasoningEffortLevel
|| fresh.reasoningEfforts != current.reasoningEfforts {
AppState.shared.setSelectedModel(fresh)
}
selectedModel = freshModel ?? defaultModel
}
}
Expand Down
Loading
Loading