@mastra/memory 1.14.0 → 1.15.0-alpha.0
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/CHANGELOG.md +8 -0
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/index.cjs +132 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +132 -40
- package/dist/index.js.map +1 -1
- package/dist/tools/om-tools.d.ts +14 -2
- package/dist/tools/om-tools.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -15426,10 +15426,16 @@ var DefaultEmbedManyResult3 = class {
|
|
|
15426
15426
|
};
|
|
15427
15427
|
createIdGenerator3({ prefix: "aiobj", size: 24 });
|
|
15428
15428
|
createIdGenerator3({ prefix: "aiobj", size: 24 });
|
|
15429
|
+
function getMessageParts(msg) {
|
|
15430
|
+
if (typeof msg.content === "string") return [];
|
|
15431
|
+
if (Array.isArray(msg.content)) return msg.content;
|
|
15432
|
+
const parts = msg.content?.parts;
|
|
15433
|
+
return Array.isArray(parts) ? parts : [];
|
|
15434
|
+
}
|
|
15429
15435
|
function hasVisibleParts(msg) {
|
|
15430
15436
|
if (typeof msg.content === "string") return msg.content.length > 0;
|
|
15431
|
-
const parts = msg
|
|
15432
|
-
if (
|
|
15437
|
+
const parts = getMessageParts(msg);
|
|
15438
|
+
if (parts.length === 0) return Boolean(msg.content?.content);
|
|
15433
15439
|
return parts.some((p) => !p.type?.startsWith("data-"));
|
|
15434
15440
|
}
|
|
15435
15441
|
function parseRangeFormat(cursor) {
|
|
@@ -15690,13 +15696,13 @@ function lowDetailPartLimit(type) {
|
|
|
15690
15696
|
if (type === "tool-result" || type === "tool-call") return AUTO_EXPAND_TOOL_TOKENS;
|
|
15691
15697
|
return LOW_DETAIL_PART_TOKENS;
|
|
15692
15698
|
}
|
|
15693
|
-
function makePart(msg, partIndex, type, fullText, detail) {
|
|
15699
|
+
function makePart(msg, partIndex, type, fullText, detail, toolName) {
|
|
15694
15700
|
if (detail === "high") {
|
|
15695
|
-
return { messageId: msg.id, partIndex, role: msg.role, type, text: fullText, fullText };
|
|
15701
|
+
return { messageId: msg.id, partIndex, role: msg.role, type, text: fullText, fullText, toolName };
|
|
15696
15702
|
}
|
|
15697
15703
|
const hint = `recall cursor="${msg.id}" partIndex=${partIndex} detail="high"`;
|
|
15698
15704
|
const { text: text4 } = truncateByTokens(fullText, lowDetailPartLimit(type), hint);
|
|
15699
|
-
return { messageId: msg.id, partIndex, role: msg.role, type, text: text4, fullText };
|
|
15705
|
+
return { messageId: msg.id, partIndex, role: msg.role, type, text: text4, fullText, toolName };
|
|
15700
15706
|
}
|
|
15701
15707
|
function formatMessageParts(msg, detail) {
|
|
15702
15708
|
const parts = [];
|
|
@@ -15704,32 +15710,73 @@ function formatMessageParts(msg, detail) {
|
|
|
15704
15710
|
parts.push(makePart(msg, 0, "text", msg.content, detail));
|
|
15705
15711
|
return parts;
|
|
15706
15712
|
}
|
|
15707
|
-
|
|
15708
|
-
|
|
15709
|
-
|
|
15713
|
+
const messageParts = getMessageParts(msg);
|
|
15714
|
+
if (messageParts.length > 0) {
|
|
15715
|
+
for (let i = 0; i < messageParts.length; i++) {
|
|
15716
|
+
const part = messageParts[i];
|
|
15710
15717
|
const partType = part.type;
|
|
15711
15718
|
if (partType === "text") {
|
|
15712
15719
|
const text4 = part.text;
|
|
15713
|
-
|
|
15720
|
+
if (text4) {
|
|
15721
|
+
parts.push(makePart(msg, i, "text", text4, detail));
|
|
15722
|
+
}
|
|
15714
15723
|
} else if (partType === "tool-invocation") {
|
|
15715
15724
|
const inv = part.toolInvocation;
|
|
15716
|
-
if (inv
|
|
15717
|
-
const
|
|
15718
|
-
|
|
15719
|
-
|
|
15720
|
-
);
|
|
15721
|
-
const resultStr = formatToolResultForObserver(resultValue, { maxTokens: HIGH_DETAIL_TOOL_RESULT_TOKENS });
|
|
15722
|
-
const fullText = `[Tool Result: ${inv.toolName}]
|
|
15723
|
-
${resultStr}`;
|
|
15724
|
-
parts.push(makePart(msg, i, "tool-result", fullText, detail));
|
|
15725
|
-
} else {
|
|
15726
|
-
const argsStr = detail === "low" ? "" : `
|
|
15725
|
+
if (inv?.toolName) {
|
|
15726
|
+
const hasArgs = inv.args != null;
|
|
15727
|
+
if (inv.state !== "partial-call" && hasArgs) {
|
|
15728
|
+
const argsStr = detail === "low" ? "" : `
|
|
15727
15729
|
${JSON.stringify(inv.args, null, 2)}`;
|
|
15728
|
-
|
|
15729
|
-
|
|
15730
|
+
const fullText = `[Tool Call: ${inv.toolName}]${argsStr}`;
|
|
15731
|
+
parts.push({
|
|
15732
|
+
messageId: msg.id,
|
|
15733
|
+
partIndex: i,
|
|
15734
|
+
role: msg.role,
|
|
15735
|
+
type: "tool-call",
|
|
15736
|
+
text: fullText,
|
|
15737
|
+
fullText,
|
|
15738
|
+
toolName: inv.toolName
|
|
15739
|
+
});
|
|
15740
|
+
}
|
|
15741
|
+
if (inv.state === "result") {
|
|
15742
|
+
const { value: resultValue } = resolveToolResultValue(
|
|
15743
|
+
part,
|
|
15744
|
+
inv.result
|
|
15745
|
+
);
|
|
15746
|
+
const resultStr = formatToolResultForObserver(resultValue, { maxTokens: HIGH_DETAIL_TOOL_RESULT_TOKENS });
|
|
15747
|
+
const fullText = `[Tool Result: ${inv.toolName}]
|
|
15748
|
+
${resultStr}`;
|
|
15749
|
+
parts.push(makePart(msg, i, "tool-result", fullText, detail, inv.toolName));
|
|
15750
|
+
}
|
|
15751
|
+
}
|
|
15752
|
+
} else if (partType === "tool-call") {
|
|
15753
|
+
const toolName = part.toolName;
|
|
15754
|
+
if (toolName) {
|
|
15755
|
+
const rawArgs = part.input ?? part.args;
|
|
15756
|
+
const argsStr = detail === "low" || rawArgs == null ? "" : `
|
|
15757
|
+
${typeof rawArgs === "string" ? rawArgs : JSON.stringify(rawArgs, null, 2)}`;
|
|
15758
|
+
const fullText = `[Tool Call: ${toolName}]${argsStr}`;
|
|
15759
|
+
parts.push({
|
|
15760
|
+
messageId: msg.id,
|
|
15761
|
+
partIndex: i,
|
|
15762
|
+
role: msg.role,
|
|
15763
|
+
type: "tool-call",
|
|
15764
|
+
text: fullText,
|
|
15765
|
+
fullText,
|
|
15766
|
+
toolName
|
|
15767
|
+
});
|
|
15768
|
+
}
|
|
15769
|
+
} else if (partType === "tool-result") {
|
|
15770
|
+
const toolName = part.toolName;
|
|
15771
|
+
if (toolName) {
|
|
15772
|
+
const rawResult = part.output ?? part.result;
|
|
15773
|
+
const resultStr = formatToolResultForObserver(rawResult, { maxTokens: HIGH_DETAIL_TOOL_RESULT_TOKENS });
|
|
15774
|
+
const fullText = `[Tool Result: ${toolName}]
|
|
15775
|
+
${resultStr}`;
|
|
15776
|
+
parts.push(makePart(msg, i, "tool-result", fullText, detail, toolName));
|
|
15730
15777
|
}
|
|
15731
15778
|
} else if (partType === "reasoning") {
|
|
15732
|
-
const reasoning = part.reasoning;
|
|
15779
|
+
const reasoning = part.reasoning ?? part.text;
|
|
15733
15780
|
if (reasoning) {
|
|
15734
15781
|
parts.push(makePart(msg, i, "reasoning", reasoning, detail));
|
|
15735
15782
|
}
|
|
@@ -15867,7 +15914,7 @@ async function recallPart({
|
|
|
15867
15914
|
`Message ${cursor} has no visible content (it may be an internal system message). Try a neighboring message ID instead.`
|
|
15868
15915
|
);
|
|
15869
15916
|
}
|
|
15870
|
-
const target = allParts.find((p) => p.partIndex === partIndex);
|
|
15917
|
+
const target = [...allParts].reverse().find((p) => p.partIndex === partIndex);
|
|
15871
15918
|
if (!target) {
|
|
15872
15919
|
const availableIndices = allParts.map((p) => p.partIndex).join(", ");
|
|
15873
15920
|
const highestVisiblePartIndex = Math.max(...allParts.map((p) => p.partIndex));
|
|
@@ -15920,6 +15967,8 @@ async function recallMessages({
|
|
|
15920
15967
|
page = 1,
|
|
15921
15968
|
limit = 20,
|
|
15922
15969
|
detail = "low",
|
|
15970
|
+
partType,
|
|
15971
|
+
toolName,
|
|
15923
15972
|
threadScope,
|
|
15924
15973
|
maxTokens = DEFAULT_MAX_RESULT_TOKENS
|
|
15925
15974
|
}) {
|
|
@@ -16009,12 +16058,18 @@ async function recallMessages({
|
|
|
16009
16058
|
}
|
|
16010
16059
|
const hasNextPage = isForward ? hasMore : pageIndex > 0;
|
|
16011
16060
|
const hasPrevPage = isForward ? pageIndex > 0 : hasMore;
|
|
16012
|
-
|
|
16061
|
+
let allParts = [];
|
|
16013
16062
|
const timestamps = /* @__PURE__ */ new Map();
|
|
16014
16063
|
for (const msg of messages) {
|
|
16015
16064
|
timestamps.set(msg.id, msg.createdAt);
|
|
16016
16065
|
allParts.push(...formatMessageParts(msg, detail));
|
|
16017
16066
|
}
|
|
16067
|
+
if (toolName) {
|
|
16068
|
+
allParts = allParts.filter((p) => (p.type === "tool-call" || p.type === "tool-result") && p.toolName === toolName);
|
|
16069
|
+
}
|
|
16070
|
+
if (partType) {
|
|
16071
|
+
allParts = allParts.filter((p) => p.type === partType);
|
|
16072
|
+
}
|
|
16018
16073
|
if (detail === "high" && allParts.length > 0) {
|
|
16019
16074
|
const firstPart = allParts[0];
|
|
16020
16075
|
const sameMsgParts = allParts.filter((p) => p.messageId === firstPart.messageId);
|
|
@@ -16051,8 +16106,9 @@ High detail returns 1 part at a time. To continue: ${hints.join(", or ")}.`;
|
|
|
16051
16106
|
};
|
|
16052
16107
|
}
|
|
16053
16108
|
const rendered = renderFormattedParts(allParts, timestamps, { maxTokens });
|
|
16109
|
+
const emptyMessage = allParts.length === 0 ? partType || toolName ? "(no message parts matched the current filters)" : "(no visible message parts found for this page)" : "(no messages found)";
|
|
16054
16110
|
return {
|
|
16055
|
-
messages: rendered.text,
|
|
16111
|
+
messages: rendered.text || emptyMessage,
|
|
16056
16112
|
count: messages.length,
|
|
16057
16113
|
cursor,
|
|
16058
16114
|
page: normalizedPage,
|
|
@@ -16071,6 +16127,9 @@ async function recallThreadFromStart({
|
|
|
16071
16127
|
page = 1,
|
|
16072
16128
|
limit = 20,
|
|
16073
16129
|
detail = "low",
|
|
16130
|
+
partType,
|
|
16131
|
+
toolName,
|
|
16132
|
+
anchor = "start",
|
|
16074
16133
|
maxTokens = DEFAULT_MAX_RESULT_TOKENS
|
|
16075
16134
|
}) {
|
|
16076
16135
|
if (!memory) {
|
|
@@ -16096,29 +16155,37 @@ async function recallThreadFromStart({
|
|
|
16096
16155
|
resourceId,
|
|
16097
16156
|
page: 0,
|
|
16098
16157
|
perPage: fetchCount,
|
|
16099
|
-
orderBy: { field: "createdAt", direction: "ASC" }
|
|
16158
|
+
orderBy: { field: "createdAt", direction: anchor === "end" ? "DESC" : "ASC" }
|
|
16100
16159
|
});
|
|
16101
|
-
const visibleMessages = result.messages.filter(hasVisibleParts);
|
|
16102
|
-
const total = visibleMessages.length;
|
|
16160
|
+
const visibleMessages = anchor === "end" ? result.messages.slice(0, fetchCount).filter(hasVisibleParts).reverse() : result.messages.slice(0, fetchCount).filter(hasVisibleParts);
|
|
16103
16161
|
const skip = pageIndex * normalizedLimit;
|
|
16104
|
-
const hasMore = total > skip + normalizedLimit;
|
|
16105
16162
|
const messages = visibleMessages.slice(skip, skip + normalizedLimit);
|
|
16106
|
-
const
|
|
16163
|
+
const hasExtraMessage = visibleMessages.length > skip + messages.length;
|
|
16164
|
+
const hasNextPage = messages.length > 0 ? anchor === "end" ? pageIndex > 0 : hasExtraMessage : false;
|
|
16165
|
+
const hasPrevPage = messages.length > 0 ? anchor === "end" ? hasExtraMessage : pageIndex > 0 : pageIndex > 0;
|
|
16166
|
+
let allParts = [];
|
|
16107
16167
|
const timestamps = /* @__PURE__ */ new Map();
|
|
16108
16168
|
for (const msg of messages) {
|
|
16109
16169
|
timestamps.set(msg.id, msg.createdAt);
|
|
16110
16170
|
allParts.push(...formatMessageParts(msg, detail));
|
|
16111
16171
|
}
|
|
16172
|
+
if (toolName) {
|
|
16173
|
+
allParts = allParts.filter((p) => (p.type === "tool-call" || p.type === "tool-result") && p.toolName === toolName);
|
|
16174
|
+
}
|
|
16175
|
+
if (partType) {
|
|
16176
|
+
allParts = allParts.filter((p) => p.type === partType);
|
|
16177
|
+
}
|
|
16112
16178
|
const rendered = renderFormattedParts(allParts, timestamps, { maxTokens });
|
|
16179
|
+
const emptyMessage = messages.length === 0 ? pageIndex > 0 ? `(no messages found on page ${normalizedPage} for this thread)` : "(no messages in this thread)" : partType || toolName ? "(no message parts matched the current filters)" : "(no messages found)";
|
|
16113
16180
|
return {
|
|
16114
|
-
messages: rendered.text ||
|
|
16181
|
+
messages: rendered.text || emptyMessage,
|
|
16115
16182
|
count: messages.length,
|
|
16116
16183
|
cursor: messages[0]?.id || "",
|
|
16117
16184
|
page: normalizedPage,
|
|
16118
16185
|
limit: normalizedLimit,
|
|
16119
16186
|
detail,
|
|
16120
|
-
hasNextPage
|
|
16121
|
-
hasPrevPage
|
|
16187
|
+
hasNextPage,
|
|
16188
|
+
hasPrevPage,
|
|
16122
16189
|
truncated: rendered.truncated,
|
|
16123
16190
|
tokenOffset: rendered.tokenOffset
|
|
16124
16191
|
};
|
|
@@ -16135,7 +16202,9 @@ var recallTool = (_memoryConfig, options) => {
|
|
|
16135
16202
|
mode: z.enum(["messages", "threads", "search"]).optional().describe(
|
|
16136
16203
|
'What to retrieve. "messages" (default) pages through message history. "threads" lists all threads for the current user. "search" finds messages by semantic similarity across all threads.'
|
|
16137
16204
|
),
|
|
16138
|
-
threadId: z.string().min(1).optional().describe(
|
|
16205
|
+
threadId: z.string().min(1).optional().describe(
|
|
16206
|
+
'Browse a different thread, or use "current" for the active thread. Use mode="threads" first to discover thread IDs.'
|
|
16207
|
+
),
|
|
16139
16208
|
before: z.string().optional().describe(
|
|
16140
16209
|
'For mode="threads": only show threads created before this date. ISO 8601 or natural date string (e.g. "2026-03-15", "2026-03-10T00:00:00Z").'
|
|
16141
16210
|
),
|
|
@@ -16151,6 +16220,9 @@ var recallTool = (_memoryConfig, options) => {
|
|
|
16151
16220
|
cursor: z.string().min(1).optional().describe(
|
|
16152
16221
|
'A message ID to use as the pagination cursor. For mode="messages", provide either cursor or threadId. If only cursor is provided, it must belong to the current thread. Extract it from the start or end of an observation group range.'
|
|
16153
16222
|
),
|
|
16223
|
+
anchor: z.enum(["start", "end"]).optional().describe(
|
|
16224
|
+
'For mode="messages" without a cursor, page from the start (oldest-first) or end (newest-first) of the thread. Defaults to "start".'
|
|
16225
|
+
),
|
|
16154
16226
|
page: z.number().int().min(-50).max(50).optional().describe(
|
|
16155
16227
|
"Pagination offset. For messages: positive pages move forward from cursor, negative move backward. For threads: page number (0-indexed). 0 is treated as 1 for messages."
|
|
16156
16228
|
),
|
|
@@ -16158,6 +16230,10 @@ var recallTool = (_memoryConfig, options) => {
|
|
|
16158
16230
|
detail: z.enum(["low", "high"]).optional().describe(
|
|
16159
16231
|
'Detail level for messages. "low" (default) returns truncated text and tool names. "high" returns full content with tool args/results.'
|
|
16160
16232
|
),
|
|
16233
|
+
partType: z.enum(["text", "tool-call", "tool-result", "reasoning", "image", "file"]).optional().describe('Filter results to only include parts of this type. Only applies to mode="messages".'),
|
|
16234
|
+
toolName: z.string().min(1).optional().describe(
|
|
16235
|
+
'Filter results to only include tool-call and tool-result parts matching this tool name. Only applies to mode="messages".'
|
|
16236
|
+
),
|
|
16161
16237
|
partIndex: z.number().int().min(0).optional().describe(
|
|
16162
16238
|
"Fetch a single part from the cursor message by its positional index. When provided, returns only that part at high detail. Indices are shown as [p0], [p1], etc. in recall results."
|
|
16163
16239
|
)
|
|
@@ -16167,9 +16243,12 @@ var recallTool = (_memoryConfig, options) => {
|
|
|
16167
16243
|
query,
|
|
16168
16244
|
cursor,
|
|
16169
16245
|
threadId: explicitThreadId,
|
|
16246
|
+
anchor,
|
|
16170
16247
|
page,
|
|
16171
16248
|
limit,
|
|
16172
16249
|
detail,
|
|
16250
|
+
partType,
|
|
16251
|
+
toolName,
|
|
16173
16252
|
partIndex,
|
|
16174
16253
|
before,
|
|
16175
16254
|
after
|
|
@@ -16177,9 +16256,13 @@ var recallTool = (_memoryConfig, options) => {
|
|
|
16177
16256
|
const memory = context2?.memory;
|
|
16178
16257
|
const currentThreadId = context2?.agent?.threadId;
|
|
16179
16258
|
const resourceId = context2?.agent?.resourceId;
|
|
16259
|
+
const resolvedExplicitThreadId = explicitThreadId === "current" ? currentThreadId : explicitThreadId;
|
|
16180
16260
|
if (!memory) {
|
|
16181
16261
|
throw new Error("Memory instance is required for recall");
|
|
16182
16262
|
}
|
|
16263
|
+
if (explicitThreadId === "current" && !currentThreadId) {
|
|
16264
|
+
throw new Error("Could not resolve current thread.");
|
|
16265
|
+
}
|
|
16183
16266
|
if (mode === "search") {
|
|
16184
16267
|
if (!query) {
|
|
16185
16268
|
throw new Error('query is required for mode="search"');
|
|
@@ -16195,11 +16278,12 @@ var recallTool = (_memoryConfig, options) => {
|
|
|
16195
16278
|
topK: limit ?? 10,
|
|
16196
16279
|
before,
|
|
16197
16280
|
after,
|
|
16198
|
-
threadScope: !isResourceScope ? currentThreadId || void 0 : void 0
|
|
16281
|
+
threadScope: !isResourceScope ? currentThreadId || void 0 : resolvedExplicitThreadId || void 0
|
|
16199
16282
|
});
|
|
16200
16283
|
}
|
|
16201
16284
|
if (mode === "threads") {
|
|
16202
|
-
|
|
16285
|
+
const requestedCurrentThread = explicitThreadId === "current";
|
|
16286
|
+
if (!isResourceScope || requestedCurrentThread) {
|
|
16203
16287
|
if (!currentThreadId || !memory.getThreadById) {
|
|
16204
16288
|
return { error: "Could not resolve current thread." };
|
|
16205
16289
|
}
|
|
@@ -16207,6 +16291,9 @@ var recallTool = (_memoryConfig, options) => {
|
|
|
16207
16291
|
if (!thread) {
|
|
16208
16292
|
return { error: "Could not resolve current thread." };
|
|
16209
16293
|
}
|
|
16294
|
+
if (isResourceScope && resourceId && thread.resourceId !== resourceId) {
|
|
16295
|
+
throw new Error("Thread does not belong to the active resource");
|
|
16296
|
+
}
|
|
16210
16297
|
return {
|
|
16211
16298
|
threads: `- **${thread.title || "(untitled)"}** \u2190 current
|
|
16212
16299
|
id: ${thread.id}
|
|
@@ -16229,7 +16316,7 @@ var recallTool = (_memoryConfig, options) => {
|
|
|
16229
16316
|
after
|
|
16230
16317
|
});
|
|
16231
16318
|
}
|
|
16232
|
-
const hasExplicitThreadId = typeof
|
|
16319
|
+
const hasExplicitThreadId = typeof resolvedExplicitThreadId === "string" && resolvedExplicitThreadId.length > 0;
|
|
16233
16320
|
const hasCursor = typeof cursor === "string" && cursor.length > 0;
|
|
16234
16321
|
if (!hasExplicitThreadId && !hasCursor) {
|
|
16235
16322
|
throw new Error('Either cursor or threadId is required for mode="messages"');
|
|
@@ -16246,7 +16333,7 @@ var recallTool = (_memoryConfig, options) => {
|
|
|
16246
16333
|
if (!memory.getThreadById) {
|
|
16247
16334
|
throw new Error("Memory instance cannot verify thread access for recall");
|
|
16248
16335
|
}
|
|
16249
|
-
const thread = await memory.getThreadById({ threadId:
|
|
16336
|
+
const thread = await memory.getThreadById({ threadId: resolvedExplicitThreadId });
|
|
16250
16337
|
if (!thread || thread.resourceId !== resourceId) {
|
|
16251
16338
|
throw new Error("Thread does not belong to the active resource");
|
|
16252
16339
|
}
|
|
@@ -16287,7 +16374,10 @@ var recallTool = (_memoryConfig, options) => {
|
|
|
16287
16374
|
resourceId: isResourceScope ? resourceId : void 0,
|
|
16288
16375
|
page: page ?? 1,
|
|
16289
16376
|
limit: limit ?? 20,
|
|
16290
|
-
detail: detail ?? "low"
|
|
16377
|
+
detail: detail ?? "low",
|
|
16378
|
+
partType,
|
|
16379
|
+
toolName,
|
|
16380
|
+
anchor: anchor ?? "start"
|
|
16291
16381
|
});
|
|
16292
16382
|
}
|
|
16293
16383
|
if (partIndex !== void 0 && partIndex !== null) {
|
|
@@ -16308,6 +16398,8 @@ var recallTool = (_memoryConfig, options) => {
|
|
|
16308
16398
|
page,
|
|
16309
16399
|
limit,
|
|
16310
16400
|
detail: detail ?? "low",
|
|
16401
|
+
partType,
|
|
16402
|
+
toolName,
|
|
16311
16403
|
threadScope
|
|
16312
16404
|
});
|
|
16313
16405
|
}
|