@outputai/llm 0.2.1-next.fd72d95.0 → 0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@outputai/llm",
3
- "version": "0.2.1-next.fd72d95.0",
3
+ "version": "0.3.0",
4
4
  "description": "Framework abstraction to interact with LLM models",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -9,20 +9,20 @@
9
9
  "./src"
10
10
  ],
11
11
  "dependencies": {
12
- "@ai-sdk/amazon-bedrock": "4.0.95",
12
+ "@ai-sdk/amazon-bedrock": "4.0.96",
13
13
  "@ai-sdk/anthropic": "3.0.71",
14
14
  "@ai-sdk/azure": "3.0.54",
15
15
  "@ai-sdk/google-vertex": "4.0.112",
16
16
  "@ai-sdk/openai": "3.0.53",
17
17
  "@ai-sdk/perplexity": "3.0.29",
18
18
  "@exalabs/ai-sdk": "2.0.1",
19
- "@perplexity-ai/ai-sdk": "0.1.2",
19
+ "@perplexity-ai/ai-sdk": "0.1.3",
20
20
  "@tavily/ai-sdk": "0.4.1",
21
21
  "ai": "6.0.168",
22
22
  "decimal.js": "10.6.0",
23
23
  "gray-matter": "4.0.3",
24
- "liquidjs": "10.25.5",
25
- "@outputai/core": "0.2.1-next.fd72d95.0"
24
+ "liquidjs": "10.25.7",
25
+ "@outputai/core": "0.3.0"
26
26
  },
27
27
  "license": "Apache-2.0",
28
28
  "publishConfig": {
package/src/agent.js CHANGED
@@ -2,9 +2,9 @@ import { ValidationError } from '@outputai/core';
2
2
  import { resolveInvocationDir } from '@outputai/core/sdk_utils';
3
3
  import { ToolLoopAgent as AIToolLoopAgent, stepCountIs } from 'ai';
4
4
  import { hydratePromptTemplate, loadAiSdkOptionsFromPrompt } from './ai_sdk.js';
5
- import { startTrace, endTraceWithError, traceStreamCallbacks } from './trace_utils.js';
6
- import { wrapInOutputResponse } from './response_utils.js';
7
- import { ROLE, isRole, getContent } from './message_utils.js';
5
+ import { startTrace, endTraceWithError } from './utils/trace.js';
6
+ import { wrapTextResponse, wrapStreamOnFinishResponse } from './utils/response_wrappers.js';
7
+ import { ROLE, isRole, getContent } from './utils/message.js';
8
8
 
9
9
  export { skill } from './skill.js';
10
10
 
@@ -17,10 +17,10 @@ export const createMemoryConversationStore = () => {
17
17
  };
18
18
 
19
19
  export class Agent extends AIToolLoopAgent {
20
- _prompt;
21
- _modelId;
22
- _initialMessages;
23
- _store;
20
+ #prompt;
21
+ #modelId;
22
+ #initialMessages;
23
+ #store;
24
24
 
25
25
  constructor( {
26
26
  prompt, promptDir, variables = {}, skills = [], tools = {},
@@ -52,48 +52,52 @@ export class Agent extends AIToolLoopAgent {
52
52
  ...rest
53
53
  } );
54
54
 
55
- this._prompt = prompt;
56
- this._modelId = loadedPrompt.config.model;
57
- this._initialMessages = allMessages.filter( isRole( ROLE.USER ) );
58
- this._store = conversationStore ?? null;
55
+ this.#prompt = prompt;
56
+ this.#modelId = loadedPrompt.config.model;
57
+ this.#initialMessages = allMessages.filter( isRole( ROLE.USER ) );
58
+ this.#store = conversationStore ?? null;
59
59
  }
60
60
 
61
- async _preSendHook( userMessages ) {
62
- const priorMessages = this._store ? await this._store.getMessages() : [];
63
- return [ ...this._initialMessages, ...priorMessages, ...userMessages ];
61
+ async #fetchMessages( userMessages ) {
62
+ const priorMessages = this.#store ? await this.#store.getMessages() : [];
63
+ return [ ...this.#initialMessages, ...priorMessages, ...userMessages ];
64
64
  }
65
65
 
66
- async _postSendHook( userMessages, result ) {
67
- if ( this._store ) {
68
- await this._store.addMessages( [ ...userMessages, ...( result.response?.messages ?? [] ) ] );
66
+ async #storeMessages( userMessages, result ) {
67
+ if ( this.#store ) {
68
+ await this.#store.addMessages( [ ...userMessages, ...( result.response?.messages ?? [] ) ] );
69
69
  }
70
70
  }
71
71
 
72
72
  async generate( { messages: userMessages = [], ...callOptions } = {} ) {
73
- const traceId = startTrace( 'Agent.generate', { prompt: this._prompt } );
73
+ const traceId = startTrace( { name: 'Agent.generate', prompt: this.#prompt } );
74
74
  try {
75
- const messages = await this._preSendHook( userMessages );
76
- const result = await super.generate( { messages, ...callOptions } );
77
- const wrapped = await wrapInOutputResponse( result, { traceId, modelId: this._modelId } );
78
- await this._postSendHook( userMessages, wrapped );
75
+ const messages = await this.#fetchMessages( userMessages );
76
+ const response = await super.generate( { messages, ...callOptions } );
77
+ const wrapped = await wrapTextResponse( { traceId, response, modelId: this.#modelId } );
78
+ await this.#storeMessages( userMessages, wrapped );
79
79
  return wrapped;
80
80
  } catch ( error ) {
81
- endTraceWithError( traceId, error );
81
+ endTraceWithError( { traceId, error } );
82
82
  throw error;
83
83
  }
84
84
  }
85
85
 
86
86
  async stream( { messages: userMessages = [], onFinish, onError, ...callOptions } = {} ) {
87
- const traceId = startTrace( 'Agent.stream', { prompt: this._prompt } );
87
+ const traceId = startTrace( { name: 'Agent.stream', prompt: this.#prompt } );
88
88
  try {
89
- const messages = await this._preSendHook( userMessages );
89
+ const messages = await this.#fetchMessages( userMessages );
90
90
  return super.stream( {
91
91
  messages,
92
92
  ...callOptions,
93
- ...traceStreamCallbacks( traceId, this._modelId, { onFinish, onError } )
93
+ ...wrapStreamOnFinishResponse( { traceId, modelId: this.#modelId, onFinish } ),
94
+ onError( event ) {
95
+ endTraceWithError( { traceId, error: event.error } );
96
+ onError?.( event );
97
+ }
94
98
  } );
95
99
  } catch ( error ) {
96
- endTraceWithError( traceId, error );
100
+ endTraceWithError( { traceId, error } );
97
101
  throw error;
98
102
  }
99
103
  }
package/src/agent.spec.js CHANGED
@@ -45,16 +45,16 @@ vi.mock( './ai_sdk.js', () => ( {
45
45
 
46
46
  const startTraceImpl = vi.fn( () => 'trace-id' );
47
47
  const endTraceWithErrorImpl = vi.fn();
48
- const traceStreamCallbacksImpl = vi.fn( () => ( {} ) );
49
- vi.mock( './trace_utils.js', () => ( {
48
+ vi.mock( './utils/trace.js', () => ( {
50
49
  startTrace: ( ...args ) => startTraceImpl( ...args ),
51
- endTraceWithError: ( ...args ) => endTraceWithErrorImpl( ...args ),
52
- traceStreamCallbacks: ( ...args ) => traceStreamCallbacksImpl( ...args )
50
+ endTraceWithError: ( ...args ) => endTraceWithErrorImpl( ...args )
53
51
  } ) );
54
52
 
55
- const wrapInOutputResponseImpl = vi.fn( response => response );
56
- vi.mock( './response_utils.js', () => ( {
57
- wrapInOutputResponse: ( ...args ) => wrapInOutputResponseImpl( ...args )
53
+ const wrapTextResponseImpl = vi.fn( async ( { response } ) => response );
54
+ const wrapStreamOnFinishResponseImpl = vi.fn( () => ( {} ) );
55
+ vi.mock( './utils/response_wrappers.js', () => ( {
56
+ wrapTextResponse: ( ...args ) => wrapTextResponseImpl( ...args ),
57
+ wrapStreamOnFinishResponse: ( ...args ) => wrapStreamOnFinishResponseImpl( ...args )
58
58
  } ) );
59
59
 
60
60
  vi.mock( './skill.js', () => ( {
@@ -88,6 +88,8 @@ beforeEach( () => {
88
88
  } );
89
89
  superGenerateImpl.mockResolvedValue( { text: 'response', response: { messages: [] } } );
90
90
  superStreamImpl.mockReturnValue( { textStream: 'stream' } );
91
+ wrapTextResponseImpl.mockImplementation( async ( { response } ) => response );
92
+ wrapStreamOnFinishResponseImpl.mockReturnValue( {} );
91
93
  } );
92
94
 
93
95
  // ─── Tests ────────────────────────────────────────────────────────────────────
@@ -263,14 +265,44 @@ describe( 'createMemoryConversationStore()', () => {
263
265
  } );
264
266
  } );
265
267
 
268
+ describe( 'Agent — utils delegation', () => {
269
+ it( 'generate() calls trace and wrapTextResponse with model id and response', async () => {
270
+ const { Agent } = await importSut();
271
+ const agent = new Agent( { prompt: 'test@v1' } );
272
+ await agent.generate( { messages: [ { role: 'user', content: 'hi' } ] } );
273
+
274
+ expect( startTraceImpl ).toHaveBeenCalledWith( { name: 'Agent.generate', prompt: 'test@v1' } );
275
+ expect( wrapTextResponseImpl ).toHaveBeenCalledWith( {
276
+ traceId: 'trace-id',
277
+ modelId: 'claude-sonnet-4-6',
278
+ response: expect.objectContaining( { text: 'response' } )
279
+ } );
280
+ } );
281
+
282
+ it( 'stream() calls trace and wrapStreamOnFinishResponse', async () => {
283
+ const { Agent } = await importSut();
284
+ const agent = new Agent( { prompt: 'test@v1' } );
285
+ await agent.stream();
286
+
287
+ expect( startTraceImpl ).toHaveBeenCalledWith( { name: 'Agent.stream', prompt: 'test@v1' } );
288
+ expect( wrapStreamOnFinishResponseImpl ).toHaveBeenCalledWith( {
289
+ traceId: 'trace-id',
290
+ modelId: 'claude-sonnet-4-6',
291
+ onFinish: undefined
292
+ } );
293
+ } );
294
+ } );
295
+
266
296
  describe( 'Agent.stream()', () => {
267
297
  it( 'uses pre-rendered messages when no variables provided', async () => {
268
298
  const { Agent } = await importSut();
269
299
  const agent = new Agent( { prompt: 'test@v1' } );
270
300
  await agent.stream();
271
- expect( superStreamImpl ).toHaveBeenCalledWith( {
272
- messages: defaultMessages
273
- } );
301
+ expect( superStreamImpl ).toHaveBeenCalledWith(
302
+ expect.objectContaining( {
303
+ messages: defaultMessages
304
+ } )
305
+ );
274
306
  } );
275
307
 
276
308
  it( 'loads prior messages from store', async () => {
@@ -284,9 +316,11 @@ describe( 'Agent.stream()', () => {
284
316
  const agent = new Agent( { prompt: 'test@v1', conversationStore: store } );
285
317
  await agent.stream( { messages: [ { role: 'user', content: 'new' } ] } );
286
318
 
287
- expect( superStreamImpl ).toHaveBeenCalledWith( {
288
- messages: [ ...defaultMessages, ...priorMessages, { role: 'user', content: 'new' } ]
289
- } );
319
+ expect( superStreamImpl ).toHaveBeenCalledWith(
320
+ expect.objectContaining( {
321
+ messages: [ ...defaultMessages, ...priorMessages, { role: 'user', content: 'new' } ]
322
+ } )
323
+ );
290
324
  } );
291
325
 
292
326
  it( 'does not auto-append to store', async () => {
package/src/ai_sdk.js CHANGED
@@ -4,8 +4,8 @@ import { stepCountIs } from 'ai';
4
4
  import { validateGenerateTextArgs, validateStreamTextArgs } from './validations.js';
5
5
  import { loadPrompt } from './prompt_loader.js';
6
6
  import { buildSystemSkillsVar, buildLoadSkillTool, loadPromptSkills, loadColocatedSkills } from './skill.js';
7
- import { startTrace, endTraceWithError, traceStreamCallbacks } from './trace_utils.js';
8
- import { wrapInOutputResponse } from './response_utils.js';
7
+ import { startTrace, endTraceWithError } from './utils/trace.js';
8
+ import { wrapTextResponse, wrapStreamOnFinishResponse } from './utils/response_wrappers.js';
9
9
 
10
10
  export const loadAiSdkOptionsFromPrompt = prompt => {
11
11
  const options = {
@@ -72,7 +72,7 @@ export async function generateText( { prompt, variables, promptDir, skills = [],
72
72
 
73
73
  validateGenerateTextArgs( { prompt, variables: allVariables } );
74
74
 
75
- const traceId = startTrace( 'generateText', { prompt, variables: allVariables, loadedPrompt } );
75
+ const traceId = startTrace( { name: 'generateText', prompt, variables: allVariables, loadedPrompt } );
76
76
  const { model: modelId } = loadedPrompt.config;
77
77
 
78
78
  try {
@@ -82,9 +82,9 @@ export async function generateText( { prompt, variables, promptDir, skills = [],
82
82
  ...( hasTools ? { tools } : {} ),
83
83
  ...( hasTools && !extraAiSdkOptions.stopWhen ? { stopWhen: stepCountIs( maxSteps ) } : {} )
84
84
  } );
85
- return wrapInOutputResponse( response, { traceId, modelId } );
85
+ return wrapTextResponse( { traceId, modelId, response } );
86
86
  } catch ( error ) {
87
- endTraceWithError( traceId, error );
87
+ endTraceWithError( { traceId, error } );
88
88
  throw error;
89
89
  }
90
90
  }
@@ -92,17 +92,21 @@ export async function generateText( { prompt, variables, promptDir, skills = [],
92
92
  export function streamText( { prompt, variables, onFinish, onError, ...restOptions } ) {
93
93
  validateStreamTextArgs( { prompt, variables } );
94
94
  const loadedPrompt = loadPrompt( prompt, variables );
95
- const traceId = startTrace( 'streamText', { prompt, variables, loadedPrompt } );
95
+ const traceId = startTrace( { name: 'streamText', prompt, variables, loadedPrompt } );
96
96
  const { model: modelId } = loadedPrompt.config;
97
97
 
98
98
  try {
99
99
  return AI.streamText( {
100
100
  ...loadAiSdkOptionsFromPrompt( loadedPrompt ),
101
101
  ...restOptions,
102
- ...traceStreamCallbacks( traceId, modelId, { onFinish, onError } )
102
+ ...wrapStreamOnFinishResponse( { traceId, modelId, onFinish } ),
103
+ onError( event ) {
104
+ endTraceWithError( { traceId, error: event.error } );
105
+ onError?.( event );
106
+ }
103
107
  } );
104
108
  } catch ( error ) {
105
- endTraceWithError( traceId, error );
109
+ endTraceWithError( { traceId, error } );
106
110
  throw error;
107
111
  }
108
112
  }