@boxxy/pepys 0.1.4 → 0.1.5

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.
@@ -51,7 +51,7 @@ export interface BoxxyItemFiledPayloadAcceptanceItem {
51
51
  evidence: string[];
52
52
  }
53
53
  export interface BoxxyItemFiledPayloadFields {
54
- designMode: unknown;
54
+ designMode?: 'agent' | 'attended';
55
55
  }
56
56
  export interface BoxxyItemNotedPayload {
57
57
  seq: number;
@@ -59,15 +59,23 @@ export interface BoxxyItemNotedPayload {
59
59
  storeVersion: number;
60
60
  }
61
61
  export interface BoxxyItemEditedPayload {
62
- title: unknown;
63
- outcome: unknown;
64
- acceptance: unknown;
65
- kind: unknown;
66
- fields: unknown;
67
- labels: unknown;
62
+ title?: string;
63
+ outcome?: string;
64
+ acceptance?: BoxxyItemEditedPayloadAcceptanceItem[];
65
+ kind?: string;
66
+ fields?: BoxxyItemEditedPayloadFields;
67
+ labels?: string[];
68
68
  byAttempt: string | null;
69
69
  storeVersion: number;
70
70
  }
71
+ export interface BoxxyItemEditedPayloadAcceptanceItem {
72
+ id: string;
73
+ text: string;
74
+ evidence: string[];
75
+ }
76
+ export interface BoxxyItemEditedPayloadFields {
77
+ designMode?: 'agent' | 'attended';
78
+ }
71
79
  export interface BoxxyItemClosedPayload {
72
80
  reason: string;
73
81
  byAttempt: string | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boxxy/pepys",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "The pepys plug-in of boxxy, its fact sink and its report reader",
5
5
  "homepage": "https://github.com/zgeoff/boxxy/tree/main/packages/pepys#readme",
6
6
  "bugs": "https://github.com/zgeoff/boxxy/issues",
@@ -40,22 +40,45 @@
40
40
  "typecheck": "tsrs --noEmit"
41
41
  },
42
42
  "dependencies": {
43
- "@boxxy/model": "0.1.4"
43
+ "@boxxy/model": "0.1.5"
44
44
  },
45
45
  "devDependencies": {
46
- "@pepys/cli": "https://github.com/zgeoff/pepys/releases/download/preview-kit-0.0.1/pepys-cli-0.0.1.tgz",
47
- "@pepys/kit": "https://github.com/zgeoff/pepys/releases/download/preview-kit-0.0.1/pepys-kit-0.0.1.tgz",
48
- "@pepys/kysely-turso": "https://github.com/zgeoff/pepys/releases/download/preview-kit-0.0.1/pepys-kysely-turso-0.0.1.tgz",
49
- "@pepys/schema": "https://github.com/zgeoff/pepys/releases/download/preview-kit-0.0.1/pepys-schema-0.0.1.tgz",
50
- "@pepys/testing": "https://github.com/zgeoff/pepys/releases/download/preview-kit-0.0.1/pepys-testing-0.0.1.tgz",
46
+ "@orpc/client": "2.0.0-beta.50",
47
+ "@pepys/api": "0.1.0",
48
+ "@pepys/cli": "0.1.0",
49
+ "@pepys/ingest": "0.1.0",
50
+ "@pepys/kit": "0.1.0",
51
+ "@pepys/source-github": "0.1.0",
52
+ "@pepys/source-telemetry": "0.1.0",
53
+ "@pepys/testing": "0.1.0",
51
54
  "zod": "4.6.7"
52
55
  },
53
56
  "peerDependencies": {
54
- "@pepys/kit": "0.0.1",
55
- "@pepys/testing": "0.0.1",
57
+ "@orpc/client": "2.0.0-beta.50",
58
+ "@pepys/api": "^0.1.0",
59
+ "@pepys/ingest": "^0.1.0",
60
+ "@pepys/kit": "^0.1.0",
61
+ "@pepys/source-github": "^0.1.0",
62
+ "@pepys/source-telemetry": "^0.1.0",
63
+ "@pepys/testing": "^0.1.0",
56
64
  "zod": "4.6.7"
57
65
  },
58
66
  "peerDependenciesMeta": {
67
+ "@orpc/client": {
68
+ "optional": true
69
+ },
70
+ "@pepys/api": {
71
+ "optional": true
72
+ },
73
+ "@pepys/ingest": {
74
+ "optional": true
75
+ },
76
+ "@pepys/source-github": {
77
+ "optional": true
78
+ },
79
+ "@pepys/source-telemetry": {
80
+ "optional": true
81
+ },
59
82
  "@pepys/testing": {
60
83
  "optional": true
61
84
  }
@@ -125,7 +125,7 @@ export interface BoxxyItemFiledPayloadAcceptanceItem {
125
125
  }
126
126
 
127
127
  export interface BoxxyItemFiledPayloadFields {
128
- designMode: unknown;
128
+ designMode?: 'agent' | 'attended';
129
129
  }
130
130
 
131
131
  export interface BoxxyItemNotedPayload {
@@ -135,16 +135,26 @@ export interface BoxxyItemNotedPayload {
135
135
  }
136
136
 
137
137
  export interface BoxxyItemEditedPayload {
138
- title: unknown;
139
- outcome: unknown;
140
- acceptance: unknown;
141
- kind: unknown;
142
- fields: unknown;
143
- labels: unknown;
138
+ title?: string;
139
+ outcome?: string;
140
+ acceptance?: BoxxyItemEditedPayloadAcceptanceItem[];
141
+ kind?: string;
142
+ fields?: BoxxyItemEditedPayloadFields;
143
+ labels?: string[];
144
144
  byAttempt: string | null;
145
145
  storeVersion: number;
146
146
  }
147
147
 
148
+ export interface BoxxyItemEditedPayloadAcceptanceItem {
149
+ id: string;
150
+ text: string;
151
+ evidence: string[];
152
+ }
153
+
154
+ export interface BoxxyItemEditedPayloadFields {
155
+ designMode?: 'agent' | 'attended';
156
+ }
157
+
148
158
  export interface BoxxyItemClosedPayload {
149
159
  reason: string;
150
160
  byAttempt: string | null;
@@ -955,10 +965,15 @@ function decodeBoxxyItemFiledPayloadFields(value: unknown): BoxxyItemFiledPayloa
955
965
  return undefined;
956
966
  }
957
967
 
958
- const field0 = 'designMode' in value ? value.designMode : undefined;
968
+ const raw0 = 'designMode' in value ? value.designMode : undefined;
969
+ const field0 = raw0 === undefined ? undefined : raw0 === 'agent' || raw0 === 'attended' ? raw0 : undefined;
970
+
971
+ if (raw0 !== undefined && field0 === undefined) {
972
+ return undefined;
973
+ }
959
974
 
960
975
  return {
961
- designMode: field0,
976
+ ...(field0 === undefined ? {} : { designMode: field0 }),
962
977
  };
963
978
  }
964
979
 
@@ -1021,17 +1036,47 @@ function decodeBoxxyItemEditedPayload(value: unknown): BoxxyItemEditedPayload |
1021
1036
  return undefined;
1022
1037
  }
1023
1038
 
1024
- const field0 = 'title' in value ? value.title : undefined;
1039
+ const raw0 = 'title' in value ? value.title : undefined;
1040
+ const field0 = raw0 === undefined ? undefined : typeof raw0 === 'string' ? raw0 : undefined;
1041
+
1042
+ if (raw0 !== undefined && field0 === undefined) {
1043
+ return undefined;
1044
+ }
1045
+
1046
+ const raw1 = 'outcome' in value ? value.outcome : undefined;
1047
+ const field1 = raw1 === undefined ? undefined : typeof raw1 === 'string' ? raw1 : undefined;
1048
+
1049
+ if (raw1 !== undefined && field1 === undefined) {
1050
+ return undefined;
1051
+ }
1052
+
1053
+ const raw2 = 'acceptance' in value ? value.acceptance : undefined;
1054
+ const field2 = raw2 === undefined ? undefined : decodeBoxxyItemEditedPayloadAcceptance(raw2);
1025
1055
 
1026
- const field1 = 'outcome' in value ? value.outcome : undefined;
1056
+ if (raw2 !== undefined && field2 === undefined) {
1057
+ return undefined;
1058
+ }
1027
1059
 
1028
- const field2 = 'acceptance' in value ? value.acceptance : undefined;
1060
+ const raw3 = 'kind' in value ? value.kind : undefined;
1061
+ const field3 = raw3 === undefined ? undefined : typeof raw3 === 'string' ? raw3 : undefined;
1029
1062
 
1030
- const field3 = 'kind' in value ? value.kind : undefined;
1063
+ if (raw3 !== undefined && field3 === undefined) {
1064
+ return undefined;
1065
+ }
1031
1066
 
1032
- const field4 = 'fields' in value ? value.fields : undefined;
1067
+ const raw4 = 'fields' in value ? value.fields : undefined;
1068
+ const field4 = raw4 === undefined ? undefined : decodeBoxxyItemEditedPayloadFields(raw4);
1033
1069
 
1034
- const field5 = 'labels' in value ? value.labels : undefined;
1070
+ if (raw4 !== undefined && field4 === undefined) {
1071
+ return undefined;
1072
+ }
1073
+
1074
+ const raw5 = 'labels' in value ? value.labels : undefined;
1075
+ const field5 = raw5 === undefined ? undefined : decodeBoxxyItemEditedPayloadLabels(raw5);
1076
+
1077
+ if (raw5 !== undefined && field5 === undefined) {
1078
+ return undefined;
1079
+ }
1035
1080
 
1036
1081
  const raw6 = 'byAttempt' in value ? value.byAttempt : undefined;
1037
1082
  const field6 = raw6 === null ? null : typeof raw6 === 'string' ? raw6 : undefined;
@@ -1048,17 +1093,130 @@ function decodeBoxxyItemEditedPayload(value: unknown): BoxxyItemEditedPayload |
1048
1093
  }
1049
1094
 
1050
1095
  return {
1051
- title: field0,
1052
- outcome: field1,
1053
- acceptance: field2,
1054
- kind: field3,
1055
- fields: field4,
1056
- labels: field5,
1096
+ ...(field0 === undefined ? {} : { title: field0 }),
1097
+ ...(field1 === undefined ? {} : { outcome: field1 }),
1098
+ ...(field2 === undefined ? {} : { acceptance: field2 }),
1099
+ ...(field3 === undefined ? {} : { kind: field3 }),
1100
+ ...(field4 === undefined ? {} : { fields: field4 }),
1101
+ ...(field5 === undefined ? {} : { labels: field5 }),
1057
1102
  byAttempt: field6,
1058
1103
  storeVersion: field7,
1059
1104
  };
1060
1105
  }
1061
1106
 
1107
+ function decodeBoxxyItemEditedPayloadAcceptance(value: unknown): BoxxyItemEditedPayloadAcceptanceItem[] | undefined {
1108
+ if (!Array.isArray(value)) {
1109
+ return undefined;
1110
+ }
1111
+
1112
+ const items: BoxxyItemEditedPayloadAcceptanceItem[] = [];
1113
+
1114
+ for (let index = 0; index < value.length; index += 1) {
1115
+ const element: unknown = value[index];
1116
+ const item = decodeBoxxyItemEditedPayloadAcceptanceItem(element);
1117
+
1118
+ if (item === undefined) {
1119
+ return undefined;
1120
+ }
1121
+
1122
+ items.push(item);
1123
+ }
1124
+
1125
+ return items;
1126
+ }
1127
+
1128
+ function decodeBoxxyItemEditedPayloadAcceptanceItem(value: unknown): BoxxyItemEditedPayloadAcceptanceItem | undefined {
1129
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
1130
+ return undefined;
1131
+ }
1132
+
1133
+ const raw0 = 'id' in value ? value.id : undefined;
1134
+ const field0 = typeof raw0 === 'string' ? raw0 : undefined;
1135
+
1136
+ if (field0 === undefined) {
1137
+ return undefined;
1138
+ }
1139
+
1140
+ const raw1 = 'text' in value ? value.text : undefined;
1141
+ const field1 = typeof raw1 === 'string' ? raw1 : undefined;
1142
+
1143
+ if (field1 === undefined) {
1144
+ return undefined;
1145
+ }
1146
+
1147
+ const raw2 = 'evidence' in value ? value.evidence : undefined;
1148
+ const field2 = decodeBoxxyItemEditedPayloadAcceptanceItemEvidence(raw2);
1149
+
1150
+ if (field2 === undefined) {
1151
+ return undefined;
1152
+ }
1153
+
1154
+ return {
1155
+ id: field0,
1156
+ text: field1,
1157
+ evidence: field2,
1158
+ };
1159
+ }
1160
+
1161
+ function decodeBoxxyItemEditedPayloadAcceptanceItemEvidence(value: unknown): string[] | undefined {
1162
+ if (!Array.isArray(value)) {
1163
+ return undefined;
1164
+ }
1165
+
1166
+ const items: string[] = [];
1167
+
1168
+ for (let index = 0; index < value.length; index += 1) {
1169
+ const element: unknown = value[index];
1170
+ const item = typeof element === 'string' ? element : undefined;
1171
+
1172
+ if (item === undefined) {
1173
+ return undefined;
1174
+ }
1175
+
1176
+ items.push(item);
1177
+ }
1178
+
1179
+ return items;
1180
+ }
1181
+
1182
+ function decodeBoxxyItemEditedPayloadFields(value: unknown): BoxxyItemEditedPayloadFields | undefined {
1183
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
1184
+ return undefined;
1185
+ }
1186
+
1187
+ const raw0 = 'designMode' in value ? value.designMode : undefined;
1188
+ const field0 = raw0 === undefined ? undefined : raw0 === 'agent' || raw0 === 'attended' ? raw0 : undefined;
1189
+
1190
+ if (raw0 !== undefined && field0 === undefined) {
1191
+ return undefined;
1192
+ }
1193
+
1194
+ return {
1195
+ ...(field0 === undefined ? {} : { designMode: field0 }),
1196
+ };
1197
+ }
1198
+
1199
+ function decodeBoxxyItemEditedPayloadLabels(value: unknown): string[] | undefined {
1200
+ if (!Array.isArray(value)) {
1201
+ return undefined;
1202
+ }
1203
+
1204
+ const items: string[] = [];
1205
+
1206
+ for (let index = 0; index < value.length; index += 1) {
1207
+ const element: unknown = value[index];
1208
+ const item = typeof element === 'string' ? element : undefined;
1209
+
1210
+ if (item === undefined) {
1211
+ return undefined;
1212
+ }
1213
+
1214
+ items.push(item);
1215
+ }
1216
+
1217
+ return items;
1218
+ }
1219
+
1062
1220
  function decodeBoxxyItemClosedPayload(value: unknown): BoxxyItemClosedPayload | undefined {
1063
1221
  if (typeof value !== 'object' || value === null || Array.isArray(value)) {
1064
1222
  return undefined;
@@ -0,0 +1,286 @@
1
+ import { createORPCClient } from '@orpc/client';
2
+ import { RPCLink } from '@orpc/client/fetch';
3
+ import { buildQueryContext, createApp } from '@pepys/api';
4
+ import { createFakeSenderProof, createIngest, createMemoryFactLog } from '@pepys/ingest';
5
+ import { checkDeployment, parseStreamName } from '@pepys/kit';
6
+ import type {
7
+ FactBatchBody,
8
+ FactEnvelope,
9
+ Sender,
10
+ SourceDefinition,
11
+ StreamName,
12
+ StreamOffset,
13
+ } from '@pepys/kit';
14
+ import {
15
+ githubDeliveryConsumers,
16
+ githubPlugin,
17
+ githubPullRequestConsumer,
18
+ parsePullRequestDeliveries,
19
+ selectPullRequestDelivery,
20
+ } from '@pepys/source-github';
21
+ import {
22
+ telemetryModelRequestConsumer,
23
+ telemetryPlugin,
24
+ telemetryTurnCostConsumer,
25
+ } from '@pepys/source-telemetry';
26
+ import { createTestStore } from '@pepys/testing';
27
+ import type { AppliedFact } from '@pepys/testing';
28
+ import { buildBoxxyPlugin } from '../build-boxxy-plugin.ts';
29
+ import type { DeliveryReads, ForeignReads } from '../queries/types.ts';
30
+ import type { UncheckedQueryClient } from '../types.ts';
31
+ import type { TelemetryRequest, TestPepys, TestPepysOptions } from './create-test-pepys.ts';
32
+
33
+ // The pull requests that the item's attempts produced, as boxxy's own rows give them, and then
34
+ // the GitHub plug-in's delivery module on those pull requests.
35
+ const delivery: DeliveryReads = {
36
+ consumers: [...githubDeliveryConsumers],
37
+ readDeliveries: async (db, input) => {
38
+ const produced = await db
39
+ .selectFrom('link_effective as produced')
40
+ .innerJoin('boxxy_attempt as attempt', 'attempt.id', 'produced.from_id')
41
+ .where('produced.type', '=', 'produced')
42
+ .where('attempt.item_id', '=', input.item)
43
+ .select('produced.to_id as id')
44
+ .distinct()
45
+ .execute();
46
+
47
+ const [first, ...rest] = produced.map((row) => row.id);
48
+
49
+ if (first === undefined) {
50
+ return [];
51
+ }
52
+
53
+ const result = await selectPullRequestDelivery({
54
+ pullRequests: [first, ...rest],
55
+ environment: input.environment,
56
+ }).execute(db);
57
+
58
+ return parsePullRequestDeliveries(result.rows).map((row) => ({
59
+ pullRequest: row.pullRequest,
60
+ mergedAt: row.mergedAt,
61
+ release: row.release === null ? null : { at: row.release.at },
62
+ deploy: row.deploy === null ? null : { at: row.deploy.at },
63
+ }));
64
+ },
65
+ };
66
+
67
+ const foreign: ForeignReads = {
68
+ telemetryModelRequest: telemetryModelRequestConsumer,
69
+ telemetryTurnCost: telemetryTurnCostConsumer,
70
+ githubPullRequest: githubPullRequestConsumer,
71
+ delivery,
72
+ };
73
+
74
+ const telemetryToken = 'telemetry-test-token';
75
+
76
+ // The real pepys ingest, the GitHub, telemetry and boxxy plug-ins, and the real query API, in
77
+ // process. Ingest appends to its memory fact log. A host applies the log to the store; here each
78
+ // query first applies what is new to the kit's test store, which the API then reads.
79
+ export async function createRealPepys(options: TestPepysOptions): Promise<TestPepys> {
80
+ const plugins = [buildBoxxyPlugin(foreign), githubPlugin, telemetryPlugin];
81
+ const checked = checkDeployment({ name: 'boxxy-system-test', plugins });
82
+
83
+ if (!checked.ok) {
84
+ throw new Error(`the deployment does not check: ${JSON.stringify(checked.failure)}`);
85
+ }
86
+
87
+ const deployment = checked.value;
88
+
89
+ const streams = deployment.sources
90
+ .flatMap((source) => source.families.map((family) => family.stream))
91
+ .filter((stream) => isStreamName(stream));
92
+
93
+ const log = createMemoryFactLog(streams);
94
+ const senders: Record<string, Sender> = {};
95
+
96
+ const ingest = createIngest({
97
+ deployment,
98
+ secrets: {
99
+ PEPYS_BOXXY_SIGNING_SECRET: options.boxxySecret,
100
+ PEPYS_GITHUB_WEBHOOK_SECRET: options.webhookSecret,
101
+ PEPYS_TELEMETRY_TOKEN: telemetryToken,
102
+ PEPYS_GITHUB_APP_ID: '1',
103
+ PEPYS_GITHUB_APP_PRIVATE_KEY: 'unused',
104
+ },
105
+ log,
106
+ senderProof: createFakeSenderProof(senders),
107
+ appendRetry: { maxAttempts: 1, retryDelayMs: 0 },
108
+ now: () => Date.now(),
109
+ });
110
+
111
+ const store = await createTestStore({ plugins });
112
+
113
+ const applied = new Map<StreamName, StreamOffset>();
114
+
115
+ for (const stream of streams) {
116
+ const tail = await log.readTail(stream);
117
+
118
+ if (tail.ok) {
119
+ applied.set(stream, tail.value);
120
+ }
121
+ }
122
+
123
+ const applyLog = async () => {
124
+ for (const source of deployment.sources) {
125
+ for (const stream of source.families.map((family) => family.stream)) {
126
+ await applyStream(source, stream);
127
+ }
128
+ }
129
+ };
130
+
131
+ const applyStream = async (source: SourceDefinition, name: string) => {
132
+ const from = isStreamName(name) ? applied.get(name) : undefined;
133
+
134
+ if (!isStreamName(name) || from === undefined) {
135
+ throw new Error(`the log holds no stream ${name}`);
136
+ }
137
+
138
+ const tail = await log.readTail(name);
139
+
140
+ const read = tail.ok ? await log.read(name, from, tail.value - from) : tail;
141
+
142
+ if (!tail.ok || !read.ok) {
143
+ throw new Error(`the log refused a read of ${name}`);
144
+ }
145
+
146
+ const facts = read.value
147
+ .map((stored) => toOutgoingFact(source.name, stored.fact))
148
+ .filter((fact) => isAppliedFact(source, fact));
149
+
150
+ if (facts.length !== read.value.length) {
151
+ throw new Error(`the log holds a fact of ${name} that the source's families refuse`);
152
+ }
153
+
154
+ if (facts.length > 0) {
155
+ await store.apply(source, facts);
156
+ }
157
+
158
+ applied.set(name, tail.value);
159
+ };
160
+
161
+ const app = createApp(buildQueryContext(store.reader, deployment));
162
+ const sendRequest = (url: string, init: RequestInit) => app.handle(new Request(url, init));
163
+
164
+ const api = createORPCClient<UncheckedQueryClient>(
165
+ new RPCLink({ origin: 'http://pepys.test', url: '/rpc', fetch: sendRequest }),
166
+ );
167
+
168
+ return {
169
+ handleRequest: (request) => ingest.handleRequest(request),
170
+ client: {
171
+ boxxy_cost_to_land: async (input) => {
172
+ await applyLog();
173
+
174
+ return api.boxxy_cost_to_land(input);
175
+ },
176
+ boxxy_stage_time: async (input) => {
177
+ await applyLog();
178
+
179
+ return api.boxxy_stage_time(input);
180
+ },
181
+ },
182
+ writeTelemetry: async (requests) => {
183
+ for (const request of requests) {
184
+ const token = `token-${request.origin.issuer}-${request.origin.subject}`;
185
+
186
+ senders[token] = { ...request.origin, name: request.origin.subject };
187
+
188
+ const body = JSON.stringify(buildOTLPLogs(request));
189
+
190
+ const answer = await ingest.handleRequest(
191
+ new Request('http://pepys.test/v1/logs', {
192
+ method: 'POST',
193
+ headers: {
194
+ authorization: `Bearer ${telemetryToken}`,
195
+ 'imp-identity': token,
196
+ 'content-type': 'application/json',
197
+ },
198
+ body,
199
+ }),
200
+ );
201
+
202
+ if (answer.status !== 200) {
203
+ throw new Error(`telemetry answered ${answer.status}: ${await answer.text()}`);
204
+ }
205
+ }
206
+ },
207
+ [Symbol.asyncDispose]: () => store[Symbol.asyncDispose](),
208
+ };
209
+ }
210
+
211
+ // The kit tags a stream name it has parsed; a declared family stream always parses.
212
+ function isStreamName(name: string): name is StreamName {
213
+ return parseStreamName(name).ok;
214
+ }
215
+
216
+ type WireFact = FactBatchBody['facts'][number];
217
+
218
+ // The fact of a batch that ingest built the envelope from; the event is the envelope's ID less
219
+ // the source's prefix.
220
+ function toOutgoingFact(sourceName: string, fact: FactEnvelope<unknown>): WireFact {
221
+ return {
222
+ type: fact.type,
223
+ event: fact.id.replace(`event:${sourceName}:`, ''),
224
+ entity: fact.entity,
225
+ ...(fact.actor === undefined ? {} : { actor: fact.actor }),
226
+ at: fact.at,
227
+ payload: fact.payload,
228
+ };
229
+ }
230
+
231
+ // The check that ingest made before it stored the fact: a type of one of the source's families,
232
+ // with a payload that the type's schema accepts.
233
+ function isAppliedFact(
234
+ source: SourceDefinition,
235
+ fact: WireFact,
236
+ ): fact is WireFact & AppliedFact<SourceDefinition> {
237
+ return source.families.some(
238
+ (family) =>
239
+ Object.hasOwn(family.types, fact.type) &&
240
+ family.types[fact.type]?.safeParse(fact.payload).success === true,
241
+ );
242
+ }
243
+
244
+ // One Claude Code `api_request` log record in an OTLP logs body, as the telemetry route reads it.
245
+ function buildOTLPLogs(request: TelemetryRequest) {
246
+ const nanos = `${Date.parse(request.at)}000000`;
247
+
248
+ const record = {
249
+ timeUnixNano: nanos,
250
+ observedTimeUnixNano: nanos,
251
+ body: { stringValue: 'event' },
252
+ attributes: buildOTLPAttributes({
253
+ 'event.name': 'api_request',
254
+ 'session.id': `agent-of-${request.session}`,
255
+ 'event.timestamp': request.at,
256
+ request_id: request.id,
257
+ model: request.model,
258
+ cost_usd: request.costUSD,
259
+ input_tokens: request.inputTokens,
260
+ cache_read_tokens: 0,
261
+ cache_creation_tokens: 0,
262
+ output_tokens: request.outputTokens,
263
+ duration_ms: 1000,
264
+ }),
265
+ };
266
+
267
+ const scope = { scope: { name: 'com.anthropic.claude_code.events' }, logRecords: [record] };
268
+ const resource = { attributes: buildOTLPAttributes({ 'atc.session.id': request.session }) };
269
+
270
+ return { resourceLogs: [{ resource, scopeLogs: [scope] }] };
271
+ }
272
+
273
+ function buildOTLPAttributes(values: Record<string, string | number>) {
274
+ return Object.entries(values).map(([key, value]) => ({ key, value: toOTLPValue(key, value) }));
275
+ }
276
+
277
+ // OTLP sends an integer as text; the cost is a double even when it is whole.
278
+ function toOTLPValue(key: string, value: string | number) {
279
+ if (typeof value === 'string') {
280
+ return { stringValue: value };
281
+ }
282
+
283
+ return Number.isInteger(value) && key !== 'cost_usd'
284
+ ? { intValue: String(value) }
285
+ : { doubleValue: value };
286
+ }