@reinsjs/lowering-fetch 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @reinsjs/lowering-fetch
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 0455c28: Deliver `compact`'s request as a directive and ask twice. `SystemNotePayload` gains an optional `directive: true` for notes that ask the model to act; `@reinsjs/lowering-fetch` (all three protocols) and `@reinsjs/lowering-pi` send those as a framed `<system_note>` user message even when the model accepts mid-conversation system messages, and record the landing as `lossy(user-role)`. Measured on a pooled DeepSeek relay, the same request was ignored 4 of 4 times as a mid-conversation system message and followed 5 of 6 times as a framed user note; the official DeepSeek endpoint followed it 6 of 6 either way. Other notes (perception, pins) keep their current landing, and notes already in a log keep theirs, so cached prefixes do not change on upgrade. `compact({ force })` now asks at two lines by default (`at: [0.75, 0.8]`; a single number still works), uses blunter wording on the second request (`CompactRequestInfo.attempt`), skips the turn right after a compaction until the model has acted, and marks requests as directives unless `force.directive` is `false`.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [0455c28]
12
+ - Updated dependencies [24ba407]
13
+ - Updated dependencies [4a57a7a]
14
+ - @reinsjs/core@0.5.0
15
+
16
+ ## 0.4.1
17
+
18
+ ### Patch Changes
19
+
20
+ - Updated dependencies [fb032c1]
21
+ - @reinsjs/core@0.4.0
22
+
3
23
  ## 0.4.0
4
24
 
5
25
  ### Minor Changes
package/README.md CHANGED
@@ -79,7 +79,7 @@ No `node:*`, no `process`, no `Buffer` — the same `dist/index.js` runs on Node
79
79
 
80
80
  Every landing is declared in `LOSS_MATRIX["openai-chat"]` and asserted by tests — nothing is dropped silently.
81
81
 
82
- - `system_note` → a mid-conversation `system` message (`exact`; verified on OpenAI and DeepSeek). Set `midConversationSystem: false` for an upstream that rejects it and the note goes out as a framed `user` message (`lossy`).
82
+ - `system_note` → a mid-conversation `system` message (`exact`; verified on OpenAI and DeepSeek). Set `midConversationSystem: false` for an upstream that rejects it and the note goes out as a framed `user` message (`lossy`). A note marked `directive: true` (an instruction the model must act on, such as `compact`'s request) always goes out as the framed `user` message, on all three protocols.
83
83
  - A user message or note that arrives while tool results are still pending is moved after them on the wire (`tool` messages must directly follow the `assistant` that called them); the user message is recorded `lossy(user)`, the log order is untouched.
84
84
  - **Thinking**: Chat Completions has no replay slot (no signature, no encrypted item). On the official API thinking events are `dropped`. With `chat.reasoningContent` (DeepSeek) they are replayed as `reasoning_content` — and DeepSeek *requires* the field on every historical assistant message once `tools` is present (400 otherwise), so the layer always sends it, empty when there is nothing to replay.
85
85
  - One turn with several text blocks is merged into one `content` string (`lossy(merged-text)`); `tool` messages carry text only: when the model takes images, each image leaves a pointer (`[image n attached in the next user message]`) and the images go into one `user` message right after the whole batch of `tool` messages — before any user message that was moved after the batch — with a line per result naming the tool, the call id and the image count and saying it is tool output, not the user; untrusted results keep their `<untrusted>` marking (`lossy(tool+lifted-images)`; verified on DeepSeek's official API — check other OpenAI-compatible upstreams before relying on it, and turn it off with `chat.liftToolImages: false` if they reject the shape). Without image support, or with `chat.liftToolImages: false`, images become a placeholder (`lossy(tool-text-only)`); `isError` has no flag on this protocol and is expressed as a `[tool error]` prefix (`lossy`).
@@ -116,6 +116,6 @@ Non-2xx responses throw `HttpError` with `status`, `headers` and the raw body (`
116
116
 
117
117
  ## Documentation
118
118
 
119
- `docs/技术方案.md` §11, `docs/模块盘点/lowering-fetch.md`, `spikes/f1-chat-live/`, `spikes/f2-anthropic-live/`, `spikes/f3-responses-live/` (live verification against DeepSeek and, through the Cloudflare AI Gateway, OpenAI and Anthropic) and `spikes/edge-runtime-check/` (Cloudflare workerd) — in Chinese, at the repository root.
119
+ `docs/archive/implementation-plans/2026-09-08-技术方案v1.0.md` §11, `docs/系统全景图_模块盘点/lowering-fetch.md`, `spikes/f1-chat-live/`, `spikes/f2-anthropic-live/`, `spikes/f3-responses-live/` (live verification against DeepSeek and, through the Cloudflare AI Gateway, OpenAI and Anthropic) and `spikes/edge-runtime-check/` (Cloudflare workerd) — in Chinese, at the repository root.
120
120
 
121
121
  MIT © 2026 NewRate Limited.
package/dist/index.d.ts CHANGED
@@ -63,6 +63,8 @@ type IrItem = {
63
63
  kind: "system_note";
64
64
  event: Event;
65
65
  noteKind: SystemNotePayload["kind"];
66
+ /** 注入者声明是要模型去做的指令:三条线都以带标签的 user 文本送达,不看 midConversationSystem */
67
+ directive: boolean;
66
68
  text: string;
67
69
  escaped: boolean;
68
70
  deferred: boolean;
@@ -291,7 +293,7 @@ declare function endpointOf(model: FetchModel): string;
291
293
  * user 侧内容(tool_result、用户消息、摘要)并进同一条 user(F0 A5b 实测同条 user 里 tool_result 后接文本可达);
292
294
  * - **中途 system 摆放**(S1):不能是首条、必须紧跟 user(含只带 tool_result 的 user)、后接 assistant 或收尾。所以说明
293
295
  * 一律攒到"下一条 assistant 之前"或末尾再放出;此刻前一条不是 user(是 assistant 或什么都没有)就退成 `<system_note>`
294
- * 框住的 user 文本,记 lossy(user-role)。放出位置比时间线晚了一条 user 的,仍算 exact(说明只换位置,见 CLAUDE.md);
296
+ * 框住的 user 文本,记 lossy(user-role)。放出位置比时间线晚了一条 user 的,仍算 exact(说明只换位置,见 AGENTS.md);
295
297
  * - **thinking 回放**:带 signature 原样回放(F0 A7b 接受、伪造签名 400);无签名(流中断)与来自别家的一律 dropped 声明——
296
298
  * 不像 pi-ai 那样降成正文,模型的私下推理不该以它"说过的话"出现在历史里;`redacted_thinking` 用 data 原样回放;
297
299
  * - **缓存断点**:最多 4 个。我们打三处——system 末块、tools 末项、最后一条 user 末块(厂商按前缀向前找命中,20 块回看窗口);
package/dist/index.js CHANGED
@@ -373,7 +373,7 @@ var CHAT_LOSS_MATRIX = {
373
373
  lossy(
374
374
  "user-role",
375
375
  "wrapped in a <system_note> tag and sent with the user role",
376
- "the model declares midConversationSystem: false"
376
+ "the model declares midConversationSystem: false, or the note is a directive the model must act on (SystemNotePayload.directive)"
377
377
  )
378
378
  ],
379
379
  "core.compaction": [
@@ -473,7 +473,7 @@ var ANTHROPIC_LOSS_MATRIX = {
473
473
  lossy2(
474
474
  "user-role",
475
475
  "wrapped in a <system_note> tag and sent with the user role",
476
- "the model family does not support mid-conversation system, or the placement rules forbid it (the previous message is an assistant, or the note would be first)"
476
+ "the model family does not support mid-conversation system, or the placement rules forbid it (the previous message is an assistant, or the note would be first), or the note is a directive the model must act on (SystemNotePayload.directive)"
477
477
  )
478
478
  ],
479
479
  "core.compaction": [
@@ -620,6 +620,7 @@ function eventsToIr(input) {
620
620
  kind: "system_note",
621
621
  event: e,
622
622
  noteKind: e.payload.kind,
623
+ directive: e.payload.directive === true,
623
624
  text: t.text,
624
625
  escaped: t.escaped,
625
626
  deferred: false
@@ -663,6 +664,7 @@ function framedSystemNote(kind, text) {
663
664
  ${text}
664
665
  </system_note>`;
665
666
  }
667
+ var DIRECTIVE_NOTE = "the note is a directive the model must act on, so it is wrapped in a <system_note> tag and sent with the user role";
666
668
  function framedSummary(summary) {
667
669
  return `[Summary of earlier conversation]
668
670
  ${summary}`;
@@ -846,7 +848,7 @@ function encodeAnthropicRequest(input) {
846
848
  break;
847
849
  }
848
850
  case "system_note": {
849
- if (capabilities.midConversationSystem) {
851
+ if (capabilities.midConversationSystem && !item.directive) {
850
852
  pendingNotes.push({ item, moved: false });
851
853
  } else {
852
854
  openUser().push({ type: "text", text: framedSystemNote(item.noteKind, item.text) });
@@ -855,7 +857,7 @@ function encodeAnthropicRequest(input) {
855
857
  item.event,
856
858
  "lossy",
857
859
  "user-role",
858
- "the model does not support mid-conversation system, so it is wrapped in a <system_note> tag and sent with the user role",
860
+ item.directive ? DIRECTIVE_NOTE : "the model does not support mid-conversation system, so it is wrapped in a <system_note> tag and sent with the user role",
859
861
  item.escaped ? ESCAPED_NOTE : void 0,
860
862
  item.deferred ? DEFERRED_NOTE : void 0
861
863
  );
@@ -1349,7 +1351,7 @@ function encodeChatRequest(input) {
1349
1351
  break;
1350
1352
  }
1351
1353
  case "system_note": {
1352
- if (capabilities.midConversationSystem) {
1354
+ if (capabilities.midConversationSystem && !item.directive) {
1353
1355
  messages.push({ role: "system", content: item.text });
1354
1356
  land2(
1355
1357
  landings,
@@ -1366,7 +1368,7 @@ function encodeChatRequest(input) {
1366
1368
  item.event,
1367
1369
  "lossy",
1368
1370
  "user-role",
1369
- "the upstream does not accept mid-conversation system, so it is wrapped in a <system_note> tag and sent with the user role",
1371
+ item.directive ? DIRECTIVE_NOTE : "the upstream does not accept mid-conversation system, so it is wrapped in a <system_note> tag and sent with the user role",
1370
1372
  item.escaped ? ESCAPED_NOTE : void 0,
1371
1373
  item.deferred ? DEFERRED_NOTE : void 0
1372
1374
  );
@@ -2127,7 +2129,7 @@ function encodeResponsesRequest(input) {
2127
2129
  break;
2128
2130
  }
2129
2131
  case "system_note": {
2130
- if (capabilities.midConversationSystem) {
2132
+ if (capabilities.midConversationSystem && !item.directive) {
2131
2133
  items.push({ role: systemRole, content: [{ type: "input_text", text: item.text }] });
2132
2134
  land3(
2133
2135
  landings,
@@ -2147,7 +2149,7 @@ function encodeResponsesRequest(input) {
2147
2149
  item.event,
2148
2150
  "lossy",
2149
2151
  "user-role",
2150
- "the host declared that this upstream does not support mid-conversation system, so it is wrapped in a <system_note> tag and sent with the user role",
2152
+ item.directive ? DIRECTIVE_NOTE : "the host declared that this upstream does not support mid-conversation system, so it is wrapped in a <system_note> tag and sent with the user role",
2151
2153
  item.escaped ? ESCAPED_NOTE : void 0,
2152
2154
  item.deferred ? DEFERRED_NOTE : void 0
2153
2155
  );
@@ -2641,7 +2643,7 @@ var RESPONSES_LOSS_MATRIX = {
2641
2643
  lossy3(
2642
2644
  "user-role",
2643
2645
  "wrapped in a <system_note> tag and sent with the user role",
2644
- "the host declared midConversationSystem: false for an unusual upstream"
2646
+ "the host declared midConversationSystem: false for an unusual upstream, or the note is a directive the model must act on (SystemNotePayload.directive)"
2645
2647
  )
2646
2648
  ],
2647
2649
  "core.compaction": [