@stina/extension-api 0.5.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.
- package/dist/chunk-V6D6CW2V.js +17 -0
- package/dist/chunk-V6D6CW2V.js.map +1 -0
- package/dist/index.cjs +35 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +135 -0
- package/dist/index.d.ts +135 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime.cjs +414 -0
- package/dist/runtime.cjs.map +1 -0
- package/dist/runtime.d.cts +17 -0
- package/dist/runtime.d.ts +17 -0
- package/dist/runtime.js +387 -0
- package/dist/runtime.js.map +1 -0
- package/dist/types-Brv9O9NY.d.cts +540 -0
- package/dist/types-Brv9O9NY.d.ts +540 -0
- package/package.json +32 -0
- package/src/index.ts +92 -0
- package/src/messages.ts +189 -0
- package/src/runtime.ts +567 -0
- package/src/types.ts +634 -0
- package/stina-extension-api-0.5.0.tgz +0 -0
- package/tsconfig.json +9 -0
- package/tsup.config.ts +9 -0
package/src/runtime.ts
ADDED
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extension Runtime - Runs inside the worker
|
|
3
|
+
*
|
|
4
|
+
* This module handles communication with the Extension Host and provides
|
|
5
|
+
* the ExtensionContext to the extension's activate function.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
ExtensionContext,
|
|
10
|
+
ExtensionModule,
|
|
11
|
+
Disposable,
|
|
12
|
+
NetworkAPI,
|
|
13
|
+
SettingsAPI,
|
|
14
|
+
ProvidersAPI,
|
|
15
|
+
ToolsAPI,
|
|
16
|
+
DatabaseAPI,
|
|
17
|
+
StorageAPI,
|
|
18
|
+
LogAPI,
|
|
19
|
+
AIProvider,
|
|
20
|
+
Tool,
|
|
21
|
+
ToolResult,
|
|
22
|
+
ModelInfo,
|
|
23
|
+
ChatMessage,
|
|
24
|
+
ChatOptions,
|
|
25
|
+
GetModelsOptions,
|
|
26
|
+
StreamEvent,
|
|
27
|
+
} from './types.js'
|
|
28
|
+
|
|
29
|
+
import type {
|
|
30
|
+
HostToWorkerMessage,
|
|
31
|
+
WorkerToHostMessage,
|
|
32
|
+
RequestMessage,
|
|
33
|
+
PendingRequest,
|
|
34
|
+
} from './messages.js'
|
|
35
|
+
|
|
36
|
+
import { generateMessageId } from './messages.js'
|
|
37
|
+
|
|
38
|
+
// ============================================================================
|
|
39
|
+
// Environment Detection and Message Port
|
|
40
|
+
// ============================================================================
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Detect if we're in Node.js Worker Thread or Web Worker
|
|
44
|
+
* and get the appropriate message port
|
|
45
|
+
*/
|
|
46
|
+
interface MessagePort {
|
|
47
|
+
postMessage(message: WorkerToHostMessage): void
|
|
48
|
+
onMessage(handler: (message: HostToWorkerMessage) => void): void
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getMessagePort(): MessagePort {
|
|
52
|
+
// Check if we're in Node.js Worker Thread
|
|
53
|
+
if (typeof process !== 'undefined' && process.versions?.node) {
|
|
54
|
+
// Node.js Worker Thread - import parentPort dynamically
|
|
55
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
56
|
+
const { parentPort } = require('node:worker_threads')
|
|
57
|
+
return {
|
|
58
|
+
postMessage: (message) => parentPort?.postMessage(message),
|
|
59
|
+
onMessage: (handler) => parentPort?.on('message', handler),
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Web Worker - use self
|
|
64
|
+
return {
|
|
65
|
+
postMessage: (message) => self.postMessage(message),
|
|
66
|
+
onMessage: (handler) => {
|
|
67
|
+
self.addEventListener('message', (event: MessageEvent<HostToWorkerMessage>) => {
|
|
68
|
+
handler(event.data)
|
|
69
|
+
})
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const messagePort = getMessagePort()
|
|
75
|
+
|
|
76
|
+
// ============================================================================
|
|
77
|
+
// Global State
|
|
78
|
+
// ============================================================================
|
|
79
|
+
|
|
80
|
+
let extensionModule: ExtensionModule | null = null
|
|
81
|
+
let extensionDisposable: Disposable | null = null
|
|
82
|
+
let extensionContext: ExtensionContext | null = null
|
|
83
|
+
|
|
84
|
+
const pendingRequests = new Map<string, PendingRequest>()
|
|
85
|
+
const registeredProviders = new Map<string, AIProvider>()
|
|
86
|
+
const registeredTools = new Map<string, Tool>()
|
|
87
|
+
const settingsCallbacks: Array<(key: string, value: unknown) => void> = []
|
|
88
|
+
|
|
89
|
+
const REQUEST_TIMEOUT = 30000 // 30 seconds
|
|
90
|
+
|
|
91
|
+
// ============================================================================
|
|
92
|
+
// Message Handling
|
|
93
|
+
// ============================================================================
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Send a message to the host
|
|
97
|
+
*/
|
|
98
|
+
function postMessage(message: WorkerToHostMessage): void {
|
|
99
|
+
messagePort.postMessage(message)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Send a request to the host and wait for response
|
|
104
|
+
*/
|
|
105
|
+
async function sendRequest<T>(method: RequestMessage['method'], payload: unknown): Promise<T> {
|
|
106
|
+
const id = generateMessageId()
|
|
107
|
+
|
|
108
|
+
return new Promise<T>((resolve, reject) => {
|
|
109
|
+
const timeout = setTimeout(() => {
|
|
110
|
+
pendingRequests.delete(id)
|
|
111
|
+
reject(new Error(`Request ${method} timed out`))
|
|
112
|
+
}, REQUEST_TIMEOUT)
|
|
113
|
+
|
|
114
|
+
pendingRequests.set(id, { resolve: resolve as (value: unknown) => void, reject, timeout })
|
|
115
|
+
|
|
116
|
+
postMessage({
|
|
117
|
+
type: 'request',
|
|
118
|
+
id,
|
|
119
|
+
method,
|
|
120
|
+
payload,
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Handle messages from the host
|
|
127
|
+
*/
|
|
128
|
+
async function handleHostMessage(message: HostToWorkerMessage): Promise<void> {
|
|
129
|
+
switch (message.type) {
|
|
130
|
+
case 'activate':
|
|
131
|
+
await handleActivate(message.payload)
|
|
132
|
+
break
|
|
133
|
+
|
|
134
|
+
case 'deactivate':
|
|
135
|
+
await handleDeactivate()
|
|
136
|
+
break
|
|
137
|
+
|
|
138
|
+
case 'settings-changed':
|
|
139
|
+
handleSettingsChanged(message.payload.key, message.payload.value)
|
|
140
|
+
break
|
|
141
|
+
|
|
142
|
+
case 'provider-chat-request':
|
|
143
|
+
await handleProviderChatRequest(message.id, message.payload)
|
|
144
|
+
break
|
|
145
|
+
|
|
146
|
+
case 'provider-models-request':
|
|
147
|
+
await handleProviderModelsRequest(message.id, message.payload)
|
|
148
|
+
break
|
|
149
|
+
|
|
150
|
+
case 'tool-execute-request':
|
|
151
|
+
await handleToolExecuteRequest(message.id, message.payload)
|
|
152
|
+
break
|
|
153
|
+
|
|
154
|
+
case 'response':
|
|
155
|
+
handleResponse(message.payload)
|
|
156
|
+
break
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function handleResponse(payload: { requestId: string; success: boolean; data?: unknown; error?: string }): void {
|
|
161
|
+
const pending = pendingRequests.get(payload.requestId)
|
|
162
|
+
if (!pending) return
|
|
163
|
+
|
|
164
|
+
clearTimeout(pending.timeout)
|
|
165
|
+
pendingRequests.delete(payload.requestId)
|
|
166
|
+
|
|
167
|
+
if (payload.success) {
|
|
168
|
+
pending.resolve(payload.data)
|
|
169
|
+
} else {
|
|
170
|
+
pending.reject(new Error(payload.error || 'Unknown error'))
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ============================================================================
|
|
175
|
+
// Activation / Deactivation
|
|
176
|
+
// ============================================================================
|
|
177
|
+
|
|
178
|
+
async function handleActivate(payload: {
|
|
179
|
+
extensionId: string
|
|
180
|
+
extensionVersion: string
|
|
181
|
+
storagePath: string
|
|
182
|
+
permissions: string[]
|
|
183
|
+
settings: Record<string, unknown>
|
|
184
|
+
}): Promise<void> {
|
|
185
|
+
const { extensionId, extensionVersion, storagePath, permissions } = payload
|
|
186
|
+
|
|
187
|
+
// Build the context based on permissions
|
|
188
|
+
extensionContext = buildContext(extensionId, extensionVersion, storagePath, permissions)
|
|
189
|
+
|
|
190
|
+
// Import and activate the extension
|
|
191
|
+
try {
|
|
192
|
+
// The actual extension code should be bundled and available
|
|
193
|
+
// This is called after the extension code has been loaded into the worker
|
|
194
|
+
if (extensionModule?.activate) {
|
|
195
|
+
const result = await extensionModule.activate(extensionContext)
|
|
196
|
+
if (result && 'dispose' in result) {
|
|
197
|
+
extensionDisposable = result
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
} catch (error) {
|
|
201
|
+
extensionContext.log.error('Failed to activate extension', {
|
|
202
|
+
error: error instanceof Error ? error.message : String(error),
|
|
203
|
+
})
|
|
204
|
+
throw error
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async function handleDeactivate(): Promise<void> {
|
|
209
|
+
try {
|
|
210
|
+
if (extensionModule?.deactivate) {
|
|
211
|
+
await extensionModule.deactivate()
|
|
212
|
+
}
|
|
213
|
+
if (extensionDisposable) {
|
|
214
|
+
extensionDisposable.dispose()
|
|
215
|
+
}
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.error('Error during deactivation:', error)
|
|
218
|
+
} finally {
|
|
219
|
+
extensionModule = null
|
|
220
|
+
extensionDisposable = null
|
|
221
|
+
extensionContext = null
|
|
222
|
+
registeredProviders.clear()
|
|
223
|
+
registeredTools.clear()
|
|
224
|
+
settingsCallbacks.length = 0
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function handleSettingsChanged(key: string, value: unknown): void {
|
|
229
|
+
for (const callback of settingsCallbacks) {
|
|
230
|
+
try {
|
|
231
|
+
callback(key, value)
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.error('Error in settings change callback:', error)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ============================================================================
|
|
239
|
+
// Provider / Tool Requests
|
|
240
|
+
// ============================================================================
|
|
241
|
+
|
|
242
|
+
async function handleProviderChatRequest(
|
|
243
|
+
requestId: string,
|
|
244
|
+
payload: { providerId: string; messages: ChatMessage[]; options: ChatOptions }
|
|
245
|
+
): Promise<void> {
|
|
246
|
+
const provider = registeredProviders.get(payload.providerId)
|
|
247
|
+
if (!provider) {
|
|
248
|
+
postMessage({
|
|
249
|
+
type: 'request',
|
|
250
|
+
id: generateMessageId(),
|
|
251
|
+
method: 'network.fetch', // Dummy, we need a proper error response
|
|
252
|
+
payload: { error: `Provider ${payload.providerId} not found` },
|
|
253
|
+
})
|
|
254
|
+
return
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
const generator = provider.chat(payload.messages, payload.options)
|
|
259
|
+
let sawDone = false
|
|
260
|
+
let sawError = false
|
|
261
|
+
|
|
262
|
+
for await (const event of generator) {
|
|
263
|
+
if (event.type === 'done') {
|
|
264
|
+
sawDone = true
|
|
265
|
+
} else if (event.type === 'error') {
|
|
266
|
+
sawError = true
|
|
267
|
+
}
|
|
268
|
+
postMessage({
|
|
269
|
+
type: 'stream-event',
|
|
270
|
+
payload: { requestId, event },
|
|
271
|
+
})
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (!sawDone && !sawError) {
|
|
275
|
+
postMessage({
|
|
276
|
+
type: 'stream-event',
|
|
277
|
+
payload: {
|
|
278
|
+
requestId,
|
|
279
|
+
event: { type: 'done' },
|
|
280
|
+
},
|
|
281
|
+
})
|
|
282
|
+
}
|
|
283
|
+
} catch (error) {
|
|
284
|
+
postMessage({
|
|
285
|
+
type: 'stream-event',
|
|
286
|
+
payload: {
|
|
287
|
+
requestId,
|
|
288
|
+
event: {
|
|
289
|
+
type: 'error',
|
|
290
|
+
message: error instanceof Error ? error.message : String(error),
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
})
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async function handleProviderModelsRequest(
|
|
298
|
+
requestId: string,
|
|
299
|
+
payload: { providerId: string; options?: GetModelsOptions }
|
|
300
|
+
): Promise<void> {
|
|
301
|
+
const provider = registeredProviders.get(payload.providerId)
|
|
302
|
+
if (!provider) {
|
|
303
|
+
// Send error response
|
|
304
|
+
postMessage({
|
|
305
|
+
type: 'provider-models-response',
|
|
306
|
+
payload: {
|
|
307
|
+
requestId,
|
|
308
|
+
models: [],
|
|
309
|
+
error: `Provider ${payload.providerId} not found`,
|
|
310
|
+
},
|
|
311
|
+
})
|
|
312
|
+
return
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
try {
|
|
316
|
+
// Pass options to getModels so provider can use settings (e.g., URL for Ollama)
|
|
317
|
+
const models = await provider.getModels(payload.options)
|
|
318
|
+
// Send response with models
|
|
319
|
+
postMessage({
|
|
320
|
+
type: 'provider-models-response',
|
|
321
|
+
payload: {
|
|
322
|
+
requestId,
|
|
323
|
+
models,
|
|
324
|
+
},
|
|
325
|
+
})
|
|
326
|
+
} catch (error) {
|
|
327
|
+
postMessage({
|
|
328
|
+
type: 'provider-models-response',
|
|
329
|
+
payload: {
|
|
330
|
+
requestId,
|
|
331
|
+
models: [],
|
|
332
|
+
error: error instanceof Error ? error.message : String(error),
|
|
333
|
+
},
|
|
334
|
+
})
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async function handleToolExecuteRequest(
|
|
339
|
+
requestId: string,
|
|
340
|
+
payload: { toolId: string; params: Record<string, unknown> }
|
|
341
|
+
): Promise<void> {
|
|
342
|
+
const tool = registeredTools.get(payload.toolId)
|
|
343
|
+
if (!tool) {
|
|
344
|
+
// Send error response
|
|
345
|
+
postMessage({
|
|
346
|
+
type: 'tool-execute-response',
|
|
347
|
+
payload: {
|
|
348
|
+
requestId,
|
|
349
|
+
result: { success: false, error: `Tool ${payload.toolId} not found` },
|
|
350
|
+
error: `Tool ${payload.toolId} not found`,
|
|
351
|
+
},
|
|
352
|
+
})
|
|
353
|
+
return
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
try {
|
|
357
|
+
const result = await tool.execute(payload.params)
|
|
358
|
+
// Send response with result
|
|
359
|
+
postMessage({
|
|
360
|
+
type: 'tool-execute-response',
|
|
361
|
+
payload: {
|
|
362
|
+
requestId,
|
|
363
|
+
result,
|
|
364
|
+
},
|
|
365
|
+
})
|
|
366
|
+
} catch (error) {
|
|
367
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
368
|
+
postMessage({
|
|
369
|
+
type: 'tool-execute-response',
|
|
370
|
+
payload: {
|
|
371
|
+
requestId,
|
|
372
|
+
result: { success: false, error: errorMessage },
|
|
373
|
+
error: errorMessage,
|
|
374
|
+
},
|
|
375
|
+
})
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// ============================================================================
|
|
380
|
+
// Context Building
|
|
381
|
+
// ============================================================================
|
|
382
|
+
|
|
383
|
+
function buildContext(
|
|
384
|
+
extensionId: string,
|
|
385
|
+
extensionVersion: string,
|
|
386
|
+
storagePath: string,
|
|
387
|
+
permissions: string[]
|
|
388
|
+
): ExtensionContext {
|
|
389
|
+
const hasPermission = (perm: string): boolean => {
|
|
390
|
+
return permissions.some((p) => {
|
|
391
|
+
if (p === perm) return true
|
|
392
|
+
if (p.endsWith(':*') && perm.startsWith(p.slice(0, -1))) return true
|
|
393
|
+
return false
|
|
394
|
+
})
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const log: LogAPI = {
|
|
398
|
+
debug: (message, data) => postMessage({ type: 'log', payload: { level: 'debug', message, data } }),
|
|
399
|
+
info: (message, data) => postMessage({ type: 'log', payload: { level: 'info', message, data } }),
|
|
400
|
+
warn: (message, data) => postMessage({ type: 'log', payload: { level: 'warn', message, data } }),
|
|
401
|
+
error: (message, data) => postMessage({ type: 'log', payload: { level: 'error', message, data } }),
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const context: ExtensionContext = {
|
|
405
|
+
extension: {
|
|
406
|
+
id: extensionId,
|
|
407
|
+
version: extensionVersion,
|
|
408
|
+
storagePath,
|
|
409
|
+
},
|
|
410
|
+
log,
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Add network API if permitted
|
|
414
|
+
if (permissions.some((p) => p.startsWith('network:'))) {
|
|
415
|
+
const networkApi: NetworkAPI = {
|
|
416
|
+
async fetch(url: string, options?: RequestInit): Promise<Response> {
|
|
417
|
+
const result = await sendRequest<{ status: number; statusText: string; headers: Record<string, string>; body: string }>('network.fetch', { url, options })
|
|
418
|
+
return new Response(result.body, {
|
|
419
|
+
status: result.status,
|
|
420
|
+
statusText: result.statusText,
|
|
421
|
+
headers: result.headers,
|
|
422
|
+
})
|
|
423
|
+
},
|
|
424
|
+
}
|
|
425
|
+
;(context as { network: NetworkAPI }).network = networkApi
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Add settings API if permitted
|
|
429
|
+
if (hasPermission('settings.register')) {
|
|
430
|
+
const settingsApi: SettingsAPI = {
|
|
431
|
+
async getAll<T extends Record<string, unknown>>(): Promise<T> {
|
|
432
|
+
return sendRequest<T>('settings.getAll', {})
|
|
433
|
+
},
|
|
434
|
+
async get<T>(key: string): Promise<T | undefined> {
|
|
435
|
+
return sendRequest<T | undefined>('settings.get', { key })
|
|
436
|
+
},
|
|
437
|
+
async set(key: string, value: unknown): Promise<void> {
|
|
438
|
+
return sendRequest<void>('settings.set', { key, value })
|
|
439
|
+
},
|
|
440
|
+
onChange(callback: (key: string, value: unknown) => void): Disposable {
|
|
441
|
+
settingsCallbacks.push(callback)
|
|
442
|
+
return {
|
|
443
|
+
dispose: () => {
|
|
444
|
+
const index = settingsCallbacks.indexOf(callback)
|
|
445
|
+
if (index >= 0) settingsCallbacks.splice(index, 1)
|
|
446
|
+
},
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
}
|
|
450
|
+
;(context as { settings: SettingsAPI }).settings = settingsApi
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Add providers API if permitted
|
|
454
|
+
if (hasPermission('provider.register')) {
|
|
455
|
+
const providersApi: ProvidersAPI = {
|
|
456
|
+
register(provider: AIProvider): Disposable {
|
|
457
|
+
registeredProviders.set(provider.id, provider)
|
|
458
|
+
postMessage({
|
|
459
|
+
type: 'provider-registered',
|
|
460
|
+
payload: { id: provider.id, name: provider.name },
|
|
461
|
+
})
|
|
462
|
+
return {
|
|
463
|
+
dispose: () => {
|
|
464
|
+
registeredProviders.delete(provider.id)
|
|
465
|
+
},
|
|
466
|
+
}
|
|
467
|
+
},
|
|
468
|
+
}
|
|
469
|
+
;(context as { providers: ProvidersAPI }).providers = providersApi
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Add tools API if permitted
|
|
473
|
+
if (hasPermission('tools.register')) {
|
|
474
|
+
const toolsApi: ToolsAPI = {
|
|
475
|
+
register(tool: Tool): Disposable {
|
|
476
|
+
registeredTools.set(tool.id, tool)
|
|
477
|
+
postMessage({
|
|
478
|
+
type: 'tool-registered',
|
|
479
|
+
payload: {
|
|
480
|
+
id: tool.id,
|
|
481
|
+
name: tool.name,
|
|
482
|
+
description: tool.description,
|
|
483
|
+
parameters: tool.parameters,
|
|
484
|
+
},
|
|
485
|
+
})
|
|
486
|
+
return {
|
|
487
|
+
dispose: () => {
|
|
488
|
+
registeredTools.delete(tool.id)
|
|
489
|
+
},
|
|
490
|
+
}
|
|
491
|
+
},
|
|
492
|
+
}
|
|
493
|
+
;(context as { tools: ToolsAPI }).tools = toolsApi
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Add database API if permitted
|
|
497
|
+
if (hasPermission('database.own')) {
|
|
498
|
+
const databaseApi: DatabaseAPI = {
|
|
499
|
+
async execute<T = unknown>(sql: string, params?: unknown[]): Promise<T[]> {
|
|
500
|
+
return sendRequest<T[]>('database.execute', { sql, params })
|
|
501
|
+
},
|
|
502
|
+
}
|
|
503
|
+
;(context as { database: DatabaseAPI }).database = databaseApi
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Add storage API if permitted
|
|
507
|
+
if (hasPermission('storage.local')) {
|
|
508
|
+
const storageApi: StorageAPI = {
|
|
509
|
+
async get<T>(key: string): Promise<T | undefined> {
|
|
510
|
+
return sendRequest<T | undefined>('storage.get', { key })
|
|
511
|
+
},
|
|
512
|
+
async set(key: string, value: unknown): Promise<void> {
|
|
513
|
+
return sendRequest<void>('storage.set', { key, value })
|
|
514
|
+
},
|
|
515
|
+
async delete(key: string): Promise<void> {
|
|
516
|
+
return sendRequest<void>('storage.delete', { key })
|
|
517
|
+
},
|
|
518
|
+
async keys(): Promise<string[]> {
|
|
519
|
+
return sendRequest<string[]>('storage.keys', {})
|
|
520
|
+
},
|
|
521
|
+
}
|
|
522
|
+
;(context as { storage: StorageAPI }).storage = storageApi
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
return context
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// ============================================================================
|
|
529
|
+
// Initialization
|
|
530
|
+
// ============================================================================
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Initialize the extension runtime
|
|
534
|
+
* This should be called by the extension's entry point
|
|
535
|
+
*/
|
|
536
|
+
export function initializeExtension(module: ExtensionModule): void {
|
|
537
|
+
extensionModule = module
|
|
538
|
+
|
|
539
|
+
// Set up message listener using the appropriate message port
|
|
540
|
+
messagePort.onMessage(async (message: HostToWorkerMessage) => {
|
|
541
|
+
try {
|
|
542
|
+
await handleHostMessage(message)
|
|
543
|
+
} catch (error) {
|
|
544
|
+
console.error('Error handling message:', error)
|
|
545
|
+
}
|
|
546
|
+
})
|
|
547
|
+
|
|
548
|
+
// Signal that we're ready
|
|
549
|
+
postMessage({ type: 'ready' })
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Re-export types for extensions to use
|
|
553
|
+
export type {
|
|
554
|
+
ExtensionContext,
|
|
555
|
+
ExtensionModule,
|
|
556
|
+
Disposable,
|
|
557
|
+
AIProvider,
|
|
558
|
+
Tool,
|
|
559
|
+
ToolDefinition,
|
|
560
|
+
ToolResult,
|
|
561
|
+
ToolCall,
|
|
562
|
+
ModelInfo,
|
|
563
|
+
ChatMessage,
|
|
564
|
+
ChatOptions,
|
|
565
|
+
GetModelsOptions,
|
|
566
|
+
StreamEvent,
|
|
567
|
+
} from './types.js'
|