agent-sh 0.15.3 → 0.15.4

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.
@@ -1,7 +1,9 @@
1
1
  /**
2
2
  * OpenAI Chat Completions-compatible local/3rd-party server (Ollama, LM
3
- * Studio, vLLM, llama.cpp, …). No reasoning hook the right shape depends
4
- * on which model the server is serving; user extensions can add one.
3
+ * Studio, vLLM, llama.cpp, …). Emits the common `reasoning_effort` shape,
4
+ * with `"none"` to disable — the value most local servers honor. A server
5
+ * wanting a different disable token can override via `reasoningShape` or a
6
+ * user extension.
5
7
  */
6
8
  import type { AgentContext } from "../host-types.js";
7
9
  export default function activate(ctx: AgentContext): void;
@@ -5,6 +5,11 @@ export default function activate(ctx) {
5
5
  // Local servers often need no key; SDK still wants a non-empty string.
6
6
  const apiKey = process.env.OPENAI_API_KEY || "no-key";
7
7
  const id = "openai-compatible";
8
+ ctx.agent.providers.configure(id, {
9
+ reasoningParams: (level) => level === "off"
10
+ ? { reasoning_effort: "none" }
11
+ : { reasoning_effort: level === "xhigh" ? "high" : level },
12
+ });
8
13
  ctx.agent.providers.register({ id, apiKey, baseURL, models: [] });
9
14
  fetchModels(baseURL, apiKey).then((models) => {
10
15
  if (models.length === 0)
@@ -157,7 +157,7 @@ function highlightInlineChanges(oldLine, newLine, oldPalette, newPalette, useTru
157
157
  }
158
158
  else {
159
159
  // Changed tokens: emphasis background, no syntax highlighting (emphasis stands out)
160
- result += palette.emphBg + p.bold + tokens[i].text + p.reset;
160
+ result += palette.emphBg + tokens[i].text + p.reset;
161
161
  }
162
162
  }
163
163
  return result;
@@ -246,55 +246,56 @@ function renderUnifiedHunk(hunk, layout) {
246
246
  const { noW, lineTextW, textWidth, useTrueColor, gutterLine, lang, removedPalette, addedPalette } = layout;
247
247
  const out = [];
248
248
  const pairs = findChangePairs(hunk);
249
- const renderedAsPartOfPair = new Set();
250
249
  const bgWidth = Math.max(1, textWidth - noW - 3);
251
250
  const gutter = (n) => `${p.dim}${n} │${p.reset} `;
252
251
  const change = (no, sigil, bg, fg, text) => {
253
252
  if (!gutterLine) {
254
- return `${bg}${fg}${padToWidth(`${no} ${sigil} ${preserveBg(text, bg)}`, textWidth)}${p.reset}`;
253
+ return `${bg}${padToWidth(`${fg}${no} ${sigil}${p.diffText} ${preserveBg(text, bg)}`, textWidth)}${p.reset}`;
255
254
  }
256
255
  if (useTrueColor)
257
- return gutter(no) + padToWidth(`${bg}${fg}${sigil} ${preserveBg(text, bg)}`, bgWidth) + p.reset;
256
+ return gutter(no) + padToWidth(`${bg}${fg}${sigil}${p.diffText} ${preserveBg(text, bg)}`, bgWidth) + p.reset;
258
257
  return `${gutter(no)}${fg}${sigil} ${text}${p.reset}`;
259
258
  };
259
+ const hlCache = new Map();
260
+ const highlightedPair = (pair) => {
261
+ let h = hlCache.get(pair);
262
+ if (!h) {
263
+ h = highlightInlineChanges(pair.removed.text, pair.added.text, removedPalette, addedPalette, useTrueColor, lang);
264
+ hlCache.set(pair, h);
265
+ }
266
+ return h;
267
+ };
260
268
  for (let i = 0; i < hunk.lines.length; i++) {
261
269
  const line = hunk.lines[i];
262
270
  const no = String(line.type === "removed" ? (line.oldNo ?? "") : (line.newNo ?? line.oldNo ?? "")).padStart(noW);
263
271
  if (line.type === "context") {
264
272
  const raw = truncateText(line.text, lineTextW);
265
273
  const text = lang ? highlightLine(raw, lang) : raw;
266
- // The flush gutter dims only the line number; the code stays normal/highlighted.
267
274
  out.push(!gutterLine ? `${p.dim}${no}${p.reset} ${text}` : `${gutter(no)} ${p.dim}${text}${p.reset}`);
268
275
  continue;
269
276
  }
277
+ const pair = pairs.get(i);
270
278
  if (line.type === "removed") {
271
- const pair = pairs.get(i);
272
279
  let removedText;
273
- let addedText = null;
274
- let addedNo = null;
275
- if (pair && pair.removedIdx === i) {
276
- const highlighted = highlightInlineChanges(line.text, pair.added.text, removedPalette, addedPalette, useTrueColor, lang);
277
- removedText = truncateText(highlighted.old, lineTextW);
278
- addedText = truncateText(highlighted.new, lineTextW);
279
- addedNo = String(pair.added.newNo ?? "").padStart(noW);
280
- renderedAsPartOfPair.add(pair.addedIdx);
280
+ if (pair) {
281
+ removedText = truncateText(highlightedPair(pair).old, lineTextW);
281
282
  }
282
283
  else {
283
284
  const raw = truncateText(line.text, lineTextW);
284
285
  removedText = lang ? highlightLine(raw, lang) : raw;
285
286
  }
286
287
  out.push(change(no, "-", p.errorBg, p.error, removedText));
287
- if (addedText !== null && addedNo !== null) {
288
- out.push(change(addedNo, "+", p.successBg, p.success, addedText));
289
- }
290
- continue;
291
288
  }
292
- if (line.type === "added") {
293
- if (renderedAsPartOfPair.has(i))
294
- continue;
295
- const raw = truncateText(line.text, lineTextW);
296
- const text = lang ? highlightLine(raw, lang) : raw;
297
- out.push(change(no, "+", p.successBg, p.success, text));
289
+ else {
290
+ let addedText;
291
+ if (pair) {
292
+ addedText = truncateText(highlightedPair(pair).new, lineTextW);
293
+ }
294
+ else {
295
+ const raw = truncateText(line.text, lineTextW);
296
+ addedText = lang ? highlightLine(raw, lang) : raw;
297
+ }
298
+ out.push(change(no, "+", p.successBg, p.success, addedText));
298
299
  }
299
300
  }
300
301
  return out;
@@ -362,7 +363,7 @@ function renderSplitHunk(hunk, layout) {
362
363
  }
363
364
  else if (row.left.type === "removed") {
364
365
  if (useTrueColor) {
365
- leftCol = padToWidth(`${p.errorBg}${p.error}${leftNo} ${preserveBg(leftText, p.errorBg)}`, colWidth) + p.reset;
366
+ leftCol = padToWidth(`${p.errorBg}${p.error}${leftNo} │${p.diffText} ${preserveBg(leftText, p.errorBg)}`, colWidth) + p.reset;
366
367
  }
367
368
  else {
368
369
  leftCol = padToWidth(`${p.error}${leftNo} │ ${leftText}${p.reset}`, colWidth);
@@ -376,7 +377,7 @@ function renderSplitHunk(hunk, layout) {
376
377
  }
377
378
  else if (row.right.type === "added") {
378
379
  if (useTrueColor) {
379
- rightCol = padToWidth(`${p.successBg}${p.success}${rightNo} ${preserveBg(rightText, p.successBg)}`, colWidth) + p.reset;
380
+ rightCol = padToWidth(`${p.successBg}${p.success}${rightNo} │${p.diffText} ${preserveBg(rightText, p.successBg)}`, colWidth) + p.reset;
380
381
  }
381
382
  else {
382
383
  rightCol = padToWidth(`${p.success}${rightNo} │ ${rightText}${p.reset}`, colWidth);
@@ -18,6 +18,7 @@ export interface ColorPalette {
18
18
  errorBg: string;
19
19
  successBgEmph: string;
20
20
  errorBgEmph: string;
21
+ diffText: string;
21
22
  bold: string;
22
23
  dim: string;
23
24
  italic: string;
@@ -14,10 +14,11 @@ const defaultPalette = {
14
14
  warning: "\x1b[33m", // yellow
15
15
  error: "\x1b[31m", // red
16
16
  muted: "\x1b[90m", // gray
17
- successBg: "\x1b[48;2;34;92;43m",
18
- errorBg: "\x1b[48;2;122;41;54m",
19
- successBgEmph: "\x1b[48;2;56;166;96m",
20
- errorBgEmph: "\x1b[48;2;179;89;107m",
17
+ successBg: "\x1b[48;2;26;70;34m",
18
+ errorBg: "\x1b[48;2;92;32;42m",
19
+ successBgEmph: "\x1b[48;2;38;104;56m",
20
+ errorBgEmph: "\x1b[48;2;124;50;64m",
21
+ diffText: "\x1b[97m", // bright white — readable on the red/green tints
21
22
  bold: "\x1b[1m",
22
23
  dim: "\x1b[2m",
23
24
  italic: "\x1b[3m",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-sh",
3
- "version": "0.15.3",
3
+ "version": "0.15.4",
4
4
  "description": "A composable agent runtime — pair any frontend with any agent backend over one shared extension layer",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -1,7 +1,9 @@
1
1
  /**
2
2
  * OpenAI Chat Completions-compatible local/3rd-party server (Ollama, LM
3
- * Studio, vLLM, llama.cpp, …). No reasoning hook the right shape depends
4
- * on which model the server is serving; user extensions can add one.
3
+ * Studio, vLLM, llama.cpp, …). Emits the common `reasoning_effort` shape,
4
+ * with `"none"` to disable — the value most local servers honor. A server
5
+ * wanting a different disable token can override via `reasoningShape` or a
6
+ * user extension.
5
7
  */
6
8
  import type { AgentContext } from "../host-types.js";
7
9
 
@@ -13,6 +15,12 @@ export default function activate(ctx: AgentContext): void {
13
15
  const apiKey = process.env.OPENAI_API_KEY || "no-key";
14
16
  const id = "openai-compatible";
15
17
 
18
+ ctx.agent.providers.configure(id, {
19
+ reasoningParams: (level) =>
20
+ level === "off"
21
+ ? { reasoning_effort: "none" }
22
+ : { reasoning_effort: level === "xhigh" ? "high" : level },
23
+ });
16
24
  ctx.agent.providers.register({ id, apiKey, baseURL, models: [] });
17
25
  fetchModels(baseURL, apiKey).then((models) => {
18
26
  if (models.length === 0) return;
@@ -216,7 +216,7 @@ function highlightInlineChanges(
216
216
  result += palette.rowBg + preserveBg(text, palette.rowBg);
217
217
  } else {
218
218
  // Changed tokens: emphasis background, no syntax highlighting (emphasis stands out)
219
- result += palette.emphBg + p.bold + tokens[i].text + p.reset;
219
+ result += palette.emphBg + tokens[i].text + p.reset;
220
220
  }
221
221
  }
222
222
  return result;
@@ -337,18 +337,29 @@ function renderUnifiedHunk(hunk: DiffHunk, layout: UnifiedLayout): string[] {
337
337
  const out: string[] = [];
338
338
 
339
339
  const pairs = findChangePairs(hunk);
340
- const renderedAsPartOfPair = new Set<number>();
341
340
  const bgWidth = Math.max(1, textWidth - noW - 3);
342
341
  const gutter = (n: string): string => `${p.dim}${n} │${p.reset} `;
343
342
 
344
343
  const change = (no: string, sigil: string, bg: string, fg: string, text: string): string => {
345
344
  if (!gutterLine) {
346
- return `${bg}${fg}${padToWidth(`${no} ${sigil} ${preserveBg(text, bg)}`, textWidth)}${p.reset}`;
345
+ return `${bg}${padToWidth(`${fg}${no} ${sigil}${p.diffText} ${preserveBg(text, bg)}`, textWidth)}${p.reset}`;
347
346
  }
348
- if (useTrueColor) return gutter(no) + padToWidth(`${bg}${fg}${sigil} ${preserveBg(text, bg)}`, bgWidth) + p.reset;
347
+ if (useTrueColor) return gutter(no) + padToWidth(`${bg}${fg}${sigil}${p.diffText} ${preserveBg(text, bg)}`, bgWidth) + p.reset;
349
348
  return `${gutter(no)}${fg}${sigil} ${text}${p.reset}`;
350
349
  };
351
350
 
351
+ const hlCache = new Map<ChangePair, { old: string; new: string }>();
352
+ const highlightedPair = (pair: ChangePair): { old: string; new: string } => {
353
+ let h = hlCache.get(pair);
354
+ if (!h) {
355
+ h = highlightInlineChanges(
356
+ pair.removed.text, pair.added.text, removedPalette, addedPalette, useTrueColor, lang,
357
+ );
358
+ hlCache.set(pair, h);
359
+ }
360
+ return h;
361
+ };
362
+
352
363
  for (let i = 0; i < hunk.lines.length; i++) {
353
364
  const line = hunk.lines[i];
354
365
  const no = String(
@@ -358,42 +369,29 @@ function renderUnifiedHunk(hunk: DiffHunk, layout: UnifiedLayout): string[] {
358
369
  if (line.type === "context") {
359
370
  const raw = truncateText(line.text, lineTextW);
360
371
  const text = lang ? highlightLine(raw, lang) : raw;
361
- // The flush gutter dims only the line number; the code stays normal/highlighted.
362
372
  out.push(!gutterLine ? `${p.dim}${no}${p.reset} ${text}` : `${gutter(no)} ${p.dim}${text}${p.reset}`);
363
373
  continue;
364
374
  }
365
375
 
376
+ const pair = pairs.get(i);
366
377
  if (line.type === "removed") {
367
- const pair = pairs.get(i);
368
378
  let removedText: string;
369
- let addedText: string | null = null;
370
- let addedNo: string | null = null;
371
-
372
- if (pair && pair.removedIdx === i) {
373
- const highlighted = highlightInlineChanges(
374
- line.text, pair.added.text, removedPalette, addedPalette, useTrueColor, lang,
375
- );
376
- removedText = truncateText(highlighted.old, lineTextW);
377
- addedText = truncateText(highlighted.new, lineTextW);
378
- addedNo = String(pair.added.newNo ?? "").padStart(noW);
379
- renderedAsPartOfPair.add(pair.addedIdx);
379
+ if (pair) {
380
+ removedText = truncateText(highlightedPair(pair).old, lineTextW);
380
381
  } else {
381
382
  const raw = truncateText(line.text, lineTextW);
382
383
  removedText = lang ? highlightLine(raw, lang) : raw;
383
384
  }
384
-
385
385
  out.push(change(no, "-", p.errorBg, p.error, removedText));
386
- if (addedText !== null && addedNo !== null) {
387
- out.push(change(addedNo, "+", p.successBg, p.success, addedText));
386
+ } else {
387
+ let addedText: string;
388
+ if (pair) {
389
+ addedText = truncateText(highlightedPair(pair).new, lineTextW);
390
+ } else {
391
+ const raw = truncateText(line.text, lineTextW);
392
+ addedText = lang ? highlightLine(raw, lang) : raw;
388
393
  }
389
- continue;
390
- }
391
-
392
- if (line.type === "added") {
393
- if (renderedAsPartOfPair.has(i)) continue;
394
- const raw = truncateText(line.text, lineTextW);
395
- const text = lang ? highlightLine(raw, lang) : raw;
396
- out.push(change(no, "+", p.successBg, p.success, text));
394
+ out.push(change(no, "+", p.successBg, p.success, addedText));
397
395
  }
398
396
  }
399
397
  return out;
@@ -478,7 +476,7 @@ function renderSplitHunk(hunk: DiffHunk, layout: SplitLayout): string[] {
478
476
  } else if (row.left.type === "removed") {
479
477
  if (useTrueColor) {
480
478
  leftCol = padToWidth(
481
- `${p.errorBg}${p.error}${leftNo} ${preserveBg(leftText, p.errorBg)}`, colWidth,
479
+ `${p.errorBg}${p.error}${leftNo} │${p.diffText} ${preserveBg(leftText, p.errorBg)}`, colWidth,
482
480
  ) + p.reset;
483
481
  } else {
484
482
  leftCol = padToWidth(`${p.error}${leftNo} │ ${leftText}${p.reset}`, colWidth);
@@ -492,7 +490,7 @@ function renderSplitHunk(hunk: DiffHunk, layout: SplitLayout): string[] {
492
490
  } else if (row.right.type === "added") {
493
491
  if (useTrueColor) {
494
492
  rightCol = padToWidth(
495
- `${p.successBg}${p.success}${rightNo} ${preserveBg(rightText, p.successBg)}`, colWidth,
493
+ `${p.successBg}${p.success}${rightNo} │${p.diffText} ${preserveBg(rightText, p.successBg)}`, colWidth,
496
494
  ) + p.reset;
497
495
  } else {
498
496
  rightCol = padToWidth(`${p.success}${rightNo} │ ${rightText}${p.reset}`, colWidth);
@@ -22,6 +22,7 @@ export interface ColorPalette {
22
22
  errorBg: string; // subtle red tint for removed lines
23
23
  successBgEmph: string; // stronger green for changed tokens
24
24
  errorBgEmph: string; // stronger red for changed tokens
25
+ diffText: string; // legible fg for diff row text on the tinted rows
25
26
 
26
27
  // ── Style modifiers ───────────────────────────────────────
27
28
  bold: string;
@@ -38,10 +39,11 @@ const defaultPalette: ColorPalette = {
38
39
  error: "\x1b[31m", // red
39
40
  muted: "\x1b[90m", // gray
40
41
 
41
- successBg: "\x1b[48;2;34;92;43m",
42
- errorBg: "\x1b[48;2;122;41;54m",
43
- successBgEmph: "\x1b[48;2;56;166;96m",
44
- errorBgEmph: "\x1b[48;2;179;89;107m",
42
+ successBg: "\x1b[48;2;26;70;34m",
43
+ errorBg: "\x1b[48;2;92;32;42m",
44
+ successBgEmph: "\x1b[48;2;38;104;56m",
45
+ errorBgEmph: "\x1b[48;2;124;50;64m",
46
+ diffText: "\x1b[97m", // bright white — readable on the red/green tints
45
47
 
46
48
  bold: "\x1b[1m",
47
49
  dim: "\x1b[2m",