@deepseek-ai/dsh-attachment 0.1.2-alpha.5 → 0.1.3-alpha.2
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 +17 -12
- package/README.zh.md +18 -13
- package/lib/index.js +151 -44
- package/lib/types/admission.d.ts +10 -8
- package/lib/types/admission.js +19 -19
- package/lib/types/error.d.ts +8 -1
- package/lib/types/error.js +23 -0
- package/lib/types/index.d.ts +59 -4
- package/lib/types/index.js +90 -3
- package/lib/types/types.d.ts +45 -2
- package/package.json +4 -4
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/attachment/attachment/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: a812182aeff6d09506a1ea2d4fa8d9a44a175936
|
|
6
|
+
README.zh.md: c487f8204c86d8f0bbdfd85280e8fbab6ea14dec
|
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "Durable image attachments for users and maintainers attaching, reusing, or debugging
|
|
2
|
+
description: "Durable image and file attachments for users and maintainers attaching, reusing, or debugging uploads in prompts and commands."
|
|
3
3
|
kind: "package-reference"
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
|
|
|
9
9
|
|
|
10
10
|
## Summary
|
|
11
11
|
|
|
12
|
-
You can attach images to prompts
|
|
12
|
+
You can attach images and generic files to prompts, and the harness keeps them durably: each source image is admitted and normalized before your message is processed, while any other file is stored byte-for-byte with no format or size limits, and both reappear in conversation history across restarts of the same session. The shipped `dsh` composition enables this with no setup. Browser paths, provider URLs, local storage paths, and base64 never enter durable session events. Images accept raster formats (PNG, JPEG, WebP, GIF) under deployment limits; files accept anything, and the model reads a stored file on demand from its saved read-only path instead of receiving its bytes. Stored objects are never deleted automatically, and audio and video have no dedicated handling yet.
|
|
13
13
|
|
|
14
14
|
## Table of Contents
|
|
15
15
|
|
|
@@ -35,9 +35,13 @@ Attach one or more images to a user prompt in the client UI. Each source is chec
|
|
|
35
35
|
- name: '@deepseek-ai/dsh-attachment-local'
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
###
|
|
38
|
+
### Attach any other file to a prompt
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
Any non-image file attaches to a prompt as a generic file: the exact bytes are saved read-only under the harness home, the message records the file name, byte size, and content digest, and the model receives one line naming the saved path so it can read the content with its file tools only when needed. There is no file-type whitelist and no size limit; what you attach is stored verbatim.
|
|
41
|
+
|
|
42
|
+
### Pass attachments to commands
|
|
43
|
+
|
|
44
|
+
Commands declaring attachment input receive images and generic files in selection order. Commands that do not accept attachments return an error and retain the composer's draft and cards.
|
|
41
45
|
|
|
42
46
|
### Reuse images across the session
|
|
43
47
|
|
|
@@ -63,11 +67,12 @@ This section explains the design decisions behind the seam and the service opera
|
|
|
63
67
|
- **Immutable and retention-neutral.** Objects are immutable once published; resumed and forked sessions may share them, so reference-aware garbage collection is deferred rather than tied to any one session's deletion.
|
|
64
68
|
- **Verify on read.** Reads check bytes and metadata against the logged reference before returning them, and request projections fully decode cached bytes, so a missing, corrupted, or swapped object fails closed.
|
|
65
69
|
- **Role-neutral image blocks.** The `ImageBlock` content block in `dsh-llm` carries an `ImageAttachmentRef`; provider adapters resolve it into deterministic request versions with explicit pixel and byte budgets, while execution filesystems may map the immutable host object to a model-readable process path.
|
|
66
|
-
- **Error routing by code.** `AttachmentError` re-implements the `HarnessError` shape instead of extending it because the base lives in `dsh-llm`, which depends on this package; consumers route on `code`, never on the prototype chain.
|
|
70
|
+
- **Error routing by code.** `AttachmentError` re-implements the `HarnessError` shape instead of extending it because the base lives in `dsh-llm`, which depends on this package; consumers use `isAttachmentError` and route on `code`, never on the prototype chain.
|
|
71
|
+
- **Files are verbatim, images are normalized.** `saveFile` commits an existing byte array, `saveFileStream` commits bounded chunks with backpressure and cancellation, `readFileStream` verifies and returns bounded chunks, and `fileHostPath` locates the stored object for read-on-demand projection; neither file write path applies admission limits. The image path keeps its separate normalization, limits, and request-version pipeline. The `FileBlock` content block in `dsh-llm` carries a `FileAttachmentRef`, and request assembly projects it to deterministic handle text for every route.
|
|
67
72
|
|
|
68
73
|
### Service operations
|
|
69
74
|
|
|
70
|
-
The service family runs one admission-and-storage flow: every entry point enforces source batch limits and canonical base64, prepares provider-independent normalized attachments before publishing any member, and commits them durably in input order without partial results. `readImageRequest` derives deterministic route-sized variants whose identity includes the attachment id, transform version, pixel and byte budgets, and encoder settings. The pure `requestImageDimensions` export computes each projection's aspect-preserving dimensions from a total-pixel budget, so providers and request pricing share one geometry. `imageHostPath` exposes an implementation-owned host location only to trusted same-process consumers that need execution-world mapping. Callers compose ordered batches while the implementation owns compression concurrency, caching, and singleflight. Reads and projections preserve caller cancellation. Failures carry stable machine-readable codes, and the caller-correctable admission subset is recognizable at runtime so each protocol adapter maps its own vocabulary; the exact per-operation contracts live in [`src/index.ts`](src/index.ts) and [`src/error.ts`](src/error.ts).
|
|
75
|
+
The service family runs one admission-and-storage flow: every entry point enforces source batch limits and canonical base64, prepares provider-independent normalized attachments before publishing any member, and commits them durably in input order without partial results. Host prompt consumers pass ordered text, encoded images, and already resolved file references to `ctx.attachments.admitPromptContent()`; the method persists images and passes file references unchanged. Encoded protocol adapters call `ctx.attachments.admitEncodedFile()`, which checks canonical base64 before delegating to `saveFile`; adapters recognize attachment failures through `ctx.attachments.isAttachmentError()`. Generic-file callers choose `saveFile` for existing bytes or `saveFileStream` for a bounded asynchronous byte source; both return the same durable reference, while `readFileStream` verifies its digest and length during a bounded read. `readImageRequest` derives deterministic route-sized variants whose identity includes the attachment id, transform version, pixel and byte budgets, and encoder settings. The pure `requestImageDimensions` export computes each projection's aspect-preserving dimensions from a total-pixel budget, so providers and request pricing share one geometry. `imageHostPath` exposes an implementation-owned host location only to trusted same-process consumers that need execution-world mapping. Callers compose ordered batches while the implementation owns compression concurrency, caching, and singleflight. Reads, streamed writes, and projections preserve caller cancellation. Failures carry stable machine-readable codes, and the caller-correctable admission subset is recognizable at runtime so each protocol adapter maps its own vocabulary; the exact per-operation contracts live in [`src/index.ts`](src/index.ts) and [`src/error.ts`](src/error.ts).
|
|
71
76
|
|
|
72
77
|
### Source map
|
|
73
78
|
|
|
@@ -75,7 +80,7 @@ The service family runs one admission-and-storage flow: every entry point enforc
|
|
|
75
80
|
|---|---|
|
|
76
81
|
| [`src/index.ts`](src/index.ts) | Plugin entry: abstract `AttachmentStore` service and re-exports |
|
|
77
82
|
| [`src/types.ts`](src/types.ts) | Durable vocabulary: references, limits, upload and store payloads |
|
|
78
|
-
| [`src/admission.ts`](src/admission.ts) |
|
|
83
|
+
| [`src/admission.ts`](src/admission.ts) | Canonical-base64 enforcement and store delegation for encoded image and file uploads |
|
|
79
84
|
| [`src/error.ts`](src/error.ts) | `AttachmentError` class and the `isImageAdmissionError` runtime subset |
|
|
80
85
|
| [`src/brand.ts`](src/brand.ts) | `AttachmentId` branded opaque identifier |
|
|
81
86
|
| — | No runtime invariant companion is published; this stateless seam owns types while implementations enforce immutable-store checks. |
|
|
@@ -98,7 +103,7 @@ For the full service contract and payload types, read the subsystem reference; f
|
|
|
98
103
|
<a id="model-experience"></a>
|
|
99
104
|
## Model Experience
|
|
100
105
|
|
|
101
|
-
Indirectly, through the provider adapter, which resolves each durable reference into an exact request version and sends its stable attachment id and actual dimensions beside the image. When the execution filesystem maps the stored object, the descriptor also includes a read-only process path and a matching extension for a writable copy.
|
|
106
|
+
Indirectly, through the provider adapter, which resolves each durable image reference into an exact request version and sends its stable attachment id and actual dimensions beside the image. When the execution filesystem maps the stored object, the descriptor also includes a read-only process path and a matching extension for a writable copy. A generic file never reaches the provider as bytes: every route receives one deterministic handle line naming the file, its byte size, its digest prefix, and the saved read-only path to read with file tools.
|
|
102
107
|
|
|
103
108
|
#### KV Cache effect
|
|
104
109
|
|
|
@@ -111,8 +116,8 @@ Adding an image changes the provider request and therefore invalidates the affec
|
|
|
111
116
|
|
|
112
117
|
These limits describe what image attachments can and cannot do; they are current package constraints, not a task backlog.
|
|
113
118
|
|
|
114
|
-
- **Raster images only** — PNG, JPEG, WebP, and GIF are accepted;
|
|
115
|
-
- **
|
|
119
|
+
- **Raster image limits apply to images only** — PNG, JPEG, WebP, and GIF are accepted as images under deployment limits; every other file is stored verbatim with no type or size limit, and audio and video have no dedicated handling yet.
|
|
120
|
+
- **Attachments are never deleted** — stored images and files are retained indefinitely; nothing removes them automatically.
|
|
116
121
|
- **Unsent drafts are not saved** — a composer draft stays in the browser until you submit the message.
|
|
117
122
|
|
|
118
123
|
<a id="dev-note"></a>
|
|
@@ -127,8 +132,8 @@ This Dev Note is working context for maintainers: undecided directions and open
|
|
|
127
132
|
|
|
128
133
|
Resumed and forked sessions may share immutable objects, so any retention policy needs a reference model that accounts for session lineage before objects can be collected. No decision is recorded yet; the local backend currently retains everything.
|
|
129
134
|
|
|
130
|
-
#### Future:
|
|
135
|
+
#### Future: audio, video, and assistant-side output
|
|
131
136
|
|
|
132
|
-
|
|
137
|
+
Audio and video would need dedicated lifecycle and provider contracts beyond the verbatim file path, and the role-neutral `ImageBlock` leaves assistant-side image output as forward compatibility — current production adapters declare text-only output, so only user content carries images. Both directions are undecided.
|
|
133
138
|
|
|
134
139
|
</details>
|
package/README.zh.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "
|
|
2
|
+
description: "持久图片与文件附件,供用户与维护者在提示词与命令中附加、复用或排查上传内容。"
|
|
3
3
|
kind: "package-reference"
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -9,7 +9,7 @@ kind: "package-reference"
|
|
|
9
9
|
|
|
10
10
|
## 概述
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
你可以把图片和通用文件附加到提示词中,harness 会持久保存它们:每张源图都会在你的消息被处理前准入并规范化,而其他任何文件都按字节原样保存、不设格式与大小限制,两者都会在同一会话重启后重新出现在对话历史中。随附的 `dsh` 组合无需任何配置即可支持这一点。浏览器路径、提供方 URL、本地存储路径与 base64 绝不会进入持久会话事件。图片接受部署限额内的光栅格式(PNG、JPEG、WebP、GIF);文件接受任何内容,模型不接收文件字节,而是在需要时从保存的只读路径按需读取。已存储对象永远不会被自动删除,音频和视频暂无专门处理。
|
|
13
13
|
|
|
14
14
|
## 目录
|
|
15
15
|
|
|
@@ -35,9 +35,13 @@ kind: "package-reference"
|
|
|
35
35
|
- name: '@deepseek-ai/dsh-attachment-local'
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
###
|
|
38
|
+
### 在提示词中附加任意其他文件
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
任何非图片文件都以通用文件的形式附加到提示词:确切字节被只读保存在 harness 主目录下,消息记录文件名、字节数与内容摘要,模型收到一行指出保存路径的文本,只在需要时用文件工具读取内容。没有文件类型白名单,也没有大小上限;你附加什么就原样存什么。
|
|
41
|
+
|
|
42
|
+
### 把附件传给命令
|
|
43
|
+
|
|
44
|
+
声明接受附件的命令会按选择顺序接收图片与通用文件。不接受附件的命令会返回错误,并保留 composer 的草稿与附件卡。
|
|
41
45
|
|
|
42
46
|
### 在整个会话中复用图片
|
|
43
47
|
|
|
@@ -63,11 +67,12 @@ kind: "package-reference"
|
|
|
63
67
|
- **不可变且保留策略中立。** 对象一经发布即不可变;恢复和 fork 后的会话可能共享它们,因此引用感知的垃圾回收被推迟,而不是与任何单个会话的删除绑定。
|
|
64
68
|
- **读取时校验。** 读取在返回前把字节和元数据与记录的引用比对,请求投影还会完整解码缓存字节,因此缺失、损坏或被替换的对象都会失败关闭。
|
|
65
69
|
- **角色无关的图片块。** `dsh-llm` 中的 `ImageBlock` 内容块携带 `ImageAttachmentRef`;提供方适配器以显式像素与字节预算把引用解析为确定性请求版本,执行文件系统则可以把不可变宿主对象映射为模型可读的进程路径。
|
|
66
|
-
- **按错误码路由。** `AttachmentError` 重新实现 `HarnessError` 的结构而不是继承它,因为基类位于 `dsh-llm
|
|
70
|
+
- **按错误码路由。** `AttachmentError` 重新实现 `HarnessError` 的结构而不是继承它,因为基类位于 `dsh-llm`,而后者依赖本包;消费方用 `isAttachmentError` 识别错误并按 `code` 路由,绝不依赖原型链。
|
|
71
|
+
- **文件原样,图片规范化。**`saveFile` 提交已有字节数组,`saveFileStream` 以背压和取消语义提交有界分块,`readFileStream` 校验并返回有界分块,`fileHostPath` 定位存储对象供按需读取投影;两种文件写入路径都不设准入限制。图片路径保留其独立的规范化、限额与请求版本流水线。`dsh-llm` 中的 `FileBlock` 内容块承载 `FileAttachmentRef`,请求组装把它对每条路由都投影成确定性 handle 文本。
|
|
67
72
|
|
|
68
73
|
### 服务操作
|
|
69
74
|
|
|
70
|
-
服务族运行同一条准入与存储流程:每个入口都强制执行源批次限制与规范 base64
|
|
75
|
+
服务族运行同一条准入与存储流程:每个入口都强制执行源批次限制与规范 base64,在发布任何成员前准备提供方无关的规范化附件,再按输入顺序持久提交而不产生部分结果。Host prompt 消费方把有序文本、编码图片和已经解析的文件引用交给 `ctx.attachments.admitPromptContent()`;该方法持久化图片,并让文件引用原样通过。编码协议适配器调用 `ctx.attachments.admitEncodedFile()`,由该方法检查规范 base64 后委托给 `saveFile`;适配器通过 `ctx.attachments.isAttachmentError()` 识别附件错误。通用文件调用方可以用 `saveFile` 提交已有字节,或用 `saveFileStream` 提交有界异步字节源;两者返回相同的持久引用,`readFileStream` 则在有界读取过程中校验摘要与长度。`readImageRequest` 派生确定性的路由尺寸变体,其身份包含附件 id、变换版本、像素与字节预算及编码参数。纯函数导出 `requestImageDimensions` 会按总像素预算计算每个投影保持宽高比的尺寸,使提供方与请求定价共享同一套几何计算。`imageHostPath` 只向需要执行世界映射的受信任同进程消费方暴露实现拥有的宿主位置。调用方组合有序批次,而实现拥有压缩并发、缓存与 singleflight。读取、流式写入和投影保留调用方的取消语义。失败带有稳定且机器可读的错误码,运行时即可识别可由调用方修正的准入子集,让每个协议适配器映射自己的词汇;各操作的确切约定见 [`src/index.ts`](src/index.ts) 与 [`src/error.ts`](src/error.ts)。
|
|
71
76
|
|
|
72
77
|
### 源码地图
|
|
73
78
|
|
|
@@ -75,7 +80,7 @@ kind: "package-reference"
|
|
|
75
80
|
|---|---|
|
|
76
81
|
| [`src/index.ts`](src/index.ts) | 插件入口:抽象 `AttachmentStore` 服务与再导出 |
|
|
77
82
|
| [`src/types.ts`](src/types.ts) | 持久词汇:引用、限额、上传与存储载荷 |
|
|
78
|
-
| [`src/admission.ts`](src/admission.ts) |
|
|
83
|
+
| [`src/admission.ts`](src/admission.ts) | 对编码图片和文件上传强制执行规范 base64 并委托存储 |
|
|
79
84
|
| [`src/error.ts`](src/error.ts) | `AttachmentError` 类与 `isImageAdmissionError` 运行时子集 |
|
|
80
85
|
| [`src/brand.ts`](src/brand.ts) | `AttachmentId` 带类型标记的不透明标识符 |
|
|
81
86
|
| — | 不发布运行时不变式伴生入口;实现负责强制不可变存储检查。 |
|
|
@@ -98,7 +103,7 @@ kind: "package-reference"
|
|
|
98
103
|
<a id="model-experience"></a>
|
|
99
104
|
## 模型体验
|
|
100
105
|
|
|
101
|
-
|
|
106
|
+
该包通过提供方适配器间接影响模型;适配器会把每个持久图片引用解析为确切请求版本,并在图片旁发送稳定附件 id 与实际尺寸。执行文件系统可以映射已存对象时,描述符还会包含只读进程路径,以及可写副本使用的匹配扩展名。通用文件的字节永远不会到达提供方:每条路由都收到一行确定性 handle 文本,指出文件名、字节数、摘要前缀,以及供文件工具读取的只读保存路径。
|
|
102
107
|
|
|
103
108
|
#### KV Cache 影响
|
|
104
109
|
|
|
@@ -109,10 +114,10 @@ kind: "package-reference"
|
|
|
109
114
|
<a id="known-limitations-and-deferred-work"></a>
|
|
110
115
|
|
|
111
116
|
|
|
112
|
-
|
|
117
|
+
这些限制描述了附件能做什么、不能做什么;它们是当前包约束,而非任务积压。
|
|
113
118
|
|
|
114
|
-
-
|
|
115
|
-
-
|
|
119
|
+
- **光栅图片限制只作用于图片**——图片接受部署限额内的 PNG、JPEG、WebP 与 GIF;其他任何文件都原样保存、不设类型与大小限制,音频和视频暂无专门处理。
|
|
120
|
+
- **附件永远不会被删除**——已存储的图片与文件无限期保留;没有任何机制自动移除它们。
|
|
116
121
|
- **未发送的草稿不会保存**——输入区草稿在提交消息前一直留在浏览器中。
|
|
117
122
|
|
|
118
123
|
<a id="dev-note"></a>
|
|
@@ -127,8 +132,8 @@ kind: "package-reference"
|
|
|
127
132
|
|
|
128
133
|
恢复和 fork 后的会话可能共享不可变对象,因此任何保留策略都需要一个能考虑会话血缘的引用模型,之后才能回收对象。目前尚未记录任何决定;本地后端当前保留一切。
|
|
129
134
|
|
|
130
|
-
####
|
|
135
|
+
#### 未来:音频、视频与助手侧输出
|
|
131
136
|
|
|
132
|
-
|
|
137
|
+
音频与视频需要原样文件路径之外的专门生命周期与提供方契约;角色无关的 `ImageBlock` 也把助手侧图片输出留作前瞻兼容——当前生产适配器声明只输出文本,因此只有用户内容携带图片。两个方向都尚未决定。
|
|
133
138
|
|
|
134
139
|
</details>
|
package/lib/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Service } from "@deepseek-ai/cordis";
|
|
2
2
|
import { Buffer } from "node:buffer";
|
|
3
3
|
//#region lib/types/error.js
|
|
4
|
-
/**
|
|
5
|
-
const
|
|
4
|
+
/** Attachment failure class. @module @deepseek-ai/dsh-attachment/error */
|
|
5
|
+
const IMAGE_ADMISSION_ERROR_CODES = [
|
|
6
6
|
"TOO_MANY_IMAGES",
|
|
7
7
|
"IMAGES_TOO_LARGE",
|
|
8
8
|
"UNSUPPORTED_IMAGE_TYPE",
|
|
@@ -12,7 +12,21 @@ const IMAGE_ADMISSION_ERROR_CODE_SET = new Set([
|
|
|
12
12
|
"IMAGE_TOO_LARGE",
|
|
13
13
|
"IMAGE_TOO_MANY_PIXELS",
|
|
14
14
|
"IMAGE_DIMENSION_TOO_LARGE"
|
|
15
|
-
]
|
|
15
|
+
];
|
|
16
|
+
const ATTACHMENT_ERROR_CODES = [
|
|
17
|
+
...IMAGE_ADMISSION_ERROR_CODES,
|
|
18
|
+
"INVALID_FILE_BASE64",
|
|
19
|
+
"INVALID_ATTACHMENT_REF",
|
|
20
|
+
"ATTACHMENT_CORRUPT",
|
|
21
|
+
"ATTACHMENT_WRITE_FAILED",
|
|
22
|
+
"ATTACHMENT_NOT_FOUND",
|
|
23
|
+
"ATTACHMENT_READ_FAILED",
|
|
24
|
+
"ATTACHMENT_PROJECTION_UNSUPPORTED",
|
|
25
|
+
"ATTACHMENT_FILES_UNSUPPORTED"
|
|
26
|
+
];
|
|
27
|
+
/** Runtime membership for structurally compatible errors crossing package boundaries. */
|
|
28
|
+
const IMAGE_ADMISSION_ERROR_CODE_SET = new Set(IMAGE_ADMISSION_ERROR_CODES);
|
|
29
|
+
const ATTACHMENT_ERROR_CODE_SET = new Set(ATTACHMENT_ERROR_CODES);
|
|
16
30
|
/**
|
|
17
31
|
* Stable failures suitable for host RPC error mapping.
|
|
18
32
|
*
|
|
@@ -37,6 +51,14 @@ var AttachmentError = class extends Error {
|
|
|
37
51
|
}
|
|
38
52
|
};
|
|
39
53
|
/**
|
|
54
|
+
* Identify attachment failures by their stable code across duplicate package installations.
|
|
55
|
+
* @param error - failure raised while validating, persisting, or reading an attachment.
|
|
56
|
+
* @returns whether the failure carries a recognized attachment error code.
|
|
57
|
+
*/
|
|
58
|
+
function isAttachmentError(error) {
|
|
59
|
+
return error instanceof Error && "code" in error && typeof error.code === "string" && ATTACHMENT_ERROR_CODE_SET.has(error.code);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
40
62
|
* Distinguish caller-correctable image admission failures from storage faults.
|
|
41
63
|
* @param error - failure raised while validating or persisting an image batch.
|
|
42
64
|
* @returns whether the caller can correct the proposed image content or batch.
|
|
@@ -45,33 +67,17 @@ function isImageAdmissionError(error) {
|
|
|
45
67
|
return error instanceof Error && "code" in error && typeof error.code === "string" && IMAGE_ADMISSION_ERROR_CODE_SET.has(error.code);
|
|
46
68
|
}
|
|
47
69
|
//#endregion
|
|
48
|
-
//#region lib/types/brand.js
|
|
49
|
-
/** Attachment identifier brand. @module @deepseek-ai/dsh-attachment/brand */
|
|
50
|
-
/**
|
|
51
|
-
* Brand a validated storage identifier.
|
|
52
|
-
* @param value - backend-produced opaque identifier.
|
|
53
|
-
* @returns the branded identifier.
|
|
54
|
-
*/
|
|
55
|
-
function AttachmentId(value) {
|
|
56
|
-
return value;
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Brand a validated request-image transformation identifier.
|
|
60
|
-
* @param value - attachment-provider-produced opaque identifier.
|
|
61
|
-
* @returns the branded identifier.
|
|
62
|
-
*/
|
|
63
|
-
function ImageVariantId(value) {
|
|
64
|
-
return value;
|
|
65
|
-
}
|
|
66
|
-
//#endregion
|
|
67
70
|
//#region lib/types/admission.js
|
|
68
71
|
/** Wire-form admission of base64-encoded image uploads. @module @deepseek-ai/dsh-attachment/admission */
|
|
69
72
|
/** Decode one upload payload while rejecting non-canonical base64 forms. */
|
|
70
|
-
function
|
|
73
|
+
function decodeCanonicalBase64(data, empty, code) {
|
|
71
74
|
const decoded = Buffer.from(data, "base64");
|
|
72
|
-
if (data.length === 0 || decoded.toString("base64") !== data) throw new AttachmentError("Image upload is not canonical base64."
|
|
75
|
+
if (data.length === 0 && empty === "reject" || decoded.toString("base64") !== data) throw new AttachmentError(code === "INVALID_IMAGE_BASE64" ? "Image upload is not canonical base64." : "File upload is not canonical base64.", code);
|
|
73
76
|
return new Uint8Array(decoded);
|
|
74
77
|
}
|
|
78
|
+
function decodeBase64(data) {
|
|
79
|
+
return decodeCanonicalBase64(data, "reject", "INVALID_IMAGE_BASE64");
|
|
80
|
+
}
|
|
75
81
|
/** Store input for one decoded upload. */
|
|
76
82
|
function saveInput(image) {
|
|
77
83
|
return {
|
|
@@ -94,29 +100,41 @@ async function admitEncodedImages(attachments, images) {
|
|
|
94
100
|
return attachments.saveImages(images.map(saveInput));
|
|
95
101
|
}
|
|
96
102
|
/**
|
|
97
|
-
* Admit one
|
|
98
|
-
*
|
|
99
|
-
* @
|
|
100
|
-
*
|
|
101
|
-
* @
|
|
102
|
-
* @
|
|
103
|
+
* Admit one wire file upload: enforce canonical base64 (an empty file is a
|
|
104
|
+
* valid zero-byte payload), then delegate verbatim commit to
|
|
105
|
+
* {@link AttachmentStore.saveFile}. The shared entry for every RPC endpoint
|
|
106
|
+
* accepting browser file uploads.
|
|
107
|
+
* @param attachments - the deployment attachment store.
|
|
108
|
+
* @param file - base64-encoded upload and optional display name.
|
|
109
|
+
* @returns the durable content-addressed file reference.
|
|
110
|
+
* @throws AttachmentError on a non-canonical payload or a storage failure.
|
|
103
111
|
*/
|
|
104
|
-
async function
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}));
|
|
109
|
-
const refs = await admitEncodedImages(attachments, content.filter((part) => part.type === "image"));
|
|
110
|
-
let next = 0;
|
|
111
|
-
return content.map((part) => part.type === "text" ? {
|
|
112
|
-
type: "text",
|
|
113
|
-
text: part.text
|
|
114
|
-
} : {
|
|
115
|
-
type: "image",
|
|
116
|
-
attachment: refs[next++]
|
|
112
|
+
async function admitEncodedFile(attachments, file) {
|
|
113
|
+
return attachments.saveFile({
|
|
114
|
+
data: decodeCanonicalBase64(file.data, "accept", "INVALID_FILE_BASE64"),
|
|
115
|
+
...file.name === void 0 ? {} : { name: file.name }
|
|
117
116
|
});
|
|
118
117
|
}
|
|
119
118
|
//#endregion
|
|
119
|
+
//#region lib/types/brand.js
|
|
120
|
+
/** Attachment identifier brand. @module @deepseek-ai/dsh-attachment/brand */
|
|
121
|
+
/**
|
|
122
|
+
* Brand a validated storage identifier.
|
|
123
|
+
* @param value - backend-produced opaque identifier.
|
|
124
|
+
* @returns the branded identifier.
|
|
125
|
+
*/
|
|
126
|
+
function AttachmentId(value) {
|
|
127
|
+
return value;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Brand a validated request-image transformation identifier.
|
|
131
|
+
* @param value - attachment-provider-produced opaque identifier.
|
|
132
|
+
* @returns the branded identifier.
|
|
133
|
+
*/
|
|
134
|
+
function ImageVariantId(value) {
|
|
135
|
+
return value;
|
|
136
|
+
}
|
|
137
|
+
//#endregion
|
|
120
138
|
//#region lib/types/request-projection.js
|
|
121
139
|
/**
|
|
122
140
|
* Pure request-projection geometry shared by attachment providers and
|
|
@@ -193,6 +211,55 @@ var AttachmentStore = class extends Service {
|
|
|
193
211
|
return refs;
|
|
194
212
|
}
|
|
195
213
|
/**
|
|
214
|
+
* Admit one Host prompt and replace each uploaded image with its durable reference.
|
|
215
|
+
* Text and durable file references pass through unchanged. A prompt without image parts performs no storage operation.
|
|
216
|
+
* @param content - prompt parts in message order after file receipt resolution.
|
|
217
|
+
* @returns admitted prompt parts in the same order as `content`.
|
|
218
|
+
* @throws AttachmentError when the image batch is refused.
|
|
219
|
+
*/
|
|
220
|
+
async admitPromptContent(content) {
|
|
221
|
+
if (content.every((part) => part.type !== "image")) return content.map((part) => part.type === "text" ? {
|
|
222
|
+
type: "text",
|
|
223
|
+
text: part.text
|
|
224
|
+
} : {
|
|
225
|
+
type: "file",
|
|
226
|
+
attachment: part.attachment
|
|
227
|
+
});
|
|
228
|
+
const refs = await admitEncodedImages(this, content.filter((part) => part.type === "image"));
|
|
229
|
+
let next = 0;
|
|
230
|
+
return content.map((part) => {
|
|
231
|
+
if (part.type === "text") return {
|
|
232
|
+
type: "text",
|
|
233
|
+
text: part.text
|
|
234
|
+
};
|
|
235
|
+
if (part.type === "file") return {
|
|
236
|
+
type: "file",
|
|
237
|
+
attachment: part.attachment
|
|
238
|
+
};
|
|
239
|
+
return {
|
|
240
|
+
type: "image",
|
|
241
|
+
attachment: refs[next++]
|
|
242
|
+
};
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Decode and durably commit one canonical base64 file upload.
|
|
247
|
+
* @param input - canonical base64 bytes and optional display name.
|
|
248
|
+
* @returns the durable content-addressed file reference.
|
|
249
|
+
* @throws AttachmentError when the encoding or storage operation is refused.
|
|
250
|
+
*/
|
|
251
|
+
admitEncodedFile(input) {
|
|
252
|
+
return admitEncodedFile(this, input);
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Identify a failure emitted by this attachment capability by its stable code.
|
|
256
|
+
* @param error - value caught from an attachment operation.
|
|
257
|
+
* @returns whether the value is an attachment failure.
|
|
258
|
+
*/
|
|
259
|
+
isAttachmentError(error) {
|
|
260
|
+
return isAttachmentError(error);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
196
263
|
* Locate the provider-owned normalized object in the harness host filesystem.
|
|
197
264
|
* @param ref - durable normalized attachment reference.
|
|
198
265
|
* @returns an absolute host path, or undefined when this backend is not host-file-backed.
|
|
@@ -200,6 +267,46 @@ var AttachmentStore = class extends Service {
|
|
|
200
267
|
*/
|
|
201
268
|
imageHostPath(ref) {}
|
|
202
269
|
/**
|
|
270
|
+
* Durably commit one file byte-for-byte before its owning session event is
|
|
271
|
+
* appended. Files carry no admission limits: any byte content and length is
|
|
272
|
+
* accepted, and the stored object is the exact submitted bytes. Backends
|
|
273
|
+
* without verbatim file storage keep this default rejection.
|
|
274
|
+
* @param input - exact bytes and optional display name.
|
|
275
|
+
* @returns the durable content-addressed file reference.
|
|
276
|
+
*/
|
|
277
|
+
saveFile(input) {
|
|
278
|
+
return Promise.reject(new AttachmentError("The mounted attachment provider cannot store verbatim files.", "ATTACHMENT_FILES_UNSUPPORTED"));
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Durably commit one file byte-for-byte from bounded chunks. Providers must
|
|
282
|
+
* apply backpressure and must not collect the complete file in memory.
|
|
283
|
+
* Backends without streamed verbatim storage keep this default rejection.
|
|
284
|
+
* @param input - ordered exact bytes, optional cancellation, and display name.
|
|
285
|
+
* @returns the durable content-addressed file reference.
|
|
286
|
+
*/
|
|
287
|
+
saveFileStream(input) {
|
|
288
|
+
return Promise.reject(new AttachmentError("The mounted attachment provider cannot stream verbatim files.", "ATTACHMENT_FILES_UNSUPPORTED"));
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Read and verify one verbatim stored file as bounded chunks. Providers must
|
|
292
|
+
* not collect the complete file in memory. Backends without verbatim file
|
|
293
|
+
* reads keep this default rejection.
|
|
294
|
+
* @param ref - durable reference from the session log.
|
|
295
|
+
* @param signal - optional cancellation for backend reads and verification work.
|
|
296
|
+
* @returns exact file bytes in order; integrity failures reject the iteration.
|
|
297
|
+
*/
|
|
298
|
+
async *readFileStream(ref, signal) {
|
|
299
|
+
signal?.throwIfAborted();
|
|
300
|
+
await Promise.reject(new AttachmentError("The mounted attachment provider cannot read verbatim files.", "ATTACHMENT_FILES_UNSUPPORTED"));
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Locate the verbatim stored file object in the harness host filesystem.
|
|
304
|
+
* @param ref - durable file reference.
|
|
305
|
+
* @returns an absolute host path, or undefined when this backend is not host-file-backed.
|
|
306
|
+
* @throws an AttachmentError when the durable reference is invalid.
|
|
307
|
+
*/
|
|
308
|
+
fileHostPath(ref) {}
|
|
309
|
+
/**
|
|
203
310
|
* Generate or read one deterministic model-request version from the stored normalized image.
|
|
204
311
|
* @param ref - durable provider-independent normalized attachment reference.
|
|
205
312
|
* @param policy - exact route pixel budget and encoded-byte target; a target no ladder quality meets yields the smallest ladder output.
|
|
@@ -212,4 +319,4 @@ var AttachmentStore = class extends Service {
|
|
|
212
319
|
}
|
|
213
320
|
};
|
|
214
321
|
//#endregion
|
|
215
|
-
export { AttachmentError, AttachmentId, AttachmentStore, AttachmentStore as default, ImageVariantId, admitEncodedImages,
|
|
322
|
+
export { AttachmentError, AttachmentId, AttachmentStore, AttachmentStore as default, ImageVariantId, admitEncodedFile, admitEncodedImages, isAttachmentError, isImageAdmissionError, requestImageDimensions };
|
package/lib/types/admission.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Wire-form admission of base64-encoded image uploads. @module @deepseek-ai/dsh-attachment/admission */
|
|
2
2
|
import type { AttachmentStore } from './index.ts';
|
|
3
|
-
import type {
|
|
3
|
+
import type { EncodedFileAttachment, EncodedImageAttachment, FileAttachmentRef, ImageAttachmentRef } from './types.ts';
|
|
4
4
|
/**
|
|
5
5
|
* Admit one wire image batch: enforce canonical base64 on every member, then
|
|
6
6
|
* delegate batch admission — count and aggregate-byte limits, media-type and
|
|
@@ -13,12 +13,14 @@ import type { AdmittedPromptContentPart, EncodedImageAttachment, ImageAttachment
|
|
|
13
13
|
*/
|
|
14
14
|
export declare function admitEncodedImages(attachments: AttachmentStore, images: readonly EncodedImageAttachment[]): Promise<readonly ImageAttachmentRef[]>;
|
|
15
15
|
/**
|
|
16
|
-
* Admit one
|
|
17
|
-
*
|
|
18
|
-
* @
|
|
19
|
-
*
|
|
20
|
-
* @
|
|
21
|
-
* @
|
|
16
|
+
* Admit one wire file upload: enforce canonical base64 (an empty file is a
|
|
17
|
+
* valid zero-byte payload), then delegate verbatim commit to
|
|
18
|
+
* {@link AttachmentStore.saveFile}. The shared entry for every RPC endpoint
|
|
19
|
+
* accepting browser file uploads.
|
|
20
|
+
* @param attachments - the deployment attachment store.
|
|
21
|
+
* @param file - base64-encoded upload and optional display name.
|
|
22
|
+
* @returns the durable content-addressed file reference.
|
|
23
|
+
* @throws AttachmentError on a non-canonical payload or a storage failure.
|
|
22
24
|
*/
|
|
23
|
-
export declare function
|
|
25
|
+
export declare function admitEncodedFile(attachments: AttachmentStore, file: EncodedFileAttachment): Promise<FileAttachmentRef>;
|
|
24
26
|
//# sourceMappingURL=admission.d.ts.map
|
package/lib/types/admission.js
CHANGED
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
import { Buffer } from 'node:buffer';
|
|
3
3
|
import { AttachmentError } from "./error.js";
|
|
4
4
|
/** Decode one upload payload while rejecting non-canonical base64 forms. */
|
|
5
|
-
function
|
|
5
|
+
function decodeCanonicalBase64(data, empty, code) {
|
|
6
6
|
const decoded = Buffer.from(data, 'base64');
|
|
7
|
-
if (data.length === 0 || decoded.toString('base64') !== data) {
|
|
8
|
-
throw new AttachmentError('Image upload is not canonical base64.'
|
|
7
|
+
if ((data.length === 0 && empty === 'reject') || decoded.toString('base64') !== data) {
|
|
8
|
+
throw new AttachmentError(code === 'INVALID_IMAGE_BASE64' ? 'Image upload is not canonical base64.' : 'File upload is not canonical base64.', code);
|
|
9
9
|
}
|
|
10
10
|
return new Uint8Array(decoded);
|
|
11
11
|
}
|
|
12
|
+
function decodeBase64(data) {
|
|
13
|
+
return decodeCanonicalBase64(data, 'reject', 'INVALID_IMAGE_BASE64');
|
|
14
|
+
}
|
|
12
15
|
/** Store input for one decoded upload. */
|
|
13
16
|
function saveInput(image) {
|
|
14
17
|
return {
|
|
@@ -31,22 +34,19 @@ export async function admitEncodedImages(attachments, images) {
|
|
|
31
34
|
return attachments.saveImages(images.map(saveInput));
|
|
32
35
|
}
|
|
33
36
|
/**
|
|
34
|
-
* Admit one
|
|
35
|
-
*
|
|
36
|
-
* @
|
|
37
|
-
*
|
|
38
|
-
* @
|
|
39
|
-
* @
|
|
37
|
+
* Admit one wire file upload: enforce canonical base64 (an empty file is a
|
|
38
|
+
* valid zero-byte payload), then delegate verbatim commit to
|
|
39
|
+
* {@link AttachmentStore.saveFile}. The shared entry for every RPC endpoint
|
|
40
|
+
* accepting browser file uploads.
|
|
41
|
+
* @param attachments - the deployment attachment store.
|
|
42
|
+
* @param file - base64-encoded upload and optional display name.
|
|
43
|
+
* @returns the durable content-addressed file reference.
|
|
44
|
+
* @throws AttachmentError on a non-canonical payload or a storage failure.
|
|
40
45
|
*/
|
|
41
|
-
export async function
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
let next = 0;
|
|
47
|
-
return content.map(part => part.type === 'text'
|
|
48
|
-
? { type: 'text', text: part.text }
|
|
49
|
-
// admitEncodedImages returns one reference per image part in order.
|
|
50
|
-
: { type: 'image', attachment: refs[next++] });
|
|
46
|
+
export async function admitEncodedFile(attachments, file) {
|
|
47
|
+
return attachments.saveFile({
|
|
48
|
+
data: decodeCanonicalBase64(file.data, 'accept', 'INVALID_FILE_BASE64'),
|
|
49
|
+
...file.name === undefined ? {} : { name: file.name },
|
|
50
|
+
});
|
|
51
51
|
}
|
|
52
52
|
//# sourceMappingURL=admission.js.map
|
package/lib/types/error.d.ts
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
declare const IMAGE_ADMISSION_ERROR_CODES: readonly ["TOO_MANY_IMAGES", "IMAGES_TOO_LARGE", "UNSUPPORTED_IMAGE_TYPE", "INVALID_IMAGE_BASE64", "INVALID_IMAGE", "IMAGE_TYPE_MISMATCH", "IMAGE_TOO_LARGE", "IMAGE_TOO_MANY_PIXELS", "IMAGE_DIMENSION_TOO_LARGE"];
|
|
3
3
|
/** Caller-correctable attachment failure codes raised while admitting image input. */
|
|
4
4
|
export type ImageAdmissionErrorCode = typeof IMAGE_ADMISSION_ERROR_CODES[number];
|
|
5
|
+
declare const ATTACHMENT_ERROR_CODES: readonly ["TOO_MANY_IMAGES", "IMAGES_TOO_LARGE", "UNSUPPORTED_IMAGE_TYPE", "INVALID_IMAGE_BASE64", "INVALID_IMAGE", "IMAGE_TYPE_MISMATCH", "IMAGE_TOO_LARGE", "IMAGE_TOO_MANY_PIXELS", "IMAGE_DIMENSION_TOO_LARGE", "INVALID_FILE_BASE64", "INVALID_ATTACHMENT_REF", "ATTACHMENT_CORRUPT", "ATTACHMENT_WRITE_FAILED", "ATTACHMENT_NOT_FOUND", "ATTACHMENT_READ_FAILED", "ATTACHMENT_PROJECTION_UNSUPPORTED", "ATTACHMENT_FILES_UNSUPPORTED"];
|
|
5
6
|
/** Stable attachment failure codes used for protocol error routing. */
|
|
6
|
-
export type AttachmentErrorCode =
|
|
7
|
+
export type AttachmentErrorCode = typeof ATTACHMENT_ERROR_CODES[number];
|
|
7
8
|
/**
|
|
8
9
|
* Stable failures suitable for host RPC error mapping.
|
|
9
10
|
*
|
|
@@ -23,6 +24,12 @@ export declare class AttachmentError extends Error {
|
|
|
23
24
|
*/
|
|
24
25
|
constructor(message: string, code: AttachmentErrorCode, options?: ErrorOptions);
|
|
25
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Identify attachment failures by their stable code across duplicate package installations.
|
|
29
|
+
* @param error - failure raised while validating, persisting, or reading an attachment.
|
|
30
|
+
* @returns whether the failure carries a recognized attachment error code.
|
|
31
|
+
*/
|
|
32
|
+
export declare function isAttachmentError(error: unknown): error is AttachmentError;
|
|
26
33
|
/**
|
|
27
34
|
* Distinguish caller-correctable image admission failures from storage faults.
|
|
28
35
|
* @param error - failure raised while validating or persisting an image batch.
|
package/lib/types/error.js
CHANGED
|
@@ -10,8 +10,20 @@ const IMAGE_ADMISSION_ERROR_CODES = [
|
|
|
10
10
|
'IMAGE_TOO_MANY_PIXELS',
|
|
11
11
|
'IMAGE_DIMENSION_TOO_LARGE',
|
|
12
12
|
];
|
|
13
|
+
const ATTACHMENT_ERROR_CODES = [
|
|
14
|
+
...IMAGE_ADMISSION_ERROR_CODES,
|
|
15
|
+
'INVALID_FILE_BASE64',
|
|
16
|
+
'INVALID_ATTACHMENT_REF',
|
|
17
|
+
'ATTACHMENT_CORRUPT',
|
|
18
|
+
'ATTACHMENT_WRITE_FAILED',
|
|
19
|
+
'ATTACHMENT_NOT_FOUND',
|
|
20
|
+
'ATTACHMENT_READ_FAILED',
|
|
21
|
+
'ATTACHMENT_PROJECTION_UNSUPPORTED',
|
|
22
|
+
'ATTACHMENT_FILES_UNSUPPORTED',
|
|
23
|
+
];
|
|
13
24
|
/** Runtime membership for structurally compatible errors crossing package boundaries. */
|
|
14
25
|
const IMAGE_ADMISSION_ERROR_CODE_SET = new Set(IMAGE_ADMISSION_ERROR_CODES);
|
|
26
|
+
const ATTACHMENT_ERROR_CODE_SET = new Set(ATTACHMENT_ERROR_CODES);
|
|
15
27
|
/**
|
|
16
28
|
* Stable failures suitable for host RPC error mapping.
|
|
17
29
|
*
|
|
@@ -35,6 +47,17 @@ export class AttachmentError extends Error {
|
|
|
35
47
|
this.code = code;
|
|
36
48
|
}
|
|
37
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Identify attachment failures by their stable code across duplicate package installations.
|
|
52
|
+
* @param error - failure raised while validating, persisting, or reading an attachment.
|
|
53
|
+
* @returns whether the failure carries a recognized attachment error code.
|
|
54
|
+
*/
|
|
55
|
+
export function isAttachmentError(error) {
|
|
56
|
+
return error instanceof Error
|
|
57
|
+
&& 'code' in error
|
|
58
|
+
&& typeof error.code === 'string'
|
|
59
|
+
&& ATTACHMENT_ERROR_CODE_SET.has(error.code);
|
|
60
|
+
}
|
|
38
61
|
/**
|
|
39
62
|
* Distinguish caller-correctable image admission failures from storage faults.
|
|
40
63
|
* @param error - failure raised while validating or persisting an image batch.
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/** Durable attachment storage seam (`ctx.attachments`). @module @deepseek-ai/dsh-attachment */
|
|
2
2
|
import { Context, Service } from '@deepseek-ai/cordis';
|
|
3
|
-
import
|
|
3
|
+
import { AttachmentError } from './error.ts';
|
|
4
|
+
import type { AdmittedPromptContentPart, AttachmentAdmissionPart, EncodedFileAttachment, FileAttachmentRef, ImageAttachmentLimits, ImageAttachmentRef, ImageRequestPolicy, RequestImageAttachment, SaveFileAttachment, SaveFileStreamAttachment, SaveImageAttachment, StoredImageAttachment } from './types.ts';
|
|
4
5
|
export { AttachmentId, ImageVariantId } from './brand.ts';
|
|
5
|
-
export { AttachmentError, isImageAdmissionError } from './error.ts';
|
|
6
|
+
export { AttachmentError, isAttachmentError, isImageAdmissionError } from './error.ts';
|
|
6
7
|
export type { AttachmentErrorCode, ImageAdmissionErrorCode } from './error.ts';
|
|
7
|
-
export {
|
|
8
|
+
export { admitEncodedFile, admitEncodedImages } from './admission.ts';
|
|
8
9
|
export { requestImageDimensions } from './request-projection.ts';
|
|
9
|
-
export type { AttachmentId as AttachmentIdType, AdmittedPromptContentPart, EncodedImageAttachment, ImageAttachmentLimits, ImageAttachmentRef, ImageRequestPolicy, ImageMediaType, PromptContentPart, RequestImageAttachment, SaveImageAttachment, StoredImageAttachment, } from './types.ts';
|
|
10
|
+
export type { AttachmentId as AttachmentIdType, AdmittedPromptContentPart, AttachmentAdmissionPart, EncodedFileAttachment, EncodedImageAttachment, FileAttachmentRef, ImageAttachmentLimits, ImageAttachmentRef, ImageRequestPolicy, ImageMediaType, PromptContentPart, RequestImageAttachment, SaveFileAttachment, SaveFileStreamAttachment, SaveImageAttachment, StoredImageAttachment, } from './types.ts';
|
|
10
11
|
declare module '@deepseek-ai/cordis' {
|
|
11
12
|
interface Context {
|
|
12
13
|
attachments: AttachmentStore;
|
|
@@ -39,6 +40,27 @@ export declare abstract class AttachmentStore extends Service {
|
|
|
39
40
|
* @returns durable normalized attachment references in the same order after every member succeeds.
|
|
40
41
|
*/
|
|
41
42
|
saveImages(inputs: readonly SaveImageAttachment[]): Promise<readonly ImageAttachmentRef[]>;
|
|
43
|
+
/**
|
|
44
|
+
* Admit one Host prompt and replace each uploaded image with its durable reference.
|
|
45
|
+
* Text and durable file references pass through unchanged. A prompt without image parts performs no storage operation.
|
|
46
|
+
* @param content - prompt parts in message order after file receipt resolution.
|
|
47
|
+
* @returns admitted prompt parts in the same order as `content`.
|
|
48
|
+
* @throws AttachmentError when the image batch is refused.
|
|
49
|
+
*/
|
|
50
|
+
admitPromptContent(content: readonly AttachmentAdmissionPart[]): Promise<AdmittedPromptContentPart[]>;
|
|
51
|
+
/**
|
|
52
|
+
* Decode and durably commit one canonical base64 file upload.
|
|
53
|
+
* @param input - canonical base64 bytes and optional display name.
|
|
54
|
+
* @returns the durable content-addressed file reference.
|
|
55
|
+
* @throws AttachmentError when the encoding or storage operation is refused.
|
|
56
|
+
*/
|
|
57
|
+
admitEncodedFile(input: EncodedFileAttachment): Promise<FileAttachmentRef>;
|
|
58
|
+
/**
|
|
59
|
+
* Identify a failure emitted by this attachment capability by its stable code.
|
|
60
|
+
* @param error - value caught from an attachment operation.
|
|
61
|
+
* @returns whether the value is an attachment failure.
|
|
62
|
+
*/
|
|
63
|
+
isAttachmentError(error: unknown): error is AttachmentError;
|
|
42
64
|
/**
|
|
43
65
|
* Validate and durably commit one image before its owning session event is appended.
|
|
44
66
|
* The returned reference describes the persisted normalized image. When
|
|
@@ -63,6 +85,39 @@ export declare abstract class AttachmentStore extends Service {
|
|
|
63
85
|
* @throws an AttachmentError when the durable reference is invalid.
|
|
64
86
|
*/
|
|
65
87
|
imageHostPath(ref: ImageAttachmentRef): string | undefined;
|
|
88
|
+
/**
|
|
89
|
+
* Durably commit one file byte-for-byte before its owning session event is
|
|
90
|
+
* appended. Files carry no admission limits: any byte content and length is
|
|
91
|
+
* accepted, and the stored object is the exact submitted bytes. Backends
|
|
92
|
+
* without verbatim file storage keep this default rejection.
|
|
93
|
+
* @param input - exact bytes and optional display name.
|
|
94
|
+
* @returns the durable content-addressed file reference.
|
|
95
|
+
*/
|
|
96
|
+
saveFile(input: SaveFileAttachment): Promise<FileAttachmentRef>;
|
|
97
|
+
/**
|
|
98
|
+
* Durably commit one file byte-for-byte from bounded chunks. Providers must
|
|
99
|
+
* apply backpressure and must not collect the complete file in memory.
|
|
100
|
+
* Backends without streamed verbatim storage keep this default rejection.
|
|
101
|
+
* @param input - ordered exact bytes, optional cancellation, and display name.
|
|
102
|
+
* @returns the durable content-addressed file reference.
|
|
103
|
+
*/
|
|
104
|
+
saveFileStream(input: SaveFileStreamAttachment): Promise<FileAttachmentRef>;
|
|
105
|
+
/**
|
|
106
|
+
* Read and verify one verbatim stored file as bounded chunks. Providers must
|
|
107
|
+
* not collect the complete file in memory. Backends without verbatim file
|
|
108
|
+
* reads keep this default rejection.
|
|
109
|
+
* @param ref - durable reference from the session log.
|
|
110
|
+
* @param signal - optional cancellation for backend reads and verification work.
|
|
111
|
+
* @returns exact file bytes in order; integrity failures reject the iteration.
|
|
112
|
+
*/
|
|
113
|
+
readFileStream(ref: FileAttachmentRef, signal?: AbortSignal): AsyncIterable<Uint8Array>;
|
|
114
|
+
/**
|
|
115
|
+
* Locate the verbatim stored file object in the harness host filesystem.
|
|
116
|
+
* @param ref - durable file reference.
|
|
117
|
+
* @returns an absolute host path, or undefined when this backend is not host-file-backed.
|
|
118
|
+
* @throws an AttachmentError when the durable reference is invalid.
|
|
119
|
+
*/
|
|
120
|
+
fileHostPath(ref: FileAttachmentRef): string | undefined;
|
|
66
121
|
/**
|
|
67
122
|
* Generate or read one deterministic model-request version from the stored normalized image.
|
|
68
123
|
* @param ref - durable provider-independent normalized attachment reference.
|
package/lib/types/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/** Durable attachment storage seam (`ctx.attachments`). @module @deepseek-ai/dsh-attachment */
|
|
2
2
|
import { Service } from '@deepseek-ai/cordis';
|
|
3
|
-
import {
|
|
3
|
+
import { admitEncodedFile as admitFileInput, admitEncodedImages } from "./admission.js";
|
|
4
|
+
import { AttachmentError, isAttachmentError as matchesAttachmentError } from "./error.js";
|
|
4
5
|
export { AttachmentId, ImageVariantId } from "./brand.js";
|
|
5
|
-
export { AttachmentError, isImageAdmissionError } from "./error.js";
|
|
6
|
-
export {
|
|
6
|
+
export { AttachmentError, isAttachmentError, isImageAdmissionError } from "./error.js";
|
|
7
|
+
export { admitEncodedFile, admitEncodedImages } from "./admission.js";
|
|
7
8
|
export { requestImageDimensions } from "./request-projection.js";
|
|
8
9
|
/** Immutable binary attachment service. Implementations validate bytes before publishing a reference. */
|
|
9
10
|
export class AttachmentStore extends Service {
|
|
@@ -47,6 +48,46 @@ export class AttachmentStore extends Service {
|
|
|
47
48
|
refs.push(await this.saveImage(input));
|
|
48
49
|
return refs;
|
|
49
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Admit one Host prompt and replace each uploaded image with its durable reference.
|
|
53
|
+
* Text and durable file references pass through unchanged. A prompt without image parts performs no storage operation.
|
|
54
|
+
* @param content - prompt parts in message order after file receipt resolution.
|
|
55
|
+
* @returns admitted prompt parts in the same order as `content`.
|
|
56
|
+
* @throws AttachmentError when the image batch is refused.
|
|
57
|
+
*/
|
|
58
|
+
async admitPromptContent(content) {
|
|
59
|
+
if (content.every(part => part.type !== 'image')) {
|
|
60
|
+
return content.map(part => part.type === 'text'
|
|
61
|
+
? { type: 'text', text: part.text }
|
|
62
|
+
: { type: 'file', attachment: part.attachment });
|
|
63
|
+
}
|
|
64
|
+
const refs = await admitEncodedImages(this, content.filter(part => part.type === 'image'));
|
|
65
|
+
let next = 0;
|
|
66
|
+
return content.map((part) => {
|
|
67
|
+
if (part.type === 'text')
|
|
68
|
+
return { type: 'text', text: part.text };
|
|
69
|
+
if (part.type === 'file')
|
|
70
|
+
return { type: 'file', attachment: part.attachment };
|
|
71
|
+
return { type: 'image', attachment: refs[next++] };
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Decode and durably commit one canonical base64 file upload.
|
|
76
|
+
* @param input - canonical base64 bytes and optional display name.
|
|
77
|
+
* @returns the durable content-addressed file reference.
|
|
78
|
+
* @throws AttachmentError when the encoding or storage operation is refused.
|
|
79
|
+
*/
|
|
80
|
+
admitEncodedFile(input) {
|
|
81
|
+
return admitFileInput(this, input);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Identify a failure emitted by this attachment capability by its stable code.
|
|
85
|
+
* @param error - value caught from an attachment operation.
|
|
86
|
+
* @returns whether the value is an attachment failure.
|
|
87
|
+
*/
|
|
88
|
+
isAttachmentError(error) {
|
|
89
|
+
return matchesAttachmentError(error);
|
|
90
|
+
}
|
|
50
91
|
/**
|
|
51
92
|
* Locate the provider-owned normalized object in the harness host filesystem.
|
|
52
93
|
* @param ref - durable normalized attachment reference.
|
|
@@ -57,6 +98,52 @@ export class AttachmentStore extends Service {
|
|
|
57
98
|
void ref;
|
|
58
99
|
return undefined;
|
|
59
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Durably commit one file byte-for-byte before its owning session event is
|
|
103
|
+
* appended. Files carry no admission limits: any byte content and length is
|
|
104
|
+
* accepted, and the stored object is the exact submitted bytes. Backends
|
|
105
|
+
* without verbatim file storage keep this default rejection.
|
|
106
|
+
* @param input - exact bytes and optional display name.
|
|
107
|
+
* @returns the durable content-addressed file reference.
|
|
108
|
+
*/
|
|
109
|
+
saveFile(input) {
|
|
110
|
+
void input;
|
|
111
|
+
return Promise.reject(new AttachmentError('The mounted attachment provider cannot store verbatim files.', 'ATTACHMENT_FILES_UNSUPPORTED'));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Durably commit one file byte-for-byte from bounded chunks. Providers must
|
|
115
|
+
* apply backpressure and must not collect the complete file in memory.
|
|
116
|
+
* Backends without streamed verbatim storage keep this default rejection.
|
|
117
|
+
* @param input - ordered exact bytes, optional cancellation, and display name.
|
|
118
|
+
* @returns the durable content-addressed file reference.
|
|
119
|
+
*/
|
|
120
|
+
saveFileStream(input) {
|
|
121
|
+
void input;
|
|
122
|
+
return Promise.reject(new AttachmentError('The mounted attachment provider cannot stream verbatim files.', 'ATTACHMENT_FILES_UNSUPPORTED'));
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Read and verify one verbatim stored file as bounded chunks. Providers must
|
|
126
|
+
* not collect the complete file in memory. Backends without verbatim file
|
|
127
|
+
* reads keep this default rejection.
|
|
128
|
+
* @param ref - durable reference from the session log.
|
|
129
|
+
* @param signal - optional cancellation for backend reads and verification work.
|
|
130
|
+
* @returns exact file bytes in order; integrity failures reject the iteration.
|
|
131
|
+
*/
|
|
132
|
+
async *readFileStream(ref, signal) {
|
|
133
|
+
signal?.throwIfAborted();
|
|
134
|
+
void ref;
|
|
135
|
+
await Promise.reject(new AttachmentError('The mounted attachment provider cannot read verbatim files.', 'ATTACHMENT_FILES_UNSUPPORTED'));
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Locate the verbatim stored file object in the harness host filesystem.
|
|
139
|
+
* @param ref - durable file reference.
|
|
140
|
+
* @returns an absolute host path, or undefined when this backend is not host-file-backed.
|
|
141
|
+
* @throws an AttachmentError when the durable reference is invalid.
|
|
142
|
+
*/
|
|
143
|
+
fileHostPath(ref) {
|
|
144
|
+
void ref;
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
60
147
|
/**
|
|
61
148
|
* Generate or read one deterministic model-request version from the stored normalized image.
|
|
62
149
|
* @param ref - durable provider-independent normalized attachment reference.
|
package/lib/types/types.d.ts
CHANGED
|
@@ -26,6 +26,41 @@ export interface ImageAttachmentRef {
|
|
|
26
26
|
height: number;
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Durable, serializable reference to one verbatim stored file. Files are
|
|
31
|
+
* stored byte-for-byte with no normalization; `attachmentId` is the sha256
|
|
32
|
+
* digest of exactly those bytes.
|
|
33
|
+
*/
|
|
34
|
+
export interface FileAttachmentRef {
|
|
35
|
+
/** Opaque content-addressed storage identifier; never a filesystem path or bearer URL. */
|
|
36
|
+
attachmentId: AttachmentId;
|
|
37
|
+
/** Sanitized display filename, also the stored object's leaf name. */
|
|
38
|
+
name: string;
|
|
39
|
+
/** Exact byte length. */
|
|
40
|
+
bytes: number;
|
|
41
|
+
}
|
|
42
|
+
/** Base64-encoded file upload accompanying one wire request. */
|
|
43
|
+
export interface EncodedFileAttachment {
|
|
44
|
+
/** Canonical base64 encoding of the file bytes. */
|
|
45
|
+
data: string;
|
|
46
|
+
/** Optional display name; it is never interpreted as a path. */
|
|
47
|
+
name?: string;
|
|
48
|
+
}
|
|
49
|
+
/** Request to durably commit one file verbatim. */
|
|
50
|
+
export interface SaveFileAttachment {
|
|
51
|
+
data: Uint8Array;
|
|
52
|
+
/** Optional browser/provider display name; it is never interpreted as a path. */
|
|
53
|
+
name?: string;
|
|
54
|
+
}
|
|
55
|
+
/** Request to durably commit one file from bounded byte chunks. */
|
|
56
|
+
export interface SaveFileStreamAttachment {
|
|
57
|
+
/** Exact file bytes in order; providers must not retain the complete sequence in memory. */
|
|
58
|
+
data: AsyncIterable<Uint8Array>;
|
|
59
|
+
/** Optional cancellation for source reads and storage writes. */
|
|
60
|
+
signal?: AbortSignal;
|
|
61
|
+
/** Optional browser/provider display name; it is never interpreted as a path. */
|
|
62
|
+
name?: string;
|
|
63
|
+
}
|
|
29
64
|
/** Deployment-resolved limits used by upload admission and request buffering. */
|
|
30
65
|
export interface ImageAttachmentLimits {
|
|
31
66
|
maxImageBytes: number;
|
|
@@ -48,7 +83,7 @@ export interface EncodedImageAttachment {
|
|
|
48
83
|
/**
|
|
49
84
|
* Browser-submitted prompt content accepted by Host prompt endpoints; the
|
|
50
85
|
* accepting Host promotes image parts to durable references through
|
|
51
|
-
* `admitPromptContent` before any message is created, so a wire caller can
|
|
86
|
+
* `ctx.attachments.admitPromptContent()` before any message is created, so a wire caller can
|
|
52
87
|
* never cite an attachment it did not upload.
|
|
53
88
|
*/
|
|
54
89
|
export type PromptContentPart = {
|
|
@@ -60,13 +95,21 @@ export type PromptContentPart = {
|
|
|
60
95
|
readonly data: string;
|
|
61
96
|
readonly name?: string;
|
|
62
97
|
};
|
|
63
|
-
/** Host
|
|
98
|
+
/** Host prompt content whose file receipts are resolved and whose image bytes await admission. */
|
|
99
|
+
export type AttachmentAdmissionPart = PromptContentPart | {
|
|
100
|
+
readonly type: 'file';
|
|
101
|
+
readonly attachment: FileAttachmentRef;
|
|
102
|
+
};
|
|
103
|
+
/** Host-admitted prompt content with every attachment represented by its durable reference. */
|
|
64
104
|
export type AdmittedPromptContentPart = {
|
|
65
105
|
readonly type: 'text';
|
|
66
106
|
readonly text: string;
|
|
67
107
|
} | {
|
|
68
108
|
readonly type: 'image';
|
|
69
109
|
readonly attachment: ImageAttachmentRef;
|
|
110
|
+
} | {
|
|
111
|
+
readonly type: 'file';
|
|
112
|
+
readonly attachment: FileAttachmentRef;
|
|
70
113
|
};
|
|
71
114
|
/** Request to validate and durably commit one image. */
|
|
72
115
|
export interface SaveImageAttachment {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-attachment",
|
|
3
3
|
"description": "Durable immutable attachment storage seam for the DeepSeek Harness",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.3-alpha.2",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -33,10 +33,10 @@
|
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
36
|
-
"@deepseek-ai/dsh-brand": "^0.1.
|
|
36
|
+
"@deepseek-ai/dsh-brand": "^0.1.3-alpha.2"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@deepseek-ai/
|
|
40
|
-
"@deepseek-ai/
|
|
39
|
+
"@deepseek-ai/dsh-brand": "^0.1.3-alpha.2",
|
|
40
|
+
"@deepseek-ai/cordis": "^4.0.2"
|
|
41
41
|
}
|
|
42
42
|
}
|