@sprucelabs/sprucebot-llm 18.1.0 → 18.1.2

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.
@@ -1,4 +1,10 @@
1
1
  import { ResponseParser, LlmCallbackMap, ParsedResponse } from '../llm.types';
2
+ /**
3
+ * Always produce a serializable error representation for @results.
4
+ * Plain Error becomes its message (never {}). Schema-like errors with enumerable
5
+ * payload fall back to message string for a stable wire format.
6
+ */
7
+ export declare function serializeCallbackError(err: unknown): string;
2
8
  export default class ResponseParserV2 implements ResponseParser {
3
9
  parse(response: string, callbacks?: LlmCallbackMap): Promise<ParsedResponse>;
4
10
  private invokeCallbacks;
@@ -9,14 +9,186 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { DONE_TOKEN } from '../bots/templates.js';
11
11
  import validateAndNormalizeCallbackOptions from './validateAndNormalizeCallbackOptions.js';
12
+ const RESERVED_CALLBACK_NAMES = new Set(['updateState', 'results']);
13
+ /**
14
+ * Always produce a serializable error representation for @results.
15
+ * Plain Error becomes its message (never {}). Schema-like errors with enumerable
16
+ * payload fall back to message string for a stable wire format.
17
+ */
18
+ export function serializeCallbackError(err) {
19
+ if (err instanceof Error) {
20
+ return err.message && err.message.length > 0 ? err.message : 'Error';
21
+ }
22
+ if (typeof err === 'string') {
23
+ return err;
24
+ }
25
+ if (err === null) {
26
+ return 'null';
27
+ }
28
+ if (err === undefined) {
29
+ return 'undefined';
30
+ }
31
+ if (typeof err === 'symbol') {
32
+ return String(err);
33
+ }
34
+ try {
35
+ const json = JSON.stringify(err);
36
+ if (json && json !== '{}') {
37
+ return json;
38
+ }
39
+ }
40
+ catch (_a) {
41
+ // fall through
42
+ }
43
+ try {
44
+ return String(err);
45
+ }
46
+ catch (_b) {
47
+ return 'Unknown error';
48
+ }
49
+ }
50
+ /** Find matching closing brace for a JSON object starting at openIdx ('{'). */
51
+ function findMatchingBrace(text, openIdx) {
52
+ let depth = 0;
53
+ let inString = false;
54
+ let escape = false;
55
+ for (let i = openIdx; i < text.length; i++) {
56
+ const ch = text[i];
57
+ if (inString) {
58
+ if (escape) {
59
+ escape = false;
60
+ continue;
61
+ }
62
+ if (ch === '\\') {
63
+ escape = true;
64
+ continue;
65
+ }
66
+ if (ch === '"') {
67
+ inString = false;
68
+ }
69
+ continue;
70
+ }
71
+ if (ch === '"') {
72
+ inString = true;
73
+ continue;
74
+ }
75
+ if (ch === '{') {
76
+ depth++;
77
+ }
78
+ else if (ch === '}') {
79
+ depth--;
80
+ if (depth === 0) {
81
+ return i;
82
+ }
83
+ }
84
+ }
85
+ return -1;
86
+ }
87
+ /**
88
+ * Extract @name(...) call spans. Supports multi-line JSON objects via brace-depth
89
+ * scanning and optional leading/trailing whitespace on the call line.
90
+ */
91
+ function extractCallbackSpans(message) {
92
+ const spans = [];
93
+ const re = /^[ \t]*@([A-Za-z_]\w*)\(/gm;
94
+ let m;
95
+ while ((m = re.exec(message)) !== null) {
96
+ const name = m[1];
97
+ const start = m.index;
98
+ const openParen = m.index + m[0].length - 1;
99
+ let i = openParen + 1;
100
+ while (i < message.length && /[ \t\r\n]/.test(message[i])) {
101
+ i++;
102
+ }
103
+ let argsJson;
104
+ let closeParen;
105
+ if (i < message.length && message[i] === ')') {
106
+ closeParen = i;
107
+ argsJson = undefined;
108
+ }
109
+ else if (i < message.length && message[i] === '{') {
110
+ const braceEnd = findMatchingBrace(message, i);
111
+ if (braceEnd < 0) {
112
+ spans.push({
113
+ name,
114
+ fullMatch: message.slice(start),
115
+ start,
116
+ end: message.length,
117
+ });
118
+ break;
119
+ }
120
+ argsJson = message.slice(i, braceEnd + 1);
121
+ let j = braceEnd + 1;
122
+ while (j < message.length && /[ \t\r\n]/.test(message[j])) {
123
+ j++;
124
+ }
125
+ if (j >= message.length || message[j] !== ')') {
126
+ spans.push({
127
+ name,
128
+ fullMatch: message.slice(start, braceEnd + 1),
129
+ argsJson,
130
+ start,
131
+ end: braceEnd + 1,
132
+ });
133
+ re.lastIndex = braceEnd + 1;
134
+ continue;
135
+ }
136
+ closeParen = j;
137
+ }
138
+ else {
139
+ re.lastIndex = openParen + 1;
140
+ continue;
141
+ }
142
+ let end = closeParen + 1;
143
+ // Include trailing newline after the call when present (common LLM layout)
144
+ if (message[end] === '\r') {
145
+ end++;
146
+ }
147
+ if (message[end] === '\n') {
148
+ end++;
149
+ }
150
+ // Leading newline is part of fullMatch when start points after prior content;
151
+ // also absorb a single leading newline just before the match for strip parity
152
+ // with historic `\n@name(...)\n` fixtures.
153
+ let matchStart = start;
154
+ if (matchStart > 0 && message[matchStart - 1] === '\n') {
155
+ matchStart--;
156
+ if (matchStart > 0 && message[matchStart - 1] === '\r') {
157
+ matchStart--;
158
+ }
159
+ }
160
+ spans.push({
161
+ name,
162
+ fullMatch: message.slice(matchStart, end),
163
+ argsJson,
164
+ start: matchStart,
165
+ end,
166
+ });
167
+ re.lastIndex = end;
168
+ }
169
+ return spans;
170
+ }
171
+ function stripCallFromMessage(message, fullMatch) {
172
+ // Historic behavior: remove the call span and glue surrounding text without
173
+ // injecting separators (trim each side after split).
174
+ return message
175
+ .split(fullMatch)
176
+ .map((s) => s.trim())
177
+ .join('')
178
+ .trim();
179
+ }
12
180
  export default class ResponseParserV2 {
13
181
  parse(response, callbacks) {
14
182
  return __awaiter(this, void 0, void 0, function* () {
15
183
  let message = response.replace(DONE_TOKEN, '').trim();
16
184
  let state = undefined;
17
185
  let callbackResults = undefined;
18
- const hasCallbacks = callbacks != null &&
186
+ // Enter the pass when any @name( call shape is present (incl. typos)
187
+ // or when a registered callback name appears as a call prefix.
188
+ const hasCallShape = message != null && /^[ \t]*@\w+\(/m.test(message);
189
+ const hasRegisteredName = callbacks != null &&
19
190
  Object.keys(callbacks).some((name) => message.includes(`@${name}(`));
191
+ const hasCallbacks = hasCallShape || hasRegisteredName;
20
192
  if (hasCallbacks) {
21
193
  const { callbackResults: c, message: m } = yield this.invokeCallbacks(message, callbacks);
22
194
  callbackResults = c;
@@ -53,26 +225,36 @@ export default class ResponseParserV2 {
53
225
  }
54
226
  invokeCallbacks(message, callbacks) {
55
227
  return __awaiter(this, void 0, void 0, function* () {
228
+ var _a;
56
229
  let callbackStrippedMessage = message;
57
230
  let callbackResults = '';
58
- const reserved = new Set(['updateState', 'results']);
59
- const matches = [...message.matchAll(/^@(\w+)\(({.*})?\)$/gm)];
60
- for (const match of matches) {
61
- if (reserved.has(match[1])) {
231
+ const spans = extractCallbackSpans(message);
232
+ for (const span of spans) {
233
+ if (RESERVED_CALLBACK_NAMES.has(span.name)) {
234
+ continue;
235
+ }
236
+ const name = span.name;
237
+ // Always strip the call from the user-facing message once recognized.
238
+ callbackStrippedMessage = stripCallFromMessage(callbackStrippedMessage, span.fullMatch.trim());
239
+ let options;
240
+ try {
241
+ options = span.argsJson ? JSON.parse(span.argsJson) : undefined;
242
+ }
243
+ catch (err) {
244
+ callbackResults += this.renderCallbackResults({
245
+ name,
246
+ error: `Invalid JSON arguments for @${name}: ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : String(err)}. Arguments must be valid JSON object.`,
247
+ });
62
248
  continue;
63
249
  }
64
- const parsed = match[2] ? JSON.parse(match[2]) : undefined;
65
- const name = match[1];
66
- const options = parsed;
67
250
  const callback = callbacks === null || callbacks === void 0 ? void 0 : callbacks[name];
68
251
  if (!callback) {
252
+ callbackResults += this.renderCallbackResults({
253
+ name,
254
+ error: `Unknown callback @${name} — not registered.`,
255
+ });
69
256
  continue;
70
257
  }
71
- const parts = callbackStrippedMessage.split(match[0]);
72
- callbackStrippedMessage = parts
73
- .map((s) => s.trim())
74
- .join('')
75
- .trim();
76
258
  try {
77
259
  if (callback === null || callback === void 0 ? void 0 : callback.parameters) {
78
260
  validateAndNormalizeCallbackOptions(callback.parameters, options);
@@ -89,7 +271,7 @@ export default class ResponseParserV2 {
89
271
  }
90
272
  callbackResults = callbackResults.trim();
91
273
  return {
92
- callbackResults,
274
+ callbackResults: callbackResults.length > 0 ? callbackResults : undefined,
93
275
  message: callbackStrippedMessage.length > 0
94
276
  ? callbackStrippedMessage
95
277
  : null,
@@ -97,35 +279,45 @@ export default class ResponseParserV2 {
97
279
  });
98
280
  }
99
281
  renderCallbackResults(callbackOptions) {
100
- return `@results ${JSON.stringify(callbackOptions)}\n`;
282
+ const { name, results, error } = callbackOptions;
283
+ if (error !== undefined) {
284
+ return `@results ${JSON.stringify({
285
+ name,
286
+ error: serializeCallbackError(error),
287
+ })}\n`;
288
+ }
289
+ // Multiline string results: header only + raw body (real newlines).
290
+ // Trailing newline separates blocks when multiple callbacks run.
291
+ if (typeof results === 'string') {
292
+ return `@results ${JSON.stringify({ name })}\n${results}\n`;
293
+ }
294
+ // undefined results: omit results key (historic wire shape).
295
+ if (results === undefined) {
296
+ return `@results ${JSON.stringify({ name })}\n`;
297
+ }
298
+ return `@results ${JSON.stringify({ name, results })}\n`;
101
299
  }
102
300
  getStateUpdateInstructions() {
103
301
  return `Updating state works similar to all function calls. Use the following syntax:
104
302
  @updateState({ "field1": "value1", "field2": "value2" })
105
- Make sure to json encode only the fields you want to change. You can update state once and do it at the end of any messages you send. IMPORTANT: JSON must be on a single line. Do NOT use multi-line or formatted JSON.
303
+ Make sure to json encode only the fields you want to change. You can update state once and do it at the end of any messages you send. Multi-line JSON objects are accepted (pretty-printed objects are parsed). IMPORTANT: Prefer a single JSON object argument; do not omit braces.
106
304
  Your user-facing message is always sent to the user, even if @updateState fails. If @updateState fails later, do not repeat the same message. Only send the specific @updateState needed to fix the missing state change.
107
305
  Good example:
108
306
  @updateState({ "favoriteColor": "blue", "firstName": "Taylor" })
109
307
  Bad examples:
110
308
  @updateState
111
309
  { "favoriteColor": "blue" }
112
- @updateState({
113
- "favoriteColor": "blue"
114
- })
115
310
  @updateState({ favoriteColor: "blue" })`;
116
311
  }
117
312
  getFunctionCallInstructions() {
118
313
  return `A function call is done using the following syntax:
119
314
  @callbackName({ "key": "value" })
120
- Make sure to json encode the options. You can call as many callbacks as you want in a single response by including multiple @functionName() lines. IMPORTANT: JSON must be on a single line. Do NOT use multi-line or formatted JSON.
315
+ Make sure to json encode the options. You can call as many callbacks as you want in a single response by including multiple @functionName() lines. Multi-line JSON arguments are accepted (pretty-printed objects are parsed). Failed callbacks always return a serializable error string in @results — never silence failures and never return an empty error object.
121
316
  Your user-facing message is always sent to the user, even if a callback fails. Successful callbacks have already run successfully. If a callback fails later, do not repeat the same message and do not repeat successful callbacks. Only call the specific callback needed to fix the failed gap.
122
317
  Good example:
123
318
  @lookupWeather({ "zip": "80524" })
124
319
  Bad examples:
125
320
  @lookupWeather { "zip": "80524" }
126
- @lookupWeather(
127
- { "zip": "80524" }
128
- )
129
321
  @lookupWeather({ zip: "80524" })`;
130
322
  }
131
323
  }
@@ -1,4 +1,10 @@
1
1
  import { ResponseParser, LlmCallbackMap, ParsedResponse } from '../llm.types';
2
+ /**
3
+ * Always produce a serializable error representation for @results.
4
+ * Plain Error becomes its message (never {}). Schema-like errors with enumerable
5
+ * payload fall back to message string for a stable wire format.
6
+ */
7
+ export declare function serializeCallbackError(err: unknown): string;
2
8
  export default class ResponseParserV2 implements ResponseParser {
3
9
  parse(response: string, callbacks?: LlmCallbackMap): Promise<ParsedResponse>;
4
10
  private invokeCallbacks;
@@ -3,15 +3,188 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.serializeCallbackError = serializeCallbackError;
6
7
  const templates_1 = require("../bots/templates");
7
8
  const validateAndNormalizeCallbackOptions_1 = __importDefault(require("./validateAndNormalizeCallbackOptions"));
9
+ const RESERVED_CALLBACK_NAMES = new Set(['updateState', 'results']);
10
+ /**
11
+ * Always produce a serializable error representation for @results.
12
+ * Plain Error becomes its message (never {}). Schema-like errors with enumerable
13
+ * payload fall back to message string for a stable wire format.
14
+ */
15
+ function serializeCallbackError(err) {
16
+ if (err instanceof Error) {
17
+ return err.message && err.message.length > 0 ? err.message : 'Error';
18
+ }
19
+ if (typeof err === 'string') {
20
+ return err;
21
+ }
22
+ if (err === null) {
23
+ return 'null';
24
+ }
25
+ if (err === undefined) {
26
+ return 'undefined';
27
+ }
28
+ if (typeof err === 'symbol') {
29
+ return String(err);
30
+ }
31
+ try {
32
+ const json = JSON.stringify(err);
33
+ if (json && json !== '{}') {
34
+ return json;
35
+ }
36
+ }
37
+ catch {
38
+ // fall through
39
+ }
40
+ try {
41
+ return String(err);
42
+ }
43
+ catch {
44
+ return 'Unknown error';
45
+ }
46
+ }
47
+ /** Find matching closing brace for a JSON object starting at openIdx ('{'). */
48
+ function findMatchingBrace(text, openIdx) {
49
+ let depth = 0;
50
+ let inString = false;
51
+ let escape = false;
52
+ for (let i = openIdx; i < text.length; i++) {
53
+ const ch = text[i];
54
+ if (inString) {
55
+ if (escape) {
56
+ escape = false;
57
+ continue;
58
+ }
59
+ if (ch === '\\') {
60
+ escape = true;
61
+ continue;
62
+ }
63
+ if (ch === '"') {
64
+ inString = false;
65
+ }
66
+ continue;
67
+ }
68
+ if (ch === '"') {
69
+ inString = true;
70
+ continue;
71
+ }
72
+ if (ch === '{') {
73
+ depth++;
74
+ }
75
+ else if (ch === '}') {
76
+ depth--;
77
+ if (depth === 0) {
78
+ return i;
79
+ }
80
+ }
81
+ }
82
+ return -1;
83
+ }
84
+ /**
85
+ * Extract @name(...) call spans. Supports multi-line JSON objects via brace-depth
86
+ * scanning and optional leading/trailing whitespace on the call line.
87
+ */
88
+ function extractCallbackSpans(message) {
89
+ const spans = [];
90
+ const re = /^[ \t]*@([A-Za-z_]\w*)\(/gm;
91
+ let m;
92
+ while ((m = re.exec(message)) !== null) {
93
+ const name = m[1];
94
+ const start = m.index;
95
+ const openParen = m.index + m[0].length - 1;
96
+ let i = openParen + 1;
97
+ while (i < message.length && /[ \t\r\n]/.test(message[i])) {
98
+ i++;
99
+ }
100
+ let argsJson;
101
+ let closeParen;
102
+ if (i < message.length && message[i] === ')') {
103
+ closeParen = i;
104
+ argsJson = undefined;
105
+ }
106
+ else if (i < message.length && message[i] === '{') {
107
+ const braceEnd = findMatchingBrace(message, i);
108
+ if (braceEnd < 0) {
109
+ spans.push({
110
+ name,
111
+ fullMatch: message.slice(start),
112
+ start,
113
+ end: message.length,
114
+ });
115
+ break;
116
+ }
117
+ argsJson = message.slice(i, braceEnd + 1);
118
+ let j = braceEnd + 1;
119
+ while (j < message.length && /[ \t\r\n]/.test(message[j])) {
120
+ j++;
121
+ }
122
+ if (j >= message.length || message[j] !== ')') {
123
+ spans.push({
124
+ name,
125
+ fullMatch: message.slice(start, braceEnd + 1),
126
+ argsJson,
127
+ start,
128
+ end: braceEnd + 1,
129
+ });
130
+ re.lastIndex = braceEnd + 1;
131
+ continue;
132
+ }
133
+ closeParen = j;
134
+ }
135
+ else {
136
+ re.lastIndex = openParen + 1;
137
+ continue;
138
+ }
139
+ let end = closeParen + 1;
140
+ // Include trailing newline after the call when present (common LLM layout)
141
+ if (message[end] === '\r') {
142
+ end++;
143
+ }
144
+ if (message[end] === '\n') {
145
+ end++;
146
+ }
147
+ // Leading newline is part of fullMatch when start points after prior content;
148
+ // also absorb a single leading newline just before the match for strip parity
149
+ // with historic `\n@name(...)\n` fixtures.
150
+ let matchStart = start;
151
+ if (matchStart > 0 && message[matchStart - 1] === '\n') {
152
+ matchStart--;
153
+ if (matchStart > 0 && message[matchStart - 1] === '\r') {
154
+ matchStart--;
155
+ }
156
+ }
157
+ spans.push({
158
+ name,
159
+ fullMatch: message.slice(matchStart, end),
160
+ argsJson,
161
+ start: matchStart,
162
+ end,
163
+ });
164
+ re.lastIndex = end;
165
+ }
166
+ return spans;
167
+ }
168
+ function stripCallFromMessage(message, fullMatch) {
169
+ // Historic behavior: remove the call span and glue surrounding text without
170
+ // injecting separators (trim each side after split).
171
+ return message
172
+ .split(fullMatch)
173
+ .map((s) => s.trim())
174
+ .join('')
175
+ .trim();
176
+ }
8
177
  class ResponseParserV2 {
9
178
  async parse(response, callbacks) {
10
179
  let message = response.replace(templates_1.DONE_TOKEN, '').trim();
11
180
  let state = undefined;
12
181
  let callbackResults = undefined;
13
- const hasCallbacks = callbacks != null &&
182
+ // Enter the pass when any @name( call shape is present (incl. typos)
183
+ // or when a registered callback name appears as a call prefix.
184
+ const hasCallShape = message != null && /^[ \t]*@\w+\(/m.test(message);
185
+ const hasRegisteredName = callbacks != null &&
14
186
  Object.keys(callbacks).some((name) => message.includes(`@${name}(`));
187
+ const hasCallbacks = hasCallShape || hasRegisteredName;
15
188
  if (hasCallbacks) {
16
189
  const { callbackResults: c, message: m } = await this.invokeCallbacks(message, callbacks);
17
190
  callbackResults = c;
@@ -48,24 +221,33 @@ class ResponseParserV2 {
48
221
  async invokeCallbacks(message, callbacks) {
49
222
  let callbackStrippedMessage = message;
50
223
  let callbackResults = '';
51
- const reserved = new Set(['updateState', 'results']);
52
- const matches = [...message.matchAll(/^@(\w+)\(({.*})?\)$/gm)];
53
- for (const match of matches) {
54
- if (reserved.has(match[1])) {
224
+ const spans = extractCallbackSpans(message);
225
+ for (const span of spans) {
226
+ if (RESERVED_CALLBACK_NAMES.has(span.name)) {
227
+ continue;
228
+ }
229
+ const name = span.name;
230
+ // Always strip the call from the user-facing message once recognized.
231
+ callbackStrippedMessage = stripCallFromMessage(callbackStrippedMessage, span.fullMatch.trim());
232
+ let options;
233
+ try {
234
+ options = span.argsJson ? JSON.parse(span.argsJson) : undefined;
235
+ }
236
+ catch (err) {
237
+ callbackResults += this.renderCallbackResults({
238
+ name,
239
+ error: `Invalid JSON arguments for @${name}: ${err?.message ?? String(err)}. Arguments must be valid JSON object.`,
240
+ });
55
241
  continue;
56
242
  }
57
- const parsed = match[2] ? JSON.parse(match[2]) : undefined;
58
- const name = match[1];
59
- const options = parsed;
60
243
  const callback = callbacks?.[name];
61
244
  if (!callback) {
245
+ callbackResults += this.renderCallbackResults({
246
+ name,
247
+ error: `Unknown callback @${name} — not registered.`,
248
+ });
62
249
  continue;
63
250
  }
64
- const parts = callbackStrippedMessage.split(match[0]);
65
- callbackStrippedMessage = parts
66
- .map((s) => s.trim())
67
- .join('')
68
- .trim();
69
251
  try {
70
252
  if (callback?.parameters) {
71
253
  (0, validateAndNormalizeCallbackOptions_1.default)(callback.parameters, options);
@@ -82,42 +264,52 @@ class ResponseParserV2 {
82
264
  }
83
265
  callbackResults = callbackResults.trim();
84
266
  return {
85
- callbackResults,
267
+ callbackResults: callbackResults.length > 0 ? callbackResults : undefined,
86
268
  message: callbackStrippedMessage.length > 0
87
269
  ? callbackStrippedMessage
88
270
  : null,
89
271
  };
90
272
  }
91
273
  renderCallbackResults(callbackOptions) {
92
- return `@results ${JSON.stringify(callbackOptions)}\n`;
274
+ const { name, results, error } = callbackOptions;
275
+ if (error !== undefined) {
276
+ return `@results ${JSON.stringify({
277
+ name,
278
+ error: serializeCallbackError(error),
279
+ })}\n`;
280
+ }
281
+ // Multiline string results: header only + raw body (real newlines).
282
+ // Trailing newline separates blocks when multiple callbacks run.
283
+ if (typeof results === 'string') {
284
+ return `@results ${JSON.stringify({ name })}\n${results}\n`;
285
+ }
286
+ // undefined results: omit results key (historic wire shape).
287
+ if (results === undefined) {
288
+ return `@results ${JSON.stringify({ name })}\n`;
289
+ }
290
+ return `@results ${JSON.stringify({ name, results })}\n`;
93
291
  }
94
292
  getStateUpdateInstructions() {
95
293
  return `Updating state works similar to all function calls. Use the following syntax:
96
294
  @updateState({ "field1": "value1", "field2": "value2" })
97
- Make sure to json encode only the fields you want to change. You can update state once and do it at the end of any messages you send. IMPORTANT: JSON must be on a single line. Do NOT use multi-line or formatted JSON.
295
+ Make sure to json encode only the fields you want to change. You can update state once and do it at the end of any messages you send. Multi-line JSON objects are accepted (pretty-printed objects are parsed). IMPORTANT: Prefer a single JSON object argument; do not omit braces.
98
296
  Your user-facing message is always sent to the user, even if @updateState fails. If @updateState fails later, do not repeat the same message. Only send the specific @updateState needed to fix the missing state change.
99
297
  Good example:
100
298
  @updateState({ "favoriteColor": "blue", "firstName": "Taylor" })
101
299
  Bad examples:
102
300
  @updateState
103
301
  { "favoriteColor": "blue" }
104
- @updateState({
105
- "favoriteColor": "blue"
106
- })
107
302
  @updateState({ favoriteColor: "blue" })`;
108
303
  }
109
304
  getFunctionCallInstructions() {
110
305
  return `A function call is done using the following syntax:
111
306
  @callbackName({ "key": "value" })
112
- Make sure to json encode the options. You can call as many callbacks as you want in a single response by including multiple @functionName() lines. IMPORTANT: JSON must be on a single line. Do NOT use multi-line or formatted JSON.
307
+ Make sure to json encode the options. You can call as many callbacks as you want in a single response by including multiple @functionName() lines. Multi-line JSON arguments are accepted (pretty-printed objects are parsed). Failed callbacks always return a serializable error string in @results — never silence failures and never return an empty error object.
113
308
  Your user-facing message is always sent to the user, even if a callback fails. Successful callbacks have already run successfully. If a callback fails later, do not repeat the same message and do not repeat successful callbacks. Only call the specific callback needed to fix the failed gap.
114
309
  Good example:
115
310
  @lookupWeather({ "zip": "80524" })
116
311
  Bad examples:
117
312
  @lookupWeather { "zip": "80524" }
118
- @lookupWeather(
119
- { "zip": "80524" }
120
- )
121
313
  @lookupWeather({ zip: "80524" })`;
122
314
  }
123
315
  }
package/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "eta"
9
9
  ]
10
10
  },
11
- "version": "18.1.0",
11
+ "version": "18.1.2",
12
12
  "files": [
13
13
  "build"
14
14
  ],
@@ -26,7 +26,8 @@
26
26
  "types": "./build/index.d.ts",
27
27
  "default": "./build/index.js"
28
28
  }
29
- }
29
+ },
30
+ "./package.json": "./package.json"
30
31
  },
31
32
  "keywords": [
32
33
  "llm",