@ganju/utils 0.0.21 → 0.0.22
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/dist/data.d.ts +4 -4
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +16 -2
- package/dist/dataApi.d.ts +32 -2
- package/dist/dataApi.d.ts.map +1 -1
- package/dist/dataApi.js +11 -1
- package/dist/dataCompiler.d.ts +1 -0
- package/dist/dataCompiler.d.ts.map +1 -1
- package/dist/dataCompiler.js +3 -2
- package/dist/dataEngine.d.ts +17 -1
- package/dist/dataEngine.d.ts.map +1 -1
- package/dist/dataEngine.js +368 -10
- package/dist/dataImport.d.ts +3 -0
- package/dist/dataImport.d.ts.map +1 -1
- package/dist/dataImport.js +12 -2
- package/dist/dataRpc.d.ts +9 -1
- package/dist/dataRpc.d.ts.map +1 -1
- package/dist/dataSheet.d.ts +34 -0
- package/dist/dataSheet.d.ts.map +1 -0
- package/dist/dataSheet.js +100 -0
- package/dist/dataTriggers.d.ts +43 -4
- package/dist/dataTriggers.d.ts.map +1 -1
- package/dist/dataTriggers.js +401 -19
- package/dist/dataTypes.d.ts +23 -0
- package/dist/dataTypes.d.ts.map +1 -1
- package/dist/dataTypes.js +15 -1
- package/dist/index.d.ts +35 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/localizeDataError.d.ts.map +1 -1
- package/dist/localizeDataError.js +45 -1
- package/dist/schema.d.ts +33 -4
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +26 -3
- package/dist/untrustedContent.d.ts +7 -0
- package/dist/untrustedContent.d.ts.map +1 -1
- package/dist/untrustedContent.js +13 -1
- package/package.json +1 -1
- package/src/data.ts +19 -3
- package/src/dataApi.ts +14 -2
- package/src/dataCompiler.ts +4 -1
- package/src/dataEngine.ts +485 -10
- package/src/dataImport.ts +16 -1
- package/src/dataRpc.ts +10 -1
- package/src/dataSheet.ts +147 -0
- package/src/dataTriggers.ts +528 -19
- package/src/dataTypes.ts +46 -0
- package/src/index.ts +4 -0
- package/src/localizeDataError.ts +82 -1
- package/src/schema.ts +27 -3
- package/src/untrustedContent.ts +15 -0
package/src/dataTypes.ts
CHANGED
|
@@ -65,9 +65,43 @@ export interface DataTableInput {
|
|
|
65
65
|
access?: DataAccess;
|
|
66
66
|
customerScoped?: boolean;
|
|
67
67
|
history?: boolean;
|
|
68
|
+
source?: DataTableSource | null;
|
|
68
69
|
columns: DataColumnInput[];
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
// A table kept in step with a spreadsheet or CSV in the project's knowledge:
|
|
73
|
+
// `key` is the unique column that names a row in both, and the sheet wins.
|
|
74
|
+
// `title` is the file's name when the source was saved, for messages.
|
|
75
|
+
export interface DataTableSource {
|
|
76
|
+
uri: string;
|
|
77
|
+
sheet?: string | null;
|
|
78
|
+
key: string;
|
|
79
|
+
title?: string | null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// How the last sync went. A refused one changed nothing.
|
|
83
|
+
export interface DataTableSync {
|
|
84
|
+
at: string;
|
|
85
|
+
status: 'ok' | 'failed';
|
|
86
|
+
inserted: number;
|
|
87
|
+
updated: number;
|
|
88
|
+
deleted: number;
|
|
89
|
+
unchanged: number;
|
|
90
|
+
error: string | null;
|
|
91
|
+
// The version of the file (its stored time) the rows were last made from.
|
|
92
|
+
// A sync of an older one is skipped, so two that overlap can't leave the
|
|
93
|
+
// older sheet's rows.
|
|
94
|
+
version?: string | null;
|
|
95
|
+
// Only in a sync's answer: skipped, an older file than the rows already are.
|
|
96
|
+
stale?: boolean;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Who a sync's changes are made by, in row history and trigger events.
|
|
100
|
+
export const DATA_SHEET_SYNC_ACTOR = 'system:sheet-sync';
|
|
101
|
+
// A source file is read whole, so it stays small.
|
|
102
|
+
export const DATA_SHEET_MAX_BYTES = 5 * 1024 * 1024;
|
|
103
|
+
export const DATA_SHEET_MAX_ROWS = 20_000;
|
|
104
|
+
|
|
71
105
|
// A column as the database object stores it: the input plus its stable id,
|
|
72
106
|
// which names its indexes and constraints so a rename never orphans them.
|
|
73
107
|
export interface DataColumn {
|
|
@@ -100,6 +134,9 @@ export interface DataTable {
|
|
|
100
134
|
history: boolean;
|
|
101
135
|
position: number;
|
|
102
136
|
columns: DataColumn[];
|
|
137
|
+
// Set while the table is synced from a sheet: its rows are then read-only.
|
|
138
|
+
source?: DataTableSource | null;
|
|
139
|
+
sync?: DataTableSync | null;
|
|
103
140
|
}
|
|
104
141
|
|
|
105
142
|
export interface DataSchema {
|
|
@@ -364,6 +401,15 @@ export const toTableInput = (table: DataTable): DataTableInput => ({
|
|
|
364
401
|
access: table.access,
|
|
365
402
|
customerScoped: table.customerScoped,
|
|
366
403
|
history: table.history,
|
|
404
|
+
...(table.source
|
|
405
|
+
? {
|
|
406
|
+
source: {
|
|
407
|
+
uri: table.source.uri,
|
|
408
|
+
...(table.source.sheet ? { sheet: table.source.sheet } : {}),
|
|
409
|
+
key: table.source.key
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
: {}),
|
|
367
413
|
columns: table.columns.map(column => {
|
|
368
414
|
const out: DataColumnInput = { name: column.name, type: column.type };
|
|
369
415
|
if (column.label) out.label = column.label;
|
package/src/index.ts
CHANGED
|
@@ -272,9 +272,11 @@ import type {
|
|
|
272
272
|
import {
|
|
273
273
|
wrapUntrustedContent,
|
|
274
274
|
unwrapUntrustedContent,
|
|
275
|
+
stripUntrustedMarks,
|
|
275
276
|
isUntrustedToolResult,
|
|
276
277
|
findInstructionLikeText,
|
|
277
278
|
UNTRUSTED_CONTENT_TAG,
|
|
279
|
+
UNTRUSTED_CONTENT_MARK_PATTERN,
|
|
278
280
|
UNTRUSTED_CONTENT_META_KEY,
|
|
279
281
|
UNTRUSTED_CONTENT_NOTE,
|
|
280
282
|
INSTRUCTION_WARNING_METADATA_KEY,
|
|
@@ -576,9 +578,11 @@ export const utils = {
|
|
|
576
578
|
stableJson,
|
|
577
579
|
wrapUntrustedContent,
|
|
578
580
|
unwrapUntrustedContent,
|
|
581
|
+
stripUntrustedMarks,
|
|
579
582
|
isUntrustedToolResult,
|
|
580
583
|
findInstructionLikeText,
|
|
581
584
|
UNTRUSTED_CONTENT_TAG,
|
|
585
|
+
UNTRUSTED_CONTENT_MARK_PATTERN,
|
|
582
586
|
UNTRUSTED_CONTENT_META_KEY,
|
|
583
587
|
UNTRUSTED_CONTENT_NOTE,
|
|
584
588
|
INSTRUCTION_WARNING_METADATA_KEY,
|
package/src/localizeDataError.ts
CHANGED
|
@@ -424,7 +424,24 @@ export const DATA_ERROR_MESSAGES_ES: Record<string, string | Render> = {
|
|
|
424
424
|
'A trigger is an object.': 'Un disparador es un objeto.',
|
|
425
425
|
'There is no table named {0}.': 'No hay ninguna tabla llamada {0}.',
|
|
426
426
|
'A trigger needs "table".': 'Un disparador necesita "table".',
|
|
427
|
-
'"on" is insert, update or
|
|
427
|
+
'"on" is insert, update, delete or time.':
|
|
428
|
+
'"on" es insert, update, delete o time.',
|
|
429
|
+
// Time triggers.
|
|
430
|
+
'"after" is for "on": "time" only.': '"after" es solo para "on": "time".',
|
|
431
|
+
'A time trigger needs "after": { "column", "minutes" }, such as { "column": "created_at", "minutes": 30 }.':
|
|
432
|
+
'Un disparador por tiempo necesita "after": { "column", "minutes" }, por ejemplo { "column": "created_at", "minutes": 30 }.',
|
|
433
|
+
'"after.column" is created_at, updated_at or a date or datetime column of {0}.':
|
|
434
|
+
'"after.column" es created_at, updated_at o una columna de fecha o de fecha y hora de {0}.',
|
|
435
|
+
'"after.minutes" is a whole number of minutes from {0} to {1} (30 days), negative for before the column\'s time.':
|
|
436
|
+
'"after.minutes" es un número entero de minutos de {0} a {1} (30 días), negativo para antes de la hora de la columna.',
|
|
437
|
+
'"after.minutes" can\'t be negative on {0}: that time has already passed when the row is written. Use a date or datetime column for a reminder before.':
|
|
438
|
+
'"after.minutes" no puede ser negativo en {0}: esa hora ya pasó cuando se escribe la fila. Usa una columna de fecha o de fecha y hora para un recordatorio antes.',
|
|
439
|
+
'"when".{0}.{1}: a time trigger reads the row as it is when it comes due, so its tests are "equals" only.':
|
|
440
|
+
'"when".{0}.{1}: un disparador por tiempo lee la fila tal como está cuando se cumple el plazo, así que sus pruebas son solo "equals".',
|
|
441
|
+
'A database holds up to {0} time triggers.':
|
|
442
|
+
'Una base de datos tiene hasta {0} disparadores por tiempo.',
|
|
443
|
+
'{0}.{1} stays a date or datetime while time triggers count from it ({2}); change or delete them first.':
|
|
444
|
+
'{0}.{1} debe seguir siendo de fecha o de fecha y hora mientras haya disparadores por tiempo que cuenten desde ella ({2}); cámbialos o elimínalos primero.',
|
|
428
445
|
"A trigger's name is lowercase letters, digits, _ and -, starting with a letter.":
|
|
429
446
|
'El nombre de un disparador usa minúsculas, dígitos, _ y -, empezando por una letra.',
|
|
430
447
|
'"rateLimit" is 1 to {0} runs a minute.':
|
|
@@ -556,6 +573,8 @@ export const DATA_ERROR_MESSAGES_ES: Record<string, string | Render> = {
|
|
|
556
573
|
'{0} no es uno de tus chats vinculados con los bots de este proyecto.',
|
|
557
574
|
'Several of your chats are linked to this project\'s bots; pick one with "participantId".':
|
|
558
575
|
'Varios de tus chats están vinculados con los bots de este proyecto; elige uno con "participantId".',
|
|
576
|
+
"This organization's plan no longer includes Data, so its triggers are paused and can't be tested. Upgrade to Pro to send a test.":
|
|
577
|
+
'El plan de esta organización ya no incluye Datos, así que sus disparadores están en pausa y no se pueden probar. Pasa a Pro para enviar una prueba.',
|
|
559
578
|
'{0} has no rows yet; add one to build the test from.':
|
|
560
579
|
'{0} aún no tiene filas; agrega una para armar la prueba.',
|
|
561
580
|
'Reading a row as its trigger event is for the project owner.':
|
|
@@ -612,6 +631,68 @@ export const DATA_ERROR_MESSAGES_ES: Record<string, string | Render> = {
|
|
|
612
631
|
'La plantilla de WhatsApp {0} tiene {1} variables; se dio {2} valor.',
|
|
613
632
|
'The WhatsApp template {0} has {1} variables; {2} values were given.':
|
|
614
633
|
'La plantilla de WhatsApp {0} tiene {1} variables; se dieron {2} valores.',
|
|
634
|
+
// Tables synced from a sheet
|
|
635
|
+
'{0} is synced from "{1}"; change the sheet, or stop syncing it.':
|
|
636
|
+
'{0} se sincroniza desde "{1}"; cambia la hoja, o deja de sincronizarla.',
|
|
637
|
+
'"source" is { "uri", "sheet"?, "key" }, or null to stop syncing.':
|
|
638
|
+
'"source" es { "uri", "sheet"?, "key" }, o null para dejar de sincronizar.',
|
|
639
|
+
'"source" takes uri, sheet and key, not "{0}".':
|
|
640
|
+
'"source" recibe uri, sheet y key, no "{0}".',
|
|
641
|
+
'"source.uri" is the uri of a spreadsheet or CSV in the project\'s knowledge.':
|
|
642
|
+
'"source.uri" es el uri de una hoja de cálculo o un CSV en el conocimiento del proyecto.',
|
|
643
|
+
'"source.sheet" is the name of a sheet in the file, up to 100 characters.':
|
|
644
|
+
'"source.sheet" es el nombre de una hoja del archivo, de hasta 100 caracteres.',
|
|
645
|
+
'"source.key" is the column that names a row in both the sheet and {0}. Columns: {1}.':
|
|
646
|
+
'"source.key" es la columna que identifica una fila tanto en la hoja como en {0}. Columnas: {1}.',
|
|
647
|
+
'{0}.{1} must be unique to be the sync key.':
|
|
648
|
+
'{0}.{1} debe ser única para ser la clave de la sincronización.',
|
|
649
|
+
'{0}.{1} is the key the sheet sync matches rows by, so it stays unique.':
|
|
650
|
+
'{0}.{1} es la clave con la que la sincronización empareja las filas, así que sigue siendo única.',
|
|
651
|
+
"{0}.{1} is the sheet sync's key: rename it on its own, then make the other changes.":
|
|
652
|
+
'{0}.{1} es la clave de la sincronización: cámbiale el nombre por separado, y luego haz los demás cambios.',
|
|
653
|
+
'{0}.{1} is the key the sheet sync matches rows by; stop syncing the table, or pick another key, first.':
|
|
654
|
+
'{0}.{1} es la clave con la que la sincronización empareja las filas; primero deja de sincronizar la tabla, o elige otra clave.',
|
|
655
|
+
"{0} isn't synced from a sheet.": '{0} no se sincroniza desde una hoja.',
|
|
656
|
+
'A sync takes "columns" and "rows".':
|
|
657
|
+
'Una sincronización recibe "columns" y "rows".',
|
|
658
|
+
"The sheet has no column for {0}'s key, {1}.":
|
|
659
|
+
'La hoja no tiene una columna para la clave de {0}, {1}.',
|
|
660
|
+
'The sheet has no column for {0}, which is required.':
|
|
661
|
+
'La hoja no tiene una columna para {0}, que es obligatoria.',
|
|
662
|
+
'The sheet has no column for {0}, which are required.':
|
|
663
|
+
'La hoja no tiene columnas para {0}, que son obligatorias.',
|
|
664
|
+
'The sheet has {0} rows; a synced table takes up to {1}.':
|
|
665
|
+
'La hoja tiene {0} filas; una tabla sincronizada recibe hasta {1}.',
|
|
666
|
+
'Line {0}: {1}': (a, t) => `Línea ${a[0]}: ${t(a[1])}`,
|
|
667
|
+
'A row is an object of column: value.':
|
|
668
|
+
'Una fila es un objeto de columna: valor.',
|
|
669
|
+
'The key, {0}, is empty.': 'La clave, {0}, está vacía.',
|
|
670
|
+
'{0} {1} is already on line {2}; each line of the sheet needs its own key.':
|
|
671
|
+
'{0} {1} ya está en la línea {2}; cada línea de la hoja necesita su propia clave.',
|
|
672
|
+
'Rows in {0} still point at a row the sheet no longer has ({1}.{2}); put them back in the sheet, or change those rows first.':
|
|
673
|
+
'Filas de {0} todavía apuntan a una fila que la hoja ya no tiene ({1}.{2}); vuelve a ponerla en la hoja, o cambia esas filas primero.',
|
|
674
|
+
'Rows in {0} still point at {1} rows the sheet no longer has ({2}.{3}); put them back in the sheet, or change those rows first.':
|
|
675
|
+
'Filas de {0} todavía apuntan a {1} filas que la hoja ya no tiene ({2}.{3}); vuelve a ponerlas en la hoja, o cambia esas filas primero.',
|
|
676
|
+
"There is no file {0} in the project's knowledge.":
|
|
677
|
+
'No hay ningún archivo {0} en el conocimiento del proyecto.',
|
|
678
|
+
'{0} is {1}; a table syncs from a spreadsheet or CSV.':
|
|
679
|
+
'{0} es {1}; una tabla se sincroniza desde una hoja de cálculo o un CSV.',
|
|
680
|
+
"{0} is no longer in the project's knowledge; pick the file again, or stop syncing {1}.":
|
|
681
|
+
'{0} ya no está en el conocimiento del proyecto; vuelve a elegir el archivo, o deja de sincronizar {1}.',
|
|
682
|
+
'{0} is over {1} MB; a synced table reads files up to that size.':
|
|
683
|
+
'{0} pesa más de {1} MB; una tabla sincronizada lee archivos de hasta ese tamaño.',
|
|
684
|
+
'The sheet could not be read; try "Sync now" again in a few minutes.':
|
|
685
|
+
'No se pudo leer la hoja; vuelve a intentar "Sincronizar ahora" en unos minutos.',
|
|
686
|
+
"{0} can't be both customer-scoped and synced from a sheet: synced rows have no customer, so the bots wouldn't see them.":
|
|
687
|
+
'{0} no puede estar limitada al cliente y a la vez sincronizarse desde una hoja: las filas sincronizadas no tienen cliente, así que los bots no las verían.',
|
|
688
|
+
'Can\'t delete: it would change a row of {0} ({1}.{2}), which is synced from "{3}"; change the sheet first, or stop syncing it.':
|
|
689
|
+
'No se puede eliminar: cambiaría una fila de {0} ({1}.{2}), que se sincroniza desde "{3}"; primero cambia la hoja, o deja de sincronizarla.',
|
|
690
|
+
'Can\'t delete: it would change {0} rows of {1} ({2}.{3}), which is synced from "{4}"; change the sheet first, or stop syncing it.':
|
|
691
|
+
'No se puede eliminar: cambiaría {0} filas de {1} ({2}.{3}), que se sincroniza desde "{4}"; primero cambia la hoja, o deja de sincronizarla.',
|
|
692
|
+
'{0} has no sheet "{1}". Sheets: {2}.': '{0} no tiene una hoja "{1}". Hojas: {2}.',
|
|
693
|
+
'{0} is over {1} MB.': '{0} pesa más de {1} MB.',
|
|
694
|
+
'{0} is {1}; a table reads CSV and Excel files.':
|
|
695
|
+
'{0} es {1}; una tabla lee archivos CSV y de Excel.',
|
|
615
696
|
'A post needs "chatId".': 'Una publicación necesita "chatId".',
|
|
616
697
|
'WhatsApp has no group chats for bots; message a customer with send().':
|
|
617
698
|
'WhatsApp no tiene chats de grupo para bots; escríbele a un cliente con send().'
|
package/src/schema.ts
CHANGED
|
@@ -543,7 +543,12 @@ const ARTIFACT_PICK_RESOURCE = z.object({
|
|
|
543
543
|
userId: z.uuid(),
|
|
544
544
|
organizationId: z.uuid(),
|
|
545
545
|
q: z.string().trim().max(200).optional(),
|
|
546
|
-
limit: z.coerce.number().int().min(1).max(50).default(20)
|
|
546
|
+
limit: z.coerce.number().int().min(1).max(50).default(20),
|
|
547
|
+
// Only spreadsheets and CSV, for a table's sheet source.
|
|
548
|
+
tabular: z
|
|
549
|
+
.enum(['1', 'true'])
|
|
550
|
+
.optional()
|
|
551
|
+
.transform(v => v !== undefined)
|
|
547
552
|
});
|
|
548
553
|
|
|
549
554
|
const ARTIFACT_REMOVE_RESOURCE = z.object({
|
|
@@ -602,6 +607,14 @@ const DATA_COLUMN_INPUT = z.strictObject({
|
|
|
602
607
|
});
|
|
603
608
|
|
|
604
609
|
// The CLI's schema file holds a list of these.
|
|
610
|
+
// A spreadsheet or CSV in the project's knowledge the table's rows come
|
|
611
|
+
// from, matched by `key`. Null stops syncing.
|
|
612
|
+
const DATA_TABLE_SOURCE = z.strictObject({
|
|
613
|
+
uri: z.string().min(1).max(2048),
|
|
614
|
+
sheet: z.string().trim().min(1).max(100).nullable().optional(),
|
|
615
|
+
key: DATA_IDENTIFIER
|
|
616
|
+
});
|
|
617
|
+
|
|
605
618
|
const DATA_TABLE_INPUT = z.strictObject({
|
|
606
619
|
name: DATA_IDENTIFIER,
|
|
607
620
|
label: DATA_TEXT(DATA_MAX_LABEL),
|
|
@@ -610,6 +623,7 @@ const DATA_TABLE_INPUT = z.strictObject({
|
|
|
610
623
|
access: z.enum(DATA_ACCESS_LEVELS).optional(),
|
|
611
624
|
customerScoped: z.boolean().optional(),
|
|
612
625
|
history: z.boolean().optional(),
|
|
626
|
+
source: DATA_TABLE_SOURCE.nullable().optional(),
|
|
613
627
|
columns: z.array(DATA_COLUMN_INPUT).max(DATA_MAX_COLUMNS)
|
|
614
628
|
});
|
|
615
629
|
|
|
@@ -815,9 +829,18 @@ const DATA_TRIGGER_INPUT = z.strictObject({
|
|
|
815
829
|
name: DATA_TRIGGER_NAME.optional(),
|
|
816
830
|
description: z.string().max(300).nullish(),
|
|
817
831
|
table: DATA_IDENTIFIER,
|
|
818
|
-
on: z.enum(['insert', 'update', 'delete']),
|
|
832
|
+
on: z.enum(['insert', 'update', 'delete', 'time']),
|
|
833
|
+
// With "time": fires this many minutes after the column's time (negative:
|
|
834
|
+
// before it). The column is created_at, updated_at or a date / datetime.
|
|
835
|
+
after: z
|
|
836
|
+
.strictObject({
|
|
837
|
+
column: DATA_IDENTIFIER,
|
|
838
|
+
minutes: z.number().int().min(-43_200).max(43_200)
|
|
839
|
+
})
|
|
840
|
+
.nullish(),
|
|
819
841
|
// { column: { changedTo | changedFrom | equals: value, changed: true } },
|
|
820
|
-
// combined with { all: [...] } or { any: [...] }.
|
|
842
|
+
// combined with { all: [...] } or { any: [...] }. A time trigger takes
|
|
843
|
+
// equals only.
|
|
821
844
|
when: z.record(z.string(), z.unknown()).nullish(),
|
|
822
845
|
enabled: z.boolean().optional(),
|
|
823
846
|
// false: refuse to save over an existing trigger of this name.
|
|
@@ -2201,6 +2224,7 @@ export const Schema = {
|
|
|
2201
2224
|
DATA_DATABASE_CREATE,
|
|
2202
2225
|
DATA_DATABASE_DELETE,
|
|
2203
2226
|
DATA_COLUMN_INPUT,
|
|
2227
|
+
DATA_TABLE_SOURCE,
|
|
2204
2228
|
DATA_TABLE_INPUT,
|
|
2205
2229
|
DATA_TABLE_PATCH,
|
|
2206
2230
|
DATA_COLUMN_PATCH,
|
package/src/untrustedContent.ts
CHANGED
|
@@ -121,6 +121,21 @@ export const unwrapUntrustedContent = (text: string): string => {
|
|
|
121
121
|
return match ? match[2] : text;
|
|
122
122
|
};
|
|
123
123
|
|
|
124
|
+
// Either line wrapUntrustedContent adds around a block, with the newline that
|
|
125
|
+
// joins it to the body. A `<` of ours inside a body was escaped, so every
|
|
126
|
+
// match is a real mark. A source string so SQL can strip the same marks.
|
|
127
|
+
export const UNTRUSTED_CONTENT_MARK_PATTERN =
|
|
128
|
+
`\\n?<${UNTRUSTED_CONTENT_TAG} id="[0-9a-f]+" source="[^"]*">\\n?` +
|
|
129
|
+
`|\\n?</${UNTRUSTED_CONTENT_TAG} id="[0-9a-f]+">`;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Text with the marks around every wrapped block removed and the bodies kept,
|
|
133
|
+
* for showing a recorded result to a person: an error that quotes an
|
|
134
|
+
* endpoint's answer reads as the message and the answer.
|
|
135
|
+
*/
|
|
136
|
+
export const stripUntrustedMarks = (text: string): string =>
|
|
137
|
+
text.replace(new RegExp(UNTRUSTED_CONTENT_MARK_PATTERN, 'g'), '\n').trim();
|
|
138
|
+
|
|
124
139
|
/** Whether a tool result carries the flag set by the server that ran it. */
|
|
125
140
|
export const isUntrustedToolResult = (result: unknown): boolean => {
|
|
126
141
|
if (!result || typeof result !== 'object') return false;
|