Skip to content

Commit 12fbb8f

Browse files
committed
Pre-release 0.34.117
1 parent 677f73f commit 12fbb8f

File tree

73 files changed

+3487
-869
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+3487
-869
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"colors" : [
3+
{
4+
"color" : {
5+
"color-space" : "srgb",
6+
"components" : {
7+
"alpha" : "1.000",
8+
"blue" : "1.000",
9+
"green" : "1.000",
10+
"red" : "1.000"
11+
}
12+
},
13+
"idiom" : "universal"
14+
},
15+
{
16+
"appearances" : [
17+
{
18+
"appearance" : "luminosity",
19+
"value" : "dark"
20+
}
21+
],
22+
"color" : {
23+
"color-space" : "srgb",
24+
"components" : {
25+
"alpha" : "1.000",
26+
"blue" : "0.250",
27+
"green" : "0.250",
28+
"red" : "0.250"
29+
}
30+
},
31+
"idiom" : "universal"
32+
}
33+
],
34+
"info" : {
35+
"author" : "xcode",
36+
"version" : 1
37+
}
38+
}

Core/Package.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ let package = Package(
131131
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
132132
.product(name: "KeyboardShortcuts", package: "KeyboardShortcuts"),
133133
.product(name: "GitHubCopilotService", package: "Tool"),
134+
.product(name: "Persist", package: "Tool"),
134135
]),
135136

136137
// MARK: - Suggestion Service

Core/Sources/ChatService/ChatService.swift

Lines changed: 63 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ public final class ChatService: ChatServiceType, ObservableObject {
9595
subscribeToConversationContextRequest()
9696
subscribeToWatchedFilesHandler()
9797
subscribeToClientToolInvokeEvent()
98+
subscribeToClientToolConfirmationEvent()
9899
}
99100

100101
private func subscribeToNotifications() {
@@ -136,6 +137,27 @@ public final class ChatService: ChatServiceType, ObservableObject {
136137
}).store(in: &cancellables)
137138
}
138139

140+
private func subscribeToClientToolConfirmationEvent() {
141+
ClientToolHandlerImpl.shared.onClientToolConfirmationEvent.sink(receiveValue: { [weak self] (request, completion) in
142+
guard let params = request.params, params.conversationId == self?.conversationId else { return }
143+
let editAgentRounds: [AgentRound] = [
144+
AgentRound(roundId: params.roundId,
145+
reply: "",
146+
toolCalls: [
147+
AgentToolCall(id: params.toolCallId, name: params.name, status: .waitForConfirmation, invokeParams: params)
148+
]
149+
)
150+
]
151+
self?.appendToolCallHistory(turnId: params.turnId, editAgentRounds: editAgentRounds)
152+
self?.pendingToolCallRequests[params.toolCallId] = ToolCallRequest(
153+
requestId: request.id,
154+
turnId: params.turnId,
155+
roundId: params.roundId,
156+
toolCallId: params.toolCallId,
157+
completion: completion)
158+
}).store(in: &cancellables)
159+
}
160+
139161
private func subscribeToClientToolInvokeEvent() {
140162
ClientToolHandlerImpl.shared.onClientToolInvokeEvent.sink(receiveValue: { [weak self] (request, completion) in
141163
guard let params = request.params, params.conversationId == self?.conversationId else { return }
@@ -154,15 +176,7 @@ public final class ChatService: ChatServiceType, ObservableObject {
154176
return
155177
}
156178

157-
let completed = copilotTool.invokeTool(request, completion: completion, chatHistoryUpdater: self?.appendToolCallHistory, contextProvider: self)
158-
if !completed {
159-
self?.pendingToolCallRequests[params.toolCallId] = ToolCallRequest(
160-
requestId: request.id,
161-
turnId: params.turnId,
162-
roundId: params.roundId,
163-
toolCallId: params.toolCallId,
164-
completion: completion)
165-
}
179+
copilotTool.invokeTool(request, completion: completion, chatHistoryUpdater: self?.appendToolCallHistory, contextProvider: self)
166180
}).store(in: &cancellables)
167181
}
168182

@@ -225,11 +239,9 @@ public final class ChatService: ChatServiceType, ObservableObject {
225239
}
226240

227241
// Send the tool call result back to the server
228-
if let toolCallRequest = self.pendingToolCallRequests[toolCallId], status == .completed, let result = payload {
242+
if let toolCallRequest = self.pendingToolCallRequests[toolCallId], status == .accepted {
229243
self.pendingToolCallRequests.removeValue(forKey: toolCallId)
230-
let toolResult = LanguageModelToolResult(content: [
231-
.init(value: result)
232-
])
244+
let toolResult = LanguageModelToolConfirmationResult(result: .Accept)
233245
let jsonResult = try? JSONEncoder().encode(toolResult)
234246
let jsonValue = (try? JSONDecoder().decode(JSONValue.self, from: jsonResult ?? Data())) ?? JSONValue.null
235247
toolCallRequest.completion(
@@ -505,12 +517,27 @@ public final class ChatService: ChatServiceType, ObservableObject {
505517
private func handleProgressBegin(token: String, progress: ConversationProgressBegin) {
506518
guard let workDoneToken = activeRequestId, workDoneToken == token else { return }
507519
conversationId = progress.conversationId
520+
let turnId = progress.turnId
508521

509522
Task {
510523
if var lastUserMessage = await memory.history.last(where: { $0.role == .user }) {
511524
lastUserMessage.clsTurnID = progress.turnId
512525
saveChatMessageToStorage(lastUserMessage)
513526
}
527+
528+
/// Display an initial assistant message immediately after the user sends a message.
529+
/// This improves perceived responsiveness, especially in Agent Mode where the first
530+
/// ProgressReport may take long time.
531+
let message = ChatMessage(
532+
id: turnId,
533+
chatTabID: self.chatTabInfo.id,
534+
clsTurnID: turnId,
535+
role: .assistant,
536+
content: ""
537+
)
538+
539+
// will persist in resetOngoingRequest()
540+
await memory.appendMessage(message)
514541
}
515542
}
516543

@@ -642,17 +669,18 @@ public final class ChatService: ChatServiceType, ObservableObject {
642669
// cancel all pending tool call requests
643670
for (_, request) in pendingToolCallRequests {
644671
pendingToolCallRequests.removeValue(forKey: request.toolCallId)
645-
request.completion(AnyJSONRPCResponse(id: request.requestId,
646-
result: JSONValue.array([
647-
JSONValue.null,
648-
JSONValue.hash(
649-
[
650-
"code": .number(-32800), // client cancelled
651-
"message": .string("The client cancelled the tool call request \(request.toolCallId)")
652-
])
653-
])
654-
)
655-
)
672+
let toolResult = LanguageModelToolConfirmationResult(result: .Dismiss)
673+
let jsonResult = try? JSONEncoder().encode(toolResult)
674+
let jsonValue = (try? JSONDecoder().decode(JSONValue.self, from: jsonResult ?? Data())) ?? JSONValue.null
675+
request.completion(
676+
AnyJSONRPCResponse(
677+
id: request.requestId,
678+
result: JSONValue.array([
679+
jsonValue,
680+
JSONValue.null
681+
])
682+
)
683+
)
656684
}
657685

658686
Task {
@@ -901,7 +929,7 @@ extension [ChatMessage] {
901929
if index + 1 < count {
902930
let nextMessage = self[index + 1]
903931
if nextMessage.role == .assistant {
904-
turn.response = nextMessage.content
932+
turn.response = nextMessage.content + extractContentFromEditAgentRounds(nextMessage.editAgentRounds)
905933
index += 1
906934
}
907935
}
@@ -912,5 +940,14 @@ extension [ChatMessage] {
912940

913941
return turns
914942
}
943+
944+
private func extractContentFromEditAgentRounds(_ editAgentRounds: [AgentRound]) -> String {
945+
var content = ""
946+
for round in editAgentRounds {
947+
if !round.reply.isEmpty {
948+
content += round.reply
949+
}
950+
}
951+
return content
952+
}
915953
}
916-
Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,42 @@
11
import ConversationServiceProvider
2+
import Terminal
3+
import XcodeInspector
24
import JSONRPC
35

46
public class RunInTerminalTool: ICopilotTool {
57
public func invokeTool(_ request: InvokeClientToolRequest, completion: @escaping (AnyJSONRPCResponse) -> Void, chatHistoryUpdater: ChatHistoryUpdater?, contextProvider: (any ToolContextProvider)?) -> Bool {
68
let params = request.params!
7-
let editAgentRounds: [AgentRound] = [
8-
AgentRound(roundId: params.roundId,
9-
reply: "",
10-
toolCalls: [
11-
AgentToolCall(id: params.toolCallId, name: params.name, status: .waitForConfirmation, invokeParams: params)
12-
]
13-
)
14-
]
15-
16-
if let chatHistoryUpdater = chatHistoryUpdater {
17-
chatHistoryUpdater(params.turnId, editAgentRounds)
9+
10+
Task {
11+
var currentDirectory: String = ""
12+
if let workspacePath = contextProvider?.chatTabInfo.workspacePath,
13+
let xcodeIntance = Utils.getXcode(by: workspacePath) {
14+
currentDirectory = xcodeIntance.realtimeProjectURL?.path ?? xcodeIntance.projectRootURL?.path ?? ""
15+
} else {
16+
currentDirectory = await XcodeInspector.shared.safe.realtimeActiveProjectURL?.path ?? ""
17+
}
18+
if let input = params.input {
19+
let command = input["command"]?.value as? String
20+
let isBackground = input["isBackground"]?.value as? Bool
21+
let toolId = params.toolCallId
22+
let session = TerminalSessionManager.shared.createSession(for: toolId)
23+
if isBackground == true {
24+
session.executeCommand(
25+
currentDirectory: currentDirectory,
26+
command: command!) { result in
27+
// do nothing
28+
}
29+
completeResponse(request, response: "Command is running in terminal with ID=\(toolId)", completion: completion)
30+
} else {
31+
session.executeCommand(
32+
currentDirectory: currentDirectory,
33+
command: command!) { result in
34+
self.completeResponse(request, response: result.output, completion: completion)
35+
}
36+
}
37+
}
1838
}
1939

20-
return false
40+
return true
2141
}
2242
}

Core/Sources/ConversationTab/Chat.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ struct Chat {
9090
case downvote(MessageID, ConversationRating)
9191
case copyCode(MessageID)
9292
case insertCode(String)
93-
case toolCallStarted(String)
93+
case toolCallAccepted(String)
9494
case toolCallCompleted(String, String)
9595
case toolCallCancelled(String)
9696

@@ -182,10 +182,10 @@ struct Chat {
182182
try await service.send(id, content: message, skillSet: skillSet, references: selectedFiles, model: selectedModelFamily, agentMode: agentMode)
183183
}.cancellable(id: CancelID.sendMessage(self.id))
184184

185-
case let .toolCallStarted(toolCallId):
185+
case let .toolCallAccepted(toolCallId):
186186
guard !toolCallId.isEmpty else { return .none }
187187
return .run { _ in
188-
service.updateToolCallStatus(toolCallId: toolCallId, status: .running)
188+
service.updateToolCallStatus(toolCallId: toolCallId, status: .accepted)
189189
}.cancellable(id: CancelID.sendMessage(self.id))
190190
case let .toolCallCancelled(toolCallId):
191191
guard !toolCallId.isEmpty else { return .none }

0 commit comments

Comments
 (0)