@outputai/llm 0.6.1-next.65cd087.0 → 0.6.1-next.7359fe9.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.6.1-next.65cd087.0",
3
+ "version": "0.6.1-next.7359fe9.0",
4
4
  "description": "Framework abstraction to interact with LLM models",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -23,7 +23,7 @@
23
23
  "gray-matter": "4.0.3",
24
24
  "liquidjs": "10.25.7",
25
25
  "undici": "8.1.0",
26
- "@outputai/core": "0.6.1-next.65cd087.0"
26
+ "@outputai/core": "0.6.1-next.7359fe9.0"
27
27
  },
28
28
  "license": "Apache-2.0",
29
29
  "publishConfig": {
@@ -13,6 +13,9 @@ import {
13
13
  } from 'ai';
14
14
  import { FatalError } from '@outputai/core';
15
15
 
16
+ // AI SDK does not expose a dedicated schema-mismatch discriminator for NoObjectGeneratedError.
17
+ const NO_OBJECT_SCHEMA_MISMATCH_MESSAGE = 'No object generated: response did not match schema.';
18
+
16
19
  /**
17
20
  * Recursively search an error cause chain until finds an error which is instance of given prototype.
18
21
  *
@@ -25,7 +28,7 @@ export const findInstanceInCauseChain = ( error, _class, depth = 0 ) => {
25
28
  if ( !error || typeof error !== 'object' ) {
26
29
  return null;
27
30
  }
28
- if ( typeof _class === 'string' && error.constructor.name === _class ) {
31
+ if ( typeof _class === 'string' && error.constructor?.name === _class ) {
29
32
  return error;
30
33
  }
31
34
  if ( typeof _class === 'function' && error instanceof _class ) {
@@ -42,20 +45,34 @@ const toFatalError = ( error, extraMessage = '' ) => new FatalError(
42
45
  { cause: error }
43
46
  );
44
47
 
48
+ /**
49
+ * Map an AI SDK error to a framework specific error:
50
+ *
51
+ * - AI SDK Unrecoverable errors become FatalErrors, check code to see options.
52
+ * - NoObjectGeneratedError from invalid schema are reinitialized with a better message.
53
+ * - Other errors are preserved.
54
+ * @param {object} error - Original Error
55
+ * @returns {object} A new Error
56
+ */
45
57
  export const mapAiError = error => {
46
58
  if ( error instanceof FatalError ) {
47
59
  return error;
48
60
  }
49
61
 
50
- // NoObjectGeneratedError can be thrown when the response doesn't match the schema
51
- // This adds a wrapper to that error serializing the first zod validation in the message, to make it easier to debug.
52
- if ( NoObjectGeneratedError.isInstance( error ) && error.message.includes( 'No object generated: response did not match schema.' ) ) {
62
+ // NoObjectGeneratedError can be thrown when the response doesn't match the schema.
63
+ // This re-creates the error with a better message, making it easier to debug.
64
+ if ( NoObjectGeneratedError.isInstance( error ) && error.message.includes( NO_OBJECT_SCHEMA_MISMATCH_MESSAGE ) ) {
53
65
  const zodError = findInstanceInCauseChain( error, 'ZodError' );
54
66
  if ( zodError && zodError.issues?.length > 0 ) {
55
- const { path, message } = zodError.issues[0];
56
- const wrapper = new Error( `${error.message} First issue is "${message}" at path [${path.join( ', ' )}].`, { cause: error } );
57
- wrapper.name = 'NoObjectGeneratedError';
58
- return wrapper;
67
+ const [ { path, message } ] = zodError.issues;
68
+ return new NoObjectGeneratedError( {
69
+ message: `${error.message} First issue is "${message}" at path [${path.join( ', ' )}].`,
70
+ cause: error.cause,
71
+ text: error.text,
72
+ response: error.response,
73
+ usage: error.usage,
74
+ finishReason: error.finishReason
75
+ } );
59
76
  }
60
77
  return error;
61
78
  }
@@ -171,6 +171,13 @@ describe( 'findInstanceInCauseChain', () => {
171
171
  expect( findInstanceInCauseChain( 'not an error', Error ) ).toBeNull();
172
172
  } );
173
173
 
174
+ it( 'returns null for object causes without constructors', () => {
175
+ const cause = Object.create( null );
176
+ const error = new FirstCustomError( 'first', { cause } );
177
+
178
+ expect( findInstanceInCauseChain( error, 'SecondCustomError' ) ).toBeNull();
179
+ } );
180
+
174
181
  it( 'stops searching after the depth limit', () => {
175
182
  const makeErrorChain = depth => depth === 0 ?
176
183
  new SecondCustomError( 'target' ) :
@@ -204,17 +211,25 @@ describe( 'mapAiError', () => {
204
211
  const error = new NoObjectGeneratedError( {
205
212
  message: 'No object generated: response did not match schema.',
206
213
  text: '{"items":[{}]}',
214
+ response: { id: 'response-1' },
215
+ usage: { totalTokens: 10 },
216
+ finishReason: 'stop',
207
217
  cause: validationError
208
218
  } );
209
219
 
210
220
  const result = mapAiError( error );
211
221
 
212
222
  expect( result ).not.toBe( error );
213
- expect( result.name ).toBe( 'NoObjectGeneratedError' );
223
+ expect( NoObjectGeneratedError.isInstance( result ) ).toBe( true );
224
+ expect( result.name ).toBe( 'AI_NoObjectGeneratedError' );
214
225
  expect( result.message ).toBe(
215
226
  'No object generated: response did not match schema. First issue is "Expected string" at path [items, 0, title].'
216
227
  );
217
- expect( result.cause ).toBe( error );
228
+ expect( result.cause ).toBe( validationError );
229
+ expect( result.text ).toBe( error.text );
230
+ expect( result.response ).toBe( error.response );
231
+ expect( result.usage ).toBe( error.usage );
232
+ expect( result.finishReason ).toBe( error.finishReason );
218
233
  } );
219
234
 
220
235
  it( 'preserves NoObjectGeneratedError schema mismatches when no schema issue is available', () => {