@deepseek-ai/dsh-session-title-llm 0.0.1-rc.1
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 +28 -0
- package/README.i18n.yaml +6 -0
- package/README.md +47 -0
- package/README.zh.md +47 -0
- package/lib/index.js +252 -0
- package/lib/invariant.js +23 -0
- package/lib/types/index.d.ts +93 -0
- package/lib/types/invariant.d.ts +16 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, DeepSeek
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.i18n.yaml
ADDED
|
@@ -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/session/session-title-llm/README.md
|
|
5
|
+
README.md: db03205cabf9f723ee9bf6ad122026d75cb92113
|
|
6
|
+
README.zh.md: a214caae84aeee7a3a5c90f55e8a01ea957b823f
|
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# @deepseek-ai/dsh-session-title-llm
|
|
2
|
+
|
|
3
|
+
English | [中文](README.zh.md)
|
|
4
|
+
|
|
5
|
+
Shared implementation policy for model-backed session-title providers. It resolves the auxiliary route, frames exact selected human messages as JSON, records the exact dispatchable request, applies a language-aware title instruction, enforces input and output budgets, composes timeout and caller cancellation, assembles the stream, and returns normalized text with exact source seqs plus the provider/model route used to generate it.
|
|
6
|
+
|
|
7
|
+
This package is a library, not a Cordis plugin. The provider plugins call `registerSessionTitleLlmProvider()` with their cadence and message selector; it validates shared config and delegates each revision to `generateSessionTitleWithLlm()`, so registration, route, prompt, cancellation, and validation behavior cannot drift between them.
|
|
8
|
+
|
|
9
|
+
## Route and failure contract
|
|
10
|
+
|
|
11
|
+
`provider` and `model` overrides are optional but must be supplied together as non-empty strings. Without that pair, the helper uses the exact provider/model route captured from the current session's logged `request/header`; an explicit refresh before any route exists therefore needs overrides. The helper measures the final JSON-framed user prompt, including seq fields, wrappers, and JSON escaping, against `maxInputBytes` before logging or dispatch instead of truncating it. Timeout and caller cancellation are rechecked while consuming the stream and after it completes, so a late successful result cannot be accepted even if an interceptor or adapter ignores abort. Malformed or empty output, tool calls, and non-stop finish reasons also reject; the session-title service decides whether that rejection is an automatic warning or an explicit caller failure.
|
|
12
|
+
|
|
13
|
+
After route and input validation, the helper appends a log-only `session/title-llm-request` event directly through `Session` before model dispatch. It contains the title-provider id, exact source seqs, route, system prompt, message list, and output-token cap used by the call. Persistence observes the record eagerly; the append does not need a title-specific marker, cast, settlement queue, or flush. The dispatched envelope is deep-frozen, carries `purpose: 'session-title'`, and deliberately lacks dsh-agent-loop's process-local request identity. Interceptors stay aligned with the record while loop-only reconstruction observers do not compare it with the conversation header. The DeepSeek adapter maps that purpose to thinking-disabled so the small output budget is reserved for visible title text; other adapters own their purpose-specific behavior. A later model failure leaves the request record intact; validation failures that never become dispatchable requests do not create one. The event stays outside derived model history.
|
|
14
|
+
|
|
15
|
+
## Configuration
|
|
16
|
+
|
|
17
|
+
Every field is required except the paired route override; there are no library defaults.
|
|
18
|
+
|
|
19
|
+
| Key | Contract |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `targetWords` | Positive target word count for non-CJK titles. |
|
|
22
|
+
| `targetCjkCharacters` | Positive target character count for Chinese, Japanese, or Korean titles. |
|
|
23
|
+
| `maxInputBytes` | Positive UTF-8 byte ceiling for the final JSON-framed user prompt. |
|
|
24
|
+
| `maxOutputTokens` | Positive auxiliary generation token cap. |
|
|
25
|
+
| `timeoutMs` | Positive end-to-end deadline within the runtime timer limit. |
|
|
26
|
+
| `provider`, `model` | Optional explicit route; both or neither. |
|
|
27
|
+
|
|
28
|
+
## Model Experience
|
|
29
|
+
|
|
30
|
+
### Auxiliary title request
|
|
31
|
+
|
|
32
|
+
#### What the model sees
|
|
33
|
+
|
|
34
|
+
The title model receives a fixed system instruction to return one concise unadorned title in the input language, including the configured word and CJK-character targets. Its one user message contains a JSON array of the exact selected human messages and their seqs.
|
|
35
|
+
|
|
36
|
+
#### Token effect
|
|
37
|
+
|
|
38
|
+
The auxiliary request consumes tokens according to selected input size and `maxOutputTokens`. It is separate from the main agent request and does not add title text or framing to agent history. DeepSeek title calls disable thinking; the main conversation retains its configured thinking mode.
|
|
39
|
+
|
|
40
|
+
#### KV Cache effect
|
|
41
|
+
|
|
42
|
+
No main-request invalidation. Auxiliary cache reuse is provider-specific; the fixed instruction is reusable while the JSON message array changes with each revision.
|
|
43
|
+
|
|
44
|
+
## Known Limitations and Deferred Work
|
|
45
|
+
|
|
46
|
+
- The helper accepts text output only and rejects tool calls; structured-output adapters and provider-specific prompt variants are not exposed.
|
|
47
|
+
- It enforces a byte ceiling for the whole framed user prompt rather than clipping individual messages or applying a retention policy.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# @deepseek-ai/dsh-session-title-llm
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 中文
|
|
4
|
+
|
|
5
|
+
由模型支持的会话标题提供方的共享实现策略。它解析辅助路由,将精确选中的用户消息封装为 JSON,记录可分发的确切请求,应用语言感知的标题指令,强制执行输入和输出预算,组合超时与调用方取消,组装流,并返回规范化文本,同时给出确切来源 seq 以及生成该文本时使用的提供方/模型路由。
|
|
6
|
+
|
|
7
|
+
此包是普通库,不是 Cordis 插件。提供方插件调用 `registerSessionTitleLlmProvider()`,传入各自节奏与消息选择器;该函数验证共享配置,并将每次修订委派给 `generateSessionTitleWithLlm()`,使各插件的注册、路由、提示词、取消与验证行为不会漂移。
|
|
8
|
+
|
|
9
|
+
## 路由与失败约定
|
|
10
|
+
|
|
11
|
+
`provider` 和 `model` 覆盖项都是可选的,但必须同时作为非空字符串提供。如果没有这一对取值,辅助模块会使用当前会话已记录 `request/header` 中捕获的确切提供方/模型路由;因此,在任何路由出现前显式刷新时必须提供覆盖项。辅助模块在记录或分发前,依据 `maxInputBytes` 检查最终 JSON 封装用户提示词的大小,包括 seq 字段、包装层与 JSON 转义,而不是将其截断。消费流期间和流完成后都会重新检查超时与调用方取消,因此即使 interceptor 或适配器忽略 abort,也不能接受迟到的成功结果。格式错误或空输出、工具调用和非 stop 结束原因同样会导致调用被拒绝;会话标题服务决定该拒绝属于自动警告还是显式调用方失败。
|
|
12
|
+
|
|
13
|
+
路由与输入验证完成后,辅助模块会在模型分发前直接通过 `Session` 追加仅写入日志的 `session/title-llm-request` 事件。它包含标题提供方 id、确切来源 seq、路由、系统提示词、消息列表,以及该调用使用的输出 token 上限。持久化会立即观察到该记录;追加不需要标题专属标记、类型断言、结算队列或刷写。分发的请求封套会深度冻结,携带 `purpose: 'session-title'`,且有意不包含 dsh-agent-loop 的进程本地请求身份。拦截器会与记录保持一致,而循环专用重建观察者不会把它与对话请求头比较。DeepSeek 适配器会根据该用途禁用思考,使少量输出预算全部用于可见标题文本;其他适配器负责自身用途专用行为。后续模型失败会保留请求记录;从未成为可分发请求的验证失败不会创建记录。该事件始终位于派生模型历史之外。
|
|
14
|
+
|
|
15
|
+
## 配置
|
|
16
|
+
|
|
17
|
+
除成对的路由覆盖项外,每个字段都必填;库不提供默认值。
|
|
18
|
+
|
|
19
|
+
| 键 | 约定 |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `targetWords` | 非 CJK 标题的正整数目标词数。 |
|
|
22
|
+
| `targetCjkCharacters` | 中文、日文或韩文标题的正整数目标字符数。 |
|
|
23
|
+
| `maxInputBytes` | 最终 JSON 封装用户提示词的正整数 UTF-8 字节上限。 |
|
|
24
|
+
| `maxOutputTokens` | 辅助生成的正整数 token 上限。 |
|
|
25
|
+
| `timeoutMs` | 运行时定时器限制内的正数端到端时限。 |
|
|
26
|
+
| `provider`, `model` | 可选显式路由;二者同时提供或同时省略。 |
|
|
27
|
+
|
|
28
|
+
## 模型体验
|
|
29
|
+
|
|
30
|
+
### 辅助标题请求
|
|
31
|
+
|
|
32
|
+
#### 模型看到的内容
|
|
33
|
+
|
|
34
|
+
标题模型会收到固定系统指令,要求以输入语言返回一个简洁且无装饰的标题;该指令包含所配置的词数与 CJK 字符数目标。它唯一的用户消息包含一个 JSON 数组,其中是精确选中的用户消息及其 seq。
|
|
35
|
+
|
|
36
|
+
#### Token 影响
|
|
37
|
+
|
|
38
|
+
辅助请求根据所选输入大小和 `maxOutputTokens` 消耗 token。它与主 agent(智能体)请求相互独立,不会向 agent 历史增加标题文本或封装内容。DeepSeek 标题调用会关闭思考;主对话保留自身配置的思考模式。
|
|
39
|
+
|
|
40
|
+
#### KV Cache 影响
|
|
41
|
+
|
|
42
|
+
不会使主请求的 KV Cache 失效。辅助缓存复用由提供方决定;固定指令可复用,而 JSON 消息数组会随每次修订变化。
|
|
43
|
+
|
|
44
|
+
## 已知限制与暂缓事项
|
|
45
|
+
|
|
46
|
+
- 辅助模块只接受文本输出,并拒绝工具调用;不公开结构化输出适配器或提供方专用提示词变体。
|
|
47
|
+
- 它对整个封装用户提示词强制执行字节上限,不会剪裁单条消息或应用保留策略。
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import z from "@deepseek-ai/schemastery";
|
|
2
|
+
import { BlockAssembler, createUserMessage, deepFreeze } from "@deepseek-ai/dsh-llm";
|
|
3
|
+
import { MAX_TIMER_DELAY_MS, deadline } from "@deepseek-ai/dsh-timeout";
|
|
4
|
+
import { SessionTitleProviderId, normalizeSessionTitle } from "@deepseek-ai/dsh-session-title";
|
|
5
|
+
//#region lib/types/index.js
|
|
6
|
+
/**
|
|
7
|
+
* Shared route, framing, timeout, assembly, and validation policy for
|
|
8
|
+
* model-backed session-title providers.
|
|
9
|
+
* @module @deepseek-ai/dsh-session-title-llm
|
|
10
|
+
*/
|
|
11
|
+
var __addDisposableResource = function(env, value, async) {
|
|
12
|
+
if (value !== null && value !== void 0) {
|
|
13
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
14
|
+
var dispose, inner;
|
|
15
|
+
if (async) {
|
|
16
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
17
|
+
dispose = value[Symbol.asyncDispose];
|
|
18
|
+
}
|
|
19
|
+
if (dispose === void 0) {
|
|
20
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
21
|
+
dispose = value[Symbol.dispose];
|
|
22
|
+
if (async) inner = dispose;
|
|
23
|
+
}
|
|
24
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
25
|
+
if (inner) dispose = function() {
|
|
26
|
+
try {
|
|
27
|
+
inner.call(this);
|
|
28
|
+
} catch (e) {
|
|
29
|
+
return Promise.reject(e);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
env.stack.push({
|
|
33
|
+
value,
|
|
34
|
+
dispose,
|
|
35
|
+
async
|
|
36
|
+
});
|
|
37
|
+
} else if (async) env.stack.push({ async: true });
|
|
38
|
+
return value;
|
|
39
|
+
};
|
|
40
|
+
var __disposeResources = (function(SuppressedError) {
|
|
41
|
+
return function(env) {
|
|
42
|
+
function fail(e) {
|
|
43
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
44
|
+
env.hasError = true;
|
|
45
|
+
}
|
|
46
|
+
var r, s = 0;
|
|
47
|
+
function next() {
|
|
48
|
+
while (r = env.stack.pop()) try {
|
|
49
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
50
|
+
if (r.dispose) {
|
|
51
|
+
var result = r.dispose.call(r.value);
|
|
52
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) {
|
|
53
|
+
fail(e);
|
|
54
|
+
return next();
|
|
55
|
+
});
|
|
56
|
+
} else s |= 1;
|
|
57
|
+
} catch (e) {
|
|
58
|
+
fail(e);
|
|
59
|
+
}
|
|
60
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
61
|
+
if (env.hasError) throw env.error;
|
|
62
|
+
}
|
|
63
|
+
return next();
|
|
64
|
+
};
|
|
65
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) {
|
|
66
|
+
var e = new Error(message);
|
|
67
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
68
|
+
});
|
|
69
|
+
/** Capability-owned timeout reason code for auxiliary title requests. */
|
|
70
|
+
const SESSION_TITLE_TIMEOUT_CODE = "SESSION_TITLE_TIMEOUT";
|
|
71
|
+
/** Shared Loader field schemas with no library defaults. */
|
|
72
|
+
const SessionTitleLlmConfigFields = {
|
|
73
|
+
targetWords: z.number().step(1).min(1).required(),
|
|
74
|
+
targetCjkCharacters: z.number().step(1).min(1).required(),
|
|
75
|
+
maxInputBytes: z.number().step(1).min(1).required(),
|
|
76
|
+
maxOutputTokens: z.number().step(1).min(1).required(),
|
|
77
|
+
timeoutMs: z.number().step(1).min(1).max(MAX_TIMER_DELAY_MS).required(),
|
|
78
|
+
provider: z.string(),
|
|
79
|
+
model: z.string()
|
|
80
|
+
};
|
|
81
|
+
/** Shared Loader schema with no library defaults. */
|
|
82
|
+
const SessionTitleLlmConfigSchema = z.object(SessionTitleLlmConfigFields);
|
|
83
|
+
/** Complete configuration key set for direct construction validation. */
|
|
84
|
+
const CONFIG_KEYS = new Set([
|
|
85
|
+
"targetWords",
|
|
86
|
+
"targetCjkCharacters",
|
|
87
|
+
"maxInputBytes",
|
|
88
|
+
"maxOutputTokens",
|
|
89
|
+
"timeoutMs",
|
|
90
|
+
"provider",
|
|
91
|
+
"model"
|
|
92
|
+
]);
|
|
93
|
+
/** Validate one positive integer limit. */
|
|
94
|
+
function assertPositiveInteger(name, value) {
|
|
95
|
+
if (!Number.isInteger(value) || value <= 0) throw new Error(`session-title-llm: ${name} must be a positive integer`);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Validate and detach required model-provider configuration.
|
|
99
|
+
* @param config - untrusted plugin configuration.
|
|
100
|
+
* @returns immutable policy with optional route absence preserved.
|
|
101
|
+
*/
|
|
102
|
+
function resolveSessionTitleLlmConfig(config) {
|
|
103
|
+
const candidate = config;
|
|
104
|
+
if (candidate === null || typeof candidate !== "object") throw new Error("session-title-llm: configuration is required");
|
|
105
|
+
const value = candidate;
|
|
106
|
+
for (const key of Object.keys(value)) if (!CONFIG_KEYS.has(key)) throw new Error(`session-title-llm: unknown config key "${key}"`);
|
|
107
|
+
assertPositiveInteger("targetWords", value.targetWords);
|
|
108
|
+
assertPositiveInteger("targetCjkCharacters", value.targetCjkCharacters);
|
|
109
|
+
assertPositiveInteger("maxInputBytes", value.maxInputBytes);
|
|
110
|
+
assertPositiveInteger("maxOutputTokens", value.maxOutputTokens);
|
|
111
|
+
assertPositiveInteger("timeoutMs", value.timeoutMs);
|
|
112
|
+
if (value.timeoutMs > MAX_TIMER_DELAY_MS) throw new Error(`session-title-llm: timeoutMs must not exceed ${MAX_TIMER_DELAY_MS}`);
|
|
113
|
+
const hasProvider = value.provider !== void 0;
|
|
114
|
+
if (hasProvider !== (value.model !== void 0)) throw new Error("session-title-llm: provider and model must be supplied together");
|
|
115
|
+
if (hasProvider && (typeof value.provider !== "string" || value.provider.length === 0 || typeof value.model !== "string" || value.model.length === 0)) throw new Error("session-title-llm: provider and model overrides must be non-empty strings");
|
|
116
|
+
return deepFreeze({ ...value });
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Register one model-backed provider through the shared configuration and call policy.
|
|
120
|
+
* @param ctx - context exposing the title and LLM services.
|
|
121
|
+
* @param config - untrusted required deployment policy.
|
|
122
|
+
* @param id - stable plugin id recorded with generated titles.
|
|
123
|
+
* @param automatic - provider-owned automatic generation cadence.
|
|
124
|
+
* @param selectMessages - exact source-message selection for one revision.
|
|
125
|
+
*/
|
|
126
|
+
function registerSessionTitleLlmProvider(ctx, config, id, automatic, selectMessages) {
|
|
127
|
+
const resolved = resolveSessionTitleLlmConfig(config);
|
|
128
|
+
const titleProvider = SessionTitleProviderId(id);
|
|
129
|
+
ctx.sessionTitle.register({
|
|
130
|
+
id: titleProvider,
|
|
131
|
+
automatic,
|
|
132
|
+
async generate(request) {
|
|
133
|
+
return generateSessionTitleWithLlm(ctx, resolved, request, selectMessages(request.messages), titleProvider);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
/** Resolve the explicit pair or the exact route captured from `request/header`. */
|
|
138
|
+
function resolveRoute(config, request) {
|
|
139
|
+
if (config.provider !== void 0 && config.model !== void 0) return {
|
|
140
|
+
provider: config.provider,
|
|
141
|
+
model: config.model
|
|
142
|
+
};
|
|
143
|
+
if (request.route === void 0) throw new Error("session-title-llm: no logged request route is available; configure provider and model together");
|
|
144
|
+
return request.route;
|
|
145
|
+
}
|
|
146
|
+
/** Stable language-aware system instruction shared by both provider plugins. */
|
|
147
|
+
function systemPrompt(config) {
|
|
148
|
+
return [
|
|
149
|
+
"Create a concise title for an AI coding-assistant session from the supplied human messages.",
|
|
150
|
+
"Return only the title on one line, **in plain text of natural language**, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.",
|
|
151
|
+
"Use the language of the messages.",
|
|
152
|
+
`Aim for about ${config.targetWords} words in non-CJK languages or ${config.targetCjkCharacters} CJK characters.`
|
|
153
|
+
].join("\n");
|
|
154
|
+
}
|
|
155
|
+
/** Frame exact messages as JSON so user text cannot break structural delimiters. */
|
|
156
|
+
function frameMessages(messages) {
|
|
157
|
+
return `Generate the session title from this JSON array of human messages:\n${JSON.stringify(messages)}`;
|
|
158
|
+
}
|
|
159
|
+
/** Translate terminal finish reasons into an auxiliary-call failure. */
|
|
160
|
+
function finishError(finish) {
|
|
161
|
+
switch (finish.kind) {
|
|
162
|
+
case "stop": return;
|
|
163
|
+
case "error":
|
|
164
|
+
case "aborted": {
|
|
165
|
+
const error = new Error(finish.failure.message);
|
|
166
|
+
error.code = finish.failure.code;
|
|
167
|
+
return error;
|
|
168
|
+
}
|
|
169
|
+
case "max-tokens": return /* @__PURE__ */ new Error("session-title-llm: title output reached maxOutputTokens");
|
|
170
|
+
case "tool-calls": return /* @__PURE__ */ new Error("session-title-llm: title model unexpectedly requested a tool");
|
|
171
|
+
default: return /* @__PURE__ */ new Error(`session-title-llm: unsupported finish reason "${String(finish.kind)}"`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Generate one title through the shared auxiliary LLM call.
|
|
176
|
+
* @param ctx - context exposing the registered LLM service.
|
|
177
|
+
* @param config - validated model-provider policy.
|
|
178
|
+
* @param request - service-owned session, route, message snapshot, and cancellation.
|
|
179
|
+
* @param selectedMessages - exact provider-selected subset to frame and attribute.
|
|
180
|
+
* @param titleProvider - registered title-provider identity recorded with the request.
|
|
181
|
+
* @returns normalized non-empty title, exact source seqs, and used model route.
|
|
182
|
+
*/
|
|
183
|
+
async function generateSessionTitleWithLlm(ctx, config, request, selectedMessages, titleProvider) {
|
|
184
|
+
const env_1 = {
|
|
185
|
+
stack: [],
|
|
186
|
+
error: void 0,
|
|
187
|
+
hasError: false
|
|
188
|
+
};
|
|
189
|
+
try {
|
|
190
|
+
request.signal.throwIfAborted();
|
|
191
|
+
if (selectedMessages.length === 0) throw new Error("session-title-llm: at least one source message is required");
|
|
192
|
+
const framedInput = frameMessages(selectedMessages);
|
|
193
|
+
const inputBytes = Buffer.byteLength(framedInput, "utf8");
|
|
194
|
+
if (inputBytes > config.maxInputBytes) throw new Error(`session-title-llm: input is ${inputBytes} bytes, exceeding maxInputBytes ${config.maxInputBytes}`);
|
|
195
|
+
const route = resolveRoute(config, request);
|
|
196
|
+
const messages = [createUserMessage({
|
|
197
|
+
content: [{
|
|
198
|
+
type: "text",
|
|
199
|
+
text: framedInput
|
|
200
|
+
}],
|
|
201
|
+
source: {
|
|
202
|
+
kind: "plugin",
|
|
203
|
+
plugin: "dsh-session-title-llm"
|
|
204
|
+
}
|
|
205
|
+
})];
|
|
206
|
+
const system = systemPrompt(config);
|
|
207
|
+
const callDeadline = __addDisposableResource(env_1, deadline(request.signal, config.timeoutMs, SESSION_TITLE_TIMEOUT_CODE), false);
|
|
208
|
+
const options = deepFreeze({
|
|
209
|
+
provider: route.provider,
|
|
210
|
+
model: route.model,
|
|
211
|
+
messages,
|
|
212
|
+
system,
|
|
213
|
+
maxTokens: config.maxOutputTokens,
|
|
214
|
+
sessionId: request.session.id,
|
|
215
|
+
purpose: "session-title",
|
|
216
|
+
signal: callDeadline.signal
|
|
217
|
+
});
|
|
218
|
+
request.session.append("session/title-llm-request", {
|
|
219
|
+
titleProvider,
|
|
220
|
+
messageSeqs: selectedMessages.map((message) => message.seq),
|
|
221
|
+
route,
|
|
222
|
+
system,
|
|
223
|
+
messages,
|
|
224
|
+
maxTokens: config.maxOutputTokens
|
|
225
|
+
});
|
|
226
|
+
callDeadline.signal.throwIfAborted();
|
|
227
|
+
const assembler = new BlockAssembler();
|
|
228
|
+
for await (const chunk of ctx.llm.stream(options)) {
|
|
229
|
+
callDeadline.signal.throwIfAborted();
|
|
230
|
+
assembler.push(chunk);
|
|
231
|
+
}
|
|
232
|
+
callDeadline.signal.throwIfAborted();
|
|
233
|
+
const terminalError = finishError(assembler.finish);
|
|
234
|
+
if (terminalError !== void 0) throw terminalError;
|
|
235
|
+
const blocks = assembler.blocks();
|
|
236
|
+
if (blocks.some((block) => block.type === "tool-call")) throw new Error("session-title-llm: title output must contain text only");
|
|
237
|
+
const title = normalizeSessionTitle(blocks.filter((block) => block.type === "text").map((block) => block.text).join(" "), Number.MAX_SAFE_INTEGER);
|
|
238
|
+
if (title.length === 0) throw new Error("session-title-llm: title model produced no text");
|
|
239
|
+
return {
|
|
240
|
+
title,
|
|
241
|
+
messageSeqs: selectedMessages.map((message) => message.seq),
|
|
242
|
+
model: route
|
|
243
|
+
};
|
|
244
|
+
} catch (e_1) {
|
|
245
|
+
env_1.error = e_1;
|
|
246
|
+
env_1.hasError = true;
|
|
247
|
+
} finally {
|
|
248
|
+
__disposeResources(env_1);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
//#endregion
|
|
252
|
+
export { SESSION_TITLE_TIMEOUT_CODE, SessionTitleLlmConfigFields, SessionTitleLlmConfigSchema, generateSessionTitleWithLlm, registerSessionTitleLlmProvider, resolveSessionTitleLlmConfig };
|
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region lib/types/invariant.js
|
|
2
|
+
/**
|
|
3
|
+
* Package-owned invariant companion for `@deepseek-ai/dsh-session-title-llm`.
|
|
4
|
+
* @module @deepseek-ai/dsh-session-title-llm/invariant
|
|
5
|
+
*/
|
|
6
|
+
const PACKAGE_NAME = "@deepseek-ai/dsh-session-title-llm";
|
|
7
|
+
/** Cordis companion plugin name. */
|
|
8
|
+
const name = "session-title-llm-invariant";
|
|
9
|
+
/** Service required before the companion can reserve package ownership. */
|
|
10
|
+
const inject = ["invariants"];
|
|
11
|
+
/**
|
|
12
|
+
* No runtime invariant: this stateless helper validates and freezes each auxiliary request before
|
|
13
|
+
* dispatch; deadline, stream, cited message seqs, and provider/model fields are checked synchronously and by tests.
|
|
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,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared route, framing, timeout, assembly, and validation policy for
|
|
3
|
+
* model-backed session-title providers.
|
|
4
|
+
* @module @deepseek-ai/dsh-session-title-llm
|
|
5
|
+
*/
|
|
6
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
7
|
+
import z from '@deepseek-ai/schemastery';
|
|
8
|
+
import type { Message } from '@deepseek-ai/dsh-llm';
|
|
9
|
+
import { SessionTitleProviderId } from '@deepseek-ai/dsh-session-title';
|
|
10
|
+
import type { SessionTitleAutomaticMode, SessionTitleModelProvenance, SessionTitleProviderRequest, SessionTitleProviderResult, SessionTitleUserMessage } from '@deepseek-ai/dsh-session-title';
|
|
11
|
+
/** Exact model-visible request recorded before one auxiliary title dispatch. */
|
|
12
|
+
export interface SessionTitleLlmRequestEventData {
|
|
13
|
+
/** Registered title-provider identity responsible for the request. */
|
|
14
|
+
readonly titleProvider: SessionTitleProviderId;
|
|
15
|
+
/** Exact human `user/message` seqs represented in `messages`. */
|
|
16
|
+
readonly messageSeqs: number[];
|
|
17
|
+
/** Exact auxiliary LLM route. */
|
|
18
|
+
readonly route: SessionTitleModelProvenance;
|
|
19
|
+
/** Exact auxiliary system prompt. */
|
|
20
|
+
readonly system: string;
|
|
21
|
+
/** Exact auxiliary message list. */
|
|
22
|
+
readonly messages: Message[];
|
|
23
|
+
/** Exact auxiliary output-token cap. */
|
|
24
|
+
readonly maxTokens: number;
|
|
25
|
+
}
|
|
26
|
+
declare module '@deepseek-ai/dsh-session/types' {
|
|
27
|
+
interface SessionEventMap {
|
|
28
|
+
/** Log-only pre-dispatch record of one session-title model request. */
|
|
29
|
+
'session/title-llm-request': SessionTitleLlmRequestEventData;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** Capability-owned timeout reason code for auxiliary title requests. */
|
|
33
|
+
export declare const SESSION_TITLE_TIMEOUT_CODE = "SESSION_TITLE_TIMEOUT";
|
|
34
|
+
/** Required deployment policy for one model-backed title plugin. */
|
|
35
|
+
export interface SessionTitleLlmConfig {
|
|
36
|
+
/** Target word count for non-CJK titles. */
|
|
37
|
+
readonly targetWords: number;
|
|
38
|
+
/** Target character count for Chinese, Japanese, or Korean titles. */
|
|
39
|
+
readonly targetCjkCharacters: number;
|
|
40
|
+
/** Maximum UTF-8 bytes in the final JSON-framed user prompt. */
|
|
41
|
+
readonly maxInputBytes: number;
|
|
42
|
+
/** Auxiliary generation output-token cap. */
|
|
43
|
+
readonly maxOutputTokens: number;
|
|
44
|
+
/** End-to-end auxiliary request deadline in milliseconds. */
|
|
45
|
+
readonly timeoutMs: number;
|
|
46
|
+
/** Optional explicit provider route; must be paired with `model`. */
|
|
47
|
+
readonly provider?: string;
|
|
48
|
+
/** Optional explicit model id; must be paired with `provider`. */
|
|
49
|
+
readonly model?: string;
|
|
50
|
+
}
|
|
51
|
+
/** Validated immutable model-provider policy. */
|
|
52
|
+
export interface ResolvedSessionTitleLlmConfig extends SessionTitleLlmConfig {
|
|
53
|
+
}
|
|
54
|
+
/** Shared Loader field schemas with no library defaults. */
|
|
55
|
+
export declare const SessionTitleLlmConfigFields: {
|
|
56
|
+
targetWords: z<number, number>;
|
|
57
|
+
targetCjkCharacters: z<number, number>;
|
|
58
|
+
maxInputBytes: z<number, number>;
|
|
59
|
+
maxOutputTokens: z<number, number>;
|
|
60
|
+
timeoutMs: z<number, number>;
|
|
61
|
+
provider: z<string, string>;
|
|
62
|
+
model: z<string, string>;
|
|
63
|
+
};
|
|
64
|
+
/** Shared Loader schema with no library defaults. */
|
|
65
|
+
export declare const SessionTitleLlmConfigSchema: z<SessionTitleLlmConfig>;
|
|
66
|
+
/**
|
|
67
|
+
* Validate and detach required model-provider configuration.
|
|
68
|
+
* @param config - untrusted plugin configuration.
|
|
69
|
+
* @returns immutable policy with optional route absence preserved.
|
|
70
|
+
*/
|
|
71
|
+
export declare function resolveSessionTitleLlmConfig(config: SessionTitleLlmConfig): ResolvedSessionTitleLlmConfig;
|
|
72
|
+
/** Select the provider-owned message subset from one fixed service revision. */
|
|
73
|
+
export type SessionTitleLlmMessageSelector = (messages: readonly SessionTitleUserMessage[]) => readonly SessionTitleUserMessage[];
|
|
74
|
+
/**
|
|
75
|
+
* Register one model-backed provider through the shared configuration and call policy.
|
|
76
|
+
* @param ctx - context exposing the title and LLM services.
|
|
77
|
+
* @param config - untrusted required deployment policy.
|
|
78
|
+
* @param id - stable plugin id recorded with generated titles.
|
|
79
|
+
* @param automatic - provider-owned automatic generation cadence.
|
|
80
|
+
* @param selectMessages - exact source-message selection for one revision.
|
|
81
|
+
*/
|
|
82
|
+
export declare function registerSessionTitleLlmProvider(ctx: Context, config: SessionTitleLlmConfig, id: string, automatic: SessionTitleAutomaticMode, selectMessages: SessionTitleLlmMessageSelector): void;
|
|
83
|
+
/**
|
|
84
|
+
* Generate one title through the shared auxiliary LLM call.
|
|
85
|
+
* @param ctx - context exposing the registered LLM service.
|
|
86
|
+
* @param config - validated model-provider policy.
|
|
87
|
+
* @param request - service-owned session, route, message snapshot, and cancellation.
|
|
88
|
+
* @param selectedMessages - exact provider-selected subset to frame and attribute.
|
|
89
|
+
* @param titleProvider - registered title-provider identity recorded with the request.
|
|
90
|
+
* @returns normalized non-empty title, exact source seqs, and used model route.
|
|
91
|
+
*/
|
|
92
|
+
export declare function generateSessionTitleWithLlm(ctx: Context, config: ResolvedSessionTitleLlmConfig, request: SessionTitleProviderRequest, selectedMessages: readonly SessionTitleUserMessage[], titleProvider: SessionTitleProviderId): Promise<SessionTitleProviderResult>;
|
|
93
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned invariant companion for `@deepseek-ai/dsh-session-title-llm`.
|
|
3
|
+
* @module @deepseek-ai/dsh-session-title-llm/invariant
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
6
|
+
/** Cordis companion plugin name. */
|
|
7
|
+
export declare const name = "session-title-llm-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
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deepseek-ai/dsh-session-title-llm",
|
|
3
|
+
"description": "Shared LLM generation policy for DeepSeek Harness session-title providers",
|
|
4
|
+
"version": "0.0.1-rc.1",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "restricted"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
|
|
11
|
+
"directory": "packages/session/session-title-llm"
|
|
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": "BSD-3-Clause",
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
|
|
36
|
+
"@deepseek-ai/dsh-llm": "^0.0.1-rc.1",
|
|
37
|
+
"@deepseek-ai/dsh-session-title": "^0.0.1-rc.1",
|
|
38
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.1",
|
|
39
|
+
"@deepseek-ai/dsh-timeout": "^0.0.1-rc.1",
|
|
40
|
+
"@deepseek-ai/dsh-session": "^0.0.1-rc.1"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@deepseek-ai/schemastery": "^3.18.1-rc.1"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
|
|
47
|
+
"@deepseek-ai/dsh-session": "^0.0.1-rc.1",
|
|
48
|
+
"@deepseek-ai/dsh-session-title": "^0.0.1-rc.1",
|
|
49
|
+
"@deepseek-ai/dsh-timeout": "^0.0.1-rc.1",
|
|
50
|
+
"@deepseek-ai/dsh-llm": "^0.0.1-rc.1",
|
|
51
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.1"
|
|
52
|
+
}
|
|
53
|
+
}
|