@librechat/agents 3.2.36 → 3.2.38

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 (62) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +1 -1
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/graphs/Graph.cjs +7 -8
  4. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  5. package/dist/cjs/langfuse.cjs +16 -5
  6. package/dist/cjs/langfuse.cjs.map +1 -1
  7. package/dist/cjs/langfuseToolOutputTracing.cjs +7 -0
  8. package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
  9. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +92 -3
  10. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  11. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +24 -4
  12. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
  13. package/dist/cjs/main.cjs +2 -0
  14. package/dist/cjs/messages/cache.cjs +183 -0
  15. package/dist/cjs/messages/cache.cjs.map +1 -1
  16. package/dist/cjs/summarization/node.cjs +1 -1
  17. package/dist/cjs/summarization/node.cjs.map +1 -1
  18. package/dist/cjs/tools/toolOutputReferences.cjs +28 -14
  19. package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
  20. package/dist/esm/agents/AgentContext.mjs +2 -2
  21. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  22. package/dist/esm/graphs/Graph.mjs +8 -9
  23. package/dist/esm/graphs/Graph.mjs.map +1 -1
  24. package/dist/esm/langfuse.mjs +16 -5
  25. package/dist/esm/langfuse.mjs.map +1 -1
  26. package/dist/esm/langfuseToolOutputTracing.mjs +7 -0
  27. package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
  28. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +92 -3
  29. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  30. package/dist/esm/llm/bedrock/utils/message_inputs.mjs +24 -4
  31. package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
  32. package/dist/esm/main.mjs +2 -2
  33. package/dist/esm/messages/cache.mjs +182 -1
  34. package/dist/esm/messages/cache.mjs.map +1 -1
  35. package/dist/esm/summarization/node.mjs +2 -2
  36. package/dist/esm/summarization/node.mjs.map +1 -1
  37. package/dist/esm/tools/toolOutputReferences.mjs +28 -14
  38. package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
  39. package/dist/types/messages/cache.d.ts +40 -0
  40. package/dist/types/types/graph.d.ts +2 -0
  41. package/package.json +8 -5
  42. package/src/agents/AgentContext.ts +2 -2
  43. package/src/agents/__tests__/AgentContext.test.ts +3 -9
  44. package/src/graphs/Graph.ts +65 -36
  45. package/src/langfuse.ts +38 -4
  46. package/src/langfuseToolOutputTracing.ts +18 -0
  47. package/src/llm/anthropic/utils/message_inputs.ts +131 -3
  48. package/src/llm/anthropic/utils/stripPrefillCache.test.ts +111 -0
  49. package/src/llm/bedrock/utils/message_inputs.test.ts +129 -0
  50. package/src/llm/bedrock/utils/message_inputs.ts +46 -4
  51. package/src/llm/bedrock/utils/toolResultCachePoint.test.ts +103 -0
  52. package/src/messages/cache.tail.test.ts +340 -0
  53. package/src/messages/cache.ts +266 -0
  54. package/src/messages/tailCacheConversion.test.ts +161 -0
  55. package/src/scripts/bench-prompt-cache.ts +479 -0
  56. package/src/specs/langfuse-config.test.ts +69 -2
  57. package/src/specs/langfuse-metadata.test.ts +44 -0
  58. package/src/specs/langfuse-tool-output-tracing.test.ts +6 -0
  59. package/src/summarization/node.ts +2 -2
  60. package/src/tools/__tests__/annotateMessagesForLLM.test.ts +50 -0
  61. package/src/tools/toolOutputReferences.ts +34 -20
  62. package/src/types/graph.ts +2 -0
@@ -37,6 +37,17 @@ function createLangfuseTraceMetadata({ messageId, parentMessageId, agentId, agen
37
37
  agentName
38
38
  });
39
39
  }
40
+ function mergeLangfuseTraceMetadata(traceMetadata, metadata) {
41
+ const merged = createTraceMetadata({
42
+ ...metadata ?? {},
43
+ ...traceMetadata ?? {}
44
+ });
45
+ return Object.keys(merged).length > 0 ? merged : void 0;
46
+ }
47
+ function mergeLangfuseTags(tags, configTags) {
48
+ const merged = [...tags ?? [], ...configTags ?? []].filter((tag) => tag.trim() !== "");
49
+ return merged.length > 0 ? [...new Set(merged)] : void 0;
50
+ }
40
51
  function getLangfuseTraceName(traceMetadata, fallback = "LibreChat Agent") {
41
52
  const agentName = traceMetadata?.agentName;
42
53
  return isPresent(agentName) ? `${fallback}: ${agentName}` : fallback;
@@ -56,17 +67,17 @@ function createLangfuseHandler({ langfuse, userId, sessionId, traceMetadata, tag
56
67
  return new CallbackHandler({
57
68
  userId,
58
69
  sessionId,
59
- traceMetadata,
60
- tags
70
+ traceMetadata: mergeLangfuseTraceMetadata(traceMetadata, langfuse?.metadata),
71
+ tags: mergeLangfuseTags(tags, langfuse?.tags)
61
72
  });
62
73
  }
63
- function createPropagateAttributeParams({ userId, sessionId, traceMetadata, traceName, tags }) {
74
+ function createPropagateAttributeParams({ langfuse, userId, sessionId, traceMetadata, traceName, tags }) {
64
75
  return {
65
76
  userId,
66
77
  sessionId,
67
78
  traceName,
68
- tags,
69
- metadata: traceMetadata
79
+ tags: mergeLangfuseTags(tags, langfuse?.tags),
80
+ metadata: mergeLangfuseTraceMetadata(traceMetadata, langfuse?.metadata)
70
81
  };
71
82
  }
72
83
  function withLangfuseAttributes(params, action) {
@@ -1 +1 @@
1
- {"version":3,"file":"langfuse.mjs","names":[],"sources":["../../src/langfuse.ts"],"sourcesContent":["import { CallbackHandler } from '@langfuse/langchain';\nimport {\n getLangfuseTracerProvider,\n propagateAttributes,\n} from '@langfuse/tracing';\nimport type { PropagateAttributesParams } from '@langfuse/tracing';\nimport type * as t from '@/types';\nimport { isPresent } from '@/utils/misc';\n\nconst TRACE_METADATA_MAX_LENGTH = 200;\nconst LANGFUSE_FORCE_FLUSH_ON_DISPOSE = 'LANGFUSE_FORCE_FLUSH_ON_DISPOSE';\n\nexport type LangfuseTraceMetadata = Record<string, string>;\n\ntype LangfuseHandlerParams = {\n userId?: string;\n sessionId?: string;\n traceMetadata?: LangfuseTraceMetadata;\n tags?: string[];\n};\n\ntype AgentLangfuseHandlerParams = LangfuseHandlerParams & {\n langfuse?: t.LangfuseConfig;\n};\n\ntype LangfuseAttributeParams = AgentLangfuseHandlerParams & {\n traceName?: string;\n};\n\ntype FlushableTracerProvider = {\n forceFlush?: () => Promise<void> | void;\n};\n\nfunction parseBooleanEnv(value?: string): boolean {\n if (value == null) {\n return false;\n }\n return ['1', 'true', 'yes', 'on'].includes(value.trim().toLowerCase());\n}\n\nfunction hasLangfuseTracingConfig(langfuse?: t.LangfuseConfig): boolean {\n return (\n langfuse?.toolNodeTracing != null || langfuse?.toolOutputTracing != null\n );\n}\n\nexport function hasLangfuseConfigCredentials(\n langfuse?: t.LangfuseConfig\n): langfuse is t.LangfuseConfig & {\n publicKey: string;\n secretKey: string;\n} {\n return (\n langfuse != null &&\n isPresent(langfuse.publicKey) &&\n isPresent(langfuse.secretKey)\n );\n}\n\nfunction hasLangfuseConfigBaseUrl(langfuse?: t.LangfuseConfig): boolean {\n return isPresent(langfuse?.baseUrl);\n}\n\nexport function isExplicitLangfuseConfig(langfuse?: t.LangfuseConfig): boolean {\n return (\n langfuse?.enabled != null ||\n isPresent(langfuse?.publicKey) ||\n isPresent(langfuse?.secretKey) ||\n isPresent(langfuse?.baseUrl) ||\n hasLangfuseTracingConfig(langfuse)\n );\n}\n\nfunction createTraceMetadata(\n metadata: Record<string, unknown>\n): LangfuseTraceMetadata {\n const traceMetadata: LangfuseTraceMetadata = {};\n for (const [key, value] of Object.entries(metadata)) {\n if (value == null) {\n continue;\n }\n const stringValue = typeof value === 'string' ? value : String(value);\n if (\n stringValue.trim() === '' ||\n stringValue.length > TRACE_METADATA_MAX_LENGTH\n ) {\n continue;\n }\n traceMetadata[key] = stringValue;\n }\n return traceMetadata;\n}\n\nexport function createLangfuseTraceMetadata({\n messageId,\n parentMessageId,\n agentId,\n agentName,\n}: {\n messageId?: unknown;\n parentMessageId?: unknown;\n agentId?: unknown;\n agentName?: unknown;\n}): LangfuseTraceMetadata {\n return createTraceMetadata({\n messageId,\n parentMessageId,\n agentId,\n agentName,\n });\n}\n\nexport function getLangfuseTraceName(\n traceMetadata?: LangfuseTraceMetadata,\n fallback: string = 'LibreChat Agent'\n): string {\n const agentName = traceMetadata?.agentName;\n return isPresent(agentName) ? `${fallback}: ${agentName}` : fallback;\n}\n\nexport function hasLangfuseEnvConfig(): boolean {\n return hasLangfuseEnvCredentials();\n}\n\nexport function hasLangfuseEnvCredentials(): boolean {\n return (\n isPresent(process.env.LANGFUSE_SECRET_KEY) &&\n isPresent(process.env.LANGFUSE_PUBLIC_KEY)\n );\n}\n\nexport function shouldCreateLangfuseHandler(\n langfuse?: t.LangfuseConfig\n): boolean {\n if (langfuse?.enabled === false) {\n return false;\n }\n return (\n hasLangfuseEnvConfig() ||\n hasLangfuseConfigCredentials(langfuse) ||\n (hasLangfuseConfigBaseUrl(langfuse) && hasLangfuseEnvCredentials())\n );\n}\n\nexport function createLegacyLangfuseHandler(\n params: LangfuseHandlerParams\n): CallbackHandler {\n return new CallbackHandler(params);\n}\n\nexport function createLangfuseHandler({\n langfuse,\n userId,\n sessionId,\n traceMetadata,\n tags,\n}: AgentLangfuseHandlerParams): CallbackHandler | undefined {\n if (!shouldCreateLangfuseHandler(langfuse)) {\n return undefined;\n }\n return new CallbackHandler({\n userId,\n sessionId,\n traceMetadata,\n tags,\n });\n}\n\nfunction createPropagateAttributeParams({\n userId,\n sessionId,\n traceMetadata,\n traceName,\n tags,\n}: LangfuseAttributeParams): PropagateAttributesParams {\n return {\n userId,\n sessionId,\n traceName,\n tags,\n metadata: traceMetadata,\n };\n}\n\nexport function withLangfuseAttributes<T>(\n params: LangfuseAttributeParams,\n action: () => T\n): T {\n if (!shouldCreateLangfuseHandler(params.langfuse)) {\n return action();\n }\n return propagateAttributes(createPropagateAttributeParams(params), action);\n}\n\nexport function hasExplicitLangfuseConfig(\n contexts: Iterable<{ langfuse?: t.LangfuseConfig }>\n): boolean {\n for (const context of contexts) {\n if (isExplicitLangfuseConfig(context.langfuse)) {\n return true;\n }\n }\n return false;\n}\n\nexport function isLangfuseCallbackHandler(value: unknown): boolean {\n return value instanceof CallbackHandler;\n}\n\nexport async function disposeLangfuseHandler(value: unknown): Promise<void> {\n if (\n value == null ||\n !parseBooleanEnv(process.env[LANGFUSE_FORCE_FLUSH_ON_DISPOSE])\n ) {\n return;\n }\n const provider = getLangfuseTracerProvider() as FlushableTracerProvider;\n await provider.forceFlush?.();\n}\n"],"mappings":";;;;AASA,MAAM,4BAA4B;AAClC,MAAM,kCAAkC;AAuBxC,SAAS,gBAAgB,OAAyB;CAChD,IAAI,SAAS,MACX,OAAO;CAET,OAAO;EAAC;EAAK;EAAQ;EAAO;CAAI,CAAC,CAAC,SAAS,MAAM,KAAK,CAAC,CAAC,YAAY,CAAC;AACvE;AAQA,SAAgB,6BACd,UAIA;CACA,OACE,YAAY,QACZ,UAAU,SAAS,SAAS,KAC5B,UAAU,SAAS,SAAS;AAEhC;AAEA,SAAS,yBAAyB,UAAsC;CACtE,OAAO,UAAU,UAAU,OAAO;AACpC;AAYA,SAAS,oBACP,UACuB;CACvB,MAAM,gBAAuC,CAAC;CAC9C,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,GAAG;EACnD,IAAI,SAAS,MACX;EAEF,MAAM,cAAc,OAAO,UAAU,WAAW,QAAQ,OAAO,KAAK;EACpE,IACE,YAAY,KAAK,MAAM,MACvB,YAAY,SAAS,2BAErB;EAEF,cAAc,OAAO;CACvB;CACA,OAAO;AACT;AAEA,SAAgB,4BAA4B,EAC1C,WACA,iBACA,SACA,aAMwB;CACxB,OAAO,oBAAoB;EACzB;EACA;EACA;EACA;CACF,CAAC;AACH;AAEA,SAAgB,qBACd,eACA,WAAmB,mBACX;CACR,MAAM,YAAY,eAAe;CACjC,OAAO,UAAU,SAAS,IAAI,GAAG,SAAS,IAAI,cAAc;AAC9D;AAEA,SAAgB,uBAAgC;CAC9C,OAAO,0BAA0B;AACnC;AAEA,SAAgB,4BAAqC;CACnD,OACE,UAAU,QAAQ,IAAI,mBAAmB,KACzC,UAAU,QAAQ,IAAI,mBAAmB;AAE7C;AAEA,SAAgB,4BACd,UACS;CACT,IAAI,UAAU,YAAY,OACxB,OAAO;CAET,OACE,qBAAqB,KACrB,6BAA6B,QAAQ,KACpC,yBAAyB,QAAQ,KAAK,0BAA0B;AAErE;AAQA,SAAgB,sBAAsB,EACpC,UACA,QACA,WACA,eACA,QAC0D;CAC1D,IAAI,CAAC,4BAA4B,QAAQ,GACvC;CAEF,OAAO,IAAI,gBAAgB;EACzB;EACA;EACA;EACA;CACF,CAAC;AACH;AAEA,SAAS,+BAA+B,EACtC,QACA,WACA,eACA,WACA,QACqD;CACrD,OAAO;EACL;EACA;EACA;EACA;EACA,UAAU;CACZ;AACF;AAEA,SAAgB,uBACd,QACA,QACG;CACH,IAAI,CAAC,4BAA4B,OAAO,QAAQ,GAC9C,OAAO,OAAO;CAEhB,OAAO,oBAAoB,+BAA+B,MAAM,GAAG,MAAM;AAC3E;AAaA,SAAgB,0BAA0B,OAAyB;CACjE,OAAO,iBAAiB;AAC1B;AAEA,eAAsB,uBAAuB,OAA+B;CAC1E,IACE,SAAS,QACT,CAAC,gBAAgB,QAAQ,IAAI,gCAAgC,GAE7D;CAGF,MADiB,0BACJ,CAAC,CAAC,aAAa;AAC9B"}
1
+ {"version":3,"file":"langfuse.mjs","names":[],"sources":["../../src/langfuse.ts"],"sourcesContent":["import { CallbackHandler } from '@langfuse/langchain';\nimport {\n getLangfuseTracerProvider,\n propagateAttributes,\n} from '@langfuse/tracing';\nimport type { PropagateAttributesParams } from '@langfuse/tracing';\nimport type * as t from '@/types';\nimport { isPresent } from '@/utils/misc';\n\nconst TRACE_METADATA_MAX_LENGTH = 200;\nconst LANGFUSE_FORCE_FLUSH_ON_DISPOSE = 'LANGFUSE_FORCE_FLUSH_ON_DISPOSE';\n\nexport type LangfuseTraceMetadata = Record<string, string>;\ntype LangfuseMetadata = NonNullable<t.LangfuseConfig['metadata']>;\n\ntype LangfuseHandlerParams = {\n userId?: string;\n sessionId?: string;\n traceMetadata?: LangfuseTraceMetadata;\n tags?: string[];\n};\n\ntype AgentLangfuseHandlerParams = LangfuseHandlerParams & {\n langfuse?: t.LangfuseConfig;\n};\n\ntype LangfuseAttributeParams = AgentLangfuseHandlerParams & {\n traceName?: string;\n};\n\ntype FlushableTracerProvider = {\n forceFlush?: () => Promise<void> | void;\n};\n\nfunction parseBooleanEnv(value?: string): boolean {\n if (value == null) {\n return false;\n }\n return ['1', 'true', 'yes', 'on'].includes(value.trim().toLowerCase());\n}\n\nfunction hasLangfuseTracingConfig(langfuse?: t.LangfuseConfig): boolean {\n return (\n langfuse?.toolNodeTracing != null || langfuse?.toolOutputTracing != null\n );\n}\n\nfunction hasLangfuseTraceAttributes(langfuse?: t.LangfuseConfig): boolean {\n return (\n Object.keys(createTraceMetadata(langfuse?.metadata ?? {})).length > 0 ||\n (mergeLangfuseTags(undefined, langfuse?.tags)?.length ?? 0) > 0\n );\n}\n\nexport function hasLangfuseConfigCredentials(\n langfuse?: t.LangfuseConfig\n): langfuse is t.LangfuseConfig & {\n publicKey: string;\n secretKey: string;\n} {\n return (\n langfuse != null &&\n isPresent(langfuse.publicKey) &&\n isPresent(langfuse.secretKey)\n );\n}\n\nfunction hasLangfuseConfigBaseUrl(langfuse?: t.LangfuseConfig): boolean {\n return isPresent(langfuse?.baseUrl);\n}\n\nexport function isExplicitLangfuseConfig(langfuse?: t.LangfuseConfig): boolean {\n return (\n langfuse?.enabled != null ||\n isPresent(langfuse?.publicKey) ||\n isPresent(langfuse?.secretKey) ||\n isPresent(langfuse?.baseUrl) ||\n hasLangfuseTraceAttributes(langfuse) ||\n hasLangfuseTracingConfig(langfuse)\n );\n}\n\nfunction createTraceMetadata(\n metadata: Record<string, unknown>\n): LangfuseTraceMetadata {\n const traceMetadata: LangfuseTraceMetadata = {};\n for (const [key, value] of Object.entries(metadata)) {\n if (value == null) {\n continue;\n }\n const stringValue = typeof value === 'string' ? value : String(value);\n if (\n stringValue.trim() === '' ||\n stringValue.length > TRACE_METADATA_MAX_LENGTH\n ) {\n continue;\n }\n traceMetadata[key] = stringValue;\n }\n return traceMetadata;\n}\n\nexport function createLangfuseTraceMetadata({\n messageId,\n parentMessageId,\n agentId,\n agentName,\n}: {\n messageId?: unknown;\n parentMessageId?: unknown;\n agentId?: unknown;\n agentName?: unknown;\n}): LangfuseTraceMetadata {\n return createTraceMetadata({\n messageId,\n parentMessageId,\n agentId,\n agentName,\n });\n}\n\nfunction mergeLangfuseTraceMetadata(\n traceMetadata?: LangfuseTraceMetadata,\n metadata?: LangfuseMetadata\n): LangfuseTraceMetadata | undefined {\n const merged = createTraceMetadata({\n ...(metadata ?? {}),\n ...(traceMetadata ?? {}),\n });\n return Object.keys(merged).length > 0 ? merged : undefined;\n}\n\nfunction mergeLangfuseTags(\n tags?: string[],\n configTags?: string[]\n): string[] | undefined {\n const merged = [...(tags ?? []), ...(configTags ?? [])].filter(\n (tag) => tag.trim() !== ''\n );\n return merged.length > 0 ? [...new Set(merged)] : undefined;\n}\n\nexport function getLangfuseTraceName(\n traceMetadata?: LangfuseTraceMetadata,\n fallback: string = 'LibreChat Agent'\n): string {\n const agentName = traceMetadata?.agentName;\n return isPresent(agentName) ? `${fallback}: ${agentName}` : fallback;\n}\n\nexport function hasLangfuseEnvConfig(): boolean {\n return hasLangfuseEnvCredentials();\n}\n\nexport function hasLangfuseEnvCredentials(): boolean {\n return (\n isPresent(process.env.LANGFUSE_SECRET_KEY) &&\n isPresent(process.env.LANGFUSE_PUBLIC_KEY)\n );\n}\n\nexport function shouldCreateLangfuseHandler(\n langfuse?: t.LangfuseConfig\n): boolean {\n if (langfuse?.enabled === false) {\n return false;\n }\n return (\n hasLangfuseEnvConfig() ||\n hasLangfuseConfigCredentials(langfuse) ||\n (hasLangfuseConfigBaseUrl(langfuse) && hasLangfuseEnvCredentials())\n );\n}\n\nexport function createLegacyLangfuseHandler(\n params: LangfuseHandlerParams\n): CallbackHandler {\n return new CallbackHandler(params);\n}\n\nexport function createLangfuseHandler({\n langfuse,\n userId,\n sessionId,\n traceMetadata,\n tags,\n}: AgentLangfuseHandlerParams): CallbackHandler | undefined {\n if (!shouldCreateLangfuseHandler(langfuse)) {\n return undefined;\n }\n return new CallbackHandler({\n userId,\n sessionId,\n traceMetadata: mergeLangfuseTraceMetadata(\n traceMetadata,\n langfuse?.metadata\n ),\n tags: mergeLangfuseTags(tags, langfuse?.tags),\n });\n}\n\nfunction createPropagateAttributeParams({\n langfuse,\n userId,\n sessionId,\n traceMetadata,\n traceName,\n tags,\n}: LangfuseAttributeParams): PropagateAttributesParams {\n return {\n userId,\n sessionId,\n traceName,\n tags: mergeLangfuseTags(tags, langfuse?.tags),\n metadata: mergeLangfuseTraceMetadata(traceMetadata, langfuse?.metadata),\n };\n}\n\nexport function withLangfuseAttributes<T>(\n params: LangfuseAttributeParams,\n action: () => T\n): T {\n if (!shouldCreateLangfuseHandler(params.langfuse)) {\n return action();\n }\n return propagateAttributes(createPropagateAttributeParams(params), action);\n}\n\nexport function hasExplicitLangfuseConfig(\n contexts: Iterable<{ langfuse?: t.LangfuseConfig }>\n): boolean {\n for (const context of contexts) {\n if (isExplicitLangfuseConfig(context.langfuse)) {\n return true;\n }\n }\n return false;\n}\n\nexport function isLangfuseCallbackHandler(value: unknown): boolean {\n return value instanceof CallbackHandler;\n}\n\nexport async function disposeLangfuseHandler(value: unknown): Promise<void> {\n if (\n value == null ||\n !parseBooleanEnv(process.env[LANGFUSE_FORCE_FLUSH_ON_DISPOSE])\n ) {\n return;\n }\n const provider = getLangfuseTracerProvider() as FlushableTracerProvider;\n await provider.forceFlush?.();\n}\n"],"mappings":";;;;AASA,MAAM,4BAA4B;AAClC,MAAM,kCAAkC;AAwBxC,SAAS,gBAAgB,OAAyB;CAChD,IAAI,SAAS,MACX,OAAO;CAET,OAAO;EAAC;EAAK;EAAQ;EAAO;CAAI,CAAC,CAAC,SAAS,MAAM,KAAK,CAAC,CAAC,YAAY,CAAC;AACvE;AAeA,SAAgB,6BACd,UAIA;CACA,OACE,YAAY,QACZ,UAAU,SAAS,SAAS,KAC5B,UAAU,SAAS,SAAS;AAEhC;AAEA,SAAS,yBAAyB,UAAsC;CACtE,OAAO,UAAU,UAAU,OAAO;AACpC;AAaA,SAAS,oBACP,UACuB;CACvB,MAAM,gBAAuC,CAAC;CAC9C,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,GAAG;EACnD,IAAI,SAAS,MACX;EAEF,MAAM,cAAc,OAAO,UAAU,WAAW,QAAQ,OAAO,KAAK;EACpE,IACE,YAAY,KAAK,MAAM,MACvB,YAAY,SAAS,2BAErB;EAEF,cAAc,OAAO;CACvB;CACA,OAAO;AACT;AAEA,SAAgB,4BAA4B,EAC1C,WACA,iBACA,SACA,aAMwB;CACxB,OAAO,oBAAoB;EACzB;EACA;EACA;EACA;CACF,CAAC;AACH;AAEA,SAAS,2BACP,eACA,UACmC;CACnC,MAAM,SAAS,oBAAoB;EACjC,GAAI,YAAY,CAAC;EACjB,GAAI,iBAAiB,CAAC;CACxB,CAAC;CACD,OAAO,OAAO,KAAK,MAAM,CAAC,CAAC,SAAS,IAAI,SAAS,KAAA;AACnD;AAEA,SAAS,kBACP,MACA,YACsB;CACtB,MAAM,SAAS,CAAC,GAAI,QAAQ,CAAC,GAAI,GAAI,cAAc,CAAC,CAAE,CAAC,CAAC,QACrD,QAAQ,IAAI,KAAK,MAAM,EAC1B;CACA,OAAO,OAAO,SAAS,IAAI,CAAC,GAAG,IAAI,IAAI,MAAM,CAAC,IAAI,KAAA;AACpD;AAEA,SAAgB,qBACd,eACA,WAAmB,mBACX;CACR,MAAM,YAAY,eAAe;CACjC,OAAO,UAAU,SAAS,IAAI,GAAG,SAAS,IAAI,cAAc;AAC9D;AAEA,SAAgB,uBAAgC;CAC9C,OAAO,0BAA0B;AACnC;AAEA,SAAgB,4BAAqC;CACnD,OACE,UAAU,QAAQ,IAAI,mBAAmB,KACzC,UAAU,QAAQ,IAAI,mBAAmB;AAE7C;AAEA,SAAgB,4BACd,UACS;CACT,IAAI,UAAU,YAAY,OACxB,OAAO;CAET,OACE,qBAAqB,KACrB,6BAA6B,QAAQ,KACpC,yBAAyB,QAAQ,KAAK,0BAA0B;AAErE;AAQA,SAAgB,sBAAsB,EACpC,UACA,QACA,WACA,eACA,QAC0D;CAC1D,IAAI,CAAC,4BAA4B,QAAQ,GACvC;CAEF,OAAO,IAAI,gBAAgB;EACzB;EACA;EACA,eAAe,2BACb,eACA,UAAU,QACZ;EACA,MAAM,kBAAkB,MAAM,UAAU,IAAI;CAC9C,CAAC;AACH;AAEA,SAAS,+BAA+B,EACtC,UACA,QACA,WACA,eACA,WACA,QACqD;CACrD,OAAO;EACL;EACA;EACA;EACA,MAAM,kBAAkB,MAAM,UAAU,IAAI;EAC5C,UAAU,2BAA2B,eAAe,UAAU,QAAQ;CACxE;AACF;AAEA,SAAgB,uBACd,QACA,QACG;CACH,IAAI,CAAC,4BAA4B,OAAO,QAAQ,GAC9C,OAAO,OAAO;CAEhB,OAAO,oBAAoB,+BAA+B,MAAM,GAAG,MAAM;AAC3E;AAaA,SAAgB,0BAA0B,OAAyB;CACjE,OAAO,iBAAiB;AAC1B;AAEA,eAAsB,uBAAuB,OAA+B;CAC1E,IACE,SAAS,QACT,CAAC,gBAAgB,QAAQ,IAAI,gCAAgC,GAE7D;CAGF,MADiB,0BACJ,CAAC,CAAC,aAAa;AAC9B"}
@@ -327,9 +327,16 @@ function resolveLangfuseConfig(runLangfuse, agentLangfuse) {
327
327
  ...runLangfuse.toolOutputTracing,
328
328
  ...agentLangfuse.toolOutputTracing
329
329
  } : void 0;
330
+ const metadata = runLangfuse.metadata != null || agentLangfuse.metadata != null ? {
331
+ ...runLangfuse.metadata,
332
+ ...agentLangfuse.metadata
333
+ } : void 0;
334
+ const tags = runLangfuse.tags != null || agentLangfuse.tags != null ? [...new Set([...runLangfuse.tags ?? [], ...agentLangfuse.tags ?? []])] : void 0;
330
335
  return {
331
336
  ...runLangfuse,
332
337
  ...agentLangfuse,
338
+ ...metadata != null ? { metadata } : {},
339
+ ...tags != null ? { tags } : {},
333
340
  ...toolNodeTracing != null ? { toolNodeTracing } : {},
334
341
  ...toolOutputTracing != null ? { toolOutputTracing } : {}
335
342
  };
@@ -1 +1 @@
1
- {"version":3,"file":"langfuseToolOutputTracing.mjs","names":[],"sources":["../../src/langfuseToolOutputTracing.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport { LangfuseSpanProcessor } from '@langfuse/otel';\nimport { context, createContextKey } from '@opentelemetry/api';\nimport { LangfuseOtelSpanAttributes } from '@langfuse/tracing';\nimport type {\n ReadableSpan,\n Span,\n SpanProcessor,\n} from '@opentelemetry/sdk-trace-base';\nimport type { LangfuseSpanProcessorParams } from '@langfuse/otel';\nimport type { Context } from '@opentelemetry/api';\nimport type * as t from '@/types';\n\nexport const LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT = '[tool output redacted]';\n\nconst langfuseToolOutputTracingConfigKey = createContextKey(\n 'librechat.langfuse.tool-output-tracing'\n);\nconst langfuseConfigKey = createContextKey('librechat.langfuse.config');\nconst toolOutputTracingStorage =\n new AsyncLocalStorage<ResolvedLangfuseToolOutputTracingConfig>();\nconst langfuseConfigStorage = new AsyncLocalStorage<t.LangfuseConfig>();\nconst LANGGRAPH_TOOL_NODE_PREFIX = 'tools=';\n\nconst CHAT_ROLES = new Set([\n 'assistant',\n 'developer',\n 'human',\n 'system',\n 'user',\n]);\n\nexport type ResolvedLangfuseToolOutputTracingConfig = {\n enabled: boolean;\n redactedToolNames: Set<string>;\n redactedToolNameMatchMode: 'exact' | 'partial';\n redactionText: string;\n};\n\ntype SpanWithAttributes = ReadableSpan & {\n attributes: Record<string, unknown>;\n};\n\ntype RedactionResult = {\n value: unknown;\n changed: boolean;\n};\n\ntype RedactionContext = {\n toolNamesByCallId: Map<string, string>;\n};\n\nconst TOOL_OUTPUT_FIELD_KEYS = ['content', 'artifact'];\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value != null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction isPresent(value: unknown): value is string {\n return typeof value === 'string' && value.trim() !== '';\n}\n\nfunction parseBoolean(value: string | undefined): boolean | undefined {\n if (value == null) {\n return undefined;\n }\n\n const normalized = value.trim().toLowerCase();\n if (['1', 'true', 'yes', 'on'].includes(normalized)) {\n return true;\n }\n if (['0', 'false', 'no', 'off'].includes(normalized)) {\n return false;\n }\n\n return undefined;\n}\n\nfunction normalizeToolName(name: string): string {\n return name.trim().toLowerCase();\n}\n\nfunction normalizeToolNames(names: string[] | undefined): Set<string> {\n const normalized = new Set<string>();\n for (const name of names ?? []) {\n if (isPresent(name)) {\n normalized.add(normalizeToolName(name));\n }\n }\n return normalized;\n}\n\nfunction parseToolNames(value: string | undefined): string[] | undefined {\n if (!isPresent(value)) {\n return undefined;\n }\n\n return value\n .split(',')\n .map((name) => name.trim())\n .filter((name) => name !== '');\n}\n\nfunction getEnvToolOutputTracingEnabled(): boolean | undefined {\n const traceToolOutputs = parseBoolean(\n process.env.LANGFUSE_TRACE_TOOL_OUTPUTS\n );\n if (traceToolOutputs != null) {\n return traceToolOutputs;\n }\n\n const redactToolOutputs = parseBoolean(\n process.env.LANGFUSE_REDACT_TOOL_OUTPUTS\n );\n if (redactToolOutputs != null) {\n return !redactToolOutputs;\n }\n\n return parseBoolean(process.env.LANGFUSE_TOOL_OUTPUT_TRACING_ENABLED);\n}\n\nfunction getEnvRedactedToolNames(): string[] | undefined {\n return (\n parseToolNames(process.env.LANGFUSE_REDACT_TOOL_OUTPUT_NAMES) ??\n parseToolNames(process.env.LANGFUSE_REDACT_TOOL_NAMES)\n );\n}\n\nfunction getEnvRedactionText(): string | undefined {\n return isPresent(process.env.LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT)\n ? process.env.LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT\n : undefined;\n}\n\nfunction getEnvToolNameMatchMode(): 'exact' | 'partial' | undefined {\n const mode = (\n process.env.LANGFUSE_REDACT_TOOL_OUTPUT_NAME_MATCH_MODE ??\n process.env.LANGFUSE_REDACT_TOOL_NAME_MATCH_MODE\n )\n ?.trim()\n .toLowerCase();\n if (mode === 'exact' || mode === 'partial') {\n return mode;\n }\n return undefined;\n}\n\nfunction resolveToolOutputTracingConfig(\n runLangfuse?: t.LangfuseConfig,\n agentLangfuse?: t.LangfuseConfig\n): ResolvedLangfuseToolOutputTracingConfig {\n const runConfig = runLangfuse?.toolOutputTracing;\n const agentConfig = agentLangfuse?.toolOutputTracing;\n\n return {\n enabled:\n agentConfig?.enabled ??\n runConfig?.enabled ??\n getEnvToolOutputTracingEnabled() ??\n true,\n redactedToolNames: normalizeToolNames(\n agentConfig?.redactedToolNames ??\n runConfig?.redactedToolNames ??\n getEnvRedactedToolNames()\n ),\n redactedToolNameMatchMode:\n agentConfig?.redactedToolNameMatchMode ??\n runConfig?.redactedToolNameMatchMode ??\n getEnvToolNameMatchMode() ??\n 'exact',\n redactionText:\n agentConfig?.redactionText ??\n runConfig?.redactionText ??\n getEnvRedactionText() ??\n LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT,\n };\n}\n\nfunction shouldApplyToolOutputRedaction(\n config: ResolvedLangfuseToolOutputTracingConfig\n): boolean {\n return config.enabled === false || config.redactedToolNames.size > 0;\n}\n\nfunction toolNameMatches(\n toolName: string | undefined,\n config: ResolvedLangfuseToolOutputTracingConfig\n): boolean {\n if (!isPresent(toolName)) {\n return false;\n }\n\n const normalizedToolName = normalizeToolName(toolName);\n if (config.redactedToolNameMatchMode === 'partial') {\n for (const redactedToolName of config.redactedToolNames) {\n if (normalizedToolName.includes(redactedToolName)) {\n return true;\n }\n }\n return false;\n }\n\n return config.redactedToolNames.has(normalizedToolName);\n}\n\nfunction shouldRedactTool(\n toolName: string | undefined,\n config: ResolvedLangfuseToolOutputTracingConfig\n): boolean {\n return config.enabled === false || toolNameMatches(toolName, config);\n}\n\nfunction getStringField(\n value: Record<string, unknown>,\n key: string\n): string | undefined {\n const field = value[key];\n return typeof field === 'string' ? field : undefined;\n}\n\nfunction getNestedStringField(\n value: Record<string, unknown>,\n objectKey: string,\n fieldKey: string\n): string | undefined {\n const nested = value[objectKey];\n if (!isRecord(nested)) {\n return undefined;\n }\n return getStringField(nested, fieldKey);\n}\n\nfunction getSerializedToolCallId(\n value: Record<string, unknown>\n): string | undefined {\n return (\n getStringField(value, 'tool_call_id') ??\n getNestedStringField(value, 'kwargs', 'tool_call_id') ??\n getNestedStringField(value, 'additional_kwargs', 'tool_call_id') ??\n getNestedStringField(value, 'data', 'tool_call_id') ??\n (typeof value.id === 'string' ? value.id : undefined)\n );\n}\n\nfunction getSerializedToolName(\n value: Record<string, unknown>,\n redactionContext?: RedactionContext\n): string | undefined {\n const role = getStringField(value, 'role');\n const explicitName =\n getStringField(value, 'name') ??\n getStringField(value, 'tool_name') ??\n getNestedStringField(value, 'function', 'name') ??\n getNestedStringField(value, 'kwargs', 'name') ??\n getNestedStringField(value, 'additional_kwargs', 'name') ??\n getNestedStringField(value, 'data', 'name') ??\n (role != null && role.toLowerCase() !== 'tool' ? role : undefined);\n\n if (explicitName != null) {\n return explicitName;\n }\n\n const toolCallId = getSerializedToolCallId(value);\n return toolCallId != null\n ? redactionContext?.toolNamesByCallId.get(toolCallId)\n : undefined;\n}\n\nfunction hasToolMessageIdentity(value: Record<string, unknown>): boolean {\n const type = getStringField(value, 'type') ?? getStringField(value, '_type');\n if (type === 'tool' || type === 'tool_message') {\n return true;\n }\n\n const id = value.id;\n if (\n Array.isArray(id) &&\n id.some((part) => typeof part === 'string' && part.includes('ToolMessage'))\n ) {\n return true;\n }\n\n if (\n 'tool_call_id' in value ||\n getNestedStringField(value, 'kwargs', 'tool_call_id') != null ||\n getNestedStringField(value, 'additional_kwargs', 'tool_call_id') != null\n ) {\n return true;\n }\n\n const role = getStringField(value, 'role');\n return (\n role != null &&\n !CHAT_ROLES.has(role.toLowerCase()) &&\n ('content' in value || isRecord(value.kwargs) || isRecord(value.data))\n );\n}\n\nfunction redactToolContentFields(\n value: Record<string, unknown>,\n config: ResolvedLangfuseToolOutputTracingConfig\n): Record<string, unknown> {\n const next = { ...value };\n\n for (const outputKey of TOOL_OUTPUT_FIELD_KEYS) {\n if (outputKey in next) {\n next[outputKey] = config.redactionText;\n }\n }\n\n for (const nestedKey of ['kwargs', 'data', 'additional_kwargs']) {\n const nested = next[nestedKey];\n if (!isRecord(nested)) {\n continue;\n }\n const nextNested = { ...nested };\n let changed = false;\n for (const outputKey of TOOL_OUTPUT_FIELD_KEYS) {\n if (outputKey in nextNested) {\n nextNested[outputKey] = config.redactionText;\n changed = true;\n }\n }\n if (changed) {\n next[nestedKey] = nextNested;\n }\n }\n\n return next;\n}\n\nfunction collectToolCallNames(\n value: unknown,\n redactionContext: RedactionContext\n): void {\n if (Array.isArray(value)) {\n for (const item of value) {\n collectToolCallNames(item, redactionContext);\n }\n return;\n }\n\n if (!isRecord(value)) {\n return;\n }\n\n const toolCallId = getSerializedToolCallId(value);\n const toolName = getSerializedToolName(value);\n if (toolCallId != null && toolName != null) {\n redactionContext.toolNamesByCallId.set(toolCallId, toolName);\n }\n\n for (const child of Object.values(value)) {\n collectToolCallNames(child, redactionContext);\n }\n}\n\nfunction redactValue(\n value: unknown,\n config: ResolvedLangfuseToolOutputTracingConfig,\n redactionContext: RedactionContext\n): RedactionResult {\n if (Array.isArray(value)) {\n let changed = false;\n const next: unknown[] = [];\n for (const item of value) {\n const result = redactValue(item, config, redactionContext);\n if (result.changed) {\n changed = true;\n }\n next.push(result.value);\n }\n return changed ? { value: next, changed } : { value, changed };\n }\n\n if (!isRecord(value)) {\n return { value, changed: false };\n }\n\n const toolName = getSerializedToolName(value, redactionContext);\n if (hasToolMessageIdentity(value) && shouldRedactTool(toolName, config)) {\n return {\n value: redactToolContentFields(value, config),\n changed: true,\n };\n }\n\n let changed = false;\n const next: Record<string, unknown> = {};\n for (const [key, child] of Object.entries(value)) {\n const result = redactValue(child, config, redactionContext);\n if (result.changed) {\n changed = true;\n }\n next[key] = result.value;\n }\n\n return changed ? { value: next, changed } : { value, changed };\n}\n\nfunction redactSerializedValue(\n value: unknown,\n config: ResolvedLangfuseToolOutputTracingConfig\n): RedactionResult {\n const redactionContext: RedactionContext = {\n toolNamesByCallId: new Map(),\n };\n if (typeof value !== 'string') {\n collectToolCallNames(value, redactionContext);\n return redactValue(value, config, redactionContext);\n }\n\n const trimmed = value.trim();\n if (!trimmed.startsWith('{') && !trimmed.startsWith('[')) {\n return { value, changed: false };\n }\n\n try {\n const parsed = JSON.parse(value) as unknown;\n collectToolCallNames(parsed, redactionContext);\n const result = redactValue(parsed, config, redactionContext);\n return result.changed\n ? { value: JSON.stringify(result.value), changed: true }\n : { value, changed: false };\n } catch {\n return { value, changed: false };\n }\n}\n\nfunction redactAttribute(\n attributes: Record<string, unknown>,\n key: string,\n config: ResolvedLangfuseToolOutputTracingConfig\n): void {\n if (!(key in attributes)) {\n return;\n }\n\n const result = redactSerializedValue(attributes[key], config);\n if (result.changed) {\n attributes[key] = result.value;\n }\n}\n\nfunction isToolObservation(attributes: Record<string, unknown>): boolean {\n const type = attributes[LangfuseOtelSpanAttributes.OBSERVATION_TYPE];\n return typeof type === 'string' && type.toLowerCase() === 'tool';\n}\n\nfunction classifyLangGraphToolNodeSpan(\n attributes: Record<string, unknown>\n): void {\n const type = attributes[LangfuseOtelSpanAttributes.OBSERVATION_TYPE];\n if (typeof type !== 'string' || type.toLowerCase() !== 'span') {\n return;\n }\n\n const langGraphNode =\n attributes[\n `${LangfuseOtelSpanAttributes.OBSERVATION_METADATA}.langgraph_node`\n ];\n if (\n typeof langGraphNode === 'string' &&\n langGraphNode.startsWith(LANGGRAPH_TOOL_NODE_PREFIX)\n ) {\n attributes[LangfuseOtelSpanAttributes.OBSERVATION_TYPE] = 'tool';\n }\n}\n\nfunction redactToolObservationOutput(\n span: ReadableSpan,\n attributes: Record<string, unknown>,\n config: ResolvedLangfuseToolOutputTracingConfig\n): void {\n if (\n !(\n isToolObservation(attributes) &&\n shouldRedactTool(span.name, config) &&\n LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT in attributes\n )\n ) {\n return;\n }\n\n attributes[LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT] =\n config.redactionText;\n}\n\nexport function redactLangfuseSpanToolOutputs(\n span: ReadableSpan,\n config: ResolvedLangfuseToolOutputTracingConfig\n): void {\n const attributes = (span as SpanWithAttributes).attributes;\n classifyLangGraphToolNodeSpan(attributes);\n\n if (!shouldApplyToolOutputRedaction(config)) {\n return;\n }\n\n redactToolObservationOutput(span, attributes, config);\n\n for (const key of [\n LangfuseOtelSpanAttributes.OBSERVATION_INPUT,\n LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT,\n LangfuseOtelSpanAttributes.TRACE_INPUT,\n LangfuseOtelSpanAttributes.TRACE_OUTPUT,\n ]) {\n redactAttribute(attributes, key, config);\n }\n}\n\nfunction getContextToolOutputTracingConfig(\n activeContext: Context\n): ResolvedLangfuseToolOutputTracingConfig | undefined {\n const asyncConfig = toolOutputTracingStorage.getStore();\n if (asyncConfig != null) {\n return asyncConfig;\n }\n\n const value = activeContext.getValue(langfuseToolOutputTracingConfigKey);\n return isRecord(value)\n ? (value as ResolvedLangfuseToolOutputTracingConfig)\n : undefined;\n}\n\nexport function getContextLangfuseConfig(\n activeContext: Context\n): t.LangfuseConfig | undefined {\n const asyncConfig = langfuseConfigStorage.getStore();\n if (asyncConfig != null) {\n return asyncConfig;\n }\n\n const value = activeContext.getValue(langfuseConfigKey);\n return isRecord(value) ? (value as t.LangfuseConfig) : undefined;\n}\n\nclass ToolOutputRedactingLangfuseSpanProcessor implements SpanProcessor {\n private readonly processor: LangfuseSpanProcessor;\n private readonly fallbackConfig?: ResolvedLangfuseToolOutputTracingConfig;\n private readonly spanConfigs = new WeakMap<\n object,\n ResolvedLangfuseToolOutputTracingConfig\n >();\n\n constructor(\n params?: LangfuseSpanProcessorParams,\n fallbackConfig?: ResolvedLangfuseToolOutputTracingConfig\n ) {\n this.processor = new LangfuseSpanProcessor(params);\n this.fallbackConfig = fallbackConfig;\n }\n\n onStart(span: Span, parentContext: Context): void {\n const config =\n getContextToolOutputTracingConfig(parentContext) ?? this.fallbackConfig;\n if (config != null) {\n this.spanConfigs.set(span, config);\n }\n this.processor.onStart(span, parentContext);\n }\n\n onEnd(span: ReadableSpan): void {\n const config =\n this.spanConfigs.get(span) ??\n toolOutputTracingStorage.getStore() ??\n this.fallbackConfig ??\n resolveToolOutputTracingConfig();\n redactLangfuseSpanToolOutputs(span, config);\n this.processor.onEnd(span);\n }\n\n forceFlush(): Promise<void> {\n return this.processor.forceFlush();\n }\n\n shutdown(): Promise<void> {\n return this.processor.shutdown();\n }\n}\n\nexport function createLangfuseSpanProcessor(\n params?: LangfuseSpanProcessorParams,\n runLangfuse?: t.LangfuseConfig,\n agentLangfuse?: t.LangfuseConfig\n): SpanProcessor {\n const fallbackConfig =\n runLangfuse != null || agentLangfuse != null\n ? resolveToolOutputTracingConfig(runLangfuse, agentLangfuse)\n : undefined;\n return new ToolOutputRedactingLangfuseSpanProcessor(params, fallbackConfig);\n}\n\nexport function withLangfuseToolOutputTracingConfig<T>(\n runLangfuse: t.LangfuseConfig | undefined,\n action: () => T,\n agentLangfuse?: t.LangfuseConfig\n): T {\n const langfuse = resolveLangfuseConfig(runLangfuse, agentLangfuse);\n const hasNoToolOutputConfig =\n runLangfuse?.toolOutputTracing == null &&\n agentLangfuse?.toolOutputTracing == null;\n\n if (langfuse == null && hasNoToolOutputConfig) {\n return action();\n }\n\n const config = hasNoToolOutputConfig\n ? undefined\n : resolveToolOutputTracingConfig(runLangfuse, agentLangfuse);\n let activeContext = context.active();\n if (langfuse != null) {\n activeContext = activeContext.setValue(langfuseConfigKey, langfuse);\n }\n if (config != null) {\n activeContext = activeContext.setValue(\n langfuseToolOutputTracingConfigKey,\n config\n );\n }\n\n const runWithContext = (): T => context.with(activeContext, action);\n const runWithToolOutputConfig = (): T =>\n config != null\n ? toolOutputTracingStorage.run(config, runWithContext)\n : runWithContext();\n\n return langfuse != null\n ? langfuseConfigStorage.run(langfuse, runWithToolOutputConfig)\n : runWithToolOutputConfig();\n}\n\nfunction hasLangfuseEnvKeys(): boolean {\n return (\n isPresent(process.env.LANGFUSE_SECRET_KEY) &&\n isPresent(process.env.LANGFUSE_PUBLIC_KEY)\n );\n}\n\nfunction hasLangfuseConfigKeys(langfuse?: t.LangfuseConfig): boolean {\n if (langfuse == null) {\n return false;\n }\n return isPresent(langfuse.secretKey) && isPresent(langfuse.publicKey);\n}\n\nexport function shouldTraceToolNodeForLangfuse({\n runLangfuse,\n agentLangfuse,\n}: {\n runLangfuse?: t.LangfuseConfig;\n agentLangfuse?: t.LangfuseConfig;\n}): boolean {\n const langfuse = resolveLangfuseConfig(runLangfuse, agentLangfuse);\n if (langfuse?.enabled === false) {\n return false;\n }\n\n const explicit = langfuse?.toolNodeTracing?.enabled;\n if (explicit != null) {\n return (\n explicit && (hasLangfuseConfigKeys(langfuse) || hasLangfuseEnvKeys())\n );\n }\n\n return hasLangfuseConfigKeys(langfuse) || hasLangfuseEnvKeys();\n}\n\nexport function resolveLangfuseConfig(\n runLangfuse?: t.LangfuseConfig,\n agentLangfuse?: t.LangfuseConfig\n): t.LangfuseConfig | undefined {\n if (runLangfuse == null) {\n return agentLangfuse;\n }\n if (agentLangfuse == null) {\n return runLangfuse;\n }\n\n const toolNodeTracing =\n runLangfuse.toolNodeTracing != null || agentLangfuse.toolNodeTracing != null\n ? {\n ...runLangfuse.toolNodeTracing,\n ...agentLangfuse.toolNodeTracing,\n }\n : undefined;\n const toolOutputTracing =\n runLangfuse.toolOutputTracing != null ||\n agentLangfuse.toolOutputTracing != null\n ? {\n ...runLangfuse.toolOutputTracing,\n ...agentLangfuse.toolOutputTracing,\n }\n : undefined;\n\n return {\n ...runLangfuse,\n ...agentLangfuse,\n ...(toolNodeTracing != null ? { toolNodeTracing } : {}),\n ...(toolOutputTracing != null ? { toolOutputTracing } : {}),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"langfuseToolOutputTracing.mjs","names":[],"sources":["../../src/langfuseToolOutputTracing.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport { LangfuseSpanProcessor } from '@langfuse/otel';\nimport { context, createContextKey } from '@opentelemetry/api';\nimport { LangfuseOtelSpanAttributes } from '@langfuse/tracing';\nimport type {\n ReadableSpan,\n Span,\n SpanProcessor,\n} from '@opentelemetry/sdk-trace-base';\nimport type { LangfuseSpanProcessorParams } from '@langfuse/otel';\nimport type { Context } from '@opentelemetry/api';\nimport type * as t from '@/types';\n\nexport const LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT = '[tool output redacted]';\n\nconst langfuseToolOutputTracingConfigKey = createContextKey(\n 'librechat.langfuse.tool-output-tracing'\n);\nconst langfuseConfigKey = createContextKey('librechat.langfuse.config');\nconst toolOutputTracingStorage =\n new AsyncLocalStorage<ResolvedLangfuseToolOutputTracingConfig>();\nconst langfuseConfigStorage = new AsyncLocalStorage<t.LangfuseConfig>();\nconst LANGGRAPH_TOOL_NODE_PREFIX = 'tools=';\n\nconst CHAT_ROLES = new Set([\n 'assistant',\n 'developer',\n 'human',\n 'system',\n 'user',\n]);\n\nexport type ResolvedLangfuseToolOutputTracingConfig = {\n enabled: boolean;\n redactedToolNames: Set<string>;\n redactedToolNameMatchMode: 'exact' | 'partial';\n redactionText: string;\n};\n\ntype SpanWithAttributes = ReadableSpan & {\n attributes: Record<string, unknown>;\n};\n\ntype RedactionResult = {\n value: unknown;\n changed: boolean;\n};\n\ntype RedactionContext = {\n toolNamesByCallId: Map<string, string>;\n};\n\nconst TOOL_OUTPUT_FIELD_KEYS = ['content', 'artifact'];\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value != null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction isPresent(value: unknown): value is string {\n return typeof value === 'string' && value.trim() !== '';\n}\n\nfunction parseBoolean(value: string | undefined): boolean | undefined {\n if (value == null) {\n return undefined;\n }\n\n const normalized = value.trim().toLowerCase();\n if (['1', 'true', 'yes', 'on'].includes(normalized)) {\n return true;\n }\n if (['0', 'false', 'no', 'off'].includes(normalized)) {\n return false;\n }\n\n return undefined;\n}\n\nfunction normalizeToolName(name: string): string {\n return name.trim().toLowerCase();\n}\n\nfunction normalizeToolNames(names: string[] | undefined): Set<string> {\n const normalized = new Set<string>();\n for (const name of names ?? []) {\n if (isPresent(name)) {\n normalized.add(normalizeToolName(name));\n }\n }\n return normalized;\n}\n\nfunction parseToolNames(value: string | undefined): string[] | undefined {\n if (!isPresent(value)) {\n return undefined;\n }\n\n return value\n .split(',')\n .map((name) => name.trim())\n .filter((name) => name !== '');\n}\n\nfunction getEnvToolOutputTracingEnabled(): boolean | undefined {\n const traceToolOutputs = parseBoolean(\n process.env.LANGFUSE_TRACE_TOOL_OUTPUTS\n );\n if (traceToolOutputs != null) {\n return traceToolOutputs;\n }\n\n const redactToolOutputs = parseBoolean(\n process.env.LANGFUSE_REDACT_TOOL_OUTPUTS\n );\n if (redactToolOutputs != null) {\n return !redactToolOutputs;\n }\n\n return parseBoolean(process.env.LANGFUSE_TOOL_OUTPUT_TRACING_ENABLED);\n}\n\nfunction getEnvRedactedToolNames(): string[] | undefined {\n return (\n parseToolNames(process.env.LANGFUSE_REDACT_TOOL_OUTPUT_NAMES) ??\n parseToolNames(process.env.LANGFUSE_REDACT_TOOL_NAMES)\n );\n}\n\nfunction getEnvRedactionText(): string | undefined {\n return isPresent(process.env.LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT)\n ? process.env.LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT\n : undefined;\n}\n\nfunction getEnvToolNameMatchMode(): 'exact' | 'partial' | undefined {\n const mode = (\n process.env.LANGFUSE_REDACT_TOOL_OUTPUT_NAME_MATCH_MODE ??\n process.env.LANGFUSE_REDACT_TOOL_NAME_MATCH_MODE\n )\n ?.trim()\n .toLowerCase();\n if (mode === 'exact' || mode === 'partial') {\n return mode;\n }\n return undefined;\n}\n\nfunction resolveToolOutputTracingConfig(\n runLangfuse?: t.LangfuseConfig,\n agentLangfuse?: t.LangfuseConfig\n): ResolvedLangfuseToolOutputTracingConfig {\n const runConfig = runLangfuse?.toolOutputTracing;\n const agentConfig = agentLangfuse?.toolOutputTracing;\n\n return {\n enabled:\n agentConfig?.enabled ??\n runConfig?.enabled ??\n getEnvToolOutputTracingEnabled() ??\n true,\n redactedToolNames: normalizeToolNames(\n agentConfig?.redactedToolNames ??\n runConfig?.redactedToolNames ??\n getEnvRedactedToolNames()\n ),\n redactedToolNameMatchMode:\n agentConfig?.redactedToolNameMatchMode ??\n runConfig?.redactedToolNameMatchMode ??\n getEnvToolNameMatchMode() ??\n 'exact',\n redactionText:\n agentConfig?.redactionText ??\n runConfig?.redactionText ??\n getEnvRedactionText() ??\n LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT,\n };\n}\n\nfunction shouldApplyToolOutputRedaction(\n config: ResolvedLangfuseToolOutputTracingConfig\n): boolean {\n return config.enabled === false || config.redactedToolNames.size > 0;\n}\n\nfunction toolNameMatches(\n toolName: string | undefined,\n config: ResolvedLangfuseToolOutputTracingConfig\n): boolean {\n if (!isPresent(toolName)) {\n return false;\n }\n\n const normalizedToolName = normalizeToolName(toolName);\n if (config.redactedToolNameMatchMode === 'partial') {\n for (const redactedToolName of config.redactedToolNames) {\n if (normalizedToolName.includes(redactedToolName)) {\n return true;\n }\n }\n return false;\n }\n\n return config.redactedToolNames.has(normalizedToolName);\n}\n\nfunction shouldRedactTool(\n toolName: string | undefined,\n config: ResolvedLangfuseToolOutputTracingConfig\n): boolean {\n return config.enabled === false || toolNameMatches(toolName, config);\n}\n\nfunction getStringField(\n value: Record<string, unknown>,\n key: string\n): string | undefined {\n const field = value[key];\n return typeof field === 'string' ? field : undefined;\n}\n\nfunction getNestedStringField(\n value: Record<string, unknown>,\n objectKey: string,\n fieldKey: string\n): string | undefined {\n const nested = value[objectKey];\n if (!isRecord(nested)) {\n return undefined;\n }\n return getStringField(nested, fieldKey);\n}\n\nfunction getSerializedToolCallId(\n value: Record<string, unknown>\n): string | undefined {\n return (\n getStringField(value, 'tool_call_id') ??\n getNestedStringField(value, 'kwargs', 'tool_call_id') ??\n getNestedStringField(value, 'additional_kwargs', 'tool_call_id') ??\n getNestedStringField(value, 'data', 'tool_call_id') ??\n (typeof value.id === 'string' ? value.id : undefined)\n );\n}\n\nfunction getSerializedToolName(\n value: Record<string, unknown>,\n redactionContext?: RedactionContext\n): string | undefined {\n const role = getStringField(value, 'role');\n const explicitName =\n getStringField(value, 'name') ??\n getStringField(value, 'tool_name') ??\n getNestedStringField(value, 'function', 'name') ??\n getNestedStringField(value, 'kwargs', 'name') ??\n getNestedStringField(value, 'additional_kwargs', 'name') ??\n getNestedStringField(value, 'data', 'name') ??\n (role != null && role.toLowerCase() !== 'tool' ? role : undefined);\n\n if (explicitName != null) {\n return explicitName;\n }\n\n const toolCallId = getSerializedToolCallId(value);\n return toolCallId != null\n ? redactionContext?.toolNamesByCallId.get(toolCallId)\n : undefined;\n}\n\nfunction hasToolMessageIdentity(value: Record<string, unknown>): boolean {\n const type = getStringField(value, 'type') ?? getStringField(value, '_type');\n if (type === 'tool' || type === 'tool_message') {\n return true;\n }\n\n const id = value.id;\n if (\n Array.isArray(id) &&\n id.some((part) => typeof part === 'string' && part.includes('ToolMessage'))\n ) {\n return true;\n }\n\n if (\n 'tool_call_id' in value ||\n getNestedStringField(value, 'kwargs', 'tool_call_id') != null ||\n getNestedStringField(value, 'additional_kwargs', 'tool_call_id') != null\n ) {\n return true;\n }\n\n const role = getStringField(value, 'role');\n return (\n role != null &&\n !CHAT_ROLES.has(role.toLowerCase()) &&\n ('content' in value || isRecord(value.kwargs) || isRecord(value.data))\n );\n}\n\nfunction redactToolContentFields(\n value: Record<string, unknown>,\n config: ResolvedLangfuseToolOutputTracingConfig\n): Record<string, unknown> {\n const next = { ...value };\n\n for (const outputKey of TOOL_OUTPUT_FIELD_KEYS) {\n if (outputKey in next) {\n next[outputKey] = config.redactionText;\n }\n }\n\n for (const nestedKey of ['kwargs', 'data', 'additional_kwargs']) {\n const nested = next[nestedKey];\n if (!isRecord(nested)) {\n continue;\n }\n const nextNested = { ...nested };\n let changed = false;\n for (const outputKey of TOOL_OUTPUT_FIELD_KEYS) {\n if (outputKey in nextNested) {\n nextNested[outputKey] = config.redactionText;\n changed = true;\n }\n }\n if (changed) {\n next[nestedKey] = nextNested;\n }\n }\n\n return next;\n}\n\nfunction collectToolCallNames(\n value: unknown,\n redactionContext: RedactionContext\n): void {\n if (Array.isArray(value)) {\n for (const item of value) {\n collectToolCallNames(item, redactionContext);\n }\n return;\n }\n\n if (!isRecord(value)) {\n return;\n }\n\n const toolCallId = getSerializedToolCallId(value);\n const toolName = getSerializedToolName(value);\n if (toolCallId != null && toolName != null) {\n redactionContext.toolNamesByCallId.set(toolCallId, toolName);\n }\n\n for (const child of Object.values(value)) {\n collectToolCallNames(child, redactionContext);\n }\n}\n\nfunction redactValue(\n value: unknown,\n config: ResolvedLangfuseToolOutputTracingConfig,\n redactionContext: RedactionContext\n): RedactionResult {\n if (Array.isArray(value)) {\n let changed = false;\n const next: unknown[] = [];\n for (const item of value) {\n const result = redactValue(item, config, redactionContext);\n if (result.changed) {\n changed = true;\n }\n next.push(result.value);\n }\n return changed ? { value: next, changed } : { value, changed };\n }\n\n if (!isRecord(value)) {\n return { value, changed: false };\n }\n\n const toolName = getSerializedToolName(value, redactionContext);\n if (hasToolMessageIdentity(value) && shouldRedactTool(toolName, config)) {\n return {\n value: redactToolContentFields(value, config),\n changed: true,\n };\n }\n\n let changed = false;\n const next: Record<string, unknown> = {};\n for (const [key, child] of Object.entries(value)) {\n const result = redactValue(child, config, redactionContext);\n if (result.changed) {\n changed = true;\n }\n next[key] = result.value;\n }\n\n return changed ? { value: next, changed } : { value, changed };\n}\n\nfunction redactSerializedValue(\n value: unknown,\n config: ResolvedLangfuseToolOutputTracingConfig\n): RedactionResult {\n const redactionContext: RedactionContext = {\n toolNamesByCallId: new Map(),\n };\n if (typeof value !== 'string') {\n collectToolCallNames(value, redactionContext);\n return redactValue(value, config, redactionContext);\n }\n\n const trimmed = value.trim();\n if (!trimmed.startsWith('{') && !trimmed.startsWith('[')) {\n return { value, changed: false };\n }\n\n try {\n const parsed = JSON.parse(value) as unknown;\n collectToolCallNames(parsed, redactionContext);\n const result = redactValue(parsed, config, redactionContext);\n return result.changed\n ? { value: JSON.stringify(result.value), changed: true }\n : { value, changed: false };\n } catch {\n return { value, changed: false };\n }\n}\n\nfunction redactAttribute(\n attributes: Record<string, unknown>,\n key: string,\n config: ResolvedLangfuseToolOutputTracingConfig\n): void {\n if (!(key in attributes)) {\n return;\n }\n\n const result = redactSerializedValue(attributes[key], config);\n if (result.changed) {\n attributes[key] = result.value;\n }\n}\n\nfunction isToolObservation(attributes: Record<string, unknown>): boolean {\n const type = attributes[LangfuseOtelSpanAttributes.OBSERVATION_TYPE];\n return typeof type === 'string' && type.toLowerCase() === 'tool';\n}\n\nfunction classifyLangGraphToolNodeSpan(\n attributes: Record<string, unknown>\n): void {\n const type = attributes[LangfuseOtelSpanAttributes.OBSERVATION_TYPE];\n if (typeof type !== 'string' || type.toLowerCase() !== 'span') {\n return;\n }\n\n const langGraphNode =\n attributes[\n `${LangfuseOtelSpanAttributes.OBSERVATION_METADATA}.langgraph_node`\n ];\n if (\n typeof langGraphNode === 'string' &&\n langGraphNode.startsWith(LANGGRAPH_TOOL_NODE_PREFIX)\n ) {\n attributes[LangfuseOtelSpanAttributes.OBSERVATION_TYPE] = 'tool';\n }\n}\n\nfunction redactToolObservationOutput(\n span: ReadableSpan,\n attributes: Record<string, unknown>,\n config: ResolvedLangfuseToolOutputTracingConfig\n): void {\n if (\n !(\n isToolObservation(attributes) &&\n shouldRedactTool(span.name, config) &&\n LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT in attributes\n )\n ) {\n return;\n }\n\n attributes[LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT] =\n config.redactionText;\n}\n\nexport function redactLangfuseSpanToolOutputs(\n span: ReadableSpan,\n config: ResolvedLangfuseToolOutputTracingConfig\n): void {\n const attributes = (span as SpanWithAttributes).attributes;\n classifyLangGraphToolNodeSpan(attributes);\n\n if (!shouldApplyToolOutputRedaction(config)) {\n return;\n }\n\n redactToolObservationOutput(span, attributes, config);\n\n for (const key of [\n LangfuseOtelSpanAttributes.OBSERVATION_INPUT,\n LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT,\n LangfuseOtelSpanAttributes.TRACE_INPUT,\n LangfuseOtelSpanAttributes.TRACE_OUTPUT,\n ]) {\n redactAttribute(attributes, key, config);\n }\n}\n\nfunction getContextToolOutputTracingConfig(\n activeContext: Context\n): ResolvedLangfuseToolOutputTracingConfig | undefined {\n const asyncConfig = toolOutputTracingStorage.getStore();\n if (asyncConfig != null) {\n return asyncConfig;\n }\n\n const value = activeContext.getValue(langfuseToolOutputTracingConfigKey);\n return isRecord(value)\n ? (value as ResolvedLangfuseToolOutputTracingConfig)\n : undefined;\n}\n\nexport function getContextLangfuseConfig(\n activeContext: Context\n): t.LangfuseConfig | undefined {\n const asyncConfig = langfuseConfigStorage.getStore();\n if (asyncConfig != null) {\n return asyncConfig;\n }\n\n const value = activeContext.getValue(langfuseConfigKey);\n return isRecord(value) ? (value as t.LangfuseConfig) : undefined;\n}\n\nclass ToolOutputRedactingLangfuseSpanProcessor implements SpanProcessor {\n private readonly processor: LangfuseSpanProcessor;\n private readonly fallbackConfig?: ResolvedLangfuseToolOutputTracingConfig;\n private readonly spanConfigs = new WeakMap<\n object,\n ResolvedLangfuseToolOutputTracingConfig\n >();\n\n constructor(\n params?: LangfuseSpanProcessorParams,\n fallbackConfig?: ResolvedLangfuseToolOutputTracingConfig\n ) {\n this.processor = new LangfuseSpanProcessor(params);\n this.fallbackConfig = fallbackConfig;\n }\n\n onStart(span: Span, parentContext: Context): void {\n const config =\n getContextToolOutputTracingConfig(parentContext) ?? this.fallbackConfig;\n if (config != null) {\n this.spanConfigs.set(span, config);\n }\n this.processor.onStart(span, parentContext);\n }\n\n onEnd(span: ReadableSpan): void {\n const config =\n this.spanConfigs.get(span) ??\n toolOutputTracingStorage.getStore() ??\n this.fallbackConfig ??\n resolveToolOutputTracingConfig();\n redactLangfuseSpanToolOutputs(span, config);\n this.processor.onEnd(span);\n }\n\n forceFlush(): Promise<void> {\n return this.processor.forceFlush();\n }\n\n shutdown(): Promise<void> {\n return this.processor.shutdown();\n }\n}\n\nexport function createLangfuseSpanProcessor(\n params?: LangfuseSpanProcessorParams,\n runLangfuse?: t.LangfuseConfig,\n agentLangfuse?: t.LangfuseConfig\n): SpanProcessor {\n const fallbackConfig =\n runLangfuse != null || agentLangfuse != null\n ? resolveToolOutputTracingConfig(runLangfuse, agentLangfuse)\n : undefined;\n return new ToolOutputRedactingLangfuseSpanProcessor(params, fallbackConfig);\n}\n\nexport function withLangfuseToolOutputTracingConfig<T>(\n runLangfuse: t.LangfuseConfig | undefined,\n action: () => T,\n agentLangfuse?: t.LangfuseConfig\n): T {\n const langfuse = resolveLangfuseConfig(runLangfuse, agentLangfuse);\n const hasNoToolOutputConfig =\n runLangfuse?.toolOutputTracing == null &&\n agentLangfuse?.toolOutputTracing == null;\n\n if (langfuse == null && hasNoToolOutputConfig) {\n return action();\n }\n\n const config = hasNoToolOutputConfig\n ? undefined\n : resolveToolOutputTracingConfig(runLangfuse, agentLangfuse);\n let activeContext = context.active();\n if (langfuse != null) {\n activeContext = activeContext.setValue(langfuseConfigKey, langfuse);\n }\n if (config != null) {\n activeContext = activeContext.setValue(\n langfuseToolOutputTracingConfigKey,\n config\n );\n }\n\n const runWithContext = (): T => context.with(activeContext, action);\n const runWithToolOutputConfig = (): T =>\n config != null\n ? toolOutputTracingStorage.run(config, runWithContext)\n : runWithContext();\n\n return langfuse != null\n ? langfuseConfigStorage.run(langfuse, runWithToolOutputConfig)\n : runWithToolOutputConfig();\n}\n\nfunction hasLangfuseEnvKeys(): boolean {\n return (\n isPresent(process.env.LANGFUSE_SECRET_KEY) &&\n isPresent(process.env.LANGFUSE_PUBLIC_KEY)\n );\n}\n\nfunction hasLangfuseConfigKeys(langfuse?: t.LangfuseConfig): boolean {\n if (langfuse == null) {\n return false;\n }\n return isPresent(langfuse.secretKey) && isPresent(langfuse.publicKey);\n}\n\nexport function shouldTraceToolNodeForLangfuse({\n runLangfuse,\n agentLangfuse,\n}: {\n runLangfuse?: t.LangfuseConfig;\n agentLangfuse?: t.LangfuseConfig;\n}): boolean {\n const langfuse = resolveLangfuseConfig(runLangfuse, agentLangfuse);\n if (langfuse?.enabled === false) {\n return false;\n }\n\n const explicit = langfuse?.toolNodeTracing?.enabled;\n if (explicit != null) {\n return (\n explicit && (hasLangfuseConfigKeys(langfuse) || hasLangfuseEnvKeys())\n );\n }\n\n return hasLangfuseConfigKeys(langfuse) || hasLangfuseEnvKeys();\n}\n\nexport function resolveLangfuseConfig(\n runLangfuse?: t.LangfuseConfig,\n agentLangfuse?: t.LangfuseConfig\n): t.LangfuseConfig | undefined {\n if (runLangfuse == null) {\n return agentLangfuse;\n }\n if (agentLangfuse == null) {\n return runLangfuse;\n }\n\n const toolNodeTracing =\n runLangfuse.toolNodeTracing != null || agentLangfuse.toolNodeTracing != null\n ? {\n ...runLangfuse.toolNodeTracing,\n ...agentLangfuse.toolNodeTracing,\n }\n : undefined;\n const toolOutputTracing =\n runLangfuse.toolOutputTracing != null ||\n agentLangfuse.toolOutputTracing != null\n ? {\n ...runLangfuse.toolOutputTracing,\n ...agentLangfuse.toolOutputTracing,\n }\n : undefined;\n const metadata =\n runLangfuse.metadata != null || agentLangfuse.metadata != null\n ? {\n ...runLangfuse.metadata,\n ...agentLangfuse.metadata,\n }\n : undefined;\n const tags =\n runLangfuse.tags != null || agentLangfuse.tags != null\n ? [\n ...new Set([\n ...(runLangfuse.tags ?? []),\n ...(agentLangfuse.tags ?? []),\n ]),\n ]\n : undefined;\n\n return {\n ...runLangfuse,\n ...agentLangfuse,\n ...(metadata != null ? { metadata } : {}),\n ...(tags != null ? { tags } : {}),\n ...(toolNodeTracing != null ? { toolNodeTracing } : {}),\n ...(toolOutputTracing != null ? { toolOutputTracing } : {}),\n };\n}\n"],"mappings":"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"}
@@ -48,6 +48,34 @@ function normalizeAnthropicToolCallId(id) {
48
48
  const prefixMaxLength = ANTHROPIC_TOOL_USE_ID_MAX_LENGTH - ANTHROPIC_TOOL_USE_ID_HASH_LENGTH - 1;
49
49
  return `${sanitized.slice(0, prefixMaxLength)}_${hash}`;
50
50
  }
51
+ /**
52
+ * Lift any `cache_control` off the inner blocks of a tool result onto the
53
+ * `tool_result` block itself. Anthropic documents the top-level
54
+ * `messages.content` block as the cacheable position and does not document
55
+ * caching of sub-content blocks; the API currently honors a nested marker, but
56
+ * anchoring on the documented position keeps the single tail breakpoint robust
57
+ * (and mirrors the Bedrock cachePoint hoist). The first marker found wins; it is
58
+ * stripped from every inner block so exactly one survives, on the outer block.
59
+ */
60
+ function hoistToolResultCacheControl(content) {
61
+ if (!Array.isArray(content)) return {
62
+ content,
63
+ cacheControl: void 0
64
+ };
65
+ let cacheControl;
66
+ return {
67
+ content: content.map((block) => {
68
+ if ("cache_control" in block) {
69
+ cacheControl ??= block.cache_control;
70
+ const clone = { ...block };
71
+ delete clone.cache_control;
72
+ return clone;
73
+ }
74
+ return block;
75
+ }),
76
+ cacheControl
77
+ };
78
+ }
51
79
  function _ensureMessageContents(messages) {
52
80
  const updatedMsgs = [];
53
81
  for (const message of messages) if (message._getType() === "tool") if (typeof message.content === "string") {
@@ -63,10 +91,14 @@ function _ensureMessageContents(messages) {
63
91
  tool_use_id: normalizeAnthropicToolCallId(message.tool_call_id)
64
92
  }] }));
65
93
  } else {
66
- const toolMessageContent = message.content;
94
+ const { content: hoistedContent, cacheControl } = message.content != null ? hoistToolResultCacheControl(_formatContent(message)) : {
95
+ content: void 0,
96
+ cacheControl: void 0
97
+ };
67
98
  updatedMsgs.push(new HumanMessage({ content: [{
68
99
  type: "tool_result",
69
- ...toolMessageContent != null ? { content: _formatContent(message) } : {},
100
+ ...hoistedContent != null ? { content: hoistedContent } : {},
101
+ ...cacheControl != null ? { cache_control: cacheControl } : {},
70
102
  tool_use_id: normalizeAnthropicToolCallId(message.tool_call_id)
71
103
  }] }));
72
104
  }
@@ -442,15 +474,72 @@ function modelDisallowsAssistantPrefill(model) {
442
474
  if (!match) return false;
443
475
  return Number(match[1]) >= 6;
444
476
  }
477
+ function messagesHaveCacheControl(messages) {
478
+ return messages.some((message) => Array.isArray(message.content) && message.content.some((block) => "cache_control" in block));
479
+ }
480
+ /** Anthropic rejects cache_control on these reasoning blocks. */
481
+ const NON_CACHEABLE_PAYLOAD_BLOCK_TYPES = new Set(["thinking", "redacted_thinking"]);
482
+ /**
483
+ * Place one ephemeral `cache_control` on the last cacheable block of the final
484
+ * message of an already-converted Anthropic payload. Used to re-anchor the tail
485
+ * breakpoint after a trailing assistant prefill is stripped. Operates on the
486
+ * post-conversion payload, where blocks the converter drops (foreign reasoning,
487
+ * input_json_delta) are already gone — only native thinking blocks must be
488
+ * skipped. Returns a new array only when it actually places a marker.
489
+ */
490
+ function reanchorTailCacheControl(messages) {
491
+ if (messages.length === 0) return messages;
492
+ const lastIndex = messages.length - 1;
493
+ const tail = messages[lastIndex];
494
+ const content = tail.content;
495
+ if (typeof content === "string") {
496
+ if (content.trim() === "") return messages;
497
+ const next = [...messages];
498
+ next[lastIndex] = {
499
+ ...tail,
500
+ content: [{
501
+ type: "text",
502
+ text: content,
503
+ cache_control: { type: "ephemeral" }
504
+ }]
505
+ };
506
+ return next;
507
+ }
508
+ if (!Array.isArray(content)) return messages;
509
+ let anchor = -1;
510
+ for (let i = 0; i < content.length; i++) {
511
+ const type = content[i].type;
512
+ if (type == null || NON_CACHEABLE_PAYLOAD_BLOCK_TYPES.has(type)) continue;
513
+ if (type === "text" && (content[i].text ?? "").trim() === "") continue;
514
+ anchor = i;
515
+ }
516
+ if (anchor < 0) return messages;
517
+ const next = [...messages];
518
+ next[lastIndex] = {
519
+ ...tail,
520
+ content: content.map((block, i) => i === anchor ? {
521
+ ...block,
522
+ cache_control: { type: "ephemeral" }
523
+ } : block)
524
+ };
525
+ return next;
526
+ }
445
527
  function stripUnsupportedAssistantPrefill(request) {
446
528
  if (!modelDisallowsAssistantPrefill(request.model)) return request;
447
529
  const messages = request.messages;
448
530
  if (messages.length <= 1 || messages[messages.length - 1]?.role !== "assistant") return request;
449
531
  const nextMessages = [...messages];
450
532
  while (nextMessages.length > 1 && nextMessages[nextMessages.length - 1]?.role === "assistant") nextMessages.pop();
533
+ /**
534
+ * If a single tail prompt-cache breakpoint rode the stripped assistant
535
+ * prefill, the survivors may now carry no `cache_control` at all, dropping
536
+ * message caching for this request. Re-anchor the breakpoint on the new tail
537
+ * (only when one was actually lost, so caching-off requests stay untouched).
538
+ */
539
+ const reanchored = messagesHaveCacheControl(messages) && !messagesHaveCacheControl(nextMessages) ? reanchorTailCacheControl(nextMessages) : nextMessages;
451
540
  return {
452
541
  ...request,
453
- messages: nextMessages
542
+ messages: reanchored
454
543
  };
455
544
  }
456
545
  function mergeMessages(messages) {