@gt-fe/eap-runtime 0.0.28 → 0.0.29

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.
@@ -18,9 +18,10 @@ import type { MessageService } from '../session/message.service.js';
18
18
  import type { SessionService, SessionRecord, MessageRecord } from '../session/session.service.js';
19
19
  import { type ILlmTurnLog, type LlmTurnIndexEntry, type LlmTurnLogKind, type LlmTurnRecord } from '../debug/llm-turn-log.js';
20
20
  import { type SessionCompressionOptions } from '../session/apply-session-compression.js';
21
+ import type { ILLMProvider } from '../graph/nodes/llm-node.js';
21
22
  import type { SSEEvent } from '../sse/contracts/sse-emitter.contract.js';
22
23
  import type { TaskService } from '../task/task.service.js';
23
- import type { ExecuteParams, ExecuteResult, ForkParams, ForkOverrides, ForkResult, HitlResponse, ResumeHitlResult, SeedSessionMessagesParams, SeedSessionMessagesResult } from './types.js';
24
+ import type { ExecuteParams, ExecuteResult, ForkParams, ForkOverrides, ForkResult, HitlResponse, ResumeHitlResult, SeedSessionMessagesParams, SeedSessionMessagesResult, GenerateSessionTitleParams, GenerateSessionTitleResult } from './types.js';
24
25
  export interface ForkRelationRecord {
25
26
  originalTraceId: string;
26
27
  forkedTraceId: string;
@@ -50,6 +51,11 @@ export interface AgentRuntimeDeps {
50
51
  recordForkRelation?: RecordForkRelation;
51
52
  /** 历史压缩;默认开。EAP_RUNTIME_COMPRESSION=0 关闭 */
52
53
  sessionCompression?: SessionCompressionOptions;
54
+ /**
55
+ * 会话标题 completion。与图引擎共用同一实例。
56
+ * 未注入时回退 `sessionCompression.provider`。
57
+ */
58
+ llmProvider?: ILLMProvider;
53
59
  /** 长期记忆注入;默认关。见 EAP_RUNTIME_MEMORY */
54
60
  sessionMemory?: import('../session/apply-session-memory.js').SessionMemoryOptions;
55
61
  /** 未注入时不启动整图超时(测试桩) */
@@ -114,6 +120,11 @@ export declare class AgentRuntime {
114
120
  listConversations(options?: {
115
121
  userId?: string;
116
122
  }): Promise<SessionRecord[]>;
123
+ /**
124
+ * 根据该 sessionId 的 Message 生成会话标题。不写库、不进 execute。
125
+ * 失败返回 `{ title: null }`。
126
+ */
127
+ generateSessionTitle(params: GenerateSessionTitleParams): Promise<GenerateSessionTitleResult>;
117
128
  private resolveTurnLog;
118
129
  /** 同步执行 Graph(经 InvokeOrchestrator,内含 loadedTools 注入) */
119
130
  execute(params: ExecuteParams): Promise<ExecuteResult>;
@@ -18,6 +18,8 @@ import { createFileLlmTurnLogFromEnv, } from '../debug/llm-turn-log.js';
18
18
  import { buildHitlWaitingTaskOutput, buildTurnTranscriptFromStreamEvents, loadModelFacingHistory, turnGraphMessagesAfterCompression, extractHitlThreadId, extractStreamReasoning, applyStreamTextDelta, createStreamAssistantBuckets, visibleStreamAssistantText, streamAssistantTexts, messageRecordsToGraphMessages, persistHitlWaitingPlaceholder, persistTurnTranscript, persistUserTurnMessage, resolveCheckpointThreadId, appendTerminalToolResult, } from '../session/persist-turn-messages.js';
19
19
  import { iterSessionCompression, withCompressionSummaryModel, } from '../session/apply-session-compression.js';
20
20
  import { applySessionMemory } from '../session/apply-session-memory.js';
21
+ import { buildSessionTitleUserPayload, invokeSessionTitle, } from '../session/session-title.js';
22
+ import { EchoLLMProvider } from '../providers/echo-llm-provider.js';
21
23
  import { resolveTokenDetail, shouldEmitTokenSse } from '../token/token-detail.js';
22
24
  import { AppError, ErrorCodes } from '@gt-fe/eap-contracts';
23
25
  import { resolveExecutionContext } from '../governance/governance-client.js';
@@ -192,6 +194,39 @@ export class AgentRuntime {
192
194
  }
193
195
  return [];
194
196
  }
197
+ /**
198
+ * 根据该 sessionId 的 Message 生成会话标题。不写库、不进 execute。
199
+ * 失败返回 `{ title: null }`。
200
+ */
201
+ async generateSessionTitle(params) {
202
+ try {
203
+ const provider = this.deps.llmProvider ?? this.deps.sessionCompression?.provider;
204
+ if (!provider || provider instanceof EchoLLMProvider) {
205
+ return { title: null };
206
+ }
207
+ if (!this.deps.messageService) {
208
+ return { title: null };
209
+ }
210
+ const messages = await this.deps.messageService.getMessages(params.sessionId);
211
+ const payload = buildSessionTitleUserPayload(messages);
212
+ if (!payload) {
213
+ return { title: null };
214
+ }
215
+ let model = params.model?.trim() ?? '';
216
+ if (!model && params.agentCode && this.deps.graphLoader) {
217
+ const resolved = await this.deps.graphLoader.resolveDefaultModel(params.agentCode, params.agentVersion);
218
+ model = resolved?.trim() ?? '';
219
+ }
220
+ const title = await invokeSessionTitle(provider, payload, {
221
+ model,
222
+ sessionId: params.sessionId,
223
+ });
224
+ return { title };
225
+ }
226
+ catch {
227
+ return { title: null };
228
+ }
229
+ }
195
230
  resolveTurnLog() {
196
231
  return this.deps.turnLog ?? createFileLlmTurnLogFromEnv();
197
232
  }
@@ -235,6 +235,7 @@ export function createDevRuntime(options) {
235
235
  sessionCompression,
236
236
  sessionMemory,
237
237
  timeoutManager,
238
+ llmProvider,
238
239
  });
239
240
  return {
240
241
  invokeOrchestrator,
@@ -1,4 +1,4 @@
1
1
  export { AgentRuntime, createAgentRuntime, type AgentRuntimeDeps, type ForkRelationRecord, type RecordForkRelation, type ConversationDetail, } from './agent-runtime.js';
2
- export type { ExecuteParams, ExecuteResult, ForkParams, ForkOverrides, ForkResult, HitlResponse, ResumeHitlResult, SSEEvent, } from './types.js';
2
+ export type { ExecuteParams, ExecuteResult, ForkParams, ForkOverrides, ForkResult, HitlResponse, ResumeHitlResult, SSEEvent, GenerateSessionTitleParams, GenerateSessionTitleResult, } from './types.js';
3
3
  export { createDevRuntime, type DevRuntime, type CreateDevRuntimeOptions, } from './dev-runtime.js';
4
4
  export { assembleRuntimeStorage, resolveRuntimeStorageProfile, resolveDevStorageDirs, type RuntimeStorageProfile, type AssembleRuntimeStorageOptions, type AssembledRuntimeStorage, } from './storage-profile.js';
@@ -38,6 +38,19 @@ export interface ExecuteParams {
38
38
  }
39
39
  /** 同步执行结果 */
40
40
  export type ExecuteResult = InvokeResponseData;
41
+ /** 会话标题生成入参。不启动图执行。 */
42
+ export interface GenerateSessionTitleParams {
43
+ sessionId: string;
44
+ /** 覆盖标题模型;未设则用 Agent `models.default` */
45
+ model?: string;
46
+ /** 用于解析 `models.default`;与 execute 的 agentCode 相同 */
47
+ agentCode?: string;
48
+ agentVersion?: string;
49
+ }
50
+ /** 已规范到 ≤50 字;失败为 null */
51
+ export interface GenerateSessionTitleResult {
52
+ title: string | null;
53
+ }
41
54
  /** HITL 恢复结果(与 @gt-fe/eap-contracts ResumeHitlResult 对齐) */
42
55
  export type { ResumeHitlResult };
43
56
  /** HITL 用户响应(与 @gt-fe/eap-contracts ResumeHitlRequest 对齐;userId 必填) */
@@ -13,6 +13,7 @@ import { EchoLLMProvider } from '../providers/echo-llm-provider.js';
13
13
  import { COMPRESSION_WINDOW_FACTOR, countRounds, DEFAULT_COMPRESSION_CONFIG, } from '../memory/message-compression.service.js';
14
14
  import { SlidingWindowCompression } from '../memory/compression/sliding-window.compression.js';
15
15
  import { emitRuntimeLog } from '../common/logger.js';
16
+ import { SESSION_TITLE_TURN_NODE_ID } from './session-title.js';
16
17
  /** 摘要 LLM 写入 llm_turns 的 nodeId。lastTurnUsage 跳过,避免用摘要占用触发下一轮压缩。 */
17
18
  export const COMPRESSION_TURN_NODE_ID = 'session-compression';
18
19
  export function isRuntimeCompressionEnabled(explicit, env = process.env) {
@@ -256,8 +257,11 @@ async function lastTurnUsage(turnLog, sessionId) {
256
257
  const index = await turnLog.list(sessionId);
257
258
  for (let i = index.length - 1; i >= 0; i--) {
258
259
  const entry = index[i];
259
- if (!entry || entry.nodeId === COMPRESSION_TURN_NODE_ID)
260
+ if (!entry ||
261
+ entry.nodeId === COMPRESSION_TURN_NODE_ID ||
262
+ entry.nodeId === SESSION_TITLE_TURN_NODE_ID) {
260
263
  continue;
264
+ }
261
265
  if (typeof entry.used !== 'number')
262
266
  continue;
263
267
  return { used: entry.used, window: entry.window };
@@ -13,3 +13,4 @@ export { CompressionMiddleware, createCompressionMiddleware } from './compressio
13
13
  export { applySessionCompression, COMPRESSION_TURN_NODE_ID, withCompressionSummaryModel, runSessionCompression, decideSessionCompression, iterSessionCompression, formatCompressionStartedMessage, formatCompressionCompletedMessage, isRuntimeCompressionEnabled, isRuntimeCompressionPersist, type SessionCompressionOptions, type RunSessionCompressionParams, type RunSessionCompressionResult, type SessionCompressionDecision, type SessionCompressionStep, } from './apply-session-compression.js';
14
14
  export type { CompressionMiddlewareConfig } from './compression-middleware.js';
15
15
  export { applySessionMemory, isRuntimeMemoryEnabled, type SessionMemoryOptions, } from './apply-session-memory.js';
16
+ export { SESSION_TITLE_MAX_CHARS, SESSION_TITLE_ASSISTANT_CHAR_BUDGET, SESSION_TITLE_TURN_NODE_ID, SESSION_TITLE_USER_CHAR_BUDGET, provisionalSessionTitle, normalizeGeneratedTitle, fitUserTexts, buildSessionTitleUserPayload, invokeSessionTitle, } from './session-title.js';
@@ -12,3 +12,4 @@ export { SessionContextService } from './session-context.service.js';
12
12
  export { CompressionMiddleware, createCompressionMiddleware } from './compression-middleware.js';
13
13
  export { applySessionCompression, COMPRESSION_TURN_NODE_ID, withCompressionSummaryModel, runSessionCompression, decideSessionCompression, iterSessionCompression, formatCompressionStartedMessage, formatCompressionCompletedMessage, isRuntimeCompressionEnabled, isRuntimeCompressionPersist, } from './apply-session-compression.js';
14
14
  export { applySessionMemory, isRuntimeMemoryEnabled, } from './apply-session-memory.js';
15
+ export { SESSION_TITLE_MAX_CHARS, SESSION_TITLE_ASSISTANT_CHAR_BUDGET, SESSION_TITLE_TURN_NODE_ID, SESSION_TITLE_USER_CHAR_BUDGET, provisionalSessionTitle, normalizeGeneratedTitle, fitUserTexts, buildSessionTitleUserPayload, invokeSessionTitle, } from './session-title.js';
@@ -0,0 +1,27 @@
1
+ /**
2
+ * 会话标题:从 Message 生成短标题字符串。
3
+ * 不写库、不进 execute。Hosting 负责落 sessions.title。
4
+ *
5
+ * @see docs/architecture/session-title-generation.md
6
+ */
7
+ import type { GraphMessage } from '../graph/contracts/executable-graph.js';
8
+ import type { ILLMProvider } from '../graph/nodes/llm-node.js';
9
+ import type { MessageRecord } from './session.service.js';
10
+ /** 与 `@gt-fe/eap-contracts` `SESSION_TITLE_MAX_CHARS` 同值。 */
11
+ export declare const SESSION_TITLE_MAX_CHARS = 50;
12
+ export declare const SESSION_TITLE_USER_CHAR_BUDGET = 2000;
13
+ export declare const SESSION_TITLE_ASSISTANT_CHAR_BUDGET = 500;
14
+ /** 标题 LLM 写入 llm_turns 的 nodeId。压缩 lastTurnUsage 跳过。 */
15
+ export declare const SESSION_TITLE_TURN_NODE_ID = "session-title";
16
+ export declare function clipChars(text: string, max: number): string;
17
+ /** 临时标题:去换行后截到 50 字。 */
18
+ export declare function provisionalSessionTitle(userText: string): string;
19
+ export declare function normalizeGeneratedTitle(raw: string): string | null;
20
+ /** 超预算时丢掉最早的 user;只剩一条仍超则保留开头。 */
21
+ export declare function fitUserTexts(texts: string[], budget: number): string[];
22
+ export declare function buildSessionTitleUserPayload(messages: MessageRecord[]): string | null;
23
+ export declare function buildSessionTitlePromptMessages(payload: string): GraphMessage[];
24
+ export declare function invokeSessionTitle(provider: ILLMProvider, payload: string, options: {
25
+ model?: string;
26
+ sessionId: string;
27
+ }): Promise<string | null>;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * 会话标题:从 Message 生成短标题字符串。
3
+ * 不写库、不进 execute。Hosting 负责落 sessions.title。
4
+ *
5
+ * @see docs/architecture/session-title-generation.md
6
+ */
7
+ import { EchoLLMProvider } from '../providers/echo-llm-provider.js';
8
+ /** 与 `@gt-fe/eap-contracts` `SESSION_TITLE_MAX_CHARS` 同值。 */
9
+ export const SESSION_TITLE_MAX_CHARS = 50;
10
+ export const SESSION_TITLE_USER_CHAR_BUDGET = 2000;
11
+ export const SESSION_TITLE_ASSISTANT_CHAR_BUDGET = 500;
12
+ /** 标题 LLM 写入 llm_turns 的 nodeId。压缩 lastTurnUsage 跳过。 */
13
+ export const SESSION_TITLE_TURN_NODE_ID = 'session-title';
14
+ const TITLE_SYSTEM_PROMPT = '根据对话写一个不超过50个字的会话标题。跟随用户主要语言。只输出标题,不要引号、不要解释。';
15
+ export function clipChars(text, max) {
16
+ const chars = Array.from(text);
17
+ return chars.length <= max ? text : chars.slice(0, max).join('');
18
+ }
19
+ /** 临时标题:去换行后截到 50 字。 */
20
+ export function provisionalSessionTitle(userText) {
21
+ const collapsed = userText.replace(/\s+/g, ' ').trim();
22
+ return clipChars(collapsed, SESSION_TITLE_MAX_CHARS);
23
+ }
24
+ export function normalizeGeneratedTitle(raw) {
25
+ const stripped = raw
26
+ .replace(/^[\s"'「」『』《》]+|[\s"'「」『』《》]+$/g, '')
27
+ .replace(/\s+/g, ' ')
28
+ .trim();
29
+ if (!stripped)
30
+ return null;
31
+ return clipChars(stripped, SESSION_TITLE_MAX_CHARS);
32
+ }
33
+ function charLength(text) {
34
+ return Array.from(text).length;
35
+ }
36
+ /** 超预算时丢掉最早的 user;只剩一条仍超则保留开头。 */
37
+ export function fitUserTexts(texts, budget) {
38
+ const out = texts.filter((t) => t.length > 0);
39
+ while (out.length > 1 && out.reduce((n, t) => n + charLength(t), 0) > budget) {
40
+ out.shift();
41
+ }
42
+ if (out.length === 1 && charLength(out[0]) > budget) {
43
+ out[0] = clipChars(out[0], budget);
44
+ }
45
+ return out;
46
+ }
47
+ export function buildSessionTitleUserPayload(messages) {
48
+ const users = messages
49
+ .filter((m) => m.role === 'user')
50
+ .map((m) => m.content.trim())
51
+ .filter(Boolean);
52
+ if (users.length === 0)
53
+ return null;
54
+ const fitted = fitUserTexts(users, SESSION_TITLE_USER_CHAR_BUDGET);
55
+ const parts = [...fitted];
56
+ const lastAssistant = [...messages]
57
+ .reverse()
58
+ .find((m) => m.role === 'assistant' && m.content.trim());
59
+ if (lastAssistant) {
60
+ parts.push(clipChars(lastAssistant.content.trim(), SESSION_TITLE_ASSISTANT_CHAR_BUDGET));
61
+ }
62
+ return parts.join('\n');
63
+ }
64
+ export function buildSessionTitlePromptMessages(payload) {
65
+ return [
66
+ { role: 'system', content: TITLE_SYSTEM_PROMPT },
67
+ { role: 'user', content: payload },
68
+ ];
69
+ }
70
+ export async function invokeSessionTitle(provider, payload, options) {
71
+ if (provider instanceof EchoLLMProvider)
72
+ return null;
73
+ const rawModel = options.model?.trim() ?? '';
74
+ const model = rawModel.toLowerCase() === 'default' ? '' : rawModel;
75
+ const response = await provider.invoke(buildSessionTitlePromptMessages(payload), {
76
+ model,
77
+ temperature: 0.2,
78
+ maxTokens: 64,
79
+ toolChoice: 'none',
80
+ turn: {
81
+ sessionId: options.sessionId,
82
+ nodeId: SESSION_TITLE_TURN_NODE_ID,
83
+ },
84
+ });
85
+ return normalizeGeneratedTitle(response.content ?? '');
86
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gt-fe/eap-runtime",
3
- "version": "0.0.28",
3
+ "version": "0.0.29",
4
4
  "description": "Enterprise Agent Platform — Runtime核心:Graph执行引擎、Task生命周期、Session管理、Checkpoint持久化",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -20,7 +20,7 @@
20
20
  "ioredis": "^5.4.0",
21
21
  "pg": "^8.13.0",
22
22
  "yaml": "^2.6.1",
23
- "@gt-fe/eap-contracts": "0.0.15"
23
+ "@gt-fe/eap-contracts": "0.0.16"
24
24
  },
25
25
  "publishConfig": {
26
26
  "registry": "https://registry.npmjs.org/",