@maximem/synap-js-sdk 0.5.7 → 0.5.9
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/README.md +5 -1
- package/dist/anticipation-cache-LPNKMRBH.cjs +20 -0
- package/dist/anticipation-cache-LPNKMRBH.cjs.map +1 -0
- package/dist/anticipation-cache-RWKF55J4.js +20 -0
- package/dist/{chunk-FXGLT6AT.js → chunk-I7TWR4S6.js} +59 -3
- package/dist/chunk-I7TWR4S6.js.map +1 -0
- package/dist/{chunk-T6U5PVAD.cjs → chunk-ICUFS3B5.cjs} +449 -12
- package/dist/chunk-ICUFS3B5.cjs.map +1 -0
- package/dist/{chunk-ODVOQOSO.js → chunk-R72YL6Q4.js} +445 -8
- package/dist/chunk-R72YL6Q4.js.map +1 -0
- package/dist/{chunk-HCULAVJC.cjs → chunk-RE4DDIWO.cjs} +66 -10
- package/dist/chunk-RE4DDIWO.cjs.map +1 -0
- package/dist/chunk-T372OL3Q.cjs.map +1 -1
- package/dist/chunk-ZKOJDUN4.cjs.map +1 -1
- package/dist/env-55ULHNJZ.cjs.map +1 -1
- package/dist/{errors-Bl3vY1eb.d.cts → errors-Bdayqlzy.d.cts} +88 -2
- package/dist/{errors-Bl3vY1eb.d.ts → errors-Bdayqlzy.d.ts} +88 -2
- package/dist/grpc/index.cjs +4 -4
- package/dist/grpc/index.cjs.map +1 -1
- package/dist/grpc/index.d.cts +8 -3
- package/dist/grpc/index.d.ts +8 -3
- package/dist/grpc/index.js +3 -3
- package/dist/index.cjs +1304 -116
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +281 -5
- package/dist/index.d.ts +281 -5
- package/dist/index.js +1257 -69
- package/dist/index.js.map +1 -1
- package/dist/onnx-encoder-4RSL6ID4.cjs +103 -0
- package/dist/onnx-encoder-4RSL6ID4.cjs.map +1 -0
- package/dist/onnx-encoder-NP35KH4H.js +103 -0
- package/dist/onnx-encoder-NP35KH4H.js.map +1 -0
- package/dist/{stream-client-VWKINE47.js → stream-client-3WHXGW4U.js} +2 -2
- package/dist/stream-client-L6OQPZN7.cjs +16 -0
- package/dist/stream-client-L6OQPZN7.cjs.map +1 -0
- package/package.json +4 -2
- package/dist/anticipation-cache-LRE3TLK4.cjs +0 -14
- package/dist/anticipation-cache-LRE3TLK4.cjs.map +0 -1
- package/dist/anticipation-cache-WLU4NMD5.js +0 -14
- package/dist/chunk-FXGLT6AT.js.map +0 -1
- package/dist/chunk-HCULAVJC.cjs.map +0 -1
- package/dist/chunk-ODVOQOSO.js.map +0 -1
- package/dist/chunk-T6U5PVAD.cjs.map +0 -1
- package/dist/stream-client-N46JBT5A.cjs +0 -16
- package/dist/stream-client-N46JBT5A.cjs.map +0 -1
- /package/dist/{anticipation-cache-WLU4NMD5.js.map → anticipation-cache-RWKF55J4.js.map} +0 -0
- /package/dist/{stream-client-VWKINE47.js.map → stream-client-3WHXGW4U.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -3,12 +3,14 @@ import {
|
|
|
3
3
|
AnticipationCache,
|
|
4
4
|
BM25,
|
|
5
5
|
CONTRACT,
|
|
6
|
+
HISTORY_SEARCH,
|
|
7
|
+
SEMANTIC_MATCH,
|
|
6
8
|
THRESHOLDS,
|
|
7
9
|
isRecallQuery,
|
|
8
10
|
rungKey,
|
|
9
11
|
stem,
|
|
10
12
|
tokenize
|
|
11
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-R72YL6Q4.js";
|
|
12
14
|
import {
|
|
13
15
|
getEnv
|
|
14
16
|
} from "./chunk-QBCVMPGK.js";
|
|
@@ -55,6 +57,7 @@ var ENDPOINTS = {
|
|
|
55
57
|
context_fetch_client: { method: "POST", path: "/v1/context/client/fetch", idempotent: true },
|
|
56
58
|
context_fetch_conversation: { method: "POST", path: "/v1/context/conversation/fetch", idempotent: true },
|
|
57
59
|
context_for_prompt: { method: "GET", path: "/v1/conversations/{conversation_id}/context-for-prompt", idempotent: true },
|
|
60
|
+
conversation_turns: { method: "GET", path: "/v1/conversations/{conversation_id}/messages", idempotent: true },
|
|
58
61
|
conversations_ingest: { method: "POST", path: "/v1/conversations/ingest", idempotent: false },
|
|
59
62
|
conversations_messages: { method: "POST", path: "/v1/conversations/messages", idempotent: false },
|
|
60
63
|
conversations_messages_batch: { method: "POST", path: "/v1/conversations/messages/batch", idempotent: false },
|
|
@@ -517,6 +520,86 @@ function sleep(ms, signal) {
|
|
|
517
520
|
});
|
|
518
521
|
}
|
|
519
522
|
|
|
523
|
+
// src/context/ranked.ts
|
|
524
|
+
var RESPONSE_SHAPES = ["by_type", "ranked"];
|
|
525
|
+
var MEMORY_TYPES = [
|
|
526
|
+
"facts",
|
|
527
|
+
"preferences",
|
|
528
|
+
"episodes",
|
|
529
|
+
"emotions",
|
|
530
|
+
"temporal_events"
|
|
531
|
+
];
|
|
532
|
+
var EXCERPT_CATEGORY = "excerpts";
|
|
533
|
+
function wireRankScore(item) {
|
|
534
|
+
const md = item.metadata;
|
|
535
|
+
let raw = md !== null && typeof md === "object" ? md["rank_score"] : void 0;
|
|
536
|
+
if (raw === void 0 || raw === null) raw = item["rank_score"];
|
|
537
|
+
return typeof raw === "number" && Number.isFinite(raw) ? raw : null;
|
|
538
|
+
}
|
|
539
|
+
function wireCategory(item, fallback = "") {
|
|
540
|
+
if (item.source === "chunks") return EXCERPT_CATEGORY;
|
|
541
|
+
const category = item["category"];
|
|
542
|
+
if (typeof category === "string" && category !== "") return category;
|
|
543
|
+
const contextType = item["context_type"];
|
|
544
|
+
if (typeof contextType === "string" && contextType !== "") return contextType;
|
|
545
|
+
return fallback;
|
|
546
|
+
}
|
|
547
|
+
function byRankScore(a, b) {
|
|
548
|
+
const sa = wireRankScore(a);
|
|
549
|
+
const sb = wireRankScore(b);
|
|
550
|
+
if (sa === null && sb === null) return 0;
|
|
551
|
+
if (sa === null) return 1;
|
|
552
|
+
if (sb === null) return -1;
|
|
553
|
+
return sb - sa;
|
|
554
|
+
}
|
|
555
|
+
function rankItems(byType) {
|
|
556
|
+
const flat = [];
|
|
557
|
+
for (const type of MEMORY_TYPES) {
|
|
558
|
+
const list = byType[type];
|
|
559
|
+
if (!Array.isArray(list)) continue;
|
|
560
|
+
for (const it of list) {
|
|
561
|
+
if (it === null || typeof it !== "object") continue;
|
|
562
|
+
flat.push({
|
|
563
|
+
...it,
|
|
564
|
+
category: wireCategory(it, type),
|
|
565
|
+
id: it.id || it.item_id || ""
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
flat.sort(byRankScore);
|
|
570
|
+
return flat.map((it, n2) => ({ ...it, rank: n2 + 1 }));
|
|
571
|
+
}
|
|
572
|
+
function applyResponseShape(raw, shape = "by_type") {
|
|
573
|
+
const wire = raw["items"];
|
|
574
|
+
if (shape !== "ranked" && !Array.isArray(wire)) return raw;
|
|
575
|
+
const source = Array.isArray(wire) ? wire : rankItems(raw);
|
|
576
|
+
const items = source.map((it) => ({
|
|
577
|
+
...it,
|
|
578
|
+
id: it.id || it.item_id || "",
|
|
579
|
+
category: wireCategory(it)
|
|
580
|
+
}));
|
|
581
|
+
const out = { ...raw, items };
|
|
582
|
+
for (const type of MEMORY_TYPES) {
|
|
583
|
+
out[type] = items.filter((it) => it["category"] === type);
|
|
584
|
+
}
|
|
585
|
+
out["excerpts"] = items.filter((it) => it["category"] === EXCERPT_CATEGORY);
|
|
586
|
+
return out;
|
|
587
|
+
}
|
|
588
|
+
var RANKED_LABELS = {
|
|
589
|
+
facts: "fact",
|
|
590
|
+
preferences: "preference",
|
|
591
|
+
episodes: "episode",
|
|
592
|
+
emotions: "emotion",
|
|
593
|
+
temporal_events: "temporal event",
|
|
594
|
+
[EXCERPT_CATEGORY]: "excerpt"
|
|
595
|
+
};
|
|
596
|
+
function rankedLabel(category) {
|
|
597
|
+
return RANKED_LABELS[category] ?? (category !== "" ? category : "item");
|
|
598
|
+
}
|
|
599
|
+
function sortByRankScore(items) {
|
|
600
|
+
return [...items].sort(byRankScore);
|
|
601
|
+
}
|
|
602
|
+
|
|
520
603
|
// src/context/fetch.ts
|
|
521
604
|
var ENDPOINT_BY_SCOPE = {
|
|
522
605
|
user: "context_fetch_user",
|
|
@@ -539,6 +622,7 @@ function buildFetchRequest(scope, options) {
|
|
|
539
622
|
const precisionLevel = opt(options, "precision_level", "precisionLevel") ?? "high";
|
|
540
623
|
const queryDate = opt(options, "query_date", "queryDate");
|
|
541
624
|
const perQuery = opt(options, "max_results_per_query", "maxResultsPerQuery");
|
|
625
|
+
const responseShape = responseShapeOf(options);
|
|
542
626
|
const mode = options.mode ?? "fast";
|
|
543
627
|
if (searchQuery !== void 0 && !Array.isArray(searchQuery)) {
|
|
544
628
|
throw new InvalidInputError("search_query must be an array when provided");
|
|
@@ -556,6 +640,7 @@ function buildFetchRequest(scope, options) {
|
|
|
556
640
|
if (precisionLevel !== "high") body["precision_level"] = precisionLevel;
|
|
557
641
|
if (queryDate) body["query_date"] = queryDate;
|
|
558
642
|
if (perQuery) body["max_results_per_query"] = perQuery;
|
|
643
|
+
if (responseShape !== "by_type") body["response_shape"] = responseShape;
|
|
559
644
|
if (options.include_conversation_context === false) {
|
|
560
645
|
body["include_conversation_context"] = false;
|
|
561
646
|
}
|
|
@@ -603,21 +688,48 @@ function buildFetchRequest(scope, options) {
|
|
|
603
688
|
return { endpoint: ENDPOINT_BY_SCOPE[scope], body };
|
|
604
689
|
}
|
|
605
690
|
var VALID_CONTEXT_MODES = ["in-conversation", "conversation-summary"];
|
|
691
|
+
function responseShapeOf(options) {
|
|
692
|
+
const shape = opt(options, "response_shape", "responseShape") ?? "by_type";
|
|
693
|
+
if (!RESPONSE_SHAPES.includes(shape)) {
|
|
694
|
+
throw new InvalidInputError(
|
|
695
|
+
`Invalid response_shape '${shape}'. Must be one of: ${RESPONSE_SHAPES.join(", ")}`
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
return shape;
|
|
699
|
+
}
|
|
606
700
|
async function fetchContext(transport, scope, options = {}, hooks = {}) {
|
|
607
701
|
const startedAt = Date.now();
|
|
608
702
|
transport.checkCustomerId(
|
|
609
703
|
opt(options, "customer_id", "customerId"),
|
|
610
704
|
`${scope}.context.fetch`
|
|
611
705
|
);
|
|
612
|
-
const
|
|
706
|
+
const responseShape = responseShapeOf(options);
|
|
707
|
+
const network = async () => {
|
|
708
|
+
const { endpoint, body } = buildFetchRequest(scope, options);
|
|
709
|
+
const result = await transport.request(endpoint, { body });
|
|
710
|
+
return applyResponseShape(result?.["context"] ?? {}, responseShape);
|
|
711
|
+
};
|
|
712
|
+
const anticipated = await hooks.beforeFetch?.(scope, options) ?? null;
|
|
713
|
+
if (anticipated !== null && anticipated.borderline) {
|
|
714
|
+
const pending = network();
|
|
715
|
+
pending.catch(() => void 0);
|
|
716
|
+
let timer;
|
|
717
|
+
const late = new Promise((resolve) => {
|
|
718
|
+
timer = setTimeout(() => resolve(null), anticipated.raceDeadlineMs ?? 250);
|
|
719
|
+
});
|
|
720
|
+
const won = await Promise.race([pending.then((r) => r, () => null), late]);
|
|
721
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
722
|
+
if (won !== null) {
|
|
723
|
+
hooks.onAssembled?.(scope, options, won, startedAt);
|
|
724
|
+
return won;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
613
727
|
if (anticipated !== null) {
|
|
614
728
|
hooks.onServedFromCache?.(scope, options, anticipated);
|
|
615
729
|
hooks.onAssembled?.(scope, options, anticipated.response, startedAt);
|
|
616
|
-
return anticipated.response;
|
|
730
|
+
return applyResponseShape(anticipated.response, responseShape);
|
|
617
731
|
}
|
|
618
|
-
const
|
|
619
|
-
const result = await transport.request(endpoint, { body });
|
|
620
|
-
const response = result?.["context"] ?? {};
|
|
732
|
+
const response = await network();
|
|
621
733
|
hooks.onAssembled?.(scope, options, response, startedAt);
|
|
622
734
|
return response;
|
|
623
735
|
}
|
|
@@ -638,6 +750,8 @@ function matchedQueries(item = {}) {
|
|
|
638
750
|
function normalizeTemporalFields(item = {}) {
|
|
639
751
|
return {
|
|
640
752
|
eventDate: pick(item["eventDate"], item.event_date, null),
|
|
753
|
+
eventEnd: pick(item["eventEnd"], item.event_end, null),
|
|
754
|
+
eventPrecision: pick(item["eventPrecision"], item.event_precision, null),
|
|
641
755
|
saidAt: pick(item["saidAt"], item.said_at, null),
|
|
642
756
|
validUntil: pick(item["validUntil"], item.valid_until, null),
|
|
643
757
|
temporalCategory: pick(item["temporalCategory"], item.temporal_category, null),
|
|
@@ -698,6 +812,8 @@ function normalizeTemporalEvent(item = {}) {
|
|
|
698
812
|
id: item.id || "",
|
|
699
813
|
content: item.content || "",
|
|
700
814
|
eventDate: pick(item["eventDate"], item.event_date, null),
|
|
815
|
+
eventEnd: pick(item["eventEnd"], item.event_end, null),
|
|
816
|
+
eventPrecision: pick(item["eventPrecision"], item.event_precision, null),
|
|
701
817
|
saidAt: pick(item["saidAt"], item.said_at, null),
|
|
702
818
|
validUntil: pick(item["validUntil"], item.valid_until, null),
|
|
703
819
|
temporalCategory: pick(item["temporalCategory"], item.temporal_category, ""),
|
|
@@ -734,6 +850,17 @@ function normalizeConversationContext(value) {
|
|
|
734
850
|
conversationId: pick(value["conversationId"], value.conversation_id, null)
|
|
735
851
|
};
|
|
736
852
|
}
|
|
853
|
+
function normalizeRetrievalHealth(raw) {
|
|
854
|
+
if (!raw || typeof raw !== "object") return null;
|
|
855
|
+
const r = raw;
|
|
856
|
+
const warnings = r["warnings"];
|
|
857
|
+
return {
|
|
858
|
+
degraded: r["degraded"] === void 0 ? true : Boolean(r["degraded"]),
|
|
859
|
+
level: r["level"] ?? null,
|
|
860
|
+
code: r["code"] ?? null,
|
|
861
|
+
warnings: Array.isArray(warnings) ? warnings.map((w) => String(w)) : []
|
|
862
|
+
};
|
|
863
|
+
}
|
|
737
864
|
function normalizeContextResponse(result = {}) {
|
|
738
865
|
const context = result["context"] ?? result;
|
|
739
866
|
return {
|
|
@@ -750,7 +877,15 @@ function normalizeContextResponse(result = {}) {
|
|
|
750
877
|
)
|
|
751
878
|
),
|
|
752
879
|
metadata: normalizeContextMetadata(obj(context.metadata)),
|
|
753
|
-
|
|
880
|
+
// `retrieval` is a SIBLING of `context` in the body, so it is read off the
|
|
881
|
+
// envelope first and only then off a bare context. Reading it from
|
|
882
|
+
// `context` alone is how the Python SDK dropped it.
|
|
883
|
+
retrieval: normalizeRetrievalHealth(
|
|
884
|
+
pick(result["retrieval"], context["retrieval"], null)
|
|
885
|
+
),
|
|
886
|
+
rawResponse: pick(context["rawResponse"], context["raw_response"], {}),
|
|
887
|
+
// Only for a ranked fetch, so a by-type result has exactly the keys it had.
|
|
888
|
+
...Array.isArray(context.items) ? { items: context.items, excerpts: Array.isArray(context.excerpts) ? context.excerpts : [] } : {}
|
|
754
889
|
};
|
|
755
890
|
}
|
|
756
891
|
|
|
@@ -1065,6 +1200,27 @@ function validateInstanceId(instanceId) {
|
|
|
1065
1200
|
}
|
|
1066
1201
|
}
|
|
1067
1202
|
|
|
1203
|
+
// src/agent-model.ts
|
|
1204
|
+
var AGENT_MODEL_KEY = "agent_model";
|
|
1205
|
+
var AGENT_CONTEXT_WINDOW_KEY = "agent_context_window";
|
|
1206
|
+
function agentModelOf(options) {
|
|
1207
|
+
const o2 = options ?? {};
|
|
1208
|
+
const window = o2["context_window"] ?? o2["contextWindow"];
|
|
1209
|
+
return {
|
|
1210
|
+
model: typeof o2["model"] === "string" ? o2["model"] : void 0,
|
|
1211
|
+
contextWindow: typeof window === "number" ? window : void 0
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
function withAgentModel(metadata, perCall, defaults, asStrings = false) {
|
|
1215
|
+
const model = perCall.model || defaults.model;
|
|
1216
|
+
const window = perCall.contextWindow || defaults.contextWindow;
|
|
1217
|
+
if (!model && !window) return metadata;
|
|
1218
|
+
const out = { ...metadata ?? {} };
|
|
1219
|
+
if (model) out[AGENT_MODEL_KEY] = String(model);
|
|
1220
|
+
if (window) out[AGENT_CONTEXT_WINDOW_KEY] = asStrings ? String(Math.trunc(window)) : Math.trunc(window);
|
|
1221
|
+
return out;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1068
1224
|
// src/conversation/interface.ts
|
|
1069
1225
|
var COMPACTION_LEVELS = [
|
|
1070
1226
|
"low",
|
|
@@ -1193,10 +1349,50 @@ var CompactionSubscribers = class {
|
|
|
1193
1349
|
}
|
|
1194
1350
|
}
|
|
1195
1351
|
};
|
|
1352
|
+
function storedBatchRows(bodies, result) {
|
|
1353
|
+
const r = result ?? {};
|
|
1354
|
+
const results = Array.isArray(r["results"]) ? r["results"] : [];
|
|
1355
|
+
const errors = Array.isArray(r["errors"]) ? r["errors"] : [];
|
|
1356
|
+
const failed = new Set(errors.map((e) => e?.["index"]));
|
|
1357
|
+
const stored = bodies.filter((_, index) => !failed.has(index));
|
|
1358
|
+
if (stored.length !== results.length) return [];
|
|
1359
|
+
const pairs = [];
|
|
1360
|
+
stored.forEach((body, i) => {
|
|
1361
|
+
const row = results[i];
|
|
1362
|
+
if (row && typeof row === "object" && row["message_id"]) pairs.push([body, row]);
|
|
1363
|
+
});
|
|
1364
|
+
return pairs;
|
|
1365
|
+
}
|
|
1196
1366
|
function createConversationNamespace(transport, subscribers = new CompactionSubscribers(), onWrite = () => {
|
|
1197
1367
|
}, onTurn = () => {
|
|
1198
1368
|
}, onFetched = () => {
|
|
1199
|
-
}, localShortTerm = { prompt: () => null, compacted: () => null }) {
|
|
1369
|
+
}, localShortTerm = { prompt: () => null, compacted: () => null }, agentModel = () => ({})) {
|
|
1370
|
+
const writesInFlight = /* @__PURE__ */ new Map();
|
|
1371
|
+
const withTurnModel = (body, options) => {
|
|
1372
|
+
const metadata = withAgentModel(
|
|
1373
|
+
body["metadata"],
|
|
1374
|
+
agentModelOf(options),
|
|
1375
|
+
agentModel()
|
|
1376
|
+
);
|
|
1377
|
+
return metadata === void 0 ? body : { ...body, metadata };
|
|
1378
|
+
};
|
|
1379
|
+
const writeStarted = (conversationId) => {
|
|
1380
|
+
writesInFlight.set(conversationId, (writesInFlight.get(conversationId) ?? 0) + 1);
|
|
1381
|
+
};
|
|
1382
|
+
const writeLanded = (conversationId) => {
|
|
1383
|
+
const remaining = (writesInFlight.get(conversationId) ?? 0) - 1;
|
|
1384
|
+
if (remaining > 0) writesInFlight.set(conversationId, remaining);
|
|
1385
|
+
else writesInFlight.delete(conversationId);
|
|
1386
|
+
};
|
|
1387
|
+
const confirmation = (result, conversationId) => {
|
|
1388
|
+
const r = result ?? {};
|
|
1389
|
+
const count = r["conversation_message_count"];
|
|
1390
|
+
return {
|
|
1391
|
+
...typeof r["message_id"] === "string" ? { messageId: r["message_id"] } : {},
|
|
1392
|
+
serverMessageCount: typeof count === "number" ? count : null,
|
|
1393
|
+
inFlight: writesInFlight.get(conversationId) ?? 0
|
|
1394
|
+
};
|
|
1395
|
+
};
|
|
1200
1396
|
const convId = (v) => {
|
|
1201
1397
|
const id = typeof v === "string" ? v : v.conversation_id ?? v.conversationId;
|
|
1202
1398
|
if (!id) throw new InvalidInputError("conversation_id is required");
|
|
@@ -1209,13 +1405,21 @@ function createConversationNamespace(transport, subscribers = new CompactionSubs
|
|
|
1209
1405
|
options["customer_id"] ?? options["customerId"],
|
|
1210
1406
|
"conversation.record_message"
|
|
1211
1407
|
);
|
|
1212
|
-
const body = buildRecordMessageBody(options);
|
|
1213
|
-
const
|
|
1408
|
+
const body = withTurnModel(buildRecordMessageBody(options), options);
|
|
1409
|
+
const conversationId = String(body["conversation_id"] ?? "");
|
|
1410
|
+
writeStarted(conversationId);
|
|
1411
|
+
let result;
|
|
1412
|
+
try {
|
|
1413
|
+
result = await transport.request("conversations_messages", { body });
|
|
1414
|
+
} finally {
|
|
1415
|
+
writeLanded(conversationId);
|
|
1416
|
+
}
|
|
1214
1417
|
onTurn({
|
|
1215
|
-
conversationId
|
|
1418
|
+
conversationId,
|
|
1216
1419
|
role: String(body["role"] ?? "user"),
|
|
1217
1420
|
content: String(body["content"] ?? ""),
|
|
1218
|
-
timestamp: body["timestamp"]
|
|
1421
|
+
timestamp: body["timestamp"],
|
|
1422
|
+
...confirmation(result, conversationId)
|
|
1219
1423
|
});
|
|
1220
1424
|
onWrite({
|
|
1221
1425
|
userId: body["user_id"],
|
|
@@ -1234,16 +1438,18 @@ function createConversationNamespace(transport, subscribers = new CompactionSubs
|
|
|
1234
1438
|
"conversation.record_messages_batch"
|
|
1235
1439
|
);
|
|
1236
1440
|
}
|
|
1237
|
-
const bodies = messages.map(buildRecordMessageBody);
|
|
1441
|
+
const bodies = messages.map((m) => withTurnModel(buildRecordMessageBody(m), m));
|
|
1238
1442
|
const result = await transport.request("conversations_messages_batch", {
|
|
1239
1443
|
body: { messages: bodies }
|
|
1240
1444
|
});
|
|
1241
|
-
for (const body of bodies) {
|
|
1445
|
+
for (const [body, row] of storedBatchRows(bodies, result)) {
|
|
1446
|
+
const conversationId = String(body["conversation_id"] ?? "");
|
|
1242
1447
|
onTurn({
|
|
1243
|
-
conversationId
|
|
1448
|
+
conversationId,
|
|
1244
1449
|
role: String(body["role"] ?? "user"),
|
|
1245
1450
|
content: String(body["content"] ?? ""),
|
|
1246
|
-
timestamp: body["timestamp"]
|
|
1451
|
+
timestamp: body["timestamp"],
|
|
1452
|
+
...confirmation(row, conversationId)
|
|
1247
1453
|
});
|
|
1248
1454
|
}
|
|
1249
1455
|
for (const body of bodies) {
|
|
@@ -1278,13 +1484,24 @@ function createConversationNamespace(transport, subscribers = new CompactionSubs
|
|
|
1278
1484
|
},
|
|
1279
1485
|
get_context_for_prompt: async (options = {}) => {
|
|
1280
1486
|
const id = convId(options);
|
|
1487
|
+
const search = (options.search_history ?? options.searchHistory ?? true) && localShortTerm.historyExcerpts;
|
|
1488
|
+
const withHistory = async (response2) => {
|
|
1489
|
+
if (!search) return response2;
|
|
1490
|
+
try {
|
|
1491
|
+
return await localShortTerm.historyExcerpts(id, response2, options.query);
|
|
1492
|
+
} catch {
|
|
1493
|
+
return response2;
|
|
1494
|
+
}
|
|
1495
|
+
};
|
|
1281
1496
|
const local = localShortTerm.prompt(id, options.style ?? "structured");
|
|
1282
|
-
if (local !== null) return local;
|
|
1497
|
+
if (local !== null) return withHistory(local);
|
|
1283
1498
|
const result = await transport.request("context_for_prompt", {
|
|
1284
1499
|
pathParams: { conversation_id: id },
|
|
1285
1500
|
query: { style: options.style ?? "structured" }
|
|
1286
1501
|
});
|
|
1287
|
-
|
|
1502
|
+
const response = result?.["context_for_prompt"] ?? result ?? {};
|
|
1503
|
+
localShortTerm.reseed?.(id, response);
|
|
1504
|
+
return withHistory(response);
|
|
1288
1505
|
},
|
|
1289
1506
|
async compact(options) {
|
|
1290
1507
|
return transport.request("conversations_compact", { body: buildCompactBody(options) });
|
|
@@ -1467,11 +1684,13 @@ function jsonOrText(value) {
|
|
|
1467
1684
|
}
|
|
1468
1685
|
}
|
|
1469
1686
|
}
|
|
1470
|
-
function createInstanceNamespace(credentials, cache, onTurn) {
|
|
1687
|
+
function createInstanceNamespace(credentials, cache, onTurn, agentModel = () => ({}), onToolResult) {
|
|
1471
1688
|
let client = null;
|
|
1472
1689
|
const openSessions = /* @__PURE__ */ new Map();
|
|
1473
1690
|
let dispatchCompaction = null;
|
|
1474
1691
|
let applyCompaction = null;
|
|
1692
|
+
let onListen = null;
|
|
1693
|
+
let onConversationState = null;
|
|
1475
1694
|
const internals = {
|
|
1476
1695
|
onCompactionUpdate(conversationId, bundle) {
|
|
1477
1696
|
applyCompaction?.(bundle);
|
|
@@ -1480,6 +1699,12 @@ function createInstanceNamespace(credentials, cache, onTurn) {
|
|
|
1480
1699
|
setCompactionApplier(apply) {
|
|
1481
1700
|
applyCompaction = apply;
|
|
1482
1701
|
},
|
|
1702
|
+
setOnListen(handle) {
|
|
1703
|
+
onListen = handle;
|
|
1704
|
+
},
|
|
1705
|
+
setConversationStateHandler(handle) {
|
|
1706
|
+
onConversationState = handle;
|
|
1707
|
+
},
|
|
1483
1708
|
sendContextUsed(event) {
|
|
1484
1709
|
client?.sendContextUsed(event);
|
|
1485
1710
|
},
|
|
@@ -1514,7 +1739,11 @@ function createInstanceNamespace(credentials, cache, onTurn) {
|
|
|
1514
1739
|
if (client !== null) {
|
|
1515
1740
|
throw new ListeningAlreadyActiveError("Listening is already active on this instance");
|
|
1516
1741
|
}
|
|
1517
|
-
|
|
1742
|
+
try {
|
|
1743
|
+
onListen?.();
|
|
1744
|
+
} catch {
|
|
1745
|
+
}
|
|
1746
|
+
const { GrpcStreamClient } = await import("./stream-client-3WHXGW4U.js");
|
|
1518
1747
|
const onContext = options.on_context ?? options.onContext;
|
|
1519
1748
|
const onDisconnect = options.on_disconnect ?? options.onDisconnect;
|
|
1520
1749
|
const onReconnect = options.on_reconnect ?? options.onReconnect;
|
|
@@ -1528,6 +1757,9 @@ function createInstanceNamespace(credentials, cache, onTurn) {
|
|
|
1528
1757
|
onCompactionUpdate: (conversationId, bundle) => {
|
|
1529
1758
|
internals.onCompactionUpdate(conversationId, bundle);
|
|
1530
1759
|
},
|
|
1760
|
+
onConversationState: (conversationId, messageCount) => {
|
|
1761
|
+
onConversationState?.(conversationId, messageCount);
|
|
1762
|
+
},
|
|
1531
1763
|
onStateChange: (state) => {
|
|
1532
1764
|
if (state !== "connected") openSessions.clear();
|
|
1533
1765
|
if (state === "reconnecting") {
|
|
@@ -1606,7 +1838,7 @@ function createInstanceNamespace(credentials, cache, onTurn) {
|
|
|
1606
1838
|
user_id: options.user_id ?? options.userId ?? "",
|
|
1607
1839
|
customer_id: options.customer_id ?? options.customerId ?? "",
|
|
1608
1840
|
session_id: options.session_id ?? options.sessionId ?? openSessions.get(conversationId) ?? "",
|
|
1609
|
-
metadata: options.metadata ?? {},
|
|
1841
|
+
metadata: withAgentModel(options.metadata, agentModelOf(options), agentModel(), true) ?? {},
|
|
1610
1842
|
timestamp_ms: Date.now(),
|
|
1611
1843
|
tool_name: options.tool_name ?? options.toolName ?? "",
|
|
1612
1844
|
// Python serialises tool_args with json.dumps into tool_args_json.
|
|
@@ -1636,6 +1868,10 @@ function createInstanceNamespace(credentials, cache, onTurn) {
|
|
|
1636
1868
|
});
|
|
1637
1869
|
},
|
|
1638
1870
|
async record_tool_result(options) {
|
|
1871
|
+
const conversationId = options.conversation_id ?? options.conversationId ?? "";
|
|
1872
|
+
if (conversationId) {
|
|
1873
|
+
onToolResult?.({ conversationId, result: options.result, toolName: options.tool_name ?? options.toolName ?? "" });
|
|
1874
|
+
}
|
|
1639
1875
|
await namespace.send_message({
|
|
1640
1876
|
content: "",
|
|
1641
1877
|
role: "tool",
|
|
@@ -1686,7 +1922,7 @@ function createInstanceNamespace(credentials, cache, onTurn) {
|
|
|
1686
1922
|
}
|
|
1687
1923
|
|
|
1688
1924
|
// src/version.ts
|
|
1689
|
-
var SDK_VERSION = "0.5.
|
|
1925
|
+
var SDK_VERSION = "0.5.9";
|
|
1690
1926
|
|
|
1691
1927
|
// src/registry.ts
|
|
1692
1928
|
var instances = /* @__PURE__ */ new Map();
|
|
@@ -1727,6 +1963,34 @@ function unregisterIfOwner(key, client) {
|
|
|
1727
1963
|
// src/cache/short-term-store.ts
|
|
1728
1964
|
var DEFAULT_MAX_CONVERSATIONS = 100;
|
|
1729
1965
|
var DEFAULT_MAX_AGE_MS = 12 * 60 * 60 * 1e3;
|
|
1966
|
+
var CONTEXT_BLOCK_PREFIX = "## Conversation so far (compacted";
|
|
1967
|
+
function contextBlockOf(cc) {
|
|
1968
|
+
const block = cc["context_block"];
|
|
1969
|
+
if (typeof block === "string" && block) return block;
|
|
1970
|
+
const summary = cc["summary"];
|
|
1971
|
+
if (typeof summary === "string" && summary.startsWith(CONTEXT_BLOCK_PREFIX)) return summary;
|
|
1972
|
+
return null;
|
|
1973
|
+
}
|
|
1974
|
+
function localMessageCount(entry) {
|
|
1975
|
+
return entry.compactedMessageCount + entry.recentTurns.length;
|
|
1976
|
+
}
|
|
1977
|
+
function isComplete(entry) {
|
|
1978
|
+
return entry.participant && entry.synced && !entry.shared && entry.serverMessageCount !== null && entry.serverMessageCount <= localMessageCount(entry);
|
|
1979
|
+
}
|
|
1980
|
+
function heldIn(turn, turns) {
|
|
1981
|
+
return turns.some(
|
|
1982
|
+
(held) => turn.message_id && held.message_id ? held.message_id === turn.message_id : held.role === turn.role && held.content === turn.content
|
|
1983
|
+
);
|
|
1984
|
+
}
|
|
1985
|
+
function turnsAfter(localTurns, serverTurns) {
|
|
1986
|
+
const newer = [];
|
|
1987
|
+
for (let i = localTurns.length - 1; i >= 0; i -= 1) {
|
|
1988
|
+
const turn = localTurns[i];
|
|
1989
|
+
if (heldIn(turn, serverTurns)) break;
|
|
1990
|
+
newer.unshift(turn);
|
|
1991
|
+
}
|
|
1992
|
+
return newer;
|
|
1993
|
+
}
|
|
1730
1994
|
function parseIso(value) {
|
|
1731
1995
|
if (value === null || value === void 0 || value === "") return null;
|
|
1732
1996
|
if (value instanceof Date) return value.getTime();
|
|
@@ -1746,11 +2010,23 @@ var ShortTermStore = class {
|
|
|
1746
2010
|
#maxConversations;
|
|
1747
2011
|
#maxAgeMs;
|
|
1748
2012
|
#now;
|
|
2013
|
+
/**
|
|
2014
|
+
* Every turn appended here is also kept in the archive (a TurnStore), which
|
|
2015
|
+
* a compaction does not prune: the turns it covers stay searchable locally
|
|
2016
|
+
* (history-search.ts). Python: `ShortTermContextStore.attach_archive`.
|
|
2017
|
+
*/
|
|
2018
|
+
#archive = null;
|
|
1749
2019
|
constructor(options = {}) {
|
|
1750
2020
|
this.#maxConversations = options.maxConversations ?? DEFAULT_MAX_CONVERSATIONS;
|
|
1751
2021
|
this.#maxAgeMs = options.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
1752
2022
|
this.#now = options.now ?? (() => Date.now());
|
|
1753
2023
|
}
|
|
2024
|
+
attachArchive(archive) {
|
|
2025
|
+
this.#archive = archive;
|
|
2026
|
+
}
|
|
2027
|
+
get archive() {
|
|
2028
|
+
return this.#archive;
|
|
2029
|
+
}
|
|
1754
2030
|
/** The entry, or null. Age-evicts on read, as Python does. */
|
|
1755
2031
|
get(conversationId) {
|
|
1756
2032
|
if (!conversationId) return null;
|
|
@@ -1776,23 +2052,88 @@ var ShortTermStore = class {
|
|
|
1776
2052
|
* exists yet (the first turn, before any compaction has arrived) a fresh one
|
|
1777
2053
|
* is created with an empty summary; the next `compaction_update` fills the
|
|
1778
2054
|
* summary fields in and prunes the turns it covers.
|
|
2055
|
+
*
|
|
2056
|
+
* `confirmation` is what the write learned from the server. See
|
|
2057
|
+
* `TurnConfirmation` and `noteServerCount`.
|
|
1779
2058
|
*/
|
|
1780
|
-
appendTurn(conversationId, role, content, timestamp) {
|
|
2059
|
+
appendTurn(conversationId, role, content, timestamp, confirmation = {}) {
|
|
1781
2060
|
if (!conversationId) return;
|
|
1782
2061
|
const stamp = timestamp ?? new Date(this.#now()).toISOString();
|
|
2062
|
+
if (this.#archive !== null) {
|
|
2063
|
+
try {
|
|
2064
|
+
this.#archive.add(conversationId, role, content, stamp, { messageId: confirmation.messageId ?? null });
|
|
2065
|
+
} catch {
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
1783
2068
|
const entry = this.#entries.get(conversationId) ?? this.#blank(conversationId);
|
|
1784
|
-
|
|
2069
|
+
const messageId = confirmation.messageId;
|
|
2070
|
+
const alreadyHeld = Boolean(messageId) && entry.recentTurns.some((t) => t.message_id === messageId);
|
|
2071
|
+
if (!alreadyHeld) {
|
|
2072
|
+
const turn = { role, content, timestamp: stamp };
|
|
2073
|
+
if (messageId) turn.message_id = messageId;
|
|
2074
|
+
entry.recentTurns.push(turn);
|
|
2075
|
+
}
|
|
2076
|
+
const count = confirmation.serverMessageCount;
|
|
2077
|
+
if (count !== null && count !== void 0) {
|
|
2078
|
+
this.#noteCount(entry, Number(count), confirmation.inFlight ?? 0);
|
|
2079
|
+
}
|
|
1785
2080
|
entry.lastActivityAt = parseIso(stamp) ?? this.#now();
|
|
1786
2081
|
this.#touch(conversationId, entry);
|
|
1787
2082
|
this.#evict();
|
|
1788
2083
|
}
|
|
2084
|
+
/**
|
|
2085
|
+
* Record the server's message count for a conversation.
|
|
2086
|
+
*
|
|
2087
|
+
* The streamed write path's half of `appendTurn`'s confirmation: a streamed
|
|
2088
|
+
* turn is appended when it is sent, and the count arrives later, once the
|
|
2089
|
+
* server has written it. A count for a conversation this process holds
|
|
2090
|
+
* nothing for is ignored.
|
|
2091
|
+
*/
|
|
2092
|
+
noteServerCount(conversationId, count) {
|
|
2093
|
+
if (!conversationId) return;
|
|
2094
|
+
const entry = this.#entries.get(conversationId);
|
|
2095
|
+
if (entry !== void 0) this.#noteCount(entry, Number(count), 0);
|
|
2096
|
+
}
|
|
2097
|
+
/**
|
|
2098
|
+
* Compare the server's count with what the entry holds.
|
|
2099
|
+
*
|
|
2100
|
+
* The count is taken after one of this process's own writes, so it proves
|
|
2101
|
+
* the process takes part in the conversation.
|
|
2102
|
+
*
|
|
2103
|
+
* Server ahead of an entry that was in sync: something else wrote, and the
|
|
2104
|
+
* entry is marked shared for good. Server ahead of an entry that was never
|
|
2105
|
+
* in sync: this process joined late (a restart, an eviction), and `reseed`
|
|
2106
|
+
* will fix it. `inFlight` covers this process's own writes that the server
|
|
2107
|
+
* has counted and the entry has not appended yet, so two concurrent writes
|
|
2108
|
+
* do not look like a second writer.
|
|
2109
|
+
*/
|
|
2110
|
+
#noteCount(entry, count, inFlight) {
|
|
2111
|
+
entry.participant = true;
|
|
2112
|
+
if (count > localMessageCount(entry) + inFlight) {
|
|
2113
|
+
if (entry.synced) entry.shared = true;
|
|
2114
|
+
} else if (entry.serverMessageCount === null) {
|
|
2115
|
+
entry.synced = true;
|
|
2116
|
+
}
|
|
2117
|
+
entry.serverMessageCount = Math.max(entry.serverMessageCount ?? 0, count);
|
|
2118
|
+
}
|
|
1789
2119
|
/**
|
|
1790
2120
|
* Apply a `compaction_update` bundle.
|
|
1791
2121
|
*
|
|
1792
2122
|
* Replaces the summary fields from the bundle's `conversation_context` and
|
|
1793
|
-
*
|
|
1794
|
-
*
|
|
1795
|
-
*
|
|
2123
|
+
* drops the buffered turns the new summary covers.
|
|
2124
|
+
*
|
|
2125
|
+
* Which turns those are is decided by count, not by clock. The bundle says
|
|
2126
|
+
* how many messages the summary covers and how many the conversation held
|
|
2127
|
+
* when the bundle was built, so the last `total - compacted` of its
|
|
2128
|
+
* `recent_turns` are exactly the uncovered messages. Turns this process
|
|
2129
|
+
* appended after the server took that count are kept behind them. Timestamps
|
|
2130
|
+
* cannot do this job: the REST write returns the time of the response and
|
|
2131
|
+
* not of the stored row, a caller may supply its own message time, and a
|
|
2132
|
+
* streamed turn is stamped when a background write runs.
|
|
2133
|
+
*
|
|
2134
|
+
* A bundle from a server that sends no counts falls back to the older
|
|
2135
|
+
* timestamp prune, and the entry is left out of sync so it is never used to
|
|
2136
|
+
* answer on its own.
|
|
1796
2137
|
*/
|
|
1797
2138
|
applyCompaction(bundle) {
|
|
1798
2139
|
if (typeof bundle !== "object" || bundle === null) return;
|
|
@@ -1802,22 +2143,84 @@ var ShortTermStore = class {
|
|
|
1802
2143
|
);
|
|
1803
2144
|
if (!conversationId) return;
|
|
1804
2145
|
const endTs = parseIso(cc["end_timestamp"] ?? cc["compacted_at"]);
|
|
2146
|
+
const compactedAt = parseIso(cc["compacted_at"]);
|
|
2147
|
+
const compactedCount = Number(cc["compacted_message_count"] ?? 0) || 0;
|
|
2148
|
+
const totalCount = Number(cc["total_message_count"] ?? 0) || 0;
|
|
1805
2149
|
const entry = this.#entries.get(conversationId) ?? this.#blank(conversationId);
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
2150
|
+
const heldAt = parseIso(entry.compactedAt);
|
|
2151
|
+
if (compactedAt !== null && heldAt !== null && compactedAt < heldAt) return;
|
|
2152
|
+
const block = contextBlockOf(cc);
|
|
2153
|
+
if (block) {
|
|
2154
|
+
entry.contextBlock = block;
|
|
2155
|
+
entry.summary = cc["summary"] || block;
|
|
2156
|
+
entry.factualParagraph = null;
|
|
2157
|
+
entry.conversationalParagraph = null;
|
|
2158
|
+
} else {
|
|
2159
|
+
entry.summary = cc["summary"] || entry.summary;
|
|
2160
|
+
entry.factualParagraph = cc["factual_paragraph"] || entry.factualParagraph;
|
|
2161
|
+
entry.conversationalParagraph = cc["conversational_paragraph"] || entry.conversationalParagraph;
|
|
2162
|
+
}
|
|
1809
2163
|
entry.currentState = { ...cc["current_state"] ?? {} };
|
|
1810
2164
|
entry.keyExtractions = { ...cc["key_extractions"] ?? {} };
|
|
1811
2165
|
entry.compactionId = cc["compaction_id"] || entry.compactionId;
|
|
1812
2166
|
entry.compactedAt = cc["compacted_at"] || entry.compactedAt;
|
|
1813
2167
|
entry.endTimestamp = cc["end_timestamp"] || entry.endTimestamp;
|
|
1814
|
-
if (
|
|
1815
|
-
|
|
2168
|
+
if (compactedCount > 0 && compactedCount <= totalCount) {
|
|
2169
|
+
const uncovered = totalCount - compactedCount;
|
|
2170
|
+
const serverTurns = Array.isArray(cc["recent_turns"]) ? cc["recent_turns"] : [];
|
|
2171
|
+
const tail = uncovered > 0 ? serverTurns.slice(-uncovered).map((t) => ({ ...t })) : [];
|
|
2172
|
+
const ahead = localMessageCount(entry) - totalCount;
|
|
2173
|
+
const newer = entry.synced && ahead > 0 ? entry.recentTurns.slice(-ahead).filter((t) => !heldIn(t, tail)) : [];
|
|
2174
|
+
entry.recentTurns = [...tail, ...newer];
|
|
2175
|
+
entry.compactedMessageCount = compactedCount;
|
|
2176
|
+
entry.serverMessageCount = Math.max(entry.serverMessageCount ?? 0, totalCount);
|
|
2177
|
+
entry.synced = tail.length === uncovered;
|
|
2178
|
+
} else {
|
|
2179
|
+
if (endTs !== null && entry.recentTurns.length > 0) {
|
|
2180
|
+
entry.recentTurns = entry.recentTurns.filter((t) => isAfter(t.timestamp, endTs));
|
|
2181
|
+
}
|
|
2182
|
+
entry.synced = false;
|
|
1816
2183
|
}
|
|
1817
2184
|
entry.lastActivityAt = this.#now();
|
|
1818
2185
|
this.#touch(conversationId, entry);
|
|
1819
2186
|
this.#evict();
|
|
1820
2187
|
}
|
|
2188
|
+
/**
|
|
2189
|
+
* Bring an entry back in step from a `/context-for-prompt` response.
|
|
2190
|
+
*
|
|
2191
|
+
* Called after the server was asked because the entry was not complete. The
|
|
2192
|
+
* response lists every message the server's summary does not cover, which is
|
|
2193
|
+
* what `recentTurns` is meant to hold.
|
|
2194
|
+
*
|
|
2195
|
+
* Only an entry that already exists is reseeded. Creating one here would let
|
|
2196
|
+
* a process that never writes to the conversation answer from a snapshot it
|
|
2197
|
+
* has no way to refresh.
|
|
2198
|
+
*
|
|
2199
|
+
* If the server's summary is not the one cached here, the entry cannot
|
|
2200
|
+
* render it, so it stays out of sync until the next bundle brings the
|
|
2201
|
+
* summary itself.
|
|
2202
|
+
*
|
|
2203
|
+
* Turns held locally and absent from the response are kept when they come
|
|
2204
|
+
* after everything the response has: they were written or sent after the
|
|
2205
|
+
* server read its messages.
|
|
2206
|
+
*/
|
|
2207
|
+
reseed(conversationId, state) {
|
|
2208
|
+
if (!conversationId) return;
|
|
2209
|
+
const entry = this.#entries.get(conversationId);
|
|
2210
|
+
if (entry === void 0 || entry.shared) return;
|
|
2211
|
+
entry.serverMessageCount = Math.max(
|
|
2212
|
+
entry.serverMessageCount ?? 0,
|
|
2213
|
+
Number(state.totalMessageCount) || 0
|
|
2214
|
+
);
|
|
2215
|
+
if ((state.compactionId || null) !== entry.compactionId) {
|
|
2216
|
+
entry.synced = false;
|
|
2217
|
+
return;
|
|
2218
|
+
}
|
|
2219
|
+
const serverTurns = state.recentMessages.map((m) => ({ ...m }));
|
|
2220
|
+
entry.recentTurns = [...serverTurns, ...turnsAfter(entry.recentTurns, serverTurns)];
|
|
2221
|
+
entry.compactedMessageCount = Number(state.compactedMessageCount) || 0;
|
|
2222
|
+
entry.synced = true;
|
|
2223
|
+
}
|
|
1821
2224
|
invalidate(conversationId) {
|
|
1822
2225
|
this.#entries.delete(conversationId);
|
|
1823
2226
|
}
|
|
@@ -1828,6 +2231,7 @@ var ShortTermStore = class {
|
|
|
1828
2231
|
return {
|
|
1829
2232
|
conversationId,
|
|
1830
2233
|
summary: null,
|
|
2234
|
+
contextBlock: null,
|
|
1831
2235
|
factualParagraph: null,
|
|
1832
2236
|
conversationalParagraph: null,
|
|
1833
2237
|
currentState: {},
|
|
@@ -1836,7 +2240,12 @@ var ShortTermStore = class {
|
|
|
1836
2240
|
compactedAt: null,
|
|
1837
2241
|
endTimestamp: null,
|
|
1838
2242
|
recentTurns: [],
|
|
1839
|
-
lastActivityAt: this.#now()
|
|
2243
|
+
lastActivityAt: this.#now(),
|
|
2244
|
+
compactedMessageCount: 0,
|
|
2245
|
+
serverMessageCount: null,
|
|
2246
|
+
participant: false,
|
|
2247
|
+
synced: false,
|
|
2248
|
+
shared: false
|
|
1840
2249
|
};
|
|
1841
2250
|
}
|
|
1842
2251
|
/** Re-insert so this key becomes the most recently used. */
|
|
@@ -1853,6 +2262,520 @@ var ShortTermStore = class {
|
|
|
1853
2262
|
}
|
|
1854
2263
|
};
|
|
1855
2264
|
|
|
2265
|
+
// src/cache/turn-store.ts
|
|
2266
|
+
var SWEEP_EVERY_WRITES = 50;
|
|
2267
|
+
function bytesOf(text) {
|
|
2268
|
+
return new TextEncoder().encode(text).length;
|
|
2269
|
+
}
|
|
2270
|
+
var TurnStore = class {
|
|
2271
|
+
limits;
|
|
2272
|
+
onDisk = false;
|
|
2273
|
+
#now;
|
|
2274
|
+
#seq = 0;
|
|
2275
|
+
#writes = 0;
|
|
2276
|
+
#conversations = /* @__PURE__ */ new Map();
|
|
2277
|
+
#vectors = /* @__PURE__ */ new Map();
|
|
2278
|
+
constructor(options = {}) {
|
|
2279
|
+
this.limits = options.limits ?? { ...HISTORY_SEARCH.store_limits };
|
|
2280
|
+
this.#now = options.now ?? (() => Date.now() / 1e3);
|
|
2281
|
+
}
|
|
2282
|
+
add(conversationId, role, content, timestamp, options = {}) {
|
|
2283
|
+
this.addMany(conversationId, [
|
|
2284
|
+
{ role, content, timestamp: timestamp ?? "", message_id: options.messageId ?? null, position: options.position ?? null }
|
|
2285
|
+
]);
|
|
2286
|
+
}
|
|
2287
|
+
/**
|
|
2288
|
+
* Insert turns not held yet; returns how many were new. A turn held without
|
|
2289
|
+
* an id or position (a streamed write) gets them when the same role and
|
|
2290
|
+
* text come back from the server.
|
|
2291
|
+
*/
|
|
2292
|
+
addMany(conversationId, turns) {
|
|
2293
|
+
if (!conversationId) return 0;
|
|
2294
|
+
const conv = this.#conversations.get(conversationId) ?? { lastActivity: 0, turns: [] };
|
|
2295
|
+
let added = 0;
|
|
2296
|
+
for (const turn of turns) {
|
|
2297
|
+
const role = String(turn.role || "user");
|
|
2298
|
+
const content = String(turn.content ?? "");
|
|
2299
|
+
if (!content) continue;
|
|
2300
|
+
const messageId = turn.message_id ? String(turn.message_id) : null;
|
|
2301
|
+
const raw = turn.position;
|
|
2302
|
+
const position = typeof raw === "number" && Number.isInteger(raw) ? raw : /^\d+$/.test(String(raw ?? "")) ? Number(raw) : null;
|
|
2303
|
+
const ts = turn.timestamp instanceof Date ? turn.timestamp.toISOString() : String(turn.timestamp ?? "");
|
|
2304
|
+
if (messageId !== null) {
|
|
2305
|
+
const held = conv.turns.find((t) => t.message_id === messageId);
|
|
2306
|
+
if (held !== void 0) {
|
|
2307
|
+
if (position !== null) held.position = position;
|
|
2308
|
+
continue;
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
const same = conv.turns.find(
|
|
2312
|
+
(t) => t.role === role && t.content === content && (t.message_id === null || t.position === null)
|
|
2313
|
+
);
|
|
2314
|
+
if (same !== void 0) {
|
|
2315
|
+
same.message_id = same.message_id ?? messageId;
|
|
2316
|
+
same.position = position ?? same.position;
|
|
2317
|
+
continue;
|
|
2318
|
+
}
|
|
2319
|
+
conv.turns.push({ seq: ++this.#seq, role, content, timestamp: ts, message_id: messageId, position });
|
|
2320
|
+
added += 1;
|
|
2321
|
+
}
|
|
2322
|
+
conv.lastActivity = this.#now();
|
|
2323
|
+
this.#conversations.set(conversationId, conv);
|
|
2324
|
+
this.#trim(conv);
|
|
2325
|
+
this.#writes += 1;
|
|
2326
|
+
if (this.#writes % SWEEP_EVERY_WRITES === 0) this.sweep();
|
|
2327
|
+
return added;
|
|
2328
|
+
}
|
|
2329
|
+
#trim(conv) {
|
|
2330
|
+
const limits = this.limits;
|
|
2331
|
+
let size = conv.turns.reduce((n2, t) => n2 + bytesOf(t.content), 0);
|
|
2332
|
+
while (conv.turns.length > limits.max_turns_per_conversation || size > limits.max_bytes_per_conversation) {
|
|
2333
|
+
const excess = Math.max(conv.turns.length - limits.max_turns_per_conversation, 1);
|
|
2334
|
+
const order = [...conv.turns].sort(
|
|
2335
|
+
(a, b) => (a.position ?? Number.MAX_SAFE_INTEGER) - (b.position ?? Number.MAX_SAFE_INTEGER) || a.seq - b.seq
|
|
2336
|
+
);
|
|
2337
|
+
const gone = new Set(order.slice(0, excess).map((t) => t.seq));
|
|
2338
|
+
if (gone.size === 0) break;
|
|
2339
|
+
for (const t of conv.turns) if (gone.has(t.seq)) size -= bytesOf(t.content);
|
|
2340
|
+
conv.turns = conv.turns.filter((t) => !gone.has(t.seq));
|
|
2341
|
+
for (const vectors of this.#vectors.values()) for (const seq of gone) vectors.delete(seq);
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
/** Idle conversations, then the least recently used beyond the count, then more until the bytes fit. */
|
|
2345
|
+
sweep() {
|
|
2346
|
+
const limits = this.limits;
|
|
2347
|
+
const now = this.#now();
|
|
2348
|
+
for (const [id, conv] of this.#conversations) {
|
|
2349
|
+
if (conv.lastActivity < now - limits.idle_ttl_seconds) this.drop(id);
|
|
2350
|
+
}
|
|
2351
|
+
const byRecent = [...this.#conversations.entries()].sort((a, b) => b[1].lastActivity - a[1].lastActivity);
|
|
2352
|
+
for (const [id] of byRecent.slice(limits.max_conversations)) this.drop(id);
|
|
2353
|
+
while (this.sizeBytes() > limits.max_file_bytes) {
|
|
2354
|
+
const oldest = this.conversations()[0];
|
|
2355
|
+
if (oldest === void 0) break;
|
|
2356
|
+
this.drop(oldest);
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
drop(conversationId) {
|
|
2360
|
+
const conv = this.#conversations.get(conversationId);
|
|
2361
|
+
if (conv === void 0) return;
|
|
2362
|
+
for (const vectors of this.#vectors.values()) for (const t of conv.turns) vectors.delete(t.seq);
|
|
2363
|
+
this.#conversations.delete(conversationId);
|
|
2364
|
+
}
|
|
2365
|
+
clear() {
|
|
2366
|
+
this.#conversations.clear();
|
|
2367
|
+
this.#vectors.clear();
|
|
2368
|
+
}
|
|
2369
|
+
close() {
|
|
2370
|
+
}
|
|
2371
|
+
/** In conversation order: by server position where known, else as written here. */
|
|
2372
|
+
turns(conversationId) {
|
|
2373
|
+
const conv = this.#conversations.get(conversationId);
|
|
2374
|
+
if (conv === void 0) return [];
|
|
2375
|
+
const bySeq = [...conv.turns].sort((a, b) => a.seq - b.seq);
|
|
2376
|
+
const key = /* @__PURE__ */ new Map();
|
|
2377
|
+
let top = 0;
|
|
2378
|
+
let step = 0;
|
|
2379
|
+
for (const t of bySeq) {
|
|
2380
|
+
if (t.position !== null) {
|
|
2381
|
+
key.set(t.seq, t.position);
|
|
2382
|
+
top = Math.max(top, t.position);
|
|
2383
|
+
step = 0;
|
|
2384
|
+
} else {
|
|
2385
|
+
step += 1;
|
|
2386
|
+
key.set(t.seq, top + step * 1e-6);
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
return bySeq.sort((a, b) => (key.get(a.seq) ?? 0) - (key.get(b.seq) ?? 0) || a.seq - b.seq).map((t) => ({ ...t }));
|
|
2390
|
+
}
|
|
2391
|
+
/** Turns the server also holds: archived tool results are local only. Python: `count`. */
|
|
2392
|
+
count(conversationId) {
|
|
2393
|
+
return (this.#conversations.get(conversationId)?.turns ?? []).filter((t) => t.role !== "tool").length;
|
|
2394
|
+
}
|
|
2395
|
+
sizeBytes() {
|
|
2396
|
+
let n2 = 0;
|
|
2397
|
+
for (const conv of this.#conversations.values()) for (const t of conv.turns) n2 += bytesOf(t.content);
|
|
2398
|
+
return n2;
|
|
2399
|
+
}
|
|
2400
|
+
/** Conversation ids, least recently active first. */
|
|
2401
|
+
conversations() {
|
|
2402
|
+
return [...this.#conversations.entries()].sort((a, b) => a[1].lastActivity - b[1].lastActivity).map(([id]) => id);
|
|
2403
|
+
}
|
|
2404
|
+
vectors(seqs, model) {
|
|
2405
|
+
const held = this.#vectors.get(model);
|
|
2406
|
+
const out = /* @__PURE__ */ new Map();
|
|
2407
|
+
if (held === void 0) return out;
|
|
2408
|
+
for (const seq of seqs) {
|
|
2409
|
+
const v = held.get(seq);
|
|
2410
|
+
if (v !== void 0) out.set(seq, v);
|
|
2411
|
+
}
|
|
2412
|
+
return out;
|
|
2413
|
+
}
|
|
2414
|
+
putVectors(model, items) {
|
|
2415
|
+
const held = this.#vectors.get(model) ?? /* @__PURE__ */ new Map();
|
|
2416
|
+
for (const [seq, vec] of items) held.set(seq, vec);
|
|
2417
|
+
this.#vectors.set(model, held);
|
|
2418
|
+
}
|
|
2419
|
+
};
|
|
2420
|
+
|
|
2421
|
+
// src/cache/history-search.ts
|
|
2422
|
+
var WORD_RE = /[\p{L}\p{N}\p{M}]+/gu;
|
|
2423
|
+
var FORMS = HISTORY_SEARCH.irregular_forms ?? {};
|
|
2424
|
+
function tokenize2(text) {
|
|
2425
|
+
const out = [];
|
|
2426
|
+
for (const word of (text || "").toLowerCase().match(WORD_RE) ?? []) {
|
|
2427
|
+
if (/^[\x00-\x7f]+$/.test(word)) out.push(...tokenize(FORMS[word] ?? word));
|
|
2428
|
+
else out.push(word);
|
|
2429
|
+
}
|
|
2430
|
+
return out;
|
|
2431
|
+
}
|
|
2432
|
+
var LITERAL_SOURCE = String.raw`"([^"]{3,})"|\b([A-Z0-9][A-Z0-9-]{3,})\b|(\d[\d,.:/-]*\d|\d)`;
|
|
2433
|
+
var SENTENCE_RE = /(?<=[.!?])\s+/;
|
|
2434
|
+
function literalsOf(query) {
|
|
2435
|
+
const out = [];
|
|
2436
|
+
for (const m of (query || "").matchAll(new RegExp(LITERAL_SOURCE, "g"))) {
|
|
2437
|
+
const quoted = m[1];
|
|
2438
|
+
const value = quoted || m[2] || m[3];
|
|
2439
|
+
if (value && (quoted || /\d/.test(value))) out.push(value.toLowerCase());
|
|
2440
|
+
}
|
|
2441
|
+
return out;
|
|
2442
|
+
}
|
|
2443
|
+
function cosine(a, b) {
|
|
2444
|
+
let dot = 0;
|
|
2445
|
+
let na = 0;
|
|
2446
|
+
let nb = 0;
|
|
2447
|
+
const n2 = Math.min(a.length, b.length);
|
|
2448
|
+
for (let i = 0; i < n2; i++) {
|
|
2449
|
+
dot += a[i] * b[i];
|
|
2450
|
+
na += a[i] * a[i];
|
|
2451
|
+
nb += b[i] * b[i];
|
|
2452
|
+
}
|
|
2453
|
+
return na && nb ? dot / (Math.sqrt(na) * Math.sqrt(nb)) : 0;
|
|
2454
|
+
}
|
|
2455
|
+
function words(text) {
|
|
2456
|
+
return text.split(/\s+/).filter(Boolean);
|
|
2457
|
+
}
|
|
2458
|
+
function excerptOf(content, terms, literals, maxWords) {
|
|
2459
|
+
let sentences = content.trim().split(SENTENCE_RE).filter((s2) => s2.trim());
|
|
2460
|
+
if (sentences.length === 0) sentences = [content.trim()];
|
|
2461
|
+
const score = (sentence) => {
|
|
2462
|
+
const low = sentence.toLowerCase();
|
|
2463
|
+
const held = new Set(tokenize2(sentence));
|
|
2464
|
+
let n2 = 0;
|
|
2465
|
+
for (const t of terms) if (held.has(t)) n2 += 1;
|
|
2466
|
+
for (const lit of literals) if (low.includes(lit)) n2 += 2;
|
|
2467
|
+
return n2;
|
|
2468
|
+
};
|
|
2469
|
+
let best = 0;
|
|
2470
|
+
for (let i = 1; i < sentences.length; i++) if (score(sentences[i]) > score(sentences[best])) best = i;
|
|
2471
|
+
let lo = best;
|
|
2472
|
+
let hi = best;
|
|
2473
|
+
let count = words(sentences[best]).length;
|
|
2474
|
+
for (; ; ) {
|
|
2475
|
+
let grew = false;
|
|
2476
|
+
for (const j of [hi + 1, lo - 1]) {
|
|
2477
|
+
if (j >= 0 && j < sentences.length && count + words(sentences[j]).length <= maxWords) {
|
|
2478
|
+
count += words(sentences[j]).length;
|
|
2479
|
+
lo = Math.min(lo, j);
|
|
2480
|
+
hi = Math.max(hi, j);
|
|
2481
|
+
grew = true;
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
if (!grew) break;
|
|
2485
|
+
}
|
|
2486
|
+
let text = sentences.slice(lo, hi + 1).join(" ");
|
|
2487
|
+
if (words(text).length > maxWords) text = words(text).slice(0, maxWords).join(" ") + " ...";
|
|
2488
|
+
return (lo > 0 ? "... " : "") + text + (hi < sentences.length - 1 && !text.endsWith("...") ? " ..." : "");
|
|
2489
|
+
}
|
|
2490
|
+
var FENCE = /^\s*(?:```|~~~)/;
|
|
2491
|
+
var TABLE_RULE = /^\s*\|?\s*:?-{2,}/;
|
|
2492
|
+
var SENTENCE = /(?<=[.!?])\s+/;
|
|
2493
|
+
function splitPassages(content, maxWords) {
|
|
2494
|
+
const text = (content ?? "").trim();
|
|
2495
|
+
if (words(text).length <= maxWords) return text ? [text] : [];
|
|
2496
|
+
const out = [];
|
|
2497
|
+
const lines = text.split("\n");
|
|
2498
|
+
let i = 0;
|
|
2499
|
+
while (i < lines.length) {
|
|
2500
|
+
const line = lines[i];
|
|
2501
|
+
if (FENCE.test(line)) {
|
|
2502
|
+
let j2 = i + 1;
|
|
2503
|
+
while (j2 < lines.length && !FENCE.test(lines[j2])) j2 += 1;
|
|
2504
|
+
out.push(...windowsOf(lines.slice(i + 1, j2), maxWords));
|
|
2505
|
+
i = j2 + 1;
|
|
2506
|
+
continue;
|
|
2507
|
+
}
|
|
2508
|
+
if (line.trim().startsWith("|")) {
|
|
2509
|
+
let j2 = i;
|
|
2510
|
+
while (j2 < lines.length && lines[j2].trim().startsWith("|")) j2 += 1;
|
|
2511
|
+
const rows = lines.slice(i, j2).filter((r) => !TABLE_RULE.test(r)).map((r) => r.trim());
|
|
2512
|
+
const [header, body] = rows.length > 1 ? [rows[0], rows.slice(1)] : ["", rows];
|
|
2513
|
+
for (const row of body) {
|
|
2514
|
+
const both = `${header} ${row}`.trim();
|
|
2515
|
+
out.push(words(both).length <= Math.trunc(maxWords * 3 / 2) ? both : row);
|
|
2516
|
+
}
|
|
2517
|
+
i = j2;
|
|
2518
|
+
continue;
|
|
2519
|
+
}
|
|
2520
|
+
let j = i;
|
|
2521
|
+
while (j < lines.length && lines[j].trim() && !FENCE.test(lines[j]) && !lines[j].trim().startsWith("|")) j += 1;
|
|
2522
|
+
if (j === i) {
|
|
2523
|
+
i += 1;
|
|
2524
|
+
continue;
|
|
2525
|
+
}
|
|
2526
|
+
const block = lines.slice(i, j);
|
|
2527
|
+
if (block.length > 1 && block.every((b) => words(b).length <= 25)) {
|
|
2528
|
+
out.push(...windowsOf(block, maxWords));
|
|
2529
|
+
} else {
|
|
2530
|
+
const sentences = block.map((b) => b.trim()).join(" ").split(SENTENCE).filter((x) => x.trim());
|
|
2531
|
+
out.push(...windowsOf(sentences, maxWords));
|
|
2532
|
+
}
|
|
2533
|
+
i = j;
|
|
2534
|
+
}
|
|
2535
|
+
const kept = out.filter((x) => x.trim());
|
|
2536
|
+
return kept.length ? kept : [text];
|
|
2537
|
+
}
|
|
2538
|
+
function windowsOf(pieces, maxWords) {
|
|
2539
|
+
const out = [];
|
|
2540
|
+
let cur = [];
|
|
2541
|
+
let used = 0;
|
|
2542
|
+
for (const raw of pieces) {
|
|
2543
|
+
const piece = raw.trim();
|
|
2544
|
+
if (!piece) continue;
|
|
2545
|
+
const n2 = words(piece).length;
|
|
2546
|
+
if (cur.length && used + n2 > maxWords) {
|
|
2547
|
+
out.push(cur.join(" "));
|
|
2548
|
+
cur = [];
|
|
2549
|
+
used = 0;
|
|
2550
|
+
}
|
|
2551
|
+
if (n2 > maxWords) {
|
|
2552
|
+
const w = words(piece);
|
|
2553
|
+
for (let k = 0; k < w.length; k += maxWords) out.push(w.slice(k, k + maxWords).join(" "));
|
|
2554
|
+
continue;
|
|
2555
|
+
}
|
|
2556
|
+
cur.push(piece);
|
|
2557
|
+
used += n2;
|
|
2558
|
+
}
|
|
2559
|
+
if (cur.length) out.push(cur.join(" "));
|
|
2560
|
+
return out;
|
|
2561
|
+
}
|
|
2562
|
+
function askedKinds(query) {
|
|
2563
|
+
const out = [];
|
|
2564
|
+
for (const [name, spec] of Object.entries(HISTORY_SEARCH.asked_kinds ?? {})) {
|
|
2565
|
+
if (name.startsWith("$") || typeof spec !== "object") continue;
|
|
2566
|
+
if (new RegExp(spec.query, "i").test(query ?? "")) out.push(new RegExp(spec.value));
|
|
2567
|
+
}
|
|
2568
|
+
return out;
|
|
2569
|
+
}
|
|
2570
|
+
function asksHowItStarted(query) {
|
|
2571
|
+
const spec = HISTORY_SEARCH.early_query;
|
|
2572
|
+
if (!spec?.pattern) return false;
|
|
2573
|
+
return new RegExp(spec.pattern, (spec.flags ?? "").includes("i") ? "i" : "").test(query ?? "");
|
|
2574
|
+
}
|
|
2575
|
+
async function searchTurns(turns, query, options = {}) {
|
|
2576
|
+
const params = HISTORY_SEARCH;
|
|
2577
|
+
const k = params.max_excerpts;
|
|
2578
|
+
const skip = new Set(options.exclude ?? []);
|
|
2579
|
+
const pool = turns.filter((t) => t.content && !skip.has(t.content) && !skip.has(t.message_id ?? ""));
|
|
2580
|
+
const terms = new Set(tokenize2(query || ""));
|
|
2581
|
+
const literals = literalsOf(query);
|
|
2582
|
+
if (pool.length === 0 || terms.size === 0 && literals.length === 0 && !options.encoder) return [];
|
|
2583
|
+
const maxWords = params.max_words_per_excerpt;
|
|
2584
|
+
const units = [];
|
|
2585
|
+
pool.forEach((t, ti) => {
|
|
2586
|
+
if (!params.passages) {
|
|
2587
|
+
units.push({ seq: t.seq, content: t.content, turn: ti, first: true, last: true, part: -1 });
|
|
2588
|
+
return;
|
|
2589
|
+
}
|
|
2590
|
+
const parts = splitPassages(t.content, maxWords);
|
|
2591
|
+
parts.forEach((part, pi) => {
|
|
2592
|
+
units.push({
|
|
2593
|
+
seq: t.seq,
|
|
2594
|
+
content: part,
|
|
2595
|
+
turn: ti,
|
|
2596
|
+
first: pi === 0,
|
|
2597
|
+
last: pi === parts.length - 1,
|
|
2598
|
+
part: parts.length > 1 ? pi : -1
|
|
2599
|
+
});
|
|
2600
|
+
});
|
|
2601
|
+
});
|
|
2602
|
+
const docs = units.map((u) => tokenize2(u.content));
|
|
2603
|
+
const bm = new BM25(docs);
|
|
2604
|
+
const termList = [...terms];
|
|
2605
|
+
const rows = units.map((turn, i) => {
|
|
2606
|
+
const held = new Set(docs[i]);
|
|
2607
|
+
let matched = 0;
|
|
2608
|
+
for (const t of terms) if (held.has(t)) matched += 1;
|
|
2609
|
+
const low = turn.content.toLowerCase();
|
|
2610
|
+
let literal = 0;
|
|
2611
|
+
for (const lit of literals) if (low.includes(lit)) literal += 1;
|
|
2612
|
+
return { i, bm25: matched ? bm.score(termList, i) : 0, matched, literal };
|
|
2613
|
+
});
|
|
2614
|
+
const need = Math.min(params.min_matched_terms, Math.max(1, terms.size));
|
|
2615
|
+
const rareCap = Math.max(1, Math.trunc((params.rare_term_max_share ?? 0) * units.length));
|
|
2616
|
+
const rare = /* @__PURE__ */ new Set();
|
|
2617
|
+
for (const t of terms) {
|
|
2618
|
+
let n3 = 0;
|
|
2619
|
+
for (const doc of docs) if (doc.includes(t)) n3 += 1;
|
|
2620
|
+
if (n3 > 0 && n3 <= rareCap) rare.add(t);
|
|
2621
|
+
}
|
|
2622
|
+
let eligible;
|
|
2623
|
+
if (params.passages) {
|
|
2624
|
+
const turnTerms = /* @__PURE__ */ new Map();
|
|
2625
|
+
for (const r of rows) {
|
|
2626
|
+
const held = turnTerms.get(units[r.i].turn) ?? /* @__PURE__ */ new Set();
|
|
2627
|
+
for (const t of docs[r.i]) if (terms.has(t)) held.add(t);
|
|
2628
|
+
turnTerms.set(units[r.i].turn, held);
|
|
2629
|
+
}
|
|
2630
|
+
eligible = rows.filter(
|
|
2631
|
+
(r) => r.literal > 0 || r.matched > 0 && (turnTerms.get(units[r.i].turn).size >= need || docs[r.i].some((t) => rare.has(t)))
|
|
2632
|
+
);
|
|
2633
|
+
} else {
|
|
2634
|
+
eligible = rows.filter(
|
|
2635
|
+
(r) => r.literal > 0 || r.matched >= need || r.matched > 0 && docs[r.i].some((t) => rare.has(t))
|
|
2636
|
+
);
|
|
2637
|
+
}
|
|
2638
|
+
const rrf = params.rrf_k;
|
|
2639
|
+
const fused = /* @__PURE__ */ new Map();
|
|
2640
|
+
const via = /* @__PURE__ */ new Map();
|
|
2641
|
+
for (const r of eligible) fused.set(r.i, 0);
|
|
2642
|
+
const add = (i, rank, how) => {
|
|
2643
|
+
fused.set(i, (fused.get(i) ?? 0) + 1 / (rrf + rank + 1));
|
|
2644
|
+
if (!via.has(i)) via.set(i, how);
|
|
2645
|
+
};
|
|
2646
|
+
eligible.filter((r) => r.literal > 0).sort((a, b) => b.literal - a.literal || a.i - b.i).forEach((r, rank) => add(r.i, rank, "literal"));
|
|
2647
|
+
eligible.filter((r) => r.bm25 > 0).sort((a, b) => b.bm25 - a.bm25 || a.i - b.i).forEach((r, rank) => add(r.i, rank, "bm25"));
|
|
2648
|
+
const kinds = askedKinds(query);
|
|
2649
|
+
if (kinds.length) {
|
|
2650
|
+
rows.filter((r) => r.matched > 0 && kinds.some((k2) => k2.test(units[r.i].content))).sort((a, b) => b.bm25 - a.bm25 || a.i - b.i).forEach((r, rank) => add(r.i, rank, "kind"));
|
|
2651
|
+
}
|
|
2652
|
+
if (options.encoder) {
|
|
2653
|
+
const ranked2 = await embeddingRanks(units, query, options.encoder, options.store ?? null, params.min_cosine ?? 0.4);
|
|
2654
|
+
ranked2.forEach(([i], rank) => add(i, rank, "embedding"));
|
|
2655
|
+
}
|
|
2656
|
+
if (fused.size === 0) return [];
|
|
2657
|
+
const n2 = Math.max(pool.length - 1, 1);
|
|
2658
|
+
const weight = asksHowItStarted(query) ? 0 : params.recency_weight;
|
|
2659
|
+
const top = /* @__PURE__ */ new Map();
|
|
2660
|
+
const ranked = /* @__PURE__ */ new Map();
|
|
2661
|
+
for (const [u, v] of fused) {
|
|
2662
|
+
const ti = units[u].turn;
|
|
2663
|
+
const score = v + weight * (ti / n2) / (rrf + 1);
|
|
2664
|
+
ranked.set(ti, [...ranked.get(ti) ?? [], [score, u]]);
|
|
2665
|
+
const held = top.get(ti);
|
|
2666
|
+
if (!held || score > held[0]) top.set(ti, [score, u]);
|
|
2667
|
+
}
|
|
2668
|
+
const best = [...top.keys()].sort((a, b) => top.get(b)[0] - top.get(a)[0] || a - b).slice(0, k);
|
|
2669
|
+
const perTurn = params.passages_per_turn ?? 1;
|
|
2670
|
+
const shown = (ti) => {
|
|
2671
|
+
const unit = units[top.get(ti)[1]];
|
|
2672
|
+
if (!params.passages || unit.first && unit.last) return excerptOf(pool[ti].content, terms, literals, maxWords);
|
|
2673
|
+
const picked = [...ranked.get(ti)].sort((a, b) => b[0] - a[0] || b[1] - a[1]).slice(0, perTurn).map(([, u]) => u).sort((a, b) => a - b);
|
|
2674
|
+
const parts = [];
|
|
2675
|
+
for (const u of picked) parts.push((units[u].first || parts.length ? "" : "... ") + units[u].content);
|
|
2676
|
+
return parts.join(" ... ") + (units[picked[picked.length - 1]].last ? "" : " ...");
|
|
2677
|
+
};
|
|
2678
|
+
const found = /* @__PURE__ */ new Map();
|
|
2679
|
+
for (const ti of best) {
|
|
2680
|
+
found.set(ti, {
|
|
2681
|
+
position: pool[ti].position,
|
|
2682
|
+
role: pool[ti].role,
|
|
2683
|
+
timestamp: pool[ti].timestamp,
|
|
2684
|
+
score: top.get(ti)[0],
|
|
2685
|
+
via: via.get(top.get(ti)[1]) ?? "",
|
|
2686
|
+
text: shown(ti)
|
|
2687
|
+
});
|
|
2688
|
+
}
|
|
2689
|
+
const before2 = params.previous_turns ?? 0;
|
|
2690
|
+
const tail = params.previous_turn_words ?? 40;
|
|
2691
|
+
for (const i of best) {
|
|
2692
|
+
for (let j = i - before2; j < i; j++) {
|
|
2693
|
+
if (j < 0 || found.has(j)) continue;
|
|
2694
|
+
const hit = pool[i].position;
|
|
2695
|
+
const prev = pool[j].position;
|
|
2696
|
+
if (hit && prev && hit - prev > before2) continue;
|
|
2697
|
+
const w = words(pool[j].content);
|
|
2698
|
+
found.set(j, {
|
|
2699
|
+
position: prev,
|
|
2700
|
+
role: pool[j].role,
|
|
2701
|
+
timestamp: pool[j].timestamp,
|
|
2702
|
+
score: 0,
|
|
2703
|
+
via: "previous",
|
|
2704
|
+
text: w.length <= tail ? w.join(" ") : "... " + w.slice(-tail).join(" ")
|
|
2705
|
+
});
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
return [...found.keys()].sort((a, b) => a - b).map((i) => found.get(i));
|
|
2709
|
+
}
|
|
2710
|
+
async function embeddingRanks(pool, query, encoder, store, minCosine) {
|
|
2711
|
+
const keyOf = (u) => (u.part ?? -1) < 0 ? encoder.model : `${encoder.model}#p${u.part}`;
|
|
2712
|
+
const vectors = /* @__PURE__ */ new Map();
|
|
2713
|
+
if (store !== null) {
|
|
2714
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
2715
|
+
pool.forEach((u, i) => byKey.set(keyOf(u), [...byKey.get(keyOf(u)) ?? [], i]));
|
|
2716
|
+
for (const [key, idx] of byKey) {
|
|
2717
|
+
const held = store.vectors(idx.map((i) => pool[i].seq), key);
|
|
2718
|
+
for (const i of idx) {
|
|
2719
|
+
const v = held.get(pool[i].seq);
|
|
2720
|
+
if (v) vectors.set(i, v);
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
const missing = pool.map((_, i) => i).filter((i) => !vectors.has(i));
|
|
2725
|
+
if (missing.length > 0) {
|
|
2726
|
+
const fresh = await encoder.encode(missing.map((i) => pool[i].content));
|
|
2727
|
+
const put = /* @__PURE__ */ new Map();
|
|
2728
|
+
missing.forEach((i, j) => {
|
|
2729
|
+
const v = Float32Array.from(fresh[j] ?? []);
|
|
2730
|
+
vectors.set(i, v);
|
|
2731
|
+
const key = keyOf(pool[i]);
|
|
2732
|
+
put.set(key, (put.get(key) ?? /* @__PURE__ */ new Map()).set(pool[i].seq, v));
|
|
2733
|
+
});
|
|
2734
|
+
if (store) for (const [key, items] of put) store.putVectors(key, items);
|
|
2735
|
+
}
|
|
2736
|
+
const q = (await (encoder.encodeQuery ?? encoder.encode).call(encoder, [query]))[0] ?? [];
|
|
2737
|
+
return pool.map((_, i) => [i, cosine(q, vectors.get(i) ?? [])]).filter(([, c]) => c >= minCosine).sort((a, b) => b[1] - a[1]);
|
|
2738
|
+
}
|
|
2739
|
+
function toolResultText(result, toolName) {
|
|
2740
|
+
let text = typeof result === "string" ? result : JSON.stringify(result) ?? String(result);
|
|
2741
|
+
const limit = HISTORY_SEARCH.tool_result_max_chars ?? 16e3;
|
|
2742
|
+
if (text.length > limit) text = text.slice(0, limit) + " ...[cut]";
|
|
2743
|
+
return `[tool result: ${toolName || "tool"}] ${text}`;
|
|
2744
|
+
}
|
|
2745
|
+
function renderExcerpts(excerpts, latest = "") {
|
|
2746
|
+
if (excerpts.length === 0) return "";
|
|
2747
|
+
const lines = [HISTORY_SEARCH.heading];
|
|
2748
|
+
for (const e of excerpts) {
|
|
2749
|
+
let where = e.position ? `turn ${e.position}, ${e.role}` : `${e.role}, earlier`;
|
|
2750
|
+
if (e.timestamp) {
|
|
2751
|
+
const ago = before(e.timestamp, latest);
|
|
2752
|
+
where += `, ${e.timestamp}` + (ago ? ` (${ago})` : "");
|
|
2753
|
+
}
|
|
2754
|
+
lines.push(`- ${where}: "${e.text}"`);
|
|
2755
|
+
}
|
|
2756
|
+
return lines.join("\n");
|
|
2757
|
+
}
|
|
2758
|
+
var ZONED = /(?:Z|[+-]\d{2}:\d{2})$/;
|
|
2759
|
+
function instant(stamp) {
|
|
2760
|
+
const text = (stamp ?? "").trim();
|
|
2761
|
+
if (!ZONED.test(text)) return null;
|
|
2762
|
+
const ms = Date.parse(text);
|
|
2763
|
+
return Number.isNaN(ms) ? null : ms / 1e3;
|
|
2764
|
+
}
|
|
2765
|
+
function before(stamp, latest) {
|
|
2766
|
+
const at = instant(stamp);
|
|
2767
|
+
const now = instant(latest);
|
|
2768
|
+
if (at === null || now === null || now < at) return "";
|
|
2769
|
+
const seconds = Math.trunc(now - at);
|
|
2770
|
+
for (const [unit, size] of [["day", 86400], ["hour", 3600], ["minute", 60]]) {
|
|
2771
|
+
if (seconds >= size) {
|
|
2772
|
+
const n2 = Math.trunc(seconds / size);
|
|
2773
|
+
return `${n2} ${unit}${n2 !== 1 ? "s" : ""} before the latest message`;
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
return "less than a minute before the latest message";
|
|
2777
|
+
}
|
|
2778
|
+
|
|
1856
2779
|
// src/context/overlay.ts
|
|
1857
2780
|
var MAX_TURNS = 20;
|
|
1858
2781
|
var TOKEN_BUDGET = 2e3;
|
|
@@ -1881,7 +2804,23 @@ function overlayLocalRecentTurns(response, store, conversationId, configured) {
|
|
|
1881
2804
|
if (entry === null) return;
|
|
1882
2805
|
const existing = response.conversation_context ?? null;
|
|
1883
2806
|
if (existing !== null && !verbatimOverlayEnabled(configured)) return;
|
|
1884
|
-
const
|
|
2807
|
+
const serverCompaction = existing === null ? null : existing["compaction_id"];
|
|
2808
|
+
const localIsWhole = isComplete(entry) && (!serverCompaction || serverCompaction === entry.compactionId);
|
|
2809
|
+
let sourceTurns = entry.recentTurns;
|
|
2810
|
+
if (existing !== null && !localIsWhole) {
|
|
2811
|
+
const raw = Array.isArray(existing["recent_turns"]) ? existing["recent_turns"] : [];
|
|
2812
|
+
const serverTurns = raw.map((t) => ({
|
|
2813
|
+
role: String(t["role"] ?? "user"),
|
|
2814
|
+
content: String(t["content"] ?? ""),
|
|
2815
|
+
timestamp: String(t["timestamp"] ?? "")
|
|
2816
|
+
}));
|
|
2817
|
+
sourceTurns = [...serverTurns, ...turnsAfter(entry.recentTurns, serverTurns)];
|
|
2818
|
+
}
|
|
2819
|
+
const localTurns = budgetRecentTurns(sourceTurns).map((t) => ({
|
|
2820
|
+
role: t.role,
|
|
2821
|
+
content: t.content,
|
|
2822
|
+
timestamp: t.timestamp
|
|
2823
|
+
}));
|
|
1885
2824
|
const serverField = (name) => existing === null ? void 0 : existing[name];
|
|
1886
2825
|
const orLocal = (name, local) => {
|
|
1887
2826
|
const value = serverField(name);
|
|
@@ -1936,14 +2875,28 @@ function buildRecentMessages(turns) {
|
|
|
1936
2875
|
role: t.role || "user",
|
|
1937
2876
|
content: t.content ?? "",
|
|
1938
2877
|
timestamp: t.timestamp,
|
|
1939
|
-
message_id: `local-${idx}`
|
|
2878
|
+
message_id: t.message_id || `local-${idx}`
|
|
1940
2879
|
}));
|
|
1941
2880
|
}
|
|
1942
2881
|
function compactedMessageCount(_entry) {
|
|
1943
2882
|
return 0;
|
|
1944
2883
|
}
|
|
2884
|
+
function blockOf(entry) {
|
|
2885
|
+
if (entry.contextBlock) return entry.contextBlock.trim();
|
|
2886
|
+
if (entry.summary && entry.summary.startsWith(CONTEXT_BLOCK_PREFIX)) return entry.summary.trim();
|
|
2887
|
+
return null;
|
|
2888
|
+
}
|
|
1945
2889
|
function formatStructured(entry, recent) {
|
|
1946
2890
|
const parts = [];
|
|
2891
|
+
const block = blockOf(entry);
|
|
2892
|
+
if (block) {
|
|
2893
|
+
parts.push(block);
|
|
2894
|
+
if (recent.length > 0) {
|
|
2895
|
+
parts.push(`## Recent Turns (${recent.length})`);
|
|
2896
|
+
for (const m of recent) parts.push(`**${m.role}**: ${m.content}`);
|
|
2897
|
+
}
|
|
2898
|
+
return parts.filter(Boolean).join("\n\n").trim();
|
|
2899
|
+
}
|
|
1947
2900
|
if (entry.summary) {
|
|
1948
2901
|
parts.push("## Summary");
|
|
1949
2902
|
parts.push(entry.summary.trim());
|
|
@@ -1978,9 +2931,11 @@ function formatStructured(entry, recent) {
|
|
|
1978
2931
|
}
|
|
1979
2932
|
function formatNarrative(entry, recent) {
|
|
1980
2933
|
const parts = [];
|
|
1981
|
-
|
|
1982
|
-
if (
|
|
1983
|
-
else if (entry.
|
|
2934
|
+
const block = blockOf(entry);
|
|
2935
|
+
if (block) parts.push(block);
|
|
2936
|
+
else if (entry.summary) parts.push(entry.summary.trim());
|
|
2937
|
+
if (!block && entry.conversationalParagraph) parts.push(entry.conversationalParagraph.trim());
|
|
2938
|
+
else if (!block && entry.factualParagraph) parts.push(entry.factualParagraph.trim());
|
|
1984
2939
|
if (recent.length > 0) {
|
|
1985
2940
|
const tail = recent.map((m) => `${m.role}: ${m.content}`).join("\n");
|
|
1986
2941
|
parts.push(`Recent exchanges:
|
|
@@ -1990,6 +2945,10 @@ ${tail}`);
|
|
|
1990
2945
|
}
|
|
1991
2946
|
function formatBullets(entry, recent) {
|
|
1992
2947
|
const parts = [];
|
|
2948
|
+
const block = blockOf(entry);
|
|
2949
|
+
if (block) {
|
|
2950
|
+
return [block, ...recent.map((m) => `- ${m.role}: ${m.content}`)].join("\n").trim();
|
|
2951
|
+
}
|
|
1993
2952
|
if (entry.summary) parts.push(`- Summary: ${entry.summary.trim()}`);
|
|
1994
2953
|
for (const [k, v] of Object.entries(entry.currentState ?? {})) {
|
|
1995
2954
|
parts.push(`- ${k}: ${stringify(v)}`);
|
|
@@ -2027,7 +2986,9 @@ function renderForPrompt(entry, style = DEFAULT_STYLE, now = Date.now) {
|
|
|
2027
2986
|
recent_messages: recent,
|
|
2028
2987
|
recent_message_count: recent.length,
|
|
2029
2988
|
compacted_message_count: compacted,
|
|
2030
|
-
total_message_count: recent.length + compacted
|
|
2989
|
+
total_message_count: recent.length + compacted,
|
|
2990
|
+
context_block: blockOf(entry),
|
|
2991
|
+
compaction_id: entry.compactionId
|
|
2031
2992
|
};
|
|
2032
2993
|
}
|
|
2033
2994
|
function renderCompacted(entry, format = "structured", correlationId = "") {
|
|
@@ -2172,10 +3133,10 @@ function servedItemIds(hit) {
|
|
|
2172
3133
|
}
|
|
2173
3134
|
return ids;
|
|
2174
3135
|
}
|
|
2175
|
-
function tryServeFromCache(cache, scope, params, maxItems) {
|
|
3136
|
+
async function tryServeFromCache(cache, scope, params, maxItems) {
|
|
2176
3137
|
try {
|
|
2177
3138
|
const scoped = scopeLookupParams(scope, params);
|
|
2178
|
-
const hit = cache.
|
|
3139
|
+
const hit = await cache.lookupAsync({
|
|
2179
3140
|
searchQuery: scoped.searchQuery ?? null,
|
|
2180
3141
|
entityId: scoped.entityId ?? null,
|
|
2181
3142
|
customerId: scoped.customerId ?? null,
|
|
@@ -2263,6 +3224,8 @@ function mergeScopeResults(scopeResults) {
|
|
|
2263
3224
|
episodes: [],
|
|
2264
3225
|
emotions: [],
|
|
2265
3226
|
temporal_events: [],
|
|
3227
|
+
items: [],
|
|
3228
|
+
excerpts: [],
|
|
2266
3229
|
scope_map: {},
|
|
2267
3230
|
conversation_context: null,
|
|
2268
3231
|
profile: null,
|
|
@@ -2272,6 +3235,8 @@ function mergeScopeResults(scopeResults) {
|
|
|
2272
3235
|
total_items: 0,
|
|
2273
3236
|
metadata: null
|
|
2274
3237
|
};
|
|
3238
|
+
const rankedSeen = /* @__PURE__ */ new Set();
|
|
3239
|
+
const rankedItems = [];
|
|
2275
3240
|
for (const [scopeName, response] of scopeResults) {
|
|
2276
3241
|
out.scopes_queried.push(scopeName);
|
|
2277
3242
|
if (out.metadata === null && response.metadata !== void 0) {
|
|
@@ -2296,8 +3261,32 @@ function mergeScopeResults(scopeResults) {
|
|
|
2296
3261
|
out[collection].push(item);
|
|
2297
3262
|
}
|
|
2298
3263
|
}
|
|
3264
|
+
const excerpts = response["excerpts"];
|
|
3265
|
+
if (Array.isArray(excerpts)) {
|
|
3266
|
+
for (const item of excerpts) {
|
|
3267
|
+
const id = itemId2(item);
|
|
3268
|
+
if (id !== void 0) {
|
|
3269
|
+
if (seen.has(id)) continue;
|
|
3270
|
+
seen.add(id);
|
|
3271
|
+
out.scope_map[id] = scopeName;
|
|
3272
|
+
}
|
|
3273
|
+
out.excerpts.push(item);
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
const ranked = response["items"];
|
|
3277
|
+
if (Array.isArray(ranked)) {
|
|
3278
|
+
for (const item of ranked) {
|
|
3279
|
+
const id = itemId2(item);
|
|
3280
|
+
if (id !== void 0) {
|
|
3281
|
+
if (rankedSeen.has(id)) continue;
|
|
3282
|
+
rankedSeen.add(id);
|
|
3283
|
+
}
|
|
3284
|
+
rankedItems.push(item);
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
2299
3287
|
}
|
|
2300
|
-
out.
|
|
3288
|
+
out.items = sortByRankScore(rankedItems).map((item, n2) => ({ ...item, rank: n2 + 1 }));
|
|
3289
|
+
out.total_items = ITEM_COLLECTIONS.reduce((n2, c) => n2 + out[c].length, 0) + out.excerpts.length;
|
|
2301
3290
|
return out;
|
|
2302
3291
|
}
|
|
2303
3292
|
function oneDecimal(value) {
|
|
@@ -2323,7 +3312,18 @@ function formatForPrompt(ctx, options = {}) {
|
|
|
2323
3312
|
const includeScope = options.includeScope ?? false;
|
|
2324
3313
|
const includeConversationContext = options.includeConversationContext ?? true;
|
|
2325
3314
|
const sections = [];
|
|
2326
|
-
|
|
3315
|
+
const ranked = ctx.items ?? [];
|
|
3316
|
+
if (ranked.length > 0) {
|
|
3317
|
+
const lines = ranked.map((it, n2) => {
|
|
3318
|
+
let label = rankedLabel(String(it["category"] ?? ""));
|
|
3319
|
+
if (includeScope) label += `, scope: ${scopeOf(ctx, it)}`;
|
|
3320
|
+
return `${n2 + 1}. [${label}] ${String(it.content ?? "")}`;
|
|
3321
|
+
});
|
|
3322
|
+
sections.push(`### Memories
|
|
3323
|
+
${lines.join("\n")}`);
|
|
3324
|
+
}
|
|
3325
|
+
const typed = ranked.length === 0;
|
|
3326
|
+
if (typed && ctx.facts.length > 0) {
|
|
2327
3327
|
const lines = ctx.facts.map((f) => {
|
|
2328
3328
|
let line = `- ${String(f.content ?? "")}`;
|
|
2329
3329
|
if (includeScope) line += ` [scope: ${scopeOf(ctx, f)}]`;
|
|
@@ -2332,7 +3332,7 @@ function formatForPrompt(ctx, options = {}) {
|
|
|
2332
3332
|
sections.push(`### Facts
|
|
2333
3333
|
${lines.join("\n")}`);
|
|
2334
3334
|
}
|
|
2335
|
-
if (ctx.preferences.length > 0) {
|
|
3335
|
+
if (typed && ctx.preferences.length > 0) {
|
|
2336
3336
|
const lines = ctx.preferences.map((p) => {
|
|
2337
3337
|
const extras = [];
|
|
2338
3338
|
if (includeScope) extras.push(`scope: ${scopeOf(ctx, p)}`);
|
|
@@ -2345,7 +3345,7 @@ ${lines.join("\n")}`);
|
|
|
2345
3345
|
sections.push(`### Preferences
|
|
2346
3346
|
${lines.join("\n")}`);
|
|
2347
3347
|
}
|
|
2348
|
-
if (ctx.episodes.length > 0) {
|
|
3348
|
+
if (typed && ctx.episodes.length > 0) {
|
|
2349
3349
|
const lines = ctx.episodes.map((e) => {
|
|
2350
3350
|
let line = `- ${String(e["summary"] ?? "")}`;
|
|
2351
3351
|
if (includeScope) line += ` [scope: ${scopeOf(ctx, e)}]`;
|
|
@@ -2354,7 +3354,7 @@ ${lines.join("\n")}`);
|
|
|
2354
3354
|
sections.push(`### Episodes
|
|
2355
3355
|
${lines.join("\n")}`);
|
|
2356
3356
|
}
|
|
2357
|
-
if (ctx.emotions.length > 0) {
|
|
3357
|
+
if (typed && ctx.emotions.length > 0) {
|
|
2358
3358
|
const lines = ctx.emotions.map((em) => {
|
|
2359
3359
|
const extras = [];
|
|
2360
3360
|
if (includeScope) extras.push(`scope: ${scopeOf(ctx, em)}`);
|
|
@@ -2368,7 +3368,7 @@ ${lines.join("\n")}`);
|
|
|
2368
3368
|
sections.push(`### Emotions
|
|
2369
3369
|
${lines.join("\n")}`);
|
|
2370
3370
|
}
|
|
2371
|
-
if (ctx.temporal_events.length > 0) {
|
|
3371
|
+
if (typed && ctx.temporal_events.length > 0) {
|
|
2372
3372
|
const lines = ctx.temporal_events.map((te) => {
|
|
2373
3373
|
const extras = [];
|
|
2374
3374
|
if (includeScope) extras.push(`scope: ${scopeOf(ctx, te)}`);
|
|
@@ -2622,7 +3622,10 @@ function buildTool(target, options = {}, onWarning = () => {
|
|
|
2622
3622
|
}
|
|
2623
3623
|
|
|
2624
3624
|
// src/client.ts
|
|
2625
|
-
|
|
3625
|
+
function localModelOff() {
|
|
3626
|
+
return ["off", "0", "false", "no", "none"].includes((getEnv("SYNAP_SDK_LOCAL_MODEL") ?? "").trim().toLowerCase());
|
|
3627
|
+
}
|
|
3628
|
+
var SynapClient = class _SynapClient {
|
|
2626
3629
|
// Native-private (`#`), not TypeScript `private`. TS `private` is erased at
|
|
2627
3630
|
// compile time, so the field stays enumerable at runtime and a JS consumer
|
|
2628
3631
|
// can still reach it. These two were public and made `client.transport
|
|
@@ -2648,7 +3651,16 @@ var SynapClient = class {
|
|
|
2648
3651
|
#registryKeys;
|
|
2649
3652
|
#compactionSubscribers;
|
|
2650
3653
|
#shortTerm;
|
|
3654
|
+
/** Every raw turn, past compaction, for local history search. */
|
|
3655
|
+
#turnStore;
|
|
3656
|
+
/** A local embedding model for history search; null = keywords only. */
|
|
3657
|
+
#localEncoder = null;
|
|
3658
|
+
/** `history_search_model`: "auto", an "onnx:..." name, or null for none. */
|
|
3659
|
+
#localModelName = "auto";
|
|
3660
|
+
#localModelStarted = false;
|
|
3661
|
+
#backfillAfter = /* @__PURE__ */ new Map();
|
|
2651
3662
|
#stVerbatimOverlay;
|
|
3663
|
+
#agentModel = {};
|
|
2652
3664
|
#logger;
|
|
2653
3665
|
#stAuthoritative;
|
|
2654
3666
|
#turns;
|
|
@@ -2688,11 +3700,11 @@ var SynapClient = class {
|
|
|
2688
3700
|
if (options.heartbeat) this.#transport.startHeartbeat();
|
|
2689
3701
|
this.#anticipationCache = new AnticipationCache();
|
|
2690
3702
|
this.#fetchHooks = {
|
|
2691
|
-
beforeFetch: (scope, options2) => {
|
|
3703
|
+
beforeFetch: async (scope, options2) => {
|
|
2692
3704
|
const o2 = options2;
|
|
2693
3705
|
if (o2["query_date"] ?? o2["queryDate"]) return null;
|
|
2694
3706
|
if (o2["max_results_per_query"] ?? o2["maxResultsPerQuery"]) return null;
|
|
2695
|
-
const attempt = tryServeFromCache(
|
|
3707
|
+
const attempt = await tryServeFromCache(
|
|
2696
3708
|
this.#anticipationCache,
|
|
2697
3709
|
scope,
|
|
2698
3710
|
{
|
|
@@ -2716,7 +3728,9 @@ var SynapClient = class {
|
|
|
2716
3728
|
return {
|
|
2717
3729
|
response: attempt.response,
|
|
2718
3730
|
servedItemIds: servedItemIds(attempt.hit),
|
|
2719
|
-
bundleId: attempt.hit.bundleIds[0] ?? ""
|
|
3731
|
+
bundleId: attempt.hit.bundleIds[0] ?? "",
|
|
3732
|
+
borderline: attempt.hit.band === "borderline",
|
|
3733
|
+
raceDeadlineMs: SEMANTIC_MATCH.race_deadline_ms
|
|
2720
3734
|
};
|
|
2721
3735
|
},
|
|
2722
3736
|
onAssembled: (scope, options2, response, startedAt) => {
|
|
@@ -2731,9 +3745,19 @@ var SynapClient = class {
|
|
|
2731
3745
|
this.client = { context: { fetch: async (o2 = {}) => fetchContext(this.#transport, "client", o2, this.#fetchHooks) } };
|
|
2732
3746
|
this.#compactionSubscribers = new CompactionSubscribers();
|
|
2733
3747
|
this.#shortTerm = new ShortTermStore();
|
|
3748
|
+
this.#turnStore = new TurnStore();
|
|
3749
|
+
this.#shortTerm.attachArchive(this.#turnStore);
|
|
2734
3750
|
this.#stVerbatimOverlay = options.st_verbatim_overlay ?? options.stVerbatimOverlay;
|
|
3751
|
+
this.#agentModel = {
|
|
3752
|
+
model: options.agent_model ?? options.agentModel,
|
|
3753
|
+
contextWindow: options.agent_context_window ?? options.agentContextWindow
|
|
3754
|
+
};
|
|
2735
3755
|
this.#logger = options.logger;
|
|
2736
3756
|
this.#stAuthoritative = options.sdk_st_authoritative ?? options.sdkStAuthoritative;
|
|
3757
|
+
this.#localEncoder = options.history_search_encoder ?? options.historySearchEncoder ?? null;
|
|
3758
|
+
const modelOption = options.history_search_model !== void 0 ? options.history_search_model : options.historySearchModel;
|
|
3759
|
+
this.#localModelName = modelOption === void 0 ? "auto" : modelOption;
|
|
3760
|
+
if (this.#localEncoder?.model === SEMANTIC_MATCH.model) this.#anticipationCache.setEncoder(this.#localEncoder);
|
|
2737
3761
|
this.#turns = new TurnCounter();
|
|
2738
3762
|
this.conversation = createConversationNamespace(
|
|
2739
3763
|
this.#transport,
|
|
@@ -2750,8 +3774,12 @@ var SynapClient = class {
|
|
|
2750
3774
|
},
|
|
2751
3775
|
// Buffer the turn locally so the next fetch sees it, ahead of the server
|
|
2752
3776
|
// having persisted and compacted it.
|
|
2753
|
-
({ conversationId, role, content, timestamp }) => {
|
|
2754
|
-
this.#shortTerm.appendTurn(conversationId, role, content, timestamp
|
|
3777
|
+
({ conversationId, role, content, timestamp, messageId, serverMessageCount, inFlight }) => {
|
|
3778
|
+
this.#shortTerm.appendTurn(conversationId, role, content, timestamp, {
|
|
3779
|
+
messageId,
|
|
3780
|
+
serverMessageCount,
|
|
3781
|
+
inFlight
|
|
3782
|
+
});
|
|
2755
3783
|
},
|
|
2756
3784
|
(response, conversationId, userId, scopePath) => {
|
|
2757
3785
|
overlayLocalRecentTurns(response, this.#shortTerm, conversationId, this.#stVerbatimOverlay);
|
|
@@ -2759,8 +3787,11 @@ var SynapClient = class {
|
|
|
2759
3787
|
},
|
|
2760
3788
|
{
|
|
2761
3789
|
prompt: (conversationId, style) => this.#localPrompt(conversationId, style),
|
|
2762
|
-
compacted: (conversationId, format) => this.#localCompacted(conversationId, format)
|
|
2763
|
-
|
|
3790
|
+
compacted: (conversationId, format) => this.#localCompacted(conversationId, format),
|
|
3791
|
+
reseed: (conversationId, response) => this.#reseedShortTerm(conversationId, response),
|
|
3792
|
+
historyExcerpts: (conversationId, response, query) => this.#withHistoryExcerpts(conversationId, response, query)
|
|
3793
|
+
},
|
|
3794
|
+
() => this.#agentModel
|
|
2764
3795
|
);
|
|
2765
3796
|
this.memories = createMemoriesInterface(this.#transport, ({ userId, customerId }) => {
|
|
2766
3797
|
for (const id of /* @__PURE__ */ new Set([userId, customerId])) {
|
|
@@ -2774,11 +3805,17 @@ var SynapClient = class {
|
|
|
2774
3805
|
this.#anticipationCache,
|
|
2775
3806
|
({ conversationId, role, content }) => {
|
|
2776
3807
|
this.#shortTerm.appendTurn(conversationId, role, content);
|
|
2777
|
-
}
|
|
3808
|
+
},
|
|
3809
|
+
() => this.#agentModel,
|
|
3810
|
+
({ conversationId, result, toolName }) => this.archive_tool_result(conversationId, result, { tool_name: toolName })
|
|
2778
3811
|
);
|
|
3812
|
+
this.instance._internal.setOnListen(() => this.#startLocalModel());
|
|
2779
3813
|
this.instance._internal.setCompactionApplier((bundle) => {
|
|
2780
3814
|
this.#shortTerm.applyCompaction(bundle);
|
|
2781
3815
|
});
|
|
3816
|
+
this.instance._internal.setConversationStateHandler((conversationId, messageCount) => {
|
|
3817
|
+
this.#shortTerm.noteServerCount(conversationId, messageCount);
|
|
3818
|
+
});
|
|
2782
3819
|
if (options._force_new !== true) {
|
|
2783
3820
|
const incumbent = registerIfAbsent(registryKey, this);
|
|
2784
3821
|
if (incumbent !== void 0) return incumbent;
|
|
@@ -2938,6 +3975,14 @@ var SynapClient = class {
|
|
|
2938
3975
|
this.#transport.setRetryPolicy(options.retryPolicy ?? options.retry_policy ?? null);
|
|
2939
3976
|
}
|
|
2940
3977
|
if (options.logger !== void 0) this.#logger = options.logger;
|
|
3978
|
+
const model = options.agent_model ?? options.agentModel;
|
|
3979
|
+
const window = options.agent_context_window ?? options.agentContextWindow;
|
|
3980
|
+
if (model !== void 0 || window !== void 0) {
|
|
3981
|
+
this.#agentModel = {
|
|
3982
|
+
model: model ?? this.#agentModel.model,
|
|
3983
|
+
contextWindow: window ?? this.#agentModel.contextWindow
|
|
3984
|
+
};
|
|
3985
|
+
}
|
|
2941
3986
|
}
|
|
2942
3987
|
/**
|
|
2943
3988
|
* Fetch and merge context across every scope with an identifier, in one call.
|
|
@@ -2971,9 +4016,11 @@ var SynapClient = class {
|
|
|
2971
4016
|
include_scope_labels: includeScopeLabels = false,
|
|
2972
4017
|
context_mode: contextMode = "in-conversation",
|
|
2973
4018
|
include_profile: includeProfile = true,
|
|
2974
|
-
last_n_conversations: lastNConversations = 1
|
|
4019
|
+
last_n_conversations: lastNConversations = 1,
|
|
4020
|
+
response_shape: responseShape = "by_type"
|
|
2975
4021
|
} = options;
|
|
2976
4022
|
validateConversationId(conversationId);
|
|
4023
|
+
responseShapeOf({ response_shape: responseShape });
|
|
2977
4024
|
const wants = (scope) => scopes === void 0 || scopes.includes(scope);
|
|
2978
4025
|
const shared = {
|
|
2979
4026
|
search_query: searchQuery,
|
|
@@ -2985,7 +4032,8 @@ var SynapClient = class {
|
|
|
2985
4032
|
// threaded three call sites by hand and missed the client branch, which
|
|
2986
4033
|
// sits one indent deeper; spreading removes that whole class of miss.
|
|
2987
4034
|
query_date: queryDate,
|
|
2988
|
-
max_results_per_query: maxResultsPerQuery
|
|
4035
|
+
max_results_per_query: maxResultsPerQuery,
|
|
4036
|
+
response_shape: responseShape
|
|
2989
4037
|
};
|
|
2990
4038
|
const labels = [];
|
|
2991
4039
|
const tasks = [];
|
|
@@ -3148,25 +4196,139 @@ var SynapClient = class {
|
|
|
3148
4196
|
/**
|
|
3149
4197
|
* Whether the SDK is authoritative for short-term context.
|
|
3150
4198
|
*
|
|
3151
|
-
* Resolution order matches Python's `_is_st_authoritative`:
|
|
3152
|
-
*
|
|
3153
|
-
*
|
|
4199
|
+
* Resolution order matches Python's `_is_st_authoritative`:
|
|
4200
|
+
* `SYNAP_SDK_ST_AUTHORITATIVE` first, as an explicit override in either
|
|
4201
|
+
* direction, then the option. Off by default. Being enabled does not mean
|
|
4202
|
+
* the local store is used: that also takes an entry that holds the whole
|
|
4203
|
+
* conversation, see `isComplete`.
|
|
3154
4204
|
*/
|
|
3155
4205
|
#isStAuthoritative() {
|
|
3156
|
-
if (this.#stAuthoritative === true) return true;
|
|
3157
4206
|
const raw = (getEnv("SYNAP_SDK_ST_AUTHORITATIVE") ?? "").trim().toLowerCase();
|
|
3158
|
-
|
|
4207
|
+
if (["0", "false", "no", "off"].includes(raw)) return false;
|
|
4208
|
+
if (["1", "true", "yes", "on"].includes(raw)) return true;
|
|
4209
|
+
return this.#stAuthoritative === true;
|
|
3159
4210
|
}
|
|
3160
4211
|
/**
|
|
3161
4212
|
* Render `get_context_for_prompt` from the local store, or null to fall
|
|
3162
|
-
* through to the network.
|
|
3163
|
-
*
|
|
3164
|
-
*
|
|
4213
|
+
* through to the network.
|
|
4214
|
+
*
|
|
4215
|
+
* The store answers only when it holds the whole conversation: the server
|
|
4216
|
+
* has confirmed a count the entry accounts for. An entry that is merely
|
|
4217
|
+
* present is not enough. After a restart or an eviction it holds one turn,
|
|
4218
|
+
* and rendering that as the conversation drops everything said before it.
|
|
4219
|
+
*/
|
|
4220
|
+
/**
|
|
4221
|
+
* `response` with the earlier turns that best match `query` (by default the
|
|
4222
|
+
* latest user message) appended, when a compaction exists. The search runs
|
|
4223
|
+
* here, over the turn archive. Python: `_with_history_excerpts`.
|
|
3165
4224
|
*/
|
|
4225
|
+
async #withHistoryExcerpts(conversationId, response, query) {
|
|
4226
|
+
const r = response ?? {};
|
|
4227
|
+
const store = this.#turnStore;
|
|
4228
|
+
const recent = Array.isArray(r["recent_messages"]) ? r["recent_messages"] : [];
|
|
4229
|
+
store.addMany(
|
|
4230
|
+
conversationId,
|
|
4231
|
+
recent.map((m) => ({
|
|
4232
|
+
role: String(m["role"] ?? "user"),
|
|
4233
|
+
content: String(m["content"] ?? ""),
|
|
4234
|
+
timestamp: String(m["timestamp"] ?? ""),
|
|
4235
|
+
message_id: typeof m["message_id"] === "string" ? m["message_id"] : null
|
|
4236
|
+
}))
|
|
4237
|
+
);
|
|
4238
|
+
const entry = this.#shortTerm.get(conversationId);
|
|
4239
|
+
const total = Math.max(Number(r["total_message_count"] ?? 0) || 0, Number(entry?.serverMessageCount ?? 0) || 0);
|
|
4240
|
+
this.#maybeBackfillTurns(conversationId, total);
|
|
4241
|
+
if (!r["context_block"]) return response;
|
|
4242
|
+
const turns = store.turns(conversationId);
|
|
4243
|
+
let q = query;
|
|
4244
|
+
if (!q) {
|
|
4245
|
+
const latest = [...recent].reverse().find((m) => m["role"] === "user");
|
|
4246
|
+
q = latest ? String(latest["content"] ?? "") : [...turns].reverse().find((t) => t.role === "user")?.content ?? "";
|
|
4247
|
+
}
|
|
4248
|
+
if (!q) return response;
|
|
4249
|
+
const exclude = /* @__PURE__ */ new Set();
|
|
4250
|
+
for (const m of recent) {
|
|
4251
|
+
if (typeof m["message_id"] === "string" && m["message_id"]) exclude.add(m["message_id"]);
|
|
4252
|
+
exclude.add(String(m["content"] ?? ""));
|
|
4253
|
+
}
|
|
4254
|
+
this.#startLocalModel();
|
|
4255
|
+
const hits = await searchTurns(turns, q, { exclude, encoder: this.#localEncoder, store });
|
|
4256
|
+
if (hits.length === 0) return response;
|
|
4257
|
+
const block = renderExcerpts(hits, turns.length ? turns[turns.length - 1].timestamp : "");
|
|
4258
|
+
return {
|
|
4259
|
+
...r,
|
|
4260
|
+
history_excerpts: hits.map((h) => ({ position: h.position, role: h.role, text: h.text })),
|
|
4261
|
+
history_block: block,
|
|
4262
|
+
formatted_context: (String(r["formatted_context"] ?? "").trimEnd() + "\n\n" + block).trim()
|
|
4263
|
+
};
|
|
4264
|
+
}
|
|
4265
|
+
/**
|
|
4266
|
+
* Keep what a tool returned in this process's turn archive, so history search
|
|
4267
|
+
* can find it after a compaction. Never sent anywhere: the server's
|
|
4268
|
+
* conversation holds what people said, and a compaction is made from that.
|
|
4269
|
+
* `instance.record_tool_result` calls this too. Python: `archive_tool_result`.
|
|
4270
|
+
*/
|
|
4271
|
+
archive_tool_result(conversationId, result, options = {}) {
|
|
4272
|
+
if (!conversationId || result === null || result === void 0) return;
|
|
4273
|
+
try {
|
|
4274
|
+
this.#turnStore.add(
|
|
4275
|
+
conversationId,
|
|
4276
|
+
"tool",
|
|
4277
|
+
toolResultText(result, options.tool_name ?? options.toolName),
|
|
4278
|
+
options.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
4279
|
+
);
|
|
4280
|
+
} catch {
|
|
4281
|
+
}
|
|
4282
|
+
}
|
|
4283
|
+
static BACKFILL_COOLDOWN_MS = 6e4;
|
|
4284
|
+
static BACKFILL_PAGE = 500;
|
|
4285
|
+
/** Fetch, in the background, turns the server holds and the archive does not. */
|
|
4286
|
+
/**
|
|
4287
|
+
* Load the local embedding model in the background, once. Searches and
|
|
4288
|
+
* lookups before it is ready match by words. Python: `_start_history_encoder`.
|
|
4289
|
+
*/
|
|
4290
|
+
#startLocalModel() {
|
|
4291
|
+
if (this.#localModelStarted) return;
|
|
4292
|
+
this.#localModelStarted = true;
|
|
4293
|
+
if (localModelOff()) return;
|
|
4294
|
+
const name = this.#localModelName === "auto" ? SEMANTIC_MATCH.model : this.#localModelName;
|
|
4295
|
+
if (!name) return;
|
|
4296
|
+
const callerEncoder = this.#localEncoder;
|
|
4297
|
+
void import("./onnx-encoder-NP35KH4H.js").then(({ canRunLocalModel, loadOnnxEncoder }) => canRunLocalModel() ? loadOnnxEncoder(name) : null).then((encoder) => {
|
|
4298
|
+
if (encoder === null) return;
|
|
4299
|
+
if (callerEncoder === null) this.#localEncoder = encoder;
|
|
4300
|
+
if (encoder.model === SEMANTIC_MATCH.model) this.#anticipationCache.setEncoder(encoder);
|
|
4301
|
+
}).catch(() => {
|
|
4302
|
+
});
|
|
4303
|
+
}
|
|
4304
|
+
#maybeBackfillTurns(conversationId, total) {
|
|
4305
|
+
if (total <= this.#turnStore.count(conversationId)) return;
|
|
4306
|
+
const now = Date.now();
|
|
4307
|
+
if (now < (this.#backfillAfter.get(conversationId) ?? 0)) return;
|
|
4308
|
+
this.#backfillAfter.set(conversationId, now + _SynapClient.BACKFILL_COOLDOWN_MS);
|
|
4309
|
+
void this.#backfillTurns(conversationId, total);
|
|
4310
|
+
}
|
|
4311
|
+
async #backfillTurns(conversationId, total) {
|
|
4312
|
+
try {
|
|
4313
|
+
let start = 1;
|
|
4314
|
+
while (start <= total) {
|
|
4315
|
+
const result = await this.#transport.request("conversation_turns", {
|
|
4316
|
+
pathParams: { conversation_id: conversationId },
|
|
4317
|
+
query: { start, limit: _SynapClient.BACKFILL_PAGE }
|
|
4318
|
+
});
|
|
4319
|
+
const page = (result ?? {})["turns"] ?? [];
|
|
4320
|
+
this.#turnStore.addMany(conversationId, page);
|
|
4321
|
+
if (page.length < _SynapClient.BACKFILL_PAGE) break;
|
|
4322
|
+
start += _SynapClient.BACKFILL_PAGE;
|
|
4323
|
+
}
|
|
4324
|
+
} catch (error) {
|
|
4325
|
+
this.#warn(`Turn backfill for ${conversationId} failed: ${String(error)}`);
|
|
4326
|
+
}
|
|
4327
|
+
}
|
|
3166
4328
|
#localPrompt(conversationId, style) {
|
|
3167
4329
|
if (!this.#isStAuthoritative()) return null;
|
|
3168
4330
|
const entry = this.#shortTerm.get(conversationId);
|
|
3169
|
-
if (entry === null) return null;
|
|
4331
|
+
if (entry === null || !isComplete(entry)) return null;
|
|
3170
4332
|
if (entry.compactionId === null && entry.recentTurns.length === 0) return null;
|
|
3171
4333
|
return renderForPrompt(entry, style);
|
|
3172
4334
|
}
|
|
@@ -3174,9 +4336,29 @@ var SynapClient = class {
|
|
|
3174
4336
|
#localCompacted(conversationId, format) {
|
|
3175
4337
|
if (!this.#isStAuthoritative()) return null;
|
|
3176
4338
|
const entry = this.#shortTerm.get(conversationId);
|
|
3177
|
-
if (entry === null || entry.compactionId === null) return null;
|
|
4339
|
+
if (entry === null || entry.compactionId === null || !isComplete(entry)) return null;
|
|
3178
4340
|
return renderCompacted(entry, format);
|
|
3179
4341
|
}
|
|
4342
|
+
/**
|
|
4343
|
+
* Bring the local store back in step from the server's answer.
|
|
4344
|
+
*
|
|
4345
|
+
* Only for a conversation this process writes to. The server's answer lists
|
|
4346
|
+
* every message its summary does not cover, which is exactly what the entry
|
|
4347
|
+
* was missing.
|
|
4348
|
+
*/
|
|
4349
|
+
#reseedShortTerm(conversationId, response) {
|
|
4350
|
+
if (!this.#isStAuthoritative()) return;
|
|
4351
|
+
const entry = this.#shortTerm.get(conversationId);
|
|
4352
|
+
if (entry === null || !entry.participant) return;
|
|
4353
|
+
const r = response ?? {};
|
|
4354
|
+
const recent = Array.isArray(r["recent_messages"]) ? r["recent_messages"] : [];
|
|
4355
|
+
this.#shortTerm.reseed(conversationId, {
|
|
4356
|
+
compactionId: typeof r["compaction_id"] === "string" ? r["compaction_id"] : null,
|
|
4357
|
+
recentMessages: recent,
|
|
4358
|
+
compactedMessageCount: Number(r["compacted_message_count"] ?? 0) || 0,
|
|
4359
|
+
totalMessageCount: Number(r["total_message_count"] ?? 0) || 0
|
|
4360
|
+
});
|
|
4361
|
+
}
|
|
3180
4362
|
/** Every diagnostic the SDK emits passes through here. */
|
|
3181
4363
|
#warn(message) {
|
|
3182
4364
|
if (this.#logger !== void 0) {
|
|
@@ -3239,6 +4421,7 @@ export {
|
|
|
3239
4421
|
DEFAULT_TIMEOUTS,
|
|
3240
4422
|
DOCUMENT_TYPES,
|
|
3241
4423
|
ENDPOINTS,
|
|
4424
|
+
EXCERPT_CATEGORY,
|
|
3242
4425
|
FORGET_MODES,
|
|
3243
4426
|
HttpTransport,
|
|
3244
4427
|
INGEST_MODES,
|
|
@@ -3249,8 +4432,10 @@ export {
|
|
|
3249
4432
|
InvalidInstanceIdError,
|
|
3250
4433
|
ListeningAlreadyActiveError,
|
|
3251
4434
|
ListeningNotActiveError,
|
|
4435
|
+
MEMORY_TYPES,
|
|
3252
4436
|
NetworkTimeoutError,
|
|
3253
4437
|
PermanentError,
|
|
4438
|
+
RESPONSE_SHAPES,
|
|
3254
4439
|
RateLimitError,
|
|
3255
4440
|
SDKError,
|
|
3256
4441
|
SDK_VERSION,
|
|
@@ -3263,15 +4448,18 @@ export {
|
|
|
3263
4448
|
THRESHOLDS,
|
|
3264
4449
|
TranscriptConflictError,
|
|
3265
4450
|
TransientError,
|
|
4451
|
+
applyResponseShape,
|
|
3266
4452
|
flattenContextItems,
|
|
3267
4453
|
formatForPrompt,
|
|
3268
4454
|
isOutcomeUnknown,
|
|
3269
4455
|
isRecallQuery,
|
|
3270
4456
|
isSynapError,
|
|
3271
4457
|
mergeScopeResults,
|
|
4458
|
+
rankItems,
|
|
3272
4459
|
resolvePath,
|
|
3273
4460
|
shouldRetry,
|
|
3274
4461
|
stem,
|
|
3275
|
-
tokenize
|
|
4462
|
+
tokenize,
|
|
4463
|
+
wireRankScore
|
|
3276
4464
|
};
|
|
3277
4465
|
//# sourceMappingURL=index.js.map
|