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.
Files changed (159) hide show
  1. package/LICENSE +21 -0
  2. package/README.i18n.yaml +6 -0
  3. package/README.md +183 -0
  4. package/README.zh.md +183 -0
  5. package/THIRD_PARTY_NOTICES.md +199 -0
  6. package/dist/_headers +3 -0
  7. package/dist/assets/fonts/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  8. package/dist/assets/fonts/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  9. package/dist/assets/fonts/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  10. package/dist/assets/fonts/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  11. package/dist/assets/fonts/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  12. package/dist/assets/fonts/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  13. package/dist/assets/fonts/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  14. package/dist/assets/fonts/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  15. package/dist/assets/fonts/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  16. package/dist/assets/fonts/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  17. package/dist/assets/fonts/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  18. package/dist/assets/fonts/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  19. package/dist/assets/fonts/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  20. package/dist/assets/fonts/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  21. package/dist/assets/fonts/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  22. package/dist/assets/fonts/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  23. package/dist/assets/fonts/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  24. package/dist/assets/fonts/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  25. package/dist/assets/fonts/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  26. package/dist/assets/fonts/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  27. package/dist/assets/fonts/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  28. package/dist/assets/fonts/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  29. package/dist/assets/fonts/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  30. package/dist/assets/fonts/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  31. package/dist/assets/fonts/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  32. package/dist/assets/fonts/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  33. package/dist/assets/fonts/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  34. package/dist/assets/fonts/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  35. package/dist/assets/fonts/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  36. package/dist/assets/fonts/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  37. package/dist/assets/fonts/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  38. package/dist/assets/fonts/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  39. package/dist/assets/fonts/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  40. package/dist/assets/fonts/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  41. package/dist/assets/fonts/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  42. package/dist/assets/fonts/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  43. package/dist/assets/fonts/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  44. package/dist/assets/fonts/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  45. package/dist/assets/fonts/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  46. package/dist/assets/fonts/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  47. package/dist/assets/fonts/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  48. package/dist/assets/fonts/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  49. package/dist/assets/fonts/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  50. package/dist/assets/fonts/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  51. package/dist/assets/fonts/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  52. package/dist/assets/fonts/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  53. package/dist/assets/fonts/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  54. package/dist/assets/fonts/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  55. package/dist/assets/fonts/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  56. package/dist/assets/fonts/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  57. package/dist/assets/fonts/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  58. package/dist/assets/fonts/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  59. package/dist/assets/fonts/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  60. package/dist/assets/fonts/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  61. package/dist/assets/fonts/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  62. package/dist/assets/fonts/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  63. package/dist/assets/fonts/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  64. package/dist/assets/fonts/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  65. package/dist/assets/fonts/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  66. package/dist/assets/index-C-1AiF3k.js +112 -0
  67. package/dist/assets/index-CSGf6Qzd.css +1 -0
  68. package/dist/assets/langs/c-BIGW1oBm.js +2 -0
  69. package/dist/assets/langs/cpp-DIPi6g--.js +2 -0
  70. package/dist/assets/langs/csharp-DSvCPggb.js +2 -0
  71. package/dist/assets/langs/css-CLj8gQPS.js +2 -0
  72. package/dist/assets/langs/go-C27-OAKa.js +2 -0
  73. package/dist/assets/langs/html-CfGypltT.js +2 -0
  74. package/dist/assets/langs/ini-BEwlwnbL.js +2 -0
  75. package/dist/assets/langs/java-CylS5w8V.js +2 -0
  76. package/dist/assets/langs/kotlin-BdnUsdx6.js +2 -0
  77. package/dist/assets/langs/less-B1dDrJ26.js +2 -0
  78. package/dist/assets/langs/lua-BaeVxFsk.js +2 -0
  79. package/dist/assets/langs/markdown-Cvjx9yec.js +2 -0
  80. package/dist/assets/langs/mdx-Cmh6b_Ma.js +2 -0
  81. package/dist/assets/langs/php-D-uCvoST.js +2 -0
  82. package/dist/assets/langs/python-B6aJPvgy.js +2 -0
  83. package/dist/assets/langs/ruby-DEItuUFs.js +2 -0
  84. package/dist/assets/langs/rust-B1yitclQ.js +2 -0
  85. package/dist/assets/langs/scss-D5BDwBP9.js +2 -0
  86. package/dist/assets/langs/sql-CRqJ_cUM.js +2 -0
  87. package/dist/assets/langs/swift-D82vCrfD.js +2 -0
  88. package/dist/assets/langs/toml-vGWfd6FD.js +2 -0
  89. package/dist/assets/langs/xml-sdJ4AIDG.js +2 -0
  90. package/dist/assets/langs/yaml-Buea-lGh.js +2 -0
  91. package/dist/assets/vendor-Cjbwl5VI.js +411 -0
  92. package/dist/assets/vendor-CjyC-hUb.css +1 -0
  93. package/dist/favicon.svg +8 -0
  94. package/dist/index.html +35 -0
  95. package/dist/manifest.webmanifest +16 -0
  96. package/dist/plugins/@deepseek-ai/dsh-api-gateway/client.js +420 -0
  97. package/dist/plugins/@deepseek-ai/dsh-api-remotes/client.js +5937 -0
  98. package/dist/plugins/@deepseek-ai/dsh-client-connection/client.js +10207 -0
  99. package/dist/plugins/@deepseek-ai/dsh-client-locale/client.js +1231 -0
  100. package/dist/plugins/@deepseek-ai/dsh-client-modules/client.js +259 -0
  101. package/dist/plugins/@deepseek-ai/dsh-client-runtime/client.js +10539 -0
  102. package/dist/plugins/@deepseek-ai/dsh-client-ui-agent-preset/client.js +1725 -0
  103. package/dist/plugins/@deepseek-ai/dsh-client-ui-commands/client.js +1116 -0
  104. package/dist/plugins/@deepseek-ai/dsh-client-ui-conversation/client.js +9832 -0
  105. package/dist/plugins/@deepseek-ai/dsh-client-ui-deliverables/client.js +373 -0
  106. package/dist/plugins/@deepseek-ai/dsh-client-ui-edge/client.js +549 -0
  107. package/dist/plugins/@deepseek-ai/dsh-client-ui-input-trigger/client.js +859 -0
  108. package/dist/plugins/@deepseek-ai/dsh-client-ui-jobs/client.js +280 -0
  109. package/dist/plugins/@deepseek-ai/dsh-client-ui-layout/client.js +456 -0
  110. package/dist/plugins/@deepseek-ai/dsh-client-ui-model-selection/client.js +804 -0
  111. package/dist/plugins/@deepseek-ai/dsh-client-ui-permission-presets/client.js +461 -0
  112. package/dist/plugins/@deepseek-ai/dsh-client-ui-settings/client.js +254 -0
  113. package/dist/plugins/@deepseek-ai/dsh-client-ui-settings-general/client.js +609 -0
  114. package/dist/plugins/@deepseek-ai/dsh-client-ui-sidebar/client.js +292 -0
  115. package/dist/plugins/@deepseek-ai/dsh-client-ui-skill/client.js +327 -0
  116. package/dist/plugins/@deepseek-ai/dsh-client-ui-subagent/client.js +710 -0
  117. package/dist/plugins/@deepseek-ai/dsh-client-ui-theme/client.js +1312 -0
  118. package/dist/plugins/@deepseek-ai/dsh-client-ui-tool/client.js +1630 -0
  119. package/dist/plugins/@deepseek-ai/dsh-client-ui-trajectory/client.js +7370 -0
  120. package/dist/plugins/@deepseek-ai/dsh-client-ui-user-questions/client.js +700 -0
  121. package/dist/plugins/@deepseek-ai/dsh-client-ui-workflow-run/client.js +474 -0
  122. package/dist/plugins/@deepseek-ai/dsh-client-ui-workspace/client.js +2426 -0
  123. package/dist/plugins/@deepseek-ai/dsh-typert-registry/client.js +1372 -0
  124. package/package.json +97 -0
  125. package/scripts/assemble-web.mjs +230 -0
  126. package/scripts/bundle-size.d.mts +2 -0
  127. package/scripts/bundle-size.mjs +26 -0
  128. package/scripts/bundle.mjs +53 -0
  129. package/scripts/cli.d.mts +39 -0
  130. package/scripts/cli.mjs +496 -0
  131. package/scripts/install.d.mts +149 -0
  132. package/scripts/install.mjs +989 -0
  133. package/scripts/legal-files.mjs +24 -0
  134. package/scripts/verify-packed.mjs +32 -0
  135. package/scripts/wrangler-config.d.mts +18 -0
  136. package/scripts/wrangler-config.mjs +84 -0
  137. package/src/agent.ts +119 -0
  138. package/src/auth.ts +352 -0
  139. package/src/deepseek.ts +231 -0
  140. package/src/deployment.ts +147 -0
  141. package/src/direct-shell-core-empty.ts +10 -0
  142. package/src/direct-shell-protocol.ts +5 -0
  143. package/src/direct-shell.ts +341 -0
  144. package/src/do-session-persistence.ts +1064 -0
  145. package/src/edge-api.ts +796 -0
  146. package/src/edge-credentials.ts +56 -0
  147. package/src/edge-remotes.ts +41 -0
  148. package/src/edge-workspace-store.ts +339 -0
  149. package/src/http.ts +212 -0
  150. package/src/index.ts +196 -0
  151. package/src/instance.ts +1007 -0
  152. package/src/isolated-direct-shell-unavailable.ts +11 -0
  153. package/src/protocol.ts +39 -0
  154. package/src/release.ts +9 -0
  155. package/src/session-store.ts +1110 -0
  156. package/src/sse.ts +86 -0
  157. package/src/web-search.ts +38 -0
  158. package/src/workspace.ts +270 -0
  159. package/wrangler.jsonc +56 -0
@@ -0,0 +1,796 @@
1
+ /** Upstream ApiProxy implementation over one Cloudflare DSH instance. */
2
+
3
+ import type {
4
+ ApiProxy,
5
+ CredentialView,
6
+ HistoryEntry,
7
+ ModelProviderGroup,
8
+ PromptContentPart,
9
+ QueueAction,
10
+ RpcError,
11
+ RpcRequest,
12
+ RpcResponse,
13
+ SessionProjectionsBlock,
14
+ SessionSummary,
15
+ WorkspaceId,
16
+ WorkspaceView,
17
+ } from '@deepseek-ai/dsh-host-apiproxy/api'
18
+ import type { MessageId } from '@deepseek-ai/dsh-llm'
19
+ import type { SessionEvent, SessionId } from '@deepseek-ai/dsh-session'
20
+ import { EDGE_SYSTEM_PROMPT } from './agent.ts'
21
+ import type { EdgeDeploymentProfile } from './deployment.ts'
22
+ import type { EdgeApiSessionSummary, EdgeSessionStore } from './session-store.ts'
23
+ import { EdgeSessionStoreError } from './session-store.ts'
24
+ import {
25
+ EDGE_WORKSPACE_ID,
26
+ EDGE_WORKSPACE_PATH,
27
+ EdgeWorkspaceStoreError,
28
+ } from './edge-workspace-store.ts'
29
+
30
+ const EDGE_PROVIDER = 'deepseek-official'
31
+ const DEFAULT_HISTORY_MESSAGES = 50
32
+ /** Maximum UTF-8 text accepted by message-bearing prompt and queue-edit operations. */
33
+ export const MAX_MESSAGE_TEXT_BYTES = 65_536
34
+
35
+ const textEncoder = new TextEncoder()
36
+
37
+ /**
38
+ * Count the UTF-8 bytes carried by one text-only message operation.
39
+ * @param content - Prompt or queue-edit text blocks.
40
+ * @returns The combined encoded byte length.
41
+ */
42
+ export function messageTextByteLength(
43
+ content: readonly Extract<PromptContentPart, { type: 'text' }>[],
44
+ ): number {
45
+ return content.reduce((total, part) => total + textEncoder.encode(part.text).byteLength, 0)
46
+ }
47
+
48
+ /** Runtime operations that turn typed API calls into one DO's live work. */
49
+ export interface EdgeApiRuntime {
50
+ readonly sessions: EdgeSessionStore
51
+ readonly model: string
52
+ readonly version: string
53
+ deploymentProfile(): EdgeDeploymentProfile
54
+ describeCredential(ref: string): Promise<CredentialView>
55
+ isRunning(sessionId: SessionId): boolean
56
+ prompt(input: {
57
+ sessionId: SessionId
58
+ mode: 'queue' | 'steer'
59
+ content: Extract<PromptContentPart, { type: 'text' }>[]
60
+ rpcId: RpcRequest<unknown>['rpcId']
61
+ clientTimeZone?: string
62
+ }): Promise<void>
63
+ updateQueue(
64
+ sessionId: SessionId,
65
+ itemId: MessageId,
66
+ action: QueueAction,
67
+ ): 'accepted' | 'queue-item-not-found' | 'steer-unavailable'
68
+ cancel(sessionId: SessionId): boolean
69
+ workspaceList(sessions: readonly EdgeApiSessionSummary[]): Promise<{
70
+ items: WorkspaceView[]
71
+ archivedSessionIds: SessionId[]
72
+ }>
73
+ workspaceCreate(path: string): Promise<{ workspace: WorkspaceView; created: boolean }>
74
+ workspaceRename(workspaceId: WorkspaceId, title: string): Promise<WorkspaceView>
75
+ workspaceDelete(workspaceId: WorkspaceId): Promise<void>
76
+ workspaceInsertBefore(workspaceId: WorkspaceId, beforeWorkspaceId?: WorkspaceId): Promise<WorkspaceId[]>
77
+ workspaceInsertSessionBefore(
78
+ workspaceId: WorkspaceId,
79
+ sessionId: SessionId,
80
+ beforeSessionId?: SessionId,
81
+ ): Promise<WorkspaceView>
82
+ archiveSession(sessionId: SessionId): Promise<SessionId[]>
83
+ sessionCreated(session: EdgeApiSessionSummary): void
84
+ sessionAttached(session: EdgeApiSessionSummary, workspaceId: WorkspaceId): Promise<void>
85
+ workspaceForSession(sessionId: SessionId): Promise<WorkspaceId | undefined>
86
+ sessionEvent(sessionId: SessionId, event: SessionEvent): void
87
+ }
88
+
89
+ /** Build the typed upstream API for one isolated Edge instance. */
90
+ export function createEdgeApi(runtime: EdgeApiRuntime): ApiProxy {
91
+ const modelGroups = (): ModelProviderGroup[] => [{
92
+ id: EDGE_PROVIDER,
93
+ name: 'DeepSeek',
94
+ models: [{ id: runtime.model, name: runtime.model }],
95
+ }]
96
+
97
+ const api: ApiProxy = {
98
+ sessions: {
99
+ async list(request) {
100
+ const sessions = await runtime.sessions.listApiSessions()
101
+ return ok(request, { items: sessions.map(summary => sessionSummary(runtime, summary)) })
102
+ },
103
+
104
+ async search(request, signal) {
105
+ try {
106
+ return ok(request, await runtime.sessions.searchApiSessions(request.payload.query, signal))
107
+ } catch (error) {
108
+ if (signal.aborted || (error instanceof DOMException && error.name === 'AbortError')) {
109
+ return fail(request, {
110
+ code: 'cancelled',
111
+ message: 'Session search was aborted.',
112
+ details: {},
113
+ })
114
+ }
115
+ return fail(request, {
116
+ code: 'internal',
117
+ message: error instanceof Error ? error.message : String(error),
118
+ details: {},
119
+ })
120
+ }
121
+ },
122
+
123
+ async create(request) {
124
+ const { workspaceId, cwd, sessionId, agentPreset } = request.payload
125
+ if (workspaceId !== undefined && workspaceId !== EDGE_WORKSPACE_ID) {
126
+ return fail(request, {
127
+ code: 'workspace-not-found',
128
+ message: `Workspace "${workspaceId}" is not available in this Edge instance.`,
129
+ details: { workspaceId },
130
+ })
131
+ }
132
+ const available = await runtime.workspaceList(await runtime.sessions.listApiSessions())
133
+ if (workspaceId !== undefined) {
134
+ if (!available.items.some(item => item.workspaceId === workspaceId)) {
135
+ return fail(request, {
136
+ code: 'workspace-not-found',
137
+ message: `Workspace "${workspaceId}" is not available in this Edge instance.`,
138
+ details: { workspaceId },
139
+ })
140
+ }
141
+ }
142
+ if (cwd !== undefined && cwd !== EDGE_WORKSPACE_PATH) {
143
+ return fail(request, {
144
+ code: 'workspace-invalid-path',
145
+ message: `Edge sessions must use ${EDGE_WORKSPACE_PATH}.`,
146
+ details: { path: cwd },
147
+ })
148
+ }
149
+ if (agentPreset !== undefined && agentPreset !== 'dsh-edge') {
150
+ return fail(request, {
151
+ code: 'agent-preset-not-found',
152
+ message: `Agent preset "${agentPreset}" is not available in this Edge instance.`,
153
+ details: { agentPreset, available: ['dsh-edge'] },
154
+ })
155
+ }
156
+ try {
157
+ const created = await runtime.sessions.createBlankSession({
158
+ model: runtime.model,
159
+ ...sessionId === undefined ? {} : { sessionId },
160
+ })
161
+ const summary = (await runtime.sessions.listApiSessions())
162
+ .find(item => item.id === created.sessionId)
163
+ if (summary !== undefined) {
164
+ if (created.created) runtime.sessionCreated(summary)
165
+ if (workspaceId !== undefined) {
166
+ const attachmentError = await attachPublishedSession(
167
+ created.sessionId,
168
+ workspaceId,
169
+ 'created',
170
+ () => runtime.sessionAttached(summary, workspaceId),
171
+ )
172
+ if (attachmentError !== undefined) return fail(request, attachmentError)
173
+ }
174
+ }
175
+ return ok(request, {
176
+ sessionId: created.sessionId,
177
+ agentPreset: created.agentPreset,
178
+ })
179
+ } catch (error) {
180
+ return sessionFailure(request, error, sessionId)
181
+ }
182
+ },
183
+
184
+ async history(request) {
185
+ const { sessionId, beforeSeq, maxMessages } = request.payload
186
+ try {
187
+ const page = await runtime.sessions.readHistoryPage(
188
+ sessionId,
189
+ beforeSeq,
190
+ maxMessages ?? DEFAULT_HISTORY_MESSAGES,
191
+ )
192
+ const entries: HistoryEntry[] = page.events.map(event => ({ event }))
193
+ return ok(request, {
194
+ events: entries,
195
+ hasMore: page.hasMore,
196
+ ...beforeSeq === undefined
197
+ ? { projections: summaryProjections(page.summary) }
198
+ : {},
199
+ })
200
+ } catch (error) {
201
+ return sessionFailure(request, error, sessionId)
202
+ }
203
+ },
204
+
205
+ async models(request) {
206
+ try {
207
+ await runtime.sessions.requireSession(request.payload.sessionId)
208
+ return ok(request, {
209
+ current: { provider: EDGE_PROVIDER, model: runtime.model },
210
+ routable: true,
211
+ groups: modelGroups(),
212
+ failures: [],
213
+ })
214
+ } catch (error) {
215
+ return sessionFailure(request, error, request.payload.sessionId)
216
+ }
217
+ },
218
+
219
+ async selectModel(request) {
220
+ const { sessionId, provider, model, reasoningEffort } = request.payload
221
+ if (provider !== EDGE_PROVIDER || model !== runtime.model || reasoningEffort !== undefined) {
222
+ return fail(request, {
223
+ code: 'model-unavailable',
224
+ message: `This Edge instance serves only ${EDGE_PROVIDER}/${runtime.model}.`,
225
+ details: { provider, model },
226
+ })
227
+ }
228
+ try {
229
+ await runtime.sessions.requireSession(sessionId)
230
+ return ok(request, { selected: { provider, model } })
231
+ } catch (error) {
232
+ return sessionFailure(request, error, sessionId)
233
+ }
234
+ },
235
+
236
+ async rename(request) {
237
+ const { sessionId, title } = request.payload
238
+ try {
239
+ const renamed = await runtime.sessions.renameSession(sessionId, title, runtime.model)
240
+ if (renamed.publishRequired) runtime.sessionEvent(sessionId, renamed.event)
241
+ return ok(request, { title: renamed.title, seq: renamed.event.seq })
242
+ } catch (error) {
243
+ return sessionFailure(request, error, sessionId)
244
+ }
245
+ },
246
+
247
+ async fork(request) {
248
+ const { sessionId, atSeq } = request.payload
249
+ try {
250
+ const workspaceId = await runtime.workspaceForSession(sessionId)
251
+ const child = await runtime.sessions.forkSession(sessionId, atSeq, runtime.model)
252
+ runtime.sessionCreated(child)
253
+ if (workspaceId !== undefined) {
254
+ const attachmentError = await attachPublishedSession(
255
+ child.id,
256
+ workspaceId,
257
+ 'forked',
258
+ () => runtime.sessionAttached(child, workspaceId),
259
+ )
260
+ if (attachmentError !== undefined) return fail(request, attachmentError)
261
+ }
262
+ return ok(request, { sessionId: child.id })
263
+ } catch (error) {
264
+ return sessionFailure(request, error, sessionId)
265
+ }
266
+ },
267
+
268
+ async prompt(request) {
269
+ const { sessionId, mode, content, clientTimeZone } = request.payload
270
+ const zone = canonicalClientTimeZone(clientTimeZone)
271
+ if (clientTimeZone !== undefined && zone === undefined) {
272
+ return fail(request, {
273
+ code: 'invalid-time-zone',
274
+ message: 'clientTimeZone must be UTC or a valid IANA Area/Location name.',
275
+ details: { value: clientTimeZone },
276
+ })
277
+ }
278
+ if (content.some(part => part.type === 'image')) {
279
+ return fail(request, {
280
+ code: 'attachment-error',
281
+ message: 'Image attachments are not available in this Edge instance.',
282
+ details: { reason: 'EDGE_ATTACHMENTS_UNAVAILABLE' },
283
+ })
284
+ }
285
+ const textContent = content as Extract<PromptContentPart, { type: 'text' }>[]
286
+ if (messageTextByteLength(textContent) > MAX_MESSAGE_TEXT_BYTES) {
287
+ return fail(request, {
288
+ code: 'attachment-error',
289
+ message: `Prompt text is limited to ${MAX_MESSAGE_TEXT_BYTES} UTF-8 bytes.`,
290
+ details: { reason: 'PROMPT_TEXT_TOO_LARGE' },
291
+ })
292
+ }
293
+ try {
294
+ await runtime.prompt({
295
+ sessionId,
296
+ mode,
297
+ content: textContent,
298
+ rpcId: request.rpcId,
299
+ ...zone === undefined ? {} : { clientTimeZone: zone },
300
+ })
301
+ return ok(request, { accepted: true as const })
302
+ } catch (error) {
303
+ return sessionFailure(request, error, sessionId)
304
+ }
305
+ },
306
+
307
+ attachment: unsupported,
308
+ updateQueue(request) {
309
+ const { sessionId, itemId, action } = request.payload
310
+ if (action.kind === 'edit' && action.content.some(block => block.type !== 'text')) {
311
+ return Promise.resolve(fail(request, {
312
+ code: 'attachment-error',
313
+ message: 'Queue edits accept text content only in this Edge instance.',
314
+ details: { reason: 'QUEUE_EDIT_NON_TEXT' },
315
+ }))
316
+ }
317
+ if (action.kind === 'edit'
318
+ && messageTextByteLength(
319
+ action.content as Extract<PromptContentPart, { type: 'text' }>[],
320
+ ) > MAX_MESSAGE_TEXT_BYTES) {
321
+ return Promise.resolve(fail(request, {
322
+ code: 'attachment-error',
323
+ message: `Queue edit text is limited to ${MAX_MESSAGE_TEXT_BYTES} UTF-8 bytes.`,
324
+ details: { reason: 'QUEUE_EDIT_TEXT_TOO_LARGE' },
325
+ }))
326
+ }
327
+ const outcome = runtime.updateQueue(sessionId, itemId, action)
328
+ if (outcome === 'queue-item-not-found') {
329
+ return Promise.resolve(fail(request, {
330
+ code: 'queue-item-not-found',
331
+ message: 'The queued item is no longer pending.',
332
+ details: { itemId },
333
+ }))
334
+ }
335
+ if (outcome === 'steer-unavailable') {
336
+ return Promise.resolve(fail(request, {
337
+ code: 'steer-unavailable',
338
+ message: 'The current turn no longer accepts steering.',
339
+ details: { itemId },
340
+ }))
341
+ }
342
+ return Promise.resolve(ok(request, { accepted: true as const }))
343
+ },
344
+
345
+ cancel(request) {
346
+ if (!runtime.cancel(request.payload.sessionId)) {
347
+ return Promise.resolve(fail(request, {
348
+ code: 'agent-busy',
349
+ message: 'The session has no active turn in this Edge instance.',
350
+ details: { reason: 'not-running' },
351
+ }))
352
+ }
353
+ return Promise.resolve(ok(request, { accepted: true as const }))
354
+ },
355
+ },
356
+
357
+ subagents: {
358
+ list: request => Promise.resolve(ok(request, { entries: [], parentAvailable: false })),
359
+ history: unsupported,
360
+ prompt: unsupported,
361
+ interrupt: unsupported,
362
+ },
363
+
364
+ host: {
365
+ async describe(request) {
366
+ return ok(request, {
367
+ version: runtime.version,
368
+ cwd: EDGE_WORKSPACE_PATH,
369
+ provider: EDGE_PROVIDER,
370
+ model: runtime.model,
371
+ attachedSessions: await runtime.sessions.attachedSessionCount(),
372
+ canOpenPath: false,
373
+ })
374
+ },
375
+ pickDirectory: unsupported,
376
+ listDirectory: unsupported,
377
+ createDirectory: unsupported,
378
+ openPath: unsupported,
379
+ },
380
+
381
+ workspace: {
382
+ async list(request) {
383
+ const sessions = await runtime.sessions.listApiSessions()
384
+ return ok(request, await runtime.workspaceList(sessions))
385
+ },
386
+ async create(request) {
387
+ try {
388
+ return ok(request, await runtime.workspaceCreate(request.payload.path))
389
+ } catch (error) {
390
+ return workspaceFailure(request, error, undefined, undefined, undefined, request.payload.path)
391
+ }
392
+ },
393
+ async rename(request) {
394
+ const { workspaceId, title } = request.payload
395
+ try {
396
+ return ok(request, {
397
+ workspace: await runtime.workspaceRename(workspaceId, title.trim()),
398
+ })
399
+ } catch (error) {
400
+ return workspaceFailure(request, error, workspaceId)
401
+ }
402
+ },
403
+ async delete(request) {
404
+ const { workspaceId } = request.payload
405
+ try {
406
+ await runtime.workspaceDelete(workspaceId)
407
+ return ok(request, { deleted: true as const })
408
+ } catch (error) {
409
+ return workspaceFailure(request, error, workspaceId)
410
+ }
411
+ },
412
+ async insertBefore(request) {
413
+ const { workspaceId, beforeWorkspaceId } = request.payload
414
+ try {
415
+ return ok(request, {
416
+ workspaceIds: await runtime.workspaceInsertBefore(workspaceId, beforeWorkspaceId),
417
+ })
418
+ } catch (error) {
419
+ return workspaceFailure(request, error, workspaceId)
420
+ }
421
+ },
422
+ async insertSessionBefore(request) {
423
+ const { workspaceId, sessionId, beforeSessionId } = request.payload
424
+ try {
425
+ return ok(request, {
426
+ workspace: await runtime.workspaceInsertSessionBefore(
427
+ workspaceId,
428
+ sessionId,
429
+ beforeSessionId,
430
+ ),
431
+ })
432
+ } catch (error) {
433
+ return workspaceFailure(request, error, workspaceId, sessionId, beforeSessionId)
434
+ }
435
+ },
436
+ async archiveSession(request) {
437
+ const { sessionId } = request.payload
438
+ try {
439
+ return ok(request, {
440
+ archivedSessionIds: await runtime.archiveSession(sessionId),
441
+ })
442
+ } catch (error) {
443
+ return sessionFailure(request, error, sessionId)
444
+ }
445
+ },
446
+ },
447
+
448
+ skills: {
449
+ list: request => Promise.resolve(ok(request, { skills: [] })),
450
+ },
451
+
452
+ agentPresets: {
453
+ list: request => Promise.resolve(ok(request, {
454
+ presets: [{
455
+ id: 'dsh-edge',
456
+ trust: 'system' as const,
457
+ isDefault: true,
458
+ name: 'DSH Edge',
459
+ description: 'DeepSeek Harness running in a Cloudflare Durable Object.',
460
+ }],
461
+ authorable: false,
462
+ hasDocument: false,
463
+ })),
464
+ select(request) {
465
+ if (request.payload.agentPreset !== 'dsh-edge') {
466
+ return Promise.resolve(fail(request, {
467
+ code: 'agent-preset-not-found',
468
+ message: `Agent preset "${request.payload.agentPreset}" is not available.`,
469
+ details: { agentPreset: request.payload.agentPreset, available: ['dsh-edge'] },
470
+ }))
471
+ }
472
+ return Promise.resolve(ok(request, { agentPreset: 'dsh-edge' }))
473
+ },
474
+ read(request) {
475
+ if (request.payload.agentPreset !== 'dsh-edge') {
476
+ return Promise.resolve(fail(request, {
477
+ code: 'agent-preset-not-found',
478
+ message: `Agent preset "${request.payload.agentPreset}" is not available.`,
479
+ details: { agentPreset: request.payload.agentPreset, available: ['dsh-edge'] },
480
+ }))
481
+ }
482
+ try {
483
+ return Promise.resolve(ok(request, {
484
+ agentPreset: 'dsh-edge',
485
+ trust: 'system' as const,
486
+ name: 'DSH Edge',
487
+ description: 'DeepSeek Harness running in a Cloudflare Durable Object.',
488
+ content: edgeAgentPresetContent(runtime, runtime.deploymentProfile()),
489
+ }))
490
+ } catch (error) {
491
+ return Promise.resolve(fail(request, {
492
+ code: 'internal',
493
+ message: error instanceof Error ? error.message : String(error),
494
+ details: {},
495
+ }))
496
+ }
497
+ },
498
+ copy: unsupported,
499
+ openDocument: unsupported,
500
+ remove: unsupported,
501
+ },
502
+
503
+ goals: {
504
+ create: unsupported,
505
+ edit: unsupported,
506
+ pause: unsupported,
507
+ resume: unsupported,
508
+ complete: unsupported,
509
+ clear: unsupported,
510
+ },
511
+
512
+ settings: {
513
+ describe: request => Promise.resolve(ok(request, {
514
+ writable: false,
515
+ hasDocument: false,
516
+ namespaces: [],
517
+ })),
518
+ openDocument: unsupported,
519
+ update: unsupported,
520
+ replace: unsupported,
521
+ mutate: unsupported,
522
+ },
523
+
524
+ credentials: {
525
+ async describe(request) {
526
+ const entries = await Promise.all(request.payload.refs.map(async ref => (
527
+ [ref, await runtime.describeCredential(ref)] as const
528
+ )))
529
+ return ok(request, { credentials: Object.fromEntries(entries) })
530
+ },
531
+ set: unsupported,
532
+ unset: unsupported,
533
+ },
534
+
535
+ llm: {
536
+ providers: request => Promise.resolve(ok(request, {
537
+ providers: [{
538
+ provider: EDGE_PROVIDER,
539
+ displayName: 'DeepSeek',
540
+ settingsNs: 'llm-deepseek',
541
+ settingsPath: [],
542
+ active: true,
543
+ declared: false,
544
+ }],
545
+ })),
546
+ models: request => Promise.resolve(ok(request, {
547
+ groups: modelGroups(),
548
+ failures: [],
549
+ })),
550
+ discoverModels: unsupported,
551
+ },
552
+
553
+ events: {
554
+ mux: (_request, _signal) => emptyFrames(),
555
+ host: (_request, _signal) => emptyFrames(),
556
+ },
557
+
558
+ downloads: {
559
+ sessionLog: () => Promise.resolve(new Response('Session export is unavailable on Edge.', {
560
+ status: 501,
561
+ })),
562
+ },
563
+
564
+ respond: () => Promise.resolve({ accepted: false, reason: 'not-pending' }),
565
+ }
566
+ return api
567
+ }
568
+
569
+ /** Render the programmatic Edge agent graph through the upstream composition viewer. */
570
+ function edgeAgentPresetContent(
571
+ runtime: EdgeApiRuntime,
572
+ deployment: EdgeDeploymentProfile,
573
+ ): string {
574
+ return [
575
+ '# Effective dsh-edge composition (read-only)',
576
+ '# Projected from the programmatic Worker runtime; this is not an editable agent.cordis.yml.',
577
+ 'preset:',
578
+ ' id: dsh-edge',
579
+ ' trust: system',
580
+ 'release:',
581
+ ` version: ${yamlString(runtime.version)}`,
582
+ ` deploymentId: ${yamlString(deployment.deploymentId)}`,
583
+ 'runtime:',
584
+ ' platform: cloudflare-workers',
585
+ ` storage: ${yamlString(deployment.storage)}`,
586
+ ` shell: ${yamlString(deployment.shell)}`,
587
+ ' workspace: /workspace',
588
+ 'agent:',
589
+ ' loop: "@deepseek-ai/dsh-agent-loop"',
590
+ ' registry: "@deepseek-ai/dsh-agent"',
591
+ ' sessionStore: "@deepseek-ai/dsh-session"',
592
+ ` systemPrompt: ${yamlString(EDGE_SYSTEM_PROMPT)}`,
593
+ 'model:',
594
+ ` provider: ${yamlString(EDGE_PROVIDER)}`,
595
+ ` id: ${yamlString(runtime.model)}`,
596
+ ` baseURL: ${yamlString(deployment.baseURL)}`,
597
+ ` reasoningEffort: ${yamlString(deployment.reasoningEffort)}`,
598
+ ` maxOutputTokens: ${String(deployment.maxTokens)}`,
599
+ ` streamIdleTimeoutMs: ${String(deployment.streamIdleTimeoutMs)}`,
600
+ ' credential:',
601
+ ' ref: DEEPSEEK_API_KEY',
602
+ ` configured: ${String(deployment.apiKeyConfigured)}`,
603
+ ' persisted: false',
604
+ 'tools:',
605
+ ' - id: bash',
606
+ ' runtime: just-bash',
607
+ ' filesystem: /workspace',
608
+ ` defaultTimeoutMs: ${String(deployment.commandTimeoutPolicy.defaultTimeoutMs)}`,
609
+ ` maxTimeoutMs: ${String(deployment.commandTimeoutPolicy.maxTimeoutMs)}`,
610
+ ' - id: web_search',
611
+ ' provider: deepseek-official',
612
+ ` baseURL: ${yamlString(deployment.searchBaseURL)}`,
613
+ ' credentialRef: DEEPSEEK_API_KEY',
614
+ ' maxResults: 8',
615
+ ' webFetchEnabled: false',
616
+ ].join('\n') + '\n'
617
+ }
618
+
619
+ /** Quote a string as a JSON scalar, which is also a valid YAML scalar. */
620
+ function yamlString(value: string): string {
621
+ return JSON.stringify(value)
622
+ }
623
+
624
+ function sessionSummary(
625
+ runtime: EdgeApiRuntime,
626
+ summary: EdgeApiSessionSummary,
627
+ ): SessionSummary {
628
+ return {
629
+ sessionId: summary.id,
630
+ updatedAt: summary.updatedAt,
631
+ running: runtime.isRunning(summary.id),
632
+ blank: summary.blank,
633
+ ...summary.parentSessionId === undefined
634
+ ? {}
635
+ : { parentSessionId: summary.parentSessionId },
636
+ ...summary.origin === undefined ? {} : { origin: summary.origin },
637
+ ...summary.cwd === undefined ? {} : { cwd: summary.cwd },
638
+ ...summary.agentPreset === undefined ? {} : { agentPreset: summary.agentPreset },
639
+ projections: summaryProjections(summary),
640
+ }
641
+ }
642
+
643
+ function summaryProjections(summary: EdgeApiSessionSummary): SessionProjectionsBlock {
644
+ return {
645
+ asOfSeq: summary.lastSeq,
646
+ values: {
647
+ sessionListMetadata: {
648
+ blank: summary.blank,
649
+ lastPromptAt: summary.lastPromptAt,
650
+ },
651
+ ...summary.title === null ? {} : { title: summary.title },
652
+ },
653
+ }
654
+ }
655
+
656
+ function canonicalClientTimeZone(value: string | undefined): string | undefined {
657
+ if (value === undefined) return undefined
658
+ const profile = /^[A-Za-z][A-Za-z0-9_+.-]*(?:\/[A-Za-z0-9_+.-]+)+$/
659
+ if (value.length === 0 || value.trim() !== value
660
+ || (value !== 'UTC' && !profile.test(value))) return undefined
661
+ try {
662
+ const canonical = new Intl.DateTimeFormat('en-US', { timeZone: value })
663
+ .resolvedOptions().timeZone
664
+ return canonical === 'UTC' || profile.test(canonical) ? canonical : undefined
665
+ } catch {
666
+ return undefined
667
+ }
668
+ }
669
+
670
+ function workspaceFailure<T>(
671
+ request: RpcRequest<unknown>,
672
+ error: unknown,
673
+ workspaceId: WorkspaceId | undefined,
674
+ sessionId?: SessionId,
675
+ beforeSessionId?: SessionId,
676
+ path?: string,
677
+ ): RpcResponse<T> {
678
+ if (error instanceof EdgeWorkspaceStoreError) {
679
+ if (error.code === 'NOT_FOUND' && workspaceId !== undefined) {
680
+ return fail(request, {
681
+ code: 'workspace-not-found',
682
+ message: error.message,
683
+ details: { workspaceId },
684
+ })
685
+ }
686
+ if (error.code === 'INVALID_PATH') {
687
+ return fail(request, {
688
+ code: 'workspace-invalid-path',
689
+ message: error.message,
690
+ details: { path: path ?? EDGE_WORKSPACE_PATH },
691
+ })
692
+ }
693
+ if (error.code === 'MOVE_INVALID' && workspaceId !== undefined && sessionId !== undefined) {
694
+ return fail(request, {
695
+ code: 'workspace-move-invalid',
696
+ message: error.message,
697
+ details: {
698
+ workspaceId,
699
+ sessionId,
700
+ ...beforeSessionId === undefined ? {} : { beforeSessionId },
701
+ },
702
+ })
703
+ }
704
+ }
705
+ return fail(request, {
706
+ code: 'internal',
707
+ message: error instanceof Error ? error.message : String(error),
708
+ details: {},
709
+ })
710
+ }
711
+
712
+ /**
713
+ * Capture post-durability Workspace completion without losing the Session id.
714
+ * @param sessionId - Session that already exists durably.
715
+ * @param workspaceId - Workspace the completion step must account for.
716
+ * @param operation - User-visible creation verb used by the diagnostic.
717
+ * @param attach - Publication and attachment work that may still fail.
718
+ * @returns The upstream recoverable error, or `undefined` after completion.
719
+ */
720
+ export async function attachPublishedSession(
721
+ sessionId: SessionId,
722
+ workspaceId: WorkspaceId,
723
+ operation: 'created' | 'forked',
724
+ attach: () => Promise<unknown>,
725
+ ): Promise<RpcError | undefined> {
726
+ try {
727
+ await attach()
728
+ return undefined
729
+ } catch (error) {
730
+ return {
731
+ code: 'workspace-attach-failed',
732
+ message: `Session "${sessionId}" was ${operation} but could not attach to workspace "${workspaceId}": ${String(error)}`,
733
+ details: { sessionId, workspaceId },
734
+ }
735
+ }
736
+ }
737
+
738
+ function ok<P, T>(request: RpcRequest<P>, value: T): RpcResponse<T> {
739
+ return { rpcId: request.rpcId, result: { ok: true, value } }
740
+ }
741
+
742
+ function fail<P, T>(request: RpcRequest<P>, error: RpcError): RpcResponse<T> {
743
+ return { rpcId: request.rpcId, result: { ok: false, error } }
744
+ }
745
+
746
+ function unsupported<T>(request: RpcRequest<unknown>): Promise<RpcResponse<T>> {
747
+ return Promise.resolve(fail(request, {
748
+ code: 'internal',
749
+ message: 'This capability is not available in the Edge runtime.',
750
+ details: {},
751
+ }))
752
+ }
753
+
754
+ function sessionFailure<T>(
755
+ request: RpcRequest<unknown>,
756
+ error: unknown,
757
+ sessionId: SessionId | undefined,
758
+ ): RpcResponse<T> {
759
+ if (error instanceof EdgeSessionStoreError) {
760
+ if (error.code === 'NOT_FOUND' && sessionId !== undefined) {
761
+ return fail(request, {
762
+ code: 'session-not-found',
763
+ message: error.message,
764
+ details: { sessionId },
765
+ })
766
+ }
767
+ if (error.code === 'BUSY') {
768
+ return fail(request, {
769
+ code: 'agent-busy',
770
+ message: error.message,
771
+ details: { reason: 'running' },
772
+ })
773
+ }
774
+ if (error.code === 'FORK_UNAVAILABLE' && sessionId !== undefined) {
775
+ return fail(request, {
776
+ code: 'fork-unavailable',
777
+ message: error.message,
778
+ details: { sessionId },
779
+ })
780
+ }
781
+ if (error.code === 'TITLE_INVALID' && sessionId !== undefined) {
782
+ return fail(request, {
783
+ code: 'title-invalid',
784
+ message: error.message,
785
+ details: { sessionId },
786
+ })
787
+ }
788
+ }
789
+ return fail(request, {
790
+ code: 'internal',
791
+ message: error instanceof Error ? error.message : String(error),
792
+ details: {},
793
+ })
794
+ }
795
+
796
+ async function* emptyFrames<T>(): AsyncGenerator<T> {}