@ouraihub/aether-ui 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/LICENSE +789 -0
  2. package/fixtures/model-classify.json +42 -0
  3. package/fixtures/pricing-cost.json +56 -0
  4. package/fixtures/reasoning-infer.json +44 -0
  5. package/fixtures/sse/anthropic-messages/chinese.sse +24 -0
  6. package/fixtures/sse/anthropic-messages/multi-tool.sse +33 -0
  7. package/fixtures/sse/anthropic-messages/single-tool.sse +39 -0
  8. package/fixtures/sse/anthropic-messages/text.sse +27 -0
  9. package/fixtures/sse/anthropic-messages/truncated.sse +21 -0
  10. package/fixtures/sse/anthropic-messages/upstream-error.sse +12 -0
  11. package/fixtures/sse/expect.json +20 -0
  12. package/fixtures/sse/openai-chat/chinese.sse +14 -0
  13. package/fixtures/sse/openai-chat/multi-tool.sse +18 -0
  14. package/fixtures/sse/openai-chat/single-tool.sse +22 -0
  15. package/fixtures/sse/openai-chat/text.sse +14 -0
  16. package/fixtures/sse/openai-chat/truncated.sse +12 -0
  17. package/fixtures/sse/openai-chat/upstream-error.sse +6 -0
  18. package/fixtures/sse/openai-responses/chinese.sse +33 -0
  19. package/fixtures/sse/openai-responses/multi-tool.sse +36 -0
  20. package/fixtures/sse/openai-responses/single-tool.sse +27 -0
  21. package/fixtures/sse/openai-responses/text.sse +33 -0
  22. package/fixtures/sse/openai-responses/truncated.sse +30 -0
  23. package/fixtures/sse/openai-responses/upstream-error.sse +9 -0
  24. package/package.json +71 -3
  25. package/src/CopyInput.svelte +33 -0
  26. package/src/KitNotifier.svelte +33 -0
  27. package/src/chat/ApprovalCard.svelte +23 -0
  28. package/src/chat/ChatPanel.svelte +2097 -0
  29. package/src/chat/MessageItem.svelte +583 -0
  30. package/src/chat/SkillPicker.svelte +126 -0
  31. package/src/chat/ToolCallItem.svelte +23 -0
  32. package/src/chat/UsageDetails.svelte +138 -0
  33. package/src/chat/index.ts +2 -0
  34. package/src/chat/run-session.svelte.ts +556 -0
  35. package/src/components/ui/alert/alert-action.svelte +20 -0
  36. package/src/components/ui/alert/alert-description.svelte +23 -0
  37. package/src/components/ui/alert/alert-title.svelte +23 -0
  38. package/src/components/ui/alert/alert.svelte +43 -0
  39. package/src/components/ui/alert/index.ts +17 -0
  40. package/src/components/ui/alert-dialog/alert-dialog-action.svelte +27 -0
  41. package/src/components/ui/alert-dialog/alert-dialog-cancel.svelte +27 -0
  42. package/src/components/ui/alert-dialog/alert-dialog-content.svelte +32 -0
  43. package/src/components/ui/alert-dialog/alert-dialog-description.svelte +17 -0
  44. package/src/components/ui/alert-dialog/alert-dialog-footer.svelte +23 -0
  45. package/src/components/ui/alert-dialog/alert-dialog-header.svelte +20 -0
  46. package/src/components/ui/alert-dialog/alert-dialog-media.svelte +20 -0
  47. package/src/components/ui/alert-dialog/alert-dialog-overlay.svelte +17 -0
  48. package/src/components/ui/alert-dialog/alert-dialog-portal.svelte +7 -0
  49. package/src/components/ui/alert-dialog/alert-dialog-title.svelte +17 -0
  50. package/src/components/ui/alert-dialog/alert-dialog-trigger.svelte +7 -0
  51. package/src/components/ui/alert-dialog/alert-dialog.svelte +7 -0
  52. package/src/components/ui/alert-dialog/index.ts +40 -0
  53. package/src/components/ui/button/button.svelte +81 -0
  54. package/src/components/ui/button/index.ts +17 -0
  55. package/src/components/ui/dialog/dialog-close.svelte +11 -0
  56. package/src/components/ui/dialog/dialog-content.svelte +48 -0
  57. package/src/components/ui/dialog/dialog-description.svelte +17 -0
  58. package/src/components/ui/dialog/dialog-footer.svelte +32 -0
  59. package/src/components/ui/dialog/dialog-header.svelte +20 -0
  60. package/src/components/ui/dialog/dialog-overlay.svelte +17 -0
  61. package/src/components/ui/dialog/dialog-portal.svelte +7 -0
  62. package/src/components/ui/dialog/dialog-title.svelte +17 -0
  63. package/src/components/ui/dialog/dialog-trigger.svelte +11 -0
  64. package/src/components/ui/dialog/dialog.svelte +7 -0
  65. package/src/components/ui/dialog/index.ts +34 -0
  66. package/src/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte +16 -0
  67. package/src/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +46 -0
  68. package/src/components/ui/dropdown-menu/dropdown-menu-content.svelte +31 -0
  69. package/src/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +22 -0
  70. package/src/components/ui/dropdown-menu/dropdown-menu-group.svelte +7 -0
  71. package/src/components/ui/dropdown-menu/dropdown-menu-item.svelte +27 -0
  72. package/src/components/ui/dropdown-menu/dropdown-menu-label.svelte +24 -0
  73. package/src/components/ui/dropdown-menu/dropdown-menu-portal.svelte +7 -0
  74. package/src/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte +17 -0
  75. package/src/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +36 -0
  76. package/src/components/ui/dropdown-menu/dropdown-menu-separator.svelte +17 -0
  77. package/src/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +20 -0
  78. package/src/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +31 -0
  79. package/src/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +29 -0
  80. package/src/components/ui/dropdown-menu/dropdown-menu-sub.svelte +7 -0
  81. package/src/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +7 -0
  82. package/src/components/ui/dropdown-menu/dropdown-menu.svelte +7 -0
  83. package/src/components/ui/dropdown-menu/index.ts +54 -0
  84. package/src/components/ui/empty/empty-content.svelte +23 -0
  85. package/src/components/ui/empty/empty-description.svelte +23 -0
  86. package/src/components/ui/empty/empty-header.svelte +20 -0
  87. package/src/components/ui/empty/empty-media.svelte +41 -0
  88. package/src/components/ui/empty/empty-title.svelte +20 -0
  89. package/src/components/ui/empty/empty.svelte +23 -0
  90. package/src/components/ui/empty/index.ts +22 -0
  91. package/src/components/ui/field/field-content.svelte +20 -0
  92. package/src/components/ui/field/field-description.svelte +25 -0
  93. package/src/components/ui/field/field-error.svelte +58 -0
  94. package/src/components/ui/field/field-group.svelte +23 -0
  95. package/src/components/ui/field/field-label.svelte +25 -0
  96. package/src/components/ui/field/field-legend.svelte +24 -0
  97. package/src/components/ui/field/field-separator.svelte +35 -0
  98. package/src/components/ui/field/field-set.svelte +20 -0
  99. package/src/components/ui/field/field-title.svelte +20 -0
  100. package/src/components/ui/field/field.svelte +47 -0
  101. package/src/components/ui/field/index.ts +33 -0
  102. package/src/components/ui/input/index.ts +7 -0
  103. package/src/components/ui/input/input.svelte +48 -0
  104. package/src/components/ui/label/index.ts +7 -0
  105. package/src/components/ui/label/label.svelte +20 -0
  106. package/src/components/ui/select/index.ts +40 -0
  107. package/src/components/ui/select/select-content.svelte +45 -0
  108. package/src/components/ui/select/select-group-heading.svelte +21 -0
  109. package/src/components/ui/select/select-group.svelte +17 -0
  110. package/src/components/ui/select/select-item.svelte +43 -0
  111. package/src/components/ui/select/select-label.svelte +20 -0
  112. package/src/components/ui/select/select-portal.svelte +7 -0
  113. package/src/components/ui/select/select-scroll-down-button.svelte +20 -0
  114. package/src/components/ui/select/select-scroll-up-button.svelte +20 -0
  115. package/src/components/ui/select/select-separator.svelte +18 -0
  116. package/src/components/ui/select/select-trigger.svelte +29 -0
  117. package/src/components/ui/select/select-value.svelte +7 -0
  118. package/src/components/ui/select/select.svelte +11 -0
  119. package/src/components/ui/separator/index.ts +7 -0
  120. package/src/components/ui/separator/separator.svelte +21 -0
  121. package/src/components/ui/sonner/index.ts +1 -0
  122. package/src/components/ui/sonner/sonner.svelte +40 -0
  123. package/src/components/ui/switch/index.ts +7 -0
  124. package/src/components/ui/switch/switch.svelte +34 -0
  125. package/src/components/ui/textarea/index.ts +7 -0
  126. package/src/components/ui/textarea/textarea.svelte +22 -0
  127. package/src/context.ts +34 -0
  128. package/src/dev/Demo.svelte +45 -0
  129. package/src/dev/main.ts +7 -0
  130. package/src/i18n/en.ts +617 -0
  131. package/src/i18n/index.ts +23 -0
  132. package/src/i18n/zh-CN.ts +599 -0
  133. package/src/index.ts +10 -0
  134. package/src/logs/LogPanel.svelte +97 -0
  135. package/src/logs/LogSettings.svelte +56 -0
  136. package/src/logs/index.ts +5 -0
  137. package/src/logs/logger.ts +96 -0
  138. package/src/markdown.ts +8 -0
  139. package/src/mcp/McpClientSettings.svelte +491 -0
  140. package/src/mcp/McpServerSettings.svelte +327 -0
  141. package/src/mcp/config.ts +26 -0
  142. package/src/mcp/index.ts +3 -0
  143. package/src/mcp/input.ts +73 -0
  144. package/src/notify.svelte.ts +43 -0
  145. package/src/ports/contract.ts +165 -0
  146. package/src/ports/fake.ts +484 -0
  147. package/src/ports/index.ts +7 -0
  148. package/src/ports/search.ts +47 -0
  149. package/src/ports/skill-sources.ts +45 -0
  150. package/src/providers/EndpointKeysDialog.svelte +343 -0
  151. package/src/providers/EndpointPanel.svelte +491 -0
  152. package/src/providers/ModelPickerDialog.svelte +187 -0
  153. package/src/providers/ModelSelect.svelte +434 -0
  154. package/src/providers/ModelSettingsDialog.svelte +453 -0
  155. package/src/providers/ProvidersSettings.svelte +631 -0
  156. package/src/providers/RequestConfigDialog.svelte +250 -0
  157. package/src/providers/SecretInput.svelte +276 -0
  158. package/src/providers/draft.ts +329 -0
  159. package/src/providers/edit-session.ts +146 -0
  160. package/src/providers/index.ts +5 -0
  161. package/src/providers/reasoning.ts +41 -0
  162. package/src/providers/revision.svelte.ts +4 -0
  163. package/src/search/SearchSettings.svelte +703 -0
  164. package/src/search/index.ts +13 -0
  165. package/src/search/settings-flow.ts +62 -0
  166. package/src/session.ts +20 -0
  167. package/src/skills/SkillAdd.svelte +347 -0
  168. package/src/skills/SkillAuthoring.svelte +381 -0
  169. package/src/skills/SkillEditor.svelte +94 -0
  170. package/src/skills/SkillPreviewDialog.svelte +122 -0
  171. package/src/skills/SkillsSettings.svelte +140 -0
  172. package/src/skills/error.ts +32 -0
  173. package/src/skills/index.ts +4 -0
  174. package/src/styles/kit.css +137 -0
  175. package/src/styles/tokens.css +22 -0
  176. package/src/tauri/index.ts +282 -0
  177. package/src/utils.ts +22 -0
  178. package/src/web/index.ts +376 -0
  179. package/README.md +0 -3
@@ -0,0 +1,376 @@
1
+ // Browser HTTP adapter. Only enabled capabilities are exposed; desktop/IPC never enter this module.
2
+ import { isContract, validateRunAccepted, validateRunEvent, validateRunSnapshot } from '@ouraihub/aether-contracts'
3
+ import type { ContractMap, ContractName, KitError, RunAccepted, RunEvent } from '@ouraihub/aether-contracts'
4
+ import { KitClientError } from '../ports/contract'
5
+ import type { CallOptions, KitPorts } from '../ports/contract'
6
+
7
+ export interface WebPortsOptions {
8
+ baseURL: string
9
+ fetch: (input: string, init: RequestInit) => Promise<Response>
10
+ csrf: string | (() => string | Promise<string>)
11
+ credentials?: RequestCredentials
12
+ chat?: boolean
13
+ conversations?: boolean
14
+ providers?: boolean
15
+ search?: boolean
16
+ skills?: boolean
17
+ mcpClient?: boolean
18
+ /** A disconnected stream is not a run outcome. Host may display pending confirmation. */
19
+ onStreamError?: (runId: string, error: KitClientError) => void
20
+ }
21
+
22
+ type Guard<T> = (value: unknown) => value is T
23
+ const contract =
24
+ <N extends ContractName>(name: N): Guard<ContractMap[N]> =>
25
+ (value): value is ContractMap[N] =>
26
+ isContract(name, value)
27
+ const array =
28
+ <T>(guard: Guard<T>): Guard<T[]> =>
29
+ (value): value is T[] =>
30
+ Array.isArray(value) && value.every((item: unknown) => guard(item))
31
+ const string: Guard<string> = (value): value is string => typeof value === 'string'
32
+ const count: Guard<{ count: number }> = (value): value is { count: number } =>
33
+ typeof value === 'object' &&
34
+ value !== null &&
35
+ 'count' in value &&
36
+ typeof value.count === 'number' &&
37
+ Number.isSafeInteger(value.count) &&
38
+ value.count >= 0 &&
39
+ Object.keys(value).length === 1
40
+ const record = (value: unknown): value is Record<string, unknown> =>
41
+ typeof value === 'object' && value !== null && !Array.isArray(value)
42
+ const failure = (code: KitError['code'], message: string, reason?: string): KitClientError =>
43
+ new KitClientError({ code, message, ...(reason ? { details: { reason } } : {}) })
44
+ const invalidResponse = () => failure('upstream', 'Invalid server response', 'invalid_response')
45
+ const normalize = (error: unknown, signal?: AbortSignal): KitClientError => {
46
+ if (error instanceof KitClientError) return error
47
+ return signal?.aborted ? failure('cancelled', 'Request cancelled') : failure('network', 'Unable to contact server')
48
+ }
49
+ const id = encodeURIComponent
50
+
51
+ export function createWebPorts(options: WebPortsOptions): KitPorts {
52
+ const base = new URL(options.baseURL)
53
+ if (!['http:', 'https:'].includes(base.protocol) || base.username || base.password || base.search || base.hash)
54
+ throw failure('bad_request', 'Invalid API base URL')
55
+ const api = new URL('/api/v1/', base)
56
+ const request = async (path: string, method: string, body?: unknown, signal?: AbortSignal): Promise<Response> => {
57
+ if (signal?.aborted) throw failure('cancelled', 'Request cancelled')
58
+ try {
59
+ const headers = new Headers({ Accept: path.endsWith('/events') ? 'text/event-stream' : 'application/json' })
60
+ if (method !== 'GET') {
61
+ const csrf = typeof options.csrf === 'function' ? await options.csrf() : options.csrf
62
+ headers.set('X-CSRF-Token', csrf)
63
+ }
64
+ if (body !== undefined) headers.set('Content-Type', 'application/json')
65
+ if (signal?.aborted) throw failure('cancelled', 'Request cancelled')
66
+ const response = await options.fetch(new URL(path, api).href, {
67
+ method,
68
+ headers,
69
+ credentials: options.credentials ?? 'include',
70
+ redirect: 'error',
71
+ ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
72
+ ...(signal ? { signal } : {}),
73
+ })
74
+ if (!response.ok) {
75
+ let value: unknown
76
+ try {
77
+ value = await response.json()
78
+ } catch {
79
+ throw invalidResponse()
80
+ }
81
+ if (isContract('KitError', value)) throw new KitClientError(value)
82
+ if (record(value) && isContract('KitError', value.error)) throw new KitClientError(value.error)
83
+ throw invalidResponse()
84
+ }
85
+ return response
86
+ } catch (error) {
87
+ throw normalize(error, signal)
88
+ }
89
+ }
90
+ const json = async <T>(
91
+ path: string,
92
+ method: string,
93
+ guard: Guard<T>,
94
+ body?: unknown,
95
+ call?: CallOptions,
96
+ ): Promise<T> => {
97
+ const response = await request(path, method, body, call?.signal)
98
+ let value: unknown
99
+ try {
100
+ value = await response.json()
101
+ } catch {
102
+ throw invalidResponse()
103
+ }
104
+ if (!guard(value)) throw invalidResponse()
105
+ return value
106
+ }
107
+ const ack = async (path: string, method = 'POST', body: unknown = {}) => {
108
+ await json(path, method, contract('Ack'), body)
109
+ }
110
+ const ports: KitPorts = {}
111
+ if (options.chat) {
112
+ const cancel = (runId: string) => ack(`runs/${id(runId)}/cancel`)
113
+ ports.chat = {
114
+ cancel,
115
+ approve: (runId, callId, allow) =>
116
+ ack(`runs/${id(runId)}/approval`, 'POST', { callId, decision: allow ? 'allow' : 'deny' }),
117
+ getSnapshot: (runId) => json(`runs/${id(runId)}`, 'GET', validateRunSnapshot),
118
+ start: async (input, onEvent, call) => {
119
+ const signal = call?.signal
120
+ if (signal?.aborted) throw failure('cancelled', 'Request cancelled')
121
+ let accepted: RunAccepted | undefined
122
+ let ended = false
123
+ let requested = false
124
+ let dispatched = false
125
+ const cleanup = () => signal?.removeEventListener('abort', abort)
126
+ const dispatchCancel = () => {
127
+ if (!accepted || ended || dispatched) return
128
+ dispatched = true
129
+ const runId = accepted.runId
130
+ void cancel(runId).catch((error: unknown) => options.onStreamError?.(runId, normalize(error)))
131
+ }
132
+ const abort = () => {
133
+ requested = true
134
+ dispatchCancel()
135
+ }
136
+ const emit = (event: RunEvent) => {
137
+ if (ended) return
138
+ if (event.kind === 'end') {
139
+ ended = true
140
+ cleanup()
141
+ }
142
+ onEvent(event)
143
+ }
144
+ signal?.addEventListener('abort', abort, { once: true })
145
+ try {
146
+ // Keep the acceptance response readable after abort: its run ID is needed to cancel the reservation.
147
+ const response = await request('runs', 'POST', input)
148
+ if (response.status !== 202) throw invalidResponse()
149
+ let envelope: unknown
150
+ try {
151
+ envelope = await response.json()
152
+ } catch {
153
+ throw invalidResponse()
154
+ }
155
+ if (
156
+ !record(envelope) ||
157
+ Object.keys(envelope).some((key) => key !== 'accepted' && key !== 'events') ||
158
+ !validateRunAccepted(envelope.accepted) ||
159
+ typeof envelope.events !== 'string'
160
+ )
161
+ throw invalidResponse()
162
+ accepted = envelope.accepted
163
+ const events = new URL(envelope.events, base)
164
+ if (
165
+ events.origin !== base.origin ||
166
+ events.pathname !== `/api/v1/runs/${id(accepted.runId)}/events` ||
167
+ events.search ||
168
+ events.hash ||
169
+ events.username ||
170
+ events.password
171
+ )
172
+ throw invalidResponse()
173
+ const runId = accepted.runId
174
+ // The Promise acknowledges acceptance only; the subscriber delivers all subsequent outcomes.
175
+ void (async () => {
176
+ try {
177
+ if (requested || signal?.aborted) {
178
+ dispatched = true
179
+ await cancel(runId).catch((error: unknown) => options.onStreamError?.(runId, normalize(error)))
180
+ }
181
+ const stream = await request(`runs/${id(runId)}/events`, 'GET')
182
+ if (!stream.headers.get('Content-Type')?.toLowerCase().startsWith('text/event-stream') || !stream.body)
183
+ throw invalidResponse()
184
+ await consumeEvents(stream.body, emit, () => ended)
185
+ if (!ended) throw failure('network', 'Event stream disconnected', 'stream_disconnected')
186
+ } catch (error) {
187
+ if (!ended) {
188
+ dispatchCancel()
189
+ try {
190
+ const snapshot = await json(`runs/${id(runId)}`, 'GET', validateRunSnapshot)
191
+ if (snapshot.status !== 'running')
192
+ emit({
193
+ kind: 'end',
194
+ status: snapshot.status,
195
+ ...(snapshot.error ? { error: snapshot.error } : {}),
196
+ ...(snapshot.exhausted !== undefined ? { exhausted: snapshot.exhausted } : {}),
197
+ })
198
+ } catch {
199
+ /* A missing snapshot is unknown, never a fabricated completion. */
200
+ }
201
+ if (!ended) options.onStreamError?.(runId, normalize(error))
202
+ }
203
+ } finally {
204
+ cleanup()
205
+ }
206
+ })()
207
+ return accepted
208
+ } catch (error) {
209
+ if (accepted) dispatchCancel()
210
+ ended = true
211
+ cleanup()
212
+ throw normalize(error)
213
+ }
214
+ },
215
+ }
216
+ }
217
+ if (options.conversations) {
218
+ const path = (conversationId: string) => `conversations/${id(conversationId)}`
219
+ ports.conversations = {
220
+ list: (scope) =>
221
+ json(
222
+ `conversations${scope === undefined ? '' : `?scope=${id(scope)}`}`,
223
+ 'GET',
224
+ array(contract('ConversationSummary')),
225
+ ),
226
+ get: (conversationId) => json(path(conversationId), 'GET', contract('Conversation')),
227
+ remove: (conversationId) => ack(path(conversationId), 'DELETE'),
228
+ rename: (conversationId, title) =>
229
+ json(`${path(conversationId)}/rename`, 'POST', contract('ConversationSummary'), { title }),
230
+ editTurn: (conversationId, turnId, text, expectedText) =>
231
+ json(`${path(conversationId)}/edit-turn`, 'POST', contract('Conversation'), { turnId, text, expectedText }),
232
+ removeTurn: (conversationId, turnId) =>
233
+ json(`${path(conversationId)}/remove-turn`, 'POST', contract('Conversation'), { turnId }),
234
+ fork: (conversationId, turnId) =>
235
+ json(`${path(conversationId)}/fork`, 'POST', contract('Conversation'), { turnId }),
236
+ selectTurn: (conversationId, turnId) =>
237
+ json(`${path(conversationId)}/select-turn`, 'POST', contract('Conversation'), { turnId }),
238
+ }
239
+ }
240
+ if (options.providers) {
241
+ const providers = array(contract('Provider'))
242
+ ports.providers = {
243
+ revealNeedsPassword: true,
244
+ keyStrategies: false,
245
+ list: () => json('providers', 'GET', providers),
246
+ save: (input) => json('providers', 'POST', providers, input),
247
+ remove: (providerId) => json(`providers/${id(providerId)}`, 'DELETE', providers),
248
+ setEnabled: (providerId, enabled) => json(`providers/${id(providerId)}/enabled`, 'POST', providers, { enabled }),
249
+ modelOptions: (filter = {}) => {
250
+ const query = new URLSearchParams()
251
+ if (filter.modelType !== undefined) query.set('modelType', filter.modelType)
252
+ if (filter.supportsTools !== undefined) query.set('supportsTools', String(filter.supportsTools))
253
+ return json(`providers/model-options?${query}`, 'GET', array(contract('ModelOption')))
254
+ },
255
+ probe: (target, call) => json('providers/probe', 'POST', contract('ProbeResult'), target, call),
256
+ saveKey: (input) => json('providers/keys', 'POST', providers, input),
257
+ removeKey: (endpointId, keyId) => json(`providers/${id(endpointId)}/keys/${id(keyId)}`, 'DELETE', providers),
258
+ revealKey: (keyId, currentPassword) =>
259
+ json(`providers/keys/${id(keyId)}/reveal`, 'POST', string, { currentPassword }),
260
+ }
261
+ }
262
+ if (options.search) {
263
+ const settings = contract('SearchSettingsDto')
264
+ ports.search = {
265
+ revealNeedsPassword: true,
266
+ settings: (capability, call) =>
267
+ json(`search/settings?capability=${id(capability)}`, 'GET', settings, undefined, call),
268
+ save: (input, call) => json('search/settings', 'POST', settings, input, call),
269
+ setDefault: (capability, provider, call) =>
270
+ json('search/default', 'POST', settings, { capability, provider }, call),
271
+ savePreferences: (input, call) => json('search/preferences', 'PUT', contract('SearchPreferences'), input, call),
272
+ saveKey: (capability, provider, value, keyId, call) =>
273
+ json('search/keys', 'POST', settings, { capability, provider, value, keyId }, call),
274
+ removeKey: (capability, provider, keyId, call) =>
275
+ json('search/keys', 'DELETE', settings, { capability, provider, keyId }, call),
276
+ reveal: (capability, provider, keyId, currentPassword, call) =>
277
+ json('search/keys/reveal', 'POST', string, { capability, provider, keyId, currentPassword }, call),
278
+ check: (capability, provider, call) => json('search/check', 'POST', count, { capability, provider }, call),
279
+ search: (query, call) => json('search/search', 'POST', contract('SearchResult'), { query }, call),
280
+ fetch: (target, call) => json('search/fetch', 'POST', contract('SearchPage'), { url: target }, call),
281
+ }
282
+ }
283
+ if (options.skills) {
284
+ const skills = array(contract('SkillMeta'))
285
+ ports.skills = {
286
+ list: () => json('skills', 'GET', skills),
287
+ get: (skillId) => json(`skills/${id(skillId)}`, 'GET', contract('SkillDto')),
288
+ preview: (source) => json('skills/preview', 'POST', contract('Preview'), { source }),
289
+ install: (source, confirmedDigest, replace) =>
290
+ json('skills/install', 'POST', skills, { source, confirmedDigest, replace }),
291
+ save: (input) => json('skills/save', 'POST', skills, input),
292
+ duplicate: (skillId, newName) => json(`skills/${id(skillId)}/duplicate`, 'POST', skills, { newName }),
293
+ reorder: (ids) => json('skills/reorder', 'POST', skills, { ids }),
294
+ remove: (skillId) => json(`skills/${id(skillId)}`, 'DELETE', skills),
295
+ }
296
+ }
297
+ if (options.mcpClient) {
298
+ const servers = array(contract('ServerDto'))
299
+ ports.mcpClient = {
300
+ list: () => json('mcp/servers', 'GET', servers),
301
+ save: (input) =>
302
+ json(input.id ? `mcp/servers/${id(input.id)}` : 'mcp/servers', input.id ? 'PUT' : 'POST', servers, input),
303
+ remove: (serverId) => json(`mcp/servers/${id(serverId)}`, 'DELETE', servers),
304
+ refresh: (serverId) => json(`mcp/servers/${id(serverId)}/refresh`, 'POST', array(contract('ToolDto')), {}),
305
+ setToolApproval: (serverId, tool, approval) =>
306
+ ack(`mcp/servers/${id(serverId)}/tools/${id(tool)}/approval`, 'PUT', { approval }),
307
+ }
308
+ }
309
+ return ports
310
+ }
311
+
312
+ async function consumeEvents(
313
+ body: ReadableStream<Uint8Array>,
314
+ emit: (event: RunEvent) => void,
315
+ ended: () => boolean,
316
+ ): Promise<void> {
317
+ const reader = body.getReader()
318
+ const decoder = new TextDecoder('utf-8', { fatal: true })
319
+ let buffer = ''
320
+ let data: string[] = []
321
+ let eventName = ''
322
+ let size = 0
323
+ const line = (value: string) => {
324
+ if (value === '') {
325
+ if (data.length > 0) {
326
+ if (eventName && eventName !== 'run_event') throw invalidResponse()
327
+ let parsed: unknown
328
+ try {
329
+ parsed = JSON.parse(data.join('\n'))
330
+ } catch {
331
+ throw invalidResponse()
332
+ }
333
+ if (!validateRunEvent(parsed)) throw invalidResponse()
334
+ emit(parsed)
335
+ }
336
+ data = []
337
+ eventName = ''
338
+ size = 0
339
+ return
340
+ }
341
+ size += value.length
342
+ if (size > 1024 * 1024) throw invalidResponse()
343
+ if (value.startsWith(':')) return
344
+ const separator = value.indexOf(':')
345
+ const field = separator < 0 ? value : value.slice(0, separator)
346
+ let content = separator < 0 ? '' : value.slice(separator + 1)
347
+ if (content.startsWith(' ')) content = content.slice(1)
348
+ if (field === 'data') data.push(content)
349
+ else if (field === 'event') eventName = content
350
+ }
351
+ try {
352
+ for (;;) {
353
+ const chunk = await reader.read()
354
+ buffer += decoder.decode(chunk.value, { stream: !chunk.done })
355
+ let at = 0
356
+ while (at < buffer.length) {
357
+ const found = buffer.slice(at).search(/[\r\n]/)
358
+ if (found < 0) break
359
+ const end = at + found
360
+ if (!chunk.done && buffer[end] === '\r' && end === buffer.length - 1) break
361
+ line(buffer.slice(at, end))
362
+ at = end + (buffer[end] === '\r' && buffer[end + 1] === '\n' ? 2 : 1)
363
+ if (ended()) return
364
+ }
365
+ buffer = buffer.slice(at)
366
+ if (buffer.length + size > 1024 * 1024) throw invalidResponse()
367
+ if (chunk.done) return
368
+ }
369
+ } finally {
370
+ try {
371
+ await reader.cancel()
372
+ } finally {
373
+ reader.releaseLock()
374
+ }
375
+ }
376
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.