@letta-ai/letta-agent-sdk 0.8.0 → 0.8.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.
Files changed (51) hide show
  1. package/README.md +23 -0
  2. package/dist/agent-creation.d.ts +2 -1
  3. package/dist/agent-creation.d.ts.map +1 -1
  4. package/dist/app-server-session.d.ts.map +1 -1
  5. package/dist/client-base.d.ts +12 -0
  6. package/dist/client-base.d.ts.map +1 -1
  7. package/dist/client-entry.d.ts +1 -0
  8. package/dist/client-entry.d.ts.map +1 -1
  9. package/dist/client-entry.js +509 -2527
  10. package/dist/client-entry.js.map +14 -12
  11. package/dist/client.d.ts +4 -0
  12. package/dist/client.d.ts.map +1 -1
  13. package/dist/cloud-session.d.ts +4 -1
  14. package/dist/cloud-session.d.ts.map +1 -1
  15. package/dist/index.d.ts +5 -1
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +795 -2645
  18. package/dist/index.js.map +18 -15
  19. package/dist/query-types.d.ts +25 -0
  20. package/dist/query-types.d.ts.map +1 -0
  21. package/dist/query.d.ts +7 -0
  22. package/dist/query.d.ts.map +1 -0
  23. package/dist/remote-session-protocol.d.ts +11 -1
  24. package/dist/remote-session-protocol.d.ts.map +1 -1
  25. package/dist/remote-turn-coordinator.d.ts.map +1 -1
  26. package/dist/repository-types.d.ts +89 -0
  27. package/dist/repository-types.d.ts.map +1 -0
  28. package/dist/skill-loading.d.ts +84 -0
  29. package/dist/skill-loading.d.ts.map +1 -0
  30. package/dist/skill-node.d.ts +28 -0
  31. package/dist/skill-node.d.ts.map +1 -0
  32. package/dist/types.d.ts +17 -84
  33. package/dist/types.d.ts.map +1 -1
  34. package/package.json +2 -2
  35. package/src/agent-creation.ts +37 -1
  36. package/src/app-server-session.ts +35 -2
  37. package/src/client-base.ts +138 -1
  38. package/src/client-entry.ts +1 -0
  39. package/src/client.ts +39 -0
  40. package/src/cloud-session.ts +84 -16
  41. package/src/index.ts +16 -0
  42. package/src/local-app-server.ts +1 -1
  43. package/src/query-types.ts +40 -0
  44. package/src/query.ts +43 -0
  45. package/src/remote-client-session-core.ts +1 -1
  46. package/src/remote-session-protocol.ts +12 -1
  47. package/src/remote-turn-coordinator.ts +5 -1
  48. package/src/repository-types.ts +105 -0
  49. package/src/skill-loading.ts +194 -0
  50. package/src/skill-node.ts +139 -0
  51. package/src/types.ts +42 -100
@@ -657,8 +657,16 @@ export class AppServerSession extends RemoteClientSessionCore {
657
657
  clientToolset,
658
658
  ),
659
659
  runtime: response.runtime,
660
- model: typeof response.agent?.model === "string" ? response.agent.model : "",
661
- modelSettings: objectRecord(response.agent?.model_settings) ?? null,
660
+ model:
661
+ typeof response.agent?.model === "string"
662
+ ? response.agent.model
663
+ : typeof response.conversation?.model === "string"
664
+ ? response.conversation.model
665
+ : "",
666
+ modelSettings:
667
+ objectRecord(response.agent?.model_settings) ??
668
+ objectRecord(response.conversation?.model_settings) ??
669
+ null,
662
670
  ...(availableTools !== undefined ? { tools: availableTools } : {}),
663
671
  ...(skillSources !== undefined ? { skillSources: [...skillSources] } : {}),
664
672
  };
@@ -763,6 +771,31 @@ export class AppServerSession extends RemoteClientSessionCore {
763
771
  return command as RuntimeStartCommand;
764
772
  }
765
773
 
774
+ if (this.mode.kind === "agent-free") {
775
+ if (this.mode.createConversation) {
776
+ const create = this.mode.createConversation;
777
+ command.create_conversation = {
778
+ body: {
779
+ model: create.model,
780
+ system: create.system,
781
+ ...(create.modelSettings !== undefined
782
+ ? { model_settings: create.modelSettings }
783
+ : {}),
784
+ ...(create.contextWindowLimit !== undefined
785
+ ? { context_window_limit: create.contextWindowLimit }
786
+ : {}),
787
+ },
788
+ };
789
+ } else if (this.mode.conversationId) {
790
+ command.conversation_id = this.mode.conversationId;
791
+ } else {
792
+ throw new Error(
793
+ "Agent-free sessions require a conversation to create or resume.",
794
+ );
795
+ }
796
+ return command as RuntimeStartCommand;
797
+ }
798
+
766
799
  if (this.mode.agentId) {
767
800
  command.agent_id = this.mode.agentId;
768
801
  if (this.mode.newConversation) {
@@ -1,5 +1,6 @@
1
1
  import type Letta from "@letta-ai/letta-client";
2
2
  import { RepositoriesClient } from "./repositories.js";
3
+ import { resolveSkillItems, type SkillNodeSupport } from "./skill-loading.js";
3
4
  import { createAgentRepositoriesClient } from "./agent-repositories.js";
4
5
  import { AppServerManagementTransport } from "./app-server-management.js";
5
6
  import {
@@ -31,6 +32,12 @@ import type {
31
32
  ConversationsClient,
32
33
  ModelsClient,
33
34
  } from "./management-types.js";
35
+ import { createQuery } from "./query.js";
36
+ import type {
37
+ AgentFreeQueryOptions,
38
+ Query,
39
+ QueryParams,
40
+ } from "./query-types.js";
34
41
  import type {
35
42
  CreateAgentOptions,
36
43
  CreateSessionOptions,
@@ -93,6 +100,28 @@ function looksLikeConversationId(id: string): boolean {
93
100
  return id.startsWith("conv-") || id.startsWith("local-conv-");
94
101
  }
95
102
 
103
+ function agentFreeSessionOptions(
104
+ options: AgentFreeQueryOptions,
105
+ ): LettaCodeClientSessionOptions {
106
+ const {
107
+ system: _system,
108
+ modelSettings: _modelSettings,
109
+ contextWindowLimit: _contextWindowLimit,
110
+ ...sessionOptions
111
+ } = options;
112
+ return sessionOptions;
113
+ }
114
+
115
+ function validateAgentFreeQueryOptions(options: AgentFreeQueryOptions): void {
116
+ if (typeof options.model !== "string" || options.model.length === 0) {
117
+ throw new Error("query() requires a non-empty model.");
118
+ }
119
+ if (typeof options.system !== "string") {
120
+ throw new Error("query() requires a system prompt.");
121
+ }
122
+ validateCreateSessionOptions(agentFreeSessionOptions(options));
123
+ }
124
+
96
125
  type OneShotSession = LettaCodeSession & {
97
126
  sendAndWaitForResult(message: SendMessage): Promise<SDKResultMessage>;
98
127
  };
@@ -227,6 +256,18 @@ export class LettaAgentClientBase {
227
256
 
228
257
  validateCreateAgentOptions(options);
229
258
 
259
+ // Resolve skill directory paths to inline skills up front so every
260
+ // backend receives the same shape. Directory loading is Node-only; the
261
+ // portable client resolves inline skills and rejects paths.
262
+ if (options.skills !== undefined && options.skills.length > 0) {
263
+ const nodeSupport = this.skillNodeSupport();
264
+ const skills = await resolveSkillItems(
265
+ options.skills,
266
+ nodeSupport?.loadSkillDirectory,
267
+ );
268
+ options = { ...options, skills };
269
+ }
270
+
230
271
  if (this.backend === "remote") {
231
272
  const session = new AppServerSession(this.appServerSessionOptions(), {
232
273
  kind: "create-agent",
@@ -234,10 +275,17 @@ export class LettaAgentClientBase {
234
275
  });
235
276
  const initMsg = await session.initialize();
236
277
  session.close();
278
+ if (!initMsg.agentId) {
279
+ throw new Error("App Server agent creation did not return an agent id.");
280
+ }
237
281
  return initMsg.agentId;
238
282
  }
239
283
  if (this.backend === "cloud") {
240
- return createCloudAgent(this.getCloudClient(), options);
284
+ return createCloudAgent(
285
+ this.getCloudClient(),
286
+ options,
287
+ this.skillNodeSupport()?.pushSkillSupportFiles,
288
+ );
241
289
  }
242
290
  return this.createLocalAgent(options);
243
291
  }
@@ -354,6 +402,79 @@ export class LettaAgentClientBase {
354
402
  }
355
403
  }
356
404
 
405
+ /** Run one prompt in a new agent-free ephemeral conversation. */
406
+ query(params: QueryParams): Query {
407
+ validateAgentFreeQueryOptions(params.options);
408
+ return createQuery((options) => this.createAgentFreeSession(options), params);
409
+ }
410
+
411
+ private async createAgentFreeSession(
412
+ options: AgentFreeQueryOptions,
413
+ ): Promise<LettaCodeSession> {
414
+ validateAgentFreeQueryOptions(options);
415
+ const sessionOptions = agentFreeSessionOptions(options);
416
+ this.assertSessionBackend("query", sessionOptions);
417
+
418
+ if (this.backend === "remote") {
419
+ return new AppServerSession(this.appServerSessionOptions(), {
420
+ kind: "agent-free",
421
+ createConversation: {
422
+ model: options.model,
423
+ system: options.system,
424
+ ...(options.modelSettings !== undefined
425
+ ? { modelSettings: options.modelSettings }
426
+ : {}),
427
+ ...(options.contextWindowLimit !== undefined
428
+ ? { contextWindowLimit: options.contextWindowLimit }
429
+ : {}),
430
+ },
431
+ options: sessionOptions,
432
+ });
433
+ }
434
+ if (this.backend === "cloud") {
435
+ const computer = options.computer ?? options.environment ?? this.computer;
436
+ if (computer === undefined) {
437
+ throw new Error(
438
+ "Cloud query() requires an explicit computer; managed sandboxes are agent-scoped.",
439
+ );
440
+ }
441
+ const conversation = await this.getCloudClient().post<unknown>(
442
+ "/v1/conversations/ephemeral",
443
+ {
444
+ body: {
445
+ model: options.model,
446
+ system: options.system,
447
+ ...(options.modelSettings !== undefined
448
+ ? { model_settings: options.modelSettings }
449
+ : {}),
450
+ ...(options.contextWindowLimit !== undefined
451
+ ? { context_window_limit: options.contextWindowLimit }
452
+ : {}),
453
+ },
454
+ },
455
+ );
456
+ if (
457
+ !conversation ||
458
+ typeof conversation !== "object" ||
459
+ typeof (conversation as { id?: unknown }).id !== "string"
460
+ ) {
461
+ throw new Error(
462
+ "Cloud ephemeral conversation response did not include a conversation id.",
463
+ );
464
+ }
465
+ return new CloudEnvironmentSession(
466
+ this.cloudOptions(),
467
+ {
468
+ kind: "agent-free",
469
+ conversationId: (conversation as { id: string }).id,
470
+ options: sessionOptions,
471
+ },
472
+ this.getCloudClient(),
473
+ );
474
+ }
475
+ return this.createLocalAgentFreeSession(options, sessionOptions);
476
+ }
477
+
357
478
  private assertSessionBackend(
358
479
  action: string,
359
480
  options: LettaCodeClientSessionOptions,
@@ -451,6 +572,15 @@ export class LettaAgentClientBase {
451
572
  throw this.localBackendUnavailableError();
452
573
  }
453
574
 
575
+ /**
576
+ * Node-only skill seeding support (directory loading, memory-repo git
577
+ * push). The Node client overrides this; the portable client returns
578
+ * undefined, which limits skills to inline SKILL.md-only objects.
579
+ */
580
+ protected skillNodeSupport(): SkillNodeSupport | undefined {
581
+ return undefined;
582
+ }
583
+
454
584
  protected createLocalSession(
455
585
  _agentId: string,
456
586
  _options: LettaCodeClientSessionOptions,
@@ -458,6 +588,13 @@ export class LettaAgentClientBase {
458
588
  throw this.localBackendUnavailableError();
459
589
  }
460
590
 
591
+ protected createLocalAgentFreeSession(
592
+ _queryOptions: AgentFreeQueryOptions,
593
+ _sessionOptions: LettaCodeClientSessionOptions,
594
+ ): LettaCodeSession {
595
+ throw this.localBackendUnavailableError();
596
+ }
597
+
461
598
  protected resumeLocalSession(
462
599
  _id: string,
463
600
  _options: LettaCodeClientSessionOptions,
@@ -54,3 +54,4 @@ export type {
54
54
  } from "./transcript-accumulator.js";
55
55
  export { createReactNativeWebSocketConstructor } from "./websocket.js";
56
56
  export type * from "./types.js";
57
+ export type * from "./query-types.js";
package/src/client.ts CHANGED
@@ -3,6 +3,9 @@ import { AppServerManagementTransport } from "./app-server-management.js";
3
3
  import type { ManagementTransport } from "./management.js";
4
4
  import { createLocalAppServerSession } from "./local-app-server-session.js";
5
5
  import { startLocalAppServer } from "./local-app-server.js";
6
+ import type { AgentFreeQueryOptions } from "./query-types.js";
7
+ import type { SkillNodeSupport } from "./skill-loading.js";
8
+ import { loadSkillDirectory, pushSkillSupportFiles } from "./skill-node.js";
6
9
  import type {
7
10
  CreateAgentOptions,
8
11
  LettaCodeClientSessionOptions,
@@ -11,6 +14,10 @@ import type {
11
14
  } from "./types.js";
12
15
 
13
16
  export class LettaAgentClient extends LettaAgentClientBase {
17
+ protected override skillNodeSupport(): SkillNodeSupport {
18
+ return { loadSkillDirectory, pushSkillSupportFiles };
19
+ }
20
+
14
21
  protected override createLocalManagementTransport(): ManagementTransport {
15
22
  const localOptions = (
16
23
  this.options as LettaCodeLocalClientOptions
@@ -48,6 +55,9 @@ export class LettaAgentClient extends LettaAgentClientBase {
48
55
  );
49
56
  const initMsg = await session.initialize();
50
57
  session.close();
58
+ if (!initMsg.agentId) {
59
+ throw new Error("Local App Server agent creation did not return an agent id.");
60
+ }
51
61
  return initMsg.agentId;
52
62
  }
53
63
 
@@ -67,6 +77,35 @@ export class LettaAgentClient extends LettaAgentClientBase {
67
77
  );
68
78
  }
69
79
 
80
+ protected override createLocalAgentFreeSession(
81
+ queryOptions: AgentFreeQueryOptions,
82
+ sessionOptions: LettaCodeClientSessionOptions,
83
+ ): LettaCodeSession {
84
+ const localOptions = this.options as LettaCodeLocalClientOptions;
85
+ if (
86
+ localOptions.appServer?.url === undefined &&
87
+ (localOptions.appServer?.harnessBackend ?? "local") === "local"
88
+ ) {
89
+ throw new Error(
90
+ 'query() requires the API-backed App Server. Set appServer.harnessBackend to "api" or connect to an API-backed remote App Server.',
91
+ );
92
+ }
93
+ return createLocalAppServerSession(localOptions.appServer, {
94
+ kind: "agent-free",
95
+ createConversation: {
96
+ model: queryOptions.model,
97
+ system: queryOptions.system,
98
+ ...(queryOptions.modelSettings !== undefined
99
+ ? { modelSettings: queryOptions.modelSettings }
100
+ : {}),
101
+ ...(queryOptions.contextWindowLimit !== undefined
102
+ ? { contextWindowLimit: queryOptions.contextWindowLimit }
103
+ : {}),
104
+ },
105
+ options: sessionOptions,
106
+ });
107
+ }
108
+
70
109
  protected override resumeLocalSession(
71
110
  id: string,
72
111
  options: LettaCodeClientSessionOptions,
@@ -6,6 +6,11 @@ import {
6
6
  type AppServerClient,
7
7
  } from "@letta-ai/letta-code/app-server-client";
8
8
  import { createAgentBody } from "./agent-creation.js";
9
+ import {
10
+ resolveSkillItems,
11
+ skillsHaveSupportFiles,
12
+ type SkillFilesPusher,
13
+ } from "./skill-loading.js";
9
14
  import {
10
15
  AppServerRuntimeController,
11
16
  agentToolNames,
@@ -78,7 +83,12 @@ type CloudRuntimeStartResponse = ProtocolMessage & {
78
83
  model?: string | null;
79
84
  model_settings?: Record<string, unknown> | null;
80
85
  }) | null;
81
- conversation: (Record<string, unknown> & { id?: string; agent_id?: string }) | null;
86
+ conversation: (Record<string, unknown> & {
87
+ id?: string;
88
+ agent_id?: string | null;
89
+ model?: string | null;
90
+ model_settings?: Record<string, unknown> | null;
91
+ }) | null;
82
92
  error?: string;
83
93
  };
84
94
 
@@ -143,7 +153,10 @@ export class CloudManagedSandboxExpiredError extends Error {
143
153
  }
144
154
  }
145
155
 
146
- type CloudSessionMode = Extract<RuntimeSessionMode, { kind: "session" }>;
156
+ type CloudSessionMode = Extract<
157
+ RuntimeSessionMode,
158
+ { kind: "session" } | { kind: "agent-free" }
159
+ >;
147
160
 
148
161
  function getWebSocketConstructor(
149
162
  websocketOverride?: LettaCodeSocketConstructor,
@@ -226,7 +239,7 @@ function environmentToRemoteTarget(
226
239
  function buildCloudStatusWebSocketUrl(params: {
227
240
  apiBaseUrl?: string;
228
241
  connectionId: string;
229
- agentId: string;
242
+ agentId: string | null;
230
243
  conversationId: string;
231
244
  apiKey?: string;
232
245
  authMode: "header" | "query";
@@ -240,7 +253,7 @@ function buildCloudStatusWebSocketUrl(params: {
240
253
  throw new Error(`Unsupported cloud apiBaseUrl protocol: ${base.protocol}`);
241
254
  }
242
255
  base.pathname = `/v1/environments/${encodeURIComponent(params.connectionId)}/status/ws`;
243
- base.searchParams.set("agentId", params.agentId);
256
+ if (params.agentId) base.searchParams.set("agentId", params.agentId);
244
257
  base.searchParams.set("conversationId", params.conversationId);
245
258
  base.searchParams.set("channel", "stream");
246
259
  if (params.authMode === "query" && params.apiKey) {
@@ -301,12 +314,38 @@ function externalToolsByName(tools: AnyAgentTool[] | undefined): Map<string, Any
301
314
  export async function createCloudAgent(
302
315
  client: Letta,
303
316
  agentOptions: CreateAgentOptions,
317
+ pushSkillSupportFiles?: SkillFilesPusher,
304
318
  ): Promise<string> {
305
- const body = await createAgentBody(agentOptions);
319
+ // Directory paths were already resolved to inline skills by the client
320
+ // facade; this validates inline skills passed directly.
321
+ const skills = await resolveSkillItems(agentOptions.skills);
322
+ const hasSupportFiles = skillsHaveSupportFiles(skills);
323
+ if (hasSupportFiles && pushSkillSupportFiles === undefined) {
324
+ throw new Error(
325
+ "Skill support files (scripts/, references/) require the Node.js " +
326
+ "package root; the portable client seeds SKILL.md-only skills.",
327
+ );
328
+ }
329
+ const body = await createAgentBody(agentOptions, skills);
306
330
  const agent = await client.agents.create(body as AgentCreateParams);
307
331
  if (typeof agent.id !== "string" || agent.id.length === 0) {
308
332
  throw new Error("Cloud create agent response did not include an agent id.");
309
333
  }
334
+ // SKILL.md contents rode the create request as memory blocks; support
335
+ // files (scripts, references) have no block representation and go to the
336
+ // agent's memory git repo in one follow-up commit.
337
+ if (hasSupportFiles && pushSkillSupportFiles) {
338
+ if (typeof client.apiKey !== "string" || client.apiKey.length === 0) {
339
+ throw new Error(
340
+ `Agent ${agent.id} was created, but skill support files need an API ` +
341
+ "key to push to the agent's memory repo and none is configured.",
342
+ );
343
+ }
344
+ await pushSkillSupportFiles(
345
+ { apiBaseUrl: client.baseURL, apiKey: client.apiKey, agentId: agent.id },
346
+ skills,
347
+ );
348
+ }
310
349
  return agent.id;
311
350
  }
312
351
 
@@ -364,10 +403,12 @@ export class CloudEnvironmentSession extends RemoteClientSessionCore {
364
403
  const resolved = await this.resolveRuntime();
365
404
  const connection = await this.resolveConnectionForRuntime(resolved.runtime).catch(
366
405
  async (error: unknown) => {
367
- await this.cleanupSessionRepositories(
368
- resolved.runtime.agent_id,
369
- resolved.runtime.conversation_id,
370
- );
406
+ if (resolved.runtime.agent_id) {
407
+ await this.cleanupSessionRepositories(
408
+ resolved.runtime.agent_id,
409
+ resolved.runtime.conversation_id,
410
+ );
411
+ }
371
412
  throw error;
372
413
  },
373
414
  );
@@ -393,10 +434,12 @@ export class CloudEnvironmentSession extends RemoteClientSessionCore {
393
434
  } catch (error) {
394
435
  await this.closeMcpBridge();
395
436
  await this.cleanupManagedSandbox();
396
- await this.cleanupSessionRepositories(
397
- resolved.runtime.agent_id,
398
- resolved.runtime.conversation_id,
399
- );
437
+ if (resolved.runtime.agent_id) {
438
+ await this.cleanupSessionRepositories(
439
+ resolved.runtime.agent_id,
440
+ resolved.runtime.conversation_id,
441
+ );
442
+ }
400
443
  throw error;
401
444
  }
402
445
  }
@@ -438,8 +481,16 @@ export class CloudEnvironmentSession extends RemoteClientSessionCore {
438
481
  clientToolset,
439
482
  ),
440
483
  runtime: response.runtime,
441
- model: typeof response.agent?.model === "string" ? response.agent.model : "",
442
- modelSettings: response.agent?.model_settings ?? null,
484
+ model:
485
+ typeof response.agent?.model === "string"
486
+ ? response.agent.model
487
+ : typeof response.conversation?.model === "string"
488
+ ? response.conversation.model
489
+ : "",
490
+ modelSettings:
491
+ response.agent?.model_settings ??
492
+ response.conversation?.model_settings ??
493
+ null,
443
494
  ...(availableTools !== undefined ? { tools: availableTools } : {}),
444
495
  ...(skillSources !== undefined ? { skillSources: [...skillSources] } : {}),
445
496
  };
@@ -590,11 +641,11 @@ export class CloudEnvironmentSession extends RemoteClientSessionCore {
590
641
  name: SDK_AGENT_ORIGIN,
591
642
  title: "Letta Agent SDK",
592
643
  },
593
- agent_id: runtime.agent_id,
594
644
  conversation_id: runtime.conversation_id,
595
645
  recover_approvals: false,
596
646
  force_device_status: true,
597
647
  };
648
+ if (runtime.agent_id) command.agent_id = runtime.agent_id;
598
649
 
599
650
  const mode = mapPermissionMode(options.permissionMode);
600
651
  if (mode) command.mode = mode;
@@ -676,6 +727,18 @@ export class CloudEnvironmentSession extends RemoteClientSessionCore {
676
727
  }
677
728
 
678
729
  private async resolveRuntime(): Promise<{ runtime: RuntimeScope }> {
730
+ if (this.cloudMode.kind === "agent-free") {
731
+ if (!this.cloudMode.conversationId) {
732
+ throw new Error("Cloud agent-free sessions require a conversation id.");
733
+ }
734
+ return {
735
+ runtime: {
736
+ agent_id: null,
737
+ conversation_id: this.cloudMode.conversationId,
738
+ },
739
+ };
740
+ }
741
+
679
742
  let agentId = this.cloudMode.agentId;
680
743
  let conversationId = this.cloudMode.conversationId;
681
744
 
@@ -890,6 +953,11 @@ export class CloudEnvironmentSession extends RemoteClientSessionCore {
890
953
  private async createManagedSandboxConnection(
891
954
  runtime: RuntimeScope,
892
955
  ): Promise<ResolvedCloudConnection> {
956
+ if (!runtime.agent_id) {
957
+ throw new Error(
958
+ "Agent-free queries require an explicit Cloud computer; managed sandboxes are agent-scoped.",
959
+ );
960
+ }
893
961
  // Scope the managed sandbox to the conversation when one exists: the
894
962
  // server treats create-with-conversationId as create-or-resume, so
895
963
  // sessions of the same conversation share one sandbox and sessions of
package/src/index.ts CHANGED
@@ -39,6 +39,7 @@ import type {
39
39
  SDKResultMessage,
40
40
  SendMessage,
41
41
  } from "./types.js";
42
+ import type { Query, QueryParams } from "./query-types.js";
42
43
  import { validateCreateSessionOptions, validateCreateAgentOptions } from "./validation.js";
43
44
 
44
45
  // Re-export types
@@ -94,6 +95,8 @@ export type {
94
95
  SessionDiffHunk,
95
96
  SessionDiffPreview,
96
97
  SkillSource,
98
+ SkillItem,
99
+ AgentSkill,
97
100
  DreamingOptions,
98
101
  SessionDreamingOptions,
99
102
  DreamingTrigger,
@@ -152,6 +155,11 @@ export type {
152
155
  McpSseServerConfig,
153
156
  McpServers,
154
157
  } from "./types.js";
158
+ export type {
159
+ AgentFreeQueryOptions,
160
+ Query,
161
+ QueryParams,
162
+ } from "./query-types.js";
155
163
  export type * from "./sandbox-files.js";
156
164
  export type {
157
165
  AgentRepositoriesClient,
@@ -325,6 +333,14 @@ export async function prompt(
325
333
  }
326
334
  }
327
335
 
336
+ /** Run an agent-free query in a new ephemeral conversation. */
337
+ export function query(params: QueryParams): Query {
338
+ return new LettaAgentClient({
339
+ backend: "local",
340
+ appServer: { harnessBackend: "api" },
341
+ }).query(params);
342
+ }
343
+
328
344
  // ═══════════════════════════════════════════════════════════════
329
345
  // SESSIONLESS APIs
330
346
  // ═══════════════════════════════════════════════════════════════
@@ -99,7 +99,7 @@ export function buildLocalAppServerArgs(
99
99
  return [
100
100
  cliPath,
101
101
  ...(options.backend !== undefined ? ["--backend", options.backend] : []),
102
- "app-server",
102
+ "server",
103
103
  "--listen",
104
104
  options.listen ?? DEFAULT_LISTEN_URL,
105
105
  ];
@@ -0,0 +1,40 @@
1
+ import type {
2
+ LettaCodeClientSessionOptions,
3
+ SDKMessage,
4
+ SendMessage,
5
+ } from "./types.js";
6
+
7
+ /** Options for an agent-free query backed by an ephemeral conversation. */
8
+ export interface AgentFreeQueryOptions
9
+ extends Omit<
10
+ LettaCodeClientSessionOptions,
11
+ | "model"
12
+ | "reasoningEffort"
13
+ | "stateless"
14
+ | "dreaming"
15
+ | "resources"
16
+ | "filesystemConfinement"
17
+ > {
18
+ /** Model handle persisted on the ephemeral conversation. */
19
+ model: string;
20
+ /** Complete system prompt for the ephemeral conversation. */
21
+ system: string;
22
+ /** Provider model settings persisted on the ephemeral conversation. */
23
+ modelSettings?: Record<string, unknown>;
24
+ /** Optional context-window limit persisted on the ephemeral conversation. */
25
+ contextWindowLimit?: number | null;
26
+ }
27
+
28
+ /** Input accepted by query(). */
29
+ export interface QueryParams {
30
+ prompt: SendMessage;
31
+ options: AgentFreeQueryOptions;
32
+ }
33
+
34
+ /** Stream returned by query(), with controls for long-running execution. */
35
+ export interface Query extends AsyncGenerator<SDKMessage, void, unknown> {
36
+ /** Interrupt the active turn without closing the query session. */
37
+ interrupt(): Promise<void>;
38
+ /** Close the query and release its underlying session. */
39
+ close(): void;
40
+ }
package/src/query.ts ADDED
@@ -0,0 +1,43 @@
1
+ import type { AgentFreeQueryOptions, Query, QueryParams } from "./query-types.js";
2
+ import type { LettaCodeSession, SDKMessage } from "./types.js";
3
+
4
+ type CreateAgentFreeSession = (
5
+ options: AgentFreeQueryOptions,
6
+ ) => Promise<LettaCodeSession>;
7
+
8
+ /** Create one agent-free ephemeral conversation and stream its query result. */
9
+ export function createQuery(
10
+ createSession: CreateAgentFreeSession,
11
+ params: QueryParams,
12
+ ): Query {
13
+ let session: LettaCodeSession | null = null;
14
+ let closed = false;
15
+
16
+ const iterator = (async function* runQuery(): AsyncGenerator<
17
+ SDKMessage,
18
+ void,
19
+ unknown
20
+ > {
21
+ try {
22
+ session = await createSession(params.options);
23
+ if (closed) return;
24
+ await session.send(params.prompt);
25
+ yield* session.stream();
26
+ } finally {
27
+ closed = true;
28
+ session?.close();
29
+ }
30
+ })();
31
+
32
+ return Object.assign(iterator, {
33
+ async interrupt(): Promise<void> {
34
+ await session?.abort();
35
+ },
36
+ close(): void {
37
+ if (closed) return;
38
+ closed = true;
39
+ session?.close();
40
+ void iterator.return();
41
+ },
42
+ });
43
+ }
@@ -166,7 +166,7 @@ export abstract class RemoteClientSessionCore implements LettaCodeSession {
166
166
  this.runtime = init.runtime;
167
167
  this._agentId = init.runtime.agent_id;
168
168
  this._conversationId = init.runtime.conversation_id;
169
- this._sessionId = `${init.runtime.agent_id}:${init.runtime.conversation_id}`;
169
+ this._sessionId = init.runtime.agent_id ? `${init.runtime.agent_id}:${init.runtime.conversation_id}` : init.runtime.conversation_id;
170
170
  this._modelSettings = init.modelSettings ?? null;
171
171
  this._model =
172
172
  typeof init.model === "string"
@@ -23,7 +23,7 @@ import type {
23
23
  } from "./types.js";
24
24
 
25
25
  export type RuntimeScope = {
26
- agent_id: string;
26
+ agent_id: string | null;
27
27
  conversation_id: string;
28
28
  };
29
29
 
@@ -35,6 +35,17 @@ export type ProtocolMessage = Record<string, unknown> & {
35
35
 
36
36
  export type RuntimeSessionMode =
37
37
  | { kind: "create-agent"; options: CreateAgentOptions }
38
+ | {
39
+ kind: "agent-free";
40
+ conversationId?: string;
41
+ createConversation?: {
42
+ model: string;
43
+ system: string;
44
+ modelSettings?: Record<string, unknown>;
45
+ contextWindowLimit?: number | null;
46
+ };
47
+ options: LettaCodeClientSessionOptions;
48
+ }
38
49
  | {
39
50
  kind: "session";
40
51
  agentId?: string;