dsh-browser-application 0.37.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 (44) hide show
  1. package/LICENSE +19 -0
  2. package/README.md +27 -0
  3. package/cordis.patch.yml +29 -0
  4. package/lib/index.js +3377 -0
  5. package/lib/invariant.js +26 -0
  6. package/lib/types/bridge-url.d.ts +27 -0
  7. package/lib/types/browser-context.d.ts +38 -0
  8. package/lib/types/dsh-gateway.d.ts +42 -0
  9. package/lib/types/event-generation.d.ts +56 -0
  10. package/lib/types/extension-sessions.d.ts +26 -0
  11. package/lib/types/host-api.d.ts +47 -0
  12. package/lib/types/image-relay.d.ts +43 -0
  13. package/lib/types/index.d.ts +158 -0
  14. package/lib/types/invariant.d.ts +16 -0
  15. package/lib/types/remote-host-api.d.ts +12 -0
  16. package/lib/types/server.d.ts +166 -0
  17. package/lib/types/session-deferral.d.ts +33 -0
  18. package/lib/types/session-history.d.ts +30 -0
  19. package/lib/types/session-purge.d.ts +55 -0
  20. package/lib/types/session-workspace.d.ts +37 -0
  21. package/lib/types/token.d.ts +57 -0
  22. package/lib/types/tools.d.ts +42 -0
  23. package/lib/types/vision-selfcheck.d.ts +18 -0
  24. package/lib/types/vision.d.ts +57 -0
  25. package/package.json +95 -0
  26. package/src/bridge-url.ts +57 -0
  27. package/src/browser-context.ts +102 -0
  28. package/src/dsh-gateway.ts +66 -0
  29. package/src/event-generation.ts +385 -0
  30. package/src/extension-sessions.ts +40 -0
  31. package/src/host-api.ts +64 -0
  32. package/src/image-relay.ts +118 -0
  33. package/src/index.ts +575 -0
  34. package/src/invariant.ts +33 -0
  35. package/src/remote-host-api.ts +397 -0
  36. package/src/server.ts +658 -0
  37. package/src/session-deferral.ts +296 -0
  38. package/src/session-history.ts +220 -0
  39. package/src/session-purge.ts +154 -0
  40. package/src/session-workspace.ts +147 -0
  41. package/src/token.ts +100 -0
  42. package/src/tools.ts +301 -0
  43. package/src/vision-selfcheck.ts +35 -0
  44. package/src/vision.ts +135 -0
package/src/vision.ts ADDED
@@ -0,0 +1,135 @@
1
+ /**
2
+ * The multimodal call the desktop makes on the extension's behalf.
3
+ *
4
+ * Only the desktop can hold the model credential without putting it in a browser
5
+ * profile, and only the desktop can reach the network through the machine's own
6
+ * proxy, VPN, or client certificate. This module is the transport: the prompt,
7
+ * the request body, and the response parser are shared with the extension's own
8
+ * outbound path, so "which side calls the model" changes nothing about what is
9
+ * asked or what counts as an answer.
10
+ *
11
+ * @module
12
+ */
13
+
14
+ import {
15
+ buildVisionRequestBody,
16
+ parseVisionResponse,
17
+ reasoningTokensOf,
18
+ type VisionCallConfig,
19
+ type VisionCallContext,
20
+ type VisionCallImage,
21
+ type VisionCallResult,
22
+ } from '@dsh-browser/protocol'
23
+
24
+ export type { VisionCallContext, VisionCallResult }
25
+
26
+ /** A 1×1 transparent PNG: the smallest thing a vision endpoint will accept. */
27
+ export const PROBE_IMAGE_BASE64 =
28
+ 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=='
29
+
30
+ /** What a probe learned about the deployment's cost controls. */
31
+ export interface VisionProbe {
32
+ ok: boolean
33
+ /** Reasoning tokens the response reported; 0 when none or unreported. */
34
+ reasoningTokens: number
35
+ message: string
36
+ }
37
+
38
+ export interface VisionConfig extends VisionCallConfig {
39
+ /** Chat-completions base URL. */
40
+ baseUrl: string
41
+ apiKey: string
42
+ timeoutMs: number
43
+ }
44
+
45
+ export interface VisionRequest extends VisionCallImage {
46
+ context: VisionCallContext
47
+ }
48
+
49
+ export type VisionResult = VisionCallResult
50
+
51
+ export class VisionClient {
52
+ private readonly config: VisionConfig
53
+ private readonly fetchImpl: typeof fetch
54
+
55
+ constructor(config: VisionConfig, fetchImpl: typeof fetch = fetch) {
56
+ this.config = config
57
+ this.fetchImpl = fetchImpl
58
+ }
59
+
60
+ /**
61
+ * Describe one image.
62
+ *
63
+ * @param request - normalized bytes plus the page context around them.
64
+ * @returns one line of description, or a classified failure.
65
+ */
66
+ async describe(request: VisionRequest): Promise<VisionResult> {
67
+ const endpoint = `${this.config.baseUrl.replace(/\/$/, '')}/chat/completions`
68
+ let response: Response
69
+ try {
70
+ response = await this.fetchImpl(endpoint, {
71
+ method: 'POST',
72
+ headers: {
73
+ 'content-type': 'application/json',
74
+ authorization: `Bearer ${this.config.apiKey}`,
75
+ },
76
+ body: JSON.stringify(buildVisionRequestBody(this.config, request, request.context)),
77
+ signal: AbortSignal.timeout(this.config.timeoutMs),
78
+ })
79
+ } catch (error: unknown) {
80
+ return { ok: false, code: 'vision-network', message: error instanceof Error ? error.message : String(error) }
81
+ }
82
+
83
+ if (!response.ok) {
84
+ const detail = await response.text().catch(() => '')
85
+ return { ok: false, code: 'vision-http', message: `${String(response.status)} ${detail.slice(0, 200)}` }
86
+ }
87
+
88
+ let payload: unknown
89
+ try {
90
+ payload = await response.json()
91
+ } catch (error: unknown) {
92
+ return { ok: false, code: 'vision-bad-response', message: error instanceof Error ? error.message : String(error) }
93
+ }
94
+ return parseVisionResponse(payload)
95
+ }
96
+
97
+ /**
98
+ * Send one minimal request and read the billing back.
99
+ *
100
+ * A thinking switch can be ignored silently — the request succeeds and the
101
+ * answer looks fine, while every image costs several times what it should. The
102
+ * only way to know is to look at `usage`, so this sends the smallest possible
103
+ * image once and reports what came back.
104
+ *
105
+ * @returns what the provider reported, or why the probe could not run.
106
+ */
107
+ async probe(): Promise<VisionProbe> {
108
+ const endpoint = `${this.config.baseUrl.replace(/\/$/, '')}/chat/completions`
109
+ let response: Response
110
+ try {
111
+ response = await this.fetchImpl(endpoint, {
112
+ method: 'POST',
113
+ headers: {
114
+ 'content-type': 'application/json',
115
+ authorization: `Bearer ${this.config.apiKey}`,
116
+ },
117
+ body: JSON.stringify(buildVisionRequestBody(
118
+ this.config,
119
+ { base64: PROBE_IMAGE_BASE64, mediaType: 'image/png' },
120
+ { alt: '', near: '', heading: '', kind: 'icon' },
121
+ )),
122
+ signal: AbortSignal.timeout(this.config.timeoutMs),
123
+ })
124
+ } catch (error: unknown) {
125
+ return { ok: false, reasoningTokens: 0, message: error instanceof Error ? error.message : String(error) }
126
+ }
127
+ if (!response.ok) return { ok: false, reasoningTokens: 0, message: `HTTP ${String(response.status)}` }
128
+ try {
129
+ const payload = await response.json() as { usage?: Record<string, unknown> }
130
+ return { ok: true, reasoningTokens: reasoningTokensOf(payload.usage), message: 'ok' }
131
+ } catch (error: unknown) {
132
+ return { ok: false, reasoningTokens: 0, message: error instanceof Error ? error.message : String(error) }
133
+ }
134
+ }
135
+ }