@deepseek-ai/dsh-api-session-controller 0.2.1-alpha.2 → 0.2.1-alpha.4

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.
@@ -21,12 +21,38 @@ export declare class ApiSessionCwdConflict extends Error {
21
21
  readonly existingCwd: string | undefined;
22
22
  constructor(sessionId: SessionId, requestedCwd: string, existingCwd: string | undefined);
23
23
  }
24
- /** Explicit-id creation attempted to adopt a Session under another preset. */
24
+ /** Reuse of a Session named an Agent preset while the deployment composes no preset roster. */
25
25
  export declare class ApiSessionPresetConflict extends Error {
26
26
  readonly sessionId: SessionId;
27
27
  readonly requestedPreset: string;
28
- readonly existingPreset: string | undefined;
29
- constructor(sessionId: SessionId, requestedPreset: string, existingPreset: string | undefined);
28
+ constructor(sessionId: SessionId, requestedPreset: string);
29
+ }
30
+ /** Placement and Agent preset for one Session created for a new conversation. */
31
+ export interface ApiSessionCreateRequest {
32
+ /** Blank Session to reuse, or the identity of a new Session when none is stored under it. */
33
+ readonly sessionId?: SessionId;
34
+ /** Directory the Session owns. */
35
+ readonly cwd: string;
36
+ /** Preset the Session runs; the configured default when omitted. */
37
+ readonly agentPreset?: string;
38
+ /**
39
+ * Placement rule the caller owns, such as Workspace membership and archive state.
40
+ * @param sessionId - existing Session named by the request.
41
+ * @returns whether that Session may be reused.
42
+ */
43
+ readonly reusable: (sessionId: SessionId) => boolean;
44
+ }
45
+ /** The Session one creation request created or reused. */
46
+ export interface ApiSessionCreated {
47
+ /** Live Agent of the Session. */
48
+ readonly agent: Agent;
49
+ /** Preset the Session ran when the request finished, before a later request for the same identity; absent without a roster. */
50
+ readonly agentPreset: string | undefined;
51
+ }
52
+ /** Resolved preset and pre-publication Agent setup for one create or resume. */
53
+ interface ApiSessionComposition {
54
+ readonly agentPreset?: string;
55
+ readonly setup: AgentSetup;
30
56
  }
31
57
  /** Failures produced while resolving one ordinary Session identity to its live Agent. */
32
58
  export type ApiSessionAgentError = RemoteError<'session/not-found' | 'session/agent-busy' | 'session/writer-held' | 'gateway/internal'>;
@@ -66,7 +92,7 @@ export declare function inspectApiSession(ctx: Context, sessionId: SessionId, si
66
92
  export declare class ApiSessionAgentController {
67
93
  private readonly ctx;
68
94
  private readonly resumes;
69
- private readonly creations;
95
+ private readonly claims;
70
96
  private readonly selections;
71
97
  private readonly imageAdmissionChains;
72
98
  /** @param ctx - Host context carrying Agent, model, persistence, and Typert services. */
@@ -85,14 +111,23 @@ export declare class ApiSessionAgentController {
85
111
  resolveObservedAgent(observation: SessionObservation): Promise<ApiSessionAgentResult>;
86
112
  private resolve;
87
113
  /**
88
- * Resolve one requested identity, creating or resuming it once.
89
- * @param sessionId - requested Session identity.
90
- * @param cwd - directory the Session must own.
91
- * @param checkPersistedIdentity - whether to inspect a cold identity before creation.
92
- * @param presetId - optional Agent preset the Session must own.
93
- * @returns the matching live ordinary Agent.
114
+ * Create a Session for a new conversation, reusing the named blank Session
115
+ * while it is still reusable. A reused Session runs the requested preset, or
116
+ * the configured default when the request names none; the preset it recorded
117
+ * is not resolved, and a change is logged as `agent-preset/selected` before
118
+ * the Agent is published. A named Session that has started a turn, fails the
119
+ * caller's placement rule, or is written by another owner stays unchanged,
120
+ * and a new Session is created instead. Requests naming one identity run in
121
+ * arrival order, including their preset resolution, and check the named
122
+ * Session's directory and ownership before resolving the preset.
123
+ * @param request - optional Session to reuse, directory, preset, and placement rule.
124
+ * @returns the reused or created Session and the preset it ran when this request finished.
125
+ * @throws ApiSessionCwdConflict when the named Session belongs to another directory.
126
+ * @throws ApiSessionSubagentOwnership when subagent routing owns the named Session.
127
+ * @throws RemoteError `agent-preset/not-found` or `agent-preset/invalid` for the requested or default preset.
128
+ * @throws ApiSessionPresetConflict when no roster is composed and the request names a preset for a reused Session.
94
129
  */
95
- ensureSession(sessionId: SessionId, cwd: string, checkPersistedIdentity: boolean, presetId?: string): Promise<Agent>;
130
+ createSession(request: ApiSessionCreateRequest): Promise<ApiSessionCreated>;
96
131
  /**
97
132
  * Install or return the Session-local model selection used by prompt assembly.
98
133
  * @param agent - live Agent that owns the selection.
@@ -132,14 +167,24 @@ export declare class ApiSessionAgentController {
132
167
  * @param presetId - requested preset or the configured default when omitted.
133
168
  * @returns the resolved preset identity and Agent setup callback.
134
169
  */
135
- composeAgent(presetId: string | undefined): Promise<{
136
- readonly agentPreset?: string;
137
- readonly setup: AgentSetup;
138
- }>;
170
+ composeAgent(presetId: string | undefined): Promise<ApiSessionComposition>;
139
171
  private liveAgent;
140
172
  private resume;
141
173
  private resumeObserved;
142
- private createOrAdopt;
174
+ private inOrder;
175
+ /** @returns the reused Session, a new Session under `sessionId` when none is stored, or a new Session under a minted identity. */
176
+ private claim;
177
+ /** @returns the live blank Session under the requested preset, or undefined when it cannot be reused. */
178
+ private reuseLive;
179
+ /** Setup that re-checks blankness under the acquired writer, then records a preset change before publication. */
180
+ private reuseSetup;
181
+ /** Whether no turn has started, by the predicate the preset registry's `select` enforces. */
182
+ private isBlank;
183
+ /** Without a roster, reuse cannot apply a named preset. */
184
+ private assertPresetApplicable;
185
+ private created;
186
+ private createMinted;
187
+ private createAgent;
143
188
  private agentOptions;
144
189
  private installSelection;
145
190
  /**
@@ -148,7 +193,6 @@ export declare class ApiSessionAgentController {
148
193
  * @returns the current preset, or undefined when the capability is absent.
149
194
  */
150
195
  presetForObservation(observation: SessionObservation): string | undefined;
151
- private assertPresetUnchanged;
152
196
  }
153
197
  export {};
154
198
  //# sourceMappingURL=agent.d.ts.map
@@ -51,11 +51,13 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
51
51
  var e = new Error(message);
52
52
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
53
53
  });
54
+ import { randomUUID } from 'node:crypto';
54
55
  import { mkdir } from 'node:fs/promises';
55
56
  import { installModelSelection } from '@deepseek-ai/dsh-agent';
57
+ import { brandString } from '@deepseek-ai/dsh-brand';
56
58
  import { ReasoningEffortId } from '@deepseek-ai/dsh-llm';
57
59
  import { SessionQueryError } from '@deepseek-ai/dsh-session-query';
58
- import { RemoteError } from '@deepseek-ai/dsh-typert-protocol';
60
+ import { RemoteError, remoteErrorOf } from '@deepseek-ai/dsh-typert-protocol';
59
61
  /** Cold Session identity absent from persistence. */
60
62
  export class ApiSessionNotFound extends Error {
61
63
  }
@@ -82,20 +84,19 @@ export class ApiSessionCwdConflict extends Error {
82
84
  this.existingCwd = existingCwd;
83
85
  }
84
86
  }
85
- /** Explicit-id creation attempted to adopt a Session under another preset. */
87
+ /** Reuse of a Session named an Agent preset while the deployment composes no preset roster. */
86
88
  export class ApiSessionPresetConflict extends Error {
87
89
  sessionId;
88
90
  requestedPreset;
89
- existingPreset;
90
- constructor(sessionId, requestedPreset, existingPreset) {
91
- super(existingPreset === undefined
92
- ? `session "${sessionId}" records no agent preset and cannot be adopted under "${requestedPreset}"`
93
- : `session "${sessionId}" runs agent preset "${existingPreset}", not "${requestedPreset}"`);
91
+ constructor(sessionId, requestedPreset) {
92
+ super(`the deployment composes no agent preset roster, so session "${sessionId}" cannot run preset "${requestedPreset}"`);
94
93
  this.sessionId = sessionId;
95
94
  this.requestedPreset = requestedPreset;
96
- this.existingPreset = existingPreset;
97
95
  }
98
96
  }
97
+ /** A blank Session chosen for reuse started a turn before this Host acquired its writer. */
98
+ class ApiSessionStartedSinceObserved extends Error {
99
+ }
99
100
  /**
100
101
  * Test whether generic Session routing must leave an identity to subagent routing.
101
102
  * @param ctx - Host context carrying the Agent ownership registry.
@@ -164,7 +165,7 @@ export async function inspectApiSession(ctx, sessionId, signal) {
164
165
  export class ApiSessionAgentController {
165
166
  ctx;
166
167
  resumes = new Map();
167
- creations = new Map();
168
+ claims = new Map();
168
169
  selections = new WeakMap();
169
170
  imageAdmissionChains = new WeakMap();
170
171
  /** @param ctx - Host context carrying Agent, model, persistence, and Typert services. */
@@ -248,45 +249,27 @@ export class ApiSessionAgentController {
248
249
  }
249
250
  }
250
251
  /**
251
- * Resolve one requested identity, creating or resuming it once.
252
- * @param sessionId - requested Session identity.
253
- * @param cwd - directory the Session must own.
254
- * @param checkPersistedIdentity - whether to inspect a cold identity before creation.
255
- * @param presetId - optional Agent preset the Session must own.
256
- * @returns the matching live ordinary Agent.
252
+ * Create a Session for a new conversation, reusing the named blank Session
253
+ * while it is still reusable. A reused Session runs the requested preset, or
254
+ * the configured default when the request names none; the preset it recorded
255
+ * is not resolved, and a change is logged as `agent-preset/selected` before
256
+ * the Agent is published. A named Session that has started a turn, fails the
257
+ * caller's placement rule, or is written by another owner stays unchanged,
258
+ * and a new Session is created instead. Requests naming one identity run in
259
+ * arrival order, including their preset resolution, and check the named
260
+ * Session's directory and ownership before resolving the preset.
261
+ * @param request - optional Session to reuse, directory, preset, and placement rule.
262
+ * @returns the reused or created Session and the preset it ran when this request finished.
263
+ * @throws ApiSessionCwdConflict when the named Session belongs to another directory.
264
+ * @throws ApiSessionSubagentOwnership when subagent routing owns the named Session.
265
+ * @throws RemoteError `agent-preset/not-found` or `agent-preset/invalid` for the requested or default preset.
266
+ * @throws ApiSessionPresetConflict when no roster is composed and the request names a preset for a reused Session.
257
267
  */
258
- async ensureSession(sessionId, cwd, checkPersistedIdentity, presetId) {
259
- let creation = this.creations.get(sessionId);
260
- if (creation === undefined) {
261
- creation = this.createOrAdopt(sessionId, cwd, checkPersistedIdentity, presetId)
262
- .catch((error) => {
263
- const live = this.ctx.agents.get(sessionId);
264
- if (live !== undefined) {
265
- if (hasApiSessionSubagentOwner(this.ctx, live.session, live)) {
266
- throw new ApiSessionSubagentOwnership(sessionId);
267
- }
268
- return live;
269
- }
270
- const attached = this.ctx.sessions.get(sessionId);
271
- if (attached !== undefined && hasApiSessionSubagentOwner(this.ctx, attached, undefined)) {
272
- throw new ApiSessionSubagentOwnership(sessionId);
273
- }
274
- throw error;
275
- })
276
- .finally(() => { this.creations.delete(sessionId); });
277
- this.creations.set(sessionId, creation);
278
- }
279
- const agent = await creation;
280
- if (hasApiSessionSubagentOwner(this.ctx, agent.session, agent)) {
281
- throw new ApiSessionSubagentOwnership(sessionId);
282
- }
283
- if (presetId !== undefined) {
284
- this.assertPresetUnchanged(sessionId, presetId, this.presetForSession(agent.session));
285
- }
286
- if (agent.session.header.cwd !== cwd) {
287
- throw new ApiSessionCwdConflict(sessionId, cwd, agent.session.header.cwd);
288
- }
289
- return agent;
268
+ async createSession(request) {
269
+ const named = request.sessionId;
270
+ if (named === undefined)
271
+ return this.created(await this.createMinted(request.cwd, await this.composeAgent(request.agentPreset)));
272
+ return this.inOrder(named, async () => this.created(await this.claim(named, request)));
290
273
  }
291
274
  /**
292
275
  * Install or return the Session-local model selection used by prompt assembly.
@@ -451,55 +434,141 @@ export class ApiSessionAgentController {
451
434
  setup: composition.setup,
452
435
  })).agent;
453
436
  }
454
- async createOrAdopt(sessionId, cwd, checkPersistedIdentity, presetId) {
437
+ async inOrder(sessionId, operation) {
438
+ const result = (this.claims.get(sessionId) ?? Promise.resolve()).then(operation);
439
+ const settled = result.then(() => undefined, () => undefined);
440
+ this.claims.set(sessionId, settled);
441
+ try {
442
+ return await result;
443
+ }
444
+ finally {
445
+ if (this.claims.get(sessionId) === settled)
446
+ this.claims.delete(sessionId);
447
+ }
448
+ }
449
+ /** @returns the reused Session, a new Session under `sessionId` when none is stored, or a new Session under a minted identity. */
450
+ async claim(sessionId, request) {
455
451
  const attached = this.ctx.sessions.get(sessionId);
456
452
  const live = this.ctx.agents.get(sessionId);
457
453
  if (attached !== undefined && hasApiSessionSubagentOwner(this.ctx, attached, live)) {
458
454
  throw new ApiSessionSubagentOwnership(sessionId);
459
455
  }
460
- if (live !== undefined)
461
- return live;
462
- if (checkPersistedIdentity) {
456
+ if (live !== undefined) {
457
+ if (live.session.header.cwd !== request.cwd) {
458
+ throw new ApiSessionCwdConflict(live.id, request.cwd, live.session.header.cwd);
459
+ }
460
+ const composition = await this.composeAgent(request.agentPreset);
461
+ return await this.reuseLive(live, request, composition) ?? this.createMinted(request.cwd, composition);
462
+ }
463
+ let observation;
464
+ try {
465
+ observation = await this.ctx.sessionQuery.observeSession(sessionId);
466
+ }
467
+ catch (error) {
468
+ if (!(error instanceof SessionQueryError) || error.code !== 'SESSION_QUERY_SESSION_NOT_FOUND')
469
+ throw error;
470
+ return this.createAgent(sessionId, request.cwd, await this.composeAgent(request.agentPreset));
471
+ }
472
+ let reusable;
473
+ {
474
+ const env_3 = { stack: [], error: void 0, hasError: false };
463
475
  try {
464
- const env_3 = { stack: [], error: void 0, hasError: false };
465
- try {
466
- const observation = __addDisposableResource(env_3, await this.ctx.sessionQuery.observeSession(sessionId), false);
467
- if (hasApiSessionSubagentOwner(this.ctx, { header: observation.header }, undefined)) {
468
- throw new ApiSessionSubagentOwnership(sessionId);
469
- }
470
- if (observation.header.cwd !== cwd) {
471
- throw new ApiSessionCwdConflict(sessionId, cwd, observation.header.cwd);
472
- }
473
- const storedPreset = this.presetForObservation(observation);
474
- this.assertPresetUnchanged(sessionId, presetId, storedPreset);
475
- const composition = await this.composeAgent(storedPreset);
476
- return (await this.ctx.agents.resume({
477
- resumeSessionId: sessionId,
478
- agentOptions: this.agentOptions(),
479
- setup: composition.setup,
480
- })).agent;
481
- }
482
- catch (e_3) {
483
- env_3.error = e_3;
484
- env_3.hasError = true;
476
+ const observed = __addDisposableResource(env_3, observation, false);
477
+ if (hasApiSessionSubagentOwner(this.ctx, { header: observed.header }, undefined)) {
478
+ throw new ApiSessionSubagentOwnership(sessionId);
485
479
  }
486
- finally {
487
- __disposeResources(env_3);
480
+ if (observed.header.cwd !== request.cwd) {
481
+ throw new ApiSessionCwdConflict(sessionId, request.cwd, observed.header.cwd);
488
482
  }
483
+ reusable = request.reusable(sessionId) && !observed.events.some(event => event.type === 'turn/start');
484
+ }
485
+ catch (e_3) {
486
+ env_3.error = e_3;
487
+ env_3.hasError = true;
489
488
  }
490
- catch (error) {
491
- if (!(error instanceof SessionQueryError)
492
- || error.code !== 'SESSION_QUERY_SESSION_NOT_FOUND')
493
- throw error;
489
+ finally {
490
+ __disposeResources(env_3);
494
491
  }
495
492
  }
493
+ const composition = await this.composeAgent(request.agentPreset);
494
+ if (!reusable)
495
+ return this.createMinted(request.cwd, composition);
496
+ this.assertPresetApplicable(sessionId, request.agentPreset);
497
+ try {
498
+ return (await this.ctx.agents.resume({
499
+ resumeSessionId: sessionId,
500
+ agentOptions: this.agentOptions(),
501
+ setup: this.reuseSetup(composition),
502
+ })).agent;
503
+ }
504
+ catch (error) {
505
+ if (error instanceof ApiSessionStartedSinceObserved)
506
+ return this.createMinted(request.cwd, composition);
507
+ if (!(error instanceof Error) || error.name !== 'SessionAlreadyOwnedError')
508
+ throw error;
509
+ // Another resume in this process may hold the writer for the Agent it published.
510
+ return this.ctx.agents.get(sessionId) === undefined
511
+ ? this.createMinted(request.cwd, composition)
512
+ : this.claim(sessionId, request);
513
+ }
514
+ }
515
+ /** @returns the live blank Session under the requested preset, or undefined when it cannot be reused. */
516
+ async reuseLive(live, request, composition) {
517
+ if (!request.reusable(live.id) || !this.isBlank(live.session))
518
+ return undefined;
519
+ this.assertPresetApplicable(live.id, request.agentPreset);
520
+ const current = this.presetForSession(live.session);
521
+ const presets = this.ctx.get('agentPresets');
522
+ const target = composition.agentPreset;
523
+ if (presets === undefined || target === undefined || target === current)
524
+ return live;
525
+ try {
526
+ await presets.select(live, target);
527
+ }
528
+ catch (error) {
529
+ if (remoteErrorOf(error)?.code !== 'agent-preset/locked')
530
+ throw error;
531
+ return undefined;
532
+ }
533
+ return live;
534
+ }
535
+ /** Setup that re-checks blankness under the acquired writer, then records a preset change before publication. */
536
+ reuseSetup(composition) {
537
+ return async (agentCtx, agent) => {
538
+ if (!this.isBlank(agent.session))
539
+ throw new ApiSessionStartedSinceObserved();
540
+ const commit = await composition.setup(agentCtx, agent);
541
+ const target = composition.agentPreset;
542
+ if (target !== undefined && this.presetForSession(agent.session) !== target) {
543
+ agent.session.append('agent-preset/selected', { agentPreset: target });
544
+ }
545
+ return commit;
546
+ };
547
+ }
548
+ /** Whether no turn has started, by the predicate the preset registry's `select` enforces. */
549
+ isBlank(session) {
550
+ const boundary = this.ctx.sessionProjections.stateOf(session, 'turnBoundary');
551
+ return boundary === undefined || (boundary.openTurnStartSeq === null && boundary.lastTurn === 0);
552
+ }
553
+ /** Without a roster, reuse cannot apply a named preset. */
554
+ assertPresetApplicable(sessionId, requested) {
555
+ if (requested === undefined || this.ctx.get('agentPresets') !== undefined)
556
+ return;
557
+ throw new ApiSessionPresetConflict(sessionId, requested);
558
+ }
559
+ created(agent) {
560
+ return { agent, agentPreset: this.presetForSession(agent.session) };
561
+ }
562
+ createMinted(cwd, composition) {
563
+ return this.createAgent(brandString(`session-${randomUUID()}`), cwd, composition);
564
+ }
565
+ async createAgent(sessionId, cwd, composition) {
496
566
  try {
497
567
  await mkdir(cwd, { recursive: true });
498
568
  }
499
569
  catch (error) {
500
570
  throw new Error(`failed to ensure project directory "${cwd}": ${String(error)}`, { cause: error });
501
571
  }
502
- const composition = await this.composeAgent(presetId);
503
572
  return (await this.ctx.agents.create({
504
573
  sessionId,
505
574
  agentOptions: this.agentOptions(),
@@ -528,11 +597,6 @@ export class ApiSessionAgentController {
528
597
  }
529
598
  return observation.projections.values.agentPreset ?? undefined;
530
599
  }
531
- assertPresetUnchanged(sessionId, requested, existing) {
532
- if (requested === undefined || requested === existing)
533
- return;
534
- throw new ApiSessionPresetConflict(sessionId, requested, existing);
535
- }
536
600
  }
537
601
  function agentModelSelection(selection) {
538
602
  return {
@@ -73,14 +73,17 @@ export interface ISessions {
73
73
  */
74
74
  readonly searchResultLimit: number;
75
75
  /**
76
- * Create or adopt a Session on the Host.
77
- * @param opts - target workspace, directory, and optional preallocated identity.
78
- * @returns the catalogued identity; retain it before borrowing its binding.
76
+ * Create a Session for a new conversation on the Host. The Host reuses the
77
+ * named blank Session while it is reusable and otherwise creates another; the
78
+ * Session runs the named preset, or the Host default when none is named.
79
+ * @param opts - target workspace or directory, an optional blank Session to reuse, and an optional Agent preset.
80
+ * @returns the catalogued identity the Host created or reused; retain it before borrowing its binding.
79
81
  */
80
82
  create(opts?: {
81
83
  workspaceId?: WorkspaceId;
82
84
  cwd?: string;
83
85
  sessionId?: SessionId;
86
+ agentPreset?: string;
84
87
  }): Promise<SessionId>;
85
88
  /**
86
89
  * Resolve an already discovered direct-parent address without opening it.
@@ -155,13 +155,14 @@ export declare class SessionManager {
155
155
  * Contract session.create; on success merge into summaries immediately (no
156
156
  * wait for the next refresh). A created session is blank by definition
157
157
  * (entity birth precedes the first message).
158
- * @param opts - target workspace or working directory, plus an optional caller-owned id.
159
- * @returns the create result.
158
+ * @param opts - target workspace or working directory, an optional blank Session to reuse, and an optional Agent preset.
159
+ * @returns the create result, naming the Session the Host created or reused.
160
160
  */
161
161
  create(opts?: {
162
162
  workspaceId?: WorkspaceId;
163
163
  cwd?: string;
164
164
  sessionId?: SessionId;
165
+ agentPreset?: string;
165
166
  }): Promise<RemoteResult<{
166
167
  sessionId: SessionId;
167
168
  }>>;
@@ -437,11 +437,14 @@ export class SessionManager {
437
437
  * Contract session.create; on success merge into summaries immediately (no
438
438
  * wait for the next refresh). A created session is blank by definition
439
439
  * (entity birth precedes the first message).
440
- * @param opts - target workspace or working directory, plus an optional caller-owned id.
441
- * @returns the create result.
440
+ * @param opts - target workspace or working directory, an optional blank Session to reuse, and an optional Agent preset.
441
+ * @returns the create result, naming the Session the Host created or reused.
442
442
  */
443
443
  async create(opts = {}) {
444
- const shared = opts.sessionId === undefined ? {} : { sessionId: opts.sessionId };
444
+ const shared = {
445
+ ...(opts.sessionId === undefined ? {} : { sessionId: opts.sessionId }),
446
+ ...(opts.agentPreset === undefined ? {} : { agentPreset: opts.agentPreset }),
447
+ };
445
448
  const payload = opts.workspaceId !== undefined
446
449
  ? { workspaceId: opts.workspaceId, ...shared }
447
450
  : { ...(opts.cwd === undefined ? {} : { cwd: opts.cwd }), ...shared };
@@ -175,14 +175,15 @@ export declare class ClientSessions implements ISessions {
175
175
  /**
176
176
  * Create a Host Session and publish its catalog row before resolving.
177
177
  * Callers retain the returned identity before borrowing its binding.
178
- * @param opts - target workspace or directory and an optional preallocated id.
179
- * @returns the new session id.
178
+ * @param opts - target workspace or directory, an optional blank Session to reuse, and an optional Agent preset.
179
+ * @returns the created or reused session id.
180
180
  * @throws {SessionCreateError} with the requested id.
181
181
  */
182
182
  create(opts?: {
183
183
  workspaceId?: WorkspaceId;
184
184
  cwd?: string;
185
185
  sessionId?: SessionId;
186
+ agentPreset?: string;
186
187
  }): Promise<SessionId>;
187
188
  /**
188
189
  * Fork a session from an exact inclusive prefix of the source (same
@@ -312,8 +312,8 @@ export class ClientSessions {
312
312
  /**
313
313
  * Create a Host Session and publish its catalog row before resolving.
314
314
  * Callers retain the returned identity before borrowing its binding.
315
- * @param opts - target workspace or directory and an optional preallocated id.
316
- * @returns the new session id.
315
+ * @param opts - target workspace or directory, an optional blank Session to reuse, and an optional Agent preset.
316
+ * @returns the created or reused session id.
317
317
  * @throws {SessionCreateError} with the requested id.
318
318
  */
319
319
  async create(opts = {}) {
@@ -202,7 +202,7 @@ export class Session {
202
202
  // wire type is used; this array is not filtered or reordered.
203
203
  const routedContent = content;
204
204
  const routed = await this.remote.subagents.prompt({
205
- requestId: randomUUID(),
205
+ requestId: requestId ?? randomUUID(),
206
206
  parentSessionId: this.address.parentSessionId,
207
207
  childSessionId: this.address.childSessionId,
208
208
  mode: 'continuable',
@@ -1,7 +1,17 @@
1
1
  /** Session commands whose activation policy is explicit at each Remote method. */
2
2
  import type { Context } from '@deepseek-ai/cordis';
3
+ import type { MessageSource } from '@deepseek-ai/dsh-llm';
3
4
  import { ApiSessionAgentController } from './agent.ts';
4
5
  import type { SessionAttachmentRequest, SessionAttachmentValue, SessionCancelRequest, SessionCancelValue, SessionCreateRequest, SessionCreateValue, SessionForkRequest, SessionForkValue, SessionPromptRequest, SessionPromptValue, SessionRenameRequest, SessionRenameValue, SessionSelectModelRequest, SessionSelectModelValue, SessionUpdateQueueRequest, SessionUpdateQueueValue } from './types.ts';
6
+ type PromptContentCandidate = SessionPromptRequest['content'][number] | Extract<SessionUpdateQueueRequest['action'], {
7
+ readonly kind: 'edit';
8
+ }>['content'][number];
9
+ /**
10
+ * Whether prompt content carries non-whitespace text or an attachment.
11
+ * @param content - submitted or edited prompt parts.
12
+ * @returns false for content the Agent must not receive.
13
+ */
14
+ export declare function hasPromptContent(content: readonly PromptContentCandidate[]): boolean;
5
15
  /** Implements Session business commands delegated by the Session Controller Remote service. */
6
16
  export declare class SessionCommandController {
7
17
  private readonly ctx;
@@ -14,9 +24,10 @@ export declare class SessionCommandController {
14
24
  */
15
25
  constructor(ctx: Context, agents: ApiSessionAgentController, defaultCwd: string);
16
26
  /**
17
- * Create or idempotently adopt one ordinary Session.
18
- * @param request - requested identity, location, and Agent preset.
19
- * @returns the Session identity and resolved preset when configured.
27
+ * Create one ordinary Session for a new conversation, reusing the named
28
+ * blank Session while it is an unarchived member of the requested Workspace.
29
+ * @param request - optional blank Session to reuse, location, and Agent preset.
30
+ * @returns the created or reused Session identity and its preset when configured.
20
31
  */
21
32
  create(request: SessionCreateRequest): Promise<SessionCreateValue>;
22
33
  /**
@@ -40,11 +51,13 @@ export declare class SessionCommandController {
40
51
  */
41
52
  fork(request: SessionForkRequest): Promise<SessionForkValue>;
42
53
  /**
43
- * Reject empty content, then admit one prompt after Agent and attachment validation.
44
- * @param request - Session identity, prompt content, source metadata, and delivery mode.
54
+ * Admit one prompt after Agent and attachment validation; the Remote method
55
+ * has already validated the content and built the durable message source.
56
+ * @param request - Session identity, prompt content, and delivery mode.
57
+ * @param source - the `user-rpc` source recorded on the accepted message.
45
58
  * @returns acknowledgement that the Agent accepted the prompt.
46
59
  */
47
- prompt(request: SessionPromptRequest): Promise<SessionPromptValue>;
60
+ prompt(request: SessionPromptRequest, source: MessageSource): Promise<SessionPromptValue>;
48
61
  private requireModel;
49
62
  /**
50
63
  * Read one durable image after proving the Session log references it.
@@ -69,4 +82,5 @@ export declare class SessionCommandController {
69
82
  private readSessionState;
70
83
  private forkWorkspace;
71
84
  }
85
+ export {};
72
86
  //# sourceMappingURL=commands.d.ts.map