@ny4n/ai 1.2.0 → 1.3.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/LICENSE CHANGED
@@ -1,6 +1,7 @@
1
1
  MIT License
2
2
 
3
3
  Copyright (c) 2025 Mario Zechner
4
+ Copyright (c) 2026 Nyan contributors
4
5
 
5
6
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
7
  of this software and associated documentation files (the "Software"), to deal
@@ -1 +1 @@
1
- {"version":3,"file":"anthropic-messages.d.ts","sourceRoot":"","sources":["../../src/api/anthropic-messages.ts"],"names":[],"mappings":"AAAA,OAAO,SAAiC,MAAM,mBAAmB,CAAC;AAmBlE,OAAO,KAAK,EASX,mBAAmB,EAEnB,cAAc,EACd,aAAa,EAMb,MAAM,aAAa,CAAC;AAoJrB,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAE1E,MAAM,MAAM,wBAAwB,GAAG,YAAY,GAAG,SAAS,CAAC;AA2ChE,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;AAwRD,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,oBAAoB,EAAE,gBAAgB,CA0UzE,CAAC;AA2BF,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,oBAAoB,EAAE,mBAAmB,CAgDlF,CAAC"}
1
+ {"version":3,"file":"anthropic-messages.d.ts","sourceRoot":"","sources":["../../src/api/anthropic-messages.ts"],"names":[],"mappings":"AAAA,OAAO,SAAiC,MAAM,mBAAmB,CAAC;AAmBlE,OAAO,KAAK,EASX,mBAAmB,EAEnB,cAAc,EACd,aAAa,EAMb,MAAM,aAAa,CAAC;AAoJrB,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAE1E,MAAM,MAAM,wBAAwB,GAAG,YAAY,GAAG,SAAS,CAAC;AA2ChE,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;AAwRD,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,oBAAoB,EAAE,gBAAgB,CA2WzE,CAAC;AA2BF,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,oBAAoB,EAAE,mBAAmB,CAgDlF,CAAC"}
@@ -425,21 +425,53 @@ export const stream = (model, context, options) => {
425
425
  client = created.client;
426
426
  isOAuth = created.isOAuthToken;
427
427
  }
428
- let params = buildParams(model, normalizedContext, isOAuth, options);
429
- const nextParams = await options?.onPayload?.(params, model);
430
- if (nextParams !== undefined) {
431
- params = { ...nextParams, stream: true };
432
- }
433
428
  const requestOptions = {
434
429
  ...(options?.signal ? { signal: options.signal } : {}),
435
430
  ...(options?.timeoutMs !== undefined ? { timeout: options.timeoutMs } : {}),
436
431
  maxRetries: 0,
437
432
  };
438
- const response = await retryProviderRequest(() => client.beta.messages.create(params, requestOptions).asResponse(), {
439
- maxRetries: options?.maxRetries,
440
- maxRetryDelayMs: options?.maxRetryDelayMs,
441
- signal: options?.signal,
442
- });
433
+ let disablePreferredStrictTools = false;
434
+ let response;
435
+ for (;;) {
436
+ options?.signal?.throwIfAborted();
437
+ const built = buildParams(model, normalizedContext, isOAuth, options, disablePreferredStrictTools);
438
+ let params = built.params;
439
+ const nextParams = await options?.onPayload?.(params, model);
440
+ if (nextParams !== undefined) {
441
+ params = { ...nextParams, stream: true };
442
+ }
443
+ try {
444
+ response = await retryProviderRequest(() => client.beta.messages.create(params, requestOptions).asResponse(), {
445
+ maxRetries: options?.maxRetries,
446
+ maxRetryDelayMs: options?.maxRetryDelayMs,
447
+ signal: options?.signal,
448
+ });
449
+ break;
450
+ }
451
+ catch (error) {
452
+ // Only downgrade a rejected request, never an already-started stream.
453
+ if (options?.signal?.aborted ||
454
+ disablePreferredStrictTools ||
455
+ !built.hasPreferredStrictTools ||
456
+ !(error instanceof Anthropic.APIError) ||
457
+ error.status !== 400 ||
458
+ error.type !== "invalid_request_error" ||
459
+ ![
460
+ "The compiled grammar is too large",
461
+ "Too many strict tools (",
462
+ "Schemas contains too many parameters with union types (",
463
+ ].some((message) => error.message.includes(message))) {
464
+ throw error;
465
+ }
466
+ disablePreferredStrictTools = true;
467
+ appendAssistantMessageDiagnostic(output, {
468
+ type: "anthropic_strict_tools_fallback",
469
+ timestamp: Date.now(),
470
+ error: { message: error.message },
471
+ details: { reason: "strict_tool_limits_exceeded" },
472
+ });
473
+ }
474
+ }
443
475
  await options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);
444
476
  stream.push({ type: "start", partial: output });
445
477
  const blocks = output.content;
@@ -855,7 +887,7 @@ function getBetaFeatures(model, context, isOAuthToken, nativeToolChanges, option
855
887
  features.push(INLINE_TOOLS_BETA);
856
888
  return [...new Set(features)];
857
889
  }
858
- function buildParams(model, context, isOAuthToken, options) {
890
+ function buildParams(model, context, isOAuthToken, options, disablePreferredStrictTools = false) {
859
891
  const { cacheControl } = getCacheControl(model, options?.cacheRetention, options?.env);
860
892
  const compat = getAnthropicCompat(model);
861
893
  const initialSystemMessage = getInitialSystemMessage(context.messages);
@@ -866,11 +898,20 @@ function buildParams(model, context, isOAuthToken, options) {
866
898
  // by value in a `tool_addition` block, which also expresses same-name redefinitions.
867
899
  // Anthropic rejects a tool list where every tool is deferred, so there must be an initial
868
900
  // active tool to anchor the placeholder. Otherwise the current tool list is sent.
901
+ let hasPreferredStrictTools = false;
902
+ const convertToolDefinitions = (tools, toolCacheControl) => {
903
+ const definitions = convertTools(tools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools, toolCacheControl, disablePreferredStrictTools);
904
+ if (definitions.some((definition, index) => {
905
+ const config = tools[index].constrainedSampling;
906
+ return definition.strict === true && config && config.type === "json_schema" && config.strict === "prefer";
907
+ })) {
908
+ hasPreferredStrictTools = true;
909
+ }
910
+ return definitions;
911
+ };
869
912
  const initialTools = initialSystemMessage?.toolsAdded ?? [];
870
913
  const nativeToolChanges = compat.supportsMidConvoSystemMessages && compat.supportsMidConvoToolChanges && initialTools.length > 0;
871
- const converted = convertMessages(conversationMessages, isOAuthToken, cacheControl, compat.allowEmptySignature, model.compat?.supportsMidConvoEffort === true ? model.provider : undefined, nativeToolChanges
872
- ? (tools) => convertTools(tools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools)
873
- : undefined);
914
+ const converted = convertMessages(conversationMessages, isOAuthToken, cacheControl, compat.allowEmptySignature, model.compat?.supportsMidConvoEffort === true ? model.provider : undefined, nativeToolChanges ? convertToolDefinitions : undefined);
874
915
  const activeEffort = options?.effort ?? "high";
875
916
  const betaFeatures = getBetaFeatures(model, context, isOAuthToken, nativeToolChanges, options);
876
917
  const params = {
@@ -922,15 +963,12 @@ function buildParams(model, context, isOAuthToken, options) {
922
963
  // placeholder. The list never changes afterwards: later tools are defined by value in
923
964
  // `tool_addition` blocks and withdrawn by `tool_removal`, so the cached prefix survives
924
965
  // every tool change.
925
- params.tools = [
926
- ...convertTools(initialTools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools, toolCacheControl),
927
- DEFERRED_TOOL_PLACEHOLDER,
928
- ];
966
+ params.tools = [...convertToolDefinitions(initialTools, toolCacheControl), DEFERRED_TOOL_PLACEHOLDER];
929
967
  }
930
968
  else {
931
969
  const tools = getCurrentTools(context.messages);
932
970
  if (tools.length > 0) {
933
- params.tools = convertTools(tools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools, toolCacheControl);
971
+ params.tools = convertToolDefinitions(tools, toolCacheControl);
934
972
  }
935
973
  }
936
974
  // Managed effort models always use adaptive thinking so prefix mismatches can
@@ -986,17 +1024,24 @@ function buildParams(model, context, isOAuthToken, options) {
986
1024
  if (allowedFallbackModels && allowedFallbackModels.length > 0) {
987
1025
  params.fallbacks = allowedFallbackModels.map((fallback) => ({ model: fallback.model }));
988
1026
  }
989
- return params;
1027
+ return { params, hasPreferredStrictTools };
990
1028
  }
991
1029
  // Normalize tool call IDs to match Anthropic's required pattern and length
992
1030
  function normalizeToolCallId(id) {
993
1031
  return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
994
1032
  }
995
1033
  function convertToolResult(msg) {
1034
+ // Anthropic rejects error tool results that contain anything but text, so images are replaced
1035
+ // with a note.
1036
+ const content = msg.isError
1037
+ ? msg.content.map((block) => block.type === "image"
1038
+ ? { type: "text", text: "[image omitted: error tool results cannot contain images]" }
1039
+ : block)
1040
+ : msg.content;
996
1041
  return {
997
1042
  type: "tool_result",
998
1043
  tool_use_id: msg.toolCallId,
999
- content: convertContentBlocks(msg.content),
1044
+ content: convertContentBlocks(content),
1000
1045
  is_error: msg.isError,
1001
1046
  };
1002
1047
  }
@@ -1255,11 +1300,14 @@ const isAnthropicStrictUnsupportedKeyword = (key, value) => {
1255
1300
  return typeof value !== "string" || !ANTHROPIC_STRICT_STRING_FORMATS.has(value);
1256
1301
  return false;
1257
1302
  };
1258
- function convertTools(tools, isOAuthToken, supportsEagerToolInputStreaming, supportsStrictTools, cacheControl) {
1303
+ function convertTools(tools, isOAuthToken, supportsEagerToolInputStreaming, supportsStrictTools, cacheControl, disablePreferredStrictTools = false) {
1259
1304
  if (!tools)
1260
1305
  return [];
1261
1306
  return tools.map((tool, index) => {
1262
- const strict = resolveJsonSchemaStrictSampling(tool, supportsStrictTools, isAnthropicStrictUnsupportedKeyword);
1307
+ const config = tool.constrainedSampling;
1308
+ const strict = disablePreferredStrictTools && config && config.type === "json_schema" && config.strict === "prefer"
1309
+ ? undefined
1310
+ : resolveJsonSchemaStrictSampling(tool, supportsStrictTools, isAnthropicStrictUnsupportedKeyword);
1263
1311
  const parameters = getJsonSchemaToolParameters(tool, strict);
1264
1312
  const schema = parameters;
1265
1313
  const legacyInputSchema = {