@kody-ade/kody-engine 0.4.286 → 0.4.288
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/bin/kody.js +217 -31
- package/package.json +1 -1
package/dist/bin/kody.js
CHANGED
|
@@ -15,7 +15,7 @@ var init_package = __esm({
|
|
|
15
15
|
"package.json"() {
|
|
16
16
|
package_default = {
|
|
17
17
|
name: "@kody-ade/kody-engine",
|
|
18
|
-
version: "0.4.
|
|
18
|
+
version: "0.4.288",
|
|
19
19
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
20
20
|
license: "MIT",
|
|
21
21
|
type: "module",
|
|
@@ -443,7 +443,7 @@ var STATE_BRANCH;
|
|
|
443
443
|
var init_stateBranch = __esm({
|
|
444
444
|
"src/stateBranch.ts"() {
|
|
445
445
|
"use strict";
|
|
446
|
-
STATE_BRANCH = "
|
|
446
|
+
STATE_BRANCH = "main";
|
|
447
447
|
}
|
|
448
448
|
});
|
|
449
449
|
|
|
@@ -497,18 +497,33 @@ function normalizeStatePath(raw, field = "statePath") {
|
|
|
497
497
|
}
|
|
498
498
|
return parts.join("/");
|
|
499
499
|
}
|
|
500
|
+
function normalizeStateBranch(raw, field = "state.branch") {
|
|
501
|
+
const value = raw?.trim() || STATE_BRANCH;
|
|
502
|
+
if (!value) throw new Error(`kody.config.json: ${field} must not be empty`);
|
|
503
|
+
if (value.startsWith("/") || value.endsWith("/") || value.includes("\\") || value.includes("..") || value.includes("@{") || /[\x00-\x20\x7f~^:?*\[]/.test(value)) {
|
|
504
|
+
throw new Error(`kody.config.json: ${field} contains an invalid branch`);
|
|
505
|
+
}
|
|
506
|
+
for (const part of value.split("/")) {
|
|
507
|
+
if (!part || part === "." || part === ".." || part.startsWith(".") || part.endsWith(".lock")) {
|
|
508
|
+
throw new Error(`kody.config.json: ${field} contains an invalid branch`);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return value;
|
|
512
|
+
}
|
|
500
513
|
function resolveStateRepoConfig(config) {
|
|
501
514
|
if (config.state?.repo && config.state?.path) {
|
|
502
515
|
parseStateRepoSlug(config.state.repo);
|
|
503
516
|
return {
|
|
504
517
|
repo: config.state.repo,
|
|
505
|
-
path: normalizeStatePath(config.state.path)
|
|
518
|
+
path: normalizeStatePath(config.state.path),
|
|
519
|
+
branch: normalizeStateBranch(config.state.branch)
|
|
506
520
|
};
|
|
507
521
|
}
|
|
508
522
|
if (config.github?.owner && config.github?.repo) {
|
|
509
523
|
return {
|
|
510
524
|
repo: `${config.github.owner}/kody-state`,
|
|
511
|
-
path: normalizeStatePath(config.github.repo)
|
|
525
|
+
path: normalizeStatePath(config.github.repo),
|
|
526
|
+
branch: normalizeStateBranch(void 0)
|
|
512
527
|
};
|
|
513
528
|
}
|
|
514
529
|
throw new Error("stateRepo: config.state or config.github owner/repo is required");
|
|
@@ -516,7 +531,7 @@ function resolveStateRepoConfig(config) {
|
|
|
516
531
|
function parseStateRepo(config) {
|
|
517
532
|
const state = resolveStateRepoConfig(config);
|
|
518
533
|
const parsed = parseStateRepoSlug(state.repo);
|
|
519
|
-
return { ...parsed, basePath: state.path };
|
|
534
|
+
return { ...parsed, basePath: state.path, branch: state.branch };
|
|
520
535
|
}
|
|
521
536
|
function stateRepoPath(config, filePath) {
|
|
522
537
|
const state = resolveStateRepoConfig(config);
|
|
@@ -528,14 +543,15 @@ function apiPath(config, targetPath) {
|
|
|
528
543
|
return `/repos/${parsed.owner}/${parsed.repo}/contents/${targetPath}`;
|
|
529
544
|
}
|
|
530
545
|
function branchApiPath(config, targetPath) {
|
|
531
|
-
|
|
546
|
+
const state = resolveStateRepoConfig(config);
|
|
547
|
+
return `${apiPath(config, targetPath)}?ref=${encodeURIComponent(state.branch)}`;
|
|
532
548
|
}
|
|
533
549
|
function ensureStateBranch(config, cwd) {
|
|
534
550
|
const parsed = parseStateRepo(config);
|
|
535
|
-
const cacheKey3 = `${parsed.owner}/${parsed.repo}:${
|
|
551
|
+
const cacheKey3 = `${parsed.owner}/${parsed.repo}:${parsed.branch}`;
|
|
536
552
|
if (ensuredStateBranches.has(cacheKey3)) return;
|
|
537
553
|
try {
|
|
538
|
-
gh(["api", `/repos/${parsed.owner}/${parsed.repo}/git/ref/heads/${
|
|
554
|
+
gh(["api", `/repos/${parsed.owner}/${parsed.repo}/git/ref/heads/${parsed.branch}`], { cwd });
|
|
539
555
|
ensuredStateBranches.add(cacheKey3);
|
|
540
556
|
return;
|
|
541
557
|
} catch (err) {
|
|
@@ -550,7 +566,7 @@ function ensureStateBranch(config, cwd) {
|
|
|
550
566
|
try {
|
|
551
567
|
gh(["api", "--method", "POST", `/repos/${parsed.owner}/${parsed.repo}/git/refs`, "--input", "-"], {
|
|
552
568
|
cwd,
|
|
553
|
-
input: JSON.stringify({ ref: `refs/heads/${
|
|
569
|
+
input: JSON.stringify({ ref: `refs/heads/${parsed.branch}`, sha })
|
|
554
570
|
});
|
|
555
571
|
} catch (err) {
|
|
556
572
|
if (!isAlreadyExists(err)) throw err;
|
|
@@ -590,7 +606,7 @@ function writeStateText(config, cwd, filePath, content, message, sha) {
|
|
|
590
606
|
const payload = {
|
|
591
607
|
message,
|
|
592
608
|
content: Buffer.from(content, "utf-8").toString("base64"),
|
|
593
|
-
branch:
|
|
609
|
+
branch: resolveStateRepoConfig(config).branch
|
|
594
610
|
};
|
|
595
611
|
if (sha) payload.sha = sha;
|
|
596
612
|
gh(["api", "--method", "PUT", apiPath(config, targetPath), "--input", "-"], {
|
|
@@ -655,11 +671,51 @@ function parseProviderModel(s) {
|
|
|
655
671
|
}
|
|
656
672
|
return { provider: s.slice(0, slash), model: s.slice(slash + 1) };
|
|
657
673
|
}
|
|
674
|
+
function optionalRuntimeString(raw, key) {
|
|
675
|
+
const value = raw[key];
|
|
676
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
677
|
+
}
|
|
678
|
+
function parseModelRuntimeConfig(modelSpec, rawConfig) {
|
|
679
|
+
const fallback = parseProviderModel(modelSpec);
|
|
680
|
+
const raw = rawConfig?.trim();
|
|
681
|
+
if (!raw) return fallback;
|
|
682
|
+
let parsed;
|
|
683
|
+
try {
|
|
684
|
+
parsed = JSON.parse(raw);
|
|
685
|
+
} catch (err) {
|
|
686
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
687
|
+
throw new Error(`KODY_MODEL_CONFIG is invalid JSON: ${msg}`);
|
|
688
|
+
}
|
|
689
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
690
|
+
throw new Error("KODY_MODEL_CONFIG must be a JSON object");
|
|
691
|
+
}
|
|
692
|
+
const record2 = parsed;
|
|
693
|
+
const modelName = optionalRuntimeString(record2, "modelName");
|
|
694
|
+
if (!modelName) {
|
|
695
|
+
throw new Error("KODY_MODEL_CONFIG.modelName is required");
|
|
696
|
+
}
|
|
697
|
+
const protocol = optionalRuntimeString(record2, "protocol");
|
|
698
|
+
const baseURL = optionalRuntimeString(record2, "baseURL");
|
|
699
|
+
const apiKeyEnvVar = optionalRuntimeString(record2, "apiKeyEnvVar");
|
|
700
|
+
const spec = optionalRuntimeString(record2, "spec");
|
|
701
|
+
const provider = optionalRuntimeString(record2, "provider") ?? fallback.provider;
|
|
702
|
+
const out = {
|
|
703
|
+
provider,
|
|
704
|
+
model: modelName
|
|
705
|
+
};
|
|
706
|
+
if (protocol) out.protocol = protocol;
|
|
707
|
+
if (baseURL) out.baseURL = baseURL;
|
|
708
|
+
if (apiKeyEnvVar) out.apiKeyEnvVar = apiKeyEnvVar;
|
|
709
|
+
if (spec) out.spec = spec;
|
|
710
|
+
if (protocol === "openai") out.litellmProvider = "openai";
|
|
711
|
+
return out;
|
|
712
|
+
}
|
|
658
713
|
function providerApiKeyEnvVar(provider) {
|
|
659
714
|
if (provider === "anthropic" || provider === "claude") return "ANTHROPIC_API_KEY";
|
|
660
715
|
return `${provider.toUpperCase()}_API_KEY`;
|
|
661
716
|
}
|
|
662
717
|
function needsLitellmProxy(model) {
|
|
718
|
+
if (model.protocol === "anthropic") return false;
|
|
663
719
|
return model.provider !== "claude" && model.provider !== "anthropic";
|
|
664
720
|
}
|
|
665
721
|
function loadConfig(projectDir = process.cwd()) {
|
|
@@ -722,12 +778,14 @@ function parseStateConfig(raw, github) {
|
|
|
722
778
|
const nested = recordValue(raw.state) ?? {};
|
|
723
779
|
const repoRaw = typeof raw.stateRepo === "string" ? raw.stateRepo : nested.repo;
|
|
724
780
|
const pathRaw = typeof raw.statePath === "string" ? raw.statePath : nested.path;
|
|
781
|
+
const branchRaw = typeof raw.stateBranch === "string" ? raw.stateBranch : nested.branch;
|
|
725
782
|
const stateRepo = typeof repoRaw === "string" && repoRaw.trim().length > 0 ? repoRaw.trim() : `https://github.com/${String(github.owner)}/kody-state`;
|
|
726
783
|
parseStateRepoSlug(stateRepo);
|
|
727
784
|
const statePath = typeof pathRaw === "string" && pathRaw.trim().length > 0 ? pathRaw.trim() : String(github.repo);
|
|
728
785
|
return {
|
|
729
786
|
repo: stateRepo,
|
|
730
|
-
path: normalizeStatePath(statePath)
|
|
787
|
+
path: normalizeStatePath(statePath),
|
|
788
|
+
branch: typeof branchRaw === "string" && branchRaw.trim().length > 0 ? normalizeStateBranch(branchRaw) : normalizeStateBranch(void 0)
|
|
731
789
|
};
|
|
732
790
|
}
|
|
733
791
|
function parseAccessConfig(raw) {
|
|
@@ -2423,14 +2481,50 @@ function listRepairCandidates(repoSlug) {
|
|
|
2423
2481
|
function dispatchVerb(workflowFile, repoSlug, capability, prNumber) {
|
|
2424
2482
|
return dispatchWorkflow(workflowFile, capability, prNumber, repoSlug);
|
|
2425
2483
|
}
|
|
2426
|
-
function
|
|
2484
|
+
function capabilityMarker(slug2) {
|
|
2485
|
+
return `<!-- kody-capability: ${slug2} -->`;
|
|
2486
|
+
}
|
|
2487
|
+
function normalizeRecommendationIntent(body) {
|
|
2488
|
+
const marker = body.match(/<!--\s*kody-intent:\s*([\s\S]*?)-->/i);
|
|
2489
|
+
if (marker?.[1]) return marker[1].trim().replace(/\s+/g, " ").toLowerCase();
|
|
2490
|
+
const legacy = body.match(/(?:^|\n)\s*(?:kody-cmd:\s*|@kody\s+)([a-z][\w-]*(?:\s+--pr\s+\d+)?)/i);
|
|
2491
|
+
if (legacy?.[1]) return legacy[1].trim().replace(/\s+/g, " ").toLowerCase();
|
|
2492
|
+
return null;
|
|
2493
|
+
}
|
|
2494
|
+
function containsExecutableKodyCommand(body) {
|
|
2495
|
+
return /@kody\b/i.test(body) || /\bkody-cmd\s*:/i.test(body);
|
|
2496
|
+
}
|
|
2497
|
+
function recommendationAlreadyExists(repoSlug, prNumber, body, capabilitySlug) {
|
|
2498
|
+
const requestedIntent = normalizeRecommendationIntent(body);
|
|
2499
|
+
const requestedText = body.trim().replace(/\s+/g, " ");
|
|
2500
|
+
const raw = gh(["issue", "view", String(prNumber), "-R", repoSlug, "--json", "comments"]);
|
|
2501
|
+
const parsed = JSON.parse(raw);
|
|
2502
|
+
return (parsed.comments ?? []).some((comment) => {
|
|
2503
|
+
const existing = comment.body ?? "";
|
|
2504
|
+
if (capabilitySlug && !existing.includes(capabilityMarker(capabilitySlug))) return false;
|
|
2505
|
+
const existingIntent = normalizeRecommendationIntent(existing);
|
|
2506
|
+
if (requestedIntent && existingIntent) return requestedIntent === existingIntent;
|
|
2507
|
+
if (capabilitySlug && requestedIntent) return true;
|
|
2508
|
+
return existing.trim().replace(/\s+/g, " ").includes(requestedText);
|
|
2509
|
+
});
|
|
2510
|
+
}
|
|
2511
|
+
function postRecommendation(repoSlug, prNumber, mention, message, capabilitySlug) {
|
|
2512
|
+
if (containsExecutableKodyCommand(message)) {
|
|
2513
|
+
return {
|
|
2514
|
+
ok: false,
|
|
2515
|
+
error: "recommendation body contains executable Kody command text; use inert kody-intent metadata"
|
|
2516
|
+
};
|
|
2517
|
+
}
|
|
2427
2518
|
const mentioned = mention ? `${mention} ${message}` : message;
|
|
2428
2519
|
const body = capabilitySlug ? `${mentioned}
|
|
2429
2520
|
|
|
2430
|
-
|
|
2521
|
+
${capabilityMarker(capabilitySlug)}` : mentioned;
|
|
2431
2522
|
try {
|
|
2432
|
-
|
|
2433
|
-
|
|
2523
|
+
if (recommendationAlreadyExists(repoSlug, prNumber, body, capabilitySlug)) {
|
|
2524
|
+
return { ok: true, posted: false };
|
|
2525
|
+
}
|
|
2526
|
+
gh(["issue", "comment", String(prNumber), "-R", repoSlug, "--body-file", "-"], { input: body });
|
|
2527
|
+
return { ok: true, posted: true };
|
|
2434
2528
|
} catch (err) {
|
|
2435
2529
|
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
2436
2530
|
}
|
|
@@ -2671,8 +2765,8 @@ function capabilityToolDefinitions(opts) {
|
|
|
2671
2765
|
handler: async (args) => {
|
|
2672
2766
|
const pr = Number(args.pr);
|
|
2673
2767
|
const body = String(args.body ?? "");
|
|
2674
|
-
const result = postRecommendation(pr, opts.operatorMention, body, opts.capabilitySlug);
|
|
2675
|
-
const text = result.ok ? `Recommendation posted on PR #${pr}.` : `Recommendation failed on PR #${pr}: ${result.error}`;
|
|
2768
|
+
const result = postRecommendation(opts.repoSlug, pr, opts.operatorMention, body, opts.capabilitySlug);
|
|
2769
|
+
const text = result.ok ? result.posted ? `Recommendation posted on PR #${pr}.` : `Recommendation already exists on PR #${pr}; skipped.` : `Recommendation failed on PR #${pr}: ${result.error}`;
|
|
2676
2770
|
return { content: [{ type: "text", text }] };
|
|
2677
2771
|
}
|
|
2678
2772
|
};
|
|
@@ -5836,13 +5930,16 @@ function resolveLitellmTimeoutMs() {
|
|
|
5836
5930
|
return DEFAULT_LITELLM_STARTUP_TIMEOUT_SEC * 1e3;
|
|
5837
5931
|
}
|
|
5838
5932
|
function generateLitellmConfigYaml(model) {
|
|
5839
|
-
const apiKeyVar = providerApiKeyEnvVar(model.provider);
|
|
5933
|
+
const apiKeyVar = model.apiKeyEnvVar ?? providerApiKeyEnvVar(model.provider);
|
|
5934
|
+
const litellmProvider = model.litellmProvider ?? model.provider;
|
|
5935
|
+
const providerParams = model.baseURL ? [["api_base", model.baseURL]] : [];
|
|
5840
5936
|
return [
|
|
5841
5937
|
"model_list:",
|
|
5842
5938
|
` - model_name: ${model.model}`,
|
|
5843
5939
|
` litellm_params:`,
|
|
5844
|
-
` model: ${
|
|
5940
|
+
` model: ${litellmProvider}/${model.model}`,
|
|
5845
5941
|
` api_key: os.environ/${apiKeyVar}`,
|
|
5942
|
+
...providerParams.map(([key, value]) => ` ${key}: ${value}`),
|
|
5846
5943
|
"",
|
|
5847
5944
|
"litellm_settings:",
|
|
5848
5945
|
" drop_params: true",
|
|
@@ -6878,6 +6975,7 @@ function stateRepoContext(config, goalId, logPath) {
|
|
|
6878
6975
|
return {
|
|
6879
6976
|
repo: state.repo,
|
|
6880
6977
|
path: state.path,
|
|
6978
|
+
branch: state.branch,
|
|
6881
6979
|
goalStatePath: `${state.path}/${goalStateLogPath(goalId)}`,
|
|
6882
6980
|
logPath: `${state.path}/${logPath}`
|
|
6883
6981
|
};
|
|
@@ -6976,16 +7074,17 @@ function linkContext(stateRepo) {
|
|
|
6976
7074
|
const server = process.env.GITHUB_SERVER_URL?.trim() || "https://github.com";
|
|
6977
7075
|
if (runId && repository) links.workflowRun = `${server}/${repository}/actions/runs/${runId}`;
|
|
6978
7076
|
const repo = stringValue(stateRepo?.repo);
|
|
7077
|
+
const branch = stringValue(stateRepo?.branch);
|
|
6979
7078
|
const goalStatePath2 = stringValue(stateRepo?.goalStatePath);
|
|
6980
7079
|
const logPath = stringValue(stateRepo?.logPath);
|
|
6981
|
-
if (repo && goalStatePath2) links.goalState = githubBlobUrl(repo, goalStatePath2);
|
|
6982
|
-
if (repo && logPath) links.log = githubBlobUrl(repo, logPath);
|
|
7080
|
+
if (repo && branch && goalStatePath2) links.goalState = githubBlobUrl(repo, branch, goalStatePath2);
|
|
7081
|
+
if (repo && branch && logPath) links.log = githubBlobUrl(repo, branch, logPath);
|
|
6983
7082
|
return Object.keys(links).length > 0 ? links : void 0;
|
|
6984
7083
|
}
|
|
6985
|
-
function githubBlobUrl(repo, filePath) {
|
|
7084
|
+
function githubBlobUrl(repo, branch, filePath) {
|
|
6986
7085
|
try {
|
|
6987
7086
|
const parsed = parseStateRepoSlug(repo);
|
|
6988
|
-
return `https://github.com/${parsed.owner}/${parsed.repo}/blob/${
|
|
7087
|
+
return `https://github.com/${parsed.owner}/${parsed.repo}/blob/${encodeURIComponent(branch)}/${filePath}`;
|
|
6989
7088
|
} catch {
|
|
6990
7089
|
return void 0;
|
|
6991
7090
|
}
|
|
@@ -7068,7 +7167,6 @@ var LOGS_KEY, LOG_RUN_KEY, LOG_STARTED_KEY;
|
|
|
7068
7167
|
var init_runLog = __esm({
|
|
7069
7168
|
"src/goal/runLog.ts"() {
|
|
7070
7169
|
"use strict";
|
|
7071
|
-
init_stateBranch();
|
|
7072
7170
|
init_stateRepo();
|
|
7073
7171
|
init_state2();
|
|
7074
7172
|
LOGS_KEY = "__goalRunLogs";
|
|
@@ -17536,7 +17634,7 @@ function overlayDirectoryChildren(cwd, sourceDir, localDir) {
|
|
|
17536
17634
|
function hydrateStateWorkspace(config, cwd) {
|
|
17537
17635
|
if (process.env.VITEST && process.env[TEST_FETCH_ENV] !== "1") return;
|
|
17538
17636
|
const parsed = parseStateRepo(config);
|
|
17539
|
-
const hydrateKey = `${path41.resolve(cwd)}|${parsed.owner}/${parsed.repo}|${parsed.basePath}|${
|
|
17637
|
+
const hydrateKey = `${path41.resolve(cwd)}|${parsed.owner}/${parsed.repo}|${parsed.basePath}|${parsed.branch}`;
|
|
17540
17638
|
if (hydratedWorkspaces.has(hydrateKey)) return;
|
|
17541
17639
|
const snapshotRoot = fetchStateSnapshot(parsed);
|
|
17542
17640
|
for (const mapping of DIR_MAPPINGS) {
|
|
@@ -17560,7 +17658,7 @@ function fetchStateSnapshot(parsed) {
|
|
|
17560
17658
|
runGit3(["clone", "--no-checkout", "--filter=blob:none", url, cacheDir]);
|
|
17561
17659
|
}
|
|
17562
17660
|
runGit3(["-C", cacheDir, "remote", "set-url", "origin", url]);
|
|
17563
|
-
runGit3(["-C", cacheDir, "fetch", "--depth=1", "origin",
|
|
17661
|
+
runGit3(["-C", cacheDir, "fetch", "--depth=1", "origin", parsed.branch]);
|
|
17564
17662
|
runGit3(["-C", cacheDir, "sparse-checkout", "init", "--cone"]);
|
|
17565
17663
|
runGit3(["-C", cacheDir, "sparse-checkout", "set", parsed.basePath]);
|
|
17566
17664
|
runGit3(["-C", cacheDir, "checkout", "--force", "--detach", "FETCH_HEAD"]);
|
|
@@ -17568,7 +17666,7 @@ function fetchStateSnapshot(parsed) {
|
|
|
17568
17666
|
} catch (err) {
|
|
17569
17667
|
const msg = err instanceof Error ? err.message : String(err);
|
|
17570
17668
|
throw new Error(
|
|
17571
|
-
`stateWorkspace: failed to fetch ${parsed.owner}/${parsed.repo}:${parsed.basePath}@${
|
|
17669
|
+
`stateWorkspace: failed to fetch ${parsed.owner}/${parsed.repo}:${parsed.basePath}@${parsed.branch}: ${msg}`
|
|
17572
17670
|
);
|
|
17573
17671
|
}
|
|
17574
17672
|
return path41.join(cacheDir, parsed.basePath);
|
|
@@ -17577,7 +17675,7 @@ function cacheRoot2() {
|
|
|
17577
17675
|
return process.env[CACHE_ENV2]?.trim() || path41.join(os7.homedir(), ".cache", "kody", "state-repo");
|
|
17578
17676
|
}
|
|
17579
17677
|
function cacheKey2(parsed) {
|
|
17580
|
-
return crypto3.createHash("sha256").update(`${parsed.owner}/${parsed.repo}#${
|
|
17678
|
+
return crypto3.createHash("sha256").update(`${parsed.owner}/${parsed.repo}#${parsed.branch}#${parsed.basePath}`).digest("hex").slice(0, 24);
|
|
17581
17679
|
}
|
|
17582
17680
|
function runGit3(args) {
|
|
17583
17681
|
try {
|
|
@@ -17612,7 +17710,6 @@ var DIR_MAPPINGS, FILE_MAPPINGS, CACHE_ENV2, TEST_FETCH_ENV, hydratedWorkspaces;
|
|
|
17612
17710
|
var init_stateWorkspace = __esm({
|
|
17613
17711
|
"src/stateWorkspace.ts"() {
|
|
17614
17712
|
"use strict";
|
|
17615
|
-
init_stateBranch();
|
|
17616
17713
|
init_stateRepo();
|
|
17617
17714
|
DIR_MAPPINGS = [
|
|
17618
17715
|
{ stateDir: "executables", localDir: path41.join(".kody", "executables") },
|
|
@@ -19367,6 +19464,16 @@ var CHAT_SYSTEM_PROMPT = [
|
|
|
19367
19464
|
"Do not invent file paths, commit SHAs, line numbers, or command output. If you",
|
|
19368
19465
|
"cite something concrete, you must have just read or run it in this session."
|
|
19369
19466
|
].join("\n");
|
|
19467
|
+
var OPENAI_CHAT_SYSTEM_PROMPT = [
|
|
19468
|
+
"You are Kody, an AI assistant for the Kody Operations Dashboard. Reply to the",
|
|
19469
|
+
"user's latest message using the full conversation below as context. Keep replies",
|
|
19470
|
+
"short and simple. Use plain terms, not jargon.",
|
|
19471
|
+
"",
|
|
19472
|
+
"This chat is running through an OpenAI-compatible model endpoint. Answer directly",
|
|
19473
|
+
"from the conversation and provided context. Do not claim to run shell commands,",
|
|
19474
|
+
"edit files, inspect the repository, or use tools unless the user has supplied",
|
|
19475
|
+
"the relevant content in the chat."
|
|
19476
|
+
].join("\n");
|
|
19370
19477
|
var CROSS_REPO_PROMPT = [
|
|
19371
19478
|
"# Working across repositories",
|
|
19372
19479
|
"You are NOT limited to the repository at your current working directory. You",
|
|
@@ -19428,7 +19535,7 @@ async function runChatTurn(opts) {
|
|
|
19428
19535
|
return { exitCode: 64, error };
|
|
19429
19536
|
}
|
|
19430
19537
|
const { turns: promptTurns, imagePaths } = prepareAttachments(turns, opts.cwd, opts.sessionId);
|
|
19431
|
-
const basePrompt = opts.systemPrompt ?? CHAT_SYSTEM_PROMPT;
|
|
19538
|
+
const basePrompt = opts.systemPrompt ?? (opts.model.protocol === "openai" ? OPENAI_CHAT_SYSTEM_PROMPT : CHAT_SYSTEM_PROMPT);
|
|
19432
19539
|
const catalog = buildExecutableCatalog();
|
|
19433
19540
|
const taskArtifactsPaths = prepareTaskArtifactsDir(opts.cwd, opts.sessionId);
|
|
19434
19541
|
const artifactAddendum = taskArtifactsPromptAddendum({
|
|
@@ -19462,6 +19569,14 @@ async function runChatTurn(opts) {
|
|
|
19462
19569
|
artifactAddendum
|
|
19463
19570
|
].filter((s) => typeof s === "string" && s.length > 0).join("\n\n");
|
|
19464
19571
|
const prompt = buildPrompt(promptTurns);
|
|
19572
|
+
if (opts.model.protocol === "openai" && opts.litellmUrl) {
|
|
19573
|
+
return runOpenAIChatTurn({
|
|
19574
|
+
opts,
|
|
19575
|
+
turns: promptTurns,
|
|
19576
|
+
systemPrompt,
|
|
19577
|
+
sessionFile: opts.sessionFile
|
|
19578
|
+
});
|
|
19579
|
+
}
|
|
19465
19580
|
let progressSeq = 0;
|
|
19466
19581
|
const invoke = opts.invokeAgent ?? ((p) => runAgent({
|
|
19467
19582
|
prompt: p,
|
|
@@ -19564,6 +19679,74 @@ async function runChatTurn(opts) {
|
|
|
19564
19679
|
}
|
|
19565
19680
|
return { exitCode: 0, reply };
|
|
19566
19681
|
}
|
|
19682
|
+
async function runOpenAIChatTurn(args) {
|
|
19683
|
+
const { opts, turns, systemPrompt, sessionFile } = args;
|
|
19684
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
19685
|
+
const url = `${opts.litellmUrl.replace(/\/+$/, "")}/v1/chat/completions`;
|
|
19686
|
+
try {
|
|
19687
|
+
const response = await doFetch(url, {
|
|
19688
|
+
method: "POST",
|
|
19689
|
+
headers: { "Content-Type": "application/json" },
|
|
19690
|
+
body: JSON.stringify({
|
|
19691
|
+
model: opts.model.model,
|
|
19692
|
+
messages: [
|
|
19693
|
+
{ role: "system", content: systemPrompt },
|
|
19694
|
+
...turns.map((turn) => ({
|
|
19695
|
+
role: turn.role,
|
|
19696
|
+
content: turn.content
|
|
19697
|
+
}))
|
|
19698
|
+
],
|
|
19699
|
+
stream: false
|
|
19700
|
+
})
|
|
19701
|
+
});
|
|
19702
|
+
if (!response.ok) {
|
|
19703
|
+
const text = await response.text().catch(() => "");
|
|
19704
|
+
const error = `OpenAI-compatible model request failed ${response.status}${text ? `: ${text.slice(0, 500)}` : ""}`;
|
|
19705
|
+
await emit(opts.sink, "chat.error", opts.sessionId, "error", { error });
|
|
19706
|
+
return { exitCode: 99, error };
|
|
19707
|
+
}
|
|
19708
|
+
const payload = await response.json();
|
|
19709
|
+
const reply = extractOpenAIReply(payload).trim();
|
|
19710
|
+
if (!reply) {
|
|
19711
|
+
const error = typeof payload.error?.message === "string" ? payload.error.message : "OpenAI-compatible model completed without producing a reply";
|
|
19712
|
+
await emit(opts.sink, "chat.error", opts.sessionId, "error", { error });
|
|
19713
|
+
return { exitCode: 99, error };
|
|
19714
|
+
}
|
|
19715
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
19716
|
+
appendTurn(sessionFile, {
|
|
19717
|
+
role: "assistant",
|
|
19718
|
+
content: reply,
|
|
19719
|
+
timestamp: now
|
|
19720
|
+
});
|
|
19721
|
+
await emit(opts.sink, "chat.message", opts.sessionId, "message", {
|
|
19722
|
+
sessionId: opts.sessionId,
|
|
19723
|
+
role: "assistant",
|
|
19724
|
+
content: reply,
|
|
19725
|
+
timestamp: now
|
|
19726
|
+
});
|
|
19727
|
+
await emit(opts.sink, "chat.done", opts.sessionId, "done", { sessionId: opts.sessionId });
|
|
19728
|
+
return { exitCode: 0, reply };
|
|
19729
|
+
} catch (err) {
|
|
19730
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
19731
|
+
await emit(opts.sink, "chat.error", opts.sessionId, "error", { error });
|
|
19732
|
+
return { exitCode: 99, error };
|
|
19733
|
+
}
|
|
19734
|
+
}
|
|
19735
|
+
function extractOpenAIReply(payload) {
|
|
19736
|
+
const content = payload.choices?.[0]?.message?.content;
|
|
19737
|
+
if (typeof content === "string") return content;
|
|
19738
|
+
if (Array.isArray(content)) {
|
|
19739
|
+
return content.map((part) => {
|
|
19740
|
+
if (typeof part === "string") return part;
|
|
19741
|
+
if (part && typeof part === "object" && "text" in part) {
|
|
19742
|
+
const text = part.text;
|
|
19743
|
+
return typeof text === "string" ? text : "";
|
|
19744
|
+
}
|
|
19745
|
+
return "";
|
|
19746
|
+
}).join("");
|
|
19747
|
+
}
|
|
19748
|
+
return "";
|
|
19749
|
+
}
|
|
19567
19750
|
function buildPrompt(turns) {
|
|
19568
19751
|
const body = turns.map((t) => `${t.role === "user" ? "User" : "Assistant"}: ${t.content}`).join("\n\n");
|
|
19569
19752
|
return `${body}
|
|
@@ -21406,7 +21589,10 @@ async function brainServe(opts) {
|
|
|
21406
21589
|
}
|
|
21407
21590
|
const apiKey = getApiKey();
|
|
21408
21591
|
const port = Number(process.env.PORT ?? DEFAULT_PORT);
|
|
21409
|
-
const model =
|
|
21592
|
+
const model = parseModelRuntimeConfig(
|
|
21593
|
+
process.env.MODEL?.trim() || "claude/claude-haiku-4-5-20251001",
|
|
21594
|
+
process.env.KODY_MODEL_CONFIG
|
|
21595
|
+
);
|
|
21410
21596
|
const usesProxy = needsLitellmProxy(model);
|
|
21411
21597
|
let handle = null;
|
|
21412
21598
|
if (usesProxy) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kody-ade/kody-engine",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.288",
|
|
4
4
|
"description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|