@lapis-notes/ai 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/CHANGELOG.md +6 -0
- package/LICENSE.md +8 -0
- package/README.md +20 -0
- package/dist/ai-plugin.d.ts +88 -0
- package/dist/ai-plugin.js +742 -0
- package/dist/bootstrap/build.d.ts +15 -0
- package/dist/bootstrap/build.js +28 -0
- package/dist/bootstrap/folder-instructions.d.ts +4 -0
- package/dist/bootstrap/folder-instructions.js +49 -0
- package/dist/bootstrap/format.d.ts +2 -0
- package/dist/bootstrap/format.js +46 -0
- package/dist/bootstrap/instructions.d.ts +1 -0
- package/dist/bootstrap/instructions.js +8 -0
- package/dist/bootstrap/types.d.ts +36 -0
- package/dist/bootstrap/types.js +3 -0
- package/dist/catalog/ai-catalog-panel.svelte +742 -0
- package/dist/catalog/ai-catalog-panel.svelte.d.ts +13 -0
- package/dist/catalog/ai-catalog-view-type.d.ts +1 -0
- package/dist/catalog/ai-catalog-view-type.js +1 -0
- package/dist/catalog/ai-catalog-view.d.ts +22 -0
- package/dist/catalog/ai-catalog-view.js +48 -0
- package/dist/catalog/filter.d.ts +3 -0
- package/dist/catalog/filter.js +24 -0
- package/dist/catalog/inventory.d.ts +21 -0
- package/dist/catalog/inventory.js +255 -0
- package/dist/catalog/tree.d.ts +12 -0
- package/dist/catalog/tree.js +60 -0
- package/dist/catalog/types.d.ts +44 -0
- package/dist/catalog/types.js +1 -0
- package/dist/chat/ai-approval-card.svelte +54 -0
- package/dist/chat/ai-approval-card.svelte.d.ts +9 -0
- package/dist/chat/ai-chat-panel.svelte +1288 -0
- package/dist/chat/ai-chat-panel.svelte.d.ts +50 -0
- package/dist/chat/ai-inventory-result.css +55 -0
- package/dist/chat/ai-inventory-result.svelte +63 -0
- package/dist/chat/ai-inventory-result.svelte.d.ts +5 -0
- package/dist/chat/ai-question-card.svelte +132 -0
- package/dist/chat/ai-question-card.svelte.d.ts +9 -0
- package/dist/chat/ai-view-bootstrap.d.ts +13 -0
- package/dist/chat/ai-view-bootstrap.js +86 -0
- package/dist/chat/ai-view-panel.svelte +89 -0
- package/dist/chat/ai-view-panel.svelte.d.ts +13 -0
- package/dist/chat/ai-view-type.d.ts +1 -0
- package/dist/chat/ai-view-type.js +1 -0
- package/dist/chat/ai-view.d.ts +60 -0
- package/dist/chat/ai-view.js +121 -0
- package/dist/chat/chat-controller.svelte.d.ts +82 -0
- package/dist/chat/chat-controller.svelte.js +1926 -0
- package/dist/chat/chat-items.d.ts +88 -0
- package/dist/chat/chat-items.js +9 -0
- package/dist/chat/chat-mentions.d.ts +15 -0
- package/dist/chat/chat-mentions.js +77 -0
- package/dist/chat/chat-result-views.d.ts +5 -0
- package/dist/chat/chat-result-views.js +15 -0
- package/dist/chat/chat-session.d.ts +28 -0
- package/dist/chat/chat-session.js +50 -0
- package/dist/chat/chat-status.d.ts +1 -0
- package/dist/chat/chat-status.js +3 -0
- package/dist/chat/chat-time.d.ts +18 -0
- package/dist/chat/chat-time.js +90 -0
- package/dist/chat/chat-tool-display.d.ts +17 -0
- package/dist/chat/chat-tool-display.js +213 -0
- package/dist/chat/chat-tool-identity.d.ts +4 -0
- package/dist/chat/chat-tool-identity.js +42 -0
- package/dist/chat/chat-trace.d.ts +6 -0
- package/dist/chat/chat-trace.js +256 -0
- package/dist/chat/live-runtime-unavailable.d.ts +1 -0
- package/dist/chat/live-runtime-unavailable.js +1 -0
- package/dist/commands/agent.d.ts +8 -0
- package/dist/commands/agent.js +38 -0
- package/dist/commands/catalog.d.ts +18 -0
- package/dist/commands/catalog.js +115 -0
- package/dist/commands/discovery.d.ts +36 -0
- package/dist/commands/discovery.js +151 -0
- package/dist/commands/groups.d.ts +16 -0
- package/dist/commands/groups.js +121 -0
- package/dist/commands/inspect.d.ts +18 -0
- package/dist/commands/inspect.js +29 -0
- package/dist/commands/markdown.d.ts +21 -0
- package/dist/commands/markdown.js +117 -0
- package/dist/commands/parser.d.ts +4 -0
- package/dist/commands/parser.js +24 -0
- package/dist/commands/reserved.d.ts +4 -0
- package/dist/commands/reserved.js +107 -0
- package/dist/commands/router.d.ts +17 -0
- package/dist/commands/router.js +170 -0
- package/dist/commands/seed.d.ts +4 -0
- package/dist/commands/seed.js +8 -0
- package/dist/commands/types.d.ts +54 -0
- package/dist/commands/types.js +1 -0
- package/dist/commands/user-agents-node.d.ts +3 -0
- package/dist/commands/user-agents-node.js +52 -0
- package/dist/commands/user-agents.d.ts +22 -0
- package/dist/commands/user-agents.js +40 -0
- package/dist/conversations/approval-grants.d.ts +20 -0
- package/dist/conversations/approval-grants.js +75 -0
- package/dist/conversations/binding-state.d.ts +6 -0
- package/dist/conversations/binding-state.js +35 -0
- package/dist/conversations/context-handoff.d.ts +25 -0
- package/dist/conversations/context-handoff.js +207 -0
- package/dist/conversations/conversation-index-coordinator.d.ts +18 -0
- package/dist/conversations/conversation-index-coordinator.js +81 -0
- package/dist/conversations/conversation-index.d.ts +16 -0
- package/dist/conversations/conversation-index.js +125 -0
- package/dist/conversations/conversation-locator.d.ts +2 -0
- package/dist/conversations/conversation-locator.js +20 -0
- package/dist/conversations/conversation-palette.d.ts +18 -0
- package/dist/conversations/conversation-palette.js +29 -0
- package/dist/conversations/conversation-repository.d.ts +39 -0
- package/dist/conversations/conversation-repository.js +213 -0
- package/dist/conversations/handoff-summary.d.ts +48 -0
- package/dist/conversations/handoff-summary.js +269 -0
- package/dist/conversations/hashes.d.ts +5 -0
- package/dist/conversations/hashes.js +40 -0
- package/dist/conversations/history-creation-scope.d.ts +12 -0
- package/dist/conversations/history-creation-scope.js +44 -0
- package/dist/conversations/jsonl.d.ts +10 -0
- package/dist/conversations/jsonl.js +41 -0
- package/dist/conversations/memory-transcript-store.d.ts +15 -0
- package/dist/conversations/memory-transcript-store.js +119 -0
- package/dist/conversations/paths.d.ts +14 -0
- package/dist/conversations/paths.js +92 -0
- package/dist/conversations/redaction.d.ts +13 -0
- package/dist/conversations/redaction.js +94 -0
- package/dist/conversations/reveal-scope.d.ts +3 -0
- package/dist/conversations/reveal-scope.js +8 -0
- package/dist/conversations/scope-resolver.d.ts +11 -0
- package/dist/conversations/scope-resolver.js +18 -0
- package/dist/conversations/scope-tree.d.ts +10 -0
- package/dist/conversations/scope-tree.js +71 -0
- package/dist/conversations/transcript-projection.d.ts +9 -0
- package/dist/conversations/transcript-projection.js +409 -0
- package/dist/conversations/transcript-store.d.ts +24 -0
- package/dist/conversations/transcript-store.js +27 -0
- package/dist/conversations/types.d.ts +224 -0
- package/dist/conversations/types.js +12 -0
- package/dist/conversations/validation.d.ts +4 -0
- package/dist/conversations/validation.js +383 -0
- package/dist/conversations/vault-transcript-store.d.ts +22 -0
- package/dist/conversations/vault-transcript-store.js +219 -0
- package/dist/core/event-queue.d.ts +6 -0
- package/dist/core/event-queue.js +39 -0
- package/dist/core/types.d.ts +241 -0
- package/dist/core/types.js +50 -0
- package/dist/history/ai-history-panel.svelte +937 -0
- package/dist/history/ai-history-panel.svelte.d.ts +17 -0
- package/dist/history/ai-history-view-type.d.ts +1 -0
- package/dist/history/ai-history-view-type.js +1 -0
- package/dist/history/ai-history-view.d.ts +26 -0
- package/dist/history/ai-history-view.js +51 -0
- package/dist/history/conversation-history-tree.d.ts +15 -0
- package/dist/history/conversation-history-tree.js +71 -0
- package/dist/host/agent-workspace.d.ts +6 -0
- package/dist/host/agent-workspace.js +16 -0
- package/dist/host/create-host-runtimes.d.ts +2 -0
- package/dist/host/create-host-runtimes.js +14 -0
- package/dist/host/desktop-process-host.d.ts +6 -0
- package/dist/host/desktop-process-host.js +74 -0
- package/dist/host/host-runtime-availability.d.ts +1 -0
- package/dist/host/host-runtime-availability.js +3 -0
- package/dist/host/process-host.d.ts +31 -0
- package/dist/host/process-host.js +6 -0
- package/dist/index.d.ts +69 -0
- package/dist/index.js +49 -0
- package/dist/jsonl/ai-jsonl-preview.d.ts +27 -0
- package/dist/jsonl/ai-jsonl-preview.js +68 -0
- package/dist/jsonl/ai-jsonl-view-type.d.ts +1 -0
- package/dist/jsonl/ai-jsonl-view-type.js +1 -0
- package/dist/jsonl/ai-jsonl-view.d.ts +16 -0
- package/dist/jsonl/ai-jsonl-view.js +57 -0
- package/dist/jsonl/ai-jsonl-view.svelte +260 -0
- package/dist/jsonl/ai-jsonl-view.svelte.d.ts +9 -0
- package/dist/memory/candidate-extractor.d.ts +25 -0
- package/dist/memory/candidate-extractor.js +191 -0
- package/dist/memory/forget-confirmation.d.ts +3 -0
- package/dist/memory/forget-confirmation.js +45 -0
- package/dist/memory/hashes.d.ts +5 -0
- package/dist/memory/hashes.js +37 -0
- package/dist/memory/memory-ingestion-coordinator.d.ts +19 -0
- package/dist/memory/memory-ingestion-coordinator.js +108 -0
- package/dist/memory/memory-record-store.d.ts +62 -0
- package/dist/memory/memory-record-store.js +369 -0
- package/dist/memory/memory-scheduler.d.ts +21 -0
- package/dist/memory/memory-scheduler.js +143 -0
- package/dist/memory/memory-tools.d.ts +61 -0
- package/dist/memory/memory-tools.js +99 -0
- package/dist/memory/native-memory-service.d.ts +72 -0
- package/dist/memory/native-memory-service.js +1256 -0
- package/dist/memory/paths.d.ts +13 -0
- package/dist/memory/paths.js +60 -0
- package/dist/memory/runtime-consolidation-provider.d.ts +18 -0
- package/dist/memory/runtime-consolidation-provider.js +154 -0
- package/dist/memory/types.d.ts +139 -0
- package/dist/memory/types.js +1 -0
- package/dist/providers/acp-model-provider.d.ts +11 -0
- package/dist/providers/acp-model-provider.js +99 -0
- package/dist/providers/codex-model-catalog.d.ts +7 -0
- package/dist/providers/codex-model-catalog.js +179 -0
- package/dist/providers/codex-model-provider.d.ts +12 -0
- package/dist/providers/codex-model-provider.js +42 -0
- package/dist/providers/model-provider.d.ts +26 -0
- package/dist/providers/model-provider.js +26 -0
- package/dist/public-components.d.ts +3 -0
- package/dist/public-components.js +3 -0
- package/dist/registry/runtime-registry.d.ts +11 -0
- package/dist/registry/runtime-registry.js +71 -0
- package/dist/registry/select-runtime.d.ts +9 -0
- package/dist/registry/select-runtime.js +31 -0
- package/dist/runtime-adapters.d.ts +16 -0
- package/dist/runtime-adapters.js +9 -0
- package/dist/runtimes/acp/acp-event-mapper.d.ts +56 -0
- package/dist/runtimes/acp/acp-event-mapper.js +237 -0
- package/dist/runtimes/acp/acp-runtime.d.ts +46 -0
- package/dist/runtimes/acp/acp-runtime.js +165 -0
- package/dist/runtimes/acp/desktop-acp-backend.d.ts +16 -0
- package/dist/runtimes/acp/desktop-acp-backend.js +243 -0
- package/dist/runtimes/codex/app-server-protocol.d.ts +28 -0
- package/dist/runtimes/codex/app-server-protocol.js +274 -0
- package/dist/runtimes/codex/codex-runtime.d.ts +24 -0
- package/dist/runtimes/codex/codex-runtime.js +318 -0
- package/dist/runtimes/fake/fake-runtime.d.ts +51 -0
- package/dist/runtimes/fake/fake-runtime.js +257 -0
- package/dist/sessions/plugin-data.d.ts +8 -0
- package/dist/sessions/plugin-data.js +29 -0
- package/dist/sessions/session-store.d.ts +47 -0
- package/dist/sessions/session-store.js +117 -0
- package/dist/settings/acp-agents.d.ts +8 -0
- package/dist/settings/acp-agents.js +11 -0
- package/dist/settings/ai-settings-tab.d.ts +8 -0
- package/dist/settings/ai-settings-tab.js +249 -0
- package/dist/settings/ai-settings.d.ts +43 -0
- package/dist/settings/ai-settings.js +147 -0
- package/dist/settings/app-tool-setting-rows.d.ts +16 -0
- package/dist/settings/app-tool-setting-rows.js +30 -0
- package/dist/settings/register-ai-settings.d.ts +3 -0
- package/dist/settings/register-ai-settings.js +393 -0
- package/dist/skills/activation.d.ts +2 -0
- package/dist/skills/activation.js +20 -0
- package/dist/skills/bundled/lapis-notes.d.ts +3 -0
- package/dist/skills/bundled/lapis-notes.js +52 -0
- package/dist/skills/bundled/research.d.ts +6 -0
- package/dist/skills/bundled/research.js +29 -0
- package/dist/skills/discovery.d.ts +21 -0
- package/dist/skills/discovery.js +227 -0
- package/dist/skills/manifest.d.ts +3 -0
- package/dist/skills/manifest.js +39 -0
- package/dist/skills/parser.d.ts +12 -0
- package/dist/skills/parser.js +124 -0
- package/dist/skills/registry.d.ts +21 -0
- package/dist/skills/registry.js +89 -0
- package/dist/skills/seed.d.ts +5 -0
- package/dist/skills/seed.js +18 -0
- package/dist/skills/skill-tools.d.ts +8 -0
- package/dist/skills/skill-tools.js +113 -0
- package/dist/skills/types.d.ts +67 -0
- package/dist/skills/types.js +1 -0
- package/dist/styles.css +514 -0
- package/dist/tools/app-tool-host.d.ts +47 -0
- package/dist/tools/app-tool-host.js +419 -0
- package/dist/tools/desktop-app-tool-bridge.d.ts +23 -0
- package/dist/tools/desktop-app-tool-bridge.js +223 -0
- package/dist/tools/mcp-server-registry.d.ts +7 -0
- package/dist/tools/mcp-server-registry.js +30 -0
- package/manifest.json +34 -0
- package/package.json +83 -0
- package/styles.css +1 -0
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import { createAppToolExecutionScope, } from "@lapis-notes/api/agent-tools";
|
|
2
|
+
import Ajv, {} from "ajv";
|
|
3
|
+
import { isAppToolEnabled, } from "../settings/ai-settings";
|
|
4
|
+
const MAX_CONTENT_ITEMS = 32;
|
|
5
|
+
const MAX_TEXT_BYTES = 64 * 1024;
|
|
6
|
+
const MAX_STRUCTURED_BYTES = 64 * 1024;
|
|
7
|
+
const MAX_IMAGE_DATA_BYTES = 5 * 1024 * 1024;
|
|
8
|
+
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
9
|
+
export class AppToolExecutionError extends Error {
|
|
10
|
+
code;
|
|
11
|
+
constructor(code, message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.code = code;
|
|
14
|
+
this.name = "AppToolExecutionError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export class AppToolApprovalBroker {
|
|
18
|
+
#listeners = new Set();
|
|
19
|
+
#pending = new Map();
|
|
20
|
+
#sequence = 0;
|
|
21
|
+
subscribe(listener) {
|
|
22
|
+
this.#listeners.add(listener);
|
|
23
|
+
return () => this.#listeners.delete(listener);
|
|
24
|
+
}
|
|
25
|
+
request(bindingId, tool, input, details, signal) {
|
|
26
|
+
if (signal.aborted) {
|
|
27
|
+
return Promise.reject(cancelledError());
|
|
28
|
+
}
|
|
29
|
+
const id = `app-tool-approval-${++this.#sequence}`;
|
|
30
|
+
const request = {
|
|
31
|
+
id,
|
|
32
|
+
origin: "app-tool",
|
|
33
|
+
kind: tool.tool.effect === "external" ? "network" : "write",
|
|
34
|
+
title: details?.title ?? `Allow ${tool.tool.name}`,
|
|
35
|
+
tool: { name: tool.tool.name, input },
|
|
36
|
+
options: [
|
|
37
|
+
{ id: "allow-once", label: "Allow once", kind: "allow-once" },
|
|
38
|
+
{
|
|
39
|
+
id: "allow-session",
|
|
40
|
+
label: "Allow for this session",
|
|
41
|
+
kind: "allow-session",
|
|
42
|
+
},
|
|
43
|
+
{ id: "deny-once", label: "Deny", kind: "deny-once" },
|
|
44
|
+
],
|
|
45
|
+
details: {
|
|
46
|
+
description: details?.description,
|
|
47
|
+
path: details?.path ?? details?.paths?.join(", "),
|
|
48
|
+
diff: formatApprovalDiff(details),
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
const decision = new Promise((resolve, reject) => {
|
|
52
|
+
const onAbort = () => {
|
|
53
|
+
const pending = this.#pending.get(id);
|
|
54
|
+
if (!pending)
|
|
55
|
+
return;
|
|
56
|
+
this.#pending.delete(id);
|
|
57
|
+
pending.removeAbortListener();
|
|
58
|
+
reject(cancelledError());
|
|
59
|
+
};
|
|
60
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
61
|
+
this.#pending.set(id, {
|
|
62
|
+
bindingId,
|
|
63
|
+
resolve,
|
|
64
|
+
reject,
|
|
65
|
+
removeAbortListener: () => signal.removeEventListener("abort", onAbort),
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
for (const listener of this.#listeners)
|
|
69
|
+
listener(request, bindingId);
|
|
70
|
+
return decision;
|
|
71
|
+
}
|
|
72
|
+
respond(requestId, decision) {
|
|
73
|
+
const pending = this.#pending.get(requestId);
|
|
74
|
+
if (!pending)
|
|
75
|
+
return false;
|
|
76
|
+
if (!isAppToolApprovalDecision(decision))
|
|
77
|
+
return false;
|
|
78
|
+
this.#pending.delete(requestId);
|
|
79
|
+
pending.removeAbortListener();
|
|
80
|
+
pending.resolve(decision);
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
closeBinding(bindingId) {
|
|
84
|
+
for (const [requestId, pending] of this.#pending) {
|
|
85
|
+
if (pending.bindingId !== bindingId)
|
|
86
|
+
continue;
|
|
87
|
+
this.#pending.delete(requestId);
|
|
88
|
+
pending.removeAbortListener();
|
|
89
|
+
pending.reject(cancelledError());
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
close() {
|
|
93
|
+
for (const pending of this.#pending.values()) {
|
|
94
|
+
pending.removeAbortListener();
|
|
95
|
+
pending.reject(cancelledError());
|
|
96
|
+
}
|
|
97
|
+
this.#pending.clear();
|
|
98
|
+
this.#listeners.clear();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
export class AppToolHost {
|
|
102
|
+
registry;
|
|
103
|
+
settings;
|
|
104
|
+
timeoutMs;
|
|
105
|
+
approvals = new AppToolApprovalBroker();
|
|
106
|
+
#ajv = new Ajv({ allErrors: true, strict: false });
|
|
107
|
+
#sessions = new Map();
|
|
108
|
+
#grants = new Set();
|
|
109
|
+
#validators = new Map();
|
|
110
|
+
#activeInvocations = new Map();
|
|
111
|
+
#registryChangeRef;
|
|
112
|
+
constructor(registry, settings, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
113
|
+
this.registry = registry;
|
|
114
|
+
this.settings = settings;
|
|
115
|
+
this.timeoutMs = timeoutMs;
|
|
116
|
+
this.#registryChangeRef = registry.on("changed", (change) => {
|
|
117
|
+
if (change.reason !== "unregistered")
|
|
118
|
+
return;
|
|
119
|
+
this.expireOwner(change.ownerPluginId);
|
|
120
|
+
this.#validators.delete(change.registrationId);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
createSession(input) {
|
|
124
|
+
if (this.#sessions.has(input.agentBindingId)) {
|
|
125
|
+
throw new Error(`App tool binding already exists: ${input.agentBindingId}`);
|
|
126
|
+
}
|
|
127
|
+
const scope = createAppToolExecutionScope(input.scopeDir);
|
|
128
|
+
const settings = this.settings();
|
|
129
|
+
const eligible = settings.appToolsEnabled && input.runtimeSupportsAppTools
|
|
130
|
+
? this.registry
|
|
131
|
+
.list()
|
|
132
|
+
.filter((registered) => isEligible(registered, settings))
|
|
133
|
+
.map(toDescriptor)
|
|
134
|
+
: [];
|
|
135
|
+
const descriptor = Object.freeze({
|
|
136
|
+
conversationId: input.conversationId,
|
|
137
|
+
agentBindingId: input.agentBindingId,
|
|
138
|
+
scopeDir: scope.directory,
|
|
139
|
+
launchNotePath: input.launchNotePath,
|
|
140
|
+
tools: Object.freeze(eligible),
|
|
141
|
+
});
|
|
142
|
+
this.#sessions.set(input.agentBindingId, { descriptor, scope });
|
|
143
|
+
return descriptor;
|
|
144
|
+
}
|
|
145
|
+
getSession(agentBindingId) {
|
|
146
|
+
return this.#sessions.get(agentBindingId)?.descriptor;
|
|
147
|
+
}
|
|
148
|
+
async invoke(agentBindingId, call, signal) {
|
|
149
|
+
const session = this.#sessions.get(agentBindingId);
|
|
150
|
+
if (!session)
|
|
151
|
+
throw unavailableError(call.name);
|
|
152
|
+
const snapshot = session.descriptor.tools.find((descriptor) => descriptor.name === call.name);
|
|
153
|
+
if (!snapshot)
|
|
154
|
+
throw unavailableError(call.name);
|
|
155
|
+
const registered = this.registry.resolve(snapshot.name, snapshot.registrationId);
|
|
156
|
+
if (!registered)
|
|
157
|
+
throw unavailableError(call.name);
|
|
158
|
+
const invocation = createInvocationSignal(signal, this.timeoutMs);
|
|
159
|
+
const activeForBinding = this.#activeInvocations.get(agentBindingId) ?? new Set();
|
|
160
|
+
activeForBinding.add(invocation.cancel);
|
|
161
|
+
this.#activeInvocations.set(agentBindingId, activeForBinding);
|
|
162
|
+
const context = Object.freeze({
|
|
163
|
+
conversationId: session.descriptor.conversationId,
|
|
164
|
+
agentBindingId: session.descriptor.agentBindingId,
|
|
165
|
+
runId: call.runId,
|
|
166
|
+
toolCallId: call.toolCallId,
|
|
167
|
+
scope: session.scope,
|
|
168
|
+
launchNotePath: session.descriptor.launchNotePath,
|
|
169
|
+
signal: invocation.signal,
|
|
170
|
+
});
|
|
171
|
+
try {
|
|
172
|
+
this.validateInput(registered, call.input);
|
|
173
|
+
let grantForSession = false;
|
|
174
|
+
if (registered.tool.effect !== "read" &&
|
|
175
|
+
!this.#grants.has(grantKey(session.descriptor, registered))) {
|
|
176
|
+
const details = registered.tool.describeApproval
|
|
177
|
+
? await raceWithAbort(registered.tool.describeApproval(call.input, context), invocation.signal)
|
|
178
|
+
: undefined;
|
|
179
|
+
const decision = await this.approvals.request(agentBindingId, registered, call.input, details, invocation.signal);
|
|
180
|
+
if (decision === "deny-once") {
|
|
181
|
+
throw new AppToolExecutionError("approval_denied", `Permission denied for app tool: ${call.name}`);
|
|
182
|
+
}
|
|
183
|
+
if (decision === "allow-session") {
|
|
184
|
+
grantForSession = true;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (!this.registry.resolve(snapshot.name, snapshot.registrationId)) {
|
|
188
|
+
throw unavailableError(call.name);
|
|
189
|
+
}
|
|
190
|
+
if (grantForSession) {
|
|
191
|
+
this.#grants.add(grantKey(session.descriptor, registered));
|
|
192
|
+
}
|
|
193
|
+
const result = await raceWithAbort(registered.tool.execute(call.input, context), invocation.signal);
|
|
194
|
+
this.validateResult(registered, result);
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
if (error instanceof AppToolExecutionError) {
|
|
199
|
+
if (error.code === "cancelled" && invocation.timedOut()) {
|
|
200
|
+
throw new AppToolExecutionError("timed_out", `App tool timed out: ${call.name}`);
|
|
201
|
+
}
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
if (invocation.signal.aborted) {
|
|
205
|
+
throw new AppToolExecutionError(invocation.timedOut() ? "timed_out" : "cancelled", invocation.timedOut()
|
|
206
|
+
? `App tool timed out: ${call.name}`
|
|
207
|
+
: `App tool cancelled: ${call.name}`);
|
|
208
|
+
}
|
|
209
|
+
throw new AppToolExecutionError("execution_failed", `App tool execution failed: ${call.name}`);
|
|
210
|
+
}
|
|
211
|
+
finally {
|
|
212
|
+
activeForBinding.delete(invocation.cancel);
|
|
213
|
+
if (activeForBinding.size === 0) {
|
|
214
|
+
this.#activeInvocations.delete(agentBindingId);
|
|
215
|
+
}
|
|
216
|
+
invocation.dispose();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
closeBinding(agentBindingId) {
|
|
220
|
+
this.#sessions.delete(agentBindingId);
|
|
221
|
+
for (const cancel of this.#activeInvocations.get(agentBindingId) ?? []) {
|
|
222
|
+
cancel();
|
|
223
|
+
}
|
|
224
|
+
this.#activeInvocations.delete(agentBindingId);
|
|
225
|
+
this.approvals.closeBinding(agentBindingId);
|
|
226
|
+
for (const key of this.#grants) {
|
|
227
|
+
if (key.includes(`\u0000${agentBindingId}\u0000`))
|
|
228
|
+
this.#grants.delete(key);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
expireOwner(ownerPluginId) {
|
|
232
|
+
for (const key of this.#grants) {
|
|
233
|
+
if (key.includes(`\u0000${ownerPluginId}\u0000`))
|
|
234
|
+
this.#grants.delete(key);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
close() {
|
|
238
|
+
for (const bindingId of this.#sessions.keys())
|
|
239
|
+
this.closeBinding(bindingId);
|
|
240
|
+
this.approvals.close();
|
|
241
|
+
this.registry.offref(this.#registryChangeRef);
|
|
242
|
+
this.#validators.clear();
|
|
243
|
+
}
|
|
244
|
+
validateInput(registered, input) {
|
|
245
|
+
let validate = this.#validators.get(registered.registrationId);
|
|
246
|
+
if (!validate) {
|
|
247
|
+
try {
|
|
248
|
+
validate = this.#ajv.compile(registered.tool.inputSchema);
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
throw new AppToolExecutionError("invalid_tool_schema", `App tool has an invalid input schema: ${registered.tool.name}`);
|
|
252
|
+
}
|
|
253
|
+
this.#validators.set(registered.registrationId, validate);
|
|
254
|
+
}
|
|
255
|
+
if (!validate(input)) {
|
|
256
|
+
throw new AppToolExecutionError("invalid_arguments", `Invalid arguments for app tool: ${registered.tool.name}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
validateResult(registered, result) {
|
|
260
|
+
if (!result || !Array.isArray(result.content)) {
|
|
261
|
+
throw invalidResult(registered.tool.name);
|
|
262
|
+
}
|
|
263
|
+
if (result.content.length > MAX_CONTENT_ITEMS) {
|
|
264
|
+
throw invalidResult(registered.tool.name);
|
|
265
|
+
}
|
|
266
|
+
let textBytes = 0;
|
|
267
|
+
for (const item of result.content) {
|
|
268
|
+
if (item.type === "text") {
|
|
269
|
+
if (typeof item.text !== "string")
|
|
270
|
+
throw invalidResult(registered.tool.name);
|
|
271
|
+
textBytes += byteLength(item.text);
|
|
272
|
+
}
|
|
273
|
+
else if (item.type === "image") {
|
|
274
|
+
if (typeof item.data !== "string" ||
|
|
275
|
+
typeof item.mimeType !== "string" ||
|
|
276
|
+
!item.mimeType.startsWith("image/") ||
|
|
277
|
+
byteLength(item.data) > MAX_IMAGE_DATA_BYTES) {
|
|
278
|
+
throw invalidResult(registered.tool.name);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
throw invalidResult(registered.tool.name);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (textBytes > MAX_TEXT_BYTES)
|
|
286
|
+
throw invalidResult(registered.tool.name);
|
|
287
|
+
if (result.structuredContent !== undefined) {
|
|
288
|
+
let encoded;
|
|
289
|
+
try {
|
|
290
|
+
encoded = JSON.stringify(result.structuredContent);
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
throw invalidResult(registered.tool.name);
|
|
294
|
+
}
|
|
295
|
+
if (byteLength(encoded) > MAX_STRUCTURED_BYTES) {
|
|
296
|
+
throw invalidResult(registered.tool.name);
|
|
297
|
+
}
|
|
298
|
+
if (registered.tool.outputSchema) {
|
|
299
|
+
let validate;
|
|
300
|
+
try {
|
|
301
|
+
validate = this.#ajv.compile(registered.tool.outputSchema);
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
throw new AppToolExecutionError("invalid_tool_schema", `App tool has an invalid output schema: ${registered.tool.name}`);
|
|
305
|
+
}
|
|
306
|
+
if (!validate(result.structuredContent)) {
|
|
307
|
+
throw invalidResult(registered.tool.name);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function isEligible(registered, settings) {
|
|
314
|
+
return isAppToolEnabled({ name: registered.tool.name, owner: registered.owner }, settings);
|
|
315
|
+
}
|
|
316
|
+
function toDescriptor(registered) {
|
|
317
|
+
return Object.freeze({
|
|
318
|
+
registrationId: registered.registrationId,
|
|
319
|
+
ownerPluginId: registered.owner.pluginId,
|
|
320
|
+
name: registered.tool.name,
|
|
321
|
+
description: registered.tool.description,
|
|
322
|
+
inputSchema: cloneJson(registered.tool.inputSchema),
|
|
323
|
+
outputSchema: registered.tool.outputSchema
|
|
324
|
+
? cloneJson(registered.tool.outputSchema)
|
|
325
|
+
: undefined,
|
|
326
|
+
effect: registered.tool.effect,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
function cloneJson(value) {
|
|
330
|
+
return deepFreezeJson(JSON.parse(JSON.stringify(value)));
|
|
331
|
+
}
|
|
332
|
+
function deepFreezeJson(value) {
|
|
333
|
+
if (!value || typeof value !== "object")
|
|
334
|
+
return value;
|
|
335
|
+
for (const nested of Object.values(value))
|
|
336
|
+
deepFreezeJson(nested);
|
|
337
|
+
return Object.freeze(value);
|
|
338
|
+
}
|
|
339
|
+
function grantKey(session, registered) {
|
|
340
|
+
return [
|
|
341
|
+
"",
|
|
342
|
+
session.conversationId,
|
|
343
|
+
session.agentBindingId,
|
|
344
|
+
session.scopeDir,
|
|
345
|
+
registered.owner.pluginId,
|
|
346
|
+
registered.tool.name,
|
|
347
|
+
registered.tool.effect,
|
|
348
|
+
"",
|
|
349
|
+
].join("\u0000");
|
|
350
|
+
}
|
|
351
|
+
function unavailableError(name) {
|
|
352
|
+
return new AppToolExecutionError("tool_unavailable", `App tool is unavailable for this binding: ${name}`);
|
|
353
|
+
}
|
|
354
|
+
function invalidResult(name) {
|
|
355
|
+
return new AppToolExecutionError("invalid_result", `App tool returned an invalid or oversized result: ${name}`);
|
|
356
|
+
}
|
|
357
|
+
function cancelledError() {
|
|
358
|
+
return new AppToolExecutionError("cancelled", "App tool call cancelled.");
|
|
359
|
+
}
|
|
360
|
+
function byteLength(value) {
|
|
361
|
+
return new TextEncoder().encode(value).byteLength;
|
|
362
|
+
}
|
|
363
|
+
function formatApprovalDiff(details) {
|
|
364
|
+
if (!details)
|
|
365
|
+
return undefined;
|
|
366
|
+
const hunks = [
|
|
367
|
+
...(details.diff
|
|
368
|
+
? [{ path: details.path, before: details.diff.before, after: details.diff.after }]
|
|
369
|
+
: []),
|
|
370
|
+
...(details.diffs ?? []),
|
|
371
|
+
];
|
|
372
|
+
if (hunks.length === 0)
|
|
373
|
+
return undefined;
|
|
374
|
+
return hunks
|
|
375
|
+
.map((hunk) => {
|
|
376
|
+
const header = hunk.path ? `# ${hunk.path}\n` : "";
|
|
377
|
+
return `${header}--- before\n${hunk.before}\n+++ after\n${hunk.after}`;
|
|
378
|
+
})
|
|
379
|
+
.join("\n\n");
|
|
380
|
+
}
|
|
381
|
+
function isAppToolApprovalDecision(decision) {
|
|
382
|
+
return ["allow-once", "allow-session", "deny-once"].includes(decision);
|
|
383
|
+
}
|
|
384
|
+
function createInvocationSignal(parent, timeoutMs) {
|
|
385
|
+
const controller = new AbortController();
|
|
386
|
+
let timeoutReached = false;
|
|
387
|
+
const onParentAbort = () => controller.abort(parent?.reason);
|
|
388
|
+
parent?.addEventListener("abort", onParentAbort, { once: true });
|
|
389
|
+
if (parent?.aborted)
|
|
390
|
+
controller.abort(parent.reason);
|
|
391
|
+
const timer = setTimeout(() => {
|
|
392
|
+
timeoutReached = true;
|
|
393
|
+
controller.abort();
|
|
394
|
+
}, timeoutMs);
|
|
395
|
+
return {
|
|
396
|
+
signal: controller.signal,
|
|
397
|
+
timedOut: () => timeoutReached,
|
|
398
|
+
cancel: () => controller.abort(),
|
|
399
|
+
dispose: () => {
|
|
400
|
+
clearTimeout(timer);
|
|
401
|
+
parent?.removeEventListener("abort", onParentAbort);
|
|
402
|
+
},
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
function raceWithAbort(promise, signal) {
|
|
406
|
+
if (signal.aborted)
|
|
407
|
+
return Promise.reject(cancelledError());
|
|
408
|
+
return new Promise((resolve, reject) => {
|
|
409
|
+
const onAbort = () => reject(cancelledError());
|
|
410
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
411
|
+
promise.then((value) => {
|
|
412
|
+
signal.removeEventListener("abort", onAbort);
|
|
413
|
+
resolve(value);
|
|
414
|
+
}, (error) => {
|
|
415
|
+
signal.removeEventListener("abort", onAbort);
|
|
416
|
+
reject(error);
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { AgentEvent, AppToolSessionDescriptor, ApprovalOptionKind } from "../core/types";
|
|
2
|
+
import { AppToolHost, type CreateAppToolSessionInput } from "./app-tool-host";
|
|
3
|
+
export type AppToolBridgeEvent = {
|
|
4
|
+
bindingId: string;
|
|
5
|
+
event: AgentEvent;
|
|
6
|
+
};
|
|
7
|
+
export interface AppToolBridgeCoordinator {
|
|
8
|
+
prepare(input: CreateAppToolSessionInput): Promise<AppToolSessionDescriptor>;
|
|
9
|
+
closeBinding(bindingId: string): Promise<void>;
|
|
10
|
+
respondToApproval(requestId: string, decision: ApprovalOptionKind): boolean;
|
|
11
|
+
subscribe(listener: (event: AppToolBridgeEvent) => void): () => void;
|
|
12
|
+
close(): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
export declare class DesktopAppToolBridge implements AppToolBridgeCoordinator {
|
|
15
|
+
#private;
|
|
16
|
+
private readonly host;
|
|
17
|
+
constructor(host: AppToolHost);
|
|
18
|
+
prepare(input: CreateAppToolSessionInput): Promise<AppToolSessionDescriptor>;
|
|
19
|
+
respondToApproval(requestId: string, decision: ApprovalOptionKind): boolean;
|
|
20
|
+
subscribe(listener: (event: AppToolBridgeEvent) => void): () => void;
|
|
21
|
+
closeBinding(bindingId: string): Promise<void>;
|
|
22
|
+
close(): Promise<void>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { getNativeDesktopBridge, getNativeDesktopCapability, } from "@lapis-notes/api/desktop-native";
|
|
2
|
+
import { AppToolExecutionError, AppToolHost, } from "./app-tool-host";
|
|
3
|
+
const APP_TOOL_MCP_SERVER_NAME = "lapis-tools";
|
|
4
|
+
const SUPPORTED_APP_TOOL_TRANSPORTS = new Set(["stdio-mcp", "http-mcp"]);
|
|
5
|
+
export class DesktopAppToolBridge {
|
|
6
|
+
host;
|
|
7
|
+
#records = new Map();
|
|
8
|
+
#listeners = new Set();
|
|
9
|
+
#unsubscribeApprovals;
|
|
10
|
+
#subscribedBridge = null;
|
|
11
|
+
#unsubscribeCall;
|
|
12
|
+
#unsubscribeCancel;
|
|
13
|
+
constructor(host) {
|
|
14
|
+
this.host = host;
|
|
15
|
+
this.#unsubscribeApprovals = host.approvals.subscribe((request, bindingId) => {
|
|
16
|
+
const record = this.#records.get(bindingId);
|
|
17
|
+
const active = record ? [...record.calls.values()].at(-1) : undefined;
|
|
18
|
+
this.#emit(bindingId, {
|
|
19
|
+
type: "permission.request",
|
|
20
|
+
request,
|
|
21
|
+
...(record && active
|
|
22
|
+
? { source: nextSource(record, active) }
|
|
23
|
+
: {}),
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
async prepare(input) {
|
|
28
|
+
if (this.#records.has(input.agentBindingId)) {
|
|
29
|
+
throw new Error(`App tool bridge already exists: ${input.agentBindingId}`);
|
|
30
|
+
}
|
|
31
|
+
const bridge = getNativeDesktopBridge();
|
|
32
|
+
const hostCapable = supportsAppTools();
|
|
33
|
+
const runtimeCapable = input.runtimeSupportsAppTools;
|
|
34
|
+
const descriptor = this.host.createSession({
|
|
35
|
+
...input,
|
|
36
|
+
runtimeSupportsAppTools: runtimeCapable && hostCapable,
|
|
37
|
+
});
|
|
38
|
+
const record = {
|
|
39
|
+
bindingId: input.agentBindingId,
|
|
40
|
+
calls: new Map(),
|
|
41
|
+
};
|
|
42
|
+
this.#records.set(input.agentBindingId, record);
|
|
43
|
+
if (!runtimeCapable) {
|
|
44
|
+
return withStatus(descriptor, "runtime-unavailable", "The selected agent runtime does not support application tools.");
|
|
45
|
+
}
|
|
46
|
+
if (!hostCapable || !bridge?.onAgentToolCall || !bridge.onAgentToolCancel) {
|
|
47
|
+
return withStatus(descriptor, "host-upgrade-required", "Application tools require an agent host with protocol v3 support.");
|
|
48
|
+
}
|
|
49
|
+
if (descriptor.tools.length === 0) {
|
|
50
|
+
return withStatus(descriptor, "disabled");
|
|
51
|
+
}
|
|
52
|
+
this.#subscribeToBridge(bridge);
|
|
53
|
+
try {
|
|
54
|
+
const opened = await bridge.invoke("desktop_agent_tools_open", {
|
|
55
|
+
bindingId: descriptor.agentBindingId,
|
|
56
|
+
conversationId: descriptor.conversationId,
|
|
57
|
+
descriptors: descriptor.tools.map((tool) => ({
|
|
58
|
+
name: tool.name,
|
|
59
|
+
description: tool.description,
|
|
60
|
+
inputSchema: tool.inputSchema,
|
|
61
|
+
outputSchema: tool.outputSchema,
|
|
62
|
+
effect: tool.effect,
|
|
63
|
+
})),
|
|
64
|
+
});
|
|
65
|
+
record.bridge = bridge;
|
|
66
|
+
record.bridgeId = opened.bridgeId;
|
|
67
|
+
return Object.freeze({
|
|
68
|
+
...descriptor,
|
|
69
|
+
bridgeId: opened.bridgeId,
|
|
70
|
+
status: "available",
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
this.#records.delete(input.agentBindingId);
|
|
75
|
+
this.host.closeBinding(input.agentBindingId);
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
respondToApproval(requestId, decision) {
|
|
80
|
+
return this.host.approvals.respond(requestId, decision);
|
|
81
|
+
}
|
|
82
|
+
subscribe(listener) {
|
|
83
|
+
this.#listeners.add(listener);
|
|
84
|
+
return () => this.#listeners.delete(listener);
|
|
85
|
+
}
|
|
86
|
+
async closeBinding(bindingId) {
|
|
87
|
+
const record = this.#records.get(bindingId);
|
|
88
|
+
this.#records.delete(bindingId);
|
|
89
|
+
for (const call of record?.calls.values() ?? [])
|
|
90
|
+
call.abort.abort();
|
|
91
|
+
record?.calls.clear();
|
|
92
|
+
this.host.closeBinding(bindingId);
|
|
93
|
+
if (record?.bridge && record.bridgeId) {
|
|
94
|
+
await record.bridge
|
|
95
|
+
.invoke("desktop_agent_tools_close", { bridgeId: record.bridgeId })
|
|
96
|
+
.catch(() => undefined);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async close() {
|
|
100
|
+
for (const bindingId of [...this.#records.keys()]) {
|
|
101
|
+
await this.closeBinding(bindingId);
|
|
102
|
+
}
|
|
103
|
+
this.#unsubscribeApprovals();
|
|
104
|
+
this.#unsubscribeCall?.();
|
|
105
|
+
this.#unsubscribeCancel?.();
|
|
106
|
+
this.#listeners.clear();
|
|
107
|
+
this.#subscribedBridge = null;
|
|
108
|
+
}
|
|
109
|
+
#subscribeToBridge(bridge) {
|
|
110
|
+
if (this.#subscribedBridge === bridge)
|
|
111
|
+
return;
|
|
112
|
+
this.#unsubscribeCall?.();
|
|
113
|
+
this.#unsubscribeCancel?.();
|
|
114
|
+
this.#subscribedBridge = bridge;
|
|
115
|
+
this.#unsubscribeCall = bridge.onAgentToolCall?.((call) => {
|
|
116
|
+
void this.#handleCall(call);
|
|
117
|
+
});
|
|
118
|
+
this.#unsubscribeCancel = bridge.onAgentToolCancel?.((cancel) => {
|
|
119
|
+
this.#handleCancel(cancel);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
async #handleCall(call) {
|
|
123
|
+
const record = this.#records.get(call.bindingId);
|
|
124
|
+
if (!record || record.bridgeId !== call.bridgeId || !record.bridge)
|
|
125
|
+
return;
|
|
126
|
+
if (record.calls.has(call.callId))
|
|
127
|
+
return;
|
|
128
|
+
const active = {
|
|
129
|
+
abort: new AbortController(),
|
|
130
|
+
runId: `app-tool-${call.callId}`,
|
|
131
|
+
sequence: 0,
|
|
132
|
+
};
|
|
133
|
+
record.calls.set(call.callId, active);
|
|
134
|
+
this.#emit(call.bindingId, {
|
|
135
|
+
type: "tool.start",
|
|
136
|
+
id: call.callId,
|
|
137
|
+
name: call.name,
|
|
138
|
+
server: APP_TOOL_MCP_SERVER_NAME,
|
|
139
|
+
input: call.input,
|
|
140
|
+
source: nextSource(record, active),
|
|
141
|
+
});
|
|
142
|
+
try {
|
|
143
|
+
const result = await this.host.invoke(call.bindingId, {
|
|
144
|
+
runId: active.runId,
|
|
145
|
+
toolCallId: call.callId,
|
|
146
|
+
name: call.name,
|
|
147
|
+
input: call.input,
|
|
148
|
+
}, active.abort.signal);
|
|
149
|
+
await record.bridge.invoke("desktop_agent_tools_respond", {
|
|
150
|
+
bridgeId: call.bridgeId,
|
|
151
|
+
callId: call.callId,
|
|
152
|
+
result,
|
|
153
|
+
});
|
|
154
|
+
this.#emit(call.bindingId, {
|
|
155
|
+
type: "tool.end",
|
|
156
|
+
id: call.callId,
|
|
157
|
+
name: call.name,
|
|
158
|
+
server: APP_TOOL_MCP_SERVER_NAME,
|
|
159
|
+
output: result,
|
|
160
|
+
source: nextSource(record, active),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
const normalized = normalizeExecutionError(error);
|
|
165
|
+
await record.bridge
|
|
166
|
+
.invoke("desktop_agent_tools_respond", {
|
|
167
|
+
bridgeId: call.bridgeId,
|
|
168
|
+
callId: call.callId,
|
|
169
|
+
error: normalized,
|
|
170
|
+
})
|
|
171
|
+
.catch(() => undefined);
|
|
172
|
+
this.#emit(call.bindingId, {
|
|
173
|
+
type: "tool.end",
|
|
174
|
+
id: call.callId,
|
|
175
|
+
name: call.name,
|
|
176
|
+
server: APP_TOOL_MCP_SERVER_NAME,
|
|
177
|
+
error: normalized,
|
|
178
|
+
source: nextSource(record, active),
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
record.calls.delete(call.callId);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
#handleCancel(cancel) {
|
|
186
|
+
const record = this.#records.get(cancel.bindingId);
|
|
187
|
+
if (!record || record.bridgeId !== cancel.bridgeId)
|
|
188
|
+
return;
|
|
189
|
+
record.calls.get(cancel.callId)?.abort.abort();
|
|
190
|
+
}
|
|
191
|
+
#emit(bindingId, event) {
|
|
192
|
+
for (const listener of this.#listeners)
|
|
193
|
+
listener({ bindingId, event });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function supportsAppTools() {
|
|
197
|
+
const capability = getNativeDesktopCapability("agent-runtime");
|
|
198
|
+
const protocolVersion = Number(capability?.details?.protocolVersion ?? 0);
|
|
199
|
+
const transport = String(capability?.details?.appTools ?? "");
|
|
200
|
+
return (capability?.status === "available" &&
|
|
201
|
+
protocolVersion >= 3 &&
|
|
202
|
+
SUPPORTED_APP_TOOL_TRANSPORTS.has(transport));
|
|
203
|
+
}
|
|
204
|
+
function withStatus(descriptor, status, unavailableReason) {
|
|
205
|
+
return Object.freeze({ ...descriptor, status, unavailableReason });
|
|
206
|
+
}
|
|
207
|
+
function nextSource(record, active) {
|
|
208
|
+
active.sequence += 1;
|
|
209
|
+
return {
|
|
210
|
+
sessionId: record.bridgeId ?? record.bindingId,
|
|
211
|
+
runId: active.runId,
|
|
212
|
+
sequence: active.sequence,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function normalizeExecutionError(error) {
|
|
216
|
+
if (error instanceof AppToolExecutionError) {
|
|
217
|
+
return { code: error.code, message: error.message };
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
code: "execution_failed",
|
|
221
|
+
message: "Application tool execution failed.",
|
|
222
|
+
};
|
|
223
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { McpServerContribution } from "../core/types";
|
|
2
|
+
export declare const APP_TOOL_MCP_SERVER_NAME = "lapis-tools";
|
|
3
|
+
export interface McpServerContributionRegistry {
|
|
4
|
+
register(contribution: McpServerContribution): () => void;
|
|
5
|
+
list(): McpServerContribution[];
|
|
6
|
+
}
|
|
7
|
+
export declare function createMcpServerContributionRegistry(initial?: McpServerContribution[]): McpServerContributionRegistry;
|