@code-yeongyu/senpi-ai 2026.7.25-2 → 2026.7.26

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 (33) hide show
  1. package/dist/api/anthropic-messages.d.ts.map +1 -1
  2. package/dist/api/anthropic-messages.js +135 -16
  3. package/dist/api/anthropic-messages.js.map +1 -1
  4. package/dist/api/azure-openai-responses.js +1 -1
  5. package/dist/api/azure-openai-responses.js.map +1 -1
  6. package/dist/api/bedrock-converse-stream.d.ts.map +1 -1
  7. package/dist/api/bedrock-converse-stream.js +1 -4
  8. package/dist/api/bedrock-converse-stream.js.map +1 -1
  9. package/dist/api/google-shared.d.ts.map +1 -1
  10. package/dist/api/google-shared.js +4 -3
  11. package/dist/api/google-shared.js.map +1 -1
  12. package/dist/api/openai-codex-responses.js +2 -2
  13. package/dist/api/openai-codex-responses.js.map +1 -1
  14. package/dist/api/openai-responses-shared.d.ts +1 -0
  15. package/dist/api/openai-responses-shared.d.ts.map +1 -1
  16. package/dist/api/openai-responses-shared.js +67 -31
  17. package/dist/api/openai-responses-shared.js.map +1 -1
  18. package/dist/api/openai-responses.js +1 -1
  19. package/dist/api/openai-responses.js.map +1 -1
  20. package/dist/providers/data/.manifest.json +1 -1
  21. package/dist/providers/data/openrouter.json +1 -1
  22. package/dist/utils/retry.d.ts.map +1 -1
  23. package/dist/utils/retry.js +10 -0
  24. package/dist/utils/retry.js.map +1 -1
  25. package/dist/utils/stop-details.d.ts +1 -1
  26. package/dist/utils/stop-details.d.ts.map +1 -1
  27. package/dist/utils/stop-details.js +7 -3
  28. package/dist/utils/stop-details.js.map +1 -1
  29. package/dist/utils/tool-call-id.d.ts +9 -0
  30. package/dist/utils/tool-call-id.d.ts.map +1 -0
  31. package/dist/utils/tool-call-id.js +16 -0
  32. package/dist/utils/tool-call-id.js.map +1 -0
  33. package/package.json +2 -2
@@ -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,EAYX,mBAAmB,EAEnB,cAAc,EACd,aAAa,EAMb,MAAM,aAAa,CAAC;AAgMrB,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAE1E,MAAM,MAAM,wBAAwB,GAAG,YAAY,GAAG,SAAS,CAAC;AAoHhE,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;AA2iBD,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,oBAAoB,EAAE,gBAAgB,CAoYzE,CAAC;AAyEF,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,EAYX,mBAAmB,EAEnB,cAAc,EACd,aAAa,EAOb,MAAM,aAAa,CAAC;AAiMrB,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAE1E,MAAM,MAAM,wBAAwB,GAAG,YAAY,GAAG,SAAS,CAAC;AAoHhE,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;AA+pBD,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,oBAAoB,EAAE,gBAAgB,CAoYzE,CAAC;AA0FF,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,oBAAoB,EAAE,mBAAmB,CA2ClF,CAAC"}
@@ -10,6 +10,7 @@ import { getProviderEnvValue } from "../utils/provider-env.js";
10
10
  import { retryProviderRequest } from "../utils/provider-retry.js";
11
11
  import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
12
12
  import { applyServerFallbackAbort, parseServerFallbackReceipt, parseStickyFallbackReceipt, } from "../utils/server-fallback-receipt.js";
13
+ import { normalizeToolCallId } from "../utils/tool-call-id.js";
13
14
  import { isForcedToolChoiceUnsupportedError, omitToolChoiceParam } from "../utils/tool-choice-fallback.js";
14
15
  import { resolveCloudflareBaseUrl } from "./cloudflare.js";
15
16
  import { resolveJsonSchemaStrictSampling } from "./constrained-sampling.js";
@@ -309,6 +310,97 @@ function isAnthropicWebSearchReplayBlock(raw) {
309
310
  return true;
310
311
  return raw.type === "server_tool_use" && raw.name === "web_search";
311
312
  }
313
+ // The user turn a pending server tool can survive is one that carries only
314
+ // tool results. Anything else the wire would emit — real text, an image —
315
+ // closes the assistant turn (the API then 400s the still-open use). A blank
316
+ // message serializes to nothing, so it closes nothing.
317
+ function userMessageClosesServerTurn(message) {
318
+ if (typeof message.content === "string")
319
+ return message.content.trim().length > 0;
320
+ return message.content.some((block) => (block.type === "text" && block.text.trim().length > 0) || block.type === "image");
321
+ }
322
+ function collectProviderNativeToolPairing(messages, model, deferredToolNames, normalizeToolName, discardedFallbackToolCallIds) {
323
+ const resolvedUseIds = new Set();
324
+ const validResultIds = new Set();
325
+ // Uses whose turn can still be resumed. A same-model assistant resets the
326
+ // set: its own result blocks may answer the prior pending uses, and any use
327
+ // it leaves pending becomes the open set. Anything that closes the turn —
328
+ // user text, a tool result whose deferred-tool references serialize sibling
329
+ // text after the results, or another model's assistant — empties it.
330
+ let pendingUseIds = new Set();
331
+ const loadedReferenceNames = new Set();
332
+ for (const message of messages) {
333
+ if (message.role === "assistant") {
334
+ const priorUseIds = pendingUseIds;
335
+ pendingUseIds = new Set();
336
+ if (!isSameAnthropicModel(message, model))
337
+ continue;
338
+ for (const block of message.content) {
339
+ if (block.type !== "providerNative" || !isRecord(block.raw))
340
+ continue;
341
+ const raw = block.raw;
342
+ if (typeof raw.type !== "string" || !REPLAYABLE_ANTHROPIC_PROVIDER_NATIVE_TYPES.has(raw.type))
343
+ continue;
344
+ if (isProviderNativeToolUseBlock(raw) && typeof raw.id === "string")
345
+ pendingUseIds.add(raw.id);
346
+ }
347
+ for (const block of message.content) {
348
+ if (block.type !== "providerNative" || !isRecord(block.raw))
349
+ continue;
350
+ const raw = block.raw;
351
+ if (typeof raw.type !== "string" || !REPLAYABLE_ANTHROPIC_PROVIDER_NATIVE_TYPES.has(raw.type))
352
+ continue;
353
+ const toolUseId = raw.tool_use_id;
354
+ if (typeof toolUseId !== "string")
355
+ continue;
356
+ if (priorUseIds.has(toolUseId) || pendingUseIds.has(toolUseId)) {
357
+ resolvedUseIds.add(toolUseId);
358
+ validResultIds.add(toolUseId);
359
+ }
360
+ }
361
+ continue;
362
+ }
363
+ if (message.role === "toolResult") {
364
+ // Conversion drops a discarded pre-fallback result without touching
365
+ // loadedToolNames, so it must not load the name here either.
366
+ if (discardedFallbackToolCallIds.has(message.toolCallId))
367
+ continue;
368
+ // convertToolResult emits sibling text after the tool_result blocks only
369
+ // for names that survive the deferred/loaded filter, so only those names
370
+ // close the turn; a stale or already-loaded name leaves the result plain
371
+ // and the pending use resumable.
372
+ let emitsReferences = false;
373
+ for (const name of message.addedToolNames ?? []) {
374
+ const normalizedName = normalizeToolName(name);
375
+ if (!deferredToolNames.has(normalizedName) || loadedReferenceNames.has(normalizedName))
376
+ continue;
377
+ loadedReferenceNames.add(normalizedName);
378
+ emitsReferences = true;
379
+ }
380
+ if (emitsReferences)
381
+ pendingUseIds = new Set();
382
+ continue;
383
+ }
384
+ if (message.role === "user" && userMessageClosesServerTurn(message)) {
385
+ pendingUseIds = new Set();
386
+ }
387
+ }
388
+ // Whatever is still pending at the end of history belongs to the last
389
+ // assistant message and can still resume, so it is live rather than unpaired.
390
+ return { resolvedUseIds, liveUseIds: pendingUseIds, validResultIds };
391
+ }
392
+ // True for a server-tool block whose counterpart can never arrive: a use that
393
+ // is neither answered nor resumable, or a result whose use is nowhere.
394
+ // Blocks that pair nothing (`fallback`, `container_upload`) are never unpaired.
395
+ function isUnpairedProviderNativeToolBlock(raw, pairing) {
396
+ if (!isRecord(raw))
397
+ return false;
398
+ if (isProviderNativeToolUseBlock(raw)) {
399
+ return typeof raw.id !== "string" || !(pairing.resolvedUseIds.has(raw.id) || pairing.liveUseIds.has(raw.id));
400
+ }
401
+ const toolUseId = raw.tool_use_id;
402
+ return typeof toolUseId === "string" && !pairing.validResultIds.has(toolUseId);
403
+ }
312
404
  // tool_use ids referenced by server-tool result blocks in content[0, boundary).
313
405
  // A pre-boundary `server_tool_use` whose id is absent here is unpaired — the
314
406
  // fallback interrupted the declined attempt before its result arrived — so
@@ -1055,6 +1147,18 @@ function cannotDisableThinking(model, compat) {
1055
1147
  return true;
1056
1148
  return matchesModelMarker(model, DISABLED_THINKING_REJECTING_MODEL_MARKERS);
1057
1149
  }
1150
+ function disableThinkingForRequest(params, model, compat) {
1151
+ // A degraded/disabled turn must not retain the caller's higher effort.
1152
+ delete params.output_config;
1153
+ if (cannotDisableThinking(model, compat)) {
1154
+ delete params.thinking;
1155
+ if (supportsAdaptiveThinking(model)) {
1156
+ params.output_config = { effort: "low" };
1157
+ }
1158
+ return;
1159
+ }
1160
+ params.thinking = { type: "disabled" };
1161
+ }
1058
1162
  function supportsAdaptiveThinking(model) {
1059
1163
  if (model.compat?.forceAdaptiveThinking !== undefined) {
1060
1164
  return model.compat.forceAdaptiveThinking;
@@ -1288,20 +1392,18 @@ function buildParams(model, context, isOAuthToken, options, unsignedThinkingRepl
1288
1392
  }
1289
1393
  }
1290
1394
  else if (options?.thinkingEnabled === false) {
1291
- if (cannotDisableThinking(model, compat)) {
1292
- // These families reject `thinking.type: "disabled"` with a 400 AND default to adaptive
1293
- // thinking when the field is absent, so omitting it silently bills full reasoning for a
1294
- // "thinking off" turn. The API exposes no true off switch here, so pin the cheapest
1295
- // effort: the request stays valid and reasoning stays at the documented minimum.
1296
- if (supportsAdaptiveThinking(model)) {
1297
- params.output_config = { effort: "low" };
1298
- }
1299
- }
1300
- else {
1301
- params.thinking = { type: "disabled" };
1302
- }
1395
+ // Some adaptive families reject `thinking.type: "disabled"` and default
1396
+ // to reasoning when omitted. Keep their request valid at the cheapest
1397
+ // legal effort; use an explicit disabled block everywhere else.
1398
+ disableThinkingForRequest(params, model, compat);
1303
1399
  }
1304
1400
  }
1401
+ // Anthropic rejects a thinking-enabled request whose final assistant turn
1402
+ // contains tool_use but does not begin with a thinking block. Cross-model
1403
+ // histories lose their signed thinking blocks (demoted to text or dropped),
1404
+ // so degrade thinking for this request instead of failing every turn.
1405
+ if (params.thinking && params.thinking.type !== "disabled" && finalAssistantTurnStartsWithToolUse(params.messages))
1406
+ disableThinkingForRequest(params, model, compat);
1305
1407
  if (options?.metadata) {
1306
1408
  const userId = options.metadata.user_id;
1307
1409
  if (typeof userId === "string") {
@@ -1331,9 +1433,24 @@ function applyExtraBodyToAnthropicParams(params, extraBody) {
1331
1433
  Object.defineProperty(params, key, { value, writable: true, enumerable: true, configurable: true });
1332
1434
  }
1333
1435
  }
1334
- // Normalize tool call IDs to match Anthropic's required pattern and length
1335
- function normalizeToolCallId(id) {
1336
- return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
1436
+ /**
1437
+ * Whether the final assistant turn contains tool_use without a leading
1438
+ * thinking/redacted_thinking block. Anthropic requires thinking-enabled
1439
+ * requests to start that turn with a thinking block.
1440
+ */
1441
+ function finalAssistantTurnStartsWithToolUse(messages) {
1442
+ for (let index = messages.length - 1; index >= 0; index--) {
1443
+ const message = messages[index];
1444
+ if (message.role !== "assistant")
1445
+ continue;
1446
+ if (!Array.isArray(message.content) || message.content.length === 0)
1447
+ return false;
1448
+ const first = message.content[0];
1449
+ if (first.type === "thinking" || first.type === "redacted_thinking")
1450
+ return false;
1451
+ return message.content.some((block) => block.type === "tool_use");
1452
+ }
1453
+ return false;
1337
1454
  }
1338
1455
  function convertToolResult(msg, isOAuthToken, deferredToolNames, loadedToolNames, normalizeToolName) {
1339
1456
  const references = [];
@@ -1372,6 +1489,7 @@ function convertMessages(transformedMessages, model, isOAuthToken, cacheControl,
1372
1489
  // assistant turn below; drop their tool_results in lockstep so none dangle.
1373
1490
  const discardedFallbackToolCallIds = collectDiscardedFallbackToolCallIds(transformedMessages, model);
1374
1491
  const rejectsNativeWebSearchReplay = !getAnthropicCompat(model).supportsWebSearch;
1492
+ const providerNativeToolPairing = collectProviderNativeToolPairing(transformedMessages, model, deferredToolNames, normalizeToolName, discardedFallbackToolCallIds);
1375
1493
  for (let i = 0; i < transformedMessages.length; i++) {
1376
1494
  const msg = transformedMessages[i];
1377
1495
  if (msg.role === "user") {
@@ -1505,7 +1623,8 @@ function convertMessages(transformedMessages, model, isOAuthToken, cacheControl,
1505
1623
  else if (block.type === "providerNative") {
1506
1624
  if (isSameModel &&
1507
1625
  isReplayableAnthropicProviderNativeBlock(block.raw) &&
1508
- !(rejectsNativeWebSearchReplay && isAnthropicWebSearchReplayBlock(block.raw))) {
1626
+ !(rejectsNativeWebSearchReplay && isAnthropicWebSearchReplayBlock(block.raw)) &&
1627
+ !isUnpairedProviderNativeToolBlock(block.raw, providerNativeToolPairing)) {
1509
1628
  blocks.push(block.raw);
1510
1629
  }
1511
1630
  }