Skip to content

Commit cd7d7a2

Browse files
ithiria894codex
andcommitted
feat: add harness adapter registry for multi-harness support
Co-Authored-By: Codex (GPT-5.5) <noreply@openai.com>
1 parent a4f8f92 commit cd7d7a2

1 file changed

Lines changed: 95 additions & 0 deletions

File tree

src/harness/registry.mjs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { readdir } from "node:fs/promises";
2+
import { join } from "node:path";
3+
import { pathToFileURL } from "node:url";
4+
import { validateAdapter } from "./interface.mjs";
5+
6+
const adapters = new Map();
7+
let discoveryPromise = null;
8+
9+
function adapterSummary(adapter) {
10+
return {
11+
id: adapter.id,
12+
displayName: adapter.displayName,
13+
shortName: adapter.shortName,
14+
icon: adapter.icon,
15+
};
16+
}
17+
18+
async function importAdapterModule(filePath) {
19+
const mod = await import(pathToFileURL(filePath).href);
20+
const candidates = [];
21+
22+
if (mod.default) candidates.push(mod.default);
23+
if (mod.adapter) candidates.push(mod.adapter);
24+
if (Array.isArray(mod.adapters)) candidates.push(...mod.adapters);
25+
26+
for (const candidate of candidates) {
27+
registerAdapter(candidate);
28+
}
29+
}
30+
31+
async function discoverAdapters() {
32+
const adaptersDir = join(import.meta.dirname, "adapters");
33+
34+
let entries;
35+
try {
36+
entries = await readdir(adaptersDir, { withFileTypes: true });
37+
} catch {
38+
return;
39+
}
40+
41+
await Promise.all(
42+
entries
43+
.filter(entry => entry.isFile() && entry.name.endsWith(".mjs"))
44+
.map(entry => importAdapterModule(join(adaptersDir, entry.name)))
45+
);
46+
}
47+
48+
async function ensureDiscovered() {
49+
if (!discoveryPromise) discoveryPromise = discoverAdapters();
50+
await discoveryPromise;
51+
}
52+
53+
/**
54+
* Register one harness adapter.
55+
*
56+
* @param {import("./interface.mjs").HarnessAdapter} adapter
57+
* @returns {import("./interface.mjs").HarnessAdapter}
58+
*/
59+
export function registerAdapter(adapter) {
60+
const validated = validateAdapter(adapter);
61+
adapters.set(validated.id, validated);
62+
return validated;
63+
}
64+
65+
/**
66+
* Return a registered adapter by id.
67+
*
68+
* @param {string} id
69+
* @returns {Promise<import("./interface.mjs").HarnessAdapter>}
70+
*/
71+
export async function getAdapter(id) {
72+
await ensureDiscovered();
73+
const adapter = adapters.get(id);
74+
if (!adapter) throw new Error(`Unknown harness adapter: ${id}`);
75+
return adapter;
76+
}
77+
78+
/**
79+
* List registered adapter descriptors.
80+
*
81+
* @returns {Promise<Array<{ id: string, displayName: string, shortName: string, icon: string }>>}
82+
*/
83+
export async function listAdapters() {
84+
await ensureDiscovered();
85+
return [...adapters.values()].map(adapterSummary);
86+
}
87+
88+
/**
89+
* Default harness adapter id.
90+
*
91+
* @returns {string}
92+
*/
93+
export function getDefaultAdapterId() {
94+
return "claude";
95+
}

0 commit comments

Comments
 (0)