@deepseek-ai/dsh-api-session-controller 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/README.i18n.yaml +2 -2
- package/README.md +6 -6
- package/README.zh.md +6 -6
- package/lib/client.js +592 -28
- package/lib/index.js +269 -214
- package/lib/typert.host.js +183 -141
- package/lib/typert.remote-client.js +79 -97
- package/lib/types/assistant-stream.d.ts +26 -0
- package/lib/types/assistant-stream.js +83 -0
- package/lib/types/client/contract/events.d.ts +38 -7
- package/lib/types/client/contract/events.js +21 -0
- package/lib/types/client/contract/session.d.ts +7 -7
- package/lib/types/client/contract/snapshot.d.ts +15 -2
- package/lib/types/client/index.d.ts +2 -2
- package/lib/types/client/index.js +2 -0
- package/lib/types/client/sessions/assistant-stream.d.ts +51 -0
- package/lib/types/client/sessions/assistant-stream.js +168 -0
- package/lib/types/client/sessions/history-records.d.ts +2 -2
- package/lib/types/client/sessions/history-records.js +3 -8
- package/lib/types/client/sessions/queue-mirror.js +3 -3
- package/lib/types/client/sessions/remotes.d.ts +2 -2
- package/lib/types/client/sessions/session.d.ts +3 -1
- package/lib/types/client/sessions/session.js +63 -15
- package/lib/types/client/transport.d.ts +7 -3
- package/lib/types/client/transport.js +18 -3
- package/lib/types/commands.js +98 -28
- package/lib/types/history.d.ts +2 -1
- package/lib/types/history.js +66 -37
- package/lib/types/index.d.ts +4 -4
- package/lib/types/index.js +13 -5
- package/lib/types/list.d.ts +2 -9
- package/lib/types/list.js +10 -124
- package/lib/types/types.d.ts +71 -29
- package/package.json +63 -57
package/lib/client.js
CHANGED
|
@@ -47,7 +47,7 @@ window.__ModuleLoader__.load({
|
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
49
|
* Read the first logical sequence represented by one wire record.
|
|
50
|
-
* @param record - validated
|
|
50
|
+
* @param record - validated Session event.
|
|
51
51
|
* @returns inclusive first Session sequence.
|
|
52
52
|
*/
|
|
53
53
|
function historyRecordFirstSeq(record) {
|
|
@@ -55,13 +55,11 @@ window.__ModuleLoader__.load({
|
|
|
55
55
|
}
|
|
56
56
|
/**
|
|
57
57
|
* Read the final logical sequence represented by one wire record.
|
|
58
|
-
* @param record - validated
|
|
58
|
+
* @param record - validated Session event.
|
|
59
59
|
* @returns inclusive final Session sequence.
|
|
60
60
|
*/
|
|
61
61
|
function historyRecordLastSeq(record) {
|
|
62
|
-
|
|
63
|
-
const length = record.event.type === "chunkrow/tool-call-chunks" ? record.event.data.args.length : record.event.data.texts.length;
|
|
64
|
-
return record.event.seq + length - 1;
|
|
62
|
+
return record.event.seq;
|
|
65
63
|
}
|
|
66
64
|
//#endregion
|
|
67
65
|
//#region lib/types/types.js
|
|
@@ -80,12 +78,14 @@ window.__ModuleLoader__.load({
|
|
|
80
78
|
...change,
|
|
81
79
|
entries: historyEntries(change.entries)
|
|
82
80
|
};
|
|
83
|
-
case "append":
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
81
|
+
case "append": return {
|
|
82
|
+
type: "append",
|
|
83
|
+
entry: change.entry
|
|
84
|
+
};
|
|
85
|
+
case "notification": return {
|
|
86
|
+
type: "assistant-stream",
|
|
87
|
+
frame: change.notification
|
|
88
|
+
};
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
/**
|
|
@@ -138,22 +138,37 @@ window.__ModuleLoader__.load({
|
|
|
138
138
|
}
|
|
139
139
|
/** @inheritdoc */
|
|
140
140
|
async *follow(request, signal) {
|
|
141
|
+
let assistantRevision;
|
|
141
142
|
for await (const frame of this.remote.session.follow({
|
|
142
143
|
address: this.address,
|
|
144
|
+
assistantStream: true,
|
|
143
145
|
...request.maxMessages === void 0 ? {} : { maxMessages: request.maxMessages }
|
|
144
146
|
}, signal)) {
|
|
145
147
|
if (frame.type === "snapshot") {
|
|
148
|
+
if (frame.assistantStream === void 0) throw new RemoteError("gateway/internal", "session assistant stream omitted its opted-in opening baseline", {});
|
|
149
|
+
assistantRevision = frame.assistantStream.revision;
|
|
146
150
|
yield {
|
|
147
151
|
type: "opened",
|
|
148
152
|
cursor: frame.cursor,
|
|
149
153
|
page: {
|
|
150
154
|
records: frame.records,
|
|
151
155
|
hasMore: frame.hasMore,
|
|
152
|
-
projections: frame.projections
|
|
156
|
+
projections: frame.projections,
|
|
157
|
+
assistantStream: frame.assistantStream
|
|
153
158
|
}
|
|
154
159
|
};
|
|
155
160
|
continue;
|
|
156
161
|
}
|
|
162
|
+
if (frame.type === "assistant-stream") {
|
|
163
|
+
const expected = (assistantRevision ?? 0) + 1;
|
|
164
|
+
if (frame.frame.revision !== expected) throw new _deepseek_ai_dsh_api_gateway_client.RemoteStreamCarrierError(`session assistant stream skipped revision ${String(expected)}`);
|
|
165
|
+
assistantRevision = frame.frame.revision;
|
|
166
|
+
yield {
|
|
167
|
+
type: "notification",
|
|
168
|
+
notification: frame.frame
|
|
169
|
+
};
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
157
172
|
yield {
|
|
158
173
|
type: "entry",
|
|
159
174
|
entry: frame
|
|
@@ -679,6 +694,25 @@ window.__ModuleLoader__.load({
|
|
|
679
694
|
entries
|
|
680
695
|
});
|
|
681
696
|
}
|
|
697
|
+
/**
|
|
698
|
+
* Replace one attempt's transient rows with its committed durable settlement.
|
|
699
|
+
* @param attemptId - process-local attempt whose live rows are now redundant.
|
|
700
|
+
* @param entry - durable settlement committed for that attempt.
|
|
701
|
+
*/
|
|
702
|
+
settleAssistant(attemptId, entry) {
|
|
703
|
+
const entries = materialize(this.window).filter((candidate) => candidate.type !== "transient" || candidate.event.data.attemptId !== attemptId);
|
|
704
|
+
if (entry !== void 0) {
|
|
705
|
+
const index = entries.findIndex((candidate) => candidate.event.seq > entry.event.seq);
|
|
706
|
+
if (index < 0) entries.push(entry);
|
|
707
|
+
else entries.splice(index, 0, entry);
|
|
708
|
+
}
|
|
709
|
+
this.window = leaf(entries);
|
|
710
|
+
this.publish(this.snapshot.hasMore, {
|
|
711
|
+
kind: "settle-assistant",
|
|
712
|
+
attemptId,
|
|
713
|
+
...entry === void 0 ? {} : { entry }
|
|
714
|
+
});
|
|
715
|
+
}
|
|
682
716
|
publish(hasMore, change) {
|
|
683
717
|
this.snapshot = windowSnapshot(this.window, hasMore, this.snapshot.revision + 1, change);
|
|
684
718
|
(0, _deepseek_ai_dsh_client_store.notifySubscribers)(this.listeners, "[session-controller] event feed");
|
|
@@ -701,7 +735,7 @@ window.__ModuleLoader__.load({
|
|
|
701
735
|
//#region lib/types/client/sessions/queue-mirror.js
|
|
702
736
|
const QUEUE_PREVIEW_CHARS = 200;
|
|
703
737
|
function previewOf(content) {
|
|
704
|
-
const flat = content.filter((block) => block.type !== "image").map((block) => block.type === "text" ? block.text : `[${block.type}]`).join(" ").replace(/\s+/g, " ").trim();
|
|
738
|
+
const flat = content.filter((block) => block.type !== "image" && block.type !== "file").map((block) => block.type === "text" ? block.text : `[${block.type}]`).join(" ").replace(/\s+/g, " ").trim();
|
|
705
739
|
const chars = Array.from(flat);
|
|
706
740
|
return chars.length > QUEUE_PREVIEW_CHARS ? `${chars.slice(0, QUEUE_PREVIEW_CHARS).join("")}…` : flat;
|
|
707
741
|
}
|
|
@@ -752,6 +786,498 @@ window.__ModuleLoader__.load({
|
|
|
752
786
|
}
|
|
753
787
|
};
|
|
754
788
|
//#endregion
|
|
789
|
+
//#region ../../util/values/lib/index.js
|
|
790
|
+
/** Whether a realm-owned intrinsic prototype is backed by its native constructor. */
|
|
791
|
+
function hasIntrinsicConstructor(prototype, name) {
|
|
792
|
+
const constructor = Object.getOwnPropertyDescriptor(prototype, "constructor")?.value;
|
|
793
|
+
if (typeof constructor !== "function") return false;
|
|
794
|
+
try {
|
|
795
|
+
return constructor.name === name && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === `function ${name}() { [native code] }`;
|
|
796
|
+
} catch {
|
|
797
|
+
return false;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
/** Whether a candidate is one realm's intrinsic `Object.prototype`. */
|
|
801
|
+
function isIntrinsicObjectPrototype(value) {
|
|
802
|
+
return Object.getPrototypeOf(value) === null && hasIntrinsicConstructor(value, "Object");
|
|
803
|
+
}
|
|
804
|
+
/** Whether an array uses one realm's intrinsic `Array.prototype`, not a subclass or forged prototype. */
|
|
805
|
+
function hasPlainArrayPrototype(value) {
|
|
806
|
+
const prototype = Object.getPrototypeOf(value);
|
|
807
|
+
if (!Array.isArray(prototype) || !hasIntrinsicConstructor(prototype, "Array")) return false;
|
|
808
|
+
const objectPrototype = Object.getPrototypeOf(prototype);
|
|
809
|
+
return typeof objectPrototype === "object" && objectPrototype !== null && isIntrinsicObjectPrototype(objectPrototype);
|
|
810
|
+
}
|
|
811
|
+
/** Whether an object is a plain or null-prototype record from any JavaScript realm. */
|
|
812
|
+
function hasPlainObjectPrototype(value) {
|
|
813
|
+
const prototype = Object.getPrototypeOf(value);
|
|
814
|
+
return prototype === null || typeof prototype === "object" && isIntrinsicObjectPrototype(prototype);
|
|
815
|
+
}
|
|
816
|
+
/** Return every JSON-visible object key, or reject own data JSON would discard. */
|
|
817
|
+
function enumerableStringKeys(value) {
|
|
818
|
+
const keys = Reflect.ownKeys(value);
|
|
819
|
+
if (keys.some((key) => typeof key !== "string" || !Object.prototype.propertyIsEnumerable.call(value, key))) return void 0;
|
|
820
|
+
return keys;
|
|
821
|
+
}
|
|
822
|
+
/** Validate lossless JSON iteratively, optionally materializing a detached snapshot. */
|
|
823
|
+
function walkJsonValue(value, detach) {
|
|
824
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
825
|
+
let root;
|
|
826
|
+
const assign = (destination, item) => {
|
|
827
|
+
if (destination === void 0) return;
|
|
828
|
+
if (destination.kind === "root") root = item;
|
|
829
|
+
else if (destination.kind === "array") destination.target[destination.index] = item;
|
|
830
|
+
else Object.defineProperty(destination.target, destination.key, {
|
|
831
|
+
value: item,
|
|
832
|
+
enumerable: true,
|
|
833
|
+
configurable: true,
|
|
834
|
+
writable: true
|
|
835
|
+
});
|
|
836
|
+
};
|
|
837
|
+
const tasks = [{
|
|
838
|
+
kind: "visit",
|
|
839
|
+
value,
|
|
840
|
+
...detach ? { destination: { kind: "root" } } : {}
|
|
841
|
+
}];
|
|
842
|
+
for (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {
|
|
843
|
+
if (task.kind === "leave") {
|
|
844
|
+
ancestors.delete(task.source);
|
|
845
|
+
continue;
|
|
846
|
+
}
|
|
847
|
+
if (task.kind === "array-item") {
|
|
848
|
+
if (!Object.prototype.hasOwnProperty.call(task.source, task.index)) return void 0;
|
|
849
|
+
tasks.push({
|
|
850
|
+
kind: "visit",
|
|
851
|
+
value: task.source[task.index],
|
|
852
|
+
...task.target === void 0 ? {} : { destination: {
|
|
853
|
+
kind: "array",
|
|
854
|
+
target: task.target,
|
|
855
|
+
index: task.index
|
|
856
|
+
} }
|
|
857
|
+
});
|
|
858
|
+
continue;
|
|
859
|
+
}
|
|
860
|
+
if (task.kind === "object-property") {
|
|
861
|
+
tasks.push({
|
|
862
|
+
kind: "visit",
|
|
863
|
+
value: task.source[task.key],
|
|
864
|
+
...task.target === void 0 ? {} : { destination: {
|
|
865
|
+
kind: "object",
|
|
866
|
+
target: task.target,
|
|
867
|
+
key: task.key
|
|
868
|
+
} }
|
|
869
|
+
});
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
const current = task.value;
|
|
873
|
+
if (current === null) {
|
|
874
|
+
assign(task.destination, null);
|
|
875
|
+
continue;
|
|
876
|
+
}
|
|
877
|
+
if (typeof current === "boolean" || typeof current === "string") {
|
|
878
|
+
assign(task.destination, current);
|
|
879
|
+
continue;
|
|
880
|
+
}
|
|
881
|
+
if (typeof current === "number") {
|
|
882
|
+
if (!Number.isFinite(current) || Object.is(current, -0)) return void 0;
|
|
883
|
+
assign(task.destination, current);
|
|
884
|
+
continue;
|
|
885
|
+
}
|
|
886
|
+
if (typeof current !== "object") return void 0;
|
|
887
|
+
if (ancestors.has(current)) return void 0;
|
|
888
|
+
if (Array.isArray(current)) {
|
|
889
|
+
if (!hasPlainArrayPrototype(current)) return void 0;
|
|
890
|
+
const length = current.length;
|
|
891
|
+
if (Reflect.ownKeys(current).length !== length + 1) return void 0;
|
|
892
|
+
const target = detach ? [] : void 0;
|
|
893
|
+
if (target !== void 0) assign(task.destination, target);
|
|
894
|
+
ancestors.add(current);
|
|
895
|
+
tasks.push({
|
|
896
|
+
kind: "leave",
|
|
897
|
+
source: current
|
|
898
|
+
});
|
|
899
|
+
for (let index = length - 1; index >= 0; index--) tasks.push({
|
|
900
|
+
kind: "array-item",
|
|
901
|
+
source: current,
|
|
902
|
+
index,
|
|
903
|
+
...target === void 0 ? {} : { target }
|
|
904
|
+
});
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
if (!hasPlainObjectPrototype(current)) return void 0;
|
|
908
|
+
const keys = enumerableStringKeys(current);
|
|
909
|
+
if (keys === void 0) return void 0;
|
|
910
|
+
const target = detach ? {} : void 0;
|
|
911
|
+
if (target !== void 0) assign(task.destination, target);
|
|
912
|
+
ancestors.add(current);
|
|
913
|
+
tasks.push({
|
|
914
|
+
kind: "leave",
|
|
915
|
+
source: current
|
|
916
|
+
});
|
|
917
|
+
for (let index = keys.length - 1; index >= 0; index--) {
|
|
918
|
+
const key = keys[index];
|
|
919
|
+
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
920
|
+
if (key === void 0) return void 0;
|
|
921
|
+
tasks.push({
|
|
922
|
+
kind: "object-property",
|
|
923
|
+
source: current,
|
|
924
|
+
key,
|
|
925
|
+
...target === void 0 ? {} : { target }
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
return detach ? root : true;
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* Validate and detach lossless JSON in one read per property.
|
|
933
|
+
* @param value - candidate value to validate and detach.
|
|
934
|
+
* @returns the detached snapshot, or `undefined` when the value is not losslessly JSON-serializable.
|
|
935
|
+
*/
|
|
936
|
+
function snapshotJsonValue(value) {
|
|
937
|
+
return walkJsonValue(value, true);
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Deep-freeze an object graph in place while leaving live AbortSignal objects mutable.
|
|
941
|
+
* @param value - value to freeze.
|
|
942
|
+
* @returns the same value after every reachable enumerable child is frozen.
|
|
943
|
+
*/
|
|
944
|
+
function deepFreeze(value) {
|
|
945
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
946
|
+
const pending = [{
|
|
947
|
+
kind: "visit",
|
|
948
|
+
node: value
|
|
949
|
+
}];
|
|
950
|
+
while (pending.length > 0) {
|
|
951
|
+
const task = pending.pop();
|
|
952
|
+
/* v8 ignore next -- the loop condition guarantees one pending task. */
|
|
953
|
+
if (task === void 0) continue;
|
|
954
|
+
if (task.kind === "property") {
|
|
955
|
+
pending.push({
|
|
956
|
+
kind: "visit",
|
|
957
|
+
node: task.source[task.key]
|
|
958
|
+
});
|
|
959
|
+
continue;
|
|
960
|
+
}
|
|
961
|
+
const node = task.node;
|
|
962
|
+
if (node === null || typeof node !== "object") continue;
|
|
963
|
+
if (node instanceof AbortSignal) continue;
|
|
964
|
+
if (seen.has(node)) continue;
|
|
965
|
+
seen.add(node);
|
|
966
|
+
Object.freeze(node);
|
|
967
|
+
const keys = Object.keys(node);
|
|
968
|
+
for (let index = keys.length - 1; index >= 0; index--) {
|
|
969
|
+
const key = keys[index];
|
|
970
|
+
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
971
|
+
if (key === void 0) continue;
|
|
972
|
+
pending.push({
|
|
973
|
+
kind: "property",
|
|
974
|
+
source: node,
|
|
975
|
+
key
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
return value;
|
|
980
|
+
}
|
|
981
|
+
//#endregion
|
|
982
|
+
//#region ../../llm/llm/lib/types/assistant-stream.js
|
|
983
|
+
/**
|
|
984
|
+
* Lossless compact representation of one model-stream attempt, plus record-level
|
|
985
|
+
* readers that answer common consumer questions without materializing members.
|
|
986
|
+
* Readers trust the static record type; expandAssistantStream is the validating
|
|
987
|
+
* path for records read at a durable boundary.
|
|
988
|
+
*/
|
|
989
|
+
function safeTime(value) {
|
|
990
|
+
if (!Number.isSafeInteger(value)) throw new TypeError(`Assistant stream time must be a safe integer, got ${String(value)}`);
|
|
991
|
+
return value;
|
|
992
|
+
}
|
|
993
|
+
function safeIndex(value, label) {
|
|
994
|
+
if (!Number.isSafeInteger(value) || value < 0 || Object.is(value, -0)) throw new TypeError(`${label} index must be a non-negative safe integer`);
|
|
995
|
+
return value;
|
|
996
|
+
}
|
|
997
|
+
function snapshotChunk(chunk) {
|
|
998
|
+
const snapshot = snapshotJsonValue(chunk);
|
|
999
|
+
if (snapshot === void 0) throw new TypeError("Assistant stream chunk must be losslessly JSON-serializable");
|
|
1000
|
+
return snapshot;
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Expand compact records into the exact timed chunk sequence.
|
|
1004
|
+
* @param stream - compact records from one durable Assistant settlement.
|
|
1005
|
+
* @returns detached timed chunks with every original delta boundary preserved.
|
|
1006
|
+
* @throws {TypeError} when a record or reconstructed timestamp is invalid.
|
|
1007
|
+
*/
|
|
1008
|
+
function expandAssistantStream(stream) {
|
|
1009
|
+
const chunks = [];
|
|
1010
|
+
for (const candidate of stream) {
|
|
1011
|
+
const record = validateRecord(candidate);
|
|
1012
|
+
if (record.type === "chunk") {
|
|
1013
|
+
chunks.push({
|
|
1014
|
+
time: record.time,
|
|
1015
|
+
chunk: record.chunk
|
|
1016
|
+
});
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
const members = record.type === "tool-call-chunks" ? record.args : record.texts;
|
|
1020
|
+
let time = record.time0;
|
|
1021
|
+
for (let index = 0; index < members.length; index += 1) {
|
|
1022
|
+
if (index > 0) time += record.dt[index - 1];
|
|
1023
|
+
let chunk;
|
|
1024
|
+
if (record.type === "text-chunks") chunk = {
|
|
1025
|
+
type: "text-delta",
|
|
1026
|
+
index: record.index,
|
|
1027
|
+
text: members[index]
|
|
1028
|
+
};
|
|
1029
|
+
else if (record.type === "reasoning-chunks") chunk = {
|
|
1030
|
+
type: "reasoning-delta",
|
|
1031
|
+
index: record.index,
|
|
1032
|
+
text: members[index]
|
|
1033
|
+
};
|
|
1034
|
+
else chunk = {
|
|
1035
|
+
type: "tool-call-delta",
|
|
1036
|
+
index: record.index,
|
|
1037
|
+
id: record.id,
|
|
1038
|
+
...Object.hasOwn(record, "name") ? { name: record.name } : {},
|
|
1039
|
+
argumentsDelta: members[index]
|
|
1040
|
+
};
|
|
1041
|
+
chunks.push({
|
|
1042
|
+
time,
|
|
1043
|
+
chunk
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
return chunks;
|
|
1048
|
+
}
|
|
1049
|
+
function validateRecord(value) {
|
|
1050
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("Assistant stream record must be an object");
|
|
1051
|
+
const record = value;
|
|
1052
|
+
switch (record.type) {
|
|
1053
|
+
case "text-chunks":
|
|
1054
|
+
case "reasoning-chunks": {
|
|
1055
|
+
exactKeys(record, [
|
|
1056
|
+
"type",
|
|
1057
|
+
"time0",
|
|
1058
|
+
"index",
|
|
1059
|
+
"dt",
|
|
1060
|
+
"texts"
|
|
1061
|
+
], record.type);
|
|
1062
|
+
const texts = stringArray(record.texts, `${record.type} texts`);
|
|
1063
|
+
if (texts.length === 0) throw new TypeError(`${record.type} texts must be non-empty`);
|
|
1064
|
+
validateRun(record, texts.length, record.type);
|
|
1065
|
+
return record;
|
|
1066
|
+
}
|
|
1067
|
+
case "tool-call-chunks": {
|
|
1068
|
+
exactKeys(record, Object.hasOwn(record, "name") ? [
|
|
1069
|
+
"type",
|
|
1070
|
+
"time0",
|
|
1071
|
+
"index",
|
|
1072
|
+
"dt",
|
|
1073
|
+
"id",
|
|
1074
|
+
"name",
|
|
1075
|
+
"args"
|
|
1076
|
+
] : [
|
|
1077
|
+
"type",
|
|
1078
|
+
"time0",
|
|
1079
|
+
"index",
|
|
1080
|
+
"dt",
|
|
1081
|
+
"id",
|
|
1082
|
+
"args"
|
|
1083
|
+
], record.type);
|
|
1084
|
+
const args = stringArray(record.args, "tool-call-chunks args");
|
|
1085
|
+
if (args.length === 0) throw new TypeError("tool-call-chunks args must be non-empty");
|
|
1086
|
+
if (typeof record.id !== "string" || record.id.length === 0) throw new TypeError("tool-call-chunks id must be a non-empty string");
|
|
1087
|
+
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");
|
|
1088
|
+
validateRun(record, args.length, record.type);
|
|
1089
|
+
return record;
|
|
1090
|
+
}
|
|
1091
|
+
case "chunk": {
|
|
1092
|
+
exactKeys(record, [
|
|
1093
|
+
"type",
|
|
1094
|
+
"time",
|
|
1095
|
+
"chunk"
|
|
1096
|
+
], "chunk");
|
|
1097
|
+
const time = safeTime(record.time);
|
|
1098
|
+
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");
|
|
1099
|
+
let chunk;
|
|
1100
|
+
try {
|
|
1101
|
+
chunk = snapshotChunk(record.chunk);
|
|
1102
|
+
} catch (error) {
|
|
1103
|
+
throw new TypeError("Assistant stream raw chunk must be a lossless JSON object", { cause: error });
|
|
1104
|
+
}
|
|
1105
|
+
return deepFreeze({
|
|
1106
|
+
type: "chunk",
|
|
1107
|
+
time,
|
|
1108
|
+
chunk
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
default: throw new TypeError(`Unsupported Assistant stream record ${JSON.stringify(record.type)}`);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
function validateRun(record, members, label) {
|
|
1115
|
+
safeTime(record.time0);
|
|
1116
|
+
safeIndex(record.index, label);
|
|
1117
|
+
if (!Array.isArray(record.dt) || record.dt.some((value) => !Number.isSafeInteger(value))) throw new TypeError(`${label} dt must contain safe integers`);
|
|
1118
|
+
if (record.dt.length !== members - 1) throw new TypeError(`${label} dt length must be one less than its members`);
|
|
1119
|
+
let time = record.time0;
|
|
1120
|
+
for (const gap of record.dt) {
|
|
1121
|
+
time += gap;
|
|
1122
|
+
if (!Number.isSafeInteger(time)) throw new TypeError(`${label} member times must stay safe integers`);
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
function stringArray(value, label) {
|
|
1126
|
+
if (!Array.isArray(value) || value.some((member) => typeof member !== "string")) throw new TypeError(`${label} must be a string array`);
|
|
1127
|
+
return value;
|
|
1128
|
+
}
|
|
1129
|
+
function exactKeys(record, keys, label) {
|
|
1130
|
+
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(", ")}`);
|
|
1131
|
+
}
|
|
1132
|
+
//#endregion
|
|
1133
|
+
//#region lib/types/client/sessions/assistant-stream.js
|
|
1134
|
+
/** Web presentation fold joining transient Assistant frames to one durable v2 settlement. */
|
|
1135
|
+
/** Keeps transient Assistant presentation behind one settlement-aware interface. */
|
|
1136
|
+
var ClientAssistantStream = class {
|
|
1137
|
+
activeAttempt;
|
|
1138
|
+
pending = /* @__PURE__ */ new Map();
|
|
1139
|
+
publishedSeqs = /* @__PURE__ */ new Set();
|
|
1140
|
+
durableCursor = -1;
|
|
1141
|
+
transientInGap = 0;
|
|
1142
|
+
/**
|
|
1143
|
+
* Replace the durable Web window and adopt an optional reconnect baseline.
|
|
1144
|
+
* @param entries - durable entries in the replacement window.
|
|
1145
|
+
* @param baseline - compact prefix for an Assistant attempt that is still live.
|
|
1146
|
+
* @returns immediately visible durable entries plus reconstructed transient chunks.
|
|
1147
|
+
*/
|
|
1148
|
+
replace(entries, baseline) {
|
|
1149
|
+
this.pending.clear();
|
|
1150
|
+
this.transientInGap = 0;
|
|
1151
|
+
this.activeAttempt = void 0;
|
|
1152
|
+
const opening = baseline?.activeAttempt;
|
|
1153
|
+
if (opening !== void 0) this.activeAttempt = {
|
|
1154
|
+
attemptId: opening.attemptId,
|
|
1155
|
+
startedAfterSeq: opening.startedAfterSeq,
|
|
1156
|
+
turn: opening.turn,
|
|
1157
|
+
step: opening.step,
|
|
1158
|
+
nextIndex: opening.nextIndex
|
|
1159
|
+
};
|
|
1160
|
+
const visible = [...entries];
|
|
1161
|
+
this.publishedSeqs = new Set(visible.map((entry) => entry.event.seq));
|
|
1162
|
+
this.durableCursor = visible.reduce((cursor, entry) => Math.max(cursor, entry.event.seq), -1);
|
|
1163
|
+
if (opening !== void 0) for (const [index, member] of expandAssistantStream(opening.stream).entries()) {
|
|
1164
|
+
this.transientInGap += 1;
|
|
1165
|
+
visible.push({
|
|
1166
|
+
type: "transient",
|
|
1167
|
+
event: {
|
|
1168
|
+
type: "assistant/live-chunk",
|
|
1169
|
+
seq: this.durableCursor + 1 - 1 / (this.transientInGap + 1),
|
|
1170
|
+
time: member.time,
|
|
1171
|
+
data: {
|
|
1172
|
+
attemptId: opening.attemptId,
|
|
1173
|
+
turn: opening.turn,
|
|
1174
|
+
step: opening.step,
|
|
1175
|
+
chunk: member.chunk
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
});
|
|
1179
|
+
if (index + 1 >= opening.nextIndex) break;
|
|
1180
|
+
}
|
|
1181
|
+
return visible;
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Stage one durable v2 settlement while its matching live attempt is open.
|
|
1185
|
+
* @param entry - newly followed durable entry.
|
|
1186
|
+
* @returns a publication decision, or `undefined` when no entry becomes visible.
|
|
1187
|
+
*/
|
|
1188
|
+
acceptDurable(entry) {
|
|
1189
|
+
const event = entry.event;
|
|
1190
|
+
this.durableCursor = Math.max(this.durableCursor, event.seq);
|
|
1191
|
+
this.transientInGap = 0;
|
|
1192
|
+
const settlement = assistantSettlementEntry(entry);
|
|
1193
|
+
if (settlement !== void 0 && this.attemptForSettlement(settlement.event) !== void 0) {
|
|
1194
|
+
if (this.pending.has(event.seq)) return { type: "rebaseline" };
|
|
1195
|
+
this.pending.set(event.seq, settlement);
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
return this.publish(entry);
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1201
|
+
* Fold one dense transient frame and release its named durable settlement.
|
|
1202
|
+
* @param frame - next Assistant stream frame received by the follow connection.
|
|
1203
|
+
* @returns a transient, publication, or rebaseline decision, or `undefined` when no entry becomes visible.
|
|
1204
|
+
*/
|
|
1205
|
+
acceptFrame(frame) {
|
|
1206
|
+
switch (frame.type) {
|
|
1207
|
+
case "start":
|
|
1208
|
+
if (this.activeAttempt !== void 0 || this.pending.size > 0) return { type: "rebaseline" };
|
|
1209
|
+
this.pending.clear();
|
|
1210
|
+
this.activeAttempt = {
|
|
1211
|
+
attemptId: frame.attemptId,
|
|
1212
|
+
startedAfterSeq: frame.startedAfterSeq,
|
|
1213
|
+
turn: frame.turn,
|
|
1214
|
+
step: frame.step,
|
|
1215
|
+
nextIndex: 0
|
|
1216
|
+
};
|
|
1217
|
+
return;
|
|
1218
|
+
case "chunk": {
|
|
1219
|
+
const attempt = this.activeAttempt;
|
|
1220
|
+
if (attempt === void 0 || attempt.attemptId !== frame.attemptId) return void 0;
|
|
1221
|
+
if (frame.index !== attempt.nextIndex) return { type: "rebaseline" };
|
|
1222
|
+
attempt.nextIndex += 1;
|
|
1223
|
+
this.transientInGap += 1;
|
|
1224
|
+
return {
|
|
1225
|
+
type: "transient",
|
|
1226
|
+
entry: {
|
|
1227
|
+
type: "transient",
|
|
1228
|
+
event: {
|
|
1229
|
+
type: "assistant/live-chunk",
|
|
1230
|
+
seq: this.durableCursor + 1 - 1 / (this.transientInGap + 1),
|
|
1231
|
+
time: frame.time,
|
|
1232
|
+
data: {
|
|
1233
|
+
attemptId: frame.attemptId,
|
|
1234
|
+
turn: attempt.turn,
|
|
1235
|
+
step: attempt.step,
|
|
1236
|
+
chunk: frame.chunk
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
case "end": {
|
|
1243
|
+
const attempt = this.activeAttempt;
|
|
1244
|
+
if (attempt === void 0 || attempt.attemptId !== frame.attemptId) return;
|
|
1245
|
+
this.activeAttempt = void 0;
|
|
1246
|
+
if (frame.index !== attempt.nextIndex) return { type: "rebaseline" };
|
|
1247
|
+
if (frame.outcome.kind === "abandoned") return this.pending.size === 0 ? {
|
|
1248
|
+
type: "abandonment",
|
|
1249
|
+
attemptId: attempt.attemptId
|
|
1250
|
+
} : { type: "rebaseline" };
|
|
1251
|
+
if (this.publishedSeqs.has(frame.outcome.seq)) return void 0;
|
|
1252
|
+
const entry = this.pending.get(frame.outcome.seq);
|
|
1253
|
+
if (entry === void 0 || entry.event.type !== frame.outcome.eventType) return { type: "rebaseline" };
|
|
1254
|
+
this.pending.delete(frame.outcome.seq);
|
|
1255
|
+
this.publishedSeqs.add(entry.event.seq);
|
|
1256
|
+
return {
|
|
1257
|
+
type: "settlement",
|
|
1258
|
+
attemptId: attempt.attemptId,
|
|
1259
|
+
entry
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
attemptForSettlement(event) {
|
|
1265
|
+
const attempt = this.activeAttempt;
|
|
1266
|
+
if (attempt === void 0 || event.type === "assistant/message" && event.surfaceOp !== "append" || event.seq <= attempt.startedAfterSeq || attempt.turn !== event.data.turn || attempt.step !== event.data.step) return void 0;
|
|
1267
|
+
return attempt;
|
|
1268
|
+
}
|
|
1269
|
+
publish(entry) {
|
|
1270
|
+
this.publishedSeqs.add(entry.event.seq);
|
|
1271
|
+
return {
|
|
1272
|
+
type: "publish",
|
|
1273
|
+
entry
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
};
|
|
1277
|
+
function assistantSettlementEntry(entry) {
|
|
1278
|
+
return entry.event.type === "assistant/message" || entry.event.type === "assistant/attempt" ? entry : void 0;
|
|
1279
|
+
}
|
|
1280
|
+
//#endregion
|
|
755
1281
|
//#region lib/types/client/sessions/session.js
|
|
756
1282
|
function projectionsBaseline(value) {
|
|
757
1283
|
return {
|
|
@@ -784,6 +1310,7 @@ window.__ModuleLoader__.load({
|
|
|
784
1310
|
jumpPromise = null;
|
|
785
1311
|
/** Authoritative stream-only inbox snapshot; pending work never hits history. */
|
|
786
1312
|
queueMirror = new SessionQueueMirror();
|
|
1313
|
+
assistantStream = new ClientAssistantStream();
|
|
787
1314
|
running = false;
|
|
788
1315
|
address;
|
|
789
1316
|
parentAvailable;
|
|
@@ -879,7 +1406,7 @@ window.__ModuleLoader__.load({
|
|
|
879
1406
|
placement: this.running ? input.mode === "steer" ? "steering" : "queued" : "transcript",
|
|
880
1407
|
time: Date.now(),
|
|
881
1408
|
text: input.text,
|
|
882
|
-
|
|
1409
|
+
attachments: input.attachments
|
|
883
1410
|
}];
|
|
884
1411
|
this.submissionSettlements.set(requestId, {
|
|
885
1412
|
onRetire: input.onRetire,
|
|
@@ -896,7 +1423,7 @@ window.__ModuleLoader__.load({
|
|
|
896
1423
|
}
|
|
897
1424
|
/**
|
|
898
1425
|
* Send (queue/steer passed through 1:1); failures land in the snapshot's promptError.
|
|
899
|
-
* @param content - text
|
|
1426
|
+
* @param content - text, browser-owned temporary image uploads, and staged-file receipts.
|
|
900
1427
|
* @param mode - queue appends after the current turn; steer interrupts it.
|
|
901
1428
|
* @param signal - optional caller cancellation for the complete admission round-trip.
|
|
902
1429
|
* @param requestId - identity from {@link beginSubmission}; a failed identified prompt retires its echo.
|
|
@@ -918,13 +1445,19 @@ window.__ModuleLoader__.load({
|
|
|
918
1445
|
content,
|
|
919
1446
|
clientTimeZone
|
|
920
1447
|
}, signal);
|
|
921
|
-
} else {
|
|
1448
|
+
} else if (content.some((part) => part.type === "file")) result = {
|
|
1449
|
+
ok: false,
|
|
1450
|
+
error: new RemoteError("subagent/attachment-invalid", "subagent continuation does not accept files", { reason: "SUBAGENT_FILE_UNSUPPORTED" })
|
|
1451
|
+
};
|
|
1452
|
+
else {
|
|
1453
|
+
const routedContent = content;
|
|
922
1454
|
const routed = await this.remote.subagents.prompt({
|
|
923
1455
|
requestId: randomUUID(),
|
|
924
1456
|
parentSessionId: this.address.parentSessionId,
|
|
925
1457
|
childSessionId: this.address.childSessionId,
|
|
926
1458
|
mode: "continuable",
|
|
927
|
-
|
|
1459
|
+
delivery: mode,
|
|
1460
|
+
content: routedContent,
|
|
928
1461
|
clientTimeZone: resolvedClientTimeZone()
|
|
929
1462
|
}, signal);
|
|
930
1463
|
result = routed.ok ? {
|
|
@@ -1257,24 +1790,53 @@ window.__ModuleLoader__.load({
|
|
|
1257
1790
|
acceptEventChange(change) {
|
|
1258
1791
|
switch (change.type) {
|
|
1259
1792
|
case "replace":
|
|
1260
|
-
this.installWindow(change.entries, change.hasMore, change.page.projections === void 0 ? void 0 : projectionsBaseline(change.page.projections));
|
|
1793
|
+
this.installWindow(change.entries, change.hasMore, change.page.projections === void 0 ? void 0 : projectionsBaseline(change.page.projections), change.page.assistantStream);
|
|
1261
1794
|
return;
|
|
1262
1795
|
case "prepend":
|
|
1263
1796
|
this.prependWindow(change.entries, change.hasMore);
|
|
1264
1797
|
return;
|
|
1265
|
-
case "append":
|
|
1798
|
+
case "append":
|
|
1799
|
+
this.publishAssistantEntry(this.assistantStream.acceptDurable(change.entry));
|
|
1800
|
+
return;
|
|
1801
|
+
case "assistant-stream": this.publishAssistantEntry(this.assistantStream.acceptFrame(change.frame));
|
|
1266
1802
|
}
|
|
1267
1803
|
}
|
|
1268
1804
|
/** Replace the complete contiguous window and apply page-owned projection metadata. */
|
|
1269
|
-
installWindow(entries, hasMore, projections) {
|
|
1805
|
+
installWindow(entries, hasMore, projections, assistantStream) {
|
|
1806
|
+
const visible = this.assistantStream.replace(entries, assistantStream);
|
|
1270
1807
|
this.baseSeq = SessionLogOffset(entries[0]?.event.seq ?? 0);
|
|
1271
1808
|
this.hasMore = hasMore;
|
|
1272
|
-
if (
|
|
1809
|
+
if (visible.some((entry) => entry.event.type === "turn/start")) this.firstPromptPendingTurn = false;
|
|
1273
1810
|
if (projections !== void 0) this.projections.seed(projections);
|
|
1274
|
-
this.eventSource.replace(
|
|
1275
|
-
for (const entry of
|
|
1811
|
+
this.eventSource.replace(visible, hasMore);
|
|
1812
|
+
for (const entry of visible) this.observeSubmissionEvent(entry.event);
|
|
1276
1813
|
this.notifier.markDirty();
|
|
1277
1814
|
}
|
|
1815
|
+
publishAssistantEntry(result) {
|
|
1816
|
+
if (result?.type === "rebaseline") {
|
|
1817
|
+
const events = this.events;
|
|
1818
|
+
queueMicrotask(() => {
|
|
1819
|
+
if (events !== void 0 && this.events === events) events.restart();
|
|
1820
|
+
});
|
|
1821
|
+
return;
|
|
1822
|
+
}
|
|
1823
|
+
if (result?.type === "settlement") {
|
|
1824
|
+
this.eventSource.settleAssistant(result.attemptId, result.entry);
|
|
1825
|
+
this.observeSubmissionEvent(result.entry.event);
|
|
1826
|
+
this.notifier.markDirty();
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1829
|
+
if (result?.type === "abandonment") {
|
|
1830
|
+
this.eventSource.settleAssistant(result.attemptId);
|
|
1831
|
+
this.notifier.markDirty();
|
|
1832
|
+
return;
|
|
1833
|
+
}
|
|
1834
|
+
if (result?.type === "publish" && this.appendLive(result.entry)) this.notifier.markDirty();
|
|
1835
|
+
else if (result?.type === "transient") {
|
|
1836
|
+
this.eventSource.append(result.entry);
|
|
1837
|
+
this.notifier.markDirty();
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1278
1840
|
/** Prepend one stream-validated history page. */
|
|
1279
1841
|
prependWindow(entries, hasMore) {
|
|
1280
1842
|
this.baseSeq = entries[0] === void 0 ? this.baseSeq : SessionLogOffset(entries[0].event.seq);
|
|
@@ -1297,12 +1859,12 @@ window.__ModuleLoader__.load({
|
|
|
1297
1859
|
const data = event.data;
|
|
1298
1860
|
const source = data?.source;
|
|
1299
1861
|
if (source?.kind !== "user" || typeof source.rpcId !== "string") return;
|
|
1300
|
-
this.scheduleObservedRetirement(source.rpcId,
|
|
1862
|
+
this.scheduleObservedRetirement(source.rpcId, attachmentRefsIn(data?.content));
|
|
1301
1863
|
}
|
|
1302
1864
|
/** Retire echoes whose prompts landed in the host inbox instead of the log (running-turn submissions). */
|
|
1303
1865
|
observeSubmissionQueue(items) {
|
|
1304
1866
|
if (this.submissionSettlements.size === 0) return;
|
|
1305
|
-
for (const item of items) if (item.rpcId !== void 0) this.scheduleObservedRetirement(item.rpcId,
|
|
1867
|
+
for (const item of items) if (item.rpcId !== void 0) this.scheduleObservedRetirement(item.rpcId, attachmentRefsIn(item.message.content));
|
|
1306
1868
|
}
|
|
1307
1869
|
/**
|
|
1308
1870
|
* Latch one observed settlement and remove the echo an animation frame
|
|
@@ -1389,14 +1951,14 @@ window.__ModuleLoader__.load({
|
|
|
1389
1951
|
});
|
|
1390
1952
|
else setTimeout(fn, 0);
|
|
1391
1953
|
}
|
|
1392
|
-
/**
|
|
1393
|
-
function
|
|
1954
|
+
/** Attachment references in one structurally-read content block list, in block order. */
|
|
1955
|
+
function attachmentRefsIn(content) {
|
|
1394
1956
|
if (!Array.isArray(content)) return [];
|
|
1395
1957
|
const refs = [];
|
|
1396
1958
|
for (const block of content) {
|
|
1397
1959
|
if (typeof block !== "object" || block === null) continue;
|
|
1398
1960
|
const candidate = block;
|
|
1399
|
-
if (candidate.type === "image" && typeof candidate.attachment === "object" && candidate.attachment !== null) refs.push(candidate.attachment);
|
|
1961
|
+
if ((candidate.type === "image" || candidate.type === "file") && typeof candidate.attachment === "object" && candidate.attachment !== null) refs.push(candidate.attachment);
|
|
1400
1962
|
}
|
|
1401
1963
|
return refs;
|
|
1402
1964
|
}
|
|
@@ -2709,6 +3271,8 @@ window.__ModuleLoader__.load({
|
|
|
2709
3271
|
/** Client Session object layer, Agent scopes, and Remote lifecycle wiring. */
|
|
2710
3272
|
/** Required Remote and Context projection services. */
|
|
2711
3273
|
const inject = [
|
|
3274
|
+
"connection",
|
|
3275
|
+
"fileUpload",
|
|
2712
3276
|
"typert",
|
|
2713
3277
|
"remote",
|
|
2714
3278
|
"remote.commands",
|