@kody-ade/kody-engine 0.4.596 → 0.4.598

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.
Files changed (2) hide show
  1. package/dist/bin/kody.js +127 -46
  2. 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.596",
18
+ version: "0.4.598",
19
19
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
20
20
  license: "MIT",
21
21
  type: "module",
@@ -4228,7 +4228,7 @@ async function runAgent(opts) {
4228
4228
  }
4229
4229
  try {
4230
4230
  const queryOptions = {
4231
- model: opts.litellmUrl ? litellmModelGroup(opts.model) : opts.model.model,
4231
+ model: opts.litellmUrl ? opts.litellmModelGroupOverride ?? litellmModelGroup(opts.model) : opts.model.model,
4232
4232
  cwd: opts.cwd,
4233
4233
  // Fresh array (never mutate the shared DEFAULT_ALLOWED_TOOLS const) so
4234
4234
  // opt-in tools like fetch_repo can be appended below.
@@ -7850,6 +7850,43 @@ function generateLitellmConfigYaml(model) {
7850
7850
  ]);
7851
7851
  return ["model_list:", ...modelEntries, "", "litellm_settings:", " drop_params: true", ""].join("\n");
7852
7852
  }
7853
+ function modelEntry(modelName, model) {
7854
+ const apiKeyVar = model.apiKeyEnvVar ?? providerApiKeyEnvVar(model.provider);
7855
+ const litellmProvider = model.litellmProvider ?? model.provider;
7856
+ return [
7857
+ ` - model_name: ${modelName}`,
7858
+ ` litellm_params:`,
7859
+ ` model: ${litellmProvider}/${model.model}`,
7860
+ ` api_key: os.environ/${apiKeyVar}`,
7861
+ ...model.baseURL ? [` api_base: ${model.baseURL}`] : []
7862
+ ];
7863
+ }
7864
+ function generateAutomaticLitellmConfigYaml(models) {
7865
+ if (models.length < 2) throw new Error("Automatic LiteLLM requires at least two models");
7866
+ const groups = models.map((_, index) => `kody-automatic-${index}`);
7867
+ const entries = models.flatMap((model, index) => modelEntry(groups[index], model));
7868
+ for (const alias of CLAUDE_CODE_PROXY_MODEL_ALIASES) entries.push(...modelEntry(alias, models[0]));
7869
+ const fallbackSources = [groups[0], ...CLAUDE_CODE_PROXY_MODEL_ALIASES, ...groups.slice(1, -1)];
7870
+ const fallbacks = fallbackSources.map((source) => {
7871
+ const sourceIndex = source.startsWith("kody-automatic-") ? Number(source.slice("kody-automatic-".length)) : 0;
7872
+ return ` - ${source}: [${groups.slice(sourceIndex + 1).join(", ")}]`;
7873
+ });
7874
+ return [
7875
+ "model_list:",
7876
+ ...entries,
7877
+ "",
7878
+ "litellm_settings:",
7879
+ " drop_params: true",
7880
+ "",
7881
+ "router_settings:",
7882
+ " num_retries: 0",
7883
+ " allowed_fails: 0",
7884
+ " disable_cooldowns: true",
7885
+ " fallbacks:",
7886
+ ...fallbacks,
7887
+ ""
7888
+ ].join("\n");
7889
+ }
7853
7890
  function litellmModelGroups(model) {
7854
7891
  const primary = litellmModelGroup(model);
7855
7892
  return Array.from(/* @__PURE__ */ new Set([primary, ...CLAUDE_CODE_PROXY_MODEL_ALIASES]));
@@ -7922,9 +7959,29 @@ function resolveLitellmCommand() {
7922
7959
  }
7923
7960
  async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL, runtimeEnvironment = {}) {
7924
7961
  if (!needsLitellmProxy(model)) return null;
7962
+ return startLitellmProxy({
7963
+ projectDir,
7964
+ url,
7965
+ runtimeEnvironment,
7966
+ configYaml: generateLitellmConfigYaml(model),
7967
+ requiredModelGroups: litellmModelGroups(model)
7968
+ });
7969
+ }
7970
+ async function startAutomaticLitellm(models, projectDir, runtimeEnvironment = {}, url = LITELLM_DEFAULT_URL) {
7971
+ const handle = await startLitellmProxy({
7972
+ projectDir,
7973
+ url,
7974
+ runtimeEnvironment,
7975
+ configYaml: generateAutomaticLitellmConfigYaml(models),
7976
+ requiredModelGroups: [AUTOMATIC_LITELLM_MODEL_GROUP, ...CLAUDE_CODE_PROXY_MODEL_ALIASES]
7977
+ });
7978
+ return { ...handle, modelGroup: AUTOMATIC_LITELLM_MODEL_GROUP };
7979
+ }
7980
+ async function startLitellmProxy(input) {
7981
+ const { projectDir, runtimeEnvironment } = input;
7925
7982
  const cmd = resolveLitellmCommand();
7926
- let activeUrl = url.replace(/\/+$/, "");
7927
- const modelGroups = litellmModelGroups(model);
7983
+ let activeUrl = input.url.replace(/\/+$/, "");
7984
+ const modelGroups = input.requiredModelGroups;
7928
7985
  const childEnv = stripBlockingEnv({
7929
7986
  ...process.env,
7930
7987
  ...readDotenvApiKeys(projectDir),
@@ -7936,7 +7993,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
7936
7993
  const portMatch = activeUrl.match(/:(\d+)/);
7937
7994
  const port = portMatch ? portMatch[1] : "4000";
7938
7995
  const configPath = path27.join(os4.tmpdir(), `kody-local-litellm-${Date.now()}.yaml`);
7939
- fs28.writeFileSync(configPath, generateLitellmConfigYaml(model));
7996
+ fs28.writeFileSync(configPath, input.configYaml);
7940
7997
  const args = ["--config", configPath, "--port", port];
7941
7998
  const nextLogPath = path27.join(os4.tmpdir(), `kody-local-litellm-${Date.now()}.log`);
7942
7999
  const outFd = fs28.openSync(nextLogPath, "w");
@@ -8062,7 +8119,7 @@ function stripBlockingEnv(env) {
8062
8119
  delete out.AI_BASE_URL;
8063
8120
  return out;
8064
8121
  }
8065
- var LITELLM_PIP_PACKAGES, DEFAULT_LITELLM_STARTUP_TIMEOUT_SEC, LITELLM_HEALTH_POLL_INTERVAL_MS, CLAUDE_CODE_PROXY_MODEL_ALIASES;
8122
+ var LITELLM_PIP_PACKAGES, DEFAULT_LITELLM_STARTUP_TIMEOUT_SEC, LITELLM_HEALTH_POLL_INTERVAL_MS, CLAUDE_CODE_PROXY_MODEL_ALIASES, AUTOMATIC_LITELLM_MODEL_GROUP;
8066
8123
  var init_litellm = __esm({
8067
8124
  "src/litellm.ts"() {
8068
8125
  "use strict";
@@ -8071,6 +8128,7 @@ var init_litellm = __esm({
8071
8128
  DEFAULT_LITELLM_STARTUP_TIMEOUT_SEC = 150;
8072
8129
  LITELLM_HEALTH_POLL_INTERVAL_MS = 2e3;
8073
8130
  CLAUDE_CODE_PROXY_MODEL_ALIASES = ["claude-haiku-4-5-20251001", "haiku"];
8131
+ AUTOMATIC_LITELLM_MODEL_GROUP = "kody-automatic-0";
8074
8132
  }
8075
8133
  });
8076
8134
 
@@ -18269,8 +18327,7 @@ function githubRepositoryParts(repoUrl) {
18269
18327
  if (!parts[0] || !repo) throw new Error("repository URL is incomplete");
18270
18328
  return { owner: parts[0], repo };
18271
18329
  }
18272
- function browserOrigin(ctx) {
18273
- const raw = typeof ctx.data.previewUrl === "string" ? ctx.data.previewUrl : "";
18330
+ function browserOrigin(raw) {
18274
18331
  if (!raw) throw new Error("QA target URL is unavailable");
18275
18332
  const parsed = new URL(raw);
18276
18333
  if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
@@ -18355,6 +18412,17 @@ async function prepareMethod(ctx, profile, method) {
18355
18412
  const { repositoryKey, credentialKey } = fieldsForKodyRepository(method);
18356
18413
  const variables = readKodyVariables(ctx.cwd);
18357
18414
  const repositoryUrl = variables[repositoryKey]?.trim() ?? "";
18415
+ const targetUrl = typeof ctx.data.previewUrl === "string" ? ctx.data.previewUrl : "";
18416
+ return prepareKodyRepositoryBrowserAuth(ctx, profile, {
18417
+ repositoryUrl,
18418
+ repositoryKey,
18419
+ credentialKey,
18420
+ methodName: method.name,
18421
+ targetUrl
18422
+ });
18423
+ }
18424
+ async function prepareKodyRepositoryBrowserAuth(ctx, profile, input) {
18425
+ const { repositoryUrl, credentialKey } = input;
18358
18426
  const credential = await resolveRuntimeSecret(credentialKey, ctx);
18359
18427
  ctx.data.qaAuthSecretSources = {
18360
18428
  ...ctx.data.qaAuthSecretSources ?? {},
@@ -18368,14 +18436,14 @@ async function prepareMethod(ctx, profile, method) {
18368
18436
  if (!repositoryUrl) {
18369
18437
  appendAuthMessage(
18370
18438
  ctx,
18371
- `Auth: ${method.name} is incomplete because no \`${repositoryKey}\` variable was found. Note this authenticated surface as a gap.`
18439
+ `Auth: ${input.methodName} is incomplete because no \`${input.repositoryKey ?? "repository"}\` variable was found. Note this authenticated surface as a gap.`
18372
18440
  );
18373
18441
  return false;
18374
18442
  }
18375
18443
  if (!credential.value) {
18376
18444
  appendAuthMessage(
18377
18445
  ctx,
18378
- `Auth: ${method.name} is incomplete because no \`${credentialKey}\` secret was found. Note this authenticated surface as a gap.`
18446
+ `Auth: ${input.methodName} is incomplete because no \`${credentialKey}\` secret was found. Note this authenticated surface as a gap.`
18379
18447
  );
18380
18448
  return false;
18381
18449
  }
@@ -18394,7 +18462,7 @@ async function prepareMethod(ctx, profile, method) {
18394
18462
  throw new Error("GitHub returned incomplete identity data");
18395
18463
  }
18396
18464
  state = writeKodyStorageState({
18397
- origin: browserOrigin(ctx),
18465
+ origin: browserOrigin(input.targetUrl),
18398
18466
  repoUrl: `https://github.com/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`,
18399
18467
  owner,
18400
18468
  repo,
@@ -18408,7 +18476,7 @@ async function prepareMethod(ctx, profile, method) {
18408
18476
  });
18409
18477
  appendAuthMessage(
18410
18478
  ctx,
18411
- `Auth: ${method.name} is already authenticated by the engine-provided browser session. The credential is not available to you; never request, reveal, or report it.`
18479
+ `Auth: ${input.methodName} is already authenticated by the engine-provided browser session. The credential is not available to you; never request, reveal, or report it.`
18412
18480
  );
18413
18481
  return true;
18414
18482
  } catch (error) {
@@ -18416,7 +18484,7 @@ async function prepareMethod(ctx, profile, method) {
18416
18484
  const reason = error instanceof Error ? error.message : String(error);
18417
18485
  appendAuthMessage(
18418
18486
  ctx,
18419
- `Auth: the engine could not prepare ${method.name} (${reason}). Note this authenticated surface as a gap.`
18487
+ `Auth: the engine could not prepare ${input.methodName} (${reason}). Note this authenticated surface as a gap.`
18420
18488
  );
18421
18489
  return false;
18422
18490
  }
@@ -18635,7 +18703,7 @@ var init_prepareSimpleCapabilityRuntime = __esm({
18635
18703
  "src/scripts/prepareSimpleCapabilityRuntime.ts"() {
18636
18704
  "use strict";
18637
18705
  init_loadQaContext();
18638
- init_runtimeSecrets();
18706
+ init_prepareBrowserAuth();
18639
18707
  PLAYWRIGHT_SERVER = {
18640
18708
  name: "playwright",
18641
18709
  command: "npx",
@@ -18647,6 +18715,18 @@ var init_prepareSimpleCapabilityRuntime = __esm({
18647
18715
  configureBrowser(ctx, profile, requirements);
18648
18716
  if (requirements.qaCredentials) {
18649
18717
  await loadQaContext(ctx, profile);
18718
+ }
18719
+ if (requirements.githubTestToken) {
18720
+ const capabilityInput = ctx.data.capabilityInput && typeof ctx.data.capabilityInput === "object" && !Array.isArray(ctx.data.capabilityInput) ? ctx.data.capabilityInput : {};
18721
+ const targetUrl = typeof capabilityInput.url === "string" ? capabilityInput.url : typeof capabilityInput.targetUrl === "string" ? capabilityInput.targetUrl : "";
18722
+ await prepareKodyRepositoryBrowserAuth(ctx, profile, {
18723
+ repositoryUrl: `https://github.com/${ctx.config.github.owner}/${ctx.config.github.repo}`,
18724
+ credentialKey: "E2E_GITHUB_TOKEN",
18725
+ methodName: "Kody protected QA login",
18726
+ targetUrl
18727
+ });
18728
+ }
18729
+ if (requirements.qaCredentials || requirements.githubTestToken) {
18650
18730
  appendPrompt(
18651
18731
  ctx,
18652
18732
  [
@@ -18658,22 +18738,6 @@ var init_prepareSimpleCapabilityRuntime = __esm({
18658
18738
  ].join("\n")
18659
18739
  );
18660
18740
  }
18661
- if (requirements.githubTestToken) {
18662
- const token = await resolveRuntimeSecret("E2E_GITHUB_TOKEN", ctx);
18663
- appendPrompt(
18664
- ctx,
18665
- token.value ? [
18666
- "## Protected GitHub test login",
18667
- "",
18668
- `A protected GitHub test token is available: \`${token.value}\``,
18669
- "Use it only if the target application asks for a GitHub personal access token; never include the token in screenshots, output, logs, files, or messages."
18670
- ].join("\n") : [
18671
- "## Protected GitHub test login",
18672
- "",
18673
- "E2E_GITHUB_TOKEN is not configured. If this Quality Scenario requires GitHub token authentication, return a blocked result."
18674
- ].join("\n")
18675
- );
18676
- }
18677
18741
  };
18678
18742
  }
18679
18743
  });
@@ -22454,6 +22518,19 @@ async function runImplementation(profileName, input) {
22454
22518
  const syntheticPath = ctx.data.syntheticPluginPath;
22455
22519
  const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
22456
22520
  const agents = loadSubagents(profile);
22521
+ if (modelSpec === "automatic") {
22522
+ const automaticEnvironment = {};
22523
+ for (const candidate of modelCandidates) {
22524
+ const resolved2 = await resolveRuntimeModelEnvironment(candidate, ctx);
22525
+ Object.assign(automaticEnvironment, resolved2.environment);
22526
+ for (const warning of resolved2.warnings) process.stderr.write(`\u26A0 ${warning}
22527
+ `);
22528
+ }
22529
+ const lm = await startAutomaticLitellm(modelCandidates, input.cwd, automaticEnvironment);
22530
+ activeLiteLLM.add(lm);
22531
+ const result = await invokeModel(modelCandidates[0], lm, automaticEnvironment, lm.modelGroup);
22532
+ return result;
22533
+ }
22457
22534
  let finalResult;
22458
22535
  for (let candidateIndex = 0; candidateIndex < modelCandidates.length; candidateIndex++) {
22459
22536
  const candidate = modelCandidates[candidateIndex];
@@ -22473,7 +22550,24 @@ async function runImplementation(profileName, input) {
22473
22550
  }
22474
22551
  ctx.data.jobModelProvider = candidate.provider;
22475
22552
  ctx.data.jobModelName = candidate.model;
22476
- const result = await runAgent({
22553
+ const result = await invokeModel(candidate, lm, runtimeModelEnvironment);
22554
+ finalResult = result;
22555
+ const next = modelCandidates[candidateIndex + 1];
22556
+ if (!next || result.outcome !== "failed" || result.outcomeKind !== "rate_limit" || result.safeToReplay !== true) {
22557
+ return result;
22558
+ }
22559
+ process.stderr.write(
22560
+ `[kody agent] ${candidate.spec ?? candidate.model} is rate limited; continuing with ${next.spec ?? next.model}
22561
+ `
22562
+ );
22563
+ if (lm) {
22564
+ lm.kill();
22565
+ activeLiteLLM.delete(lm);
22566
+ }
22567
+ }
22568
+ return finalResult;
22569
+ async function invokeModel(candidate, lm, runtimeModelEnvironment, litellmModelGroupOverride) {
22570
+ return runAgent({
22477
22571
  prompt,
22478
22572
  model: candidate,
22479
22573
  cwd: input.cwd,
@@ -22482,6 +22576,7 @@ async function runImplementation(profileName, input) {
22482
22576
  ...runtimeModelEnvironment
22483
22577
  },
22484
22578
  litellmUrl: lm?.url ?? null,
22579
+ litellmModelGroupOverride,
22485
22580
  // On a connection drop mid-run, restart the (possibly crashed) proxy
22486
22581
  // before the agent retries. No-op for direct-Anthropic runs (lm null).
22487
22582
  ensureBackend: lm ? () => lm.ensureHealthy().then(() => void 0) : void 0,
@@ -22491,7 +22586,7 @@ async function runImplementation(profileName, input) {
22491
22586
  verbose: input.verbose,
22492
22587
  quiet: input.quiet,
22493
22588
  abortController: input.abortController,
22494
- stopOnRateLimit: candidateIndex < modelCandidates.length - 1,
22589
+ stopOnRateLimit: false,
22495
22590
  deadlineAtMs: input.deadlineAtMs,
22496
22591
  ndjsonDir,
22497
22592
  additionalDirectories: agentTaskArtifacts ? [agentTaskArtifacts.absDir] : void 0,
@@ -22541,21 +22636,7 @@ async function runImplementation(profileName, input) {
22541
22636
  schema: ctx.data.capabilityOutputSchema
22542
22637
  } : void 0
22543
22638
  });
22544
- finalResult = result;
22545
- const next = modelCandidates[candidateIndex + 1];
22546
- if (!next || result.outcome !== "failed" || result.outcomeKind !== "rate_limit" || result.safeToReplay !== true) {
22547
- return result;
22548
- }
22549
- process.stderr.write(
22550
- `[kody agent] ${candidate.spec ?? candidate.model} is rate limited; continuing with ${next.spec ?? next.model}
22551
- `
22552
- );
22553
- if (lm) {
22554
- lm.kill();
22555
- activeLiteLLM.delete(lm);
22556
- }
22557
22639
  }
22558
- return finalResult;
22559
22640
  };
22560
22641
  ctx.data.__invokeAgent = invokeAgent;
22561
22642
  const cc = profile.claudeCode;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.596",
3
+ "version": "0.4.598",
4
4
  "description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
5
5
  "license": "MIT",
6
6
  "type": "module",