agent-sh 0.13.2 → 0.13.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.
@@ -1201,7 +1201,7 @@ export class AgentLoop {
1201
1201
  signal });
1202
1202
  // Truncate large outputs to avoid blowing context.
1203
1203
  let content = result.content;
1204
- const maxBytes = tool.maxResultBytes ?? 16_384; // ~4k tokens
1204
+ const maxBytes = tool.maxResultBytes ?? 100_000; // ~25k tokens
1205
1205
  if (content.length > maxBytes) {
1206
1206
  const headBytes = Math.floor(maxBytes * 0.6);
1207
1207
  const tailBytes = maxBytes - headBytes;
@@ -14,6 +14,12 @@ export interface DiffRenderOptions {
14
14
  /** Enable syntax highlighting on diff lines. Default true. */
15
15
  syntaxHighlight?: boolean;
16
16
  }
17
+ export declare function detectLanguage(filePath?: string): string | undefined;
18
+ /**
19
+ * Syntax-highlight a single line of code.
20
+ * Returns the original text if highlighting fails or no language detected.
21
+ */
22
+ export declare function highlightLine(text: string, language?: string): string;
17
23
  /** Select display mode based on available terminal width. */
18
24
  export declare function selectMode(width: number): DiffDisplayMode;
19
25
  /** Render a diff result as an array of ANSI-formatted terminal lines. */
@@ -25,7 +25,7 @@ const EXT_TO_LANG = {
25
25
  ".hs": "haskell", ".ml": "ocaml", ".clj": "clojure",
26
26
  ".vim": "vim", ".dockerfile": "dockerfile",
27
27
  };
28
- function detectLanguage(filePath) {
28
+ export function detectLanguage(filePath) {
29
29
  if (!filePath)
30
30
  return undefined;
31
31
  const dot = filePath.lastIndexOf(".");
@@ -44,7 +44,7 @@ function detectLanguage(filePath) {
44
44
  * Syntax-highlight a single line of code.
45
45
  * Returns the original text if highlighting fails or no language detected.
46
46
  */
47
- function highlightLine(text, language) {
47
+ export function highlightLine(text, language) {
48
48
  if (!language || text.trim() === "")
49
49
  return text;
50
50
  try {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guanyilun/ashi",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Ash in an interactive TUI — agent-sh's built-in agent without the shell underneath",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",
@@ -145,7 +145,7 @@ async function main(): Promise<void> {
145
145
  registerDefaultToolRenderers(ctx);
146
146
 
147
147
  ctx.advise("conversation:format-prior-history", () => null);
148
- ctx.advise("system-prompt:build", (base) => `${base}\n\n<cwd>${process.cwd()}</cwd>`);
148
+ ctx.advise("system-prompt:build", (next) => `${next()}\n\n<cwd>${process.cwd()}</cwd>`);
149
149
 
150
150
  const handle = mountAshi(ctx, getStore, capture);
151
151
  stopFrontend = handle.stop;
@@ -17,8 +17,8 @@ const TOOL_ICON: Record<string, string> = {
17
17
  };
18
18
 
19
19
  function iconPrefix(name: string): string {
20
- const icon = TOOL_ICON[name];
21
- return icon ? `${theme.fg("warning", icon)} ` : "";
20
+ const icon = TOOL_ICON[name] ?? "⚙";
21
+ return `${theme.fg("warning", icon)} `;
22
22
  }
23
23
 
24
24
  interface StatusOpts { exitCode: number | null; elapsedMs: number; summary?: string }
@@ -145,7 +145,7 @@ function pathOnlyLabel(name: string, args: ToolCallArgs): string {
145
145
 
146
146
  function genericLabel(args: ToolCallArgs): string {
147
147
  const detail = args.displayDetail ? ` ${muted(args.displayDetail)}` : "";
148
- return `${bold(args.title)}${detail}`;
148
+ return `${iconPrefix(args.name)}${bold(args.title)}${detail}`;
149
149
  }
150
150
 
151
151
  export function registerDefaultToolRenderers(ctx: ExtensionContext): void {
@@ -40,7 +40,7 @@ import { resumeSession } from "./session-commands.js";
40
40
  import { applyBranchMessages } from "./commands.js";
41
41
  import type { Capture } from "./capture.js";
42
42
  import { execSync } from "node:child_process";
43
- import { renderDiff } from "agent-sh/utils/diff-renderer.js";
43
+ import { renderDiff, detectLanguage, highlightLine } from "agent-sh/utils/diff-renderer.js";
44
44
  import { renderBoxFrame } from "agent-sh/utils/box-frame.js";
45
45
 
46
46
  interface DiffStats { added: number; removed: number; isNewFile: boolean; isIdentical: boolean }
@@ -53,10 +53,10 @@ function buildDiffRenderer(
53
53
  const boxW = Math.max(40, width);
54
54
  const contentW = Math.max(20, boxW - 4);
55
55
  const inner = diff.isNewFile
56
- ? renderNewFilePreview(diff, 30)
56
+ ? renderNewFilePreview(diff, 30, filePath)
57
57
  : ((): string[] => {
58
58
  const lines = renderDiff(diff, {
59
- width: contentW, filePath, trueColor: true, maxLines: 30, mode: "unified",
59
+ width: contentW, filePath, trueColor: true, maxLines: Number.MAX_SAFE_INTEGER, mode: "unified",
60
60
  });
61
61
  return lines.length > 1 ? ["", ...lines.slice(1), ""] : lines;
62
62
  })();
@@ -71,14 +71,16 @@ function buildDiffRenderer(
71
71
  function renderNewFilePreview(
72
72
  diff: { hunks?: { lines: { type: string; text: string }[] }[] },
73
73
  maxLines: number,
74
+ filePath: string,
74
75
  ): string[] {
75
76
  const lines = diff.hunks?.[0]?.lines.filter((l) => l.type === "added") ?? [];
76
77
  const shown = lines.slice(0, maxLines);
77
78
  const overflow = lines.length - shown.length;
78
79
  const noW = String(shown.length).length || 1;
80
+ const lang = detectLanguage(filePath);
79
81
  const body = shown.map((l, i) => {
80
82
  const no = String(i + 1).padStart(noW);
81
- return `${theme.fg("muted", `${no} │`)} ${l.text}`;
83
+ return `${theme.fg("muted", `${no} │`)} ${highlightLine(l.text, lang)}`;
82
84
  });
83
85
  if (overflow > 0) body.push(theme.fg("muted", `… ${overflow} more lines`));
84
86
  return ["", ...body, ""];
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Ollama Cloud — hosted Ollama models (https://ollama.com).
3
+ *
4
+ * Auth: agent-sh auth login ollama-cloud
5
+ * Usage: agent-sh -e ./examples/extensions/ollama-cloud.ts
6
+ */
7
+ import { resolveApiKey } from "agent-sh/auth";
8
+ import type { AgentContext } from "agent-sh/types";
9
+
10
+ const HOST = "https://ollama.com";
11
+ const BASE_URL = `${HOST}/v1`;
12
+ const ID = "ollama-cloud";
13
+
14
+ function buildReasoningParams(level: string, _model?: string): Record<string, unknown> {
15
+ return { reasoning_effort: level === "off" ? "none" : level };
16
+ }
17
+
18
+ async function fetchModels(apiKey: string) {
19
+ const headers: Record<string, string> = { Authorization: `Bearer ${apiKey}` };
20
+ const tagsRes = await fetch(`${HOST}/api/tags`, { headers });
21
+ if (!tagsRes.ok) return [];
22
+ const tagsData = await tagsRes.json() as { models?: { name: string }[] };
23
+ const names = (tagsData.models ?? []).map((m) => m.name);
24
+ if (!names.length) return [];
25
+
26
+ const ctxs = await Promise.all(
27
+ names.map((name) =>
28
+ fetch(`${HOST}/api/show`, {
29
+ method: "POST",
30
+ headers: { ...headers, "Content-Type": "application/json" },
31
+ body: JSON.stringify({ name }),
32
+ })
33
+ .then((r) => r.ok ? r.json() as Promise<{ model_info?: Record<string, unknown> }> : null)
34
+ .then((d) => {
35
+ if (!d?.model_info) return undefined;
36
+ const info = d.model_info;
37
+ const arch = info["general.architecture"] as string | undefined;
38
+ if (arch) {
39
+ const ctx = info[`${arch}.context_length`];
40
+ if (typeof ctx === "number") return ctx;
41
+ }
42
+ for (const [k, v] of Object.entries(info)) {
43
+ if (k.endsWith(".context_length") && typeof v === "number") return v;
44
+ }
45
+ return undefined;
46
+ })
47
+ .catch(() => undefined),
48
+ ),
49
+ );
50
+
51
+ return names.map((name, i) => ({
52
+ id: name,
53
+ contextWindow: ctxs[i],
54
+ echoReasoning: /deepseek/i.test(name),
55
+ }));
56
+ }
57
+
58
+ export default function activate(ctx: AgentContext): void {
59
+ const { key } = resolveApiKey(ID);
60
+ const apiKey = key ?? process.env.OLLAMA_API_KEY;
61
+ if (!apiKey) return;
62
+
63
+ ctx.agent.providers.configure(ID, { reasoningParams: buildReasoningParams });
64
+
65
+ // Register placeholder while catalog loads
66
+ ctx.bus.emit("provider:register", { id: ID, apiKey, baseURL: BASE_URL, models: [] });
67
+
68
+ fetchModels(apiKey).then((models) => {
69
+ if (!models.length) return;
70
+ ctx.bus.emit("provider:register", {
71
+ id: ID,
72
+ apiKey,
73
+ baseURL: BASE_URL,
74
+ defaultModel: models[0]!.id,
75
+ models,
76
+ });
77
+ }).catch(() => { /* keep placeholder */ });
78
+ }
@@ -37,6 +37,10 @@ export default function activate(ctx: ExtensionContext): void {
37
37
  const headers: Record<string, string> = {};
38
38
  if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
39
39
 
40
+ ctx.agent.providers.configure(id, {
41
+ reasoningParams: (level) => ({ reasoning_effort: level === "off" ? "none" : level }),
42
+ });
43
+
40
44
  ctx.bus.emit("provider:register", { id, apiKey: sdkKey, baseURL, models: [] });
41
45
 
42
46
  fetchCatalog(host, headers).then((models) => {
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Z.AI Coding Plan — Zhipu AI's subscription GLM models for coding tools.
3
+ *
4
+ * Auth: agent-sh auth login zai-coding-plan
5
+ * Usage: agent-sh -e ./examples/extensions/zai-coding-plan.ts
6
+ */
7
+ import { resolveApiKey } from "agent-sh/auth";
8
+ import type { AgentContext } from "agent-sh/types";
9
+
10
+ const BASE_URL = "https://api.z.ai/api/coding/paas/v4";
11
+ const ID = "zai-coding-plan";
12
+
13
+ const DEFAULT_MODELS = [
14
+ { id: "glm-5.1", reasoning: true, contextWindow: 200_000 },
15
+ { id: "glm-5-turbo", reasoning: true, contextWindow: 200_000 },
16
+ { id: "glm-4.7", reasoning: true, contextWindow: 204_800 },
17
+ { id: "glm-4.5-air", reasoning: true, contextWindow: 131_072 },
18
+ ];
19
+
20
+ function buildReasoningParams(level: string, _model?: string): Record<string, unknown> {
21
+ if (level === "off") return { thinking: { type: "disabled" } };
22
+ return { thinking: { type: "enabled" }, reasoning_effort: level };
23
+ }
24
+
25
+ export default function activate(ctx: AgentContext): void {
26
+ const { key } = resolveApiKey(ID);
27
+ const apiKey = key ?? process.env.ZAI_API_KEY ?? process.env.ZHIPU_API_KEY;
28
+ if (!apiKey) return;
29
+
30
+ ctx.agent.providers.configure(ID, { reasoningParams: buildReasoningParams });
31
+
32
+ ctx.bus.emit("provider:register", {
33
+ id: ID,
34
+ apiKey: apiKey,
35
+ baseURL: BASE_URL,
36
+ defaultModel: DEFAULT_MODELS[0].id,
37
+ models: DEFAULT_MODELS,
38
+ });
39
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-sh",
3
- "version": "0.13.2",
3
+ "version": "0.13.3",
4
4
  "description": "A shell-first terminal where AI is one keystroke away",
5
5
  "type": "module",
6
6
  "main": "dist/core/index.js",
@@ -101,6 +101,10 @@
101
101
  "./cli/auth": {
102
102
  "types": "./dist/cli/auth/cli.d.ts",
103
103
  "default": "./dist/cli/auth/cli.js"
104
+ },
105
+ "./auth": {
106
+ "types": "./dist/cli/auth/keys.d.ts",
107
+ "default": "./dist/cli/auth/keys.js"
104
108
  }
105
109
  },
106
110
  "files": [