simple-agents-wasm 0.2.32 → 0.2.33

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/index.js CHANGED
@@ -1,18 +1,19 @@
1
1
  import { parse as parseYaml } from "yaml";
2
+ import { configError, runtimeError } from "./runtime/errors.js";
3
+ import {
4
+ applyDeltaToAggregate,
5
+ createStreamAggregator,
6
+ createStreamEventBridge,
7
+ iterateSse,
8
+ parseSseEventBlock
9
+ } from "./runtime/stream.js";
10
+ import { loadRustModule } from "./runtime/rust-runtime.js";
2
11
 
3
12
  const DEFAULT_BASE_URLS = {
4
13
  openai: "https://api.openai.com/v1",
5
14
  openrouter: "https://openrouter.ai/api/v1"
6
15
  };
7
16
 
8
- function configError(message) {
9
- return new Error(`simple-agents-wasm config error: ${message}`);
10
- }
11
-
12
- function runtimeError(message) {
13
- return new Error(`simple-agents-wasm runtime error: ${message}`);
14
- }
15
-
16
17
  function toMessages(promptOrMessages) {
17
18
  if (typeof promptOrMessages === "string") {
18
19
  const content = promptOrMessages.trim();
@@ -62,62 +63,6 @@ function toToolCalls(toolCalls) {
62
63
  }));
63
64
  }
64
65
 
65
- function createStreamEventBridge(model, onChunk) {
66
- let aggregate = "";
67
- let finalId = "";
68
- let finalModel = model;
69
- let finalFinishReason;
70
-
71
- return {
72
- onEvent(event) {
73
- if (event.eventType === "delta") {
74
- const delta = event.delta;
75
- if (!delta) {
76
- return;
77
- }
78
-
79
- if (!finalId && delta.id) {
80
- finalId = delta.id;
81
- }
82
- if (delta.model) {
83
- finalModel = delta.model;
84
- }
85
- if (delta.content) {
86
- aggregate += delta.content;
87
- }
88
- if (delta.finishReason) {
89
- finalFinishReason = delta.finishReason;
90
- }
91
-
92
- onChunk({
93
- id: delta.id,
94
- model: delta.model,
95
- content: delta.content,
96
- finishReason: delta.finishReason,
97
- raw: delta.raw
98
- });
99
- }
100
-
101
- if (event.eventType === "error") {
102
- onChunk({
103
- id: finalId || "error",
104
- model: finalModel,
105
- error: event.error?.message ?? "stream error"
106
- });
107
- }
108
- },
109
- mergeResult(result, started) {
110
- return {
111
- ...result,
112
- id: result.id || finalId,
113
- model: result.model || finalModel,
114
- content: result.content ?? aggregate,
115
- finishReason: result.finishReason ?? finalFinishReason,
116
- latencyMs: Math.max(0, Math.round(performance.now() - started))
117
- };
118
- }
119
- };
120
- }
121
66
 
122
67
  function assertWorkflowResultShape(result) {
123
68
  if (result === null || typeof result !== "object") {
@@ -180,36 +125,17 @@ function finiteNumberOrNull(value) {
180
125
  }
181
126
 
182
127
  function buildStepDetail(step) {
183
- const detail = {
184
- elapsed_ms: step.elapsedMs,
128
+ return {
185
129
  node_id: step.nodeId,
186
- node_kind: step.nodeKind
130
+ node_kind: step.nodeKind,
131
+ model_name: step.modelName ?? null,
132
+ elapsed_ms: step.elapsedMs,
133
+ prompt_tokens: finiteNumberOrNull(step.promptTokens),
134
+ completion_tokens: finiteNumberOrNull(step.completionTokens),
135
+ total_tokens: finiteNumberOrNull(step.totalTokens),
136
+ reasoning_tokens: 0,
137
+ tokens_per_second: finiteNumberOrNull(step.tokensPerSecond)
187
138
  };
188
-
189
- if (step.nodeKind === "llm_call") {
190
- if (typeof step.modelName === "string") {
191
- detail.model_name = step.modelName;
192
- }
193
- const promptTokens = finiteNumberOrNull(step.promptTokens);
194
- if (promptTokens !== null) {
195
- detail.prompt_tokens = promptTokens;
196
- }
197
- const completionTokens = finiteNumberOrNull(step.completionTokens);
198
- if (completionTokens !== null) {
199
- detail.completion_tokens = completionTokens;
200
- }
201
- const totalTokens = finiteNumberOrNull(step.totalTokens);
202
- if (totalTokens !== null) {
203
- detail.total_tokens = totalTokens;
204
- }
205
- detail.reasoning_tokens = 0;
206
- const tokensPerSecond = finiteNumberOrNull(step.tokensPerSecond);
207
- if (tokensPerSecond !== null) {
208
- detail.tokens_per_second = tokensPerSecond;
209
- }
210
- }
211
-
212
- return detail;
213
139
  }
214
140
 
215
141
  function buildWorkflowNerdstats(summary) {
@@ -605,10 +531,6 @@ function llmOutputSchema(node) {
605
531
  };
606
532
  }
607
533
 
608
- function normalizeSseChunk(chunk) {
609
- return chunk.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
610
- }
611
-
612
534
  function evaluateSwitchCondition(condition, context) {
613
535
  if (typeof condition !== "string") {
614
536
  return false;
@@ -629,66 +551,6 @@ function evaluateSwitchCondition(condition, context) {
629
551
  return false;
630
552
  }
631
553
 
632
- function parseSseEventBlock(block) {
633
- const lines = block.split("\n");
634
- const dataLines = [];
635
- for (const line of lines) {
636
- if (line.startsWith("data:")) {
637
- dataLines.push(line.slice(5).trimStart());
638
- }
639
- }
640
-
641
- if (dataLines.length === 0) {
642
- return null;
643
- }
644
-
645
- const payload = dataLines.join("\n");
646
- if (payload === "[DONE]") {
647
- return { done: true };
648
- }
649
-
650
- try {
651
- return { done: false, json: JSON.parse(payload), raw: payload };
652
- } catch {
653
- return { done: false, raw: payload };
654
- }
655
- }
656
-
657
- async function* iterateSse(response) {
658
- if (!response.body) {
659
- throw runtimeError("stream response had no body");
660
- }
661
-
662
- const reader = response.body.getReader();
663
- const decoder = new TextDecoder();
664
- let buffer = "";
665
-
666
- while (true) {
667
- const { value, done } = await reader.read();
668
- if (done) {
669
- break;
670
- }
671
-
672
- buffer += normalizeSseChunk(decoder.decode(value, { stream: true }));
673
- let delimiterIndex = buffer.indexOf("\n\n");
674
- while (delimiterIndex !== -1) {
675
- const block = buffer.slice(0, delimiterIndex).trim();
676
- buffer = buffer.slice(delimiterIndex + 2);
677
- if (block.length > 0) {
678
- yield block;
679
- }
680
- delimiterIndex = buffer.indexOf("\n\n");
681
- }
682
- }
683
-
684
- buffer += normalizeSseChunk(decoder.decode());
685
-
686
- const trailing = buffer.trim();
687
- if (trailing.length > 0) {
688
- yield trailing;
689
- }
690
- }
691
-
692
554
  class BrowserJsClient {
693
555
  constructor(provider, config) {
694
556
  if (provider !== "openai" && provider !== "openrouter") {
@@ -829,10 +691,7 @@ class BrowserJsClient {
829
691
  }
830
692
 
831
693
  const started = performance.now();
832
- let responseId = "";
833
- let responseModel = model;
834
- let aggregate = "";
835
- let finishReason;
694
+ const aggregateState = createStreamAggregator(model);
836
695
  let usage = {
837
696
  promptTokens: 0,
838
697
  completionTokens: 0,
@@ -867,18 +726,7 @@ class BrowserJsClient {
867
726
  raw: parsed.raw
868
727
  };
869
728
 
870
- if (!responseId && delta.id) {
871
- responseId = delta.id;
872
- }
873
- if (delta.model) {
874
- responseModel = delta.model;
875
- }
876
- if (delta.content) {
877
- aggregate += delta.content;
878
- }
879
- if (delta.finishReason) {
880
- finishReason = delta.finishReason;
881
- }
729
+ applyDeltaToAggregate(aggregateState, delta);
882
730
  if (chunk?.usage && typeof chunk.usage === "object") {
883
731
  usage = toUsage(chunk.usage);
884
732
  usageAvailable = true;
@@ -897,11 +745,11 @@ class BrowserJsClient {
897
745
  const latencyMs = Math.max(0, Math.round(performance.now() - started));
898
746
 
899
747
  return {
900
- id: responseId,
901
- model: responseModel,
748
+ id: aggregateState.responseId,
749
+ model: aggregateState.responseModel,
902
750
  role: "assistant",
903
- content: aggregate,
904
- finishReason,
751
+ content: aggregateState.aggregate,
752
+ finishReason: aggregateState.finishReason,
905
753
  usage: {
906
754
  ...usage
907
755
  },
@@ -1323,25 +1171,6 @@ class BrowserJsClient {
1323
1171
  }
1324
1172
  }
1325
1173
 
1326
- let rustModulePromise;
1327
-
1328
- async function loadRustModule() {
1329
- if (!rustModulePromise) {
1330
- rustModulePromise = (async () => {
1331
- try {
1332
- const moduleValue = await import("./pkg/simple_agents_wasm.js");
1333
- const wasmUrl = new URL("./pkg/simple_agents_wasm_bg.wasm", import.meta.url);
1334
- await moduleValue.default({ module_or_path: wasmUrl });
1335
- return moduleValue;
1336
- } catch {
1337
- return null;
1338
- }
1339
- })();
1340
- }
1341
-
1342
- return rustModulePromise;
1343
- }
1344
-
1345
1174
  export class Client {
1346
1175
  constructor(provider, config) {
1347
1176
  this.fallbackClient = new BrowserJsClient(provider, config);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simple-agents-wasm",
3
- "version": "0.2.32",
3
+ "version": "0.2.33",
4
4
  "description": "Browser-compatible SimpleAgents client for OpenAI-compatible providers",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -16,6 +16,7 @@
16
16
  "files": [
17
17
  "index.js",
18
18
  "index.d.ts",
19
+ "runtime",
19
20
  "README.md",
20
21
  "pkg",
21
22
  "rust/Cargo.toml",
Binary file
@@ -0,0 +1,7 @@
1
+ export function configError(message) {
2
+ return new Error(`simple-agents-wasm config error: ${message}`);
3
+ }
4
+
5
+ export function runtimeError(message) {
6
+ return new Error(`simple-agents-wasm runtime error: ${message}`);
7
+ }
@@ -0,0 +1,18 @@
1
+ let rustModulePromise;
2
+
3
+ export async function loadRustModule() {
4
+ if (!rustModulePromise) {
5
+ rustModulePromise = (async () => {
6
+ try {
7
+ const moduleValue = await import("../pkg/simple_agents_wasm.js");
8
+ const wasmUrl = new URL("../pkg/simple_agents_wasm_bg.wasm", import.meta.url);
9
+ await moduleValue.default({ module_or_path: wasmUrl });
10
+ return moduleValue;
11
+ } catch {
12
+ return null;
13
+ }
14
+ })();
15
+ }
16
+
17
+ return rustModulePromise;
18
+ }
@@ -0,0 +1,136 @@
1
+ import { runtimeError } from "./errors.js";
2
+
3
+ export function createStreamAggregator(model) {
4
+ return {
5
+ responseId: "",
6
+ responseModel: model,
7
+ aggregate: "",
8
+ finishReason: undefined
9
+ };
10
+ }
11
+
12
+ export function applyDeltaToAggregate(state, delta) {
13
+ if (!state || !delta) {
14
+ return;
15
+ }
16
+
17
+ if (!state.responseId && delta.id) {
18
+ state.responseId = delta.id;
19
+ }
20
+ if (delta.model) {
21
+ state.responseModel = delta.model;
22
+ }
23
+ if (delta.content) {
24
+ state.aggregate += delta.content;
25
+ }
26
+ if (delta.finishReason) {
27
+ state.finishReason = delta.finishReason;
28
+ }
29
+ }
30
+
31
+ export function createStreamEventBridge(model, onChunk) {
32
+ const aggregateState = createStreamAggregator(model);
33
+
34
+ return {
35
+ onEvent(event) {
36
+ if (event.eventType === "delta") {
37
+ const delta = event.delta;
38
+ if (!delta) {
39
+ return;
40
+ }
41
+
42
+ applyDeltaToAggregate(aggregateState, delta);
43
+
44
+ onChunk({
45
+ id: delta.id,
46
+ model: delta.model,
47
+ content: delta.content,
48
+ finishReason: delta.finishReason,
49
+ raw: delta.raw
50
+ });
51
+ }
52
+
53
+ if (event.eventType === "error") {
54
+ onChunk({
55
+ id: aggregateState.responseId || "error",
56
+ model: aggregateState.responseModel,
57
+ error: event.error?.message ?? "stream error"
58
+ });
59
+ }
60
+ },
61
+ mergeResult(result, started) {
62
+ return {
63
+ ...result,
64
+ id: result.id || aggregateState.responseId,
65
+ model: result.model || aggregateState.responseModel,
66
+ content: result.content ?? aggregateState.aggregate,
67
+ finishReason: result.finishReason ?? aggregateState.finishReason,
68
+ latencyMs: Math.max(0, Math.round(performance.now() - started))
69
+ };
70
+ }
71
+ };
72
+ }
73
+
74
+ function normalizeSseChunk(chunk) {
75
+ return chunk.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
76
+ }
77
+
78
+ export function parseSseEventBlock(block) {
79
+ const lines = block.split("\n");
80
+ const dataLines = [];
81
+ for (const line of lines) {
82
+ if (line.startsWith("data:")) {
83
+ dataLines.push(line.slice(5).trimStart());
84
+ }
85
+ }
86
+
87
+ if (dataLines.length === 0) {
88
+ return null;
89
+ }
90
+
91
+ const payload = dataLines.join("\n");
92
+ if (payload === "[DONE]") {
93
+ return { done: true };
94
+ }
95
+
96
+ try {
97
+ return { done: false, json: JSON.parse(payload), raw: payload };
98
+ } catch {
99
+ return { done: false, raw: payload };
100
+ }
101
+ }
102
+
103
+ export async function* iterateSse(response) {
104
+ if (!response.body) {
105
+ throw runtimeError("stream response had no body");
106
+ }
107
+
108
+ const reader = response.body.getReader();
109
+ const decoder = new TextDecoder();
110
+ let buffer = "";
111
+
112
+ while (true) {
113
+ const { value, done } = await reader.read();
114
+ if (done) {
115
+ break;
116
+ }
117
+
118
+ buffer += normalizeSseChunk(decoder.decode(value, { stream: true }));
119
+ let delimiterIndex = buffer.indexOf("\n\n");
120
+ while (delimiterIndex !== -1) {
121
+ const block = buffer.slice(0, delimiterIndex).trim();
122
+ buffer = buffer.slice(delimiterIndex + 2);
123
+ if (block.length > 0) {
124
+ yield block;
125
+ }
126
+ delimiterIndex = buffer.indexOf("\n\n");
127
+ }
128
+ }
129
+
130
+ buffer += normalizeSseChunk(decoder.decode());
131
+
132
+ const trailing = buffer.trim();
133
+ if (trailing.length > 0) {
134
+ yield trailing;
135
+ }
136
+ }
package/rust/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "simple-agents-wasm-rust"
3
- version = "0.2.32"
3
+ version = "0.2.33"
4
4
  edition = "2021"
5
5
  license = "MIT OR Apache-2.0"
6
6