feat: surface codex thread collaboration capabilities
This commit is contained in:
@@ -57,6 +57,10 @@ function parseCodexVersion(raw) {
|
||||
return match?.[1] || "unknown";
|
||||
}
|
||||
|
||||
function stripTrailingWhitespace(text) {
|
||||
return String(text || "").replace(/[ \t]+$/gm, "");
|
||||
}
|
||||
|
||||
async function listFiles(root) {
|
||||
const entries = await readdir(root, { withFileTypes: true });
|
||||
const files = [];
|
||||
@@ -71,21 +75,106 @@ async function listFiles(root) {
|
||||
return files;
|
||||
}
|
||||
|
||||
function extractProtocolMethodsFromText(text) {
|
||||
const methods = new Set();
|
||||
const pattern = /"([A-Za-z][A-Za-z0-9]*(?:\/[A-Za-z0-9_-]+)+)"/g;
|
||||
const PROTOCOL_METHOD_PREFIXES = [
|
||||
"account",
|
||||
"app",
|
||||
"collaborationMode",
|
||||
"command",
|
||||
"config",
|
||||
"configRequirements",
|
||||
"experimentalFeature",
|
||||
"externalAgentConfig",
|
||||
"feedback",
|
||||
"fs",
|
||||
"fuzzyFileSearch",
|
||||
"hooks",
|
||||
"item",
|
||||
"marketplace",
|
||||
"mcpServer",
|
||||
"mcpServerStatus",
|
||||
"model",
|
||||
"modelProvider",
|
||||
"permissionProfile",
|
||||
"plugin",
|
||||
"process",
|
||||
"rawResponseItem",
|
||||
"remoteControl",
|
||||
"review",
|
||||
"serverRequest",
|
||||
"skills",
|
||||
"thread",
|
||||
"turn",
|
||||
"windows",
|
||||
"windowsSandbox",
|
||||
];
|
||||
|
||||
const PROTOCOL_EXACT_METHODS = new Set([
|
||||
"fuzzyFileSearch",
|
||||
"getAuthStatus",
|
||||
"getConversationSummary",
|
||||
"gitDiffToRemote",
|
||||
"initialize",
|
||||
"initialized",
|
||||
]);
|
||||
|
||||
const THREAD_ITEM_TYPES = new Set([
|
||||
"agentMessage",
|
||||
"collabToolCall",
|
||||
"commandExecution",
|
||||
"contextCompaction",
|
||||
"dynamicToolCall",
|
||||
"enteredReviewMode",
|
||||
"exitedReviewMode",
|
||||
"fileChange",
|
||||
"imageGeneration",
|
||||
"imageView",
|
||||
"mcpToolCall",
|
||||
"plan",
|
||||
"reasoning",
|
||||
"userMessage",
|
||||
"webSearch",
|
||||
]);
|
||||
|
||||
function isProtocolMethod(value) {
|
||||
return (
|
||||
PROTOCOL_EXACT_METHODS.has(value) ||
|
||||
PROTOCOL_METHOD_PREFIXES.some((prefix) => value.startsWith(`${prefix}/`))
|
||||
);
|
||||
}
|
||||
|
||||
function extractQuotedValues(text) {
|
||||
const values = [];
|
||||
const pattern = /"([A-Za-z][A-Za-z0-9_]*(?:\/[A-Za-z0-9_-]+)*)"/g;
|
||||
let match;
|
||||
while ((match = pattern.exec(text))) {
|
||||
const value = match[1];
|
||||
if (/^(thread|turn|item|rawResponseItem|model|modelProvider|experimentalFeature|permissionProfile|process|command|review|account|config|mcpServer|plugin|marketplace|skills|hooks|fs|remoteControl|externalAgentConfig|fuzzyFileSearch|windowsSandbox)\//.test(value)) {
|
||||
values.push(match[1]);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
function extractProtocolMethodsFromText(text) {
|
||||
const methods = new Set();
|
||||
for (const value of extractQuotedValues(text)) {
|
||||
if (isProtocolMethod(value)) {
|
||||
methods.add(value);
|
||||
}
|
||||
}
|
||||
return methods;
|
||||
}
|
||||
|
||||
async function extractProtocolMethods(root) {
|
||||
function extractThreadItemTypesFromText(text) {
|
||||
const itemTypes = new Set();
|
||||
for (const value of extractQuotedValues(text)) {
|
||||
if (THREAD_ITEM_TYPES.has(value)) {
|
||||
itemTypes.add(value);
|
||||
}
|
||||
}
|
||||
return itemTypes;
|
||||
}
|
||||
|
||||
async function extractProtocolArtifacts(root) {
|
||||
const methods = new Set();
|
||||
const itemTypes = new Set();
|
||||
for (const filePath of await listFiles(root)) {
|
||||
if (!/\.(json|ts)$/.test(filePath)) {
|
||||
continue;
|
||||
@@ -94,17 +183,30 @@ async function extractProtocolMethods(root) {
|
||||
for (const method of extractProtocolMethodsFromText(text)) {
|
||||
methods.add(method);
|
||||
}
|
||||
for (const itemType of extractThreadItemTypesFromText(text)) {
|
||||
itemTypes.add(itemType);
|
||||
}
|
||||
}
|
||||
return Array.from(methods).sort();
|
||||
return {
|
||||
methods: Array.from(methods).sort(),
|
||||
itemTypes: Array.from(itemTypes).sort(),
|
||||
};
|
||||
}
|
||||
|
||||
function buildSupportMatrix({ helpText, methods }) {
|
||||
function buildSupportMatrix({ helpText, methods, itemTypes }) {
|
||||
const methodSet = new Set(methods);
|
||||
const itemTypeSet = new Set(itemTypes);
|
||||
return {
|
||||
stdioTransport: /stdio:\/\//.test(helpText) || /stdio/.test(helpText),
|
||||
unixTransport: /unix:\/\//.test(helpText),
|
||||
wsTransport: /ws:\/\/|websocket/i.test(helpText),
|
||||
wsAuth: /--ws-auth/.test(helpText),
|
||||
threadStart: methodSet.has("thread/start"),
|
||||
threadResume: methodSet.has("thread/resume"),
|
||||
threadRead: methodSet.has("thread/read"),
|
||||
threadList: methodSet.has("thread/list"),
|
||||
threadLoadedList: methodSet.has("thread/loaded/list"),
|
||||
threadTurnHistory: methodSet.has("thread/turns/list"),
|
||||
threadInjectItems: methodSet.has("thread/inject_items"),
|
||||
threadRollback: methodSet.has("thread/rollback"),
|
||||
threadArchive: methodSet.has("thread/archive"),
|
||||
@@ -121,6 +223,11 @@ function buildSupportMatrix({ helpText, methods }) {
|
||||
commandExec: methodSet.has("command/exec"),
|
||||
realtimeThread: methods.some((method) => method.startsWith("thread/realtime/")),
|
||||
modelList: methodSet.has("model/list"),
|
||||
appList: methodSet.has("app/list"),
|
||||
appListUpdated: methodSet.has("app/list/updated"),
|
||||
collaborationModeList: methodSet.has("collaborationMode/list"),
|
||||
configRequirementsRead: methodSet.has("configRequirements/read"),
|
||||
mcpServerStatusList: methodSet.has("mcpServerStatus/list"),
|
||||
skillsExtraRoots: methodSet.has("skills/extraRoots/set"),
|
||||
hooksList: methodSet.has("hooks/list"),
|
||||
pluginInstall: methodSet.has("plugin/install"),
|
||||
@@ -197,6 +304,8 @@ function buildSupportMatrix({ helpText, methods }) {
|
||||
methodSet.has("item/commandExecution/outputDelta"),
|
||||
terminalInteraction: methodSet.has("item/commandExecution/terminalInteraction"),
|
||||
fileChangeOutputDelta: methodSet.has("item/fileChange/outputDelta"),
|
||||
threadCollaborationItems: itemTypeSet.has("collabToolCall"),
|
||||
contextCompactionItem: itemTypeSet.has("contextCompaction"),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -216,18 +325,16 @@ async function main() {
|
||||
await mkdir(schemaDir, { recursive: true });
|
||||
await mkdir(typescriptDir, { recursive: true });
|
||||
|
||||
const helpText = run(options.codexBin, ["app-server", "--help"]);
|
||||
const helpText = stripTrailingWhitespace(run(options.codexBin, ["app-server", "--help"]));
|
||||
await writeFile(path.join(snapshotDir, "app-server-help.txt"), helpText, "utf8");
|
||||
|
||||
run(options.codexBin, ["app-server", "generate-json-schema", "--out", schemaDir]);
|
||||
run(options.codexBin, ["app-server", "generate-ts", "--out", typescriptDir]);
|
||||
|
||||
const methods = Array.from(
|
||||
new Set([
|
||||
...(await extractProtocolMethods(schemaDir)),
|
||||
...(await extractProtocolMethods(typescriptDir)),
|
||||
]),
|
||||
).sort();
|
||||
const schemaArtifacts = await extractProtocolArtifacts(schemaDir);
|
||||
const typescriptArtifacts = await extractProtocolArtifacts(typescriptDir);
|
||||
const methods = Array.from(new Set([...schemaArtifacts.methods, ...typescriptArtifacts.methods])).sort();
|
||||
const itemTypes = Array.from(new Set([...schemaArtifacts.itemTypes, ...typescriptArtifacts.itemTypes])).sort();
|
||||
|
||||
const manifest = {
|
||||
generatedAt: new Date().toISOString(),
|
||||
@@ -236,7 +343,8 @@ async function main() {
|
||||
codexBin: options.codexBin,
|
||||
snapshotDir,
|
||||
methods,
|
||||
supports: buildSupportMatrix({ helpText, methods }),
|
||||
itemTypes,
|
||||
supports: buildSupportMatrix({ helpText, methods, itemTypes }),
|
||||
};
|
||||
await writeFile(path.join(snapshotDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
|
||||
console.log(JSON.stringify({ ok: true, snapshotDir, codexVersion, methodCount: methods.length }));
|
||||
|
||||
Reference in New Issue
Block a user