diff --git a/.gitattributes b/.gitattributes
index c1965c216..adb3de78e 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1 +1,2 @@
-.github/workflows/*.lock.yml linguist-generated=true merge=ours
\ No newline at end of file
+.github/workflows/*.lock.yml linguist-generated=true merge=ours
+src/generated/java/** eol=lf linguist-generated=true
diff --git a/.github/workflows/codegen-check.yml b/.github/workflows/codegen-check.yml
new file mode 100644
index 000000000..675629c1e
--- /dev/null
+++ b/.github/workflows/codegen-check.yml
@@ -0,0 +1,44 @@
+name: "Codegen Check"
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ paths:
+ - 'scripts/codegen/**'
+ - 'src/generated/java/**'
+ - '.github/workflows/codegen-check.yml'
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ check:
+ name: "Verify generated files are up-to-date"
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+
+ - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
+ with:
+ node-version: 22
+
+ - name: Install codegen dependencies
+ working-directory: ./scripts/codegen
+ run: npm ci
+
+ - name: Run codegen
+ working-directory: ./scripts/codegen
+ run: npm run generate
+
+ - name: Check for uncommitted changes
+ run: |
+ if [ -n "$(git status --porcelain)" ]; then
+ echo "::error::Generated files are out of date. Run 'cd scripts/codegen && npm run generate' and commit the changes."
+ git diff --stat
+ git diff
+ exit 1
+ fi
+ echo "✅ Generated files are up-to-date"
diff --git a/.github/workflows/update-copilot-dependency.yml b/.github/workflows/update-copilot-dependency.yml
new file mode 100644
index 000000000..277434ead
--- /dev/null
+++ b/.github/workflows/update-copilot-dependency.yml
@@ -0,0 +1,92 @@
+name: "Update @github/copilot Dependency"
+
+on:
+ workflow_dispatch:
+ inputs:
+ version:
+ description: 'Target version of @github/copilot (e.g. 1.0.24)'
+ required: true
+ type: string
+
+permissions:
+ contents: write
+ pull-requests: write
+
+jobs:
+ update:
+ name: "Update @github/copilot to ${{ inputs.version }}"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Validate version input
+ env:
+ VERSION: ${{ inputs.version }}
+ run: |
+ if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9._-]+)?$ ]]; then
+ echo "::error::Invalid version format '$VERSION'. Expected semver (e.g. 1.0.24)."
+ exit 1
+ fi
+
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+
+ - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
+ with:
+ node-version: 22
+
+ - name: Update @github/copilot in scripts/codegen
+ env:
+ VERSION: ${{ inputs.version }}
+ working-directory: ./scripts/codegen
+ run: npm install "@github/copilot@$VERSION"
+
+ - name: Install codegen dependencies
+ working-directory: ./scripts/codegen
+ run: npm ci
+
+ - name: Run codegen
+ working-directory: ./scripts/codegen
+ run: npm run generate
+
+ - name: Create pull request
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ VERSION: ${{ inputs.version }}
+ run: |
+ BRANCH="update-copilot-$VERSION"
+ git config user.name "github-actions[bot]"
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
+
+ if git rev-parse --verify "origin/$BRANCH" >/dev/null 2>&1; then
+ git checkout "$BRANCH"
+ git reset --hard HEAD
+ else
+ git checkout -b "$BRANCH"
+ fi
+
+ git add -A
+
+ if git diff --cached --quiet; then
+ echo "No changes detected; skipping commit and PR creation."
+ exit 0
+ fi
+
+ git commit -m "Update @github/copilot to $VERSION
+
+ - Updated @github/copilot in scripts/codegen
+ - Re-ran Java code generator"
+ git push origin "$BRANCH" --force-with-lease
+
+ if gh pr view "$BRANCH" >/dev/null 2>&1; then
+ echo "Pull request for branch '$BRANCH' already exists; updated branch only."
+ else
+ gh pr create \
+ --title "Update @github/copilot to $VERSION" \
+ --body "Automated update of \`@github/copilot\` to version \`$VERSION\`.
+
+ ### Changes
+ - Updated \`@github/copilot\` in \`scripts/codegen/package.json\`
+ - Re-ran Java code generator (\`scripts/codegen\`)
+
+ > Created by the **Update @github/copilot Dependency** workflow." \
+ --base main \
+ --head "$BRANCH"
+ fi
diff --git a/.gitignore b/.gitignore
index ddb2508ba..85e604a7f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,4 @@ changebundle.txt*
.classpath
.project
.settings
+scripts/codegen/node_modules/
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
index 33c9ac5f2..9a6f3761b 100644
--- a/config/checkstyle/checkstyle.xml
+++ b/config/checkstyle/checkstyle.xml
@@ -11,9 +11,9 @@
-
+
-
+
diff --git a/docs/WORKFLOWS.md b/docs/WORKFLOWS.md
index 74afbd337..4725ff8f2 100644
--- a/docs/WORKFLOWS.md
+++ b/docs/WORKFLOWS.md
@@ -5,6 +5,8 @@
| Workflow | Description | Triggers | Schedule |
|----------|-------------|----------|----------|
| [Build & Test](workflows/build-test.yml) | Builds, lints, and tests the Java SDK | `push` (main), `pull_request`, `merge_group`, `workflow_dispatch` | Sundays at 00:00 UTC |
+| [Codegen Check](workflows/codegen-check.yml) | Verifies that generated Java files are up-to-date with the JSON schemas | `push` (main), `pull_request`, `workflow_dispatch` | — |
+| [Update @github/copilot Dependency](workflows/update-copilot-dependency.yml) | Updates the `@github/copilot` npm package, re-runs code generation, and opens a PR | `workflow_dispatch` | — |
| [Deploy Documentation](workflows/deploy-site.yml) | Generates and deploys versioned docs to GitHub Pages | `workflow_run` (after Build & Test), `release`, `workflow_dispatch` | — |
| [Publish to Maven Central](workflows/publish-maven.yml) | Releases the SDK to Maven Central and creates a GitHub Release | `workflow_dispatch` | — |
| [Weekly Upstream Sync](workflows/weekly-upstream-sync.yml) | Checks for new upstream commits and creates an issue for Copilot to merge | `workflow_dispatch` | Mondays at 10:00 UTC |
@@ -87,6 +89,42 @@ Auto-generated compiled workflow produced by `gh aw compile` from the correspond
---
+## Codegen Check
+
+**File:** [`codegen-check.yml`](workflows/codegen-check.yml)
+
+Verifies that the generated Java source files in `src/generated/java/` are up-to-date with the JSON schemas distributed in the `@github/copilot` npm package.
+
+Steps:
+1. Installs the codegen dependencies from `scripts/codegen/`
+2. Runs the Java code generator (`npm run generate`)
+3. Fails with a diff if any generated file differs from what is committed
+
+Run this locally with:
+```bash
+cd scripts/codegen && npm ci && npm run generate
+```
+
+If changes appear, commit the updated generated files.
+
+---
+
+## Update @github/copilot Dependency
+
+**File:** [`update-copilot-dependency.yml`](workflows/update-copilot-dependency.yml)
+
+Manual workflow triggered when a new version of the `@github/copilot` npm package is published. Accepts a `version` input (e.g. `1.0.25`).
+
+Steps:
+1. Updates `@github/copilot` in `scripts/codegen/package.json`
+2. Re-runs the Java code generator
+3. Commits the updated `package.json`, `package-lock.json`, and all regenerated Java files
+4. Opens (or updates) a pull request for review
+
+The resulting PR will be automatically validated by the **Codegen Check** workflow.
+
+---
+
## Copilot Setup Steps
**File:** [`copilot-setup-steps.yml`](workflows/copilot-setup-steps.yml)
diff --git a/pom.xml b/pom.xml
index b61c36166..95ed2602d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -255,12 +255,35 @@
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ 3.6.1
+
+
+ add-generated-source
+ generate-sources
+
+ add-source
+
+
+
+ ${project.basedir}/src/generated/java
+
+
+
+
+
com.diffplug.spotless
spotless-maven-plugin
2.44.5
+
+ src/generated/java/**/*.java
+
4.33
diff --git a/scripts/codegen/.gitignore b/scripts/codegen/.gitignore
new file mode 100644
index 000000000..c2658d7d1
--- /dev/null
+++ b/scripts/codegen/.gitignore
@@ -0,0 +1 @@
+node_modules/
diff --git a/scripts/codegen/java.ts b/scripts/codegen/java.ts
new file mode 100644
index 000000000..2a37e92af
--- /dev/null
+++ b/scripts/codegen/java.ts
@@ -0,0 +1,821 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+/**
+ * Java code generator for session-events and RPC types.
+ * Generates Java source files under src/generated/java/ from JSON Schema files.
+ */
+
+import fs from "fs/promises";
+import path from "path";
+import { fileURLToPath } from "url";
+import type { JSONSchema7 } from "json-schema";
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+/** Root of the copilot-sdk-java repo */
+const REPO_ROOT = path.resolve(__dirname, "../..");
+
+/** Event types to exclude from generation (internal/legacy types) */
+const EXCLUDED_EVENT_TYPES = new Set(["session.import_legacy"]);
+
+const AUTO_GENERATED_HEADER = `// AUTO-GENERATED FILE - DO NOT EDIT`;
+const GENERATED_FROM_SESSION_EVENTS = `// Generated from: session-events.schema.json`;
+const GENERATED_FROM_API = `// Generated from: api.schema.json`;
+const GENERATED_ANNOTATION = `@javax.annotation.processing.Generated("copilot-sdk-codegen")`;
+const COPYRIGHT = `/*---------------------------------------------------------------------------------------------\n * Copyright (c) Microsoft Corporation. All rights reserved.\n *--------------------------------------------------------------------------------------------*/`;
+
+// ── Naming utilities ─────────────────────────────────────────────────────────
+
+function toPascalCase(name: string): string {
+ return name.split(/[-_.]/).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
+}
+
+function toJavaClassName(typeName: string): string {
+ return typeName.split(/[._]/).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
+}
+
+function toCamelCase(name: string): string {
+ const pascal = toPascalCase(name);
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
+}
+
+function toEnumConstant(value: string): string {
+ return value.toUpperCase().replace(/[-. ]/g, "_");
+}
+
+// ── Schema path resolution ───────────────────────────────────────────────────
+
+async function getSessionEventsSchemaPath(): Promise {
+ const candidates = [
+ path.join(REPO_ROOT, "scripts/codegen/node_modules/@github/copilot/schemas/session-events.schema.json"),
+ path.join(REPO_ROOT, "nodejs/node_modules/@github/copilot/schemas/session-events.schema.json"),
+ ];
+ for (const p of candidates) {
+ try {
+ await fs.access(p);
+ return p;
+ } catch {
+ // try next
+ }
+ }
+ throw new Error("session-events.schema.json not found. Run 'npm ci' in scripts/codegen first.");
+}
+
+async function getApiSchemaPath(): Promise {
+ const candidates = [
+ path.join(REPO_ROOT, "scripts/codegen/node_modules/@github/copilot/schemas/api.schema.json"),
+ path.join(REPO_ROOT, "nodejs/node_modules/@github/copilot/schemas/api.schema.json"),
+ ];
+ for (const p of candidates) {
+ try {
+ await fs.access(p);
+ return p;
+ } catch {
+ // try next
+ }
+ }
+ throw new Error("api.schema.json not found. Run 'npm ci' in scripts/codegen first.");
+}
+
+// ── File writing ─────────────────────────────────────────────────────────────
+
+async function writeGeneratedFile(relativePath: string, content: string): Promise {
+ const fullPath = path.join(REPO_ROOT, relativePath);
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
+ await fs.writeFile(fullPath, content, "utf-8");
+ console.log(` ✓ ${relativePath}`);
+ return fullPath;
+}
+
+// ── Java type mapping ─────────────────────────────────────────────────────────
+
+interface JavaTypeResult {
+ javaType: string;
+ imports: Set;
+}
+
+function schemaTypeToJava(
+ schema: JSONSchema7,
+ required: boolean,
+ context: string,
+ propName: string,
+ nestedTypes: Map
+): JavaTypeResult {
+ const imports = new Set();
+
+ if (schema.anyOf) {
+ const hasNull = schema.anyOf.some((s) => typeof s === "object" && (s as JSONSchema7).type === "null");
+ const nonNull = schema.anyOf.filter((s) => typeof s === "object" && (s as JSONSchema7).type !== "null");
+ if (nonNull.length === 1) {
+ const result = schemaTypeToJava(nonNull[0] as JSONSchema7, required && !hasNull, context, propName, nestedTypes);
+ return result;
+ }
+ return { javaType: "Object", imports };
+ }
+
+ if (schema.type === "string") {
+ if (schema.format === "uuid") {
+ imports.add("java.util.UUID");
+ return { javaType: "UUID", imports };
+ }
+ if (schema.format === "date-time") {
+ imports.add("java.time.OffsetDateTime");
+ return { javaType: "OffsetDateTime", imports };
+ }
+ if (schema.enum && Array.isArray(schema.enum)) {
+ const enumName = `${context}${toPascalCase(propName)}`;
+ nestedTypes.set(enumName, {
+ kind: "enum",
+ name: enumName,
+ values: schema.enum as string[],
+ description: schema.description,
+ });
+ return { javaType: enumName, imports };
+ }
+ return { javaType: "String", imports };
+ }
+
+ if (Array.isArray(schema.type)) {
+ const nonNullTypes = schema.type.filter((t) => t !== "null");
+ if (nonNullTypes.length === 1) {
+ const baseSchema = { ...schema, type: nonNullTypes[0] };
+ return schemaTypeToJava(baseSchema as JSONSchema7, required, context, propName, nestedTypes);
+ }
+ }
+
+ if (schema.type === "number" || schema.type === "integer") {
+ return { javaType: "Double", imports };
+ }
+
+ if (schema.type === "boolean") {
+ return { javaType: "Boolean", imports };
+ }
+
+ if (schema.type === "array") {
+ const items = schema.items as JSONSchema7 | undefined;
+ if (items) {
+ const itemResult = schemaTypeToJava(items, true, context, propName + "Item", nestedTypes);
+ imports.add("java.util.List");
+ for (const imp of itemResult.imports) imports.add(imp);
+ return { javaType: `List<${itemResult.javaType}>`, imports };
+ }
+ imports.add("java.util.List");
+ return { javaType: "List