@deepseek-ai/dsh-api-session-controller 0.2.1-alpha.2 → 0.2.1-alpha.3
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 +3 -1
- package/README.zh.md +3 -1
- package/lib/client.js +11 -6
- package/lib/index.js +222 -97
- package/lib/typert.host.js +1243 -691
- package/lib/typert.remote-client.d.ts +25 -2
- package/lib/typert.remote-client.js +1039 -676
- package/lib/types/agent.d.ts +61 -17
- package/lib/types/agent.js +148 -84
- package/lib/types/client/contract/sessions.d.ts +6 -3
- package/lib/types/client/sessions/manager.d.ts +3 -2
- package/lib/types/client/sessions/manager.js +6 -3
- package/lib/types/client/sessions/service.d.ts +3 -2
- package/lib/types/client/sessions/service.js +2 -2
- package/lib/types/client/sessions/session.js +1 -1
- package/lib/types/commands.d.ts +20 -6
- package/lib/types/commands.js +28 -34
- package/lib/types/index.d.ts +6 -4
- package/lib/types/index.js +65 -15
- package/lib/types/types.d.ts +14 -3
- package/locale/en.json +5 -0
- package/locale/zh.json +6 -0
- package/package.json +83 -80
package/lib/types/agent.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
-
|
|
29
|
-
|
|
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
|
|
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
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
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
|
-
|
|
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
|
|
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
|
package/lib/types/agent.js
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
-
|
|
90
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
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
|
|
259
|
-
|
|
260
|
-
if (
|
|
261
|
-
|
|
262
|
-
|
|
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
|
|
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
|
-
|
|
462
|
-
|
|
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
|
|
465
|
-
|
|
466
|
-
|
|
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
|
-
|
|
487
|
-
|
|
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
|
-
|
|
491
|
-
|
|
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
|
|
77
|
-
*
|
|
78
|
-
*
|
|
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,
|
|
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,
|
|
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 =
|
|
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
|
|
179
|
-
* @returns the
|
|
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
|
|
316
|
-
* @returns the
|
|
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',
|
package/lib/types/commands.d.ts
CHANGED
|
@@ -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
|
|
18
|
-
*
|
|
19
|
-
* @
|
|
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
|
-
*
|
|
44
|
-
*
|
|
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
|