@mono-agent/agent-runtime 0.6.2 → 0.8.0
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 +36 -16
- package/package.json +14 -7
- package/src/agent/approval.js +52 -17
- package/src/agent/sandbox-seam.js +1 -0
- package/src/agent/tools/pi-bridge.js +2 -0
- package/src/agent/tools/shared/ripgrep.js +12 -8
- package/src/ai/index.js +8 -0
- package/src/ai/providers/claude-cli.js +109 -5
- package/src/ai/providers/claude-sandbox.js +71 -0
- package/src/ai/providers/claude-sdk-discovery-worker.js +53 -0
- package/src/ai/providers/claude-sdk-discovery.js +352 -0
- package/src/ai/providers/claude-sdk.js +313 -35
- package/src/ai/providers/codex-app.js +823 -78
- package/src/ai/providers/opencode-app.js +682 -96
- package/src/ai/providers/opencode-server.js +508 -0
- package/src/ai/runtime/capabilities.js +12 -0
- package/src/ai/runtime/context-windows.js +8 -0
- package/src/ai/runtime/registry.js +8 -2
- package/src/ai/runtime/router.js +627 -29
- package/src/ai/types.js +29 -2
- package/src/index.js +6 -0
- package/src/runtime.js +17 -1
- package/types/agent/approval.d.ts +4 -7
- package/types/agent/sandbox-seam.d.ts +5 -0
- package/types/ai/backend.d.ts +16 -0
- package/types/ai/index.d.ts +1 -0
- package/types/ai/providers/claude-cli.d.ts +116 -0
- package/types/ai/providers/claude-sandbox.d.ts +79 -0
- package/types/ai/providers/claude-sdk-discovery-worker.d.ts +1 -0
- package/types/ai/providers/claude-sdk-discovery.d.ts +97 -0
- package/types/ai/providers/claude-sdk.d.ts +81 -5
- package/types/ai/providers/codex-app.d.ts +11 -7
- package/types/ai/providers/opencode-app.d.ts +15 -16
- package/types/ai/providers/opencode-server.d.ts +20 -0
- package/types/ai/runtime/capabilities.d.ts +19 -0
- package/types/ai/runtime/context-windows.d.ts +1 -0
- package/types/ai/runtime/router.d.ts +24 -23
- package/types/ai/types.d.ts +75 -2
- package/types/index.d.ts +1 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
import { normalizeClaudeSdkCatalog } from "./claude-sdk-discovery.js";
|
|
3
|
+
|
|
4
|
+
const abortController = new AbortController();
|
|
5
|
+
let activeQuery = null;
|
|
6
|
+
|
|
7
|
+
async function* emptyInput() {
|
|
8
|
+
// Initialization is the operation. An empty async input keeps the SDK from
|
|
9
|
+
// executing a paid model turn while still opening the control channel.
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function abort() {
|
|
13
|
+
abortController.abort();
|
|
14
|
+
try { activeQuery?.close?.(); } catch { /* best effort */ }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
process.on("message", (message) => {
|
|
18
|
+
if (message && typeof message === "object" && /** @type {any} */ (message).type === "abort") abort();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
async function main() {
|
|
22
|
+
try {
|
|
23
|
+
activeQuery = query({
|
|
24
|
+
prompt: emptyInput(),
|
|
25
|
+
options: /** @type {any} */ ({
|
|
26
|
+
cwd: process.cwd(),
|
|
27
|
+
abortController,
|
|
28
|
+
persistSession: false,
|
|
29
|
+
settingSources: [],
|
|
30
|
+
tools: [],
|
|
31
|
+
mcpServers: {},
|
|
32
|
+
strictMcpConfig: true,
|
|
33
|
+
env: {
|
|
34
|
+
...process.env,
|
|
35
|
+
MCP_CONNECTION_NONBLOCKING: "0",
|
|
36
|
+
},
|
|
37
|
+
}),
|
|
38
|
+
});
|
|
39
|
+
const initialization = await activeQuery.initializationResult();
|
|
40
|
+
const models = normalizeClaudeSdkCatalog(initialization?.models, "discovered");
|
|
41
|
+
process.send?.({ type: "claude_catalog", models });
|
|
42
|
+
} catch {
|
|
43
|
+
// Raw SDK errors may contain paths or account detail. The parent needs
|
|
44
|
+
// only a typed failure signal so it can use the curated cache.
|
|
45
|
+
process.send?.({ type: "claude_catalog_error" });
|
|
46
|
+
process.exitCode = 1;
|
|
47
|
+
} finally {
|
|
48
|
+
try { activeQuery?.close?.(); } catch { /* best effort */ }
|
|
49
|
+
try { process.disconnect?.(); } catch { /* best effort */ }
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
void main();
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import { fork } from "node:child_process";
|
|
2
|
+
import { chmod, mkdir, mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
8
|
+
const CHILD_STOP_TIMEOUT_MS = 300;
|
|
9
|
+
const MAX_MODELS = 64;
|
|
10
|
+
const MAX_DESCRIPTION_CHARS = 320;
|
|
11
|
+
const MODEL_ALIASES = new Set(["default", "opus", "sonnet", "haiku", "fable", "mythos", "inherit"]);
|
|
12
|
+
const SUPPORTED_EFFORTS = new Set(["low", "medium", "high", "xhigh", "max"]);
|
|
13
|
+
const WORKER_PATH = fileURLToPath(new URL("./claude-sdk-discovery-worker.js", import.meta.url));
|
|
14
|
+
|
|
15
|
+
export const CLAUDE_SDK_CATALOG_VERSION = "claude-agent-sdk-0.3.206";
|
|
16
|
+
|
|
17
|
+
/** @typedef {"low"|"medium"|"high"|"xhigh"|"max"} ClaudeSdkEffort */
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {Object} ClaudeSdkCatalogModel
|
|
20
|
+
* @property {string} model Exact model id accepted by the SDK.
|
|
21
|
+
* @property {`claude:${string}`} reference Canonical mono-agent reference.
|
|
22
|
+
* @property {string} displayName
|
|
23
|
+
* @property {string} description
|
|
24
|
+
* @property {readonly ClaudeSdkEffort[]} supportedEfforts
|
|
25
|
+
* @property {boolean} supportsAdaptiveThinking
|
|
26
|
+
* @property {boolean} supportsFastMode
|
|
27
|
+
* @property {"discovered"|"cached"} source
|
|
28
|
+
* @property {typeof CLAUDE_SDK_CATALOG_VERSION} catalogVersion
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
const CURATED_CATALOG = Object.freeze([
|
|
32
|
+
curatedModel("claude-sonnet-5", "Claude Sonnet 5", "Efficient for routine tasks", {
|
|
33
|
+
supportedEfforts: ["low", "medium", "high", "xhigh", "max"],
|
|
34
|
+
supportsAdaptiveThinking: true,
|
|
35
|
+
}),
|
|
36
|
+
curatedModel("claude-opus-4-8[1m]", "Claude Opus 4.8 (1M context)", "Opus 4.8 with the 1M context window", {
|
|
37
|
+
supportedEfforts: ["low", "medium", "high", "xhigh", "max"],
|
|
38
|
+
supportsAdaptiveThinking: true,
|
|
39
|
+
supportsFastMode: true,
|
|
40
|
+
}),
|
|
41
|
+
curatedModel("claude-haiku-4-5-20251001", "Claude Haiku 4.5", "Fastest for quick answers"),
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {string} model
|
|
46
|
+
* @param {string} displayName
|
|
47
|
+
* @param {string} description
|
|
48
|
+
* @param {{supportedEfforts?: ClaudeSdkEffort[], supportsAdaptiveThinking?: boolean, supportsFastMode?: boolean}} [capabilities]
|
|
49
|
+
* @returns {Readonly<ClaudeSdkCatalogModel>}
|
|
50
|
+
*/
|
|
51
|
+
function curatedModel(model, displayName, description, capabilities = {}) {
|
|
52
|
+
return Object.freeze({
|
|
53
|
+
model,
|
|
54
|
+
reference: `claude:${model}`,
|
|
55
|
+
displayName,
|
|
56
|
+
description,
|
|
57
|
+
supportedEfforts: Object.freeze([...(capabilities.supportedEfforts || [])]),
|
|
58
|
+
supportsAdaptiveThinking: capabilities.supportsAdaptiveThinking === true,
|
|
59
|
+
supportsFastMode: capabilities.supportsFastMode === true,
|
|
60
|
+
source: "cached",
|
|
61
|
+
catalogVersion: CLAUDE_SDK_CATALOG_VERSION,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Return the versioned, SDK-matched fallback without exposing mutable shared
|
|
67
|
+
* objects to callers.
|
|
68
|
+
*/
|
|
69
|
+
export function curatedClaudeSdkModels() {
|
|
70
|
+
return CURATED_CATALOG.map((entry) => ({ ...entry, supportedEfforts: [...entry.supportedEfforts] }));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Normalize only exact Claude model ids. CLI convenience aliases are rejected
|
|
75
|
+
* so persisted configuration never changes meaning when an alias advances.
|
|
76
|
+
* Exact dated ids and a canonical `[1m]` suffix are preserved.
|
|
77
|
+
* @param {unknown} value
|
|
78
|
+
* @returns {string|null}
|
|
79
|
+
*/
|
|
80
|
+
export function normalizeClaudeSdkModelId(value) {
|
|
81
|
+
let model = String(value ?? "").trim().toLowerCase();
|
|
82
|
+
if (!model || model.length > 160) return null;
|
|
83
|
+
if (model.startsWith("claude:")) model = model.slice("claude:".length);
|
|
84
|
+
if (MODEL_ALIASES.has(model)) return null;
|
|
85
|
+
|
|
86
|
+
const contextSuffix = model.endsWith("[1m]") ? "[1m]" : "";
|
|
87
|
+
if (contextSuffix) model = model.slice(0, -contextSuffix.length);
|
|
88
|
+
if (!/^claude-(?:opus|sonnet|haiku|fable|mythos)-\d+(?:-\d+)*$/u.test(model)) return null;
|
|
89
|
+
return `${model}${contextSuffix}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function displayNameForModel(model) {
|
|
93
|
+
const oneMillion = model.endsWith("[1m]");
|
|
94
|
+
const base = oneMillion ? model.slice(0, -4) : model;
|
|
95
|
+
const match = /^claude-([a-z]+)-(.+)$/u.exec(base);
|
|
96
|
+
if (!match) return model;
|
|
97
|
+
const family = `${match[1][0].toUpperCase()}${match[1].slice(1)}`;
|
|
98
|
+
const version = match[2].replace(/-20\d{6}$/u, "").replace(/-/g, ".");
|
|
99
|
+
return `Claude ${family} ${version}${oneMillion ? " (1M context)" : ""}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function boundedCatalogText(value, limit) {
|
|
103
|
+
const text = String(value ?? "")
|
|
104
|
+
.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ")
|
|
105
|
+
.replace(/\s+/g, " ")
|
|
106
|
+
.trim();
|
|
107
|
+
return text.length <= limit ? text : `${text.slice(0, Math.max(0, limit - 1))}…`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function normalizedEfforts(entry) {
|
|
111
|
+
const values = Array.isArray(entry?.supportedEffortLevels)
|
|
112
|
+
? entry.supportedEffortLevels
|
|
113
|
+
: Array.isArray(entry?.supportedEfforts)
|
|
114
|
+
? entry.supportedEfforts
|
|
115
|
+
: [];
|
|
116
|
+
return [...new Set(values.map((value) => String(value)).filter((value) => SUPPORTED_EFFORTS.has(value)))];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Whitelist the SDK initialization catalog into the public mono-agent shape.
|
|
121
|
+
* No account, organization, token source, raw error, or unknown SDK field can
|
|
122
|
+
* cross this boundary.
|
|
123
|
+
* @param {unknown} rows
|
|
124
|
+
* @param {"discovered"|"cached"} [source]
|
|
125
|
+
* @returns {ClaudeSdkCatalogModel[]}
|
|
126
|
+
*/
|
|
127
|
+
export function normalizeClaudeSdkCatalog(rows, source = "discovered") {
|
|
128
|
+
if (!Array.isArray(rows)) return [];
|
|
129
|
+
const byModel = new Map();
|
|
130
|
+
for (const raw of rows.slice(0, MAX_MODELS * 4)) {
|
|
131
|
+
if (!raw || typeof raw !== "object") continue;
|
|
132
|
+
const row = /** @type {any} */ (raw);
|
|
133
|
+
const model = normalizeClaudeSdkModelId(
|
|
134
|
+
row.resolvedModel ?? row.model ?? row.reference ?? row.value,
|
|
135
|
+
);
|
|
136
|
+
if (!model) continue;
|
|
137
|
+
const supportedEfforts = /** @type {ClaudeSdkEffort[]} */ (normalizedEfforts(row));
|
|
138
|
+
const description = boundedCatalogText(row.description, MAX_DESCRIPTION_CHARS)
|
|
139
|
+
|| `Claude ${displayNameForModel(model).replace(/^Claude /u, "")}`;
|
|
140
|
+
const current = byModel.get(model);
|
|
141
|
+
/** @type {ClaudeSdkCatalogModel} */
|
|
142
|
+
const normalized = {
|
|
143
|
+
model,
|
|
144
|
+
reference: `claude:${model}`,
|
|
145
|
+
displayName: displayNameForModel(model),
|
|
146
|
+
description,
|
|
147
|
+
supportedEfforts,
|
|
148
|
+
supportsAdaptiveThinking: row.supportsAdaptiveThinking === true,
|
|
149
|
+
supportsFastMode: row.supportsFastMode === true,
|
|
150
|
+
source,
|
|
151
|
+
catalogVersion: CLAUDE_SDK_CATALOG_VERSION,
|
|
152
|
+
};
|
|
153
|
+
if (!current) {
|
|
154
|
+
byModel.set(model, normalized);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
byModel.set(model, {
|
|
158
|
+
...current,
|
|
159
|
+
description: current.description.length >= normalized.description.length
|
|
160
|
+
? current.description
|
|
161
|
+
: normalized.description,
|
|
162
|
+
supportedEfforts: [...new Set([...current.supportedEfforts, ...supportedEfforts])],
|
|
163
|
+
supportsAdaptiveThinking: current.supportsAdaptiveThinking || normalized.supportsAdaptiveThinking,
|
|
164
|
+
supportsFastMode: current.supportsFastMode || normalized.supportsFastMode,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
return [...byModel.values()].slice(0, MAX_MODELS);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function authoredCatalogRows(references) {
|
|
171
|
+
return (Array.isArray(references) ? references : []).map((reference) => ({ reference }));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** @param {...ClaudeSdkCatalogModel[]} catalogs @returns {ClaudeSdkCatalogModel[]} */
|
|
175
|
+
function mergeCatalogs(...catalogs) {
|
|
176
|
+
const byModel = new Map();
|
|
177
|
+
for (const catalog of catalogs) {
|
|
178
|
+
for (const entry of catalog) {
|
|
179
|
+
const existing = byModel.get(entry.model);
|
|
180
|
+
if (!existing || (existing.source === "cached" && entry.source === "discovered")) {
|
|
181
|
+
byModel.set(entry.model, entry);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return [...byModel.values()].slice(0, MAX_MODELS);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function safeDiscoveryEnvironment(baseEnvironment = process.env) {
|
|
189
|
+
const env = {};
|
|
190
|
+
for (const key of [
|
|
191
|
+
"PATH", "LANG", "LC_ALL", "LC_CTYPE", "SHELL",
|
|
192
|
+
"SystemRoot", "WINDIR", "ComSpec", "PATHEXT",
|
|
193
|
+
]) {
|
|
194
|
+
const value = baseEnvironment[key];
|
|
195
|
+
if (typeof value === "string" && value) env[key] = value;
|
|
196
|
+
}
|
|
197
|
+
return env;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function privateDirectory(parent, name) {
|
|
201
|
+
const path = join(parent, name);
|
|
202
|
+
await mkdir(path, { mode: 0o700 });
|
|
203
|
+
if (process.platform !== "win32") await chmod(path, 0o700);
|
|
204
|
+
return path;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** @internal Exported for deterministic isolation tests. */
|
|
208
|
+
export async function createClaudeSdkDiscoveryIsolation({ baseEnvironment = process.env } = {}) {
|
|
209
|
+
const root = await mkdtemp(join(tmpdir(), "mono-agent-claude-discovery-"));
|
|
210
|
+
try {
|
|
211
|
+
if (process.platform !== "win32") await chmod(root, 0o700);
|
|
212
|
+
const home = await privateDirectory(root, "home");
|
|
213
|
+
const claudeConfig = await privateDirectory(root, "secure-storage");
|
|
214
|
+
const config = await privateDirectory(root, "xdg-config");
|
|
215
|
+
const cache = await privateDirectory(root, "xdg-cache");
|
|
216
|
+
const data = await privateDirectory(root, "xdg-data");
|
|
217
|
+
const state = await privateDirectory(root, "xdg-state");
|
|
218
|
+
const temp = await privateDirectory(root, "tmp");
|
|
219
|
+
const cwd = await privateDirectory(root, "cwd");
|
|
220
|
+
const env = {
|
|
221
|
+
...safeDiscoveryEnvironment(baseEnvironment),
|
|
222
|
+
HOME: home,
|
|
223
|
+
CLAUDE_CONFIG_DIR: claudeConfig,
|
|
224
|
+
XDG_CONFIG_HOME: config,
|
|
225
|
+
XDG_CACHE_HOME: cache,
|
|
226
|
+
XDG_DATA_HOME: data,
|
|
227
|
+
XDG_STATE_HOME: state,
|
|
228
|
+
TMPDIR: temp,
|
|
229
|
+
TMP: temp,
|
|
230
|
+
TEMP: temp,
|
|
231
|
+
CLAUDE_AGENT_SDK_CLIENT_APP: "mono-agent-model-discovery/0.6",
|
|
232
|
+
MCP_CONNECTION_NONBLOCKING: "0",
|
|
233
|
+
};
|
|
234
|
+
return {
|
|
235
|
+
root,
|
|
236
|
+
cwd,
|
|
237
|
+
env,
|
|
238
|
+
cleanup: async () => {
|
|
239
|
+
await rm(root, { recursive: true, force: true }).catch(() => undefined);
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
} catch (error) {
|
|
243
|
+
await rm(root, { recursive: true, force: true }).catch(() => undefined);
|
|
244
|
+
throw error;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function waitForWorker(child, timeoutMs) {
|
|
249
|
+
return new Promise((resolve, reject) => {
|
|
250
|
+
let settled = false;
|
|
251
|
+
const finish = (callback, value) => {
|
|
252
|
+
if (settled) return;
|
|
253
|
+
settled = true;
|
|
254
|
+
clearTimeout(timer);
|
|
255
|
+
child.removeListener?.("message", onMessage);
|
|
256
|
+
child.removeListener?.("error", onError);
|
|
257
|
+
child.removeListener?.("exit", onExit);
|
|
258
|
+
callback(value);
|
|
259
|
+
};
|
|
260
|
+
const onMessage = (message) => {
|
|
261
|
+
if (message?.type === "claude_catalog") finish(resolve, message.models);
|
|
262
|
+
else if (message?.type === "claude_catalog_error") finish(reject, new Error("Claude catalog worker failed"));
|
|
263
|
+
};
|
|
264
|
+
const onError = () => finish(reject, new Error("Claude catalog worker failed"));
|
|
265
|
+
const onExit = (code) => {
|
|
266
|
+
if (code !== 0) finish(reject, new Error("Claude catalog worker exited before returning a catalog"));
|
|
267
|
+
};
|
|
268
|
+
const timer = setTimeout(() => {
|
|
269
|
+
try {
|
|
270
|
+
if (child.connected !== false) child.send?.({ type: "abort" }, () => undefined);
|
|
271
|
+
} catch { /* best effort */ }
|
|
272
|
+
finish(reject, new Error("Claude catalog discovery timed out"));
|
|
273
|
+
}, timeoutMs);
|
|
274
|
+
timer.unref?.();
|
|
275
|
+
child.on("message", onMessage);
|
|
276
|
+
child.on("error", onError);
|
|
277
|
+
child.on("exit", onExit);
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async function stopWorker(child) {
|
|
282
|
+
if (!child || child.exitCode != null || child.signalCode != null) return;
|
|
283
|
+
await new Promise((resolve) => {
|
|
284
|
+
let settled = false;
|
|
285
|
+
let timer;
|
|
286
|
+
const finish = () => {
|
|
287
|
+
if (settled) return;
|
|
288
|
+
settled = true;
|
|
289
|
+
clearTimeout(timer);
|
|
290
|
+
child.removeListener?.("exit", finish);
|
|
291
|
+
child.removeListener?.("error", finish);
|
|
292
|
+
resolve(undefined);
|
|
293
|
+
};
|
|
294
|
+
child.once?.("exit", finish);
|
|
295
|
+
child.once?.("error", finish);
|
|
296
|
+
try {
|
|
297
|
+
if (child.connected !== false) child.send?.({ type: "abort" }, () => undefined);
|
|
298
|
+
} catch { /* best effort */ }
|
|
299
|
+
try { child.disconnect?.(); } catch { /* best effort */ }
|
|
300
|
+
timer = setTimeout(() => {
|
|
301
|
+
try { child.kill?.("SIGKILL"); } catch { /* best effort */ }
|
|
302
|
+
finish();
|
|
303
|
+
}, CHILD_STOP_TIMEOUT_MS);
|
|
304
|
+
timer.unref?.();
|
|
305
|
+
try { child.kill?.("SIGTERM"); } catch { finish(); }
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Discover Claude's current model catalog in a throwaway, no-auth process.
|
|
311
|
+
* Failure is deliberately non-fatal: the exact SDK-versioned curated catalog
|
|
312
|
+
* remains available with `source: "cached"`.
|
|
313
|
+
*
|
|
314
|
+
* @param {Object} [options]
|
|
315
|
+
* @param {number} [options.timeoutMs]
|
|
316
|
+
* @param {readonly string[]} [options.authoredModelRefs]
|
|
317
|
+
* @param {typeof fork} [options.forkProcess]
|
|
318
|
+
* @param {(isolation: Awaited<ReturnType<typeof createClaudeSdkDiscoveryIsolation>>) => void} [options.onIsolation]
|
|
319
|
+
* @returns {Promise<ClaudeSdkCatalogModel[]>}
|
|
320
|
+
*/
|
|
321
|
+
export async function discoverClaudeSdkModels({
|
|
322
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
323
|
+
authoredModelRefs = [],
|
|
324
|
+
forkProcess = fork,
|
|
325
|
+
onIsolation,
|
|
326
|
+
} = {}) {
|
|
327
|
+
const cached = curatedClaudeSdkModels();
|
|
328
|
+
const authored = normalizeClaudeSdkCatalog(authoredCatalogRows(authoredModelRefs), "cached");
|
|
329
|
+
let isolation;
|
|
330
|
+
let child;
|
|
331
|
+
try {
|
|
332
|
+
isolation = await createClaudeSdkDiscoveryIsolation();
|
|
333
|
+
onIsolation?.(isolation);
|
|
334
|
+
child = forkProcess(WORKER_PATH, [], {
|
|
335
|
+
cwd: isolation.cwd,
|
|
336
|
+
env: isolation.env,
|
|
337
|
+
// Do not inherit caller preload/debug/input-type flags into the
|
|
338
|
+
// credential-isolated discovery process.
|
|
339
|
+
execArgv: [],
|
|
340
|
+
stdio: ["ignore", "ignore", "ignore", "ipc"],
|
|
341
|
+
serialization: "json",
|
|
342
|
+
});
|
|
343
|
+
const raw = await waitForWorker(child, Math.max(1, Math.min(30_000, Number(timeoutMs) || DEFAULT_TIMEOUT_MS)));
|
|
344
|
+
const discovered = normalizeClaudeSdkCatalog(raw, "discovered");
|
|
345
|
+
return mergeCatalogs(cached, authored, discovered);
|
|
346
|
+
} catch {
|
|
347
|
+
return mergeCatalogs(cached, authored);
|
|
348
|
+
} finally {
|
|
349
|
+
await stopWorker(child);
|
|
350
|
+
await isolation?.cleanup();
|
|
351
|
+
}
|
|
352
|
+
}
|