@letta-ai/letta-code 0.27.19 → 0.27.20

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/letta.js CHANGED
@@ -4790,7 +4790,7 @@ var package_default;
4790
4790
  var init_package = __esm(() => {
4791
4791
  package_default = {
4792
4792
  name: "@letta-ai/letta-code",
4793
- version: "0.27.19",
4793
+ version: "0.27.20",
4794
4794
  description: "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
4795
4795
  type: "module",
4796
4796
  packageManager: "bun@1.3.0",
@@ -170051,7 +170051,13 @@ var init_system_prompt_warning = __esm(() => {
170051
170051
  });
170052
170052
 
170053
170053
  // src/websocket/listener/constants.ts
170054
- var MAX_RETRY_DURATION_MS, INITIAL_RETRY_DELAY_MS2 = 1000, MAX_RETRY_DELAY_MS = 30000, SYSTEM_REMINDER_RE, LLM_API_ERROR_MAX_RETRIES = 3, EMPTY_RESPONSE_MAX_RETRIES = 2, MAX_PRE_STREAM_RECOVERY = 2, MAX_POST_STOP_APPROVAL_RECOVERY = 2, PROVIDER_FALLBACK_MAP, PROVIDER_FALLBACK_NOTICE = "Anthropic API error; falling back to Bedrock...";
170054
+ function isListenerPongStale(lastPongAt, now, timeoutMs) {
170055
+ if (lastPongAt === null) {
170056
+ return false;
170057
+ }
170058
+ return now - lastPongAt > timeoutMs;
170059
+ }
170060
+ var MAX_RETRY_DURATION_MS, INITIAL_RETRY_DELAY_MS2 = 1000, MAX_RETRY_DELAY_MS = 30000, LISTENER_HEARTBEAT_INTERVAL_MS = 30000, LISTENER_PONG_TIMEOUT_MS = 90000, SYSTEM_REMINDER_RE, LLM_API_ERROR_MAX_RETRIES = 3, EMPTY_RESPONSE_MAX_RETRIES = 2, MAX_PRE_STREAM_RECOVERY = 2, MAX_POST_STOP_APPROVAL_RECOVERY = 2, PROVIDER_FALLBACK_MAP, PROVIDER_FALLBACK_NOTICE = "Anthropic API error; falling back to Bedrock...";
170055
170061
  var init_constants3 = __esm(() => {
170056
170062
  MAX_RETRY_DURATION_MS = 5 * 60 * 1000;
170057
170063
  SYSTEM_REMINDER_RE = /<system-reminder>[\s\S]*?<\/system-reminder>/g;
@@ -381853,6 +381859,18 @@ function isModelOutputEvent(event2) {
381853
381859
  return false;
381854
381860
  }
381855
381861
  }
381862
+ function llmEndErrorFromError(error54) {
381863
+ const info = normalizeLocalProviderError(error54);
381864
+ return {
381865
+ error: {
381866
+ message: info.message,
381867
+ detail: info.detail,
381868
+ errorType: info.error_type,
381869
+ retryable: info.retryable
381870
+ },
381871
+ stopReason: info.stop_reason
381872
+ };
381873
+ }
381856
381874
  function isOverflowError(error54, contextWindow) {
381857
381875
  if (error54 instanceof PiProviderError) {
381858
381876
  return isContextOverflow(error54.assistant, contextWindow);
@@ -382027,6 +382045,17 @@ class PiStreamAdapter {
382027
382045
  }
382028
382046
  const restoreEnv = applyPiEnvOverrides(resolved.envOverrides);
382029
382047
  const llmStartedAt = Date.now();
382048
+ let llmEnded = false;
382049
+ const emitLlmEnd = async (info) => {
382050
+ llmEnded = true;
382051
+ await this.onLlmEnd?.({
382052
+ agentId: input.agentId,
382053
+ conversationId: input.conversationId,
382054
+ model: input.agent.model,
382055
+ durationMs: Date.now() - llmStartedAt,
382056
+ ...info
382057
+ });
382058
+ };
382030
382059
  try {
382031
382060
  await this.onLlmStart?.({
382032
382061
  agentId: input.agentId,
@@ -382055,17 +382084,15 @@ class PiStreamAdapter {
382055
382084
  if (streamError)
382056
382085
  throw streamError;
382057
382086
  finalMessage ??= await result.result();
382058
- await this.onLlmEnd?.({
382059
- agentId: input.agentId,
382060
- conversationId: input.conversationId,
382061
- model: input.agent.model,
382087
+ const finalMessageError = finalMessage.stopReason === "error" || finalMessage.stopReason === "aborted" ? llmEndErrorFromError(new PiProviderError(finalMessage)).error : undefined;
382088
+ await emitLlmEnd({
382062
382089
  stopReason: finalMessage.stopReason,
382063
382090
  usage: {
382064
382091
  promptTokens: finalMessage.usage.input,
382065
382092
  completionTokens: finalMessage.usage.output,
382066
382093
  totalTokens: finalMessage.usage.totalTokens
382067
382094
  },
382068
- durationMs: Date.now() - llmStartedAt
382095
+ ...finalMessageError ? { error: finalMessageError } : {}
382069
382096
  });
382070
382097
  if (finalMessage.stopReason === "error" || finalMessage.stopReason === "aborted") {
382071
382098
  throw new PiProviderError(finalMessage);
@@ -382076,6 +382103,16 @@ class PiStreamAdapter {
382076
382103
  yield* this.emitCompactionChunks(compaction, "context_window_limit");
382077
382104
  }
382078
382105
  }
382106
+ } catch (error54) {
382107
+ if (!llmEnded) {
382108
+ const endError = llmEndErrorFromError(error54);
382109
+ await emitLlmEnd({
382110
+ stopReason: endError.stopReason,
382111
+ usage: null,
382112
+ error: endError.error
382113
+ });
382114
+ }
382115
+ throw error54;
382079
382116
  } finally {
382080
382117
  restoreEnv();
382081
382118
  }
@@ -445547,6 +445584,9 @@ function createListenerMessageHandler(params) {
445547
445584
  try {
445548
445585
  const lifecycleMessage = parseServerLifecycleMessage(data);
445549
445586
  if (lifecycleMessage) {
445587
+ if (lifecycleMessage.type === "pong") {
445588
+ runtime.lastPongAt = Date.now();
445589
+ }
445550
445590
  safeEmitWsEvent("recv", "lifecycle", lifecycleMessage);
445551
445591
  return;
445552
445592
  }
@@ -446398,6 +446438,7 @@ function createRuntime() {
446398
446438
  streamTransport: null,
446399
446439
  heartbeatInterval: null,
446400
446440
  reconnectTimeout: null,
446441
+ lastPongAt: null,
446401
446442
  intentionallyClosed: false,
446402
446443
  hasSuccessfulConnection: false,
446403
446444
  everConnected: false,
@@ -446544,9 +446585,15 @@ async function startConnectedListenerRuntime(runtime, transport, opts, processQu
446544
446585
  scheduleQueuePump(targetRuntime, transport, opts, processQueuedTurn);
446545
446586
  });
446546
446587
  if (shouldStartHeartbeat) {
446588
+ runtime.lastPongAt = Date.now();
446547
446589
  runtime.heartbeatInterval = setInterval(() => {
446590
+ if (getListenerTransportKind(transport) === "websocket" && isListenerPongStale(runtime.lastPongAt, Date.now(), LISTENER_PONG_TIMEOUT_MS)) {
446591
+ trackListenerError4("listener_pong_timeout", new Error(`No relay pong within ${LISTENER_PONG_TIMEOUT_MS}ms; terminating half-open socket to force reconnect`), "listener_heartbeat");
446592
+ runtime.socket?.terminate();
446593
+ return;
446594
+ }
446548
446595
  safeTransportSend(transport, { type: "ping" }, "listener_ping_send_failed", "listener_heartbeat");
446549
- }, 30000);
446596
+ }, LISTENER_HEARTBEAT_INTERVAL_MS);
446550
446597
  }
446551
446598
  if (shouldStartCronScheduler) {
446552
446599
  startScheduler(transport, opts, processQueuedTurn);
@@ -449938,6 +449985,12 @@ function createLegacyTestRuntime() {
449938
449985
  listener.heartbeatInterval = value;
449939
449986
  }
449940
449987
  },
449988
+ lastPongAt: {
449989
+ get: () => listener.lastPongAt,
449990
+ set: (value) => {
449991
+ listener.lastPongAt = value;
449992
+ }
449993
+ },
449941
449994
  intentionallyClosed: {
449942
449995
  get: () => listener.intentionallyClosed,
449943
449996
  set: (value) => {
@@ -454084,7 +454137,8 @@ function installLocalBackendModEventHooks(options3) {
454084
454137
  model: info.model,
454085
454138
  stopReason: info.stopReason,
454086
454139
  usage: info.usage,
454087
- durationMs: info.durationMs
454140
+ durationMs: info.durationMs,
454141
+ ...info.error ? { error: info.error } : {}
454088
454142
  }, buildContext(info.conversationId));
454089
454143
  }
454090
454144
  });
@@ -524463,6 +524517,8 @@ await __promiseAll([
524463
524517
  var DEFAULT_LISTEN_URL = "ws://127.0.0.1:0";
524464
524518
  var DEFAULT_WS_PATH = "/ws";
524465
524519
  var PENDING_STREAM_TIMEOUT_MS = 5000;
524520
+ var APP_SERVER_HEARTBEAT_INTERVAL_MS = 30000;
524521
+ var APP_SERVER_PONG_TIMEOUT_MS = 90000;
524466
524522
  function getRequiredAddressInfo(server2) {
524467
524523
  const address = server2.address();
524468
524524
  if (!address || typeof address === "string") {
@@ -524598,6 +524654,7 @@ async function startAppServer(options3 = {}) {
524598
524654
  let pendingStreamSocket = null;
524599
524655
  let pendingStreamTimeout = null;
524600
524656
  let resolvedInfo = null;
524657
+ const lastPongAtBySocket = new WeakMap;
524601
524658
  const clearPendingStream = () => {
524602
524659
  if (pendingStreamTimeout) {
524603
524660
  clearTimeout(pendingStreamTimeout);
@@ -524608,6 +524665,10 @@ async function startAppServer(options3 = {}) {
524608
524665
  return socket;
524609
524666
  };
524610
524667
  const handleWebSocketConnection = (socket, channel) => {
524668
+ lastPongAtBySocket.set(socket, Date.now());
524669
+ socket.on("pong", () => {
524670
+ lastPongAtBySocket.set(socket, Date.now());
524671
+ });
524611
524672
  if (channel === "stream") {
524612
524673
  if (activeSession) {
524613
524674
  attachStreamSocket(activeSession, socket);
@@ -524706,6 +524767,26 @@ async function startAppServer(options3 = {}) {
524706
524767
  handleWebSocketConnection(websocket, channel);
524707
524768
  });
524708
524769
  });
524770
+ const heartbeatIntervalMs = options3.heartbeatIntervalMs ?? APP_SERVER_HEARTBEAT_INTERVAL_MS;
524771
+ const pongTimeoutMs = options3.pongTimeoutMs ?? APP_SERVER_PONG_TIMEOUT_MS;
524772
+ const heartbeatInterval = setInterval(() => {
524773
+ const now = Date.now();
524774
+ for (const client of wss.clients) {
524775
+ const lastPongAt = lastPongAtBySocket.get(client) ?? now;
524776
+ if (now - lastPongAt > pongTimeoutMs) {
524777
+ options3.onLog?.(`App-server terminating unresponsive socket (no pong in ${pongTimeoutMs}ms)`);
524778
+ client.terminate();
524779
+ continue;
524780
+ }
524781
+ if (client.readyState === WebSocket7.OPEN) {
524782
+ client.ping();
524783
+ }
524784
+ }
524785
+ }, heartbeatIntervalMs);
524786
+ heartbeatInterval.unref?.();
524787
+ wss.on("close", () => {
524788
+ clearInterval(heartbeatInterval);
524789
+ });
524709
524790
  await new Promise((resolve30, reject) => {
524710
524791
  const onError = (error54) => {
524711
524792
  server2.off("listening", onListening);
@@ -524730,6 +524811,7 @@ async function startAppServer(options3 = {}) {
524730
524811
  return {
524731
524812
  ...resolvedInfo,
524732
524813
  close: async () => {
524814
+ clearInterval(heartbeatInterval);
524733
524815
  const streamSocket = clearPendingStream();
524734
524816
  terminateSocket(streamSocket);
524735
524817
  if (activeSession) {
@@ -533971,4 +534053,4 @@ Error during initialization: ${message}`);
533971
534053
  }
533972
534054
  main2();
533973
534055
 
533974
- //# debugId=3D66948C285952FE64756E2164756E21
534056
+ //# debugId=EC4DC695909CD41C64756E2164756E21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letta-ai/letta-code",
3
- "version": "0.27.19",
3
+ "version": "0.27.20",
4
4
  "description": "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
5
5
  "type": "module",
6
6
  "packageManager": "bun@1.3.0",
@@ -284,12 +284,18 @@ Handlers run in registration order. Later handlers see the current input after e
284
284
  promptTokens: number;
285
285
  completionTokens: number;
286
286
  totalTokens: number;
287
- };
287
+ } | null;
288
288
  durationMs: number;
289
+ error?: {
290
+ message: string;
291
+ detail: string;
292
+ errorType: "llm_error" | "local_backend_error";
293
+ retryable: boolean;
294
+ };
289
295
  }
290
296
  ```
291
297
 
292
- `llm_end` fires once a provider request produces a final message, carrying the stop reason, token usage, and wall-clock duration. A request that fails before producing a final message (e.g. a transport error that triggers a retry) emits no `llm_end`. Both events are notification-only; return values are ignored. A throwing handler is isolated and never breaks the provider request.
298
+ `llm_end` fires when a provider request ends, success or failure. Successful requests include token usage. Requests that fail before usage is available set `usage: null` and include `error`. Retry/failover effects are not supported yet; both events are notification-only and return values are ignored. A throwing handler is isolated and never breaks the provider request.
293
299
 
294
300
  Handlers also receive:
295
301