@data-club/ai-hub-server 0.0.1

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 (248) hide show
  1. package/dist/auth/argon2.d.ts +72 -0
  2. package/dist/auth/argon2.d.ts.map +1 -0
  3. package/dist/auth/argon2.js +145 -0
  4. package/dist/auth/argon2.js.map +1 -0
  5. package/dist/auth/encryption.d.ts +52 -0
  6. package/dist/auth/encryption.d.ts.map +1 -0
  7. package/dist/auth/encryption.js +98 -0
  8. package/dist/auth/encryption.js.map +1 -0
  9. package/dist/auth/jwt.d.ts +133 -0
  10. package/dist/auth/jwt.d.ts.map +1 -0
  11. package/dist/auth/jwt.js +147 -0
  12. package/dist/auth/jwt.js.map +1 -0
  13. package/dist/auth/middleware.d.ts +101 -0
  14. package/dist/auth/middleware.d.ts.map +1 -0
  15. package/dist/auth/middleware.js +161 -0
  16. package/dist/auth/middleware.js.map +1 -0
  17. package/dist/auth/passwordPolicy.d.ts +72 -0
  18. package/dist/auth/passwordPolicy.d.ts.map +1 -0
  19. package/dist/auth/passwordPolicy.js +88 -0
  20. package/dist/auth/passwordPolicy.js.map +1 -0
  21. package/dist/auth/roleRules.d.ts +200 -0
  22. package/dist/auth/roleRules.d.ts.map +1 -0
  23. package/dist/auth/roleRules.js +365 -0
  24. package/dist/auth/roleRules.js.map +1 -0
  25. package/dist/auth/userView.d.ts +55 -0
  26. package/dist/auth/userView.d.ts.map +1 -0
  27. package/dist/auth/userView.js +77 -0
  28. package/dist/auth/userView.js.map +1 -0
  29. package/dist/bootstrap.d.ts +104 -0
  30. package/dist/bootstrap.d.ts.map +1 -0
  31. package/dist/bootstrap.js +160 -0
  32. package/dist/bootstrap.js.map +1 -0
  33. package/dist/db/connection.d.ts +39 -0
  34. package/dist/db/connection.d.ts.map +1 -0
  35. package/dist/db/connection.js +62 -0
  36. package/dist/db/connection.js.map +1 -0
  37. package/dist/db/migrate.d.ts +31 -0
  38. package/dist/db/migrate.d.ts.map +1 -0
  39. package/dist/db/migrate.js +197 -0
  40. package/dist/db/migrate.js.map +1 -0
  41. package/dist/db/schema.d.ts +1902 -0
  42. package/dist/db/schema.d.ts.map +1 -0
  43. package/dist/db/schema.js +662 -0
  44. package/dist/db/schema.js.map +1 -0
  45. package/dist/db/seed.d.ts +92 -0
  46. package/dist/db/seed.d.ts.map +1 -0
  47. package/dist/db/seed.js +160 -0
  48. package/dist/db/seed.js.map +1 -0
  49. package/dist/handlers/fetch.d.ts +89 -0
  50. package/dist/handlers/fetch.d.ts.map +1 -0
  51. package/dist/handlers/fetch.js +173 -0
  52. package/dist/handlers/fetch.js.map +1 -0
  53. package/dist/handlers/nextjs.d.ts +40 -0
  54. package/dist/handlers/nextjs.d.ts.map +1 -0
  55. package/dist/handlers/nextjs.js +42 -0
  56. package/dist/handlers/nextjs.js.map +1 -0
  57. package/dist/handlers/responses.d.ts +46 -0
  58. package/dist/handlers/responses.d.ts.map +1 -0
  59. package/dist/handlers/responses.js +74 -0
  60. package/dist/handlers/responses.js.map +1 -0
  61. package/dist/handlers/routeTable.d.ts +21 -0
  62. package/dist/handlers/routeTable.d.ts.map +1 -0
  63. package/dist/handlers/routeTable.js +133 -0
  64. package/dist/handlers/routeTable.js.map +1 -0
  65. package/dist/handlers/router.d.ts +37 -0
  66. package/dist/handlers/router.d.ts.map +1 -0
  67. package/dist/handlers/router.js +82 -0
  68. package/dist/handlers/router.js.map +1 -0
  69. package/dist/handlers/sse.d.ts +38 -0
  70. package/dist/handlers/sse.d.ts.map +1 -0
  71. package/dist/handlers/sse.js +52 -0
  72. package/dist/handlers/sse.js.map +1 -0
  73. package/dist/handlers/types.d.ts +96 -0
  74. package/dist/handlers/types.d.ts.map +1 -0
  75. package/dist/handlers/types.js +14 -0
  76. package/dist/handlers/types.js.map +1 -0
  77. package/dist/index.d.ts +25 -0
  78. package/dist/index.d.ts.map +1 -0
  79. package/dist/index.js +24 -0
  80. package/dist/index.js.map +1 -0
  81. package/dist/letta/client.d.ts +355 -0
  82. package/dist/letta/client.d.ts.map +1 -0
  83. package/dist/letta/client.js +529 -0
  84. package/dist/letta/client.js.map +1 -0
  85. package/dist/letta/eventTranslator.d.ts +166 -0
  86. package/dist/letta/eventTranslator.d.ts.map +1 -0
  87. package/dist/letta/eventTranslator.js +532 -0
  88. package/dist/letta/eventTranslator.js.map +1 -0
  89. package/dist/letta/index.d.ts +13 -0
  90. package/dist/letta/index.d.ts.map +1 -0
  91. package/dist/letta/index.js +12 -0
  92. package/dist/letta/index.js.map +1 -0
  93. package/dist/letta/modelSettings.d.ts +176 -0
  94. package/dist/letta/modelSettings.d.ts.map +1 -0
  95. package/dist/letta/modelSettings.js +314 -0
  96. package/dist/letta/modelSettings.js.map +1 -0
  97. package/dist/letta/promptWrapping.d.ts +141 -0
  98. package/dist/letta/promptWrapping.d.ts.map +1 -0
  99. package/dist/letta/promptWrapping.js +213 -0
  100. package/dist/letta/promptWrapping.js.map +1 -0
  101. package/dist/letta/sandbox.d.ts +100 -0
  102. package/dist/letta/sandbox.d.ts.map +1 -0
  103. package/dist/letta/sandbox.js +151 -0
  104. package/dist/letta/sandbox.js.map +1 -0
  105. package/dist/letta/settingsFileSeed.d.ts +146 -0
  106. package/dist/letta/settingsFileSeed.d.ts.map +1 -0
  107. package/dist/letta/settingsFileSeed.js +177 -0
  108. package/dist/letta/settingsFileSeed.js.map +1 -0
  109. package/dist/letta/subprocessThrottle.d.ts +151 -0
  110. package/dist/letta/subprocessThrottle.d.ts.map +1 -0
  111. package/dist/letta/subprocessThrottle.js +195 -0
  112. package/dist/letta/subprocessThrottle.js.map +1 -0
  113. package/dist/letta/tpmRecovery.d.ts +202 -0
  114. package/dist/letta/tpmRecovery.d.ts.map +1 -0
  115. package/dist/letta/tpmRecovery.js +314 -0
  116. package/dist/letta/tpmRecovery.js.map +1 -0
  117. package/dist/letta/types.d.ts +54 -0
  118. package/dist/letta/types.d.ts.map +1 -0
  119. package/dist/letta/types.js +16 -0
  120. package/dist/letta/types.js.map +1 -0
  121. package/dist/observability/logger.d.ts +48 -0
  122. package/dist/observability/logger.d.ts.map +1 -0
  123. package/dist/observability/logger.js +81 -0
  124. package/dist/observability/logger.js.map +1 -0
  125. package/dist/observability/metrics.d.ts +53 -0
  126. package/dist/observability/metrics.d.ts.map +1 -0
  127. package/dist/observability/metrics.js +92 -0
  128. package/dist/observability/metrics.js.map +1 -0
  129. package/dist/routes/admin/agents.d.ts +63 -0
  130. package/dist/routes/admin/agents.d.ts.map +1 -0
  131. package/dist/routes/admin/agents.js +461 -0
  132. package/dist/routes/admin/agents.js.map +1 -0
  133. package/dist/routes/admin/rotatingMessages.d.ts +51 -0
  134. package/dist/routes/admin/rotatingMessages.d.ts.map +1 -0
  135. package/dist/routes/admin/rotatingMessages.js +292 -0
  136. package/dist/routes/admin/rotatingMessages.js.map +1 -0
  137. package/dist/routes/admin/sessions.d.ts +61 -0
  138. package/dist/routes/admin/sessions.d.ts.map +1 -0
  139. package/dist/routes/admin/sessions.js +232 -0
  140. package/dist/routes/admin/sessions.js.map +1 -0
  141. package/dist/routes/admin/settings.d.ts +110 -0
  142. package/dist/routes/admin/settings.d.ts.map +1 -0
  143. package/dist/routes/admin/settings.js +401 -0
  144. package/dist/routes/admin/settings.js.map +1 -0
  145. package/dist/routes/admin/users.d.ts +115 -0
  146. package/dist/routes/admin/users.d.ts.map +1 -0
  147. package/dist/routes/admin/users.js +374 -0
  148. package/dist/routes/admin/users.js.map +1 -0
  149. package/dist/routes/agents.d.ts +30 -0
  150. package/dist/routes/agents.d.ts.map +1 -0
  151. package/dist/routes/agents.js +55 -0
  152. package/dist/routes/agents.js.map +1 -0
  153. package/dist/routes/auth.d.ts +121 -0
  154. package/dist/routes/auth.d.ts.map +1 -0
  155. package/dist/routes/auth.js +422 -0
  156. package/dist/routes/auth.js.map +1 -0
  157. package/dist/routes/chat.d.ts +97 -0
  158. package/dist/routes/chat.d.ts.map +1 -0
  159. package/dist/routes/chat.js +484 -0
  160. package/dist/routes/chat.js.map +1 -0
  161. package/dist/routes/featureToggles.d.ts +34 -0
  162. package/dist/routes/featureToggles.d.ts.map +1 -0
  163. package/dist/routes/featureToggles.js +47 -0
  164. package/dist/routes/featureToggles.js.map +1 -0
  165. package/dist/routes/health.d.ts +24 -0
  166. package/dist/routes/health.d.ts.map +1 -0
  167. package/dist/routes/health.js +37 -0
  168. package/dist/routes/health.js.map +1 -0
  169. package/dist/routes/metrics.d.ts +35 -0
  170. package/dist/routes/metrics.d.ts.map +1 -0
  171. package/dist/routes/metrics.js +106 -0
  172. package/dist/routes/metrics.js.map +1 -0
  173. package/dist/routes/sessions.d.ts +161 -0
  174. package/dist/routes/sessions.d.ts.map +1 -0
  175. package/dist/routes/sessions.js +1501 -0
  176. package/dist/routes/sessions.js.map +1 -0
  177. package/dist/routes/stubs.d.ts +25 -0
  178. package/dist/routes/stubs.d.ts.map +1 -0
  179. package/dist/routes/stubs.js +25 -0
  180. package/dist/routes/stubs.js.map +1 -0
  181. package/dist/state/cron.d.ts +58 -0
  182. package/dist/state/cron.d.ts.map +1 -0
  183. package/dist/state/cron.js +160 -0
  184. package/dist/state/cron.js.map +1 -0
  185. package/dist/state/rotationRunner.d.ts +96 -0
  186. package/dist/state/rotationRunner.d.ts.map +1 -0
  187. package/dist/state/rotationRunner.js +246 -0
  188. package/dist/state/rotationRunner.js.map +1 -0
  189. package/dist/state/rotationScheduler.d.ts +76 -0
  190. package/dist/state/rotationScheduler.d.ts.map +1 -0
  191. package/dist/state/rotationScheduler.js +124 -0
  192. package/dist/state/rotationScheduler.js.map +1 -0
  193. package/dist/state/turnRegistry.d.ts +95 -0
  194. package/dist/state/turnRegistry.d.ts.map +1 -0
  195. package/dist/state/turnRegistry.js +149 -0
  196. package/dist/state/turnRegistry.js.map +1 -0
  197. package/dist/state/turnRunner.d.ts +134 -0
  198. package/dist/state/turnRunner.d.ts.map +1 -0
  199. package/dist/state/turnRunner.js +476 -0
  200. package/dist/state/turnRunner.js.map +1 -0
  201. package/package.json +47 -0
  202. package/src/auth/argon2.ts +152 -0
  203. package/src/auth/encryption.ts +110 -0
  204. package/src/auth/jwt.ts +217 -0
  205. package/src/auth/middleware.ts +250 -0
  206. package/src/auth/passwordPolicy.ts +99 -0
  207. package/src/auth/roleRules.ts +497 -0
  208. package/src/auth/userView.ts +88 -0
  209. package/src/bootstrap.ts +254 -0
  210. package/src/db/connection.ts +75 -0
  211. package/src/db/migrate.ts +212 -0
  212. package/src/db/schema.ts +750 -0
  213. package/src/db/seed.ts +236 -0
  214. package/src/handlers/fetch.ts +251 -0
  215. package/src/handlers/nextjs.ts +49 -0
  216. package/src/handlers/responses.ts +95 -0
  217. package/src/handlers/routeTable.ts +188 -0
  218. package/src/handlers/router.ts +96 -0
  219. package/src/handlers/sse.ts +55 -0
  220. package/src/handlers/types.ts +104 -0
  221. package/src/index.ts +60 -0
  222. package/src/letta/client.ts +860 -0
  223. package/src/letta/eventTranslator.ts +667 -0
  224. package/src/letta/index.ts +46 -0
  225. package/src/letta/lettaCliShim.mjs +40 -0
  226. package/src/letta/sandbox.ts +167 -0
  227. package/src/letta/settingsFileSeed.ts +254 -0
  228. package/src/letta/subprocessThrottle.ts +251 -0
  229. package/src/letta/types.ts +65 -0
  230. package/src/observability/logger.ts +103 -0
  231. package/src/observability/metrics.ts +114 -0
  232. package/src/routes/admin/agents.ts +574 -0
  233. package/src/routes/admin/rotatingMessages.ts +351 -0
  234. package/src/routes/admin/sessions.ts +283 -0
  235. package/src/routes/admin/settings.ts +463 -0
  236. package/src/routes/admin/users.ts +455 -0
  237. package/src/routes/agents.ts +66 -0
  238. package/src/routes/auth.ts +513 -0
  239. package/src/routes/chat.ts +534 -0
  240. package/src/routes/featureToggles.ts +52 -0
  241. package/src/routes/health.ts +44 -0
  242. package/src/routes/metrics.ts +111 -0
  243. package/src/routes/sessions.ts +1878 -0
  244. package/src/state/cron.ts +179 -0
  245. package/src/state/rotationRunner.ts +326 -0
  246. package/src/state/rotationScheduler.ts +164 -0
  247. package/src/state/turnRegistry.ts +210 -0
  248. package/src/state/turnRunner.ts +588 -0
@@ -0,0 +1,202 @@
1
+ /**
2
+ * TPM-recovery primitives — provider-429 retry/continuation loop (§6.5).
3
+ *
4
+ * The orchestration lives in `state/turnRunner.ts`. This file contains the
5
+ * pure helpers + the per-agent config resolver + the continuation prompt
6
+ * text + a cancellable sleep, in one place so the runner stays focused on
7
+ * the state-machine narrative and the helpers stay unit-testable in
8
+ * isolation.
9
+ *
10
+ * ## Detection (§17 question 3)
11
+ *
12
+ * The Letta TypeScript SDK exposes a typed `RateLimitError extends APIError<429, Headers>`
13
+ * (see `@letta-ai/letta-client/core/error`). When a provider 429 surfaces
14
+ * through `messages.create`, the SDK throws an error whose `status === 429`
15
+ * — either before the iterator yields its first event (pre-stream) or
16
+ * mid-iteration (mid-stream). `is429(err)` is the single detection
17
+ * predicate; it tolerates both the canonical `RateLimitError` instance and
18
+ * plain `{ status: 429, headers }` objects so tests can craft fakes
19
+ * without importing the SDK class directly.
20
+ *
21
+ * `readRetryAfterSeconds(err, fallback)` consults `err.headers.get("retry-after")`
22
+ * — the spec calls for "Honor the `Retry-After` header when present;
23
+ * otherwise use `default_retry_after_seconds`" (§6.5). Per RFC 9110 the
24
+ * header value is either delta-seconds (e.g. `"60"`) or an HTTP-date; we
25
+ * parse both. Anything malformed falls back to the per-agent default.
26
+ *
27
+ * ## The continuation prompt
28
+ *
29
+ * `CONTINUATION_PROMPT` is the exact text §6.5 specifies for mid-stream
30
+ * recovery. Wording is load-bearing — changing it would change how the
31
+ * agent resumes — so the constant lives here, not as a magic string in the
32
+ * runner.
33
+ *
34
+ * ## Cancellation safety (§6.9)
35
+ *
36
+ * `cancelableSleep(ms, signal)` resolves on either the timer firing OR the
37
+ * signal aborting. On abort the pending `setTimeout` is cleared so the
38
+ * Node event loop doesn't keep the process alive past cancel. The runner's
39
+ * AbortController is composed with `signal` so a `POST /chat/:id/cancel`
40
+ * mid-wait clears the pending recovery in one step.
41
+ *
42
+ * @see SPECIFICATIONS.md §6.5 — TPM recovery
43
+ * @see SPECIFICATIONS.md §6.9 — cancellation
44
+ * @see SPECIFICATIONS.md §17 question 3 — 429 surface
45
+ */
46
+ import type { DrizzleDB } from "../db/connection.js";
47
+ /**
48
+ * Per-agent recovery config as resolved at the top of a turn run.
49
+ *
50
+ * Carried into the runner without re-reading the DB on every retry so a
51
+ * concurrent `PATCH /admin/agents/:id` doesn't change the contract
52
+ * mid-recovery (operationally surprising; the in-flight turn keeps the
53
+ * settings it started with).
54
+ */
55
+ export interface TpmRecoveryConfig {
56
+ /** Master toggle. When `false`, the runner never sleeps + retries on 429 — the turn fails fast with `tpm_exhausted`. */
57
+ enabled: boolean;
58
+ /** Effective ceiling on the *recovery* attempts (the double-failure abort condition in §6.5). Default 2. */
59
+ maxRecoveryAttempts: number;
60
+ /** Fallback when a 429 carries no `Retry-After`. Default 65 (§6.5 — Anthropic's TPM window + buffer). */
61
+ defaultRetryAfterSeconds: number;
62
+ }
63
+ /**
64
+ * The continuation message the runner sends to Letta after a successful
65
+ * mid-stream sleep. Wording verbatim from §6.5; **do not edit** without
66
+ * updating the spec.
67
+ *
68
+ * @see SPECIFICATIONS.md §6.5
69
+ */
70
+ export declare const CONTINUATION_PROMPT = "The previous response was interrupted by an API rate limit. Continue exactly where you left off \u2014 do not restart, do not summarize, just continue.";
71
+ /**
72
+ * Resolves the per-agent TPM-recovery config for the agent that owns
73
+ * `sessionId`. The runner calls this once before opening the Letta stream
74
+ * — settings are pinned for the duration of the turn (see file-level
75
+ * comment).
76
+ *
77
+ * Returns the disabled-default config if the session or agent row can't be
78
+ * found (defensive — handlers above already validate ownership, so a miss
79
+ * here means concurrent deletion, which is impossible in v1 since neither
80
+ * agent nor session can be hard-deleted (§6.3.1 / §6.3.4)).
81
+ *
82
+ * @param db - Drizzle handle.
83
+ * @param sessionId - the session's id (from the request body / runner opts).
84
+ * @returns the per-agent recovery config.
85
+ *
86
+ * @see SPECIFICATIONS.md §11 — `agent` table fields
87
+ */
88
+ export declare function resolveTpmConfig(db: DrizzleDB, sessionId: string): TpmRecoveryConfig;
89
+ /**
90
+ * Resolves the per-agent `stream_idle_timeout_seconds` for the agent
91
+ * owning `sessionId`. The runner calls this once before opening the
92
+ * Letta stream — settings are pinned for the duration of the turn (same
93
+ * pin-at-start contract as `resolveTpmConfig`).
94
+ *
95
+ * Returns the v1 default when no row is found (defensive).
96
+ *
97
+ * @param db - Drizzle handle.
98
+ * @param sessionId - the session's id.
99
+ * @returns the idle timeout in seconds.
100
+ */
101
+ export declare function resolveStreamIdleTimeout(db: DrizzleDB, sessionId: string): number;
102
+ /**
103
+ * Detects a provider 429 surfaced through the Letta SDK.
104
+ *
105
+ * Tolerates three shapes, in order of decreasing specificity:
106
+ *
107
+ * 1. **Canonical** — `RateLimitError` instance from
108
+ * `@letta-ai/letta-client/core/error`. The SDK-typed path; matches
109
+ * when Letta forwards the upstream 429 as an HTTP-level 429 on the
110
+ * `messages.create` connection.
111
+ * 2. **Status-shaped** — plain `{ status: 429, ... }` objects. Tests
112
+ * craft these via `makeFake429`; production also sees them when the
113
+ * SDK has serialized a typed error into a plain object boundary.
114
+ * 3. **Message-shaped fallback** — plain `Error` instances whose
115
+ * `.message` contains "rate limit" / "429" / "too many requests".
116
+ * This catches the [letta-ai/letta#3310](https://github.com/letta-ai/letta/issues/3310)
117
+ * wrapped surface that the **live investigation** observed against
118
+ * `letta/letta:latest` (server 0.16.7) + Anthropic Claude Haiku 4.5:
119
+ * Letta threw a plain Error with the upstream message text and no
120
+ * `status` field. The SDK-source-of-truth `RateLimitError` shape
121
+ * exists in `.d.ts` but is not the surface that actually fires.
122
+ *
123
+ * The match excludes context-window-style messages to avoid the obvious
124
+ * "limit" false-positive — see `looksLikeProviderRateLimitMessage`.
125
+ *
126
+ * @param err - the thrown value.
127
+ * @returns `true` iff the error represents a provider 429.
128
+ *
129
+ * @see SPECIFICATIONS.md §17 question 3
130
+ */
131
+ export declare function is429(err: unknown): boolean;
132
+ /**
133
+ * Predicate that matches a string containing a provider-rate-limit
134
+ * indicator while excluding common false-positives.
135
+ *
136
+ * Used by both `is429` (caught Error → "is this a 429?") and the
137
+ * translator's `error_message` handler (Letta wrapped event → "is this a
138
+ * 429?"). Centralized here so both detection paths apply the same
139
+ * matching rules.
140
+ *
141
+ * **Matches** (case-insensitive substring): `rate limit`, `rate-limit`,
142
+ * `rate_limit`, `ratelimit`, `429`, `too many requests`.
143
+ *
144
+ * **Excludes**: messages containing `context_window` / `context window`
145
+ * (those are size-limit errors, not rate limits) and `usage limit` /
146
+ * `usage_limit` (those are billing-cap exhaustions, not transient rate
147
+ * limits — recovering doesn't help).
148
+ *
149
+ * @param text - the message text to inspect.
150
+ * @returns `true` when `text` looks like a provider rate limit.
151
+ *
152
+ * @see SPECIFICATIONS.md §17 question 3
153
+ */
154
+ export declare function looksLikeProviderRateLimitMessage(text: string): boolean;
155
+ /**
156
+ * Reads the `Retry-After` header off a 429-shaped error.
157
+ *
158
+ * Per RFC 9110 §10.2.3 the header is either delta-seconds (`"60"`) or an
159
+ * `IMF-fixdate`; we handle both. Malformed / missing → `fallbackSeconds`.
160
+ * A `Date.now()`-relative date in the past clamps to 0 (the server is
161
+ * already past the suggested retry window).
162
+ *
163
+ * @param err - the 429-shaped error.
164
+ * @param fallbackSeconds - what to return when no usable header is found.
165
+ * @returns the suggested wait in *seconds* (not ms — the caller multiplies).
166
+ *
167
+ * @see https://datatracker.ietf.org/doc/html/rfc9110#section-10.2.3
168
+ */
169
+ export declare function readRetryAfterSeconds(err: unknown, fallbackSeconds: number): number;
170
+ /**
171
+ * Promise wrapper around `setTimeout` that resolves when the timer fires
172
+ * OR rejects with the supplied `AbortError` shape when the signal fires.
173
+ * Clears the pending `setTimeout` on abort so the Node event loop doesn't
174
+ * keep the process alive past the cancel.
175
+ *
176
+ * Cancellation-safety is the load-bearing property: §6.9 calls for "any
177
+ * in-flight `setTimeout` for TPM recovery is cleared" on cancel, and the
178
+ * Step-6 acceptance criteria has a test that asserts no continuation
179
+ * request fires after a cancel during the wait.
180
+ *
181
+ * @param ms - the delay in milliseconds.
182
+ * @param signal - the abort signal to honor.
183
+ * @returns a promise resolved on timer fire; rejected with `AbortError` on abort.
184
+ *
185
+ * @see SPECIFICATIONS.md §6.9 — cancellation
186
+ */
187
+ export declare function cancelableSleep(ms: number, signal: AbortSignal | undefined): Promise<void>;
188
+ /**
189
+ * Adds a small jitter (0..200 ms) to the suggested wait in seconds so two
190
+ * recovering turns don't both wake up at the exact same millisecond and
191
+ * thunder against the provider again.
192
+ *
193
+ * The jitter is *added*, not subtracted, so we never come in earlier than
194
+ * the provider's suggested window. v1 keeps it deterministic across tests
195
+ * by reading from `Math.random()` — fake-timer tests assert behavior at
196
+ * the boundary, not the exact jitter offset.
197
+ *
198
+ * @param baseSeconds - the value returned by `readRetryAfterSeconds`.
199
+ * @returns the actual delay in milliseconds.
200
+ */
201
+ export declare function withJitter(baseSeconds: number): number;
202
+ //# sourceMappingURL=tpmRecovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tpmRecovery.d.ts","sourceRoot":"","sources":["../../src/letta/tpmRecovery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAOH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,wHAAwH;IACxH,OAAO,EAAE,OAAO,CAAC;IACjB,4GAA4G;IAC5G,mBAAmB,EAAE,MAAM,CAAC;IAC5B,yGAAyG;IACzG,wBAAwB,EAAE,MAAM,CAAC;CAClC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,4JACsH,CAAC;AAEvJ;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,SAAS,EACb,SAAS,EAAE,MAAM,GAChB,iBAAiB,CAuBnB;AAYD;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CACtC,EAAE,EAAE,SAAS,EACb,SAAS,EAAE,MAAM,GAChB,MAAM,CAQR;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAU3C;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,iCAAiC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAYvE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,OAAO,EACZ,eAAe,EAAE,MAAM,GACtB,MAAM,CAaR;AAkBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAC7B,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,WAAW,GAAG,SAAS,GAC9B,OAAO,CAAC,IAAI,CAAC,CAgBf;AASD;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAGtD"}
@@ -0,0 +1,314 @@
1
+ /**
2
+ * TPM-recovery primitives — provider-429 retry/continuation loop (§6.5).
3
+ *
4
+ * The orchestration lives in `state/turnRunner.ts`. This file contains the
5
+ * pure helpers + the per-agent config resolver + the continuation prompt
6
+ * text + a cancellable sleep, in one place so the runner stays focused on
7
+ * the state-machine narrative and the helpers stay unit-testable in
8
+ * isolation.
9
+ *
10
+ * ## Detection (§17 question 3)
11
+ *
12
+ * The Letta TypeScript SDK exposes a typed `RateLimitError extends APIError<429, Headers>`
13
+ * (see `@letta-ai/letta-client/core/error`). When a provider 429 surfaces
14
+ * through `messages.create`, the SDK throws an error whose `status === 429`
15
+ * — either before the iterator yields its first event (pre-stream) or
16
+ * mid-iteration (mid-stream). `is429(err)` is the single detection
17
+ * predicate; it tolerates both the canonical `RateLimitError` instance and
18
+ * plain `{ status: 429, headers }` objects so tests can craft fakes
19
+ * without importing the SDK class directly.
20
+ *
21
+ * `readRetryAfterSeconds(err, fallback)` consults `err.headers.get("retry-after")`
22
+ * — the spec calls for "Honor the `Retry-After` header when present;
23
+ * otherwise use `default_retry_after_seconds`" (§6.5). Per RFC 9110 the
24
+ * header value is either delta-seconds (e.g. `"60"`) or an HTTP-date; we
25
+ * parse both. Anything malformed falls back to the per-agent default.
26
+ *
27
+ * ## The continuation prompt
28
+ *
29
+ * `CONTINUATION_PROMPT` is the exact text §6.5 specifies for mid-stream
30
+ * recovery. Wording is load-bearing — changing it would change how the
31
+ * agent resumes — so the constant lives here, not as a magic string in the
32
+ * runner.
33
+ *
34
+ * ## Cancellation safety (§6.9)
35
+ *
36
+ * `cancelableSleep(ms, signal)` resolves on either the timer firing OR the
37
+ * signal aborting. On abort the pending `setTimeout` is cleared so the
38
+ * Node event loop doesn't keep the process alive past cancel. The runner's
39
+ * AbortController is composed with `signal` so a `POST /chat/:id/cancel`
40
+ * mid-wait clears the pending recovery in one step.
41
+ *
42
+ * @see SPECIFICATIONS.md §6.5 — TPM recovery
43
+ * @see SPECIFICATIONS.md §6.9 — cancellation
44
+ * @see SPECIFICATIONS.md §17 question 3 — 429 surface
45
+ */
46
+ import { eq } from "drizzle-orm";
47
+ import { RateLimitError } from "@letta-ai/letta-client";
48
+ import { agent, session } from "../db/schema.js";
49
+ /**
50
+ * The continuation message the runner sends to Letta after a successful
51
+ * mid-stream sleep. Wording verbatim from §6.5; **do not edit** without
52
+ * updating the spec.
53
+ *
54
+ * @see SPECIFICATIONS.md §6.5
55
+ */
56
+ export const CONTINUATION_PROMPT = "The previous response was interrupted by an API rate limit. Continue exactly where you left off — do not restart, do not summarize, just continue.";
57
+ /**
58
+ * Resolves the per-agent TPM-recovery config for the agent that owns
59
+ * `sessionId`. The runner calls this once before opening the Letta stream
60
+ * — settings are pinned for the duration of the turn (see file-level
61
+ * comment).
62
+ *
63
+ * Returns the disabled-default config if the session or agent row can't be
64
+ * found (defensive — handlers above already validate ownership, so a miss
65
+ * here means concurrent deletion, which is impossible in v1 since neither
66
+ * agent nor session can be hard-deleted (§6.3.1 / §6.3.4)).
67
+ *
68
+ * @param db - Drizzle handle.
69
+ * @param sessionId - the session's id (from the request body / runner opts).
70
+ * @returns the per-agent recovery config.
71
+ *
72
+ * @see SPECIFICATIONS.md §11 — `agent` table fields
73
+ */
74
+ export function resolveTpmConfig(db, sessionId) {
75
+ const row = db
76
+ .select({
77
+ enabled: agent.tpmRecoveryEnabled,
78
+ max: agent.maxRecoveryAttempts,
79
+ retryAfter: agent.defaultRetryAfterSeconds,
80
+ })
81
+ .from(session)
82
+ .innerJoin(agent, eq(session.agentId, agent.id))
83
+ .where(eq(session.id, sessionId))
84
+ .get();
85
+ if (!row) {
86
+ return {
87
+ enabled: false,
88
+ maxRecoveryAttempts: 2,
89
+ defaultRetryAfterSeconds: 65,
90
+ };
91
+ }
92
+ return {
93
+ enabled: row.enabled,
94
+ maxRecoveryAttempts: row.max,
95
+ defaultRetryAfterSeconds: row.retryAfter,
96
+ };
97
+ }
98
+ /**
99
+ * Default `streamIdleTimeoutSeconds` when no agent row can be found (e.g.
100
+ * concurrent deletion — impossible in v1 since agents are archive-only,
101
+ * but the defensive default keeps the runner well-behaved either way).
102
+ * Matches the column default in `agent` (Step-10 closeout). Independent
103
+ * of TPM defaults — TPM has its own 65s timer that doesn't share with
104
+ * this idle watchdog.
105
+ */
106
+ const DEFAULT_STREAM_IDLE_TIMEOUT_SECONDS = 60;
107
+ /**
108
+ * Resolves the per-agent `stream_idle_timeout_seconds` for the agent
109
+ * owning `sessionId`. The runner calls this once before opening the
110
+ * Letta stream — settings are pinned for the duration of the turn (same
111
+ * pin-at-start contract as `resolveTpmConfig`).
112
+ *
113
+ * Returns the v1 default when no row is found (defensive).
114
+ *
115
+ * @param db - Drizzle handle.
116
+ * @param sessionId - the session's id.
117
+ * @returns the idle timeout in seconds.
118
+ */
119
+ export function resolveStreamIdleTimeout(db, sessionId) {
120
+ const row = db
121
+ .select({ idleTimeout: agent.streamIdleTimeoutSeconds })
122
+ .from(session)
123
+ .innerJoin(agent, eq(session.agentId, agent.id))
124
+ .where(eq(session.id, sessionId))
125
+ .get();
126
+ return row?.idleTimeout ?? DEFAULT_STREAM_IDLE_TIMEOUT_SECONDS;
127
+ }
128
+ /**
129
+ * Detects a provider 429 surfaced through the Letta SDK.
130
+ *
131
+ * Tolerates three shapes, in order of decreasing specificity:
132
+ *
133
+ * 1. **Canonical** — `RateLimitError` instance from
134
+ * `@letta-ai/letta-client/core/error`. The SDK-typed path; matches
135
+ * when Letta forwards the upstream 429 as an HTTP-level 429 on the
136
+ * `messages.create` connection.
137
+ * 2. **Status-shaped** — plain `{ status: 429, ... }` objects. Tests
138
+ * craft these via `makeFake429`; production also sees them when the
139
+ * SDK has serialized a typed error into a plain object boundary.
140
+ * 3. **Message-shaped fallback** — plain `Error` instances whose
141
+ * `.message` contains "rate limit" / "429" / "too many requests".
142
+ * This catches the [letta-ai/letta#3310](https://github.com/letta-ai/letta/issues/3310)
143
+ * wrapped surface that the **live investigation** observed against
144
+ * `letta/letta:latest` (server 0.16.7) + Anthropic Claude Haiku 4.5:
145
+ * Letta threw a plain Error with the upstream message text and no
146
+ * `status` field. The SDK-source-of-truth `RateLimitError` shape
147
+ * exists in `.d.ts` but is not the surface that actually fires.
148
+ *
149
+ * The match excludes context-window-style messages to avoid the obvious
150
+ * "limit" false-positive — see `looksLikeProviderRateLimitMessage`.
151
+ *
152
+ * @param err - the thrown value.
153
+ * @returns `true` iff the error represents a provider 429.
154
+ *
155
+ * @see SPECIFICATIONS.md §17 question 3
156
+ */
157
+ export function is429(err) {
158
+ if (err instanceof RateLimitError)
159
+ return true;
160
+ if (err && typeof err === "object") {
161
+ const e = err;
162
+ if (e.status === 429)
163
+ return true;
164
+ if (typeof e.message === "string" && looksLikeProviderRateLimitMessage(e.message)) {
165
+ return true;
166
+ }
167
+ }
168
+ return false;
169
+ }
170
+ /**
171
+ * Predicate that matches a string containing a provider-rate-limit
172
+ * indicator while excluding common false-positives.
173
+ *
174
+ * Used by both `is429` (caught Error → "is this a 429?") and the
175
+ * translator's `error_message` handler (Letta wrapped event → "is this a
176
+ * 429?"). Centralized here so both detection paths apply the same
177
+ * matching rules.
178
+ *
179
+ * **Matches** (case-insensitive substring): `rate limit`, `rate-limit`,
180
+ * `rate_limit`, `ratelimit`, `429`, `too many requests`.
181
+ *
182
+ * **Excludes**: messages containing `context_window` / `context window`
183
+ * (those are size-limit errors, not rate limits) and `usage limit` /
184
+ * `usage_limit` (those are billing-cap exhaustions, not transient rate
185
+ * limits — recovering doesn't help).
186
+ *
187
+ * @param text - the message text to inspect.
188
+ * @returns `true` when `text` looks like a provider rate limit.
189
+ *
190
+ * @see SPECIFICATIONS.md §17 question 3
191
+ */
192
+ export function looksLikeProviderRateLimitMessage(text) {
193
+ const l = text.toLowerCase();
194
+ if (l.includes("context_window") || l.includes("context window"))
195
+ return false;
196
+ if (l.includes("usage limit") || l.includes("usage_limit"))
197
+ return false;
198
+ return (l.includes("rate limit") ||
199
+ l.includes("rate-limit") ||
200
+ l.includes("rate_limit") ||
201
+ l.includes("ratelimit") ||
202
+ l.includes("429") ||
203
+ l.includes("too many requests"));
204
+ }
205
+ /**
206
+ * Reads the `Retry-After` header off a 429-shaped error.
207
+ *
208
+ * Per RFC 9110 §10.2.3 the header is either delta-seconds (`"60"`) or an
209
+ * `IMF-fixdate`; we handle both. Malformed / missing → `fallbackSeconds`.
210
+ * A `Date.now()`-relative date in the past clamps to 0 (the server is
211
+ * already past the suggested retry window).
212
+ *
213
+ * @param err - the 429-shaped error.
214
+ * @param fallbackSeconds - what to return when no usable header is found.
215
+ * @returns the suggested wait in *seconds* (not ms — the caller multiplies).
216
+ *
217
+ * @see https://datatracker.ietf.org/doc/html/rfc9110#section-10.2.3
218
+ */
219
+ export function readRetryAfterSeconds(err, fallbackSeconds) {
220
+ const headers = extractHeaders(err);
221
+ if (!headers)
222
+ return fallbackSeconds;
223
+ const raw = headers.get("retry-after");
224
+ if (!raw)
225
+ return fallbackSeconds;
226
+ const asNumber = Number(raw);
227
+ if (Number.isFinite(asNumber) && asNumber >= 0)
228
+ return Math.round(asNumber);
229
+ const asDate = Date.parse(raw);
230
+ if (!Number.isNaN(asDate)) {
231
+ const delta = Math.round((asDate - Date.now()) / 1000);
232
+ return delta > 0 ? delta : 0;
233
+ }
234
+ return fallbackSeconds;
235
+ }
236
+ /** Pulls the `Headers` off either the typed SDK error or a plain mock. */
237
+ function extractHeaders(err) {
238
+ if (!err || typeof err !== "object")
239
+ return null;
240
+ const e = err;
241
+ if (e.headers instanceof Headers)
242
+ return e.headers;
243
+ // Plain object support — tests build a `{ "retry-after": "1" }`-style record.
244
+ if (e.headers && typeof e.headers === "object") {
245
+ const h = new Headers();
246
+ for (const [k, v] of Object.entries(e.headers)) {
247
+ if (typeof v === "string")
248
+ h.set(k, v);
249
+ }
250
+ return h;
251
+ }
252
+ return null;
253
+ }
254
+ /**
255
+ * Promise wrapper around `setTimeout` that resolves when the timer fires
256
+ * OR rejects with the supplied `AbortError` shape when the signal fires.
257
+ * Clears the pending `setTimeout` on abort so the Node event loop doesn't
258
+ * keep the process alive past the cancel.
259
+ *
260
+ * Cancellation-safety is the load-bearing property: §6.9 calls for "any
261
+ * in-flight `setTimeout` for TPM recovery is cleared" on cancel, and the
262
+ * Step-6 acceptance criteria has a test that asserts no continuation
263
+ * request fires after a cancel during the wait.
264
+ *
265
+ * @param ms - the delay in milliseconds.
266
+ * @param signal - the abort signal to honor.
267
+ * @returns a promise resolved on timer fire; rejected with `AbortError` on abort.
268
+ *
269
+ * @see SPECIFICATIONS.md §6.9 — cancellation
270
+ */
271
+ export function cancelableSleep(ms, signal) {
272
+ if (signal?.aborted) {
273
+ return Promise.reject(makeAbortError());
274
+ }
275
+ return new Promise((resolve, reject) => {
276
+ const timer = setTimeout(() => {
277
+ if (signal)
278
+ signal.removeEventListener("abort", onAbort);
279
+ resolve();
280
+ }, ms);
281
+ const onAbort = () => {
282
+ clearTimeout(timer);
283
+ if (signal)
284
+ signal.removeEventListener("abort", onAbort);
285
+ reject(makeAbortError());
286
+ };
287
+ if (signal)
288
+ signal.addEventListener("abort", onAbort, { once: true });
289
+ });
290
+ }
291
+ /** Constructs an `AbortError`-shaped Error consistent with the runner's existing sentinel. */
292
+ function makeAbortError() {
293
+ const e = new Error("aborted");
294
+ e.name = "AbortError";
295
+ return e;
296
+ }
297
+ /**
298
+ * Adds a small jitter (0..200 ms) to the suggested wait in seconds so two
299
+ * recovering turns don't both wake up at the exact same millisecond and
300
+ * thunder against the provider again.
301
+ *
302
+ * The jitter is *added*, not subtracted, so we never come in earlier than
303
+ * the provider's suggested window. v1 keeps it deterministic across tests
304
+ * by reading from `Math.random()` — fake-timer tests assert behavior at
305
+ * the boundary, not the exact jitter offset.
306
+ *
307
+ * @param baseSeconds - the value returned by `readRetryAfterSeconds`.
308
+ * @returns the actual delay in milliseconds.
309
+ */
310
+ export function withJitter(baseSeconds) {
311
+ const jitter = Math.floor(Math.random() * 200);
312
+ return Math.max(0, baseSeconds * 1000 + jitter);
313
+ }
314
+ //# sourceMappingURL=tpmRecovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tpmRecovery.js","sourceRoot":"","sources":["../../src/letta/tpmRecovery.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Project-shaped Letta types.
3
+ *
4
+ * Re-exports only the SDK surfaces ai-hub uses, so the rest of the server
5
+ * code never has to traverse `@letta-ai/letta-client`'s deep namespace
6
+ * structure. **No raw HTTP, ever** — these are aliases over the SDK's own
7
+ * types, not parallel inventions (§6.4).
8
+ *
9
+ * If the SDK rev removes or renames one of these, the typechecker flags it
10
+ * at upgrade time and `letta/client.ts` is the single place to adapt.
11
+ *
12
+ * @see SPECIFICATIONS.md §6.4 — SDK-only access pattern
13
+ * @see SPECIFICATIONS.md §6.4.1 — first-class surfaces we depend on
14
+ */
15
+ import type Letta from "@letta-ai/letta-client";
16
+ import type { AssistantMessage, HiddenReasoningMessage, Message as LettaSdkMessage, ReasoningMessage, ToolCallMessage, UserMessage } from "@letta-ai/letta-client/resources/agents/messages.js";
17
+ import type { LettaStreamingResponse } from "@letta-ai/letta-client/resources/agents/messages.js";
18
+ import type { ToolReturnMessage } from "@letta-ai/letta-client/resources/tools.js";
19
+ /**
20
+ * The underlying SDK client. We `import type` everywhere except in
21
+ * `letta/client.ts` (where the constructor actually runs) so the rest of the
22
+ * codebase stays SDK-version-agnostic in shape.
23
+ */
24
+ export type LettaSdk = Letta;
25
+ /**
26
+ * One streaming event from `client.conversations.messages.create({ streaming: true })`.
27
+ *
28
+ * The union spans every variant Letta emits in an SSE stream — assistant
29
+ * text, reasoning, tool calls, tool returns, errors, pings, stop reasons,
30
+ * usage stats. `letta/eventTranslator.ts` is the pure function from this
31
+ * union to ai-hub's wire-protocol events (§9).
32
+ *
33
+ * @see SPECIFICATIONS.md §9 — wire protocol
34
+ */
35
+ export type LettaStreamEvent = LettaStreamingResponse;
36
+ /**
37
+ * Alias for `AssistantMessage` — used by the translator + tests. See
38
+ * `letta/eventTranslator.ts` for the streamed-delta vs. cumulative-content
39
+ * note.
40
+ */
41
+ export type LettaAssistantMessage = AssistantMessage;
42
+ /** Alias for `ReasoningMessage` — see `eventTranslator.ts`. */
43
+ export type LettaReasoningMessage = ReasoningMessage;
44
+ /** Alias for `HiddenReasoningMessage` — see `eventTranslator.ts`. */
45
+ export type LettaHiddenReasoningMessage = HiddenReasoningMessage;
46
+ /** Alias for `ToolCallMessage` — see `eventTranslator.ts`. */
47
+ export type LettaToolCallMessage = ToolCallMessage;
48
+ /** Alias for `ToolReturnMessage` — see `eventTranslator.ts`. */
49
+ export type LettaToolReturnMessage = ToolReturnMessage;
50
+ /** Alias for `UserMessage` — only appears in history reads, not in streams. */
51
+ export type LettaUserMessage = UserMessage;
52
+ /** Full assembled Letta message (used by `messages.list`). */
53
+ export type LettaMessage = LettaSdkMessage;
54
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/letta/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,KAAK,MAAM,wBAAwB,CAAC;AAChD,OAAO,KAAK,EACV,gBAAgB,EAChB,sBAAsB,EACtB,OAAO,IAAI,eAAe,EAC1B,gBAAgB,EAChB,eAAe,EACf,WAAW,EACZ,MAAM,qDAAqD,CAAC;AAC7D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qDAAqD,CAAC;AAClG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2CAA2C,CAAC;AAEnF;;;;GAIG;AACH,MAAM,MAAM,QAAQ,GAAG,KAAK,CAAC;AAE7B;;;;;;;;;GASG;AACH,MAAM,MAAM,gBAAgB,GAAG,sBAAsB,CAAC;AAEtD;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAAG,gBAAgB,CAAC;AACrD,+DAA+D;AAC/D,MAAM,MAAM,qBAAqB,GAAG,gBAAgB,CAAC;AACrD,qEAAqE;AACrE,MAAM,MAAM,2BAA2B,GAAG,sBAAsB,CAAC;AACjE,8DAA8D;AAC9D,MAAM,MAAM,oBAAoB,GAAG,eAAe,CAAC;AACnD,gEAAgE;AAChE,MAAM,MAAM,sBAAsB,GAAG,iBAAiB,CAAC;AACvD,+EAA+E;AAC/E,MAAM,MAAM,gBAAgB,GAAG,WAAW,CAAC;AAE3C,8DAA8D;AAC9D,MAAM,MAAM,YAAY,GAAG,eAAe,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Project-shaped Letta types.
3
+ *
4
+ * Re-exports only the SDK surfaces ai-hub uses, so the rest of the server
5
+ * code never has to traverse `@letta-ai/letta-client`'s deep namespace
6
+ * structure. **No raw HTTP, ever** — these are aliases over the SDK's own
7
+ * types, not parallel inventions (§6.4).
8
+ *
9
+ * If the SDK rev removes or renames one of these, the typechecker flags it
10
+ * at upgrade time and `letta/client.ts` is the single place to adapt.
11
+ *
12
+ * @see SPECIFICATIONS.md §6.4 — SDK-only access pattern
13
+ * @see SPECIFICATIONS.md §6.4.1 — first-class surfaces we depend on
14
+ */
15
+ export {};
16
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/letta/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Structured logging via pino.
3
+ *
4
+ * Production: raw JSON to stdout, one line per record (§6.10 — twelve-factor
5
+ * pattern). Dev: piped through `pino-pretty` for human-readable output, gated
6
+ * on `NODE_ENV !== "production"` or `LOG_LEVEL=debug`.
7
+ *
8
+ * **Redaction is a load-bearing security control** (§13.3). pino's built-in
9
+ * `redact` option is the single point of truth — every log call goes through
10
+ * the configured logger, and a regression test asserts a known-secret value
11
+ * never appears in captured stdout.
12
+ *
13
+ * @see SPECIFICATIONS.md §6.10 — observability
14
+ * @see SPECIFICATIONS.md §13.3 — secrets
15
+ */
16
+ import { type Logger as PinoLogger } from "pino";
17
+ /** What the rest of the server consumes — keeps pino out of import surfaces. */
18
+ export type Logger = PinoLogger;
19
+ /**
20
+ * Optional shape passed to `createLogger`. The dev-mode pretty-print stream
21
+ * is set up by the caller (the dev/server entrypoint) — `createLogger` itself
22
+ * stays format-agnostic so tests can pass a writable stream.
23
+ */
24
+ export interface CreateLoggerOptions {
25
+ /** pino level (`fatal | error | warn | info | debug | trace`). Default `info`. */
26
+ level?: string;
27
+ /**
28
+ * Optional destination. Tests pass a memory stream so they can assert on
29
+ * the captured output. Production passes nothing — pino writes to stdout
30
+ * by default.
31
+ */
32
+ destination?: NodeJS.WritableStream;
33
+ }
34
+ /**
35
+ * Builds a pino logger with the project's redaction policy applied.
36
+ *
37
+ * @param opts - level + optional destination override.
38
+ * @returns the pino logger instance.
39
+ */
40
+ export declare function createLogger(opts?: CreateLoggerOptions): Logger;
41
+ /**
42
+ * The redaction-path list — exposed for tests so a new path added in
43
+ * `REDACT_PATHS` is automatically covered without test churn.
44
+ *
45
+ * @returns a fresh array of the redaction paths used by `createLogger`.
46
+ */
47
+ export declare function getRedactPaths(): string[];
48
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/observability/logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAQ,KAAK,MAAM,IAAI,UAAU,EAAsB,MAAM,MAAM,CAAC;AAE3E,gFAAgF;AAChF,MAAM,MAAM,MAAM,GAAG,UAAU,CAAC;AAyChC;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,kFAAkF;IAClF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;CACrC;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,GAAE,mBAAwB,GAAG,MAAM,CAUnE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,IAAI,MAAM,EAAE,CAEzC"}