@midhunxavier/code-flow 0.0.0-stage → 0.1.0-preview.3
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/LICENSE +21 -0
- package/README.md +141 -2
- package/bin/codeflow.mjs +8 -0
- package/bin/launcher.mjs +127 -0
- package/dist/remote-host.json +1 -0
- package/licenses/inter.txt +92 -0
- package/licenses/preact.txt +21 -0
- package/licenses/signals-core.txt +21 -0
- package/licenses/signals.txt +21 -0
- package/licenses/typescript-notices.txt +193 -0
- package/licenses/typescript.txt +55 -0
- package/licenses/uqr.txt +22 -0
- package/licenses/xterm-fit.txt +19 -0
- package/licenses/xterm.txt +21 -0
- package/package.json +34 -4
- package/public/assets/changes-62ZIEQSN.js +1 -0
- package/public/assets/chunk-2PZVOKID.js +1 -0
- package/public/assets/chunk-4D5IENEB.js +1 -0
- package/public/assets/chunk-5SARP5MU.js +1 -0
- package/public/assets/chunk-63YLZ3PR.js +1 -0
- package/public/assets/chunk-7FUHKF2M.js +1 -0
- package/public/assets/chunk-APQEVJ2K.js +1 -0
- package/public/assets/chunk-CGOZQMEZ.js +2 -0
- package/public/assets/chunk-J4VYS3KV.js +2 -0
- package/public/assets/chunk-L7TUCIYF.js +1 -0
- package/public/assets/chunk-SIHDWM2Y.js +154 -0
- package/public/assets/chunk-TEJVYIKC.js +1 -0
- package/public/assets/code-browser-VB2HGZSG.js +4 -0
- package/public/assets/filtered-tasks-ES5CP4KV.js +1 -0
- package/public/assets/interactive-browser-7F5ZXRUV.js +7 -0
- package/public/assets/join-project-6RTFHNCA.js +1 -0
- package/public/assets/main-65EW73SB.js +1 -0
- package/public/assets/project-invitations-view-GMPWGWYM.js +1 -0
- package/public/assets/settings-UB5Q4AQM.js +9 -0
- package/public/assets/styles-18792BB4.css +32 -0
- package/public/assets/team-server-wizard-2GLDWS6D.js +8 -0
- package/public/assets/terminal-IWAIM4DE.js +62 -0
- package/public/assets.json +1 -0
- package/public/bundle-meta.json +1 -0
- package/public/docs/.nojekyll +0 -0
- package/public/docs/accessibility.html +19 -0
- package/public/docs/acp-agents.html +117 -0
- package/public/docs/agent-containers.html +79 -0
- package/public/docs/api.html +1130 -0
- package/public/docs/app.js +342 -0
- package/public/docs/application-improvements.html +275 -0
- package/public/docs/architecture-explorer.html +19 -0
- package/public/docs/architecture.html +149 -0
- package/public/docs/auto-team.html +148 -0
- package/public/docs/automations.html +109 -0
- package/public/docs/checkpoint-review.html +37 -0
- package/public/docs/code-hosts.html +35 -0
- package/public/docs/code-intelligence.html +35 -0
- package/public/docs/configuration.html +346 -0
- package/public/docs/context-inspection.html +23 -0
- package/public/docs/coordinated-changes.html +28 -0
- package/public/docs/deferred-runs.html +25 -0
- package/public/docs/dependency-discovery.html +19 -0
- package/public/docs/documentation.html +84 -0
- package/public/docs/dynamic-teams-evaluation.html +174 -0
- package/public/docs/dynamic-teams-implementation-plan.html +882 -0
- package/public/docs/dynamic-teams-implementation-review-1.html +141 -0
- package/public/docs/dynamic-teams-plan-review-0.html +204 -0
- package/public/docs/extensions.html +42 -0
- package/public/docs/external-sources.html +96 -0
- package/public/docs/filesystem-policy.html +26 -0
- package/public/docs/filtered-tasks.html +44 -0
- package/public/docs/font-license.txt +92 -0
- package/public/docs/getting-started.html +143 -0
- package/public/docs/github-review-threads.html +26 -0
- package/public/docs/github.html +157 -0
- package/public/docs/help.html +69 -0
- package/public/docs/index.html +88 -0
- package/public/docs/installation.html +80 -0
- package/public/docs/launch-profiles.html +48 -0
- package/public/docs/maintenance.html +60 -0
- package/public/docs/memory-claims.html +31 -0
- package/public/docs/memory-storage.html +58 -0
- package/public/docs/notifications.html +39 -0
- package/public/docs/patterns.html +195 -0
- package/public/docs/performance-results.html +210 -0
- package/public/docs/performance.html +166 -0
- package/public/docs/project-conventions.html +22 -0
- package/public/docs/project-first-navigation-plan.html +587 -0
- package/public/docs/project-teams-implementation-plan.html +772 -0
- package/public/docs/project-teams.html +129 -0
- package/public/docs/provider-compatibility.html +20 -0
- package/public/docs/releasing.html +65 -0
- package/public/docs/remote-workers.html +64 -0
- package/public/docs/review-inbox.html +26 -0
- package/public/docs/run-storage.html +23 -0
- package/public/docs/runs.html +141 -0
- package/public/docs/search.json +1 -0
- package/public/docs/security.html +133 -0
- package/public/docs/shared-projects.html +31 -0
- package/public/docs/ssh-projects.html +60 -0
- package/public/docs/style.css +768 -0
- package/public/docs/task-reruns.html +30 -0
- package/public/docs/team-collaboration.html +412 -0
- package/public/docs/verification.html +22 -0
- package/public/docs/vscode.html +53 -0
- package/public/docs/worker-permission-verification.html +131 -0
- package/public/index.html +17 -0
- package/scripts/check-node.mjs +7 -0
- package/scripts/codeflow.mjs +55 -0
- package/scripts/computer-use.swift +64 -0
- package/scripts/pty.py +273 -0
- package/src/acp-connections.js +110 -0
- package/src/adapters/acp-presets.js +37 -0
- package/src/adapters/acp-types.js +0 -0
- package/src/adapters/acp.js +328 -0
- package/src/adapters/agent-tools.js +28 -0
- package/src/adapters/agy-auth.js +67 -0
- package/src/adapters/agy-isolation.js +242 -0
- package/src/adapters/agy-tools.js +45 -0
- package/src/adapters/agy.js +326 -0
- package/src/adapters/claude-stream.js +58 -0
- package/src/adapters/claude.js +282 -0
- package/src/adapters/codex-app-server.js +555 -0
- package/src/adapters/codex.js +311 -0
- package/src/adapters/errors.js +57 -0
- package/src/adapters/index.js +91 -0
- package/src/adapters/managed-permissions.js +37 -0
- package/src/adapters/model-discovery.js +100 -0
- package/src/adapters/process.js +433 -0
- package/src/adapters/team-tools.js +103 -0
- package/src/adapters/types.js +0 -0
- package/src/agent-browser.js +688 -0
- package/src/agent-computer.js +88 -0
- package/src/agent-container.js +155 -0
- package/src/agent-team-definitions.js +131 -0
- package/src/agent-tool-client.mjs +79 -0
- package/src/agent-tools.js +296 -0
- package/src/approvals.js +134 -0
- package/src/architecture.js +456 -0
- package/src/assets.js +44 -0
- package/src/automation.js +901 -0
- package/src/browser-files.js +293 -0
- package/src/browser-ownership.js +150 -0
- package/src/certificate-renewal.js +75 -0
- package/src/chat-family.js +25 -0
- package/src/chat-handoff.js +87 -0
- package/src/checkpoint-review.js +72 -0
- package/src/checks.js +303 -0
- package/src/code-host-git.js +102 -0
- package/src/code-hosts-api.js +99 -0
- package/src/code-hosts.js +482 -0
- package/src/code-intelligence-worker.mjs +452 -0
- package/src/code-intelligence.js +141 -0
- package/src/collab/api.js +365 -0
- package/src/collab/checkpoints.js +198 -0
- package/src/collab/connections.js +139 -0
- package/src/collab/host-execution.js +347 -0
- package/src/collab/hub-client.js +210 -0
- package/src/collab/link-api.js +229 -0
- package/src/collab/link.js +595 -0
- package/src/collab/model.js +496 -0
- package/src/collab/runner.js +1014 -0
- package/src/collab/store.js +1452 -0
- package/src/collab/terminals.js +361 -0
- package/src/collaboration-reviews.js +116 -0
- package/src/config.js +48 -0
- package/src/context-budget.js +14 -0
- package/src/context-inspection.js +204 -0
- package/src/context.js +154 -0
- package/src/conventions.js +283 -0
- package/src/coordinated-api.js +247 -0
- package/src/deferred-launch.js +82 -0
- package/src/deferred-runs.js +515 -0
- package/src/dependency-health.js +35 -0
- package/src/diff.js +43 -0
- package/src/docs-help.js +316 -0
- package/src/doctor.js +232 -0
- package/src/documentation-changes.js +20 -0
- package/src/engine.js +5258 -0
- package/src/env.js +44 -0
- package/src/execution-budget.js +8 -0
- package/src/experience.js +50 -0
- package/src/extensions.js +144 -0
- package/src/filesystem-access.js +107 -0
- package/src/filesystem-policy.js +238 -0
- package/src/filtered-task-limits.js +93 -0
- package/src/filtered-tasks-api.js +53 -0
- package/src/filtered-tasks.js +576 -0
- package/src/frame-policy.js +66 -0
- package/src/fs.js +57 -0
- package/src/git.js +626 -0
- package/src/github-review-threads.js +181 -0
- package/src/github.js +538 -0
- package/src/http.js +69 -0
- package/src/improve-settings.js +38 -0
- package/src/improve.js +187 -0
- package/src/integrations.js +373 -0
- package/src/integrity.js +259 -0
- package/src/journey-api.js +77 -0
- package/src/journey-replay.js +65 -0
- package/src/journey-store.js +97 -0
- package/src/journeys.js +60 -0
- package/src/json.js +81 -0
- package/src/launch-profiles.js +292 -0
- package/src/learnings.js +80 -0
- package/src/local-app-url.js +19 -0
- package/src/log.js +39 -0
- package/src/maintenance.js +335 -0
- package/src/memory-analysis.js +182 -0
- package/src/memory-claims.js +153 -0
- package/src/memory-sqlite.js +236 -0
- package/src/memory.js +552 -0
- package/src/merge-validation.js +58 -0
- package/src/model-catalog.js +239 -0
- package/src/model-research.js +91 -0
- package/src/notes.js +176 -0
- package/src/notifications.js +413 -0
- package/src/patterns/auto-team-model.js +230 -0
- package/src/patterns/auto-team.js +816 -0
- package/src/patterns/council.js +199 -0
- package/src/patterns/debate.js +188 -0
- package/src/patterns/findings.js +141 -0
- package/src/patterns/index.js +21 -0
- package/src/patterns/loop.js +158 -0
- package/src/patterns/pipeline.js +125 -0
- package/src/patterns/prompts.js +181 -0
- package/src/patterns/race.js +147 -0
- package/src/patterns/schemas.js +40 -0
- package/src/patterns/solo.js +46 -0
- package/src/patterns/supervisor.js +235 -0
- package/src/patterns/task-draft.js +29 -0
- package/src/patterns/task-review.js +45 -0
- package/src/patterns/tdd.js +231 -0
- package/src/patterns/team-strategies.js +67 -0
- package/src/performance.js +43 -0
- package/src/persistence-status.js +35 -0
- package/src/phone-sessions.js +96 -0
- package/src/preview-overlay.js +392 -0
- package/src/preview.js +230 -0
- package/src/privacy.js +23 -0
- package/src/project-report.js +22 -0
- package/src/project-team-budget.js +22 -0
- package/src/project-team-model.js +225 -0
- package/src/project-team-policy.js +42 -0
- package/src/project-team-store.js +234 -0
- package/src/project-team-usage.js +24 -0
- package/src/project-teams-api.js +43 -0
- package/src/project-teams.js +644 -0
- package/src/projects.js +290 -0
- package/src/provider-eligibility.js +9 -0
- package/src/readiness.js +98 -0
- package/src/remote-tls.js +66 -0
- package/src/remote-workers/bundles.js +198 -0
- package/src/remote-workers/client.js +332 -0
- package/src/remote-workers/durability.js +37 -0
- package/src/remote-workers/protocol.js +68 -0
- package/src/remote-workers/stdio.js +138 -0
- package/src/remote-workers/worker.js +304 -0
- package/src/remote.js +204 -0
- package/src/repo-config.js +201 -0
- package/src/repo-coordination.js +725 -0
- package/src/repo-tree.js +106 -0
- package/src/repository-identity.js +13 -0
- package/src/retention-policy.js +466 -0
- package/src/review-inbox.js +38 -0
- package/src/routing.js +49 -0
- package/src/run-analysis.js +111 -0
- package/src/run-cold-store.js +231 -0
- package/src/run-environment.js +15 -0
- package/src/run-history.js +91 -0
- package/src/run-log.js +160 -0
- package/src/run-permissions.js +111 -0
- package/src/run-projection.js +77 -0
- package/src/run-recovery.js +53 -0
- package/src/run-status.js +56 -0
- package/src/serve.js +138 -0
- package/src/server.js +2871 -0
- package/src/shared-access.js +103 -0
- package/src/shared-accounts.js +381 -0
- package/src/shared-api.js +239 -0
- package/src/shell.js +346 -0
- package/src/ship.js +344 -0
- package/src/slots.js +74 -0
- package/src/snapshot.js +388 -0
- package/src/source-context.js +17 -0
- package/src/sse-writer.js +70 -0
- package/src/ssh-projects/bootstrap.mjs +95 -0
- package/src/ssh-projects/config.js +45 -0
- package/src/ssh-projects/gateway.js +143 -0
- package/src/ssh-projects/manager.js +320 -0
- package/src/ssh-projects/team-autostart.mjs +93 -0
- package/src/ssh-projects/team-remove.mjs +69 -0
- package/src/ssh-projects/team-status.mjs +39 -0
- package/src/ssh-projects/transport.js +133 -0
- package/src/stats.js +139 -0
- package/src/store.js +544 -0
- package/src/stream.js +91 -0
- package/src/sweeps.js +241 -0
- package/src/tailscale.js +101 -0
- package/src/task-draft.js +23 -0
- package/src/task-team.js +164 -0
- package/src/team-evaluation.js +243 -0
- package/src/team-mailbox.js +160 -0
- package/src/team-memory.js +84 -0
- package/src/team-rerun-graph.js +15 -0
- package/src/team-reruns.js +130 -0
- package/src/team-scheduler.js +121 -0
- package/src/team-steering.js +21 -0
- package/src/team-template-store.js +98 -0
- package/src/team-templates.js +117 -0
- package/src/team-tool-client.mjs +79 -0
- package/src/team-tools.js +235 -0
- package/src/teams.js +0 -0
- package/src/templates.js +62 -0
- package/src/transcript.js +134 -0
- package/src/verification-integrity.js +27 -0
- package/src/verification.js +183 -0
- package/src/watch.js +195 -0
- package/src/work-queue.js +47 -0
- package/src/workflow-control.js +83 -0
- package/src/workspace-api.js +207 -0
- package/src/workspace-changes.js +61 -0
- package/src/workspace.js +146 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { prepareAgentTools } from "./agent-tools.js";
|
|
2
|
+
import { discoverModels } from "./model-discovery.js";
|
|
3
|
+
import { managedPermissionProblem } from "./managed-permissions.js";
|
|
4
|
+
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
5
|
+
import { homedir, tmpdir } from "node:os";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
|
+
import { codexTransport } from "../config.js";
|
|
8
|
+
import { logWarn } from "../log.js";
|
|
9
|
+
import { runAppServerTurn } from "./codex-app-server.js";
|
|
10
|
+
import { classifyError, cliErrorLine, resetFromText } from "./errors.js";
|
|
11
|
+
import { captureOutput, commandExists, describeFailure, processErrorKind, startJsonlProcess, stringify, truncate, unknownEvents } from "./process.js";
|
|
12
|
+
import { adapterTeamCapabilities, prepareTeamTools } from "./team-tools.js";
|
|
13
|
+
const appServerCheck = {};
|
|
14
|
+
const RECHECK_MS = 10 * 6e4;
|
|
15
|
+
const codexAdapter = {
|
|
16
|
+
id: "codex",
|
|
17
|
+
discoverModels: (context) => discoverModels("codex", context),
|
|
18
|
+
displayName: "Codex",
|
|
19
|
+
binary: "codex",
|
|
20
|
+
available: () => commandExists("codex"),
|
|
21
|
+
async catalog() {
|
|
22
|
+
const raw = JSON.parse(await captureOutput("codex", ["debug", "models"]));
|
|
23
|
+
const models = (raw.models ?? []).filter((m) => m.slug && m.visibility !== "hide").sort((a, b) => (a.priority ?? 999) - (b.priority ?? 999)).map((m) => ({
|
|
24
|
+
id: m.slug,
|
|
25
|
+
label: m.display_name ?? m.slug,
|
|
26
|
+
efforts: (m.supported_reasoning_levels ?? []).map((l) => l.effort).filter(Boolean),
|
|
27
|
+
defaultEffort: m.default_reasoning_level
|
|
28
|
+
}));
|
|
29
|
+
const config = await readFile(join(process.env.CODEX_HOME ?? join(homedir(), ".codex"), "config.toml"), "utf8").catch(() => "");
|
|
30
|
+
const efforts = [...new Set(models.flatMap((m) => m.efforts ?? []))];
|
|
31
|
+
return {
|
|
32
|
+
models,
|
|
33
|
+
efforts: efforts.length ? efforts : ["low", "medium", "high"],
|
|
34
|
+
defaultModel: topLevelTomlString(config, "model"),
|
|
35
|
+
defaultEffort: topLevelTomlString(config, "model_reasoning_effort"),
|
|
36
|
+
allowCustom: true
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
async run(opts, onEvent) {
|
|
40
|
+
const permissionProblem = await managedPermissionProblem("codex", opts);
|
|
41
|
+
if (permissionProblem) return permissionProblem;
|
|
42
|
+
const supportsTools = !opts.agentContainer && opts.teamTools && (await adapterTeamCapabilities("codex")).scopedTools === "tested";
|
|
43
|
+
const teamTools = supportsTools ? await prepareTeamTools(opts.teamTools) : void 0;
|
|
44
|
+
const agentTools = opts.agentTools && !opts.agentContainer && opts.permissions?.externalTools !== "disabled" ? await prepareAgentTools(opts.agentTools) : void 0;
|
|
45
|
+
if (opts.agentContainer) onEvent({ kind: "status", text: `Codex runs in Docker image ${opts.agentContainer.image}. Live team tools are unavailable in containers; use structured outcomes and next-turn replies.` });
|
|
46
|
+
else if (opts.teamTools && !teamTools) onEvent({ kind: "status", text: "This Codex CLI has no verified per-turn team tools; use structured outcomes and next-turn replies." });
|
|
47
|
+
try {
|
|
48
|
+
const sandbox = opts.mode === "read" ? "read-only" : opts.trust === "full" ? "danger-full-access" : "workspace-write";
|
|
49
|
+
const network = opts.permissions && sandbox !== "danger-full-access" ? ["-c", `sandbox_workspace_write.network_access=${opts.permissions.commandNetwork === "allowed"}`] : opts.mode === "write" && opts.trust === "trusted" ? ["-c", "sandbox_workspace_write.network_access=true"] : [];
|
|
50
|
+
const managedSandbox = opts.permissions && sandbox !== "danger-full-access" ? ["-c", "sandbox_workspace_write.writable_roots=[]", ...opts.permissions.required.includes("workspace-writes") ? ["-c", "sandbox_workspace_write.exclude_slash_tmp=true", "-c", "sandbox_workspace_write.exclude_tmpdir_env_var=true"] : []] : [];
|
|
51
|
+
const nativeTools = [...teamTools?.codexConfig ?? [], ...agentTools?.codexConfig ?? []];
|
|
52
|
+
const teamConfig = [...nativeTools, ...opts.questionProtocol === "structured" || opts.permissions?.nativeDelegation === false ? ["-c", "agents.enabled=false", "-c", "features.multi_agent=false", "-c", "features.multi_agent_v2.enabled=false", "-c", "features.hooks=false"] : []];
|
|
53
|
+
const transport = codexTransport();
|
|
54
|
+
let sessionId;
|
|
55
|
+
let finalText = "";
|
|
56
|
+
let turnError;
|
|
57
|
+
let turnErrorKind;
|
|
58
|
+
let lastError;
|
|
59
|
+
let lastErrorKind;
|
|
60
|
+
let completed = false;
|
|
61
|
+
let resultSeen = () => {
|
|
62
|
+
};
|
|
63
|
+
const unknown = unknownEvents("codex");
|
|
64
|
+
const unknownItems = /* @__PURE__ */ new Set();
|
|
65
|
+
const unknownItem = (type, id) => {
|
|
66
|
+
if (id && unknownItems.has(id)) return;
|
|
67
|
+
if (id) unknownItems.add(id);
|
|
68
|
+
unknown.add(type);
|
|
69
|
+
};
|
|
70
|
+
const handle = (msg) => {
|
|
71
|
+
switch (msg.type) {
|
|
72
|
+
case "thread.started":
|
|
73
|
+
if (!msg.thread_id) break;
|
|
74
|
+
sessionId = msg.thread_id;
|
|
75
|
+
onEvent({ kind: "session", sessionId: msg.thread_id });
|
|
76
|
+
break;
|
|
77
|
+
case "turn.started":
|
|
78
|
+
break;
|
|
79
|
+
case "item.started":
|
|
80
|
+
case "item.updated":
|
|
81
|
+
case "item.completed":
|
|
82
|
+
handleItem(msg.type, msg.item, onEvent, (text) => finalText = text, unknownItem);
|
|
83
|
+
break;
|
|
84
|
+
case "item.delta":
|
|
85
|
+
if (msg.item.type === "command_execution") onEvent({ kind: "tool_result", callId: msg.item.id, output: msg.item.text, isError: false, delta: true });
|
|
86
|
+
else onEvent({ kind: msg.item.type === "reasoning" ? "thinking" : "text", messageId: msg.item.id, text: msg.item.text, delta: true });
|
|
87
|
+
break;
|
|
88
|
+
case "turn.completed": {
|
|
89
|
+
completed = true;
|
|
90
|
+
resultSeen();
|
|
91
|
+
const cached = msg.usage?.cached_input_tokens ?? 0;
|
|
92
|
+
onEvent({
|
|
93
|
+
kind: "usage",
|
|
94
|
+
// Codex counts cached input inside input_tokens.
|
|
95
|
+
inputTokens: msg.usage?.input_tokens !== void 0 ? Math.max(0, msg.usage.input_tokens - cached) : void 0,
|
|
96
|
+
outputTokens: msg.usage?.output_tokens,
|
|
97
|
+
cacheReadTokens: cached || void 0
|
|
98
|
+
});
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
case "turn.failed":
|
|
102
|
+
resultSeen();
|
|
103
|
+
turnError = msg.error?.message ?? lastError ?? "Codex turn failed";
|
|
104
|
+
turnErrorKind = msg.error?.kind ?? (msg.error?.message ? void 0 : lastErrorKind);
|
|
105
|
+
break;
|
|
106
|
+
case "error":
|
|
107
|
+
lastError = msg.message ?? stringify(msg);
|
|
108
|
+
lastErrorKind = msg.kind;
|
|
109
|
+
onEvent({ kind: "status", text: `Codex: ${lastError}` });
|
|
110
|
+
break;
|
|
111
|
+
// A notification app-server sent that its client doesn't know (codex-app-server.ts).
|
|
112
|
+
case "unrecognized":
|
|
113
|
+
unknown.add(msg.name);
|
|
114
|
+
break;
|
|
115
|
+
default:
|
|
116
|
+
unknown.add(msg.type);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const onText = (line) => onEvent({ kind: "status", text: line });
|
|
120
|
+
let outcome;
|
|
121
|
+
let schemaFile;
|
|
122
|
+
const missing = appServerCheck.missing && Date.now() - appServerCheck.missing.at < RECHECK_MS ? appServerCheck.missing : void 0;
|
|
123
|
+
if (transport === "app-server" || transport === "auto" && !missing) {
|
|
124
|
+
const served = await runAppServerTurn({
|
|
125
|
+
prompt: opts.prompt,
|
|
126
|
+
cwd: opts.cwd,
|
|
127
|
+
sandbox,
|
|
128
|
+
resumeThreadId: opts.resumeSessionId,
|
|
129
|
+
forkThreadId: !opts.resumeSessionId ? opts.forkSessionId : void 0,
|
|
130
|
+
model: opts.model,
|
|
131
|
+
effort: opts.effort,
|
|
132
|
+
outputSchema: opts.jsonSchema,
|
|
133
|
+
questionProtocol: opts.questionProtocol,
|
|
134
|
+
config: [...network, ...teamConfig, ...managedSandbox],
|
|
135
|
+
threadConfig: { ...teamTools?.codexThreadConfig, ...agentTools?.codexThreadConfig, ...Object.fromEntries([...network, ...teamConfig.slice(nativeTools.length), ...managedSandbox].filter((v) => v !== "-c").map((v) => {
|
|
136
|
+
const split = v.indexOf("=");
|
|
137
|
+
return [v.slice(0, split), JSON.parse(v.slice(split + 1))];
|
|
138
|
+
})) },
|
|
139
|
+
signal: opts.signal,
|
|
140
|
+
timeoutMs: opts.timeoutMs,
|
|
141
|
+
idleTimeoutMs: opts.idleTimeoutMs,
|
|
142
|
+
isPaused: opts.isPaused,
|
|
143
|
+
onMessage: handle,
|
|
144
|
+
onText,
|
|
145
|
+
onSteerable: opts.onSteerable,
|
|
146
|
+
interactiveApprovals: opts.interactiveApprovals,
|
|
147
|
+
onApproval: opts.onApproval,
|
|
148
|
+
onQuestion: opts.onQuestion,
|
|
149
|
+
agentContainer: opts.agentContainer
|
|
150
|
+
});
|
|
151
|
+
if (!("unavailable" in served)) {
|
|
152
|
+
outcome = served;
|
|
153
|
+
appServerCheck.missing = void 0;
|
|
154
|
+
} else if (transport === "app-server") {
|
|
155
|
+
const error = `codex app-server isn't available (${served.unavailable}); unset CODEFLOW_CODEX_TRANSPORT to fall back to codex exec`;
|
|
156
|
+
onEvent({ kind: "error", message: error });
|
|
157
|
+
return { ok: false, finalText, error, errorKind: "cli_incompatible" };
|
|
158
|
+
} else {
|
|
159
|
+
if (!appServerCheck.missing) logWarn("codex app-server isn't available; using codex exec", { reason: served.unavailable });
|
|
160
|
+
appServerCheck.missing = { reason: served.unavailable, at: Date.now() };
|
|
161
|
+
sessionId = void 0;
|
|
162
|
+
onEvent({ kind: "status", text: `codex app-server isn't available (${served.unavailable.split("\n")[0]}); using codex exec \u2014 your messages wait for the next turn` });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (!outcome) {
|
|
166
|
+
if (opts.permissions?.toolApprovals) return { ok: false, finalText: "", error: "This run requires interactive approvals; Codex exec cannot preserve them. Use an available app-server transport.", errorKind: "cli_incompatible" };
|
|
167
|
+
if (opts.interactiveApprovals) onEvent({ kind: "status", text: "Native tool approvals require Codex app-server; this exec turn remains headless." });
|
|
168
|
+
const common = ["--json", "--skip-git-repo-check", ...opts.permissions ? ["-c", 'approval_policy="never"'] : [], "-c", `sandbox_mode="${sandbox}"`, ...network, ...teamConfig, ...managedSandbox];
|
|
169
|
+
if (opts.model) common.push("-m", opts.model);
|
|
170
|
+
if (opts.effort) common.push("-c", `model_reasoning_effort="${opts.effort}"`);
|
|
171
|
+
schemaFile = opts.jsonSchema ? await writeSchemaFile(opts.jsonSchema) : void 0;
|
|
172
|
+
if (schemaFile) common.push("--output-schema", schemaFile);
|
|
173
|
+
const args = opts.resumeSessionId ? ["exec", "resume", ...common, opts.resumeSessionId, "-"] : ["exec", ...common, "-C", opts.cwd, "-"];
|
|
174
|
+
const proc = startJsonlProcess({
|
|
175
|
+
command: "codex",
|
|
176
|
+
args,
|
|
177
|
+
input: opts.prompt,
|
|
178
|
+
cwd: opts.cwd,
|
|
179
|
+
signal: opts.signal,
|
|
180
|
+
timeoutMs: opts.timeoutMs,
|
|
181
|
+
idleTimeoutMs: opts.idleTimeoutMs,
|
|
182
|
+
isPaused: opts.isPaused,
|
|
183
|
+
onText,
|
|
184
|
+
onJson: handle,
|
|
185
|
+
agentContainer: opts.agentContainer ? { config: opts.agentContainer, mode: opts.mode, readOnly: schemaFile ? [schemaFile] : void 0 } : void 0
|
|
186
|
+
});
|
|
187
|
+
resultSeen = () => proc.resultSeen();
|
|
188
|
+
outcome = await proc.outcome;
|
|
189
|
+
}
|
|
190
|
+
if (schemaFile) await rm(dirname(schemaFile), { recursive: true, force: true }).catch(() => void 0);
|
|
191
|
+
const unknownCount = unknown.count;
|
|
192
|
+
const drift = unknown.report();
|
|
193
|
+
if (drift) onEvent({ kind: "status", text: drift });
|
|
194
|
+
if (!completed || turnError || outcome.aborted) {
|
|
195
|
+
const stopped = processErrorKind(outcome);
|
|
196
|
+
const said = turnError ?? (stopped ? void 0 : lastError);
|
|
197
|
+
const error = said ?? describeFailure("codex", outcome);
|
|
198
|
+
const stderrLine = cliErrorLine(outcome.stderr);
|
|
199
|
+
const fromText = classifyError(said);
|
|
200
|
+
const errorKind = stopped ?? (turnError ? turnErrorKind : lastErrorKind) ?? (turnError || fromText !== "other" ? fromText : classifyError(stderrLine));
|
|
201
|
+
const resetsAt = errorKind === "usage_limit" ? resetFromText(said) ?? (turnError ? void 0 : resetFromText(stderrLine)) : void 0;
|
|
202
|
+
onEvent({ kind: "error", message: error });
|
|
203
|
+
return {
|
|
204
|
+
ok: false,
|
|
205
|
+
finalText,
|
|
206
|
+
sessionId: errorKind === "session_not_found" ? void 0 : sessionId,
|
|
207
|
+
error,
|
|
208
|
+
errorKind,
|
|
209
|
+
...resetsAt && { resetsAt }
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
if (!finalText.trim() && unknownCount) {
|
|
213
|
+
const error = "Codex finished the turn, but code-flow didn't recognise its output, so its answer is missing. Update codex-cli or code-flow.";
|
|
214
|
+
onEvent({ kind: "error", message: error });
|
|
215
|
+
return { ok: false, finalText, sessionId, error, errorKind: "cli_incompatible" };
|
|
216
|
+
}
|
|
217
|
+
let structured;
|
|
218
|
+
if (opts.jsonSchema) {
|
|
219
|
+
try {
|
|
220
|
+
structured = JSON.parse(finalText);
|
|
221
|
+
} catch {
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return { ok: true, finalText, sessionId, structured };
|
|
225
|
+
} finally {
|
|
226
|
+
await teamTools?.cleanup();
|
|
227
|
+
await agentTools?.cleanup();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
async function writeSchemaFile(schema) {
|
|
232
|
+
const dir = await mkdtemp(join(tmpdir(), "codeflow-schema-"));
|
|
233
|
+
const file = join(dir, "schema.json");
|
|
234
|
+
await writeFile(file, JSON.stringify(schema));
|
|
235
|
+
return file;
|
|
236
|
+
}
|
|
237
|
+
function topLevelTomlString(toml, key) {
|
|
238
|
+
const top = toml.split(/^\s*\[/m)[0] ?? "";
|
|
239
|
+
return top.match(new RegExp(`^\\s*${key}\\s*=\\s*"([^"]*)"`, "m"))?.[1];
|
|
240
|
+
}
|
|
241
|
+
function handleItem(phase, item, onEvent, setFinal, unknown) {
|
|
242
|
+
if (!item) return;
|
|
243
|
+
const done = phase === "item.completed";
|
|
244
|
+
switch (item.type) {
|
|
245
|
+
case "agent_message":
|
|
246
|
+
if (done && item.text) {
|
|
247
|
+
setFinal(item.text);
|
|
248
|
+
onEvent({ kind: "text", text: item.text, messageId: item.id, phase: item.phase });
|
|
249
|
+
} else if (done && typeof item.text !== "string") {
|
|
250
|
+
unknown("agent_message without text", item.id);
|
|
251
|
+
}
|
|
252
|
+
break;
|
|
253
|
+
case "reasoning":
|
|
254
|
+
if (done && item.text) onEvent({ kind: "thinking", text: item.text, messageId: item.id });
|
|
255
|
+
break;
|
|
256
|
+
case "command_execution":
|
|
257
|
+
if (phase === "item.started") {
|
|
258
|
+
onEvent({ kind: "tool_call", callId: item.id, name: "shell", input: item.command });
|
|
259
|
+
} else if (done) {
|
|
260
|
+
onEvent({
|
|
261
|
+
kind: "tool_result",
|
|
262
|
+
callId: item.id,
|
|
263
|
+
output: truncate(item.aggregated_output ?? ""),
|
|
264
|
+
isError: item.exit_code !== 0 && item.exit_code != null || item.status === "declined"
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
break;
|
|
268
|
+
case "file_change":
|
|
269
|
+
if (done) {
|
|
270
|
+
const changes = (item.changes ?? []).map((c) => `${c.kind} ${c.path}`).join("\n");
|
|
271
|
+
onEvent({ kind: "tool_call", callId: item.id, name: "file_change", input: changes });
|
|
272
|
+
onEvent({ kind: "tool_result", callId: item.id, output: item.status ?? "", isError: item.status === "failed" || item.status === "declined" });
|
|
273
|
+
}
|
|
274
|
+
break;
|
|
275
|
+
case "mcp_tool_call":
|
|
276
|
+
if (phase === "item.started") {
|
|
277
|
+
onEvent({
|
|
278
|
+
kind: "tool_call",
|
|
279
|
+
callId: item.id,
|
|
280
|
+
name: `${item.server}.${item.tool}`,
|
|
281
|
+
input: truncate(stringify(item.arguments ?? ""), 2e3)
|
|
282
|
+
});
|
|
283
|
+
} else if (done) {
|
|
284
|
+
onEvent({
|
|
285
|
+
kind: "tool_result",
|
|
286
|
+
callId: item.id,
|
|
287
|
+
output: truncate(Array.isArray(item.result?.content) ? item.result.content.map((content) => content.type === "text" ? content.text : `[${content.type}${content.mimeType ? `: ${content.mimeType}` : ""}]`).join("\n") : stringify(item.result ?? item.error ?? "")),
|
|
288
|
+
isError: item.status === "failed"
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
break;
|
|
292
|
+
case "web_search":
|
|
293
|
+
if (done) onEvent({ kind: "status", text: `web search: ${item.query ?? ""}` });
|
|
294
|
+
break;
|
|
295
|
+
case "todo_list":
|
|
296
|
+
if (done) {
|
|
297
|
+
const todos = (item.items ?? []).map((t) => `${t.completed ? "\u2713" : "\u25CB"} ${t.text}`).join("\n");
|
|
298
|
+
onEvent({ kind: "status", text: todos });
|
|
299
|
+
}
|
|
300
|
+
break;
|
|
301
|
+
case "error":
|
|
302
|
+
if (done) onEvent({ kind: "error", message: item.message ?? "error" });
|
|
303
|
+
break;
|
|
304
|
+
default:
|
|
305
|
+
unknown(`${item.type === "unrecognized" ? item.name ?? "(no type)" : item.type} item`, item.id);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
export {
|
|
309
|
+
appServerCheck,
|
|
310
|
+
codexAdapter
|
|
311
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const status = (codes) => String.raw`\b(?:status|code|http(?:/[\d.]+)?|error)\b[\s:=#]*(?:${codes})\b|\b(?:${codes})\s+(?:unauthorized|too many requests|internal server error|bad gateway|service unavailable|gateway time-?out|overloaded)`;
|
|
2
|
+
const PATTERNS = [
|
|
3
|
+
// Claude: "No conversation found with session ID: …"; Codex: "no rollout found for thread id …"
|
|
4
|
+
["session_not_found", /no conversation found|no rollout found|(?:thread|conversation|session) (?:not found|does not exist)/i],
|
|
5
|
+
// commander (Claude, agy): "error: unknown option '--x'"; clap (Codex): "error: unexpected argument '--x' found"
|
|
6
|
+
["cli_incompatible", /unknown option|unknown flag|unrecognized (?:option|argument|subcommand)|unexpected argument|wasn't expected|invalid value for '--/i],
|
|
7
|
+
[
|
|
8
|
+
"auth",
|
|
9
|
+
new RegExp(String.raw`not logged in|please run /login|run \`?\w+ login\`?|invalid api key|oauth token (?:has )?expired|token (?:has )?expired|login (?:has )?expired|authentication_error|unauthenticated|unauthorized|(?:log|sign) in again|${status("401")}`, "i")
|
|
10
|
+
],
|
|
11
|
+
// Per-minute limits, even when worded as a quota (Google's 429s say "Quota exceeded for quota
|
|
12
|
+
// metric '… requests per minute'"). Told apart before usage limits, which stop the run and keep
|
|
13
|
+
// Auto away from the agent until the plan resets: taking a 429 for one costs far more than the
|
|
14
|
+
// reverse (a retry that fails again).
|
|
15
|
+
["rate_limit", /\bper (?:minute|second|min\b)|\brequests per min/i],
|
|
16
|
+
// Google's RESOURCE_EXHAUSTED is also its per-minute 429: a usage limit only when it says it's the plan's or the day's.
|
|
17
|
+
["usage_limit", /RESOURCE_EXHAUSTED[^\n]*(?:daily|per day|plan\b|usage limit)|(?:daily|per day|plan\b|usage limit)[^\n]*RESOURCE_EXHAUSTED/i],
|
|
18
|
+
[
|
|
19
|
+
"usage_limit",
|
|
20
|
+
/usage limit|hit your (?:\w+[ -])?limit|quota (?:exceeded|reached|exhausted)|exceeded (?:your|the) [\w -]{0,30}quota|out of credits|credit balance is too low/i
|
|
21
|
+
],
|
|
22
|
+
// A plan's "limit reached" (below) is a usage limit unless it says "rate".
|
|
23
|
+
["rate_limit", new RegExp(String.raw`too many requests|rate.?limit|RESOURCE_EXHAUSTED|${status("429")}`, "i")],
|
|
24
|
+
// "5-hour limit reached", "Weekly limit reached": not a context, token or turn limit.
|
|
25
|
+
["usage_limit", /(?<!\b(?:context|token|tokens|turn|turns|output|length|size|depth|recursion) )limit reached/i],
|
|
26
|
+
[
|
|
27
|
+
"transient",
|
|
28
|
+
new RegExp(
|
|
29
|
+
String.raw`overloaded|internal server error|bad gateway|service unavailable|gateway timeout|temporar(?:il)?y unavailable|ECONNRESET|ECONNREFUSED|ETIMEDOUT|EAI_AGAIN|ENOTFOUND|socket hang up|fetch failed|network error|connection (?:reset|closed|error|refused|failed)|stream disconnected|${status("50[0234]|529")}`,
|
|
30
|
+
"i"
|
|
31
|
+
)
|
|
32
|
+
]
|
|
33
|
+
];
|
|
34
|
+
function classifyError(text) {
|
|
35
|
+
if (!text) return "other";
|
|
36
|
+
return PATTERNS.find(([, re]) => re.test(text))?.[0] ?? "other";
|
|
37
|
+
}
|
|
38
|
+
function cliErrorLine(stderr) {
|
|
39
|
+
const lines = (stderr ?? "").split("\n").map((l) => l.trim()).filter(Boolean);
|
|
40
|
+
return [...lines].reverse().find((l) => /^(?:\w+ )?(?:error|fatal|panic)\b/i.test(l)) ?? lines.at(-1) ?? "";
|
|
41
|
+
}
|
|
42
|
+
const UNIT_MS = { d: 864e5, h: 36e5, m: 6e4, s: 1e3 };
|
|
43
|
+
function resetFromText(text, now = Date.now()) {
|
|
44
|
+
if (!text) return void 0;
|
|
45
|
+
const epoch = /limit reached\|(\d{10})\b/i.exec(text);
|
|
46
|
+
if (epoch) return Number(epoch[1]) * 1e3;
|
|
47
|
+
const after = /try again in ((?:\d+\s*(?:days?|d|hours?|hrs?|h|minutes?|mins?|m|seconds?|secs?|s)\b[\s,]*(?:and\s+)?)+)/i.exec(text);
|
|
48
|
+
if (!after) return void 0;
|
|
49
|
+
let ms = 0;
|
|
50
|
+
for (const [, n, unit] of after[1].matchAll(/(\d+)\s*([a-z]+)/gi)) ms += Number(n) * (UNIT_MS[unit[0].toLowerCase()] ?? 0);
|
|
51
|
+
return ms ? now + ms : void 0;
|
|
52
|
+
}
|
|
53
|
+
export {
|
|
54
|
+
classifyError,
|
|
55
|
+
cliErrorLine,
|
|
56
|
+
resetFromText
|
|
57
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { claudeAdapter } from "./claude.js";
|
|
2
|
+
import { codexAdapter } from "./codex.js";
|
|
3
|
+
import { agyAdapter } from "./agy.js";
|
|
4
|
+
export * from "./types.js";
|
|
5
|
+
const adapters = {
|
|
6
|
+
claude: claudeAdapter,
|
|
7
|
+
codex: codexAdapter,
|
|
8
|
+
agy: agyAdapter
|
|
9
|
+
};
|
|
10
|
+
const AGENT_IDS = Object.keys(adapters);
|
|
11
|
+
function isAgentId(value) {
|
|
12
|
+
return typeof value === "string" && Object.hasOwn(adapters, value);
|
|
13
|
+
}
|
|
14
|
+
function getAdapter(id) {
|
|
15
|
+
if (!Object.hasOwn(adapters, id) || !adapters[id]) throw new Error(`Provider ${id} is unavailable; restore its pinned extension or choose an installed provider`);
|
|
16
|
+
return adapters[id];
|
|
17
|
+
}
|
|
18
|
+
function registerProviderAdapters(providers) {
|
|
19
|
+
const ids = providers.map((provider) => provider.id);
|
|
20
|
+
if (new Set(ids).size !== ids.length || ids.some((id) => !id.startsWith("plugin:") || Object.hasOwn(adapters, id))) throw new Error("Provider IDs must be unique and cannot replace installed adapters");
|
|
21
|
+
for (const provider of providers) {
|
|
22
|
+
adapters[provider.id] = Object.freeze({ ...provider });
|
|
23
|
+
AGENT_IDS.push(provider.id);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function replaceAcpAdapters(providers) {
|
|
27
|
+
if (providers.some((provider) => !provider.id.startsWith("acp:")) || new Set(providers.map((provider) => provider.id)).size !== providers.length) throw new Error("Invalid ACP provider registration");
|
|
28
|
+
for (const id of AGENT_IDS.filter((id2) => id2.startsWith("acp:"))) {
|
|
29
|
+
delete adapters[id];
|
|
30
|
+
catalogCache.delete(id);
|
|
31
|
+
}
|
|
32
|
+
AGENT_IDS.splice(0, AGENT_IDS.length, ...AGENT_IDS.filter((id) => !id.startsWith("acp:")));
|
|
33
|
+
for (const provider of providers) {
|
|
34
|
+
adapters[provider.id] = Object.freeze({ ...provider });
|
|
35
|
+
AGENT_IDS.push(provider.id);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const CATALOG_TTL_MS = 10 * 6e4;
|
|
39
|
+
const catalogCache = /* @__PURE__ */ new Map();
|
|
40
|
+
function agentCatalog(id) {
|
|
41
|
+
const cached = catalogCache.get(id);
|
|
42
|
+
if (cached && Date.now() - cached.at < CATALOG_TTL_MS) return cached.value;
|
|
43
|
+
const value = getAdapter(id).catalog();
|
|
44
|
+
catalogCache.set(id, { at: Date.now(), value });
|
|
45
|
+
value.catch(() => catalogCache.delete(id));
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
const MODEL_RE = /^[A-Za-z0-9][\w.:/[\]@-]{0,99}$/;
|
|
49
|
+
const EFFORT_RE = /^[a-z]{2,12}$/;
|
|
50
|
+
function validateAgentSettings(input) {
|
|
51
|
+
const raw = input && typeof input === "object" ? input : {};
|
|
52
|
+
const settings = {};
|
|
53
|
+
for (const id of AGENT_IDS) {
|
|
54
|
+
const model = typeof raw[id]?.model === "string" ? raw[id].model.trim() : "";
|
|
55
|
+
const effort = typeof raw[id]?.effort === "string" ? raw[id].effort.trim() : "";
|
|
56
|
+
if (model && !(id.startsWith("acp:") ? model.length <= 256 && !/[\x00-\x1f\x7f]/.test(model) : MODEL_RE.test(model))) throw new Error(`Invalid model name for ${getAdapter(id).displayName}: "${model}"`);
|
|
57
|
+
if (effort && !(id.startsWith("acp:") ? effort.length <= 64 && !/[\x00-\x1f\x7f]/.test(effort) : EFFORT_RE.test(effort))) throw new Error(`Invalid effort for ${getAdapter(id).displayName}: "${effort}"`);
|
|
58
|
+
if (model || effort) settings[id] = { ...model && { model }, ...effort && { effort } };
|
|
59
|
+
if (raw[id]?.variants !== void 0) {
|
|
60
|
+
if (!Array.isArray(raw[id].variants) || raw[id].variants.length > 128) throw new Error("Choose at most 128 additional models per provider");
|
|
61
|
+
const variants = raw[id].variants.map((value) => {
|
|
62
|
+
if (!value || typeof value.model !== "string" || !value.model.trim() || value.variants !== void 0) throw new Error("Each additional agent needs a model");
|
|
63
|
+
const valid = validateAgentSettings({ [id]: { model: value.model, effort: value.effort } })[id];
|
|
64
|
+
return { model: valid.model, ...valid.effort ? { effort: valid.effort } : {} };
|
|
65
|
+
});
|
|
66
|
+
settings[id] = { ...settings[id], variants };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return settings;
|
|
70
|
+
}
|
|
71
|
+
async function agentAvailability() {
|
|
72
|
+
return Promise.all(
|
|
73
|
+
AGENT_IDS.map(async (id) => ({
|
|
74
|
+
id,
|
|
75
|
+
displayName: getAdapter(id).displayName,
|
|
76
|
+
binary: getAdapter(id).binary,
|
|
77
|
+
available: await getAdapter(id).available()
|
|
78
|
+
}))
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
export {
|
|
82
|
+
AGENT_IDS,
|
|
83
|
+
adapters,
|
|
84
|
+
agentAvailability,
|
|
85
|
+
agentCatalog,
|
|
86
|
+
getAdapter,
|
|
87
|
+
isAgentId,
|
|
88
|
+
registerProviderAdapters,
|
|
89
|
+
replaceAcpAdapters,
|
|
90
|
+
validateAgentSettings
|
|
91
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { captureOutput, runJsonlProcess } from "./process.js";
|
|
2
|
+
async function managedPermissionProblem(provider, opts) {
|
|
3
|
+
const p = opts.permissions;
|
|
4
|
+
if (!p) return;
|
|
5
|
+
const fail = (error) => ({ ok: false, finalText: "", error, errorKind: "cli_incompatible" });
|
|
6
|
+
if (p.nativeDelegation !== false && !opts.conversation || p.provider !== provider || p.trust !== (opts.trust ?? "standard") || p.mode !== opts.mode || p.toolApprovals !== !!opts.interactiveApprovals) return fail("Adapter options do not match the resolved run permission policy");
|
|
7
|
+
if (p.toolApprovals && (!["codex", "claude"].includes(provider) || !opts.onApproval)) return fail("The required interactive approval handler is unavailable");
|
|
8
|
+
try {
|
|
9
|
+
const probe = async (args) => {
|
|
10
|
+
if (!opts.agentContainer) return captureOutput(provider, args, 5e3);
|
|
11
|
+
let text = "";
|
|
12
|
+
const result = await runJsonlProcess({ command: provider, args, cwd: opts.cwd, signal: opts.signal, timeoutMs: 5e3, agentContainer: { config: opts.agentContainer, mode: "read" }, onText: (line) => {
|
|
13
|
+
text += line + "\n";
|
|
14
|
+
}, onJson: (value) => {
|
|
15
|
+
text += JSON.stringify(value) + "\n";
|
|
16
|
+
} });
|
|
17
|
+
if (result.exitCode !== 0 || result.aborted || result.timedOut || text.length > 128e3) throw new Error("Container CLI capability probe failed");
|
|
18
|
+
return text;
|
|
19
|
+
};
|
|
20
|
+
const help = await probe(["--help"]);
|
|
21
|
+
let nested = p.nativeDelegation || provider === "claude" && ["--disallowedTools", "--settings"].every((flag) => help.includes(flag));
|
|
22
|
+
if (provider === "codex" && !p.nativeDelegation) {
|
|
23
|
+
const version = (await probe(["--version"])).match(/codex-cli (\d+)\.(\d+)\.(\d+)/);
|
|
24
|
+
if (!version || Number(version[1]) === 0 && (Number(version[2]) < 162 || Number(version[2]) === 162 && Number(version[3]) < 1)) return fail("codex: managed delegation requires CLI 0.162.1 or newer (agents.enabled control)");
|
|
25
|
+
const features = await probe(["features", "list", "-c", "agents.enabled=false", "-c", "features.multi_agent=false", "-c", "features.multi_agent_v2.enabled=false"]);
|
|
26
|
+
nested = ["multi_agent", "multi_agent_v2"].every((key) => new RegExp(`^${key}\\s+\\S+\\s+false\\s*$`, "m").test(features));
|
|
27
|
+
}
|
|
28
|
+
const external = provider === "claude" && ["--mcp-config", "--strict-mcp-config", "--disallowedTools", "--no-chrome"].every((flag) => help.includes(flag));
|
|
29
|
+
if (!nested) return fail(`${provider}: installed CLI cannot confirm native-delegation controls; update it before launching this managed run`);
|
|
30
|
+
if (p.externalTools === "disabled" && !external) return fail(`${provider}: installed CLI cannot confirm external-tool isolation`);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
return fail(`Could not verify ${provider} permission controls: ${error.message}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export {
|
|
36
|
+
managedPermissionProblem
|
|
37
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { startJsonlProcess } from "./process.js";
|
|
6
|
+
async function discoverModels(provider, context = {}) {
|
|
7
|
+
if (provider !== "claude" && provider !== "codex")
|
|
8
|
+
throw new Error("Model discovery is unsupported by this adapter");
|
|
9
|
+
const temporary = context.cwd ? void 0 : await mkdtemp(join(tmpdir(), "codeflow-models-"));
|
|
10
|
+
const cwd = context.cwd ?? temporary;
|
|
11
|
+
const args = provider === "codex" ? ["app-server"] : ["-p", "--input-format", "stream-json", "--output-format", "stream-json", "--verbose", "--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}', "--no-chrome", "--tools", "", "--settings", '{"disableAllHooks":true,"disableWorkflows":true,"ultracode":false}'];
|
|
12
|
+
let sequence = 0, bytes = 0;
|
|
13
|
+
const pending = /* @__PURE__ */ new Map();
|
|
14
|
+
const process = startJsonlProcess({ command: provider, args, cwd, timeoutMs: 12e3, interactive: true, ...context.agentContainer ? { agentContainer: { config: context.agentContainer, mode: "read" } } : {}, onText: () => {
|
|
15
|
+
}, onJson(value) {
|
|
16
|
+
bytes += JSON.stringify(value).length;
|
|
17
|
+
if (bytes > 2e6) {
|
|
18
|
+
process.kill();
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const response = provider === "claude" ? value.type === "control_response" ? value.response : void 0 : value;
|
|
22
|
+
const id = response?.request_id ?? response?.id;
|
|
23
|
+
const waiter = pending.get(String(id));
|
|
24
|
+
if (!waiter)
|
|
25
|
+
return;
|
|
26
|
+
pending.delete(String(id));
|
|
27
|
+
if (response.error || response.subtype === "error")
|
|
28
|
+
waiter.reject(new Error("Provider rejected model discovery"));
|
|
29
|
+
else
|
|
30
|
+
waiter.resolve(provider === "claude" ? response.response : response.result);
|
|
31
|
+
} });
|
|
32
|
+
const request = (method, params = {}) => new Promise((resolve, reject) => {
|
|
33
|
+
const id = String(++sequence);
|
|
34
|
+
pending.set(id, { resolve, reject });
|
|
35
|
+
process.send(provider === "codex" ? { id, method, params } : { type: "control_request", request_id: id, request: { subtype: method, ...params } });
|
|
36
|
+
});
|
|
37
|
+
void process.outcome.then(() => {
|
|
38
|
+
for (const waiter of pending.values())
|
|
39
|
+
waiter.reject(new Error("Model discovery unavailable or timed out"));
|
|
40
|
+
pending.clear();
|
|
41
|
+
});
|
|
42
|
+
try {
|
|
43
|
+
const init = await request("initialize", provider === "codex" ? { clientInfo: { name: "code_flow_catalog", version: "0.1.0" } } : {});
|
|
44
|
+
let raw, account, source;
|
|
45
|
+
if (provider === "codex") {
|
|
46
|
+
process.send({ method: "initialized", params: {} });
|
|
47
|
+
account = (await request("account/read", { refreshToken: false }).catch(() => void 0))?.account;
|
|
48
|
+
raw = [];
|
|
49
|
+
let cursor;
|
|
50
|
+
for (let page = 0; page < 8; page++) {
|
|
51
|
+
const response = await request("model/list", { limit: 100, ...cursor ? { cursor } : {} });
|
|
52
|
+
if (!Array.isArray(response?.data))
|
|
53
|
+
throw new Error("Invalid model discovery response");
|
|
54
|
+
raw.push(...response.data);
|
|
55
|
+
cursor = response.nextCursor;
|
|
56
|
+
if (!cursor)
|
|
57
|
+
break;
|
|
58
|
+
if (page === 7)
|
|
59
|
+
throw new Error("Model discovery exceeds the bounded catalog");
|
|
60
|
+
}
|
|
61
|
+
source = "codex app-server model/list";
|
|
62
|
+
} else {
|
|
63
|
+
raw = init?.models;
|
|
64
|
+
account = init?.account;
|
|
65
|
+
source = "claude initialize models";
|
|
66
|
+
}
|
|
67
|
+
if (!Array.isArray(raw) || raw.length > 800)
|
|
68
|
+
throw new Error("Invalid model discovery response");
|
|
69
|
+
const models = [];
|
|
70
|
+
let defaultModel;
|
|
71
|
+
for (const m of raw) {
|
|
72
|
+
if (m.hidden === true)
|
|
73
|
+
continue;
|
|
74
|
+
const id = provider === "claude" ? m.resolvedModel ?? m.value : m.model ?? m.id;
|
|
75
|
+
if (typeof id !== "string" || !/^[A-Za-z0-9][\w.:[\]/@-]{0,199}$/.test(id))
|
|
76
|
+
continue;
|
|
77
|
+
const effortList = provider === "claude" ? m.supportedEffortLevels : m.supportedReasoningEfforts?.map((e) => e.reasoningEffort);
|
|
78
|
+
const efforts = Array.isArray(effortList) ? effortList.filter((e) => typeof e === "string" && /^[a-z]{2,12}$/.test(e) && e !== "ultracode") : m.supportsEffort === false ? [] : void 0;
|
|
79
|
+
const entry = { id, label: typeof m.displayName === "string" ? m.displayName.slice(0, 120) : id, efforts, defaultEffort: m.defaultReasoningEffort, aliases: provider === "claude" && m.value !== id ? [m.value] : [], discovery: { source, confidence: "reported", verifiedAt: Date.now() } };
|
|
80
|
+
const existing = models.find((x) => x.id === id);
|
|
81
|
+
if (existing)
|
|
82
|
+
existing.aliases = [.../* @__PURE__ */ new Set([...existing.aliases ?? [], ...entry.aliases ?? []])];
|
|
83
|
+
else
|
|
84
|
+
models.push(entry);
|
|
85
|
+
if (m.isDefault || m.value === "default")
|
|
86
|
+
defaultModel = id;
|
|
87
|
+
}
|
|
88
|
+
if (!models.length)
|
|
89
|
+
throw new Error("Provider reported no selectable models");
|
|
90
|
+
return { models, efforts: [...new Set(models.flatMap((m) => m.efforts ?? []))], defaultModel, allowCustom: true, discovery: { source, confidence: "reported", verifiedAt: Date.now() }, accountKey: account ? createHash("sha256").update(JSON.stringify(account)).digest("hex") : void 0 };
|
|
91
|
+
} finally {
|
|
92
|
+
process.kill();
|
|
93
|
+
await process.outcome;
|
|
94
|
+
if (temporary)
|
|
95
|
+
await rm(temporary, { recursive: true, force: true });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export {
|
|
99
|
+
discoverModels
|
|
100
|
+
};
|