@posthog/ai 8.13.5 → 8.13.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.
package/dist/index.d.ts CHANGED
@@ -115,6 +115,11 @@ interface ClientOptions {
115
115
  declare const wrapVercelLanguageModel: <T extends LanguageModel>(model: T, phClient: PostHog, options: ClientOptions) => T;
116
116
  //#endregion
117
117
  //#region src/prompts.d.ts
118
+ /** Carries the server's own cooldown so a rate-limited client waits as long as it was told to. */
119
+ declare class PromptFetchError extends Error {
120
+ readonly retryAfterSeconds?: number;
121
+ constructor(message: string, retryAfterSeconds?: number);
122
+ }
118
123
  interface PromptsWithPostHogOptions {
119
124
  posthog: PostHog;
120
125
  defaultCacheTtlSeconds?: number;
@@ -170,6 +175,7 @@ declare class Prompts {
170
175
  private host;
171
176
  private defaultCacheTtlSeconds;
172
177
  private cache;
178
+ private inFlight;
173
179
  constructor(options: PromptsOptions);
174
180
  private getPromptCache;
175
181
  private getOrCreatePromptCache;
@@ -212,6 +218,7 @@ declare class Prompts {
212
218
  * Does NOT handle the string `fallback` option — callers handle that.
213
219
  */
214
220
  private getInternal;
221
+ private fetchPromptOnce;
215
222
  private readCacheEntry;
216
223
  /**
217
224
  * Compile a prompt template with variable substitution
@@ -331,5 +338,5 @@ interface CaptureAiGenerationOptions {
331
338
  */
332
339
  declare const captureAiGeneration: (client: PostHog, options: CaptureAiGenerationOptions) => Promise<void>;
333
340
  //#endregion
334
- export { AIEvent, type CaptureAiGenerationOptions, type PromptCodeFallbackResult, type PromptRemoteResult, type PromptResult, Prompts, captureAiGeneration, wrapVercelLanguageModel as withTracing };
341
+ export { AIEvent, type CaptureAiGenerationOptions, type PromptCodeFallbackResult, PromptFetchError, type PromptRemoteResult, type PromptResult, Prompts, captureAiGeneration, wrapVercelLanguageModel as withTracing };
335
342
  //# sourceMappingURL=index.d.ts.map
package/dist/index.mjs CHANGED
@@ -395,7 +395,7 @@ function sanitizeValues(obj) {
395
395
  }
396
396
  //#endregion
397
397
  //#region package.json
398
- var version = "8.13.5";
398
+ var version = "8.13.7";
399
399
  //#endregion
400
400
  //#region src/serializeError.ts
401
401
  const DEFAULT_MAX_DEPTH = 3;
@@ -1090,6 +1090,7 @@ const DEFAULT_PROMPTS_HOST = "https://us.posthog.com";
1090
1090
  const MAX_PROMPT_LIST_PAGES = 100;
1091
1091
  const DEFAULT_REFETCH_COOLDOWN_SECONDS = 60;
1092
1092
  const MAX_REFETCH_COOLDOWN_SECONDS = 3600;
1093
+ const FETCH_TIMEOUT_MS = 1e4;
1093
1094
  function normalizeApiKey(value) {
1094
1095
  return typeof value === "string" ? value.trim() : "";
1095
1096
  }
@@ -1125,6 +1126,30 @@ function isPromptApiResponse(data) {
1125
1126
  const record = data;
1126
1127
  return typeof record.prompt === "string" && typeof record.name === "string" && typeof record.version === "number" && (record.label === void 0 || typeof record.label === "string");
1127
1128
  }
1129
+ /**
1130
+ * GET with the client's timeout. The timer is cleared only after the body is
1131
+ * read, so a response whose headers arrive but whose body stalls still times out.
1132
+ */
1133
+ async function fetchJsonWithTimeout(url, personalApiKey, timeoutReference) {
1134
+ const controller = new AbortController();
1135
+ const timer = setTimeout(() => controller.abort(new PromptFetchError(`[PostHog Prompts] Timed out fetching ${timeoutReference} after ${FETCH_TIMEOUT_MS}ms`)), FETCH_TIMEOUT_MS);
1136
+ try {
1137
+ const response = await fetch(url, {
1138
+ method: "GET",
1139
+ headers: { Authorization: `Bearer ${personalApiKey}` },
1140
+ signal: controller.signal
1141
+ });
1142
+ return {
1143
+ response,
1144
+ data: response.ok ? await response.json() : void 0
1145
+ };
1146
+ } catch (error) {
1147
+ if (controller.signal.aborted) throw controller.signal.reason;
1148
+ throw error;
1149
+ } finally {
1150
+ clearTimeout(timer);
1151
+ }
1152
+ }
1128
1153
  function isSameOrigin(url, host) {
1129
1154
  try {
1130
1155
  return new URL(url).origin === new URL(host).origin;
@@ -1204,6 +1229,7 @@ function isPromptsWithPostHog(options) {
1204
1229
  var Prompts = class {
1205
1230
  constructor(options) {
1206
1231
  this.cache = /* @__PURE__ */ new Map();
1232
+ this.inFlight = /* @__PURE__ */ new Map();
1207
1233
  this.defaultCacheTtlSeconds = options.defaultCacheTtlSeconds ?? DEFAULT_CACHE_TTL_SECONDS;
1208
1234
  if (isPromptsWithPostHog(options)) {
1209
1235
  this.personalApiKey = options.posthog.options.personalApiKey ?? "";
@@ -1348,7 +1374,7 @@ var Prompts = class {
1348
1374
  };
1349
1375
  }
1350
1376
  try {
1351
- const fetched = await this.fetchPromptFromApi(name, version, label);
1377
+ const fetched = await this.fetchPromptOnce(name, version, label);
1352
1378
  if (label !== void 0 && fetched.label !== label) console.warn(`[PostHog Prompts] Requested label "${label}" for prompt "${name}" but the server resolved ${fetched.label === void 0 ? "no label" : `"${fetched.label}"`}. It may not support prompt labels yet and returned the latest version instead.`);
1353
1379
  this.getOrCreatePromptCache(name).set(cacheEntryKey, {
1354
1380
  ...fetched,
@@ -1372,6 +1398,20 @@ var Prompts = class {
1372
1398
  throw error;
1373
1399
  }
1374
1400
  }
1401
+ fetchPromptOnce(name, version, label) {
1402
+ const inFlightKey = JSON.stringify([
1403
+ name,
1404
+ version ?? null,
1405
+ label ?? null
1406
+ ]);
1407
+ const pending = this.inFlight.get(inFlightKey);
1408
+ if (pending !== void 0) return pending;
1409
+ const request = this.fetchPromptFromApi(name, version, label).finally(() => {
1410
+ this.inFlight.delete(inFlightKey);
1411
+ });
1412
+ this.inFlight.set(inFlightKey, request);
1413
+ return request;
1414
+ }
1375
1415
  readCacheEntry(cached) {
1376
1416
  const { fetchedAt: _fetchedAt, retryNotBefore: _retryNotBefore, ...cachedResult } = cached;
1377
1417
  return {
@@ -1438,15 +1478,11 @@ var Prompts = class {
1438
1478
  let pages = 0;
1439
1479
  while (url !== void 0) {
1440
1480
  if (pages >= MAX_PROMPT_LIST_PAGES) throw new Error(`[PostHog Prompts] ${reference} spans more than ${MAX_PROMPT_LIST_PAGES} pages. Refusing to return an incomplete result.`);
1441
- const response = await fetch(url, {
1442
- method: "GET",
1443
- headers: { Authorization: `Bearer ${this.personalApiKey}` }
1444
- });
1481
+ const { response, data } = await fetchJsonWithTimeout(url, this.personalApiKey, reference);
1445
1482
  if (!response.ok) {
1446
1483
  if (response.status === 403) throw new Error(`[PostHog Prompts] Access denied for ${reference}. Check that your personalApiKey has the correct permissions and the LLM prompts feature is enabled.`);
1447
1484
  throw new PromptFetchError(`[PostHog Prompts] Failed to fetch ${reference}: HTTP ${response.status}`, response.status === 429 ? parseRetryAfterSeconds(response.headers?.get("Retry-After")) : void 0);
1448
1485
  }
1449
- const data = await response.json();
1450
1486
  if (typeof data !== "object" || data === null || !Array.isArray(data.results)) throw new Error(`[PostHog Prompts] Invalid response format for ${reference}`);
1451
1487
  rows.push(...data.results);
1452
1488
  const nextUrl = data.next;
@@ -1463,17 +1499,12 @@ var Prompts = class {
1463
1499
  const versionQuery = version === void 0 ? "" : `&version=${encodeURIComponent(String(version))}`;
1464
1500
  const labelQuery = label === void 0 ? "" : `&label=${encodeURIComponent(label)}`;
1465
1501
  const promptReference = this.getPromptReference(name, version, label);
1466
- const url = `${this.host}/api/environments/@current/llm_prompts/name/${encodedPromptName}/?token=${encodedProjectApiKey}${versionQuery}${labelQuery}`;
1467
- const response = await fetch(url, {
1468
- method: "GET",
1469
- headers: { Authorization: `Bearer ${this.personalApiKey}` }
1470
- });
1502
+ const { response, data } = await fetchJsonWithTimeout(`${this.host}/api/environments/@current/llm_prompts/name/${encodedPromptName}/?token=${encodedProjectApiKey}${versionQuery}${labelQuery}`, this.personalApiKey, `prompt ${promptReference}`);
1471
1503
  if (!response.ok) {
1472
1504
  if (response.status === 404) throw new Error(`[PostHog Prompts] Prompt ${promptReference} not found`);
1473
1505
  if (response.status === 403) throw new Error(`[PostHog Prompts] Access denied for prompt ${promptReference}. Check that your personalApiKey has the correct permissions and the LLM prompts feature is enabled.`);
1474
1506
  throw new PromptFetchError(`[PostHog Prompts] Failed to fetch prompt ${promptReference}: HTTP ${response.status}`, response.status === 429 ? parseRetryAfterSeconds(response.headers?.get("Retry-After")) : void 0);
1475
1507
  }
1476
- const data = await response.json();
1477
1508
  if (!isPromptApiResponse(data)) throw new Error(`[PostHog Prompts] Invalid response format for prompt ${promptReference}`);
1478
1509
  return {
1479
1510
  prompt: data.prompt,
@@ -1485,6 +1516,6 @@ var Prompts = class {
1485
1516
  }
1486
1517
  };
1487
1518
  //#endregion
1488
- export { AIEvent, Prompts, captureAiGeneration, wrapVercelLanguageModel as withTracing };
1519
+ export { AIEvent, PromptFetchError, Prompts, captureAiGeneration, wrapVercelLanguageModel as withTracing };
1489
1520
 
1490
1521
  //# sourceMappingURL=index.mjs.map