@sparkelf/dsh-patch-document-attachments 0.1.0-rc.11 → 0.1.0-rc.13
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/README.i18n.yaml +2 -2
- package/README.md +3 -3
- package/README.zh.md +3 -3
- package/package.json +5 -4
- package/patches/document-attachments.patch +757 -301
|
@@ -1,11 +1,58 @@
|
|
|
1
|
+
diff --git a/packages/api/session-controller/src/client/contract/session.ts b/packages/api/session-controller/src/client/contract/session.ts
|
|
2
|
+
index 7810249c46..db2c674ce2 100644
|
|
3
|
+
--- a/packages/api/session-controller/src/client/contract/session.ts
|
|
4
|
+
+++ b/packages/api/session-controller/src/client/contract/session.ts
|
|
5
|
+
@@ -7,7 +7,7 @@
|
|
6
|
+
* must stub); implementation-internal entry points (history staging, wire-frame
|
|
7
|
+
* dispatch) stay on the class, invisible out here.
|
|
8
|
+
*/
|
|
9
|
+
-import type { AttachmentIdType, ImageAttachmentRef } from '@deepseek-ai/dsh-attachment'
|
|
10
|
+
+import type { AttachmentIdType, FileAttachmentRef, ImageAttachmentRef } from '@deepseek-ai/dsh-attachment'
|
|
11
|
+
import type { MessageId } from '@deepseek-ai/dsh-llm/brand'
|
|
12
|
+
import type { SessionId } from '@deepseek-ai/dsh-session/types'
|
|
13
|
+
import type { RemoteResult } from '@deepseek-ai/dsh-typert-protocol'
|
|
14
|
+
@@ -85,13 +85,13 @@ export interface ISession {
|
|
15
|
+
requestId?: SessionRequestId,
|
|
16
|
+
): Promise<RemoteResult<{ accepted: true }>>
|
|
17
|
+
/**
|
|
18
|
+
- * Resolve one durable image referenced by this session.
|
|
19
|
+
+ * Resolve one durable file referenced by this session.
|
|
20
|
+
* @param attachmentId - opaque id found in the folded session log.
|
|
21
|
+
* @returns the authenticated reference and decoded bytes.
|
|
22
|
+
*/
|
|
23
|
+
readAttachment(
|
|
24
|
+
attachmentId: AttachmentIdType,
|
|
25
|
+
- ): Promise<RemoteResult<{ attachment: ImageAttachmentRef; data: Uint8Array }>>
|
|
26
|
+
+ ): Promise<RemoteResult<{ attachment: FileAttachmentRef; data: Uint8Array }>>
|
|
27
|
+
/**
|
|
28
|
+
* Apply one edit, remove, or strict steer action to a still-pending queue occurrence.
|
|
29
|
+
* @param itemId - agent-owned inbox occurrence identity.
|
|
1
30
|
diff --git a/packages/api/session-controller/src/client/sessions/session.ts b/packages/api/session-controller/src/client/sessions/session.ts
|
|
2
|
-
index
|
|
31
|
+
index 8af47b5aad..3599956698 100644
|
|
3
32
|
--- a/packages/api/session-controller/src/client/sessions/session.ts
|
|
4
33
|
+++ b/packages/api/session-controller/src/client/sessions/session.ts
|
|
5
|
-
@@ -
|
|
34
|
+
@@ -2,7 +2,7 @@
|
|
35
|
+
|
|
36
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
37
|
+
import { randomUUID } from '@deepseek-ai/dsh-util-crypto'
|
|
38
|
+
-import type { AttachmentIdType, ImageAttachmentRef } from '@deepseek-ai/dsh-attachment'
|
|
39
|
+
+import type { AttachmentIdType, FileAttachmentRef, ImageAttachmentRef } from '@deepseek-ai/dsh-attachment'
|
|
40
|
+
import type { SubagentAddress } from '@deepseek-ai/dsh-subagent/client'
|
|
41
|
+
import type { MessageId } from '@deepseek-ai/dsh-llm/brand'
|
|
42
|
+
import type { SessionId } from '@deepseek-ai/dsh-session/types'
|
|
43
|
+
@@ -28,7 +28,7 @@ import type {
|
|
44
|
+
} from '../contract/events.ts'
|
|
45
|
+
import { Notifier } from './notifier.ts'
|
|
46
|
+
import { isRemoteFailure } from '@deepseek-ai/dsh-api-gateway/client'
|
|
47
|
+
-import type { RemoteFailure, RemoteResult } from '@deepseek-ai/dsh-typert-protocol'
|
|
48
|
+
+import { RemoteError, type RemoteFailure, type RemoteResult } from '@deepseek-ai/dsh-typert-protocol'
|
|
49
|
+
import type { SessionRemotes } from './remotes.ts'
|
|
50
|
+
import { ProjectionValueStore } from './projection-store.ts'
|
|
51
|
+
import type { ProjectionsBaseline } from './projection-store.ts'
|
|
52
|
+
@@ -210,25 +210,17 @@ export class Session implements SessionFace {
|
|
6
53
|
signal?: AbortSignal,
|
|
7
54
|
requestId?: SessionRequestId,
|
|
8
|
-
): Promise<
|
|
55
|
+
): Promise<RemoteResult<{ accepted: true }>> {
|
|
9
56
|
- this.promptError = null
|
|
10
57
|
- this.lastAgentError = null
|
|
11
58
|
- // Synchronous, before the first await: the blank → engaging edge must be
|
|
@@ -14,72 +61,36 @@ index 815cf89c40e404a32dd1ec8c20568fc15995a73d..9e03894a8d895bc73fd5ea8ac853b007
|
|
|
14
61
|
- this.promptAttempted = true
|
|
15
62
|
- if (this.blankBit) this.firstPromptPendingTurn = true
|
|
16
63
|
- this.notifier.markDirty()
|
|
17
|
-
- let result:
|
|
18
|
-
-
|
|
64
|
+
- let result: RemoteResult<{ accepted: true }>
|
|
65
|
+
- if (this.address === undefined) {
|
|
66
|
+
- const clientTimeZone = resolvedClientTimeZone()
|
|
67
|
+
- result = await this.remote.session.prompt({
|
|
68
|
+
- requestId: requestId ?? randomUUID() as SessionRequestId,
|
|
69
|
+
- sessionId: this.sessionId,
|
|
70
|
+
- mode,
|
|
71
|
+
- content,
|
|
72
|
+
- clientTimeZone,
|
|
73
|
+
- }, signal)
|
|
74
|
+
- } else {
|
|
19
75
|
+ return this.settlePrompt(requestId, async () => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
+
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}, signal))
|
|
30
|
-
- } else if (this.address.mode === 'one-shot') {
|
|
31
|
-
- result = {
|
|
32
|
-
+ }
|
|
33
|
-
+ if (this.address.mode === 'one-shot') {
|
|
34
|
-
+ return {
|
|
35
|
-
ok: false,
|
|
36
|
-
error: {
|
|
37
|
-
code: 'subagent-not-resumable',
|
|
38
|
-
@@ -243,30 +235,64 @@ export class Session implements SessionFace {
|
|
39
|
-
details: { childSessionId: this.address.childSessionId },
|
|
40
|
-
},
|
|
41
|
-
}
|
|
42
|
-
- } else {
|
|
43
|
-
- if (content.some(part => part.type === 'image')) {
|
|
44
|
-
- result = {
|
|
45
|
-
- ok: false,
|
|
46
|
-
- error: {
|
|
47
|
-
- code: 'attachment-error',
|
|
48
|
-
- message: 'Image input is unavailable for subagent continuations.',
|
|
49
|
-
- details: { reason: 'SUBAGENT_IMAGE_UNSUPPORTED' },
|
|
50
|
-
- },
|
|
51
|
-
- }
|
|
52
|
-
- } else {
|
|
53
|
-
- const routed = toSessionResult(await this.remote.subagents.prompt({
|
|
54
|
-
- requestId: randomUUID() as SessionRequestId,
|
|
55
|
-
- parentSessionId: this.address.parentSessionId,
|
|
56
|
-
- childSessionId: this.address.childSessionId,
|
|
57
|
-
- mode: this.address.mode,
|
|
58
|
-
- content: content.flatMap(part => part.type === 'text'
|
|
59
|
-
- ? [{ type: 'text' as const, text: part.text }]
|
|
60
|
-
- : []),
|
|
61
|
-
- clientTimeZone: resolvedClientTimeZone(),
|
|
62
|
-
- }, signal))
|
|
63
|
-
- result = routed.ok ? { ok: true, value: { accepted: true } } : routed
|
|
76
|
+
+ if (this.address === undefined) {
|
|
77
|
+
+ const clientTimeZone = resolvedClientTimeZone()
|
|
78
|
+
+ return this.remote.session.prompt({
|
|
79
|
+
+ requestId: requestId ?? randomUUID() as SessionRequestId,
|
|
80
|
+
+ sessionId: this.sessionId,
|
|
81
|
+
+ mode,
|
|
82
|
+
+ content,
|
|
83
|
+
+ clientTimeZone,
|
|
84
|
+
+ }, signal)
|
|
64
85
|
+ }
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
+
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
+ const routed = toSessionResult(await this.remote.subagents.prompt({
|
|
76
|
-
+ requestId: randomUUID() as SessionRequestId,
|
|
77
|
-
+ parentSessionId: this.address.parentSessionId,
|
|
78
|
-
+ childSessionId: this.address.childSessionId,
|
|
79
|
-
+ mode: this.address.mode,
|
|
80
|
-
+ content: content.flatMap(part => part.type === 'text' ? [{ type: 'text' as const, text: part.text }] : []),
|
|
81
|
-
+ clientTimeZone: resolvedClientTimeZone(),
|
|
82
|
-
+ }, signal))
|
|
86
|
+
const routed = await this.remote.subagents.prompt({
|
|
87
|
+
requestId: randomUUID() as SessionRequestId,
|
|
88
|
+
parentSessionId: this.address.parentSessionId,
|
|
89
|
+
@@ -237,22 +229,49 @@ export class Session implements SessionFace {
|
|
90
|
+
content,
|
|
91
|
+
clientTimeZone: resolvedClientTimeZone(),
|
|
92
|
+
}, signal)
|
|
93
|
+
- result = routed.ok ? { ok: true, value: { accepted: true } } : routed
|
|
83
94
|
+ return routed.ok ? { ok: true, value: { accepted: true } } : routed
|
|
84
95
|
+ })
|
|
85
96
|
+ }
|
|
@@ -92,37 +103,34 @@ index 815cf89c40e404a32dd1ec8c20568fc15995a73d..9e03894a8d895bc73fd5ea8ac853b007
|
|
|
92
103
|
+ */
|
|
93
104
|
+ promptPrepared(
|
|
94
105
|
+ requestId: SessionRequestId,
|
|
95
|
-
+ submit: () => Promise<
|
|
96
|
-
+ ): Promise<
|
|
106
|
+
+ submit: () => Promise<RemoteResult<{ accepted: true }>>,
|
|
107
|
+
+ ): Promise<RemoteResult<{ accepted: true }>> {
|
|
97
108
|
+ if (this.address !== undefined) {
|
|
98
109
|
+ return this.settlePrompt(requestId, async () => ({
|
|
99
110
|
+ ok: false,
|
|
100
|
-
+ error:
|
|
101
|
-
+
|
|
102
|
-
+
|
|
103
|
-
+
|
|
104
|
-
+
|
|
111
|
+
+ error: new RemoteError(
|
|
112
|
+
+ 'session/attachment-invalid',
|
|
113
|
+
+ 'Document input is unavailable for subagent continuations.',
|
|
114
|
+
+ { reason: 'SUBAGENT_DOCUMENT_UNSUPPORTED' },
|
|
115
|
+
+ ),
|
|
105
116
|
+ }))
|
|
106
|
-
|
|
117
|
+
}
|
|
107
118
|
+ return this.settlePrompt(requestId, submit)
|
|
108
119
|
+ }
|
|
109
120
|
+
|
|
110
121
|
+ private async settlePrompt(
|
|
111
122
|
+ requestId: SessionRequestId | undefined,
|
|
112
|
-
+ submit: () => Promise<
|
|
113
|
-
+ ): Promise<
|
|
123
|
+
+ submit: () => Promise<RemoteResult<{ accepted: true }>>,
|
|
124
|
+
+ ): Promise<RemoteResult<{ accepted: true }>> {
|
|
114
125
|
+ this.promptError = null
|
|
115
126
|
+ this.lastAgentError = null
|
|
116
127
|
+ this.promptAttempted = true
|
|
117
128
|
+ if (this.blankBit) this.firstPromptPendingTurn = true
|
|
118
129
|
+ this.notifier.markDirty()
|
|
119
|
-
+
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
result = transportResult(error)
|
|
124
|
-
}
|
|
125
|
-
@@ -276,14 +302,6 @@ export class Session implements SessionFace {
|
|
130
|
+
+ const result = await submit()
|
|
131
|
+
if (!result.ok) {
|
|
132
|
+
if (requestId !== undefined) this.retireFailedSubmission(requestId)
|
|
133
|
+
this.promptError = { op: 'send', error: result.error }
|
|
126
134
|
this.notifier.markDirty()
|
|
127
135
|
return result
|
|
128
136
|
}
|
|
@@ -137,28 +145,45 @@ index 815cf89c40e404a32dd1ec8c20568fc15995a73d..9e03894a8d895bc73fd5ea8ac853b007
|
|
|
137
145
|
if (this.blankBit) {
|
|
138
146
|
this.blankBit = false
|
|
139
147
|
this.options.onEngaged?.(this)
|
|
148
|
+
@@ -262,13 +281,13 @@ export class Session implements SessionFace {
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
- * Resolve one image referenced by this session into browser-consumable bytes.
|
|
153
|
+
+ * Resolve one file referenced by this session into browser-consumable bytes.
|
|
154
|
+
* @param attachmentId - opaque id found in the folded session log.
|
|
155
|
+
* @returns the authenticated reference and decoded bytes.
|
|
156
|
+
*/
|
|
157
|
+
async readAttachment(
|
|
158
|
+
attachmentId: AttachmentIdType,
|
|
159
|
+
- ): Promise<RemoteResult<{ attachment: ImageAttachmentRef; data: Uint8Array }>> {
|
|
160
|
+
+ ): Promise<RemoteResult<{ attachment: FileAttachmentRef; data: Uint8Array }>> {
|
|
161
|
+
const result = await this.remote.session.attachment({
|
|
162
|
+
sessionId: this.sessionId,
|
|
163
|
+
attachmentId,
|
|
140
164
|
diff --git a/packages/api/session-controller/src/commands.ts b/packages/api/session-controller/src/commands.ts
|
|
141
|
-
index
|
|
165
|
+
index 1ad9127925..4384b275a8 100644
|
|
142
166
|
--- a/packages/api/session-controller/src/commands.ts
|
|
143
167
|
+++ b/packages/api/session-controller/src/commands.ts
|
|
144
|
-
@@ -
|
|
168
|
+
@@ -5,7 +5,7 @@ import type { Context } from '@deepseek-ai/cordis'
|
|
169
|
+
import { brandString } from '@deepseek-ai/dsh-brand'
|
|
170
|
+
import type { Agent, ModelSelection as AgentModelSelection } from '@deepseek-ai/dsh-agent'
|
|
171
|
+
import { AttachmentError, admitEncodedImages } from '@deepseek-ai/dsh-attachment'
|
|
172
|
+
-import type { ImageAttachmentRef } from '@deepseek-ai/dsh-attachment'
|
|
173
|
+
+import type { FileAttachmentRef, ImageAttachmentRef } from '@deepseek-ai/dsh-attachment'
|
|
174
|
+
import {
|
|
175
|
+
ReasoningEffortId, createUserMessage, freezeMessage,
|
|
176
|
+
} from '@deepseek-ai/dsh-llm'
|
|
177
|
+
@@ -281,6 +281,26 @@ export class SessionCommandController {
|
|
145
178
|
* @returns acknowledgement that the Agent accepted the prompt.
|
|
146
179
|
*/
|
|
147
180
|
async prompt(request: SessionPromptRequest): Promise<SessionPromptValue> {
|
|
148
181
|
+ const hasImage = request.content.some(part => part.type === 'image')
|
|
149
|
-
+
|
|
150
|
-
+
|
|
151
|
-
+
|
|
152
|
-
+
|
|
153
|
-
+
|
|
154
|
-
+ )
|
|
155
|
-
+ } catch (error) {
|
|
156
|
-
+ if (error instanceof TypertRemoteFailure) throw error
|
|
157
|
-
+ if (error instanceof AttachmentError) {
|
|
158
|
-
+ reject('attachment-error', error.message, { reason: error.code })
|
|
159
|
-
+ }
|
|
160
|
-
+ reject('agent-busy', 'prompt rejected', { reason: String(error) })
|
|
161
|
-
+ }
|
|
182
|
+
+ return this.promptPrepared(
|
|
183
|
+
+ request,
|
|
184
|
+
+ hasImage,
|
|
185
|
+
+ () => durablePromptContent(this.ctx, request.content),
|
|
186
|
+
+ )
|
|
162
187
|
+ }
|
|
163
188
|
+
|
|
164
189
|
+ /**
|
|
@@ -176,52 +201,193 @@ index 48c41f3aa143d420890a4ad244d5b7b75b7d3eb4..cae9940f2ae4d5b6b8f57b0d88d48629
|
|
|
176
201
|
const clientTimeZone = request.clientTimeZone === undefined
|
|
177
202
|
? undefined
|
|
178
203
|
: canonicalClientTimeZone(request.clientTimeZone)
|
|
179
|
-
@@ -305,
|
|
204
|
+
@@ -305,7 +325,6 @@ export class SessionCommandController {
|
|
180
205
|
rpcId: request.requestId,
|
|
181
206
|
...(clientTimeZone === undefined ? {} : { clientTimeZone }),
|
|
182
207
|
}
|
|
183
208
|
- const hasImage = request.content.some(part => part.type === 'image')
|
|
184
209
|
const admit = async (): Promise<SessionPromptValue> => {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
- 'attachment-error',
|
|
192
|
-
- `Model "${current.model}" does not support image input.`,
|
|
193
|
-
- { reason: 'MODEL_DOES_NOT_SUPPORT_IMAGES' },
|
|
194
|
-
- )
|
|
195
|
-
- }
|
|
196
|
-
- }
|
|
210
|
+
try {
|
|
211
|
+
if (hasImage) {
|
|
212
|
+
@@ -319,8 +338,7 @@ export class SessionCommandController {
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
197
216
|
- const content = await durablePromptContent(this.ctx, request.content)
|
|
198
217
|
- const message: UserMessage = createUserMessage({ content, source })
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
+
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
+
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
|
|
218
|
+
+ const message: UserMessage = createUserMessage({ content: await prepare(), source })
|
|
219
|
+
if (request.mode === 'steer') agent.steer(message)
|
|
220
|
+
else agent.followup(message)
|
|
221
|
+
} catch (error) {
|
|
222
|
+
@@ -336,7 +354,7 @@ export class SessionCommandController {
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
- * Read one durable image after proving the Session log references it.
|
|
227
|
+
+ * Read one durable attachment after proving the Session log references it.
|
|
228
|
+
* @param request - Session and attachment identities used for authorization.
|
|
229
|
+
* @returns the durable attachment reference and base64-encoded bytes.
|
|
230
|
+
*/
|
|
231
|
+
@@ -348,31 +366,35 @@ export class SessionCommandController {
|
|
232
|
+
if (error instanceof ApiSessionNotFound) {
|
|
233
|
+
throw new RemoteError('session/not-found', error.message, { sessionId: request.sessionId })
|
|
234
|
+
}
|
|
235
|
+
+ console.error('session-controller: attachment authorization failed', error)
|
|
236
|
+
throw new RemoteError(
|
|
237
|
+
'gateway/internal',
|
|
238
|
+
`attachment authorization unavailable for session "${request.sessionId}": ${String(error)}`,
|
|
239
|
+
{},
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
- const ref = referencedImage(source.events, String(request.attachmentId))
|
|
243
|
+
- if (ref === undefined) {
|
|
244
|
+
+ const referenced = referencedAttachment(source.events, String(request.attachmentId))
|
|
245
|
+
+ if (referenced === undefined) {
|
|
246
|
+
throw new RemoteError(
|
|
247
|
+
'session/attachment-invalid',
|
|
248
|
+
- 'Image is not referenced by this session.',
|
|
249
|
+
+ 'Attachment is not referenced by this session.',
|
|
250
|
+
{ reason: 'ATTACHMENT_NOT_REFERENCED' },
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
- const stored = await this.ctx.attachments.readImage(ref)
|
|
255
|
+
+ const stored = referenced.kind === 'image'
|
|
256
|
+
+ ? await this.ctx.attachments.readImage(referenced.ref)
|
|
257
|
+
+ : await this.ctx.attachments.readFile(referenced.ref)
|
|
258
|
+
return {
|
|
259
|
+
attachment: stored.ref,
|
|
260
|
+
data: Buffer.from(stored.data).toString('base64'),
|
|
261
|
+
}
|
|
262
|
+
} catch (error) {
|
|
263
|
+
+ console.error('session-controller: attachment read failed', error)
|
|
264
|
+
if (error instanceof AttachmentError) {
|
|
265
|
+
throw new RemoteError('session/attachment-invalid', error.message, { reason: error.code })
|
|
216
266
|
}
|
|
217
|
-
|
|
218
|
-
+
|
|
219
|
-
+ else agent.followup(message)
|
|
220
|
-
return { accepted: true }
|
|
267
|
+
- throw new RemoteError('gateway/internal', 'Unable to read image attachment.', {})
|
|
268
|
+
+ throw new RemoteError('gateway/internal', 'Unable to read attachment.', {})
|
|
221
269
|
}
|
|
222
|
-
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
@@ -507,55 +529,98 @@ async function durablePromptContent(
|
|
273
|
+
: { type: 'image', attachment: refs[next++] as ImageAttachmentRef })
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
-function imageBlockIn(
|
|
277
|
+
+type ReferencedAttachment =
|
|
278
|
+
+ | { readonly kind: 'image'; readonly ref: ImageAttachmentRef }
|
|
279
|
+
+ | { readonly kind: 'file'; readonly ref: FileAttachmentRef }
|
|
280
|
+
+
|
|
281
|
+
+function matchingAttachment(
|
|
282
|
+
+ value: unknown,
|
|
283
|
+
+ kind: ReferencedAttachment['kind'],
|
|
284
|
+
+ attachmentId: string,
|
|
285
|
+
+): ReferencedAttachment | undefined {
|
|
286
|
+
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined
|
|
287
|
+
+ if (String((value as { readonly attachmentId?: unknown }).attachmentId) !== attachmentId) return undefined
|
|
288
|
+
+ return kind === 'image'
|
|
289
|
+
+ ? { kind, ref: value as ImageAttachmentRef }
|
|
290
|
+
+ : { kind, ref: value as FileAttachmentRef }
|
|
291
|
+
+}
|
|
292
|
+
+
|
|
293
|
+
+function attachmentBlockIn(
|
|
294
|
+
content: unknown,
|
|
295
|
+
- match: (ref: ImageAttachmentRef) => boolean,
|
|
296
|
+
-): ImageAttachmentRef | undefined {
|
|
297
|
+
+ attachmentId: string,
|
|
298
|
+
+): ReferencedAttachment | undefined {
|
|
299
|
+
if (!Array.isArray(content)) return undefined
|
|
300
|
+
for (const value of content) {
|
|
301
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) continue
|
|
302
|
+
- const block = value as { readonly type?: unknown; readonly attachment?: unknown; readonly content?: unknown }
|
|
303
|
+
- if (block.type === 'image' && typeof block.attachment === 'object' && block.attachment !== null) {
|
|
304
|
+
- const ref = block.attachment as ImageAttachmentRef
|
|
305
|
+
- if (match(ref)) return ref
|
|
306
|
+
+ const block = value as {
|
|
307
|
+
+ readonly type?: unknown
|
|
308
|
+
+ readonly attachment?: unknown
|
|
309
|
+
+ readonly parsed?: unknown
|
|
310
|
+
+ readonly content?: unknown
|
|
311
|
+
+ }
|
|
312
|
+
+ if (block.type === 'image') {
|
|
313
|
+
+ const found = matchingAttachment(block.attachment, 'image', attachmentId)
|
|
314
|
+
+ if (found !== undefined) return found
|
|
315
|
+
+ }
|
|
316
|
+
+ if (block.type === 'document') {
|
|
317
|
+
+ const original = matchingAttachment(block.attachment, 'file', attachmentId)
|
|
318
|
+
+ if (original !== undefined) return original
|
|
319
|
+
+ if (typeof block.parsed === 'object' && block.parsed !== null && !Array.isArray(block.parsed)) {
|
|
320
|
+
+ const parsed = block.parsed as {
|
|
321
|
+
+ readonly markdown?: unknown
|
|
322
|
+
+ readonly modelText?: unknown
|
|
323
|
+
+ readonly contentList?: unknown
|
|
324
|
+
+ readonly images?: unknown
|
|
325
|
+
+ }
|
|
326
|
+
+ for (const artifact of [parsed.markdown, parsed.modelText, parsed.contentList]) {
|
|
327
|
+
+ const found = matchingAttachment(artifact, 'file', attachmentId)
|
|
328
|
+
+ if (found !== undefined) return found
|
|
329
|
+
+ }
|
|
330
|
+
+ if (Array.isArray(parsed.images)) {
|
|
331
|
+
+ for (const image of parsed.images) {
|
|
332
|
+
+ const found = matchingAttachment(image, 'image', attachmentId)
|
|
333
|
+
+ if (found !== undefined) return found
|
|
334
|
+
+ }
|
|
335
|
+
+ }
|
|
336
|
+
+ }
|
|
337
|
+
}
|
|
338
|
+
if (block.type === 'tool-result') {
|
|
339
|
+
- const nested = imageBlockIn(block.content, match)
|
|
340
|
+
+ const nested = attachmentBlockIn(block.content, attachmentId)
|
|
341
|
+
if (nested !== undefined) return nested
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return undefined
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
-function imageInEvent(
|
|
348
|
+
+function attachmentInEvent(
|
|
349
|
+
event: SessionEvent,
|
|
350
|
+
- match: (ref: ImageAttachmentRef) => boolean,
|
|
351
|
+
-): ImageAttachmentRef | undefined {
|
|
352
|
+
+ attachmentId: string,
|
|
353
|
+
+): ReferencedAttachment | undefined {
|
|
354
|
+
const data = event.data as {
|
|
355
|
+
readonly content?: unknown
|
|
356
|
+
readonly message?: { readonly content?: unknown }
|
|
357
|
+
readonly inserted?: readonly { readonly content?: unknown }[]
|
|
358
|
+
readonly chunk?: { readonly type?: unknown; readonly block?: unknown }
|
|
359
|
+
}
|
|
360
|
+
- const direct = imageBlockIn(data.content, match)
|
|
361
|
+
+ const direct = attachmentBlockIn(data.content, attachmentId)
|
|
362
|
+
if (direct !== undefined) return direct
|
|
363
|
+
- const message = imageBlockIn(data.message?.content, match)
|
|
364
|
+
+ const message = attachmentBlockIn(data.message?.content, attachmentId)
|
|
365
|
+
if (message !== undefined) return message
|
|
366
|
+
for (const inserted of data.inserted ?? []) {
|
|
367
|
+
- const found = imageBlockIn(inserted.content, match)
|
|
368
|
+
+ const found = attachmentBlockIn(inserted.content, attachmentId)
|
|
369
|
+
if (found !== undefined) return found
|
|
370
|
+
}
|
|
371
|
+
return event.type === 'assistant/chunk' && data.chunk?.type === 'block-end'
|
|
372
|
+
- ? imageBlockIn([data.chunk.block], match)
|
|
373
|
+
+ ? attachmentBlockIn([data.chunk.block], attachmentId)
|
|
374
|
+
: undefined
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
-function referencedImage(
|
|
378
|
+
+function referencedAttachment(
|
|
379
|
+
events: readonly SessionEvent[],
|
|
380
|
+
attachmentId: string,
|
|
381
|
+
-): ImageAttachmentRef | undefined {
|
|
382
|
+
+): ReferencedAttachment | undefined {
|
|
383
|
+
for (const event of events) {
|
|
384
|
+
- const found = imageInEvent(event, ref => String(ref.attachmentId) === attachmentId)
|
|
385
|
+
+ const found = attachmentInEvent(event, attachmentId)
|
|
386
|
+
if (found !== undefined) return found
|
|
387
|
+
}
|
|
388
|
+
return undefined
|
|
223
389
|
diff --git a/packages/api/session-controller/src/index.ts b/packages/api/session-controller/src/index.ts
|
|
224
|
-
index
|
|
390
|
+
index 572f895754..b828038982 100644
|
|
225
391
|
--- a/packages/api/session-controller/src/index.ts
|
|
226
392
|
+++ b/packages/api/session-controller/src/index.ts
|
|
227
393
|
@@ -3,6 +3,7 @@
|
|
@@ -232,7 +398,7 @@ index 342dd977ebcb7b12e6c8e4c7f9cb93c72e23261c..f2a42d4e33a450a5874aed5a43bf9184
|
|
|
232
398
|
import { canOpenNativePath, openNativePath } from '@deepseek-ai/dsh-native-command'
|
|
233
399
|
import type { SessionEvent, SessionHeader, SessionId } from '@deepseek-ai/dsh-session'
|
|
234
400
|
import type { SessionObservation } from '@deepseek-ai/dsh-session-query'
|
|
235
|
-
@@ -
|
|
401
|
+
@@ -324,6 +325,21 @@ export class SessionController extends TypertRemoteService {
|
|
236
402
|
return this.commands.prompt(request)
|
|
237
403
|
}
|
|
238
404
|
|
|
@@ -255,7 +421,7 @@ index 342dd977ebcb7b12e6c8e4c7f9cb93c72e23261c..f2a42d4e33a450a5874aed5a43bf9184
|
|
|
255
421
|
* Read one image proven reachable from the addressed Session log.
|
|
256
422
|
* @param request - Session and attachment identities used for authorization.
|
|
257
423
|
diff --git a/packages/api/session-controller/src/list.ts b/packages/api/session-controller/src/list.ts
|
|
258
|
-
index
|
|
424
|
+
index ab72da1c92..be3fbe1e31 100644
|
|
259
425
|
--- a/packages/api/session-controller/src/list.ts
|
|
260
426
|
+++ b/packages/api/session-controller/src/list.ts
|
|
261
427
|
@@ -3,7 +3,7 @@
|
|
@@ -281,18 +447,18 @@ index c9a53a6bd0209669b3e7994d0cb1aa29dfeabf2f..f15912a035586dfa324bf220378c6407
|
|
|
281
447
|
/**
|
|
282
448
|
* Advance the Session-list metadata projection by one committed event.
|
|
283
449
|
* @param state - metadata before the event.
|
|
284
|
-
@@ -
|
|
450
|
+
@@ -107,6 +114,17 @@ export class ApiSessionList {
|
|
285
451
|
},
|
|
286
452
|
stateVersion: 1,
|
|
287
453
|
})
|
|
288
|
-
+
|
|
454
|
+
+ ctx.sessionProjections.register<'documentLimits', null>({
|
|
289
455
|
+ key: 'documentLimits',
|
|
290
456
|
+ stateSchema: z.null(),
|
|
291
457
|
+ init: () => null,
|
|
292
458
|
+ apply: state => state,
|
|
293
459
|
+ wire: {
|
|
294
460
|
+ viewSchema: documentLimitsSchema,
|
|
295
|
-
+ view: () =>
|
|
461
|
+
+ view: () => attachmentCtx.attachments.documentLimits,
|
|
296
462
|
+ },
|
|
297
463
|
+ stateVersion: 1,
|
|
298
464
|
+ })
|
|
@@ -300,7 +466,7 @@ index c9a53a6bd0209669b3e7994d0cb1aa29dfeabf2f..f15912a035586dfa324bf220378c6407
|
|
|
300
466
|
}
|
|
301
467
|
|
|
302
468
|
diff --git a/packages/api/session-controller/src/types.ts b/packages/api/session-controller/src/types.ts
|
|
303
|
-
index
|
|
469
|
+
index c0b25fcf2c..f080be8601 100644
|
|
304
470
|
--- a/packages/api/session-controller/src/types.ts
|
|
305
471
|
+++ b/packages/api/session-controller/src/types.ts
|
|
306
472
|
@@ -1,7 +1,7 @@
|
|
@@ -308,11 +474,11 @@ index 167937e3d3317bdc20878662f337f88b85ea47f8..97fc8315afe139fe7c009e5295ee807e
|
|
|
308
474
|
|
|
309
475
|
import type {
|
|
310
476
|
- AttachmentIdType, ImageAttachmentLimits, ImageAttachmentRef, ImageMediaType,
|
|
311
|
-
+ AttachmentIdType, DocumentAttachmentLimits,
|
|
477
|
+
+ AttachmentIdType, DocumentAttachmentLimits, FileAttachmentRef, ImageAttachmentLimits, ImageMediaType,
|
|
312
478
|
} from '@deepseek-ai/dsh-attachment'
|
|
313
479
|
import type { Branded } from '@deepseek-ai/dsh-brand'
|
|
314
480
|
import type { MessageId } from '@deepseek-ai/dsh-llm/brand'
|
|
315
|
-
@@ -
|
|
481
|
+
@@ -19,6 +19,8 @@ declare module '@deepseek-ai/dsh-session-projection/types' {
|
|
316
482
|
sessionListMetadata: SessionListMetadata
|
|
317
483
|
/** Host state for the boot-constant image-limit view. */
|
|
318
484
|
imageLimits: null
|
|
@@ -321,7 +487,7 @@ index 167937e3d3317bdc20878662f337f88b85ea47f8..97fc8315afe139fe7c009e5295ee807e
|
|
|
321
487
|
/** Durable model selection already used by a request and still pending for a later request. */
|
|
322
488
|
modelSelection: ModelSelectionProjectionState
|
|
323
489
|
}
|
|
324
|
-
@@ -
|
|
490
|
+
@@ -27,6 +29,8 @@ declare module '@deepseek-ai/dsh-session-projection/types' {
|
|
325
491
|
sessionListMetadata: SessionListMetadata
|
|
326
492
|
/** Image-intake limits enforced by the Session prompt endpoint. */
|
|
327
493
|
imageLimits: ImageAttachmentLimits
|
|
@@ -330,8 +496,27 @@ index 167937e3d3317bdc20878662f337f88b85ea47f8..97fc8315afe139fe7c009e5295ee807e
|
|
|
330
496
|
/** Durable model selection already used and selected for the next request. */
|
|
331
497
|
modelSelection: ModelSelectionProjection
|
|
332
498
|
}
|
|
499
|
+
@@ -313,15 +317,15 @@ export interface SessionPromptValue {
|
|
500
|
+
readonly accepted: true
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
-/** Durable image read request. */
|
|
504
|
+
+/** Durable attachment read request. */
|
|
505
|
+
export interface SessionAttachmentRequest {
|
|
506
|
+
readonly sessionId: SessionId
|
|
507
|
+
readonly attachmentId: AttachmentIdType
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
-/** Durable image read response value. */
|
|
511
|
+
+/** Durable attachment read response value. */
|
|
512
|
+
export interface SessionAttachmentValue {
|
|
513
|
+
- readonly attachment: ImageAttachmentRef
|
|
514
|
+
+ readonly attachment: FileAttachmentRef
|
|
515
|
+
readonly data: string
|
|
516
|
+
}
|
|
517
|
+
|
|
333
518
|
diff --git a/packages/attachment/attachment-local/src/index.ts b/packages/attachment/attachment-local/src/index.ts
|
|
334
|
-
index
|
|
519
|
+
index 16b963f225..c10fe04f5d 100644
|
|
335
520
|
--- a/packages/attachment/attachment-local/src/index.ts
|
|
336
521
|
+++ b/packages/attachment/attachment-local/src/index.ts
|
|
337
522
|
@@ -5,22 +5,26 @@ import { Context } from '@deepseek-ai/cordis'
|
|
@@ -445,7 +630,7 @@ index 16b963f2259d0fc7e7a0761afb9a73eb1603e7ad..c10fe04f5dd1cf9812c37d814a9ce7c4
|
|
|
445
630
|
await this.compression.run(() => validateImageFile(input, this.imageLimits, this.normalizationPolicy))
|
|
446
631
|
}
|
|
447
632
|
diff --git a/packages/attachment/attachment-local/src/store.ts b/packages/attachment/attachment-local/src/store.ts
|
|
448
|
-
index
|
|
633
|
+
index e5a979aec1..14de66088e 100644
|
|
449
634
|
--- a/packages/attachment/attachment-local/src/store.ts
|
|
450
635
|
+++ b/packages/attachment/attachment-local/src/store.ts
|
|
451
636
|
@@ -9,9 +9,12 @@ import {
|
|
@@ -489,13 +674,14 @@ index e5a979aec14e91ae726b5aef414b307dc0780795..26f8e40e2a97809660f0df1dc7d03f81
|
|
|
489
674
|
}
|
|
490
675
|
|
|
491
676
|
async function inspectMetadata(
|
|
492
|
-
@@ -183,73 +189,
|
|
677
|
+
@@ -183,73 +189,121 @@ async function ensureDurableHome(path: string): Promise<string> {
|
|
493
678
|
}
|
|
494
679
|
|
|
495
680
|
/**
|
|
496
681
|
- * Publish one already verified normalized image below a versioned attachment root.
|
|
497
682
|
+ * Publish one immutable object after its content digest is known.
|
|
498
|
-
+ *
|
|
683
|
+
+ * The operation returns only after file and directory durability; Sessions
|
|
684
|
+
+ * may record only references returned by this function.
|
|
499
685
|
* @param root - absolute `DSH_HOME/attachments/v1` root.
|
|
500
686
|
- * @param prepared - deterministic normalized bytes and reference.
|
|
501
687
|
- * @returns durable content-addressed normalized image reference.
|
|
@@ -650,7 +836,7 @@ index e5a979aec14e91ae726b5aef414b307dc0780795..26f8e40e2a97809660f0df1dc7d03f81
|
|
|
650
836
|
}
|
|
651
837
|
|
|
652
838
|
diff --git a/packages/attachment/attachment/src/error.ts b/packages/attachment/attachment/src/error.ts
|
|
653
|
-
index
|
|
839
|
+
index c19229872b..67f9852511 100644
|
|
654
840
|
--- a/packages/attachment/attachment/src/error.ts
|
|
655
841
|
+++ b/packages/attachment/attachment/src/error.ts
|
|
656
842
|
@@ -24,6 +24,7 @@ export type AttachmentErrorCode =
|
|
@@ -662,7 +848,7 @@ index c19229872b976dc7e20b32220cd07bcc7d380395..67f9852511845c38e0a8359d85da24b9
|
|
|
662
848
|
/** Runtime membership for structurally compatible errors crossing package boundaries. */
|
|
663
849
|
const IMAGE_ADMISSION_ERROR_CODE_SET: ReadonlySet<string> = new Set(IMAGE_ADMISSION_ERROR_CODES)
|
|
664
850
|
diff --git a/packages/attachment/attachment/src/index.ts b/packages/attachment/attachment/src/index.ts
|
|
665
|
-
index
|
|
851
|
+
index 4ee001b86c..9f96b4bf5c 100644
|
|
666
852
|
--- a/packages/attachment/attachment/src/index.ts
|
|
667
853
|
+++ b/packages/attachment/attachment/src/index.ts
|
|
668
854
|
@@ -3,11 +3,15 @@
|
|
@@ -746,7 +932,7 @@ index 4ee001b86c55aaa1da788543477ca670a24f8a5d..9f96b4bf5cdac35afb804e378013cb63
|
|
|
746
932
|
* Validate one image without persisting it.
|
|
747
933
|
* Batch callers validate every member before saving any member.
|
|
748
934
|
diff --git a/packages/attachment/attachment/src/types.ts b/packages/attachment/attachment/src/types.ts
|
|
749
|
-
index
|
|
935
|
+
index 046444cd76..146fe3d501 100644
|
|
750
936
|
--- a/packages/attachment/attachment/src/types.ts
|
|
751
937
|
+++ b/packages/attachment/attachment/src/types.ts
|
|
752
938
|
@@ -7,6 +7,47 @@ export type { AttachmentId } from './brand.ts'
|
|
@@ -840,23 +1026,20 @@ index 046444cd76551380a8d1d924f27db9a002d8a93f..146fe3d501b038c8affc61a59c16d727
|
|
|
840
1026
|
/** Stored image bytes returned after reference and digest verification. */
|
|
841
1027
|
export interface StoredImageAttachment {
|
|
842
1028
|
ref: ImageAttachmentRef
|
|
843
|
-
diff --git a/packages/client/ui-attachment/src/client/
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
+ display: flex;
|
|
850
|
-
+ padding: 4px 12px 0;
|
|
851
|
-
+}
|
|
852
|
-
+
|
|
1029
|
+
diff --git a/packages/client/ui-attachment/src/client/AttachmentPicker.module.css b/packages/client/ui-attachment/src/client/AttachmentPicker.module.css
|
|
1030
|
+
new file mode 100644
|
|
1031
|
+
index 0000000000..da3ae830f9
|
|
1032
|
+
--- /dev/null
|
|
1033
|
+
+++ b/packages/client/ui-attachment/src/client/AttachmentPicker.module.css
|
|
1034
|
+
@@ -0,0 +1,26 @@
|
|
853
1035
|
+.fileInput {
|
|
854
1036
|
+ display: none;
|
|
855
1037
|
+}
|
|
856
1038
|
+
|
|
857
|
-
+.
|
|
1039
|
+
+.trigger {
|
|
858
1040
|
+ display: grid;
|
|
859
1041
|
+ place-items: center;
|
|
1042
|
+
+ flex: none;
|
|
860
1043
|
+ width: 28px;
|
|
861
1044
|
+ height: 28px;
|
|
862
1045
|
+ padding: 0;
|
|
@@ -867,34 +1050,79 @@ index 770a64cef5dba2baded4adb7afe63ae88c7fcffc..1cdfd8c3f5b1138b76ce294db6e0198c
|
|
|
867
1050
|
+ cursor: pointer;
|
|
868
1051
|
+}
|
|
869
1052
|
+
|
|
870
|
-
+.
|
|
1053
|
+
+.trigger:hover:not(:disabled) {
|
|
871
1054
|
+ background: var(--dsw-alias-interactive-bg-hover-solid);
|
|
872
1055
|
+}
|
|
873
1056
|
+
|
|
874
|
-
+.
|
|
1057
|
+
+.trigger:disabled {
|
|
875
1058
|
+ opacity: 0.5;
|
|
876
1059
|
+ cursor: default;
|
|
877
1060
|
+}
|
|
1061
|
+
diff --git a/packages/client/ui-attachment/src/client/AttachmentPicker.tsx b/packages/client/ui-attachment/src/client/AttachmentPicker.tsx
|
|
1062
|
+
new file mode 100644
|
|
1063
|
+
index 0000000000..8edb8c5da5
|
|
1064
|
+
--- /dev/null
|
|
1065
|
+
+++ b/packages/client/ui-attachment/src/client/AttachmentPicker.tsx
|
|
1066
|
+
@@ -0,0 +1,46 @@
|
|
1067
|
+
+import { useCallback, useRef } from 'react'
|
|
1068
|
+
+import type { ChangeEvent } from 'react'
|
|
1069
|
+
+import { IconPaperclipOutline16, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
1070
|
+
+import type { ComposerAttachmentPickerProps } from '@deepseek-ai/dsh-client-ui-conversation/client'
|
|
1071
|
+
+import css from './AttachmentPicker.module.css'
|
|
1072
|
+
+
|
|
1073
|
+
+/**
|
|
1074
|
+
+ * Render one image-and-document chooser directly after the command button.
|
|
1075
|
+
+ * @param props - current intake availability, file callback, and localized labels.
|
|
1076
|
+
+ * @returns the hidden file input and its visible attachment action.
|
|
1077
|
+
+ */
|
|
1078
|
+
+export function AttachmentPicker({ canAcceptFiles, onAddFiles, t }: ComposerAttachmentPickerProps) {
|
|
1079
|
+
+ const fileInput = useRef<HTMLInputElement>(null)
|
|
1080
|
+
+ const chooseFiles = useCallback(() => { fileInput.current?.click() }, [])
|
|
1081
|
+
+ const onFilesChosen = useCallback((event: ChangeEvent<HTMLInputElement>) => {
|
|
1082
|
+
+ const files = event.currentTarget.files
|
|
1083
|
+
+ if (files !== null && files.length > 0) onAddFiles([...files])
|
|
1084
|
+
+ event.currentTarget.value = ''
|
|
1085
|
+
+ }, [onAddFiles])
|
|
878
1086
|
+
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
1087
|
+
+ return (
|
|
1088
|
+
+ <>
|
|
1089
|
+
+ <input
|
|
1090
|
+
+ ref={fileInput}
|
|
1091
|
+
+ className={css.fileInput}
|
|
1092
|
+
+ type="file"
|
|
1093
|
+
+ multiple
|
|
1094
|
+
+ accept="image/png,image/jpeg,image/webp,image/gif,.pdf,.docx,.pptx,.xlsx"
|
|
1095
|
+
+ disabled={!canAcceptFiles}
|
|
1096
|
+
+ onChange={onFilesChosen}
|
|
1097
|
+
+ />
|
|
1098
|
+
+ <Tooltip label={t('attachment.add')} side="top" delayMs={500}>
|
|
1099
|
+
+ <button
|
|
1100
|
+
+ type="button"
|
|
1101
|
+
+ className={css.trigger}
|
|
1102
|
+
+ aria-label={t('attachment.add')}
|
|
1103
|
+
+ disabled={!canAcceptFiles}
|
|
1104
|
+
+ onMouseDown={(event) => { event.preventDefault() }}
|
|
1105
|
+
+ onClick={chooseFiles}
|
|
1106
|
+
+ >
|
|
1107
|
+
+ <IconPaperclipOutline16 />
|
|
1108
|
+
+ </button>
|
|
1109
|
+
+ </Tooltip>
|
|
1110
|
+
+ </>
|
|
1111
|
+
+ )
|
|
1112
|
+
+}
|
|
882
1113
|
diff --git a/packages/client/ui-attachment/src/client/ComposerAttachments.tsx b/packages/client/ui-attachment/src/client/ComposerAttachments.tsx
|
|
883
|
-
index
|
|
1114
|
+
index 1e45dc6230..79db6a90b3 100644
|
|
884
1115
|
--- a/packages/client/ui-attachment/src/client/ComposerAttachments.tsx
|
|
885
1116
|
+++ b/packages/client/ui-attachment/src/client/ComposerAttachments.tsx
|
|
886
|
-
@@ -
|
|
887
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
888
|
-
+import type { ChangeEvent } from 'react'
|
|
1117
|
+
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
889
1118
|
import type {
|
|
890
1119
|
ComposerAttachment, ComposerAttachmentsProps,
|
|
891
1120
|
} from '@deepseek-ai/dsh-client-ui-conversation/client'
|
|
892
1121
|
+import type { PropsRenderSlots } from '@deepseek-ai/dsh-client-ui-slots'
|
|
893
|
-
+import { IconPaperclipOutline16, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
894
1122
|
import { AttachmentRail } from '../AttachmentRail.tsx'
|
|
895
1123
|
import type { AttachmentRailItem } from '../AttachmentRail.tsx'
|
|
896
1124
|
import { DropOverlay } from '../DropOverlay.tsx'
|
|
897
|
-
@@ -15,
|
|
1125
|
+
@@ -15,9 +16,12 @@ interface ComposerRailItem extends AttachmentRailItem {
|
|
898
1126
|
}
|
|
899
1127
|
|
|
900
1128
|
/** Draft-image rail, document drop target, and original-image preview slot entry. */
|
|
@@ -909,11 +1137,7 @@ index 1e45dc6230ac024154822ddca86509bf2850d791..553c6bc24acfb65d11358ebf9fa9f082
|
|
|
909
1137
|
const [preview, setPreview] = useState<ComposerAttachment | null>(null)
|
|
910
1138
|
const [dragActive, setDragActive] = useState(false)
|
|
911
1139
|
const dragDepth = useRef(0)
|
|
912
|
-
+
|
|
913
|
-
const closePreview = useCallback(() => { setPreview(null) }, [])
|
|
914
|
-
|
|
915
|
-
useEffect(() => {
|
|
916
|
-
@@ -62,7 +69,7 @@ export function ComposerAttachments({
|
|
1140
|
+
@@ -62,7 +66,7 @@ export function ComposerAttachments({
|
|
917
1141
|
if (dataTransfer === null) return
|
|
918
1142
|
event.preventDefault()
|
|
919
1143
|
reset()
|
|
@@ -922,23 +1146,16 @@ index 1e45dc6230ac024154822ddca86509bf2850d791..553c6bc24acfb65d11358ebf9fa9f082
|
|
|
922
1146
|
}
|
|
923
1147
|
document.addEventListener('dragenter', onDragEnter)
|
|
924
1148
|
document.addEventListener('dragover', onDragOver)
|
|
925
|
-
@@ -76,7 +
|
|
1149
|
+
@@ -76,7 +80,7 @@ export function ComposerAttachments({
|
|
926
1150
|
document.removeEventListener('drop', onDrop)
|
|
927
1151
|
window.removeEventListener('dragend', reset)
|
|
928
1152
|
}
|
|
929
1153
|
- }, [canAcceptDrop, onAddImages])
|
|
930
1154
|
+ }, [canAcceptDrop, onAddFiles])
|
|
931
|
-
+
|
|
932
|
-
+ const chooseFiles = useCallback(() => { fileInput.current?.click() }, [])
|
|
933
|
-
+ const onFilesChosen = useCallback((event: ChangeEvent<HTMLInputElement>) => {
|
|
934
|
-
+ const files = event.currentTarget.files
|
|
935
|
-
+ if (files !== null && files.length > 0) onAddFiles([...files])
|
|
936
|
-
+ event.currentTarget.value = ''
|
|
937
|
-
+ }, [onAddFiles])
|
|
938
1155
|
|
|
939
1156
|
const railItems = useMemo<ComposerRailItem[]>(() => attachments.map(attachment => ({
|
|
940
1157
|
id: attachment.id,
|
|
941
|
-
@@ -91,9 +
|
|
1158
|
+
@@ -91,9 +95,12 @@ export function ComposerAttachments({
|
|
942
1159
|
{dragActive && (
|
|
943
1160
|
<DropOverlay
|
|
944
1161
|
disabled={!canAcceptDrop}
|
|
@@ -948,28 +1165,12 @@ index 1e45dc6230ac024154822ddca86509bf2850d791..553c6bc24acfb65d11358ebf9fa9f082
|
|
|
948
1165
|
+ : dropOverlayLabels(t, canAcceptDrop, dropLimits)}
|
|
949
1166
|
/>
|
|
950
1167
|
)}
|
|
951
|
-
+ <div className={css.picker}>
|
|
952
|
-
+ <input
|
|
953
|
-
+ ref={fileInput}
|
|
954
|
-
+ className={css.fileInput}
|
|
955
|
-
+ type="file"
|
|
956
|
-
+ multiple
|
|
957
|
-
+ accept="image/png,image/jpeg,image/webp,image/gif,.pdf,.docx,.pptx,.xlsx"
|
|
958
|
-
+ disabled={!canAcceptDrop}
|
|
959
|
-
+ onChange={onFilesChosen}
|
|
960
|
-
+ />
|
|
961
|
-
+ <Tooltip label={t('attachment.add')} side="top">
|
|
962
|
-
+ <button type="button" className={css.add} aria-label={t('attachment.add')} disabled={!canAcceptDrop} onClick={chooseFiles}>
|
|
963
|
-
+ <IconPaperclipOutline16 />
|
|
964
|
-
+ </button>
|
|
965
|
-
+ </Tooltip>
|
|
966
|
-
+ </div>
|
|
967
1168
|
+ {renderSlot('conversation.input.attachments.documents', { documents, onRemoveDocument })}
|
|
968
1169
|
{railItems.length > 0 && (
|
|
969
1170
|
<div className={css.rail}>
|
|
970
1171
|
<AttachmentRail
|
|
971
1172
|
diff --git a/packages/client/ui-attachment/src/client/MessageImages.tsx b/packages/client/ui-attachment/src/client/MessageImages.tsx
|
|
972
|
-
index
|
|
1173
|
+
index c84914db54..46426f4519 100644
|
|
973
1174
|
--- a/packages/client/ui-attachment/src/client/MessageImages.tsx
|
|
974
1175
|
+++ b/packages/client/ui-attachment/src/client/MessageImages.tsx
|
|
975
1176
|
@@ -1,8 +1,30 @@
|
|
@@ -994,7 +1195,7 @@ index c84914db543de23165732809bbe9c723b34dfa90..70b28eb66089203432df9efed6368923
|
|
|
994
1195
|
+}
|
|
995
1196
|
+
|
|
996
1197
|
+/** Chat attachment slot entry. */
|
|
997
|
-
+export function MessageImages({ images, documents
|
|
1198
|
+
+export function MessageImages({ images, documents, loadImage, align, t, renderSlot }: MessageImagesProps) {
|
|
998
1199
|
+ return <AttachmentGallery images={images} loadImage={loadImage} align={align} t={t} documentCards={renderSlot('conversation.message.images.documents', { documents })} />
|
|
999
1200
|
+}
|
|
1000
1201
|
+
|
|
@@ -1003,23 +1204,31 @@ index c84914db543de23165732809bbe9c723b34dfa90..70b28eb66089203432df9efed6368923
|
|
|
1003
1204
|
+ & PropsLocale<'conversation'>
|
|
1004
1205
|
+
|
|
1005
1206
|
+/** Trajectory attachment slot entry. */
|
|
1006
|
-
+export function TrajectoryImages({ images, documents
|
|
1207
|
+
+export function TrajectoryImages({ images, documents, loadImage, align, t, renderSlot }: TrajectoryProps) {
|
|
1007
1208
|
+ return <AttachmentGallery images={images} loadImage={loadImage} align={align} t={t} documentCards={renderSlot('conversation.trajectory.images.documents', { documents })} />
|
|
1008
1209
|
}
|
|
1009
1210
|
diff --git a/packages/client/ui-attachment/src/client/index.ts b/packages/client/ui-attachment/src/client/index.ts
|
|
1010
|
-
index
|
|
1211
|
+
index 8fe94f64ee..0654b882f4 100644
|
|
1011
1212
|
--- a/packages/client/ui-attachment/src/client/index.ts
|
|
1012
1213
|
+++ b/packages/client/ui-attachment/src/client/index.ts
|
|
1013
|
-
@@ -
|
|
1214
|
+
@@ -4,24 +4,38 @@ import type {} from '@deepseek-ai/dsh-client-ui-chat/client'
|
|
1215
|
+
import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
|
|
1014
1216
|
import type {} from '@deepseek-ai/dsh-client-ui-renderer/client'
|
|
1015
1217
|
import type {} from '@deepseek-ai/dsh-client-ui-trajectory/client'
|
|
1218
|
+
+import { AttachmentPicker } from './AttachmentPicker.tsx'
|
|
1016
1219
|
import { ComposerAttachments } from './ComposerAttachments.tsx'
|
|
1017
1220
|
-import { MessageImages } from './MessageImages.tsx'
|
|
1018
1221
|
+import { MessageImages, TrajectoryImages } from './MessageImages.tsx'
|
|
1019
1222
|
|
|
1020
1223
|
/** Slot registry required by this presentation plugin. */
|
|
1021
1224
|
export const inject = ['slots']
|
|
1022
|
-
|
|
1225
|
+
|
|
1226
|
+
/** Register attachment presentation without exporting React components as package values. */
|
|
1227
|
+
export function apply(ctx: ClientContext): void {
|
|
1228
|
+
+ ctx.slots.inject('conversation.input.attachment-picker', () => ctx.slots.register({
|
|
1229
|
+
+ name: 'conversation.input.attachment-picker',
|
|
1230
|
+
+ locale: 'conversation',
|
|
1231
|
+
+ }, AttachmentPicker))
|
|
1023
1232
|
ctx.slots.inject('conversation.input.attachments', () => ctx.slots.register({
|
|
1024
1233
|
name: 'conversation.input.attachments',
|
|
1025
1234
|
locale: 'conversation',
|
|
@@ -1044,10 +1253,10 @@ index 8fe94f64ee171798431716e3765e446ca95c935f..a045a3433ef7984df767ce00000ef52d
|
|
|
1044
1253
|
+ }, TrajectoryImages))
|
|
1045
1254
|
}
|
|
1046
1255
|
diff --git a/packages/client/ui-attachment/tests/composer-attachments.client.spec.tsx b/packages/client/ui-attachment/tests/composer-attachments.client.spec.tsx
|
|
1047
|
-
index
|
|
1256
|
+
index ef77068f7e..ba4fc855d5 100644
|
|
1048
1257
|
--- a/packages/client/ui-attachment/tests/composer-attachments.client.spec.tsx
|
|
1049
1258
|
+++ b/packages/client/ui-attachment/tests/composer-attachments.client.spec.tsx
|
|
1050
|
-
@@ -53,
|
|
1259
|
+
@@ -53,22 +53,26 @@ function attachment(id: string, name = `${id}.png`): ComposerAttachment {
|
|
1051
1260
|
}
|
|
1052
1261
|
}
|
|
1053
1262
|
|
|
@@ -1055,9 +1264,12 @@ index ef77068f7e017845bd400c96bb63340aa0b3cc16..b5f9c55df4a2d75f99df8ab17755b3b2
|
|
|
1055
1264
|
+function props(overrides: Partial<ComposerAttachmentsOwnerProps> = {}): Parameters<typeof ComposerAttachments>[0] {
|
|
1056
1265
|
return {
|
|
1057
1266
|
attachments: [],
|
|
1267
|
+
+ documents: [],
|
|
1058
1268
|
canAcceptDrop: true,
|
|
1059
1269
|
onAddImages: () => {},
|
|
1270
|
+
+ onAddFiles: () => {},
|
|
1060
1271
|
onRemoveImage: () => {},
|
|
1272
|
+
+ onRemoveDocument: () => {},
|
|
1061
1273
|
t,
|
|
1062
1274
|
+ renderSlot: () => null,
|
|
1063
1275
|
...overrides,
|
|
@@ -1066,8 +1278,26 @@ index ef77068f7e017845bd400c96bb63340aa0b3cc16..b5f9c55df4a2d75f99df8ab17755b3b2
|
|
|
1066
1278
|
}
|
|
1067
1279
|
|
|
1068
1280
|
describe('ComposerAttachments', () => {
|
|
1281
|
+
it('accepts file drops anywhere on the document and keeps non-file drags native', () => {
|
|
1282
|
+
- const onAddImages = vi.fn()
|
|
1283
|
+
+ const onAddFiles = vi.fn()
|
|
1284
|
+
const view = render(<ComposerAttachments {...props({
|
|
1285
|
+
- onAddImages,
|
|
1286
|
+
+ onAddFiles,
|
|
1287
|
+
dropLimits: { count: 20, size: '5MB' },
|
|
1288
|
+
})} />)
|
|
1289
|
+
|
|
1290
|
+
@@ -87,7 +91,7 @@ describe('ComposerAttachments', () => {
|
|
1291
|
+
expect(fireEvent.dragOver(document.body, { dataTransfer })).toBe(false)
|
|
1292
|
+
expect(dataTransfer.dropEffect).toBe('copy')
|
|
1293
|
+
expect(fireEvent.drop(document.body, { dataTransfer })).toBe(false)
|
|
1294
|
+
- expect(onAddImages).toHaveBeenCalledWith([image])
|
|
1295
|
+
+ expect(onAddFiles).toHaveBeenCalledWith([image])
|
|
1296
|
+
expect(view.queryByRole('status')).toBeNull()
|
|
1297
|
+
})
|
|
1298
|
+
|
|
1069
1299
|
diff --git a/packages/client/ui-attachment/tests/message-image.client.spec.tsx b/packages/client/ui-attachment/tests/message-image.client.spec.tsx
|
|
1070
|
-
index
|
|
1300
|
+
index 76a720b983..0ed7b07d03 100644
|
|
1071
1301
|
--- a/packages/client/ui-attachment/tests/message-image.client.spec.tsx
|
|
1072
1302
|
+++ b/packages/client/ui-attachment/tests/message-image.client.spec.tsx
|
|
1073
1303
|
@@ -261,6 +261,7 @@ describe('ImageGallery', () => {
|
|
@@ -1078,7 +1308,11 @@ index 76a720b983be3c54d4af91435f5b82ecf746685c..62475e6924e1373611d7035b4319329d
|
|
|
1078
1308
|
useSession,
|
|
1079
1309
|
useSessions,
|
|
1080
1310
|
useSessionPendingInteraction,
|
|
1081
|
-
@@ -
|
|
1311
|
+
@@ -278,9 +279,11 @@ describe('ImageGallery', () => {
|
|
1312
|
+
submit: vi.fn(),
|
|
1313
|
+
},
|
|
1314
|
+
images: [{ attachment }],
|
|
1315
|
+
+ documents: [],
|
|
1082
1316
|
loadImage,
|
|
1083
1317
|
align: 'end',
|
|
1084
1318
|
t,
|
|
@@ -1086,10 +1320,31 @@ index 76a720b983be3c54d4af91435f5b82ecf746685c..62475e6924e1373611d7035b4319329d
|
|
|
1086
1320
|
}
|
|
1087
1321
|
const view = render(<MessageImages {...props} />)
|
|
1088
1322
|
await waitFor(() => { expect(view.getByAltText('history.png')).toBeTruthy() })
|
|
1323
|
+
diff --git a/packages/client/ui-chat/src/client/chat/AssistantMarkdown.tsx b/packages/client/ui-chat/src/client/chat/AssistantMarkdown.tsx
|
|
1324
|
+
index c517f9d0c2..f24f94ea1d 100644
|
|
1325
|
+
--- a/packages/client/ui-chat/src/client/chat/AssistantMarkdown.tsx
|
|
1326
|
+
+++ b/packages/client/ui-chat/src/client/chat/AssistantMarkdown.tsx
|
|
1327
|
+
@@ -87,6 +87,7 @@ export const AssistantMarkdown = memo(function AssistantMarkdown({
|
|
1328
|
+
<Fragment key={start}>
|
|
1329
|
+
{renderMessageImages({
|
|
1330
|
+
images: group.map(({ attachment }) => ({ attachment })),
|
|
1331
|
+
+ documents: [],
|
|
1332
|
+
align: 'start',
|
|
1333
|
+
})}
|
|
1334
|
+
</Fragment>,
|
|
1089
1335
|
diff --git a/packages/client/ui-chat/src/client/chat/MessageItem.tsx b/packages/client/ui-chat/src/client/chat/MessageItem.tsx
|
|
1090
|
-
index
|
|
1336
|
+
index cb8084fe41..5dd3aa8512 100644
|
|
1091
1337
|
--- a/packages/client/ui-chat/src/client/chat/MessageItem.tsx
|
|
1092
1338
|
+++ b/packages/client/ui-chat/src/client/chat/MessageItem.tsx
|
|
1339
|
+
@@ -1,7 +1,7 @@
|
|
1340
|
+
import { memo, useEffect, useMemo, useState } from 'react'
|
|
1341
|
+
import type { ReactNode } from 'react'
|
|
1342
|
+
import type { PendingSubmission } from '@deepseek-ai/dsh-api-session-controller/client'
|
|
1343
|
+
-import type { MessageImageSource } from '@deepseek-ai/dsh-client-ui-conversation/client'
|
|
1344
|
+
+import type { MessageDocumentSource, MessageImageSource } from '@deepseek-ai/dsh-client-ui-conversation/client'
|
|
1345
|
+
import { JsonBlock, projectUserText, StateDot } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
1346
|
+
import type { ChatNodeOwnerProps, ChatNodeViewProps, ChatViewSlotProps } from '../contract/slots.ts'
|
|
1347
|
+
import type { ModelRetryNode, TurnErrorNode, UserMessageNode } from '../contract/snapshot.ts'
|
|
1093
1348
|
@@ -11,14 +11,17 @@ import { MessageIconActions } from './MessageIconActions.tsx'
|
|
1094
1349
|
import css from './MessageItem.module.css'
|
|
1095
1350
|
|
|
@@ -1099,22 +1354,28 @@ index 467e486ecb3b28ddf7aa99281ad231032a98a76c..d89c77009fb52bdf7992f8f35c14553b
|
|
|
1099
1354
|
function contentParts(content: readonly unknown[]): {
|
|
1100
1355
|
text: string
|
|
1101
1356
|
images: { attachment: UserImage['attachment'] }[]
|
|
1102
|
-
+ documents:
|
|
1357
|
+
+ documents: MessageDocumentSource[]
|
|
1103
1358
|
rest: unknown[]
|
|
1104
1359
|
} {
|
|
1105
1360
|
const texts: string[] = []
|
|
1106
1361
|
const images: { attachment: UserImage['attachment'] }[] = []
|
|
1107
|
-
+ const documents:
|
|
1362
|
+
+ const documents: MessageDocumentSource[] = []
|
|
1108
1363
|
const rest: unknown[] = []
|
|
1109
1364
|
for (const block of content) {
|
|
1110
1365
|
const b = block as { type?: string; text?: string; attachment?: unknown }
|
|
1111
|
-
@@ -26,9 +29,
|
|
1366
|
+
@@ -26,9 +29,19 @@ function contentParts(content: readonly unknown[]): {
|
|
1112
1367
|
else if (b.type === 'image' && b.attachment !== undefined) {
|
|
1113
1368
|
images.push({ attachment: (b as UserImage).attachment })
|
|
1114
1369
|
}
|
|
1115
1370
|
+ else if (b.type === 'document' && b.attachment !== undefined) {
|
|
1116
|
-
+ const
|
|
1117
|
-
+
|
|
1371
|
+
+ const document = b as UserDocument
|
|
1372
|
+
+ const attachment = document.attachment
|
|
1373
|
+
+ documents.push({
|
|
1374
|
+
+ previewAttachmentId: document.parsed.markdown.attachmentId,
|
|
1375
|
+
+ name: attachment.name,
|
|
1376
|
+
+ mediaType: attachment.mediaType,
|
|
1377
|
+
+ bytes: attachment.bytes,
|
|
1378
|
+
+ })
|
|
1118
1379
|
+ }
|
|
1119
1380
|
else rest.push(block)
|
|
1120
1381
|
}
|
|
@@ -1123,8 +1384,8 @@ index 467e486ecb3b28ddf7aa99281ad231032a98a76c..d89c77009fb52bdf7992f8f35c14553b
|
|
|
1123
1384
|
}
|
|
1124
1385
|
|
|
1125
1386
|
function retrySeconds(milliseconds: number): number {
|
|
1126
|
-
@@ -
|
|
1127
|
-
|
|
1387
|
+
@@ -166,7 +179,7 @@ function UserStyleBubble({
|
|
1388
|
+
reveal?: 'always' | 'hover'
|
|
1128
1389
|
t: ChatViewSlotProps['t']
|
|
1129
1390
|
}): ReactNode {
|
|
1130
1391
|
- const { text, images: contentImages, rest } = contentParts(content)
|
|
@@ -1132,8 +1393,8 @@ index 467e486ecb3b28ddf7aa99281ad231032a98a76c..d89c77009fb52bdf7992f8f35c14553b
|
|
|
1132
1393
|
const images = previewImages ?? contentImages
|
|
1133
1394
|
const truncated = (total: number): string => t('json.truncated', { total })
|
|
1134
1395
|
const showBubble = text !== '' || rest.length > 0
|
|
1135
|
-
@@ -
|
|
1136
|
-
data-
|
|
1396
|
+
@@ -178,7 +191,7 @@ function UserStyleBubble({
|
|
1397
|
+
data-actions-reveal={reveal}
|
|
1137
1398
|
>
|
|
1138
1399
|
<div className={css.userStack}>
|
|
1139
1400
|
- {renderMessageImages({ images, align: 'end' })}
|
|
@@ -1142,7 +1403,7 @@ index 467e486ecb3b28ddf7aa99281ad231032a98a76c..d89c77009fb52bdf7992f8f35c14553b
|
|
|
1142
1403
|
{projectUserText(text, referenceLabels)}
|
|
1143
1404
|
{rest.map((block, i) => <JsonBlock key={i} label={t('message.extraBlock')} payload={block} truncatedLabel={truncated} />)}
|
|
1144
1405
|
diff --git a/packages/client/ui-chat/src/client/contract/slots.ts b/packages/client/ui-chat/src/client/contract/slots.ts
|
|
1145
|
-
index
|
|
1406
|
+
index 307d8f703b..7679be0fba 100644
|
|
1146
1407
|
--- a/packages/client/ui-chat/src/client/contract/slots.ts
|
|
1147
1408
|
+++ b/packages/client/ui-chat/src/client/contract/slots.ts
|
|
1148
1409
|
@@ -1,7 +1,7 @@
|
|
@@ -1165,19 +1426,33 @@ index 307d8f703b013962d19212618c522ef35feec0d2..0183aede0996a91d4de2f17bdf54e0b3
|
|
|
1165
1426
|
|
|
1166
1427
|
/** Details-panel callbacks. */
|
|
1167
1428
|
export interface DetailsInjected {
|
|
1168
|
-
@@ -182,6 +184,
|
|
1429
|
+
@@ -182,6 +184,13 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
1169
1430
|
* registration replaces the shipped gallery; without one, images are omitted.
|
|
1170
1431
|
*/
|
|
1171
1432
|
'conversation.message.images': { kind: 'single'; scope: 'session'; owner: MessageImagesOwnerProps }
|
|
1433
|
+
+ /**
|
|
1434
|
+
+ * Render durable Document cards inside one Chat message's attachment rail.
|
|
1435
|
+
+ * A contribution receives the message's projected Document metadata and
|
|
1436
|
+
+ * replaces the prior single card renderer. Without one, Chat keeps image
|
|
1437
|
+
+ * presentation but omits Document cards.
|
|
1438
|
+
+ */
|
|
1172
1439
|
+ 'conversation.message.images.documents': { kind: 'single'; scope: 'session'; owner: { documents: readonly MessageDocumentSource[] } }
|
|
1173
1440
|
/**
|
|
1174
1441
|
* Command row keyed by the command name. The component receives the folded
|
|
1175
1442
|
* command lifecycle and linked compaction when present. Reusing a key
|
|
1176
1443
|
diff --git a/packages/client/ui-conversation/src/client/apply.ts b/packages/client/ui-conversation/src/client/apply.ts
|
|
1177
|
-
index
|
|
1444
|
+
index b154e73f4a..480e242a30 100644
|
|
1178
1445
|
--- a/packages/client/ui-conversation/src/client/apply.ts
|
|
1179
1446
|
+++ b/packages/client/ui-conversation/src/client/apply.ts
|
|
1180
|
-
@@ -
|
|
1447
|
+
@@ -244,6 +244,7 @@ export function apply(ctx: Context): void {
|
|
1448
|
+
locale: NS,
|
|
1449
|
+
children: {
|
|
1450
|
+
'conversation.input.attachments': { kind: 'single', scope: 'session-maybe' },
|
|
1451
|
+
+ 'conversation.input.attachment-picker': { kind: 'single', scope: 'session-maybe' },
|
|
1452
|
+
'conversation.input.plan': { kind: 'single', scope: 'session' },
|
|
1453
|
+
'conversation.input.model': { kind: 'single', scope: 'session' },
|
|
1454
|
+
},
|
|
1455
|
+
@@ -254,6 +255,10 @@ export function apply(ctx: Context): void {
|
|
1181
1456
|
addImages: undefined,
|
|
1182
1457
|
removeImage: undefined,
|
|
1183
1458
|
draftImages: undefined,
|
|
@@ -1188,7 +1463,7 @@ index b154e73f4abc7034d93cac9b4634c65483731a08..8c9c1a2c7a2a7d2a3ea02e7931959065
|
|
|
1188
1463
|
resolveSubmitMode: (running, gesture, steeringAvailable) =>
|
|
1189
1464
|
submissionPolicy.resolve(running, gesture, steeringAvailable),
|
|
1190
1465
|
toggleCommandMenu: undefined,
|
|
1191
|
-
@@ -269,25 +
|
|
1466
|
+
@@ -269,25 +274,51 @@ export function apply(ctx: Context): void {
|
|
1192
1467
|
const conversation = concreteConversation(ctx)
|
|
1193
1468
|
const shell = inputHub.shell(sessionId)
|
|
1194
1469
|
const inputTriggers = inputHub.inputTriggers(sessionId)
|
|
@@ -1253,10 +1528,18 @@ index b154e73f4abc7034d93cac9b4634c65483731a08..8c9c1a2c7a2a7d2a3ea02e7931959065
|
|
|
1253
1528
|
submissionPolicy.resolve(running, gesture, steeringAvailable),
|
|
1254
1529
|
toggleCommandMenu: inputTriggers === undefined
|
|
1255
1530
|
diff --git a/packages/client/ui-conversation/src/client/contract/slots.ts b/packages/client/ui-conversation/src/client/contract/slots.ts
|
|
1256
|
-
index
|
|
1531
|
+
index 377a75ee00..08f4507c92 100644
|
|
1257
1532
|
--- a/packages/client/ui-conversation/src/client/contract/slots.ts
|
|
1258
1533
|
+++ b/packages/client/ui-conversation/src/client/contract/slots.ts
|
|
1259
|
-
@@ -
|
|
1534
|
+
@@ -1,6 +1,6 @@
|
|
1535
|
+
/** Target-neutral Conversation slot declarations and composed component props. */
|
|
1536
|
+
import type { ReactNode, RefObject } from 'react'
|
|
1537
|
+
-import type { ImageAttachmentRef } from '@deepseek-ai/dsh-attachment'
|
|
1538
|
+
+import type { AttachmentIdType, ImageAttachmentRef } from '@deepseek-ai/dsh-attachment'
|
|
1539
|
+
import type { SessionSnapshot } from '@deepseek-ai/dsh-api-session-controller/client'
|
|
1540
|
+
import type { WorkspaceSnapshot } from '@deepseek-ai/dsh-api-workspace-controller/client'
|
|
1541
|
+
import type {
|
|
1542
|
+
@@ -34,17 +34,53 @@ export interface ComposerAttachment {
|
|
1260
1543
|
height?: number
|
|
1261
1544
|
}
|
|
1262
1545
|
|
|
@@ -1280,6 +1563,14 @@ index 377a75ee0019ac0b791bf659ac390df1bd46d81a..9b8cc42deb4c58fc8e00fc54aa244b82
|
|
|
1280
1563
|
+ documents: readonly ComposerDocumentAttachment[]
|
|
1281
1564
|
+ onRemoveDocument: (id: DraftAttachmentId) => void
|
|
1282
1565
|
+}
|
|
1566
|
+
+
|
|
1567
|
+
+/** Mixed file chooser state handed to the optional attachment UI plugin. */
|
|
1568
|
+
+export interface ComposerAttachmentPickerOwnerProps {
|
|
1569
|
+
+ /** Whether this composer can accept another browser file batch. */
|
|
1570
|
+
+ canAcceptFiles: boolean
|
|
1571
|
+
+ /** Add one selected file batch through the composer's validation path. */
|
|
1572
|
+
+ onAddFiles: (files: readonly File[]) => void
|
|
1573
|
+
+}
|
|
1283
1574
|
+
|
|
1284
1575
|
export interface ComposerAttachmentsOwnerProps {
|
|
1285
1576
|
/** Browser-owned draft images in input order. */
|
|
@@ -1304,13 +1595,14 @@ index 377a75ee0019ac0b791bf659ac390df1bd46d81a..9b8cc42deb4c58fc8e00fc54aa244b82
|
|
|
1304
1595
|
dropLimits?: { readonly count: number; readonly size: string } | undefined
|
|
1305
1596
|
}
|
|
1306
1597
|
|
|
1307
|
-
@@ -71,10 +
|
|
1598
|
+
@@ -71,10 +107,20 @@ export type MessageImageLoader = ((attachment: ImageAttachmentRef) => Promise<st
|
|
1308
1599
|
peek?: (attachment: ImageAttachmentRef) => string | undefined
|
|
1309
1600
|
}
|
|
1310
1601
|
|
|
1311
1602
|
-/** Message image group handed to the optional attachment presentation plugin. */
|
|
1312
1603
|
+/** Display metadata for one durable Document history card. */
|
|
1313
1604
|
+export interface MessageDocumentSource {
|
|
1605
|
+
+ readonly previewAttachmentId: AttachmentIdType
|
|
1314
1606
|
+ readonly name: string
|
|
1315
1607
|
+ readonly mediaType: string
|
|
1316
1608
|
+ readonly bytes: number
|
|
@@ -1321,14 +1613,20 @@ index 377a75ee0019ac0b791bf659ac390df1bd46d81a..9b8cc42deb4c58fc8e00fc54aa244b82
|
|
|
1321
1613
|
/** Durable references or submission-echo previews in source order. */
|
|
1322
1614
|
images: readonly MessageImageSource[]
|
|
1323
1615
|
+ /** Durable Document cards associated with this user message. */
|
|
1324
|
-
+ documents
|
|
1616
|
+
+ documents: readonly MessageDocumentSource[]
|
|
1325
1617
|
/** Session-authorized image URL loader for the durable arm. */
|
|
1326
1618
|
loadImage: MessageImageLoader
|
|
1327
1619
|
/** Horizontal placement inside the owning record. */
|
|
1328
|
-
@@ -141,6 +
|
|
1620
|
+
@@ -141,6 +187,18 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
1329
1621
|
scope: 'session-maybe'
|
|
1330
1622
|
owner: ComposerAttachmentsOwnerProps
|
|
1331
1623
|
}
|
|
1624
|
+
+ /** Optional mixed file chooser placed immediately after the command button. */
|
|
1625
|
+
+ 'conversation.input.attachment-picker': {
|
|
1626
|
+
+ kind: 'single'
|
|
1627
|
+
+ scope: 'session-maybe'
|
|
1628
|
+
+ owner: ComposerAttachmentPickerOwnerProps
|
|
1629
|
+
+ }
|
|
1332
1630
|
+ /** Document cards rendered inside the shared attachment rail. */
|
|
1333
1631
|
+ 'conversation.input.attachments.documents': {
|
|
1334
1632
|
+ kind: 'single'
|
|
@@ -1338,7 +1636,7 @@ index 377a75ee0019ac0b791bf659ac390df1bd46d81a..9b8cc42deb4c58fc8e00fc54aa244b82
|
|
|
1338
1636
|
/** Plan control inside the composer tool row. */
|
|
1339
1637
|
'conversation.input.plan': { kind: 'single'; scope: 'session'; owner: InputControlOwnerProps }
|
|
1340
1638
|
/** Model selector inside the composer tool row. */
|
|
1341
|
-
@@ -267,6 +
|
|
1639
|
+
@@ -267,6 +325,14 @@ export interface ComposerBarInjected {
|
|
1342
1640
|
addImages: ((files: readonly File[]) => string | null) | undefined
|
|
1343
1641
|
removeImage: ((id: DraftAttachmentId) => void) | undefined
|
|
1344
1642
|
draftImages: ((ids: readonly DraftAttachmentId[]) => readonly ComposerAttachment[]) | undefined
|
|
@@ -1353,16 +1651,36 @@ index 377a75ee0019ac0b791bf659ac390df1bd46d81a..9b8cc42deb4c58fc8e00fc54aa244b82
|
|
|
1353
1651
|
resolveSubmitMode: (
|
|
1354
1652
|
running: boolean,
|
|
1355
1653
|
gesture: ComposerSubmitGesture,
|
|
1654
|
+
@@ -292,7 +358,8 @@ export interface InputControlOwnerProps {
|
|
1655
|
+
export type ComposerBarProps =
|
|
1656
|
+
PropsRuntime<'conversation.composer.bar'>
|
|
1657
|
+
& PropsRenderSlots<
|
|
1658
|
+
- 'conversation.input.attachments' | 'conversation.input.plan' | 'conversation.input.model'
|
|
1659
|
+
+ | 'conversation.input.attachments' | 'conversation.input.attachment-picker'
|
|
1660
|
+
+ | 'conversation.input.plan' | 'conversation.input.model'
|
|
1661
|
+
>
|
|
1662
|
+
& InjectFace<ComposerBarInjected>
|
|
1663
|
+
& PropsLocale<'conversation'>
|
|
1664
|
+
@@ -356,6 +423,8 @@ export type ConversationSessionHeaderSlotProps =
|
|
1665
|
+
/** Full props of the draft-image attachment renderer. */
|
|
1666
|
+
export type ComposerAttachmentsProps =
|
|
1667
|
+
PropsRuntime<'conversation.input.attachments'> & PropsLocale<'conversation'>
|
|
1668
|
+
+export type ComposerAttachmentPickerProps =
|
|
1669
|
+
+ PropsRuntime<'conversation.input.attachment-picker'> & PropsLocale<'conversation'>
|
|
1670
|
+
|
|
1671
|
+
/** Owner share common to blank-session Workspace pickers. */
|
|
1672
|
+
export interface EmptyWorkspaceOwnerProps {
|
|
1356
1673
|
diff --git a/packages/client/ui-conversation/src/client/index.ts b/packages/client/ui-conversation/src/client/index.ts
|
|
1357
|
-
index
|
|
1674
|
+
index 102b385e8c..a278db2780 100644
|
|
1358
1675
|
--- a/packages/client/ui-conversation/src/client/index.ts
|
|
1359
1676
|
+++ b/packages/client/ui-conversation/src/client/index.ts
|
|
1360
|
-
@@ -46,14 +46,
|
|
1677
|
+
@@ -46,14 +46,15 @@ export { ConversationViewRegistry } from './conversation/view-registry.ts'
|
|
1361
1678
|
|
|
1362
1679
|
export type { ConversationKey } from './locales.ts'
|
|
1363
1680
|
export type {
|
|
1364
1681
|
- ComposerAttachment, ComposerAttachmentsOwnerProps, ComposerAttachmentsProps,
|
|
1365
|
-
+ ComposerAttachment,
|
|
1682
|
+
+ ComposerAttachment, ComposerAttachmentPickerOwnerProps, ComposerAttachmentPickerProps,
|
|
1683
|
+
+ ComposerDocumentAttachment, ComposerDocumentLimits, ComposerAttachmentsOwnerProps, ComposerAttachmentsProps,
|
|
1366
1684
|
ComposerBarInjected, ComposerBarOwnerProps, ComposerBarProps, ComposerChainProps,
|
|
1367
1685
|
ConversationHeaderActionOwnerProps, ConversationHeaderLineageOwnerProps,
|
|
1368
1686
|
ConversationInjected, ConversationSessionHeaderInjected, ConversationSessionHeaderSlotProps,
|
|
@@ -1375,7 +1693,7 @@ index 102b385e8cfedd49b769c45446b31e0b96dd1782..1b08839b96a6a937b3216da2092ca6f2
|
|
|
1375
1693
|
} from './contract/slots.ts'
|
|
1376
1694
|
export type {
|
|
1377
1695
|
diff --git a/packages/client/ui-conversation/src/client/locales.ts b/packages/client/ui-conversation/src/client/locales.ts
|
|
1378
|
-
index
|
|
1696
|
+
index 3b1f9c1fc8..ab8e44d430 100644
|
|
1379
1697
|
--- a/packages/client/ui-conversation/src/client/locales.ts
|
|
1380
1698
|
+++ b/packages/client/ui-conversation/src/client/locales.ts
|
|
1381
1699
|
@@ -27,6 +27,7 @@ export const zh = {
|
|
@@ -1386,7 +1704,7 @@ index 9eaf2a82a02fad08032dfc50d2720b8c77d26c69..1cf4726092bea967269029a8bc9c2b33
|
|
|
1386
1704
|
'image.openOriginal': '查看原图',
|
|
1387
1705
|
'image.openOriginalLabel': '{label},点击查看原图',
|
|
1388
1706
|
'image.remove': '移除图片 {name}',
|
|
1389
|
-
@@ -
|
|
1707
|
+
@@ -177,6 +178,7 @@ export const en = {
|
|
1390
1708
|
'image.dropDesc': 'Up to {count} images, {size} each',
|
|
1391
1709
|
'image.dropBlocked': 'Images cannot be added right now',
|
|
1392
1710
|
'image.pending': 'Pending images',
|
|
@@ -1395,7 +1713,7 @@ index 9eaf2a82a02fad08032dfc50d2720b8c77d26c69..1cf4726092bea967269029a8bc9c2b33
|
|
|
1395
1713
|
'image.openOriginalLabel': '{label}, click to view original',
|
|
1396
1714
|
'image.remove': 'Remove image {name}',
|
|
1397
1715
|
diff --git a/packages/client/ui-conversation/src/client/service.ts b/packages/client/ui-conversation/src/client/service.ts
|
|
1398
|
-
index
|
|
1716
|
+
index fd59a492f0..7357b9cc72 100644
|
|
1399
1717
|
--- a/packages/client/ui-conversation/src/client/service.ts
|
|
1400
1718
|
+++ b/packages/client/ui-conversation/src/client/service.ts
|
|
1401
1719
|
@@ -18,7 +18,7 @@ import type {
|
|
@@ -1796,15 +2114,13 @@ index 2fb0c99b51092091815af04a172699de24cd867d..0321824f6ada49487995ec01f4487e2b
|
|
|
1796
2114
|
if (url.startsWith('blob:')) URL.revokeObjectURL(url)
|
|
1797
2115
|
}
|
|
1798
2116
|
diff --git a/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx b/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx
|
|
1799
|
-
index
|
|
2117
|
+
index d4c06d8bf3..d0bce93cd1 100644
|
|
1800
2118
|
--- a/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx
|
|
1801
2119
|
+++ b/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx
|
|
1802
|
-
@@ -
|
|
1803
|
-
export type InputBarProps = ComposerBarProps
|
|
2120
|
+
@@ -41,6 +41,7 @@ export type InputBarProps = ComposerBarProps
|
|
1804
2121
|
|
|
1805
2122
|
export function InputBar({
|
|
1806
|
-
|
|
1807
|
-
+ useSession, useInput, inputActions, keyboard, removeImage, draftImages,
|
|
2123
|
+
useSession, useInput, inputActions, keyboard, addImages, removeImage, draftImages,
|
|
1808
2124
|
+ addFiles, resolveDocumentDropLabels, removeDocument, draftDocuments,
|
|
1809
2125
|
resolveSubmitMode, toggleCommandMenu, stop, command, t,
|
|
1810
2126
|
renderSlot, useNotices, useLexicon, useMenuLauncher,
|
|
@@ -1830,7 +2146,7 @@ index d6126acdfd62b2b9be0af3fbb99c63b4435859b8..cf99cdac86bc2f308032de81baa89c04
|
|
|
1830
2146
|
// Prompt failures are ordinary failures (no create/attach transaction exists
|
|
1831
2147
|
// anymore): the toast announces promptError, the draft stays in the machine,
|
|
1832
2148
|
// and the user resubmits. A remount over a session whose machine still holds
|
|
1833
|
-
@@ -
|
|
2149
|
+
@@ -136,10 +142,13 @@ export function InputBar({
|
|
1834
2150
|
|
|
1835
2151
|
useEffect(() => {
|
|
1836
2152
|
if (input === undefined || inputActions === undefined) return
|
|
@@ -1847,7 +2163,7 @@ index d6126acdfd62b2b9be0af3fbb99c63b4435859b8..cf99cdac86bc2f308032de81baa89c04
|
|
|
1847
2163
|
|
|
1848
2164
|
// Scroll the draft scrollport the minimum that brings the selection focus
|
|
1849
2165
|
// into view — the browser's own behavior for typing, performed for the
|
|
1850
|
-
@@ -
|
|
2166
|
+
@@ -212,46 +221,67 @@ export function InputBar({
|
|
1851
2167
|
return () => { el.removeEventListener('wheel', onWheel) }
|
|
1852
2168
|
}, [])
|
|
1853
2169
|
|
|
@@ -1856,8 +2172,29 @@ index d6126acdfd62b2b9be0af3fbb99c63b4435859b8..cf99cdac86bc2f308032de81baa89c04
|
|
|
1856
2172
|
- // never enters the rail — no more submit-time failure rolling the rail
|
|
1857
2173
|
- // back. The host enforces the same limits at submit for callers that bypass
|
|
1858
2174
|
- // this composer.
|
|
1859
|
-
|
|
1860
|
-
|
|
2175
|
+
+ // Intake pre-check: an addition that would break a projected image limit is
|
|
2176
|
+
+ // refused before the single mixed draft transaction starts. The Host repeats
|
|
2177
|
+
+ // every admission check for callers that bypass this composer.
|
|
2178
|
+
+ const imageIntakeError = useCallback((files: readonly File[]): string | null => {
|
|
2179
|
+
+ if (files.length === 0 || imageLimits === undefined) return null
|
|
2180
|
+
+ if (files.some(file => !(imageLimits.mediaTypes as readonly string[]).includes(file.type))) {
|
|
2181
|
+
+ return t('image.unsupportedType')
|
|
2182
|
+
+ }
|
|
2183
|
+
+ if (attachments.length + files.length > imageLimits.maxImagesPerMessage) {
|
|
2184
|
+
+ return t('image.tooMany', { count: imageLimits.maxImagesPerMessage })
|
|
2185
|
+
+ }
|
|
2186
|
+
+ if (files.some(file => file.size > imageLimits.maxImageBytes)) {
|
|
2187
|
+
+ return t('image.fileTooLarge', { size: imageSizeText(imageLimits.maxImageBytes) })
|
|
2188
|
+
+ }
|
|
2189
|
+
+ const total = attachments.reduce((sum, attachment) => sum + attachment.file.size, 0)
|
|
2190
|
+
+ + files.reduce((sum, file) => sum + file.size, 0)
|
|
2191
|
+
+ return total > imageLimits.maxMessageImageBytes
|
|
2192
|
+
+ ? t('image.totalTooLarge', { size: imageSizeText(imageLimits.maxMessageImageBytes) })
|
|
2193
|
+
+ : null
|
|
2194
|
+
+ }, [attachments, imageLimits, t])
|
|
2195
|
+
+
|
|
2196
|
+
const intakeImages = useCallback((files: readonly File[]): void => {
|
|
2197
|
+
if (addImages === undefined || files.length === 0) return
|
|
1861
2198
|
- const rejected = ((): string | null => {
|
|
1862
2199
|
- if (imageLimits !== undefined) {
|
|
1863
2200
|
- // Format precedes limits: a batch with
|
|
@@ -1880,29 +2217,21 @@ index d6126acdfd62b2b9be0af3fbb99c63b4435859b8..cf99cdac86bc2f308032de81baa89c04
|
|
|
1880
2217
|
- }
|
|
1881
2218
|
- return addImages(files)
|
|
1882
2219
|
- })()
|
|
1883
|
-
+
|
|
1884
|
-
+
|
|
1885
|
-
+
|
|
1886
|
-
+
|
|
1887
|
-
+ if (files.length === 0 || imageLimits === undefined) return null
|
|
1888
|
-
+ if (files.some(file => !(imageLimits.mediaTypes as readonly string[]).includes(file.type))) {
|
|
1889
|
-
+ return t('image.unsupportedType')
|
|
1890
|
-
+ }
|
|
1891
|
-
+ if (attachments.length + files.length > imageLimits.maxImagesPerMessage) {
|
|
1892
|
-
+ return t('image.tooMany', { count: imageLimits.maxImagesPerMessage })
|
|
1893
|
-
+ }
|
|
1894
|
-
+ if (files.some(file => file.size > imageLimits.maxImageBytes)) {
|
|
1895
|
-
+ return t('image.fileTooLarge', { size: imageSizeText(imageLimits.maxImageBytes) })
|
|
2220
|
+
+ const imageRejected = imageIntakeError(files)
|
|
2221
|
+
+ if (imageRejected !== null) {
|
|
2222
|
+
+ showToast(imageRejected)
|
|
2223
|
+
+ return
|
|
1896
2224
|
+ }
|
|
1897
|
-
+ const
|
|
1898
|
-
+
|
|
1899
|
-
+
|
|
1900
|
-
+ ? t('image.totalTooLarge', { size: imageSizeText(imageLimits.maxMessageImageBytes) })
|
|
1901
|
-
+ : null
|
|
1902
|
-
+ }, [attachments, imageLimits, t])
|
|
2225
|
+
+ const rejected = addImages(files)
|
|
2226
|
+
+ if (rejected !== null) showToast(rejected)
|
|
2227
|
+
+ }, [addImages, imageIntakeError, showToast])
|
|
1903
2228
|
+
|
|
1904
2229
|
+ const intakeFiles = useCallback((files: readonly File[]): void => {
|
|
1905
|
-
+ if (
|
|
2230
|
+
+ if (files.length === 0) return
|
|
2231
|
+
+ if (addFiles === undefined) {
|
|
2232
|
+
+ intakeImages(files)
|
|
2233
|
+
+ return
|
|
2234
|
+
+ }
|
|
1906
2235
|
+ const documentTypes = draftDocuments === undefined || documentLimits === undefined
|
|
1907
2236
|
+ ? []
|
|
1908
2237
|
+ : documentLimits.mediaTypes as readonly string[]
|
|
@@ -1916,14 +2245,11 @@ index d6126acdfd62b2b9be0af3fbb99c63b4435859b8..cf99cdac86bc2f308032de81baa89c04
|
|
|
1916
2245
|
+ const rejected = addFiles(imageFiles, documentFiles, documentLimits)
|
|
1917
2246
|
if (rejected !== null) showToast(rejected)
|
|
1918
2247
|
- }, [addImages, attachments, imageLimits, showToast, t])
|
|
1919
|
-
+ }, [addFiles, documentLimits, draftDocuments, imageIntakeError, showToast])
|
|
1920
|
-
+
|
|
1921
|
-
+ const intakeImages = useCallback((files: readonly File[]): void => {
|
|
1922
|
-
+ intakeFiles(files)
|
|
1923
|
-
+ }, [intakeFiles])
|
|
2248
|
+
+ }, [addFiles, documentLimits, draftDocuments, imageIntakeError, intakeImages, showToast])
|
|
1924
2249
|
|
|
1925
2250
|
- const canAcceptDrop = !locked && !machineBusy && addImages !== undefined
|
|
1926
|
-
+ const canAcceptDrop = !locked && !machineBusy && addFiles !== undefined
|
|
2251
|
+
+ const canAcceptDrop = !locked && !machineBusy && (addFiles !== undefined || addImages !== undefined)
|
|
2252
|
+
+ const canAcceptFiles = !locked && !machineBusy && addFiles !== undefined
|
|
1927
2253
|
|
|
1928
2254
|
// The keymap handlers read live bar state through this ref so the editor
|
|
1929
2255
|
// registration survives re-renders without re-arming per keystroke.
|
|
@@ -1936,7 +2262,7 @@ index d6126acdfd62b2b9be0af3fbb99c63b4435859b8..cf99cdac86bc2f308032de81baa89c04
|
|
|
1936
2262
|
|
|
1937
2263
|
useEffect(() => {
|
|
1938
2264
|
if (editor === null || keyboard === undefined) return
|
|
1939
|
-
@@ -
|
|
2265
|
+
@@ -278,7 +308,7 @@ export function InputBar({
|
|
1940
2266
|
g.subagent === null,
|
|
1941
2267
|
))
|
|
1942
2268
|
},
|
|
@@ -1945,7 +2271,7 @@ index d6126acdfd62b2b9be0af3fbb99c63b4435859b8..cf99cdac86bc2f308032de81baa89c04
|
|
|
1945
2271
|
pasteText: (text) => {
|
|
1946
2272
|
if (gate.current.machineBusy || gate.current.locked) return
|
|
1947
2273
|
keyboard.paste(text)
|
|
1948
|
-
@@ -
|
|
2274
|
+
@@ -395,9 +425,13 @@ export function InputBar({
|
|
1949
2275
|
{accessory !== undefined && <div className={css.accessory}>{accessory}</div>}
|
|
1950
2276
|
{renderSlot('conversation.input.attachments', {
|
|
1951
2277
|
attachments,
|
|
@@ -1959,11 +2285,22 @@ index d6126acdfd62b2b9be0af3fbb99c63b4435859b8..cf99cdac86bc2f308032de81baa89c04
|
|
|
1959
2285
|
dropLimits: imageLimits === undefined ? undefined : {
|
|
1960
2286
|
count: imageLimits.maxImagesPerMessage,
|
|
1961
2287
|
size: imageSizeText(imageLimits.maxImageBytes),
|
|
2288
|
+
@@ -450,6 +484,10 @@ export function InputBar({
|
|
2289
|
+
<IconPlusOutline16 size={14} />
|
|
2290
|
+
</button>
|
|
2291
|
+
</Tooltip>
|
|
2292
|
+
+ {renderSlot('conversation.input.attachment-picker', {
|
|
2293
|
+
+ canAcceptFiles,
|
|
2294
|
+
+ onAddFiles: intakeFiles,
|
|
2295
|
+
+ })}
|
|
2296
|
+
<div className={css.modes}>
|
|
2297
|
+
{accessSelect}
|
|
2298
|
+
{sessionId === undefined ? null : renderSlot('conversation.input.plan', { locked })}
|
|
1962
2299
|
diff --git a/packages/client/ui-conversation/tests/input-bar.client.spec.tsx b/packages/client/ui-conversation/tests/input-bar.client.spec.tsx
|
|
1963
|
-
index
|
|
2300
|
+
index fafa85283b..9a8ee83691 100644
|
|
1964
2301
|
--- a/packages/client/ui-conversation/tests/input-bar.client.spec.tsx
|
|
1965
2302
|
+++ b/packages/client/ui-conversation/tests/input-bar.client.spec.tsx
|
|
1966
|
-
@@ -
|
|
2303
|
+
@@ -175,6 +175,10 @@ function bench(over?: BenchOptions) {
|
|
1967
2304
|
const attachment = over?.attachments?.find(candidate => candidate.id === id)
|
|
1968
2305
|
return attachment === undefined ? [] : [attachment]
|
|
1969
2306
|
}),
|
|
@@ -1974,8 +2311,46 @@ index 9becd7f1095f135ecd3d24b6a06f52b956fe4834..b00b618d0bd1288a552cc4dcb46760c9
|
|
|
1974
2311
|
resolveSubmitMode: (running, gesture, steeringAvailable) => {
|
|
1975
2312
|
if (!running || !steeringAvailable) return 'queue'
|
|
1976
2313
|
const preferred = over?.busyEnter ?? 'queue'
|
|
2314
|
+
@@ -320,7 +324,7 @@ describe('image draft rail', () => {
|
|
2315
|
+
new File([new ArrayBuffer(64)], 'b.pdf', { type: 'application/pdf' }),
|
|
2316
|
+
]
|
|
2317
|
+
act(() => { attachmentOwner(result.slotCalls).onAddImages(files) })
|
|
2318
|
+
- expect(addImages).toHaveBeenCalledWith(files)
|
|
2319
|
+
+ expect(addImages).not.toHaveBeenCalled()
|
|
2320
|
+
expect(result.view.getByRole('alert').textContent).toContain('仅支持 PNG、JPG、WebP、GIF 格式的图片')
|
|
2321
|
+
})
|
|
2322
|
+
|
|
2323
|
+
@@ -1300,7 +1304,8 @@ describe('command launcher chrome and control seats', () => {
|
|
2324
|
+
// Every seat dispatched, nothing rendered (render passes may repeat; the
|
|
2325
|
+
// seat set is the contract).
|
|
2326
|
+
expect([...new Set(slotCalls.map(c => c.key))]).toEqual([
|
|
2327
|
+
- 'conversation.input.attachments', 'conversation.input.plan', 'conversation.input.model',
|
|
2328
|
+
+ 'conversation.input.attachments', 'conversation.input.attachment-picker',
|
|
2329
|
+
+ 'conversation.input.plan', 'conversation.input.model',
|
|
2330
|
+
])
|
|
2331
|
+
expect(view.queryByLabelText('Plan mode')).toBeNull()
|
|
2332
|
+
expect(view.queryByLabelText('Model')).toBeNull()
|
|
2333
|
+
@@ -1466,12 +1471,16 @@ describe('command launcher chrome and control seats', () => {
|
|
2334
|
+
expect(view.getByTestId('plan-entry')).toBeTruthy()
|
|
2335
|
+
expect(view.getByTestId('model-entry')).toBeTruthy()
|
|
2336
|
+
// The bar hands its chrome disable state to the filling entry.
|
|
2337
|
+
- const controls = slotCalls.filter(call => call.key !== 'conversation.input.attachments')
|
|
2338
|
+
+ const controls = slotCalls.filter(call =>
|
|
2339
|
+
+ call.key !== 'conversation.input.attachments'
|
|
2340
|
+
+ && call.key !== 'conversation.input.attachment-picker')
|
|
2341
|
+
expect(controls.every(c => (c.owner as { locked: boolean }).locked)).toBe(true)
|
|
2342
|
+
expect(attachmentOwner(slotCalls).canAcceptDrop).toBe(false)
|
|
2343
|
+
cleanup()
|
|
2344
|
+
const live = bench({ running: true })
|
|
2345
|
+
- const liveControls = live.slotCalls.filter(call => call.key !== 'conversation.input.attachments')
|
|
2346
|
+
+ const liveControls = live.slotCalls.filter(call =>
|
|
2347
|
+
+ call.key !== 'conversation.input.attachments'
|
|
2348
|
+
+ && call.key !== 'conversation.input.attachment-picker')
|
|
2349
|
+
expect(liveControls.every(c => !(c.owner as { locked: boolean }).locked)).toBe(true)
|
|
2350
|
+
expect(attachmentOwner(live.slotCalls).canAcceptDrop).toBe(true)
|
|
2351
|
+
})
|
|
1977
2352
|
diff --git a/packages/client/ui-conversation/tests/input-matrix.client.spec.tsx b/packages/client/ui-conversation/tests/input-matrix.client.spec.tsx
|
|
1978
|
-
index
|
|
2353
|
+
index bdb878672b..dc155229a1 100644
|
|
1979
2354
|
--- a/packages/client/ui-conversation/tests/input-matrix.client.spec.tsx
|
|
1980
2355
|
+++ b/packages/client/ui-conversation/tests/input-matrix.client.spec.tsx
|
|
1981
2356
|
@@ -71,6 +71,10 @@ function mountBar(shell: SessionInputShell, over?: { running?: boolean; disabled
|
|
@@ -1990,7 +2365,7 @@ index bdb878672b080e06ec84b065b5bfbfea04a951ee..dc155229a154a0e23384ad821135146b
|
|
|
1990
2365
|
toggleCommandMenu: vi.fn(),
|
|
1991
2366
|
useNotices: bindSnapshotSelector(shell.notices),
|
|
1992
2367
|
diff --git a/packages/client/ui-conversation/tests/input-scenarios.client.spec.tsx b/packages/client/ui-conversation/tests/input-scenarios.client.spec.tsx
|
|
1993
|
-
index
|
|
2368
|
+
index ec7f586683..266d2e15c7 100644
|
|
1994
2369
|
--- a/packages/client/ui-conversation/tests/input-scenarios.client.spec.tsx
|
|
1995
2370
|
+++ b/packages/client/ui-conversation/tests/input-scenarios.client.spec.tsx
|
|
1996
2371
|
@@ -160,6 +160,10 @@ async function scopedBench(register?: (inputTriggers: InputTriggerService) => vo
|
|
@@ -2005,7 +2380,7 @@ index ec7f586683f96d9374bc31cc0e6d8b39e4a4df43..266d2e15c742a9eb9dbbea670f98943d
|
|
|
2005
2380
|
toggleCommandMenu: (selection) => {
|
|
2006
2381
|
const snapshot = shell.snapshot
|
|
2007
2382
|
diff --git a/packages/client/ui-conversation/tests/skeleton.client.spec.tsx b/packages/client/ui-conversation/tests/skeleton.client.spec.tsx
|
|
2008
|
-
index
|
|
2383
|
+
index 1d3f4159f0..08a36c8912 100644
|
|
2009
2384
|
--- a/packages/client/ui-conversation/tests/skeleton.client.spec.tsx
|
|
2010
2385
|
+++ b/packages/client/ui-conversation/tests/skeleton.client.spec.tsx
|
|
2011
2386
|
@@ -250,6 +250,10 @@ function mount(
|
|
@@ -2020,14 +2395,16 @@ index 40f6dfda6d1b6d04003b7c210cd600f6cbaa1d83..24632b55e94b1053793b761e047e03db
|
|
|
2020
2395
|
toggleCommandMenu={vi.fn()}
|
|
2021
2396
|
useNotices={bindSnapshotSelector(wiring.notices)}
|
|
2022
2397
|
diff --git a/packages/client/ui-trajectory/src/client/TrajectoryTable.tsx b/packages/client/ui-trajectory/src/client/TrajectoryTable.tsx
|
|
2023
|
-
index
|
|
2398
|
+
index 81a428aa35..ad9d38ff64 100644
|
|
2024
2399
|
--- a/packages/client/ui-trajectory/src/client/TrajectoryTable.tsx
|
|
2025
2400
|
+++ b/packages/client/ui-trajectory/src/client/TrajectoryTable.tsx
|
|
2026
|
-
@@ -
|
|
2401
|
+
@@ -1163,19 +1163,27 @@ function SourceBlocks({
|
|
2402
|
+
{/* The Raw view keeps model block order and granularity: one
|
|
2027
2403
|
gallery per image block, unlike the aggregated record gallery. */}
|
|
2028
2404
|
{block.attachment !== undefined
|
|
2029
|
-
|
|
2405
|
+
- ? renderImages({ images: [{ attachment: block.attachment }], align: 'start' })
|
|
2030
2406
|
- : <pre className={css.sourceBlockContent}>{block.content}</pre>}
|
|
2407
|
+
+ ? renderImages({ images: [{ attachment: block.attachment }], documents: [], align: 'start' })
|
|
2031
2408
|
+ : block.document !== undefined
|
|
2032
2409
|
+ ? renderImages({ images: [], documents: [block.document], align: 'start' })
|
|
2033
2410
|
+ : <pre className={css.sourceBlockContent}>{block.content}</pre>}
|
|
@@ -2082,7 +2459,7 @@ index 81a428aa35fada3286b0f7b025347dc6a51a3766..8f40cb75c119394dc32963ce6733775a
|
|
|
2082
2459
|
- {renderImages({ images: [{ attachment: block.attachment }], align: 'start' })}
|
|
2083
2460
|
+ {renderImages({
|
|
2084
2461
|
+ images: block.attachment === undefined ? [] : [{ attachment: block.attachment }],
|
|
2085
|
-
+
|
|
2462
|
+
+ documents: block.document === undefined ? [] : [block.document],
|
|
2086
2463
|
+ align: 'start',
|
|
2087
2464
|
+ })}
|
|
2088
2465
|
</div>
|
|
@@ -2111,20 +2488,47 @@ index 81a428aa35fada3286b0f7b025347dc6a51a3766..8f40cb75c119394dc32963ce6733775a
|
|
|
2111
2488
|
}
|
|
2112
2489
|
return (
|
|
2113
2490
|
diff --git a/packages/client/ui-trajectory/src/client/layout.ts b/packages/client/ui-trajectory/src/client/layout.ts
|
|
2114
|
-
index
|
|
2491
|
+
index a811e915f2..0353bce987 100644
|
|
2115
2492
|
--- a/packages/client/ui-trajectory/src/client/layout.ts
|
|
2116
2493
|
+++ b/packages/client/ui-trajectory/src/client/layout.ts
|
|
2117
|
-
@@ -
|
|
2494
|
+
@@ -6,6 +6,7 @@ import type {
|
|
2495
|
+
AssistantBlock,
|
|
2496
|
+
AssistantMessageNode,
|
|
2497
|
+
ConversationLocation,
|
|
2498
|
+
+ MessageDocumentSource,
|
|
2499
|
+
RequestInspectionSnapshot,
|
|
2500
|
+
RequestPromptChange,
|
|
2501
|
+
RequestView,
|
|
2502
|
+
@@ -841,6 +842,35 @@ function sourceBlock(value: unknown): TrajectorySourceBlock {
|
|
2118
2503
|
// wire-shaped 'other' blocks with an unrelated `attachment` member out.
|
|
2119
2504
|
return { type, content: '', attachment: block.attachment as ImageAttachmentRef }
|
|
2120
2505
|
}
|
|
2121
|
-
+ if (
|
|
2506
|
+
+ if (
|
|
2507
|
+
+ type === 'document'
|
|
2508
|
+
+ && typeof block.attachment === 'object' && block.attachment !== null
|
|
2509
|
+
+ && typeof block.parsed === 'object' && block.parsed !== null
|
|
2510
|
+
+ ) {
|
|
2122
2511
|
+ const attachment = block.attachment as Record<string, unknown>
|
|
2123
|
-
+
|
|
2512
|
+
+ const parsed = block.parsed as Record<string, unknown>
|
|
2513
|
+
+ const markdown = typeof parsed.markdown === 'object' && parsed.markdown !== null
|
|
2514
|
+
+ ? parsed.markdown as Record<string, unknown>
|
|
2515
|
+
+ : null
|
|
2516
|
+
+ if (
|
|
2517
|
+
+ typeof attachment.attachmentId === 'string'
|
|
2518
|
+
+ && typeof markdown?.attachmentId === 'string'
|
|
2519
|
+
+ && typeof attachment.name === 'string'
|
|
2520
|
+
+ && typeof attachment.mediaType === 'string'
|
|
2521
|
+
+ && typeof attachment.bytes === 'number'
|
|
2522
|
+
+ ) {
|
|
2124
2523
|
+ return {
|
|
2125
2524
|
+ type,
|
|
2126
2525
|
+ content: '',
|
|
2127
|
-
+ document: {
|
|
2526
|
+
+ document: {
|
|
2527
|
+
+ previewAttachmentId: markdown.attachmentId as MessageDocumentSource['previewAttachmentId'],
|
|
2528
|
+
+ name: attachment.name,
|
|
2529
|
+
+ mediaType: attachment.mediaType,
|
|
2530
|
+
+ bytes: attachment.bytes,
|
|
2531
|
+
+ },
|
|
2128
2532
|
+ }
|
|
2129
2533
|
+ }
|
|
2130
2534
|
+ }
|
|
@@ -2132,7 +2536,7 @@ index a811e915f20d7a2247726dc6a750bb67e0697304..8676ea46dcf367243f96cf53ffa29b13
|
|
|
2132
2536
|
}
|
|
2133
2537
|
|
|
2134
2538
|
diff --git a/packages/client/ui-trajectory/src/client/trajectory-contract.ts b/packages/client/ui-trajectory/src/client/trajectory-contract.ts
|
|
2135
|
-
index
|
|
2539
|
+
index 37571f2b83..6a327454c4 100644
|
|
2136
2540
|
--- a/packages/client/ui-trajectory/src/client/trajectory-contract.ts
|
|
2137
2541
|
+++ b/packages/client/ui-trajectory/src/client/trajectory-contract.ts
|
|
2138
2542
|
@@ -1,6 +1,6 @@
|
|
@@ -2143,15 +2547,21 @@ index 37571f2b83403944c2cb0e1e8a71b17972461994..1c326ebfca6e1b8636457d04c4517f39
|
|
|
2143
2547
|
RequestView, RunningToolCall, ToolCallBlock,
|
|
2144
2548
|
} from '@deepseek-ai/dsh-client-ui-conversation/client'
|
|
2145
2549
|
import type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots'
|
|
2146
|
-
@@ -93,5 +93,
|
|
2550
|
+
@@ -93,5 +93,12 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
2147
2551
|
* images are omitted.
|
|
2148
2552
|
*/
|
|
2149
2553
|
'conversation.trajectory.images': { kind: 'single'; scope: 'session'; owner: MessageImagesOwnerProps }
|
|
2554
|
+
+ /**
|
|
2555
|
+
+ * Render durable Document cards inside one Trajectory attachment rail.
|
|
2556
|
+
+ * A contribution receives the record's projected Document metadata and
|
|
2557
|
+
+ * replaces the prior single card renderer. Without one, Trajectory keeps
|
|
2558
|
+
+ * image presentation but omits Document cards.
|
|
2559
|
+
+ */
|
|
2150
2560
|
+ 'conversation.trajectory.images.documents': { kind: 'single'; scope: 'session'; owner: { documents: readonly MessageDocumentSource[] } }
|
|
2151
2561
|
}
|
|
2152
2562
|
}
|
|
2153
2563
|
diff --git a/packages/client/ui-trajectory/src/client/trajectory-record.ts b/packages/client/ui-trajectory/src/client/trajectory-record.ts
|
|
2154
|
-
index
|
|
2564
|
+
index da330f2ae4..0bb73888c9 100644
|
|
2155
2565
|
--- a/packages/client/ui-trajectory/src/client/trajectory-record.ts
|
|
2156
2566
|
+++ b/packages/client/ui-trajectory/src/client/trajectory-record.ts
|
|
2157
2567
|
@@ -2,7 +2,7 @@
|
|
@@ -2171,8 +2581,21 @@ index da330f2ae43a55ca813bfffe867dbfc33a2c15a9..0bb73888c955883403d785e7bcc737c8
|
|
|
2171
2581
|
callId?: string
|
|
2172
2582
|
toolName?: string
|
|
2173
2583
|
}
|
|
2584
|
+
diff --git a/packages/extensions/cordis-client-runner/src/client/api-catalog.ts b/packages/extensions/cordis-client-runner/src/client/api-catalog.ts
|
|
2585
|
+
index bef6356509..9a79565c0c 100644
|
|
2586
|
+
--- a/packages/extensions/cordis-client-runner/src/client/api-catalog.ts
|
|
2587
|
+
+++ b/packages/extensions/cordis-client-runner/src/client/api-catalog.ts
|
|
2588
|
+
@@ -559,7 +559,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
|
2589
|
+
},
|
|
2590
|
+
{
|
|
2591
|
+
name: 'ISession',
|
|
2592
|
+
- declaration: 'export interface ISession {\n readonly sessionId: SessionId;\n readonly projections: ProjectionsFace;\n beginSubmission(input: BeginSubmissionInput): SubmissionHandle;\n prompt(content: PromptContentPart[], mode: \'queue\' | \'steer\', signal?: AbortSignal, requestId?: SessionRequestId): Promise<RemoteResult<{\n accepted: true;\n }>>;\n readAttachment(attachmentId: AttachmentIdType): Promise<RemoteResult<{\n attachment: ImageAttachmentRef;\n data: Uint8Array;\n }>>;\n updateQueue(itemId: MessageId, action: QueueAction): Promise<RemoteResult<{\n accepted: true;\n }>>;\n cancel(): Promise<RemoteResult<{\n accepted: true;\n }>>;\n rename(title: string): Promise<RemoteResult<{\n title: string;\n seq: number;\n }>>;\n loadOlder(): Promise<void>;\n command(line: string): Promise<RemoteResult<{\n matched: boolean;\n }>>;\n}',
|
|
2593
|
+
+ declaration: 'export interface ISession {\n readonly sessionId: SessionId;\n readonly projections: ProjectionsFace;\n beginSubmission(input: BeginSubmissionInput): SubmissionHandle;\n prompt(content: PromptContentPart[], mode: \'queue\' | \'steer\', signal?: AbortSignal, requestId?: SessionRequestId): Promise<RemoteResult<{\n accepted: true;\n }>>;\n readAttachment(attachmentId: AttachmentIdType): Promise<RemoteResult<{\n attachment: FileAttachmentRef;\n data: Uint8Array;\n }>>;\n updateQueue(itemId: MessageId, action: QueueAction): Promise<RemoteResult<{\n accepted: true;\n }>>;\n cancel(): Promise<RemoteResult<{\n accepted: true;\n }>>;\n rename(title: string): Promise<RemoteResult<{\n title: string;\n seq: number;\n }>>;\n loadOlder(): Promise<void>;\n command(line: string): Promise<RemoteResult<{\n matched: boolean;\n }>>;\n}',
|
|
2594
|
+
},
|
|
2595
|
+
{
|
|
2596
|
+
name: 'KeyPropsOf',
|
|
2174
2597
|
diff --git a/packages/llm/llm-deepseek/src/adapter.ts b/packages/llm/llm-deepseek/src/adapter.ts
|
|
2175
|
-
index
|
|
2598
|
+
index e211655633..4e468b655b 100644
|
|
2176
2599
|
--- a/packages/llm/llm-deepseek/src/adapter.ts
|
|
2177
2600
|
+++ b/packages/llm/llm-deepseek/src/adapter.ts
|
|
2178
2601
|
@@ -8,7 +8,7 @@
|
|
@@ -2235,7 +2658,7 @@ index e2116556331159ee348aacfb194d46e2ae7b5920..4e468b655b20fd8a7664334be3365be1
|
|
|
2235
2658
|
)[Symbol.asyncIterator]()
|
|
2236
2659
|
let exhausted = false
|
|
2237
2660
|
diff --git a/packages/llm/llm-pi-ai/src/adapter.ts b/packages/llm/llm-pi-ai/src/adapter.ts
|
|
2238
|
-
index
|
|
2661
|
+
index e20b8e0072..058e8f2824 100644
|
|
2239
2662
|
--- a/packages/llm/llm-pi-ai/src/adapter.ts
|
|
2240
2663
|
+++ b/packages/llm/llm-pi-ai/src/adapter.ts
|
|
2241
2664
|
@@ -40,9 +40,11 @@ import type {
|
|
@@ -2250,7 +2673,40 @@ index e20b8e00720d52ef9cbb3712884661e8025f3dde..acd9dcda4fd57e0effe1f2bb6c3f4063
|
|
|
2250
2673
|
ReasoningEffortId,
|
|
2251
2674
|
} from '@deepseek-ai/dsh-llm'
|
|
2252
2675
|
import type {
|
|
2253
|
-
@@ -
|
|
2676
|
+
@@ -111,6 +113,24 @@ export interface PiAiAuthInjection {
|
|
2677
|
+
authContext: AuthContext
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
+/** Whether one Responses input item is replayed reasoning with response status metadata. */
|
|
2681
|
+
+function hasReasoningInputStatus(item: unknown): boolean {
|
|
2682
|
+
+ const inputItem = item as Record<string, unknown>
|
|
2683
|
+
+ return inputItem.type === 'reasoning' && Object.hasOwn(inputItem, 'status')
|
|
2684
|
+
+}
|
|
2685
|
+
+
|
|
2686
|
+
+/** Remove response-only status metadata from replayed reasoning input items. */
|
|
2687
|
+
+function omitReasoningInputStatus(payload: unknown): unknown {
|
|
2688
|
+
+ const request = payload as Record<string, unknown> & { input: unknown[] }
|
|
2689
|
+
+ if (!request.input.some(hasReasoningInputStatus)) return undefined
|
|
2690
|
+
+ const input = request.input.map((item) => {
|
|
2691
|
+
+ if (!hasReasoningInputStatus(item)) return item
|
|
2692
|
+
+ const { status: _status, ...withoutStatus } = item as Record<string, unknown>
|
|
2693
|
+
+ return withoutStatus
|
|
2694
|
+
+ })
|
|
2695
|
+
+ return { ...request, input }
|
|
2696
|
+
+}
|
|
2697
|
+
+
|
|
2698
|
+
/** Copy profile stream knobs into pi-ai's common option vocabulary. */
|
|
2699
|
+
function profileOptions(
|
|
2700
|
+
profile: ResolvedPiAiProviderProfile,
|
|
2701
|
+
@@ -126,6 +146,7 @@ function profileOptions(
|
|
2702
|
+
...profile.transport === undefined ? {} : { transport: profile.transport },
|
|
2703
|
+
...profile.timeoutMs === undefined ? {} : { timeoutMs: profile.timeoutMs },
|
|
2704
|
+
...profile.websocketConnectTimeoutMs === undefined ? {} : { websocketConnectTimeoutMs: profile.websocketConnectTimeoutMs },
|
|
2705
|
+
+ ...profile.responsesCompatibility?.omitReasoningInputStatus === true ? { onPayload: omitReasoningInputStatus } : {},
|
|
2706
|
+
// The agent recovery layer owns visible attempts; one adapter call is one SDK attempt.
|
|
2707
|
+
maxRetries: 0,
|
|
2708
|
+
}
|
|
2709
|
+
@@ -350,20 +371,24 @@ export class PiAiAdapter extends LlmAdapter {
|
|
2254
2710
|
using watchdog = idleWatchdog(upstream, streamIdleTimeoutMs, 'LLM_STREAM_IDLE_TIMEOUT')
|
|
2255
2711
|
|
|
2256
2712
|
try {
|
|
@@ -2281,7 +2737,7 @@ index e20b8e00720d52ef9cbb3712884661e8025f3dde..acd9dcda4fd57e0effe1f2bb6c3f4063
|
|
|
2281
2737
|
resolveImageAccess: ref => this.config.resolveImageAccess?.(attachments, ref),
|
|
2282
2738
|
maxRequestImageBytes: profile.maxRequestImageBytes,
|
|
2283
2739
|
diff --git a/packages/llm/llm/src/content.ts b/packages/llm/llm/src/content.ts
|
|
2284
|
-
index
|
|
2740
|
+
index 5880f82c98..3e46284c5c 100644
|
|
2285
2741
|
--- a/packages/llm/llm/src/content.ts
|
|
2286
2742
|
+++ b/packages/llm/llm/src/content.ts
|
|
2287
2743
|
@@ -1,10 +1,71 @@
|
|
@@ -2291,7 +2747,7 @@ index 43c392a64129981a1dd99f6f38fcae39df5e4228..96e9fece476649b8e8dfeb13979e40d4
|
|
|
2291
2747
|
+import type { ContentBlock, DocumentBlock } from './types.ts'
|
|
2292
2748
|
import type { Message } from './message.ts'
|
|
2293
2749
|
import type { AttachmentStore, ImageAttachmentRef, ImageMediaType, RequestImageAttachment } from '@deepseek-ai/dsh-attachment'
|
|
2294
|
-
import { assertNever } from '
|
|
2750
|
+
import { assertNever } from '@deepseek-ai/dsh-util-values'
|
|
2295
2751
|
|
|
2296
2752
|
+/** Return whether typed model content contains a durable parsed document. */
|
|
2297
2753
|
+export function contentHasDocument(content: readonly ContentBlock[]): boolean {
|
|
@@ -2308,7 +2764,7 @@ index 43c392a64129981a1dd99f6f38fcae39df5e4228..96e9fece476649b8e8dfeb13979e40d4
|
|
|
2308
2764
|
+ return new TextDecoder('utf-8', { fatal: true }).decode(stored.data)
|
|
2309
2765
|
+}
|
|
2310
2766
|
+
|
|
2311
|
-
+/**
|
|
2767
|
+
+/** Project request-only text without changing durable reference blocks in the Session. */
|
|
2312
2768
|
+async function projectDocumentsWithAttachments(
|
|
2313
2769
|
+ blocks: readonly ContentBlock[],
|
|
2314
2770
|
+ attachments: AttachmentStore,
|
|
@@ -2358,7 +2814,7 @@ index 43c392a64129981a1dd99f6f38fcae39df5e4228..96e9fece476649b8e8dfeb13979e40d4
|
|
|
2358
2814
|
export interface ImageAttachmentAccess {
|
|
2359
2815
|
/** Absolute path to immutable normalized bytes; callers must treat it as read-only. */
|
|
2360
2816
|
diff --git a/packages/llm/llm/src/types.ts b/packages/llm/llm/src/types.ts
|
|
2361
|
-
index
|
|
2817
|
+
index 4303538892..7c30dac96f 100644
|
|
2362
2818
|
--- a/packages/llm/llm/src/types.ts
|
|
2363
2819
|
+++ b/packages/llm/llm/src/types.ts
|
|
2364
2820
|
@@ -5,7 +5,7 @@
|