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
package/src/edge-api.ts
ADDED
|
@@ -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> {}
|