@buoy-gg/agent-core 7.0.35 → 7.0.38

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 (208) hide show
  1. package/README.md +21 -14
  2. package/lib/commonjs/blocks/receipts.js +57 -1
  3. package/lib/commonjs/blocks/receipts.js.map +1 -1
  4. package/lib/commonjs/blocks/types.js +23 -2
  5. package/lib/commonjs/blocks/types.js.map +1 -1
  6. package/lib/commonjs/blocks/uiTool.js +423 -10
  7. package/lib/commonjs/blocks/uiTool.js.map +1 -1
  8. package/lib/commonjs/catalog/catalog.g.js +213 -48
  9. package/lib/commonjs/catalog/catalog.g.js.map +1 -1
  10. package/lib/commonjs/catalog/catalog.source.json +226 -48
  11. package/lib/commonjs/catalog/catalog.types.g.js +44 -0
  12. package/lib/commonjs/catalog/catalog.types.g.js.map +1 -0
  13. package/lib/commonjs/catalog/normalizeParams.js +333 -0
  14. package/lib/commonjs/catalog/normalizeParams.js.map +1 -0
  15. package/lib/commonjs/catalog/signature.js +68 -0
  16. package/lib/commonjs/catalog/signature.js.map +1 -0
  17. package/lib/commonjs/catalog/snapshotReads.js +13 -6
  18. package/lib/commonjs/catalog/snapshotReads.js.map +1 -1
  19. package/lib/commonjs/catalog/toProviderTools.js +44 -6
  20. package/lib/commonjs/catalog/toProviderTools.js.map +1 -1
  21. package/lib/commonjs/context/buildContextPack.js +367 -19
  22. package/lib/commonjs/context/buildContextPack.js.map +1 -1
  23. package/lib/commonjs/effects/digest.js +34 -0
  24. package/lib/commonjs/effects/digest.js.map +1 -0
  25. package/lib/commonjs/effects/ledger.js +98 -2
  26. package/lib/commonjs/effects/ledger.js.map +1 -1
  27. package/lib/commonjs/engine/askGate.js +287 -0
  28. package/lib/commonjs/engine/askGate.js.map +1 -0
  29. package/lib/commonjs/engine/effectFor.js +82 -1
  30. package/lib/commonjs/engine/effectFor.js.map +1 -1
  31. package/lib/commonjs/engine/evidence.js +113 -0
  32. package/lib/commonjs/engine/evidence.js.map +1 -0
  33. package/lib/commonjs/engine/historyBudget.js +363 -0
  34. package/lib/commonjs/engine/historyBudget.js.map +1 -0
  35. package/lib/commonjs/engine/retrieve.js +214 -0
  36. package/lib/commonjs/engine/retrieve.js.map +1 -0
  37. package/lib/commonjs/engine/runAgentTurn.js +1231 -120
  38. package/lib/commonjs/engine/runAgentTurn.js.map +1 -1
  39. package/lib/commonjs/engine/systemPrompt.js +103 -9
  40. package/lib/commonjs/engine/systemPrompt.js.map +1 -1
  41. package/lib/commonjs/engine/textToolCalls.js +276 -0
  42. package/lib/commonjs/engine/textToolCalls.js.map +1 -0
  43. package/lib/commonjs/engine/tokenCalibration.js +81 -0
  44. package/lib/commonjs/engine/tokenCalibration.js.map +1 -0
  45. package/lib/commonjs/engine/verify.js +300 -0
  46. package/lib/commonjs/engine/verify.js.map +1 -0
  47. package/lib/commonjs/index.js +74 -0
  48. package/lib/commonjs/index.js.map +1 -1
  49. package/lib/commonjs/policy/labels.js +19 -3
  50. package/lib/commonjs/policy/labels.js.map +1 -1
  51. package/lib/commonjs/policy/policy.js +6 -1
  52. package/lib/commonjs/policy/policy.js.map +1 -1
  53. package/lib/commonjs/policy/redact.js +42 -5
  54. package/lib/commonjs/policy/redact.js.map +1 -1
  55. package/lib/commonjs/providers/anthropic.js +340 -208
  56. package/lib/commonjs/providers/anthropic.js.map +1 -1
  57. package/lib/commonjs/providers/openai.js +237 -103
  58. package/lib/commonjs/providers/openai.js.map +1 -1
  59. package/lib/commonjs/providers/problem.js +98 -0
  60. package/lib/commonjs/providers/problem.js.map +1 -0
  61. package/lib/commonjs/providers/sse.js +170 -34
  62. package/lib/commonjs/providers/sse.js.map +1 -1
  63. package/lib/commonjs/providers/streamTimer.js +123 -0
  64. package/lib/commonjs/providers/streamTimer.js.map +1 -0
  65. package/lib/commonjs/providers/transport.js +123 -0
  66. package/lib/commonjs/providers/transport.js.map +1 -0
  67. package/lib/commonjs/providers/xhrStream.js +212 -0
  68. package/lib/commonjs/providers/xhrStream.js.map +1 -0
  69. package/lib/commonjs/session.js +242 -58
  70. package/lib/commonjs/session.js.map +1 -1
  71. package/lib/module/blocks/receipts.js +57 -1
  72. package/lib/module/blocks/receipts.js.map +1 -1
  73. package/lib/module/blocks/types.js +23 -2
  74. package/lib/module/blocks/types.js.map +1 -1
  75. package/lib/module/blocks/uiTool.js +421 -10
  76. package/lib/module/blocks/uiTool.js.map +1 -1
  77. package/lib/module/catalog/catalog.g.js +213 -48
  78. package/lib/module/catalog/catalog.g.js.map +1 -1
  79. package/lib/module/catalog/catalog.source.json +226 -48
  80. package/lib/module/catalog/catalog.types.g.js +40 -0
  81. package/lib/module/catalog/catalog.types.g.js.map +1 -0
  82. package/lib/module/catalog/normalizeParams.js +327 -0
  83. package/lib/module/catalog/normalizeParams.js.map +1 -0
  84. package/lib/module/catalog/signature.js +63 -0
  85. package/lib/module/catalog/signature.js.map +1 -0
  86. package/lib/module/catalog/snapshotReads.js +13 -6
  87. package/lib/module/catalog/snapshotReads.js.map +1 -1
  88. package/lib/module/catalog/toProviderTools.js +44 -6
  89. package/lib/module/catalog/toProviderTools.js.map +1 -1
  90. package/lib/module/context/buildContextPack.js +366 -19
  91. package/lib/module/context/buildContextPack.js.map +1 -1
  92. package/lib/module/effects/digest.js +29 -0
  93. package/lib/module/effects/digest.js.map +1 -0
  94. package/lib/module/effects/ledger.js +98 -2
  95. package/lib/module/effects/ledger.js.map +1 -1
  96. package/lib/module/engine/askGate.js +280 -0
  97. package/lib/module/engine/askGate.js.map +1 -0
  98. package/lib/module/engine/effectFor.js +83 -1
  99. package/lib/module/engine/effectFor.js.map +1 -1
  100. package/lib/module/engine/evidence.js +107 -0
  101. package/lib/module/engine/evidence.js.map +1 -0
  102. package/lib/module/engine/historyBudget.js +355 -0
  103. package/lib/module/engine/historyBudget.js.map +1 -0
  104. package/lib/module/engine/retrieve.js +210 -0
  105. package/lib/module/engine/retrieve.js.map +1 -0
  106. package/lib/module/engine/runAgentTurn.js +1227 -121
  107. package/lib/module/engine/runAgentTurn.js.map +1 -1
  108. package/lib/module/engine/systemPrompt.js +101 -9
  109. package/lib/module/engine/systemPrompt.js.map +1 -1
  110. package/lib/module/engine/textToolCalls.js +270 -0
  111. package/lib/module/engine/textToolCalls.js.map +1 -0
  112. package/lib/module/engine/tokenCalibration.js +75 -0
  113. package/lib/module/engine/tokenCalibration.js.map +1 -0
  114. package/lib/module/engine/verify.js +292 -0
  115. package/lib/module/engine/verify.js.map +1 -0
  116. package/lib/module/index.js +7 -3
  117. package/lib/module/index.js.map +1 -1
  118. package/lib/module/policy/labels.js +19 -3
  119. package/lib/module/policy/labels.js.map +1 -1
  120. package/lib/module/policy/policy.js +6 -1
  121. package/lib/module/policy/policy.js.map +1 -1
  122. package/lib/module/policy/redact.js +42 -5
  123. package/lib/module/policy/redact.js.map +1 -1
  124. package/lib/module/providers/anthropic.js +341 -209
  125. package/lib/module/providers/anthropic.js.map +1 -1
  126. package/lib/module/providers/openai.js +238 -104
  127. package/lib/module/providers/openai.js.map +1 -1
  128. package/lib/module/providers/problem.js +92 -0
  129. package/lib/module/providers/problem.js.map +1 -0
  130. package/lib/module/providers/sse.js +166 -34
  131. package/lib/module/providers/sse.js.map +1 -1
  132. package/lib/module/providers/streamTimer.js +118 -0
  133. package/lib/module/providers/streamTimer.js.map +1 -0
  134. package/lib/module/providers/transport.js +119 -0
  135. package/lib/module/providers/transport.js.map +1 -0
  136. package/lib/module/providers/xhrStream.js +207 -0
  137. package/lib/module/providers/xhrStream.js.map +1 -0
  138. package/lib/module/session.js +226 -60
  139. package/lib/module/session.js.map +1 -1
  140. package/lib/typescript/blocks/receipts.d.ts +5 -0
  141. package/lib/typescript/blocks/receipts.d.ts.map +1 -1
  142. package/lib/typescript/blocks/types.d.ts +23 -2
  143. package/lib/typescript/blocks/types.d.ts.map +1 -1
  144. package/lib/typescript/blocks/uiTool.d.ts +11 -2
  145. package/lib/typescript/blocks/uiTool.d.ts.map +1 -1
  146. package/lib/typescript/catalog/catalog.g.d.ts +2 -2
  147. package/lib/typescript/catalog/catalog.g.d.ts.map +1 -1
  148. package/lib/typescript/catalog/catalog.types.g.d.ts +40 -0
  149. package/lib/typescript/catalog/catalog.types.g.d.ts.map +1 -0
  150. package/lib/typescript/catalog/normalizeParams.d.ts +43 -0
  151. package/lib/typescript/catalog/normalizeParams.d.ts.map +1 -0
  152. package/lib/typescript/catalog/signature.d.ts +29 -0
  153. package/lib/typescript/catalog/signature.d.ts.map +1 -0
  154. package/lib/typescript/catalog/snapshotReads.d.ts.map +1 -1
  155. package/lib/typescript/catalog/toProviderTools.d.ts +28 -15
  156. package/lib/typescript/catalog/toProviderTools.d.ts.map +1 -1
  157. package/lib/typescript/context/buildContextPack.d.ts +24 -2
  158. package/lib/typescript/context/buildContextPack.d.ts.map +1 -1
  159. package/lib/typescript/effects/digest.d.ts +10 -0
  160. package/lib/typescript/effects/digest.d.ts.map +1 -0
  161. package/lib/typescript/effects/ledger.d.ts +73 -0
  162. package/lib/typescript/effects/ledger.d.ts.map +1 -1
  163. package/lib/typescript/engine/askGate.d.ts +29 -0
  164. package/lib/typescript/engine/askGate.d.ts.map +1 -0
  165. package/lib/typescript/engine/effectFor.d.ts +6 -1
  166. package/lib/typescript/engine/effectFor.d.ts.map +1 -1
  167. package/lib/typescript/engine/evidence.d.ts +67 -0
  168. package/lib/typescript/engine/evidence.d.ts.map +1 -0
  169. package/lib/typescript/engine/historyBudget.d.ts +115 -0
  170. package/lib/typescript/engine/historyBudget.d.ts.map +1 -0
  171. package/lib/typescript/engine/retrieve.d.ts +32 -0
  172. package/lib/typescript/engine/retrieve.d.ts.map +1 -0
  173. package/lib/typescript/engine/runAgentTurn.d.ts +179 -3
  174. package/lib/typescript/engine/runAgentTurn.d.ts.map +1 -1
  175. package/lib/typescript/engine/systemPrompt.d.ts +80 -0
  176. package/lib/typescript/engine/systemPrompt.d.ts.map +1 -1
  177. package/lib/typescript/engine/textToolCalls.d.ts +58 -0
  178. package/lib/typescript/engine/textToolCalls.d.ts.map +1 -0
  179. package/lib/typescript/engine/tokenCalibration.d.ts +46 -0
  180. package/lib/typescript/engine/tokenCalibration.d.ts.map +1 -0
  181. package/lib/typescript/engine/verify.d.ts +67 -0
  182. package/lib/typescript/engine/verify.d.ts.map +1 -0
  183. package/lib/typescript/index.d.ts +7 -3
  184. package/lib/typescript/index.d.ts.map +1 -1
  185. package/lib/typescript/policy/labels.d.ts.map +1 -1
  186. package/lib/typescript/policy/policy.d.ts.map +1 -1
  187. package/lib/typescript/policy/redact.d.ts.map +1 -1
  188. package/lib/typescript/providers/anthropic.d.ts.map +1 -1
  189. package/lib/typescript/providers/openai.d.ts +0 -16
  190. package/lib/typescript/providers/openai.d.ts.map +1 -1
  191. package/lib/typescript/providers/problem.d.ts +50 -0
  192. package/lib/typescript/providers/problem.d.ts.map +1 -0
  193. package/lib/typescript/providers/sse.d.ts +86 -2
  194. package/lib/typescript/providers/sse.d.ts.map +1 -1
  195. package/lib/typescript/providers/streamTimer.d.ts +59 -0
  196. package/lib/typescript/providers/streamTimer.d.ts.map +1 -0
  197. package/lib/typescript/providers/transport.d.ts +48 -0
  198. package/lib/typescript/providers/transport.d.ts.map +1 -0
  199. package/lib/typescript/providers/types.d.ts +105 -3
  200. package/lib/typescript/providers/types.d.ts.map +1 -1
  201. package/lib/typescript/providers/xhrStream.d.ts +45 -0
  202. package/lib/typescript/providers/xhrStream.d.ts.map +1 -0
  203. package/lib/typescript/session.d.ts +55 -4
  204. package/lib/typescript/session.d.ts.map +1 -1
  205. package/lib/typescript/types.d.ts +6 -0
  206. package/lib/typescript/types.d.ts.map +1 -1
  207. package/package.json +10 -10
  208. package/LICENSE +0 -58
@@ -0,0 +1,276 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.SALVAGE_NOTE = void 0;
7
+ exports.couldBeToolCallEnvelope = couldBeToolCallEnvelope;
8
+ exports.parseTextToolCalls = parseTextToolCalls;
9
+ var _toProviderTools = require("../catalog/toProviderTools");
10
+ var _uiTool = require("../blocks/uiTool");
11
+ var _runId = require("../blocks/runId");
12
+ /**
13
+ * Recovering a tool call a model wrote as TEXT instead of calling.
14
+ *
15
+ * Every provider here is sent real tool definitions, and most models use them.
16
+ * Some don't — under a gateway, after a clarifying question, or just on the
17
+ * wrong roll, a model answers with the call spelled out as JSON:
18
+ *
19
+ * ```json
20
+ * {"calls":[{"tool":"buoy_zustand","action":"setState","params":{…}}],
21
+ * "reply":"Done — I added one Jigglypuff to your bag."}
22
+ * ```
23
+ *
24
+ * Left alone this is the worst outcome the product has: the user said "yes,
25
+ * go ahead", nothing happened, and what they got back was a wall of JSON
26
+ * claiming it did. The same class of problem as the parameter aliases in
27
+ * normalizeParams — models reach for a convention they know, and meeting them
28
+ * costs one parse.
29
+ *
30
+ * Two halves, and the first is why this file exists at all:
31
+ *
32
+ * 1. {@link couldBeToolCallEnvelope} runs on every delta, because the decision
33
+ * has to be made BEFORE the text reaches the screen. Streamed text cannot
34
+ * be taken back. It withholds only what could still be an envelope and
35
+ * releases the moment it can't be.
36
+ * 2. {@link parseTextToolCalls} turns the finished blob into ordinary calls,
37
+ * which then go through validation, the policy, and approval exactly like a
38
+ * native one. Nothing here decides that a call is allowed to run — a
39
+ * recovered destructive call still stops at the same approval sheet.
40
+ */
41
+
42
+ const id = (0, _runId.idFactory)("txt");
43
+
44
+ /** `{`, optionally inside a ```json fence. */
45
+ const OPENER = /^(?:```[a-zA-Z]*[ \t]*\r?\n?)?\{/;
46
+ /** A fence still arriving a backtick at a time: "`", "``", "```json\n". */
47
+ const PARTIAL_OPENER = /^`{1,3}[a-zA-Z]*\s*$/;
48
+ /**
49
+ * The keys an envelope names almost immediately.
50
+ *
51
+ * Deliberately excludes `name` and `action`: a response body the user asked to
52
+ * see routinely has a `name` field, and holding those back would make every
53
+ * JSON answer in the product stutter to catch a shape no model emits.
54
+ */
55
+ const ENVELOPE_KEY = /"(calls|tool_calls|toolCalls|tool|function)"/;
56
+ /**
57
+ * How far past the opening brace to keep withholding before an envelope has to
58
+ * have named itself. Long enough for `{\n "calls": [` with any indentation,
59
+ * short enough that a JSON answer the user actually asked for barely stutters.
60
+ */
61
+ const KEY_WINDOW = 80;
62
+
63
+ /**
64
+ * Could the text so far still turn out to be a tool-call envelope?
65
+ *
66
+ * False the instant it can't be, so an ordinary answer streams exactly as it
67
+ * did before — the window this holds is one opening brace plus a key name.
68
+ */
69
+ function couldBeToolCallEnvelope(text) {
70
+ const t = text.trimStart();
71
+ if (t.length === 0) return true;
72
+ if (PARTIAL_OPENER.test(t)) return true;
73
+ const opener = OPENER.exec(t);
74
+ if (!opener) return false;
75
+ const body = t.slice(opener[0].length);
76
+ return body.length <= KEY_WINDOW || ENVELOPE_KEY.test(body);
77
+ }
78
+
79
+ /**
80
+ * The JSON object inside the text, fence and trailing prose removed — as up
81
+ * to two readings, tried in order.
82
+ *
83
+ * The first version cut at the FIRST closing fence. Seen live: an envelope
84
+ * whose `reply` carried its own ```json block — the bag line the user had
85
+ * asked for — was cut off inside that string, failed to parse, and the raw
86
+ * blob became the answer. So: (1) cut at the LAST fence, which is the
87
+ * envelope's own closer when the text is fenced; (2) ignore fences entirely
88
+ * and take everything up to the last "}". Whichever parses wins.
89
+ */
90
+ function jsonBodies(text) {
91
+ const t = text.trim();
92
+ const unfenced = t.startsWith("```") ? t.replace(/^```[a-zA-Z]*[ \t]*\r?\n?/, "") : t;
93
+ const close = unfenced.lastIndexOf("```");
94
+ const candidates = [close >= 0 ? unfenced.slice(0, close) : unfenced, unfenced];
95
+ const out = [];
96
+ for (const raw of candidates) {
97
+ const c = raw.trim();
98
+ if (!c.startsWith("{")) continue;
99
+ const end = c.lastIndexOf("}");
100
+ if (end === -1) continue;
101
+ const body = c.slice(0, end + 1);
102
+ if (!out.includes(body)) out.push(body);
103
+ }
104
+ return out;
105
+ }
106
+
107
+ /**
108
+ * A last-resort parse for JSON a model got slightly wrong.
109
+ *
110
+ * The envelope that produced this file was not valid JSON. The model wrote
111
+ * `…"unitPrice":999}}}}],"reply":…` — four closing braces where five were
112
+ * needed, so the call object was never closed and `]` arrived while still
113
+ * inside it. Strict parsing rejects the whole thing, and rejecting it hands
114
+ * the user the blob and no action: exactly the failure this file exists to
115
+ * stop, and it survived the first version of it. A model that writes its call
116
+ * as prose is already off the rails; expecting it to balance twelve nested
117
+ * braces by hand is expecting the wrong thing.
118
+ *
119
+ * The repair is mechanical and changes no values: a container is auto-closed
120
+ * when a mismatched closer arrives, a stray closer with nothing open is
121
+ * dropped, and anything still open at the end is closed. Whatever comes out
122
+ * still has to READ as an envelope naming a tool this app has, and every call
123
+ * it yields goes through validation, the policy and approval. A repaired call
124
+ * is not a trusted one — it is only a legible one.
125
+ */
126
+ function repairJson(text) {
127
+ const out = [];
128
+ const stack = [];
129
+ let inString = false;
130
+ let escaped = false;
131
+ for (const ch of text) {
132
+ if (inString) {
133
+ out.push(ch);
134
+ if (escaped) escaped = false;else if (ch === "\\") escaped = true;else if (ch === '"') inString = false;
135
+ continue;
136
+ }
137
+ if (ch === '"') {
138
+ inString = true;
139
+ out.push(ch);
140
+ continue;
141
+ }
142
+ if (ch === "{" || ch === "[") {
143
+ stack.push(ch);
144
+ out.push(ch);
145
+ continue;
146
+ }
147
+ if (ch === "}" || ch === "]") {
148
+ const want = ch === "}" ? "{" : "[";
149
+ let i = stack.length - 1;
150
+ while (i >= 0 && stack[i] !== want) i--;
151
+ // Nothing this could be closing. Dropping it is the only repair that
152
+ // does not invent structure.
153
+ if (i < 0) continue;
154
+ while (stack.length - 1 > i) out.push(stack.pop() === "{" ? "}" : "]");
155
+ stack.pop();
156
+ out.push(ch);
157
+ continue;
158
+ }
159
+ out.push(ch);
160
+ }
161
+ if (inString) out.push('"');
162
+ while (stack.length) out.push(stack.pop() === "{" ? "}" : "]");
163
+ return out.join("");
164
+ }
165
+ const rec = v => v && typeof v === "object" && !Array.isArray(v) ? v : undefined;
166
+ const str = v => typeof v === "string" && v.length > 0 ? v : undefined;
167
+ function tryParse(text) {
168
+ try {
169
+ return JSON.parse(text);
170
+ } catch {
171
+ return undefined;
172
+ }
173
+ }
174
+
175
+ /** `params` may arrive as an object or, in the OpenAI convention, as a string. */
176
+ function asParams(v) {
177
+ const direct = rec(v);
178
+ if (direct) return direct;
179
+ const s = str(v);
180
+ if (!s) return undefined;
181
+ try {
182
+ return rec(JSON.parse(s));
183
+ } catch {
184
+ return undefined;
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Name it the way the provider would have, so the rest of the loop can't tell
190
+ * — or nothing, if it names no tool this app has.
191
+ *
192
+ * This is the guard that keeps ordinary JSON out. `{"name":"Bulbasaur", …}` is
193
+ * a Pokémon the user asked to see, not a call, and the only reliable way to
194
+ * tell is that no tool is called Bulbasaur.
195
+ */
196
+ function providerName(name, catalog) {
197
+ if (name === _uiTool.UI_TOOL_NAME) return name;
198
+ if (catalog.some(t => t.toolId === name)) return (0, _toProviderTools.toProviderToolName)(name);
199
+ if (catalog.some(t => (0, _toProviderTools.toProviderToolName)(t.toolId) === name)) return name;
200
+ return undefined;
201
+ }
202
+ function toCall(entry, catalog) {
203
+ const e = rec(entry);
204
+ if (!e) return undefined;
205
+ // `function` is the OpenAI nesting; `input` is Anthropic's.
206
+ const fn = rec(e.function);
207
+ const inner = rec(e.input) ?? rec(e.arguments) ?? fn;
208
+ const named = str(e.tool) ?? str(e.name) ?? str(fn?.name) ?? str(e.toolName);
209
+ const name = named ? providerName(named, catalog) : undefined;
210
+ if (!name) return undefined;
211
+ const action = str(e.action) ?? str(inner?.action);
212
+ // Every real action is named. Requiring it keeps a JSON object that merely
213
+ // happens to share a word with a tool from being run as one.
214
+ if (!action && name !== _uiTool.UI_TOOL_NAME) return undefined;
215
+ const params = asParams(e.params) ?? asParams(inner?.params) ?? asParams(fn?.arguments) ?? asParams(e.arguments) ?? {};
216
+ return {
217
+ id: id(),
218
+ name,
219
+ input: {
220
+ ...(action ? {
221
+ action
222
+ } : {}),
223
+ params
224
+ }
225
+ };
226
+ }
227
+ /**
228
+ * Read a text envelope as tool calls, or nothing if that is not what it is.
229
+ *
230
+ * Deliberately strict about the SHAPE and loose about the spelling: it will
231
+ * not turn arbitrary JSON into calls (a model showing the user a response body
232
+ * must still be shown), but it accepts every naming convention a model might
233
+ * carry over — `tool`/`name`, `params`/`input`/`arguments`, one call or many.
234
+ */
235
+ function parseTextToolCalls(text, catalog) {
236
+ const bodies = jsonBodies(text);
237
+ if (bodies.length === 0) return undefined;
238
+ // Strict first, on every reading; the repair only ever sees JSON that
239
+ // already failed every strict attempt.
240
+ let parsed;
241
+ for (const body of bodies) if ((parsed = tryParse(body)) !== undefined) break;
242
+ if (parsed === undefined) for (const body of bodies) if ((parsed = tryParse(repairJson(body))) !== undefined) break;
243
+ const env = rec(parsed);
244
+ if (!env) return undefined;
245
+ const list = env.calls ?? env.tool_calls ?? env.toolCalls;
246
+ const entries = Array.isArray(list) ? list : env.tool || env.name ? [env] : undefined;
247
+ if (!entries) return undefined;
248
+ const reply = str(env.reply) ?? str(env.text) ?? str(env.message) ?? "";
249
+
250
+ // An envelope with NO calls is the model wrapping a plain answer in the
251
+ // shape it uses for calls. Seen live: `{"calls":[],"reply":"Here is the
252
+ // full Poké Mart menu…"}` in a ```json fence, with a whole markdown table
253
+ // inside the string — and the screen showed the blob, because "nothing to
254
+ // salvage" used to mean "show the text as-is". There is nothing to run, but
255
+ // the reply is the answer and the wrapper is not. Show the words.
256
+ if (entries.length === 0) return reply ? {
257
+ calls: [],
258
+ reply
259
+ } : undefined;
260
+ const calls = entries.map(e => toCall(e, catalog)).filter(Boolean);
261
+ // Every entry has to read as a call. A partial parse would run half of what
262
+ // the model described and silently drop the rest, which is worse than
263
+ // running none of it.
264
+ if (calls.length === 0 || calls.length !== entries.length) return undefined;
265
+ return {
266
+ calls,
267
+ reply
268
+ };
269
+ }
270
+
271
+ /**
272
+ * Appended to a recovered call's result, so the model corrects itself for the
273
+ * rest of the conversation instead of writing the next one as text too.
274
+ */
275
+ const SALVAGE_NOTE = exports.SALVAGE_NOTE = "\n\n[You wrote this call as JSON in your reply instead of calling the tool. It was recovered and run this time. Call tools through the tool interface — text is shown to the user as-is, and a call written there does not run.]";
276
+ //# sourceMappingURL=textToolCalls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_toProviderTools","require","_uiTool","_runId","id","idFactory","OPENER","PARTIAL_OPENER","ENVELOPE_KEY","KEY_WINDOW","couldBeToolCallEnvelope","text","t","trimStart","length","test","opener","exec","body","slice","jsonBodies","trim","unfenced","startsWith","replace","close","lastIndexOf","candidates","out","raw","c","end","includes","push","repairJson","stack","inString","escaped","ch","want","i","pop","join","rec","v","Array","isArray","undefined","str","tryParse","JSON","parse","asParams","direct","s","providerName","name","catalog","UI_TOOL_NAME","some","toolId","toProviderToolName","toCall","entry","e","fn","function","inner","input","arguments","named","tool","toolName","action","params","parseTextToolCalls","bodies","parsed","env","list","calls","tool_calls","toolCalls","entries","reply","message","map","filter","Boolean","SALVAGE_NOTE","exports"],"sourceRoot":"../../../src","sources":["engine/textToolCalls.ts"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.TokenCalibration = exports.DEFAULT_CHARS_PER_TOKEN = void 0;
7
+ exports.promptTokensOf = promptTokensOf;
8
+ /**
9
+ * How many characters make a token — measured, not assumed.
10
+ *
11
+ * Every budget in this engine is kept in characters because that is what a
12
+ * string has, but every LIMIT that matters is in tokens: the provider's
13
+ * context window, the reply room, the cost. The conversion was a constant
14
+ * (4) everywhere. English prose does run near 4; the tool results this agent
15
+ * actually sends are JSON, which tokenizes nearer 3 — so a request the budget
16
+ * called 100k tokens could be 130k, on a gateway whose window is 128k. The
17
+ * proactive budget was optimistic in exactly the sessions it exists for.
18
+ *
19
+ * The provider tells us the truth after every request: its usage report
20
+ * carries the prompt's token count, and the engine knows how many characters
21
+ * it sent. One division gives the ratio for THIS conversation on THIS
22
+ * tokenizer; an exponential average smooths a single odd request (a round
23
+ * that was all prose, or all ids). Strands' design 0008 does the same in
24
+ * reverse — a known token baseline plus an estimate of the delta — and its
25
+ * decision record says why: public limits should be in LLM-native units.
26
+ *
27
+ * Clamped, because a wrong sample must not be able to blow the budget wide
28
+ * open (a gateway that reports nonsense usage) or shrink it to nothing.
29
+ */
30
+
31
+ const DEFAULT_CHARS_PER_TOKEN = exports.DEFAULT_CHARS_PER_TOKEN = 4;
32
+ const MIN_CHARS_PER_TOKEN = 2.5;
33
+ const MAX_CHARS_PER_TOKEN = 5;
34
+ /** Weight of the newest sample once there is history. The first sample replaces the default outright. */
35
+ const EMA_ALPHA = 0.3;
36
+ /** Below this many prompt tokens a sample says more about rounding than about the tokenizer. */
37
+ const MIN_SAMPLE_TOKENS = 500;
38
+ class TokenCalibration {
39
+ ratio = DEFAULT_CHARS_PER_TOKEN;
40
+ samples = 0;
41
+
42
+ /** Characters per token, as currently believed. */
43
+ get charsPerToken() {
44
+ return this.ratio;
45
+ }
46
+ get sampleCount() {
47
+ return this.samples;
48
+ }
49
+
50
+ /** Fold in one request: what was sent, and what the provider counted. */
51
+ observe(requestChars, promptTokens) {
52
+ if (!(requestChars > 0) || !(promptTokens >= MIN_SAMPLE_TOKENS)) return;
53
+ const sample = Math.min(MAX_CHARS_PER_TOKEN, Math.max(MIN_CHARS_PER_TOKEN, requestChars / promptTokens));
54
+ this.ratio = this.samples === 0 ? sample : this.ratio + EMA_ALPHA * (sample - this.ratio);
55
+ this.samples += 1;
56
+ }
57
+
58
+ /** Characters a token budget allows, at the believed ratio. */
59
+ chars(tokens) {
60
+ return Math.floor(tokens * this.ratio);
61
+ }
62
+ reset() {
63
+ this.ratio = DEFAULT_CHARS_PER_TOKEN;
64
+ this.samples = 0;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * The whole prompt's token count from a usage report.
70
+ *
71
+ * Anthropic reports the cached and freshly-written prefix SEPARATELY from
72
+ * `input`; OpenAI-shaped providers fold `cached_tokens` INTO `prompt_tokens`.
73
+ * See TokenUsage. Adding on the wrong protocol double-counts the cache and
74
+ * halves the believed ratio.
75
+ */
76
+ exports.TokenCalibration = TokenCalibration;
77
+ function promptTokensOf(usage, protocol) {
78
+ if (protocol === "anthropic") return usage.input + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
79
+ return usage.input;
80
+ }
81
+ //# sourceMappingURL=tokenCalibration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["DEFAULT_CHARS_PER_TOKEN","exports","MIN_CHARS_PER_TOKEN","MAX_CHARS_PER_TOKEN","EMA_ALPHA","MIN_SAMPLE_TOKENS","TokenCalibration","ratio","samples","charsPerToken","sampleCount","observe","requestChars","promptTokens","sample","Math","min","max","chars","tokens","floor","reset","promptTokensOf","usage","protocol","input","cacheRead","cacheWrite"],"sourceRoot":"../../../src","sources":["engine/tokenCalibration.ts"],"mappings":";;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAGO,MAAMA,uBAAuB,GAAAC,OAAA,CAAAD,uBAAA,GAAG,CAAC;AACxC,MAAME,mBAAmB,GAAG,GAAG;AAC/B,MAAMC,mBAAmB,GAAG,CAAC;AAC7B;AACA,MAAMC,SAAS,GAAG,GAAG;AACrB;AACA,MAAMC,iBAAiB,GAAG,GAAG;AAEtB,MAAMC,gBAAgB,CAAC;EACpBC,KAAK,GAAGP,uBAAuB;EAC/BQ,OAAO,GAAG,CAAC;;EAEnB;EACA,IAAIC,aAAaA,CAAA,EAAW;IAC1B,OAAO,IAAI,CAACF,KAAK;EACnB;EAEA,IAAIG,WAAWA,CAAA,EAAW;IACxB,OAAO,IAAI,CAACF,OAAO;EACrB;;EAEA;EACAG,OAAOA,CAACC,YAAoB,EAAEC,YAAoB,EAAQ;IACxD,IAAI,EAAED,YAAY,GAAG,CAAC,CAAC,IAAI,EAAEC,YAAY,IAAIR,iBAAiB,CAAC,EAAE;IACjE,MAAMS,MAAM,GAAGC,IAAI,CAACC,GAAG,CAACb,mBAAmB,EAAEY,IAAI,CAACE,GAAG,CAACf,mBAAmB,EAAEU,YAAY,GAAGC,YAAY,CAAC,CAAC;IACxG,IAAI,CAACN,KAAK,GAAG,IAAI,CAACC,OAAO,KAAK,CAAC,GAAGM,MAAM,GAAG,IAAI,CAACP,KAAK,GAAGH,SAAS,IAAIU,MAAM,GAAG,IAAI,CAACP,KAAK,CAAC;IACzF,IAAI,CAACC,OAAO,IAAI,CAAC;EACnB;;EAEA;EACAU,KAAKA,CAACC,MAAc,EAAU;IAC5B,OAAOJ,IAAI,CAACK,KAAK,CAACD,MAAM,GAAG,IAAI,CAACZ,KAAK,CAAC;EACxC;EAEAc,KAAKA,CAAA,EAAS;IACZ,IAAI,CAACd,KAAK,GAAGP,uBAAuB;IACpC,IAAI,CAACQ,OAAO,GAAG,CAAC;EAClB;AACF;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPAP,OAAA,CAAAK,gBAAA,GAAAA,gBAAA;AAQO,SAASgB,cAAcA,CAACC,KAAiB,EAAEC,QAAgC,EAAU;EAC1F,IAAIA,QAAQ,KAAK,WAAW,EAAE,OAAOD,KAAK,CAACE,KAAK,IAAIF,KAAK,CAACG,SAAS,IAAI,CAAC,CAAC,IAAIH,KAAK,CAACI,UAAU,IAAI,CAAC,CAAC;EACnG,OAAOJ,KAAK,CAACE,KAAK;AACpB","ignoreList":[]}
@@ -0,0 +1,300 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.VERIFIERS = void 0;
7
+ exports.atPath = atPath;
8
+ exports.isSubset = isSubset;
9
+ exports.verificationTrailer = verificationTrailer;
10
+ exports.verifyOutcome = verifyOutcome;
11
+ /**
12
+ * Outcome verification: after a write lands, check that what the user asked
13
+ * for actually happened — not just that the adapter said ok.
14
+ *
15
+ * "Install override → `{ok:true}`" was reported as done. But installed ≠ a
16
+ * request has used it ≠ the cart shows it, and each step can succeed while
17
+ * the outcome fails: the app had the old response cached, the screen kept
18
+ * saying In Stock, and the reply said "the item is now sold out". Strands'
19
+ * GoalLoop frames this as a completion criterion with bounded correction; the
20
+ * criterion borrowed here is DETERMINISTIC — a read-back the engine performs
21
+ * with the same tools the model has — not a second model judging the reply.
22
+ *
23
+ * Three outcomes, each honest about what it is:
24
+ *
25
+ * - `verified` — the read-back shows the requested state.
26
+ * - `unverified` — the write landed but the read-back cannot yet show the
27
+ * outcome (an override is installed but no request has
28
+ * matched it). Reported as exactly that, never as proof.
29
+ * - `failed` — the read-back contradicts the write. The model is told
30
+ * what was found and asked for ONE correction through the
31
+ * same policy and ledger path; never to repeat the write.
32
+ *
33
+ * Bounded by construction: each verifier makes a few reads, polls for at most
34
+ * `waitMs` (small, per action), and stops on the turn's abort signal. It never
35
+ * dispatches a write, and its reads are not stashed as evidence. Undefined
36
+ * means "no opinion" — nothing is appended, which is what every action
37
+ * without a verifier gets.
38
+ */
39
+
40
+ const rec = v => v && typeof v === "object" && !Array.isArray(v) ? v : undefined;
41
+ function sleep(ms, signal) {
42
+ return new Promise(resolve => {
43
+ if (signal?.aborted) return resolve();
44
+ const t = setTimeout(done, ms);
45
+ function done() {
46
+ clearTimeout(t);
47
+ signal?.removeEventListener("abort", done);
48
+ resolve();
49
+ }
50
+ signal?.addEventListener("abort", done, {
51
+ once: true
52
+ });
53
+ });
54
+ }
55
+
56
+ /** Poll `read` every `everyMs` until `until` says so or `waitMs` is spent. Returns the last read. */
57
+ async function poll(read, until, waitMs, everyMs, signal) {
58
+ const deadline = Date.now() + waitMs;
59
+ for (;;) {
60
+ const value = await read();
61
+ if (until(value)) return {
62
+ value,
63
+ satisfied: true
64
+ };
65
+ if (signal?.aborted || Date.now() + everyMs > deadline) return {
66
+ value,
67
+ satisfied: false
68
+ };
69
+ await sleep(everyMs, signal);
70
+ }
71
+ }
72
+
73
+ /** Every key in `want` present in `have` with an equal (deep) value. Arrays compare whole. */
74
+ function isSubset(want, have) {
75
+ if (want === null || typeof want !== "object" || Array.isArray(want)) return JSON.stringify(want) === JSON.stringify(have);
76
+ const h = rec(have);
77
+ if (!h) return false;
78
+ for (const [k, v] of Object.entries(want)) {
79
+ if (!(k in h)) return false;
80
+ if (!isSubset(v, h[k])) return false;
81
+ }
82
+ return true;
83
+ }
84
+
85
+ /**
86
+ * The adapters' path grammar — dotted keys, `[0]`, and the id-addressed
87
+ * `[field=value]` the prompt recommends for list edits ("lines[lineId=seed-1].qty").
88
+ * A mirror of shared/sync/valuePath.ts, kept here because agent-core imports
89
+ * nothing. The first live run wrote exactly that form and this resolver, then
90
+ * dotted-only, reported a landed edit as "did not take".
91
+ */
92
+ function atPath(root, path) {
93
+ const segs = path.replace(/^\/+/, "").match(/\[[^\]]*\]|[^.[\]/]+/g) ?? [];
94
+ let cur = root;
95
+ for (const seg of segs) {
96
+ if (cur === null || typeof cur !== "object") return undefined;
97
+ if (seg.startsWith("[")) {
98
+ const inner = seg.slice(1, -1).trim();
99
+ if (!Array.isArray(cur)) return undefined;
100
+ const eq = inner.indexOf("=");
101
+ if (eq < 0) {
102
+ cur = cur[Number(inner)];
103
+ } else {
104
+ const field = inner.slice(0, eq).trim();
105
+ const value = inner.slice(eq + 1).trim().replace(/^["']|["']$/g, "");
106
+ cur = cur.find(el => el && typeof el === "object" && String(el[field]) === value);
107
+ }
108
+ } else {
109
+ cur = cur[seg];
110
+ }
111
+ }
112
+ return cur;
113
+ }
114
+ const brief = v => {
115
+ const s = JSON.stringify(v);
116
+ return s === undefined ? "undefined" : s.length > 80 ? `${s.slice(0, 77)}…` : s;
117
+ };
118
+
119
+ // ── the verifiers ────────────────────────────────────────────────────────────
120
+
121
+ const storageSetItem = async ({
122
+ params,
123
+ dispatch
124
+ }) => {
125
+ if (typeof params.key !== "string") return undefined;
126
+ const back = await dispatch("storage", "async.getItem", {
127
+ key: params.key
128
+ });
129
+ const value = typeof back === "string" ? back : rec(back)?.value;
130
+ const wanted = typeof params.value === "string" ? params.value : JSON.stringify(params.value);
131
+ if (value === wanted) return {
132
+ status: "verified",
133
+ detail: `Read back: "${params.key}" now holds the written value.`
134
+ };
135
+ return {
136
+ status: "failed",
137
+ detail: `Read back after the write: "${params.key}" holds ${brief(value)}, not what was written. The write did not take.`
138
+ };
139
+ };
140
+ const zustandSetState = async ({
141
+ params,
142
+ dispatch,
143
+ after
144
+ }) => {
145
+ if (typeof params.storeName !== "string") return undefined;
146
+ const back = rec(after !== undefined ? after : await dispatch("zustand", "getStoreState", {
147
+ storeName: params.storeName
148
+ }));
149
+ if (!back || back.found === false) return {
150
+ status: "failed",
151
+ detail: `Read back: store "${params.storeName}" could not be read after the write.`
152
+ };
153
+ const current = back.currentState;
154
+ if (typeof params.path === "string") {
155
+ const got = atPath(current, params.path);
156
+ if (JSON.stringify(got) === JSON.stringify(params.value)) return {
157
+ status: "verified",
158
+ detail: `Read back: ${params.storeName}.${params.path} is now ${brief(got)}.`
159
+ };
160
+ return {
161
+ status: "failed",
162
+ detail: `Read back: ${params.storeName}.${params.path} is ${brief(got)}, not ${brief(params.value)}. The store did not take the write.`
163
+ };
164
+ }
165
+ const want = rec(params.state);
166
+ if (!want) return undefined;
167
+ if (isSubset(want, current)) return {
168
+ status: "verified",
169
+ detail: `Read back: "${params.storeName}" now holds the written fields (${Object.keys(want).join(", ")}).`
170
+ };
171
+ const wrong = Object.keys(want).filter(k => !isSubset(want[k], rec(current)?.[k]));
172
+ return {
173
+ status: "failed",
174
+ detail: `Read back: "${params.storeName}" does not hold the written value for ${wrong.join(", ")}. The store did not take the write — read it before deciding what to do.`
175
+ };
176
+ };
177
+ const querySetQueryData = async ({
178
+ params,
179
+ dispatch,
180
+ after
181
+ }) => {
182
+ const key = params.queryHash !== undefined ? {
183
+ queryHash: params.queryHash
184
+ } : params.queryKey !== undefined ? {
185
+ queryKey: params.queryKey
186
+ } : undefined;
187
+ if (!key) return undefined;
188
+ const back = rec(after !== undefined ? after : await dispatch("query", "getQueryData", key));
189
+ if (!back || back.found === false) return {
190
+ status: "failed",
191
+ detail: "Read back: the query could not be read after the write."
192
+ };
193
+ const data = back.data;
194
+ if (typeof params.path === "string") {
195
+ const got = atPath(data, params.path);
196
+ if (JSON.stringify(got) === JSON.stringify(params.value)) return {
197
+ status: "verified",
198
+ detail: `Read back: the cached ${params.path} is now ${brief(got)}.`
199
+ };
200
+ return {
201
+ status: "failed",
202
+ detail: `Read back: the cached ${params.path} is ${brief(got)}, not ${brief(params.value)}. The cache did not take the write — the app may have refetched over it.`
203
+ };
204
+ }
205
+ if (params.data === undefined) return undefined;
206
+ if (isSubset(params.data, data)) return {
207
+ status: "verified",
208
+ detail: "Read back: the cache now holds the written data."
209
+ };
210
+ return {
211
+ status: "failed",
212
+ detail: "Read back: the cache does not hold the written data — the app may have refetched over it, or the shape differed."
213
+ };
214
+ };
215
+ const navigate = async ({
216
+ params,
217
+ dispatch,
218
+ signal
219
+ }) => {
220
+ if (typeof params.path !== "string") return undefined;
221
+ const wanted = params.path.split("?")[0];
222
+ const {
223
+ value,
224
+ satisfied
225
+ } = await poll(async () => rec(await dispatch("route-events", "getCurrentRoute", {})), r => typeof r?.path === "string" && (r.path === wanted || r.path.split("?")[0] === wanted), 2_000, 250, signal);
226
+ if (satisfied) return {
227
+ status: "verified",
228
+ detail: `The app is now on ${wanted}.`
229
+ };
230
+ const at = typeof value?.path === "string" ? value.path : "an unknown route";
231
+ return {
232
+ status: "failed",
233
+ detail: `Two seconds after navigating, the app is on ${at}, not ${wanted}. Do not navigate again blindly — check the route exists (the sitemap) and whether something redirected.`
234
+ };
235
+ };
236
+ const upsertOverrideRule = async ({
237
+ params,
238
+ result,
239
+ dispatch,
240
+ signal
241
+ }) => {
242
+ const id = rec(rec(result)?.rule)?.id ?? rec(params.rule)?.id;
243
+ if (typeof id !== "string") return undefined;
244
+ const find = async () => {
245
+ const list = rec(await dispatch("network", "listOverrideRules", {}));
246
+ const rules = Array.isArray(list?.rules) ? list.rules : [];
247
+ return rules.map(rec).find(r => r?.id === id);
248
+ };
249
+ const {
250
+ value,
251
+ satisfied
252
+ } = await poll(find, r => typeof r?.hits === "number" && r.hits > 0, 3_000, 500, signal);
253
+ if (!value) return {
254
+ status: "failed",
255
+ detail: "The rule is not in the override list after the write. It did not install."
256
+ };
257
+ if (value.enabled === false) return {
258
+ status: "failed",
259
+ detail: "The rule is installed but disabled, so it will not fire."
260
+ };
261
+ if (satisfied) return {
262
+ status: "verified",
263
+ detail: `The rule has matched ${value.hits} request${value.hits === 1 ? "" : "s"} — the app has already received the overridden response.`
264
+ };
265
+ return {
266
+ status: "unverified",
267
+ detail: "The rule is installed, but no request has matched it yet — the app is still showing whatever it loaded before. A refetch or a visit to the screen that fetches this will exercise it; until then, do not say the screen shows the override."
268
+ };
269
+ };
270
+ const VERIFIERS = exports.VERIFIERS = {
271
+ "storage.async.setItem": storageSetItem,
272
+ "zustand.setState": zustandSetState,
273
+ "query.setQueryData": querySetQueryData,
274
+ "route-events.navigate": navigate,
275
+ "network.upsertOverrideRule": upsertOverrideRule
276
+ };
277
+
278
+ /** Run the verifier for this action, if there is one. Never throws: a read that blows up is "no opinion". */
279
+ async function verifyOutcome(input) {
280
+ const verifier = VERIFIERS[`${input.toolId}.${input.action}`];
281
+ if (!verifier) return undefined;
282
+ try {
283
+ return await verifier(input);
284
+ } catch {
285
+ return undefined;
286
+ }
287
+ }
288
+
289
+ /** The trailer the model reads. Kept in the `[Buoy] ` trailer form the bench's parser splits on. */
290
+ function verificationTrailer(v) {
291
+ switch (v.status) {
292
+ case "verified":
293
+ return `\n\n[Buoy] Verified: ${v.detail}`;
294
+ case "unverified":
295
+ return `\n\n[Buoy] Unverified: ${v.detail}`;
296
+ case "failed":
297
+ return `\n\n[Buoy] Check FAILED: ${v.detail} You may correct this ONCE — read the current state first, then make a different, targeted change; never repeat the same write and never claim the outcome happened.`;
298
+ }
299
+ }
300
+ //# sourceMappingURL=verify.js.map