@deepseek-ai/dsh-api-session-controller 0.1.2-rc.1 → 0.1.5-alpha.1
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 +71 -58
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;
|
|
@@ -672,7 +676,7 @@ var SessionCommandController = class {
|
|
|
672
676
|
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 });
|
|
673
677
|
throw new RemoteError("gateway/internal", `fork source unavailable for session "${request.sessionId}": ${String(error)}`, {});
|
|
674
678
|
}
|
|
675
|
-
const source = __addDisposableResource$
|
|
679
|
+
const source = __addDisposableResource$3(env_1, observed, false);
|
|
676
680
|
const lastSeq = source.events.at(-1)?.seq ?? -1;
|
|
677
681
|
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);
|
|
678
682
|
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 });
|
|
@@ -720,18 +724,20 @@ var SessionCommandController = class {
|
|
|
720
724
|
env_1.error = e_1;
|
|
721
725
|
env_1.hasError = true;
|
|
722
726
|
} finally {
|
|
723
|
-
__disposeResources$
|
|
727
|
+
__disposeResources$3(env_1);
|
|
724
728
|
}
|
|
725
729
|
}
|
|
726
730
|
/**
|
|
727
|
-
*
|
|
731
|
+
* Reject empty content, then admit one prompt after Agent and attachment validation.
|
|
728
732
|
* @param request - Session identity, prompt content, source metadata, and delivery mode.
|
|
729
733
|
* @returns acknowledgement that the Agent accepted the prompt.
|
|
730
734
|
*/
|
|
731
735
|
async prompt(request) {
|
|
736
|
+
if (!hasPromptContent(request.content)) throw new RemoteError("gateway/bad-request", "prompt content must include non-whitespace text or an attachment", {});
|
|
732
737
|
const clientTimeZone = request.clientTimeZone === void 0 ? void 0 : canonicalClientTimeZone(request.clientTimeZone);
|
|
733
738
|
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 });
|
|
734
739
|
const agent = await this.resolveAgent(request.sessionId);
|
|
740
|
+
if (hasPromptRequest(agent, request.requestId)) return { accepted: true };
|
|
735
741
|
const selection = this.agents.selectionFor(agent).current;
|
|
736
742
|
if (!routeServed(this.ctx, selection.provider)) throw new RemoteError("session/model-unavailable", `no adapter serves provider "${selection.provider}"; select a model for this session`, {
|
|
737
743
|
provider: selection.provider,
|
|
@@ -745,17 +751,33 @@ var SessionCommandController = class {
|
|
|
745
751
|
const hasImage = request.content.some((part) => part.type === "image");
|
|
746
752
|
const admit = async () => {
|
|
747
753
|
try {
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
754
|
+
const env_2 = {
|
|
755
|
+
stack: [],
|
|
756
|
+
error: void 0,
|
|
757
|
+
hasError: false
|
|
758
|
+
};
|
|
759
|
+
try {
|
|
760
|
+
if (hasImage) {
|
|
761
|
+
const current = this.agents.selectionFor(agent).current;
|
|
762
|
+
const model = await this.ctx.llm.resolveModelInfo(current.provider, current.model);
|
|
763
|
+
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" });
|
|
764
|
+
}
|
|
765
|
+
const admission = resolvePromptFileReceipts(request.content, (receiptId) => this.ctx.fileUploads.resolve(agent, receiptId));
|
|
766
|
+
const message = createUserMessage({
|
|
767
|
+
content: await this.ctx.attachments.admitPromptContent(admission.content),
|
|
768
|
+
source
|
|
769
|
+
});
|
|
770
|
+
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 });
|
|
771
|
+
const binding = __addDisposableResource$3(env_2, this.ctx.fileUploads.bindPrompt(agent, admission.receiptIds, request.requestId), false);
|
|
772
|
+
if (request.mode === "steer") agent.steer(message);
|
|
773
|
+
else agent.followup(message);
|
|
774
|
+
binding.commit();
|
|
775
|
+
} catch (e_2) {
|
|
776
|
+
env_2.error = e_2;
|
|
777
|
+
env_2.hasError = true;
|
|
778
|
+
} finally {
|
|
779
|
+
__disposeResources$3(env_2);
|
|
752
780
|
}
|
|
753
|
-
const message = createUserMessage({
|
|
754
|
-
content: await admitPromptContent(this.ctx.attachments, request.content),
|
|
755
|
-
source
|
|
756
|
-
});
|
|
757
|
-
if (request.mode === "steer") agent.steer(message);
|
|
758
|
-
else agent.followup(message);
|
|
759
781
|
} catch (error) {
|
|
760
782
|
if (remoteErrorOf(error) !== void 0) throw error;
|
|
761
783
|
if (error instanceof AttachmentError) throw new RemoteError("session/attachment-invalid", error.message, { reason: error.code });
|
|
@@ -797,10 +819,16 @@ var SessionCommandController = class {
|
|
|
797
819
|
* @returns acknowledgement that the queue mutation was applied.
|
|
798
820
|
*/
|
|
799
821
|
updateQueue(request) {
|
|
800
|
-
if (request.action.kind === "edit"
|
|
822
|
+
if (request.action.kind === "edit") {
|
|
823
|
+
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" });
|
|
824
|
+
if (!hasPromptContent(request.action.content)) throw new RemoteError("gateway/bad-request", "queue edit content must include non-whitespace text", {});
|
|
825
|
+
}
|
|
801
826
|
const agent = this.ctx.agents.get(request.sessionId);
|
|
802
|
-
if (agent !== void 0 && hasApiSessionSubagentOwner(this.ctx, agent.session, agent)) throw apiSessionSubagentOwnershipError(request.sessionId);
|
|
803
827
|
if (agent === void 0) throw new RemoteError("session/queue-item-not-found", "queued item is no longer pending", { itemId: request.itemId });
|
|
828
|
+
if (hasApiSessionSubagentOwner(this.ctx, agent.session, agent)) {
|
|
829
|
+
const identity = this.ctx.sessionProjections.snapshot(agent.session, ["subagent"]).values.subagent;
|
|
830
|
+
if (identity?.mode !== "continuable" || !agent.session.isOwnSeq(identity.seq)) throw apiSessionSubagentOwnershipError(request.sessionId);
|
|
831
|
+
}
|
|
804
832
|
const nextTurn = agent.inbox.nextTurn.find((message) => message.id === request.itemId);
|
|
805
833
|
const nextStep = agent.inbox.nextStep.find((message) => message.id === request.itemId);
|
|
806
834
|
const located = nextTurn === void 0 ? nextStep === void 0 ? void 0 : {
|
|
@@ -813,13 +841,25 @@ var SessionCommandController = class {
|
|
|
813
841
|
if (located === void 0) throw new RemoteError("session/queue-item-not-found", "queued item is no longer pending", { itemId: request.itemId });
|
|
814
842
|
const { target, message } = located;
|
|
815
843
|
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 });
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
844
|
+
switch (request.action.kind) {
|
|
845
|
+
case "edit":
|
|
846
|
+
agent.inbox.replace(request.itemId, freezeMessage({
|
|
847
|
+
...message,
|
|
848
|
+
content: [...request.action.content]
|
|
849
|
+
}));
|
|
850
|
+
break;
|
|
851
|
+
case "remove": {
|
|
852
|
+
agent.inbox.remove(request.itemId);
|
|
853
|
+
const source = message.source;
|
|
854
|
+
if (source.kind === "user" && "rpcId" in source) this.ctx.fileUploads.retirePrompt(agent, source.rpcId);
|
|
855
|
+
break;
|
|
856
|
+
}
|
|
857
|
+
case "steer":
|
|
858
|
+
agent.inbox.remove(request.itemId);
|
|
859
|
+
agent.steer(message);
|
|
860
|
+
break;
|
|
861
|
+
/* v8 ignore next 2 -- closed-union exhaustiveness guard */
|
|
862
|
+
default: assertNever(request.action, "queue action");
|
|
823
863
|
}
|
|
824
864
|
return { accepted: true };
|
|
825
865
|
}
|
|
@@ -880,6 +920,34 @@ var SessionCommandController = class {
|
|
|
880
920
|
}
|
|
881
921
|
}
|
|
882
922
|
};
|
|
923
|
+
function resolvePromptFileReceipts(content, stagedFile) {
|
|
924
|
+
const receiptIds = /* @__PURE__ */ new Set();
|
|
925
|
+
return {
|
|
926
|
+
content: content.map((part) => {
|
|
927
|
+
if (part.type !== "file") return part;
|
|
928
|
+
const attachment = stagedFile(part.receiptId);
|
|
929
|
+
if (attachment === void 0) throw new RemoteError("session/attachment-invalid", "File was not uploaded for this session.", { reason: "FILE_NOT_STAGED" });
|
|
930
|
+
receiptIds.add(part.receiptId);
|
|
931
|
+
return {
|
|
932
|
+
type: "file",
|
|
933
|
+
attachment
|
|
934
|
+
};
|
|
935
|
+
}),
|
|
936
|
+
receiptIds: [...receiptIds]
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
function hasPromptRequest(agent, requestId) {
|
|
940
|
+
const matches = (message) => {
|
|
941
|
+
const source = message.source;
|
|
942
|
+
return source.kind === "user" && "rpcId" in source && source.rpcId === requestId;
|
|
943
|
+
};
|
|
944
|
+
if (agent.inbox.nextTurn.some(matches) || agent.inbox.nextStep.some(matches)) return true;
|
|
945
|
+
return agent.session.snapshotEvents().some((event) => {
|
|
946
|
+
if (event.type !== "user/message") return false;
|
|
947
|
+
const source = event.data.source;
|
|
948
|
+
return source.kind === "user" && "rpcId" in source && source.rpcId === requestId;
|
|
949
|
+
});
|
|
950
|
+
}
|
|
883
951
|
function imageBlockIn(content, match) {
|
|
884
952
|
if (!Array.isArray(content)) return void 0;
|
|
885
953
|
for (const value of content) {
|
|
@@ -905,7 +973,10 @@ function imageInEvent(event, match) {
|
|
|
905
973
|
const found = imageBlockIn(inserted.content, match);
|
|
906
974
|
if (found !== void 0) return found;
|
|
907
975
|
}
|
|
908
|
-
|
|
976
|
+
if (event.type === "assistant/message" || event.type === "assistant/attempt") for (const chunk of assistantStreamChunks(event.data.stream, "block-end")) {
|
|
977
|
+
const found = imageBlockIn([chunk.block], match);
|
|
978
|
+
if (found !== void 0) return found;
|
|
979
|
+
}
|
|
909
980
|
}
|
|
910
981
|
function referencedImage(events, attachmentId) {
|
|
911
982
|
for (const event of events) {
|
|
@@ -926,9 +997,6 @@ var SessionControlController = class {
|
|
|
926
997
|
/** @param ctx - Host context carrying live Agent, projection, and jobs services. */
|
|
927
998
|
constructor(ctx) {
|
|
928
999
|
this.ctx = ctx;
|
|
929
|
-
ctx.on("session/event", (session, event) => {
|
|
930
|
-
this.onSessionEvent(session, event);
|
|
931
|
-
});
|
|
932
1000
|
ctx.sessionProjections.onChanged((session, key, value, seq) => {
|
|
933
1001
|
this.broadcast({
|
|
934
1002
|
type: "projection",
|
|
@@ -937,6 +1005,13 @@ var SessionControlController = class {
|
|
|
937
1005
|
value,
|
|
938
1006
|
seq
|
|
939
1007
|
});
|
|
1008
|
+
if (key !== "inbox") return;
|
|
1009
|
+
if (this.ctx.agents.get(session.id)?.session !== session) return;
|
|
1010
|
+
this.broadcast({
|
|
1011
|
+
type: "queue",
|
|
1012
|
+
sessionId: session.id,
|
|
1013
|
+
items: queueItemsFromInbox(value)
|
|
1014
|
+
});
|
|
940
1015
|
});
|
|
941
1016
|
ctx.inject(["jobs"], (jobsCtx) => {
|
|
942
1017
|
jobsCtx.jobs.onJobsChanged((owner) => {
|
|
@@ -1002,16 +1077,6 @@ var SessionControlController = class {
|
|
|
1002
1077
|
}
|
|
1003
1078
|
return blocks;
|
|
1004
1079
|
}
|
|
1005
|
-
onSessionEvent(session, event) {
|
|
1006
|
-
if (event.type !== "agent/inbox/spliced") return;
|
|
1007
|
-
const agent = this.ctx.agents.get(session.id);
|
|
1008
|
-
if (agent?.session !== session) return;
|
|
1009
|
-
this.broadcast({
|
|
1010
|
-
type: "queue",
|
|
1011
|
-
sessionId: session.id,
|
|
1012
|
-
items: queueItems(agent, event.data)
|
|
1013
|
-
});
|
|
1014
|
-
}
|
|
1015
1080
|
onJobsChanged(owner) {
|
|
1016
1081
|
if (owner !== void 0) {
|
|
1017
1082
|
this.broadcast({
|
|
@@ -1076,12 +1141,14 @@ var ControlQueue = class {
|
|
|
1076
1141
|
}
|
|
1077
1142
|
}
|
|
1078
1143
|
};
|
|
1079
|
-
function queueItems(agent
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
};
|
|
1084
|
-
|
|
1144
|
+
function queueItems(agent) {
|
|
1145
|
+
return queueItemsFromInbox({
|
|
1146
|
+
"next-turn": agent.inbox.nextTurn,
|
|
1147
|
+
"next-step": agent.inbox.nextStep
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
function queueItemsFromInbox(inbox) {
|
|
1151
|
+
return [...inbox["next-turn"].map((message) => ({
|
|
1085
1152
|
id: message.id,
|
|
1086
1153
|
placement: "queued",
|
|
1087
1154
|
...promptRpcId(message),
|
|
@@ -1089,7 +1156,7 @@ function queueItems(agent, splice) {
|
|
|
1089
1156
|
id: message.id,
|
|
1090
1157
|
content: message.content
|
|
1091
1158
|
}
|
|
1092
|
-
})), ...
|
|
1159
|
+
})), ...inbox["next-step"].map((message) => ({
|
|
1093
1160
|
id: message.id,
|
|
1094
1161
|
placement: message.source.kind === "user" ? "steering" : "context",
|
|
1095
1162
|
...promptRpcId(message),
|
|
@@ -1116,9 +1183,90 @@ function jobView(job) {
|
|
|
1116
1183
|
};
|
|
1117
1184
|
}
|
|
1118
1185
|
//#endregion
|
|
1186
|
+
//#region lib/types/assistant-stream.js
|
|
1187
|
+
/** Process-local assistant state retained for reconnecting Web followers. */
|
|
1188
|
+
const EMPTY_BASELINE = { revision: 0 };
|
|
1189
|
+
/**
|
|
1190
|
+
* Folds dense Agent frames and materializes one shared immutable reconnect
|
|
1191
|
+
* baseline per accepted revision.
|
|
1192
|
+
*/
|
|
1193
|
+
var SessionAssistantStreamAccumulator = class {
|
|
1194
|
+
activeAttempt;
|
|
1195
|
+
revision = 0;
|
|
1196
|
+
snapshotValue = EMPTY_BASELINE;
|
|
1197
|
+
dirty = false;
|
|
1198
|
+
/**
|
|
1199
|
+
* Fold one trusted frame from the current attached Agent lifecycle.
|
|
1200
|
+
* @param frame - next dense process-local Assistant frame.
|
|
1201
|
+
* @param durableCursor - last committed Session seq when this frame was observed.
|
|
1202
|
+
*/
|
|
1203
|
+
accept(frame, durableCursor) {
|
|
1204
|
+
if (frame.type === "start" && frame.revision === 1 && this.revision !== 0) {
|
|
1205
|
+
this.activeAttempt = void 0;
|
|
1206
|
+
this.revision = 0;
|
|
1207
|
+
}
|
|
1208
|
+
if (frame.revision !== this.revision + 1) {
|
|
1209
|
+
this.activeAttempt = void 0;
|
|
1210
|
+
this.revision = frame.revision;
|
|
1211
|
+
this.dirty = true;
|
|
1212
|
+
return;
|
|
1213
|
+
}
|
|
1214
|
+
this.revision = frame.revision;
|
|
1215
|
+
switch (frame.type) {
|
|
1216
|
+
case "start":
|
|
1217
|
+
this.activeAttempt = {
|
|
1218
|
+
attemptId: frame.attemptId,
|
|
1219
|
+
startedAfterSeq: durableCursor,
|
|
1220
|
+
turn: frame.turn,
|
|
1221
|
+
step: frame.step,
|
|
1222
|
+
stream: new AssistantStreamAccumulator(),
|
|
1223
|
+
nextIndex: 0
|
|
1224
|
+
};
|
|
1225
|
+
break;
|
|
1226
|
+
case "chunk": {
|
|
1227
|
+
const attempt = this.activeAttempt;
|
|
1228
|
+
if (attempt === void 0 || attempt.attemptId !== frame.attemptId || frame.index !== attempt.nextIndex) {
|
|
1229
|
+
this.activeAttempt = void 0;
|
|
1230
|
+
break;
|
|
1231
|
+
}
|
|
1232
|
+
attempt.stream.push({
|
|
1233
|
+
time: frame.time,
|
|
1234
|
+
chunk: frame.chunk
|
|
1235
|
+
});
|
|
1236
|
+
attempt.nextIndex += 1;
|
|
1237
|
+
break;
|
|
1238
|
+
}
|
|
1239
|
+
case "end":
|
|
1240
|
+
this.activeAttempt = void 0;
|
|
1241
|
+
break;
|
|
1242
|
+
}
|
|
1243
|
+
this.dirty = true;
|
|
1244
|
+
}
|
|
1245
|
+
/**
|
|
1246
|
+
* Read the cached reconnect baseline, materializing it after a state change.
|
|
1247
|
+
* @returns the identity-stable baseline for the latest accepted revision.
|
|
1248
|
+
*/
|
|
1249
|
+
snapshot() {
|
|
1250
|
+
if (!this.dirty) return this.snapshotValue;
|
|
1251
|
+
this.snapshotValue = {
|
|
1252
|
+
revision: this.revision,
|
|
1253
|
+
...this.activeAttempt === void 0 ? {} : { activeAttempt: {
|
|
1254
|
+
attemptId: this.activeAttempt.attemptId,
|
|
1255
|
+
startedAfterSeq: this.activeAttempt.startedAfterSeq,
|
|
1256
|
+
turn: this.activeAttempt.turn,
|
|
1257
|
+
step: this.activeAttempt.step,
|
|
1258
|
+
nextIndex: this.activeAttempt.nextIndex,
|
|
1259
|
+
stream: this.activeAttempt.stream.snapshot()
|
|
1260
|
+
} }
|
|
1261
|
+
};
|
|
1262
|
+
this.dirty = false;
|
|
1263
|
+
return this.snapshotValue;
|
|
1264
|
+
}
|
|
1265
|
+
};
|
|
1266
|
+
//#endregion
|
|
1119
1267
|
//#region lib/types/history.js
|
|
1120
1268
|
/** Cold Session history pagination and live-event source. */
|
|
1121
|
-
var __addDisposableResource$
|
|
1269
|
+
var __addDisposableResource$2 = function(env, value, async) {
|
|
1122
1270
|
if (value !== null && value !== void 0) {
|
|
1123
1271
|
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
1124
1272
|
var dispose, inner;
|
|
@@ -1147,7 +1295,7 @@ var __addDisposableResource$3 = function(env, value, async) {
|
|
|
1147
1295
|
} else if (async) env.stack.push({ async: true });
|
|
1148
1296
|
return value;
|
|
1149
1297
|
};
|
|
1150
|
-
var __disposeResources$
|
|
1298
|
+
var __disposeResources$2 = (function(SuppressedError) {
|
|
1151
1299
|
return function(env) {
|
|
1152
1300
|
function fail(e) {
|
|
1153
1301
|
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
@@ -1183,6 +1331,7 @@ var SessionHistoryController = class {
|
|
|
1183
1331
|
ctx;
|
|
1184
1332
|
promote;
|
|
1185
1333
|
closeFollowers = /* @__PURE__ */ new Set();
|
|
1334
|
+
assistantStreams = /* @__PURE__ */ new Map();
|
|
1186
1335
|
/**
|
|
1187
1336
|
* @param ctx - Host context carrying Session query and projection services.
|
|
1188
1337
|
* @param promote - starts ordinary Session activation after snapshot delivery.
|
|
@@ -1190,6 +1339,17 @@ var SessionHistoryController = class {
|
|
|
1190
1339
|
constructor(ctx, promote) {
|
|
1191
1340
|
this.ctx = ctx;
|
|
1192
1341
|
this.promote = promote;
|
|
1342
|
+
ctx.on("agent/assistant-stream", ({ agent, frame }) => {
|
|
1343
|
+
let stream = this.assistantStreams.get(agent.session.id);
|
|
1344
|
+
if (stream === void 0) {
|
|
1345
|
+
stream = new SessionAssistantStreamAccumulator();
|
|
1346
|
+
this.assistantStreams.set(agent.session.id, stream);
|
|
1347
|
+
}
|
|
1348
|
+
stream.accept(frame, cursorBeforeNext(agent.session.seq));
|
|
1349
|
+
}, { global: true });
|
|
1350
|
+
ctx.on("agent/disposed", ({ agent }) => {
|
|
1351
|
+
this.assistantStreams.delete(agent.session.id);
|
|
1352
|
+
}, { global: true });
|
|
1193
1353
|
ctx.effect(() => () => {
|
|
1194
1354
|
for (const close of this.closeFollowers) close();
|
|
1195
1355
|
this.closeFollowers.clear();
|
|
@@ -1211,7 +1371,7 @@ var SessionHistoryController = class {
|
|
|
1211
1371
|
validatePageRequest(request);
|
|
1212
1372
|
const throughSeq = request.throughSeq === -1 ? -1 : SessionSeq(request.throughSeq);
|
|
1213
1373
|
const beforeSeq = request.beforeSeq === void 0 ? void 0 : SessionLogOffset(request.beforeSeq);
|
|
1214
|
-
const source = __addDisposableResource$
|
|
1374
|
+
const source = __addDisposableResource$2(env_1, await this.sourceFor(request.address, signal, false), false);
|
|
1215
1375
|
signal.throwIfAborted();
|
|
1216
1376
|
const sourceLog = source.events;
|
|
1217
1377
|
const sourceCursor = sourceLog.at(-1)?.seq ?? -1;
|
|
@@ -1227,14 +1387,14 @@ var SessionHistoryController = class {
|
|
|
1227
1387
|
env_1.error = e_1;
|
|
1228
1388
|
env_1.hasError = true;
|
|
1229
1389
|
} finally {
|
|
1230
|
-
__disposeResources$
|
|
1390
|
+
__disposeResources$2(env_1);
|
|
1231
1391
|
}
|
|
1232
1392
|
}
|
|
1233
1393
|
/**
|
|
1234
1394
|
* Follow events appended after an initial cursor on one durable address.
|
|
1235
1395
|
* @param request - durable address and last committed sequence already held by the caller.
|
|
1236
1396
|
* @param signal - stream cancellation owned by the Remote carrier.
|
|
1237
|
-
* @returns a complete opening snapshot followed by gap-free
|
|
1397
|
+
* @returns a complete opening snapshot followed by gap-free durable events and opted-in assistant frames.
|
|
1238
1398
|
*/
|
|
1239
1399
|
async *follow(request, signal) {
|
|
1240
1400
|
validateFollowRequest(request);
|
|
@@ -1242,6 +1402,7 @@ var SessionHistoryController = class {
|
|
|
1242
1402
|
const target = addressId(address);
|
|
1243
1403
|
const buffered = new Deque();
|
|
1244
1404
|
let snapshotCursor;
|
|
1405
|
+
let assistantStreamOrdinal = 0;
|
|
1245
1406
|
let wake;
|
|
1246
1407
|
const notify = () => {
|
|
1247
1408
|
const resume = wake;
|
|
@@ -1256,13 +1417,28 @@ var SessionHistoryController = class {
|
|
|
1256
1417
|
this.closeFollowers.add(close);
|
|
1257
1418
|
const disposeEvent = this.ctx.on("session/event", (session, event) => {
|
|
1258
1419
|
if (session.id !== target) return;
|
|
1259
|
-
buffered.pushBack(
|
|
1420
|
+
buffered.pushBack({
|
|
1421
|
+
type: "event",
|
|
1422
|
+
event
|
|
1423
|
+
});
|
|
1260
1424
|
notify();
|
|
1261
1425
|
}, { global: true });
|
|
1262
1426
|
const disposeCreated = this.ctx.on("session/created", (session) => {
|
|
1263
1427
|
if (session.id !== target) return;
|
|
1264
1428
|
const suffix = session.snapshotEvents(snapshotCursor === void 0 ? session.firstLiveSeq : SessionLogOffset(snapshotCursor + 1));
|
|
1265
|
-
for (let index = suffix.length - 1; index >= 0; index -= 1) buffered.pushFront(
|
|
1429
|
+
for (let index = suffix.length - 1; index >= 0; index -= 1) buffered.pushFront({
|
|
1430
|
+
type: "event",
|
|
1431
|
+
event: suffix[index]
|
|
1432
|
+
});
|
|
1433
|
+
notify();
|
|
1434
|
+
}, { global: true });
|
|
1435
|
+
const disposeAssistantStream = request.assistantStream !== true ? void 0 : this.ctx.on("agent/assistant-stream", ({ agent, frame }) => {
|
|
1436
|
+
if (agent.session.id !== target) return;
|
|
1437
|
+
buffered.pushBack({
|
|
1438
|
+
type: "assistant-stream",
|
|
1439
|
+
frame: wireAssistantStreamFrame(frame, cursorBeforeNext(agent.session.seq)),
|
|
1440
|
+
ordinal: ++assistantStreamOrdinal
|
|
1441
|
+
});
|
|
1266
1442
|
notify();
|
|
1267
1443
|
}, { global: true });
|
|
1268
1444
|
const onAbort = () => {
|
|
@@ -1276,22 +1452,25 @@ var SessionHistoryController = class {
|
|
|
1276
1452
|
hasError: false
|
|
1277
1453
|
};
|
|
1278
1454
|
try {
|
|
1279
|
-
const source = __addDisposableResource$
|
|
1455
|
+
const source = __addDisposableResource$2(env_2, await this.sourceFor(address, signal, true), false);
|
|
1280
1456
|
const events = source.events;
|
|
1281
1457
|
signal.throwIfAborted();
|
|
1282
1458
|
const cursor = source.cursor;
|
|
1283
1459
|
snapshotCursor = cursor;
|
|
1284
1460
|
const page = paginate(events, void 0, request.maxMessages ?? DEFAULT_MAX_MESSAGES);
|
|
1461
|
+
const assistantStream = request.assistantStream === true ? this.assistantStreams.get(target)?.snapshot() ?? { revision: 0 } : void 0;
|
|
1462
|
+
const assistantStreamOrdinalCut = assistantStreamOrdinal;
|
|
1285
1463
|
yield {
|
|
1286
1464
|
type: "snapshot",
|
|
1287
|
-
header: wireHeader(source.header
|
|
1465
|
+
header: wireHeader(source.header),
|
|
1288
1466
|
cursor,
|
|
1289
1467
|
records: pageRecords(page.events),
|
|
1290
1468
|
hasMore: page.hasMore,
|
|
1291
1469
|
projections: source.projections === void 0 ? {
|
|
1292
1470
|
asOfSeq: cursor,
|
|
1293
1471
|
values: {}
|
|
1294
|
-
} : projectionBlock(source.projections)
|
|
1472
|
+
} : projectionBlock(source.projections),
|
|
1473
|
+
...assistantStream === void 0 ? {} : { assistantStream }
|
|
1295
1474
|
};
|
|
1296
1475
|
if (address.kind === "session" && source.source === "prepared") {
|
|
1297
1476
|
const promotion = source.retain();
|
|
@@ -1311,23 +1490,31 @@ var SessionHistoryController = class {
|
|
|
1311
1490
|
});
|
|
1312
1491
|
continue;
|
|
1313
1492
|
}
|
|
1493
|
+
if (item.type === "assistant-stream") {
|
|
1494
|
+
if (item.ordinal > assistantStreamOrdinalCut) yield {
|
|
1495
|
+
type: "assistant-stream",
|
|
1496
|
+
frame: item.frame
|
|
1497
|
+
};
|
|
1498
|
+
continue;
|
|
1499
|
+
}
|
|
1314
1500
|
const expectedSeq = SessionSeq(nextOffset);
|
|
1315
|
-
if (item.seq < expectedSeq) continue;
|
|
1316
|
-
if (item.seq !== expectedSeq) throw new RemoteError("gateway/internal", `session event stream skipped seq ${String(expectedSeq)}`, {});
|
|
1501
|
+
if (item.event.seq < expectedSeq) continue;
|
|
1502
|
+
if (item.event.seq !== expectedSeq) throw new RemoteError("gateway/internal", `session event stream skipped seq ${String(expectedSeq)}`, {});
|
|
1317
1503
|
nextOffset = SessionLogOffset(nextOffset + 1);
|
|
1318
|
-
yield entryFor(item);
|
|
1504
|
+
yield entryFor(item.event);
|
|
1319
1505
|
}
|
|
1320
1506
|
} catch (e_2) {
|
|
1321
1507
|
env_2.error = e_2;
|
|
1322
1508
|
env_2.hasError = true;
|
|
1323
1509
|
} finally {
|
|
1324
|
-
__disposeResources$
|
|
1510
|
+
__disposeResources$2(env_2);
|
|
1325
1511
|
}
|
|
1326
1512
|
} finally {
|
|
1327
1513
|
this.closeFollowers.delete(close);
|
|
1328
1514
|
signal.removeEventListener("abort", onAbort);
|
|
1329
1515
|
disposeCreated();
|
|
1330
1516
|
disposeEvent();
|
|
1517
|
+
disposeAssistantStream?.();
|
|
1331
1518
|
}
|
|
1332
1519
|
}
|
|
1333
1520
|
async sourceFor(address, signal, withProjections) {
|
|
@@ -1354,6 +1541,20 @@ var SessionHistoryController = class {
|
|
|
1354
1541
|
}
|
|
1355
1542
|
}
|
|
1356
1543
|
};
|
|
1544
|
+
function cursorBeforeNext(nextSeq) {
|
|
1545
|
+
return nextSeq === 0 ? -1 : SessionSeq(nextSeq - 1);
|
|
1546
|
+
}
|
|
1547
|
+
function wireAssistantStreamFrame(frame, durableCursor) {
|
|
1548
|
+
if (frame.type === "start") return {
|
|
1549
|
+
...frame,
|
|
1550
|
+
startedAfterSeq: durableCursor
|
|
1551
|
+
};
|
|
1552
|
+
if (frame.type === "end") return frame;
|
|
1553
|
+
return {
|
|
1554
|
+
...frame,
|
|
1555
|
+
chunk: frame.chunk
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1357
1558
|
function projectionBlock(snapshot) {
|
|
1358
1559
|
return {
|
|
1359
1560
|
asOfSeq: snapshot.asOfSeq,
|
|
@@ -1420,13 +1621,9 @@ function paginate(events, beforeSeq, maxMessages, throughSeq = events.at(-1)?.se
|
|
|
1420
1621
|
hasMore: cut > 0
|
|
1421
1622
|
};
|
|
1422
1623
|
}
|
|
1423
|
-
/** Translate logical Session metadata to the
|
|
1424
|
-
function wireHeader(header
|
|
1425
|
-
|
|
1426
|
-
return {
|
|
1427
|
-
...wire,
|
|
1428
|
-
...isSeeded ? { seedLength: inheritedEventCount } : {}
|
|
1429
|
-
};
|
|
1624
|
+
/** Translate current logical Session metadata to the browser wire. */
|
|
1625
|
+
function wireHeader(header) {
|
|
1626
|
+
return { ...header };
|
|
1430
1627
|
}
|
|
1431
1628
|
function entryFor(event) {
|
|
1432
1629
|
return {
|
|
@@ -1434,40 +1631,9 @@ function entryFor(event) {
|
|
|
1434
1631
|
event
|
|
1435
1632
|
};
|
|
1436
1633
|
}
|
|
1437
|
-
function chunkEntryFor(row) {
|
|
1438
|
-
switch (row.type) {
|
|
1439
|
-
case "text-chunks": return {
|
|
1440
|
-
type: "chunks",
|
|
1441
|
-
event: {
|
|
1442
|
-
type: "chunkrow/text-chunks",
|
|
1443
|
-
seq: row.seq0,
|
|
1444
|
-
time: row.time0,
|
|
1445
|
-
data: row.data
|
|
1446
|
-
}
|
|
1447
|
-
};
|
|
1448
|
-
case "reasoning-chunks": return {
|
|
1449
|
-
type: "chunks",
|
|
1450
|
-
event: {
|
|
1451
|
-
type: "chunkrow/reasoning-chunks",
|
|
1452
|
-
seq: row.seq0,
|
|
1453
|
-
time: row.time0,
|
|
1454
|
-
data: row.data
|
|
1455
|
-
}
|
|
1456
|
-
};
|
|
1457
|
-
case "tool-call-chunks": return {
|
|
1458
|
-
type: "chunks",
|
|
1459
|
-
event: {
|
|
1460
|
-
type: "chunkrow/tool-call-chunks",
|
|
1461
|
-
seq: row.seq0,
|
|
1462
|
-
time: row.time0,
|
|
1463
|
-
data: row.data
|
|
1464
|
-
}
|
|
1465
|
-
};
|
|
1466
|
-
}
|
|
1467
|
-
}
|
|
1468
1634
|
/** Encode one bounded logical page without changing its pagination cut. */
|
|
1469
1635
|
function pageRecords(events) {
|
|
1470
|
-
return
|
|
1636
|
+
return events.map(entryFor);
|
|
1471
1637
|
}
|
|
1472
1638
|
//#endregion
|
|
1473
1639
|
//#region lib/types/file-references.js
|
|
@@ -1558,67 +1724,6 @@ let SessionFileReferences = (() => {
|
|
|
1558
1724
|
//#endregion
|
|
1559
1725
|
//#region lib/types/list.js
|
|
1560
1726
|
/** Cold-safe Session list and search projection. */
|
|
1561
|
-
var __addDisposableResource$2 = function(env, value, async) {
|
|
1562
|
-
if (value !== null && value !== void 0) {
|
|
1563
|
-
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
1564
|
-
var dispose, inner;
|
|
1565
|
-
if (async) {
|
|
1566
|
-
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
1567
|
-
dispose = value[Symbol.asyncDispose];
|
|
1568
|
-
}
|
|
1569
|
-
if (dispose === void 0) {
|
|
1570
|
-
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
1571
|
-
dispose = value[Symbol.dispose];
|
|
1572
|
-
if (async) inner = dispose;
|
|
1573
|
-
}
|
|
1574
|
-
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
1575
|
-
if (inner) dispose = function() {
|
|
1576
|
-
try {
|
|
1577
|
-
inner.call(this);
|
|
1578
|
-
} catch (e) {
|
|
1579
|
-
return Promise.reject(e);
|
|
1580
|
-
}
|
|
1581
|
-
};
|
|
1582
|
-
env.stack.push({
|
|
1583
|
-
value,
|
|
1584
|
-
dispose,
|
|
1585
|
-
async
|
|
1586
|
-
});
|
|
1587
|
-
} else if (async) env.stack.push({ async: true });
|
|
1588
|
-
return value;
|
|
1589
|
-
};
|
|
1590
|
-
var __disposeResources$2 = (function(SuppressedError) {
|
|
1591
|
-
return function(env) {
|
|
1592
|
-
function fail(e) {
|
|
1593
|
-
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
1594
|
-
env.hasError = true;
|
|
1595
|
-
}
|
|
1596
|
-
var r, s = 0;
|
|
1597
|
-
function next() {
|
|
1598
|
-
while (r = env.stack.pop()) try {
|
|
1599
|
-
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
1600
|
-
if (r.dispose) {
|
|
1601
|
-
var result = r.dispose.call(r.value);
|
|
1602
|
-
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) {
|
|
1603
|
-
fail(e);
|
|
1604
|
-
return next();
|
|
1605
|
-
});
|
|
1606
|
-
} else s |= 1;
|
|
1607
|
-
} catch (e) {
|
|
1608
|
-
fail(e);
|
|
1609
|
-
}
|
|
1610
|
-
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
1611
|
-
if (env.hasError) throw env.error;
|
|
1612
|
-
}
|
|
1613
|
-
return next();
|
|
1614
|
-
};
|
|
1615
|
-
})(typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) {
|
|
1616
|
-
var e = new Error(message);
|
|
1617
|
-
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
1618
|
-
});
|
|
1619
|
-
/** Default maximum artifact size eligible for one cold projection observation. */
|
|
1620
|
-
const DEFAULT_COLD_BLANK_PROBE_MAX_BYTES = 1024;
|
|
1621
|
-
const COLD_SUMMARY_BATCH_SIZE = 16;
|
|
1622
1727
|
const SEARCH_PROVIDER_CALL_LIMIT = 100;
|
|
1623
1728
|
const SESSION_SEARCH_QUERY_MAX_CHARS = 500;
|
|
1624
1729
|
const MESSAGE_TYPES = new Set(["user/message", "assistant/message"]);
|
|
@@ -1667,14 +1772,9 @@ function truncateUnicodeCodePoints(value, maximum) {
|
|
|
1667
1772
|
/** Owns list projection registration, bounded cold summaries, and authorized search. */
|
|
1668
1773
|
var ApiSessionList = class {
|
|
1669
1774
|
ctx;
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
* @param ctx - Host context carrying Session, query, persistence, and projection services.
|
|
1673
|
-
* @param coldBlankProbeMaxBytes - maximum physical artifact size eligible for a full observation.
|
|
1674
|
-
*/
|
|
1675
|
-
constructor(ctx, coldBlankProbeMaxBytes) {
|
|
1775
|
+
/** @param ctx - Host context carrying Session, query, persistence, and projection services. */
|
|
1776
|
+
constructor(ctx) {
|
|
1676
1777
|
this.ctx = ctx;
|
|
1677
|
-
this.coldBlankProbeMaxBytes = coldBlankProbeMaxBytes;
|
|
1678
1778
|
ctx.sessionProjections.register({
|
|
1679
1779
|
key: "sessionListMetadata",
|
|
1680
1780
|
stateSchema: sessionListMetadataSchema,
|
|
@@ -1740,21 +1840,12 @@ var ApiSessionList = class {
|
|
|
1740
1840
|
if (record.header.cwd === void 0) continue;
|
|
1741
1841
|
cold.push(record.header);
|
|
1742
1842
|
}
|
|
1743
|
-
for (
|
|
1744
|
-
const settled = await Promise.allSettled(cold.slice(offset, offset + COLD_SUMMARY_BATCH_SIZE).map((header) => this.summarizeCold(header, signal)));
|
|
1745
|
-
for (const result of settled) {
|
|
1746
|
-
if (result.status === "rejected") throw result.reason;
|
|
1747
|
-
items.push(result.value);
|
|
1748
|
-
}
|
|
1749
|
-
}
|
|
1843
|
+
for (const header of cold) items.push(this.summarizeCold(header));
|
|
1750
1844
|
items.sort((left, right) => right.updatedAt - left.updatedAt);
|
|
1751
1845
|
return items;
|
|
1752
1846
|
}
|
|
1753
|
-
|
|
1754
|
-
const
|
|
1755
|
-
const projections = cached?.values.sessionListMetadata?.blank === false ? cached : await this.probeSmallCold(header, signal) ?? cached;
|
|
1756
|
-
const raced = this.ctx.sessions.get(header.id);
|
|
1757
|
-
if (raced !== void 0) return this.summaryFor(raced);
|
|
1847
|
+
summarizeCold(header) {
|
|
1848
|
+
const projections = this.projectionsFor(header, void 0);
|
|
1758
1849
|
const metadata = projections?.values.sessionListMetadata;
|
|
1759
1850
|
return {
|
|
1760
1851
|
sessionId: header.id,
|
|
@@ -1765,44 +1856,6 @@ var ApiSessionList = class {
|
|
|
1765
1856
|
...projections === void 0 ? {} : { projections }
|
|
1766
1857
|
};
|
|
1767
1858
|
}
|
|
1768
|
-
async probeSmallCold(header, signal) {
|
|
1769
|
-
if (this.coldBlankProbeMaxBytes === 0) return void 0;
|
|
1770
|
-
const location = this.ctx.get("sessionPersistence")?.locate(header);
|
|
1771
|
-
if (location === void 0) return void 0;
|
|
1772
|
-
signal?.throwIfAborted();
|
|
1773
|
-
try {
|
|
1774
|
-
if ((await stat(location.path)).size > this.coldBlankProbeMaxBytes) return void 0;
|
|
1775
|
-
} catch {
|
|
1776
|
-
signal?.throwIfAborted();
|
|
1777
|
-
return;
|
|
1778
|
-
}
|
|
1779
|
-
try {
|
|
1780
|
-
const env_1 = {
|
|
1781
|
-
stack: [],
|
|
1782
|
-
error: void 0,
|
|
1783
|
-
hasError: false
|
|
1784
|
-
};
|
|
1785
|
-
try {
|
|
1786
|
-
const block = __addDisposableResource$2(env_1, await this.ctx.sessionQuery.observeSession(header.id, {
|
|
1787
|
-
...signal === void 0 ? {} : { signal },
|
|
1788
|
-
projectionMode: "all"
|
|
1789
|
-
}), false).projections;
|
|
1790
|
-
return block === void 0 ? void 0 : {
|
|
1791
|
-
asOfSeq: block.asOfSeq,
|
|
1792
|
-
values: block.values
|
|
1793
|
-
};
|
|
1794
|
-
} catch (e_1) {
|
|
1795
|
-
env_1.error = e_1;
|
|
1796
|
-
env_1.hasError = true;
|
|
1797
|
-
} finally {
|
|
1798
|
-
__disposeResources$2(env_1);
|
|
1799
|
-
}
|
|
1800
|
-
} catch (error) {
|
|
1801
|
-
signal?.throwIfAborted();
|
|
1802
|
-
this.ctx.logger.warn(`api-session.list: small cold observation for "${header.id}" failed; serving it as visible: ${String(error)}`);
|
|
1803
|
-
return;
|
|
1804
|
-
}
|
|
1805
|
-
}
|
|
1806
1859
|
/**
|
|
1807
1860
|
* Search current visible message content without activating any matching Session.
|
|
1808
1861
|
* @param query - literal message-content query.
|
|
@@ -1893,7 +1946,8 @@ var ApiSessionList = class {
|
|
|
1893
1946
|
}
|
|
1894
1947
|
projectionsFor(header, session) {
|
|
1895
1948
|
try {
|
|
1896
|
-
const
|
|
1949
|
+
const cache = this.ctx.get("sessionProjectionCache");
|
|
1950
|
+
const block = session === void 0 ? header.isSeeded ? void 0 : cache?.cachedSnapshot(header, SessionLogOffset(0)) ?? cache?.cachedPredecessorTitle(header, SessionLogOffset(0)) : this.ctx.sessionProjections.cachedSnapshot(session);
|
|
1897
1951
|
return block !== void 0 && Object.keys(block.values).length > 0 ? {
|
|
1898
1952
|
asOfSeq: block.asOfSeq,
|
|
1899
1953
|
values: block.values
|
|
@@ -2247,6 +2301,79 @@ let SessionSkillCatalog = (() => {
|
|
|
2247
2301
|
};
|
|
2248
2302
|
})();
|
|
2249
2303
|
//#endregion
|
|
2304
|
+
//#region lib/types/media-references.js
|
|
2305
|
+
/**
|
|
2306
|
+
* Authenticated GET/HEAD /api/file reads bounded file responses through
|
|
2307
|
+
* the composed filesystem provider. Paths and MIME types do not restrict access;
|
|
2308
|
+
* the connection service authenticates requests before this handler.
|
|
2309
|
+
* @module @deepseek-ai/dsh-api-session-controller/media-references
|
|
2310
|
+
*/
|
|
2311
|
+
const BASE_HEADERS = {
|
|
2312
|
+
"Cache-Control": "private, no-store",
|
|
2313
|
+
"X-Content-Type-Options": "nosniff",
|
|
2314
|
+
"Content-Security-Policy": "sandbox; default-src 'none'"
|
|
2315
|
+
};
|
|
2316
|
+
async function serveFile(request, fs, maxBytes) {
|
|
2317
|
+
const fail = (status, text) => new Response(request.method === "HEAD" ? null : text, {
|
|
2318
|
+
status,
|
|
2319
|
+
headers: BASE_HEADERS
|
|
2320
|
+
});
|
|
2321
|
+
const path = new URL(request.url).searchParams.get("path");
|
|
2322
|
+
if (path === null || path.length === 0) return fail(400, "missing path");
|
|
2323
|
+
if (path.includes("\0") || !isAbsolute(path)) return fail(400, "absolute path required");
|
|
2324
|
+
try {
|
|
2325
|
+
const target = await fs.resolve(path, { signal: request.signal });
|
|
2326
|
+
const mediaType = mime.lookup(target.displayPath) || "application/octet-stream";
|
|
2327
|
+
const headers = {
|
|
2328
|
+
...BASE_HEADERS,
|
|
2329
|
+
"Content-Type": mediaType
|
|
2330
|
+
};
|
|
2331
|
+
if (request.method === "HEAD") {
|
|
2332
|
+
const info = await fs.stat(target, request.signal);
|
|
2333
|
+
if (info === void 0) return fail(404, "not found");
|
|
2334
|
+
if (info.type !== "file") return fail(403, "not a regular file");
|
|
2335
|
+
if (info.size !== void 0) {
|
|
2336
|
+
if (info.size > maxBytes) return fail(413, "file exceeds byte limit");
|
|
2337
|
+
headers["Content-Length"] = String(info.size);
|
|
2338
|
+
}
|
|
2339
|
+
return new Response(null, { headers });
|
|
2340
|
+
}
|
|
2341
|
+
const bytes = await fs.readBytes(target, request.signal, maxBytes);
|
|
2342
|
+
headers["Content-Length"] = String(bytes.byteLength);
|
|
2343
|
+
return new Response(bytes.slice(), { headers });
|
|
2344
|
+
} catch (error) {
|
|
2345
|
+
if (!(error instanceof FsError)) throw error;
|
|
2346
|
+
return fail({
|
|
2347
|
+
FS_NOT_FOUND: 404,
|
|
2348
|
+
FS_NOT_REGULAR_FILE: 403,
|
|
2349
|
+
FS_PERMISSION_DENIED: 403,
|
|
2350
|
+
FS_SANDBOX_DENIED: 403,
|
|
2351
|
+
FS_TOO_LARGE: 413,
|
|
2352
|
+
FS_ABORTED: 499
|
|
2353
|
+
}[error.code] ?? 500, error.code);
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
/**
|
|
2357
|
+
* File-display contribution. The connection service supplies authentication;
|
|
2358
|
+
* `ctx.fs` supplies the execution world's paths, reads, and access policy.
|
|
2359
|
+
*/
|
|
2360
|
+
const SessionMediaReferences = {
|
|
2361
|
+
inject: [
|
|
2362
|
+
"connection",
|
|
2363
|
+
"fs",
|
|
2364
|
+
"attachments"
|
|
2365
|
+
],
|
|
2366
|
+
apply(ctx) {
|
|
2367
|
+
const maxBytes = ctx.attachments.imageLimits.maxImageBytes;
|
|
2368
|
+
ctx.effect(() => ctx.connection.fetch.register({
|
|
2369
|
+
path: "/api/file",
|
|
2370
|
+
methods: ["GET", "HEAD"],
|
|
2371
|
+
requestBody: "buffered",
|
|
2372
|
+
fetch: (request) => serveFile(request, ctx.fs, maxBytes)
|
|
2373
|
+
}), "session-controller: /api/file");
|
|
2374
|
+
}
|
|
2375
|
+
};
|
|
2376
|
+
//#endregion
|
|
2250
2377
|
//#region lib/types/index.js
|
|
2251
2378
|
/** Session Remote owner: cold reads, explicit Agent commands, and live control state. */
|
|
2252
2379
|
var __runInitializers = function(thisArg, initializers, value) {
|
|
@@ -2571,6 +2698,7 @@ let SessionController = (() => {
|
|
|
2571
2698
|
"agentDefaultModel",
|
|
2572
2699
|
"agents",
|
|
2573
2700
|
"attachments",
|
|
2701
|
+
"fileUploads",
|
|
2574
2702
|
"llm",
|
|
2575
2703
|
"sessions",
|
|
2576
2704
|
"sessionProjections",
|
|
@@ -2578,10 +2706,7 @@ let SessionController = (() => {
|
|
|
2578
2706
|
"typert",
|
|
2579
2707
|
"workspaceRegistry"
|
|
2580
2708
|
];
|
|
2581
|
-
static Config = z.object({
|
|
2582
|
-
coldBlankProbeMaxBytes: z.natural().default(DEFAULT_COLD_BLANK_PROBE_MAX_BYTES),
|
|
2583
|
-
nativeOpen: z.boolean()
|
|
2584
|
-
});
|
|
2709
|
+
static Config = z.object({ nativeOpen: z.boolean() });
|
|
2585
2710
|
agents = __runInitializers(this, _instanceExtraInitializers);
|
|
2586
2711
|
commands;
|
|
2587
2712
|
controlState;
|
|
@@ -2592,13 +2717,19 @@ let SessionController = (() => {
|
|
|
2592
2717
|
promotions = /* @__PURE__ */ new Set();
|
|
2593
2718
|
/**
|
|
2594
2719
|
* @param ctx - Host context containing the Session capability assembly.
|
|
2595
|
-
* @param config -
|
|
2720
|
+
* @param config - native-opener deployment policy.
|
|
2721
|
+
* @param internals - host integrations replaceable by direct unit tests.
|
|
2596
2722
|
*/
|
|
2597
2723
|
constructor(ctx, config, internals = {}) {
|
|
2598
2724
|
super(ctx, "sessionController", { namespace: "session" });
|
|
2599
2725
|
installModelSelectionProjection(ctx);
|
|
2600
2726
|
this.agents = new ApiSessionAgentController(ctx);
|
|
2601
2727
|
this.commands = new SessionCommandController(ctx, this.agents, process.cwd());
|
|
2728
|
+
ctx.effect(() => ctx.fileUploads.registerAgentResolver(async (sessionId) => {
|
|
2729
|
+
const result = await this.agents.resolveAgent(sessionId);
|
|
2730
|
+
if ("error" in result) throw result.error;
|
|
2731
|
+
return result.agent;
|
|
2732
|
+
}), "session-controller: file-upload Agent resolver");
|
|
2602
2733
|
this.controlState = new SessionControlController(ctx);
|
|
2603
2734
|
ctx.effect(() => async () => {
|
|
2604
2735
|
await Promise.allSettled([...this.promotions]);
|
|
@@ -2606,10 +2737,11 @@ let SessionController = (() => {
|
|
|
2606
2737
|
this.history = new SessionHistoryController(ctx, (observation) => {
|
|
2607
2738
|
this.promote(observation);
|
|
2608
2739
|
});
|
|
2609
|
-
this.listState = new ApiSessionList(ctx
|
|
2740
|
+
this.listState = new ApiSessionList(ctx);
|
|
2610
2741
|
this.openPath = internals.openPath ?? openNativePath;
|
|
2611
2742
|
this.canOpenPath = internals.canOpenPath ?? (() => config.nativeOpen ?? (internals.openPath !== void 0 || canOpenNativePath()));
|
|
2612
2743
|
ctx.plugin(SessionFileReferences);
|
|
2744
|
+
ctx.plugin(SessionMediaReferences);
|
|
2613
2745
|
ctx.plugin(SessionSkillCatalog);
|
|
2614
2746
|
ctx.on("session/created", (session) => {
|
|
2615
2747
|
ctx.emit("api-session/added", this.listState.summaryFor(session));
|
|
@@ -2810,7 +2942,8 @@ let SessionController = (() => {
|
|
|
2810
2942
|
* Follow one Session log from its opening or resume cursor.
|
|
2811
2943
|
* @param request - durable address and last committed sequence already held by the caller.
|
|
2812
2944
|
* @param signal - cancellation owned by the Remote stream carrier.
|
|
2813
|
-
* @returns a complete opening snapshot followed by gap-free event
|
|
2945
|
+
* @returns a complete opening snapshot followed by gap-free durable event
|
|
2946
|
+
* frames and optional cursorless assistant-stream frames.
|
|
2814
2947
|
*/
|
|
2815
2948
|
follow(request, signal) {
|
|
2816
2949
|
return this.history.follow(request, signal);
|