@myassis/gateway 1.0.97 → 1.0.99

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.
@@ -100,7 +100,7 @@ exports.appConfig = {
100
100
  /** 摘要后保留的原始历史消息条数 */
101
101
  historyKeep: parseInt(process.env.HISTORY_KEEP || '4', 10),
102
102
  /** 保留完整输出的最近工具调用轮数 */
103
- toolPairKeep: parseInt(process.env.TOOL_PAIR_KEEP || '8', 10),
103
+ toolPairKeep: parseInt(process.env.TOOL_PAIR_KEEP || '2', 10),
104
104
  /** 极端裁剪时至少保留的工具调用轮数 */
105
105
  minToolPairKeep: 2,
106
106
  /** 单次请求上下文字符预算(LLMClient 硬上限为 200000,此处留出余量) */
@@ -116,9 +116,12 @@ exports.appConfig = {
116
116
  * 体积完全可能在轮内涨破预算。超过此阈值就在轮内先做摘要,
117
117
  * 而不是等到裁剪到最重级别仍超限、把整轮请求打断。
118
118
  */
119
- turnCompactTriggerChars: parseInt(process.env.TURN_COMPACT_TRIGGER_CHARS || '144000', 10),
119
+ /** 模型 maxTokens 缺失时的轮内压缩兜底阈值(字符) */
120
+ turnCompactTriggerChars: parseInt(process.env.TURN_COMPACT_TRIGGER_CHARS || '1000000', 10),
121
+ /** 轮内压缩阈值占模型上下文窗口的百分比(对齐 Codex 的 90%) */
122
+ turnCompactTriggerPercent: parseInt(process.env.TURN_COMPACT_TRIGGER_PERCENT || '90', 10),
120
123
  /** 轮内压缩时保留原文的最近工具调用轮数 */
121
- turnCompactKeepPairs: parseInt(process.env.TURN_COMPACT_KEEP_PAIRS || '3', 10),
124
+ turnCompactKeepPairs: parseInt(process.env.TURN_COMPACT_KEEP_PAIRS || '2', 10),
122
125
  /** 单轮请求内最多做几次轮内压缩,防止摘要模型被反复调用 */
123
126
  maxInTurnCompactions: parseInt(process.env.MAX_IN_TURN_COMPACTIONS || '4', 10),
124
127
  /** 模型瞬时故障(空回复 / 超时 / 网络抖动)的自动重试次数 */
package/dist/main.js CHANGED
@@ -168,26 +168,26 @@ if (cliCommand) {
168
168
  }
169
169
  }
170
170
  else if (cliCommand === '--help' || cliCommand === '-h') {
171
- console.log(`我的助手 Gateway CLI
172
-
173
- 用法: gateway <命令>
174
-
175
- 服务管理命令:
176
- install 安装 Gateway 服务(后台运行)
177
- uninstall 卸载 Gateway 服务
178
- start 启动 Gateway 服务
179
- stop 停止 Gateway 服务
180
- restart 重启 Gateway 服务
181
- update 更新 Gateway(需重新安装)
182
- status 查看服务状态
183
-
184
- CORS 管理命令:
185
- addCors <域名> 添加允许的跨域域名
186
- removeCors <域名> 移除允许的跨域域名
187
- listCors 列出所有跨域域名(含内置域名)
188
-
189
- 其他:
190
- --help, -h 显示本帮助信息
171
+ console.log(`我的助手 Gateway CLI
172
+
173
+ 用法: gateway <命令>
174
+
175
+ 服务管理命令:
176
+ install 安装 Gateway 服务(后台运行)
177
+ uninstall 卸载 Gateway 服务
178
+ start 启动 Gateway 服务
179
+ stop 停止 Gateway 服务
180
+ restart 重启 Gateway 服务
181
+ update 更新 Gateway(需重新安装)
182
+ status 查看服务状态
183
+
184
+ CORS 管理命令:
185
+ addCors <域名> 添加允许的跨域域名
186
+ removeCors <域名> 移除允许的跨域域名
187
+ listCors 列出所有跨域域名(含内置域名)
188
+
189
+ 其他:
190
+ --help, -h 显示本帮助信息
191
191
  `);
192
192
  }
193
193
  else {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.nextTrimLevel = exports.planTurnCompaction = exports.buildContext = exports.estimateTokens = exports.estimateChars = exports.TrimLevel = void 0;
3
+ exports.nextTrimLevel = exports.planTurnCompaction = exports.buildContext = exports.estimateTokens = exports.estimateChars = exports.TrimLevel = exports.CHARS_PER_TOKEN = void 0;
4
4
  const shared_1 = require("@myassis/shared");
5
5
  const index_js_1 = require("../../config/index.js");
6
6
  const ToolLedger_js_1 = require("./ToolLedger.js");
@@ -14,7 +14,7 @@ const logger = (0, shared_1.getLogger)('ContextBuilder');
14
14
  * 关键约束:本模块只对 *投影副本* 做裁剪,绝不修改会话持久化数据。
15
15
  */
16
16
  /** 中英混合场景下的粗略 token 估算系数 */
17
- const CHARS_PER_TOKEN = 1.6;
17
+ exports.CHARS_PER_TOKEN = 1.6;
18
18
  /** 骨架化后单条工具结果保留的长度 */
19
19
  const SKELETON_OUTPUT_LIMIT = 120;
20
20
  /**
@@ -76,7 +76,7 @@ function estimateChars(messages) {
76
76
  exports.estimateChars = estimateChars;
77
77
  /** 估算 token 数(粗略) */
78
78
  function estimateTokens(messages) {
79
- return Math.ceil(estimateChars(messages) / CHARS_PER_TOKEN);
79
+ return Math.ceil(estimateChars(messages) / exports.CHARS_PER_TOKEN);
80
80
  }
81
81
  exports.estimateTokens = estimateTokens;
82
82
  /** 按上限截断字符串,保留可读提示 */
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toSystemModel = exports.toModel = void 0;
3
+ exports.toSystemModel = exports.toModel = exports.DEFAULT_CONTEXT_WINDOW = void 0;
4
+ /** 未知模型的默认上下文窗口(对齐 Codex 的 fallback 272000 tokens) */
5
+ exports.DEFAULT_CONTEXT_WINDOW = 272000;
4
6
  function toModel(m) {
5
7
  return {
6
8
  id: String(m.id),
@@ -10,7 +12,7 @@ function toModel(m) {
10
12
  type: m.type || 'chat',
11
13
  description: m.description || '',
12
14
  capabilities: m.capabilities || [],
13
- maxTokens: m.maxTokens || 0,
15
+ maxTokens: m.maxTokens || exports.DEFAULT_CONTEXT_WINDOW,
14
16
  isActive: m.isActive,
15
17
  baseUrl: m.baseUrl,
16
18
  score: m.score || 0,
@@ -27,7 +29,7 @@ function toSystemModel(m) {
27
29
  type: m.type || 'chat',
28
30
  description: m.description || '',
29
31
  capabilities: m.capabilities || [],
30
- maxTokens: m.maxTokens || 0,
32
+ maxTokens: m.maxTokens || exports.DEFAULT_CONTEXT_WINDOW,
31
33
  isActive: 1,
32
34
  baseUrl: m.baseUrl,
33
35
  score: m.score || 0,
@@ -43,7 +43,7 @@ class RelayClient {
43
43
  stopped = false;
44
44
  /** Server 在 HELLO_ACK 中下发的参数,未收到前用协议默认值 */
45
45
  initialWindowSize = protocol_js_1.INITIAL_WINDOW_SIZE;
46
- wsInitialWindowSize = protocol_js_1.WS_INITIAL_WINDOW_SIZE;
46
+ wsInitialWindowSize = protocol_js_1.INITIAL_WINDOW_SIZE;
47
47
  maxConcurrentStreams = protocol_js_1.MAX_CONCURRENT_STREAMS;
48
48
  /**
49
49
  * 启动中继。
@@ -353,8 +353,8 @@ class RelayClient {
353
353
  this.maxConcurrentStreams = ack.maxConcurrentStreams;
354
354
  // 旧 Server 不下发该字段:回退到 HTTP 窗口,与改造前行为一致。
355
355
  // 两侧必须用同一个值,否则接收方会把合法数据当成「对端超发」。
356
- this.wsInitialWindowSize = ack.wsInitialWindowSize > 0
357
- ? ack.wsInitialWindowSize
356
+ this.wsInitialWindowSize = ack.initialWindowSize > 0
357
+ ? ack.initialWindowSize
358
358
  : this.initialWindowSize;
359
359
  this.failureCount = 0;
360
360
  this.lastError = undefined;
@@ -9,7 +9,7 @@
9
9
  * CI 会校验本文件与源文件的一致性,不一致将导致构建失败。
10
10
  */
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.stripHopByHopHeaders = exports.checkGatewayFeatures = exports.checkProtocolVersion = exports.ReceiveWindow = exports.SendWindow = exports.StreamIdAllocator = exports.decodeWsData = exports.encodeWsDataFrames = exports.MAX_WS_DATA_CHUNK = exports.WS_DATA_PREFIX_SIZE = exports.WsDataKind = exports.splitIntoFrames = exports.decodeJsonPayload = exports.decodeFrame = exports.encodeJsonFrame = exports.encodeFrame = exports.RelayProtocolError = exports.AbortReason = exports.isControlFrame = exports.isKnownFrameType = exports.frameTypeName = exports.FrameType = exports.CONTROL_STREAM_ID = exports.PING_TIMEOUT_MS = exports.PING_INTERVAL_MS = exports.MAX_CONCURRENT_STREAMS = exports.WS_INITIAL_WINDOW_SIZE = exports.INITIAL_WINDOW_SIZE = exports.MAX_FRAME_PAYLOAD = exports.FRAME_HEADER_SIZE = exports.RELAY_REQUIRED_FEATURE = exports.RELAY_MIN_PROTOCOL_VERSION = exports.RELAY_PROTOCOL_VERSION = void 0;
12
+ exports.stripHopByHopHeaders = exports.checkGatewayFeatures = exports.checkProtocolVersion = exports.ReceiveWindow = exports.SendWindow = exports.StreamIdAllocator = exports.decodeWsData = exports.encodeWsDataFrames = exports.MAX_WS_DATA_CHUNK = exports.WS_DATA_PREFIX_SIZE = exports.WsDataKind = exports.splitIntoFrames = exports.decodeJsonPayload = exports.decodeFrame = exports.encodeJsonFrame = exports.encodeFrame = exports.RelayProtocolError = exports.AbortReason = exports.isControlFrame = exports.isKnownFrameType = exports.frameTypeName = exports.FrameType = exports.CONTROL_STREAM_ID = exports.PING_TIMEOUT_MS = exports.PING_INTERVAL_MS = exports.MAX_CONCURRENT_STREAMS = exports.INITIAL_WINDOW_SIZE = exports.MAX_FRAME_PAYLOAD = exports.FRAME_HEADER_SIZE = exports.RELAY_REQUIRED_FEATURE = exports.RELAY_MIN_PROTOCOL_VERSION = exports.RELAY_PROTOCOL_VERSION = void 0;
13
13
  /**
14
14
  * 中继帧协议(单源实现)
15
15
  * ============================================================
@@ -55,29 +55,8 @@ exports.FRAME_HEADER_SIZE = 5;
55
55
  * 偏小是有意为之:帧越小,SSE/流式响应的转发粒度越细,首字延迟越低。
56
56
  */
57
57
  exports.MAX_FRAME_PAYLOAD = 64 * 1024;
58
- /**
59
- * HTTP 流的初始接收窗口(信用式流控)。
60
- *
61
- * 保持 256KB:HTTP 流有真正的背压——信用耗尽会 req.pause() 暂停
62
- * 上游,等 WINDOW_UPDATE 回来再继续,窗口小只影响吞吐不会丢数据,
63
- * 而帧细粒度对 SSE 首字延迟有利。
64
- */
58
+ /** 每条流的初始接收窗口(信用式流控) */
65
59
  exports.INITIAL_WINDOW_SIZE = 256 * 1024;
66
- /**
67
- * WebSocket 流的初始接收窗口。
68
- *
69
- * 必须远大于 HTTP 窗口,因为 WS 没有背压可用:ws 库不支持逗条
70
- * 暂停,一条消息发不下去就只能 abortStream(见
71
- * RelayHub.forwardWebSocket 与 RelayClient.onWsOpen 里的 QUOTA_EXCEEDED 分支)。
72
- * 一旦中止,终端的通知通道就断了,那一段的会话镜像事件全丢。
73
- *
74
- * 现实触发条件:会话镜像会把含 base64 附件的用户消息整条下发,
75
- * 一张 5MB 的图经 base64 膨胀约 4/3 后接近 7MB,瞬时打穿 256KB。
76
- *
77
- * 16MB 不是预分配:窗口只是「允许对端在未收到确认前先发多少」。
78
- * 而且 WS 流数量等于在线终端数(个位数),不是 MAX_CONCURRENT_STREAMS。
79
- */
80
- exports.WS_INITIAL_WINDOW_SIZE = 16 * 1024 * 1024;
81
60
  /** 单条隧道允许的最大并发流数 */
82
61
  exports.MAX_CONCURRENT_STREAMS = 64;
83
62
  /** 心跳间隔与超时(毫秒) */
@@ -925,6 +925,13 @@ class Session {
925
925
  const models = this.useSystemMode
926
926
  ? (await dataService_js_1.modelsService.listSystem()).data.map(x => (0, models_js_1.toSystemModel)(x))
927
927
  : (await dataService_js_1.modelsService.list(token)).data.map(x => (0, models_js_1.toModel)(x));
928
+ // 轮内压缩阈值:优先按当前模型上下文窗口的百分比(对齐 Codex 的 90%),
929
+ // 模型未声明 maxTokens 时回退到固定字符阈值。
930
+ const activeModel = models.find(m => m.modelId === this.selectModelId || m.id === this.selectModelId) || models[0];
931
+ const modelMaxTokens = activeModel?.maxTokens || 0;
932
+ const turnCompactTokenThreshold = modelMaxTokens > 0
933
+ ? Math.floor(modelMaxTokens * index_js_2.appConfig.turnCompactTriggerPercent / 100)
934
+ : Math.floor(index_js_2.appConfig.turnCompactTriggerChars / ContextBuilder_js_1.CHARS_PER_TOKEN);
928
935
  // 工具调用轮次计数,防止无限循环
929
936
  let toolRound = 0;
930
937
  // 达到工具轮上限后置位:强制模型停止调用工具、直接产出最终回复
@@ -1010,7 +1017,7 @@ class Session {
1010
1017
  while (true) {
1011
1018
  // 预防式压缩:等到厂商报超限才处理已经太晚(那意味着一次废掉的请求,
1012
1019
  // 且部分厂商在超限时返回的是难以识别的 400),这里提前把体积降下来。
1013
- if ((0, ContextBuilder_js_1.estimateChars)(messages) > index_js_2.appConfig.turnCompactTriggerChars) {
1020
+ if ((0, ContextBuilder_js_1.estimateTokens)(messages) > turnCompactTokenThreshold) {
1014
1021
  await compactInTurn();
1015
1022
  }
1016
1023
  const built = (0, ContextBuilder_js_1.buildContext)(messages, messagesLength, index_js_2.appConfig.contextMaxChars, trimLevel);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myassis/gateway",
3
- "version": "1.0.97",
3
+ "version": "1.0.99",
4
4
  "description": "我的助手 Gateway Service - 本地 AI 网关服务,支持认证、WebSocket 实时通信和任务调度",
5
5
  "main": "dist/index.js",
6
6
  "bin": {