@crazx/dsh-api-session-controller 0.1.2-rc.1.zw.2 → 0.1.5-alpha.1.zw.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 +16 -6
- package/README.zh.md +16 -6
- package/lib/client.js +838 -58
- package/lib/index.js +376 -243
- package/lib/typert.host.js +236 -178
- package/lib/typert.remote-client.js +97 -115
- package/lib/types/agent.js +6 -8
- 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/session-wire-event.d.ts +11 -0
- package/lib/types/client/session-wire-event.js +43 -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 +24 -3
- package/lib/types/commands.d.ts +1 -1
- package/lib/types/commands.js +112 -32
- package/lib/types/control.d.ts +0 -1
- package/lib/types/control.js +19 -22
- 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 +15 -5
- package/lib/types/list.d.ts +2 -9
- package/lib/types/list.js +10 -124
- package/lib/types/media-references.d.ts +16 -0
- package/lib/types/media-references.js +77 -0
- package/lib/types/types.d.ts +85 -35
- package/package.json +70 -57
package/lib/index.js
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
import z from "@deepseek-ai/schemastery";
|
|
2
|
-
import { ReasoningEffortId, createUserMessage, errorChain, freezeMessage } from "@deepseek-ai/dsh-llm";
|
|
2
|
+
import { AssistantStreamAccumulator, ReasoningEffortId, assistantStreamChunks, createUserMessage, errorChain, freezeMessage } from "@deepseek-ai/dsh-llm";
|
|
3
3
|
import { canOpenNativePath, openNativePath } from "@deepseek-ai/dsh-native-command";
|
|
4
4
|
import { Remote, RemoteError, TypertRemoteService, remoteErrorOf } from "@deepseek-ai/dsh-typert-protocol";
|
|
5
|
-
import { mkdir
|
|
5
|
+
import { mkdir } from "node:fs/promises";
|
|
6
6
|
import { installModelSelection } from "@deepseek-ai/dsh-agent";
|
|
7
7
|
import { SessionQueryError } from "@deepseek-ai/dsh-session-query";
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
9
|
import { brandString } from "@deepseek-ai/dsh-brand";
|
|
10
|
-
import { AttachmentError
|
|
10
|
+
import { AttachmentError } from "@deepseek-ai/dsh-attachment";
|
|
11
11
|
import { SessionLogOffset, SessionSeq, isAppendSurfaceEvent } from "@deepseek-ai/dsh-session";
|
|
12
12
|
import { SessionTitleInvalidError } from "@deepseek-ai/dsh-session-title";
|
|
13
13
|
import { canonicalClientTimeZone } from "@deepseek-ai/dsh-util-time";
|
|
14
|
+
import { assertNever } from "@deepseek-ai/dsh-util-values";
|
|
14
15
|
import { Deque } from "@deepseek-ai/dsh-deque";
|
|
15
|
-
import { isChunkRow, packChunkRuns } from "@deepseek-ai/dsh-session/chunk-rows";
|
|
16
16
|
import { z as z$1 } from "zod";
|
|
17
17
|
import { isUserInvocable } from "@deepseek-ai/dsh-skill";
|
|
18
|
+
import { isAbsolute } from "node:path";
|
|
19
|
+
import { FsError } from "@deepseek-ai/dsh-fs";
|
|
20
|
+
import mime from "mime-types";
|
|
18
21
|
//#region lib/types/agent.js
|
|
19
22
|
/** Agent activation, composition, and model-selection policy owned by API Session. */
|
|
20
|
-
var __addDisposableResource$
|
|
23
|
+
var __addDisposableResource$4 = function(env, value, async) {
|
|
21
24
|
if (value !== null && value !== void 0) {
|
|
22
25
|
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
23
26
|
var dispose, inner;
|
|
@@ -46,7 +49,7 @@ var __addDisposableResource$5 = function(env, value, async) {
|
|
|
46
49
|
} else if (async) env.stack.push({ async: true });
|
|
47
50
|
return value;
|
|
48
51
|
};
|
|
49
|
-
var __disposeResources$
|
|
52
|
+
var __disposeResources$4 = (function(SuppressedError) {
|
|
50
53
|
return function(env) {
|
|
51
54
|
function fail(e) {
|
|
52
55
|
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
@@ -147,7 +150,7 @@ async function inspectApiSession(ctx, sessionId, signal) {
|
|
|
147
150
|
hasError: false
|
|
148
151
|
};
|
|
149
152
|
try {
|
|
150
|
-
const observation = __addDisposableResource$
|
|
153
|
+
const observation = __addDisposableResource$4(env_1, await ctx.sessionQuery.observeSession(sessionId, {
|
|
151
154
|
...signal === void 0 ? {} : { signal },
|
|
152
155
|
projectionMode: "none"
|
|
153
156
|
}), false);
|
|
@@ -161,7 +164,7 @@ async function inspectApiSession(ctx, sessionId, signal) {
|
|
|
161
164
|
env_1.error = e_1;
|
|
162
165
|
env_1.hasError = true;
|
|
163
166
|
} finally {
|
|
164
|
-
__disposeResources$
|
|
167
|
+
__disposeResources$4(env_1);
|
|
165
168
|
}
|
|
166
169
|
} catch (error) {
|
|
167
170
|
if (error instanceof SessionQueryError && error.code === "SESSION_QUERY_SESSION_NOT_FOUND") throw new ApiSessionNotFound(`session "${sessionId}" not found`);
|
|
@@ -349,14 +352,14 @@ var ApiSessionAgentController = class {
|
|
|
349
352
|
*/
|
|
350
353
|
async composeAgent(presetId) {
|
|
351
354
|
const presets = this.ctx.get("agentPresets");
|
|
352
|
-
if (presets === void 0) return { setup: (
|
|
353
|
-
this.installSelection(
|
|
355
|
+
if (presets === void 0) return { setup: (_agentCtx, agent) => {
|
|
356
|
+
this.installSelection(agent);
|
|
354
357
|
} };
|
|
355
358
|
const resolvedId = (await presets.resolve(presetId)).id;
|
|
356
359
|
return {
|
|
357
360
|
agentPreset: resolvedId,
|
|
358
|
-
setup: async (agentCtx) => {
|
|
359
|
-
this.installSelection(
|
|
361
|
+
setup: async (agentCtx, agent) => {
|
|
362
|
+
this.installSelection(agent);
|
|
360
363
|
await presets.mount(agentCtx, resolvedId);
|
|
361
364
|
}
|
|
362
365
|
};
|
|
@@ -375,13 +378,13 @@ var ApiSessionAgentController = class {
|
|
|
375
378
|
hasError: false
|
|
376
379
|
};
|
|
377
380
|
try {
|
|
378
|
-
const observation = __addDisposableResource$
|
|
381
|
+
const observation = __addDisposableResource$4(env_2, await this.ctx.sessionQuery.observeSession(sessionId), false);
|
|
379
382
|
return await this.resumeObserved(sessionId, observation);
|
|
380
383
|
} catch (e_2) {
|
|
381
384
|
env_2.error = e_2;
|
|
382
385
|
env_2.hasError = true;
|
|
383
386
|
} finally {
|
|
384
|
-
__disposeResources$
|
|
387
|
+
__disposeResources$4(env_2);
|
|
385
388
|
}
|
|
386
389
|
} catch (error) {
|
|
387
390
|
if (error instanceof SessionQueryError && error.code === "SESSION_QUERY_SESSION_NOT_FOUND") throw new ApiSessionNotFound(`session "${sessionId}" not found`);
|
|
@@ -413,7 +416,7 @@ var ApiSessionAgentController = class {
|
|
|
413
416
|
hasError: false
|
|
414
417
|
};
|
|
415
418
|
try {
|
|
416
|
-
const observation = __addDisposableResource$
|
|
419
|
+
const observation = __addDisposableResource$4(env_3, await this.ctx.sessionQuery.observeSession(sessionId), false);
|
|
417
420
|
if (hasApiSessionSubagentOwner(this.ctx, { header: observation.header }, void 0)) throw new ApiSessionSubagentOwnership(sessionId);
|
|
418
421
|
if (observation.header.cwd !== cwd) throw new ApiSessionCwdConflict(sessionId, cwd, observation.header.cwd);
|
|
419
422
|
const storedPreset = this.presetForObservation(observation);
|
|
@@ -428,7 +431,7 @@ var ApiSessionAgentController = class {
|
|
|
428
431
|
env_3.error = e_3;
|
|
429
432
|
env_3.hasError = true;
|
|
430
433
|
} finally {
|
|
431
|
-
__disposeResources$
|
|
434
|
+
__disposeResources$4(env_3);
|
|
432
435
|
}
|
|
433
436
|
} catch (error) {
|
|
434
437
|
if (!(error instanceof SessionQueryError) || error.code !== "SESSION_QUERY_SESSION_NOT_FOUND") throw error;
|
|
@@ -456,9 +459,7 @@ var ApiSessionAgentController = class {
|
|
|
456
459
|
model
|
|
457
460
|
};
|
|
458
461
|
}
|
|
459
|
-
installSelection(
|
|
460
|
-
const agent = agentCtx.agent;
|
|
461
|
-
if (agent === void 0) throw new Error("api-session: Agent setup has no scoped Agent");
|
|
462
|
+
installSelection(agent) {
|
|
462
463
|
this.selectionFor(agent);
|
|
463
464
|
}
|
|
464
465
|
/**
|
|
@@ -485,7 +486,7 @@ function agentModelSelection(selection) {
|
|
|
485
486
|
//#endregion
|
|
486
487
|
//#region lib/types/commands.js
|
|
487
488
|
/** Session commands whose activation policy is explicit at each Remote method. */
|
|
488
|
-
var __addDisposableResource$
|
|
489
|
+
var __addDisposableResource$3 = function(env, value, async) {
|
|
489
490
|
if (value !== null && value !== void 0) {
|
|
490
491
|
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
491
492
|
var dispose, inner;
|
|
@@ -514,7 +515,7 @@ var __addDisposableResource$4 = function(env, value, async) {
|
|
|
514
515
|
} else if (async) env.stack.push({ async: true });
|
|
515
516
|
return value;
|
|
516
517
|
};
|
|
517
|
-
var __disposeResources$
|
|
518
|
+
var __disposeResources$3 = (function(SuppressedError) {
|
|
518
519
|
return function(env) {
|
|
519
520
|
function fail(e) {
|
|
520
521
|
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
@@ -543,6 +544,9 @@ var __disposeResources$4 = (function(SuppressedError) {
|
|
|
543
544
|
var e = new Error(message);
|
|
544
545
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
545
546
|
});
|
|
547
|
+
function hasPromptContent(content) {
|
|
548
|
+
return content.some((part) => part.type !== "text" || part.text.trim().length > 0);
|
|
549
|
+
}
|
|
546
550
|
/** Implements Session business commands delegated by the Session Controller Remote service. */
|
|
547
551
|
var SessionCommandController = class {
|
|
548
552
|
ctx;
|
|
@@ -689,7 +693,7 @@ var SessionCommandController = class {
|
|
|
689
693
|
if (error instanceof SessionQueryError && error.code === "SESSION_QUERY_SESSION_NOT_FOUND") throw new RemoteError("session/not-found", `session "${request.sessionId}" not found`, { sessionId: request.sessionId });
|
|
690
694
|
throw new RemoteError("gateway/internal", `fork source unavailable for session "${request.sessionId}": ${String(error)}`, {});
|
|
691
695
|
}
|
|
692
|
-
const source = __addDisposableResource$
|
|
696
|
+
const source = __addDisposableResource$3(env_1, observed, false);
|
|
693
697
|
const lastSeq = source.events.at(-1)?.seq ?? -1;
|
|
694
698
|
const boundary = (atSeq === void 0 ? void 0 : source.events.find((event) => event.type === "turn/end" && event.seq >= atSeq)) ?? (atSeq === void 0 || atSeq > lastSeq ? source.events.findLast((event) => event.type === "turn/end") : void 0);
|
|
695
699
|
if (boundary === void 0) throw new RemoteError("session/fork-unavailable", atSeq !== void 0 && atSeq <= lastSeq ? `session "${request.sessionId}" has not completed the turn containing event ${String(atSeq)}` : `session "${request.sessionId}" has no completed turn to fork from`, { sessionId: request.sessionId });
|
|
@@ -737,18 +741,20 @@ var SessionCommandController = class {
|
|
|
737
741
|
env_1.error = e_1;
|
|
738
742
|
env_1.hasError = true;
|
|
739
743
|
} finally {
|
|
740
|
-
__disposeResources$
|
|
744
|
+
__disposeResources$3(env_1);
|
|
741
745
|
}
|
|
742
746
|
}
|
|
743
747
|
/**
|
|
744
|
-
*
|
|
748
|
+
* Reject empty content, then admit one prompt after Agent and attachment validation.
|
|
745
749
|
* @param request - Session identity, prompt content, source metadata, and delivery mode.
|
|
746
750
|
* @returns acknowledgement that the Agent accepted the prompt.
|
|
747
751
|
*/
|
|
748
752
|
async prompt(request) {
|
|
753
|
+
if (!hasPromptContent(request.content)) throw new RemoteError("gateway/bad-request", "prompt content must include non-whitespace text or an attachment", {});
|
|
749
754
|
const clientTimeZone = request.clientTimeZone === void 0 ? void 0 : canonicalClientTimeZone(request.clientTimeZone);
|
|
750
755
|
if (request.clientTimeZone !== void 0 && clientTimeZone === void 0) throw new RemoteError("session/invalid-time-zone", "clientTimeZone must be UTC or a valid IANA Area/Location name", { value: request.clientTimeZone });
|
|
751
756
|
const agent = await this.resolveAgent(request.sessionId);
|
|
757
|
+
if (hasPromptRequest(agent, request.requestId)) return { accepted: true };
|
|
752
758
|
const selection = this.agents.selectionFor(agent).current;
|
|
753
759
|
if (!routeServed(this.ctx, selection.provider)) throw new RemoteError("session/model-unavailable", `no adapter serves provider "${selection.provider}"; select a model for this session`, {
|
|
754
760
|
provider: selection.provider,
|
|
@@ -762,17 +768,33 @@ var SessionCommandController = class {
|
|
|
762
768
|
const hasImage = request.content.some((part) => part.type === "image");
|
|
763
769
|
const admit = async () => {
|
|
764
770
|
try {
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
771
|
+
const env_2 = {
|
|
772
|
+
stack: [],
|
|
773
|
+
error: void 0,
|
|
774
|
+
hasError: false
|
|
775
|
+
};
|
|
776
|
+
try {
|
|
777
|
+
if (hasImage) {
|
|
778
|
+
const current = this.agents.selectionFor(agent).current;
|
|
779
|
+
const model = await this.ctx.llm.resolveModelInfo(current.provider, current.model);
|
|
780
|
+
if (model.inputModalities !== void 0 && !model.inputModalities.includes("image")) throw new RemoteError("session/attachment-invalid", `Model "${current.model}" does not support image input.`, { reason: "MODEL_DOES_NOT_SUPPORT_IMAGES" });
|
|
781
|
+
}
|
|
782
|
+
const admission = resolvePromptFileReceipts(request.content, (receiptId) => this.ctx.fileUploads.resolve(agent, receiptId));
|
|
783
|
+
const message = createUserMessage({
|
|
784
|
+
content: await this.ctx.attachments.admitPromptContent(admission.content),
|
|
785
|
+
source
|
|
786
|
+
});
|
|
787
|
+
if (this.ctx.agents.get(agent.id) !== agent) throw new RemoteError("session/not-found", `session "${agent.id}" was disposed during prompt admission`, { sessionId: agent.id });
|
|
788
|
+
const binding = __addDisposableResource$3(env_2, this.ctx.fileUploads.bindPrompt(agent, admission.receiptIds, request.requestId), false);
|
|
789
|
+
if (request.mode === "steer") agent.steer(message);
|
|
790
|
+
else agent.followup(message);
|
|
791
|
+
binding.commit();
|
|
792
|
+
} catch (e_2) {
|
|
793
|
+
env_2.error = e_2;
|
|
794
|
+
env_2.hasError = true;
|
|
795
|
+
} finally {
|
|
796
|
+
__disposeResources$3(env_2);
|
|
769
797
|
}
|
|
770
|
-
const message = createUserMessage({
|
|
771
|
-
content: await admitPromptContent(this.ctx.attachments, request.content),
|
|
772
|
-
source
|
|
773
|
-
});
|
|
774
|
-
if (request.mode === "steer") agent.steer(message);
|
|
775
|
-
else agent.followup(message);
|
|
776
798
|
} catch (error) {
|
|
777
799
|
if (remoteErrorOf(error) !== void 0) throw error;
|
|
778
800
|
if (error instanceof AttachmentError) throw new RemoteError("session/attachment-invalid", error.message, { reason: error.code });
|
|
@@ -814,10 +836,16 @@ var SessionCommandController = class {
|
|
|
814
836
|
* @returns acknowledgement that the queue mutation was applied.
|
|
815
837
|
*/
|
|
816
838
|
updateQueue(request) {
|
|
817
|
-
if (request.action.kind === "edit"
|
|
839
|
+
if (request.action.kind === "edit") {
|
|
840
|
+
if (request.action.content.some((block) => block.type !== "text")) throw new RemoteError("session/attachment-invalid", "queue edits accept text content only", { reason: "QUEUE_EDIT_NON_TEXT" });
|
|
841
|
+
if (!hasPromptContent(request.action.content)) throw new RemoteError("gateway/bad-request", "queue edit content must include non-whitespace text", {});
|
|
842
|
+
}
|
|
818
843
|
const agent = this.ctx.agents.get(request.sessionId);
|
|
819
|
-
if (agent !== void 0 && hasApiSessionSubagentOwner(this.ctx, agent.session, agent)) throw apiSessionSubagentOwnershipError(request.sessionId);
|
|
820
844
|
if (agent === void 0) throw new RemoteError("session/queue-item-not-found", "queued item is no longer pending", { itemId: request.itemId });
|
|
845
|
+
if (hasApiSessionSubagentOwner(this.ctx, agent.session, agent)) {
|
|
846
|
+
const identity = this.ctx.sessionProjections.snapshot(agent.session, ["subagent"]).values.subagent;
|
|
847
|
+
if (identity?.mode !== "continuable" || !agent.session.isOwnSeq(identity.seq)) throw apiSessionSubagentOwnershipError(request.sessionId);
|
|
848
|
+
}
|
|
821
849
|
const nextTurn = agent.inbox.nextTurn.find((message) => message.id === request.itemId);
|
|
822
850
|
const nextStep = agent.inbox.nextStep.find((message) => message.id === request.itemId);
|
|
823
851
|
const located = nextTurn === void 0 ? nextStep === void 0 ? void 0 : {
|
|
@@ -830,13 +858,25 @@ var SessionCommandController = class {
|
|
|
830
858
|
if (located === void 0) throw new RemoteError("session/queue-item-not-found", "queued item is no longer pending", { itemId: request.itemId });
|
|
831
859
|
const { target, message } = located;
|
|
832
860
|
if (request.action.kind === "steer" && (target !== "next-turn" || agent.status !== "running")) throw new RemoteError("session/steer-unavailable", "current turn no longer accepts steering", { itemId: request.itemId });
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
861
|
+
switch (request.action.kind) {
|
|
862
|
+
case "edit":
|
|
863
|
+
agent.inbox.replace(request.itemId, freezeMessage({
|
|
864
|
+
...message,
|
|
865
|
+
content: [...request.action.content]
|
|
866
|
+
}));
|
|
867
|
+
break;
|
|
868
|
+
case "remove": {
|
|
869
|
+
agent.inbox.remove(request.itemId);
|
|
870
|
+
const source = message.source;
|
|
871
|
+
if (source.kind === "user" && "rpcId" in source) this.ctx.fileUploads.retirePrompt(agent, source.rpcId);
|
|
872
|
+
break;
|
|
873
|
+
}
|
|
874
|
+
case "steer":
|
|
875
|
+
agent.inbox.remove(request.itemId);
|
|
876
|
+
agent.steer(message);
|
|
877
|
+
break;
|
|
878
|
+
/* v8 ignore next 2 -- closed-union exhaustiveness guard */
|
|
879
|
+
default: assertNever(request.action, "queue action");
|
|
840
880
|
}
|
|
841
881
|
return { accepted: true };
|
|
842
882
|
}
|
|
@@ -897,6 +937,34 @@ var SessionCommandController = class {
|
|
|
897
937
|
}
|
|
898
938
|
}
|
|
899
939
|
};
|
|
940
|
+
function resolvePromptFileReceipts(content, stagedFile) {
|
|
941
|
+
const receiptIds = /* @__PURE__ */ new Set();
|
|
942
|
+
return {
|
|
943
|
+
content: content.map((part) => {
|
|
944
|
+
if (part.type !== "file") return part;
|
|
945
|
+
const attachment = stagedFile(part.receiptId);
|
|
946
|
+
if (attachment === void 0) throw new RemoteError("session/attachment-invalid", "File was not uploaded for this session.", { reason: "FILE_NOT_STAGED" });
|
|
947
|
+
receiptIds.add(part.receiptId);
|
|
948
|
+
return {
|
|
949
|
+
type: "file",
|
|
950
|
+
attachment
|
|
951
|
+
};
|
|
952
|
+
}),
|
|
953
|
+
receiptIds: [...receiptIds]
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
function hasPromptRequest(agent, requestId) {
|
|
957
|
+
const matches = (message) => {
|
|
958
|
+
const source = message.source;
|
|
959
|
+
return source.kind === "user" && "rpcId" in source && source.rpcId === requestId;
|
|
960
|
+
};
|
|
961
|
+
if (agent.inbox.nextTurn.some(matches) || agent.inbox.nextStep.some(matches)) return true;
|
|
962
|
+
return agent.session.snapshotEvents().some((event) => {
|
|
963
|
+
if (event.type !== "user/message") return false;
|
|
964
|
+
const source = event.data.source;
|
|
965
|
+
return source.kind === "user" && "rpcId" in source && source.rpcId === requestId;
|
|
966
|
+
});
|
|
967
|
+
}
|
|
900
968
|
function imageBlockIn(content, match) {
|
|
901
969
|
if (!Array.isArray(content)) return void 0;
|
|
902
970
|
for (const value of content) {
|
|
@@ -922,7 +990,10 @@ function imageInEvent(event, match) {
|
|
|
922
990
|
const found = imageBlockIn(inserted.content, match);
|
|
923
991
|
if (found !== void 0) return found;
|
|
924
992
|
}
|
|
925
|
-
|
|
993
|
+
if (event.type === "assistant/message" || event.type === "assistant/attempt") for (const chunk of assistantStreamChunks(event.data.stream, "block-end")) {
|
|
994
|
+
const found = imageBlockIn([chunk.block], match);
|
|
995
|
+
if (found !== void 0) return found;
|
|
996
|
+
}
|
|
926
997
|
}
|
|
927
998
|
function referencedImage(events, attachmentId) {
|
|
928
999
|
for (const event of events) {
|
|
@@ -943,9 +1014,6 @@ var SessionControlController = class {
|
|
|
943
1014
|
/** @param ctx - Host context carrying live Agent, projection, and jobs services. */
|
|
944
1015
|
constructor(ctx) {
|
|
945
1016
|
this.ctx = ctx;
|
|
946
|
-
ctx.on("session/event", (session, event) => {
|
|
947
|
-
this.onSessionEvent(session, event);
|
|
948
|
-
});
|
|
949
1017
|
ctx.sessionProjections.onChanged((session, key, value, seq) => {
|
|
950
1018
|
this.broadcast({
|
|
951
1019
|
type: "projection",
|
|
@@ -954,6 +1022,13 @@ var SessionControlController = class {
|
|
|
954
1022
|
value,
|
|
955
1023
|
seq
|
|
956
1024
|
});
|
|
1025
|
+
if (key !== "inbox") return;
|
|
1026
|
+
if (this.ctx.agents.get(session.id)?.session !== session) return;
|
|
1027
|
+
this.broadcast({
|
|
1028
|
+
type: "queue",
|
|
1029
|
+
sessionId: session.id,
|
|
1030
|
+
items: queueItemsFromInbox(value)
|
|
1031
|
+
});
|
|
957
1032
|
});
|
|
958
1033
|
ctx.inject(["jobs"], (jobsCtx) => {
|
|
959
1034
|
jobsCtx.jobs.onJobsChanged((owner) => {
|
|
@@ -1019,16 +1094,6 @@ var SessionControlController = class {
|
|
|
1019
1094
|
}
|
|
1020
1095
|
return blocks;
|
|
1021
1096
|
}
|
|
1022
|
-
onSessionEvent(session, event) {
|
|
1023
|
-
if (event.type !== "agent/inbox/spliced") return;
|
|
1024
|
-
const agent = this.ctx.agents.get(session.id);
|
|
1025
|
-
if (agent?.session !== session) return;
|
|
1026
|
-
this.broadcast({
|
|
1027
|
-
type: "queue",
|
|
1028
|
-
sessionId: session.id,
|
|
1029
|
-
items: queueItems(agent, event.data)
|
|
1030
|
-
});
|
|
1031
|
-
}
|
|
1032
1097
|
onJobsChanged(owner) {
|
|
1033
1098
|
if (owner !== void 0) {
|
|
1034
1099
|
this.broadcast({
|
|
@@ -1093,12 +1158,14 @@ var ControlQueue = class {
|
|
|
1093
1158
|
}
|
|
1094
1159
|
}
|
|
1095
1160
|
};
|
|
1096
|
-
function queueItems(agent
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
};
|
|
1101
|
-
|
|
1161
|
+
function queueItems(agent) {
|
|
1162
|
+
return queueItemsFromInbox({
|
|
1163
|
+
"next-turn": agent.inbox.nextTurn,
|
|
1164
|
+
"next-step": agent.inbox.nextStep
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
function queueItemsFromInbox(inbox) {
|
|
1168
|
+
return [...inbox["next-turn"].map((message) => ({
|
|
1102
1169
|
id: message.id,
|
|
1103
1170
|
placement: "queued",
|
|
1104
1171
|
...promptRpcId(message),
|
|
@@ -1106,7 +1173,7 @@ function queueItems(agent, splice) {
|
|
|
1106
1173
|
id: message.id,
|
|
1107
1174
|
content: message.content
|
|
1108
1175
|
}
|
|
1109
|
-
})), ...
|
|
1176
|
+
})), ...inbox["next-step"].map((message) => ({
|
|
1110
1177
|
id: message.id,
|
|
1111
1178
|
placement: message.source.kind === "user" ? "steering" : "context",
|
|
1112
1179
|
...promptRpcId(message),
|
|
@@ -1133,9 +1200,90 @@ function jobView(job) {
|
|
|
1133
1200
|
};
|
|
1134
1201
|
}
|
|
1135
1202
|
//#endregion
|
|
1203
|
+
//#region lib/types/assistant-stream.js
|
|
1204
|
+
/** Process-local assistant state retained for reconnecting Web followers. */
|
|
1205
|
+
const EMPTY_BASELINE = { revision: 0 };
|
|
1206
|
+
/**
|
|
1207
|
+
* Folds dense Agent frames and materializes one shared immutable reconnect
|
|
1208
|
+
* baseline per accepted revision.
|
|
1209
|
+
*/
|
|
1210
|
+
var SessionAssistantStreamAccumulator = class {
|
|
1211
|
+
activeAttempt;
|
|
1212
|
+
revision = 0;
|
|
1213
|
+
snapshotValue = EMPTY_BASELINE;
|
|
1214
|
+
dirty = false;
|
|
1215
|
+
/**
|
|
1216
|
+
* Fold one trusted frame from the current attached Agent lifecycle.
|
|
1217
|
+
* @param frame - next dense process-local Assistant frame.
|
|
1218
|
+
* @param durableCursor - last committed Session seq when this frame was observed.
|
|
1219
|
+
*/
|
|
1220
|
+
accept(frame, durableCursor) {
|
|
1221
|
+
if (frame.type === "start" && frame.revision === 1 && this.revision !== 0) {
|
|
1222
|
+
this.activeAttempt = void 0;
|
|
1223
|
+
this.revision = 0;
|
|
1224
|
+
}
|
|
1225
|
+
if (frame.revision !== this.revision + 1) {
|
|
1226
|
+
this.activeAttempt = void 0;
|
|
1227
|
+
this.revision = frame.revision;
|
|
1228
|
+
this.dirty = true;
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
this.revision = frame.revision;
|
|
1232
|
+
switch (frame.type) {
|
|
1233
|
+
case "start":
|
|
1234
|
+
this.activeAttempt = {
|
|
1235
|
+
attemptId: frame.attemptId,
|
|
1236
|
+
startedAfterSeq: durableCursor,
|
|
1237
|
+
turn: frame.turn,
|
|
1238
|
+
step: frame.step,
|
|
1239
|
+
stream: new AssistantStreamAccumulator(),
|
|
1240
|
+
nextIndex: 0
|
|
1241
|
+
};
|
|
1242
|
+
break;
|
|
1243
|
+
case "chunk": {
|
|
1244
|
+
const attempt = this.activeAttempt;
|
|
1245
|
+
if (attempt === void 0 || attempt.attemptId !== frame.attemptId || frame.index !== attempt.nextIndex) {
|
|
1246
|
+
this.activeAttempt = void 0;
|
|
1247
|
+
break;
|
|
1248
|
+
}
|
|
1249
|
+
attempt.stream.push({
|
|
1250
|
+
time: frame.time,
|
|
1251
|
+
chunk: frame.chunk
|
|
1252
|
+
});
|
|
1253
|
+
attempt.nextIndex += 1;
|
|
1254
|
+
break;
|
|
1255
|
+
}
|
|
1256
|
+
case "end":
|
|
1257
|
+
this.activeAttempt = void 0;
|
|
1258
|
+
break;
|
|
1259
|
+
}
|
|
1260
|
+
this.dirty = true;
|
|
1261
|
+
}
|
|
1262
|
+
/**
|
|
1263
|
+
* Read the cached reconnect baseline, materializing it after a state change.
|
|
1264
|
+
* @returns the identity-stable baseline for the latest accepted revision.
|
|
1265
|
+
*/
|
|
1266
|
+
snapshot() {
|
|
1267
|
+
if (!this.dirty) return this.snapshotValue;
|
|
1268
|
+
this.snapshotValue = {
|
|
1269
|
+
revision: this.revision,
|
|
1270
|
+
...this.activeAttempt === void 0 ? {} : { activeAttempt: {
|
|
1271
|
+
attemptId: this.activeAttempt.attemptId,
|
|
1272
|
+
startedAfterSeq: this.activeAttempt.startedAfterSeq,
|
|
1273
|
+
turn: this.activeAttempt.turn,
|
|
1274
|
+
step: this.activeAttempt.step,
|
|
1275
|
+
nextIndex: this.activeAttempt.nextIndex,
|
|
1276
|
+
stream: this.activeAttempt.stream.snapshot()
|
|
1277
|
+
} }
|
|
1278
|
+
};
|
|
1279
|
+
this.dirty = false;
|
|
1280
|
+
return this.snapshotValue;
|
|
1281
|
+
}
|
|
1282
|
+
};
|
|
1283
|
+
//#endregion
|
|
1136
1284
|
//#region lib/types/history.js
|
|
1137
1285
|
/** Cold Session history pagination and live-event source. */
|
|
1138
|
-
var __addDisposableResource$
|
|
1286
|
+
var __addDisposableResource$2 = function(env, value, async) {
|
|
1139
1287
|
if (value !== null && value !== void 0) {
|
|
1140
1288
|
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
1141
1289
|
var dispose, inner;
|
|
@@ -1164,7 +1312,7 @@ var __addDisposableResource$3 = function(env, value, async) {
|
|
|
1164
1312
|
} else if (async) env.stack.push({ async: true });
|
|
1165
1313
|
return value;
|
|
1166
1314
|
};
|
|
1167
|
-
var __disposeResources$
|
|
1315
|
+
var __disposeResources$2 = (function(SuppressedError) {
|
|
1168
1316
|
return function(env) {
|
|
1169
1317
|
function fail(e) {
|
|
1170
1318
|
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
@@ -1200,6 +1348,7 @@ var SessionHistoryController = class {
|
|
|
1200
1348
|
ctx;
|
|
1201
1349
|
promote;
|
|
1202
1350
|
closeFollowers = /* @__PURE__ */ new Set();
|
|
1351
|
+
assistantStreams = /* @__PURE__ */ new Map();
|
|
1203
1352
|
/**
|
|
1204
1353
|
* @param ctx - Host context carrying Session query and projection services.
|
|
1205
1354
|
* @param promote - starts ordinary Session activation after snapshot delivery.
|
|
@@ -1207,6 +1356,17 @@ var SessionHistoryController = class {
|
|
|
1207
1356
|
constructor(ctx, promote) {
|
|
1208
1357
|
this.ctx = ctx;
|
|
1209
1358
|
this.promote = promote;
|
|
1359
|
+
ctx.on("agent/assistant-stream", ({ agent, frame }) => {
|
|
1360
|
+
let stream = this.assistantStreams.get(agent.session.id);
|
|
1361
|
+
if (stream === void 0) {
|
|
1362
|
+
stream = new SessionAssistantStreamAccumulator();
|
|
1363
|
+
this.assistantStreams.set(agent.session.id, stream);
|
|
1364
|
+
}
|
|
1365
|
+
stream.accept(frame, cursorBeforeNext(agent.session.seq));
|
|
1366
|
+
}, { global: true });
|
|
1367
|
+
ctx.on("agent/disposed", ({ agent }) => {
|
|
1368
|
+
this.assistantStreams.delete(agent.session.id);
|
|
1369
|
+
}, { global: true });
|
|
1210
1370
|
ctx.effect(() => () => {
|
|
1211
1371
|
for (const close of this.closeFollowers) close();
|
|
1212
1372
|
this.closeFollowers.clear();
|
|
@@ -1228,7 +1388,7 @@ var SessionHistoryController = class {
|
|
|
1228
1388
|
validatePageRequest(request);
|
|
1229
1389
|
const throughSeq = request.throughSeq === -1 ? -1 : SessionSeq(request.throughSeq);
|
|
1230
1390
|
const beforeSeq = request.beforeSeq === void 0 ? void 0 : SessionLogOffset(request.beforeSeq);
|
|
1231
|
-
const source = __addDisposableResource$
|
|
1391
|
+
const source = __addDisposableResource$2(env_1, await this.sourceFor(request.address, signal, false), false);
|
|
1232
1392
|
signal.throwIfAborted();
|
|
1233
1393
|
const sourceLog = source.events;
|
|
1234
1394
|
const sourceCursor = sourceLog.at(-1)?.seq ?? -1;
|
|
@@ -1244,14 +1404,14 @@ var SessionHistoryController = class {
|
|
|
1244
1404
|
env_1.error = e_1;
|
|
1245
1405
|
env_1.hasError = true;
|
|
1246
1406
|
} finally {
|
|
1247
|
-
__disposeResources$
|
|
1407
|
+
__disposeResources$2(env_1);
|
|
1248
1408
|
}
|
|
1249
1409
|
}
|
|
1250
1410
|
/**
|
|
1251
1411
|
* Follow events appended after an initial cursor on one durable address.
|
|
1252
1412
|
* @param request - durable address and last committed sequence already held by the caller.
|
|
1253
1413
|
* @param signal - stream cancellation owned by the Remote carrier.
|
|
1254
|
-
* @returns a complete opening snapshot followed by gap-free
|
|
1414
|
+
* @returns a complete opening snapshot followed by gap-free durable events and opted-in assistant frames.
|
|
1255
1415
|
*/
|
|
1256
1416
|
async *follow(request, signal) {
|
|
1257
1417
|
validateFollowRequest(request);
|
|
@@ -1259,6 +1419,7 @@ var SessionHistoryController = class {
|
|
|
1259
1419
|
const target = addressId(address);
|
|
1260
1420
|
const buffered = new Deque();
|
|
1261
1421
|
let snapshotCursor;
|
|
1422
|
+
let assistantStreamOrdinal = 0;
|
|
1262
1423
|
let wake;
|
|
1263
1424
|
const notify = () => {
|
|
1264
1425
|
const resume = wake;
|
|
@@ -1273,13 +1434,28 @@ var SessionHistoryController = class {
|
|
|
1273
1434
|
this.closeFollowers.add(close);
|
|
1274
1435
|
const disposeEvent = this.ctx.on("session/event", (session, event) => {
|
|
1275
1436
|
if (session.id !== target) return;
|
|
1276
|
-
buffered.pushBack(
|
|
1437
|
+
buffered.pushBack({
|
|
1438
|
+
type: "event",
|
|
1439
|
+
event
|
|
1440
|
+
});
|
|
1277
1441
|
notify();
|
|
1278
1442
|
}, { global: true });
|
|
1279
1443
|
const disposeCreated = this.ctx.on("session/created", (session) => {
|
|
1280
1444
|
if (session.id !== target) return;
|
|
1281
1445
|
const suffix = session.snapshotEvents(snapshotCursor === void 0 ? session.firstLiveSeq : SessionLogOffset(snapshotCursor + 1));
|
|
1282
|
-
for (let index = suffix.length - 1; index >= 0; index -= 1) buffered.pushFront(
|
|
1446
|
+
for (let index = suffix.length - 1; index >= 0; index -= 1) buffered.pushFront({
|
|
1447
|
+
type: "event",
|
|
1448
|
+
event: suffix[index]
|
|
1449
|
+
});
|
|
1450
|
+
notify();
|
|
1451
|
+
}, { global: true });
|
|
1452
|
+
const disposeAssistantStream = request.assistantStream !== true ? void 0 : this.ctx.on("agent/assistant-stream", ({ agent, frame }) => {
|
|
1453
|
+
if (agent.session.id !== target) return;
|
|
1454
|
+
buffered.pushBack({
|
|
1455
|
+
type: "assistant-stream",
|
|
1456
|
+
frame: wireAssistantStreamFrame(frame, cursorBeforeNext(agent.session.seq)),
|
|
1457
|
+
ordinal: ++assistantStreamOrdinal
|
|
1458
|
+
});
|
|
1283
1459
|
notify();
|
|
1284
1460
|
}, { global: true });
|
|
1285
1461
|
const onAbort = () => {
|
|
@@ -1293,22 +1469,25 @@ var SessionHistoryController = class {
|
|
|
1293
1469
|
hasError: false
|
|
1294
1470
|
};
|
|
1295
1471
|
try {
|
|
1296
|
-
const source = __addDisposableResource$
|
|
1472
|
+
const source = __addDisposableResource$2(env_2, await this.sourceFor(address, signal, true), false);
|
|
1297
1473
|
const events = source.events;
|
|
1298
1474
|
signal.throwIfAborted();
|
|
1299
1475
|
const cursor = source.cursor;
|
|
1300
1476
|
snapshotCursor = cursor;
|
|
1301
1477
|
const page = paginate(events, void 0, request.maxMessages ?? DEFAULT_MAX_MESSAGES);
|
|
1478
|
+
const assistantStream = request.assistantStream === true ? this.assistantStreams.get(target)?.snapshot() ?? { revision: 0 } : void 0;
|
|
1479
|
+
const assistantStreamOrdinalCut = assistantStreamOrdinal;
|
|
1302
1480
|
yield {
|
|
1303
1481
|
type: "snapshot",
|
|
1304
|
-
header: wireHeader(source.header
|
|
1482
|
+
header: wireHeader(source.header),
|
|
1305
1483
|
cursor,
|
|
1306
1484
|
records: pageRecords(page.events),
|
|
1307
1485
|
hasMore: page.hasMore,
|
|
1308
1486
|
projections: source.projections === void 0 ? {
|
|
1309
1487
|
asOfSeq: cursor,
|
|
1310
1488
|
values: {}
|
|
1311
|
-
} : projectionBlock(source.projections)
|
|
1489
|
+
} : projectionBlock(source.projections),
|
|
1490
|
+
...assistantStream === void 0 ? {} : { assistantStream }
|
|
1312
1491
|
};
|
|
1313
1492
|
if (address.kind === "session" && source.source === "prepared") {
|
|
1314
1493
|
const promotion = source.retain();
|
|
@@ -1328,23 +1507,31 @@ var SessionHistoryController = class {
|
|
|
1328
1507
|
});
|
|
1329
1508
|
continue;
|
|
1330
1509
|
}
|
|
1510
|
+
if (item.type === "assistant-stream") {
|
|
1511
|
+
if (item.ordinal > assistantStreamOrdinalCut) yield {
|
|
1512
|
+
type: "assistant-stream",
|
|
1513
|
+
frame: item.frame
|
|
1514
|
+
};
|
|
1515
|
+
continue;
|
|
1516
|
+
}
|
|
1331
1517
|
const expectedSeq = SessionSeq(nextOffset);
|
|
1332
|
-
if (item.seq < expectedSeq) continue;
|
|
1333
|
-
if (item.seq !== expectedSeq) throw new RemoteError("gateway/internal", `session event stream skipped seq ${String(expectedSeq)}`, {});
|
|
1518
|
+
if (item.event.seq < expectedSeq) continue;
|
|
1519
|
+
if (item.event.seq !== expectedSeq) throw new RemoteError("gateway/internal", `session event stream skipped seq ${String(expectedSeq)}`, {});
|
|
1334
1520
|
nextOffset = SessionLogOffset(nextOffset + 1);
|
|
1335
|
-
yield entryFor(item);
|
|
1521
|
+
yield entryFor(item.event);
|
|
1336
1522
|
}
|
|
1337
1523
|
} catch (e_2) {
|
|
1338
1524
|
env_2.error = e_2;
|
|
1339
1525
|
env_2.hasError = true;
|
|
1340
1526
|
} finally {
|
|
1341
|
-
__disposeResources$
|
|
1527
|
+
__disposeResources$2(env_2);
|
|
1342
1528
|
}
|
|
1343
1529
|
} finally {
|
|
1344
1530
|
this.closeFollowers.delete(close);
|
|
1345
1531
|
signal.removeEventListener("abort", onAbort);
|
|
1346
1532
|
disposeCreated();
|
|
1347
1533
|
disposeEvent();
|
|
1534
|
+
disposeAssistantStream?.();
|
|
1348
1535
|
}
|
|
1349
1536
|
}
|
|
1350
1537
|
async sourceFor(address, signal, withProjections) {
|
|
@@ -1371,6 +1558,20 @@ var SessionHistoryController = class {
|
|
|
1371
1558
|
}
|
|
1372
1559
|
}
|
|
1373
1560
|
};
|
|
1561
|
+
function cursorBeforeNext(nextSeq) {
|
|
1562
|
+
return nextSeq === 0 ? -1 : SessionSeq(nextSeq - 1);
|
|
1563
|
+
}
|
|
1564
|
+
function wireAssistantStreamFrame(frame, durableCursor) {
|
|
1565
|
+
if (frame.type === "start") return {
|
|
1566
|
+
...frame,
|
|
1567
|
+
startedAfterSeq: durableCursor
|
|
1568
|
+
};
|
|
1569
|
+
if (frame.type === "end") return frame;
|
|
1570
|
+
return {
|
|
1571
|
+
...frame,
|
|
1572
|
+
chunk: frame.chunk
|
|
1573
|
+
};
|
|
1574
|
+
}
|
|
1374
1575
|
function projectionBlock(snapshot) {
|
|
1375
1576
|
return {
|
|
1376
1577
|
asOfSeq: snapshot.asOfSeq,
|
|
@@ -1437,13 +1638,9 @@ function paginate(events, beforeSeq, maxMessages, throughSeq = events.at(-1)?.se
|
|
|
1437
1638
|
hasMore: cut > 0
|
|
1438
1639
|
};
|
|
1439
1640
|
}
|
|
1440
|
-
/** Translate logical Session metadata to the
|
|
1441
|
-
function wireHeader(header
|
|
1442
|
-
|
|
1443
|
-
return {
|
|
1444
|
-
...wire,
|
|
1445
|
-
...isSeeded ? { seedLength: inheritedEventCount } : {}
|
|
1446
|
-
};
|
|
1641
|
+
/** Translate current logical Session metadata to the browser wire. */
|
|
1642
|
+
function wireHeader(header) {
|
|
1643
|
+
return { ...header };
|
|
1447
1644
|
}
|
|
1448
1645
|
function entryFor(event) {
|
|
1449
1646
|
return {
|
|
@@ -1451,40 +1648,9 @@ function entryFor(event) {
|
|
|
1451
1648
|
event
|
|
1452
1649
|
};
|
|
1453
1650
|
}
|
|
1454
|
-
function chunkEntryFor(row) {
|
|
1455
|
-
switch (row.type) {
|
|
1456
|
-
case "text-chunks": return {
|
|
1457
|
-
type: "chunks",
|
|
1458
|
-
event: {
|
|
1459
|
-
type: "chunkrow/text-chunks",
|
|
1460
|
-
seq: row.seq0,
|
|
1461
|
-
time: row.time0,
|
|
1462
|
-
data: row.data
|
|
1463
|
-
}
|
|
1464
|
-
};
|
|
1465
|
-
case "reasoning-chunks": return {
|
|
1466
|
-
type: "chunks",
|
|
1467
|
-
event: {
|
|
1468
|
-
type: "chunkrow/reasoning-chunks",
|
|
1469
|
-
seq: row.seq0,
|
|
1470
|
-
time: row.time0,
|
|
1471
|
-
data: row.data
|
|
1472
|
-
}
|
|
1473
|
-
};
|
|
1474
|
-
case "tool-call-chunks": return {
|
|
1475
|
-
type: "chunks",
|
|
1476
|
-
event: {
|
|
1477
|
-
type: "chunkrow/tool-call-chunks",
|
|
1478
|
-
seq: row.seq0,
|
|
1479
|
-
time: row.time0,
|
|
1480
|
-
data: row.data
|
|
1481
|
-
}
|
|
1482
|
-
};
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1485
1651
|
/** Encode one bounded logical page without changing its pagination cut. */
|
|
1486
1652
|
function pageRecords(events) {
|
|
1487
|
-
return
|
|
1653
|
+
return events.map(entryFor);
|
|
1488
1654
|
}
|
|
1489
1655
|
//#endregion
|
|
1490
1656
|
//#region lib/types/file-references.js
|
|
@@ -1575,67 +1741,6 @@ let SessionFileReferences = (() => {
|
|
|
1575
1741
|
//#endregion
|
|
1576
1742
|
//#region lib/types/list.js
|
|
1577
1743
|
/** Cold-safe Session list and search projection. */
|
|
1578
|
-
var __addDisposableResource$2 = function(env, value, async) {
|
|
1579
|
-
if (value !== null && value !== void 0) {
|
|
1580
|
-
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
1581
|
-
var dispose, inner;
|
|
1582
|
-
if (async) {
|
|
1583
|
-
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
1584
|
-
dispose = value[Symbol.asyncDispose];
|
|
1585
|
-
}
|
|
1586
|
-
if (dispose === void 0) {
|
|
1587
|
-
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
1588
|
-
dispose = value[Symbol.dispose];
|
|
1589
|
-
if (async) inner = dispose;
|
|
1590
|
-
}
|
|
1591
|
-
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
1592
|
-
if (inner) dispose = function() {
|
|
1593
|
-
try {
|
|
1594
|
-
inner.call(this);
|
|
1595
|
-
} catch (e) {
|
|
1596
|
-
return Promise.reject(e);
|
|
1597
|
-
}
|
|
1598
|
-
};
|
|
1599
|
-
env.stack.push({
|
|
1600
|
-
value,
|
|
1601
|
-
dispose,
|
|
1602
|
-
async
|
|
1603
|
-
});
|
|
1604
|
-
} else if (async) env.stack.push({ async: true });
|
|
1605
|
-
return value;
|
|
1606
|
-
};
|
|
1607
|
-
var __disposeResources$2 = (function(SuppressedError) {
|
|
1608
|
-
return function(env) {
|
|
1609
|
-
function fail(e) {
|
|
1610
|
-
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
1611
|
-
env.hasError = true;
|
|
1612
|
-
}
|
|
1613
|
-
var r, s = 0;
|
|
1614
|
-
function next() {
|
|
1615
|
-
while (r = env.stack.pop()) try {
|
|
1616
|
-
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
1617
|
-
if (r.dispose) {
|
|
1618
|
-
var result = r.dispose.call(r.value);
|
|
1619
|
-
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) {
|
|
1620
|
-
fail(e);
|
|
1621
|
-
return next();
|
|
1622
|
-
});
|
|
1623
|
-
} else s |= 1;
|
|
1624
|
-
} catch (e) {
|
|
1625
|
-
fail(e);
|
|
1626
|
-
}
|
|
1627
|
-
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
1628
|
-
if (env.hasError) throw env.error;
|
|
1629
|
-
}
|
|
1630
|
-
return next();
|
|
1631
|
-
};
|
|
1632
|
-
})(typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) {
|
|
1633
|
-
var e = new Error(message);
|
|
1634
|
-
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
1635
|
-
});
|
|
1636
|
-
/** Default maximum artifact size eligible for one cold projection observation. */
|
|
1637
|
-
const DEFAULT_COLD_BLANK_PROBE_MAX_BYTES = 1024;
|
|
1638
|
-
const COLD_SUMMARY_BATCH_SIZE = 16;
|
|
1639
1744
|
const SEARCH_PROVIDER_CALL_LIMIT = 100;
|
|
1640
1745
|
const SESSION_SEARCH_QUERY_MAX_CHARS = 500;
|
|
1641
1746
|
const MESSAGE_TYPES = new Set(["user/message", "assistant/message"]);
|
|
@@ -1684,14 +1789,9 @@ function truncateUnicodeCodePoints(value, maximum) {
|
|
|
1684
1789
|
/** Owns list projection registration, bounded cold summaries, and authorized search. */
|
|
1685
1790
|
var ApiSessionList = class {
|
|
1686
1791
|
ctx;
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
* @param ctx - Host context carrying Session, query, persistence, and projection services.
|
|
1690
|
-
* @param coldBlankProbeMaxBytes - maximum physical artifact size eligible for a full observation.
|
|
1691
|
-
*/
|
|
1692
|
-
constructor(ctx, coldBlankProbeMaxBytes) {
|
|
1792
|
+
/** @param ctx - Host context carrying Session, query, persistence, and projection services. */
|
|
1793
|
+
constructor(ctx) {
|
|
1693
1794
|
this.ctx = ctx;
|
|
1694
|
-
this.coldBlankProbeMaxBytes = coldBlankProbeMaxBytes;
|
|
1695
1795
|
ctx.sessionProjections.register({
|
|
1696
1796
|
key: "sessionListMetadata",
|
|
1697
1797
|
stateSchema: sessionListMetadataSchema,
|
|
@@ -1757,21 +1857,12 @@ var ApiSessionList = class {
|
|
|
1757
1857
|
if (record.header.cwd === void 0) continue;
|
|
1758
1858
|
cold.push(record.header);
|
|
1759
1859
|
}
|
|
1760
|
-
for (
|
|
1761
|
-
const settled = await Promise.allSettled(cold.slice(offset, offset + COLD_SUMMARY_BATCH_SIZE).map((header) => this.summarizeCold(header, signal)));
|
|
1762
|
-
for (const result of settled) {
|
|
1763
|
-
if (result.status === "rejected") throw result.reason;
|
|
1764
|
-
items.push(result.value);
|
|
1765
|
-
}
|
|
1766
|
-
}
|
|
1860
|
+
for (const header of cold) items.push(this.summarizeCold(header));
|
|
1767
1861
|
items.sort((left, right) => right.updatedAt - left.updatedAt);
|
|
1768
1862
|
return items;
|
|
1769
1863
|
}
|
|
1770
|
-
|
|
1771
|
-
const
|
|
1772
|
-
const projections = cached?.values.sessionListMetadata?.blank === false ? cached : await this.probeSmallCold(header, signal) ?? cached;
|
|
1773
|
-
const raced = this.ctx.sessions.get(header.id);
|
|
1774
|
-
if (raced !== void 0) return this.summaryFor(raced);
|
|
1864
|
+
summarizeCold(header) {
|
|
1865
|
+
const projections = this.projectionsFor(header, void 0);
|
|
1775
1866
|
const metadata = projections?.values.sessionListMetadata;
|
|
1776
1867
|
return {
|
|
1777
1868
|
sessionId: header.id,
|
|
@@ -1782,44 +1873,6 @@ var ApiSessionList = class {
|
|
|
1782
1873
|
...projections === void 0 ? {} : { projections }
|
|
1783
1874
|
};
|
|
1784
1875
|
}
|
|
1785
|
-
async probeSmallCold(header, signal) {
|
|
1786
|
-
if (this.coldBlankProbeMaxBytes === 0) return void 0;
|
|
1787
|
-
const location = this.ctx.get("sessionPersistence")?.locate(header);
|
|
1788
|
-
if (location === void 0) return void 0;
|
|
1789
|
-
signal?.throwIfAborted();
|
|
1790
|
-
try {
|
|
1791
|
-
if ((await stat(location.path)).size > this.coldBlankProbeMaxBytes) return void 0;
|
|
1792
|
-
} catch {
|
|
1793
|
-
signal?.throwIfAborted();
|
|
1794
|
-
return;
|
|
1795
|
-
}
|
|
1796
|
-
try {
|
|
1797
|
-
const env_1 = {
|
|
1798
|
-
stack: [],
|
|
1799
|
-
error: void 0,
|
|
1800
|
-
hasError: false
|
|
1801
|
-
};
|
|
1802
|
-
try {
|
|
1803
|
-
const block = __addDisposableResource$2(env_1, await this.ctx.sessionQuery.observeSession(header.id, {
|
|
1804
|
-
...signal === void 0 ? {} : { signal },
|
|
1805
|
-
projectionMode: "all"
|
|
1806
|
-
}), false).projections;
|
|
1807
|
-
return block === void 0 ? void 0 : {
|
|
1808
|
-
asOfSeq: block.asOfSeq,
|
|
1809
|
-
values: block.values
|
|
1810
|
-
};
|
|
1811
|
-
} catch (e_1) {
|
|
1812
|
-
env_1.error = e_1;
|
|
1813
|
-
env_1.hasError = true;
|
|
1814
|
-
} finally {
|
|
1815
|
-
__disposeResources$2(env_1);
|
|
1816
|
-
}
|
|
1817
|
-
} catch (error) {
|
|
1818
|
-
signal?.throwIfAborted();
|
|
1819
|
-
this.ctx.logger.warn(`api-session.list: small cold observation for "${header.id}" failed; serving it as visible: ${String(error)}`);
|
|
1820
|
-
return;
|
|
1821
|
-
}
|
|
1822
|
-
}
|
|
1823
1876
|
/**
|
|
1824
1877
|
* Search current visible message content without activating any matching Session.
|
|
1825
1878
|
* @param query - literal message-content query.
|
|
@@ -1910,7 +1963,8 @@ var ApiSessionList = class {
|
|
|
1910
1963
|
}
|
|
1911
1964
|
projectionsFor(header, session) {
|
|
1912
1965
|
try {
|
|
1913
|
-
const
|
|
1966
|
+
const cache = this.ctx.get("sessionProjectionCache");
|
|
1967
|
+
const block = session === void 0 ? header.isSeeded ? void 0 : cache?.cachedSnapshot(header, SessionLogOffset(0)) ?? cache?.cachedPredecessorTitle(header, SessionLogOffset(0)) : this.ctx.sessionProjections.cachedSnapshot(session);
|
|
1914
1968
|
return block !== void 0 && Object.keys(block.values).length > 0 ? {
|
|
1915
1969
|
asOfSeq: block.asOfSeq,
|
|
1916
1970
|
values: block.values
|
|
@@ -2264,6 +2318,79 @@ let SessionSkillCatalog = (() => {
|
|
|
2264
2318
|
};
|
|
2265
2319
|
})();
|
|
2266
2320
|
//#endregion
|
|
2321
|
+
//#region lib/types/media-references.js
|
|
2322
|
+
/**
|
|
2323
|
+
* Authenticated GET/HEAD /api/file reads bounded file responses through
|
|
2324
|
+
* the composed filesystem provider. Paths and MIME types do not restrict access;
|
|
2325
|
+
* the connection service authenticates requests before this handler.
|
|
2326
|
+
* @module @deepseek-ai/dsh-api-session-controller/media-references
|
|
2327
|
+
*/
|
|
2328
|
+
const BASE_HEADERS = {
|
|
2329
|
+
"Cache-Control": "private, no-store",
|
|
2330
|
+
"X-Content-Type-Options": "nosniff",
|
|
2331
|
+
"Content-Security-Policy": "sandbox; default-src 'none'"
|
|
2332
|
+
};
|
|
2333
|
+
async function serveFile(request, fs, maxBytes) {
|
|
2334
|
+
const fail = (status, text) => new Response(request.method === "HEAD" ? null : text, {
|
|
2335
|
+
status,
|
|
2336
|
+
headers: BASE_HEADERS
|
|
2337
|
+
});
|
|
2338
|
+
const path = new URL(request.url).searchParams.get("path");
|
|
2339
|
+
if (path === null || path.length === 0) return fail(400, "missing path");
|
|
2340
|
+
if (path.includes("\0") || !isAbsolute(path)) return fail(400, "absolute path required");
|
|
2341
|
+
try {
|
|
2342
|
+
const target = await fs.resolve(path, { signal: request.signal });
|
|
2343
|
+
const mediaType = mime.lookup(target.displayPath) || "application/octet-stream";
|
|
2344
|
+
const headers = {
|
|
2345
|
+
...BASE_HEADERS,
|
|
2346
|
+
"Content-Type": mediaType
|
|
2347
|
+
};
|
|
2348
|
+
if (request.method === "HEAD") {
|
|
2349
|
+
const info = await fs.stat(target, request.signal);
|
|
2350
|
+
if (info === void 0) return fail(404, "not found");
|
|
2351
|
+
if (info.type !== "file") return fail(403, "not a regular file");
|
|
2352
|
+
if (info.size !== void 0) {
|
|
2353
|
+
if (info.size > maxBytes) return fail(413, "file exceeds byte limit");
|
|
2354
|
+
headers["Content-Length"] = String(info.size);
|
|
2355
|
+
}
|
|
2356
|
+
return new Response(null, { headers });
|
|
2357
|
+
}
|
|
2358
|
+
const bytes = await fs.readBytes(target, request.signal, maxBytes);
|
|
2359
|
+
headers["Content-Length"] = String(bytes.byteLength);
|
|
2360
|
+
return new Response(bytes.slice(), { headers });
|
|
2361
|
+
} catch (error) {
|
|
2362
|
+
if (!(error instanceof FsError)) throw error;
|
|
2363
|
+
return fail({
|
|
2364
|
+
FS_NOT_FOUND: 404,
|
|
2365
|
+
FS_NOT_REGULAR_FILE: 403,
|
|
2366
|
+
FS_PERMISSION_DENIED: 403,
|
|
2367
|
+
FS_SANDBOX_DENIED: 403,
|
|
2368
|
+
FS_TOO_LARGE: 413,
|
|
2369
|
+
FS_ABORTED: 499
|
|
2370
|
+
}[error.code] ?? 500, error.code);
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
/**
|
|
2374
|
+
* File-display contribution. The connection service supplies authentication;
|
|
2375
|
+
* `ctx.fs` supplies the execution world's paths, reads, and access policy.
|
|
2376
|
+
*/
|
|
2377
|
+
const SessionMediaReferences = {
|
|
2378
|
+
inject: [
|
|
2379
|
+
"connection",
|
|
2380
|
+
"fs",
|
|
2381
|
+
"attachments"
|
|
2382
|
+
],
|
|
2383
|
+
apply(ctx) {
|
|
2384
|
+
const maxBytes = ctx.attachments.imageLimits.maxImageBytes;
|
|
2385
|
+
ctx.effect(() => ctx.connection.fetch.register({
|
|
2386
|
+
path: "/api/file",
|
|
2387
|
+
methods: ["GET", "HEAD"],
|
|
2388
|
+
requestBody: "buffered",
|
|
2389
|
+
fetch: (request) => serveFile(request, ctx.fs, maxBytes)
|
|
2390
|
+
}), "session-controller: /api/file");
|
|
2391
|
+
}
|
|
2392
|
+
};
|
|
2393
|
+
//#endregion
|
|
2267
2394
|
//#region lib/types/index.js
|
|
2268
2395
|
/** Session Remote owner: cold reads, explicit Agent commands, and live control state. */
|
|
2269
2396
|
var __runInitializers = function(thisArg, initializers, value) {
|
|
@@ -2588,6 +2715,7 @@ let SessionController = (() => {
|
|
|
2588
2715
|
"agentDefaultModel",
|
|
2589
2716
|
"agents",
|
|
2590
2717
|
"attachments",
|
|
2718
|
+
"fileUploads",
|
|
2591
2719
|
"llm",
|
|
2592
2720
|
"sessions",
|
|
2593
2721
|
"sessionProjections",
|
|
@@ -2595,10 +2723,7 @@ let SessionController = (() => {
|
|
|
2595
2723
|
"typert",
|
|
2596
2724
|
"workspaceRegistry"
|
|
2597
2725
|
];
|
|
2598
|
-
static Config = z.object({
|
|
2599
|
-
coldBlankProbeMaxBytes: z.natural().default(DEFAULT_COLD_BLANK_PROBE_MAX_BYTES),
|
|
2600
|
-
nativeOpen: z.boolean()
|
|
2601
|
-
});
|
|
2726
|
+
static Config = z.object({ nativeOpen: z.boolean() });
|
|
2602
2727
|
agents = __runInitializers(this, _instanceExtraInitializers);
|
|
2603
2728
|
commands;
|
|
2604
2729
|
controlState;
|
|
@@ -2609,13 +2734,19 @@ let SessionController = (() => {
|
|
|
2609
2734
|
promotions = /* @__PURE__ */ new Set();
|
|
2610
2735
|
/**
|
|
2611
2736
|
* @param ctx - Host context containing the Session capability assembly.
|
|
2612
|
-
* @param config -
|
|
2737
|
+
* @param config - native-opener deployment policy.
|
|
2738
|
+
* @param internals - host integrations replaceable by direct unit tests.
|
|
2613
2739
|
*/
|
|
2614
2740
|
constructor(ctx, config, internals = {}) {
|
|
2615
2741
|
super(ctx, "sessionController", { namespace: "session" });
|
|
2616
2742
|
installModelSelectionProjection(ctx);
|
|
2617
2743
|
this.agents = new ApiSessionAgentController(ctx);
|
|
2618
2744
|
this.commands = new SessionCommandController(ctx, this.agents, process.cwd());
|
|
2745
|
+
ctx.effect(() => ctx.fileUploads.registerAgentResolver(async (sessionId) => {
|
|
2746
|
+
const result = await this.agents.resolveAgent(sessionId);
|
|
2747
|
+
if ("error" in result) throw result.error;
|
|
2748
|
+
return result.agent;
|
|
2749
|
+
}), "session-controller: file-upload Agent resolver");
|
|
2619
2750
|
this.controlState = new SessionControlController(ctx);
|
|
2620
2751
|
ctx.effect(() => async () => {
|
|
2621
2752
|
await Promise.allSettled([...this.promotions]);
|
|
@@ -2623,10 +2754,11 @@ let SessionController = (() => {
|
|
|
2623
2754
|
this.history = new SessionHistoryController(ctx, (observation) => {
|
|
2624
2755
|
this.promote(observation);
|
|
2625
2756
|
});
|
|
2626
|
-
this.listState = new ApiSessionList(ctx
|
|
2757
|
+
this.listState = new ApiSessionList(ctx);
|
|
2627
2758
|
this.openPath = internals.openPath ?? openNativePath;
|
|
2628
2759
|
this.canOpenPath = internals.canOpenPath ?? (() => config.nativeOpen ?? (internals.openPath !== void 0 || canOpenNativePath()));
|
|
2629
2760
|
ctx.plugin(SessionFileReferences);
|
|
2761
|
+
ctx.plugin(SessionMediaReferences);
|
|
2630
2762
|
ctx.plugin(SessionSkillCatalog);
|
|
2631
2763
|
ctx.on("session/created", (session) => {
|
|
2632
2764
|
ctx.emit("api-session/added", this.listState.summaryFor(session));
|
|
@@ -2827,7 +2959,8 @@ let SessionController = (() => {
|
|
|
2827
2959
|
* Follow one Session log from its opening or resume cursor.
|
|
2828
2960
|
* @param request - durable address and last committed sequence already held by the caller.
|
|
2829
2961
|
* @param signal - cancellation owned by the Remote stream carrier.
|
|
2830
|
-
* @returns a complete opening snapshot followed by gap-free event
|
|
2962
|
+
* @returns a complete opening snapshot followed by gap-free durable event
|
|
2963
|
+
* frames and optional cursorless assistant-stream frames.
|
|
2831
2964
|
*/
|
|
2832
2965
|
follow(request, signal) {
|
|
2833
2966
|
return this.history.follow(request, signal);
|