openbot 0.2.11 → 0.2.13
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/.prettierrc +8 -0
- package/AGENTS.md +68 -0
- package/CONTRIBUTING.md +74 -0
- package/LICENSE +21 -0
- package/README.md +117 -14
- package/dist/agents/system.js +106 -0
- package/dist/app/cli.js +27 -0
- package/dist/app/config.js +64 -0
- package/dist/app/server.js +237 -0
- package/dist/app/utils.js +35 -0
- package/dist/harness/agent-harness.js +45 -0
- package/dist/harness/mcp.js +61 -0
- package/dist/harness/orchestrator.js +273 -0
- package/dist/harness/process.js +7 -0
- package/dist/plugins/ai-sdk.js +141 -0
- package/dist/plugins/delegation.js +52 -0
- package/dist/plugins/mcp.js +140 -0
- package/dist/plugins/storage.js +502 -0
- package/dist/plugins/ui.js +47 -0
- package/dist/registry/plugins.js +73 -0
- package/dist/services/storage.js +724 -0
- package/docs/README.md +7 -0
- package/docs/agents.md +83 -0
- package/docs/architecture.md +34 -0
- package/docs/plugins.md +77 -0
- package/logo-black.png +0 -0
- package/{dist/assets/logo.js → logo-black.svg} +24 -24
- package/{dist/ui/sidebar.js → logo-white.svg} +23 -88
- package/package.json +10 -9
- package/src/agents/system.ts +112 -0
- package/src/app/cli.ts +38 -0
- package/src/app/config.ts +104 -0
- package/src/app/server.ts +284 -0
- package/src/app/types.ts +476 -0
- package/src/app/utils.ts +43 -0
- package/src/assets/icon.svg +1 -0
- package/src/harness/agent-harness.ts +58 -0
- package/src/harness/mcp.ts +78 -0
- package/src/harness/orchestrator.ts +342 -0
- package/src/harness/process.ts +9 -0
- package/src/harness/types.ts +34 -0
- package/src/plugins/ai-sdk.ts +197 -0
- package/src/plugins/delegation.ts +60 -0
- package/src/plugins/mcp.ts +154 -0
- package/src/plugins/storage.ts +725 -0
- package/src/plugins/ui.ts +57 -0
- package/src/registry/plugins.ts +85 -0
- package/src/services/storage.ts +957 -0
- package/tsconfig.json +18 -0
- package/dist/agents/agent-creator.js +0 -74
- package/dist/agents/browser-agent.js +0 -31
- package/dist/agents/os-agent.js +0 -32
- package/dist/agents/planner-agent.js +0 -32
- package/dist/agents/topic-agent.js +0 -46
- package/dist/architecture/execution-engine.js +0 -151
- package/dist/architecture/intent-classifier.js +0 -26
- package/dist/architecture/planner.js +0 -106
- package/dist/automation-worker.js +0 -121
- package/dist/automations.js +0 -52
- package/dist/cli.js +0 -275
- package/dist/config.js +0 -53
- package/dist/core/agents.js +0 -41
- package/dist/core/delegation.js +0 -230
- package/dist/core/manager.js +0 -96
- package/dist/core/plugins.js +0 -74
- package/dist/core/router.js +0 -191
- package/dist/handlers/init.js +0 -29
- package/dist/handlers/session-change.js +0 -21
- package/dist/handlers/settings.js +0 -47
- package/dist/handlers/tab-change.js +0 -14
- package/dist/installers.js +0 -156
- package/dist/marketplace.js +0 -80
- package/dist/model-catalog.js +0 -132
- package/dist/model-defaults.js +0 -25
- package/dist/models.js +0 -47
- package/dist/open-bot.js +0 -51
- package/dist/orchestrator/direct-invocation.js +0 -13
- package/dist/orchestrator/events.js +0 -36
- package/dist/orchestrator/state.js +0 -54
- package/dist/orchestrator.js +0 -422
- package/dist/plugins/agent/index.js +0 -81
- package/dist/plugins/approval/index.js +0 -100
- package/dist/plugins/brain/identity.js +0 -77
- package/dist/plugins/brain/index.js +0 -204
- package/dist/plugins/brain/memory.js +0 -120
- package/dist/plugins/brain/prompt.js +0 -46
- package/dist/plugins/brain/types.js +0 -45
- package/dist/plugins/brain/ui.js +0 -7
- package/dist/plugins/browser/index.js +0 -629
- package/dist/plugins/browser/ui.js +0 -13
- package/dist/plugins/file-system/index.js +0 -171
- package/dist/plugins/file-system/ui.js +0 -6
- package/dist/plugins/llm/context-budget.js +0 -139
- package/dist/plugins/llm/context-shaping.js +0 -177
- package/dist/plugins/llm/index.js +0 -380
- package/dist/plugins/memory/index.js +0 -220
- package/dist/plugins/memory/memory.js +0 -122
- package/dist/plugins/memory/prompt.js +0 -55
- package/dist/plugins/memory/types.js +0 -45
- package/dist/plugins/meta-agent/index.js +0 -570
- package/dist/plugins/meta-agent/ui.js +0 -11
- package/dist/plugins/shell/index.js +0 -100
- package/dist/plugins/shell/ui.js +0 -6
- package/dist/plugins/skills/index.js +0 -286
- package/dist/plugins/skills/types.js +0 -50
- package/dist/plugins/skills/ui.js +0 -12
- package/dist/registry/agent-registry.js +0 -35
- package/dist/registry/index.js +0 -2
- package/dist/registry/plugin-loader.js +0 -499
- package/dist/registry/plugin-registry.js +0 -44
- package/dist/registry/ts-agent-loader.js +0 -82
- package/dist/registry/yaml-agent-loader.js +0 -246
- package/dist/runtime/execution-trace.js +0 -41
- package/dist/runtime/intent-routing.js +0 -26
- package/dist/runtime/openbot-runtime.js +0 -354
- package/dist/server.js +0 -890
- package/dist/session.js +0 -179
- package/dist/ui/block.js +0 -12
- package/dist/ui/header.js +0 -52
- package/dist/ui/layout.js +0 -26
- package/dist/ui/navigation.js +0 -15
- package/dist/ui/settings.js +0 -106
- package/dist/ui/skills.js +0 -7
- package/dist/ui/thread.js +0 -16
- package/dist/ui/widgets/action-list.js +0 -2
- package/dist/ui/widgets/approval-card.js +0 -9
- package/dist/ui/widgets/code-snippet.js +0 -2
- package/dist/ui/widgets/data-block.js +0 -2
- package/dist/ui/widgets/data-table.js +0 -2
- package/dist/ui/widgets/delegation.js +0 -29
- package/dist/ui/widgets/empty-state.js +0 -2
- package/dist/ui/widgets/index.js +0 -23
- package/dist/ui/widgets/inquiry.js +0 -7
- package/dist/ui/widgets/key-value.js +0 -2
- package/dist/ui/widgets/progress-step.js +0 -2
- package/dist/ui/widgets/resource-card.js +0 -2
- package/dist/ui/widgets/status.js +0 -2
- package/dist/ui/widgets/todo-list.js +0 -2
- package/dist/version.js +0 -62
- /package/dist/{types.js → app/types.js} +0 -0
- /package/dist/{architecture/contracts.js → harness/types.js} +0 -0
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
import { uiEvent } from "../../ui/block.js";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import * as fs from "node:fs/promises";
|
|
4
|
-
import * as path from "node:path";
|
|
5
|
-
import { statusWidget } from "../../ui/widgets/status.js";
|
|
6
|
-
export const fileSystemToolDefinitions = {
|
|
7
|
-
readFile: {
|
|
8
|
-
description: "Read the contents of a file. Path can be absolute or relative to the current working directory.",
|
|
9
|
-
inputSchema: z.object({
|
|
10
|
-
path: z.string().describe("The path to the file (e.g., 'src/main.ts' or '/etc/hosts')"),
|
|
11
|
-
}),
|
|
12
|
-
},
|
|
13
|
-
writeFile: {
|
|
14
|
-
description: "Write content to a file. Path can be absolute or relative to the current working directory.",
|
|
15
|
-
inputSchema: z.object({
|
|
16
|
-
path: z.string().describe("The path to the file (e.g., 'new-file.ts')"),
|
|
17
|
-
content: z.string().describe("The content to write to the file"),
|
|
18
|
-
}),
|
|
19
|
-
},
|
|
20
|
-
listFiles: {
|
|
21
|
-
description: "List files in a directory. Path can be absolute or relative to the current working directory.",
|
|
22
|
-
inputSchema: z.object({
|
|
23
|
-
path: z.string().describe("The path to the directory (use '.' for current directory)"),
|
|
24
|
-
}),
|
|
25
|
-
},
|
|
26
|
-
deleteFile: {
|
|
27
|
-
description: "Delete a file. Path can be absolute or relative to the current working directory.",
|
|
28
|
-
inputSchema: z.object({
|
|
29
|
-
path: z.string().describe("The path to the file"),
|
|
30
|
-
}),
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
/**
|
|
34
|
-
* Truncates a string by keeping the first and last N characters.
|
|
35
|
-
*/
|
|
36
|
-
function truncate(str, maxChars) {
|
|
37
|
-
if (!str || str.length <= maxChars)
|
|
38
|
-
return str;
|
|
39
|
-
const half = Math.floor(maxChars / 2);
|
|
40
|
-
const truncatedCount = str.length - maxChars;
|
|
41
|
-
return `${str.slice(0, half)}\n\n[... ${truncatedCount} characters truncated ...]\n\n${str.slice(-half)}`;
|
|
42
|
-
}
|
|
43
|
-
export const fileSystemPlugin = (options = {}) => (builder) => {
|
|
44
|
-
const { baseDir = "/", maxFileReadLength = 10000 } = options;
|
|
45
|
-
const resolvePath = (p, stateCwd) => {
|
|
46
|
-
// If p is absolute, resolve from root. If relative, resolve from stateCwd or baseDir.
|
|
47
|
-
const resolved = path.isAbsolute(p) ? p : path.resolve(stateCwd || baseDir, p);
|
|
48
|
-
return resolved;
|
|
49
|
-
};
|
|
50
|
-
builder.on("action:readFile", async function* (event, { state }) {
|
|
51
|
-
const { path: filePath, toolCallId } = event.data;
|
|
52
|
-
yield {
|
|
53
|
-
type: "file-system:status",
|
|
54
|
-
data: { message: `Reading file: ${filePath} (relative to ${state.cwd || baseDir})` }
|
|
55
|
-
};
|
|
56
|
-
try {
|
|
57
|
-
const content = await fs.readFile(resolvePath(filePath, state.cwd), "utf-8");
|
|
58
|
-
yield {
|
|
59
|
-
type: "action:result",
|
|
60
|
-
data: {
|
|
61
|
-
action: "readFile",
|
|
62
|
-
result: { content: truncate(content, maxFileReadLength) },
|
|
63
|
-
toolCallId
|
|
64
|
-
},
|
|
65
|
-
};
|
|
66
|
-
yield {
|
|
67
|
-
type: "file-system:status",
|
|
68
|
-
data: { message: `File read successfully`, severity: "success" }
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
catch (error) {
|
|
72
|
-
yield {
|
|
73
|
-
type: "action:result",
|
|
74
|
-
data: { action: "readFile", result: { error: error.message }, toolCallId },
|
|
75
|
-
};
|
|
76
|
-
yield {
|
|
77
|
-
type: "file-system:status",
|
|
78
|
-
data: { message: `File read failed: ${error.message}`, severity: "error" }
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
builder.on("action:writeFile", async function* (event, { state }) {
|
|
83
|
-
const { path: filePath, content, toolCallId } = event.data;
|
|
84
|
-
yield {
|
|
85
|
-
type: "file-system:status",
|
|
86
|
-
data: { message: `Writing file: ${filePath}` }
|
|
87
|
-
};
|
|
88
|
-
try {
|
|
89
|
-
const fullPath = resolvePath(filePath, state.cwd);
|
|
90
|
-
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
91
|
-
await fs.writeFile(fullPath, content, "utf-8");
|
|
92
|
-
yield {
|
|
93
|
-
type: "action:result",
|
|
94
|
-
data: { action: "writeFile", result: { success: true }, toolCallId },
|
|
95
|
-
};
|
|
96
|
-
yield {
|
|
97
|
-
type: "file-system:status",
|
|
98
|
-
data: { message: `File written successfully`, severity: "success" }
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
catch (error) {
|
|
102
|
-
yield {
|
|
103
|
-
type: "action:result",
|
|
104
|
-
data: { action: "writeFile", result: { error: error.message }, toolCallId },
|
|
105
|
-
};
|
|
106
|
-
yield {
|
|
107
|
-
type: "file-system:status",
|
|
108
|
-
data: { message: `File write failed: ${error.message}`, severity: "error" }
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
builder.on("action:listFiles", async function* (event, { state }) {
|
|
113
|
-
const { path: dirPath, toolCallId } = event.data;
|
|
114
|
-
yield {
|
|
115
|
-
type: "file-system:status",
|
|
116
|
-
data: { message: `Listing files in: ${dirPath}` }
|
|
117
|
-
};
|
|
118
|
-
try {
|
|
119
|
-
const files = await fs.readdir(resolvePath(dirPath, state.cwd));
|
|
120
|
-
yield {
|
|
121
|
-
type: "file-system:status",
|
|
122
|
-
data: { message: `Files listed successfully`, severity: "success" }
|
|
123
|
-
};
|
|
124
|
-
yield {
|
|
125
|
-
type: "action:result",
|
|
126
|
-
data: { action: "listFiles", result: { files }, toolCallId },
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
catch (error) {
|
|
130
|
-
yield {
|
|
131
|
-
type: "file-system:status",
|
|
132
|
-
data: { message: `Files listing failed: ${error.message}`, severity: "error" }
|
|
133
|
-
};
|
|
134
|
-
yield {
|
|
135
|
-
type: "action:result",
|
|
136
|
-
data: { action: "listFiles", result: { error: error.message }, toolCallId },
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
builder.on("action:deleteFile", async function* (event, { state }) {
|
|
141
|
-
const { path: filePath, toolCallId } = event.data;
|
|
142
|
-
yield {
|
|
143
|
-
type: "file-system:status",
|
|
144
|
-
data: { message: `Deleting file: ${filePath}` }
|
|
145
|
-
};
|
|
146
|
-
try {
|
|
147
|
-
await fs.unlink(resolvePath(filePath, state.cwd));
|
|
148
|
-
yield {
|
|
149
|
-
type: "action:result",
|
|
150
|
-
data: { action: "deleteFile", result: { success: true }, toolCallId },
|
|
151
|
-
};
|
|
152
|
-
yield {
|
|
153
|
-
type: "file-system:status",
|
|
154
|
-
data: { message: `File deleted successfully`, severity: "success" }
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
catch (error) {
|
|
158
|
-
yield {
|
|
159
|
-
type: "action:result",
|
|
160
|
-
data: { action: "deleteFile", result: { error: error.message }, toolCallId },
|
|
161
|
-
};
|
|
162
|
-
yield {
|
|
163
|
-
type: "file-system:status",
|
|
164
|
-
data: { message: `File deletion failed: ${error.message}`, severity: "error" }
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
builder.on("file-system:status", async function* (event) {
|
|
169
|
-
yield uiEvent(statusWidget(event.data.message, event.data.severity));
|
|
170
|
-
});
|
|
171
|
-
};
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
function truncateMiddle(value, maxChars) {
|
|
2
|
-
if (value.length <= maxChars)
|
|
3
|
-
return value;
|
|
4
|
-
const half = Math.floor(maxChars / 2);
|
|
5
|
-
const removed = value.length - maxChars;
|
|
6
|
-
return `${value.slice(0, half)}\n\n[... ${removed} characters truncated ...]\n\n${value.slice(-half)}`;
|
|
7
|
-
}
|
|
8
|
-
function estimateStringTokens(value) {
|
|
9
|
-
if (!value)
|
|
10
|
-
return 0;
|
|
11
|
-
return Math.max(1, Math.ceil(value.length / 4));
|
|
12
|
-
}
|
|
13
|
-
function estimateMessageTokens(message) {
|
|
14
|
-
if (typeof message.content === "string") {
|
|
15
|
-
return estimateStringTokens(message.content);
|
|
16
|
-
}
|
|
17
|
-
if (Array.isArray(message.content)) {
|
|
18
|
-
let total = 0;
|
|
19
|
-
for (const part of message.content) {
|
|
20
|
-
if (typeof part === "string") {
|
|
21
|
-
total += estimateStringTokens(part);
|
|
22
|
-
}
|
|
23
|
-
else if (part && typeof part === "object") {
|
|
24
|
-
total += estimateStringTokens(JSON.stringify(part));
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return total;
|
|
28
|
-
}
|
|
29
|
-
return 0;
|
|
30
|
-
}
|
|
31
|
-
function cloneWithTrimmedSystemMessages(messages, maxSystemChars) {
|
|
32
|
-
return messages.map((message) => {
|
|
33
|
-
if (message.role !== "system" || typeof message.content !== "string") {
|
|
34
|
-
return message;
|
|
35
|
-
}
|
|
36
|
-
return {
|
|
37
|
-
...message,
|
|
38
|
-
content: truncateMiddle(message.content, maxSystemChars),
|
|
39
|
-
};
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
function getLeadingSystemCount(messages) {
|
|
43
|
-
let count = 0;
|
|
44
|
-
for (const message of messages) {
|
|
45
|
-
if (message.role !== "system")
|
|
46
|
-
break;
|
|
47
|
-
count++;
|
|
48
|
-
}
|
|
49
|
-
return count;
|
|
50
|
-
}
|
|
51
|
-
function collectAssistantToolPairings(messages) {
|
|
52
|
-
const toolCallToAssistant = new Map();
|
|
53
|
-
const assistantToToolResults = new Map();
|
|
54
|
-
messages.forEach((message, index) => {
|
|
55
|
-
if (message.role !== "assistant" || !Array.isArray(message.content))
|
|
56
|
-
return;
|
|
57
|
-
for (const part of message.content) {
|
|
58
|
-
if (part
|
|
59
|
-
&& typeof part === "object"
|
|
60
|
-
&& part.type === "tool-call"
|
|
61
|
-
&& typeof part.toolCallId === "string") {
|
|
62
|
-
toolCallToAssistant.set(part.toolCallId, index);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
messages.forEach((message, index) => {
|
|
67
|
-
if (message.role !== "tool" || !Array.isArray(message.content))
|
|
68
|
-
return;
|
|
69
|
-
for (const part of message.content) {
|
|
70
|
-
if (!part || typeof part !== "object")
|
|
71
|
-
continue;
|
|
72
|
-
const toolCallId = part.toolCallId;
|
|
73
|
-
if (typeof toolCallId !== "string")
|
|
74
|
-
continue;
|
|
75
|
-
const assistantIndex = toolCallToAssistant.get(toolCallId);
|
|
76
|
-
if (typeof assistantIndex !== "number")
|
|
77
|
-
continue;
|
|
78
|
-
const bucket = assistantToToolResults.get(assistantIndex) ?? new Set();
|
|
79
|
-
bucket.add(index);
|
|
80
|
-
assistantToToolResults.set(assistantIndex, bucket);
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
return assistantToToolResults;
|
|
84
|
-
}
|
|
85
|
-
function buildAtomicGroupIndices(index, messages, assistantToToolResults) {
|
|
86
|
-
const message = messages[index];
|
|
87
|
-
if (!message)
|
|
88
|
-
return [];
|
|
89
|
-
if (message.role === "assistant") {
|
|
90
|
-
const results = assistantToToolResults.get(index);
|
|
91
|
-
if (!results || results.size === 0)
|
|
92
|
-
return [index];
|
|
93
|
-
return [index, ...Array.from(results)].sort((a, b) => a - b);
|
|
94
|
-
}
|
|
95
|
-
if (message.role === "tool") {
|
|
96
|
-
for (const [assistantIndex, resultIndices] of assistantToToolResults.entries()) {
|
|
97
|
-
if (resultIndices.has(index)) {
|
|
98
|
-
return [assistantIndex, ...Array.from(resultIndices)].sort((a, b) => a - b);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return [index];
|
|
103
|
-
}
|
|
104
|
-
export function buildBudgetedMessages(messages, options) {
|
|
105
|
-
if (messages.length === 0)
|
|
106
|
-
return messages;
|
|
107
|
-
const normalized = cloneWithTrimmedSystemMessages(messages, options.maxSystemChars);
|
|
108
|
-
const systemCount = getLeadingSystemCount(normalized);
|
|
109
|
-
const leadingSystem = normalized.slice(0, systemCount);
|
|
110
|
-
const history = normalized.slice(systemCount);
|
|
111
|
-
const usableBudget = Math.max(1, options.maxContextTokens - options.reserveOutputTokens);
|
|
112
|
-
let used = leadingSystem.reduce((sum, message) => sum + estimateMessageTokens(message), 0);
|
|
113
|
-
const requiredIndices = new Set();
|
|
114
|
-
for (let i = history.length - 1; i >= 0; i--) {
|
|
115
|
-
if (history[i].role === "user") {
|
|
116
|
-
requiredIndices.add(i);
|
|
117
|
-
break;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
const assistantToToolResults = collectAssistantToolPairings(history);
|
|
121
|
-
const included = new Set();
|
|
122
|
-
const visited = new Set();
|
|
123
|
-
for (let i = history.length - 1; i >= 0; i--) {
|
|
124
|
-
if (visited.has(i))
|
|
125
|
-
continue;
|
|
126
|
-
const group = buildAtomicGroupIndices(i, history, assistantToToolResults);
|
|
127
|
-
for (const idx of group)
|
|
128
|
-
visited.add(idx);
|
|
129
|
-
const groupTokens = group.reduce((sum, idx) => sum + estimateMessageTokens(history[idx]), 0);
|
|
130
|
-
const isRequired = group.some((idx) => requiredIndices.has(idx));
|
|
131
|
-
if (isRequired || used + groupTokens <= usableBudget) {
|
|
132
|
-
for (const idx of group)
|
|
133
|
-
included.add(idx);
|
|
134
|
-
used += groupTokens;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
const selectedHistory = history.filter((_, index) => included.has(index));
|
|
138
|
-
return [...leadingSystem, ...selectedHistory];
|
|
139
|
-
}
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
const DEFAULT_OPTIONS = {
|
|
2
|
-
maxRecentRawMessages: 4,
|
|
3
|
-
maxRelevantMessages: 6,
|
|
4
|
-
maxContextChars: 12000,
|
|
5
|
-
maxTurnSummaries: 20,
|
|
6
|
-
maxConstraints: 8,
|
|
7
|
-
};
|
|
8
|
-
const STOP_WORDS = new Set([
|
|
9
|
-
"the",
|
|
10
|
-
"and",
|
|
11
|
-
"for",
|
|
12
|
-
"that",
|
|
13
|
-
"with",
|
|
14
|
-
"this",
|
|
15
|
-
"from",
|
|
16
|
-
"have",
|
|
17
|
-
"your",
|
|
18
|
-
"will",
|
|
19
|
-
"would",
|
|
20
|
-
"should",
|
|
21
|
-
"could",
|
|
22
|
-
"about",
|
|
23
|
-
"what",
|
|
24
|
-
"when",
|
|
25
|
-
"where",
|
|
26
|
-
"which",
|
|
27
|
-
"into",
|
|
28
|
-
"just",
|
|
29
|
-
"like",
|
|
30
|
-
"than",
|
|
31
|
-
"then",
|
|
32
|
-
"there",
|
|
33
|
-
"their",
|
|
34
|
-
"them",
|
|
35
|
-
"also",
|
|
36
|
-
"need",
|
|
37
|
-
"want",
|
|
38
|
-
"please",
|
|
39
|
-
]);
|
|
40
|
-
function mergeOptions(overrides) {
|
|
41
|
-
return {
|
|
42
|
-
...DEFAULT_OPTIONS,
|
|
43
|
-
...(overrides ?? {}),
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
function clip(text, maxChars) {
|
|
47
|
-
if (text.length <= maxChars)
|
|
48
|
-
return text;
|
|
49
|
-
return `${text.slice(0, maxChars - 3)}...`;
|
|
50
|
-
}
|
|
51
|
-
function toSearchText(message) {
|
|
52
|
-
return message.content.toLowerCase();
|
|
53
|
-
}
|
|
54
|
-
function tokenize(text) {
|
|
55
|
-
return text
|
|
56
|
-
.toLowerCase()
|
|
57
|
-
.replace(/[^a-z0-9\s]/g, " ")
|
|
58
|
-
.split(/\s+/)
|
|
59
|
-
.filter((token) => token.length > 2 && !STOP_WORDS.has(token));
|
|
60
|
-
}
|
|
61
|
-
function overlapScore(messageText, queryTerms) {
|
|
62
|
-
if (queryTerms.length === 0)
|
|
63
|
-
return 0;
|
|
64
|
-
let matched = 0;
|
|
65
|
-
for (const term of queryTerms) {
|
|
66
|
-
if (messageText.includes(term))
|
|
67
|
-
matched += 1;
|
|
68
|
-
}
|
|
69
|
-
return matched / queryTerms.length;
|
|
70
|
-
}
|
|
71
|
-
function recencyBoost(index, total) {
|
|
72
|
-
if (total <= 1)
|
|
73
|
-
return 0;
|
|
74
|
-
return index / (total - 1);
|
|
75
|
-
}
|
|
76
|
-
function estimateChars(messages) {
|
|
77
|
-
return messages.reduce((sum, m) => sum + (m.content?.length ?? 0), 0);
|
|
78
|
-
}
|
|
79
|
-
function buildContextBrief(state, maxChars) {
|
|
80
|
-
if (!state)
|
|
81
|
-
return "";
|
|
82
|
-
const lines = [];
|
|
83
|
-
if (state.currentGoal)
|
|
84
|
-
lines.push(`Current objective: ${state.currentGoal}`);
|
|
85
|
-
if (state.constraints && state.constraints.length > 0) {
|
|
86
|
-
lines.push(`Constraints: ${state.constraints.join(" | ")}`);
|
|
87
|
-
}
|
|
88
|
-
if (state.rollingSummary)
|
|
89
|
-
lines.push(`Recent summary: ${state.rollingSummary}`);
|
|
90
|
-
if (lines.length === 0)
|
|
91
|
-
return "";
|
|
92
|
-
return clip(`System context for this turn:\n${lines.map((line) => `- ${line}`).join("\n")}`, maxChars);
|
|
93
|
-
}
|
|
94
|
-
export function buildShapedContext(input) {
|
|
95
|
-
const options = mergeOptions(input.options);
|
|
96
|
-
const messages = input.messages;
|
|
97
|
-
if (messages.length <= options.maxRecentRawMessages) {
|
|
98
|
-
const brief = buildContextBrief(input.contextState, 1500);
|
|
99
|
-
return brief
|
|
100
|
-
? [{ role: "user", content: brief }, ...messages]
|
|
101
|
-
: messages;
|
|
102
|
-
}
|
|
103
|
-
const recentStart = Math.max(0, messages.length - options.maxRecentRawMessages);
|
|
104
|
-
const recent = messages.slice(recentStart);
|
|
105
|
-
const older = messages.slice(0, recentStart);
|
|
106
|
-
const latestUser = [...messages]
|
|
107
|
-
.reverse()
|
|
108
|
-
.find((message) => message.role === "user");
|
|
109
|
-
const queryTerms = latestUser ? tokenize(latestUser.content) : [];
|
|
110
|
-
const scored = older
|
|
111
|
-
.map((message, index) => {
|
|
112
|
-
const text = toSearchText(message);
|
|
113
|
-
const relevance = overlapScore(text, queryTerms);
|
|
114
|
-
const score = relevance * 0.8 + recencyBoost(index, older.length) * 0.2;
|
|
115
|
-
return { index, score };
|
|
116
|
-
})
|
|
117
|
-
.filter((row) => row.score > 0)
|
|
118
|
-
.sort((a, b) => b.score - a.score)
|
|
119
|
-
.slice(0, options.maxRelevantMessages)
|
|
120
|
-
.sort((a, b) => a.index - b.index);
|
|
121
|
-
const selectedOlder = scored.map((row) => older[row.index]);
|
|
122
|
-
const brief = buildContextBrief(input.contextState, 1500);
|
|
123
|
-
let selected = [
|
|
124
|
-
...(brief ? [{ role: "user", content: brief }] : []),
|
|
125
|
-
...selectedOlder,
|
|
126
|
-
...recent,
|
|
127
|
-
];
|
|
128
|
-
while (estimateChars(selected) > options.maxContextChars) {
|
|
129
|
-
const removableIndex = selected.findIndex((message, index) => {
|
|
130
|
-
if (brief && index === 0)
|
|
131
|
-
return false;
|
|
132
|
-
return index < selected.length - options.maxRecentRawMessages;
|
|
133
|
-
});
|
|
134
|
-
if (removableIndex === -1)
|
|
135
|
-
break;
|
|
136
|
-
selected.splice(removableIndex, 1);
|
|
137
|
-
}
|
|
138
|
-
return selected;
|
|
139
|
-
}
|
|
140
|
-
function extractConstraints(text, existing, maxConstraints) {
|
|
141
|
-
const hasConstraintLanguage = /\b(do not|don't|must|never|always|only)\b/i.test(text);
|
|
142
|
-
if (!hasConstraintLanguage)
|
|
143
|
-
return existing;
|
|
144
|
-
const normalized = clip(text.trim().replace(/\s+/g, " "), 180);
|
|
145
|
-
if (!normalized)
|
|
146
|
-
return existing;
|
|
147
|
-
if (existing.includes(normalized))
|
|
148
|
-
return existing;
|
|
149
|
-
const next = [...existing, normalized];
|
|
150
|
-
return next.slice(-maxConstraints);
|
|
151
|
-
}
|
|
152
|
-
export function updateContextState(input, optionsOverride) {
|
|
153
|
-
const options = mergeOptions(optionsOverride);
|
|
154
|
-
const existing = input.contextState ?? {};
|
|
155
|
-
const constraints = extractConstraints(input.latestUserMessage, existing.constraints ?? [], options.maxConstraints);
|
|
156
|
-
const compactUser = clip(input.latestUserMessage.trim().replace(/\s+/g, " "), 200);
|
|
157
|
-
const compactAssistant = clip(input.latestAssistantMessage.trim().replace(/\s+/g, " "), 240);
|
|
158
|
-
const turnSummary = compactUser || compactAssistant
|
|
159
|
-
? `U: ${compactUser || "-"} | A: ${compactAssistant || "-"}`
|
|
160
|
-
: "";
|
|
161
|
-
const turnSummaries = turnSummary
|
|
162
|
-
? [...(existing.turnSummaries ?? []), turnSummary].slice(-options.maxTurnSummaries)
|
|
163
|
-
: existing.turnSummaries ?? [];
|
|
164
|
-
const rollingSummary = turnSummaries.slice(-6).join(" || ");
|
|
165
|
-
const latestUserMessage = input.latestUserMessage.trim();
|
|
166
|
-
const isToolEcho = latestUserMessage.startsWith("System: Action ");
|
|
167
|
-
const currentGoal = !isToolEcho && latestUserMessage
|
|
168
|
-
? clip(latestUserMessage, 240)
|
|
169
|
-
: existing.currentGoal;
|
|
170
|
-
return {
|
|
171
|
-
currentGoal,
|
|
172
|
-
constraints,
|
|
173
|
-
turnSummaries,
|
|
174
|
-
rollingSummary,
|
|
175
|
-
updatedAt: new Date().toISOString(),
|
|
176
|
-
};
|
|
177
|
-
}
|