@deepseek-ai/dsh-llm 0.1.2-alpha.5 → 0.1.3-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { Remote, RemoteError, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
3
- import { assertNever, deepFreeze } from "@deepseek-ai/dsh-util-values";
3
+ import { assertNever, deepFreeze, snapshotJsonValue } from "@deepseek-ai/dsh-util-values";
4
4
  import { randomUUID } from "@deepseek-ai/dsh-util-crypto";
5
5
  import { brandString } from "@deepseek-ai/dsh-brand";
6
6
  import z from "@deepseek-ai/schemastery";
@@ -557,6 +557,74 @@ function offloadedImageText(ref, access) {
557
557
  function contentHasImage(content) {
558
558
  return content.some((block) => block.type === "image" || block.type === "tool-result" && contentHasImage(block.content));
559
559
  }
560
+ /**
561
+ * True when typed model content contains a file block, walking nested
562
+ * tool-result content on the same recursion every file policy shares.
563
+ * @param content - typed model content blocks.
564
+ * @returns whether any nested block is a file.
565
+ */
566
+ function contentHasFile(content) {
567
+ return content.some((block) => block.type === "file" || block.type === "tool-result" && contentHasFile(block.content));
568
+ }
569
+ /**
570
+ * Stable model-facing handle for one durable file reference: the address of
571
+ * the verbatim stored copy and the instruction to read it on demand. This is
572
+ * the only representation a provider ever receives for a file.
573
+ * @param ref - durable verbatim file reference.
574
+ * @param readonlyPath - execution-world path of the stored copy, when resolvable.
575
+ * @returns deterministic handle text naming the file, its size, and its address.
576
+ */
577
+ function fileHandleText(ref, readonlyPath) {
578
+ const digest = String(ref.attachmentId).slice(7, 15);
579
+ const identity = `File ${quoted(ref.name)} (${ref.bytes} bytes, sha256:${digest})`;
580
+ if (readonlyPath === void 0) return `[${identity} was uploaded, but the current execution environment cannot access a readable path. Report that limitation if its contents are needed; do not claim to have read it.]`;
581
+ return `[${identity}: verbatim read-only copy saved at ${quoted(readonlyPath)}. Read that path with your file tools when its contents are needed; copy it to a writable location before modifying it. When delegating file work, include this saved path in the delegation prompt; only subagents sharing this execution environment can read it.]`;
582
+ }
583
+ /** Replace every file occurrence, including nested tool results, with handle text. */
584
+ function replaceFilesWithHandles(blocks, resolvePath) {
585
+ let next;
586
+ for (const [index, block] of blocks.entries()) {
587
+ if (block.type === "file") {
588
+ next ??= blocks.slice(0, index);
589
+ next.push({
590
+ type: "text",
591
+ text: fileHandleText(block.attachment, resolvePath(block.attachment))
592
+ });
593
+ continue;
594
+ }
595
+ if (block.type === "tool-result") {
596
+ const content = replaceFilesWithHandles(block.content, resolvePath);
597
+ if (content !== block.content) {
598
+ next ??= blocks.slice(0, index);
599
+ next.push({
600
+ ...block,
601
+ content
602
+ });
603
+ continue;
604
+ }
605
+ }
606
+ next?.push(block);
607
+ }
608
+ return next ?? blocks;
609
+ }
610
+ /**
611
+ * Project durable file history into deterministic handle text for every model
612
+ * route. Unlike images, no provider receives file blocks natively, so this
613
+ * projection is unconditional in request assembly.
614
+ * @param messages - complete request history.
615
+ * @param resolvePath - resolve one reference's current execution-world read path.
616
+ * @returns the original list without files, otherwise shallow message copies with handle text.
617
+ */
618
+ function projectFilesToText(messages, resolvePath) {
619
+ if (!messages.some((message) => contentHasFile(message.content))) return messages;
620
+ return messages.map((message) => {
621
+ const content = replaceFilesWithHandles(message.content, resolvePath);
622
+ return content === message.content ? message : {
623
+ ...message,
624
+ content
625
+ };
626
+ });
627
+ }
560
628
  /** Base64 length of raw image bytes, including padding. */
561
629
  function base64Length(bytes) {
562
630
  return Math.ceil(bytes / 3) * 4;
@@ -770,6 +838,14 @@ function ProviderRequestId(id) {
770
838
  return brandString(id);
771
839
  }
772
840
  /**
841
+ * Brand one loop-owned streaming attempt identifier.
842
+ * @param id - the opaque Agent-lifecycle-local identifier.
843
+ * @returns the same string with the attempt-id brand.
844
+ */
845
+ function LlmAttemptId(id) {
846
+ return brandString(id);
847
+ }
848
+ /**
773
849
  * Brand an adapter-owned reasoning-effort identifier.
774
850
  * @param id - the opaque identifier exposed by one model capability.
775
851
  * @returns the same string, branded; no validation is performed.
@@ -973,6 +1049,446 @@ var BlockAssembler = class {
973
1049
  }
974
1050
  };
975
1051
  //#endregion
1052
+ //#region lib/types/assistant-stream.js
1053
+ /**
1054
+ * Lossless compact representation of one model-stream attempt, plus record-level
1055
+ * readers that answer common consumer questions without materializing members.
1056
+ * Readers trust the static record type; expandAssistantStream is the validating
1057
+ * path for records read at a durable boundary.
1058
+ */
1059
+ function safeTime(value) {
1060
+ if (!Number.isSafeInteger(value)) throw new TypeError(`Assistant stream time must be a safe integer, got ${String(value)}`);
1061
+ return value;
1062
+ }
1063
+ function safeIndex(value, label) {
1064
+ if (!Number.isSafeInteger(value) || value < 0 || Object.is(value, -0)) throw new TypeError(`${label} index must be a non-negative safe integer`);
1065
+ return value;
1066
+ }
1067
+ function snapshotChunk(chunk) {
1068
+ const snapshot = snapshotJsonValue(chunk);
1069
+ if (snapshot === void 0) throw new TypeError("Assistant stream chunk must be losslessly JSON-serializable");
1070
+ return snapshot;
1071
+ }
1072
+ function safeGap(previous, next) {
1073
+ const gap = next - previous;
1074
+ return Number.isSafeInteger(gap) && previous + gap === next ? gap : void 0;
1075
+ }
1076
+ /** Incrementally compacts one attempt without retaining a second raw-chunk list. */
1077
+ var AssistantStreamAccumulator = class {
1078
+ records = [];
1079
+ /**
1080
+ * Add one timed chunk to the compact attempt stream.
1081
+ * @param value - model chunk and its original Session timestamp.
1082
+ * @returns a detached immutable copy for assembly and live publication.
1083
+ */
1084
+ push(value) {
1085
+ const time = safeTime(value.time);
1086
+ const chunk = snapshotChunk(value.chunk);
1087
+ const timed = deepFreeze({
1088
+ time,
1089
+ chunk
1090
+ });
1091
+ const previous = this.records.at(-1);
1092
+ switch (chunk.type) {
1093
+ case "text-delta":
1094
+ case "reasoning-delta": {
1095
+ safeIndex(chunk.index, chunk.type);
1096
+ if (typeof chunk.text !== "string") throw new TypeError(`${chunk.type} text must be a string`);
1097
+ const type = chunk.type === "text-delta" ? "text-chunks" : "reasoning-chunks";
1098
+ const gap = previous !== void 0 && previous.type === type ? safeGap(previous.lastTime, time) : void 0;
1099
+ if (previous !== void 0 && previous.type === type && previous.index === chunk.index && gap !== void 0) {
1100
+ previous.dt.push(gap);
1101
+ previous.texts.push(chunk.text);
1102
+ previous.lastTime = time;
1103
+ } else this.records.push({
1104
+ type,
1105
+ time0: time,
1106
+ index: chunk.index,
1107
+ dt: [],
1108
+ texts: [chunk.text],
1109
+ lastTime: time
1110
+ });
1111
+ return timed;
1112
+ }
1113
+ case "tool-call-delta": {
1114
+ safeIndex(chunk.index, chunk.type);
1115
+ if (typeof chunk.id !== "string") throw new TypeError("tool-call-delta id must be a string");
1116
+ if (Object.hasOwn(chunk, "name") && typeof chunk.name !== "string") throw new TypeError("tool-call-delta name must be a string");
1117
+ if (typeof chunk.argumentsDelta !== "string") throw new TypeError("tool-call-delta argumentsDelta must be a string");
1118
+ if (chunk.id.length === 0 || chunk.name === "") {
1119
+ this.records.push({
1120
+ type: "chunk",
1121
+ time,
1122
+ chunk
1123
+ });
1124
+ return timed;
1125
+ }
1126
+ const gap = previous?.type === "tool-call-chunks" ? safeGap(previous.lastTime, time) : void 0;
1127
+ const sameName = previous?.type === "tool-call-chunks" && Object.hasOwn(previous, "name") === Object.hasOwn(chunk, "name") && previous.name === chunk.name;
1128
+ if (previous?.type === "tool-call-chunks" && previous.index === chunk.index && previous.id === chunk.id && sameName && gap !== void 0) {
1129
+ previous.dt.push(gap);
1130
+ previous.args.push(chunk.argumentsDelta);
1131
+ previous.lastTime = time;
1132
+ } else this.records.push({
1133
+ type: "tool-call-chunks",
1134
+ time0: time,
1135
+ index: chunk.index,
1136
+ dt: [],
1137
+ id: chunk.id,
1138
+ ...Object.hasOwn(chunk, "name") ? { name: chunk.name } : {},
1139
+ args: [chunk.argumentsDelta],
1140
+ lastTime: time
1141
+ });
1142
+ return timed;
1143
+ }
1144
+ case "block-start":
1145
+ case "block-end":
1146
+ case "usage":
1147
+ case "finish":
1148
+ this.records.push({
1149
+ type: "chunk",
1150
+ time,
1151
+ chunk
1152
+ });
1153
+ return timed;
1154
+ default: return assertNever(chunk, "AssistantStreamAccumulator.push");
1155
+ }
1156
+ }
1157
+ /**
1158
+ * Return the current compact attempt stream.
1159
+ * @returns a detached immutable record list suitable for a durable event.
1160
+ */
1161
+ snapshot() {
1162
+ return deepFreeze(this.records.map((record) => {
1163
+ if (record.type === "chunk") return { ...record };
1164
+ const { lastTime: _lastTime, ...durable } = record;
1165
+ if (durable.type === "tool-call-chunks") return {
1166
+ ...durable,
1167
+ dt: [...durable.dt],
1168
+ args: [...durable.args]
1169
+ };
1170
+ return {
1171
+ ...durable,
1172
+ dt: [...durable.dt],
1173
+ texts: [...durable.texts]
1174
+ };
1175
+ }));
1176
+ }
1177
+ };
1178
+ /**
1179
+ * Expand compact records into the exact timed chunk sequence.
1180
+ * @param stream - compact records from one durable Assistant settlement.
1181
+ * @returns detached timed chunks with every original delta boundary preserved.
1182
+ * @throws {TypeError} when a record or reconstructed timestamp is invalid.
1183
+ */
1184
+ function expandAssistantStream(stream) {
1185
+ const chunks = [];
1186
+ for (const candidate of stream) {
1187
+ const record = validateRecord(candidate);
1188
+ if (record.type === "chunk") {
1189
+ chunks.push({
1190
+ time: record.time,
1191
+ chunk: record.chunk
1192
+ });
1193
+ continue;
1194
+ }
1195
+ const members = record.type === "tool-call-chunks" ? record.args : record.texts;
1196
+ let time = record.time0;
1197
+ for (let index = 0; index < members.length; index += 1) {
1198
+ if (index > 0) time += record.dt[index - 1];
1199
+ let chunk;
1200
+ if (record.type === "text-chunks") chunk = {
1201
+ type: "text-delta",
1202
+ index: record.index,
1203
+ text: members[index]
1204
+ };
1205
+ else if (record.type === "reasoning-chunks") chunk = {
1206
+ type: "reasoning-delta",
1207
+ index: record.index,
1208
+ text: members[index]
1209
+ };
1210
+ else chunk = {
1211
+ type: "tool-call-delta",
1212
+ index: record.index,
1213
+ id: record.id,
1214
+ ...Object.hasOwn(record, "name") ? { name: record.name } : {},
1215
+ argumentsDelta: members[index]
1216
+ };
1217
+ chunks.push({
1218
+ time,
1219
+ chunk
1220
+ });
1221
+ }
1222
+ }
1223
+ return chunks;
1224
+ }
1225
+ function hasNonWhitespace(text) {
1226
+ return /\S/.test(text);
1227
+ }
1228
+ function blockIsVisible(block) {
1229
+ if (block.type === "tool-call") return false;
1230
+ if (block.type === "text" || block.type === "reasoning") return hasNonWhitespace(block.text);
1231
+ return true;
1232
+ }
1233
+ /**
1234
+ * Whether one chunk carries the model's first output token for latency measurement.
1235
+ * @param chunk - any stream chunk.
1236
+ * @returns true for a non-empty text, reasoning, or Tool-call arguments fragment and for
1237
+ * every name-bearing Tool-call delta; false for block, usage, and finish chunks.
1238
+ */
1239
+ function isTokenDelta(chunk) {
1240
+ switch (chunk.type) {
1241
+ case "text-delta":
1242
+ case "reasoning-delta": return chunk.text !== "";
1243
+ case "tool-call-delta": return chunk.argumentsDelta !== "" || chunk.name !== void 0;
1244
+ default: return false;
1245
+ }
1246
+ }
1247
+ /**
1248
+ * Whether one chunk by itself contributes reader-visible transcript content.
1249
+ * Text and reasoning count only with non-whitespace content, streamed as a delta or
1250
+ * completed as a block; a block of any other kind counts at its start and its end,
1251
+ * except a Tool call, which is protocol rather than content. Usage and finish never count.
1252
+ * @param chunk - any stream chunk.
1253
+ * @returns whether a transcript reader would see this chunk.
1254
+ */
1255
+ function isVisibleChunk(chunk) {
1256
+ switch (chunk.type) {
1257
+ case "text-delta":
1258
+ case "reasoning-delta": return hasNonWhitespace(chunk.text);
1259
+ case "block-start": return chunk.blockType !== "text" && chunk.blockType !== "reasoning" && chunk.blockType !== "tool-call";
1260
+ case "block-end": return blockIsVisible(chunk.block);
1261
+ default: return false;
1262
+ }
1263
+ }
1264
+ /**
1265
+ * Whether one chunk carries non-whitespace text, as a text delta or a completed text block.
1266
+ * Reasoning, Tool calls, and other block kinds never count.
1267
+ * @param chunk - any stream chunk.
1268
+ * @returns whether the chunk contributes visible text.
1269
+ */
1270
+ function chunkHasVisibleText(chunk) {
1271
+ if (chunk.type === "text-delta") return hasNonWhitespace(chunk.text);
1272
+ return chunk.type === "block-end" && chunk.block.type === "text" && hasNonWhitespace(chunk.block.text);
1273
+ }
1274
+ function firstRunMemberTime(run, predicate) {
1275
+ const fragments = run.type === "tool-call-chunks" ? run.args : run.texts;
1276
+ let time = run.time0;
1277
+ for (let index = 0; index < fragments.length; index += 1) {
1278
+ if (index > 0) time += run.dt[index - 1];
1279
+ if (predicate(fragments[index])) return time;
1280
+ }
1281
+ }
1282
+ /**
1283
+ * Time of the first member of one packed run that {@link isTokenDelta} accepts: a
1284
+ * name-bearing Tool-call run starts at its first member, otherwise the first non-empty fragment.
1285
+ * Stops scanning at that member.
1286
+ * @param run - one packed delta run.
1287
+ * @returns the member's reconstructed time, or undefined when no member qualifies.
1288
+ */
1289
+ function runFirstTokenTime(run) {
1290
+ if (run.type === "tool-call-chunks" && run.name !== void 0) return run.time0;
1291
+ return firstRunMemberTime(run, (fragment) => fragment !== "");
1292
+ }
1293
+ /**
1294
+ * Time of the first member of one packed run that {@link isVisibleChunk} accepts: the first
1295
+ * non-whitespace text or reasoning fragment. A Tool-call run has none. Stops scanning at that member.
1296
+ * @param run - one packed delta run.
1297
+ * @returns the member's reconstructed time, or undefined when no member qualifies.
1298
+ */
1299
+ function runFirstVisibleTime(run) {
1300
+ return run.type === "tool-call-chunks" ? void 0 : firstRunMemberTime(run, hasNonWhitespace);
1301
+ }
1302
+ /**
1303
+ * Time of the first token in one compact stream per {@link isTokenDelta}, read from the
1304
+ * records themselves and stopping at the first qualifying member.
1305
+ * @param stream - compact records from one durable Assistant settlement.
1306
+ * @returns the first token's time, or undefined when the stream carries no token.
1307
+ */
1308
+ function assistantStreamFirstTokenTime(stream) {
1309
+ for (const record of stream) {
1310
+ const time = record.type === "chunk" ? isTokenDelta(record.chunk) ? record.time : void 0 : runFirstTokenTime(record);
1311
+ if (time !== void 0) return time;
1312
+ }
1313
+ }
1314
+ /**
1315
+ * Whether one compact stream carries any reader-visible content per {@link isVisibleChunk},
1316
+ * stopping at the first qualifying member.
1317
+ * @param stream - compact records from one durable Assistant settlement.
1318
+ * @returns whether a transcript reader would see anything from this stream.
1319
+ */
1320
+ function assistantStreamHasVisibleContent(stream) {
1321
+ return stream.some((record) => record.type === "chunk" ? isVisibleChunk(record.chunk) : runFirstVisibleTime(record) !== void 0);
1322
+ }
1323
+ /**
1324
+ * Whether one compact stream carries non-whitespace text per {@link chunkHasVisibleText},
1325
+ * stopping at the first qualifying member.
1326
+ * @param stream - compact records from one durable Assistant settlement.
1327
+ * @returns whether the stream contributes visible text.
1328
+ */
1329
+ function assistantStreamHasVisibleText(stream) {
1330
+ return stream.some((record) => record.type === "text-chunks" ? record.texts.some(hasNonWhitespace) : record.type === "chunk" && chunkHasVisibleText(record.chunk));
1331
+ }
1332
+ /**
1333
+ * The last raw chunk of one never-packed type, scanning backwards and stopping at the first hit.
1334
+ * @param stream - compact records from one durable Assistant settlement.
1335
+ * @param type - chunk type that only appears as a raw record.
1336
+ * @returns the stream's final chunk of that type, or undefined when it has none.
1337
+ */
1338
+ function lastAssistantStreamChunk(stream, type) {
1339
+ for (let index = stream.length - 1; index >= 0; index -= 1) {
1340
+ const record = stream[index];
1341
+ if (record.type === "chunk" && record.chunk.type === type) return record.chunk;
1342
+ }
1343
+ }
1344
+ /**
1345
+ * Every raw chunk of one never-packed type, in stream order.
1346
+ * @param stream - compact records from one durable Assistant settlement.
1347
+ * @param type - chunk type that only appears as a raw record.
1348
+ * @returns the matching chunks; empty when the stream has none.
1349
+ */
1350
+ function assistantStreamChunks(stream, type) {
1351
+ const chunks = [];
1352
+ for (const record of stream) if (record.type === "chunk" && record.chunk.type === type) chunks.push(record.chunk);
1353
+ return chunks;
1354
+ }
1355
+ /**
1356
+ * Every streamed text-delta fragment joined in stream order; reasoning and Tool-call fragments are excluded.
1357
+ * @param stream - compact records from one durable Assistant settlement.
1358
+ * @returns the joined text, empty when the stream carries no text delta.
1359
+ */
1360
+ function joinAssistantStreamText(stream) {
1361
+ const parts = [];
1362
+ for (const record of stream) if (record.type === "text-chunks") parts.push(record.texts.join(""));
1363
+ else if (record.type === "chunk" && record.chunk.type === "text-delta") parts.push(record.chunk.text);
1364
+ return parts.join("");
1365
+ }
1366
+ /**
1367
+ * Feed one compact stream into a {@link BlockAssembler} without materializing members.
1368
+ * Each run contributes one delta carrying its joined fragments, which assembles the same
1369
+ * blocks as the original per-member deltas because assembly only concatenates them;
1370
+ * raw chunks are pushed as recorded. The records are trusted, not validated: validate a
1371
+ * stream read at a durable boundary with {@link expandAssistantStream} first.
1372
+ * @param stream - compact records from one durable Assistant settlement.
1373
+ * @param assembler - assembler to feed; a fresh one by default.
1374
+ * @returns the same assembler after every record was pushed.
1375
+ */
1376
+ function assembleAssistantStream(stream, assembler = new BlockAssembler()) {
1377
+ for (const record of stream) switch (record.type) {
1378
+ case "chunk":
1379
+ assembler.push(record.chunk);
1380
+ break;
1381
+ case "text-chunks":
1382
+ assembler.push({
1383
+ type: "text-delta",
1384
+ index: record.index,
1385
+ text: record.texts.join("")
1386
+ });
1387
+ break;
1388
+ case "reasoning-chunks":
1389
+ assembler.push({
1390
+ type: "reasoning-delta",
1391
+ index: record.index,
1392
+ text: record.texts.join("")
1393
+ });
1394
+ break;
1395
+ case "tool-call-chunks":
1396
+ assembler.push({
1397
+ type: "tool-call-delta",
1398
+ index: record.index,
1399
+ id: record.id,
1400
+ ...record.name === void 0 ? {} : { name: record.name },
1401
+ argumentsDelta: record.args.join("")
1402
+ });
1403
+ break;
1404
+ default: assertNever(record, "assembleAssistantStream");
1405
+ }
1406
+ return assembler;
1407
+ }
1408
+ function validateRecord(value) {
1409
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("Assistant stream record must be an object");
1410
+ const record = value;
1411
+ switch (record.type) {
1412
+ case "text-chunks":
1413
+ case "reasoning-chunks": {
1414
+ exactKeys(record, [
1415
+ "type",
1416
+ "time0",
1417
+ "index",
1418
+ "dt",
1419
+ "texts"
1420
+ ], record.type);
1421
+ const texts = stringArray(record.texts, `${record.type} texts`);
1422
+ if (texts.length === 0) throw new TypeError(`${record.type} texts must be non-empty`);
1423
+ validateRun(record, texts.length, record.type);
1424
+ return record;
1425
+ }
1426
+ case "tool-call-chunks": {
1427
+ exactKeys(record, Object.hasOwn(record, "name") ? [
1428
+ "type",
1429
+ "time0",
1430
+ "index",
1431
+ "dt",
1432
+ "id",
1433
+ "name",
1434
+ "args"
1435
+ ] : [
1436
+ "type",
1437
+ "time0",
1438
+ "index",
1439
+ "dt",
1440
+ "id",
1441
+ "args"
1442
+ ], record.type);
1443
+ const args = stringArray(record.args, "tool-call-chunks args");
1444
+ if (args.length === 0) throw new TypeError("tool-call-chunks args must be non-empty");
1445
+ if (typeof record.id !== "string" || record.id.length === 0) throw new TypeError("tool-call-chunks id must be a non-empty string");
1446
+ if (record.name !== void 0 && (typeof record.name !== "string" || record.name.length === 0)) throw new TypeError("tool-call-chunks name must be a non-empty string");
1447
+ validateRun(record, args.length, record.type);
1448
+ return record;
1449
+ }
1450
+ case "chunk": {
1451
+ exactKeys(record, [
1452
+ "type",
1453
+ "time",
1454
+ "chunk"
1455
+ ], "chunk");
1456
+ const time = safeTime(record.time);
1457
+ if (typeof record.chunk !== "object" || record.chunk === null || Array.isArray(record.chunk)) throw new TypeError("Assistant stream raw chunk must be a lossless JSON object");
1458
+ let chunk;
1459
+ try {
1460
+ chunk = snapshotChunk(record.chunk);
1461
+ } catch (error) {
1462
+ throw new TypeError("Assistant stream raw chunk must be a lossless JSON object", { cause: error });
1463
+ }
1464
+ return deepFreeze({
1465
+ type: "chunk",
1466
+ time,
1467
+ chunk
1468
+ });
1469
+ }
1470
+ default: throw new TypeError(`Unsupported Assistant stream record ${JSON.stringify(record.type)}`);
1471
+ }
1472
+ }
1473
+ function validateRun(record, members, label) {
1474
+ safeTime(record.time0);
1475
+ safeIndex(record.index, label);
1476
+ if (!Array.isArray(record.dt) || record.dt.some((value) => !Number.isSafeInteger(value))) throw new TypeError(`${label} dt must contain safe integers`);
1477
+ if (record.dt.length !== members - 1) throw new TypeError(`${label} dt length must be one less than its members`);
1478
+ let time = record.time0;
1479
+ for (const gap of record.dt) {
1480
+ time += gap;
1481
+ if (!Number.isSafeInteger(time)) throw new TypeError(`${label} member times must stay safe integers`);
1482
+ }
1483
+ }
1484
+ function stringArray(value, label) {
1485
+ if (!Array.isArray(value) || value.some((member) => typeof member !== "string")) throw new TypeError(`${label} must be a string array`);
1486
+ return value;
1487
+ }
1488
+ function exactKeys(record, keys, label) {
1489
+ if (Object.keys(record).length !== keys.length || !keys.every((key) => Object.hasOwn(record, key))) throw new TypeError(`${label} Assistant stream record must contain exactly ${keys.join(", ")}`);
1490
+ }
1491
+ //#endregion
976
1492
  //#region lib/types/index.js
977
1493
  /**
978
1494
  * LLM service: adapter registry with a waterfall-interceptable streaming call
@@ -1458,6 +1974,15 @@ let LlmRuntime = (() => {
1458
1974
  imageRequestPricing(provider, model) {
1459
1975
  return this.adapters.get(provider)?.adapter.imageRequestPricing(provider, model);
1460
1976
  }
1977
+ /**
1978
+ * Resolve the exact text one durable file occurrence contributes to every
1979
+ * provider request in the current execution environment.
1980
+ * @param ref - durable verbatim file reference from model history.
1981
+ * @returns the same deterministic handle text used at adapter dispatch.
1982
+ */
1983
+ fileRequestText(ref) {
1984
+ return fileHandleText(ref, this.fileReadPath(ref));
1985
+ }
1461
1986
  /** Detach typed adapter-owned modality metadata. */
1462
1987
  detachedModalities(modalities) {
1463
1988
  return modalities === void 0 ? void 0 : [...modalities];
@@ -1651,6 +2176,20 @@ let LlmRuntime = (() => {
1651
2176
  return Object.isFrozen(options) ? deepFreeze(filtered) : filtered;
1652
2177
  }
1653
2178
  /**
2179
+ * Resolve the current execution-world read path of one durable file
2180
+ * reference through the mounted attachment and filesystem providers.
2181
+ */
2182
+ fileReadPath(ref) {
2183
+ let hostPath;
2184
+ try {
2185
+ hostPath = this.ctx.get("attachments")?.fileHostPath(ref);
2186
+ } catch {
2187
+ return;
2188
+ }
2189
+ if (hostPath === void 0) return void 0;
2190
+ return this.ctx.get("fs")?.processPathFromHostPath(hostPath);
2191
+ }
2192
+ /**
1654
2193
  * Final adapter boundary. Adapter selection, dispatch, iterator construction,
1655
2194
  * and iteration failures become one terminal failure chunk. Middleware and
1656
2195
  * downstream consumer failures remain thrown plugin or consumer errors.
@@ -1681,13 +2220,16 @@ let LlmRuntime = (() => {
1681
2220
  ...options,
1682
2221
  ...resolvedConfig
1683
2222
  };
1684
- const projectedOptions = modelInfo.inputModalities !== void 0 && !modelInfo.inputModalities.includes("image") && resolvedOptions.messages.some((message) => contentHasImage(message.content)) ? Object.isFrozen(resolvedOptions) ? deepFreeze({
2223
+ let projectedMessages = resolvedOptions.messages;
2224
+ if (projectedMessages.some((message) => contentHasFile(message.content))) projectedMessages = projectFilesToText(projectedMessages, (ref) => this.fileReadPath(ref));
2225
+ if (modelInfo.inputModalities !== void 0 && !modelInfo.inputModalities.includes("image") && projectedMessages.some((message) => contentHasImage(message.content))) projectedMessages = projectImagesForTextModel(projectedMessages);
2226
+ const projectedOptions = projectedMessages === resolvedOptions.messages ? resolvedOptions : Object.isFrozen(resolvedOptions) ? deepFreeze({
1685
2227
  ...resolvedOptions,
1686
- messages: projectImagesForTextModel(resolvedOptions.messages)
2228
+ messages: projectedMessages
1687
2229
  }) : {
1688
2230
  ...resolvedOptions,
1689
- messages: projectImagesForTextModel(resolvedOptions.messages)
1690
- } : resolvedOptions;
2231
+ messages: projectedMessages
2232
+ };
1691
2233
  iterator = dispatch(this.forAdapter(projectedOptions, adapter))[Symbol.asyncIterator]();
1692
2234
  } catch (error) {
1693
2235
  yield adapterFailureChunk(error, options.signal);
@@ -1755,4 +2297,4 @@ function adapterFailureChunk(error, signal) {
1755
2297
  };
1756
2298
  }
1757
2299
  //#endregion
1758
- export { APP_IDENTITY, BlockAssembler, CONTEXT_SUMMARY_MAX_CHARS, CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, HarnessError, INVALID_CREDENTIAL_CODE, LlmAdapter, LlmError, LlmRuntime, LlmRuntime as default, MessageId, ProviderRequestId, QUOTA_EXCEEDED_CODE, ReasoningEffortId, RetryPolicySchema, ToolCallId, assertUsableApiKey, attributionHeaders, boundContextSummary, callConfigEquals, contentHasImage, createAssistantMessage, createMessage, createToolResultMessage, createUserMessage, errorChain, freezeMessage, isAgentLoopRequest, isContextWindowExceededError, isHarnessError, isQuotaExceededError, markAgentLoopRequest, normalizeApiKey, offloadRequestImagesWithPolicy, offloadedImagePrefixCount, offloadedImageText, projectImagesForTextModel, requestImageHandleText, resolveImageAttachmentAccess, resolveRetryPolicy, textOnlyImageText, userAgent };
2300
+ export { APP_IDENTITY, AssistantStreamAccumulator, BlockAssembler, CONTEXT_SUMMARY_MAX_CHARS, CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, HarnessError, INVALID_CREDENTIAL_CODE, LlmAdapter, LlmAttemptId, LlmError, LlmRuntime, LlmRuntime as default, MessageId, ProviderRequestId, QUOTA_EXCEEDED_CODE, ReasoningEffortId, RetryPolicySchema, ToolCallId, assembleAssistantStream, assertUsableApiKey, assistantStreamChunks, assistantStreamFirstTokenTime, assistantStreamHasVisibleContent, assistantStreamHasVisibleText, attributionHeaders, boundContextSummary, callConfigEquals, chunkHasVisibleText, contentHasFile, contentHasImage, createAssistantMessage, createMessage, createToolResultMessage, createUserMessage, errorChain, expandAssistantStream, fileHandleText, freezeMessage, isAgentLoopRequest, isContextWindowExceededError, isHarnessError, isQuotaExceededError, isTokenDelta, isVisibleChunk, joinAssistantStreamText, lastAssistantStreamChunk, markAgentLoopRequest, normalizeApiKey, offloadRequestImagesWithPolicy, offloadedImagePrefixCount, offloadedImageText, projectFilesToText, projectImagesForTextModel, requestImageHandleText, resolveImageAttachmentAccess, resolveRetryPolicy, runFirstTokenTime, runFirstVisibleTime, textOnlyImageText, userAgent };
@@ -67,7 +67,7 @@ export const TYPERT = {
67
67
  typeSymbol: '@deepseek-ai/dsh-llm#llm/discoverModels:result',
68
68
  schema: _deepseek_ai_dsh_llm_llm_discoverModels_result$schema,
69
69
  },
70
- sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":621,"column":9},
70
+ sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":625,"column":9},
71
71
  },
72
72
  {
73
73
  id: '@deepseek-ai/dsh-llm#llm/listConfigurableProviders',
@@ -82,7 +82,7 @@ export const TYPERT = {
82
82
  typeSymbol: '@deepseek-ai/dsh-llm#llm/listConfigurableProviders:result',
83
83
  schema: _deepseek_ai_dsh_llm_llm_listConfigurableProviders_result$schema,
84
84
  },
85
- sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":534,"column":3},
85
+ sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":538,"column":3},
86
86
  },
87
87
  {
88
88
  id: '@deepseek-ai/dsh-llm#llm/listProviders',
@@ -97,7 +97,7 @@ export const TYPERT = {
97
97
  typeSymbol: '@deepseek-ai/dsh-llm#llm/listProviders:result',
98
98
  schema: _deepseek_ai_dsh_llm_llm_listProviders_result$schema,
99
99
  },
100
- sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":462,"column":3},
100
+ sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":466,"column":3},
101
101
  },
102
102
  ],
103
103
  model: {
@@ -173,6 +173,13 @@ export const TYPERT = {
173
173
  "summary": "Resolve provider-side request-image pricing for one exact route, or `undefined` when the provider is unregistered or declares none.",
174
174
  "jsDoc": "/**\n * Resolve provider-side request-image pricing for one exact route, or\n * `undefined` when the provider is unregistered or declares none. Unknown\n * providers degrade to `undefined` rather than throwing because callers\n * price durable history whose route may no longer be mounted.\n * @param provider - provider route named by a request header.\n * @param model - exact model id named by the same header.\n * @returns the owning adapter's image pricing for the route, when declared.\n */"
175
175
  },
176
+ {
177
+ "kind": "method",
178
+ "name": "fileRequestText",
179
+ "signature": "fileRequestText(ref: FileAttachmentRef): string",
180
+ "summary": "Resolve the exact text one durable file occurrence contributes to every provider request in the current execution environment.",
181
+ "jsDoc": "/**\n * Resolve the exact text one durable file occurrence contributes to every\n * provider request in the current execution environment.\n * @param ref - durable verbatim file reference from model history.\n * @returns the same deterministic handle text used at adapter dispatch.\n */"
182
+ },
176
183
  {
177
184
  "kind": "method",
178
185
  "name": "listModels",
@@ -240,7 +247,7 @@ export const TYPERT = {
240
247
  },
241
248
  {
242
249
  "name": "ContentBlockMap",
243
- "declaration": "export interface ContentBlockMap {\n text: TextBlock;\n reasoning: ReasoningBlock;\n image: ImageBlock;\n 'tool-call': ToolCallBlock;\n 'tool-result': ToolResultBlock;\n}"
250
+ "declaration": "export interface ContentBlockMap {\n text: TextBlock;\n reasoning: ReasoningBlock;\n image: ImageBlock;\n file: FileBlock;\n 'tool-call': ToolCallBlock;\n 'tool-result': ToolResultBlock;\n}"
244
251
  },
245
252
  {
246
253
  "name": "ContentBlockType",
@@ -258,6 +265,14 @@ export const TYPERT = {
258
265
  "name": "DirectoryRegistrationHandle",
259
266
  "declaration": "export interface DirectoryRegistrationHandle {\n (): void;\n replace(entries: readonly LlmConfigurableProvider[]): void;\n}"
260
267
  },
268
+ {
269
+ "name": "FileAttachmentRef",
270
+ "declaration": "export interface FileAttachmentRef {\n attachmentId: AttachmentId;\n name: string;\n bytes: number;\n}"
271
+ },
272
+ {
273
+ "name": "FileBlock",
274
+ "declaration": "export interface FileBlock {\n type: 'file';\n attachment: FileAttachmentRef;\n}"
275
+ },
261
276
  {
262
277
  "name": "FinishReason",
263
278
  "declaration": "export type FinishReason = FinishReasonMap[keyof FinishReasonMap];"
@@ -428,7 +443,7 @@ export const TYPERT = {
428
443
  },
429
444
  {
430
445
  "name": "SessionReferenceSource",
431
- "declaration": "export interface SessionReferenceSource {\n kind: 'session-reference';\n form: 'recall';\n version: 1;\n references: { sessionId: string; label: string; capturedThroughSeq: OptionalSessionSeq; compacted: boolean; originalMessages: number; retainedMessages: number; omittedMessages: number; omittedBytes: number; truncated: boolean; inputIndex: number; }[];\n}"
446
+ "declaration": "export interface SessionReferenceSource {\n kind: 'session-reference';\n form: 'recall';\n version: 1;\n references: { sessionId: string; label: string; capturedFormatVersion?: number; capturedThroughSeq: OptionalSessionSeq; compacted: boolean; originalMessages: number; retainedMessages: number; omittedMessages: number; omittedBytes: number; truncated: boolean; inputIndex: number; }[];\n}"
432
447
  },
433
448
  {
434
449
  "name": "SessionRequestId",