@elizaos/plugin-elizacloud 2.0.0-beta.1 → 2.0.11-beta.7

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 (285) hide show
  1. package/README.md +20 -44
  2. package/auto-enable.ts +10 -5
  3. package/dist/browser/index.browser.js +2 -2
  4. package/dist/browser/index.browser.js.map +4 -4
  5. package/dist/cjs/index.node.cjs +2874 -5915
  6. package/dist/cjs/index.node.js.map +47 -116
  7. package/dist/cloud/auth-service-types.d.ts +8 -0
  8. package/dist/cloud/auth-service-types.d.ts.map +1 -0
  9. package/dist/cloud/auth-service-types.js +36 -0
  10. package/dist/cloud/auth-service-types.js.map +10 -0
  11. package/dist/cloud/auth.js +4 -51
  12. package/dist/cloud/auth.js.map +4 -4
  13. package/dist/cloud/base-url.d.ts +6 -2
  14. package/dist/cloud/base-url.d.ts.map +1 -1
  15. package/dist/cloud/base-url.js +3 -51
  16. package/dist/cloud/base-url.js.map +3 -3
  17. package/dist/cloud/bridge-client.d.ts +3 -3
  18. package/dist/cloud/bridge-client.d.ts.map +1 -1
  19. package/dist/cloud/bridge-client.js +3 -51
  20. package/dist/cloud/bridge-client.js.map +3 -3
  21. package/dist/cloud/clack-observer.d.ts +35 -0
  22. package/dist/cloud/clack-observer.d.ts.map +1 -0
  23. package/dist/cloud/clack-observer.js +143 -0
  24. package/dist/cloud/clack-observer.js.map +10 -0
  25. package/dist/cloud/cloud-manager.js +45 -92
  26. package/dist/cloud/cloud-manager.js.map +6 -6
  27. package/dist/cloud/cloud-wallet.js +2 -4835
  28. package/dist/cloud/cloud-wallet.js.map +3 -82
  29. package/dist/cloud/duffel-client.d.ts +181 -0
  30. package/dist/cloud/duffel-client.d.ts.map +1 -0
  31. package/dist/cloud/duffel-client.js +506 -0
  32. package/dist/cloud/duffel-client.js.map +11 -0
  33. package/dist/cloud/index.d.ts +6 -0
  34. package/dist/cloud/index.d.ts.map +1 -1
  35. package/dist/cloud/index.js +1782 -1
  36. package/dist/cloud/index.js.map +18 -3
  37. package/dist/cloud/lifeops-schedule-sync-client.d.ts +43 -0
  38. package/dist/cloud/lifeops-schedule-sync-client.d.ts.map +1 -0
  39. package/dist/cloud/lifeops-schedule-sync-client.js +180 -0
  40. package/dist/cloud/lifeops-schedule-sync-client.js.map +11 -0
  41. package/dist/cloud/lifeops-schedule-sync-contracts.d.ts +89 -0
  42. package/dist/cloud/lifeops-schedule-sync-contracts.d.ts.map +1 -0
  43. package/dist/cloud/lifeops-schedule-sync-contracts.js +39 -0
  44. package/dist/cloud/lifeops-schedule-sync-contracts.js.map +10 -0
  45. package/dist/cloud/managed-payment-clients.d.ts +166 -0
  46. package/dist/cloud/managed-payment-clients.d.ts.map +1 -0
  47. package/dist/cloud/managed-payment-clients.js +238 -0
  48. package/dist/cloud/managed-payment-clients.js.map +11 -0
  49. package/dist/cloud/null-observer.d.ts +35 -0
  50. package/dist/cloud/null-observer.d.ts.map +1 -0
  51. package/dist/cloud/null-observer.js +45 -0
  52. package/dist/cloud/null-observer.js.map +10 -0
  53. package/dist/cloud/setup-observer.d.ts +98 -0
  54. package/dist/cloud/setup-observer.d.ts.map +1 -0
  55. package/dist/cloud/setup-observer.js +2 -0
  56. package/dist/cloud/setup-observer.js.map +9 -0
  57. package/dist/cloud/validate-url.d.ts.map +1 -1
  58. package/dist/cloud/validate-url.js +2 -1
  59. package/dist/cloud/validate-url.js.map +3 -3
  60. package/dist/cloud/x402-payment-handler.d.ts +85 -0
  61. package/dist/cloud/x402-payment-handler.d.ts.map +1 -0
  62. package/dist/cloud/x402-payment-handler.js +119 -0
  63. package/dist/cloud/x402-payment-handler.js.map +10 -0
  64. package/dist/cloud-setup.d.ts +36 -0
  65. package/dist/cloud-setup.d.ts.map +1 -0
  66. package/dist/{onboarding.js → cloud-setup.js} +139 -139
  67. package/dist/cloud-setup.js.map +14 -0
  68. package/dist/cloud-voice-catalog.d.ts +65 -0
  69. package/dist/cloud-voice-catalog.d.ts.map +1 -0
  70. package/dist/cloud-voice-catalog.js +278 -0
  71. package/dist/cloud-voice-catalog.js.map +12 -0
  72. package/dist/index.browser.d.ts +11 -0
  73. package/dist/index.browser.d.ts.map +1 -1
  74. package/dist/index.d.ts +7 -1
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +5416 -8405
  77. package/dist/index.js.map +48 -116
  78. package/dist/index.node.d.ts +8 -1
  79. package/dist/index.node.d.ts.map +1 -1
  80. package/dist/init.js +17 -4
  81. package/dist/init.js.map +4 -4
  82. package/dist/lib/cloud-connection.d.ts +0 -1
  83. package/dist/lib/cloud-connection.d.ts.map +1 -1
  84. package/dist/lib/cloud-connection.js +14 -91
  85. package/dist/lib/cloud-connection.js.map +7 -7
  86. package/dist/lib/cloud-secrets.d.ts +5 -18
  87. package/dist/lib/cloud-secrets.d.ts.map +1 -1
  88. package/dist/lib/cloud-secrets.js +8 -36
  89. package/dist/lib/cloud-secrets.js.map +3 -3
  90. package/dist/lib/config-like.d.ts +1 -1
  91. package/dist/lib/config-like.d.ts.map +1 -1
  92. package/dist/lib/config-like.js +3 -3
  93. package/dist/lib/config-like.js.map +3 -3
  94. package/dist/lib/credential-type-map.d.ts +1 -1
  95. package/dist/lib/credential-type-map.js.map +1 -1
  96. package/dist/lib/http.d.ts +0 -11
  97. package/dist/lib/http.d.ts.map +1 -1
  98. package/dist/lib/http.js.map +2 -2
  99. package/dist/lib/server-cloud-tts.d.ts +12 -25
  100. package/dist/lib/server-cloud-tts.d.ts.map +1 -1
  101. package/dist/lib/server-cloud-tts.js +31 -329
  102. package/dist/lib/server-cloud-tts.js.map +4 -7
  103. package/dist/lib/tts-debug.d.ts +5 -3
  104. package/dist/lib/tts-debug.d.ts.map +1 -1
  105. package/dist/lib/tts-debug.js +1 -34
  106. package/dist/lib/tts-debug.js.map +3 -4
  107. package/dist/models/embeddings.d.ts.map +1 -1
  108. package/dist/models/embeddings.js +79 -69
  109. package/dist/models/embeddings.js.map +6 -6
  110. package/dist/models/image.d.ts.map +1 -1
  111. package/dist/models/image.js +42 -15
  112. package/dist/models/image.js.map +6 -6
  113. package/dist/models/index.js +676 -166
  114. package/dist/models/index.js.map +11 -12
  115. package/dist/models/research.d.ts.map +1 -1
  116. package/dist/models/research.js +24 -7
  117. package/dist/models/research.js.map +6 -6
  118. package/dist/models/speech.d.ts +61 -3
  119. package/dist/models/speech.d.ts.map +1 -1
  120. package/dist/models/speech.js +173 -17
  121. package/dist/models/speech.js.map +5 -5
  122. package/dist/models/text.d.ts +106 -1
  123. package/dist/models/text.d.ts.map +1 -1
  124. package/dist/models/text.js +452 -82
  125. package/dist/models/text.js.map +7 -8
  126. package/dist/models/tokenization.d.ts.map +1 -1
  127. package/dist/models/tokenization.js.map +2 -2
  128. package/dist/models/transcription.d.ts.map +1 -1
  129. package/dist/models/transcription.js +20 -6
  130. package/dist/models/transcription.js.map +5 -5
  131. package/dist/node/index.node.js +2828 -5838
  132. package/dist/node/index.node.js.map +47 -116
  133. package/dist/plugin.d.ts.map +1 -1
  134. package/dist/plugin.js +376 -5050
  135. package/dist/plugin.js.map +16 -92
  136. package/dist/providers/openai.js +11 -2
  137. package/dist/providers/openai.js.map +3 -3
  138. package/dist/register-routes.js +376 -5050
  139. package/dist/register-routes.js.map +16 -92
  140. package/dist/routes/cloud-billing-routes.d.ts.map +1 -1
  141. package/dist/routes/cloud-billing-routes.js +17 -60
  142. package/dist/routes/cloud-billing-routes.js.map +8 -7
  143. package/dist/routes/cloud-coding-container-routes.d.ts +8 -0
  144. package/dist/routes/cloud-coding-container-routes.d.ts.map +1 -0
  145. package/dist/routes/cloud-coding-container-routes.js +214 -0
  146. package/dist/routes/cloud-coding-container-routes.js.map +11 -0
  147. package/dist/routes/cloud-compat-routes.d.ts.map +1 -1
  148. package/dist/routes/cloud-compat-routes.js +17 -60
  149. package/dist/routes/cloud-compat-routes.js.map +8 -7
  150. package/dist/routes/cloud-features-routes.js +2 -2
  151. package/dist/routes/cloud-features-routes.js.map +4 -4
  152. package/dist/routes/cloud-relay-routes.d.ts +2 -1
  153. package/dist/routes/cloud-relay-routes.d.ts.map +1 -1
  154. package/dist/routes/cloud-relay-routes.js +84 -2
  155. package/dist/routes/cloud-relay-routes.js.map +5 -4
  156. package/dist/routes/cloud-routes-autonomous.d.ts +3 -4
  157. package/dist/routes/cloud-routes-autonomous.d.ts.map +1 -1
  158. package/dist/routes/cloud-routes-autonomous.js +11 -4893
  159. package/dist/routes/cloud-routes-autonomous.js.map +8 -87
  160. package/dist/routes/cloud-routes.d.ts +2 -2
  161. package/dist/routes/cloud-routes.d.ts.map +1 -1
  162. package/dist/routes/cloud-routes.js +343 -5058
  163. package/dist/routes/cloud-routes.js.map +13 -90
  164. package/dist/routes/cloud-status-routes-autonomous.d.ts +1 -2
  165. package/dist/routes/cloud-status-routes-autonomous.d.ts.map +1 -1
  166. package/dist/routes/cloud-status-routes-autonomous.js +4 -51
  167. package/dist/routes/cloud-status-routes-autonomous.js.map +5 -5
  168. package/dist/routes/cloud-status-routes.js +14 -90
  169. package/dist/routes/cloud-status-routes.js.map +7 -7
  170. package/dist/routes/home-remote-runner-access-url.d.ts +16 -0
  171. package/dist/routes/home-remote-runner-access-url.d.ts.map +1 -0
  172. package/dist/routes/home-remote-runner-access-url.js +91 -0
  173. package/dist/routes/home-remote-runner-access-url.js.map +10 -0
  174. package/dist/routes/travel-provider-relay-routes.d.ts +9 -0
  175. package/dist/routes/travel-provider-relay-routes.d.ts.map +1 -0
  176. package/dist/routes/travel-provider-relay-routes.js +358 -0
  177. package/dist/routes/travel-provider-relay-routes.js.map +14 -0
  178. package/dist/services/cloud-auth.d.ts +1 -1
  179. package/dist/services/cloud-auth.d.ts.map +1 -1
  180. package/dist/services/cloud-auth.js +7 -2
  181. package/dist/services/cloud-auth.js.map +4 -4
  182. package/dist/services/cloud-backup.js.map +2 -2
  183. package/dist/services/cloud-bootstrap.d.ts.map +1 -1
  184. package/dist/services/cloud-bootstrap.js.map +2 -2
  185. package/dist/services/cloud-bridge.js.map +3 -3
  186. package/dist/services/cloud-container.d.ts +5 -1
  187. package/dist/services/cloud-container.d.ts.map +1 -1
  188. package/dist/services/cloud-container.js +52 -1
  189. package/dist/services/cloud-container.js.map +4 -4
  190. package/dist/services/cloud-credential-provider.js.map +2 -2
  191. package/dist/services/cloud-model-registry.js.map +2 -2
  192. package/dist/types/cloud.d.ts +1 -0
  193. package/dist/types/cloud.d.ts.map +1 -1
  194. package/dist/types/cloud.js.map +2 -2
  195. package/dist/types/index.d.ts +1 -1
  196. package/dist/types/index.d.ts.map +1 -1
  197. package/dist/utils/cloud-sdk/client.d.ts.map +1 -1
  198. package/dist/utils/cloud-sdk/client.js +136 -4
  199. package/dist/utils/cloud-sdk/client.js.map +5 -5
  200. package/dist/utils/cloud-sdk/http.js.map +1 -1
  201. package/dist/utils/cloud-sdk/public-routes.d.ts +186 -0
  202. package/dist/utils/cloud-sdk/public-routes.d.ts.map +1 -1
  203. package/dist/utils/cloud-sdk/public-routes.js +99 -1
  204. package/dist/utils/cloud-sdk/public-routes.js.map +3 -3
  205. package/dist/utils/cloud-sdk/types.d.ts +0 -2
  206. package/dist/utils/cloud-sdk/types.d.ts.map +1 -1
  207. package/dist/utils/cloud-sdk/types.js.map +1 -1
  208. package/dist/utils/config.d.ts +10 -1
  209. package/dist/utils/config.d.ts.map +1 -1
  210. package/dist/utils/config.js +12 -2
  211. package/dist/utils/config.js.map +3 -3
  212. package/dist/utils/events.d.ts +23 -2
  213. package/dist/utils/events.d.ts.map +1 -1
  214. package/dist/utils/events.js +5 -3
  215. package/dist/utils/events.js.map +3 -3
  216. package/dist/utils/sdk-client.d.ts.map +1 -1
  217. package/dist/utils/sdk-client.js +17 -4
  218. package/dist/utils/sdk-client.js.map +4 -4
  219. package/dist/utils/waifu-metering.d.ts +108 -0
  220. package/dist/utils/waifu-metering.d.ts.map +1 -0
  221. package/dist/utils/waifu-metering.js +166 -0
  222. package/dist/utils/waifu-metering.js.map +10 -0
  223. package/package.json +51 -22
  224. package/src/cloud/auth-service-types.ts +24 -0
  225. package/src/cloud/base-url.ts +6 -62
  226. package/src/cloud/clack-observer.ts +189 -0
  227. package/src/cloud/duffel-client.ts +847 -0
  228. package/src/cloud/index.ts +10 -0
  229. package/src/cloud/lifeops-schedule-sync-client.ts +245 -0
  230. package/src/cloud/lifeops-schedule-sync-contracts.ts +124 -0
  231. package/src/cloud/managed-payment-clients.ts +374 -0
  232. package/src/cloud/null-observer.ts +45 -0
  233. package/src/cloud/setup-observer.ts +125 -0
  234. package/src/cloud/validate-url.ts +7 -1
  235. package/src/cloud/x402-payment-handler.ts +215 -0
  236. package/src/cloud-setup.ts +531 -0
  237. package/src/cloud-voice-catalog.test.ts +254 -0
  238. package/src/cloud-voice-catalog.ts +246 -0
  239. package/src/index.browser.ts +29 -0
  240. package/src/index.node.ts +31 -1
  241. package/src/index.ts +76 -4
  242. package/src/lib/cloud-connection.ts +2 -4
  243. package/src/lib/cloud-secrets.ts +10 -54
  244. package/src/lib/config-like.ts +1 -1
  245. package/src/lib/credential-type-map.ts +2 -2
  246. package/src/lib/http.ts +0 -17
  247. package/src/lib/server-cloud-tts.ts +33 -341
  248. package/src/lib/tts-debug.ts +5 -34
  249. package/src/models/embeddings.ts +140 -76
  250. package/src/models/image.ts +29 -14
  251. package/src/models/research.ts +11 -1
  252. package/src/models/speech.ts +269 -23
  253. package/src/models/text.ts +704 -110
  254. package/src/models/tokenization.ts +2 -2
  255. package/src/models/transcription.ts +7 -3
  256. package/src/plugin.ts +38 -0
  257. package/src/routes/cloud-billing-routes.ts +4 -14
  258. package/src/routes/cloud-coding-container-routes.ts +198 -0
  259. package/src/routes/cloud-compat-routes.ts +4 -14
  260. package/src/routes/cloud-features-routes.ts +1 -1
  261. package/src/routes/cloud-relay-routes.ts +47 -1
  262. package/src/routes/cloud-routes-autonomous.ts +7 -10
  263. package/src/routes/cloud-routes.ts +68 -7
  264. package/src/routes/cloud-status-routes-autonomous.ts +6 -2
  265. package/src/routes/home-remote-runner-access-url.ts +83 -0
  266. package/src/routes/travel-provider-relay-routes.ts +193 -0
  267. package/src/services/cloud-auth.ts +9 -2
  268. package/src/services/cloud-bootstrap.ts +1 -3
  269. package/src/services/cloud-bridge.ts +1 -1
  270. package/src/services/cloud-container.ts +93 -0
  271. package/src/services/cloud-credential-provider.ts +1 -1
  272. package/src/services/cloud-model-registry.ts +1 -1
  273. package/src/types/cloud.ts +22 -0
  274. package/src/types/index.ts +19 -0
  275. package/src/utils/cloud-sdk/client.ts +42 -3
  276. package/src/utils/cloud-sdk/public-routes.ts +168 -0
  277. package/src/utils/cloud-sdk/types.ts +0 -2
  278. package/src/utils/config.ts +20 -1
  279. package/src/utils/events.ts +30 -2
  280. package/src/utils/sdk-client.ts +5 -1
  281. package/src/utils/waifu-metering.ts +302 -0
  282. package/dist/onboarding.d.ts +0 -35
  283. package/dist/onboarding.d.ts.map +0 -1
  284. package/dist/onboarding.js.map +0 -14
  285. package/src/onboarding.ts +0 -396
@@ -1,10 +1,54 @@
1
1
  import type { IAgentRuntime, TextEmbeddingParams } from "@elizaos/core";
2
- import { logger, ModelType, VECTOR_DIMS } from "@elizaos/core";
2
+ import {
3
+ logger,
4
+ ModelType,
5
+ timeInferenceSpan,
6
+ VECTOR_DIMS,
7
+ } from "@elizaos/core";
3
8
  import { getSetting } from "../utils/config";
4
9
  import { emitModelUsageEvent } from "../utils/events";
5
10
  import { createCloudApiClient } from "../utils/sdk-client";
6
11
 
7
12
  const MAX_BATCH_SIZE = 100;
13
+
14
+ // ── Bounded retry/backoff for the /embeddings round-trip ──────────────────
15
+ // Embeddings are off the turn's critical path (queueEmbeddingGeneration is
16
+ // fire-and-forget), so a stall here delays the embedding QUEUE, not a reply.
17
+ // The old behaviour — one blind 30s (or full retry-after) sleep then a single
18
+ // retry — could park the queue for 30s+ on a transient 429. Replaced with
19
+ // bounded exponential backoff + jitter, a CAP on any single wait (so a large
20
+ // server retry-after can't stall the queue indefinitely), and a per-request
21
+ // client-side timeout (the endpoint had none, so a hung gateway hung the
22
+ // queue forever).
23
+ //
24
+ // Handler retries are deliberately SMALL: the EmbeddingGenerationService
25
+ // BatchQueue already wraps generateEmbedding in its own multi-attempt backoff,
26
+ // so this layer absorbs only a single transient burst (one quick retry) and
27
+ // defers sustained pressure to the queue — otherwise the two backoffs compound.
28
+ const EMBED_MAX_ATTEMPTS = 2;
29
+ const EMBED_BACKOFF_BASE_MS = 1_000;
30
+ const EMBED_BACKOFF_CAP_MS = 8_000;
31
+ const EMBED_REQUEST_TIMEOUT_MS = 60_000;
32
+
33
+ /**
34
+ * Backoff before the next embedding attempt. Exponential (base·2^attempt) as a
35
+ * floor, honoring the server's `retry-after` when present, but never longer
36
+ * than {@link EMBED_BACKOFF_CAP_MS}; ±25% jitter spreads retries from a burst.
37
+ */
38
+ function embeddingBackoffMs(attempt: number, retryAfterSec?: number): number {
39
+ const exp = EMBED_BACKOFF_BASE_MS * 2 ** attempt;
40
+ const serverHint =
41
+ typeof retryAfterSec === "number" && retryAfterSec > 0
42
+ ? retryAfterSec * 1000
43
+ : 0;
44
+ const base = Math.min(EMBED_BACKOFF_CAP_MS, Math.max(exp, serverHint));
45
+ return Math.round(base * (1 + Math.random() * 0.25));
46
+ }
47
+
48
+ function sleep(ms: number): Promise<void> {
49
+ return new Promise((resolve) => setTimeout(resolve, ms));
50
+ }
51
+
8
52
  function extractRateLimitInfo(response: Response): {
9
53
  remainingRequests?: number;
10
54
  remainingTokens?: number;
@@ -48,9 +92,16 @@ function getEmbeddingConfig(runtime: IAgentRuntime) {
48
92
  return { embeddingModelName, embeddingDimension };
49
93
  }
50
94
 
51
- function createErrorVector(dimension: number, marker: number): number[] {
95
+ /**
96
+ * The init probe vector. `runtime.ensureEmbeddingDimension()` calls the handler
97
+ * with `null` purely to learn the vector length; it only inspects `.length`, so
98
+ * a deterministic non-zero[0] marker vector is the correct, legitimate response.
99
+ * This is the ONLY place a synthetic vector is returned — every real failure
100
+ * throws so it can never be persisted as a corrupt embedding (Commandment 8).
101
+ */
102
+ function createInitProbeVector(dimension: number): number[] {
52
103
  const vector = Array(dimension).fill(0);
53
- vector[0] = marker;
104
+ vector[0] = 0.1;
54
105
  return vector;
55
106
  }
56
107
 
@@ -66,7 +117,7 @@ export async function handleTextEmbedding(
66
117
 
67
118
  if (params === null) {
68
119
  logger.debug("Creating test embedding for initialization");
69
- return createErrorVector(embeddingDimension, 0.1);
120
+ return createInitProbeVector(embeddingDimension);
70
121
  }
71
122
 
72
123
  let text: string;
@@ -75,13 +126,14 @@ export async function handleTextEmbedding(
75
126
  } else if (typeof params === "object" && params.text) {
76
127
  text = params.text;
77
128
  } else {
78
- logger.warn("Invalid input format for embedding");
79
- return createErrorVector(embeddingDimension, 0.2);
129
+ // A malformed request is a programming error, not a recoverable runtime
130
+ // state. Throw instead of returning a marker vector that would silently
131
+ // corrupt the embedding store (Commandment 8).
132
+ throw new Error("Invalid input format for embedding: expected string or { text: string }");
80
133
  }
81
134
 
82
135
  if (!text.trim()) {
83
- logger.warn("Empty text for embedding");
84
- return createErrorVector(embeddingDimension, 0.3);
136
+ throw new Error("Cannot generate embedding for empty text");
85
137
  }
86
138
 
87
139
  const results = await handleBatchTextEmbedding(runtime, [text]);
@@ -103,26 +155,22 @@ export async function handleBatchTextEmbedding(
103
155
  const client = createCloudApiClient(runtime, true);
104
156
 
105
157
  if (!texts || texts.length === 0) {
106
- logger.warn("[BatchEmbeddings] Empty texts array");
107
158
  return [];
108
159
  }
109
160
 
161
+ // Every text must be non-empty: an empty input cannot produce a meaningful
162
+ // vector, and a marker/zero vector would silently corrupt the store. Surface
163
+ // the bad input to the caller (Commandment 8) instead of papering over it.
110
164
  const validTexts: { text: string; originalIndex: number }[] = [];
111
- const results: number[][] = new Array(texts.length);
112
-
113
165
  for (let i = 0; i < texts.length; i++) {
114
166
  const text = texts[i]?.trim();
115
- if (text) {
116
- validTexts.push({ text, originalIndex: i });
117
- } else {
118
- results[i] = createErrorVector(embeddingDimension, 0.3);
167
+ if (!text) {
168
+ throw new Error(`Cannot generate embedding for empty text at index ${i}`);
119
169
  }
170
+ validTexts.push({ text, originalIndex: i });
120
171
  }
121
172
 
122
- if (validTexts.length === 0) {
123
- logger.warn("[BatchEmbeddings] All texts were empty");
124
- return results;
125
- }
173
+ const results: number[][] = new Array(texts.length);
126
174
 
127
175
  for (let batchStart = 0; batchStart < validTexts.length; batchStart += MAX_BATCH_SIZE) {
128
176
  const batchEnd = Math.min(batchStart + MAX_BATCH_SIZE, validTexts.length);
@@ -134,74 +182,88 @@ export async function handleBatchTextEmbedding(
134
182
  );
135
183
 
136
184
  try {
137
- const response = await client.requestRaw("POST", "/embeddings", {
138
- json: {
139
- model: embeddingModelName,
140
- input: batchTexts,
141
- },
142
- });
143
-
144
- const rateLimitInfo = extractRateLimitInfo(response);
145
-
146
- if (rateLimitInfo.remainingRequests !== undefined && rateLimitInfo.remainingRequests < 50) {
147
- logger.warn(
148
- `[BatchEmbeddings] Rate limit: ${rateLimitInfo.remainingRequests}/${rateLimitInfo.limitRequests} requests remaining`
185
+ // Records a `cloud.embedding` span on the active per-turn timer when an
186
+ // embedding happens to be on a turn's critical path (most are queued /
187
+ // detached, so this is a no-op there — which is exactly what proves they
188
+ // don't add to turn latency). Retries transient throttling/5xx with
189
+ // bounded exponential backoff (see EMBED_* constants) instead of a single
190
+ // 30s blind sleep.
191
+ let response: Response | null = null;
192
+ for (let attempt = 0; attempt < EMBED_MAX_ATTEMPTS; attempt++) {
193
+ const resp = await timeInferenceSpan(
194
+ "cloud.embedding",
195
+ () =>
196
+ client.requestRaw("POST", "/embeddings", {
197
+ json: {
198
+ model: embeddingModelName,
199
+ input: batchTexts,
200
+ },
201
+ timeoutMs: EMBED_REQUEST_TIMEOUT_MS,
202
+ }),
203
+ { batch: batchTexts.length, attempt }
149
204
  );
150
- }
151
205
 
152
- if (response.status === 429) {
153
- const retryAfter = rateLimitInfo.retryAfter || 30;
154
- logger.warn(`[BatchEmbeddings] Rate limited, waiting ${retryAfter}s...`);
155
- await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
156
-
157
- const retryResponse = await client.requestRaw("POST", "/embeddings", {
158
- json: {
159
- model: embeddingModelName,
160
- input: batchTexts,
161
- },
162
- });
163
-
164
- if (!retryResponse.ok) {
165
- logger.error(`[BatchEmbeddings] Retry failed: ${retryResponse.status}`);
166
- for (const item of batch) {
167
- results[item.originalIndex] = createErrorVector(embeddingDimension, 0.4);
168
- }
169
- continue;
206
+ const rateLimitInfo = extractRateLimitInfo(resp);
207
+ if (
208
+ rateLimitInfo.remainingRequests !== undefined &&
209
+ rateLimitInfo.remainingRequests < 50
210
+ ) {
211
+ logger.warn(
212
+ `[BatchEmbeddings] Rate limit: ${rateLimitInfo.remainingRequests}/${rateLimitInfo.limitRequests} requests remaining`
213
+ );
170
214
  }
171
215
 
172
- const retryData = (await retryResponse.json()) as {
173
- data: Array<{ embedding: number[]; index: number }>;
174
- };
175
-
176
- if (retryData?.data) {
177
- for (const item of retryData.data) {
178
- const originalIndex = batch[item.index].originalIndex;
179
- results[originalIndex] = item.embedding;
180
- }
181
- logger.info(`[BatchEmbeddings] Retry successful for ${batch.length} embeddings`);
216
+ const transient =
217
+ resp.status === 429 ||
218
+ resp.status === 502 ||
219
+ resp.status === 503 ||
220
+ resp.status === 504;
221
+ if (transient && attempt < EMBED_MAX_ATTEMPTS - 1) {
222
+ const delay = embeddingBackoffMs(attempt, rateLimitInfo.retryAfter);
223
+ logger.warn(
224
+ `[BatchEmbeddings] ${resp.status} (attempt ${attempt + 1}/${EMBED_MAX_ATTEMPTS}) — backing off ${delay}ms`
225
+ );
226
+ // Drain the body so the underlying connection can be reused.
227
+ await resp.text().catch(() => undefined);
228
+ await sleep(delay);
229
+ continue;
182
230
  }
183
- continue;
231
+ response = resp;
232
+ break;
233
+ }
234
+
235
+ // Type guard: the loop assigns `response` on its final iteration, so this
236
+ // is unreachable in practice.
237
+ if (!response) {
238
+ throw new Error("[BatchEmbeddings] No response after retry loop");
184
239
  }
185
240
 
186
241
  if (!response.ok) {
187
- logger.error(`[BatchEmbeddings] API error: ${response.status} - ${response.statusText}`);
188
- for (const item of batch) {
189
- results[item.originalIndex] = createErrorVector(embeddingDimension, 0.4);
242
+ // Auth errors (401/403) are non-recoverable with the current key.
243
+ // Every other non-OK status is just as fatal for this batch — neither
244
+ // can produce real vectors. Throw in both cases so the router falls
245
+ // through to the next provider (e.g. local inference) instead of
246
+ // silently persisting marker/zero vectors that corrupt the embedding
247
+ // store. Commandment 8: don't hide broken pipelines behind fallbacks.
248
+ if (response.status === 401 || response.status === 403) {
249
+ throw new Error(
250
+ `[BatchEmbeddings] Authentication failed (${response.status}). ` +
251
+ `Check ELIZAOS_CLOUD_API_KEY or ELIZAOS_CLOUD_EMBEDDING_API_KEY — ` +
252
+ `the current key is not authorized for the embedding endpoint.`
253
+ );
190
254
  }
191
- continue;
255
+ throw new Error(
256
+ `[BatchEmbeddings] API error: ${response.status} ${response.statusText}`
257
+ );
192
258
  }
193
259
 
194
260
  const data = (await response.json()) as {
195
- data: Array<{ embedding: number[]; index: number }>;
261
+ data?: Array<{ embedding: number[]; index: number }>;
196
262
  usage?: { prompt_tokens: number; total_tokens: number };
197
263
  };
198
264
 
199
265
  if (!data?.data || !Array.isArray(data.data)) {
200
- logger.error("[BatchEmbeddings] API returned invalid structure");
201
- for (const item of batch) {
202
- results[item.originalIndex] = createErrorVector(embeddingDimension, 0.5);
203
- }
204
- continue;
266
+ throw new Error("[BatchEmbeddings] API returned invalid response structure");
205
267
  }
206
268
 
207
269
  for (const item of data.data) {
@@ -219,14 +281,16 @@ export async function handleBatchTextEmbedding(
219
281
  }
220
282
 
221
283
  logger.debug(
222
- `[BatchEmbeddings] Got ${batch.length} embeddings (${embeddingDimension}d), remaining: ${rateLimitInfo.remainingRequests ?? "unknown"}`
284
+ `[BatchEmbeddings] Got ${batch.length} embeddings (${embeddingDimension}d)`
223
285
  );
224
286
  } catch (error) {
287
+ // Any failure in this batch (HTTP error, transport error, malformed body)
288
+ // means we have no real vectors for it. Log context and re-throw so the
289
+ // router can fall through to another provider; never persist marker/zero
290
+ // vectors that would corrupt the embedding store (Commandment 8).
225
291
  const message = error instanceof Error ? error.message : String(error);
226
- logger.error(`[BatchEmbeddings] Error: ${message}`);
227
- for (const item of batch) {
228
- results[item.originalIndex] = createErrorVector(embeddingDimension, 0.6);
229
- }
292
+ logger.error(`[BatchEmbeddings] Batch failed: ${message}`);
293
+ throw error instanceof Error ? error : new Error(message);
230
294
  }
231
295
  }
232
296
 
@@ -1,6 +1,11 @@
1
1
  import type { IAgentRuntime, ImageDescriptionParams, ImageGenerationParams } from "@elizaos/core";
2
2
  import { logger, ModelType } from "@elizaos/core";
3
- import { getImageDescriptionModel, getImageGenerationModel, getSetting } from "../utils/config";
3
+ import {
4
+ getImageDescriptionModel,
5
+ getImageGenerationModel,
6
+ getSetting,
7
+ resolveCloudTimeoutMs,
8
+ } from "../utils/config";
4
9
  import { emitModelUsageEvent } from "../utils/events";
5
10
  import { parseImageDescriptionResponse } from "../utils/helpers";
6
11
  import { createElizaCloudClient } from "../utils/sdk-client";
@@ -32,7 +37,7 @@ export async function handleImageGeneration(
32
37
 
33
38
  const typedData = await createElizaCloudClient(runtime).generateImage(requestBody);
34
39
 
35
- const result = typedData.images.map((img) => ({
40
+ const result = typedData.images.map((img: { url?: string; image?: string }) => ({
36
41
  url: img.url ?? img.image ?? "",
37
42
  }));
38
43
  return result;
@@ -55,11 +60,10 @@ export async function handleImageDescription(
55
60
  // every other caller (agent-orchestrator's task validator, vision, lifeops,
56
61
  // farcaster, telegram) free to spend the rate-limit budget.
57
62
  const disableSetting = getSetting(runtime, "DISABLE_IMAGE_DESCRIPTION", "");
58
- const disabled =
59
- disableSetting === "true" ||
60
- disableSetting === "1" ||
61
- process.env.DISABLE_IMAGE_DESCRIPTION === "true" ||
62
- process.env.DISABLE_IMAGE_DESCRIPTION === "1";
63
+ const disabled = [disableSetting, process.env.DISABLE_IMAGE_DESCRIPTION].some((value) => {
64
+ const normalized = value?.trim().toLowerCase();
65
+ return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
66
+ });
63
67
  if (disabled) {
64
68
  logger.debug("[ELIZAOS_CLOUD] IMAGE_DESCRIPTION skipped — DISABLE_IMAGE_DESCRIPTION is set");
65
69
  return {
@@ -115,22 +119,27 @@ export async function handleImageDescription(
115
119
  let response: Response | null = null;
116
120
  let attemptedRetry = false;
117
121
  for (let attempt = 0; attempt < 2; attempt++) {
118
- response = await client.routes.postApiV1ChatCompletionsRaw({
122
+ const attemptResponse = await client.routes.postApiV1ChatCompletionsRaw({
119
123
  json: requestBody,
124
+ timeoutMs: resolveCloudTimeoutMs("ELIZAOS_CLOUD_IMAGE_TIMEOUT_MS", 120_000),
120
125
  });
121
- if (response.status !== 429 || attemptedRetry) break;
126
+ if (!attemptResponse) {
127
+ continue;
128
+ }
129
+ response = attemptResponse;
130
+ if (attemptResponse.status !== 429 || attemptedRetry) break;
122
131
 
123
132
  // `Number(null) === 0`, so guard against a missing header before
124
133
  // calling `Number(...)` — otherwise the header path always wins with a
125
134
  // bogus `0` and the body fallback becomes unreachable.
126
- const headerValue = response.headers.get("retry-after");
135
+ const headerValue = attemptResponse.headers.get("retry-after");
127
136
  const headerRetryAfter =
128
137
  headerValue !== null && Number.isFinite(Number(headerValue))
129
138
  ? Number(headerValue)
130
139
  : undefined;
131
140
  let bodyRetryAfter: number | undefined;
132
141
  try {
133
- const peek = (await response.clone().json()) as {
142
+ const peek = (await attemptResponse.clone().json()) as {
134
143
  retryAfter?: unknown;
135
144
  };
136
145
  bodyRetryAfter =
@@ -157,8 +166,14 @@ export async function handleImageDescription(
157
166
  break;
158
167
  }
159
168
 
160
- if (!response?.ok) {
161
- const status = response?.status ?? 0;
169
+ if (!response) {
170
+ throw new Error("ElizaOS Cloud API did not return a response");
171
+ }
172
+
173
+ const finalResponse = response;
174
+
175
+ if (!finalResponse.ok) {
176
+ const status = finalResponse.status;
162
177
  if (status === 402) {
163
178
  throw new Error(
164
179
  "Eliza Cloud credits exhausted — top up at https://www.elizacloud.ai/dashboard/settings?tab=billing"
@@ -184,7 +199,7 @@ export async function handleImageDescription(
184
199
  };
185
200
  };
186
201
 
187
- const typedResult = (await response.json()) as OpenAIResponseType;
202
+ const typedResult = (await finalResponse.json()) as OpenAIResponseType;
188
203
  const content = typedResult.choices?.[0]?.message?.content;
189
204
 
190
205
  if (typedResult.usage) {
@@ -8,10 +8,16 @@ import type {
8
8
  ResearchResult,
9
9
  } from "@elizaos/core";
10
10
  import { logger, ModelType } from "@elizaos/core";
11
- import { getResearchModel } from "../utils/config";
11
+ import { getResearchModel, resolveCloudTimeoutMs } from "../utils/config";
12
12
  import { emitModelUsageEvent } from "../utils/events";
13
13
  import { createCloudApiClient } from "../utils/sdk-client";
14
14
 
15
+ // Deep research is a long-running, TURN-BLOCKING call; without a timeout a
16
+ // stalled gateway hangs the turn forever (cloud-sdk applies no default). The
17
+ // default is deliberately generous (10 min) so a legitimately slow run isn't
18
+ // aborted; `ELIZAOS_CLOUD_RESEARCH_TIMEOUT_MS=0` opts out.
19
+ const DEFAULT_RESEARCH_TIMEOUT_MS = 600_000;
20
+
15
21
  interface ResponsesAPIOutput {
16
22
  id: string;
17
23
  status: string;
@@ -221,6 +227,10 @@ export async function handleResearch(
221
227
 
222
228
  const response = await createCloudApiClient(runtime).requestRaw("POST", "/responses", {
223
229
  json: requestBody,
230
+ timeoutMs: resolveCloudTimeoutMs(
231
+ "ELIZAOS_CLOUD_RESEARCH_TIMEOUT_MS",
232
+ DEFAULT_RESEARCH_TIMEOUT_MS
233
+ ),
224
234
  });
225
235
 
226
236
  if (!response.ok) {