[{"data":1,"prerenderedAt":12354},["ShallowReactive",2],{"/2026/sse":3,"surround-/2026/sse":12348},{"id":4,"title":5,"body":6,"categories":12328,"date":12330,"description":12331,"draft":12332,"extension":12333,"image":12334,"meta":12335,"navigation":687,"path":12336,"rawbody":12337,"readingTime":12338,"recommend":12334,"references":12334,"seo":12343,"sitemap":12344,"stem":12345,"tags":12346,"type":12334,"updated":12330,"url":12334,"__hash__":12347},"content/posts/2026/SSE.md","SSE",{"type":7,"value":8,"toc":12301},"minimark",[9,14,22,32,39,68,73,150,156,159,165,174,177,191,725,734,759,762,766,941,980,988,991,1217,1229,1343,1363,1367,1377,1385,1391,1425,1431,1435,1441,2085,2139,2142,2167,2170,2175,2180,2238,2244,2247,2281,2284,2287,2294,2616,2652,2657,2841,2844,2847,5750,5879,5888,5891,6531,6539,6542,6545,6548,6555,8107,8116,8121,8128,8138,8283,8291,8308,8373,8470,8481,8484,8490,8731,8736,8782,8799,8802,8812,8816,8823,9262,9268,9416,9422,9426,9447,9450,9457,9460,9473,9553,9572,9856,9859,9884,11527,11530,11548,11551,11761,11768,12291,12297],[10,11,13],"h3",{"id":12},"什么是sse","什么是SSE？",[15,16,17,21],"p",{},[18,19,20],"strong",{},"SSE（Server-Sent Events，服务器推送事件）"," 是一种基于 HTTP 的长连接技术。\n核心思想极其朴素：",[23,24,25],"blockquote",{},[15,26,27,28,31],{},"普通的 HTTP 响应是\"服务器一次性把整个 body 发完就断开\"。\nSSE 则是",[18,29,30],{},"服务器保持连接不关闭，把数据分成一小块一小块持续发","。",[15,33,34,35,38],{},"OcEasy 用的是一种简化变体（也叫 ",[18,36,37],{},"NDJSON over SSE"," / 逐行 JSON）：",[40,41,42,50,61],"ul",{},[43,44,45,46],"li",{},"响应头标注 ",[47,48,49],"code",{"code":49},"Content-Type: text/event-stream",[43,51,52,53,56,57,60],{},"响应体是",[18,54,55],{},"一行一个 JSON 对象","，行之间用 ",[47,58,59],{"code":59},"\\n"," 换行符分隔",[43,62,63,64,67],{},"每次 ",[47,65,66],{"code":66},"yield"," 一行，数据就立刻到达浏览器，不等后面的行",[15,69,70],{},[18,71,72],{},"与 WebSocket 的区别",[74,75,76,91],"table",{},[77,78,79],"thead",{},[80,81,82,86,88],"tr",{},[83,84,85],"th",{},"特性",[83,87,5],{},[83,89,90],{},"WebSocket",[92,93,94,106,117,128,139],"tbody",{},[80,95,96,100,103],{},[97,98,99],"td",{},"方向",[97,101,102],{},"服务器 → 客户端（单向）",[97,104,105],{},"双向",[80,107,108,111,114],{},[97,109,110],{},"协议",[97,112,113],{},"纯 HTTP，无需升级",[97,115,116],{},"需要 101 协议升级",[80,118,119,122,125],{},[97,120,121],{},"自动重连",[97,123,124],{},"浏览器原生支持",[97,126,127],{},"需自己实现",[80,129,130,133,136],{},[97,131,132],{},"断点续传",[97,134,135],{},"容易（带游标）",[97,137,138],{},"较难",[80,140,141,144,147],{},[97,142,143],{},"适用场景",[97,145,146],{},"流式文本、进度、通知",[97,148,149],{},"游戏、实时协作、聊天",[15,151,152,155],{},[18,153,154],{},"聊天 AI 的逐字输出是 SSE 的典型场景","：只需要服务端往客户端推文本，不需要客户端往服务器推数据（发消息走普通 POST）。",[10,157,158],{"id":158},"架构",[15,160,161],{},[162,163],"img",{"alt":162,"src":164},"https://assets.yangzy.top/ChatGPT%20Image%202026%E5%B9%B48%E6%9C%883%E6%97%A5%2012_47_39.webp",[23,166,167],{},[15,168,169,170,173],{},"即后端 LLM 每次生成一个 token → 包成 Packet → 序列化成\"一行 JSON + 换行\" → 立刻 flush 给浏览器；\n前端用 ",[47,171,172],{"code":172},"response.body"," 的流式 reader 逐块读取 → 按换行切分 → JSON.parse → 累加到界面。",[10,175,176],{"id":176},"后端",[15,178,179,180,186,187,190],{},"这是流式聊天的 HTTP 端点。",[18,181,182,183],{},"注意返回值类型是 ",[47,184,185],{"code":185},"StreamingResponse | ChatFullResponse","，根据请求参数 ",[47,188,189],{"code":189},"stream"," 决定走流式还是非流式。",[192,193,198],"pre",{"className":194,"code":195,"language":196,"meta":197,"style":197},"language-python shiki shiki-themes catppuccin-latte one-dark-pro","# chat_backend.py:681\ndef stream_generator() -> Generator[str, None, None]:\n    state_container = ChatStateContainer()\n    try:\n        for obj in handle_stream_message_objects(...):\n            yield get_json_line(obj.model_dump())   # 每个对象 → 一行 JSON\n    except Exception as e:\n        yield json.dumps({\"error\": str(e)})\n    finally:\n        logger.debug(\"Stream generator finished\")\n\nreturn StreamingResponse(stream_generator(), media_type=\"text/event-stream\")\n","python","",[47,199,200,217,285,323,343,401,500,539,608,628,682,689],{"__ignoreMap":197},[201,202,205,209,214],"span",{"class":203,"data-line":204,"line":204},"line",1,[201,206,208],{"class":207},"skYY2","#",[201,210,213],{"class":211},[212],"space"," ",[201,215,216],{"class":207},"chat_backend.py:681\n",[201,218,220,224,227,231,235,238,241,245,248,251,255,258,262,265,268,272,274,277,279,282],{"class":203,"data-line":219,"line":219},2,[201,221,223],{"class":222},"sSWcl","def",[201,225,213],{"class":226},[212],[201,228,230],{"class":229},"seVD2","stream_generator",[201,232,234],{"class":233},"scfJ_","(",[201,236,237],{"class":233},")",[201,239,213],{"class":240},[212],[201,242,244],{"class":243},"sgT6j","-",[201,246,247],{"class":243},">",[201,249,213],{"class":250},[212],[201,252,254],{"class":253},"sQGkZ","Generator",[201,256,257],{"class":233},"[",[201,259,261],{"class":260},"s0gAL","str",[201,263,264],{"class":243},",",[201,266,213],{"class":267},[212],[201,269,271],{"class":270},"s1D4B","None",[201,273,264],{"class":243},[201,275,213],{"class":276},[212],[201,278,271],{"class":270},[201,280,281],{"class":233},"]",[201,283,284],{"class":243},":\n",[201,286,288,291,294,297,300,304,307,311,314,318,320],{"class":203,"data-line":287,"line":287},3,[201,289,213],{"class":290},[212],[201,292,213],{"class":293},[212],[201,295,213],{"class":296},[212],[201,298,213],{"class":299},[212],[201,301,303],{"class":302},"sa2x1","state_container",[201,305,213],{"class":306},[212],[201,308,310],{"class":309},"sqgB4","=",[201,312,213],{"class":313},[212],[201,315,317],{"class":316},"s3w6o","ChatStateContainer",[201,319,234],{"class":233},[201,321,322],{"class":233},")\n",[201,324,326,329,332,335,338,341],{"class":203,"data-line":325,"line":325},4,[201,327,213],{"class":328},[212],[201,330,213],{"class":331},[212],[201,333,213],{"class":334},[212],[201,336,213],{"class":337},[212],[201,339,340],{"class":222},"try",[201,342,284],{"class":243},[201,344,346,349,352,355,358,361,364,367,370,373,376,379,382,385,388,391,393,397,399],{"class":203,"data-line":345,"line":345},5,[201,347,213],{"class":348},[212],[201,350,213],{"class":351},[212],[201,353,213],{"class":354},[212],[201,356,213],{"class":357},[212],[201,359,213],{"class":360},[212],[201,362,213],{"class":363},[212],[201,365,213],{"class":366},[212],[201,368,213],{"class":369},[212],[201,371,372],{"class":222},"for",[201,374,213],{"class":375},[212],[201,377,378],{"class":302},"obj",[201,380,213],{"class":381},[212],[201,383,384],{"class":222},"in",[201,386,213],{"class":387},[212],[201,389,390],{"class":316},"handle_stream_message_objects",[201,392,234],{"class":233},[201,394,396],{"class":395},"sJ4Hi","...",[201,398,237],{"class":233},[201,400,284],{"class":243},[201,402,404,407,410,413,416,419,422,425,428,431,434,437,440,442,445,448,450,452,455,458,461,463,465,468,471,474,476,479,482,485,488,491,494,497],{"class":203,"data-line":403,"line":403},6,[201,405,213],{"class":406},[212],[201,408,213],{"class":409},[212],[201,411,213],{"class":412},[212],[201,414,213],{"class":415},[212],[201,417,213],{"class":418},[212],[201,420,213],{"class":421},[212],[201,423,213],{"class":424},[212],[201,426,213],{"class":427},[212],[201,429,213],{"class":430},[212],[201,432,213],{"class":433},[212],[201,435,213],{"class":436},[212],[201,438,213],{"class":439},[212],[201,441,66],{"class":222},[201,443,213],{"class":444},[212],[201,446,447],{"class":316},"get_json_line",[201,449,234],{"class":233},[201,451,378],{"class":302},[201,453,454],{"class":243},".",[201,456,457],{"class":316},"model_dump",[201,459,234],{"class":460},"si0CV",[201,462,237],{"class":460},[201,464,237],{"class":233},[201,466,213],{"class":467},[212],[201,469,213],{"class":470},[212],[201,472,213],{"class":473},[212],[201,475,208],{"class":207},[201,477,213],{"class":478},[212],[201,480,481],{"class":207},"每个对象",[201,483,213],{"class":484},[212],[201,486,487],{"class":207},"→",[201,489,213],{"class":490},[212],[201,492,493],{"class":207},"一行",[201,495,213],{"class":496},[212],[201,498,499],{"class":207},"JSON\n",[201,501,503,506,509,512,515,518,521,525,528,531,534,537],{"class":203,"data-line":502,"line":502},7,[201,504,213],{"class":505},[212],[201,507,213],{"class":508},[212],[201,510,213],{"class":511},[212],[201,513,213],{"class":514},[212],[201,516,517],{"class":222},"except",[201,519,213],{"class":520},[212],[201,522,524],{"class":523},"siVku","Exception",[201,526,213],{"class":527},[212],[201,529,530],{"class":222},"as",[201,532,213],{"class":533},[212],[201,535,536],{"class":302},"e",[201,538,284],{"class":243},[201,540,542,545,548,551,554,557,560,563,566,568,571,574,576,579,581,584,588,591,594,596,599,601,603,606],{"class":203,"data-line":541,"line":541},8,[201,543,213],{"class":544},[212],[201,546,213],{"class":547},[212],[201,549,213],{"class":550},[212],[201,552,213],{"class":553},[212],[201,555,213],{"class":556},[212],[201,558,213],{"class":559},[212],[201,561,213],{"class":562},[212],[201,564,213],{"class":565},[212],[201,567,66],{"class":222},[201,569,213],{"class":570},[212],[201,572,573],{"class":302},"json",[201,575,454],{"class":243},[201,577,578],{"class":316},"dumps",[201,580,234],{"class":233},[201,582,583],{"class":460},"{",[201,585,587],{"class":586},"sw_MA","\"error\"",[201,589,590],{"class":243},":",[201,592,213],{"class":593},[212],[201,595,261],{"class":260},[201,597,234],{"class":598},"sSXcQ",[201,600,536],{"class":302},[201,602,237],{"class":598},[201,604,605],{"class":460},"}",[201,607,322],{"class":233},[201,609,611,614,617,620,623,626],{"class":203,"data-line":610,"line":610},9,[201,612,213],{"class":613},[212],[201,615,213],{"class":616},[212],[201,618,213],{"class":619},[212],[201,621,213],{"class":622},[212],[201,624,625],{"class":222},"finally",[201,627,284],{"class":243},[201,629,631,634,637,640,643,646,649,652,655,658,660,663,665,668,671,674,677,680],{"class":203,"data-line":630,"line":630},10,[201,632,213],{"class":633},[212],[201,635,213],{"class":636},[212],[201,638,213],{"class":639},[212],[201,641,213],{"class":642},[212],[201,644,213],{"class":645},[212],[201,647,213],{"class":648},[212],[201,650,213],{"class":651},[212],[201,653,213],{"class":654},[212],[201,656,657],{"class":302},"logger",[201,659,454],{"class":243},[201,661,662],{"class":316},"debug",[201,664,234],{"class":233},[201,666,667],{"class":586},"\"Stream",[201,669,213],{"class":670},[212],[201,672,673],{"class":586},"generator",[201,675,213],{"class":676},[212],[201,678,679],{"class":586},"finished\"",[201,681,322],{"class":233},[201,683,685],{"class":203,"data-line":684,"line":684},11,[201,686,688],{"emptyLinePlaceholder":687},true,"\n",[201,690,692,695,698,701,703,705,707,709,711,714,718,720,723],{"class":203,"data-line":691,"line":691},12,[201,693,694],{"class":222},"return",[201,696,213],{"class":697},[212],[201,699,700],{"class":316},"StreamingResponse",[201,702,234],{"class":233},[201,704,230],{"class":316},[201,706,234],{"class":460},[201,708,237],{"class":460},[201,710,264],{"class":243},[201,712,213],{"class":713},[212],[201,715,717],{"class":716},"sddMY","media_type",[201,719,310],{"class":309},[201,721,722],{"class":586},"\"text/event-stream\"",[201,724,322],{"class":233},[15,726,727,733],{},[18,728,729,730,732],{},"FastAPI 的 ",[47,731,700],{"code":700}," 是流式输出的\"发动机\"","：",[40,735,736,742,752],{},[43,737,738,739,31],{},"它接受一个",[18,740,741],{},"生成器（generator）",[43,743,744,745,747,748,751],{},"每次生成器 ",[47,746,66],{"code":66}," 一个字符串，FastAPI（底层是 Starlette）就把它",[18,749,750],{},"立即作为一块 HTTP chunk 发出去","，不会攒起来。",[43,753,754,755,758],{},"连接保持打开，直到生成器结束（",[47,756,757],{"code":757},"StopIteration","）或客户端断开。",[15,760,761],{},"这就是\"后端不一次给完、前端能收到增量\"的根源。",[763,764,765],"h4",{"id":765},"序列化",[192,767,769],{"className":194,"code":768,"language":196,"meta":197,"style":197},"def get_json_line(\n    json_dict: dict[str, Any], encoder: type[json.JSONEncoder] = OnyxJSONEncoder\n) -> str:\n    return json.dumps(json_dict, cls=encoder) + \"\\n\"\n",[47,770,771,783,862,880],{"__ignoreMap":197},[201,772,773,775,778,780],{"class":203,"data-line":204,"line":204},[201,774,223],{"class":222},[201,776,213],{"class":777},[212],[201,779,447],{"class":229},[201,781,782],{"class":233},"(\n",[201,784,785,788,791,794,797,801,803,806,809,811,813,815,818,821,823,825,828,831,833,836,839,841,843,845,848,850,853,855,858],{"class":203,"data-line":219,"line":219},[201,786,213],{"class":787},[212],[201,789,213],{"class":790},[212],[201,792,213],{"class":793},[212],[201,795,213],{"class":796},[212],[201,798,800],{"class":799},"sZ7dW","json_dict",[201,802,590],{"class":243},[201,804,213],{"class":805},[212],[201,807,808],{"class":253},"dict",[201,810,257],{"class":460},[201,812,261],{"class":260},[201,814,264],{"class":243},[201,816,213],{"class":817},[212],[201,819,820],{"class":253},"Any",[201,822,281],{"class":460},[201,824,264],{"class":243},[201,826,213],{"class":827},[212],[201,829,830],{"class":799},"encoder",[201,832,590],{"class":243},[201,834,213],{"class":835},[212],[201,837,838],{"class":253},"type",[201,840,257],{"class":460},[201,842,573],{"class":253},[201,844,454],{"class":243},[201,846,847],{"class":253},"JSONEncoder",[201,849,281],{"class":460},[201,851,213],{"class":852},[212],[201,854,310],{"class":309},[201,856,213],{"class":857},[212],[201,859,861],{"class":860},"slFfU","OnyxJSONEncoder\n",[201,863,864,866,869,871,873,876,878],{"class":203,"data-line":287,"line":287},[201,865,237],{"class":233},[201,867,213],{"class":868},[212],[201,870,244],{"class":243},[201,872,247],{"class":243},[201,874,213],{"class":875},[212],[201,877,261],{"class":260},[201,879,284],{"class":243},[201,881,882,885,888,891,894,896,899,901,903,905,907,909,911,914,917,919,921,923,926,929,932,935,938],{"class":203,"data-line":325,"line":325},[201,883,213],{"class":884},[212],[201,886,213],{"class":887},[212],[201,889,213],{"class":890},[212],[201,892,213],{"class":893},[212],[201,895,694],{"class":222},[201,897,213],{"class":898},[212],[201,900,573],{"class":302},[201,902,454],{"class":243},[201,904,578],{"class":316},[201,906,234],{"class":233},[201,908,800],{"class":302},[201,910,264],{"class":243},[201,912,213],{"class":913},[212],[201,915,916],{"class":716},"cls",[201,918,310],{"class":309},[201,920,830],{"class":302},[201,922,237],{"class":233},[201,924,213],{"class":925},[212],[201,927,928],{"class":309},"+",[201,930,213],{"class":931},[212],[201,933,934],{"class":586},"\"",[201,936,59],{"class":937},"sFWNk",[201,939,940],{"class":586},"\"\n",[40,942,943,953,961],{},[43,944,945,948,949,952],{},[47,946,947],{"code":947},"json.dumps"," 把 Pydantic 对象 ",[47,950,951],{"code":951},"model_dump()"," 后的字典序列化。",[43,954,955,960],{},[18,956,957,958],{},"末尾加 ",[47,959,59],{"code":59},"——这个换行符是前端的\"消息分隔符\"（协议关键）。",[43,962,963,964,967,968,971,972,975,976,979],{},"自定义 ",[47,965,966],{"code":966},"OnyxJSONEncoder"," 处理 ",[47,969,970],{"code":970},"datetime","、",[47,973,974],{"code":974},"UUID"," 等非原生 JSON 类型（",[47,977,978],{"code":978},"server/utils.py:20","）。",[23,981,982],{},[15,983,984,985],{},"每个包在线上就是一行文本，例如：\n",[47,986,987],{"code":987},"{\"placement\":{\"turn_index\":0},\"obj\":{\"type\":\"agent_response_delta\",\"content\":\"你\"}}",[763,989,990],{"id":990},"数据模型",[192,992,994],{"className":194,"code":993,"language":196,"meta":197,"style":197},"class Packet(BaseModel):\n    placement: Placement                      # 定位信息（哪个 turn / 哪个模型）\n    obj: Annotated[PacketObj, Field(discriminator=\"type\")]   # 具体内容，按 type 区分\n",[47,995,996,1017,1134],{"__ignoreMap":197},[201,997,998,1001,1004,1008,1010,1013,1015],{"class":203,"data-line":204,"line":204},[201,999,1000],{"class":222},"class",[201,1002,213],{"class":1003},[212],[201,1005,1007],{"class":1006},"sIkkJ","Packet",[201,1009,234],{"class":233},[201,1011,1012],{"class":1006},"BaseModel",[201,1014,237],{"class":233},[201,1016,284],{"class":243},[201,1018,1019,1022,1025,1028,1031,1034,1036,1039,1042,1045,1048,1051,1054,1057,1060,1063,1066,1069,1072,1075,1078,1081,1084,1087,1090,1093,1096,1099,1102,1105,1108,1110,1113,1116,1119,1122,1125,1128,1131],{"class":203,"data-line":219,"line":219},[201,1020,213],{"class":1021},[212],[201,1023,213],{"class":1024},[212],[201,1026,213],{"class":1027},[212],[201,1029,213],{"class":1030},[212],[201,1032,1033],{"class":302},"placement",[201,1035,590],{"class":243},[201,1037,213],{"class":1038},[212],[201,1040,1041],{"class":302},"Placement",[201,1043,213],{"class":1044},[212],[201,1046,213],{"class":1047},[212],[201,1049,213],{"class":1050},[212],[201,1052,213],{"class":1053},[212],[201,1055,213],{"class":1056},[212],[201,1058,213],{"class":1059},[212],[201,1061,213],{"class":1062},[212],[201,1064,213],{"class":1065},[212],[201,1067,213],{"class":1068},[212],[201,1070,213],{"class":1071},[212],[201,1073,213],{"class":1074},[212],[201,1076,213],{"class":1077},[212],[201,1079,213],{"class":1080},[212],[201,1082,213],{"class":1083},[212],[201,1085,213],{"class":1086},[212],[201,1088,213],{"class":1089},[212],[201,1091,213],{"class":1092},[212],[201,1094,213],{"class":1095},[212],[201,1097,213],{"class":1098},[212],[201,1100,213],{"class":1101},[212],[201,1103,213],{"class":1104},[212],[201,1106,213],{"class":1107},[212],[201,1109,208],{"class":207},[201,1111,213],{"class":1112},[212],[201,1114,1115],{"class":207},"定位信息（哪个",[201,1117,213],{"class":1118},[212],[201,1120,1121],{"class":207},"turn",[201,1123,213],{"class":1124},[212],[201,1126,1127],{"class":207},"/",[201,1129,213],{"class":1130},[212],[201,1132,1133],{"class":207},"哪个模型）\n",[201,1135,1136,1139,1142,1145,1148,1150,1152,1155,1158,1160,1163,1165,1168,1171,1173,1176,1178,1180,1183,1185,1187,1189,1192,1195,1198,1200,1203,1206,1209,1211,1214],{"class":203,"data-line":287,"line":287},[201,1137,213],{"class":1138},[212],[201,1140,213],{"class":1141},[212],[201,1143,213],{"class":1144},[212],[201,1146,213],{"class":1147},[212],[201,1149,378],{"class":302},[201,1151,590],{"class":243},[201,1153,213],{"class":1154},[212],[201,1156,1157],{"class":253},"Annotated",[201,1159,257],{"class":233},[201,1161,1162],{"class":253},"PacketObj",[201,1164,264],{"class":243},[201,1166,213],{"class":1167},[212],[201,1169,1170],{"class":229},"Field",[201,1172,234],{"class":460},[201,1174,1175],{"class":716},"discriminator",[201,1177,310],{"class":309},[201,1179,934],{"class":586},[201,1181,838],{"class":1182},"s2-b8",[201,1184,934],{"class":586},[201,1186,237],{"class":460},[201,1188,281],{"class":233},[201,1190,213],{"class":1191},[212],[201,1193,213],{"class":1194},[212],[201,1196,213],{"class":1197},[212],[201,1199,208],{"class":207},[201,1201,213],{"class":1202},[212],[201,1204,1205],{"class":207},"具体内容，按",[201,1207,213],{"class":1208},[212],[201,1210,838],{"class":207},[201,1212,213],{"class":1213},[212],[201,1215,1216],{"class":207},"区分\n",[15,1218,1219,1221,1222,1228],{},[47,1220,1162],{"code":1162}," 是一个",[18,1223,1224,1225,1227],{},"按 ",[47,1226,838],{"code":838}," 字段判别的联合类型","，几十种包包括：",[74,1230,1231,1241],{},[77,1232,1233],{},[80,1234,1235,1238],{},[83,1236,1237],{},"包类型",[83,1239,1240],{},"作用",[92,1242,1243,1256,1266,1276,1286,1296,1306,1316,1330],{},[80,1244,1245,1250],{},[97,1246,1247],{},[47,1248,1249],{"code":1249},"agent_response_delta",[97,1251,1252,1255],{},[18,1253,1254],{},"回答内容增量","（真正打字的包）",[80,1257,1258,1263],{},[97,1259,1260],{},[47,1261,1262],{"code":1262},"agent_response_start",[97,1264,1265],{},"回答开始（携带最终文档、预处理耗时）",[80,1267,1268,1273],{},[97,1269,1270],{},[47,1271,1272],{"code":1272},"reasoning_delta",[97,1274,1275],{},"思考/推理内容增量",[80,1277,1278,1283],{},[97,1279,1280],{},[47,1281,1282],{"code":1282},"search_tool_*_delta",[97,1284,1285],{},"搜索工具参数/结果增量",[80,1287,1288,1293],{},[97,1289,1290],{},[47,1291,1292],{"code":1292},"tool_call_argument_delta",[97,1294,1295],{},"工具调用参数流式生成",[80,1297,1298,1303],{},[97,1299,1300],{},[47,1301,1302],{"code":1302},"citation_info",[97,1304,1305],{},"引用文献信息",[80,1307,1308,1313],{},[97,1309,1310],{},[47,1311,1312],{"code":1312},"chat_heartbeat",[97,1314,1315],{},"心跳（见第 6 节）",[80,1317,1318,1323],{},[97,1319,1320],{},[47,1321,1322],{"code":1322},"stream_stop",[97,1324,1325,1326,1329],{},"流结束（含 ",[47,1327,1328],{"code":1328},"stop_reason","）",[80,1331,1332,1337],{},[97,1333,1334],{},[47,1335,1336],{"code":1336},"error",[97,1338,1339,1340,1329],{},"错误（",[47,1341,1342],{"code":1342},"StreamingError",[23,1344,1345],{},[15,1346,1347,1348,1351,1352,1358,1359,1362],{},"Pydantic 的 ",[47,1349,1350],{"code":1350},"discriminator=\"type\""," 意味着",[18,1353,1354,1355,1357],{},"反序列化时前端必须带 ",[47,1356,838],{"code":838}," 字段","，\n前端 ",[47,1360,1361],{"code":1361},"PacketType","（lib.tsx:92）是同样的判别联合。",[763,1364,1366],{"id":1365},"生成器链包怎么从-llm-流到-http","生成器链：包怎么从 LLM 流到 HTTP",[15,1368,1369,1370,1372,1373,1376],{},"调用链是层层嵌套的生成器，每一层把上层 ",[47,1371,66],{"code":66}," 的东西",[18,1374,1375],{},"透传","给下层：",[192,1378,1383],{"className":1379,"code":1381,"language":1382},[1380],"language-text","stream_generator (chat_backend.py:682)\n   └─ handle_stream_message_objects (process_message.py:1752)\n        └─ _stream_chat_turn (process_message.py:1528)\n             └─ _run_models (process_message.py:1062)\n                  └─ _read_stream (process_message.py:1496)  ← 实际对外 yield\n","text",[47,1384,1381],{"__ignoreMap":197},[15,1386,1387,1390],{},[47,1388,1389],{"code":1389},"_stream_chat_turn","（process_message.py:1582-1661）的关键逻辑：",[1392,1393,1394,1408,1415],"ol",{},[43,1395,1396,1397,1400,1401,1404,1405,1407],{},"在一个短命 DB session 里执行 ",[47,1398,1399],{"code":1399},"build_chat_turn","，把",[18,1402,1403],{},"准备阶段产生的包","（如 session id、message id）先 ",[47,1406,66],{"code":66}," 出去；",[43,1409,1410,1411,1414],{},"构造 ",[47,1412,1413],{"code":1413},"StreamBufferWriter","（流缓冲）；",[43,1416,1417,1418,1421,1422,31],{},"调用 ",[47,1419,1420],{"code":1420},"_run_models"," 得到 run 流并 ",[47,1423,1424],{"code":1424},"yield from",[15,1426,1427,1430],{},[18,1428,1429],{},"为什么先发准备阶段包？"," 前端需要立刻知道\"这条消息分配到了哪个 session / 哪个 message id\"，才能在本地消息树上建出节点来承接后续的内容包。",[763,1432,1434],{"id":1433},"逐token","逐Token",[15,1436,1437,1438,733],{},"这是\"字一个个出来\"的",[18,1439,1440],{},"最底层源头",[192,1442,1444],{"className":194,"code":1443,"language":196,"meta":197,"style":197},"for packet in llm.stream(\n    prompt=llm_msg_history,\n    tools=tool_definitions,\n    tool_choice=tool_choice,\n    max_tokens=max_tokens,\n    reasoning_effort=reasoning_effort,\n    ...\n):\n    delta = packet.choice.delta\n\n    if not answer_start:\n        yield Packet(obj=AgentResponseStart(final_documents=..., ...))\n        answer_start = True\n\n    accumulated_answer += content_chunk                    # 后端也累加一份\n    state_container.set_answer_tokens(accumulated_answer)  # 用于最终持久化\n    yield Packet(\n        placement=_current_placement(),\n        obj=AgentResponseDelta(content=content_chunk),     # 每块一个增量包\n    )\n",[47,1445,1446,1473,1498,1522,1545,1568,1591,1608,1614,1651,1655,1686,1748,1789,1794,1892,1934,1958,1998,2068],{"__ignoreMap":197},[201,1447,1448,1450,1453,1456,1459,1461,1464,1467,1469,1471],{"class":203,"data-line":204,"line":204},[201,1449,372],{"class":222},[201,1451,213],{"class":1452},[212],[201,1454,1455],{"class":302},"packet",[201,1457,213],{"class":1458},[212],[201,1460,384],{"class":222},[201,1462,213],{"class":1463},[212],[201,1465,1466],{"class":302},"llm",[201,1468,454],{"class":243},[201,1470,189],{"class":316},[201,1472,782],{"class":233},[201,1474,1475,1478,1481,1484,1487,1490,1492,1495],{"class":203,"data-line":219,"line":219},[201,1476,213],{"class":1477},[212],[201,1479,213],{"class":1480},[212],[201,1482,213],{"class":1483},[212],[201,1485,213],{"class":1486},[212],[201,1488,1489],{"class":716},"prompt",[201,1491,310],{"class":309},[201,1493,1494],{"class":302},"llm_msg_history",[201,1496,1497],{"class":243},",\n",[201,1499,1500,1503,1506,1509,1512,1515,1517,1520],{"class":203,"data-line":287,"line":287},[201,1501,213],{"class":1502},[212],[201,1504,213],{"class":1505},[212],[201,1507,213],{"class":1508},[212],[201,1510,213],{"class":1511},[212],[201,1513,1514],{"class":716},"tools",[201,1516,310],{"class":309},[201,1518,1519],{"class":302},"tool_definitions",[201,1521,1497],{"class":243},[201,1523,1524,1527,1530,1533,1536,1539,1541,1543],{"class":203,"data-line":325,"line":325},[201,1525,213],{"class":1526},[212],[201,1528,213],{"class":1529},[212],[201,1531,213],{"class":1532},[212],[201,1534,213],{"class":1535},[212],[201,1537,1538],{"class":716},"tool_choice",[201,1540,310],{"class":309},[201,1542,1538],{"class":302},[201,1544,1497],{"class":243},[201,1546,1547,1550,1553,1556,1559,1562,1564,1566],{"class":203,"data-line":345,"line":345},[201,1548,213],{"class":1549},[212],[201,1551,213],{"class":1552},[212],[201,1554,213],{"class":1555},[212],[201,1557,213],{"class":1558},[212],[201,1560,1561],{"class":716},"max_tokens",[201,1563,310],{"class":309},[201,1565,1561],{"class":302},[201,1567,1497],{"class":243},[201,1569,1570,1573,1576,1579,1582,1585,1587,1589],{"class":203,"data-line":403,"line":403},[201,1571,213],{"class":1572},[212],[201,1574,213],{"class":1575},[212],[201,1577,213],{"class":1578},[212],[201,1580,213],{"class":1581},[212],[201,1583,1584],{"class":716},"reasoning_effort",[201,1586,310],{"class":309},[201,1588,1584],{"class":302},[201,1590,1497],{"class":243},[201,1592,1593,1596,1599,1602,1605],{"class":203,"data-line":502,"line":502},[201,1594,213],{"class":1595},[212],[201,1597,213],{"class":1598},[212],[201,1600,213],{"class":1601},[212],[201,1603,213],{"class":1604},[212],[201,1606,1607],{"class":395},"...\n",[201,1609,1610,1612],{"class":203,"data-line":541,"line":541},[201,1611,237],{"class":233},[201,1613,284],{"class":243},[201,1615,1616,1619,1622,1625,1628,1631,1634,1636,1639,1641,1643,1646,1648],{"class":203,"data-line":610,"line":610},[201,1617,213],{"class":1618},[212],[201,1620,213],{"class":1621},[212],[201,1623,213],{"class":1624},[212],[201,1626,213],{"class":1627},[212],[201,1629,1630],{"class":302},"delta",[201,1632,213],{"class":1633},[212],[201,1635,310],{"class":309},[201,1637,213],{"class":1638},[212],[201,1640,1455],{"class":302},[201,1642,454],{"class":243},[201,1644,1645],{"class":302},"choice",[201,1647,454],{"class":243},[201,1649,1650],{"class":302},"delta\n",[201,1652,1653],{"class":203,"data-line":630,"line":630},[201,1654,688],{"emptyLinePlaceholder":687},[201,1656,1657,1660,1663,1666,1669,1672,1675,1678,1681,1684],{"class":203,"data-line":684,"line":684},[201,1658,213],{"class":1659},[212],[201,1661,213],{"class":1662},[212],[201,1664,213],{"class":1665},[212],[201,1667,213],{"class":1668},[212],[201,1670,1671],{"class":222},"if",[201,1673,213],{"class":1674},[212],[201,1676,1677],{"class":222},"not",[201,1679,213],{"class":1680},[212],[201,1682,1683],{"class":302},"answer_start",[201,1685,284],{"class":243},[201,1687,1688,1691,1694,1697,1700,1703,1706,1709,1712,1714,1717,1719,1721,1723,1725,1728,1730,1733,1735,1737,1739,1742,1744,1746],{"class":203,"data-line":691,"line":691},[201,1689,213],{"class":1690},[212],[201,1692,213],{"class":1693},[212],[201,1695,213],{"class":1696},[212],[201,1698,213],{"class":1699},[212],[201,1701,213],{"class":1702},[212],[201,1704,213],{"class":1705},[212],[201,1707,213],{"class":1708},[212],[201,1710,213],{"class":1711},[212],[201,1713,66],{"class":222},[201,1715,213],{"class":1716},[212],[201,1718,1007],{"class":316},[201,1720,234],{"class":233},[201,1722,378],{"class":716},[201,1724,310],{"class":309},[201,1726,1727],{"class":316},"AgentResponseStart",[201,1729,234],{"class":460},[201,1731,1732],{"class":716},"final_documents",[201,1734,310],{"class":309},[201,1736,396],{"class":395},[201,1738,264],{"class":243},[201,1740,213],{"class":1741},[212],[201,1743,396],{"class":395},[201,1745,237],{"class":460},[201,1747,322],{"class":233},[201,1749,1751,1754,1757,1760,1763,1766,1769,1772,1775,1777,1780,1782,1785],{"class":203,"data-line":1750,"line":1750},13,[201,1752,213],{"class":1753},[212],[201,1755,213],{"class":1756},[212],[201,1758,213],{"class":1759},[212],[201,1761,213],{"class":1762},[212],[201,1764,213],{"class":1765},[212],[201,1767,213],{"class":1768},[212],[201,1770,213],{"class":1771},[212],[201,1773,213],{"class":1774},[212],[201,1776,1683],{"class":302},[201,1778,213],{"class":1779},[212],[201,1781,310],{"class":309},[201,1783,213],{"class":1784},[212],[201,1786,1788],{"class":1787},"sYQis","True\n",[201,1790,1792],{"class":203,"data-line":1791,"line":1791},14,[201,1793,688],{"emptyLinePlaceholder":687},[201,1795,1797,1800,1803,1806,1809,1812,1815,1818,1821,1824,1827,1830,1833,1836,1839,1842,1845,1848,1851,1854,1857,1860,1863,1866,1869,1872,1875,1878,1881,1884,1886,1889],{"class":203,"data-line":1796,"line":1796},15,[201,1798,213],{"class":1799},[212],[201,1801,213],{"class":1802},[212],[201,1804,213],{"class":1805},[212],[201,1807,213],{"class":1808},[212],[201,1810,1811],{"class":302},"accumulated_answer",[201,1813,213],{"class":1814},[212],[201,1816,1817],{"class":309},"+=",[201,1819,213],{"class":1820},[212],[201,1822,1823],{"class":302},"content_chunk",[201,1825,213],{"class":1826},[212],[201,1828,213],{"class":1829},[212],[201,1831,213],{"class":1832},[212],[201,1834,213],{"class":1835},[212],[201,1837,213],{"class":1838},[212],[201,1840,213],{"class":1841},[212],[201,1843,213],{"class":1844},[212],[201,1846,213],{"class":1847},[212],[201,1849,213],{"class":1850},[212],[201,1852,213],{"class":1853},[212],[201,1855,213],{"class":1856},[212],[201,1858,213],{"class":1859},[212],[201,1861,213],{"class":1862},[212],[201,1864,213],{"class":1865},[212],[201,1867,213],{"class":1868},[212],[201,1870,213],{"class":1871},[212],[201,1873,213],{"class":1874},[212],[201,1876,213],{"class":1877},[212],[201,1879,213],{"class":1880},[212],[201,1882,213],{"class":1883},[212],[201,1885,208],{"class":207},[201,1887,213],{"class":1888},[212],[201,1890,1891],{"class":207},"后端也累加一份\n",[201,1893,1895,1898,1901,1904,1907,1909,1911,1914,1916,1918,1920,1923,1926,1928,1931],{"class":203,"data-line":1894,"line":1894},16,[201,1896,213],{"class":1897},[212],[201,1899,213],{"class":1900},[212],[201,1902,213],{"class":1903},[212],[201,1905,213],{"class":1906},[212],[201,1908,303],{"class":302},[201,1910,454],{"class":243},[201,1912,1913],{"class":316},"set_answer_tokens",[201,1915,234],{"class":233},[201,1917,1811],{"class":302},[201,1919,237],{"class":233},[201,1921,213],{"class":1922},[212],[201,1924,213],{"class":1925},[212],[201,1927,208],{"class":207},[201,1929,213],{"class":1930},[212],[201,1932,1933],{"class":207},"用于最终持久化\n",[201,1935,1937,1940,1943,1946,1949,1951,1954,1956],{"class":203,"data-line":1936,"line":1936},17,[201,1938,213],{"class":1939},[212],[201,1941,213],{"class":1942},[212],[201,1944,213],{"class":1945},[212],[201,1947,213],{"class":1948},[212],[201,1950,66],{"class":222},[201,1952,213],{"class":1953},[212],[201,1955,1007],{"class":316},[201,1957,782],{"class":233},[201,1959,1961,1964,1967,1970,1973,1976,1979,1982,1985,1987,1989,1992,1994,1996],{"class":203,"data-line":1960,"line":1960},18,[201,1962,213],{"class":1963},[212],[201,1965,213],{"class":1966},[212],[201,1968,213],{"class":1969},[212],[201,1971,213],{"class":1972},[212],[201,1974,213],{"class":1975},[212],[201,1977,213],{"class":1978},[212],[201,1980,213],{"class":1981},[212],[201,1983,213],{"class":1984},[212],[201,1986,1033],{"class":716},[201,1988,310],{"class":309},[201,1990,1991],{"class":316},"_current_placement",[201,1993,234],{"class":460},[201,1995,237],{"class":460},[201,1997,1497],{"class":243},[201,1999,2001,2004,2007,2010,2013,2016,2019,2022,2025,2027,2029,2032,2034,2037,2039,2041,2043,2045,2048,2051,2054,2057,2060,2062,2065],{"class":203,"data-line":2000,"line":2000},19,[201,2002,213],{"class":2003},[212],[201,2005,213],{"class":2006},[212],[201,2008,213],{"class":2009},[212],[201,2011,213],{"class":2012},[212],[201,2014,213],{"class":2015},[212],[201,2017,213],{"class":2018},[212],[201,2020,213],{"class":2021},[212],[201,2023,213],{"class":2024},[212],[201,2026,378],{"class":716},[201,2028,310],{"class":309},[201,2030,2031],{"class":316},"AgentResponseDelta",[201,2033,234],{"class":460},[201,2035,2036],{"class":716},"content",[201,2038,310],{"class":309},[201,2040,1823],{"class":302},[201,2042,237],{"class":460},[201,2044,264],{"class":243},[201,2046,213],{"class":2047},[212],[201,2049,213],{"class":2050},[212],[201,2052,213],{"class":2053},[212],[201,2055,213],{"class":2056},[212],[201,2058,213],{"class":2059},[212],[201,2061,208],{"class":207},[201,2063,213],{"class":2064},[212],[201,2066,2067],{"class":207},"每块一个增量包\n",[201,2069,2071,2074,2077,2080,2083],{"class":203,"data-line":2070,"line":2070},20,[201,2072,213],{"class":2073},[212],[201,2075,213],{"class":2076},[212],[201,2078,213],{"class":2079},[212],[201,2081,213],{"class":2082},[212],[201,2084,322],{"class":233},[40,2086,2087,2109,2115,2128],{},[43,2088,2089,2092,2093,2096,2097,2100,2101,2104,2105,2108],{},[47,2090,2091],{"code":2091},"llm.stream()"," 的实现封装在 ",[47,2094,2095],{"code":2095},"backend/onyx/llm/multi_llm.py:981","，底层是 ",[18,2098,2099],{},"litellm"," 的 ",[47,2102,2103],{"code":2103},"CustomStreamWrapper","（",[47,2106,2107],{"code":2107},"multi_llm.py:992","），再往下是各家 LLM 的 HTTP 流。",[43,2110,2111,2114],{},[18,2112,2113],{},"每次迭代只有一个 token（或一小段）","。这就是\"一个一个\"的最小粒度。",[43,2116,2117,2118,2120,2121,2123,2124,2127],{},"回答开始前还有一个 ",[47,2119,1727],{"code":1727}," 包，携带 ",[47,2122,1732],{"code":1732},"（检索到的文档）和 ",[47,2125,2126],{"code":2126},"pre_answer_processing_seconds","（从发消息到开始作答的耗时）。",[43,2129,2130,2131,2134,2135,2138],{},"同时 ",[47,2132,2133],{"code":2133},"accumulated_answer += content_chunk","：后端维护一个",[18,2136,2137],{},"累积文本","，用于流结束后一次性持久化到数据库（前端是增量的，数据库要的是完整文本）。",[763,2140,2141],{"id":2141},"回答带引用",[15,2143,2144,2145,971,2148,2151,2152,2155,2156,2159,2160,2163,2164,31],{},"真实项目里回答文本中常带 ",[47,2146,2147],{"code":2147},"[1]",[47,2149,2150],{"code":2150},"[2]"," 这样的引文标记。\n代码通过 ",[47,2153,2154],{"code":2154},"citation_processor.process_token(content_chunk)","（llm_step.py:1258）",[18,2157,2158],{},"逐 token 扫描","，把属于引文标记的部分拆出来单独发 ",[47,2161,2162],{"code":2162},"CitationInfo"," 包，正文里的标记换成占位符。前端再把引文号和文档 ID 对应起来，渲染成可点击的 ",[201,2165,2166],{},"1",[763,2168,2169],{"id":2169},"并发",[23,2171,2172],{},[15,2173,2174],{},"多个模型同时生产数据，一个writer统一收集和保存，一个reader负责把数据发给浏览器",[15,2176,2177,2179],{},[47,2178,1420],{"code":1420},"（process_message.py:1062）是后端流式的\"调度中心\"。核心设计：",[40,2181,2182,2202,2226],{},[43,2183,2184,2104,2187,2190,2191,2194,2195,213,2198,2201],{},[18,2185,2186],{},"每个模型一个 worker 线程",[47,2188,2189],{"code":2189},"_run_model","，process_message.py:1209），各自的 ",[47,2192,2193],{"code":2193},"Emitter"," 把 Packet 推进一个共享的",[18,2196,2197],{},"无界队列",[47,2199,2200],{"code":2200},"merged_queue","；",[43,2203,2204,2104,2207,2210,2211],{},[18,2205,2206],{},"一个 writer 线程",[47,2208,2209],{"code":2209},"_drain_to_completion","）从队列 drain，负责两件事：\n",[1392,2212,2213,2219],{},[43,2214,2215,2216,2218],{},"把每个包写入 ",[47,2217,1413],{"code":1413},"（供断线回放）；",[43,2220,2221,2222,2225],{},"转发给 reader 的 ",[47,2223,2224],{"code":2224},"tee"," 队列；",[43,2227,2228,2104,2231,2234,2235,2237],{},[18,2229,2230],{},"reader 生成器",[47,2232,2233],{"code":2233},"_read_stream","，process_message.py:1496）从 ",[47,2236,2224],{"code":2224}," 里取包 yield 给 HTTP。",[192,2239,2242],{"className":2240,"code":2241,"language":1382},[1380],"worker0 ──Emitter──▶ merged_queue ──┐\nworker1 ──Emitter──▶ merged_queue ──┼─▶ writer线程 ──▶ tee队列 ──▶ reader → yield → HTTP\nworker2 ──Emitter──▶ merged_queue ──┘      │\n                                          └─▶ StreamBufferWriter（持久化缓冲）\n",[47,2243,2241],{"__ignoreMap":197},[15,2245,2246],{},"几个关键点：",[40,2248,2249,2259,2272],{},[43,2250,2251,2254,2255,2258],{},[18,2252,2253],{},"多生产者单消费者","：多个模型并发产包，但最终合成",[18,2256,2257],{},"一个"," SSE 流（按到达顺序交错）。",[43,2260,2261,2264,2265,2268,2269,31],{},[18,2262,2263],{},"reader 可以\"死\"而 writer 必须活","：客户端断开（GeneratorExit）时，reader 直接 return，但 writer 继续把剩余包写进缓冲（",[47,2266,2267],{"code":2267},"process_message.py:1515-1523","），这样",[18,2270,2271],{},"断线的客户端重连后能回放到完整内容",[43,2273,2274,2277,2278,2280],{},[47,2275,2276],{"code":2276},"reader_gone"," 事件标记 reader 已离开，",[47,2279,2224],{"code":2224}," 队列停止累积无人消费的包，避免内存泄漏。",[10,2282,2283],{"id":2283},"前端",[763,2285,2286],{"id":2286},"发消息",[15,2288,2289,2290,2293],{},"发消息：",[47,2291,2292],{"code":2292},"sendMessage","（app/services/lib.tsx:145）",[192,2295,2299],{"className":2296,"code":2297,"language":2298,"meta":197,"style":197},"language-ts shiki shiki-themes catppuccin-latte one-dark-pro","const response = await fetch(`/api/chat/send-chat-message`, {\n  method: \"POST\",\n  headers: { \"Content-Type\": \"application/json\" },\n  body,\n  signal,                       // ← 取消信号\n});\n\nif (!response.ok) { /* 抛错 */ }\n\nyield* withoutHeartbeats(handleSSEStream\u003CPacketType>(response, signal));\n","ts",[47,2300,2301,2342,2365,2402,2415,2512,2521,2525,2572,2576],{"__ignoreMap":197},[201,2302,2303,2306,2309,2313,2316,2318,2321,2324,2327,2330,2332,2335,2337,2339],{"class":203,"data-line":204,"line":204},[201,2304,2305],{"class":222},"const",[201,2307,213],{"class":2308},[212],[201,2310,2312],{"class":2311},"szf8G","response",[201,2314,213],{"class":2315},[212],[201,2317,310],{"class":309},[201,2319,213],{"class":2320},[212],[201,2322,2323],{"class":222},"await",[201,2325,213],{"class":2326},[212],[201,2328,2329],{"class":229},"fetch",[201,2331,234],{"class":233},[201,2333,2334],{"class":586},"`/api/chat/send-chat-message`",[201,2336,264],{"class":243},[201,2338,213],{"class":243},[201,2340,2341],{"class":460},"{\n",[201,2343,2344,2347,2350,2354,2357,2360,2363],{"class":203,"data-line":219,"line":219},[201,2345,213],{"class":2346},[212],[201,2348,213],{"class":2349},[212],[201,2351,2353],{"class":2352},"sIGPt","method",[201,2355,590],{"class":2356},"sxizN",[201,2358,213],{"class":2359},[212],[201,2361,2362],{"class":586},"\"POST\"",[201,2364,1497],{"class":243},[201,2366,2367,2370,2373,2376,2378,2380,2382,2385,2388,2390,2393,2396,2398,2400],{"class":203,"data-line":287,"line":287},[201,2368,213],{"class":2369},[212],[201,2371,213],{"class":2372},[212],[201,2374,2375],{"class":2352},"headers",[201,2377,590],{"class":2356},[201,2379,213],{"class":243},[201,2381,583],{"class":598},[201,2383,213],{"class":2384},[212],[201,2386,2387],{"class":586},"\"Content-Type\"",[201,2389,590],{"class":2356},[201,2391,213],{"class":2392},[212],[201,2394,2395],{"class":586},"\"application/json\"",[201,2397,213],{"class":243},[201,2399,605],{"class":598},[201,2401,1497],{"class":243},[201,2403,2404,2407,2410,2413],{"class":203,"data-line":325,"line":325},[201,2405,213],{"class":2406},[212],[201,2408,213],{"class":2409},[212],[201,2411,2412],{"class":2352},"body",[201,2414,1497],{"class":243},[201,2416,2417,2420,2423,2426,2428,2431,2434,2437,2440,2443,2446,2449,2452,2455,2458,2461,2464,2467,2470,2473,2476,2479,2482,2485,2488,2491,2494,2497,2500,2503,2506,2509],{"class":203,"data-line":345,"line":345},[201,2418,213],{"class":2419},[212],[201,2421,213],{"class":2422},[212],[201,2424,2425],{"class":2352},"signal",[201,2427,264],{"class":243},[201,2429,213],{"class":2430},[212],[201,2432,213],{"class":2433},[212],[201,2435,213],{"class":2436},[212],[201,2438,213],{"class":2439},[212],[201,2441,213],{"class":2442},[212],[201,2444,213],{"class":2445},[212],[201,2447,213],{"class":2448},[212],[201,2450,213],{"class":2451},[212],[201,2453,213],{"class":2454},[212],[201,2456,213],{"class":2457},[212],[201,2459,213],{"class":2460},[212],[201,2462,213],{"class":2463},[212],[201,2465,213],{"class":2466},[212],[201,2468,213],{"class":2469},[212],[201,2471,213],{"class":2472},[212],[201,2474,213],{"class":2475},[212],[201,2477,213],{"class":2478},[212],[201,2480,213],{"class":2481},[212],[201,2483,213],{"class":2484},[212],[201,2486,213],{"class":2487},[212],[201,2489,213],{"class":2490},[212],[201,2492,213],{"class":2493},[212],[201,2495,213],{"class":2496},[212],[201,2498,2499],{"class":207},"//",[201,2501,213],{"class":2502},[212],[201,2504,2505],{"class":207},"←",[201,2507,213],{"class":2508},[212],[201,2510,2511],{"class":207},"取消信号\n",[201,2513,2514,2516,2518],{"class":203,"data-line":403,"line":403},[201,2515,605],{"class":460},[201,2517,237],{"class":233},[201,2519,2520],{"class":243},";\n",[201,2522,2523],{"class":203,"data-line":502,"line":502},[201,2524,688],{"emptyLinePlaceholder":687},[201,2526,2527,2529,2531,2533,2536,2538,2540,2543,2545,2547,2549,2552,2555,2558,2561,2564,2567,2569],{"class":203,"data-line":541,"line":541},[201,2528,1671],{"class":222},[201,2530,213],{"class":302},[201,2532,234],{"class":233},[201,2534,2535],{"class":309},"!",[201,2537,2312],{"class":2311},[201,2539,454],{"class":2356},[201,2541,2542],{"class":2352},"ok",[201,2544,237],{"class":233},[201,2546,213],{"class":302},[201,2548,583],{"class":233},[201,2550,213],{"class":2551},[212],[201,2553,2554],{"class":207},"/*",[201,2556,213],{"class":2557},[212],[201,2559,2560],{"class":207},"抛错",[201,2562,213],{"class":2563},[212],[201,2565,2566],{"class":207},"*/",[201,2568,213],{"class":243},[201,2570,2571],{"class":233},"}\n",[201,2573,2574],{"class":203,"data-line":610,"line":610},[201,2575,688],{"emptyLinePlaceholder":687},[201,2577,2578,2581,2584,2587,2589,2592,2595,2597,2599,2601,2603,2605,2608,2610,2612,2614],{"class":203,"data-line":630,"line":630},[201,2579,2580],{"class":222},"yield*",[201,2582,213],{"class":2583},[212],[201,2585,2586],{"class":229},"withoutHeartbeats",[201,2588,234],{"class":233},[201,2590,2591],{"class":229},"handleSSEStream",[201,2593,2594],{"class":460},"\u003C",[201,2596,1361],{"class":1006},[201,2598,247],{"class":460},[201,2600,234],{"class":460},[201,2602,2312],{"class":2352},[201,2604,264],{"class":243},[201,2606,213],{"class":2607},[212],[201,2609,2425],{"class":2352},[201,2611,237],{"class":460},[201,2613,237],{"class":233},[201,2615,2520],{"class":243},[15,2617,2618,733,2621,2623,2624,2627,2628,213,2631,2634,2635,2637,2638,2641,2642,2644,2645,2647,2648,2651],{},[18,2619,2620],{},"最反直觉的一点",[47,2622,2329],{"code":2329}," 在这里返回的是一个 ",[47,2625,2626],{"code":2626},"Response","，但 ",[47,2629,2630],{"code":2630},"await fetch(...)",[18,2632,2633],{},"只等响应头","，不等 body。因为 ",[47,2636,2292],{"code":2292}," 是 ",[47,2639,2640],{"code":2640},"async function*","（异步生成器），它把 ",[47,2643,2626],{"code":2626}," 交给 ",[47,2646,2591],{"code":2591},"，调用方再 ",[47,2649,2650],{"code":2650},"for await"," 逐包消费。",[15,2653,2654,2656],{},[47,2655,2586],{"code":2586},"（lib.tsx:207）是一个过滤层：",[192,2658,2660],{"className":2296,"code":2659,"language":2298,"meta":197,"style":197},"async function* withoutHeartbeats(stream) {\n  for await (const packet of stream) {\n    if (\"obj\" in packet && packet.obj.type === \"chat_heartbeat\") continue;\n    yield packet;\n  }\n}\n",[47,2661,2662,2688,2731,2804,2827,2837],{"__ignoreMap":197},[201,2663,2664,2667,2670,2673,2676,2678,2680,2682,2684,2686],{"class":203,"data-line":204,"line":204},[201,2665,2666],{"class":222},"async",[201,2668,213],{"class":2669},[212],[201,2671,2672],{"class":222},"function*",[201,2674,213],{"class":2675},[212],[201,2677,2586],{"class":229},[201,2679,234],{"class":233},[201,2681,189],{"class":716},[201,2683,237],{"class":233},[201,2685,213],{"class":243},[201,2687,2341],{"class":233},[201,2689,2690,2693,2696,2698,2701,2703,2705,2707,2709,2712,2714,2717,2720,2723,2725,2727,2729],{"class":203,"data-line":219,"line":219},[201,2691,213],{"class":2692},[212],[201,2694,213],{"class":2695},[212],[201,2697,372],{"class":222},[201,2699,213],{"class":2700},[212],[201,2702,2323],{"class":222},[201,2704,213],{"class":302},[201,2706,234],{"class":460},[201,2708,2305],{"class":222},[201,2710,213],{"class":2711},[212],[201,2713,1455],{"class":2311},[201,2715,213],{"class":2716},[212],[201,2718,2719],{"class":222},"of",[201,2721,213],{"class":2722},[212],[201,2724,189],{"class":2352},[201,2726,237],{"class":460},[201,2728,213],{"class":302},[201,2730,2341],{"class":460},[201,2732,2733,2736,2739,2742,2745,2747,2749,2751,2754,2757,2759,2762,2764,2767,2770,2773,2775,2777,2779,2781,2783,2786,2789,2792,2795,2797,2799,2802],{"class":203,"data-line":287,"line":287},[201,2734,213],{"class":2735},[212],[201,2737,213],{"class":2738},[212],[201,2740,213],{"class":2741},[212],[201,2743,213],{"class":2744},[212],[201,2746,1671],{"class":222},[201,2748,213],{"class":302},[201,2750,234],{"class":598},[201,2752,2753],{"class":586},"\"obj\"",[201,2755,213],{"class":2756},[212],[201,2758,384],{"class":222},[201,2760,213],{"class":2761},[212],[201,2763,1455],{"class":2352},[201,2765,213],{"class":2766},[212],[201,2768,2769],{"class":309},"&&",[201,2771,213],{"class":2772},[212],[201,2774,1455],{"class":2311},[201,2776,454],{"class":2356},[201,2778,378],{"class":2311},[201,2780,454],{"class":2356},[201,2782,838],{"class":2352},[201,2784,213],{"class":2785},[212],[201,2787,2788],{"class":309},"===",[201,2790,213],{"class":2791},[212],[201,2793,2794],{"class":586},"\"chat_heartbeat\"",[201,2796,237],{"class":598},[201,2798,213],{"class":302},[201,2800,2801],{"class":222},"continue",[201,2803,2520],{"class":243},[201,2805,2806,2809,2812,2815,2818,2820,2823,2825],{"class":203,"data-line":325,"line":325},[201,2807,213],{"class":2808},[212],[201,2810,213],{"class":2811},[212],[201,2813,213],{"class":2814},[212],[201,2816,213],{"class":2817},[212],[201,2819,66],{"class":222},[201,2821,213],{"class":2822},[212],[201,2824,1455],{"class":2352},[201,2826,2520],{"class":243},[201,2828,2829,2832,2835],{"class":203,"data-line":345,"line":345},[201,2830,213],{"class":2831},[212],[201,2833,213],{"class":2834},[212],[201,2836,2571],{"class":460},[201,2838,2839],{"class":203,"data-line":403,"line":403},[201,2840,2571],{"class":233},[15,2842,2843],{},"心跳包不承载运行状态，消费端一律看不见它（心跳本身干什么，见下文）。",[763,2845,2846],{"id":2846},"核心解析器",[192,2848,2850],{"className":2296,"code":2849,"language":2298,"meta":197,"style":197},"export async function* handleSSEStream\u003CT extends PacketType>(\n  streamingResponse: Response,\n  signal?: AbortSignal\n): AsyncGenerator\u003CT, void, unknown> {\n  const reader = streamingResponse.body?.getReader();  // ① 取流的 reader\n  const decoder = new TextDecoder();                    // ② 二进制 → 字符串\n  let buffer = \"\";                                      // ③ 半包缓冲\n\n  if (signal) {\n    signal.addEventListener(\"abort\", () => {\n      reader?.cancel();                                 // ④ 中止 → 取消底层连接\n    });\n  }\n\n  try {\n    while (true) {\n      const rawChunk = await reader?.read();            // ⑤ 异步读一块（可能几个包 or 半个包）\n      const { done, value } = rawChunk;\n      if (done) break;                                  // 流结束\n\n      buffer += decoder.decode(value, { stream: true }); // ⑥ 关键：stream 模式解码\n\n      const lines = buffer.split(\"\\n\");                 // ⑦ 按换行切\n      buffer = lines.pop() || \"\";                       // ⑧ 最后不完整的一行留回 buffer\n\n      for (const line of lines) {\n        if (line.trim() === \"\") continue;\n        try {\n          const data = JSON.parse(line) as T;           // ⑨ 还原对象\n          yield data;                                    // ⑩ 吐给调用方\n        } catch (error) {\n          // 某些工具结果会把多个 JSON 挤在一行，用正则兜底提取\n          const jsonObjects = line.match(/\\{[^{}]*\\}/g);\n          if (jsonObjects) {\n            for (const jsonObj of jsonObjects) {\n              try {\n                yield JSON.parse(jsonObj) as T;\n              } catch (innerError) { /* 忽略 */ }\n            }\n          }\n        }\n      }\n    }\n\n    // 流结束后处理 buffer 里残留的半行\n    if (buffer.trim() !== \"\") {\n      try { yield JSON.parse(buffer) as T; } catch { /* 忽略 */ }\n    }\n  } finally {\n    // ⑪ 生成器被提前 break 时必须 cancel，否则连接永远挂着\n    void reader?.cancel().catch(() => {});\n  }\n}\n",[47,2851,2852,2892,2912,2934,2973,3042,3167,3325,3329,3351,3397,3555,3575,3585,3589,3603,3633,3749,3805,3952,3956,4047,4052,4173,4316,4321,4371,4433,4466,4588,4752,4799,4851,4942,4989,5058,5109,5188,5273,5314,5349,5378,5401,5418,5423,5457,5504,5594,5611,5631,5677,5734,5745],{"__ignoreMap":197},[201,2853,2854,2857,2860,2862,2865,2867,2870,2872,2874,2877,2880,2883,2886,2888,2890],{"class":203,"data-line":204,"line":204},[201,2855,2856],{"class":222},"export",[201,2858,213],{"class":2859},[212],[201,2861,2666],{"class":222},[201,2863,213],{"class":2864},[212],[201,2866,2672],{"class":222},[201,2868,213],{"class":2869},[212],[201,2871,2591],{"class":229},[201,2873,2594],{"class":233},[201,2875,2876],{"class":1006},"T",[201,2878,213],{"class":2879},[212],[201,2881,2882],{"class":222},"extends",[201,2884,213],{"class":2885},[212],[201,2887,1361],{"class":1006},[201,2889,247],{"class":233},[201,2891,782],{"class":233},[201,2893,2894,2897,2900,2903,2905,2908,2910],{"class":203,"data-line":219,"line":219},[201,2895,213],{"class":2896},[212],[201,2898,213],{"class":2899},[212],[201,2901,2902],{"class":716},"streamingResponse",[201,2904,590],{"class":2356},[201,2906,213],{"class":2907},[212],[201,2909,2626],{"class":1006},[201,2911,1497],{"class":243},[201,2913,2914,2917,2920,2922,2926,2928,2931],{"class":203,"data-line":287,"line":287},[201,2915,213],{"class":2916},[212],[201,2918,213],{"class":2919},[212],[201,2921,2425],{"class":716},[201,2923,2925],{"class":2924},"saddK","?",[201,2927,590],{"class":2356},[201,2929,213],{"class":2930},[212],[201,2932,2933],{"class":1006},"AbortSignal\n",[201,2935,2936,2938,2940,2943,2946,2948,2950,2952,2955,2959,2961,2964,2967,2969,2971],{"class":203,"data-line":325,"line":325},[201,2937,237],{"class":233},[201,2939,590],{"class":2356},[201,2941,213],{"class":2942},[212],[201,2944,2945],{"class":1006},"AsyncGenerator",[201,2947,2594],{"class":233},[201,2949,2876],{"class":1006},[201,2951,264],{"class":243},[201,2953,213],{"class":2954},[212],[201,2956,2958],{"class":2957},"sPahJ","void",[201,2960,264],{"class":243},[201,2962,213],{"class":2963},[212],[201,2965,2966],{"class":2957},"unknown",[201,2968,247],{"class":233},[201,2970,213],{"class":243},[201,2972,2341],{"class":233},[201,2974,2975,2978,2981,2983,2986,2989,2992,2994,2997,2999,3001,3003,3006,3009,3011,3013,3016,3019,3022,3024,3027,3030,3033,3036,3039],{"class":203,"data-line":345,"line":345},[201,2976,213],{"class":2977},[212],[201,2979,213],{"class":2980},[212],[201,2982,2305],{"class":222},[201,2984,213],{"class":2985},[212],[201,2987,2988],{"class":2311},"reader",[201,2990,213],{"class":2991},[212],[201,2993,310],{"class":309},[201,2995,213],{"class":2996},[212],[201,2998,2902],{"class":2311},[201,3000,454],{"class":2356},[201,3002,2412],{"class":2311},[201,3004,3005],{"class":2356},"?.",[201,3007,3008],{"class":229},"getReader",[201,3010,234],{"class":460},[201,3012,237],{"class":460},[201,3014,3015],{"class":243},";",[201,3017,213],{"class":3018},[212],[201,3020,213],{"class":3021},[212],[201,3023,2499],{"class":207},[201,3025,213],{"class":3026},[212],[201,3028,3029],{"class":207},"①",[201,3031,213],{"class":3032},[212],[201,3034,3035],{"class":207},"取流的",[201,3037,213],{"class":3038},[212],[201,3040,3041],{"class":207},"reader\n",[201,3043,3044,3047,3050,3052,3055,3058,3061,3063,3066,3070,3073,3076,3078,3080,3082,3085,3088,3091,3094,3097,3100,3103,3106,3109,3112,3115,3118,3121,3124,3127,3130,3133,3136,3139,3142,3144,3147,3150,3153,3156,3159,3161,3164],{"class":203,"data-line":403,"line":403},[201,3045,213],{"class":3046},[212],[201,3048,213],{"class":3049},[212],[201,3051,2305],{"class":222},[201,3053,213],{"class":3054},[212],[201,3056,3057],{"class":2311},"decoder",[201,3059,213],{"class":3060},[212],[201,3062,310],{"class":309},[201,3064,213],{"class":3065},[212],[201,3067,3069],{"class":3068},"snqon","new",[201,3071,213],{"class":3072},[212],[201,3074,3075],{"class":229},"TextDecoder",[201,3077,234],{"class":460},[201,3079,237],{"class":460},[201,3081,3015],{"class":243},[201,3083,213],{"class":3084},[212],[201,3086,213],{"class":3087},[212],[201,3089,213],{"class":3090},[212],[201,3092,213],{"class":3093},[212],[201,3095,213],{"class":3096},[212],[201,3098,213],{"class":3099},[212],[201,3101,213],{"class":3102},[212],[201,3104,213],{"class":3105},[212],[201,3107,213],{"class":3108},[212],[201,3110,213],{"class":3111},[212],[201,3113,213],{"class":3114},[212],[201,3116,213],{"class":3117},[212],[201,3119,213],{"class":3120},[212],[201,3122,213],{"class":3123},[212],[201,3125,213],{"class":3126},[212],[201,3128,213],{"class":3129},[212],[201,3131,213],{"class":3132},[212],[201,3134,213],{"class":3135},[212],[201,3137,213],{"class":3138},[212],[201,3140,213],{"class":3141},[212],[201,3143,2499],{"class":207},[201,3145,213],{"class":3146},[212],[201,3148,3149],{"class":207},"②",[201,3151,213],{"class":3152},[212],[201,3154,3155],{"class":207},"二进制",[201,3157,213],{"class":3158},[212],[201,3160,487],{"class":207},[201,3162,213],{"class":3163},[212],[201,3165,3166],{"class":207},"字符串\n",[201,3168,3169,3172,3175,3178,3181,3184,3187,3189,3192,3195,3197,3200,3203,3206,3209,3212,3215,3218,3221,3224,3227,3230,3233,3236,3239,3242,3245,3248,3251,3254,3257,3260,3263,3266,3269,3272,3275,3278,3281,3284,3287,3290,3293,3296,3299,3302,3305,3308,3311,3313,3316,3319,3322],{"class":203,"data-line":502,"line":502},[201,3170,213],{"class":3171},[212],[201,3173,213],{"class":3174},[212],[201,3176,3177],{"class":222},"let",[201,3179,213],{"class":3180},[212],[201,3182,3183],{"class":2352},"buffer",[201,3185,213],{"class":3186},[212],[201,3188,310],{"class":309},[201,3190,213],{"class":3191},[212],[201,3193,3194],{"class":586},"\"\"",[201,3196,3015],{"class":243},[201,3198,213],{"class":3199},[212],[201,3201,213],{"class":3202},[212],[201,3204,213],{"class":3205},[212],[201,3207,213],{"class":3208},[212],[201,3210,213],{"class":3211},[212],[201,3213,213],{"class":3214},[212],[201,3216,213],{"class":3217},[212],[201,3219,213],{"class":3220},[212],[201,3222,213],{"class":3223},[212],[201,3225,213],{"class":3226},[212],[201,3228,213],{"class":3229},[212],[201,3231,213],{"class":3232},[212],[201,3234,213],{"class":3235},[212],[201,3237,213],{"class":3238},[212],[201,3240,213],{"class":3241},[212],[201,3243,213],{"class":3244},[212],[201,3246,213],{"class":3247},[212],[201,3249,213],{"class":3250},[212],[201,3252,213],{"class":3253},[212],[201,3255,213],{"class":3256},[212],[201,3258,213],{"class":3259},[212],[201,3261,213],{"class":3262},[212],[201,3264,213],{"class":3265},[212],[201,3267,213],{"class":3268},[212],[201,3270,213],{"class":3271},[212],[201,3273,213],{"class":3274},[212],[201,3276,213],{"class":3277},[212],[201,3279,213],{"class":3280},[212],[201,3282,213],{"class":3283},[212],[201,3285,213],{"class":3286},[212],[201,3288,213],{"class":3289},[212],[201,3291,213],{"class":3292},[212],[201,3294,213],{"class":3295},[212],[201,3297,213],{"class":3298},[212],[201,3300,213],{"class":3301},[212],[201,3303,213],{"class":3304},[212],[201,3306,213],{"class":3307},[212],[201,3309,213],{"class":3310},[212],[201,3312,2499],{"class":207},[201,3314,213],{"class":3315},[212],[201,3317,3318],{"class":207},"③",[201,3320,213],{"class":3321},[212],[201,3323,3324],{"class":207},"半包缓冲\n",[201,3326,3327],{"class":203,"data-line":541,"line":541},[201,3328,688],{"emptyLinePlaceholder":687},[201,3330,3331,3334,3337,3339,3341,3343,3345,3347,3349],{"class":203,"data-line":610,"line":610},[201,3332,213],{"class":3333},[212],[201,3335,213],{"class":3336},[212],[201,3338,1671],{"class":222},[201,3340,213],{"class":302},[201,3342,234],{"class":460},[201,3344,2425],{"class":2352},[201,3346,237],{"class":460},[201,3348,213],{"class":302},[201,3350,2341],{"class":460},[201,3352,3353,3356,3359,3362,3365,3367,3369,3372,3374,3377,3379,3381,3384,3386,3389,3391,3393,3395],{"class":203,"data-line":630,"line":630},[201,3354,213],{"class":3355},[212],[201,3357,213],{"class":3358},[212],[201,3360,213],{"class":3361},[212],[201,3363,213],{"class":3364},[212],[201,3366,2425],{"class":2311},[201,3368,454],{"class":2356},[201,3370,3371],{"class":229},"addEventListener",[201,3373,234],{"class":598},[201,3375,3376],{"class":586},"\"abort\"",[201,3378,264],{"class":243},[201,3380,213],{"class":243},[201,3382,234],{"class":3383},"sqrby",[201,3385,237],{"class":3383},[201,3387,213],{"class":3388},[212],[201,3390,310],{"class":2924},[201,3392,247],{"class":2924},[201,3394,213],{"class":243},[201,3396,2341],{"class":3383},[201,3398,3399,3402,3405,3408,3411,3414,3417,3419,3421,3424,3427,3429,3431,3434,3437,3440,3443,3446,3449,3452,3455,3458,3461,3464,3467,3470,3473,3476,3479,3482,3485,3488,3491,3494,3497,3500,3503,3506,3509,3512,3515,3518,3521,3524,3527,3530,3532,3535,3538,3541,3544,3547,3549,3552],{"class":203,"data-line":684,"line":684},[201,3400,213],{"class":3401},[212],[201,3403,213],{"class":3404},[212],[201,3406,213],{"class":3407},[212],[201,3409,213],{"class":3410},[212],[201,3412,213],{"class":3413},[212],[201,3415,213],{"class":3416},[212],[201,3418,2988],{"class":2311},[201,3420,3005],{"class":2356},[201,3422,3423],{"class":229},"cancel",[201,3425,234],{"class":3426},"sUpK1",[201,3428,237],{"class":3426},[201,3430,3015],{"class":243},[201,3432,213],{"class":3433},[212],[201,3435,213],{"class":3436},[212],[201,3438,213],{"class":3439},[212],[201,3441,213],{"class":3442},[212],[201,3444,213],{"class":3445},[212],[201,3447,213],{"class":3448},[212],[201,3450,213],{"class":3451},[212],[201,3453,213],{"class":3454},[212],[201,3456,213],{"class":3457},[212],[201,3459,213],{"class":3460},[212],[201,3462,213],{"class":3463},[212],[201,3465,213],{"class":3466},[212],[201,3468,213],{"class":3469},[212],[201,3471,213],{"class":3472},[212],[201,3474,213],{"class":3475},[212],[201,3477,213],{"class":3478},[212],[201,3480,213],{"class":3481},[212],[201,3483,213],{"class":3484},[212],[201,3486,213],{"class":3487},[212],[201,3489,213],{"class":3490},[212],[201,3492,213],{"class":3493},[212],[201,3495,213],{"class":3496},[212],[201,3498,213],{"class":3499},[212],[201,3501,213],{"class":3502},[212],[201,3504,213],{"class":3505},[212],[201,3507,213],{"class":3508},[212],[201,3510,213],{"class":3511},[212],[201,3513,213],{"class":3514},[212],[201,3516,213],{"class":3517},[212],[201,3519,213],{"class":3520},[212],[201,3522,213],{"class":3523},[212],[201,3525,213],{"class":3526},[212],[201,3528,213],{"class":3529},[212],[201,3531,2499],{"class":207},[201,3533,213],{"class":3534},[212],[201,3536,3537],{"class":207},"④",[201,3539,213],{"class":3540},[212],[201,3542,3543],{"class":207},"中止",[201,3545,213],{"class":3546},[212],[201,3548,487],{"class":207},[201,3550,213],{"class":3551},[212],[201,3553,3554],{"class":207},"取消底层连接\n",[201,3556,3557,3560,3563,3566,3569,3571,3573],{"class":203,"data-line":691,"line":691},[201,3558,213],{"class":3559},[212],[201,3561,213],{"class":3562},[212],[201,3564,213],{"class":3565},[212],[201,3567,213],{"class":3568},[212],[201,3570,605],{"class":3383},[201,3572,237],{"class":598},[201,3574,2520],{"class":243},[201,3576,3577,3580,3583],{"class":203,"data-line":1750,"line":1750},[201,3578,213],{"class":3579},[212],[201,3581,213],{"class":3582},[212],[201,3584,2571],{"class":460},[201,3586,3587],{"class":203,"data-line":1791,"line":1791},[201,3588,688],{"emptyLinePlaceholder":687},[201,3590,3591,3594,3597,3599,3601],{"class":203,"data-line":1796,"line":1796},[201,3592,213],{"class":3593},[212],[201,3595,213],{"class":3596},[212],[201,3598,340],{"class":222},[201,3600,213],{"class":243},[201,3602,2341],{"class":460},[201,3604,3605,3608,3611,3614,3617,3620,3622,3624,3627,3629,3631],{"class":203,"data-line":1894,"line":1894},[201,3606,213],{"class":3607},[212],[201,3609,213],{"class":3610},[212],[201,3612,213],{"class":3613},[212],[201,3615,213],{"class":3616},[212],[201,3618,3619],{"class":222},"while",[201,3621,213],{"class":302},[201,3623,234],{"class":598},[201,3625,3626],{"class":1787},"true",[201,3628,237],{"class":598},[201,3630,213],{"class":302},[201,3632,2341],{"class":598},[201,3634,3635,3638,3641,3644,3647,3650,3653,3655,3658,3661,3664,3666,3669,3671,3674,3676,3678,3681,3683,3685,3687,3690,3693,3696,3699,3702,3705,3708,3711,3714,3717,3720,3723,3725,3728,3731,3734,3737,3740,3743,3746],{"class":203,"data-line":1936,"line":1936},[201,3636,213],{"class":3637},[212],[201,3639,213],{"class":3640},[212],[201,3642,213],{"class":3643},[212],[201,3645,213],{"class":3646},[212],[201,3648,213],{"class":3649},[212],[201,3651,213],{"class":3652},[212],[201,3654,2305],{"class":222},[201,3656,213],{"class":3657},[212],[201,3659,3660],{"class":2311},"rawChunk",[201,3662,213],{"class":3663},[212],[201,3665,310],{"class":309},[201,3667,213],{"class":3668},[212],[201,3670,2323],{"class":222},[201,3672,213],{"class":3673},[212],[201,3675,2988],{"class":2311},[201,3677,3005],{"class":2356},[201,3679,3680],{"class":229},"read",[201,3682,234],{"class":3383},[201,3684,237],{"class":3383},[201,3686,3015],{"class":243},[201,3688,213],{"class":3689},[212],[201,3691,213],{"class":3692},[212],[201,3694,213],{"class":3695},[212],[201,3697,213],{"class":3698},[212],[201,3700,213],{"class":3701},[212],[201,3703,213],{"class":3704},[212],[201,3706,213],{"class":3707},[212],[201,3709,213],{"class":3710},[212],[201,3712,213],{"class":3713},[212],[201,3715,213],{"class":3716},[212],[201,3718,213],{"class":3719},[212],[201,3721,213],{"class":3722},[212],[201,3724,2499],{"class":207},[201,3726,213],{"class":3727},[212],[201,3729,3730],{"class":207},"⑤",[201,3732,213],{"class":3733},[212],[201,3735,3736],{"class":207},"异步读一块（可能几个包",[201,3738,213],{"class":3739},[212],[201,3741,3742],{"class":207},"or",[201,3744,213],{"class":3745},[212],[201,3747,3748],{"class":207},"半个包）\n",[201,3750,3751,3754,3757,3760,3763,3766,3769,3771,3773,3775,3778,3781,3783,3786,3789,3791,3793,3796,3798,3801,3803],{"class":203,"data-line":1960,"line":1960},[201,3752,213],{"class":3753},[212],[201,3755,213],{"class":3756},[212],[201,3758,213],{"class":3759},[212],[201,3761,213],{"class":3762},[212],[201,3764,213],{"class":3765},[212],[201,3767,213],{"class":3768},[212],[201,3770,2305],{"class":222},[201,3772,213],{"class":243},[201,3774,583],{"class":3383},[201,3776,213],{"class":3777},[212],[201,3779,3780],{"class":2311},"done",[201,3782,264],{"class":243},[201,3784,213],{"class":3785},[212],[201,3787,3788],{"class":2311},"value",[201,3790,213],{"class":243},[201,3792,605],{"class":3383},[201,3794,213],{"class":3795},[212],[201,3797,310],{"class":309},[201,3799,213],{"class":3800},[212],[201,3802,3660],{"class":2352},[201,3804,2520],{"class":243},[201,3806,3807,3810,3813,3816,3819,3822,3825,3827,3829,3831,3833,3835,3837,3840,3842,3845,3848,3851,3854,3857,3860,3863,3866,3869,3872,3875,3878,3881,3884,3887,3890,3893,3896,3899,3902,3905,3908,3911,3914,3917,3920,3923,3926,3929,3932,3935,3938,3941,3944,3946,3949],{"class":203,"data-line":2000,"line":2000},[201,3808,213],{"class":3809},[212],[201,3811,213],{"class":3812},[212],[201,3814,213],{"class":3815},[212],[201,3817,213],{"class":3818},[212],[201,3820,213],{"class":3821},[212],[201,3823,213],{"class":3824},[212],[201,3826,1671],{"class":222},[201,3828,213],{"class":302},[201,3830,234],{"class":3383},[201,3832,3780],{"class":2352},[201,3834,237],{"class":3383},[201,3836,213],{"class":302},[201,3838,3839],{"class":222},"break",[201,3841,3015],{"class":243},[201,3843,213],{"class":3844},[212],[201,3846,213],{"class":3847},[212],[201,3849,213],{"class":3850},[212],[201,3852,213],{"class":3853},[212],[201,3855,213],{"class":3856},[212],[201,3858,213],{"class":3859},[212],[201,3861,213],{"class":3862},[212],[201,3864,213],{"class":3865},[212],[201,3867,213],{"class":3868},[212],[201,3870,213],{"class":3871},[212],[201,3873,213],{"class":3874},[212],[201,3876,213],{"class":3877},[212],[201,3879,213],{"class":3880},[212],[201,3882,213],{"class":3883},[212],[201,3885,213],{"class":3886},[212],[201,3888,213],{"class":3889},[212],[201,3891,213],{"class":3892},[212],[201,3894,213],{"class":3895},[212],[201,3897,213],{"class":3898},[212],[201,3900,213],{"class":3901},[212],[201,3903,213],{"class":3904},[212],[201,3906,213],{"class":3907},[212],[201,3909,213],{"class":3910},[212],[201,3912,213],{"class":3913},[212],[201,3915,213],{"class":3916},[212],[201,3918,213],{"class":3919},[212],[201,3921,213],{"class":3922},[212],[201,3924,213],{"class":3925},[212],[201,3927,213],{"class":3928},[212],[201,3930,213],{"class":3931},[212],[201,3933,213],{"class":3934},[212],[201,3936,213],{"class":3937},[212],[201,3939,213],{"class":3940},[212],[201,3942,213],{"class":3943},[212],[201,3945,2499],{"class":207},[201,3947,213],{"class":3948},[212],[201,3950,3951],{"class":207},"流结束\n",[201,3953,3954],{"class":203,"data-line":2070,"line":2070},[201,3955,688],{"emptyLinePlaceholder":687},[201,3957,3959,3962,3965,3968,3971,3974,3977,3979,3982,3984,3987,3989,3991,3994,3996,3998,4000,4002,4004,4007,4009,4011,4014,4016,4018,4020,4022,4024,4027,4029,4032,4035,4038,4041,4044],{"class":203,"data-line":3958,"line":3958},21,[201,3960,213],{"class":3961},[212],[201,3963,213],{"class":3964},[212],[201,3966,213],{"class":3967},[212],[201,3969,213],{"class":3970},[212],[201,3972,213],{"class":3973},[212],[201,3975,213],{"class":3976},[212],[201,3978,3183],{"class":2352},[201,3980,213],{"class":3981},[212],[201,3983,1817],{"class":309},[201,3985,213],{"class":3986},[212],[201,3988,3057],{"class":2311},[201,3990,454],{"class":2356},[201,3992,3993],{"class":229},"decode",[201,3995,234],{"class":3383},[201,3997,3788],{"class":2352},[201,3999,264],{"class":243},[201,4001,213],{"class":243},[201,4003,583],{"class":3426},[201,4005,213],{"class":4006},[212],[201,4008,189],{"class":2352},[201,4010,590],{"class":2356},[201,4012,213],{"class":4013},[212],[201,4015,3626],{"class":1787},[201,4017,213],{"class":243},[201,4019,605],{"class":3426},[201,4021,237],{"class":3383},[201,4023,3015],{"class":243},[201,4025,213],{"class":4026},[212],[201,4028,2499],{"class":207},[201,4030,213],{"class":4031},[212],[201,4033,4034],{"class":207},"⑥",[201,4036,213],{"class":4037},[212],[201,4039,4040],{"class":207},"关键：stream",[201,4042,213],{"class":4043},[212],[201,4045,4046],{"class":207},"模式解码\n",[201,4048,4050],{"class":203,"data-line":4049,"line":4049},22,[201,4051,688],{"emptyLinePlaceholder":687},[201,4053,4055,4058,4061,4064,4067,4070,4073,4075,4078,4081,4084,4086,4089,4091,4093,4096,4098,4100,4102,4104,4106,4108,4111,4114,4117,4120,4123,4126,4129,4132,4135,4138,4141,4144,4147,4150,4153,4156,4159,4161,4164,4167,4170],{"class":203,"data-line":4054,"line":4054},23,[201,4056,213],{"class":4057},[212],[201,4059,213],{"class":4060},[212],[201,4062,213],{"class":4063},[212],[201,4065,213],{"class":4066},[212],[201,4068,213],{"class":4069},[212],[201,4071,213],{"class":4072},[212],[201,4074,2305],{"class":222},[201,4076,213],{"class":4077},[212],[201,4079,4080],{"class":2311},"lines",[201,4082,213],{"class":4083},[212],[201,4085,310],{"class":309},[201,4087,213],{"class":4088},[212],[201,4090,3183],{"class":2311},[201,4092,454],{"class":2356},[201,4094,4095],{"class":229},"split",[201,4097,234],{"class":3383},[201,4099,934],{"class":586},[201,4101,59],{"class":937},[201,4103,934],{"class":586},[201,4105,237],{"class":3383},[201,4107,3015],{"class":243},[201,4109,213],{"class":4110},[212],[201,4112,213],{"class":4113},[212],[201,4115,213],{"class":4116},[212],[201,4118,213],{"class":4119},[212],[201,4121,213],{"class":4122},[212],[201,4124,213],{"class":4125},[212],[201,4127,213],{"class":4128},[212],[201,4130,213],{"class":4131},[212],[201,4133,213],{"class":4134},[212],[201,4136,213],{"class":4137},[212],[201,4139,213],{"class":4140},[212],[201,4142,213],{"class":4143},[212],[201,4145,213],{"class":4146},[212],[201,4148,213],{"class":4149},[212],[201,4151,213],{"class":4152},[212],[201,4154,213],{"class":4155},[212],[201,4157,213],{"class":4158},[212],[201,4160,2499],{"class":207},[201,4162,213],{"class":4163},[212],[201,4165,4166],{"class":207},"⑦",[201,4168,213],{"class":4169},[212],[201,4171,4172],{"class":207},"按换行切\n",[201,4174,4176,4179,4182,4185,4188,4191,4194,4196,4199,4201,4204,4206,4208,4211,4213,4215,4217,4220,4223,4225,4227,4230,4233,4236,4239,4242,4245,4248,4251,4254,4257,4260,4263,4266,4269,4272,4275,4278,4281,4284,4287,4290,4293,4296,4298,4301,4304,4307,4310,4313],{"class":203,"data-line":4175,"line":4175},24,[201,4177,213],{"class":4178},[212],[201,4180,213],{"class":4181},[212],[201,4183,213],{"class":4184},[212],[201,4186,213],{"class":4187},[212],[201,4189,213],{"class":4190},[212],[201,4192,213],{"class":4193},[212],[201,4195,3183],{"class":2352},[201,4197,213],{"class":4198},[212],[201,4200,310],{"class":309},[201,4202,213],{"class":4203},[212],[201,4205,4080],{"class":2311},[201,4207,454],{"class":2356},[201,4209,4210],{"class":229},"pop",[201,4212,234],{"class":3383},[201,4214,237],{"class":3383},[201,4216,213],{"class":302},[201,4218,4219],{"class":309},"||",[201,4221,213],{"class":4222},[212],[201,4224,3194],{"class":586},[201,4226,3015],{"class":243},[201,4228,213],{"class":4229},[212],[201,4231,213],{"class":4232},[212],[201,4234,213],{"class":4235},[212],[201,4237,213],{"class":4238},[212],[201,4240,213],{"class":4241},[212],[201,4243,213],{"class":4244},[212],[201,4246,213],{"class":4247},[212],[201,4249,213],{"class":4250},[212],[201,4252,213],{"class":4253},[212],[201,4255,213],{"class":4256},[212],[201,4258,213],{"class":4259},[212],[201,4261,213],{"class":4262},[212],[201,4264,213],{"class":4265},[212],[201,4267,213],{"class":4268},[212],[201,4270,213],{"class":4271},[212],[201,4273,213],{"class":4274},[212],[201,4276,213],{"class":4277},[212],[201,4279,213],{"class":4280},[212],[201,4282,213],{"class":4283},[212],[201,4285,213],{"class":4286},[212],[201,4288,213],{"class":4289},[212],[201,4291,213],{"class":4292},[212],[201,4294,213],{"class":4295},[212],[201,4297,2499],{"class":207},[201,4299,213],{"class":4300},[212],[201,4302,4303],{"class":207},"⑧",[201,4305,213],{"class":4306},[212],[201,4308,4309],{"class":207},"最后不完整的一行留回",[201,4311,213],{"class":4312},[212],[201,4314,4315],{"class":207},"buffer\n",[201,4317,4319],{"class":203,"data-line":4318,"line":4318},25,[201,4320,688],{"emptyLinePlaceholder":687},[201,4322,4324,4327,4330,4333,4336,4339,4342,4344,4346,4348,4350,4353,4355,4358,4360,4363,4365,4367,4369],{"class":203,"data-line":4323,"line":4323},26,[201,4325,213],{"class":4326},[212],[201,4328,213],{"class":4329},[212],[201,4331,213],{"class":4332},[212],[201,4334,213],{"class":4335},[212],[201,4337,213],{"class":4338},[212],[201,4340,213],{"class":4341},[212],[201,4343,372],{"class":222},[201,4345,213],{"class":302},[201,4347,234],{"class":3383},[201,4349,2305],{"class":222},[201,4351,213],{"class":4352},[212],[201,4354,203],{"class":2311},[201,4356,213],{"class":4357},[212],[201,4359,2719],{"class":222},[201,4361,213],{"class":4362},[212],[201,4364,4080],{"class":2352},[201,4366,237],{"class":3383},[201,4368,213],{"class":302},[201,4370,2341],{"class":3383},[201,4372,4374,4377,4380,4383,4386,4389,4392,4395,4398,4400,4402,4404,4406,4408,4411,4414,4416,4418,4420,4423,4425,4427,4429,4431],{"class":203,"data-line":4373,"line":4373},27,[201,4375,213],{"class":4376},[212],[201,4378,213],{"class":4379},[212],[201,4381,213],{"class":4382},[212],[201,4384,213],{"class":4385},[212],[201,4387,213],{"class":4388},[212],[201,4390,213],{"class":4391},[212],[201,4393,213],{"class":4394},[212],[201,4396,213],{"class":4397},[212],[201,4399,1671],{"class":222},[201,4401,213],{"class":302},[201,4403,234],{"class":3426},[201,4405,203],{"class":2311},[201,4407,454],{"class":2356},[201,4409,4410],{"class":229},"trim",[201,4412,234],{"class":4413},"sdqnd",[201,4415,237],{"class":4413},[201,4417,213],{"class":302},[201,4419,2788],{"class":309},[201,4421,213],{"class":4422},[212],[201,4424,3194],{"class":586},[201,4426,237],{"class":3426},[201,4428,213],{"class":302},[201,4430,2801],{"class":222},[201,4432,2520],{"class":243},[201,4434,4436,4439,4442,4445,4448,4451,4454,4457,4460,4462,4464],{"class":203,"data-line":4435,"line":4435},28,[201,4437,213],{"class":4438},[212],[201,4440,213],{"class":4441},[212],[201,4443,213],{"class":4444},[212],[201,4446,213],{"class":4447},[212],[201,4449,213],{"class":4450},[212],[201,4452,213],{"class":4453},[212],[201,4455,213],{"class":4456},[212],[201,4458,213],{"class":4459},[212],[201,4461,340],{"class":222},[201,4463,213],{"class":243},[201,4465,2341],{"class":3426},[201,4467,4469,4472,4475,4478,4481,4484,4487,4490,4493,4496,4499,4501,4504,4507,4510,4512,4515,4519,4521,4524,4526,4528,4530,4532,4534,4537,4539,4541,4544,4547,4550,4553,4556,4559,4562,4565,4568,4571,4574,4576,4579,4582,4585],{"class":203,"data-line":4468,"line":4468},29,[201,4470,213],{"class":4471},[212],[201,4473,213],{"class":4474},[212],[201,4476,213],{"class":4477},[212],[201,4479,213],{"class":4480},[212],[201,4482,213],{"class":4483},[212],[201,4485,213],{"class":4486},[212],[201,4488,213],{"class":4489},[212],[201,4491,213],{"class":4492},[212],[201,4494,213],{"class":4495},[212],[201,4497,213],{"class":4498},[212],[201,4500,2305],{"class":222},[201,4502,213],{"class":4503},[212],[201,4505,4506],{"class":2311},"data",[201,4508,213],{"class":4509},[212],[201,4511,310],{"class":309},[201,4513,213],{"class":4514},[212],[201,4516,4518],{"class":4517},"saiVi","JSON",[201,4520,454],{"class":2356},[201,4522,4523],{"class":229},"parse",[201,4525,234],{"class":4413},[201,4527,203],{"class":2352},[201,4529,237],{"class":4413},[201,4531,213],{"class":302},[201,4533,530],{"class":222},[201,4535,213],{"class":4536},[212],[201,4538,2876],{"class":1006},[201,4540,3015],{"class":243},[201,4542,213],{"class":4543},[212],[201,4545,213],{"class":4546},[212],[201,4548,213],{"class":4549},[212],[201,4551,213],{"class":4552},[212],[201,4554,213],{"class":4555},[212],[201,4557,213],{"class":4558},[212],[201,4560,213],{"class":4561},[212],[201,4563,213],{"class":4564},[212],[201,4566,213],{"class":4567},[212],[201,4569,213],{"class":4570},[212],[201,4572,213],{"class":4573},[212],[201,4575,2499],{"class":207},[201,4577,213],{"class":4578},[212],[201,4580,4581],{"class":207},"⑨",[201,4583,213],{"class":4584},[212],[201,4586,4587],{"class":207},"还原对象\n",[201,4589,4591,4594,4597,4600,4603,4606,4609,4612,4615,4618,4621,4623,4626,4628,4630,4633,4636,4639,4642,4645,4648,4651,4654,4657,4660,4663,4666,4669,4672,4675,4678,4681,4684,4687,4690,4693,4696,4699,4702,4705,4708,4711,4714,4717,4720,4723,4726,4729,4732,4735,4738,4740,4743,4746,4749],{"class":203,"data-line":4590,"line":4590},30,[201,4592,213],{"class":4593},[212],[201,4595,213],{"class":4596},[212],[201,4598,213],{"class":4599},[212],[201,4601,213],{"class":4602},[212],[201,4604,213],{"class":4605},[212],[201,4607,213],{"class":4608},[212],[201,4610,213],{"class":4611},[212],[201,4613,213],{"class":4614},[212],[201,4616,213],{"class":4617},[212],[201,4619,213],{"class":4620},[212],[201,4622,66],{"class":222},[201,4624,213],{"class":4625},[212],[201,4627,4506],{"class":2352},[201,4629,3015],{"class":243},[201,4631,213],{"class":4632},[212],[201,4634,213],{"class":4635},[212],[201,4637,213],{"class":4638},[212],[201,4640,213],{"class":4641},[212],[201,4643,213],{"class":4644},[212],[201,4646,213],{"class":4647},[212],[201,4649,213],{"class":4650},[212],[201,4652,213],{"class":4653},[212],[201,4655,213],{"class":4656},[212],[201,4658,213],{"class":4659},[212],[201,4661,213],{"class":4662},[212],[201,4664,213],{"class":4665},[212],[201,4667,213],{"class":4668},[212],[201,4670,213],{"class":4671},[212],[201,4673,213],{"class":4674},[212],[201,4676,213],{"class":4677},[212],[201,4679,213],{"class":4680},[212],[201,4682,213],{"class":4683},[212],[201,4685,213],{"class":4686},[212],[201,4688,213],{"class":4689},[212],[201,4691,213],{"class":4692},[212],[201,4694,213],{"class":4695},[212],[201,4697,213],{"class":4698},[212],[201,4700,213],{"class":4701},[212],[201,4703,213],{"class":4704},[212],[201,4706,213],{"class":4707},[212],[201,4709,213],{"class":4710},[212],[201,4712,213],{"class":4713},[212],[201,4715,213],{"class":4716},[212],[201,4718,213],{"class":4719},[212],[201,4721,213],{"class":4722},[212],[201,4724,213],{"class":4725},[212],[201,4727,213],{"class":4728},[212],[201,4730,213],{"class":4731},[212],[201,4733,213],{"class":4734},[212],[201,4736,213],{"class":4737},[212],[201,4739,2499],{"class":207},[201,4741,213],{"class":4742},[212],[201,4744,4745],{"class":207},"⑩",[201,4747,213],{"class":4748},[212],[201,4750,4751],{"class":207},"吐给调用方\n",[201,4753,4755,4758,4761,4764,4767,4770,4773,4776,4779,4781,4784,4787,4789,4791,4793,4795,4797],{"class":203,"data-line":4754,"line":4754},31,[201,4756,213],{"class":4757},[212],[201,4759,213],{"class":4760},[212],[201,4762,213],{"class":4763},[212],[201,4765,213],{"class":4766},[212],[201,4768,213],{"class":4769},[212],[201,4771,213],{"class":4772},[212],[201,4774,213],{"class":4775},[212],[201,4777,213],{"class":4778},[212],[201,4780,605],{"class":3426},[201,4782,213],{"class":4783},[212],[201,4785,4786],{"class":222},"catch",[201,4788,213],{"class":302},[201,4790,234],{"class":3426},[201,4792,1336],{"class":2352},[201,4794,237],{"class":3426},[201,4796,213],{"class":302},[201,4798,2341],{"class":3426},[201,4800,4802,4805,4808,4811,4814,4817,4820,4823,4826,4829,4832,4834,4837,4840,4843,4845,4848],{"class":203,"data-line":4801,"line":4801},32,[201,4803,213],{"class":4804},[212],[201,4806,213],{"class":4807},[212],[201,4809,213],{"class":4810},[212],[201,4812,213],{"class":4813},[212],[201,4815,213],{"class":4816},[212],[201,4818,213],{"class":4819},[212],[201,4821,213],{"class":4822},[212],[201,4824,213],{"class":4825},[212],[201,4827,213],{"class":4828},[212],[201,4830,213],{"class":4831},[212],[201,4833,2499],{"class":207},[201,4835,213],{"class":4836},[212],[201,4838,4839],{"class":207},"某些工具结果会把多个",[201,4841,213],{"class":4842},[212],[201,4844,4518],{"class":207},[201,4846,213],{"class":4847},[212],[201,4849,4850],{"class":207},"挤在一行，用正则兜底提取\n",[201,4852,4854,4857,4860,4863,4866,4869,4872,4875,4878,4881,4884,4886,4889,4892,4895,4897,4900,4902,4904,4907,4909,4912,4915,4918,4921,4924,4926,4930,4933,4935,4938,4940],{"class":203,"data-line":4853,"line":4853},33,[201,4855,213],{"class":4856},[212],[201,4858,213],{"class":4859},[212],[201,4861,213],{"class":4862},[212],[201,4864,213],{"class":4865},[212],[201,4867,213],{"class":4868},[212],[201,4870,213],{"class":4871},[212],[201,4873,213],{"class":4874},[212],[201,4876,213],{"class":4877},[212],[201,4879,213],{"class":4880},[212],[201,4882,213],{"class":4883},[212],[201,4885,2305],{"class":222},[201,4887,213],{"class":4888},[212],[201,4890,4891],{"class":2311},"jsonObjects",[201,4893,213],{"class":4894},[212],[201,4896,310],{"class":309},[201,4898,213],{"class":4899},[212],[201,4901,203],{"class":2311},[201,4903,454],{"class":2356},[201,4905,4906],{"class":229},"match",[201,4908,234],{"class":4413},[201,4910,1127],{"class":4911},"sCSXW",[201,4913,4914],{"class":937},"\\{",[201,4916,257],{"class":4917},"sK5Ct",[201,4919,4920],{"class":2356},"^",[201,4922,4923],{"class":395},"{}",[201,4925,281],{"class":4917},[201,4927,4929],{"class":4928},"sqqMN","*",[201,4931,4932],{"class":937},"\\}",[201,4934,1127],{"class":4911},[201,4936,4937],{"class":222},"g",[201,4939,237],{"class":4413},[201,4941,2520],{"class":243},[201,4943,4945,4948,4951,4954,4957,4960,4963,4966,4969,4972,4975,4977,4979,4981,4983,4985,4987],{"class":203,"data-line":4944,"line":4944},34,[201,4946,213],{"class":4947},[212],[201,4949,213],{"class":4950},[212],[201,4952,213],{"class":4953},[212],[201,4955,213],{"class":4956},[212],[201,4958,213],{"class":4959},[212],[201,4961,213],{"class":4962},[212],[201,4964,213],{"class":4965},[212],[201,4967,213],{"class":4968},[212],[201,4970,213],{"class":4971},[212],[201,4973,213],{"class":4974},[212],[201,4976,1671],{"class":222},[201,4978,213],{"class":302},[201,4980,234],{"class":4413},[201,4982,4891],{"class":2352},[201,4984,237],{"class":4413},[201,4986,213],{"class":302},[201,4988,2341],{"class":4413},[201,4990,4992,4995,4998,5001,5004,5007,5010,5013,5016,5019,5022,5025,5028,5030,5032,5034,5036,5039,5042,5045,5047,5050,5052,5054,5056],{"class":203,"data-line":4991,"line":4991},35,[201,4993,213],{"class":4994},[212],[201,4996,213],{"class":4997},[212],[201,4999,213],{"class":5000},[212],[201,5002,213],{"class":5003},[212],[201,5005,213],{"class":5006},[212],[201,5008,213],{"class":5009},[212],[201,5011,213],{"class":5012},[212],[201,5014,213],{"class":5015},[212],[201,5017,213],{"class":5018},[212],[201,5020,213],{"class":5021},[212],[201,5023,213],{"class":5024},[212],[201,5026,213],{"class":5027},[212],[201,5029,372],{"class":222},[201,5031,213],{"class":302},[201,5033,234],{"class":233},[201,5035,2305],{"class":222},[201,5037,213],{"class":5038},[212],[201,5040,5041],{"class":2311},"jsonObj",[201,5043,213],{"class":5044},[212],[201,5046,2719],{"class":222},[201,5048,213],{"class":5049},[212],[201,5051,4891],{"class":2352},[201,5053,237],{"class":233},[201,5055,213],{"class":302},[201,5057,2341],{"class":233},[201,5059,5061,5064,5067,5070,5073,5076,5079,5082,5085,5088,5091,5094,5097,5100,5103,5105,5107],{"class":203,"data-line":5060,"line":5060},36,[201,5062,213],{"class":5063},[212],[201,5065,213],{"class":5066},[212],[201,5068,213],{"class":5069},[212],[201,5071,213],{"class":5072},[212],[201,5074,213],{"class":5075},[212],[201,5077,213],{"class":5078},[212],[201,5080,213],{"class":5081},[212],[201,5083,213],{"class":5084},[212],[201,5086,213],{"class":5087},[212],[201,5089,213],{"class":5090},[212],[201,5092,213],{"class":5093},[212],[201,5095,213],{"class":5096},[212],[201,5098,213],{"class":5099},[212],[201,5101,213],{"class":5102},[212],[201,5104,340],{"class":222},[201,5106,213],{"class":243},[201,5108,2341],{"class":460},[201,5110,5112,5115,5118,5121,5124,5127,5130,5133,5136,5139,5142,5145,5148,5151,5154,5157,5160,5162,5165,5167,5169,5171,5173,5175,5177,5179,5181,5184,5186],{"class":203,"data-line":5111,"line":5111},37,[201,5113,213],{"class":5114},[212],[201,5116,213],{"class":5117},[212],[201,5119,213],{"class":5120},[212],[201,5122,213],{"class":5123},[212],[201,5125,213],{"class":5126},[212],[201,5128,213],{"class":5129},[212],[201,5131,213],{"class":5132},[212],[201,5134,213],{"class":5135},[212],[201,5137,213],{"class":5138},[212],[201,5140,213],{"class":5141},[212],[201,5143,213],{"class":5144},[212],[201,5146,213],{"class":5147},[212],[201,5149,213],{"class":5150},[212],[201,5152,213],{"class":5153},[212],[201,5155,213],{"class":5156},[212],[201,5158,213],{"class":5159},[212],[201,5161,66],{"class":222},[201,5163,213],{"class":5164},[212],[201,5166,4518],{"class":4517},[201,5168,454],{"class":2356},[201,5170,4523],{"class":229},[201,5172,234],{"class":598},[201,5174,5041],{"class":2352},[201,5176,237],{"class":598},[201,5178,213],{"class":302},[201,5180,530],{"class":222},[201,5182,213],{"class":5183},[212],[201,5185,2876],{"class":1006},[201,5187,2520],{"class":243},[201,5189,5191,5194,5197,5200,5203,5206,5209,5212,5215,5218,5221,5224,5227,5230,5233,5235,5238,5240,5242,5244,5247,5249,5251,5253,5256,5258,5261,5264,5267,5269,5271],{"class":203,"data-line":5190,"line":5190},38,[201,5192,213],{"class":5193},[212],[201,5195,213],{"class":5196},[212],[201,5198,213],{"class":5199},[212],[201,5201,213],{"class":5202},[212],[201,5204,213],{"class":5205},[212],[201,5207,213],{"class":5208},[212],[201,5210,213],{"class":5211},[212],[201,5213,213],{"class":5214},[212],[201,5216,213],{"class":5217},[212],[201,5219,213],{"class":5220},[212],[201,5222,213],{"class":5223},[212],[201,5225,213],{"class":5226},[212],[201,5228,213],{"class":5229},[212],[201,5231,213],{"class":5232},[212],[201,5234,605],{"class":460},[201,5236,213],{"class":5237},[212],[201,5239,4786],{"class":222},[201,5241,213],{"class":302},[201,5243,234],{"class":460},[201,5245,5246],{"class":2352},"innerError",[201,5248,237],{"class":460},[201,5250,213],{"class":302},[201,5252,583],{"class":460},[201,5254,213],{"class":5255},[212],[201,5257,2554],{"class":207},[201,5259,213],{"class":5260},[212],[201,5262,5263],{"class":207},"忽略",[201,5265,213],{"class":5266},[212],[201,5268,2566],{"class":207},[201,5270,213],{"class":243},[201,5272,2571],{"class":460},[201,5274,5276,5279,5282,5285,5288,5291,5294,5297,5300,5303,5306,5309,5312],{"class":203,"data-line":5275,"line":5275},39,[201,5277,213],{"class":5278},[212],[201,5280,213],{"class":5281},[212],[201,5283,213],{"class":5284},[212],[201,5286,213],{"class":5287},[212],[201,5289,213],{"class":5290},[212],[201,5292,213],{"class":5293},[212],[201,5295,213],{"class":5296},[212],[201,5298,213],{"class":5299},[212],[201,5301,213],{"class":5302},[212],[201,5304,213],{"class":5305},[212],[201,5307,213],{"class":5308},[212],[201,5310,213],{"class":5311},[212],[201,5313,2571],{"class":233},[201,5315,5317,5320,5323,5326,5329,5332,5335,5338,5341,5344,5347],{"class":203,"data-line":5316,"line":5316},40,[201,5318,213],{"class":5319},[212],[201,5321,213],{"class":5322},[212],[201,5324,213],{"class":5325},[212],[201,5327,213],{"class":5328},[212],[201,5330,213],{"class":5331},[212],[201,5333,213],{"class":5334},[212],[201,5336,213],{"class":5337},[212],[201,5339,213],{"class":5340},[212],[201,5342,213],{"class":5343},[212],[201,5345,213],{"class":5346},[212],[201,5348,2571],{"class":4413},[201,5350,5352,5355,5358,5361,5364,5367,5370,5373,5376],{"class":203,"data-line":5351,"line":5351},41,[201,5353,213],{"class":5354},[212],[201,5356,213],{"class":5357},[212],[201,5359,213],{"class":5360},[212],[201,5362,213],{"class":5363},[212],[201,5365,213],{"class":5366},[212],[201,5368,213],{"class":5369},[212],[201,5371,213],{"class":5372},[212],[201,5374,213],{"class":5375},[212],[201,5377,2571],{"class":3426},[201,5379,5381,5384,5387,5390,5393,5396,5399],{"class":203,"data-line":5380,"line":5380},42,[201,5382,213],{"class":5383},[212],[201,5385,213],{"class":5386},[212],[201,5388,213],{"class":5389},[212],[201,5391,213],{"class":5392},[212],[201,5394,213],{"class":5395},[212],[201,5397,213],{"class":5398},[212],[201,5400,2571],{"class":3383},[201,5402,5404,5407,5410,5413,5416],{"class":203,"data-line":5403,"line":5403},43,[201,5405,213],{"class":5406},[212],[201,5408,213],{"class":5409},[212],[201,5411,213],{"class":5412},[212],[201,5414,213],{"class":5415},[212],[201,5417,2571],{"class":598},[201,5419,5421],{"class":203,"data-line":5420,"line":5420},44,[201,5422,688],{"emptyLinePlaceholder":687},[201,5424,5426,5429,5432,5435,5438,5440,5443,5446,5449,5451,5454],{"class":203,"data-line":5425,"line":5425},45,[201,5427,213],{"class":5428},[212],[201,5430,213],{"class":5431},[212],[201,5433,213],{"class":5434},[212],[201,5436,213],{"class":5437},[212],[201,5439,2499],{"class":207},[201,5441,213],{"class":5442},[212],[201,5444,5445],{"class":207},"流结束后处理",[201,5447,213],{"class":5448},[212],[201,5450,3183],{"class":207},[201,5452,213],{"class":5453},[212],[201,5455,5456],{"class":207},"里残留的半行\n",[201,5458,5460,5463,5466,5469,5472,5474,5476,5478,5480,5482,5484,5486,5488,5490,5493,5496,5498,5500,5502],{"class":203,"data-line":5459,"line":5459},46,[201,5461,213],{"class":5462},[212],[201,5464,213],{"class":5465},[212],[201,5467,213],{"class":5468},[212],[201,5470,213],{"class":5471},[212],[201,5473,1671],{"class":222},[201,5475,213],{"class":302},[201,5477,234],{"class":598},[201,5479,3183],{"class":2311},[201,5481,454],{"class":2356},[201,5483,4410],{"class":229},[201,5485,234],{"class":3383},[201,5487,237],{"class":3383},[201,5489,213],{"class":302},[201,5491,5492],{"class":309},"!==",[201,5494,213],{"class":5495},[212],[201,5497,3194],{"class":586},[201,5499,237],{"class":598},[201,5501,213],{"class":302},[201,5503,2341],{"class":598},[201,5505,5507,5510,5513,5516,5519,5522,5525,5527,5529,5531,5534,5536,5539,5541,5543,5545,5547,5549,5551,5553,5555,5558,5560,5562,5564,5566,5569,5571,5573,5575,5578,5580,5583,5585,5588,5590,5592],{"class":203,"data-line":5506,"line":5506},47,[201,5508,213],{"class":5509},[212],[201,5511,213],{"class":5512},[212],[201,5514,213],{"class":5515},[212],[201,5517,213],{"class":5518},[212],[201,5520,213],{"class":5521},[212],[201,5523,213],{"class":5524},[212],[201,5526,340],{"class":222},[201,5528,213],{"class":243},[201,5530,583],{"class":3383},[201,5532,213],{"class":5533},[212],[201,5535,66],{"class":222},[201,5537,213],{"class":5538},[212],[201,5540,4518],{"class":4517},[201,5542,454],{"class":2356},[201,5544,4523],{"class":229},[201,5546,234],{"class":3426},[201,5548,3183],{"class":2352},[201,5550,237],{"class":3426},[201,5552,213],{"class":302},[201,5554,530],{"class":222},[201,5556,213],{"class":5557},[212],[201,5559,2876],{"class":1006},[201,5561,3015],{"class":243},[201,5563,213],{"class":243},[201,5565,605],{"class":3383},[201,5567,213],{"class":5568},[212],[201,5570,4786],{"class":222},[201,5572,213],{"class":243},[201,5574,583],{"class":3383},[201,5576,213],{"class":5577},[212],[201,5579,2554],{"class":207},[201,5581,213],{"class":5582},[212],[201,5584,5263],{"class":207},[201,5586,213],{"class":5587},[212],[201,5589,2566],{"class":207},[201,5591,213],{"class":243},[201,5593,2571],{"class":3383},[201,5595,5597,5600,5603,5606,5609],{"class":203,"data-line":5596,"line":5596},48,[201,5598,213],{"class":5599},[212],[201,5601,213],{"class":5602},[212],[201,5604,213],{"class":5605},[212],[201,5607,213],{"class":5608},[212],[201,5610,2571],{"class":598},[201,5612,5614,5617,5620,5622,5625,5627,5629],{"class":203,"data-line":5613,"line":5613},49,[201,5615,213],{"class":5616},[212],[201,5618,213],{"class":5619},[212],[201,5621,605],{"class":460},[201,5623,213],{"class":5624},[212],[201,5626,625],{"class":222},[201,5628,213],{"class":243},[201,5630,2341],{"class":460},[201,5632,5634,5637,5640,5643,5646,5648,5651,5654,5657,5660,5663,5665,5668,5671,5674],{"class":203,"data-line":5633,"line":5633},50,[201,5635,213],{"class":5636},[212],[201,5638,213],{"class":5639},[212],[201,5641,213],{"class":5642},[212],[201,5644,213],{"class":5645},[212],[201,5647,2499],{"class":207},[201,5649,213],{"class":5650},[212],[201,5652,5653],{"class":207},"⑪",[201,5655,213],{"class":5656},[212],[201,5658,5659],{"class":207},"生成器被提前",[201,5661,213],{"class":5662},[212],[201,5664,3839],{"class":207},[201,5666,213],{"class":5667},[212],[201,5669,5670],{"class":207},"时必须",[201,5672,213],{"class":5673},[212],[201,5675,5676],{"class":207},"cancel，否则连接永远挂着\n",[201,5678,5680,5683,5686,5689,5692,5694,5697,5699,5701,5703,5705,5707,5709,5711,5713,5715,5717,5720,5722,5724,5726,5728,5730,5732],{"class":203,"data-line":5679,"line":5679},51,[201,5681,213],{"class":5682},[212],[201,5684,213],{"class":5685},[212],[201,5687,213],{"class":5688},[212],[201,5690,213],{"class":5691},[212],[201,5693,2958],{"class":2924},[201,5695,213],{"class":5696},[212],[201,5698,2988],{"class":2311},[201,5700,3005],{"class":2356},[201,5702,3423],{"class":229},[201,5704,234],{"class":598},[201,5706,237],{"class":598},[201,5708,454],{"class":2356},[201,5710,4786],{"class":229},[201,5712,234],{"class":598},[201,5714,234],{"class":3383},[201,5716,237],{"class":3383},[201,5718,213],{"class":5719},[212],[201,5721,310],{"class":2924},[201,5723,247],{"class":2924},[201,5725,213],{"class":243},[201,5727,583],{"class":3383},[201,5729,605],{"class":3383},[201,5731,237],{"class":598},[201,5733,2520],{"class":243},[201,5735,5737,5740,5743],{"class":203,"data-line":5736,"line":5736},52,[201,5738,213],{"class":5739},[212],[201,5741,213],{"class":5742},[212],[201,5744,2571],{"class":460},[201,5746,5748],{"class":203,"data-line":5747,"line":5747},53,[201,5749,2571],{"class":233},[74,5751,5752,5762],{},[77,5753,5754],{},[80,5755,5756,5759],{},[83,5757,5758],{},"步骤",[83,5760,5761],{},"原因",[92,5763,5764,5789,5799,5813,5827,5845,5863],{},[80,5765,5766,5772],{},[97,5767,5768,5769],{},"① ",[47,5770,5771],{"code":5771},"getReader()",[97,5773,5774,5776,5777,5780,5781,5784,5785,5788],{},[47,5775,172],{"code":172}," 是一个 ",[47,5778,5779],{"code":5779},"ReadableStream","，用 reader 才能",[18,5782,5783],{},"异步增量","读取，而不是 ",[47,5786,5787],{"code":5787},"await response.json()"," 一次性拿完",[80,5790,5791,5796],{},[97,5792,5793,5794],{},"② ",[47,5795,3075],{"code":3075},[97,5797,5798],{},"网络传的是字节（Uint8Array），需要解码成字符串",[80,5800,5801,5806],{},[97,5802,5803,5804],{},"③ ",[47,5805,3183],{"code":3183},[97,5807,5808,5809,5812],{},"网络数据可能",[18,5810,5811],{},"切在任意位置","（一个 JSON 的后半段），需要暂存\"不完整的尾巴\"",[80,5814,5815,5821],{},[97,5816,5817,5818],{},"④ ",[47,5819,5820],{"code":5820},"reader.cancel()",[97,5822,5823,5826],{},[47,5824,5825],{"code":5825},"AbortController"," 触发时，必须主动 cancel 流，否则底层连接挂起（见第 5 节）",[80,5828,5829,5835],{},[97,5830,5831,5832],{},"⑥ ",[47,5833,5834],{"code":5834},"{ stream: true }",[97,5836,5837,5840,5841,5844],{},[18,5838,5839],{},"UTF-8 多字节字符可能被切到字节边界","，",[47,5842,5843],{"code":5843},"stream:true"," 让解码器缓存不完整字节、下次补全。不传会乱码！",[80,5846,5847,5853],{},[97,5848,5849,5850,5852],{},"⑦⑧ 按 ",[47,5851,59],{"code":59}," 切行",[97,5854,5855,5856,5858,5859,5862],{},"协议规定一行一个 JSON。",[47,5857,4095],{"code":4095}," 后最后一段没有换行符结尾，说明是",[18,5860,5861],{},"半包","，留回 buffer",[80,5864,5865,5871],{},[97,5866,5867,5868,5870],{},"⑪ ",[47,5869,625],{"code":625}," cancel",[97,5872,5873,5874,1127,5876,5878],{},"消费方 ",[47,5875,3839],{"code":3839},[47,5877,694],{"code":694}," 提前退出生成器时，只有 cancel 才会真正断开 SSE",[23,5880,5881],{},[15,5882,5883,5884,5887],{},"💡 步骤 ⑦⑧ 是",[18,5885,5886],{},"所有流式协议通用","的\"粘包/半包处理\"思路，在 WebSocket 消息、NDJSON 日志流、日志采集里都能见到。",[763,5889,5890],{"id":5890},"缓冲层",[192,5892,5894],{"className":2296,"code":5893,"language":2298,"meta":197,"style":197},"export class CurrentMessageFIFO {\n  private stack: PacketType[] = [];\n  isComplete: boolean = false;\n  error: string | null = null;\n\n  push(packetBunch) { this.stack.push(packetBunch); }\n  nextPacket() { return this.stack.shift(); }   // FIFO：先入先出\n  isEmpty() { return this.stack.length === 0; }\n}\n\nexport async function updateCurrentMessageFIFO(stack, params) {\n  try {\n    for await (const packet of sendMessage(params)) {\n      if (params.signal?.aborted) throw new Error(\"AbortError\");\n      stack.push(packet);\n    }\n  } catch (error) { /* 记入 stack.error */ }\n  finally { stack.isComplete = true; }\n}\n",[47,5895,5896,5915,5957,5989,6033,6037,6086,6151,6206,6210,6214,6253,6267,6321,6385,6419,6435,6488,6527],{"__ignoreMap":197},[201,5897,5898,5900,5903,5905,5908,5911,5913],{"class":203,"data-line":204,"line":204},[201,5899,2856],{"class":222},[201,5901,213],{"class":5902},[212],[201,5904,1000],{"class":222},[201,5906,213],{"class":5907},[212],[201,5909,5910],{"class":1006},"CurrentMessageFIFO",[201,5912,213],{"class":243},[201,5914,2341],{"class":233},[201,5916,5917,5920,5923,5926,5929,5932,5934,5937,5939,5942,5944,5947,5949,5951,5953,5955],{"class":203,"data-line":219,"line":219},[201,5918,213],{"class":5919},[212],[201,5921,213],{"class":5922},[212],[201,5924,5925],{"class":222},"private",[201,5927,213],{"class":5928},[212],[201,5930,5931],{"class":2352},"stack",[201,5933,590],{"class":2356},[201,5935,213],{"class":5936},[212],[201,5938,1361],{"class":1006},[201,5940,257],{"class":5941},"sYEiT",[201,5943,281],{"class":5941},[201,5945,213],{"class":5946},"sNyJ2",[201,5948,310],{"class":309},[201,5950,213],{"class":302},[201,5952,257],{"class":460},[201,5954,281],{"class":460},[201,5956,2520],{"class":243},[201,5958,5959,5962,5965,5968,5970,5973,5976,5979,5981,5984,5987],{"class":203,"data-line":287,"line":287},[201,5960,213],{"class":5961},[212],[201,5963,213],{"class":5964},[212],[201,5966,5967],{"class":2352},"isComplete",[201,5969,590],{"class":2356},[201,5971,213],{"class":5972},[212],[201,5974,5975],{"class":2957},"boolean",[201,5977,213],{"class":5978},[212],[201,5980,310],{"class":309},[201,5982,213],{"class":5983},[212],[201,5985,5986],{"class":1787},"false",[201,5988,2520],{"class":243},[201,5990,5991,5994,5997,5999,6001,6004,6007,6010,6013,6016,6020,6023,6025,6028,6031],{"class":203,"data-line":325,"line":325},[201,5992,213],{"class":5993},[212],[201,5995,213],{"class":5996},[212],[201,5998,1336],{"class":2352},[201,6000,590],{"class":2356},[201,6002,213],{"class":6003},[212],[201,6005,6006],{"class":2957},"string",[201,6008,213],{"class":6009},[212],[201,6011,6012],{"class":2356},"|",[201,6014,213],{"class":6015},[212],[201,6017,6019],{"class":6018},"sP-bm","null",[201,6021,213],{"class":6022},[212],[201,6024,310],{"class":309},[201,6026,213],{"class":6027},[212],[201,6029,6019],{"class":6030},"sEaR9",[201,6032,2520],{"class":243},[201,6034,6035],{"class":203,"data-line":345,"line":345},[201,6036,688],{"emptyLinePlaceholder":687},[201,6038,6039,6042,6045,6048,6050,6053,6055,6057,6059,6062,6066,6068,6070,6072,6074,6076,6078,6080,6082,6084],{"class":203,"data-line":403,"line":403},[201,6040,213],{"class":6041},[212],[201,6043,213],{"class":6044},[212],[201,6046,6047],{"class":229},"push",[201,6049,234],{"class":460},[201,6051,6052],{"class":716},"packetBunch",[201,6054,237],{"class":460},[201,6056,213],{"class":243},[201,6058,583],{"class":460},[201,6060,213],{"class":6061},[212],[201,6063,6065],{"class":6064},"sQee8","this",[201,6067,454],{"class":2356},[201,6069,5931],{"class":2311},[201,6071,454],{"class":2356},[201,6073,6047],{"class":229},[201,6075,234],{"class":598},[201,6077,6052],{"class":2352},[201,6079,237],{"class":598},[201,6081,3015],{"class":243},[201,6083,213],{"class":243},[201,6085,2571],{"class":460},[201,6087,6088,6091,6094,6097,6099,6101,6103,6105,6108,6110,6113,6115,6117,6119,6121,6124,6126,6128,6130,6132,6134,6137,6140,6143,6145,6148],{"class":203,"data-line":502,"line":502},[201,6089,213],{"class":6090},[212],[201,6092,213],{"class":6093},[212],[201,6095,6096],{"class":229},"nextPacket",[201,6098,234],{"class":460},[201,6100,237],{"class":460},[201,6102,213],{"class":243},[201,6104,583],{"class":460},[201,6106,213],{"class":6107},[212],[201,6109,694],{"class":222},[201,6111,213],{"class":6112},[212],[201,6114,6065],{"class":6064},[201,6116,454],{"class":2356},[201,6118,5931],{"class":2311},[201,6120,454],{"class":2356},[201,6122,6123],{"class":229},"shift",[201,6125,234],{"class":598},[201,6127,237],{"class":598},[201,6129,3015],{"class":243},[201,6131,213],{"class":243},[201,6133,605],{"class":460},[201,6135,213],{"class":6136},[212],[201,6138,213],{"class":6139},[212],[201,6141,213],{"class":6142},[212],[201,6144,2499],{"class":207},[201,6146,213],{"class":6147},[212],[201,6149,6150],{"class":207},"FIFO：先入先出\n",[201,6152,6153,6156,6159,6162,6164,6166,6168,6170,6173,6175,6178,6180,6182,6184,6186,6189,6192,6194,6197,6200,6202,6204],{"class":203,"data-line":541,"line":541},[201,6154,213],{"class":6155},[212],[201,6157,213],{"class":6158},[212],[201,6160,6161],{"class":229},"isEmpty",[201,6163,234],{"class":460},[201,6165,237],{"class":460},[201,6167,213],{"class":243},[201,6169,583],{"class":460},[201,6171,213],{"class":6172},[212],[201,6174,694],{"class":222},[201,6176,213],{"class":6177},[212],[201,6179,6065],{"class":6064},[201,6181,454],{"class":2356},[201,6183,5931],{"class":2311},[201,6185,454],{"class":2356},[201,6187,6188],{"class":2352},"length",[201,6190,213],{"class":6191},[212],[201,6193,2788],{"class":309},[201,6195,213],{"class":6196},[212],[201,6198,6199],{"class":1787},"0",[201,6201,3015],{"class":243},[201,6203,213],{"class":243},[201,6205,2571],{"class":460},[201,6207,6208],{"class":203,"data-line":610,"line":610},[201,6209,2571],{"class":233},[201,6211,6212],{"class":203,"data-line":630,"line":630},[201,6213,688],{"emptyLinePlaceholder":687},[201,6215,6216,6218,6221,6223,6226,6229,6232,6235,6237,6239,6241,6244,6247,6249,6251],{"class":203,"data-line":684,"line":684},[201,6217,2856],{"class":222},[201,6219,213],{"class":6220},[212],[201,6222,2666],{"class":222},[201,6224,213],{"class":6225},[212],[201,6227,6228],{"class":222},"function",[201,6230,213],{"class":6231},[212],[201,6233,6234],{"class":229},"updateCurrentMessageFIFO",[201,6236,234],{"class":233},[201,6238,5931],{"class":716},[201,6240,264],{"class":243},[201,6242,213],{"class":6243},[212],[201,6245,6246],{"class":716},"params",[201,6248,237],{"class":233},[201,6250,213],{"class":243},[201,6252,2341],{"class":233},[201,6254,6255,6258,6261,6263,6265],{"class":203,"data-line":691,"line":691},[201,6256,213],{"class":6257},[212],[201,6259,213],{"class":6260},[212],[201,6262,340],{"class":222},[201,6264,213],{"class":243},[201,6266,2341],{"class":460},[201,6268,6269,6272,6275,6278,6281,6283,6286,6288,6290,6292,6294,6297,6299,6302,6304,6307,6309,6311,6313,6315,6317,6319],{"class":203,"data-line":1750,"line":1750},[201,6270,213],{"class":6271},[212],[201,6273,213],{"class":6274},[212],[201,6276,213],{"class":6277},[212],[201,6279,213],{"class":6280},[212],[201,6282,372],{"class":222},[201,6284,213],{"class":6285},[212],[201,6287,2323],{"class":222},[201,6289,213],{"class":302},[201,6291,234],{"class":598},[201,6293,2305],{"class":222},[201,6295,213],{"class":6296},[212],[201,6298,1455],{"class":2311},[201,6300,213],{"class":6301},[212],[201,6303,2719],{"class":222},[201,6305,213],{"class":6306},[212],[201,6308,2292],{"class":229},[201,6310,234],{"class":3383},[201,6312,6246],{"class":2352},[201,6314,237],{"class":3383},[201,6316,237],{"class":598},[201,6318,213],{"class":302},[201,6320,2341],{"class":598},[201,6322,6323,6326,6329,6332,6335,6338,6341,6343,6345,6347,6349,6351,6353,6355,6358,6360,6362,6365,6368,6370,6373,6376,6378,6381,6383],{"class":203,"data-line":1791,"line":1791},[201,6324,213],{"class":6325},[212],[201,6327,213],{"class":6328},[212],[201,6330,213],{"class":6331},[212],[201,6333,213],{"class":6334},[212],[201,6336,213],{"class":6337},[212],[201,6339,213],{"class":6340},[212],[201,6342,1671],{"class":222},[201,6344,213],{"class":302},[201,6346,234],{"class":3383},[201,6348,6246],{"class":2311},[201,6350,454],{"class":2356},[201,6352,2425],{"class":2311},[201,6354,3005],{"class":2356},[201,6356,6357],{"class":2352},"aborted",[201,6359,237],{"class":3383},[201,6361,213],{"class":302},[201,6363,6364],{"class":222},"throw",[201,6366,213],{"class":6367},[212],[201,6369,3069],{"class":3068},[201,6371,213],{"class":6372},[212],[201,6374,6375],{"class":229},"Error",[201,6377,234],{"class":3383},[201,6379,6380],{"class":586},"\"AbortError\"",[201,6382,237],{"class":3383},[201,6384,2520],{"class":243},[201,6386,6387,6390,6393,6396,6399,6402,6405,6407,6409,6411,6413,6415,6417],{"class":203,"data-line":1796,"line":1796},[201,6388,213],{"class":6389},[212],[201,6391,213],{"class":6392},[212],[201,6394,213],{"class":6395},[212],[201,6397,213],{"class":6398},[212],[201,6400,213],{"class":6401},[212],[201,6403,213],{"class":6404},[212],[201,6406,5931],{"class":2311},[201,6408,454],{"class":2356},[201,6410,6047],{"class":229},[201,6412,234],{"class":3383},[201,6414,1455],{"class":2352},[201,6416,237],{"class":3383},[201,6418,2520],{"class":243},[201,6420,6421,6424,6427,6430,6433],{"class":203,"data-line":1894,"line":1894},[201,6422,213],{"class":6423},[212],[201,6425,213],{"class":6426},[212],[201,6428,213],{"class":6429},[212],[201,6431,213],{"class":6432},[212],[201,6434,2571],{"class":598},[201,6436,6437,6440,6443,6445,6448,6450,6452,6454,6456,6458,6460,6462,6465,6467,6470,6473,6476,6479,6482,6484,6486],{"class":203,"data-line":1936,"line":1936},[201,6438,213],{"class":6439},[212],[201,6441,213],{"class":6442},[212],[201,6444,605],{"class":460},[201,6446,213],{"class":6447},[212],[201,6449,4786],{"class":222},[201,6451,213],{"class":302},[201,6453,234],{"class":460},[201,6455,1336],{"class":2352},[201,6457,237],{"class":460},[201,6459,213],{"class":302},[201,6461,583],{"class":460},[201,6463,213],{"class":6464},[212],[201,6466,2554],{"class":207},[201,6468,213],{"class":6469},[212],[201,6471,6472],{"class":207},"记入",[201,6474,213],{"class":6475},[212],[201,6477,6478],{"class":207},"stack.error",[201,6480,213],{"class":6481},[212],[201,6483,2566],{"class":207},[201,6485,213],{"class":243},[201,6487,2571],{"class":460},[201,6489,6490,6493,6496,6498,6500,6502,6505,6507,6509,6511,6514,6516,6519,6521,6523,6525],{"class":203,"data-line":1960,"line":1960},[201,6491,213],{"class":6492},[212],[201,6494,213],{"class":6495},[212],[201,6497,625],{"class":222},[201,6499,213],{"class":243},[201,6501,583],{"class":460},[201,6503,213],{"class":6504},[212],[201,6506,5931],{"class":2311},[201,6508,454],{"class":2356},[201,6510,5967],{"class":2352},[201,6512,213],{"class":6513},[212],[201,6515,310],{"class":309},[201,6517,213],{"class":6518},[212],[201,6520,3626],{"class":1787},[201,6522,3015],{"class":243},[201,6524,213],{"class":243},[201,6526,2571],{"class":460},[201,6528,6529],{"class":203,"data-line":2000,"line":2000},[201,6530,2571],{"class":233},[15,6532,6533,6534,31],{},"为什么需要 FIFO？因为 ",[18,6535,6536,6538],{},[47,6537,2650],{"code":2650}," 的生产速度远快于 React 渲染速度",[15,6540,6541],{},"如果直接在每个包上 setState，React 会被几万个重渲染压垮。",[15,6543,6544],{},"FIFO 把\"生产\"（后台异步迭代，持续 push）和\"消费\"（主循环按帧取包渲染）解耦。",[763,6546,6547],{"id":6547},"消费循环",[15,6549,6550,6551,6554],{},"主循环 ",[47,6552,6553],{"code":6553},"while (!stack.isComplete || !stack.isEmpty())"," 是前端流式消费的心脏：",[192,6556,6558],{"className":2296,"code":6557,"language":2298,"meta":197,"style":197},"const stack = new CurrentMessageFIFO();\nupdateCurrentMessageFIFO(stack, { signal, message, ... });\n\nawait delay(50);   // 给第一批包一点时间\n\nwhile (!stack.isComplete || !stack.isEmpty()) {\n  if (stack.isEmpty()) {\n    if (pendingFlush) await flushViaRAF();   // 攒一批，下一帧统一刷新\n    else await delay(0.5);                    // 空转等待生产方\n  }\n\n  if (!stack.isEmpty() && !controller.signal.aborted) {\n    const packet = stack.nextPacket();\n    updateChatStateAction(frozenSessionId, \"streaming\");  // 状态机：loading → streaming\n\n    // —— 按包类型分流 ——\n    if ((packet as MessageResponseIDInfo).user_message_id) {\n      newUserMessageId = ...;   // 记下 user message id\n    }\n    if ((packet as MessageResponseIDInfo).reserved_assistant_message_id) {\n      newAgentMessageId = ...;  // 记下预留的 assistant message id\n    }\n    if (Object.hasOwn(packet, \"user_files\")) { /* 用户文件包 */ }\n    if (Object.hasOwn(packet, \"file_ids\")) { /* 生成图片 */ }\n    else if (Object.hasOwn(packet, \"error\")) {\n      // 单模型：throw 终止；多模型：把错误路由到具体模型的面板\n    }\n    else if (Object.hasOwn(packet, \"message_id\")) { /* 完整消息 */ }\n    else if (Object.hasOwn(packet, \"stop_reason\")) { /* 流结束原因 */ }\n    else if (Object.hasOwn(packet, \"obj\")) {\n      const packetObj = (packet as Packet).obj;\n      packets.push(typedPacket);        // ← AgentResponseDelta 都堆在这里\n      packetsVersion++;\n      singleModelDirty = true;\n      // 引文 / 开始包单独处理\n    }\n    pendingFlush = true;\n  }\n}\nflushPendingUpdates();\n",[47,6559,6560,6590,6632,6636,6672,6676,6718,6748,6806,6910,6920,6924,6976,7017,7076,7080,7114,7162,7231,7247,7294,7359,7375,7443,7509,7559,7593,7609,7680,7751,7801,7858,7936,7964,7999,8038,8054,8082,8092,8096],{"__ignoreMap":197},[201,6561,6562,6564,6567,6569,6572,6574,6577,6579,6582,6584,6586,6588],{"class":203,"data-line":204,"line":204},[201,6563,2305],{"class":222},[201,6565,213],{"class":6566},[212],[201,6568,5931],{"class":2311},[201,6570,213],{"class":6571},[212],[201,6573,310],{"class":309},[201,6575,213],{"class":6576},[212],[201,6578,3069],{"class":3068},[201,6580,213],{"class":6581},[212],[201,6583,5910],{"class":229},[201,6585,234],{"class":233},[201,6587,237],{"class":233},[201,6589,2520],{"class":243},[201,6591,6592,6594,6596,6598,6600,6602,6604,6607,6609,6611,6614,6617,6619,6622,6624,6626,6628,6630],{"class":203,"data-line":219,"line":219},[201,6593,6234],{"class":229},[201,6595,234],{"class":233},[201,6597,5931],{"class":2352},[201,6599,264],{"class":243},[201,6601,213],{"class":243},[201,6603,583],{"class":460},[201,6605,213],{"class":6606},[212],[201,6608,2425],{"class":2352},[201,6610,264],{"class":243},[201,6612,213],{"class":6613},[212],[201,6615,6616],{"class":2352},"message",[201,6618,264],{"class":243},[201,6620,213],{"class":6621},[212],[201,6623,396],{"class":2356},[201,6625,213],{"class":243},[201,6627,605],{"class":460},[201,6629,237],{"class":233},[201,6631,2520],{"class":243},[201,6633,6634],{"class":203,"data-line":287,"line":287},[201,6635,688],{"emptyLinePlaceholder":687},[201,6637,6638,6640,6643,6646,6648,6651,6653,6655,6658,6661,6664,6666,6669],{"class":203,"data-line":325,"line":325},[201,6639,2323],{"class":222},[201,6641,213],{"class":6642},[212],[201,6644,6645],{"class":229},"delay",[201,6647,234],{"class":233},[201,6649,6650],{"class":1787},"50",[201,6652,237],{"class":233},[201,6654,3015],{"class":243},[201,6656,213],{"class":6657},[212],[201,6659,213],{"class":6660},[212],[201,6662,213],{"class":6663},[212],[201,6665,2499],{"class":207},[201,6667,213],{"class":6668},[212],[201,6670,6671],{"class":207},"给第一批包一点时间\n",[201,6673,6674],{"class":203,"data-line":345,"line":345},[201,6675,688],{"emptyLinePlaceholder":687},[201,6677,6678,6680,6682,6684,6686,6688,6690,6692,6695,6697,6700,6702,6704,6706,6708,6710,6712,6714,6716],{"class":203,"data-line":403,"line":403},[201,6679,3619],{"class":222},[201,6681,213],{"class":302},[201,6683,234],{"class":233},[201,6685,2535],{"class":309},[201,6687,5931],{"class":2311},[201,6689,454],{"class":2356},[201,6691,5967],{"class":2352},[201,6693,213],{"class":6694},[212],[201,6696,4219],{"class":309},[201,6698,213],{"class":6699},[212],[201,6701,2535],{"class":309},[201,6703,5931],{"class":2311},[201,6705,454],{"class":2356},[201,6707,6161],{"class":229},[201,6709,234],{"class":460},[201,6711,237],{"class":460},[201,6713,237],{"class":233},[201,6715,213],{"class":302},[201,6717,2341],{"class":233},[201,6719,6720,6723,6726,6728,6730,6732,6734,6736,6738,6740,6742,6744,6746],{"class":203,"data-line":502,"line":502},[201,6721,213],{"class":6722},[212],[201,6724,213],{"class":6725},[212],[201,6727,1671],{"class":222},[201,6729,213],{"class":302},[201,6731,234],{"class":460},[201,6733,5931],{"class":2311},[201,6735,454],{"class":2356},[201,6737,6161],{"class":229},[201,6739,234],{"class":598},[201,6741,237],{"class":598},[201,6743,237],{"class":460},[201,6745,213],{"class":302},[201,6747,2341],{"class":460},[201,6749,6750,6753,6756,6759,6762,6764,6766,6768,6771,6773,6775,6777,6780,6783,6785,6787,6789,6792,6795,6798,6800,6803],{"class":203,"data-line":541,"line":541},[201,6751,213],{"class":6752},[212],[201,6754,213],{"class":6755},[212],[201,6757,213],{"class":6758},[212],[201,6760,213],{"class":6761},[212],[201,6763,1671],{"class":222},[201,6765,213],{"class":302},[201,6767,234],{"class":598},[201,6769,6770],{"class":2352},"pendingFlush",[201,6772,237],{"class":598},[201,6774,213],{"class":302},[201,6776,2323],{"class":222},[201,6778,213],{"class":6779},[212],[201,6781,6782],{"class":229},"flushViaRAF",[201,6784,234],{"class":598},[201,6786,237],{"class":598},[201,6788,3015],{"class":243},[201,6790,213],{"class":6791},[212],[201,6793,213],{"class":6794},[212],[201,6796,213],{"class":6797},[212],[201,6799,2499],{"class":207},[201,6801,213],{"class":6802},[212],[201,6804,6805],{"class":207},"攒一批，下一帧统一刷新\n",[201,6807,6808,6811,6814,6817,6820,6823,6826,6828,6831,6833,6835,6838,6840,6842,6845,6848,6851,6854,6857,6860,6863,6866,6869,6872,6875,6878,6881,6884,6887,6890,6893,6896,6899,6902,6904,6907],{"class":203,"data-line":610,"line":610},[201,6809,213],{"class":6810},[212],[201,6812,213],{"class":6813},[212],[201,6815,213],{"class":6816},[212],[201,6818,213],{"class":6819},[212],[201,6821,6822],{"class":222},"else",[201,6824,213],{"class":6825},[212],[201,6827,2323],{"class":222},[201,6829,213],{"class":6830},[212],[201,6832,6645],{"class":229},[201,6834,234],{"class":598},[201,6836,6837],{"class":1787},"0.5",[201,6839,237],{"class":598},[201,6841,3015],{"class":243},[201,6843,213],{"class":6844},[212],[201,6846,213],{"class":6847},[212],[201,6849,213],{"class":6850},[212],[201,6852,213],{"class":6853},[212],[201,6855,213],{"class":6856},[212],[201,6858,213],{"class":6859},[212],[201,6861,213],{"class":6862},[212],[201,6864,213],{"class":6865},[212],[201,6867,213],{"class":6868},[212],[201,6870,213],{"class":6871},[212],[201,6873,213],{"class":6874},[212],[201,6876,213],{"class":6877},[212],[201,6879,213],{"class":6880},[212],[201,6882,213],{"class":6883},[212],[201,6885,213],{"class":6886},[212],[201,6888,213],{"class":6889},[212],[201,6891,213],{"class":6892},[212],[201,6894,213],{"class":6895},[212],[201,6897,213],{"class":6898},[212],[201,6900,213],{"class":6901},[212],[201,6903,2499],{"class":207},[201,6905,213],{"class":6906},[212],[201,6908,6909],{"class":207},"空转等待生产方\n",[201,6911,6912,6915,6918],{"class":203,"data-line":630,"line":630},[201,6913,213],{"class":6914},[212],[201,6916,213],{"class":6917},[212],[201,6919,2571],{"class":460},[201,6921,6922],{"class":203,"data-line":684,"line":684},[201,6923,688],{"emptyLinePlaceholder":687},[201,6925,6926,6929,6932,6934,6936,6938,6940,6942,6944,6946,6948,6950,6952,6954,6957,6959,6962,6964,6966,6968,6970,6972,6974],{"class":203,"data-line":691,"line":691},[201,6927,213],{"class":6928},[212],[201,6930,213],{"class":6931},[212],[201,6933,1671],{"class":222},[201,6935,213],{"class":302},[201,6937,234],{"class":460},[201,6939,2535],{"class":309},[201,6941,5931],{"class":2311},[201,6943,454],{"class":2356},[201,6945,6161],{"class":229},[201,6947,234],{"class":598},[201,6949,237],{"class":598},[201,6951,213],{"class":302},[201,6953,2769],{"class":309},[201,6955,213],{"class":6956},[212],[201,6958,2535],{"class":309},[201,6960,6961],{"class":2311},"controller",[201,6963,454],{"class":2356},[201,6965,2425],{"class":2311},[201,6967,454],{"class":2356},[201,6969,6357],{"class":2352},[201,6971,237],{"class":460},[201,6973,213],{"class":302},[201,6975,2341],{"class":460},[201,6977,6978,6981,6984,6987,6990,6992,6995,6997,7000,7002,7005,7007,7009,7011,7013,7015],{"class":203,"data-line":1750,"line":1750},[201,6979,213],{"class":6980},[212],[201,6982,213],{"class":6983},[212],[201,6985,213],{"class":6986},[212],[201,6988,213],{"class":6989},[212],[201,6991,2305],{"class":222},[201,6993,213],{"class":6994},[212],[201,6996,1455],{"class":2311},[201,6998,213],{"class":6999},[212],[201,7001,310],{"class":309},[201,7003,213],{"class":7004},[212],[201,7006,5931],{"class":2311},[201,7008,454],{"class":2356},[201,7010,6096],{"class":229},[201,7012,234],{"class":598},[201,7014,237],{"class":598},[201,7016,2520],{"class":243},[201,7018,7019,7022,7025,7028,7031,7034,7036,7039,7041,7044,7047,7049,7051,7054,7057,7059,7062,7065,7068,7070,7073],{"class":203,"data-line":1791,"line":1791},[201,7020,213],{"class":7021},[212],[201,7023,213],{"class":7024},[212],[201,7026,213],{"class":7027},[212],[201,7029,213],{"class":7030},[212],[201,7032,7033],{"class":229},"updateChatStateAction",[201,7035,234],{"class":598},[201,7037,7038],{"class":2352},"frozenSessionId",[201,7040,264],{"class":243},[201,7042,213],{"class":7043},[212],[201,7045,7046],{"class":586},"\"streaming\"",[201,7048,237],{"class":598},[201,7050,3015],{"class":243},[201,7052,213],{"class":7053},[212],[201,7055,213],{"class":7056},[212],[201,7058,2499],{"class":207},[201,7060,213],{"class":7061},[212],[201,7063,7064],{"class":207},"状态机：loading",[201,7066,213],{"class":7067},[212],[201,7069,487],{"class":207},[201,7071,213],{"class":7072},[212],[201,7074,7075],{"class":207},"streaming\n",[201,7077,7078],{"class":203,"data-line":1796,"line":1796},[201,7079,688],{"emptyLinePlaceholder":687},[201,7081,7082,7085,7088,7091,7094,7096,7099,7102,7105,7108,7111],{"class":203,"data-line":1894,"line":1894},[201,7083,213],{"class":7084},[212],[201,7086,213],{"class":7087},[212],[201,7089,213],{"class":7090},[212],[201,7092,213],{"class":7093},[212],[201,7095,2499],{"class":207},[201,7097,213],{"class":7098},[212],[201,7100,7101],{"class":207},"——",[201,7103,213],{"class":7104},[212],[201,7106,7107],{"class":207},"按包类型分流",[201,7109,213],{"class":7110},[212],[201,7112,7113],{"class":207},"——\n",[201,7115,7116,7119,7122,7125,7128,7130,7132,7134,7136,7138,7141,7143,7146,7149,7151,7153,7156,7158,7160],{"class":203,"data-line":1936,"line":1936},[201,7117,213],{"class":7118},[212],[201,7120,213],{"class":7121},[212],[201,7123,213],{"class":7124},[212],[201,7126,213],{"class":7127},[212],[201,7129,1671],{"class":222},[201,7131,213],{"class":302},[201,7133,234],{"class":598},[201,7135,234],{"class":3383},[201,7137,1455],{"class":2352},[201,7139,213],{"class":7140},[212],[201,7142,530],{"class":222},[201,7144,213],{"class":7145},[212],[201,7147,7148],{"class":1006},"MessageResponseIDInfo",[201,7150,237],{"class":3383},[201,7152,454],{"class":2356},[201,7154,7155],{"class":2352},"user_message_id",[201,7157,237],{"class":598},[201,7159,213],{"class":302},[201,7161,2341],{"class":598},[201,7163,7164,7167,7170,7173,7176,7179,7182,7185,7188,7190,7193,7195,7197,7200,7203,7206,7208,7211,7214,7217,7220,7223,7225,7228],{"class":203,"data-line":1960,"line":1960},[201,7165,213],{"class":7166},[212],[201,7168,213],{"class":7169},[212],[201,7171,213],{"class":7172},[212],[201,7174,213],{"class":7175},[212],[201,7177,213],{"class":7178},[212],[201,7180,213],{"class":7181},[212],[201,7183,7184],{"class":2352},"newUserMessageId",[201,7186,213],{"class":7187},[212],[201,7189,310],{"class":309},[201,7191,213],{"class":7192},[212],[201,7194,396],{"class":2356},[201,7196,3015],{"class":243},[201,7198,213],{"class":7199},[212],[201,7201,213],{"class":7202},[212],[201,7204,213],{"class":7205},[212],[201,7207,2499],{"class":207},[201,7209,213],{"class":7210},[212],[201,7212,7213],{"class":207},"记下",[201,7215,213],{"class":7216},[212],[201,7218,7219],{"class":207},"user",[201,7221,213],{"class":7222},[212],[201,7224,6616],{"class":207},[201,7226,213],{"class":7227},[212],[201,7229,7230],{"class":207},"id\n",[201,7232,7233,7236,7239,7242,7245],{"class":203,"data-line":2000,"line":2000},[201,7234,213],{"class":7235},[212],[201,7237,213],{"class":7238},[212],[201,7240,213],{"class":7241},[212],[201,7243,213],{"class":7244},[212],[201,7246,2571],{"class":598},[201,7248,7249,7252,7255,7258,7261,7263,7265,7267,7269,7271,7274,7276,7279,7281,7283,7285,7288,7290,7292],{"class":203,"data-line":2070,"line":2070},[201,7250,213],{"class":7251},[212],[201,7253,213],{"class":7254},[212],[201,7256,213],{"class":7257},[212],[201,7259,213],{"class":7260},[212],[201,7262,1671],{"class":222},[201,7264,213],{"class":302},[201,7266,234],{"class":598},[201,7268,234],{"class":3383},[201,7270,1455],{"class":2352},[201,7272,213],{"class":7273},[212],[201,7275,530],{"class":222},[201,7277,213],{"class":7278},[212],[201,7280,7148],{"class":1006},[201,7282,237],{"class":3383},[201,7284,454],{"class":2356},[201,7286,7287],{"class":2352},"reserved_assistant_message_id",[201,7289,237],{"class":598},[201,7291,213],{"class":302},[201,7293,2341],{"class":598},[201,7295,7296,7299,7302,7305,7308,7311,7314,7317,7320,7322,7325,7327,7329,7332,7335,7337,7340,7343,7346,7349,7352,7354,7357],{"class":203,"data-line":3958,"line":3958},[201,7297,213],{"class":7298},[212],[201,7300,213],{"class":7301},[212],[201,7303,213],{"class":7304},[212],[201,7306,213],{"class":7307},[212],[201,7309,213],{"class":7310},[212],[201,7312,213],{"class":7313},[212],[201,7315,7316],{"class":2352},"newAgentMessageId",[201,7318,213],{"class":7319},[212],[201,7321,310],{"class":309},[201,7323,213],{"class":7324},[212],[201,7326,396],{"class":2356},[201,7328,3015],{"class":243},[201,7330,213],{"class":7331},[212],[201,7333,213],{"class":7334},[212],[201,7336,2499],{"class":207},[201,7338,213],{"class":7339},[212],[201,7341,7342],{"class":207},"记下预留的",[201,7344,213],{"class":7345},[212],[201,7347,7348],{"class":207},"assistant",[201,7350,213],{"class":7351},[212],[201,7353,6616],{"class":207},[201,7355,213],{"class":7356},[212],[201,7358,7230],{"class":207},[201,7360,7361,7364,7367,7370,7373],{"class":203,"data-line":4049,"line":4049},[201,7362,213],{"class":7363},[212],[201,7365,213],{"class":7366},[212],[201,7368,213],{"class":7369},[212],[201,7371,213],{"class":7372},[212],[201,7374,2571],{"class":598},[201,7376,7377,7380,7383,7386,7389,7391,7393,7395,7398,7400,7403,7405,7407,7409,7412,7415,7417,7419,7421,7423,7426,7428,7431,7434,7437,7439,7441],{"class":203,"data-line":4054,"line":4054},[201,7378,213],{"class":7379},[212],[201,7381,213],{"class":7382},[212],[201,7384,213],{"class":7385},[212],[201,7387,213],{"class":7388},[212],[201,7390,1671],{"class":222},[201,7392,213],{"class":302},[201,7394,234],{"class":598},[201,7396,7397],{"class":2311},"Object",[201,7399,454],{"class":2356},[201,7401,7402],{"class":229},"hasOwn",[201,7404,234],{"class":3383},[201,7406,1455],{"class":2352},[201,7408,264],{"class":243},[201,7410,213],{"class":7411},[212],[201,7413,7414],{"class":586},"\"user_files\"",[201,7416,237],{"class":3383},[201,7418,237],{"class":598},[201,7420,213],{"class":302},[201,7422,583],{"class":598},[201,7424,213],{"class":7425},[212],[201,7427,2554],{"class":207},[201,7429,213],{"class":7430},[212],[201,7432,7433],{"class":207},"用户文件包",[201,7435,213],{"class":7436},[212],[201,7438,2566],{"class":207},[201,7440,213],{"class":243},[201,7442,2571],{"class":598},[201,7444,7445,7448,7451,7454,7457,7459,7461,7463,7465,7467,7469,7471,7473,7475,7478,7481,7483,7485,7487,7489,7492,7494,7497,7500,7503,7505,7507],{"class":203,"data-line":4175,"line":4175},[201,7446,213],{"class":7447},[212],[201,7449,213],{"class":7450},[212],[201,7452,213],{"class":7453},[212],[201,7455,213],{"class":7456},[212],[201,7458,1671],{"class":222},[201,7460,213],{"class":302},[201,7462,234],{"class":598},[201,7464,7397],{"class":2311},[201,7466,454],{"class":2356},[201,7468,7402],{"class":229},[201,7470,234],{"class":3383},[201,7472,1455],{"class":2352},[201,7474,264],{"class":243},[201,7476,213],{"class":7477},[212],[201,7479,7480],{"class":586},"\"file_ids\"",[201,7482,237],{"class":3383},[201,7484,237],{"class":598},[201,7486,213],{"class":302},[201,7488,583],{"class":598},[201,7490,213],{"class":7491},[212],[201,7493,2554],{"class":207},[201,7495,213],{"class":7496},[212],[201,7498,7499],{"class":207},"生成图片",[201,7501,213],{"class":7502},[212],[201,7504,2566],{"class":207},[201,7506,213],{"class":243},[201,7508,2571],{"class":598},[201,7510,7511,7514,7517,7520,7523,7525,7528,7530,7532,7534,7536,7538,7540,7542,7544,7546,7549,7551,7553,7555,7557],{"class":203,"data-line":4318,"line":4318},[201,7512,213],{"class":7513},[212],[201,7515,213],{"class":7516},[212],[201,7518,213],{"class":7519},[212],[201,7521,213],{"class":7522},[212],[201,7524,6822],{"class":222},[201,7526,213],{"class":7527},[212],[201,7529,1671],{"class":222},[201,7531,213],{"class":302},[201,7533,234],{"class":598},[201,7535,7397],{"class":2311},[201,7537,454],{"class":2356},[201,7539,7402],{"class":229},[201,7541,234],{"class":3383},[201,7543,1455],{"class":2352},[201,7545,264],{"class":243},[201,7547,213],{"class":7548},[212],[201,7550,587],{"class":586},[201,7552,237],{"class":3383},[201,7554,237],{"class":598},[201,7556,213],{"class":302},[201,7558,2341],{"class":598},[201,7560,7561,7564,7567,7570,7573,7576,7579,7581,7584,7587,7590],{"class":203,"data-line":4323,"line":4323},[201,7562,213],{"class":7563},[212],[201,7565,213],{"class":7566},[212],[201,7568,213],{"class":7569},[212],[201,7571,213],{"class":7572},[212],[201,7574,213],{"class":7575},[212],[201,7577,213],{"class":7578},[212],[201,7580,2499],{"class":207},[201,7582,213],{"class":7583},[212],[201,7585,7586],{"class":207},"单模型：throw",[201,7588,213],{"class":7589},[212],[201,7591,7592],{"class":207},"终止；多模型：把错误路由到具体模型的面板\n",[201,7594,7595,7598,7601,7604,7607],{"class":203,"data-line":4373,"line":4373},[201,7596,213],{"class":7597},[212],[201,7599,213],{"class":7600},[212],[201,7602,213],{"class":7603},[212],[201,7605,213],{"class":7606},[212],[201,7608,2571],{"class":598},[201,7610,7611,7614,7617,7620,7623,7625,7628,7630,7632,7634,7636,7638,7640,7642,7644,7646,7649,7652,7654,7656,7658,7660,7663,7665,7668,7671,7674,7676,7678],{"class":203,"data-line":4435,"line":4435},[201,7612,213],{"class":7613},[212],[201,7615,213],{"class":7616},[212],[201,7618,213],{"class":7619},[212],[201,7621,213],{"class":7622},[212],[201,7624,6822],{"class":222},[201,7626,213],{"class":7627},[212],[201,7629,1671],{"class":222},[201,7631,213],{"class":302},[201,7633,234],{"class":598},[201,7635,7397],{"class":2311},[201,7637,454],{"class":2356},[201,7639,7402],{"class":229},[201,7641,234],{"class":3383},[201,7643,1455],{"class":2352},[201,7645,264],{"class":243},[201,7647,213],{"class":7648},[212],[201,7650,7651],{"class":586},"\"message_id\"",[201,7653,237],{"class":3383},[201,7655,237],{"class":598},[201,7657,213],{"class":302},[201,7659,583],{"class":598},[201,7661,213],{"class":7662},[212],[201,7664,2554],{"class":207},[201,7666,213],{"class":7667},[212],[201,7669,7670],{"class":207},"完整消息",[201,7672,213],{"class":7673},[212],[201,7675,2566],{"class":207},[201,7677,213],{"class":243},[201,7679,2571],{"class":598},[201,7681,7682,7685,7688,7691,7694,7696,7699,7701,7703,7705,7707,7709,7711,7713,7715,7717,7720,7723,7725,7727,7729,7731,7734,7736,7739,7742,7745,7747,7749],{"class":203,"data-line":4468,"line":4468},[201,7683,213],{"class":7684},[212],[201,7686,213],{"class":7687},[212],[201,7689,213],{"class":7690},[212],[201,7692,213],{"class":7693},[212],[201,7695,6822],{"class":222},[201,7697,213],{"class":7698},[212],[201,7700,1671],{"class":222},[201,7702,213],{"class":302},[201,7704,234],{"class":598},[201,7706,7397],{"class":2311},[201,7708,454],{"class":2356},[201,7710,7402],{"class":229},[201,7712,234],{"class":3383},[201,7714,1455],{"class":2352},[201,7716,264],{"class":243},[201,7718,213],{"class":7719},[212],[201,7721,7722],{"class":586},"\"stop_reason\"",[201,7724,237],{"class":3383},[201,7726,237],{"class":598},[201,7728,213],{"class":302},[201,7730,583],{"class":598},[201,7732,213],{"class":7733},[212],[201,7735,2554],{"class":207},[201,7737,213],{"class":7738},[212],[201,7740,7741],{"class":207},"流结束原因",[201,7743,213],{"class":7744},[212],[201,7746,2566],{"class":207},[201,7748,213],{"class":243},[201,7750,2571],{"class":598},[201,7752,7753,7756,7759,7762,7765,7767,7770,7772,7774,7776,7778,7780,7782,7784,7786,7788,7791,7793,7795,7797,7799],{"class":203,"data-line":4590,"line":4590},[201,7754,213],{"class":7755},[212],[201,7757,213],{"class":7758},[212],[201,7760,213],{"class":7761},[212],[201,7763,213],{"class":7764},[212],[201,7766,6822],{"class":222},[201,7768,213],{"class":7769},[212],[201,7771,1671],{"class":222},[201,7773,213],{"class":302},[201,7775,234],{"class":598},[201,7777,7397],{"class":2311},[201,7779,454],{"class":2356},[201,7781,7402],{"class":229},[201,7783,234],{"class":3383},[201,7785,1455],{"class":2352},[201,7787,264],{"class":243},[201,7789,213],{"class":7790},[212],[201,7792,2753],{"class":586},[201,7794,237],{"class":3383},[201,7796,237],{"class":598},[201,7798,213],{"class":302},[201,7800,2341],{"class":598},[201,7802,7803,7806,7809,7812,7815,7818,7821,7823,7826,7829,7832,7834,7836,7838,7840,7843,7845,7848,7850,7852,7854,7856],{"class":203,"data-line":4754,"line":4754},[201,7804,213],{"class":7805},[212],[201,7807,213],{"class":7808},[212],[201,7810,213],{"class":7811},[212],[201,7813,213],{"class":7814},[212],[201,7816,213],{"class":7817},[212],[201,7819,213],{"class":7820},[212],[201,7822,2305],{"class":222},[201,7824,213],{"class":7825},[212],[201,7827,7828],{"class":2311},"packetObj",[201,7830,213],{"class":7831},[212],[201,7833,310],{"class":309},[201,7835,213],{"class":302},[201,7837,234],{"class":3383},[201,7839,1455],{"class":2352},[201,7841,213],{"class":7842},[212],[201,7844,530],{"class":222},[201,7846,213],{"class":7847},[212],[201,7849,1007],{"class":1006},[201,7851,237],{"class":3383},[201,7853,454],{"class":2356},[201,7855,378],{"class":2352},[201,7857,2520],{"class":243},[201,7859,7860,7863,7866,7869,7872,7875,7878,7881,7883,7885,7887,7890,7892,7894,7897,7900,7903,7906,7909,7912,7915,7918,7920,7923,7925,7928,7930,7933],{"class":203,"data-line":4801,"line":4801},[201,7861,213],{"class":7862},[212],[201,7864,213],{"class":7865},[212],[201,7867,213],{"class":7868},[212],[201,7870,213],{"class":7871},[212],[201,7873,213],{"class":7874},[212],[201,7876,213],{"class":7877},[212],[201,7879,7880],{"class":2311},"packets",[201,7882,454],{"class":2356},[201,7884,6047],{"class":229},[201,7886,234],{"class":3383},[201,7888,7889],{"class":2352},"typedPacket",[201,7891,237],{"class":3383},[201,7893,3015],{"class":243},[201,7895,213],{"class":7896},[212],[201,7898,213],{"class":7899},[212],[201,7901,213],{"class":7902},[212],[201,7904,213],{"class":7905},[212],[201,7907,213],{"class":7908},[212],[201,7910,213],{"class":7911},[212],[201,7913,213],{"class":7914},[212],[201,7916,213],{"class":7917},[212],[201,7919,2499],{"class":207},[201,7921,213],{"class":7922},[212],[201,7924,2505],{"class":207},[201,7926,213],{"class":7927},[212],[201,7929,2031],{"class":207},[201,7931,213],{"class":7932},[212],[201,7934,7935],{"class":207},"都堆在这里\n",[201,7937,7938,7941,7944,7947,7950,7953,7956,7959,7962],{"class":203,"data-line":4853,"line":4853},[201,7939,213],{"class":7940},[212],[201,7942,213],{"class":7943},[212],[201,7945,213],{"class":7946},[212],[201,7948,213],{"class":7949},[212],[201,7951,213],{"class":7952},[212],[201,7954,213],{"class":7955},[212],[201,7957,7958],{"class":2352},"packetsVersion",[201,7960,7961],{"class":309},"++",[201,7963,2520],{"class":243},[201,7965,7966,7969,7972,7975,7978,7981,7984,7987,7990,7992,7995,7997],{"class":203,"data-line":4944,"line":4944},[201,7967,213],{"class":7968},[212],[201,7970,213],{"class":7971},[212],[201,7973,213],{"class":7974},[212],[201,7976,213],{"class":7977},[212],[201,7979,213],{"class":7980},[212],[201,7982,213],{"class":7983},[212],[201,7985,7986],{"class":2352},"singleModelDirty",[201,7988,213],{"class":7989},[212],[201,7991,310],{"class":309},[201,7993,213],{"class":7994},[212],[201,7996,3626],{"class":1787},[201,7998,2520],{"class":243},[201,8000,8001,8004,8007,8010,8013,8016,8019,8021,8024,8027,8030,8032,8035],{"class":203,"data-line":4991,"line":4991},[201,8002,213],{"class":8003},[212],[201,8005,213],{"class":8006},[212],[201,8008,213],{"class":8009},[212],[201,8011,213],{"class":8012},[212],[201,8014,213],{"class":8015},[212],[201,8017,213],{"class":8018},[212],[201,8020,2499],{"class":207},[201,8022,213],{"class":8023},[212],[201,8025,8026],{"class":207},"引文",[201,8028,213],{"class":8029},[212],[201,8031,1127],{"class":207},[201,8033,213],{"class":8034},[212],[201,8036,8037],{"class":207},"开始包单独处理\n",[201,8039,8040,8043,8046,8049,8052],{"class":203,"data-line":5060,"line":5060},[201,8041,213],{"class":8042},[212],[201,8044,213],{"class":8045},[212],[201,8047,213],{"class":8048},[212],[201,8050,213],{"class":8051},[212],[201,8053,2571],{"class":598},[201,8055,8056,8059,8062,8065,8068,8070,8073,8075,8078,8080],{"class":203,"data-line":5111,"line":5111},[201,8057,213],{"class":8058},[212],[201,8060,213],{"class":8061},[212],[201,8063,213],{"class":8064},[212],[201,8066,213],{"class":8067},[212],[201,8069,6770],{"class":2352},[201,8071,213],{"class":8072},[212],[201,8074,310],{"class":309},[201,8076,213],{"class":8077},[212],[201,8079,3626],{"class":1787},[201,8081,2520],{"class":243},[201,8083,8084,8087,8090],{"class":203,"data-line":5190,"line":5190},[201,8085,213],{"class":8086},[212],[201,8088,213],{"class":8089},[212],[201,8091,2571],{"class":460},[201,8093,8094],{"class":203,"data-line":5275,"line":5275},[201,8095,2571],{"class":233},[201,8097,8098,8101,8103,8105],{"class":203,"data-line":5316,"line":5316},[201,8099,8100],{"class":229},"flushPendingUpdates",[201,8102,234],{"class":233},[201,8104,237],{"class":233},[201,8106,2520],{"class":243},[15,8108,8109,8115],{},[18,8110,1224,8111,8114],{},[47,8112,8113],{"code":8113},"Object.hasOwn(packet, \"xxx\")"," 判断包类型","——不同包有不同的顶层字段，这是项目里判别\"这是哪种包\"的惯用手法（对应后端 Pydantic 的判别联合）。",[15,8117,8118],{},[18,8119,8120],{},"渲染性能三件套",[15,8122,8123],{},[18,8124,5768,8125,8127],{},[47,8126,6782],{"code":6782}," 批量刷新",[15,8129,8130,8133,8134,8137],{},[47,8131,8132],{"code":8132},"requestAnimationFrame"," 把一帧内的所有包",[18,8135,8136],{},"合并成一次"," React 更新：",[192,8139,8141],{"className":2296,"code":8140,"language":2298,"meta":197,"style":197},"await delay(50);\nwhile (...) {\n  if (stack.isEmpty()) {\n    if (pendingFlush) await flushViaRAF();  // 攒到下一帧再刷\n  }\n  ...\n}\n",[47,8142,8143,8160,8176,8206,8259,8269,8279],{"__ignoreMap":197},[201,8144,8145,8147,8150,8152,8154,8156,8158],{"class":203,"data-line":204,"line":204},[201,8146,2323],{"class":222},[201,8148,213],{"class":8149},[212],[201,8151,6645],{"class":229},[201,8153,234],{"class":233},[201,8155,6650],{"class":1787},[201,8157,237],{"class":233},[201,8159,2520],{"class":243},[201,8161,8162,8164,8166,8168,8170,8172,8174],{"class":203,"data-line":219,"line":219},[201,8163,3619],{"class":222},[201,8165,213],{"class":302},[201,8167,234],{"class":233},[201,8169,396],{"class":2356},[201,8171,237],{"class":233},[201,8173,213],{"class":302},[201,8175,2341],{"class":233},[201,8177,8178,8181,8184,8186,8188,8190,8192,8194,8196,8198,8200,8202,8204],{"class":203,"data-line":287,"line":287},[201,8179,213],{"class":8180},[212],[201,8182,213],{"class":8183},[212],[201,8185,1671],{"class":222},[201,8187,213],{"class":302},[201,8189,234],{"class":460},[201,8191,5931],{"class":2311},[201,8193,454],{"class":2356},[201,8195,6161],{"class":229},[201,8197,234],{"class":598},[201,8199,237],{"class":598},[201,8201,237],{"class":460},[201,8203,213],{"class":302},[201,8205,2341],{"class":460},[201,8207,8208,8211,8214,8217,8220,8222,8224,8226,8228,8230,8232,8234,8237,8239,8241,8243,8245,8248,8251,8253,8256],{"class":203,"data-line":325,"line":325},[201,8209,213],{"class":8210},[212],[201,8212,213],{"class":8213},[212],[201,8215,213],{"class":8216},[212],[201,8218,213],{"class":8219},[212],[201,8221,1671],{"class":222},[201,8223,213],{"class":302},[201,8225,234],{"class":598},[201,8227,6770],{"class":2352},[201,8229,237],{"class":598},[201,8231,213],{"class":302},[201,8233,2323],{"class":222},[201,8235,213],{"class":8236},[212],[201,8238,6782],{"class":229},[201,8240,234],{"class":598},[201,8242,237],{"class":598},[201,8244,3015],{"class":243},[201,8246,213],{"class":8247},[212],[201,8249,213],{"class":8250},[212],[201,8252,2499],{"class":207},[201,8254,213],{"class":8255},[212],[201,8257,8258],{"class":207},"攒到下一帧再刷\n",[201,8260,8261,8264,8267],{"class":203,"data-line":345,"line":345},[201,8262,213],{"class":8263},[212],[201,8265,213],{"class":8266},[212],[201,8268,2571],{"class":460},[201,8270,8271,8274,8277],{"class":203,"data-line":403,"line":403},[201,8272,213],{"class":8273},[212],[201,8275,213],{"class":8276},[212],[201,8278,1607],{"class":2356},[201,8280,8281],{"class":203,"data-line":502,"line":502},[201,8282,2571],{"class":233},[15,8284,8285],{},[18,8286,5793,8287,8290],{},[47,8288,8289],{"code":8289},"packetCount"," 代替数组做 memo（AgentMessage.tsx:73）",[15,8292,8293,8294,8296,8297,8300,8301,8303,8304,8307],{},"React.memo 比较 props。",[47,8295,7880],{"code":7880}," 数组被",[18,8298,8299],{},"原地 push","（引用不变），所以 memo 直接比较数组会认为\"没变\"。项目用 ",[47,8302,8289],{"code":8289}," 这个",[18,8305,8306],{},"原始数字","做比较：",[192,8309,8313],{"className":8310,"code":8311,"language":8312,"meta":197,"style":197},"language-tsx shiki shiki-themes catppuccin-latte one-dark-pro","prev.packetCount === next.packetCount &&\nprev.chatState.agent === next.chatState.agent &&\n... // 其余字段\n","tsx",[47,8314,8315,8337,8358],{"__ignoreMap":197},[201,8316,8317,8320,8323,8325,8328,8331,8334],{"class":203,"data-line":204,"line":204},[201,8318,8319],{},"prev.packetCount",[201,8321,213],{"class":8322},[212],[201,8324,2788],{},[201,8326,213],{"class":8327},[212],[201,8329,8330],{},"next.packetCount",[201,8332,213],{"class":8333},[212],[201,8335,8336],{},"&&\n",[201,8338,8339,8342,8345,8347,8350,8353,8356],{"class":203,"data-line":219,"line":219},[201,8340,8341],{},"prev.chatState.agent",[201,8343,213],{"class":8344},[212],[201,8346,2788],{},[201,8348,213],{"class":8349},[212],[201,8351,8352],{},"next.chatState.agent",[201,8354,213],{"class":8355},[212],[201,8357,8336],{},[201,8359,8360,8362,8365,8367,8370],{"class":203,"data-line":287,"line":287},[201,8361,396],{},[201,8363,213],{"class":8364},[212],[201,8366,2499],{},[201,8368,213],{"class":8369},[212],[201,8371,8372],{},"其余字段\n",[192,8374,8376],{"className":2296,"code":8375,"language":2298,"meta":197,"style":197},"// useChatSessionController.ts:319 的注释点明了设计意图：\n// \"AgentMessage's memo compares packetCount, not the packets array.\"\nnode.packetCount = accumulated.length;\n",[47,8377,8378,8394,8444],{"__ignoreMap":197},[201,8379,8380,8382,8385,8388,8391],{"class":203,"data-line":204,"line":204},[201,8381,2499],{"class":207},[201,8383,213],{"class":8384},[212],[201,8386,8387],{"class":207},"useChatSessionController.ts:319",[201,8389,213],{"class":8390},[212],[201,8392,8393],{"class":207},"的注释点明了设计意图：\n",[201,8395,8396,8398,8401,8404,8407,8410,8413,8416,8419,8422,8425,8427,8430,8433,8436,8438,8441],{"class":203,"data-line":219,"line":219},[201,8397,2499],{"class":207},[201,8399,213],{"class":8400},[212],[201,8402,8403],{"class":207},"\"AgentMessage's",[201,8405,213],{"class":8406},[212],[201,8408,8409],{"class":207},"memo",[201,8411,213],{"class":8412},[212],[201,8414,8415],{"class":207},"compares",[201,8417,213],{"class":8418},[212],[201,8420,8421],{"class":207},"packetCount,",[201,8423,213],{"class":8424},[212],[201,8426,1677],{"class":207},[201,8428,213],{"class":8429},[212],[201,8431,8432],{"class":207},"the",[201,8434,213],{"class":8435},[212],[201,8437,7880],{"class":207},[201,8439,213],{"class":8440},[212],[201,8442,8443],{"class":207},"array.\"\n",[201,8445,8446,8449,8451,8453,8456,8458,8461,8464,8466,8468],{"class":203,"data-line":287,"line":287},[201,8447,8448],{"class":2311},"node",[201,8450,454],{"class":2356},[201,8452,8289],{"class":2352},[201,8454,213],{"class":8455},[212],[201,8457,310],{"class":309},[201,8459,213],{"class":8460},[212],[201,8462,8463],{"class":2311},"accumulated",[201,8465,454],{"class":2356},[201,8467,6188],{"class":2352},[201,8469,2520],{"class":243},[15,8471,8472,8473,8476,8477,8480],{},"③ trailing flush：burst 结束不饿死最后几个包",[47,8474,8475],{"code":8475},"useChatSessionController.ts:346","：如果距上次 flush 不足 100ms 就设一个 ",[18,8478,8479],{},"120ms 的定时器","，保证\"突发数据的最后几个包\"即使等不到下一个包，也会被定时刷出来。",[763,8482,8483],{"id":8483},"渲染层",[15,8485,8486,8489],{},[47,8487,8488],{"code":8488},"AgentMessage"," 拿到原始 packets 后交给这个 hook 处理成 UI 数据：",[192,8491,8493],{"className":2296,"code":8492,"language":2298,"meta":197,"style":197},"// 处理状态放在 ref 里：增量、同步、不触发双渲染\nconst stateRef = useRef\u003CProcessorState>(createInitialState(nodeId));\n// 只有真正的 UI 状态才用 useState\nconst [renderComplete, setRenderComplete] = useState(false);\n\n// 增量处理：只处理新到的包（避免重复处理已消费的）\nif (rawPackets.length > stateRef.current.nextPacketIndex) {\n  stateRef.current = processPackets(stateRef.current, rawPackets);\n}\n",[47,8494,8495,8517,8561,8589,8629,8633,8643,8683,8727],{"__ignoreMap":197},[201,8496,8497,8499,8502,8505,8508,8511,8514],{"class":203,"data-line":204,"line":204},[201,8498,2499],{"class":207},[201,8500,213],{"class":8501},[212],[201,8503,8504],{"class":207},"处理状态放在",[201,8506,213],{"class":8507},[212],[201,8509,8510],{"class":207},"ref",[201,8512,213],{"class":8513},[212],[201,8515,8516],{"class":207},"里：增量、同步、不触发双渲染\n",[201,8518,8519,8521,8524,8527,8530,8532,8535,8538,8540,8543,8545,8547,8550,8552,8555,8557,8559],{"class":203,"data-line":219,"line":219},[201,8520,2305],{"class":222},[201,8522,213],{"class":8523},[212],[201,8525,8526],{"class":2311},"stateRef",[201,8528,213],{"class":8529},[212],[201,8531,310],{"class":309},[201,8533,213],{"class":8534},[212],[201,8536,8537],{"class":229},"useRef",[201,8539,2594],{"class":233},[201,8541,8542],{"class":1006},"ProcessorState",[201,8544,247],{"class":233},[201,8546,234],{"class":233},[201,8548,8549],{"class":229},"createInitialState",[201,8551,234],{"class":460},[201,8553,8554],{"class":2352},"nodeId",[201,8556,237],{"class":460},[201,8558,237],{"class":233},[201,8560,2520],{"class":243},[201,8562,8563,8565,8568,8571,8574,8577,8580,8583,8586],{"class":203,"data-line":287,"line":287},[201,8564,2499],{"class":207},[201,8566,213],{"class":8567},[212],[201,8569,8570],{"class":207},"只有真正的",[201,8572,213],{"class":8573},[212],[201,8575,8576],{"class":207},"UI",[201,8578,213],{"class":8579},[212],[201,8581,8582],{"class":207},"状态才用",[201,8584,213],{"class":8585},[212],[201,8587,8588],{"class":207},"useState\n",[201,8590,8591,8593,8595,8597,8600,8602,8605,8608,8610,8613,8615,8618,8621,8623,8625,8627],{"class":203,"data-line":325,"line":325},[201,8592,2305],{"class":222},[201,8594,213],{"class":243},[201,8596,257],{"class":233},[201,8598,8599],{"class":2311},"renderComplete",[201,8601,264],{"class":243},[201,8603,213],{"class":8604},[212],[201,8606,8607],{"class":2311},"setRenderComplete",[201,8609,281],{"class":233},[201,8611,213],{"class":8612},[212],[201,8614,310],{"class":309},[201,8616,213],{"class":8617},[212],[201,8619,8620],{"class":229},"useState",[201,8622,234],{"class":233},[201,8624,5986],{"class":1787},[201,8626,237],{"class":233},[201,8628,2520],{"class":243},[201,8630,8631],{"class":203,"data-line":345,"line":345},[201,8632,688],{"emptyLinePlaceholder":687},[201,8634,8635,8637,8640],{"class":203,"data-line":403,"line":403},[201,8636,2499],{"class":207},[201,8638,213],{"class":8639},[212],[201,8641,8642],{"class":207},"增量处理：只处理新到的包（避免重复处理已消费的）\n",[201,8644,8645,8647,8649,8651,8654,8656,8658,8660,8662,8665,8667,8669,8672,8674,8677,8679,8681],{"class":203,"data-line":502,"line":502},[201,8646,1671],{"class":222},[201,8648,213],{"class":302},[201,8650,234],{"class":233},[201,8652,8653],{"class":2311},"rawPackets",[201,8655,454],{"class":2356},[201,8657,6188],{"class":2352},[201,8659,213],{"class":309},[201,8661,247],{"class":309},[201,8663,213],{"class":8664},[212],[201,8666,8526],{"class":2311},[201,8668,454],{"class":2356},[201,8670,8671],{"class":2311},"current",[201,8673,454],{"class":2356},[201,8675,8676],{"class":2352},"nextPacketIndex",[201,8678,237],{"class":233},[201,8680,213],{"class":302},[201,8682,2341],{"class":233},[201,8684,8685,8688,8691,8693,8695,8697,8700,8702,8705,8708,8710,8712,8714,8716,8718,8721,8723,8725],{"class":203,"data-line":541,"line":541},[201,8686,213],{"class":8687},[212],[201,8689,213],{"class":8690},[212],[201,8692,8526],{"class":2311},[201,8694,454],{"class":2356},[201,8696,8671],{"class":2352},[201,8698,213],{"class":8699},[212],[201,8701,310],{"class":309},[201,8703,213],{"class":8704},[212],[201,8706,8707],{"class":229},"processPackets",[201,8709,234],{"class":460},[201,8711,8526],{"class":2311},[201,8713,454],{"class":2356},[201,8715,8671],{"class":2352},[201,8717,264],{"class":243},[201,8719,213],{"class":8720},[212],[201,8722,8653],{"class":2352},[201,8724,237],{"class":460},[201,8726,2520],{"class":243},[201,8728,8729],{"class":203,"data-line":610,"line":610},[201,8730,2571],{"class":233},[15,8732,8733,8735],{},[47,8734,8707],{"code":8707},"（packetProcessor.ts）遍历新包：",[40,8737,8738,8747,8754,8763,8774],{},[43,8739,8740,8742,8743,8746],{},[47,8741,1249],{"code":1249}," → 累加进 ",[47,8744,8745],{"code":8745},"potentialDisplayGroups"," 的文本块；",[43,8748,8749,8750,8753],{},"工具类包 → 归类成 ",[47,8751,8752],{"code":8752},"toolGroups","（步骤/轮次分组，供时间线 UI 展示\"思考了几步\"）；",[43,8755,8756,8759,8760,2201],{},[47,8757,8758],{"code":8758},"message_start"," → 标记 ",[47,8761,8762],{"code":8762},"finalAnswerComing",[43,8764,8765,8759,8767,8770,8771,2201],{},[47,8766,1322],{"code":1322},[47,8768,8769],{"code":8769},"stopPacketSeen"," 和 ",[47,8772,8773],{"code":8773},"stopReason",[43,8775,8776,8778,8779,31],{},[47,8777,1302],{"code":1302}," → 进 ",[47,8780,8781],{"code":8781},"citationMap",[15,8783,8784,8785,8788,8789,8792,8793,8795,8796,31],{},"最终回答文本由 ",[47,8786,8787],{"code":8787},"displayGroups"," 渲染成 Markdown，每次新 delta 到达 → ",[47,8790,8791],{"code":8791},"rawPackets.length"," 增加 → 重新 ",[47,8794,8707],{"code":8707}," → 重渲染。\n",[18,8797,8798],{},"打字机效果 = 增量数据 + 高频重渲染",[10,8800,8801],{"id":8801},"心跳机制",[15,8803,8804,8805,8808,8809,8811],{},"LLM 生成可能长时间\"没动静\"（比如调工具、检索文档），但 HTTP 长连接如果长时间没有数据，",[18,8806,8807],{},"中间代理/负载均衡可能把连接掐掉","。\n心跳（",[47,8810,1312],{"code":1312},"）就是用来保活的。",[763,8813,8815],{"id":8814},"产生方后端","产生方（后端）",[15,8817,8818,8819,8822],{},"主 stream 的 reader 在**空闲超过 ",[47,8820,8821],{"code":8821},"CHAT_HEARTBEAT_INTERVAL_S","（默认 15 秒）**时发一个心跳包（process_message.py:1500-1508）：",[192,8824,8826],{"className":194,"code":8825,"language":196,"meta":197,"style":197},"while True:\n    try:\n        item = tee.get(timeout=_CANCEL_POLL_INTERVAL_S)\n    except queue.Empty:   # 队列空 = 没有真实包到来\n        now = time.monotonic()\n        if now - last_packet_yield >= CHAT_HEARTBEAT_INTERVAL_S:\n            yield heartbeat_packet()          # 发个心跳保活\n            last_packet_yield = now\n        continue\n    ...\n",[47,8827,8828,8840,8858,8914,8971,9020,9078,9166,9217,9246],{"__ignoreMap":197},[201,8829,8830,8832,8835,8838],{"class":203,"data-line":204,"line":204},[201,8831,3619],{"class":222},[201,8833,213],{"class":8834},[212],[201,8836,8837],{"class":1787},"True",[201,8839,284],{"class":243},[201,8841,8842,8845,8848,8851,8854,8856],{"class":203,"data-line":219,"line":219},[201,8843,213],{"class":8844},[212],[201,8846,213],{"class":8847},[212],[201,8849,213],{"class":8850},[212],[201,8852,213],{"class":8853},[212],[201,8855,340],{"class":222},[201,8857,284],{"class":243},[201,8859,8860,8863,8866,8869,8872,8875,8878,8881,8884,8887,8890,8892,8895,8897,8899,8902,8904,8907,8909,8912],{"class":203,"data-line":287,"line":287},[201,8861,213],{"class":8862},[212],[201,8864,213],{"class":8865},[212],[201,8867,213],{"class":8868},[212],[201,8870,213],{"class":8871},[212],[201,8873,213],{"class":8874},[212],[201,8876,213],{"class":8877},[212],[201,8879,213],{"class":8880},[212],[201,8882,213],{"class":8883},[212],[201,8885,8886],{"class":302},"item",[201,8888,213],{"class":8889},[212],[201,8891,310],{"class":309},[201,8893,213],{"class":8894},[212],[201,8896,2224],{"class":302},[201,8898,454],{"class":243},[201,8900,8901],{"class":316},"get",[201,8903,234],{"class":233},[201,8905,8906],{"class":716},"timeout",[201,8908,310],{"class":309},[201,8910,8911],{"class":395},"_CANCEL_POLL_INTERVAL_S",[201,8913,322],{"class":233},[201,8915,8916,8919,8922,8925,8928,8930,8933,8936,8938,8941,8943,8946,8949,8952,8954,8957,8960,8963,8965,8968],{"class":203,"data-line":325,"line":325},[201,8917,213],{"class":8918},[212],[201,8920,213],{"class":8921},[212],[201,8923,213],{"class":8924},[212],[201,8926,213],{"class":8927},[212],[201,8929,517],{"class":222},[201,8931,213],{"class":8932},[212],[201,8934,8935],{"class":302},"queue",[201,8937,454],{"class":243},[201,8939,8940],{"class":302},"Empty",[201,8942,590],{"class":243},[201,8944,213],{"class":8945},[212],[201,8947,213],{"class":8948},[212],[201,8950,213],{"class":8951},[212],[201,8953,208],{"class":207},[201,8955,213],{"class":8956},[212],[201,8958,8959],{"class":207},"队列空",[201,8961,213],{"class":8962},[212],[201,8964,310],{"class":207},[201,8966,213],{"class":8967},[212],[201,8969,8970],{"class":207},"没有真实包到来\n",[201,8972,8973,8976,8979,8982,8985,8988,8991,8994,8997,9000,9003,9005,9008,9011,9013,9016,9018],{"class":203,"data-line":345,"line":345},[201,8974,213],{"class":8975},[212],[201,8977,213],{"class":8978},[212],[201,8980,213],{"class":8981},[212],[201,8983,213],{"class":8984},[212],[201,8986,213],{"class":8987},[212],[201,8989,213],{"class":8990},[212],[201,8992,213],{"class":8993},[212],[201,8995,213],{"class":8996},[212],[201,8998,8999],{"class":302},"now",[201,9001,213],{"class":9002},[212],[201,9004,310],{"class":309},[201,9006,213],{"class":9007},[212],[201,9009,9010],{"class":302},"time",[201,9012,454],{"class":243},[201,9014,9015],{"class":316},"monotonic",[201,9017,234],{"class":233},[201,9019,322],{"class":233},[201,9021,9022,9025,9028,9031,9034,9037,9040,9043,9046,9048,9051,9053,9056,9058,9061,9064,9067,9069,9071,9074,9076],{"class":203,"data-line":403,"line":403},[201,9023,213],{"class":9024},[212],[201,9026,213],{"class":9027},[212],[201,9029,213],{"class":9030},[212],[201,9032,213],{"class":9033},[212],[201,9035,213],{"class":9036},[212],[201,9038,213],{"class":9039},[212],[201,9041,213],{"class":9042},[212],[201,9044,213],{"class":9045},[212],[201,9047,1671],{"class":222},[201,9049,213],{"class":9050},[212],[201,9052,8999],{"class":302},[201,9054,213],{"class":9055},[212],[201,9057,244],{"class":309},[201,9059,213],{"class":9060},[212],[201,9062,9063],{"class":302},"last_packet_yield",[201,9065,213],{"class":9066},[212],[201,9068,247],{"class":309},[201,9070,310],{"class":309},[201,9072,213],{"class":9073},[212],[201,9075,8821],{"class":395},[201,9077,284],{"class":243},[201,9079,9080,9083,9086,9089,9092,9095,9098,9101,9104,9107,9110,9113,9116,9118,9121,9124,9126,9128,9131,9134,9137,9140,9143,9146,9149,9152,9155,9158,9160,9163],{"class":203,"data-line":502,"line":502},[201,9081,213],{"class":9082},[212],[201,9084,213],{"class":9085},[212],[201,9087,213],{"class":9088},[212],[201,9090,213],{"class":9091},[212],[201,9093,213],{"class":9094},[212],[201,9096,213],{"class":9097},[212],[201,9099,213],{"class":9100},[212],[201,9102,213],{"class":9103},[212],[201,9105,213],{"class":9106},[212],[201,9108,213],{"class":9109},[212],[201,9111,213],{"class":9112},[212],[201,9114,213],{"class":9115},[212],[201,9117,66],{"class":222},[201,9119,213],{"class":9120},[212],[201,9122,9123],{"class":316},"heartbeat_packet",[201,9125,234],{"class":233},[201,9127,237],{"class":233},[201,9129,213],{"class":9130},[212],[201,9132,213],{"class":9133},[212],[201,9135,213],{"class":9136},[212],[201,9138,213],{"class":9139},[212],[201,9141,213],{"class":9142},[212],[201,9144,213],{"class":9145},[212],[201,9147,213],{"class":9148},[212],[201,9150,213],{"class":9151},[212],[201,9153,213],{"class":9154},[212],[201,9156,213],{"class":9157},[212],[201,9159,208],{"class":207},[201,9161,213],{"class":9162},[212],[201,9164,9165],{"class":207},"发个心跳保活\n",[201,9167,9168,9171,9174,9177,9180,9183,9186,9189,9192,9195,9198,9201,9204,9206,9209,9211,9214],{"class":203,"data-line":541,"line":541},[201,9169,213],{"class":9170},[212],[201,9172,213],{"class":9173},[212],[201,9175,213],{"class":9176},[212],[201,9178,213],{"class":9179},[212],[201,9181,213],{"class":9182},[212],[201,9184,213],{"class":9185},[212],[201,9187,213],{"class":9188},[212],[201,9190,213],{"class":9191},[212],[201,9193,213],{"class":9194},[212],[201,9196,213],{"class":9197},[212],[201,9199,213],{"class":9200},[212],[201,9202,213],{"class":9203},[212],[201,9205,9063],{"class":302},[201,9207,213],{"class":9208},[212],[201,9210,310],{"class":309},[201,9212,213],{"class":9213},[212],[201,9215,9216],{"class":302},"now\n",[201,9218,9219,9222,9225,9228,9231,9234,9237,9240,9243],{"class":203,"data-line":610,"line":610},[201,9220,213],{"class":9221},[212],[201,9223,213],{"class":9224},[212],[201,9226,213],{"class":9227},[212],[201,9229,213],{"class":9230},[212],[201,9232,213],{"class":9233},[212],[201,9235,213],{"class":9236},[212],[201,9238,213],{"class":9239},[212],[201,9241,213],{"class":9242},[212],[201,9244,9245],{"class":222},"continue\n",[201,9247,9248,9251,9254,9257,9260],{"class":203,"data-line":630,"line":630},[201,9249,213],{"class":9250},[212],[201,9252,213],{"class":9253},[212],[201,9255,213],{"class":9256},[212],[201,9258,213],{"class":9259},[212],[201,9261,1607],{"class":395},[15,9263,9264,9267],{},[47,9265,9266],{"code":9266},"heartbeat_packet()","（streaming_models.py:496）：",[192,9269,9271],{"className":194,"code":9270,"language":196,"meta":197,"style":197},"def heartbeat_packet() -> Packet:\n    \"\"\"Keepalive for silent stretches; carries no run state.\"\"\"\n    return Packet(placement=Placement(turn_index=0), obj=ChatHeartbeat())\n",[47,9272,9273,9300,9358],{"__ignoreMap":197},[201,9274,9275,9277,9280,9282,9284,9286,9289,9291,9293,9296,9298],{"class":203,"data-line":204,"line":204},[201,9276,223],{"class":222},[201,9278,213],{"class":9279},[212],[201,9281,9123],{"class":229},[201,9283,234],{"class":233},[201,9285,237],{"class":233},[201,9287,213],{"class":9288},[212],[201,9290,244],{"class":243},[201,9292,247],{"class":243},[201,9294,213],{"class":9295},[212],[201,9297,1007],{"class":302},[201,9299,284],{"class":243},[201,9301,9302,9305,9308,9311,9314,9317,9320,9322,9325,9328,9331,9334,9337,9340,9343,9346,9349,9352,9355],{"class":203,"data-line":219,"line":219},[201,9303,213],{"class":9304},[212],[201,9306,213],{"class":9307},[212],[201,9309,213],{"class":9310},[212],[201,9312,213],{"class":9313},[212],[201,9315,9316],{"class":586},"\"\"\"Keepalive",[201,9318,213],{"class":9319},[212],[201,9321,372],{"class":586},[201,9323,213],{"class":9324},[212],[201,9326,9327],{"class":586},"silent",[201,9329,213],{"class":9330},[212],[201,9332,9333],{"class":586},"stretches;",[201,9335,213],{"class":9336},[212],[201,9338,9339],{"class":586},"carries",[201,9341,213],{"class":9342},[212],[201,9344,9345],{"class":586},"no",[201,9347,213],{"class":9348},[212],[201,9350,9351],{"class":586},"run",[201,9353,213],{"class":9354},[212],[201,9356,9357],{"class":586},"state.\"\"\"\n",[201,9359,9360,9363,9366,9369,9372,9374,9377,9379,9381,9383,9385,9387,9389,9392,9394,9396,9398,9400,9403,9405,9407,9410,9412,9414],{"class":203,"data-line":287,"line":287},[201,9361,213],{"class":9362},[212],[201,9364,213],{"class":9365},[212],[201,9367,213],{"class":9368},[212],[201,9370,213],{"class":9371},[212],[201,9373,694],{"class":222},[201,9375,213],{"class":9376},[212],[201,9378,1007],{"class":316},[201,9380,234],{"class":233},[201,9382,1033],{"class":716},[201,9384,310],{"class":309},[201,9386,1041],{"class":316},[201,9388,234],{"class":460},[201,9390,9391],{"class":716},"turn_index",[201,9393,310],{"class":309},[201,9395,6199],{"class":1787},[201,9397,237],{"class":460},[201,9399,264],{"class":243},[201,9401,213],{"class":9402},[212],[201,9404,378],{"class":716},[201,9406,310],{"class":309},[201,9408,9409],{"class":316},"ChatHeartbeat",[201,9411,234],{"class":460},[201,9413,237],{"class":460},[201,9415,322],{"class":233},[15,9417,9418,9421],{},[47,9419,9420],{"code":9420},"CHAT_HEARTBEAT_INTERVAL_S = int(os.environ.get(\"CHAT_HEARTBEAT_INTERVAL_S\") or \"15\")","（chat_configs.py:35）——可用环境变量调整。",[763,9423,9425],{"id":9424},"消费方前端","消费方（前端）",[40,9427,9428,9437,9444],{},[43,9429,9430,9432,9433,9436],{},[47,9431,2586],{"code":2586},"（lib.tsx:207）在",[18,9434,9435],{},"发消息链路","里直接丢弃心跳；",[43,9438,9439,9440,9443],{},"resume 链路里心跳被用作",[18,9441,9442],{},"liveness tick","（useChatSessionController.ts:341）：\n收到心跳说明连接还活着、会话还是当前焦点，用于决定是否继续刷新；",[43,9445,9446],{},"渲染层完全看不见心跳包。",[10,9448,9449],{"id":9449},"断线重连与流回放",[15,9451,9452,9453,9456],{},"SSE 是单向长连接，任何网络抖动都可能断。OcEasy 用\"",[18,9454,9455],{},"持久化缓冲 + 游标回放","\"实现断线续传。",[763,9458,176],{"id":9459},"后端-1",[15,9461,9462,9463,9465,9466,9468,9469,9472],{},"写路径：",[47,9464,1420],{"code":1420}," 的 writer 线程把每个包 ",[47,9467,447],{"code":447}," 后",[18,9470,9471],{},"同时","写入缓冲：",[192,9474,9476],{"className":194,"code":9475,"language":196,"meta":197,"style":197},"# process_message.py:1650\nfor pre_run_packet in pre_run_packets:\n    stream_buffer.append_line(get_json_line(pre_run_packet.model_dump()))\n",[47,9477,9478,9488,9511],{"__ignoreMap":197},[201,9479,9480,9482,9485],{"class":203,"data-line":204,"line":204},[201,9481,208],{"class":207},[201,9483,213],{"class":9484},[212],[201,9486,9487],{"class":207},"process_message.py:1650\n",[201,9489,9490,9492,9495,9498,9501,9503,9506,9509],{"class":203,"data-line":219,"line":219},[201,9491,372],{"class":222},[201,9493,213],{"class":9494},[212],[201,9496,9497],{"class":302},"pre_run_packet",[201,9499,213],{"class":9500},[212],[201,9502,384],{"class":222},[201,9504,213],{"class":9505},[212],[201,9507,9508],{"class":302},"pre_run_packets",[201,9510,284],{"class":243},[201,9512,9513,9516,9519,9522,9525,9528,9530,9533,9535,9537,9539,9541,9543,9545,9547,9549,9551],{"class":203,"data-line":287,"line":287},[201,9514,213],{"class":9515},[212],[201,9517,213],{"class":9518},[212],[201,9520,213],{"class":9521},[212],[201,9523,213],{"class":9524},[212],[201,9526,9527],{"class":302},"stream_buffer",[201,9529,454],{"class":243},[201,9531,9532],{"class":316},"append_line",[201,9534,234],{"class":233},[201,9536,447],{"class":316},[201,9538,234],{"class":460},[201,9540,9497],{"class":302},[201,9542,454],{"class":243},[201,9544,457],{"class":316},[201,9546,234],{"class":598},[201,9548,237],{"class":598},[201,9550,237],{"class":460},[201,9552,322],{"class":233},[15,9554,9555,9557,9558,2201,9561,9563,9564,9567,9568,9571],{},[47,9556,9532],{"code":9532},"（stream_buffer.py:80）攒到一定字节再批量 ",[47,9559,9560],{"code":9560},"flush",[47,9562,9560],{"code":9560}," 用 ",[18,9565,9566],{},"zlib 压缩","后按 ",[47,9569,9570],{"code":9570},"chunk_count"," 分块写进共享缓存（Redis），带 TTL：",[192,9573,9575],{"className":194,"code":9574,"language":196,"meta":197,"style":197},"def flush(self) -> None:\n    payload = zlib.compress(\"\".join(self._pending).encode(\"utf-8\"))\n    self._cache.set(\n        _chunk_key(self._chat_session_id, self._run_id, self._meta.chunk_count),\n        payload,\n        ex=CHAT_STREAM_BUFFER_TTL_S,\n    )\n",[47,9576,9577,9608,9676,9704,9774,9804,9840],{"__ignoreMap":197},[201,9578,9579,9581,9584,9586,9588,9592,9594,9597,9599,9601,9604,9606],{"class":203,"data-line":204,"line":204},[201,9580,223],{"class":222},[201,9582,213],{"class":9583},[212],[201,9585,9560],{"class":229},[201,9587,234],{"class":233},[201,9589,9591],{"class":9590},"sLEyy","self",[201,9593,237],{"class":233},[201,9595,213],{"class":9596},[212],[201,9598,244],{"class":243},[201,9600,247],{"class":243},[201,9602,213],{"class":9603},[212],[201,9605,271],{"class":1787},[201,9607,284],{"class":243},[201,9609,9610,9613,9616,9619,9622,9625,9628,9630,9633,9636,9638,9641,9643,9645,9647,9650,9652,9655,9657,9660,9662,9664,9667,9669,9672,9674],{"class":203,"data-line":219,"line":219},[201,9611,213],{"class":9612},[212],[201,9614,213],{"class":9615},[212],[201,9617,213],{"class":9618},[212],[201,9620,213],{"class":9621},[212],[201,9623,9624],{"class":302},"payload",[201,9626,213],{"class":9627},[212],[201,9629,310],{"class":309},[201,9631,213],{"class":9632},[212],[201,9634,9635],{"class":302},"zlib",[201,9637,454],{"class":243},[201,9639,9640],{"class":316},"compress",[201,9642,234],{"class":233},[201,9644,3194],{"class":586},[201,9646,454],{"class":243},[201,9648,9649],{"class":316},"join",[201,9651,234],{"class":460},[201,9653,9591],{"class":9654},"sQf5i",[201,9656,454],{"class":243},[201,9658,9659],{"class":302},"_pending",[201,9661,237],{"class":460},[201,9663,454],{"class":243},[201,9665,9666],{"class":316},"encode",[201,9668,234],{"class":460},[201,9670,9671],{"class":586},"\"utf-8\"",[201,9673,237],{"class":460},[201,9675,322],{"class":233},[201,9677,9678,9681,9684,9687,9690,9692,9694,9697,9699,9702],{"class":203,"data-line":287,"line":287},[201,9679,213],{"class":9680},[212],[201,9682,213],{"class":9683},[212],[201,9685,213],{"class":9686},[212],[201,9688,213],{"class":9689},[212],[201,9691,9591],{"class":9654},[201,9693,454],{"class":243},[201,9695,9696],{"class":302},"_cache",[201,9698,454],{"class":243},[201,9700,9701],{"class":316},"set",[201,9703,782],{"class":233},[201,9705,9706,9709,9712,9715,9718,9721,9724,9727,9730,9733,9735,9737,9739,9742,9744,9747,9749,9751,9754,9756,9759,9761,9763,9766,9768,9770,9772],{"class":203,"data-line":325,"line":325},[201,9707,213],{"class":9708},[212],[201,9710,213],{"class":9711},[212],[201,9713,213],{"class":9714},[212],[201,9716,213],{"class":9717},[212],[201,9719,213],{"class":9720},[212],[201,9722,213],{"class":9723},[212],[201,9725,213],{"class":9726},[212],[201,9728,213],{"class":9729},[212],[201,9731,9732],{"class":316},"_chunk_key",[201,9734,234],{"class":460},[201,9736,9591],{"class":9654},[201,9738,454],{"class":243},[201,9740,9741],{"class":302},"_chat_session_id",[201,9743,264],{"class":243},[201,9745,213],{"class":9746},[212],[201,9748,9591],{"class":9654},[201,9750,454],{"class":243},[201,9752,9753],{"class":302},"_run_id",[201,9755,264],{"class":243},[201,9757,213],{"class":9758},[212],[201,9760,9591],{"class":9654},[201,9762,454],{"class":243},[201,9764,9765],{"class":302},"_meta",[201,9767,454],{"class":243},[201,9769,9570],{"class":302},[201,9771,237],{"class":460},[201,9773,1497],{"class":243},[201,9775,9776,9779,9782,9785,9788,9791,9794,9797,9800,9802],{"class":203,"data-line":345,"line":345},[201,9777,213],{"class":9778},[212],[201,9780,213],{"class":9781},[212],[201,9783,213],{"class":9784},[212],[201,9786,213],{"class":9787},[212],[201,9789,213],{"class":9790},[212],[201,9792,213],{"class":9793},[212],[201,9795,213],{"class":9796},[212],[201,9798,213],{"class":9799},[212],[201,9801,9624],{"class":302},[201,9803,1497],{"class":243},[201,9805,9806,9809,9812,9815,9818,9821,9824,9827,9830,9833,9835,9838],{"class":203,"data-line":403,"line":403},[201,9807,213],{"class":9808},[212],[201,9810,213],{"class":9811},[212],[201,9813,213],{"class":9814},[212],[201,9816,213],{"class":9817},[212],[201,9819,213],{"class":9820},[212],[201,9822,213],{"class":9823},[212],[201,9825,213],{"class":9826},[212],[201,9828,213],{"class":9829},[212],[201,9831,9832],{"class":716},"ex",[201,9834,310],{"class":309},[201,9836,9837],{"class":395},"CHAT_STREAM_BUFFER_TTL_S",[201,9839,1497],{"class":243},[201,9841,9842,9845,9848,9851,9854],{"class":203,"data-line":502,"line":502},[201,9843,213],{"class":9844},[212],[201,9846,213],{"class":9847},[212],[201,9849,213],{"class":9850},[212],[201,9852,213],{"class":9853},[212],[201,9855,322],{"class":233},[15,9857,9858],{},"关键属性：",[40,9860,9861,9873],{},[43,9862,9863,9868,9869,9872],{},[18,9864,9865],{},[47,9866,9867],{"code":9867},"truncated","：缓冲超上限（",[47,9870,9871],{"code":9871},"CHAT_STREAM_BUFFER_MAX_BYTES","）就标记截断，降级为\"不可恢复\"；",[43,9874,9875,9879,9880,9883],{},[18,9876,9877],{},[47,9878,3780],{"code":3780},"：流正常结束后 ",[47,9881,9882],{"code":9882},"mark_done()","（stream_buffer.py:131），延长数据保留期，\n让刷新页面后的客户端能回放完整内容。",[192,9885,9887],{"className":194,"code":9886,"language":196,"meta":197,"style":197},"def resume_chat_stream(\n    session_id: UUID,\n    cursor: int = Query(0, ge=0),     # 客户端上次读到哪\n    user: User = Depends(...),\n) -> StreamingResponse:\n    ...\n    run_id = get_processing_run_id(session_id, cache)\n    if run_id is None or not has_stream_buffer(cache, session_id, run_id):\n        raise OnyxError(NOT_FOUND, \"No resumable run for this chat session\")\n\n    def stream_buffered_run() -> Generator[str, None, None]:\n        chunk_cursor = cursor\n        while True:\n            read = read_stream_chunks(cache, session_id, run_id, chunk_cursor, ...)\n            if read is None or read.gap: return      # 缓冲丢了 → 终止，客户端重新拉全量\n            if read.blocks:\n                yield \"\".join(read.blocks)           # 回放缓冲内容\n                chunk_cursor = read.next_cursor\n                continue\n            if read.done: return                     # 正常结束\n            if not is_chat_session_processing(session_id, cache):\n                # writer 死了但没标 done → 再补一次 drain\n                ...\n            # 缓冲读完了 → 实时 tail 新的包\n            time.sleep(CHAT_RESUME_POLL_INTERVAL_S)\n            if now - last_emit >= CHAT_HEARTBEAT_INTERVAL_S:\n                yield get_json_line(heartbeat_packet().model_dump())  # 轮询中也发心跳\n    ...\n",[47,9888,9889,9901,9928,10007,10050,10068,10084,10126,10196,10279,10283,10343,10383,10418,10503,10617,10669,10781,10848,10900,11027,11093,11179,11231,11300,11352,11422,11511],{"__ignoreMap":197},[201,9890,9891,9893,9896,9899],{"class":203,"data-line":204,"line":204},[201,9892,223],{"class":222},[201,9894,213],{"class":9895},[212],[201,9897,9898],{"class":229},"resume_chat_stream",[201,9900,782],{"class":233},[201,9902,9903,9906,9909,9912,9915,9918,9920,9923,9926],{"class":203,"data-line":219,"line":219},[201,9904,213],{"class":9905},[212],[201,9907,213],{"class":9908},[212],[201,9910,213],{"class":9911},[212],[201,9913,213],{"class":9914},[212],[201,9916,9917],{"class":799},"session_id",[201,9919,590],{"class":243},[201,9921,213],{"class":9922},[212],[201,9924,974],{"class":9925},"sFbyl",[201,9927,1497],{"class":243},[201,9929,9930,9933,9936,9939,9942,9945,9947,9950,9953,9956,9958,9961,9964,9966,9968,9970,9973,9976,9978,9980,9982,9984,9987,9990,9993,9996,9999,10001,10004],{"class":203,"data-line":287,"line":287},[201,9931,213],{"class":9932},[212],[201,9934,213],{"class":9935},[212],[201,9937,213],{"class":9938},[212],[201,9940,213],{"class":9941},[212],[201,9943,9944],{"class":799},"cursor",[201,9946,590],{"class":243},[201,9948,213],{"class":9949},[212],[201,9951,9952],{"class":260},"int",[201,9954,213],{"class":9955},[212],[201,9957,310],{"class":309},[201,9959,213],{"class":9960},[212],[201,9962,9963],{"class":316},"Query",[201,9965,234],{"class":460},[201,9967,6199],{"class":1787},[201,9969,264],{"class":243},[201,9971,213],{"class":9972},[212],[201,9974,9975],{"class":716},"ge",[201,9977,310],{"class":309},[201,9979,6199],{"class":1787},[201,9981,237],{"class":460},[201,9983,264],{"class":243},[201,9985,213],{"class":9986},[212],[201,9988,213],{"class":9989},[212],[201,9991,213],{"class":9992},[212],[201,9994,213],{"class":9995},[212],[201,9997,213],{"class":9998},[212],[201,10000,208],{"class":207},[201,10002,213],{"class":10003},[212],[201,10005,10006],{"class":207},"客户端上次读到哪\n",[201,10008,10009,10012,10015,10018,10021,10023,10025,10028,10031,10034,10036,10039,10042,10044,10046,10048],{"class":203,"data-line":325,"line":325},[201,10010,213],{"class":10011},[212],[201,10013,213],{"class":10014},[212],[201,10016,213],{"class":10017},[212],[201,10019,213],{"class":10020},[212],[201,10022,7219],{"class":799},[201,10024,590],{"class":243},[201,10026,213],{"class":10027},[212],[201,10029,10030],{"class":860},"User",[201,10032,213],{"class":10033},[212],[201,10035,310],{"class":309},[201,10037,213],{"class":10038},[212],[201,10040,10041],{"class":316},"Depends",[201,10043,234],{"class":460},[201,10045,396],{"class":395},[201,10047,237],{"class":460},[201,10049,1497],{"class":243},[201,10051,10052,10054,10057,10059,10061,10064,10066],{"class":203,"data-line":345,"line":345},[201,10053,237],{"class":233},[201,10055,213],{"class":10056},[212],[201,10058,244],{"class":243},[201,10060,247],{"class":243},[201,10062,213],{"class":10063},[212],[201,10065,700],{"class":302},[201,10067,284],{"class":243},[201,10069,10070,10073,10076,10079,10082],{"class":203,"data-line":403,"line":403},[201,10071,213],{"class":10072},[212],[201,10074,213],{"class":10075},[212],[201,10077,213],{"class":10078},[212],[201,10080,213],{"class":10081},[212],[201,10083,1607],{"class":395},[201,10085,10086,10089,10092,10095,10098,10101,10104,10106,10109,10112,10114,10116,10118,10121,10124],{"class":203,"data-line":502,"line":502},[201,10087,213],{"class":10088},[212],[201,10090,213],{"class":10091},[212],[201,10093,213],{"class":10094},[212],[201,10096,213],{"class":10097},[212],[201,10099,10100],{"class":302},"run_id",[201,10102,213],{"class":10103},[212],[201,10105,310],{"class":309},[201,10107,213],{"class":10108},[212],[201,10110,10111],{"class":316},"get_processing_run_id",[201,10113,234],{"class":233},[201,10115,9917],{"class":302},[201,10117,264],{"class":243},[201,10119,213],{"class":10120},[212],[201,10122,10123],{"class":302},"cache",[201,10125,322],{"class":233},[201,10127,10128,10131,10134,10137,10140,10142,10145,10147,10150,10153,10156,10158,10161,10163,10166,10168,10171,10174,10176,10178,10180,10183,10185,10187,10190,10192,10194],{"class":203,"data-line":541,"line":541},[201,10129,213],{"class":10130},[212],[201,10132,213],{"class":10133},[212],[201,10135,213],{"class":10136},[212],[201,10138,213],{"class":10139},[212],[201,10141,1671],{"class":222},[201,10143,213],{"class":10144},[212],[201,10146,10100],{"class":302},[201,10148,213],{"class":10149},[212],[201,10151,10152],{"class":222},"is",[201,10154,213],{"class":10155},[212],[201,10157,271],{"class":1787},[201,10159,213],{"class":10160},[212],[201,10162,3742],{"class":222},[201,10164,213],{"class":10165},[212],[201,10167,1677],{"class":222},[201,10169,213],{"class":10170},[212],[201,10172,10173],{"class":316},"has_stream_buffer",[201,10175,234],{"class":233},[201,10177,10123],{"class":302},[201,10179,264],{"class":243},[201,10181,213],{"class":10182},[212],[201,10184,9917],{"class":302},[201,10186,264],{"class":243},[201,10188,213],{"class":10189},[212],[201,10191,10100],{"class":302},[201,10193,237],{"class":233},[201,10195,284],{"class":243},[201,10197,10198,10201,10204,10207,10210,10213,10216,10219,10222,10225,10228,10231,10233,10236,10238,10241,10244,10247,10250,10253,10255,10258,10260,10263,10265,10268,10271,10274,10277],{"class":203,"data-line":610,"line":610},[201,10199,213],{"class":10200},[212],[201,10202,213],{"class":10203},[212],[201,10205,213],{"class":10206},[212],[201,10208,213],{"class":10209},[212],[201,10211,213],{"class":10212},[212],[201,10214,213],{"class":10215},[212],[201,10217,213],{"class":10218},[212],[201,10220,213],{"class":10221},[212],[201,10223,10224],{"class":222},"raise",[201,10226,213],{"class":10227},[212],[201,10229,10230],{"class":316},"OnyxError",[201,10232,234],{"class":233},[201,10234,10235],{"class":395},"NOT_FOUND",[201,10237,264],{"class":243},[201,10239,213],{"class":10240},[212],[201,10242,10243],{"class":586},"\"No",[201,10245,213],{"class":10246},[212],[201,10248,10249],{"class":586},"resumable",[201,10251,213],{"class":10252},[212],[201,10254,9351],{"class":586},[201,10256,213],{"class":10257},[212],[201,10259,372],{"class":586},[201,10261,213],{"class":10262},[212],[201,10264,6065],{"class":586},[201,10266,213],{"class":10267},[212],[201,10269,10270],{"class":586},"chat",[201,10272,213],{"class":10273},[212],[201,10275,10276],{"class":586},"session\"",[201,10278,322],{"class":233},[201,10280,10281],{"class":203,"data-line":630,"line":630},[201,10282,688],{"emptyLinePlaceholder":687},[201,10284,10285,10288,10291,10294,10297,10299,10302,10305,10307,10309,10312,10314,10316,10319,10321,10323,10325,10327,10330,10332,10334,10337,10339,10341],{"class":203,"data-line":684,"line":684},[201,10286,213],{"class":10287},[212],[201,10289,213],{"class":10290},[212],[201,10292,213],{"class":10293},[212],[201,10295,213],{"class":10296},[212],[201,10298,223],{"class":222},[201,10300,213],{"class":10301},[212],[201,10303,10304],{"class":229},"stream_buffered_run",[201,10306,234],{"class":233},[201,10308,237],{"class":233},[201,10310,213],{"class":10311},[212],[201,10313,244],{"class":243},[201,10315,247],{"class":243},[201,10317,213],{"class":10318},[212],[201,10320,254],{"class":253},[201,10322,257],{"class":233},[201,10324,261],{"class":260},[201,10326,264],{"class":243},[201,10328,213],{"class":10329},[212],[201,10331,271],{"class":270},[201,10333,264],{"class":243},[201,10335,213],{"class":10336},[212],[201,10338,271],{"class":270},[201,10340,281],{"class":233},[201,10342,284],{"class":243},[201,10344,10345,10348,10351,10354,10357,10360,10363,10366,10369,10372,10375,10377,10380],{"class":203,"data-line":691,"line":691},[201,10346,213],{"class":10347},[212],[201,10349,213],{"class":10350},[212],[201,10352,213],{"class":10353},[212],[201,10355,213],{"class":10356},[212],[201,10358,213],{"class":10359},[212],[201,10361,213],{"class":10362},[212],[201,10364,213],{"class":10365},[212],[201,10367,213],{"class":10368},[212],[201,10370,10371],{"class":302},"chunk_cursor",[201,10373,213],{"class":10374},[212],[201,10376,310],{"class":309},[201,10378,213],{"class":10379},[212],[201,10381,10382],{"class":302},"cursor\n",[201,10384,10385,10388,10391,10394,10397,10400,10403,10406,10409,10411,10414,10416],{"class":203,"data-line":1750,"line":1750},[201,10386,213],{"class":10387},[212],[201,10389,213],{"class":10390},[212],[201,10392,213],{"class":10393},[212],[201,10395,213],{"class":10396},[212],[201,10398,213],{"class":10399},[212],[201,10401,213],{"class":10402},[212],[201,10404,213],{"class":10405},[212],[201,10407,213],{"class":10408},[212],[201,10410,3619],{"class":222},[201,10412,213],{"class":10413},[212],[201,10415,8837],{"class":1787},[201,10417,284],{"class":243},[201,10419,10420,10423,10426,10429,10432,10435,10438,10441,10444,10447,10450,10453,10456,10458,10461,10463,10466,10469,10471,10473,10475,10478,10480,10482,10485,10487,10489,10492,10494,10496,10499,10501],{"class":203,"data-line":1791,"line":1791},[201,10421,213],{"class":10422},[212],[201,10424,213],{"class":10425},[212],[201,10427,213],{"class":10428},[212],[201,10430,213],{"class":10431},[212],[201,10433,213],{"class":10434},[212],[201,10436,213],{"class":10437},[212],[201,10439,213],{"class":10440},[212],[201,10442,213],{"class":10443},[212],[201,10445,213],{"class":10446},[212],[201,10448,213],{"class":10449},[212],[201,10451,213],{"class":10452},[212],[201,10454,213],{"class":10455},[212],[201,10457,3680],{"class":302},[201,10459,213],{"class":10460},[212],[201,10462,310],{"class":309},[201,10464,213],{"class":10465},[212],[201,10467,10468],{"class":316},"read_stream_chunks",[201,10470,234],{"class":233},[201,10472,10123],{"class":302},[201,10474,264],{"class":243},[201,10476,213],{"class":10477},[212],[201,10479,9917],{"class":302},[201,10481,264],{"class":243},[201,10483,213],{"class":10484},[212],[201,10486,10100],{"class":302},[201,10488,264],{"class":243},[201,10490,213],{"class":10491},[212],[201,10493,10371],{"class":302},[201,10495,264],{"class":243},[201,10497,213],{"class":10498},[212],[201,10500,396],{"class":395},[201,10502,322],{"class":233},[201,10504,10505,10508,10511,10514,10517,10520,10523,10526,10529,10532,10535,10538,10541,10543,10546,10548,10551,10553,10556,10558,10561,10563,10566,10568,10570,10573,10575,10578,10580,10583,10586,10589,10592,10595,10598,10600,10603,10606,10609,10611,10614],{"class":203,"data-line":1796,"line":1796},[201,10506,213],{"class":10507},[212],[201,10509,213],{"class":10510},[212],[201,10512,213],{"class":10513},[212],[201,10515,213],{"class":10516},[212],[201,10518,213],{"class":10519},[212],[201,10521,213],{"class":10522},[212],[201,10524,213],{"class":10525},[212],[201,10527,213],{"class":10528},[212],[201,10530,213],{"class":10531},[212],[201,10533,213],{"class":10534},[212],[201,10536,213],{"class":10537},[212],[201,10539,213],{"class":10540},[212],[201,10542,1671],{"class":222},[201,10544,213],{"class":10545},[212],[201,10547,3680],{"class":302},[201,10549,213],{"class":10550},[212],[201,10552,10152],{"class":222},[201,10554,213],{"class":10555},[212],[201,10557,271],{"class":1787},[201,10559,213],{"class":10560},[212],[201,10562,3742],{"class":222},[201,10564,213],{"class":10565},[212],[201,10567,3680],{"class":302},[201,10569,454],{"class":243},[201,10571,10572],{"class":302},"gap",[201,10574,590],{"class":243},[201,10576,213],{"class":10577},[212],[201,10579,694],{"class":222},[201,10581,213],{"class":10582},[212],[201,10584,213],{"class":10585},[212],[201,10587,213],{"class":10588},[212],[201,10590,213],{"class":10591},[212],[201,10593,213],{"class":10594},[212],[201,10596,213],{"class":10597},[212],[201,10599,208],{"class":207},[201,10601,213],{"class":10602},[212],[201,10604,10605],{"class":207},"缓冲丢了",[201,10607,213],{"class":10608},[212],[201,10610,487],{"class":207},[201,10612,213],{"class":10613},[212],[201,10615,10616],{"class":207},"终止，客户端重新拉全量\n",[201,10618,10619,10622,10625,10628,10631,10634,10637,10640,10643,10646,10649,10652,10655,10657,10660,10662,10664,10667],{"class":203,"data-line":1894,"line":1894},[201,10620,213],{"class":10621},[212],[201,10623,213],{"class":10624},[212],[201,10626,213],{"class":10627},[212],[201,10629,213],{"class":10630},[212],[201,10632,213],{"class":10633},[212],[201,10635,213],{"class":10636},[212],[201,10638,213],{"class":10639},[212],[201,10641,213],{"class":10642},[212],[201,10644,213],{"class":10645},[212],[201,10647,213],{"class":10648},[212],[201,10650,213],{"class":10651},[212],[201,10653,213],{"class":10654},[212],[201,10656,1671],{"class":222},[201,10658,213],{"class":10659},[212],[201,10661,3680],{"class":302},[201,10663,454],{"class":243},[201,10665,10666],{"class":302},"blocks",[201,10668,284],{"class":243},[201,10670,10671,10674,10677,10680,10683,10686,10689,10692,10695,10698,10701,10704,10707,10710,10713,10716,10719,10721,10724,10726,10728,10730,10732,10734,10736,10738,10740,10743,10746,10749,10752,10755,10758,10761,10764,10767,10770,10773,10775,10778],{"class":203,"data-line":1936,"line":1936},[201,10672,213],{"class":10673},[212],[201,10675,213],{"class":10676},[212],[201,10678,213],{"class":10679},[212],[201,10681,213],{"class":10682},[212],[201,10684,213],{"class":10685},[212],[201,10687,213],{"class":10688},[212],[201,10690,213],{"class":10691},[212],[201,10693,213],{"class":10694},[212],[201,10696,213],{"class":10697},[212],[201,10699,213],{"class":10700},[212],[201,10702,213],{"class":10703},[212],[201,10705,213],{"class":10706},[212],[201,10708,213],{"class":10709},[212],[201,10711,213],{"class":10712},[212],[201,10714,213],{"class":10715},[212],[201,10717,213],{"class":10718},[212],[201,10720,66],{"class":222},[201,10722,213],{"class":10723},[212],[201,10725,3194],{"class":586},[201,10727,454],{"class":243},[201,10729,9649],{"class":316},[201,10731,234],{"class":233},[201,10733,3680],{"class":302},[201,10735,454],{"class":243},[201,10737,10666],{"class":302},[201,10739,237],{"class":233},[201,10741,213],{"class":10742},[212],[201,10744,213],{"class":10745},[212],[201,10747,213],{"class":10748},[212],[201,10750,213],{"class":10751},[212],[201,10753,213],{"class":10754},[212],[201,10756,213],{"class":10757},[212],[201,10759,213],{"class":10760},[212],[201,10762,213],{"class":10763},[212],[201,10765,213],{"class":10766},[212],[201,10768,213],{"class":10769},[212],[201,10771,213],{"class":10772},[212],[201,10774,208],{"class":207},[201,10776,213],{"class":10777},[212],[201,10779,10780],{"class":207},"回放缓冲内容\n",[201,10782,10783,10786,10789,10792,10795,10798,10801,10804,10807,10810,10813,10816,10819,10822,10825,10828,10831,10833,10836,10838,10841,10843,10845],{"class":203,"data-line":1960,"line":1960},[201,10784,213],{"class":10785},[212],[201,10787,213],{"class":10788},[212],[201,10790,213],{"class":10791},[212],[201,10793,213],{"class":10794},[212],[201,10796,213],{"class":10797},[212],[201,10799,213],{"class":10800},[212],[201,10802,213],{"class":10803},[212],[201,10805,213],{"class":10806},[212],[201,10808,213],{"class":10809},[212],[201,10811,213],{"class":10812},[212],[201,10814,213],{"class":10815},[212],[201,10817,213],{"class":10818},[212],[201,10820,213],{"class":10821},[212],[201,10823,213],{"class":10824},[212],[201,10826,213],{"class":10827},[212],[201,10829,213],{"class":10830},[212],[201,10832,10371],{"class":302},[201,10834,213],{"class":10835},[212],[201,10837,310],{"class":309},[201,10839,213],{"class":10840},[212],[201,10842,3680],{"class":302},[201,10844,454],{"class":243},[201,10846,10847],{"class":302},"next_cursor\n",[201,10849,10850,10853,10856,10859,10862,10865,10868,10871,10874,10877,10880,10883,10886,10889,10892,10895,10898],{"class":203,"data-line":2000,"line":2000},[201,10851,213],{"class":10852},[212],[201,10854,213],{"class":10855},[212],[201,10857,213],{"class":10858},[212],[201,10860,213],{"class":10861},[212],[201,10863,213],{"class":10864},[212],[201,10866,213],{"class":10867},[212],[201,10869,213],{"class":10870},[212],[201,10872,213],{"class":10873},[212],[201,10875,213],{"class":10876},[212],[201,10878,213],{"class":10879},[212],[201,10881,213],{"class":10882},[212],[201,10884,213],{"class":10885},[212],[201,10887,213],{"class":10888},[212],[201,10890,213],{"class":10891},[212],[201,10893,213],{"class":10894},[212],[201,10896,213],{"class":10897},[212],[201,10899,9245],{"class":222},[201,10901,10902,10905,10908,10911,10914,10917,10920,10923,10926,10929,10932,10935,10938,10940,10943,10945,10947,10949,10951,10954,10956,10959,10962,10965,10968,10971,10974,10977,10980,10983,10986,10989,10992,10995,10998,11001,11004,11007,11010,11013,11016,11019,11021,11024],{"class":203,"data-line":2070,"line":2070},[201,10903,213],{"class":10904},[212],[201,10906,213],{"class":10907},[212],[201,10909,213],{"class":10910},[212],[201,10912,213],{"class":10913},[212],[201,10915,213],{"class":10916},[212],[201,10918,213],{"class":10919},[212],[201,10921,213],{"class":10922},[212],[201,10924,213],{"class":10925},[212],[201,10927,213],{"class":10928},[212],[201,10930,213],{"class":10931},[212],[201,10933,213],{"class":10934},[212],[201,10936,213],{"class":10937},[212],[201,10939,1671],{"class":222},[201,10941,213],{"class":10942},[212],[201,10944,3680],{"class":302},[201,10946,454],{"class":243},[201,10948,3780],{"class":302},[201,10950,590],{"class":243},[201,10952,213],{"class":10953},[212],[201,10955,694],{"class":222},[201,10957,213],{"class":10958},[212],[201,10960,213],{"class":10961},[212],[201,10963,213],{"class":10964},[212],[201,10966,213],{"class":10967},[212],[201,10969,213],{"class":10970},[212],[201,10972,213],{"class":10973},[212],[201,10975,213],{"class":10976},[212],[201,10978,213],{"class":10979},[212],[201,10981,213],{"class":10982},[212],[201,10984,213],{"class":10985},[212],[201,10987,213],{"class":10988},[212],[201,10990,213],{"class":10991},[212],[201,10993,213],{"class":10994},[212],[201,10996,213],{"class":10997},[212],[201,10999,213],{"class":11000},[212],[201,11002,213],{"class":11003},[212],[201,11005,213],{"class":11006},[212],[201,11008,213],{"class":11009},[212],[201,11011,213],{"class":11012},[212],[201,11014,213],{"class":11015},[212],[201,11017,213],{"class":11018},[212],[201,11020,208],{"class":207},[201,11022,213],{"class":11023},[212],[201,11025,11026],{"class":207},"正常结束\n",[201,11028,11029,11032,11035,11038,11041,11044,11047,11050,11053,11056,11059,11062,11065,11067,11070,11072,11075,11078,11080,11082,11084,11087,11089,11091],{"class":203,"data-line":3958,"line":3958},[201,11030,213],{"class":11031},[212],[201,11033,213],{"class":11034},[212],[201,11036,213],{"class":11037},[212],[201,11039,213],{"class":11040},[212],[201,11042,213],{"class":11043},[212],[201,11045,213],{"class":11046},[212],[201,11048,213],{"class":11049},[212],[201,11051,213],{"class":11052},[212],[201,11054,213],{"class":11055},[212],[201,11057,213],{"class":11058},[212],[201,11060,213],{"class":11061},[212],[201,11063,213],{"class":11064},[212],[201,11066,1671],{"class":222},[201,11068,213],{"class":11069},[212],[201,11071,1677],{"class":222},[201,11073,213],{"class":11074},[212],[201,11076,11077],{"class":316},"is_chat_session_processing",[201,11079,234],{"class":233},[201,11081,9917],{"class":302},[201,11083,264],{"class":243},[201,11085,213],{"class":11086},[212],[201,11088,10123],{"class":302},[201,11090,237],{"class":233},[201,11092,284],{"class":243},[201,11094,11095,11098,11101,11104,11107,11110,11113,11116,11119,11122,11125,11128,11131,11134,11137,11140,11143,11145,11148,11151,11154,11157,11160,11162,11165,11167,11170,11173,11176],{"class":203,"data-line":4049,"line":4049},[201,11096,213],{"class":11097},[212],[201,11099,213],{"class":11100},[212],[201,11102,213],{"class":11103},[212],[201,11105,213],{"class":11106},[212],[201,11108,213],{"class":11109},[212],[201,11111,213],{"class":11112},[212],[201,11114,213],{"class":11115},[212],[201,11117,213],{"class":11118},[212],[201,11120,213],{"class":11121},[212],[201,11123,213],{"class":11124},[212],[201,11126,213],{"class":11127},[212],[201,11129,213],{"class":11130},[212],[201,11132,213],{"class":11133},[212],[201,11135,213],{"class":11136},[212],[201,11138,213],{"class":11139},[212],[201,11141,213],{"class":11142},[212],[201,11144,208],{"class":207},[201,11146,213],{"class":11147},[212],[201,11149,11150],{"class":207},"writer",[201,11152,213],{"class":11153},[212],[201,11155,11156],{"class":207},"死了但没标",[201,11158,213],{"class":11159},[212],[201,11161,3780],{"class":207},[201,11163,213],{"class":11164},[212],[201,11166,487],{"class":207},[201,11168,213],{"class":11169},[212],[201,11171,11172],{"class":207},"再补一次",[201,11174,213],{"class":11175},[212],[201,11177,11178],{"class":207},"drain\n",[201,11180,11181,11184,11187,11190,11193,11196,11199,11202,11205,11208,11211,11214,11217,11220,11223,11226,11229],{"class":203,"data-line":4054,"line":4054},[201,11182,213],{"class":11183},[212],[201,11185,213],{"class":11186},[212],[201,11188,213],{"class":11189},[212],[201,11191,213],{"class":11192},[212],[201,11194,213],{"class":11195},[212],[201,11197,213],{"class":11198},[212],[201,11200,213],{"class":11201},[212],[201,11203,213],{"class":11204},[212],[201,11206,213],{"class":11207},[212],[201,11209,213],{"class":11210},[212],[201,11212,213],{"class":11213},[212],[201,11215,213],{"class":11216},[212],[201,11218,213],{"class":11219},[212],[201,11221,213],{"class":11222},[212],[201,11224,213],{"class":11225},[212],[201,11227,213],{"class":11228},[212],[201,11230,1607],{"class":395},[201,11232,11233,11236,11239,11242,11245,11248,11251,11254,11257,11260,11263,11266,11269,11271,11274,11277,11280,11282,11285,11288,11291,11294,11297],{"class":203,"data-line":4175,"line":4175},[201,11234,213],{"class":11235},[212],[201,11237,213],{"class":11238},[212],[201,11240,213],{"class":11241},[212],[201,11243,213],{"class":11244},[212],[201,11246,213],{"class":11247},[212],[201,11249,213],{"class":11250},[212],[201,11252,213],{"class":11253},[212],[201,11255,213],{"class":11256},[212],[201,11258,213],{"class":11259},[212],[201,11261,213],{"class":11262},[212],[201,11264,213],{"class":11265},[212],[201,11267,213],{"class":11268},[212],[201,11270,208],{"class":207},[201,11272,213],{"class":11273},[212],[201,11275,11276],{"class":207},"缓冲读完了",[201,11278,213],{"class":11279},[212],[201,11281,487],{"class":207},[201,11283,213],{"class":11284},[212],[201,11286,11287],{"class":207},"实时",[201,11289,213],{"class":11290},[212],[201,11292,11293],{"class":207},"tail",[201,11295,213],{"class":11296},[212],[201,11298,11299],{"class":207},"新的包\n",[201,11301,11302,11305,11308,11311,11314,11317,11320,11323,11326,11329,11332,11335,11338,11340,11342,11345,11347,11350],{"class":203,"data-line":4318,"line":4318},[201,11303,213],{"class":11304},[212],[201,11306,213],{"class":11307},[212],[201,11309,213],{"class":11310},[212],[201,11312,213],{"class":11313},[212],[201,11315,213],{"class":11316},[212],[201,11318,213],{"class":11319},[212],[201,11321,213],{"class":11322},[212],[201,11324,213],{"class":11325},[212],[201,11327,213],{"class":11328},[212],[201,11330,213],{"class":11331},[212],[201,11333,213],{"class":11334},[212],[201,11336,213],{"class":11337},[212],[201,11339,9010],{"class":302},[201,11341,454],{"class":243},[201,11343,11344],{"class":316},"sleep",[201,11346,234],{"class":233},[201,11348,11349],{"class":395},"CHAT_RESUME_POLL_INTERVAL_S",[201,11351,322],{"class":233},[201,11353,11354,11357,11360,11363,11366,11369,11372,11375,11378,11381,11384,11387,11390,11392,11395,11397,11400,11402,11405,11408,11411,11413,11415,11418,11420],{"class":203,"data-line":4323,"line":4323},[201,11355,213],{"class":11356},[212],[201,11358,213],{"class":11359},[212],[201,11361,213],{"class":11362},[212],[201,11364,213],{"class":11365},[212],[201,11367,213],{"class":11368},[212],[201,11370,213],{"class":11371},[212],[201,11373,213],{"class":11374},[212],[201,11376,213],{"class":11377},[212],[201,11379,213],{"class":11380},[212],[201,11382,213],{"class":11383},[212],[201,11385,213],{"class":11386},[212],[201,11388,213],{"class":11389},[212],[201,11391,1671],{"class":222},[201,11393,213],{"class":11394},[212],[201,11396,8999],{"class":302},[201,11398,213],{"class":11399},[212],[201,11401,244],{"class":309},[201,11403,213],{"class":11404},[212],[201,11406,11407],{"class":302},"last_emit",[201,11409,213],{"class":11410},[212],[201,11412,247],{"class":309},[201,11414,310],{"class":309},[201,11416,213],{"class":11417},[212],[201,11419,8821],{"class":395},[201,11421,284],{"class":243},[201,11423,11424,11427,11430,11433,11436,11439,11442,11445,11448,11451,11454,11457,11460,11463,11466,11469,11472,11474,11477,11479,11481,11483,11485,11487,11489,11491,11493,11495,11497,11500,11503,11505,11508],{"class":203,"data-line":4373,"line":4373},[201,11425,213],{"class":11426},[212],[201,11428,213],{"class":11429},[212],[201,11431,213],{"class":11432},[212],[201,11434,213],{"class":11435},[212],[201,11437,213],{"class":11438},[212],[201,11440,213],{"class":11441},[212],[201,11443,213],{"class":11444},[212],[201,11446,213],{"class":11447},[212],[201,11449,213],{"class":11450},[212],[201,11452,213],{"class":11453},[212],[201,11455,213],{"class":11456},[212],[201,11458,213],{"class":11459},[212],[201,11461,213],{"class":11462},[212],[201,11464,213],{"class":11465},[212],[201,11467,213],{"class":11468},[212],[201,11470,213],{"class":11471},[212],[201,11473,66],{"class":222},[201,11475,213],{"class":11476},[212],[201,11478,447],{"class":316},[201,11480,234],{"class":233},[201,11482,9123],{"class":316},[201,11484,234],{"class":460},[201,11486,237],{"class":460},[201,11488,454],{"class":243},[201,11490,457],{"class":316},[201,11492,234],{"class":460},[201,11494,237],{"class":460},[201,11496,237],{"class":233},[201,11498,213],{"class":11499},[212],[201,11501,213],{"class":11502},[212],[201,11504,208],{"class":207},[201,11506,213],{"class":11507},[212],[201,11509,11510],{"class":207},"轮询中也发心跳\n",[201,11512,11513,11516,11519,11522,11525],{"class":203,"data-line":4435,"line":4435},[201,11514,213],{"class":11515},[212],[201,11517,213],{"class":11518},[212],[201,11520,213],{"class":11521},[212],[201,11523,213],{"class":11524},[212],[201,11526,1607],{"class":395},[15,11528,11529],{},"设计要点：",[40,11531,11532,11538,11541],{},[43,11533,11534,11537],{},[18,11535,11536],{},"缓冲在共享缓存里，任何 pod 都能服务","（注释：\"Serves any pod\"）；",[43,11539,11540],{},"404（无可恢复 run）是正常业务信号，前端回退到重新拉取落库 session；",[43,11542,11543,11544,11547],{},"回放结束进入",[18,11545,11546],{},"实时 tail","：轮询新 chunk + 心跳保活。",[763,11549,2283],{"id":11550},"前端-1",[192,11552,11554],{"className":2296,"code":11553,"language":2298,"meta":197,"style":197},"export async function* resumeStream(chatSessionId, cursor, signal?) {\n  const response = await fetch(\n    `/api/chat/chat-session/${chatSessionId}/resume-stream?cursor=${cursor}`,\n    { signal }\n  );\n  ...\n  yield* handleSSEStream\u003CPacketType>(response, signal);\n}\n",[47,11555,11556,11603,11635,11674,11699,11711,11721,11757],{"__ignoreMap":197},[201,11557,11558,11560,11563,11565,11568,11570,11573,11576,11578,11581,11583,11586,11588,11590,11593,11595,11597,11599,11601],{"class":203,"data-line":204,"line":204},[201,11559,2856],{"class":222},[201,11561,213],{"class":11562},[212],[201,11564,2666],{"class":222},[201,11566,213],{"class":11567},[212],[201,11569,2672],{"class":222},[201,11571,213],{"class":11572},[212],[201,11574,11575],{"class":229},"resumeStream",[201,11577,234],{"class":233},[201,11579,11580],{"class":716},"chatSessionId",[201,11582,264],{"class":243},[201,11584,213],{"class":11585},[212],[201,11587,9944],{"class":716},[201,11589,264],{"class":243},[201,11591,213],{"class":11592},[212],[201,11594,2425],{"class":716},[201,11596,2925],{"class":2924},[201,11598,237],{"class":233},[201,11600,213],{"class":243},[201,11602,2341],{"class":233},[201,11604,11605,11608,11611,11613,11616,11618,11621,11623,11626,11628,11631,11633],{"class":203,"data-line":219,"line":219},[201,11606,213],{"class":11607},[212],[201,11609,213],{"class":11610},[212],[201,11612,2305],{"class":222},[201,11614,213],{"class":11615},[212],[201,11617,2312],{"class":2311},[201,11619,213],{"class":11620},[212],[201,11622,310],{"class":309},[201,11624,213],{"class":11625},[212],[201,11627,2323],{"class":222},[201,11629,213],{"class":11630},[212],[201,11632,2329],{"class":229},[201,11634,782],{"class":460},[201,11636,11637,11640,11643,11646,11649,11652,11656,11658,11660,11663,11665,11667,11669,11672],{"class":203,"data-line":287,"line":287},[201,11638,213],{"class":11639},[212],[201,11641,213],{"class":11642},[212],[201,11644,213],{"class":11645},[212],[201,11647,213],{"class":11648},[212],[201,11650,11651],{"class":586},"`/api/chat/chat-session/",[201,11653,11655],{"class":11654},"sHUla","${",[201,11657,11580],{"class":2352},[201,11659,605],{"class":11654},[201,11661,11662],{"class":586},"/resume-stream?cursor=",[201,11664,11655],{"class":11654},[201,11666,9944],{"class":2352},[201,11668,605],{"class":11654},[201,11670,11671],{"class":586},"`",[201,11673,1497],{"class":243},[201,11675,11676,11679,11682,11685,11688,11690,11693,11695,11697],{"class":203,"data-line":325,"line":325},[201,11677,213],{"class":11678},[212],[201,11680,213],{"class":11681},[212],[201,11683,213],{"class":11684},[212],[201,11686,213],{"class":11687},[212],[201,11689,583],{"class":598},[201,11691,213],{"class":11692},[212],[201,11694,2425],{"class":2352},[201,11696,213],{"class":243},[201,11698,2571],{"class":598},[201,11700,11701,11704,11707,11709],{"class":203,"data-line":345,"line":345},[201,11702,213],{"class":11703},[212],[201,11705,213],{"class":11706},[212],[201,11708,237],{"class":460},[201,11710,2520],{"class":243},[201,11712,11713,11716,11719],{"class":203,"data-line":403,"line":403},[201,11714,213],{"class":11715},[212],[201,11717,213],{"class":11718},[212],[201,11720,1607],{"class":2356},[201,11722,11723,11726,11729,11731,11734,11736,11738,11740,11742,11744,11746,11748,11751,11753,11755],{"class":203,"data-line":502,"line":502},[201,11724,213],{"class":11725},[212],[201,11727,213],{"class":11728},[212],[201,11730,2580],{"class":222},[201,11732,213],{"class":11733},[212],[201,11735,2591],{"class":229},[201,11737,2594],{"class":460},[201,11739,1361],{"class":1006},[201,11741,247],{"class":460},[201,11743,234],{"class":460},[201,11745,2312],{"class":2352},[201,11747,264],{"class":243},[201,11749,213],{"class":11750},[212],[201,11752,2425],{"class":2352},[201,11754,237],{"class":460},[201,11756,2520],{"class":243},[201,11758,11759],{"class":203,"data-line":541,"line":541},[201,11760,2571],{"class":233},[15,11762,11763,11764,11767],{},"页面刷新后，如果 session 有 ",[47,11765,11766],{"code":11766},"current_run"," 且是 assistant 节点，就\"粘\"上去续读：",[192,11769,11771],{"className":2296,"code":11770,"language":2298,"meta":197,"style":197},"const abortController = new AbortController();\ntry {\n  for await (const rawPacket of resumeStream(sessionId, 0, abortController.signal)) {\n    if (!stillCurrent()) return;              // 用户已切走会话 → 停\n    if (packet.obj.type === \"chat_heartbeat\") continue;\n    accumulated.push(packet);\n    // 每 100ms 或 trailing 120ms 刷一次到消息树\n  }\n} finally {\n  abortController.abort();\n  // 流结束后拉一次落库的完整 session 做\"结算\"\n  const settled = await (await fetch(`/api/chat/get-chat-session/${sessionId}`)).json();\n  updateSessionAndMessageTree(sessionId, processRawChatHistory(settled.messages, ...));\n}\n",[47,11772,11773,11804,11812,11880,11978,12026,12054,12106,12116,12129,12150,12178,12242,12287],{"__ignoreMap":197},[201,11774,11775,11777,11780,11783,11786,11788,11791,11793,11796,11798,11800,11802],{"class":203,"data-line":204,"line":204},[201,11776,2305],{"class":222},[201,11778,213],{"class":11779},[212],[201,11781,11782],{"class":2311},"abortController",[201,11784,213],{"class":11785},[212],[201,11787,310],{"class":309},[201,11789,213],{"class":11790},[212],[201,11792,3069],{"class":3068},[201,11794,213],{"class":11795},[212],[201,11797,5825],{"class":229},[201,11799,234],{"class":233},[201,11801,237],{"class":233},[201,11803,2520],{"class":243},[201,11805,11806,11808,11810],{"class":203,"data-line":219,"line":219},[201,11807,340],{"class":222},[201,11809,213],{"class":243},[201,11811,2341],{"class":233},[201,11813,11814,11817,11820,11822,11825,11827,11829,11831,11833,11836,11839,11842,11844,11847,11849,11851,11854,11856,11859,11861,11863,11866,11868,11870,11872,11874,11876,11878],{"class":203,"data-line":287,"line":287},[201,11815,213],{"class":11816},[212],[201,11818,213],{"class":11819},[212],[201,11821,372],{"class":222},[201,11823,213],{"class":11824},[212],[201,11826,2323],{"class":222},[201,11828,213],{"class":302},[201,11830,234],{"class":460},[201,11832,2305],{"class":222},[201,11834,213],{"class":11835},[212],[201,11837,11838],{"class":2311},"rawPacket",[201,11840,213],{"class":11841},[212],[201,11843,2719],{"class":222},[201,11845,213],{"class":11846},[212],[201,11848,11575],{"class":229},[201,11850,234],{"class":598},[201,11852,11853],{"class":2352},"sessionId",[201,11855,264],{"class":243},[201,11857,213],{"class":11858},[212],[201,11860,6199],{"class":1787},[201,11862,264],{"class":243},[201,11864,213],{"class":11865},[212],[201,11867,11782],{"class":2311},[201,11869,454],{"class":2356},[201,11871,2425],{"class":2352},[201,11873,237],{"class":598},[201,11875,237],{"class":460},[201,11877,213],{"class":302},[201,11879,2341],{"class":460},[201,11881,11882,11885,11888,11891,11894,11896,11898,11900,11902,11905,11907,11909,11911,11913,11915,11917,11920,11923,11926,11929,11932,11935,11938,11941,11944,11947,11950,11953,11956,11959,11961,11964,11967,11970,11972,11975],{"class":203,"data-line":325,"line":325},[201,11883,213],{"class":11884},[212],[201,11886,213],{"class":11887},[212],[201,11889,213],{"class":11890},[212],[201,11892,213],{"class":11893},[212],[201,11895,1671],{"class":222},[201,11897,213],{"class":302},[201,11899,234],{"class":598},[201,11901,2535],{"class":309},[201,11903,11904],{"class":229},"stillCurrent",[201,11906,234],{"class":3383},[201,11908,237],{"class":3383},[201,11910,237],{"class":598},[201,11912,213],{"class":302},[201,11914,694],{"class":222},[201,11916,3015],{"class":243},[201,11918,213],{"class":11919},[212],[201,11921,213],{"class":11922},[212],[201,11924,213],{"class":11925},[212],[201,11927,213],{"class":11928},[212],[201,11930,213],{"class":11931},[212],[201,11933,213],{"class":11934},[212],[201,11936,213],{"class":11937},[212],[201,11939,213],{"class":11940},[212],[201,11942,213],{"class":11943},[212],[201,11945,213],{"class":11946},[212],[201,11948,213],{"class":11949},[212],[201,11951,213],{"class":11952},[212],[201,11954,213],{"class":11955},[212],[201,11957,213],{"class":11958},[212],[201,11960,2499],{"class":207},[201,11962,213],{"class":11963},[212],[201,11965,11966],{"class":207},"用户已切走会话",[201,11968,213],{"class":11969},[212],[201,11971,487],{"class":207},[201,11973,213],{"class":11974},[212],[201,11976,11977],{"class":207},"停\n",[201,11979,11980,11983,11986,11989,11992,11994,11996,11998,12000,12002,12004,12006,12008,12011,12013,12016,12018,12020,12022,12024],{"class":203,"data-line":345,"line":345},[201,11981,213],{"class":11982},[212],[201,11984,213],{"class":11985},[212],[201,11987,213],{"class":11988},[212],[201,11990,213],{"class":11991},[212],[201,11993,1671],{"class":222},[201,11995,213],{"class":302},[201,11997,234],{"class":598},[201,11999,1455],{"class":2311},[201,12001,454],{"class":2356},[201,12003,378],{"class":2311},[201,12005,454],{"class":2356},[201,12007,838],{"class":2352},[201,12009,213],{"class":12010},[212],[201,12012,2788],{"class":309},[201,12014,213],{"class":12015},[212],[201,12017,2794],{"class":586},[201,12019,237],{"class":598},[201,12021,213],{"class":302},[201,12023,2801],{"class":222},[201,12025,2520],{"class":243},[201,12027,12028,12031,12034,12037,12040,12042,12044,12046,12048,12050,12052],{"class":203,"data-line":403,"line":403},[201,12029,213],{"class":12030},[212],[201,12032,213],{"class":12033},[212],[201,12035,213],{"class":12036},[212],[201,12038,213],{"class":12039},[212],[201,12041,8463],{"class":2311},[201,12043,454],{"class":2356},[201,12045,6047],{"class":229},[201,12047,234],{"class":598},[201,12049,1455],{"class":2352},[201,12051,237],{"class":598},[201,12053,2520],{"class":243},[201,12055,12056,12059,12062,12065,12068,12070,12073,12076,12079,12082,12085,12088,12091,12094,12097,12100,12103],{"class":203,"data-line":502,"line":502},[201,12057,213],{"class":12058},[212],[201,12060,213],{"class":12061},[212],[201,12063,213],{"class":12064},[212],[201,12066,213],{"class":12067},[212],[201,12069,2499],{"class":207},[201,12071,213],{"class":12072},[212],[201,12074,12075],{"class":207},"每",[201,12077,213],{"class":12078},[212],[201,12080,12081],{"class":207},"100ms",[201,12083,213],{"class":12084},[212],[201,12086,12087],{"class":207},"或",[201,12089,213],{"class":12090},[212],[201,12092,12093],{"class":207},"trailing",[201,12095,213],{"class":12096},[212],[201,12098,12099],{"class":207},"120ms",[201,12101,213],{"class":12102},[212],[201,12104,12105],{"class":207},"刷一次到消息树\n",[201,12107,12108,12111,12114],{"class":203,"data-line":541,"line":541},[201,12109,213],{"class":12110},[212],[201,12112,213],{"class":12113},[212],[201,12115,2571],{"class":460},[201,12117,12118,12120,12123,12125,12127],{"class":203,"data-line":610,"line":610},[201,12119,605],{"class":233},[201,12121,213],{"class":12122},[212],[201,12124,625],{"class":222},[201,12126,213],{"class":243},[201,12128,2341],{"class":233},[201,12130,12131,12134,12137,12139,12141,12144,12146,12148],{"class":203,"data-line":630,"line":630},[201,12132,213],{"class":12133},[212],[201,12135,213],{"class":12136},[212],[201,12138,11782],{"class":2311},[201,12140,454],{"class":2356},[201,12142,12143],{"class":229},"abort",[201,12145,234],{"class":460},[201,12147,237],{"class":460},[201,12149,2520],{"class":243},[201,12151,12152,12155,12158,12160,12163,12166,12169,12172,12175],{"class":203,"data-line":684,"line":684},[201,12153,213],{"class":12154},[212],[201,12156,213],{"class":12157},[212],[201,12159,2499],{"class":207},[201,12161,213],{"class":12162},[212],[201,12164,12165],{"class":207},"流结束后拉一次落库的完整",[201,12167,213],{"class":12168},[212],[201,12170,12171],{"class":207},"session",[201,12173,213],{"class":12174},[212],[201,12176,12177],{"class":207},"做\"结算\"\n",[201,12179,12180,12183,12186,12188,12191,12194,12197,12199,12202,12204,12206,12208,12210,12213,12215,12217,12220,12222,12224,12226,12228,12230,12232,12234,12236,12238,12240],{"class":203,"data-line":691,"line":691},[201,12181,213],{"class":12182},[212],[201,12184,213],{"class":12185},[212],[201,12187,2305],{"class":222},[201,12189,213],{"class":12190},[212],[201,12192,12193],{"class":2311},"settled",[201,12195,213],{"class":12196},[212],[201,12198,310],{"class":309},[201,12200,213],{"class":12201},[212],[201,12203,2323],{"class":222},[201,12205,213],{"class":302},[201,12207,234],{"class":460},[201,12209,2323],{"class":222},[201,12211,213],{"class":12212},[212],[201,12214,2329],{"class":229},[201,12216,234],{"class":598},[201,12218,12219],{"class":586},"`/api/chat/get-chat-session/",[201,12221,11655],{"class":11654},[201,12223,11853],{"class":2352},[201,12225,605],{"class":11654},[201,12227,11671],{"class":586},[201,12229,237],{"class":598},[201,12231,237],{"class":460},[201,12233,454],{"class":2356},[201,12235,573],{"class":229},[201,12237,234],{"class":460},[201,12239,237],{"class":460},[201,12241,2520],{"class":243},[201,12243,12244,12247,12250,12253,12255,12257,12259,12262,12265,12267,12269,12271,12274,12276,12279,12281,12283,12285],{"class":203,"data-line":1750,"line":1750},[201,12245,213],{"class":12246},[212],[201,12248,213],{"class":12249},[212],[201,12251,12252],{"class":229},"updateSessionAndMessageTree",[201,12254,234],{"class":460},[201,12256,11853],{"class":2352},[201,12258,264],{"class":243},[201,12260,213],{"class":12261},[212],[201,12263,12264],{"class":229},"processRawChatHistory",[201,12266,234],{"class":598},[201,12268,12193],{"class":2311},[201,12270,454],{"class":2356},[201,12272,12273],{"class":2352},"messages",[201,12275,264],{"class":243},[201,12277,213],{"class":12278},[212],[201,12280,396],{"class":2356},[201,12282,237],{"class":598},[201,12284,237],{"class":460},[201,12286,2520],{"class":243},[201,12288,12289],{"class":203,"data-line":1791,"line":1791},[201,12290,2571],{"class":233},[15,12292,12293,12296],{},[18,12294,12295],{},"\"流式 + 落库\"双保险","：在线期间用增量流；断线/刷新后用回放流；回放结束用落库数据最终对齐。",[12298,12299,12300],"style",{},"html pre.shiki code .skYY2, html code.shiki .skYY2{--shiki-default:#7C7F93;--shiki-default-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic}html pre.shiki code .sSWcl, html code.shiki .sSWcl{--shiki-default:#8839EF;--shiki-dark:#C678DD}html pre.shiki code .seVD2, html code.shiki .seVD2{--shiki-default:#1E66F5;--shiki-default-font-style:italic;--shiki-dark:#61AFEF;--shiki-dark-font-style:inherit}html pre.shiki code .scfJ_, html code.shiki .scfJ_{--shiki-default:#d20f39;--shiki-dark:#d19a66}html pre.shiki code .sgT6j, html code.shiki .sgT6j{--shiki-default:#7C7F93;--shiki-dark:#ABB2BF}html pre.shiki code .sQGkZ, html code.shiki .sQGkZ{--shiki-default:#E64553;--shiki-default-font-style:italic;--shiki-dark:#ABB2BF;--shiki-dark-font-style:inherit}html pre.shiki code .s0gAL, html code.shiki .s0gAL{--shiki-default:#8839EF;--shiki-default-font-style:italic;--shiki-dark:#56B6C2;--shiki-dark-font-style:inherit}html pre.shiki code .s1D4B, html code.shiki .s1D4B{--shiki-default:#FE640B;--shiki-default-font-style:italic;--shiki-dark:#D19A66;--shiki-dark-font-style:inherit}html pre.shiki code .sa2x1, html code.shiki .sa2x1{--shiki-default:#4C4F69;--shiki-dark:#ABB2BF}html pre.shiki code .sqgB4, html code.shiki .sqgB4{--shiki-default:#179299;--shiki-dark:#56B6C2}html pre.shiki code .s3w6o, html code.shiki .s3w6o{--shiki-default:#1E66F5;--shiki-dark:#61AFEF}html pre.shiki code .sJ4Hi, html code.shiki .sJ4Hi{--shiki-default:#4C4F69;--shiki-dark:#D19A66}html pre.shiki code .si0CV, html code.shiki .si0CV{--shiki-default:#fe640b;--shiki-dark:#c678dd}html pre.shiki code .siVku, html code.shiki .siVku{--shiki-default:#FE640B;--shiki-default-font-style:italic;--shiki-dark:#ABB2BF;--shiki-dark-font-style:inherit}html pre.shiki code .sw_MA, html code.shiki .sw_MA{--shiki-default:#40A02B;--shiki-dark:#98C379}html pre.shiki code .sSXcQ, html code.shiki .sSXcQ{--shiki-default:#df8e1d;--shiki-dark:#56b6c2}html pre.shiki code .sddMY, html code.shiki .sddMY{--shiki-default:#E64553;--shiki-default-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sZ7dW, html code.shiki .sZ7dW{--shiki-default:#E64553;--shiki-default-font-style:italic;--shiki-dark:#D19A66;--shiki-dark-font-style:italic}html pre.shiki code .slFfU, html code.shiki .slFfU{--shiki-default:#E64553;--shiki-dark:#ABB2BF}html pre.shiki code .sFWNk, html code.shiki .sFWNk{--shiki-default:#EA76CB;--shiki-dark:#56B6C2}html pre.shiki code .sIkkJ, html code.shiki .sIkkJ{--shiki-default:#DF8E1D;--shiki-default-font-style:italic;--shiki-dark:#E5C07B;--shiki-dark-font-style:inherit}html pre.shiki code .s2-b8, html code.shiki .s2-b8{--shiki-default:#40A02B;--shiki-default-font-style:italic;--shiki-dark:#98C379;--shiki-dark-font-style:inherit}html pre.shiki code .sYQis, html code.shiki .sYQis{--shiki-default:#FE640B;--shiki-dark:#D19A66}html pre.shiki code .sLEyy, html code.shiki .sLEyy{--shiki-default:#D20F39;--shiki-default-font-style:italic;--shiki-dark:#E5C07B;--shiki-dark-font-style:italic}html pre.shiki code .sQf5i, html code.shiki .sQf5i{--shiki-default:#D20F39;--shiki-default-font-style:italic;--shiki-dark:#E5C07B;--shiki-dark-font-style:inherit}html pre.shiki code .sFbyl, html code.shiki .sFbyl{--shiki-default:#E64553;--shiki-dark:#D19A66}html pre.shiki code .szf8G, html code.shiki .szf8G{--shiki-default:#4C4F69;--shiki-dark:#E5C07B}html pre.shiki code .sIGPt, html code.shiki .sIGPt{--shiki-default:#4C4F69;--shiki-dark:#E06C75}html pre.shiki code .sxizN, html code.shiki .sxizN{--shiki-default:#179299;--shiki-dark:#ABB2BF}html pre.shiki code .saddK, html code.shiki .saddK{--shiki-default:#179299;--shiki-dark:#C678DD}html pre.shiki code .sPahJ, html code.shiki .sPahJ{--shiki-default:#8839EF;--shiki-dark:#E5C07B}html pre.shiki code .snqon, html code.shiki .snqon{--shiki-default:#8839EF;--shiki-default-font-weight:bold;--shiki-dark:#C678DD;--shiki-dark-font-weight:inherit}html pre.shiki code .sqrby, html code.shiki .sqrby{--shiki-default:#40a02b;--shiki-dark:#d19a66}html pre.shiki code .sUpK1, html code.shiki .sUpK1{--shiki-default:#209fb5;--shiki-dark:#c678dd}html pre.shiki code .sdqnd, html code.shiki .sdqnd{--shiki-default:#8839ef;--shiki-dark:#56b6c2}html pre.shiki code .saiVi, html code.shiki .saiVi{--shiki-default:#FE640B;--shiki-dark:#E5C07B}html pre.shiki code .sCSXW, html code.shiki .sCSXW{--shiki-default:#EA76CB;--shiki-dark:#E06C75}html pre.shiki code .sK5Ct, html code.shiki .sK5Ct{--shiki-default:#DF8E1D;--shiki-dark:#D19A66}html pre.shiki code .sqqMN, html code.shiki .sqqMN{--shiki-default:#179299;--shiki-dark:#D19A66}html pre.shiki code .sYEiT, html code.shiki .sYEiT{--shiki-default:#fe640b;--shiki-default-font-style:italic;--shiki-dark:#c678dd;--shiki-dark-font-style:inherit}html pre.shiki code .sNyJ2, html code.shiki .sNyJ2{--shiki-default:#DF8E1D;--shiki-default-font-style:italic;--shiki-dark:#ABB2BF;--shiki-dark-font-style:inherit}html pre.shiki code .sP-bm, html code.shiki .sP-bm{--shiki-default:#8839EF;--shiki-default-font-style:italic;--shiki-dark:#E5C07B;--shiki-dark-font-style:inherit}html pre.shiki code .sEaR9, html code.shiki .sEaR9{--shiki-default:#8839EF;--shiki-dark:#D19A66}html pre.shiki code .sQee8, html code.shiki .sQee8{--shiki-default:#D20F39;--shiki-dark:#E5C07B}html pre.shiki code .sHUla, html code.shiki .sHUla{--shiki-default:#7C7F93;--shiki-dark:#C678DD}",{"title":197,"searchDepth":325,"depth":325,"links":12302},[12303,12304,12305,12313,12320,12324],{"id":12,"depth":287,"text":13},{"id":158,"depth":287,"text":158},{"id":176,"depth":287,"text":176,"children":12306},[12307,12308,12309,12310,12311,12312],{"id":765,"depth":325,"text":765},{"id":990,"depth":325,"text":990},{"id":1365,"depth":325,"text":1366},{"id":1433,"depth":325,"text":1434},{"id":2141,"depth":325,"text":2141},{"id":2169,"depth":325,"text":2169},{"id":2283,"depth":287,"text":2283,"children":12314},[12315,12316,12317,12318,12319],{"id":2286,"depth":325,"text":2286},{"id":2846,"depth":325,"text":2846},{"id":5890,"depth":325,"text":5890},{"id":6547,"depth":325,"text":6547},{"id":8483,"depth":325,"text":8483},{"id":8801,"depth":287,"text":8801,"children":12321},[12322,12323],{"id":8814,"depth":325,"text":8815},{"id":9424,"depth":325,"text":9425},{"id":9449,"depth":287,"text":9449,"children":12325},[12326,12327],{"id":9459,"depth":325,"text":176},{"id":11550,"depth":325,"text":2283},[12329],"经验分享","2026-08-03 12:22:56","OcEasy项目SSE流式输出实战解析",false,"md",null,{},"/2026/sse","---\ntitle: SSE\ndescription: OcEasy项目SSE流式输出实战解析\ndate: 2026-08-03 12:22:56\nupdated: 2026-08-03 12:22:56\nimage: # 图片\ncategories: [经验分享]\n---\n\n### 什么是SSE？\n\n**SSE（Server-Sent Events，服务器推送事件）** 是一种基于 HTTP 的长连接技术。\n核心思想极其朴素：\n\n> 普通的 HTTP 响应是\"服务器一次性把整个 body 发完就断开\"。\n> SSE 则是**服务器保持连接不关闭，把数据分成一小块一小块持续发**。\n\nOcEasy 用的是一种简化变体（也叫 **NDJSON over SSE** / 逐行 JSON）：\n\n- 响应头标注 `Content-Type: text/event-stream`\n- 响应体是**一行一个 JSON 对象**，行之间用 `\\n` 换行符分隔\n- 每次 `yield` 一行，数据就立刻到达浏览器，不等后面的行\n\n**与 WebSocket 的区别**\n\n| 特性     | SSE                     | WebSocket            |\n| -------- | ----------------------- | -------------------- |\n| 方向     | 服务器 → 客户端（单向） | 双向                 |\n| 协议     | 纯 HTTP，无需升级       | 需要 101 协议升级    |\n| 自动重连 | 浏览器原生支持          | 需自己实现           |\n| 断点续传 | 容易（带游标）          | 较难                 |\n| 适用场景 | 流式文本、进度、通知    | 游戏、实时协作、聊天 |\n\n**聊天 AI 的逐字输出是 SSE 的典型场景**：只需要服务端往客户端推文本，不需要客户端往服务器推数据（发消息走普通 POST）。\n\n### 架构\n\n![img](https://assets.yangzy.top/ChatGPT%20Image%202026%E5%B9%B48%E6%9C%883%E6%97%A5%2012_47_39.webp)\n\n> 即后端 LLM 每次生成一个 token → 包成 Packet → 序列化成\"一行 JSON + 换行\" → 立刻 flush 给浏览器；\n> 前端用 `response.body` 的流式 reader 逐块读取 → 按换行切分 → JSON.parse → 累加到界面。\n\n### 后端\n\n这是流式聊天的 HTTP 端点。**注意返回值类型是 `StreamingResponse | ChatFullResponse`**，根据请求参数 `stream` 决定走流式还是非流式。\n\n```python\n# chat_backend.py:681\ndef stream_generator() -> Generator[str, None, None]:\n    state_container = ChatStateContainer()\n    try:\n        for obj in handle_stream_message_objects(...):\n            yield get_json_line(obj.model_dump())   # 每个对象 → 一行 JSON\n    except Exception as e:\n        yield json.dumps({\"error\": str(e)})\n    finally:\n        logger.debug(\"Stream generator finished\")\n\nreturn StreamingResponse(stream_generator(), media_type=\"text/event-stream\")\n```\n\n**FastAPI 的 `StreamingResponse` 是流式输出的\"发动机\"**：\n\n- 它接受一个**生成器（generator）**。\n- 每次生成器 `yield` 一个字符串，FastAPI（底层是 Starlette）就把它**立即作为一块 HTTP chunk 发出去**，不会攒起来。\n- 连接保持打开，直到生成器结束（`StopIteration`）或客户端断开。\n\n这就是\"后端不一次给完、前端能收到增量\"的根源。\n\n#### 序列化\n\n```python\ndef get_json_line(\n    json_dict: dict[str, Any], encoder: type[json.JSONEncoder] = OnyxJSONEncoder\n) -> str:\n    return json.dumps(json_dict, cls=encoder) + \"\\n\"\n```\n\n- `json.dumps` 把 Pydantic 对象 `model_dump()` 后的字典序列化。\n- **末尾加 `\\n`**——这个换行符是前端的\"消息分隔符\"（协议关键）。\n- 自定义 `OnyxJSONEncoder` 处理 `datetime`、`UUID` 等非原生 JSON 类型（`server/utils.py:20`）。\n\n> 每个包在线上就是一行文本，例如：\n> `{\"placement\":{\"turn_index\":0},\"obj\":{\"type\":\"agent_response_delta\",\"content\":\"你\"}}`\n\n#### 数据模型\n\n```python\nclass Packet(BaseModel):\n    placement: Placement                      # 定位信息（哪个 turn / 哪个模型）\n    obj: Annotated[PacketObj, Field(discriminator=\"type\")]   # 具体内容，按 type 区分\n```\n\n`PacketObj` 是一个**按 `type` 字段判别的联合类型**，几十种包包括：\n\n| 包类型                     | 作用                                 |\n| -------------------------- | ------------------------------------ |\n| `agent_response_delta`     | **回答内容增量**（真正打字的包）     |\n| `agent_response_start`     | 回答开始（携带最终文档、预处理耗时） |\n| `reasoning_delta`          | 思考/推理内容增量                    |\n| `search_tool_*_delta`      | 搜索工具参数/结果增量                |\n| `tool_call_argument_delta` | 工具调用参数流式生成                 |\n| `citation_info`            | 引用文献信息                         |\n| `chat_heartbeat`           | 心跳（见第 6 节）                    |\n| `stream_stop`              | 流结束（含 `stop_reason`）           |\n| `error`                    | 错误（`StreamingError`）             |\n\n> Pydantic 的 `discriminator=\"type\"` 意味着**反序列化时前端必须带 `type` 字段**，\n> 前端 `PacketType`（lib.tsx:92）是同样的判别联合。\n\n#### 生成器链：包怎么从 LLM 流到 HTTP\n\n调用链是层层嵌套的生成器，每一层把上层 `yield` 的东西**透传**给下层：\n\n```\nstream_generator (chat_backend.py:682)\n   └─ handle_stream_message_objects (process_message.py:1752)\n        └─ _stream_chat_turn (process_message.py:1528)\n             └─ _run_models (process_message.py:1062)\n                  └─ _read_stream (process_message.py:1496)  ← 实际对外 yield\n```\n\n`_stream_chat_turn`（process_message.py:1582-1661）的关键逻辑：\n\n1. 在一个短命 DB session 里执行 `build_chat_turn`，把**准备阶段产生的包**（如 session id、message id）先 `yield` 出去；\n2. 构造 `StreamBufferWriter`（流缓冲）；\n3. 调用 `_run_models` 得到 run 流并 `yield from`。\n\n**为什么先发准备阶段包？** 前端需要立刻知道\"这条消息分配到了哪个 session / 哪个 message id\"，才能在本地消息树上建出节点来承接后续的内容包。\n\n#### 逐Token\n\n这是\"字一个个出来\"的**最底层源头**：\n\n```python\nfor packet in llm.stream(\n    prompt=llm_msg_history,\n    tools=tool_definitions,\n    tool_choice=tool_choice,\n    max_tokens=max_tokens,\n    reasoning_effort=reasoning_effort,\n    ...\n):\n    delta = packet.choice.delta\n\n    if not answer_start:\n        yield Packet(obj=AgentResponseStart(final_documents=..., ...))\n        answer_start = True\n\n    accumulated_answer += content_chunk                    # 后端也累加一份\n    state_container.set_answer_tokens(accumulated_answer)  # 用于最终持久化\n    yield Packet(\n        placement=_current_placement(),\n        obj=AgentResponseDelta(content=content_chunk),     # 每块一个增量包\n    )\n```\n\n- `llm.stream()` 的实现封装在 `backend/onyx/llm/multi_llm.py:981`，底层是 **litellm** 的 `CustomStreamWrapper`（`multi_llm.py:992`），再往下是各家 LLM 的 HTTP 流。\n- **每次迭代只有一个 token（或一小段）**。这就是\"一个一个\"的最小粒度。\n- 回答开始前还有一个 `AgentResponseStart` 包，携带 `final_documents`（检索到的文档）和 `pre_answer_processing_seconds`（从发消息到开始作答的耗时）。\n- 同时 `accumulated_answer += content_chunk`：后端维护一个**累积文本**，用于流结束后一次性持久化到数据库（前端是增量的，数据库要的是完整文本）。\n\n#### 回答带引用\n\n真实项目里回答文本中常带 `[1]`、`[2]` 这样的引文标记。\n代码通过 `citation_processor.process_token(content_chunk)`（llm_step.py:1258）**逐 token 扫描**，把属于引文标记的部分拆出来单独发 `CitationInfo` 包，正文里的标记换成占位符。前端再把引文号和文档 ID 对应起来，渲染成可点击的 [1]。\n\n#### 并发\n\n>多个模型同时生产数据，一个writer统一收集和保存，一个reader负责把数据发给浏览器\n\n`_run_models`（process_message.py:1062）是后端流式的\"调度中心\"。核心设计：\n\n- **每个模型一个 worker 线程**（`_run_model`，process_message.py:1209），各自的 `Emitter` 把 Packet 推进一个共享的**无界队列** `merged_queue`；\n- **一个 writer 线程**（`_drain_to_completion`）从队列 drain，负责两件事：\n  1. 把每个包写入 `StreamBufferWriter`（供断线回放）；\n  2. 转发给 reader 的 `tee` 队列；\n- **reader 生成器**（`_read_stream`，process_message.py:1496）从 `tee` 里取包 yield 给 HTTP。\n\n```\nworker0 ──Emitter──▶ merged_queue ──┐\nworker1 ──Emitter──▶ merged_queue ──┼─▶ writer线程 ──▶ tee队列 ──▶ reader → yield → HTTP\nworker2 ──Emitter──▶ merged_queue ──┘      │\n                                          └─▶ StreamBufferWriter（持久化缓冲）\n```\n\n几个关键点：\n\n- **多生产者单消费者**：多个模型并发产包，但最终合成**一个** SSE 流（按到达顺序交错）。\n- **reader 可以\"死\"而 writer 必须活**：客户端断开（GeneratorExit）时，reader 直接 return，但 writer 继续把剩余包写进缓冲（`process_message.py:1515-1523`），这样**断线的客户端重连后能回放到完整内容**。\n- `reader_gone` 事件标记 reader 已离开，`tee` 队列停止累积无人消费的包，避免内存泄漏。\n\n### 前端\n\n#### 发消息\n\n发消息：`sendMessage`（app/services/lib.tsx:145）\n\n```ts\nconst response = await fetch(`/api/chat/send-chat-message`, {\n  method: \"POST\",\n  headers: { \"Content-Type\": \"application/json\" },\n  body,\n  signal,                       // ← 取消信号\n});\n\nif (!response.ok) { /* 抛错 */ }\n\nyield* withoutHeartbeats(handleSSEStream\u003CPacketType>(response, signal));\n```\n\n**最反直觉的一点**：`fetch` 在这里返回的是一个 `Response`，但 `await fetch(...)` **只等响应头**，不等 body。因为 `sendMessage` 是 `async function*`（异步生成器），它把 `Response` 交给 `handleSSEStream`，调用方再 `for await` 逐包消费。\n\n`withoutHeartbeats`（lib.tsx:207）是一个过滤层：\n\n```ts\nasync function* withoutHeartbeats(stream) {\n  for await (const packet of stream) {\n    if (\"obj\" in packet && packet.obj.type === \"chat_heartbeat\") continue;\n    yield packet;\n  }\n}\n```\n\n心跳包不承载运行状态，消费端一律看不见它（心跳本身干什么，见下文）。\n\n#### 核心解析器\n\n```ts\nexport async function* handleSSEStream\u003CT extends PacketType>(\n  streamingResponse: Response,\n  signal?: AbortSignal\n): AsyncGenerator\u003CT, void, unknown> {\n  const reader = streamingResponse.body?.getReader();  // ① 取流的 reader\n  const decoder = new TextDecoder();                    // ② 二进制 → 字符串\n  let buffer = \"\";                                      // ③ 半包缓冲\n\n  if (signal) {\n    signal.addEventListener(\"abort\", () => {\n      reader?.cancel();                                 // ④ 中止 → 取消底层连接\n    });\n  }\n\n  try {\n    while (true) {\n      const rawChunk = await reader?.read();            // ⑤ 异步读一块（可能几个包 or 半个包）\n      const { done, value } = rawChunk;\n      if (done) break;                                  // 流结束\n\n      buffer += decoder.decode(value, { stream: true }); // ⑥ 关键：stream 模式解码\n\n      const lines = buffer.split(\"\\n\");                 // ⑦ 按换行切\n      buffer = lines.pop() || \"\";                       // ⑧ 最后不完整的一行留回 buffer\n\n      for (const line of lines) {\n        if (line.trim() === \"\") continue;\n        try {\n          const data = JSON.parse(line) as T;           // ⑨ 还原对象\n          yield data;                                    // ⑩ 吐给调用方\n        } catch (error) {\n          // 某些工具结果会把多个 JSON 挤在一行，用正则兜底提取\n          const jsonObjects = line.match(/\\{[^{}]*\\}/g);\n          if (jsonObjects) {\n            for (const jsonObj of jsonObjects) {\n              try {\n                yield JSON.parse(jsonObj) as T;\n              } catch (innerError) { /* 忽略 */ }\n            }\n          }\n        }\n      }\n    }\n\n    // 流结束后处理 buffer 里残留的半行\n    if (buffer.trim() !== \"\") {\n      try { yield JSON.parse(buffer) as T; } catch { /* 忽略 */ }\n    }\n  } finally {\n    // ⑪ 生成器被提前 break 时必须 cancel，否则连接永远挂着\n    void reader?.cancel().catch(() => {});\n  }\n}\n```\n\n| 步骤                 | 原因                                                         |\n| -------------------- | ------------------------------------------------------------ |\n| ① `getReader()`      | `response.body` 是一个 `ReadableStream`，用 reader 才能**异步增量**读取，而不是 `await response.json()` 一次性拿完 |\n| ② `TextDecoder`      | 网络传的是字节（Uint8Array），需要解码成字符串               |\n| ③ `buffer`           | 网络数据可能**切在任意位置**（一个 JSON 的后半段），需要暂存\"不完整的尾巴\" |\n| ④ `reader.cancel()`  | `AbortController` 触发时，必须主动 cancel 流，否则底层连接挂起（见第 5 节） |\n| ⑥ `{ stream: true }` | **UTF-8 多字节字符可能被切到字节边界**，`stream:true` 让解码器缓存不完整字节、下次补全。不传会乱码！ |\n| ⑦⑧ 按 `\\n` 切行      | 协议规定一行一个 JSON。`split` 后最后一段没有换行符结尾，说明是**半包**，留回 buffer |\n| ⑪ `finally` cancel   | 消费方 `break`/`return` 提前退出生成器时，只有 cancel 才会真正断开 SSE |\n\n> 💡 步骤 ⑦⑧ 是**所有流式协议通用**的\"粘包/半包处理\"思路，在 WebSocket 消息、NDJSON 日志流、日志采集里都能见到。\n\n#### 缓冲层\n\n```ts\nexport class CurrentMessageFIFO {\n  private stack: PacketType[] = [];\n  isComplete: boolean = false;\n  error: string | null = null;\n\n  push(packetBunch) { this.stack.push(packetBunch); }\n  nextPacket() { return this.stack.shift(); }   // FIFO：先入先出\n  isEmpty() { return this.stack.length === 0; }\n}\n\nexport async function updateCurrentMessageFIFO(stack, params) {\n  try {\n    for await (const packet of sendMessage(params)) {\n      if (params.signal?.aborted) throw new Error(\"AbortError\");\n      stack.push(packet);\n    }\n  } catch (error) { /* 记入 stack.error */ }\n  finally { stack.isComplete = true; }\n}\n```\n\n为什么需要 FIFO？因为 **`for await` 的生产速度远快于 React 渲染速度**。\n\n如果直接在每个包上 setState，React 会被几万个重渲染压垮。\n\nFIFO 把\"生产\"（后台异步迭代，持续 push）和\"消费\"（主循环按帧取包渲染）解耦。\n\n#### 消费循环\n\n主循环 `while (!stack.isComplete || !stack.isEmpty())` 是前端流式消费的心脏：\n\n```ts\nconst stack = new CurrentMessageFIFO();\nupdateCurrentMessageFIFO(stack, { signal, message, ... });\n\nawait delay(50);   // 给第一批包一点时间\n\nwhile (!stack.isComplete || !stack.isEmpty()) {\n  if (stack.isEmpty()) {\n    if (pendingFlush) await flushViaRAF();   // 攒一批，下一帧统一刷新\n    else await delay(0.5);                    // 空转等待生产方\n  }\n\n  if (!stack.isEmpty() && !controller.signal.aborted) {\n    const packet = stack.nextPacket();\n    updateChatStateAction(frozenSessionId, \"streaming\");  // 状态机：loading → streaming\n\n    // —— 按包类型分流 ——\n    if ((packet as MessageResponseIDInfo).user_message_id) {\n      newUserMessageId = ...;   // 记下 user message id\n    }\n    if ((packet as MessageResponseIDInfo).reserved_assistant_message_id) {\n      newAgentMessageId = ...;  // 记下预留的 assistant message id\n    }\n    if (Object.hasOwn(packet, \"user_files\")) { /* 用户文件包 */ }\n    if (Object.hasOwn(packet, \"file_ids\")) { /* 生成图片 */ }\n    else if (Object.hasOwn(packet, \"error\")) {\n      // 单模型：throw 终止；多模型：把错误路由到具体模型的面板\n    }\n    else if (Object.hasOwn(packet, \"message_id\")) { /* 完整消息 */ }\n    else if (Object.hasOwn(packet, \"stop_reason\")) { /* 流结束原因 */ }\n    else if (Object.hasOwn(packet, \"obj\")) {\n      const packetObj = (packet as Packet).obj;\n      packets.push(typedPacket);        // ← AgentResponseDelta 都堆在这里\n      packetsVersion++;\n      singleModelDirty = true;\n      // 引文 / 开始包单独处理\n    }\n    pendingFlush = true;\n  }\n}\nflushPendingUpdates();\n```\n\n**按 `Object.hasOwn(packet, \"xxx\")` 判断包类型**——不同包有不同的顶层字段，这是项目里判别\"这是哪种包\"的惯用手法（对应后端 Pydantic 的判别联合）。\n\n**渲染性能三件套**\n\n**① `flushViaRAF` 批量刷新**\n\n`requestAnimationFrame` 把一帧内的所有包**合并成一次** React 更新：\n\n```ts\nawait delay(50);\nwhile (...) {\n  if (stack.isEmpty()) {\n    if (pendingFlush) await flushViaRAF();  // 攒到下一帧再刷\n  }\n  ...\n}\n```\n\n**② `packetCount` 代替数组做 memo（AgentMessage.tsx:73）**\n\nReact.memo 比较 props。`packets` 数组被**原地 push**（引用不变），所以 memo 直接比较数组会认为\"没变\"。项目用 `packetCount` 这个**原始数字**做比较：\n\n```tsx\nprev.packetCount === next.packetCount &&\nprev.chatState.agent === next.chatState.agent &&\n... // 其余字段\n```\n\n```ts\n// useChatSessionController.ts:319 的注释点明了设计意图：\n// \"AgentMessage's memo compares packetCount, not the packets array.\"\nnode.packetCount = accumulated.length;\n```\n\n③ trailing flush：burst 结束不饿死最后几个包`useChatSessionController.ts:346`：如果距上次 flush 不足 100ms 就设一个 **120ms 的定时器**，保证\"突发数据的最后几个包\"即使等不到下一个包，也会被定时刷出来。\n\n#### 渲染层\n\n`AgentMessage` 拿到原始 packets 后交给这个 hook 处理成 UI 数据：\n\n```ts\n// 处理状态放在 ref 里：增量、同步、不触发双渲染\nconst stateRef = useRef\u003CProcessorState>(createInitialState(nodeId));\n// 只有真正的 UI 状态才用 useState\nconst [renderComplete, setRenderComplete] = useState(false);\n\n// 增量处理：只处理新到的包（避免重复处理已消费的）\nif (rawPackets.length > stateRef.current.nextPacketIndex) {\n  stateRef.current = processPackets(stateRef.current, rawPackets);\n}\n```\n\n`processPackets`（packetProcessor.ts）遍历新包：\n\n- `agent_response_delta` → 累加进 `potentialDisplayGroups` 的文本块；\n- 工具类包 → 归类成 `toolGroups`（步骤/轮次分组，供时间线 UI 展示\"思考了几步\"）；\n- `message_start` → 标记 `finalAnswerComing`；\n- `stream_stop` → 标记 `stopPacketSeen` 和 `stopReason`；\n- `citation_info` → 进 `citationMap`。\n\n最终回答文本由 `displayGroups` 渲染成 Markdown，每次新 delta 到达 → `rawPackets.length` 增加 → 重新 `processPackets` → 重渲染。\n**打字机效果 = 增量数据 + 高频重渲染**。\n\n### 心跳机制\n\nLLM 生成可能长时间\"没动静\"（比如调工具、检索文档），但 HTTP 长连接如果长时间没有数据，**中间代理/负载均衡可能把连接掐掉**。\n心跳（`chat_heartbeat`）就是用来保活的。\n\n#### 产生方（后端）\n\n主 stream 的 reader 在**空闲超过 `CHAT_HEARTBEAT_INTERVAL_S`（默认 15 秒）**时发一个心跳包（process_message.py:1500-1508）：\n\n```python\nwhile True:\n    try:\n        item = tee.get(timeout=_CANCEL_POLL_INTERVAL_S)\n    except queue.Empty:   # 队列空 = 没有真实包到来\n        now = time.monotonic()\n        if now - last_packet_yield >= CHAT_HEARTBEAT_INTERVAL_S:\n            yield heartbeat_packet()          # 发个心跳保活\n            last_packet_yield = now\n        continue\n    ...\n```\n\n`heartbeat_packet()`（streaming_models.py:496）：\n\n```python\ndef heartbeat_packet() -> Packet:\n    \"\"\"Keepalive for silent stretches; carries no run state.\"\"\"\n    return Packet(placement=Placement(turn_index=0), obj=ChatHeartbeat())\n```\n\n`CHAT_HEARTBEAT_INTERVAL_S = int(os.environ.get(\"CHAT_HEARTBEAT_INTERVAL_S\") or \"15\")`（chat_configs.py:35）——可用环境变量调整。\n\n#### 消费方（前端）\n\n- `withoutHeartbeats`（lib.tsx:207）在**发消息链路**里直接丢弃心跳；\n- resume 链路里心跳被用作**liveness tick**（useChatSessionController.ts:341）：\n  收到心跳说明连接还活着、会话还是当前焦点，用于决定是否继续刷新；\n- 渲染层完全看不见心跳包。\n\n### 断线重连与流回放\n\nSSE 是单向长连接，任何网络抖动都可能断。OcEasy 用\"**持久化缓冲 + 游标回放**\"实现断线续传。\n\n#### 后端\n\n写路径：`_run_models` 的 writer 线程把每个包 `get_json_line` 后**同时**写入缓冲：\n\n```python\n# process_message.py:1650\nfor pre_run_packet in pre_run_packets:\n    stream_buffer.append_line(get_json_line(pre_run_packet.model_dump()))\n```\n\n`append_line`（stream_buffer.py:80）攒到一定字节再批量 `flush`；`flush` 用 **zlib 压缩**后按 `chunk_count` 分块写进共享缓存（Redis），带 TTL：\n\n```python\ndef flush(self) -> None:\n    payload = zlib.compress(\"\".join(self._pending).encode(\"utf-8\"))\n    self._cache.set(\n        _chunk_key(self._chat_session_id, self._run_id, self._meta.chunk_count),\n        payload,\n        ex=CHAT_STREAM_BUFFER_TTL_S,\n    )\n```\n\n关键属性：\n\n- **`truncated`**：缓冲超上限（`CHAT_STREAM_BUFFER_MAX_BYTES`）就标记截断，降级为\"不可恢复\"；\n- **`done`**：流正常结束后 `mark_done()`（stream_buffer.py:131），延长数据保留期，\n  让刷新页面后的客户端能回放完整内容。\n\n```python\ndef resume_chat_stream(\n    session_id: UUID,\n    cursor: int = Query(0, ge=0),     # 客户端上次读到哪\n    user: User = Depends(...),\n) -> StreamingResponse:\n    ...\n    run_id = get_processing_run_id(session_id, cache)\n    if run_id is None or not has_stream_buffer(cache, session_id, run_id):\n        raise OnyxError(NOT_FOUND, \"No resumable run for this chat session\")\n\n    def stream_buffered_run() -> Generator[str, None, None]:\n        chunk_cursor = cursor\n        while True:\n            read = read_stream_chunks(cache, session_id, run_id, chunk_cursor, ...)\n            if read is None or read.gap: return      # 缓冲丢了 → 终止，客户端重新拉全量\n            if read.blocks:\n                yield \"\".join(read.blocks)           # 回放缓冲内容\n                chunk_cursor = read.next_cursor\n                continue\n            if read.done: return                     # 正常结束\n            if not is_chat_session_processing(session_id, cache):\n                # writer 死了但没标 done → 再补一次 drain\n                ...\n            # 缓冲读完了 → 实时 tail 新的包\n            time.sleep(CHAT_RESUME_POLL_INTERVAL_S)\n            if now - last_emit >= CHAT_HEARTBEAT_INTERVAL_S:\n                yield get_json_line(heartbeat_packet().model_dump())  # 轮询中也发心跳\n    ...\n```\n\n设计要点：\n\n- **缓冲在共享缓存里，任何 pod 都能服务**（注释：\"Serves any pod\"）；\n- 404（无可恢复 run）是正常业务信号，前端回退到重新拉取落库 session；\n- 回放结束进入**实时 tail**：轮询新 chunk + 心跳保活。\n\n#### 前端\n\n```ts\nexport async function* resumeStream(chatSessionId, cursor, signal?) {\n  const response = await fetch(\n    `/api/chat/chat-session/${chatSessionId}/resume-stream?cursor=${cursor}`,\n    { signal }\n  );\n  ...\n  yield* handleSSEStream\u003CPacketType>(response, signal);\n}\n```\n\n页面刷新后，如果 session 有 `current_run` 且是 assistant 节点，就\"粘\"上去续读：\n\n```ts\nconst abortController = new AbortController();\ntry {\n  for await (const rawPacket of resumeStream(sessionId, 0, abortController.signal)) {\n    if (!stillCurrent()) return;              // 用户已切走会话 → 停\n    if (packet.obj.type === \"chat_heartbeat\") continue;\n    accumulated.push(packet);\n    // 每 100ms 或 trailing 120ms 刷一次到消息树\n  }\n} finally {\n  abortController.abort();\n  // 流结束后拉一次落库的完整 session 做\"结算\"\n  const settled = await (await fetch(`/api/chat/get-chat-session/${sessionId}`)).json();\n  updateSessionAndMessageTree(sessionId, processRawChatHistory(settled.messages, ...));\n}\n```\n\n**\"流式 + 落库\"双保险**：在线期间用增量流；断线/刷新后用回放流；回放结束用落库数据最终对齐。\n",{"text":12339,"minutes":12340,"time":12341,"words":12342},"19 min read",18.415,1104900,3683,{"title":5,"description":12331},{"loc":12336},"posts/2026/SSE",[],"QbcMQPi4PC3DkzdCK2Kf5R18NWaLagfDUjZ4MnRhT94",[12349,12334],{"title":12350,"path":12351,"stem":12352,"date":12353,"type":12334,"children":-1},"python_essay","/2026/python_essay","posts/2026/python_essay","2026-05-21 11:20:00",1785741804177]