@hasna-internal/kai-spill 0.1.1-rc.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DeepSeek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,6 @@
1
+ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
2
+ # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
+ # after editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/spill/spill/README.md
5
+ README.md: f36dccfa1e6f1f21e80c0b0aa97ee150a0f5fd45
6
+ README.zh.md: 64f0a99bd1cdb10af2bbe0007de802d8ce658b7a
package/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # @hasna-internal/kai-spill
2
+
3
+ English | [中文](README.zh.md)
4
+
5
+ The **`SpillStore`** (`ctx.spillStore`) defines WHAT a spill backend does — persist a tool's oversized text and return a model-facing locator plus retrieval guidance — without saying HOW.
6
+
7
+ This package is one third of the spill capability, split so each concern evolves (and swaps) independently:
8
+
9
+ | Package | Role |
10
+ |---|---|
11
+ | `@hasna-internal/kai-spill` (this) | Service Definition: abstract service + vocabulary types |
12
+ | `@hasna-internal/kai-spill-local` | Service Provider: private session-scoped files on the host filesystem |
13
+ | `@hasna-internal/kai-spill-policy` | Consumer: the tool-result policy that spills oversized final results |
14
+
15
+ The split mirrors the shell/fs seams. A future remote or virtual backend (e.g. a `spill://…` URI, a database key, or a backend-specific retrieval tool) implements this Service Definition without touching the policy plugin.
16
+
17
+ ## Service API (`ctx.spillStore`)
18
+
19
+ | Member | Semantics |
20
+ |---|---|
21
+ | `saveText(input)` | Persist `input.content` verbatim; resolves with a `SpillRef` (opaque locator, exact bytes written, and retrieval hint). **Rejects on a real storage failure** (permissions, ENOSPC, backend unavailable) — the caller decides how to degrade. |
22
+
23
+ Storage is grouped by the request's `owner` session as a save-time namespace; the backend chooses its own private representation and may derive names from — never trust as a path — the caller's `suggestedName`. The seam owns storage only: NO retention policy (that is [`@hasna-internal/kai-output-retention`](../../util/output-retention)), NO tool-result replacement (that is `@hasna-internal/kai-spill-policy`), NO retrieval/search API (the backend's `retrievalHint` tells the model what to do with the locator).
24
+
25
+ ## Vocabulary
26
+
27
+ `SaveTextSpill` (owner, source, suggestedName, content) is the request; `SpillRef` (locator, bytes, retrievalHint) is the result. `SpillLocator` is [branded](../../util/brand) and rendered to the model as an opaque string — a local path for `dsh-spill-local`, but a future backend may return a URI, key, or command token without changing policy/tool consumers. `SpillOwner.sessionId` is the save-time storage namespace: forked sessions inherit existing locators from the seeded log without copying or re-owning them, and new spills after the fork use the child session id. `SpillSource` records the producing `toolName`, `callId`, and `label` for backend naming and inspection, not access control. See `src/types.ts` for the full contracts.
28
+
29
+ See the [tool output spill Agent Note](../../../.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md) for the design rationale, including why creation belongs to the runtime spill seam rather than the model-facing `write` tool.
30
+
31
+ ## Model Experience
32
+
33
+ Indirectly, through spill consumers that render a backend locator and retrieval guidance.
34
+
35
+ #### KV Cache effect
36
+
37
+ No direct invalidation; the named consumer owns any request-prefix changes.
38
+
39
+ ## Known Limitations and Deferred Work
40
+
41
+ - **The seam has no retrieval or deletion API** — consumers can only render the backend's locator and guidance; lifecycle and access semantics remain backend-specific.
42
+ - **Storage is not access control** — `SpillOwner` namespaces writes but does not authorize reads of a locator; each backend and retrieval consumer must enforce its own boundary.
package/README.zh.md ADDED
@@ -0,0 +1,42 @@
1
+ # @hasna-internal/kai-spill
2
+
3
+ [English](README.md) | 中文
4
+
5
+ **`SpillStore`**(`ctx.spillStore`)定义 spill 后端做什么,即持久化某个工具过大的文本,并返回面向模型的定位信息与取回指引;它不规定如何实现。
6
+
7
+ 该包是 spill 能力的三个组成部分之一。拆分后,各项关注点可独立演进和替换:
8
+
9
+ | 包 | 职责 |
10
+ |---|---|
11
+ | `@hasna-internal/kai-spill`(本包) | Service Definition:抽象服务与词汇类型 |
12
+ | `@hasna-internal/kai-spill-local` | Service Provider:位于宿主文件系统中的私有会话级文件 |
13
+ | `@hasna-internal/kai-spill-policy` | Consumer:对过大最终结果执行 spill 的工具结果策略 |
14
+
15
+ 这种拆分方式与 shell/fs seam 相同。未来的远程或虚拟后端(例如 `spill://…` URI、数据库键或后端专用取回工具)可实现此 Service Definition,无需修改策略插件。
16
+
17
+ ## 服务 API(`ctx.spillStore`)
18
+
19
+ | 成员 | 语义 |
20
+ |---|---|
21
+ | `saveText(input)` | 逐字保存 `input.content`;成功时返回 `SpillRef`(不透明定位信息、写入的精确字节数和取回指引)。**发生真实存储故障时,调用会以拒绝状态结束**(权限、ENOSPC、后端不可用);由调用方决定如何降级。 |
22
+
23
+ 存储操作以请求的 `owner` 会话作为保存时命名空间进行分组;后端自行选择私有表示,并可以从调用方的 `suggestedName` 派生名称,但绝不能将其当作可信路径。该 seam 只负责存储:不提供保留策略(由 [`@hasna-internal/kai-output-retention`](../../util/output-retention) 负责),不替换工具结果(由 `@hasna-internal/kai-spill-policy` 负责),也不提供取回/搜索 API(后端的 `retrievalHint` 会告诉模型如何使用定位信息)。
24
+
25
+ ## 词汇
26
+
27
+ `SaveTextSpill`(owner、source、suggestedName、content)是请求;`SpillRef`(locator、bytes、retrievalHint)是结果。`SpillLocator` 是[带品牌类型](../../util/brand)的值,并以不透明字符串的形式呈现给模型;对 `dsh-spill-local` 而言它是本地路径,但未来的后端可以返回 URI、键或命令 token,无需修改策略/工具消费方。`SpillOwner.sessionId` 是保存时存储命名空间:fork 后的会话会从种子日志继承现有定位信息,无需复制或更改其归属;fork 后新产生的 spill 使用子会话 id。`SpillSource` 记录产生该 spill 的 `toolName`、`callId` 和 `label`,供后端命名和检查使用,不用于访问控制。完整约定见 `src/types.ts`。
28
+
29
+ 设计原理见[工具输出 spill Agent Note](../../../.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.zh.md),其中说明了为什么创建操作应由运行时 spill seam 而非面向模型的 `write` 工具承担。
30
+
31
+ ## 模型体验
32
+
33
+ 通过渲染后端定位信息和取回指引的 spill 消费方间接影响模型。
34
+
35
+ #### KV Cache 影响
36
+
37
+ 不会直接导致 KV Cache 失效;请求前缀变更由上述消费方负责。
38
+
39
+ ## 已知限制与暂缓事项
40
+
41
+ - **该 seam 没有取回或删除 API**:消费方只能渲染后端的定位信息与指引;生命周期和访问语义仍由后端自行决定。
42
+ - **存储不等于访问控制**:`SpillOwner` 会区分写入命名空间,但不会授予通过定位信息读取内容的权限;每个后端和取回消费方都必须自行强制执行访问边界。
package/lib/index.js ADDED
@@ -0,0 +1,58 @@
1
+ import { Service } from "@deepseek-ai/cordis";
2
+ //#region lib/types/types.js
3
+ /**
4
+ * Vocabulary for the spill storage Service Definition. Types only — the abstract service
5
+ * lives in `./index.ts`, implementations in sibling packages
6
+ * (`@hasna-internal/kai-spill-local` first).
7
+ *
8
+ * @module @hasna-internal/kai-spill/types
9
+ */
10
+ /**
11
+ * Brand a string as a {@link SpillLocator}.
12
+ *
13
+ * @param locator The backend-produced locator string to brand.
14
+ * @returns The branded spill locator.
15
+ */
16
+ function SpillLocator(locator) {
17
+ return locator;
18
+ }
19
+ //#endregion
20
+ //#region lib/types/index.js
21
+ /**
22
+ * Service Definition for the spill storage capability seam (`ctx.spillStore`): an abstract service defining WHAT a
23
+ * spill backend does — persist a tool's oversized text and return a model-facing
24
+ * locator plus retrieval guidance — without saying HOW. Implementations
25
+ * subclass {@link SpillStore} and register as the `spillStore` service;
26
+ * `@hasna-internal/kai-spill-local` (host filesystem) is the first.
27
+ *
28
+ * The Service Definition is deliberately minimal: `saveText` and nothing else. It owns NO
29
+ * retention policy (that is `@hasna-internal/kai-output-retention`), NO tool-result
30
+ * replacement (that is `@hasna-internal/kai-spill-policy`), and NO retrieval or
31
+ * search API. The backend supplies the locator and retrieval hint appropriate
32
+ * for its storage substrate.
33
+ *
34
+ * @module @hasna-internal/kai-spill
35
+ */
36
+ /**
37
+ * Abstract spill storage service. Subclass, implement {@link saveText}, and load
38
+ * the subclass as a plugin — it registers as `ctx.spillStore` (one
39
+ * implementation per context; loading a second throws, cordis' standard
40
+ * duplicate-service behavior).
41
+ *
42
+ * Semantics every implementation must honor:
43
+ * - {@link saveText} persists the FULL `content` verbatim and returns an opaque
44
+ * locator, exact byte length, and model-facing retrieval guidance.
45
+ * - Storage is scoped by the request's {@link SaveTextSpill.owner} session; the
46
+ * backend chooses a private (not world-readable) location and a collision-free
47
+ * name derived from — never equal to — the caller's `suggestedName`.
48
+ * - `saveText` REJECTS on a real storage failure (permissions, ENOSPC, backend
49
+ * unavailable); the caller decides how to degrade (the spill policy treats a
50
+ * rejection as best-effort and keeps the inline result).
51
+ */
52
+ var SpillStore = class extends Service {
53
+ constructor(ctx) {
54
+ super(ctx, "spillStore");
55
+ }
56
+ };
57
+ //#endregion
58
+ export { SpillLocator, SpillStore, SpillStore as default };
@@ -0,0 +1,23 @@
1
+ //#region lib/types/invariant.js
2
+ /**
3
+ * Package-owned invariant companion for `@hasna-internal/kai-spill`.
4
+ * @module @hasna-internal/kai-spill/invariant
5
+ */
6
+ const PACKAGE_NAME = "@hasna-internal/kai-spill";
7
+ /** Cordis companion plugin name. */
8
+ const name = "spill-invariant";
9
+ /** Service required before the companion can reserve package ownership. */
10
+ const inject = ["invariants"];
11
+ /**
12
+ * No runtime invariant: this package exposes no independent event sequence or mutable data relation
13
+ * beyond contracts enforced at its owning seam.
14
+ */
15
+ const install = () => {};
16
+ /**
17
+ * Register this package's invariant companion.
18
+ * @param ctx - Cordis context carrying the invariant service.
19
+ * @returns the installed registration's disposer after setup succeeds.
20
+ */
21
+ const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
22
+ //#endregion
23
+ export { apply, inject, name };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Service Definition for the spill storage capability seam (`ctx.spillStore`): an abstract service defining WHAT a
3
+ * spill backend does — persist a tool's oversized text and return a model-facing
4
+ * locator plus retrieval guidance — without saying HOW. Implementations
5
+ * subclass {@link SpillStore} and register as the `spillStore` service;
6
+ * `@hasna-internal/kai-spill-local` (host filesystem) is the first.
7
+ *
8
+ * The Service Definition is deliberately minimal: `saveText` and nothing else. It owns NO
9
+ * retention policy (that is `@hasna-internal/kai-output-retention`), NO tool-result
10
+ * replacement (that is `@hasna-internal/kai-spill-policy`), and NO retrieval or
11
+ * search API. The backend supplies the locator and retrieval hint appropriate
12
+ * for its storage substrate.
13
+ *
14
+ * @module @hasna-internal/kai-spill
15
+ */
16
+ import { Context, Service } from '@deepseek-ai/cordis';
17
+ import type { SaveTextSpill, SpillRef } from './types.ts';
18
+ export { SpillLocator } from './types.ts';
19
+ export type { SaveTextSpill, SpillOwner, SpillRef, SpillSource } from './types.ts';
20
+ declare module '@deepseek-ai/cordis' {
21
+ interface Context {
22
+ spillStore: SpillStore;
23
+ }
24
+ }
25
+ /**
26
+ * Abstract spill storage service. Subclass, implement {@link saveText}, and load
27
+ * the subclass as a plugin — it registers as `ctx.spillStore` (one
28
+ * implementation per context; loading a second throws, cordis' standard
29
+ * duplicate-service behavior).
30
+ *
31
+ * Semantics every implementation must honor:
32
+ * - {@link saveText} persists the FULL `content` verbatim and returns an opaque
33
+ * locator, exact byte length, and model-facing retrieval guidance.
34
+ * - Storage is scoped by the request's {@link SaveTextSpill.owner} session; the
35
+ * backend chooses a private (not world-readable) location and a collision-free
36
+ * name derived from — never equal to — the caller's `suggestedName`.
37
+ * - `saveText` REJECTS on a real storage failure (permissions, ENOSPC, backend
38
+ * unavailable); the caller decides how to degrade (the spill policy treats a
39
+ * rejection as best-effort and keeps the inline result).
40
+ */
41
+ export declare abstract class SpillStore extends Service {
42
+ constructor(ctx: Context);
43
+ /**
44
+ * Persist `input.content` to a session-scoped spill artifact.
45
+ * @param input - the owner, caller-supplied source fields, suggested name, and full text to save.
46
+ * @returns the saved artifact's {@link SpillRef}; rejects on a storage failure.
47
+ */
48
+ abstract saveText(input: SaveTextSpill): Promise<SpillRef>;
49
+ }
50
+ export default SpillStore;
51
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Package-owned invariant companion for `@hasna-internal/kai-spill`.
3
+ * @module @hasna-internal/kai-spill/invariant
4
+ */
5
+ import type { Context } from '@deepseek-ai/cordis';
6
+ /** Cordis companion plugin name. */
7
+ export declare const name = "spill-invariant";
8
+ /** Service required before the companion can reserve package ownership. */
9
+ export declare const inject: string[];
10
+ /**
11
+ * Register this package's invariant companion.
12
+ * @param ctx - Cordis context carrying the invariant service.
13
+ * @returns the installed registration's disposer after setup succeeds.
14
+ */
15
+ export declare const apply: (ctx: Context) => Promise<() => void>;
16
+ //# sourceMappingURL=invariant.d.ts.map
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Vocabulary for the spill storage Service Definition. Types only — the abstract service
3
+ * lives in `./index.ts`, implementations in sibling packages
4
+ * (`@hasna-internal/kai-spill-local` first).
5
+ *
6
+ * @module @hasna-internal/kai-spill/types
7
+ */
8
+ import type { Branded } from '@hasna-internal/kai-brand';
9
+ import type { CallId } from '@hasna-internal/kai-llm';
10
+ import type { SessionId } from '@hasna-internal/kai-session';
11
+ /**
12
+ * Opaque model-facing handle for one spilled artifact. A local backend may use a
13
+ * filesystem path; a remote or database backend may use a URI or key. Consumers
14
+ * render it with {@link SpillRef.retrievalHint}, but do not parse it.
15
+ */
16
+ export type SpillLocator = Branded<'SpillLocator'>;
17
+ /**
18
+ * Brand a string as a {@link SpillLocator}.
19
+ *
20
+ * @param locator The backend-produced locator string to brand.
21
+ * @returns The branded spill locator.
22
+ */
23
+ export declare function SpillLocator(locator: string): SpillLocator;
24
+ /**
25
+ * Save-time storage namespace for a spilled artifact. The session id lets a
26
+ * backend group storage under the producing session, but the returned
27
+ * {@link SpillLocator} is the model-facing handle. Forked sessions inherit
28
+ * locators already present in the seeded log; those artifacts are not copied or
29
+ * re-owned, and spills produced after the fork use the child session id.
30
+ */
31
+ export interface SpillOwner {
32
+ sessionId: SessionId;
33
+ }
34
+ /**
35
+ * Tool and call that produced one spilled artifact — recorded by the backend for a readable
36
+ * filename and inspection. Not interpreted for access control; purely
37
+ * descriptive.
38
+ */
39
+ export interface SpillSource {
40
+ /** The tool whose result was spilled (e.g. `web_fetch`). */
41
+ toolName: string;
42
+ /** The model-issued call id the result belongs to. */
43
+ callId: CallId;
44
+ /** A short human label for the artifact (e.g. `result`). */
45
+ label: string;
46
+ }
47
+ /** One request to persist text to a spill artifact. */
48
+ export interface SaveTextSpill {
49
+ owner: SpillOwner;
50
+ source: SpillSource;
51
+ /**
52
+ * A caller-suggested base name (e.g. `web_fetch.txt`). The backend sanitizes
53
+ * it to a single safe path segment before use — it is a hint, never a path.
54
+ */
55
+ suggestedName: string;
56
+ /** The full text to persist (UTF-8). */
57
+ content: string;
58
+ }
59
+ /** A saved spill artifact: its locator, byte length, and backend-specific retrieval guidance. */
60
+ export interface SpillRef {
61
+ locator: SpillLocator;
62
+ bytes: number;
63
+ retrievalHint: string;
64
+ }
65
+ //# sourceMappingURL=types.d.ts.map
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@hasna-internal/kai-spill",
3
+ "description": "Abstract spill storage seam (ctx.spillStore) for the DeepSeek Harness — save oversized tool text and return a retrieval locator",
4
+ "version": "0.1.1-rc.2",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
11
+ "directory": "packages/spill/spill"
12
+ },
13
+ "type": "module",
14
+ "main": "lib/index.js",
15
+ "types": "lib/types/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./lib/types/index.d.ts",
19
+ "default": "./lib/index.js"
20
+ },
21
+ "./invariant": {
22
+ "types": "./lib/types/invariant.d.ts",
23
+ "default": "./lib/invariant.js"
24
+ },
25
+ "./src/*": "./src/*",
26
+ "./package.json": "./package.json"
27
+ },
28
+ "files": [
29
+ "lib/index.js",
30
+ "lib/invariant.js",
31
+ "lib/types/**/*.d.ts"
32
+ ],
33
+ "license": "MIT",
34
+ "peerDependencies": {
35
+ "@hasna-internal/kai-llm": "^0.1.1-rc.2",
36
+ "@hasna-internal/kai-invariants": "^0.1.1-rc.2",
37
+ "@hasna-internal/kai-brand": "^0.1.1-rc.2",
38
+ "@deepseek-ai/cordis": "^4.0.1",
39
+ "@hasna-internal/kai-session": "^0.1.1-rc.2"
40
+ },
41
+ "devDependencies": {
42
+ "@hasna-internal/kai-brand": "^0.1.1-rc.2",
43
+ "@hasna-internal/kai-llm": "^0.1.1-rc.2",
44
+ "@deepseek-ai/cordis": "^4.0.1",
45
+ "@hasna-internal/kai-invariants": "^0.1.1-rc.2",
46
+ "@hasna-internal/kai-session": "^0.1.1-rc.2"
47
+ }
48
+ }