@o11y-one/sdk 0.0.0
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/LICENSE +201 -0
- package/README.md +84 -0
- package/dist/auth.d.ts +51 -0
- package/dist/auth.js +64 -0
- package/dist/client.d.ts +105 -0
- package/dist/client.js +107 -0
- package/dist/errors.d.ts +47 -0
- package/dist/errors.js +83 -0
- package/dist/evaluation.d.ts +253 -0
- package/dist/evaluation.js +455 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +24 -0
- package/dist/refusals.d.ts +116 -0
- package/dist/refusals.js +125 -0
- package/dist/result.d.ts +38 -0
- package/dist/result.js +40 -0
- package/dist/validate.d.ts +58 -0
- package/dist/validate.js +103 -0
- package/package.json +42 -0
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
import { timestampFromDate } from "@bufbuild/protobuf/wkt";
|
|
2
|
+
import { AgenticEvaluationService, MachinePrincipalScopeV1, PlatformAnnotationKindV1, EvaluationLaunchRejectionV1Schema, EvaluationCaptureRefusalV1Schema, EvaluationDatasetVersionRejectionV1Schema, PlatformAnnotationRejectionV1Schema, } from "@o11y-one/api-agentic/o11y_one/agentic/v1/evaluation_pb";
|
|
3
|
+
import { ok, err } from "./result.js";
|
|
4
|
+
import { findDetail, toLaunchRejection, toCaptureRefusal, toDatasetVersionRejection, toAnnotationRejection, toLeaseRefusal, toCaseAck, toCursorResync, } from "./refusals.js";
|
|
5
|
+
import { requireNonEmpty, requireIdempotencyKey, requireExactlyOne, rejectUnspecified, } from "./validate.js";
|
|
6
|
+
// --- scope vocabulary bridge ------------------------------------------------
|
|
7
|
+
// The five canonical scope strings (errors.ts, frozen taxonomy) <-> the proto's
|
|
8
|
+
// numeric MachinePrincipalScopeV1. Callers speak strings; the wire speaks the
|
|
9
|
+
// enum; neither leaks into the other.
|
|
10
|
+
const SCOPE_TO_ENUM = {
|
|
11
|
+
"eval:read": MachinePrincipalScopeV1.EVAL_READ,
|
|
12
|
+
"run:execute": MachinePrincipalScopeV1.RUN_EXECUTE,
|
|
13
|
+
"dataset:write": MachinePrincipalScopeV1.DATASET_WRITE,
|
|
14
|
+
"lease:submit": MachinePrincipalScopeV1.LEASE_SUBMIT,
|
|
15
|
+
"platform-annotation:write": MachinePrincipalScopeV1.PLATFORM_ANNOTATION_WRITE,
|
|
16
|
+
};
|
|
17
|
+
const ENUM_TO_SCOPE = {
|
|
18
|
+
[MachinePrincipalScopeV1.EVAL_READ]: "eval:read",
|
|
19
|
+
[MachinePrincipalScopeV1.RUN_EXECUTE]: "run:execute",
|
|
20
|
+
[MachinePrincipalScopeV1.DATASET_WRITE]: "dataset:write",
|
|
21
|
+
[MachinePrincipalScopeV1.LEASE_SUBMIT]: "lease:submit",
|
|
22
|
+
[MachinePrincipalScopeV1.PLATFORM_ANNOTATION_WRITE]: "platform-annotation:write",
|
|
23
|
+
};
|
|
24
|
+
/** Canonical scope strings for a set of wire discriminants; UNSPECIFIED drops. */
|
|
25
|
+
export function scopesToCanonical(values) {
|
|
26
|
+
const out = [];
|
|
27
|
+
for (const v of values) {
|
|
28
|
+
const s = ENUM_TO_SCOPE[v];
|
|
29
|
+
if (s !== undefined) {
|
|
30
|
+
out.push(s);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
// --- one-time credential material -------------------------------------------
|
|
36
|
+
/**
|
|
37
|
+
* The plaintext credential returned exactly once by `createMachineCredential`.
|
|
38
|
+
*
|
|
39
|
+
* It is a DISTINCT type the caller must consume deliberately: `reveal()` hands
|
|
40
|
+
* over the string, and every accidental-logging path — `toString`,
|
|
41
|
+
* `toJSON`, Node's `util.inspect` — returns a redaction instead. The server
|
|
42
|
+
* keeps no read-back copy, so this object is the only place the material ever
|
|
43
|
+
* exists; the redaction is there so a stray `console.log(result)` cannot leak
|
|
44
|
+
* it into a CI log.
|
|
45
|
+
*/
|
|
46
|
+
export class OneTimeCredential {
|
|
47
|
+
#plaintext;
|
|
48
|
+
/** The credential-id this token belongs to, safe to log. */
|
|
49
|
+
credentialId;
|
|
50
|
+
constructor(plaintext, credentialId) {
|
|
51
|
+
this.#plaintext = plaintext;
|
|
52
|
+
this.credentialId = credentialId;
|
|
53
|
+
}
|
|
54
|
+
/** Hand over the plaintext. Store it in your secret manager; never log it. */
|
|
55
|
+
reveal() {
|
|
56
|
+
return this.#plaintext;
|
|
57
|
+
}
|
|
58
|
+
toString() {
|
|
59
|
+
return "[o11y one-time credential — redacted; call reveal()]";
|
|
60
|
+
}
|
|
61
|
+
toJSON() {
|
|
62
|
+
return "[o11y one-time credential — redacted]";
|
|
63
|
+
}
|
|
64
|
+
[Symbol.for("nodejs.util.inspect.custom")]() {
|
|
65
|
+
return this.toString();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// --- the client -------------------------------------------------------------
|
|
69
|
+
export class AgenticEvaluationClient {
|
|
70
|
+
raw;
|
|
71
|
+
constructor(client) {
|
|
72
|
+
this.raw = client.service(AgenticEvaluationService);
|
|
73
|
+
}
|
|
74
|
+
// -- 1. identity ----------------------------------------------------------
|
|
75
|
+
/**
|
|
76
|
+
* Answer "is my credential valid, and which scopes does it carry?" BEFORE a
|
|
77
|
+
* run. An UNAUTHENTICATED / PERMISSION_DENIED here is a thrown `ConnectError`
|
|
78
|
+
* the caller renders with `classify()`; a success carries the scope set that
|
|
79
|
+
* turns a minute-40 failure into a minute-0 one.
|
|
80
|
+
*/
|
|
81
|
+
async whoami() {
|
|
82
|
+
const res = await this.raw.getCallerPrincipal({});
|
|
83
|
+
const scopes = scopesToCanonical(res.machinePrincipal?.scopes ?? []);
|
|
84
|
+
const scopeSet = new Set(scopes);
|
|
85
|
+
return {
|
|
86
|
+
tenantId: res.tenantId,
|
|
87
|
+
orgId: res.orgId,
|
|
88
|
+
machinePrincipalId: res.machinePrincipal?.machinePrincipalId,
|
|
89
|
+
credentialId: res.credential?.credentialId,
|
|
90
|
+
scopes,
|
|
91
|
+
hasScope: (scope) => scopeSet.has(scope),
|
|
92
|
+
raw: res,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
// -- 2. machine credentials ----------------------------------------------
|
|
96
|
+
async createMachinePrincipal(args) {
|
|
97
|
+
requireNonEmpty("displayName", args.displayName);
|
|
98
|
+
const scopes = args.scopes.map((s) => SCOPE_TO_ENUM[s]);
|
|
99
|
+
rejectUnspecified("scopes", scopes);
|
|
100
|
+
const res = await this.raw.createMachinePrincipal({
|
|
101
|
+
displayName: args.displayName,
|
|
102
|
+
...(args.description !== undefined ? { description: args.description } : {}),
|
|
103
|
+
scopes,
|
|
104
|
+
});
|
|
105
|
+
if (res.principal === undefined) {
|
|
106
|
+
throw new Error("createMachinePrincipal: server returned no principal");
|
|
107
|
+
}
|
|
108
|
+
return res.principal;
|
|
109
|
+
}
|
|
110
|
+
async listMachinePrincipals(args) {
|
|
111
|
+
const res = await this.raw.listMachinePrincipals({
|
|
112
|
+
...(args?.pageSize !== undefined ? { pageSize: args.pageSize } : {}),
|
|
113
|
+
...(args?.pageToken !== undefined ? { pageToken: args.pageToken } : {}),
|
|
114
|
+
});
|
|
115
|
+
return { principals: res.principals, nextPageToken: res.nextPageToken };
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Mint a credential. The plaintext comes back once, wrapped in a
|
|
119
|
+
* {@link OneTimeCredential} the caller must `reveal()` to read and can never
|
|
120
|
+
* accidentally log.
|
|
121
|
+
*/
|
|
122
|
+
async createMachineCredential(args) {
|
|
123
|
+
requireNonEmpty("machinePrincipalId", args.machinePrincipalId);
|
|
124
|
+
const res = await this.raw.createMachineCredential({
|
|
125
|
+
machinePrincipalId: args.machinePrincipalId,
|
|
126
|
+
...(args.description !== undefined ? { description: args.description } : {}),
|
|
127
|
+
...(args.expiresAt !== undefined ? { expiresAt: timestampFromDate(args.expiresAt) } : {}),
|
|
128
|
+
});
|
|
129
|
+
if (res.credential === undefined) {
|
|
130
|
+
throw new Error("createMachineCredential: server returned no credential metadata");
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
credential: res.credential,
|
|
134
|
+
token: new OneTimeCredential(res.plaintextTokenOnce, res.credential.credentialId),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
async revokeMachineCredential(args) {
|
|
138
|
+
requireNonEmpty("credentialId", args.credentialId);
|
|
139
|
+
const res = await this.raw.revokeMachineCredential({
|
|
140
|
+
credentialId: args.credentialId,
|
|
141
|
+
...(args.revokeReason !== undefined ? { revokeReason: args.revokeReason } : {}),
|
|
142
|
+
});
|
|
143
|
+
if (res.credential === undefined) {
|
|
144
|
+
throw new Error("revokeMachineCredential: server returned no credential");
|
|
145
|
+
}
|
|
146
|
+
return res.credential;
|
|
147
|
+
}
|
|
148
|
+
async revokeMachinePrincipal(args) {
|
|
149
|
+
requireNonEmpty("machinePrincipalId", args.machinePrincipalId);
|
|
150
|
+
const res = await this.raw.revokeMachinePrincipal({
|
|
151
|
+
machinePrincipalId: args.machinePrincipalId,
|
|
152
|
+
...(args.revokeReason !== undefined ? { revokeReason: args.revokeReason } : {}),
|
|
153
|
+
});
|
|
154
|
+
if (res.principal === undefined) {
|
|
155
|
+
throw new Error("revokeMachinePrincipal: server returned no principal");
|
|
156
|
+
}
|
|
157
|
+
return { principal: res.principal, revokedCredentials: res.revokedCredentials };
|
|
158
|
+
}
|
|
159
|
+
// -- 3. run submission ----------------------------------------------------
|
|
160
|
+
/**
|
|
161
|
+
* Resolve and freeze what a launch WOULD do: the preview token, its digest
|
|
162
|
+
* and expiry, the frozen manifest, the estimates and any blockers. Launch
|
|
163
|
+
* from the token this returns.
|
|
164
|
+
*
|
|
165
|
+
* NOTE: the wire contract launches from a definition (+ optional revision)
|
|
166
|
+
* through a preview token; there is no inline-draft launch path in the proto
|
|
167
|
+
* at this capability. See l1-notes.
|
|
168
|
+
*/
|
|
169
|
+
async previewRun(args) {
|
|
170
|
+
requireNonEmpty("definitionId", args.definitionId);
|
|
171
|
+
return await this.raw.previewEvaluationRun({
|
|
172
|
+
definitionId: args.definitionId,
|
|
173
|
+
...(args.revisionId !== undefined ? { revisionId: args.revisionId } : {}),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Launch a run from an accepted preview token. Returns `err(LaunchRejection)`
|
|
178
|
+
* when a binding moved under the token (the refusal rides in `Status.details`
|
|
179
|
+
* on a FAILED_PRECONDITION); any other transport error rethrows.
|
|
180
|
+
*/
|
|
181
|
+
async launchRun(args) {
|
|
182
|
+
requireNonEmpty("definitionId", args.definitionId);
|
|
183
|
+
requireIdempotencyKey("idempotencyKey", args.idempotencyKey);
|
|
184
|
+
requireNonEmpty("previewToken", args.previewToken);
|
|
185
|
+
try {
|
|
186
|
+
const res = await this.raw.createEvaluationRun({
|
|
187
|
+
definitionId: args.definitionId,
|
|
188
|
+
idempotencyKey: args.idempotencyKey,
|
|
189
|
+
previewToken: args.previewToken,
|
|
190
|
+
});
|
|
191
|
+
return ok(res);
|
|
192
|
+
}
|
|
193
|
+
catch (e) {
|
|
194
|
+
const detail = findDetail(e, EvaluationLaunchRejectionV1Schema);
|
|
195
|
+
if (detail !== undefined) {
|
|
196
|
+
return err(toLaunchRejection(detail));
|
|
197
|
+
}
|
|
198
|
+
throw e;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// -- 4/5. externally-executed loop: lease -> renew -> submit -> release ----
|
|
202
|
+
/**
|
|
203
|
+
* Claim a bounded set of prepared cases. `err(LeaseRefusal)` when the server
|
|
204
|
+
* refuses on a 200 (`SCOPE_MISSING`, `RUN_NOT_EXECUTABLE`, …). `ok` otherwise
|
|
205
|
+
* — and an `ok` with no lease is not a failure: check `exhausted`.
|
|
206
|
+
*/
|
|
207
|
+
async leaseCases(args) {
|
|
208
|
+
requireNonEmpty("evaluationRunId", args.evaluationRunId);
|
|
209
|
+
requireNonEmpty("candidateKey", args.candidateKey);
|
|
210
|
+
requireNonEmpty("runtimeKey", args.runtimeKey);
|
|
211
|
+
const res = await this.raw.leaseEvaluationCases({
|
|
212
|
+
evaluationRunId: args.evaluationRunId,
|
|
213
|
+
candidateKey: args.candidateKey,
|
|
214
|
+
runtimeKey: args.runtimeKey,
|
|
215
|
+
maxCases: args.maxCases ?? 0,
|
|
216
|
+
leaseSeconds: args.leaseSeconds ?? 0,
|
|
217
|
+
});
|
|
218
|
+
if (res.refusal !== undefined) {
|
|
219
|
+
return err(toLeaseRefusal(res.refusal));
|
|
220
|
+
}
|
|
221
|
+
return ok({
|
|
222
|
+
lease: res.lease,
|
|
223
|
+
cases: res.cases,
|
|
224
|
+
remainingUnleasedCaseCount: res.remainingUnleasedCaseCount,
|
|
225
|
+
exhausted: res.lease === undefined && res.remainingUnleasedCaseCount === 0,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
/** Heartbeat a held lease. `err(LeaseRefusal)` when the fence was lost. */
|
|
229
|
+
async renewLease(args) {
|
|
230
|
+
requireNonEmpty("evaluationRunId", args.evaluationRunId);
|
|
231
|
+
requireNonEmpty("leaseId", args.leaseId);
|
|
232
|
+
requireNonEmpty("leaseToken", args.leaseToken);
|
|
233
|
+
const res = await this.raw.renewEvaluationCaseLease({
|
|
234
|
+
evaluationRunId: args.evaluationRunId,
|
|
235
|
+
leaseId: args.leaseId,
|
|
236
|
+
leaseToken: args.leaseToken,
|
|
237
|
+
leaseSeconds: args.leaseSeconds ?? 0,
|
|
238
|
+
});
|
|
239
|
+
if (res.refusal !== undefined) {
|
|
240
|
+
return err(toLeaseRefusal(res.refusal));
|
|
241
|
+
}
|
|
242
|
+
if (res.lease === undefined) {
|
|
243
|
+
throw new Error("renewEvaluationCaseLease: neither lease nor refusal was returned");
|
|
244
|
+
}
|
|
245
|
+
return ok(res.lease);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Submit a batch of case outputs — the recorded-output upload and the
|
|
249
|
+
* external-loop submit are the same verb. Returns a {@link SubmissionOutcome}
|
|
250
|
+
* that partitions the batch: which coordinates landed, which were duplicates,
|
|
251
|
+
* which were rejected and why — never an all-or-nothing throw. A whole-request
|
|
252
|
+
* refusal (fence lost) appears as `outcome.refusal`, with no per-case answers.
|
|
253
|
+
*
|
|
254
|
+
* Idempotent by lease identity and coordinate: a resubmit of the same batch
|
|
255
|
+
* reports the landed cases as `ALREADY_SUBMITTED`.
|
|
256
|
+
*/
|
|
257
|
+
async submitCaseOutputs(args) {
|
|
258
|
+
requireNonEmpty("evaluationRunId", args.evaluationRunId);
|
|
259
|
+
requireNonEmpty("leaseId", args.leaseId);
|
|
260
|
+
requireNonEmpty("leaseToken", args.leaseToken);
|
|
261
|
+
requireIdempotencyKey("idempotencyKey", args.idempotencyKey);
|
|
262
|
+
if (args.outputs.length === 0) {
|
|
263
|
+
requireNonEmpty("outputs", "");
|
|
264
|
+
}
|
|
265
|
+
const res = await this.raw.submitEvaluationCaseOutputs({
|
|
266
|
+
evaluationRunId: args.evaluationRunId,
|
|
267
|
+
leaseId: args.leaseId,
|
|
268
|
+
leaseToken: args.leaseToken,
|
|
269
|
+
outputs: args.outputs,
|
|
270
|
+
idempotencyKey: args.idempotencyKey,
|
|
271
|
+
});
|
|
272
|
+
const acks = res.acks.map(toCaseAck);
|
|
273
|
+
return {
|
|
274
|
+
acceptedCount: res.acceptedCount,
|
|
275
|
+
alreadySubmittedCount: res.alreadySubmittedCount,
|
|
276
|
+
rejectedCount: res.rejectedCount,
|
|
277
|
+
acks,
|
|
278
|
+
accepted: acks.filter((a) => a.kind === "ACCEPTED"),
|
|
279
|
+
alreadySubmitted: acks.filter((a) => a.kind === "ALREADY_SUBMITTED"),
|
|
280
|
+
rejected: acks.filter((a) => a.kind === "REJECTED"),
|
|
281
|
+
remainingLeasedCaseCount: res.remainingLeasedCaseCount,
|
|
282
|
+
refusal: res.refusal !== undefined ? toLeaseRefusal(res.refusal) : undefined,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
/** Release a held lease early. `err(LeaseRefusal)` when the fence was lost. */
|
|
286
|
+
async releaseLease(args) {
|
|
287
|
+
requireNonEmpty("evaluationRunId", args.evaluationRunId);
|
|
288
|
+
requireNonEmpty("leaseId", args.leaseId);
|
|
289
|
+
requireNonEmpty("leaseToken", args.leaseToken);
|
|
290
|
+
const res = await this.raw.releaseEvaluationCaseLease({
|
|
291
|
+
evaluationRunId: args.evaluationRunId,
|
|
292
|
+
leaseId: args.leaseId,
|
|
293
|
+
leaseToken: args.leaseToken,
|
|
294
|
+
});
|
|
295
|
+
if (res.refusal !== undefined) {
|
|
296
|
+
return err(toLeaseRefusal(res.refusal));
|
|
297
|
+
}
|
|
298
|
+
return ok(res.releasedCaseCount);
|
|
299
|
+
}
|
|
300
|
+
// -- 6. dataset drafts: create / append / publish -------------------------
|
|
301
|
+
/**
|
|
302
|
+
* Create a dataset version from EXACTLY ONE ingest source: raw JSONL bytes, or
|
|
303
|
+
* a finalized capture draft by id. `err(DatasetVersionRejection)` on a typed
|
|
304
|
+
* ingest refusal (rides in `Status.details`); any other error rethrows.
|
|
305
|
+
*/
|
|
306
|
+
async createDatasetVersion(args) {
|
|
307
|
+
requireIdempotencyKey("idempotencyKey", args.idempotencyKey);
|
|
308
|
+
requireExactlyOne("ingest", [
|
|
309
|
+
{ name: "jsonl", present: args.jsonl !== undefined },
|
|
310
|
+
{ name: "draftId", present: args.draftId !== undefined },
|
|
311
|
+
]);
|
|
312
|
+
const ingest = args.jsonl !== undefined
|
|
313
|
+
? { case: "jsonl", value: args.jsonl }
|
|
314
|
+
: { case: "draftId", value: args.draftId };
|
|
315
|
+
try {
|
|
316
|
+
const res = await this.raw.createEvaluationDatasetVersion({
|
|
317
|
+
...(args.datasetCollectionId !== undefined
|
|
318
|
+
? { datasetCollectionId: args.datasetCollectionId }
|
|
319
|
+
: {}),
|
|
320
|
+
...(args.collectionName !== undefined ? { collectionName: args.collectionName } : {}),
|
|
321
|
+
...(args.label !== undefined ? { label: args.label } : {}),
|
|
322
|
+
ingest,
|
|
323
|
+
...(args.recordedOutputFieldPath !== undefined
|
|
324
|
+
? { recordedOutputFieldPath: args.recordedOutputFieldPath }
|
|
325
|
+
: {}),
|
|
326
|
+
...(args.expectedDraftVersion !== undefined
|
|
327
|
+
? { expectedDraftVersion: args.expectedDraftVersion }
|
|
328
|
+
: {}),
|
|
329
|
+
idempotencyKey: args.idempotencyKey,
|
|
330
|
+
});
|
|
331
|
+
return ok(res);
|
|
332
|
+
}
|
|
333
|
+
catch (e) {
|
|
334
|
+
const detail = findDetail(e, EvaluationDatasetVersionRejectionV1Schema);
|
|
335
|
+
if (detail !== undefined) {
|
|
336
|
+
return err(toDatasetVersionRejection(detail));
|
|
337
|
+
}
|
|
338
|
+
throw e;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Append one case to a collection's open draft, from EXACTLY ONE source: a
|
|
343
|
+
* trace span or a matrix cell. `err(CaptureRefusal)` on a typed capture
|
|
344
|
+
* refusal (rides in `Status.details`).
|
|
345
|
+
*/
|
|
346
|
+
async captureCase(args) {
|
|
347
|
+
requireNonEmpty("datasetCollectionId", args.datasetCollectionId);
|
|
348
|
+
requireIdempotencyKey("idempotencyKey", args.idempotencyKey);
|
|
349
|
+
requireExactlyOne("source", [
|
|
350
|
+
{ name: "span", present: args.span !== undefined },
|
|
351
|
+
{ name: "cell", present: args.cell !== undefined },
|
|
352
|
+
]);
|
|
353
|
+
const source = args.span !== undefined
|
|
354
|
+
? { case: "span", value: args.span }
|
|
355
|
+
: { case: "cell", value: args.cell };
|
|
356
|
+
try {
|
|
357
|
+
const res = await this.raw.captureEvaluationCase({
|
|
358
|
+
datasetCollectionId: args.datasetCollectionId,
|
|
359
|
+
...(args.draftId !== undefined ? { draftId: args.draftId } : {}),
|
|
360
|
+
source,
|
|
361
|
+
fieldMappings: (args.fieldMappings ?? []),
|
|
362
|
+
...(args.recordedOutputFieldPath !== undefined
|
|
363
|
+
? { recordedOutputFieldPath: args.recordedOutputFieldPath }
|
|
364
|
+
: {}),
|
|
365
|
+
...(args.baseDatasetVersionId !== undefined
|
|
366
|
+
? { baseDatasetVersionId: args.baseDatasetVersionId }
|
|
367
|
+
: {}),
|
|
368
|
+
...(args.expectedContentDigest !== undefined
|
|
369
|
+
? { expectedContentDigest: args.expectedContentDigest }
|
|
370
|
+
: {}),
|
|
371
|
+
idempotencyKey: args.idempotencyKey,
|
|
372
|
+
});
|
|
373
|
+
return ok(res);
|
|
374
|
+
}
|
|
375
|
+
catch (e) {
|
|
376
|
+
const detail = findDetail(e, EvaluationCaptureRefusalV1Schema);
|
|
377
|
+
if (detail !== undefined) {
|
|
378
|
+
return err(toCaptureRefusal(detail));
|
|
379
|
+
}
|
|
380
|
+
throw e;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Publish a reviewed changeset. A digest mismatch is a FAILED_PRECONDITION
|
|
385
|
+
* transport error (never a silent rebase) and is thrown for `classify()`.
|
|
386
|
+
*/
|
|
387
|
+
async publishDatasetDrafts(args) {
|
|
388
|
+
requireNonEmpty("changesetId", args.changesetId);
|
|
389
|
+
requireNonEmpty("previewDigest", args.previewDigest);
|
|
390
|
+
requireIdempotencyKey("idempotencyKey", args.idempotencyKey);
|
|
391
|
+
return await this.raw.publishDatasetCaseDrafts({
|
|
392
|
+
changesetId: args.changesetId,
|
|
393
|
+
previewDigest: args.previewDigest,
|
|
394
|
+
...(args.label !== undefined ? { label: args.label } : {}),
|
|
395
|
+
idempotencyKey: args.idempotencyKey,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
// -- 7. annotations -------------------------------------------------------
|
|
399
|
+
/**
|
|
400
|
+
* Record a deployment marker, custom event, or time-range highlight.
|
|
401
|
+
* `err(AnnotationRejection)` on a typed rejection (rides in `Status.details`).
|
|
402
|
+
* A `startAt`/`endAt` are `Date`s; omit `endAt` for an instant.
|
|
403
|
+
*/
|
|
404
|
+
async recordAnnotation(args) {
|
|
405
|
+
requireNonEmpty("title", args.title);
|
|
406
|
+
requireIdempotencyKey("idempotencyKey", args.idempotencyKey);
|
|
407
|
+
rejectUnspecified("kind", [args.kind]);
|
|
408
|
+
try {
|
|
409
|
+
const res = await this.raw.recordPlatformAnnotation({
|
|
410
|
+
kind: args.kind,
|
|
411
|
+
title: args.title,
|
|
412
|
+
...(args.startAt !== undefined ? { startAt: timestampFromDate(args.startAt) } : {}),
|
|
413
|
+
...(args.endAt !== undefined ? { endAt: timestampFromDate(args.endAt) } : {}),
|
|
414
|
+
attributes: (args.attributes ?? []),
|
|
415
|
+
links: (args.links ?? []),
|
|
416
|
+
idempotencyKey: args.idempotencyKey,
|
|
417
|
+
});
|
|
418
|
+
return ok(res);
|
|
419
|
+
}
|
|
420
|
+
catch (e) {
|
|
421
|
+
const detail = findDetail(e, PlatformAnnotationRejectionV1Schema);
|
|
422
|
+
if (detail !== undefined) {
|
|
423
|
+
return err(toAnnotationRejection(detail));
|
|
424
|
+
}
|
|
425
|
+
throw e;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* List annotations overlapping a window. `resync`, when present, is a typed
|
|
430
|
+
* directive that the caller's cursor cannot be honoured and the listing must
|
|
431
|
+
* be restarted — it arrives with an empty page, never as a transport error.
|
|
432
|
+
*/
|
|
433
|
+
async listAnnotations(args) {
|
|
434
|
+
const windowStart = args.windowStart instanceof Date ? timestampFromDate(args.windowStart) : args.windowStart;
|
|
435
|
+
const windowEnd = args.windowEnd instanceof Date ? timestampFromDate(args.windowEnd) : args.windowEnd;
|
|
436
|
+
if (args.kinds !== undefined) {
|
|
437
|
+
rejectUnspecified("kinds", args.kinds);
|
|
438
|
+
}
|
|
439
|
+
const res = await this.raw.listPlatformAnnotations({
|
|
440
|
+
windowStart,
|
|
441
|
+
windowEnd,
|
|
442
|
+
kinds: (args.kinds ?? []),
|
|
443
|
+
page: {
|
|
444
|
+
...(args.limit !== undefined ? { limit: args.limit } : {}),
|
|
445
|
+
...(args.pageToken !== undefined ? { pageToken: args.pageToken } : {}),
|
|
446
|
+
},
|
|
447
|
+
});
|
|
448
|
+
return {
|
|
449
|
+
annotations: res.annotations,
|
|
450
|
+
nextPageToken: res.page?.nextPageToken,
|
|
451
|
+
hasMore: res.page?.hasMore ?? false,
|
|
452
|
+
resync: res.resync !== undefined ? toCursorResync(res.resync) : undefined,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@o11y-one/sdk` — hand-written client layer over the generated `@o11y-one/api-agentic`.
|
|
3
|
+
*
|
|
4
|
+
* The foundation:
|
|
5
|
+
* - transport construction (client.ts)
|
|
6
|
+
* - credential injection and its local validation (auth.ts)
|
|
7
|
+
* - the failure taxonomy an SDK exists to render (errors.ts)
|
|
8
|
+
*
|
|
9
|
+
* The agentic-evaluation journey surface (evaluation.ts) binds the code-first
|
|
10
|
+
* loop's verbs — identity, machine credentials, run submission, the
|
|
11
|
+
* externally-executed lease loop, dataset drafts, annotations — as thin, typed,
|
|
12
|
+
* validated wrappers. Its refusals are TYPED VALUES (refusals.ts) returned in a
|
|
13
|
+
* `Result` (result.ts), not thrown-away errors; its inputs are guarded
|
|
14
|
+
* synchronously (validate.ts) before a wire call. The generated client stays
|
|
15
|
+
* reachable for any verb the surface has not lifted.
|
|
16
|
+
*/
|
|
17
|
+
export { O11yClient, createTransport, credentialInterceptor, type ClientOptions, } from "./client.js";
|
|
18
|
+
export { assertLooksLikeMachineCredential, CREDENTIAL_HEADER, CREDENTIAL_MAX_LEN, MACHINE_CREDENTIAL_PREFIX, ORG_ID_HEADER, TENANT_ID_HEADER, type MachineCredential, } from "./auth.js";
|
|
19
|
+
export { classify, MACHINE_SCOPES, type ClassifiedFailure, type FailureDisposition, type MachineScope, } from "./errors.js";
|
|
20
|
+
export { AgenticEvaluationClient, OneTimeCredential, scopesToCanonical, type CallerIdentity, type LeaseOutcome, } from "./evaluation.js";
|
|
21
|
+
export { ok, err, isOk, isErr, unwrap, type Result, type Ok, type Err, } from "./result.js";
|
|
22
|
+
export { ValidationError, requireNonEmpty, requireIdempotencyKey, requireExactlyOne, rejectUnspecified, requireWithinBytes, byteLength, } from "./validate.js";
|
|
23
|
+
export { findDetail, toLeaseRefusal, toCaseAck, toLaunchRejection, toCaptureRefusal, toDatasetVersionRejection, toAnnotationRejection, toCursorResync, type RecoveryAction, type LeaseRefusal, type LeaseRefusalReason, type CaseAck, type CaseAckKind, type SubmissionOutcome, type LaunchRejection, type LaunchRejectionReason, type CaptureRefusal, type CaptureRefusalReason, type DatasetVersionRejection, type DatasetVersionRejectionReason, type AnnotationRejection, type AnnotationRejectionReason, type CursorResync, type CursorResyncReason, } from "./refusals.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@o11y-one/sdk` — hand-written client layer over the generated `@o11y-one/api-agentic`.
|
|
3
|
+
*
|
|
4
|
+
* The foundation:
|
|
5
|
+
* - transport construction (client.ts)
|
|
6
|
+
* - credential injection and its local validation (auth.ts)
|
|
7
|
+
* - the failure taxonomy an SDK exists to render (errors.ts)
|
|
8
|
+
*
|
|
9
|
+
* The agentic-evaluation journey surface (evaluation.ts) binds the code-first
|
|
10
|
+
* loop's verbs — identity, machine credentials, run submission, the
|
|
11
|
+
* externally-executed lease loop, dataset drafts, annotations — as thin, typed,
|
|
12
|
+
* validated wrappers. Its refusals are TYPED VALUES (refusals.ts) returned in a
|
|
13
|
+
* `Result` (result.ts), not thrown-away errors; its inputs are guarded
|
|
14
|
+
* synchronously (validate.ts) before a wire call. The generated client stays
|
|
15
|
+
* reachable for any verb the surface has not lifted.
|
|
16
|
+
*/
|
|
17
|
+
export { O11yClient, createTransport, credentialInterceptor, } from "./client.js";
|
|
18
|
+
export { assertLooksLikeMachineCredential, CREDENTIAL_HEADER, CREDENTIAL_MAX_LEN, MACHINE_CREDENTIAL_PREFIX, ORG_ID_HEADER, TENANT_ID_HEADER, } from "./auth.js";
|
|
19
|
+
export { classify, MACHINE_SCOPES, } from "./errors.js";
|
|
20
|
+
// --- the journey surface ----------------------------------------------------
|
|
21
|
+
export { AgenticEvaluationClient, OneTimeCredential, scopesToCanonical, } from "./evaluation.js";
|
|
22
|
+
export { ok, err, isOk, isErr, unwrap, } from "./result.js";
|
|
23
|
+
export { ValidationError, requireNonEmpty, requireIdempotencyKey, requireExactlyOne, rejectUnspecified, requireWithinBytes, byteLength, } from "./validate.js";
|
|
24
|
+
export { findDetail, toLeaseRefusal, toCaseAck, toLaunchRejection, toCaptureRefusal, toDatasetVersionRejection, toAnnotationRejection, toCursorResync, } from "./refusals.js";
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { DescMessage, MessageShape } from "@bufbuild/protobuf";
|
|
2
|
+
import { type ExternalLeaseRefusalV1, type EvaluationLaunchRejectionV1, type EvaluationCaptureRefusalV1, type EvaluationDatasetVersionRejectionV1, type PlatformAnnotationRejectionV1, type EvaluationCursorResyncV1, type ExternalCaseOutputAckV1 } from "@o11y-one/api-agentic/o11y_one/agentic/v1/evaluation_pb";
|
|
3
|
+
/** The one remedial action the server named, as a wire enum member name. */
|
|
4
|
+
export type RecoveryAction = "UNSPECIFIED" | "RETRY" | "RE_PREVIEW" | "EDIT_DEFINITION" | "WAIT" | "CONTACT_SUPPORT";
|
|
5
|
+
export type LeaseRefusalReason = "UNSPECIFIED" | "RUN_NOT_EXECUTABLE" | "CANDIDATE_NOT_EXTERNALLY_EXECUTED" | "LEASE_NOT_HELD" | "LEASE_EXPIRED" | "RENEWAL_BUDGET_EXHAUSTED" | "BOUNDS_EXCEEDED" | "SCOPE_MISSING";
|
|
6
|
+
export interface LeaseRefusal {
|
|
7
|
+
readonly type: "lease-refusal";
|
|
8
|
+
readonly reason: LeaseRefusalReason;
|
|
9
|
+
readonly message: string;
|
|
10
|
+
/** Set on `SCOPE_MISSING`: the canonical scope the credential lacks. */
|
|
11
|
+
readonly missingScope: string | undefined;
|
|
12
|
+
readonly recovery: RecoveryAction;
|
|
13
|
+
readonly raw: ExternalLeaseRefusalV1;
|
|
14
|
+
}
|
|
15
|
+
export declare function toLeaseRefusal(m: ExternalLeaseRefusalV1): LeaseRefusal;
|
|
16
|
+
export type CaseAckKind = "UNSPECIFIED" | "ACCEPTED" | "ALREADY_SUBMITTED" | "REJECTED";
|
|
17
|
+
export interface CaseAck {
|
|
18
|
+
readonly cohortKey: string;
|
|
19
|
+
readonly candidateKey: string;
|
|
20
|
+
readonly caseRevisionId: string;
|
|
21
|
+
readonly trial: number;
|
|
22
|
+
readonly kind: CaseAckKind;
|
|
23
|
+
/** The frozen execution state the server sees this coordinate in. */
|
|
24
|
+
readonly observedState: number;
|
|
25
|
+
readonly raw: ExternalCaseOutputAckV1;
|
|
26
|
+
}
|
|
27
|
+
export declare function toCaseAck(m: ExternalCaseOutputAckV1): CaseAck;
|
|
28
|
+
/**
|
|
29
|
+
* The partial-batch outcome of a `submitCaseOutputs`. This is not an error even
|
|
30
|
+
* when `rejected` is non-empty: the accepted cases DID land, and the caller
|
|
31
|
+
* resubmits only the rejected coordinates. `refusal`, when present, is a
|
|
32
|
+
* WHOLE-request refusal (the fence was lost) and no per-case answer is
|
|
33
|
+
* meaningful — the two are never both set.
|
|
34
|
+
*/
|
|
35
|
+
export interface SubmissionOutcome {
|
|
36
|
+
readonly acceptedCount: number;
|
|
37
|
+
readonly alreadySubmittedCount: number;
|
|
38
|
+
readonly rejectedCount: number;
|
|
39
|
+
readonly acks: readonly CaseAck[];
|
|
40
|
+
readonly accepted: readonly CaseAck[];
|
|
41
|
+
readonly alreadySubmitted: readonly CaseAck[];
|
|
42
|
+
readonly rejected: readonly CaseAck[];
|
|
43
|
+
readonly remainingLeasedCaseCount: number;
|
|
44
|
+
/** Whole-request refusal; set only when the lease fence was lost. */
|
|
45
|
+
readonly refusal: LeaseRefusal | undefined;
|
|
46
|
+
}
|
|
47
|
+
export type LaunchRejectionReason = "UNSPECIFIED" | "PREVIEW_EXPIRED" | "PREVIEW_MALFORMED" | "DEFINITION_REVISION_MOVED" | "DEPENDENCY_VERSIONS_MOVED" | "CARDINALITY_MOVED" | "CAPABILITY_SET_MOVED" | "IDEMPOTENCY_KEY_REUSED";
|
|
48
|
+
export interface LaunchRejection {
|
|
49
|
+
readonly type: "launch-rejection";
|
|
50
|
+
readonly reason: LaunchRejectionReason;
|
|
51
|
+
readonly expectedDigest: string;
|
|
52
|
+
readonly observedDigest: string;
|
|
53
|
+
/** Set on `IDEMPOTENCY_KEY_REUSED`: the run the key already named. */
|
|
54
|
+
readonly existingEvaluationRunId: string | undefined;
|
|
55
|
+
readonly recovery: RecoveryAction;
|
|
56
|
+
readonly detail: string;
|
|
57
|
+
readonly raw: EvaluationLaunchRejectionV1;
|
|
58
|
+
}
|
|
59
|
+
export declare function toLaunchRejection(m: EvaluationLaunchRejectionV1): LaunchRejection;
|
|
60
|
+
export type CaptureRefusalReason = "UNSPECIFIED" | "SOURCE_NOT_FOUND" | "SOURCE_PLANE_NOT_SHIPPED" | "REQUIRED_FIELD_UNMAPPED" | "MAPPING_RESOLVED_NOTHING" | string;
|
|
61
|
+
export interface CaptureRefusal {
|
|
62
|
+
readonly type: "capture-refusal";
|
|
63
|
+
readonly reason: CaptureRefusalReason;
|
|
64
|
+
readonly detail: string;
|
|
65
|
+
readonly missingTargetPaths: readonly string[];
|
|
66
|
+
readonly recovery: RecoveryAction;
|
|
67
|
+
/** Set on `IDEMPOTENCY_KEY_REUSED`: the case the key already captured. */
|
|
68
|
+
readonly existingProposedCaseId: string | undefined;
|
|
69
|
+
readonly raw: EvaluationCaptureRefusalV1;
|
|
70
|
+
}
|
|
71
|
+
export declare function toCaptureRefusal(m: EvaluationCaptureRefusalV1): CaptureRefusal;
|
|
72
|
+
export type DatasetVersionRejectionReason = "UNSPECIFIED" | "COLLECTION_CAP_EXCEEDED" | "VERSION_CAP_ALL_PINNED" | "MALFORMED_JSONL_LINE" | "EMPTY_INGEST" | "INGEST_TOO_LARGE" | "DRAFT_NOT_FINALIZABLE" | "INGEST_BYTES_EXCEEDED" | "INGEST_CASE_COUNT_EXCEEDED" | "DRAFT_HAS_UNAPPROVED_CASES";
|
|
73
|
+
export interface DatasetVersionRejection {
|
|
74
|
+
readonly type: "dataset-version-rejection";
|
|
75
|
+
readonly reason: DatasetVersionRejectionReason;
|
|
76
|
+
/** The server's stable `reason_code` string, carried alongside the kind. */
|
|
77
|
+
readonly reasonCode: string;
|
|
78
|
+
/** 1-based, set only on `MALFORMED_JSONL_LINE`. */
|
|
79
|
+
readonly lineNumber: number | undefined;
|
|
80
|
+
readonly observedCount: number;
|
|
81
|
+
readonly maxCount: number;
|
|
82
|
+
readonly detail: string;
|
|
83
|
+
readonly recovery: RecoveryAction;
|
|
84
|
+
readonly raw: EvaluationDatasetVersionRejectionV1;
|
|
85
|
+
}
|
|
86
|
+
export declare function toDatasetVersionRejection(m: EvaluationDatasetVersionRejectionV1): DatasetVersionRejection;
|
|
87
|
+
export type AnnotationRejectionReason = "UNSPECIFIED" | "KIND_UNKNOWN" | "TITLE_TOO_LARGE" | "ATTRIBUTE_TOO_LARGE" | "TOO_MANY_ATTRIBUTES" | "TOO_MANY_LINKS" | "LINK_REF_TOO_LARGE" | "RANGE_INVERTED" | "IDEMPOTENCY_KEY_REUSED" | "WINDOW_INVALID";
|
|
88
|
+
export interface AnnotationRejection {
|
|
89
|
+
readonly type: "annotation-rejection";
|
|
90
|
+
readonly reason: AnnotationRejectionReason;
|
|
91
|
+
/** The field the server refused, when it named one. */
|
|
92
|
+
readonly field: string | undefined;
|
|
93
|
+
/** The bound that was exceeded, when the reason is a size/count cap. */
|
|
94
|
+
readonly limitValue: bigint | undefined;
|
|
95
|
+
/** On `KIND_UNKNOWN`: the kinds this server DOES support, as wire names. */
|
|
96
|
+
readonly supportedKinds: readonly string[];
|
|
97
|
+
readonly raw: PlatformAnnotationRejectionV1;
|
|
98
|
+
}
|
|
99
|
+
export declare function toAnnotationRejection(m: PlatformAnnotationRejectionV1): AnnotationRejection;
|
|
100
|
+
export type CursorResyncReason = "UNSPECIFIED" | "SNAPSHOT_SUPERSEDED" | "FILTER_CHANGED" | "CURSOR_MALFORMED" | "BACKLOG_EXCEEDED";
|
|
101
|
+
export interface CursorResync {
|
|
102
|
+
readonly type: "cursor-resync";
|
|
103
|
+
readonly reason: CursorResyncReason;
|
|
104
|
+
readonly raw: EvaluationCursorResyncV1;
|
|
105
|
+
}
|
|
106
|
+
export declare function toCursorResync(m: EvaluationCursorResyncV1): CursorResync;
|
|
107
|
+
/**
|
|
108
|
+
* Pull the first typed detail of a given schema off a thrown error.
|
|
109
|
+
*
|
|
110
|
+
* This is how a refusal that rides in `google.rpc.Status.details` (launch,
|
|
111
|
+
* capture, dataset-version, annotation) is recovered: connect-es already
|
|
112
|
+
* decodes `Status.details`, and `ConnectError.findDetails(schema)` hands back
|
|
113
|
+
* the typed messages. A non-Connect error, or a Connect error with no such
|
|
114
|
+
* detail, returns `undefined` and the caller rethrows.
|
|
115
|
+
*/
|
|
116
|
+
export declare function findDetail<Desc extends DescMessage>(error: unknown, schema: Desc): MessageShape<Desc> | undefined;
|