agentHost: upstream reducer logic from AHP

Goes with https://github.com/microsoft/agent-host-protocol/pull/11
This commit is contained in:
Connor Peet
2026-03-18 13:09:11 -07:00
parent a185beaf10
commit 60d6da9614
29 changed files with 974 additions and 938 deletions

View File

@@ -9,12 +9,10 @@
// npx tsx scripts/sync-agent-host-protocol.ts
//
// Transformations applied:
// 1. Converts `const enum` to `const` object + string literal union (VS Code
// tsconfig uses `preserveConstEnums` which makes `const enum` nominal).
// 2. Converts 2-space indentation to tabs.
// 3. Merges duplicate imports from the same module.
// 4. Formats with the project's tsfmt.json settings.
// 5. Adds Microsoft copyright header.
// 1. Converts 2-space indentation to tabs.
// 2. Merges duplicate imports from the same module.
// 3. Formats with the project's tsfmt.json settings.
// 4. Adds Microsoft copyright header.
//
// URI stays as `string` (the protocol's canonical representation). VS Code code
// should call `URI.parse()` at point-of-use where a URI class is needed.
@@ -70,6 +68,8 @@ const BANNER = '// allow-any-unicode-comment-file\n// DO NOT EDIT -- auto-genera
const FILES: { src: string; dest: string }[] = [
{ src: 'state.ts', dest: 'state.ts' },
{ src: 'actions.ts', dest: 'actions.ts' },
{ src: 'action-origin.generated.ts', dest: 'action-origin.generated.ts' },
{ src: 'reducers.ts', dest: 'reducers.ts' },
{ src: 'commands.ts', dest: 'commands.ts' },
{ src: 'errors.ts', dest: 'errors.ts' },
{ src: 'notifications.ts', dest: 'notifications.ts' },
@@ -168,99 +168,14 @@ function mergeDuplicateImports(content: string): string {
}).join('\n');
}
// Global enum definitions collected from all files before per-file processing
let globalEnumDefs = new Map<string, Map<string, string>>();
function collectAllEnumDefs(): void {
globalEnumDefs = new Map();
for (const file of FILES) {
const srcPath = path.join(TYPES_DIR, file.src);
if (!fs.existsSync(srcPath)) {
continue;
}
const content = fs.readFileSync(srcPath, 'utf-8');
content.replace(
/export const enum (\w+) \{([^}]+)\}/g,
(_match, name: string, body: string) => {
const members = new Map<string, string>();
for (const line of body.split('\n')) {
const memberMatch = line.match(/^\s*(\w+)\s*=\s*'([^']+)'/);
if (memberMatch) {
members.set(memberMatch[1], memberMatch[2]);
}
}
if (members.size > 0) {
globalEnumDefs.set(name, members);
}
return _match;
}
);
}
}
/**
* Converts `const enum Foo { A = 'a', B = 'b' }` into:
* ```
* export const Foo = { A: 'a', B: 'b' } as const;
* export type Foo = typeof Foo[keyof typeof Foo];
* ```
* Then replaces `Foo.A` in type positions with the string literal `'a'`,
* using the global enum definitions collected from all protocol files.
*/
function convertConstEnums(content: string): string {
// Replace the const enum declarations in this file
content = content.replace(
/export const enum (\w+) \{([^}]+)\}/g,
(_match, name: string) => {
const members = globalEnumDefs.get(name);
if (!members) {
return _match;
}
const objEntries = [...members.entries()].map(([k, v]) => ` ${k}: '${v}'`).join(',\n');
return `export const ${name} = {\n${objEntries},\n} as const;\nexport type ${name} = typeof ${name}[keyof typeof ${name}];`;
}
);
// Replace Enum.Member references with their resolved string literals
for (const [enumName, members] of globalEnumDefs) {
for (const [memberName, value] of members) {
const ref = `${enumName}.${memberName}`;
content = content.split(ref).join(`'${value}'`);
}
}
// Remove value imports of enums that are no longer referenced as values
content = content.replace(
/import \{([^}]+)\} from '([^']+)';/g,
(_match, names: string, from: string) => {
const parts = names.split(',').map((s: string) => s.trim()).filter((s: string) => s.length > 0);
const remaining = parts.filter((name: string) => {
if (!globalEnumDefs.has(name)) {
return true;
}
const uses = content.split(name).length - 1;
return uses > 1;
});
if (remaining.length === 0) {
return '';
}
if (remaining.length === parts.length) {
return _match;
}
return `import { ${remaining.join(', ')} } from '${from}';`;
}
);
return content;
}
function processFile(src: string, dest: string, commitHash: string): void {
let content = fs.readFileSync(src, 'utf-8');
content = stripExistingHeader(content);
// Convert `const enum` to plain `const` object + string literal union
content = convertConstEnums(content);
// Merge duplicate imports from the same module
content = mergeDuplicateImports(content);
@@ -297,10 +212,6 @@ function main() {
console.log(` Dest: ${DEST_DIR}`);
console.log();
// Collect all enum definitions across all protocol files
collectAllEnumDefs();
console.log(` Collected ${globalEnumDefs.size} const enums`);
// Copy protocol files
for (const file of FILES) {
const srcPath = path.join(TYPES_DIR, file.src);