dsh-edge 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.
- package/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +183 -0
- package/README.zh.md +183 -0
- package/THIRD_PARTY_NOTICES.md +199 -0
- package/dist/_headers +3 -0
- package/dist/assets/fonts/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/fonts/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/fonts/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/fonts/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/fonts/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/fonts/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/fonts/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/fonts/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/fonts/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/fonts/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/fonts/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/fonts/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/fonts/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/fonts/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/fonts/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/fonts/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/fonts/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/fonts/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/fonts/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/fonts/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/fonts/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/fonts/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/fonts/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/fonts/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/index-C-1AiF3k.js +112 -0
- package/dist/assets/index-CSGf6Qzd.css +1 -0
- package/dist/assets/langs/c-BIGW1oBm.js +2 -0
- package/dist/assets/langs/cpp-DIPi6g--.js +2 -0
- package/dist/assets/langs/csharp-DSvCPggb.js +2 -0
- package/dist/assets/langs/css-CLj8gQPS.js +2 -0
- package/dist/assets/langs/go-C27-OAKa.js +2 -0
- package/dist/assets/langs/html-CfGypltT.js +2 -0
- package/dist/assets/langs/ini-BEwlwnbL.js +2 -0
- package/dist/assets/langs/java-CylS5w8V.js +2 -0
- package/dist/assets/langs/kotlin-BdnUsdx6.js +2 -0
- package/dist/assets/langs/less-B1dDrJ26.js +2 -0
- package/dist/assets/langs/lua-BaeVxFsk.js +2 -0
- package/dist/assets/langs/markdown-Cvjx9yec.js +2 -0
- package/dist/assets/langs/mdx-Cmh6b_Ma.js +2 -0
- package/dist/assets/langs/php-D-uCvoST.js +2 -0
- package/dist/assets/langs/python-B6aJPvgy.js +2 -0
- package/dist/assets/langs/ruby-DEItuUFs.js +2 -0
- package/dist/assets/langs/rust-B1yitclQ.js +2 -0
- package/dist/assets/langs/scss-D5BDwBP9.js +2 -0
- package/dist/assets/langs/sql-CRqJ_cUM.js +2 -0
- package/dist/assets/langs/swift-D82vCrfD.js +2 -0
- package/dist/assets/langs/toml-vGWfd6FD.js +2 -0
- package/dist/assets/langs/xml-sdJ4AIDG.js +2 -0
- package/dist/assets/langs/yaml-Buea-lGh.js +2 -0
- package/dist/assets/vendor-Cjbwl5VI.js +411 -0
- package/dist/assets/vendor-CjyC-hUb.css +1 -0
- package/dist/favicon.svg +8 -0
- package/dist/index.html +35 -0
- package/dist/manifest.webmanifest +16 -0
- package/dist/plugins/@deepseek-ai/dsh-api-gateway/client.js +420 -0
- package/dist/plugins/@deepseek-ai/dsh-api-remotes/client.js +5937 -0
- package/dist/plugins/@deepseek-ai/dsh-client-connection/client.js +10207 -0
- package/dist/plugins/@deepseek-ai/dsh-client-locale/client.js +1231 -0
- package/dist/plugins/@deepseek-ai/dsh-client-modules/client.js +259 -0
- package/dist/plugins/@deepseek-ai/dsh-client-runtime/client.js +10539 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-agent-preset/client.js +1725 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-commands/client.js +1116 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-conversation/client.js +9832 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-deliverables/client.js +373 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-edge/client.js +549 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-input-trigger/client.js +859 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-jobs/client.js +280 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-layout/client.js +456 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-model-selection/client.js +804 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-permission-presets/client.js +461 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-settings/client.js +254 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-settings-general/client.js +609 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-sidebar/client.js +292 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-skill/client.js +327 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-subagent/client.js +710 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-theme/client.js +1312 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-tool/client.js +1630 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-trajectory/client.js +7370 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-user-questions/client.js +700 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-workflow-run/client.js +474 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-workspace/client.js +2426 -0
- package/dist/plugins/@deepseek-ai/dsh-typert-registry/client.js +1372 -0
- package/package.json +97 -0
- package/scripts/assemble-web.mjs +230 -0
- package/scripts/bundle-size.d.mts +2 -0
- package/scripts/bundle-size.mjs +26 -0
- package/scripts/bundle.mjs +53 -0
- package/scripts/cli.d.mts +39 -0
- package/scripts/cli.mjs +496 -0
- package/scripts/install.d.mts +149 -0
- package/scripts/install.mjs +989 -0
- package/scripts/legal-files.mjs +24 -0
- package/scripts/verify-packed.mjs +32 -0
- package/scripts/wrangler-config.d.mts +18 -0
- package/scripts/wrangler-config.mjs +84 -0
- package/src/agent.ts +119 -0
- package/src/auth.ts +352 -0
- package/src/deepseek.ts +231 -0
- package/src/deployment.ts +147 -0
- package/src/direct-shell-core-empty.ts +10 -0
- package/src/direct-shell-protocol.ts +5 -0
- package/src/direct-shell.ts +341 -0
- package/src/do-session-persistence.ts +1064 -0
- package/src/edge-api.ts +796 -0
- package/src/edge-credentials.ts +56 -0
- package/src/edge-remotes.ts +41 -0
- package/src/edge-workspace-store.ts +339 -0
- package/src/http.ts +212 -0
- package/src/index.ts +196 -0
- package/src/instance.ts +1007 -0
- package/src/isolated-direct-shell-unavailable.ts +11 -0
- package/src/protocol.ts +39 -0
- package/src/release.ts +9 -0
- package/src/session-store.ts +1110 -0
- package/src/sse.ts +86 -0
- package/src/web-search.ts +38 -0
- package/src/workspace.ts +270 -0
- package/wrangler.jsonc +56 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/** In-process just-bash backend for the Cloudflare Computer workspace runtime. */
|
|
2
|
+
|
|
3
|
+
import type { BackendHandle, WorkspaceBackend } from '@cloudflare/computer'
|
|
4
|
+
import {
|
|
5
|
+
WorkspaceFsAdapter,
|
|
6
|
+
defineArtifactsCommand,
|
|
7
|
+
defineAssetsCommand,
|
|
8
|
+
defineGitCommand,
|
|
9
|
+
type WorkspaceFs,
|
|
10
|
+
} from '@cloudflare/computer/backends/worker-shell'
|
|
11
|
+
import {
|
|
12
|
+
Bash,
|
|
13
|
+
type BashExecResult,
|
|
14
|
+
type IFileSystem,
|
|
15
|
+
} from 'just-bash/browser'
|
|
16
|
+
import {
|
|
17
|
+
DIRECT_SHELL_OUTPUT_TRUNCATED,
|
|
18
|
+
EDGE_SHELL_OUTPUT_LIMIT_BYTES,
|
|
19
|
+
} from './direct-shell-protocol.ts'
|
|
20
|
+
|
|
21
|
+
const DEFAULT_BACKEND_ID = 'worker-shell'
|
|
22
|
+
const DEFAULT_CWD = '/workspace'
|
|
23
|
+
type DirectBackendHost = Parameters<WorkspaceBackend['connect']>[0]
|
|
24
|
+
type WorkspaceSyncRpc = BackendHandle['rpc']['sync']
|
|
25
|
+
type ShellExec = BackendHandle['rpc']['shell']['exec']
|
|
26
|
+
type ShellExecInput = Parameters<ShellExec>[0]
|
|
27
|
+
type ShellExecEnvelope = Awaited<ReturnType<ShellExec>>
|
|
28
|
+
type ShellExecEvent = ShellExecEnvelope['events'] extends ReadableStream<infer Event>
|
|
29
|
+
? Event
|
|
30
|
+
: never
|
|
31
|
+
|
|
32
|
+
interface DirectBashExecOptions {
|
|
33
|
+
cwd?: string
|
|
34
|
+
env?: Record<string, string>
|
|
35
|
+
stdin?: string
|
|
36
|
+
stdinKind?: 'bytes'
|
|
37
|
+
signal?: AbortSignal
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface DirectExecution {
|
|
41
|
+
controller: AbortController
|
|
42
|
+
done: Promise<void>
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface DirectBashOutcome {
|
|
46
|
+
result: BashExecResult
|
|
47
|
+
terminalResult?: typeof DIRECT_SHELL_OUTPUT_TRUNCATED
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Options for the free in-process shell backend. */
|
|
51
|
+
export interface DirectShellBackendOptions {
|
|
52
|
+
id?: string
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Run just-bash in the owning Durable Object against Computer's local VFS. */
|
|
56
|
+
export class DirectShellBackend implements WorkspaceBackend {
|
|
57
|
+
readonly type = 'direct-shell'
|
|
58
|
+
readonly id: string
|
|
59
|
+
|
|
60
|
+
constructor(options: DirectShellBackendOptions = {}) {
|
|
61
|
+
this.id = options.id ?? DEFAULT_BACKEND_ID
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Connect one workspace-local shell runtime. */
|
|
65
|
+
connect(host: DirectBackendHost): Promise<BackendHandle> {
|
|
66
|
+
const runtime = new DirectShellRuntime(host)
|
|
67
|
+
return Promise.resolve({
|
|
68
|
+
rpc: {
|
|
69
|
+
sync: disabledSyncRpc(),
|
|
70
|
+
shell: {
|
|
71
|
+
exec: input => runtime.exec(input),
|
|
72
|
+
getExec: input => runtime.getExec(input.id),
|
|
73
|
+
killExec: input => runtime.killExec(input.id, input.signal),
|
|
74
|
+
disposeExec: input => runtime.disposeExec(input.id),
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
sync: 'none',
|
|
78
|
+
close: () => runtime.close(),
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
class DirectShellRuntime {
|
|
84
|
+
private readonly fs: WorkspaceFsAdapter
|
|
85
|
+
private readonly executions = new Map<string, DirectExecution>()
|
|
86
|
+
private closed = false
|
|
87
|
+
|
|
88
|
+
constructor(private readonly host: DirectBackendHost) {
|
|
89
|
+
this.fs = new WorkspaceFsAdapter(workspaceFs(host))
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
exec(input: ShellExecInput): Promise<ShellExecEnvelope> {
|
|
93
|
+
if (this.closed) throw directShellError('ECLOSED', 'direct shell backend is closed')
|
|
94
|
+
const id = input.id ?? crypto.randomUUID()
|
|
95
|
+
if (this.executions.has(id)) {
|
|
96
|
+
throw directShellError('EEXEC_BUSY', `execution ${id} is running`)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const controller = new AbortController()
|
|
100
|
+
const result = Promise.resolve().then(() => this.run(input, controller.signal))
|
|
101
|
+
const done = result.then(() => undefined, () => undefined)
|
|
102
|
+
this.executions.set(id, { controller, done })
|
|
103
|
+
return Promise.resolve({
|
|
104
|
+
id,
|
|
105
|
+
events: executionEvents(
|
|
106
|
+
id,
|
|
107
|
+
result,
|
|
108
|
+
() => { this.deleteExecution(id, controller) },
|
|
109
|
+
() => this.disposeExec(id),
|
|
110
|
+
),
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
getExec(id: string): Promise<ShellExecEnvelope> {
|
|
115
|
+
throw directShellError('ENOENT', `no such exec: ${id}`)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
killExec(id: string, signal = 'SIGTERM'): Promise<void> {
|
|
119
|
+
this.executions.get(id)?.controller.abort(
|
|
120
|
+
directShellError('EEXEC_CANCELLED', `execution cancelled with ${signal}`),
|
|
121
|
+
)
|
|
122
|
+
return Promise.resolve()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async disposeExec(id: string): Promise<void> {
|
|
126
|
+
const execution = this.executions.get(id)
|
|
127
|
+
if (execution === undefined) return
|
|
128
|
+
execution.controller.abort(
|
|
129
|
+
directShellError('EEXEC_DISPOSED', `execution disposed: ${id}`),
|
|
130
|
+
)
|
|
131
|
+
await execution.done
|
|
132
|
+
this.deleteExecution(id, execution.controller)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async close(): Promise<void> {
|
|
136
|
+
if (this.closed) return
|
|
137
|
+
this.closed = true
|
|
138
|
+
const active = [...this.executions.values()]
|
|
139
|
+
for (const execution of active) {
|
|
140
|
+
execution.controller.abort(directShellError('ECLOSED', 'direct shell backend is closing'))
|
|
141
|
+
}
|
|
142
|
+
await Promise.allSettled(active.map(execution => execution.done))
|
|
143
|
+
this.executions.clear()
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private async run(input: ShellExecInput, signal: AbortSignal): Promise<DirectBashOutcome> {
|
|
147
|
+
const timeoutLimit = input.timeoutMs !== undefined && input.timeoutMs > 0
|
|
148
|
+
? { maxExecutionTimeMs: input.timeoutMs }
|
|
149
|
+
: {}
|
|
150
|
+
const bash = new Bash({
|
|
151
|
+
// Computer and just-bash accept the same runtime encodings, but their
|
|
152
|
+
// interfaces spell the optional UTF-8 alias set differently.
|
|
153
|
+
fs: this.fs as unknown as IFileSystem,
|
|
154
|
+
cwd: input.cwd ?? DEFAULT_CWD,
|
|
155
|
+
...(input.env === undefined ? {} : { env: input.env }),
|
|
156
|
+
customCommands: [
|
|
157
|
+
defineGitCommand(this.host),
|
|
158
|
+
defineAssetsCommand({}),
|
|
159
|
+
defineArtifactsCommand(this.host),
|
|
160
|
+
],
|
|
161
|
+
defenseInDepth: { enabled: 'auto' },
|
|
162
|
+
executionLimitProfile: 'hardened',
|
|
163
|
+
executionLimits: {
|
|
164
|
+
maxOutputSize: EDGE_SHELL_OUTPUT_LIMIT_BYTES,
|
|
165
|
+
...timeoutLimit,
|
|
166
|
+
},
|
|
167
|
+
})
|
|
168
|
+
const directBash = bash as unknown as {
|
|
169
|
+
exec(command: string, options: DirectBashExecOptions): Promise<BashExecResult>
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
const result = await directBash.exec(input.source, {
|
|
173
|
+
cwd: input.cwd ?? DEFAULT_CWD,
|
|
174
|
+
...(input.env === undefined ? {} : { env: input.env }),
|
|
175
|
+
...(input.stdin === undefined
|
|
176
|
+
? {}
|
|
177
|
+
: { stdin: latin1FromBytes(input.stdin), stdinKind: 'bytes' as const }),
|
|
178
|
+
signal,
|
|
179
|
+
})
|
|
180
|
+
return {
|
|
181
|
+
result,
|
|
182
|
+
...(hitDirectOutputLimit(result)
|
|
183
|
+
? { terminalResult: DIRECT_SHELL_OUTPUT_TRUNCATED }
|
|
184
|
+
: {}),
|
|
185
|
+
}
|
|
186
|
+
} catch (error) {
|
|
187
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
188
|
+
return {
|
|
189
|
+
result: {
|
|
190
|
+
stdout: '',
|
|
191
|
+
stderr: `${message}\n`,
|
|
192
|
+
exitCode: signal.aborted ? 130 : 1,
|
|
193
|
+
env: input.env ?? {},
|
|
194
|
+
},
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private deleteExecution(id: string, controller: AbortController): void {
|
|
200
|
+
if (this.executions.get(id)?.controller === controller) this.executions.delete(id)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function workspaceFs(host: DirectBackendHost): WorkspaceFs {
|
|
205
|
+
const fs = host.fs
|
|
206
|
+
return {
|
|
207
|
+
readFile: fs.readFile.bind(fs),
|
|
208
|
+
exists: async path => await statOrNull(() => fs.stat(path)) !== null,
|
|
209
|
+
stat: path => fs.stat(path),
|
|
210
|
+
statOrNull: path => statOrNull(() => fs.stat(path)),
|
|
211
|
+
lstat: path => fs.lstat(path),
|
|
212
|
+
lstatOrNull: path => statOrNull(() => fs.lstat(path)),
|
|
213
|
+
readdir: path => fs.readdir(path),
|
|
214
|
+
find: (directory, pattern) => fs.find(directory, pattern),
|
|
215
|
+
ls: prefix => fs.ls(prefix),
|
|
216
|
+
grep: (pattern, path, options) => fs.grep(pattern, path, options),
|
|
217
|
+
readlink: path => fs.readlink(path),
|
|
218
|
+
writeFile: (path, content, options) => fs.writeFile(path, content, options),
|
|
219
|
+
mkdir: (path, options) => fs.mkdir(path, options),
|
|
220
|
+
rm: (path, options) => fs.rm(path, options),
|
|
221
|
+
chmod: (path, mode) => fs.chmod(path, mode),
|
|
222
|
+
symlink: (target, path) => fs.symlink(target, path),
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function statOrNull<T>(read: () => Promise<T>): Promise<T | null> {
|
|
227
|
+
try {
|
|
228
|
+
return await read()
|
|
229
|
+
} catch (error) {
|
|
230
|
+
if (hasErrorCode(error, 'ENOENT')) return null
|
|
231
|
+
throw error
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function hasErrorCode(error: unknown, code: string): boolean {
|
|
236
|
+
return typeof error === 'object'
|
|
237
|
+
&& error !== null
|
|
238
|
+
&& 'code' in error
|
|
239
|
+
&& error.code === code
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function executionEvents(
|
|
243
|
+
id: string,
|
|
244
|
+
result: Promise<DirectBashOutcome>,
|
|
245
|
+
onTerminal: () => void,
|
|
246
|
+
onCancel: () => Promise<void>,
|
|
247
|
+
): ReadableStream<ShellExecEvent> {
|
|
248
|
+
const encoder = new TextEncoder()
|
|
249
|
+
let events: ShellExecEvent[] | undefined
|
|
250
|
+
return new ReadableStream({
|
|
251
|
+
async pull(controller) {
|
|
252
|
+
try {
|
|
253
|
+
events ??= shellExecEvents(id, encoder, await result)
|
|
254
|
+
const event = events.shift()
|
|
255
|
+
if (event === undefined) return
|
|
256
|
+
controller.enqueue(event)
|
|
257
|
+
if (event.name === 'exit') {
|
|
258
|
+
controller.close()
|
|
259
|
+
onTerminal()
|
|
260
|
+
}
|
|
261
|
+
} catch (error) {
|
|
262
|
+
controller.error(error)
|
|
263
|
+
onTerminal()
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
cancel: onCancel,
|
|
267
|
+
}, { highWaterMark: 0 })
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function shellExecEvents(
|
|
271
|
+
id: string,
|
|
272
|
+
encoder: TextEncoder,
|
|
273
|
+
outcome: DirectBashOutcome,
|
|
274
|
+
): ShellExecEvent[] {
|
|
275
|
+
const { result } = outcome
|
|
276
|
+
const events: ShellExecEvent[] = []
|
|
277
|
+
let seq = 0
|
|
278
|
+
if (result.stdout.length > 0) {
|
|
279
|
+
events.push({ id, seq: ++seq, name: 'stdout', value: encoder.encode(result.stdout) })
|
|
280
|
+
}
|
|
281
|
+
if (result.stderr.length > 0) {
|
|
282
|
+
events.push({ id, seq: ++seq, name: 'stderr', value: encoder.encode(result.stderr) })
|
|
283
|
+
}
|
|
284
|
+
events.push({
|
|
285
|
+
id,
|
|
286
|
+
seq: ++seq,
|
|
287
|
+
name: 'exit',
|
|
288
|
+
code: result.exitCode,
|
|
289
|
+
...(outcome.terminalResult === undefined ? {} : { result: outcome.terminalResult }),
|
|
290
|
+
})
|
|
291
|
+
return events
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function hitDirectOutputLimit(result: BashExecResult): boolean {
|
|
295
|
+
if (result.exitCode !== 126) return false
|
|
296
|
+
const accountedBytes = result.internalOutputAccounting?.stderr
|
|
297
|
+
if (accountedBytes === undefined) return false
|
|
298
|
+
const stderr = new TextEncoder().encode(result.stderr)
|
|
299
|
+
if (accountedBytes < 0 || accountedBytes > stderr.byteLength) return false
|
|
300
|
+
const interpreterDiagnostic = new TextDecoder().decode(stderr.subarray(accountedBytes))
|
|
301
|
+
return [
|
|
302
|
+
`limit exceeded (${EDGE_SHELL_OUTPUT_LIMIT_BYTES} bytes)`,
|
|
303
|
+
`total output size exceeded (>${EDGE_SHELL_OUTPUT_LIMIT_BYTES} bytes), `
|
|
304
|
+
+ 'increase executionLimits.maxOutputSize',
|
|
305
|
+
].some(diagnostic => interpreterDiagnostic.includes(diagnostic))
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function latin1FromBytes(bytes: Uint8Array): string {
|
|
309
|
+
let result = ''
|
|
310
|
+
for (const byte of bytes) result += String.fromCharCode(byte)
|
|
311
|
+
return result
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function directShellError(code: string, message: string): Error & { code: string } {
|
|
315
|
+
const error = new Error(message) as Error & { code: string }
|
|
316
|
+
error.name = 'DirectShellError'
|
|
317
|
+
error.code = code
|
|
318
|
+
return error
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function disabledSyncRpc(): WorkspaceSyncRpc {
|
|
322
|
+
const refuse = (name: string): never => {
|
|
323
|
+
throw directShellError(
|
|
324
|
+
'ENOSYS',
|
|
325
|
+
`DirectShellBackend: sync.${name} must not be called because sync is disabled`,
|
|
326
|
+
)
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
push: () => refuse('push'),
|
|
330
|
+
fetchChanges: () => refuse('fetchChanges'),
|
|
331
|
+
watermarks: () => refuse('watermarks'),
|
|
332
|
+
readEntry: () => refuse('readEntry'),
|
|
333
|
+
hasObjects: () => refuse('hasObjects'),
|
|
334
|
+
fetchObjects: () => new ReadableStream({
|
|
335
|
+
start(controller) {
|
|
336
|
+
controller.error(directShellError('ENOSYS', 'DirectShellBackend: sync is disabled'))
|
|
337
|
+
},
|
|
338
|
+
}),
|
|
339
|
+
pushObjects: () => refuse('pushObjects'),
|
|
340
|
+
}
|
|
341
|
+
}
|