@code-yeongyu/senpi 2026.7.4 → 2026.7.5-2

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.
Files changed (21) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/examples/sdk/12-full-control.ts +1 -1
  3. package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
  4. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.d.ts.map +1 -1
  5. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js +115 -19
  6. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js.map +1 -1
  7. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +1 -171
  8. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
  9. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.d.ts +0 -170
  10. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.d.ts.map +1 -1
  11. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.js +3 -173
  12. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.js.map +1 -1
  13. package/node_modules/@earendil-works/pi-ai/dist/providers/mistral.models.d.ts +1 -1
  14. package/node_modules/@earendil-works/pi-ai/dist/providers/mistral.models.js +4 -4
  15. package/node_modules/@earendil-works/pi-ai/dist/providers/mistral.models.js.map +1 -1
  16. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js +4 -4
  17. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js.map +1 -1
  18. package/node_modules/@earendil-works/pi-ai/package.json +1 -1
  19. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  20. package/npm-shrinkwrap.json +9 -9
  21. package/package.json +4 -4
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## [2026.7.5-2] - 2026-07-05
4
+
5
+ ### Added
6
+
7
+ ### Changed
8
+
9
+ ### Fixed
10
+
11
+ ### Removed
12
+
13
+ ## [2026.7.5] - 2026-07-05
14
+
15
+ ### Added
16
+
17
+ ### Changed
18
+
19
+ ### Fixed
20
+
21
+ ### Removed
22
+
3
23
  ## [2026.7.4] - 2026-07-04
4
24
 
5
25
  ### Added
@@ -26,7 +26,7 @@ if (process.env.MY_ANTHROPIC_KEY) {
26
26
  // Model registry with no custom models.json
27
27
  const modelRegistry = ModelRegistry.inMemory(authStorage);
28
28
 
29
- const model = getModel("anthropic", "claude-sonnet-4-20250514");
29
+ const model = getModel("anthropic", "claude-opus-4-5");
30
30
  if (!model) throw new Error("Model not found");
31
31
 
32
32
  // In-memory settings with overrides
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@earendil-works/pi-agent-core",
3
3
  "private": true,
4
- "version": "2026.7.4",
4
+ "version": "2026.7.5-2",
5
5
  "description": "General-purpose agent with transport abstraction, state management, and attachment support",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
@@ -30,7 +30,7 @@
30
30
  "prepublishOnly": "npm run clean && npm run build"
31
31
  },
32
32
  "dependencies": {
33
- "@earendil-works/pi-ai": "^2026.7.4",
33
+ "@earendil-works/pi-ai": "^2026.7.5-2",
34
34
  "ignore": "7.0.5",
35
35
  "typebox": "1.1.38",
36
36
  "yaml": "2.9.0"
@@ -1 +1 @@
1
- {"version":3,"file":"anthropic-messages.d.ts","sourceRoot":"","sources":["../../src/api/anthropic-messages.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAU1C,OAAO,KAAK,EAWX,mBAAmB,EAEnB,cAAc,EACd,aAAa,EAMb,MAAM,aAAa,CAAC;AA+JrB,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAE1E,MAAM,MAAM,wBAAwB,GAAG,YAAY,GAAG,SAAS,CAAC;AAwChE,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACtD;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB;;;;;;;;;;;OAWG;IACH,eAAe,CAAC,EAAE,wBAAwB,CAAC;IAC3C;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtE;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;CACnB;AA2cD,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,oBAAoB,EAAE,gBAAgB,CAoTzE,CAAC;AAuEF,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,oBAAoB,EAAE,mBAAmB,CA2ClF,CAAC"}
1
+ {"version":3,"file":"anthropic-messages.d.ts","sourceRoot":"","sources":["../../src/api/anthropic-messages.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAU1C,OAAO,KAAK,EAWX,mBAAmB,EAEnB,cAAc,EACd,aAAa,EAMb,MAAM,aAAa,CAAC;AA+JrB,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAE1E,MAAM,MAAM,wBAAwB,GAAG,YAAY,GAAG,SAAS,CAAC;AAwChE,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACtD;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB;;;;;;;;;;;OAWG;IACH,eAAe,CAAC,EAAE,wBAAwB,CAAC;IAC3C;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtE;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;CACnB;AAghBD,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,oBAAoB,EAAE,gBAAgB,CAoTzE,CAAC;AAuEF,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,oBAAoB,EAAE,mBAAmB,CA2ClF,CAAC"}
@@ -174,15 +174,80 @@ const REPLAYABLE_ANTHROPIC_PROVIDER_NATIVE_TYPES = new Set([
174
174
  "tool_search_tool_result",
175
175
  "container_upload",
176
176
  // Server-side fallback beta (server-side-fallback-2026-06-01) emits a
177
- // `fallback` block mid-response, interleaved with thinking blocks. The API
178
- // requires the latest assistant message to be replayed with its block
179
- // sequence unmodified; dropping the block yields a 400
180
- // ("thinking ... blocks in the latest assistant message cannot be modified").
177
+ // `fallback` marker mid-response. The marker itself is replayed as a kept
178
+ // audit block. Blocks emitted *before* the final marker are the declined
179
+ // attempt and are pruned in convertMessages (see lastAnthropicFallbackBoundary
180
+ // and collectDiscardedFallbackToolCallIds); the marker onward replays verbatim.
181
181
  "fallback",
182
182
  ]);
183
183
  function isReplayableAnthropicProviderNativeBlock(raw) {
184
184
  return isRecord(raw) && typeof raw.type === "string" && REPLAYABLE_ANTHROPIC_PROVIDER_NATIVE_TYPES.has(raw.type);
185
185
  }
186
+ function isSameAnthropicModel(message, model) {
187
+ return message.provider === model.provider && message.api === model.api && message.model === model.id;
188
+ }
189
+ function isAnthropicFallbackMarkerBlock(block) {
190
+ return (block.type === "providerNative" &&
191
+ block.subtype === "fallback" &&
192
+ isRecord(block.raw) &&
193
+ block.raw.type === "fallback");
194
+ }
195
+ // The server-side fallback beta (server-side-fallback-2026-06-01) can emit a
196
+ // `fallback` marker mid-message after a declined attempt is replaced. Blocks
197
+ // before the final marker belong to the discarded attempt: replaying its
198
+ // thinking/tool_use makes the API reject the turn (a discarded tool_use has no
199
+ // matching tool_result after normalization). Returns the index of the last
200
+ // fallback marker, or -1 when the message has none.
201
+ function lastAnthropicFallbackBoundary(content) {
202
+ let boundary = -1;
203
+ for (let index = 0; index < content.length; index++) {
204
+ if (isAnthropicFallbackMarkerBlock(content[index])) {
205
+ boundary = index;
206
+ }
207
+ }
208
+ return boundary;
209
+ }
210
+ function isAnthropicServerToolUseBlock(raw) {
211
+ return isRecord(raw) && raw.type === "server_tool_use" && typeof raw.id === "string";
212
+ }
213
+ // tool_use ids referenced by server-tool result blocks in content[0, boundary).
214
+ // A pre-boundary `server_tool_use` whose id is absent here is unpaired — the
215
+ // fallback interrupted the declined attempt before its result arrived — so
216
+ // replaying it would leave a server tool_use with no adjacent result and 400 the
217
+ // turn. Paired server-tool blocks replay verbatim per the fallback contract.
218
+ function pairedServerToolUseIdsBeforeBoundary(content, boundary) {
219
+ const paired = new Set();
220
+ for (let index = 0; index < boundary; index++) {
221
+ const block = content[index];
222
+ if (block.type !== "providerNative" || !isRecord(block.raw))
223
+ continue;
224
+ const toolUseId = block.raw.tool_use_id;
225
+ if (typeof toolUseId === "string")
226
+ paired.add(toolUseId);
227
+ }
228
+ return paired;
229
+ }
230
+ // Tool-call ids emitted by a discarded pre-fallback attempt on a same-model
231
+ // assistant turn. These tool_use blocks are dropped from the assistant turn, so
232
+ // their tool_result blocks must be dropped from the following user turn too — an
233
+ // orphaned tool_result triggers its own 400.
234
+ function collectDiscardedFallbackToolCallIds(messages, model) {
235
+ const discarded = new Set();
236
+ for (const message of messages) {
237
+ if (message.role !== "assistant" || !isSameAnthropicModel(message, model))
238
+ continue;
239
+ const boundary = lastAnthropicFallbackBoundary(message.content);
240
+ if (boundary < 0)
241
+ continue;
242
+ for (let index = 0; index < boundary; index++) {
243
+ const block = message.content[index];
244
+ if (block.type === "toolCall") {
245
+ discarded.add(block.id);
246
+ }
247
+ }
248
+ }
249
+ return discarded;
250
+ }
186
251
  function stringRecord(value) {
187
252
  if (!isRecord(value)) {
188
253
  return undefined;
@@ -1070,6 +1135,9 @@ function convertMessages(messages, model, isOAuthToken, cacheControl, preserveTh
1070
1135
  preserveThinking,
1071
1136
  preserveUnsignedThinking: true,
1072
1137
  });
1138
+ // Tool calls from a declined pre-fallback attempt are dropped from their
1139
+ // assistant turn below; drop their tool_results in lockstep so none dangle.
1140
+ const discardedFallbackToolCallIds = collectDiscardedFallbackToolCallIds(transformedMessages, model);
1073
1141
  for (let i = 0; i < transformedMessages.length; i++) {
1074
1142
  const msg = transformedMessages[i];
1075
1143
  if (msg.role === "user") {
@@ -1116,8 +1184,28 @@ function convertMessages(messages, model, isOAuthToken, cacheControl, preserveTh
1116
1184
  }
1117
1185
  else if (msg.role === "assistant") {
1118
1186
  const blocks = [];
1119
- const isSameModel = msg.provider === model.provider && msg.api === model.api && msg.model === model.id;
1120
- for (const block of msg.content) {
1187
+ const isSameModel = isSameAnthropicModel(msg, model);
1188
+ // Blocks before the final fallback marker are the declined attempt; the
1189
+ // marker onward is the serving model's output and replays verbatim.
1190
+ const fallbackBoundary = isSameModel ? lastAnthropicFallbackBoundary(msg.content) : -1;
1191
+ const preBoundaryPairedServerToolUseIds = fallbackBoundary >= 0
1192
+ ? pairedServerToolUseIdsBeforeBoundary(msg.content, fallbackBoundary)
1193
+ : new Set();
1194
+ for (let blockIndex = 0; blockIndex < msg.content.length; blockIndex++) {
1195
+ const block = msg.content[blockIndex];
1196
+ if (fallbackBoundary >= 0 && blockIndex < fallbackBoundary) {
1197
+ // Drop the declined attempt's model-internal blocks: thinking,
1198
+ // tool_use, and any unpaired server_tool_use. Paired server-tool
1199
+ // blocks and text survive per the fallback replay contract.
1200
+ if (block.type === "thinking" || block.type === "toolCall") {
1201
+ continue;
1202
+ }
1203
+ if (block.type === "providerNative" &&
1204
+ isAnthropicServerToolUseBlock(block.raw) &&
1205
+ !preBoundaryPairedServerToolUseIds.has(block.raw.id)) {
1206
+ continue;
1207
+ }
1208
+ }
1121
1209
  if (block.type === "text") {
1122
1210
  if (block.text.trim().length === 0)
1123
1211
  continue;
@@ -1184,27 +1272,35 @@ function convertMessages(messages, model, isOAuthToken, cacheControl, preserveTh
1184
1272
  else if (msg.role === "toolResult") {
1185
1273
  // Collect all consecutive toolResult messages, needed for z.ai Anthropic endpoint
1186
1274
  const toolResults = [];
1187
- // Add the current tool result
1188
- toolResults.push({
1189
- type: "tool_result",
1190
- tool_use_id: msg.toolCallId,
1191
- content: convertContentBlocks(msg.content),
1192
- is_error: msg.isError,
1193
- });
1275
+ // Add the current tool result, unless its tool_use was dropped as a
1276
+ // declined pre-fallback attempt (its result would otherwise dangle).
1277
+ if (!discardedFallbackToolCallIds.has(msg.toolCallId)) {
1278
+ toolResults.push({
1279
+ type: "tool_result",
1280
+ tool_use_id: msg.toolCallId,
1281
+ content: convertContentBlocks(msg.content),
1282
+ is_error: msg.isError,
1283
+ });
1284
+ }
1194
1285
  // Look ahead for consecutive toolResult messages
1195
1286
  let j = i + 1;
1196
1287
  while (j < transformedMessages.length && transformedMessages[j].role === "toolResult") {
1197
1288
  const nextMsg = transformedMessages[j]; // We know it's a toolResult
1198
- toolResults.push({
1199
- type: "tool_result",
1200
- tool_use_id: nextMsg.toolCallId,
1201
- content: convertContentBlocks(nextMsg.content),
1202
- is_error: nextMsg.isError,
1203
- });
1289
+ if (!discardedFallbackToolCallIds.has(nextMsg.toolCallId)) {
1290
+ toolResults.push({
1291
+ type: "tool_result",
1292
+ tool_use_id: nextMsg.toolCallId,
1293
+ content: convertContentBlocks(nextMsg.content),
1294
+ is_error: nextMsg.isError,
1295
+ });
1296
+ }
1204
1297
  j++;
1205
1298
  }
1206
1299
  // Skip the messages we've already processed
1207
1300
  i = j - 1;
1301
+ // Every result in this run may have been dropped; don't emit an empty turn.
1302
+ if (toolResults.length === 0)
1303
+ continue;
1208
1304
  // Add a single user message with all tool results
1209
1305
  params.push({
1210
1306
  role: "user",