agent-sh 0.12.27 → 0.13.0
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 +13 -2
- package/dist/agent/agent-loop.d.ts +3 -5
- package/dist/agent/agent-loop.js +42 -98
- package/dist/agent/conversation-state.d.ts +9 -0
- package/dist/agent/conversation-state.js +16 -0
- package/dist/agent/history-file.d.ts +6 -0
- package/dist/agent/history-file.js +1 -1
- package/dist/agent/host-types.d.ts +125 -0
- package/dist/agent/index.d.ts +12 -4
- package/dist/agent/index.js +357 -6
- package/dist/agent/nuclear-form.d.ts +7 -0
- package/dist/{extensions → agent}/providers/deepseek.d.ts +2 -2
- package/dist/{extensions → agent}/providers/deepseek.js +5 -4
- package/dist/{extensions → agent}/providers/openai-compatible.d.ts +2 -2
- package/dist/{extensions → agent}/providers/openai.d.ts +2 -2
- package/dist/{extensions → agent}/providers/openai.js +3 -2
- package/dist/{extensions → agent}/providers/openrouter.d.ts +2 -2
- package/dist/{extensions → agent}/providers/openrouter.js +4 -3
- package/dist/agent/skills.js +51 -7
- package/dist/agent/subagent.d.ts +1 -1
- package/dist/agent/system-prompt.js +14 -17
- package/dist/agent/tool-protocol.d.ts +1 -1
- package/dist/agent/tool-protocol.js +5 -3
- package/dist/agent/tool-registry.d.ts +9 -4
- package/dist/agent/tool-registry.js +27 -4
- package/dist/agent/tools/bash.d.ts +1 -1
- package/dist/agent/tools/bash.js +3 -2
- package/dist/agent/tools/edit-file.js +0 -1
- package/dist/agent/tools/glob.js +1 -1
- package/dist/agent/tools/grep.js +1 -1
- package/dist/agent/tools/pwsh.d.ts +1 -1
- package/dist/agent/tools/pwsh.js +1 -2
- package/dist/agent/tools/read-file.js +7 -4
- package/dist/agent/tools/write-file.js +0 -1
- package/dist/agent/types.d.ts +17 -2
- package/dist/cli/auth/cli.d.ts +1 -0
- package/dist/cli/auth/cli.js +216 -0
- package/dist/cli/auth/keys.d.ts +31 -0
- package/dist/cli/auth/keys.js +102 -0
- package/dist/{index.js → cli/index.js} +29 -32
- package/dist/{init.js → cli/init.js} +1 -1
- package/dist/{install.js → cli/install.js} +31 -2
- package/dist/cli/subcommands.d.ts +1 -0
- package/dist/cli/subcommands.js +17 -0
- package/dist/{event-bus.d.ts → core/event-bus.d.ts} +7 -13
- package/dist/{extension-loader.d.ts → core/extension-loader.d.ts} +1 -1
- package/dist/{extension-loader.js → core/extension-loader.js} +62 -70
- package/dist/{core.d.ts → core/index.d.ts} +18 -15
- package/dist/{core.js → core/index.js} +18 -92
- package/dist/{settings.d.ts → core/settings.d.ts} +7 -0
- package/dist/{settings.js → core/settings.js} +1 -0
- package/dist/core/types.d.ts +49 -0
- package/dist/core/types.js +1 -0
- package/dist/extensions/file-autocomplete.d.ts +1 -1
- package/dist/extensions/index.d.ts +7 -14
- package/dist/extensions/index.js +2 -19
- package/dist/extensions/slash-commands.d.ts +1 -1
- package/dist/extensions/slash-commands.js +7 -2
- package/dist/shell/host-types.d.ts +114 -0
- package/dist/shell/host-types.js +1 -0
- package/dist/shell/index.d.ts +8 -7
- package/dist/shell/index.js +58 -9
- package/dist/shell/input-handler.d.ts +7 -1
- package/dist/shell/input-handler.js +5 -2
- package/dist/shell/output-parser.d.ts +1 -1
- package/dist/{extensions → shell}/shell-context.d.ts +1 -1
- package/dist/{extensions → shell}/shell-context.js +18 -12
- package/dist/shell/shell.d.ts +6 -4
- package/dist/shell/shell.js +33 -109
- package/dist/shell/strategies/bash.d.ts +2 -0
- package/dist/shell/strategies/bash.js +68 -0
- package/dist/shell/strategies/fish.d.ts +2 -0
- package/dist/shell/strategies/fish.js +65 -0
- package/dist/shell/strategies/index.d.ts +13 -0
- package/dist/shell/strategies/index.js +17 -0
- package/dist/shell/strategies/types.d.ts +50 -0
- package/dist/shell/strategies/types.js +9 -0
- package/dist/shell/strategies/zsh.d.ts +2 -0
- package/dist/shell/strategies/zsh.js +72 -0
- package/dist/shell/tui-input-view.js +14 -3
- package/dist/{extensions → shell}/tui-renderer.d.ts +1 -1
- package/dist/{extensions → shell}/tui-renderer.js +27 -55
- package/dist/utils/box-frame.d.ts +4 -0
- package/dist/utils/box-frame.js +17 -6
- package/dist/utils/compositor.d.ts +1 -1
- package/dist/utils/compositor.js +2 -1
- package/dist/{executor.js → utils/executor.js} +1 -1
- package/dist/utils/floating-panel.d.ts +1 -1
- package/dist/utils/floating-panel.js +9 -4
- package/dist/utils/llm-facade.d.ts +7 -3
- package/dist/utils/stream-transform.d.ts +1 -1
- package/dist/utils/terminal-buffer.d.ts +1 -1
- package/dist/utils/tool-display.js +4 -0
- package/dist/utils/tool-interactive.d.ts +1 -1
- package/dist/utils/tty.d.ts +7 -0
- package/dist/utils/tty.js +15 -0
- package/examples/extensions/ash-acp-bridge/README.md +4 -1
- package/examples/extensions/ash-acp-bridge/src/index.ts +654 -0
- package/examples/extensions/ash-mcp-bridge/index.ts +1 -1
- package/examples/extensions/ashi/README.md +250 -0
- package/examples/extensions/ashi/package.json +60 -0
- package/examples/extensions/ashi/src/autocomplete.ts +91 -0
- package/examples/extensions/ashi/src/capture.ts +34 -0
- package/examples/extensions/ashi/src/cli.ts +126 -0
- package/examples/extensions/ashi/src/commands.ts +82 -0
- package/examples/extensions/ashi/src/compaction.ts +157 -0
- package/examples/extensions/ashi/src/components.ts +332 -0
- package/examples/extensions/ashi/src/default-renderers.ts +153 -0
- package/examples/extensions/ashi/src/display-config.ts +62 -0
- package/examples/extensions/ashi/src/frontend.ts +735 -0
- package/examples/extensions/ashi/src/hooks.ts +136 -0
- package/examples/extensions/ashi/src/multi-session-store.ts +146 -0
- package/examples/extensions/ashi/src/session-commands.ts +76 -0
- package/examples/extensions/ashi/src/session-store.ts +264 -0
- package/examples/extensions/ashi/src/status-footer.ts +66 -0
- package/examples/extensions/ashi/src/theme.ts +151 -0
- package/examples/extensions/ashi/tsconfig.json +14 -0
- package/examples/extensions/emacs-buffer.ts +1 -1
- package/examples/extensions/interactive-prompts.ts +114 -69
- package/examples/extensions/latex-images.ts +3 -3
- package/examples/extensions/opencode-bridge/index.ts +1 -1
- package/examples/extensions/overlay-agent.ts +7 -5
- package/examples/extensions/peer-mesh.ts +1 -1
- package/examples/extensions/pi-bridge/index.ts +0 -1
- package/examples/extensions/questionnaire.ts +2 -1
- package/examples/extensions/rtk-proxy.ts +3 -3
- package/examples/extensions/solarized-theme.ts +3 -3
- package/examples/extensions/subagents.ts +6 -6
- package/examples/extensions/terminal-buffer.ts +1 -1
- package/examples/extensions/tmux-pane.ts +6 -4
- package/examples/extensions/tunnel-vision.ts +5 -5
- package/examples/extensions/user-shell.ts +1 -1
- package/examples/extensions/web-access.ts +5 -5
- package/package.json +26 -22
- package/dist/extensions/agent-backend.d.ts +0 -14
- package/dist/extensions/agent-backend.js +0 -307
- package/dist/types.d.ts +0 -227
- /package/dist/{types.js → agent/host-types.js} +0 -0
- /package/dist/{extensions → agent}/providers/openai-compatible.js +0 -0
- /package/dist/{index.d.ts → cli/index.d.ts} +0 -0
- /package/dist/{init.d.ts → cli/init.d.ts} +0 -0
- /package/dist/{install.d.ts → cli/install.d.ts} +0 -0
- /package/dist/{event-bus.js → core/event-bus.js} +0 -0
- /package/dist/{executor.d.ts → utils/executor.d.ts} +0 -0
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
*/
|
|
31
31
|
import * as fs from "node:fs";
|
|
32
32
|
import * as path from "node:path";
|
|
33
|
-
import type {
|
|
33
|
+
import type { AgentContext } from "agent-sh/types";
|
|
34
34
|
|
|
35
35
|
const BIND_RE = /\x1b\]9996;vt=([^;]+);BIND;([^;]*);([^\x07]*)\x07/;
|
|
36
36
|
const OSC7_RE = /\x1b\]7;file:\/\/[^/]*(\/[^\x07\x1b]*)/;
|
|
@@ -137,7 +137,7 @@ function renderInjection(): string {
|
|
|
137
137
|
return lines.join("\n");
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
export default function activate(ctx:
|
|
140
|
+
export default function activate(ctx: AgentContext): void {
|
|
141
141
|
bindingFile = path.join(ctx.getStoragePath("tunnel-vision"), "binding.json");
|
|
142
142
|
loadBinding();
|
|
143
143
|
|
|
@@ -216,7 +216,7 @@ export default function activate(ctx: ExtensionContext): void {
|
|
|
216
216
|
ctx.bus.emit("ui:info", { message: `Tunnel-vision binding to ${host} cleared.` });
|
|
217
217
|
});
|
|
218
218
|
|
|
219
|
-
ctx.registerInstruction("tunnel-vision",
|
|
219
|
+
ctx.agent.registerInstruction("tunnel-vision",
|
|
220
220
|
`# Tunnel-vision — driving a remote shell through pty_send
|
|
221
221
|
|
|
222
222
|
When tunnel-vision is active (you'll see a "Tunnel-vision active" block in
|
|
@@ -302,7 +302,7 @@ prompt-wait to capture output.`
|
|
|
302
302
|
});
|
|
303
303
|
});
|
|
304
304
|
|
|
305
|
-
ctx.registerTool({
|
|
305
|
+
ctx.agent.registerTool({
|
|
306
306
|
name: "pty_send",
|
|
307
307
|
description:
|
|
308
308
|
"Run a command in the user's interactive remote shell session (tunnel-vision). " +
|
|
@@ -399,7 +399,7 @@ prompt-wait to capture output.`
|
|
|
399
399
|
},
|
|
400
400
|
});
|
|
401
401
|
|
|
402
|
-
ctx.registerContextProducer("tunnel-vision", () =>
|
|
402
|
+
ctx.agent.registerContextProducer("tunnel-vision", () =>
|
|
403
403
|
binding ? renderInjection() : null,
|
|
404
404
|
);
|
|
405
405
|
}
|
|
@@ -17,7 +17,7 @@ import type { ExtensionContext } from "agent-sh/types";
|
|
|
17
17
|
import type { ToolDefinition } from "agent-sh/agent/types";
|
|
18
18
|
|
|
19
19
|
export default function activate(ctx: ExtensionContext): void {
|
|
20
|
-
const { bus
|
|
20
|
+
const { bus } = ctx; const { registerTool, registerInstruction } = ctx.agent;
|
|
21
21
|
const getCwd = () => ctx.call("cwd") as string;
|
|
22
22
|
|
|
23
23
|
// ── Tool ───────────────────────────────────────────────────────
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*
|
|
17
17
|
* Inspired by: https://github.com/nicobailon/pi-web-access
|
|
18
18
|
*/
|
|
19
|
-
import type {
|
|
19
|
+
import type { AgentContext } from "agent-sh/types";
|
|
20
20
|
|
|
21
21
|
// ── Constants ────────────────────────────────────────────────────────
|
|
22
22
|
|
|
@@ -123,7 +123,7 @@ async function directFetch(url: string, timeout: number): Promise<string> {
|
|
|
123
123
|
|
|
124
124
|
// ── Extension entry point ────────────────────────────────────────────
|
|
125
125
|
|
|
126
|
-
export default function activate(ctx:
|
|
126
|
+
export default function activate(ctx: AgentContext) {
|
|
127
127
|
const config = ctx.getExtensionSettings("web-access", {
|
|
128
128
|
timeout: 30000,
|
|
129
129
|
searchNumResults: 5,
|
|
@@ -134,7 +134,7 @@ export default function activate(ctx: ExtensionContext) {
|
|
|
134
134
|
|
|
135
135
|
// ── System instruction ────────────────────────────────────────────
|
|
136
136
|
|
|
137
|
-
ctx.registerInstruction(
|
|
137
|
+
ctx.agent.registerInstruction(
|
|
138
138
|
"You have access to web search and fetching tools. " +
|
|
139
139
|
"Use `web_search` to find information on the web, then `web_fetch` to read specific pages. " +
|
|
140
140
|
"Use `web_fetch` with `raw: true` for JSON APIs or plain text files.",
|
|
@@ -142,7 +142,7 @@ export default function activate(ctx: ExtensionContext) {
|
|
|
142
142
|
|
|
143
143
|
// ── Tool: web_search (Exa MCP, free) ────────────────────────────
|
|
144
144
|
|
|
145
|
-
ctx.registerTool({
|
|
145
|
+
ctx.agent.registerTool({
|
|
146
146
|
name: "web_search",
|
|
147
147
|
displayName: "Web Search",
|
|
148
148
|
description:
|
|
@@ -178,7 +178,7 @@ export default function activate(ctx: ExtensionContext) {
|
|
|
178
178
|
|
|
179
179
|
// ── Tool: web_fetch ─────────────────────────────────────────────
|
|
180
180
|
|
|
181
|
-
ctx.registerTool({
|
|
181
|
+
ctx.agent.registerTool({
|
|
182
182
|
name: "web_fetch",
|
|
183
183
|
displayName: "Web Fetch",
|
|
184
184
|
description:
|
package/package.json
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-sh",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "A shell-first terminal where AI is one keystroke away",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "dist/core.js",
|
|
7
|
-
"types": "dist/core.d.ts",
|
|
6
|
+
"main": "dist/core/index.js",
|
|
7
|
+
"types": "dist/core/index.d.ts",
|
|
8
8
|
"bin": {
|
|
9
|
-
"agent-sh": "dist/index.js"
|
|
9
|
+
"agent-sh": "dist/cli/index.js"
|
|
10
10
|
},
|
|
11
11
|
"exports": {
|
|
12
12
|
".": {
|
|
13
|
-
"types": "./dist/core.d.ts",
|
|
14
|
-
"default": "./dist/core.js"
|
|
13
|
+
"types": "./dist/core/index.d.ts",
|
|
14
|
+
"default": "./dist/core/index.js"
|
|
15
15
|
},
|
|
16
16
|
"./core": {
|
|
17
|
-
"types": "./dist/core.d.ts",
|
|
18
|
-
"default": "./dist/core.js"
|
|
17
|
+
"types": "./dist/core/index.d.ts",
|
|
18
|
+
"default": "./dist/core/index.js"
|
|
19
19
|
},
|
|
20
20
|
"./utils/*": "./dist/utils/*",
|
|
21
21
|
"./types": {
|
|
22
|
-
"types": "./dist/
|
|
23
|
-
"default": "./dist/
|
|
22
|
+
"types": "./dist/core/index.d.ts",
|
|
23
|
+
"default": "./dist/core/index.js"
|
|
24
24
|
},
|
|
25
25
|
"./settings": {
|
|
26
|
-
"types": "./dist/settings.d.ts",
|
|
27
|
-
"default": "./dist/settings.js"
|
|
26
|
+
"types": "./dist/core/settings.d.ts",
|
|
27
|
+
"default": "./dist/core/settings.js"
|
|
28
28
|
},
|
|
29
29
|
"./extension-loader": {
|
|
30
|
-
"types": "./dist/extension-loader.d.ts",
|
|
31
|
-
"default": "./dist/extension-loader.js"
|
|
30
|
+
"types": "./dist/core/extension-loader.d.ts",
|
|
31
|
+
"default": "./dist/core/extension-loader.js"
|
|
32
32
|
},
|
|
33
33
|
"./extensions": {
|
|
34
34
|
"types": "./dist/extensions/index.d.ts",
|
|
@@ -46,6 +46,10 @@
|
|
|
46
46
|
"types": "./dist/utils/terminal-buffer.d.ts",
|
|
47
47
|
"default": "./dist/utils/terminal-buffer.js"
|
|
48
48
|
},
|
|
49
|
+
"./agent": {
|
|
50
|
+
"types": "./dist/agent/index.d.ts",
|
|
51
|
+
"default": "./dist/agent/index.js"
|
|
52
|
+
},
|
|
49
53
|
"./agent/types": {
|
|
50
54
|
"types": "./dist/agent/types.d.ts",
|
|
51
55
|
"default": "./dist/agent/types.js"
|
|
@@ -59,8 +63,8 @@
|
|
|
59
63
|
"default": "./dist/agent/agent-loop.js"
|
|
60
64
|
},
|
|
61
65
|
"./event-bus": {
|
|
62
|
-
"types": "./dist/event-bus.d.ts",
|
|
63
|
-
"default": "./dist/event-bus.js"
|
|
66
|
+
"types": "./dist/core/event-bus.d.ts",
|
|
67
|
+
"default": "./dist/core/event-bus.js"
|
|
64
68
|
},
|
|
65
69
|
"./utils/compositor": {
|
|
66
70
|
"types": "./dist/utils/compositor.d.ts",
|
|
@@ -83,8 +87,8 @@
|
|
|
83
87
|
"default": "./dist/agent/nuclear-form.js"
|
|
84
88
|
},
|
|
85
89
|
"./executor": {
|
|
86
|
-
"types": "./dist/executor.d.ts",
|
|
87
|
-
"default": "./dist/executor.js"
|
|
90
|
+
"types": "./dist/utils/executor.d.ts",
|
|
91
|
+
"default": "./dist/utils/executor.js"
|
|
88
92
|
}
|
|
89
93
|
},
|
|
90
94
|
"files": [
|
|
@@ -93,15 +97,15 @@
|
|
|
93
97
|
"examples/extensions/*/package.json",
|
|
94
98
|
"examples/extensions/*/tsconfig.json",
|
|
95
99
|
"examples/extensions/*/README.md",
|
|
96
|
-
"examples/extensions/*/src",
|
|
100
|
+
"examples/extensions/*/src/**",
|
|
97
101
|
"examples/extensions/*/index.ts",
|
|
98
102
|
"examples/extensions/*/index.js"
|
|
99
103
|
],
|
|
100
104
|
"scripts": {
|
|
101
|
-
"dev": "tsx src/index.ts",
|
|
105
|
+
"dev": "tsx src/cli/index.ts",
|
|
102
106
|
"build": "tsc",
|
|
103
|
-
"start": "node dist/index.js",
|
|
104
|
-
"prepare": "npm run build"
|
|
107
|
+
"start": "node dist/cli/index.js",
|
|
108
|
+
"prepare": "test -d dist || npm run build"
|
|
105
109
|
},
|
|
106
110
|
"keywords": [
|
|
107
111
|
"terminal",
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Built-in agent backend extension.
|
|
3
|
-
*
|
|
4
|
-
* Constructs the AgentLoop synchronously with a placeholder LlmClient,
|
|
5
|
-
* so core handlers (history:append, system-prompt:build, conversation:*)
|
|
6
|
-
* are defined before user extensions activate. Mode resolution is
|
|
7
|
-
* deferred to `core:extensions-loaded`, giving runtime-registered
|
|
8
|
-
* providers (e.g. openrouter) a chance to register before we look up
|
|
9
|
-
* settings.defaultProvider. Without this deferral, a persisted
|
|
10
|
-
* `defaultProvider: "openrouter"` loses to a cold-start race and the
|
|
11
|
-
* backend bails silently.
|
|
12
|
-
*/
|
|
13
|
-
import type { ExtensionContext } from "../types.js";
|
|
14
|
-
export default function agentBackend(ctx: ExtensionContext): void;
|
|
@@ -1,307 +0,0 @@
|
|
|
1
|
-
import { AgentLoop } from "../agent/agent-loop.js";
|
|
2
|
-
import { LlmClient } from "../utils/llm-client.js";
|
|
3
|
-
import { resolveProvider, getProviderNames, getSettings } from "../settings.js";
|
|
4
|
-
import { PACKAGE_VERSION } from "../utils/package-version.js";
|
|
5
|
-
import { discoverSkills } from "../agent/skills.js";
|
|
6
|
-
/** Read the user's persisted defaultModel for a provider, if any. */
|
|
7
|
-
function persistedModelFor(providerName) {
|
|
8
|
-
if (!providerName)
|
|
9
|
-
return undefined;
|
|
10
|
-
return getSettings().providers?.[providerName]?.defaultModel;
|
|
11
|
-
}
|
|
12
|
-
function defaultReasoningBuilder(level) {
|
|
13
|
-
return level === "off" ? {} : { reasoning_effort: level };
|
|
14
|
-
}
|
|
15
|
-
function mergeCaps(settingsCaps, payloadCaps, modelIds) {
|
|
16
|
-
if (!settingsCaps)
|
|
17
|
-
return payloadCaps.size > 0 ? payloadCaps : undefined;
|
|
18
|
-
const out = new Map();
|
|
19
|
-
for (const id of modelIds) {
|
|
20
|
-
const s = settingsCaps.get(id);
|
|
21
|
-
const p = payloadCaps.get(id);
|
|
22
|
-
if (!s && !p)
|
|
23
|
-
continue;
|
|
24
|
-
out.set(id, {
|
|
25
|
-
reasoning: s?.reasoning ?? p?.reasoning,
|
|
26
|
-
contextWindow: s?.contextWindow ?? p?.contextWindow,
|
|
27
|
-
maxTokens: s?.maxTokens ?? p?.maxTokens,
|
|
28
|
-
echoReasoning: s?.echoReasoning ?? p?.echoReasoning,
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
return out.size > 0 ? out : undefined;
|
|
32
|
-
}
|
|
33
|
-
export default function agentBackend(ctx) {
|
|
34
|
-
const { bus } = ctx;
|
|
35
|
-
const config = ctx.call("config:get-shell-config") ?? {};
|
|
36
|
-
// Immutable settings snapshot; provider:register payloads merge against it.
|
|
37
|
-
const providerRegistry = new Map();
|
|
38
|
-
const settingsProviders = new Map();
|
|
39
|
-
for (const name of getProviderNames()) {
|
|
40
|
-
const p = resolveProvider(name);
|
|
41
|
-
if (p) {
|
|
42
|
-
providerRegistry.set(name, p);
|
|
43
|
-
settingsProviders.set(name, p);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
const providerHooks = new Map();
|
|
47
|
-
// Bakes model id into the hook so AgentMode.buildReasoningParams keeps
|
|
48
|
-
// its (level) signature while the hook can branch on model.
|
|
49
|
-
const bindReasoning = (shapeId, model) => {
|
|
50
|
-
const hook = providerHooks.get(shapeId)?.reasoningParams;
|
|
51
|
-
return hook ? (level) => hook(level, model) : defaultReasoningBuilder;
|
|
52
|
-
};
|
|
53
|
-
const buildModes = () => {
|
|
54
|
-
const allModes = [];
|
|
55
|
-
for (const [id, p] of providerRegistry) {
|
|
56
|
-
if (!p.apiKey)
|
|
57
|
-
continue;
|
|
58
|
-
const shapeId = p.reasoningShape ?? id;
|
|
59
|
-
for (const model of p.models) {
|
|
60
|
-
const mc = p.modelCapabilities?.get(model);
|
|
61
|
-
allModes.push({
|
|
62
|
-
model,
|
|
63
|
-
provider: id,
|
|
64
|
-
providerConfig: { apiKey: p.apiKey, baseURL: p.baseURL },
|
|
65
|
-
contextWindow: mc?.contextWindow ?? p.contextWindow,
|
|
66
|
-
maxTokens: mc?.maxTokens ?? (mc?.contextWindow ? Math.min(Math.floor(mc.contextWindow * 0.4), 65536) : undefined),
|
|
67
|
-
reasoning: mc?.reasoning,
|
|
68
|
-
supportsReasoningEffort: p.supportsReasoningEffort,
|
|
69
|
-
echoReasoning: mc?.echoReasoning,
|
|
70
|
-
buildReasoningParams: bindReasoning(shapeId, model),
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return allModes;
|
|
75
|
-
};
|
|
76
|
-
// Placeholder client — reconfigured at core:extensions-loaded. Any
|
|
77
|
-
// stream() call before then fails from the OpenAI SDK; start() won't
|
|
78
|
-
// wire the loop until we've resolved, so users never hit that path.
|
|
79
|
-
const llmClient = new LlmClient({ apiKey: "not-configured", model: "not-configured" });
|
|
80
|
-
ctx.define("llm:get-client", () => llmClient);
|
|
81
|
-
ctx.define("llm:invoke", (messages, opts) => {
|
|
82
|
-
return llmClient.complete({
|
|
83
|
-
messages: messages,
|
|
84
|
-
max_tokens: opts?.maxTokens,
|
|
85
|
-
model: opts?.model,
|
|
86
|
-
reasoning_effort: opts?.reasoningEffort,
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
let modes = [];
|
|
90
|
-
let initialModeIndex = 0;
|
|
91
|
-
let resolved = false;
|
|
92
|
-
// Gates late-registration reconcile so its config:switch-model emit doesn't misroute under a non-ash backend.
|
|
93
|
-
let ashActive = false;
|
|
94
|
-
bus.onPipe("config:get-initial-modes", () => ({ modes, initialModeIndex }));
|
|
95
|
-
// AgentLoop must be constructed *before* user extensions activate,
|
|
96
|
-
// because its ctor defines handlers (history:append, etc.) that
|
|
97
|
-
// extensions like superash call synchronously during their own
|
|
98
|
-
// activate. Advise-before-define works for advisers, but plain calls
|
|
99
|
-
// would hit a no-op stub.
|
|
100
|
-
const agentLoop = new AgentLoop({
|
|
101
|
-
bus,
|
|
102
|
-
llmClient,
|
|
103
|
-
handlers: { define: ctx.define, advise: ctx.advise, call: ctx.call, list: ctx.list },
|
|
104
|
-
modes,
|
|
105
|
-
initialModeIndex,
|
|
106
|
-
compositor: ctx.compositor,
|
|
107
|
-
instanceId: ctx.instanceId,
|
|
108
|
-
history: config.history,
|
|
109
|
-
});
|
|
110
|
-
let loadedExtensionNames = [];
|
|
111
|
-
bus.on("core:extensions-loaded", ({ names }) => {
|
|
112
|
-
loadedExtensionNames = names;
|
|
113
|
-
const settings = getSettings();
|
|
114
|
-
// If the user didn't pick a default, fall back to the first registered
|
|
115
|
-
// provider (built-in load order biases to openrouter → openai).
|
|
116
|
-
const providerName = config.provider ?? settings.defaultProvider
|
|
117
|
-
?? (providerRegistry.size > 0 ? providerRegistry.keys().next().value : undefined);
|
|
118
|
-
const activeProvider = providerName ? providerRegistry.get(providerName) ?? null : null;
|
|
119
|
-
// User's persisted defaultModel wins over the provider's declared
|
|
120
|
-
// default. Dynamic providers (openrouter) re-register with their
|
|
121
|
-
// hardcoded DEFAULT_MODELS[0] each startup, which would otherwise
|
|
122
|
-
// clobber the user's /model selection.
|
|
123
|
-
const effectiveApiKey = config.apiKey ?? activeProvider?.apiKey;
|
|
124
|
-
const effectiveBaseURL = config.baseURL ?? activeProvider?.baseURL;
|
|
125
|
-
const effectiveModel = config.model ?? persistedModelFor(providerName) ?? activeProvider?.defaultModel;
|
|
126
|
-
// No provider → don't register ash at all, so another backend (e.g.
|
|
127
|
-
// claude-code-bridge) can own activation. index.ts hard-fails only
|
|
128
|
-
// when no backend ended up registered.
|
|
129
|
-
if (!effectiveApiKey || !effectiveModel)
|
|
130
|
-
return;
|
|
131
|
-
modes = buildModes();
|
|
132
|
-
if (modes.length === 0)
|
|
133
|
-
modes = [{ model: effectiveModel }];
|
|
134
|
-
let foundIdx = modes.findIndex((m) => m.model === effectiveModel && (!activeProvider || m.provider === activeProvider.id));
|
|
135
|
-
// Persisted default may not be in the provider's curated list yet (e.g.
|
|
136
|
-
// openrouter's async catalog fetch hasn't returned). Prepend a stub so
|
|
137
|
-
// the initial config:set-modes activeIndex points at the real model —
|
|
138
|
-
// otherwise AgentLoop reconfigures llmClient back to modes[0].
|
|
139
|
-
if (foundIdx === -1 && activeProvider) {
|
|
140
|
-
modes = [
|
|
141
|
-
{
|
|
142
|
-
model: effectiveModel,
|
|
143
|
-
provider: activeProvider.id,
|
|
144
|
-
providerConfig: { apiKey: effectiveApiKey, baseURL: effectiveBaseURL },
|
|
145
|
-
supportsReasoningEffort: activeProvider.supportsReasoningEffort,
|
|
146
|
-
},
|
|
147
|
-
...modes,
|
|
148
|
-
];
|
|
149
|
-
foundIdx = 0;
|
|
150
|
-
}
|
|
151
|
-
initialModeIndex = Math.max(0, foundIdx);
|
|
152
|
-
llmClient.reconfigure({ apiKey: effectiveApiKey, baseURL: effectiveBaseURL, model: effectiveModel });
|
|
153
|
-
bus.emit("config:set-modes", { modes, activeIndex: initialModeIndex });
|
|
154
|
-
resolved = true;
|
|
155
|
-
bus.emit("agent:register-backend", {
|
|
156
|
-
name: "ash",
|
|
157
|
-
kill: () => {
|
|
158
|
-
ashActive = false;
|
|
159
|
-
bus.emit("command:unregister", { name: "/compact" });
|
|
160
|
-
bus.emit("command:unregister", { name: "/context" });
|
|
161
|
-
agentLoop.kill();
|
|
162
|
-
},
|
|
163
|
-
start: async () => {
|
|
164
|
-
agentLoop.wire();
|
|
165
|
-
ashActive = true;
|
|
166
|
-
bus.emit("command:register", {
|
|
167
|
-
name: "/compact",
|
|
168
|
-
description: "Compact conversation via the active compaction strategy",
|
|
169
|
-
handler: () => bus.emit("agent:compact-request", {}),
|
|
170
|
-
});
|
|
171
|
-
bus.emit("command:register", {
|
|
172
|
-
name: "/context",
|
|
173
|
-
description: "Show context budget usage",
|
|
174
|
-
handler: () => {
|
|
175
|
-
const stats = bus.emitPipe("context:get-stats", {
|
|
176
|
-
activeTokens: 0,
|
|
177
|
-
totalTokens: 0,
|
|
178
|
-
budgetTokens: 0,
|
|
179
|
-
});
|
|
180
|
-
const pct = stats.budgetTokens > 0
|
|
181
|
-
? Math.round((stats.activeTokens / stats.budgetTokens) * 100)
|
|
182
|
-
: 0;
|
|
183
|
-
bus.emit("ui:info", {
|
|
184
|
-
message: `Active context: ~${stats.activeTokens.toLocaleString()} tokens / ${stats.budgetTokens.toLocaleString()} budget (${pct}%)`,
|
|
185
|
-
});
|
|
186
|
-
},
|
|
187
|
-
});
|
|
188
|
-
bus.emit("agent:info", {
|
|
189
|
-
name: "ash",
|
|
190
|
-
version: PACKAGE_VERSION,
|
|
191
|
-
model: llmClient.model,
|
|
192
|
-
provider: modes[initialModeIndex]?.provider,
|
|
193
|
-
contextWindow: modes[initialModeIndex]?.contextWindow,
|
|
194
|
-
});
|
|
195
|
-
},
|
|
196
|
-
});
|
|
197
|
-
});
|
|
198
|
-
bus.on("provider:configure", ({ id, reasoningParams }) => {
|
|
199
|
-
const prev = providerHooks.get(id) ?? {};
|
|
200
|
-
if (reasoningParams !== undefined)
|
|
201
|
-
prev.reasoningParams = reasoningParams;
|
|
202
|
-
providerHooks.set(id, prev);
|
|
203
|
-
});
|
|
204
|
-
bus.on("provider:register", (p) => {
|
|
205
|
-
const rawModels = p.models ?? (p.defaultModel ? [p.defaultModel] : []);
|
|
206
|
-
const payloadModelIds = [];
|
|
207
|
-
const payloadCaps = new Map();
|
|
208
|
-
for (const m of rawModels) {
|
|
209
|
-
if (typeof m === "string") {
|
|
210
|
-
payloadModelIds.push(m);
|
|
211
|
-
}
|
|
212
|
-
else {
|
|
213
|
-
payloadModelIds.push(m.id);
|
|
214
|
-
payloadCaps.set(m.id, { reasoning: m.reasoning, contextWindow: m.contextWindow, maxTokens: m.maxTokens, echoReasoning: m.echoReasoning });
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
const settings = settingsProviders.get(p.id);
|
|
218
|
-
const modelIds = settings?.modelsExplicit && settings.models.length > 0 ? settings.models : payloadModelIds;
|
|
219
|
-
const mergedCaps = mergeCaps(settings?.modelCapabilities, payloadCaps, modelIds);
|
|
220
|
-
const merged = {
|
|
221
|
-
id: p.id,
|
|
222
|
-
apiKey: settings?.apiKey ?? p.apiKey,
|
|
223
|
-
baseURL: settings?.baseURL ?? p.baseURL,
|
|
224
|
-
defaultModel: settings?.defaultModel ?? p.defaultModel,
|
|
225
|
-
models: modelIds,
|
|
226
|
-
modelsExplicit: settings?.modelsExplicit ?? false,
|
|
227
|
-
contextWindow: settings?.contextWindow,
|
|
228
|
-
supportsReasoningEffort: settings?.supportsReasoningEffort ?? p.supportsReasoningEffort,
|
|
229
|
-
modelCapabilities: mergedCaps,
|
|
230
|
-
reasoningShape: settings?.reasoningShape,
|
|
231
|
-
};
|
|
232
|
-
providerRegistry.set(p.id, merged);
|
|
233
|
-
const addModes = modelIds.map((m) => {
|
|
234
|
-
const mc = mergedCaps?.get(m);
|
|
235
|
-
return {
|
|
236
|
-
model: m,
|
|
237
|
-
provider: p.id,
|
|
238
|
-
providerConfig: { apiKey: merged.apiKey ?? "", baseURL: merged.baseURL },
|
|
239
|
-
contextWindow: mc?.contextWindow,
|
|
240
|
-
maxTokens: mc?.maxTokens,
|
|
241
|
-
reasoning: mc?.reasoning,
|
|
242
|
-
supportsReasoningEffort: merged.supportsReasoningEffort,
|
|
243
|
-
echoReasoning: mc?.echoReasoning,
|
|
244
|
-
buildReasoningParams: bindReasoning(p.id, m),
|
|
245
|
-
};
|
|
246
|
-
});
|
|
247
|
-
bus.emit("config:add-modes", { modes: addModes });
|
|
248
|
-
// Late-registration reconcile: if this completes the user's persisted
|
|
249
|
-
// default (openrouter's async fetch delivers the full catalog after
|
|
250
|
-
// we've already fallen back to mode 0), quietly switch to it.
|
|
251
|
-
if (!resolved || !ashActive)
|
|
252
|
-
return;
|
|
253
|
-
const pendingProvider = getSettings().defaultProvider;
|
|
254
|
-
if (pendingProvider !== p.id)
|
|
255
|
-
return;
|
|
256
|
-
const pendingModel = persistedModelFor(pendingProvider);
|
|
257
|
-
if (pendingModel && modelIds.includes(pendingModel) && llmClient.model !== pendingModel) {
|
|
258
|
-
bus.emit("config:switch-model", { model: pendingModel });
|
|
259
|
-
}
|
|
260
|
-
});
|
|
261
|
-
bus.on("config:switch-provider", ({ provider: name }) => {
|
|
262
|
-
const p = providerRegistry.get(name);
|
|
263
|
-
if (!p) {
|
|
264
|
-
bus.emit("ui:error", { message: `Unknown provider: ${name}` });
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
if (!p.apiKey) {
|
|
268
|
-
bus.emit("ui:error", { message: `Provider "${name}" has no API key configured` });
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
const switchModel = p.defaultModel ?? p.models[0];
|
|
272
|
-
if (!switchModel) {
|
|
273
|
-
bus.emit("ui:error", { message: `Provider "${name}" has no models configured` });
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
llmClient.reconfigure({ apiKey: p.apiKey, baseURL: p.baseURL, model: switchModel });
|
|
277
|
-
const newModes = p.models.map((m) => {
|
|
278
|
-
const mc = p.modelCapabilities?.get(m);
|
|
279
|
-
return {
|
|
280
|
-
model: m,
|
|
281
|
-
provider: name,
|
|
282
|
-
providerConfig: { apiKey: p.apiKey, baseURL: p.baseURL },
|
|
283
|
-
contextWindow: mc?.contextWindow ?? p.contextWindow,
|
|
284
|
-
maxTokens: mc?.maxTokens ?? (mc?.contextWindow ? Math.min(Math.floor(mc.contextWindow * 0.4), 65536) : undefined),
|
|
285
|
-
reasoning: mc?.reasoning,
|
|
286
|
-
supportsReasoningEffort: p.supportsReasoningEffort,
|
|
287
|
-
echoReasoning: mc?.echoReasoning,
|
|
288
|
-
};
|
|
289
|
-
});
|
|
290
|
-
bus.emit("config:set-modes", { modes: newModes });
|
|
291
|
-
bus.emit("agent:info", { name: "ash", version: PACKAGE_VERSION, model: switchModel, provider: name, contextWindow: p.contextWindow });
|
|
292
|
-
bus.emit("ui:info", { message: `Switched to ${name} (${switchModel})` });
|
|
293
|
-
bus.emit("config:changed", {});
|
|
294
|
-
});
|
|
295
|
-
bus.onPipe("banner:collect", (e) => {
|
|
296
|
-
if (e.activeBackend && e.activeBackend !== "ash")
|
|
297
|
-
return e;
|
|
298
|
-
if (loadedExtensionNames.length > 0) {
|
|
299
|
-
e.sections.push({ label: "Extensions", items: [...loadedExtensionNames] });
|
|
300
|
-
}
|
|
301
|
-
const skills = discoverSkills(ctx.call("cwd") ?? process.cwd());
|
|
302
|
-
if (skills.length > 0) {
|
|
303
|
-
e.sections.push({ label: "Skills", items: skills.map((s) => s.name) });
|
|
304
|
-
}
|
|
305
|
-
return e;
|
|
306
|
-
});
|
|
307
|
-
}
|