#!/usr/bin/env node import * as NodeRuntime from "@effect/platform-node/NodeRuntime";import * as NodeServices from "@effect/platform-node/NodeServices";import { make as makeJsonSchemaGenerator } from "@effect/openapi-generator/JsonSchemaGenerator";import { Effect, FileSystem, Layer, Logger, Path, Schema } from "effect";import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; const UPSTREAM_REF = "be75785504ff152fa6333e380a2d50642f42fba0";const USER_AGENT = "effect-codex-app-server-generator";const GITHUB_API_BASE = "https://api.github.com/repos/openai/codex/contents/codex-rs/app-server-protocol"; const GithubContentEntries = Schema.Array( Schema.Struct({ name: Schema.String, path: Schema.String, download_url: Schema.NullOr(Schema.String), type: Schema.String, }),);type GithubContentEntry = (typeof GithubContentEntries.Type)[number]; interface GeneratedPaths { readonly generatedDir: string; readonly schemaOutputPath: string; readonly metaOutputPath: string; readonly namespacesOutputPath: string;} interface MethodEntry { readonly method: string; readonly paramsType?: string;} interface JsonSchemaFile { readonly namespace?: string; readonly exportName: string; readonly fileName: string; readonly downloadUrl: string; readonly qualifiedName: string;} class GeneratorError extends Schema.TaggedErrorClass<GeneratorError>()("GeneratorError", { detail: Schema.String, cause: Schema.optional(Schema.Defect),}) { override get message() { return this.detail; }} const ManualSchemas: Record<string, typeof Schema.Json.Type> = { GetAuthStatusParams: { type: "object", title: "GetAuthStatusParams", properties: { includeToken: { anyOf: [{ type: "boolean" }, { type: "null" }], }, refreshToken: { anyOf: [{ type: "boolean" }, { type: "null" }], }, }, }, GetConversationSummaryParams: { title: "GetConversationSummaryParams", oneOf: [ { type: "object", properties: { rolloutPath: { type: "string" }, }, required: ["rolloutPath"], }, { type: "object", properties: { conversationId: { type: "string" }, }, required: ["conversationId"], }, ], }, GetConversationSummaryResponse: { type: "object", title: "GetConversationSummaryResponse", properties: { summary: {}, }, required: ["summary"], }, GitDiffToRemoteParams: { type: "object", title: "GitDiffToRemoteParams", properties: { cwd: { type: "string" }, }, required: ["cwd"], }, GitDiffToRemoteResponse: { type: "object", title: "GitDiffToRemoteResponse", properties: { sha: { type: "string" }, diff: { type: "string" }, }, required: ["sha", "diff"], }, GetAuthStatusResponse: { type: "object", title: "GetAuthStatusResponse", properties: { authMethod: { anyOf: [{}, { type: "null" }], }, authToken: { anyOf: [{ type: "string" }, { type: "null" }], }, requiresOpenaiAuth: { anyOf: [{ type: "boolean" }, { type: "null" }], }, }, required: ["authMethod", "authToken", "requiresOpenaiAuth"], },}; const getGeneratedPaths = Effect.fn("getGeneratedPaths")(function* () { const path = yield* Path.Path; const generatedDir = path.join(import.meta.dirname, "..", "src", "_generated"); return { generatedDir, schemaOutputPath: path.join(generatedDir, "schema.gen.ts"), metaOutputPath: path.join(generatedDir, "meta.gen.ts"), namespacesOutputPath: path.join(generatedDir, "namespaces.gen.ts"), } satisfies GeneratedPaths;}); const ensureGeneratedDir = Effect.fn("ensureGeneratedDir")(function* () { const fs = yield* FileSystem.FileSystem; const { generatedDir } = yield* getGeneratedPaths(); yield* fs.makeDirectory(generatedDir, { recursive: true });}); const fetchText = Effect.fn("fetchText")(function* (url: string) { const response = yield* Effect.tryPromise({ try: () => fetch(url, { headers: { "user-agent": USER_AGENT, }, }), catch: (cause) => new GeneratorError({ detail: `Failed to fetch ${url}`, cause, }), }); if (!response.ok) { const detail = yield* Effect.tryPromise({ try: () => response.text(), catch: () => "", }); return yield* Effect.fail( new GeneratorError({ detail: `Failed to download ${url}: ${response.status} ${detail}`, }), ); } return yield* Effect.tryPromise({ try: () => response.text(), catch: (cause) => new GeneratorError({ detail: `Failed to read response body for ${url}`, cause, }), });}); const fetchDirectoryEntries = Effect.fn("fetchDirectoryEntries")(function* (path: string) { const raw = yield* fetchText(`${GITHUB_API_BASE}/${path}?ref=${UPSTREAM_REF}`); return yield* Schema.decodeEffect(Schema.fromJsonString(GithubContentEntries))(raw);}); function collectSchemaEntries( chunk: string,): ReadonlyArray<{ readonly name: string; readonly code: string }> { const lines = chunk .split("\n") .map((line) => line.trim()) .filter((line) => line.length > 0 && !line.startsWith("//")); const entries: Array<{ name: string; code: string }> = []; for (let index = 0; index < lines.length; index += 1) { const typeLine = lines[index]; if (!typeLine?.startsWith("export type ")) { continue; } const constLine = lines[index + 1]; if (!constLine?.startsWith("export const ")) { throw new Error(`Malformed generator output near: ${typeLine}`); } const match = /^export type ([A-Za-z0-9_]+)/.exec(typeLine); if (!match?.[1]) { throw new Error(`Could not extract schema name from: ${typeLine}`); } entries.push({ name: match[1], code: `${typeLine}\n${constLine}`, }); index += 1; } return entries;} function normalizeNullableTypes(value: typeof Schema.Json.Type): typeof Schema.Json.Type { if (Array.isArray(value)) { return value.map(normalizeNullableTypes); } if (value === null || typeof value !== "object") { return value; } const normalizedEntries = Object.entries(value).map(([key, child]) => [ key, normalizeNullableTypes(child), ]); const normalizedObject = Object.fromEntries(normalizedEntries) as Record< string, typeof Schema.Json.Type >; const typeValue = normalizedObject.type; if (!Array.isArray(typeValue)) { return normalizedObject; } const normalizedTypes = typeValue.filter((entry): entry is string => typeof entry === "string"); if (normalizedTypes.length !== typeValue.length || !normalizedTypes.includes("null")) { return normalizedObject; } const nonNullTypes = normalizedTypes.filter((entry) => entry !== "null"); if (nonNullTypes.length !== 1) { return normalizedObject; } const nonNullType = nonNullTypes[0]!; const nextObject: Record<string, typeof Schema.Json.Type> = {}; for (const [key, child] of Object.entries(normalizedObject)) { if (key !== "type") { nextObject[key] = child; } } return { anyOf: [ { ...nextObject, type: nonNullType, }, { type: "null" }, ], };} function stripNullDefaults(value: typeof Schema.Json.Type): typeof Schema.Json.Type { if (Array.isArray(value)) { return value.map(stripNullDefaults); } if (value === null || typeof value !== "object") { return value; } return Object.fromEntries( Object.entries(value) .filter(([key, child]) => !(key === "default" && child === null)) .map(([key, child]) => [key, stripNullDefaults(child)]), ) as typeof Schema.Json.Type;} function toPascalCaseMethod(method: string) { return method .split("/") .flatMap((segment) => segment.split(/(?=[A-Z])/)) .flatMap((segment) => segment.split(/[-_]/)) .filter(Boolean) .map((segment) => segment[0]!.toUpperCase() + segment.slice(1)) .join("");} function parseRequestEntries(fileContents: string): ReadonlyArray<MethodEntry> { const entryPattern = /\{\s*"method":\s*"([^"]+)",\s*id:\s*RequestId,\s*params:\s*([^,}]+)/g; const entries: Array<MethodEntry> = []; let match: RegExpExecArray | null; while ((match = entryPattern.exec(fileContents)) !== null) { entries.push({ method: match[1]!, paramsType: match[2]!.trim(), }); } return entries;} function parseNotificationEntries(fileContents: string): ReadonlyArray<MethodEntry> { const entryPattern = /\{\s*"method":\s*"([^"]+)"(?:,\s*"params":\s*([^ }]+))?\s*\}/g; const entries: Array<MethodEntry> = []; let match: RegExpExecArray | null; while ((match = entryPattern.exec(fileContents)) !== null) { entries.push({ method: match[1]!, ...(match[2] ? { paramsType: match[2].trim() } : {}), }); } return entries;} function resolveSchemaTypeName( rawTypeName: string, generatedSchemaNames: ReadonlySet<string>,): string { if (rawTypeName === "undefined") { return "undefined"; } const candidates = [ rawTypeName, `V2${rawTypeName}`, `V1${rawTypeName}`, `SerdeJson${rawTypeName}`, ]; for (const candidate of candidates) { if (generatedSchemaNames.has(candidate)) { return candidate; } } throw new Error(`Unable to resolve schema type name: ${rawTypeName}`);} function resolveResponseTypeName( method: string, paramsType: string | undefined, generatedSchemaNames: ReadonlySet<string>,): string { const overrides: Record<string, string> = { "account/logout": "LogoutAccountResponse", "account/rateLimits/read": "GetAccountRateLimitsResponse", "config/batchWrite": "ConfigWriteResponse", "config/mcpServer/reload": "McpServerRefreshResponse", "config/value/write": "ConfigWriteResponse", "configRequirements/read": "ConfigRequirementsReadResponse", }; const override = overrides[method]; if (override) { return resolveSchemaTypeName(override, generatedSchemaNames); } if (paramsType && paramsType !== "undefined") { const fromParams = paramsType.replace(/Params$/, "Response"); try { return resolveSchemaTypeName(fromParams, generatedSchemaNames); } catch { // Fall through to method-based lookup. } } return resolveSchemaTypeName(`${toPascalCaseMethod(method)}Response`, generatedSchemaNames);} function renderMethodConstants(constantName: string, entries: ReadonlyArray<MethodEntry>) { return [ `export const ${constantName} = {`, ...entries.map( (entry) => ` ${JSON.stringify(entry.method)}: ${JSON.stringify(entry.method)},`, ), "} as const;", "", ].join("\n");} function renderTypeInterface( interfaceName: string, entries: ReadonlyArray<MethodEntry>, typeName: (entry: MethodEntry) => string,) { return [ `export interface ${interfaceName} {`, ...entries.map((entry) => ` readonly ${JSON.stringify(entry.method)}: ${typeName(entry)};`), "}", "", ].join("\n");} function renderSchemaMap( constantName: string, entries: ReadonlyArray<MethodEntry>, typeName: (entry: MethodEntry) => string,) { return [ `export const ${constantName} = {`, ...entries.map((entry) => { const schemaName = typeName(entry); return ` ${JSON.stringify(entry.method)}: ${ schemaName === "undefined" ? "undefined" : `CodexSchema.${schemaName}` },`; }), "} as const;", "", ].join("\n");} function renderSchemaTypeReference(schemaName: string) { return schemaName === "undefined" ? "undefined" : `typeof CodexSchema.${schemaName}.Type`;} function exportNameForPath(filePath: string): string { const relative = filePath.replace(/^schema\/json\//, "").replace(/\.json$/, ""); if (!relative.includes("/")) { return relative; } const [namespace, name] = relative.split("/", 2) as [string, string]; const namespacePrefix = namespace .split(/[^A-Za-z0-9]+/) .filter(Boolean) .map((segment) => segment[0]!.toUpperCase() + segment.slice(1)) .join(""); return `${namespacePrefix}${name}`;} function buildJsonSchemaFiles( entries: ReadonlyArray<GithubContentEntry>,): ReadonlyArray<JsonSchemaFile> { return entries .filter( (entry) => entry.type === "file" && entry.name.endsWith(".json") && entry.download_url !== null && !entry.name.startsWith("codex_app_server_protocol."), ) .map((entry) => { const relative = entry.path.replace(/^codex-rs\/app-server-protocol\/schema\/json\//, ""); const parts = relative.split("/"); if (parts.length > 1) { return { namespace: parts[0]!, exportName: exportNameForPath(relative), fileName: entry.name, downloadUrl: entry.download_url!, qualifiedName: relative.replace(/\.json$/, ""), } satisfies JsonSchemaFile; } return { exportName: exportNameForPath(relative), fileName: entry.name, downloadUrl: entry.download_url!, qualifiedName: relative.replace(/\.json$/, ""), } satisfies JsonSchemaFile; });} function rewriteExternalRefs( value: typeof Schema.Json.Type, localDefinitionNames: ReadonlyMap<string, string>, currentNamespace: string | undefined, exportNameByQualifiedName: ReadonlyMap<string, string>,): typeof Schema.Json.Type { if (Array.isArray(value)) { return value.map((entry) => rewriteExternalRefs(entry, localDefinitionNames, currentNamespace, exportNameByQualifiedName), ); } if (value === null || typeof value !== "object") { return value; } return Object.fromEntries( Object.entries(value).map(([key, child]) => { if (key === "$ref" && typeof child === "string" && child.startsWith("#/definitions/")) { const definitionName = child.slice("#/definitions/".length); const localRewrite = localDefinitionNames.get(definitionName); if (localRewrite) { return [key, `#/definitions/${localRewrite}`]; } const candidates = [ ...(currentNamespace ? [`${currentNamespace}/${definitionName}`] : []), definitionName, definitionName.replace(/^v[12]\//, ""), definitionName.replace(/^serde_json\//, ""), `v2/${definitionName}`,Lines 1–500 of 772. Load more
#!/usr/bin/env node import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { make as makeJsonSchemaGenerator } from "@effect/openapi-generator/JsonSchemaGenerator"; import { Effect, FileSystem, Layer, Logger, Path, Schema } from "effect"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; const UPSTREAM_REF = "be75785504ff152fa6333e380a2d50642f42fba0"; const USER_AGENT = "effect-codex-app-server-generator"; const GITHUB_API_BASE = "https://api.github.com/repos/openai/codex/contents/codex-rs/app-server-protocol"; const GithubContentEntries = Schema.Array( Schema.Struct({ name: Schema.String, path: Schema.String, download_url: Schema.NullOr(Schema.String), type: Schema.String, }), ); type GithubContentEntry = (typeof GithubContentEntries.Type)[number]; interface GeneratedPaths { readonly generatedDir: string; readonly schemaOutputPath: string; readonly metaOutputPath: string; readonly namespacesOutputPath: string; } interface MethodEntry { readonly method: string; readonly paramsType?: string; } interface JsonSchemaFile { readonly namespace?: string; readonly exportName: string; readonly fileName: string; readonly downloadUrl: string; readonly qualifiedName: string; } class GeneratorError extends Schema.TaggedErrorClass<GeneratorError>()("GeneratorError", { detail: Schema.String, cause: Schema.optional(Schema.Defect), }) { override get message() { return this.detail; } } const ManualSchemas: Record<string, typeof Schema.Json.Type> = { GetAuthStatusParams: { type: "object", title: "GetAuthStatusParams", properties: { includeToken: { anyOf: [{ type: "boolean" }, { type: "null" }], }, refreshToken: { anyOf: [{ type: "boolean" }, { type: "null" }], }, }, }, GetConversationSummaryParams: { title: "GetConversationSummaryParams", oneOf: [ { type: "object", properties: { rolloutPath: { type: "string" }, }, required: ["rolloutPath"], }, { type: "object", properties: { conversationId: { type: "string" }, }, required: ["conversationId"], }, ], }, GetConversationSummaryResponse: { type: "object", title: "GetConversationSummaryResponse", properties: { summary: {}, }, required: ["summary"], }, GitDiffToRemoteParams: { type: "object", title: "GitDiffToRemoteParams", properties: { cwd: { type: "string" }, }, required: ["cwd"], }, GitDiffToRemoteResponse: { type: "object", title: "GitDiffToRemoteResponse", properties: { sha: { type: "string" }, diff: { type: "string" }, }, required: ["sha", "diff"], }, GetAuthStatusResponse: { type: "object", title: "GetAuthStatusResponse", properties: { authMethod: { anyOf: [{}, { type: "null" }], }, authToken: { anyOf: [{ type: "string" }, { type: "null" }], }, requiresOpenaiAuth: { anyOf: [{ type: "boolean" }, { type: "null" }], }, }, required: ["authMethod", "authToken", "requiresOpenaiAuth"], }, }; const getGeneratedPaths = Effect.fn("getGeneratedPaths")(function* () { const path = yield* Path.Path; const generatedDir = path.join(import.meta.dirname, "..", "src", "_generated"); return { generatedDir, schemaOutputPath: path.join(generatedDir, "schema.gen.ts"), metaOutputPath: path.join(generatedDir, "meta.gen.ts"), namespacesOutputPath: path.join(generatedDir, "namespaces.gen.ts"), } satisfies GeneratedPaths; }); const ensureGeneratedDir = Effect.fn("ensureGeneratedDir")(function* () { const fs = yield* FileSystem.FileSystem; const { generatedDir } = yield* getGeneratedPaths(); yield* fs.makeDirectory(generatedDir, { recursive: true }); }); const fetchText = Effect.fn("fetchText")(function* (url: string) { const response = yield* Effect.tryPromise({ try: () => fetch(url, { headers: { "user-agent": USER_AGENT, }, }), catch: (cause) => new GeneratorError({ detail: `Failed to fetch ${url}`, cause, }), }); if (!response.ok) { const detail = yield* Effect.tryPromise({ try: () => response.text(), catch: () => "", }); return yield* Effect.fail( new GeneratorError({ detail: `Failed to download ${url}: ${response.status} ${detail}`, }), ); } return yield* Effect.tryPromise({ try: () => response.text(), catch: (cause) => new GeneratorError({ detail: `Failed to read response body for ${url}`, cause, }), }); }); const fetchDirectoryEntries = Effect.fn("fetchDirectoryEntries")(function* (path: string) { const raw = yield* fetchText(`${GITHUB_API_BASE}/${path}?ref=${UPSTREAM_REF}`); return yield* Schema.decodeEffect(Schema.fromJsonString(GithubContentEntries))(raw); }); function collectSchemaEntries( chunk: string, ): ReadonlyArray<{ readonly name: string; readonly code: string }> { const lines = chunk .split("\n") .map((line) => line.trim()) .filter((line) => line.length > 0 && !line.startsWith("//")); const entries: Array<{ name: string; code: string }> = []; for (let index = 0; index < lines.length; index += 1) { const typeLine = lines[index]; if (!typeLine?.startsWith("export type ")) { continue; } const constLine = lines[index + 1]; if (!constLine?.startsWith("export const ")) { throw new Error(`Malformed generator output near: ${typeLine}`); } const match = /^export type ([A-Za-z0-9_]+)/.exec(typeLine); if (!match?.[1]) { throw new Error(`Could not extract schema name from: ${typeLine}`); } entries.push({ name: match[1], code: `${typeLine}\n${constLine}`, }); index += 1; } return entries; } function normalizeNullableTypes(value: typeof Schema.Json.Type): typeof Schema.Json.Type { if (Array.isArray(value)) { return value.map(normalizeNullableTypes); } if (value === null || typeof value !== "object") { return value; } const normalizedEntries = Object.entries(value).map(([key, child]) => [ key, normalizeNullableTypes(child), ]); const normalizedObject = Object.fromEntries(normalizedEntries) as Record< string, typeof Schema.Json.Type >; const typeValue = normalizedObject.type; if (!Array.isArray(typeValue)) { return normalizedObject; } const normalizedTypes = typeValue.filter((entry): entry is string => typeof entry === "string"); if (normalizedTypes.length !== typeValue.length || !normalizedTypes.includes("null")) { return normalizedObject; } const nonNullTypes = normalizedTypes.filter((entry) => entry !== "null"); if (nonNullTypes.length !== 1) { return normalizedObject; } const nonNullType = nonNullTypes[0]!; const nextObject: Record<string, typeof Schema.Json.Type> = {}; for (const [key, child] of Object.entries(normalizedObject)) { if (key !== "type") { nextObject[key] = child; } } return { anyOf: [ { ...nextObject, type: nonNullType, }, { type: "null" }, ], }; } function stripNullDefaults(value: typeof Schema.Json.Type): typeof Schema.Json.Type { if (Array.isArray(value)) { return value.map(stripNullDefaults); } if (value === null || typeof value !== "object") { return value; } return Object.fromEntries( Object.entries(value) .filter(([key, child]) => !(key === "default" && child === null)) .map(([key, child]) => [key, stripNullDefaults(child)]), ) as typeof Schema.Json.Type; } function toPascalCaseMethod(method: string) { return method .split("/") .flatMap((segment) => segment.split(/(?=[A-Z])/)) .flatMap((segment) => segment.split(/[-_]/)) .filter(Boolean) .map((segment) => segment[0]!.toUpperCase() + segment.slice(1)) .join(""); } function parseRequestEntries(fileContents: string): ReadonlyArray<MethodEntry> { const entryPattern = /\{\s*"method":\s*"([^"]+)",\s*id:\s*RequestId,\s*params:\s*([^,}]+)/g; const entries: Array<MethodEntry> = []; let match: RegExpExecArray | null; while ((match = entryPattern.exec(fileContents)) !== null) { entries.push({ method: match[1]!, paramsType: match[2]!.trim(), }); } return entries; } function parseNotificationEntries(fileContents: string): ReadonlyArray<MethodEntry> { const entryPattern = /\{\s*"method":\s*"([^"]+)"(?:,\s*"params":\s*([^ }]+))?\s*\}/g; const entries: Array<MethodEntry> = []; let match: RegExpExecArray | null; while ((match = entryPattern.exec(fileContents)) !== null) { entries.push({ method: match[1]!, ...(match[2] ? { paramsType: match[2].trim() } : {}), }); } return entries; } function resolveSchemaTypeName( rawTypeName: string, generatedSchemaNames: ReadonlySet<string>, ): string { if (rawTypeName === "undefined") { return "undefined"; } const candidates = [ rawTypeName, `V2${rawTypeName}`, `V1${rawTypeName}`, `SerdeJson${rawTypeName}`, ]; for (const candidate of candidates) { if (generatedSchemaNames.has(candidate)) { return candidate; } } throw new Error(`Unable to resolve schema type name: ${rawTypeName}`); } function resolveResponseTypeName( method: string, paramsType: string | undefined, generatedSchemaNames: ReadonlySet<string>, ): string { const overrides: Record<string, string> = { "account/logout": "LogoutAccountResponse", "account/rateLimits/read": "GetAccountRateLimitsResponse", "config/batchWrite": "ConfigWriteResponse", "config/mcpServer/reload": "McpServerRefreshResponse", "config/value/write": "ConfigWriteResponse", "configRequirements/read": "ConfigRequirementsReadResponse", }; const override = overrides[method]; if (override) { return resolveSchemaTypeName(override, generatedSchemaNames); } if (paramsType && paramsType !== "undefined") { const fromParams = paramsType.replace(/Params$/, "Response"); try { return resolveSchemaTypeName(fromParams, generatedSchemaNames); } catch { // Fall through to method-based lookup. } } return resolveSchemaTypeName(`${toPascalCaseMethod(method)}Response`, generatedSchemaNames); } function renderMethodConstants(constantName: string, entries: ReadonlyArray<MethodEntry>) { return [ `export const ${constantName} = {`, ...entries.map( (entry) => ` ${JSON.stringify(entry.method)}: ${JSON.stringify(entry.method)},`, ), "} as const;", "", ].join("\n"); } function renderTypeInterface( interfaceName: string, entries: ReadonlyArray<MethodEntry>, typeName: (entry: MethodEntry) => string, ) { return [ `export interface ${interfaceName} {`, ...entries.map((entry) => ` readonly ${JSON.stringify(entry.method)}: ${typeName(entry)};`), "}", "", ].join("\n"); } function renderSchemaMap( constantName: string, entries: ReadonlyArray<MethodEntry>, typeName: (entry: MethodEntry) => string, ) { return [ `export const ${constantName} = {`, ...entries.map((entry) => { const schemaName = typeName(entry); return ` ${JSON.stringify(entry.method)}: ${ schemaName === "undefined" ? "undefined" : `CodexSchema.${schemaName}` },`; }), "} as const;", "", ].join("\n"); } function renderSchemaTypeReference(schemaName: string) { return schemaName === "undefined" ? "undefined" : `typeof CodexSchema.${schemaName}.Type`; } function exportNameForPath(filePath: string): string { const relative = filePath.replace(/^schema\/json\//, "").replace(/\.json$/, ""); if (!relative.includes("/")) { return relative; } const [namespace, name] = relative.split("/", 2) as [string, string]; const namespacePrefix = namespace .split(/[^A-Za-z0-9]+/) .filter(Boolean) .map((segment) => segment[0]!.toUpperCase() + segment.slice(1)) .join(""); return `${namespacePrefix}${name}`; } function buildJsonSchemaFiles( entries: ReadonlyArray<GithubContentEntry>, ): ReadonlyArray<JsonSchemaFile> { return entries .filter( (entry) => entry.type === "file" && entry.name.endsWith(".json") && entry.download_url !== null && !entry.name.startsWith("codex_app_server_protocol."), ) .map((entry) => { const relative = entry.path.replace(/^codex-rs\/app-server-protocol\/schema\/json\//, ""); const parts = relative.split("/"); if (parts.length > 1) { return { namespace: parts[0]!, exportName: exportNameForPath(relative), fileName: entry.name, downloadUrl: entry.download_url!, qualifiedName: relative.replace(/\.json$/, ""), } satisfies JsonSchemaFile; } return { exportName: exportNameForPath(relative), fileName: entry.name, downloadUrl: entry.download_url!, qualifiedName: relative.replace(/\.json$/, ""), } satisfies JsonSchemaFile; }); } function rewriteExternalRefs( value: typeof Schema.Json.Type, localDefinitionNames: ReadonlyMap<string, string>, currentNamespace: string | undefined, exportNameByQualifiedName: ReadonlyMap<string, string>, ): typeof Schema.Json.Type { if (Array.isArray(value)) { return value.map((entry) => rewriteExternalRefs(entry, localDefinitionNames, currentNamespace, exportNameByQualifiedName), ); } if (value === null || typeof value !== "object") { return value; } return Object.fromEntries( Object.entries(value).map(([key, child]) => { if (key === "$ref" && typeof child === "string" && child.startsWith("#/definitions/")) { const definitionName = child.slice("#/definitions/".length); const localRewrite = localDefinitionNames.get(definitionName); if (localRewrite) { return [key, `#/definitions/${localRewrite}`]; } const candidates = [ ...(currentNamespace ? [`${currentNamespace}/${definitionName}`] : []), definitionName, definitionName.replace(/^v[12]\//, ""), definitionName.replace(/^serde_json\//, ""), `v2/${definitionName}`,