@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/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,10 +94,11 @@ 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";
|
|
101
|
+
import { SessionMediaReferences } from "./media-references.js";
|
|
101
102
|
export { ApiSessionNotFound } from "./agent.js";
|
|
102
103
|
export { SessionFileReferences } from "./file-references.js";
|
|
103
104
|
export { SessionSkillCatalog } from "./skill-catalog.js";
|
|
@@ -162,6 +163,7 @@ let SessionController = (() => {
|
|
|
162
163
|
'agentDefaultModel',
|
|
163
164
|
'agents',
|
|
164
165
|
'attachments',
|
|
166
|
+
'fileUploads',
|
|
165
167
|
'llm',
|
|
166
168
|
'sessions',
|
|
167
169
|
'sessionProjections',
|
|
@@ -170,7 +172,6 @@ let SessionController = (() => {
|
|
|
170
172
|
'workspaceRegistry',
|
|
171
173
|
];
|
|
172
174
|
static Config = z.object({
|
|
173
|
-
coldBlankProbeMaxBytes: z.natural().default(DEFAULT_COLD_BLANK_PROBE_MAX_BYTES),
|
|
174
175
|
nativeOpen: z.boolean(),
|
|
175
176
|
});
|
|
176
177
|
agents = __runInitializers(this, _instanceExtraInitializers);
|
|
@@ -183,13 +184,20 @@ let SessionController = (() => {
|
|
|
183
184
|
promotions = new Set();
|
|
184
185
|
/**
|
|
185
186
|
* @param ctx - Host context containing the Session capability assembly.
|
|
186
|
-
* @param config -
|
|
187
|
+
* @param config - native-opener deployment policy.
|
|
188
|
+
* @param internals - host integrations replaceable by direct unit tests.
|
|
187
189
|
*/
|
|
188
190
|
constructor(ctx, config, internals = {}) {
|
|
189
191
|
super(ctx, 'sessionController', { namespace: 'session' });
|
|
190
192
|
installModelSelectionProjection(ctx);
|
|
191
193
|
this.agents = new ApiSessionAgentController(ctx);
|
|
192
194
|
this.commands = new SessionCommandController(ctx, this.agents, process.cwd());
|
|
195
|
+
ctx.effect(() => ctx.fileUploads.registerAgentResolver(async (sessionId) => {
|
|
196
|
+
const result = await this.agents.resolveAgent(sessionId);
|
|
197
|
+
if ('error' in result)
|
|
198
|
+
throw result.error;
|
|
199
|
+
return result.agent;
|
|
200
|
+
}), 'session-controller: file-upload Agent resolver');
|
|
193
201
|
this.controlState = new SessionControlController(ctx);
|
|
194
202
|
// Registered before history so reverse-order teardown closes every
|
|
195
203
|
// follower before waiting for already-admitted promotions.
|
|
@@ -197,11 +205,12 @@ let SessionController = (() => {
|
|
|
197
205
|
await Promise.allSettled([...this.promotions]);
|
|
198
206
|
}, 'session-controller.promotions');
|
|
199
207
|
this.history = new SessionHistoryController(ctx, (observation) => { this.promote(observation); });
|
|
200
|
-
this.listState = new ApiSessionList(ctx
|
|
208
|
+
this.listState = new ApiSessionList(ctx);
|
|
201
209
|
this.openPath = internals.openPath ?? openNativePath;
|
|
202
210
|
this.canOpenPath = internals.canOpenPath
|
|
203
211
|
?? (() => config.nativeOpen ?? (internals.openPath !== undefined || canOpenNativePath()));
|
|
204
212
|
ctx.plugin(SessionFileReferences);
|
|
213
|
+
ctx.plugin(SessionMediaReferences);
|
|
205
214
|
ctx.plugin(SessionSkillCatalog);
|
|
206
215
|
ctx.on('session/created', (session) => {
|
|
207
216
|
ctx.emit('api-session/added', this.listState.summaryFor(session));
|
|
@@ -407,7 +416,8 @@ let SessionController = (() => {
|
|
|
407
416
|
* Follow one Session log from its opening or resume cursor.
|
|
408
417
|
* @param request - durable address and last committed sequence already held by the caller.
|
|
409
418
|
* @param signal - cancellation owned by the Remote stream carrier.
|
|
410
|
-
* @returns a complete opening snapshot followed by gap-free event
|
|
419
|
+
* @returns a complete opening snapshot followed by gap-free durable event
|
|
420
|
+
* frames and optional cursorless assistant-stream frames.
|
|
411
421
|
*/
|
|
412
422
|
follow(request, signal) {
|
|
413
423
|
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.
|
package/lib/types/list.js
CHANGED
|
@@ -1,65 +1,9 @@
|
|
|
1
1
|
/** Cold-safe Session list and search projection. */
|
|
2
|
-
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
3
|
-
if (value !== null && value !== void 0) {
|
|
4
|
-
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
5
|
-
var dispose, inner;
|
|
6
|
-
if (async) {
|
|
7
|
-
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
8
|
-
dispose = value[Symbol.asyncDispose];
|
|
9
|
-
}
|
|
10
|
-
if (dispose === void 0) {
|
|
11
|
-
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
12
|
-
dispose = value[Symbol.dispose];
|
|
13
|
-
if (async) inner = dispose;
|
|
14
|
-
}
|
|
15
|
-
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
16
|
-
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
17
|
-
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
18
|
-
}
|
|
19
|
-
else if (async) {
|
|
20
|
-
env.stack.push({ async: true });
|
|
21
|
-
}
|
|
22
|
-
return value;
|
|
23
|
-
};
|
|
24
|
-
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
25
|
-
return function (env) {
|
|
26
|
-
function fail(e) {
|
|
27
|
-
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
28
|
-
env.hasError = true;
|
|
29
|
-
}
|
|
30
|
-
var r, s = 0;
|
|
31
|
-
function next() {
|
|
32
|
-
while (r = env.stack.pop()) {
|
|
33
|
-
try {
|
|
34
|
-
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
35
|
-
if (r.dispose) {
|
|
36
|
-
var result = r.dispose.call(r.value);
|
|
37
|
-
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
38
|
-
}
|
|
39
|
-
else s |= 1;
|
|
40
|
-
}
|
|
41
|
-
catch (e) {
|
|
42
|
-
fail(e);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
46
|
-
if (env.hasError) throw env.error;
|
|
47
|
-
}
|
|
48
|
-
return next();
|
|
49
|
-
};
|
|
50
|
-
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
51
|
-
var e = new Error(message);
|
|
52
|
-
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
53
|
-
});
|
|
54
|
-
import { stat } from 'node:fs/promises';
|
|
55
2
|
import { SessionLogOffset } from '@deepseek-ai/dsh-session';
|
|
56
3
|
import { SessionQueryError } from '@deepseek-ai/dsh-session-query';
|
|
57
4
|
import { RemoteError } from '@deepseek-ai/dsh-typert-protocol';
|
|
58
5
|
import { z } from 'zod';
|
|
59
6
|
import { SESSION_SEARCH_RESULT_LIMIT, SESSION_SEARCH_SNIPPET_MAX_CODE_POINTS, } from "./types.js";
|
|
60
|
-
/** Default maximum artifact size eligible for one cold projection observation. */
|
|
61
|
-
export const DEFAULT_COLD_BLANK_PROBE_MAX_BYTES = 1024;
|
|
62
|
-
const COLD_SUMMARY_BATCH_SIZE = 16;
|
|
63
7
|
const SEARCH_PROVIDER_CALL_LIMIT = 100;
|
|
64
8
|
const SESSION_SEARCH_QUERY_MAX_CHARS = 500;
|
|
65
9
|
const MESSAGE_TYPES = new Set(['user/message', 'assistant/message']);
|
|
@@ -110,14 +54,9 @@ export function truncateUnicodeCodePoints(value, maximum) {
|
|
|
110
54
|
/** Owns list projection registration, bounded cold summaries, and authorized search. */
|
|
111
55
|
export class ApiSessionList {
|
|
112
56
|
ctx;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
* @param ctx - Host context carrying Session, query, persistence, and projection services.
|
|
116
|
-
* @param coldBlankProbeMaxBytes - maximum physical artifact size eligible for a full observation.
|
|
117
|
-
*/
|
|
118
|
-
constructor(ctx, coldBlankProbeMaxBytes) {
|
|
57
|
+
/** @param ctx - Host context carrying Session, query, persistence, and projection services. */
|
|
58
|
+
constructor(ctx) {
|
|
119
59
|
this.ctx = ctx;
|
|
120
|
-
this.coldBlankProbeMaxBytes = coldBlankProbeMaxBytes;
|
|
121
60
|
ctx.sessionProjections.register({
|
|
122
61
|
key: 'sessionListMetadata',
|
|
123
62
|
stateSchema: sessionListMetadataSchema,
|
|
@@ -178,79 +117,24 @@ export class ApiSessionList {
|
|
|
178
117
|
continue;
|
|
179
118
|
cold.push(record.header);
|
|
180
119
|
}
|
|
181
|
-
for (
|
|
182
|
-
|
|
183
|
-
.map(header => this.summarizeCold(header, signal)));
|
|
184
|
-
for (const result of settled) {
|
|
185
|
-
if (result.status === 'rejected')
|
|
186
|
-
throw result.reason;
|
|
187
|
-
items.push(result.value);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
120
|
+
for (const header of cold)
|
|
121
|
+
items.push(this.summarizeCold(header));
|
|
190
122
|
items.sort((left, right) => right.updatedAt - left.updatedAt);
|
|
191
123
|
return items;
|
|
192
124
|
}
|
|
193
|
-
|
|
194
|
-
const
|
|
195
|
-
const projections = cached?.values.sessionListMetadata?.blank === false
|
|
196
|
-
? cached
|
|
197
|
-
: await this.probeSmallCold(header, signal) ?? cached;
|
|
198
|
-
const raced = this.ctx.sessions.get(header.id);
|
|
199
|
-
if (raced !== undefined)
|
|
200
|
-
return this.summaryFor(raced);
|
|
125
|
+
summarizeCold(header) {
|
|
126
|
+
const projections = this.projectionsFor(header, undefined);
|
|
201
127
|
const metadata = projections?.values.sessionListMetadata;
|
|
202
128
|
return {
|
|
203
129
|
sessionId: header.id,
|
|
204
130
|
updatedAt: updatedAt(header, metadata),
|
|
205
131
|
running: false,
|
|
206
|
-
// A large or inaccessible cache miss remains unknown and visible.
|
|
132
|
+
// A large, metadata-less, or inaccessible cache miss remains unknown and visible.
|
|
207
133
|
blank: metadata?.blank ?? false,
|
|
208
134
|
...listFields(header),
|
|
209
135
|
...(projections === undefined ? {} : { projections }),
|
|
210
136
|
};
|
|
211
137
|
}
|
|
212
|
-
async probeSmallCold(header, signal) {
|
|
213
|
-
if (this.coldBlankProbeMaxBytes === 0)
|
|
214
|
-
return undefined;
|
|
215
|
-
const persistence = this.ctx.get('sessionPersistence');
|
|
216
|
-
const location = persistence?.locate(header);
|
|
217
|
-
if (location === undefined)
|
|
218
|
-
return undefined;
|
|
219
|
-
signal?.throwIfAborted();
|
|
220
|
-
try {
|
|
221
|
-
if ((await stat(location.path)).size > this.coldBlankProbeMaxBytes)
|
|
222
|
-
return undefined;
|
|
223
|
-
}
|
|
224
|
-
catch {
|
|
225
|
-
signal?.throwIfAborted();
|
|
226
|
-
return undefined;
|
|
227
|
-
}
|
|
228
|
-
try {
|
|
229
|
-
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
230
|
-
try {
|
|
231
|
-
const observation = __addDisposableResource(env_1, await this.ctx.sessionQuery.observeSession(header.id, {
|
|
232
|
-
...(signal === undefined ? {} : { signal }),
|
|
233
|
-
projectionMode: 'all',
|
|
234
|
-
}), false);
|
|
235
|
-
const block = observation.projections;
|
|
236
|
-
return block === undefined
|
|
237
|
-
? undefined
|
|
238
|
-
: { asOfSeq: block.asOfSeq, values: block.values };
|
|
239
|
-
}
|
|
240
|
-
catch (e_1) {
|
|
241
|
-
env_1.error = e_1;
|
|
242
|
-
env_1.hasError = true;
|
|
243
|
-
}
|
|
244
|
-
finally {
|
|
245
|
-
__disposeResources(env_1);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
catch (error) {
|
|
249
|
-
signal?.throwIfAborted();
|
|
250
|
-
this.ctx.logger.warn(`api-session.list: small cold observation for "${header.id}" failed; serving it as visible: ${String(error)}`);
|
|
251
|
-
return undefined;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
138
|
/**
|
|
255
139
|
* Search current visible message content without activating any matching Session.
|
|
256
140
|
* @param query - literal message-content query.
|
|
@@ -362,10 +246,12 @@ export class ApiSessionList {
|
|
|
362
246
|
}
|
|
363
247
|
projectionsFor(header, session) {
|
|
364
248
|
try {
|
|
249
|
+
const cache = this.ctx.get('sessionProjectionCache');
|
|
365
250
|
const block = session === undefined
|
|
366
251
|
? header.isSeeded
|
|
367
252
|
? undefined
|
|
368
|
-
:
|
|
253
|
+
: cache?.cachedSnapshot(header, SessionLogOffset(0))
|
|
254
|
+
?? cache?.cachedPredecessorTitle(header, SessionLogOffset(0))
|
|
369
255
|
: this.ctx.sessionProjections.cachedSnapshot(session);
|
|
370
256
|
return block !== undefined && Object.keys(block.values).length > 0
|
|
371
257
|
? {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authenticated GET/HEAD /api/file reads bounded file responses through
|
|
3
|
+
* the composed filesystem provider. Paths and MIME types do not restrict access;
|
|
4
|
+
* the connection service authenticates requests before this handler.
|
|
5
|
+
* @module @deepseek-ai/dsh-api-session-controller/media-references
|
|
6
|
+
*/
|
|
7
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
8
|
+
/**
|
|
9
|
+
* File-display contribution. The connection service supplies authentication;
|
|
10
|
+
* `ctx.fs` supplies the execution world's paths, reads, and access policy.
|
|
11
|
+
*/
|
|
12
|
+
export declare const SessionMediaReferences: {
|
|
13
|
+
inject: string[];
|
|
14
|
+
apply(ctx: Context): void;
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=media-references.d.ts.map
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authenticated GET/HEAD /api/file reads bounded file responses through
|
|
3
|
+
* the composed filesystem provider. Paths and MIME types do not restrict access;
|
|
4
|
+
* the connection service authenticates requests before this handler.
|
|
5
|
+
* @module @deepseek-ai/dsh-api-session-controller/media-references
|
|
6
|
+
*/
|
|
7
|
+
import { isAbsolute } from 'node:path';
|
|
8
|
+
import { FsError } from '@deepseek-ai/dsh-fs';
|
|
9
|
+
import mime from 'mime-types';
|
|
10
|
+
const BASE_HEADERS = {
|
|
11
|
+
'Cache-Control': 'private, no-store',
|
|
12
|
+
'X-Content-Type-Options': 'nosniff',
|
|
13
|
+
// HTML and SVG files may be opened directly on the authenticated API origin.
|
|
14
|
+
'Content-Security-Policy': "sandbox; default-src 'none'",
|
|
15
|
+
};
|
|
16
|
+
async function serveFile(request, fs, maxBytes) {
|
|
17
|
+
const fail = (status, text) => new Response(request.method === 'HEAD' ? null : text, { status, headers: BASE_HEADERS });
|
|
18
|
+
const path = new URL(request.url).searchParams.get('path');
|
|
19
|
+
if (path === null || path.length === 0)
|
|
20
|
+
return fail(400, 'missing path');
|
|
21
|
+
if (path.includes('\0') || !isAbsolute(path))
|
|
22
|
+
return fail(400, 'absolute path required');
|
|
23
|
+
try {
|
|
24
|
+
const target = await fs.resolve(path, { signal: request.signal });
|
|
25
|
+
const mediaType = mime.lookup(target.displayPath) || 'application/octet-stream';
|
|
26
|
+
const headers = {
|
|
27
|
+
...BASE_HEADERS,
|
|
28
|
+
'Content-Type': mediaType,
|
|
29
|
+
};
|
|
30
|
+
if (request.method === 'HEAD') {
|
|
31
|
+
const info = await fs.stat(target, request.signal);
|
|
32
|
+
if (info === undefined)
|
|
33
|
+
return fail(404, 'not found');
|
|
34
|
+
if (info.type !== 'file')
|
|
35
|
+
return fail(403, 'not a regular file');
|
|
36
|
+
if (info.size !== undefined) {
|
|
37
|
+
if (info.size > maxBytes)
|
|
38
|
+
return fail(413, 'file exceeds byte limit');
|
|
39
|
+
headers['Content-Length'] = String(info.size);
|
|
40
|
+
}
|
|
41
|
+
return new Response(null, { headers });
|
|
42
|
+
}
|
|
43
|
+
const bytes = await fs.readBytes(target, request.signal, maxBytes);
|
|
44
|
+
headers['Content-Length'] = String(bytes.byteLength);
|
|
45
|
+
return new Response(bytes.slice(), { headers });
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
if (!(error instanceof FsError))
|
|
49
|
+
throw error;
|
|
50
|
+
const statuses = {
|
|
51
|
+
FS_NOT_FOUND: 404,
|
|
52
|
+
FS_NOT_REGULAR_FILE: 403,
|
|
53
|
+
FS_PERMISSION_DENIED: 403,
|
|
54
|
+
FS_SANDBOX_DENIED: 403,
|
|
55
|
+
FS_TOO_LARGE: 413,
|
|
56
|
+
FS_ABORTED: 499,
|
|
57
|
+
};
|
|
58
|
+
return fail(statuses[error.code] ?? 500, error.code);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* File-display contribution. The connection service supplies authentication;
|
|
63
|
+
* `ctx.fs` supplies the execution world's paths, reads, and access policy.
|
|
64
|
+
*/
|
|
65
|
+
export const SessionMediaReferences = {
|
|
66
|
+
inject: ['connection', 'fs', 'attachments'],
|
|
67
|
+
apply(ctx) {
|
|
68
|
+
const maxBytes = ctx.attachments.imageLimits.maxImageBytes;
|
|
69
|
+
ctx.effect(() => ctx.connection.fetch.register({
|
|
70
|
+
path: '/api/file',
|
|
71
|
+
methods: ['GET', 'HEAD'],
|
|
72
|
+
requestBody: 'buffered',
|
|
73
|
+
fetch: request => serveFile(request, ctx.fs, maxBytes),
|
|
74
|
+
}), 'session-controller: /api/file');
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
//# sourceMappingURL=media-references.js.map
|
package/lib/types/types.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/** Browser-safe request, result, and lifecycle vocabulary for the Session Remote service. */
|
|
2
2
|
import type { AttachmentIdType, ImageAttachmentLimits, ImageAttachmentRef, ImageMediaType } from '@deepseek-ai/dsh-attachment';
|
|
3
3
|
import type { Branded } from '@deepseek-ai/dsh-brand';
|
|
4
|
-
import type { MessageId } from '@deepseek-ai/dsh-llm/brand';
|
|
5
|
-
import type { ContentBlock } from '@deepseek-ai/dsh-llm
|
|
6
|
-
import type {
|
|
7
|
-
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
4
|
+
import type { LlmAttemptId, MessageId } from '@deepseek-ai/dsh-llm/brand';
|
|
5
|
+
import type { ContentBlock } from '@deepseek-ai/dsh-llm';
|
|
6
|
+
import type { SessionId, SessionSeqCursor } from '@deepseek-ai/dsh-session/types';
|
|
8
7
|
import type { SessionProjectionMap } from '@deepseek-ai/dsh-session-projection/types';
|
|
9
8
|
import type { JobId } from '@deepseek-ai/dsh-jobs/brand';
|
|
10
9
|
import type { JsonValue } from '@deepseek-ai/dsh-util-values';
|
|
@@ -57,7 +56,11 @@ export interface SessionProjectionBaseline {
|
|
|
57
56
|
}
|
|
58
57
|
/** Typed known projections plus JSON-safe values contributed outside this compilation face. */
|
|
59
58
|
export type SessionProjectionValues = Partial<SessionProjectionMap> & Readonly<Record<string, SessionProjectionValue>>;
|
|
60
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* Browser-submitted prompt content; the Host promotes image bytes to durable
|
|
61
|
+
* references. File parts carry the opaque receipt returned by a preceding
|
|
62
|
+
* `uploadFile` call on the same Session.
|
|
63
|
+
*/
|
|
61
64
|
export type PromptContentPart = {
|
|
62
65
|
readonly type: 'text';
|
|
63
66
|
readonly text: string;
|
|
@@ -66,6 +69,9 @@ export type PromptContentPart = {
|
|
|
66
69
|
readonly mediaType: ImageMediaType;
|
|
67
70
|
readonly data: string;
|
|
68
71
|
readonly name?: string;
|
|
72
|
+
} | {
|
|
73
|
+
readonly type: 'file';
|
|
74
|
+
readonly receiptId: Branded<'file-upload-receipt-id'>;
|
|
69
75
|
};
|
|
70
76
|
/** Complete model selection for one Session. */
|
|
71
77
|
export interface ModelSelection {
|
|
@@ -128,6 +134,7 @@ export interface ModelCatalog {
|
|
|
128
134
|
/** One client-requested mutation of a still-pending queue item. */
|
|
129
135
|
export type QueueAction = {
|
|
130
136
|
readonly kind: 'edit';
|
|
137
|
+
/** Non-empty text-only replacement content. */
|
|
131
138
|
readonly content: readonly ContentBlock[];
|
|
132
139
|
} | {
|
|
133
140
|
readonly kind: 'remove';
|
|
@@ -287,6 +294,7 @@ export interface SessionPromptRequest {
|
|
|
287
294
|
readonly requestId: SessionRequestId;
|
|
288
295
|
readonly sessionId: SessionId;
|
|
289
296
|
readonly mode: 'queue' | 'steer';
|
|
297
|
+
/** At least one non-whitespace text part or attachment. */
|
|
290
298
|
readonly content: readonly PromptContentPart[];
|
|
291
299
|
readonly clientTimeZone?: string;
|
|
292
300
|
}
|
|
@@ -358,52 +366,42 @@ export interface SessionEventEntry {
|
|
|
358
366
|
readonly type: 'event';
|
|
359
367
|
readonly event: SessionWireEvent;
|
|
360
368
|
}
|
|
361
|
-
/**
|
|
369
|
+
/** Current logical Session metadata carried on the browser wire. */
|
|
362
370
|
export interface SessionWireHeader {
|
|
363
371
|
readonly version: number;
|
|
364
372
|
readonly id: SessionId;
|
|
365
373
|
readonly createdAt: number;
|
|
366
374
|
readonly cwd?: string;
|
|
367
375
|
readonly parentSession?: SessionId;
|
|
368
|
-
/**
|
|
369
|
-
readonly
|
|
376
|
+
/** Whether the Session contains a fork-inherited prefix. */
|
|
377
|
+
readonly isSeeded: boolean;
|
|
370
378
|
readonly origin?: 'subagent';
|
|
371
379
|
readonly delegationDepth?: number;
|
|
372
380
|
readonly agentPreset?: string;
|
|
373
381
|
}
|
|
374
|
-
/** Browser wire
|
|
382
|
+
/** Browser wire surface operation; replacement endpoints are earlier event seqs in surface order. */
|
|
375
383
|
export type SessionWireSurfaceOp = 'append' | {
|
|
376
384
|
readonly op: 'replace';
|
|
377
|
-
readonly
|
|
378
|
-
readonly
|
|
385
|
+
readonly startSeq: number;
|
|
386
|
+
readonly endSeq: number;
|
|
379
387
|
};
|
|
380
|
-
/**
|
|
381
|
-
export type
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
readonly type: Kind;
|
|
388
|
-
}>['data'];
|
|
389
|
-
};
|
|
390
|
-
}[ChunkRow['type']];
|
|
391
|
-
/** One lossless run of consecutive Assistant delta events in a history page. */
|
|
392
|
-
export interface SessionChunkRun {
|
|
393
|
-
readonly type: 'chunks';
|
|
394
|
-
readonly event: ChunkRowEvent;
|
|
395
|
-
}
|
|
396
|
-
/** One history-page record: a raw event or a packed Assistant delta run. */
|
|
397
|
-
export type SessionHistoryRecord = SessionEventEntry | SessionChunkRun;
|
|
398
|
-
/** Session event wire form; durable readers own recognition of merge-extensible event names. */
|
|
388
|
+
/** One history-page record with compact Assistant streams embedded inside events. */
|
|
389
|
+
export type SessionHistoryRecord = SessionEventEntry;
|
|
390
|
+
/**
|
|
391
|
+
* Exact Session event envelope accepted by the Client journal adapter.
|
|
392
|
+
* Surface events require surfaceOp; only non-Assistant surface events may cite earlier sources.
|
|
393
|
+
* Durable readers own recognition of merge-extensible event names.
|
|
394
|
+
*/
|
|
399
395
|
export interface SessionWireEvent {
|
|
400
396
|
readonly type: string;
|
|
401
397
|
readonly seq: number;
|
|
402
398
|
readonly time: number;
|
|
403
399
|
readonly data: JsonValue;
|
|
404
400
|
readonly ignorable?: true;
|
|
405
|
-
|
|
406
|
-
readonly
|
|
401
|
+
/** Earlier sources on current surface events; opaque JSON on unknown ignorable events. */
|
|
402
|
+
readonly sourceEventSeqs?: JsonValue;
|
|
403
|
+
/** Canonical placement on current surface events; opaque JSON on unknown ignorable events. */
|
|
404
|
+
readonly surfaceOp?: JsonValue;
|
|
407
405
|
}
|
|
408
406
|
/** One message-aligned backwards-history request. */
|
|
409
407
|
export interface SessionPageRequest {
|
|
@@ -417,13 +415,61 @@ export interface SessionPageRequest {
|
|
|
417
415
|
export interface SessionFollowRequest {
|
|
418
416
|
readonly address: SessionAddress;
|
|
419
417
|
readonly maxMessages?: number;
|
|
420
|
-
|
|
418
|
+
/** Include process-local assistant presentation frames for the Web client. */
|
|
419
|
+
readonly assistantStream?: true;
|
|
420
|
+
}
|
|
421
|
+
/** One active assistant attempt in a reconnect opening snapshot. */
|
|
422
|
+
export interface SessionAssistantStreamAttempt {
|
|
423
|
+
readonly attemptId: LlmAttemptId;
|
|
424
|
+
/** Last durable Session seq observed when this attempt started. */
|
|
425
|
+
readonly startedAfterSeq: SessionSeqCursor;
|
|
426
|
+
readonly turn: number;
|
|
427
|
+
readonly step: number;
|
|
428
|
+
/** Dense position expected for the next live chunk frame. */
|
|
429
|
+
readonly nextIndex: number;
|
|
430
|
+
/** Compact detached stream accumulated at this opening revision. */
|
|
431
|
+
readonly stream: readonly JsonValue[];
|
|
432
|
+
}
|
|
433
|
+
/** Complete process-local assistant state at one follow opening. */
|
|
434
|
+
export interface SessionAssistantStreamBaseline {
|
|
435
|
+
readonly revision: number;
|
|
436
|
+
readonly activeAttempt?: SessionAssistantStreamAttempt;
|
|
437
|
+
}
|
|
438
|
+
/** Browser wire form of one process-local assistant frame. */
|
|
439
|
+
export type SessionAssistantStreamFrame = {
|
|
440
|
+
readonly type: 'start';
|
|
441
|
+
readonly attemptId: LlmAttemptId;
|
|
442
|
+
readonly revision: number;
|
|
443
|
+
readonly startedAfterSeq: SessionSeqCursor;
|
|
444
|
+
readonly turn: number;
|
|
445
|
+
readonly step: number;
|
|
446
|
+
} | {
|
|
447
|
+
readonly type: 'chunk';
|
|
448
|
+
readonly attemptId: LlmAttemptId;
|
|
449
|
+
readonly revision: number;
|
|
450
|
+
readonly index: number;
|
|
451
|
+
readonly time: number;
|
|
452
|
+
readonly chunk: JsonValue;
|
|
453
|
+
} | {
|
|
454
|
+
readonly type: 'end';
|
|
455
|
+
readonly attemptId: LlmAttemptId;
|
|
456
|
+
readonly revision: number;
|
|
457
|
+
/** Number of chunk frames represented by this terminal marker. */
|
|
458
|
+
readonly index: number;
|
|
459
|
+
readonly outcome: {
|
|
460
|
+
readonly kind: 'committed';
|
|
461
|
+
readonly eventType: 'assistant/message' | 'assistant/attempt';
|
|
462
|
+
readonly seq: number;
|
|
463
|
+
} | {
|
|
464
|
+
readonly kind: 'abandoned';
|
|
465
|
+
};
|
|
466
|
+
};
|
|
421
467
|
/** One contiguous backwards page of a Session log. */
|
|
422
468
|
export interface SessionPage {
|
|
423
469
|
readonly records: readonly SessionHistoryRecord[];
|
|
424
470
|
readonly hasMore: boolean;
|
|
425
471
|
}
|
|
426
|
-
/** Complete opening window followed by ordered events
|
|
472
|
+
/** Complete opening window followed by ordered durable events and opted-in assistant frames. */
|
|
427
473
|
export type SessionFollowFrame = {
|
|
428
474
|
readonly type: 'snapshot';
|
|
429
475
|
readonly header: SessionWireHeader;
|
|
@@ -431,7 +477,11 @@ export type SessionFollowFrame = {
|
|
|
431
477
|
readonly records: readonly SessionHistoryRecord[];
|
|
432
478
|
readonly hasMore: boolean;
|
|
433
479
|
readonly projections: SessionProjectionBaseline;
|
|
434
|
-
|
|
480
|
+
readonly assistantStream?: SessionAssistantStreamBaseline;
|
|
481
|
+
} | SessionEventEntry | {
|
|
482
|
+
readonly type: 'assistant-stream';
|
|
483
|
+
readonly frame: SessionAssistantStreamFrame;
|
|
484
|
+
};
|
|
435
485
|
/** One pending inbox occurrence in the authoritative queue snapshot. */
|
|
436
486
|
export interface SessionQueuedItem {
|
|
437
487
|
readonly id: MessageId;
|