agent-sh 0.12.26 → 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 +44 -100
- package/dist/agent/conversation-state.d.ts +9 -0
- package/dist/agent/conversation-state.js +38 -1
- 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} +114 -5
- 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 +17 -5
- package/dist/utils/floating-panel.js +218 -70
- 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 +364 -0
- 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 +35 -10
- 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 +174 -33
- package/examples/extensions/tmux-pane.ts +6 -4
- package/examples/extensions/tunnel-vision.ts +405 -0
- package/examples/extensions/user-shell.ts +1 -1
- package/examples/extensions/web-access.ts +8 -113
- 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
package/dist/agent/index.js
CHANGED
|
@@ -1,9 +1,360 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { AgentLoop } from "./agent-loop.js";
|
|
2
|
+
import { LlmClient } from "../utils/llm-client.js";
|
|
3
|
+
import { createLlmFacade } from "../utils/llm-facade.js";
|
|
4
|
+
import { resolveProvider, getProviderNames, getSettings } from "../core/settings.js";
|
|
5
|
+
import { PACKAGE_VERSION } from "../utils/package-version.js";
|
|
6
|
+
import { discoverSkills } from "./skills.js";
|
|
7
|
+
import { resolveApiKey } from "../cli/auth/keys.js";
|
|
8
|
+
import activateOpenrouter from "./providers/openrouter.js";
|
|
9
|
+
import activateOpenai from "./providers/openai.js";
|
|
10
|
+
import activateOpenaiCompatible from "./providers/openai-compatible.js";
|
|
11
|
+
import activateDeepseek from "./providers/deepseek.js";
|
|
12
|
+
function persistedModelFor(providerName) {
|
|
13
|
+
if (!providerName)
|
|
14
|
+
return undefined;
|
|
15
|
+
return getSettings().providers?.[providerName]?.defaultModel;
|
|
16
|
+
}
|
|
17
|
+
function defaultReasoningBuilder(level) {
|
|
18
|
+
return level === "off" ? {} : { reasoning_effort: level };
|
|
19
|
+
}
|
|
20
|
+
function mergeCaps(settingsCaps, payloadCaps, modelIds) {
|
|
21
|
+
if (!settingsCaps)
|
|
22
|
+
return payloadCaps.size > 0 ? payloadCaps : undefined;
|
|
23
|
+
const out = new Map();
|
|
24
|
+
for (const id of modelIds) {
|
|
25
|
+
const s = settingsCaps.get(id);
|
|
26
|
+
const p = payloadCaps.get(id);
|
|
27
|
+
if (!s && !p)
|
|
28
|
+
continue;
|
|
29
|
+
out.set(id, {
|
|
30
|
+
reasoning: s?.reasoning ?? p?.reasoning,
|
|
31
|
+
contextWindow: s?.contextWindow ?? p?.contextWindow,
|
|
32
|
+
maxTokens: s?.maxTokens ?? p?.maxTokens,
|
|
33
|
+
echoReasoning: s?.echoReasoning ?? p?.echoReasoning,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return out.size > 0 ? out : undefined;
|
|
37
|
+
}
|
|
38
|
+
export default function agentBackend(ctx) {
|
|
39
|
+
const { bus } = ctx;
|
|
40
|
+
const config = ctx.call("config:get-app-config") ?? {};
|
|
41
|
+
const agentSurface = {
|
|
42
|
+
llm: createLlmFacade({ list: ctx.list, call: ctx.call }),
|
|
43
|
+
providers: {
|
|
44
|
+
configure: (id, configureOpts) => bus.emit("provider:configure", { id, ...configureOpts }),
|
|
45
|
+
},
|
|
46
|
+
registerTool: (tool) => bus.emit("agent:register-tool", { tool, extensionName: "" }),
|
|
47
|
+
unregisterTool: (name) => bus.emit("agent:unregister-tool", { name }),
|
|
48
|
+
adviseTool: (name, advisor) => ctx.advise(`tool:${name}`, advisor),
|
|
49
|
+
adviseToolSchema: (name, advisor) => ctx.advise(`tool:${name}:schema`, advisor),
|
|
50
|
+
getTools: () => bus.emitPipe("agent:get-tools", { tools: [] }).tools,
|
|
51
|
+
registerInstruction: (name, text) => bus.emit("agent:register-instruction", { name, text, extensionName: "" }),
|
|
52
|
+
removeInstruction: (name) => bus.emit("agent:remove-instruction", { name }),
|
|
53
|
+
adviseInstruction: (name, advisor) => ctx.advise(`instruction:${name}`, advisor),
|
|
54
|
+
registerSkill: (name, description, filePath) => bus.emit("agent:register-skill", { name, description, filePath, extensionName: "" }),
|
|
55
|
+
removeSkill: (name) => bus.emit("agent:remove-skill", { name }),
|
|
56
|
+
adviseSkill: (name, advisor) => ctx.advise(`skill:${name}:view`, advisor),
|
|
57
|
+
registerContextProducer: (_name, producer, producerOpts) => {
|
|
58
|
+
const handlerName = producerOpts?.mode === "per-query"
|
|
59
|
+
? "query-context:build"
|
|
60
|
+
: "dynamic-context:build";
|
|
61
|
+
return ctx.advise(handlerName, (next) => {
|
|
62
|
+
const base = next();
|
|
63
|
+
const part = producer();
|
|
64
|
+
if (!part)
|
|
65
|
+
return base;
|
|
66
|
+
const trimmed = part.trim();
|
|
67
|
+
if (!trimmed)
|
|
68
|
+
return base;
|
|
69
|
+
return base ? `${base}\n\n${trimmed}` : trimmed;
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
ctx.agent = agentSurface;
|
|
74
|
+
// Immutable settings snapshot; provider:register payloads merge against it.
|
|
75
|
+
const providerRegistry = new Map();
|
|
76
|
+
const settingsProviders = new Map();
|
|
77
|
+
for (const name of getProviderNames()) {
|
|
78
|
+
const p = resolveProvider(name);
|
|
79
|
+
if (p) {
|
|
80
|
+
providerRegistry.set(name, p);
|
|
81
|
+
settingsProviders.set(name, p);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const providerHooks = new Map();
|
|
85
|
+
// Bakes model id into the hook so AgentMode.buildReasoningParams keeps
|
|
86
|
+
// its (level) signature while the hook can branch on model.
|
|
87
|
+
const bindReasoning = (shapeId, model) => {
|
|
88
|
+
const hook = providerHooks.get(shapeId)?.reasoningParams;
|
|
89
|
+
return hook ? (level) => hook(level, model) : defaultReasoningBuilder;
|
|
90
|
+
};
|
|
91
|
+
const buildModes = () => {
|
|
92
|
+
const allModes = [];
|
|
93
|
+
for (const [id, p] of providerRegistry) {
|
|
94
|
+
if (!p.apiKey)
|
|
95
|
+
continue;
|
|
96
|
+
const shapeId = p.reasoningShape ?? id;
|
|
97
|
+
for (const model of p.models) {
|
|
98
|
+
const mc = p.modelCapabilities?.get(model);
|
|
99
|
+
allModes.push({
|
|
100
|
+
model,
|
|
101
|
+
provider: id,
|
|
102
|
+
providerConfig: { apiKey: p.apiKey, baseURL: p.baseURL },
|
|
103
|
+
contextWindow: mc?.contextWindow ?? p.contextWindow,
|
|
104
|
+
maxTokens: mc?.maxTokens ?? (mc?.contextWindow ? Math.min(Math.floor(mc.contextWindow * 0.4), 65536) : undefined),
|
|
105
|
+
reasoning: mc?.reasoning,
|
|
106
|
+
supportsReasoningEffort: p.supportsReasoningEffort,
|
|
107
|
+
echoReasoning: mc?.echoReasoning,
|
|
108
|
+
buildReasoningParams: bindReasoning(shapeId, model),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return allModes;
|
|
113
|
+
};
|
|
114
|
+
// Placeholder client — reconfigured at core:extensions-loaded. Any
|
|
115
|
+
// stream() call before then fails from the OpenAI SDK; start() won't
|
|
116
|
+
// wire the loop until we've resolved, so users never hit that path.
|
|
117
|
+
const llmClient = new LlmClient({ apiKey: "not-configured", model: "not-configured" });
|
|
118
|
+
ctx.define("llm:get-client", () => llmClient);
|
|
119
|
+
ctx.define("llm:invoke", (messages, opts) => {
|
|
120
|
+
return llmClient.complete({
|
|
121
|
+
messages: messages,
|
|
122
|
+
max_tokens: opts?.maxTokens,
|
|
123
|
+
model: opts?.model,
|
|
124
|
+
reasoning_effort: opts?.reasoningEffort,
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
let modes = [];
|
|
128
|
+
let initialModeIndex = 0;
|
|
129
|
+
let resolved = false;
|
|
130
|
+
// Gates late-registration reconcile so its config:switch-model emit doesn't misroute under a non-ash backend.
|
|
131
|
+
let ashActive = false;
|
|
132
|
+
bus.onPipe("config:get-initial-modes", () => ({ modes, initialModeIndex }));
|
|
133
|
+
// AgentLoop must be constructed *before* user extensions activate,
|
|
134
|
+
// because its ctor defines handlers (history:append, etc.) that
|
|
135
|
+
// extensions like superash call synchronously during their own
|
|
136
|
+
// activate. Advise-before-define works for advisers, but plain calls
|
|
137
|
+
// would hit a no-op stub.
|
|
138
|
+
const agentLoop = new AgentLoop({
|
|
139
|
+
bus,
|
|
140
|
+
llmClient,
|
|
141
|
+
handlers: { define: ctx.define, advise: ctx.advise, call: ctx.call, list: ctx.list },
|
|
142
|
+
modes,
|
|
143
|
+
initialModeIndex,
|
|
144
|
+
compositor: ctx.shell?.compositor,
|
|
145
|
+
instanceId: ctx.instanceId,
|
|
146
|
+
history: config.history,
|
|
147
|
+
});
|
|
148
|
+
let loadedExtensionNames = [];
|
|
149
|
+
bus.on("core:extensions-loaded", ({ names }) => {
|
|
150
|
+
loadedExtensionNames = names;
|
|
151
|
+
const settings = getSettings();
|
|
152
|
+
// If the user didn't pick a default, fall back to the first registered
|
|
153
|
+
// provider (built-in load order biases to openrouter → openai).
|
|
154
|
+
const providerName = config.provider ?? settings.defaultProvider
|
|
155
|
+
?? (providerRegistry.size > 0 ? providerRegistry.keys().next().value : undefined);
|
|
156
|
+
const activeProvider = providerName ? providerRegistry.get(providerName) ?? null : null;
|
|
157
|
+
// User's persisted defaultModel wins over the provider's declared
|
|
158
|
+
// default. Dynamic providers (openrouter) re-register with their
|
|
159
|
+
// hardcoded DEFAULT_MODELS[0] each startup, which would otherwise
|
|
160
|
+
// clobber the user's /model selection.
|
|
161
|
+
const effectiveApiKey = config.apiKey ?? activeProvider?.apiKey;
|
|
162
|
+
const effectiveBaseURL = config.baseURL ?? activeProvider?.baseURL;
|
|
163
|
+
const effectiveModel = config.model ?? persistedModelFor(providerName) ?? activeProvider?.defaultModel;
|
|
164
|
+
// No provider → don't register ash at all, so another backend (e.g.
|
|
165
|
+
// claude-code-bridge) can own activation. index.ts hard-fails only
|
|
166
|
+
// when no backend ended up registered.
|
|
167
|
+
if (!effectiveApiKey || !effectiveModel)
|
|
168
|
+
return;
|
|
169
|
+
modes = buildModes();
|
|
170
|
+
if (modes.length === 0)
|
|
171
|
+
modes = [{ model: effectiveModel }];
|
|
172
|
+
let foundIdx = modes.findIndex((m) => m.model === effectiveModel && (!activeProvider || m.provider === activeProvider.id));
|
|
173
|
+
// Persisted default may not be in the provider's curated list yet (e.g.
|
|
174
|
+
// openrouter's async catalog fetch hasn't returned). Prepend a stub so
|
|
175
|
+
// the initial config:set-modes activeIndex points at the real model —
|
|
176
|
+
// otherwise AgentLoop reconfigures llmClient back to modes[0].
|
|
177
|
+
if (foundIdx === -1 && activeProvider) {
|
|
178
|
+
modes = [
|
|
179
|
+
{
|
|
180
|
+
model: effectiveModel,
|
|
181
|
+
provider: activeProvider.id,
|
|
182
|
+
providerConfig: { apiKey: effectiveApiKey, baseURL: effectiveBaseURL },
|
|
183
|
+
supportsReasoningEffort: activeProvider.supportsReasoningEffort,
|
|
184
|
+
},
|
|
185
|
+
...modes,
|
|
186
|
+
];
|
|
187
|
+
foundIdx = 0;
|
|
188
|
+
}
|
|
189
|
+
initialModeIndex = Math.max(0, foundIdx);
|
|
190
|
+
llmClient.reconfigure({ apiKey: effectiveApiKey, baseURL: effectiveBaseURL, model: effectiveModel });
|
|
191
|
+
bus.emit("config:set-modes", { modes, activeIndex: initialModeIndex });
|
|
192
|
+
resolved = true;
|
|
193
|
+
bus.emit("agent:register-backend", {
|
|
194
|
+
name: "ash",
|
|
195
|
+
kill: () => {
|
|
196
|
+
ashActive = false;
|
|
197
|
+
bus.emit("command:unregister", { name: "/compact" });
|
|
198
|
+
bus.emit("command:unregister", { name: "/context" });
|
|
199
|
+
agentLoop.kill();
|
|
200
|
+
},
|
|
201
|
+
start: async () => {
|
|
202
|
+
agentLoop.wire();
|
|
203
|
+
ashActive = true;
|
|
204
|
+
bus.emit("command:register", {
|
|
205
|
+
name: "/compact",
|
|
206
|
+
description: "Compact conversation via the active compaction strategy",
|
|
207
|
+
handler: () => bus.emit("agent:compact-request", {}),
|
|
208
|
+
});
|
|
209
|
+
bus.emit("command:register", {
|
|
210
|
+
name: "/context",
|
|
211
|
+
description: "Show context budget usage",
|
|
212
|
+
handler: () => {
|
|
213
|
+
const stats = bus.emitPipe("context:get-stats", {
|
|
214
|
+
activeTokens: 0,
|
|
215
|
+
totalTokens: 0,
|
|
216
|
+
budgetTokens: 0,
|
|
217
|
+
});
|
|
218
|
+
const pct = stats.budgetTokens > 0
|
|
219
|
+
? Math.round((stats.activeTokens / stats.budgetTokens) * 100)
|
|
220
|
+
: 0;
|
|
221
|
+
bus.emit("ui:info", {
|
|
222
|
+
message: `Active context: ~${stats.activeTokens.toLocaleString()} tokens / ${stats.budgetTokens.toLocaleString()} budget (${pct}%)`,
|
|
223
|
+
});
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
bus.emit("agent:info", {
|
|
227
|
+
name: "ash",
|
|
228
|
+
version: PACKAGE_VERSION,
|
|
229
|
+
model: llmClient.model,
|
|
230
|
+
provider: modes[initialModeIndex]?.provider,
|
|
231
|
+
contextWindow: modes[initialModeIndex]?.contextWindow,
|
|
232
|
+
});
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
bus.on("provider:configure", ({ id, reasoningParams }) => {
|
|
237
|
+
const prev = providerHooks.get(id) ?? {};
|
|
238
|
+
if (reasoningParams !== undefined)
|
|
239
|
+
prev.reasoningParams = reasoningParams;
|
|
240
|
+
providerHooks.set(id, prev);
|
|
241
|
+
});
|
|
242
|
+
bus.on("provider:register", (p) => {
|
|
243
|
+
const rawModels = p.models ?? (p.defaultModel ? [p.defaultModel] : []);
|
|
244
|
+
const payloadModelIds = [];
|
|
245
|
+
const payloadCaps = new Map();
|
|
246
|
+
for (const m of rawModels) {
|
|
247
|
+
if (typeof m === "string") {
|
|
248
|
+
payloadModelIds.push(m);
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
payloadModelIds.push(m.id);
|
|
252
|
+
payloadCaps.set(m.id, { reasoning: m.reasoning, contextWindow: m.contextWindow, maxTokens: m.maxTokens, echoReasoning: m.echoReasoning });
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const settings = settingsProviders.get(p.id);
|
|
256
|
+
const modelIds = settings?.modelsExplicit && settings.models.length > 0 ? settings.models : payloadModelIds;
|
|
257
|
+
const mergedCaps = mergeCaps(settings?.modelCapabilities, payloadCaps, modelIds);
|
|
258
|
+
const merged = {
|
|
259
|
+
id: p.id,
|
|
260
|
+
apiKey: settings?.apiKey ?? p.apiKey,
|
|
261
|
+
baseURL: settings?.baseURL ?? p.baseURL,
|
|
262
|
+
defaultModel: settings?.defaultModel ?? p.defaultModel,
|
|
263
|
+
models: modelIds,
|
|
264
|
+
modelsExplicit: settings?.modelsExplicit ?? false,
|
|
265
|
+
contextWindow: settings?.contextWindow,
|
|
266
|
+
supportsReasoningEffort: settings?.supportsReasoningEffort ?? p.supportsReasoningEffort,
|
|
267
|
+
modelCapabilities: mergedCaps,
|
|
268
|
+
reasoningShape: settings?.reasoningShape,
|
|
269
|
+
};
|
|
270
|
+
providerRegistry.set(p.id, merged);
|
|
271
|
+
const addModes = modelIds.map((m) => {
|
|
272
|
+
const mc = mergedCaps?.get(m);
|
|
273
|
+
return {
|
|
274
|
+
model: m,
|
|
275
|
+
provider: p.id,
|
|
276
|
+
providerConfig: { apiKey: merged.apiKey ?? "", baseURL: merged.baseURL },
|
|
277
|
+
contextWindow: mc?.contextWindow,
|
|
278
|
+
maxTokens: mc?.maxTokens,
|
|
279
|
+
reasoning: mc?.reasoning,
|
|
280
|
+
supportsReasoningEffort: merged.supportsReasoningEffort,
|
|
281
|
+
echoReasoning: mc?.echoReasoning,
|
|
282
|
+
buildReasoningParams: bindReasoning(p.id, m),
|
|
283
|
+
};
|
|
284
|
+
});
|
|
285
|
+
bus.emit("config:add-modes", { modes: addModes });
|
|
286
|
+
// Late-registration reconcile: if this completes the user's persisted
|
|
287
|
+
// default (openrouter's async fetch delivers the full catalog after
|
|
288
|
+
// we've already fallen back to mode 0), quietly switch to it.
|
|
289
|
+
if (!resolved || !ashActive)
|
|
290
|
+
return;
|
|
291
|
+
const pendingProvider = getSettings().defaultProvider;
|
|
292
|
+
if (pendingProvider !== p.id)
|
|
293
|
+
return;
|
|
294
|
+
const pendingModel = persistedModelFor(pendingProvider);
|
|
295
|
+
if (pendingModel && modelIds.includes(pendingModel) && llmClient.model !== pendingModel) {
|
|
296
|
+
bus.emit("config:switch-model", { model: pendingModel });
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
bus.on("config:switch-provider", ({ provider: name }) => {
|
|
300
|
+
const p = providerRegistry.get(name);
|
|
301
|
+
if (!p) {
|
|
302
|
+
bus.emit("ui:error", { message: `Unknown provider: ${name}` });
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (!p.apiKey) {
|
|
306
|
+
bus.emit("ui:error", { message: `Provider "${name}" has no API key configured` });
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const switchModel = p.defaultModel ?? p.models[0];
|
|
310
|
+
if (!switchModel) {
|
|
311
|
+
bus.emit("ui:error", { message: `Provider "${name}" has no models configured` });
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
llmClient.reconfigure({ apiKey: p.apiKey, baseURL: p.baseURL, model: switchModel });
|
|
315
|
+
const newModes = p.models.map((m) => {
|
|
316
|
+
const mc = p.modelCapabilities?.get(m);
|
|
317
|
+
return {
|
|
318
|
+
model: m,
|
|
319
|
+
provider: name,
|
|
320
|
+
providerConfig: { apiKey: p.apiKey, baseURL: p.baseURL },
|
|
321
|
+
contextWindow: mc?.contextWindow ?? p.contextWindow,
|
|
322
|
+
maxTokens: mc?.maxTokens ?? (mc?.contextWindow ? Math.min(Math.floor(mc.contextWindow * 0.4), 65536) : undefined),
|
|
323
|
+
reasoning: mc?.reasoning,
|
|
324
|
+
supportsReasoningEffort: p.supportsReasoningEffort,
|
|
325
|
+
echoReasoning: mc?.echoReasoning,
|
|
326
|
+
};
|
|
327
|
+
});
|
|
328
|
+
bus.emit("config:set-modes", { modes: newModes });
|
|
329
|
+
bus.emit("agent:info", { name: "ash", version: PACKAGE_VERSION, model: switchModel, provider: name, contextWindow: p.contextWindow });
|
|
330
|
+
bus.emit("ui:info", { message: `Switched to ${name} (${switchModel})` });
|
|
331
|
+
bus.emit("config:changed", {});
|
|
332
|
+
});
|
|
333
|
+
bus.onPipe("banner:collect", (e) => {
|
|
334
|
+
if (e.activeBackend && e.activeBackend !== "ash")
|
|
335
|
+
return e;
|
|
336
|
+
if (loadedExtensionNames.length > 0) {
|
|
337
|
+
e.sections.push({ label: "Extensions", items: [...loadedExtensionNames] });
|
|
338
|
+
}
|
|
339
|
+
const skills = discoverSkills(ctx.call("cwd") ?? process.cwd());
|
|
340
|
+
if (skills.length > 0) {
|
|
341
|
+
e.sections.push({ label: "Skills", items: skills.map((s) => s.name) });
|
|
342
|
+
}
|
|
343
|
+
return e;
|
|
344
|
+
});
|
|
345
|
+
}
|
|
7
346
|
export { AgentLoop } from "./agent-loop.js";
|
|
8
347
|
export { ToolRegistry } from "./tool-registry.js";
|
|
9
348
|
export { runSubagent } from "./subagent.js";
|
|
349
|
+
/** Activate the ash backend and any provider whose key is configured. */
|
|
350
|
+
export function activateAgent(ctx) {
|
|
351
|
+
agentBackend(ctx);
|
|
352
|
+
const agentCtx = ctx;
|
|
353
|
+
if (resolveApiKey("openrouter").key)
|
|
354
|
+
activateOpenrouter(agentCtx);
|
|
355
|
+
if (resolveApiKey("openai").key && !process.env.OPENAI_BASE_URL)
|
|
356
|
+
activateOpenai(agentCtx);
|
|
357
|
+
if (process.env.OPENAI_BASE_URL)
|
|
358
|
+
activateOpenaiCompatible(agentCtx);
|
|
359
|
+
activateDeepseek(agentCtx);
|
|
360
|
+
}
|
|
@@ -32,6 +32,13 @@ export interface NuclearEntry {
|
|
|
32
32
|
* survives into summaries. Displayed as `{why}` in formatNuclearLine.
|
|
33
33
|
*/
|
|
34
34
|
why?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Optional parent pointer for tree-shaped history. The default
|
|
37
|
+
* HistoryFile adapter ignores this and treats the file as linear;
|
|
38
|
+
* tree-aware HistoryAdapter implementations use it to fork and to
|
|
39
|
+
* walk a single path on resume.
|
|
40
|
+
*/
|
|
41
|
+
parentSeq?: number;
|
|
35
42
|
}
|
|
36
43
|
/**
|
|
37
44
|
* Create a session-start marker entry. Markers use seq=0 by default —
|
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* to enabled. The hook always attaches; provider registration via env
|
|
5
5
|
* is opt-in alongside any settings.json entry.
|
|
6
6
|
*/
|
|
7
|
-
import type {
|
|
8
|
-
export default function activate(ctx:
|
|
7
|
+
import type { AgentContext } from "../host-types.js";
|
|
8
|
+
export default function activate(ctx: AgentContext): void;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { resolveApiKey } from "../../cli/auth/keys.js";
|
|
1
2
|
const BASE_URL = "https://api.deepseek.com";
|
|
2
3
|
const DEFAULT_MODELS = [
|
|
3
|
-
{ id: "deepseek-v4-flash", reasoning: true, echoReasoning: true },
|
|
4
|
-
{ id: "deepseek-v4-pro", reasoning: true, echoReasoning: true },
|
|
4
|
+
{ id: "deepseek-v4-flash", reasoning: true, echoReasoning: true, contextWindow: 1_000_000 },
|
|
5
|
+
{ id: "deepseek-v4-pro", reasoning: true, echoReasoning: true, contextWindow: 1_000_000 },
|
|
5
6
|
];
|
|
6
7
|
function buildReasoningParams(level, _model) {
|
|
7
8
|
return level === "off"
|
|
@@ -9,8 +10,8 @@ function buildReasoningParams(level, _model) {
|
|
|
9
10
|
: { thinking: { type: "enabled" }, reasoning_effort: level };
|
|
10
11
|
}
|
|
11
12
|
export default function activate(ctx) {
|
|
12
|
-
ctx.providers.configure("deepseek", { reasoningParams: buildReasoningParams });
|
|
13
|
-
const apiKey =
|
|
13
|
+
ctx.agent.providers.configure("deepseek", { reasoningParams: buildReasoningParams });
|
|
14
|
+
const apiKey = resolveApiKey("deepseek").key;
|
|
14
15
|
if (!apiKey)
|
|
15
16
|
return;
|
|
16
17
|
ctx.bus.emit("provider:register", {
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
* Studio, vLLM, llama.cpp, …). No reasoning hook — the right shape depends
|
|
4
4
|
* on which model the server is serving; user extensions can add one.
|
|
5
5
|
*/
|
|
6
|
-
import type {
|
|
7
|
-
export default function activate(ctx:
|
|
6
|
+
import type { AgentContext } from "../host-types.js";
|
|
7
|
+
export default function activate(ctx: AgentContext): void;
|
|
@@ -3,5 +3,5 @@
|
|
|
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
5
|
*/
|
|
6
|
-
import type {
|
|
7
|
-
export default function activate(ctx:
|
|
6
|
+
import type { AgentContext } from "../host-types.js";
|
|
7
|
+
export default function activate(ctx: AgentContext): void;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveApiKey } from "../../cli/auth/keys.js";
|
|
1
2
|
const CLOUD_MODELS = [
|
|
2
3
|
{ id: "gpt-5", reasoning: true },
|
|
3
4
|
{ id: "gpt-4.1", reasoning: false },
|
|
@@ -24,12 +25,12 @@ function buildReasoningParams(level, model) {
|
|
|
24
25
|
return off ? { reasoning_effort: off } : {};
|
|
25
26
|
}
|
|
26
27
|
export default function activate(ctx) {
|
|
27
|
-
const apiKey =
|
|
28
|
+
const apiKey = resolveApiKey("openai").key;
|
|
28
29
|
if (!apiKey)
|
|
29
30
|
return;
|
|
30
31
|
if (process.env.OPENAI_BASE_URL)
|
|
31
32
|
return; // openai-compatible handles this
|
|
32
|
-
ctx.providers.configure("openai", { reasoningParams: buildReasoningParams });
|
|
33
|
+
ctx.agent.providers.configure("openai", { reasoningParams: buildReasoningParams });
|
|
33
34
|
ctx.bus.emit("provider:register", {
|
|
34
35
|
id: "openai",
|
|
35
36
|
apiKey,
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
* Registers curated defaults synchronously so the first query works, then
|
|
4
4
|
* fetches the full catalog to populate /model autocomplete.
|
|
5
5
|
*/
|
|
6
|
-
import type {
|
|
7
|
-
export default function activate(ctx:
|
|
6
|
+
import type { AgentContext } from "../host-types.js";
|
|
7
|
+
export default function activate(ctx: AgentContext): void;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { getSettings } from "../../settings.js";
|
|
1
|
+
import { getSettings } from "../../core/settings.js";
|
|
2
|
+
import { resolveApiKey } from "../../cli/auth/keys.js";
|
|
2
3
|
const BASE_URL = "https://openrouter.ai/api/v1";
|
|
3
4
|
const DEFAULT_MODELS = ["deepseek/deepseek-v4-flash"];
|
|
4
5
|
// Built-in defaults for models requiring reasoning_content echoed back
|
|
@@ -14,10 +15,10 @@ function buildReasoningParams(level, _model) {
|
|
|
14
15
|
: { reasoning: { effort: level } };
|
|
15
16
|
}
|
|
16
17
|
export default function activate(ctx) {
|
|
17
|
-
const apiKey =
|
|
18
|
+
const apiKey = resolveApiKey("openrouter").key;
|
|
18
19
|
if (!apiKey)
|
|
19
20
|
return;
|
|
20
|
-
ctx.providers.configure("openrouter", { reasoningParams: buildReasoningParams });
|
|
21
|
+
ctx.agent.providers.configure("openrouter", { reasoningParams: buildReasoningParams });
|
|
21
22
|
ctx.bus.emit("provider:register", {
|
|
22
23
|
id: "openrouter",
|
|
23
24
|
apiKey,
|
package/dist/agent/skills.js
CHANGED
|
@@ -13,20 +13,64 @@
|
|
|
13
13
|
import * as fs from "node:fs";
|
|
14
14
|
import * as path from "node:path";
|
|
15
15
|
import * as os from "node:os";
|
|
16
|
-
import { CONFIG_DIR, getSettings } from "../settings.js";
|
|
17
|
-
/** Parse YAML frontmatter from a SKILL.md file.
|
|
16
|
+
import { CONFIG_DIR, getSettings } from "../core/settings.js";
|
|
17
|
+
/** Parse YAML frontmatter from a SKILL.md file. Supports inline scalars
|
|
18
|
+
* and block scalars (`>`, `>-`, `|`, `|-`) for multi-line descriptions. */
|
|
18
19
|
function parseFrontmatter(content) {
|
|
19
20
|
const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/);
|
|
20
21
|
if (!match)
|
|
21
22
|
return null;
|
|
22
23
|
const meta = {};
|
|
23
|
-
|
|
24
|
+
const lines = match[1].split("\n");
|
|
25
|
+
let i = 0;
|
|
26
|
+
while (i < lines.length) {
|
|
27
|
+
const line = lines[i];
|
|
28
|
+
const indent = line.length - line.trimStart().length;
|
|
24
29
|
const colon = line.indexOf(":");
|
|
25
|
-
if (colon > 0) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
if (colon <= 0 || indent > 0) {
|
|
31
|
+
i++;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const key = line.slice(0, colon).trim();
|
|
35
|
+
const rawValue = line.slice(colon + 1).trim();
|
|
36
|
+
const blockStyle = rawValue.match(/^([>|])([+-]?)\s*$/);
|
|
37
|
+
if (!blockStyle) {
|
|
38
|
+
meta[key] = rawValue;
|
|
39
|
+
i++;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const folded = blockStyle[1] === ">";
|
|
43
|
+
const chomp = blockStyle[2];
|
|
44
|
+
const body = [];
|
|
45
|
+
let blockIndent = -1;
|
|
46
|
+
let j = i + 1;
|
|
47
|
+
while (j < lines.length) {
|
|
48
|
+
const next = lines[j];
|
|
49
|
+
if (next.trim() === "") {
|
|
50
|
+
body.push("");
|
|
51
|
+
j++;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const ind = next.length - next.trimStart().length;
|
|
55
|
+
if (blockIndent === -1)
|
|
56
|
+
blockIndent = ind;
|
|
57
|
+
if (ind < blockIndent)
|
|
58
|
+
break;
|
|
59
|
+
body.push(next.slice(blockIndent));
|
|
60
|
+
j++;
|
|
61
|
+
}
|
|
62
|
+
let end = body.length;
|
|
63
|
+
if (chomp !== "+") {
|
|
64
|
+
while (end > 0 && body[end - 1] === "")
|
|
65
|
+
end--;
|
|
66
|
+
if (chomp !== "-" && end < body.length)
|
|
67
|
+
end++;
|
|
29
68
|
}
|
|
69
|
+
const kept = body.slice(0, end);
|
|
70
|
+
meta[key] = folded
|
|
71
|
+
? kept.join(" ").replace(/\s+/g, " ").trim()
|
|
72
|
+
: kept.join("\n");
|
|
73
|
+
i = j;
|
|
30
74
|
}
|
|
31
75
|
return { meta, body: match[2] };
|
|
32
76
|
}
|
package/dist/agent/subagent.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Used by the subagent extension to delegate tasks from the main agent.
|
|
11
11
|
*/
|
|
12
|
-
import type { EventBus } from "../event-bus.js";
|
|
12
|
+
import type { EventBus } from "../core/event-bus.js";
|
|
13
13
|
import type { LlmClient } from "../utils/llm-client.js";
|
|
14
14
|
import type { ToolDefinition } from "./types.js";
|
|
15
15
|
export interface SubagentOptions {
|
|
@@ -11,10 +11,10 @@ export function formatSkillsBlock(skills) {
|
|
|
11
11
|
if (skills.length === 0)
|
|
12
12
|
return "";
|
|
13
13
|
return "# Available Skills\n\n"
|
|
14
|
-
+ "Load a skill's full content
|
|
14
|
+
+ "Load a skill's full content from its file path with your file-reading tool when needed.\n\n"
|
|
15
15
|
+ skills.map(s => `- **${s.name}**: ${s.description}\n Path: ${s.filePath}`).join("\n\n");
|
|
16
16
|
}
|
|
17
|
-
import { CONFIG_DIR } from "../settings.js";
|
|
17
|
+
import { CONFIG_DIR } from "../core/settings.js";
|
|
18
18
|
const GLOBAL_AGENTS_MD = path.join(CONFIG_DIR, "AGENTS.md");
|
|
19
19
|
// ── File caches ─────────────────────────────────────────────────────
|
|
20
20
|
// Convention files (CLAUDE.md/AGENT.md) are walked synchronously from
|
|
@@ -88,24 +88,21 @@ function loadConventionFiles(dir) {
|
|
|
88
88
|
* Static system prompt — identical across all queries, cacheable.
|
|
89
89
|
* Contains only identity and behavioral instructions.
|
|
90
90
|
*/
|
|
91
|
-
export const STATIC_SYSTEM_PROMPT = `You are ash, an AI coding assistant running inside agent-sh
|
|
92
|
-
You have access to the user's shell environment and can read, write, and execute code.
|
|
93
|
-
You share the user's working directory, environment variables, and shell history.
|
|
94
|
-
agent-sh documentation is at ${path.join(CODE_DIR, "docs")} — start with README.md for an index. Read the docs when you need to understand how the runtime works.
|
|
91
|
+
export const STATIC_SYSTEM_PROMPT = `You are ash, an AI coding assistant running inside agent-sh — a composable agent runtime with a small core and everything else, including the shell integration, layered on as extensions.
|
|
95
92
|
|
|
96
|
-
|
|
97
|
-
bash, read_file, grep, glob, ls, edit_file, write_file::
|
|
98
|
-
Use these to investigate, search, read, and modify files. Output is returned
|
|
99
|
-
to you for reasoning — the user doesn't see it directly.
|
|
93
|
+
You may be paired with a terminal shell that shares the user's CWD, environment, and history — in that mode you can read shell events and act on the user's session. Otherwise you may be embedded as a library, exposed over a bridge protocol, or running headless, with no shell available; in those modes you operate purely through your registered tools.
|
|
100
94
|
|
|
101
|
-
|
|
95
|
+
agent-sh source and documentation live at ${CODE_DIR}. Read them when you need to understand how the runtime works, or when the user asks how to modify or extend it:
|
|
96
|
+
- ${path.join(CODE_DIR, "docs")} — start with README.md; architecture.md and extensions.md cover the kernel boundary and extension API
|
|
97
|
+
- ${path.join(CODE_DIR, "src")} — kernel in src/core, default backend in src/agent, shell host in src/shell, built-in extensions in src/extensions
|
|
98
|
+
- ${path.join(CODE_DIR, "examples/extensions")} — reference extensions to study or copy when adding functionality
|
|
102
99
|
|
|
103
|
-
#
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
100
|
+
# Tools
|
|
101
|
+
|
|
102
|
+
Use your registered tools to investigate, search, read, and modify files.
|
|
103
|
+
Each tool's description tells you when and how to use it; follow that
|
|
104
|
+
guidance rather than assuming a particular tool exists. Tool output is
|
|
105
|
+
returned to you for reasoning — the user doesn't see it directly.
|
|
109
106
|
|
|
110
107
|
# Context Envelopes
|
|
111
108
|
- \`<query_context>\` (contains \`<cwd>\` always, and \`<shell_events>\` when there were user shell commands since the last turn): the user's situation when they sent this turn — \`<cwd>\` anchors where they are right now, \`<shell_events>\` grounds "fix this" / "what just happened" requests. Trust the most recent \`<cwd>\` over any cwd referenced in earlier history.
|
|
@@ -102,4 +102,4 @@ export declare class DeferredLookupProtocol implements ToolProtocol {
|
|
|
102
102
|
createStreamFilter(): null;
|
|
103
103
|
getProtocolTools(): ToolDefinition[];
|
|
104
104
|
}
|
|
105
|
-
export declare function createToolProtocol(mode: "api" | "inline" | "deferred" | "deferred-lookup"): ToolProtocol;
|
|
105
|
+
export declare function createToolProtocol(mode: "api" | "inline" | "deferred" | "deferred-lookup", extraCore?: string[]): ToolProtocol;
|
|
@@ -545,13 +545,15 @@ const CORE_TOOLS = [
|
|
|
545
545
|
"bash", "read_file", "write_file", "edit_file",
|
|
546
546
|
"grep", "glob", "ls",
|
|
547
547
|
"list_skills",
|
|
548
|
+
"conversation_recall",
|
|
548
549
|
];
|
|
549
|
-
export function createToolProtocol(mode) {
|
|
550
|
+
export function createToolProtocol(mode, extraCore = []) {
|
|
551
|
+
const core = extraCore.length === 0 ? CORE_TOOLS : [...CORE_TOOLS, ...extraCore];
|
|
550
552
|
if (mode === "inline")
|
|
551
553
|
return new InlineToolProtocol();
|
|
552
554
|
if (mode === "deferred")
|
|
553
|
-
return new DeferredToolProtocol(
|
|
555
|
+
return new DeferredToolProtocol(core);
|
|
554
556
|
if (mode === "deferred-lookup")
|
|
555
|
-
return new DeferredLookupProtocol(
|
|
557
|
+
return new DeferredLookupProtocol(core);
|
|
556
558
|
return new ApiToolProtocol();
|
|
557
559
|
}
|