@cydm/happy-elves 0.1.0-beta.254 → 0.1.0-beta.259
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/apps/cli/package.json +1 -1
- package/apps/daemon/package.json +1 -1
- package/apps/relay/dist/projections.js +31 -1
- package/package.json +1 -1
- package/packages/runtime-cli/dist/codex-app-server.d.ts +14 -1
- package/packages/runtime-cli/dist/codex-app-server.js +100 -0
- package/packages/runtime-cli/dist/index.d.ts +5 -1
- package/packages/runtime-cli/dist/index.js +43 -3
package/apps/cli/package.json
CHANGED
package/apps/daemon/package.json
CHANGED
|
@@ -21,17 +21,47 @@ function primitiveSupport(value) {
|
|
|
21
21
|
function historyModel(value) {
|
|
22
22
|
return value === "append-only" || value === "authoritative-linear" ? value : undefined;
|
|
23
23
|
}
|
|
24
|
+
function stringField(value) {
|
|
25
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
26
|
+
}
|
|
27
|
+
function agentModelOptions(value) {
|
|
28
|
+
if (!Array.isArray(value))
|
|
29
|
+
return undefined;
|
|
30
|
+
const seen = new Set();
|
|
31
|
+
const models = value.flatMap((item) => {
|
|
32
|
+
if (!isRecord(item))
|
|
33
|
+
return [];
|
|
34
|
+
const id = stringField(item.id);
|
|
35
|
+
if (!id || seen.has(id))
|
|
36
|
+
return [];
|
|
37
|
+
seen.add(id);
|
|
38
|
+
const label = stringField(item.label);
|
|
39
|
+
const reason = stringField(item.reason);
|
|
40
|
+
return [{
|
|
41
|
+
id,
|
|
42
|
+
...(label ? { label } : {}),
|
|
43
|
+
...(item.unavailable === true ? { unavailable: true } : {}),
|
|
44
|
+
...(reason ? { reason } : {}),
|
|
45
|
+
}];
|
|
46
|
+
});
|
|
47
|
+
return models.length > 0 ? models : undefined;
|
|
48
|
+
}
|
|
24
49
|
export function normalizeMachineCapabilities(value) {
|
|
25
50
|
const record = isRecord(value) ? value : {};
|
|
26
51
|
const agents = Array.isArray(record.agents)
|
|
27
52
|
? record.agents.flatMap((agent) => {
|
|
28
53
|
if (!isRecord(agent) || typeof agent.id !== "string" || typeof agent.label !== "string")
|
|
29
54
|
return [];
|
|
55
|
+
const agentHistoryModel = historyModel(agent.historyModel);
|
|
56
|
+
const defaultModel = stringField(agent.defaultModel);
|
|
57
|
+
const models = agentModelOptions(agent.models);
|
|
30
58
|
return [{
|
|
31
59
|
id: agent.id,
|
|
32
60
|
label: agent.label,
|
|
33
61
|
...(typeof agent.command === "string" ? { command: agent.command } : {}),
|
|
34
|
-
...(
|
|
62
|
+
...(agentHistoryModel ? { historyModel: agentHistoryModel } : {}),
|
|
63
|
+
...(defaultModel ? { defaultModel } : {}),
|
|
64
|
+
...(models ? { models } : {}),
|
|
35
65
|
...(isRecord(agent.sessionPrimitives)
|
|
36
66
|
? {
|
|
37
67
|
sessionPrimitives: {
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RuntimePromptMode, RuntimeTurn } from "../../runtime/dist/index.js";
|
|
1
|
+
import type { RuntimeModelOption, RuntimePromptMode, RuntimeTurn } from "../../runtime/dist/index.js";
|
|
2
2
|
import type { RuntimePermissionMode } from "./permissions.js";
|
|
3
3
|
export declare class CodexAppServerSession {
|
|
4
4
|
private readonly command;
|
|
@@ -20,7 +20,12 @@ export declare class CodexAppServerSession {
|
|
|
20
20
|
threadId: string | null;
|
|
21
21
|
constructor(command: string, cwd: string, threadId?: string, onThreadIdChanged?: ((threadId: string) => void) | undefined, restartableEmptyThread?: boolean, initialModel?: string | undefined);
|
|
22
22
|
ensure(permissionMode?: RuntimePermissionMode, model?: string): Promise<string>;
|
|
23
|
+
discoverRuntimeOptions(): Promise<{
|
|
24
|
+
defaultModel?: string;
|
|
25
|
+
models?: RuntimeModelOption[];
|
|
26
|
+
} | undefined>;
|
|
23
27
|
private ensureCurrentPermission;
|
|
28
|
+
private loadAllModelListPages;
|
|
24
29
|
private ensurePermission;
|
|
25
30
|
private startNewThread;
|
|
26
31
|
private canRestartEmptyThread;
|
|
@@ -56,4 +61,12 @@ export declare class CodexAppServerSession {
|
|
|
56
61
|
private noteTurnActivity;
|
|
57
62
|
private emptyCompletionFailure;
|
|
58
63
|
}
|
|
64
|
+
export declare function discoverCodexRuntimeOptions(input: {
|
|
65
|
+
command: string;
|
|
66
|
+
cwd: string;
|
|
67
|
+
timeoutMs?: number;
|
|
68
|
+
}): Promise<{
|
|
69
|
+
defaultModel?: string;
|
|
70
|
+
models?: RuntimeModelOption[];
|
|
71
|
+
} | undefined>;
|
|
59
72
|
//# sourceMappingURL=codex-app-server.d.ts.map
|
|
@@ -33,9 +33,43 @@ export class CodexAppServerSession {
|
|
|
33
33
|
async ensure(permissionMode, model) {
|
|
34
34
|
return await this.ensurePermission(normalizeCodexPermissionMode(permissionMode), normalizeModel(model ?? this.initialModel));
|
|
35
35
|
}
|
|
36
|
+
async discoverRuntimeOptions() {
|
|
37
|
+
if (!this.rpc)
|
|
38
|
+
await this.connect();
|
|
39
|
+
return codexRuntimeOptionsFromModelList(await this.loadAllModelListPages());
|
|
40
|
+
}
|
|
36
41
|
async ensureCurrentPermission() {
|
|
37
42
|
return await this.ensurePermission(normalizeCodexPermissionMode(this.appliedPermissionMode), this.appliedModel ?? normalizeModel(this.initialModel));
|
|
38
43
|
}
|
|
44
|
+
async loadAllModelListPages() {
|
|
45
|
+
const models = [];
|
|
46
|
+
const seenModelIds = new Set();
|
|
47
|
+
const seenCursors = new Set();
|
|
48
|
+
let cursor;
|
|
49
|
+
for (let pageCount = 0; pageCount < CODEX_MODEL_LIST_PAGE_SAFETY_LIMIT; pageCount += 1) {
|
|
50
|
+
const result = await this.request("model/list", {
|
|
51
|
+
limit: CODEX_MODEL_LIST_PAGE_LIMIT,
|
|
52
|
+
...(cursor !== undefined ? { cursor } : {}),
|
|
53
|
+
});
|
|
54
|
+
for (const model of codexModelsFromModelList(result)) {
|
|
55
|
+
const id = codexModelId(model);
|
|
56
|
+
if (id && seenModelIds.has(id))
|
|
57
|
+
continue;
|
|
58
|
+
if (id)
|
|
59
|
+
seenModelIds.add(id);
|
|
60
|
+
models.push(model);
|
|
61
|
+
}
|
|
62
|
+
const nextCursor = codexModelListNextCursor(result);
|
|
63
|
+
if (nextCursor === undefined)
|
|
64
|
+
return { data: models };
|
|
65
|
+
if (seenCursors.has(nextCursor)) {
|
|
66
|
+
throw new Error(`Codex model/list returned repeated cursor ${JSON.stringify(nextCursor)}`);
|
|
67
|
+
}
|
|
68
|
+
seenCursors.add(nextCursor);
|
|
69
|
+
cursor = nextCursor;
|
|
70
|
+
}
|
|
71
|
+
throw new Error(`Codex model/list exceeded the ${CODEX_MODEL_LIST_PAGE_SAFETY_LIMIT} page safety limit`);
|
|
72
|
+
}
|
|
39
73
|
async ensurePermission(permissionMode, model) {
|
|
40
74
|
const hadProcess = Boolean(this.rpc);
|
|
41
75
|
if (!this.rpc)
|
|
@@ -349,6 +383,27 @@ export class CodexAppServerSession {
|
|
|
349
383
|
return `Codex turn completed without assistant output after runtime errors${detail ? `: ${detail}` : ""}`;
|
|
350
384
|
}
|
|
351
385
|
}
|
|
386
|
+
const CODEX_MODEL_LIST_PAGE_LIMIT = 100;
|
|
387
|
+
const CODEX_MODEL_LIST_PAGE_SAFETY_LIMIT = 100;
|
|
388
|
+
const CODEX_PROVIDER_DISCOVERY_TIMEOUT_MS = 5_000;
|
|
389
|
+
export async function discoverCodexRuntimeOptions(input) {
|
|
390
|
+
const discovery = new CodexAppServerSession(input.command, input.cwd);
|
|
391
|
+
const timeoutMs = Math.max(1, input.timeoutMs ?? CODEX_PROVIDER_DISCOVERY_TIMEOUT_MS);
|
|
392
|
+
let timeout;
|
|
393
|
+
try {
|
|
394
|
+
return await Promise.race([
|
|
395
|
+
discovery.discoverRuntimeOptions(),
|
|
396
|
+
new Promise((_, reject) => {
|
|
397
|
+
timeout = setTimeout(() => reject(new Error(`Codex provider model discovery timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
398
|
+
}),
|
|
399
|
+
]);
|
|
400
|
+
}
|
|
401
|
+
finally {
|
|
402
|
+
if (timeout)
|
|
403
|
+
clearTimeout(timeout);
|
|
404
|
+
await discovery.close();
|
|
405
|
+
}
|
|
406
|
+
}
|
|
352
407
|
function codexStderrMessage(text) {
|
|
353
408
|
try {
|
|
354
409
|
const parsed = JSON.parse(text);
|
|
@@ -415,4 +470,49 @@ function numberField(record, ...keys) {
|
|
|
415
470
|
function normalizeModel(value) {
|
|
416
471
|
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
417
472
|
}
|
|
473
|
+
function codexModelsFromModelList(value) {
|
|
474
|
+
const record = recordFrom(value);
|
|
475
|
+
return Array.isArray(record.models)
|
|
476
|
+
? record.models
|
|
477
|
+
: Array.isArray(record.items)
|
|
478
|
+
? record.items
|
|
479
|
+
: Array.isArray(record.data)
|
|
480
|
+
? record.data
|
|
481
|
+
: [];
|
|
482
|
+
}
|
|
483
|
+
function codexModelId(value) {
|
|
484
|
+
return stringField(recordFrom(value), "id", "key", "name");
|
|
485
|
+
}
|
|
486
|
+
function codexModelListNextCursor(value) {
|
|
487
|
+
const nextCursor = recordFrom(value).nextCursor;
|
|
488
|
+
if (nextCursor === undefined || nextCursor === null)
|
|
489
|
+
return undefined;
|
|
490
|
+
if (typeof nextCursor !== "string" || nextCursor.length === 0) {
|
|
491
|
+
throw new Error("Codex model/list returned an invalid nextCursor");
|
|
492
|
+
}
|
|
493
|
+
return nextCursor;
|
|
494
|
+
}
|
|
495
|
+
function codexRuntimeOptionsFromModelList(value) {
|
|
496
|
+
const rawModels = codexModelsFromModelList(value);
|
|
497
|
+
const seen = new Set();
|
|
498
|
+
const models = rawModels.flatMap((item) => {
|
|
499
|
+
const model = recordFrom(item);
|
|
500
|
+
const id = codexModelId(model)?.trim();
|
|
501
|
+
if (!id || seen.has(id))
|
|
502
|
+
return [];
|
|
503
|
+
seen.add(id);
|
|
504
|
+
const label = stringField(model, "label", "displayName", "name");
|
|
505
|
+
return [{ id, ...(label ? { label } : {}) }];
|
|
506
|
+
});
|
|
507
|
+
if (models.length === 0)
|
|
508
|
+
return undefined;
|
|
509
|
+
const defaultModel = rawModels
|
|
510
|
+
.map((item) => recordFrom(item))
|
|
511
|
+
.find((item) => item.isDefault === true);
|
|
512
|
+
const defaultModelId = defaultModel ? codexModelId(defaultModel)?.trim() : undefined;
|
|
513
|
+
return {
|
|
514
|
+
...(defaultModelId ? { defaultModel: defaultModelId } : {}),
|
|
515
|
+
models,
|
|
516
|
+
};
|
|
517
|
+
}
|
|
418
518
|
//# sourceMappingURL=codex-app-server.js.map
|
|
@@ -5,7 +5,11 @@ export type CliRuntimeProviderOptions = {
|
|
|
5
5
|
cwd: string;
|
|
6
6
|
stateDir: string;
|
|
7
7
|
codexCommand?: string;
|
|
8
|
+
/** Maximum total time for the provider-level Codex catalog probe. */
|
|
9
|
+
codexDiscoveryTimeoutMs?: number;
|
|
8
10
|
claudeCommand?: string;
|
|
9
11
|
};
|
|
10
|
-
export declare function createCliRuntimeProvider(options: CliRuntimeProviderOptions): HappyElvesRuntime
|
|
12
|
+
export declare function createCliRuntimeProvider(options: CliRuntimeProviderOptions): HappyElvesRuntime & {
|
|
13
|
+
initialize(): Promise<void>;
|
|
14
|
+
};
|
|
11
15
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -3,6 +3,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import { readClaudeHistoricalEvents, readClaudeHistoricalSessionPage } from "./claude-history.js";
|
|
5
5
|
import { renameClaudeSession } from "./claude-rename.js";
|
|
6
|
+
import { discoverCodexRuntimeOptions } from "./codex-app-server.js";
|
|
6
7
|
import { forkAndTruncateClaudeSession, forkClaudeSession } from "./claude-session-store.js";
|
|
7
8
|
import { startClaudeTurn } from "./claude-turn.js";
|
|
8
9
|
import { readCodexCanonicalHistoricalSessionPage, readCodexHistoricalEvents } from "./codex-history.js";
|
|
@@ -13,6 +14,7 @@ export { HistoryCursorStaleError, isHistoryCursorStaleError, mergeHistoricalSess
|
|
|
13
14
|
export function createCliRuntimeProvider(options) {
|
|
14
15
|
const codexCommand = options.codexCommand ?? process.env.HAPPY_ELVES_CODEX_COMMAND ?? "codex";
|
|
15
16
|
const claudeCommand = options.claudeCommand ?? process.env.HAPPY_ELVES_CLAUDE_COMMAND ?? "claude";
|
|
17
|
+
let codexOptions = codexAgentModels();
|
|
16
18
|
const sessions = new CliSessionStore({ cwd: options.cwd, codexCommand });
|
|
17
19
|
const historySnapshotDir = path.join(options.stateDir, "history-snapshots");
|
|
18
20
|
async function listHistoricalSessionPage(input) {
|
|
@@ -66,6 +68,24 @@ export function createCliRuntimeProvider(options) {
|
|
|
66
68
|
}), loadedByBackendId);
|
|
67
69
|
}
|
|
68
70
|
return {
|
|
71
|
+
async initialize() {
|
|
72
|
+
const configured = agentModelsFromEnv("HAPPY_ELVES_CODEX");
|
|
73
|
+
if (configured.models && configured.models.length > 0) {
|
|
74
|
+
codexOptions = configured;
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const discovered = await discoverCodexRuntimeOptions({
|
|
78
|
+
command: codexCommand,
|
|
79
|
+
cwd: options.cwd,
|
|
80
|
+
timeoutMs: options.codexDiscoveryTimeoutMs,
|
|
81
|
+
}).catch(() => undefined);
|
|
82
|
+
codexOptions = discovered?.models && discovered.models.length > 0
|
|
83
|
+
? {
|
|
84
|
+
...(configured.defaultModel ? { defaultModel: configured.defaultModel } : discovered.defaultModel ? { defaultModel: discovered.defaultModel } : {}),
|
|
85
|
+
models: discovered.models,
|
|
86
|
+
}
|
|
87
|
+
: codexAgentModels();
|
|
88
|
+
},
|
|
69
89
|
getCapabilities() {
|
|
70
90
|
return {
|
|
71
91
|
provider: "cli",
|
|
@@ -75,7 +95,7 @@ export function createCliRuntimeProvider(options) {
|
|
|
75
95
|
},
|
|
76
96
|
listAgents() {
|
|
77
97
|
return [
|
|
78
|
-
{ id: "codex", label: "Codex", ...
|
|
98
|
+
{ id: "codex", label: "Codex", ...codexOptions },
|
|
79
99
|
{ id: "claude", label: "Claude Code", ...agentModelsFromEnv("HAPPY_ELVES_CLAUDE") },
|
|
80
100
|
];
|
|
81
101
|
},
|
|
@@ -360,8 +380,8 @@ function modelOptionsFromEnv(value) {
|
|
|
360
380
|
});
|
|
361
381
|
}
|
|
362
382
|
function modelOptionsFromCodexCache(codexHome) {
|
|
363
|
-
const cachePath =
|
|
364
|
-
if (!
|
|
383
|
+
const cachePath = codexModelCachePaths(codexHome).find((candidate) => existsSync(candidate));
|
|
384
|
+
if (!cachePath)
|
|
365
385
|
return [];
|
|
366
386
|
try {
|
|
367
387
|
const parsed = JSON.parse(readFileSync(cachePath, "utf8"));
|
|
@@ -388,6 +408,26 @@ function modelOptionsFromCodexCache(codexHome) {
|
|
|
388
408
|
return [];
|
|
389
409
|
}
|
|
390
410
|
}
|
|
411
|
+
function codexModelCachePaths(codexHome) {
|
|
412
|
+
const candidates = [
|
|
413
|
+
process.env.HAPPY_ELVES_CODEX_MODELS_CACHE,
|
|
414
|
+
codexHome?.trim() ? path.join(codexHome.trim(), "models_cache.json") : undefined,
|
|
415
|
+
process.env.APPDATA ? path.join(process.env.APPDATA, "Codex", "models_cache.json") : undefined,
|
|
416
|
+
process.env.APPDATA ? path.join(process.env.APPDATA, "codex", "models_cache.json") : undefined,
|
|
417
|
+
process.env.LOCALAPPDATA ? path.join(process.env.LOCALAPPDATA, "Codex", "models_cache.json") : undefined,
|
|
418
|
+
process.env.LOCALAPPDATA ? path.join(process.env.LOCALAPPDATA, "codex", "models_cache.json") : undefined,
|
|
419
|
+
path.join(os.homedir(), ".codex", "models_cache.json"),
|
|
420
|
+
process.env.USERPROFILE ? path.join(process.env.USERPROFILE, ".codex", "models_cache.json") : undefined,
|
|
421
|
+
];
|
|
422
|
+
const seen = new Set();
|
|
423
|
+
return candidates.flatMap((candidate) => {
|
|
424
|
+
const normalized = candidate?.trim();
|
|
425
|
+
if (!normalized || seen.has(normalized))
|
|
426
|
+
return [];
|
|
427
|
+
seen.add(normalized);
|
|
428
|
+
return [normalized];
|
|
429
|
+
});
|
|
430
|
+
}
|
|
391
431
|
function isRecord(value) {
|
|
392
432
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
393
433
|
}
|