@lapis-notes/ai 0.1.1 → 0.1.5
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 +25 -1
- package/LICENSE.md +3 -7
- package/README.md +1 -1
- package/dist/ai-plugin.d.ts +2 -2
- package/dist/ai-plugin.js +28 -11
- package/dist/catalog/ai-catalog-panel.svelte +20 -3
- package/dist/chat/ai-chat-panel.svelte +187 -87
- package/dist/chat/ai-chat-panel.svelte.d.ts +2 -0
- package/dist/chat/ai-view-bootstrap.d.ts +1 -0
- package/dist/chat/ai-view-bootstrap.js +32 -12
- package/dist/chat/ai-view-panel.svelte +14 -0
- package/dist/chat/ai-view.d.ts +1 -1
- package/dist/chat/chat-controller.svelte.d.ts +1 -1
- package/dist/chat/chat-controller.svelte.js +94 -29
- package/dist/chat/chat-tool-display.d.ts +2 -12
- package/dist/chat/chat-tool-display.js +2 -206
- package/dist/chat/composer-model-catalog.d.ts +20 -0
- package/dist/chat/composer-model-catalog.js +44 -0
- package/dist/commands/agent.d.ts +1 -1
- package/dist/commands/agent.js +6 -1
- package/dist/conversations/context-handoff.d.ts +1 -25
- package/dist/conversations/context-handoff.js +1 -207
- package/dist/conversations/hashes.d.ts +1 -5
- package/dist/conversations/hashes.js +1 -40
- package/dist/core/types.d.ts +1 -241
- package/dist/core/types.js +1 -50
- package/dist/history/ai-history-panel.svelte +4 -1
- package/dist/host/controller-connection.d.ts +14 -0
- package/dist/host/controller-connection.js +62 -0
- package/dist/host/controller-runtime.d.ts +13 -0
- package/dist/host/controller-runtime.js +112 -0
- package/dist/host/create-host-runtimes.d.ts +2 -1
- package/dist/host/create-host-runtimes.js +5 -7
- package/dist/index.d.ts +1 -1
- package/dist/memory/candidate-extractor.d.ts +2 -25
- package/dist/memory/candidate-extractor.js +1 -191
- package/dist/memory/hashes.d.ts +1 -5
- package/dist/memory/hashes.js +1 -37
- package/dist/memory/memory-record-store.d.ts +3 -48
- package/dist/memory/memory-record-store.js +16 -220
- package/dist/memory/memory-scheduler.d.ts +1 -21
- package/dist/memory/memory-scheduler.js +1 -143
- package/dist/memory/native-memory-service.d.ts +1 -72
- package/dist/memory/native-memory-service.js +1 -1256
- package/dist/memory/paths.d.ts +1 -13
- package/dist/memory/paths.js +1 -60
- package/dist/memory/runtime-consolidation-provider.d.ts +2 -18
- package/dist/memory/runtime-consolidation-provider.js +1 -154
- package/dist/memory/types.d.ts +1 -139
- package/dist/providers/acp-model-provider.d.ts +8 -3
- package/dist/providers/acp-model-provider.js +21 -49
- package/dist/providers/model-provider.d.ts +3 -2
- package/dist/providers/model-provider.js +15 -2
- package/dist/runtime-adapters.d.ts +3 -0
- package/dist/runtime-adapters.js +2 -0
- package/dist/settings/acp-agents.d.ts +4 -1
- package/dist/settings/acp-agents.js +5 -1
- package/dist/settings/ai-settings-tab.js +8 -6
- package/dist/settings/ai-settings.d.ts +4 -4
- package/dist/settings/ai-settings.js +5 -3
- package/dist/settings/register-ai-settings.js +27 -14
- package/dist/tools/app-tool-host.d.ts +22 -2
- package/dist/tools/app-tool-host.js +51 -4
- package/dist/tools/desktop-app-tool-bridge.d.ts +4 -1
- package/dist/tools/desktop-app-tool-bridge.js +94 -157
- package/manifest.json +20 -2
- package/package.json +20 -7
- package/registry-content/overview.md +10 -0
- package/registry.json +221 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AiThinkingLevel } from "../core/types";
|
|
2
|
-
import { type AcpAgentId } from "./acp-agents";
|
|
2
|
+
import { type AcpAgentId, type ProcessorAgentId } from "./acp-agents";
|
|
3
3
|
export type AppToolEnablementOwner = {
|
|
4
4
|
pluginId: string;
|
|
5
5
|
source: "core" | "community" | "official" | "system";
|
|
@@ -9,7 +9,7 @@ export type AppToolEnablementRef = {
|
|
|
9
9
|
owner: AppToolEnablementOwner;
|
|
10
10
|
};
|
|
11
11
|
export type AiPluginSettings = {
|
|
12
|
-
defaultRuntime: "auto" | "acp" | "codex-native" | "fake";
|
|
12
|
+
defaultRuntime: "auto" | "acp" | "codex-native" | "opencode-native" | "fake";
|
|
13
13
|
acpAgent: AcpAgentId;
|
|
14
14
|
defaultModels: Record<AcpAgentId, string>;
|
|
15
15
|
/** Active-agent compatibility alias. Persisted model ownership lives in defaultModels. */
|
|
@@ -18,11 +18,11 @@ export type AiPluginSettings = {
|
|
|
18
18
|
memoryAutomaticRecall: boolean;
|
|
19
19
|
memoryConsolidationEnabled: boolean;
|
|
20
20
|
memoryConsolidationRuntime: "acp" | "codex-native";
|
|
21
|
-
memoryConsolidationAgent:
|
|
21
|
+
memoryConsolidationAgent: ProcessorAgentId;
|
|
22
22
|
memoryConsolidationModel: string;
|
|
23
23
|
handoffSummariesEnabled: boolean;
|
|
24
24
|
handoffSummaryRuntime: "acp" | "codex-native";
|
|
25
|
-
handoffSummaryAgent:
|
|
25
|
+
handoffSummaryAgent: ProcessorAgentId;
|
|
26
26
|
handoffSummaryModel: string;
|
|
27
27
|
appToolsEnabled: boolean;
|
|
28
28
|
disabledAppToolNames: string[];
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { DEFAULT_ACP_AGENT, normalizeAcpAgent, } from "./acp-agents";
|
|
1
|
+
import { DEFAULT_ACP_AGENT, normalizeAcpAgent, normalizeProcessorAgent, } from "./acp-agents";
|
|
2
2
|
export const DEFAULT_AI_SETTINGS = {
|
|
3
3
|
defaultRuntime: "auto",
|
|
4
4
|
acpAgent: DEFAULT_ACP_AGENT,
|
|
5
5
|
defaultModels: {
|
|
6
6
|
codex: "gpt-5.6-sol",
|
|
7
7
|
cursor: "",
|
|
8
|
+
opencode: "",
|
|
8
9
|
},
|
|
9
10
|
defaultModel: "gpt-5.6-sol",
|
|
10
11
|
thinking: "medium",
|
|
@@ -113,6 +114,7 @@ export function mergeAiSettings(value, registeredTools) {
|
|
|
113
114
|
legacyModel ||
|
|
114
115
|
DEFAULT_AI_SETTINGS.defaultModels.codex,
|
|
115
116
|
cursor: storedModels?.cursor?.trim() || "",
|
|
117
|
+
opencode: storedModels?.opencode?.trim() || "",
|
|
116
118
|
};
|
|
117
119
|
const thinking = value?.thinking;
|
|
118
120
|
const merged = {
|
|
@@ -128,12 +130,12 @@ export function mergeAiSettings(value, registeredTools) {
|
|
|
128
130
|
memoryConsolidationRuntime: value?.memoryConsolidationRuntime === "codex-native"
|
|
129
131
|
? "codex-native"
|
|
130
132
|
: "acp",
|
|
131
|
-
memoryConsolidationAgent:
|
|
133
|
+
memoryConsolidationAgent: normalizeProcessorAgent(value?.memoryConsolidationAgent),
|
|
132
134
|
memoryConsolidationModel: value?.memoryConsolidationModel?.trim() ||
|
|
133
135
|
DEFAULT_AI_SETTINGS.memoryConsolidationModel,
|
|
134
136
|
handoffSummariesEnabled: value?.handoffSummariesEnabled === true,
|
|
135
137
|
handoffSummaryRuntime: value?.handoffSummaryRuntime === "codex-native" ? "codex-native" : "acp",
|
|
136
|
-
handoffSummaryAgent:
|
|
138
|
+
handoffSummaryAgent: normalizeProcessorAgent(value?.handoffSummaryAgent),
|
|
137
139
|
handoffSummaryModel: value?.handoffSummaryModel?.trim() ||
|
|
138
140
|
DEFAULT_AI_SETTINGS.handoffSummaryModel,
|
|
139
141
|
appToolsEnabled: value?.appToolsEnabled !== false,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { modelCatalogProviderId } from "../providers/model-provider";
|
|
1
2
|
import { getWorkspaceHostBinding } from "@lapis-notes/api/workspace-host";
|
|
2
|
-
import { ACP_AGENT_IDS, normalizeAcpAgent, } from "./acp-agents";
|
|
3
|
+
import { ACP_AGENT_IDS, PROCESSOR_AGENT_IDS, normalizeAcpAgent, normalizeProcessorAgent, } from "./acp-agents";
|
|
3
4
|
import { APP_TOOL_SETTING_PREFIX, DEFAULT_AI_SETTINGS, applyAppToolEnablement, migrateLegacyCommunityToolOptIns, } from "./ai-settings";
|
|
4
5
|
import { listAppToolSettingRows, registeredAppToolRefs, } from "./app-tool-setting-rows";
|
|
5
6
|
const FIELD_IDS = {
|
|
@@ -25,7 +26,7 @@ export function registerAiSettings(plugin) {
|
|
|
25
26
|
return;
|
|
26
27
|
const controller = binding.controller;
|
|
27
28
|
const settings = plugin.getSettings();
|
|
28
|
-
const modelSourceId = (provider) => `ai.models.${provider}`;
|
|
29
|
+
const modelSourceId = (provider, runtime) => `ai.models.${modelCatalogProviderId(provider, runtime)}`;
|
|
29
30
|
const persistLegacyToolMigration = () => {
|
|
30
31
|
const current = plugin.getSettings();
|
|
31
32
|
const migrated = migrateLegacyCommunityToolOptIns(current, registeredAppToolRefs(plugin.app));
|
|
@@ -40,14 +41,18 @@ export function registerAiSettings(plugin) {
|
|
|
40
41
|
enabledCommunityToolPluginIds: migrated.enabledCommunityToolPluginIds,
|
|
41
42
|
});
|
|
42
43
|
};
|
|
43
|
-
for (const provider of
|
|
44
|
+
for (const { provider, runtime } of [
|
|
45
|
+
...ACP_AGENT_IDS.map((provider) => ({ provider, runtime: "acp" })),
|
|
46
|
+
{ provider: "codex", runtime: "codex-native" },
|
|
47
|
+
{ provider: "opencode", runtime: "opencode-native" },
|
|
48
|
+
]) {
|
|
44
49
|
const dispose = controller.configuration.optionSources.register({
|
|
45
|
-
id: modelSourceId(provider),
|
|
50
|
+
id: modelSourceId(provider, runtime),
|
|
46
51
|
load: async () => {
|
|
47
52
|
const current = plugin.getSettings();
|
|
48
53
|
const saved = current.defaultModels[provider];
|
|
49
54
|
try {
|
|
50
|
-
const models = await plugin.models.listModels(provider);
|
|
55
|
+
const models = await plugin.models.listModels(provider, runtime);
|
|
51
56
|
if (models.length === 0) {
|
|
52
57
|
return saved
|
|
53
58
|
? [
|
|
@@ -112,6 +117,7 @@ export function registerAiSettings(plugin) {
|
|
|
112
117
|
{ value: "auto", label: "Automatic" },
|
|
113
118
|
{ value: "acp", label: "ACP" },
|
|
114
119
|
{ value: "codex-native", label: "Codex native" },
|
|
120
|
+
{ value: "opencode-native", label: "OpenCode native" },
|
|
115
121
|
{ value: "fake", label: "Fake (tests)" },
|
|
116
122
|
],
|
|
117
123
|
},
|
|
@@ -123,7 +129,11 @@ export function registerAiSettings(plugin) {
|
|
|
123
129
|
default: DEFAULT_AI_SETTINGS.acpAgent,
|
|
124
130
|
options: ACP_AGENT_IDS.map((value) => ({
|
|
125
131
|
value,
|
|
126
|
-
label: value === "cursor"
|
|
132
|
+
label: value === "cursor"
|
|
133
|
+
? "Cursor"
|
|
134
|
+
: value === "opencode"
|
|
135
|
+
? "OpenCode"
|
|
136
|
+
: "Codex",
|
|
127
137
|
})),
|
|
128
138
|
},
|
|
129
139
|
{
|
|
@@ -132,7 +142,7 @@ export function registerAiSettings(plugin) {
|
|
|
132
142
|
title: "Default model",
|
|
133
143
|
description: "Model reported by the selected agent provider and sent on the next request.",
|
|
134
144
|
default: current.defaultModel,
|
|
135
|
-
optionsSource: modelSourceId(current.acpAgent),
|
|
145
|
+
optionsSource: modelSourceId(current.acpAgent, current.defaultRuntime),
|
|
136
146
|
allowUnknownOptions: false,
|
|
137
147
|
},
|
|
138
148
|
{
|
|
@@ -179,7 +189,7 @@ export function registerAiSettings(plugin) {
|
|
|
179
189
|
title: "Memory processor agent",
|
|
180
190
|
description: "Pinned independently from the interactive chat agent.",
|
|
181
191
|
default: DEFAULT_AI_SETTINGS.memoryConsolidationAgent,
|
|
182
|
-
options:
|
|
192
|
+
options: PROCESSOR_AGENT_IDS.map((value) => ({
|
|
183
193
|
value,
|
|
184
194
|
label: value === "cursor" ? "Cursor" : "Codex",
|
|
185
195
|
})),
|
|
@@ -215,7 +225,7 @@ export function registerAiSettings(plugin) {
|
|
|
215
225
|
title: "Handoff summary agent",
|
|
216
226
|
description: "Pinned independently from the interactive chat agent.",
|
|
217
227
|
default: DEFAULT_AI_SETTINGS.handoffSummaryAgent,
|
|
218
|
-
options:
|
|
228
|
+
options: PROCESSOR_AGENT_IDS.map((value) => ({
|
|
219
229
|
value,
|
|
220
230
|
label: value === "cursor" ? "Cursor" : "Codex",
|
|
221
231
|
})),
|
|
@@ -305,9 +315,12 @@ export function registerAiSettings(plugin) {
|
|
|
305
315
|
return;
|
|
306
316
|
}
|
|
307
317
|
if (event.id === FIELD_IDS.defaultRuntime) {
|
|
308
|
-
void
|
|
309
|
-
|
|
310
|
-
|
|
318
|
+
void (async () => {
|
|
319
|
+
await plugin.updateSettings({
|
|
320
|
+
defaultRuntime: values[FIELD_IDS.defaultRuntime],
|
|
321
|
+
});
|
|
322
|
+
refreshSection(plugin.getSettings());
|
|
323
|
+
})();
|
|
311
324
|
return;
|
|
312
325
|
}
|
|
313
326
|
if (event.id === FIELD_IDS.thinking) {
|
|
@@ -338,7 +351,7 @@ export function registerAiSettings(plugin) {
|
|
|
338
351
|
}
|
|
339
352
|
if (event.id === FIELD_IDS.memoryConsolidationAgent) {
|
|
340
353
|
void plugin.updateSettings({
|
|
341
|
-
memoryConsolidationAgent:
|
|
354
|
+
memoryConsolidationAgent: normalizeProcessorAgent(values[FIELD_IDS.memoryConsolidationAgent]),
|
|
342
355
|
});
|
|
343
356
|
return;
|
|
344
357
|
}
|
|
@@ -364,7 +377,7 @@ export function registerAiSettings(plugin) {
|
|
|
364
377
|
}
|
|
365
378
|
if (event.id === FIELD_IDS.handoffSummaryAgent) {
|
|
366
379
|
void plugin.updateSettings({
|
|
367
|
-
handoffSummaryAgent:
|
|
380
|
+
handoffSummaryAgent: normalizeProcessorAgent(values[FIELD_IDS.handoffSummaryAgent]),
|
|
368
381
|
});
|
|
369
382
|
return;
|
|
370
383
|
}
|
|
@@ -1,7 +1,26 @@
|
|
|
1
1
|
import { type AppToolApprovalDetails, type AppToolRegistry, type AppToolResult, type RegisteredAppTool } from "@lapis-notes/api/agent-tools";
|
|
2
2
|
import type { AppToolSessionDescriptor, ApprovalOptionKind, ApprovalRequest } from "../core/types";
|
|
3
3
|
import { type AiPluginSettings } from "../settings/ai-settings";
|
|
4
|
-
export type AppToolExecutionErrorCode = "approval_denied" | "cancelled" | "execution_failed" | "invalid_arguments" | "invalid_result" | "invalid_tool_schema" | "timed_out" | "tool_unavailable";
|
|
4
|
+
export type AppToolExecutionErrorCode = "approval_denied" | "cancelled" | "capture_unavailable" | "execution_failed" | "invalid_arguments" | "invalid_result" | "invalid_tool_schema" | "timed_out" | "tool_unavailable";
|
|
5
|
+
export interface AppToolCapture {
|
|
6
|
+
begin(input: {
|
|
7
|
+
caller: string;
|
|
8
|
+
requestKey: string;
|
|
9
|
+
workspaceId: string;
|
|
10
|
+
captureMode: "cooperative" | "observed";
|
|
11
|
+
intent: string;
|
|
12
|
+
toolName: string;
|
|
13
|
+
}): Promise<{
|
|
14
|
+
spanId: string;
|
|
15
|
+
}>;
|
|
16
|
+
finish(input: {
|
|
17
|
+
caller: string;
|
|
18
|
+
requestKey: string;
|
|
19
|
+
workspaceId: string;
|
|
20
|
+
spanId: string;
|
|
21
|
+
outcome: "succeeded" | "failed" | "unknown";
|
|
22
|
+
}): Promise<void>;
|
|
23
|
+
}
|
|
5
24
|
export declare class AppToolExecutionError extends Error {
|
|
6
25
|
readonly code: AppToolExecutionErrorCode;
|
|
7
26
|
constructor(code: AppToolExecutionErrorCode, message: string);
|
|
@@ -34,8 +53,9 @@ export declare class AppToolHost {
|
|
|
34
53
|
private readonly registry;
|
|
35
54
|
private readonly settings;
|
|
36
55
|
private readonly timeoutMs;
|
|
56
|
+
private readonly capture?;
|
|
37
57
|
readonly approvals: AppToolApprovalBroker;
|
|
38
|
-
constructor(registry: AppToolRegistry, settings: () => AppToolPolicySettings, timeoutMs?: number);
|
|
58
|
+
constructor(registry: AppToolRegistry, settings: () => AppToolPolicySettings, timeoutMs?: number, capture?: AppToolCapture | undefined);
|
|
39
59
|
createSession(input: CreateAppToolSessionInput): AppToolSessionDescriptor;
|
|
40
60
|
getSession(agentBindingId: string): AppToolSessionDescriptor | undefined;
|
|
41
61
|
invoke(agentBindingId: string, call: AppToolCall, signal?: AbortSignal): Promise<AppToolResult>;
|
|
@@ -102,6 +102,7 @@ export class AppToolHost {
|
|
|
102
102
|
registry;
|
|
103
103
|
settings;
|
|
104
104
|
timeoutMs;
|
|
105
|
+
capture;
|
|
105
106
|
approvals = new AppToolApprovalBroker();
|
|
106
107
|
#ajv = new Ajv({ allErrors: true, strict: false });
|
|
107
108
|
#sessions = new Map();
|
|
@@ -109,10 +110,11 @@ export class AppToolHost {
|
|
|
109
110
|
#validators = new Map();
|
|
110
111
|
#activeInvocations = new Map();
|
|
111
112
|
#registryChangeRef;
|
|
112
|
-
constructor(registry, settings, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
113
|
+
constructor(registry, settings, timeoutMs = DEFAULT_TIMEOUT_MS, capture) {
|
|
113
114
|
this.registry = registry;
|
|
114
115
|
this.settings = settings;
|
|
115
116
|
this.timeoutMs = timeoutMs;
|
|
117
|
+
this.capture = capture;
|
|
116
118
|
this.#registryChangeRef = registry.on("changed", (change) => {
|
|
117
119
|
if (change.reason !== "unregistered")
|
|
118
120
|
return;
|
|
@@ -190,9 +192,54 @@ export class AppToolHost {
|
|
|
190
192
|
if (grantForSession) {
|
|
191
193
|
this.#grants.add(grantKey(session.descriptor, registered));
|
|
192
194
|
}
|
|
193
|
-
|
|
194
|
-
this.
|
|
195
|
-
|
|
195
|
+
let spanId;
|
|
196
|
+
if (this.capture && registered.tool.effect !== "read") {
|
|
197
|
+
try {
|
|
198
|
+
const span = await this.capture.begin({
|
|
199
|
+
caller: "app-tool-host",
|
|
200
|
+
requestKey: call.toolCallId,
|
|
201
|
+
workspaceId: session.descriptor.scopeDir,
|
|
202
|
+
captureMode: "cooperative",
|
|
203
|
+
intent: "tool",
|
|
204
|
+
toolName: call.name,
|
|
205
|
+
});
|
|
206
|
+
spanId = span.spanId;
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
throw new AppToolExecutionError("capture_unavailable", `Checkpoint capture is unavailable for app tool: ${call.name}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
const result = await raceWithAbort(registered.tool.execute(call.input, context), invocation.signal);
|
|
214
|
+
this.validateResult(registered, result);
|
|
215
|
+
if (spanId && this.capture) {
|
|
216
|
+
await this.capture.finish({
|
|
217
|
+
caller: "app-tool-host",
|
|
218
|
+
requestKey: `${call.toolCallId}:finish`,
|
|
219
|
+
workspaceId: session.descriptor.scopeDir,
|
|
220
|
+
spanId,
|
|
221
|
+
outcome: "succeeded",
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
if (spanId && this.capture) {
|
|
228
|
+
try {
|
|
229
|
+
await this.capture.finish({
|
|
230
|
+
caller: "app-tool-host",
|
|
231
|
+
requestKey: `${call.toolCallId}:failed`,
|
|
232
|
+
workspaceId: session.descriptor.scopeDir,
|
|
233
|
+
spanId,
|
|
234
|
+
outcome: "failed",
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
// Preserve the original tool error.
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
196
243
|
}
|
|
197
244
|
catch (error) {
|
|
198
245
|
if (error instanceof AppToolExecutionError) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentEvent, AppToolSessionDescriptor, ApprovalOptionKind } from "../core/types";
|
|
2
|
+
import type { ControllerConnection } from "../host/controller-connection";
|
|
2
3
|
import { AppToolHost, type CreateAppToolSessionInput } from "./app-tool-host";
|
|
3
4
|
export type AppToolBridgeEvent = {
|
|
4
5
|
bindingId: string;
|
|
@@ -11,10 +12,12 @@ export interface AppToolBridgeCoordinator {
|
|
|
11
12
|
subscribe(listener: (event: AppToolBridgeEvent) => void): () => void;
|
|
12
13
|
close(): Promise<void>;
|
|
13
14
|
}
|
|
15
|
+
/** App authorization stays here; the shared SDK owns transport and tool receipts. */
|
|
14
16
|
export declare class DesktopAppToolBridge implements AppToolBridgeCoordinator {
|
|
15
17
|
#private;
|
|
16
18
|
private readonly host;
|
|
17
|
-
|
|
19
|
+
private readonly connection;
|
|
20
|
+
constructor(host: AppToolHost, connection: () => Promise<ControllerConnection>);
|
|
18
21
|
prepare(input: CreateAppToolSessionInput): Promise<AppToolSessionDescriptor>;
|
|
19
22
|
respondToApproval(requestId: string, decision: ApprovalOptionKind): boolean;
|
|
20
23
|
subscribe(listener: (event: AppToolBridgeEvent) => void): () => void;
|
|
@@ -1,78 +1,127 @@
|
|
|
1
|
-
import { getNativeDesktopBridge, getNativeDesktopCapability, } from "@lapis-notes/api/desktop-native";
|
|
2
1
|
import { AppToolExecutionError, AppToolHost, } from "./app-tool-host";
|
|
3
|
-
|
|
4
|
-
const SUPPORTED_APP_TOOL_TRANSPORTS = new Set(["stdio-mcp", "http-mcp"]);
|
|
2
|
+
/** App authorization stays here; the shared SDK owns transport and tool receipts. */
|
|
5
3
|
export class DesktopAppToolBridge {
|
|
6
4
|
host;
|
|
5
|
+
connection;
|
|
7
6
|
#records = new Map();
|
|
8
7
|
#listeners = new Set();
|
|
9
8
|
#unsubscribeApprovals;
|
|
10
|
-
|
|
11
|
-
#unsubscribeCall;
|
|
12
|
-
#unsubscribeCancel;
|
|
13
|
-
constructor(host) {
|
|
9
|
+
constructor(host, connection) {
|
|
14
10
|
this.host = host;
|
|
11
|
+
this.connection = connection;
|
|
15
12
|
this.#unsubscribeApprovals = host.approvals.subscribe((request, bindingId) => {
|
|
16
13
|
const record = this.#records.get(bindingId);
|
|
17
14
|
const active = record ? [...record.calls.values()].at(-1) : undefined;
|
|
18
15
|
this.#emit(bindingId, {
|
|
19
16
|
type: "permission.request",
|
|
20
17
|
request,
|
|
21
|
-
...(record && active
|
|
22
|
-
? { source: nextSource(record, active) }
|
|
23
|
-
: {}),
|
|
18
|
+
...(record && active ? { source: nextSource(record, active) } : {}),
|
|
24
19
|
});
|
|
25
20
|
});
|
|
26
21
|
}
|
|
27
22
|
async prepare(input) {
|
|
28
|
-
if (this.#records.has(input.agentBindingId))
|
|
23
|
+
if (this.#records.has(input.agentBindingId))
|
|
29
24
|
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
|
-
});
|
|
25
|
+
const descriptor = this.host.createSession(input);
|
|
38
26
|
const record = {
|
|
39
27
|
bindingId: input.agentBindingId,
|
|
28
|
+
bridgeId: `lapis.tools.${input.agentBindingId}`,
|
|
40
29
|
calls: new Map(),
|
|
41
30
|
};
|
|
42
31
|
this.#records.set(input.agentBindingId, record);
|
|
43
|
-
if (!
|
|
44
|
-
return
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (descriptor.tools.length
|
|
50
|
-
return
|
|
51
|
-
}
|
|
52
|
-
this.#subscribeToBridge(bridge);
|
|
32
|
+
if (!input.runtimeSupportsAppTools)
|
|
33
|
+
return Object.freeze({
|
|
34
|
+
...descriptor,
|
|
35
|
+
status: "runtime-unavailable",
|
|
36
|
+
unavailableReason: "The selected runtime does not support application tools.",
|
|
37
|
+
});
|
|
38
|
+
if (!descriptor.tools.length)
|
|
39
|
+
return Object.freeze({ ...descriptor, status: "disabled" });
|
|
53
40
|
try {
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
41
|
+
const { client } = await this.connection();
|
|
42
|
+
record.unregister = await client.registerEndpoint({
|
|
43
|
+
id: record.bridgeId,
|
|
44
|
+
tools: descriptor.tools.map((tool) => ({
|
|
58
45
|
name: tool.name,
|
|
59
46
|
description: tool.description,
|
|
60
47
|
inputSchema: tool.inputSchema,
|
|
61
|
-
|
|
62
|
-
effect: tool.effect,
|
|
48
|
+
effect: tool.effect === "read" ? "read" : "write",
|
|
63
49
|
})),
|
|
50
|
+
handle: async (request, signal) => {
|
|
51
|
+
if (request.operation !== "tool.execute" ||
|
|
52
|
+
this.#records.get(record.bindingId) !== record)
|
|
53
|
+
throw new Error("Application tool binding is unavailable.");
|
|
54
|
+
const call = request.payload;
|
|
55
|
+
const active = {
|
|
56
|
+
abort: new AbortController(),
|
|
57
|
+
runId: `app-tool-${call.invocationId}`,
|
|
58
|
+
sequence: 0,
|
|
59
|
+
};
|
|
60
|
+
const abort = () => active.abort.abort();
|
|
61
|
+
if (signal.aborted)
|
|
62
|
+
abort();
|
|
63
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
64
|
+
record.calls.set(call.invocationId, active);
|
|
65
|
+
this.#emit(record.bindingId, {
|
|
66
|
+
type: "tool.start",
|
|
67
|
+
id: call.invocationId,
|
|
68
|
+
name: call.name,
|
|
69
|
+
server: "lapis-tools",
|
|
70
|
+
input: call.arguments,
|
|
71
|
+
source: nextSource(record, active),
|
|
72
|
+
});
|
|
73
|
+
try {
|
|
74
|
+
const result = await this.host.invoke(record.bindingId, {
|
|
75
|
+
runId: active.runId,
|
|
76
|
+
toolCallId: call.invocationId,
|
|
77
|
+
name: call.name,
|
|
78
|
+
input: call.arguments,
|
|
79
|
+
}, active.abort.signal);
|
|
80
|
+
this.#emit(record.bindingId, {
|
|
81
|
+
type: "tool.end",
|
|
82
|
+
id: call.invocationId,
|
|
83
|
+
name: call.name,
|
|
84
|
+
server: "lapis-tools",
|
|
85
|
+
output: result,
|
|
86
|
+
source: nextSource(record, active),
|
|
87
|
+
});
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
const normalized = error instanceof AppToolExecutionError
|
|
92
|
+
? { code: error.code, message: error.message }
|
|
93
|
+
: {
|
|
94
|
+
code: "execution_failed",
|
|
95
|
+
message: "Application tool execution failed.",
|
|
96
|
+
};
|
|
97
|
+
this.#emit(record.bindingId, {
|
|
98
|
+
type: "tool.end",
|
|
99
|
+
id: call.invocationId,
|
|
100
|
+
name: call.name,
|
|
101
|
+
server: "lapis-tools",
|
|
102
|
+
error: normalized,
|
|
103
|
+
source: nextSource(record, active),
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
content: [{ type: "text", text: normalized.message }],
|
|
107
|
+
isError: true,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
signal.removeEventListener("abort", abort);
|
|
112
|
+
record.calls.delete(call.invocationId);
|
|
113
|
+
}
|
|
114
|
+
},
|
|
64
115
|
});
|
|
65
|
-
record.bridge = bridge;
|
|
66
|
-
record.bridgeId = opened.bridgeId;
|
|
67
116
|
return Object.freeze({
|
|
68
117
|
...descriptor,
|
|
69
|
-
bridgeId:
|
|
118
|
+
bridgeId: record.bridgeId,
|
|
70
119
|
status: "available",
|
|
71
120
|
});
|
|
72
121
|
}
|
|
73
122
|
catch (error) {
|
|
74
|
-
this.#records.delete(
|
|
75
|
-
this.host.closeBinding(
|
|
123
|
+
this.#records.delete(record.bindingId);
|
|
124
|
+
this.host.closeBinding(record.bindingId);
|
|
76
125
|
throw error;
|
|
77
126
|
}
|
|
78
127
|
}
|
|
@@ -88,136 +137,24 @@ export class DesktopAppToolBridge {
|
|
|
88
137
|
this.#records.delete(bindingId);
|
|
89
138
|
for (const call of record?.calls.values() ?? [])
|
|
90
139
|
call.abort.abort();
|
|
91
|
-
record?.calls.clear();
|
|
92
140
|
this.host.closeBinding(bindingId);
|
|
93
|
-
|
|
94
|
-
await record.bridge
|
|
95
|
-
.invoke("desktop_agent_tools_close", { bridgeId: record.bridgeId })
|
|
96
|
-
.catch(() => undefined);
|
|
97
|
-
}
|
|
141
|
+
await record?.unregister?.().catch(() => undefined);
|
|
98
142
|
}
|
|
99
143
|
async close() {
|
|
100
|
-
for (const
|
|
101
|
-
await this.closeBinding(
|
|
102
|
-
}
|
|
144
|
+
for (const id of [...this.#records.keys()])
|
|
145
|
+
await this.closeBinding(id);
|
|
103
146
|
this.#unsubscribeApprovals();
|
|
104
|
-
this.#unsubscribeCall?.();
|
|
105
|
-
this.#unsubscribeCancel?.();
|
|
106
147
|
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
148
|
}
|
|
191
149
|
#emit(bindingId, event) {
|
|
192
150
|
for (const listener of this.#listeners)
|
|
193
151
|
listener({ bindingId, event });
|
|
194
152
|
}
|
|
195
153
|
}
|
|
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
154
|
function nextSource(record, active) {
|
|
208
|
-
active.sequence += 1;
|
|
209
155
|
return {
|
|
210
|
-
sessionId: record.bridgeId
|
|
156
|
+
sessionId: record.bridgeId,
|
|
211
157
|
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.",
|
|
158
|
+
sequence: ++active.sequence,
|
|
222
159
|
};
|
|
223
160
|
}
|
package/manifest.json
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "ai",
|
|
3
3
|
"name": "AI",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.5",
|
|
5
5
|
"minAppVersion": "0.1.0",
|
|
6
6
|
"description": "Provider-agnostic agent chat with ACP and optional native runtimes.",
|
|
7
7
|
"author": "Lapis Notes",
|
|
8
|
+
"authorAvatarUrl": "https://www.gravatar.com/avatar/1df68d9ff087b0dd72fd8626056fca7f?s=128&d=404",
|
|
9
|
+
"isDesktopOnly": false,
|
|
8
10
|
"lapis": {
|
|
9
11
|
"manifestVersion": 1,
|
|
10
12
|
"source": "official",
|
|
@@ -24,7 +26,23 @@
|
|
|
24
26
|
"@lapis-notes/api/path",
|
|
25
27
|
"@lapis-notes/api/vault",
|
|
26
28
|
"@lapis-notes/api/workspace-host",
|
|
27
|
-
"@lapis-notes/markdown/embed"
|
|
29
|
+
"@lapis-notes/markdown/embed",
|
|
30
|
+
"@codemirror/autocomplete",
|
|
31
|
+
"@codemirror/commands",
|
|
32
|
+
"@codemirror/language",
|
|
33
|
+
"@codemirror/state",
|
|
34
|
+
"@codemirror/view",
|
|
35
|
+
"@lezer/common",
|
|
36
|
+
"@lezer/highlight",
|
|
37
|
+
"@lezer/lr",
|
|
38
|
+
"svelte/renderer/5.56.10",
|
|
39
|
+
"svelte/renderer/5.56.10/attachments",
|
|
40
|
+
"svelte/renderer/5.56.10/events",
|
|
41
|
+
"svelte/renderer/5.56.10/internal/client",
|
|
42
|
+
"svelte/renderer/5.56.10/internal/disclose-version",
|
|
43
|
+
"svelte/renderer/5.56.10/internal/flags/legacy",
|
|
44
|
+
"svelte/renderer/5.56.10/reactivity",
|
|
45
|
+
"svelte/renderer/5.56.10/store"
|
|
28
46
|
],
|
|
29
47
|
"requiresReloadOnUpdate": false
|
|
30
48
|
}
|