@kitsy/coop-ai 2.1.0 → 2.1.2
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/dist/index.cjs +289 -6
- package/dist/index.d.cts +51 -3
- package/dist/index.d.ts +51 -3
- package/dist/index.js +287 -6
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -30,6 +30,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
CliAgentClient: () => CliAgentClient,
|
|
34
|
+
CliCompletionProvider: () => CliCompletionProvider,
|
|
33
35
|
buildIdeaRefinementPrompt: () => buildIdeaRefinementPrompt,
|
|
34
36
|
buildTaskRefinementPrompt: () => buildTaskRefinementPrompt,
|
|
35
37
|
build_contract: () => build_contract,
|
|
@@ -928,7 +930,15 @@ var DEFAULT_MODELS = {
|
|
|
928
930
|
openai: "gpt-5-mini",
|
|
929
931
|
anthropic: "claude-3-5-sonnet-latest",
|
|
930
932
|
gemini: "gemini-2.0-flash",
|
|
931
|
-
ollama: "llama3.2"
|
|
933
|
+
ollama: "llama3.2",
|
|
934
|
+
codex_cli: "gpt-5-codex",
|
|
935
|
+
claude_cli: "sonnet",
|
|
936
|
+
gemini_cli: "gemini-2.5-pro"
|
|
937
|
+
};
|
|
938
|
+
var DEFAULT_COMMAND = {
|
|
939
|
+
codex_cli: "codex",
|
|
940
|
+
claude_cli: "claude",
|
|
941
|
+
gemini_cli: "gemini"
|
|
932
942
|
};
|
|
933
943
|
var DEFAULT_KEY_ENV = {
|
|
934
944
|
openai: "OPENAI_API_KEY",
|
|
@@ -958,7 +968,7 @@ function asFinite(value) {
|
|
|
958
968
|
}
|
|
959
969
|
function readProvider(value) {
|
|
960
970
|
const normalized = asString(value)?.toLowerCase();
|
|
961
|
-
if (normalized === "openai" || normalized === "anthropic" || normalized === "gemini" || normalized === "ollama" || normalized === "mock") {
|
|
971
|
+
if (normalized === "openai" || normalized === "anthropic" || normalized === "gemini" || normalized === "ollama" || normalized === "mock" || normalized === "codex_cli" || normalized === "claude_cli" || normalized === "gemini_cli") {
|
|
962
972
|
return normalized;
|
|
963
973
|
}
|
|
964
974
|
return "mock";
|
|
@@ -979,15 +989,34 @@ function resolve_provider_config(config) {
|
|
|
979
989
|
timeout_ms: 6e4
|
|
980
990
|
};
|
|
981
991
|
}
|
|
992
|
+
if (provider === "codex_cli" || provider === "claude_cli" || provider === "gemini_cli") {
|
|
993
|
+
const section2 = lookupProviderSection(ai, provider);
|
|
994
|
+
const model2 = asString(section2.model) ?? asString(ai.model) ?? DEFAULT_MODELS[provider];
|
|
995
|
+
const timeout_ms2 = asFinite(section2.timeout_ms) ?? asFinite(ai.timeout_ms) ?? 3e5;
|
|
996
|
+
const command = asString(section2.command) ?? DEFAULT_COMMAND[provider];
|
|
997
|
+
const argsRaw = Array.isArray(section2.args) ? section2.args : Array.isArray(ai.args) ? ai.args : [];
|
|
998
|
+
const args = argsRaw.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
999
|
+
return {
|
|
1000
|
+
provider,
|
|
1001
|
+
model: model2,
|
|
1002
|
+
command,
|
|
1003
|
+
args,
|
|
1004
|
+
temperature: 0.2,
|
|
1005
|
+
max_output_tokens: 4096,
|
|
1006
|
+
timeout_ms: timeout_ms2
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
982
1009
|
const section = lookupProviderSection(ai, provider);
|
|
983
|
-
const
|
|
984
|
-
const
|
|
1010
|
+
const cloudProvider = provider;
|
|
1011
|
+
const model = asString(section.model) ?? asString(ai.model) ?? DEFAULT_MODELS[cloudProvider];
|
|
1012
|
+
const base_url = asString(section.base_url) ?? asString(ai.base_url) ?? DEFAULT_BASE_URL[cloudProvider];
|
|
985
1013
|
const temperature = asFinite(section.temperature) ?? asFinite(ai.temperature) ?? 0.2;
|
|
986
1014
|
const max_output_tokens = asFinite(section.max_output_tokens) ?? asFinite(ai.max_output_tokens) ?? 1024;
|
|
987
1015
|
const timeout_ms = asFinite(section.timeout_ms) ?? asFinite(ai.timeout_ms) ?? 6e4;
|
|
988
1016
|
let api_key;
|
|
989
1017
|
if (provider !== "ollama") {
|
|
990
|
-
const
|
|
1018
|
+
const keyedProvider = provider;
|
|
1019
|
+
const envName = asString(section.api_key_env) ?? asString(ai.api_key_env) ?? DEFAULT_KEY_ENV[keyedProvider];
|
|
991
1020
|
const envValue = envName ? asString(process.env[envName]) : null;
|
|
992
1021
|
api_key = asString(section.api_key) ?? asString(ai.api_key) ?? envValue ?? void 0;
|
|
993
1022
|
if (!api_key) {
|
|
@@ -1007,6 +1036,250 @@ function resolve_provider_config(config) {
|
|
|
1007
1036
|
};
|
|
1008
1037
|
}
|
|
1009
1038
|
|
|
1039
|
+
// src/providers/cli.ts
|
|
1040
|
+
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
1041
|
+
var import_node_os = __toESM(require("os"), 1);
|
|
1042
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
1043
|
+
var import_node_child_process2 = require("child_process");
|
|
1044
|
+
function defaultRunner(invocation) {
|
|
1045
|
+
const result = (0, import_node_child_process2.spawnSync)(invocation.command, invocation.args, {
|
|
1046
|
+
cwd: invocation.cwd,
|
|
1047
|
+
encoding: "utf8",
|
|
1048
|
+
input: invocation.input,
|
|
1049
|
+
timeout: invocation.timeout_ms,
|
|
1050
|
+
windowsHide: true
|
|
1051
|
+
});
|
|
1052
|
+
return {
|
|
1053
|
+
status: result.status,
|
|
1054
|
+
stdout: result.stdout ?? "",
|
|
1055
|
+
stderr: result.stderr ?? "",
|
|
1056
|
+
error: result.error
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
function asText(result) {
|
|
1060
|
+
return [result.stdout, result.stderr].map((value) => value.trim()).filter(Boolean).join("\n").trim();
|
|
1061
|
+
}
|
|
1062
|
+
function requireSuccess(provider, result) {
|
|
1063
|
+
if ((result.status ?? 1) === 0 && !result.error) {
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
const details = asText(result) || result.error?.message || "unknown error";
|
|
1067
|
+
throw new Error(`${provider} execution failed: ${details}`);
|
|
1068
|
+
}
|
|
1069
|
+
function tempOutputFile(prefix) {
|
|
1070
|
+
return import_node_path3.default.join(import_node_os.default.tmpdir(), `${prefix}-${Date.now()}-${Math.random().toString(16).slice(2)}.txt`);
|
|
1071
|
+
}
|
|
1072
|
+
function combinedPrompt(input) {
|
|
1073
|
+
return [input.system.trim(), input.prompt.trim()].filter(Boolean).join("\n\n");
|
|
1074
|
+
}
|
|
1075
|
+
function completionInvocation(config, input, cwd) {
|
|
1076
|
+
if (config.provider === "codex_cli") {
|
|
1077
|
+
const outputFile = tempOutputFile("coop-codex-complete");
|
|
1078
|
+
return {
|
|
1079
|
+
command: config.command ?? "codex",
|
|
1080
|
+
args: [
|
|
1081
|
+
"exec",
|
|
1082
|
+
"-C",
|
|
1083
|
+
cwd,
|
|
1084
|
+
"--skip-git-repo-check",
|
|
1085
|
+
"--sandbox",
|
|
1086
|
+
"read-only",
|
|
1087
|
+
...config.model ? ["--model", config.model] : [],
|
|
1088
|
+
"--output-last-message",
|
|
1089
|
+
outputFile,
|
|
1090
|
+
...config.args ?? [],
|
|
1091
|
+
"-"
|
|
1092
|
+
],
|
|
1093
|
+
cwd,
|
|
1094
|
+
input: combinedPrompt(input),
|
|
1095
|
+
timeout_ms: config.timeout_ms
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
if (config.provider === "claude_cli") {
|
|
1099
|
+
return {
|
|
1100
|
+
command: config.command ?? "claude",
|
|
1101
|
+
args: [
|
|
1102
|
+
"-p",
|
|
1103
|
+
combinedPrompt(input),
|
|
1104
|
+
"--output-format",
|
|
1105
|
+
"text",
|
|
1106
|
+
"--permission-mode",
|
|
1107
|
+
"plan",
|
|
1108
|
+
...config.model ? ["--model", config.model] : [],
|
|
1109
|
+
...config.args ?? []
|
|
1110
|
+
],
|
|
1111
|
+
cwd,
|
|
1112
|
+
timeout_ms: config.timeout_ms
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
return {
|
|
1116
|
+
command: config.command ?? "gemini",
|
|
1117
|
+
args: [
|
|
1118
|
+
"-p",
|
|
1119
|
+
combinedPrompt(input),
|
|
1120
|
+
"--output-format",
|
|
1121
|
+
"text",
|
|
1122
|
+
"--approval-mode",
|
|
1123
|
+
"plan",
|
|
1124
|
+
...config.model ? ["--model", config.model] : [],
|
|
1125
|
+
...config.args ?? []
|
|
1126
|
+
],
|
|
1127
|
+
cwd,
|
|
1128
|
+
timeout_ms: config.timeout_ms
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
function readCodexOutput(invocation) {
|
|
1132
|
+
const index = invocation.args.indexOf("--output-last-message");
|
|
1133
|
+
const outputFile = index >= 0 ? invocation.args[index + 1] : void 0;
|
|
1134
|
+
if (!outputFile) return null;
|
|
1135
|
+
if (!import_node_fs2.default.existsSync(outputFile)) return null;
|
|
1136
|
+
try {
|
|
1137
|
+
return import_node_fs2.default.readFileSync(outputFile, "utf8").trim();
|
|
1138
|
+
} finally {
|
|
1139
|
+
import_node_fs2.default.rmSync(outputFile, { force: true });
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
var CliCompletionProvider = class {
|
|
1143
|
+
name;
|
|
1144
|
+
config;
|
|
1145
|
+
cwd;
|
|
1146
|
+
runner;
|
|
1147
|
+
constructor(config, options = {}) {
|
|
1148
|
+
this.name = config.provider;
|
|
1149
|
+
this.config = config;
|
|
1150
|
+
this.cwd = import_node_path3.default.resolve(options.cwd ?? process.cwd());
|
|
1151
|
+
this.runner = options.runner ?? defaultRunner;
|
|
1152
|
+
}
|
|
1153
|
+
async complete(input) {
|
|
1154
|
+
const invocation = completionInvocation(this.config, input, this.cwd);
|
|
1155
|
+
const result = this.runner(invocation);
|
|
1156
|
+
requireSuccess(this.name, result);
|
|
1157
|
+
const codexOutput = this.config.provider === "codex_cli" ? readCodexOutput(invocation) : null;
|
|
1158
|
+
return {
|
|
1159
|
+
text: codexOutput || result.stdout.trim() || result.stderr.trim()
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
1163
|
+
function buildExecutionPrompt(task, contract, step, prompt, mode) {
|
|
1164
|
+
const sections = [
|
|
1165
|
+
mode === "execute" ? "You are executing a COOP task inside the repo. Make the necessary file changes, run allowed commands when needed, and finish with a concise summary." : "You are reviewing a COOP task inside the repo. Inspect the current workspace and return concise review findings only.",
|
|
1166
|
+
`Task: ${task.id} - ${task.title}`,
|
|
1167
|
+
`Goal: ${contract.goal}`,
|
|
1168
|
+
`Step: ${step.step} (${step.action})`,
|
|
1169
|
+
contract.context.acceptance_criteria.length > 0 ? `Acceptance: ${contract.context.acceptance_criteria.join(" | ")}` : "",
|
|
1170
|
+
contract.context.tests_required.length > 0 ? `Tests Required: ${contract.context.tests_required.join(" | ")}` : "",
|
|
1171
|
+
contract.context.authority_refs.length > 0 ? `Authority Refs: ${contract.context.authority_refs.join(" | ")}` : "",
|
|
1172
|
+
contract.context.derived_refs.length > 0 ? `Derived Refs: ${contract.context.derived_refs.join(" | ")}` : "",
|
|
1173
|
+
contract.permissions.read_paths.length > 0 ? `Allowed Read Paths: ${contract.permissions.read_paths.join(", ")}` : "",
|
|
1174
|
+
contract.permissions.write_paths.length > 0 ? `Allowed Write Paths: ${contract.permissions.write_paths.join(", ")}` : "",
|
|
1175
|
+
contract.permissions.allowed_commands.length > 0 ? `Allowed Commands: ${contract.permissions.allowed_commands.join(", ")}` : "",
|
|
1176
|
+
contract.permissions.forbidden_commands.length > 0 ? `Forbidden Commands: ${contract.permissions.forbidden_commands.join(", ")}` : "",
|
|
1177
|
+
prompt
|
|
1178
|
+
].filter(Boolean);
|
|
1179
|
+
return sections.join("\n\n");
|
|
1180
|
+
}
|
|
1181
|
+
function countGitChanges(repoRoot, runner, timeoutMs) {
|
|
1182
|
+
const result = runner({
|
|
1183
|
+
command: "git",
|
|
1184
|
+
args: ["status", "--porcelain"],
|
|
1185
|
+
cwd: repoRoot,
|
|
1186
|
+
timeout_ms: timeoutMs
|
|
1187
|
+
});
|
|
1188
|
+
if ((result.status ?? 1) !== 0) return 0;
|
|
1189
|
+
return result.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).length;
|
|
1190
|
+
}
|
|
1191
|
+
function executionInvocation(config, prompt, repoRoot, mode) {
|
|
1192
|
+
if (config.provider === "codex_cli") {
|
|
1193
|
+
const outputFile = tempOutputFile("coop-codex-run");
|
|
1194
|
+
return {
|
|
1195
|
+
command: config.command ?? "codex",
|
|
1196
|
+
args: [
|
|
1197
|
+
"exec",
|
|
1198
|
+
"-C",
|
|
1199
|
+
repoRoot,
|
|
1200
|
+
"--skip-git-repo-check",
|
|
1201
|
+
"--sandbox",
|
|
1202
|
+
mode === "execute" ? "workspace-write" : "read-only",
|
|
1203
|
+
...mode === "execute" ? ["--full-auto"] : [],
|
|
1204
|
+
...config.model ? ["--model", config.model] : [],
|
|
1205
|
+
"--output-last-message",
|
|
1206
|
+
outputFile,
|
|
1207
|
+
...config.args ?? [],
|
|
1208
|
+
"-"
|
|
1209
|
+
],
|
|
1210
|
+
cwd: repoRoot,
|
|
1211
|
+
input: prompt,
|
|
1212
|
+
timeout_ms: config.timeout_ms
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
if (config.provider === "claude_cli") {
|
|
1216
|
+
return {
|
|
1217
|
+
command: config.command ?? "claude",
|
|
1218
|
+
args: [
|
|
1219
|
+
"-p",
|
|
1220
|
+
prompt,
|
|
1221
|
+
"--output-format",
|
|
1222
|
+
"text",
|
|
1223
|
+
"--permission-mode",
|
|
1224
|
+
mode === "execute" ? "bypassPermissions" : "plan",
|
|
1225
|
+
...config.model ? ["--model", config.model] : [],
|
|
1226
|
+
...config.args ?? []
|
|
1227
|
+
],
|
|
1228
|
+
cwd: repoRoot,
|
|
1229
|
+
timeout_ms: config.timeout_ms
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
return {
|
|
1233
|
+
command: config.command ?? "gemini",
|
|
1234
|
+
args: [
|
|
1235
|
+
"-p",
|
|
1236
|
+
prompt,
|
|
1237
|
+
"--output-format",
|
|
1238
|
+
"text",
|
|
1239
|
+
"--approval-mode",
|
|
1240
|
+
mode === "execute" ? "yolo" : "plan",
|
|
1241
|
+
...config.model ? ["--model", config.model] : [],
|
|
1242
|
+
...config.args ?? []
|
|
1243
|
+
],
|
|
1244
|
+
cwd: repoRoot,
|
|
1245
|
+
timeout_ms: config.timeout_ms
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
var CliAgentClient = class {
|
|
1249
|
+
config;
|
|
1250
|
+
repoRoot;
|
|
1251
|
+
runner;
|
|
1252
|
+
constructor(config, options = {}) {
|
|
1253
|
+
this.config = config;
|
|
1254
|
+
this.repoRoot = import_node_path3.default.resolve(options.cwd ?? process.cwd());
|
|
1255
|
+
this.runner = options.runner ?? defaultRunner;
|
|
1256
|
+
}
|
|
1257
|
+
async generate(input) {
|
|
1258
|
+
const before = countGitChanges(this.repoRoot, this.runner, this.config.timeout_ms);
|
|
1259
|
+
const prompt = buildExecutionPrompt(input.task, input.contract, input.step, input.prompt, "execute");
|
|
1260
|
+
const invocation = executionInvocation(this.config, prompt, this.repoRoot, "execute");
|
|
1261
|
+
const result = this.runner(invocation);
|
|
1262
|
+
requireSuccess(this.config.provider, result);
|
|
1263
|
+
const after = countGitChanges(this.repoRoot, this.runner, this.config.timeout_ms);
|
|
1264
|
+
const codexOutput = this.config.provider === "codex_cli" ? readCodexOutput(invocation) : null;
|
|
1265
|
+
return {
|
|
1266
|
+
summary: codexOutput || result.stdout.trim() || result.stderr.trim() || `Completed ${input.step.step}.`,
|
|
1267
|
+
file_changes: Math.max(0, after - before)
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
async review(input) {
|
|
1271
|
+
const prompt = buildExecutionPrompt(input.task, input.contract, input.step, input.prompt, "review");
|
|
1272
|
+
const invocation = executionInvocation(this.config, prompt, this.repoRoot, "review");
|
|
1273
|
+
const result = this.runner(invocation);
|
|
1274
|
+
requireSuccess(this.config.provider, result);
|
|
1275
|
+
const codexOutput = this.config.provider === "codex_cli" ? readCodexOutput(invocation) : null;
|
|
1276
|
+
return {
|
|
1277
|
+
summary: codexOutput || result.stdout.trim() || result.stderr.trim() || `Reviewed ${input.step.step}.`,
|
|
1278
|
+
file_changes: 0
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
};
|
|
1282
|
+
|
|
1010
1283
|
// src/providers/http.ts
|
|
1011
1284
|
async function post_json(url, init) {
|
|
1012
1285
|
const controller = new AbortController();
|
|
@@ -1203,6 +1476,10 @@ function create_provider(config) {
|
|
|
1203
1476
|
return new GeminiProvider(resolved);
|
|
1204
1477
|
case "ollama":
|
|
1205
1478
|
return new OllamaProvider(resolved);
|
|
1479
|
+
case "codex_cli":
|
|
1480
|
+
case "claude_cli":
|
|
1481
|
+
case "gemini_cli":
|
|
1482
|
+
return new CliCompletionProvider(resolved);
|
|
1206
1483
|
case "mock":
|
|
1207
1484
|
default:
|
|
1208
1485
|
return new MockProvider();
|
|
@@ -1266,7 +1543,11 @@ function asAgentResponse(text, tokens) {
|
|
|
1266
1543
|
tokens_used: tokens
|
|
1267
1544
|
};
|
|
1268
1545
|
}
|
|
1269
|
-
function create_provider_agent_client(config) {
|
|
1546
|
+
function create_provider_agent_client(config, runtime = {}) {
|
|
1547
|
+
const resolved = resolve_provider_config(config);
|
|
1548
|
+
if (resolved.provider === "codex_cli" || resolved.provider === "claude_cli" || resolved.provider === "gemini_cli") {
|
|
1549
|
+
return new CliAgentClient(resolved, runtime);
|
|
1550
|
+
}
|
|
1270
1551
|
const provider = create_provider(config);
|
|
1271
1552
|
return {
|
|
1272
1553
|
async generate(input) {
|
|
@@ -1322,6 +1603,8 @@ function create_provider_refinement_client(config) {
|
|
|
1322
1603
|
}
|
|
1323
1604
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1324
1605
|
0 && (module.exports = {
|
|
1606
|
+
CliAgentClient,
|
|
1607
|
+
CliCompletionProvider,
|
|
1325
1608
|
buildIdeaRefinementPrompt,
|
|
1326
1609
|
buildTaskRefinementPrompt,
|
|
1327
1610
|
build_contract,
|
package/dist/index.d.cts
CHANGED
|
@@ -216,12 +216,14 @@ declare function refine_idea_to_draft(input: IdeaRefinementInput, client?: Refin
|
|
|
216
216
|
declare function refine_task_to_draft(input: TaskRefinementInput, client?: RefinementClient): Promise<RefinementDraft>;
|
|
217
217
|
declare function parseRefinementDraftResponse(text: string, mode: "idea" | "task", sourceId: string, sourceTitle: string): RefinementDraft | null;
|
|
218
218
|
|
|
219
|
-
type AiProviderName = "mock" | "openai" | "anthropic" | "gemini" | "ollama";
|
|
219
|
+
type AiProviderName = "mock" | "openai" | "anthropic" | "gemini" | "ollama" | "codex_cli" | "claude_cli" | "gemini_cli";
|
|
220
220
|
interface ProviderConfig {
|
|
221
221
|
provider: AiProviderName;
|
|
222
222
|
model: string;
|
|
223
223
|
base_url?: string;
|
|
224
224
|
api_key?: string;
|
|
225
|
+
command?: string;
|
|
226
|
+
args?: string[];
|
|
225
227
|
temperature: number;
|
|
226
228
|
max_output_tokens: number;
|
|
227
229
|
timeout_ms: number;
|
|
@@ -245,6 +247,52 @@ interface LlmProvider {
|
|
|
245
247
|
*/
|
|
246
248
|
declare function resolve_provider_config(config: unknown): ProviderConfig;
|
|
247
249
|
|
|
250
|
+
type CliProviderName = Extract<ProviderConfig["provider"], "codex_cli" | "claude_cli" | "gemini_cli">;
|
|
251
|
+
interface CliRunnerInvocation {
|
|
252
|
+
command: string;
|
|
253
|
+
args: string[];
|
|
254
|
+
cwd: string;
|
|
255
|
+
input?: string;
|
|
256
|
+
timeout_ms: number;
|
|
257
|
+
}
|
|
258
|
+
interface CliRunnerResult {
|
|
259
|
+
status: number | null;
|
|
260
|
+
stdout: string;
|
|
261
|
+
stderr: string;
|
|
262
|
+
error?: Error;
|
|
263
|
+
}
|
|
264
|
+
type CliRunner = (invocation: CliRunnerInvocation) => CliRunnerResult;
|
|
265
|
+
interface CliRuntimeOptions {
|
|
266
|
+
cwd?: string;
|
|
267
|
+
runner?: CliRunner;
|
|
268
|
+
}
|
|
269
|
+
declare class CliCompletionProvider implements LlmProvider {
|
|
270
|
+
readonly name: CliProviderName;
|
|
271
|
+
private readonly config;
|
|
272
|
+
private readonly cwd;
|
|
273
|
+
private readonly runner;
|
|
274
|
+
constructor(config: ProviderConfig, options?: CliRuntimeOptions);
|
|
275
|
+
complete(input: CompletionInput): Promise<CompletionResult>;
|
|
276
|
+
}
|
|
277
|
+
declare class CliAgentClient implements AgentClient {
|
|
278
|
+
private readonly config;
|
|
279
|
+
private readonly repoRoot;
|
|
280
|
+
private readonly runner;
|
|
281
|
+
constructor(config: ProviderConfig, options?: CliRuntimeOptions);
|
|
282
|
+
generate(input: {
|
|
283
|
+
task: Task;
|
|
284
|
+
step: NonNullable<NonNullable<Task["execution"]>["runbook"]>[number];
|
|
285
|
+
contract: AgentContract;
|
|
286
|
+
prompt: string;
|
|
287
|
+
}): Promise<AgentResponse>;
|
|
288
|
+
review(input: {
|
|
289
|
+
task: Task;
|
|
290
|
+
step: NonNullable<NonNullable<Task["execution"]>["runbook"]>[number];
|
|
291
|
+
contract: AgentContract;
|
|
292
|
+
prompt: string;
|
|
293
|
+
}): Promise<AgentResponse>;
|
|
294
|
+
}
|
|
295
|
+
|
|
248
296
|
/**
|
|
249
297
|
* Creates an LLM provider instance from COOP config.
|
|
250
298
|
* [SPEC: Architecture v2.0 §13]
|
|
@@ -255,7 +303,7 @@ declare function create_provider(config: unknown): LlmProvider;
|
|
|
255
303
|
* Creates an executor client backed by configured LLM provider(s).
|
|
256
304
|
* [SPEC: Architecture v2.0 §13]
|
|
257
305
|
*/
|
|
258
|
-
declare function create_provider_agent_client(config: unknown): AgentClient;
|
|
306
|
+
declare function create_provider_agent_client(config: unknown, runtime?: CliRuntimeOptions): AgentClient;
|
|
259
307
|
/**
|
|
260
308
|
* Creates an idea decomposition client backed by configured LLM provider(s).
|
|
261
309
|
* [SPEC: Architecture v2.0 §Phase 4]
|
|
@@ -263,4 +311,4 @@ declare function create_provider_agent_client(config: unknown): AgentClient;
|
|
|
263
311
|
declare function create_provider_idea_decomposer(config: unknown): IdeaDecomposerClient;
|
|
264
312
|
declare function create_provider_refinement_client(config: unknown): RefinementClient;
|
|
265
313
|
|
|
266
|
-
export { type AgentClient, type AgentContract, type AgentContractConstraints, type AgentContractContext, type AgentContractPermissions, type AgentContractRelatedArtifacts, type AgentResponse, type AiProviderName, type CompletionInput, type CompletionResult, type DecomposedTaskDraft, type ExecuteTaskOptions, type FileAccessMode, type IdeaDecomposerClient, type IdeaDecompositionInput, type IdeaRefinementInput, type LlmProvider, type ProviderConfig, type RefinementClient, type RefinementDraft, type RefinementProposalAction, type RefinementTaskProposal, type RunResult, type SandboxRunState, type TaskRefinementInput, buildIdeaRefinementPrompt, buildTaskRefinementPrompt, build_contract, build_decomposition_prompt, constraint_violation_reasons, create_provider, create_provider_agent_client, create_provider_idea_decomposer, create_provider_refinement_client, create_run, decompose_idea_to_tasks, enforce_constraints, execute_task, finalize_run, log_step, parseRefinementDraftResponse, refine_idea_to_draft, refine_task_to_draft, resolve_provider_config, select_agent, validate_command, validate_file_access, write_run };
|
|
314
|
+
export { type AgentClient, type AgentContract, type AgentContractConstraints, type AgentContractContext, type AgentContractPermissions, type AgentContractRelatedArtifacts, type AgentResponse, type AiProviderName, CliAgentClient, CliCompletionProvider, type CliRunner, type CliRunnerInvocation, type CliRunnerResult, type CliRuntimeOptions, type CompletionInput, type CompletionResult, type DecomposedTaskDraft, type ExecuteTaskOptions, type FileAccessMode, type IdeaDecomposerClient, type IdeaDecompositionInput, type IdeaRefinementInput, type LlmProvider, type ProviderConfig, type RefinementClient, type RefinementDraft, type RefinementProposalAction, type RefinementTaskProposal, type RunResult, type SandboxRunState, type TaskRefinementInput, buildIdeaRefinementPrompt, buildTaskRefinementPrompt, build_contract, build_decomposition_prompt, constraint_violation_reasons, create_provider, create_provider_agent_client, create_provider_idea_decomposer, create_provider_refinement_client, create_run, decompose_idea_to_tasks, enforce_constraints, execute_task, finalize_run, log_step, parseRefinementDraftResponse, refine_idea_to_draft, refine_task_to_draft, resolve_provider_config, select_agent, validate_command, validate_file_access, write_run };
|
package/dist/index.d.ts
CHANGED
|
@@ -216,12 +216,14 @@ declare function refine_idea_to_draft(input: IdeaRefinementInput, client?: Refin
|
|
|
216
216
|
declare function refine_task_to_draft(input: TaskRefinementInput, client?: RefinementClient): Promise<RefinementDraft>;
|
|
217
217
|
declare function parseRefinementDraftResponse(text: string, mode: "idea" | "task", sourceId: string, sourceTitle: string): RefinementDraft | null;
|
|
218
218
|
|
|
219
|
-
type AiProviderName = "mock" | "openai" | "anthropic" | "gemini" | "ollama";
|
|
219
|
+
type AiProviderName = "mock" | "openai" | "anthropic" | "gemini" | "ollama" | "codex_cli" | "claude_cli" | "gemini_cli";
|
|
220
220
|
interface ProviderConfig {
|
|
221
221
|
provider: AiProviderName;
|
|
222
222
|
model: string;
|
|
223
223
|
base_url?: string;
|
|
224
224
|
api_key?: string;
|
|
225
|
+
command?: string;
|
|
226
|
+
args?: string[];
|
|
225
227
|
temperature: number;
|
|
226
228
|
max_output_tokens: number;
|
|
227
229
|
timeout_ms: number;
|
|
@@ -245,6 +247,52 @@ interface LlmProvider {
|
|
|
245
247
|
*/
|
|
246
248
|
declare function resolve_provider_config(config: unknown): ProviderConfig;
|
|
247
249
|
|
|
250
|
+
type CliProviderName = Extract<ProviderConfig["provider"], "codex_cli" | "claude_cli" | "gemini_cli">;
|
|
251
|
+
interface CliRunnerInvocation {
|
|
252
|
+
command: string;
|
|
253
|
+
args: string[];
|
|
254
|
+
cwd: string;
|
|
255
|
+
input?: string;
|
|
256
|
+
timeout_ms: number;
|
|
257
|
+
}
|
|
258
|
+
interface CliRunnerResult {
|
|
259
|
+
status: number | null;
|
|
260
|
+
stdout: string;
|
|
261
|
+
stderr: string;
|
|
262
|
+
error?: Error;
|
|
263
|
+
}
|
|
264
|
+
type CliRunner = (invocation: CliRunnerInvocation) => CliRunnerResult;
|
|
265
|
+
interface CliRuntimeOptions {
|
|
266
|
+
cwd?: string;
|
|
267
|
+
runner?: CliRunner;
|
|
268
|
+
}
|
|
269
|
+
declare class CliCompletionProvider implements LlmProvider {
|
|
270
|
+
readonly name: CliProviderName;
|
|
271
|
+
private readonly config;
|
|
272
|
+
private readonly cwd;
|
|
273
|
+
private readonly runner;
|
|
274
|
+
constructor(config: ProviderConfig, options?: CliRuntimeOptions);
|
|
275
|
+
complete(input: CompletionInput): Promise<CompletionResult>;
|
|
276
|
+
}
|
|
277
|
+
declare class CliAgentClient implements AgentClient {
|
|
278
|
+
private readonly config;
|
|
279
|
+
private readonly repoRoot;
|
|
280
|
+
private readonly runner;
|
|
281
|
+
constructor(config: ProviderConfig, options?: CliRuntimeOptions);
|
|
282
|
+
generate(input: {
|
|
283
|
+
task: Task;
|
|
284
|
+
step: NonNullable<NonNullable<Task["execution"]>["runbook"]>[number];
|
|
285
|
+
contract: AgentContract;
|
|
286
|
+
prompt: string;
|
|
287
|
+
}): Promise<AgentResponse>;
|
|
288
|
+
review(input: {
|
|
289
|
+
task: Task;
|
|
290
|
+
step: NonNullable<NonNullable<Task["execution"]>["runbook"]>[number];
|
|
291
|
+
contract: AgentContract;
|
|
292
|
+
prompt: string;
|
|
293
|
+
}): Promise<AgentResponse>;
|
|
294
|
+
}
|
|
295
|
+
|
|
248
296
|
/**
|
|
249
297
|
* Creates an LLM provider instance from COOP config.
|
|
250
298
|
* [SPEC: Architecture v2.0 §13]
|
|
@@ -255,7 +303,7 @@ declare function create_provider(config: unknown): LlmProvider;
|
|
|
255
303
|
* Creates an executor client backed by configured LLM provider(s).
|
|
256
304
|
* [SPEC: Architecture v2.0 §13]
|
|
257
305
|
*/
|
|
258
|
-
declare function create_provider_agent_client(config: unknown): AgentClient;
|
|
306
|
+
declare function create_provider_agent_client(config: unknown, runtime?: CliRuntimeOptions): AgentClient;
|
|
259
307
|
/**
|
|
260
308
|
* Creates an idea decomposition client backed by configured LLM provider(s).
|
|
261
309
|
* [SPEC: Architecture v2.0 §Phase 4]
|
|
@@ -263,4 +311,4 @@ declare function create_provider_agent_client(config: unknown): AgentClient;
|
|
|
263
311
|
declare function create_provider_idea_decomposer(config: unknown): IdeaDecomposerClient;
|
|
264
312
|
declare function create_provider_refinement_client(config: unknown): RefinementClient;
|
|
265
313
|
|
|
266
|
-
export { type AgentClient, type AgentContract, type AgentContractConstraints, type AgentContractContext, type AgentContractPermissions, type AgentContractRelatedArtifacts, type AgentResponse, type AiProviderName, type CompletionInput, type CompletionResult, type DecomposedTaskDraft, type ExecuteTaskOptions, type FileAccessMode, type IdeaDecomposerClient, type IdeaDecompositionInput, type IdeaRefinementInput, type LlmProvider, type ProviderConfig, type RefinementClient, type RefinementDraft, type RefinementProposalAction, type RefinementTaskProposal, type RunResult, type SandboxRunState, type TaskRefinementInput, buildIdeaRefinementPrompt, buildTaskRefinementPrompt, build_contract, build_decomposition_prompt, constraint_violation_reasons, create_provider, create_provider_agent_client, create_provider_idea_decomposer, create_provider_refinement_client, create_run, decompose_idea_to_tasks, enforce_constraints, execute_task, finalize_run, log_step, parseRefinementDraftResponse, refine_idea_to_draft, refine_task_to_draft, resolve_provider_config, select_agent, validate_command, validate_file_access, write_run };
|
|
314
|
+
export { type AgentClient, type AgentContract, type AgentContractConstraints, type AgentContractContext, type AgentContractPermissions, type AgentContractRelatedArtifacts, type AgentResponse, type AiProviderName, CliAgentClient, CliCompletionProvider, type CliRunner, type CliRunnerInvocation, type CliRunnerResult, type CliRuntimeOptions, type CompletionInput, type CompletionResult, type DecomposedTaskDraft, type ExecuteTaskOptions, type FileAccessMode, type IdeaDecomposerClient, type IdeaDecompositionInput, type IdeaRefinementInput, type LlmProvider, type ProviderConfig, type RefinementClient, type RefinementDraft, type RefinementProposalAction, type RefinementTaskProposal, type RunResult, type SandboxRunState, type TaskRefinementInput, buildIdeaRefinementPrompt, buildTaskRefinementPrompt, build_contract, build_decomposition_prompt, constraint_violation_reasons, create_provider, create_provider_agent_client, create_provider_idea_decomposer, create_provider_refinement_client, create_run, decompose_idea_to_tasks, enforce_constraints, execute_task, finalize_run, log_step, parseRefinementDraftResponse, refine_idea_to_draft, refine_task_to_draft, resolve_provider_config, select_agent, validate_command, validate_file_access, write_run };
|
package/dist/index.js
CHANGED
|
@@ -870,7 +870,15 @@ var DEFAULT_MODELS = {
|
|
|
870
870
|
openai: "gpt-5-mini",
|
|
871
871
|
anthropic: "claude-3-5-sonnet-latest",
|
|
872
872
|
gemini: "gemini-2.0-flash",
|
|
873
|
-
ollama: "llama3.2"
|
|
873
|
+
ollama: "llama3.2",
|
|
874
|
+
codex_cli: "gpt-5-codex",
|
|
875
|
+
claude_cli: "sonnet",
|
|
876
|
+
gemini_cli: "gemini-2.5-pro"
|
|
877
|
+
};
|
|
878
|
+
var DEFAULT_COMMAND = {
|
|
879
|
+
codex_cli: "codex",
|
|
880
|
+
claude_cli: "claude",
|
|
881
|
+
gemini_cli: "gemini"
|
|
874
882
|
};
|
|
875
883
|
var DEFAULT_KEY_ENV = {
|
|
876
884
|
openai: "OPENAI_API_KEY",
|
|
@@ -900,7 +908,7 @@ function asFinite(value) {
|
|
|
900
908
|
}
|
|
901
909
|
function readProvider(value) {
|
|
902
910
|
const normalized = asString(value)?.toLowerCase();
|
|
903
|
-
if (normalized === "openai" || normalized === "anthropic" || normalized === "gemini" || normalized === "ollama" || normalized === "mock") {
|
|
911
|
+
if (normalized === "openai" || normalized === "anthropic" || normalized === "gemini" || normalized === "ollama" || normalized === "mock" || normalized === "codex_cli" || normalized === "claude_cli" || normalized === "gemini_cli") {
|
|
904
912
|
return normalized;
|
|
905
913
|
}
|
|
906
914
|
return "mock";
|
|
@@ -921,15 +929,34 @@ function resolve_provider_config(config) {
|
|
|
921
929
|
timeout_ms: 6e4
|
|
922
930
|
};
|
|
923
931
|
}
|
|
932
|
+
if (provider === "codex_cli" || provider === "claude_cli" || provider === "gemini_cli") {
|
|
933
|
+
const section2 = lookupProviderSection(ai, provider);
|
|
934
|
+
const model2 = asString(section2.model) ?? asString(ai.model) ?? DEFAULT_MODELS[provider];
|
|
935
|
+
const timeout_ms2 = asFinite(section2.timeout_ms) ?? asFinite(ai.timeout_ms) ?? 3e5;
|
|
936
|
+
const command = asString(section2.command) ?? DEFAULT_COMMAND[provider];
|
|
937
|
+
const argsRaw = Array.isArray(section2.args) ? section2.args : Array.isArray(ai.args) ? ai.args : [];
|
|
938
|
+
const args = argsRaw.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
939
|
+
return {
|
|
940
|
+
provider,
|
|
941
|
+
model: model2,
|
|
942
|
+
command,
|
|
943
|
+
args,
|
|
944
|
+
temperature: 0.2,
|
|
945
|
+
max_output_tokens: 4096,
|
|
946
|
+
timeout_ms: timeout_ms2
|
|
947
|
+
};
|
|
948
|
+
}
|
|
924
949
|
const section = lookupProviderSection(ai, provider);
|
|
925
|
-
const
|
|
926
|
-
const
|
|
950
|
+
const cloudProvider = provider;
|
|
951
|
+
const model = asString(section.model) ?? asString(ai.model) ?? DEFAULT_MODELS[cloudProvider];
|
|
952
|
+
const base_url = asString(section.base_url) ?? asString(ai.base_url) ?? DEFAULT_BASE_URL[cloudProvider];
|
|
927
953
|
const temperature = asFinite(section.temperature) ?? asFinite(ai.temperature) ?? 0.2;
|
|
928
954
|
const max_output_tokens = asFinite(section.max_output_tokens) ?? asFinite(ai.max_output_tokens) ?? 1024;
|
|
929
955
|
const timeout_ms = asFinite(section.timeout_ms) ?? asFinite(ai.timeout_ms) ?? 6e4;
|
|
930
956
|
let api_key;
|
|
931
957
|
if (provider !== "ollama") {
|
|
932
|
-
const
|
|
958
|
+
const keyedProvider = provider;
|
|
959
|
+
const envName = asString(section.api_key_env) ?? asString(ai.api_key_env) ?? DEFAULT_KEY_ENV[keyedProvider];
|
|
933
960
|
const envValue = envName ? asString(process.env[envName]) : null;
|
|
934
961
|
api_key = asString(section.api_key) ?? asString(ai.api_key) ?? envValue ?? void 0;
|
|
935
962
|
if (!api_key) {
|
|
@@ -949,6 +976,250 @@ function resolve_provider_config(config) {
|
|
|
949
976
|
};
|
|
950
977
|
}
|
|
951
978
|
|
|
979
|
+
// src/providers/cli.ts
|
|
980
|
+
import fs2 from "fs";
|
|
981
|
+
import os from "os";
|
|
982
|
+
import path3 from "path";
|
|
983
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
984
|
+
function defaultRunner(invocation) {
|
|
985
|
+
const result = spawnSync2(invocation.command, invocation.args, {
|
|
986
|
+
cwd: invocation.cwd,
|
|
987
|
+
encoding: "utf8",
|
|
988
|
+
input: invocation.input,
|
|
989
|
+
timeout: invocation.timeout_ms,
|
|
990
|
+
windowsHide: true
|
|
991
|
+
});
|
|
992
|
+
return {
|
|
993
|
+
status: result.status,
|
|
994
|
+
stdout: result.stdout ?? "",
|
|
995
|
+
stderr: result.stderr ?? "",
|
|
996
|
+
error: result.error
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
function asText(result) {
|
|
1000
|
+
return [result.stdout, result.stderr].map((value) => value.trim()).filter(Boolean).join("\n").trim();
|
|
1001
|
+
}
|
|
1002
|
+
function requireSuccess(provider, result) {
|
|
1003
|
+
if ((result.status ?? 1) === 0 && !result.error) {
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
const details = asText(result) || result.error?.message || "unknown error";
|
|
1007
|
+
throw new Error(`${provider} execution failed: ${details}`);
|
|
1008
|
+
}
|
|
1009
|
+
function tempOutputFile(prefix) {
|
|
1010
|
+
return path3.join(os.tmpdir(), `${prefix}-${Date.now()}-${Math.random().toString(16).slice(2)}.txt`);
|
|
1011
|
+
}
|
|
1012
|
+
function combinedPrompt(input) {
|
|
1013
|
+
return [input.system.trim(), input.prompt.trim()].filter(Boolean).join("\n\n");
|
|
1014
|
+
}
|
|
1015
|
+
function completionInvocation(config, input, cwd) {
|
|
1016
|
+
if (config.provider === "codex_cli") {
|
|
1017
|
+
const outputFile = tempOutputFile("coop-codex-complete");
|
|
1018
|
+
return {
|
|
1019
|
+
command: config.command ?? "codex",
|
|
1020
|
+
args: [
|
|
1021
|
+
"exec",
|
|
1022
|
+
"-C",
|
|
1023
|
+
cwd,
|
|
1024
|
+
"--skip-git-repo-check",
|
|
1025
|
+
"--sandbox",
|
|
1026
|
+
"read-only",
|
|
1027
|
+
...config.model ? ["--model", config.model] : [],
|
|
1028
|
+
"--output-last-message",
|
|
1029
|
+
outputFile,
|
|
1030
|
+
...config.args ?? [],
|
|
1031
|
+
"-"
|
|
1032
|
+
],
|
|
1033
|
+
cwd,
|
|
1034
|
+
input: combinedPrompt(input),
|
|
1035
|
+
timeout_ms: config.timeout_ms
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
if (config.provider === "claude_cli") {
|
|
1039
|
+
return {
|
|
1040
|
+
command: config.command ?? "claude",
|
|
1041
|
+
args: [
|
|
1042
|
+
"-p",
|
|
1043
|
+
combinedPrompt(input),
|
|
1044
|
+
"--output-format",
|
|
1045
|
+
"text",
|
|
1046
|
+
"--permission-mode",
|
|
1047
|
+
"plan",
|
|
1048
|
+
...config.model ? ["--model", config.model] : [],
|
|
1049
|
+
...config.args ?? []
|
|
1050
|
+
],
|
|
1051
|
+
cwd,
|
|
1052
|
+
timeout_ms: config.timeout_ms
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
return {
|
|
1056
|
+
command: config.command ?? "gemini",
|
|
1057
|
+
args: [
|
|
1058
|
+
"-p",
|
|
1059
|
+
combinedPrompt(input),
|
|
1060
|
+
"--output-format",
|
|
1061
|
+
"text",
|
|
1062
|
+
"--approval-mode",
|
|
1063
|
+
"plan",
|
|
1064
|
+
...config.model ? ["--model", config.model] : [],
|
|
1065
|
+
...config.args ?? []
|
|
1066
|
+
],
|
|
1067
|
+
cwd,
|
|
1068
|
+
timeout_ms: config.timeout_ms
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
function readCodexOutput(invocation) {
|
|
1072
|
+
const index = invocation.args.indexOf("--output-last-message");
|
|
1073
|
+
const outputFile = index >= 0 ? invocation.args[index + 1] : void 0;
|
|
1074
|
+
if (!outputFile) return null;
|
|
1075
|
+
if (!fs2.existsSync(outputFile)) return null;
|
|
1076
|
+
try {
|
|
1077
|
+
return fs2.readFileSync(outputFile, "utf8").trim();
|
|
1078
|
+
} finally {
|
|
1079
|
+
fs2.rmSync(outputFile, { force: true });
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
var CliCompletionProvider = class {
|
|
1083
|
+
name;
|
|
1084
|
+
config;
|
|
1085
|
+
cwd;
|
|
1086
|
+
runner;
|
|
1087
|
+
constructor(config, options = {}) {
|
|
1088
|
+
this.name = config.provider;
|
|
1089
|
+
this.config = config;
|
|
1090
|
+
this.cwd = path3.resolve(options.cwd ?? process.cwd());
|
|
1091
|
+
this.runner = options.runner ?? defaultRunner;
|
|
1092
|
+
}
|
|
1093
|
+
async complete(input) {
|
|
1094
|
+
const invocation = completionInvocation(this.config, input, this.cwd);
|
|
1095
|
+
const result = this.runner(invocation);
|
|
1096
|
+
requireSuccess(this.name, result);
|
|
1097
|
+
const codexOutput = this.config.provider === "codex_cli" ? readCodexOutput(invocation) : null;
|
|
1098
|
+
return {
|
|
1099
|
+
text: codexOutput || result.stdout.trim() || result.stderr.trim()
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
};
|
|
1103
|
+
function buildExecutionPrompt(task, contract, step, prompt, mode) {
|
|
1104
|
+
const sections = [
|
|
1105
|
+
mode === "execute" ? "You are executing a COOP task inside the repo. Make the necessary file changes, run allowed commands when needed, and finish with a concise summary." : "You are reviewing a COOP task inside the repo. Inspect the current workspace and return concise review findings only.",
|
|
1106
|
+
`Task: ${task.id} - ${task.title}`,
|
|
1107
|
+
`Goal: ${contract.goal}`,
|
|
1108
|
+
`Step: ${step.step} (${step.action})`,
|
|
1109
|
+
contract.context.acceptance_criteria.length > 0 ? `Acceptance: ${contract.context.acceptance_criteria.join(" | ")}` : "",
|
|
1110
|
+
contract.context.tests_required.length > 0 ? `Tests Required: ${contract.context.tests_required.join(" | ")}` : "",
|
|
1111
|
+
contract.context.authority_refs.length > 0 ? `Authority Refs: ${contract.context.authority_refs.join(" | ")}` : "",
|
|
1112
|
+
contract.context.derived_refs.length > 0 ? `Derived Refs: ${contract.context.derived_refs.join(" | ")}` : "",
|
|
1113
|
+
contract.permissions.read_paths.length > 0 ? `Allowed Read Paths: ${contract.permissions.read_paths.join(", ")}` : "",
|
|
1114
|
+
contract.permissions.write_paths.length > 0 ? `Allowed Write Paths: ${contract.permissions.write_paths.join(", ")}` : "",
|
|
1115
|
+
contract.permissions.allowed_commands.length > 0 ? `Allowed Commands: ${contract.permissions.allowed_commands.join(", ")}` : "",
|
|
1116
|
+
contract.permissions.forbidden_commands.length > 0 ? `Forbidden Commands: ${contract.permissions.forbidden_commands.join(", ")}` : "",
|
|
1117
|
+
prompt
|
|
1118
|
+
].filter(Boolean);
|
|
1119
|
+
return sections.join("\n\n");
|
|
1120
|
+
}
|
|
1121
|
+
function countGitChanges(repoRoot, runner, timeoutMs) {
|
|
1122
|
+
const result = runner({
|
|
1123
|
+
command: "git",
|
|
1124
|
+
args: ["status", "--porcelain"],
|
|
1125
|
+
cwd: repoRoot,
|
|
1126
|
+
timeout_ms: timeoutMs
|
|
1127
|
+
});
|
|
1128
|
+
if ((result.status ?? 1) !== 0) return 0;
|
|
1129
|
+
return result.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).length;
|
|
1130
|
+
}
|
|
1131
|
+
function executionInvocation(config, prompt, repoRoot, mode) {
|
|
1132
|
+
if (config.provider === "codex_cli") {
|
|
1133
|
+
const outputFile = tempOutputFile("coop-codex-run");
|
|
1134
|
+
return {
|
|
1135
|
+
command: config.command ?? "codex",
|
|
1136
|
+
args: [
|
|
1137
|
+
"exec",
|
|
1138
|
+
"-C",
|
|
1139
|
+
repoRoot,
|
|
1140
|
+
"--skip-git-repo-check",
|
|
1141
|
+
"--sandbox",
|
|
1142
|
+
mode === "execute" ? "workspace-write" : "read-only",
|
|
1143
|
+
...mode === "execute" ? ["--full-auto"] : [],
|
|
1144
|
+
...config.model ? ["--model", config.model] : [],
|
|
1145
|
+
"--output-last-message",
|
|
1146
|
+
outputFile,
|
|
1147
|
+
...config.args ?? [],
|
|
1148
|
+
"-"
|
|
1149
|
+
],
|
|
1150
|
+
cwd: repoRoot,
|
|
1151
|
+
input: prompt,
|
|
1152
|
+
timeout_ms: config.timeout_ms
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
if (config.provider === "claude_cli") {
|
|
1156
|
+
return {
|
|
1157
|
+
command: config.command ?? "claude",
|
|
1158
|
+
args: [
|
|
1159
|
+
"-p",
|
|
1160
|
+
prompt,
|
|
1161
|
+
"--output-format",
|
|
1162
|
+
"text",
|
|
1163
|
+
"--permission-mode",
|
|
1164
|
+
mode === "execute" ? "bypassPermissions" : "plan",
|
|
1165
|
+
...config.model ? ["--model", config.model] : [],
|
|
1166
|
+
...config.args ?? []
|
|
1167
|
+
],
|
|
1168
|
+
cwd: repoRoot,
|
|
1169
|
+
timeout_ms: config.timeout_ms
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
return {
|
|
1173
|
+
command: config.command ?? "gemini",
|
|
1174
|
+
args: [
|
|
1175
|
+
"-p",
|
|
1176
|
+
prompt,
|
|
1177
|
+
"--output-format",
|
|
1178
|
+
"text",
|
|
1179
|
+
"--approval-mode",
|
|
1180
|
+
mode === "execute" ? "yolo" : "plan",
|
|
1181
|
+
...config.model ? ["--model", config.model] : [],
|
|
1182
|
+
...config.args ?? []
|
|
1183
|
+
],
|
|
1184
|
+
cwd: repoRoot,
|
|
1185
|
+
timeout_ms: config.timeout_ms
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
var CliAgentClient = class {
|
|
1189
|
+
config;
|
|
1190
|
+
repoRoot;
|
|
1191
|
+
runner;
|
|
1192
|
+
constructor(config, options = {}) {
|
|
1193
|
+
this.config = config;
|
|
1194
|
+
this.repoRoot = path3.resolve(options.cwd ?? process.cwd());
|
|
1195
|
+
this.runner = options.runner ?? defaultRunner;
|
|
1196
|
+
}
|
|
1197
|
+
async generate(input) {
|
|
1198
|
+
const before = countGitChanges(this.repoRoot, this.runner, this.config.timeout_ms);
|
|
1199
|
+
const prompt = buildExecutionPrompt(input.task, input.contract, input.step, input.prompt, "execute");
|
|
1200
|
+
const invocation = executionInvocation(this.config, prompt, this.repoRoot, "execute");
|
|
1201
|
+
const result = this.runner(invocation);
|
|
1202
|
+
requireSuccess(this.config.provider, result);
|
|
1203
|
+
const after = countGitChanges(this.repoRoot, this.runner, this.config.timeout_ms);
|
|
1204
|
+
const codexOutput = this.config.provider === "codex_cli" ? readCodexOutput(invocation) : null;
|
|
1205
|
+
return {
|
|
1206
|
+
summary: codexOutput || result.stdout.trim() || result.stderr.trim() || `Completed ${input.step.step}.`,
|
|
1207
|
+
file_changes: Math.max(0, after - before)
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
async review(input) {
|
|
1211
|
+
const prompt = buildExecutionPrompt(input.task, input.contract, input.step, input.prompt, "review");
|
|
1212
|
+
const invocation = executionInvocation(this.config, prompt, this.repoRoot, "review");
|
|
1213
|
+
const result = this.runner(invocation);
|
|
1214
|
+
requireSuccess(this.config.provider, result);
|
|
1215
|
+
const codexOutput = this.config.provider === "codex_cli" ? readCodexOutput(invocation) : null;
|
|
1216
|
+
return {
|
|
1217
|
+
summary: codexOutput || result.stdout.trim() || result.stderr.trim() || `Reviewed ${input.step.step}.`,
|
|
1218
|
+
file_changes: 0
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
};
|
|
1222
|
+
|
|
952
1223
|
// src/providers/http.ts
|
|
953
1224
|
async function post_json(url, init) {
|
|
954
1225
|
const controller = new AbortController();
|
|
@@ -1145,6 +1416,10 @@ function create_provider(config) {
|
|
|
1145
1416
|
return new GeminiProvider(resolved);
|
|
1146
1417
|
case "ollama":
|
|
1147
1418
|
return new OllamaProvider(resolved);
|
|
1419
|
+
case "codex_cli":
|
|
1420
|
+
case "claude_cli":
|
|
1421
|
+
case "gemini_cli":
|
|
1422
|
+
return new CliCompletionProvider(resolved);
|
|
1148
1423
|
case "mock":
|
|
1149
1424
|
default:
|
|
1150
1425
|
return new MockProvider();
|
|
@@ -1208,7 +1483,11 @@ function asAgentResponse(text, tokens) {
|
|
|
1208
1483
|
tokens_used: tokens
|
|
1209
1484
|
};
|
|
1210
1485
|
}
|
|
1211
|
-
function create_provider_agent_client(config) {
|
|
1486
|
+
function create_provider_agent_client(config, runtime = {}) {
|
|
1487
|
+
const resolved = resolve_provider_config(config);
|
|
1488
|
+
if (resolved.provider === "codex_cli" || resolved.provider === "claude_cli" || resolved.provider === "gemini_cli") {
|
|
1489
|
+
return new CliAgentClient(resolved, runtime);
|
|
1490
|
+
}
|
|
1212
1491
|
const provider = create_provider(config);
|
|
1213
1492
|
return {
|
|
1214
1493
|
async generate(input) {
|
|
@@ -1263,6 +1542,8 @@ function create_provider_refinement_client(config) {
|
|
|
1263
1542
|
};
|
|
1264
1543
|
}
|
|
1265
1544
|
export {
|
|
1545
|
+
CliAgentClient,
|
|
1546
|
+
CliCompletionProvider,
|
|
1266
1547
|
buildIdeaRefinementPrompt,
|
|
1267
1548
|
buildTaskRefinementPrompt,
|
|
1268
1549
|
build_contract,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kitsy/coop-ai",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"LICENSE"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@kitsy/coop-core": "2.1.
|
|
20
|
+
"@kitsy/coop-core": "2.1.2"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@types/node": "^24.12.0",
|