Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions dotnet/src/Generated/Rpc.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions dotnet/src/Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ internal static void WriteValue(Utf8JsonWriter writer, string value, Type typeTo
}
}

/// <summary>Diagnostic IDs for the Copilot SDK.</summary>
internal static class Diagnostics
{
/// <summary>Indicates an experimental API that may change or be removed.</summary>
internal const string Experimental = "GHCP001";
}

/// <summary>
/// Represents the connection state of the Copilot client.
/// </summary>
Expand Down
120 changes: 76 additions & 44 deletions scripts/codegen/csharp.ts

Large diffs are not rendered by default.

105 changes: 87 additions & 18 deletions scripts/codegen/go.ts

Large diffs are not rendered by default.

74 changes: 62 additions & 12 deletions scripts/codegen/python.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
isNodeFullyExperimental,
isNodeFullyDeprecated,
isSchemaDeprecated,
isSchemaExperimental,
postProcessSchema,
stripBooleanLiterals,
writeGeneratedFile,
Expand All @@ -43,6 +44,20 @@ import {

// ── Utilities ───────────────────────────────────────────────────────────────

type PyExperimentalSubject = "type" | "enum" | "event";

function pyExperimentalComment(subject: PyExperimentalSubject, indent = ""): string {
return `${indent}# Experimental: this ${subject} is part of an experimental API and may change or be removed.`;
}

function pushPyExperimentalComment(lines: string[], subject: PyExperimentalSubject, indent = ""): void {
lines.push(pyExperimentalComment(subject, indent));
}

function pushPyExperimentalApiGroupComment(lines: string[]): void {
lines.push("# Experimental: this API group is experimental and may change or be removed.");
}

/**
* Modernize quicktype's Python 3.7 output to Python 3.11+ syntax:
* - Optional[T] → T | None
Expand Down Expand Up @@ -478,6 +493,8 @@ interface PyEventVariant {
dataClassName: string;
dataSchema: JSONSchema7;
dataDescription?: string;
eventExperimental: boolean;
Comment thread
stephentoub marked this conversation as resolved.
dataExperimental: boolean;
Comment thread
stephentoub marked this conversation as resolved.
}

interface PyEventEnvelopeProperty extends SessionEventEnvelopeProperty {
Expand Down Expand Up @@ -650,6 +667,8 @@ function extractPyEventVariants(schema: JSONSchema7): PyEventVariant[] {
dataClassName: `${toPascalCase(typeName)}Data`,
dataSchema,
dataDescription: dataSchema.description,
eventExperimental: isSchemaExperimental(variant),
dataExperimental: isSchemaExperimental(dataSchema),
};
});
}
Expand Down Expand Up @@ -721,14 +740,18 @@ function getOrCreatePyEnum(
values: string[],
ctx: PyCodegenCtx,
description?: string,
deprecated?: boolean
deprecated?: boolean,
experimental?: boolean
): string {
const existing = ctx.enumsByName.get(enumName);
if (existing) {
return existing;
}

const lines: string[] = [];
if (experimental) {
pushPyExperimentalComment(lines, "enum");
}
if (deprecated) {
lines.push(`# Deprecated: this enum is deprecated and will be removed in a future version.`);
}
Expand Down Expand Up @@ -761,7 +784,7 @@ function resolvePyPropertyType(
const resolved = resolveSchema(propSchema, ctx.definitions);
if (resolved && resolved !== propSchema) {
if (resolved.enum && Array.isArray(resolved.enum) && resolved.enum.every((value) => typeof value === "string")) {
const enumType = getOrCreatePyEnum(typeName, resolved.enum as string[], ctx, resolved.description, isSchemaDeprecated(resolved));
const enumType = getOrCreatePyEnum(typeName, resolved.enum as string[], ctx, resolved.description, isSchemaDeprecated(resolved), isSchemaExperimental(resolved));
const enumResolved: PyResolvedType = {
annotation: enumType,
fromExpr: (expr) => `parse_enum(${enumType}, ${expr})`,
Expand Down Expand Up @@ -820,7 +843,8 @@ function resolvePyPropertyType(
discriminator.property,
discriminator.mapping,
ctx,
propSchema.description
propSchema.description,
isSchemaExperimental(propSchema)
);
const resolved: PyResolvedType = {
annotation: nestedName,
Expand All @@ -840,7 +864,8 @@ function resolvePyPropertyType(
propSchema.enum as string[],
ctx,
propSchema.description,
isSchemaDeprecated(propSchema)
isSchemaDeprecated(propSchema),
isSchemaExperimental(propSchema)
);
const resolved: PyResolvedType = {
annotation: enumType,
Expand Down Expand Up @@ -963,7 +988,8 @@ function resolvePyPropertyType(
discriminator.property,
discriminator.mapping,
ctx,
items.description
items.description,
isSchemaExperimental(items)
);
const resolved: PyResolvedType = {
annotation: `list[${itemTypeName}]`,
Expand Down Expand Up @@ -1032,7 +1058,8 @@ function emitPyClass(
typeName: string,
schema: JSONSchema7,
ctx: PyCodegenCtx,
description?: string
description?: string,
experimental = isSchemaExperimental(schema)
): void {
if (ctx.generatedNames.has(typeName)) {
return;
Expand Down Expand Up @@ -1063,6 +1090,9 @@ function emitPyClass(
});

const lines: string[] = [];
if (experimental) {
pushPyExperimentalComment(lines, "type");
}
if (isSchemaDeprecated(schema)) {
lines.push(`# Deprecated: this type is deprecated and will be removed in a future version.`);
}
Expand Down Expand Up @@ -1131,7 +1161,8 @@ function emitPyFlatDiscriminatedUnion(
discriminatorProp: string,
mapping: Map<string, JSONSchema7>,
ctx: PyCodegenCtx,
description?: string
description?: string,
experimental = false
): void {
if (ctx.generatedNames.has(typeName)) {
return;
Expand Down Expand Up @@ -1173,7 +1204,9 @@ function emitPyFlatDiscriminatedUnion(
typeName + toPascalCase(discriminatorProp),
[...mapping.keys()],
ctx,
description ? `${description} discriminator` : `${typeName} discriminator`
description ? `${description} discriminator` : `${typeName} discriminator`,
false,
experimental
);

const fieldEntries: Array<[string, JSONSchema7, boolean]> = [
Expand Down Expand Up @@ -1219,6 +1252,9 @@ function emitPyFlatDiscriminatedUnion(
});

const lines: string[] = [];
if (experimental) {
pushPyExperimentalComment(lines, "type");
}
lines.push(`@dataclass`);
lines.push(`class ${typeName}:`);
if (description) {
Expand Down Expand Up @@ -1279,7 +1315,13 @@ export function generatePythonSessionEventsCode(schema: JSONSchema7): string {
};

for (const variant of variants) {
emitPyClass(variant.dataClassName, variant.dataSchema, ctx, variant.dataDescription);
emitPyClass(
variant.dataClassName,
variant.dataSchema,
ctx,
variant.dataDescription,
variant.dataExperimental
);
}
const envelopeProperties = getPySharedEventEnvelopeProperties(schema, ctx);
const envelopePropertiesWithoutDefaults = envelopeProperties.filter((property) => !property.hasDefault);
Expand All @@ -1288,6 +1330,9 @@ export function generatePythonSessionEventsCode(schema: JSONSchema7): string {
const eventTypeLines: string[] = [];
eventTypeLines.push(`class SessionEventType(Enum):`);
for (const variant of variants) {
if (variant.eventExperimental) {
pushPyExperimentalComment(eventTypeLines, "event", " ");
}
eventTypeLines.push(` ${toEnumMemberName(variant.typeName)} = ${JSON.stringify(variant.typeName)}`);
}
eventTypeLines.push(` UNKNOWN = "unknown"`);
Expand Down Expand Up @@ -1740,6 +1785,11 @@ async function generateRpc(schemaPath?: string): Promise<void> {

// Annotate experimental data types
const experimentalTypeNames = new Set<string>();
for (const [definitionName, definition] of Object.entries(allDefinitions)) {
if (typeof definition === "object" && definition !== null && isSchemaExperimental(definition as JSONSchema7)) {
experimentalTypeNames.add(definitionName);
}
}
for (const method of allMethods) {
if (method.stability !== "experimental") continue;
experimentalTypeNames.add(pythonResultTypeName(method));
Expand All @@ -1751,7 +1801,7 @@ async function generateRpc(schemaPath?: string): Promise<void> {
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}`
(match) => `${pyExperimentalComment("type")}\n${match}`
);
}

Expand Down Expand Up @@ -1916,7 +1966,7 @@ function emitPyApiGroup(
lines.push(`# Deprecated: this API group is deprecated and will be removed in a future version.`);
}
if (groupExperimental) {
lines.push(`# Experimental: this API group is experimental and may change or be removed.`);
pushPyExperimentalApiGroupComment(lines);
}
lines.push(`class ${apiName}:`);
if (isSession) {
Expand Down Expand Up @@ -2105,7 +2155,7 @@ function emitClientSessionApiRegistration(
lines.push(`# Deprecated: this API group is deprecated and will be removed in a future version.`);
}
if (groupExperimental) {
lines.push(`# Experimental: this API group is experimental and may change or be removed.`);
pushPyExperimentalApiGroupComment(lines);
}
lines.push(`class ${handlerName}(Protocol):`);
for (const [methodName, value] of Object.entries(groupNode as Record<string, unknown>)) {
Expand Down
Loading
Loading