@4onstudios/iris-agent 0.1.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/LICENSE +21 -0
- package/README.md +690 -0
- package/dist/api/acp/acpServer.d.ts +23 -0
- package/dist/api/acp/acpServer.js +814 -0
- package/dist/api/acp/index.d.ts +2 -0
- package/dist/api/acp/index.js +2 -0
- package/dist/api/acp/irisClient.d.ts +58 -0
- package/dist/api/acp/irisClient.js +214 -0
- package/dist/api/agent.d.ts +14 -0
- package/dist/api/agent.js +4171 -0
- package/dist/api/backendService.d.ts +4 -0
- package/dist/api/backendService.js +14 -0
- package/dist/api/core/agent/host/AgentContract.d.ts +82 -0
- package/dist/api/core/agent/host/AgentContract.js +1 -0
- package/dist/api/core/agent/host/AgentRegistry.d.ts +24 -0
- package/dist/api/core/agent/host/AgentRegistry.js +125 -0
- package/dist/api/core/agent/host/defaultRegistry.d.ts +15 -0
- package/dist/api/core/agent/host/defaultRegistry.js +46 -0
- package/dist/api/core/agent/host/externalAgentLifecycle.d.ts +65 -0
- package/dist/api/core/agent/host/externalAgentLifecycle.js +117 -0
- package/dist/api/core/agent/host/hostSessionManager.d.ts +111 -0
- package/dist/api/core/agent/host/hostSessionManager.js +352 -0
- package/dist/api/core/agent/host/index.d.ts +5 -0
- package/dist/api/core/agent/host/index.js +5 -0
- package/dist/api/core/agent/index.d.ts +87 -0
- package/dist/api/core/agent/index.js +1024 -0
- package/dist/api/core/agent/publicContracts.d.ts +35 -0
- package/dist/api/core/agent/publicContracts.js +103 -0
- package/dist/api/core/agent/tools/applyDiff.d.ts +51 -0
- package/dist/api/core/agent/tools/applyDiff.js +131 -0
- package/dist/api/core/agent/tools/backgroundTasks.d.ts +240 -0
- package/dist/api/core/agent/tools/backgroundTasks.js +313 -0
- package/dist/api/core/agent/tools/createDirectory.d.ts +43 -0
- package/dist/api/core/agent/tools/createDirectory.js +77 -0
- package/dist/api/core/agent/tools/deleteFile.d.ts +37 -0
- package/dist/api/core/agent/tools/deleteFile.js +66 -0
- package/dist/api/core/agent/tools/editFile.d.ts +73 -0
- package/dist/api/core/agent/tools/editFile.js +173 -0
- package/dist/api/core/agent/tools/executeCommand.d.ts +59 -0
- package/dist/api/core/agent/tools/executeCommand.js +250 -0
- package/dist/api/core/agent/tools/fileContent.d.ts +54 -0
- package/dist/api/core/agent/tools/fileContent.js +227 -0
- package/dist/api/core/agent/tools/findDefinition.d.ts +72 -0
- package/dist/api/core/agent/tools/findDefinition.js +109 -0
- package/dist/api/core/agent/tools/findReferences.d.ts +86 -0
- package/dist/api/core/agent/tools/findReferences.js +135 -0
- package/dist/api/core/agent/tools/formatDocument.d.ts +67 -0
- package/dist/api/core/agent/tools/formatDocument.js +90 -0
- package/dist/api/core/agent/tools/getCodeActions.d.ts +77 -0
- package/dist/api/core/agent/tools/getCodeActions.js +97 -0
- package/dist/api/core/agent/tools/getCodeCompletion.d.ts +77 -0
- package/dist/api/core/agent/tools/getCodeCompletion.js +174 -0
- package/dist/api/core/agent/tools/getCodeContext.d.ts +14 -0
- package/dist/api/core/agent/tools/getCodeContext.js +282 -0
- package/dist/api/core/agent/tools/getSignatureHelp.d.ts +61 -0
- package/dist/api/core/agent/tools/getSignatureHelp.js +85 -0
- package/dist/api/core/agent/tools/getSymbols.d.ts +50 -0
- package/dist/api/core/agent/tools/getSymbols.js +164 -0
- package/dist/api/core/agent/tools/getSymbolsLSP.d.ts +58 -0
- package/dist/api/core/agent/tools/getSymbolsLSP.js +147 -0
- package/dist/api/core/agent/tools/getTypeInfo.d.ts +70 -0
- package/dist/api/core/agent/tools/getTypeInfo.js +84 -0
- package/dist/api/core/agent/tools/getWorkspaceInfo.d.ts +80 -0
- package/dist/api/core/agent/tools/getWorkspaceInfo.js +281 -0
- package/dist/api/core/agent/tools/getWorkspaceSymbols.d.ts +53 -0
- package/dist/api/core/agent/tools/getWorkspaceSymbols.js +97 -0
- package/dist/api/core/agent/tools/grepSearch.d.ts +100 -0
- package/dist/api/core/agent/tools/grepSearch.js +211 -0
- package/dist/api/core/agent/tools/languageModelToolsIntegration.d.ts +13 -0
- package/dist/api/core/agent/tools/languageModelToolsIntegration.js +22 -0
- package/dist/api/core/agent/tools/listDirectory.d.ts +66 -0
- package/dist/api/core/agent/tools/listDirectory.js +161 -0
- package/dist/api/core/agent/tools/mcpTools.d.ts +48 -0
- package/dist/api/core/agent/tools/mcpTools.js +442 -0
- package/dist/api/core/agent/tools/queryKnowledgeGraph.d.ts +120 -0
- package/dist/api/core/agent/tools/queryKnowledgeGraph.js +306 -0
- package/dist/api/core/agent/tools/readFile.d.ts +92 -0
- package/dist/api/core/agent/tools/readFile.js +337 -0
- package/dist/api/core/agent/tools/renameFile.d.ts +49 -0
- package/dist/api/core/agent/tools/renameFile.js +86 -0
- package/dist/api/core/agent/tools/renameSymbol.d.ts +70 -0
- package/dist/api/core/agent/tools/renameSymbol.js +106 -0
- package/dist/api/core/agent/tools/runTerminalCommand.d.ts +72 -0
- package/dist/api/core/agent/tools/runTerminalCommand.js +69 -0
- package/dist/api/core/agent/tools/searchFiles.d.ts +82 -0
- package/dist/api/core/agent/tools/searchFiles.js +280 -0
- package/dist/api/core/agent/tools/terminalAutoApprove.d.ts +3 -0
- package/dist/api/core/agent/tools/terminalAutoApprove.js +676 -0
- package/dist/api/core/agent/tools/treeTraversal.d.ts +60 -0
- package/dist/api/core/agent/tools/treeTraversal.js +217 -0
- package/dist/api/core/agent/tools/webSearch.d.ts +37 -0
- package/dist/api/core/agent/tools/webSearch.js +80 -0
- package/dist/api/core/agent/tools/writeFile.d.ts +59 -0
- package/dist/api/core/agent/tools/writeFile.js +97 -0
- package/dist/api/core/agent/utils/capturedWorkspaceMutationBridge.d.ts +12 -0
- package/dist/api/core/agent/utils/capturedWorkspaceMutationBridge.js +67 -0
- package/dist/api/core/agent/utils/diffUtils.d.ts +14 -0
- package/dist/api/core/agent/utils/diffUtils.js +39 -0
- package/dist/api/core/agent/utils/environmentSnapshot.d.ts +42 -0
- package/dist/api/core/agent/utils/environmentSnapshot.js +213 -0
- package/dist/api/core/agent/utils/errorRecovery.d.ts +49 -0
- package/dist/api/core/agent/utils/errorRecovery.js +229 -0
- package/dist/api/core/agent/utils/multimodalTokenLimiter.d.ts +8 -0
- package/dist/api/core/agent/utils/multimodalTokenLimiter.js +238 -0
- package/dist/api/core/agent/utils/openRouterModelSettings.d.ts +12 -0
- package/dist/api/core/agent/utils/openRouterModelSettings.js +34 -0
- package/dist/api/core/agent/utils/pathRecovery.d.ts +1 -0
- package/dist/api/core/agent/utils/pathRecovery.js +29 -0
- package/dist/api/core/agent/utils/repoMapIndex.d.ts +3 -0
- package/dist/api/core/agent/utils/repoMapIndex.js +197 -0
- package/dist/api/core/agent/utils/skillsDiscovery.d.ts +10 -0
- package/dist/api/core/agent/utils/skillsDiscovery.js +50 -0
- package/dist/api/core/agent/utils/toolCallBudget.d.ts +9 -0
- package/dist/api/core/agent/utils/toolCallBudget.js +55 -0
- package/dist/api/core/agent/utils/toolLifecycle.d.ts +30 -0
- package/dist/api/core/agent/utils/toolLifecycle.js +361 -0
- package/dist/api/core/agent/utils/toolResultSafetyProcessor.d.ts +12 -0
- package/dist/api/core/agent/utils/toolResultSafetyProcessor.js +57 -0
- package/dist/api/core/agent/utils/workspaceMutationBridge.d.ts +13 -0
- package/dist/api/core/agent/utils/workspaceMutationBridge.js +149 -0
- package/dist/api/core/agent/utils/workspacePathGuard.d.ts +7 -0
- package/dist/api/core/agent/utils/workspacePathGuard.js +79 -0
- package/dist/api/core/containers/chat/toolResultSerialization.d.ts +8 -0
- package/dist/api/core/containers/chat/toolResultSerialization.js +71 -0
- package/dist/api/core/library/BrowserManager.d.ts +11 -0
- package/dist/api/core/library/BrowserManager.js +66 -0
- package/dist/api/core/library/desktopWorkspace.d.ts +1 -0
- package/dist/api/core/library/desktopWorkspace.js +3 -0
- package/dist/api/core/library/extensionManager.d.ts +10 -0
- package/dist/api/core/library/extensionManager.js +27 -0
- package/dist/api/core/library/knowledgeGraph.d.ts +268 -0
- package/dist/api/core/library/knowledgeGraph.js +989 -0
- package/dist/api/core/library/languageModelTools.d.ts +141 -0
- package/dist/api/core/library/languageModelTools.js +207 -0
- package/dist/api/core/library/localRuntime.d.ts +23 -0
- package/dist/api/core/library/localRuntime.js +144 -0
- package/dist/api/core/library/lsp/coreLsp.d.ts +367 -0
- package/dist/api/core/library/lsp/coreLsp.js +2076 -0
- package/dist/api/core/library/lsp/protocol.d.ts +25 -0
- package/dist/api/core/library/lsp/protocol.js +1 -0
- package/dist/api/core/library/lsp/serverManager.d.ts +37 -0
- package/dist/api/core/library/lsp/serverManager.js +427 -0
- package/dist/api/core/library/mcpServerProvider.d.ts +69 -0
- package/dist/api/core/library/mcpServerProvider.js +152 -0
- package/dist/api/core/library/mcpSettings.d.ts +20 -0
- package/dist/api/core/library/mcpSettings.js +131 -0
- package/dist/api/core/library/patternMatcher.d.ts +66 -0
- package/dist/api/core/library/patternMatcher.js +284 -0
- package/dist/api/core/library/regexEscape.d.ts +4 -0
- package/dist/api/core/library/regexEscape.js +4 -0
- package/dist/api/core/library/runtimeEventBus.d.ts +27 -0
- package/dist/api/core/library/runtimeEventBus.js +78 -0
- package/dist/api/core/library/safetyMiddleware.d.ts +98 -0
- package/dist/api/core/library/safetyMiddleware.js +215 -0
- package/dist/api/core/library/tauri.d.ts +71 -0
- package/dist/api/core/library/tauri.js +222 -0
- package/dist/api/core/library/tauriImport.d.ts +5 -0
- package/dist/api/core/library/tauriImport.js +7 -0
- package/dist/api/core/library/terminalAutoApproveSettings.d.ts +32 -0
- package/dist/api/core/library/terminalAutoApproveSettings.js +529 -0
- package/dist/api/core/library/workspaceIdentity.d.ts +10 -0
- package/dist/api/core/library/workspaceIdentity.js +49 -0
- package/dist/api/core/library/workspaceSummary.d.ts +19 -0
- package/dist/api/core/library/workspaceSummary.js +35 -0
- package/dist/api/core/skills/agent-customization/SKILL.md +27 -0
- package/dist/api/core/skills/bug-fix/SKILL.md +21 -0
- package/dist/api/core/skills/create-pr/SKILL.md +32 -0
- package/dist/api/core/skills/dev-server/SKILL.md +20 -0
- package/dist/api/core/skills/fix-suggestions/SKILL.md +21 -0
- package/dist/api/core/skills/github-search/SKILL.md +21 -0
- package/dist/api/core/skills/github-summary/SKILL.md +25 -0
- package/dist/api/core/skills/integration-tests/SKILL.md +29 -0
- package/dist/api/core/skills/pr-comments/SKILL.md +23 -0
- package/dist/api/core/skills/project-setup/SKILL.md +20 -0
- package/dist/api/core/skills/search-integration/SKILL.md +19 -0
- package/dist/api/core/skills/search-results/SKILL.md +19 -0
- package/dist/api/core/skills/typescript-upgrade/SKILL.md +21 -0
- package/dist/api/data/runStore.d.ts +46 -0
- package/dist/api/data/runStore.js +244 -0
- package/dist/api/helpers/agentUtils.d.ts +11 -0
- package/dist/api/helpers/agentUtils.js +23 -0
- package/dist/api/helpers/modelTokenLimits.d.ts +2 -0
- package/dist/api/helpers/modelTokenLimits.js +158 -0
- package/dist/api/helpers/observationalMemory.d.ts +10 -0
- package/dist/api/helpers/observationalMemory.js +36 -0
- package/dist/api/helpers/promptBudget.d.ts +30 -0
- package/dist/api/helpers/promptBudget.js +216 -0
- package/dist/api/helpers/resolveImageMessageParts.d.ts +9 -0
- package/dist/api/helpers/resolveImageMessageParts.js +140 -0
- package/dist/api/helpers/slashCommands.d.ts +31 -0
- package/dist/api/helpers/slashCommands.js +129 -0
- package/dist/api/helpers/tokenUsage.d.ts +19 -0
- package/dist/api/helpers/tokenUsage.js +81 -0
- package/dist/api/routes/fileRoutes.d.ts +2 -0
- package/dist/api/routes/fileRoutes.js +108 -0
- package/dist/api/routes/lspDocumentRoutes.d.ts +4 -0
- package/dist/api/routes/lspDocumentRoutes.js +84 -0
- package/dist/api/routes/lspHierarchyRoutes.d.ts +4 -0
- package/dist/api/routes/lspHierarchyRoutes.js +122 -0
- package/dist/api/routes/lspPositionRoutes.d.ts +4 -0
- package/dist/api/routes/lspPositionRoutes.js +263 -0
- package/dist/api/routes/lspQueryRoutes.d.ts +4 -0
- package/dist/api/routes/lspQueryRoutes.js +679 -0
- package/dist/api/routes/lspResolveRoutes.d.ts +4 -0
- package/dist/api/routes/lspResolveRoutes.js +143 -0
- package/dist/api/routes/lspSemanticDocumentRoutes.d.ts +4 -0
- package/dist/api/routes/lspSemanticDocumentRoutes.js +98 -0
- package/dist/api/routes/semanticRoutes.d.ts +2 -0
- package/dist/api/routes/semanticRoutes.js +126 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +160 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +38 -0
- package/package.json +100 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
const REGISTERED_SLASH_COMMANDS = [
|
|
2
|
+
{
|
|
3
|
+
name: "help",
|
|
4
|
+
description: "Show available slash commands.",
|
|
5
|
+
executeImmediately: true,
|
|
6
|
+
kind: "builtin",
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
name: "compact",
|
|
10
|
+
description: "Summarize recent conversation context into a compact digest.",
|
|
11
|
+
executeImmediately: true,
|
|
12
|
+
kind: "builtin",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: "run",
|
|
16
|
+
description: "Run a shell command. Example: /run npm test",
|
|
17
|
+
executeImmediately: false,
|
|
18
|
+
kind: "shell",
|
|
19
|
+
aliases: ["sh", "shell"],
|
|
20
|
+
},
|
|
21
|
+
];
|
|
22
|
+
const getRegisteredSlashCommand = (name) => REGISTERED_SLASH_COMMANDS.find((command) => command.name === name ||
|
|
23
|
+
Boolean(command.aliases?.includes(name)));
|
|
24
|
+
export const getSlashCommandDescriptors = () => REGISTERED_SLASH_COMMANDS.map((command) => ({
|
|
25
|
+
name: command.name,
|
|
26
|
+
description: command.description,
|
|
27
|
+
executeImmediately: command.executeImmediately,
|
|
28
|
+
kind: command.kind,
|
|
29
|
+
aliases: command.aliases || [],
|
|
30
|
+
}));
|
|
31
|
+
export const parseSlashCommandRequest = (input) => {
|
|
32
|
+
if (!input)
|
|
33
|
+
return null;
|
|
34
|
+
const trimmed = input.trim();
|
|
35
|
+
if (!trimmed.startsWith("/"))
|
|
36
|
+
return null;
|
|
37
|
+
if (trimmed.startsWith("//"))
|
|
38
|
+
return null;
|
|
39
|
+
const rawCommand = trimmed.slice(1).trim();
|
|
40
|
+
if (!rawCommand)
|
|
41
|
+
return null;
|
|
42
|
+
const [commandName, ...argParts] = rawCommand.split(/\s+/);
|
|
43
|
+
const name = (commandName || "").trim().toLowerCase();
|
|
44
|
+
const args = argParts.join(" ").trim();
|
|
45
|
+
if (!name)
|
|
46
|
+
return null;
|
|
47
|
+
const resolvedCommand = getRegisteredSlashCommand(name);
|
|
48
|
+
const shellCommand = resolvedCommand?.kind === "shell" ? args : rawCommand;
|
|
49
|
+
return {
|
|
50
|
+
name,
|
|
51
|
+
shellCommand,
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
export const isSlashCommandsFeatureEnabled = () => {
|
|
55
|
+
const raw = process.env.IRIS_ENABLE_SLASH_COMMANDS;
|
|
56
|
+
return raw === undefined
|
|
57
|
+
? true
|
|
58
|
+
: !["0", "false", "off", "no"].includes(raw.trim().toLowerCase());
|
|
59
|
+
};
|
|
60
|
+
const formatSlashCommandsHelp = () => {
|
|
61
|
+
const lines = ["Available slash commands:"];
|
|
62
|
+
for (const command of REGISTERED_SLASH_COMMANDS) {
|
|
63
|
+
const aliasSuffix = command.aliases && command.aliases.length > 0
|
|
64
|
+
? ` (aliases: ${command.aliases.map((alias) => `/${alias}`).join(", ")})`
|
|
65
|
+
: "";
|
|
66
|
+
lines.push(`- /${command.name}${aliasSuffix}: ${command.description}`);
|
|
67
|
+
}
|
|
68
|
+
return lines.join("\n");
|
|
69
|
+
};
|
|
70
|
+
const buildCompactDigest = (conversationHistory, contextSummary) => {
|
|
71
|
+
const recentMessages = (conversationHistory || [])
|
|
72
|
+
.filter((message) => message?.content)
|
|
73
|
+
.slice(-8);
|
|
74
|
+
const recentUserMessages = recentMessages
|
|
75
|
+
.filter((message) => message.role === "user")
|
|
76
|
+
.slice(-3)
|
|
77
|
+
.map((message) => message.content.replace(/\s+/g, " ").trim())
|
|
78
|
+
.filter(Boolean)
|
|
79
|
+
.map((text) => (text.length > 140 ? `${text.slice(0, 137)}...` : text));
|
|
80
|
+
const contextItems = (contextSummary || [])
|
|
81
|
+
.slice(-5)
|
|
82
|
+
.map((item) => item.preview.trim())
|
|
83
|
+
.filter(Boolean)
|
|
84
|
+
.map((text) => (text.length > 120 ? `${text.slice(0, 117)}...` : text));
|
|
85
|
+
const lines = [
|
|
86
|
+
"Conversation compact digest:",
|
|
87
|
+
`- Recent messages: ${recentMessages.length}`,
|
|
88
|
+
`- Context snippets: ${contextItems.length}`,
|
|
89
|
+
];
|
|
90
|
+
if (recentUserMessages.length > 0) {
|
|
91
|
+
lines.push("- Recent user intents:");
|
|
92
|
+
for (const message of recentUserMessages) {
|
|
93
|
+
lines.push(` - ${message}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (contextItems.length > 0) {
|
|
97
|
+
lines.push("- Context summary excerpts:");
|
|
98
|
+
for (const item of contextItems) {
|
|
99
|
+
lines.push(` - ${item}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (recentUserMessages.length === 0 && contextItems.length === 0) {
|
|
103
|
+
lines.push("- No prior conversation context is available yet.");
|
|
104
|
+
}
|
|
105
|
+
return lines.join("\n");
|
|
106
|
+
};
|
|
107
|
+
export const executeRegisteredSlashCommand = ({ parsedCommand, conversationHistory, contextSummary, }) => {
|
|
108
|
+
const command = getRegisteredSlashCommand(parsedCommand.name);
|
|
109
|
+
if (!command || command.kind !== "builtin") {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
if (command.name === "help") {
|
|
113
|
+
return {
|
|
114
|
+
success: true,
|
|
115
|
+
response: formatSlashCommandsHelp(),
|
|
116
|
+
toolCalls: [],
|
|
117
|
+
executedToolResults: [],
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
if (command.name === "compact") {
|
|
121
|
+
return {
|
|
122
|
+
success: true,
|
|
123
|
+
response: buildCompactDigest(conversationHistory, contextSummary),
|
|
124
|
+
toolCalls: [],
|
|
125
|
+
executedToolResults: [],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type TokenUsageSummary = {
|
|
2
|
+
inputTokens?: number;
|
|
3
|
+
outputTokens?: number;
|
|
4
|
+
totalTokens?: number;
|
|
5
|
+
};
|
|
6
|
+
export declare const normalizeTokenUsage: (raw: unknown) => TokenUsageSummary | undefined;
|
|
7
|
+
export declare const mergeTokenUsage: (base: TokenUsageSummary | undefined, incoming: TokenUsageSummary | undefined) => TokenUsageSummary | undefined;
|
|
8
|
+
export declare const extractTokenUsageFromChunkPayload: (payload: unknown) => TokenUsageSummary | undefined;
|
|
9
|
+
export declare const isTokenUsageSourceDebugEnabled: () => boolean;
|
|
10
|
+
export declare const logTokenUsageSource: ({ mode, source, usage, modelId, }: {
|
|
11
|
+
mode: "stream" | "non_stream";
|
|
12
|
+
source: "final" | "stream" | "none";
|
|
13
|
+
usage: TokenUsageSummary | undefined;
|
|
14
|
+
modelId: string;
|
|
15
|
+
}) => void;
|
|
16
|
+
export declare const buildTokenUsageDebug: ({ mode, source, }: {
|
|
17
|
+
mode: "stream" | "non_stream";
|
|
18
|
+
source: "final" | "stream" | "none";
|
|
19
|
+
}) => Record<string, unknown> | undefined;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const asFiniteNonNegativeInt = (value) => {
|
|
2
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
3
|
+
return undefined;
|
|
4
|
+
}
|
|
5
|
+
if (value < 0) {
|
|
6
|
+
return undefined;
|
|
7
|
+
}
|
|
8
|
+
return Math.round(value);
|
|
9
|
+
};
|
|
10
|
+
export const normalizeTokenUsage = (raw) => {
|
|
11
|
+
if (!raw || typeof raw !== "object") {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
const record = raw;
|
|
15
|
+
const inputTokens = asFiniteNonNegativeInt(record.inputTokens) ??
|
|
16
|
+
asFiniteNonNegativeInt(record.promptTokens) ??
|
|
17
|
+
asFiniteNonNegativeInt(record.prompt_tokens);
|
|
18
|
+
const outputTokens = asFiniteNonNegativeInt(record.outputTokens) ??
|
|
19
|
+
asFiniteNonNegativeInt(record.completionTokens) ??
|
|
20
|
+
asFiniteNonNegativeInt(record.completion_tokens);
|
|
21
|
+
let totalTokens = asFiniteNonNegativeInt(record.totalTokens) ??
|
|
22
|
+
asFiniteNonNegativeInt(record.total_tokens);
|
|
23
|
+
if (totalTokens === undefined && inputTokens !== undefined && outputTokens !== undefined) {
|
|
24
|
+
totalTokens = inputTokens + outputTokens;
|
|
25
|
+
}
|
|
26
|
+
if (inputTokens === undefined && outputTokens === undefined && totalTokens === undefined) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
...(inputTokens !== undefined ? { inputTokens } : {}),
|
|
31
|
+
...(outputTokens !== undefined ? { outputTokens } : {}),
|
|
32
|
+
...(totalTokens !== undefined ? { totalTokens } : {}),
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
export const mergeTokenUsage = (base, incoming) => {
|
|
36
|
+
if (!base && !incoming) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
...(base || {}),
|
|
41
|
+
...(incoming || {}),
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
export const extractTokenUsageFromChunkPayload = (payload) => {
|
|
45
|
+
if (!payload || typeof payload !== "object") {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
const payloadRecord = payload;
|
|
49
|
+
const nested = normalizeTokenUsage(payloadRecord.usage);
|
|
50
|
+
if (nested) {
|
|
51
|
+
return nested;
|
|
52
|
+
}
|
|
53
|
+
return normalizeTokenUsage(payload);
|
|
54
|
+
};
|
|
55
|
+
export const isTokenUsageSourceDebugEnabled = () => {
|
|
56
|
+
const raw = process.env.IRIS_DEBUG_TOKEN_USAGE_SOURCE;
|
|
57
|
+
if (!raw) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return ["1", "true", "yes", "on"].includes(raw.trim().toLowerCase());
|
|
61
|
+
};
|
|
62
|
+
export const logTokenUsageSource = ({ mode, source, usage, modelId, }) => {
|
|
63
|
+
if (!isTokenUsageSourceDebugEnabled()) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
console.log("📊 Token usage source", {
|
|
67
|
+
mode,
|
|
68
|
+
source,
|
|
69
|
+
modelId,
|
|
70
|
+
usage,
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
export const buildTokenUsageDebug = ({ mode, source, }) => {
|
|
74
|
+
if (!isTokenUsageSourceDebugEnabled()) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
mode,
|
|
79
|
+
source,
|
|
80
|
+
};
|
|
81
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
export const registerFileRoutes = (router, requireDesktopAuth) => {
|
|
4
|
+
// POST /api/agent/write-file - Write file content (for revert/undo operations)
|
|
5
|
+
router.post("/write-file", requireDesktopAuth, async (req, res) => {
|
|
6
|
+
try {
|
|
7
|
+
const { filePath, content, workspaceRoot } = req.body;
|
|
8
|
+
if (!filePath) {
|
|
9
|
+
return res.status(400).json({ error: "File path is required" });
|
|
10
|
+
}
|
|
11
|
+
if (content === undefined || content === null) {
|
|
12
|
+
return res.status(400).json({ error: "Content is required" });
|
|
13
|
+
}
|
|
14
|
+
const workspacePath = workspaceRoot || process.cwd();
|
|
15
|
+
// Resolve path and guard against traversal outside workspace
|
|
16
|
+
const absolutePath = path.isAbsolute(filePath)
|
|
17
|
+
? filePath
|
|
18
|
+
: path.join(workspacePath, filePath);
|
|
19
|
+
const resolvedWorkspace = path.resolve(workspacePath);
|
|
20
|
+
const resolvedTarget = path.resolve(absolutePath);
|
|
21
|
+
if (!resolvedTarget.startsWith(resolvedWorkspace + path.sep) &&
|
|
22
|
+
resolvedTarget !== resolvedWorkspace) {
|
|
23
|
+
return res.status(400).json({ error: "Path outside workspace" });
|
|
24
|
+
}
|
|
25
|
+
await fs.writeFile(resolvedTarget, content, "utf8");
|
|
26
|
+
return res.json({ success: true, path: filePath });
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
const err = error;
|
|
30
|
+
console.error("Write file error:", error);
|
|
31
|
+
return res
|
|
32
|
+
.status(500)
|
|
33
|
+
.json({ success: false, error: err.message || "Failed to write file" });
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
// POST /api/agent/delete-file - Delete file (for undoing newly created files)
|
|
37
|
+
router.post("/delete-file", requireDesktopAuth, async (req, res) => {
|
|
38
|
+
try {
|
|
39
|
+
const { filePath, workspaceRoot } = req.body;
|
|
40
|
+
if (!filePath) {
|
|
41
|
+
return res.status(400).json({ error: "File path is required" });
|
|
42
|
+
}
|
|
43
|
+
const workspacePath = workspaceRoot || process.cwd();
|
|
44
|
+
// Resolve path and guard against traversal outside workspace
|
|
45
|
+
const absolutePath = path.isAbsolute(filePath)
|
|
46
|
+
? filePath
|
|
47
|
+
: path.join(workspacePath, filePath);
|
|
48
|
+
const resolvedWorkspace = path.resolve(workspacePath);
|
|
49
|
+
const resolvedTarget = path.resolve(absolutePath);
|
|
50
|
+
if (!resolvedTarget.startsWith(resolvedWorkspace + path.sep) &&
|
|
51
|
+
resolvedTarget !== resolvedWorkspace) {
|
|
52
|
+
return res.status(400).json({ error: "Path outside workspace" });
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
await fs.unlink(resolvedTarget);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
const err = error;
|
|
59
|
+
// If already missing, treat as success for idempotent undo behavior.
|
|
60
|
+
if (err.code !== "ENOENT") {
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return res.json({ success: true, path: filePath });
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
const err = error;
|
|
68
|
+
console.error("Delete file error:", error);
|
|
69
|
+
return res
|
|
70
|
+
.status(500)
|
|
71
|
+
.json({ success: false, error: err.message || "Failed to delete file" });
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
// POST /api/agent/read-file - Read file content (for web workspace)
|
|
75
|
+
router.post("/read-file", async (req, res) => {
|
|
76
|
+
try {
|
|
77
|
+
const { filePath, workspaceRoot } = req.body;
|
|
78
|
+
if (!filePath) {
|
|
79
|
+
return res.status(400).json({ error: "File path is required" });
|
|
80
|
+
}
|
|
81
|
+
const workspacePath = workspaceRoot || process.cwd();
|
|
82
|
+
const isWebWorkspace = workspacePath.startsWith("/workspace/");
|
|
83
|
+
if (isWebWorkspace) {
|
|
84
|
+
return res.status(400).json({
|
|
85
|
+
error: "Cannot read files from web workspace. File content must be provided by the client.",
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
// Resolve file path relative to workspace
|
|
89
|
+
const absolutePath = path.isAbsolute(filePath)
|
|
90
|
+
? filePath
|
|
91
|
+
: path.join(workspacePath, filePath);
|
|
92
|
+
const content = await fs.readFile(absolutePath, "utf8");
|
|
93
|
+
return res.json({
|
|
94
|
+
success: true,
|
|
95
|
+
content,
|
|
96
|
+
path: filePath,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
const err = error;
|
|
101
|
+
console.error("Read file error:", error);
|
|
102
|
+
return res.status(500).json({
|
|
103
|
+
success: false,
|
|
104
|
+
error: err.message || "Failed to read file",
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { NextFunction, Request, Response, Router } from "express";
|
|
2
|
+
type AuthMiddleware = (req: Request, res: Response, next: NextFunction) => void;
|
|
3
|
+
export declare const registerLspDocumentRoutes: (router: Router, requireDesktopAuth: AuthMiddleware) => void;
|
|
4
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { coreLsp } from "../core/library/lsp/coreLsp.js";
|
|
3
|
+
const resolveAbsolutePath = (workspacePath, filePath) => path.isAbsolute(filePath) ? filePath : path.join(workspacePath, filePath);
|
|
4
|
+
export const registerLspDocumentRoutes = (router, requireDesktopAuth) => {
|
|
5
|
+
router.post("/lsp/document/open", requireDesktopAuth, async (req, res) => {
|
|
6
|
+
try {
|
|
7
|
+
const { filePath, workspaceRoot, text } = req.body;
|
|
8
|
+
if (typeof filePath !== "string" || filePath.trim().length === 0) {
|
|
9
|
+
return res.status(400).json({ success: false, error: "filePath is required" });
|
|
10
|
+
}
|
|
11
|
+
const workspacePath = workspaceRoot || process.cwd();
|
|
12
|
+
const absolutePath = resolveAbsolutePath(workspacePath, filePath);
|
|
13
|
+
coreLsp.setRootPath(workspacePath);
|
|
14
|
+
const opened = await coreLsp.openDocument(absolutePath, typeof text === "string" ? text : undefined);
|
|
15
|
+
return res.json({ success: opened });
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
const err = error;
|
|
19
|
+
return res
|
|
20
|
+
.status(500)
|
|
21
|
+
.json({ success: false, error: err.message || "Failed to open LSP document" });
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
router.post("/lsp/document/change", requireDesktopAuth, async (req, res) => {
|
|
25
|
+
try {
|
|
26
|
+
const { filePath, workspaceRoot, text } = req.body;
|
|
27
|
+
if (typeof filePath !== "string" || filePath.trim().length === 0) {
|
|
28
|
+
return res.status(400).json({ success: false, error: "filePath is required" });
|
|
29
|
+
}
|
|
30
|
+
if (typeof text !== "string") {
|
|
31
|
+
return res.status(400).json({ success: false, error: "text is required" });
|
|
32
|
+
}
|
|
33
|
+
const workspacePath = workspaceRoot || process.cwd();
|
|
34
|
+
const absolutePath = resolveAbsolutePath(workspacePath, filePath);
|
|
35
|
+
coreLsp.setRootPath(workspacePath);
|
|
36
|
+
const changed = await coreLsp.changeDocument(absolutePath, text);
|
|
37
|
+
return res.json({ success: changed });
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
const err = error;
|
|
41
|
+
return res
|
|
42
|
+
.status(500)
|
|
43
|
+
.json({ success: false, error: err.message || "Failed to change LSP document" });
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
router.post("/lsp/document/save", requireDesktopAuth, async (req, res) => {
|
|
47
|
+
try {
|
|
48
|
+
const { filePath, workspaceRoot } = req.body;
|
|
49
|
+
if (typeof filePath !== "string" || filePath.trim().length === 0) {
|
|
50
|
+
return res.status(400).json({ success: false, error: "filePath is required" });
|
|
51
|
+
}
|
|
52
|
+
const workspacePath = workspaceRoot || process.cwd();
|
|
53
|
+
const absolutePath = resolveAbsolutePath(workspacePath, filePath);
|
|
54
|
+
coreLsp.setRootPath(workspacePath);
|
|
55
|
+
const saved = await coreLsp.saveDocument(absolutePath);
|
|
56
|
+
return res.json({ success: saved });
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
const err = error;
|
|
60
|
+
return res
|
|
61
|
+
.status(500)
|
|
62
|
+
.json({ success: false, error: err.message || "Failed to save LSP document" });
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
router.post("/lsp/document/close", requireDesktopAuth, async (req, res) => {
|
|
66
|
+
try {
|
|
67
|
+
const { filePath, workspaceRoot } = req.body;
|
|
68
|
+
if (typeof filePath !== "string" || filePath.trim().length === 0) {
|
|
69
|
+
return res.status(400).json({ success: false, error: "filePath is required" });
|
|
70
|
+
}
|
|
71
|
+
const workspacePath = workspaceRoot || process.cwd();
|
|
72
|
+
const absolutePath = resolveAbsolutePath(workspacePath, filePath);
|
|
73
|
+
coreLsp.setRootPath(workspacePath);
|
|
74
|
+
const closed = await coreLsp.closeDocument(absolutePath);
|
|
75
|
+
return res.json({ success: closed });
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
const err = error;
|
|
79
|
+
return res
|
|
80
|
+
.status(500)
|
|
81
|
+
.json({ success: false, error: err.message || "Failed to close LSP document" });
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { NextFunction, Request, Response, Router } from "express";
|
|
2
|
+
type AuthMiddleware = (req: Request, res: Response, next: NextFunction) => void;
|
|
3
|
+
export declare const registerLspHierarchyRoutes: (router: Router, requireDesktopAuth: AuthMiddleware) => void;
|
|
4
|
+
export {};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { coreLsp } from "../core/library/lsp/coreLsp.js";
|
|
3
|
+
const resolveAbsolutePath = (workspacePath, filePath) => path.isAbsolute(filePath) ? filePath : path.join(workspacePath, filePath);
|
|
4
|
+
export const registerLspHierarchyRoutes = (router, requireDesktopAuth) => {
|
|
5
|
+
router.post("/lsp/call-hierarchy/prepare", requireDesktopAuth, async (req, res) => {
|
|
6
|
+
try {
|
|
7
|
+
const { filePath, workspaceRoot, line, character } = req.body;
|
|
8
|
+
if (typeof filePath !== "string" || filePath.trim().length === 0) {
|
|
9
|
+
return res.status(400).json({ success: false, error: "filePath is required" });
|
|
10
|
+
}
|
|
11
|
+
if (typeof line !== "number" || typeof character !== "number") {
|
|
12
|
+
return res.status(400).json({ success: false, error: "line and character are required" });
|
|
13
|
+
}
|
|
14
|
+
const workspacePath = workspaceRoot || process.cwd();
|
|
15
|
+
const absolutePath = resolveAbsolutePath(workspacePath, filePath);
|
|
16
|
+
coreLsp.setRootPath(workspacePath);
|
|
17
|
+
const items = await coreLsp.prepareCallHierarchy(absolutePath, line, character);
|
|
18
|
+
return res.json({ success: true, items: Array.isArray(items) ? items : [] });
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
const err = error;
|
|
22
|
+
return res
|
|
23
|
+
.status(500)
|
|
24
|
+
.json({ success: false, error: err.message || "Failed to prepare call hierarchy" });
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
router.post("/lsp/call-hierarchy/incoming", requireDesktopAuth, async (req, res) => {
|
|
28
|
+
try {
|
|
29
|
+
const { workspaceRoot, item } = req.body;
|
|
30
|
+
if (!item || typeof item !== "object") {
|
|
31
|
+
return res.status(400).json({ success: false, error: "item is required" });
|
|
32
|
+
}
|
|
33
|
+
const workspacePath = workspaceRoot || process.cwd();
|
|
34
|
+
coreLsp.setRootPath(workspacePath);
|
|
35
|
+
const calls = await coreLsp.getIncomingCalls(item);
|
|
36
|
+
return res.json({ success: true, calls: Array.isArray(calls) ? calls : [] });
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
const err = error;
|
|
40
|
+
return res
|
|
41
|
+
.status(500)
|
|
42
|
+
.json({ success: false, error: err.message || "Failed to get incoming calls" });
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
router.post("/lsp/call-hierarchy/outgoing", requireDesktopAuth, async (req, res) => {
|
|
46
|
+
try {
|
|
47
|
+
const { workspaceRoot, item } = req.body;
|
|
48
|
+
if (!item || typeof item !== "object") {
|
|
49
|
+
return res.status(400).json({ success: false, error: "item is required" });
|
|
50
|
+
}
|
|
51
|
+
const workspacePath = workspaceRoot || process.cwd();
|
|
52
|
+
coreLsp.setRootPath(workspacePath);
|
|
53
|
+
const calls = await coreLsp.getOutgoingCalls(item);
|
|
54
|
+
return res.json({ success: true, calls: Array.isArray(calls) ? calls : [] });
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
const err = error;
|
|
58
|
+
return res
|
|
59
|
+
.status(500)
|
|
60
|
+
.json({ success: false, error: err.message || "Failed to get outgoing calls" });
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
router.post("/lsp/type-hierarchy/prepare", requireDesktopAuth, async (req, res) => {
|
|
64
|
+
try {
|
|
65
|
+
const { filePath, workspaceRoot, line, character } = req.body;
|
|
66
|
+
if (typeof filePath !== "string" || filePath.trim().length === 0) {
|
|
67
|
+
return res.status(400).json({ success: false, error: "filePath is required" });
|
|
68
|
+
}
|
|
69
|
+
if (typeof line !== "number" || typeof character !== "number") {
|
|
70
|
+
return res.status(400).json({ success: false, error: "line and character are required" });
|
|
71
|
+
}
|
|
72
|
+
const workspacePath = workspaceRoot || process.cwd();
|
|
73
|
+
const absolutePath = resolveAbsolutePath(workspacePath, filePath);
|
|
74
|
+
coreLsp.setRootPath(workspacePath);
|
|
75
|
+
const items = await coreLsp.prepareTypeHierarchy(absolutePath, line, character);
|
|
76
|
+
return res.json({ success: true, items: Array.isArray(items) ? items : [] });
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
const err = error;
|
|
80
|
+
return res
|
|
81
|
+
.status(500)
|
|
82
|
+
.json({ success: false, error: err.message || "Failed to prepare type hierarchy" });
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
router.post("/lsp/type-hierarchy/supertypes", requireDesktopAuth, async (req, res) => {
|
|
86
|
+
try {
|
|
87
|
+
const { workspaceRoot, item } = req.body;
|
|
88
|
+
if (!item || typeof item !== "object") {
|
|
89
|
+
return res.status(400).json({ success: false, error: "item is required" });
|
|
90
|
+
}
|
|
91
|
+
const workspacePath = workspaceRoot || process.cwd();
|
|
92
|
+
coreLsp.setRootPath(workspacePath);
|
|
93
|
+
const items = await coreLsp.getTypeHierarchySupertypes(item);
|
|
94
|
+
return res.json({ success: true, items: Array.isArray(items) ? items : [] });
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
const err = error;
|
|
98
|
+
return res.status(500).json({
|
|
99
|
+
success: false,
|
|
100
|
+
error: err.message || "Failed to get type hierarchy supertypes",
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
router.post("/lsp/type-hierarchy/subtypes", requireDesktopAuth, async (req, res) => {
|
|
105
|
+
try {
|
|
106
|
+
const { workspaceRoot, item } = req.body;
|
|
107
|
+
if (!item || typeof item !== "object") {
|
|
108
|
+
return res.status(400).json({ success: false, error: "item is required" });
|
|
109
|
+
}
|
|
110
|
+
const workspacePath = workspaceRoot || process.cwd();
|
|
111
|
+
coreLsp.setRootPath(workspacePath);
|
|
112
|
+
const items = await coreLsp.getTypeHierarchySubtypes(item);
|
|
113
|
+
return res.json({ success: true, items: Array.isArray(items) ? items : [] });
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
const err = error;
|
|
117
|
+
return res
|
|
118
|
+
.status(500)
|
|
119
|
+
.json({ success: false, error: err.message || "Failed to get type hierarchy subtypes" });
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { NextFunction, Request, Response, Router } from "express";
|
|
2
|
+
type AuthMiddleware = (req: Request, res: Response, next: NextFunction) => void;
|
|
3
|
+
export declare const registerLspPositionRoutes: (router: Router, requireDesktopAuth: AuthMiddleware) => void;
|
|
4
|
+
export {};
|