open-research 1.1.2 → 1.2.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/builtin-skills/draft-paper/references/formats/imrad.md +150 -0
- package/builtin-skills/draft-paper/references/formats/position-paper.md +170 -0
- package/builtin-skills/draft-paper/references/formats/systematic-review.md +176 -0
- package/builtin-skills/draft-paper/references/venues/acl.md +146 -0
- package/builtin-skills/draft-paper/references/venues/chi.md +131 -0
- package/builtin-skills/draft-paper/references/venues/ieee-transactions.md +173 -0
- package/builtin-skills/draft-paper/references/venues/nature.md +167 -0
- package/builtin-skills/draft-paper/references/venues/neurips.md +115 -0
- package/dist/chunk-32Q63SUH.js +6440 -0
- package/dist/chunk-CRSHN3PQ.js +98 -0
- package/dist/chunk-EW27OWCA.js +272 -0
- package/dist/{chunk-HRVDYJEC.js → chunk-KOBMIIQM.js} +6 -1
- package/dist/chunk-W3MWVV2O.js +533 -0
- package/dist/{chunk-TJA4CAZE.js → chunk-XOSPMXYH.js} +43 -101
- package/dist/cli.js +293 -6557
- package/dist/context-manager-2FA5ANQN.js +28 -0
- package/dist/finish-subagent-NCDLMSBT.js +22 -0
- package/dist/server/serve.js +20 -0
- package/dist/server-PLHMTHCG.js +16 -0
- package/dist/{sessions-KL4LUGD7.js → sessions-SDED5HVO.js} +1 -1
- package/dist/traverse-citations-CPKPE33Y.js +90 -0
- package/dist/{web-search-IBZ6UAXL.js → web-search-OBNKKXV7.js} +87 -14
- package/package.json +3 -1
- package/dist/{manager-queue-FBAUCAGI.js → manager-queue-NK5B47A4.js} +4 -4
- package/dist/{query-agent-WM6UNZ37.js → query-agent-HINWAVC5.js} +3 -3
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readJsonFile,
|
|
3
|
+
writeJsonFile
|
|
4
|
+
} from "./chunk-77Q5B5H7.js";
|
|
5
|
+
import {
|
|
6
|
+
getOpenResearchConfigFile
|
|
7
|
+
} from "./chunk-4HCPHCC2.js";
|
|
8
|
+
|
|
9
|
+
// src/lib/config/store.ts
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
var themeValues = ["dark", "light"];
|
|
12
|
+
var providerValues = ["openai", "gemini"];
|
|
13
|
+
var openAIProviderConfigSchema = z.object({
|
|
14
|
+
apiKey: z.string().optional()
|
|
15
|
+
}).optional();
|
|
16
|
+
var geminiProviderConfigSchema = z.object({
|
|
17
|
+
apiKey: z.string().optional()
|
|
18
|
+
}).optional();
|
|
19
|
+
var openResearchConfigSchema = z.object({
|
|
20
|
+
version: z.literal(1),
|
|
21
|
+
defaults: z.object({
|
|
22
|
+
model: z.string().min(1),
|
|
23
|
+
reasoningEffort: z.enum(["low", "medium", "high", "xhigh"]),
|
|
24
|
+
editPolicy: z.literal("mixed")
|
|
25
|
+
}),
|
|
26
|
+
theme: z.enum(themeValues).default("dark"),
|
|
27
|
+
activeProvider: z.enum(providerValues).default("openai"),
|
|
28
|
+
lastWorkspace: z.string().nullable(),
|
|
29
|
+
providers: z.object({
|
|
30
|
+
openai: openAIProviderConfigSchema,
|
|
31
|
+
gemini: geminiProviderConfigSchema
|
|
32
|
+
}).optional(),
|
|
33
|
+
apiKeys: z.object({
|
|
34
|
+
openai: z.string().optional(),
|
|
35
|
+
semanticScholar: z.string().optional(),
|
|
36
|
+
openAlex: z.string().optional(),
|
|
37
|
+
brave: z.string().optional(),
|
|
38
|
+
gemini: z.string().optional()
|
|
39
|
+
}).optional()
|
|
40
|
+
});
|
|
41
|
+
var DEFAULT_OPEN_RESEARCH_CONFIG = {
|
|
42
|
+
version: 1,
|
|
43
|
+
defaults: {
|
|
44
|
+
model: "gpt-5.4",
|
|
45
|
+
reasoningEffort: "medium",
|
|
46
|
+
editPolicy: "mixed"
|
|
47
|
+
},
|
|
48
|
+
theme: "dark",
|
|
49
|
+
activeProvider: "openai",
|
|
50
|
+
lastWorkspace: null,
|
|
51
|
+
providers: {
|
|
52
|
+
openai: {},
|
|
53
|
+
gemini: {}
|
|
54
|
+
},
|
|
55
|
+
apiKeys: {}
|
|
56
|
+
};
|
|
57
|
+
function getConfiguredOpenAIApiKey(config) {
|
|
58
|
+
return config?.providers?.openai?.apiKey || config?.apiKeys?.openai;
|
|
59
|
+
}
|
|
60
|
+
function getSemanticScholarApiKey(config) {
|
|
61
|
+
return config?.apiKeys?.semanticScholar || process.env.SEMANTIC_SCHOLAR_API_KEY;
|
|
62
|
+
}
|
|
63
|
+
function getOpenAlexApiKey(config) {
|
|
64
|
+
return config?.apiKeys?.openAlex || process.env.OPENALEX_API_KEY;
|
|
65
|
+
}
|
|
66
|
+
function getBraveApiKey(config) {
|
|
67
|
+
return config?.apiKeys?.brave || process.env.BRAVE_API_KEY;
|
|
68
|
+
}
|
|
69
|
+
async function loadOpenResearchConfig(options) {
|
|
70
|
+
const configFile = getOpenResearchConfigFile(options);
|
|
71
|
+
const config = await readJsonFile(configFile, null);
|
|
72
|
+
if (!config) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
return openResearchConfigSchema.parse(config);
|
|
76
|
+
}
|
|
77
|
+
async function saveOpenResearchConfig(config, options) {
|
|
78
|
+
await writeJsonFile(getOpenResearchConfigFile(options), config);
|
|
79
|
+
}
|
|
80
|
+
async function ensureOpenResearchConfig(options) {
|
|
81
|
+
const existing = await loadOpenResearchConfig(options);
|
|
82
|
+
if (existing) {
|
|
83
|
+
return existing;
|
|
84
|
+
}
|
|
85
|
+
await writeJsonFile(getOpenResearchConfigFile(options), DEFAULT_OPEN_RESEARCH_CONFIG);
|
|
86
|
+
return DEFAULT_OPEN_RESEARCH_CONFIG;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export {
|
|
90
|
+
themeValues,
|
|
91
|
+
getConfiguredOpenAIApiKey,
|
|
92
|
+
getSemanticScholarApiKey,
|
|
93
|
+
getOpenAlexApiKey,
|
|
94
|
+
getBraveApiKey,
|
|
95
|
+
loadOpenResearchConfig,
|
|
96
|
+
saveOpenResearchConfig,
|
|
97
|
+
ensureOpenResearchConfig
|
|
98
|
+
};
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import {
|
|
2
|
+
selectModelForTask
|
|
3
|
+
} from "./chunk-GVEVKDGV.js";
|
|
4
|
+
|
|
5
|
+
// src/lib/agent/context-manager.ts
|
|
6
|
+
var BYTES_PER_TOKEN = 4;
|
|
7
|
+
function estimateTokens(text) {
|
|
8
|
+
const bytes = Buffer.byteLength(text, "utf8");
|
|
9
|
+
return Math.ceil(bytes / BYTES_PER_TOKEN);
|
|
10
|
+
}
|
|
11
|
+
function estimateMessageTokens(msg) {
|
|
12
|
+
let tokens = 4;
|
|
13
|
+
if (typeof msg.content === "string") {
|
|
14
|
+
tokens += estimateTokens(msg.content);
|
|
15
|
+
} else if (Array.isArray(msg.content)) {
|
|
16
|
+
for (const part of msg.content) {
|
|
17
|
+
if ("text" in part) tokens += estimateTokens(part.text);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
if (msg.tool_calls) {
|
|
21
|
+
for (const tc of msg.tool_calls) {
|
|
22
|
+
tokens += estimateTokens(tc.function.name) + estimateTokens(tc.function.arguments);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return tokens;
|
|
26
|
+
}
|
|
27
|
+
function estimateConversationTokens(messages) {
|
|
28
|
+
let total = 0;
|
|
29
|
+
for (const msg of messages) {
|
|
30
|
+
total += estimateMessageTokens(msg);
|
|
31
|
+
}
|
|
32
|
+
return total;
|
|
33
|
+
}
|
|
34
|
+
var MODEL_CONTEXT_WINDOWS = {
|
|
35
|
+
"gpt-5.4": 272e3,
|
|
36
|
+
"gpt-5.3-codex": 272e3,
|
|
37
|
+
"gpt-5.2-codex": 272e3,
|
|
38
|
+
"gpt-5.2": 272e3,
|
|
39
|
+
"gpt-5.1-codex": 272e3,
|
|
40
|
+
"gpt-5.1": 272e3,
|
|
41
|
+
"gpt-5": 272e3,
|
|
42
|
+
"gpt-4o": 128e3,
|
|
43
|
+
"gpt-5.4-mini": 128e3,
|
|
44
|
+
"o3": 2e5,
|
|
45
|
+
"o4-mini": 2e5,
|
|
46
|
+
"gemini-3.1-pro-preview": 1048576,
|
|
47
|
+
"gemini-3-flash-preview": 1048576
|
|
48
|
+
};
|
|
49
|
+
var DEFAULT_CONTEXT_WINDOW = 128e3;
|
|
50
|
+
var AUTO_COMPACT_TOKEN_LIMIT = 25e4;
|
|
51
|
+
function getContextWindow(model) {
|
|
52
|
+
return MODEL_CONTEXT_WINDOWS[model] ?? DEFAULT_CONTEXT_WINDOW;
|
|
53
|
+
}
|
|
54
|
+
function getCompactThreshold(model) {
|
|
55
|
+
const window = getContextWindow(model);
|
|
56
|
+
return window > AUTO_COMPACT_TOKEN_LIMIT ? AUTO_COMPACT_TOKEN_LIMIT : Math.floor(window * 0.8);
|
|
57
|
+
}
|
|
58
|
+
function emptyBreakdown() {
|
|
59
|
+
return { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 }, total: 0 };
|
|
60
|
+
}
|
|
61
|
+
function createSessionUsage() {
|
|
62
|
+
return {
|
|
63
|
+
cumulative: emptyBreakdown(),
|
|
64
|
+
lastTurn: emptyBreakdown(),
|
|
65
|
+
estimatedCurrentTokens: 0,
|
|
66
|
+
compactionCount: 0,
|
|
67
|
+
inputTokens: 0,
|
|
68
|
+
outputTokens: 0,
|
|
69
|
+
totalTokens: 0,
|
|
70
|
+
lastTurnTokens: 0
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function updateUsageFromApi(usage, apiUsage) {
|
|
74
|
+
const cached = apiUsage.cachedTokens ?? 0;
|
|
75
|
+
const reasoning = apiUsage.reasoningTokens ?? 0;
|
|
76
|
+
const adjustedInput = Math.max(0, apiUsage.promptTokens - cached);
|
|
77
|
+
const adjustedOutput = Math.max(0, apiUsage.completionTokens - reasoning);
|
|
78
|
+
usage.cumulative.input += adjustedInput;
|
|
79
|
+
usage.cumulative.output += adjustedOutput;
|
|
80
|
+
usage.cumulative.reasoning += reasoning;
|
|
81
|
+
usage.cumulative.cache.read += cached;
|
|
82
|
+
usage.cumulative.total += apiUsage.totalTokens;
|
|
83
|
+
usage.lastTurn = {
|
|
84
|
+
input: adjustedInput,
|
|
85
|
+
output: adjustedOutput,
|
|
86
|
+
reasoning,
|
|
87
|
+
cache: { read: cached, write: 0 },
|
|
88
|
+
total: apiUsage.totalTokens
|
|
89
|
+
};
|
|
90
|
+
usage.inputTokens = usage.cumulative.input;
|
|
91
|
+
usage.outputTokens = usage.cumulative.output;
|
|
92
|
+
usage.totalTokens = usage.cumulative.total;
|
|
93
|
+
usage.lastTurnTokens = apiUsage.totalTokens;
|
|
94
|
+
}
|
|
95
|
+
var PRUNE_PROTECT_TOKENS = 4e4;
|
|
96
|
+
var PRUNE_MIN_SAVINGS = 2e4;
|
|
97
|
+
var PRUNE_SKIP_RECENT_USER_TURNS = 2;
|
|
98
|
+
function pruneToolOutputs(messages) {
|
|
99
|
+
const userIndices = [];
|
|
100
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
101
|
+
if (messages[i].role === "user") userIndices.push(i);
|
|
102
|
+
}
|
|
103
|
+
const protectBoundary = userIndices.length >= PRUNE_SKIP_RECENT_USER_TURNS ? userIndices[PRUNE_SKIP_RECENT_USER_TURNS - 1] : 0;
|
|
104
|
+
const toolIndices = [];
|
|
105
|
+
for (let i = 0; i < messages.length; i++) {
|
|
106
|
+
if (messages[i].role === "tool" && i < protectBoundary) {
|
|
107
|
+
toolIndices.push(i);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (toolIndices.length === 0) return { messages, savedTokens: 0 };
|
|
111
|
+
let protectedTokens = 0;
|
|
112
|
+
let cutoffIdx = toolIndices.length;
|
|
113
|
+
for (let i = toolIndices.length - 1; i >= 0; i--) {
|
|
114
|
+
const msg = messages[toolIndices[i]];
|
|
115
|
+
const tokens = estimateMessageTokens(msg);
|
|
116
|
+
if (protectedTokens + tokens > PRUNE_PROTECT_TOKENS) {
|
|
117
|
+
cutoffIdx = i;
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
protectedTokens += tokens;
|
|
121
|
+
}
|
|
122
|
+
let savedTokens = 0;
|
|
123
|
+
const result = [...messages];
|
|
124
|
+
for (let i = 0; i < cutoffIdx; i++) {
|
|
125
|
+
const idx = toolIndices[i];
|
|
126
|
+
const msg = result[idx];
|
|
127
|
+
const oldTokens = estimateMessageTokens(msg);
|
|
128
|
+
const stub = "[output pruned \u2014 use read_file to re-read if needed]";
|
|
129
|
+
savedTokens += oldTokens - estimateTokens(stub);
|
|
130
|
+
result[idx] = { ...msg, content: stub };
|
|
131
|
+
}
|
|
132
|
+
if (savedTokens < PRUNE_MIN_SAVINGS) {
|
|
133
|
+
return { messages, savedTokens: 0 };
|
|
134
|
+
}
|
|
135
|
+
return { messages: result, savedTokens };
|
|
136
|
+
}
|
|
137
|
+
var COMPACTION_SYSTEM_PROMPT = `You are a conversation summarizer for a research agent. Your job is to create a handoff summary that another agent instance can use to seamlessly continue the work.
|
|
138
|
+
|
|
139
|
+
Do not respond to any questions in the conversation. Only output the summary.
|
|
140
|
+
Respond in the same language the user used.`;
|
|
141
|
+
var COMPACTION_USER_TEMPLATE = `Provide a detailed summary of our conversation above for handoff to another agent that will continue the work.
|
|
142
|
+
|
|
143
|
+
Stick to this template:
|
|
144
|
+
|
|
145
|
+
## Goal
|
|
146
|
+
[What is the user trying to accomplish? Be specific.]
|
|
147
|
+
|
|
148
|
+
## Instructions
|
|
149
|
+
- [Important instructions or preferences the user gave]
|
|
150
|
+
- [Research methodology constraints or requirements]
|
|
151
|
+
- [If there is a research charter or plan, summarize its key points]
|
|
152
|
+
|
|
153
|
+
## Discoveries
|
|
154
|
+
- [Key findings from paper searches, data analysis, or experiments]
|
|
155
|
+
- [Important facts, numbers, or evidence discovered]
|
|
156
|
+
- [Any surprising or contradicting results]
|
|
157
|
+
|
|
158
|
+
## Accomplished
|
|
159
|
+
- [What work has been completed]
|
|
160
|
+
- [What is currently in progress]
|
|
161
|
+
- [What remains to be done]
|
|
162
|
+
|
|
163
|
+
## Relevant Files
|
|
164
|
+
[List workspace files that were read, created, or modified. Include what each contains.]
|
|
165
|
+
- path/to/file.md \u2014 description of contents
|
|
166
|
+
- experiments/script.py \u2014 what it does and its results
|
|
167
|
+
|
|
168
|
+
## Active Context
|
|
169
|
+
- [Current research question or hypothesis being investigated]
|
|
170
|
+
- [Which skills are active]
|
|
171
|
+
- [Any pending user decisions or questions]
|
|
172
|
+
|
|
173
|
+
## Next Steps
|
|
174
|
+
1. [Most immediate next action]
|
|
175
|
+
2. [Following action]
|
|
176
|
+
3. [And so on]
|
|
177
|
+
|
|
178
|
+
{CUSTOM_INSTRUCTIONS}`;
|
|
179
|
+
async function compactConversation(messages, provider, model, customInstructions, signal) {
|
|
180
|
+
const systemMsg = messages.find((m) => m.role === "system");
|
|
181
|
+
const conversationMsgs = messages.filter((m) => m.role !== "system");
|
|
182
|
+
const conversationText = conversationMsgs.map((m) => {
|
|
183
|
+
const role = m.role === "assistant" ? "Agent" : m.role === "user" ? "User" : "Tool";
|
|
184
|
+
let content;
|
|
185
|
+
if (typeof m.content === "string") {
|
|
186
|
+
content = m.content.length > 3e3 ? m.content.slice(0, 3e3) + "\n[... truncated]" : m.content;
|
|
187
|
+
} else if (m.content) {
|
|
188
|
+
content = JSON.stringify(m.content).slice(0, 1e3);
|
|
189
|
+
} else if (m.tool_calls?.length) {
|
|
190
|
+
content = m.tool_calls.map((tc) => `[tool: ${tc.function.name}]`).join(", ");
|
|
191
|
+
} else {
|
|
192
|
+
content = "[empty]";
|
|
193
|
+
}
|
|
194
|
+
return `[${role}]: ${content}`;
|
|
195
|
+
}).join("\n\n");
|
|
196
|
+
const customBlock = customInstructions ? `
|
|
197
|
+
|
|
198
|
+
Additional instructions: ${customInstructions}` : "";
|
|
199
|
+
const userPrompt = COMPACTION_USER_TEMPLATE.replace("{CUSTOM_INSTRUCTIONS}", customBlock);
|
|
200
|
+
const compactionModel = selectModelForTask(provider.kind, model, "compaction");
|
|
201
|
+
const summaryResponse = await provider.callLLM({
|
|
202
|
+
messages: [
|
|
203
|
+
{ role: "system", content: COMPACTION_SYSTEM_PROMPT },
|
|
204
|
+
{
|
|
205
|
+
role: "user",
|
|
206
|
+
content: `Here is the conversation to summarize:
|
|
207
|
+
|
|
208
|
+
${conversationText.slice(0, 12e4)}
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
${userPrompt}`
|
|
213
|
+
}
|
|
214
|
+
],
|
|
215
|
+
model: compactionModel,
|
|
216
|
+
maxTokens: 4096
|
|
217
|
+
});
|
|
218
|
+
const compacted = [];
|
|
219
|
+
if (systemMsg) compacted.push(systemMsg);
|
|
220
|
+
compacted.push({
|
|
221
|
+
role: "user",
|
|
222
|
+
content: "What have we accomplished so far in this research session?"
|
|
223
|
+
});
|
|
224
|
+
compacted.push({
|
|
225
|
+
role: "assistant",
|
|
226
|
+
content: summaryResponse.content
|
|
227
|
+
});
|
|
228
|
+
return compacted;
|
|
229
|
+
}
|
|
230
|
+
async function maybeCompact(messages, model, provider, usage, signal) {
|
|
231
|
+
const estimated = estimateConversationTokens(messages);
|
|
232
|
+
usage.estimatedCurrentTokens = estimated;
|
|
233
|
+
const threshold = getCompactThreshold(model);
|
|
234
|
+
if (estimated < threshold) {
|
|
235
|
+
return { messages, didCompact: false };
|
|
236
|
+
}
|
|
237
|
+
const { messages: pruned, savedTokens } = pruneToolOutputs(messages);
|
|
238
|
+
const afterPrune = estimated - savedTokens;
|
|
239
|
+
if (afterPrune < threshold) {
|
|
240
|
+
usage.estimatedCurrentTokens = afterPrune;
|
|
241
|
+
usage.compactionCount++;
|
|
242
|
+
return { messages: pruned, didCompact: true };
|
|
243
|
+
}
|
|
244
|
+
const compacted = await compactConversation(pruned, provider, model, void 0, signal);
|
|
245
|
+
usage.estimatedCurrentTokens = estimateConversationTokens(compacted);
|
|
246
|
+
usage.compactionCount++;
|
|
247
|
+
return { messages: compacted, didCompact: true };
|
|
248
|
+
}
|
|
249
|
+
async function manualCompact(messages, model, provider, usage, customInstructions, signal) {
|
|
250
|
+
if (messages.length <= 2) {
|
|
251
|
+
return { messages, didCompact: false };
|
|
252
|
+
}
|
|
253
|
+
const { messages: pruned } = pruneToolOutputs(messages);
|
|
254
|
+
const compacted = await compactConversation(pruned, provider, model, customInstructions, signal);
|
|
255
|
+
usage.estimatedCurrentTokens = estimateConversationTokens(compacted);
|
|
256
|
+
usage.compactionCount++;
|
|
257
|
+
return { messages: compacted, didCompact: true };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export {
|
|
261
|
+
estimateTokens,
|
|
262
|
+
estimateMessageTokens,
|
|
263
|
+
estimateConversationTokens,
|
|
264
|
+
getContextWindow,
|
|
265
|
+
getCompactThreshold,
|
|
266
|
+
createSessionUsage,
|
|
267
|
+
updateUsageFromApi,
|
|
268
|
+
pruneToolOutputs,
|
|
269
|
+
compactConversation,
|
|
270
|
+
maybeCompact,
|
|
271
|
+
manualCompact
|
|
272
|
+
};
|
|
@@ -59,6 +59,7 @@ async function loadSessionHistory(workspaceDir, sessionId) {
|
|
|
59
59
|
const events = parseEvents(raw);
|
|
60
60
|
const messages = [];
|
|
61
61
|
const llmHistory = [];
|
|
62
|
+
const turnSnapshots = [];
|
|
62
63
|
for (const event of events) {
|
|
63
64
|
if (event.type === "chat.turn") {
|
|
64
65
|
const payload = event.payload;
|
|
@@ -76,8 +77,12 @@ async function loadSessionHistory(workspaceDir, sessionId) {
|
|
|
76
77
|
llmHistory.push(...payload.llmHistory);
|
|
77
78
|
}
|
|
78
79
|
}
|
|
80
|
+
if (event.type === "snapshot.turn") {
|
|
81
|
+
const payload = event.payload;
|
|
82
|
+
turnSnapshots.push(payload);
|
|
83
|
+
}
|
|
79
84
|
}
|
|
80
|
-
return { messages, llmHistory };
|
|
85
|
+
return { messages, llmHistory, turnSnapshots };
|
|
81
86
|
}
|
|
82
87
|
|
|
83
88
|
export {
|