diff --git a/dotnet/test/E2E/RpcServerE2ETests.cs b/dotnet/test/E2E/RpcServerE2ETests.cs index 5e39c5ca5a..b13151db60 100644 --- a/dotnet/test/E2E/RpcServerE2ETests.cs +++ b/dotnet/test/E2E/RpcServerE2ETests.cs @@ -125,6 +125,15 @@ public async Task Should_Call_Rpc_Ping_With_Typed_Params_And_Result() Assert.NotEqual(default, result.Timestamp); } + [Fact] + [Trait(E2ETestTraits.Backend, E2ETestTraits.CapiOnly)] + public async Task Should_Clear_The_Managed_Settings_Cache() + { + await Client.StartAsync(); + + await Client.Rpc.ManagedSettings.ClearCacheAsync(); + } + [Fact] public async Task Should_Reject_Llm_Inference_Response_Frames_For_Missing_Request() { diff --git a/dotnet/test/Unit/SerializationTests.cs b/dotnet/test/Unit/SerializationTests.cs index 62dc7d001b..02568e7571 100644 --- a/dotnet/test/Unit/SerializationTests.cs +++ b/dotnet/test/Unit/SerializationTests.cs @@ -1156,6 +1156,35 @@ public void QueuePendingItems_MessageId_UsesCamelCaseAndIsOptional(string? messa } #pragma warning restore GHCP001 + [Fact] + public void ModelSwitchRequests_DistinguishRequiredNullFromOmittedOptionalValue() + { + var options = GetSerializerOptions(); + var assembly = typeof(CopilotClient).Assembly; + + var switchAutoTierType = assembly.GetType("GitHub.Copilot.Rpc.ModelSwitchAutoTierRequest"); + Assert.NotNull(switchAutoTierType); + var switchAutoTierRequest = CreateInternalRequest( + switchAutoTierType!, + ("SessionId", "session-id"), + ("AutoTier", null)); + using var switchAutoTierDocument = JsonDocument.Parse( + JsonSerializer.Serialize(switchAutoTierRequest, switchAutoTierType!, options)); + Assert.True(switchAutoTierDocument.RootElement.TryGetProperty("autoTier", out var requiredAutoTier)); + Assert.Equal(JsonValueKind.Null, requiredAutoTier.ValueKind); + + var switchToType = assembly.GetType("GitHub.Copilot.Rpc.ModelSwitchToRequest"); + Assert.NotNull(switchToType); + var switchToRequest = CreateInternalRequest( + switchToType!, + ("SessionId", "session-id"), + ("ModelId", "auto"), + ("AutoTier", null)); + using var switchToDocument = JsonDocument.Parse( + JsonSerializer.Serialize(switchToRequest, switchToType!, options)); + Assert.False(switchToDocument.RootElement.TryGetProperty("autoTier", out _)); + } + private static JsonSerializerOptions GetSerializerOptions() { var prop = typeof(CopilotClient) diff --git a/go/internal/e2e/rpc_server_e2e_test.go b/go/internal/e2e/rpc_server_e2e_test.go index f1aa5a19c7..fb24309c1d 100644 --- a/go/internal/e2e/rpc_server_e2e_test.go +++ b/go/internal/e2e/rpc_server_e2e_test.go @@ -17,6 +17,21 @@ import ( // Mirrors dotnet/test/RpcServerTests.cs (snapshot category "rpc_server"). // Tests server-scoped (non-session) RPCs. func TestRPCServerE2E(t *testing.T) { + t.Run("should clear the managed settings cache", func(t *testing.T) { + ctx := testharness.NewTestContext(t) + ctx.ConfigureForTest(t) + client := ctx.NewClient() + t.Cleanup(func() { client.ForceStop() }) + + if err := client.Start(t.Context()); err != nil { + t.Fatalf("Start failed: %v", err) + } + + if _, err := client.RPC.ManagedSettings.ClearCache(t.Context()); err != nil { + t.Fatalf("ManagedSettings.ClearCache failed: %v", err) + } + }) + t.Run("should call rpc ping with typed params and result", func(t *testing.T) { ctx := testharness.NewTestContext(t) ctx.ConfigureForTest(t) diff --git a/java/scripts/codegen/java.ts b/java/scripts/codegen/java.ts index 785049afa1..9bd9ed8cfc 100644 --- a/java/scripts/codegen/java.ts +++ b/java/scripts/codegen/java.ts @@ -1326,6 +1326,18 @@ function rpcMethodToClassName(rpcMethod: string): string { return rpcMethod.split(/[._-]/).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(""); } +function schemaAllowsNull(schema: JSONSchema7): boolean { + if (schema.type === "null" || (Array.isArray(schema.type) && schema.type.includes("null"))) { + return true; + } + if (schema.const === null || schema.enum?.includes(null)) { + return true; + } + return [...(schema.anyOf || []), ...(schema.oneOf || [])].some( + (variant) => typeof variant === "object" && schemaAllowsNull(variant) + ); +} + /** Generate a Java record for a JSON Schema object type. Returns the class content. */ function generateRpcClass( className: string, @@ -1340,13 +1352,20 @@ function generateRpcClass( const visModifier = visibility === "public" ? "public " : ""; const properties = Object.entries(schema.properties || {}); + const required = new Set(schema.required || []); const fields = properties.flatMap(([propName, propSchema]) => { if (typeof propSchema !== "object") return []; const prop = propSchema as JSONSchema7; // Record components are always boxed (nullable by design). const result = schemaTypeToJava(prop, false, className, propName, localNestedTypes); for (const imp of result.imports) imports.add(imp); - return [{ propName, javaName: toCamelCase(propName), javaType: result.javaType, description: prop.description }]; + return [{ + propName, + javaName: toCamelCase(propName), + javaType: result.javaType, + description: prop.description, + includeNull: required.has(propName) && schemaAllowsNull(prop), + }]; }); lines.push(`@JsonInclude(JsonInclude.Include.NON_NULL)`); @@ -1361,6 +1380,9 @@ function generateRpcClass( if (f.description) { lines.push(` /** ${f.description} */`); } + if (f.includeNull) { + lines.push(` @JsonInclude(JsonInclude.Include.ALWAYS)`); + } lines.push(` @JsonProperty("${f.propName}") ${f.javaType} ${f.javaName}${comma}`); } lines.push(`) {`); diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/CustomAgentsUpdatedAgent.java b/java/sdk/src/generated/java/com/github/copilot/generated/CustomAgentsUpdatedAgent.java index 762f0b1ac8..4bea6b921b 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/CustomAgentsUpdatedAgent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/CustomAgentsUpdatedAgent.java @@ -33,6 +33,7 @@ public record CustomAgentsUpdatedAgent( /** Source location: user, project, inherited, remote, or plugin */ @JsonProperty("source") String source, /** List of tool names available to this agent, or null when all tools are available */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("tools") List tools, /** Whether the agent can be selected by the user */ @JsonProperty("userInvocable") Boolean userInvocable, diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/BuiltinToolDescriptor.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/BuiltinToolDescriptor.java index 057498fb98..1d901ad496 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/BuiltinToolDescriptor.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/BuiltinToolDescriptor.java @@ -24,16 +24,21 @@ public record BuiltinToolDescriptor( /** Stable name used to invoke the built-in tool. */ @JsonProperty("name") String name, /** Optional human-readable title for the tool. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("title") String title, /** Model-facing description of the tool's behavior. */ @JsonProperty("description") String description, /** JSON Schema for the tool input, or null when the tool uses a custom format. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("inputSchema") BuiltinToolInputSchema inputSchema, /** Optional supplemental usage instructions for the tool. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("instructions") String instructions, /** Optional tool category discriminator. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("type") String type, /** Optional custom input format used instead of a JSON Schema. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("format") BuiltinToolFormat format, /** Policy describing which tool metadata may be recorded without obfuscation. */ @JsonProperty("safeForTelemetry") Object safeForTelemetry, diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryAgentSummary.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryAgentSummary.java index d1bd2329b2..1a912c7e02 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryAgentSummary.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryAgentSummary.java @@ -28,6 +28,7 @@ public record FactoryAgentSummary( /** Owning factory run identifier. */ @JsonProperty("runId") String runId, /** Phase identifier active when the agent was launched, or null. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("phaseId") String phaseId, /** Friendly, non-unique name intended for display */ @JsonProperty("label") String label, diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryCurrentPhase.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryCurrentPhase.java index e0a30f51a6..fc6902d83c 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryCurrentPhase.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryCurrentPhase.java @@ -24,6 +24,7 @@ public record FactoryCurrentPhase( /** Current phase identifier. */ @JsonProperty("id") String id, /** Zero-based declared phase ordinal, or null for an undeclared phase. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("ordinal") Long ordinal ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryPhaseObservation.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryPhaseObservation.java index aa96e0cb90..833ceab067 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryPhaseObservation.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryPhaseObservation.java @@ -24,6 +24,7 @@ public record FactoryPhaseObservation( /** Phase identifier. */ @JsonProperty("id") String id, /** Zero-based declared phase ordinal, or null for an undeclared phase. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("ordinal") Long ordinal, /** Human-readable phase title. */ @JsonProperty("title") String title, diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryProgressLine.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryProgressLine.java index 3a26b67d79..65ffc36521 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryProgressLine.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryProgressLine.java @@ -26,6 +26,7 @@ public record FactoryProgressLine( /** Resume attempt that emitted this record. */ @JsonProperty("attempt") Long attempt, /** Phase active when the record was emitted, or null before any phase. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("phaseId") String phaseId, /** Epoch milliseconds when the record was persisted. */ @JsonProperty("recordedAt") Long recordedAt, diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryProgressPage.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryProgressPage.java index 76278a1585..f0b1930686 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryProgressPage.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryProgressPage.java @@ -25,8 +25,10 @@ public record FactoryProgressPage( /** Progress records in sequence order. */ @JsonProperty("records") List records, /** Oldest sequence number in this page, or null when empty. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("oldestSeq") Long oldestSeq, /** Newest sequence number in this page, or null when empty. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("newestSeq") Long newestSeq, /** Whether progress records older than this page exist. */ @JsonProperty("hasMoreOlder") Boolean hasMoreOlder, diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunSummary.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunSummary.java index f482acfa16..f73aa9c2a0 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunSummary.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunSummary.java @@ -34,12 +34,15 @@ public record FactoryRunSummary( /** Epoch milliseconds when the run was created. */ @JsonProperty("createdAt") Long createdAt, /** Epoch milliseconds when execution first started, or null before start. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("startedAt") Long startedAt, /** Epoch milliseconds when the durable run was last updated. */ @JsonProperty("updatedAt") Long updatedAt, /** Epoch milliseconds when the run completed, or null while nonterminal. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("completedAt") Long completedAt, /** Current phase identity, or null before any phase is entered. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("currentPhase") FactoryCurrentPhase currentPhase, /** Number of phases declared by the factory. */ @JsonProperty("declaredPhaseCount") Long declaredPhaseCount, @@ -52,12 +55,15 @@ public record FactoryRunSummary( /** Resource ceilings declared by the factory. */ @JsonProperty("declaredLimits") FactoryDeclaredLimits declaredLimits, /** Approved effective resource ceilings, or null until approved. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("approved") FactoryDeclaredLimits approved, /** Epoch milliseconds when this live-overlay snapshot was observed. */ @JsonProperty("observedAt") Long observedAt, /** Epoch milliseconds when the current active segment started, or null while inactive. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("activeSegmentStartedAt") Long activeSegmentStartedAt, /** Terminal run outcome, or null while nonterminal. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("terminal") FactoryRunTerminal terminal ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionRule.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionRule.java index 8e7a6c769e..e37d9dbb33 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionRule.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionRule.java @@ -24,6 +24,7 @@ public record PermissionRule( /** The rule kind, such as Shell or GitHubMCP */ @JsonProperty("kind") String kind, /** Argument value matched against the request, or null when the rule kind has no argument (e.g. 'read', 'write', 'memory'). */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("argument") String argument ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunDetailResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunDetailResult.java index 01204cb832..a72ad0bc8e 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunDetailResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunDetailResult.java @@ -38,12 +38,15 @@ public record SessionFactoryGetRunDetailResult( /** Epoch milliseconds when the run was created. */ @JsonProperty("createdAt") Long createdAt, /** Epoch milliseconds when execution first started, or null before start. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("startedAt") Long startedAt, /** Epoch milliseconds when the durable run was last updated. */ @JsonProperty("updatedAt") Long updatedAt, /** Epoch milliseconds when the run completed, or null while nonterminal. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("completedAt") Long completedAt, /** Current phase identity, or null before any phase is entered. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("currentPhase") FactoryCurrentPhase currentPhase, /** Number of phases declared by the factory. */ @JsonProperty("declaredPhaseCount") Long declaredPhaseCount, @@ -56,12 +59,15 @@ public record SessionFactoryGetRunDetailResult( /** Resource ceilings declared by the factory. */ @JsonProperty("declaredLimits") FactoryDeclaredLimits declaredLimits, /** Approved effective resource ceilings, or null until approved. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("approved") FactoryDeclaredLimits approved, /** Epoch milliseconds when this live-overlay snapshot was observed. */ @JsonProperty("observedAt") Long observedAt, /** Epoch milliseconds when the current active segment started, or null while inactive. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("activeSegmentStartedAt") Long activeSegmentStartedAt, /** Terminal run outcome, or null while nonterminal. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("terminal") FactoryRunTerminal terminal, /** Lifecycle and timing observations for each factory phase. */ @JsonProperty("phases") List phases, diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunProgressResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunProgressResult.java index 2a4cb78cb2..ec6ac51496 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunProgressResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunProgressResult.java @@ -28,8 +28,10 @@ public record SessionFactoryGetRunProgressResult( /** Progress records in sequence order. */ @JsonProperty("records") List records, /** Oldest sequence number in this page, or null when empty. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("oldestSeq") Long oldestSeq, /** Newest sequence number in this page, or null when empty. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("newestSeq") Long newestSeq, /** Whether progress records older than this page exist. */ @JsonProperty("hasMoreOlder") Boolean hasMoreOlder, diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataSnapshotResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataSnapshotResult.java index 6c29e07b6e..33bf5891d9 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataSnapshotResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataSnapshotResult.java @@ -36,6 +36,7 @@ public record SessionMetadataSnapshotResult( /** True when the session was detected to be in use by another process at construction time. Local consumers may surface a confirmation prompt before fully attaching. Always false for new sessions. */ @JsonProperty("alreadyInUse") Boolean alreadyInUse, /** Absolute path to the session's workspace directory on disk, or null if the session has no associated workspace */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("workspacePath") String workspacePath, /** User-provided name supplied at session construction (via `--name`), if any. Immutable after construction. */ @JsonProperty("initialName") String initialName, @@ -52,6 +53,7 @@ public record SessionMetadataSnapshotResult( /** Currently selected model identifier, if any */ @JsonProperty("selectedModel") String selectedModel, /** Current session limits, or null when no limits are active */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("sessionLimits") SessionLimitsConfig sessionLimits, /** Public-facing workspace metadata for this session, or null if the session has no associated workspace. Excludes runtime-internal fields (GitHub IDs, summary count, internal flags). */ @JsonProperty("workspace") SessionMetadataSnapshotResultWorkspace workspace diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchAutoTierParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchAutoTierParams.java index 576df55aa1..dfa020c034 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchAutoTierParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchAutoTierParams.java @@ -27,6 +27,7 @@ public record SessionModelSwitchAutoTierParams( /** Target session identifier */ @JsonProperty("sessionId") String sessionId, /** Auto preference to activate when a future user turn using the `auto` model safely mints a replacement model and token pair. Pass null to return to provider-default Auto routing. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("autoTier") AutoTier autoTier, /** Origin to record on the effective `session.model_change` event. Defaults to `sdk` when omitted. */ @JsonProperty("source") ModelChangeSource source diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionNameGetResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionNameGetResult.java index 4743adaed2..de3ae5a494 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionNameGetResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionNameGetResult.java @@ -25,6 +25,7 @@ @JsonIgnoreProperties(ignoreUnknown = true) public record SessionNameGetResult( /** The session name (user-set or auto-generated), or null if not yet set */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("name") String name ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPlanReadResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPlanReadResult.java index 5fd82d3e14..97db4cfcf6 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPlanReadResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPlanReadResult.java @@ -27,8 +27,10 @@ public record SessionPlanReadResult( /** Whether the plan file exists in the workspace */ @JsonProperty("exists") Boolean exists, /** The content of the plan file, or null if it does not exist */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("content") String content, /** Absolute file path of the plan file, or null if workspace is not enabled */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("path") String path ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsGetCurrentMetadataResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsGetCurrentMetadataResult.java index 8f3bf99125..e6cb29f310 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsGetCurrentMetadataResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsGetCurrentMetadataResult.java @@ -26,6 +26,7 @@ @JsonIgnoreProperties(ignoreUnknown = true) public record SessionToolsGetCurrentMetadataResult( /** Current tool metadata, or null when tools have not been initialized yet */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("tools") List tools ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesEnsureResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesEnsureResult.java index c2d7e9ce37..77362d450a 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesEnsureResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesEnsureResult.java @@ -26,6 +26,7 @@ @JsonIgnoreProperties(ignoreUnknown = true) public record SessionWorkspacesEnsureResult( /** Current workspace metadata, or null if not available */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("workspace") SessionWorkspacesEnsureResultWorkspace workspace, /** Absolute filesystem path to the workspace directory. Omitted when the session has no workspace (e.g. remote sessions). */ @JsonProperty("path") String path diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesGetWorkspaceResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesGetWorkspaceResult.java index 6f4714a594..8d8ff53c98 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesGetWorkspaceResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesGetWorkspaceResult.java @@ -26,6 +26,7 @@ @JsonIgnoreProperties(ignoreUnknown = true) public record SessionWorkspacesGetWorkspaceResult( /** Current workspace metadata, or null if not available */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("workspace") SessionWorkspacesGetWorkspaceResultWorkspace workspace, /** Absolute filesystem path to the workspace directory. Omitted when the session has no workspace (e.g. remote sessions). */ @JsonProperty("path") String path diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadAutopilotObjectiveResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadAutopilotObjectiveResult.java index 7b2e157b56..6928c1c88d 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadAutopilotObjectiveResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadAutopilotObjectiveResult.java @@ -25,6 +25,7 @@ @JsonIgnoreProperties(ignoreUnknown = true) public record SessionWorkspacesReadAutopilotObjectiveResult( /** Autopilot objective file content, or null when missing. */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("content") String content ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadCheckpointResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadCheckpointResult.java index 21aa5009fe..02a413a4ab 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadCheckpointResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadCheckpointResult.java @@ -25,6 +25,7 @@ @JsonIgnoreProperties(ignoreUnknown = true) public record SessionWorkspacesReadCheckpointResult( /** Checkpoint content as a UTF-8 string, or null when the checkpoint or workspace is missing */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("content") String content ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesSaveLargePasteResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesSaveLargePasteResult.java index 08df378c90..5604588123 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesSaveLargePasteResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesSaveLargePasteResult.java @@ -25,6 +25,7 @@ @JsonIgnoreProperties(ignoreUnknown = true) public record SessionWorkspacesSaveLargePasteResult( /** Saved-paste descriptor, or null when the workspace is unavailable (e.g. CCA runtime, non-infinite sessions, remote sessions) */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("saved") SessionWorkspacesSaveLargePasteResultSaved saved ) { diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesTruncateSummariesResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesTruncateSummariesResult.java index 1dc348e67e..6d957fe52b 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesTruncateSummariesResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesTruncateSummariesResult.java @@ -26,6 +26,7 @@ @JsonIgnoreProperties(ignoreUnknown = true) public record SessionWorkspacesTruncateSummariesResult( /** Current workspace metadata, or null if not available */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("workspace") SessionWorkspacesTruncateSummariesResultWorkspace workspace, /** Absolute filesystem path to the workspace directory. Omitted when the session has no workspace (e.g. remote sessions). */ @JsonProperty("path") String path diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesUpdateMetadataResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesUpdateMetadataResult.java index 03731650a8..06a2dea59c 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesUpdateMetadataResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesUpdateMetadataResult.java @@ -26,6 +26,7 @@ @JsonIgnoreProperties(ignoreUnknown = true) public record SessionWorkspacesUpdateMetadataResult( /** Current workspace metadata, or null if not available */ + @JsonInclude(JsonInclude.Include.ALWAYS) @JsonProperty("workspace") SessionWorkspacesUpdateMetadataResultWorkspace workspace, /** Absolute filesystem path to the workspace directory. Omitted when the session has no workspace (e.g. remote sessions). */ @JsonProperty("path") String path diff --git a/java/sdk/src/test/java/com/github/copilot/RpcServerE2ETest.java b/java/sdk/src/test/java/com/github/copilot/RpcServerE2ETest.java index 6c9753025a..1da37ad1d9 100644 --- a/java/sdk/src/test/java/com/github/copilot/RpcServerE2ETest.java +++ b/java/sdk/src/test/java/com/github/copilot/RpcServerE2ETest.java @@ -95,6 +95,17 @@ void testShouldCallRpcPingWithTypedParamsAndResult() throws Exception { } } + @Test + void testShouldClearTheManagedSettingsCache() throws Exception { + ctx.configureForTest("rpc_server", "should_clear_the_managed_settings_cache"); + + try (var client = ctx.createClient()) { + client.start().get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + + client.getRpc().managedSettings.clearCache().get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + } + } + @Test void testShouldRejectLlmInferenceResponseFramesForMissingRequest() throws Exception { ctx.initializeProxy(); diff --git a/java/sdk/src/test/java/com/github/copilot/generated/rpc/GeneratedRpcRecordsCoverageTest.java b/java/sdk/src/test/java/com/github/copilot/generated/rpc/GeneratedRpcRecordsCoverageTest.java index 1702561bdc..7310e087e9 100644 --- a/java/sdk/src/test/java/com/github/copilot/generated/rpc/GeneratedRpcRecordsCoverageTest.java +++ b/java/sdk/src/test/java/com/github/copilot/generated/rpc/GeneratedRpcRecordsCoverageTest.java @@ -13,6 +13,7 @@ import org.junit.jupiter.api.Test; +import com.fasterxml.jackson.databind.ObjectMapper; import com.github.copilot.TestUtil; /** @@ -330,6 +331,7 @@ void sessionModelSwitchToParams_record() { null, null, null, null, null, null, null, null); assertEquals("sess-32", params.sessionId()); assertEquals("claude-sonnet-5", params.modelId()); + assertNull(params.autoTier()); assertEquals("high", params.reasoningEffort()); assertNull(params.reasoningSummary()); assertNull(params.verbosity()); @@ -337,6 +339,18 @@ void sessionModelSwitchToParams_record() { assertNull(params.deferIfModelChangeQueued()); } + @Test + void sessionModelSwitchParams_distinguishRequiredNullFromOmittedOptionalValue() { + var mapper = new ObjectMapper(); + var switchAutoTier = mapper.valueToTree(new SessionModelSwitchAutoTierParams("sess-32", null, null)); + assertTrue(switchAutoTier.has("autoTier")); + assertTrue(switchAutoTier.get("autoTier").isNull()); + + var switchTo = mapper.valueToTree(new SessionModelSwitchToParams("sess-32", "auto", null, null, null, null, + null, null, null, null, null, null, null, null, null, null)); + assertFalse(switchTo.has("autoTier")); + } + @Test void sessionPermissionsHandlePendingPermissionRequestParams_record() { var params = new SessionPermissionsHandlePendingPermissionRequestParams("sess-33", "req-1", "allow", null); diff --git a/nodejs/test/e2e/rpc_server.e2e.test.ts b/nodejs/test/e2e/rpc_server.e2e.test.ts index 13a63875e9..cf08f916a2 100644 --- a/nodejs/test/e2e/rpc_server.e2e.test.ts +++ b/nodejs/test/e2e/rpc_server.e2e.test.ts @@ -7,7 +7,7 @@ import * as path from "path"; import { randomUUID } from "node:crypto"; import { describe, expect, it, onTestFinished } from "vitest"; import { CopilotClient, RuntimeConnection } from "../../src/index.js"; -import { createSdkTestContext } from "./harness/sdkTestContext.js"; +import { createSdkTestContext, isInProcessTransport } from "./harness/sdkTestContext.js"; import { waitForCondition } from "./harness/sdkTestHelper.js"; describe("Server-scoped RPC", async () => { @@ -103,6 +103,11 @@ describe("Server-scoped RPC", async () => { expect(Date.parse(result.timestamp)).not.toBeNaN(); }); + it.skipIf(isInProcessTransport)("should clear the managed settings cache", async () => { + await client.start(); + await expect(client.rpc.managedSettings.clearCache()).resolves.toBeNull(); + }); + it("should reject llm inference response frames for missing request", async () => { await client.start(); diff --git a/python/e2e/test_rpc_server_e2e.py b/python/e2e/test_rpc_server_e2e.py index fdff3b8004..83dc4a01e2 100644 --- a/python/e2e/test_rpc_server_e2e.py +++ b/python/e2e/test_rpc_server_e2e.py @@ -55,7 +55,7 @@ ) from copilot.session import PermissionHandler -from .testharness import E2ETestContext, wait_for_condition +from .testharness import E2ETestContext, is_inprocess_transport, wait_for_condition pytestmark = pytest.mark.asyncio(loop_scope="module") @@ -137,6 +137,14 @@ async def test_should_call_rpc_ping_with_typed_params_and_result(self, ctx: E2ET assert result.message == "pong: typed rpc test" assert result.timestamp is not None + @pytest.mark.skipif( + is_inprocess_transport(), + reason="managedSettings.clearCache is unavailable in the in-process host", + ) + async def test_should_clear_the_managed_settings_cache(self, ctx: E2ETestContext): + await ctx.client.start() + assert await ctx.client.rpc.managed_settings.clear_cache() is None + async def test_should_reject_llm_inference_response_frames_for_missing_request( self, ctx: E2ETestContext ): diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 0a3886a82e..12e7be776c 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -2491,6 +2491,7 @@ impl Client { .client_info .as_ref() .and_then(ClientInfo::to_wire), + supported_task_kinds: None, }; let value = self .call( diff --git a/rust/tests/e2e/rpc_mcp_config.rs b/rust/tests/e2e/rpc_mcp_config.rs index 29f5f42d39..b3c91fc180 100644 --- a/rust/tests/e2e/rpc_mcp_config.rs +++ b/rust/tests/e2e/rpc_mcp_config.rs @@ -17,8 +17,8 @@ async fn should_call_server_mcp_config_rpcs() { let config = client.rpc().mcp().config(); let _ = config .remove(McpConfigRemoveRequest { - name: server_name.to_string(), auth_client_id_metadata_url: None, + name: server_name.to_string(), }) .await; @@ -74,8 +74,8 @@ async fn should_call_server_mcp_config_rpcs() { .expect("enable"); config .remove(McpConfigRemoveRequest { - name: server_name.to_string(), auth_client_id_metadata_url: None, + name: server_name.to_string(), }) .await .expect("remove"); @@ -103,8 +103,8 @@ async fn should_round_trip_http_mcp_oauth_config_rpc() { let config = client.rpc().mcp().config(); let _ = config .remove(McpConfigRemoveRequest { - name: server_name.to_string(), auth_client_id_metadata_url: None, + name: server_name.to_string(), }) .await; @@ -199,8 +199,8 @@ async fn should_round_trip_http_mcp_oauth_config_rpc() { config .remove(McpConfigRemoveRequest { - name: server_name.to_string(), auth_client_id_metadata_url: None, + name: server_name.to_string(), }) .await .expect("remove"); diff --git a/rust/tests/e2e/rpc_server.rs b/rust/tests/e2e/rpc_server.rs index 2e80ae1d70..628d5597c5 100644 --- a/rust/tests/e2e/rpc_server.rs +++ b/rust/tests/e2e/rpc_server.rs @@ -46,6 +46,32 @@ async fn should_call_rpc_ping_with_typed_params_and_result() { .await; } +#[tokio::test] +async fn should_clear_the_managed_settings_cache() { + if super::support::skip_inprocess("managedSettings.clearCache is unavailable in-process") { + return; + } + with_e2e_context( + "rpc_server", + "should_clear_the_managed_settings_cache", + |ctx| { + Box::pin(async move { + let client = ctx.start_client().await; + + client + .rpc() + .managed_settings() + .clear_cache() + .await + .expect("clear managed settings cache"); + + client.stop().await.expect("stop client"); + }) + }, + ) + .await; +} + #[tokio::test] async fn should_call_rpc_models_list_with_typed_result() { // TODO(cli-1.0.81-2): CLI 1.0.81-2 stopped honoring client-level GitHub tokens over the diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts index 00afec6000..e170727528 100644 --- a/scripts/codegen/csharp.ts +++ b/scripts/codegen/csharp.ts @@ -1828,6 +1828,9 @@ function emitRpcClass( if (isMillisecondsDurationProperty(propName, prop)) lines.push(` [JsonConverter(typeof(MillisecondsTimeSpanConverter))]`); const propVisibility = pushCSharpInternalAttribute(lines, prop); lines.push(` [JsonPropertyName("${propName}")]`); + if (isReq && csharpType.endsWith("?")) { + lines.push(` [JsonIgnore(Condition = JsonIgnoreCondition.Never)]`); + } let defaultVal = ""; let propAccessors = "{ get; set; }"; diff --git a/test/snapshots/rpc_server/should_clear_the_managed_settings_cache.yaml b/test/snapshots/rpc_server/should_clear_the_managed_settings_cache.yaml new file mode 100644 index 0000000000..0c6b353c19 --- /dev/null +++ b/test/snapshots/rpc_server/should_clear_the_managed_settings_cache.yaml @@ -0,0 +1,3 @@ +models: + - claude-sonnet-5 +conversations: []