@letta-ai/letta-agent-sdk 0.6.0 → 0.6.2
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.md +0 -1
- package/dist/agent-creation.d.ts +3 -7
- package/dist/agent-creation.d.ts.map +1 -1
- package/dist/app-server-session.d.ts +0 -5
- package/dist/app-server-session.d.ts.map +1 -1
- package/dist/client-base.d.ts +4 -1
- package/dist/client-base.d.ts.map +1 -1
- package/dist/client-entry.js +250 -317
- package/dist/client-entry.js.map +12 -12
- package/dist/cloud-management.d.ts.map +1 -1
- package/dist/cloud-session.d.ts.map +1 -1
- package/dist/index.d.ts +2 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +252 -319
- package/dist/index.js.map +14 -14
- package/dist/management-types.d.ts +3 -0
- package/dist/management-types.d.ts.map +1 -1
- package/dist/remote-client-session-core.d.ts +22 -2
- package/dist/remote-client-session-core.d.ts.map +1 -1
- package/dist/remote-turn-coordinator.d.ts +10 -0
- package/dist/remote-turn-coordinator.d.ts.map +1 -1
- package/dist/tests/advanced-session.d.ts +1 -1
- package/dist/tests/advanced-session.d.ts.map +1 -1
- package/dist/types.d.ts +0 -21
- package/dist/types.d.ts.map +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/agent-creation.ts +93 -55
- package/src/app-server-session.ts +4 -151
- package/src/client-base.ts +7 -4
- package/src/cloud-management.ts +18 -1
- package/src/cloud-session.ts +3 -1
- package/src/index.ts +9 -10
- package/src/management-types.ts +3 -0
- package/src/remote-client-session-core.ts +35 -5
- package/src/remote-turn-coordinator.ts +27 -0
- package/src/types.ts +0 -25
- package/src/validation.ts +0 -23
|
@@ -11,12 +11,7 @@ import type {
|
|
|
11
11
|
RuntimeStartResponseMessage,
|
|
12
12
|
UpdateModelResponseMessage,
|
|
13
13
|
} from "@letta-ai/letta-code/app-server-protocol";
|
|
14
|
-
import {
|
|
15
|
-
buildSystemPrompt,
|
|
16
|
-
GIT_MEMORY_ENABLED_TAG,
|
|
17
|
-
LETTA_CODE_ORIGIN_TAG,
|
|
18
|
-
} from "@letta-ai/letta-code/agent-presets";
|
|
19
|
-
import { buildInitialAgentBody } from "./agent-creation.js";
|
|
14
|
+
import { createAgentBody } from "./agent-creation.js";
|
|
20
15
|
import { normalizeAppServerModels } from "./app-server-models.js";
|
|
21
16
|
import {
|
|
22
17
|
buildCanUseToolContext,
|
|
@@ -99,152 +94,10 @@ export type AppServerSessionOptions = Partial<LettaCodeRemoteClientOptions> & {
|
|
|
99
94
|
connect?: (
|
|
100
95
|
sessionEnv?: Record<string, string>,
|
|
101
96
|
) => Promise<{ url: string; close(): void }>;
|
|
102
|
-
/** Whether SDK create-agent payloads should add the origin tag automatically. */
|
|
103
|
-
includeSdkOriginTag?: boolean;
|
|
104
97
|
};
|
|
105
98
|
|
|
106
99
|
export type AppServerSessionMode = RuntimeSessionMode;
|
|
107
100
|
|
|
108
|
-
function isPresetSystemPrompt(value: string): boolean {
|
|
109
|
-
return [
|
|
110
|
-
"default",
|
|
111
|
-
"letta-claude",
|
|
112
|
-
"letta-codex",
|
|
113
|
-
"letta-gemini",
|
|
114
|
-
"claude",
|
|
115
|
-
"codex",
|
|
116
|
-
"gemini",
|
|
117
|
-
].includes(value);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function assertRemoteCreateAgentOptionsSupported(options: CreateAgentOptions): void {
|
|
121
|
-
if (options.allowedTools !== undefined || options.disallowedTools !== undefined) {
|
|
122
|
-
throw new Error("App-server createAgent() does not yet support allowedTools/disallowedTools.");
|
|
123
|
-
}
|
|
124
|
-
if (options.canUseTool !== undefined) {
|
|
125
|
-
throw new Error("App-server createAgent() does not yet support canUseTool callbacks.");
|
|
126
|
-
}
|
|
127
|
-
if (options.systemInfoReminder !== undefined) {
|
|
128
|
-
throw new Error("App-server createAgent() does not yet support systemInfoReminder overrides.");
|
|
129
|
-
}
|
|
130
|
-
if (options.dreaming?.behavior !== undefined) {
|
|
131
|
-
throw new Error("App-server createAgent() does not yet support dreaming.behavior overrides.");
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function normalizeMemoryBlock(block: Record<string, unknown>): Record<string, unknown> {
|
|
136
|
-
const normalized = { ...block };
|
|
137
|
-
if (normalized.value === undefined && typeof normalized.content === "string") {
|
|
138
|
-
normalized.value = normalized.content;
|
|
139
|
-
}
|
|
140
|
-
return normalized;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function upsertMemoryBlock(
|
|
144
|
-
blocks: Array<Record<string, unknown>>,
|
|
145
|
-
block: Record<string, unknown>,
|
|
146
|
-
): void {
|
|
147
|
-
const label = block.label;
|
|
148
|
-
if (typeof label === "string") {
|
|
149
|
-
const existingIndex = blocks.findIndex((candidate) => candidate.label === label);
|
|
150
|
-
if (existingIndex >= 0) {
|
|
151
|
-
blocks[existingIndex] = block;
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
blocks.push(block);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
export async function createAgentBody(
|
|
159
|
-
options: CreateAgentOptions,
|
|
160
|
-
settings: { includeSdkOriginTag?: boolean } = {},
|
|
161
|
-
): Promise<Record<string, unknown>> {
|
|
162
|
-
assertRemoteCreateAgentOptionsSupported(options);
|
|
163
|
-
|
|
164
|
-
const includeOriginTag = settings.includeSdkOriginTag ?? true;
|
|
165
|
-
const body = await buildInitialAgentBody(options);
|
|
166
|
-
|
|
167
|
-
if (Array.isArray(body.tags)) {
|
|
168
|
-
body.tags = body.tags.filter(
|
|
169
|
-
(tag) =>
|
|
170
|
-
(includeOriginTag || tag !== LETTA_CODE_ORIGIN_TAG) &&
|
|
171
|
-
(options.memfs !== false || tag !== GIT_MEMORY_ENABLED_TAG),
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (options.embedding !== undefined) body.embedding = options.embedding;
|
|
176
|
-
if (options.hidden !== undefined) body.hidden = options.hidden;
|
|
177
|
-
// When baseTools is omitted, the harness applies its created-agent
|
|
178
|
-
// defaults (web_search, fetch_webpage). An explicit list is pinned exactly:
|
|
179
|
-
// `tools` alone does not suppress the Letta agent type's defaults, so both
|
|
180
|
-
// default tool attachment and its default tool rules are disabled here.
|
|
181
|
-
if (options.baseTools === undefined) {
|
|
182
|
-
delete body.tools;
|
|
183
|
-
delete body.include_base_tools;
|
|
184
|
-
delete body.include_base_tool_rules;
|
|
185
|
-
} else {
|
|
186
|
-
body.tools = options.baseTools;
|
|
187
|
-
body.include_base_tools = false;
|
|
188
|
-
body.include_base_tool_rules = false;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (options.systemPrompt === undefined) {
|
|
192
|
-
if (options.memfs === false) {
|
|
193
|
-
body.system = buildSystemPrompt("default", "standard");
|
|
194
|
-
}
|
|
195
|
-
} else {
|
|
196
|
-
if (typeof options.systemPrompt === "string") {
|
|
197
|
-
if (isPresetSystemPrompt(options.systemPrompt)) {
|
|
198
|
-
throw new Error("createAgent() does not yet support system prompt presets for this backend.");
|
|
199
|
-
}
|
|
200
|
-
body.system = options.systemPrompt;
|
|
201
|
-
} else {
|
|
202
|
-
throw new Error("createAgent() does not yet support system prompt preset objects for this backend.");
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const hasMemoryConfiguration =
|
|
207
|
-
Array.isArray(body.memory_blocks) ||
|
|
208
|
-
options.memory !== undefined ||
|
|
209
|
-
options.persona !== undefined ||
|
|
210
|
-
options.human !== undefined;
|
|
211
|
-
const memoryBlocks = Array.isArray(body.memory_blocks)
|
|
212
|
-
? body.memory_blocks
|
|
213
|
-
.filter(
|
|
214
|
-
(block): block is Record<string, unknown> =>
|
|
215
|
-
block !== null && typeof block === "object",
|
|
216
|
-
)
|
|
217
|
-
.map((block) => ({ ...block }))
|
|
218
|
-
: [];
|
|
219
|
-
const blockIds: string[] = [];
|
|
220
|
-
for (const item of options.memory ?? []) {
|
|
221
|
-
if (typeof item === "string") {
|
|
222
|
-
throw new Error("App-server createAgent() does not yet support memory preset names.");
|
|
223
|
-
}
|
|
224
|
-
if ("blockId" in item) {
|
|
225
|
-
blockIds.push(item.blockId);
|
|
226
|
-
} else {
|
|
227
|
-
upsertMemoryBlock(
|
|
228
|
-
memoryBlocks,
|
|
229
|
-
normalizeMemoryBlock(item as unknown as Record<string, unknown>),
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
if (options.persona !== undefined) {
|
|
234
|
-
upsertMemoryBlock(memoryBlocks, {
|
|
235
|
-
label: "persona",
|
|
236
|
-
value: options.persona,
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
if (options.human !== undefined) {
|
|
240
|
-
upsertMemoryBlock(memoryBlocks, { label: "human", value: options.human });
|
|
241
|
-
}
|
|
242
|
-
if (hasMemoryConfiguration) body.memory_blocks = memoryBlocks;
|
|
243
|
-
if (blockIds.length > 0) body.block_ids = blockIds;
|
|
244
|
-
|
|
245
|
-
return body;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
101
|
export function externalToolGroups(tools: AnyAgentTool[] | undefined): Array<Record<string, unknown>> | undefined {
|
|
249
102
|
if (!tools || tools.length === 0) return undefined;
|
|
250
103
|
return [
|
|
@@ -779,6 +632,8 @@ export class AppServerSession extends RemoteClientSessionCore {
|
|
|
779
632
|
throw new Error(response.error ?? "Failed to start app-server runtime");
|
|
780
633
|
}
|
|
781
634
|
|
|
635
|
+
this.watchTransportDisconnect(client);
|
|
636
|
+
|
|
782
637
|
const tools = agentToolNames(response.agent);
|
|
783
638
|
const skillSources = options.skillSources;
|
|
784
639
|
const clientToolset = "toolset" in options ? options.toolset : undefined;
|
|
@@ -883,9 +738,7 @@ export class AppServerSession extends RemoteClientSessionCore {
|
|
|
883
738
|
|
|
884
739
|
if (this.mode.kind === "create-agent") {
|
|
885
740
|
command.create_agent = {
|
|
886
|
-
body: await createAgentBody(this.mode.options,
|
|
887
|
-
includeSdkOriginTag: this.remoteOptions.includeSdkOriginTag,
|
|
888
|
-
}),
|
|
741
|
+
body: await createAgentBody(this.mode.options),
|
|
889
742
|
// Hidden (worker-style) agents default to unpinned.
|
|
890
743
|
pin_global:
|
|
891
744
|
this.remoteOptions.pinGlobalAgent ??
|
package/src/client-base.ts
CHANGED
|
@@ -86,8 +86,8 @@ function looksLikeConversationId(id: string): boolean {
|
|
|
86
86
|
return id.startsWith("conv-") || id.startsWith("local-conv-");
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
type
|
|
90
|
-
|
|
89
|
+
type OneShotSession = LettaCodeSession & {
|
|
90
|
+
sendAndWaitForResult(message: SendMessage): Promise<SDKResultMessage>;
|
|
91
91
|
};
|
|
92
92
|
|
|
93
93
|
/**
|
|
@@ -314,7 +314,10 @@ export class LettaAgentClientBase {
|
|
|
314
314
|
return this.resumeLocalSession(id, options);
|
|
315
315
|
}
|
|
316
316
|
|
|
317
|
-
/**
|
|
317
|
+
/**
|
|
318
|
+
* One-shot convenience for scripts, smoke tests, and evals.
|
|
319
|
+
* Applications should use a session when they need interactive turn state.
|
|
320
|
+
*/
|
|
318
321
|
async prompt(
|
|
319
322
|
message: SendMessage,
|
|
320
323
|
agentId: string,
|
|
@@ -323,7 +326,7 @@ export class LettaAgentClientBase {
|
|
|
323
326
|
const session = this.createSession(agentId, options);
|
|
324
327
|
|
|
325
328
|
try {
|
|
326
|
-
return await (session as
|
|
329
|
+
return await (session as OneShotSession).sendAndWaitForResult(message);
|
|
327
330
|
} finally {
|
|
328
331
|
session.close();
|
|
329
332
|
}
|
package/src/cloud-management.ts
CHANGED
|
@@ -74,6 +74,23 @@ function isNotFound(error: unknown): boolean {
|
|
|
74
74
|
return error instanceof APIError && error.status === 404;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
/**
|
|
78
|
+
* The SDK cursor contract is chronological: `before` always means older and
|
|
79
|
+
* `after` always means newer. The Cloud REST API interprets cursors relative
|
|
80
|
+
* to sort order instead, and its default order is newest-first, so descending
|
|
81
|
+
* requests need their cursor keys swapped at this boundary. The app-server
|
|
82
|
+
* transport already receives chronological cursors and must not swap.
|
|
83
|
+
*/
|
|
84
|
+
function toOrderRelativeCursors(query: MessageListParams): MessageListParams {
|
|
85
|
+
if ((query.order ?? "desc") !== "desc") return query;
|
|
86
|
+
const { before, after, ...rest } = query;
|
|
87
|
+
return {
|
|
88
|
+
...rest,
|
|
89
|
+
...(after != null ? { before: after } : {}),
|
|
90
|
+
...(before != null ? { after: before } : {}),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
77
94
|
/** Cloud management backed by the canonical generated Letta TypeScript client. */
|
|
78
95
|
export class CloudManagementTransport implements ManagementTransport {
|
|
79
96
|
constructor(private readonly client: Letta) {}
|
|
@@ -206,7 +223,7 @@ export class CloudManagementTransport implements ManagementTransport {
|
|
|
206
223
|
): Promise<ConversationMessagesResult> {
|
|
207
224
|
const page = await this.client.conversations.messages.list(
|
|
208
225
|
conversationId,
|
|
209
|
-
query,
|
|
226
|
+
toOrderRelativeCursors(query),
|
|
210
227
|
);
|
|
211
228
|
return { messages: page.items };
|
|
212
229
|
}
|
package/src/cloud-session.ts
CHANGED
|
@@ -5,10 +5,10 @@ import {
|
|
|
5
5
|
createAppServerClient,
|
|
6
6
|
type AppServerClient,
|
|
7
7
|
} from "@letta-ai/letta-code/app-server-client";
|
|
8
|
+
import { createAgentBody } from "./agent-creation.js";
|
|
8
9
|
import {
|
|
9
10
|
AppServerRuntimeController,
|
|
10
11
|
agentToolNames,
|
|
11
|
-
createAgentBody,
|
|
12
12
|
createExternalToolCallHandler,
|
|
13
13
|
externalToolGroups,
|
|
14
14
|
registerAppServerControlRequestHandler,
|
|
@@ -398,6 +398,8 @@ export class CloudEnvironmentSession extends RemoteClientSessionCore {
|
|
|
398
398
|
throw new Error(response.error ?? "Failed to start Cloud status runtime");
|
|
399
399
|
}
|
|
400
400
|
|
|
401
|
+
this.watchTransportDisconnect(client);
|
|
402
|
+
|
|
401
403
|
const tools = agentToolNames(response.agent);
|
|
402
404
|
const skillSources = options.skillSources;
|
|
403
405
|
const clientToolset = "toolset" in options ? options.toolset : undefined;
|
package/src/index.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @example
|
|
7
7
|
* ```typescript
|
|
8
|
-
* import { LettaAgentClient, createAgent, createSession, resumeSession
|
|
8
|
+
* import { LettaAgentClient, createAgent, createSession, resumeSession } from '@letta-ai/letta-agent-sdk';
|
|
9
9
|
*
|
|
10
10
|
* const client = new LettaAgentClient({ backend: 'local' });
|
|
11
11
|
* const agentId = await client.createAgent();
|
|
@@ -23,8 +23,6 @@
|
|
|
23
23
|
* // Create new conversation on specific agent
|
|
24
24
|
* const session = createSession(agentId);
|
|
25
25
|
*
|
|
26
|
-
* // One-shot prompt in a new conversation
|
|
27
|
-
* const result = await prompt('Hello', agentId);
|
|
28
26
|
* ```
|
|
29
27
|
*/
|
|
30
28
|
|
|
@@ -83,7 +81,6 @@ export type {
|
|
|
83
81
|
SDKProtocolMessage,
|
|
84
82
|
SDKProtocolCommand,
|
|
85
83
|
SendCommandOptions,
|
|
86
|
-
RunTurnOptions,
|
|
87
84
|
RecoverPendingApprovalsOptions,
|
|
88
85
|
RecoverPendingApprovalsResult,
|
|
89
86
|
ChangeDeviceStateOptions,
|
|
@@ -265,18 +262,20 @@ export function resumeSession(
|
|
|
265
262
|
}
|
|
266
263
|
|
|
267
264
|
/**
|
|
268
|
-
* One-shot
|
|
265
|
+
* One-shot convenience for scripts, smoke tests, and evals.
|
|
269
266
|
*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
267
|
+
* Creates a short-lived conversation, waits for the final result, and closes
|
|
268
|
+
* the session. Applications that need streaming, approvals, cancellation,
|
|
269
|
+
* queueing, or continued interaction should use createSession() with send()
|
|
270
|
+
* and stream() instead.
|
|
272
271
|
*
|
|
273
272
|
* @example
|
|
274
273
|
* ```typescript
|
|
275
274
|
* const result = await prompt('What is the capital of France?', agentId); // specific agent
|
|
276
275
|
* ```
|
|
277
276
|
*/
|
|
278
|
-
type
|
|
279
|
-
|
|
277
|
+
type OneShotSession = LettaCodeSession & {
|
|
278
|
+
sendAndWaitForResult(message: SendMessage): Promise<SDKResultMessage>;
|
|
280
279
|
};
|
|
281
280
|
|
|
282
281
|
type InitializableSession = LettaCodeSession & {
|
|
@@ -291,7 +290,7 @@ export async function prompt(
|
|
|
291
290
|
const session = createSession(agentId, options);
|
|
292
291
|
|
|
293
292
|
try {
|
|
294
|
-
return await (session as
|
|
293
|
+
return await (session as OneShotSession).sendAndWaitForResult(message);
|
|
295
294
|
} finally {
|
|
296
295
|
session.close();
|
|
297
296
|
}
|
package/src/management-types.ts
CHANGED
|
@@ -81,8 +81,11 @@ export interface UpdateConversationOptions {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
export interface ConversationMessagesOptions {
|
|
84
|
+
/** Return messages before this message ID (cursor for older pages, regardless of `order`). */
|
|
84
85
|
before?: Present<MessageListParams["before"]>;
|
|
86
|
+
/** Return messages after this message ID (cursor for newer pages, regardless of `order`). */
|
|
85
87
|
after?: Present<MessageListParams["after"]>;
|
|
88
|
+
/** Sort order of the returned page. Defaults to "desc" (newest first). */
|
|
86
89
|
order?: "asc" | "desc";
|
|
87
90
|
limit?: Present<MessageListParams["limit"]>;
|
|
88
91
|
}
|
|
@@ -13,7 +13,6 @@ import type {
|
|
|
13
13
|
RecoverPendingApprovalsOptions,
|
|
14
14
|
RecoverPendingApprovalsResult,
|
|
15
15
|
RemoveQueuedMessageResult,
|
|
16
|
-
RunTurnOptions,
|
|
17
16
|
SDKInitMessage,
|
|
18
17
|
SDKMessage,
|
|
19
18
|
SDKProtocolCommand,
|
|
@@ -78,6 +77,7 @@ export abstract class RemoteClientSessionCore implements LettaCodeSession {
|
|
|
78
77
|
private readonly requestTimeoutMs: number | undefined;
|
|
79
78
|
private initializePromise: Promise<SDKInitMessage> | null = null;
|
|
80
79
|
private removeMessageHandler: (() => void) | null = null;
|
|
80
|
+
private detachTransportDisconnect: (() => void) | null = null;
|
|
81
81
|
private readonly turns: RemoteTurnCoordinator;
|
|
82
82
|
private toolNames: string[] | undefined;
|
|
83
83
|
private deviceStatusListeners = new Set<(status: SessionDeviceStatus) => void>();
|
|
@@ -189,6 +189,8 @@ export abstract class RemoteClientSessionCore implements LettaCodeSession {
|
|
|
189
189
|
private cleanupFailedInitialize(): void {
|
|
190
190
|
this.removeMessageHandler?.();
|
|
191
191
|
this.removeMessageHandler = null;
|
|
192
|
+
this.detachTransportDisconnect?.();
|
|
193
|
+
this.detachTransportDisconnect = null;
|
|
192
194
|
this.controller?.close();
|
|
193
195
|
this.controller = null;
|
|
194
196
|
// Subclass-owned resources (spawned connections, sandboxes, handlers)
|
|
@@ -225,10 +227,7 @@ export abstract class RemoteClientSessionCore implements LettaCodeSession {
|
|
|
225
227
|
}
|
|
226
228
|
}
|
|
227
229
|
|
|
228
|
-
async
|
|
229
|
-
message: SendMessage,
|
|
230
|
-
_options: RunTurnOptions = {},
|
|
231
|
-
): Promise<SDKResultMessage> {
|
|
230
|
+
async sendAndWaitForResult(message: SendMessage): Promise<SDKResultMessage> {
|
|
232
231
|
if (this.turns.hasInFlightTurn()) {
|
|
233
232
|
throw new Error(
|
|
234
233
|
`A turn is already in flight for this ${this.label} session. Use send() and stream() to let the listener queue messages.`,
|
|
@@ -547,6 +546,8 @@ export abstract class RemoteClientSessionCore implements LettaCodeSession {
|
|
|
547
546
|
this.closed = true;
|
|
548
547
|
this.removeMessageHandler?.();
|
|
549
548
|
this.removeMessageHandler = null;
|
|
549
|
+
this.detachTransportDisconnect?.();
|
|
550
|
+
this.detachTransportDisconnect = null;
|
|
550
551
|
this.turns.close();
|
|
551
552
|
for (const cancel of [...this.deviceStatusRefreshCancels]) {
|
|
552
553
|
cancel(new Error(`Session closed while waiting for ${this.label} device status`));
|
|
@@ -623,6 +624,35 @@ export abstract class RemoteClientSessionCore implements LettaCodeSession {
|
|
|
623
624
|
|
|
624
625
|
protected abstract initializeRuntimeController(): Promise<RuntimeSessionInit>;
|
|
625
626
|
|
|
627
|
+
/**
|
|
628
|
+
* Fail the session when its transport drops unexpectedly.
|
|
629
|
+
*
|
|
630
|
+
* Without this an in-flight turn parks forever: nextMessage() only settles
|
|
631
|
+
* through the coordinator, and the socket's own pending-request rejection
|
|
632
|
+
* covers request/response commands, not streaming turns.
|
|
633
|
+
*
|
|
634
|
+
* The session is not revived — the runtime lived on the dead socket. The
|
|
635
|
+
* caller observes the error and rebuilds via resumeSession().
|
|
636
|
+
*
|
|
637
|
+
* Subclasses call this only once their runtime is live: a drop before that
|
|
638
|
+
* already surfaces as a rejected connect or runtime-start, and failing the
|
|
639
|
+
* session there would defeat initialize()'s retry path. Explicit closes do
|
|
640
|
+
* not notify, so this fires for faults only. Detaching stays here so every
|
|
641
|
+
* teardown path — clean close and failed initialize alike — covers it.
|
|
642
|
+
*/
|
|
643
|
+
protected watchTransportDisconnect(client: {
|
|
644
|
+
onDisconnect(handler: () => void): () => void;
|
|
645
|
+
}): void {
|
|
646
|
+
this.detachTransportDisconnect?.();
|
|
647
|
+
this.detachTransportDisconnect = client.onDisconnect(() => {
|
|
648
|
+
if (this.closed) return;
|
|
649
|
+
this.turns.closeWithError(
|
|
650
|
+
`The ${this.label} connection closed unexpectedly; resume the conversation to continue.`,
|
|
651
|
+
);
|
|
652
|
+
this.close();
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
|
|
626
656
|
protected async afterRuntimeInitialized(): Promise<void> {
|
|
627
657
|
// Optional hook for subclasses to send transport-specific startup frames.
|
|
628
658
|
}
|
|
@@ -160,6 +160,33 @@ export class RemoteTurnCoordinator {
|
|
|
160
160
|
});
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Close after the transport dropped, surfacing the failure to stream
|
|
165
|
+
* consumers first.
|
|
166
|
+
*
|
|
167
|
+
* A plain close() resolves waiting consumers with `null`, which is
|
|
168
|
+
* indistinguishable from a conversation ending normally. A dead socket
|
|
169
|
+
* cannot deliver a turn-scoped failure of its own, so the error is
|
|
170
|
+
* synthesized here before the queue drains.
|
|
171
|
+
*/
|
|
172
|
+
closeWithError(detail: string): void {
|
|
173
|
+
if (this.closed) return;
|
|
174
|
+
const active = this.activeTurn;
|
|
175
|
+
if (active) {
|
|
176
|
+
this.failTurn(active, detail);
|
|
177
|
+
} else {
|
|
178
|
+
this.enqueue({
|
|
179
|
+
type: "error",
|
|
180
|
+
message: detail,
|
|
181
|
+
errorCode: "error",
|
|
182
|
+
stopReason: "error",
|
|
183
|
+
errorDetail: detail,
|
|
184
|
+
recoverable: false,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
this.close();
|
|
188
|
+
}
|
|
189
|
+
|
|
163
190
|
close(): void {
|
|
164
191
|
if (this.closed) return;
|
|
165
192
|
this.closed = true;
|
package/src/types.ts
CHANGED
|
@@ -68,20 +68,6 @@ export interface TextContent {
|
|
|
68
68
|
text: string;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
export interface RunTurnOptions {
|
|
72
|
-
/**
|
|
73
|
-
* Max automatic approval-conflict recovery attempts for this turn.
|
|
74
|
-
* Overrides session-level maxApprovalRecoveryAttempts when provided.
|
|
75
|
-
*/
|
|
76
|
-
maxApprovalRecoveryAttempts?: number;
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Timeout in milliseconds for each approval recovery request.
|
|
80
|
-
* Overrides session-level approvalRecoveryTimeoutMs when provided.
|
|
81
|
-
*/
|
|
82
|
-
recoveryTimeoutMs?: number;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
71
|
export interface RecoverPendingApprovalsOptions {
|
|
86
72
|
/**
|
|
87
73
|
* Timeout in milliseconds for the recovery control request.
|
|
@@ -711,17 +697,6 @@ export interface CreateSessionOptions {
|
|
|
711
697
|
*/
|
|
712
698
|
mcpServers?: McpServers;
|
|
713
699
|
|
|
714
|
-
/**
|
|
715
|
-
* Max automatic approval-conflict recovery attempts per runTurn() call.
|
|
716
|
-
* Set to 0 to disable automatic recovery.
|
|
717
|
-
*/
|
|
718
|
-
maxApprovalRecoveryAttempts?: number;
|
|
719
|
-
|
|
720
|
-
/**
|
|
721
|
-
* Timeout in milliseconds for a single approval recovery request.
|
|
722
|
-
*/
|
|
723
|
-
approvalRecoveryTimeoutMs?: number;
|
|
724
|
-
|
|
725
700
|
/**
|
|
726
701
|
* Cloud repository resources to attach for the lifetime of the SDK session.
|
|
727
702
|
* For relationships that should outlive the session, use
|
package/src/validation.ts
CHANGED
|
@@ -54,28 +54,6 @@ function getBlockLabels(memory: MemoryItem[]): string[] {
|
|
|
54
54
|
.filter((label): label is string => label !== null);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
function validateApprovalRecoveryOptions(options: CreateSessionOptions): void {
|
|
58
|
-
if (
|
|
59
|
-
options.maxApprovalRecoveryAttempts !== undefined &&
|
|
60
|
-
(!Number.isInteger(options.maxApprovalRecoveryAttempts) ||
|
|
61
|
-
options.maxApprovalRecoveryAttempts < 0)
|
|
62
|
-
) {
|
|
63
|
-
throw new Error(
|
|
64
|
-
"Invalid maxApprovalRecoveryAttempts. Expected a non-negative integer."
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (
|
|
69
|
-
options.approvalRecoveryTimeoutMs !== undefined &&
|
|
70
|
-
(!Number.isInteger(options.approvalRecoveryTimeoutMs) ||
|
|
71
|
-
options.approvalRecoveryTimeoutMs <= 0)
|
|
72
|
-
) {
|
|
73
|
-
throw new Error(
|
|
74
|
-
"Invalid approvalRecoveryTimeoutMs. Expected a positive integer."
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
57
|
function validateRemovedSessionOptions(options: CreateSessionOptions): void {
|
|
80
58
|
const removedOptions = options as CreateSessionOptions & Record<string, unknown>;
|
|
81
59
|
if (removedOptions.systemPrompt !== undefined) {
|
|
@@ -250,7 +228,6 @@ export function validateCreateSessionOptions(options: CreateSessionOptions): voi
|
|
|
250
228
|
validateMcpServers(options.mcpServers);
|
|
251
229
|
validateReasoningEffort(options.reasoningEffort);
|
|
252
230
|
validateDreamingOptions(options.dreaming);
|
|
253
|
-
validateApprovalRecoveryOptions(options);
|
|
254
231
|
validateRemovedSessionOptions(options);
|
|
255
232
|
}
|
|
256
233
|
|