@elmohq/sdk 0.1.0 → 0.1.2

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 (45) hide show
  1. package/README.md +18 -17
  2. package/dist/calls/index.d.mts +2 -2
  3. package/dist/calls/index.mjs +1 -1
  4. package/dist/index.d.mts +1 -1
  5. package/dist/index.mjs +52 -50
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/{request-options-DWYI_Ldj.d.mts → request-options-DHd9ZVo4.d.mts} +71 -72
  8. package/dist/request-options-DHd9ZVo4.d.mts.map +1 -0
  9. package/dist/{tools-SwP0sK8H.mjs → tools-B1lVtYAr.mjs} +10 -9
  10. package/dist/tools-B1lVtYAr.mjs.map +1 -0
  11. package/dist/{tools-7Hs5J2xb.d.mts → tools-DpxrXc33.d.mts} +50 -36
  12. package/dist/tools-DpxrXc33.d.mts.map +1 -0
  13. package/dist/types/index.d.mts +1 -1
  14. package/package.json +2 -1
  15. package/src/calls/brands-list.ts +1 -1
  16. package/src/calls/competitors-list.ts +1 -1
  17. package/src/calls/organizations-list.ts +1 -1
  18. package/src/calls/prompts-list.ts +1 -1
  19. package/src/calls/prompts-runs-list.ts +1 -1
  20. package/src/calls/reports-list.ts +1 -1
  21. package/src/client.ts +7 -20
  22. package/src/internal/core/dispatch.ts +2 -4
  23. package/src/internal/core/errors.ts +5 -5
  24. package/src/internal/{page → core}/page.ts +5 -5
  25. package/src/internal/core/types.ts +6 -8
  26. package/src/internal/feature/logger.ts +2 -2
  27. package/src/internal/feature/retry.ts +53 -50
  28. package/src/internal/feature/timeout.ts +2 -2
  29. package/src/internal/feature/validate.ts +8 -0
  30. package/src/resources/analytics.ts +8 -7
  31. package/src/resources/brands.ts +7 -4
  32. package/src/resources/competitors.ts +4 -3
  33. package/src/resources/identity.ts +2 -1
  34. package/src/resources/opportunities.ts +3 -1
  35. package/src/resources/organizations.ts +6 -5
  36. package/src/resources/prompts.ts +3 -2
  37. package/src/resources/reports.ts +3 -2
  38. package/src/resources/runs.ts +5 -4
  39. package/src/resources/tools.ts +1 -0
  40. package/src/types/analytics.ts +35 -25
  41. package/src/types/runs.ts +7 -5
  42. package/src/types/shared/model-filter.ts +7 -5
  43. package/dist/request-options-DWYI_Ldj.d.mts.map +0 -1
  44. package/dist/tools-7Hs5J2xb.d.mts.map +0 -1
  45. package/dist/tools-SwP0sK8H.mjs.map +0 -1
@@ -1,7 +1,7 @@
1
1
  import { backoffDelay, exceedsMaxRetryAfter, retryAfterDelay, sleep } from '../core/backoff';
2
2
  import { discardBody, isStreamed } from '../core/body';
3
3
  import { calledOffWith, classifyError, ElmoError, failureOf, TransportError } from '../core/errors';
4
- import type { Feature, PreparedRequest, Result, RetryRules } from '../core/types';
4
+ import type { Feature, PreparedRequest, Result, RetryRules, Send } from '../core/types';
5
5
 
6
6
  /** Which failed calls are sent again, how many times, and how long each waits first. */
7
7
  export interface RetryOptions extends RetryRules {
@@ -9,7 +9,7 @@ export interface RetryOptions extends RetryRules {
9
9
  * How long to wait before the next attempt, in milliseconds, in place of the
10
10
  * API's retry headers and the backoff. `undefined` keeps the usual wait.
11
11
  * After a failure that got no reply, `result` holds only the `error`.
12
- * **{@link maxRetryAfter} does not limit what this returns.**
12
+ * {@link maxRetryAfter} does not limit what this returns.
13
13
  */
14
14
  retryDelay?(result: Result, request: PreparedRequest, attempt: number): number | undefined;
15
15
  /**
@@ -17,8 +17,9 @@ export interface RetryOptions extends RetryRules {
17
17
  * `retryOnTimeout`. After a reply, the body is unread: read it from
18
18
  * `response.clone()`. After a failure that got no reply, `result` holds only
19
19
  * the `error`. A call the API may have acted on is never asked about: a
20
- * method outside `methods` is sent again only after 408, 425 or 429, or when
21
- * the request never reached the API.
20
+ * method outside `methods` is sent again only after 408, 425 or 429, after
21
+ * a failure that answers `x-should-retry: true`, or when the request never
22
+ * reached the API.
22
23
  */
23
24
  retryOn?(result: Result, request: PreparedRequest): boolean;
24
25
  }
@@ -93,9 +94,12 @@ function shouldRetry(
93
94
  ): boolean {
94
95
  const { response } = result;
95
96
  if (!response) return false;
96
- if (!repeatable && !REFUSED_STATUSES.includes(response.status)) return false;
97
+ const asked = response.headers.get(RETRY_HEADER);
98
+ const invited = asked === 'true' && response.status >= 400;
99
+ if (!repeatable && !invited && !REFUSED_STATUSES.includes(response.status)) return false;
97
100
  if (config.retryOn) return config.retryOn(result, request);
98
- if (response.headers.get(RETRY_HEADER) === 'false') return false;
101
+ if (asked === 'false') return false;
102
+ if (invited) return true;
99
103
  const { statuses } = config;
100
104
  return statuses ? statuses.includes(response.status) : worthAnotherAttempt(response.status);
101
105
  }
@@ -115,51 +119,50 @@ function shouldRetryError(
115
119
  }
116
120
 
117
121
  export function retryFeature(options: RetryOptions = {}): Feature {
118
- return {
119
- name: 'retry',
120
- async onSend(request, next) {
121
- const config = isStreamed(request.body) ? undefined : resolveRetry(options, request);
122
- if (!config) return next(request);
123
-
124
- const attempts = (config.maxRetries ?? DEFAULT_MAX_RETRIES) + 1;
125
- const repeatable = isRepeatable(config, request);
126
- const started = Date.now();
127
-
128
- for (let attempt = 0; ; attempt++) {
129
- const last = attempt >= attempts - 1;
130
- let result: Result | undefined;
131
- let threw = false;
132
- let wait: number | undefined;
133
-
134
- if (attempt) request.attempt = attempt;
135
- try {
136
- result = await next(request);
137
- if (last || !shouldRetry(config, result, request, repeatable)) return result;
138
-
139
- wait = config.retryDelay?.(result, request, attempt);
140
- if (wait === undefined) {
141
- wait = askedFor(config, result);
142
- if (exceedsMaxRetryAfter(wait, config.maxRetryAfter)) return result;
143
- }
144
- } catch (error) {
145
- if (last || !shouldRetryError(config, error, request, repeatable)) throw error;
146
- threw = true;
147
- result = { error };
148
- wait = config.retryDelay?.(result, request, attempt);
149
- }
150
-
151
- wait ??= backoffDelay(attempt, config);
152
- if (config.budget !== undefined && Date.now() - started + wait >= config.budget) {
153
- if (threw) throw result.error;
154
- return result;
122
+ async function repeated(request: PreparedRequest, next: Send): Promise<Result> {
123
+ const config = isStreamed(request.body) ? undefined : resolveRetry(options, request);
124
+ if (!config) return next(request);
125
+
126
+ const attempts = (config.maxRetries ?? DEFAULT_MAX_RETRIES) + 1;
127
+ const repeatable = isRepeatable(config, request);
128
+ const started = Date.now();
129
+
130
+ for (let attempt = 0; ; attempt++) {
131
+ const last = attempt >= attempts - 1;
132
+ let result: Result | undefined;
133
+ let threw = false;
134
+ let wait: number | undefined;
135
+
136
+ if (attempt) request.attempt = attempt;
137
+ try {
138
+ result = await next(request);
139
+ if (last || !shouldRetry(config, result, request, repeatable)) return result;
140
+
141
+ wait = config.retryDelay?.(result, request, attempt);
142
+ if (wait === undefined) {
143
+ wait = askedFor(config, result);
144
+ if (exceedsMaxRetryAfter(wait, config.maxRetryAfter)) return result;
155
145
  }
146
+ } catch (error) {
147
+ if (last || !shouldRetryError(config, error, request, repeatable)) throw error;
148
+ threw = true;
149
+ result = { error };
150
+ wait = config.retryDelay?.(result, request, attempt);
151
+ }
156
152
 
157
- discardBody(result);
158
- request.log?.retrying(wait, attempt + 1, attempts - 1, result);
159
- await sleep(wait, request.signal).catch((reason: unknown) => {
160
- throw calledOffWith(reason);
161
- });
153
+ wait ??= backoffDelay(attempt, config);
154
+ if (config.budget !== undefined && Date.now() - started + wait >= config.budget) {
155
+ if (threw) throw result.error;
156
+ return result;
162
157
  }
163
- },
164
- };
158
+
159
+ discardBody(result);
160
+ request.log?.retrying(wait, attempt + 1, attempts - 1, result);
161
+ await sleep(wait, request.signal).catch((reason: unknown) => {
162
+ throw calledOffWith(reason);
163
+ });
164
+ }
165
+ }
166
+
167
+ return { name: 'retry', onOpen: repeated, onSend: repeated };
165
168
  }
@@ -131,7 +131,7 @@ export function timeoutFeature(options: TimeoutOptions = {}): Feature {
131
131
  request.signal = caller;
132
132
  });
133
133
  const { response } = result;
134
- if (response?.body && (stream || request.options.unread)) {
134
+ if (response?.body && stream) {
135
135
  return { ...result, response: watchedReply(response, wait, done) };
136
136
  }
137
137
  done();
@@ -141,6 +141,6 @@ export function timeoutFeature(options: TimeoutOptions = {}): Feature {
141
141
  return {
142
142
  name: 'timeout',
143
143
  onOpen: (request, next) => bounded(request, next, true),
144
- onSend: (request, next) => bounded(request, next, false),
144
+ onSend: (request, next) => bounded(request, next, !!request.options.unread),
145
145
  };
146
146
  }
@@ -66,6 +66,14 @@ export function decoded<TIn, TOut>(at: string, value: TIn, read: (value: TIn) =>
66
66
  return result;
67
67
  }
68
68
 
69
+ export type Mutable<T> = T extends (...args: Array<never>) => unknown
70
+ ? T
71
+ : T extends object
72
+ ? {
73
+ -readonly [K in keyof T]: Mutable<T[K]>;
74
+ }
75
+ : T;
76
+
69
77
  export function reviving<T>(revive: (value: T) => void): Validate {
70
78
  return (data) => {
71
79
  if (data != null) revive(data as T);
@@ -4,6 +4,7 @@ import { ElmoResource } from './shared/resource';
4
4
  import { callPromise, RestCallPromise } from '../internal/core/call-promise';
5
5
  import { callable } from '../internal/core/callable';
6
6
  import { mergeParams, placeParams } from '../internal/core/params';
7
+ import type { Mutable } from '../internal/feature/validate';
7
8
  import { decoded, reviving } from '../internal/feature/validate';
8
9
  import type {
9
10
  BrandAnalytics,
@@ -40,7 +41,7 @@ import type { BrandIdPath } from '../types/shared/brand-id-path';
40
41
 
41
42
  export const getBrandAnalyticsFields = { path: ['brandId'] };
42
43
 
43
- function reviveDateRange(value: DateRange): void {
44
+ function reviveDateRange(value: Mutable<DateRange>): void {
44
45
  if (value.start != null) {
45
46
  value.start = decoded('DateRange.start', value.start, (raw) => new Date(raw));
46
47
  }
@@ -49,7 +50,7 @@ function reviveDateRange(value: DateRange): void {
49
50
  }
50
51
  }
51
52
 
52
- function reviveBrandAnalytics(value: BrandAnalytics): void {
53
+ function reviveBrandAnalytics(value: Mutable<BrandAnalytics>): void {
53
54
  if (value.range != null) {
54
55
  reviveDateRange(value.range);
55
56
  }
@@ -65,7 +66,7 @@ export const getBrandAnalyticsDescriptor = /* @__PURE__ */ callable({
65
66
 
66
67
  export const listBrandCitationDomainsFields = { path: ['brandId'] };
67
68
 
68
- function reviveCitationDomainList(value: CitationDomainList): void {
69
+ function reviveCitationDomainList(value: Mutable<CitationDomainList>): void {
69
70
  if (value.range != null) {
70
71
  reviveDateRange(value.range);
71
72
  }
@@ -81,7 +82,7 @@ export const listBrandCitationDomainsDescriptor = /* @__PURE__ */ callable({
81
82
 
82
83
  export const listBrandCitationUrlsFields = { path: ['brandId'] };
83
84
 
84
- function reviveCitationUrlList(value: CitationURLList): void {
85
+ function reviveCitationUrlList(value: Mutable<CitationURLList>): void {
85
86
  if (value.range != null) {
86
87
  reviveDateRange(value.range);
87
88
  }
@@ -97,7 +98,7 @@ export const listBrandCitationURLsDescriptor = /* @__PURE__ */ callable({
97
98
 
98
99
  export const listBrandPromptPerformanceFields = { path: ['brandId'] };
99
100
 
100
- function revivePromptPerformance(value: PromptPerformanceSchema): void {
101
+ function revivePromptPerformance(value: Mutable<PromptPerformanceSchema>): void {
101
102
  if (value.lastRunAt != null) {
102
103
  value.lastRunAt = decoded(
103
104
  'PromptPerformance.lastRunAt',
@@ -114,7 +115,7 @@ function revivePromptPerformance(value: PromptPerformanceSchema): void {
114
115
  }
115
116
  }
116
117
 
117
- function revivePromptPerformanceList(value: PromptPerformanceList): void {
118
+ function revivePromptPerformanceList(value: Mutable<PromptPerformanceList>): void {
118
119
  if (value.range != null) {
119
120
  reviveDateRange(value.range);
120
121
  }
@@ -135,7 +136,7 @@ export const listBrandPromptPerformanceDescriptor = /* @__PURE__ */ callable({
135
136
 
136
137
  export const getBrandQueryFanoutFields = { path: ['brandId'] };
137
138
 
138
- function reviveBrandQueryFanout(value: BrandQueryFanout): void {
139
+ function reviveBrandQueryFanout(value: Mutable<BrandQueryFanout>): void {
139
140
  if (value.range != null) {
140
141
  reviveDateRange(value.range);
141
142
  }
@@ -7,9 +7,10 @@ import { brandNameRule, domainsRule, nameRule } from './shared/rule';
7
7
  import { Tags } from './tags';
8
8
  import { callPromise, RestCallPromise } from '../internal/core/call-promise';
9
9
  import { callable } from '../internal/core/callable';
10
+ import { PagePromise, pages } from '../internal/core/page';
10
11
  import { groupParams, mergeParams, placeParams } from '../internal/core/params';
12
+ import type { Mutable } from '../internal/feature/validate';
11
13
  import { decoded, reviving } from '../internal/feature/validate';
12
- import { PagePromise, pages } from '../internal/page/page';
13
14
  import type {
14
15
  BrandAnalytics,
15
16
  BrandQueryFanout,
@@ -26,6 +27,7 @@ import type {
26
27
  ListBrandPromptPerformanceResponses,
27
28
  ModelVisibility,
28
29
  PromptPerformanceList,
30
+ PromptPerformance as PromptPerformanceSchema,
29
31
  ShareOfVoiceEntry,
30
32
  ShareOfVoicePoint,
31
33
  TagsFilter,
@@ -57,7 +59,7 @@ import type {
57
59
  import type { BrandIdPath } from '../types/shared/brand-id-path';
58
60
  import type { ListBrandTagsErrors, ListBrandTagsResponses, Tag, TagList } from '../types/tags';
59
61
 
60
- function reviveBrand(value: Brand): void {
62
+ function reviveBrand(value: Mutable<Brand>): void {
61
63
  if (value.createdAt != null) {
62
64
  value.createdAt = decoded('Brand.createdAt', value.createdAt, (raw) => new Date(raw));
63
65
  }
@@ -66,7 +68,7 @@ function reviveBrand(value: Brand): void {
66
68
  }
67
69
  }
68
70
 
69
- function reviveBrandsList(value: BrandsList): void {
71
+ function reviveBrandsList(value: Mutable<BrandsList>): void {
70
72
  if (value.data != null) {
71
73
  value.data.forEach((item) => {
72
74
  reviveBrand(item);
@@ -268,8 +270,9 @@ export declare namespace Brands {
268
270
  Opportunities,
269
271
  Opportunity,
270
272
  OpportunityPrompt,
271
- PromptPerformance,
273
+ PromptPerformanceSchema as PromptPerformance,
272
274
  PromptPerformanceList,
275
+ PromptPerformance as PromptPerformanceResource,
273
276
  QueryFanout,
274
277
  ShareOfVoiceEntry,
275
278
  ShareOfVoicePoint,
@@ -4,9 +4,10 @@ import { ElmoResource } from './shared/resource';
4
4
  import { nameRule } from './shared/rule';
5
5
  import { callPromise, RestCallPromise } from '../internal/core/call-promise';
6
6
  import { callable } from '../internal/core/callable';
7
+ import { PagePromise, pages } from '../internal/core/page';
7
8
  import { groupParams, mergeParams, placeParams } from '../internal/core/params';
9
+ import type { Mutable } from '../internal/feature/validate';
8
10
  import { decoded, reviving } from '../internal/feature/validate';
9
- import { PagePromise, pages } from '../internal/page/page';
10
11
  import type {
11
12
  Competitor,
12
13
  CompetitorsList,
@@ -25,7 +26,7 @@ import type {
25
26
  UpdateCompetitorResponses,
26
27
  } from '../types/competitors';
27
28
 
28
- function reviveCompetitor(value: Competitor): void {
29
+ function reviveCompetitor(value: Mutable<Competitor>): void {
29
30
  if (value.createdAt != null) {
30
31
  value.createdAt = decoded('Competitor.createdAt', value.createdAt, (raw) => new Date(raw));
31
32
  }
@@ -34,7 +35,7 @@ function reviveCompetitor(value: Competitor): void {
34
35
  }
35
36
  }
36
37
 
37
- function reviveCompetitorsList(value: CompetitorsList): void {
38
+ function reviveCompetitorsList(value: Mutable<CompetitorsList>): void {
38
39
  if (value.data != null) {
39
40
  value.data.forEach((item) => {
40
41
  reviveCompetitor(item);
@@ -3,10 +3,11 @@ import type { RequestOptions } from './shared/request-options';
3
3
  import { ElmoResource } from './shared/resource';
4
4
  import { callPromise, RestCallPromise } from '../internal/core/call-promise';
5
5
  import { callable } from '../internal/core/callable';
6
+ import type { Mutable } from '../internal/feature/validate';
6
7
  import { decoded, reviving } from '../internal/feature/validate';
7
8
  import type { APIKeyIdentity, GetMeErrors, GetMeResponses } from '../types/identity';
8
9
 
9
- function reviveApiKeyIdentity(value: APIKeyIdentity): void {
10
+ function reviveApiKeyIdentity(value: Mutable<APIKeyIdentity>): void {
10
11
  if (value.createdAt != null) {
11
12
  value.createdAt = decoded('ApiKeyIdentity.createdAt', value.createdAt, (raw) => new Date(raw));
12
13
  }
@@ -4,6 +4,7 @@ import { ElmoResource } from './shared/resource';
4
4
  import { callPromise, RestCallPromise } from '../internal/core/call-promise';
5
5
  import { callable } from '../internal/core/callable';
6
6
  import { mergeParams, placeParams } from '../internal/core/params';
7
+ import type { Mutable } from '../internal/feature/validate';
7
8
  import { decoded, reviving } from '../internal/feature/validate';
8
9
  import type {
9
10
  BrandOpportunities,
@@ -17,7 +18,7 @@ import type { BrandIdPath } from '../types/shared/brand-id-path';
17
18
 
18
19
  export const getBrandOpportunitiesFields = { path: ['brandId'] };
19
20
 
20
- function reviveBrandOpportunities(value: BrandOpportunities): void {
21
+ function reviveBrandOpportunities(value: Mutable<BrandOpportunities>): void {
21
22
  if (value.generatedAt != null) {
22
23
  value.generatedAt = decoded(
23
24
  'BrandOpportunities.generatedAt',
@@ -32,6 +33,7 @@ export const getBrandOpportunitiesDescriptor = /* @__PURE__ */ callable({
32
33
  auth: apiKeyRequirements,
33
34
  method: 'get',
34
35
  returns: 'json',
36
+ timeout: 600000,
35
37
  validators: { response: /* @__PURE__ */ reviving(reviveBrandOpportunities) },
36
38
  } as const);
37
39
 
@@ -3,9 +3,10 @@ import type { RequestOptions } from './shared/request-options';
3
3
  import { ElmoResource } from './shared/resource';
4
4
  import { callPromise, RestCallPromise } from '../internal/core/call-promise';
5
5
  import { callable } from '../internal/core/callable';
6
+ import { PagePromise, pages } from '../internal/core/page';
6
7
  import { groupParams, mergeParams, placeParams } from '../internal/core/params';
8
+ import type { Mutable } from '../internal/feature/validate';
7
9
  import { decoded, reviving } from '../internal/feature/validate';
8
- import { PagePromise, pages } from '../internal/page/page';
9
10
  import type {
10
11
  BillingPlan,
11
12
  GetOrganizationBillingErrors,
@@ -23,13 +24,13 @@ import type {
23
24
 
24
25
  export const getOrganizationBillingFields = { path: ['organizationId'] };
25
26
 
26
- function reviveBillingPlan(value: BillingPlan): void {
27
+ function reviveBillingPlan(value: Mutable<BillingPlan>): void {
27
28
  if (value.periodEnd != null) {
28
29
  value.periodEnd = decoded('BillingPlan.periodEnd', value.periodEnd, (raw) => new Date(raw));
29
30
  }
30
31
  }
31
32
 
32
- function reviveOrganizationBilling(value: OrganizationBilling): void {
33
+ function reviveOrganizationBilling(value: Mutable<OrganizationBilling>): void {
33
34
  if (value.plan != null) {
34
35
  reviveBillingPlan(value.plan);
35
36
  }
@@ -43,13 +44,13 @@ export const getOrganizationBillingDescriptor = /* @__PURE__ */ callable({
43
44
  validators: { response: /* @__PURE__ */ reviving(reviveOrganizationBilling) },
44
45
  } as const);
45
46
 
46
- function reviveOrganization(value: Organization): void {
47
+ function reviveOrganization(value: Mutable<Organization>): void {
47
48
  if (value.createdAt != null) {
48
49
  value.createdAt = decoded('Organization.createdAt', value.createdAt, (raw) => new Date(raw));
49
50
  }
50
51
  }
51
52
 
52
- function reviveOrganizationList(value: OrganizationList): void {
53
+ function reviveOrganizationList(value: Mutable<OrganizationList>): void {
53
54
  if (value.data != null) {
54
55
  value.data.forEach((item) => {
55
56
  reviveOrganization(item);
@@ -6,9 +6,10 @@ import { valueRule } from './shared/rule';
6
6
  import { Snapshot } from './snapshots';
7
7
  import { callPromise, RestCallPromise } from '../internal/core/call-promise';
8
8
  import { callable } from '../internal/core/callable';
9
+ import { PagePromise, pages } from '../internal/core/page';
9
10
  import { groupParams, mergeParams, placeParams } from '../internal/core/params';
11
+ import type { Mutable } from '../internal/feature/validate';
10
12
  import { decoded, reviving } from '../internal/feature/validate';
11
- import { PagePromise, pages } from '../internal/page/page';
12
13
  import type {
13
14
  CreatePromptErrors,
14
15
  CreatePromptParams,
@@ -46,7 +47,7 @@ import type {
46
47
  PromptSnapshot,
47
48
  } from '../types/snapshots';
48
49
 
49
- function revivePrompt(value: Prompt): void {
50
+ function revivePrompt(value: Mutable<Prompt>): void {
50
51
  if (value.createdAt != null) {
51
52
  value.createdAt = decoded('Prompt.createdAt', value.createdAt, (raw) => new Date(raw));
52
53
  }
@@ -4,9 +4,10 @@ import { ElmoResource } from './shared/resource';
4
4
  import { brandNameRule } from './shared/rule';
5
5
  import { callPromise, RestCallPromise } from '../internal/core/call-promise';
6
6
  import { callable } from '../internal/core/callable';
7
+ import { PagePromise, pages } from '../internal/core/page';
7
8
  import { groupParams, mergeParams, placeParams } from '../internal/core/params';
9
+ import type { Mutable } from '../internal/feature/validate';
8
10
  import { decoded } from '../internal/feature/validate';
9
- import { PagePromise, pages } from '../internal/page/page';
10
11
  import type {
11
12
  CreateReportErrors,
12
13
  CreateReportParams,
@@ -24,7 +25,7 @@ import type {
24
25
  ReportSummary,
25
26
  } from '../types/reports';
26
27
 
27
- function reviveReportSummary(value: ReportSummary): void {
28
+ function reviveReportSummary(value: Mutable<ReportSummary>): void {
28
29
  if (value.createdAt != null) {
29
30
  value.createdAt = decoded('ReportSummary.createdAt', value.createdAt, (raw) => new Date(raw));
30
31
  }
@@ -3,9 +3,10 @@ import type { RequestOptions } from './shared/request-options';
3
3
  import { ElmoResource } from './shared/resource';
4
4
  import { callPromise, RestCallPromise } from '../internal/core/call-promise';
5
5
  import { callable } from '../internal/core/callable';
6
+ import { PagePromise, pages } from '../internal/core/page';
6
7
  import { mergeParams, placeParams } from '../internal/core/params';
8
+ import type { Mutable } from '../internal/feature/validate';
7
9
  import { decoded, reviving } from '../internal/feature/validate';
8
- import { PagePromise, pages } from '../internal/page/page';
9
10
  import type {
10
11
  GetRunErrors,
11
12
  GetRunResponses,
@@ -20,13 +21,13 @@ import type {
20
21
 
21
22
  export const listPromptRunsFields = { path: ['promptId'] };
22
23
 
23
- function reviveRunSummary(value: RunSummary): void {
24
+ function reviveRunSummary(value: Mutable<RunSummary>): void {
24
25
  if (value.createdAt != null) {
25
26
  value.createdAt = decoded('RunSummary.createdAt', value.createdAt, (raw) => new Date(raw));
26
27
  }
27
28
  }
28
29
 
29
- function reviveRunList(value: RunList): void {
30
+ function reviveRunList(value: Mutable<RunList>): void {
30
31
  if (value.data != null) {
31
32
  value.data.forEach((item) => {
32
33
  reviveRunSummary(item);
@@ -53,7 +54,7 @@ export const listPromptRunsDescriptor = /* @__PURE__ */ callable({
53
54
 
54
55
  export const getRunFields = { path: ['promptId', 'runId'] };
55
56
 
56
- function reviveRun(value: Run): void {
57
+ function reviveRun(value: Mutable<Run>): void {
57
58
  if (value.createdAt != null) {
58
59
  value.createdAt = decoded('Run.createdAt', value.createdAt, (raw) => new Date(raw));
59
60
  }
@@ -34,6 +34,7 @@ export const analyzeBrandDescriptor = /* @__PURE__ */ callable({
34
34
  name: 'maxPrompts',
35
35
  },
36
36
  ],
37
+ timeout: 600000,
37
38
  } as const);
38
39
 
39
40
  /** One-shot helpers (e.g. brand analysis) that don't persist anything */
@@ -408,14 +408,16 @@ export type GetBrandQueryFanoutResponses = {
408
408
 
409
409
  export type GetBrandAnalyticsParams = {
410
410
  /**
411
- * Inclusive lower bound of the window, an ISO 8601 timestamp such as `2026-01-01T00:00:00Z`. A
412
- * bare `YYYY-MM-DD` is rejected: that is `/prompts/{promptId}/snapshot`'s spelling and means a
413
- * local calendar day there.
411
+ * Inclusive lower bound of the window, an ISO 8601 timestamp such as `2026-01-01T00:00:00Z`.
412
+ * Aligned down to the quarter hour, the grain analytics are aggregated at; where a response
413
+ * repeats the window, it repeats the aligned one. A bare `YYYY-MM-DD` is rejected: that is
414
+ * `/prompts/{promptId}/snapshot`'s spelling and means a local calendar day there.
414
415
  */
415
416
  start: WindowStart;
416
417
  /**
417
- * Exclusive upper bound of the window, an ISO 8601 timestamp. The window is half-open: a run at
418
- * exactly `end` is outside it.
418
+ * Exclusive upper bound of the window, an ISO 8601 timestamp, also aligned down to the quarter
419
+ * hour. The window is half-open: a run at exactly `end` is outside it. `start` and `end` inside
420
+ * one quarter-hour bucket leave an empty window and are rejected.
419
421
  */
420
422
  end: WindowEnd;
421
423
  /** Restrict to one model, e.g. `chatgpt`. See `models.list()`. */
@@ -426,14 +428,16 @@ export type GetBrandAnalyticsParams = {
426
428
 
427
429
  export type ListBrandCitationDomainsParams = {
428
430
  /**
429
- * Inclusive lower bound of the window, an ISO 8601 timestamp such as `2026-01-01T00:00:00Z`. A
430
- * bare `YYYY-MM-DD` is rejected: that is `/prompts/{promptId}/snapshot`'s spelling and means a
431
- * local calendar day there.
431
+ * Inclusive lower bound of the window, an ISO 8601 timestamp such as `2026-01-01T00:00:00Z`.
432
+ * Aligned down to the quarter hour, the grain analytics are aggregated at; where a response
433
+ * repeats the window, it repeats the aligned one. A bare `YYYY-MM-DD` is rejected: that is
434
+ * `/prompts/{promptId}/snapshot`'s spelling and means a local calendar day there.
432
435
  */
433
436
  start: WindowStart;
434
437
  /**
435
- * Exclusive upper bound of the window, an ISO 8601 timestamp. The window is half-open: a run at
436
- * exactly `end` is outside it.
438
+ * Exclusive upper bound of the window, an ISO 8601 timestamp, also aligned down to the quarter
439
+ * hour. The window is half-open: a run at exactly `end` is outside it. `start` and `end` inside
440
+ * one quarter-hour bucket leave an empty window and are rejected.
437
441
  */
438
442
  end: WindowEnd;
439
443
  /** Restrict to one model, e.g. `chatgpt`. See `models.list()`. */
@@ -444,14 +448,16 @@ export type ListBrandCitationDomainsParams = {
444
448
 
445
449
  export type ListBrandCitationURLsParams = {
446
450
  /**
447
- * Inclusive lower bound of the window, an ISO 8601 timestamp such as `2026-01-01T00:00:00Z`. A
448
- * bare `YYYY-MM-DD` is rejected: that is `/prompts/{promptId}/snapshot`'s spelling and means a
449
- * local calendar day there.
451
+ * Inclusive lower bound of the window, an ISO 8601 timestamp such as `2026-01-01T00:00:00Z`.
452
+ * Aligned down to the quarter hour, the grain analytics are aggregated at; where a response
453
+ * repeats the window, it repeats the aligned one. A bare `YYYY-MM-DD` is rejected: that is
454
+ * `/prompts/{promptId}/snapshot`'s spelling and means a local calendar day there.
450
455
  */
451
456
  start: WindowStart;
452
457
  /**
453
- * Exclusive upper bound of the window, an ISO 8601 timestamp. The window is half-open: a run at
454
- * exactly `end` is outside it.
458
+ * Exclusive upper bound of the window, an ISO 8601 timestamp, also aligned down to the quarter
459
+ * hour. The window is half-open: a run at exactly `end` is outside it. `start` and `end` inside
460
+ * one quarter-hour bucket leave an empty window and are rejected.
455
461
  */
456
462
  end: WindowEnd;
457
463
  /** Restrict to one model, e.g. `chatgpt`. See `models.list()`. */
@@ -462,14 +468,16 @@ export type ListBrandCitationURLsParams = {
462
468
 
463
469
  export type ListBrandPromptPerformanceParams = {
464
470
  /**
465
- * Inclusive lower bound of the window, an ISO 8601 timestamp such as `2026-01-01T00:00:00Z`. A
466
- * bare `YYYY-MM-DD` is rejected: that is `/prompts/{promptId}/snapshot`'s spelling and means a
467
- * local calendar day there.
471
+ * Inclusive lower bound of the window, an ISO 8601 timestamp such as `2026-01-01T00:00:00Z`.
472
+ * Aligned down to the quarter hour, the grain analytics are aggregated at; where a response
473
+ * repeats the window, it repeats the aligned one. A bare `YYYY-MM-DD` is rejected: that is
474
+ * `/prompts/{promptId}/snapshot`'s spelling and means a local calendar day there.
468
475
  */
469
476
  start: WindowStart;
470
477
  /**
471
- * Exclusive upper bound of the window, an ISO 8601 timestamp. The window is half-open: a run at
472
- * exactly `end` is outside it.
478
+ * Exclusive upper bound of the window, an ISO 8601 timestamp, also aligned down to the quarter
479
+ * hour. The window is half-open: a run at exactly `end` is outside it. `start` and `end` inside
480
+ * one quarter-hour bucket leave an empty window and are rejected.
473
481
  */
474
482
  end: WindowEnd;
475
483
  /** Restrict to one model, e.g. `chatgpt`. See `models.list()`. */
@@ -480,14 +488,16 @@ export type ListBrandPromptPerformanceParams = {
480
488
 
481
489
  export type GetBrandQueryFanoutParams = {
482
490
  /**
483
- * Inclusive lower bound of the window, an ISO 8601 timestamp such as `2026-01-01T00:00:00Z`. A
484
- * bare `YYYY-MM-DD` is rejected: that is `/prompts/{promptId}/snapshot`'s spelling and means a
485
- * local calendar day there.
491
+ * Inclusive lower bound of the window, an ISO 8601 timestamp such as `2026-01-01T00:00:00Z`.
492
+ * Aligned down to the quarter hour, the grain analytics are aggregated at; where a response
493
+ * repeats the window, it repeats the aligned one. A bare `YYYY-MM-DD` is rejected: that is
494
+ * `/prompts/{promptId}/snapshot`'s spelling and means a local calendar day there.
486
495
  */
487
496
  start: WindowStart;
488
497
  /**
489
- * Exclusive upper bound of the window, an ISO 8601 timestamp. The window is half-open: a run at
490
- * exactly `end` is outside it.
498
+ * Exclusive upper bound of the window, an ISO 8601 timestamp, also aligned down to the quarter
499
+ * hour. The window is half-open: a run at exactly `end` is outside it. `start` and `end` inside
500
+ * one quarter-hour bucket leave an empty window and are rejected.
491
501
  */
492
502
  end: WindowEnd;
493
503
  /** Restrict to one model, e.g. `chatgpt`. See `models.list()`. */
package/src/types/runs.ts CHANGED
@@ -101,14 +101,16 @@ export type GetRunResponses = {
101
101
 
102
102
  export type ListPromptRunsParams = {
103
103
  /**
104
- * Inclusive lower bound of the window, an ISO 8601 timestamp such as `2026-01-01T00:00:00Z`. A
105
- * bare `YYYY-MM-DD` is rejected: that is `/prompts/{promptId}/snapshot`'s spelling and means a
106
- * local calendar day there.
104
+ * Inclusive lower bound of the window, an ISO 8601 timestamp such as `2026-01-01T00:00:00Z`.
105
+ * Aligned down to the quarter hour, the grain analytics are aggregated at; where a response
106
+ * repeats the window, it repeats the aligned one. A bare `YYYY-MM-DD` is rejected: that is
107
+ * `/prompts/{promptId}/snapshot`'s spelling and means a local calendar day there.
107
108
  */
108
109
  start: WindowStart;
109
110
  /**
110
- * Exclusive upper bound of the window, an ISO 8601 timestamp. The window is half-open: a run at
111
- * exactly `end` is outside it.
111
+ * Exclusive upper bound of the window, an ISO 8601 timestamp, also aligned down to the quarter
112
+ * hour. The window is half-open: a run at exactly `end` is outside it. `start` and `end` inside
113
+ * one quarter-hour bucket leave an empty window and are rejected.
112
114
  */
113
115
  end: WindowEnd;
114
116
  /** Restrict to one model, e.g. `chatgpt`. See `models.list()`. */