@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
|
@@ -7,22 +7,22 @@
|
|
|
7
7
|
* must stub); implementation-internal entry points (history staging, wire-frame
|
|
8
8
|
* dispatch) stay on the class, invisible out here.
|
|
9
9
|
*/
|
|
10
|
-
import type { AttachmentIdType, ImageAttachmentRef } from '@deepseek-ai/dsh-attachment';
|
|
10
|
+
import type { AttachmentIdType, FileAttachmentRef, ImageAttachmentRef } from '@deepseek-ai/dsh-attachment';
|
|
11
11
|
import type { MessageId } from '@deepseek-ai/dsh-llm/brand';
|
|
12
12
|
import type { SessionId, SessionSeq } from '@deepseek-ai/dsh-session/types';
|
|
13
13
|
import type { RemoteResult } from '@deepseek-ai/dsh-typert-protocol';
|
|
14
14
|
import type { ObservableSnapshot } from '@deepseek-ai/dsh-client-store';
|
|
15
15
|
import type { PromptContentPart, QueueAction, SessionRequestId } from '../../types.ts';
|
|
16
|
-
import type {
|
|
16
|
+
import type { PendingSubmissionAttachment, SessionSnapshot } from './snapshot.ts';
|
|
17
17
|
/**
|
|
18
18
|
* Why a local submission echo left the snapshot: `observed` when its durable
|
|
19
19
|
* `user/message` event or host queue occurrence arrived (with the admitted
|
|
20
|
-
*
|
|
20
|
+
* attachment references in prompt order), `failed` when the prompt was rejected,
|
|
21
21
|
* threw, or was aborted before acceptance.
|
|
22
22
|
*/
|
|
23
23
|
export type PendingSubmissionRetirement = {
|
|
24
24
|
readonly reason: 'observed';
|
|
25
|
-
readonly attachments: readonly ImageAttachmentRef[];
|
|
25
|
+
readonly attachments: readonly (ImageAttachmentRef | FileAttachmentRef)[];
|
|
26
26
|
} | {
|
|
27
27
|
readonly reason: 'failed';
|
|
28
28
|
};
|
|
@@ -32,8 +32,8 @@ export interface BeginSubmissionInput {
|
|
|
32
32
|
readonly mode: 'queue' | 'steer';
|
|
33
33
|
/** Prompt text exactly as the upcoming prompt will send it. */
|
|
34
34
|
readonly text: string;
|
|
35
|
-
/** Ordered image previews matching the upcoming prompt
|
|
36
|
-
readonly
|
|
35
|
+
/** Ordered image previews and durable file metadata matching the upcoming prompt attachments. */
|
|
36
|
+
readonly attachments: readonly PendingSubmissionAttachment[];
|
|
37
37
|
/** Settlement callback fired exactly once when the echo retires. */
|
|
38
38
|
readonly onRetire?: (retirement: PendingSubmissionRetirement) => void;
|
|
39
39
|
}
|
|
@@ -91,7 +91,7 @@ export interface ISession {
|
|
|
91
91
|
data: Uint8Array;
|
|
92
92
|
}>>;
|
|
93
93
|
/**
|
|
94
|
-
* Apply one edit, remove, or
|
|
94
|
+
* Apply one edit, remove, or Steer action to a still-pending queue occurrence.
|
|
95
95
|
* @param itemId - agent-owned inbox occurrence identity.
|
|
96
96
|
* @param action - requested queue operation.
|
|
97
97
|
* @returns acceptance, or a business/transport error.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Session-owned observable state excluding Conversation target data. */
|
|
2
2
|
import type { ContentBlock } from '@deepseek-ai/dsh-llm/types';
|
|
3
|
+
import type { FileAttachmentRef } from '@deepseek-ai/dsh-attachment';
|
|
3
4
|
import type { MessageId } from '@deepseek-ai/dsh-llm/brand';
|
|
4
5
|
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
5
6
|
import type { SubagentAddress } from '@deepseek-ai/dsh-subagent/client';
|
|
@@ -27,6 +28,18 @@ export interface PendingSubmissionImage {
|
|
|
27
28
|
/** Intrinsic pixel height, when the submitter has probed it. */
|
|
28
29
|
readonly height?: number;
|
|
29
30
|
}
|
|
31
|
+
/** Image branch of a local submission echo attachment. */
|
|
32
|
+
export interface PendingSubmissionImageAttachment {
|
|
33
|
+
readonly type: 'image';
|
|
34
|
+
readonly value: PendingSubmissionImage;
|
|
35
|
+
}
|
|
36
|
+
/** File branch of a local submission echo attachment. */
|
|
37
|
+
export interface PendingSubmissionFileAttachment {
|
|
38
|
+
readonly type: 'file';
|
|
39
|
+
readonly value: FileAttachmentRef;
|
|
40
|
+
}
|
|
41
|
+
/** One attachment displayed by a local submission echo, in prompt order. */
|
|
42
|
+
export type PendingSubmissionAttachment = PendingSubmissionImageAttachment | PendingSubmissionFileAttachment;
|
|
30
43
|
/** Client surface selected when a local submission begins. */
|
|
31
44
|
export type PendingSubmissionPlacement = 'transcript' | 'queued' | 'steering';
|
|
32
45
|
/**
|
|
@@ -44,8 +57,8 @@ export interface PendingSubmission {
|
|
|
44
57
|
readonly time: number;
|
|
45
58
|
/** Prompt text exactly as it will be sent (one text block). */
|
|
46
59
|
readonly text: string;
|
|
47
|
-
/** Ordered image previews matching the prompt
|
|
48
|
-
readonly
|
|
60
|
+
/** Ordered image previews and durable file metadata matching the prompt attachments. */
|
|
61
|
+
readonly attachments: readonly PendingSubmissionAttachment[];
|
|
49
62
|
}
|
|
50
63
|
/** History-open lifecycle of a Session event window. */
|
|
51
64
|
export type OpenState = 'cold' | 'loading' | 'open' | 'error';
|
|
@@ -12,8 +12,8 @@ export type { ProjectionsBaseline, ProjectionValueStore, SessionProjectionMap, U
|
|
|
12
12
|
export type { BeginSubmissionInput, ISession, PendingSubmissionRetirement, ProjectionsFace, SessionFace, SubmissionHandle, } from './contract/session.ts';
|
|
13
13
|
export type { ISessions } from './contract/sessions.ts';
|
|
14
14
|
export { MutableSessionEventSource } from './contract/events.ts';
|
|
15
|
-
export type { SessionEventChange, SessionEventLike, SessionEventLikeEntry, SessionEventSource, SessionEventWindow, SessionLiveEventEntry, } from './contract/events.ts';
|
|
16
|
-
export type { OpenState, PendingSubmission, PendingSubmissionImage, PendingSubmissionPlacement, PromptError, QueuedMessage, SessionSnapshot, } from './contract/snapshot.ts';
|
|
15
|
+
export type { AssistantLiveChunkEvent, SessionAssistantSettlementEntry, SessionEventChange, SessionEventLike, SessionEventLikeEntry, SessionEventSource, SessionEventWindow, SessionLiveEventEntry, SessionTransientEventEntry, } from './contract/events.ts';
|
|
16
|
+
export type { OpenState, PendingSubmission, PendingSubmissionAttachment, PendingSubmissionFileAttachment, PendingSubmissionImage, PendingSubmissionImageAttachment, PendingSubmissionPlacement, PromptError, QueuedMessage, SessionSnapshot, } from './contract/snapshot.ts';
|
|
17
17
|
declare module '@deepseek-ai/cordis' {
|
|
18
18
|
interface Context {
|
|
19
19
|
/** Client Session object layer and Agent scope owner. */
|
|
@@ -7,6 +7,8 @@ export { SessionCreateError, SessionForkError } from "./sessions/service.js";
|
|
|
7
7
|
export { MutableSessionEventSource } from "./contract/events.js";
|
|
8
8
|
/** Required Remote and Context projection services. */
|
|
9
9
|
export const inject = [
|
|
10
|
+
'connection',
|
|
11
|
+
'fileUpload',
|
|
10
12
|
'typert',
|
|
11
13
|
'remote',
|
|
12
14
|
'remote.commands',
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Event-local acceptance for raw Session journal responses; payloads remain owner-defined JSON. */
|
|
2
|
+
import type { SessionWireEvent } from '../types.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Reject non-current event envelopes without stripping or normalizing wire fields.
|
|
5
|
+
* Range membership and source existence require the durable log and remain Host-owned.
|
|
6
|
+
* @param value - one event received in a follow frame or history page.
|
|
7
|
+
* @returns nothing after narrowing the accepted event envelope.
|
|
8
|
+
* @throws when the envelope or current event-local metadata is invalid.
|
|
9
|
+
*/
|
|
10
|
+
export declare function assertSessionWireEvent(value: unknown): asserts value is SessionWireEvent;
|
|
11
|
+
//# sourceMappingURL=session-wire-event.d.ts.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** Event-local acceptance for raw Session journal responses; payloads remain owner-defined JSON. */
|
|
2
|
+
import { validateSessionEventData, validateSurfaceMetadata } from '@deepseek-ai/dsh-session/surface';
|
|
3
|
+
/**
|
|
4
|
+
* Reject non-current event envelopes without stripping or normalizing wire fields.
|
|
5
|
+
* Range membership and source existence require the durable log and remain Host-owned.
|
|
6
|
+
* @param value - one event received in a follow frame or history page.
|
|
7
|
+
* @returns nothing after narrowing the accepted event envelope.
|
|
8
|
+
* @throws when the envelope or current event-local metadata is invalid.
|
|
9
|
+
*/
|
|
10
|
+
export function assertSessionWireEvent(value) {
|
|
11
|
+
const subject = 'session wire event';
|
|
12
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
13
|
+
throw new Error(`${subject} must be an object`);
|
|
14
|
+
}
|
|
15
|
+
const event = value;
|
|
16
|
+
for (const key of Object.keys(event)) {
|
|
17
|
+
switch (key) {
|
|
18
|
+
case 'type':
|
|
19
|
+
case 'seq':
|
|
20
|
+
case 'time':
|
|
21
|
+
case 'data':
|
|
22
|
+
case 'ignorable':
|
|
23
|
+
case 'surfaceOp':
|
|
24
|
+
case 'sourceEventSeqs':
|
|
25
|
+
break;
|
|
26
|
+
default:
|
|
27
|
+
throw new Error(`${subject} has unexpected field ${key}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const seq = event['seq'];
|
|
31
|
+
if (typeof event['type'] !== 'string'
|
|
32
|
+
|| typeof seq !== 'number' || !Number.isSafeInteger(seq) || seq < 0 || Object.is(seq, -0)
|
|
33
|
+
|| typeof event['time'] !== 'number' || !Number.isSafeInteger(event['time'])
|
|
34
|
+
|| !Object.hasOwn(event, 'data') || event['data'] === undefined
|
|
35
|
+
|| (Object.hasOwn(event, 'ignorable') && event['ignorable'] !== true)) {
|
|
36
|
+
throw new Error(`${subject} has an invalid envelope`);
|
|
37
|
+
}
|
|
38
|
+
// Event names and payloads are merge-extensible; only event-local owner rules run here.
|
|
39
|
+
const current = event;
|
|
40
|
+
validateSurfaceMetadata(current);
|
|
41
|
+
validateSessionEventData(current, subject);
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=session-wire-event.js.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** Web presentation fold joining transient Assistant frames to one durable v2 settlement. */
|
|
2
|
+
import type { SessionAssistantStreamBaseline, SessionAssistantStreamFrame } from '../../types.ts';
|
|
3
|
+
import type { LlmAttemptId } from '@deepseek-ai/dsh-llm/brand';
|
|
4
|
+
import type { SessionAssistantSettlementEntry, SessionEventLikeEntry, SessionLiveEventEntry, SessionTransientEventEntry } from '../contract/events.ts';
|
|
5
|
+
/** One Web publication decision from the assistant stream fold. */
|
|
6
|
+
export type ClientAssistantStreamResult = {
|
|
7
|
+
readonly type: 'publish';
|
|
8
|
+
readonly entry: SessionLiveEventEntry;
|
|
9
|
+
} | {
|
|
10
|
+
readonly type: 'settlement';
|
|
11
|
+
readonly attemptId: LlmAttemptId;
|
|
12
|
+
readonly entry: SessionAssistantSettlementEntry;
|
|
13
|
+
} | {
|
|
14
|
+
readonly type: 'abandonment';
|
|
15
|
+
readonly attemptId: LlmAttemptId;
|
|
16
|
+
} | {
|
|
17
|
+
readonly type: 'transient';
|
|
18
|
+
readonly entry: SessionTransientEventEntry;
|
|
19
|
+
} | {
|
|
20
|
+
readonly type: 'rebaseline';
|
|
21
|
+
} | undefined;
|
|
22
|
+
/** Keeps transient Assistant presentation behind one settlement-aware interface. */
|
|
23
|
+
export declare class ClientAssistantStream {
|
|
24
|
+
private activeAttempt;
|
|
25
|
+
private readonly pending;
|
|
26
|
+
private publishedSeqs;
|
|
27
|
+
private durableCursor;
|
|
28
|
+
private transientInGap;
|
|
29
|
+
/**
|
|
30
|
+
* Replace the durable Web window and adopt an optional reconnect baseline.
|
|
31
|
+
* @param entries - durable entries in the replacement window.
|
|
32
|
+
* @param baseline - compact prefix for an Assistant attempt that is still live.
|
|
33
|
+
* @returns immediately visible durable entries plus reconstructed transient chunks.
|
|
34
|
+
*/
|
|
35
|
+
replace(entries: readonly SessionEventLikeEntry[], baseline?: SessionAssistantStreamBaseline): readonly SessionEventLikeEntry[];
|
|
36
|
+
/**
|
|
37
|
+
* Stage one durable v2 settlement while its matching live attempt is open.
|
|
38
|
+
* @param entry - newly followed durable entry.
|
|
39
|
+
* @returns a publication decision, or `undefined` when no entry becomes visible.
|
|
40
|
+
*/
|
|
41
|
+
acceptDurable(entry: SessionLiveEventEntry): ClientAssistantStreamResult;
|
|
42
|
+
/**
|
|
43
|
+
* Fold one dense transient frame and release its named durable settlement.
|
|
44
|
+
* @param frame - next Assistant stream frame received by the follow connection.
|
|
45
|
+
* @returns a transient, publication, or rebaseline decision, or `undefined` when no entry becomes visible.
|
|
46
|
+
*/
|
|
47
|
+
acceptFrame(frame: SessionAssistantStreamFrame): ClientAssistantStreamResult;
|
|
48
|
+
private attemptForSettlement;
|
|
49
|
+
private publish;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=assistant-stream.d.ts.map
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/** Web presentation fold joining transient Assistant frames to one durable v2 settlement. */
|
|
2
|
+
import { expandAssistantStream } from '@deepseek-ai/dsh-llm/assistant-stream';
|
|
3
|
+
/** Keeps transient Assistant presentation behind one settlement-aware interface. */
|
|
4
|
+
export class ClientAssistantStream {
|
|
5
|
+
activeAttempt;
|
|
6
|
+
pending = new Map();
|
|
7
|
+
publishedSeqs = new Set();
|
|
8
|
+
durableCursor = -1;
|
|
9
|
+
transientInGap = 0;
|
|
10
|
+
/**
|
|
11
|
+
* Replace the durable Web window and adopt an optional reconnect baseline.
|
|
12
|
+
* @param entries - durable entries in the replacement window.
|
|
13
|
+
* @param baseline - compact prefix for an Assistant attempt that is still live.
|
|
14
|
+
* @returns immediately visible durable entries plus reconstructed transient chunks.
|
|
15
|
+
*/
|
|
16
|
+
replace(entries, baseline) {
|
|
17
|
+
this.pending.clear();
|
|
18
|
+
this.transientInGap = 0;
|
|
19
|
+
this.activeAttempt = undefined;
|
|
20
|
+
const opening = baseline?.activeAttempt;
|
|
21
|
+
if (opening !== undefined) {
|
|
22
|
+
this.activeAttempt = {
|
|
23
|
+
attemptId: opening.attemptId,
|
|
24
|
+
startedAfterSeq: opening.startedAfterSeq,
|
|
25
|
+
turn: opening.turn,
|
|
26
|
+
step: opening.step,
|
|
27
|
+
nextIndex: opening.nextIndex,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const visible = [...entries];
|
|
31
|
+
this.publishedSeqs = new Set(visible.map(entry => entry.event.seq));
|
|
32
|
+
this.durableCursor = visible.reduce((cursor, entry) => Math.max(cursor, entry.event.seq), -1);
|
|
33
|
+
if (opening !== undefined) {
|
|
34
|
+
for (const [index, member] of expandAssistantStream(opening.stream).entries()) {
|
|
35
|
+
this.transientInGap += 1;
|
|
36
|
+
visible.push({
|
|
37
|
+
type: 'transient',
|
|
38
|
+
event: {
|
|
39
|
+
type: 'assistant/live-chunk',
|
|
40
|
+
seq: this.durableCursor + 1 - 1 / (this.transientInGap + 1),
|
|
41
|
+
time: member.time,
|
|
42
|
+
data: {
|
|
43
|
+
attemptId: opening.attemptId,
|
|
44
|
+
turn: opening.turn,
|
|
45
|
+
step: opening.step,
|
|
46
|
+
chunk: member.chunk,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
if (index + 1 >= opening.nextIndex)
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return visible;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Stage one durable v2 settlement while its matching live attempt is open.
|
|
58
|
+
* @param entry - newly followed durable entry.
|
|
59
|
+
* @returns a publication decision, or `undefined` when no entry becomes visible.
|
|
60
|
+
*/
|
|
61
|
+
acceptDurable(entry) {
|
|
62
|
+
const event = entry.event;
|
|
63
|
+
this.durableCursor = Math.max(this.durableCursor, event.seq);
|
|
64
|
+
this.transientInGap = 0;
|
|
65
|
+
const settlement = assistantSettlementEntry(entry);
|
|
66
|
+
if (settlement !== undefined && this.attemptForSettlement(settlement.event) !== undefined) {
|
|
67
|
+
if (this.pending.has(event.seq))
|
|
68
|
+
return { type: 'rebaseline' };
|
|
69
|
+
this.pending.set(event.seq, settlement);
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
return this.publish(entry);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Fold one dense transient frame and release its named durable settlement.
|
|
76
|
+
* @param frame - next Assistant stream frame received by the follow connection.
|
|
77
|
+
* @returns a transient, publication, or rebaseline decision, or `undefined` when no entry becomes visible.
|
|
78
|
+
*/
|
|
79
|
+
acceptFrame(frame) {
|
|
80
|
+
switch (frame.type) {
|
|
81
|
+
case 'start':
|
|
82
|
+
if (this.activeAttempt !== undefined || this.pending.size > 0)
|
|
83
|
+
return { type: 'rebaseline' };
|
|
84
|
+
this.pending.clear();
|
|
85
|
+
this.activeAttempt = {
|
|
86
|
+
attemptId: frame.attemptId,
|
|
87
|
+
startedAfterSeq: frame.startedAfterSeq,
|
|
88
|
+
turn: frame.turn,
|
|
89
|
+
step: frame.step,
|
|
90
|
+
nextIndex: 0,
|
|
91
|
+
};
|
|
92
|
+
return undefined;
|
|
93
|
+
case 'chunk': {
|
|
94
|
+
const attempt = this.activeAttempt;
|
|
95
|
+
// A controller mounted after the Host saw this attempt has no start
|
|
96
|
+
// frame to reconstruct. Its durable settlement publishes directly;
|
|
97
|
+
// ignore the transient suffix until the next known start.
|
|
98
|
+
if (attempt === undefined || attempt.attemptId !== frame.attemptId)
|
|
99
|
+
return undefined;
|
|
100
|
+
if (frame.index !== attempt.nextIndex)
|
|
101
|
+
return { type: 'rebaseline' };
|
|
102
|
+
attempt.nextIndex += 1;
|
|
103
|
+
this.transientInGap += 1;
|
|
104
|
+
return {
|
|
105
|
+
type: 'transient',
|
|
106
|
+
entry: {
|
|
107
|
+
type: 'transient',
|
|
108
|
+
event: {
|
|
109
|
+
type: 'assistant/live-chunk',
|
|
110
|
+
seq: this.durableCursor + 1 - 1 / (this.transientInGap + 1),
|
|
111
|
+
time: frame.time,
|
|
112
|
+
data: {
|
|
113
|
+
attemptId: frame.attemptId,
|
|
114
|
+
turn: attempt.turn,
|
|
115
|
+
step: attempt.step,
|
|
116
|
+
chunk: frame.chunk,
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
case 'end': {
|
|
123
|
+
const attempt = this.activeAttempt;
|
|
124
|
+
if (attempt === undefined || attempt.attemptId !== frame.attemptId) {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
this.activeAttempt = undefined;
|
|
128
|
+
if (frame.index !== attempt.nextIndex)
|
|
129
|
+
return { type: 'rebaseline' };
|
|
130
|
+
if (frame.outcome.kind === 'abandoned') {
|
|
131
|
+
return this.pending.size === 0
|
|
132
|
+
? { type: 'abandonment', attemptId: attempt.attemptId }
|
|
133
|
+
: { type: 'rebaseline' };
|
|
134
|
+
}
|
|
135
|
+
if (this.publishedSeqs.has(frame.outcome.seq))
|
|
136
|
+
return undefined;
|
|
137
|
+
const entry = this.pending.get(frame.outcome.seq);
|
|
138
|
+
if (entry === undefined
|
|
139
|
+
|| entry.event.type !== frame.outcome.eventType) {
|
|
140
|
+
return { type: 'rebaseline' };
|
|
141
|
+
}
|
|
142
|
+
this.pending.delete(frame.outcome.seq);
|
|
143
|
+
this.publishedSeqs.add(entry.event.seq);
|
|
144
|
+
return { type: 'settlement', attemptId: attempt.attemptId, entry };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
attemptForSettlement(event) {
|
|
149
|
+
const attempt = this.activeAttempt;
|
|
150
|
+
if (attempt === undefined
|
|
151
|
+
|| (event.type === 'assistant/message' && event.surfaceOp !== 'append')
|
|
152
|
+
|| event.seq <= attempt.startedAfterSeq
|
|
153
|
+
|| attempt.turn !== event.data.turn
|
|
154
|
+
|| attempt.step !== event.data.step)
|
|
155
|
+
return undefined;
|
|
156
|
+
return attempt;
|
|
157
|
+
}
|
|
158
|
+
publish(entry) {
|
|
159
|
+
this.publishedSeqs.add(entry.event.seq);
|
|
160
|
+
return { type: 'publish', entry };
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function assistantSettlementEntry(entry) {
|
|
164
|
+
return entry.event.type === 'assistant/message' || entry.event.type === 'assistant/attempt'
|
|
165
|
+
? entry
|
|
166
|
+
: undefined;
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=assistant-stream.js.map
|
|
@@ -9,13 +9,13 @@ import type { SessionEventLikeEntry } from '../contract/events.ts';
|
|
|
9
9
|
export declare function historyEntries(records: readonly SessionHistoryRecord[]): readonly SessionEventLikeEntry[];
|
|
10
10
|
/**
|
|
11
11
|
* Read the first logical sequence represented by one wire record.
|
|
12
|
-
* @param record - validated
|
|
12
|
+
* @param record - validated Session event.
|
|
13
13
|
* @returns inclusive first Session sequence.
|
|
14
14
|
*/
|
|
15
15
|
export declare function historyRecordFirstSeq(record: SessionHistoryRecord): number;
|
|
16
16
|
/**
|
|
17
17
|
* Read the final logical sequence represented by one wire record.
|
|
18
|
-
* @param record - validated
|
|
18
|
+
* @param record - validated Session event.
|
|
19
19
|
* @returns inclusive final Session sequence.
|
|
20
20
|
*/
|
|
21
21
|
export declare function historyRecordLastSeq(record: SessionHistoryRecord): number;
|
|
@@ -9,7 +9,7 @@ export function historyEntries(records) {
|
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
11
|
* Read the first logical sequence represented by one wire record.
|
|
12
|
-
* @param record - validated
|
|
12
|
+
* @param record - validated Session event.
|
|
13
13
|
* @returns inclusive first Session sequence.
|
|
14
14
|
*/
|
|
15
15
|
export function historyRecordFirstSeq(record) {
|
|
@@ -17,15 +17,10 @@ export function historyRecordFirstSeq(record) {
|
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
19
|
* Read the final logical sequence represented by one wire record.
|
|
20
|
-
* @param record - validated
|
|
20
|
+
* @param record - validated Session event.
|
|
21
21
|
* @returns inclusive final Session sequence.
|
|
22
22
|
*/
|
|
23
23
|
export function historyRecordLastSeq(record) {
|
|
24
|
-
|
|
25
|
-
return record.event.seq;
|
|
26
|
-
const length = record.event.type === 'chunkrow/tool-call-chunks'
|
|
27
|
-
? record.event.data.args.length
|
|
28
|
-
: record.event.data.texts.length;
|
|
29
|
-
return record.event.seq + length - 1;
|
|
24
|
+
return record.event.seq;
|
|
30
25
|
}
|
|
31
26
|
//# sourceMappingURL=history-records.js.map
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
const QUEUE_PREVIEW_CHARS = 200;
|
|
2
|
-
//
|
|
3
|
-
//
|
|
2
|
+
// Attachment blocks are excluded: queue presentation renders them from
|
|
3
|
+
// `content`, so the text preview covers only what has no visual form.
|
|
4
4
|
function previewOf(content) {
|
|
5
5
|
const flat = content
|
|
6
|
-
.filter(block => block.type !== 'image')
|
|
6
|
+
.filter(block => block.type !== 'image' && block.type !== 'file')
|
|
7
7
|
.map(block => (block.type === 'text' ? block.text : `[${block.type}]`))
|
|
8
8
|
.join(' ').replace(/\s+/g, ' ').trim();
|
|
9
9
|
const chars = Array.from(flat);
|
|
@@ -4,15 +4,15 @@
|
|
|
4
4
|
*
|
|
5
5
|
* @module @deepseek-ai/dsh-api-session-controller/client/sessions/remotes
|
|
6
6
|
*/
|
|
7
|
-
import type { EncodedImageAttachment } from '@deepseek-ai/dsh-attachment/types';
|
|
8
7
|
import type { ClientRemote } from '@deepseek-ai/dsh-api-gateway/client';
|
|
8
|
+
import type { CommandSubmitAttachment } from '@deepseek-ai/dsh-commands/types';
|
|
9
9
|
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
10
10
|
import type { SubagentCatalog, SubagentInterruptReceipt, SubagentPromptReceipt, SubagentPromptRequest } from '@deepseek-ai/dsh-subagent/client';
|
|
11
11
|
import type { RemoteResult } from '@deepseek-ai/dsh-typert-protocol';
|
|
12
12
|
import type { SessionRemote } from '../transport.ts';
|
|
13
13
|
/** Narrow Commands namespace consumed by a Client Session. */
|
|
14
14
|
export interface SessionCommandsRemote {
|
|
15
|
-
execute(agentId: SessionId, line: string,
|
|
15
|
+
execute(agentId: SessionId, line: string, attachments: readonly CommandSubmitAttachment[], signal?: AbortSignal): Promise<RemoteResult<object | undefined>>;
|
|
16
16
|
}
|
|
17
17
|
/** Narrow subagent namespace consumed by a Client Session and its manager. */
|
|
18
18
|
export interface SessionSubagentsRemote {
|
|
@@ -61,6 +61,7 @@ export declare class Session implements SessionFace {
|
|
|
61
61
|
private jumpPromise;
|
|
62
62
|
/** Authoritative stream-only inbox snapshot; pending work never hits history. */
|
|
63
63
|
private readonly queueMirror;
|
|
64
|
+
private readonly assistantStream;
|
|
64
65
|
private running;
|
|
65
66
|
private address;
|
|
66
67
|
private parentAvailable;
|
|
@@ -136,7 +137,7 @@ export declare class Session implements SessionFace {
|
|
|
136
137
|
beginSubmission(input: BeginSubmissionInput): SubmissionHandle;
|
|
137
138
|
/**
|
|
138
139
|
* Send (queue/steer passed through 1:1); failures land in the snapshot's promptError.
|
|
139
|
-
* @param content - text
|
|
140
|
+
* @param content - text, browser-owned temporary image uploads, and staged-file receipts.
|
|
140
141
|
* @param mode - queue appends after the current turn; steer interrupts it.
|
|
141
142
|
* @param signal - optional caller cancellation for the complete admission round-trip.
|
|
142
143
|
* @param requestId - identity from {@link beginSubmission}; a failed identified prompt retires its echo.
|
|
@@ -267,6 +268,7 @@ export declare class Session implements SessionFace {
|
|
|
267
268
|
private acceptEventChange;
|
|
268
269
|
/** Replace the complete contiguous window and apply page-owned projection metadata. */
|
|
269
270
|
private installWindow;
|
|
271
|
+
private publishAssistantEntry;
|
|
270
272
|
/** Prepend one stream-validated history page. */
|
|
271
273
|
private prependWindow;
|
|
272
274
|
/** Append one stream-validated live event. */
|
|
@@ -5,9 +5,11 @@ import { SessionEventStream } from "../transport.js";
|
|
|
5
5
|
import { MutableSessionEventSource } from "../contract/events.js";
|
|
6
6
|
import { Notifier } from "./notifier.js";
|
|
7
7
|
import { isRemoteFailure } from '@deepseek-ai/dsh-api-gateway/client';
|
|
8
|
+
import { RemoteError } from '@deepseek-ai/dsh-typert-protocol';
|
|
8
9
|
import { ProjectionValueStore } from "./projection-store.js";
|
|
9
10
|
import { resolvedClientTimeZone } from "../time-zone.js";
|
|
10
11
|
import { SessionQueueMirror } from "./queue-mirror.js";
|
|
12
|
+
import { ClientAssistantStream, } from "./assistant-stream.js";
|
|
11
13
|
function projectionsBaseline(value) {
|
|
12
14
|
return {
|
|
13
15
|
...value,
|
|
@@ -44,6 +46,7 @@ export class Session {
|
|
|
44
46
|
jumpPromise = null;
|
|
45
47
|
/** Authoritative stream-only inbox snapshot; pending work never hits history. */
|
|
46
48
|
queueMirror = new SessionQueueMirror();
|
|
49
|
+
assistantStream = new ClientAssistantStream();
|
|
47
50
|
running = false;
|
|
48
51
|
address;
|
|
49
52
|
parentAvailable;
|
|
@@ -143,7 +146,7 @@ export class Session {
|
|
|
143
146
|
: 'transcript',
|
|
144
147
|
time: Date.now(),
|
|
145
148
|
text: input.text,
|
|
146
|
-
|
|
149
|
+
attachments: input.attachments,
|
|
147
150
|
}];
|
|
148
151
|
this.submissionSettlements.set(requestId, { onRetire: input.onRetire, retiring: false });
|
|
149
152
|
// The blank → engaging edge flips here, ahead of prompt(): the composer
|
|
@@ -154,7 +157,7 @@ export class Session {
|
|
|
154
157
|
}
|
|
155
158
|
/**
|
|
156
159
|
* Send (queue/steer passed through 1:1); failures land in the snapshot's promptError.
|
|
157
|
-
* @param content - text
|
|
160
|
+
* @param content - text, browser-owned temporary image uploads, and staged-file receipts.
|
|
158
161
|
* @param mode - queue appends after the current turn; steer interrupts it.
|
|
159
162
|
* @param signal - optional caller cancellation for the complete admission round-trip.
|
|
160
163
|
* @param requestId - identity from {@link beginSubmission}; a failed identified prompt retires its echo.
|
|
@@ -181,13 +184,23 @@ export class Session {
|
|
|
181
184
|
clientTimeZone,
|
|
182
185
|
}, signal);
|
|
183
186
|
}
|
|
187
|
+
else if (content.some(part => part.type === 'file')) {
|
|
188
|
+
result = {
|
|
189
|
+
ok: false,
|
|
190
|
+
error: new RemoteError('subagent/attachment-invalid', 'subagent continuation does not accept files', { reason: 'SUBAGENT_FILE_UNSUPPORTED' }),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
184
193
|
else {
|
|
194
|
+
// The preceding branch rejects file parts before the narrower subagent
|
|
195
|
+
// wire type is used; this array is not filtered or reordered.
|
|
196
|
+
const routedContent = content;
|
|
185
197
|
const routed = await this.remote.subagents.prompt({
|
|
186
198
|
requestId: randomUUID(),
|
|
187
199
|
parentSessionId: this.address.parentSessionId,
|
|
188
200
|
childSessionId: this.address.childSessionId,
|
|
189
201
|
mode: 'continuable',
|
|
190
|
-
|
|
202
|
+
delivery: mode,
|
|
203
|
+
content: routedContent,
|
|
191
204
|
clientTimeZone: resolvedClientTimeZone(),
|
|
192
205
|
}, signal);
|
|
193
206
|
result = routed.ok ? { ok: true, value: { accepted: true } } : routed;
|
|
@@ -553,29 +566,63 @@ export class Session {
|
|
|
553
566
|
acceptEventChange(change) {
|
|
554
567
|
switch (change.type) {
|
|
555
568
|
case 'replace':
|
|
556
|
-
this.installWindow(change.entries, change.hasMore, change.page.projections === undefined ? undefined : projectionsBaseline(change.page.projections));
|
|
569
|
+
this.installWindow(change.entries, change.hasMore, change.page.projections === undefined ? undefined : projectionsBaseline(change.page.projections), change.page.assistantStream);
|
|
557
570
|
return;
|
|
558
571
|
case 'prepend':
|
|
559
572
|
this.prependWindow(change.entries, change.hasMore);
|
|
560
573
|
return;
|
|
561
574
|
case 'append':
|
|
562
|
-
|
|
563
|
-
|
|
575
|
+
this.publishAssistantEntry(this.assistantStream.acceptDurable(change.entry));
|
|
576
|
+
return;
|
|
577
|
+
case 'assistant-stream':
|
|
578
|
+
this.publishAssistantEntry(this.assistantStream.acceptFrame(change.frame));
|
|
564
579
|
}
|
|
565
580
|
}
|
|
566
581
|
/** Replace the complete contiguous window and apply page-owned projection metadata. */
|
|
567
|
-
installWindow(entries, hasMore, projections) {
|
|
582
|
+
installWindow(entries, hasMore, projections, assistantStream) {
|
|
583
|
+
// A durable gap-repair page has no assistant baseline. Clearing transient
|
|
584
|
+
// attempts makes a held notification reopen follow once for an atomic
|
|
585
|
+
// page/baseline pair instead of applying it to an unrelated repair cut.
|
|
586
|
+
const visible = this.assistantStream.replace(entries, assistantStream);
|
|
568
587
|
this.baseSeq = SessionLogOffset(entries[0]?.event.seq ?? 0);
|
|
569
588
|
this.hasMore = hasMore;
|
|
570
|
-
if (
|
|
589
|
+
if (visible.some(entry => entry.event.type === 'turn/start'))
|
|
571
590
|
this.firstPromptPendingTurn = false;
|
|
572
591
|
if (projections !== undefined)
|
|
573
592
|
this.projections.seed(projections);
|
|
574
|
-
this.eventSource.replace(
|
|
575
|
-
for (const entry of
|
|
593
|
+
this.eventSource.replace(visible, hasMore);
|
|
594
|
+
for (const entry of visible)
|
|
576
595
|
this.observeSubmissionEvent(entry.event);
|
|
577
596
|
this.notifier.markDirty();
|
|
578
597
|
}
|
|
598
|
+
publishAssistantEntry(result) {
|
|
599
|
+
if (result?.type === 'rebaseline') {
|
|
600
|
+
const events = this.events;
|
|
601
|
+
queueMicrotask(() => {
|
|
602
|
+
if (events !== undefined && this.events === events)
|
|
603
|
+
events.restart();
|
|
604
|
+
});
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
if (result?.type === 'settlement') {
|
|
608
|
+
this.eventSource.settleAssistant(result.attemptId, result.entry);
|
|
609
|
+
this.observeSubmissionEvent(result.entry.event);
|
|
610
|
+
this.notifier.markDirty();
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
if (result?.type === 'abandonment') {
|
|
614
|
+
this.eventSource.settleAssistant(result.attemptId);
|
|
615
|
+
this.notifier.markDirty();
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
if (result?.type === 'publish' && this.appendLive(result.entry)) {
|
|
619
|
+
this.notifier.markDirty();
|
|
620
|
+
}
|
|
621
|
+
else if (result?.type === 'transient') {
|
|
622
|
+
this.eventSource.append(result.entry);
|
|
623
|
+
this.notifier.markDirty();
|
|
624
|
+
}
|
|
625
|
+
}
|
|
579
626
|
/** Prepend one stream-validated history page. */
|
|
580
627
|
prependWindow(entries, hasMore) {
|
|
581
628
|
this.baseSeq = entries[0] === undefined ? this.baseSeq : SessionLogOffset(entries[0].event.seq);
|
|
@@ -607,7 +654,7 @@ export class Session {
|
|
|
607
654
|
const source = data?.source;
|
|
608
655
|
if (source?.kind !== 'user' || typeof source.rpcId !== 'string')
|
|
609
656
|
return;
|
|
610
|
-
this.scheduleObservedRetirement(source.rpcId,
|
|
657
|
+
this.scheduleObservedRetirement(source.rpcId, attachmentRefsIn(data?.content));
|
|
611
658
|
}
|
|
612
659
|
/** Retire echoes whose prompts landed in the host inbox instead of the log (running-turn submissions). */
|
|
613
660
|
observeSubmissionQueue(items) {
|
|
@@ -615,7 +662,7 @@ export class Session {
|
|
|
615
662
|
return;
|
|
616
663
|
for (const item of items) {
|
|
617
664
|
if (item.rpcId !== undefined) {
|
|
618
|
-
this.scheduleObservedRetirement(item.rpcId,
|
|
665
|
+
this.scheduleObservedRetirement(item.rpcId, attachmentRefsIn(item.message.content));
|
|
619
666
|
}
|
|
620
667
|
}
|
|
621
668
|
}
|
|
@@ -702,8 +749,8 @@ function scheduleFrame(fn) {
|
|
|
702
749
|
else
|
|
703
750
|
setTimeout(fn, 0);
|
|
704
751
|
}
|
|
705
|
-
/**
|
|
706
|
-
function
|
|
752
|
+
/** Attachment references in one structurally-read content block list, in block order. */
|
|
753
|
+
function attachmentRefsIn(content) {
|
|
707
754
|
if (!Array.isArray(content))
|
|
708
755
|
return [];
|
|
709
756
|
const refs = [];
|
|
@@ -711,7 +758,8 @@ function imageRefsIn(content) {
|
|
|
711
758
|
if (typeof block !== 'object' || block === null)
|
|
712
759
|
continue;
|
|
713
760
|
const candidate = block;
|
|
714
|
-
if (candidate.type === 'image'
|
|
761
|
+
if ((candidate.type === 'image' || candidate.type === 'file')
|
|
762
|
+
&& typeof candidate.attachment === 'object' && candidate.attachment !== null) {
|
|
715
763
|
refs.push(candidate.attachment);
|
|
716
764
|
}
|
|
717
765
|
}
|