@eclipse-lyra/extension-ai-system 0.0.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.
Files changed (137) hide show
  1. package/dist/agents/agent-registry.d.ts +8 -0
  2. package/dist/agents/agent-registry.d.ts.map +1 -0
  3. package/dist/agents/index.d.ts +6 -0
  4. package/dist/agents/index.d.ts.map +1 -0
  5. package/dist/agents/message-processor.d.ts +9 -0
  6. package/dist/agents/message-processor.d.ts.map +1 -0
  7. package/dist/agents/orchestrator.d.ts +12 -0
  8. package/dist/agents/orchestrator.d.ts.map +1 -0
  9. package/dist/agents/prompt-builder.d.ts +21 -0
  10. package/dist/agents/prompt-builder.d.ts.map +1 -0
  11. package/dist/agents/reviewer.d.ts +15 -0
  12. package/dist/agents/reviewer.d.ts.map +1 -0
  13. package/dist/ai-service-CGdlV3FV.js +1731 -0
  14. package/dist/ai-service-CGdlV3FV.js.map +1 -0
  15. package/dist/ai-system-extension-CPLV13Lk.js +2394 -0
  16. package/dist/ai-system-extension-CPLV13Lk.js.map +1 -0
  17. package/dist/ai-system-extension.d.ts +1 -0
  18. package/dist/ai-system-extension.d.ts.map +1 -0
  19. package/dist/api.d.ts +8 -0
  20. package/dist/api.d.ts.map +1 -0
  21. package/dist/api.js +46 -0
  22. package/dist/api.js.map +1 -0
  23. package/dist/chat-provider-contributions.d.ts +2 -0
  24. package/dist/chat-provider-contributions.d.ts.map +1 -0
  25. package/dist/core/constants.d.ts +21 -0
  26. package/dist/core/constants.d.ts.map +1 -0
  27. package/dist/core/index.d.ts +4 -0
  28. package/dist/core/index.d.ts.map +1 -0
  29. package/dist/core/interfaces.d.ts +138 -0
  30. package/dist/core/interfaces.d.ts.map +1 -0
  31. package/dist/core/message-utils.d.ts +4 -0
  32. package/dist/core/message-utils.d.ts.map +1 -0
  33. package/dist/core/types.d.ts +128 -0
  34. package/dist/core/types.d.ts.map +1 -0
  35. package/dist/index.d.ts +2 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +10 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/prompt-enhancer-contributions.d.ts +2 -0
  40. package/dist/prompt-enhancer-contributions.d.ts.map +1 -0
  41. package/dist/providers/base-provider.d.ts +15 -0
  42. package/dist/providers/base-provider.d.ts.map +1 -0
  43. package/dist/providers/index.d.ts +9 -0
  44. package/dist/providers/index.d.ts.map +1 -0
  45. package/dist/providers/ollama-provider.d.ts +7 -0
  46. package/dist/providers/ollama-provider.d.ts.map +1 -0
  47. package/dist/providers/openai-provider.d.ts +7 -0
  48. package/dist/providers/openai-provider.d.ts.map +1 -0
  49. package/dist/providers/provider-factory.d.ts +10 -0
  50. package/dist/providers/provider-factory.d.ts.map +1 -0
  51. package/dist/providers/provider-utils.d.ts +5 -0
  52. package/dist/providers/provider-utils.d.ts.map +1 -0
  53. package/dist/providers/streaming/ollama-parser.d.ts +9 -0
  54. package/dist/providers/streaming/ollama-parser.d.ts.map +1 -0
  55. package/dist/providers/streaming/sse-parser.d.ts +10 -0
  56. package/dist/providers/streaming/sse-parser.d.ts.map +1 -0
  57. package/dist/providers/streaming/stream-parser.d.ts +10 -0
  58. package/dist/providers/streaming/stream-parser.d.ts.map +1 -0
  59. package/dist/service/ai-service.d.ts +47 -0
  60. package/dist/service/ai-service.d.ts.map +1 -0
  61. package/dist/service/token-usage-tracker.d.ts +16 -0
  62. package/dist/service/token-usage-tracker.d.ts.map +1 -0
  63. package/dist/task/task-checkpoint.d.ts +12 -0
  64. package/dist/task/task-checkpoint.d.ts.map +1 -0
  65. package/dist/task/task-plan.d.ts +7 -0
  66. package/dist/task/task-plan.d.ts.map +1 -0
  67. package/dist/task/task-runner.d.ts +10 -0
  68. package/dist/task/task-runner.d.ts.map +1 -0
  69. package/dist/tools/index.d.ts +6 -0
  70. package/dist/tools/index.d.ts.map +1 -0
  71. package/dist/tools/token-estimator.d.ts +12 -0
  72. package/dist/tools/token-estimator.d.ts.map +1 -0
  73. package/dist/tools/tool-call-accumulator.d.ts +9 -0
  74. package/dist/tools/tool-call-accumulator.d.ts.map +1 -0
  75. package/dist/tools/tool-executor.d.ts +13 -0
  76. package/dist/tools/tool-executor.d.ts.map +1 -0
  77. package/dist/tools/tool-name-utils.d.ts +2 -0
  78. package/dist/tools/tool-name-utils.d.ts.map +1 -0
  79. package/dist/tools/tool-registry.d.ts +7 -0
  80. package/dist/tools/tool-registry.d.ts.map +1 -0
  81. package/dist/utils/token-estimator.d.ts +2 -0
  82. package/dist/utils/token-estimator.d.ts.map +1 -0
  83. package/dist/utils/tool-detector.d.ts +10 -0
  84. package/dist/utils/tool-detector.d.ts.map +1 -0
  85. package/dist/view/agent-group-manager.d.ts +37 -0
  86. package/dist/view/agent-group-manager.d.ts.map +1 -0
  87. package/dist/view/aiview.d.ts +42 -0
  88. package/dist/view/aiview.d.ts.map +1 -0
  89. package/dist/view/components/ai-agent-response-group.d.ts +18 -0
  90. package/dist/view/components/ai-agent-response-group.d.ts.map +1 -0
  91. package/dist/view/components/ai-chat-input.d.ts +20 -0
  92. package/dist/view/components/ai-chat-input.d.ts.map +1 -0
  93. package/dist/view/components/ai-chat-message.d.ts +22 -0
  94. package/dist/view/components/ai-chat-message.d.ts.map +1 -0
  95. package/dist/view/components/ai-config-editor.d.ts +33 -0
  96. package/dist/view/components/ai-config-editor.d.ts.map +1 -0
  97. package/dist/view/components/ai-empty-state.d.ts +13 -0
  98. package/dist/view/components/ai-empty-state.d.ts.map +1 -0
  99. package/dist/view/components/ai-tool-approval.d.ts +26 -0
  100. package/dist/view/components/ai-tool-approval.d.ts.map +1 -0
  101. package/dist/view/components/index.d.ts +7 -0
  102. package/dist/view/components/index.d.ts.map +1 -0
  103. package/dist/view/provider-manager.d.ts +30 -0
  104. package/dist/view/provider-manager.d.ts.map +1 -0
  105. package/dist/view/session-manager.d.ts +25 -0
  106. package/dist/view/session-manager.d.ts.map +1 -0
  107. package/dist/view/stream-manager.d.ts +23 -0
  108. package/dist/view/stream-manager.d.ts.map +1 -0
  109. package/dist/view/task-progress-panel.d.ts +14 -0
  110. package/dist/view/task-progress-panel.d.ts.map +1 -0
  111. package/dist/view/token-usage.d.ts +18 -0
  112. package/dist/view/token-usage.d.ts.map +1 -0
  113. package/dist/view/workspace-panel.d.ts +15 -0
  114. package/dist/view/workspace-panel.d.ts.map +1 -0
  115. package/dist/workflows/base-sequential-workflow.d.ts +13 -0
  116. package/dist/workflows/base-sequential-workflow.d.ts.map +1 -0
  117. package/dist/workflows/conditional-workflow.d.ts +7 -0
  118. package/dist/workflows/conditional-workflow.d.ts.map +1 -0
  119. package/dist/workflows/index.d.ts +10 -0
  120. package/dist/workflows/index.d.ts.map +1 -0
  121. package/dist/workflows/orchestrated-workflow.d.ts +10 -0
  122. package/dist/workflows/orchestrated-workflow.d.ts.map +1 -0
  123. package/dist/workflows/parallel-workflow.d.ts +6 -0
  124. package/dist/workflows/parallel-workflow.d.ts.map +1 -0
  125. package/dist/workflows/pipeline-workflow.d.ts +7 -0
  126. package/dist/workflows/pipeline-workflow.d.ts.map +1 -0
  127. package/dist/workflows/review-workflow.d.ts +10 -0
  128. package/dist/workflows/review-workflow.d.ts.map +1 -0
  129. package/dist/workflows/sequential-workflow.d.ts +7 -0
  130. package/dist/workflows/sequential-workflow.d.ts.map +1 -0
  131. package/dist/workflows/workflow-engine.d.ts +8 -0
  132. package/dist/workflows/workflow-engine.d.ts.map +1 -0
  133. package/dist/workflows/workflow-strategy.d.ts +8 -0
  134. package/dist/workflows/workflow-strategy.d.ts.map +1 -0
  135. package/dist/workspace/workspace.d.ts +20 -0
  136. package/dist/workspace/workspace.d.ts.map +1 -0
  137. package/package.json +34 -0
@@ -0,0 +1,2394 @@
1
+ import { css, LitElement, html, nothing } from "lit";
2
+ import { contributionRegistry, workspaceService, editorRegistry, appSettings, LyraPart, toastError, commandRegistry, uiContext, taskService, LyraElement, subscribe, confirmDialog, TOPIC_SETTINGS_CHANGED, SIDEBAR_AUXILIARY, TOOLBAR_BOTTOM, registerAll, TOOLBAR_MAIN_RIGHT, rootContext } from "@eclipse-lyra/core";
3
+ import { c as CID_CHAT_PROVIDERS, d as CID_PROMPT_ENHANCERS, m as ProviderFactory, K as KEY_AI_CONFIG, E as aiService, r as TOPIC_AICONFIG_CHANGED, I as EMPTY_USAGE, t as TOPIC_AI_STREAM_COMPLETE, J as tokenUsageTracker, C as CID_AGENTS } from "./ai-service-CGdlV3FV.js";
4
+ import { property, customElement, query, state } from "lit/decorators.js";
5
+ import { when } from "lit/directives/when.js";
6
+ import { unsafeHTML } from "lit/directives/unsafe-html.js";
7
+ import { marked } from "marked";
8
+ import { repeat } from "lit/directives/repeat.js";
9
+ const GENERAL_SYS_PROMPT = "You are an assistant in a web application with workspace, editors, and AI chat.\n\n**Tools:**\nCommands are exposed as AI-callable tools. Tools are context-aware - available commands depend on active editor, selected files, and workspace state.\n\n**Tool Usage Rules:**\n1. If tools are available and match the request, use them - don't describe manual steps\n2. Read tool descriptions/parameters to select the correct tool\n3. Call tools in sequence for multi-step tasks\n4. After successful tool execution, provide a final response - don't loop or call more tools unless explicitly requested\n5. If no tools are available, explain what context is needed\n\nKeep responses concise. Use tools when available rather than discussing alternatives.\n\n";
10
+ const providers = [
11
+ { label: "Ollama (Local)", name: "ollama", model: "gemma3:12b", chatApiEndpoint: "https://<your-server>/v1/chat/completions", apiKey: "" },
12
+ { label: "OpenWebUI (Self Hosted)", name: "openwebui", model: "gemma3:12b", chatApiEndpoint: "https://<your-server>/api/v1/chat/completion", apiKey: "" },
13
+ { label: "OpenAI", name: "openai", model: "gpt-4.1", chatApiEndpoint: "https://api.openai.com/v1/chat/completions", apiKey: "<your api key>" },
14
+ { label: "Groq", name: "groq", model: "llama-3.1-8b-instant", chatApiEndpoint: "https://api.groq.com/openai/v1/chat/completions", apiKey: "<your api key>" },
15
+ { label: "Cerebras", name: "cerebras", model: "llama3.1-8b", chatApiEndpoint: "https://api.cerebras.ai/v1/chat/completions", apiKey: "<your api key>" },
16
+ { label: "WebLLM", name: "webllm", model: "gemma-2-9b-it-q4f16_1-MLC", chatApiEndpoint: "", apiKey: "", parameters: { context_window_size: 4096 } },
17
+ { label: "Mistral", name: "mistral", model: "mistral-large-latest", chatApiEndpoint: "https://api.mistral.ai/v1/chat/completions", apiKey: "<your api key>" },
18
+ { label: "LiteLLM", name: "litellm", model: "gpt-3.5-turbo", chatApiEndpoint: "https://<your-server>/v1/chat/completions", apiKey: "<your api key>" }
19
+ ];
20
+ for (const { label, ...provider } of providers) {
21
+ contributionRegistry.registerContribution(CID_CHAT_PROVIDERS, {
22
+ target: CID_CHAT_PROVIDERS,
23
+ label,
24
+ provider
25
+ });
26
+ }
27
+ const appStateEnhancer = {
28
+ priority: 20,
29
+ enhance: async (prompt, _context) => {
30
+ try {
31
+ const workspace = await workspaceService.getWorkspace();
32
+ const activeEditor = editorRegistry.getEditorArea()?.getActiveEditor();
33
+ const appState = {
34
+ workspace: workspace?.getName() || null,
35
+ activeEditor: activeEditor ? {
36
+ title: activeEditor.input?.title || null,
37
+ editorId: activeEditor.input?.editorId || null
38
+ } : null
39
+ };
40
+ return `${prompt}
41
+
42
+ ***App's state:***
43
+ ${JSON.stringify(appState, null, 2)}`;
44
+ } catch {
45
+ return prompt;
46
+ }
47
+ }
48
+ };
49
+ contributionRegistry.registerContribution(CID_PROMPT_ENHANCERS, {
50
+ label: "App State Enhancer",
51
+ enhancer: appStateEnhancer
52
+ });
53
+ class SessionManager {
54
+ constructor() {
55
+ this.activeSession = null;
56
+ this.pastSessions = [];
57
+ }
58
+ async load() {
59
+ const saved = await appSettings.get("aiChatSessions");
60
+ if (!saved) return;
61
+ if (saved.active && Array.isArray(saved.history)) {
62
+ this.activeSession = saved.active;
63
+ } else if (saved.activeSessionId && Array.isArray(saved.sessions)) {
64
+ this.activeSession = saved.sessions.find((s) => s.id === saved.activeSessionId) || null;
65
+ this.pastSessions = saved.sessions.filter((s) => s.id !== saved.activeSessionId);
66
+ } else if (Array.isArray(saved.all)) {
67
+ const [first, ...rest] = saved.all.sort((a, b) => b.updatedAt - a.updatedAt);
68
+ this.activeSession = first || null;
69
+ this.pastSessions = rest;
70
+ }
71
+ }
72
+ async persist() {
73
+ const all = [];
74
+ if (this.activeSession) all.push(this.activeSession);
75
+ all.push(...this.pastSessions);
76
+ await appSettings.set("aiChatSessions", {
77
+ all,
78
+ activeSessionId: this.activeSession?.id || null
79
+ });
80
+ }
81
+ createSession() {
82
+ const session = {
83
+ id: `session-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
84
+ history: [],
85
+ title: "New Chat",
86
+ createdAt: Date.now(),
87
+ updatedAt: Date.now()
88
+ };
89
+ if (this.activeSession) {
90
+ this.pastSessions.unshift(this.activeSession);
91
+ }
92
+ this.activeSession = session;
93
+ this.persist();
94
+ return session;
95
+ }
96
+ getActiveSession() {
97
+ return this.activeSession;
98
+ }
99
+ getActiveSessionId() {
100
+ return this.activeSession?.id || "";
101
+ }
102
+ switchToSession(sessionId) {
103
+ if (this.activeSession?.id === sessionId) return true;
104
+ const idx = this.pastSessions.findIndex((s) => s.id === sessionId);
105
+ if (idx === -1) return false;
106
+ const [target] = this.pastSessions.splice(idx, 1);
107
+ if (!target) return false;
108
+ if (this.activeSession) this.pastSessions.unshift(this.activeSession);
109
+ this.activeSession = target;
110
+ this.persist();
111
+ return true;
112
+ }
113
+ getPastSessions() {
114
+ return this.pastSessions;
115
+ }
116
+ deletePastSession(sessionId) {
117
+ const idx = this.pastSessions.findIndex((s) => s.id === sessionId);
118
+ if (idx === -1) return false;
119
+ this.pastSessions.splice(idx, 1);
120
+ this.persist();
121
+ return true;
122
+ }
123
+ addMessage(message) {
124
+ if (!this.activeSession) return;
125
+ this.activeSession.history.push(message);
126
+ this.activeSession.updatedAt = Date.now();
127
+ this.persist();
128
+ }
129
+ setTitle(title) {
130
+ if (!this.activeSession) return;
131
+ this.activeSession.title = title;
132
+ this.persist();
133
+ }
134
+ generateTitle(prompt) {
135
+ const trimmed = prompt.trim();
136
+ if (!trimmed) return "New Chat";
137
+ return trimmed.length <= 30 ? trimmed : trimmed.substring(0, 30).trim() + "...";
138
+ }
139
+ deleteActiveAndSwitchToFirst() {
140
+ if (!this.activeSession) return;
141
+ this.activeSession = this.pastSessions.shift() || null;
142
+ if (!this.activeSession) {
143
+ this.createSession();
144
+ }
145
+ this.persist();
146
+ }
147
+ }
148
+ class StreamManager {
149
+ constructor(onUpdate) {
150
+ this.streamingMessages = /* @__PURE__ */ new Map();
151
+ this.currentIndex = -1;
152
+ this.pendingUpdate = false;
153
+ this.onUpdate = onUpdate;
154
+ }
155
+ createStreamingMessage(role) {
156
+ const index = ++this.currentIndex;
157
+ this.streamingMessages.set(index, { message: { role, content: "" }, isStreaming: true });
158
+ return index;
159
+ }
160
+ updateStreamingMessage(index, token) {
161
+ const msg = this.streamingMessages.get(index);
162
+ if (!msg) return;
163
+ msg.message.content += token;
164
+ this.scheduleUpdate();
165
+ }
166
+ completeStreamingMessage(index, message) {
167
+ const msg = this.streamingMessages.get(index);
168
+ if (!msg) return;
169
+ msg.message = message;
170
+ msg.isStreaming = false;
171
+ }
172
+ removeStreamingMessage(index) {
173
+ this.streamingMessages.delete(index);
174
+ }
175
+ findStreamingMessage(role) {
176
+ return Array.from(this.streamingMessages.values()).find((m) => m.message.role === role)?.message;
177
+ }
178
+ getAllStreamingMessages() {
179
+ return Array.from(this.streamingMessages.values());
180
+ }
181
+ scheduleUpdate() {
182
+ if (this.pendingUpdate) return;
183
+ this.pendingUpdate = true;
184
+ this.rafHandle = requestAnimationFrame(() => {
185
+ this.pendingUpdate = false;
186
+ this.onUpdate?.();
187
+ });
188
+ }
189
+ cancelUpdates() {
190
+ if (this.rafHandle !== void 0) {
191
+ cancelAnimationFrame(this.rafHandle);
192
+ this.rafHandle = void 0;
193
+ this.pendingUpdate = false;
194
+ }
195
+ }
196
+ reset() {
197
+ this.streamingMessages.clear();
198
+ this.cancelUpdates();
199
+ this.currentIndex = -1;
200
+ }
201
+ }
202
+ const VIEW_SETTINGS_KEY = "aiViewChat";
203
+ class ProviderManager {
204
+ constructor(aiService2) {
205
+ this.aiService = aiService2;
206
+ this.providers = [];
207
+ this.availableModels = [];
208
+ this.loadingModels = false;
209
+ this.providerFactory = new ProviderFactory();
210
+ }
211
+ async initialize() {
212
+ this.providers = await this.aiService.getProviders() || [];
213
+ const defaultProvider = await this.aiService.getDefaultProvider();
214
+ if (defaultProvider) {
215
+ this.selectedProvider = defaultProvider;
216
+ }
217
+ }
218
+ getProviders() {
219
+ return this.providers;
220
+ }
221
+ getSelectedProvider() {
222
+ return this.selectedProvider;
223
+ }
224
+ setSelectedProvider(provider) {
225
+ this.selectedProvider = provider;
226
+ }
227
+ getAvailableModels() {
228
+ return this.availableModels;
229
+ }
230
+ isLoadingModels() {
231
+ return this.loadingModels;
232
+ }
233
+ async saveSettings(providerName, model, apiKey, requireToolApproval, toolApprovalAllowlist) {
234
+ const current = await appSettings.get(VIEW_SETTINGS_KEY) || {};
235
+ const settings = { ...current };
236
+ if (requireToolApproval !== void 0) settings.requireToolApproval = requireToolApproval;
237
+ if (toolApprovalAllowlist !== void 0) settings.toolApprovalAllowlist = toolApprovalAllowlist;
238
+ await appSettings.set(VIEW_SETTINGS_KEY, settings);
239
+ const provider = this.providers.find((p) => p.name === providerName);
240
+ if (provider) {
241
+ const updated = { ...provider, model, ...apiKey !== void 0 && { apiKey } };
242
+ this.selectedProvider = updated;
243
+ await this.updateProviderInAIConfig(providerName, { model, ...apiKey !== void 0 && { apiKey } });
244
+ await this.aiService.setDefaultProvider(providerName);
245
+ }
246
+ }
247
+ async updateProviderInAIConfig(providerName, updates) {
248
+ const aiConfig = await appSettings.get(KEY_AI_CONFIG) || {};
249
+ if (!aiConfig.providers || !Array.isArray(aiConfig.providers)) return;
250
+ const idx = aiConfig.providers.findIndex((p) => p.name === providerName);
251
+ if (idx >= 0) {
252
+ aiConfig.providers[idx] = { ...aiConfig.providers[idx], ...updates };
253
+ await appSettings.set(KEY_AI_CONFIG, aiConfig);
254
+ }
255
+ }
256
+ async loadToolApprovalAllowlist() {
257
+ const settings = await appSettings.get(VIEW_SETTINGS_KEY) || {};
258
+ return settings.toolApprovalAllowlist || [];
259
+ }
260
+ async fetchModels(providerName) {
261
+ const provider = this.providers.find((p) => p.name === providerName);
262
+ if (!provider) return;
263
+ this.loadingModels = true;
264
+ this.availableModels = [];
265
+ try {
266
+ const baseProvider = this.providerFactory.getProvider(provider);
267
+ this.availableModels = await baseProvider.getAvailableModels?.(provider) ?? [];
268
+ } finally {
269
+ this.loadingModels = false;
270
+ }
271
+ }
272
+ }
273
+ class AgentGroupManager {
274
+ constructor() {
275
+ this.groups = /* @__PURE__ */ new Map();
276
+ }
277
+ createGroup(sessionId, userMessageIndex, userMessage, roles, getAgentMetadata) {
278
+ const groupId = `group-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
279
+ this.currentGroupId = groupId;
280
+ const group = {
281
+ id: groupId,
282
+ sessionId,
283
+ userMessageIndex,
284
+ userMessage,
285
+ timestamp: /* @__PURE__ */ new Date(),
286
+ agents: /* @__PURE__ */ new Map(),
287
+ messageIndices: /* @__PURE__ */ new Map()
288
+ };
289
+ roles.forEach((role) => {
290
+ const { label, icon } = getAgentMetadata(role);
291
+ group.agents.set(role, { role, label, icon, status: "streaming" });
292
+ });
293
+ this.groups.set(groupId, group);
294
+ return groupId;
295
+ }
296
+ getGroup(groupId) {
297
+ return this.groups.get(groupId);
298
+ }
299
+ updateAgentStatus(groupId, role, status, message, messageIndex) {
300
+ const group = this.groups.get(groupId);
301
+ if (!group) return;
302
+ const agentInfo = group.agents.get(role);
303
+ if (!agentInfo) return;
304
+ agentInfo.status = status;
305
+ if (message) agentInfo.message = message;
306
+ if (messageIndex !== void 0) {
307
+ agentInfo.messageIndex = messageIndex;
308
+ group.messageIndices.set(role, messageIndex);
309
+ }
310
+ }
311
+ getGroupsForSession(sessionId) {
312
+ return Array.from(this.groups.values()).filter((g) => g.sessionId === sessionId);
313
+ }
314
+ findGroupForUserMessage(sessionId, userMessageIndex, userMessage) {
315
+ return Array.from(this.groups.values()).find(
316
+ (g) => g.sessionId === sessionId && g.userMessageIndex === userMessageIndex && g.userMessage === userMessage
317
+ );
318
+ }
319
+ findGroupForMessage(sessionId, messageRole, messageIndex) {
320
+ return Array.from(this.groups.values()).find(
321
+ (g) => g.sessionId === sessionId && g.messageIndices.get(messageRole) === messageIndex
322
+ );
323
+ }
324
+ getCurrentGroupId() {
325
+ return this.currentGroupId;
326
+ }
327
+ setCurrentGroupId(groupId) {
328
+ this.currentGroupId = groupId;
329
+ }
330
+ clearCurrentGroup() {
331
+ this.currentGroupId = void 0;
332
+ }
333
+ getAllGroups() {
334
+ return Array.from(this.groups.values());
335
+ }
336
+ clearAll() {
337
+ this.groups.clear();
338
+ this.currentGroupId = void 0;
339
+ }
340
+ }
341
+ var __defProp$8 = Object.defineProperty;
342
+ var __getOwnPropDesc$9 = Object.getOwnPropertyDescriptor;
343
+ var __decorateClass$9 = (decorators, target, key, kind) => {
344
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$9(target, key) : target;
345
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
346
+ if (decorator = decorators[i])
347
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
348
+ if (kind && result) __defProp$8(target, key, result);
349
+ return result;
350
+ };
351
+ let AIChatMessage = class extends LitElement {
352
+ constructor() {
353
+ super(...arguments);
354
+ this.isStreaming = false;
355
+ this.showHeader = true;
356
+ }
357
+ updated(_changedProperties) {
358
+ super.updated(_changedProperties);
359
+ if (_changedProperties.has("message") || !this.hasAttribute("data-is-user")) {
360
+ this.updateAlignment();
361
+ }
362
+ }
363
+ updateAlignment() {
364
+ if (this.message) {
365
+ this.setAttribute("data-is-user", String(this.message.role === "user"));
366
+ }
367
+ }
368
+ copyToClipboard(text) {
369
+ navigator.clipboard.writeText(text).catch((err) => console.error("Failed to copy:", err));
370
+ }
371
+ processMarkdownContent(markdownHtml) {
372
+ if (markdownHtml.includes("code-blocwrapper")) return markdownHtml;
373
+ return markdownHtml.replace(/<pre><code([^>]*)>([\s\S]*?)<\/code><\/pre>/gi, (_, attrs, codeText) => `
374
+ <div class="code-blocwrapper">
375
+ <div class="code-blocheader">
376
+ <wa-copy-button value="${this.escapeHtmlAttribute(codeText.trim())}" size="small" label="Copy code"></wa-copy-button>
377
+ </div>
378
+ <div class="code-bloccontent">
379
+ <pre><code${attrs}>${codeText}</code></pre>
380
+ </div>
381
+ </div>`);
382
+ }
383
+ escapeHtmlAttribute(text) {
384
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
385
+ }
386
+ handleResend(e) {
387
+ e?.preventDefault();
388
+ e?.stopPropagation();
389
+ if (!this.message) return;
390
+ this.dispatchEvent(new CustomEvent("resend", {
391
+ detail: { message: this.message, messageIndex: this.messageIndex },
392
+ bubbles: true,
393
+ composed: true
394
+ }));
395
+ }
396
+ render() {
397
+ if (!this.message) return html``;
398
+ const message = this.message;
399
+ const isUser = message.role === "user";
400
+ return html`
401
+ <div class="message-wrapper ${isUser ? "user" : "assistant"} ${this.isStreaming ? "streaming" : ""}">
402
+ ${when(this.showHeader && !isUser, () => html`
403
+ <div class="message-header">
404
+ <div class="message-meta">
405
+ <wa-icon name="robot" label="${message.role}"></wa-icon>
406
+ <span class="role-name">${message.role}</span>
407
+ </div>
408
+ <div class="message-actions">
409
+ <wa-button variant="neutral" appearance="plain" size="small" title="Copy"
410
+ @click="${() => this.copyToClipboard(message.content)}">
411
+ <wa-icon slot="label" name="copy" label="Copy"></wa-icon>
412
+ </wa-button>
413
+ </div>
414
+ </div>
415
+ `)}
416
+ <div class="message-content-wrapper ${isUser ? "user" : ""}">
417
+ <div class="message-content">
418
+ ${unsafeHTML(this.processMarkdownContent(marked.parse(message.content || "")))}
419
+ ${when(this.isStreaming, () => html`<span class="streaming-cursor">▋</span>`)}
420
+ </div>
421
+ ${when(isUser, () => html`
422
+ <wa-button variant="neutral" appearance="plain" size="small" title="Copy"
423
+ @click="${() => this.copyToClipboard(message.content)}">
424
+ <wa-icon name="copy" label="Copy"></wa-icon>
425
+ </wa-button>
426
+ <wa-button variant="neutral" appearance="plain" size="small" title="Resend"
427
+ @click="${(e) => this.handleResend(e)}">
428
+ <wa-icon name="rotate-right" label="Resend"></wa-icon>
429
+ </wa-button>
430
+ `)}
431
+ </div>
432
+ </div>
433
+ `;
434
+ }
435
+ };
436
+ AIChatMessage.styles = css`
437
+ :host {
438
+ display: flex;
439
+ flex-direction: column;
440
+ width: 100%;
441
+ max-width: 85%;
442
+ box-sizing: border-box;
443
+ animation: slideIn 0.2s ease-out;
444
+ }
445
+
446
+ :host([data-is-user="true"]) { align-self: flex-end; }
447
+ :host([data-is-user="false"]) { align-self: flex-start; }
448
+
449
+ @keyframes slideIn {
450
+ from { opacity: 0; transform: translateY(10px); }
451
+ to { opacity: 1; transform: translateY(0); }
452
+ }
453
+
454
+ .message-wrapper {
455
+ display: flex;
456
+ flex-direction: column;
457
+ gap: 0.5rem;
458
+ width: 100%;
459
+ box-sizing: border-box;
460
+ }
461
+
462
+ .message-header {
463
+ display: flex;
464
+ justify-content: space-between;
465
+ align-items: center;
466
+ gap: 0.5rem;
467
+ padding: 0 0.5rem;
468
+ }
469
+
470
+ .message-meta {
471
+ display: flex;
472
+ align-items: center;
473
+ gap: 0.5rem;
474
+ font-size: 0.875rem;
475
+ color: var(--wa-color-text-quiet);
476
+ }
477
+
478
+ .role-name { text-transform: capitalize; }
479
+
480
+ .message-actions {
481
+ display: flex;
482
+ gap: 0.25rem;
483
+ opacity: 0;
484
+ transition: opacity 0.2s;
485
+ }
486
+
487
+ .message-wrapper:hover .message-actions,
488
+ :host:hover .message-actions { opacity: 1; }
489
+
490
+ .message-content-wrapper {
491
+ display: flex;
492
+ align-items: flex-start;
493
+ gap: 0.5rem;
494
+ width: 100%;
495
+ }
496
+
497
+ .message-content-wrapper.user {
498
+ flex-direction: row;
499
+ align-items: center;
500
+ }
501
+
502
+ .message-content {
503
+ padding: 0.5rem 0.75rem;
504
+ border-radius: 0.25rem;
505
+ background-color: var(--wa-color-surface-default);
506
+ word-break: breaword;
507
+ overflow-wrap: breaword;
508
+ max-width: 100%;
509
+ box-sizing: border-box;
510
+ line-height: 1.3;
511
+ font-size: 0.9rem;
512
+ }
513
+
514
+ .message-content-wrapper.user .message-content {
515
+ padding: 0.0625rem 0.75rem;
516
+ background-color: var(--wa-color-brand-fill-quiet);
517
+ color: var(--wa-color-text-normal);
518
+ line-height: 1.4;
519
+ flex: 1;
520
+ }
521
+
522
+ .message-content p { margin: 0; padding: 0; }
523
+ .message-content ul, .message-content ol { margin: 0.25rem 0; padding-left: 1.25rem; }
524
+ .message-content li { margin: 0.125rem 0; padding: 0; line-height: 1.3; }
525
+ .message-content :first-child { margin-top: 0; padding-top: 0; }
526
+ .message-content :last-child { margin-bottom: 0; padding-bottom: 0; }
527
+
528
+ .message-content pre {
529
+ white-space: pre-wrap;
530
+ word-break: breaall;
531
+ max-width: 100%;
532
+ box-sizing: border-box;
533
+ overflow-x: auto;
534
+ margin: 0;
535
+ }
536
+
537
+ .message-content code {
538
+ font-family: 'Courier New', monospace;
539
+ background-color: var(--wa-color-surface-lowered);
540
+ padding: 0.125rem 0.25rem;
541
+ border-radius: 0.125rem;
542
+ }
543
+
544
+ .message-content pre code { background-color: transparent; padding: 0; display: block; }
545
+
546
+ .code-blocwrapper {
547
+ margin: 0.75rem 0;
548
+ border: solid var(--wa-border-width-s) var(--wa-color-neutral-border-loud);
549
+ border-radius: var(--wa-border-radius-m);
550
+ background-color: var(--wa-color-surface-lowered);
551
+ overflow: hidden;
552
+ }
553
+
554
+ .code-blocheader {
555
+ display: flex;
556
+ justify-content: flex-end;
557
+ align-items: center;
558
+ padding: 0.375rem 0.5rem;
559
+ border-bottom: solid var(--wa-border-width-s) var(--wa-color-neutral-border-loud);
560
+ background-color: var(--wa-color-surface-default);
561
+ }
562
+
563
+ .code-bloccontent { padding: 0.75rem; overflow-x: auto; }
564
+ .code-bloccontent pre { margin: 0; background-color: transparent; }
565
+ .code-bloccontent code { background-color: transparent; padding: 0; }
566
+
567
+ .streaming-cursor {
568
+ display: inline-block;
569
+ animation: blink 1s infinite;
570
+ color: var(--wa-color-brand-50);
571
+ }
572
+
573
+ @keyframes blink {
574
+ 0%, 50% { opacity: 1; }
575
+ 51%, 100% { opacity: 0; }
576
+ }
577
+ `;
578
+ __decorateClass$9([
579
+ property({ type: Object, attribute: false })
580
+ ], AIChatMessage.prototype, "message", 2);
581
+ __decorateClass$9([
582
+ property({ type: Boolean })
583
+ ], AIChatMessage.prototype, "isStreaming", 2);
584
+ __decorateClass$9([
585
+ property({ type: Boolean })
586
+ ], AIChatMessage.prototype, "showHeader", 2);
587
+ __decorateClass$9([
588
+ property({ type: Number, attribute: false })
589
+ ], AIChatMessage.prototype, "messageIndex", 2);
590
+ AIChatMessage = __decorateClass$9([
591
+ customElement("lyra-ai-chat-message")
592
+ ], AIChatMessage);
593
+ var __defProp$7 = Object.defineProperty;
594
+ var __getOwnPropDesc$8 = Object.getOwnPropertyDescriptor;
595
+ var __decorateClass$8 = (decorators, target, key, kind) => {
596
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$8(target, key) : target;
597
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
598
+ if (decorator = decorators[i])
599
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
600
+ if (kind && result) __defProp$7(target, key, result);
601
+ return result;
602
+ };
603
+ let AIChatInput = class extends LitElement {
604
+ constructor() {
605
+ super(...arguments);
606
+ this.value = "";
607
+ this.disabled = false;
608
+ this.busy = false;
609
+ this.hasProvider = true;
610
+ }
611
+ onInput(event) {
612
+ this.value = event.target.value;
613
+ this.dispatchEvent(new CustomEvent("input-change", { detail: { value: this.value }, bubbles: true, composed: true }));
614
+ }
615
+ onKeyDown(event) {
616
+ if (event.key === "Enter" && !event.shiftKey) {
617
+ event.preventDefault();
618
+ this.send();
619
+ }
620
+ }
621
+ async send() {
622
+ if (!this.value.trim() || this.disabled || !this.hasProvider) return;
623
+ const messageValue = this.value;
624
+ this.value = "";
625
+ this.requestUpdate();
626
+ await this.updateComplete;
627
+ if (this.textareaElement) {
628
+ this.textareaElement.value = "";
629
+ this.textareaElement.focus();
630
+ }
631
+ this.dispatchEvent(new CustomEvent("send", { detail: { value: messageValue }, bubbles: true, composed: true }));
632
+ }
633
+ cancel() {
634
+ this.dispatchEvent(new CustomEvent("cancel", { bubbles: true, composed: true }));
635
+ }
636
+ render() {
637
+ return html`
638
+ <div class="input-container">
639
+ <div class="input-row">
640
+ <wa-textarea
641
+ placeholder="Type a message... (Enter to send, Shift+Enter for new line)"
642
+ size="small"
643
+ resize="auto"
644
+ rows="1"
645
+ .value="${this.value}"
646
+ ?disabled="${this.disabled || !this.hasProvider}"
647
+ @input="${this.onInput}"
648
+ @keydown="${this.onKeyDown}">
649
+ </wa-textarea>
650
+ ${when(this.busy, () => html`
651
+ <wa-button appearance="plain" size="small" @click="${this.cancel}">
652
+ <wa-icon name="stop" label="Stop"></wa-icon>
653
+ </wa-button>
654
+ `)}
655
+ </div>
656
+ </div>
657
+ `;
658
+ }
659
+ };
660
+ AIChatInput.styles = css`
661
+ :host { display: block; width: 100%; }
662
+ .input-container { margin-bottom: 0.25rem; margin-left: 0.25rem; }
663
+ .input-row { display: flex; gap: 0.5rem; align-items: flex-end; }
664
+ wa-textarea { flex: 1; min-width: 0; }
665
+ `;
666
+ __decorateClass$8([
667
+ property({ type: String })
668
+ ], AIChatInput.prototype, "value", 2);
669
+ __decorateClass$8([
670
+ property({ type: Boolean })
671
+ ], AIChatInput.prototype, "disabled", 2);
672
+ __decorateClass$8([
673
+ property({ type: Boolean })
674
+ ], AIChatInput.prototype, "busy", 2);
675
+ __decorateClass$8([
676
+ property({ type: Boolean })
677
+ ], AIChatInput.prototype, "hasProvider", 2);
678
+ __decorateClass$8([
679
+ query("wa-textarea")
680
+ ], AIChatInput.prototype, "textareaElement", 2);
681
+ AIChatInput = __decorateClass$8([
682
+ customElement("lyra-ai-chat-input")
683
+ ], AIChatInput);
684
+ var __defProp$6 = Object.defineProperty;
685
+ var __getOwnPropDesc$7 = Object.getOwnPropertyDescriptor;
686
+ var __decorateClass$7 = (decorators, target, key, kind) => {
687
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$7(target, key) : target;
688
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
689
+ if (decorator = decorators[i])
690
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
691
+ if (kind && result) __defProp$6(target, key, result);
692
+ return result;
693
+ };
694
+ let AIAgentResponseGroup = class extends LitElement {
695
+ copyToClipboard(text) {
696
+ navigator.clipboard.writeText(text).catch((err) => console.error("Failed to copy:", err));
697
+ }
698
+ renderStatusIcon(status) {
699
+ switch (status) {
700
+ case "streaming":
701
+ return html`<wa-icon name="spinner" class="spinning"></wa-icon>`;
702
+ case "completed":
703
+ return html`<wa-icon name="check-circle" class="status-success"></wa-icon>`;
704
+ case "error":
705
+ return html`<wa-icon name="exclamation-circle" class="status-error"></wa-icon>`;
706
+ }
707
+ }
708
+ renderCard(agentInfo, message) {
709
+ if (!message) {
710
+ return html`
711
+ <div class="agent-card status-${agentInfo.status}">
712
+ <div class="agent-card-header">
713
+ <wa-icon name="${agentInfo.icon}" label="${agentInfo.label}"></wa-icon>
714
+ <span>${agentInfo.label}</span>
715
+ ${this.renderStatusIcon(agentInfo.status)}
716
+ </div>
717
+ <div class="agent-card-content waiting">Waiting for response...</div>
718
+ </div>
719
+ `;
720
+ }
721
+ return html`
722
+ <div class="agent-card status-${agentInfo.status}">
723
+ <div class="agent-card-header">
724
+ <wa-icon name="${agentInfo.icon}" label="${agentInfo.label}"></wa-icon>
725
+ <span>${agentInfo.label}</span>
726
+ ${this.renderStatusIcon(agentInfo.status)}
727
+ <div class="agent-card-actions">
728
+ <wa-button variant="neutral" appearance="plain" size="small" title="Copy"
729
+ @click="${() => this.copyToClipboard(message.content || "")}">
730
+ <wa-icon name="copy" label="Copy"></wa-icon>
731
+ </wa-button>
732
+ </div>
733
+ </div>
734
+ <div class="agent-card-content">
735
+ <lyra-ai-chat-message
736
+ .message="${message}"
737
+ .isStreaming="${agentInfo.status === "streaming"}"
738
+ .showHeader="${false}"
739
+ .messageIndex="${agentInfo.messageIndex}">
740
+ </lyra-ai-chat-message>
741
+ </div>
742
+ </div>
743
+ `;
744
+ }
745
+ render() {
746
+ if (!this.group) return html``;
747
+ const agents = Array.from(this.group.agents.values());
748
+ const completedCount = agents.filter((a) => a.status === "completed").length;
749
+ const streamingCount = agents.filter((a) => a.status === "streaming").length;
750
+ const errorCount = agents.filter((a) => a.status === "error").length;
751
+ const allDone = agents.length > 0 && completedCount + errorCount === agents.length;
752
+ const isSingle = agents.length === 1;
753
+ return html`
754
+ <div class="agent-response-group">
755
+ ${when(!isSingle, () => html`
756
+ <div class="group-header">
757
+ <wa-icon name="robot" label="Multiple Agents"></wa-icon>
758
+ <span>Multiple Agents</span>
759
+ <span class="status-badge">
760
+ ${when(streamingCount > 0, () => html`<span class="streaming">${streamingCount} responding</span>`)}
761
+ ${when(allDone, () => html`<span class="done">All completed (${completedCount})</span>`)}
762
+ </span>
763
+ </div>
764
+ `)}
765
+ <div class="group-content">
766
+ ${repeat(agents, (a) => a.role, (agentInfo) => {
767
+ const message = agentInfo.message || (agentInfo.status === "streaming" && this.findStreamingMessage ? this.findStreamingMessage(agentInfo.role) : void 0);
768
+ return this.renderCard(agentInfo, message);
769
+ })}
770
+ </div>
771
+ </div>
772
+ `;
773
+ }
774
+ };
775
+ AIAgentResponseGroup.styles = css`
776
+ :host { display: block; width: 100%; box-sizing: border-box; }
777
+
778
+ .agent-response-group {
779
+ display: flex;
780
+ flex-direction: column;
781
+ gap: 0.5rem;
782
+ width: 100%;
783
+ }
784
+
785
+ .group-header {
786
+ display: flex;
787
+ align-items: center;
788
+ gap: 0.5rem;
789
+ padding: 0.5rem 0.75rem;
790
+ background-color: var(--wa-color-surface-lowered);
791
+ border: solid var(--wa-border-width-s) var(--wa-color-surface-border);
792
+ font-weight: 500;
793
+ }
794
+
795
+ .status-badge {
796
+ display: flex;
797
+ gap: 0.5rem;
798
+ margin-left: auto;
799
+ font-size: 0.875rem;
800
+ }
801
+
802
+ .streaming { color: var(--wa-color-brand-50); }
803
+ .done { color: var(--wa-color-success-70); font-weight: 600; }
804
+
805
+ .group-content {
806
+ display: flex;
807
+ flex-direction: column;
808
+ gap: 0.5rem;
809
+ width: 100%;
810
+ }
811
+
812
+ .agent-card {
813
+ display: flex;
814
+ flex-direction: column;
815
+ border: solid var(--wa-border-width-s) var(--wa-color-surface-border);
816
+ background-color: var(--wa-color-surface-default);
817
+ }
818
+
819
+ .agent-card.status-streaming { border-color: var(--wa-color-brand-border-quiet); }
820
+ .agent-card.status-completed { border-color: var(--wa-color-success-border-quiet); }
821
+ .agent-card.status-error { border-color: var(--wa-color-danger-border-quiet); }
822
+
823
+ .agent-card-header {
824
+ display: flex;
825
+ align-items: center;
826
+ gap: 0.375rem;
827
+ padding: 0.375rem 0.5rem;
828
+ border-bottom: solid var(--wa-border-width-s) var(--wa-color-surface-border);
829
+ background-color: var(--wa-color-surface-lowered);
830
+ font-weight: 500;
831
+ font-size: 0.875rem;
832
+ }
833
+
834
+ .agent-card-actions { margin-left: auto; display: flex; gap: 0.25rem; }
835
+ .agent-card-content { padding: 0.375rem; }
836
+ .waiting { padding: 1rem; text-align: center; color: var(--wa-color-text-quiet); }
837
+
838
+ .spinning { animation: spin 1s linear infinite; }
839
+ .status-success { color: var(--wa-color-success-60); }
840
+ .status-error { color: var(--wa-color-danger-60); }
841
+
842
+ @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
843
+ `;
844
+ __decorateClass$7([
845
+ property({ type: Object, attribute: false })
846
+ ], AIAgentResponseGroup.prototype, "group", 2);
847
+ __decorateClass$7([
848
+ property({ type: Function, attribute: false })
849
+ ], AIAgentResponseGroup.prototype, "findStreamingMessage", 2);
850
+ AIAgentResponseGroup = __decorateClass$7([
851
+ customElement("lyra-ai-agent-response-group")
852
+ ], AIAgentResponseGroup);
853
+ var __defProp$5 = Object.defineProperty;
854
+ var __getOwnPropDesc$6 = Object.getOwnPropertyDescriptor;
855
+ var __decorateClass$6 = (decorators, target, key, kind) => {
856
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$6(target, key) : target;
857
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
858
+ if (decorator = decorators[i])
859
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
860
+ if (kind && result) __defProp$5(target, key, result);
861
+ return result;
862
+ };
863
+ let AIToolApproval = class extends LitElement {
864
+ constructor() {
865
+ super(...arguments);
866
+ this.pendingApprovals = /* @__PURE__ */ new Map();
867
+ }
868
+ approve(approvalId, approval) {
869
+ this.dispatchEvent(new CustomEvent("approve", {
870
+ detail: { approvalId, approval },
871
+ bubbles: true,
872
+ composed: true
873
+ }));
874
+ approval.resolve(true);
875
+ this.pendingApprovals.delete(approvalId);
876
+ this.requestUpdate();
877
+ }
878
+ deny(approvalId, approval) {
879
+ approval.resolve(false);
880
+ this.pendingApprovals.delete(approvalId);
881
+ this.requestUpdate();
882
+ }
883
+ formatArgs(argsStr) {
884
+ let parsed = {};
885
+ try {
886
+ parsed = JSON.parse(argsStr);
887
+ } catch {
888
+ }
889
+ return Object.entries(parsed).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(", ");
890
+ }
891
+ render() {
892
+ if (this.pendingApprovals.size === 0) return html``;
893
+ return html`
894
+ <div class="approval-container">
895
+ ${Array.from(this.pendingApprovals.entries()).map(([id, approval]) => {
896
+ const toolCalls = approval.request.toolCalls;
897
+ const first = toolCalls[0];
898
+ const summaryText = toolCalls.length === 1 ? `AI wants to execute: ${first?.function.name}()` : `AI wants to execute ${toolCalls.length} tools`;
899
+ return html`
900
+ <wa-details class="approval-item">
901
+ <span slot="summary" class="approval-summary">
902
+ <span>${summaryText}</span>
903
+ <div class="approval-inline-actions">
904
+ <wa-button appearance="plain" size="small" variant="neutral"
905
+ @click="${(e) => {
906
+ e.stopPropagation();
907
+ this.deny(id, approval);
908
+ }}">
909
+ <wa-icon name="xmark" label="Deny"></wa-icon>
910
+ </wa-button>
911
+ <wa-button appearance="plain" size="small" variant="success"
912
+ @click="${async (e) => {
913
+ e.stopPropagation();
914
+ this.approve(id, approval);
915
+ }}">
916
+ <wa-icon name="check" label="Approve"></wa-icon>
917
+ </wa-button>
918
+ </div>
919
+ </span>
920
+ <div class="approval-detail">
921
+ <strong>${approval.role} wants to execute:</strong>
922
+ <ul class="tool-list">
923
+ ${repeat(toolCalls, (tc) => tc.id, (tc) => {
924
+ const argsStr = this.formatArgs(tc.function.arguments || "{}");
925
+ const isSelected = approval.alwaysAllowSelections.get(tc.id) || false;
926
+ return html`
927
+ <li class="tool-item">
928
+ <label class="always-allow-label">
929
+ <wa-checkbox
930
+ ?checked="${isSelected}"
931
+ @change="${(e) => {
932
+ approval.alwaysAllowSelections.set(tc.id, e.target.checked);
933
+ this.requestUpdate();
934
+ }}">
935
+ </wa-checkbox>
936
+ <span>Always allow</span>
937
+ </label>
938
+ <code>${tc.function.name}(${argsStr})</code>
939
+ </li>
940
+ `;
941
+ })}
942
+ </ul>
943
+ </div>
944
+ </wa-details>
945
+ `;
946
+ })}
947
+ </div>
948
+ `;
949
+ }
950
+ };
951
+ AIToolApproval.styles = css`
952
+ :host { display: block; }
953
+
954
+ .approval-container {
955
+ display: flex;
956
+ flex-direction: column;
957
+ gap: 0.5rem;
958
+ padding: 0.75rem 1rem;
959
+ border-top: solid var(--wa-border-width-s) var(--wa-color-warning-border-normal);
960
+ background-color: var(--wa-color-warning-fill-quiet);
961
+ }
962
+
963
+ .approval-summary {
964
+ display: flex;
965
+ align-items: center;
966
+ justify-content: space-between;
967
+ gap: 1rem;
968
+ width: 100%;
969
+ }
970
+
971
+ .approval-inline-actions { display: flex; gap: 0.5rem; flex-shrink: 0; }
972
+
973
+ .approval-detail {
974
+ display: flex;
975
+ flex-direction: column;
976
+ gap: 0.5rem;
977
+ padding: 0.75rem 0;
978
+ font-size: 0.875rem;
979
+ }
980
+
981
+ .tool-list { margin: 0.5rem 0 0 1.5rem; padding: 0; list-style: disc; }
982
+
983
+ .tool-item {
984
+ display: flex;
985
+ align-items: center;
986
+ gap: 0.5rem;
987
+ margin: 0.5rem 0;
988
+ }
989
+
990
+ .always-allow-label {
991
+ display: flex;
992
+ align-items: center;
993
+ gap: 0.375rem;
994
+ cursor: pointer;
995
+ }
996
+
997
+ code {
998
+ font-family: var(--wa-font-mono);
999
+ font-size: 0.875rem;
1000
+ padding: 0.125rem 0.25rem;
1001
+ background-color: var(--wa-color-neutral-fill-subtle);
1002
+ border-radius: var(--wa-border-radius-s);
1003
+ }
1004
+ `;
1005
+ __decorateClass$6([
1006
+ property({ type: Map, attribute: false })
1007
+ ], AIToolApproval.prototype, "pendingApprovals", 2);
1008
+ AIToolApproval = __decorateClass$6([
1009
+ customElement("lyra-ai-tool-approval")
1010
+ ], AIToolApproval);
1011
+ var __defProp$4 = Object.defineProperty;
1012
+ var __getOwnPropDesc$5 = Object.getOwnPropertyDescriptor;
1013
+ var __decorateClass$5 = (decorators, target, key, kind) => {
1014
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$5(target, key) : target;
1015
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
1016
+ if (decorator = decorators[i])
1017
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
1018
+ if (kind && result) __defProp$4(target, key, result);
1019
+ return result;
1020
+ };
1021
+ let AIEmptyState = class extends LitElement {
1022
+ constructor() {
1023
+ super(...arguments);
1024
+ this.message = "No AI provider configured";
1025
+ this.hint = "Click the settings icon to configure an AI provider";
1026
+ }
1027
+ render() {
1028
+ return html`
1029
+ <div class="empty-state">
1030
+ <wa-icon name="robot" style="font-size: 3rem; opacity: 0.3;"></wa-icon>
1031
+ <p>${this.message}</p>
1032
+ <p class="hint">${this.hint}</p>
1033
+ </div>
1034
+ `;
1035
+ }
1036
+ };
1037
+ AIEmptyState.styles = css`
1038
+ :host {
1039
+ display: flex;
1040
+ align-items: center;
1041
+ justify-content: center;
1042
+ width: 100%;
1043
+ height: 100%;
1044
+ }
1045
+
1046
+ .empty-state {
1047
+ display: flex;
1048
+ flex-direction: column;
1049
+ align-items: center;
1050
+ justify-content: center;
1051
+ padding: 2rem;
1052
+ text-align: center;
1053
+ color: var(--wa-color-text-quiet);
1054
+ }
1055
+
1056
+ .empty-state p { margin: 0.5rem 0; }
1057
+ .hint { font-size: 0.875rem; opacity: 0.7; }
1058
+ `;
1059
+ __decorateClass$5([
1060
+ property({ type: String })
1061
+ ], AIEmptyState.prototype, "message", 2);
1062
+ __decorateClass$5([
1063
+ property({ type: String })
1064
+ ], AIEmptyState.prototype, "hint", 2);
1065
+ AIEmptyState = __decorateClass$5([
1066
+ customElement("lyra-ai-empty-state")
1067
+ ], AIEmptyState);
1068
+ var __defProp$3 = Object.defineProperty;
1069
+ var __getOwnPropDesc$4 = Object.getOwnPropertyDescriptor;
1070
+ var __decorateClass$4 = (decorators, target, key, kind) => {
1071
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$4(target, key) : target;
1072
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
1073
+ if (decorator = decorators[i])
1074
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
1075
+ if (kind && result) __defProp$3(target, key, result);
1076
+ return result;
1077
+ };
1078
+ const STATUS_ICON = {
1079
+ running: "spinner",
1080
+ completed: "check-circle",
1081
+ failed: "exclamation-circle",
1082
+ skipped: "forward",
1083
+ pending: "circle"
1084
+ };
1085
+ const STATUS_COLOR = {
1086
+ running: "var(--wa-color-brand-50)",
1087
+ completed: "var(--wa-color-success-60)",
1088
+ failed: "var(--wa-color-danger-60)",
1089
+ skipped: "var(--wa-color-neutral-40)",
1090
+ pending: "var(--wa-color-neutral-40)"
1091
+ };
1092
+ let AITaskProgressPanel = class extends LitElement {
1093
+ constructor() {
1094
+ super(...arguments);
1095
+ this.expanded = true;
1096
+ }
1097
+ render() {
1098
+ if (!this.plan) return html``;
1099
+ const completedCount = this.plan.steps.filter((s) => s.status === "completed").length;
1100
+ const totalCount = this.plan.steps.length;
1101
+ const progress = totalCount > 0 ? Math.round(completedCount / totalCount * 100) : 0;
1102
+ return html`
1103
+ <div class="taspanel">
1104
+ <div class="panel-header" @click="${() => {
1105
+ this.expanded = !this.expanded;
1106
+ }}">
1107
+ <wa-icon name="diagram-project" label="Task Plan"></wa-icon>
1108
+ <span class="panel-title">Task Plan</span>
1109
+ <span class="progress-text">${completedCount}/${totalCount}</span>
1110
+ <wa-progress-bar value="${progress}" class="progress-bar"></wa-progress-bar>
1111
+ <wa-icon name="${this.expanded ? "chevron-up" : "chevron-down"}" label="toggle"></wa-icon>
1112
+ </div>
1113
+ ${when(this.expanded, () => html`
1114
+ <div class="panel-body">
1115
+ ${repeat(this.plan.steps, (s) => s.id, (step) => html`
1116
+ <div class="step-row">
1117
+ <wa-icon
1118
+ name="${STATUS_ICON[step.status] ?? "circle"}"
1119
+ style="color: ${STATUS_COLOR[step.status] ?? "var(--wa-color-neutral-40)"}; ${step.status === "running" ? "animation: spin 1s linear infinite;" : ""}">
1120
+ </wa-icon>
1121
+ <div class="step-info">
1122
+ <span class="step-role">${step.role}</span>
1123
+ <span class="step-task">${step.subTask}</span>
1124
+ </div>
1125
+ ${when(step.revisions > 0, () => html`
1126
+ <span class="revisions-badge">${step.revisions} rev</span>
1127
+ `)}
1128
+ </div>
1129
+ `)}
1130
+ </div>
1131
+ `)}
1132
+ </div>
1133
+ `;
1134
+ }
1135
+ };
1136
+ AITaskProgressPanel.styles = css`
1137
+ :host { display: block; }
1138
+
1139
+ .taspanel {
1140
+ border: solid var(--wa-border-width-s) var(--wa-color-brand-border-quiet);
1141
+ border-radius: var(--wa-border-radius-m);
1142
+ background: var(--wa-color-surface-default);
1143
+ margin: 0.5rem 0;
1144
+ }
1145
+
1146
+ .panel-header {
1147
+ display: flex;
1148
+ align-items: center;
1149
+ gap: 0.5rem;
1150
+ padding: 0.5rem 0.75rem;
1151
+ cursor: pointer;
1152
+ user-select: none;
1153
+ }
1154
+
1155
+ .panel-title {
1156
+ font-weight: 500;
1157
+ flex: 0 0 auto;
1158
+ }
1159
+
1160
+ .progress-text {
1161
+ font-size: 0.8rem;
1162
+ color: var(--wa-color-text-quiet);
1163
+ }
1164
+
1165
+ .progress-bar {
1166
+ flex: 1;
1167
+ min-width: 60px;
1168
+ }
1169
+
1170
+ .panel-body {
1171
+ display: flex;
1172
+ flex-direction: column;
1173
+ gap: 0.25rem;
1174
+ padding: 0.5rem 0.75rem;
1175
+ border-top: solid var(--wa-border-width-s) var(--wa-color-neutral-border-subtle);
1176
+ }
1177
+
1178
+ .step-row {
1179
+ display: flex;
1180
+ align-items: flex-start;
1181
+ gap: 0.5rem;
1182
+ padding: 0.25rem 0;
1183
+ }
1184
+
1185
+ .step-info {
1186
+ display: flex;
1187
+ flex-direction: column;
1188
+ gap: 0.125rem;
1189
+ flex: 1;
1190
+ min-width: 0;
1191
+ }
1192
+
1193
+ .step-role {
1194
+ font-size: 0.75rem;
1195
+ font-weight: 600;
1196
+ color: var(--wa-color-text-quiet);
1197
+ text-transform: uppercase;
1198
+ }
1199
+
1200
+ .step-task {
1201
+ font-size: 0.85rem;
1202
+ white-space: nowrap;
1203
+ overflow: hidden;
1204
+ text-overflow: ellipsis;
1205
+ }
1206
+
1207
+ .revisions-badge {
1208
+ font-size: 0.7rem;
1209
+ padding: 0.1rem 0.3rem;
1210
+ background: var(--wa-color-warning-fill-quiet);
1211
+ border-radius: var(--wa-border-radius-s);
1212
+ color: var(--wa-color-warning-70);
1213
+ flex-shrink: 0;
1214
+ }
1215
+
1216
+ @keyframes spin {
1217
+ from { transform: rotate(0deg); }
1218
+ to { transform: rotate(360deg); }
1219
+ }
1220
+ `;
1221
+ __decorateClass$4([
1222
+ property({ type: Object, attribute: false })
1223
+ ], AITaskProgressPanel.prototype, "plan", 2);
1224
+ __decorateClass$4([
1225
+ state()
1226
+ ], AITaskProgressPanel.prototype, "expanded", 2);
1227
+ AITaskProgressPanel = __decorateClass$4([
1228
+ customElement("lyra-ai-task-progress-panel")
1229
+ ], AITaskProgressPanel);
1230
+ var __defProp$2 = Object.defineProperty;
1231
+ var __getOwnPropDesc$3 = Object.getOwnPropertyDescriptor;
1232
+ var __decorateClass$3 = (decorators, target, key, kind) => {
1233
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$3(target, key) : target;
1234
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
1235
+ if (decorator = decorators[i])
1236
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
1237
+ if (kind && result) __defProp$2(target, key, result);
1238
+ return result;
1239
+ };
1240
+ const ARTIFACT_TYPE_ICON = {
1241
+ code: "code",
1242
+ json: "brackets-curly",
1243
+ "file-list": "list",
1244
+ plan: "diagram-project",
1245
+ review: "magnifying-glass",
1246
+ text: "file-lines"
1247
+ };
1248
+ let AIWorkspacePanel = class extends LitElement {
1249
+ constructor() {
1250
+ super(...arguments);
1251
+ this.artifacts = [];
1252
+ this.expanded = false;
1253
+ }
1254
+ render() {
1255
+ if (this.artifacts.length === 0) return html``;
1256
+ return html`
1257
+ <div class="workspace-panel">
1258
+ <div class="panel-header" @click="${() => {
1259
+ this.expanded = !this.expanded;
1260
+ this.selectedArtifact = void 0;
1261
+ }}">
1262
+ <wa-icon name="folder-open" label="Workspace"></wa-icon>
1263
+ <span class="panel-title">Workspace</span>
1264
+ <span class="count-badge">${this.artifacts.length} artifact${this.artifacts.length !== 1 ? "s" : ""}</span>
1265
+ <wa-icon name="${this.expanded ? "chevron-up" : "chevron-down"}" label="toggle"></wa-icon>
1266
+ </div>
1267
+ ${when(this.expanded, () => html`
1268
+ <div class="panel-body">
1269
+ <div class="artifact-list">
1270
+ ${repeat(this.artifacts, (a) => a.id, (artifact) => html`
1271
+ <div
1272
+ class="artifact-item ${this.selectedArtifact?.id === artifact.id ? "selected" : ""}"
1273
+ @click="${() => {
1274
+ this.selectedArtifact = this.selectedArtifact?.id === artifact.id ? void 0 : artifact;
1275
+ }}">
1276
+ <wa-icon name="${ARTIFACT_TYPE_ICON[artifact.type] ?? "file-lines"}" label="${artifact.type}"></wa-icon>
1277
+ <div class="artifact-meta">
1278
+ <span class="artifact-id">${artifact.id}</span>
1279
+ <span class="artifact-producer">by ${artifact.producedBy}</span>
1280
+ </div>
1281
+ <span class="artifact-type">${artifact.type}</span>
1282
+ </div>
1283
+ ${when(this.selectedArtifact?.id === artifact.id, () => html`
1284
+ <div class="artifact-content">
1285
+ <pre>${artifact.content}</pre>
1286
+ </div>
1287
+ `)}
1288
+ `)}
1289
+ </div>
1290
+ </div>
1291
+ `)}
1292
+ </div>
1293
+ `;
1294
+ }
1295
+ };
1296
+ AIWorkspacePanel.styles = css`
1297
+ :host { display: block; }
1298
+
1299
+ .workspace-panel {
1300
+ border: solid var(--wa-border-width-s) var(--wa-color-neutral-border-subtle);
1301
+ border-radius: var(--wa-border-radius-m);
1302
+ background: var(--wa-color-surface-default);
1303
+ margin: 0.5rem 0;
1304
+ }
1305
+
1306
+ .panel-header {
1307
+ display: flex;
1308
+ align-items: center;
1309
+ gap: 0.5rem;
1310
+ padding: 0.5rem 0.75rem;
1311
+ cursor: pointer;
1312
+ user-select: none;
1313
+ }
1314
+
1315
+ .panel-title { font-weight: 500; }
1316
+
1317
+ .count-badge {
1318
+ font-size: 0.8rem;
1319
+ color: var(--wa-color-text-quiet);
1320
+ margin-left: auto;
1321
+ }
1322
+
1323
+ .panel-body {
1324
+ border-top: solid var(--wa-border-width-s) var(--wa-color-neutral-border-subtle);
1325
+ }
1326
+
1327
+ .artifact-list { display: flex; flex-direction: column; }
1328
+
1329
+ .artifact-item {
1330
+ display: flex;
1331
+ align-items: center;
1332
+ gap: 0.5rem;
1333
+ padding: 0.4rem 0.75rem;
1334
+ cursor: pointer;
1335
+ }
1336
+
1337
+ .artifact-item:hover { background: var(--wa-color-surface-lowered); }
1338
+ .artifact-item.selected { background: var(--wa-color-brand-fill-quiet); }
1339
+
1340
+ .artifact-meta {
1341
+ display: flex;
1342
+ flex-direction: column;
1343
+ flex: 1;
1344
+ min-width: 0;
1345
+ }
1346
+
1347
+ .artifact-id {
1348
+ font-size: 0.85rem;
1349
+ font-weight: 500;
1350
+ white-space: nowrap;
1351
+ overflow: hidden;
1352
+ text-overflow: ellipsis;
1353
+ }
1354
+
1355
+ .artifact-producer {
1356
+ font-size: 0.75rem;
1357
+ color: var(--wa-color-text-quiet);
1358
+ }
1359
+
1360
+ .artifact-type {
1361
+ font-size: 0.75rem;
1362
+ padding: 0.1rem 0.3rem;
1363
+ background: var(--wa-color-surface-lowered);
1364
+ border-radius: var(--wa-border-radius-s);
1365
+ }
1366
+
1367
+ .artifact-content {
1368
+ padding: 0.5rem 0.75rem;
1369
+ border-top: solid var(--wa-border-width-s) var(--wa-color-neutral-border-subtle);
1370
+ background: var(--wa-color-surface-lowered);
1371
+ }
1372
+
1373
+ .artifact-content pre {
1374
+ margin: 0;
1375
+ white-space: pre-wrap;
1376
+ word-break: breaword;
1377
+ font-size: 0.8rem;
1378
+ max-height: 200px;
1379
+ overflow-y: auto;
1380
+ }
1381
+ `;
1382
+ __decorateClass$3([
1383
+ property({ type: Array, attribute: false })
1384
+ ], AIWorkspacePanel.prototype, "artifacts", 2);
1385
+ __decorateClass$3([
1386
+ state()
1387
+ ], AIWorkspacePanel.prototype, "expanded", 2);
1388
+ __decorateClass$3([
1389
+ state()
1390
+ ], AIWorkspacePanel.prototype, "selectedArtifact", 2);
1391
+ AIWorkspacePanel = __decorateClass$3([
1392
+ customElement("lyra-ai-workspace-panel")
1393
+ ], AIWorkspacePanel);
1394
+ var __defProp$1 = Object.defineProperty;
1395
+ var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
1396
+ var __decorateClass$2 = (decorators, target, key, kind) => {
1397
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
1398
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
1399
+ if (decorator = decorators[i])
1400
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
1401
+ if (kind && result) __defProp$1(target, key, result);
1402
+ return result;
1403
+ };
1404
+ let LyraAIView = class extends LyraPart {
1405
+ constructor() {
1406
+ super(...arguments);
1407
+ this.sessionManager = new SessionManager();
1408
+ this.streamManager = new StreamManager(() => {
1409
+ this.requestUpdate();
1410
+ if (this.scrollDebounceTimer) clearTimeout(this.scrollDebounceTimer);
1411
+ this.scrollDebounceTimer = setTimeout(async () => {
1412
+ await this.updateComplete;
1413
+ this.scrollToBottom();
1414
+ this.scrollDebounceTimer = void 0;
1415
+ }, 100);
1416
+ });
1417
+ this.providerManager = new ProviderManager(aiService);
1418
+ this.agentGroupManager = new AgentGroupManager();
1419
+ this.busy = false;
1420
+ this.inputValue = "";
1421
+ this.requireToolApproval = true;
1422
+ this.showHistory = false;
1423
+ this.currentArtifacts = [];
1424
+ this.pendingToolApprovals = /* @__PURE__ */ new Map();
1425
+ this.toolApprovalAllowlist = /* @__PURE__ */ new Set();
1426
+ }
1427
+ async doBeforeUI() {
1428
+ this.subscribe(TOPIC_AICONFIG_CHANGED, () => this.onAIConfigChanged());
1429
+ await this.sessionManager.load();
1430
+ if (!this.sessionManager.getActiveSession()) {
1431
+ this.sessionManager.createSession();
1432
+ }
1433
+ await this.providerManager.initialize();
1434
+ await this.loadSettings();
1435
+ this.requestUpdate();
1436
+ }
1437
+ async onAIConfigChanged() {
1438
+ await this.providerManager.initialize();
1439
+ await this.loadSettings();
1440
+ this.requestUpdate();
1441
+ }
1442
+ async loadSettings() {
1443
+ const config = await appSettings.get(KEY_AI_CONFIG) || {};
1444
+ this.requireToolApproval = config.requireToolApproval !== false;
1445
+ const allowlist = await this.providerManager.loadToolApprovalAllowlist();
1446
+ this.toolApprovalAllowlist = new Set(allowlist);
1447
+ }
1448
+ async scrollToBottom() {
1449
+ await this.updateComplete;
1450
+ const scroller = this.shadowRoot?.querySelector("wa-scroller.chat-messages");
1451
+ if (!scroller) return;
1452
+ const container = scroller.shadowRoot?.querySelector(".scroll-container");
1453
+ if (container) {
1454
+ container.scrollTop = container.scrollHeight;
1455
+ } else if (scroller.scrollTo) {
1456
+ scroller.scrollTo({ top: scroller.scrollHeight, behavior: "smooth" });
1457
+ }
1458
+ }
1459
+ resetViewState() {
1460
+ this.inputValue = "";
1461
+ this.showHistory = false;
1462
+ this.currentTaskPlan = void 0;
1463
+ this.currentArtifacts = [];
1464
+ this.requestUpdate();
1465
+ this.updateToolbar();
1466
+ }
1467
+ createNewSession() {
1468
+ this.sessionManager.createSession();
1469
+ this.resetViewState();
1470
+ }
1471
+ switchToSession(sessionId) {
1472
+ if (!this.sessionManager.switchToSession(sessionId)) return;
1473
+ this.resetViewState();
1474
+ }
1475
+ deletePastSession(sessionId) {
1476
+ this.sessionManager.deletePastSession(sessionId);
1477
+ this.requestUpdate();
1478
+ this.updateToolbar();
1479
+ }
1480
+ async sendMessage() {
1481
+ const prompt = this.inputValue.trim();
1482
+ if (!prompt || this.busy) return;
1483
+ this.inputValue = "";
1484
+ await this.handlePrompt(prompt);
1485
+ }
1486
+ async handleResend(message) {
1487
+ if (!message || message.role !== "user") return;
1488
+ await this.handlePrompt(message.content);
1489
+ }
1490
+ cancelStream() {
1491
+ this.abortController?.abort();
1492
+ this.abortController = void 0;
1493
+ this.busy = false;
1494
+ this.streamManager.cancelUpdates();
1495
+ }
1496
+ async handlePrompt(prompt) {
1497
+ if (prompt.startsWith("/")) {
1498
+ await this.runCommand(prompt.substring(1));
1499
+ return;
1500
+ }
1501
+ const selectedProvider = this.providerManager.getSelectedProvider();
1502
+ if (!selectedProvider) {
1503
+ toastError("Please configure an AI provider in settings");
1504
+ return;
1505
+ }
1506
+ const session = this.sessionManager.getActiveSession();
1507
+ if (!session) return;
1508
+ const message = aiService.createMessage(prompt);
1509
+ this.sessionManager.addMessage(message);
1510
+ if (session.history.length === 1) {
1511
+ this.sessionManager.setTitle(this.sessionManager.generateTitle(prompt));
1512
+ this.updateToolbar();
1513
+ }
1514
+ this.requestUpdate();
1515
+ await this.updateComplete;
1516
+ this.scrollToBottom();
1517
+ this.busy = true;
1518
+ this.currentTaskPlan = void 0;
1519
+ this.currentArtifacts = [];
1520
+ this.abortController = new AbortController();
1521
+ const streamingAgents = /* @__PURE__ */ new Map();
1522
+ const chatContext = { history: [...session.history] };
1523
+ const sessionId = session.id;
1524
+ const execContext = commandRegistry.createExecutionContext();
1525
+ const callContext = uiContext.createChild({ ...execContext });
1526
+ const contributions = aiService.getAgentContributions();
1527
+ if (contributions.length === 0) {
1528
+ toastError("No agents are registered.");
1529
+ this.busy = false;
1530
+ return;
1531
+ }
1532
+ const matchingAgents = contributions.filter(
1533
+ (c) => !c.canHandle || c.canHandle({ ...callContext.getProxy(), userPrompt: prompt })
1534
+ ).sort((a, b) => (b.priority || 0) - (a.priority || 0));
1535
+ if (matchingAgents.length === 0) {
1536
+ toastError(`No agents available. Available: ${contributions.map((c) => c.role).join(", ")}`);
1537
+ this.busy = false;
1538
+ return;
1539
+ }
1540
+ const roles = matchingAgents.map((a) => a.role);
1541
+ const currentSession = this.sessionManager.getActiveSession();
1542
+ if (!currentSession) return;
1543
+ const groupId = this.agentGroupManager.createGroup(
1544
+ sessionId,
1545
+ currentSession.history.length - 1,
1546
+ message,
1547
+ roles,
1548
+ (role) => {
1549
+ const contrib = contributions.find((c) => c.role === role);
1550
+ return { label: contrib?.label || role, icon: contrib?.icon || "robot" };
1551
+ }
1552
+ );
1553
+ taskService.runAsync("Calling AI assistant", async () => {
1554
+ return aiService.executeAgentWorkflow({
1555
+ chatContext,
1556
+ chatConfig: selectedProvider,
1557
+ callContext,
1558
+ execution: "parallel",
1559
+ stream: true,
1560
+ signal: this.abortController.signal,
1561
+ roles,
1562
+ requireToolApproval: this.requireToolApproval,
1563
+ onToolApprovalRequest: async (role, request) => {
1564
+ const allAllowed = request.toolCalls.every((tc) => this.toolApprovalAllowlist.has(tc.function.name));
1565
+ if (allAllowed) return true;
1566
+ return new Promise((resolve) => {
1567
+ const approvalId = `approval-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
1568
+ const pending = {
1569
+ role,
1570
+ request,
1571
+ resolve,
1572
+ alwaysAllowSelections: /* @__PURE__ */ new Map()
1573
+ };
1574
+ this.pendingToolApprovals.set(approvalId, pending);
1575
+ this.requestUpdate();
1576
+ });
1577
+ },
1578
+ onAgentStart: async (role) => {
1579
+ const streamIndex = this.streamManager.createStreamingMessage(role);
1580
+ streamingAgents.set(role, streamIndex);
1581
+ this.agentGroupManager.updateAgentStatus(groupId, role, "streaming");
1582
+ this.requestUpdate();
1583
+ await this.updateComplete;
1584
+ this.scrollToBottom();
1585
+ },
1586
+ onToken: (role, token) => {
1587
+ const streamIndex = streamingAgents.get(role);
1588
+ if (streamIndex !== void 0) this.streamManager.updateStreamingMessage(streamIndex, token);
1589
+ },
1590
+ onAgentComplete: async (role, completedMessage) => {
1591
+ const targetSession = this.sessionManager.getActiveSession();
1592
+ if (!targetSession || targetSession.id !== sessionId) return;
1593
+ const streamIndex = streamingAgents.get(role);
1594
+ if (streamIndex !== void 0) {
1595
+ this.streamManager.completeStreamingMessage(streamIndex, completedMessage);
1596
+ const messageIndex = targetSession.history.length;
1597
+ this.sessionManager.addMessage(completedMessage);
1598
+ streamingAgents.delete(role);
1599
+ this.streamManager.removeStreamingMessage(streamIndex);
1600
+ this.agentGroupManager.updateAgentStatus(groupId, role, "completed", completedMessage, messageIndex);
1601
+ this.requestUpdate();
1602
+ await this.updateComplete;
1603
+ this.scrollToBottom();
1604
+ }
1605
+ },
1606
+ onAgentError: (role, error) => {
1607
+ const streamIndex = streamingAgents.get(role);
1608
+ if (streamIndex !== void 0) {
1609
+ this.streamManager.removeStreamingMessage(streamIndex);
1610
+ streamingAgents.delete(role);
1611
+ }
1612
+ this.agentGroupManager.updateAgentStatus(groupId, role, "error", { role, content: `Error: ${error.message}` });
1613
+ this.requestUpdate();
1614
+ toastError(`Agent ${role} error: ${error.message}`);
1615
+ }
1616
+ }).then(() => {
1617
+ this.agentGroupManager.clearCurrentGroup();
1618
+ });
1619
+ }).catch((error) => {
1620
+ if (error?.name !== "AbortError") toastError(`${error}`);
1621
+ }).finally(async () => {
1622
+ this.busy = false;
1623
+ this.abortController = void 0;
1624
+ this.streamManager.reset();
1625
+ this.agentGroupManager.clearCurrentGroup();
1626
+ this.requestUpdate();
1627
+ this.updateToolbar();
1628
+ });
1629
+ }
1630
+ async runCommand(prompt) {
1631
+ const splits = prompt.trim().split(/\s+/);
1632
+ if (splits.length === 0) return;
1633
+ const commandId = splits.shift();
1634
+ const command = commandRegistry.getCommand(commandId);
1635
+ if (!command) {
1636
+ toastError(`Command not found: ${commandId}`);
1637
+ return;
1638
+ }
1639
+ const params = {};
1640
+ splits.forEach((c, i) => {
1641
+ if (command.parameters?.[i]) params[command.parameters[i].name] = c;
1642
+ });
1643
+ await commandRegistry.execute(commandId, commandRegistry.createExecutionContext(params));
1644
+ this.requestUpdate();
1645
+ }
1646
+ handleToolApproval(e) {
1647
+ const { approvalId, approval } = e.detail;
1648
+ const alwaysAllowed = Array.from(approval.alwaysAllowSelections.entries()).filter(([, v]) => v).map(([k]) => k);
1649
+ alwaysAllowed.forEach((name) => this.toolApprovalAllowlist.add(name));
1650
+ this.pendingToolApprovals.delete(approvalId);
1651
+ this.requestUpdate();
1652
+ }
1653
+ renderMessage(message, index, isStreaming = false) {
1654
+ return html`
1655
+ <lyra-ai-chat-message
1656
+ .message="${message}"
1657
+ .isStreaming="${isStreaming}"
1658
+ .showHeader="${true}"
1659
+ .messageIndex="${index}"
1660
+ @resend="${(e) => this.handleResend(e.detail.message)}">
1661
+ </lyra-ai-chat-message>
1662
+ `;
1663
+ }
1664
+ renderToolbar() {
1665
+ const past = this.sessionManager.getPastSessions();
1666
+ const session = this.sessionManager.getActiveSession();
1667
+ return html`
1668
+ <span style="flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:0.875rem;font-weight:500;padding:0 0.25rem;">${session?.title || "New Chat"}</span>
1669
+ <wa-button appearance="plain" size="small" title="New chat"
1670
+ @click="${() => this.createNewSession()}">
1671
+ <wa-icon name="plus" label="New chat"></wa-icon>
1672
+ </wa-button>
1673
+ ${past.length > 0 ? html`
1674
+ <wa-dropdown
1675
+ ?open="${this.showHistory}"
1676
+ @wa-after-hide="${() => {
1677
+ this.showHistory = false;
1678
+ }}"
1679
+ placement="bottom-start">
1680
+ <wa-button slot="trigger" appearance="plain" size="small" with-caret
1681
+ title="Chat history"
1682
+ @click="${() => {
1683
+ this.showHistory = !this.showHistory;
1684
+ }}">
1685
+ <wa-icon name="clock-rotate-left" label="History"></wa-icon>
1686
+ </wa-button>
1687
+ ${past.map((s) => html`
1688
+ <wa-dropdown-item @click="${() => this.switchToSession(s.id)}">
1689
+ <wa-icon name="message" label="Session" slot="icon"></wa-icon>
1690
+ <span style="flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${s.title || "Unnamed Chat"}</span>
1691
+ <wa-button slot="details" appearance="plain" size="small" title="Delete"
1692
+ @click="${(e) => {
1693
+ e.stopPropagation();
1694
+ this.deletePastSession(s.id);
1695
+ }}">
1696
+ <wa-icon name="trash" label="Delete"></wa-icon>
1697
+ </wa-button>
1698
+ </wa-dropdown-item>
1699
+ `)}
1700
+ </wa-dropdown>
1701
+ ` : nothing}
1702
+ <lyra-command cmd="open_ai_config" icon="gear" title="AI Settings"></lyra-command>
1703
+ `;
1704
+ }
1705
+ render() {
1706
+ const session = this.sessionManager.getActiveSession();
1707
+ const selectedProvider = this.providerManager.getSelectedProvider();
1708
+ return html`
1709
+ <div class="chat-container">
1710
+ <wa-scroller class="chat-messages" orientation="vertical">
1711
+ <div class="chat-content">
1712
+ ${when(!selectedProvider, () => html`
1713
+ <lyra-ai-empty-state
1714
+ message="No AI provider configured"
1715
+ hint='Click the settings icon below to configure an AI provider'>
1716
+ </lyra-ai-empty-state>
1717
+ `, () => when(!session || session.history.length === 0, () => html`
1718
+ <lyra-ai-empty-state message="How can I help you?" hint=""></lyra-ai-empty-state>
1719
+ `, () => html`
1720
+ ${session.history.map((message, idx) => {
1721
+ const group = this.agentGroupManager.findGroupForUserMessage(session.id, idx, message);
1722
+ if (group && message.role === "user") {
1723
+ return html`
1724
+ <lyra-ai-chat-message
1725
+ .message="${message}"
1726
+ .isStreaming="${false}"
1727
+ .showHeader="${true}"
1728
+ .messageIndex="${idx}"
1729
+ @resend="${(e) => this.handleResend(e.detail.message)}">
1730
+ </lyra-ai-chat-message>
1731
+ <lyra-ai-agent-response-group
1732
+ .group="${group}"
1733
+ .findStreamingMessage="${(role) => this.streamManager.findStreamingMessage(role)}">
1734
+ </lyra-ai-agent-response-group>
1735
+ `;
1736
+ }
1737
+ if (this.agentGroupManager.findGroupForMessage(session.id, message.role, idx)) {
1738
+ return html``;
1739
+ }
1740
+ return this.renderMessage(message, idx);
1741
+ })}
1742
+
1743
+ ${this.streamManager.getAllStreamingMessages().filter((m) => !this.agentGroupManager.getAllGroups().some((g) => g.sessionId === session.id && g.agents.has(m.message.role))).map((m) => this.renderMessage(m.message, -1, m.isStreaming))}
1744
+
1745
+ ${when(this.busy && this.streamManager.getAllStreamingMessages().length === 0, () => html`
1746
+ <div class="thinking-indicator">
1747
+ <wa-progress-ring indeterminate size="small"></wa-progress-ring>
1748
+ <span>Thinking…</span>
1749
+ </div>
1750
+ `)}
1751
+ `))}
1752
+
1753
+ ${when(this.currentTaskPlan, () => html`
1754
+ <lyra-ai-task-progress-panel .plan="${this.currentTaskPlan}"></lyra-ai-task-progress-panel>
1755
+ `)}
1756
+
1757
+ ${when(this.currentArtifacts.length > 0, () => html`
1758
+ <lyra-ai-workspace-panel .artifacts="${this.currentArtifacts}"></lyra-ai-workspace-panel>
1759
+ `)}
1760
+ </div>
1761
+ </wa-scroller>
1762
+
1763
+ ${when(this.pendingToolApprovals.size > 0, () => html`
1764
+ <lyra-ai-tool-approval
1765
+ .pendingApprovals="${this.pendingToolApprovals}"
1766
+ @approve="${(e) => this.handleToolApproval(e)}">
1767
+ </lyra-ai-tool-approval>
1768
+ `)}
1769
+
1770
+ <div class="input-area">
1771
+ <lyra-ai-chat-input
1772
+ .value="${this.inputValue}"
1773
+ .busy="${this.busy}"
1774
+ .disabled="${!selectedProvider}"
1775
+ .hasProvider="${!!selectedProvider}"
1776
+ @input-change="${(e) => {
1777
+ this.inputValue = e.detail.value;
1778
+ }}"
1779
+ @send="${(e) => {
1780
+ this.inputValue = e.detail.value;
1781
+ this.sendMessage();
1782
+ }}"
1783
+ @cancel="${() => this.cancelStream()}">
1784
+ </lyra-ai-chat-input>
1785
+ </div>
1786
+ </div>
1787
+ `;
1788
+ }
1789
+ };
1790
+ LyraAIView.styles = css`
1791
+ :host {
1792
+ display: flex;
1793
+ flex-direction: column;
1794
+ height: 100%;
1795
+ overflow: hidden;
1796
+ background: var(--wa-color-surface-default);
1797
+ }
1798
+
1799
+ .chat-container {
1800
+ display: flex;
1801
+ flex-direction: column;
1802
+ height: 100%;
1803
+ overflow: hidden;
1804
+ }
1805
+
1806
+ .chat-messages {
1807
+ flex: 1;
1808
+ overflow: hidden;
1809
+ }
1810
+
1811
+ .chat-content {
1812
+ display: flex;
1813
+ flex-direction: column;
1814
+ gap: 0.75rem;
1815
+ padding: 1rem;
1816
+ min-height: 100%;
1817
+ box-sizing: border-box;
1818
+ }
1819
+
1820
+ .thinking-indicator {
1821
+ display: flex;
1822
+ align-items: center;
1823
+ gap: 0.5rem;
1824
+ padding: 0.5rem 0.75rem;
1825
+ color: var(--wa-color-text-quiet);
1826
+ font-size: 0.875rem;
1827
+ }
1828
+
1829
+ .input-area {
1830
+ padding: 0.5rem;
1831
+ border-top: solid var(--wa-border-width-s) var(--wa-color-neutral-border-subtle);
1832
+ flex-shrink: 0;
1833
+ }
1834
+ `;
1835
+ __decorateClass$2([
1836
+ state()
1837
+ ], LyraAIView.prototype, "busy", 2);
1838
+ __decorateClass$2([
1839
+ state()
1840
+ ], LyraAIView.prototype, "inputValue", 2);
1841
+ __decorateClass$2([
1842
+ state()
1843
+ ], LyraAIView.prototype, "requireToolApproval", 2);
1844
+ __decorateClass$2([
1845
+ state()
1846
+ ], LyraAIView.prototype, "showHistory", 2);
1847
+ __decorateClass$2([
1848
+ state()
1849
+ ], LyraAIView.prototype, "currentTaskPlan", 2);
1850
+ __decorateClass$2([
1851
+ state()
1852
+ ], LyraAIView.prototype, "currentArtifacts", 2);
1853
+ __decorateClass$2([
1854
+ state()
1855
+ ], LyraAIView.prototype, "pendingToolApprovals", 2);
1856
+ LyraAIView = __decorateClass$2([
1857
+ customElement("lyra-aiview")
1858
+ ], LyraAIView);
1859
+ var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
1860
+ var __decorateClass$1 = (decorators, target, key, kind) => {
1861
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
1862
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
1863
+ if (decorator = decorators[i])
1864
+ result = decorator(result) || result;
1865
+ return result;
1866
+ };
1867
+ let LyraTokenUsage = class extends LyraElement {
1868
+ constructor() {
1869
+ super(...arguments);
1870
+ this.totalUsage = { ...EMPTY_USAGE };
1871
+ this.providerUsage = {};
1872
+ }
1873
+ connectedCallback() {
1874
+ super.connectedCallback();
1875
+ this.loadUsage();
1876
+ subscribe(TOPIC_AI_STREAM_COMPLETE, () => {
1877
+ this.loadUsage();
1878
+ });
1879
+ }
1880
+ async loadUsage() {
1881
+ this.totalUsage = await tokenUsageTracker.getTotalUsage();
1882
+ this.providerUsage = await tokenUsageTracker.getAllProviderUsage();
1883
+ this.requestUpdate();
1884
+ }
1885
+ formatNumber(num) {
1886
+ if (num >= 1e6) return (num / 1e6).toFixed(2) + "M";
1887
+ if (num >= 1e3) return (num / 1e3).toFixed(1) + "K";
1888
+ return num.toString();
1889
+ }
1890
+ async handleReset() {
1891
+ if (await confirmDialog("Reset all token usage statistics?")) {
1892
+ await tokenUsageTracker.reset();
1893
+ await this.loadUsage();
1894
+ }
1895
+ }
1896
+ renderStatItem(label, value) {
1897
+ return html`
1898
+ <div class="stat-item">
1899
+ <span class="stat-label">${label}</span>
1900
+ <span class="stat-value">${this.formatNumber(value)}</span>
1901
+ </div>
1902
+ `;
1903
+ }
1904
+ render() {
1905
+ if (this.totalUsage.totalTokens === 0) return html``;
1906
+ return html`
1907
+ <wa-dropdown placement="top-end" distance="8">
1908
+ <wa-button slot="trigger" appearance="plain" size="small" title="Token usage">
1909
+ <wa-icon name="database" label="Tokens" slot="start"></wa-icon>
1910
+ ${this.formatNumber(this.totalUsage.totalTokens)} tokens
1911
+ </wa-button>
1912
+
1913
+ <h3>Token Usage</h3>
1914
+
1915
+ <h6>Total</h6>
1916
+ <wa-dropdown-item>
1917
+ <span>All providers</span>
1918
+ <div class="stats-row">
1919
+ ${this.renderStatItem("Prompt", this.totalUsage.promptTokens)}
1920
+ ${this.renderStatItem("Completion", this.totalUsage.completionTokens)}
1921
+ ${this.renderStatItem("Total", this.totalUsage.totalTokens)}
1922
+ ${this.renderStatItem("Requests", this.totalUsage.requestCount)}
1923
+ </div>
1924
+ </wa-dropdown-item>
1925
+
1926
+ ${Object.keys(this.providerUsage).length > 0 ? html`
1927
+ <wa-divider></wa-divider>
1928
+ <h6>By Provider</h6>
1929
+ ${Object.entries(this.providerUsage).map(([name, usage]) => html`
1930
+ <wa-dropdown-item>
1931
+ <span class="provider-name">${name}</span>
1932
+ <div class="stats-row">
1933
+ ${this.renderStatItem("Prompt", usage.promptTokens)}
1934
+ ${this.renderStatItem("Completion", usage.completionTokens)}
1935
+ ${this.renderStatItem("Total", usage.totalTokens)}
1936
+ ${this.renderStatItem("Req", usage.requestCount)}
1937
+ </div>
1938
+ </wa-dropdown-item>
1939
+ `)}
1940
+ ` : ""}
1941
+
1942
+ <wa-divider></wa-divider>
1943
+ <wa-dropdown-item variant="danger" @click="${() => this.handleReset()}">
1944
+ <wa-icon name="trash" slot="icon"></wa-icon>
1945
+ Reset statistics
1946
+ </wa-dropdown-item>
1947
+ </wa-dropdown>
1948
+ `;
1949
+ }
1950
+ };
1951
+ LyraTokenUsage.styles = css`
1952
+ :host { display: inline-block; }
1953
+
1954
+ wa-dropdown::part(menu) { min-width: 320px; max-width: 420px; }
1955
+
1956
+ h3 {
1957
+ padding: var(--wa-space-s) var(--wa-space-m);
1958
+ margin: 0;
1959
+ font-weight: 600;
1960
+ font-size: 0.95em;
1961
+ }
1962
+
1963
+ h6 {
1964
+ padding: var(--wa-space-xs) var(--wa-space-m);
1965
+ margin: 0;
1966
+ font-weight: 600;
1967
+ font-size: 0.85em;
1968
+ color: var(--wa-color-neutral-text-subtle);
1969
+ text-transform: uppercase;
1970
+ letter-spacing: 0.05em;
1971
+ }
1972
+
1973
+ .provider-name { font-weight: 500; }
1974
+
1975
+ .stats-row { display: flex; gap: var(--wa-space-m); font-size: 0.875rem; }
1976
+
1977
+ .stat-item {
1978
+ display: flex;
1979
+ flex-direction: column;
1980
+ align-items: flex-end;
1981
+ }
1982
+
1983
+ .stat-label { font-size: 0.8em; color: var(--wa-color-neutral-text-subtle); }
1984
+ .stat-value { font-weight: 600; }
1985
+ `;
1986
+ LyraTokenUsage = __decorateClass$1([
1987
+ customElement("lyra-token-usage")
1988
+ ], LyraTokenUsage);
1989
+ var __defProp = Object.defineProperty;
1990
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
1991
+ var __decorateClass = (decorators, target, key, kind) => {
1992
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
1993
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
1994
+ if (decorator = decorators[i])
1995
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
1996
+ if (kind && result) __defProp(target, key, result);
1997
+ return result;
1998
+ };
1999
+ let LyraAIConfigEditor = class extends LyraPart {
2000
+ constructor() {
2001
+ super(...arguments);
2002
+ this.providers = [];
2003
+ this.defaultProvider = "";
2004
+ this.hasChanges = false;
2005
+ this.availableModels = [];
2006
+ this.loadingModels = false;
2007
+ this.requireToolApproval = true;
2008
+ this.smartToolDetection = false;
2009
+ this.editingState = {};
2010
+ this.providerFactory = new ProviderFactory();
2011
+ }
2012
+ async doInitUI() {
2013
+ await this.loadConfig();
2014
+ subscribe(TOPIC_AICONFIG_CHANGED, () => this.loadConfig());
2015
+ subscribe(TOPIC_SETTINGS_CHANGED, () => this.loadConfig());
2016
+ }
2017
+ async loadConfig() {
2018
+ const config = await appSettings.get(KEY_AI_CONFIG);
2019
+ this.aiConfig = config;
2020
+ const contributed = contributionRegistry.getContributions(CID_CHAT_PROVIDERS).map((c) => c.provider);
2021
+ const configProviders = config?.providers || [];
2022
+ const existingNames = new Set(configProviders.map((p) => p.name));
2023
+ this.providers = [...configProviders, ...contributed.filter((p) => !existingNames.has(p.name))];
2024
+ this.defaultProvider = config?.defaultProvider || "";
2025
+ this.requireToolApproval = config?.requireToolApproval !== false;
2026
+ this.smartToolDetection = config?.smartToolDetection !== void 0 ? config.smartToolDetection : false;
2027
+ this.editingState = {};
2028
+ this.hasChanges = false;
2029
+ this.markDirty(false);
2030
+ }
2031
+ getEditValue(index, field) {
2032
+ const editing = this.editingState[index];
2033
+ if (editing && field in editing) return editing[field] ?? "";
2034
+ const provider = this.providers[index];
2035
+ return provider ? provider[field] ?? "" : "";
2036
+ }
2037
+ setEditValue(index, field, value) {
2038
+ this.editingState = {
2039
+ ...this.editingState,
2040
+ [index]: { ...this.editingState[index] || {}, [field]: value }
2041
+ };
2042
+ this.providers = this.providers.map(
2043
+ (p, i) => i === index ? { ...p, [field]: value } : p
2044
+ );
2045
+ this.markDirtyAndUpdate();
2046
+ }
2047
+ markDirtyAndUpdate() {
2048
+ this.hasChanges = true;
2049
+ this.markDirty(true);
2050
+ }
2051
+ async fetchModels(index) {
2052
+ const provider = this.providers[index];
2053
+ if (!provider) return;
2054
+ this.loadingModels = true;
2055
+ this.availableModels = [];
2056
+ try {
2057
+ const instance = this.providerFactory.getProvider(provider);
2058
+ if (instance.getAvailableModels) {
2059
+ const models = await instance.getAvailableModels(provider);
2060
+ this.availableModels = Array.isArray(models) ? models : [];
2061
+ }
2062
+ } finally {
2063
+ this.loadingModels = false;
2064
+ }
2065
+ }
2066
+ async saveConfig() {
2067
+ const updatedConfig = {
2068
+ ...this.aiConfig ?? {},
2069
+ defaultProvider: this.defaultProvider,
2070
+ providers: this.providers,
2071
+ requireToolApproval: this.requireToolApproval,
2072
+ smartToolDetection: this.smartToolDetection
2073
+ };
2074
+ await appSettings.set(KEY_AI_CONFIG, updatedConfig);
2075
+ this.aiConfig = updatedConfig;
2076
+ this.hasChanges = false;
2077
+ this.markDirty(false);
2078
+ }
2079
+ async save() {
2080
+ if (!this.hasChanges) return;
2081
+ await this.saveConfig();
2082
+ }
2083
+ addProvider() {
2084
+ this.providers = [...this.providers, { name: "new-provider", model: "", apiKey: "", chatApiEndpoint: "" }];
2085
+ this.markDirtyAndUpdate();
2086
+ }
2087
+ async deleteProvider(index) {
2088
+ const provider = this.providers[index];
2089
+ if (!await confirmDialog(`Delete provider "${provider.name}"?`)) return;
2090
+ if (this.defaultProvider === provider.name) this.defaultProvider = "";
2091
+ this.providers = this.providers.filter((_, i) => i !== index);
2092
+ this.markDirtyAndUpdate();
2093
+ }
2094
+ renderProviderField(index, field, type = "text") {
2095
+ const value = this.getEditValue(index, field);
2096
+ return html`
2097
+ <wa-input
2098
+ type="${type}"
2099
+ ?password-toggle="${type === "password"}"
2100
+ .value="${value}"
2101
+ @input="${(e) => this.setEditValue(index, field, e.target.value)}">
2102
+ </wa-input>
2103
+ `;
2104
+ }
2105
+ render() {
2106
+ return html`
2107
+ <div class="editor">
2108
+ <div class="editor-header">
2109
+ <h2>AI Providers</h2>
2110
+ <wa-button variant="brand" appearance="filled" @click="${this.addProvider}">
2111
+ Add Provider
2112
+ </wa-button>
2113
+ </div>
2114
+
2115
+ ${when(this.providers.length === 0, () => html`
2116
+ <div class="empty-state"><p>No providers configured.</p></div>
2117
+ `, () => html`
2118
+ <div class="providers-list">
2119
+ ${repeat(this.providers, (_, i) => i, (provider, index) => html`
2120
+ <div class="provider-card">
2121
+ <div class="provider-card-header ${this.defaultProvider === provider.name ? "is-default" : ""}">
2122
+ <span class="provider-name">${provider.name}</span>
2123
+ ${this.defaultProvider === provider.name ? html`<span class="default-badge">Default</span>` : html`<wa-button appearance="plain" size="small" title="Set as default"
2124
+ @click="${() => {
2125
+ this.defaultProvider = provider.name;
2126
+ this.markDirtyAndUpdate();
2127
+ }}">
2128
+ Set default
2129
+ </wa-button>`}
2130
+ <wa-button variant="danger" appearance="plain" size="small"
2131
+ @click="${() => this.deleteProvider(index)}">
2132
+ Delete
2133
+ </wa-button>
2134
+ </div>
2135
+ <div class="provider-fields">
2136
+ <div class="field-row">
2137
+ <label>Name</label>
2138
+ ${this.renderProviderField(index, "name")}
2139
+ </div>
2140
+ <div class="field-row">
2141
+ <label>Model</label>
2142
+ <div class="model-row">
2143
+ ${this.renderProviderField(index, "model")}
2144
+ <wa-button appearance="plain" size="small"
2145
+ @click="${async () => {
2146
+ await this.fetchModels(index);
2147
+ }}"
2148
+ title="Fetch available models">
2149
+ <wa-icon name="refresh" label="Refresh"></wa-icon>
2150
+ </wa-button>
2151
+ </div>
2152
+ ${when(this.loadingModels, () => html`
2153
+ <wa-progress-ring indeterminate size="small"></wa-progress-ring>
2154
+ `)}
2155
+ ${when(this.availableModels.length > 0, () => html`
2156
+ <wa-dropdown
2157
+ @wa-select="${(e) => {
2158
+ if (e.detail.item?.value) this.setEditValue(index, "model", e.detail.item.value);
2159
+ }}">
2160
+ <wa-button slot="trigger" size="small" appearance="plain" with-caret>
2161
+ Select model
2162
+ </wa-button>
2163
+ ${this.availableModels.map((m) => html`
2164
+ <wa-dropdown-item value="${m.id}">${m.name || m.id}</wa-dropdown-item>
2165
+ `)}
2166
+ </wa-dropdown>
2167
+ `)}
2168
+ </div>
2169
+ <div class="field-row">
2170
+ <label>API Endpoint</label>
2171
+ ${this.renderProviderField(index, "chatApiEndpoint")}
2172
+ </div>
2173
+ <div class="field-row">
2174
+ <label>API Key</label>
2175
+ ${this.renderProviderField(index, "apiKey", "password")}
2176
+ </div>
2177
+ </div>
2178
+ </div>
2179
+ `)}
2180
+ </div>
2181
+ `)}
2182
+
2183
+ <div class="settings-section">
2184
+ <h3>Tool Settings</h3>
2185
+ <wa-checkbox
2186
+ ?checked="${this.requireToolApproval}"
2187
+ @change="${(e) => {
2188
+ this.requireToolApproval = e.target.checked;
2189
+ this.markDirtyAndUpdate();
2190
+ }}">
2191
+ Require approval before executing tools
2192
+ </wa-checkbox>
2193
+ <wa-checkbox
2194
+ ?checked="${this.smartToolDetection}"
2195
+ @change="${(e) => {
2196
+ this.smartToolDetection = e.target.checked;
2197
+ this.markDirtyAndUpdate();
2198
+ }}">
2199
+ Smart tool detection (use ML to detect when tools are needed)
2200
+ </wa-checkbox>
2201
+ </div>
2202
+ </div>
2203
+ `;
2204
+ }
2205
+ };
2206
+ LyraAIConfigEditor.styles = css`
2207
+ :host { display: block; height: 100%; overflow: auto; }
2208
+
2209
+ .editor {
2210
+ display: flex;
2211
+ flex-direction: column;
2212
+ gap: 1.5rem;
2213
+ padding: 1rem;
2214
+ }
2215
+
2216
+ .editor-header {
2217
+ display: flex;
2218
+ justify-content: space-between;
2219
+ align-items: center;
2220
+ }
2221
+
2222
+ .editor-header h2 { margin: 0; font-size: 1.25rem; }
2223
+
2224
+ .providers-list { display: flex; flex-direction: column; gap: 1rem; }
2225
+
2226
+ .provider-card {
2227
+ border: solid var(--wa-border-width-s) var(--wa-color-neutral-border-loud);
2228
+ border-radius: var(--wa-border-radius-m);
2229
+ overflow: hidden;
2230
+ }
2231
+
2232
+ .provider-card-header {
2233
+ display: flex;
2234
+ align-items: center;
2235
+ gap: 0.75rem;
2236
+ padding: 0.5rem 0.75rem;
2237
+ background: var(--wa-color-surface-lowered);
2238
+ border-bottom: solid var(--wa-border-width-s) var(--wa-color-neutral-border-subtle);
2239
+ }
2240
+
2241
+ .provider-card-header.is-default {
2242
+ background: var(--wa-color-brand-fill-quiet);
2243
+ border-bottom-color: var(--wa-color-brand-border-quiet);
2244
+ }
2245
+
2246
+ .default-badge {
2247
+ font-size: 0.75rem;
2248
+ font-weight: 600;
2249
+ padding: 0.1rem 0.4rem;
2250
+ background: var(--wa-color-brand-fill-loud);
2251
+ color: var(--wa-color-brand-on-loud);
2252
+ border-radius: var(--wa-border-radius-s);
2253
+ text-transform: uppercase;
2254
+ letter-spacing: 0.04em;
2255
+ }
2256
+
2257
+ .provider-name {
2258
+ font-weight: 500;
2259
+ flex: 1;
2260
+ }
2261
+
2262
+ .provider-fields {
2263
+ display: flex;
2264
+ flex-direction: column;
2265
+ gap: 0.75rem;
2266
+ padding: 0.75rem;
2267
+ }
2268
+
2269
+ .field-row {
2270
+ display: grid;
2271
+ grid-template-columns: 120px 1fr;
2272
+ align-items: start;
2273
+ gap: 0.5rem;
2274
+ }
2275
+
2276
+ .field-row label {
2277
+ font-size: 0.875rem;
2278
+ color: var(--wa-color-text-quiet);
2279
+ padding-top: 0.4rem;
2280
+ }
2281
+
2282
+ .model-row { display: flex; gap: 0.25rem; align-items: center; }
2283
+ .model-row wa-input { flex: 1; }
2284
+
2285
+ .settings-section {
2286
+ display: flex;
2287
+ flex-direction: column;
2288
+ gap: 0.75rem;
2289
+ padding-top: 1rem;
2290
+ border-top: solid var(--wa-border-width-s) var(--wa-color-neutral-border-subtle);
2291
+ }
2292
+
2293
+ .settings-section h3 { margin: 0 0 0.5rem 0; font-size: 1rem; }
2294
+
2295
+ .empty-state {
2296
+ display: flex;
2297
+ justify-content: center;
2298
+ padding: 3rem;
2299
+ color: var(--wa-color-text-subtle);
2300
+ }
2301
+ `;
2302
+ __decorateClass([
2303
+ property({ attribute: false })
2304
+ ], LyraAIConfigEditor.prototype, "input", 2);
2305
+ __decorateClass([
2306
+ state()
2307
+ ], LyraAIConfigEditor.prototype, "providers", 2);
2308
+ __decorateClass([
2309
+ state()
2310
+ ], LyraAIConfigEditor.prototype, "defaultProvider", 2);
2311
+ __decorateClass([
2312
+ state()
2313
+ ], LyraAIConfigEditor.prototype, "hasChanges", 2);
2314
+ __decorateClass([
2315
+ state()
2316
+ ], LyraAIConfigEditor.prototype, "availableModels", 2);
2317
+ __decorateClass([
2318
+ state()
2319
+ ], LyraAIConfigEditor.prototype, "loadingModels", 2);
2320
+ __decorateClass([
2321
+ state()
2322
+ ], LyraAIConfigEditor.prototype, "requireToolApproval", 2);
2323
+ __decorateClass([
2324
+ state()
2325
+ ], LyraAIConfigEditor.prototype, "smartToolDetection", 2);
2326
+ __decorateClass([
2327
+ state()
2328
+ ], LyraAIConfigEditor.prototype, "editingState", 2);
2329
+ LyraAIConfigEditor = __decorateClass([
2330
+ customElement("lyra-ai-config-editor")
2331
+ ], LyraAIConfigEditor);
2332
+ contributionRegistry.registerContribution(SIDEBAR_AUXILIARY, {
2333
+ name: "aiview",
2334
+ label: "AI Assistant",
2335
+ icon: "robot",
2336
+ component: (id) => html`<lyra-aiview id="${id}"></lyra-aiview>`
2337
+ });
2338
+ contributionRegistry.registerContribution(CID_AGENTS, {
2339
+ label: "App Support",
2340
+ description: "General-purpose assistant that can answer questions and execute app commands",
2341
+ role: "appsupport",
2342
+ priority: 100,
2343
+ icon: "question-circle",
2344
+ sysPrompt: GENERAL_SYS_PROMPT,
2345
+ tools: async () => {
2346
+ const config = await appSettings.get(KEY_AI_CONFIG);
2347
+ return {
2348
+ enabled: true,
2349
+ smartToolDetection: config?.smartToolDetection ?? false
2350
+ };
2351
+ }
2352
+ });
2353
+ contributionRegistry.registerContribution(TOOLBAR_BOTTOM, {
2354
+ target: TOOLBAR_BOTTOM,
2355
+ label: "Token Usage",
2356
+ html: "<lyra-token-usage></lyra-token-usage>"
2357
+ });
2358
+ editorRegistry.registerEditorInputHandler({
2359
+ editorId: "system.ai-config-editor",
2360
+ label: "AI Config",
2361
+ ranking: 1e3,
2362
+ canHandle: (input) => input.key === ".system.ai-config",
2363
+ handle: async (input) => {
2364
+ input.widgetFactory = () => html`<lyra-ai-config-editor .input="${input}"></lyra-ai-config-editor>`;
2365
+ return input;
2366
+ }
2367
+ });
2368
+ registerAll({
2369
+ command: {
2370
+ id: "open_ai_config",
2371
+ name: "Open AI Configuration",
2372
+ description: "Open the AI system configuration editor",
2373
+ parameters: []
2374
+ },
2375
+ handler: {
2376
+ execute: (_context) => {
2377
+ const editorInput = {
2378
+ title: "AI Settings",
2379
+ data: {},
2380
+ key: ".system.ai-config",
2381
+ icon: "robot",
2382
+ state: {}
2383
+ };
2384
+ editorRegistry.loadEditor(editorInput).then();
2385
+ }
2386
+ },
2387
+ contribution: {
2388
+ target: TOOLBAR_MAIN_RIGHT,
2389
+ icon: "robot",
2390
+ label: "AI Config"
2391
+ }
2392
+ });
2393
+ rootContext.put("aiService", aiService);
2394
+ //# sourceMappingURL=ai-system-extension-CPLV13Lk.js.map