modelfusion 0.53.0 → 0.53.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.
@@ -55,53 +55,90 @@ async function executeStreamCall({ model, options, input, functionType, startStr
55
55
  const responseQueue = new AsyncQueue_js_1.AsyncQueue();
56
56
  // run async:
57
57
  (async function () {
58
- for await (const event of deltaIterable) {
59
- if (event?.type === "error") {
60
- const error = event.error;
58
+ try {
59
+ const loopResult = await (0, runSafe_js_1.runSafe)(async () => {
60
+ for await (const event of deltaIterable) {
61
+ if (event?.type === "error") {
62
+ const error = event.error;
63
+ const finishMetadata = {
64
+ eventType: "finished",
65
+ ...startMetadata,
66
+ finishTimestamp: new Date(),
67
+ durationInMs: durationMeasurement.durationInMs,
68
+ };
69
+ eventSource.notify(error instanceof AbortError_js_1.AbortError
70
+ ? {
71
+ ...finishMetadata,
72
+ result: { status: "abort" },
73
+ }
74
+ : {
75
+ ...finishMetadata,
76
+ result: { status: "error", error },
77
+ });
78
+ throw error;
79
+ }
80
+ if (event?.type === "delta") {
81
+ const value = processDelta(event);
82
+ if (value !== undefined) {
83
+ responseQueue.push(value);
84
+ }
85
+ }
86
+ }
87
+ if (processFinished != null) {
88
+ const value = processFinished();
89
+ if (value !== undefined) {
90
+ responseQueue.push(value);
91
+ }
92
+ }
93
+ });
94
+ // deal with abort or errors that happened during streaming:
95
+ if (!loopResult.ok) {
61
96
  const finishMetadata = {
62
97
  eventType: "finished",
63
98
  ...startMetadata,
64
99
  finishTimestamp: new Date(),
65
100
  durationInMs: durationMeasurement.durationInMs,
66
101
  };
67
- eventSource.notify(error instanceof AbortError_js_1.AbortError
68
- ? {
69
- ...finishMetadata,
70
- result: { status: "abort" },
71
- }
72
- : {
102
+ if (loopResult.isAborted) {
103
+ eventSource.notify({
73
104
  ...finishMetadata,
74
- result: { status: "error", error },
105
+ eventType: "finished",
106
+ result: {
107
+ status: "abort",
108
+ },
75
109
  });
76
- throw error;
77
- }
78
- if (event?.type === "delta") {
79
- const value = processDelta(event);
80
- if (value !== undefined) {
81
- responseQueue.push(value);
110
+ responseQueue.error(new AbortError_js_1.AbortError());
111
+ return; // error is handled through queue
82
112
  }
113
+ eventSource.notify({
114
+ ...finishMetadata,
115
+ eventType: "finished",
116
+ result: {
117
+ status: "error",
118
+ error: loopResult.error,
119
+ },
120
+ });
121
+ responseQueue.error(loopResult.error);
122
+ return; // error is handled through queue
83
123
  }
124
+ const finishMetadata = {
125
+ eventType: "finished",
126
+ ...startMetadata,
127
+ finishTimestamp: new Date(),
128
+ durationInMs: durationMeasurement.durationInMs,
129
+ };
130
+ eventSource.notify({
131
+ ...finishMetadata,
132
+ result: {
133
+ status: "success",
134
+ ...getResult(),
135
+ },
136
+ });
84
137
  }
85
- if (processFinished != null) {
86
- const value = processFinished();
87
- if (value !== undefined) {
88
- responseQueue.push(value);
89
- }
138
+ finally {
139
+ // always close the queue when done, no matter where a potential error happened:
140
+ responseQueue.close();
90
141
  }
91
- responseQueue.close();
92
- const finishMetadata = {
93
- eventType: "finished",
94
- ...startMetadata,
95
- finishTimestamp: new Date(),
96
- durationInMs: durationMeasurement.durationInMs,
97
- };
98
- eventSource.notify({
99
- ...finishMetadata,
100
- result: {
101
- status: "success",
102
- ...getResult(),
103
- },
104
- });
105
142
  })();
106
143
  return responseQueue;
107
144
  });
@@ -52,53 +52,90 @@ export async function executeStreamCall({ model, options, input, functionType, s
52
52
  const responseQueue = new AsyncQueue();
53
53
  // run async:
54
54
  (async function () {
55
- for await (const event of deltaIterable) {
56
- if (event?.type === "error") {
57
- const error = event.error;
55
+ try {
56
+ const loopResult = await runSafe(async () => {
57
+ for await (const event of deltaIterable) {
58
+ if (event?.type === "error") {
59
+ const error = event.error;
60
+ const finishMetadata = {
61
+ eventType: "finished",
62
+ ...startMetadata,
63
+ finishTimestamp: new Date(),
64
+ durationInMs: durationMeasurement.durationInMs,
65
+ };
66
+ eventSource.notify(error instanceof AbortError
67
+ ? {
68
+ ...finishMetadata,
69
+ result: { status: "abort" },
70
+ }
71
+ : {
72
+ ...finishMetadata,
73
+ result: { status: "error", error },
74
+ });
75
+ throw error;
76
+ }
77
+ if (event?.type === "delta") {
78
+ const value = processDelta(event);
79
+ if (value !== undefined) {
80
+ responseQueue.push(value);
81
+ }
82
+ }
83
+ }
84
+ if (processFinished != null) {
85
+ const value = processFinished();
86
+ if (value !== undefined) {
87
+ responseQueue.push(value);
88
+ }
89
+ }
90
+ });
91
+ // deal with abort or errors that happened during streaming:
92
+ if (!loopResult.ok) {
58
93
  const finishMetadata = {
59
94
  eventType: "finished",
60
95
  ...startMetadata,
61
96
  finishTimestamp: new Date(),
62
97
  durationInMs: durationMeasurement.durationInMs,
63
98
  };
64
- eventSource.notify(error instanceof AbortError
65
- ? {
66
- ...finishMetadata,
67
- result: { status: "abort" },
68
- }
69
- : {
99
+ if (loopResult.isAborted) {
100
+ eventSource.notify({
70
101
  ...finishMetadata,
71
- result: { status: "error", error },
102
+ eventType: "finished",
103
+ result: {
104
+ status: "abort",
105
+ },
72
106
  });
73
- throw error;
74
- }
75
- if (event?.type === "delta") {
76
- const value = processDelta(event);
77
- if (value !== undefined) {
78
- responseQueue.push(value);
107
+ responseQueue.error(new AbortError());
108
+ return; // error is handled through queue
79
109
  }
110
+ eventSource.notify({
111
+ ...finishMetadata,
112
+ eventType: "finished",
113
+ result: {
114
+ status: "error",
115
+ error: loopResult.error,
116
+ },
117
+ });
118
+ responseQueue.error(loopResult.error);
119
+ return; // error is handled through queue
80
120
  }
121
+ const finishMetadata = {
122
+ eventType: "finished",
123
+ ...startMetadata,
124
+ finishTimestamp: new Date(),
125
+ durationInMs: durationMeasurement.durationInMs,
126
+ };
127
+ eventSource.notify({
128
+ ...finishMetadata,
129
+ result: {
130
+ status: "success",
131
+ ...getResult(),
132
+ },
133
+ });
81
134
  }
82
- if (processFinished != null) {
83
- const value = processFinished();
84
- if (value !== undefined) {
85
- responseQueue.push(value);
86
- }
135
+ finally {
136
+ // always close the queue when done, no matter where a potential error happened:
137
+ responseQueue.close();
87
138
  }
88
- responseQueue.close();
89
- const finishMetadata = {
90
- eventType: "finished",
91
- ...startMetadata,
92
- finishTimestamp: new Date(),
93
- durationInMs: durationMeasurement.durationInMs,
94
- };
95
- eventSource.notify({
96
- ...finishMetadata,
97
- result: {
98
- status: "success",
99
- ...getResult(),
100
- },
101
- });
102
139
  })();
103
140
  return responseQueue;
104
141
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "modelfusion",
3
3
  "description": "Build multimodal applications, chatbots, and agents with JavaScript and TypeScript.",
4
- "version": "0.53.0",
4
+ "version": "0.53.1",
5
5
  "author": "Lars Grammel",
6
6
  "license": "MIT",
7
7
  "keywords": [
@@ -22,7 +22,7 @@ class AsyncQueue {
22
22
  enumerable: true,
23
23
  configurable: true,
24
24
  writable: true,
25
- value: []
25
+ value: Array()
26
26
  });
27
27
  Object.defineProperty(this, "pendingResolvers", {
28
28
  enumerable: true,
@@ -37,6 +37,11 @@ class AsyncQueue {
37
37
  value: false
38
38
  });
39
39
  }
40
+ processPendingResolvers() {
41
+ while (this.pendingResolvers.length > 0) {
42
+ this.pendingResolvers.shift()?.();
43
+ }
44
+ }
40
45
  /**
41
46
  * Pushes an element onto the queue. If the queue is closed, an error is thrown.
42
47
  *
@@ -47,12 +52,17 @@ class AsyncQueue {
47
52
  */
48
53
  push(value) {
49
54
  if (this.closed) {
50
- throw new Error("Cannot push value to closed queue. The queue has been closed and is no longer accepting new items.");
55
+ throw new Error("Cannot push value to closed queue.");
51
56
  }
52
- this.values.push(value);
53
- while (this.pendingResolvers.length > 0) {
54
- this.pendingResolvers.shift()?.();
57
+ this.values.push({ type: "value", value });
58
+ this.processPendingResolvers();
59
+ }
60
+ error(error) {
61
+ if (this.closed) {
62
+ throw new Error("Cannot push error to closed queue.");
55
63
  }
64
+ this.values.push({ type: "error", error });
65
+ this.processPendingResolvers();
56
66
  }
57
67
  /**
58
68
  * Closes the queue, preventing more elements from being pushed onto it.
@@ -62,9 +72,7 @@ class AsyncQueue {
62
72
  */
63
73
  close() {
64
74
  this.closed = true;
65
- while (this.pendingResolvers.length > 0) {
66
- this.pendingResolvers.shift()?.();
67
- }
75
+ this.processPendingResolvers();
68
76
  }
69
77
  /**
70
78
  * Creates and returns an async iterator that allows the queue to be consumed.
@@ -81,11 +89,18 @@ class AsyncQueue {
81
89
  [Symbol.asyncIterator]() {
82
90
  let position = 0;
83
91
  return {
84
- next: () => new Promise((resolve) => {
92
+ next: () => new Promise((resolve, reject) => {
85
93
  const attemptResolve = () => {
86
94
  if (position < this.values.length) {
87
- // There's an available value, resolve it immediately.
88
- resolve({ value: this.values[position++], done: false });
95
+ const entry = this.values[position++];
96
+ switch (entry.type) {
97
+ case "value":
98
+ resolve({ value: entry.value, done: false });
99
+ break;
100
+ case "error":
101
+ reject(entry.error);
102
+ break;
103
+ }
89
104
  }
90
105
  else if (this.closed) {
91
106
  // The queue is closed, and there are no more values. Complete the iteration.
@@ -17,6 +17,7 @@ export declare class AsyncQueue<T> implements AsyncIterable<T> {
17
17
  private values;
18
18
  private pendingResolvers;
19
19
  private closed;
20
+ private processPendingResolvers;
20
21
  /**
21
22
  * Pushes an element onto the queue. If the queue is closed, an error is thrown.
22
23
  *
@@ -26,6 +27,7 @@ export declare class AsyncQueue<T> implements AsyncIterable<T> {
26
27
  * queue.push(2);
27
28
  */
28
29
  push(value: T): void;
30
+ error(error: unknown): void;
29
31
  /**
30
32
  * Closes the queue, preventing more elements from being pushed onto it.
31
33
  *
@@ -19,7 +19,7 @@ export class AsyncQueue {
19
19
  enumerable: true,
20
20
  configurable: true,
21
21
  writable: true,
22
- value: []
22
+ value: Array()
23
23
  });
24
24
  Object.defineProperty(this, "pendingResolvers", {
25
25
  enumerable: true,
@@ -34,6 +34,11 @@ export class AsyncQueue {
34
34
  value: false
35
35
  });
36
36
  }
37
+ processPendingResolvers() {
38
+ while (this.pendingResolvers.length > 0) {
39
+ this.pendingResolvers.shift()?.();
40
+ }
41
+ }
37
42
  /**
38
43
  * Pushes an element onto the queue. If the queue is closed, an error is thrown.
39
44
  *
@@ -44,12 +49,17 @@ export class AsyncQueue {
44
49
  */
45
50
  push(value) {
46
51
  if (this.closed) {
47
- throw new Error("Cannot push value to closed queue. The queue has been closed and is no longer accepting new items.");
52
+ throw new Error("Cannot push value to closed queue.");
48
53
  }
49
- this.values.push(value);
50
- while (this.pendingResolvers.length > 0) {
51
- this.pendingResolvers.shift()?.();
54
+ this.values.push({ type: "value", value });
55
+ this.processPendingResolvers();
56
+ }
57
+ error(error) {
58
+ if (this.closed) {
59
+ throw new Error("Cannot push error to closed queue.");
52
60
  }
61
+ this.values.push({ type: "error", error });
62
+ this.processPendingResolvers();
53
63
  }
54
64
  /**
55
65
  * Closes the queue, preventing more elements from being pushed onto it.
@@ -59,9 +69,7 @@ export class AsyncQueue {
59
69
  */
60
70
  close() {
61
71
  this.closed = true;
62
- while (this.pendingResolvers.length > 0) {
63
- this.pendingResolvers.shift()?.();
64
- }
72
+ this.processPendingResolvers();
65
73
  }
66
74
  /**
67
75
  * Creates and returns an async iterator that allows the queue to be consumed.
@@ -78,11 +86,18 @@ export class AsyncQueue {
78
86
  [Symbol.asyncIterator]() {
79
87
  let position = 0;
80
88
  return {
81
- next: () => new Promise((resolve) => {
89
+ next: () => new Promise((resolve, reject) => {
82
90
  const attemptResolve = () => {
83
91
  if (position < this.values.length) {
84
- // There's an available value, resolve it immediately.
85
- resolve({ value: this.values[position++], done: false });
92
+ const entry = this.values[position++];
93
+ switch (entry.type) {
94
+ case "value":
95
+ resolve({ value: entry.value, done: false });
96
+ break;
97
+ case "error":
98
+ reject(entry.error);
99
+ break;
100
+ }
86
101
  }
87
102
  else if (this.closed) {
88
103
  // The queue is closed, and there are no more values. Complete the iteration.
@@ -134,5 +134,5 @@ const AsyncQueue_js_1 = require("./AsyncQueue.cjs");
134
134
  (0, vitest_1.test)("throw error when pushing to a closed queue", async () => {
135
135
  const asyncQueue = new AsyncQueue_js_1.AsyncQueue();
136
136
  asyncQueue.close();
137
- (0, vitest_1.expect)(() => asyncQueue.push(1)).toThrowError("Cannot push value to closed queue. The queue has been closed and is no longer accepting new items.");
137
+ (0, vitest_1.expect)(() => asyncQueue.push(1)).toThrowError("Cannot push value to closed queue.");
138
138
  });
@@ -132,5 +132,5 @@ test("each consumer receives all pushed values under varying conditions", async
132
132
  test("throw error when pushing to a closed queue", async () => {
133
133
  const asyncQueue = new AsyncQueue();
134
134
  asyncQueue.close();
135
- expect(() => asyncQueue.push(1)).toThrowError("Cannot push value to closed queue. The queue has been closed and is no longer accepting new items.");
135
+ expect(() => asyncQueue.push(1)).toThrowError("Cannot push value to closed queue.");
136
136
  });
package/util/delay.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.delay = void 0;
4
- async function delay(ms) {
5
- return new Promise((resolve) => setTimeout(resolve, ms));
4
+ async function delay(delayInMs) {
5
+ return new Promise((resolve) => setTimeout(resolve, delayInMs));
6
6
  }
7
7
  exports.delay = delay;
package/util/delay.d.ts CHANGED
@@ -1 +1 @@
1
- export declare function delay(ms: number): Promise<void>;
1
+ export declare function delay(delayInMs: number): Promise<void>;
package/util/delay.js CHANGED
@@ -1,3 +1,3 @@
1
- export async function delay(ms) {
2
- return new Promise((resolve) => setTimeout(resolve, ms));
1
+ export async function delay(delayInMs) {
2
+ return new Promise((resolve) => setTimeout(resolve, delayInMs));
3
3
  }
package/util/index.cjs CHANGED
@@ -17,5 +17,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./AsyncQueue.cjs"), exports);
18
18
  __exportStar(require("./JSONParseError.cjs"), exports);
19
19
  __exportStar(require("./cosineSimilarity.cjs"), exports);
20
+ __exportStar(require("./delay.cjs"), exports);
20
21
  __exportStar(require("./getAudioFileExtension.cjs"), exports);
21
22
  __exportStar(require("./parseJSON.cjs"), exports);
package/util/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./AsyncQueue.js";
2
2
  export * from "./JSONParseError.js";
3
3
  export * from "./cosineSimilarity.js";
4
+ export * from "./delay.js";
4
5
  export * from "./getAudioFileExtension.js";
5
6
  export * from "./parseJSON.js";
package/util/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./AsyncQueue.js";
2
2
  export * from "./JSONParseError.js";
3
3
  export * from "./cosineSimilarity.js";
4
+ export * from "./delay.js";
4
5
  export * from "./getAudioFileExtension.js";
5
6
  export * from "./parseJSON.js";