Skip to content

Commit b2fbe64

Browse files
Copilotbrunoborges
andcommitted
Port session.setModel() and built-in tool override support from upstream
Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com>
1 parent b24e25f commit b2fbe64

File tree

7 files changed

+175
-4
lines changed

7 files changed

+175
-4
lines changed

.lastmerge

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
b9f746ab1b9c5f31af03a6f8a6cf4e680b3fd6b8
1+
dcd86c189501ce1b46b787ca60d90f3f315f3079

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
88
99
## [Unreleased]
1010

11-
> **Upstream sync:** [`github/copilot-sdk@b9f746a`](https://github.com/github/copilot-sdk/commit/b9f746ab1b9c5f31af03a6f8a6cf4e680b3fd6b8)
11+
> **Upstream sync:** [`github/copilot-sdk@dcd86c1`](https://github.com/github/copilot-sdk/commit/dcd86c189501ce1b46b787ca60d90f3f315f3079)
1212
1313
### Added
1414

15+
- `CopilotSession.setModel(String)` — changes the model for an existing session mid-conversation; the new model takes effect for the next message, and conversation history is preserved (upstream: [`bd98e3a`](https://github.com/github/copilot-sdk/commit/bd98e3a))
16+
- `ToolDefinition.createOverride(String, String, Map, ToolHandler)` — creates a tool definition that overrides a built-in CLI tool with the same name (upstream: [`f843c80`](https://github.com/github/copilot-sdk/commit/f843c80))
17+
- `ToolDefinition` record now includes `overridesBuiltInTool` field; when `true`, signals to the CLI that the custom tool intentionally replaces a built-in (upstream: [`f843c80`](https://github.com/github/copilot-sdk/commit/f843c80))
1518
- `CopilotSession.listAgents()` — lists custom agents available for selection (upstream: [`9d998fb`](https://github.com/github/copilot-sdk/commit/9d998fb))
1619
- `CopilotSession.getCurrentAgent()` — gets the currently selected custom agent (upstream: [`9d998fb`](https://github.com/github/copilot-sdk/commit/9d998fb))
1720
- `CopilotSession.selectAgent(String)` — selects a custom agent for the session (upstream: [`9d998fb`](https://github.com/github/copilot-sdk/commit/9d998fb))

src/main/java/com/github/copilot/sdk/CopilotSession.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,28 @@ public CompletableFuture<Void> abort() {
808808
return rpc.invoke("session.abort", Map.of("sessionId", sessionId), Void.class);
809809
}
810810

811+
/**
812+
* Changes the model for this session.
813+
* <p>
814+
* The new model takes effect for the next message. Conversation history is
815+
* preserved.
816+
*
817+
* <pre>{@code
818+
* session.setModel("gpt-4.1").get();
819+
* }</pre>
820+
*
821+
* @param model
822+
* the model ID to switch to (e.g., {@code "gpt-4.1"})
823+
* @return a future that completes when the model switch is acknowledged
824+
* @throws IllegalStateException
825+
* if this session has been terminated
826+
* @since 1.0.11
827+
*/
828+
public CompletableFuture<Void> setModel(String model) {
829+
ensureNotTerminated();
830+
return rpc.invoke("session.model.switchTo", Map.of("sessionId", sessionId, "modelId", model), Void.class);
831+
}
832+
811833
/**
812834
* Lists the custom agents available for selection in this session.
813835
*

src/main/java/com/github/copilot/sdk/json/ToolDefinition.java

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,18 @@
4848
* the JSON Schema defining the tool's parameters
4949
* @param handler
5050
* the handler function to execute when invoked
51+
* @param overridesBuiltInTool
52+
* when {@code true}, indicates that this tool intentionally
53+
* overrides a built-in CLI tool with the same name; {@code null} or
54+
* {@code false} means the tool is purely custom
5155
* @see SessionConfig#setTools(java.util.List)
5256
* @see ToolHandler
5357
* @since 1.0.0
5458
*/
5559
@JsonInclude(JsonInclude.Include.NON_NULL)
5660
public record ToolDefinition(@JsonProperty("name") String name, @JsonProperty("description") String description,
57-
@JsonProperty("parameters") Object parameters, @JsonIgnore ToolHandler handler) {
61+
@JsonProperty("parameters") Object parameters, @JsonIgnore ToolHandler handler,
62+
@JsonProperty("overridesBuiltInTool") Boolean overridesBuiltInTool) {
5863

5964
/**
6065
* Creates a tool definition with a JSON schema for parameters.
@@ -74,6 +79,30 @@ public record ToolDefinition(@JsonProperty("name") String name, @JsonProperty("d
7479
*/
7580
public static ToolDefinition create(String name, String description, Map<String, Object> schema,
7681
ToolHandler handler) {
77-
return new ToolDefinition(name, description, schema, handler);
82+
return new ToolDefinition(name, description, schema, handler, null);
83+
}
84+
85+
/**
86+
* Creates a tool definition that overrides a built-in CLI tool.
87+
* <p>
88+
* Use this factory method when you want your custom tool to replace a built-in
89+
* tool (e.g., {@code grep}, {@code read_file}) with the same name. Setting
90+
* {@code overridesBuiltInTool} to {@code true} signals to the CLI that this is
91+
* intentional.
92+
*
93+
* @param name
94+
* the name of the built-in tool to override
95+
* @param description
96+
* a description of what the tool does
97+
* @param schema
98+
* the JSON Schema as a {@code Map}
99+
* @param handler
100+
* the handler function to execute when invoked
101+
* @return a new tool definition with the override flag set
102+
* @since 1.0.11
103+
*/
104+
public static ToolDefinition createOverride(String name, String description, Map<String, Object> schema,
105+
ToolHandler handler) {
106+
return new ToolDefinition(name, description, schema, handler, true);
78107
}
79108
}

src/site/markdown/advanced.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ This guide covers advanced scenarios for extending and customizing your Copilot
77
## Table of Contents
88

99
- [Custom Tools](#Custom_Tools)
10+
- [Overriding Built-in Tools](#Overriding_Built-in_Tools)
11+
- [Switching Models Mid-Session](#Switching_Models_Mid-Session)
1012
- [System Messages](#System_Messages)
1113
- [Adding Rules](#Adding_Rules)
1214
- [Full Control](#Full_Control)
@@ -73,6 +75,62 @@ var session = client.createSession(
7375

7476
See [ToolDefinition](apidocs/com/github/copilot/sdk/json/ToolDefinition.html) Javadoc for schema details.
7577

78+
### Overriding Built-in Tools
79+
80+
You can replace a built-in CLI tool (such as `grep` or `read_file`) with your own implementation
81+
by using `ToolDefinition.createOverride()`. This signals to the CLI that the name collision is
82+
intentional and your custom implementation should be used instead.
83+
84+
```java
85+
var customGrep = ToolDefinition.createOverride(
86+
"grep",
87+
"Project-aware search with custom filtering",
88+
Map.of(
89+
"type", "object",
90+
"properties", Map.of(
91+
"query", Map.of("type", "string", "description", "Search query")
92+
),
93+
"required", List.of("query")
94+
),
95+
invocation -> {
96+
String query = (String) invocation.getArguments().get("query");
97+
// Your custom search logic here
98+
return CompletableFuture.completedFuture("Results for: " + query);
99+
}
100+
);
101+
102+
var session = client.createSession(
103+
new SessionConfig()
104+
.setTools(List.of(customGrep))
105+
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
106+
).get();
107+
```
108+
109+
---
110+
111+
## Switching Models Mid-Session
112+
113+
You can change the model used by an existing session without losing conversation history.
114+
The new model takes effect starting with the next message sent.
115+
116+
```java
117+
var session = client.createSession(
118+
new SessionConfig()
119+
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
120+
).get();
121+
122+
// Switch to a different model mid-conversation
123+
session.setModel("gpt-4.1").get();
124+
125+
// Next message will use the new model
126+
session.sendAndWait(new MessageOptions().setPrompt("Continue with the new model")).get();
127+
```
128+
129+
The session emits a [`SessionModelChangeEvent`](apidocs/com/github/copilot/sdk/events/SessionModelChangeEvent.html)
130+
when the switch completes, which you can observe with `session.on(SessionModelChangeEvent.class, event -> ...)`.
131+
132+
See [CopilotSession.setModel()](apidocs/com/github/copilot/sdk/CopilotSession.html#setModel(java.lang.String)) Javadoc for details.
133+
76134
---
77135

78136
## System Messages

src/test/java/com/github/copilot/sdk/ClosedSessionGuardTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,4 +352,23 @@ void testTryWithResourcesDoubleClose() throws Exception {
352352
});
353353
}
354354
}
355+
356+
/**
357+
* Verifies that setModel() throws IllegalStateException after session is
358+
* terminated.
359+
*/
360+
@Test
361+
void testSetModelThrowsAfterTermination() throws Exception {
362+
ctx.configureForTest("session", "should_receive_session_events");
363+
364+
try (CopilotClient client = ctx.createClient()) {
365+
CopilotSession session = client.createSession(new SessionConfig()
366+
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setModel("fake-test-model")).get();
367+
session.close();
368+
369+
assertThrows(IllegalStateException.class, () -> {
370+
session.setModel("gpt-4.1");
371+
});
372+
}
373+
}
355374
}

src/test/java/com/github/copilot/sdk/ToolsTest.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,4 +320,44 @@ void testDeniesCustomToolWhenPermissionDenied(TestInfo testInfo) throws Exceptio
320320
session.close();
321321
}
322322
}
323+
324+
/**
325+
* Verifies that a custom tool can override a built-in CLI tool with the same
326+
* name when {@code overridesBuiltInTool} is set to {@code true}.
327+
*
328+
* @see Snapshot: tools/overrides_built_in_tool_with_custom_tool
329+
*/
330+
@Test
331+
void testOverridesBuiltInToolWithCustomTool(TestInfo testInfo) throws Exception {
332+
ctx.configureForTest("tools", "overrides_built_in_tool_with_custom_tool");
333+
334+
var parameters = new HashMap<String, Object>();
335+
var properties = new HashMap<String, Object>();
336+
properties.put("query", Map.of("type", "string", "description", "Search query"));
337+
parameters.put("type", "object");
338+
parameters.put("properties", properties);
339+
parameters.put("required", List.of("query"));
340+
341+
ToolDefinition customGrep = ToolDefinition.createOverride("grep", "A custom grep implementation", parameters,
342+
(invocation) -> {
343+
Map<String, Object> args = invocation.getArguments();
344+
String query = (String) args.get("query");
345+
return CompletableFuture.completedFuture("CUSTOM_GREP_RESULT: " + query);
346+
});
347+
348+
try (CopilotClient client = ctx.createClient()) {
349+
CopilotSession session = client.createSession(new SessionConfig().setTools(List.of(customGrep))
350+
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get();
351+
352+
AssistantMessageEvent response = session
353+
.sendAndWait(new MessageOptions().setPrompt("Use grep to search for the word 'hello'"))
354+
.get(60, TimeUnit.SECONDS);
355+
356+
assertNotNull(response);
357+
assertTrue(response.getData().content().contains("CUSTOM_GREP_RESULT"),
358+
"Response should contain CUSTOM_GREP_RESULT: " + response.getData().content());
359+
360+
session.close();
361+
}
362+
}
323363
}

0 commit comments

Comments
 (0)