agent-sh 0.13.7 → 0.14.1
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/README.md +7 -18
- package/dist/agent/agent-loop.d.ts +13 -17
- package/dist/agent/agent-loop.js +118 -224
- package/dist/agent/conversation-state.d.ts +1 -1
- package/dist/agent/events.d.ts +218 -0
- package/dist/agent/events.js +1 -0
- package/dist/agent/host-types.d.ts +20 -0
- package/dist/agent/index.d.ts +5 -9
- package/dist/agent/index.js +269 -167
- package/dist/{utils → agent}/llm-client.js +1 -0
- package/dist/agent/llm-facade.d.ts +13 -0
- package/dist/{utils → agent}/llm-facade.js +1 -1
- package/dist/agent/nuclear-form.d.ts +1 -1
- package/dist/agent/providers/deepseek.js +2 -5
- package/dist/agent/providers/openai-compatible.js +2 -2
- package/dist/agent/providers/openai.js +2 -5
- package/dist/agent/providers/openrouter.js +5 -5
- package/dist/agent/subagent.d.ts +1 -1
- package/dist/agent/tool-protocol.d.ts +1 -1
- package/dist/agent/tool-registry.d.ts +1 -1
- package/dist/agent/types.d.ts +2 -2
- package/dist/cli/args.js +3 -1
- package/dist/cli/auth/cli.js +11 -6
- package/dist/cli/auth/discover.d.ts +5 -0
- package/dist/cli/auth/discover.js +25 -0
- package/dist/cli/auth/keys.d.ts +5 -2
- package/dist/cli/auth/keys.js +22 -2
- package/dist/cli/index.d.ts +16 -0
- package/dist/cli/index.js +12 -2
- package/dist/cli/install.d.ts +1 -0
- package/dist/cli/install.js +86 -2
- package/dist/cli/subcommands.js +4 -1
- package/dist/core/event-bus.d.ts +28 -371
- package/dist/core/extension-loader.js +6 -6
- package/dist/core/index.d.ts +10 -29
- package/dist/core/index.js +32 -84
- package/dist/extensions/index.d.ts +2 -1
- package/dist/extensions/index.js +1 -1
- package/dist/extensions/slash-commands/events.d.ts +18 -0
- package/dist/extensions/slash-commands/events.js +1 -0
- package/dist/extensions/slash-commands/index.d.ts +15 -0
- package/dist/extensions/{slash-commands.js → slash-commands/index.js} +4 -3
- package/dist/shell/events.d.ts +85 -0
- package/dist/shell/events.js +1 -0
- package/dist/shell/index.d.ts +1 -0
- package/dist/shell/index.js +6 -0
- package/dist/shell/tui-renderer.js +0 -1
- package/dist/utils/tool-interactive.js +4 -2
- package/examples/extensions/ash-acp-bridge/src/index.ts +2 -2
- package/examples/extensions/ashi/package.json +2 -2
- package/examples/extensions/ashi/src/cli.ts +4 -1
- package/examples/extensions/claude-code-bridge/index.ts +7 -2
- package/examples/extensions/claude-code-bridge/package.json +1 -1
- package/examples/extensions/ollama.ts +47 -42
- package/examples/extensions/opencode-bridge/README.md +4 -0
- package/examples/extensions/opencode-bridge/index.ts +208 -54
- package/examples/extensions/opencode-bridge/package.json +1 -1
- package/examples/extensions/pi-bridge/index.ts +3 -4
- package/examples/extensions/zai-coding-plan.ts +2 -6
- package/package.json +1 -1
- package/dist/extensions/slash-commands.d.ts +0 -2
- package/dist/utils/llm-facade.d.ts +0 -11
- /package/dist/{utils → agent}/llm-client.d.ts +0 -0
package/dist/core/index.js
CHANGED
|
@@ -1,23 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Core kernel —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* subscribing to bus events. Shell-specific tracking lives in the
|
|
7
|
-
* shell-context built-in extension.
|
|
8
|
-
*
|
|
9
|
-
* Agent backends register themselves via the agent:register-backend bus
|
|
10
|
-
* event. The built-in "ash" backend lives in src/agent/ and is activated
|
|
11
|
-
* by hosts via activateAgent().
|
|
12
|
-
*
|
|
13
|
-
* Usage:
|
|
14
|
-
* import { createCore } from "agent-sh";
|
|
15
|
-
* const core = createCore({ apiKey: "...", model: "gpt-4o" });
|
|
16
|
-
* core.bus.on("agent:response-chunk", ({ blocks }) => { ... });
|
|
17
|
-
* core.activateBackend();
|
|
18
|
-
* const response = await core.query("hello");
|
|
2
|
+
* Core kernel — EventBus + HandlerRegistry + backend registry. Knows
|
|
3
|
+
* nothing about LLMs, tools, or specific backends; backends (ash,
|
|
4
|
+
* claude-code-bridge, ...) register through `agent:register-backend`
|
|
5
|
+
* and core dispatches to whichever is configured as default.
|
|
19
6
|
*/
|
|
20
7
|
import { EventBus } from "./event-bus.js";
|
|
8
|
+
// Side-effect imports so downstream tsc sees module-augmented BusEvents.
|
|
9
|
+
import "../shell/events.js";
|
|
10
|
+
import "../agent/events.js";
|
|
11
|
+
import "../extensions/slash-commands/events.js";
|
|
21
12
|
import * as settingsMod from "./settings.js";
|
|
22
13
|
import { HandlerRegistry } from "../utils/handler-registry.js";
|
|
23
14
|
import crypto from "node:crypto";
|
|
@@ -27,118 +18,74 @@ import { CONFIG_DIR } from "./settings.js";
|
|
|
27
18
|
export { EventBus } from "./event-bus.js";
|
|
28
19
|
export { palette, setPalette, resetPalette } from "../utils/palette.js";
|
|
29
20
|
export { runSubagent } from "../agent/subagent.js";
|
|
30
|
-
export { LlmClient } from "../
|
|
21
|
+
export { LlmClient } from "../agent/llm-client.js";
|
|
31
22
|
export { HistoryFile, InMemoryHistory, NoopHistory } from "../agent/history-file.js";
|
|
32
23
|
export { compileSearchRegex, matchEntry, formatNuclearLine } from "../agent/nuclear-form.js";
|
|
33
24
|
export function createCore(config) {
|
|
34
25
|
const bus = new EventBus();
|
|
35
26
|
const handlers = new HandlerRegistry();
|
|
36
|
-
// 3 bytes = 6 hex chars
|
|
37
|
-
//
|
|
38
|
-
// parsers should accept ≥6 hex chars.
|
|
27
|
+
// 3 bytes = 6 hex chars; legacy content may have 16-char iids so parsers
|
|
28
|
+
// should accept ≥6 hex chars.
|
|
39
29
|
const instanceId = crypto.randomBytes(3).toString("hex");
|
|
40
30
|
bus.setSource(instanceId);
|
|
41
|
-
const settings = settingsMod.getSettings();
|
|
42
31
|
handlers.define("config:get-app-config", () => config);
|
|
43
|
-
// Default; shell-context advises with the PTY-tracked cwd when loaded.
|
|
44
32
|
handlers.define("cwd", () => process.cwd());
|
|
45
33
|
// Empty defaults so registerContextProducer can advise regardless of
|
|
46
|
-
// backend. Each backend chooses
|
|
47
|
-
// wraps them in <dynamic_context>/<query_context>; bridges may pull
|
|
48
|
-
// query-context:build and splice into the target SDK however they like.
|
|
34
|
+
// backend. Each backend chooses how to consume the strings.
|
|
49
35
|
handlers.define("dynamic-context:build", () => "");
|
|
50
36
|
handlers.define("query-context:build", () => "");
|
|
51
37
|
const backends = new Map();
|
|
52
38
|
let activeBackendName = null;
|
|
39
|
+
bus.on("agent:register-backend", (backend) => {
|
|
40
|
+
backends.set(backend.name, backend);
|
|
41
|
+
});
|
|
42
|
+
bus.onPipe("config:get-backends", () => ({
|
|
43
|
+
names: [...backends.keys()],
|
|
44
|
+
active: activeBackendName,
|
|
45
|
+
}));
|
|
53
46
|
const activateByName = async (name) => {
|
|
54
47
|
const backend = backends.get(name);
|
|
55
48
|
if (!backend) {
|
|
56
49
|
bus.emit("ui:error", { message: `Unknown backend: ${name}` });
|
|
57
50
|
return false;
|
|
58
51
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
otherBackend.kill();
|
|
52
|
+
if (activeBackendName && activeBackendName !== name) {
|
|
53
|
+
backends.get(activeBackendName)?.kill();
|
|
62
54
|
}
|
|
63
|
-
await backend.start?.();
|
|
64
55
|
activeBackendName = name;
|
|
56
|
+
await backend.start?.();
|
|
65
57
|
return true;
|
|
66
58
|
};
|
|
67
|
-
bus.on("agent:register-backend", (backend) => {
|
|
68
|
-
backends.set(backend.name, backend);
|
|
69
|
-
});
|
|
70
59
|
bus.on("config:switch-backend", ({ name }) => {
|
|
71
60
|
activateByName(name).then((ok) => {
|
|
72
61
|
if (!ok)
|
|
73
62
|
return;
|
|
74
63
|
settingsMod.updateSettings({ defaultBackend: name });
|
|
75
|
-
// Single ui:info; config:changed (which triggers prompt redraw) follows it.
|
|
76
64
|
bus.emit("ui:info", { message: `Backend: ${name} (saved as default)` });
|
|
77
65
|
bus.emit("config:changed", {});
|
|
78
66
|
});
|
|
79
67
|
});
|
|
80
68
|
bus.on("config:list-backends", () => {
|
|
81
|
-
const
|
|
82
|
-
const list = names
|
|
69
|
+
const list = [...backends.keys()]
|
|
83
70
|
.map((n) => n === activeBackendName ? `${n} (active)` : n)
|
|
84
71
|
.join(", ");
|
|
85
|
-
bus.emit("ui:info", { message: `Backends: ${list}` });
|
|
86
|
-
});
|
|
87
|
-
bus.onPipe("config:get-backends", () => {
|
|
88
|
-
const names = [...backends.keys()];
|
|
89
|
-
return { names, active: activeBackendName };
|
|
72
|
+
bus.emit("ui:info", { message: `Backends: ${list || "(none registered)"}` });
|
|
90
73
|
});
|
|
91
74
|
return {
|
|
92
75
|
bus,
|
|
93
76
|
handlers,
|
|
94
77
|
instanceId,
|
|
95
78
|
async activateBackend(override) {
|
|
96
|
-
if (backends.size === 0)
|
|
79
|
+
if (backends.size === 0) {
|
|
80
|
+
bus.emit("ui:info", { message: "No agent backend registered." });
|
|
97
81
|
return;
|
|
98
|
-
|
|
99
|
-
const
|
|
82
|
+
}
|
|
83
|
+
const preferred = override ?? settingsMod.getSettings().defaultBackend;
|
|
84
|
+
const name = preferred && backends.has(preferred)
|
|
85
|
+
? preferred
|
|
86
|
+
: backends.keys().next().value;
|
|
100
87
|
await activateByName(name);
|
|
101
88
|
},
|
|
102
|
-
async query(text) {
|
|
103
|
-
return new Promise((resolve, reject) => {
|
|
104
|
-
let response = "";
|
|
105
|
-
let settled = false;
|
|
106
|
-
const onChunk = (e) => {
|
|
107
|
-
for (const b of e.blocks)
|
|
108
|
-
if (b.type === "text")
|
|
109
|
-
response += b.text;
|
|
110
|
-
};
|
|
111
|
-
const onDone = () => {
|
|
112
|
-
if (settled)
|
|
113
|
-
return;
|
|
114
|
-
settled = true;
|
|
115
|
-
cleanup();
|
|
116
|
-
resolve(response);
|
|
117
|
-
};
|
|
118
|
-
const onError = (e) => {
|
|
119
|
-
if (settled)
|
|
120
|
-
return;
|
|
121
|
-
settled = true;
|
|
122
|
-
cleanup();
|
|
123
|
-
reject(new Error(e.message));
|
|
124
|
-
};
|
|
125
|
-
const cleanup = () => {
|
|
126
|
-
bus.off("agent:response-chunk", onChunk);
|
|
127
|
-
bus.off("agent:processing-done", onDone);
|
|
128
|
-
bus.off("agent:error", onError);
|
|
129
|
-
};
|
|
130
|
-
bus.on("agent:response-chunk", onChunk);
|
|
131
|
-
bus.on("agent:processing-done", onDone);
|
|
132
|
-
bus.on("agent:error", onError);
|
|
133
|
-
bus.emit("agent:submit", { query: text });
|
|
134
|
-
});
|
|
135
|
-
},
|
|
136
|
-
cancel() {
|
|
137
|
-
bus.emit("agent:cancel-request", {});
|
|
138
|
-
},
|
|
139
|
-
appendUserMessage(text) {
|
|
140
|
-
bus.emit("agent:append-user-message", { text });
|
|
141
|
-
},
|
|
142
89
|
extensionContext(opts) {
|
|
143
90
|
const ctx = {
|
|
144
91
|
bus,
|
|
@@ -166,6 +113,7 @@ export function createCore(config) {
|
|
|
166
113
|
kill() {
|
|
167
114
|
if (activeBackendName) {
|
|
168
115
|
backends.get(activeBackendName)?.kill();
|
|
116
|
+
activeBackendName = null;
|
|
169
117
|
}
|
|
170
118
|
},
|
|
171
119
|
};
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Cross-cutting built-ins, toggleable via `disabledBuiltins`.
|
|
3
3
|
* Module-owned built-ins activate inline:
|
|
4
4
|
* shell-context, tui-renderer → registerShellHandlers (src/shell/)
|
|
5
|
-
*
|
|
5
|
+
* ash (a specific backend) → activateAgent (src/agent/)
|
|
6
|
+
* backend registry → createCore (src/core/)
|
|
6
7
|
*/
|
|
7
8
|
import type { ExtensionContext } from "../shell/host-types.js";
|
|
8
9
|
type ActivateFn = (ctx: ExtensionContext) => void;
|
package/dist/extensions/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const BUILTIN_EXTENSIONS = [
|
|
2
|
-
{ name: "slash-commands", load: () => import("./slash-commands.js").then(m => m.default) },
|
|
2
|
+
{ name: "slash-commands", load: () => import("./slash-commands/index.js").then(m => m.default) },
|
|
3
3
|
{ name: "file-autocomplete", load: () => import("./file-autocomplete.js").then(m => m.default) },
|
|
4
4
|
];
|
|
5
5
|
/**
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Events slash-commands owns. */
|
|
2
|
+
declare module "../../core/event-bus.js" {
|
|
3
|
+
interface BusEvents {
|
|
4
|
+
"command:register": {
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
handler: (args: string) => Promise<void> | void;
|
|
8
|
+
};
|
|
9
|
+
"command:unregister": {
|
|
10
|
+
name: string;
|
|
11
|
+
};
|
|
12
|
+
"command:execute": {
|
|
13
|
+
name: string;
|
|
14
|
+
args: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slash commands extension.
|
|
3
|
+
*
|
|
4
|
+
* Registers built-in slash commands on the event bus:
|
|
5
|
+
* - Listens for "command:register" to accept commands from extensions
|
|
6
|
+
* - Responds to "autocomplete:request" pipe for /-prefixed completions
|
|
7
|
+
* - Handles "command:execute" events and dispatches to matching handler
|
|
8
|
+
* - Uses "ui:info"/"ui:error" for user feedback (no direct TUI dependency)
|
|
9
|
+
*
|
|
10
|
+
* Argument completion is composable: any extension can onPipe("autocomplete:request")
|
|
11
|
+
* and check payload.command / payload.commandArgs to add completions for any command.
|
|
12
|
+
*/
|
|
13
|
+
import "./events.js";
|
|
14
|
+
import type { ExtensionContext } from "../../shell/host-types.js";
|
|
15
|
+
export default function activate(ctx: ExtensionContext): void;
|
|
@@ -10,9 +10,10 @@
|
|
|
10
10
|
* Argument completion is composable: any extension can onPipe("autocomplete:request")
|
|
11
11
|
* and check payload.command / payload.commandArgs to add completions for any command.
|
|
12
12
|
*/
|
|
13
|
-
import
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
13
|
+
import "./events.js";
|
|
14
|
+
import { palette as p } from "../../utils/palette.js";
|
|
15
|
+
import { discoverSkills, loadSkillContent } from "../../agent/skills.js";
|
|
16
|
+
import { reloadExtensions } from "../../core/extension-loader.js";
|
|
16
17
|
export default function activate(ctx) {
|
|
17
18
|
const { bus } = ctx;
|
|
18
19
|
const commands = new Map();
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/** Events owned by the shell subsystem. */
|
|
2
|
+
declare module "../core/event-bus.js" {
|
|
3
|
+
interface BusEvents {
|
|
4
|
+
"shell:command-start": {
|
|
5
|
+
command: string;
|
|
6
|
+
cwd: string;
|
|
7
|
+
};
|
|
8
|
+
"shell:command-done": {
|
|
9
|
+
command: string;
|
|
10
|
+
output: string;
|
|
11
|
+
cwd: string;
|
|
12
|
+
exitCode: number | null;
|
|
13
|
+
};
|
|
14
|
+
"shell:cwd-change": {
|
|
15
|
+
cwd: string;
|
|
16
|
+
};
|
|
17
|
+
"shell:foreground-busy": {
|
|
18
|
+
busy: boolean;
|
|
19
|
+
};
|
|
20
|
+
"shell:agent-exec-start": Record<string, never>;
|
|
21
|
+
"shell:agent-exec-done": Record<string, never>;
|
|
22
|
+
"shell:pty-data": {
|
|
23
|
+
raw: string;
|
|
24
|
+
};
|
|
25
|
+
"shell:pty-write": {
|
|
26
|
+
data: string;
|
|
27
|
+
};
|
|
28
|
+
"shell:pty-resize": {
|
|
29
|
+
cols: number;
|
|
30
|
+
rows: number;
|
|
31
|
+
};
|
|
32
|
+
"shell:buffer-request": Record<string, never>;
|
|
33
|
+
"shell:buffer-snapshot": {
|
|
34
|
+
text: string;
|
|
35
|
+
altScreen: boolean;
|
|
36
|
+
cursor: {
|
|
37
|
+
x: number;
|
|
38
|
+
y: number;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
"shell:stdout-hold": Record<string, never>;
|
|
42
|
+
"shell:stdout-release": Record<string, never>;
|
|
43
|
+
"shell:stdout-show": Record<string, never>;
|
|
44
|
+
"shell:stdout-hide": Record<string, never>;
|
|
45
|
+
/** Sync pipe: handled=true suppresses default redraw. */
|
|
46
|
+
"shell:redraw-prompt": {
|
|
47
|
+
cwd: string;
|
|
48
|
+
kind: "fresh" | "redraw";
|
|
49
|
+
handled: boolean;
|
|
50
|
+
};
|
|
51
|
+
/** Async pipe: extension → user's PTY. */
|
|
52
|
+
"shell:exec-request": {
|
|
53
|
+
command: string;
|
|
54
|
+
output: string;
|
|
55
|
+
cwd: string;
|
|
56
|
+
exitCode: number | null;
|
|
57
|
+
done: boolean;
|
|
58
|
+
};
|
|
59
|
+
"input-mode:register": import("./host-types.js").InputModeConfig;
|
|
60
|
+
"input:keypress": {
|
|
61
|
+
key: string;
|
|
62
|
+
};
|
|
63
|
+
"input:intercept": {
|
|
64
|
+
data: string;
|
|
65
|
+
consumed: boolean;
|
|
66
|
+
};
|
|
67
|
+
"compositor:write": {
|
|
68
|
+
stream: string;
|
|
69
|
+
text: string;
|
|
70
|
+
};
|
|
71
|
+
/** Sync pipe: extensions append items. */
|
|
72
|
+
"autocomplete:request": {
|
|
73
|
+
buffer: string;
|
|
74
|
+
/** "/backend" or null if not a command. */
|
|
75
|
+
command: string | null;
|
|
76
|
+
/** Text after the command name, or null. */
|
|
77
|
+
commandArgs: string | null;
|
|
78
|
+
items: {
|
|
79
|
+
name: string;
|
|
80
|
+
description: string;
|
|
81
|
+
}[];
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/shell/index.d.ts
CHANGED
package/dist/shell/index.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frontend bootstrap. Loaded directly from src/cli/index.ts (not the
|
|
3
|
+
* built-in extensions manifest) because PTY + stdin raw mode ownership is
|
|
4
|
+
* order-critical.
|
|
5
|
+
*/
|
|
6
|
+
import "./events.js"; // augments BusEvents with shell-owned events
|
|
1
7
|
import { Shell } from "./shell.js";
|
|
2
8
|
import { DefaultCompositor, StdoutSurface } from "../utils/compositor.js";
|
|
3
9
|
import { TerminalBuffer } from "../utils/terminal-buffer.js";
|
|
@@ -149,7 +149,6 @@ export default function activate(ctx) {
|
|
|
149
149
|
titleRight: modelLabel,
|
|
150
150
|
});
|
|
151
151
|
});
|
|
152
|
-
// Track backend/model info for display on response border
|
|
153
152
|
let backendInfo = null;
|
|
154
153
|
bus.on("agent:info", (info) => { backendInfo = info; });
|
|
155
154
|
// ── Register fenced block transform (code blocks → ContentBlock) ──
|
|
@@ -14,7 +14,6 @@ export function createToolUI(bus, surface) {
|
|
|
14
14
|
if (finished)
|
|
15
15
|
return;
|
|
16
16
|
finished = true;
|
|
17
|
-
clearLines(surface, prevLineCount);
|
|
18
17
|
bus.offPipe("input:intercept", interceptor);
|
|
19
18
|
bus.emit("shell:stdout-hide", {});
|
|
20
19
|
bus.emit("tool:interactive-end", {});
|
|
@@ -45,7 +44,10 @@ export function createToolUI(bus, surface) {
|
|
|
45
44
|
bus.emit("tool:interactive-start", {});
|
|
46
45
|
bus.emit("shell:stdout-show", {});
|
|
47
46
|
bus.onPipe("input:intercept", interceptor);
|
|
48
|
-
|
|
47
|
+
// Drop to a fresh row in case the cursor was mid-line; uncounted
|
|
48
|
+
// so clearLines on dismiss stops at the gap, not above it.
|
|
49
|
+
surface.write("\n");
|
|
50
|
+
session.onMount?.(() => render(), done);
|
|
49
51
|
render();
|
|
50
52
|
});
|
|
51
53
|
},
|
|
@@ -434,10 +434,10 @@ function waitForModelsToSettle(
|
|
|
434
434
|
timer = setTimeout(done, Math.max(0, Math.min(quietMs, remaining)));
|
|
435
435
|
};
|
|
436
436
|
const done = () => {
|
|
437
|
-
core.bus.off("
|
|
437
|
+
core.bus.off("agent:modes-changed", arm);
|
|
438
438
|
resolve();
|
|
439
439
|
};
|
|
440
|
-
core.bus.on("
|
|
440
|
+
core.bus.on("agent:modes-changed", arm);
|
|
441
441
|
arm();
|
|
442
442
|
});
|
|
443
443
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guanyilun/ashi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
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.
|
|
51
|
+
"agent-sh": "^0.14.0",
|
|
52
52
|
"chalk": "^5.5.0",
|
|
53
53
|
"cli-highlight": "^2.1.11"
|
|
54
54
|
},
|
|
@@ -70,7 +70,10 @@ async function main(): Promise<void> {
|
|
|
70
70
|
|
|
71
71
|
if (sub === "install" || sub === "uninstall" || sub === "list") {
|
|
72
72
|
const { runInstall, runUninstall, runList } = await import("agent-sh/cli/install");
|
|
73
|
-
if (sub === "install") await runInstall(rest[0] ?? "", {
|
|
73
|
+
if (sub === "install") await runInstall(rest[0] ?? "", {
|
|
74
|
+
force: rest.includes("--force"),
|
|
75
|
+
syncDeps: rest.includes("--sync-deps"),
|
|
76
|
+
});
|
|
74
77
|
else if (sub === "uninstall") await runUninstall(rest[0] ?? "");
|
|
75
78
|
else runList();
|
|
76
79
|
process.exit(0);
|
|
@@ -21,6 +21,7 @@ export default function activate(ctx: ExtensionContext): void {
|
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
let activeQuery: Query | null = null;
|
|
24
|
+
let sessionId: string | null = null;
|
|
24
25
|
const listeners: Array<{ event: string; fn: Function }> = [];
|
|
25
26
|
|
|
26
27
|
// ── Tool display helpers ────────────────────────────────────────
|
|
@@ -97,6 +98,7 @@ export default function activate(ctx: ExtensionContext): void {
|
|
|
97
98
|
allowedTools: ["Read", "Edit", "Write", "Bash", "Glob", "Grep"],
|
|
98
99
|
permissionMode: "acceptEdits",
|
|
99
100
|
includePartialMessages: true,
|
|
101
|
+
...(sessionId ? { resume: sessionId } : {}),
|
|
100
102
|
},
|
|
101
103
|
});
|
|
102
104
|
|
|
@@ -262,8 +264,11 @@ export default function activate(ctx: ExtensionContext): void {
|
|
|
262
264
|
// Tool still running — nothing to do, TUI spinner already active
|
|
263
265
|
break;
|
|
264
266
|
|
|
265
|
-
case "result":
|
|
267
|
+
case "result": {
|
|
268
|
+
const sid = (message as any).session_id;
|
|
269
|
+
if (typeof sid === "string" && sid) sessionId = sid;
|
|
266
270
|
break;
|
|
271
|
+
}
|
|
267
272
|
}
|
|
268
273
|
}
|
|
269
274
|
|
|
@@ -291,7 +296,7 @@ export default function activate(ctx: ExtensionContext): void {
|
|
|
291
296
|
};
|
|
292
297
|
|
|
293
298
|
const onCancel = () => { activeQuery?.interrupt(); };
|
|
294
|
-
const onReset = () => {
|
|
299
|
+
const onReset = () => { sessionId = null; };
|
|
295
300
|
|
|
296
301
|
bus.on("agent:submit", onSubmit);
|
|
297
302
|
bus.on("agent:cancel-request", onCancel);
|
|
@@ -1,60 +1,65 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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)
|
|
10
|
-
*
|
|
11
|
-
* Catalog comes from /api/tags; per-model context length is fetched
|
|
12
|
-
* from /api/show (model_info["${arch}.context_length"]). Chat goes
|
|
13
|
-
* through the OpenAI-compatible /v1/chat/completions shim.
|
|
14
|
-
*
|
|
15
|
-
* Usage:
|
|
16
|
-
* agent-sh -e ./examples/extensions/ollama.ts
|
|
17
|
-
*
|
|
18
|
-
* # Or add to settings.json:
|
|
19
|
-
* { "extensions": ["./examples/extensions/ollama.ts"] }
|
|
2
|
+
* Registers `ollama` (local, no auth) and `ollama-cloud` (login via
|
|
3
|
+
* `agent-sh auth login ollama-cloud` or OLLAMA_API_KEY). Local host
|
|
4
|
+
* overridable via OLLAMA_HOST.
|
|
20
5
|
*/
|
|
21
6
|
import { resolveApiKey } from "agent-sh/auth";
|
|
22
7
|
import type { AgentContext } from "agent-sh/types";
|
|
23
8
|
|
|
24
9
|
const ECHO_REASONING_PATTERNS: RegExp[] = [/deepseek/i];
|
|
25
10
|
|
|
11
|
+
function reasoningParams(level: string): Record<string, unknown> {
|
|
12
|
+
if (level === "off") return { reasoning_effort: "none" };
|
|
13
|
+
return { reasoning_effort: level === "xhigh" ? "high" : level };
|
|
14
|
+
}
|
|
15
|
+
|
|
26
16
|
export default function activate(ctx: AgentContext): void {
|
|
27
17
|
const cloudKey = resolveApiKey("ollama-cloud").key ?? process.env.OLLAMA_API_KEY;
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const headers: Record<string, string> = {};
|
|
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
|
-
},
|
|
18
|
+
const cloudHost = "https://ollama.com";
|
|
19
|
+
const cloudBaseURL = `${cloudHost}/v1`;
|
|
20
|
+
ctx.agent.providers.configure("ollama-cloud", { reasoningParams });
|
|
21
|
+
ctx.agent.providers.register({
|
|
22
|
+
id: "ollama-cloud",
|
|
23
|
+
apiKey: cloudKey ?? undefined,
|
|
24
|
+
baseURL: cloudBaseURL,
|
|
25
|
+
models: [],
|
|
44
26
|
});
|
|
27
|
+
if (cloudKey) {
|
|
28
|
+
const headers = { Authorization: `Bearer ${cloudKey}` };
|
|
29
|
+
fetchCatalog(cloudHost, headers).then((models) => {
|
|
30
|
+
if (models.length === 0) return;
|
|
31
|
+
ctx.agent.providers.register({
|
|
32
|
+
id: "ollama-cloud",
|
|
33
|
+
apiKey: cloudKey,
|
|
34
|
+
baseURL: cloudBaseURL,
|
|
35
|
+
defaultModel: models[0]!.id,
|
|
36
|
+
models,
|
|
37
|
+
});
|
|
38
|
+
}).catch(() => {});
|
|
39
|
+
}
|
|
45
40
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
const localHost = (process.env.OLLAMA_HOST ?? "http://localhost:11434").replace(/\/$/, "");
|
|
42
|
+
const localBaseURL = `${localHost}/v1`;
|
|
43
|
+
ctx.agent.providers.configure("ollama", { reasoningParams });
|
|
44
|
+
// OpenAI SDK rejects an empty apiKey; the local daemon ignores it.
|
|
45
|
+
ctx.agent.providers.register({
|
|
46
|
+
id: "ollama",
|
|
47
|
+
apiKey: "no-key",
|
|
48
|
+
baseURL: localBaseURL,
|
|
49
|
+
models: [],
|
|
50
|
+
noAuth: true,
|
|
51
|
+
});
|
|
52
|
+
fetchCatalog(localHost, {}).then((models) => {
|
|
49
53
|
if (models.length === 0) return;
|
|
50
|
-
ctx.
|
|
51
|
-
id,
|
|
52
|
-
apiKey:
|
|
53
|
-
baseURL,
|
|
54
|
+
ctx.agent.providers.register({
|
|
55
|
+
id: "ollama",
|
|
56
|
+
apiKey: "no-key",
|
|
57
|
+
baseURL: localBaseURL,
|
|
54
58
|
defaultModel: models[0]!.id,
|
|
55
59
|
models,
|
|
60
|
+
noAuth: true,
|
|
56
61
|
});
|
|
57
|
-
}).catch(() => {
|
|
62
|
+
}).catch(() => {});
|
|
58
63
|
}
|
|
59
64
|
|
|
60
65
|
async function fetchCatalog(
|
|
@@ -40,6 +40,10 @@ opencode reads its own config from `~/.local/share/opencode/` (auth credentials)
|
|
|
40
40
|
- opencode authenticated locally — run `opencode auth login` once before using this bridge.
|
|
41
41
|
- Provider env vars (e.g. `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`) as required by opencode for the model you've selected.
|
|
42
42
|
|
|
43
|
+
## Environment
|
|
44
|
+
|
|
45
|
+
- `OPENCODE_SDK_PORT` — port for the in-process opencode HTTP server. Defaults to `0` (OS-assigned free port) so a stale standalone opencode on the SDK's default port 4096 can't collide with the bridge. Set explicitly only if you need a deterministic port.
|
|
46
|
+
|
|
43
47
|
## What works under opencode
|
|
44
48
|
|
|
45
49
|
agent-sh's per-query context producers (e.g. `<shell_events>` from `shell-context`) are inlined into opencode's prompt before each query, so opencode sees the user's recent shell activity even though the SDK doesn't subscribe to agent-sh's shell bus directly. The current cwd is part of that context, so opencode knows where the user is even when its tools are anchored elsewhere.
|