@turingpulse/sdk 1.0.1

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 (160) hide show
  1. package/.github/dependabot.yml +38 -0
  2. package/.github/workflows/ci.yml +246 -0
  3. package/.github/workflows/framework-compat.yml +169 -0
  4. package/.github/workflows/security.yml +336 -0
  5. package/CHANGELOG.md +29 -0
  6. package/LICENSE +13 -0
  7. package/MIGRATION.md +30 -0
  8. package/README.md +221 -0
  9. package/dist/attachments.d.ts +28 -0
  10. package/dist/attachments.d.ts.map +1 -0
  11. package/dist/attachments.js +59 -0
  12. package/dist/attachments.js.map +1 -0
  13. package/dist/config.d.ts +72 -0
  14. package/dist/config.d.ts.map +1 -0
  15. package/dist/config.js +78 -0
  16. package/dist/config.js.map +1 -0
  17. package/dist/context.d.ts +126 -0
  18. package/dist/context.d.ts.map +1 -0
  19. package/dist/context.js +163 -0
  20. package/dist/context.js.map +1 -0
  21. package/dist/decorators.d.ts +6 -0
  22. package/dist/decorators.d.ts.map +1 -0
  23. package/dist/decorators.js +52 -0
  24. package/dist/decorators.js.map +1 -0
  25. package/dist/deploy.d.ts +89 -0
  26. package/dist/deploy.d.ts.map +1 -0
  27. package/dist/deploy.js +203 -0
  28. package/dist/deploy.js.map +1 -0
  29. package/dist/errors.d.ts +18 -0
  30. package/dist/errors.d.ts.map +1 -0
  31. package/dist/errors.js +34 -0
  32. package/dist/errors.js.map +1 -0
  33. package/dist/eventBuilder.d.ts +21 -0
  34. package/dist/eventBuilder.d.ts.map +1 -0
  35. package/dist/eventBuilder.js +127 -0
  36. package/dist/eventBuilder.js.map +1 -0
  37. package/dist/fingerprint.d.ts +158 -0
  38. package/dist/fingerprint.d.ts.map +1 -0
  39. package/dist/fingerprint.js +339 -0
  40. package/dist/fingerprint.js.map +1 -0
  41. package/dist/governance.d.ts +47 -0
  42. package/dist/governance.d.ts.map +1 -0
  43. package/dist/governance.js +104 -0
  44. package/dist/governance.js.map +1 -0
  45. package/dist/http.d.ts +62 -0
  46. package/dist/http.d.ts.map +1 -0
  47. package/dist/http.js +181 -0
  48. package/dist/http.js.map +1 -0
  49. package/dist/index.d.ts +15 -0
  50. package/dist/index.d.ts.map +1 -0
  51. package/dist/index.js +23 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/instrumentation.d.ts +40 -0
  54. package/dist/instrumentation.d.ts.map +1 -0
  55. package/dist/instrumentation.js +31 -0
  56. package/dist/instrumentation.js.map +1 -0
  57. package/dist/integrations/mastra.d.ts +64 -0
  58. package/dist/integrations/mastra.d.ts.map +1 -0
  59. package/dist/integrations/mastra.js +256 -0
  60. package/dist/integrations/mastra.js.map +1 -0
  61. package/dist/kpi.d.ts +21 -0
  62. package/dist/kpi.d.ts.map +1 -0
  63. package/dist/kpi.js +83 -0
  64. package/dist/kpi.js.map +1 -0
  65. package/dist/llmDetector.d.ts +22 -0
  66. package/dist/llmDetector.d.ts.map +1 -0
  67. package/dist/llmDetector.js +269 -0
  68. package/dist/llmDetector.js.map +1 -0
  69. package/dist/plugin.d.ts +33 -0
  70. package/dist/plugin.d.ts.map +1 -0
  71. package/dist/plugin.js +312 -0
  72. package/dist/plugin.js.map +1 -0
  73. package/dist/registry.d.ts +13 -0
  74. package/dist/registry.d.ts.map +1 -0
  75. package/dist/registry.js +18 -0
  76. package/dist/registry.js.map +1 -0
  77. package/dist/tracing.d.ts +10 -0
  78. package/dist/tracing.d.ts.map +1 -0
  79. package/dist/tracing.js +30 -0
  80. package/dist/tracing.js.map +1 -0
  81. package/dist/triggerState.d.ts +5 -0
  82. package/dist/triggerState.d.ts.map +1 -0
  83. package/dist/triggerState.js +19 -0
  84. package/dist/triggerState.js.map +1 -0
  85. package/dist/utils.d.ts +27 -0
  86. package/dist/utils.d.ts.map +1 -0
  87. package/dist/utils.js +72 -0
  88. package/dist/utils.js.map +1 -0
  89. package/package.json +37 -0
  90. package/packages/anthropic/package.json +16 -0
  91. package/packages/anthropic/src/index.ts +5 -0
  92. package/packages/anthropic/src/wrapper.ts +102 -0
  93. package/packages/anthropic/tsconfig.build.json +20 -0
  94. package/packages/langchain/package.json +16 -0
  95. package/packages/langchain/src/index.ts +7 -0
  96. package/packages/langchain/src/wrapper.ts +51 -0
  97. package/packages/mastra/package.json +17 -0
  98. package/packages/mastra/src/index.ts +8 -0
  99. package/packages/mastra/src/wrapper.ts +301 -0
  100. package/packages/openai/package.json +16 -0
  101. package/packages/openai/src/index.ts +8 -0
  102. package/packages/openai/src/wrapper.ts +103 -0
  103. package/packages/openai/tsconfig.build.json +20 -0
  104. package/packages/openclaw/openclaw.plugin.json +100 -0
  105. package/packages/openclaw/package.json +41 -0
  106. package/packages/openclaw/src/buffer.ts +99 -0
  107. package/packages/openclaw/src/config.ts +139 -0
  108. package/packages/openclaw/src/hooks/governance.ts +267 -0
  109. package/packages/openclaw/src/hooks/lifecycle.ts +75 -0
  110. package/packages/openclaw/src/hooks/telemetry.ts +207 -0
  111. package/packages/openclaw/src/index.ts +91 -0
  112. package/packages/openclaw/src/mapper.ts +233 -0
  113. package/packages/openclaw/src/session-tracker.ts +181 -0
  114. package/packages/openclaw/src/types.ts +220 -0
  115. package/packages/openclaw/tests/buffer.test.ts +148 -0
  116. package/packages/openclaw/tests/config.test.ts +122 -0
  117. package/packages/openclaw/tests/governance.test.ts +232 -0
  118. package/packages/openclaw/tests/mapper.test.ts +242 -0
  119. package/packages/openclaw/tests/session-tracker.test.ts +124 -0
  120. package/packages/openclaw/tsconfig.json +18 -0
  121. package/packages/openclaw/vitest.config.ts +8 -0
  122. package/packages/vercel-ai/package.json +16 -0
  123. package/packages/vercel-ai/src/index.ts +5 -0
  124. package/packages/vercel-ai/src/wrapper.ts +49 -0
  125. package/scripts/bump-version.sh +58 -0
  126. package/scripts/update-readme-compat.mjs +151 -0
  127. package/src/__tests__/fingerprint.test.ts +328 -0
  128. package/src/attachments.ts +88 -0
  129. package/src/config.ts +164 -0
  130. package/src/context.ts +258 -0
  131. package/src/decorators.ts +61 -0
  132. package/src/deploy.ts +260 -0
  133. package/src/errors.ts +44 -0
  134. package/src/eventBuilder.ts +153 -0
  135. package/src/fingerprint.ts +421 -0
  136. package/src/governance.ts +156 -0
  137. package/src/http.ts +241 -0
  138. package/src/index.ts +57 -0
  139. package/src/instrumentation.ts +68 -0
  140. package/src/integrations/mastra.ts +335 -0
  141. package/src/kpi.ts +112 -0
  142. package/src/llmDetector.ts +330 -0
  143. package/src/plugin.ts +384 -0
  144. package/src/registry.ts +27 -0
  145. package/src/tracing.ts +39 -0
  146. package/src/triggerState.ts +27 -0
  147. package/src/utils.ts +78 -0
  148. package/tests/compat/anthropic.test.ts +61 -0
  149. package/tests/compat/cohere.test.ts +57 -0
  150. package/tests/compat/google-genai.test.ts +61 -0
  151. package/tests/compat/langchain-openai.test.ts +41 -0
  152. package/tests/compat/langchain.test.ts +64 -0
  153. package/tests/compat/mistral.test.ts +58 -0
  154. package/tests/compat/openai.test.ts +71 -0
  155. package/tests/compat/vercel-ai.test.ts +56 -0
  156. package/tests/plugins/anthropic-wrapper.test.ts +120 -0
  157. package/tests/plugins/langchain-wrapper.test.ts +128 -0
  158. package/tests/plugins/openai-wrapper.test.ts +165 -0
  159. package/tsconfig.json +21 -0
  160. package/vitest.config.ts +9 -0
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Compatibility tests for the Google Generative AI TypeScript SDK.
3
+ */
4
+ import { describe, it, expect } from 'vitest';
5
+ import { FingerprintBuilder, classifyConfigKey } from '../../src/fingerprint.js';
6
+
7
+ // ── Import guard ──────────────────────────────────────────────────────────
8
+
9
+ describe('Google GenAI import', () => {
10
+ it('@google/generative-ai is importable', async () => {
11
+ const mod = await import('@google/generative-ai');
12
+ expect(mod).toBeDefined();
13
+ });
14
+ });
15
+
16
+ // ── Key classification ────────────────────────────────────────────────────
17
+
18
+ describe('Google GenAI key classification', () => {
19
+ const modelKeys = [
20
+ 'model', 'temperature', 'top_p', 'top_k',
21
+ 'max_output_tokens', 'candidate_count', 'stop_sequences',
22
+ 'maxOutputTokens', 'topP', 'topK', 'candidateCount', 'stopSequences',
23
+ ];
24
+ const generalKeys = ['safetySettings', 'generationConfig'];
25
+
26
+ it.each(modelKeys)('classifies %s as model', (key) => {
27
+ expect(classifyConfigKey(key)).toBe('model');
28
+ });
29
+
30
+ it.each(generalKeys)('classifies %s as general', (key) => {
31
+ expect(classifyConfigKey(key)).toBe('general');
32
+ });
33
+ });
34
+
35
+ // ── Fingerprint hashing ──────────────────────────────────────────────────
36
+
37
+ describe('Google GenAI fingerprint', () => {
38
+ it('produces model hash', () => {
39
+ const builder = new FingerprintBuilder({ captureConfigs: true, capturePrompts: true });
40
+ builder.recordNode('gemini', 'llm', {
41
+ model: 'gemini-1.5-pro',
42
+ temperature: 0.4,
43
+ maxOutputTokens: 2048,
44
+ topP: 0.8,
45
+ topK: 32,
46
+ });
47
+ const fp = builder.getFingerprint();
48
+ expect(fp.node_model_hashes['gemini']).toBeDefined();
49
+ });
50
+
51
+ it('maxOutputTokens change alters model hash', () => {
52
+ const before = new FingerprintBuilder({ captureConfigs: true, capturePrompts: true });
53
+ const after = new FingerprintBuilder({ captureConfigs: true, capturePrompts: true });
54
+
55
+ before.recordNode('llm', 'llm', { model: 'gemini-1.5-pro', maxOutputTokens: 2048 });
56
+ after.recordNode('llm', 'llm', { model: 'gemini-1.5-pro', maxOutputTokens: 4096 });
57
+
58
+ expect(before.getFingerprint().node_model_hashes['llm'])
59
+ .not.toBe(after.getFingerprint().node_model_hashes['llm']);
60
+ });
61
+ });
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Compatibility tests for the @langchain/openai package.
3
+ */
4
+ import { describe, it, expect } from 'vitest';
5
+ import { FingerprintBuilder, classifyConfigKey } from '../../src/fingerprint.js';
6
+
7
+ // ── Import guard ──────────────────────────────────────────────────────────
8
+
9
+ describe('LangChain-OpenAI import', () => {
10
+ it('@langchain/openai is importable', async () => {
11
+ const mod = await import('@langchain/openai');
12
+ expect(mod).toBeDefined();
13
+ });
14
+ });
15
+
16
+ // ── Fingerprint hashing ──────────────────────────────────────────────────
17
+
18
+ describe('LangChain-OpenAI fingerprint', () => {
19
+ it('produces model hash', () => {
20
+ const builder = new FingerprintBuilder({ captureConfigs: true, capturePrompts: true });
21
+ builder.recordNode('lc_openai', 'llm', {
22
+ model: 'gpt-4-turbo',
23
+ temperature: 0.3,
24
+ maxTokens: 4096,
25
+ streaming: true,
26
+ });
27
+ const fp = builder.getFingerprint();
28
+ expect(fp.node_model_hashes['lc_openai']).toBeDefined();
29
+ });
30
+
31
+ it('streaming change does not affect model hash', () => {
32
+ const before = new FingerprintBuilder({ captureConfigs: true, capturePrompts: true });
33
+ const after = new FingerprintBuilder({ captureConfigs: true, capturePrompts: true });
34
+
35
+ before.recordNode('llm', 'llm', { model: 'gpt-4-turbo', temperature: 0.3, streaming: false });
36
+ after.recordNode('llm', 'llm', { model: 'gpt-4-turbo', temperature: 0.3, streaming: true });
37
+
38
+ expect(before.getFingerprint().node_model_hashes['llm'])
39
+ .toBe(after.getFingerprint().node_model_hashes['llm']);
40
+ });
41
+ });
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Compatibility tests for the LangChain.js framework.
3
+ */
4
+ import { describe, it, expect } from 'vitest';
5
+ import { FingerprintBuilder, classifyConfigKey } from '../../src/fingerprint.js';
6
+
7
+ // ── Import guard ──────────────────────────────────────────────────────────
8
+
9
+ describe('LangChain import', () => {
10
+ it('@langchain/core is importable', async () => {
11
+ const mod = await import('@langchain/core');
12
+ expect(mod).toBeDefined();
13
+ });
14
+ });
15
+
16
+ // ── Key classification ────────────────────────────────────────────────────
17
+
18
+ describe('LangChain key classification', () => {
19
+ const modelKeys = [
20
+ 'model', 'model_name', 'modelName', 'temperature',
21
+ 'max_tokens', 'maxTokens', 'top_p', 'topP',
22
+ 'frequency_penalty', 'frequencyPenalty',
23
+ 'presence_penalty', 'presencePenalty',
24
+ 'model_kwargs', 'modelKwargs',
25
+ ];
26
+ const generalKeys = ['verbose', 'callbacks', 'cache', 'streaming', 'tags'];
27
+
28
+ it.each(modelKeys)('classifies %s as model', (key) => {
29
+ expect(classifyConfigKey(key)).toBe('model');
30
+ });
31
+
32
+ it.each(generalKeys)('classifies %s as general', (key) => {
33
+ expect(classifyConfigKey(key)).toBe('general');
34
+ });
35
+ });
36
+
37
+ // ── Fingerprint hashing ──────────────────────────────────────────────────
38
+
39
+ describe('LangChain fingerprint', () => {
40
+ it('produces model hash', () => {
41
+ const builder = new FingerprintBuilder({ captureConfigs: true, capturePrompts: true });
42
+ builder.recordNode('lc_chat', 'llm', {
43
+ modelName: 'gpt-4',
44
+ temperature: 0.0,
45
+ maxTokens: 2048,
46
+ verbose: true,
47
+ });
48
+ const fp = builder.getFingerprint();
49
+ expect(fp.node_model_hashes['lc_chat']).toBeDefined();
50
+ });
51
+
52
+ it('verbose change does not alter model hash', () => {
53
+ const before = new FingerprintBuilder({ captureConfigs: true, capturePrompts: true });
54
+ const after = new FingerprintBuilder({ captureConfigs: true, capturePrompts: true });
55
+
56
+ before.recordNode('llm', 'llm', { modelName: 'gpt-4', temperature: 0.0, verbose: false });
57
+ after.recordNode('llm', 'llm', { modelName: 'gpt-4', temperature: 0.0, verbose: true });
58
+
59
+ expect(before.getFingerprint().node_model_hashes['llm'])
60
+ .toBe(after.getFingerprint().node_model_hashes['llm']);
61
+ expect(before.getFingerprint().node_config_hashes['llm'])
62
+ .not.toBe(after.getFingerprint().node_config_hashes['llm']);
63
+ });
64
+ });
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Compatibility tests for the Mistral AI TypeScript SDK.
3
+ */
4
+ import { describe, it, expect } from 'vitest';
5
+ import { FingerprintBuilder, classifyConfigKey } from '../../src/fingerprint.js';
6
+
7
+ // ── Import guard ──────────────────────────────────────────────────────────
8
+
9
+ describe('Mistral import', () => {
10
+ it('@mistralai/mistralai is importable', async () => {
11
+ const mod = await import('@mistralai/mistralai');
12
+ expect(mod).toBeDefined();
13
+ });
14
+ });
15
+
16
+ // ── Key classification ────────────────────────────────────────────────────
17
+
18
+ describe('Mistral key classification', () => {
19
+ const modelKeys = ['model', 'temperature', 'top_p', 'topP', 'max_tokens', 'maxTokens', 'stop'];
20
+ const generalKeys = ['stream', 'safe_prompt'];
21
+
22
+ it.each(modelKeys)('classifies %s as model', (key) => {
23
+ expect(classifyConfigKey(key)).toBe('model');
24
+ });
25
+
26
+ it.each(generalKeys)('classifies %s as general', (key) => {
27
+ expect(classifyConfigKey(key)).toBe('general');
28
+ });
29
+ });
30
+
31
+ // ── Fingerprint hashing ──────────────────────────────────────────────────
32
+
33
+ describe('Mistral fingerprint', () => {
34
+ it('produces model hash', () => {
35
+ const builder = new FingerprintBuilder({ captureConfigs: true, capturePrompts: true });
36
+ builder.recordNode('mistral_chat', 'llm', {
37
+ model: 'mistral-large-latest',
38
+ temperature: 0.7,
39
+ maxTokens: 4096,
40
+ stream: false,
41
+ });
42
+ const fp = builder.getFingerprint();
43
+ expect(fp.node_model_hashes['mistral_chat']).toBeDefined();
44
+ });
45
+
46
+ it('safe_prompt is general', () => {
47
+ const before = new FingerprintBuilder({ captureConfigs: true, capturePrompts: true });
48
+ const after = new FingerprintBuilder({ captureConfigs: true, capturePrompts: true });
49
+
50
+ before.recordNode('llm', 'llm', { model: 'mistral-large-latest', temperature: 0.7, safe_prompt: false });
51
+ after.recordNode('llm', 'llm', { model: 'mistral-large-latest', temperature: 0.7, safe_prompt: true });
52
+
53
+ expect(before.getFingerprint().node_model_hashes['llm'])
54
+ .toBe(after.getFingerprint().node_model_hashes['llm']);
55
+ expect(before.getFingerprint().node_config_hashes['llm'])
56
+ .not.toBe(after.getFingerprint().node_config_hashes['llm']);
57
+ });
58
+ });
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Compatibility tests for the OpenAI Node.js SDK.
3
+ */
4
+ import { describe, it, expect } from 'vitest';
5
+ import { FingerprintBuilder, classifyConfigKey, splitConfig } from '../../src/fingerprint.js';
6
+
7
+ // ── Import guard ──────────────────────────────────────────────────────────
8
+
9
+ describe('OpenAI import', () => {
10
+ it('openai package is importable', async () => {
11
+ const mod = await import('openai');
12
+ expect(mod).toBeDefined();
13
+ });
14
+ });
15
+
16
+ // ── Key classification ────────────────────────────────────────────────────
17
+
18
+ describe('OpenAI key classification', () => {
19
+ const modelKeys = ['model', 'temperature', 'top_p', 'max_tokens', 'frequency_penalty', 'presence_penalty', 'stop', 'response_format'];
20
+ const camelKeys = ['maxTokens', 'topP', 'frequencyPenalty', 'presencePenalty', 'responseFormat'];
21
+ const generalKeys = ['stream', 'n', 'logprobs', 'user', 'seed'];
22
+
23
+ it.each(modelKeys)('classifies %s as model', (key) => {
24
+ expect(classifyConfigKey(key)).toBe('model');
25
+ });
26
+
27
+ it.each(camelKeys)('classifies camelCase %s as model', (key) => {
28
+ expect(classifyConfigKey(key)).toBe('model');
29
+ });
30
+
31
+ it.each(generalKeys)('classifies %s as general', (key) => {
32
+ expect(classifyConfigKey(key)).toBe('general');
33
+ });
34
+ });
35
+
36
+ // ── Fingerprint hashing ──────────────────────────────────────────────────
37
+
38
+ describe('OpenAI fingerprint', () => {
39
+ it('produces model hash for OpenAI config', () => {
40
+ const builder = new FingerprintBuilder({ captureConfigs: true, capturePrompts: true });
41
+ builder.recordNode('chat_llm', 'llm', { model: 'gpt-4o', temperature: 0.7, max_tokens: 4096, stream: true });
42
+ const fp = builder.getFingerprint();
43
+ expect(fp.node_model_hashes['chat_llm']).toBeDefined();
44
+ expect(fp.node_config_hashes['chat_llm']).toBeDefined();
45
+ });
46
+
47
+ it('model change alters model hash only', () => {
48
+ const before = new FingerprintBuilder({ captureConfigs: true, capturePrompts: true });
49
+ const after = new FingerprintBuilder({ captureConfigs: true, capturePrompts: true });
50
+
51
+ const base = { model: 'gpt-4o', temperature: 0.7, stream: true };
52
+ before.recordNode('llm', 'llm', { ...base, temperature: 0.7 });
53
+ after.recordNode('llm', 'llm', { ...base, temperature: 0.2 });
54
+
55
+ expect(before.getFingerprint().node_model_hashes['llm'])
56
+ .not.toBe(after.getFingerprint().node_model_hashes['llm']);
57
+ });
58
+
59
+ it('general-only change does not affect model hash', () => {
60
+ const before = new FingerprintBuilder({ captureConfigs: true, capturePrompts: true });
61
+ const after = new FingerprintBuilder({ captureConfigs: true, capturePrompts: true });
62
+
63
+ before.recordNode('llm', 'llm', { model: 'gpt-4o', temperature: 0.7, stream: true });
64
+ after.recordNode('llm', 'llm', { model: 'gpt-4o', temperature: 0.7, stream: false });
65
+
66
+ expect(before.getFingerprint().node_model_hashes['llm'])
67
+ .toBe(after.getFingerprint().node_model_hashes['llm']);
68
+ expect(before.getFingerprint().node_config_hashes['llm'])
69
+ .not.toBe(after.getFingerprint().node_config_hashes['llm']);
70
+ });
71
+ });
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Compatibility tests for the Vercel AI SDK.
3
+ */
4
+ import { describe, it, expect } from 'vitest';
5
+ import { FingerprintBuilder, classifyConfigKey } from '../../src/fingerprint.js';
6
+
7
+ // ── Import guard ──────────────────────────────────────────────────────────
8
+
9
+ describe('Vercel AI import', () => {
10
+ it('ai package is importable', async () => {
11
+ const mod = await import('ai');
12
+ expect(mod).toBeDefined();
13
+ });
14
+ });
15
+
16
+ // ── Key classification ────────────────────────────────────────────────────
17
+
18
+ describe('Vercel AI key classification', () => {
19
+ const modelKeys = ['model', 'temperature', 'topP', 'maxTokens', 'frequencyPenalty', 'presencePenalty', 'stopSequences'];
20
+ const generalKeys = ['onFinish', 'onStepFinish', 'experimental_telemetry'];
21
+
22
+ it.each(modelKeys)('classifies %s as model', (key) => {
23
+ expect(classifyConfigKey(key)).toBe('model');
24
+ });
25
+
26
+ it.each(generalKeys)('classifies %s as general', (key) => {
27
+ expect(classifyConfigKey(key)).toBe('general');
28
+ });
29
+ });
30
+
31
+ // ── Fingerprint hashing ──────────────────────────────────────────────────
32
+
33
+ describe('Vercel AI fingerprint', () => {
34
+ it('produces model hash', () => {
35
+ const builder = new FingerprintBuilder({ captureConfigs: true, capturePrompts: true });
36
+ builder.recordNode('vercel_chat', 'llm', {
37
+ model: 'gpt-4o',
38
+ temperature: 0.5,
39
+ maxTokens: 2048,
40
+ topP: 0.9,
41
+ });
42
+ const fp = builder.getFingerprint();
43
+ expect(fp.node_model_hashes['vercel_chat']).toBeDefined();
44
+ });
45
+
46
+ it('temperature change alters model hash', () => {
47
+ const before = new FingerprintBuilder({ captureConfigs: true, capturePrompts: true });
48
+ const after = new FingerprintBuilder({ captureConfigs: true, capturePrompts: true });
49
+
50
+ before.recordNode('llm', 'llm', { model: 'gpt-4o', temperature: 0.5 });
51
+ after.recordNode('llm', 'llm', { model: 'gpt-4o', temperature: 0.1 });
52
+
53
+ expect(before.getFingerprint().node_model_hashes['llm'])
54
+ .not.toBe(after.getFingerprint().node_model_hashes['llm']);
55
+ });
56
+ });
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Integration tests for @turingpulse/sdk-anthropic plugin.
3
+ *
4
+ * Verifies patchAnthropic monkey-patches the caller's Anthropic module
5
+ * and emits events with correct metadata fields.
6
+ */
7
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
8
+ import { init } from '@turingpulse/sdk';
9
+ import { patchAnthropic, unpatchAnthropic } from '../../packages/anthropic/src/wrapper.js';
10
+
11
+ const MOCK_RESPONSE = {
12
+ id: 'msg_test',
13
+ type: 'message',
14
+ role: 'assistant',
15
+ model: 'claude-3-sonnet-20240229',
16
+ content: [{ type: 'text', text: 'instrumented reply' }],
17
+ stop_reason: 'end_turn',
18
+ usage: { input_tokens: 8, output_tokens: 4 },
19
+ };
20
+
21
+ let capturedEvents: Record<string, unknown>[] = [];
22
+ let originalFetch: typeof globalThis.fetch;
23
+
24
+ beforeEach(() => {
25
+ capturedEvents = [];
26
+ originalFetch = globalThis.fetch;
27
+
28
+ globalThis.fetch = async (input: RequestInfo | URL, initOpts?: RequestInit): Promise<Response> => {
29
+ const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : (input as Request).url;
30
+
31
+ if (url.includes('api.anthropic.com')) {
32
+ return new Response(JSON.stringify(MOCK_RESPONSE), {
33
+ status: 200,
34
+ headers: { 'Content-Type': 'application/json' },
35
+ });
36
+ }
37
+
38
+ if (url.includes('/api/v1/sdk/events') && initOpts?.body) {
39
+ try {
40
+ const parsed = JSON.parse(initOpts.body as string);
41
+ capturedEvents.push(...(parsed.events ?? [parsed]));
42
+ } catch { /* ignore */ }
43
+ return new Response(JSON.stringify({ success: true }), {
44
+ status: 200,
45
+ headers: { 'Content-Type': 'application/json' },
46
+ });
47
+ }
48
+
49
+ return new Response('{}', { status: 200 });
50
+ };
51
+
52
+ init({
53
+ apiKey: 'test-key',
54
+ workflowName: 'test-anthropic-plugin',
55
+ endpoint: 'https://test-endpoint.turingpulse.ai',
56
+ fetchImpl: globalThis.fetch,
57
+ });
58
+ });
59
+
60
+ afterEach(async () => {
61
+ await unpatchAnthropic();
62
+ globalThis.fetch = originalFetch;
63
+ });
64
+
65
+ describe('patchAnthropic plugin wrapper', () => {
66
+ it('patches the caller-supplied Anthropic module prototype', async () => {
67
+ const anthropicMod = await import('@anthropic-ai/sdk');
68
+ const cls = anthropicMod.default ?? anthropicMod;
69
+ const proto = (cls as Record<string, unknown>).Messages
70
+ ? ((cls as Record<string, unknown>).Messages as Record<string, unknown>).prototype as Record<string, unknown>
71
+ : undefined;
72
+
73
+ if (!proto) {
74
+ console.warn('Could not resolve Anthropic.Messages.prototype — skipping');
75
+ return;
76
+ }
77
+
78
+ const originalName = (proto.create as Function).name;
79
+ await patchAnthropic({ name: 'test-workflow', anthropicModule: anthropicMod });
80
+
81
+ expect((proto.create as Function).name).toBe('instrumented');
82
+ expect((proto.create as Function).name).not.toBe(originalName);
83
+ });
84
+
85
+ it('emits event with correct node_type, model, provider, tokens', async () => {
86
+ const anthropicMod = await import('@anthropic-ai/sdk');
87
+ await patchAnthropic({ name: 'test-anthropic', anthropicModule: anthropicMod });
88
+
89
+ const Anthropic = anthropicMod.default;
90
+ const client = new Anthropic({ apiKey: 'mock-key' });
91
+ await client.messages.create({
92
+ model: 'claude-3-sonnet-20240229',
93
+ max_tokens: 100,
94
+ messages: [{ role: 'user', content: 'hello from test' }],
95
+ });
96
+
97
+ await new Promise((r) => setTimeout(r, 500));
98
+
99
+ expect(capturedEvents.length).toBeGreaterThanOrEqual(1);
100
+
101
+ const event = capturedEvents[0] as Record<string, unknown>;
102
+ const payload = event.payload as Record<string, unknown>;
103
+ const metadata = payload.metadata as Record<string, string>;
104
+
105
+ expect(metadata.node_type).toBe('llm');
106
+ expect(metadata.model).toBe('claude-3-sonnet-20240229');
107
+ expect(metadata.provider).toBe('anthropic');
108
+ expect(metadata.framework).toBe('anthropic');
109
+ expect(metadata.input).toBeDefined();
110
+ expect(metadata.output).toBeDefined();
111
+
112
+ const tokens = payload.tokens as { prompt: number; completion: number };
113
+ expect(tokens.prompt).toBe(8);
114
+ expect(tokens.completion).toBe(4);
115
+ });
116
+
117
+ it('throws when name is missing', async () => {
118
+ await expect(patchAnthropic({})).rejects.toThrow(/name or TP_WORKFLOW_NAME/);
119
+ });
120
+ });
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Integration tests for @turingpulse/sdk-langchain plugin.
3
+ *
4
+ * Verifies instrumentLangchain wraps a Runnable and emits events
5
+ * with correct metadata fields (node_type, model, provider, I/O).
6
+ */
7
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
8
+ import { init } from '@turingpulse/sdk';
9
+ import { instrumentLangchain } from '../../packages/langchain/src/wrapper.js';
10
+
11
+ let capturedEvents: Record<string, unknown>[] = [];
12
+ let originalFetch: typeof globalThis.fetch;
13
+
14
+ beforeEach(() => {
15
+ capturedEvents = [];
16
+ originalFetch = globalThis.fetch;
17
+
18
+ globalThis.fetch = async (input: RequestInfo | URL, initOpts?: RequestInit): Promise<Response> => {
19
+ const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : (input as Request).url;
20
+
21
+ if (url.includes('/api/v1/sdk/events') && initOpts?.body) {
22
+ try {
23
+ const parsed = JSON.parse(initOpts.body as string);
24
+ capturedEvents.push(...(parsed.events ?? [parsed]));
25
+ } catch { /* ignore */ }
26
+ return new Response(JSON.stringify({ success: true }), {
27
+ status: 200,
28
+ headers: { 'Content-Type': 'application/json' },
29
+ });
30
+ }
31
+
32
+ return new Response('{}', { status: 200 });
33
+ };
34
+
35
+ init({
36
+ apiKey: 'test-key',
37
+ workflowName: 'test-langchain-plugin',
38
+ endpoint: 'https://test-endpoint.turingpulse.ai',
39
+ fetchImpl: globalThis.fetch,
40
+ });
41
+ });
42
+
43
+ afterEach(() => {
44
+ globalThis.fetch = originalFetch;
45
+ });
46
+
47
+ describe('instrumentLangchain plugin wrapper', () => {
48
+ it('wraps a runnable and emits event with correct metadata', async () => {
49
+ const mockRunnable = {
50
+ invoke: async (input: unknown) => ({
51
+ content: `Response to: ${input}`,
52
+ response_metadata: {
53
+ model_name: 'gpt-4o',
54
+ tokenUsage: { promptTokens: 15, completionTokens: 10 },
55
+ },
56
+ }),
57
+ };
58
+
59
+ const runner = instrumentLangchain(mockRunnable, {
60
+ name: 'langchain-test-agent',
61
+ model: 'gpt-4o',
62
+ provider: 'openai',
63
+ });
64
+
65
+ await runner('what is AI?');
66
+ await new Promise((r) => setTimeout(r, 500));
67
+
68
+ expect(capturedEvents.length).toBeGreaterThanOrEqual(1);
69
+
70
+ const event = capturedEvents[0] as Record<string, unknown>;
71
+ const payload = event.payload as Record<string, unknown>;
72
+ const metadata = payload.metadata as Record<string, string>;
73
+
74
+ expect(metadata.node_type).toBe('llm');
75
+ expect(metadata.framework).toBe('langchain');
76
+ expect(metadata.model).toBe('gpt-4o');
77
+ expect(metadata.provider).toBe('openai');
78
+ expect(metadata.input).toBeDefined();
79
+ expect(metadata.output).toBeDefined();
80
+ });
81
+
82
+ it('extracts token counts from response_metadata', async () => {
83
+ const mockRunnable = {
84
+ invoke: async () => ({
85
+ content: 'reply',
86
+ response_metadata: {
87
+ tokenUsage: { promptTokens: 20, completionTokens: 12 },
88
+ },
89
+ }),
90
+ };
91
+
92
+ const runner = instrumentLangchain(mockRunnable, {
93
+ name: 'token-test',
94
+ model: 'gpt-4o-mini',
95
+ });
96
+
97
+ await runner('test prompt');
98
+ await new Promise((r) => setTimeout(r, 500));
99
+
100
+ const payload = (capturedEvents[0] as Record<string, unknown>).payload as Record<string, unknown>;
101
+ const tokens = payload.tokens as { prompt: number; completion: number };
102
+
103
+ expect(tokens.prompt).toBe(20);
104
+ expect(tokens.completion).toBe(12);
105
+ });
106
+
107
+ it('uses provided model and provider when response has no model_name', async () => {
108
+ const mockRunnable = {
109
+ invoke: async () => ({ content: 'plain response' }),
110
+ };
111
+
112
+ const runner = instrumentLangchain(mockRunnable, {
113
+ name: 'model-fallback',
114
+ model: 'claude-3-haiku',
115
+ provider: 'anthropic',
116
+ });
117
+
118
+ await runner('test');
119
+ await new Promise((r) => setTimeout(r, 500));
120
+
121
+ const metadata = (
122
+ (capturedEvents[0] as Record<string, unknown>).payload as Record<string, unknown>
123
+ ).metadata as Record<string, string>;
124
+
125
+ expect(metadata.model).toBe('claude-3-haiku');
126
+ expect(metadata.provider).toBe('anthropic');
127
+ });
128
+ });