@outputai/llm 0.8.2-next.c12766f.0 → 0.8.2-next.e658cc2.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/package.json +10 -7
- package/src/agent.js +3 -3
- package/src/agent.spec.js +4 -2
- package/src/cost/fetch_models_pricing.js +4 -2
- package/src/cost/fetch_models_pricing.spec.js +19 -32
- package/src/cost/index.js +5 -4
- package/src/cost/index.spec.js +2 -7
- package/src/prompt/escape.js +2 -2
- package/src/prompt/load_content.js +2 -2
- package/src/prompt/load_content.spec.js +5 -3
- package/src/prompt/validations.js +3 -3
- package/src/prompt/validations.spec.js +1 -8
- package/src/utils/trace.js +2 -2
- package/src/utils/trace.spec.js +8 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@outputai/llm",
|
|
3
|
-
"version": "0.8.2-next.
|
|
3
|
+
"version": "0.8.2-next.e658cc2.0",
|
|
4
4
|
"description": "Framework abstraction to interact with LLM models",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -12,29 +12,32 @@
|
|
|
12
12
|
"entities": "8.0.0",
|
|
13
13
|
"gray-matter": "4.0.3",
|
|
14
14
|
"liquidjs": "10.25.7",
|
|
15
|
-
"undici": "8.
|
|
16
|
-
"@outputai/core": "0.8.2-next.
|
|
15
|
+
"undici": "8.5.0",
|
|
16
|
+
"@outputai/core": "0.8.2-next.e658cc2.0"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"ai": "6.0.168",
|
|
20
19
|
"@ai-sdk/amazon-bedrock": "4.0.111",
|
|
21
20
|
"@ai-sdk/anthropic": "3.0.81",
|
|
22
21
|
"@ai-sdk/azure": "3.0.68",
|
|
23
22
|
"@ai-sdk/google-vertex": "4.0.140",
|
|
24
23
|
"@ai-sdk/openai": "3.0.67",
|
|
25
|
-
"@ai-sdk/perplexity": "3.0.33"
|
|
24
|
+
"@ai-sdk/perplexity": "3.0.33",
|
|
25
|
+
"ai": "6.0.168"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
|
-
"ai": ">=6 <7",
|
|
29
28
|
"@ai-sdk/amazon-bedrock": ">=4 <5",
|
|
30
29
|
"@ai-sdk/anthropic": ">=3 <4",
|
|
31
30
|
"@ai-sdk/azure": ">=3 <4",
|
|
32
31
|
"@ai-sdk/google-vertex": ">=4 <5",
|
|
33
32
|
"@ai-sdk/openai": ">=3 <4",
|
|
34
|
-
"@ai-sdk/perplexity": ">=3 <4"
|
|
33
|
+
"@ai-sdk/perplexity": ">=3 <4",
|
|
34
|
+
"ai": ">=6 <7"
|
|
35
35
|
},
|
|
36
36
|
"license": "Apache-2.0",
|
|
37
37
|
"publishConfig": {
|
|
38
38
|
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "pnpm exec tsc -p ./tsconfig.typecheck.json"
|
|
39
42
|
}
|
|
40
43
|
}
|
package/src/agent.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ValidationError } from '@outputai/core';
|
|
2
|
-
import {
|
|
2
|
+
import { Path } from '@outputai/core/sdk/helpers';
|
|
3
3
|
import { ToolLoopAgent as AIToolLoopAgent, stepCountIs } from 'ai';
|
|
4
4
|
import { loadAiSdkTextOptions } from './ai_sdk_options.js';
|
|
5
5
|
import { prepareTextPrompt } from './prompt/prepare_text.js';
|
|
@@ -38,8 +38,8 @@ export class Agent extends AIToolLoopAgent {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
// Must be captured synchronously — Temporal async activity execution
|
|
41
|
-
// breaks the call stack, so resolveInvocationDir() fails if called lazily.
|
|
42
|
-
const resolvedPromptDir = promptDir ?? resolveInvocationDir();
|
|
41
|
+
// breaks the call stack, so Path.resolveInvocationDir() fails if called lazily.
|
|
42
|
+
const resolvedPromptDir = promptDir ?? Path.resolveInvocationDir();
|
|
43
43
|
|
|
44
44
|
const { loadedPrompt, tools } = prepareTextPrompt( { prompt, variables, promptDir: resolvedPromptDir, skills, tools: toolsArg } );
|
|
45
45
|
|
package/src/agent.spec.js
CHANGED
|
@@ -46,8 +46,10 @@ vi.mock( '@outputai/core', () => ( {
|
|
|
46
46
|
ValidationError: coreMocks.ValidationError
|
|
47
47
|
} ) );
|
|
48
48
|
|
|
49
|
-
vi.mock( '@outputai/core/
|
|
50
|
-
|
|
49
|
+
vi.mock( '@outputai/core/sdk/helpers', () => ( {
|
|
50
|
+
Path: {
|
|
51
|
+
resolveInvocationDir: () => state.invocationDir
|
|
52
|
+
}
|
|
51
53
|
} ) );
|
|
52
54
|
|
|
53
55
|
vi.mock( 'ai', () => {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Logger } from '@outputai/core';
|
|
2
|
+
|
|
1
3
|
const costTableUrl = 'https://models.dev/api.json';
|
|
2
4
|
const cacheTTL = 1000 * 60 * 60 * 24; // 1 day
|
|
3
5
|
|
|
@@ -26,10 +28,10 @@ export const fetchModelsPricing = async () => {
|
|
|
26
28
|
const res = await fetch( costTableUrl );
|
|
27
29
|
if ( !res.ok ) {
|
|
28
30
|
if ( cache.content ) {
|
|
29
|
-
|
|
31
|
+
Logger.warn( `Error ${res.status} when fetching models pricing at ${costTableUrl}, falling back to stale cache`, { namespace: 'LLM' } );
|
|
30
32
|
return cache.content;
|
|
31
33
|
}
|
|
32
|
-
|
|
34
|
+
Logger.error( `Error ${res.status} when fetching models pricing at ${costTableUrl}`, { namespace: 'LLM' } );
|
|
33
35
|
return null;
|
|
34
36
|
}
|
|
35
37
|
cache.content = buildModelMap( await res.json() );
|
|
@@ -9,24 +9,29 @@ const fixturePath = join( __dirname, 'fixtures', 'models_api_light.json' );
|
|
|
9
9
|
const fixture = JSON.parse( readFileSync( fixturePath, 'utf8' ) );
|
|
10
10
|
|
|
11
11
|
const costTableUrl = 'https://models.dev/api.json';
|
|
12
|
-
const
|
|
13
|
-
|
|
12
|
+
const okResponse = data => ( {
|
|
13
|
+
ok: true,
|
|
14
|
+
json: () => Promise.resolve( data )
|
|
15
|
+
} );
|
|
16
|
+
const stubFetch = response => {
|
|
17
|
+
const fetchMock = vi.fn().mockResolvedValue( response );
|
|
18
|
+
vi.stubGlobal( 'fetch', fetchMock );
|
|
19
|
+
return fetchMock;
|
|
20
|
+
};
|
|
14
21
|
|
|
15
22
|
describe( 'fetchModelsPricing', () => {
|
|
16
23
|
beforeEach( () => {
|
|
17
24
|
cache.content = null;
|
|
18
25
|
cache.expiresAt = 0;
|
|
19
|
-
vi.
|
|
26
|
+
vi.unstubAllGlobals();
|
|
20
27
|
} );
|
|
21
28
|
|
|
22
29
|
it( 'returns a Map of model costs when fetch succeeds', async () => {
|
|
23
|
-
|
|
24
|
-
ok: true,
|
|
25
|
-
json: () => Promise.resolve( fixture )
|
|
26
|
-
} ) );
|
|
30
|
+
const fetchMock = stubFetch( okResponse( fixture ) );
|
|
27
31
|
|
|
28
32
|
const result = await fetchModelsPricing();
|
|
29
33
|
|
|
34
|
+
expect( fetchMock ).toHaveBeenCalledWith( costTableUrl );
|
|
30
35
|
expect( result ).toBeInstanceOf( Map );
|
|
31
36
|
expect( result.size ).toBeGreaterThan( 0 );
|
|
32
37
|
const firstModel = Object.values( fixture )[0];
|
|
@@ -37,10 +42,7 @@ describe( 'fetchModelsPricing', () => {
|
|
|
37
42
|
} );
|
|
38
43
|
|
|
39
44
|
it( 'includes main providers from fixture (openai, anthropic, google, nvidia, perplexity)', async () => {
|
|
40
|
-
|
|
41
|
-
ok: true,
|
|
42
|
-
json: () => Promise.resolve( fixture )
|
|
43
|
-
} ) );
|
|
45
|
+
stubFetch( okResponse( fixture ) );
|
|
44
46
|
|
|
45
47
|
const result = await fetchModelsPricing();
|
|
46
48
|
|
|
@@ -55,47 +57,35 @@ describe( 'fetchModelsPricing', () => {
|
|
|
55
57
|
|
|
56
58
|
it( 'returns null when response is not ok and no cache', async () => {
|
|
57
59
|
const status = 500;
|
|
58
|
-
|
|
59
|
-
vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue( { ok: false, status } ) );
|
|
60
|
+
stubFetch( { ok: false, status } );
|
|
60
61
|
|
|
61
62
|
const result = await fetchModelsPricing();
|
|
62
63
|
|
|
63
64
|
expect( result ).toBeNull();
|
|
64
|
-
expect( err ).toHaveBeenCalledWith( errNoCache( status ) );
|
|
65
|
-
err.mockRestore();
|
|
66
65
|
} );
|
|
67
66
|
|
|
68
67
|
it( 'returns stale cache when response is not ok but cache exists', async () => {
|
|
69
|
-
|
|
70
|
-
ok: true,
|
|
71
|
-
json: () => Promise.resolve( fixture )
|
|
72
|
-
} ) );
|
|
68
|
+
stubFetch( okResponse( fixture ) );
|
|
73
69
|
await fetchModelsPricing();
|
|
74
70
|
cache.expiresAt = 0; // force refetch so we hit the !res.ok path
|
|
75
71
|
|
|
76
72
|
const status = 404;
|
|
77
|
-
|
|
78
|
-
vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue( { ok: false, status } ) );
|
|
73
|
+
stubFetch( { ok: false, status } );
|
|
79
74
|
|
|
80
75
|
const result = await fetchModelsPricing();
|
|
81
76
|
|
|
82
77
|
expect( result ).toBeInstanceOf( Map );
|
|
83
78
|
expect( result.size ).toBeGreaterThan( 0 );
|
|
84
|
-
expect( warn ).toHaveBeenCalledWith( warnStaleCache( status ) );
|
|
85
|
-
warn.mockRestore();
|
|
86
79
|
} );
|
|
87
80
|
|
|
88
81
|
it( 'returns cached Map when cache is still valid', async () => {
|
|
89
|
-
|
|
90
|
-
ok: true,
|
|
91
|
-
json: () => Promise.resolve( fixture )
|
|
92
|
-
} ) );
|
|
82
|
+
const fetchMock = stubFetch( okResponse( fixture ) );
|
|
93
83
|
|
|
94
84
|
const first = await fetchModelsPricing();
|
|
95
85
|
const second = await fetchModelsPricing();
|
|
96
86
|
|
|
97
87
|
expect( first ).toBe( second );
|
|
98
|
-
expect(
|
|
88
|
+
expect( fetchMock ).toHaveBeenCalledTimes( 1 );
|
|
99
89
|
} );
|
|
100
90
|
|
|
101
91
|
it( 'only stores models that have a cost object', async () => {
|
|
@@ -108,10 +98,7 @@ describe( 'fetchModelsPricing', () => {
|
|
|
108
98
|
}
|
|
109
99
|
}
|
|
110
100
|
};
|
|
111
|
-
|
|
112
|
-
ok: true,
|
|
113
|
-
json: () => Promise.resolve( dataWithMissingCost )
|
|
114
|
-
} ) );
|
|
101
|
+
stubFetch( okResponse( dataWithMissingCost ) );
|
|
115
102
|
|
|
116
103
|
const result = await fetchModelsPricing();
|
|
117
104
|
|
package/src/cost/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { fetchModelsPricing } from './fetch_models_pricing.js';
|
|
2
|
-
import { Tracing } from '@outputai/core/
|
|
2
|
+
import { Tracing } from '@outputai/core/sdk/runtime';
|
|
3
|
+
import { Logger } from '@outputai/core';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Calculates the cost of an llm call based on the model and usage.
|
|
@@ -13,13 +14,13 @@ export const calculateLLMCallCost = async ( { modelId, usage } ) => {
|
|
|
13
14
|
const models = await fetchModelsPricing();
|
|
14
15
|
|
|
15
16
|
if ( !models ) {
|
|
16
|
-
|
|
17
|
+
Logger.warn( 'Failed to fetch models pricing', { namespace: 'LLM' } );
|
|
17
18
|
return null;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
const pricing = models.get( modelId );
|
|
21
22
|
if ( !pricing ) {
|
|
22
|
-
|
|
23
|
+
Logger.warn( 'Missing cost reference for model', { namespace: 'LLM' } );
|
|
23
24
|
return null;
|
|
24
25
|
}
|
|
25
26
|
|
|
@@ -50,7 +51,7 @@ export const calculateLLMCallCost = async ( { modelId, usage } ) => {
|
|
|
50
51
|
|
|
51
52
|
return llmUsage;
|
|
52
53
|
} catch ( error ) {
|
|
53
|
-
|
|
54
|
+
Logger.error( 'Error calculating LLM call costs', { error: error.message, namespace: 'LLM' } );
|
|
54
55
|
return null;
|
|
55
56
|
}
|
|
56
57
|
};
|
package/src/cost/index.spec.js
CHANGED
|
@@ -6,7 +6,7 @@ vi.mock( './fetch_models_pricing.js', () => ( {
|
|
|
6
6
|
fetchModelsPricing: ( ...args ) => mockFetchModelsPricing( ...args )
|
|
7
7
|
} ) );
|
|
8
8
|
|
|
9
|
-
vi.mock( '@outputai/core/
|
|
9
|
+
vi.mock( '@outputai/core/sdk/runtime', () => {
|
|
10
10
|
class LLMUsage {
|
|
11
11
|
static TYPE = 'llm:usage';
|
|
12
12
|
type = LLMUsage.TYPE;
|
|
@@ -44,7 +44,7 @@ vi.mock( '@outputai/core/sdk_activity_integration', () => {
|
|
|
44
44
|
};
|
|
45
45
|
} );
|
|
46
46
|
|
|
47
|
-
import { Tracing } from '@outputai/core/
|
|
47
|
+
import { Tracing } from '@outputai/core/sdk/runtime';
|
|
48
48
|
import { calculateLLMCallCost } from './index.js';
|
|
49
49
|
|
|
50
50
|
const expectLLMUsage = ( result, { modelId, usage, total, tokensUsed } ) => {
|
|
@@ -61,8 +61,6 @@ const expectLLMUsage = ( result, { modelId, usage, total, tokensUsed } ) => {
|
|
|
61
61
|
describe( 'calculateLLMCallCost', () => {
|
|
62
62
|
beforeEach( () => {
|
|
63
63
|
vi.clearAllMocks();
|
|
64
|
-
vi.spyOn( console, 'warn' ).mockImplementation( () => {} );
|
|
65
|
-
vi.spyOn( console, 'error' ).mockImplementation( () => {} );
|
|
66
64
|
} );
|
|
67
65
|
|
|
68
66
|
afterEach( () => {
|
|
@@ -78,7 +76,6 @@ describe( 'calculateLLMCallCost', () => {
|
|
|
78
76
|
} );
|
|
79
77
|
|
|
80
78
|
expect( result ).toBeNull();
|
|
81
|
-
expect( console.warn ).toHaveBeenCalledWith( 'Failed to fetch models pricing' );
|
|
82
79
|
} );
|
|
83
80
|
|
|
84
81
|
it( 'returns null when model is missing from cost table', async () => {
|
|
@@ -90,7 +87,6 @@ describe( 'calculateLLMCallCost', () => {
|
|
|
90
87
|
} );
|
|
91
88
|
|
|
92
89
|
expect( result ).toBeNull();
|
|
93
|
-
expect( console.warn ).toHaveBeenCalledWith( 'Missing cost reference for model' );
|
|
94
90
|
} );
|
|
95
91
|
|
|
96
92
|
it( 'calculates input and output usage from model pricing', async () => {
|
|
@@ -283,6 +279,5 @@ describe( 'calculateLLMCallCost', () => {
|
|
|
283
279
|
} );
|
|
284
280
|
|
|
285
281
|
expect( result ).toBeNull();
|
|
286
|
-
expect( console.error ).toHaveBeenCalledWith( 'Error calculating LLM call costs', error );
|
|
287
282
|
} );
|
|
288
283
|
} );
|
package/src/prompt/escape.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { encodeXML, decodeXML } from 'entities';
|
|
2
|
-
import {
|
|
2
|
+
import { Objects } from '@outputai/core/sdk/helpers';
|
|
3
3
|
|
|
4
4
|
const VAR_SAFE_FILTER = '__var_safe';
|
|
5
5
|
|
|
@@ -56,7 +56,7 @@ export const decode = value => {
|
|
|
56
56
|
if ( Array.isArray( value ) ) {
|
|
57
57
|
return value.map( decode );
|
|
58
58
|
}
|
|
59
|
-
if ( isPlainObject( value ) ) {
|
|
59
|
+
if ( Objects.isPlainObject( value ) ) {
|
|
60
60
|
return Object.fromEntries(
|
|
61
61
|
Object.entries( value ).map( ( [ k, v ] ) => [ k, decode( v ) ] )
|
|
62
62
|
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { join } from 'path';
|
|
2
2
|
import { readdirSync, readFileSync } from 'node:fs';
|
|
3
|
-
import {
|
|
3
|
+
import { Path } from '@outputai/core/sdk/helpers';
|
|
4
4
|
import { FatalError } from '@outputai/core';
|
|
5
5
|
|
|
6
6
|
const scanDir = dir => {
|
|
@@ -41,5 +41,5 @@ const findContent = ( name, dir ) => {
|
|
|
41
41
|
* @param {string} [dir] - Directory to search, defaults to invocation directory
|
|
42
42
|
* @returns {{ content: string, dir: string } | null}
|
|
43
43
|
*/
|
|
44
|
-
export const loadContent = ( name, dir = resolveInvocationDir() ) =>
|
|
44
|
+
export const loadContent = ( name, dir = Path.resolveInvocationDir() ) =>
|
|
45
45
|
findContent( name, dir ) ?? null;
|
|
@@ -6,9 +6,11 @@ import { tmpdir } from 'node:os';
|
|
|
6
6
|
// Hoisted state so mocks can read dynamic values set in tests
|
|
7
7
|
const state = vi.hoisted( () => ( { dir: '', entries: {} } ) );
|
|
8
8
|
|
|
9
|
-
// Mock
|
|
10
|
-
vi.mock( '@outputai/core/
|
|
11
|
-
|
|
9
|
+
// Mock SDK helpers to control resolveInvocationDir
|
|
10
|
+
vi.mock( '@outputai/core/sdk/helpers', () => ( {
|
|
11
|
+
Path: {
|
|
12
|
+
resolveInvocationDir: () => state.dir
|
|
13
|
+
}
|
|
12
14
|
} ) );
|
|
13
15
|
|
|
14
16
|
// Mock node:fs.readFileSync for directory scans while delegating file reads
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ValidationError, z } from '@outputai/core';
|
|
1
|
+
import { Logger, ValidationError, z } from '@outputai/core';
|
|
2
2
|
import { attributesSchema } from './block_options.js';
|
|
3
3
|
|
|
4
4
|
const toolConfigSchema = z.record( z.string(), z.unknown() );
|
|
@@ -70,12 +70,12 @@ const SNAKE_CASE_WARNINGS = {
|
|
|
70
70
|
function warnSnakeCaseFields( config ) {
|
|
71
71
|
for ( const [ snake, camel ] of Object.entries( SNAKE_CASE_WARNINGS ) ) {
|
|
72
72
|
if ( Object.hasOwn( config, snake ) ) {
|
|
73
|
-
|
|
73
|
+
Logger.warn( `[output-llm] "${snake}" found in prompt config. Did you mean "${camel}"?`, { namespace: 'LLM' } );
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
const thinking = config.providerOptions?.thinking;
|
|
77
77
|
if ( thinking && Object.hasOwn( thinking, 'budget_tokens' ) ) {
|
|
78
|
-
|
|
78
|
+
Logger.warn( '[output-llm] "budget_tokens" found in providerOptions.thinking. Did you mean "budgetTokens"?', { namespace: 'LLM' } );
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, it, expect
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
2
|
import { ValidationError } from '@outputai/core';
|
|
3
3
|
import { validatePrompt } from './validations.js';
|
|
4
4
|
|
|
@@ -548,8 +548,6 @@ describe( 'validatePrompt', () => {
|
|
|
548
548
|
} );
|
|
549
549
|
|
|
550
550
|
it( 'should pass through budget_tokens in thinking and warn about snake_case', () => {
|
|
551
|
-
const warnSpy = vi.spyOn( console, 'warn' ).mockImplementation( () => {} );
|
|
552
|
-
|
|
553
551
|
const promptWithBudgetTokensSnake = {
|
|
554
552
|
name: 'thinking-budget-snake',
|
|
555
553
|
config: {
|
|
@@ -571,11 +569,6 @@ describe( 'validatePrompt', () => {
|
|
|
571
569
|
};
|
|
572
570
|
|
|
573
571
|
expect( () => validatePrompt( promptWithBudgetTokensSnake ) ).not.toThrow();
|
|
574
|
-
expect( warnSpy ).toHaveBeenCalledWith(
|
|
575
|
-
'[output-llm] "budget_tokens" found in providerOptions.thinking. Did you mean "budgetTokens"?'
|
|
576
|
-
);
|
|
577
|
-
|
|
578
|
-
warnSpy.mockRestore();
|
|
579
572
|
} );
|
|
580
573
|
|
|
581
574
|
it( 'should allow snake_case fields in config via passthrough (no longer strict)', () => {
|
package/src/utils/trace.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Tracing,
|
|
1
|
+
import { Tracing, Event } from '@outputai/core/sdk/runtime';
|
|
2
2
|
|
|
3
3
|
export const startTrace = ( { name, ...details } ) => {
|
|
4
4
|
const traceId = `${name}-${Date.now()}`;
|
|
@@ -13,7 +13,7 @@ export const endTraceWithError = ( { traceId, error } ) => {
|
|
|
13
13
|
export const endTraceWithSuccess = ( { traceId, result, cost, ...extra } ) => {
|
|
14
14
|
if ( cost ) {
|
|
15
15
|
Tracing.addEventAttribute( { eventId: traceId, attribute: cost } );
|
|
16
|
-
|
|
16
|
+
Event.emit( 'cost:llm:request', cost );
|
|
17
17
|
}
|
|
18
18
|
Tracing.addEventEnd( { id: traceId, details: { result, ...extra } } );
|
|
19
19
|
};
|
package/src/utils/trace.spec.js
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
2
|
|
|
3
|
-
vi.mock( '@outputai/core/
|
|
3
|
+
vi.mock( '@outputai/core/sdk/runtime', () => ( {
|
|
4
4
|
Tracing: {
|
|
5
5
|
addEventStart: vi.fn(),
|
|
6
6
|
addEventError: vi.fn(),
|
|
7
7
|
addEventAttribute: vi.fn(),
|
|
8
8
|
addEventEnd: vi.fn()
|
|
9
9
|
},
|
|
10
|
-
|
|
10
|
+
Event: {
|
|
11
|
+
emit: vi.fn()
|
|
12
|
+
}
|
|
11
13
|
} ) );
|
|
12
14
|
|
|
13
|
-
import { Tracing,
|
|
15
|
+
import { Tracing, Event } from '@outputai/core/sdk/runtime';
|
|
14
16
|
import { startTrace, endTraceWithError, endTraceWithSuccess } from './trace.js';
|
|
15
17
|
|
|
16
18
|
const tracing = vi.mocked( Tracing, true );
|
|
19
|
+
const event = vi.mocked( Event, true );
|
|
17
20
|
|
|
18
21
|
describe( 'trace utils', () => {
|
|
19
22
|
beforeEach( () => {
|
|
@@ -69,7 +72,7 @@ describe( 'trace utils', () => {
|
|
|
69
72
|
eventId: 'trace-a',
|
|
70
73
|
attribute: cost
|
|
71
74
|
} );
|
|
72
|
-
expect(
|
|
75
|
+
expect( event.emit ).toHaveBeenCalledWith( 'cost:llm:request', cost );
|
|
73
76
|
expect( tracing.addEventEnd ).toHaveBeenCalledWith( {
|
|
74
77
|
id: 'trace-a',
|
|
75
78
|
details: {
|
|
@@ -94,7 +97,7 @@ describe( 'trace utils', () => {
|
|
|
94
97
|
} );
|
|
95
98
|
|
|
96
99
|
expect( tracing.addEventAttribute ).not.toHaveBeenCalled();
|
|
97
|
-
expect(
|
|
100
|
+
expect( event.emit ).not.toHaveBeenCalled();
|
|
98
101
|
expect( tracing.addEventEnd ).toHaveBeenCalledWith( {
|
|
99
102
|
id: 'trace-no-cost',
|
|
100
103
|
details: {
|