@meistrari/agent-core 0.2.0-experimental.0 → 0.2.0-experimental.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -0
- package/docs/consumer-capability-matrix.md +374 -0
- package/package.json +17 -10
- package/scripts/generate-codex-protocol.ts +711 -0
- package/src/agents/agent-operation.ts +8 -0
- package/src/agents/agent-provider.ts +3 -0
- package/src/agents/agent-run.ts +14 -2
- package/src/agents/agent-runtime-error.ts +28 -0
- package/src/agents/agent-session-events.ts +33 -0
- package/src/agents/agent-tool-runner.ts +12 -11
- package/src/agents/agent-tool.ts +12 -4
- package/src/agents/author-context.ts +1 -1
- package/src/agents/claude/claude-auth.ts +40 -0
- package/src/agents/claude/claude-browser-turn-metadata.ts +70 -0
- package/src/agents/claude/claude-code-process.ts +98 -0
- package/src/agents/claude/claude-command-mapper.ts +63 -3
- package/src/agents/claude/claude-desktop.ts +55 -0
- package/src/agents/claude/claude-event-mapper.ts +876 -124
- package/src/agents/claude/claude-file-change.ts +128 -0
- package/src/agents/claude/claude-provider.ts +344 -29
- package/src/agents/claude/claude-run.ts +922 -91
- package/src/agents/claude/claude-selected-repository-instructions.ts +91 -0
- package/src/agents/claude/claude-skill-catalog.ts +75 -0
- package/src/agents/claude/claude-steered-notifications.ts +180 -0
- package/src/agents/claude/claude-tool-mapper.ts +81 -2
- package/src/agents/claude/claude-workspace-skills.ts +87 -0
- package/src/agents/claude/index.ts +2 -0
- package/src/agents/codex/codex-auth-mapper.ts +27 -0
- package/src/agents/codex/codex-auth.ts +19 -0
- package/src/agents/codex/codex-browser-auth-broker.ts +320 -0
- package/src/agents/codex/codex-browser-auth-status.ts +55 -0
- package/src/agents/codex/codex-command-mapper.ts +38 -3
- package/src/agents/codex/codex-event-mapper.ts +375 -64
- package/src/agents/codex/codex-json-rpc-client.ts +66 -21
- package/src/agents/codex/codex-provider.ts +118 -236
- package/src/agents/codex/codex-run-auth.ts +113 -0
- package/src/agents/codex/codex-run.ts +428 -48
- package/src/agents/codex/codex-tool-mapper.ts +2 -2
- package/src/agents/codex/codex.errors.ts +8 -0
- package/src/agents/codex/generated/meta.gen.ts +144 -6
- package/src/agents/codex/generated/namespaces.gen.ts +92 -3
- package/src/agents/codex/generated/schema.gen.ts +24698 -15638
- package/src/agents/codex/lazy-codex-run-auth.ts +47 -0
- package/src/agents/index.ts +5 -0
- package/src/agents/input-attachment-preparation.ts +13 -0
- package/src/agents/mcp-tool-content.ts +82 -0
- package/src/agents/model-rebinding-run.ts +14 -3
- package/src/agents/selected-repository-instructions.ts +202 -0
- package/src/agents/session-context.ts +41 -0
- package/src/agents/subscription-token-refresh.ts +73 -0
- package/src/agents/user-input-request.ts +19 -20
- package/src/errors/index.ts +1 -1
- package/src/errors/validation-error.ts +6 -0
- package/src/protocol/agent-catalog.ts +159 -0
- package/src/protocol/agent-command.ts +54 -7
- package/src/protocol/agent-content.ts +36 -16
- package/src/protocol/agent-event.ts +210 -73
- package/src/protocol/agent-identity.ts +29 -0
- package/src/protocol/agent-json.ts +122 -8
- package/src/protocol/agent-metadata.ts +2 -2
- package/src/protocol/agent-model.ts +13 -1
- package/src/protocol/agent-notification.ts +110 -0
- package/src/protocol/agent-presentation.ts +71 -20
- package/src/protocol/agent-tool.ts +66 -10
- package/src/protocol/agent-usage.ts +4 -1
- package/src/protocol/agent-user-input.ts +4 -4
- package/src/protocol/agent-work.ts +12 -10
- package/src/protocol/index.ts +2 -0
- package/src/protocol/rejected-keys.ts +11 -0
- package/src/provenance.gen.ts +3 -3
- package/src/steps/contract.ts +4 -1
- package/src/steps/projector.ts +17 -1
- package/src/supervisor/agent-provider-factory.ts +60 -5
- package/src/supervisor/bootstrap-binder.ts +3 -2
- package/src/supervisor/index.ts +1 -1
- package/src/supervisor/persistence/supervisor-store.ts +22 -8
- package/src/supervisor/runtime-handler.ts +30 -2
- package/src/supervisor-protocol/agent-event-wrapper.ts +30 -0
- package/src/supervisor-protocol/bootstrap.ts +4 -1
- package/src/supervisor-protocol/command-body.ts +4 -1
- package/src/supervisor-protocol/envelopes/supervisor-to-control-plane.ts +1 -0
- package/src/testing/in-memory-runtime-control-plane.ts +118 -12
- package/src/testing/index.ts +1 -1
- package/src/worker-runtime-client/command-pump.ts +85 -10
- package/src/worker-runtime-client/connection-attempt.ts +95 -20
- package/src/worker-runtime-client/frame-processor.ts +242 -58
- package/src/worker-runtime-client/index.ts +8 -2
- package/src/worker-runtime-client/ports.ts +34 -2
- package/src/worker-runtime-client/rpc-request-manager.ts +152 -107
- package/src/worker-runtime-client/sandbox-connection-runtime.ts +72 -35
|
@@ -0,0 +1,711 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import type { JSONSchema } from 'json-schema-to-typescript'
|
|
3
|
+
import { spawnSync } from 'node:child_process'
|
|
4
|
+
import {
|
|
5
|
+
existsSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
mkdtempSync,
|
|
8
|
+
readdirSync,
|
|
9
|
+
readFileSync,
|
|
10
|
+
rmSync,
|
|
11
|
+
writeFileSync,
|
|
12
|
+
} from 'node:fs'
|
|
13
|
+
import { tmpdir } from 'node:os'
|
|
14
|
+
import { basename, dirname, join, relative, resolve } from 'node:path'
|
|
15
|
+
import { fileURLToPath } from 'node:url'
|
|
16
|
+
import { compile } from 'json-schema-to-typescript'
|
|
17
|
+
|
|
18
|
+
type JsonSchemaObject = JSONSchema & { [key: string]: unknown }
|
|
19
|
+
interface SchemaMap { [key: string]: JSONSchema }
|
|
20
|
+
|
|
21
|
+
interface JsonSchemaFile {
|
|
22
|
+
path: string
|
|
23
|
+
namespace: string | undefined
|
|
24
|
+
fileName: string
|
|
25
|
+
qualifiedName: string
|
|
26
|
+
exportName: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface MethodEntry {
|
|
30
|
+
method: string
|
|
31
|
+
paramsType: string | undefined
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface ResponseEntry {
|
|
35
|
+
method: string
|
|
36
|
+
responseType: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
40
|
+
const codexCliVersion = '0.156.1'
|
|
41
|
+
const upstreamRef = '3d2ee51ca2d5db578f328aa75e20aa22c0197c9a'
|
|
42
|
+
const upstreamSchemaRoot = generateCodexSchemas()
|
|
43
|
+
const upstreamJsonDir = join(upstreamSchemaRoot, 'json')
|
|
44
|
+
const upstreamTypescriptDir = join(upstreamSchemaRoot, 'typescript')
|
|
45
|
+
const generatedDir = join(root, 'src', 'agents', 'codex', 'generated')
|
|
46
|
+
|
|
47
|
+
const MANUAL_SCHEMAS = {
|
|
48
|
+
GetAuthStatusParams: {
|
|
49
|
+
type: 'object',
|
|
50
|
+
title: 'GetAuthStatusParams',
|
|
51
|
+
properties: {
|
|
52
|
+
includeToken: { anyOf: [{ type: 'boolean' }, { type: 'null' }] },
|
|
53
|
+
refreshToken: { anyOf: [{ type: 'boolean' }, { type: 'null' }] },
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
GetConversationSummaryParams: {
|
|
57
|
+
title: 'GetConversationSummaryParams',
|
|
58
|
+
oneOf: [
|
|
59
|
+
{
|
|
60
|
+
type: 'object',
|
|
61
|
+
properties: { rolloutPath: { type: 'string' } },
|
|
62
|
+
required: ['rolloutPath'],
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
type: 'object',
|
|
66
|
+
properties: { conversationId: { type: 'string' } },
|
|
67
|
+
required: ['conversationId'],
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
GetConversationSummaryResponse: {
|
|
72
|
+
type: 'object',
|
|
73
|
+
title: 'GetConversationSummaryResponse',
|
|
74
|
+
properties: { summary: {} },
|
|
75
|
+
required: ['summary'],
|
|
76
|
+
},
|
|
77
|
+
GitDiffToRemoteParams: {
|
|
78
|
+
type: 'object',
|
|
79
|
+
title: 'GitDiffToRemoteParams',
|
|
80
|
+
properties: { cwd: { type: 'string' } },
|
|
81
|
+
required: ['cwd'],
|
|
82
|
+
},
|
|
83
|
+
GitDiffToRemoteResponse: {
|
|
84
|
+
type: 'object',
|
|
85
|
+
title: 'GitDiffToRemoteResponse',
|
|
86
|
+
properties: {
|
|
87
|
+
sha: { type: 'string' },
|
|
88
|
+
diff: { type: 'string' },
|
|
89
|
+
},
|
|
90
|
+
required: ['sha', 'diff'],
|
|
91
|
+
},
|
|
92
|
+
GetAuthStatusResponse: {
|
|
93
|
+
type: 'object',
|
|
94
|
+
title: 'GetAuthStatusResponse',
|
|
95
|
+
properties: {
|
|
96
|
+
authMethod: { anyOf: [{}, { type: 'null' }] },
|
|
97
|
+
authToken: { anyOf: [{ type: 'string' }, { type: 'null' }] },
|
|
98
|
+
requiresOpenaiAuth: { anyOf: [{ type: 'boolean' }, { type: 'null' }] },
|
|
99
|
+
},
|
|
100
|
+
required: ['authMethod', 'authToken', 'requiresOpenaiAuth'],
|
|
101
|
+
},
|
|
102
|
+
} satisfies SchemaMap
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
await generateProtocolTypes()
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
rmSync(upstreamSchemaRoot, { recursive: true, force: true })
|
|
109
|
+
}
|
|
110
|
+
console.log(`Generated Codex ${codexCliVersion} experimental app-server protocol types at ${upstreamRef}`)
|
|
111
|
+
|
|
112
|
+
async function generateProtocolTypes(): Promise<void> {
|
|
113
|
+
if (!existsSync(upstreamJsonDir))
|
|
114
|
+
throw new Error(`Codex JSON schema fixtures not found: ${upstreamJsonDir}`)
|
|
115
|
+
if (!existsSync(upstreamTypescriptDir))
|
|
116
|
+
throw new Error(`Codex TypeScript method fixtures not found: ${upstreamTypescriptDir}`)
|
|
117
|
+
|
|
118
|
+
rmSync(generatedDir, { recursive: true, force: true })
|
|
119
|
+
mkdirSync(generatedDir, { recursive: true })
|
|
120
|
+
|
|
121
|
+
const jsonSchemaFiles = listJsonSchemaFiles(upstreamJsonDir)
|
|
122
|
+
const exportNameByQualifiedName = new Map(
|
|
123
|
+
jsonSchemaFiles.map(file => [file.qualifiedName, file.exportName]),
|
|
124
|
+
)
|
|
125
|
+
const aggregateSchemas: SchemaMap = {}
|
|
126
|
+
|
|
127
|
+
for (const file of jsonSchemaFiles) {
|
|
128
|
+
const parsed = parseSchemaFile(file.path)
|
|
129
|
+
const localDefinitionNames = new Map(
|
|
130
|
+
Object.keys(parsed.definitions ?? {}).map(definitionName => [
|
|
131
|
+
definitionName,
|
|
132
|
+
`${file.exportName}__${definitionName.replace(/[^A-Z0-9]/gi, '')}`,
|
|
133
|
+
]),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
for (const [definitionName, definitionSchema] of Object.entries(parsed.definitions ?? {})) {
|
|
137
|
+
const exportName = localDefinitionNames.get(definitionName)
|
|
138
|
+
if (!exportName)
|
|
139
|
+
throw new Error(`Missing local definition export name for ${definitionName}`)
|
|
140
|
+
aggregateSchemas[exportName] = namedSchema(
|
|
141
|
+
exportName,
|
|
142
|
+
normalizeSchema(
|
|
143
|
+
rewriteExternalRefs(
|
|
144
|
+
definitionSchema,
|
|
145
|
+
localDefinitionNames,
|
|
146
|
+
file.namespace,
|
|
147
|
+
exportNameByQualifiedName,
|
|
148
|
+
),
|
|
149
|
+
),
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const topLevelSchema = Object.fromEntries(
|
|
154
|
+
Object.entries(parsed).filter(([key]) => key !== 'definitions'),
|
|
155
|
+
)
|
|
156
|
+
aggregateSchemas[file.exportName] = namedSchema(
|
|
157
|
+
file.exportName,
|
|
158
|
+
normalizeSchema(
|
|
159
|
+
rewriteExternalRefs(
|
|
160
|
+
topLevelSchema,
|
|
161
|
+
localDefinitionNames,
|
|
162
|
+
file.namespace,
|
|
163
|
+
exportNameByQualifiedName,
|
|
164
|
+
),
|
|
165
|
+
),
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
for (const [name, schema] of Object.entries(MANUAL_SCHEMAS)) {
|
|
170
|
+
if (!(name in aggregateSchemas))
|
|
171
|
+
aggregateSchemas[name] = namedSchema(name, normalizeSchema(schema))
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const generatedSchemaNames = new Set(Object.keys(aggregateSchemas))
|
|
175
|
+
const schemaSource = await compile(
|
|
176
|
+
{
|
|
177
|
+
title: 'CodexSchema',
|
|
178
|
+
type: 'object',
|
|
179
|
+
additionalProperties: true,
|
|
180
|
+
definitions: aggregateSchemas,
|
|
181
|
+
},
|
|
182
|
+
'CodexSchema',
|
|
183
|
+
{
|
|
184
|
+
bannerComment: '',
|
|
185
|
+
unreachableDefinitions: true,
|
|
186
|
+
style: {
|
|
187
|
+
bracketSpacing: true,
|
|
188
|
+
printWidth: 120,
|
|
189
|
+
semi: true,
|
|
190
|
+
singleQuote: false,
|
|
191
|
+
tabWidth: 2,
|
|
192
|
+
trailingComma: 'all',
|
|
193
|
+
useTabs: false,
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
writeFileSync(
|
|
199
|
+
join(generatedDir, 'schema.gen.ts'),
|
|
200
|
+
[
|
|
201
|
+
'/* eslint-disable style/indent, style/member-delimiter-style, style/quotes, style/semi, style/operator-linebreak, style/indent-binary-ops */',
|
|
202
|
+
'// This file is generated by scripts/generate-codex-protocol.ts. Do not edit manually.',
|
|
203
|
+
`// Upstream Codex protocol ref: ${upstreamRef}`,
|
|
204
|
+
'',
|
|
205
|
+
schemaSource.trim(),
|
|
206
|
+
'',
|
|
207
|
+
].join('\n'),
|
|
208
|
+
'utf8',
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
const clientRequests = parseRequestEntries(readFileSync(join(upstreamTypescriptDir, 'ClientRequest.ts'), 'utf8'))
|
|
212
|
+
const clientNotifications = parseNotificationEntries(readFileSync(join(upstreamTypescriptDir, 'ClientNotification.ts'), 'utf8'))
|
|
213
|
+
const serverRequests = parseRequestEntries(readFileSync(join(upstreamTypescriptDir, 'ServerRequest.ts'), 'utf8'))
|
|
214
|
+
const serverNotifications = parseNotificationEntries(readFileSync(join(upstreamTypescriptDir, 'ServerNotification.ts'), 'utf8'))
|
|
215
|
+
|
|
216
|
+
writeFileSync(
|
|
217
|
+
join(generatedDir, 'meta.gen.ts'),
|
|
218
|
+
renderMeta({
|
|
219
|
+
upstreamRef,
|
|
220
|
+
generatedSchemaNames,
|
|
221
|
+
clientRequests,
|
|
222
|
+
clientNotifications,
|
|
223
|
+
serverRequests,
|
|
224
|
+
serverNotifications,
|
|
225
|
+
}),
|
|
226
|
+
'utf8',
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
writeFileSync(
|
|
230
|
+
join(generatedDir, 'namespaces.gen.ts'),
|
|
231
|
+
renderNamespaces(upstreamRef, jsonSchemaFiles),
|
|
232
|
+
'utf8',
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function generateCodexSchemas(): string {
|
|
237
|
+
const dir = mkdtempSync(join(tmpdir(), 'codex-protocol-'))
|
|
238
|
+
try {
|
|
239
|
+
const jsonDir = join(dir, 'json')
|
|
240
|
+
const typescriptDir = join(dir, 'typescript')
|
|
241
|
+
mkdirSync(jsonDir, { recursive: true })
|
|
242
|
+
mkdirSync(typescriptDir, { recursive: true })
|
|
243
|
+
|
|
244
|
+
const executable = process.env.CODEX_EXECUTABLE
|
|
245
|
+
if (executable)
|
|
246
|
+
assertCodexVersion(executable)
|
|
247
|
+
const command = executable ?? 'bunx'
|
|
248
|
+
const prefix = executable ? [] : [`@openai/codex@${codexCliVersion}`]
|
|
249
|
+
run(command, [...prefix, 'app-server', 'generate-json-schema', '--out', jsonDir, '--experimental'])
|
|
250
|
+
run(command, [...prefix, 'app-server', 'generate-ts', '--out', typescriptDir, '--experimental'])
|
|
251
|
+
return dir
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
rmSync(dir, { recursive: true, force: true })
|
|
255
|
+
throw error
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function assertCodexVersion(executable: string): void {
|
|
260
|
+
const result = spawnSync(executable, ['--version'], { encoding: 'utf8' })
|
|
261
|
+
const expected = `codex-cli ${codexCliVersion}`
|
|
262
|
+
if (result.status !== 0 || result.stdout.trim() !== expected)
|
|
263
|
+
throw new Error(`CODEX_EXECUTABLE must report ${expected}.`)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function run(command: string, args: string[]): void {
|
|
267
|
+
const result = spawnSync(command, args, { encoding: 'utf8' })
|
|
268
|
+
if (result.status !== 0) {
|
|
269
|
+
process.stderr.write(result.stdout)
|
|
270
|
+
process.stderr.write(result.stderr)
|
|
271
|
+
throw new Error(`${command} ${args.join(' ')} failed`)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function parseSchemaFile(path: string): JsonSchemaObject {
|
|
276
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8'))
|
|
277
|
+
if (!isJsonObject(parsed))
|
|
278
|
+
throw new Error(`Codex schema fixture must be an object: ${path}`)
|
|
279
|
+
return parsed
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function listJsonSchemaFiles(dir: string): JsonSchemaFile[] {
|
|
283
|
+
return walk(dir)
|
|
284
|
+
.filter(path => path.endsWith('.json') && !basename(path).startsWith('codex_app_server_protocol.'))
|
|
285
|
+
.map((path) => {
|
|
286
|
+
const qualifiedName = relative(dir, path).replace(/\\/g, '/').replace(/\.json$/, '')
|
|
287
|
+
const parts = qualifiedName.split('/')
|
|
288
|
+
const namespace = parts.length > 1 ? parts[0] : undefined
|
|
289
|
+
return {
|
|
290
|
+
path,
|
|
291
|
+
namespace,
|
|
292
|
+
fileName: basename(path),
|
|
293
|
+
qualifiedName,
|
|
294
|
+
exportName: exportNameForQualifiedName(qualifiedName),
|
|
295
|
+
}
|
|
296
|
+
})
|
|
297
|
+
.sort((left, right) => left.exportName.localeCompare(right.exportName))
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function walk(dir: string): string[] {
|
|
301
|
+
const entries: string[] = []
|
|
302
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
303
|
+
const path = join(dir, entry.name)
|
|
304
|
+
if (entry.isDirectory())
|
|
305
|
+
entries.push(...walk(path))
|
|
306
|
+
else entries.push(path)
|
|
307
|
+
}
|
|
308
|
+
return entries
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function exportNameForQualifiedName(qualifiedName: string): string {
|
|
312
|
+
const parts = qualifiedName.split('/')
|
|
313
|
+
if (parts.length === 1)
|
|
314
|
+
return parts[0] ?? qualifiedName
|
|
315
|
+
const namespacePrefix = parts
|
|
316
|
+
.slice(0, -1)
|
|
317
|
+
.flatMap(part => part.split(/[^A-Z0-9]+/i))
|
|
318
|
+
.filter(segment => segment.length > 0)
|
|
319
|
+
.map(toPascalSegment)
|
|
320
|
+
.join('')
|
|
321
|
+
return `${namespacePrefix}${parts[parts.length - 1] ?? ''}`
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function normalizeSchema(value: unknown): JSONSchema {
|
|
325
|
+
return stripNullDefaults(normalizeNullableTypes(value)) as JSONSchema
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function namedSchema(name: string, schema: JSONSchema): JSONSchema {
|
|
329
|
+
if (!isJsonObject(schema))
|
|
330
|
+
return schema
|
|
331
|
+
return { ...schema, title: name }
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function normalizeNullableTypes(value: unknown): unknown {
|
|
335
|
+
if (Array.isArray(value))
|
|
336
|
+
return value.map(normalizeNullableTypes)
|
|
337
|
+
if (!isJsonObject(value))
|
|
338
|
+
return value
|
|
339
|
+
|
|
340
|
+
const normalizedObject: JsonSchemaObject = Object.fromEntries(
|
|
341
|
+
Object.entries(value).map(([key, child]) => [key, normalizeNullableTypes(child)]),
|
|
342
|
+
)
|
|
343
|
+
const typeValue = normalizedObject.type
|
|
344
|
+
if (!Array.isArray(typeValue))
|
|
345
|
+
return normalizedObject
|
|
346
|
+
|
|
347
|
+
const normalizedTypes = typeValue.filter(entry => typeof entry === 'string')
|
|
348
|
+
if (normalizedTypes.length !== typeValue.length || !normalizedTypes.includes('null'))
|
|
349
|
+
return normalizedObject
|
|
350
|
+
const nonNullTypes = normalizedTypes.filter(entry => entry !== 'null')
|
|
351
|
+
if (nonNullTypes.length !== 1)
|
|
352
|
+
return normalizedObject
|
|
353
|
+
|
|
354
|
+
const withoutType = { ...normalizedObject }
|
|
355
|
+
delete withoutType.type
|
|
356
|
+
return {
|
|
357
|
+
anyOf: [
|
|
358
|
+
{
|
|
359
|
+
...withoutType,
|
|
360
|
+
type: nonNullTypes[0],
|
|
361
|
+
},
|
|
362
|
+
{ type: 'null' },
|
|
363
|
+
],
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function stripNullDefaults(value: unknown): unknown {
|
|
368
|
+
if (Array.isArray(value))
|
|
369
|
+
return value.map(stripNullDefaults)
|
|
370
|
+
if (!isJsonObject(value))
|
|
371
|
+
return value
|
|
372
|
+
return Object.fromEntries(
|
|
373
|
+
Object.entries(value)
|
|
374
|
+
.filter(([key, child]) => !(key === 'default' && child === null))
|
|
375
|
+
.map(([key, child]) => [key, stripNullDefaults(child)]),
|
|
376
|
+
)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function rewriteExternalRefs(
|
|
380
|
+
value: unknown,
|
|
381
|
+
localDefinitionNames: Map<string, string>,
|
|
382
|
+
currentNamespace: string | undefined,
|
|
383
|
+
exportNameByQualifiedName: Map<string, string>,
|
|
384
|
+
): unknown {
|
|
385
|
+
if (Array.isArray(value)) {
|
|
386
|
+
return value.map(entry =>
|
|
387
|
+
rewriteExternalRefs(entry, localDefinitionNames, currentNamespace, exportNameByQualifiedName),
|
|
388
|
+
)
|
|
389
|
+
}
|
|
390
|
+
if (!isJsonObject(value))
|
|
391
|
+
return value
|
|
392
|
+
|
|
393
|
+
return Object.fromEntries(
|
|
394
|
+
Object.entries(value).map(([key, child]) => {
|
|
395
|
+
if (key === '$ref' && typeof child === 'string' && child.startsWith('#/definitions/')) {
|
|
396
|
+
const definitionName = child.slice('#/definitions/'.length)
|
|
397
|
+
const localRewrite = localDefinitionNames.get(definitionName)
|
|
398
|
+
if (localRewrite)
|
|
399
|
+
return [key, `#/definitions/${localRewrite}`]
|
|
400
|
+
|
|
401
|
+
const candidates = [
|
|
402
|
+
...(currentNamespace ? [`${currentNamespace}/${definitionName}`] : []),
|
|
403
|
+
definitionName,
|
|
404
|
+
definitionName.replace(/^v[12]\//, ''),
|
|
405
|
+
definitionName.replace(/^serde_json\//, ''),
|
|
406
|
+
`v2/${definitionName}`,
|
|
407
|
+
`v1/${definitionName}`,
|
|
408
|
+
`serde_json/${definitionName}`,
|
|
409
|
+
]
|
|
410
|
+
const rewritten = candidates
|
|
411
|
+
.map(candidate => exportNameByQualifiedName.get(candidate))
|
|
412
|
+
.find((candidate): candidate is string => candidate !== undefined)
|
|
413
|
+
if (!rewritten)
|
|
414
|
+
throw new Error(`Missing rewritten definition for ref: ${child}`)
|
|
415
|
+
return [key, `#/definitions/${rewritten}`]
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return [
|
|
419
|
+
key,
|
|
420
|
+
rewriteExternalRefs(child, localDefinitionNames, currentNamespace, exportNameByQualifiedName),
|
|
421
|
+
]
|
|
422
|
+
}),
|
|
423
|
+
)
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function parseRequestEntries(source: string): MethodEntry[] {
|
|
427
|
+
const declaration = extractRequestTypeDeclaration(source)
|
|
428
|
+
const nativeMethods = [...declaration.matchAll(/(?:["']method["']|\bmethod)\s*:\s*["']([^"']+)["']/g)]
|
|
429
|
+
.map(match => match[1])
|
|
430
|
+
.filter((method): method is string => method !== undefined)
|
|
431
|
+
assertUniqueMethods(nativeMethods, 'native request declaration')
|
|
432
|
+
if (nativeMethods.length === 0)
|
|
433
|
+
throw new Error('Codex request declaration contains no method entries')
|
|
434
|
+
|
|
435
|
+
const entryPattern = /\{\s*"method":\s*"([^"]+)",\s*id:\s*RequestId,\s*params(\?)?:([^,{}]+),\s*\}/g
|
|
436
|
+
const entries: MethodEntry[] = []
|
|
437
|
+
for (const match of declaration.matchAll(entryPattern)) {
|
|
438
|
+
const method = match[1]
|
|
439
|
+
const optionalParams = match[2] === '?'
|
|
440
|
+
const rawParamsType = match[3]?.trim()
|
|
441
|
+
if (!method || !rawParamsType)
|
|
442
|
+
throw new Error(`Unable to parse Codex request entry: ${match[0]}`)
|
|
443
|
+
const parsedParamsType = parseSupportedParamsType(rawParamsType)
|
|
444
|
+
const paramsType = optionalParams && !parsedParamsType.includesUndefined
|
|
445
|
+
? `${rawParamsType} | undefined`
|
|
446
|
+
: rawParamsType
|
|
447
|
+
entries.push({ method, paramsType })
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const parsedMethods = entries.map(entry => entry.method)
|
|
451
|
+
assertUniqueMethods(parsedMethods, 'parsed request entries')
|
|
452
|
+
const parsedMethodSet = new Set(parsedMethods)
|
|
453
|
+
const nativeMethodSet = new Set(nativeMethods)
|
|
454
|
+
const missingMethods = nativeMethods.filter(method => !parsedMethodSet.has(method))
|
|
455
|
+
const unexpectedMethods = parsedMethods.filter(method => !nativeMethodSet.has(method))
|
|
456
|
+
if (missingMethods.length > 0 || unexpectedMethods.length > 0) {
|
|
457
|
+
throw new Error([
|
|
458
|
+
'Codex request parsing did not cover the native method set.',
|
|
459
|
+
`Missing: ${missingMethods.join(', ') || 'none'}.`,
|
|
460
|
+
`Unexpected: ${unexpectedMethods.join(', ') || 'none'}.`,
|
|
461
|
+
].join(' '))
|
|
462
|
+
}
|
|
463
|
+
return entries
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function extractRequestTypeDeclaration(source: string): string {
|
|
467
|
+
const declarations = [...source.matchAll(/export type [A-Z]\w*Request\s*=\s*/g)]
|
|
468
|
+
if (declarations.length !== 1)
|
|
469
|
+
throw new Error(`Expected one Codex request type declaration, found ${declarations.length}`)
|
|
470
|
+
const declaration = declarations[0]
|
|
471
|
+
if (declaration?.index === undefined)
|
|
472
|
+
throw new Error('Unable to locate Codex request type declaration')
|
|
473
|
+
const body = source.slice(declaration.index + declaration[0].length).trim()
|
|
474
|
+
if (!body.endsWith(';'))
|
|
475
|
+
throw new Error('Codex request type declaration must end with a semicolon')
|
|
476
|
+
return body.slice(0, -1).trim()
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function assertUniqueMethods(methods: string[], subject: string): void {
|
|
480
|
+
const seen = new Set<string>()
|
|
481
|
+
const duplicates = new Set<string>()
|
|
482
|
+
for (const method of methods) {
|
|
483
|
+
if (seen.has(method))
|
|
484
|
+
duplicates.add(method)
|
|
485
|
+
seen.add(method)
|
|
486
|
+
}
|
|
487
|
+
if (duplicates.size > 0)
|
|
488
|
+
throw new Error(`Duplicate methods in ${subject}: ${[...duplicates].join(', ')}`)
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function parseNotificationEntries(source: string): MethodEntry[] {
|
|
492
|
+
const entryPattern = /"method": "([^"]+)"(?:, "params": (\w+(?: \| null)?))?/g
|
|
493
|
+
const entries: MethodEntry[] = []
|
|
494
|
+
for (const match of source.matchAll(entryPattern)) {
|
|
495
|
+
const method = match[1]
|
|
496
|
+
if (!method)
|
|
497
|
+
continue
|
|
498
|
+
entries.push({ method, paramsType: match[2]?.trim() })
|
|
499
|
+
}
|
|
500
|
+
return entries
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function renderMeta({
|
|
504
|
+
upstreamRef,
|
|
505
|
+
generatedSchemaNames,
|
|
506
|
+
clientRequests,
|
|
507
|
+
clientNotifications,
|
|
508
|
+
serverRequests,
|
|
509
|
+
serverNotifications,
|
|
510
|
+
}: {
|
|
511
|
+
upstreamRef: string
|
|
512
|
+
generatedSchemaNames: Set<string>
|
|
513
|
+
clientRequests: MethodEntry[]
|
|
514
|
+
clientNotifications: MethodEntry[]
|
|
515
|
+
serverRequests: MethodEntry[]
|
|
516
|
+
serverNotifications: MethodEntry[]
|
|
517
|
+
}): string {
|
|
518
|
+
const clientResponses = responseEntries(clientRequests, generatedSchemaNames)
|
|
519
|
+
const serverResponses = responseEntries(serverRequests, generatedSchemaNames)
|
|
520
|
+
return [
|
|
521
|
+
'/* eslint-disable style/indent, style/member-delimiter-style, style/quotes, style/semi, style/quote-props */',
|
|
522
|
+
'// This file is generated by scripts/generate-codex-protocol.ts. Do not edit manually.',
|
|
523
|
+
`// Upstream Codex protocol ref: ${upstreamRef}`,
|
|
524
|
+
'',
|
|
525
|
+
'import type * as CodexSchema from "./schema.gen.js";',
|
|
526
|
+
'',
|
|
527
|
+
renderMethodConstants('CLIENT_REQUEST_METHODS', clientRequests),
|
|
528
|
+
renderMethodConstants('CLIENT_NOTIFICATION_METHODS', clientNotifications),
|
|
529
|
+
renderMethodConstants('SERVER_REQUEST_METHODS', serverRequests),
|
|
530
|
+
renderMethodConstants('SERVER_NOTIFICATION_METHODS', serverNotifications),
|
|
531
|
+
'export type ClientRequestMethod = keyof typeof CLIENT_REQUEST_METHODS;',
|
|
532
|
+
'export type ClientNotificationMethod = keyof typeof CLIENT_NOTIFICATION_METHODS;',
|
|
533
|
+
'export type ServerRequestMethod = keyof typeof SERVER_REQUEST_METHODS;',
|
|
534
|
+
'export type ServerNotificationMethod = keyof typeof SERVER_NOTIFICATION_METHODS;',
|
|
535
|
+
'',
|
|
536
|
+
renderTypeInterface('ClientRequestParamsByMethod', clientRequests, entry =>
|
|
537
|
+
resolveParamsTypeReference(entry.paramsType ?? 'undefined', generatedSchemaNames)),
|
|
538
|
+
renderTypeInterface('ClientRequestResponsesByMethod', clientResponses, entry =>
|
|
539
|
+
renderSchemaTypeReference(entry.responseType)),
|
|
540
|
+
renderTypeInterface('ClientNotificationParamsByMethod', clientNotifications, entry =>
|
|
541
|
+
resolveParamsTypeReference(entry.paramsType ?? 'undefined', generatedSchemaNames)),
|
|
542
|
+
renderTypeInterface('ServerRequestParamsByMethod', serverRequests, entry =>
|
|
543
|
+
resolveParamsTypeReference(entry.paramsType ?? 'undefined', generatedSchemaNames)),
|
|
544
|
+
renderTypeInterface('ServerRequestResponsesByMethod', serverResponses, entry =>
|
|
545
|
+
renderSchemaTypeReference(entry.responseType)),
|
|
546
|
+
renderTypeInterface('ServerNotificationParamsByMethod', serverNotifications, entry =>
|
|
547
|
+
resolveParamsTypeReference(entry.paramsType ?? 'undefined', generatedSchemaNames)),
|
|
548
|
+
].join('\n')
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function renderNamespaces(upstreamRef: string, jsonSchemaFiles: JsonSchemaFile[]): string {
|
|
552
|
+
const namespaceGroups = new Map<string, JsonSchemaFile[]>()
|
|
553
|
+
for (const file of jsonSchemaFiles) {
|
|
554
|
+
if (!file.namespace)
|
|
555
|
+
continue
|
|
556
|
+
const current = namespaceGroups.get(file.namespace) ?? []
|
|
557
|
+
current.push(file)
|
|
558
|
+
namespaceGroups.set(file.namespace, current)
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return [
|
|
562
|
+
'/* eslint-disable style/indent, style/quotes, style/semi, style/quote-props */',
|
|
563
|
+
'// This file is generated by scripts/generate-codex-protocol.ts. Do not edit manually.',
|
|
564
|
+
`// Upstream Codex protocol ref: ${upstreamRef}`,
|
|
565
|
+
'',
|
|
566
|
+
...[...namespaceGroups.entries()]
|
|
567
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
568
|
+
.map(([namespace, files]) => [
|
|
569
|
+
`export const ${namespace.replace(/[^A-Z0-9]/gi, '')} = {`,
|
|
570
|
+
...files
|
|
571
|
+
.sort((left, right) => left.fileName.localeCompare(right.fileName))
|
|
572
|
+
.map(file => ` ${JSON.stringify(file.fileName.replace(/\.json$/, ''))}: ${JSON.stringify(file.exportName)},`),
|
|
573
|
+
'} as const;',
|
|
574
|
+
'',
|
|
575
|
+
].join('\n')),
|
|
576
|
+
].join('\n')
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function responseEntries(requestEntries: MethodEntry[], generatedSchemaNames: Set<string>): ResponseEntry[] {
|
|
580
|
+
return requestEntries.map(entry => ({
|
|
581
|
+
method: entry.method,
|
|
582
|
+
responseType: resolveResponseTypeName(entry.method, entry.paramsType, generatedSchemaNames),
|
|
583
|
+
}))
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function resolveSchemaTypeName(rawTypeName: string, generatedSchemaNames: Set<string>): string {
|
|
587
|
+
if (rawTypeName === 'undefined')
|
|
588
|
+
return 'undefined'
|
|
589
|
+
const resolved = schemaTypeCandidates(rawTypeName, false)
|
|
590
|
+
.find(candidate => generatedSchemaNames.has(candidate))
|
|
591
|
+
if (resolved)
|
|
592
|
+
return resolved
|
|
593
|
+
throw new Error(`Unable to resolve schema type name: ${rawTypeName}`)
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function resolveParamsTypeReference(rawTypeName: string, generatedSchemaNames: Set<string>): string {
|
|
597
|
+
const parsed = parseSupportedParamsType(rawTypeName)
|
|
598
|
+
if (!parsed.typeName)
|
|
599
|
+
return 'undefined'
|
|
600
|
+
|
|
601
|
+
const candidates = parsed.includesNull
|
|
602
|
+
? schemaTypeCandidates(parsed.typeName, true)
|
|
603
|
+
: [
|
|
604
|
+
...schemaTypeCandidates(parsed.typeName, false),
|
|
605
|
+
...(parsed.includesUndefined ? schemaTypeCandidates(parsed.typeName, true) : []),
|
|
606
|
+
]
|
|
607
|
+
const schemaName = candidates.find(candidate => generatedSchemaNames.has(candidate))
|
|
608
|
+
if (!schemaName)
|
|
609
|
+
throw new Error(`Unable to resolve schema type name: ${rawTypeName}`)
|
|
610
|
+
const typeReference = renderSchemaTypeReference(schemaName)
|
|
611
|
+
return parsed.includesUndefined ? `${typeReference} | undefined` : typeReference
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function schemaTypeCandidates(typeName: string, nullable: boolean): string[] {
|
|
615
|
+
return nullable
|
|
616
|
+
? [`V2Nullable${typeName}`, `V1Nullable${typeName}`]
|
|
617
|
+
: [typeName, `V2${typeName}`, `V1${typeName}`, `SerdeJson${typeName}`]
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function parseSupportedParamsType(rawTypeName: string): {
|
|
621
|
+
typeName: string | undefined
|
|
622
|
+
includesNull: boolean
|
|
623
|
+
includesUndefined: boolean
|
|
624
|
+
} {
|
|
625
|
+
const parts = rawTypeName.split(/\s*\|\s*/)
|
|
626
|
+
if (parts.some(part => !/^[A-Z_$][\w$]*$/i.test(part)))
|
|
627
|
+
throw new Error(`Unsupported Codex params type syntax: ${rawTypeName}`)
|
|
628
|
+
if (new Set(parts).size !== parts.length)
|
|
629
|
+
throw new Error(`Duplicate Codex params type member: ${rawTypeName}`)
|
|
630
|
+
|
|
631
|
+
const typeNames = parts.filter(part => part !== 'null' && part !== 'undefined')
|
|
632
|
+
if (typeNames.length > 1)
|
|
633
|
+
throw new Error(`Unsupported Codex params type union: ${rawTypeName}`)
|
|
634
|
+
const includesNull = parts.includes('null')
|
|
635
|
+
const includesUndefined = parts.includes('undefined')
|
|
636
|
+
if (typeNames.length === 0 && (includesNull || !includesUndefined))
|
|
637
|
+
throw new Error(`Unsupported Codex params type union: ${rawTypeName}`)
|
|
638
|
+
return { typeName: typeNames[0], includesNull, includesUndefined }
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function resolveResponseTypeName(method: string, paramsType: string | undefined, generatedSchemaNames: Set<string>): string {
|
|
642
|
+
const overrides: { [method: string]: string } = {
|
|
643
|
+
'account/logout': 'LogoutAccountResponse',
|
|
644
|
+
'account/rateLimits/read': 'GetAccountRateLimitsResponse',
|
|
645
|
+
'account/usage/read': 'GetAccountTokenUsageResponse',
|
|
646
|
+
'account/workspaceMessages/read': 'GetWorkspaceMessagesResponse',
|
|
647
|
+
'config/batchWrite': 'ConfigWriteResponse',
|
|
648
|
+
'config/mcpServer/reload': 'McpServerRefreshResponse',
|
|
649
|
+
'config/value/write': 'ConfigWriteResponse',
|
|
650
|
+
'configRequirements/read': 'ConfigRequirementsReadResponse',
|
|
651
|
+
'externalAgentConfig/import/readHistories': 'ExternalAgentConfigImportHistoriesReadResponse',
|
|
652
|
+
'windowsSandbox/readiness': 'WindowsSandboxReadinessResponse',
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const override = overrides[method]
|
|
656
|
+
if (override)
|
|
657
|
+
return resolveSchemaTypeName(override, generatedSchemaNames)
|
|
658
|
+
|
|
659
|
+
if (paramsType && paramsType !== 'undefined') {
|
|
660
|
+
try {
|
|
661
|
+
const responseType = parseSupportedParamsType(paramsType).typeName?.replace(/Params$/, 'Response')
|
|
662
|
+
if (responseType)
|
|
663
|
+
return resolveSchemaTypeName(responseType, generatedSchemaNames)
|
|
664
|
+
}
|
|
665
|
+
catch {}
|
|
666
|
+
}
|
|
667
|
+
return resolveSchemaTypeName(`${toPascalCaseMethod(method)}Response`, generatedSchemaNames)
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function toPascalCaseMethod(method: string): string {
|
|
671
|
+
return method
|
|
672
|
+
.split('/')
|
|
673
|
+
.flatMap(segment => segment.split(/(?=[A-Z])/))
|
|
674
|
+
.flatMap(segment => segment.split(/[-_]/))
|
|
675
|
+
.filter(segment => segment.length > 0)
|
|
676
|
+
.map(toPascalSegment)
|
|
677
|
+
.join('')
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
function toPascalSegment(segment: string): string {
|
|
681
|
+
const first = segment[0]
|
|
682
|
+
if (!first)
|
|
683
|
+
return ''
|
|
684
|
+
return `${first.toUpperCase()}${segment.slice(1)}`
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function renderMethodConstants(name: string, entries: MethodEntry[]): string {
|
|
688
|
+
return [
|
|
689
|
+
`export const ${name} = {`,
|
|
690
|
+
...entries.map(entry => ` ${JSON.stringify(entry.method)}: ${JSON.stringify(entry.method)},`),
|
|
691
|
+
'} as const;',
|
|
692
|
+
'',
|
|
693
|
+
].join('\n')
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function renderTypeInterface<TEntry extends { method: string }>(name: string, entries: TEntry[], typeName: (entry: TEntry) => string): string {
|
|
697
|
+
return [
|
|
698
|
+
`export interface ${name} {`,
|
|
699
|
+
...entries.map(entry => ` readonly ${JSON.stringify(entry.method)}: ${typeName(entry)};`),
|
|
700
|
+
'}',
|
|
701
|
+
'',
|
|
702
|
+
].join('\n')
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
function renderSchemaTypeReference(schemaName: string): string {
|
|
706
|
+
return schemaName === 'undefined' ? 'undefined' : `CodexSchema.${schemaName}`
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function isJsonObject(value: unknown): value is { [key: string]: unknown } {
|
|
710
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
711
|
+
}
|