agent-sh 0.14.10 → 0.15.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 +36 -13
- package/dist/agent/agent-loop.d.ts +9 -17
- package/dist/agent/agent-loop.js +123 -150
- package/dist/agent/events.d.ts +10 -12
- package/dist/agent/host-types.d.ts +17 -11
- package/dist/agent/index.d.ts +1 -1
- package/dist/agent/index.js +76 -29
- package/dist/agent/live-view.d.ts +3 -3
- package/dist/agent/live-view.js +15 -7
- package/dist/agent/providers/deepseek.js +9 -1
- package/dist/agent/providers/openrouter.js +9 -0
- package/dist/agent/session-store.js +1 -1
- package/dist/agent/subagent.js +1 -1
- package/dist/agent/system-prompt.d.ts +7 -3
- package/dist/agent/system-prompt.js +11 -14
- package/dist/agent/tool-protocol.js +0 -7
- package/dist/cli/args.js +2 -1
- package/dist/cli/install.d.ts +1 -0
- package/dist/cli/install.js +39 -2
- package/dist/cli/subcommands.js +1 -0
- package/dist/core/event-bus.js +0 -2
- package/dist/core/extension-loader.js +3 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +3 -2
- package/dist/extensions/slash-commands/index.js +16 -11
- package/dist/shell/events.d.ts +3 -0
- package/dist/shell/index.js +9 -0
- package/dist/shell/shell-context.d.ts +2 -2
- package/dist/shell/shell-context.js +26 -11
- package/dist/shell/shell.js +3 -0
- package/dist/shell/tui-renderer.js +0 -1
- package/dist/utils/diff-renderer.d.ts +4 -0
- package/dist/utils/diff-renderer.js +15 -27
- package/dist/utils/handler-registry.d.ts +1 -6
- package/dist/utils/handler-registry.js +1 -6
- package/dist/utils/line-editor.js +0 -2
- package/dist/utils/palette.js +4 -4
- package/dist/utils/terminal-buffer.d.ts +2 -0
- package/dist/utils/terminal-buffer.js +4 -0
- package/examples/extensions/ads/SKILL.md +170 -0
- package/examples/extensions/ash-acp-bridge/src/index.ts +11 -7
- package/examples/extensions/ash-scheme/index.ts +377 -687
- package/examples/extensions/ash-scheme/package.json +1 -1
- package/examples/extensions/ashi/EXTENDING.md +118 -0
- package/examples/extensions/ashi/README.md +26 -54
- package/examples/extensions/ashi/docs/ui-surface-protocol.md +163 -0
- package/examples/extensions/ashi/package.json +14 -2
- package/examples/extensions/ashi/src/autocomplete-controller.ts +95 -0
- package/examples/extensions/ashi/src/autocomplete.ts +1 -23
- package/examples/extensions/ashi/src/capture.ts +54 -10
- package/examples/extensions/ashi/src/chat/assistant.ts +67 -0
- package/examples/extensions/ashi/src/chat/lines.ts +39 -0
- package/examples/extensions/ashi/src/chat/thinking.ts +42 -0
- package/examples/extensions/ashi/src/chat/tool-group.ts +84 -0
- package/examples/extensions/ashi/src/chat/user-message.ts +20 -0
- package/examples/extensions/ashi/src/cli.ts +80 -12
- package/examples/extensions/ashi/src/clipboard-image.ts +41 -0
- package/examples/extensions/ashi/src/commands.ts +11 -1
- package/examples/extensions/ashi/src/dialogs.ts +67 -0
- package/examples/extensions/ashi/src/display-config.ts +16 -1
- package/examples/extensions/ashi/src/docks.ts +31 -0
- package/examples/extensions/ashi/src/events.ts +16 -0
- package/examples/extensions/ashi/src/frontend.ts +456 -268
- package/examples/extensions/ashi/src/hooks.ts +27 -40
- package/examples/extensions/ashi/src/input-prompt.ts +64 -0
- package/examples/extensions/ashi/src/renderer.ts +222 -0
- package/examples/extensions/ashi/src/renderers/pi-tui/app.ts +122 -0
- package/examples/extensions/ashi/src/renderers/pi-tui/index.ts +27 -0
- package/examples/extensions/ashi/src/renderers/pi-tui/nodes.ts +190 -0
- package/examples/extensions/ashi/src/renderers/pi-tui/schema-mount.ts +203 -0
- package/examples/extensions/ashi/src/renderers/pi-tui/theme-adapters.ts +48 -0
- package/examples/extensions/ashi/src/renderers/pi-tui/tool-group.ts +21 -0
- package/examples/extensions/ashi/src/schema.ts +46 -205
- package/examples/extensions/ashi/src/session-commands.ts +2 -1
- package/examples/extensions/ashi/src/status-footer.ts +35 -25
- package/examples/extensions/ashi/src/terminal-mode.ts +9 -0
- package/examples/extensions/ashi/src/theme.ts +1 -47
- package/examples/extensions/ashi/src/ui.ts +88 -0
- package/examples/extensions/ashi-ink/README.md +61 -0
- package/examples/extensions/ashi-ink/package.json +30 -0
- package/examples/extensions/ashi-ink/src/index.ts +6 -0
- package/examples/extensions/ashi-ink/src/ink-renderer.tsx +865 -0
- package/examples/extensions/ashi-ink/src/shims.d.ts +5 -0
- package/examples/extensions/ashi-ink/test/render.test.tsx +408 -0
- package/examples/extensions/ashi-ink/tsconfig.json +14 -0
- package/examples/extensions/ashi-scheme-render.ts +10 -10
- package/examples/extensions/ashi-shell-passthrough.ts +95 -0
- package/examples/extensions/ashi-ui-demo.ts +63 -0
- package/examples/extensions/latex-images.ts +70 -19
- package/examples/extensions/overlay-agent.ts +5 -5
- package/examples/extensions/pi-bridge/index.ts +7 -12
- package/examples/extensions/terminal-buffer.ts +4 -2
- package/package.json +3 -9
- package/examples/extensions/ashi/src/components.ts +0 -238
- package/examples/extensions/ollama.ts +0 -108
- package/examples/extensions/opencode-provider.ts +0 -251
- package/examples/extensions/zai-coding-plan.ts +0 -35
package/dist/agent/events.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ProviderRegistration } from "./host-types.js";
|
|
2
|
-
import type { ToolDefinition, ToolResultDisplay } from "./types.js";
|
|
1
|
+
import type { Model, ProviderRegistration } from "./host-types.js";
|
|
2
|
+
import type { ImageContent, ToolDefinition, ToolResultDisplay } from "./types.js";
|
|
3
3
|
export interface AgentIdentity {
|
|
4
4
|
name: string;
|
|
5
5
|
version: string;
|
|
@@ -20,8 +20,9 @@ declare module "../core/event-bus.js" {
|
|
|
20
20
|
"provider:configure": {
|
|
21
21
|
id: string;
|
|
22
22
|
reasoningParams?: (level: string, model?: string) => Record<string, unknown>;
|
|
23
|
+
cacheTokens?: (usage: Record<string, unknown>) => number | undefined;
|
|
23
24
|
};
|
|
24
|
-
"agent:
|
|
25
|
+
"agent:models-changed": Record<string, never>;
|
|
25
26
|
"config:switch-provider": {
|
|
26
27
|
provider: string;
|
|
27
28
|
};
|
|
@@ -44,6 +45,7 @@ declare module "../core/event-bus.js" {
|
|
|
44
45
|
};
|
|
45
46
|
"agent:submit": {
|
|
46
47
|
query: string;
|
|
48
|
+
images?: ImageContent[];
|
|
47
49
|
};
|
|
48
50
|
"agent:cancel-request": {
|
|
49
51
|
silent?: boolean;
|
|
@@ -69,6 +71,7 @@ declare module "../core/event-bus.js" {
|
|
|
69
71
|
prompt_tokens: number;
|
|
70
72
|
completion_tokens: number;
|
|
71
73
|
total_tokens: number;
|
|
74
|
+
cached_prompt_tokens?: number;
|
|
72
75
|
};
|
|
73
76
|
"agent:processing-start": Record<string, never>;
|
|
74
77
|
"agent:processing-done": Record<string, never>;
|
|
@@ -189,17 +192,12 @@ declare module "../core/event-bus.js" {
|
|
|
189
192
|
};
|
|
190
193
|
};
|
|
191
194
|
"config:switch-model": {
|
|
192
|
-
|
|
195
|
+
id: string;
|
|
196
|
+
provider: string;
|
|
193
197
|
};
|
|
194
198
|
"config:get-models": {
|
|
195
|
-
models:
|
|
196
|
-
|
|
197
|
-
provider: string;
|
|
198
|
-
}[];
|
|
199
|
-
active: {
|
|
200
|
-
model: string;
|
|
201
|
-
provider: string;
|
|
202
|
-
} | null;
|
|
199
|
+
models: Model[];
|
|
200
|
+
active: Model | null;
|
|
203
201
|
};
|
|
204
202
|
"config:set-thinking": {
|
|
205
203
|
level: string;
|
|
@@ -56,19 +56,16 @@ export interface ProviderRegistration {
|
|
|
56
56
|
/** Local daemons etc. — `auth list/login` shows "no auth required". */
|
|
57
57
|
noAuth?: boolean;
|
|
58
58
|
}
|
|
59
|
-
/** A model
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
apiKey: string;
|
|
67
|
-
baseURL?: string;
|
|
68
|
-
};
|
|
59
|
+
/** A selectable (provider, model) target the frontend lists and switches.
|
|
60
|
+
* Serializable — identity + capabilities only; the secret + closures needed to
|
|
61
|
+
* invoke it live in ModelEndpoint, so this can safely cross to frontends and
|
|
62
|
+
* out-of-process bridges. */
|
|
63
|
+
export interface Model {
|
|
64
|
+
id: string;
|
|
65
|
+
provider: string;
|
|
69
66
|
/** Context window size in tokens (for usage display). */
|
|
70
67
|
contextWindow?: number;
|
|
71
|
-
/** Max output tokens
|
|
68
|
+
/** Max output tokens. */
|
|
72
69
|
maxTokens?: number;
|
|
73
70
|
/** Model supports reasoning/thinking tokens. */
|
|
74
71
|
reasoning?: boolean;
|
|
@@ -79,7 +76,15 @@ export interface AgentMode {
|
|
|
79
76
|
echoReasoning?: boolean;
|
|
80
77
|
/** Input modalities the model supports. Defaults to ["text"]. */
|
|
81
78
|
modalities?: ("text" | "image")[];
|
|
79
|
+
}
|
|
80
|
+
/** Credentials + provider-shape transforms for invoking a Model, resolved by
|
|
81
|
+
* (provider, id). Internal: holds a secret (apiKey) and non-serializable
|
|
82
|
+
* closures, so it must never ride a bus event. */
|
|
83
|
+
export interface ModelEndpoint {
|
|
84
|
+
apiKey: string;
|
|
85
|
+
baseURL?: string;
|
|
82
86
|
buildReasoningParams?: (level: string) => Record<string, unknown>;
|
|
87
|
+
extractCachedTokens?: (usage: Record<string, unknown>) => number | undefined;
|
|
83
88
|
}
|
|
84
89
|
/**
|
|
85
90
|
* Capabilities the agent host adds on top of CoreContext. Only available
|
|
@@ -94,6 +99,7 @@ export interface AgentSurface {
|
|
|
94
99
|
unregister: (id: string) => void;
|
|
95
100
|
configure: (id: string, opts: {
|
|
96
101
|
reasoningParams?: (level: string, model?: string) => Record<string, unknown>;
|
|
102
|
+
cacheTokens?: (usage: Record<string, unknown>) => number | undefined;
|
|
97
103
|
}) => void;
|
|
98
104
|
};
|
|
99
105
|
registerTool: (tool: ToolDefinition) => void;
|
package/dist/agent/index.d.ts
CHANGED
|
@@ -11,5 +11,5 @@ export { AgentLoop } from "./agent-loop.js";
|
|
|
11
11
|
export { ToolRegistry } from "./tool-registry.js";
|
|
12
12
|
export { runSubagent, type SubagentOptions } from "./subagent.js";
|
|
13
13
|
/** Built-in providers register unconditionally so `auth list` can
|
|
14
|
-
* enumerate them;
|
|
14
|
+
* enumerate them; buildModels() skips entries without an apiKey. */
|
|
15
15
|
export declare function activateAgent(ctx: ExtensionContext): void;
|
package/dist/agent/index.js
CHANGED
|
@@ -32,11 +32,21 @@ function persistedModelFor(providerName) {
|
|
|
32
32
|
return undefined;
|
|
33
33
|
return getSettings().providers?.[providerName]?.defaultModel;
|
|
34
34
|
}
|
|
35
|
+
/** The OpenAI SDK silently defaults an empty baseURL to api.openai.com, so a
|
|
36
|
+
* provider with a key but no endpoint would misroute its key there. `openai`
|
|
37
|
+
* is exempt: that default is its endpoint. */
|
|
38
|
+
function usableProvider(p) {
|
|
39
|
+
return !!p?.apiKey && (!!p.baseURL || p.id === "openai");
|
|
40
|
+
}
|
|
35
41
|
function defaultReasoningBuilder(level) {
|
|
36
42
|
if (level === "off")
|
|
37
43
|
return {};
|
|
38
44
|
return { reasoning_effort: level === "xhigh" ? "high" : level };
|
|
39
45
|
}
|
|
46
|
+
function defaultCacheTokens(usage) {
|
|
47
|
+
const details = usage.prompt_tokens_details;
|
|
48
|
+
return typeof details?.cached_tokens === "number" ? details.cached_tokens : undefined;
|
|
49
|
+
}
|
|
40
50
|
function mergeCaps(settingsCaps, payloadCaps, modelIds) {
|
|
41
51
|
if (!settingsCaps)
|
|
42
52
|
return payloadCaps.size > 0 ? payloadCaps : undefined;
|
|
@@ -85,11 +95,12 @@ export default function agentBackend(ctx) {
|
|
|
85
95
|
settingsProviders.set(name, p);
|
|
86
96
|
}
|
|
87
97
|
const providerHooks = new Map();
|
|
88
|
-
// Bakes model id so
|
|
98
|
+
// Bakes model id so ModelEndpoint.buildReasoningParams keeps its (level) signature.
|
|
89
99
|
const bindReasoning = (shapeId, model) => {
|
|
90
100
|
const hook = providerHooks.get(shapeId)?.reasoningParams;
|
|
91
101
|
return hook ? (level) => hook(level, model) : defaultReasoningBuilder;
|
|
92
102
|
};
|
|
103
|
+
const bindCacheTokens = (shapeId) => providerHooks.get(shapeId)?.cacheTokens ?? defaultCacheTokens;
|
|
93
104
|
const agentSurface = {
|
|
94
105
|
llm: createLlmFacade({ list: ctx.list, call: ctx.call }),
|
|
95
106
|
providers: {
|
|
@@ -274,31 +285,43 @@ export default function agentBackend(ctx) {
|
|
|
274
285
|
}
|
|
275
286
|
return out;
|
|
276
287
|
};
|
|
277
|
-
const
|
|
288
|
+
const buildModels = () => {
|
|
278
289
|
const out = [];
|
|
279
290
|
for (const [id, p] of resolvedProviders) {
|
|
280
291
|
if (!p.apiKey)
|
|
281
292
|
continue;
|
|
282
|
-
|
|
293
|
+
if (!usableProvider(p))
|
|
294
|
+
continue;
|
|
283
295
|
for (const model of p.models) {
|
|
284
296
|
const mc = p.modelCapabilities?.get(model);
|
|
285
297
|
out.push({
|
|
286
|
-
model,
|
|
298
|
+
id: model,
|
|
287
299
|
provider: id,
|
|
288
|
-
providerConfig: { apiKey: p.apiKey, baseURL: p.baseURL },
|
|
289
300
|
contextWindow: mc?.contextWindow ?? p.contextWindow,
|
|
290
301
|
maxTokens: mc?.maxTokens ?? (mc?.contextWindow ? Math.min(Math.floor(mc.contextWindow * 0.4), 65536) : undefined),
|
|
291
302
|
reasoning: mc?.reasoning,
|
|
292
303
|
supportsReasoningEffort: p.supportsReasoningEffort,
|
|
293
304
|
echoReasoning: mc?.echoReasoning,
|
|
294
305
|
modalities: mc?.modalities,
|
|
295
|
-
buildReasoningParams: bindReasoning(shapeId, model),
|
|
296
306
|
});
|
|
297
307
|
}
|
|
298
308
|
}
|
|
299
309
|
return out;
|
|
300
310
|
};
|
|
301
|
-
|
|
311
|
+
const resolveEndpoint = (providerId, modelId) => {
|
|
312
|
+
const p = resolvedProviders.get(providerId);
|
|
313
|
+
if (!p?.apiKey)
|
|
314
|
+
return undefined;
|
|
315
|
+
const shapeId = p.reasoningShape ?? providerId;
|
|
316
|
+
return {
|
|
317
|
+
apiKey: p.apiKey,
|
|
318
|
+
baseURL: p.baseURL,
|
|
319
|
+
buildReasoningParams: bindReasoning(shapeId, modelId),
|
|
320
|
+
extractCachedTokens: bindCacheTokens(shapeId),
|
|
321
|
+
};
|
|
322
|
+
};
|
|
323
|
+
ctx.define("agent:get-models", () => buildModels());
|
|
324
|
+
ctx.define("agent:resolve-endpoint", ({ provider, id }) => resolveEndpoint(provider, id));
|
|
302
325
|
// Reconfigured at core:extensions-loaded; start() gates on `resolved`.
|
|
303
326
|
const llmClient = new LlmClient({ apiKey: "not-configured", model: "not-configured" });
|
|
304
327
|
ctx.define("llm:get-client", () => llmClient);
|
|
@@ -321,10 +344,10 @@ export default function agentBackend(ctx) {
|
|
|
321
344
|
resolvedProviders = computeResolvedProviders();
|
|
322
345
|
if (!resolved)
|
|
323
346
|
return;
|
|
324
|
-
bus.emit("agent:
|
|
347
|
+
bus.emit("agent:models-changed", {});
|
|
325
348
|
if (!ashActive)
|
|
326
349
|
return;
|
|
327
|
-
if (
|
|
350
|
+
if (buildModels().some((m) => m.id === llmClient.model))
|
|
328
351
|
return;
|
|
329
352
|
const pendingProvider = getSettings().defaultProvider;
|
|
330
353
|
if (!pendingProvider)
|
|
@@ -334,26 +357,47 @@ export default function agentBackend(ctx) {
|
|
|
334
357
|
return;
|
|
335
358
|
const pendingModel = persistedModelFor(pendingProvider);
|
|
336
359
|
if (pendingModel && p.models.includes(pendingModel) && llmClient.model !== pendingModel) {
|
|
337
|
-
bus.emit("config:switch-model", {
|
|
360
|
+
bus.emit("config:switch-model", { id: pendingModel, provider: pendingProvider });
|
|
338
361
|
}
|
|
339
362
|
});
|
|
340
|
-
bus.on("provider:configure", ({ id, reasoningParams }) => {
|
|
363
|
+
bus.on("provider:configure", ({ id, reasoningParams, cacheTokens }) => {
|
|
341
364
|
const prev = providerHooks.get(id) ?? {};
|
|
342
365
|
if (reasoningParams !== undefined)
|
|
343
366
|
prev.reasoningParams = reasoningParams;
|
|
367
|
+
if (cacheTokens !== undefined)
|
|
368
|
+
prev.cacheTokens = cacheTokens;
|
|
344
369
|
providerHooks.set(id, prev);
|
|
345
370
|
});
|
|
346
371
|
bus.on("core:extensions-loaded", ({ names }) => {
|
|
347
372
|
loadedExtensionNames = names;
|
|
348
373
|
resolvedProviders = computeResolvedProviders();
|
|
349
374
|
const settings = getSettings();
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
//
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
375
|
+
let providerName = config.provider ?? settings.defaultProvider;
|
|
376
|
+
let activeProvider = providerName ? resolvedProviders.get(providerName) ?? null : null;
|
|
377
|
+
// Inline CLI credentials carry their own endpoint, so they skip the
|
|
378
|
+
// usable-provider fallback that registry-driven selection needs.
|
|
379
|
+
if (!config.apiKey) {
|
|
380
|
+
if (!providerName) {
|
|
381
|
+
const first = [...resolvedProviders].find(([, p]) => usableProvider(p));
|
|
382
|
+
providerName = first?.[0];
|
|
383
|
+
activeProvider = first?.[1] ?? null;
|
|
384
|
+
}
|
|
385
|
+
else if (!usableProvider(activeProvider)) {
|
|
386
|
+
const reason = !activeProvider ? "is not registered"
|
|
387
|
+
: !activeProvider.apiKey ? "has no API key configured"
|
|
388
|
+
: "has no endpoint configured";
|
|
389
|
+
const next = [...resolvedProviders].find(([, p]) => usableProvider(p));
|
|
390
|
+
if (next) {
|
|
391
|
+
bus.emit("ui:error", { message: `Provider "${providerName}" ${reason}; falling back to "${next[0]}".` });
|
|
392
|
+
providerName = next[0];
|
|
393
|
+
activeProvider = next[1];
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
bus.emit("ui:error", { message: `Provider "${providerName}" ${reason}, and no other configured provider has both an API key and an endpoint. Run \`agent-sh auth\` to configure one.` });
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
357
401
|
// Persisted defaultModel wins over openrouter's hardcoded DEFAULT_MODELS[0].
|
|
358
402
|
const effectiveApiKey = config.apiKey ?? activeProvider?.apiKey;
|
|
359
403
|
const effectiveBaseURL = config.baseURL ?? activeProvider?.baseURL;
|
|
@@ -361,15 +405,14 @@ export default function agentBackend(ctx) {
|
|
|
361
405
|
// No provider → don't register ash; let another backend own activation.
|
|
362
406
|
if (!effectiveApiKey || !effectiveModel)
|
|
363
407
|
return;
|
|
364
|
-
const
|
|
408
|
+
const foundModel = buildModels().find((m) => m.id === effectiveModel && (!activeProvider || m.provider === activeProvider.id));
|
|
365
409
|
// Stub when openrouter's async catalog hasn't returned yet; reconciled
|
|
366
410
|
// later via agent:providers:changed → config:switch-model.
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
provider: activeProvider
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
} : { model: effectiveModel });
|
|
411
|
+
const initialModel = foundModel ?? {
|
|
412
|
+
id: effectiveModel,
|
|
413
|
+
provider: activeProvider?.id ?? providerName ?? "custom",
|
|
414
|
+
supportsReasoningEffort: activeProvider?.supportsReasoningEffort,
|
|
415
|
+
};
|
|
373
416
|
llmClient.reconfigure({ apiKey: effectiveApiKey, baseURL: effectiveBaseURL, model: effectiveModel });
|
|
374
417
|
resolved = true;
|
|
375
418
|
bus.emit("agent:register-backend", {
|
|
@@ -386,7 +429,7 @@ export default function agentBackend(ctx) {
|
|
|
386
429
|
bus,
|
|
387
430
|
llmClient,
|
|
388
431
|
handlers: { define: ctx.define, advise: ctx.advise, call: ctx.call, list: ctx.list },
|
|
389
|
-
|
|
432
|
+
initialModel,
|
|
390
433
|
compositor: ctx.shell?.compositor,
|
|
391
434
|
instanceId: ctx.instanceId,
|
|
392
435
|
});
|
|
@@ -468,14 +511,18 @@ export default function agentBackend(ctx) {
|
|
|
468
511
|
bus.emit("ui:error", { message: `Provider "${name}" has no API key configured` });
|
|
469
512
|
return;
|
|
470
513
|
}
|
|
514
|
+
if (!p.baseURL && p.id !== "openai") {
|
|
515
|
+
bus.emit("ui:error", { message: `Provider "${name}" has no endpoint configured` });
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
471
518
|
const switchModel = p.defaultModel ?? p.models[0];
|
|
472
519
|
if (!switchModel) {
|
|
473
520
|
bus.emit("ui:error", { message: `Provider "${name}" has no models configured` });
|
|
474
521
|
return;
|
|
475
522
|
}
|
|
476
523
|
llmClient.reconfigure({ apiKey: p.apiKey, baseURL: p.baseURL, model: switchModel });
|
|
477
|
-
bus.emit("agent:
|
|
478
|
-
bus.emit("config:switch-model", {
|
|
524
|
+
bus.emit("agent:models-changed", {});
|
|
525
|
+
bus.emit("config:switch-model", { id: switchModel, provider: name });
|
|
479
526
|
bus.emit("ui:info", { message: `Switched to ${name} (${switchModel})` });
|
|
480
527
|
});
|
|
481
528
|
bus.onPipe("banner:collect", (e) => {
|
|
@@ -495,7 +542,7 @@ export { AgentLoop } from "./agent-loop.js";
|
|
|
495
542
|
export { ToolRegistry } from "./tool-registry.js";
|
|
496
543
|
export { runSubagent } from "./subagent.js";
|
|
497
544
|
/** Built-in providers register unconditionally so `auth list` can
|
|
498
|
-
* enumerate them;
|
|
545
|
+
* enumerate them; buildModels() skips entries without an apiKey. */
|
|
499
546
|
export function activateAgent(ctx) {
|
|
500
547
|
agentBackend(ctx);
|
|
501
548
|
const agentCtx = ctx;
|
|
@@ -19,7 +19,7 @@ export declare class LiveView {
|
|
|
19
19
|
constructor(handlers?: HandlerFunctions, instanceId?: string);
|
|
20
20
|
private getMessagesJson;
|
|
21
21
|
private invalidateMessagesCache;
|
|
22
|
-
addUserMessage(text: string): void;
|
|
22
|
+
addUserMessage(text: string, images?: ImageContent[]): void;
|
|
23
23
|
addAssistantMessage(content: string | null, toolCalls?: {
|
|
24
24
|
id: string;
|
|
25
25
|
function: {
|
|
@@ -34,9 +34,9 @@ export declare class LiveView {
|
|
|
34
34
|
appendUserMessage(text: string): void;
|
|
35
35
|
private hasOpenToolCalls;
|
|
36
36
|
private flushPendingMessages;
|
|
37
|
-
|
|
38
|
-
get(): AgentShMessage[];
|
|
37
|
+
/** Send-shaped; may be longer than get() (dangling calls stubbed) — never link()/replace() by these indices. */
|
|
39
38
|
forLLM(): ChatCompletionMessageParam[];
|
|
39
|
+
get(): AgentShMessage[];
|
|
40
40
|
replace(msgs: AgentShMessage[]): void;
|
|
41
41
|
link(index: number, entryId: string): void;
|
|
42
42
|
/** DeepSeek 400s on tool messages without a matching tool_call;
|
package/dist/agent/live-view.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { stripMeta } from "./llm-client.js";
|
|
2
1
|
export class LiveView {
|
|
3
2
|
messages = [];
|
|
4
3
|
messagesDirty = true;
|
|
@@ -26,8 +25,19 @@ export class LiveView {
|
|
|
26
25
|
this.messagesDirty = true;
|
|
27
26
|
this.cachedMessagesJson = null;
|
|
28
27
|
}
|
|
29
|
-
addUserMessage(text) {
|
|
30
|
-
|
|
28
|
+
addUserMessage(text, images) {
|
|
29
|
+
if (images?.length) {
|
|
30
|
+
const parts = [];
|
|
31
|
+
if (text)
|
|
32
|
+
parts.push({ type: "text", text });
|
|
33
|
+
for (const img of images) {
|
|
34
|
+
parts.push({ type: "image_url", image_url: { url: `data:${img.mimeType};base64,${img.data}` } });
|
|
35
|
+
}
|
|
36
|
+
this.messages.push({ role: "user", content: parts });
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
this.messages.push({ role: "user", content: text });
|
|
40
|
+
}
|
|
31
41
|
this.invalidateMessagesCache();
|
|
32
42
|
}
|
|
33
43
|
addAssistantMessage(content, toolCalls, extras) {
|
|
@@ -131,15 +141,13 @@ export class LiveView {
|
|
|
131
141
|
}
|
|
132
142
|
this.invalidateMessagesCache();
|
|
133
143
|
}
|
|
134
|
-
|
|
144
|
+
/** Send-shaped; may be longer than get() (dangling calls stubbed) — never link()/replace() by these indices. */
|
|
145
|
+
forLLM() {
|
|
135
146
|
return this.normalizeReasoningConsistency(this.stubDanglingToolCalls(this.dropOrphanToolMessages(this.messages)));
|
|
136
147
|
}
|
|
137
148
|
get() {
|
|
138
149
|
return this.messages;
|
|
139
150
|
}
|
|
140
|
-
forLLM() {
|
|
141
|
-
return this.getMessages().map(stripMeta);
|
|
142
|
-
}
|
|
143
151
|
replace(msgs) {
|
|
144
152
|
this.replaceMessages(msgs);
|
|
145
153
|
}
|
|
@@ -10,7 +10,15 @@ function buildReasoningParams(level, _model) {
|
|
|
10
10
|
: { thinking: { type: "enabled" }, reasoning_effort: level };
|
|
11
11
|
}
|
|
12
12
|
export default function activate(ctx) {
|
|
13
|
-
ctx.agent.providers.configure("deepseek", {
|
|
13
|
+
ctx.agent.providers.configure("deepseek", {
|
|
14
|
+
reasoningParams: buildReasoningParams,
|
|
15
|
+
// Native DeepSeek reports caching as flat hit/miss counts, not the
|
|
16
|
+
// OpenAI-standard prompt_tokens_details.cached_tokens the default reads.
|
|
17
|
+
cacheTokens: (u) => {
|
|
18
|
+
const hit = u.prompt_cache_hit_tokens;
|
|
19
|
+
return typeof hit === "number" ? hit : undefined;
|
|
20
|
+
},
|
|
21
|
+
});
|
|
14
22
|
ctx.agent.providers.register({
|
|
15
23
|
id: "deepseek",
|
|
16
24
|
apiKey: resolveApiKey("deepseek").key ?? undefined,
|
|
@@ -14,6 +14,14 @@ function buildReasoningParams(level, _model) {
|
|
|
14
14
|
? { reasoning: { effort: "none" } }
|
|
15
15
|
: { reasoning: { effort: level } };
|
|
16
16
|
}
|
|
17
|
+
/** OpenRouter's input_modalities → the text/image subset; undefined when absent
|
|
18
|
+
* so the fail-closed image guard treats the model as text-only. */
|
|
19
|
+
function toModalities(input) {
|
|
20
|
+
if (!Array.isArray(input))
|
|
21
|
+
return undefined;
|
|
22
|
+
const out = input.filter((v) => v === "text" || v === "image");
|
|
23
|
+
return out.length ? out : undefined;
|
|
24
|
+
}
|
|
17
25
|
export default function activate(ctx) {
|
|
18
26
|
const apiKey = resolveApiKey("openrouter").key;
|
|
19
27
|
ctx.agent.providers.configure("openrouter", { reasoningParams: buildReasoningParams });
|
|
@@ -42,6 +50,7 @@ export default function activate(ctx) {
|
|
|
42
50
|
reasoning: m.supported_parameters?.includes("reasoning") ?? false,
|
|
43
51
|
contextWindow: m.context_length,
|
|
44
52
|
echoReasoning: userOverrides.get(m.id) ?? patterns.some((re) => re.test(m.id)),
|
|
53
|
+
modalities: toModalities(m.architecture?.input_modalities),
|
|
45
54
|
})),
|
|
46
55
|
});
|
|
47
56
|
}).catch(() => { });
|
|
@@ -237,7 +237,7 @@ export class SessionStore {
|
|
|
237
237
|
for (const e of this.entries.values()) {
|
|
238
238
|
if (e.type === "message" && e.message.role === "user") {
|
|
239
239
|
const raw = typeof e.message.content === "string" ? e.message.content : "";
|
|
240
|
-
const txt = stripContextWrappers(raw);
|
|
240
|
+
const txt = stripContextWrappers(raw).replace(/\s+/g, " ").trim();
|
|
241
241
|
if (txt)
|
|
242
242
|
return txt.slice(0, 80);
|
|
243
243
|
}
|
package/dist/agent/subagent.js
CHANGED
|
@@ -109,7 +109,7 @@ async function streamOnce(llmClient, systemPrompt, conversation, apiTools, model
|
|
|
109
109
|
const stream = await llmClient.stream({
|
|
110
110
|
messages: [
|
|
111
111
|
{ role: "system", content: systemPrompt },
|
|
112
|
-
...wrapTrailingWithDynamicContext(conversation.
|
|
112
|
+
...wrapTrailingWithDynamicContext(conversation.forLLM(), dynamicContext ?? ""),
|
|
113
113
|
],
|
|
114
114
|
tools: apiTools.length > 0 ? apiTools : undefined,
|
|
115
115
|
model,
|
|
@@ -7,10 +7,14 @@ import { type Skill } from "./skills.js";
|
|
|
7
7
|
export declare function formatSkillsBlock(skills: Skill[]): string;
|
|
8
8
|
export declare function loadGlobalAgentsMd(): string | null;
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
* Contains only identity and behavioral instructions.
|
|
10
|
+
* Identity — paragraph one of the system prompt. Surface-agnostic, cacheable.
|
|
12
11
|
*/
|
|
13
|
-
export declare const
|
|
12
|
+
export declare const STATIC_IDENTITY = "You are ash, an AI coding assistant running inside agent-sh \u2014 a composable agent runtime with a small core and everything else, including the frontend you're attached to, layered on as extensions.";
|
|
13
|
+
/**
|
|
14
|
+
* The rest of the static prompt — code map, tool guidance, envelope contract.
|
|
15
|
+
* Follows the frontend surface description in the assembled prompt.
|
|
16
|
+
*/
|
|
17
|
+
export declare const STATIC_GUIDE: string;
|
|
14
18
|
/**
|
|
15
19
|
* CWD-scoped static context: project conventions (CLAUDE.md / AGENT.md)
|
|
16
20
|
* and discovered skills. Stable for a given cwd — callers should cache
|
|
@@ -85,14 +85,14 @@ function loadConventionFiles(dir) {
|
|
|
85
85
|
return result;
|
|
86
86
|
}
|
|
87
87
|
/**
|
|
88
|
-
*
|
|
89
|
-
* Contains only identity and behavioral instructions.
|
|
88
|
+
* Identity — paragraph one of the system prompt. Surface-agnostic, cacheable.
|
|
90
89
|
*/
|
|
91
|
-
export const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
export const STATIC_IDENTITY = `You are ash, an AI coding assistant running inside agent-sh — a composable agent runtime with a small core and everything else, including the frontend you're attached to, layered on as extensions.`;
|
|
91
|
+
/**
|
|
92
|
+
* The rest of the static prompt — code map, tool guidance, envelope contract.
|
|
93
|
+
* Follows the frontend surface description in the assembled prompt.
|
|
94
|
+
*/
|
|
95
|
+
export const STATIC_GUIDE = `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
96
|
- ${path.join(CODE_DIR, "docs")} — start with README.md; architecture.md and extensions.md cover the kernel boundary and extension API
|
|
97
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
98
|
- ${path.join(CODE_DIR, "examples/extensions")} — reference extensions to study or copy when adding functionality
|
|
@@ -105,15 +105,12 @@ guidance rather than assuming a particular tool exists. Tool output is
|
|
|
105
105
|
returned to you for reasoning — the user doesn't see it directly.
|
|
106
106
|
|
|
107
107
|
# Context Envelopes
|
|
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.
|
|
109
|
-
- \`<dynamic_context>\`: current system state — in-flight work, mode markers, warnings.
|
|
110
|
-
\`<dynamic_context>\` may be absent on any turn.
|
|
111
108
|
|
|
112
|
-
|
|
109
|
+
A turn may be preceded by either of two wrappers:
|
|
110
|
+
- \`<query_context>\`: the user's situation when they sent this turn — the frontend and extensions inject what grounds the request here. Trust the most recent values over anything referenced earlier in history.
|
|
111
|
+
- \`<dynamic_context>\`: current system state — in-flight work, mode markers, warnings.
|
|
113
112
|
|
|
114
|
-
|
|
115
|
-
and conversation context for recurring patterns — apply them proactively and do not wait to
|
|
116
|
-
be reminded.`;
|
|
113
|
+
Either may be absent on any turn.`;
|
|
117
114
|
/**
|
|
118
115
|
* CWD-scoped static context: project conventions (CLAUDE.md / AGENT.md)
|
|
119
116
|
* and discovered skills. Stable for a given cwd — callers should cache
|
|
@@ -84,7 +84,6 @@ export class InlineToolProtocol {
|
|
|
84
84
|
const name = obj.tool;
|
|
85
85
|
if (typeof name !== "string")
|
|
86
86
|
continue;
|
|
87
|
-
// Separate tool name from args
|
|
88
87
|
const { tool: _, ...args } = obj;
|
|
89
88
|
calls.push({
|
|
90
89
|
id: `inline_${++this.callCounter}`,
|
|
@@ -128,7 +127,6 @@ class CodeBlockFilter {
|
|
|
128
127
|
let raw = "";
|
|
129
128
|
while (this.buf.length > 0) {
|
|
130
129
|
if (this.inFence) {
|
|
131
|
-
// Look for closing ```
|
|
132
130
|
const closeIdx = this.buf.indexOf("```");
|
|
133
131
|
if (closeIdx !== -1) {
|
|
134
132
|
// Skip past closing ``` and any trailing whitespace on that line
|
|
@@ -142,7 +140,6 @@ class CodeBlockFilter {
|
|
|
142
140
|
// No closing yet — keep buffering
|
|
143
141
|
break;
|
|
144
142
|
}
|
|
145
|
-
// Look for opening ```tool
|
|
146
143
|
const openIdx = this.buf.indexOf("```tool");
|
|
147
144
|
if (openIdx !== -1) {
|
|
148
145
|
// Emit everything before the fence, trimming trailing newline
|
|
@@ -184,7 +181,6 @@ class CodeBlockFilter {
|
|
|
184
181
|
raw += this.buf;
|
|
185
182
|
this.buf = "";
|
|
186
183
|
}
|
|
187
|
-
// Collapse runs of 3+ newlines into 2 (one blank line max)
|
|
188
184
|
return this.collapseNewlines(raw);
|
|
189
185
|
}
|
|
190
186
|
flush() {
|
|
@@ -209,7 +205,6 @@ class CodeBlockFilter {
|
|
|
209
205
|
prefix = "\n".repeat(Math.min(leading, allowed));
|
|
210
206
|
text = text.slice(leading);
|
|
211
207
|
}
|
|
212
|
-
// Collapse internal runs
|
|
213
208
|
text = text.replace(/\n{3,}/g, "\n\n");
|
|
214
209
|
// Track trailing newlines for next call
|
|
215
210
|
let trailing = 0;
|
|
@@ -322,9 +317,7 @@ export class DeferredToolProtocol {
|
|
|
322
317
|
if (schemaProps) {
|
|
323
318
|
const validParams = new Set(Object.keys(schemaProps));
|
|
324
319
|
const providedParams = Object.keys(targetArgs);
|
|
325
|
-
// Check for unknown params (likely wrong names)
|
|
326
320
|
const unknown = providedParams.filter((p) => !validParams.has(p));
|
|
327
|
-
// Check for missing required params
|
|
328
321
|
const missing = [...requiredParams].filter((p) => !targetArgs[p]);
|
|
329
322
|
if (unknown.length > 0 || missing.length > 0) {
|
|
330
323
|
const expected = [...validParams]
|
package/dist/cli/args.js
CHANGED
|
@@ -3,9 +3,10 @@ const HELP_TEXT = `agent-sh — a shell-first terminal where AI is one keystroke
|
|
|
3
3
|
|
|
4
4
|
Usage: agent-sh [options]
|
|
5
5
|
agent-sh init [--force] Scaffold ~/.agent-sh/ (settings, examples, AGENTS.md)
|
|
6
|
-
agent-sh install <spec> [--force] [--sync-deps]
|
|
6
|
+
agent-sh install <spec> [--force] [--sync-deps] [--dev]
|
|
7
7
|
Install an extension (bundled name, file:, npm:, github:)
|
|
8
8
|
--sync-deps rewrites a stale agent-sh pin to the host version
|
|
9
|
+
--dev links the extension against the running host's core (local development)
|
|
9
10
|
agent-sh uninstall <name> Remove an installed extension
|
|
10
11
|
agent-sh list List installed extensions
|
|
11
12
|
agent-sh auth login [provider] Store an API key for a built-in provider
|
package/dist/cli/install.d.ts
CHANGED