diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs
index f6ca0382f..6fc593c12 100644
--- a/dotnet/src/Generated/Rpc.cs
+++ b/dotnet/src/Generated/Rpc.cs
@@ -5,12 +5,20 @@
// AUTO-GENERATED FILE - DO NOT EDIT
// Generated from: api.schema.json
+using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Serialization;
using StreamJsonRpc;
namespace GitHub.Copilot.SDK.Rpc;
+/// Diagnostic IDs for the Copilot SDK.
+internal static class Diagnostics
+{
+ /// Indicates an experimental API that may change or be removed.
+ internal const string Experimental = "GHCP001";
+}
+
/// RPC data type for Ping operations.
public class PingResult
{
@@ -427,6 +435,7 @@ internal class SessionWorkspaceCreateFileRequest
}
/// RPC data type for SessionFleetStart operations.
+[Experimental(Diagnostics.Experimental)]
public class SessionFleetStartResult
{
/// Whether fleet mode was successfully activated.
@@ -435,6 +444,7 @@ public class SessionFleetStartResult
}
/// RPC data type for SessionFleetStart operations.
+[Experimental(Diagnostics.Experimental)]
internal class SessionFleetStartRequest
{
/// Target session identifier.
@@ -463,6 +473,7 @@ public class Agent
}
/// RPC data type for SessionAgentList operations.
+[Experimental(Diagnostics.Experimental)]
public class SessionAgentListResult
{
/// Available custom agents.
@@ -471,6 +482,7 @@ public class SessionAgentListResult
}
/// RPC data type for SessionAgentList operations.
+[Experimental(Diagnostics.Experimental)]
internal class SessionAgentListRequest
{
/// Target session identifier.
@@ -495,6 +507,7 @@ public class SessionAgentGetCurrentResultAgent
}
/// RPC data type for SessionAgentGetCurrent operations.
+[Experimental(Diagnostics.Experimental)]
public class SessionAgentGetCurrentResult
{
/// Currently selected custom agent, or null if using the default agent.
@@ -503,6 +516,7 @@ public class SessionAgentGetCurrentResult
}
/// RPC data type for SessionAgentGetCurrent operations.
+[Experimental(Diagnostics.Experimental)]
internal class SessionAgentGetCurrentRequest
{
/// Target session identifier.
@@ -527,6 +541,7 @@ public class SessionAgentSelectResultAgent
}
/// RPC data type for SessionAgentSelect operations.
+[Experimental(Diagnostics.Experimental)]
public class SessionAgentSelectResult
{
/// The newly selected custom agent.
@@ -535,6 +550,7 @@ public class SessionAgentSelectResult
}
/// RPC data type for SessionAgentSelect operations.
+[Experimental(Diagnostics.Experimental)]
internal class SessionAgentSelectRequest
{
/// Target session identifier.
@@ -547,11 +563,13 @@ internal class SessionAgentSelectRequest
}
/// RPC data type for SessionAgentDeselect operations.
+[Experimental(Diagnostics.Experimental)]
public class SessionAgentDeselectResult
{
}
/// RPC data type for SessionAgentDeselect operations.
+[Experimental(Diagnostics.Experimental)]
internal class SessionAgentDeselectRequest
{
/// Target session identifier.
@@ -560,6 +578,7 @@ internal class SessionAgentDeselectRequest
}
/// RPC data type for SessionCompactionCompact operations.
+[Experimental(Diagnostics.Experimental)]
public class SessionCompactionCompactResult
{
/// Whether compaction completed successfully.
@@ -576,6 +595,7 @@ public class SessionCompactionCompactResult
}
/// RPC data type for SessionCompactionCompact operations.
+[Experimental(Diagnostics.Experimental)]
internal class SessionCompactionCompactRequest
{
/// Target session identifier.
@@ -1000,6 +1020,7 @@ public async Task CreateFileAsync(string path,
}
/// Provides session-scoped Fleet APIs.
+[Experimental(Diagnostics.Experimental)]
public class FleetApi
{
private readonly JsonRpc _rpc;
@@ -1020,6 +1041,7 @@ public async Task StartAsync(string? prompt = null, Can
}
/// Provides session-scoped Agent APIs.
+[Experimental(Diagnostics.Experimental)]
public class AgentApi
{
private readonly JsonRpc _rpc;
@@ -1061,6 +1083,7 @@ public async Task DeselectAsync(CancellationToken ca
}
/// Provides session-scoped Compaction APIs.
+[Experimental(Diagnostics.Experimental)]
public class CompactionApi
{
private readonly JsonRpc _rpc;
diff --git a/dotnet/src/GitHub.Copilot.SDK.csproj b/dotnet/src/GitHub.Copilot.SDK.csproj
index 5d2502c87..38eb0cf3a 100644
--- a/dotnet/src/GitHub.Copilot.SDK.csproj
+++ b/dotnet/src/GitHub.Copilot.SDK.csproj
@@ -20,6 +20,10 @@
true
+
+ $(NoWarn);GHCP001
+
+
true
diff --git a/dotnet/test/GitHub.Copilot.SDK.Test.csproj b/dotnet/test/GitHub.Copilot.SDK.Test.csproj
index fbc9f17c3..8e0dbf6b7 100644
--- a/dotnet/test/GitHub.Copilot.SDK.Test.csproj
+++ b/dotnet/test/GitHub.Copilot.SDK.Test.csproj
@@ -2,6 +2,7 @@
false
+ $(NoWarn);GHCP001
diff --git a/go/rpc/generated_rpc.go b/go/rpc/generated_rpc.go
index ffe87455e..401f38305 100644
--- a/go/rpc/generated_rpc.go
+++ b/go/rpc/generated_rpc.go
@@ -208,16 +208,19 @@ type SessionWorkspaceCreateFileParams struct {
Path string `json:"path"`
}
+// Experimental: SessionFleetStartResult is part of an experimental API and may change or be removed.
type SessionFleetStartResult struct {
// Whether fleet mode was successfully activated
Started bool `json:"started"`
}
+// Experimental: SessionFleetStartParams is part of an experimental API and may change or be removed.
type SessionFleetStartParams struct {
// Optional user prompt to combine with fleet instructions
Prompt *string `json:"prompt,omitempty"`
}
+// Experimental: SessionAgentListResult is part of an experimental API and may change or be removed.
type SessionAgentListResult struct {
// Available custom agents
Agents []AgentElement `json:"agents"`
@@ -232,6 +235,7 @@ type AgentElement struct {
Name string `json:"name"`
}
+// Experimental: SessionAgentGetCurrentResult is part of an experimental API and may change or be removed.
type SessionAgentGetCurrentResult struct {
// Currently selected custom agent, or null if using the default agent
Agent *SessionAgentGetCurrentResultAgent `json:"agent"`
@@ -246,6 +250,7 @@ type SessionAgentGetCurrentResultAgent struct {
Name string `json:"name"`
}
+// Experimental: SessionAgentSelectResult is part of an experimental API and may change or be removed.
type SessionAgentSelectResult struct {
// The newly selected custom agent
Agent SessionAgentSelectResultAgent `json:"agent"`
@@ -261,14 +266,17 @@ type SessionAgentSelectResultAgent struct {
Name string `json:"name"`
}
+// Experimental: SessionAgentSelectParams is part of an experimental API and may change or be removed.
type SessionAgentSelectParams struct {
// Name of the custom agent to select
Name string `json:"name"`
}
+// Experimental: SessionAgentDeselectResult is part of an experimental API and may change or be removed.
type SessionAgentDeselectResult struct {
}
+// Experimental: SessionCompactionCompactResult is part of an experimental API and may change or be removed.
type SessionCompactionCompactResult struct {
// Number of messages removed during compaction
MessagesRemoved float64 `json:"messagesRemoved"`
@@ -402,7 +410,9 @@ type ResultUnion struct {
String *string
}
-type ServerModelsRpcApi struct{ client *jsonrpc2.Client }
+type ServerModelsRpcApi struct {
+ client *jsonrpc2.Client
+}
func (a *ServerModelsRpcApi) List(ctx context.Context) (*ModelsListResult, error) {
raw, err := a.client.Request("models.list", map[string]interface{}{})
@@ -416,7 +426,9 @@ func (a *ServerModelsRpcApi) List(ctx context.Context) (*ModelsListResult, error
return &result, nil
}
-type ServerToolsRpcApi struct{ client *jsonrpc2.Client }
+type ServerToolsRpcApi struct {
+ client *jsonrpc2.Client
+}
func (a *ServerToolsRpcApi) List(ctx context.Context, params *ToolsListParams) (*ToolsListResult, error) {
raw, err := a.client.Request("tools.list", params)
@@ -430,7 +442,9 @@ func (a *ServerToolsRpcApi) List(ctx context.Context, params *ToolsListParams) (
return &result, nil
}
-type ServerAccountRpcApi struct{ client *jsonrpc2.Client }
+type ServerAccountRpcApi struct {
+ client *jsonrpc2.Client
+}
func (a *ServerAccountRpcApi) GetQuota(ctx context.Context) (*AccountGetQuotaResult, error) {
raw, err := a.client.Request("account.getQuota", map[string]interface{}{})
@@ -641,6 +655,7 @@ func (a *WorkspaceRpcApi) CreateFile(ctx context.Context, params *SessionWorkspa
return &result, nil
}
+// Experimental: FleetRpcApi contains experimental APIs that may change or be removed.
type FleetRpcApi struct {
client *jsonrpc2.Client
sessionID string
@@ -664,6 +679,7 @@ func (a *FleetRpcApi) Start(ctx context.Context, params *SessionFleetStartParams
return &result, nil
}
+// Experimental: AgentRpcApi contains experimental APIs that may change or be removed.
type AgentRpcApi struct {
client *jsonrpc2.Client
sessionID string
@@ -724,6 +740,7 @@ func (a *AgentRpcApi) Deselect(ctx context.Context) (*SessionAgentDeselectResult
return &result, nil
}
+// Experimental: CompactionRpcApi contains experimental APIs that may change or be removed.
type CompactionRpcApi struct {
client *jsonrpc2.Client
sessionID string
diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts
index e5ba9ad4c..16907fdba 100644
--- a/nodejs/src/generated/rpc.ts
+++ b/nodejs/src/generated/rpc.ts
@@ -340,6 +340,7 @@ export interface SessionWorkspaceCreateFileParams {
content: string;
}
+/** @experimental */
export interface SessionFleetStartResult {
/**
* Whether fleet mode was successfully activated
@@ -347,6 +348,7 @@ export interface SessionFleetStartResult {
started: boolean;
}
+/** @experimental */
export interface SessionFleetStartParams {
/**
* Target session identifier
@@ -358,6 +360,7 @@ export interface SessionFleetStartParams {
prompt?: string;
}
+/** @experimental */
export interface SessionAgentListResult {
/**
* Available custom agents
@@ -378,6 +381,7 @@ export interface SessionAgentListResult {
}[];
}
+/** @experimental */
export interface SessionAgentListParams {
/**
* Target session identifier
@@ -385,6 +389,7 @@ export interface SessionAgentListParams {
sessionId: string;
}
+/** @experimental */
export interface SessionAgentGetCurrentResult {
/**
* Currently selected custom agent, or null if using the default agent
@@ -405,6 +410,7 @@ export interface SessionAgentGetCurrentResult {
} | null;
}
+/** @experimental */
export interface SessionAgentGetCurrentParams {
/**
* Target session identifier
@@ -412,6 +418,7 @@ export interface SessionAgentGetCurrentParams {
sessionId: string;
}
+/** @experimental */
export interface SessionAgentSelectResult {
/**
* The newly selected custom agent
@@ -432,6 +439,7 @@ export interface SessionAgentSelectResult {
};
}
+/** @experimental */
export interface SessionAgentSelectParams {
/**
* Target session identifier
@@ -443,8 +451,10 @@ export interface SessionAgentSelectParams {
name: string;
}
+/** @experimental */
export interface SessionAgentDeselectResult {}
+/** @experimental */
export interface SessionAgentDeselectParams {
/**
* Target session identifier
@@ -452,6 +462,7 @@ export interface SessionAgentDeselectParams {
sessionId: string;
}
+/** @experimental */
export interface SessionCompactionCompactResult {
/**
* Whether compaction completed successfully
@@ -467,6 +478,7 @@ export interface SessionCompactionCompactResult {
messagesRemoved: number;
}
+/** @experimental */
export interface SessionCompactionCompactParams {
/**
* Target session identifier
@@ -660,10 +672,12 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin
createFile: async (params: Omit): Promise =>
connection.sendRequest("session.workspace.createFile", { sessionId, ...params }),
},
+ /** @experimental */
fleet: {
start: async (params: Omit): Promise =>
connection.sendRequest("session.fleet.start", { sessionId, ...params }),
},
+ /** @experimental */
agent: {
list: async (): Promise =>
connection.sendRequest("session.agent.list", { sessionId }),
@@ -674,6 +688,7 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin
deselect: async (): Promise =>
connection.sendRequest("session.agent.deselect", { sessionId }),
},
+ /** @experimental */
compaction: {
compact: async (): Promise =>
connection.sendRequest("session.compaction.compact", { sessionId }),
diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py
index 29b7463df..564ccf64e 100644
--- a/python/copilot/generated/rpc.py
+++ b/python/copilot/generated/rpc.py
@@ -724,6 +724,7 @@ def to_dict(self) -> dict:
return result
+# Experimental: this type is part of an experimental API and may change or be removed.
@dataclass
class SessionFleetStartResult:
started: bool
@@ -741,6 +742,7 @@ def to_dict(self) -> dict:
return result
+# Experimental: this type is part of an experimental API and may change or be removed.
@dataclass
class SessionFleetStartParams:
prompt: str | None = None
@@ -786,6 +788,7 @@ def to_dict(self) -> dict:
return result
+# Experimental: this type is part of an experimental API and may change or be removed.
@dataclass
class SessionAgentListResult:
agents: list[AgentElement]
@@ -830,6 +833,7 @@ def to_dict(self) -> dict:
return result
+# Experimental: this type is part of an experimental API and may change or be removed.
@dataclass
class SessionAgentGetCurrentResult:
agent: SessionAgentGetCurrentResultAgent | None = None
@@ -876,6 +880,7 @@ def to_dict(self) -> dict:
return result
+# Experimental: this type is part of an experimental API and may change or be removed.
@dataclass
class SessionAgentSelectResult:
agent: SessionAgentSelectResultAgent
@@ -893,6 +898,7 @@ def to_dict(self) -> dict:
return result
+# Experimental: this type is part of an experimental API and may change or be removed.
@dataclass
class SessionAgentSelectParams:
name: str
@@ -910,6 +916,7 @@ def to_dict(self) -> dict:
return result
+# Experimental: this type is part of an experimental API and may change or be removed.
@dataclass
class SessionAgentDeselectResult:
@staticmethod
@@ -922,6 +929,7 @@ def to_dict(self) -> dict:
return result
+# Experimental: this type is part of an experimental API and may change or be removed.
@dataclass
class SessionCompactionCompactResult:
messages_removed: float
@@ -1666,6 +1674,7 @@ async def create_file(self, params: SessionWorkspaceCreateFileParams, *, timeout
return SessionWorkspaceCreateFileResult.from_dict(await self._client.request("session.workspace.createFile", params_dict, **_timeout_kwargs(timeout)))
+# Experimental: this API group is experimental and may change or be removed.
class FleetApi:
def __init__(self, client: "JsonRpcClient", session_id: str):
self._client = client
@@ -1677,6 +1686,7 @@ async def start(self, params: SessionFleetStartParams, *, timeout: float | None
return SessionFleetStartResult.from_dict(await self._client.request("session.fleet.start", params_dict, **_timeout_kwargs(timeout)))
+# Experimental: this API group is experimental and may change or be removed.
class AgentApi:
def __init__(self, client: "JsonRpcClient", session_id: str):
self._client = client
@@ -1697,6 +1707,7 @@ async def deselect(self, *, timeout: float | None = None) -> SessionAgentDeselec
return SessionAgentDeselectResult.from_dict(await self._client.request("session.agent.deselect", {"sessionId": self._session_id}, **_timeout_kwargs(timeout)))
+# Experimental: this API group is experimental and may change or be removed.
class CompactionApi:
def __init__(self, client: "JsonRpcClient", session_id: str):
self._client = client
diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts
index 3aeb0eef3..57e8fcbcb 100644
--- a/scripts/codegen/csharp.ts
+++ b/scripts/codegen/csharp.ts
@@ -16,6 +16,7 @@ import {
getApiSchemaPath,
writeGeneratedFile,
isRpcMethod,
+ isNodeFullyExperimental,
EXCLUDED_EVENT_TYPES,
REPO_ROOT,
type ApiSchema,
@@ -594,6 +595,7 @@ export async function generateSessionEvents(schemaPath?: string): Promise
// ══════════════════════════════════════════════════════════════════════════════
let emittedRpcClasses = new Set();
+let experimentalRpcTypes = new Set();
let rpcKnownTypes = new Map();
let rpcEnumOutput: string[] = [];
@@ -651,6 +653,9 @@ function emitRpcClass(className: string, schema: JSONSchema7, visibility: "publi
const requiredSet = new Set(schema.required || []);
const lines: string[] = [];
lines.push(...xmlDocComment(schema.description || `RPC data type for ${className.replace(/Request$/, "").replace(/Result$/, "")} operations.`, ""));
+ if (experimentalRpcTypes.has(className)) {
+ lines.push(`[Experimental(Diagnostics.Experimental)]`);
+ }
lines.push(`${visibility} class ${className}`, `{`);
const props = Object.entries(schema.properties || {});
@@ -712,7 +717,7 @@ function emitServerRpcClasses(node: Record, classes: string[]):
// Top-level methods (like ping)
for (const [key, value] of topLevelMethods) {
if (!isRpcMethod(value)) continue;
- emitServerInstanceMethod(key, value, srLines, classes, " ");
+ emitServerInstanceMethod(key, value, srLines, classes, " ", false);
}
// Group properties
@@ -737,6 +742,10 @@ function emitServerApiClass(className: string, node: Record, cl
const lines: string[] = [];
const displayName = className.replace(/^Server/, "").replace(/Api$/, "");
lines.push(`/// Provides server-scoped ${displayName} APIs.`);
+ const groupExperimental = isNodeFullyExperimental(node);
+ if (groupExperimental) {
+ lines.push(`[Experimental(Diagnostics.Experimental)]`);
+ }
lines.push(`public class ${className}`);
lines.push(`{`);
lines.push(` private readonly JsonRpc _rpc;`);
@@ -748,7 +757,7 @@ function emitServerApiClass(className: string, node: Record, cl
for (const [key, value] of Object.entries(node)) {
if (!isRpcMethod(value)) continue;
- emitServerInstanceMethod(key, value, lines, classes, " ");
+ emitServerInstanceMethod(key, value, lines, classes, " ", groupExperimental);
}
lines.push(`}`);
@@ -757,13 +766,17 @@ function emitServerApiClass(className: string, node: Record, cl
function emitServerInstanceMethod(
name: string,
- method: { rpcMethod: string; params: JSONSchema7 | null; result: JSONSchema7 },
+ method: RpcMethod,
lines: string[],
classes: string[],
- indent: string
+ indent: string,
+ groupExperimental: boolean
): void {
const methodName = toPascalCase(name);
const resultClassName = `${typeToClassName(method.rpcMethod)}Result`;
+ if (method.stability === "experimental") {
+ experimentalRpcTypes.add(resultClassName);
+ }
const resultClass = emitRpcClass(resultClassName, method.result, "public", classes);
if (resultClass) classes.push(resultClass);
@@ -773,12 +786,18 @@ function emitServerInstanceMethod(
let requestClassName: string | null = null;
if (paramEntries.length > 0) {
requestClassName = `${typeToClassName(method.rpcMethod)}Request`;
+ if (method.stability === "experimental") {
+ experimentalRpcTypes.add(requestClassName);
+ }
const reqClass = emitRpcClass(requestClassName, method.params!, "internal", classes);
if (reqClass) classes.push(reqClass);
}
lines.push("");
lines.push(`${indent}/// Calls "${method.rpcMethod}".`);
+ if (method.stability === "experimental" && !groupExperimental) {
+ lines.push(`${indent}[Experimental(Diagnostics.Experimental)]`);
+ }
const sigParams: string[] = [];
const bodyAssignments: string[] = [];
@@ -817,7 +836,7 @@ function emitSessionRpcClasses(node: Record, classes: string[])
// Emit top-level session RPC methods directly on the SessionRpc class
const topLevelLines: string[] = [];
for (const [key, value] of topLevelMethods) {
- emitSessionMethod(key, value as RpcMethod, topLevelLines, classes, " ");
+ emitSessionMethod(key, value as RpcMethod, topLevelLines, classes, " ", false);
}
srLines.push(...topLevelLines);
@@ -830,9 +849,12 @@ function emitSessionRpcClasses(node: Record, classes: string[])
return result;
}
-function emitSessionMethod(key: string, method: RpcMethod, lines: string[], classes: string[], indent: string): void {
+function emitSessionMethod(key: string, method: RpcMethod, lines: string[], classes: string[], indent: string, groupExperimental: boolean): void {
const methodName = toPascalCase(key);
const resultClassName = `${typeToClassName(method.rpcMethod)}Result`;
+ if (method.stability === "experimental") {
+ experimentalRpcTypes.add(resultClassName);
+ }
const resultClass = emitRpcClass(resultClassName, method.result, "public", classes);
if (resultClass) classes.push(resultClass);
@@ -847,12 +869,18 @@ function emitSessionMethod(key: string, method: RpcMethod, lines: string[], clas
});
const requestClassName = `${typeToClassName(method.rpcMethod)}Request`;
+ if (method.stability === "experimental") {
+ experimentalRpcTypes.add(requestClassName);
+ }
if (method.params) {
const reqClass = emitRpcClass(requestClassName, method.params, "internal", classes);
if (reqClass) classes.push(reqClass);
}
lines.push("", `${indent}/// Calls "${method.rpcMethod}".`);
+ if (method.stability === "experimental" && !groupExperimental) {
+ lines.push(`${indent}[Experimental(Diagnostics.Experimental)]`);
+ }
const sigParams: string[] = [];
const bodyAssignments = [`SessionId = _sessionId`];
@@ -872,12 +900,14 @@ function emitSessionMethod(key: string, method: RpcMethod, lines: string[], clas
function emitSessionApiClass(className: string, node: Record, classes: string[]): string {
const displayName = className.replace(/Api$/, "");
- const lines = [`/// Provides session-scoped ${displayName} APIs.`, `public class ${className}`, `{`, ` private readonly JsonRpc _rpc;`, ` private readonly string _sessionId;`, ""];
+ const groupExperimental = isNodeFullyExperimental(node);
+ const experimentalAttr = groupExperimental ? `[Experimental(Diagnostics.Experimental)]\n` : "";
+ const lines = [`/// Provides session-scoped ${displayName} APIs.`, `${experimentalAttr}public class ${className}`, `{`, ` private readonly JsonRpc _rpc;`, ` private readonly string _sessionId;`, ""];
lines.push(` internal ${className}(JsonRpc rpc, string sessionId)`, ` {`, ` _rpc = rpc;`, ` _sessionId = sessionId;`, ` }`);
for (const [key, value] of Object.entries(node)) {
if (!isRpcMethod(value)) continue;
- emitSessionMethod(key, value, lines, classes, " ");
+ emitSessionMethod(key, value, lines, classes, " ", groupExperimental);
}
lines.push(`}`);
return lines.join("\n");
@@ -885,6 +915,7 @@ function emitSessionApiClass(className: string, node: Record, c
function generateRpcCode(schema: ApiSchema): string {
emittedRpcClasses.clear();
+ experimentalRpcTypes.clear();
rpcKnownTypes.clear();
rpcEnumOutput = [];
generatedEnums.clear(); // Clear shared enum deduplication map
@@ -902,11 +933,19 @@ function generateRpcCode(schema: ApiSchema): string {
// AUTO-GENERATED FILE - DO NOT EDIT
// Generated from: api.schema.json
+using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Serialization;
using StreamJsonRpc;
namespace GitHub.Copilot.SDK.Rpc;
+
+/// Diagnostic IDs for the Copilot SDK.
+internal static class Diagnostics
+{
+ /// Indicates an experimental API that may change or be removed.
+ internal const string Experimental = "GHCP001";
+}
`);
for (const cls of classes) if (cls) lines.push(cls, "");
diff --git a/scripts/codegen/go.ts b/scripts/codegen/go.ts
index 1ebc50797..c467761d0 100644
--- a/scripts/codegen/go.ts
+++ b/scripts/codegen/go.ts
@@ -17,6 +17,7 @@ import {
postProcessSchema,
writeGeneratedFile,
isRpcMethod,
+ isNodeFullyExperimental,
type ApiSchema,
type RpcMethod,
} from "./utils.js";
@@ -161,16 +162,35 @@ async function generateRpc(schemaPath?: string): Promise {
lines.push(`package rpc`);
lines.push(``);
lines.push(`import (`);
- lines.push(` "context"`);
- lines.push(` "encoding/json"`);
+ lines.push(`\t"context"`);
+ lines.push(`\t"encoding/json"`);
lines.push(``);
- lines.push(` "github.com/github/copilot-sdk/go/internal/jsonrpc2"`);
+ lines.push(`\t"github.com/github/copilot-sdk/go/internal/jsonrpc2"`);
lines.push(`)`);
lines.push(``);
- // Add quicktype-generated types (skip package line)
- const qtLines = qtResult.lines.filter((l) => !l.startsWith("package "));
- lines.push(...qtLines);
+ // Add quicktype-generated types (skip package line), annotating experimental types
+ const experimentalTypeNames = new Set();
+ for (const method of allMethods) {
+ if (method.stability !== "experimental") continue;
+ experimentalTypeNames.add(toPascalCase(method.rpcMethod) + "Result");
+ const baseName = toPascalCase(method.rpcMethod);
+ if (combinedSchema.definitions![baseName + "Params"]) {
+ experimentalTypeNames.add(baseName + "Params");
+ }
+ }
+ let qtCode = qtResult.lines.filter((l) => !l.startsWith("package ")).join("\n");
+ // Strip trailing whitespace from quicktype output (gofmt requirement)
+ qtCode = qtCode.replace(/[ \t]+$/gm, "");
+ for (const typeName of experimentalTypeNames) {
+ qtCode = qtCode.replace(
+ new RegExp(`^(type ${typeName} struct)`, "m"),
+ `// Experimental: ${typeName} is part of an experimental API and may change or be removed.\n$1`
+ );
+ }
+ // Remove trailing blank lines from quicktype output before appending
+ qtCode = qtCode.replace(/\n+$/, "");
+ lines.push(qtCode);
lines.push(``);
// Emit ServerRpc
@@ -200,23 +220,39 @@ function emitRpcWrapper(lines: string[], node: Record, isSessio
for (const [groupName, groupNode] of groups) {
const prefix = isSession ? "" : "Server";
const apiName = prefix + toPascalCase(groupName) + apiSuffix;
- const fields = isSession ? "client *jsonrpc2.Client; sessionID string" : "client *jsonrpc2.Client";
- lines.push(`type ${apiName} struct { ${fields} }`);
+ const groupExperimental = isNodeFullyExperimental(groupNode as Record);
+ if (groupExperimental) {
+ lines.push(`// Experimental: ${apiName} contains experimental APIs that may change or be removed.`);
+ }
+ lines.push(`type ${apiName} struct {`);
+ if (isSession) {
+ lines.push(`\tclient *jsonrpc2.Client`);
+ lines.push(`\tsessionID string`);
+ } else {
+ lines.push(`\tclient *jsonrpc2.Client`);
+ }
+ lines.push(`}`);
lines.push(``);
for (const [key, value] of Object.entries(groupNode as Record)) {
if (!isRpcMethod(value)) continue;
- emitMethod(lines, apiName, key, value, isSession);
+ emitMethod(lines, apiName, key, value, isSession, groupExperimental);
}
}
+ // Compute field name lengths for gofmt-compatible column alignment
+ const groupPascalNames = groups.map(([g]) => toPascalCase(g));
+ const allFieldNames = isSession ? ["client", "sessionID", ...groupPascalNames] : ["client", ...groupPascalNames];
+ const maxFieldLen = Math.max(...allFieldNames.map((n) => n.length));
+ const pad = (name: string) => name.padEnd(maxFieldLen);
+
// Emit wrapper struct
lines.push(`// ${wrapperName} provides typed ${isSession ? "session" : "server"}-scoped RPC methods.`);
lines.push(`type ${wrapperName} struct {`);
- lines.push(` client *jsonrpc2.Client`);
- if (isSession) lines.push(` sessionID string`);
+ lines.push(`\t${pad("client")} *jsonrpc2.Client`);
+ if (isSession) lines.push(`\t${pad("sessionID")} string`);
for (const [groupName] of groups) {
const prefix = isSession ? "" : "Server";
- lines.push(` ${toPascalCase(groupName)} *${prefix}${toPascalCase(groupName)}${apiSuffix}`);
+ lines.push(`\t${pad(toPascalCase(groupName))} *${prefix}${toPascalCase(groupName)}${apiSuffix}`);
}
lines.push(`}`);
lines.push(``);
@@ -224,27 +260,31 @@ function emitRpcWrapper(lines: string[], node: Record, isSessio
// Top-level methods (server only)
for (const [key, value] of topLevelMethods) {
if (!isRpcMethod(value)) continue;
- emitMethod(lines, wrapperName, key, value, isSession);
+ emitMethod(lines, wrapperName, key, value, isSession, false);
}
+ // Compute key alignment for constructor composite literal (gofmt aligns key: value)
+ const maxKeyLen = Math.max(...groupPascalNames.map((n) => n.length + 1)); // +1 for colon
+ const padKey = (name: string) => (name + ":").padEnd(maxKeyLen + 1); // +1 for min trailing space
+
// Constructor
const ctorParams = isSession ? "client *jsonrpc2.Client, sessionID string" : "client *jsonrpc2.Client";
const ctorFields = isSession ? "client: client, sessionID: sessionID," : "client: client,";
lines.push(`func New${wrapperName}(${ctorParams}) *${wrapperName} {`);
- lines.push(` return &${wrapperName}{${ctorFields}`);
+ lines.push(`\treturn &${wrapperName}{${ctorFields}`);
for (const [groupName] of groups) {
const prefix = isSession ? "" : "Server";
const apiInit = isSession
? `&${toPascalCase(groupName)}${apiSuffix}{client: client, sessionID: sessionID}`
: `&${prefix}${toPascalCase(groupName)}${apiSuffix}{client: client}`;
- lines.push(` ${toPascalCase(groupName)}: ${apiInit},`);
+ lines.push(`\t\t${padKey(toPascalCase(groupName))}${apiInit},`);
}
- lines.push(` }`);
+ lines.push(`\t}`);
lines.push(`}`);
lines.push(``);
}
-function emitMethod(lines: string[], receiver: string, name: string, method: RpcMethod, isSession: boolean): void {
+function emitMethod(lines: string[], receiver: string, name: string, method: RpcMethod, isSession: boolean, groupExperimental = false): void {
const methodName = toPascalCase(name);
const resultType = toPascalCase(method.rpcMethod) + "Result";
@@ -254,6 +294,9 @@ function emitMethod(lines: string[], receiver: string, name: string, method: Rpc
const hasParams = isSession ? nonSessionParams.length > 0 : Object.keys(paramProps).length > 0;
const paramsType = hasParams ? toPascalCase(method.rpcMethod) + "Params" : "";
+ if (method.stability === "experimental" && !groupExperimental) {
+ lines.push(`// Experimental: ${methodName} is an experimental API and may change or be removed in future versions.`);
+ }
const sig = hasParams
? `func (a *${receiver}) ${methodName}(ctx context.Context, params *${paramsType}) (*${resultType}, error)`
: `func (a *${receiver}) ${methodName}(ctx context.Context) (*${resultType}, error)`;
@@ -261,33 +304,37 @@ function emitMethod(lines: string[], receiver: string, name: string, method: Rpc
lines.push(sig + ` {`);
if (isSession) {
- lines.push(` req := map[string]interface{}{"sessionId": a.sessionID}`);
+ lines.push(`\treq := map[string]interface{}{"sessionId": a.sessionID}`);
if (hasParams) {
- lines.push(` if params != nil {`);
+ lines.push(`\tif params != nil {`);
for (const pName of nonSessionParams) {
const goField = toGoFieldName(pName);
const isOptional = !requiredParams.has(pName);
if (isOptional) {
// Optional fields are pointers - only add when non-nil and dereference
- lines.push(` if params.${goField} != nil {`);
- lines.push(` req["${pName}"] = *params.${goField}`);
- lines.push(` }`);
+ lines.push(`\t\tif params.${goField} != nil {`);
+ lines.push(`\t\t\treq["${pName}"] = *params.${goField}`);
+ lines.push(`\t\t}`);
} else {
- lines.push(` req["${pName}"] = params.${goField}`);
+ lines.push(`\t\treq["${pName}"] = params.${goField}`);
}
}
- lines.push(` }`);
+ lines.push(`\t}`);
}
- lines.push(` raw, err := a.client.Request("${method.rpcMethod}", req)`);
+ lines.push(`\traw, err := a.client.Request("${method.rpcMethod}", req)`);
} else {
const arg = hasParams ? "params" : "map[string]interface{}{}";
- lines.push(` raw, err := a.client.Request("${method.rpcMethod}", ${arg})`);
+ lines.push(`\traw, err := a.client.Request("${method.rpcMethod}", ${arg})`);
}
- lines.push(` if err != nil { return nil, err }`);
- lines.push(` var result ${resultType}`);
- lines.push(` if err := json.Unmarshal(raw, &result); err != nil { return nil, err }`);
- lines.push(` return &result, nil`);
+ lines.push(`\tif err != nil {`);
+ lines.push(`\t\treturn nil, err`);
+ lines.push(`\t}`);
+ lines.push(`\tvar result ${resultType}`);
+ lines.push(`\tif err := json.Unmarshal(raw, &result); err != nil {`);
+ lines.push(`\t\treturn nil, err`);
+ lines.push(`\t}`);
+ lines.push(`\treturn &result, nil`);
lines.push(`}`);
lines.push(``);
}
diff --git a/scripts/codegen/python.ts b/scripts/codegen/python.ts
index 65563d741..3dfa52535 100644
--- a/scripts/codegen/python.ts
+++ b/scripts/codegen/python.ts
@@ -15,6 +15,7 @@ import {
postProcessSchema,
writeGeneratedFile,
isRpcMethod,
+ isNodeFullyExperimental,
type ApiSchema,
type RpcMethod,
} from "./utils.js";
@@ -215,6 +216,23 @@ async function generateRpc(schemaPath?: string): Promise {
// Modernize to Python 3.11+ syntax
typesCode = modernizePython(typesCode);
+ // Annotate experimental data types
+ const experimentalTypeNames = new Set();
+ for (const method of allMethods) {
+ if (method.stability !== "experimental") continue;
+ experimentalTypeNames.add(toPascalCase(method.rpcMethod) + "Result");
+ const baseName = toPascalCase(method.rpcMethod);
+ if (combinedSchema.definitions![baseName + "Params"]) {
+ experimentalTypeNames.add(baseName + "Params");
+ }
+ }
+ for (const typeName of experimentalTypeNames) {
+ typesCode = typesCode.replace(
+ new RegExp(`^(@dataclass\\n)?class ${typeName}[:(]`, "m"),
+ (match) => `# Experimental: this type is part of an experimental API and may change or be removed.\n${match}`
+ );
+ }
+
const lines: string[] = [];
lines.push(`"""
AUTO-GENERATED FILE - DO NOT EDIT
@@ -259,12 +277,19 @@ function emitRpcWrapper(lines: string[], node: Record, isSessio
for (const [groupName, groupNode] of groups) {
const prefix = isSession ? "" : "Server";
const apiName = prefix + toPascalCase(groupName) + "Api";
+ const groupExperimental = isNodeFullyExperimental(groupNode as Record);
if (isSession) {
+ if (groupExperimental) {
+ lines.push(`# Experimental: this API group is experimental and may change or be removed.`);
+ }
lines.push(`class ${apiName}:`);
lines.push(` def __init__(self, client: "JsonRpcClient", session_id: str):`);
lines.push(` self._client = client`);
lines.push(` self._session_id = session_id`);
} else {
+ if (groupExperimental) {
+ lines.push(`# Experimental: this API group is experimental and may change or be removed.`);
+ }
lines.push(`class ${apiName}:`);
lines.push(` def __init__(self, client: "JsonRpcClient"):`);
lines.push(` self._client = client`);
@@ -272,7 +297,7 @@ function emitRpcWrapper(lines: string[], node: Record, isSessio
lines.push(``);
for (const [key, value] of Object.entries(groupNode as Record)) {
if (!isRpcMethod(value)) continue;
- emitMethod(lines, key, value, isSession);
+ emitMethod(lines, key, value, isSession, groupExperimental);
}
lines.push(``);
}
@@ -301,12 +326,12 @@ function emitRpcWrapper(lines: string[], node: Record, isSessio
// Top-level methods
for (const [key, value] of topLevelMethods) {
if (!isRpcMethod(value)) continue;
- emitMethod(lines, key, value, isSession);
+ emitMethod(lines, key, value, isSession, false);
}
lines.push(``);
}
-function emitMethod(lines: string[], name: string, method: RpcMethod, isSession: boolean): void {
+function emitMethod(lines: string[], name: string, method: RpcMethod, isSession: boolean, groupExperimental = false): void {
const methodName = toSnakeCase(name);
const resultType = toPascalCase(method.rpcMethod) + "Result";
@@ -322,6 +347,10 @@ function emitMethod(lines: string[], name: string, method: RpcMethod, isSession:
lines.push(sig);
+ if (method.stability === "experimental" && !groupExperimental) {
+ lines.push(` """.. warning:: This API is experimental and may change or be removed in future versions."""`);
+ }
+
// Build request body with proper serialization/deserialization
if (isSession) {
if (hasParams) {
diff --git a/scripts/codegen/typescript.ts b/scripts/codegen/typescript.ts
index 77c31019a..8d23b428f 100644
--- a/scripts/codegen/typescript.ts
+++ b/scripts/codegen/typescript.ts
@@ -15,6 +15,7 @@ import {
postProcessSchema,
writeGeneratedFile,
isRpcMethod,
+ isNodeFullyExperimental,
type ApiSchema,
type RpcMethod,
} from "./utils.js";
@@ -91,6 +92,9 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js";
bannerComment: "",
additionalProperties: false,
});
+ if (method.stability === "experimental") {
+ lines.push("/** @experimental */");
+ }
lines.push(compiled.trim());
lines.push("");
@@ -99,6 +103,9 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js";
bannerComment: "",
additionalProperties: false,
});
+ if (method.stability === "experimental") {
+ lines.push("/** @experimental */");
+ }
lines.push(paramsCompiled.trim());
lines.push("");
}
@@ -129,7 +136,7 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js";
console.log(` ✓ ${outPath}`);
}
-function emitGroup(node: Record, indent: string, isSession: boolean): string[] {
+function emitGroup(node: Record, indent: string, isSession: boolean, parentExperimental = false): string[] {
const lines: string[] = [];
for (const [key, value] of Object.entries(node)) {
if (isRpcMethod(value)) {
@@ -160,11 +167,18 @@ function emitGroup(node: Record, indent: string, isSession: boo
}
}
+ if ((value as RpcMethod).stability === "experimental" && !parentExperimental) {
+ lines.push(`${indent}/** @experimental */`);
+ }
lines.push(`${indent}${key}: async (${sigParams.join(", ")}): Promise<${resultType}> =>`);
lines.push(`${indent} connection.sendRequest("${rpcMethod}", ${bodyArg}),`);
} else if (typeof value === "object" && value !== null) {
+ const groupExperimental = isNodeFullyExperimental(value as Record);
+ if (groupExperimental) {
+ lines.push(`${indent}/** @experimental */`);
+ }
lines.push(`${indent}${key}: {`);
- lines.push(...emitGroup(value as Record, indent + " ", isSession));
+ lines.push(...emitGroup(value as Record, indent + " ", isSession, groupExperimental));
lines.push(`${indent}},`);
}
}
diff --git a/scripts/codegen/utils.ts b/scripts/codegen/utils.ts
index 88ca68de8..2c13b1d96 100644
--- a/scripts/codegen/utils.ts
+++ b/scripts/codegen/utils.ts
@@ -126,6 +126,7 @@ export interface RpcMethod {
rpcMethod: string;
params: JSONSchema7 | null;
result: JSONSchema7;
+ stability?: string;
}
export interface ApiSchema {
@@ -136,3 +137,18 @@ export interface ApiSchema {
export function isRpcMethod(node: unknown): node is RpcMethod {
return typeof node === "object" && node !== null && "rpcMethod" in node;
}
+
+/** Returns true when every leaf RPC method inside `node` is marked experimental. */
+export function isNodeFullyExperimental(node: Record): boolean {
+ const methods: RpcMethod[] = [];
+ (function collect(n: Record) {
+ for (const value of Object.values(n)) {
+ if (isRpcMethod(value)) {
+ methods.push(value);
+ } else if (typeof value === "object" && value !== null) {
+ collect(value as Record);
+ }
+ }
+ })(node);
+ return methods.length > 0 && methods.every(m => m.stability === "experimental");
+}