agent-sh 0.13.2 → 0.13.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.
- package/dist/agent/agent-loop.js +93 -77
- package/dist/agent/index.js +5 -2
- package/dist/agent/providers/openai.d.ts +2 -0
- package/dist/agent/providers/openai.js +9 -2
- package/dist/utils/diff-renderer.d.ts +6 -0
- package/dist/utils/diff-renderer.js +2 -2
- package/examples/extensions/ash-acp-bridge/src/index.ts +5 -2
- package/examples/extensions/ashi/package.json +2 -2
- package/examples/extensions/ashi/src/cli.ts +1 -1
- package/examples/extensions/ashi/src/default-renderers.ts +3 -3
- package/examples/extensions/ashi/src/frontend.ts +6 -4
- package/examples/extensions/ollama.ts +22 -16
- package/examples/extensions/zai-coding-plan.ts +40 -0
- package/package.json +5 -1
package/dist/agent/agent-loop.js
CHANGED
|
@@ -87,7 +87,7 @@ export class AgentLoop {
|
|
|
87
87
|
// doing X." Addresses Q3 in QUESTIONS.md.
|
|
88
88
|
lastErrorByTool = new Map(); // tool → error summary
|
|
89
89
|
lastErrorByFile = new Map(); // file path → error summary
|
|
90
|
-
static THINKING_LEVELS = ["off", "low", "medium", "high"];
|
|
90
|
+
static THINKING_LEVELS = ["off", "low", "medium", "high", "xhigh"];
|
|
91
91
|
bus;
|
|
92
92
|
llmClient;
|
|
93
93
|
handlers;
|
|
@@ -168,8 +168,13 @@ export class AgentLoop {
|
|
|
168
168
|
];
|
|
169
169
|
if (prev) {
|
|
170
170
|
const newIdx = this.modes.findIndex((m) => m.model === prev.model && m.provider === prev.provider);
|
|
171
|
-
if (newIdx !== -1)
|
|
171
|
+
if (newIdx !== -1) {
|
|
172
172
|
this.currentModeIndex = newIdx;
|
|
173
|
+
const next = this.modes[newIdx];
|
|
174
|
+
if (next.providerConfig && next.providerConfig !== prev.providerConfig) {
|
|
175
|
+
this.llmClient.reconfigure({ ...next.providerConfig, model: next.model });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
173
178
|
}
|
|
174
179
|
if (activePreserved && prev) {
|
|
175
180
|
this.bus.emit("ui:info", {
|
|
@@ -507,7 +512,8 @@ export class AgentLoop {
|
|
|
507
512
|
return mode.buildReasoningParams(this.thinkingLevel);
|
|
508
513
|
if (this.thinkingLevel === "off")
|
|
509
514
|
return {};
|
|
510
|
-
|
|
515
|
+
const effort = this.thinkingLevel === "xhigh" ? "high" : this.thinkingLevel;
|
|
516
|
+
return { reasoning_effort: effort };
|
|
511
517
|
}
|
|
512
518
|
get currentMode() {
|
|
513
519
|
return this.modes[this.currentModeIndex];
|
|
@@ -1076,12 +1082,15 @@ export class AgentLoop {
|
|
|
1076
1082
|
streamedCalls: streamedToolCalls,
|
|
1077
1083
|
});
|
|
1078
1084
|
fullResponseText += text;
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
+
if (text || toolCalls.length > 0) {
|
|
1086
|
+
this.toolProtocol.recordAssistant(this.conversation, text, toolCalls, extras);
|
|
1087
|
+
this.bus.emit("conversation:message-appended", {
|
|
1088
|
+
role: "assistant",
|
|
1089
|
+
content: text,
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
if (signal.aborted)
|
|
1093
|
+
break;
|
|
1085
1094
|
// No tool calls → agent is done
|
|
1086
1095
|
if (toolCalls.length === 0) {
|
|
1087
1096
|
this.conversation.eagerNucleateAgent(fullResponseText);
|
|
@@ -1201,7 +1210,7 @@ export class AgentLoop {
|
|
|
1201
1210
|
signal });
|
|
1202
1211
|
// Truncate large outputs to avoid blowing context.
|
|
1203
1212
|
let content = result.content;
|
|
1204
|
-
const maxBytes = tool.maxResultBytes ??
|
|
1213
|
+
const maxBytes = tool.maxResultBytes ?? 100_000; // ~25k tokens
|
|
1205
1214
|
if (content.length > maxBytes) {
|
|
1206
1215
|
const headBytes = Math.floor(maxBytes * 0.6);
|
|
1207
1216
|
const tailBytes = maxBytes - headBytes;
|
|
@@ -1502,83 +1511,90 @@ export class AgentLoop {
|
|
|
1502
1511
|
};
|
|
1503
1512
|
this.bus.emit("llm:request", requestParams);
|
|
1504
1513
|
const stream = await this.llmClient.stream({ ...requestParams, signal });
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
// Feed accurate token count back to conversation state
|
|
1519
|
-
if (promptTokens > 0) {
|
|
1520
|
-
this.conversation.updateApiTokenCount(promptTokens);
|
|
1521
|
-
}
|
|
1522
|
-
}
|
|
1523
|
-
const choice = chunk.choices[0];
|
|
1524
|
-
if (!choice)
|
|
1525
|
-
continue;
|
|
1526
|
-
const delta = choice.delta;
|
|
1527
|
-
// Text content
|
|
1528
|
-
if (delta?.content) {
|
|
1529
|
-
text += delta.content;
|
|
1530
|
-
// Filter tool tags from display output (inline mode)
|
|
1531
|
-
const displayText = streamFilter
|
|
1532
|
-
? streamFilter.feed(delta.content)
|
|
1533
|
-
: delta.content;
|
|
1534
|
-
if (displayText) {
|
|
1535
|
-
this.bus.emitTransform("agent:response-chunk", {
|
|
1536
|
-
blocks: [{ type: "text", text: displayText }],
|
|
1514
|
+
try {
|
|
1515
|
+
for await (const chunk of stream) {
|
|
1516
|
+
if (signal.aborted)
|
|
1517
|
+
break;
|
|
1518
|
+
this.bus.emit("llm:chunk", { chunk });
|
|
1519
|
+
// Token usage (may arrive in a chunk with empty choices)
|
|
1520
|
+
if (chunk.usage) {
|
|
1521
|
+
const u = chunk.usage;
|
|
1522
|
+
const promptTokens = u.prompt_tokens ?? 0;
|
|
1523
|
+
this.bus.emit("agent:usage", {
|
|
1524
|
+
prompt_tokens: promptTokens,
|
|
1525
|
+
completion_tokens: u.completion_tokens ?? 0,
|
|
1526
|
+
total_tokens: u.total_tokens ?? 0,
|
|
1537
1527
|
});
|
|
1528
|
+
// Feed accurate token count back to conversation state
|
|
1529
|
+
if (promptTokens > 0) {
|
|
1530
|
+
this.conversation.updateApiTokenCount(promptTokens);
|
|
1531
|
+
}
|
|
1538
1532
|
}
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1533
|
+
const choice = chunk.choices[0];
|
|
1534
|
+
if (!choice)
|
|
1535
|
+
continue;
|
|
1536
|
+
const delta = choice.delta;
|
|
1537
|
+
// Text content
|
|
1538
|
+
if (delta?.content) {
|
|
1539
|
+
text += delta.content;
|
|
1540
|
+
// Filter tool tags from display output (inline mode)
|
|
1541
|
+
const displayText = streamFilter
|
|
1542
|
+
? streamFilter.feed(delta.content)
|
|
1543
|
+
: delta.content;
|
|
1544
|
+
if (displayText) {
|
|
1545
|
+
this.bus.emitTransform("agent:response-chunk", {
|
|
1546
|
+
blocks: [{ type: "text", text: displayText }],
|
|
1547
|
+
});
|
|
1554
1548
|
}
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1549
|
+
}
|
|
1550
|
+
const d = delta;
|
|
1551
|
+
for (const name of ["reasoning", "reasoning_content"]) {
|
|
1552
|
+
if (typeof d?.[name] === "string" && d[name].length > 0) {
|
|
1553
|
+
reasoning += d[name];
|
|
1554
|
+
reasoningField ??= name;
|
|
1555
|
+
this.bus.emit("agent:thinking-chunk", { text: d[name] });
|
|
1561
1556
|
}
|
|
1562
1557
|
}
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1558
|
+
if (Array.isArray(d?.reasoning_details)) {
|
|
1559
|
+
for (const x of d.reasoning_details) {
|
|
1560
|
+
const idx = typeof x?.index === "number" ? x.index : reasoningDetailsByIndex.size;
|
|
1561
|
+
const prev = reasoningDetailsByIndex.get(idx);
|
|
1562
|
+
if (!prev) {
|
|
1563
|
+
reasoningDetailsByIndex.set(idx, { ...x });
|
|
1564
|
+
}
|
|
1565
|
+
else {
|
|
1566
|
+
if (typeof x.text === "string")
|
|
1567
|
+
prev.text = (prev.text ?? "") + x.text;
|
|
1568
|
+
for (const [k, v] of Object.entries(x))
|
|
1569
|
+
if (k !== "text" && prev[k] === undefined)
|
|
1570
|
+
prev[k] = v;
|
|
1571
|
+
}
|
|
1574
1572
|
}
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1573
|
+
}
|
|
1574
|
+
// Tool calls (streamed incrementally)
|
|
1575
|
+
if (delta?.tool_calls) {
|
|
1576
|
+
for (const tc of delta.tool_calls) {
|
|
1577
|
+
const idx = tc.index;
|
|
1578
|
+
if (!pendingToolCalls[idx]) {
|
|
1579
|
+
pendingToolCalls[idx] = {
|
|
1580
|
+
id: tc.id,
|
|
1581
|
+
name: tc.function.name,
|
|
1582
|
+
argumentsJson: "",
|
|
1583
|
+
};
|
|
1584
|
+
}
|
|
1585
|
+
if (tc.function?.arguments) {
|
|
1586
|
+
pendingToolCalls[idx].argumentsJson +=
|
|
1587
|
+
tc.function.arguments;
|
|
1588
|
+
}
|
|
1578
1589
|
}
|
|
1579
1590
|
}
|
|
1580
1591
|
}
|
|
1581
1592
|
}
|
|
1593
|
+
catch (e) {
|
|
1594
|
+
// On abort, fall through with whatever was accumulated so far.
|
|
1595
|
+
if (!signal.aborted)
|
|
1596
|
+
throw e;
|
|
1597
|
+
}
|
|
1582
1598
|
// Flush any buffered content from the stream filter
|
|
1583
1599
|
if (streamFilter) {
|
|
1584
1600
|
const remaining = streamFilter.flush();
|
package/dist/agent/index.js
CHANGED
|
@@ -15,7 +15,9 @@ function persistedModelFor(providerName) {
|
|
|
15
15
|
return getSettings().providers?.[providerName]?.defaultModel;
|
|
16
16
|
}
|
|
17
17
|
function defaultReasoningBuilder(level) {
|
|
18
|
-
|
|
18
|
+
if (level === "off")
|
|
19
|
+
return {};
|
|
20
|
+
return { reasoning_effort: level === "xhigh" ? "high" : level };
|
|
19
21
|
}
|
|
20
22
|
function mergeCaps(settingsCaps, payloadCaps, modelIds) {
|
|
21
23
|
if (!settingsCaps)
|
|
@@ -118,11 +120,12 @@ export default function agentBackend(ctx) {
|
|
|
118
120
|
ctx.define("llm:get-client", () => llmClient);
|
|
119
121
|
ctx.define("llm:invoke", (messages, opts) => {
|
|
120
122
|
const effort = opts?.reasoningEffort;
|
|
123
|
+
const clampedEffort = effort === "xhigh" ? "high" : effort;
|
|
121
124
|
return llmClient.complete({
|
|
122
125
|
messages: messages,
|
|
123
126
|
max_tokens: opts?.maxTokens,
|
|
124
127
|
model: opts?.model,
|
|
125
|
-
...(
|
|
128
|
+
...(clampedEffort && clampedEffort !== "off" ? { reasoning_effort: clampedEffort } : {}),
|
|
126
129
|
});
|
|
127
130
|
});
|
|
128
131
|
let modes = [];
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Cloud OpenAI (api.openai.com). reasoning_effort vocabulary diverges per
|
|
3
3
|
* family: o-series has no off; gpt-5-codex floors at "low"; plain gpt-5
|
|
4
4
|
* floors at "minimal"; gpt-5.1+ accepts "none" as documented full off.
|
|
5
|
+
* Top tier: only gpt-5.1-codex-max and gpt-5.[4-9]+ accept "xhigh"; others
|
|
6
|
+
* clamp to "high".
|
|
5
7
|
*/
|
|
6
8
|
import type { AgentContext } from "../host-types.js";
|
|
7
9
|
export default function activate(ctx: AgentContext): void;
|
|
@@ -18,9 +18,16 @@ function offEffortFor(model) {
|
|
|
18
18
|
return "minimal";
|
|
19
19
|
return null;
|
|
20
20
|
}
|
|
21
|
+
function supportsXhigh(model) {
|
|
22
|
+
if (model.startsWith("gpt-5.1-codex-max"))
|
|
23
|
+
return true;
|
|
24
|
+
return /^gpt-5\.[4-9]/.test(model);
|
|
25
|
+
}
|
|
21
26
|
function buildReasoningParams(level, model) {
|
|
22
|
-
if (level !== "off")
|
|
23
|
-
|
|
27
|
+
if (level !== "off") {
|
|
28
|
+
const effort = level === "xhigh" && !(model && supportsXhigh(model)) ? "high" : level;
|
|
29
|
+
return { reasoning_effort: effort };
|
|
30
|
+
}
|
|
24
31
|
const off = model ? offEffortFor(model) : null;
|
|
25
32
|
return off ? { reasoning_effort: off } : {};
|
|
26
33
|
}
|
|
@@ -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 {
|
|
@@ -446,10 +446,13 @@ function getModelsPayload(): Record<string, unknown> | undefined {
|
|
|
446
446
|
if (!core) return undefined;
|
|
447
447
|
const info = core.bus.emitPipe("config:get-models", { models: [], active: null });
|
|
448
448
|
if (!info.models.length) return undefined;
|
|
449
|
+
const idFor = (m: { model: string; provider: string }) =>
|
|
450
|
+
m.provider ? `${m.model}@${m.provider}` : m.model;
|
|
451
|
+
const current = info.active ?? info.models[0]!;
|
|
449
452
|
return {
|
|
450
|
-
currentModelId:
|
|
453
|
+
currentModelId: idFor(current),
|
|
451
454
|
availableModels: info.models.map((m) => ({
|
|
452
|
-
modelId: m
|
|
455
|
+
modelId: idFor(m),
|
|
453
456
|
name: m.provider ? `${m.provider}/${m.model}` : m.model,
|
|
454
457
|
description: m.provider ? `Provider: ${m.provider}` : "",
|
|
455
458
|
})),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guanyilun/ashi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
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",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@earendil-works/pi-tui": "^0.74.0",
|
|
51
|
-
"agent-sh": "^0.13.
|
|
51
|
+
"agent-sh": "^0.13.3",
|
|
52
52
|
"chalk": "^5.5.0",
|
|
53
53
|
"cli-highlight": "^2.1.11"
|
|
54
54
|
},
|
|
@@ -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", (
|
|
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
|
|
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:
|
|
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, ""];
|
|
@@ -1,41 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Ollama provider extension — local daemon
|
|
2
|
+
* Ollama provider extension — local daemon or Ollama Cloud.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Cloud auth (any of):
|
|
5
|
+
* agent-sh auth login ollama-cloud # preferred
|
|
6
|
+
* OLLAMA_API_KEY=... # env fallback
|
|
7
|
+
*
|
|
8
|
+
* Local host:
|
|
9
|
+
* OLLAMA_HOST (default http://localhost:11434)
|
|
6
10
|
*
|
|
7
11
|
* Catalog comes from /api/tags; per-model context length is fetched
|
|
8
12
|
* from /api/show (model_info["${arch}.context_length"]). Chat goes
|
|
9
13
|
* through the OpenAI-compatible /v1/chat/completions shim.
|
|
10
14
|
*
|
|
11
|
-
* Setup (cloud):
|
|
12
|
-
* export OLLAMA_API_KEY="your-key"
|
|
13
|
-
*
|
|
14
|
-
* Setup (local):
|
|
15
|
-
* ollama serve # default http://localhost:11434
|
|
16
|
-
*
|
|
17
15
|
* Usage:
|
|
18
16
|
* agent-sh -e ./examples/extensions/ollama.ts
|
|
19
17
|
*
|
|
20
18
|
* # Or add to settings.json:
|
|
21
19
|
* { "extensions": ["./examples/extensions/ollama.ts"] }
|
|
22
20
|
*/
|
|
23
|
-
import
|
|
21
|
+
import { resolveApiKey } from "agent-sh/auth";
|
|
22
|
+
import type { AgentContext } from "agent-sh/types";
|
|
24
23
|
|
|
25
24
|
const ECHO_REASONING_PATTERNS: RegExp[] = [/deepseek/i];
|
|
26
25
|
|
|
27
|
-
export default function activate(ctx:
|
|
28
|
-
const
|
|
29
|
-
const host =
|
|
26
|
+
export default function activate(ctx: AgentContext): void {
|
|
27
|
+
const cloudKey = resolveApiKey("ollama-cloud").key ?? process.env.OLLAMA_API_KEY;
|
|
28
|
+
const host = cloudKey
|
|
30
29
|
? "https://ollama.com"
|
|
31
30
|
: (process.env.OLLAMA_HOST ?? "http://localhost:11434").replace(/\/$/, "");
|
|
32
|
-
const id =
|
|
31
|
+
const id = cloudKey ? "ollama-cloud" : "ollama";
|
|
33
32
|
|
|
34
33
|
// OpenAI SDK rejects an empty apiKey; the local daemon ignores the value.
|
|
35
|
-
const sdkKey =
|
|
34
|
+
const sdkKey = cloudKey || "no-key";
|
|
36
35
|
const baseURL = `${host}/v1`;
|
|
37
36
|
const headers: Record<string, string> = {};
|
|
38
|
-
if (
|
|
37
|
+
if (cloudKey) headers.Authorization = `Bearer ${cloudKey}`;
|
|
38
|
+
|
|
39
|
+
ctx.agent.providers.configure(id, {
|
|
40
|
+
reasoningParams: (level) => {
|
|
41
|
+
if (level === "off") return { reasoning_effort: "none" };
|
|
42
|
+
return { reasoning_effort: level === "xhigh" ? "high" : level };
|
|
43
|
+
},
|
|
44
|
+
});
|
|
39
45
|
|
|
40
46
|
ctx.bus.emit("provider:register", { id, apiKey: sdkKey, baseURL, models: [] });
|
|
41
47
|
|
|
@@ -0,0 +1,40 @@
|
|
|
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
|
+
const effort = level === "xhigh" ? "high" : level;
|
|
23
|
+
return { thinking: { type: "enabled" }, reasoning_effort: effort };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default function activate(ctx: AgentContext): void {
|
|
27
|
+
const { key } = resolveApiKey(ID);
|
|
28
|
+
const apiKey = key ?? process.env.ZAI_API_KEY ?? process.env.ZHIPU_API_KEY;
|
|
29
|
+
if (!apiKey) return;
|
|
30
|
+
|
|
31
|
+
ctx.agent.providers.configure(ID, { reasoningParams: buildReasoningParams });
|
|
32
|
+
|
|
33
|
+
ctx.bus.emit("provider:register", {
|
|
34
|
+
id: ID,
|
|
35
|
+
apiKey: apiKey,
|
|
36
|
+
baseURL: BASE_URL,
|
|
37
|
+
defaultModel: DEFAULT_MODELS[0].id,
|
|
38
|
+
models: DEFAULT_MODELS,
|
|
39
|
+
});
|
|
40
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-sh",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.4",
|
|
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": [
|