wave-agent-sdk 1.0.0 → 1.0.2
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/agent.d.ts +9 -20
- package/dist/agent.js +35 -97
- package/dist/managers/aiManager.d.ts +63 -8
- package/dist/managers/aiManager.js +274 -80
- package/dist/managers/messageManager.d.ts +9 -5
- package/dist/managers/messageManager.js +36 -12
- package/dist/managers/permissionManager.js +13 -11
- package/dist/managers/subagentManager.d.ts +6 -0
- package/dist/managers/subagentManager.js +33 -22
- package/dist/prompts/index.d.ts +0 -2
- package/dist/prompts/index.js +71 -47
- package/dist/services/aiService.d.ts +1 -34
- package/dist/services/aiService.js +18 -130
- package/dist/services/autoMemoryService.d.ts +27 -2
- package/dist/services/autoMemoryService.js +124 -36
- package/dist/services/configurationService.js +14 -1
- package/dist/services/session.d.ts +13 -0
- package/dist/services/session.js +64 -0
- package/dist/types/agent.d.ts +0 -2
- package/dist/types/config.d.ts +7 -0
- package/dist/types/core.d.ts +1 -1
- package/dist/utils/containerSetup.js +12 -3
- package/package.json +1 -1
- package/src/agent.ts +50 -110
- package/src/managers/aiManager.ts +370 -105
- package/src/managers/messageManager.ts +51 -23
- package/src/managers/permissionManager.ts +15 -13
- package/src/managers/subagentManager.ts +36 -25
- package/src/prompts/index.ts +75 -56
- package/src/services/aiService.ts +25 -203
- package/src/services/autoMemoryService.ts +145 -39
- package/src/services/configurationService.ts +16 -1
- package/src/services/session.ts +68 -0
- package/src/types/agent.ts +0 -6
- package/src/types/config.ts +7 -0
- package/src/types/core.ts +1 -1
- package/src/utils/containerSetup.ts +12 -4
- package/dist/constants/goalPrompts.d.ts +0 -1
- package/dist/constants/goalPrompts.js +0 -10
- package/dist/managers/goalManager.d.ts +0 -42
- package/dist/managers/goalManager.js +0 -177
- package/src/constants/goalPrompts.ts +0 -10
- package/src/managers/goalManager.ts +0 -232
|
@@ -538,18 +538,21 @@ export class SubagentManager {
|
|
|
538
538
|
createSubagentCallbacks(subagentId) {
|
|
539
539
|
return {
|
|
540
540
|
onUserMessageAdded: (params) => {
|
|
541
|
+
this.refreshSubagentState(subagentId);
|
|
541
542
|
// Forward user message events to parent via SubagentManager callbacks
|
|
542
543
|
if (this.callbacks?.onSubagentUserMessageAdded) {
|
|
543
544
|
this.callbacks.onSubagentUserMessageAdded(subagentId, params);
|
|
544
545
|
}
|
|
545
546
|
},
|
|
546
547
|
onAssistantMessageAdded: (messageId) => {
|
|
548
|
+
this.refreshSubagentState(subagentId);
|
|
547
549
|
// Forward assistant message events to parent via SubagentManager callbacks
|
|
548
550
|
if (this.callbacks?.onSubagentAssistantMessageAdded) {
|
|
549
551
|
this.callbacks.onSubagentAssistantMessageAdded(subagentId, messageId);
|
|
550
552
|
}
|
|
551
553
|
},
|
|
552
554
|
onAssistantContentUpdated: (params) => {
|
|
555
|
+
this.refreshSubagentState(subagentId);
|
|
553
556
|
// Forward assistant content updates to parent via SubagentManager callbacks
|
|
554
557
|
if (this.callbacks?.onSubagentAssistantContentUpdated) {
|
|
555
558
|
this.callbacks.onSubagentAssistantContentUpdated({
|
|
@@ -559,6 +562,7 @@ export class SubagentManager {
|
|
|
559
562
|
}
|
|
560
563
|
},
|
|
561
564
|
onAssistantReasoningUpdated: (params) => {
|
|
565
|
+
this.refreshSubagentState(subagentId);
|
|
562
566
|
// Forward assistant reasoning updates to parent via SubagentManager callbacks
|
|
563
567
|
if (this.callbacks?.onSubagentAssistantReasoningUpdated) {
|
|
564
568
|
this.callbacks.onSubagentAssistantReasoningUpdated({
|
|
@@ -568,6 +572,7 @@ export class SubagentManager {
|
|
|
568
572
|
}
|
|
569
573
|
},
|
|
570
574
|
onToolBlockUpdated: (params) => {
|
|
575
|
+
this.refreshSubagentState(subagentId);
|
|
571
576
|
const instance = this.instances.get(subagentId);
|
|
572
577
|
if (instance) {
|
|
573
578
|
// Log tool execution to file only when finalized
|
|
@@ -582,28 +587,6 @@ export class SubagentManager {
|
|
|
582
587
|
this.callbacks.onSubagentToolBlockUpdated(subagentId, params);
|
|
583
588
|
}
|
|
584
589
|
},
|
|
585
|
-
// These callbacks will be handled by the parent agent
|
|
586
|
-
onMessagesChange: (messages) => {
|
|
587
|
-
const instance = this.instances.get(subagentId);
|
|
588
|
-
if (instance) {
|
|
589
|
-
instance.messages = messages;
|
|
590
|
-
// Compute usedTools from messages (last 2 tool blocks)
|
|
591
|
-
const toolBlocks = messages.flatMap((m) => m.blocks?.filter((b) => b.type === "tool") ?? []);
|
|
592
|
-
const last2 = toolBlocks.slice(-2);
|
|
593
|
-
instance.usedTools = last2.map((tb) => ({
|
|
594
|
-
name: tb.name ?? "",
|
|
595
|
-
parameters: tb.parameters ?? "",
|
|
596
|
-
compactParams: tb.compactParams,
|
|
597
|
-
stage: tb.stage,
|
|
598
|
-
}));
|
|
599
|
-
// Trigger the onUpdate callback if provided
|
|
600
|
-
instance.onUpdate?.();
|
|
601
|
-
// Forward subagent message changes to parent via callbacks
|
|
602
|
-
if (this.callbacks?.onSubagentMessagesChange) {
|
|
603
|
-
this.callbacks.onSubagentMessagesChange(subagentId, messages);
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
},
|
|
607
590
|
onLatestTotalTokensChange: (tokens) => {
|
|
608
591
|
const instance = this.instances.get(subagentId);
|
|
609
592
|
if (instance) {
|
|
@@ -616,6 +599,7 @@ export class SubagentManager {
|
|
|
616
599
|
}
|
|
617
600
|
},
|
|
618
601
|
onErrorBlockAdded: (error) => {
|
|
602
|
+
this.refreshSubagentState(subagentId);
|
|
619
603
|
const instance = this.instances.get(subagentId);
|
|
620
604
|
if (instance?.logStream) {
|
|
621
605
|
instance.logStream.write(`[${new Date().toISOString()}] Error: ${error}\n`);
|
|
@@ -623,4 +607,31 @@ export class SubagentManager {
|
|
|
623
607
|
},
|
|
624
608
|
};
|
|
625
609
|
}
|
|
610
|
+
/**
|
|
611
|
+
* Pull the latest messages from the subagent instance's MessageManager and
|
|
612
|
+
* refresh the instance's cached messages, usedTools, onUpdate and the
|
|
613
|
+
* onSubagentMessagesChange forwarding. Triggered by incremental callbacks.
|
|
614
|
+
*/
|
|
615
|
+
refreshSubagentState(subagentId) {
|
|
616
|
+
const instance = this.instances.get(subagentId);
|
|
617
|
+
if (!instance)
|
|
618
|
+
return;
|
|
619
|
+
const messages = instance.messageManager.getMessages();
|
|
620
|
+
instance.messages = messages;
|
|
621
|
+
// Compute usedTools from messages (last 2 tool blocks)
|
|
622
|
+
const toolBlocks = messages.flatMap((m) => m.blocks?.filter((b) => b.type === "tool") ?? []);
|
|
623
|
+
const last2 = toolBlocks.slice(-2);
|
|
624
|
+
instance.usedTools = last2.map((tb) => ({
|
|
625
|
+
name: tb.name ?? "",
|
|
626
|
+
parameters: tb.parameters ?? "",
|
|
627
|
+
compactParams: tb.compactParams,
|
|
628
|
+
stage: tb.stage,
|
|
629
|
+
}));
|
|
630
|
+
// Trigger the onUpdate callback if provided
|
|
631
|
+
instance.onUpdate?.();
|
|
632
|
+
// Forward subagent message changes to parent via callbacks
|
|
633
|
+
if (this.callbacks?.onSubagentMessagesChange) {
|
|
634
|
+
this.callbacks.onSubagentMessagesChange(subagentId, messages);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
626
637
|
}
|
package/dist/prompts/index.d.ts
CHANGED
|
@@ -34,7 +34,6 @@ export declare function getCompactPrompt(customInstructions?: string): string;
|
|
|
34
34
|
*/
|
|
35
35
|
export declare function formatCompactSummary(summary: string): string;
|
|
36
36
|
export declare const WEB_CONTENT_SYSTEM_PROMPT = "You are a helpful assistant that extracts information from web content. The content is provided in Markdown format.";
|
|
37
|
-
export declare const BTW_SYSTEM_PROMPT = "You are a helpful assistant. Answer the user's side question based on the conversation history. \nDo NOT say things like \"Let me try...\", \"I'll now...\", \"Let me check...\", or promise to take any action. \nIf you don't know the answer, say so - do not offer to look it up or investigate. \nSimply answer the question with the information you have.";
|
|
38
37
|
export declare function buildSystemPrompt(basePrompt: string | undefined, tools: ToolPlugin[], options?: {
|
|
39
38
|
workdir?: string;
|
|
40
39
|
originalWorkdir?: string;
|
|
@@ -46,4 +45,3 @@ export declare function buildSystemPrompt(basePrompt: string | undefined, tools:
|
|
|
46
45
|
content: string;
|
|
47
46
|
};
|
|
48
47
|
}): SystemPromptBlock[];
|
|
49
|
-
export declare function enhanceSystemPromptWithEnvDetails(existingSystemPrompt: string, workdir: string, originalWorkdir?: string, worktreeSession?: WorktreeSession | null): string;
|
package/dist/prompts/index.js
CHANGED
|
@@ -285,10 +285,43 @@ export function formatCompactSummary(summary) {
|
|
|
285
285
|
return formattedSummary.trim();
|
|
286
286
|
}
|
|
287
287
|
export const WEB_CONTENT_SYSTEM_PROMPT = `You are a helpful assistant that extracts information from web content. The content is provided in Markdown format.`;
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
288
|
+
/**
|
|
289
|
+
* Notes block prepended to the subagent env section, aligned with Claude
|
|
290
|
+
* Code's enhanceSystemPromptWithEnvDetails().
|
|
291
|
+
*/
|
|
292
|
+
const SUBAGENT_ENV_NOTES = `Notes:
|
|
293
|
+
- Agent threads always have their cwd reset between bash calls, as a result please only use absolute file paths.
|
|
294
|
+
- In your final response, share file paths (always absolute, never relative) that are relevant to the task. Include code snippets only when the exact text is load-bearing (e.g., a bug you found, a function signature the caller asked for) — do not recap code you merely read.
|
|
295
|
+
- For clear communication with the user the assistant MUST avoid using emojis.
|
|
296
|
+
- Do not use a colon before tool calls. Text like "Let me read the file:" followed by a read tool call should just be "Let me read the file." with a period.`;
|
|
297
|
+
/**
|
|
298
|
+
* Shell info line, aligned with Claude Code's getShellInfoLine(). On win32 an
|
|
299
|
+
* extra Unix-syntax hint is appended.
|
|
300
|
+
*/
|
|
301
|
+
function getShellInfoLine() {
|
|
302
|
+
const shell = process.env.SHELL || "unknown";
|
|
303
|
+
const shellName = shell.includes("zsh")
|
|
304
|
+
? "zsh"
|
|
305
|
+
: shell.includes("bash")
|
|
306
|
+
? "bash"
|
|
307
|
+
: shell;
|
|
308
|
+
if (os.platform() === "win32") {
|
|
309
|
+
return `Shell: ${shellName} (use Unix shell syntax, not Windows — e.g., /dev/null not NUL, forward slashes in paths)`;
|
|
310
|
+
}
|
|
311
|
+
return `Shell: ${shellName}`;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* OS Version value, aligned with Claude Code's getUnameSR(). os.type() and
|
|
315
|
+
* os.release() wrap uname(3) on POSIX, producing output byte-identical to
|
|
316
|
+
* `uname -sr`. Windows has no uname(3); os.type() returns "Windows_NT" there,
|
|
317
|
+
* but os.version() gives the friendlier "Windows 11 Pro", so use that instead.
|
|
318
|
+
*/
|
|
319
|
+
function getUnameSR() {
|
|
320
|
+
if (os.platform() === "win32") {
|
|
321
|
+
return `${os.version()} ${os.release()}`;
|
|
322
|
+
}
|
|
323
|
+
return `${os.type()} ${os.release()}`;
|
|
324
|
+
}
|
|
292
325
|
export function buildSystemPrompt(basePrompt, tools, options = {}) {
|
|
293
326
|
// --- Static block (cacheable) ---
|
|
294
327
|
let staticText = basePrompt || DEFAULT_SYSTEM_PROMPT;
|
|
@@ -309,27 +342,49 @@ export function buildSystemPrompt(basePrompt, tools, options = {}) {
|
|
|
309
342
|
if (options.workdir) {
|
|
310
343
|
const isGitRepo = isGitRepository(options.workdir);
|
|
311
344
|
const platform = os.platform();
|
|
312
|
-
const
|
|
313
|
-
const
|
|
314
|
-
const
|
|
315
|
-
const shellName = shell.includes("zsh")
|
|
316
|
-
? "zsh"
|
|
317
|
-
: shell.includes("bash")
|
|
318
|
-
? "bash"
|
|
319
|
-
: shell;
|
|
345
|
+
const shellInfo = getShellInfoLine();
|
|
346
|
+
const osVersion = getUnameSR();
|
|
347
|
+
const primaryWorkdir = options.originalWorkdir ?? options.workdir;
|
|
320
348
|
const worktreeSession = options.worktreeSession;
|
|
321
|
-
|
|
349
|
+
if (options.isSubagent) {
|
|
350
|
+
// Subagent env section, aligned with Claude Code's computeEnvInfo() +
|
|
351
|
+
// enhanceSystemPromptWithEnvDetails() (without the model description and
|
|
352
|
+
// knowledge cutoff lines, which Wave does not use).
|
|
353
|
+
dynamicText += `
|
|
354
|
+
|
|
355
|
+
${SUBAGENT_ENV_NOTES}
|
|
322
356
|
|
|
323
357
|
Here is useful information about the environment you are running in:
|
|
324
358
|
<env>
|
|
325
|
-
|
|
359
|
+
Working directory: ${primaryWorkdir}
|
|
326
360
|
Is directory a git repo: ${isGitRepo}
|
|
327
361
|
Platform: ${platform}
|
|
328
|
-
|
|
362
|
+
${shellInfo}
|
|
329
363
|
OS Version: ${osVersion}
|
|
330
|
-
Today's date: ${today}
|
|
331
364
|
</env>
|
|
332
365
|
`;
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
// Main agent env section, aligned with Claude Code's
|
|
369
|
+
// computeSimpleEnvInfo() (without the model description, knowledge
|
|
370
|
+
// cutoff, and marketing lines, which Wave does not use).
|
|
371
|
+
const envItems = [
|
|
372
|
+
`Primary working directory: ${primaryWorkdir}`,
|
|
373
|
+
worktreeSession
|
|
374
|
+
? `This is a git worktree — an isolated copy of the repository. Run all commands from this directory. Do NOT \`cd\` to the original repository root.`
|
|
375
|
+
: null,
|
|
376
|
+
`Is a git repository: ${isGitRepo}`,
|
|
377
|
+
`Platform: ${platform}`,
|
|
378
|
+
shellInfo,
|
|
379
|
+
`OS Version: ${osVersion}`,
|
|
380
|
+
].filter((item) => item !== null);
|
|
381
|
+
const envBlock = [
|
|
382
|
+
`# Environment`,
|
|
383
|
+
`You have been invoked in the following environment: `,
|
|
384
|
+
...envItems.map((item) => ` - ${item}`),
|
|
385
|
+
].join("\n");
|
|
386
|
+
dynamicText += `\n\n${envBlock}`;
|
|
387
|
+
}
|
|
333
388
|
}
|
|
334
389
|
if (options.autoMemory) {
|
|
335
390
|
dynamicText += `\n\n${buildAutoMemoryPrompt(options.autoMemory.directory)}`;
|
|
@@ -342,34 +397,3 @@ Today's date: ${today}
|
|
|
342
397
|
}
|
|
343
398
|
return blocks;
|
|
344
399
|
}
|
|
345
|
-
export function enhanceSystemPromptWithEnvDetails(existingSystemPrompt, workdir, originalWorkdir, worktreeSession) {
|
|
346
|
-
const isGitRepo = isGitRepository(workdir);
|
|
347
|
-
const platform = os.platform();
|
|
348
|
-
const osVersion = `${os.type()} ${os.release()}`;
|
|
349
|
-
const today = new Date().toISOString().split("T")[0];
|
|
350
|
-
const shell = process.env.SHELL || "unknown";
|
|
351
|
-
const shellName = shell.includes("zsh")
|
|
352
|
-
? "zsh"
|
|
353
|
-
: shell.includes("bash")
|
|
354
|
-
? "bash"
|
|
355
|
-
: shell;
|
|
356
|
-
const notes = `Notes:
|
|
357
|
-
- Agent threads always have their cwd reset between bash calls, as a result please only use absolute file paths.${worktreeSession ? `\n- You are in a git worktree at ${worktreeSession.worktreePath} (branch: ${worktreeSession.worktreeBranch}). Absolute paths from prior context may refer to the original repo at ${worktreeSession.originalCwd}; translate them to your worktree. Do NOT edit files outside this worktree.` : ""}
|
|
358
|
-
- In your final response, share file paths (always absolute, never relative) that are relevant to the task. Include code snippets only when the exact text is load-bearing (e.g., a bug you found, a function signature the caller asked for) — do not recap code you merely read.
|
|
359
|
-
- For clear communication with the user the assistant MUST avoid using emojis.
|
|
360
|
-
- Do not use a colon before tool calls. Text like "Let me read the file:" followed by a read tool call should just be "Let me read the file." with a period.`;
|
|
361
|
-
return `${existingSystemPrompt}
|
|
362
|
-
|
|
363
|
-
${notes}
|
|
364
|
-
|
|
365
|
-
Here is useful information about the environment you are running in:
|
|
366
|
-
<env>
|
|
367
|
-
Primary working directory: ${originalWorkdir ?? workdir}${worktreeSession ? `\nThis is a git worktree — an isolated copy of the repository. Run all commands from this directory. Do NOT \`cd\` to the original repository root at ${worktreeSession.originalCwd}.` : ""}
|
|
368
|
-
Is directory a git repo: ${isGitRepo}
|
|
369
|
-
Platform: ${platform}
|
|
370
|
-
Shell: ${shellName}
|
|
371
|
-
OS Version: ${osVersion}
|
|
372
|
-
Today's date: ${today}
|
|
373
|
-
</env>
|
|
374
|
-
`;
|
|
375
|
-
}
|
|
@@ -34,6 +34,7 @@ export interface CallAgentOptions {
|
|
|
34
34
|
stage?: "start" | "streaming" | "running" | "end";
|
|
35
35
|
}) => void;
|
|
36
36
|
onReasoningUpdate?: (content: string) => void;
|
|
37
|
+
disableThinkingOptions?: Record<string, unknown>;
|
|
37
38
|
}
|
|
38
39
|
export interface CallAgentResult {
|
|
39
40
|
content?: string;
|
|
@@ -62,37 +63,3 @@ export interface ProcessWebContentResult {
|
|
|
62
63
|
};
|
|
63
64
|
}
|
|
64
65
|
export declare function processWebContent(options: ProcessWebContentOptions): Promise<ProcessWebContentResult>;
|
|
65
|
-
export interface BtwOptions {
|
|
66
|
-
gatewayConfig: GatewayConfig;
|
|
67
|
-
modelConfig: ModelConfig;
|
|
68
|
-
messages: ChatCompletionMessageParam[];
|
|
69
|
-
question: string;
|
|
70
|
-
abortSignal?: AbortSignal;
|
|
71
|
-
model?: string;
|
|
72
|
-
}
|
|
73
|
-
export interface BtwResult {
|
|
74
|
-
content: string;
|
|
75
|
-
usage?: {
|
|
76
|
-
prompt_tokens: number;
|
|
77
|
-
completion_tokens: number;
|
|
78
|
-
total_tokens: number;
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
export declare function btw(options: BtwOptions): Promise<BtwResult>;
|
|
82
|
-
export interface EvaluateGoalOptions {
|
|
83
|
-
gatewayConfig: GatewayConfig;
|
|
84
|
-
modelConfig: ModelConfig;
|
|
85
|
-
model: string;
|
|
86
|
-
goalCondition: string;
|
|
87
|
-
messages: ChatCompletionMessageParam[];
|
|
88
|
-
abortSignal?: AbortSignal;
|
|
89
|
-
}
|
|
90
|
-
export interface EvaluateGoalResult {
|
|
91
|
-
content: string;
|
|
92
|
-
usage?: {
|
|
93
|
-
prompt_tokens: number;
|
|
94
|
-
completion_tokens: number;
|
|
95
|
-
total_tokens: number;
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
export declare function evaluateGoal(options: EvaluateGoalOptions): Promise<EvaluateGoalResult>;
|
|
@@ -7,8 +7,7 @@ import { supportsPromptCaching } from "../utils/modelCapabilities.js";
|
|
|
7
7
|
import * as os from "os";
|
|
8
8
|
import * as fs from "fs";
|
|
9
9
|
import * as path from "path";
|
|
10
|
-
import { WEB_CONTENT_SYSTEM_PROMPT,
|
|
11
|
-
import { GOAL_EVALUATION_SYSTEM_PROMPT } from "../constants/goalPrompts.js";
|
|
10
|
+
import { WEB_CONTENT_SYSTEM_PROMPT, } from "../prompts/index.js";
|
|
12
11
|
// Global rate limiter state for 1 QPS
|
|
13
12
|
let nextAllowedTime = 0;
|
|
14
13
|
const MIN_INTERVAL = 1000; // 1 second for 1 QPS
|
|
@@ -66,6 +65,15 @@ function getModelConfig(modelName, baseConfig = {}) {
|
|
|
66
65
|
}
|
|
67
66
|
return config;
|
|
68
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Effective disable-thinking params for a model config. No default: these
|
|
70
|
+
* params are only sent when the user explicitly configures
|
|
71
|
+
* `models[X].disableThinkingOptions` (an empty object clears them), so a
|
|
72
|
+
* gateway that doesn't understand the params is never hit with them.
|
|
73
|
+
*/
|
|
74
|
+
function effectiveDisableThinkingOptions(modelConfig) {
|
|
75
|
+
return modelConfig.disableThinkingOptions;
|
|
76
|
+
}
|
|
69
77
|
function validateModelConfig(modelConfig) {
|
|
70
78
|
if (!modelConfig.model) {
|
|
71
79
|
throw new ConfigurationError(CONFIG_ERRORS.MISSING_MODEL, "model", {
|
|
@@ -81,7 +89,7 @@ function validateModelConfig(modelConfig) {
|
|
|
81
89
|
}
|
|
82
90
|
}
|
|
83
91
|
export async function callAgent(options) {
|
|
84
|
-
const { gatewayConfig, modelConfig, messages, abortSignal, workdir, tools, model, systemPrompt, onContentUpdate, onToolUpdate, onReasoningUpdate, } = options;
|
|
92
|
+
const { gatewayConfig, modelConfig, messages, abortSignal, workdir, tools, model, systemPrompt, onContentUpdate, onToolUpdate, onReasoningUpdate, disableThinkingOptions, } = options;
|
|
85
93
|
// Validate model config at call time
|
|
86
94
|
validateModelConfig(modelConfig);
|
|
87
95
|
// Apply global 1 QPS rate limit
|
|
@@ -147,6 +155,7 @@ export async function callAgent(options) {
|
|
|
147
155
|
const openaiModelConfig = getModelConfig(model || modelConfig.model, {
|
|
148
156
|
max_tokens: resolvedMaxTokens,
|
|
149
157
|
...(modelConfig.options || {}),
|
|
158
|
+
...(disableThinkingOptions ?? {}),
|
|
150
159
|
});
|
|
151
160
|
// Determine if streaming is needed
|
|
152
161
|
const isStreaming = options.stream === true ||
|
|
@@ -509,10 +518,16 @@ export async function processWebContent(options) {
|
|
|
509
518
|
const activeExtraParams = options.model
|
|
510
519
|
? modelConfig.fastModelOptions || {}
|
|
511
520
|
: modelConfig.options || {};
|
|
521
|
+
// Disable-thinking params only apply to the fast-model override path;
|
|
522
|
+
// the agent-model path is untouched.
|
|
523
|
+
const disableThinking = options.model
|
|
524
|
+
? effectiveDisableThinkingOptions(modelConfig)
|
|
525
|
+
: undefined;
|
|
512
526
|
const openaiModelConfig = getModelConfig(options.model || modelConfig.model, {
|
|
513
527
|
temperature: 0.1,
|
|
514
528
|
max_tokens: 4096,
|
|
515
529
|
...activeExtraParams,
|
|
530
|
+
...(disableThinking || {}),
|
|
516
531
|
});
|
|
517
532
|
try {
|
|
518
533
|
const response = await openai.chat.completions.create({
|
|
@@ -555,130 +570,3 @@ export async function processWebContent(options) {
|
|
|
555
570
|
throw error;
|
|
556
571
|
}
|
|
557
572
|
}
|
|
558
|
-
export async function btw(options) {
|
|
559
|
-
const { gatewayConfig, modelConfig, messages, question, abortSignal } = options;
|
|
560
|
-
// Validate model config at call time
|
|
561
|
-
validateModelConfig(modelConfig);
|
|
562
|
-
// Apply global 1 QPS rate limit
|
|
563
|
-
if (process.env.NODE_ENV !== "test" ||
|
|
564
|
-
modelConfig.model === "rate-limit-test") {
|
|
565
|
-
await acquireSlot(abortSignal);
|
|
566
|
-
}
|
|
567
|
-
// Create OpenAI client with injected configuration
|
|
568
|
-
const openai = new OpenAIClient({
|
|
569
|
-
apiKey: gatewayConfig.apiKey,
|
|
570
|
-
baseURL: gatewayConfig.baseURL,
|
|
571
|
-
defaultHeaders: gatewayConfig.defaultHeaders,
|
|
572
|
-
fetchOptions: gatewayConfig.fetchOptions,
|
|
573
|
-
fetch: gatewayConfig.fetch,
|
|
574
|
-
});
|
|
575
|
-
const openaiModelConfig = getModelConfig(options.model || modelConfig.model, {
|
|
576
|
-
temperature: 0.1,
|
|
577
|
-
max_tokens: 4096,
|
|
578
|
-
...(modelConfig.options || {}),
|
|
579
|
-
});
|
|
580
|
-
try {
|
|
581
|
-
const response = await openai.chat.completions.create({
|
|
582
|
-
...openaiModelConfig,
|
|
583
|
-
messages: [
|
|
584
|
-
{
|
|
585
|
-
role: "system",
|
|
586
|
-
content: BTW_SYSTEM_PROMPT,
|
|
587
|
-
},
|
|
588
|
-
...messages,
|
|
589
|
-
{
|
|
590
|
-
role: "user",
|
|
591
|
-
content: question,
|
|
592
|
-
},
|
|
593
|
-
],
|
|
594
|
-
}, {
|
|
595
|
-
signal: abortSignal,
|
|
596
|
-
});
|
|
597
|
-
const result = response.choices[0]?.message?.content?.trim();
|
|
598
|
-
if (!result) {
|
|
599
|
-
throw new Error("Failed to process side question: Empty response from AI");
|
|
600
|
-
}
|
|
601
|
-
const usage = response.usage
|
|
602
|
-
? {
|
|
603
|
-
prompt_tokens: response.usage.prompt_tokens,
|
|
604
|
-
completion_tokens: response.usage.completion_tokens,
|
|
605
|
-
total_tokens: response.usage.total_tokens,
|
|
606
|
-
}
|
|
607
|
-
: undefined;
|
|
608
|
-
return {
|
|
609
|
-
content: result,
|
|
610
|
-
usage,
|
|
611
|
-
};
|
|
612
|
-
}
|
|
613
|
-
catch (error) {
|
|
614
|
-
if (error.name === "AbortError") {
|
|
615
|
-
logger.info("Side question request was aborted");
|
|
616
|
-
throw new Error("Side question request was aborted");
|
|
617
|
-
}
|
|
618
|
-
logger.error("Failed to process side question:", error);
|
|
619
|
-
throw error;
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
export async function evaluateGoal(options) {
|
|
623
|
-
const { gatewayConfig, modelConfig, model, goalCondition, messages, abortSignal, } = options;
|
|
624
|
-
// Create OpenAI client with injected configuration (no rate limiter — bypasses 1 QPS)
|
|
625
|
-
const openai = new OpenAIClient({
|
|
626
|
-
apiKey: gatewayConfig.apiKey,
|
|
627
|
-
baseURL: gatewayConfig.baseURL,
|
|
628
|
-
defaultHeaders: gatewayConfig.defaultHeaders,
|
|
629
|
-
fetchOptions: gatewayConfig.fetchOptions,
|
|
630
|
-
fetch: gatewayConfig.fetch,
|
|
631
|
-
});
|
|
632
|
-
const openaiModelConfig = getModelConfig(model, {
|
|
633
|
-
temperature: 0,
|
|
634
|
-
max_tokens: 200,
|
|
635
|
-
...(modelConfig.fastModelOptions || {}),
|
|
636
|
-
});
|
|
637
|
-
// Strip images from messages to reduce token usage (same as compact)
|
|
638
|
-
const cleanedMessages = messages.map((msg) => {
|
|
639
|
-
if (Array.isArray(msg.content)) {
|
|
640
|
-
const textParts = msg.content.filter((part) => part.type === "text");
|
|
641
|
-
const text = textParts.map((p) => p.text).join("\n");
|
|
642
|
-
return { ...msg, content: text || "(empty message)" };
|
|
643
|
-
}
|
|
644
|
-
return msg;
|
|
645
|
-
});
|
|
646
|
-
try {
|
|
647
|
-
const response = await openai.chat.completions.create({
|
|
648
|
-
...openaiModelConfig,
|
|
649
|
-
messages: [
|
|
650
|
-
{
|
|
651
|
-
role: "system",
|
|
652
|
-
content: GOAL_EVALUATION_SYSTEM_PROMPT,
|
|
653
|
-
},
|
|
654
|
-
...cleanedMessages,
|
|
655
|
-
{
|
|
656
|
-
role: "user",
|
|
657
|
-
content: `Goal condition: ${goalCondition}\n\nHas this goal been achieved based on the conversation above?`,
|
|
658
|
-
},
|
|
659
|
-
],
|
|
660
|
-
}, {
|
|
661
|
-
signal: abortSignal,
|
|
662
|
-
});
|
|
663
|
-
const result = response.choices[0]?.message?.content?.trim();
|
|
664
|
-
if (!result) {
|
|
665
|
-
throw new Error("Goal evaluation returned empty response");
|
|
666
|
-
}
|
|
667
|
-
const usage = response.usage
|
|
668
|
-
? {
|
|
669
|
-
prompt_tokens: response.usage.prompt_tokens,
|
|
670
|
-
completion_tokens: response.usage.completion_tokens,
|
|
671
|
-
total_tokens: response.usage.total_tokens,
|
|
672
|
-
}
|
|
673
|
-
: undefined;
|
|
674
|
-
return { content: result, usage };
|
|
675
|
-
}
|
|
676
|
-
catch (error) {
|
|
677
|
-
if (error.name === "AbortError") {
|
|
678
|
-
logger.info("Goal evaluation was aborted");
|
|
679
|
-
throw new Error("Goal evaluation was aborted");
|
|
680
|
-
}
|
|
681
|
-
logger.error("Goal evaluation failed:", error);
|
|
682
|
-
throw error;
|
|
683
|
-
}
|
|
684
|
-
}
|
|
@@ -7,9 +7,11 @@ export declare class AutoMemoryService {
|
|
|
7
7
|
private container;
|
|
8
8
|
private lastMemoryMessageId;
|
|
9
9
|
private turnsSinceLastExtraction;
|
|
10
|
+
private extractionInProgress;
|
|
11
|
+
private pendingExtraction;
|
|
10
12
|
constructor(container: Container);
|
|
11
13
|
private get messageManager();
|
|
12
|
-
private get
|
|
14
|
+
private get aiManager();
|
|
13
15
|
private get memoryService();
|
|
14
16
|
private get configurationService();
|
|
15
17
|
/**
|
|
@@ -17,7 +19,30 @@ export declare class AutoMemoryService {
|
|
|
17
19
|
*/
|
|
18
20
|
onTurnEnd(workdir: string): Promise<void>;
|
|
19
21
|
/**
|
|
20
|
-
*
|
|
22
|
+
* Wait for an in-flight extraction to settle. Called from Agent.dispose so
|
|
23
|
+
* the process doesn't exit while the extraction fork is mid-flight.
|
|
24
|
+
*/
|
|
25
|
+
drain(): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Initialize and execute the extraction in a perfect fork: same system
|
|
28
|
+
* prompt, tools, model, and message prefix as the main conversation, so the
|
|
29
|
+
* prompt cache is reused. A tool gate confines the fork to read-only
|
|
30
|
+
* inspection and memory-directory writes. Runs in-process; callers treat it
|
|
31
|
+
* as fire-and-forget.
|
|
21
32
|
*/
|
|
22
33
|
private runExtraction;
|
|
34
|
+
/**
|
|
35
|
+
* Tool gate for the extraction fork: Read/Grep/Glob are always allowed;
|
|
36
|
+
* Write/Edit only when the target path is inside the memory directory; Bash
|
|
37
|
+
* only for read-only commands (aligned with the permission manager's
|
|
38
|
+
* read-only bash classification). Everything else — Bash rm, MCP tools,
|
|
39
|
+
* Agent, out-of-dir writes — is denied.
|
|
40
|
+
*/
|
|
41
|
+
private isAllowedForkTool;
|
|
42
|
+
/**
|
|
43
|
+
* A bash command is read-only when every part is a READ_ONLY_COMMANDS entry
|
|
44
|
+
* without write redirections, command/process substitution, sed -i, or
|
|
45
|
+
* dangerous find flags. Mirrors PermissionManager.isAutoAllowedPart.
|
|
46
|
+
*/
|
|
47
|
+
private isReadOnlyBashCommand;
|
|
23
48
|
}
|