@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.
@@ -0,0 +1,125 @@
1
+ /**
2
+ * The typed-refusal vocabulary.
3
+ *
4
+ * The generated messages carry refusal *kinds* as numeric proto enums. A caller
5
+ * branching on a bare number is a caller reading a magic constant, so this
6
+ * module lifts each one to its WIRE ENUM MEMBER NAME — the SCREAMING_SNAKE
7
+ * string the proto itself declares (`LEASE_EXPIRED`, `PREVIEW_MALFORMED`, …).
8
+ * No name is invented here: the reverse map of the generated `enum` is the
9
+ * single source, so this SDK and the Python SDK that mirrors it cannot drift
10
+ * apart or drift from the server.
11
+ *
12
+ * Each refusal is a small, flat, discriminated record: a `type` naming the
13
+ * refusal FAMILY (which verb refused), a `reason` naming the wire kind WITHIN
14
+ * that family, the structured fields the server attached, and `raw` — the
15
+ * untouched generated message — for the caller who needs a field this shape
16
+ * did not lift.
17
+ */
18
+ import { ConnectError } from "@connectrpc/connect";
19
+ import { ExternalLeaseRefusalKindV1, ExternalSubmissionAckKindV1, EvaluationLaunchRejectionKindV1, EvaluationCaptureRefusalKindV1, EvaluationDatasetVersionRejectionKindV1, EvaluationResyncReasonV1, PlatformAnnotationRejectionReasonV1, PlatformAnnotationKindV1, RecoveryActionV1, } from "@o11y-one/api-agentic/o11y_one/agentic/v1/evaluation_pb";
20
+ /**
21
+ * The member name of a numeric proto enum value, e.g.
22
+ * `enumName(ExternalLeaseRefusalKindV1, 4)` → `"LEASE_EXPIRED"`. Unknown or
23
+ * absent values (a discriminant added server-side after this SDK was built)
24
+ * fold to `"UNSPECIFIED"` rather than a raw number, so a newer server never
25
+ * makes an older client emit a bare integer.
26
+ */
27
+ function enumName(enumObj, value) {
28
+ return enumObj[value] ?? "UNSPECIFIED";
29
+ }
30
+ function recoveryName(value) {
31
+ if (value === undefined) {
32
+ return "UNSPECIFIED";
33
+ }
34
+ return enumName(RecoveryActionV1, value);
35
+ }
36
+ export function toLeaseRefusal(m) {
37
+ return {
38
+ type: "lease-refusal",
39
+ reason: enumName(ExternalLeaseRefusalKindV1, m.kind),
40
+ message: m.message,
41
+ missingScope: m.missingScope,
42
+ recovery: recoveryName(m.recovery),
43
+ raw: m,
44
+ };
45
+ }
46
+ export function toCaseAck(m) {
47
+ return {
48
+ cohortKey: m.cohortKey,
49
+ candidateKey: m.candidateKey,
50
+ caseRevisionId: m.caseRevisionId,
51
+ trial: m.trial,
52
+ kind: enumName(ExternalSubmissionAckKindV1, m.kind),
53
+ observedState: m.observedState,
54
+ raw: m,
55
+ };
56
+ }
57
+ export function toLaunchRejection(m) {
58
+ return {
59
+ type: "launch-rejection",
60
+ reason: enumName(EvaluationLaunchRejectionKindV1, m.kind),
61
+ expectedDigest: m.expectedDigest,
62
+ observedDigest: m.observedDigest,
63
+ existingEvaluationRunId: m.existingEvaluationRunId,
64
+ recovery: recoveryName(m.recovery),
65
+ detail: m.detail,
66
+ raw: m,
67
+ };
68
+ }
69
+ export function toCaptureRefusal(m) {
70
+ return {
71
+ type: "capture-refusal",
72
+ reason: enumName(EvaluationCaptureRefusalKindV1, m.kind),
73
+ detail: m.detail,
74
+ missingTargetPaths: m.missingTargetPaths,
75
+ recovery: recoveryName(m.recovery),
76
+ existingProposedCaseId: m.existingProposedCaseId,
77
+ raw: m,
78
+ };
79
+ }
80
+ export function toDatasetVersionRejection(m) {
81
+ return {
82
+ type: "dataset-version-rejection",
83
+ reason: enumName(EvaluationDatasetVersionRejectionKindV1, m.kind),
84
+ reasonCode: m.reasonCode,
85
+ lineNumber: m.lineNumber,
86
+ observedCount: m.observedCount,
87
+ maxCount: m.maxCount,
88
+ detail: m.detail,
89
+ recovery: recoveryName(m.recovery),
90
+ raw: m,
91
+ };
92
+ }
93
+ export function toAnnotationRejection(m) {
94
+ return {
95
+ type: "annotation-rejection",
96
+ reason: enumName(PlatformAnnotationRejectionReasonV1, m.reason),
97
+ field: m.field,
98
+ limitValue: m.limitValue,
99
+ supportedKinds: m.supportedKinds.map((k) => enumName(PlatformAnnotationKindV1, k)),
100
+ raw: m,
101
+ };
102
+ }
103
+ export function toCursorResync(m) {
104
+ return {
105
+ type: "cursor-resync",
106
+ reason: enumName(EvaluationResyncReasonV1, m.reason),
107
+ raw: m,
108
+ };
109
+ }
110
+ /**
111
+ * Pull the first typed detail of a given schema off a thrown error.
112
+ *
113
+ * This is how a refusal that rides in `google.rpc.Status.details` (launch,
114
+ * capture, dataset-version, annotation) is recovered: connect-es already
115
+ * decodes `Status.details`, and `ConnectError.findDetails(schema)` hands back
116
+ * the typed messages. A non-Connect error, or a Connect error with no such
117
+ * detail, returns `undefined` and the caller rethrows.
118
+ */
119
+ export function findDetail(error, schema) {
120
+ if (!(error instanceof ConnectError)) {
121
+ return undefined;
122
+ }
123
+ const details = error.findDetails(schema);
124
+ return details.length > 0 ? details[0] : undefined;
125
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * A `Result<T, E>` discriminated union.
3
+ *
4
+ * This is the SDK's answer to the compendium's key shape: many verbs on the
5
+ * agentic-evaluation surface refuse ON A 200 — the refusal is a field on the
6
+ * response message, not a transport error. Throwing on it would erase a typed,
7
+ * structured, actionable value and hand the caller a stack trace instead. So
8
+ * the journey methods that can be refused this way return a `Result`: `ok` on
9
+ * acceptance, `err` carrying a TYPED refusal the caller branches on.
10
+ *
11
+ * Errors that arrive as a transport failure (a real non-200 with a
12
+ * `google.rpc.Status`) are a different axis and still throw a `ConnectError` —
13
+ * `classify()` in errors.ts renders those. A method whose refusal rides in
14
+ * `Status.details` (launch, annotation, dataset-version) catches that throw and
15
+ * folds it back into an `err(...)` so the caller has ONE branch to write, not
16
+ * two. Which methods do which is documented on each.
17
+ */
18
+ /** The acceptance arm. */
19
+ export interface Ok<T> {
20
+ readonly ok: true;
21
+ readonly value: T;
22
+ }
23
+ /** The refusal arm. `refusal` is always one of the typed unions in refusals.ts. */
24
+ export interface Err<E> {
25
+ readonly ok: false;
26
+ readonly refusal: E;
27
+ }
28
+ export type Result<T, E> = Ok<T> | Err<E>;
29
+ export declare function ok<T>(value: T): Ok<T>;
30
+ export declare function err<E>(refusal: E): Err<E>;
31
+ export declare function isOk<T, E>(r: Result<T, E>): r is Ok<T>;
32
+ export declare function isErr<T, E>(r: Result<T, E>): r is Err<E>;
33
+ /**
34
+ * Unwrap or throw. A convenience for scripts and tests that WANT the throw —
35
+ * the library itself never calls this. The thrown error names the refusal so a
36
+ * stack trace is still legible.
37
+ */
38
+ export declare function unwrap<T, E>(r: Result<T, E>): T;
package/dist/result.js ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * A `Result<T, E>` discriminated union.
3
+ *
4
+ * This is the SDK's answer to the compendium's key shape: many verbs on the
5
+ * agentic-evaluation surface refuse ON A 200 — the refusal is a field on the
6
+ * response message, not a transport error. Throwing on it would erase a typed,
7
+ * structured, actionable value and hand the caller a stack trace instead. So
8
+ * the journey methods that can be refused this way return a `Result`: `ok` on
9
+ * acceptance, `err` carrying a TYPED refusal the caller branches on.
10
+ *
11
+ * Errors that arrive as a transport failure (a real non-200 with a
12
+ * `google.rpc.Status`) are a different axis and still throw a `ConnectError` —
13
+ * `classify()` in errors.ts renders those. A method whose refusal rides in
14
+ * `Status.details` (launch, annotation, dataset-version) catches that throw and
15
+ * folds it back into an `err(...)` so the caller has ONE branch to write, not
16
+ * two. Which methods do which is documented on each.
17
+ */
18
+ export function ok(value) {
19
+ return { ok: true, value };
20
+ }
21
+ export function err(refusal) {
22
+ return { ok: false, refusal };
23
+ }
24
+ export function isOk(r) {
25
+ return r.ok;
26
+ }
27
+ export function isErr(r) {
28
+ return !r.ok;
29
+ }
30
+ /**
31
+ * Unwrap or throw. A convenience for scripts and tests that WANT the throw —
32
+ * the library itself never calls this. The thrown error names the refusal so a
33
+ * stack trace is still legible.
34
+ */
35
+ export function unwrap(r) {
36
+ if (r.ok) {
37
+ return r.value;
38
+ }
39
+ throw new Error(`o11y call was refused: ${JSON.stringify(r.refusal)}`);
40
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Client-side input validation — the SDK's contract-guard.
3
+ *
4
+ * It fails FAST, SYNCHRONOUSLY, and TYPED, before a wire call is made, on the
5
+ * mistakes the server would otherwise turn into an opaque `INVALID_ARGUMENT` a
6
+ * round trip later: a missing required field, both arms of a mutually-exclusive
7
+ * oneof set at once (or neither), an `UNSPECIFIED` enum discriminant the server
8
+ * refuses, an idempotency key a CI retry forgot to mint.
9
+ *
10
+ * It is NOT the server's validation and never duplicates the authoritative
11
+ * byte-caps — those are published on the capability envelope and enforced
12
+ * server-side, and this layer surfaces the resulting typed rejection rather
13
+ * than second-guessing the number. What it checks is STRUCTURE the SDK can be
14
+ * certain of without the network.
15
+ */
16
+ /**
17
+ * A local, pre-flight validation failure. `field` names the offending input in
18
+ * the caller's terms so the message is actionable without reading the proto.
19
+ */
20
+ export declare class ValidationError extends Error {
21
+ readonly field: string;
22
+ constructor(field: string, message: string);
23
+ }
24
+ /** Trim-aware non-empty string check. */
25
+ export declare function requireNonEmpty(field: string, value: string | undefined): string;
26
+ /**
27
+ * An idempotency key the caller must supply. Required on every write that a CI
28
+ * job could retry — a retry that forgot its key is exactly how a run, a case,
29
+ * or a marker gets created twice.
30
+ */
31
+ export declare function requireIdempotencyKey(field: string, value: string | undefined): string;
32
+ /**
33
+ * Exactly-one-of, for a proto `oneof` the SDK exposes as separate arguments.
34
+ * `entries` lists each arm and whether the caller supplied it; zero or two-plus
35
+ * present is a `ValidationError` naming what was seen.
36
+ */
37
+ export declare function requireExactlyOne(label: string, entries: ReadonlyArray<{
38
+ readonly name: string;
39
+ readonly present: boolean;
40
+ }>): void;
41
+ /**
42
+ * Reject an `UNSPECIFIED` (zero) discriminant in a set the server refuses
43
+ * outright. An `UNSPECIFIED` scope on a principal-create, or inside a list
44
+ * filter, is `INVALID_ARGUMENT` server-side; catching it here names the
45
+ * position instead of the whole call.
46
+ */
47
+ export declare function rejectUnspecified(field: string, values: readonly number[]): void;
48
+ /**
49
+ * UTF-8 byte length, for the size guards a caller can opt into. Computed
50
+ * without `TextEncoder` so this module pulls in neither the DOM lib nor
51
+ * `@types/node` — it runs identically in a browser, a Worker and Node.
52
+ */
53
+ export declare function byteLength(value: string): number;
54
+ /**
55
+ * An optional upper byte-bound. Used only where the SDK holds a published limit
56
+ * (e.g. the 128-byte credential cap in auth.ts); never with an invented number.
57
+ */
58
+ export declare function requireWithinBytes(field: string, value: string, maxBytes: number): string;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Client-side input validation — the SDK's contract-guard.
3
+ *
4
+ * It fails FAST, SYNCHRONOUSLY, and TYPED, before a wire call is made, on the
5
+ * mistakes the server would otherwise turn into an opaque `INVALID_ARGUMENT` a
6
+ * round trip later: a missing required field, both arms of a mutually-exclusive
7
+ * oneof set at once (or neither), an `UNSPECIFIED` enum discriminant the server
8
+ * refuses, an idempotency key a CI retry forgot to mint.
9
+ *
10
+ * It is NOT the server's validation and never duplicates the authoritative
11
+ * byte-caps — those are published on the capability envelope and enforced
12
+ * server-side, and this layer surfaces the resulting typed rejection rather
13
+ * than second-guessing the number. What it checks is STRUCTURE the SDK can be
14
+ * certain of without the network.
15
+ */
16
+ /**
17
+ * A local, pre-flight validation failure. `field` names the offending input in
18
+ * the caller's terms so the message is actionable without reading the proto.
19
+ */
20
+ export class ValidationError extends Error {
21
+ field;
22
+ constructor(field, message) {
23
+ super(message);
24
+ this.name = "ValidationError";
25
+ this.field = field;
26
+ }
27
+ }
28
+ /** Trim-aware non-empty string check. */
29
+ export function requireNonEmpty(field, value) {
30
+ if (value === undefined || value.trim().length === 0) {
31
+ throw new ValidationError(field, `${field} is required and must not be empty`);
32
+ }
33
+ return value;
34
+ }
35
+ /**
36
+ * An idempotency key the caller must supply. Required on every write that a CI
37
+ * job could retry — a retry that forgot its key is exactly how a run, a case,
38
+ * or a marker gets created twice.
39
+ */
40
+ export function requireIdempotencyKey(field, value) {
41
+ if (value === undefined || value.trim().length === 0) {
42
+ throw new ValidationError(field, `${field} is required: a retry-safe write needs a caller-minted idempotency key`);
43
+ }
44
+ return value;
45
+ }
46
+ /**
47
+ * Exactly-one-of, for a proto `oneof` the SDK exposes as separate arguments.
48
+ * `entries` lists each arm and whether the caller supplied it; zero or two-plus
49
+ * present is a `ValidationError` naming what was seen.
50
+ */
51
+ export function requireExactlyOne(label, entries) {
52
+ const present = entries.filter((e) => e.present).map((e) => e.name);
53
+ if (present.length === 1) {
54
+ return;
55
+ }
56
+ const names = entries.map((e) => e.name).join(", ");
57
+ if (present.length === 0) {
58
+ throw new ValidationError(label, `exactly one of {${names}} is required — none was supplied`);
59
+ }
60
+ throw new ValidationError(label, `exactly one of {${names}} is allowed — got {${present.join(", ")}}`);
61
+ }
62
+ /**
63
+ * Reject an `UNSPECIFIED` (zero) discriminant in a set the server refuses
64
+ * outright. An `UNSPECIFIED` scope on a principal-create, or inside a list
65
+ * filter, is `INVALID_ARGUMENT` server-side; catching it here names the
66
+ * position instead of the whole call.
67
+ */
68
+ export function rejectUnspecified(field, values) {
69
+ const at = values.indexOf(0);
70
+ if (at !== -1) {
71
+ throw new ValidationError(field, `${field}[${at}] is UNSPECIFIED (0); the server refuses an unspecified discriminant`);
72
+ }
73
+ }
74
+ /**
75
+ * UTF-8 byte length, for the size guards a caller can opt into. Computed
76
+ * without `TextEncoder` so this module pulls in neither the DOM lib nor
77
+ * `@types/node` — it runs identically in a browser, a Worker and Node.
78
+ */
79
+ export function byteLength(value) {
80
+ let bytes = 0;
81
+ for (let i = 0; i < value.length; i++) {
82
+ const code = value.codePointAt(i);
83
+ if (code === undefined) {
84
+ continue;
85
+ }
86
+ if (code > 0xffff) {
87
+ i++; // a surrogate pair spans two UTF-16 code units
88
+ }
89
+ bytes += code <= 0x7f ? 1 : code <= 0x7ff ? 2 : code <= 0xffff ? 3 : 4;
90
+ }
91
+ return bytes;
92
+ }
93
+ /**
94
+ * An optional upper byte-bound. Used only where the SDK holds a published limit
95
+ * (e.g. the 128-byte credential cap in auth.ts); never with an invented number.
96
+ */
97
+ export function requireWithinBytes(field, value, maxBytes) {
98
+ const len = byteLength(value);
99
+ if (len > maxBytes) {
100
+ throw new ValidationError(field, `${field} is ${len} bytes, over the ${maxBytes}-byte limit`);
101
+ }
102
+ return value;
103
+ }
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@o11y-one/sdk",
3
+ "version": "0.0.0",
4
+ "description": "Hand-written client wrapper over @o11y-one/api-agentic: transport construction, credential injection, and the O11y One error taxonomy.",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "dependencies": {
18
+ "@bufbuild/protobuf": "^2.15.0",
19
+ "@connectrpc/connect": "^2.2.0",
20
+ "@connectrpc/connect-node": "^2.2.0",
21
+ "@connectrpc/connect-web": "^2.2.0",
22
+ "@o11y-one/api-agentic": "0.0.0"
23
+ },
24
+ "devDependencies": {
25
+ "typescript": "^7.0.2"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public",
29
+ "provenance": true
30
+ },
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/o11y-one/o11y-one-sdk.git",
34
+ "directory": "packages/sdk-ts"
35
+ },
36
+ "scripts": {
37
+ "build": "tsc -p tsconfig.json",
38
+ "typecheck": "tsc -p tsconfig.json --noEmit",
39
+ "test": "node --test test/*.test.js",
40
+ "clean": "rm -rf dist *.tsbuildinfo"
41
+ }
42
+ }