wave-agent-sdk 1.2.0 → 1.3.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/dist/agent.d.ts +58 -4
- package/dist/agent.js +91 -19
- package/dist/builtin/index.js +2 -0
- package/dist/builtin/skills/settings.js +1 -12
- package/dist/builtin/skills/wave-daemon.d.ts +1 -0
- package/dist/builtin/skills/wave-daemon.js +194 -0
- package/dist/constants/images.d.ts +26 -0
- package/dist/constants/images.js +26 -0
- package/dist/constants/index.d.ts +16 -0
- package/dist/constants/index.js +16 -0
- package/dist/constants/memory.d.ts +26 -0
- package/dist/constants/memory.js +34 -0
- package/dist/constants/messages.d.ts +11 -0
- package/dist/constants/messages.js +11 -0
- package/dist/constants/plugins.d.ts +8 -0
- package/dist/constants/plugins.js +8 -0
- package/dist/constants/tools.d.ts +1 -0
- package/dist/constants/tools.js +1 -0
- package/dist/core/plugin.d.ts +53 -13
- package/dist/core/plugin.js +134 -26
- package/dist/core/session.d.ts +1 -1
- package/dist/core/session.js +1 -1
- package/dist/exec/catalog.d.ts +140 -0
- package/dist/exec/catalog.js +470 -0
- package/dist/exec/catalogAnnouncement.d.ts +89 -0
- package/dist/exec/catalogAnnouncement.js +293 -0
- package/dist/exec/constants.d.ts +51 -0
- package/dist/exec/constants.js +51 -0
- package/dist/exec/execRuntime.d.ts +55 -0
- package/dist/exec/execRuntime.js +217 -0
- package/dist/exec/workerSource.d.ts +28 -0
- package/dist/exec/workerSource.js +299 -0
- package/dist/host/index.d.ts +23 -0
- package/dist/host/index.js +23 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -0
- package/dist/managers/MemoryRuleManager.d.ts +6 -0
- package/dist/managers/MemoryRuleManager.js +12 -0
- package/dist/managers/aiManager.d.ts +35 -1
- package/dist/managers/aiManager.js +190 -21
- package/dist/managers/backgroundTaskManager.js +14 -0
- package/dist/managers/hookManager.d.ts +13 -0
- package/dist/managers/hookManager.js +31 -4
- package/dist/managers/liveConfigManager.d.ts +33 -0
- package/dist/managers/liveConfigManager.js +103 -8
- package/dist/managers/lspManager.d.ts +9 -0
- package/dist/managers/lspManager.js +47 -18
- package/dist/managers/mcpManager.d.ts +45 -10
- package/dist/managers/mcpManager.js +103 -1
- package/dist/managers/messageManager.d.ts +48 -5
- package/dist/managers/messageManager.js +107 -21
- package/dist/managers/permissionManager.d.ts +40 -0
- package/dist/managers/permissionManager.js +63 -8
- package/dist/managers/pluginManager.d.ts +46 -2
- package/dist/managers/pluginManager.js +117 -11
- package/dist/managers/pluginScopeManager.d.ts +15 -2
- package/dist/managers/pluginScopeManager.js +20 -1
- package/dist/managers/skillManager.d.ts +19 -0
- package/dist/managers/skillManager.js +44 -0
- package/dist/managers/slashCommandManager.d.ts +10 -0
- package/dist/managers/slashCommandManager.js +35 -3
- package/dist/managers/subagentManager.d.ts +8 -0
- package/dist/managers/subagentManager.js +20 -0
- package/dist/managers/toolManager.d.ts +29 -3
- package/dist/managers/toolManager.js +87 -13
- package/dist/prompts/autoMemory.d.ts +9 -0
- package/dist/prompts/autoMemory.js +30 -31
- package/dist/prompts/autoMemoryExtraction.d.ts +4 -0
- package/dist/prompts/autoMemoryExtraction.js +8 -111
- package/dist/prompts/memoryTypes.d.ts +63 -0
- package/dist/prompts/memoryTypes.js +191 -0
- package/dist/services/GitService.d.ts +7 -0
- package/dist/services/GitService.js +23 -0
- package/dist/services/MarketplaceService.d.ts +101 -17
- package/dist/services/MarketplaceService.js +318 -119
- package/dist/services/artifactContent.d.ts +84 -0
- package/dist/services/artifactContent.js +204 -0
- package/dist/services/artifactSession.d.ts +6 -0
- package/dist/services/artifactSession.js +17 -0
- package/dist/services/autoMemoryService.js +5 -13
- package/dist/services/configurationService.d.ts +60 -9
- package/dist/services/configurationService.js +129 -54
- package/dist/services/contentSummarizer.d.ts +15 -0
- package/dist/services/contentSummarizer.js +45 -0
- package/dist/services/execAvailability.d.ts +9 -0
- package/dist/services/execAvailability.js +32 -0
- package/dist/services/fileWatcher.js +61 -6
- package/dist/services/initializationService.js +19 -15
- package/dist/services/interactionService.d.ts +9 -1
- package/dist/services/interactionService.js +28 -8
- package/dist/services/jsonlHandler.d.ts +84 -0
- package/dist/services/jsonlHandler.js +209 -14
- package/dist/services/memory.d.ts +3 -1
- package/dist/services/memory.js +13 -9
- package/dist/services/officialMarketplaceMirror.js +3 -2
- package/dist/services/pluginLoader.d.ts +12 -4
- package/dist/services/pluginLoader.js +38 -7
- package/dist/services/remoteSettingsService.js +16 -2
- package/dist/services/session.d.ts +74 -0
- package/dist/services/session.js +144 -3
- package/dist/services/sessionEntries.d.ts +2 -0
- package/dist/services/sessionEntries.js +20 -0
- package/dist/stdio/index.d.ts +3 -1
- package/dist/stdio/index.js +3 -1
- package/dist/stdio/notificationRouter.js +1 -0
- package/dist/stdio/stdioAgent.d.ts +14 -7
- package/dist/stdio/stdioAgent.js +19 -0
- package/dist/tools/artifactTool.js +406 -273
- package/dist/tools/bashTool.js +8 -6
- package/dist/tools/editTool.js +6 -3
- package/dist/tools/execTool.d.ts +2 -0
- package/dist/tools/execTool.js +165 -0
- package/dist/tools/grepTool.js +7 -1
- package/dist/tools/readTool.js +30 -2
- package/dist/tools/types.d.ts +34 -8
- package/dist/tools/webFetchTool.js +15 -166
- package/dist/tools/workflowTool.js +40 -8
- package/dist/tools/writeTool.js +6 -3
- package/dist/types/agent.d.ts +20 -5
- package/dist/types/configuration.d.ts +39 -1
- package/dist/types/marketplace.d.ts +40 -2
- package/dist/types/mcp.d.ts +39 -0
- package/dist/types/permissions.d.ts +22 -0
- package/dist/types/permissions.js +17 -0
- package/dist/types/plugins.d.ts +26 -2
- package/dist/types/skills.d.ts +15 -0
- package/dist/utils/constants.d.ts +10 -0
- package/dist/utils/constants.js +10 -0
- package/dist/utils/containerSetup.js +43 -0
- package/dist/utils/convertMessagesForAPI.d.ts +7 -1
- package/dist/utils/convertMessagesForAPI.js +64 -14
- package/dist/utils/fileChangeReminder.d.ts +20 -0
- package/dist/utils/fileChangeReminder.js +153 -0
- package/dist/utils/fileSearch.js +4 -3
- package/dist/utils/fileUtils.d.ts +33 -0
- package/dist/utils/fileUtils.js +81 -0
- package/dist/utils/frontmatterYaml.d.ts +33 -0
- package/dist/utils/frontmatterYaml.js +192 -0
- package/dist/utils/imageBudget.d.ts +85 -0
- package/dist/utils/imageBudget.js +109 -0
- package/dist/utils/imageDimensions.d.ts +83 -0
- package/dist/utils/imageDimensions.js +232 -0
- package/dist/utils/imageProcessor.d.ts +66 -0
- package/dist/utils/imageProcessor.js +84 -0
- package/dist/utils/imageRewrite.d.ts +29 -0
- package/dist/utils/imageRewrite.js +251 -0
- package/dist/utils/markdownParser.d.ts +5 -1
- package/dist/utils/markdownParser.js +9 -51
- package/dist/utils/mcpInstructions.d.ts +61 -0
- package/dist/utils/mcpInstructions.js +126 -0
- package/dist/utils/mcpUtils.d.ts +7 -0
- package/dist/utils/mcpUtils.js +11 -2
- package/dist/utils/memoryAge.d.ts +32 -0
- package/dist/utils/memoryAge.js +47 -0
- package/dist/utils/memoryEntrypoint.d.ts +20 -0
- package/dist/utils/memoryEntrypoint.js +49 -0
- package/dist/utils/memoryIndex.d.ts +30 -0
- package/dist/utils/memoryIndex.js +76 -0
- package/dist/utils/messageOperations.d.ts +6 -2
- package/dist/utils/messageOperations.js +40 -29
- package/dist/utils/nestedMemory.d.ts +22 -0
- package/dist/utils/nestedMemory.js +61 -0
- package/dist/utils/npmTarball.d.ts +19 -0
- package/dist/utils/npmTarball.js +92 -0
- package/dist/utils/pluginSource.d.ts +37 -0
- package/dist/utils/pluginSource.js +73 -0
- package/dist/utils/ripgrep.d.ts +18 -4
- package/dist/utils/ripgrep.js +56 -4
- package/dist/utils/runtimeDeps.d.ts +35 -0
- package/dist/utils/runtimeDeps.js +426 -0
- package/dist/utils/skillParser.js +22 -52
- package/dist/utils/subagentParser.js +39 -43
- package/dist/utils/userSettings.d.ts +90 -0
- package/dist/utils/userSettings.js +291 -0
- package/package.json +10 -7
|
@@ -5,64 +5,22 @@ import { join } from "path";
|
|
|
5
5
|
import { tmpdir } from "os";
|
|
6
6
|
import { SKILL_BASH_MAX_OUTPUT_CHARS, PREVIEW_SIZE_BYTES, } from "../constants/toolLimits.js";
|
|
7
7
|
import { logger } from "./globalLogger.js";
|
|
8
|
+
import { parseFrontmatterYaml, splitFrontmatter } from "./frontmatterYaml.js";
|
|
8
9
|
import { resolveShellPath } from "./shellResolver.js";
|
|
9
10
|
const execAsync = promisify(exec);
|
|
10
11
|
/**
|
|
11
|
-
* Parse YAML frontmatter from markdown content
|
|
12
|
+
* Parse YAML frontmatter from markdown content.
|
|
13
|
+
*
|
|
14
|
+
* Value parsing is shared with skills, subagents and memory files — see
|
|
15
|
+
* `frontmatterYaml.ts` (spec `ui/slash-commands` 场景 3,
|
|
16
|
+
* `core/memory-management` 场景 4/13).
|
|
12
17
|
*/
|
|
13
18
|
export function parseFrontmatter(content) {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
if (!match) {
|
|
17
|
-
return { content };
|
|
18
|
-
}
|
|
19
|
-
const [, frontmatterStr, bodyContent] = match;
|
|
20
|
-
try {
|
|
21
|
-
// Simple YAML parser for our use case (supports key: value and list items)
|
|
22
|
-
const frontmatter = {};
|
|
23
|
-
const lines = frontmatterStr.split("\n");
|
|
24
|
-
let currentKey = null;
|
|
25
|
-
for (const line of lines) {
|
|
26
|
-
const trimmedLine = line.trim();
|
|
27
|
-
if (!trimmedLine || trimmedLine.startsWith("#"))
|
|
28
|
-
continue;
|
|
29
|
-
// Check if it's a list item
|
|
30
|
-
if (trimmedLine.startsWith("-") && currentKey) {
|
|
31
|
-
let value = trimmedLine.slice(1).trim();
|
|
32
|
-
if (value) {
|
|
33
|
-
// Remove surrounding quotes if present
|
|
34
|
-
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
35
|
-
(value.startsWith("'") && value.endsWith("'"))) {
|
|
36
|
-
value = value.slice(1, -1);
|
|
37
|
-
}
|
|
38
|
-
if (!Array.isArray(frontmatter[currentKey])) {
|
|
39
|
-
frontmatter[currentKey] = [];
|
|
40
|
-
}
|
|
41
|
-
frontmatter[currentKey].push(value);
|
|
42
|
-
}
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
const colonIndex = trimmedLine.indexOf(":");
|
|
46
|
-
if (colonIndex === -1)
|
|
47
|
-
continue;
|
|
48
|
-
const key = trimmedLine.slice(0, colonIndex).trim();
|
|
49
|
-
const value = trimmedLine.slice(colonIndex + 1).trim();
|
|
50
|
-
currentKey = key;
|
|
51
|
-
if (value) {
|
|
52
|
-
// Remove surrounding quotes if present
|
|
53
|
-
const unquotedValue = (value.startsWith('"') && value.endsWith('"')) ||
|
|
54
|
-
(value.startsWith("'") && value.endsWith("'"))
|
|
55
|
-
? value.slice(1, -1)
|
|
56
|
-
: value;
|
|
57
|
-
frontmatter[key] = unquotedValue;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return { frontmatter, content: bodyContent };
|
|
61
|
-
}
|
|
62
|
-
catch {
|
|
63
|
-
// If parsing fails, just return the content without frontmatter
|
|
19
|
+
const { yaml, body } = splitFrontmatter(content);
|
|
20
|
+
if (yaml === null) {
|
|
64
21
|
return { content };
|
|
65
22
|
}
|
|
23
|
+
return { frontmatter: parseFrontmatterYaml(yaml), content: body };
|
|
66
24
|
}
|
|
67
25
|
/**
|
|
68
26
|
* Parse markdown file and extract config and content
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP server usage notes, announced in the conversation instead of the system
|
|
3
|
+
* prompt (aligned with Claude Code's `mcp_instructions_delta` attachment).
|
|
4
|
+
*
|
|
5
|
+
* A server's `initialize.instructions` is the one channel for "you need to know
|
|
6
|
+
* this without calling anything" content: rate limits, preconditions, whole-server
|
|
7
|
+
* conventions. It cannot ride in a tool description — the catalog compresses those
|
|
8
|
+
* — and it must not ride in the system prompt: it appears only when a connection
|
|
9
|
+
* happens, and a connection that happens mid-session would rewrite the whole
|
|
10
|
+
* cached prefix (that is exactly the note Claude Code left on the system-prompt
|
|
11
|
+
* variant it replaced: "busts the prompt cache on late MCP connect").
|
|
12
|
+
*
|
|
13
|
+
* So each server's notes are announced once, as a persisted meta message appended
|
|
14
|
+
* at the tail. Two consequences are deliberate:
|
|
15
|
+
* - History is not rewritten, so a disconnection cannot take the prose back out
|
|
16
|
+
* of the context. It gets a "no longer applies" notice instead.
|
|
17
|
+
* - The announced set is derived from the history itself (a marker line), not
|
|
18
|
+
* from process state, so there is nothing to desync: if the marker is no longer
|
|
19
|
+
* there (compaction, rewind), the next turn announces the server again. A
|
|
20
|
+
* duplicate is better than a loss.
|
|
21
|
+
*/
|
|
22
|
+
import type { Message } from "../types/messaging.js";
|
|
23
|
+
/**
|
|
24
|
+
* Per-server cap on announced notes, in characters. Borrowed from Claude Code's
|
|
25
|
+
* `MAX_MCP_DESCRIPTION_LENGTH` (`services/mcp/client.ts`), which caps both MCP tool
|
|
26
|
+
* descriptions and server instructions at this value. Counted in characters like
|
|
27
|
+
* CC, not CJK-aware: what MCP servers write about themselves is overwhelmingly
|
|
28
|
+
* English, the same assumption the catalog budget makes.
|
|
29
|
+
*/
|
|
30
|
+
export declare const MCP_INSTRUCTIONS_MAX_CHARS = 2048;
|
|
31
|
+
/** What one announcement carries: who appeared, who went away. */
|
|
32
|
+
export interface McpInstructionsDelta {
|
|
33
|
+
added: Array<{
|
|
34
|
+
name: string;
|
|
35
|
+
instructions: string;
|
|
36
|
+
}>;
|
|
37
|
+
removed: string[];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Bound a server's notes to the cap, marking the cut so the model knows it is
|
|
41
|
+
* reading an incomplete copy rather than a short one.
|
|
42
|
+
*/
|
|
43
|
+
export declare function truncateMcpInstructions(raw: string | undefined): string | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Servers whose notes the history already contains: markers are folded in message
|
|
46
|
+
* order, so an announced server that later went away stops counting as announced
|
|
47
|
+
* (and is announced again if it comes back).
|
|
48
|
+
*
|
|
49
|
+
* Only the scanner's own messages are read. A marker quoted anywhere else — a
|
|
50
|
+
* reply explaining the mechanism, a hook echoing one — is prose *about* a marker,
|
|
51
|
+
* not a statement of connection state. Counting it invents an announcement, and
|
|
52
|
+
* the invented server is then reported as departed as soon as it is unavailable.
|
|
53
|
+
*/
|
|
54
|
+
export declare function collectAnnouncedServers(messages: Message[]): Set<string>;
|
|
55
|
+
/**
|
|
56
|
+
* Render one announcement, or null when there is nothing to say. The marker line
|
|
57
|
+
* leads so the scan reads state without parsing the prose, and the prose keeps
|
|
58
|
+
* opencode's `<mcp_instructions>` envelope so notes stay distinguishable per
|
|
59
|
+
* server and verbatim per line.
|
|
60
|
+
*/
|
|
61
|
+
export declare function buildMcpInstructionsAnnouncement(delta: McpInstructionsDelta): string | null;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP server usage notes, announced in the conversation instead of the system
|
|
3
|
+
* prompt (aligned with Claude Code's `mcp_instructions_delta` attachment).
|
|
4
|
+
*
|
|
5
|
+
* A server's `initialize.instructions` is the one channel for "you need to know
|
|
6
|
+
* this without calling anything" content: rate limits, preconditions, whole-server
|
|
7
|
+
* conventions. It cannot ride in a tool description — the catalog compresses those
|
|
8
|
+
* — and it must not ride in the system prompt: it appears only when a connection
|
|
9
|
+
* happens, and a connection that happens mid-session would rewrite the whole
|
|
10
|
+
* cached prefix (that is exactly the note Claude Code left on the system-prompt
|
|
11
|
+
* variant it replaced: "busts the prompt cache on late MCP connect").
|
|
12
|
+
*
|
|
13
|
+
* So each server's notes are announced once, as a persisted meta message appended
|
|
14
|
+
* at the tail. Two consequences are deliberate:
|
|
15
|
+
* - History is not rewritten, so a disconnection cannot take the prose back out
|
|
16
|
+
* of the context. It gets a "no longer applies" notice instead.
|
|
17
|
+
* - The announced set is derived from the history itself (a marker line), not
|
|
18
|
+
* from process state, so there is nothing to desync: if the marker is no longer
|
|
19
|
+
* there (compaction, rewind), the next turn announces the server again. A
|
|
20
|
+
* duplicate is better than a loss.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Per-server cap on announced notes, in characters. Borrowed from Claude Code's
|
|
24
|
+
* `MAX_MCP_DESCRIPTION_LENGTH` (`services/mcp/client.ts`), which caps both MCP tool
|
|
25
|
+
* descriptions and server instructions at this value. Counted in characters like
|
|
26
|
+
* CC, not CJK-aware: what MCP servers write about themselves is overwhelmingly
|
|
27
|
+
* English, the same assumption the catalog budget makes.
|
|
28
|
+
*/
|
|
29
|
+
export const MCP_INSTRUCTIONS_MAX_CHARS = 2048;
|
|
30
|
+
/** Marks an announcement message so the history scan can find it. */
|
|
31
|
+
const MARKER_PREFIX = "<!-- mcp-instructions ";
|
|
32
|
+
const MARKER_SUFFIX = " -->";
|
|
33
|
+
/**
|
|
34
|
+
* Bound a server's notes to the cap, marking the cut so the model knows it is
|
|
35
|
+
* reading an incomplete copy rather than a short one.
|
|
36
|
+
*/
|
|
37
|
+
export function truncateMcpInstructions(raw) {
|
|
38
|
+
if (raw === undefined || raw.length <= MCP_INSTRUCTIONS_MAX_CHARS) {
|
|
39
|
+
return raw;
|
|
40
|
+
}
|
|
41
|
+
return `${raw.slice(0, MCP_INSTRUCTIONS_MAX_CHARS)}… [truncated]`;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Servers whose notes the history already contains: markers are folded in message
|
|
45
|
+
* order, so an announced server that later went away stops counting as announced
|
|
46
|
+
* (and is announced again if it comes back).
|
|
47
|
+
*
|
|
48
|
+
* Only the scanner's own messages are read. A marker quoted anywhere else — a
|
|
49
|
+
* reply explaining the mechanism, a hook echoing one — is prose *about* a marker,
|
|
50
|
+
* not a statement of connection state. Counting it invents an announcement, and
|
|
51
|
+
* the invented server is then reported as departed as soon as it is unavailable.
|
|
52
|
+
*/
|
|
53
|
+
export function collectAnnouncedServers(messages) {
|
|
54
|
+
const announced = new Set();
|
|
55
|
+
for (const message of messages) {
|
|
56
|
+
if (message.isMeta !== true)
|
|
57
|
+
continue;
|
|
58
|
+
for (const block of message.blocks) {
|
|
59
|
+
if (block.type !== "text")
|
|
60
|
+
continue;
|
|
61
|
+
for (const line of block.content.split("\n")) {
|
|
62
|
+
const delta = parseMarkerLine(line);
|
|
63
|
+
if (!delta)
|
|
64
|
+
continue;
|
|
65
|
+
for (const name of delta.added)
|
|
66
|
+
announced.add(name);
|
|
67
|
+
for (const name of delta.removed)
|
|
68
|
+
announced.delete(name);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return announced;
|
|
73
|
+
}
|
|
74
|
+
function parseMarkerLine(line) {
|
|
75
|
+
const trimmed = line.trim();
|
|
76
|
+
if (!trimmed.startsWith(MARKER_PREFIX) || !trimmed.endsWith(MARKER_SUFFIX)) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const payload = trimmed.slice(MARKER_PREFIX.length, trimmed.length - MARKER_SUFFIX.length);
|
|
80
|
+
try {
|
|
81
|
+
const parsed = JSON.parse(payload);
|
|
82
|
+
return {
|
|
83
|
+
added: Array.isArray(parsed.added) ? parsed.added.map(String) : [],
|
|
84
|
+
removed: Array.isArray(parsed.removed) ? parsed.removed.map(String) : [],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// A marker we cannot read (hand-edited or corrupted history) must not take
|
|
89
|
+
// the turn down; the worst case is announcing that server once more.
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Render one announcement, or null when there is nothing to say. The marker line
|
|
95
|
+
* leads so the scan reads state without parsing the prose, and the prose keeps
|
|
96
|
+
* opencode's `<mcp_instructions>` envelope so notes stay distinguishable per
|
|
97
|
+
* server and verbatim per line.
|
|
98
|
+
*/
|
|
99
|
+
export function buildMcpInstructionsAnnouncement(delta) {
|
|
100
|
+
if (delta.added.length === 0 && delta.removed.length === 0)
|
|
101
|
+
return null;
|
|
102
|
+
const parts = [
|
|
103
|
+
`${MARKER_PREFIX}${JSON.stringify({
|
|
104
|
+
added: delta.added.map((server) => server.name),
|
|
105
|
+
removed: delta.removed,
|
|
106
|
+
})}${MARKER_SUFFIX}`,
|
|
107
|
+
];
|
|
108
|
+
if (delta.added.length > 0) {
|
|
109
|
+
parts.push("The following MCP servers have provided instructions for how to use their tools and resources:", "", renderEnvelope(delta.added));
|
|
110
|
+
}
|
|
111
|
+
if (delta.removed.length > 0) {
|
|
112
|
+
parts.push("The following MCP servers have disconnected. Their instructions above no longer apply:", ...delta.removed);
|
|
113
|
+
}
|
|
114
|
+
return parts.join("\n");
|
|
115
|
+
}
|
|
116
|
+
function renderEnvelope(servers) {
|
|
117
|
+
return [
|
|
118
|
+
"<mcp_instructions>",
|
|
119
|
+
...servers.flatMap((server) => [
|
|
120
|
+
` <server name="${server.name}">`,
|
|
121
|
+
...server.instructions.split("\n").map((line) => ` ${line}`),
|
|
122
|
+
" </server>",
|
|
123
|
+
]),
|
|
124
|
+
"</mcp_instructions>",
|
|
125
|
+
].join("\n");
|
|
126
|
+
}
|
package/dist/utils/mcpUtils.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { ChatCompletionFunctionTool } from "openai/resources.js";
|
|
2
2
|
import type { ToolPlugin, ToolContext } from "../tools/types.js";
|
|
3
3
|
import type { McpTool, McpServerStatus } from "../types/index.js";
|
|
4
|
+
/**
|
|
5
|
+
* The flattened name an MCP tool is declared and called under.
|
|
6
|
+
*
|
|
7
|
+
* Single source of truth: `McpManager.executeMcpTool` parses names in this
|
|
8
|
+
* shape back into (server, tool), so every producer must agree exactly.
|
|
9
|
+
*/
|
|
10
|
+
export declare function mcpToolFlatName(serverName: string, toolName: string): string;
|
|
4
11
|
/**
|
|
5
12
|
* Convert MCP tool to OpenAI function tool format
|
|
6
13
|
*/
|
package/dist/utils/mcpUtils.js
CHANGED
|
@@ -36,12 +36,21 @@ function cleanSchema(schema) {
|
|
|
36
36
|
}
|
|
37
37
|
return newSchema;
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* The flattened name an MCP tool is declared and called under.
|
|
41
|
+
*
|
|
42
|
+
* Single source of truth: `McpManager.executeMcpTool` parses names in this
|
|
43
|
+
* shape back into (server, tool), so every producer must agree exactly.
|
|
44
|
+
*/
|
|
45
|
+
export function mcpToolFlatName(serverName, toolName) {
|
|
46
|
+
return `mcp__${serverName}__${toolName}`;
|
|
47
|
+
}
|
|
39
48
|
/**
|
|
40
49
|
* Convert MCP tool to OpenAI function tool format
|
|
41
50
|
*/
|
|
42
51
|
export function mcpToolToOpenAITool(mcpTool, serverName) {
|
|
43
52
|
const cleanInputSchema = cleanSchema(mcpTool.inputSchema);
|
|
44
|
-
const prefixedName =
|
|
53
|
+
const prefixedName = mcpToolFlatName(serverName, mcpTool.name);
|
|
45
54
|
return {
|
|
46
55
|
type: "function",
|
|
47
56
|
function: {
|
|
@@ -55,7 +64,7 @@ export function mcpToolToOpenAITool(mcpTool, serverName) {
|
|
|
55
64
|
* Create a tool plugin wrapper for an MCP tool
|
|
56
65
|
*/
|
|
57
66
|
export function createMcpToolPlugin(mcpTool, serverName, executeTool) {
|
|
58
|
-
const prefixedName =
|
|
67
|
+
const prefixedName = mcpToolFlatName(serverName, mcpTool.name);
|
|
59
68
|
return {
|
|
60
69
|
name: prefixedName,
|
|
61
70
|
config: mcpToolToOpenAITool(mcpTool, serverName),
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Staleness annotations for auto-memory files.
|
|
3
|
+
*
|
|
4
|
+
* A memory is a point-in-time observation, so its `file:line` citations and
|
|
5
|
+
* symbol names can silently rot as the code moves on. A stale claim that
|
|
6
|
+
* carries a citation reads as *more* authoritative, not less, so the age has to
|
|
7
|
+
* be stated explicitly when the file is read.
|
|
8
|
+
*
|
|
9
|
+
* Aligned with Claude Code's `memdir/memoryAge.ts`.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Days elapsed since mtime, floor-rounded — 0 for today, 1 for yesterday, 2+
|
|
13
|
+
* for older. Negative inputs (future mtime, clock skew) clamp to 0.
|
|
14
|
+
*/
|
|
15
|
+
export declare function memoryAgeDays(mtimeMs: number): number;
|
|
16
|
+
/**
|
|
17
|
+
* Plain-text staleness caveat for memories more than a day old, or `""` for
|
|
18
|
+
* fresh ones (warning there is noise). Models are poor at date arithmetic — a
|
|
19
|
+
* raw ISO timestamp does not trigger staleness reasoning the way "47 days ago"
|
|
20
|
+
* does.
|
|
21
|
+
*
|
|
22
|
+
* Only the day count is derived from the clock, so the text is byte-stable
|
|
23
|
+
* within a day: re-reading the same file at the same mtime cannot change the
|
|
24
|
+
* bytes in the conversation history.
|
|
25
|
+
*/
|
|
26
|
+
export declare function memoryFreshnessText(mtimeMs: number): string;
|
|
27
|
+
/**
|
|
28
|
+
* Per-memory staleness note wrapped in `<system-reminder>` tags, or `""` for
|
|
29
|
+
* memories up to a day old. For callers that do not add their own wrapper
|
|
30
|
+
* (the Read tool prepends this to the file content it returns).
|
|
31
|
+
*/
|
|
32
|
+
export declare function memoryFreshnessNote(mtimeMs: number): string;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Staleness annotations for auto-memory files.
|
|
3
|
+
*
|
|
4
|
+
* A memory is a point-in-time observation, so its `file:line` citations and
|
|
5
|
+
* symbol names can silently rot as the code moves on. A stale claim that
|
|
6
|
+
* carries a citation reads as *more* authoritative, not less, so the age has to
|
|
7
|
+
* be stated explicitly when the file is read.
|
|
8
|
+
*
|
|
9
|
+
* Aligned with Claude Code's `memdir/memoryAge.ts`.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Days elapsed since mtime, floor-rounded — 0 for today, 1 for yesterday, 2+
|
|
13
|
+
* for older. Negative inputs (future mtime, clock skew) clamp to 0.
|
|
14
|
+
*/
|
|
15
|
+
export function memoryAgeDays(mtimeMs) {
|
|
16
|
+
return Math.max(0, Math.floor((Date.now() - mtimeMs) / 86400000));
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Plain-text staleness caveat for memories more than a day old, or `""` for
|
|
20
|
+
* fresh ones (warning there is noise). Models are poor at date arithmetic — a
|
|
21
|
+
* raw ISO timestamp does not trigger staleness reasoning the way "47 days ago"
|
|
22
|
+
* does.
|
|
23
|
+
*
|
|
24
|
+
* Only the day count is derived from the clock, so the text is byte-stable
|
|
25
|
+
* within a day: re-reading the same file at the same mtime cannot change the
|
|
26
|
+
* bytes in the conversation history.
|
|
27
|
+
*/
|
|
28
|
+
export function memoryFreshnessText(mtimeMs) {
|
|
29
|
+
const days = memoryAgeDays(mtimeMs);
|
|
30
|
+
if (days <= 1)
|
|
31
|
+
return "";
|
|
32
|
+
return (`This memory is ${days} days old. ` +
|
|
33
|
+
`Memories are point-in-time observations, not live state — ` +
|
|
34
|
+
`claims about code behavior or file:line citations may be outdated. ` +
|
|
35
|
+
`Verify against current code before asserting as fact.`);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Per-memory staleness note wrapped in `<system-reminder>` tags, or `""` for
|
|
39
|
+
* memories up to a day old. For callers that do not add their own wrapper
|
|
40
|
+
* (the Read tool prepends this to the file content it returns).
|
|
41
|
+
*/
|
|
42
|
+
export function memoryFreshnessNote(mtimeMs) {
|
|
43
|
+
const text = memoryFreshnessText(mtimeMs);
|
|
44
|
+
if (!text)
|
|
45
|
+
return "";
|
|
46
|
+
return `<system-reminder>${text}</system-reminder>\n`;
|
|
47
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface EntrypointTruncation {
|
|
2
|
+
/** Content ready to be injected, including the warning when truncated. */
|
|
3
|
+
content: string;
|
|
4
|
+
lineCount: number;
|
|
5
|
+
charCount: number;
|
|
6
|
+
wasLineTruncated: boolean;
|
|
7
|
+
wasCharTruncated: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Truncate the auto-memory entrypoint to the line AND character caps, appending
|
|
11
|
+
* a warning that names which cap fired.
|
|
12
|
+
*
|
|
13
|
+
* Line-truncates first (a natural boundary), then character-truncates at the
|
|
14
|
+
* last newline before the cap so a line is never cut in half. The character cap
|
|
15
|
+
* is checked against the *original* size: long lines are the failure mode it
|
|
16
|
+
* targets, so measuring after the line truncation would understate the warning.
|
|
17
|
+
*
|
|
18
|
+
* Aligned with Claude Code's `truncateEntrypointContent()` in `memdir.ts`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function truncateEntrypointContent(raw: string): EntrypointTruncation;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { MAX_MEMORY_ENTRYPOINT_CHARS, MAX_MEMORY_ENTRYPOINT_LINES, MEMORY_ENTRYPOINT_NAME, } from "../constants/memory.js";
|
|
2
|
+
/**
|
|
3
|
+
* Truncate the auto-memory entrypoint to the line AND character caps, appending
|
|
4
|
+
* a warning that names which cap fired.
|
|
5
|
+
*
|
|
6
|
+
* Line-truncates first (a natural boundary), then character-truncates at the
|
|
7
|
+
* last newline before the cap so a line is never cut in half. The character cap
|
|
8
|
+
* is checked against the *original* size: long lines are the failure mode it
|
|
9
|
+
* targets, so measuring after the line truncation would understate the warning.
|
|
10
|
+
*
|
|
11
|
+
* Aligned with Claude Code's `truncateEntrypointContent()` in `memdir.ts`.
|
|
12
|
+
*/
|
|
13
|
+
export function truncateEntrypointContent(raw) {
|
|
14
|
+
const trimmed = raw.trim();
|
|
15
|
+
const contentLines = trimmed.split("\n");
|
|
16
|
+
const lineCount = contentLines.length;
|
|
17
|
+
const charCount = trimmed.length;
|
|
18
|
+
const wasLineTruncated = lineCount > MAX_MEMORY_ENTRYPOINT_LINES;
|
|
19
|
+
const wasCharTruncated = charCount > MAX_MEMORY_ENTRYPOINT_CHARS;
|
|
20
|
+
if (!wasLineTruncated && !wasCharTruncated) {
|
|
21
|
+
return {
|
|
22
|
+
content: trimmed,
|
|
23
|
+
lineCount,
|
|
24
|
+
charCount,
|
|
25
|
+
wasLineTruncated,
|
|
26
|
+
wasCharTruncated,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
let truncated = wasLineTruncated
|
|
30
|
+
? contentLines.slice(0, MAX_MEMORY_ENTRYPOINT_LINES).join("\n")
|
|
31
|
+
: trimmed;
|
|
32
|
+
if (truncated.length > MAX_MEMORY_ENTRYPOINT_CHARS) {
|
|
33
|
+
const cutAt = truncated.lastIndexOf("\n", MAX_MEMORY_ENTRYPOINT_CHARS);
|
|
34
|
+
truncated = truncated.slice(0, cutAt > 0 ? cutAt : MAX_MEMORY_ENTRYPOINT_CHARS);
|
|
35
|
+
}
|
|
36
|
+
const reason = wasCharTruncated && !wasLineTruncated
|
|
37
|
+
? `${charCount} characters (limit: ${MAX_MEMORY_ENTRYPOINT_CHARS}) — index entries are too long`
|
|
38
|
+
: wasLineTruncated && !wasCharTruncated
|
|
39
|
+
? `${lineCount} lines (limit: ${MAX_MEMORY_ENTRYPOINT_LINES})`
|
|
40
|
+
: `${lineCount} lines and ${charCount} characters`;
|
|
41
|
+
return {
|
|
42
|
+
content: truncated +
|
|
43
|
+
`\n\n> WARNING: ${MEMORY_ENTRYPOINT_NAME} is ${reason}. Only part of it was loaded. Keep index entries to one line under ~200 chars; move detail into topic files.`,
|
|
44
|
+
lineCount,
|
|
45
|
+
charCount,
|
|
46
|
+
wasLineTruncated,
|
|
47
|
+
wasCharTruncated,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type MemoryType } from "../constants/memory.js";
|
|
2
|
+
export interface MemoryHeader {
|
|
3
|
+
filename: string;
|
|
4
|
+
filePath: string;
|
|
5
|
+
mtimeMs: number;
|
|
6
|
+
description: string | null;
|
|
7
|
+
type: MemoryType | undefined;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* List the topic files in the auto-memory directory with their frontmatter
|
|
11
|
+
* metadata, newest first.
|
|
12
|
+
*
|
|
13
|
+
* The listing is injected into the extraction fork prompt so the agent can tell
|
|
14
|
+
* whether a memory already exists without spending a turn on `ls`. `MEMORY.md`
|
|
15
|
+
* is excluded — it is the index, not a topic file.
|
|
16
|
+
*
|
|
17
|
+
* Aligned with Claude Code's `memdir/memoryScan.ts`. Two deliberate
|
|
18
|
+
* differences: Wave has a single memory directory (no private/team split), and
|
|
19
|
+
* we read the whole file instead of the first 30 lines — memory topic files are
|
|
20
|
+
* small, and one `readFile` plus one `stat` is simpler than a bounded read that
|
|
21
|
+
* has to return the mtime itself.
|
|
22
|
+
*/
|
|
23
|
+
export declare function scanMemoryFiles(memoryDir: string): Promise<MemoryHeader[]>;
|
|
24
|
+
/**
|
|
25
|
+
* Format memory headers as one line per file:
|
|
26
|
+
* `- [type] filename (timestamp): description`. The type tag and the
|
|
27
|
+
* description are each omitted when absent, so a legacy file without
|
|
28
|
+
* frontmatter degrades to `- filename (timestamp)`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function formatMemoryManifest(memories: MemoryHeader[]): string;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { MEMORY_ENTRYPOINT_NAME, parseMemoryType, } from "../constants/memory.js";
|
|
4
|
+
import { parseFrontmatter } from "./markdownParser.js";
|
|
5
|
+
const MAX_MEMORY_FILES = 200;
|
|
6
|
+
/**
|
|
7
|
+
* List the topic files in the auto-memory directory with their frontmatter
|
|
8
|
+
* metadata, newest first.
|
|
9
|
+
*
|
|
10
|
+
* The listing is injected into the extraction fork prompt so the agent can tell
|
|
11
|
+
* whether a memory already exists without spending a turn on `ls`. `MEMORY.md`
|
|
12
|
+
* is excluded — it is the index, not a topic file.
|
|
13
|
+
*
|
|
14
|
+
* Aligned with Claude Code's `memdir/memoryScan.ts`. Two deliberate
|
|
15
|
+
* differences: Wave has a single memory directory (no private/team split), and
|
|
16
|
+
* we read the whole file instead of the first 30 lines — memory topic files are
|
|
17
|
+
* small, and one `readFile` plus one `stat` is simpler than a bounded read that
|
|
18
|
+
* has to return the mtime itself.
|
|
19
|
+
*/
|
|
20
|
+
export async function scanMemoryFiles(memoryDir) {
|
|
21
|
+
let entries;
|
|
22
|
+
try {
|
|
23
|
+
entries = await fs.readdir(memoryDir, { recursive: true });
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// The directory is created lazily; a missing one simply has no memories.
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
const headers = await Promise.all(entries
|
|
30
|
+
.filter((entry) => entry.endsWith(".md") &&
|
|
31
|
+
path.basename(entry) !== MEMORY_ENTRYPOINT_NAME)
|
|
32
|
+
.map(async (filename) => {
|
|
33
|
+
const filePath = path.join(memoryDir, filename);
|
|
34
|
+
try {
|
|
35
|
+
const [content, stats] = await Promise.all([
|
|
36
|
+
fs.readFile(filePath, "utf-8"),
|
|
37
|
+
fs.stat(filePath),
|
|
38
|
+
]);
|
|
39
|
+
const { frontmatter } = parseFrontmatter(content);
|
|
40
|
+
return {
|
|
41
|
+
filename,
|
|
42
|
+
filePath,
|
|
43
|
+
mtimeMs: stats.mtimeMs,
|
|
44
|
+
description: typeof frontmatter?.description === "string"
|
|
45
|
+
? frontmatter.description
|
|
46
|
+
: null,
|
|
47
|
+
type: parseMemoryType(frontmatter?.type),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// A single unreadable file must not hide the rest of the directory.
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}));
|
|
55
|
+
return headers
|
|
56
|
+
.filter((header) => header !== null)
|
|
57
|
+
.sort((a, b) => b.mtimeMs - a.mtimeMs)
|
|
58
|
+
.slice(0, MAX_MEMORY_FILES);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Format memory headers as one line per file:
|
|
62
|
+
* `- [type] filename (timestamp): description`. The type tag and the
|
|
63
|
+
* description are each omitted when absent, so a legacy file without
|
|
64
|
+
* frontmatter degrades to `- filename (timestamp)`.
|
|
65
|
+
*/
|
|
66
|
+
export function formatMemoryManifest(memories) {
|
|
67
|
+
return memories
|
|
68
|
+
.map((memory) => {
|
|
69
|
+
const tag = memory.type ? `[${memory.type}] ` : "";
|
|
70
|
+
const timestamp = new Date(memory.mtimeMs).toISOString();
|
|
71
|
+
return memory.description
|
|
72
|
+
? `- ${tag}${memory.filename} (${timestamp}): ${memory.description}`
|
|
73
|
+
: `- ${tag}${memory.filename} (${timestamp})`;
|
|
74
|
+
})
|
|
75
|
+
.join("\n");
|
|
76
|
+
}
|
|
@@ -57,9 +57,13 @@ export interface AddErrorBlockParams {
|
|
|
57
57
|
/**
|
|
58
58
|
* Convert image file path to base64 format
|
|
59
59
|
* @param imagePath Image file path
|
|
60
|
-
* @returns base64 format image data URL
|
|
60
|
+
* @returns base64 format image data URL, or `undefined` when the file cannot be
|
|
61
|
+
* used as an image (unreadable, empty, or an extension we cannot map to a
|
|
62
|
+
* MIME type). Callers must skip the image instead of sending an empty or
|
|
63
|
+
* mislabelled payload — the model gateway rejects both with
|
|
64
|
+
* `HTTP 400 ... You have uploaded an unsupported image`.
|
|
61
65
|
*/
|
|
62
|
-
export declare const convertImageToBase64: (imagePath: string) => string;
|
|
66
|
+
export declare const convertImageToBase64: (imagePath: string) => string | undefined;
|
|
63
67
|
export declare const generateMessageId: () => string;
|
|
64
68
|
export declare const addUserMessageToMessages: ({ messages, content, customCommandContent, images, source, id, isMeta, }: AddUserMessageParams) => Message[];
|
|
65
69
|
/**
|
|
@@ -5,42 +5,53 @@ import { logger } from "./globalLogger.js";
|
|
|
5
5
|
/**
|
|
6
6
|
* Convert image file path to base64 format
|
|
7
7
|
* @param imagePath Image file path
|
|
8
|
-
* @returns base64 format image data URL
|
|
8
|
+
* @returns base64 format image data URL, or `undefined` when the file cannot be
|
|
9
|
+
* used as an image (unreadable, empty, or an extension we cannot map to a
|
|
10
|
+
* MIME type). Callers must skip the image instead of sending an empty or
|
|
11
|
+
* mislabelled payload — the model gateway rejects both with
|
|
12
|
+
* `HTTP 400 ... You have uploaded an unsupported image`.
|
|
9
13
|
*/
|
|
10
14
|
export const convertImageToBase64 = (imagePath) => {
|
|
15
|
+
let imageBuffer;
|
|
11
16
|
try {
|
|
12
|
-
|
|
13
|
-
const ext = extname(imagePath).toLowerCase().substring(1);
|
|
14
|
-
// Determine MIME type based on file extension
|
|
15
|
-
let mimeType = "image/png"; // Default
|
|
16
|
-
switch (ext) {
|
|
17
|
-
case "jpg":
|
|
18
|
-
case "jpeg":
|
|
19
|
-
mimeType = "image/jpeg";
|
|
20
|
-
break;
|
|
21
|
-
case "png":
|
|
22
|
-
mimeType = "image/png";
|
|
23
|
-
break;
|
|
24
|
-
case "gif":
|
|
25
|
-
mimeType = "image/gif";
|
|
26
|
-
break;
|
|
27
|
-
case "webp":
|
|
28
|
-
mimeType = "image/webp";
|
|
29
|
-
break;
|
|
30
|
-
case "bmp":
|
|
31
|
-
mimeType = "image/bmp";
|
|
32
|
-
break;
|
|
33
|
-
default:
|
|
34
|
-
mimeType = "image/png";
|
|
35
|
-
}
|
|
36
|
-
const base64String = imageBuffer.toString("base64");
|
|
37
|
-
return `data:${mimeType};base64,${base64String}`;
|
|
17
|
+
imageBuffer = readFileSync(imagePath);
|
|
38
18
|
}
|
|
39
19
|
catch (error) {
|
|
40
20
|
logger.error(`Failed to convert image to base64: ${imagePath}`, error);
|
|
41
|
-
|
|
42
|
-
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
if (imageBuffer.length === 0) {
|
|
24
|
+
logger.warn(`Skipping empty image file: ${imagePath}`);
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
const ext = extname(imagePath).toLowerCase().substring(1);
|
|
28
|
+
// Determine MIME type based on file extension. Unknown extensions are NOT
|
|
29
|
+
// relabelled as image/png (that is how svg/heic/avif bytes used to reach the
|
|
30
|
+
// model dressed up as PNG).
|
|
31
|
+
let mimeType;
|
|
32
|
+
switch (ext) {
|
|
33
|
+
case "jpg":
|
|
34
|
+
case "jpeg":
|
|
35
|
+
mimeType = "image/jpeg";
|
|
36
|
+
break;
|
|
37
|
+
case "png":
|
|
38
|
+
mimeType = "image/png";
|
|
39
|
+
break;
|
|
40
|
+
case "gif":
|
|
41
|
+
mimeType = "image/gif";
|
|
42
|
+
break;
|
|
43
|
+
case "webp":
|
|
44
|
+
mimeType = "image/webp";
|
|
45
|
+
break;
|
|
46
|
+
case "bmp":
|
|
47
|
+
mimeType = "image/bmp";
|
|
48
|
+
break;
|
|
49
|
+
default:
|
|
50
|
+
logger.warn(`Skipping image with unsupported extension: ${imagePath}`);
|
|
51
|
+
return undefined;
|
|
43
52
|
}
|
|
53
|
+
const base64String = imageBuffer.toString("base64");
|
|
54
|
+
return `data:${mimeType};base64,${base64String}`;
|
|
44
55
|
};
|
|
45
56
|
export const generateMessageId = () => `msg-${randomUUID()}`;
|
|
46
57
|
// Add user message
|