@deepagents/context 0.8.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +125 -22
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +371 -127
- package/dist/index.js.map +4 -4
- package/dist/lib/agent.d.ts +28 -0
- package/dist/lib/agent.d.ts.map +1 -0
- package/dist/lib/codec.d.ts +17 -0
- package/dist/lib/codec.d.ts.map +1 -0
- package/dist/lib/context.d.ts +6 -0
- package/dist/lib/context.d.ts.map +1 -1
- package/dist/lib/estimate.d.ts +15 -2
- package/dist/lib/estimate.d.ts.map +1 -1
- package/dist/lib/store/sqlite.store.d.ts +4 -3
- package/dist/lib/store/sqlite.store.d.ts.map +1 -1
- package/dist/lib/store/store.d.ts +14 -3
- package/dist/lib/store/store.d.ts.map +1 -1
- package/package.json +7 -2
package/dist/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// packages/context/src/index.ts
|
|
2
|
+
import { generateId } from "ai";
|
|
3
|
+
|
|
1
4
|
// packages/context/src/lib/context.ts
|
|
2
5
|
function isFragment(data) {
|
|
3
6
|
return typeof data === "object" && data !== null && "name" in data && "data" in data && typeof data.name === "string";
|
|
@@ -119,7 +122,8 @@ var ModelsRegistry = class {
|
|
|
119
122
|
context: model.limit.context,
|
|
120
123
|
output: model.limit.output,
|
|
121
124
|
exceedsContext: tokens > model.limit.context
|
|
122
|
-
}
|
|
125
|
+
},
|
|
126
|
+
fragments: []
|
|
123
127
|
};
|
|
124
128
|
}
|
|
125
129
|
};
|
|
@@ -130,6 +134,43 @@ function getModelsRegistry() {
|
|
|
130
134
|
}
|
|
131
135
|
return _registry;
|
|
132
136
|
}
|
|
137
|
+
async function estimate(modelId, renderer, ...fragments) {
|
|
138
|
+
const registry = getModelsRegistry();
|
|
139
|
+
await registry.load();
|
|
140
|
+
const input = renderer.render(fragments);
|
|
141
|
+
const model = registry.get(modelId);
|
|
142
|
+
if (!model) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
`Model "${modelId}" not found. Call load() first or check model ID.`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
const tokenizer = registry.getTokenizer(modelId);
|
|
148
|
+
const totalTokens = tokenizer.count(input);
|
|
149
|
+
const totalCost = totalTokens / 1e6 * model.cost.input;
|
|
150
|
+
const fragmentEstimates = fragments.map((fragment2) => {
|
|
151
|
+
const rendered = renderer.render([fragment2]);
|
|
152
|
+
const tokens = tokenizer.count(rendered);
|
|
153
|
+
const cost = tokens / 1e6 * model.cost.input;
|
|
154
|
+
return {
|
|
155
|
+
id: fragment2.id,
|
|
156
|
+
name: fragment2.name,
|
|
157
|
+
tokens,
|
|
158
|
+
cost
|
|
159
|
+
};
|
|
160
|
+
});
|
|
161
|
+
return {
|
|
162
|
+
model: model.id,
|
|
163
|
+
provider: model.provider,
|
|
164
|
+
tokens: totalTokens,
|
|
165
|
+
cost: totalCost,
|
|
166
|
+
limits: {
|
|
167
|
+
context: model.limit.context,
|
|
168
|
+
output: model.limit.output,
|
|
169
|
+
exceedsContext: totalTokens > model.limit.context
|
|
170
|
+
},
|
|
171
|
+
fragments: fragmentEstimates
|
|
172
|
+
};
|
|
173
|
+
}
|
|
133
174
|
|
|
134
175
|
// packages/context/src/lib/renderers/abstract.renderer.ts
|
|
135
176
|
import pluralize from "pluralize";
|
|
@@ -789,12 +830,13 @@ var ContextStore = class {
|
|
|
789
830
|
import { DatabaseSync } from "node:sqlite";
|
|
790
831
|
var STORE_DDL = `
|
|
791
832
|
-- Chats table
|
|
833
|
+
-- createdAt/updatedAt: DEFAULT for insert, inline SET for updates
|
|
792
834
|
CREATE TABLE IF NOT EXISTS chats (
|
|
793
835
|
id TEXT PRIMARY KEY,
|
|
794
836
|
title TEXT,
|
|
795
837
|
metadata TEXT,
|
|
796
|
-
createdAt INTEGER NOT NULL,
|
|
797
|
-
updatedAt INTEGER NOT NULL
|
|
838
|
+
createdAt INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),
|
|
839
|
+
updatedAt INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000)
|
|
798
840
|
);
|
|
799
841
|
|
|
800
842
|
CREATE INDEX IF NOT EXISTS idx_chats_updatedAt ON chats(updatedAt);
|
|
@@ -807,7 +849,6 @@ CREATE TABLE IF NOT EXISTS messages (
|
|
|
807
849
|
name TEXT NOT NULL,
|
|
808
850
|
type TEXT,
|
|
809
851
|
data TEXT NOT NULL,
|
|
810
|
-
persist INTEGER NOT NULL DEFAULT 1,
|
|
811
852
|
createdAt INTEGER NOT NULL,
|
|
812
853
|
FOREIGN KEY (chatId) REFERENCES chats(id) ON DELETE CASCADE,
|
|
813
854
|
FOREIGN KEY (parentId) REFERENCES messages(id)
|
|
@@ -846,13 +887,13 @@ CREATE TABLE IF NOT EXISTS checkpoints (
|
|
|
846
887
|
CREATE INDEX IF NOT EXISTS idx_checkpoints_chatId ON checkpoints(chatId);
|
|
847
888
|
|
|
848
889
|
-- FTS5 virtual table for full-text search
|
|
849
|
-
--
|
|
890
|
+
-- messageId/chatId/name are UNINDEXED (stored but not searchable, used for filtering/joining)
|
|
891
|
+
-- Only 'content' is indexed for full-text search
|
|
850
892
|
CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(
|
|
851
|
-
messageId,
|
|
852
|
-
chatId,
|
|
853
|
-
name,
|
|
893
|
+
messageId UNINDEXED,
|
|
894
|
+
chatId UNINDEXED,
|
|
895
|
+
name UNINDEXED,
|
|
854
896
|
content,
|
|
855
|
-
content='',
|
|
856
897
|
tokenize='porter unicode61'
|
|
857
898
|
);
|
|
858
899
|
`;
|
|
@@ -869,16 +910,33 @@ var SqliteContextStore = class extends ContextStore {
|
|
|
869
910
|
// ==========================================================================
|
|
870
911
|
async createChat(chat) {
|
|
871
912
|
this.#db.prepare(
|
|
872
|
-
`INSERT INTO chats (id, title, metadata
|
|
873
|
-
VALUES (?, ?,
|
|
913
|
+
`INSERT INTO chats (id, title, metadata)
|
|
914
|
+
VALUES (?, ?, ?)`
|
|
874
915
|
).run(
|
|
875
916
|
chat.id,
|
|
876
917
|
chat.title ?? null,
|
|
877
|
-
chat.metadata ? JSON.stringify(chat.metadata) : null
|
|
878
|
-
chat.createdAt,
|
|
879
|
-
chat.updatedAt
|
|
918
|
+
chat.metadata ? JSON.stringify(chat.metadata) : null
|
|
880
919
|
);
|
|
881
920
|
}
|
|
921
|
+
async upsertChat(chat) {
|
|
922
|
+
const row = this.#db.prepare(
|
|
923
|
+
`INSERT INTO chats (id, title, metadata)
|
|
924
|
+
VALUES (?, ?, ?)
|
|
925
|
+
ON CONFLICT(id) DO UPDATE SET id = excluded.id
|
|
926
|
+
RETURNING *`
|
|
927
|
+
).get(
|
|
928
|
+
chat.id,
|
|
929
|
+
chat.title ?? null,
|
|
930
|
+
chat.metadata ? JSON.stringify(chat.metadata) : null
|
|
931
|
+
);
|
|
932
|
+
return {
|
|
933
|
+
id: row.id,
|
|
934
|
+
title: row.title ?? void 0,
|
|
935
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
|
|
936
|
+
createdAt: row.createdAt,
|
|
937
|
+
updatedAt: row.updatedAt
|
|
938
|
+
};
|
|
939
|
+
}
|
|
882
940
|
async getChat(chatId) {
|
|
883
941
|
const row = this.#db.prepare("SELECT * FROM chats WHERE id = ?").get(chatId);
|
|
884
942
|
if (!row) {
|
|
@@ -893,7 +951,7 @@ var SqliteContextStore = class extends ContextStore {
|
|
|
893
951
|
};
|
|
894
952
|
}
|
|
895
953
|
async updateChat(chatId, updates) {
|
|
896
|
-
const setClauses = [];
|
|
954
|
+
const setClauses = ["updatedAt = strftime('%s', 'now') * 1000"];
|
|
897
955
|
const params = [];
|
|
898
956
|
if (updates.title !== void 0) {
|
|
899
957
|
setClauses.push("title = ?");
|
|
@@ -903,15 +961,17 @@ var SqliteContextStore = class extends ContextStore {
|
|
|
903
961
|
setClauses.push("metadata = ?");
|
|
904
962
|
params.push(JSON.stringify(updates.metadata));
|
|
905
963
|
}
|
|
906
|
-
if (updates.updatedAt !== void 0) {
|
|
907
|
-
setClauses.push("updatedAt = ?");
|
|
908
|
-
params.push(updates.updatedAt);
|
|
909
|
-
}
|
|
910
|
-
if (setClauses.length === 0) {
|
|
911
|
-
return;
|
|
912
|
-
}
|
|
913
964
|
params.push(chatId);
|
|
914
|
-
this.#db.prepare(
|
|
965
|
+
const row = this.#db.prepare(
|
|
966
|
+
`UPDATE chats SET ${setClauses.join(", ")} WHERE id = ? RETURNING *`
|
|
967
|
+
).get(...params);
|
|
968
|
+
return {
|
|
969
|
+
id: row.id,
|
|
970
|
+
title: row.title ?? void 0,
|
|
971
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
|
|
972
|
+
createdAt: row.createdAt,
|
|
973
|
+
updatedAt: row.updatedAt
|
|
974
|
+
};
|
|
915
975
|
}
|
|
916
976
|
async listChats() {
|
|
917
977
|
const rows = this.#db.prepare(
|
|
@@ -940,25 +1000,30 @@ var SqliteContextStore = class extends ContextStore {
|
|
|
940
1000
|
// ==========================================================================
|
|
941
1001
|
// Message Operations (Graph Nodes)
|
|
942
1002
|
// ==========================================================================
|
|
943
|
-
async addMessage(
|
|
1003
|
+
async addMessage(message2) {
|
|
944
1004
|
this.#db.prepare(
|
|
945
|
-
`INSERT INTO messages (id, chatId, parentId, name, type, data,
|
|
946
|
-
VALUES (?, ?, ?, ?, ?, ?,
|
|
1005
|
+
`INSERT INTO messages (id, chatId, parentId, name, type, data, createdAt)
|
|
1006
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1007
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
1008
|
+
parentId = excluded.parentId,
|
|
1009
|
+
name = excluded.name,
|
|
1010
|
+
type = excluded.type,
|
|
1011
|
+
data = excluded.data`
|
|
947
1012
|
).run(
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
JSON.stringify(
|
|
954
|
-
|
|
955
|
-
message.createdAt
|
|
1013
|
+
message2.id,
|
|
1014
|
+
message2.chatId,
|
|
1015
|
+
message2.parentId,
|
|
1016
|
+
message2.name,
|
|
1017
|
+
message2.type ?? null,
|
|
1018
|
+
JSON.stringify(message2.data),
|
|
1019
|
+
message2.createdAt
|
|
956
1020
|
);
|
|
957
|
-
const content = typeof
|
|
1021
|
+
const content = typeof message2.data === "string" ? message2.data : JSON.stringify(message2.data);
|
|
1022
|
+
this.#db.prepare(`DELETE FROM messages_fts WHERE messageId = ?`).run(message2.id);
|
|
958
1023
|
this.#db.prepare(
|
|
959
1024
|
`INSERT INTO messages_fts(messageId, chatId, name, content)
|
|
960
1025
|
VALUES (?, ?, ?, ?)`
|
|
961
|
-
).run(
|
|
1026
|
+
).run(message2.id, message2.chatId, message2.name, content);
|
|
962
1027
|
}
|
|
963
1028
|
async getMessage(messageId) {
|
|
964
1029
|
const row = this.#db.prepare("SELECT * FROM messages WHERE id = ?").get(messageId);
|
|
@@ -972,7 +1037,6 @@ var SqliteContextStore = class extends ContextStore {
|
|
|
972
1037
|
name: row.name,
|
|
973
1038
|
type: row.type ?? void 0,
|
|
974
1039
|
data: JSON.parse(row.data),
|
|
975
|
-
persist: row.persist === 1,
|
|
976
1040
|
createdAt: row.createdAt
|
|
977
1041
|
};
|
|
978
1042
|
}
|
|
@@ -994,7 +1058,6 @@ var SqliteContextStore = class extends ContextStore {
|
|
|
994
1058
|
name: row.name,
|
|
995
1059
|
type: row.type ?? void 0,
|
|
996
1060
|
data: JSON.parse(row.data),
|
|
997
|
-
persist: row.persist === 1,
|
|
998
1061
|
createdAt: row.createdAt
|
|
999
1062
|
}));
|
|
1000
1063
|
}
|
|
@@ -1155,7 +1218,6 @@ var SqliteContextStore = class extends ContextStore {
|
|
|
1155
1218
|
m.name,
|
|
1156
1219
|
m.type,
|
|
1157
1220
|
m.data,
|
|
1158
|
-
m.persist,
|
|
1159
1221
|
m.createdAt,
|
|
1160
1222
|
fts.rank,
|
|
1161
1223
|
snippet(messages_fts, 3, '<mark>', '</mark>', '...', 32) as snippet
|
|
@@ -1181,7 +1243,6 @@ var SqliteContextStore = class extends ContextStore {
|
|
|
1181
1243
|
name: row.name,
|
|
1182
1244
|
type: row.type ?? void 0,
|
|
1183
1245
|
data: JSON.parse(row.data),
|
|
1184
|
-
persist: row.persist === 1,
|
|
1185
1246
|
createdAt: row.createdAt
|
|
1186
1247
|
},
|
|
1187
1248
|
rank: row.rank,
|
|
@@ -1305,7 +1366,7 @@ function visualizeGraph(data) {
|
|
|
1305
1366
|
// packages/context/src/index.ts
|
|
1306
1367
|
var ContextEngine = class {
|
|
1307
1368
|
/** Non-message fragments (role, hints, etc.) - not persisted in graph */
|
|
1308
|
-
#
|
|
1369
|
+
#fragments = [];
|
|
1309
1370
|
/** Pending message fragments to be added to graph */
|
|
1310
1371
|
#pendingMessages = [];
|
|
1311
1372
|
#store;
|
|
@@ -1329,18 +1390,7 @@ var ContextEngine = class {
|
|
|
1329
1390
|
if (this.#initialized) {
|
|
1330
1391
|
return;
|
|
1331
1392
|
}
|
|
1332
|
-
|
|
1333
|
-
if (existingChat) {
|
|
1334
|
-
this.#chatData = existingChat;
|
|
1335
|
-
} else {
|
|
1336
|
-
const now = Date.now();
|
|
1337
|
-
this.#chatData = {
|
|
1338
|
-
id: this.#chatId,
|
|
1339
|
-
createdAt: now,
|
|
1340
|
-
updatedAt: now
|
|
1341
|
-
};
|
|
1342
|
-
await this.#store.createChat(this.#chatData);
|
|
1343
|
-
}
|
|
1393
|
+
this.#chatData = await this.#store.upsertChat({ id: this.#chatId });
|
|
1344
1394
|
const existingBranch = await this.#store.getBranch(
|
|
1345
1395
|
this.#chatId,
|
|
1346
1396
|
this.#branchName
|
|
@@ -1360,6 +1410,41 @@ var ContextEngine = class {
|
|
|
1360
1410
|
}
|
|
1361
1411
|
this.#initialized = true;
|
|
1362
1412
|
}
|
|
1413
|
+
/**
|
|
1414
|
+
* Create a new branch from a specific message.
|
|
1415
|
+
* Shared logic between rewind() and btw().
|
|
1416
|
+
*/
|
|
1417
|
+
async #createBranchFrom(messageId, switchTo) {
|
|
1418
|
+
const branches = await this.#store.listBranches(this.#chatId);
|
|
1419
|
+
const samePrefix = branches.filter(
|
|
1420
|
+
(b) => b.name === this.#branchName || b.name.startsWith(`${this.#branchName}-v`)
|
|
1421
|
+
);
|
|
1422
|
+
const newBranchName = `${this.#branchName}-v${samePrefix.length + 1}`;
|
|
1423
|
+
const newBranch = {
|
|
1424
|
+
id: crypto.randomUUID(),
|
|
1425
|
+
chatId: this.#chatId,
|
|
1426
|
+
name: newBranchName,
|
|
1427
|
+
headMessageId: messageId,
|
|
1428
|
+
isActive: false,
|
|
1429
|
+
createdAt: Date.now()
|
|
1430
|
+
};
|
|
1431
|
+
await this.#store.createBranch(newBranch);
|
|
1432
|
+
if (switchTo) {
|
|
1433
|
+
await this.#store.setActiveBranch(this.#chatId, newBranch.id);
|
|
1434
|
+
this.#branch = { ...newBranch, isActive: true };
|
|
1435
|
+
this.#branchName = newBranchName;
|
|
1436
|
+
this.#pendingMessages = [];
|
|
1437
|
+
}
|
|
1438
|
+
const chain = await this.#store.getMessageChain(messageId);
|
|
1439
|
+
return {
|
|
1440
|
+
id: newBranch.id,
|
|
1441
|
+
name: newBranch.name,
|
|
1442
|
+
headMessageId: newBranch.headMessageId,
|
|
1443
|
+
isActive: switchTo,
|
|
1444
|
+
messageCount: chain.length,
|
|
1445
|
+
createdAt: newBranch.createdAt
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1363
1448
|
/**
|
|
1364
1449
|
* Get the current chat ID.
|
|
1365
1450
|
*/
|
|
@@ -1399,7 +1484,7 @@ var ContextEngine = class {
|
|
|
1399
1484
|
if (isMessageFragment(fragment2)) {
|
|
1400
1485
|
this.#pendingMessages.push(fragment2);
|
|
1401
1486
|
} else {
|
|
1402
|
-
this.#
|
|
1487
|
+
this.#fragments.push(fragment2);
|
|
1403
1488
|
}
|
|
1404
1489
|
}
|
|
1405
1490
|
return this;
|
|
@@ -1409,7 +1494,7 @@ var ContextEngine = class {
|
|
|
1409
1494
|
* @internal Use resolve() instead for public API.
|
|
1410
1495
|
*/
|
|
1411
1496
|
render(renderer) {
|
|
1412
|
-
return renderer.render(this.#
|
|
1497
|
+
return renderer.render(this.#fragments);
|
|
1413
1498
|
}
|
|
1414
1499
|
/**
|
|
1415
1500
|
* Resolve context into AI SDK-ready format.
|
|
@@ -1428,26 +1513,21 @@ var ContextEngine = class {
|
|
|
1428
1513
|
* await generateText({ system: systemPrompt, messages });
|
|
1429
1514
|
* ```
|
|
1430
1515
|
*/
|
|
1431
|
-
async resolve(options
|
|
1516
|
+
async resolve(options) {
|
|
1432
1517
|
await this.#ensureInitialized();
|
|
1433
|
-
const
|
|
1434
|
-
const
|
|
1435
|
-
const persistedMessages = [];
|
|
1518
|
+
const systemPrompt = options.renderer.render(this.#fragments);
|
|
1519
|
+
const messages = [];
|
|
1436
1520
|
if (this.#branch?.headMessageId) {
|
|
1437
1521
|
const chain = await this.#store.getMessageChain(
|
|
1438
1522
|
this.#branch.headMessageId
|
|
1439
1523
|
);
|
|
1440
|
-
|
|
1524
|
+
for (const msg of chain) {
|
|
1525
|
+
messages.push(message(msg.data).codec?.decode());
|
|
1526
|
+
}
|
|
1441
1527
|
}
|
|
1442
|
-
const messages = persistedMessages.map((msg) => ({
|
|
1443
|
-
role: msg.name,
|
|
1444
|
-
content: String(msg.data)
|
|
1445
|
-
}));
|
|
1446
1528
|
for (const fragment2 of this.#pendingMessages) {
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
content: String(fragment2.data)
|
|
1450
|
-
});
|
|
1529
|
+
const decoded = fragment2.codec.decode();
|
|
1530
|
+
messages.push(decoded);
|
|
1451
1531
|
}
|
|
1452
1532
|
return { systemPrompt, messages };
|
|
1453
1533
|
}
|
|
@@ -1479,8 +1559,7 @@ var ContextEngine = class {
|
|
|
1479
1559
|
parentId,
|
|
1480
1560
|
name: fragment2.name,
|
|
1481
1561
|
type: fragment2.type,
|
|
1482
|
-
data: fragment2.
|
|
1483
|
-
persist: fragment2.persist ?? true,
|
|
1562
|
+
data: fragment2.codec.encode(),
|
|
1484
1563
|
createdAt: now
|
|
1485
1564
|
};
|
|
1486
1565
|
await this.#store.addMessage(messageData);
|
|
@@ -1488,25 +1567,85 @@ var ContextEngine = class {
|
|
|
1488
1567
|
}
|
|
1489
1568
|
await this.#store.updateBranchHead(this.#branch.id, parentId);
|
|
1490
1569
|
this.#branch.headMessageId = parentId;
|
|
1491
|
-
await this.#store.updateChat(this.#chatId, { updatedAt: now });
|
|
1492
|
-
if (this.#chatData) {
|
|
1493
|
-
this.#chatData.updatedAt = now;
|
|
1494
|
-
}
|
|
1495
1570
|
this.#pendingMessages = [];
|
|
1496
1571
|
}
|
|
1497
1572
|
/**
|
|
1498
|
-
* Estimate token count and cost for the
|
|
1573
|
+
* Estimate token count and cost for the full context.
|
|
1574
|
+
*
|
|
1575
|
+
* Includes:
|
|
1576
|
+
* - System prompt fragments (role, hints, etc.)
|
|
1577
|
+
* - Persisted chat messages (from store)
|
|
1578
|
+
* - Pending messages (not yet saved)
|
|
1499
1579
|
*
|
|
1500
1580
|
* @param modelId - Model ID (e.g., "openai:gpt-4o", "anthropic:claude-3-5-sonnet")
|
|
1501
1581
|
* @param options - Optional settings
|
|
1502
|
-
* @returns Estimate result with token counts and
|
|
1582
|
+
* @returns Estimate result with token counts, costs, and per-fragment breakdown
|
|
1503
1583
|
*/
|
|
1504
1584
|
async estimate(modelId, options = {}) {
|
|
1585
|
+
await this.#ensureInitialized();
|
|
1505
1586
|
const renderer = options.renderer ?? new XmlRenderer();
|
|
1506
|
-
const renderedContext = this.render(renderer);
|
|
1507
1587
|
const registry = getModelsRegistry();
|
|
1508
1588
|
await registry.load();
|
|
1509
|
-
|
|
1589
|
+
const model = registry.get(modelId);
|
|
1590
|
+
if (!model) {
|
|
1591
|
+
throw new Error(
|
|
1592
|
+
`Model "${modelId}" not found. Call load() first or check model ID.`
|
|
1593
|
+
);
|
|
1594
|
+
}
|
|
1595
|
+
const tokenizer = registry.getTokenizer(modelId);
|
|
1596
|
+
const fragmentEstimates = [];
|
|
1597
|
+
for (const fragment2 of this.#fragments) {
|
|
1598
|
+
const rendered = renderer.render([fragment2]);
|
|
1599
|
+
const tokens = tokenizer.count(rendered);
|
|
1600
|
+
const cost = tokens / 1e6 * model.cost.input;
|
|
1601
|
+
fragmentEstimates.push({
|
|
1602
|
+
id: fragment2.id,
|
|
1603
|
+
name: fragment2.name,
|
|
1604
|
+
tokens,
|
|
1605
|
+
cost
|
|
1606
|
+
});
|
|
1607
|
+
}
|
|
1608
|
+
if (this.#branch?.headMessageId) {
|
|
1609
|
+
const chain = await this.#store.getMessageChain(
|
|
1610
|
+
this.#branch.headMessageId
|
|
1611
|
+
);
|
|
1612
|
+
for (const msg of chain) {
|
|
1613
|
+
const content = String(msg.data);
|
|
1614
|
+
const tokens = tokenizer.count(content);
|
|
1615
|
+
const cost = tokens / 1e6 * model.cost.input;
|
|
1616
|
+
fragmentEstimates.push({
|
|
1617
|
+
name: msg.name,
|
|
1618
|
+
id: msg.id,
|
|
1619
|
+
tokens,
|
|
1620
|
+
cost
|
|
1621
|
+
});
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
for (const fragment2 of this.#pendingMessages) {
|
|
1625
|
+
const content = String(fragment2.data);
|
|
1626
|
+
const tokens = tokenizer.count(content);
|
|
1627
|
+
const cost = tokens / 1e6 * model.cost.input;
|
|
1628
|
+
fragmentEstimates.push({
|
|
1629
|
+
name: fragment2.name,
|
|
1630
|
+
id: fragment2.id,
|
|
1631
|
+
tokens,
|
|
1632
|
+
cost
|
|
1633
|
+
});
|
|
1634
|
+
}
|
|
1635
|
+
const totalTokens = fragmentEstimates.reduce((sum, f) => sum + f.tokens, 0);
|
|
1636
|
+
const totalCost = fragmentEstimates.reduce((sum, f) => sum + f.cost, 0);
|
|
1637
|
+
return {
|
|
1638
|
+
model: model.id,
|
|
1639
|
+
provider: model.provider,
|
|
1640
|
+
tokens: totalTokens,
|
|
1641
|
+
cost: totalCost,
|
|
1642
|
+
limits: {
|
|
1643
|
+
context: model.limit.context,
|
|
1644
|
+
output: model.limit.output,
|
|
1645
|
+
exceedsContext: totalTokens > model.limit.context
|
|
1646
|
+
},
|
|
1647
|
+
fragments: fragmentEstimates
|
|
1648
|
+
};
|
|
1510
1649
|
}
|
|
1511
1650
|
/**
|
|
1512
1651
|
* Rewind to a specific message by ID.
|
|
@@ -1533,37 +1672,14 @@ var ContextEngine = class {
|
|
|
1533
1672
|
*/
|
|
1534
1673
|
async rewind(messageId) {
|
|
1535
1674
|
await this.#ensureInitialized();
|
|
1536
|
-
const
|
|
1537
|
-
if (!
|
|
1675
|
+
const message2 = await this.#store.getMessage(messageId);
|
|
1676
|
+
if (!message2) {
|
|
1538
1677
|
throw new Error(`Message "${messageId}" not found`);
|
|
1539
1678
|
}
|
|
1540
|
-
if (
|
|
1679
|
+
if (message2.chatId !== this.#chatId) {
|
|
1541
1680
|
throw new Error(`Message "${messageId}" belongs to a different chat`);
|
|
1542
1681
|
}
|
|
1543
|
-
|
|
1544
|
-
const newBranchName = `${this.#branchName}-v${branches.length + 1}`;
|
|
1545
|
-
const newBranch = {
|
|
1546
|
-
id: crypto.randomUUID(),
|
|
1547
|
-
chatId: this.#chatId,
|
|
1548
|
-
name: newBranchName,
|
|
1549
|
-
headMessageId: messageId,
|
|
1550
|
-
isActive: false,
|
|
1551
|
-
createdAt: Date.now()
|
|
1552
|
-
};
|
|
1553
|
-
await this.#store.createBranch(newBranch);
|
|
1554
|
-
await this.#store.setActiveBranch(this.#chatId, newBranch.id);
|
|
1555
|
-
this.#branch = { ...newBranch, isActive: true };
|
|
1556
|
-
this.#branchName = newBranchName;
|
|
1557
|
-
this.#pendingMessages = [];
|
|
1558
|
-
const chain = await this.#store.getMessageChain(messageId);
|
|
1559
|
-
return {
|
|
1560
|
-
id: newBranch.id,
|
|
1561
|
-
name: newBranch.name,
|
|
1562
|
-
headMessageId: newBranch.headMessageId,
|
|
1563
|
-
isActive: true,
|
|
1564
|
-
messageCount: chain.length,
|
|
1565
|
-
createdAt: newBranch.createdAt
|
|
1566
|
-
};
|
|
1682
|
+
return this.#createBranchFrom(messageId, true);
|
|
1567
1683
|
}
|
|
1568
1684
|
/**
|
|
1569
1685
|
* Create a checkpoint at the current position.
|
|
@@ -1656,6 +1772,44 @@ var ContextEngine = class {
|
|
|
1656
1772
|
this.#branchName = name;
|
|
1657
1773
|
this.#pendingMessages = [];
|
|
1658
1774
|
}
|
|
1775
|
+
/**
|
|
1776
|
+
* Create a parallel branch from the current position ("by the way").
|
|
1777
|
+
*
|
|
1778
|
+
* Use this when you want to fork the conversation without leaving
|
|
1779
|
+
* the current branch. Common use case: user wants to ask another
|
|
1780
|
+
* question while waiting for the model to respond.
|
|
1781
|
+
*
|
|
1782
|
+
* Unlike rewind(), this method:
|
|
1783
|
+
* - Uses the current HEAD (no messageId needed)
|
|
1784
|
+
* - Does NOT switch to the new branch
|
|
1785
|
+
* - Keeps pending messages intact
|
|
1786
|
+
*
|
|
1787
|
+
* @returns The new branch info (does not switch to it)
|
|
1788
|
+
* @throws Error if no messages exist in the conversation
|
|
1789
|
+
*
|
|
1790
|
+
* @example
|
|
1791
|
+
* ```ts
|
|
1792
|
+
* // User asked a question, model is generating...
|
|
1793
|
+
* context.set(user('What is the weather?'));
|
|
1794
|
+
* await context.save();
|
|
1795
|
+
*
|
|
1796
|
+
* // User wants to ask something else without waiting
|
|
1797
|
+
* const newBranch = await context.btw();
|
|
1798
|
+
* // newBranch = { name: 'main-v2', ... }
|
|
1799
|
+
*
|
|
1800
|
+
* // Later, switch to the new branch and add the question
|
|
1801
|
+
* await context.switchBranch(newBranch.name);
|
|
1802
|
+
* context.set(user('Also, what time is it?'));
|
|
1803
|
+
* await context.save();
|
|
1804
|
+
* ```
|
|
1805
|
+
*/
|
|
1806
|
+
async btw() {
|
|
1807
|
+
await this.#ensureInitialized();
|
|
1808
|
+
if (!this.#branch?.headMessageId) {
|
|
1809
|
+
throw new Error("Cannot create btw branch: no messages in conversation");
|
|
1810
|
+
}
|
|
1811
|
+
return this.#createBranchFrom(this.#branch.headMessageId, false);
|
|
1812
|
+
}
|
|
1659
1813
|
/**
|
|
1660
1814
|
* Update metadata for the current chat.
|
|
1661
1815
|
*
|
|
@@ -1671,10 +1825,7 @@ var ContextEngine = class {
|
|
|
1671
1825
|
*/
|
|
1672
1826
|
async updateChat(updates) {
|
|
1673
1827
|
await this.#ensureInitialized();
|
|
1674
|
-
const
|
|
1675
|
-
const storeUpdates = {
|
|
1676
|
-
updatedAt: now
|
|
1677
|
-
};
|
|
1828
|
+
const storeUpdates = {};
|
|
1678
1829
|
if (updates.title !== void 0) {
|
|
1679
1830
|
storeUpdates.title = updates.title;
|
|
1680
1831
|
}
|
|
@@ -1684,16 +1835,7 @@ var ContextEngine = class {
|
|
|
1684
1835
|
...updates.metadata
|
|
1685
1836
|
};
|
|
1686
1837
|
}
|
|
1687
|
-
await this.#store.updateChat(this.#chatId, storeUpdates);
|
|
1688
|
-
if (this.#chatData) {
|
|
1689
|
-
if (storeUpdates.title !== void 0) {
|
|
1690
|
-
this.#chatData.title = storeUpdates.title;
|
|
1691
|
-
}
|
|
1692
|
-
if (storeUpdates.metadata !== void 0) {
|
|
1693
|
-
this.#chatData.metadata = storeUpdates.metadata;
|
|
1694
|
-
}
|
|
1695
|
-
this.#chatData.updatedAt = now;
|
|
1696
|
-
}
|
|
1838
|
+
this.#chatData = await this.#store.updateChat(this.#chatId, storeUpdates);
|
|
1697
1839
|
}
|
|
1698
1840
|
/**
|
|
1699
1841
|
* Consolidate context fragments (no-op for now).
|
|
@@ -1706,6 +1848,54 @@ var ContextEngine = class {
|
|
|
1706
1848
|
consolidate() {
|
|
1707
1849
|
return void 0;
|
|
1708
1850
|
}
|
|
1851
|
+
/**
|
|
1852
|
+
* Inspect the full context state for debugging.
|
|
1853
|
+
* Returns a comprehensive JSON-serializable object with all context information.
|
|
1854
|
+
*
|
|
1855
|
+
* @param options - Inspection options (modelId and renderer required)
|
|
1856
|
+
* @returns Complete inspection data including estimates, rendered output, fragments, and graph
|
|
1857
|
+
*
|
|
1858
|
+
* @example
|
|
1859
|
+
* ```ts
|
|
1860
|
+
* const inspection = await context.inspect({
|
|
1861
|
+
* modelId: 'openai:gpt-4o',
|
|
1862
|
+
* renderer: new XmlRenderer(),
|
|
1863
|
+
* });
|
|
1864
|
+
* console.log(JSON.stringify(inspection, null, 2));
|
|
1865
|
+
*
|
|
1866
|
+
* // Or write to file for analysis
|
|
1867
|
+
* await fs.writeFile('context-debug.json', JSON.stringify(inspection, null, 2));
|
|
1868
|
+
* ```
|
|
1869
|
+
*/
|
|
1870
|
+
async inspect(options) {
|
|
1871
|
+
await this.#ensureInitialized();
|
|
1872
|
+
const { renderer } = options;
|
|
1873
|
+
const estimateResult = await this.estimate(options.modelId, { renderer });
|
|
1874
|
+
const rendered = renderer.render(this.#fragments);
|
|
1875
|
+
const persistedMessages = [];
|
|
1876
|
+
if (this.#branch?.headMessageId) {
|
|
1877
|
+
const chain = await this.#store.getMessageChain(
|
|
1878
|
+
this.#branch.headMessageId
|
|
1879
|
+
);
|
|
1880
|
+
persistedMessages.push(...chain);
|
|
1881
|
+
}
|
|
1882
|
+
const graph = await this.#store.getGraph(this.#chatId);
|
|
1883
|
+
return {
|
|
1884
|
+
estimate: estimateResult,
|
|
1885
|
+
rendered,
|
|
1886
|
+
fragments: {
|
|
1887
|
+
context: [...this.#fragments],
|
|
1888
|
+
pending: [...this.#pendingMessages],
|
|
1889
|
+
persisted: persistedMessages
|
|
1890
|
+
},
|
|
1891
|
+
graph,
|
|
1892
|
+
meta: {
|
|
1893
|
+
chatId: this.#chatId,
|
|
1894
|
+
branch: this.#branchName,
|
|
1895
|
+
timestamp: Date.now()
|
|
1896
|
+
}
|
|
1897
|
+
};
|
|
1898
|
+
}
|
|
1709
1899
|
};
|
|
1710
1900
|
function hint(text) {
|
|
1711
1901
|
return {
|
|
@@ -1725,24 +1915,75 @@ function role(content) {
|
|
|
1725
1915
|
data: content
|
|
1726
1916
|
};
|
|
1727
1917
|
}
|
|
1728
|
-
function user(content
|
|
1918
|
+
function user(content) {
|
|
1919
|
+
const message2 = typeof content === "string" ? {
|
|
1920
|
+
id: generateId(),
|
|
1921
|
+
role: "user",
|
|
1922
|
+
parts: [{ type: "text", text: content }]
|
|
1923
|
+
} : content;
|
|
1729
1924
|
return {
|
|
1730
|
-
id:
|
|
1925
|
+
id: message2.id,
|
|
1731
1926
|
name: "user",
|
|
1732
|
-
data: content,
|
|
1927
|
+
data: "content",
|
|
1733
1928
|
type: "message",
|
|
1734
|
-
persist: true
|
|
1929
|
+
persist: true,
|
|
1930
|
+
codec: {
|
|
1931
|
+
decode() {
|
|
1932
|
+
return message2;
|
|
1933
|
+
},
|
|
1934
|
+
encode() {
|
|
1935
|
+
return message2;
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1735
1938
|
};
|
|
1736
1939
|
}
|
|
1737
|
-
function assistant(
|
|
1940
|
+
function assistant(message2) {
|
|
1738
1941
|
return {
|
|
1739
|
-
id:
|
|
1942
|
+
id: message2.id,
|
|
1740
1943
|
name: "assistant",
|
|
1741
|
-
data: content,
|
|
1944
|
+
data: "content",
|
|
1742
1945
|
type: "message",
|
|
1743
|
-
persist: true
|
|
1946
|
+
persist: true,
|
|
1947
|
+
codec: {
|
|
1948
|
+
decode() {
|
|
1949
|
+
return message2;
|
|
1950
|
+
},
|
|
1951
|
+
encode() {
|
|
1952
|
+
return message2;
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1744
1955
|
};
|
|
1745
1956
|
}
|
|
1957
|
+
function message(content) {
|
|
1958
|
+
const message2 = typeof content === "string" ? {
|
|
1959
|
+
id: generateId(),
|
|
1960
|
+
role: "user",
|
|
1961
|
+
parts: [{ type: "text", text: content }]
|
|
1962
|
+
} : content;
|
|
1963
|
+
return {
|
|
1964
|
+
id: message2.id,
|
|
1965
|
+
name: "message",
|
|
1966
|
+
data: "content",
|
|
1967
|
+
type: "message",
|
|
1968
|
+
persist: true,
|
|
1969
|
+
codec: {
|
|
1970
|
+
decode() {
|
|
1971
|
+
return message2;
|
|
1972
|
+
},
|
|
1973
|
+
encode() {
|
|
1974
|
+
return message2;
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
};
|
|
1978
|
+
}
|
|
1979
|
+
function assistantText(content, options) {
|
|
1980
|
+
const id = options?.id ?? crypto.randomUUID();
|
|
1981
|
+
return assistant({
|
|
1982
|
+
id,
|
|
1983
|
+
role: "assistant",
|
|
1984
|
+
parts: [{ type: "text", text: content }]
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1746
1987
|
export {
|
|
1747
1988
|
ContextEngine,
|
|
1748
1989
|
ContextStore,
|
|
@@ -1754,11 +1995,14 @@ export {
|
|
|
1754
1995
|
ToonRenderer,
|
|
1755
1996
|
XmlRenderer,
|
|
1756
1997
|
assistant,
|
|
1998
|
+
assistantText,
|
|
1757
1999
|
defaultTokenizer,
|
|
2000
|
+
estimate,
|
|
1758
2001
|
fragment,
|
|
1759
2002
|
getModelsRegistry,
|
|
1760
2003
|
hint,
|
|
1761
2004
|
isMessageFragment,
|
|
2005
|
+
message,
|
|
1762
2006
|
role,
|
|
1763
2007
|
user,
|
|
1764
2008
|
visualizeGraph
|