wave-agent-sdk 0.18.7 → 0.19.1
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/settings/ENV.md +0 -1
- package/builtin/skills/settings/HOOKS.md +10 -0
- package/builtin/skills/settings/MODELS.md +55 -10
- package/builtin/skills/settings/SKILL.md +8 -4
- package/builtin/skills/settings/SKILLS.md +6 -4
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +12 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +20 -4
- package/dist/managers/mcpManager.d.ts.map +1 -1
- package/dist/managers/mcpManager.js +7 -2
- package/dist/managers/messageManager.d.ts +3 -5
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +17 -77
- package/dist/managers/skillManager.d.ts +1 -0
- package/dist/managers/skillManager.d.ts.map +1 -1
- package/dist/managers/skillManager.js +11 -2
- package/dist/prompts/index.d.ts +3 -2
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +5 -1
- package/dist/services/aiService.d.ts.map +1 -1
- package/dist/services/aiService.js +16 -47
- package/dist/services/configurationService.d.ts.map +1 -1
- package/dist/services/configurationService.js +25 -19
- package/dist/services/hook.d.ts.map +1 -1
- package/dist/services/hook.js +6 -0
- package/dist/services/initializationService.d.ts.map +1 -1
- package/dist/services/initializationService.js +1 -1
- package/dist/services/interactionService.js +1 -1
- package/dist/services/reversionService.d.ts +5 -1
- package/dist/services/reversionService.d.ts.map +1 -1
- package/dist/services/reversionService.js +50 -22
- package/dist/services/session.d.ts +4 -13
- package/dist/services/session.d.ts.map +1 -1
- package/dist/services/session.js +59 -215
- package/dist/telemetry/sessionTracing.d.ts +14 -4
- package/dist/telemetry/sessionTracing.d.ts.map +1 -1
- package/dist/telemetry/sessionTracing.js +134 -8
- package/dist/tools/bashTool.d.ts.map +1 -1
- package/dist/tools/bashTool.js +16 -1
- package/dist/types/config.d.ts +11 -2
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/hooks.d.ts +1 -0
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/messaging.d.ts +0 -1
- package/dist/types/messaging.d.ts.map +1 -1
- package/dist/types/skills.d.ts +1 -0
- package/dist/types/skills.d.ts.map +1 -1
- package/dist/types/telemetry.d.ts +14 -1
- package/dist/types/telemetry.d.ts.map +1 -1
- package/dist/utils/atomicWrite.d.ts +15 -0
- package/dist/utils/atomicWrite.d.ts.map +1 -0
- package/dist/utils/atomicWrite.js +59 -0
- package/dist/utils/cacheControlUtils.d.ts +3 -15
- package/dist/utils/cacheControlUtils.d.ts.map +1 -1
- package/dist/utils/cacheControlUtils.js +4 -35
- package/dist/utils/containerSetup.d.ts.map +1 -1
- package/dist/utils/containerSetup.js +5 -1
- package/dist/utils/convertMessagesForAPI.d.ts +9 -1
- package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
- package/dist/utils/convertMessagesForAPI.js +74 -55
- package/dist/utils/mcpUtils.d.ts.map +1 -1
- package/dist/utils/mcpUtils.js +2 -1
- package/dist/utils/modelCapabilities.d.ts +22 -0
- package/dist/utils/modelCapabilities.d.ts.map +1 -0
- package/dist/utils/modelCapabilities.js +24 -0
- package/dist/utils/skillParser.d.ts.map +1 -1
- package/dist/utils/skillParser.js +3 -1
- package/package.json +1 -1
- package/src/agent.ts +11 -5
- package/src/index.ts +1 -0
- package/src/managers/aiManager.ts +22 -2
- package/src/managers/mcpManager.ts +8 -2
- package/src/managers/messageManager.ts +22 -104
- package/src/managers/skillManager.ts +16 -2
- package/src/prompts/index.ts +6 -1
- package/src/services/aiService.ts +15 -85
- package/src/services/configurationService.ts +27 -41
- package/src/services/hook.ts +7 -0
- package/src/services/initializationService.ts +1 -3
- package/src/services/interactionService.ts +1 -1
- package/src/services/reversionService.ts +50 -27
- package/src/services/session.ts +63 -262
- package/src/telemetry/sessionTracing.ts +167 -12
- package/src/tools/bashTool.ts +23 -1
- package/src/types/config.ts +12 -2
- package/src/types/hooks.ts +1 -0
- package/src/types/messaging.ts +0 -1
- package/src/types/skills.ts +1 -0
- package/src/types/telemetry.ts +17 -1
- package/src/utils/atomicWrite.ts +61 -0
- package/src/utils/cacheControlUtils.ts +5 -38
- package/src/utils/containerSetup.ts +5 -1
- package/src/utils/convertMessagesForAPI.ts +87 -57
- package/src/utils/mcpUtils.ts +2 -1
- package/src/utils/modelCapabilities.ts +30 -0
- package/src/utils/skillParser.ts +3 -1
|
@@ -36,9 +36,11 @@ function safeToolArguments(args) {
|
|
|
36
36
|
* Convert message format to API call format, stopping when a compacted message is encountered.
|
|
37
37
|
* Messages with no meaningful content or tool calls are filtered out.
|
|
38
38
|
* @param messages Message list
|
|
39
|
+
* @param options Optional conversion options (e.g. supportsVision)
|
|
39
40
|
* @returns Converted API message format list
|
|
40
41
|
*/
|
|
41
|
-
export function convertMessagesForAPI(messages) {
|
|
42
|
+
export function convertMessagesForAPI(messages, options) {
|
|
43
|
+
const supportsVision = options?.supportsVision !== false;
|
|
42
44
|
const recentMessages = [];
|
|
43
45
|
const startIndex = messages.length - 1;
|
|
44
46
|
for (let i = startIndex; i >= 0; i--) {
|
|
@@ -66,49 +68,57 @@ export function convertMessagesForAPI(messages) {
|
|
|
66
68
|
const toolBlocks = message.blocks.filter((block) => block.type === "tool");
|
|
67
69
|
const completedToolIds = new Set(); // Record completed tool IDs
|
|
68
70
|
if (toolBlocks.length > 0) {
|
|
71
|
+
// Collect image user messages to place after all tool messages
|
|
72
|
+
const imageUserMessages = [];
|
|
69
73
|
toolBlocks.forEach((toolBlock) => {
|
|
70
74
|
// Only add completed tool blocks (i.e., stage is 'end')
|
|
71
75
|
if (toolBlock.id && toolBlock.stage === "end") {
|
|
72
76
|
completedToolIds.add(toolBlock.id);
|
|
73
|
-
//
|
|
77
|
+
// Always add a proper tool message to satisfy tool_call_id pairing
|
|
78
|
+
recentMessages.unshift({
|
|
79
|
+
tool_call_id: toolBlock.id,
|
|
80
|
+
role: "tool",
|
|
81
|
+
content: stripAnsiColors(toolBlock.result || ""),
|
|
82
|
+
});
|
|
83
|
+
// If there are images, also prepare a user message with image content
|
|
74
84
|
if (toolBlock.images && toolBlock.images.length > 0) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
contentParts.push({
|
|
89
|
-
type: "image_url",
|
|
90
|
-
image_url: {
|
|
91
|
-
url: imageUrl,
|
|
92
|
-
detail: "auto",
|
|
93
|
-
},
|
|
85
|
+
if (supportsVision) {
|
|
86
|
+
const contentParts = [];
|
|
87
|
+
toolBlock.images.forEach((image) => {
|
|
88
|
+
const imageUrl = image.data.startsWith("data:")
|
|
89
|
+
? image.data
|
|
90
|
+
: `data:${image.mediaType || "image/png"};base64,${image.data}`;
|
|
91
|
+
contentParts.push({
|
|
92
|
+
type: "image_url",
|
|
93
|
+
image_url: {
|
|
94
|
+
url: imageUrl,
|
|
95
|
+
detail: "auto",
|
|
96
|
+
},
|
|
97
|
+
});
|
|
94
98
|
});
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
content: stripAnsiColors(toolBlock.result || ""),
|
|
108
|
-
});
|
|
99
|
+
imageUserMessages.push({
|
|
100
|
+
role: "user",
|
|
101
|
+
content: contentParts,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// Non-vision model: replace images with a text placeholder
|
|
106
|
+
imageUserMessages.push({
|
|
107
|
+
role: "user",
|
|
108
|
+
content: "[Tool returned an image, but the current model does not support image recognition]",
|
|
109
|
+
});
|
|
110
|
+
}
|
|
109
111
|
}
|
|
110
112
|
}
|
|
111
113
|
});
|
|
114
|
+
// Insert image user messages after all tool messages but before the
|
|
115
|
+
// assistant message (which will be unshifted next). Since tool messages
|
|
116
|
+
// were unshifted to the front, we splice images right after them.
|
|
117
|
+
if (imageUserMessages.length > 0) {
|
|
118
|
+
// Tool messages are at indices 0..N-1, existing messages start at N
|
|
119
|
+
const toolMsgCount = completedToolIds.size;
|
|
120
|
+
recentMessages.splice(toolMsgCount, 0, ...imageUserMessages);
|
|
121
|
+
}
|
|
112
122
|
}
|
|
113
123
|
// Construct the content of the assistant message
|
|
114
124
|
let content = "";
|
|
@@ -180,28 +190,37 @@ export function convertMessagesForAPI(messages) {
|
|
|
180
190
|
if (block.type === "image" &&
|
|
181
191
|
block.imageUrls &&
|
|
182
192
|
block.imageUrls.length > 0) {
|
|
183
|
-
|
|
184
|
-
//
|
|
185
|
-
let finalImageUrl = imageUrl;
|
|
186
|
-
if (!imageUrl.startsWith("data:image/")) {
|
|
187
|
-
// If it's a file path, it needs to be converted to base64
|
|
188
|
-
try {
|
|
189
|
-
finalImageUrl = convertImageToBase64(imageUrl);
|
|
190
|
-
}
|
|
191
|
-
catch (error) {
|
|
192
|
-
logger.error("Failed to convert image path to base64:", imageUrl, error);
|
|
193
|
-
// Skip this image, do not add to content
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
193
|
+
if (!supportsVision) {
|
|
194
|
+
// Non-vision model: replace images with a text placeholder
|
|
197
195
|
contentParts.push({
|
|
198
|
-
type: "
|
|
199
|
-
|
|
200
|
-
url: finalImageUrl,
|
|
201
|
-
detail: "auto",
|
|
202
|
-
},
|
|
196
|
+
type: "text",
|
|
197
|
+
text: "[User shared an image, but the current model does not support image recognition]",
|
|
203
198
|
});
|
|
204
|
-
}
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
block.imageUrls.forEach((imageUrl) => {
|
|
202
|
+
// Check if it's already base64, convert if not
|
|
203
|
+
let finalImageUrl = imageUrl;
|
|
204
|
+
if (!imageUrl.startsWith("data:image/")) {
|
|
205
|
+
// If it's a file path, it needs to be converted to base64
|
|
206
|
+
try {
|
|
207
|
+
finalImageUrl = convertImageToBase64(imageUrl);
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
logger.error("Failed to convert image path to base64:", imageUrl, error);
|
|
211
|
+
// Skip this image, do not add to content
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
contentParts.push({
|
|
216
|
+
type: "image_url",
|
|
217
|
+
image_url: {
|
|
218
|
+
url: finalImageUrl,
|
|
219
|
+
detail: "auto",
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
}
|
|
205
224
|
}
|
|
206
225
|
// If there is a tool block in user message, add its result
|
|
207
226
|
if (block.type === "tool" && block.stage === "end" && block.result) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcpUtils.d.ts","sourceRoot":"","sources":["../../src/utils/mcpUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,KAAK,EAAE,UAAU,EAAc,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAgDlE;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,GACjB,0BAA0B,CAgB5B;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,CACX,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,WAAW,KAClB,OAAO,CAAC;IACX,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACtD,CAAC,GACD,UAAU,CAiCZ;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,eAAe,EAAE,GACzB,eAAe,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"mcpUtils.d.ts","sourceRoot":"","sources":["../../src/utils/mcpUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,KAAK,EAAE,UAAU,EAAc,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAgDlE;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,GACjB,0BAA0B,CAgB5B;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,CACX,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,WAAW,KAClB,OAAO,CAAC;IACX,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACtD,CAAC,GACD,UAAU,CAiCZ;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,eAAe,EAAE,GACzB,eAAe,GAAG,SAAS,CAM7B"}
|
package/dist/utils/mcpUtils.js
CHANGED
|
@@ -84,5 +84,6 @@ export function createMcpToolPlugin(mcpTool, serverName, executeTool) {
|
|
|
84
84
|
* Find which server a tool belongs to
|
|
85
85
|
*/
|
|
86
86
|
export function findToolServer(toolName, servers) {
|
|
87
|
-
return servers.find((s) => s.status === "connected"
|
|
87
|
+
return servers.find((s) => (s.status === "connected" || s.status === "reconnecting") &&
|
|
88
|
+
s.tools?.some((t) => t.name === toolName));
|
|
88
89
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model Capability Detection
|
|
3
|
+
*
|
|
4
|
+
* Pure declarative capability accessors. Capabilities are configured per-model
|
|
5
|
+
* via the `capabilities` field in `ModelConfig` (settings.json), not via regex
|
|
6
|
+
* or environment variables.
|
|
7
|
+
*/
|
|
8
|
+
import type { ModelCapabilities } from "../types/config.js";
|
|
9
|
+
/**
|
|
10
|
+
* Determines if a model supports prompt caching.
|
|
11
|
+
* @param capabilities - Declarative model capabilities
|
|
12
|
+
* @returns True if promptCaching is explicitly enabled (default: false)
|
|
13
|
+
*/
|
|
14
|
+
export declare function supportsPromptCaching(capabilities?: ModelCapabilities): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Determines if a model supports image/vision recognition.
|
|
17
|
+
* Opt-out semantics: by default all models are assumed to support vision.
|
|
18
|
+
* @param capabilities - Declarative model capabilities
|
|
19
|
+
* @returns True unless vision is explicitly disabled (default: true)
|
|
20
|
+
*/
|
|
21
|
+
export declare function supportsVision(capabilities?: ModelCapabilities): boolean;
|
|
22
|
+
//# sourceMappingURL=modelCapabilities.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modelCapabilities.d.ts","sourceRoot":"","sources":["../../src/utils/modelCapabilities.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,YAAY,CAAC,EAAE,iBAAiB,GAC/B,OAAO,CAET;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,YAAY,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAExE"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model Capability Detection
|
|
3
|
+
*
|
|
4
|
+
* Pure declarative capability accessors. Capabilities are configured per-model
|
|
5
|
+
* via the `capabilities` field in `ModelConfig` (settings.json), not via regex
|
|
6
|
+
* or environment variables.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Determines if a model supports prompt caching.
|
|
10
|
+
* @param capabilities - Declarative model capabilities
|
|
11
|
+
* @returns True if promptCaching is explicitly enabled (default: false)
|
|
12
|
+
*/
|
|
13
|
+
export function supportsPromptCaching(capabilities) {
|
|
14
|
+
return capabilities?.promptCaching ?? false;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Determines if a model supports image/vision recognition.
|
|
18
|
+
* Opt-out semantics: by default all models are assumed to support vision.
|
|
19
|
+
* @param capabilities - Declarative model capabilities
|
|
20
|
+
* @returns True unless vision is explicitly disabled (default: true)
|
|
21
|
+
*/
|
|
22
|
+
export function supportsVision(capabilities) {
|
|
23
|
+
return capabilities?.vision ?? true;
|
|
24
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skillParser.d.ts","sourceRoot":"","sources":["../../src/utils/skillParser.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,eAAe,EACf,iBAAiB,EAEjB,aAAa,EACd,MAAM,mBAAmB,CAAC;AAE3B;;GAEG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,iBAAsB,GAC9B,eAAe,
|
|
1
|
+
{"version":3,"file":"skillParser.d.ts","sourceRoot":"","sources":["../../src/utils/skillParser.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,eAAe,EACf,iBAAiB,EAEjB,aAAa,EACd,MAAM,mBAAmB,CAAC;AAE3B;;GAEG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,iBAAsB,GAC9B,eAAe,CAoGjB;AAyDD;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,EAAE,CAwCvE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOtD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAa5E"}
|
|
@@ -38,7 +38,9 @@ export function parseSkillFile(filePath, options = {}) {
|
|
|
38
38
|
const skillType = skillPath.includes("/.wave/skills") ||
|
|
39
39
|
skillPath.includes("\\.wave\\skills") ||
|
|
40
40
|
skillPath.includes("/.claude/skills") ||
|
|
41
|
-
skillPath.includes("\\.claude\\skills")
|
|
41
|
+
skillPath.includes("\\.claude\\skills") ||
|
|
42
|
+
skillPath.includes("/.agents/skills") ||
|
|
43
|
+
skillPath.includes("\\.agents\\skills")
|
|
42
44
|
? "project"
|
|
43
45
|
: "personal";
|
|
44
46
|
// Extract allowed tools
|
package/package.json
CHANGED
package/src/agent.ts
CHANGED
|
@@ -41,6 +41,7 @@ import { SkillManager } from "./managers/skillManager.js";
|
|
|
41
41
|
import { TaskManager } from "./services/taskManager.js";
|
|
42
42
|
import { btw } from "./services/aiService.js";
|
|
43
43
|
import { convertMessagesForAPI } from "./utils/convertMessagesForAPI.js";
|
|
44
|
+
import { supportsVision } from "./utils/modelCapabilities.js";
|
|
44
45
|
import { parseTaskNotificationXml } from "./utils/notificationXml.js";
|
|
45
46
|
import { InitializationService } from "./services/initializationService.js";
|
|
46
47
|
import { InteractionService } from "./services/interactionService.js";
|
|
@@ -598,17 +599,20 @@ export class Agent {
|
|
|
598
599
|
options,
|
|
599
600
|
);
|
|
600
601
|
|
|
601
|
-
// Initialize OpenTelemetry (
|
|
602
|
+
// Initialize OpenTelemetry (awaited to ensure ALS context is ready before
|
|
603
|
+
// startInteractionSpan is called, preventing trace context fragmentation)
|
|
602
604
|
const telemetryConfig = this.configurationService.resolveTelemetryConfig();
|
|
603
|
-
|
|
605
|
+
try {
|
|
606
|
+
await initializeTelemetry(telemetryConfig);
|
|
607
|
+
} catch (error) {
|
|
604
608
|
this.logger?.warn("Telemetry initialization failed:", error);
|
|
605
|
-
}
|
|
609
|
+
}
|
|
606
610
|
|
|
607
611
|
// Log session_start event
|
|
608
612
|
try {
|
|
609
613
|
const modelConfig = this.getModelConfig();
|
|
610
614
|
if (modelConfig.model) {
|
|
611
|
-
logOTelEvent("session_start", {
|
|
615
|
+
await logOTelEvent("session_start", {
|
|
612
616
|
sessionId: this.messageManager.getSessionId(),
|
|
613
617
|
model: modelConfig.model,
|
|
614
618
|
workdir: this.workdir,
|
|
@@ -992,7 +996,9 @@ export class Agent {
|
|
|
992
996
|
* @returns Promise that resolves to the AI's answer
|
|
993
997
|
*/
|
|
994
998
|
public async askBtw(question: string): Promise<string> {
|
|
995
|
-
const messages = convertMessagesForAPI(this.messageManager.getMessages()
|
|
999
|
+
const messages = convertMessagesForAPI(this.messageManager.getMessages(), {
|
|
1000
|
+
supportsVision: supportsVision(this.getModelConfig().capabilities),
|
|
1001
|
+
});
|
|
996
1002
|
const result = await btw({
|
|
997
1003
|
gatewayConfig: this.getGatewayConfig(),
|
|
998
1004
|
modelConfig: this.getModelConfig(),
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ export * from "./managers/messageQueue.js";
|
|
|
14
14
|
// Export all utilities
|
|
15
15
|
export * from "./utils/bashParser.js";
|
|
16
16
|
export * from "./utils/convertMessagesForAPI.js";
|
|
17
|
+
export * from "./utils/modelCapabilities.js";
|
|
17
18
|
export * from "./utils/fileSearch.js";
|
|
18
19
|
export * from "./utils/globalLogger.js";
|
|
19
20
|
export * from "./utils/mcpUtils.js";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type CallAgentOptions } from "../services/aiService.js";
|
|
2
2
|
import * as aiService from "../services/aiService.js";
|
|
3
3
|
import { convertMessagesForAPI } from "../utils/convertMessagesForAPI.js";
|
|
4
|
+
import { supportsVision } from "../utils/modelCapabilities.js";
|
|
4
5
|
import { parseTaskNotificationXml } from "../utils/notificationXml.js";
|
|
5
6
|
import { calculateComprehensiveTotalTokens } from "../utils/tokenCalculation.js";
|
|
6
7
|
import { estimateTokens } from "../utils/tokenEstimate.js";
|
|
@@ -45,6 +46,7 @@ import {
|
|
|
45
46
|
endInteractionSpan,
|
|
46
47
|
startLLMRequestSpan,
|
|
47
48
|
endLLMRequestSpan,
|
|
49
|
+
resetTracingState,
|
|
48
50
|
} from "../telemetry/sessionTracing.js";
|
|
49
51
|
import { logOTelEvent } from "../telemetry/events.js";
|
|
50
52
|
|
|
@@ -492,7 +494,7 @@ export class AIManager {
|
|
|
492
494
|
);
|
|
493
495
|
|
|
494
496
|
// 7. Execute message reconstruction
|
|
495
|
-
this.messageManager.compactMessagesAndUpdateSession(
|
|
497
|
+
await this.messageManager.compactMessagesAndUpdateSession(
|
|
496
498
|
enhancedSummary,
|
|
497
499
|
compactUsage,
|
|
498
500
|
);
|
|
@@ -523,6 +525,9 @@ export class AIManager {
|
|
|
523
525
|
|
|
524
526
|
this.consecutiveCompactionFailures = 0;
|
|
525
527
|
|
|
528
|
+
// Reset incremental tracing state after compaction
|
|
529
|
+
resetTracingState();
|
|
530
|
+
|
|
526
531
|
// 9. Log OTEL event
|
|
527
532
|
logOTelEvent("compaction", {
|
|
528
533
|
beforeTokens: String(messagesToCompact.length),
|
|
@@ -838,7 +843,10 @@ export class AIManager {
|
|
|
838
843
|
|
|
839
844
|
// Get recent message history
|
|
840
845
|
const rawMessages = this.messageManager.getMessages();
|
|
841
|
-
const
|
|
846
|
+
const currentModelConfig = this.getModelConfig();
|
|
847
|
+
const recentMessages = convertMessagesForAPI(rawMessages, {
|
|
848
|
+
supportsVision: supportsVision(currentModelConfig.capabilities),
|
|
849
|
+
});
|
|
842
850
|
|
|
843
851
|
// Track if assistant message has been created
|
|
844
852
|
let assistantMessageCreated = false;
|
|
@@ -953,6 +961,17 @@ export class AIManager {
|
|
|
953
961
|
|
|
954
962
|
llmSpan = startLLMRequestSpan(
|
|
955
963
|
model || this.getModelConfig().model || "",
|
|
964
|
+
{
|
|
965
|
+
context: "interaction",
|
|
966
|
+
systemPrompt:
|
|
967
|
+
typeof callAgentOptions.systemPrompt === "string"
|
|
968
|
+
? callAgentOptions.systemPrompt
|
|
969
|
+
: undefined,
|
|
970
|
+
inputMessages: callAgentOptions.messages,
|
|
971
|
+
toolsSchema: callAgentOptions.tools
|
|
972
|
+
? JSON.stringify(callAgentOptions.tools)
|
|
973
|
+
: undefined,
|
|
974
|
+
},
|
|
956
975
|
);
|
|
957
976
|
|
|
958
977
|
const result = await aiService.callAgent(callAgentOptions);
|
|
@@ -966,6 +985,7 @@ export class AIManager {
|
|
|
966
985
|
outputTokens: result.usage?.completion_tokens,
|
|
967
986
|
cacheReadTokens: result.usage?.cache_read_input_tokens,
|
|
968
987
|
cacheCreationTokens: result.usage?.cache_creation_input_tokens,
|
|
988
|
+
modelOutput: result.content,
|
|
969
989
|
});
|
|
970
990
|
|
|
971
991
|
const createdByStreaming = assistantMessageCreated;
|
|
@@ -725,11 +725,17 @@ export class McpManager {
|
|
|
725
725
|
}
|
|
726
726
|
}
|
|
727
727
|
|
|
728
|
-
// Get all tools from connected servers
|
|
728
|
+
// Get all tools from connected servers.
|
|
729
|
+
// "reconnecting" servers retain their last-known tool snapshot so that
|
|
730
|
+
// transient SSE disconnects don't churn the tool list (which would bust
|
|
731
|
+
// the prompt cache). Tools are only dropped once the server truly fails
|
|
732
|
+
// (status "error"/"disconnected").
|
|
729
733
|
getAllConnectedTools(): McpTool[] {
|
|
730
734
|
const allTools: McpTool[] = [];
|
|
731
735
|
for (const server of this.servers.values()) {
|
|
732
|
-
|
|
736
|
+
const usable =
|
|
737
|
+
server.status === "connected" || server.status === "reconnecting";
|
|
738
|
+
if (usable && server.tools) {
|
|
733
739
|
allTools.push(...server.tools);
|
|
734
740
|
}
|
|
735
741
|
}
|
|
@@ -94,8 +94,6 @@ export interface MessageManagerOptions {
|
|
|
94
94
|
export class MessageManager {
|
|
95
95
|
// Private state properties
|
|
96
96
|
private sessionId: string;
|
|
97
|
-
private rootSessionId: string;
|
|
98
|
-
private parentSessionId?: string;
|
|
99
97
|
private messages: Message[];
|
|
100
98
|
private latestTotalTokens: number;
|
|
101
99
|
private workdir: string;
|
|
@@ -118,7 +116,6 @@ export class MessageManager {
|
|
|
118
116
|
options: MessageManagerOptions,
|
|
119
117
|
) {
|
|
120
118
|
this.sessionId = generateSessionId();
|
|
121
|
-
this.rootSessionId = this.sessionId;
|
|
122
119
|
this.messages = [];
|
|
123
120
|
this.latestTotalTokens = 0;
|
|
124
121
|
this.workdir = options.workdir;
|
|
@@ -152,11 +149,7 @@ export class MessageManager {
|
|
|
152
149
|
}
|
|
153
150
|
|
|
154
151
|
public getRootSessionId(): string {
|
|
155
|
-
return this.
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
public getParentSessionId(): string | undefined {
|
|
159
|
-
return this.parentSessionId;
|
|
152
|
+
return this.sessionId;
|
|
160
153
|
}
|
|
161
154
|
|
|
162
155
|
public getMessages(): Message[] {
|
|
@@ -358,8 +351,6 @@ export class MessageManager {
|
|
|
358
351
|
unsavedMessages,
|
|
359
352
|
this.workdir,
|
|
360
353
|
this.sessionType,
|
|
361
|
-
this.rootSessionId,
|
|
362
|
-
this.parentSessionId,
|
|
363
354
|
);
|
|
364
355
|
|
|
365
356
|
// Update the saved message count
|
|
@@ -383,7 +374,6 @@ export class MessageManager {
|
|
|
383
374
|
public clearMessages(): void {
|
|
384
375
|
this.setMessages([]);
|
|
385
376
|
const newSessionId = generateSessionId();
|
|
386
|
-
this.rootSessionId = newSessionId;
|
|
387
377
|
this.setSessionId(newSessionId);
|
|
388
378
|
this.setlatestTotalTokens(0);
|
|
389
379
|
this.savedMessageCount = 0; // Reset saved message count
|
|
@@ -399,8 +389,6 @@ export class MessageManager {
|
|
|
399
389
|
// Initialize state from session data
|
|
400
390
|
public initializeFromSession(sessionData: SessionData): void {
|
|
401
391
|
this.setSessionId(sessionData.id);
|
|
402
|
-
this.rootSessionId = sessionData.rootSessionId || sessionData.id;
|
|
403
|
-
this.parentSessionId = sessionData.parentSessionId;
|
|
404
392
|
this.setMessages([...sessionData.messages]);
|
|
405
393
|
this.setlatestTotalTokens(sessionData.metadata.latestTotalTokens);
|
|
406
394
|
|
|
@@ -563,12 +551,13 @@ export class MessageManager {
|
|
|
563
551
|
}
|
|
564
552
|
|
|
565
553
|
/**
|
|
566
|
-
* Compact messages and update session
|
|
554
|
+
* Compact messages and update session — append compact message to same file (append-only).
|
|
555
|
+
* After compaction, in-memory messages are [compactMessage, ...lastThreeMessages].
|
|
567
556
|
*/
|
|
568
|
-
public compactMessagesAndUpdateSession(
|
|
557
|
+
public async compactMessagesAndUpdateSession(
|
|
569
558
|
compactedContent: string,
|
|
570
559
|
usage?: Usage,
|
|
571
|
-
): void {
|
|
560
|
+
): Promise<void> {
|
|
572
561
|
// Get last 2 API rounds to preserve (structurally safe boundary)
|
|
573
562
|
const lastThreeMessages = getLastApiRounds(this.messages, 2);
|
|
574
563
|
|
|
@@ -580,23 +569,27 @@ export class MessageManager {
|
|
|
580
569
|
{
|
|
581
570
|
type: "compact",
|
|
582
571
|
content: compactedContent,
|
|
583
|
-
sessionId: this.sessionId,
|
|
584
572
|
},
|
|
585
573
|
],
|
|
586
574
|
timestamp: new Date().toISOString(),
|
|
587
575
|
...(usage && { usage }),
|
|
588
576
|
};
|
|
589
577
|
|
|
590
|
-
// Build new message array: keep the compacted message and last
|
|
578
|
+
// Build new message array: keep the compacted message and last messages
|
|
591
579
|
const newMessages: Message[] = [compactMessage, ...lastThreeMessages];
|
|
592
580
|
|
|
593
|
-
//
|
|
594
|
-
const
|
|
595
|
-
|
|
596
|
-
|
|
581
|
+
// APPEND to the same session file (append-only, like Claude Code)
|
|
582
|
+
const { appendMessages } = await import("../services/session.js");
|
|
583
|
+
await appendMessages(
|
|
584
|
+
this.sessionId,
|
|
585
|
+
newMessages,
|
|
586
|
+
this.workdir,
|
|
587
|
+
this.sessionType,
|
|
588
|
+
);
|
|
597
589
|
|
|
598
|
-
//
|
|
590
|
+
// Update in-memory state
|
|
599
591
|
this.setMessages(newMessages);
|
|
592
|
+
this.savedMessageCount = newMessages.length;
|
|
600
593
|
|
|
601
594
|
// Clear and rebuild loaded rule IDs from remaining meta messages
|
|
602
595
|
this.clearLoadedRuleIds();
|
|
@@ -911,101 +904,26 @@ export class MessageManager {
|
|
|
911
904
|
index: number,
|
|
912
905
|
reversionManager?: import("./reversionManager.js").ReversionManager,
|
|
913
906
|
): Promise<void> {
|
|
914
|
-
const { messages
|
|
907
|
+
const { messages } = await this.getFullMessageThread();
|
|
915
908
|
|
|
916
909
|
if (index < 0 || index >= messages.length) {
|
|
917
910
|
throw new Error(`Invalid message index: ${index}`);
|
|
918
911
|
}
|
|
919
912
|
|
|
920
|
-
|
|
921
|
-
let targetSessionId = this.sessionId;
|
|
922
|
-
let targetIndexInSession = index;
|
|
923
|
-
|
|
924
|
-
// We need to be careful here because loadFullMessageThread might have removed "compact" blocks
|
|
925
|
-
// Let's re-calculate based on the actual messages returned.
|
|
926
|
-
// Actually, it's easier to just load sessions one by one again or keep track of counts.
|
|
927
|
-
|
|
928
|
-
// For simplicity, let's assume we want to truncate the WHOLE thread.
|
|
929
|
-
// If the index is in a previous session, we need to:
|
|
930
|
-
// 1. Load that session.
|
|
931
|
-
// 2. Truncate it.
|
|
932
|
-
// 3. Make it the current session.
|
|
933
|
-
// 4. Delete/Invalidate subsequent sessions.
|
|
934
|
-
|
|
935
|
-
// To correctly map 'index' to a session, we need to know the message count of each session
|
|
936
|
-
// as they appear in the concatenated 'messages' array.
|
|
937
|
-
|
|
938
|
-
let remainingIndex = index;
|
|
939
|
-
const { loadSessionFromJsonl } = await import("../services/session.js");
|
|
940
|
-
|
|
941
|
-
for (const sid of sessionIds) {
|
|
942
|
-
const sessionData = await loadSessionFromJsonl(sid, this.workdir);
|
|
943
|
-
if (!sessionData) continue;
|
|
944
|
-
|
|
945
|
-
const sessionMessages = sessionData.messages;
|
|
946
|
-
// If this is not the first session in the thread, it might have a compact block at the start
|
|
947
|
-
// that was removed in getFullMessageThread.
|
|
948
|
-
const hasCompactBlock = sessionMessages[0]?.blocks.some(
|
|
949
|
-
(b) => b.type === "compact",
|
|
950
|
-
);
|
|
951
|
-
const effectiveMessages =
|
|
952
|
-
hasCompactBlock && sid !== sessionIds[0]
|
|
953
|
-
? sessionMessages.slice(1)
|
|
954
|
-
: sessionMessages;
|
|
955
|
-
|
|
956
|
-
if (remainingIndex < effectiveMessages.length) {
|
|
957
|
-
targetSessionId = sid;
|
|
958
|
-
targetIndexInSession = hasCompactBlock
|
|
959
|
-
? remainingIndex + 1
|
|
960
|
-
: remainingIndex;
|
|
961
|
-
break;
|
|
962
|
-
}
|
|
963
|
-
remainingIndex -= effectiveMessages.length;
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
// Load the target session to perform truncation
|
|
967
|
-
const targetSessionData = await loadSessionFromJsonl(
|
|
968
|
-
targetSessionId,
|
|
969
|
-
this.workdir,
|
|
970
|
-
);
|
|
971
|
-
if (!targetSessionData)
|
|
972
|
-
throw new Error(`Target session ${targetSessionId} not found`);
|
|
973
|
-
|
|
974
|
-
// Identify messages to be removed (from the whole thread)
|
|
913
|
+
const newMessages = messages.slice(0, index);
|
|
975
914
|
const messagesToRemove = messages.slice(index);
|
|
976
915
|
const messageIdsToRemove = messagesToRemove
|
|
977
916
|
.map((m) => m.id as string)
|
|
978
917
|
.filter((id) => !!id);
|
|
979
918
|
|
|
980
|
-
// Revert file changes if manager is provided
|
|
981
919
|
if (reversionManager && messageIdsToRemove.length > 0) {
|
|
982
920
|
await reversionManager.revertTo(messageIdsToRemove, messages);
|
|
983
921
|
}
|
|
984
922
|
|
|
985
|
-
//
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
);
|
|
990
|
-
|
|
991
|
-
// Update target session file
|
|
992
|
-
this.sessionId = targetSessionId;
|
|
993
|
-
this.rootSessionId = targetSessionData.rootSessionId || targetSessionId;
|
|
994
|
-
this.parentSessionId = targetSessionData.parentSessionId;
|
|
995
|
-
this.transcriptPath = this.computeTranscriptPath();
|
|
996
|
-
|
|
997
|
-
await this.rewriteSessionFile(newMessagesInSession);
|
|
998
|
-
|
|
999
|
-
// Update in-memory messages to the truncated session messages
|
|
1000
|
-
// We do NOT include ancestor messages here to avoid exceeding context limits.
|
|
1001
|
-
// The 'compact' block at the start of the session (if any) already summarizes them.
|
|
1002
|
-
this.setMessages(newMessagesInSession);
|
|
1003
|
-
|
|
1004
|
-
// Update saved message count
|
|
1005
|
-
this.savedMessageCount = newMessagesInSession.length;
|
|
1006
|
-
|
|
1007
|
-
// Notify session ID change if it changed
|
|
1008
|
-
this.callbacks.onSessionIdChange?.(this.sessionId);
|
|
923
|
+
// Rewrite file with truncated messages
|
|
924
|
+
await this.rewriteSessionFile(newMessages);
|
|
925
|
+
this.setMessages(newMessages);
|
|
926
|
+
this.savedMessageCount = newMessages.length;
|
|
1009
927
|
}
|
|
1010
928
|
|
|
1011
929
|
/**
|