@deepseek-ai/dsh-api-session-controller 0.1.2-alpha.5 → 0.1.3-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +6 -6
- package/README.zh.md +6 -6
- package/lib/client.js +592 -28
- package/lib/index.js +269 -214
- package/lib/typert.host.js +183 -141
- package/lib/typert.remote-client.js +79 -97
- package/lib/types/assistant-stream.d.ts +26 -0
- package/lib/types/assistant-stream.js +83 -0
- package/lib/types/client/contract/events.d.ts +38 -7
- package/lib/types/client/contract/events.js +21 -0
- package/lib/types/client/contract/session.d.ts +7 -7
- package/lib/types/client/contract/snapshot.d.ts +15 -2
- package/lib/types/client/index.d.ts +2 -2
- package/lib/types/client/index.js +2 -0
- package/lib/types/client/sessions/assistant-stream.d.ts +51 -0
- package/lib/types/client/sessions/assistant-stream.js +168 -0
- package/lib/types/client/sessions/history-records.d.ts +2 -2
- package/lib/types/client/sessions/history-records.js +3 -8
- package/lib/types/client/sessions/queue-mirror.js +3 -3
- package/lib/types/client/sessions/remotes.d.ts +2 -2
- package/lib/types/client/sessions/session.d.ts +3 -1
- package/lib/types/client/sessions/session.js +63 -15
- package/lib/types/client/transport.d.ts +7 -3
- package/lib/types/client/transport.js +18 -3
- package/lib/types/commands.js +98 -28
- package/lib/types/history.d.ts +2 -1
- package/lib/types/history.js +66 -37
- package/lib/types/index.d.ts +4 -4
- package/lib/types/index.js +13 -5
- package/lib/types/list.d.ts +2 -9
- package/lib/types/list.js +10 -124
- package/lib/types/types.d.ts +71 -29
- package/package.json +63 -57
package/lib/types/commands.js
CHANGED
|
@@ -53,12 +53,13 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
53
53
|
});
|
|
54
54
|
import { randomUUID } from 'node:crypto';
|
|
55
55
|
import { brandString } from '@deepseek-ai/dsh-brand';
|
|
56
|
-
import { AttachmentError
|
|
57
|
-
import { ReasoningEffortId, createUserMessage, freezeMessage, } from '@deepseek-ai/dsh-llm';
|
|
56
|
+
import { AttachmentError } from '@deepseek-ai/dsh-attachment';
|
|
57
|
+
import { ReasoningEffortId, assistantStreamChunks, createUserMessage, freezeMessage, } from '@deepseek-ai/dsh-llm';
|
|
58
58
|
import { SessionLogOffset, SessionSeq } from '@deepseek-ai/dsh-session';
|
|
59
59
|
import { SessionQueryError } from '@deepseek-ai/dsh-session-query';
|
|
60
60
|
import { SessionTitleInvalidError } from '@deepseek-ai/dsh-session-title';
|
|
61
61
|
import { canonicalClientTimeZone } from '@deepseek-ai/dsh-util-time';
|
|
62
|
+
import { assertNever } from '@deepseek-ai/dsh-util-values';
|
|
62
63
|
import { RemoteError, remoteErrorOf } from '@deepseek-ai/dsh-typert-protocol';
|
|
63
64
|
import { ApiSessionCwdConflict, ApiSessionNotFound, ApiSessionPresetConflict, ApiSessionSubagentOwnership, apiSessionSubagentOwnershipError, hasApiSessionSubagentOwner, inspectApiSession, } from "./agent.js";
|
|
64
65
|
/** Implements Session business commands delegated by the Session Controller Remote service. */
|
|
@@ -282,6 +283,8 @@ export class SessionCommandController {
|
|
|
282
283
|
throw new RemoteError('session/invalid-time-zone', 'clientTimeZone must be UTC or a valid IANA Area/Location name', { value: request.clientTimeZone });
|
|
283
284
|
}
|
|
284
285
|
const agent = await this.resolveAgent(request.sessionId);
|
|
286
|
+
if (hasPromptRequest(agent, request.requestId))
|
|
287
|
+
return { accepted: true };
|
|
285
288
|
const selection = this.agents.selectionFor(agent).current;
|
|
286
289
|
if (!routeServed(this.ctx, selection.provider)) {
|
|
287
290
|
throw new RemoteError('session/model-unavailable', `no adapter serves provider "${selection.provider}"; select a model for this session`, { provider: selection.provider, model: selection.model });
|
|
@@ -294,19 +297,35 @@ export class SessionCommandController {
|
|
|
294
297
|
const hasImage = request.content.some(part => part.type === 'image');
|
|
295
298
|
const admit = async () => {
|
|
296
299
|
try {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
300
|
+
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
301
|
+
try {
|
|
302
|
+
if (hasImage) {
|
|
303
|
+
const current = this.agents.selectionFor(agent).current;
|
|
304
|
+
const model = await this.ctx.llm.resolveModelInfo(current.provider, current.model);
|
|
305
|
+
if (model.inputModalities !== undefined && !model.inputModalities.includes('image')) {
|
|
306
|
+
throw new RemoteError('session/attachment-invalid', `Model "${current.model}" does not support image input.`, { reason: 'MODEL_DOES_NOT_SUPPORT_IMAGES' });
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const admission = resolvePromptFileReceipts(request.content, receiptId => this.ctx.fileUploads.resolve(agent, receiptId));
|
|
310
|
+
const content = await this.ctx.attachments.admitPromptContent(admission.content);
|
|
311
|
+
const message = createUserMessage({ content, source });
|
|
312
|
+
if (this.ctx.agents.get(agent.id) !== agent) {
|
|
313
|
+
throw new RemoteError('session/not-found', `session "${agent.id}" was disposed during prompt admission`, { sessionId: agent.id });
|
|
302
314
|
}
|
|
315
|
+
const binding = __addDisposableResource(env_2, this.ctx.fileUploads.bindPrompt(agent, admission.receiptIds, request.requestId), false);
|
|
316
|
+
if (request.mode === 'steer')
|
|
317
|
+
agent.steer(message);
|
|
318
|
+
else
|
|
319
|
+
agent.followup(message);
|
|
320
|
+
binding.commit();
|
|
321
|
+
}
|
|
322
|
+
catch (e_2) {
|
|
323
|
+
env_2.error = e_2;
|
|
324
|
+
env_2.hasError = true;
|
|
325
|
+
}
|
|
326
|
+
finally {
|
|
327
|
+
__disposeResources(env_2);
|
|
303
328
|
}
|
|
304
|
-
const content = await admitPromptContent(this.ctx.attachments, request.content);
|
|
305
|
-
const message = createUserMessage({ content, source });
|
|
306
|
-
if (request.mode === 'steer')
|
|
307
|
-
agent.steer(message);
|
|
308
|
-
else
|
|
309
|
-
agent.followup(message);
|
|
310
329
|
}
|
|
311
330
|
catch (error) {
|
|
312
331
|
if (remoteErrorOf(error) !== undefined)
|
|
@@ -365,12 +384,18 @@ export class SessionCommandController {
|
|
|
365
384
|
throw new RemoteError('session/attachment-invalid', 'queue edits accept text content only', { reason: 'QUEUE_EDIT_NON_TEXT' });
|
|
366
385
|
}
|
|
367
386
|
const agent = this.ctx.agents.get(request.sessionId);
|
|
368
|
-
if (agent !== undefined && hasApiSessionSubagentOwner(this.ctx, agent.session, agent)) {
|
|
369
|
-
throw apiSessionSubagentOwnershipError(request.sessionId);
|
|
370
|
-
}
|
|
371
387
|
if (agent === undefined) {
|
|
372
388
|
throw new RemoteError('session/queue-item-not-found', 'queued item is no longer pending', { itemId: request.itemId });
|
|
373
389
|
}
|
|
390
|
+
if (hasApiSessionSubagentOwner(this.ctx, agent.session, agent)) {
|
|
391
|
+
const identity = this.ctx.sessionProjections
|
|
392
|
+
.snapshot(agent.session, ['subagent'])
|
|
393
|
+
.values.subagent;
|
|
394
|
+
if (identity?.mode !== 'continuable'
|
|
395
|
+
|| !agent.session.isOwnSeq(identity.seq)) {
|
|
396
|
+
throw apiSessionSubagentOwnershipError(request.sessionId);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
374
399
|
const nextTurn = agent.inbox.nextTurn.find(message => message.id === request.itemId);
|
|
375
400
|
const nextStep = agent.inbox.nextStep.find(message => message.id === request.itemId);
|
|
376
401
|
const located = nextTurn === undefined
|
|
@@ -383,16 +408,28 @@ export class SessionCommandController {
|
|
|
383
408
|
if (request.action.kind === 'steer' && (target !== 'next-turn' || agent.status !== 'running')) {
|
|
384
409
|
throw new RemoteError('session/steer-unavailable', 'current turn no longer accepts steering', { itemId: request.itemId });
|
|
385
410
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
411
|
+
switch (request.action.kind) {
|
|
412
|
+
case 'edit':
|
|
413
|
+
agent.inbox.replace(request.itemId, freezeMessage({
|
|
414
|
+
...message,
|
|
415
|
+
content: [...request.action.content],
|
|
416
|
+
}));
|
|
417
|
+
break;
|
|
418
|
+
case 'remove': {
|
|
419
|
+
agent.inbox.remove(request.itemId);
|
|
420
|
+
const source = message.source;
|
|
421
|
+
if (source.kind === 'user' && 'rpcId' in source) {
|
|
422
|
+
this.ctx.fileUploads.retirePrompt(agent, source.rpcId);
|
|
423
|
+
}
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
case 'steer':
|
|
427
|
+
agent.inbox.remove(request.itemId);
|
|
395
428
|
agent.steer(message);
|
|
429
|
+
break;
|
|
430
|
+
/* v8 ignore next 2 -- closed-union exhaustiveness guard */
|
|
431
|
+
default:
|
|
432
|
+
assertNever(request.action, 'queue action');
|
|
396
433
|
}
|
|
397
434
|
return { accepted: true };
|
|
398
435
|
}
|
|
@@ -462,6 +499,34 @@ export class SessionCommandController {
|
|
|
462
499
|
return undefined;
|
|
463
500
|
}
|
|
464
501
|
}
|
|
502
|
+
function resolvePromptFileReceipts(content, stagedFile) {
|
|
503
|
+
const receiptIds = new Set();
|
|
504
|
+
const resolved = content.map((part) => {
|
|
505
|
+
if (part.type !== 'file')
|
|
506
|
+
return part;
|
|
507
|
+
const attachment = stagedFile(part.receiptId);
|
|
508
|
+
if (attachment === undefined) {
|
|
509
|
+
throw new RemoteError('session/attachment-invalid', 'File was not uploaded for this session.', { reason: 'FILE_NOT_STAGED' });
|
|
510
|
+
}
|
|
511
|
+
receiptIds.add(part.receiptId);
|
|
512
|
+
return { type: 'file', attachment };
|
|
513
|
+
});
|
|
514
|
+
return { content: resolved, receiptIds: [...receiptIds] };
|
|
515
|
+
}
|
|
516
|
+
function hasPromptRequest(agent, requestId) {
|
|
517
|
+
const matches = (message) => {
|
|
518
|
+
const source = message.source;
|
|
519
|
+
return source.kind === 'user' && 'rpcId' in source && source.rpcId === requestId;
|
|
520
|
+
};
|
|
521
|
+
if (agent.inbox.nextTurn.some(matches) || agent.inbox.nextStep.some(matches))
|
|
522
|
+
return true;
|
|
523
|
+
return agent.session.snapshotEvents().some((event) => {
|
|
524
|
+
if (event.type !== 'user/message')
|
|
525
|
+
return false;
|
|
526
|
+
const source = event.data.source;
|
|
527
|
+
return source.kind === 'user' && 'rpcId' in source && source.rpcId === requestId;
|
|
528
|
+
});
|
|
529
|
+
}
|
|
465
530
|
function imageBlockIn(content, match) {
|
|
466
531
|
if (!Array.isArray(content))
|
|
467
532
|
return undefined;
|
|
@@ -495,9 +560,14 @@ function imageInEvent(event, match) {
|
|
|
495
560
|
if (found !== undefined)
|
|
496
561
|
return found;
|
|
497
562
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
563
|
+
if (event.type === 'assistant/message' || event.type === 'assistant/attempt') {
|
|
564
|
+
for (const chunk of assistantStreamChunks(event.data.stream, 'block-end')) {
|
|
565
|
+
const found = imageBlockIn([chunk.block], match);
|
|
566
|
+
if (found !== undefined)
|
|
567
|
+
return found;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
return undefined;
|
|
501
571
|
}
|
|
502
572
|
function referencedImage(events, attachmentId) {
|
|
503
573
|
for (const event of events) {
|
package/lib/types/history.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export declare class SessionHistoryController {
|
|
|
7
7
|
private readonly ctx;
|
|
8
8
|
private readonly promote;
|
|
9
9
|
private readonly closeFollowers;
|
|
10
|
+
private readonly assistantStreams;
|
|
10
11
|
/**
|
|
11
12
|
* @param ctx - Host context carrying Session query and projection services.
|
|
12
13
|
* @param promote - starts ordinary Session activation after snapshot delivery.
|
|
@@ -23,7 +24,7 @@ export declare class SessionHistoryController {
|
|
|
23
24
|
* Follow events appended after an initial cursor on one durable address.
|
|
24
25
|
* @param request - durable address and last committed sequence already held by the caller.
|
|
25
26
|
* @param signal - stream cancellation owned by the Remote carrier.
|
|
26
|
-
* @returns a complete opening snapshot followed by gap-free
|
|
27
|
+
* @returns a complete opening snapshot followed by gap-free durable events and opted-in assistant frames.
|
|
27
28
|
*/
|
|
28
29
|
follow(request: SessionFollowRequest, signal: AbortSignal): AsyncIterable<SessionFollowFrame>;
|
|
29
30
|
private sourceFor;
|
package/lib/types/history.js
CHANGED
|
@@ -53,9 +53,9 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
53
53
|
});
|
|
54
54
|
import { Deque } from '@deepseek-ai/dsh-deque';
|
|
55
55
|
import { isAppendSurfaceEvent, SessionLogOffset, SessionSeq, } from '@deepseek-ai/dsh-session';
|
|
56
|
-
import { isChunkRow, packChunkRuns } from '@deepseek-ai/dsh-session/chunk-rows';
|
|
57
56
|
import { SessionQueryError } from '@deepseek-ai/dsh-session-query';
|
|
58
57
|
import { RemoteError } from '@deepseek-ai/dsh-typert-protocol';
|
|
58
|
+
import { SessionAssistantStreamAccumulator } from "./assistant-stream.js";
|
|
59
59
|
const DEFAULT_MAX_MESSAGES = 50;
|
|
60
60
|
const MESSAGE_TYPES = new Set(['user/message', 'assistant/message']);
|
|
61
61
|
/** Implements cold-safe history operations delegated by the Session Controller. */
|
|
@@ -63,6 +63,7 @@ export class SessionHistoryController {
|
|
|
63
63
|
ctx;
|
|
64
64
|
promote;
|
|
65
65
|
closeFollowers = new Set();
|
|
66
|
+
assistantStreams = new Map();
|
|
66
67
|
/**
|
|
67
68
|
* @param ctx - Host context carrying Session query and projection services.
|
|
68
69
|
* @param promote - starts ordinary Session activation after snapshot delivery.
|
|
@@ -70,6 +71,17 @@ export class SessionHistoryController {
|
|
|
70
71
|
constructor(ctx, promote) {
|
|
71
72
|
this.ctx = ctx;
|
|
72
73
|
this.promote = promote;
|
|
74
|
+
ctx.on('agent/assistant-stream', ({ agent, frame }) => {
|
|
75
|
+
let stream = this.assistantStreams.get(agent.session.id);
|
|
76
|
+
if (stream === undefined) {
|
|
77
|
+
stream = new SessionAssistantStreamAccumulator();
|
|
78
|
+
this.assistantStreams.set(agent.session.id, stream);
|
|
79
|
+
}
|
|
80
|
+
stream.accept(frame, cursorBeforeNext(agent.session.seq));
|
|
81
|
+
}, { global: true });
|
|
82
|
+
ctx.on('agent/disposed', ({ agent }) => {
|
|
83
|
+
this.assistantStreams.delete(agent.session.id);
|
|
84
|
+
}, { global: true });
|
|
73
85
|
ctx.effect(() => () => {
|
|
74
86
|
for (const close of this.closeFollowers)
|
|
75
87
|
close();
|
|
@@ -122,7 +134,7 @@ export class SessionHistoryController {
|
|
|
122
134
|
* Follow events appended after an initial cursor on one durable address.
|
|
123
135
|
* @param request - durable address and last committed sequence already held by the caller.
|
|
124
136
|
* @param signal - stream cancellation owned by the Remote carrier.
|
|
125
|
-
* @returns a complete opening snapshot followed by gap-free
|
|
137
|
+
* @returns a complete opening snapshot followed by gap-free durable events and opted-in assistant frames.
|
|
126
138
|
*/
|
|
127
139
|
async *follow(request, signal) {
|
|
128
140
|
validateFollowRequest(request);
|
|
@@ -130,6 +142,7 @@ export class SessionHistoryController {
|
|
|
130
142
|
const target = addressId(address);
|
|
131
143
|
const buffered = new Deque();
|
|
132
144
|
let snapshotCursor;
|
|
145
|
+
let assistantStreamOrdinal = 0;
|
|
133
146
|
let wake;
|
|
134
147
|
const notify = () => {
|
|
135
148
|
const resume = wake;
|
|
@@ -145,7 +158,7 @@ export class SessionHistoryController {
|
|
|
145
158
|
const disposeEvent = this.ctx.on('session/event', (session, event) => {
|
|
146
159
|
if (session.id !== target)
|
|
147
160
|
return;
|
|
148
|
-
buffered.pushBack(event);
|
|
161
|
+
buffered.pushBack({ type: 'event', event });
|
|
149
162
|
notify();
|
|
150
163
|
}, { global: true });
|
|
151
164
|
const disposeCreated = this.ctx.on('session/created', (session) => {
|
|
@@ -158,10 +171,22 @@ export class SessionHistoryController {
|
|
|
158
171
|
? session.firstLiveSeq
|
|
159
172
|
: SessionLogOffset(snapshotCursor + 1));
|
|
160
173
|
for (let index = suffix.length - 1; index >= 0; index -= 1) {
|
|
161
|
-
buffered.pushFront(suffix[index]);
|
|
174
|
+
buffered.pushFront({ type: 'event', event: suffix[index] });
|
|
162
175
|
}
|
|
163
176
|
notify();
|
|
164
177
|
}, { global: true });
|
|
178
|
+
const disposeAssistantStream = request.assistantStream !== true
|
|
179
|
+
? undefined
|
|
180
|
+
: this.ctx.on('agent/assistant-stream', ({ agent, frame }) => {
|
|
181
|
+
if (agent.session.id !== target)
|
|
182
|
+
return;
|
|
183
|
+
buffered.pushBack({
|
|
184
|
+
type: 'assistant-stream',
|
|
185
|
+
frame: wireAssistantStreamFrame(frame, cursorBeforeNext(agent.session.seq)),
|
|
186
|
+
ordinal: ++assistantStreamOrdinal,
|
|
187
|
+
});
|
|
188
|
+
notify();
|
|
189
|
+
}, { global: true });
|
|
165
190
|
const onAbort = () => { notify(); };
|
|
166
191
|
signal.addEventListener('abort', onAbort, { once: true });
|
|
167
192
|
try {
|
|
@@ -173,15 +198,24 @@ export class SessionHistoryController {
|
|
|
173
198
|
const cursor = source.cursor;
|
|
174
199
|
snapshotCursor = cursor;
|
|
175
200
|
const page = paginate(events, undefined, request.maxMessages ?? DEFAULT_MAX_MESSAGES);
|
|
201
|
+
const assistantStream = request.assistantStream === true
|
|
202
|
+
? this.assistantStreams.get(target)?.snapshot() ?? { revision: 0 }
|
|
203
|
+
: undefined;
|
|
204
|
+
// The accumulator snapshot and this watermark are synchronous. Frames
|
|
205
|
+
// through the cut are represented or superseded by that baseline,
|
|
206
|
+
// including larger revisions from a retired Agent; later revision
|
|
207
|
+
// resets reach Client continuity validation.
|
|
208
|
+
const assistantStreamOrdinalCut = assistantStreamOrdinal;
|
|
176
209
|
yield {
|
|
177
210
|
type: 'snapshot',
|
|
178
|
-
header: wireHeader(source.header
|
|
211
|
+
header: wireHeader(source.header),
|
|
179
212
|
cursor,
|
|
180
213
|
records: pageRecords(page.events),
|
|
181
214
|
hasMore: page.hasMore,
|
|
182
215
|
projections: source.projections === undefined
|
|
183
216
|
? { asOfSeq: cursor, values: {} }
|
|
184
217
|
: projectionBlock(source.projections),
|
|
218
|
+
...assistantStream === undefined ? {} : { assistantStream },
|
|
185
219
|
};
|
|
186
220
|
if (address.kind === 'session' && source.source === 'prepared') {
|
|
187
221
|
const promotion = source.retain();
|
|
@@ -200,14 +234,20 @@ export class SessionHistoryController {
|
|
|
200
234
|
await new Promise((resolve) => { wake = resolve; });
|
|
201
235
|
continue;
|
|
202
236
|
}
|
|
237
|
+
if (item.type === 'assistant-stream') {
|
|
238
|
+
if (item.ordinal > assistantStreamOrdinalCut) {
|
|
239
|
+
yield { type: 'assistant-stream', frame: item.frame };
|
|
240
|
+
}
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
203
243
|
const expectedSeq = SessionSeq(nextOffset);
|
|
204
|
-
if (item.seq < expectedSeq)
|
|
244
|
+
if (item.event.seq < expectedSeq)
|
|
205
245
|
continue;
|
|
206
|
-
if (item.seq !== expectedSeq) {
|
|
246
|
+
if (item.event.seq !== expectedSeq) {
|
|
207
247
|
throw new RemoteError('gateway/internal', `session event stream skipped seq ${String(expectedSeq)}`, {});
|
|
208
248
|
}
|
|
209
249
|
nextOffset = SessionLogOffset(nextOffset + 1);
|
|
210
|
-
yield entryFor(item);
|
|
250
|
+
yield entryFor(item.event);
|
|
211
251
|
}
|
|
212
252
|
}
|
|
213
253
|
catch (e_2) {
|
|
@@ -223,6 +263,7 @@ export class SessionHistoryController {
|
|
|
223
263
|
signal.removeEventListener('abort', onAbort);
|
|
224
264
|
disposeCreated();
|
|
225
265
|
disposeEvent();
|
|
266
|
+
disposeAssistantStream?.();
|
|
226
267
|
}
|
|
227
268
|
}
|
|
228
269
|
async sourceFor(address, signal, withProjections) {
|
|
@@ -253,6 +294,19 @@ export class SessionHistoryController {
|
|
|
253
294
|
}
|
|
254
295
|
}
|
|
255
296
|
}
|
|
297
|
+
function cursorBeforeNext(nextSeq) {
|
|
298
|
+
return nextSeq === 0 ? -1 : SessionSeq(nextSeq - 1);
|
|
299
|
+
}
|
|
300
|
+
function wireAssistantStreamFrame(frame, durableCursor) {
|
|
301
|
+
if (frame.type === 'start')
|
|
302
|
+
return { ...frame, startedAfterSeq: durableCursor };
|
|
303
|
+
if (frame.type === 'end')
|
|
304
|
+
return frame;
|
|
305
|
+
return {
|
|
306
|
+
...frame,
|
|
307
|
+
chunk: frame.chunk,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
256
310
|
function projectionBlock(snapshot) {
|
|
257
311
|
return {
|
|
258
312
|
asOfSeq: snapshot.asOfSeq,
|
|
@@ -354,13 +408,9 @@ function paginate(events, beforeSeq, maxMessages, throughSeq = events.at(-1)?.se
|
|
|
354
408
|
}
|
|
355
409
|
return { events: events.slice(cut, end), hasMore: cut > 0 };
|
|
356
410
|
}
|
|
357
|
-
/** Translate logical Session metadata to the
|
|
358
|
-
function wireHeader(header
|
|
359
|
-
|
|
360
|
-
return {
|
|
361
|
-
...wire,
|
|
362
|
-
...isSeeded ? { seedLength: inheritedEventCount } : {},
|
|
363
|
-
};
|
|
411
|
+
/** Translate current logical Session metadata to the browser wire. */
|
|
412
|
+
function wireHeader(header) {
|
|
413
|
+
return { ...header };
|
|
364
414
|
}
|
|
365
415
|
function entryFor(event) {
|
|
366
416
|
return {
|
|
@@ -369,29 +419,8 @@ function entryFor(event) {
|
|
|
369
419
|
event: event,
|
|
370
420
|
};
|
|
371
421
|
}
|
|
372
|
-
function chunkEntryFor(row) {
|
|
373
|
-
switch (row.type) {
|
|
374
|
-
case 'text-chunks':
|
|
375
|
-
return {
|
|
376
|
-
type: 'chunks',
|
|
377
|
-
event: { type: 'chunkrow/text-chunks', seq: row.seq0, time: row.time0, data: row.data },
|
|
378
|
-
};
|
|
379
|
-
case 'reasoning-chunks':
|
|
380
|
-
return {
|
|
381
|
-
type: 'chunks',
|
|
382
|
-
event: { type: 'chunkrow/reasoning-chunks', seq: row.seq0, time: row.time0, data: row.data },
|
|
383
|
-
};
|
|
384
|
-
case 'tool-call-chunks':
|
|
385
|
-
return {
|
|
386
|
-
type: 'chunks',
|
|
387
|
-
event: { type: 'chunkrow/tool-call-chunks', seq: row.seq0, time: row.time0, data: row.data },
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
422
|
/** Encode one bounded logical page without changing its pagination cut. */
|
|
392
423
|
function pageRecords(events) {
|
|
393
|
-
return
|
|
394
|
-
? chunkEntryFor(record)
|
|
395
|
-
: entryFor(record));
|
|
424
|
+
return events.map(entryFor);
|
|
396
425
|
}
|
|
397
426
|
//# sourceMappingURL=history.js.map
|
package/lib/types/index.d.ts
CHANGED
|
@@ -19,8 +19,6 @@ declare module '@deepseek-ai/cordis' {
|
|
|
19
19
|
}
|
|
20
20
|
/** Session Controller deployment policy. */
|
|
21
21
|
export interface Config {
|
|
22
|
-
/** Maximum cold Session artifact size eligible for one full projection observation. */
|
|
23
|
-
readonly coldBlankProbeMaxBytes?: number;
|
|
24
22
|
/** Override platform desktop-opener detection. */
|
|
25
23
|
readonly nativeOpen?: boolean;
|
|
26
24
|
}
|
|
@@ -45,7 +43,8 @@ export declare class SessionController extends TypertRemoteService {
|
|
|
45
43
|
private readonly promotions;
|
|
46
44
|
/**
|
|
47
45
|
* @param ctx - Host context containing the Session capability assembly.
|
|
48
|
-
* @param config -
|
|
46
|
+
* @param config - native-opener deployment policy.
|
|
47
|
+
* @param internals - host integrations replaceable by direct unit tests.
|
|
49
48
|
*/
|
|
50
49
|
constructor(ctx: Context, config: Config, internals?: SessionControllerInternals);
|
|
51
50
|
private promote;
|
|
@@ -154,7 +153,8 @@ export declare class SessionController extends TypertRemoteService {
|
|
|
154
153
|
* Follow one Session log from its opening or resume cursor.
|
|
155
154
|
* @param request - durable address and last committed sequence already held by the caller.
|
|
156
155
|
* @param signal - cancellation owned by the Remote stream carrier.
|
|
157
|
-
* @returns a complete opening snapshot followed by gap-free event
|
|
156
|
+
* @returns a complete opening snapshot followed by gap-free durable event
|
|
157
|
+
* frames and optional cursorless assistant-stream frames.
|
|
158
158
|
*/
|
|
159
159
|
follow(request: SessionFollowRequest, signal: AbortSignal): AsyncIterable<SessionFollowFrame>;
|
|
160
160
|
/**
|
package/lib/types/index.js
CHANGED
|
@@ -94,7 +94,7 @@ import { SessionCommandController } from "./commands.js";
|
|
|
94
94
|
import { SessionControlController } from "./control.js";
|
|
95
95
|
import { SessionHistoryController } from "./history.js";
|
|
96
96
|
import { SessionFileReferences } from "./file-references.js";
|
|
97
|
-
import { ApiSessionList
|
|
97
|
+
import { ApiSessionList } from "./list.js";
|
|
98
98
|
import { buildModelCatalog } from "./catalog.js";
|
|
99
99
|
import { installModelSelectionProjection } from "./model-selection-projection.js";
|
|
100
100
|
import { SessionSkillCatalog } from "./skill-catalog.js";
|
|
@@ -162,6 +162,7 @@ let SessionController = (() => {
|
|
|
162
162
|
'agentDefaultModel',
|
|
163
163
|
'agents',
|
|
164
164
|
'attachments',
|
|
165
|
+
'fileUploads',
|
|
165
166
|
'llm',
|
|
166
167
|
'sessions',
|
|
167
168
|
'sessionProjections',
|
|
@@ -170,7 +171,6 @@ let SessionController = (() => {
|
|
|
170
171
|
'workspaceRegistry',
|
|
171
172
|
];
|
|
172
173
|
static Config = z.object({
|
|
173
|
-
coldBlankProbeMaxBytes: z.natural().default(DEFAULT_COLD_BLANK_PROBE_MAX_BYTES),
|
|
174
174
|
nativeOpen: z.boolean(),
|
|
175
175
|
});
|
|
176
176
|
agents = __runInitializers(this, _instanceExtraInitializers);
|
|
@@ -183,13 +183,20 @@ let SessionController = (() => {
|
|
|
183
183
|
promotions = new Set();
|
|
184
184
|
/**
|
|
185
185
|
* @param ctx - Host context containing the Session capability assembly.
|
|
186
|
-
* @param config -
|
|
186
|
+
* @param config - native-opener deployment policy.
|
|
187
|
+
* @param internals - host integrations replaceable by direct unit tests.
|
|
187
188
|
*/
|
|
188
189
|
constructor(ctx, config, internals = {}) {
|
|
189
190
|
super(ctx, 'sessionController', { namespace: 'session' });
|
|
190
191
|
installModelSelectionProjection(ctx);
|
|
191
192
|
this.agents = new ApiSessionAgentController(ctx);
|
|
192
193
|
this.commands = new SessionCommandController(ctx, this.agents, process.cwd());
|
|
194
|
+
ctx.effect(() => ctx.fileUploads.registerAgentResolver(async (sessionId) => {
|
|
195
|
+
const result = await this.agents.resolveAgent(sessionId);
|
|
196
|
+
if ('error' in result)
|
|
197
|
+
throw result.error;
|
|
198
|
+
return result.agent;
|
|
199
|
+
}), 'session-controller: file-upload Agent resolver');
|
|
193
200
|
this.controlState = new SessionControlController(ctx);
|
|
194
201
|
// Registered before history so reverse-order teardown closes every
|
|
195
202
|
// follower before waiting for already-admitted promotions.
|
|
@@ -197,7 +204,7 @@ let SessionController = (() => {
|
|
|
197
204
|
await Promise.allSettled([...this.promotions]);
|
|
198
205
|
}, 'session-controller.promotions');
|
|
199
206
|
this.history = new SessionHistoryController(ctx, (observation) => { this.promote(observation); });
|
|
200
|
-
this.listState = new ApiSessionList(ctx
|
|
207
|
+
this.listState = new ApiSessionList(ctx);
|
|
201
208
|
this.openPath = internals.openPath ?? openNativePath;
|
|
202
209
|
this.canOpenPath = internals.canOpenPath
|
|
203
210
|
?? (() => config.nativeOpen ?? (internals.openPath !== undefined || canOpenNativePath()));
|
|
@@ -407,7 +414,8 @@ let SessionController = (() => {
|
|
|
407
414
|
* Follow one Session log from its opening or resume cursor.
|
|
408
415
|
* @param request - durable address and last committed sequence already held by the caller.
|
|
409
416
|
* @param signal - cancellation owned by the Remote stream carrier.
|
|
410
|
-
* @returns a complete opening snapshot followed by gap-free event
|
|
417
|
+
* @returns a complete opening snapshot followed by gap-free durable event
|
|
418
|
+
* frames and optional cursorless assistant-stream frames.
|
|
411
419
|
*/
|
|
412
420
|
follow(request, signal) {
|
|
413
421
|
return this.history.follow(request, signal);
|
package/lib/types/list.d.ts
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
import type { Context } from '@deepseek-ai/cordis';
|
|
3
3
|
import type { Session, SessionEvent } from '@deepseek-ai/dsh-session';
|
|
4
4
|
import type { SessionListMetadata, SessionSearchValue, SessionSummary } from './types.ts';
|
|
5
|
-
/** Default maximum artifact size eligible for one cold projection observation. */
|
|
6
|
-
export declare const DEFAULT_COLD_BLANK_PROBE_MAX_BYTES = 1024;
|
|
7
5
|
/**
|
|
8
6
|
* Advance the Session-list metadata projection by one committed event.
|
|
9
7
|
* @param state - metadata before the event.
|
|
@@ -21,12 +19,8 @@ export declare function truncateUnicodeCodePoints(value: string, maximum: number
|
|
|
21
19
|
/** Owns list projection registration, bounded cold summaries, and authorized search. */
|
|
22
20
|
export declare class ApiSessionList {
|
|
23
21
|
private readonly ctx;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
* @param ctx - Host context carrying Session, query, persistence, and projection services.
|
|
27
|
-
* @param coldBlankProbeMaxBytes - maximum physical artifact size eligible for a full observation.
|
|
28
|
-
*/
|
|
29
|
-
constructor(ctx: Context, coldBlankProbeMaxBytes: number);
|
|
22
|
+
/** @param ctx - Host context carrying Session, query, persistence, and projection services. */
|
|
23
|
+
constructor(ctx: Context);
|
|
30
24
|
/**
|
|
31
25
|
* Build one current attached-Session summary.
|
|
32
26
|
* @param session - attached Session to summarize.
|
|
@@ -40,7 +34,6 @@ export declare class ApiSessionList {
|
|
|
40
34
|
*/
|
|
41
35
|
list(signal?: AbortSignal): Promise<SessionSummary[]>;
|
|
42
36
|
private summarizeCold;
|
|
43
|
-
private probeSmallCold;
|
|
44
37
|
/**
|
|
45
38
|
* Search current visible message content without activating any matching Session.
|
|
46
39
|
* @param query - literal message-content query.
|