@posthog/ai 7.2.1 → 7.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/dist/anthropic/index.cjs +13 -3
- package/dist/anthropic/index.cjs.map +1 -1
- package/dist/anthropic/index.mjs +13 -3
- package/dist/anthropic/index.mjs.map +1 -1
- package/dist/gemini/index.cjs +69 -8
- package/dist/gemini/index.cjs.map +1 -1
- package/dist/gemini/index.d.ts +1 -0
- package/dist/gemini/index.mjs +69 -8
- package/dist/gemini/index.mjs.map +1 -1
- package/dist/index.cjs +253 -151
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.mjs +253 -151
- package/dist/index.mjs.map +1 -1
- package/dist/langchain/index.cjs +13 -2
- package/dist/langchain/index.cjs.map +1 -1
- package/dist/langchain/index.mjs +13 -2
- package/dist/langchain/index.mjs.map +1 -1
- package/dist/openai/index.cjs +48 -14
- package/dist/openai/index.cjs.map +1 -1
- package/dist/openai/index.mjs +48 -14
- package/dist/openai/index.mjs.map +1 -1
- package/dist/vercel/index.cjs +141 -113
- package/dist/vercel/index.cjs.map +1 -1
- package/dist/vercel/index.d.ts +7 -2
- package/dist/vercel/index.mjs +141 -113
- package/dist/vercel/index.mjs.map +1 -1
- package/package.json +17 -12
package/dist/vercel/index.mjs
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { wrapLanguageModel } from 'ai';
|
|
2
1
|
import { v4 } from 'uuid';
|
|
3
2
|
import { Buffer } from 'buffer';
|
|
4
3
|
|
|
5
|
-
var version = "7.
|
|
4
|
+
var version = "7.3.0";
|
|
6
5
|
|
|
7
6
|
// Type guards for safer type checking
|
|
8
7
|
|
|
@@ -331,6 +330,15 @@ const sendEventToPosthog = async ({
|
|
|
331
330
|
|
|
332
331
|
const REDACTED_IMAGE_PLACEHOLDER = '[base64 image redacted]';
|
|
333
332
|
|
|
333
|
+
// ============================================
|
|
334
|
+
// Multimodal Feature Toggle
|
|
335
|
+
// ============================================
|
|
336
|
+
|
|
337
|
+
const isMultimodalEnabled = () => {
|
|
338
|
+
const val = process.env._INTERNAL_LLMA_MULTIMODAL || '';
|
|
339
|
+
return val.toLowerCase() === 'true' || val === '1' || val.toLowerCase() === 'yes';
|
|
340
|
+
};
|
|
341
|
+
|
|
334
342
|
// ============================================
|
|
335
343
|
// Base64 Detection Helpers
|
|
336
344
|
// ============================================
|
|
@@ -358,6 +366,7 @@ const isRawBase64 = str => {
|
|
|
358
366
|
return str.length > 20 && /^[A-Za-z0-9+/]+=*$/.test(str);
|
|
359
367
|
};
|
|
360
368
|
function redactBase64DataUrl(str) {
|
|
369
|
+
if (isMultimodalEnabled()) return str;
|
|
361
370
|
if (!isString(str)) return str;
|
|
362
371
|
|
|
363
372
|
// Check for data URL format
|
|
@@ -572,68 +581,126 @@ const extractProvider = model => {
|
|
|
572
581
|
const providerName = provider.split('.')[0];
|
|
573
582
|
return providerName;
|
|
574
583
|
};
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
584
|
+
|
|
585
|
+
// Extract web search count from provider metadata (works for both V2 and V3)
|
|
586
|
+
const extractWebSearchCount = (providerMetadata, usage) => {
|
|
587
|
+
// Try Anthropic-specific extraction
|
|
588
|
+
if (providerMetadata && typeof providerMetadata === 'object' && 'anthropic' in providerMetadata && providerMetadata.anthropic && typeof providerMetadata.anthropic === 'object' && 'server_tool_use' in providerMetadata.anthropic) {
|
|
589
|
+
const serverToolUse = providerMetadata.anthropic.server_tool_use;
|
|
590
|
+
if (serverToolUse && typeof serverToolUse === 'object' && 'web_search_requests' in serverToolUse && typeof serverToolUse.web_search_requests === 'number') {
|
|
591
|
+
return serverToolUse.web_search_requests;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Fall back to generic calculation
|
|
596
|
+
return calculateWebSearchCount({
|
|
597
|
+
usage,
|
|
598
|
+
providerMetadata
|
|
599
|
+
});
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
// Extract additional token values from provider metadata
|
|
603
|
+
const extractAdditionalTokenValues = providerMetadata => {
|
|
604
|
+
if (providerMetadata && typeof providerMetadata === 'object' && 'anthropic' in providerMetadata && providerMetadata.anthropic && typeof providerMetadata.anthropic === 'object' && 'cacheCreationInputTokens' in providerMetadata.anthropic) {
|
|
605
|
+
return {
|
|
606
|
+
cacheCreationInputTokens: providerMetadata.anthropic.cacheCreationInputTokens
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
return {};
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
// Helper to extract numeric token value from V2 (number) or V3 (object with .total) usage formats
|
|
613
|
+
const extractTokenCount = value => {
|
|
614
|
+
if (typeof value === 'number') {
|
|
615
|
+
return value;
|
|
616
|
+
}
|
|
617
|
+
if (value && typeof value === 'object' && 'total' in value && typeof value.total === 'number') {
|
|
618
|
+
return value.total;
|
|
619
|
+
}
|
|
620
|
+
return undefined;
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
// Helper to extract reasoning tokens from V2 (usage.reasoningTokens) or V3 (usage.outputTokens.reasoning)
|
|
624
|
+
const extractReasoningTokens = usage => {
|
|
625
|
+
// V2 style: top-level reasoningTokens
|
|
626
|
+
if ('reasoningTokens' in usage) {
|
|
627
|
+
return usage.reasoningTokens;
|
|
628
|
+
}
|
|
629
|
+
// V3 style: nested in outputTokens.reasoning
|
|
630
|
+
if ('outputTokens' in usage && usage.outputTokens && typeof usage.outputTokens === 'object' && 'reasoning' in usage.outputTokens) {
|
|
631
|
+
return usage.outputTokens.reasoning;
|
|
632
|
+
}
|
|
633
|
+
return undefined;
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
// Helper to extract cached input tokens from V2 (usage.cachedInputTokens) or V3 (usage.inputTokens.cacheRead)
|
|
637
|
+
const extractCacheReadTokens = usage => {
|
|
638
|
+
// V2 style: top-level cachedInputTokens
|
|
639
|
+
if ('cachedInputTokens' in usage) {
|
|
640
|
+
return usage.cachedInputTokens;
|
|
641
|
+
}
|
|
642
|
+
// V3 style: nested in inputTokens.cacheRead
|
|
643
|
+
if ('inputTokens' in usage && usage.inputTokens && typeof usage.inputTokens === 'object' && 'cacheRead' in usage.inputTokens) {
|
|
644
|
+
return usage.inputTokens.cacheRead;
|
|
645
|
+
}
|
|
646
|
+
return undefined;
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Wraps a Vercel AI SDK language model (V2 or V3) with PostHog tracing.
|
|
651
|
+
* Automatically detects the model version and applies appropriate instrumentation.
|
|
652
|
+
*/
|
|
653
|
+
const wrapVercelLanguageModel = (model, phClient, options) => {
|
|
654
|
+
const traceId = options.posthogTraceId ?? v4();
|
|
655
|
+
const mergedOptions = {
|
|
656
|
+
...options,
|
|
657
|
+
posthogTraceId: traceId,
|
|
658
|
+
posthogDistinctId: options.posthogDistinctId,
|
|
659
|
+
posthogProperties: {
|
|
660
|
+
...options.posthogProperties,
|
|
661
|
+
$ai_framework: 'vercel',
|
|
662
|
+
$ai_framework_version: model.specificationVersion === 'v3' ? '6' : '5'
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
// Create wrapped model that preserves the original type
|
|
667
|
+
const wrappedModel = {
|
|
668
|
+
...model,
|
|
669
|
+
doGenerate: async params => {
|
|
581
670
|
const startTime = Date.now();
|
|
582
671
|
const mergedParams = {
|
|
583
|
-
...
|
|
584
|
-
...mapVercelParams(params)
|
|
585
|
-
posthogProperties: {
|
|
586
|
-
...options.posthogProperties,
|
|
587
|
-
$ai_framework: 'vercel'
|
|
588
|
-
}
|
|
672
|
+
...mergedOptions,
|
|
673
|
+
...mapVercelParams(params)
|
|
589
674
|
};
|
|
590
675
|
const availableTools = extractAvailableToolCalls('vercel', params);
|
|
591
676
|
try {
|
|
592
|
-
const result = await doGenerate();
|
|
593
|
-
const modelId =
|
|
594
|
-
const provider =
|
|
677
|
+
const result = await model.doGenerate(params);
|
|
678
|
+
const modelId = mergedOptions.posthogModelOverride ?? (result.response?.modelId ? result.response.modelId : model.modelId);
|
|
679
|
+
const provider = mergedOptions.posthogProviderOverride ?? extractProvider(model);
|
|
595
680
|
const baseURL = ''; // cannot currently get baseURL from vercel
|
|
596
681
|
const content = mapVercelOutput(result.content);
|
|
597
682
|
const latency = (Date.now() - startTime) / 1000;
|
|
598
683
|
const providerMetadata = result.providerMetadata;
|
|
599
|
-
const additionalTokenValues =
|
|
600
|
-
|
|
601
|
-
cacheCreationInputTokens: providerMetadata.anthropic.cacheCreationInputTokens
|
|
602
|
-
} : {})
|
|
603
|
-
};
|
|
684
|
+
const additionalTokenValues = extractAdditionalTokenValues(providerMetadata);
|
|
685
|
+
const webSearchCount = extractWebSearchCount(providerMetadata, result.usage);
|
|
604
686
|
|
|
605
|
-
//
|
|
606
|
-
|
|
607
|
-
if (providerMetadata?.anthropic && typeof providerMetadata.anthropic === 'object' && 'server_tool_use' in providerMetadata.anthropic) {
|
|
608
|
-
// Anthropic-specific extraction
|
|
609
|
-
const serverToolUse = providerMetadata.anthropic.server_tool_use;
|
|
610
|
-
if (serverToolUse && typeof serverToolUse === 'object' && 'web_search_requests' in serverToolUse && typeof serverToolUse.web_search_requests === 'number') {
|
|
611
|
-
webSearchCount = serverToolUse.web_search_requests;
|
|
612
|
-
}
|
|
613
|
-
} else {
|
|
614
|
-
// For other providers through Vercel, pass available metadata to helper
|
|
615
|
-
// Note: Vercel abstracts provider responses, so we may not have access to
|
|
616
|
-
// raw citations/annotations unless Vercel exposes them in usage/metadata
|
|
617
|
-
webSearchCount = calculateWebSearchCount({
|
|
618
|
-
usage: result.usage,
|
|
619
|
-
providerMetadata: providerMetadata
|
|
620
|
-
});
|
|
621
|
-
}
|
|
687
|
+
// V2 usage has simple numbers, V3 has objects with .total - normalize both
|
|
688
|
+
const usageObj = result.usage;
|
|
622
689
|
const usage = {
|
|
623
|
-
inputTokens: result.usage.inputTokens,
|
|
624
|
-
outputTokens: result.usage.outputTokens,
|
|
625
|
-
reasoningTokens:
|
|
626
|
-
cacheReadInputTokens:
|
|
690
|
+
inputTokens: extractTokenCount(result.usage.inputTokens),
|
|
691
|
+
outputTokens: extractTokenCount(result.usage.outputTokens),
|
|
692
|
+
reasoningTokens: extractReasoningTokens(usageObj),
|
|
693
|
+
cacheReadInputTokens: extractCacheReadTokens(usageObj),
|
|
627
694
|
webSearchCount,
|
|
628
695
|
...additionalTokenValues
|
|
629
696
|
};
|
|
630
697
|
await sendEventToPosthog({
|
|
631
698
|
client: phClient,
|
|
632
|
-
distinctId:
|
|
633
|
-
traceId:
|
|
699
|
+
distinctId: mergedOptions.posthogDistinctId,
|
|
700
|
+
traceId: mergedOptions.posthogTraceId ?? v4(),
|
|
634
701
|
model: modelId,
|
|
635
702
|
provider: provider,
|
|
636
|
-
input:
|
|
703
|
+
input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
|
|
637
704
|
output: content,
|
|
638
705
|
latency,
|
|
639
706
|
baseURL,
|
|
@@ -641,18 +708,18 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
|
|
|
641
708
|
httpStatus: 200,
|
|
642
709
|
usage,
|
|
643
710
|
tools: availableTools,
|
|
644
|
-
captureImmediate:
|
|
711
|
+
captureImmediate: mergedOptions.posthogCaptureImmediate
|
|
645
712
|
});
|
|
646
713
|
return result;
|
|
647
714
|
} catch (error) {
|
|
648
715
|
const modelId = model.modelId;
|
|
649
716
|
await sendEventToPosthog({
|
|
650
717
|
client: phClient,
|
|
651
|
-
distinctId:
|
|
652
|
-
traceId:
|
|
718
|
+
distinctId: mergedOptions.posthogDistinctId,
|
|
719
|
+
traceId: mergedOptions.posthogTraceId ?? v4(),
|
|
653
720
|
model: modelId,
|
|
654
721
|
provider: model.provider,
|
|
655
|
-
input:
|
|
722
|
+
input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
|
|
656
723
|
output: [],
|
|
657
724
|
latency: 0,
|
|
658
725
|
baseURL: '',
|
|
@@ -665,30 +732,23 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
|
|
|
665
732
|
isError: true,
|
|
666
733
|
error: truncate(JSON.stringify(error)),
|
|
667
734
|
tools: availableTools,
|
|
668
|
-
captureImmediate:
|
|
735
|
+
captureImmediate: mergedOptions.posthogCaptureImmediate
|
|
669
736
|
});
|
|
670
737
|
throw error;
|
|
671
738
|
}
|
|
672
739
|
},
|
|
673
|
-
|
|
674
|
-
doStream,
|
|
675
|
-
params
|
|
676
|
-
}) => {
|
|
740
|
+
doStream: async params => {
|
|
677
741
|
const startTime = Date.now();
|
|
678
742
|
let generatedText = '';
|
|
679
743
|
let reasoningText = '';
|
|
680
744
|
let usage = {};
|
|
681
745
|
let providerMetadata = undefined;
|
|
682
746
|
const mergedParams = {
|
|
683
|
-
...
|
|
684
|
-
...mapVercelParams(params)
|
|
685
|
-
posthogProperties: {
|
|
686
|
-
...options.posthogProperties,
|
|
687
|
-
$ai_framework: 'vercel'
|
|
688
|
-
}
|
|
747
|
+
...mergedOptions,
|
|
748
|
+
...mapVercelParams(params)
|
|
689
749
|
};
|
|
690
|
-
const modelId =
|
|
691
|
-
const provider =
|
|
750
|
+
const modelId = mergedOptions.posthogModelOverride ?? model.modelId;
|
|
751
|
+
const provider = mergedOptions.posthogProviderOverride ?? extractProvider(model);
|
|
692
752
|
const availableTools = extractAvailableToolCalls('vercel', params);
|
|
693
753
|
const baseURL = ''; // cannot currently get baseURL from vercel
|
|
694
754
|
|
|
@@ -698,15 +758,15 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
|
|
|
698
758
|
const {
|
|
699
759
|
stream,
|
|
700
760
|
...rest
|
|
701
|
-
} = await doStream();
|
|
761
|
+
} = await model.doStream(params);
|
|
702
762
|
const transformStream = new TransformStream({
|
|
703
763
|
transform(chunk, controller) {
|
|
704
|
-
// Handle
|
|
764
|
+
// Handle streaming patterns - compatible with both V2 and V3
|
|
705
765
|
if (chunk.type === 'text-delta') {
|
|
706
766
|
generatedText += chunk.delta;
|
|
707
767
|
}
|
|
708
768
|
if (chunk.type === 'reasoning-delta') {
|
|
709
|
-
reasoningText += chunk.delta;
|
|
769
|
+
reasoningText += chunk.delta;
|
|
710
770
|
}
|
|
711
771
|
|
|
712
772
|
// Handle tool call chunks
|
|
@@ -727,7 +787,6 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
|
|
|
727
787
|
}
|
|
728
788
|
if (chunk.type === 'tool-input-end') {
|
|
729
789
|
// Tool call is complete, keep it in the map for final processing
|
|
730
|
-
// Nothing specific to do here, the tool call is already complete
|
|
731
790
|
}
|
|
732
791
|
if (chunk.type === 'tool-call') {
|
|
733
792
|
// Direct tool call chunk (complete tool call)
|
|
@@ -739,14 +798,13 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
|
|
|
739
798
|
}
|
|
740
799
|
if (chunk.type === 'finish') {
|
|
741
800
|
providerMetadata = chunk.providerMetadata;
|
|
742
|
-
const additionalTokenValues = providerMetadata
|
|
743
|
-
|
|
744
|
-
} : {};
|
|
801
|
+
const additionalTokenValues = extractAdditionalTokenValues(providerMetadata);
|
|
802
|
+
const chunkUsage = chunk.usage || {};
|
|
745
803
|
usage = {
|
|
746
|
-
inputTokens: chunk.usage?.inputTokens,
|
|
747
|
-
outputTokens: chunk.usage?.outputTokens,
|
|
748
|
-
reasoningTokens:
|
|
749
|
-
cacheReadInputTokens:
|
|
804
|
+
inputTokens: extractTokenCount(chunk.usage?.inputTokens),
|
|
805
|
+
outputTokens: extractTokenCount(chunk.usage?.outputTokens),
|
|
806
|
+
reasoningTokens: extractReasoningTokens(chunkUsage),
|
|
807
|
+
cacheReadInputTokens: extractCacheReadTokens(chunkUsage),
|
|
750
808
|
...additionalTokenValues
|
|
751
809
|
};
|
|
752
810
|
}
|
|
@@ -788,24 +846,7 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
|
|
|
788
846
|
role: 'assistant',
|
|
789
847
|
content: content.length === 1 && content[0].type === 'text' ? content[0].text : content
|
|
790
848
|
}] : [];
|
|
791
|
-
|
|
792
|
-
// Calculate web search count based on provider
|
|
793
|
-
let webSearchCount = 0;
|
|
794
|
-
if (providerMetadata && typeof providerMetadata === 'object' && 'anthropic' in providerMetadata && providerMetadata.anthropic && typeof providerMetadata.anthropic === 'object' && 'server_tool_use' in providerMetadata.anthropic) {
|
|
795
|
-
// Anthropic-specific extraction
|
|
796
|
-
const serverToolUse = providerMetadata.anthropic.server_tool_use;
|
|
797
|
-
if (serverToolUse && typeof serverToolUse === 'object' && 'web_search_requests' in serverToolUse && typeof serverToolUse.web_search_requests === 'number') {
|
|
798
|
-
webSearchCount = serverToolUse.web_search_requests;
|
|
799
|
-
}
|
|
800
|
-
} else {
|
|
801
|
-
// For other providers through Vercel, pass available metadata to helper
|
|
802
|
-
// Note: Vercel abstracts provider responses, so we may not have access to
|
|
803
|
-
// raw citations/annotations unless Vercel exposes them in usage/metadata
|
|
804
|
-
webSearchCount = calculateWebSearchCount({
|
|
805
|
-
usage: usage,
|
|
806
|
-
providerMetadata: providerMetadata
|
|
807
|
-
});
|
|
808
|
-
}
|
|
849
|
+
const webSearchCount = extractWebSearchCount(providerMetadata, usage);
|
|
809
850
|
|
|
810
851
|
// Update usage with web search count
|
|
811
852
|
const finalUsage = {
|
|
@@ -814,11 +855,11 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
|
|
|
814
855
|
};
|
|
815
856
|
await sendEventToPosthog({
|
|
816
857
|
client: phClient,
|
|
817
|
-
distinctId:
|
|
818
|
-
traceId:
|
|
858
|
+
distinctId: mergedOptions.posthogDistinctId,
|
|
859
|
+
traceId: mergedOptions.posthogTraceId ?? v4(),
|
|
819
860
|
model: modelId,
|
|
820
861
|
provider: provider,
|
|
821
|
-
input:
|
|
862
|
+
input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
|
|
822
863
|
output: output,
|
|
823
864
|
latency,
|
|
824
865
|
baseURL,
|
|
@@ -826,7 +867,7 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
|
|
|
826
867
|
httpStatus: 200,
|
|
827
868
|
usage: finalUsage,
|
|
828
869
|
tools: availableTools,
|
|
829
|
-
captureImmediate:
|
|
870
|
+
captureImmediate: mergedOptions.posthogCaptureImmediate
|
|
830
871
|
});
|
|
831
872
|
}
|
|
832
873
|
});
|
|
@@ -837,11 +878,11 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
|
|
|
837
878
|
} catch (error) {
|
|
838
879
|
await sendEventToPosthog({
|
|
839
880
|
client: phClient,
|
|
840
|
-
distinctId:
|
|
841
|
-
traceId:
|
|
881
|
+
distinctId: mergedOptions.posthogDistinctId,
|
|
882
|
+
traceId: mergedOptions.posthogTraceId ?? v4(),
|
|
842
883
|
model: modelId,
|
|
843
884
|
provider: provider,
|
|
844
|
-
input:
|
|
885
|
+
input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
|
|
845
886
|
output: [],
|
|
846
887
|
latency: 0,
|
|
847
888
|
baseURL: '',
|
|
@@ -854,25 +895,12 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
|
|
|
854
895
|
isError: true,
|
|
855
896
|
error: truncate(JSON.stringify(error)),
|
|
856
897
|
tools: availableTools,
|
|
857
|
-
captureImmediate:
|
|
898
|
+
captureImmediate: mergedOptions.posthogCaptureImmediate
|
|
858
899
|
});
|
|
859
900
|
throw error;
|
|
860
901
|
}
|
|
861
902
|
}
|
|
862
903
|
};
|
|
863
|
-
return middleware;
|
|
864
|
-
};
|
|
865
|
-
const wrapVercelLanguageModel = (model, phClient, options) => {
|
|
866
|
-
const traceId = options.posthogTraceId ?? v4();
|
|
867
|
-
const middleware = createInstrumentationMiddleware(phClient, model, {
|
|
868
|
-
...options,
|
|
869
|
-
posthogTraceId: traceId,
|
|
870
|
-
posthogDistinctId: options.posthogDistinctId
|
|
871
|
-
});
|
|
872
|
-
const wrappedModel = wrapLanguageModel({
|
|
873
|
-
model,
|
|
874
|
-
middleware
|
|
875
|
-
});
|
|
876
904
|
return wrappedModel;
|
|
877
905
|
};
|
|
878
906
|
|