@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.
Files changed (42) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +16 -6
  3. package/README.zh.md +16 -6
  4. package/lib/client.js +838 -58
  5. package/lib/index.js +376 -243
  6. package/lib/typert.host.js +236 -178
  7. package/lib/typert.remote-client.js +97 -115
  8. package/lib/types/agent.js +6 -8
  9. package/lib/types/assistant-stream.d.ts +26 -0
  10. package/lib/types/assistant-stream.js +83 -0
  11. package/lib/types/client/contract/events.d.ts +38 -7
  12. package/lib/types/client/contract/events.js +21 -0
  13. package/lib/types/client/contract/session.d.ts +7 -7
  14. package/lib/types/client/contract/snapshot.d.ts +15 -2
  15. package/lib/types/client/index.d.ts +2 -2
  16. package/lib/types/client/index.js +2 -0
  17. package/lib/types/client/session-wire-event.d.ts +11 -0
  18. package/lib/types/client/session-wire-event.js +43 -0
  19. package/lib/types/client/sessions/assistant-stream.d.ts +51 -0
  20. package/lib/types/client/sessions/assistant-stream.js +168 -0
  21. package/lib/types/client/sessions/history-records.d.ts +2 -2
  22. package/lib/types/client/sessions/history-records.js +3 -8
  23. package/lib/types/client/sessions/queue-mirror.js +3 -3
  24. package/lib/types/client/sessions/remotes.d.ts +2 -2
  25. package/lib/types/client/sessions/session.d.ts +3 -1
  26. package/lib/types/client/sessions/session.js +63 -15
  27. package/lib/types/client/transport.d.ts +7 -3
  28. package/lib/types/client/transport.js +24 -3
  29. package/lib/types/commands.d.ts +1 -1
  30. package/lib/types/commands.js +112 -32
  31. package/lib/types/control.d.ts +0 -1
  32. package/lib/types/control.js +19 -22
  33. package/lib/types/history.d.ts +2 -1
  34. package/lib/types/history.js +66 -37
  35. package/lib/types/index.d.ts +4 -4
  36. package/lib/types/index.js +15 -5
  37. package/lib/types/list.d.ts +2 -9
  38. package/lib/types/list.js +10 -124
  39. package/lib/types/media-references.d.ts +16 -0
  40. package/lib/types/media-references.js +77 -0
  41. package/lib/types/types.d.ts +85 -35
  42. package/package.json +71 -58
@@ -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 - cold-list observation policy.
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 frames.
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
  /**
@@ -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, DEFAULT_COLD_BLANK_PROBE_MAX_BYTES } from "./list.js";
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 - cold-list observation policy.
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, config.coldBlankProbeMaxBytes ?? DEFAULT_COLD_BLANK_PROBE_MAX_BYTES);
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 frames.
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);
@@ -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
- private readonly coldBlankProbeMaxBytes;
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
- coldBlankProbeMaxBytes;
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 (let offset = 0; offset < cold.length; offset += COLD_SUMMARY_BATCH_SIZE) {
182
- const settled = await Promise.allSettled(cold.slice(offset, offset + COLD_SUMMARY_BATCH_SIZE)
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
- async summarizeCold(header, signal) {
194
- const cached = this.projectionsFor(header, undefined);
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
- : this.ctx.get('sessionProjectionCache')?.cachedSnapshot(header, SessionLogOffset(0))
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
@@ -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/types';
6
- import type { ChunkRow } from '@deepseek-ai/dsh-session/chunk-rows';
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
- /** Browser-submitted prompt content; the Host promotes image bytes to durable references. */
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
- /** v0-compatible Session metadata carried on the browser wire. */
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
- /** Exact inherited prefix length; absent for an unseeded Session. */
369
- readonly seedLength?: number;
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 form of one Session surface operation. */
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 start: number;
378
- readonly end: number;
385
+ readonly startSeq: number;
386
+ readonly endSeq: number;
379
387
  };
380
- /** Event-shaped wire representation of one packed chunk row. */
381
- export type ChunkRowEvent = {
382
- [Kind in ChunkRow['type']]: {
383
- readonly type: `chunkrow/${Kind}`;
384
- readonly seq: number;
385
- readonly time: number;
386
- readonly data: Extract<ChunkRow, {
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
- readonly sourceEventSeqs?: number[];
406
- readonly surfaceOp?: SessionWireSurfaceOp;
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 appended after its cursor. */
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
- } | SessionEventEntry;
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;