pzero-operator 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/agent-session.js +3 -3
- package/dist/core/keybindings.js +1 -0
- package/dist/core/sdk.js +3 -3
- package/dist/core/system-prompt.js +5 -1
- package/dist/core/tools/bash.js +28 -3
- package/dist/core/tools/index.js +12 -2
- package/dist/core/tools/web-search.js +162 -0
- package/dist/migrations.js +36 -2
- package/dist/modes/interactive/components/footer.js +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +5 -6
- package/dist/modes/interactive/interactive-mode.js +353 -74
- package/dist/utils/shell.js +70 -39
- package/node_modules/@operator/ai/dist/providers/openai-completions.js +6 -1
- package/package.json +1 -1
|
@@ -52,9 +52,9 @@ export function parseSkillBlock(text) {
|
|
|
52
52
|
// Constants
|
|
53
53
|
// ============================================================================
|
|
54
54
|
/** Standard thinking levels */
|
|
55
|
-
const THINKING_LEVELS = ["off", "
|
|
55
|
+
const THINKING_LEVELS = ["off", "low", "medium", "high"];
|
|
56
56
|
/** Thinking levels including xhigh (for supported models) */
|
|
57
|
-
const THINKING_LEVELS_WITH_XHIGH = ["off", "
|
|
57
|
+
const THINKING_LEVELS_WITH_XHIGH = ["off", "low", "medium", "high", "xhigh"];
|
|
58
58
|
// ============================================================================
|
|
59
59
|
// AgentSession Class
|
|
60
60
|
// ============================================================================
|
|
@@ -1887,7 +1887,7 @@ export class AgentSession {
|
|
|
1887
1887
|
this._applyExtensionBindings(this._extensionRunner);
|
|
1888
1888
|
const defaultActiveToolNames = this._baseToolsOverride
|
|
1889
1889
|
? Object.keys(this._baseToolsOverride)
|
|
1890
|
-
: ["read", "bash", "edit", "write"];
|
|
1890
|
+
: ["read", "bash", "edit", "write", "web_search"];
|
|
1891
1891
|
const baseActiveToolNames = options.activeToolNames ?? defaultActiveToolNames;
|
|
1892
1892
|
this._refreshToolRegistry({
|
|
1893
1893
|
activeToolNames: baseActiveToolNames,
|
package/dist/core/keybindings.js
CHANGED
|
@@ -25,6 +25,7 @@ export const KEYBINDINGS = {
|
|
|
25
25
|
},
|
|
26
26
|
"app.model.select": { defaultKeys: "ctrl+l", description: "Open model selector" },
|
|
27
27
|
"app.tools.expand": { defaultKeys: "ctrl+o", description: "Toggle tool output" },
|
|
28
|
+
"app.history.full": { defaultKeys: "ctrl+r", description: "Toggle full conversation view" },
|
|
28
29
|
"app.thinking.toggle": {
|
|
29
30
|
defaultKeys: "ctrl+t",
|
|
30
31
|
description: "Toggle thinking blocks",
|
package/dist/core/sdk.js
CHANGED
|
@@ -13,12 +13,12 @@ import { getDefaultSessionDir, SessionManager } from "./session-manager.js";
|
|
|
13
13
|
import { SettingsManager } from "./settings-manager.js";
|
|
14
14
|
import { isInstallTelemetryEnabled } from "./telemetry.js";
|
|
15
15
|
import { time } from "./timings.js";
|
|
16
|
-
import { createBashTool, createCodingTools, createEditTool, createFindTool, createGrepTool, createLsTool, createReadOnlyTools, createReadTool, createWriteTool, withFileMutationQueue, } from "./tools/index.js";
|
|
16
|
+
import { createBashTool, createCodingTools, createEditTool, createFindTool, createGrepTool, createLsTool, createReadOnlyTools, createReadTool, createWebSearchTool, createWriteTool, withFileMutationQueue, } from "./tools/index.js";
|
|
17
17
|
// Re-exports
|
|
18
18
|
export * from "./agent-session-runtime.js";
|
|
19
19
|
export { withFileMutationQueue,
|
|
20
20
|
// Tool factories (for custom cwd)
|
|
21
|
-
createCodingTools, createReadOnlyTools, createReadTool, createBashTool, createEditTool, createWriteTool, createGrepTool, createFindTool, createLsTool, };
|
|
21
|
+
createCodingTools, createReadOnlyTools, createReadTool, createBashTool, createEditTool, createWriteTool, createGrepTool, createFindTool, createLsTool, createWebSearchTool, };
|
|
22
22
|
// Helper Functions
|
|
23
23
|
function getDefaultAgentDir() {
|
|
24
24
|
return getAgentDir();
|
|
@@ -135,7 +135,7 @@ export async function createAgentSession(options = {}) {
|
|
|
135
135
|
if (!model || !model.reasoning) {
|
|
136
136
|
thinkingLevel = "off";
|
|
137
137
|
}
|
|
138
|
-
const defaultActiveToolNames = ["read", "bash", "edit", "write"];
|
|
138
|
+
const defaultActiveToolNames = ["read", "bash", "edit", "write", "web_search"];
|
|
139
139
|
const initialActiveToolNames = options.tools ? [...options.tools] : defaultActiveToolNames;
|
|
140
140
|
let agent;
|
|
141
141
|
// Create convertToLlm wrapper that filters images if blockImages is enabled (defense-in-depth)
|
|
@@ -45,7 +45,7 @@ export function buildSystemPrompt(options) {
|
|
|
45
45
|
const examplesPath = getExamplesPath();
|
|
46
46
|
// Build tools list based on selected tools.
|
|
47
47
|
// A tool appears in Available tools only when the caller provides a one-line snippet.
|
|
48
|
-
const tools = selectedTools || ["read", "bash", "edit", "write"];
|
|
48
|
+
const tools = selectedTools || ["read", "bash", "edit", "write", "web_search"];
|
|
49
49
|
const visibleTools = tools.filter((name) => !!toolSnippets?.[name]);
|
|
50
50
|
const toolsList = visibleTools.length > 0 ? visibleTools.map((name) => `- ${name}: ${toolSnippets[name]}`).join("\n") : "(none)";
|
|
51
51
|
// Build guidelines based on which tools are actually available
|
|
@@ -63,6 +63,7 @@ export function buildSystemPrompt(options) {
|
|
|
63
63
|
const hasFind = tools.includes("find");
|
|
64
64
|
const hasLs = tools.includes("ls");
|
|
65
65
|
const hasRead = tools.includes("read");
|
|
66
|
+
const hasWebSearch = tools.includes("web_search");
|
|
66
67
|
// File exploration guidelines
|
|
67
68
|
if (hasBash && !hasGrep && !hasFind && !hasLs) {
|
|
68
69
|
addGuideline("Use bash for file operations like ls, rg, find");
|
|
@@ -70,6 +71,9 @@ export function buildSystemPrompt(options) {
|
|
|
70
71
|
else if (hasBash && (hasGrep || hasFind || hasLs)) {
|
|
71
72
|
addGuideline("Prefer grep/find/ls tools over bash for file exploration (faster, respects .gitignore)");
|
|
72
73
|
}
|
|
74
|
+
if (hasWebSearch) {
|
|
75
|
+
addGuideline("Use web_search for current external information, public docs, and recent web content.");
|
|
76
|
+
}
|
|
73
77
|
for (const guideline of promptGuidelines ?? []) {
|
|
74
78
|
const normalized = guideline.trim();
|
|
75
79
|
if (normalized.length > 0) {
|
package/dist/core/tools/bash.js
CHANGED
|
@@ -36,7 +36,7 @@ export function createLocalBashOperations(options) {
|
|
|
36
36
|
return new Promise((resolve, reject) => {
|
|
37
37
|
const { shell, args } = getShellConfig(options?.shellPath);
|
|
38
38
|
if (!existsSync(cwd)) {
|
|
39
|
-
reject(new Error(`Working directory does not exist: ${cwd}\nCannot execute
|
|
39
|
+
reject(new Error(`Working directory does not exist: ${cwd}\nCannot execute shell commands.`));
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
42
|
const child = spawn(shell, [...args, command], {
|
|
@@ -119,6 +119,30 @@ class BashResultRenderComponent extends Container {
|
|
|
119
119
|
function formatDuration(ms) {
|
|
120
120
|
return `${(ms / 1000).toFixed(1)}s`;
|
|
121
121
|
}
|
|
122
|
+
function getShellPromptText(shellPath) {
|
|
123
|
+
try {
|
|
124
|
+
const shellConfig = getShellConfig(shellPath);
|
|
125
|
+
if (process.platform === "win32") {
|
|
126
|
+
if (shellConfig.kind === "powershell") {
|
|
127
|
+
return {
|
|
128
|
+
description: `Execute a ${shellConfig.displayName} command in the current working directory. Returns stdout and stderr. Prefer Windows-native commands and PowerShell syntax. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`,
|
|
129
|
+
promptSnippet: "Execute PowerShell commands on Windows (Get-ChildItem, Select-String, Test-Path, Remove-Item, etc.)",
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
description: `Execute a shell command in the current working directory using ${shellConfig.displayName}. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`,
|
|
134
|
+
promptSnippet: "Execute shell commands on Windows using Bash (ls, grep, find, etc.)",
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// Fall back to the default Unix-oriented prompt text if shell discovery fails.
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
description: `Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`,
|
|
143
|
+
promptSnippet: "Execute bash commands (ls, grep, find, etc.)",
|
|
144
|
+
};
|
|
145
|
+
}
|
|
122
146
|
function formatBashCall(args) {
|
|
123
147
|
const command = str(args?.command);
|
|
124
148
|
const timeout = args?.timeout;
|
|
@@ -192,11 +216,12 @@ export function createBashToolDefinition(cwd, options) {
|
|
|
192
216
|
const ops = options?.operations ?? createLocalBashOperations({ shellPath: options?.shellPath });
|
|
193
217
|
const commandPrefix = options?.commandPrefix;
|
|
194
218
|
const spawnHook = options?.spawnHook;
|
|
219
|
+
const promptText = getShellPromptText(options?.shellPath);
|
|
195
220
|
return {
|
|
196
221
|
name: "bash",
|
|
197
222
|
label: "terminal.exec",
|
|
198
|
-
description:
|
|
199
|
-
promptSnippet:
|
|
223
|
+
description: promptText.description,
|
|
224
|
+
promptSnippet: promptText.promptSnippet,
|
|
200
225
|
parameters: bashSchema,
|
|
201
226
|
async execute(_toolCallId, { command, timeout }, signal, onUpdate, _ctx) {
|
|
202
227
|
const resolvedCommand = commandPrefix ? `${commandPrefix}\n${command}` : command;
|
package/dist/core/tools/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export { createGrepTool, createGrepToolDefinition, } from "./grep.js";
|
|
|
6
6
|
export { createLsTool, createLsToolDefinition, } from "./ls.js";
|
|
7
7
|
export { createReadTool, createReadToolDefinition, } from "./read.js";
|
|
8
8
|
export { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, truncateHead, truncateLine, truncateTail, } from "./truncate.js";
|
|
9
|
+
export { createWebSearchTool, createWebSearchToolDefinition, } from "./web-search.js";
|
|
9
10
|
export { createWriteTool, createWriteToolDefinition, } from "./write.js";
|
|
10
11
|
import { createBashTool, createBashToolDefinition } from "./bash.js";
|
|
11
12
|
import { createEditTool, createEditToolDefinition } from "./edit.js";
|
|
@@ -13,8 +14,9 @@ import { createFindTool, createFindToolDefinition } from "./find.js";
|
|
|
13
14
|
import { createGrepTool, createGrepToolDefinition } from "./grep.js";
|
|
14
15
|
import { createLsTool, createLsToolDefinition } from "./ls.js";
|
|
15
16
|
import { createReadTool, createReadToolDefinition } from "./read.js";
|
|
17
|
+
import { createWebSearchTool, createWebSearchToolDefinition } from "./web-search.js";
|
|
16
18
|
import { createWriteTool, createWriteToolDefinition } from "./write.js";
|
|
17
|
-
export const allToolNames = new Set(["read", "bash", "edit", "write", "grep", "find", "ls"]);
|
|
19
|
+
export const allToolNames = new Set(["read", "bash", "edit", "write", "grep", "find", "ls", "web_search"]);
|
|
18
20
|
export function createToolDefinition(toolName, cwd, options) {
|
|
19
21
|
switch (toolName) {
|
|
20
22
|
case "read":
|
|
@@ -31,6 +33,8 @@ export function createToolDefinition(toolName, cwd, options) {
|
|
|
31
33
|
return createFindToolDefinition(cwd, options?.find);
|
|
32
34
|
case "ls":
|
|
33
35
|
return createLsToolDefinition(cwd, options?.ls);
|
|
36
|
+
case "web_search":
|
|
37
|
+
return createWebSearchToolDefinition(cwd, options?.web_search);
|
|
34
38
|
default:
|
|
35
39
|
throw new Error(`Unknown tool name: ${toolName}`);
|
|
36
40
|
}
|
|
@@ -51,6 +55,8 @@ export function createTool(toolName, cwd, options) {
|
|
|
51
55
|
return createFindTool(cwd, options?.find);
|
|
52
56
|
case "ls":
|
|
53
57
|
return createLsTool(cwd, options?.ls);
|
|
58
|
+
case "web_search":
|
|
59
|
+
return createWebSearchTool(cwd, options?.web_search);
|
|
54
60
|
default:
|
|
55
61
|
throw new Error(`Unknown tool name: ${toolName}`);
|
|
56
62
|
}
|
|
@@ -61,6 +67,7 @@ export function createCodingToolDefinitions(cwd, options) {
|
|
|
61
67
|
createBashToolDefinition(cwd, options?.bash),
|
|
62
68
|
createEditToolDefinition(cwd, options?.edit),
|
|
63
69
|
createWriteToolDefinition(cwd, options?.write),
|
|
70
|
+
createWebSearchToolDefinition(cwd, options?.web_search),
|
|
64
71
|
];
|
|
65
72
|
}
|
|
66
73
|
export function createReadOnlyToolDefinitions(cwd, options) {
|
|
@@ -80,6 +87,7 @@ export function createAllToolDefinitions(cwd, options) {
|
|
|
80
87
|
grep: createGrepToolDefinition(cwd, options?.grep),
|
|
81
88
|
find: createFindToolDefinition(cwd, options?.find),
|
|
82
89
|
ls: createLsToolDefinition(cwd, options?.ls),
|
|
90
|
+
web_search: createWebSearchToolDefinition(cwd, options?.web_search),
|
|
83
91
|
};
|
|
84
92
|
}
|
|
85
93
|
export function createCodingTools(cwd, options) {
|
|
@@ -88,6 +96,7 @@ export function createCodingTools(cwd, options) {
|
|
|
88
96
|
createBashTool(cwd, options?.bash),
|
|
89
97
|
createEditTool(cwd, options?.edit),
|
|
90
98
|
createWriteTool(cwd, options?.write),
|
|
99
|
+
createWebSearchTool(cwd, options?.web_search),
|
|
91
100
|
];
|
|
92
101
|
}
|
|
93
102
|
export function createReadOnlyTools(cwd, options) {
|
|
@@ -107,6 +116,7 @@ export function createAllTools(cwd, options) {
|
|
|
107
116
|
grep: createGrepTool(cwd, options?.grep),
|
|
108
117
|
find: createFindTool(cwd, options?.find),
|
|
109
118
|
ls: createLsTool(cwd, options?.ls),
|
|
119
|
+
web_search: createWebSearchTool(cwd, options?.web_search),
|
|
110
120
|
};
|
|
111
121
|
}
|
|
112
|
-
//# sourceMappingURL=index.js.map
|
|
122
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { Text } from "@operator/tui";
|
|
2
|
+
import { spawn } from "child_process";
|
|
3
|
+
import { existsSync } from "fs";
|
|
4
|
+
import { Type } from "typebox";
|
|
5
|
+
import { keyHint } from "../../modes/interactive/components/keybinding-hints.js";
|
|
6
|
+
import { getTextOutput, invalidArgText, str } from "./render-utils.js";
|
|
7
|
+
import { wrapToolDefinition } from "./tool-definition-wrapper.js";
|
|
8
|
+
import { DEFAULT_MAX_BYTES, formatSize, truncateHead } from "./truncate.js";
|
|
9
|
+
const WEB_SEARCH_HELPER = `${process.env.HOME || ""}/.tools/web_search/search.cjs`;
|
|
10
|
+
const webSearchSchema = Type.Object({
|
|
11
|
+
query: Type.Optional(Type.String({ description: "Search query for full, ddg, instant, or wiki modes" })),
|
|
12
|
+
url: Type.Optional(Type.String({ description: "Page URL to extract when mode is 'page'" })),
|
|
13
|
+
mode: Type.Optional(Type.Union([
|
|
14
|
+
Type.Literal("full"),
|
|
15
|
+
Type.Literal("ddg"),
|
|
16
|
+
Type.Literal("instant"),
|
|
17
|
+
Type.Literal("wiki"),
|
|
18
|
+
Type.Literal("page"),
|
|
19
|
+
], { description: "Search mode (default: full)" })),
|
|
20
|
+
limit: Type.Optional(Type.Number({ description: "Maximum number of results for ddg/wiki/full (default: 5)" })),
|
|
21
|
+
});
|
|
22
|
+
function formatWebSearchCall(args, theme) {
|
|
23
|
+
const mode = str(args?.mode) || "full";
|
|
24
|
+
const query = str(args?.query);
|
|
25
|
+
const url = str(args?.url);
|
|
26
|
+
const target = mode === "page" ? url : query;
|
|
27
|
+
const invalidArg = invalidArgText(theme);
|
|
28
|
+
return `${theme.fg("toolTitle", theme.bold("web.search"))} ${theme.fg("toolOutput", `[${mode}] `)}${target === null ? invalidArg : theme.fg("accent", target || "...")}`;
|
|
29
|
+
}
|
|
30
|
+
function formatWebSearchResult(result, options, theme, showImages) {
|
|
31
|
+
const output = getTextOutput(result, showImages).trim();
|
|
32
|
+
if (!output) {
|
|
33
|
+
return "";
|
|
34
|
+
}
|
|
35
|
+
const lines = output.split("\n");
|
|
36
|
+
const maxLines = options.expanded ? lines.length : 20;
|
|
37
|
+
const displayLines = lines.slice(0, maxLines);
|
|
38
|
+
const remaining = lines.length - maxLines;
|
|
39
|
+
let text = `\n${displayLines.map((line) => theme.fg("toolOutput", line)).join("\n")}`;
|
|
40
|
+
if (remaining > 0) {
|
|
41
|
+
text += `${theme.fg("muted", `\n... (${remaining} more lines,`)} ${keyHint("app.tools.expand", "to expand")})`;
|
|
42
|
+
}
|
|
43
|
+
const truncation = result.details?.truncation;
|
|
44
|
+
if (truncation?.truncated) {
|
|
45
|
+
text += `\n${theme.fg("warning", `[Truncated: ${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit]`)}`;
|
|
46
|
+
}
|
|
47
|
+
return text;
|
|
48
|
+
}
|
|
49
|
+
export function createWebSearchToolDefinition(_cwd, _options) {
|
|
50
|
+
return {
|
|
51
|
+
name: "web_search",
|
|
52
|
+
label: "web.search",
|
|
53
|
+
description: `Search the web using DuckDuckGo and Wikipedia, or extract readable text from a web page. Best for current external information, documentation, and quick validation. Output is truncated to ${DEFAULT_MAX_BYTES / 1024}KB if needed.`,
|
|
54
|
+
promptSnippet: "Search the web for current external information or extract text from a URL",
|
|
55
|
+
promptGuidelines: [
|
|
56
|
+
"Use web_search when the user asks for current external information, recent updates, or public web content.",
|
|
57
|
+
],
|
|
58
|
+
parameters: webSearchSchema,
|
|
59
|
+
async execute(_toolCallId, { query, url, mode, limit }, signal, _onUpdate, _ctx) {
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
if (signal?.aborted) {
|
|
62
|
+
reject(new Error("Operation aborted"));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (!existsSync(WEB_SEARCH_HELPER)) {
|
|
66
|
+
reject(new Error(`Web search helper not found: ${WEB_SEARCH_HELPER}`));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const effectiveMode = mode || "full";
|
|
70
|
+
const target = effectiveMode === "page" ? url : query;
|
|
71
|
+
if (!target) {
|
|
72
|
+
reject(new Error(effectiveMode === "page" ? "url is required for page mode" : "query is required for search mode"));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const args = [WEB_SEARCH_HELPER, "--json"];
|
|
76
|
+
if (effectiveMode === "page") {
|
|
77
|
+
args.push("--page", target);
|
|
78
|
+
}
|
|
79
|
+
else if (effectiveMode === "ddg") {
|
|
80
|
+
args.push("--ddg", target);
|
|
81
|
+
}
|
|
82
|
+
else if (effectiveMode === "instant") {
|
|
83
|
+
args.push("--instant", target);
|
|
84
|
+
}
|
|
85
|
+
else if (effectiveMode === "wiki") {
|
|
86
|
+
args.push("--wiki", target);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
args.push(target);
|
|
90
|
+
}
|
|
91
|
+
if (limit !== undefined && effectiveMode !== "page" && effectiveMode !== "instant") {
|
|
92
|
+
args.push("--limit", String(limit));
|
|
93
|
+
}
|
|
94
|
+
const child = spawn(process.execPath, args, {
|
|
95
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
96
|
+
});
|
|
97
|
+
let stdout = "";
|
|
98
|
+
let stderr = "";
|
|
99
|
+
let settled = false;
|
|
100
|
+
const settle = (fn) => {
|
|
101
|
+
if (settled)
|
|
102
|
+
return;
|
|
103
|
+
settled = true;
|
|
104
|
+
signal?.removeEventListener("abort", onAbort);
|
|
105
|
+
fn();
|
|
106
|
+
};
|
|
107
|
+
const onAbort = () => {
|
|
108
|
+
if (!child.killed) {
|
|
109
|
+
child.kill();
|
|
110
|
+
}
|
|
111
|
+
settle(() => reject(new Error("Operation aborted")));
|
|
112
|
+
};
|
|
113
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
114
|
+
child.stdout?.on("data", (chunk) => {
|
|
115
|
+
stdout += chunk.toString();
|
|
116
|
+
});
|
|
117
|
+
child.stderr?.on("data", (chunk) => {
|
|
118
|
+
stderr += chunk.toString();
|
|
119
|
+
});
|
|
120
|
+
child.on("error", (error) => {
|
|
121
|
+
settle(() => reject(new Error(`Failed to run web search helper: ${error.message}`)));
|
|
122
|
+
});
|
|
123
|
+
child.on("close", (code) => {
|
|
124
|
+
if (signal?.aborted) {
|
|
125
|
+
settle(() => reject(new Error("Operation aborted")));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (code !== 0) {
|
|
129
|
+
settle(() => reject(new Error(stderr.trim() || `web_search exited with code ${code}`)));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const output = stdout.trim();
|
|
133
|
+
if (!output) {
|
|
134
|
+
settle(() => resolve({
|
|
135
|
+
content: [{ type: "text", text: "No web search results returned" }],
|
|
136
|
+
details: undefined,
|
|
137
|
+
}));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const truncation = truncateHead(output, { maxLines: Number.MAX_SAFE_INTEGER });
|
|
141
|
+
settle(() => resolve({
|
|
142
|
+
content: [{ type: "text", text: truncation.content }],
|
|
143
|
+
details: truncation.truncated ? { truncation } : undefined,
|
|
144
|
+
}));
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
renderCall(args, theme, context) {
|
|
149
|
+
const text = context.lastComponent ?? new Text("", 0, 0);
|
|
150
|
+
text.setText(formatWebSearchCall(args, theme));
|
|
151
|
+
return text;
|
|
152
|
+
},
|
|
153
|
+
renderResult(result, options, theme, context) {
|
|
154
|
+
const text = context.lastComponent ?? new Text("", 0, 0);
|
|
155
|
+
text.setText(formatWebSearchResult(result, options, theme, context.showImages));
|
|
156
|
+
return text;
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
export function createWebSearchTool(cwd, options) {
|
|
161
|
+
return wrapToolDefinition(createWebSearchToolDefinition(cwd, options));
|
|
162
|
+
}
|
package/dist/migrations.js
CHANGED
|
@@ -32,13 +32,14 @@ const DEFAULT_MODELS_CONFIG = {
|
|
|
32
32
|
authHeader: true,
|
|
33
33
|
compat: {
|
|
34
34
|
supportsDeveloperRole: true,
|
|
35
|
-
supportsReasoningEffort:
|
|
35
|
+
supportsReasoningEffort: true,
|
|
36
36
|
},
|
|
37
37
|
models: [
|
|
38
38
|
{
|
|
39
39
|
id: "qwen3-6",
|
|
40
40
|
name: "qwen3.6 27b",
|
|
41
41
|
reasoning: true,
|
|
42
|
+
input: ["text", "image"],
|
|
42
43
|
},
|
|
43
44
|
],
|
|
44
45
|
},
|
|
@@ -48,13 +49,14 @@ const DEFAULT_MODELS_CONFIG = {
|
|
|
48
49
|
authHeader: true,
|
|
49
50
|
compat: {
|
|
50
51
|
supportsDeveloperRole: true,
|
|
51
|
-
supportsReasoningEffort:
|
|
52
|
+
supportsReasoningEffort: true,
|
|
52
53
|
},
|
|
53
54
|
models: [
|
|
54
55
|
{
|
|
55
56
|
id: "gemma4",
|
|
56
57
|
name: "gemma4",
|
|
57
58
|
reasoning: true,
|
|
59
|
+
input: ["text", "image"],
|
|
58
60
|
},
|
|
59
61
|
],
|
|
60
62
|
},
|
|
@@ -107,6 +109,13 @@ function migrateAlemBaseUrls() {
|
|
|
107
109
|
gptOssProvider.baseUrl = "https://llm.alem.ai";
|
|
108
110
|
changed = true;
|
|
109
111
|
}
|
|
112
|
+
if (!gptOssProvider.compat || typeof gptOssProvider.compat !== "object") {
|
|
113
|
+
gptOssProvider.compat = {};
|
|
114
|
+
}
|
|
115
|
+
if (gptOssProvider.compat.supportsReasoningEffort !== true) {
|
|
116
|
+
gptOssProvider.compat.supportsReasoningEffort = true;
|
|
117
|
+
changed = true;
|
|
118
|
+
}
|
|
110
119
|
}
|
|
111
120
|
for (const providerName of ["alem-ai-plus-qwen3-6", "alem-ai-plus-gemma4"]) {
|
|
112
121
|
const provider = parsed.providers[providerName];
|
|
@@ -119,6 +128,31 @@ function migrateAlemBaseUrls() {
|
|
|
119
128
|
provider.baseUrl = "https://llm.alem.ai/v1";
|
|
120
129
|
changed = true;
|
|
121
130
|
}
|
|
131
|
+
if (!provider.compat || typeof provider.compat !== "object") {
|
|
132
|
+
provider.compat = {};
|
|
133
|
+
}
|
|
134
|
+
if (provider.compat.supportsReasoningEffort !== true) {
|
|
135
|
+
provider.compat.supportsReasoningEffort = true;
|
|
136
|
+
changed = true;
|
|
137
|
+
}
|
|
138
|
+
if (Array.isArray(provider.models)) {
|
|
139
|
+
for (const model of provider.models) {
|
|
140
|
+
if (!model || typeof model !== "object") {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (!model.compat || typeof model.compat !== "object") {
|
|
144
|
+
model.compat = {};
|
|
145
|
+
}
|
|
146
|
+
if (model.compat.supportsReasoningEffort !== true) {
|
|
147
|
+
model.compat.supportsReasoningEffort = true;
|
|
148
|
+
changed = true;
|
|
149
|
+
}
|
|
150
|
+
if (model.reasoning !== true) {
|
|
151
|
+
model.reasoning = true;
|
|
152
|
+
changed = true;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
122
156
|
}
|
|
123
157
|
if (changed) {
|
|
124
158
|
writeFileSync(modelsPath, `${JSON.stringify(parsed, null, 2)}\n`, "utf-8");
|
|
@@ -134,7 +134,7 @@ export class FooterComponent {
|
|
|
134
134
|
if (state.model?.reasoning) {
|
|
135
135
|
const thinkingLevel = state.thinkingLevel || "off";
|
|
136
136
|
rightSideWithoutProvider =
|
|
137
|
-
thinkingLevel === "off" ? `${modelName} | reasoning
|
|
137
|
+
thinkingLevel === "off" ? `${modelName} | reasoning fast` : `${modelName} | reasoning ${thinkingLevel}`;
|
|
138
138
|
}
|
|
139
139
|
const providerName = state.model?.provider || "none";
|
|
140
140
|
const sessionValue = sessionName || "unnamed";
|
|
@@ -6,11 +6,10 @@ const THINKING_SELECT_LIST_LAYOUT = {
|
|
|
6
6
|
maxPrimaryColumnWidth: 32,
|
|
7
7
|
};
|
|
8
8
|
const LEVEL_DESCRIPTIONS = {
|
|
9
|
-
off: "
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
high: "Deep analysis pass (~16k tokens)",
|
|
9
|
+
off: "Fast mode. Direct output, lowest latency.",
|
|
10
|
+
low: "Light analysis. Quick check before answering.",
|
|
11
|
+
medium: "Balanced analysis. Best default for most tasks.",
|
|
12
|
+
high: "Deep analysis. Slower, but stronger reasoning.",
|
|
14
13
|
xhigh: "Maximum analysis pass (~32k tokens)",
|
|
15
14
|
};
|
|
16
15
|
/**
|
|
@@ -22,7 +21,7 @@ export class ThinkingSelectorComponent extends Container {
|
|
|
22
21
|
super();
|
|
23
22
|
const thinkingLevels = availableLevels.map((level) => ({
|
|
24
23
|
value: level,
|
|
25
|
-
label: level,
|
|
24
|
+
label: level === "off" ? "fast" : level,
|
|
26
25
|
description: LEVEL_DESCRIPTIONS[level],
|
|
27
26
|
}));
|
|
28
27
|
// Add top border
|