@letta-ai/letta-agent-sdk 0.3.2 → 0.3.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/AGENTS.md +47 -0
- package/README.md +17 -0
- package/dist/client-entry.js +800 -732
- package/dist/client-entry.js.map +8 -5
- package/dist/cloud-sandbox.d.ts +33 -0
- package/dist/cloud-sandbox.d.ts.map +1 -0
- package/dist/cloud-session.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +799 -732
- package/dist/index.js.map +9 -6
- package/dist/remote-client-session-core.d.ts +6 -93
- package/dist/remote-client-session-core.d.ts.map +1 -1
- package/dist/remote-session-protocol.d.ts +130 -0
- package/dist/remote-session-protocol.d.ts.map +1 -0
- package/dist/remote-turn-coordinator.d.ts +49 -0
- package/dist/remote-turn-coordinator.d.ts.map +1 -0
- package/dist/types.d.ts +2 -20
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -2
- package/src/app-server-management.ts +641 -0
- package/src/app-server-session.ts +948 -0
- package/src/cli-resolver.ts +46 -0
- package/src/client-base.ts +482 -0
- package/src/client-entry.ts +31 -0
- package/src/client.ts +138 -0
- package/src/cloud-management.ts +360 -0
- package/src/cloud-sandbox.ts +117 -0
- package/src/cloud-session.ts +1313 -0
- package/src/index.ts +440 -0
- package/src/interactiveToolPolicy.ts +62 -0
- package/src/local-app-server-session.ts +39 -0
- package/src/local-app-server.ts +137 -0
- package/src/management-types.ts +133 -0
- package/src/management.ts +206 -0
- package/src/protocol.ts +249 -0
- package/src/remote-client-session-core.ts +786 -0
- package/src/remote-session-protocol.ts +660 -0
- package/src/remote-turn-coordinator.ts +505 -0
- package/src/remote.ts +177 -0
- package/src/repositories.ts +340 -0
- package/src/request-ids.ts +33 -0
- package/src/session.ts +1638 -0
- package/src/stream-events.ts +88 -0
- package/src/tool-helpers.ts +147 -0
- package/src/transport.ts +484 -0
- package/src/types.ts +1328 -0
- package/src/validation.ts +223 -0
- package/src/websocket.ts +22 -0
|
@@ -0,0 +1,660 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CreateAgentOptions,
|
|
3
|
+
LettaCodeClientSessionOptions,
|
|
4
|
+
LettaCodeModelEntry,
|
|
5
|
+
ListMessagesOptions,
|
|
6
|
+
ListMessagesResult,
|
|
7
|
+
ListModelsResult,
|
|
8
|
+
MessageContentItem,
|
|
9
|
+
RecoverPendingApprovalsOptions,
|
|
10
|
+
RecoverPendingApprovalsResult,
|
|
11
|
+
ReasoningEffort,
|
|
12
|
+
SDKErrorCode,
|
|
13
|
+
SDKQueueItem,
|
|
14
|
+
SendMessage,
|
|
15
|
+
SessionDeviceStatus,
|
|
16
|
+
SessionDiffHunk,
|
|
17
|
+
SessionDiffPreview,
|
|
18
|
+
SessionPendingControlRequest,
|
|
19
|
+
SessionPermissionSuggestion,
|
|
20
|
+
SkillSource,
|
|
21
|
+
UpdateModelOptions,
|
|
22
|
+
UpdateModelResult,
|
|
23
|
+
} from "./types.js";
|
|
24
|
+
|
|
25
|
+
export type RuntimeScope = {
|
|
26
|
+
agent_id: string;
|
|
27
|
+
conversation_id: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type ProtocolMessage = Record<string, unknown> & {
|
|
31
|
+
type: string;
|
|
32
|
+
request_id?: string;
|
|
33
|
+
runtime?: RuntimeScope;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type RuntimeSessionMode =
|
|
37
|
+
| { kind: "create-agent"; options: CreateAgentOptions }
|
|
38
|
+
| {
|
|
39
|
+
kind: "session";
|
|
40
|
+
agentId?: string;
|
|
41
|
+
conversationId?: string;
|
|
42
|
+
newConversation?: boolean;
|
|
43
|
+
defaultConversation?: boolean;
|
|
44
|
+
options: LettaCodeClientSessionOptions;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type RuntimeTurnResult = {
|
|
48
|
+
runtime: RuntimeScope;
|
|
49
|
+
stopReason: string | null;
|
|
50
|
+
runIds: string[];
|
|
51
|
+
success?: boolean;
|
|
52
|
+
detail?: string;
|
|
53
|
+
errorCode?: SDKErrorCode;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type RuntimeSendTurnOptions = {
|
|
57
|
+
clientMessageId: string;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type RuntimeRequestOptions = {
|
|
61
|
+
timeoutMs?: number;
|
|
62
|
+
predicate?: (message: ProtocolMessage) => boolean;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export interface RemoteClientRuntimeController {
|
|
66
|
+
onMessage(handler: (message: ProtocolMessage, channel?: string) => void): () => void;
|
|
67
|
+
send(command: Record<string, unknown>): void;
|
|
68
|
+
sendTurnMessage(
|
|
69
|
+
runtime: RuntimeScope,
|
|
70
|
+
message: SendMessage,
|
|
71
|
+
options: RuntimeSendTurnOptions,
|
|
72
|
+
): void;
|
|
73
|
+
abort(runtime: RuntimeScope): Promise<void>;
|
|
74
|
+
request(
|
|
75
|
+
type: string,
|
|
76
|
+
body: Record<string, unknown>,
|
|
77
|
+
options?: RuntimeRequestOptions,
|
|
78
|
+
): Promise<ProtocolMessage>;
|
|
79
|
+
recoverPendingApprovals(
|
|
80
|
+
runtime: RuntimeScope,
|
|
81
|
+
options?: RecoverPendingApprovalsOptions,
|
|
82
|
+
): Promise<RecoverPendingApprovalsResult>;
|
|
83
|
+
listMessages(
|
|
84
|
+
conversationId: string,
|
|
85
|
+
options?: ListMessagesOptions,
|
|
86
|
+
): Promise<ListMessagesResult>;
|
|
87
|
+
listModels(): Promise<ListModelsResult>;
|
|
88
|
+
updateModel(
|
|
89
|
+
runtime: RuntimeScope,
|
|
90
|
+
payload: { model_id?: string; model_handle?: string },
|
|
91
|
+
): Promise<UpdateModelResult>;
|
|
92
|
+
close(): void;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export type RuntimeSessionInit = {
|
|
96
|
+
controller: RemoteClientRuntimeController;
|
|
97
|
+
runtime: RuntimeScope;
|
|
98
|
+
model?: string | null;
|
|
99
|
+
modelSettings?: Record<string, unknown> | null;
|
|
100
|
+
tools?: string[];
|
|
101
|
+
/** Effective runtime skill source override, when explicitly requested. */
|
|
102
|
+
skillSources?: SkillSource[];
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export type RemoteClientSessionCoreConfig = {
|
|
106
|
+
label: string;
|
|
107
|
+
requestTimeoutMs?: number;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
type ReflectionSettings = {
|
|
111
|
+
trigger: "off" | "step-count" | "compaction-event";
|
|
112
|
+
step_count: number;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export type UpdateModelPayload = {
|
|
116
|
+
model_id?: string;
|
|
117
|
+
model_handle?: string;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export type NormalizedUpdateModelInput = {
|
|
121
|
+
model?: string;
|
|
122
|
+
modelId?: string;
|
|
123
|
+
modelHandle?: string;
|
|
124
|
+
reasoningEffort?: ReasoningEffort;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export type TurnTracker = {
|
|
128
|
+
id: number;
|
|
129
|
+
runtime: RuntimeScope;
|
|
130
|
+
clientMessageId: string;
|
|
131
|
+
queuedAt: number;
|
|
132
|
+
startedAt: number;
|
|
133
|
+
assistantText: string;
|
|
134
|
+
runIds: Set<string>;
|
|
135
|
+
observedTurnEvidence: boolean;
|
|
136
|
+
observedRequiresApprovalStop: boolean;
|
|
137
|
+
abortRequested: boolean;
|
|
138
|
+
timeout: ReturnType<typeof setTimeout> | null;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export const FAILURE_STOP_REASONS = new Set([
|
|
142
|
+
"error",
|
|
143
|
+
"llm_api_error",
|
|
144
|
+
"max_steps",
|
|
145
|
+
"interrupted",
|
|
146
|
+
"cancelled",
|
|
147
|
+
"canceled",
|
|
148
|
+
]);
|
|
149
|
+
const REASONING_EFFORTS = new Set<ReasoningEffort>([
|
|
150
|
+
"none",
|
|
151
|
+
"minimal",
|
|
152
|
+
"low",
|
|
153
|
+
"medium",
|
|
154
|
+
"high",
|
|
155
|
+
"xhigh",
|
|
156
|
+
]);
|
|
157
|
+
const KNOWN_SDK_ERROR_CODES = new Set<SDKErrorCode>([
|
|
158
|
+
"approval_conflict",
|
|
159
|
+
"approval_conflict_terminal",
|
|
160
|
+
"protocol_error",
|
|
161
|
+
"error",
|
|
162
|
+
"llm_api_error",
|
|
163
|
+
"max_steps",
|
|
164
|
+
"interrupted",
|
|
165
|
+
"stream_closed",
|
|
166
|
+
]);
|
|
167
|
+
|
|
168
|
+
type LegacyPermissionMode = "default" | "bypassPermissions" | "fullAccess";
|
|
169
|
+
|
|
170
|
+
export function normalizePermissionMode(
|
|
171
|
+
mode:
|
|
172
|
+
| LettaCodeClientSessionOptions["permissionMode"]
|
|
173
|
+
| LegacyPermissionMode
|
|
174
|
+
| undefined,
|
|
175
|
+
): LettaCodeClientSessionOptions["permissionMode"] | undefined {
|
|
176
|
+
if (mode === undefined || mode === "default") {
|
|
177
|
+
return "standard";
|
|
178
|
+
}
|
|
179
|
+
if (mode === "bypassPermissions" || mode === "fullAccess") {
|
|
180
|
+
return "unrestricted";
|
|
181
|
+
}
|
|
182
|
+
if (
|
|
183
|
+
mode === "standard" ||
|
|
184
|
+
mode === "acceptEdits" ||
|
|
185
|
+
mode === "unrestricted"
|
|
186
|
+
) {
|
|
187
|
+
return mode;
|
|
188
|
+
}
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function mapPermissionMode(
|
|
193
|
+
mode:
|
|
194
|
+
| LettaCodeClientSessionOptions["permissionMode"]
|
|
195
|
+
| LegacyPermissionMode
|
|
196
|
+
| undefined,
|
|
197
|
+
): string | undefined {
|
|
198
|
+
return normalizePermissionMode(mode);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function isUnrestrictedPermissionMode(
|
|
202
|
+
mode:
|
|
203
|
+
| LettaCodeClientSessionOptions["permissionMode"]
|
|
204
|
+
| LegacyPermissionMode
|
|
205
|
+
| undefined,
|
|
206
|
+
): boolean {
|
|
207
|
+
return normalizePermissionMode(mode) === "unrestricted";
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function ensureSuccess(message: Record<string, unknown>, fallback: string): void {
|
|
211
|
+
if (message.success === false) {
|
|
212
|
+
throw new Error(typeof message.error === "string" ? message.error : fallback);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function toSdkErrorCode(value: string | null | undefined): SDKErrorCode | undefined {
|
|
217
|
+
if (!value || value.length === 0) return undefined;
|
|
218
|
+
return KNOWN_SDK_ERROR_CODES.has(value as SDKErrorCode)
|
|
219
|
+
? (value as SDKErrorCode)
|
|
220
|
+
: undefined;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function isReasoningEffort(value: unknown): value is ReasoningEffort {
|
|
224
|
+
return typeof value === "string" && REASONING_EFFORTS.has(value as ReasoningEffort);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function nonEmptyString(value: unknown, name: string): string | undefined {
|
|
228
|
+
if (value === undefined) return undefined;
|
|
229
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
230
|
+
throw new Error(`Invalid ${name}. Expected a non-empty string.`);
|
|
231
|
+
}
|
|
232
|
+
return value;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function normalizeUpdateModelInput(update: string | UpdateModelOptions): NormalizedUpdateModelInput {
|
|
236
|
+
if (typeof update === "string") {
|
|
237
|
+
if (update.length === 0) {
|
|
238
|
+
throw new Error("Invalid model. Expected a non-empty string.");
|
|
239
|
+
}
|
|
240
|
+
return { model: update };
|
|
241
|
+
}
|
|
242
|
+
if (!update || typeof update !== "object" || Array.isArray(update)) {
|
|
243
|
+
throw new Error("Invalid updateModel options. Expected a model string or options object.");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const model = nonEmptyString(update.model, "model");
|
|
247
|
+
const modelId = nonEmptyString(update.modelId, "modelId");
|
|
248
|
+
const modelHandle = nonEmptyString(update.modelHandle, "modelHandle");
|
|
249
|
+
const reasoningEffort = update.reasoningEffort;
|
|
250
|
+
if (reasoningEffort !== undefined && !isReasoningEffort(reasoningEffort)) {
|
|
251
|
+
throw new Error(
|
|
252
|
+
`Invalid reasoningEffort '${String(reasoningEffort)}'. Valid values: ${[...REASONING_EFFORTS].join(", ")}`,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
if (model !== undefined && (modelId !== undefined || modelHandle !== undefined)) {
|
|
256
|
+
throw new Error("Invalid updateModel options. Use either model or explicit modelId/modelHandle, not both.");
|
|
257
|
+
}
|
|
258
|
+
if (
|
|
259
|
+
model === undefined &&
|
|
260
|
+
modelId === undefined &&
|
|
261
|
+
modelHandle === undefined &&
|
|
262
|
+
reasoningEffort === undefined
|
|
263
|
+
) {
|
|
264
|
+
throw new Error("Invalid updateModel options. Provide model, modelId, modelHandle, or reasoningEffort.");
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
...(model !== undefined ? { model } : {}),
|
|
268
|
+
...(modelId !== undefined ? { modelId } : {}),
|
|
269
|
+
...(modelHandle !== undefined ? { modelHandle } : {}),
|
|
270
|
+
...(reasoningEffort !== undefined ? { reasoningEffort } : {}),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function modelPayloadWithoutReasoning(input: NormalizedUpdateModelInput): UpdateModelPayload {
|
|
275
|
+
const payload: UpdateModelPayload = {};
|
|
276
|
+
if (input.modelId !== undefined) payload.model_id = input.modelId;
|
|
277
|
+
if (input.modelHandle !== undefined) payload.model_handle = input.modelHandle;
|
|
278
|
+
if (input.model !== undefined) {
|
|
279
|
+
if (input.model.includes("/")) payload.model_handle = input.model;
|
|
280
|
+
else payload.model_id = input.model;
|
|
281
|
+
}
|
|
282
|
+
return payload;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function toBaseModelHandle(
|
|
286
|
+
handle: string | undefined,
|
|
287
|
+
byokProviderAliases: Record<string, string> | undefined,
|
|
288
|
+
): string | undefined {
|
|
289
|
+
if (!handle) return undefined;
|
|
290
|
+
const slashIndex = handle.indexOf("/");
|
|
291
|
+
if (slashIndex === -1) return handle;
|
|
292
|
+
const provider = handle.slice(0, slashIndex);
|
|
293
|
+
const model = handle.slice(slashIndex + 1);
|
|
294
|
+
const baseProvider = byokProviderAliases?.[provider];
|
|
295
|
+
return baseProvider ? `${baseProvider}/${model}` : handle;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export function getContextWindow(value: Record<string, unknown> | null | undefined): number | undefined {
|
|
299
|
+
const contextWindow = value?.context_window;
|
|
300
|
+
return typeof contextWindow === "number" ? contextWindow : undefined;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export function getReasoningEffort(entry: LettaCodeModelEntry): string | undefined {
|
|
304
|
+
const effort = entry.updateArgs?.reasoning_effort;
|
|
305
|
+
return typeof effort === "string" ? effort : undefined;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function sameContextCandidates(
|
|
309
|
+
candidates: LettaCodeModelEntry[],
|
|
310
|
+
contextWindow: number | undefined,
|
|
311
|
+
): LettaCodeModelEntry[] {
|
|
312
|
+
if (contextWindow === undefined) return candidates;
|
|
313
|
+
const matches = candidates.filter(
|
|
314
|
+
(entry) => getContextWindow(entry.updateArgs) === contextWindow,
|
|
315
|
+
);
|
|
316
|
+
return matches.length > 0 ? matches : candidates;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export function isApprovalConflictSignal(params: {
|
|
320
|
+
detail?: string;
|
|
321
|
+
message?: string;
|
|
322
|
+
stopReason?: string | null;
|
|
323
|
+
}): boolean {
|
|
324
|
+
if (params.stopReason === "requires_approval") return true;
|
|
325
|
+
const haystack = [params.detail, params.message]
|
|
326
|
+
.filter((value): value is string => typeof value === "string" && value.length > 0)
|
|
327
|
+
.join("\n")
|
|
328
|
+
.toLowerCase();
|
|
329
|
+
return (
|
|
330
|
+
haystack.includes("waiting for approval on a tool call") ||
|
|
331
|
+
haystack.includes("cannot send a new message") ||
|
|
332
|
+
haystack.includes("requires_approval")
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export function resolveDreamingSettings(
|
|
337
|
+
dreaming: LettaCodeClientSessionOptions["dreaming"],
|
|
338
|
+
): ReflectionSettings | null {
|
|
339
|
+
if (!dreaming) return null;
|
|
340
|
+
return {
|
|
341
|
+
trigger: dreaming.trigger ?? "step-count",
|
|
342
|
+
step_count: dreaming.stepCount ?? 5,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export function extractTextFromContent(content: unknown): string | null {
|
|
347
|
+
if (typeof content === "string") return content;
|
|
348
|
+
if (Array.isArray(content)) {
|
|
349
|
+
const pieces: string[] = [];
|
|
350
|
+
for (const part of content) {
|
|
351
|
+
if (typeof part === "string") {
|
|
352
|
+
pieces.push(part);
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
if (part && typeof part === "object") {
|
|
356
|
+
const record = part as Record<string, unknown>;
|
|
357
|
+
if (typeof record.text === "string") {
|
|
358
|
+
pieces.push(record.text);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
const joined = pieces.join("");
|
|
363
|
+
return joined.length > 0 ? joined : null;
|
|
364
|
+
}
|
|
365
|
+
if (content && typeof content === "object") {
|
|
366
|
+
const record = content as Record<string, unknown>;
|
|
367
|
+
if (typeof record.text === "string") return record.text;
|
|
368
|
+
}
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export function toolInputFromArguments(args: unknown): { input: Record<string, unknown>; raw?: string } {
|
|
373
|
+
if (args && typeof args === "object" && !Array.isArray(args)) {
|
|
374
|
+
return { input: args as Record<string, unknown> };
|
|
375
|
+
}
|
|
376
|
+
const raw = typeof args === "string" ? args : "";
|
|
377
|
+
if (!raw) return { input: {} };
|
|
378
|
+
try {
|
|
379
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
380
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
381
|
+
return { input: parsed as Record<string, unknown>, raw };
|
|
382
|
+
}
|
|
383
|
+
} catch {
|
|
384
|
+
// Fall through to raw wrapper.
|
|
385
|
+
}
|
|
386
|
+
return { input: { raw }, raw };
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export function firstToolCall(delta: Record<string, unknown>): Record<string, unknown> | undefined {
|
|
390
|
+
const toolCalls = delta.tool_calls;
|
|
391
|
+
if (Array.isArray(toolCalls)) {
|
|
392
|
+
const first = toolCalls[0];
|
|
393
|
+
return first && typeof first === "object" ? (first as Record<string, unknown>) : undefined;
|
|
394
|
+
}
|
|
395
|
+
if (toolCalls && typeof toolCalls === "object") {
|
|
396
|
+
return toolCalls as Record<string, unknown>;
|
|
397
|
+
}
|
|
398
|
+
const toolCall = delta.tool_call;
|
|
399
|
+
return toolCall && typeof toolCall === "object" ? (toolCall as Record<string, unknown>) : undefined;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export function firstToolReturn(delta: Record<string, unknown>): Record<string, unknown> | undefined {
|
|
403
|
+
const toolReturns = delta.tool_returns;
|
|
404
|
+
if (Array.isArray(toolReturns)) {
|
|
405
|
+
const first = toolReturns[0];
|
|
406
|
+
return first && typeof first === "object" ? (first as Record<string, unknown>) : undefined;
|
|
407
|
+
}
|
|
408
|
+
return undefined;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export function sameRuntime(message: ProtocolMessage, runtime: RuntimeScope): boolean {
|
|
412
|
+
const msgRuntime = message.runtime;
|
|
413
|
+
if (msgRuntime) {
|
|
414
|
+
return (
|
|
415
|
+
msgRuntime.agent_id === runtime.agent_id &&
|
|
416
|
+
msgRuntime.conversation_id === runtime.conversation_id
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
const messageAgentId =
|
|
420
|
+
typeof message.agent_id === "string"
|
|
421
|
+
? message.agent_id
|
|
422
|
+
: typeof message.agentId === "string"
|
|
423
|
+
? message.agentId
|
|
424
|
+
: undefined;
|
|
425
|
+
const messageConversationId =
|
|
426
|
+
typeof message.conversation_id === "string"
|
|
427
|
+
? message.conversation_id
|
|
428
|
+
: typeof message.conversationId === "string"
|
|
429
|
+
? message.conversationId
|
|
430
|
+
: undefined;
|
|
431
|
+
|
|
432
|
+
if (messageAgentId && messageAgentId !== runtime.agent_id) return false;
|
|
433
|
+
if (messageConversationId && messageConversationId !== runtime.conversation_id) return false;
|
|
434
|
+
return true;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export function streamDeltaRecord(message: ProtocolMessage): Record<string, unknown> | null {
|
|
438
|
+
if (message.type !== "stream_delta") return null;
|
|
439
|
+
const delta = message.delta;
|
|
440
|
+
return delta && typeof delta === "object" && !Array.isArray(delta)
|
|
441
|
+
? (delta as Record<string, unknown>)
|
|
442
|
+
: null;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export function streamDeltaMessageType(delta: Record<string, unknown>): string | undefined {
|
|
446
|
+
return typeof delta.message_type === "string" ? delta.message_type : undefined;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export function streamDeltaRunId(delta: Record<string, unknown>): string | undefined {
|
|
450
|
+
return typeof delta.run_id === "string" ? delta.run_id : undefined;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export function streamDeltaStopReason(delta: Record<string, unknown>): string | null | undefined {
|
|
454
|
+
return typeof delta.stop_reason === "string" ? delta.stop_reason : undefined;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function loopStatusRecord(message: ProtocolMessage): Record<string, unknown> | null {
|
|
458
|
+
if (message.type !== "update_loop_status") return null;
|
|
459
|
+
const loopStatus = message.loop_status;
|
|
460
|
+
return loopStatus && typeof loopStatus === "object" && !Array.isArray(loopStatus)
|
|
461
|
+
? (loopStatus as Record<string, unknown>)
|
|
462
|
+
: null;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
export function loopStatusValue(message: ProtocolMessage): string | undefined {
|
|
466
|
+
const loopStatus = loopStatusRecord(message);
|
|
467
|
+
return typeof loopStatus?.status === "string" ? loopStatus.status : undefined;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
export function loopStatusRunIds(message: ProtocolMessage): string[] {
|
|
471
|
+
const activeRunIds = loopStatusRecord(message)?.active_run_ids;
|
|
472
|
+
return Array.isArray(activeRunIds)
|
|
473
|
+
? activeRunIds.filter((runId): runId is string => typeof runId === "string")
|
|
474
|
+
: [];
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
export function queueItems(message: ProtocolMessage): SDKQueueItem[] {
|
|
478
|
+
const queue = message.queue;
|
|
479
|
+
if (!Array.isArray(queue)) return [];
|
|
480
|
+
return queue.flatMap((item) => {
|
|
481
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return [];
|
|
482
|
+
const record = item as Record<string, unknown>;
|
|
483
|
+
if (typeof record.id !== "string") return [];
|
|
484
|
+
return [
|
|
485
|
+
{
|
|
486
|
+
id: record.id,
|
|
487
|
+
clientMessageId:
|
|
488
|
+
typeof record.client_message_id === "string" ? record.client_message_id : "",
|
|
489
|
+
kind: typeof record.kind === "string" ? record.kind : "message",
|
|
490
|
+
source: typeof record.source === "string" ? record.source : "user",
|
|
491
|
+
content: record.content,
|
|
492
|
+
enqueuedAt: typeof record.enqueued_at === "string" ? record.enqueued_at : "",
|
|
493
|
+
},
|
|
494
|
+
];
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
export function deviceStatusRecord(message: ProtocolMessage): Record<string, unknown> | null {
|
|
499
|
+
if (message.type !== "update_device_status") return null;
|
|
500
|
+
const status = message.device_status;
|
|
501
|
+
return status && typeof status === "object" && !Array.isArray(status)
|
|
502
|
+
? (status as Record<string, unknown>)
|
|
503
|
+
: null;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function pendingControlRequests(
|
|
507
|
+
status: Record<string, unknown>,
|
|
508
|
+
): SessionPendingControlRequest[] {
|
|
509
|
+
const pending = status.pending_control_requests;
|
|
510
|
+
if (!Array.isArray(pending)) return [];
|
|
511
|
+
return pending.flatMap((item) => {
|
|
512
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return [];
|
|
513
|
+
const record = item as Record<string, unknown>;
|
|
514
|
+
if (typeof record.request_id !== "string") return [];
|
|
515
|
+
const request =
|
|
516
|
+
record.request && typeof record.request === "object" && !Array.isArray(record.request)
|
|
517
|
+
? (record.request as Record<string, unknown>)
|
|
518
|
+
: null;
|
|
519
|
+
if (!request || typeof request.tool_name !== "string") return [];
|
|
520
|
+
const entry: SessionPendingControlRequest = {
|
|
521
|
+
requestId: record.request_id,
|
|
522
|
+
toolName: request.tool_name,
|
|
523
|
+
permissionSuggestions: permissionSuggestions(
|
|
524
|
+
request.permission_suggestions,
|
|
525
|
+
),
|
|
526
|
+
blockedPath:
|
|
527
|
+
typeof request.blocked_path === "string" ||
|
|
528
|
+
request.blocked_path === null
|
|
529
|
+
? request.blocked_path
|
|
530
|
+
: null,
|
|
531
|
+
};
|
|
532
|
+
if (typeof request.tool_call_id === "string") entry.toolCallId = request.tool_call_id;
|
|
533
|
+
if (request.input && typeof request.input === "object" && !Array.isArray(request.input)) {
|
|
534
|
+
entry.toolInput = request.input as Record<string, unknown>;
|
|
535
|
+
}
|
|
536
|
+
const previews = diffPreviews(request.diffs);
|
|
537
|
+
if (previews !== undefined) entry.diffs = previews;
|
|
538
|
+
return [entry];
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function permissionSuggestions(
|
|
543
|
+
value: unknown,
|
|
544
|
+
): SessionPermissionSuggestion[] {
|
|
545
|
+
if (!Array.isArray(value)) return [];
|
|
546
|
+
return value.flatMap((item) => {
|
|
547
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return [];
|
|
548
|
+
const record = item as Record<string, unknown>;
|
|
549
|
+
return typeof record.id === "string" && typeof record.text === "string"
|
|
550
|
+
? [{ id: record.id, text: record.text }]
|
|
551
|
+
: [];
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function diffPreviews(value: unknown): SessionDiffPreview[] | undefined {
|
|
556
|
+
if (!Array.isArray(value)) return undefined;
|
|
557
|
+
return value.flatMap<SessionDiffPreview>((item) => {
|
|
558
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return [];
|
|
559
|
+
const record = item as Record<string, unknown>;
|
|
560
|
+
if (
|
|
561
|
+
record.mode === "advanced" &&
|
|
562
|
+
typeof record.fileName === "string" &&
|
|
563
|
+
Array.isArray(record.hunks)
|
|
564
|
+
) {
|
|
565
|
+
return [{
|
|
566
|
+
mode: "advanced" as const,
|
|
567
|
+
fileName: record.fileName,
|
|
568
|
+
hunks: diffHunks(record.hunks),
|
|
569
|
+
}];
|
|
570
|
+
}
|
|
571
|
+
if (
|
|
572
|
+
(record.mode === "fallback" || record.mode === "unpreviewable") &&
|
|
573
|
+
typeof record.fileName === "string" &&
|
|
574
|
+
typeof record.reason === "string"
|
|
575
|
+
) {
|
|
576
|
+
return [{
|
|
577
|
+
mode: record.mode,
|
|
578
|
+
fileName: record.fileName,
|
|
579
|
+
reason: record.reason,
|
|
580
|
+
}];
|
|
581
|
+
}
|
|
582
|
+
return [];
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function diffHunks(value: unknown[]): SessionDiffHunk[] {
|
|
587
|
+
return value.flatMap<SessionDiffHunk>((item) => {
|
|
588
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return [];
|
|
589
|
+
const record = item as Record<string, unknown>;
|
|
590
|
+
if (
|
|
591
|
+
typeof record.oldStart !== "number" ||
|
|
592
|
+
typeof record.oldLines !== "number" ||
|
|
593
|
+
typeof record.newStart !== "number" ||
|
|
594
|
+
typeof record.newLines !== "number" ||
|
|
595
|
+
!Array.isArray(record.lines)
|
|
596
|
+
) {
|
|
597
|
+
return [];
|
|
598
|
+
}
|
|
599
|
+
const lines: SessionDiffHunk["lines"] = [];
|
|
600
|
+
for (const line of record.lines) {
|
|
601
|
+
if (!line || typeof line !== "object" || Array.isArray(line)) continue;
|
|
602
|
+
const lineRecord = line as Record<string, unknown>;
|
|
603
|
+
const type = lineRecord.type;
|
|
604
|
+
if (
|
|
605
|
+
(
|
|
606
|
+
type !== "context" &&
|
|
607
|
+
type !== "add" &&
|
|
608
|
+
type !== "remove"
|
|
609
|
+
) ||
|
|
610
|
+
typeof lineRecord.content !== "string"
|
|
611
|
+
) {
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
lines.push({
|
|
615
|
+
type,
|
|
616
|
+
content: lineRecord.content,
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
return [{
|
|
620
|
+
oldStart: record.oldStart,
|
|
621
|
+
oldLines: record.oldLines,
|
|
622
|
+
newStart: record.newStart,
|
|
623
|
+
newLines: record.newLines,
|
|
624
|
+
lines,
|
|
625
|
+
}];
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
export function toSessionDeviceStatus(
|
|
630
|
+
status: Record<string, unknown>,
|
|
631
|
+
): SessionDeviceStatus | null {
|
|
632
|
+
const permissionMode = normalizePermissionMode(
|
|
633
|
+
status.current_permission_mode as
|
|
634
|
+
| LettaCodeClientSessionOptions["permissionMode"]
|
|
635
|
+
| undefined,
|
|
636
|
+
);
|
|
637
|
+
if (
|
|
638
|
+
typeof status.is_online !== "boolean" ||
|
|
639
|
+
typeof status.is_processing !== "boolean" ||
|
|
640
|
+
permissionMode === undefined ||
|
|
641
|
+
!(
|
|
642
|
+
typeof status.current_working_directory === "string" ||
|
|
643
|
+
status.current_working_directory === null
|
|
644
|
+
)
|
|
645
|
+
) {
|
|
646
|
+
return null;
|
|
647
|
+
}
|
|
648
|
+
return {
|
|
649
|
+
isOnline: status.is_online,
|
|
650
|
+
isProcessing: status.is_processing,
|
|
651
|
+
permissionMode,
|
|
652
|
+
workingDirectory: status.current_working_directory,
|
|
653
|
+
pendingControlRequests: pendingControlRequests(status),
|
|
654
|
+
raw: { ...status },
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
export function normalizeSendMessage(message: SendMessage): string | MessageContentItem[] {
|
|
659
|
+
return message;
|
|
660
|
+
}
|