@deepseek-ai/dsh-compaction-image-offload 0.2.2-rc.1 → 0.2.2-rc.3

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-offload.d.ts","sourceRoot":"","sources":["../../src/image-offload.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAG/E,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAGnE;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,SAAS,UAAU,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CA8BpH"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-offload.js","sourceRoot":"","sources":["../../src/image-offload.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAM/E;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAgB,EAAE,eAAsC,EAAE,KAAa;IACzG,MAAM,OAAO,GAAyB,EAAE,CAAA;IACxC,KAAK,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;QAClC,IAAI,KAAK,KAAK,CAAC;YAAE,MAAK;QACtB,kGAAkG;QAClG,sFAAsF;QACtF,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAE,CAAA;QACnC,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,IAAI,KAAK,CAAC,IAAI,KAAK,aAAa;YAAE,SAAQ;QAC3E,uGAAuG;QACvG,MAAM,OAAO,GAAG,OAAO,CAAC,kBAAkB,CAAC,KAAK,CAAE,CAAA;QAClD,MAAM,YAAY,GAAa,EAAE,CAAA;QACjC,IAAI,UAAU,GAAG,CAAC,CAAA;QAClB,MAAM,KAAK,GAAG,CAAC,MAA+B,EAAQ,EAAE;YACtD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC3B,IAAI,KAAK,KAAK,CAAC;oBAAE,MAAK;gBACtB,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;oBAC3B,IAAI,KAAK,CAAC,SAAS,KAAK,IAAI,EAAE,CAAC;wBAC7B,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;wBAC7B,KAAK,IAAI,CAAC,CAAA;oBACZ,CAAC;oBACD,UAAU,IAAI,CAAC,CAAA;gBACjB,CAAC;YACH,CAAC;QACH,CAAC,CAAA;QACD,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;QACtB,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,YAAY,EAAE,CAAC,CAAA;IAClE,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IACtC,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,EAAE,OAAO,EAAE,CAAC,CAAA;IAC5C,OAAO,IAAI,CAAA;AACb,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAOlD,eAAO,MAAM,IAAI,6BAA6B,CAAA;AAC9C,eAAO,MAAM,MAAM,UAAyB,CAAA;AAE5C;;;GAGG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAgBxC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,EAAE,2BAA2B,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAC5E,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AAExD,MAAM,CAAC,MAAM,IAAI,GAAG,0BAA0B,CAAA;AAC9C,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAA;AAE5C;;;GAGG;AACH,MAAM,UAAU,KAAK,CAAC,GAAY;IAChC,GAAG,CAAC,QAAQ,CAAC,yBAAyB,CAAC,sBAAsB,CAAC,CAAA;IAC9D,GAAG,CAAC,EAAE,CAAC,qBAAqB,EAAE,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAA+B,EAAE;QAC9F,IAAI,OAAO,CAAC,IAAI,KAAK,2BAA2B,IAAI,OAAO,CAAC,aAAa,KAAK,SAAS,IAAI,MAAM,CAAC,OAAO;YAAE,OAAO,IAAI,EAAE,CAAA;QACxH,qGAAqG;QACrG,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,aAAa,CAAC;YAAE,OAAO,IAAI,EAAE,CAAA;QAC1G,OAAO,OAAO,CAAC,OAAO,CAAqB,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAA;IAC/D,CAAC,CAAC,CAAA;IACF,GAAG,CAAC,EAAE,CAAC,0BAA0B,EAAE,CAAC,EAAE,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE;QACvF,IAAI,CAAC,CAAC,KAAK,YAAY,QAAQ,CAAC;eAC3B,KAAK,CAAC,IAAI,KAAK,2BAA2B;eAC1C,KAAK,CAAC,OAAO,CAAC,aAAa,KAAK,SAAS;YAAE,OAAO,IAAI,EAAE,CAAA;QAC7D,MAAM,EAAE,cAAc,EAAE,CAAA;QACxB,IAAI,CAAC,mBAAmB,CAAC,OAAO,EAAE,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC;YAAE,OAAO,IAAI,EAAE,CAAA;QAC9F,OAAO,IAAI,CAAA;IACb,CAAC,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-message.d.ts","sourceRoot":"","sources":["../../src/project-message.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAEhF,OAAO,KAAK,EAAgB,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAGjE;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAuB1F"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-message.js","sourceRoot":"","sources":["../../src/project-message.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAGhF,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAA;AAEzD;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAgB,EAAE,OAA0B;IAC/E,IAAI,UAAU,GAAG,CAAC,CAAA;IAClB,IAAI,QAAQ,GAAG,CAAC,CAAA;IAChB,MAAM,KAAK,GAAG,CAAC,MAA+B,EAAkB,EAAE;QAChE,IAAI,IAAgC,CAAA;QACpC,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC;YAC9C,IAAI,SAAS,GAAG,KAAK,CAAA;YACrB,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;gBAC3B,IAAI,UAAU,KAAK,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;oBACrC,IAAI,KAAK,CAAC,SAAS,KAAK,IAAI;wBAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,UAAU,uBAAuB,CAAC,CAAA;oBAC9G,SAAS,GAAG,EAAE,GAAG,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,CAAA;oBACzC,QAAQ,IAAI,CAAC,CAAA;gBACf,CAAC;gBACD,UAAU,IAAI,CAAC,CAAA;YACjB,CAAC;YACD,IAAI,SAAS,KAAK,KAAK;gBAAE,IAAI,KAAK,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;YACxD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAA;QACvB,CAAC;QACD,OAAO,IAAI,IAAI,MAAwB,CAAA;IACzC,CAAC,CAAA;IACD,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IACtC,IAAI,QAAQ,KAAK,OAAO,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAA;IAClH,OAAO,UAAU,CAAC,EAAE,GAAG,OAAO,EAAE,OAAO,EAAE,CAAC,CAAA;AAC5C,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projection.d.ts","sourceRoot":"","sources":["../../src/projection.ts"],"names":[],"mappings":"AAAA,mFAAmF;AAGnF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAA;AAChE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAA;AAGhF,8EAA8E;AAC9E,MAAM,WAAW,kBAAkB;IACjC,oEAAoE;IACpE,GAAG,EAAE,UAAU,CAAA;IACf,yEAAyE;IACzE,YAAY,EAAE,MAAM,EAAE,CAAA;CACvB;AAED,OAAO,QAAQ,gCAAgC,CAAC;IAC9C,UAAU,eAAe;QACvB;;;;;;WAMG;QACH,eAAe,EAAE;YAAE,OAAO,EAAE,kBAAkB,EAAE,CAAA;SAAE,CAAA;KACnD;CACF;AAYD,wFAAwF;AACxF,eAAO,MAAM,sBAAsB,EAAE,wBAAwB,CAAC,eAAe,CAkC5E,CAAA"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projection.js","sourceRoot":"","sources":["../../src/projection.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-compaction-image-offload",
3
3
  "description": "Durable image offload for image-capable routes: replace over-budget request images with placeholders and retry",
4
- "version": "0.2.2-rc.1",
4
+ "version": "0.2.2-rc.3",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -29,31 +29,34 @@
29
29
  "locale/*.json",
30
30
  "lib/index.js",
31
31
  "lib/types/**/*.js",
32
- "lib/types/**/*.d.ts"
32
+ "lib/types/**/*.d.ts",
33
+ "lib/types/**/*.d.ts.map",
34
+ "lib/**/*.js.map",
35
+ "src"
33
36
  ],
34
37
  "license": "MIT",
35
38
  "peerDependencies": {
36
39
  "@deepseek-ai/cordis": "~4.1.0",
37
- "@deepseek-ai/dsh-agent": "0.2.2-rc.1",
38
- "@deepseek-ai/dsh-compaction": "0.2.2-rc.1",
39
- "@deepseek-ai/dsh-llm": "0.2.2-rc.1",
40
- "@deepseek-ai/dsh-session": "0.2.2-rc.1"
40
+ "@deepseek-ai/dsh-agent": "0.2.2-rc.3",
41
+ "@deepseek-ai/dsh-compaction": "0.2.2-rc.3",
42
+ "@deepseek-ai/dsh-llm": "0.2.2-rc.3",
43
+ "@deepseek-ai/dsh-session": "0.2.2-rc.3"
41
44
  },
42
45
  "devDependencies": {
43
46
  "@deepseek-ai/cordis": "~4.1.0",
44
- "@deepseek-ai/dsh-agent": "0.2.2-rc.1",
45
- "@deepseek-ai/dsh-agent-loop": "0.2.2-rc.1",
46
- "@deepseek-ai/dsh-agent-loop-testkit": "0.2.2-rc.1",
47
- "@deepseek-ai/dsh-attachment": "0.2.2-rc.1",
48
- "@deepseek-ai/dsh-compaction": "0.2.2-rc.1",
49
- "@deepseek-ai/dsh-compaction-basic": "0.2.2-rc.1",
50
- "@deepseek-ai/dsh-llm": "0.2.2-rc.1",
51
- "@deepseek-ai/dsh-llm-deepseek": "0.2.2-rc.1",
52
- "@deepseek-ai/dsh-session": "0.2.2-rc.1",
53
- "@deepseek-ai/dsh-session-projection": "0.2.2-rc.1",
54
- "@deepseek-ai/dsh-token-meter": "0.2.2-rc.1"
47
+ "@deepseek-ai/dsh-agent": "0.2.2-rc.3",
48
+ "@deepseek-ai/dsh-agent-loop": "0.2.2-rc.3",
49
+ "@deepseek-ai/dsh-agent-loop-testkit": "0.2.2-rc.3",
50
+ "@deepseek-ai/dsh-attachment": "0.2.2-rc.3",
51
+ "@deepseek-ai/dsh-compaction": "0.2.2-rc.3",
52
+ "@deepseek-ai/dsh-compaction-basic": "0.2.2-rc.3",
53
+ "@deepseek-ai/dsh-llm": "0.2.2-rc.3",
54
+ "@deepseek-ai/dsh-llm-deepseek": "0.2.2-rc.3",
55
+ "@deepseek-ai/dsh-session": "0.2.2-rc.3",
56
+ "@deepseek-ai/dsh-session-projection": "0.2.2-rc.3",
57
+ "@deepseek-ai/dsh-token-meter": "0.2.2-rc.3"
55
58
  },
56
59
  "dependencies": {
57
- "@deepseek-ai/dsh-util-values": "0.2.2-rc.1"
60
+ "@deepseek-ai/dsh-util-values": "0.2.2-rc.3"
58
61
  }
59
62
  }
@@ -0,0 +1,46 @@
1
+ /** Select and log permanent image omissions in current model-request order. */
2
+
3
+ import type { ContentBlock } from '@deepseek-ai/dsh-llm'
4
+ import type { Session, SessionSeq } from '@deepseek-ai/dsh-session'
5
+ import type { ImageOffloadTarget } from './projection.ts'
6
+
7
+ /**
8
+ * Record one decision omitting the oldest retained input-image occurrences.
9
+ * Assistant nodes carry model output and are excluded. Image indexes count
10
+ * every occurrence, including previously offloaded ones, within each message.
11
+ * @param session - session whose next request applies the decision.
12
+ * @param sourceEventSeqs - input message events in the failed request's order.
13
+ * @param count - additional retained occurrences the adapter needs omitted.
14
+ * @returns whether any occurrence remained to offload.
15
+ */
16
+ export function offloadOldestImages(session: Session, sourceEventSeqs: readonly SessionSeq[], count: number): boolean {
17
+ const targets: ImageOffloadTarget[] = []
18
+ for (const seq of sourceEventSeqs) {
19
+ if (count === 0) break
20
+ // Existing Session history read; migration deferred. Current surface nodes index the durable log.
21
+ // oxlint-disable-next-line typescript/no-non-null-assertion, typescript/no-deprecated
22
+ const event = session.eventAt(seq)!
23
+ if (event.type !== 'user/message' && event.type !== 'tool/result') continue
24
+ // oxlint-disable-next-line typescript/no-non-null-assertion -- both input node types produce a message
25
+ const message = session.deriveEventMessage(event)!
26
+ const imageIndexes: number[] = []
27
+ let imageIndex = 0
28
+ const visit = (blocks: readonly ContentBlock[]): void => {
29
+ for (const block of blocks) {
30
+ if (count === 0) break
31
+ if (block.type === 'image') {
32
+ if (block.offloaded !== true) {
33
+ imageIndexes.push(imageIndex)
34
+ count -= 1
35
+ }
36
+ imageIndex += 1
37
+ }
38
+ }
39
+ }
40
+ visit(message.content)
41
+ if (imageIndexes.length > 0) targets.push({ seq, imageIndexes })
42
+ }
43
+ if (targets.length === 0) return false
44
+ session.append('image/offload', { targets })
45
+ return true
46
+ }
package/src/index.ts ADDED
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Image offload executor for the compaction seam. When an image-capable route
3
+ * fails a request with `IMAGE_OFFLOAD_REQUIRED`, the plugin records one
4
+ * `image/offload` decision selecting the oldest retained input occurrences
5
+ * and retries through the agent or compaction summary error waterfall. Every route
6
+ * sends placeholder text for those occurrences in subsequent requests.
7
+ *
8
+ * @module @deepseek-ai/dsh-compaction-image-offload
9
+ */
10
+
11
+ import type { Context } from '@deepseek-ai/cordis'
12
+ import type {} from '@deepseek-ai/dsh-compaction'
13
+ import type { RequestErrorAction } from '@deepseek-ai/dsh-agent'
14
+ import { IMAGE_OFFLOAD_REQUIRED_CODE, LlmError } from '@deepseek-ai/dsh-llm'
15
+ import { offloadOldestImages } from './image-offload.ts'
16
+ import { imageOffloadProjection } from './projection.ts'
17
+
18
+ export const name = 'compaction-image-offload'
19
+ export const inject = ['agents', 'sessions']
20
+
21
+ /**
22
+ * Mount agent and summary recovery listeners without configuration.
23
+ * @param ctx - the plugin context.
24
+ */
25
+ export function apply(ctx: Context): void {
26
+ ctx.sessions.registerMessageProjection(imageOffloadProjection)
27
+ ctx.on('agent/request-error', ({ agent, failure, signal }, next): Promise<RequestErrorAction> => {
28
+ if (failure.code !== IMAGE_OFFLOAD_REQUIRED_CODE || failure.offloadImages === undefined || signal.aborted) return next()
29
+ // A durable surface repair, not a provider retry: it spends no retry budget and logs no retry event.
30
+ if (!offloadOldestImages(agent.session, agent.session.surface.nodes, failure.offloadImages)) return next()
31
+ return Promise.resolve<RequestErrorAction>({ kind: 'retry' })
32
+ })
33
+ ctx.on('compaction/summary-error', ({ session, sourceEventSeqs, error, signal }, next) => {
34
+ if (!(error instanceof LlmError)
35
+ || error.code !== IMAGE_OFFLOAD_REQUIRED_CODE
36
+ || error.failure.offloadImages === undefined) return next()
37
+ signal?.throwIfAborted()
38
+ if (!offloadOldestImages(session, sourceEventSeqs, error.failure.offloadImages)) return next()
39
+ return true
40
+ })
41
+ }
@@ -0,0 +1,36 @@
1
+ /** Immutable application of the image occurrences recorded by image/offload. */
2
+
3
+ import type { ContentBlock, Message } from '@deepseek-ai/dsh-llm'
4
+ import { deepFreeze } from '@deepseek-ai/dsh-util-values'
5
+
6
+ /**
7
+ * Project selected image occurrences to immutable offloaded blocks.
8
+ * @param message - message projected before this decision.
9
+ * @param indexes - nonempty, strictly increasing depth-first image indexes.
10
+ * @returns an immutable message with the same identity and selected images marked.
11
+ * @throws when a selected occurrence is missing or already offloaded.
12
+ */
13
+ export function offloadMessageImages(message: Message, indexes: readonly number[]): Message {
14
+ let imageIndex = 0
15
+ let selected = 0
16
+ const visit = (blocks: readonly ContentBlock[]): ContentBlock[] => {
17
+ let next: ContentBlock[] | undefined
18
+ for (const [index, block] of blocks.entries()) {
19
+ let projected = block
20
+ if (block.type === 'image') {
21
+ if (imageIndex === indexes[selected]) {
22
+ if (block.offloaded === true) throw new Error(`image/offload: image index ${imageIndex} is already offloaded`)
23
+ projected = { ...block, offloaded: true }
24
+ selected += 1
25
+ }
26
+ imageIndex += 1
27
+ }
28
+ if (projected !== block) next ??= blocks.slice(0, index)
29
+ next?.push(projected)
30
+ }
31
+ return next ?? blocks as ContentBlock[]
32
+ }
33
+ const content = visit(message.content)
34
+ if (selected !== indexes.length) throw new Error(`image/offload: image index ${indexes[selected]} does not exist`)
35
+ return deepFreeze({ ...message, content })
36
+ }
@@ -0,0 +1,74 @@
1
+ /** Browser-safe durable image selection declaration and pure replay definition. */
2
+
3
+ import type { Message } from '@deepseek-ai/dsh-llm'
4
+ import type { SessionSeq } from '@deepseek-ai/dsh-session/types'
5
+ import type { SessionMessageProjection } from '@deepseek-ai/dsh-session/surface'
6
+ import { offloadMessageImages } from './project-message.ts'
7
+
8
+ /** Exact input-image occurrences selected by one durable offload decision. */
9
+ export interface ImageOffloadTarget {
10
+ /** Current message-producing event containing these occurrences. */
11
+ seq: SessionSeq
12
+ /** Zero-based depth-first image indexes within the immutable message. */
13
+ imageIndexes: number[]
14
+ }
15
+
16
+ declare module '@deepseek-ai/dsh-session/types' {
17
+ interface SessionEventMap {
18
+ /**
19
+ * Permanently omit selected input-image occurrences from subsequent model requests.
20
+ * Targets name unique current user/message or tool/result nodes. Nonempty, strictly
21
+ * increasing indexes count all images in depth-first order, including nested tool
22
+ * results and already omitted images. Message nodes and identities remain unchanged.
23
+ * @messageProjection
24
+ */
25
+ 'image/offload': { targets: ImageOffloadTarget[] }
26
+ }
27
+ }
28
+
29
+ /** Whether a durable value is a JSON object. */
30
+ function isRecord(value: unknown): value is Record<string, unknown> {
31
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
32
+ }
33
+
34
+ /** Whether a durable occurrence index or sequence is canonical. */
35
+ function isIndex(value: unknown): value is SessionSeq {
36
+ return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 && !Object.is(value, -0)
37
+ }
38
+
39
+ /** Atomic validation and reconstruction shared by live sessions and detached replay. */
40
+ export const imageOffloadProjection: SessionMessageProjection<'image/offload'> = {
41
+ type: 'image/offload',
42
+ project(event, context) {
43
+ const data: unknown = event.data
44
+ if (!isRecord(data) || Object.keys(data).length !== 1 || !Array.isArray(data['targets']) || data['targets'].length === 0) {
45
+ throw new Error('image/offload: data must contain a nonempty targets array')
46
+ }
47
+ const messages = new Map<SessionSeq, Message>()
48
+ const nodes = new Set(context.nodes)
49
+ for (const target of data['targets'] as unknown[]) {
50
+ if (!isRecord(target) || Object.keys(target).length !== 2 || !isIndex(target['seq'])
51
+ || !Array.isArray(target['imageIndexes']) || target['imageIndexes'].length === 0) {
52
+ throw new Error('image/offload: each target must contain a seq and nonempty imageIndexes')
53
+ }
54
+ const seq = target['seq']
55
+ if (messages.has(seq)) throw new Error(`image/offload: duplicate target seq ${seq}`)
56
+ if (!nodes.has(seq)) throw new Error(`image/offload: target seq ${seq} is not a current surface node`)
57
+ const source = context.events[seq - context.baseSeq]
58
+ if (source?.type !== 'user/message' && source?.type !== 'tool/result') {
59
+ throw new Error(`image/offload: target seq ${seq} must be user/message or tool/result`)
60
+ }
61
+ let previous = -1
62
+ for (const index of target['imageIndexes'] as unknown[]) {
63
+ if (!isIndex(index) || index <= previous) {
64
+ throw new Error('image/offload: imageIndexes must be strictly increasing non-negative safe integers')
65
+ }
66
+ previous = index
67
+ }
68
+ const message = context.messages.get(seq)
69
+ ?? (source.type === 'user/message' ? source.data : source.data.message)
70
+ messages.set(seq, offloadMessageImages(message, target['imageIndexes'] as number[]))
71
+ }
72
+ return messages
73
+ },
74
+ }