@demicodes/web-ui 0.3.1

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 (114) hide show
  1. package/package.json +55 -0
  2. package/src/agent/AgentMessageInput.vue +164 -0
  3. package/src/agent/AgentMessageList.vue +124 -0
  4. package/src/agent/AgentPanel.vue +18 -0
  5. package/src/agent/AgentRoot.vue +17 -0
  6. package/src/agent/AgentTabBar.vue +422 -0
  7. package/src/agent/AgentTabItem.vue +150 -0
  8. package/src/agent/ContextUsageIndicator.vue +136 -0
  9. package/src/agent/ConversationListDropdown.vue +71 -0
  10. package/src/agent/ConversationStatusDot.vue +26 -0
  11. package/src/agent/ConversationView.vue +82 -0
  12. package/src/agent/MessageQueueBar.vue +67 -0
  13. package/src/agent/ModelSelector.vue +125 -0
  14. package/src/agent/ReasoningSelector.vue +106 -0
  15. package/src/agent/SelectorTrigger.vue +17 -0
  16. package/src/agent/__tests__/block-helpers.test.ts +73 -0
  17. package/src/agent/__tests__/input-actions.test.ts +130 -0
  18. package/src/agent/__tests__/input-editor.test.ts +15 -0
  19. package/src/agent/__tests__/pending-steers.test.ts +99 -0
  20. package/src/agent/__tests__/queue-submit.test.ts +14 -0
  21. package/src/agent/__tests__/reasoning.test.ts +32 -0
  22. package/src/agent/__tests__/tail-loading.test.ts +131 -0
  23. package/src/agent/__tests__/tool-rendering.test.ts +35 -0
  24. package/src/agent/__tests__/visible-blocks.test.ts +125 -0
  25. package/src/agent/block-helpers.ts +59 -0
  26. package/src/agent/block-types.ts +12 -0
  27. package/src/agent/blocks/AbortedBlock.vue +26 -0
  28. package/src/agent/blocks/AgentMessageVirtualBlock.vue +88 -0
  29. package/src/agent/blocks/AnsiText.vue +75 -0
  30. package/src/agent/blocks/AssistantTextBlock.vue +34 -0
  31. package/src/agent/blocks/CompactionBlock.vue +36 -0
  32. package/src/agent/blocks/ErrorBlock.vue +79 -0
  33. package/src/agent/blocks/FunctionalBlock.vue +118 -0
  34. package/src/agent/blocks/LoadingBlock.vue +28 -0
  35. package/src/agent/blocks/ThinkingBlock.vue +86 -0
  36. package/src/agent/blocks/ToolCallBlock.vue +40 -0
  37. package/src/agent/blocks/ToolGenericBlock.vue +43 -0
  38. package/src/agent/blocks/ToolShellAbortBlock.vue +13 -0
  39. package/src/agent/blocks/ToolShellBlock.vue +47 -0
  40. package/src/agent/blocks/ToolShellControlBlock.vue +51 -0
  41. package/src/agent/blocks/ToolShellStatusBlock.vue +13 -0
  42. package/src/agent/blocks/ToolShellWriteBlock.vue +13 -0
  43. package/src/agent/blocks/ToolStatusBadge.vue +13 -0
  44. package/src/agent/blocks/ToolYieldBlock.vue +13 -0
  45. package/src/agent/blocks/UserBlock.vue +112 -0
  46. package/src/agent/conversation-runtime.ts +205 -0
  47. package/src/agent/conversation-status.ts +12 -0
  48. package/src/agent/message-input/input-model.ts +33 -0
  49. package/src/agent/message-input/useAgentInputActions.ts +96 -0
  50. package/src/agent/message-input/useAgentInputEditor.ts +90 -0
  51. package/src/agent/message-input/useAgentInputSessionState.ts +48 -0
  52. package/src/agent/pending-steers.ts +134 -0
  53. package/src/agent/providers/ProviderIcon.vue +32 -0
  54. package/src/agent/providers/icons/claude.svg +1 -0
  55. package/src/agent/providers/icons/openai.svg +3 -0
  56. package/src/agent/queue-submit.ts +4 -0
  57. package/src/agent/reasoning.ts +45 -0
  58. package/src/agent/tail-loading.ts +31 -0
  59. package/src/agent/thinking-streaming.ts +7 -0
  60. package/src/agent/tool-rendering.ts +59 -0
  61. package/src/agent/types.ts +39 -0
  62. package/src/agent/ui-options.ts +21 -0
  63. package/src/agent/visible-blocks.ts +25 -0
  64. package/src/agent/workspace.ts +355 -0
  65. package/src/composables/useBlockVirtualizer.ts +290 -0
  66. package/src/composables/useContextMenuOwner.ts +44 -0
  67. package/src/composables/useOverlay.ts +18 -0
  68. package/src/composables/useTerminalTheme.ts +75 -0
  69. package/src/env.d.ts +10 -0
  70. package/src/infra/errors.ts +29 -0
  71. package/src/infra/i18n.ts +41 -0
  72. package/src/markdown/filePath.ts +54 -0
  73. package/src/markdown/highlight.ts +139 -0
  74. package/src/markdown/md.ts +58 -0
  75. package/src/markdown/render.ts +117 -0
  76. package/src/markdown/types.ts +20 -0
  77. package/src/overlay/appOverlay.ts +3 -0
  78. package/src/overlay/overlayStore.ts +54 -0
  79. package/src/store/createStore.ts +33 -0
  80. package/src/store/useStore.ts +7 -0
  81. package/src/styles/base.css +342 -0
  82. package/src/theme/appTheme.ts +56 -0
  83. package/src/theme/codeThemes.ts +454 -0
  84. package/src/theme/themeStore.ts +33 -0
  85. package/src/transport/agent-socket.ts +20 -0
  86. package/src/transport/control-client.ts +64 -0
  87. package/src/transport/protocol.ts +57 -0
  88. package/src/ui/Button.vue +30 -0
  89. package/src/ui/Checkbox.vue +25 -0
  90. package/src/ui/ContextMenu.vue +33 -0
  91. package/src/ui/Dialog.vue +48 -0
  92. package/src/ui/DropdownMenu.vue +77 -0
  93. package/src/ui/ErrorBox.vue +43 -0
  94. package/src/ui/HighlightText.vue +39 -0
  95. package/src/ui/IconButton.vue +30 -0
  96. package/src/ui/IndeterminateSpinner.vue +72 -0
  97. package/src/ui/InlineError.vue +25 -0
  98. package/src/ui/LoadMore.vue +32 -0
  99. package/src/ui/MarkdownPreview.vue +31 -0
  100. package/src/ui/Menu.vue +8 -0
  101. package/src/ui/MenuDivider.vue +6 -0
  102. package/src/ui/MenuItem.vue +48 -0
  103. package/src/ui/OptionMenu.vue +8 -0
  104. package/src/ui/OptionMenuGroup.vue +16 -0
  105. package/src/ui/OptionMenuItem.vue +28 -0
  106. package/src/ui/Popover.vue +90 -0
  107. package/src/ui/ScrollToBottomButton.vue +32 -0
  108. package/src/ui/SelectDropdown.vue +231 -0
  109. package/src/ui/Switch.vue +41 -0
  110. package/src/ui/TextInput.vue +44 -0
  111. package/src/ui/ThemeToggle.vue +49 -0
  112. package/src/ui/ToggleSwitch.vue +34 -0
  113. package/src/ui/Tooltip.vue +143 -0
  114. package/src/ui/blankClickGuard.ts +12 -0
@@ -0,0 +1,454 @@
1
+ import type { BundledTheme } from 'shiki'
2
+
3
+ export interface CodeThemePalette {
4
+ // Editor UI
5
+ background: string
6
+ foreground: string
7
+ selection: string
8
+ lineHighlight: string
9
+ cursor: string
10
+ lineNumber: string
11
+ activeLineNumber: string
12
+ // Syntax highlighting
13
+ keyword: string
14
+ controlKeyword: string
15
+ string: string
16
+ number: string
17
+ comment: string
18
+ function: string
19
+ variable: string
20
+ type: string
21
+ constant: string
22
+ operator: string
23
+ punctuation: string
24
+ tag: string
25
+ attribute: string
26
+ regexp: string
27
+ invalid: string
28
+ }
29
+
30
+ export interface CodeThemeDefinition {
31
+ id: string
32
+ label: string
33
+ dark: {
34
+ palette: CodeThemePalette
35
+ shikiTheme: BundledTheme
36
+ }
37
+ light: {
38
+ palette: CodeThemePalette
39
+ shikiTheme: BundledTheme
40
+ }
41
+ }
42
+
43
+ export const codeThemes: CodeThemeDefinition[] = [
44
+ {
45
+ id: 'one',
46
+ label: 'One',
47
+ dark: {
48
+ shikiTheme: 'one-dark-pro',
49
+ palette: {
50
+ background: '#282c34',
51
+ foreground: '#abb2bf',
52
+ selection: '#67769660',
53
+ lineHighlight: '#2c313c',
54
+ cursor: '#528bff',
55
+ lineNumber: '#495162',
56
+ activeLineNumber: '#abb2bf',
57
+ keyword: '#c678dd',
58
+ controlKeyword: '#c678dd',
59
+ string: '#98c379',
60
+ number: '#d19a66',
61
+ comment: '#7f848e',
62
+ function: '#61afef',
63
+ variable: '#e06c75',
64
+ type: '#e5c07b',
65
+ constant: '#d19a66',
66
+ operator: '#56b6c2',
67
+ punctuation: '#abb2bf',
68
+ tag: '#e06c75',
69
+ attribute: '#d19a66',
70
+ regexp: '#56b6c2',
71
+ invalid: '#f44747',
72
+ },
73
+ },
74
+ light: {
75
+ shikiTheme: 'one-light',
76
+ palette: {
77
+ background: '#fafafa',
78
+ foreground: '#383a42',
79
+ selection: '#e5e5e6',
80
+ lineHighlight: '#383a420c',
81
+ cursor: '#526fff',
82
+ lineNumber: '#9d9d9f',
83
+ activeLineNumber: '#383a42',
84
+ keyword: '#a626a4',
85
+ controlKeyword: '#a626a4',
86
+ string: '#50a14f',
87
+ number: '#986801',
88
+ comment: '#a0a1a7',
89
+ function: '#4078f2',
90
+ variable: '#e45649',
91
+ type: '#c18401',
92
+ constant: '#986801',
93
+ operator: '#0184bc',
94
+ punctuation: '#383a42',
95
+ tag: '#e45649',
96
+ attribute: '#986801',
97
+ regexp: '#0184bc',
98
+ invalid: '#ff1414',
99
+ },
100
+ },
101
+ },
102
+ {
103
+ id: 'vitesse',
104
+ label: 'Vitesse',
105
+ dark: {
106
+ shikiTheme: 'vitesse-dark',
107
+ palette: {
108
+ background: '#121212',
109
+ foreground: '#dbd7caee',
110
+ selection: '#eeeeee18',
111
+ lineHighlight: '#181818',
112
+ cursor: '#dbd7ca',
113
+ lineNumber: '#444444',
114
+ activeLineNumber: '#888888',
115
+ keyword: '#4d9375',
116
+ controlKeyword: '#c98a7d',
117
+ string: '#c98a7d',
118
+ number: '#4c9a91',
119
+ comment: '#758575dd',
120
+ function: '#80a665',
121
+ variable: '#bd976a',
122
+ type: '#5da994',
123
+ constant: '#c99076',
124
+ operator: '#cb7676',
125
+ punctuation: '#666666',
126
+ tag: '#4d9375',
127
+ attribute: '#bd976a',
128
+ regexp: '#c4704f',
129
+ invalid: '#cb7676',
130
+ },
131
+ },
132
+ light: {
133
+ shikiTheme: 'vitesse-light',
134
+ palette: {
135
+ background: '#ffffff',
136
+ foreground: '#393a34',
137
+ selection: '#22222218',
138
+ lineHighlight: '#f7f7f7',
139
+ cursor: '#393a34',
140
+ lineNumber: '#a0a0a0',
141
+ activeLineNumber: '#555555',
142
+ keyword: '#1e754f',
143
+ controlKeyword: '#ab5959',
144
+ string: '#b56959',
145
+ number: '#2e808f',
146
+ comment: '#a0ada0',
147
+ function: '#59873a',
148
+ variable: '#b07d48',
149
+ type: '#2e8f82',
150
+ constant: '#a65e2b',
151
+ operator: '#ab5959',
152
+ punctuation: '#999999',
153
+ tag: '#1e754f',
154
+ attribute: '#b07d48',
155
+ regexp: '#ab5e3f',
156
+ invalid: '#ab5959',
157
+ },
158
+ },
159
+ },
160
+ {
161
+ id: 'github',
162
+ label: 'GitHub',
163
+ dark: {
164
+ shikiTheme: 'github-dark',
165
+ palette: {
166
+ background: '#0d1117',
167
+ foreground: '#e6edf3',
168
+ selection: '#264f78',
169
+ lineHighlight: '#161b22',
170
+ cursor: '#e6edf3',
171
+ lineNumber: '#484f58',
172
+ activeLineNumber: '#e6edf3',
173
+ keyword: '#ff7b72',
174
+ controlKeyword: '#ff7b72',
175
+ string: '#a5d6ff',
176
+ number: '#79c0ff',
177
+ comment: '#8b949e',
178
+ function: '#d2a8ff',
179
+ variable: '#ffa657',
180
+ type: '#79c0ff',
181
+ constant: '#79c0ff',
182
+ operator: '#ff7b72',
183
+ punctuation: '#e6edf3',
184
+ tag: '#7ee787',
185
+ attribute: '#79c0ff',
186
+ regexp: '#7ee787',
187
+ invalid: '#f85149',
188
+ },
189
+ },
190
+ light: {
191
+ shikiTheme: 'github-light',
192
+ palette: {
193
+ background: '#ffffff',
194
+ foreground: '#1f2328',
195
+ selection: '#add6ff',
196
+ lineHighlight: '#f6f8fa',
197
+ cursor: '#1f2328',
198
+ lineNumber: '#8c959f',
199
+ activeLineNumber: '#1f2328',
200
+ keyword: '#cf222e',
201
+ controlKeyword: '#cf222e',
202
+ string: '#0a3069',
203
+ number: '#0550ae',
204
+ comment: '#6e7781',
205
+ function: '#8250df',
206
+ variable: '#953800',
207
+ type: '#0550ae',
208
+ constant: '#0550ae',
209
+ operator: '#cf222e',
210
+ punctuation: '#1f2328',
211
+ tag: '#116329',
212
+ attribute: '#0550ae',
213
+ regexp: '#116329',
214
+ invalid: '#82071e',
215
+ },
216
+ },
217
+ },
218
+ {
219
+ id: 'catppuccin',
220
+ label: 'Catppuccin',
221
+ dark: {
222
+ shikiTheme: 'catppuccin-mocha',
223
+ palette: {
224
+ background: '#1e1e2e',
225
+ foreground: '#cdd6f4',
226
+ selection: '#585b7066',
227
+ lineHighlight: '#313244',
228
+ cursor: '#f5e0dc',
229
+ lineNumber: '#6c7086',
230
+ activeLineNumber: '#cdd6f4',
231
+ keyword: '#cba6f7',
232
+ controlKeyword: '#cba6f7',
233
+ string: '#a6e3a1',
234
+ number: '#fab387',
235
+ comment: '#6c7086',
236
+ function: '#89b4fa',
237
+ variable: '#cdd6f4',
238
+ type: '#f9e2af',
239
+ constant: '#fab387',
240
+ operator: '#89dceb',
241
+ punctuation: '#9399b2',
242
+ tag: '#cba6f7',
243
+ attribute: '#f9e2af',
244
+ regexp: '#f38ba8',
245
+ invalid: '#f38ba8',
246
+ },
247
+ },
248
+ light: {
249
+ shikiTheme: 'catppuccin-latte',
250
+ palette: {
251
+ background: '#eff1f5',
252
+ foreground: '#4c4f69',
253
+ selection: '#acb0be66',
254
+ lineHighlight: '#ccd0da',
255
+ cursor: '#dc8a78',
256
+ lineNumber: '#9ca0b0',
257
+ activeLineNumber: '#4c4f69',
258
+ keyword: '#8839ef',
259
+ controlKeyword: '#8839ef',
260
+ string: '#40a02b',
261
+ number: '#fe640b',
262
+ comment: '#9ca0b0',
263
+ function: '#1e66f5',
264
+ variable: '#4c4f69',
265
+ type: '#df8e1d',
266
+ constant: '#fe640b',
267
+ operator: '#04a5e5',
268
+ punctuation: '#7c7f93',
269
+ tag: '#8839ef',
270
+ attribute: '#df8e1d',
271
+ regexp: '#d20f39',
272
+ invalid: '#d20f39',
273
+ },
274
+ },
275
+ },
276
+ {
277
+ id: 'everforest',
278
+ label: 'Everforest',
279
+ dark: {
280
+ shikiTheme: 'everforest-dark',
281
+ palette: {
282
+ background: '#2d353b',
283
+ foreground: '#d3c6aa',
284
+ selection: '#475258c0',
285
+ lineHighlight: '#3d484d',
286
+ cursor: '#d3c6aa',
287
+ lineNumber: '#7f897d',
288
+ activeLineNumber: '#9aa79d',
289
+ keyword: '#e67e80',
290
+ controlKeyword: '#e69875',
291
+ string: '#dbbc7f',
292
+ number: '#d699b6',
293
+ comment: '#859289',
294
+ function: '#a7c080',
295
+ variable: '#d3c6aa',
296
+ type: '#83c092',
297
+ constant: '#d699b6',
298
+ operator: '#e69875',
299
+ punctuation: '#d3c6aa',
300
+ tag: '#e69875',
301
+ attribute: '#dbbc7f',
302
+ regexp: '#dbbc7f',
303
+ invalid: '#e67e80',
304
+ },
305
+ },
306
+ light: {
307
+ shikiTheme: 'everforest-light',
308
+ palette: {
309
+ background: '#fdf6e3',
310
+ foreground: '#5c6a72',
311
+ selection: '#edeae0c0',
312
+ lineHighlight: '#f0eed9',
313
+ cursor: '#5c6a72',
314
+ lineNumber: '#939f91',
315
+ activeLineNumber: '#708868',
316
+ keyword: '#f85552',
317
+ controlKeyword: '#f57d26',
318
+ string: '#dfa000',
319
+ number: '#df69ba',
320
+ comment: '#939f91',
321
+ function: '#8da101',
322
+ variable: '#5c6a72',
323
+ type: '#35a77c',
324
+ constant: '#df69ba',
325
+ operator: '#f57d26',
326
+ punctuation: '#5c6a72',
327
+ tag: '#f57d26',
328
+ attribute: '#dfa000',
329
+ regexp: '#dfa000',
330
+ invalid: '#f85552',
331
+ },
332
+ },
333
+ },
334
+ {
335
+ id: 'solarized',
336
+ label: 'Solarized',
337
+ dark: {
338
+ shikiTheme: 'solarized-dark',
339
+ palette: {
340
+ background: '#002b36',
341
+ foreground: '#839496',
342
+ selection: '#073642',
343
+ lineHighlight: '#073642',
344
+ cursor: '#839496',
345
+ lineNumber: '#586e75',
346
+ activeLineNumber: '#93a1a1',
347
+ keyword: '#859900',
348
+ controlKeyword: '#859900',
349
+ string: '#2aa198',
350
+ number: '#d33682',
351
+ comment: '#586e75',
352
+ function: '#268bd2',
353
+ variable: '#839496',
354
+ type: '#b58900',
355
+ constant: '#cb4b16',
356
+ operator: '#839496',
357
+ punctuation: '#839496',
358
+ tag: '#268bd2',
359
+ attribute: '#b58900',
360
+ regexp: '#d33682',
361
+ invalid: '#dc322f',
362
+ },
363
+ },
364
+ light: {
365
+ shikiTheme: 'solarized-light',
366
+ palette: {
367
+ background: '#fdf6e3',
368
+ foreground: '#657b83',
369
+ selection: '#eee8d5',
370
+ lineHighlight: '#eee8d5',
371
+ cursor: '#657b83',
372
+ lineNumber: '#93a1a1',
373
+ activeLineNumber: '#586e75',
374
+ keyword: '#859900',
375
+ controlKeyword: '#859900',
376
+ string: '#2aa198',
377
+ number: '#d33682',
378
+ comment: '#93a1a1',
379
+ function: '#268bd2',
380
+ variable: '#657b83',
381
+ type: '#b58900',
382
+ constant: '#cb4b16',
383
+ operator: '#657b83',
384
+ punctuation: '#657b83',
385
+ tag: '#268bd2',
386
+ attribute: '#b58900',
387
+ regexp: '#d33682',
388
+ invalid: '#dc322f',
389
+ },
390
+ },
391
+ },
392
+ {
393
+ id: 'rose-pine',
394
+ label: 'Rosé Pine',
395
+ dark: {
396
+ shikiTheme: 'rose-pine-moon',
397
+ palette: {
398
+ background: '#232136',
399
+ foreground: '#e0def4',
400
+ selection: '#817c9c26',
401
+ lineHighlight: '#817c9c14',
402
+ cursor: '#e0def4',
403
+ lineNumber: '#6e6a86',
404
+ activeLineNumber: '#e0def4',
405
+ keyword: '#3e8fb0',
406
+ controlKeyword: '#3e8fb0',
407
+ string: '#f6c177',
408
+ number: '#ea9a97',
409
+ comment: '#6e6a86',
410
+ function: '#eb6f92',
411
+ variable: '#e0def4',
412
+ type: '#9ccfd8',
413
+ constant: '#ea9a97',
414
+ operator: '#908caa',
415
+ punctuation: '#908caa',
416
+ tag: '#eb6f92',
417
+ attribute: '#c4a7e7',
418
+ regexp: '#3e8fb0',
419
+ invalid: '#eb6f92',
420
+ },
421
+ },
422
+ light: {
423
+ shikiTheme: 'rose-pine-dawn',
424
+ palette: {
425
+ background: '#faf4ed',
426
+ foreground: '#575279',
427
+ selection: '#6e6a8614',
428
+ lineHighlight: '#6e6a860d',
429
+ cursor: '#575279',
430
+ lineNumber: '#9893a5',
431
+ activeLineNumber: '#575279',
432
+ keyword: '#286983',
433
+ controlKeyword: '#286983',
434
+ string: '#ea9d34',
435
+ number: '#d7827e',
436
+ comment: '#9893a5',
437
+ function: '#b4637a',
438
+ variable: '#575279',
439
+ type: '#56949f',
440
+ constant: '#d7827e',
441
+ operator: '#797593',
442
+ punctuation: '#797593',
443
+ tag: '#b4637a',
444
+ attribute: '#907aa9',
445
+ regexp: '#286983',
446
+ invalid: '#b4637a',
447
+ },
448
+ },
449
+ },
450
+ ]
451
+
452
+ export function findCodeTheme(id: string): CodeThemeDefinition {
453
+ return codeThemes.find((t) => t.id === id) ?? codeThemes[0]!
454
+ }
@@ -0,0 +1,33 @@
1
+ import { createStore } from '../store/createStore'
2
+
3
+ export interface ThemeStoreState {
4
+ mode: 'light' | 'dark'
5
+ codeThemeId: string
6
+ }
7
+
8
+ export interface ThemeStore {
9
+ state: ThemeStoreState
10
+ setMode(mode: ThemeStoreState['mode']): void
11
+ setCodeThemeId(codeThemeId: string): void
12
+ subscribe(listener: () => void): () => void
13
+ }
14
+
15
+ export function createThemeStore(initial: Partial<ThemeStoreState> = {}): ThemeStore {
16
+ const store = createStore<ThemeStoreState>({
17
+ mode: initial.mode ?? 'dark',
18
+ codeThemeId: initial.codeThemeId ?? 'one',
19
+ })
20
+
21
+ return {
22
+ state: store.state,
23
+ subscribe: store.subscribe,
24
+ setMode(mode) {
25
+ if (store.state.mode === mode) return
26
+ store.patch({ mode })
27
+ },
28
+ setCodeThemeId(codeThemeId) {
29
+ if (store.state.codeThemeId === codeThemeId) return
30
+ store.patch({ codeThemeId })
31
+ },
32
+ }
33
+ }
@@ -0,0 +1,20 @@
1
+ import { AgentClient, createWebSocketClientTransport, type JsonWebSocket } from '@demicodes/agent/client'
2
+
3
+ export function agentSocketUrl(baseUrl: string, cwd: string): string {
4
+ const url = new URL('/agent', baseUrl)
5
+ url.protocol = url.protocol === 'https:' ? 'wss:' : url.protocol === 'http:' ? 'ws:' : url.protocol
6
+ url.searchParams.set('cwd', cwd)
7
+ return url.toString()
8
+ }
9
+
10
+ export function connectAgentClient(url: string): Promise<AgentClient> {
11
+ return new Promise((resolve, reject) => {
12
+ const socket = new WebSocket(url)
13
+ socket.addEventListener(
14
+ 'open',
15
+ () => resolve(new AgentClient(createWebSocketClientTransport(socket as unknown as JsonWebSocket))),
16
+ { once: true },
17
+ )
18
+ socket.addEventListener('error', () => reject(new Error('Agent socket failed to connect')), { once: true })
19
+ })
20
+ }
@@ -0,0 +1,64 @@
1
+ import type { ProviderSelection } from '@demicodes/agent/client'
2
+ import type {
3
+ ControlApi,
4
+ ControlMethod,
5
+ ControlRequest,
6
+ ControlResponse,
7
+ ModelInfo,
8
+ PrepareSessionParams,
9
+ ProviderInfo,
10
+ WorkspaceInfo,
11
+ } from './protocol'
12
+
13
+ interface PendingCall {
14
+ resolve: (value: unknown) => void
15
+ reject: (error: Error) => void
16
+ }
17
+
18
+ export function connectControlClient(url: string): Promise<ControlApi> {
19
+ return new Promise((resolve, reject) => {
20
+ const socket = new WebSocket(url)
21
+ const pending = new Map<number, PendingCall>()
22
+ let nextId = 1
23
+
24
+ socket.addEventListener('message', (event) => {
25
+ const text = typeof event.data === 'string' ? event.data : String(event.data)
26
+ const response = JSON.parse(text) as ControlResponse
27
+ const waiter = pending.get(response.id)
28
+ if (!waiter) return
29
+ pending.delete(response.id)
30
+ if (response.ok) waiter.resolve(response.result)
31
+ else waiter.reject(new Error(response.error))
32
+ })
33
+
34
+ socket.addEventListener('close', () => {
35
+ for (const waiter of pending.values()) waiter.reject(new Error('Control socket closed'))
36
+ pending.clear()
37
+ })
38
+
39
+ socket.addEventListener('error', () => reject(new Error('Control socket failed to connect')), { once: true })
40
+
41
+ function call(method: ControlMethod, params: unknown): Promise<unknown> {
42
+ return new Promise((settle, fail) => {
43
+ const id = nextId++
44
+ pending.set(id, { resolve: settle, reject: fail })
45
+ const request: ControlRequest = { id, method, params }
46
+ socket.send(JSON.stringify(request))
47
+ })
48
+ }
49
+
50
+ socket.addEventListener(
51
+ 'open',
52
+ () => {
53
+ const api: ControlApi = {
54
+ listProviders: () => call('listProviders', undefined) as Promise<ProviderInfo[]>,
55
+ listModels: (params) => call('listModels', params) as Promise<ModelInfo[]>,
56
+ prepareSession: (params: PrepareSessionParams) => call('prepareSession', params) as Promise<ProviderSelection>,
57
+ defaultWorkspace: () => call('defaultWorkspace', undefined) as Promise<WorkspaceInfo>,
58
+ }
59
+ resolve(api)
60
+ },
61
+ { once: true },
62
+ )
63
+ })
64
+ }
@@ -0,0 +1,57 @@
1
+ import type { ProviderSelection } from '@demicodes/agent/client'
2
+
3
+ // Control-plane protocol. Decoupled from @demicodes/provider so the component library stays
4
+ // portable: hosts map their own catalogs onto these DTOs.
5
+
6
+ export interface ProviderInfo {
7
+ id: string
8
+ label: string
9
+ isAvailable: boolean
10
+ }
11
+
12
+ export interface ModelReasoning {
13
+ efforts: string[]
14
+ defaultEffort: string | null
15
+ /** Whether thinking can be turned off entirely. When false, the UI offers only effort levels and
16
+ * no "No reasoning" option (e.g. Claude Code, which can level thinking but never disable it). */
17
+ canDisable: boolean
18
+ }
19
+
20
+ export interface ModelInfo {
21
+ id: string
22
+ name: string
23
+ contextWindow: number | null
24
+ inputLimit: number | null
25
+ acceptedExtensions: string[]
26
+ reasoning: ModelReasoning | null
27
+ }
28
+
29
+ export interface PrepareSessionParams {
30
+ providerId: string
31
+ modelId: string
32
+ thinkingEffort?: string | null
33
+ serviceTierId?: string | null
34
+ }
35
+
36
+ export interface WorkspaceInfo {
37
+ cwd: string
38
+ }
39
+
40
+ export interface ControlApi {
41
+ listProviders(): Promise<ProviderInfo[]>
42
+ listModels(params: { providerId: string }): Promise<ModelInfo[]>
43
+ prepareSession(params: PrepareSessionParams): Promise<ProviderSelection>
44
+ defaultWorkspace(): Promise<WorkspaceInfo>
45
+ }
46
+
47
+ export type ControlMethod = keyof ControlApi
48
+
49
+ export interface ControlRequest {
50
+ id: number
51
+ method: ControlMethod
52
+ params: unknown
53
+ }
54
+
55
+ export type ControlResponse =
56
+ | { id: number; ok: true; result: unknown }
57
+ | { id: number; ok: false; error: string }
@@ -0,0 +1,30 @@
1
+ <script setup lang="ts">
2
+ defineProps<{
3
+ size?: 'xs' | 'sm' | 'md'
4
+ disabled?: boolean
5
+ }>()
6
+ </script>
7
+
8
+ <template>
9
+ <span
10
+ class="btn inline-flex cursor-pointer items-center justify-center gap-1 rounded-md font-medium text-fg-body transition-all select-none hover:text-fg-emphasis active:translate-y-px active:brightness-90"
11
+ :class="[
12
+ size === 'md' ? 'px-3 py-1 text-[12px]' : size === 'xs' ? 'px-1.5 py-0.5 text-[11px]' : 'px-2.5 py-0.5 text-[11px]',
13
+ disabled ? 'pointer-events-none opacity-40' : '',
14
+ ]"
15
+ >
16
+ <slot />
17
+ </span>
18
+ </template>
19
+
20
+ <style scoped>
21
+ .btn {
22
+ background: var(--btn-bg);
23
+ box-shadow: var(--shadow-btn);
24
+ }
25
+
26
+ .btn:hover {
27
+ background: var(--btn-bg-hover);
28
+ box-shadow: var(--shadow-btn-hover);
29
+ }
30
+ </style>
@@ -0,0 +1,25 @@
1
+ <script setup lang="ts">
2
+ import { CheckFill } from '@mingcute/vue/check'
3
+ import { MinimizeLine } from '@mingcute/vue/minimize'
4
+
5
+ defineProps<{
6
+ state: 'checked' | 'unchecked' | 'partial'
7
+ }>()
8
+
9
+ const emit = defineEmits<{
10
+ toggle: []
11
+ }>()
12
+ </script>
13
+
14
+ <template>
15
+ <span
16
+ class="flex size-3.5 shrink-0 cursor-pointer items-center justify-center rounded-[3px] transition-colors"
17
+ :class="state === 'unchecked'
18
+ ? 'bg-fg-subtle/10 hover:bg-fg-subtle/20'
19
+ : 'bg-fg-subtle/20 text-fg hover:bg-fg-subtle/30'"
20
+ @click.stop="emit('toggle')"
21
+ >
22
+ <MinimizeLine v-if="state === 'partial'" class="size-2.5" />
23
+ <CheckFill v-if="state === 'checked'" class="size-2.5" />
24
+ </span>
25
+ </template>