@outputai/llm 0.11.1-next.da26845.0 → 0.12.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.
Files changed (47) hide show
  1. package/package.json +7 -3
  2. package/src/agent.js +7 -2
  3. package/src/generate.js +1 -0
  4. package/src/index.d.ts +14 -6
  5. package/src/utils/wrap.js +35 -2
  6. package/src/agent.spec.js +0 -595
  7. package/src/ai_provider.spec.js +0 -217
  8. package/src/ai_sdk_options.spec.js +0 -390
  9. package/src/fixtures/image_response_v7_google_vertex.js +0 -72
  10. package/src/fixtures/image_response_v7_openai.js +0 -77
  11. package/src/fixtures/models_api_light.json +0 -675
  12. package/src/fixtures/models_api_v7_subset.json +0 -259
  13. package/src/fixtures/stream_response_v7_anthropic.js +0 -389
  14. package/src/fixtures/stream_response_v7_google_vertex.js +0 -462
  15. package/src/fixtures/stream_response_v7_openai.js +0 -353
  16. package/src/fixtures/stream_response_v7_perplexity.js +0 -671
  17. package/src/fixtures/text_response_v7_anthropic.js +0 -359
  18. package/src/fixtures/text_response_v7_anthropic_cache.js +0 -719
  19. package/src/fixtures/text_response_v7_google_vertex.js +0 -435
  20. package/src/fixtures/text_response_v7_openai.js +0 -347
  21. package/src/fixtures/text_response_v7_perplexity.js +0 -764
  22. package/src/generate.spec.js +0 -525
  23. package/src/prompt/content.spec.js +0 -208
  24. package/src/prompt/interpolations.spec.js +0 -109
  25. package/src/prompt/loader.full.spec.js +0 -309
  26. package/src/prompt/loader.spec.js +0 -258
  27. package/src/prompt/markup/attributes.spec.js +0 -132
  28. package/src/prompt/markup/nodes.spec.js +0 -333
  29. package/src/prompt/markup/tokenizer.spec.js +0 -149
  30. package/src/prompt/markup/tokens.spec.js +0 -250
  31. package/src/prompt/validations.spec.js +0 -812
  32. package/src/utils/cost.full.spec.js +0 -272
  33. package/src/utils/cost.spec.js +0 -290
  34. package/src/utils/error_handler.spec.js +0 -230
  35. package/src/utils/file.spec.js +0 -89
  36. package/src/utils/image.spec.js +0 -20
  37. package/src/utils/legacy_cost_attribute.spec.js +0 -278
  38. package/src/utils/models.spec.js +0 -119
  39. package/src/utils/models_pricing.spec.js +0 -162
  40. package/src/utils/skills.spec.js +0 -168
  41. package/src/utils/sources.spec.js +0 -122
  42. package/src/utils/stream.spec.js +0 -55
  43. package/src/utils/tools.spec.js +0 -167
  44. package/src/utils/usage.full.spec.js +0 -215
  45. package/src/utils/usage.spec.js +0 -246
  46. package/src/utils/wrap.spec.js +0 -467
  47. package/src/validations.spec.js +0 -631
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@outputai/llm",
3
- "version": "0.11.1-next.da26845.0",
3
+ "version": "0.12.0",
4
4
  "description": "Framework abstraction to interact with LLM models",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -9,7 +9,11 @@
9
9
  "node": ">=24.15.0"
10
10
  },
11
11
  "files": [
12
- "./src"
12
+ "./src/!(*.spec).js",
13
+ "./src/index.d.ts",
14
+ "./src/prompt/!(*.spec).js",
15
+ "./src/prompt/markup/!(*.spec).js",
16
+ "./src/utils/!(*.spec).js"
13
17
  ],
14
18
  "dependencies": {
15
19
  "decimal.js": "10.6.0",
@@ -17,7 +21,7 @@
17
21
  "gray-matter": "4.0.3",
18
22
  "liquidjs": "10.27.2",
19
23
  "undici": "8.9.0",
20
- "@outputai/core": "0.11.1-next.da26845.0"
24
+ "@outputai/core": "0.12.0"
21
25
  },
22
26
  "devDependencies": {
23
27
  "@ai-sdk/amazon-bedrock": "5.0.57",
package/src/agent.js CHANGED
@@ -58,7 +58,9 @@ export class Agent {
58
58
  ...( abortSignal && { abortSignal } ),
59
59
  ...( toolChoice && { toolChoice } )
60
60
  } );
61
- await this.#storeMessages( messages.concat( response.responseMessages ?? [] ) );
61
+ if ( response.finishReason !== 'error' ) {
62
+ await this.#storeMessages( messages.concat( response.responseMessages ?? [] ) );
63
+ }
62
64
  return response;
63
65
  }
64
66
  } );
@@ -95,7 +97,9 @@ export class Agent {
95
97
  }
96
98
 
97
99
  state.response.output = await stream.output;
98
- await this.#storeMessages( messages.concat( state.response.responseMessages ?? [] ) );
100
+ if ( state.response.finishReason !== 'error' ) {
101
+ await this.#storeMessages( messages.concat( state.response.responseMessages ?? [] ) );
102
+ }
99
103
 
100
104
  return state.response;
101
105
  }
@@ -109,6 +113,7 @@ export class Agent {
109
113
  return wrapStream( {
110
114
  name: 'Agent.stream',
111
115
  prompt: this.#prompt,
116
+ abortSignal,
112
117
  fn: ( { onEndHook, onErrorHook } ) => this.#agent.stream( {
113
118
  messages: combinedMessages,
114
119
  allowSystemInMessages: true,
package/src/generate.js CHANGED
@@ -26,6 +26,7 @@ export const streamText = args => {
26
26
  return wrapStream( {
27
27
  name: 'streamText',
28
28
  prompt,
29
+ abortSignal: aiOptions.abortSignal,
29
30
  fn: ( { onEndHook, onErrorHook } ) => AI.streamText( {
30
31
  ...loadAiSdkTextOptions( { prompt, skills, ...aiOptions } ),
31
32
  ...( onChunk && { onChunk } ),
package/src/index.d.ts CHANGED
@@ -281,12 +281,20 @@ export type GenerateImageInput =
281
281
  mediaType?: string;
282
282
  };
283
283
 
284
+ type GenerateImageInputs =
285
+ | {
286
+ /** Runtime image inputs for image-to-image generation */
287
+ images: GenerateImageInput[];
288
+ /** Optional mask for image editing */
289
+ mask?: GenerateImageInput;
290
+ } |
291
+ {
292
+ images?: undefined;
293
+ mask?: never;
294
+ };
295
+
284
296
  /** Parameters accepted by {@link generateImage}. */
285
- export type GenerateImageParameters = PromptCallOptions & {
286
- /** Runtime image inputs for image-to-image generation */
287
- images?: GenerateImageInput[];
288
- /** Optional mask for image editing; requires `images` */
289
- mask?: GenerateImageInput;
297
+ export type GenerateImageParameters = PromptCallOptions & GenerateImageInputs & {
290
298
  /** Abort signal for the request */
291
299
  abortSignal?: AbortSignal;
292
300
  };
@@ -493,7 +501,7 @@ export function getProviderNames(): string[];
493
501
  * Use an LLM model to generate text.
494
502
  *
495
503
  * This function is a wrapper over the AI SDK's `generateText`.
496
- * The prompt sets `model`, `messages`, `temperature`, `maxTokens`, `maxSteps`, `skills`, and
504
+ * The prompt sets `model`, `messages`, generation settings, `maxSteps`, `skills`, and
497
505
  * `providerOptions`. Pass either a prompt filename with optional `promptDir` / `variables`, or a
498
506
  * loaded or custom {@link Prompt} object. Other call arguments are `tools`, `output`, `toolChoice`,
499
507
  * `stopWhen`, and `abortSignal`.
package/src/utils/wrap.js CHANGED
@@ -7,6 +7,7 @@ import { mapAiError } from './error_handler.js';
7
7
  import { isPromise } from 'node:util/types';
8
8
  import { Logger } from '@outputai/core';
9
9
  import { convertCostToLegacy } from './legacy_cost_attribute.js';
10
+ import { randomBytes } from 'node:crypto';
10
11
 
11
12
  /** Creates a proxy of the AI SDK response and attach virtual getters to it based on an object map */
12
13
  const createResponseProxy = ( { response, properties } ) => new Proxy( response, {
@@ -17,7 +18,7 @@ const createResponseProxy = ( { response, properties } ) => new Proxy( response,
17
18
 
18
19
  /** Generate a trace id, starts the tracing and return the id */
19
20
  const startTrace = ( { name, prompt } ) => {
20
- const traceId = `${name}-${Date.now()}`;
21
+ const traceId = `${name}-${Date.now()}-${randomBytes( 4 ).toString( 'hex' )}`;
21
22
  Tracing.addEventStart( { kind: 'llm', id: traceId, name, details: { prompt } } );
22
23
  return traceId;
23
24
  };
@@ -105,20 +106,48 @@ export const wrapGeneration = async ( { name, prompt, fn } ) => {
105
106
  *
106
107
  * A throw or rejected Promise from `fn` (stream creation / Agent setup) is mapped and recorded
107
108
  * on the LLM trace. A non-Promise return (the `streamText` stream) is returned immediately.
109
+ * When `abortSignal` aborts, its reason is recorded as an LLM trace error. The listener is removed
110
+ * when the stream ends, reports an error, or fails during setup.
108
111
  *
109
112
  * @param {object} args
110
113
  * @param {string} args.name - Trace event name
111
114
  * @param {object} args.prompt - Loaded prompt (`config.provider` / `config.model` used for cost)
115
+ * @param {AbortSignal} [args.abortSignal] - Optional signal used to trace stream cancellation
112
116
  * @param {(hooks: { onEndHook: Function, onErrorHook: Function }) => object | Promise<object>} args.fn -
113
117
  * Return the SDK stream, or a Promise of that stream (`Agent.stream`); wire the hooks into SDK
114
118
  * `onEnd` / `onError`
115
119
  * @returns {object | Promise<object>} Value returned by `fn`; a rejected Promise is remapped
116
120
  */
117
- export const wrapStream = ( { name, prompt, fn } ) => {
121
+ export const wrapStream = ( { name, prompt, abortSignal, fn } ) => {
118
122
  const traceId = startTrace( { name, prompt } );
119
123
 
124
+ const onAbortHook = reason =>
125
+ handleError( {
126
+ traceId,
127
+ error: reason instanceof Error ? reason : new Error( 'Streaming aborted.', { cause: reason } )
128
+ } );
129
+
130
+ const handleAbort = () => {
131
+ if ( !abortSignal ) {
132
+ return () => {};
133
+ }
134
+
135
+ if ( abortSignal.aborted ) {
136
+ onAbortHook( abortSignal.reason );
137
+ return () => {};
138
+ }
139
+
140
+ const listener = () => onAbortHook( abortSignal.reason );
141
+ abortSignal.addEventListener( 'abort', listener, { once: true } );
142
+
143
+ return () => abortSignal.removeEventListener( 'abort', listener );
144
+ };
145
+
146
+ const removeAbortListener = handleAbort();
147
+
120
148
  const onEndHook = async ( response, callback ) => {
121
149
  const state = { proxyResponse: null };
150
+ removeAbortListener();
122
151
  try {
123
152
  const { text: result, finalStep, usage } = response;
124
153
  const { providerMetadata } = finalStep;
@@ -127,6 +156,7 @@ export const wrapStream = ( { name, prompt, fn } ) => {
127
156
  Tracing.addEventEnd( { id: traceId, details: { result, usage, providerMetadata, sources } } );
128
157
  state.proxyResponse = createResponseProxy( { response, properties: { cost, sources, result } } );
129
158
  } catch ( error ) {
159
+ Logger.error( 'Stream onEnd() handler failed', { namespace: 'LLM', error: error?.message ?? String( error ) } );
130
160
  throw handleError( { traceId, error } );
131
161
  }
132
162
 
@@ -143,6 +173,7 @@ export const wrapStream = ( { name, prompt, fn } ) => {
143
173
 
144
174
  const onErrorHook = async ( event, callback ) => {
145
175
  const error = handleError( { traceId, error: event.error } );
176
+ removeAbortListener();
146
177
  try {
147
178
  await callback?.( error );
148
179
  } catch ( callbackError ) {
@@ -157,9 +188,11 @@ export const wrapStream = ( { name, prompt, fn } ) => {
157
188
  try {
158
189
  const stream = fn( { onEndHook, onErrorHook } );
159
190
  return isPromise( stream ) ? stream.catch( error => {
191
+ removeAbortListener();
160
192
  throw handleError( { traceId, error } );
161
193
  } ) : stream;
162
194
  } catch ( error ) {
195
+ removeAbortListener();
163
196
  throw handleError( { traceId, error } );
164
197
  }
165
198
  };