@buildinternet/releases-api-types 0.51.0 → 0.52.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buildinternet/releases-api-types",
3
- "version": "0.51.0",
3
+ "version": "0.52.1",
4
4
  "description": "Wire protocol — request/response types for the Releases registry API. Shared by web, workers, and clients.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/api-types.ts CHANGED
@@ -14,6 +14,13 @@ export type {
14
14
  import type { SourceType, SourceFetchPriority } from "@buildinternet/releases-core/source-enums";
15
15
  import type { ApiScope } from "@buildinternet/releases-core/api-token";
16
16
  import type { BreakingLevel } from "@buildinternet/releases-core/breaking";
17
+ import type {
18
+ AiLaneIdSchema,
19
+ AiLaneModelsPutSchema,
20
+ AiLaneStateSchema,
21
+ OpenRouterCatalogModelSchema,
22
+ AiLaneModelsResponseSchema,
23
+ } from "./schemas/ai-models.js";
17
24
  import type {
18
25
  MediaItemSchema,
19
26
  PaginationSchema,
@@ -67,6 +74,12 @@ import type {
67
74
  PatchWorkspaceProfileBodySchema,
68
75
  WorkspaceProfileResponseSchema,
69
76
  } from "./schemas/account-profile.js";
77
+ import type {
78
+ UploadsIntegrationStatusSchema,
79
+ UploadsOAuthConnectResponseSchema,
80
+ UploadsOAuthCallbackBodySchema,
81
+ UploadsOAuthCallbackResponseSchema,
82
+ } from "./schemas/workspace-integrations.js";
70
83
  import type {
71
84
  SourceListItemSchema,
72
85
  SourceWithOrgSchema,
@@ -203,6 +216,15 @@ import type {
203
216
  FeedbackUpdateBodySchema,
204
217
  FeedbackDeleteResponseSchema,
205
218
  } from "./schemas/feedback.js";
219
+ import type {
220
+ ClassificationSummarySchema,
221
+ ClassificationRecentResponseSchema,
222
+ ClassificationRecentItemSchema,
223
+ } from "./schemas/classifications.js";
224
+ import type {
225
+ RecommendationNotifyAddedBodySchema,
226
+ RecommendationNotifyAddedResultSchema,
227
+ } from "./schemas/recommendations.js";
206
228
  import type {
207
229
  SitemapSourceSchema,
208
230
  SitemapPayloadSchema,
@@ -280,6 +302,13 @@ export {
280
302
  NoticeSchema,
281
303
  } from "./schemas/shared.js";
282
304
  export { SiteNoticeSchema, SiteNoticeResponseSchema } from "./schemas/site-notice.js";
305
+ export {
306
+ AiLaneIdSchema,
307
+ AiLaneModelsPutSchema,
308
+ AiLaneStateSchema,
309
+ OpenRouterCatalogModelSchema,
310
+ AiLaneModelsResponseSchema,
311
+ } from "./schemas/ai-models.js";
283
312
  export {
284
313
  OrgListItemSchema,
285
314
  OrgListResponseSchema,
@@ -320,6 +349,12 @@ export {
320
349
  PatchWorkspaceProfileBodySchema,
321
350
  WorkspaceProfileResponseSchema,
322
351
  } from "./schemas/account-profile.js";
352
+ export {
353
+ UploadsIntegrationStatusSchema,
354
+ UploadsOAuthConnectResponseSchema,
355
+ UploadsOAuthCallbackBodySchema,
356
+ UploadsOAuthCallbackResponseSchema,
357
+ } from "./schemas/workspace-integrations.js";
323
358
  export {
324
359
  CreateStubOrgBodySchema,
325
360
  StubFromDomainBodySchema,
@@ -521,6 +556,15 @@ export {
521
556
  FeedbackUpdateBodySchema,
522
557
  FeedbackDeleteResponseSchema,
523
558
  } from "./schemas/feedback.js";
559
+ export {
560
+ ClassificationSummarySchema,
561
+ ClassificationRecentResponseSchema,
562
+ ClassificationRecentItemSchema,
563
+ } from "./schemas/classifications.js";
564
+ export {
565
+ RecommendationNotifyAddedBodySchema,
566
+ RecommendationNotifyAddedResultSchema,
567
+ } from "./schemas/recommendations.js";
524
568
  export {
525
569
  SitemapSourceSchema,
526
570
  SitemapPayloadSchema,
@@ -589,6 +633,12 @@ export type { ResolveResponse } from "./schemas/resolve.js";
589
633
 
590
634
  export type MediaItem = z.infer<typeof MediaItemSchema>;
591
635
 
636
+ export type AiLaneId = z.infer<typeof AiLaneIdSchema>;
637
+ export type AiLaneModelsPut = z.infer<typeof AiLaneModelsPutSchema>;
638
+ export type AiLaneState = z.infer<typeof AiLaneStateSchema>;
639
+ export type OpenRouterCatalogModel = z.infer<typeof OpenRouterCatalogModelSchema>;
640
+ export type AiLaneModelsResponse = z.infer<typeof AiLaneModelsResponseSchema>;
641
+
592
642
  // ── Stub tier (#1947) ──
593
643
 
594
644
  export type OrgStatus = z.infer<typeof OrgStatusSchema>;
@@ -677,6 +727,10 @@ export type UploadAvatarResponse = z.infer<typeof UploadAvatarResponseSchema>;
677
727
  export type WorkspaceProfileFields = z.infer<typeof WorkspaceProfileFieldsSchema>;
678
728
  export type PatchWorkspaceProfileBody = z.infer<typeof PatchWorkspaceProfileBodySchema>;
679
729
  export type WorkspaceProfileResponse = z.infer<typeof WorkspaceProfileResponseSchema>;
730
+ export type UploadsIntegrationStatus = z.infer<typeof UploadsIntegrationStatusSchema>;
731
+ export type UploadsOAuthConnectResponse = z.infer<typeof UploadsOAuthConnectResponseSchema>;
732
+ export type UploadsOAuthCallbackBody = z.infer<typeof UploadsOAuthCallbackBodySchema>;
733
+ export type UploadsOAuthCallbackResponse = z.infer<typeof UploadsOAuthCallbackResponseSchema>;
680
734
  export type OrgDetail = z.infer<typeof OrgDetailSchema>;
681
735
 
682
736
  export type OrgCatalogItem = z.infer<typeof OrgCatalogItemSchema>;
@@ -869,6 +923,12 @@ export interface ProviderHealthResponse extends ListResponse<ProviderHealthSourc
869
923
  overdueOrgs: number;
870
924
  /** Distinct orgs represented among active sources. */
871
925
  totalOrgs: number;
926
+ /**
927
+ * Active sources that should still be polling but whose SourceActor mirror
928
+ * is unmanaged / missing `nextAlarmAt` (#2286). Independent of overdue
929
+ * last-check — a freshly fetched source can still have a dead alarm.
930
+ */
931
+ unmanagedActors: number;
872
932
  };
873
933
  }
874
934
 
@@ -1123,7 +1183,7 @@ export type UserWebhookScope = "org" | "follows";
1123
1183
  export type UserWebhookReleaseTypeFilter = "feature" | "rollup";
1124
1184
 
1125
1185
  /** Webhook delivery output format. */
1126
- export type UserWebhookFormat = "json" | "slack";
1186
+ export type UserWebhookFormat = "json" | "slack" | "discord";
1127
1187
 
1128
1188
  /** A user-owned webhook subscription row (no signing secret). */
1129
1189
  export interface UserWebhookSubscription {
@@ -1170,7 +1230,7 @@ export interface UserWebhookListResponse {
1170
1230
  subscriptions: UserWebhookListItem[];
1171
1231
  }
1172
1232
 
1173
- /** POST /v1/me/webhooks response — signing key shown once at creation (omitted for slack format). */
1233
+ /** POST /v1/me/webhooks response — signing key shown once at creation (omitted for slack/discord). */
1174
1234
  export interface CreateUserWebhookResponse
1175
1235
  extends UserWebhookSubscription, UserWebhookDeliveryHealth {
1176
1236
  orgSlug: string | null;
@@ -1197,7 +1257,7 @@ export interface WebhookDeliveryRow {
1197
1257
  error_message?: string | null;
1198
1258
  error_code?: string | null;
1199
1259
  outcome?: string;
1200
- /** Delivery format — "json" (signed raw event) or "slack" (Block Kit). Empty on pre-format rows. */
1260
+ /** Delivery format — "json" (signed raw event) or a chat card (`slack` / `discord`). Empty on pre-format rows. */
1201
1261
  format?: UserWebhookFormat | "";
1202
1262
  http_status?: number;
1203
1263
  latency_ms?: number;
@@ -1705,6 +1765,15 @@ export type FeedbackListResponse = z.infer<typeof FeedbackListResponseSchema>;
1705
1765
  export type FeedbackUpdateBody = z.infer<typeof FeedbackUpdateBodySchema>;
1706
1766
  export type FeedbackDeleteResponse = z.infer<typeof FeedbackDeleteResponseSchema>;
1707
1767
 
1768
+ // ── Classification analytics ──
1769
+ export type ClassificationSummary = z.infer<typeof ClassificationSummarySchema>;
1770
+ export type ClassificationRecentResponse = z.infer<typeof ClassificationRecentResponseSchema>;
1771
+ export type ClassificationRecentItem = z.infer<typeof ClassificationRecentItemSchema>;
1772
+
1773
+ // ── Recommendations ──
1774
+ export type RecommendationNotifyAddedBody = z.infer<typeof RecommendationNotifyAddedBodySchema>;
1775
+ export type RecommendationNotifyAddedResult = z.infer<typeof RecommendationNotifyAddedResultSchema>;
1776
+
1708
1777
  // ── Fetch log ──
1709
1778
 
1710
1779
  export interface FetchLogEntry {
@@ -0,0 +1,29 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { AiLaneModelsPutSchema } from "./ai-models";
3
+
4
+ describe("AiLaneModelsPutSchema", () => {
5
+ it("accepts a partial map", () => {
6
+ expect(
7
+ AiLaneModelsPutSchema.safeParse({
8
+ models: { summarize: "deepseek/deepseek-v4.1-flash" },
9
+ }).success,
10
+ ).toBe(true);
11
+ });
12
+
13
+ it("accepts null to clear an override", () => {
14
+ expect(AiLaneModelsPutSchema.safeParse({ models: { extract: null } }).success).toBe(true);
15
+ });
16
+
17
+ it("rejects an empty models object", () => {
18
+ expect(AiLaneModelsPutSchema.safeParse({ models: {} }).success).toBe(false);
19
+ });
20
+
21
+ it("rejects unknown keys", () => {
22
+ expect(
23
+ AiLaneModelsPutSchema.safeParse({
24
+ models: { summarize: "x" },
25
+ extra: true,
26
+ }).success,
27
+ ).toBe(false);
28
+ });
29
+ });
@@ -0,0 +1,49 @@
1
+ import { z } from "zod";
2
+
3
+ export const AiLaneIdSchema = z.enum(["summarize", "extract", "feed-enrich", "marketing"]);
4
+
5
+ const ModelIdOrClearSchema = z.string().max(200).nullable().optional();
6
+
7
+ /** PUT /v1/ai/models — partial map; `null` or `""` clears that lane's override. */
8
+ export const AiLaneModelsPutSchema = z
9
+ .object({
10
+ models: z
11
+ .object({
12
+ summarize: ModelIdOrClearSchema,
13
+ extract: ModelIdOrClearSchema,
14
+ "feed-enrich": ModelIdOrClearSchema,
15
+ marketing: ModelIdOrClearSchema,
16
+ })
17
+ .refine((m) => Object.values(m).some((v) => v !== undefined), {
18
+ message: "at least one lane is required",
19
+ }),
20
+ })
21
+ .strict();
22
+
23
+ export const AiLaneStateSchema = z.object({
24
+ id: AiLaneIdSchema,
25
+ label: z.string(),
26
+ description: z.string(),
27
+ envVar: z.string(),
28
+ wranglerDefault: z.string().nullable(),
29
+ override: z.string().nullable(),
30
+ effective: z.string().nullable(),
31
+ });
32
+
33
+ export const OpenRouterCatalogModelSchema = z.object({
34
+ id: z.string(),
35
+ name: z.string(),
36
+ contextLength: z.number().nullable(),
37
+ promptPricePerMillion: z.number().nullable(),
38
+ completionPricePerMillion: z.number().nullable(),
39
+ vision: z.boolean(),
40
+ });
41
+
42
+ /** GET /v1/ai/models — current assignments plus the OpenRouter catalog. */
43
+ export const AiLaneModelsResponseSchema = z.object({
44
+ lanes: z.array(AiLaneStateSchema),
45
+ catalog: z.array(OpenRouterCatalogModelSchema),
46
+ catalogFetchedAt: z.string().nullable(),
47
+ catalogError: z.string().nullable(),
48
+ updatedAt: z.string().nullable(),
49
+ });
@@ -0,0 +1,105 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Admin classification analytics (`GET /v1/admin/classifications/summary`
5
+ * and `/recent`). Decisions live in Analytics Engine; these shapes are the
6
+ * hydrated read model, not the positional point schema.
7
+ */
8
+
9
+ const ClassificationOriginSchema = z.enum(["ingest", "manual", "eval"]);
10
+ const ClassificationOriginFilterSchema = z.enum(["ingest", "manual", "eval", "all"]);
11
+ const ClassificationDispositionSchema = z.enum(["kept", "suppressed", "failed", "skipped"]);
12
+
13
+ const ProbabilityBinSchema = z.object({
14
+ /** Inclusive. Width is 0.1. `0.8` is the start of the threshold bin. */
15
+ start: z.number(),
16
+ /** Exclusive, except the last bin (`0.9`–`1`) which includes `1`. */
17
+ end: z.number(),
18
+ count: z.number(),
19
+ });
20
+
21
+ const ProbabilityHistogramSchema = z.object({
22
+ bins: z.array(ProbabilityBinSchema),
23
+ /** Sampled rows whose double is `< 0` (the absent sentinel). */
24
+ missing: z.number(),
25
+ });
26
+
27
+ /** `GET /v1/admin/classifications/summary`. */
28
+ export const ClassificationSummarySchema = z.object({
29
+ after: z.string(),
30
+ before: z.string(),
31
+ bucket: z.enum(["hour", "day"]),
32
+ origin: ClassificationOriginFilterSchema,
33
+ totals: z.object({
34
+ classified: z.number(),
35
+ kept: z.number(),
36
+ suppressed: z.number(),
37
+ failed: z.number(),
38
+ skipped: z.number(),
39
+ costUsd: z.number(),
40
+ /** `suppressed / (kept + suppressed)`, or null when the denominator is 0. */
41
+ suppressionRate: z.number().nullable(),
42
+ }),
43
+ series: z.array(
44
+ z.object({
45
+ t: z.string(),
46
+ kept: z.number(),
47
+ suppressed: z.number(),
48
+ failed: z.number(),
49
+ skipped: z.number(),
50
+ }),
51
+ ),
52
+ choices: z.array(z.object({ choice: z.string(), count: z.number() })),
53
+ choiceSeries: z.array(
54
+ z.object({
55
+ t: z.string(),
56
+ choices: z.record(z.string(), z.number()),
57
+ }),
58
+ ),
59
+ models: z.array(
60
+ z.object({
61
+ provider: z.string(),
62
+ model: z.string(),
63
+ count: z.number(),
64
+ costUsd: z.number(),
65
+ }),
66
+ ),
67
+ probability: z.object({
68
+ threshold: z.literal(0.8),
69
+ selected: ProbabilityHistogramSchema,
70
+ confidence: ProbabilityHistogramSchema,
71
+ }),
72
+ meta: z.object({
73
+ dataset: z.string(),
74
+ retentionDays: z.literal(90),
75
+ sampled: z.literal(true),
76
+ }),
77
+ });
78
+
79
+ /** One sampled decision from `GET /v1/admin/classifications/recent`. */
80
+ export const ClassificationRecentItemSchema = z.object({
81
+ timestamp: z.string(),
82
+ origin: ClassificationOriginSchema,
83
+ sourceId: z.string().nullable(),
84
+ sourceName: z.string().nullable(),
85
+ sourceSlug: z.string().nullable(),
86
+ orgSlug: z.string().nullable(),
87
+ releaseId: z.string().nullable(),
88
+ releaseTitle: z.string().nullable(),
89
+ provider: z.string(),
90
+ model: z.string(),
91
+ choice: z.string(),
92
+ selectedChoiceProbability: z.number().nullable(),
93
+ providerConfidence: z.number().nullable(),
94
+ disposition: ClassificationDispositionSchema,
95
+ reason: z.string(),
96
+ failureCategory: z.string(),
97
+ costUsd: z.number().nullable(),
98
+ durationMs: z.number().nullable(),
99
+ });
100
+
101
+ /** `GET /v1/admin/classifications/recent` — newest sampled rows, not weighted. */
102
+ export const ClassificationRecentResponseSchema = z.object({
103
+ items: z.array(ClassificationRecentItemSchema),
104
+ nextCursor: z.string().nullable(),
105
+ });
@@ -0,0 +1,39 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * `POST /v1/admin/recommendations/:id/notify-added` body.
5
+ *
6
+ * Resolves a live registry listing (org required, source optional) and
7
+ * emails the submitter's `contactEmail` that their suggestion was added.
8
+ * Opt-in only — triage / close / archive never send this.
9
+ */
10
+ export const RecommendationNotifyAddedBodySchema = z.object({
11
+ orgSlug: z
12
+ .string()
13
+ .min(1)
14
+ .describe(
15
+ "Org slug or `org_…` id of the onboarded listing. Required so the email can link a live registry URL.",
16
+ ),
17
+ sourceSlug: z
18
+ .string()
19
+ .min(1)
20
+ .optional()
21
+ .describe(
22
+ "Optional source slug or `src_…` id under that org. When set, the email links the source page.",
23
+ ),
24
+ });
25
+
26
+ /**
27
+ * `POST /v1/admin/recommendations/:id/notify-added` success body.
28
+ *
29
+ * `sent: false` + `reason: "already_notified"` is the idempotent replay —
30
+ * a second call does not email again.
31
+ */
32
+ export const RecommendationNotifyAddedResultSchema = z.object({
33
+ ok: z.literal(true),
34
+ sent: z.boolean(),
35
+ reason: z.enum(["already_notified"]).optional(),
36
+ notifiedAt: z.number().nullable(),
37
+ registryUrl: z.string().optional(),
38
+ contactEmail: z.string().optional(),
39
+ });
@@ -0,0 +1,26 @@
1
+ import { z } from "zod";
2
+
3
+ export const UploadsIntegrationStatusSchema = z.object({
4
+ provider: z.literal("uploads"),
5
+ connected: z.boolean(),
6
+ /** False when the token-encryption key is missing. */
7
+ configured: z.boolean(),
8
+ connectedAt: z.string().nullable(),
9
+ scope: z.string().nullable(),
10
+ /** uploads.sh workspace slug from the access-token JWT (`workspace`, else `workspaces[]`). */
11
+ uploadsWorkspace: z.string().nullable(),
12
+ });
13
+
14
+ export const UploadsOAuthConnectResponseSchema = z.object({
15
+ authorizeUrl: z.string(),
16
+ redirectUri: z.string(),
17
+ });
18
+
19
+ export const UploadsOAuthCallbackBodySchema = z.object({
20
+ code: z.string().min(1),
21
+ state: z.string().min(1),
22
+ });
23
+
24
+ export const UploadsOAuthCallbackResponseSchema = UploadsIntegrationStatusSchema.extend({
25
+ workspaceId: z.string(),
26
+ });