@axiom-lattice/gateway 1.0.46 → 2.0.0

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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @axiom-lattice/gateway@1.0.12 build /home/runner/work/agentic/agentic/packages/gateway
2
+ > @axiom-lattice/gateway@2.0.0 build /home/runner/work/agentic/agentic/packages/gateway
3
3
  > tsup src/index.ts --format cjs,esm --dts --clean --sourcemap
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -9,13 +9,13 @@
9
9
  CLI Cleaning output folder
10
10
  CJS Build start
11
11
  ESM Build start
12
- CJS dist/index.js 24.48 KB
13
- CJS dist/index.js.map 49.67 KB
14
- CJS ⚡️ Build success in 72ms
15
- ESM dist/index.mjs 22.38 KB
16
- ESM dist/index.mjs.map 49.53 KB
17
- ESM ⚡️ Build success in 72ms
12
+ ESM dist/index.mjs 23.98 KB
13
+ ESM dist/index.mjs.map 51.37 KB
14
+ ESM ⚡️ Build success in 87ms
15
+ CJS dist/index.js 26.07 KB
16
+ CJS dist/index.js.map 51.45 KB
17
+ CJS ⚡️ Build success in 88ms
18
18
  DTS Build start
19
- DTS ⚡️ Build success in 6015ms
19
+ DTS ⚡️ Build success in 6652ms
20
20
  DTS dist/index.d.ts 1.97 KB
21
21
  DTS dist/index.d.mts 1.97 KB
package/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # @axiom-lattice/gateway
2
+
3
+ ## 2.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - dbfac55: streaming 断开续传
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [dbfac55]
12
+ - @axiom-lattice/protocols@2.0.0
13
+ - @axiom-lattice/core@2.0.0
@@ -0,0 +1,326 @@
1
+ # Content-Based Resume Stream
2
+
3
+ ## 概述 (Overview)
4
+
5
+ **重要更新**: `resume_stream` 现在基于**内容匹配**而不是长度匹配。
6
+
7
+ 这是一个更实用的设计,因为在实际应用中,用户通常只能获取到已接收的**内容本身**,而不知道精确的字节长度。
8
+
9
+ ## 为什么要改变?(Why the Change?)
10
+
11
+ ### 之前的问题 (Previous Issues)
12
+
13
+ 使用 `known_content_length` 的问题:
14
+
15
+ 1. **内容处理**: 前端显示的内容可能经过格式化、转义、HTML渲染等处理
16
+ 2. **字符编码**: 多字节字符(如中文、emoji)的长度计算不准确
17
+ 3. **数据存储**: 内容存储到数据库或localStorage后,可能有格式变化
18
+ 4. **不直观**: 用户需要手动计算和追踪字符长度
19
+
20
+ ```typescript
21
+ // ❌ 之前:需要追踪长度
22
+ const knownLength = 150; // 怎么知道是150?需要手动计算
23
+ await resume_stream({
24
+ thread_id,
25
+ message_id,
26
+ known_content_length: knownLength
27
+ });
28
+ ```
29
+
30
+ ### 现在的优势 (Current Advantages)
31
+
32
+ 使用 `known_content` 的优势:
33
+
34
+ 1. **直观简单**: 直接传入已接收的内容字符串
35
+ 2. **无需计算**: 不需要手动计算或追踪长度
36
+ 3. **可靠匹配**: 基于内容本身匹配,更准确
37
+ 4. **易于存储**: 内容可以直接从UI、state、localStorage获取
38
+
39
+ ```typescript
40
+ // ✅ 现在:直接使用内容
41
+ const knownContent = getContentFromUI(); // 或从localStorage读取
42
+ await resume_stream({
43
+ thread_id,
44
+ message_id,
45
+ known_content: knownContent
46
+ });
47
+ ```
48
+
49
+ ## 匹配算法 (Matching Algorithm)
50
+
51
+ `getNewChunksSinceContent` 使用智能匹配算法:
52
+
53
+ ### 1. 精确匹配 (Exact Match)
54
+ ```typescript
55
+ // 累积内容完全等于已知内容
56
+ if (accumulatedContent === knownContent) {
57
+ // 返回后续所有chunks
58
+ }
59
+ ```
60
+
61
+ ### 2. 后缀匹配 (Suffix Match)
62
+ ```typescript
63
+ // 已知内容是累积内容的后缀
64
+ if (accumulatedContent.endsWith(knownContent)) {
65
+ // 返回后续所有chunks
66
+ }
67
+ ```
68
+
69
+ ### 3. 前缀匹配 (Prefix Match)
70
+ ```typescript
71
+ // 已知内容是累积内容的前缀
72
+ if (accumulatedContent.startsWith(knownContent)) {
73
+ // 返回剩余部分
74
+ const remaining = accumulatedContent.substring(knownContent.length);
75
+ // 智能重构chunks
76
+ }
77
+ ```
78
+
79
+ ### 4. 降级处理 (Fallback)
80
+ ```typescript
81
+ // 如果没有匹配
82
+ // 返回所有chunks(最安全的降级策略)
83
+ return allChunks;
84
+ ```
85
+
86
+ ## 使用示例 (Usage Examples)
87
+
88
+ ### 基本使用 (Basic Usage)
89
+
90
+ ```typescript
91
+ // 1. 用户刷新前收到了部分内容
92
+ const receivedContent = "Hello world! This is a streaming response...";
93
+
94
+ // 2. 页面刷新后,从localStorage恢复内容
95
+ const savedContent = localStorage.getItem('chat_content');
96
+
97
+ // 3. 继续接收新内容
98
+ const stream = await resume_stream({
99
+ thread_id: 'thread-123',
100
+ message_id: 'msg-456',
101
+ known_content: savedContent || '',
102
+ });
103
+
104
+ for await (const chunk of stream) {
105
+ displayNewContent(chunk.content);
106
+ }
107
+ ```
108
+
109
+ ### React 示例 (React Example)
110
+
111
+ ```typescript
112
+ function ChatMessage({ threadId, messageId }) {
113
+ const [content, setContent] = useState('');
114
+
115
+ useEffect(() => {
116
+ // 从localStorage恢复
117
+ const saved = localStorage.getItem(`message_${messageId}`);
118
+ if (saved) {
119
+ setContent(saved);
120
+
121
+ // 继续接收新内容
122
+ (async () => {
123
+ const stream = await resume_stream({
124
+ thread_id: threadId,
125
+ message_id: messageId,
126
+ known_content: saved,
127
+ });
128
+
129
+ for await (const chunk of stream) {
130
+ setContent(prev => prev + chunk.content);
131
+ }
132
+ })();
133
+ }
134
+ }, [threadId, messageId]);
135
+
136
+ // 保存内容
137
+ useEffect(() => {
138
+ localStorage.setItem(`message_${messageId}`, content);
139
+ }, [content, messageId]);
140
+
141
+ return <div>{content}</div>;
142
+ }
143
+ ```
144
+
145
+ ### 错误处理 (Error Handling)
146
+
147
+ ```typescript
148
+ async function resumeWithRetry(threadId, messageId, knownContent, maxRetries = 3) {
149
+ for (let i = 0; i < maxRetries; i++) {
150
+ try {
151
+ const stream = await resume_stream({
152
+ thread_id: threadId,
153
+ message_id: messageId,
154
+ known_content: knownContent,
155
+ });
156
+
157
+ for await (const chunk of stream) {
158
+ yield chunk;
159
+ }
160
+
161
+ return; // 成功
162
+ } catch (error) {
163
+ console.error(`Resume attempt ${i + 1} failed:`, error);
164
+ if (i === maxRetries - 1) throw error;
165
+
166
+ // 等待后重试
167
+ await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
168
+ }
169
+ }
170
+ }
171
+ ```
172
+
173
+ ## API 变更 (API Changes)
174
+
175
+ ### Before (之前)
176
+
177
+ ```typescript
178
+ interface ResumeStreamOptions {
179
+ thread_id: string;
180
+ message_id: string;
181
+ known_content_length: number; // ❌ 需要长度
182
+ poll_interval?: number;
183
+ }
184
+ ```
185
+
186
+ ### After (现在)
187
+
188
+ ```typescript
189
+ interface ResumeStreamOptions {
190
+ thread_id: string;
191
+ message_id: string;
192
+ known_content: string; // ✅ 直接用内容
193
+ poll_interval?: number;
194
+ }
195
+ ```
196
+
197
+ ### ChunkBuffer API
198
+
199
+ ```typescript
200
+ // Before (之前)
201
+ getNewChunksSinceLength(
202
+ threadId: string,
203
+ messageId: string,
204
+ knownContentLength: number
205
+ ): Promise<Chunk[]>
206
+
207
+ // After (现在)
208
+ getNewChunksSinceContent(
209
+ threadId: string,
210
+ messageId: string,
211
+ knownContent: string
212
+ ): Promise<Chunk[]>
213
+ ```
214
+
215
+ ## 性能考虑 (Performance Considerations)
216
+
217
+ ### 时间复杂度 (Time Complexity)
218
+ - **匹配**: O(n×m) 其中 n = chunks数量, m = 平均chunk长度
219
+ - **实践中**: 通常很快,因为chunks数量通常不多(< 100)
220
+
221
+ ### 优化建议 (Optimization Tips)
222
+
223
+ 1. **限制内容长度**: 只传递最后的N个字符用于匹配
224
+ ```typescript
225
+ const lastNChars = 1000; // 只用最后1000字符匹配
226
+ const knownContent = fullContent.slice(-lastNChars);
227
+ ```
228
+
229
+ 2. **使用哈希**: 对于超长内容,可以考虑使用内容哈希
230
+ ```typescript
231
+ // 未来可能的优化
232
+ const contentHash = hashContent(knownContent);
233
+ await resume_stream({
234
+ thread_id,
235
+ message_id,
236
+ known_content_hash: contentHash
237
+ });
238
+ ```
239
+
240
+ ## 注意事项 (Important Notes)
241
+
242
+ ### ✅ 优点 (Advantages)
243
+
244
+ 1. **用户友好**: 不需要追踪长度
245
+ 2. **更可靠**: 基于内容本身,不受编码影响
246
+ 3. **易于实现**: 前端代码更简单
247
+ 4. **灵活匹配**: 多种匹配策略
248
+
249
+ ### ⚠️ 限制 (Limitations)
250
+
251
+ 1. **精确匹配**: 内容必须完全匹配(包括空格、换行)
252
+ 2. **性能**: 对于超大内容可能需要优化
253
+ 3. **编码**: 必须使用相同的编码格式
254
+
255
+ ### 💡 最佳实践 (Best Practices)
256
+
257
+ 1. **保存原始内容**: 不要对内容进行格式化后再保存
258
+ ```typescript
259
+ // ✅ 好
260
+ const rawContent = chunks.join('');
261
+ localStorage.setItem('content', rawContent);
262
+
263
+ // ❌ 不好
264
+ const formatted = formatContent(chunks.join(''));
265
+ localStorage.setItem('content', formatted);
266
+ ```
267
+
268
+ 2. **检查状态**: 恢复前先检查thread状态
269
+ ```typescript
270
+ const status = await get_thread_status(threadId);
271
+ if (status.status === 'completed') {
272
+ // 已完成,获取完整内容
273
+ const fullContent = await get_accumulated_content(threadId);
274
+ } else {
275
+ // 继续streaming
276
+ await resume_stream({ ... });
277
+ }
278
+ ```
279
+
280
+ 3. **处理失败**: 如果匹配失败,fallback到完整内容
281
+ ```typescript
282
+ const stream = await resume_stream({
283
+ thread_id: threadId,
284
+ message_id: messageId,
285
+ known_content: knownContent || '', // 空字符串会返回所有内容
286
+ });
287
+ ```
288
+
289
+ ## 迁移指南 (Migration Guide)
290
+
291
+ 如果你正在使用旧版本的 `resume_stream`:
292
+
293
+ ```typescript
294
+ // 旧代码 (Old Code)
295
+ const knownLength = currentContent.length;
296
+ await resume_stream({
297
+ thread_id,
298
+ message_id,
299
+ known_content_length: knownLength // ❌
300
+ });
301
+
302
+ // 新代码 (New Code)
303
+ await resume_stream({
304
+ thread_id,
305
+ message_id,
306
+ known_content: currentContent // ✅
307
+ });
308
+ ```
309
+
310
+ 简单改动:删除 `.length`,直接传内容!
311
+
312
+ ## 总结 (Summary)
313
+
314
+ 这次改动让 `resume_stream` 更加:
315
+ - ✅ **实用** (Practical): 符合实际使用场景
316
+ - ✅ **简单** (Simple): API更直观易用
317
+ - ✅ **可靠** (Reliable): 基于内容匹配更准确
318
+ - ✅ **灵活** (Flexible): 多种匹配策略
319
+
320
+ **核心理念**: 用户知道的是内容,而不是长度!
321
+
322
+
323
+
324
+
325
+
326
+