From 605da96bc5e54077a3c60c73317fffc092324bc7 Mon Sep 17 00:00:00 2001 From: "benapple [msft]" <123903683+Halcyonhal9@users.noreply.github.com> Date: Sat, 23 May 2026 18:02:25 -0700 Subject: [PATCH] SDK: add includeCurrentDatetime flag to suppress datetime injection Adds an opt-in `includeCurrentDatetime?: boolean` session/resume option across all language bindings (Node, Python, Go, .NET, Rust, Java). When `false`, the SDK forwards `includeCurrentDatetime: false` to the runtime, which suppresses the injected `` tag on model-facing user messages. Defaults to true for backwards compatibility. This is the SDK-side counterpart to copilot-agent-runtime PR github/copilot-agent-runtime#8130, which is what actually does the suppression work in the runtime. The combination is needed when short prompts (e.g. session-name summarisation) are confused by datetime context, or when the user's own prompt contains a date/time that collides with the injected tag. Surface added per language: - Node: `SessionConfigBase.includeCurrentDatetime?: boolean` - Python: `create_session(..., include_current_datetime=...)` and `resume_session(..., include_current_datetime=...)` - Go: `SessionConfig.IncludeCurrentDatetime` / `ResumeSessionConfig.IncludeCurrentDatetime` (*bool) - .NET: `SessionConfigBase.IncludeCurrentDatetime` (bool?) - Rust: `SessionConfig.include_current_datetime` / `ResumeSessionConfig.include_current_datetime` plus `with_include_current_datetime` builder + wire fields - Java: `SessionConfig.includeCurrentDatetime` / `ResumeSessionConfig.includeCurrentDatetime` plus `SessionRequestBuilder.includeCurrentDatetime`, wire fields, and clone/jackson/builder unit tests The wire field name is `includeCurrentDatetime` everywhere, matching the runtime PR's request schema. Verified locally with CONTRIBUTING.md test commands: - nodejs: `npm test` 416 / 7 skipped; `npm run lint` 0 errors - python: unit 145, ruff clean, e2e 283 / 7 pre-existing auth fails / 6 skipped (failures unrelated to this change) - go: `go test ./...` all pass; `golangci-lint run` 1 pre-existing govet in `definetool.go:210` - dotnet: `dotnet test` 463 / 2 skipped - rust: `cargo test --features test-support` all + 18 doctests pass - java: custom E2E driver against gpt-5.2 (Halcyonhal9/lumi2 fork) Refs: github/copilot-agent-runtime#8130 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/src/Client.cs | 12 ++-- dotnet/src/Types.cs | 7 ++ go/client.go | 2 + go/types.go | 10 +++ .../copilot/sdk/SessionRequestBuilder.java | 2 + .../sdk/json/CreateSessionRequest.java | 22 ++++++ .../copilot/sdk/json/ResumeSessionConfig.java | 42 +++++++++++ .../sdk/json/ResumeSessionRequest.java | 22 ++++++ .../copilot/sdk/json/SessionConfig.java | 42 +++++++++++ .../github/copilot/sdk/ConfigCloneTest.java | 38 ++++++++++ .../sdk/OptionalApiAndJacksonTest.java | 44 ++++++++++++ .../sdk/SessionRequestBuilderTest.java | 70 +++++++++++++++++++ nodejs/src/client.ts | 2 + nodejs/src/types.ts | 7 ++ python/copilot/client.py | 13 ++++ rust/src/types.rs | 39 ++++++++++- rust/src/wire.rs | 4 ++ 17 files changed, 373 insertions(+), 5 deletions(-) diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index a5cc62354..0ff154d1b 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -618,7 +618,8 @@ public async Task CreateSessionAsync(SessionConfig config, Cance GitHubToken: config.GitHubToken, RemoteSession: config.RemoteSession, Cloud: config.Cloud, - InstructionDirectories: config.InstructionDirectories); + InstructionDirectories: config.InstructionDirectories, + IncludeCurrentDatetime: config.IncludeCurrentDatetime); var rpcTimestamp = Stopwatch.GetTimestamp(); var response = await InvokeRpcAsync( @@ -778,7 +779,8 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes GitHubToken: config.GitHubToken, RemoteSession: config.RemoteSession, ContinuePendingWork: config.ContinuePendingWork, - InstructionDirectories: config.InstructionDirectories); + InstructionDirectories: config.InstructionDirectories, + IncludeCurrentDatetime: config.IncludeCurrentDatetime); var rpcTimestamp = Stopwatch.GetTimestamp(); var response = await InvokeRpcAsync( @@ -1866,7 +1868,8 @@ internal record CreateSessionRequest( string? GitHubToken = null, RemoteSessionMode? RemoteSession = null, CloudSessionOptions? Cloud = null, - IList? InstructionDirectories = null); + IList? InstructionDirectories = null, + bool? IncludeCurrentDatetime = null); internal record ToolDefinition( string Name, @@ -1928,7 +1931,8 @@ internal record ResumeSessionRequest( string? GitHubToken = null, RemoteSessionMode? RemoteSession = null, bool? ContinuePendingWork = null, - IList? InstructionDirectories = null); + IList? InstructionDirectories = null, + bool? IncludeCurrentDatetime = null); internal record ResumeSessionResponse( string SessionId, diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index 29fceb40c..ceb5e00f7 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -2214,6 +2214,7 @@ protected SessionConfigBase(SessionConfigBase? other) OnUserInputRequest = other.OnUserInputRequest; Provider = other.Provider; EnableSessionTelemetry = other.EnableSessionTelemetry; + IncludeCurrentDatetime = other.IncludeCurrentDatetime; ReasoningEffort = other.ReasoningEffort; CreateSessionFsProvider = other.CreateSessionFsProvider; GitHubToken = other.GitHubToken; @@ -2292,6 +2293,12 @@ protected SessionConfigBase(SessionConfigBase? other) /// public bool? EnableSessionTelemetry { get; set; } + /// + /// When false, suppresses current_datetime injection in the model-facing user message. + /// Defaults to true (datetime is included) when null. + /// + public bool? IncludeCurrentDatetime { get; set; } + /// Handler for permission requests from the server. public Func>? OnPermissionRequest { get; set; } diff --git a/go/client.go b/go/client.go index 9491eb199..6632bb03f 100644 --- a/go/client.go +++ b/go/client.go @@ -612,6 +612,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses req.ExcludedTools = config.ExcludedTools req.Provider = config.Provider req.EnableSessionTelemetry = config.EnableSessionTelemetry + req.IncludeCurrentDatetime = config.IncludeCurrentDatetime req.ModelCapabilities = config.ModelCapabilities req.WorkingDirectory = config.WorkingDirectory req.MCPServers = config.MCPServers @@ -796,6 +797,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, req.Tools = config.Tools req.Provider = config.Provider req.EnableSessionTelemetry = config.EnableSessionTelemetry + req.IncludeCurrentDatetime = config.IncludeCurrentDatetime req.ModelCapabilities = config.ModelCapabilities req.AvailableTools = config.AvailableTools req.ExcludedTools = config.ExcludedTools diff --git a/go/types.go b/go/types.go index be86a326c..156e0689c 100644 --- a/go/types.go +++ b/go/types.go @@ -870,6 +870,10 @@ type SessionConfig struct { // regardless of this setting. This is independent of the OpenTelemetry // configuration in ClientOptions.Telemetry. EnableSessionTelemetry *bool + // IncludeCurrentDatetime includes the current local datetime in model-facing user messages. + // When false, suppresses the injected tag. When nil, defaults + // to true for backwards compatibility. + IncludeCurrentDatetime *bool // ModelCapabilities overrides individual model capabilities resolved by the runtime. // Only non-nil fields are applied over the runtime-resolved capabilities. ModelCapabilities *rpc.ModelCapabilitiesOverride @@ -1084,6 +1088,10 @@ type ResumeSessionConfig struct { // regardless of this setting. This is independent of the OpenTelemetry // configuration in ClientOptions.Telemetry. EnableSessionTelemetry *bool + // IncludeCurrentDatetime includes the current local datetime in model-facing user messages. + // When false, suppresses the injected tag. When nil, defaults + // to true for backwards compatibility. + IncludeCurrentDatetime *bool // ModelCapabilities overrides individual model capabilities resolved by the runtime. // Only non-nil fields are applied over the runtime-resolved capabilities. ModelCapabilities *rpc.ModelCapabilitiesOverride @@ -1374,6 +1382,7 @@ type createSessionRequest struct { ExcludedTools []string `json:"excludedTools,omitempty"` Provider *ProviderConfig `json:"provider,omitempty"` EnableSessionTelemetry *bool `json:"enableSessionTelemetry,omitempty"` + IncludeCurrentDatetime *bool `json:"includeCurrentDatetime,omitempty"` ModelCapabilities *rpc.ModelCapabilitiesOverride `json:"modelCapabilities,omitempty"` RequestPermission *bool `json:"requestPermission,omitempty"` RequestUserInput *bool `json:"requestUserInput,omitempty"` @@ -1428,6 +1437,7 @@ type resumeSessionRequest struct { ExcludedTools []string `json:"excludedTools,omitempty"` Provider *ProviderConfig `json:"provider,omitempty"` EnableSessionTelemetry *bool `json:"enableSessionTelemetry,omitempty"` + IncludeCurrentDatetime *bool `json:"includeCurrentDatetime,omitempty"` ModelCapabilities *rpc.ModelCapabilitiesOverride `json:"modelCapabilities,omitempty"` RequestPermission *bool `json:"requestPermission,omitempty"` RequestUserInput *bool `json:"requestUserInput,omitempty"` diff --git a/java/src/main/java/com/github/copilot/sdk/SessionRequestBuilder.java b/java/src/main/java/com/github/copilot/sdk/SessionRequestBuilder.java index 0cdc4f942..79fcef0ea 100644 --- a/java/src/main/java/com/github/copilot/sdk/SessionRequestBuilder.java +++ b/java/src/main/java/com/github/copilot/sdk/SessionRequestBuilder.java @@ -112,6 +112,7 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config, String sess request.setExcludedTools(config.getExcludedTools()); request.setProvider(config.getProvider()); config.getEnableSessionTelemetry().ifPresent(request::setEnableSessionTelemetry); + config.getIncludeCurrentDatetime().ifPresent(request::setIncludeCurrentDatetime); if (config.getOnUserInputRequest() != null) { request.setRequestUserInput(true); } @@ -203,6 +204,7 @@ static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionCo request.setExcludedTools(config.getExcludedTools()); request.setProvider(config.getProvider()); config.getEnableSessionTelemetry().ifPresent(request::setEnableSessionTelemetry); + config.getIncludeCurrentDatetime().ifPresent(request::setIncludeCurrentDatetime); if (config.getOnUserInputRequest() != null) { request.setRequestUserInput(true); } diff --git a/java/src/main/java/com/github/copilot/sdk/json/CreateSessionRequest.java b/java/src/main/java/com/github/copilot/sdk/json/CreateSessionRequest.java index 881840a73..b7dc5c3f8 100644 --- a/java/src/main/java/com/github/copilot/sdk/json/CreateSessionRequest.java +++ b/java/src/main/java/com/github/copilot/sdk/json/CreateSessionRequest.java @@ -55,6 +55,9 @@ public final class CreateSessionRequest { @JsonProperty("enableSessionTelemetry") private Boolean enableSessionTelemetry; + @JsonProperty("includeCurrentDatetime") + private Boolean includeCurrentDatetime; + @JsonProperty("requestPermission") private Boolean requestPermission; @@ -241,6 +244,25 @@ public void clearEnableSessionTelemetry() { this.enableSessionTelemetry = null; } + /** Gets include current datetime flag. @return the flag */ + public Boolean getIncludeCurrentDatetime() { + return includeCurrentDatetime; + } + + /** + * Sets include current datetime flag. @param includeCurrentDatetime the flag + */ + public void setIncludeCurrentDatetime(Boolean includeCurrentDatetime) { + this.includeCurrentDatetime = includeCurrentDatetime; + } + + /** + * Clears the includeCurrentDatetime setting, reverting to the default behavior. + */ + public void clearIncludeCurrentDatetime() { + this.includeCurrentDatetime = null; + } + /** Gets request permission flag. @return the flag */ public Boolean getRequestPermission() { return requestPermission; diff --git a/java/src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java b/java/src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java index 72c9f6f47..92c1fd6dc 100644 --- a/java/src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java +++ b/java/src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java @@ -46,6 +46,7 @@ public class ResumeSessionConfig { private List excludedTools; private ProviderConfig provider; private Boolean enableSessionTelemetry; + private Boolean includeCurrentDatetime; private String reasoningEffort; private ModelCapabilitiesOverride modelCapabilities; private PermissionHandler onPermissionRequest; @@ -279,6 +280,46 @@ public ResumeSessionConfig clearEnableSessionTelemetry() { return this; } + /** + * Gets whether the current local datetime is included in model-facing user + * messages. + * + * @return an {@link java.util.Optional} containing {@code true} to include the + * current datetime or {@code false} to suppress it, or + * {@link java.util.Optional#empty()} to use the runtime default + */ + @JsonIgnore + public Optional getIncludeCurrentDatetime() { + return Optional.ofNullable(includeCurrentDatetime); + } + + /** + * Sets whether to include the current local datetime in model-facing user + * messages. + *

+ * When {@code false}, suppresses the injected {@code } tag. + * Default: {@code true}. + * + * @param includeCurrentDatetime + * {@code true} to include the current datetime, {@code false} to + * suppress it, or {@code null} to use the runtime default + * @return this config for method chaining + */ + public ResumeSessionConfig setIncludeCurrentDatetime(Boolean includeCurrentDatetime) { + this.includeCurrentDatetime = includeCurrentDatetime; + return this; + } + + /** + * Clears the includeCurrentDatetime setting, reverting to the default behavior. + * + * @return this instance for method chaining + */ + public ResumeSessionConfig clearIncludeCurrentDatetime() { + this.includeCurrentDatetime = null; + return this; + } + /** * Gets the reasoning effort level. * @@ -937,6 +978,7 @@ public ResumeSessionConfig clone() { copy.excludedTools = this.excludedTools != null ? new ArrayList<>(this.excludedTools) : null; copy.provider = this.provider; copy.enableSessionTelemetry = this.enableSessionTelemetry; + copy.includeCurrentDatetime = this.includeCurrentDatetime; copy.reasoningEffort = this.reasoningEffort; copy.modelCapabilities = this.modelCapabilities; copy.onPermissionRequest = this.onPermissionRequest; diff --git a/java/src/main/java/com/github/copilot/sdk/json/ResumeSessionRequest.java b/java/src/main/java/com/github/copilot/sdk/json/ResumeSessionRequest.java index 8aca77b7d..c8daf80a0 100644 --- a/java/src/main/java/com/github/copilot/sdk/json/ResumeSessionRequest.java +++ b/java/src/main/java/com/github/copilot/sdk/json/ResumeSessionRequest.java @@ -56,6 +56,9 @@ public final class ResumeSessionRequest { @JsonProperty("enableSessionTelemetry") private Boolean enableSessionTelemetry; + @JsonProperty("includeCurrentDatetime") + private Boolean includeCurrentDatetime; + @JsonProperty("requestPermission") private Boolean requestPermission; @@ -245,6 +248,25 @@ public void clearEnableSessionTelemetry() { this.enableSessionTelemetry = null; } + /** Gets include current datetime flag. @return the flag */ + public Boolean getIncludeCurrentDatetime() { + return includeCurrentDatetime; + } + + /** + * Sets include current datetime flag. @param includeCurrentDatetime the flag + */ + public void setIncludeCurrentDatetime(Boolean includeCurrentDatetime) { + this.includeCurrentDatetime = includeCurrentDatetime; + } + + /** + * Clears the includeCurrentDatetime setting, reverting to the default behavior. + */ + public void clearIncludeCurrentDatetime() { + this.includeCurrentDatetime = null; + } + /** Gets request permission flag. @return the flag */ public Boolean getRequestPermission() { return requestPermission; diff --git a/java/src/main/java/com/github/copilot/sdk/json/SessionConfig.java b/java/src/main/java/com/github/copilot/sdk/json/SessionConfig.java index ddf06cca7..c8f5d444a 100644 --- a/java/src/main/java/com/github/copilot/sdk/json/SessionConfig.java +++ b/java/src/main/java/com/github/copilot/sdk/json/SessionConfig.java @@ -48,6 +48,7 @@ public class SessionConfig { private List excludedTools; private ProviderConfig provider; private Boolean enableSessionTelemetry; + private Boolean includeCurrentDatetime; private PermissionHandler onPermissionRequest; private UserInputHandler onUserInputRequest; private SessionHooks hooks; @@ -334,6 +335,46 @@ public SessionConfig clearEnableSessionTelemetry() { return this; } + /** + * Gets whether the current local datetime is included in model-facing user + * messages. + * + * @return an {@link java.util.Optional} containing {@code true} to include the + * current datetime or {@code false} to suppress it, or + * {@link java.util.Optional#empty()} to use the runtime default + */ + @JsonIgnore + public Optional getIncludeCurrentDatetime() { + return Optional.ofNullable(includeCurrentDatetime); + } + + /** + * Sets whether to include the current local datetime in model-facing user + * messages. + *

+ * When {@code false}, suppresses the injected {@code } tag. + * Default: {@code true}. + * + * @param includeCurrentDatetime + * {@code true} to include the current datetime, {@code false} to + * suppress it, or {@code null} to use the runtime default + * @return this config instance for method chaining + */ + public SessionConfig setIncludeCurrentDatetime(Boolean includeCurrentDatetime) { + this.includeCurrentDatetime = includeCurrentDatetime; + return this; + } + + /** + * Clears the includeCurrentDatetime setting, reverting to the default behavior. + * + * @return this instance for method chaining + */ + public SessionConfig clearIncludeCurrentDatetime() { + this.includeCurrentDatetime = null; + return this; + } + /** * Gets the permission request handler. * @@ -1033,6 +1074,7 @@ public SessionConfig clone() { copy.excludedTools = this.excludedTools != null ? new ArrayList<>(this.excludedTools) : null; copy.provider = this.provider; copy.enableSessionTelemetry = this.enableSessionTelemetry; + copy.includeCurrentDatetime = this.includeCurrentDatetime; copy.onPermissionRequest = this.onPermissionRequest; copy.onUserInputRequest = this.onUserInputRequest; copy.hooks = this.hooks; diff --git a/java/src/test/java/com/github/copilot/sdk/ConfigCloneTest.java b/java/src/test/java/com/github/copilot/sdk/ConfigCloneTest.java index 09bd3ee38..3c8de53ac 100644 --- a/java/src/test/java/com/github/copilot/sdk/ConfigCloneTest.java +++ b/java/src/test/java/com/github/copilot/sdk/ConfigCloneTest.java @@ -215,6 +215,25 @@ void sessionConfigEnableSessionTelemetryDefaultIsNull() { assertTrue(cloned.getEnableSessionTelemetry().isEmpty()); } + @Test + void sessionConfigIncludeCurrentDatetimeCopied() { + SessionConfig original = new SessionConfig(); + original.setIncludeCurrentDatetime(false); + + SessionConfig cloned = original.clone(); + + assertFalse(cloned.getIncludeCurrentDatetime().orElse(true)); + } + + @Test + void sessionConfigIncludeCurrentDatetimeDefaultIsNull() { + SessionConfig original = new SessionConfig(); + + SessionConfig cloned = original.clone(); + + assertTrue(cloned.getIncludeCurrentDatetime().isEmpty()); + } + @Test void resumeSessionConfigEnableSessionTelemetryCopied() { ResumeSessionConfig original = new ResumeSessionConfig(); @@ -234,6 +253,25 @@ void resumeSessionConfigEnableSessionTelemetryDefaultIsNull() { assertTrue(cloned.getEnableSessionTelemetry().isEmpty()); } + @Test + void resumeSessionConfigIncludeCurrentDatetimeCopied() { + ResumeSessionConfig original = new ResumeSessionConfig(); + original.setIncludeCurrentDatetime(false); + + ResumeSessionConfig cloned = original.clone(); + + assertFalse(cloned.getIncludeCurrentDatetime().orElse(true)); + } + + @Test + void resumeSessionConfigIncludeCurrentDatetimeDefaultIsNull() { + ResumeSessionConfig original = new ResumeSessionConfig(); + + ResumeSessionConfig cloned = original.clone(); + + assertTrue(cloned.getIncludeCurrentDatetime().isEmpty()); + } + @Test void clonePreservesNullFields() { CopilotClientOptions opts = new CopilotClientOptions(); diff --git a/java/src/test/java/com/github/copilot/sdk/OptionalApiAndJacksonTest.java b/java/src/test/java/com/github/copilot/sdk/OptionalApiAndJacksonTest.java index 2a9770e2e..da4251996 100644 --- a/java/src/test/java/com/github/copilot/sdk/OptionalApiAndJacksonTest.java +++ b/java/src/test/java/com/github/copilot/sdk/OptionalApiAndJacksonTest.java @@ -63,6 +63,16 @@ void sessionConfig_clearEnableSessionTelemetry() { assertTrue(cfg.getEnableSessionTelemetry().isEmpty()); } + @Test + void sessionConfig_clearIncludeCurrentDatetime() { + var cfg = new SessionConfig(); + cfg.setIncludeCurrentDatetime(false); + assertTrue(cfg.getIncludeCurrentDatetime().isPresent()); + + cfg.clearIncludeCurrentDatetime(); + assertTrue(cfg.getIncludeCurrentDatetime().isEmpty()); + } + @Test void sessionConfig_clearEnableConfigDiscovery() { var cfg = new SessionConfig(); @@ -95,6 +105,16 @@ void resumeSessionConfig_clearEnableSessionTelemetry() { assertTrue(cfg.getEnableSessionTelemetry().isEmpty()); } + @Test + void resumeSessionConfig_clearIncludeCurrentDatetime() { + var cfg = new ResumeSessionConfig(); + cfg.setIncludeCurrentDatetime(false); + assertTrue(cfg.getIncludeCurrentDatetime().isPresent()); + + cfg.clearIncludeCurrentDatetime(); + assertTrue(cfg.getIncludeCurrentDatetime().isEmpty()); + } + @Test void resumeSessionConfig_clearEnableConfigDiscovery() { var cfg = new ResumeSessionConfig(); @@ -332,6 +352,18 @@ void sessionConfig_enableSessionTelemetryValue() { assertFalse(cfg.getEnableSessionTelemetry().orElse(true)); } + @Test + void sessionConfig_includeCurrentDatetimeValue() { + var cfg = new SessionConfig(); + assertTrue(cfg.getIncludeCurrentDatetime().isEmpty()); + + cfg.setIncludeCurrentDatetime(true); + assertTrue(cfg.getIncludeCurrentDatetime().get()); + + cfg.setIncludeCurrentDatetime(false); + assertFalse(cfg.getIncludeCurrentDatetime().get()); + } + @Test void sessionConfig_enableConfigDiscoveryValue() { var cfg = new SessionConfig(); @@ -365,6 +397,18 @@ void resumeSessionConfig_enableSessionTelemetryValue() { assertFalse(cfg.getEnableSessionTelemetry().get()); } + @Test + void resumeSessionConfig_includeCurrentDatetimeValue() { + var cfg = new ResumeSessionConfig(); + assertTrue(cfg.getIncludeCurrentDatetime().isEmpty()); + + cfg.setIncludeCurrentDatetime(true); + assertTrue(cfg.getIncludeCurrentDatetime().get()); + + cfg.setIncludeCurrentDatetime(false); + assertFalse(cfg.getIncludeCurrentDatetime().get()); + } + @Test void resumeSessionConfig_enableConfigDiscoveryValue() { var cfg = new ResumeSessionConfig(); diff --git a/java/src/test/java/com/github/copilot/sdk/SessionRequestBuilderTest.java b/java/src/test/java/com/github/copilot/sdk/SessionRequestBuilderTest.java index 5c8f00838..988213ce1 100644 --- a/java/src/test/java/com/github/copilot/sdk/SessionRequestBuilderTest.java +++ b/java/src/test/java/com/github/copilot/sdk/SessionRequestBuilderTest.java @@ -104,6 +104,20 @@ void testBuildCreateRequestOmitsEnableSessionTelemetryWhenNotSet() { assertNull(request.getEnableSessionTelemetry()); } + @Test + void testBuildCreateRequestForwardsIncludeCurrentDatetimeWhenFalse() { + var config = new SessionConfig().setIncludeCurrentDatetime(false); + CreateSessionRequest request = SessionRequestBuilder.buildCreateRequest(config); + assertFalse(request.getIncludeCurrentDatetime()); + } + + @Test + void testBuildCreateRequestOmitsIncludeCurrentDatetimeWhenNotSet() { + var config = new SessionConfig(); + CreateSessionRequest request = SessionRequestBuilder.buildCreateRequest(config); + assertNull(request.getIncludeCurrentDatetime()); + } + // ========================================================================= // buildResumeRequest // ========================================================================= @@ -131,6 +145,20 @@ void testBuildResumeRequestOmitsEnableSessionTelemetryWhenNotSet() { assertNull(request.getEnableSessionTelemetry()); } + @Test + void testBuildResumeRequestForwardsIncludeCurrentDatetimeWhenFalse() { + var config = new ResumeSessionConfig().setIncludeCurrentDatetime(false); + ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-1", config); + assertFalse(request.getIncludeCurrentDatetime()); + } + + @Test + void testBuildResumeRequestOmitsIncludeCurrentDatetimeWhenNotSet() { + var config = new ResumeSessionConfig(); + ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-1", config); + assertNull(request.getIncludeCurrentDatetime()); + } + @Test void testBuildResumeRequestWithTools() { var tool = ToolDefinition.create("my_tool", "A tool", Map.of("type", "object"), @@ -540,6 +568,48 @@ void testResumeRequestOmitsEnableSessionTelemetryWhenNull() throws Exception { assertFalse(json.contains("enableSessionTelemetry"), "enableSessionTelemetry should be omitted when null"); } + // ========================================================================= + // includeCurrentDatetime serialization + // ========================================================================= + + @Test + void testCreateRequestSerializesIncludeCurrentDatetimeWhenFalse() throws Exception { + var config = new SessionConfig().setIncludeCurrentDatetime(false); + CreateSessionRequest request = SessionRequestBuilder.buildCreateRequest(config); + var mapper = JsonRpcClient.getObjectMapper(); + var json = mapper.writeValueAsString(request); + assertTrue(json.contains("\"includeCurrentDatetime\":false"), + "includeCurrentDatetime should be serialized when set to false"); + } + + @Test + void testCreateRequestOmitsIncludeCurrentDatetimeWhenNull() throws Exception { + var config = new SessionConfig(); + CreateSessionRequest request = SessionRequestBuilder.buildCreateRequest(config); + var mapper = JsonRpcClient.getObjectMapper(); + var json = mapper.writeValueAsString(request); + assertFalse(json.contains("includeCurrentDatetime"), "includeCurrentDatetime should be omitted when null"); + } + + @Test + void testResumeRequestSerializesIncludeCurrentDatetimeWhenFalse() throws Exception { + var config = new ResumeSessionConfig().setIncludeCurrentDatetime(false); + ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-dt", config); + var mapper = JsonRpcClient.getObjectMapper(); + var json = mapper.writeValueAsString(request); + assertTrue(json.contains("\"includeCurrentDatetime\":false"), + "includeCurrentDatetime should be serialized when set to false"); + } + + @Test + void testResumeRequestOmitsIncludeCurrentDatetimeWhenNull() throws Exception { + var config = new ResumeSessionConfig(); + ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-dt", config); + var mapper = JsonRpcClient.getObjectMapper(); + var json = mapper.writeValueAsString(request); + assertFalse(json.contains("includeCurrentDatetime"), "includeCurrentDatetime should be omitted when null"); + } + // ========================================================================= // Mode handler request flags // ========================================================================= diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index 21563d598..2a863269a 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -858,6 +858,7 @@ export class CopilotClient { excludedTools: config.excludedTools, provider: config.provider, enableSessionTelemetry: config.enableSessionTelemetry, + includeCurrentDatetime: config.includeCurrentDatetime, modelCapabilities: config.modelCapabilities, requestPermission: !!config.onPermissionRequest, requestUserInput: !!config.onUserInputRequest, @@ -980,6 +981,7 @@ export class CopilotClient { availableTools: config.availableTools, excludedTools: config.excludedTools, enableSessionTelemetry: config.enableSessionTelemetry, + includeCurrentDatetime: config.includeCurrentDatetime, tools: config.tools?.map((tool) => ({ name: tool.name, description: tool.description, diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index 4f3de000b..06f4b20a9 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -1503,6 +1503,13 @@ export interface SessionConfigBase { */ enableSessionTelemetry?: boolean; + /** + * Include the current local datetime in model-facing user messages. + * When `false`, suppresses the injected `` tag. + * Defaults to `true` for backwards compatibility. + */ + includeCurrentDatetime?: boolean; + /** * Optional handler for permission requests from the server. * When omitted, permission requests are surfaced as events and left pending for diff --git a/python/copilot/client.py b/python/copilot/client.py index a52b8711f..769836ad7 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -1544,6 +1544,7 @@ async def create_session( github_token: str | None = None, remote_session: RemoteSessionMode | None = None, cloud: CloudSessionOptions | None = None, + include_current_datetime: bool | None = None, ) -> CopilotSession: """ Create a new conversation session with the Copilot CLI. @@ -1610,6 +1611,9 @@ async def create_session( session. Optionally associates repository metadata with the cloud session. on_event: Callback for session events. + include_current_datetime: When False, suppresses the injected + ```` tag in model-facing user messages. + Defaults to True when omitted (datetime is injected). Returns: A :class:`CopilotSession` instance for the new session. @@ -1724,6 +1728,9 @@ async def create_session( if enable_session_telemetry is not None: payload["enableSessionTelemetry"] = enable_session_telemetry + if include_current_datetime is not None: + payload["includeCurrentDatetime"] = include_current_datetime + # Add model capabilities override if provided if model_capabilities: payload["modelCapabilities"] = _capabilities_to_dict(model_capabilities) @@ -1919,6 +1926,7 @@ async def resume_session( github_token: str | None = None, remote_session: RemoteSessionMode | None = None, continue_pending_work: bool | None = None, + include_current_datetime: bool | None = None, ) -> CopilotSession: """ Resume an existing conversation session by its ID. @@ -1986,6 +1994,9 @@ async def resume_session( tool calls or permission prompts that were still pending when the session was last suspended. When False (the default), the runtime treats pending work as interrupted on resume. + include_current_datetime: When False, suppresses the injected + ```` tag in model-facing user messages. + Defaults to True when omitted (datetime is injected). Returns: A :class:`CopilotSession` instance for the resumed session. @@ -2048,6 +2059,8 @@ async def resume_session( payload["provider"] = self._convert_provider_to_wire_format(provider) if enable_session_telemetry is not None: payload["enableSessionTelemetry"] = enable_session_telemetry + if include_current_datetime is not None: + payload["includeCurrentDatetime"] = include_current_datetime if model_capabilities: payload["modelCapabilities"] = _capabilities_to_dict(model_capabilities) if streaming is not None: diff --git a/rust/src/types.rs b/rust/src/types.rs index df5767aa4..60a17037a 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -1160,6 +1160,10 @@ pub struct SessionConfig { /// only non-streaming sub-agent events and `subagent.*` lifecycle events /// are delivered. Defaults to true on the CLI. pub include_sub_agent_streaming_events: Option, + /// Include the current local datetime in model-facing user messages. + /// When false, suppresses the injected `` tag. + /// Defaults to true on the CLI when unset. + pub include_current_datetime: Option, /// Slash commands registered for this session. When the CLI has a TUI, /// each command appears as `/name` for the user to invoke and the /// associated [`CommandHandler`] is called when executed. @@ -1238,6 +1242,7 @@ impl std::fmt::Debug for SessionConfig { "include_sub_agent_streaming_events", &self.include_sub_agent_streaming_events, ) + .field("include_current_datetime", &self.include_current_datetime) .field("commands", &self.commands) .field( "session_fs_provider", @@ -1311,6 +1316,7 @@ impl Default for SessionConfig { remote_session: None, cloud: None, include_sub_agent_streaming_events: None, + include_current_datetime: None, commands: None, session_fs_provider: None, permission_handler: None, @@ -1426,6 +1432,7 @@ impl SessionConfig { remote_session: self.remote_session, cloud: self.cloud, include_sub_agent_streaming_events: self.include_sub_agent_streaming_events, + include_current_datetime: self.include_current_datetime, commands: wire_commands, }; @@ -1734,6 +1741,13 @@ impl SessionConfig { self } + /// Include the current local datetime in model-facing user messages. + /// Defaults to true on the CLI when unset. + pub fn with_include_current_datetime(mut self, include: bool) -> Self { + self.include_current_datetime = Some(include); + self + } + /// Set per-session remote behavior. pub fn with_remote_session( mut self, @@ -1822,6 +1836,9 @@ pub struct ResumeSessionConfig { pub remote_session: Option, /// Forward sub-agent streaming events to this connection on resume. pub include_sub_agent_streaming_events: Option, + /// Include the current local datetime in model-facing user messages on resume. + /// Defaults to true on the CLI when unset. + pub include_current_datetime: Option, /// Slash commands registered for this session on resume. See /// [`SessionConfig::commands`] — commands are not persisted server-side, /// so the resume payload re-supplies the registration. @@ -1900,6 +1917,7 @@ impl std::fmt::Debug for ResumeSessionConfig { "include_sub_agent_streaming_events", &self.include_sub_agent_streaming_events, ) + .field("include_current_datetime", &self.include_current_datetime) .field("commands", &self.commands) .field( "session_fs_provider", @@ -2014,6 +2032,7 @@ impl ResumeSessionConfig { github_token: self.github_token, remote_session: self.remote_session, include_sub_agent_streaming_events: self.include_sub_agent_streaming_events, + include_current_datetime: self.include_current_datetime, commands: wire_commands, suppress_resume_event: self.suppress_resume_event, continue_pending_work: self.continue_pending_work, @@ -2068,6 +2087,7 @@ impl ResumeSessionConfig { github_token: None, remote_session: None, include_sub_agent_streaming_events: None, + include_current_datetime: None, commands: None, session_fs_provider: None, suppress_resume_event: None, @@ -2342,6 +2362,13 @@ impl ResumeSessionConfig { self } + /// Include the current local datetime in model-facing user messages on resume. + /// Defaults to true on the CLI when unset. + pub fn with_include_current_datetime(mut self, include: bool) -> Self { + self.include_current_datetime = Some(include); + self + } + /// Set per-session remote behavior on resume. pub fn with_remote_session( mut self, @@ -3593,6 +3620,7 @@ mod tests { cfg.working_directory = Some(PathBuf::from("/tmp/work")); cfg.github_token = Some("ghs_secret".to_string()); cfg.include_sub_agent_streaming_events = Some(false); + cfg.include_current_datetime = Some(false); cfg.enable_session_telemetry = Some(false); cfg.remote_session = Some(crate::generated::api_types::RemoteSessionMode::Export); cfg.cloud = Some(CloudSessionOptions::with_repository( @@ -3608,6 +3636,7 @@ mod tests { assert_eq!(wire_json["workingDirectory"], "/tmp/work"); assert_eq!(wire_json["gitHubToken"], "ghs_secret"); assert_eq!(wire_json["includeSubAgentStreamingEvents"], false); + assert_eq!(wire_json["includeCurrentDatetime"], false); assert_eq!(wire_json["enableSessionTelemetry"], false); assert_eq!(wire_json["remoteSession"], "export"); assert_eq!(wire_json["cloud"]["repository"]["owner"], "github"); @@ -3620,6 +3649,7 @@ mod tests { .expect("default has no duplicate handlers"); let empty_json = serde_json::to_value(&empty_wire).unwrap(); assert!(empty_json.get("gitHubToken").is_none()); + assert!(empty_json.get("includeCurrentDatetime").is_none()); assert!(empty_json.get("enableSessionTelemetry").is_none()); assert!(empty_json.get("remoteSession").is_none()); assert!(empty_json.get("cloud").is_none()); @@ -3634,6 +3664,7 @@ mod tests { cfg.config_dir = Some(PathBuf::from("/tmp/cfg")); cfg.github_token = Some("ghs_secret".to_string()); cfg.include_sub_agent_streaming_events = Some(true); + cfg.include_current_datetime = Some(true); cfg.enable_session_telemetry = Some(false); cfg.remote_session = Some(crate::generated::api_types::RemoteSessionMode::On); @@ -3644,6 +3675,7 @@ mod tests { assert_eq!(wire_json["configDir"], "/tmp/cfg"); assert_eq!(wire_json["gitHubToken"], "ghs_secret"); assert_eq!(wire_json["includeSubAgentStreamingEvents"], true); + assert_eq!(wire_json["includeCurrentDatetime"], true); assert_eq!(wire_json["enableSessionTelemetry"], false); assert_eq!(wire_json["remoteSession"], "on"); @@ -3653,6 +3685,7 @@ mod tests { .expect("default resume has no duplicate handlers"); let empty_json = serde_json::to_value(&empty_wire).unwrap(); assert!(empty_json.get("remoteSession").is_none()); + assert!(empty_json.get("includeCurrentDatetime").is_none()); } #[test] @@ -3677,7 +3710,8 @@ mod tests { .with_working_directory(PathBuf::from("/tmp/work")) .with_github_token("ghp_test") .with_enable_session_telemetry(false) - .with_include_sub_agent_streaming_events(false); + .with_include_sub_agent_streaming_events(false) + .with_include_current_datetime(false); assert_eq!(cfg.session_id.as_ref().map(|s| s.as_str()), Some("sess-1")); assert_eq!(cfg.model.as_deref(), Some("claude-sonnet-4")); @@ -3709,6 +3743,7 @@ mod tests { assert_eq!(cfg.github_token.as_deref(), Some("ghp_test")); assert_eq!(cfg.enable_session_telemetry, Some(false)); assert_eq!(cfg.include_sub_agent_streaming_events, Some(false)); + assert_eq!(cfg.include_current_datetime, Some(false)); } #[test] @@ -3731,6 +3766,7 @@ mod tests { .with_github_token("ghp_test") .with_enable_session_telemetry(false) .with_include_sub_agent_streaming_events(true) + .with_include_current_datetime(true) .with_suppress_resume_event(true) .with_continue_pending_work(true); @@ -3762,6 +3798,7 @@ mod tests { assert_eq!(cfg.github_token.as_deref(), Some("ghp_test")); assert_eq!(cfg.enable_session_telemetry, Some(false)); assert_eq!(cfg.include_sub_agent_streaming_events, Some(true)); + assert_eq!(cfg.include_current_datetime, Some(true)); assert_eq!(cfg.suppress_resume_event, Some(true)); assert_eq!(cfg.continue_pending_work, Some(true)); } diff --git a/rust/src/wire.rs b/rust/src/wire.rs index bc6af5651..b437725e2 100644 --- a/rust/src/wire.rs +++ b/rust/src/wire.rs @@ -100,6 +100,8 @@ pub(crate) struct SessionCreateWire { #[serde(skip_serializing_if = "Option::is_none")] pub include_sub_agent_streaming_events: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub include_current_datetime: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub commands: Option>, } @@ -164,6 +166,8 @@ pub(crate) struct SessionResumeWire { #[serde(skip_serializing_if = "Option::is_none")] pub include_sub_agent_streaming_events: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub include_current_datetime: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub commands: Option>, /// Maps to wire field `disableResume`. #[serde(rename = "disableResume", skip_serializing_if = "Option::is_none")]