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