Skip to content

Commit bca812e

Browse files
ethanyhouCopilot
andcommitted
Merge branch 'main' into ethan/thinking-ui
Co-authored-by: Copilot <copilot@github.com>
2 parents f7e9bd0 + d269a2b commit bca812e

33 files changed

Lines changed: 2017 additions & 141 deletions

com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/CopilotLanguageServer.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationAgent;
2626
import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationCodeCopyParams;
2727
import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationCreateParams;
28+
import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationDestroyParams;
2829
import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationMode;
2930
import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationModesParams;
3031
import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationTemplate;
@@ -161,6 +162,12 @@ public interface CopilotLanguageServer extends LanguageServer {
161162
@JsonRequest("conversation/persistence")
162163
CompletableFuture<ChatPersistence> persistence(NullParams param);
163164

165+
/**
166+
* Destroy a conversation, stopping any in-progress processing.
167+
*/
168+
@JsonRequest("conversation/destroy")
169+
CompletableFuture<String> destroy(ConversationDestroyParams param);
170+
164171
/**
165172
* Register agent tools to the language server.
166173
*/

com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/CopilotLanguageServerConnection.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationAgent;
4343
import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationCodeCopyParams;
4444
import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationCreateParams;
45+
import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationDestroyParams;
4546
import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationMode;
4647
import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationModesParams;
4748
import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationTemplate;
@@ -431,6 +432,21 @@ public CompletableFuture<ChatPersistence> persistence() {
431432
});
432433
}
433434

435+
/**
436+
* Destroy a conversation, stopping any in-progress processing on the server.
437+
*/
438+
public void destroyConversation(String conversationId) {
439+
if (StringUtils.isBlank(conversationId)) {
440+
return;
441+
}
442+
Function<LanguageServer, CompletableFuture<String>> fn = server -> ((CopilotLanguageServer) server)
443+
.destroy(new ConversationDestroyParams(conversationId));
444+
this.languageServerWrapper.execute(fn).exceptionally(ex -> {
445+
CopilotCore.LOGGER.error("Failed to destroy conversation: " + conversationId, ex);
446+
return null;
447+
});
448+
}
449+
434450
/**
435451
* Used to register the tools for the language server.
436452
*/

com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/LsStreamConnectionProvider.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ public void start() throws IOException {
7979
Thread.sleep(1000);
8080
CopilotCore.LOGGER.info("Retrying binary LSP agent start on Linux.");
8181
startBinaryLspAgent();
82+
} else {
83+
throw e;
8284
}
8385
}
8486
} catch (Exception e) {
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
4+
package com.microsoft.copilot.eclipse.core.lsp.protocol;
5+
6+
import java.util.Objects;
7+
8+
import org.apache.commons.lang3.builder.ToStringBuilder;
9+
10+
/**
11+
* Parameters for destroying a conversation.
12+
*/
13+
public class ConversationDestroyParams {
14+
private String conversationId;
15+
16+
/**
17+
* Creates a new ConversationDestroyParams.
18+
*/
19+
public ConversationDestroyParams(String conversationId) {
20+
this.conversationId = conversationId;
21+
}
22+
23+
public String getConversationId() {
24+
return conversationId;
25+
}
26+
27+
public void setConversationId(String conversationId) {
28+
this.conversationId = conversationId;
29+
}
30+
31+
@Override
32+
public int hashCode() {
33+
return Objects.hash(conversationId);
34+
}
35+
36+
@Override
37+
public boolean equals(Object obj) {
38+
if (this == obj) {
39+
return true;
40+
}
41+
if (obj == null || getClass() != obj.getClass()) {
42+
return false;
43+
}
44+
ConversationDestroyParams other = (ConversationDestroyParams) obj;
45+
return Objects.equals(conversationId, other.conversationId);
46+
}
47+
48+
@Override
49+
public String toString() {
50+
ToStringBuilder builder = new ToStringBuilder(this);
51+
builder.append("conversationId", conversationId);
52+
return builder.toString();
53+
}
54+
}

com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/CopilotModel.java

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public class CopilotModel {
1616
private String modelFamily;
1717
private String modelName;
1818
private String id;
19+
private String vendor;
1920
private CopilotModelPolicy modelPolicy;
2021
private List<String> scopes;
2122
private boolean preview;
@@ -26,6 +27,7 @@ public class CopilotModel {
2627
private String degradationReason;
2728
private String providerName;
2829
private String modelPickerCategory;
30+
private String modelPickerPriceCategory;
2931

3032
/**
3133
* Policy for the model.
@@ -52,27 +54,65 @@ public String toString() {
5254
}
5355
}
5456

57+
/**
58+
* Capabilities limits for the model. All components are optional ({@code null} when the server does not provide a
59+
* value), mirroring the {@code number | undefined} fields in the language-server schema.
60+
*/
61+
public record CopilotModelCapabilitiesLimits(Integer maxContextWindowTokens, Integer maxOutputTokens,
62+
Integer maxInputTokens, Integer maxNonStreamingOutputTokens) {
63+
@Override
64+
public String toString() {
65+
ToStringBuilder builder = new ToStringBuilder(this);
66+
builder.append("maxContextWindowTokens", maxContextWindowTokens);
67+
builder.append("maxOutputTokens", maxOutputTokens);
68+
builder.append("maxInputTokens", maxInputTokens);
69+
builder.append("maxNonStreamingOutputTokens", maxNonStreamingOutputTokens);
70+
return builder.toString();
71+
}
72+
}
73+
5574
/**
5675
* Capabilities for the model.
5776
*/
58-
public record CopilotModelCapabilities(CopilotModelCapabilitiesSupports supports) {
77+
public record CopilotModelCapabilities(CopilotModelCapabilitiesSupports supports,
78+
CopilotModelCapabilitiesLimits limits) {
5979
@Override
6080
public String toString() {
6181
ToStringBuilder builder = new ToStringBuilder(this);
6282
builder.append("supports", supports);
83+
builder.append("limits", limits);
84+
return builder.toString();
85+
}
86+
}
87+
88+
/**
89+
* Per-token prices for the model, returned in USD.
90+
*/
91+
public record CopilotModelBillingTokenPrices(Double cachePrice, Double inputPrice, Double outputPrice,
92+
Double tokenUnit) {
93+
@Override
94+
public String toString() {
95+
ToStringBuilder builder = new ToStringBuilder(this);
96+
builder.append("cachePrice", cachePrice);
97+
builder.append("inputPrice", inputPrice);
98+
builder.append("outputPrice", outputPrice);
99+
builder.append("tokenUnit", tokenUnit);
63100
return builder.toString();
64101
}
65102
}
66103

67104
/**
68105
* Billing for the model.
69106
*/
70-
public record CopilotModelBilling(boolean isPremium, double multiplier) {
107+
public record CopilotModelBilling(boolean isPremium, double multiplier, boolean tokenBasedBillingEnabled,
108+
CopilotModelBillingTokenPrices tokenPrices) {
71109
@Override
72110
public String toString() {
73111
ToStringBuilder builder = new ToStringBuilder(this);
74112
builder.append("isPremium", isPremium);
75113
builder.append("multiplier", multiplier);
114+
builder.append("tokenBasedBillingEnabled", tokenBasedBillingEnabled);
115+
builder.append("tokenPrices", tokenPrices);
76116
return builder.toString();
77117
}
78118
}
@@ -101,6 +141,14 @@ public void setId(String id) {
101141
this.id = id;
102142
}
103143

144+
public String getVendor() {
145+
return vendor;
146+
}
147+
148+
public void setVendor(String vendor) {
149+
this.vendor = vendor;
150+
}
151+
104152
public CopilotModelPolicy getModelPolicy() {
105153
return modelPolicy;
106154
}
@@ -181,6 +229,14 @@ public void setModelPickerCategory(String modelPickerCategory) {
181229
this.modelPickerCategory = modelPickerCategory;
182230
}
183231

232+
public String getModelPickerPriceCategory() {
233+
return modelPickerPriceCategory;
234+
}
235+
236+
public void setModelPickerPriceCategory(String modelPickerPriceCategory) {
237+
this.modelPickerPriceCategory = modelPickerPriceCategory;
238+
}
239+
184240
@Override
185241
public boolean equals(Object obj) {
186242
if (this == obj) {
@@ -198,14 +254,16 @@ public boolean equals(Object obj) {
198254
&& isChatDefault == other.isChatDefault && isChatFallback == other.isChatFallback
199255
&& Objects.equals(modelFamily, other.modelFamily) && Objects.equals(modelName, other.modelName)
200256
&& Objects.equals(modelPickerCategory, other.modelPickerCategory)
257+
&& Objects.equals(modelPickerPriceCategory, other.modelPickerPriceCategory)
201258
&& Objects.equals(modelPolicy, other.modelPolicy) && preview == other.preview
202-
&& Objects.equals(providerName, other.providerName) && Objects.equals(scopes, other.scopes);
259+
&& Objects.equals(providerName, other.providerName) && Objects.equals(scopes, other.scopes)
260+
&& Objects.equals(vendor, other.vendor);
203261
}
204262

205263
@Override
206264
public int hashCode() {
207265
return Objects.hash(billing, capabilities, degradationReason, id, isChatDefault, isChatFallback, modelFamily,
208-
modelName, modelPickerCategory, modelPolicy, preview, providerName, scopes);
266+
modelName, modelPickerCategory, modelPickerPriceCategory, modelPolicy, preview, providerName, scopes, vendor);
209267
}
210268

211269
@Override
@@ -214,6 +272,7 @@ public String toString() {
214272
builder.append("modelFamily", modelFamily);
215273
builder.append("modelName", modelName);
216274
builder.append("id", id);
275+
builder.append("vendor", vendor);
217276
builder.append("modelPolicy", modelPolicy);
218277
builder.append("scopes", scopes);
219278
builder.append("preview", preview);
@@ -224,6 +283,7 @@ public String toString() {
224283
builder.append("degradationReason", degradationReason);
225284
builder.append("providerName", providerName);
226285
builder.append("modelPickerCategory", modelPickerCategory);
286+
builder.append("modelPickerPriceCategory", modelPickerPriceCategory);
227287
return builder.toString();
228288
}
229289

com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/ThinkingTypeAdapter.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,11 @@ public Thinking read(JsonReader in) throws IOException {
7070
private static JsonElement flattenTextArray(JsonArray arr) {
7171
StringBuilder sb = new StringBuilder();
7272
for (JsonElement e : arr) {
73-
if (!e.isJsonNull()) {
74-
sb.append(e.getAsString());
73+
if (!e.isJsonNull() && e.isJsonPrimitive()) {
74+
JsonPrimitive prim = e.getAsJsonPrimitive();
75+
if (prim.isString()) {
76+
sb.append(prim.getAsString());
77+
}
7578
}
7679
}
7780
return sb.length() == 0 ? JsonNull.INSTANCE : new JsonPrimitive(sb.toString());

com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/persistence/ConversationDataFactory.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,11 @@ public List<Turn> convertToTurns(List<AbstractTurnData> turnDataList) {
212212
if (turnData == null) {
213213
continue;
214214
}
215+
// Skip subagent turns - they are not part of the main conversation history
216+
if (turnData instanceof CopilotTurnData copilotCheck
217+
&& copilotCheck.getParentTurnId() != null) {
218+
continue;
219+
}
215220
if (turnData instanceof UserTurnData userTurnData) {
216221
String requestText = userTurnData.getMessage() != null ? userTurnData.getMessage().getText() : "";
217222
Either<String, List<ChatCompletionContentPart>> request = Either

com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/persistence/ConversationPersistenceManager.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,11 @@ private ConversationData updateConversationProgressInternal(String conversationI
286286
CopilotTurnData copilotTurnData = findOrCreateCopilotTurn(conversationData, progress.getTurnId());
287287
dataFactory.updateReplyFromProgress(copilotTurnData.getReply(), progress);
288288

289+
// Mark subagent turns with their parent turn ID
290+
if (StringUtils.isNotBlank(progress.getParentTurnId())) {
291+
copilotTurnData.setParentTurnId(progress.getParentTurnId());
292+
}
293+
289294
// Update suggested title in CopilotTurnData if present
290295
if (StringUtils.isNotBlank(progress.getSuggestedTitle())) {
291296
copilotTurnData.setSuggestedTitle(progress.getSuggestedTitle());
@@ -543,4 +548,37 @@ public CompletableFuture<Void> persistModelInfo(String conversationId, String tu
543548
public ConversationDataFactory getDataFactory() {
544549
return dataFactory;
545550
}
551+
552+
/**
553+
* Sets the subagentToolCallId on a subagent's CopilotTurnData to associate it with the parent turn's run_subagent
554+
* tool call. This enables precise positioning of subagent content during conversation restoration.
555+
*
556+
* @param conversationId the conversation ID
557+
* @param subagentTurnId the subagent's turn ID
558+
* @param toolCallId the run_subagent tool call ID from the parent turn
559+
* @return a future that completes when the tool call ID has been set
560+
*/
561+
public CompletableFuture<Void> setSubagentToolCallId(String conversationId, String subagentTurnId,
562+
String toolCallId) {
563+
if (toolCallId == null || subagentTurnId == null) {
564+
return CompletableFuture.completedFuture(null);
565+
}
566+
return CompletableFuture.runAsync(() -> {
567+
lock.writeLock().lock();
568+
try {
569+
ConversationData conversation = getConversationFromCacheOrLoadFromDisk(conversationId);
570+
if (conversation == null) {
571+
return;
572+
}
573+
AbstractTurnData turnData = findTurn(conversation, subagentTurnId);
574+
if (turnData instanceof CopilotTurnData turn && turn.getSubagentToolCallId() == null) {
575+
turn.setSubagentToolCallId(toolCallId);
576+
}
577+
} catch (IOException e) {
578+
CopilotCore.LOGGER.error("Failed to set subagent tool call ID: " + conversationId, e);
579+
} finally {
580+
lock.writeLock().unlock();
581+
}
582+
});
583+
}
546584
}

com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/persistence/CopilotTurnData.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
public class CopilotTurnData extends AbstractTurnData {
2020
private ReplyData reply;
2121
private String suggestedTitle;
22+
private String parentTurnId;
23+
private String subagentToolCallId;
2224

2325
/**
2426
* Default constructor initializing default values.
@@ -43,11 +45,27 @@ public void setSuggestedTitle(String suggestedTitle) {
4345
this.suggestedTitle = suggestedTitle;
4446
}
4547

48+
public String getParentTurnId() {
49+
return parentTurnId;
50+
}
51+
52+
public void setParentTurnId(String parentTurnId) {
53+
this.parentTurnId = parentTurnId;
54+
}
55+
56+
public String getSubagentToolCallId() {
57+
return subagentToolCallId;
58+
}
59+
60+
public void setSubagentToolCallId(String subagentToolCallId) {
61+
this.subagentToolCallId = subagentToolCallId;
62+
}
63+
4664
@Override
4765
public int hashCode() {
4866
final int prime = 31;
4967
int result = super.hashCode();
50-
result = prime * result + Objects.hash(reply, suggestedTitle);
68+
result = prime * result + Objects.hash(reply, suggestedTitle, parentTurnId, subagentToolCallId);
5169
return result;
5270
}
5371

@@ -63,7 +81,9 @@ public boolean equals(Object obj) {
6381
return false;
6482
}
6583
CopilotTurnData other = (CopilotTurnData) obj;
66-
return Objects.equals(reply, other.reply) && Objects.equals(suggestedTitle, other.suggestedTitle);
84+
return Objects.equals(reply, other.reply) && Objects.equals(suggestedTitle, other.suggestedTitle)
85+
&& Objects.equals(parentTurnId, other.parentTurnId)
86+
&& Objects.equals(subagentToolCallId, other.subagentToolCallId);
6787
}
6888

6989
@Override
@@ -77,6 +97,8 @@ public String toString() {
7797
// Include CopilotTurnData specific properties
7898
builder.append("reply", reply);
7999
builder.append("suggestedTitle", suggestedTitle);
100+
builder.append("parentTurnId", parentTurnId);
101+
builder.append("subagentToolCallId", subagentToolCallId);
80102
return builder.toString();
81103
}
82104

0 commit comments

Comments
 (0)