@kernelonpanic/kitcode 1.4.2 → 1.4.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/dist/index.js CHANGED
@@ -170,7 +170,10 @@ function describeProviderFailure(error, providerId, knownSecrets = []) {
170
170
  }
171
171
  function statusOf(error) {
172
172
  const value = prop(error, "status") ?? prop(error, "statusCode");
173
- return typeof value === "number" && value >= 100 ? value : void 0;
173
+ if (typeof value === "number" && value >= 100) return value;
174
+ const code = prop(error, "code") ?? prop(prop(error, "error"), "code");
175
+ const numeric = typeof code === "string" && /^\d{3}$/.test(code) ? Number(code) : code;
176
+ return typeof numeric === "number" && numeric >= 400 && numeric <= 599 ? numeric : void 0;
174
177
  }
175
178
  function isUserAbort(error, signal) {
176
179
  return signal?.aborted === true || abortKind(error) === "user";
@@ -1440,13 +1443,20 @@ function resolveEffort(effort, messages) {
1440
1443
  return text.length > 500 || HARD_TASK.test(text) ? "high" : "medium";
1441
1444
  }
1442
1445
  var rejected = /* @__PURE__ */ new Set();
1446
+ var capped = /* @__PURE__ */ new Map();
1447
+ var RANK = { low: 0, medium: 1, high: 2, xhigh: 3, max: 4 };
1443
1448
  function markEffortRejected(providerId, model) {
1444
1449
  rejected.add(`${providerId}/${model}`);
1445
1450
  }
1451
+ function markEffortCapped(providerId, model, level) {
1452
+ capped.set(`${providerId}/${model}`, level);
1453
+ }
1446
1454
  function effectiveEffort(kind, model, effort, messages, providerId) {
1447
1455
  if (providerId !== void 0 && rejected.has(`${providerId}/${model}`)) return void 0;
1448
1456
  const requested = resolveEffort(effort, messages);
1449
- return kind === "openai" ? openAiEffort(model, requested) : anthropicEffort(model, requested);
1457
+ const level = kind === "openai" ? openAiEffort(model, requested) : anthropicEffort(model, requested);
1458
+ const cap = providerId === void 0 ? void 0 : capped.get(`${providerId}/${model}`);
1459
+ return level && cap && RANK[level] > RANK[cap] ? cap : level;
1450
1460
  }
1451
1461
  function anthropicEffort(model, effort) {
1452
1462
  if (!effort) return void 0;
@@ -2137,18 +2147,18 @@ function sleep(ms, signal) {
2137
2147
  signal.addEventListener("abort", onAbort, { once: true });
2138
2148
  });
2139
2149
  }
2150
+ var CHARS_PER_TOKEN = 4;
2140
2151
  function estimateRequestTokens(cfg, messages) {
2141
2152
  let characters3 = cfg.system.length;
2142
2153
  try {
2143
2154
  characters3 += JSON.stringify(
2144
2155
  messages,
2145
- (_key, value) => typeof value === "object" && value !== null && value.type === "image" ? `[image:${"#".repeat(2e3)}]` : value
2156
+ (_key, value) => typeof value === "object" && value !== null && value.type === "image" ? `[image:${"#".repeat(6e3)}]` : value
2146
2157
  ).length;
2147
2158
  characters3 += JSON.stringify(cfg.tools.schemas()).length;
2148
2159
  } catch {
2149
- return Math.max(1, characters3);
2150
2160
  }
2151
- return Math.max(1, characters3);
2161
+ return Math.max(1, Math.ceil(characters3 / CHARS_PER_TOKEN));
2152
2162
  }
2153
2163
  async function runToolCalls(cfg, content, hooks, signal) {
2154
2164
  const calls = content.filter((block) => block.type === "tool_use");
@@ -3789,6 +3799,7 @@ function withoutThinkingReplay(history) {
3789
3799
  });
3790
3800
  return changed ? out : history;
3791
3801
  }
3802
+ var CHARS_PER_TOKEN2 = 4;
3792
3803
  function estimateHistoryTokens(history) {
3793
3804
  let chars = 0;
3794
3805
  for (const message of history) {
@@ -3800,7 +3811,7 @@ function estimateHistoryTokens(history) {
3800
3811
  else if (block.type === "image") chars += 6e3;
3801
3812
  }
3802
3813
  }
3803
- return Math.round(chars / 4);
3814
+ return Math.round(chars / CHARS_PER_TOKEN2);
3804
3815
  }
3805
3816
  function shouldAutoCompact(used, window) {
3806
3817
  return window !== null && window > 0 && used / window >= 0.8;
@@ -3820,7 +3831,9 @@ function estimateCompactBudget(history, maxTokens = MERGE_MAX_TOKENS) {
3820
3831
  const rendered = renderedMessages.join("\n\n");
3821
3832
  if (rendered.length <= SINGLE_REQUEST_MAX_CHARS) {
3822
3833
  return {
3823
- inputTokens: Math.max(1, rendered.length + 1e3),
3834
+ // Tokens, not characters: counting characters made a turn budget refuse
3835
+ // compaction (four times too expensive) just when the window was full.
3836
+ inputTokens: Math.max(1, Math.ceil(rendered.length / CHARS_PER_TOKEN2) + 1e3),
3824
3837
  maxOutputTokens: Math.max(1, Math.min(SINGLE_MAX_TOKENS, Math.floor(maxTokens)))
3825
3838
  };
3826
3839
  }
@@ -3838,7 +3851,7 @@ function estimateCompactBudget(history, maxTokens = MERGE_MAX_TOKENS) {
3838
3851
  const intermediateOutput = chunks.length * chunkOutput + Math.max(0, mergeRequests - 1) * mergeOutput;
3839
3852
  const requestCount = chunks.length + mergeRequests;
3840
3853
  return {
3841
- inputTokens: Math.max(1, chunkInput + intermediateOutput * 8 + requestCount * 1e3),
3854
+ inputTokens: Math.max(1, Math.ceil(chunkInput / CHARS_PER_TOKEN2) + intermediateOutput * 2 + requestCount * 1e3),
3842
3855
  maxOutputTokens: totalOutput
3843
3856
  };
3844
3857
  }
@@ -4400,7 +4413,7 @@ function clip(value) {
4400
4413
  // package.json
4401
4414
  var package_default = {
4402
4415
  name: "@kernelonpanic/kitcode",
4403
- version: "1.4.2",
4416
+ version: "1.4.3",
4404
4417
  description: "Terminal coding agent with a config you never have to write by hand",
4405
4418
  type: "module",
4406
4419
  license: "MIT",
@@ -4468,7 +4481,22 @@ var package_default = {
4468
4481
 
4469
4482
  // src/version.ts
4470
4483
  var KITCODE_VERSION = package_default.version;
4471
- var KITCODE_COMMIT = true ? "6b1ec0a742c44892447e71f477c068d8ba514c6a" : "development";
4484
+ var KITCODE_COMMIT = true ? "586eb4def64a8ef824bf2fe2f58a9f71e18090f0" : "development";
4485
+ function kitcodeUserAgent(sdkUserAgent2) {
4486
+ return `KitCode/${KITCODE_VERSION} ${sdkUserAgent2}`.trim();
4487
+ }
4488
+ function sdkUserAgent(client) {
4489
+ const read = client?.getUserAgent;
4490
+ try {
4491
+ return typeof read === "function" ? String(read.call(client)) : "";
4492
+ } catch {
4493
+ return "";
4494
+ }
4495
+ }
4496
+ function withKitcodeUserAgent(sdkUserAgent2, configured = {}) {
4497
+ const overridden = Object.keys(configured).some((name) => name.toLowerCase() === "user-agent");
4498
+ return overridden ? { ...configured } : { "User-Agent": kitcodeUserAgent(sdkUserAgent2), ...configured };
4499
+ }
4472
4500
 
4473
4501
  // src/mcp/client.ts
4474
4502
  var clientInfo = { name: "kitcode", version: KITCODE_VERSION };
@@ -5047,13 +5075,14 @@ var KNOWN_MODELS = [
5047
5075
  knownModel("claude-fable-5", "Claude Fable 5", 1e6, 128e3)
5048
5076
  ];
5049
5077
  function createAnthropicProvider(args) {
5050
- const client = new Anthropic2({
5078
+ const options = {
5051
5079
  apiKey: args.apiKey,
5052
5080
  baseURL: args.baseUrl,
5053
- defaultHeaders: args.headers,
5054
5081
  maxRetries: MAX_RETRIES,
5055
5082
  timeout: args.timeoutMs ?? REQUEST_TIMEOUT_MS
5056
- });
5083
+ };
5084
+ const userAgent = sdkUserAgent(new Anthropic2(options));
5085
+ const client = new Anthropic2({ ...options, defaultHeaders: withKitcodeUserAgent(userAgent, args.headers) });
5057
5086
  return {
5058
5087
  id: args.id,
5059
5088
  kind: "anthropic",
@@ -5062,7 +5091,35 @@ function createAnthropicProvider(args) {
5062
5091
  knownModels: () => KNOWN_MODELS
5063
5092
  };
5064
5093
  }
5094
+ var thinkingRejected = /* @__PURE__ */ new Set();
5065
5095
  async function* streamTurn(client, providerId, apiKey2, req) {
5096
+ const key = `${providerId}/${req.model}`;
5097
+ let thinking = req.thinking === true && !thinkingRejected.has(key);
5098
+ for (; ; ) {
5099
+ let streamed = false;
5100
+ try {
5101
+ for await (const event of streamOnce(client, providerId, apiKey2, req, thinking)) {
5102
+ if (event.type !== "rate_limits" && event.type !== "usage") streamed = true;
5103
+ yield event;
5104
+ }
5105
+ return;
5106
+ } catch (error) {
5107
+ if (!streamed && thinking && refusesThinking(error)) {
5108
+ thinkingRejected.add(key);
5109
+ thinking = false;
5110
+ continue;
5111
+ }
5112
+ throw error;
5113
+ }
5114
+ }
5115
+ }
5116
+ function refusesThinking(error) {
5117
+ if (!(error instanceof ProviderError)) return false;
5118
+ if (error.status !== void 0 && error.status !== 400 && error.status !== 422) return false;
5119
+ const cause = error.cause instanceof Error ? error.cause.message : "";
5120
+ return /thinking|budget_tokens|adaptive/i.test(`${error.message} ${cause}`);
5121
+ }
5122
+ async function* streamOnce(client, providerId, apiKey2, req, thinkingOn) {
5066
5123
  const params = {
5067
5124
  model: req.model,
5068
5125
  max_tokens: req.maxTokens,
@@ -5070,7 +5127,7 @@ async function* streamTurn(client, providerId, apiKey2, req) {
5070
5127
  messages: toMessageParams(req.messages),
5071
5128
  tools: toToolParams(req.tools)
5072
5129
  };
5073
- if (req.thinking) {
5130
+ if (thinkingOn) {
5074
5131
  const thinking2 = thinkingParam(req.model, req.maxTokens);
5075
5132
  if (thinking2) params.thinking = thinking2;
5076
5133
  }
@@ -5232,7 +5289,7 @@ var THINKING_BUDGET = 16e3;
5232
5289
  var MIN_THINKING_BUDGET = 1024;
5233
5290
  function thinkingParam(model, maxTokens) {
5234
5291
  const id = (model.split("/").at(-1) ?? model).replace(/^(?:[a-z]{2}\.)?anthropic\./, "");
5235
- if (!BUDGET_THINKING.test(id)) return { type: "adaptive", display: "summarized" };
5292
+ if (id.startsWith("claude-") && !BUDGET_THINKING.test(id)) return { type: "adaptive", display: "summarized" };
5236
5293
  const budget = Math.min(THINKING_BUDGET, maxTokens - 1);
5237
5294
  return budget >= MIN_THINKING_BUDGET ? { type: "enabled", budget_tokens: budget } : void 0;
5238
5295
  }
@@ -5252,13 +5309,14 @@ var FINISH_REASONS = {
5252
5309
  content_filter: "refusal"
5253
5310
  };
5254
5311
  function createOpenAiProvider(args) {
5255
- const client = new OpenAI2({
5312
+ const options = {
5256
5313
  baseURL: args.baseUrl,
5257
5314
  apiKey: args.apiKey,
5258
- defaultHeaders: args.headers,
5259
5315
  maxRetries: MAX_RETRIES,
5260
5316
  timeout: args.timeoutMs ?? REQUEST_TIMEOUT_MS
5261
- });
5317
+ };
5318
+ const userAgent = sdkUserAgent(new OpenAI2(options));
5319
+ const client = new OpenAI2({ ...options, defaultHeaders: withKitcodeUserAgent(userAgent, args.headers) });
5262
5320
  return {
5263
5321
  id: args.id,
5264
5322
  kind: "openai",
@@ -5275,7 +5333,7 @@ async function* streamTurn2(client, providerId, apiKey2, req) {
5275
5333
  for (; ; ) {
5276
5334
  let streamed = false;
5277
5335
  try {
5278
- for await (const event of streamOnce(client, providerId, apiKey2, req, effort, thinkingOff, tokenField)) {
5336
+ for await (const event of streamOnce2(client, providerId, apiKey2, req, effort, thinkingOff, tokenField)) {
5279
5337
  if (event.type !== "rate_limits" && event.type !== "usage") streamed = true;
5280
5338
  yield event;
5281
5339
  }
@@ -5288,8 +5346,14 @@ async function* streamTurn2(client, providerId, apiKey2, req) {
5288
5346
  continue;
5289
5347
  }
5290
5348
  if (effort && rejectsField(error, /reasoning|effort/i)) {
5291
- markEffortRejected(providerId, req.model);
5292
- effort = void 0;
5349
+ const lower = effort === "xhigh" || effort === "max" ? "high" : effort === "high" ? "medium" : void 0;
5350
+ if (lower) {
5351
+ markEffortCapped(providerId, req.model, lower);
5352
+ effort = lower;
5353
+ } else {
5354
+ markEffortRejected(providerId, req.model);
5355
+ effort = void 0;
5356
+ }
5293
5357
  continue;
5294
5358
  }
5295
5359
  if (thinkingOff && rejectsField(error, /thinking/i)) {
@@ -5314,11 +5378,12 @@ function thinkingOffParams(model) {
5314
5378
  return void 0;
5315
5379
  }
5316
5380
  function rejectsField(error, field2) {
5317
- if (!(error instanceof ProviderError) || error.status !== 400 && error.status !== 422) return false;
5381
+ if (!(error instanceof ProviderError)) return false;
5382
+ if (error.status !== void 0 && error.status !== 400 && error.status !== 422) return false;
5318
5383
  const cause = error.cause instanceof Error ? error.cause.message : "";
5319
5384
  return field2.test(`${error.message} ${cause}`);
5320
5385
  }
5321
- async function* streamOnce(client, providerId, apiKey2, req, effort, thinkingOff, tokenField) {
5386
+ async function* streamOnce2(client, providerId, apiKey2, req, effort, thinkingOff, tokenField) {
5322
5387
  let text = "";
5323
5388
  let thinking = "";
5324
5389
  let finishReason = null;
@@ -8041,7 +8106,7 @@ ${recovered.removed === 1 ? "The image was" : `${recovered.removed} images were`
8041
8106
  }
8042
8107
  function imageRejection(error, history) {
8043
8108
  const status = error?.status;
8044
- if (typeof status !== "number" || ![400, 413, 415, 422].includes(status)) return null;
8109
+ if (status !== void 0 && (typeof status !== "number" || ![400, 413, 415, 422].includes(status))) return null;
8045
8110
  const message = error instanceof Error ? error.message : String(error);
8046
8111
  if (status !== 413 && !/image|vision|multi-?modal/i.test(message)) return null;
8047
8112
  const result = withoutImages(history);
@@ -8387,7 +8452,7 @@ function splitCommandLine(raw) {
8387
8452
  }
8388
8453
 
8389
8454
  // src/ui/speed.ts
8390
- var CHARS_PER_TOKEN = 4;
8455
+ var CHARS_PER_TOKEN3 = 4;
8391
8456
  var MIN_LIVE_MS = 1e3;
8392
8457
  function createSpeedMeter() {
8393
8458
  return { streamedChars: 0, firstTokenAt: null, last: null };
@@ -8414,7 +8479,7 @@ function readSpeed(meter, busy, now = Date.now()) {
8414
8479
  if (busy && meter.firstTokenAt !== null && meter.streamedChars > 0) {
8415
8480
  const ms = now - meter.firstTokenAt;
8416
8481
  if (ms >= MIN_LIVE_MS) {
8417
- return { tokensPerSecond: meter.streamedChars / CHARS_PER_TOKEN / (ms / 1e3), live: true };
8482
+ return { tokensPerSecond: meter.streamedChars / CHARS_PER_TOKEN3 / (ms / 1e3), live: true };
8418
8483
  }
8419
8484
  }
8420
8485
  return meter.last === null || !Number.isFinite(meter.last) ? null : { tokensPerSecond: meter.last, live: false };