@letta-ai/letta-code 0.11.2-next.2 → 0.11.2-next.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,11 +7,13 @@
7
7
  * It reads agent ID from LETTA_AGENT_ID env var or --agent-id arg.
8
8
  *
9
9
  * Usage:
10
- * npx tsx copy-block.ts --block-id <block-id> [--label <new-label>] [--agent-id <agent-id>]
10
+ * npx tsx copy-block.ts --block-id <block-id> [--label <new-label>] [--agent-id <agent-id>] [--override]
11
11
  *
12
12
  * Options:
13
- * --label Override the block label (required if you already have a block with that label)
13
+ * --label Override the block label (useful to avoid duplicate label errors)
14
14
  * --agent-id Target agent ID (overrides LETTA_AGENT_ID env var)
15
+ * --override If you already have a block with the same label, detach it first
16
+ * (on error, the original block is reattached)
15
17
  *
16
18
  * This creates a new block with the same content as the source block,
17
19
  * then attaches it to the current agent. Changes to the new block
@@ -37,6 +39,7 @@ interface CopyBlockResult {
37
39
  sourceBlock: Awaited<ReturnType<LettaClient["blocks"]["retrieve"]>>;
38
40
  newBlock: Awaited<ReturnType<LettaClient["blocks"]["create"]>>;
39
41
  attachResult: Awaited<ReturnType<LettaClient["agents"]["blocks"]["attach"]>>;
42
+ detachedBlock?: Awaited<ReturnType<LettaClient["blocks"]["retrieve"]>>;
40
43
  }
41
44
 
42
45
  /**
@@ -86,44 +89,125 @@ function createClient(): LettaClient {
86
89
  * Copy a block's content to a new block and attach to the current agent
87
90
  * @param client - Letta client instance
88
91
  * @param blockId - The source block ID to copy from
89
- * @param options - Optional settings: labelOverride, targetAgentId
92
+ * @param options - Optional settings: labelOverride, targetAgentId, override
90
93
  * @returns Object containing source block, new block, and attach result
91
94
  */
92
95
  export async function copyBlock(
93
96
  client: LettaClient,
94
97
  blockId: string,
95
- options?: { labelOverride?: string; targetAgentId?: string },
98
+ options?: {
99
+ labelOverride?: string;
100
+ targetAgentId?: string;
101
+ override?: boolean;
102
+ },
96
103
  ): Promise<CopyBlockResult> {
97
- // Get current agent ID (the agent calling this script) or use provided ID
98
104
  const currentAgentId = getAgentId(options?.targetAgentId);
105
+ let detachedBlock:
106
+ | Awaited<ReturnType<LettaClient["blocks"]["retrieve"]>>
107
+ | undefined;
99
108
 
100
109
  // 1. Get source block details
101
110
  const sourceBlock = await client.blocks.retrieve(blockId);
111
+ const targetLabel =
112
+ options?.labelOverride || sourceBlock.label || "migrated-block";
102
113
 
103
- // 2. Create new block with same content (optionally override label)
104
- const newBlock = await client.blocks.create({
105
- label: options?.labelOverride || sourceBlock.label || "migrated-block",
106
- value: sourceBlock.value,
107
- description: sourceBlock.description || undefined,
108
- limit: sourceBlock.limit,
109
- });
114
+ // 2. If override is requested, check for existing block with same label and detach it
115
+ if (options?.override) {
116
+ const currentBlocksResponse =
117
+ await client.agents.blocks.list(currentAgentId);
118
+ // The response may be paginated or an array depending on SDK version
119
+ const currentBlocks = Array.isArray(currentBlocksResponse)
120
+ ? currentBlocksResponse
121
+ : (currentBlocksResponse as { items?: unknown[] }).items || [];
122
+ const conflictingBlock = currentBlocks.find(
123
+ (b: { label?: string }) => b.label === targetLabel,
124
+ );
110
125
 
111
- // 3. Attach new block to current agent
112
- const attachResult = await client.agents.blocks.attach(newBlock.id, {
113
- agent_id: currentAgentId,
114
- });
126
+ if (conflictingBlock) {
127
+ console.error(
128
+ `Detaching existing block with label "${targetLabel}" (${conflictingBlock.id})...`,
129
+ );
130
+ detachedBlock = conflictingBlock;
131
+ try {
132
+ await client.agents.blocks.detach(conflictingBlock.id, {
133
+ agent_id: currentAgentId,
134
+ });
135
+ } catch (detachError) {
136
+ throw new Error(
137
+ `Failed to detach existing block "${targetLabel}": ${detachError instanceof Error ? detachError.message : String(detachError)}`,
138
+ );
139
+ }
140
+ }
141
+ }
115
142
 
116
- return { sourceBlock, newBlock, attachResult };
143
+ // 3. Create new block with same content
144
+ let newBlock: Awaited<ReturnType<LettaClient["blocks"]["create"]>>;
145
+ try {
146
+ newBlock = await client.blocks.create({
147
+ label: targetLabel,
148
+ value: sourceBlock.value,
149
+ description: sourceBlock.description || undefined,
150
+ limit: sourceBlock.limit,
151
+ });
152
+ } catch (createError) {
153
+ // If create failed and we detached a block, try to reattach it
154
+ if (detachedBlock) {
155
+ console.error(
156
+ `Create failed, reattaching original block "${detachedBlock.label}"...`,
157
+ );
158
+ try {
159
+ await client.agents.blocks.attach(detachedBlock.id, {
160
+ agent_id: currentAgentId,
161
+ });
162
+ console.error("Original block reattached successfully.");
163
+ } catch {
164
+ console.error(
165
+ `WARNING: Failed to reattach original block! Block ID: ${detachedBlock.id}`,
166
+ );
167
+ }
168
+ }
169
+ throw createError;
170
+ }
171
+
172
+ // 4. Attach new block to current agent
173
+ let attachResult: Awaited<ReturnType<typeof client.agents.blocks.attach>>;
174
+ try {
175
+ attachResult = await client.agents.blocks.attach(newBlock.id, {
176
+ agent_id: currentAgentId,
177
+ });
178
+ } catch (attachError) {
179
+ // If attach failed and we detached a block, try to reattach it
180
+ if (detachedBlock) {
181
+ console.error(
182
+ `Attach failed, reattaching original block "${detachedBlock.label}"...`,
183
+ );
184
+ try {
185
+ await client.agents.blocks.attach(detachedBlock.id, {
186
+ agent_id: currentAgentId,
187
+ });
188
+ console.error("Original block reattached successfully.");
189
+ } catch {
190
+ console.error(
191
+ `WARNING: Failed to reattach original block! Block ID: ${detachedBlock.id}`,
192
+ );
193
+ }
194
+ }
195
+ throw attachError;
196
+ }
197
+
198
+ return { sourceBlock, newBlock, attachResult, detachedBlock };
117
199
  }
118
200
 
119
201
  function parseArgs(args: string[]): {
120
202
  blockId: string;
121
203
  label?: string;
122
204
  agentId?: string;
205
+ override: boolean;
123
206
  } {
124
207
  const blockIdIndex = args.indexOf("--block-id");
125
208
  const labelIndex = args.indexOf("--label");
126
209
  const agentIdIndex = args.indexOf("--agent-id");
210
+ const override = args.includes("--override");
127
211
 
128
212
  if (blockIdIndex === -1 || blockIdIndex + 1 >= args.length) {
129
213
  throw new Error("Missing required argument: --block-id <block-id>");
@@ -139,6 +223,7 @@ function parseArgs(args: string[]): {
139
223
  agentIdIndex !== -1 && agentIdIndex + 1 < args.length
140
224
  ? (args[agentIdIndex + 1] as string)
141
225
  : undefined,
226
+ override,
142
227
  };
143
228
  }
144
229
 
@@ -147,11 +232,14 @@ const isMainModule = import.meta.url === `file://${process.argv[1]}`;
147
232
  if (isMainModule) {
148
233
  (async () => {
149
234
  try {
150
- const { blockId, label, agentId } = parseArgs(process.argv.slice(2));
235
+ const { blockId, label, agentId, override } = parseArgs(
236
+ process.argv.slice(2),
237
+ );
151
238
  const client = createClient();
152
239
  const result = await copyBlock(client, blockId, {
153
240
  labelOverride: label,
154
241
  targetAgentId: agentId,
242
+ override,
155
243
  });
156
244
  console.log(JSON.stringify(result, null, 2));
157
245
  } catch (error) {
@@ -164,7 +252,7 @@ if (isMainModule) {
164
252
  error.message.includes("Missing required argument")
165
253
  ) {
166
254
  console.error(
167
- "\nUsage: npx tsx copy-block.ts --block-id <block-id> [--label <new-label>] [--agent-id <agent-id>]",
255
+ "\nUsage: npx tsx copy-block.ts --block-id <block-id> [--label <new-label>] [--agent-id <agent-id>] [--override]",
168
256
  );
169
257
  }
170
258
  process.exit(1);