Skip to content

Commit 06dc43a

Browse files
committed
Add history.clearContext and Tool.isTerminal across all SDKs
Regenerates the RPC clients for the new `session.history.clearContext` method and the `session.context_cleared` event, and adds a hand-authored `isTerminal` tool flag to every language surface. `isTerminal` lets a tool declare that a successful call ends the agent turn: the runtime's tool phase halts instead of feeding the result back to the model for another round. A failed call leaves the loop running so the model can read the error and retry. Without it a turn-ending tool can only approximate the behavior by returning a rejected result, which halts the loop but is semantically wrong. Per language: - Node.js: `Tool.isTerminal`, `defineTool` config, both session-config serialization sites. - Go: `Tool.IsTerminal` with `json:"isTerminal,omitempty"`. - Python: `Tool.is_terminal`, `define_tool` overloads, both client serialization sites. - Rust: `Tool::is_terminal`, skipped when false. - Java: `ToolDefinition.isTerminal` as a record component, plus a seven-argument convenience constructor so existing call sites keep compiling. - .NET: `CopilotToolOptions.IsTerminal`, the `is_terminal` additional-property key, and the wire `ToolDefinition`. Adds serialization tests in Go, Rust and Java covering both the camelCase wire name and omission when unset; the Java test also pins the seven-argument constructor so the record change stays source-compatible.
1 parent 40ae242 commit 06dc43a

24 files changed

Lines changed: 626 additions & 7 deletions

File tree

dotnet/src/Client.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2485,17 +2485,20 @@ internal record ToolDefinition(
24852485
JsonElement Parameters, /* JSON schema */
24862486
bool? OverridesBuiltInTool = null,
24872487
bool? SkipPermission = null,
2488-
CopilotToolDefer? Defer = null)
2488+
CopilotToolDefer? Defer = null,
2489+
bool? IsTerminal = null)
24892490
{
24902491
public static ToolDefinition FromAIFunction(AIFunctionDeclaration function)
24912492
{
24922493
var overrides = function.AdditionalProperties.TryGetValue(CopilotTool.OverridesBuiltInToolKey, out var val) && val is true;
24932494
var skipPerm = function.AdditionalProperties.TryGetValue(CopilotTool.SkipPermissionKey, out var skipVal) && skipVal is true;
24942495
var defer = function.AdditionalProperties.TryGetValue(CopilotTool.DeferKey, out var deferVal) && deferVal is CopilotToolDefer d ? d : (CopilotToolDefer?)null;
2496+
var isTerminal = function.AdditionalProperties.TryGetValue(CopilotTool.IsTerminalKey, out var terminalVal) && terminalVal is true;
24952497
return new ToolDefinition(function.Name, function.Description, function.JsonSchema,
24962498
overrides ? true : null,
24972499
skipPerm ? true : null,
2498-
defer);
2500+
defer,
2501+
isTerminal ? true : null);
24992502
}
25002503
}
25012504

dotnet/src/CopilotTool.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ public static class CopilotTool
1717
/// <summary>The key used in <see cref="AITool.AdditionalProperties"/> to indicate that a tool can execute without a permission prompt.</summary>
1818
internal const string SkipPermissionKey = "skip_permission";
1919

20+
/// <summary>The key used in <see cref="AITool.AdditionalProperties"/> to indicate that a successful call to the tool ends the agent turn.</summary>
21+
internal const string IsTerminalKey = "is_terminal";
22+
2023
/// <summary>The key used in <see cref="AITool.AdditionalProperties"/> to carry the tool's <see cref="CopilotToolDefer"/> deferral mode.</summary>
2124
internal const string DeferKey = "defer";
2225

@@ -87,7 +90,7 @@ static void ApplyToolInvocationBinding(AIFunctionFactoryOptions factoryOptions)
8790

8891
static void ApplyToolOptions(AIFunctionFactoryOptions factoryOptions, CopilotToolOptions? toolOptions)
8992
{
90-
if (toolOptions is not null && (toolOptions.OverridesBuiltInTool || toolOptions.SkipPermission || toolOptions.Defer is not null))
93+
if (toolOptions is not null && (toolOptions.OverridesBuiltInTool || toolOptions.SkipPermission || toolOptions.IsTerminal || toolOptions.Defer is not null))
9194
{
9295
Dictionary<string, object?> additionalProperties = new(StringComparer.Ordinal);
9396
if (factoryOptions.AdditionalProperties is not null)
@@ -108,6 +111,11 @@ static void ApplyToolOptions(AIFunctionFactoryOptions factoryOptions, CopilotToo
108111
additionalProperties[SkipPermissionKey] = true;
109112
}
110113

114+
if (toolOptions.IsTerminal)
115+
{
116+
additionalProperties[IsTerminalKey] = true;
117+
}
118+
111119
if (toolOptions.Defer is { } defer)
112120
{
113121
additionalProperties[DeferKey] = defer;
@@ -143,6 +151,16 @@ public sealed class CopilotToolOptions
143151
/// </remarks>
144152
public bool SkipPermission { get; set; }
145153

154+
/// <summary>
155+
/// Gets or sets a value indicating whether a successful call to this tool ends the agent turn.
156+
/// </summary>
157+
/// <remarks>
158+
/// When true, the runtime's tool phase halts after a successful call instead of feeding the result back to the
159+
/// model for another round. A failed call leaves the loop running so the model can read the error and retry.
160+
/// The resulting <see cref="AIFunction"/> includes "is_terminal": true in its <see cref="AITool.AdditionalProperties"/>.
161+
/// </remarks>
162+
public bool IsTerminal { get; set; }
163+
146164
/// <summary>
147165
/// Gets or sets a value controlling whether this tool may be deferred (loaded lazily via tool search) rather than always pre-loaded.
148166
/// </summary>

dotnet/src/Generated/Rpc.cs

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dotnet/src/Generated/SessionEvents.cs

Lines changed: 34 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

go/client_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2302,3 +2302,40 @@ func TestResumeSessionRequest_ExpAssignments(t *testing.T) {
23022302
}
23032303
})
23042304
}
2305+
2306+
func TestIsTerminal(t *testing.T) {
2307+
t.Run("IsTerminal is serialized in tool definition", func(t *testing.T) {
2308+
tool := Tool{
2309+
Name: "clear_context",
2310+
Description: "Clear the conversation",
2311+
IsTerminal: true,
2312+
Handler: func(_ ToolInvocation) (ToolResult, error) { return ToolResult{}, nil },
2313+
}
2314+
data, err := json.Marshal(tool)
2315+
if err != nil {
2316+
t.Fatalf("Failed to marshal: %v", err)
2317+
}
2318+
var m map[string]any
2319+
if err := json.Unmarshal(data, &m); err != nil {
2320+
t.Fatalf("Failed to unmarshal: %v", err)
2321+
}
2322+
if m["isTerminal"] != true {
2323+
t.Errorf("Expected isTerminal to be true, got %v", m["isTerminal"])
2324+
}
2325+
})
2326+
2327+
t.Run("IsTerminal is omitted when false", func(t *testing.T) {
2328+
tool := Tool{Name: "plain", Description: "A plain tool"}
2329+
data, err := json.Marshal(tool)
2330+
if err != nil {
2331+
t.Fatalf("Failed to marshal: %v", err)
2332+
}
2333+
var m map[string]any
2334+
if err := json.Unmarshal(data, &m); err != nil {
2335+
t.Fatalf("Failed to unmarshal: %v", err)
2336+
}
2337+
if _, ok := m["isTerminal"]; ok {
2338+
t.Error("Expected isTerminal to be omitted when false")
2339+
}
2340+
})
2341+
}

go/rpc/zrpc.go

Lines changed: 45 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

go/rpc/zsession_encoding.go

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

go/rpc/zsession_events.go

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

go/types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,6 +1178,11 @@ type Tool struct {
11781178
Parameters map[string]any `json:"parameters,omitzero"`
11791179
OverridesBuiltInTool bool `json:"overridesBuiltInTool,omitempty"`
11801180
SkipPermission bool `json:"skipPermission,omitempty"`
1181+
// IsTerminal reports that a successful call to this tool ends the agent
1182+
// turn: the runtime halts instead of feeding the result back to the model
1183+
// for another round. A failed call leaves the loop running so the model can
1184+
// read the error and retry.
1185+
IsTerminal bool `json:"isTerminal,omitempty"`
11811186
// Defer controls whether the tool may be deferred (loaded lazily via tool
11821187
// search) rather than always pre-loaded. When empty, the runtime decides.
11831188
Defer ToolDefer `json:"defer,omitempty"`

go/zsession_events.go

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)