@airprompter/agent-core 0.1.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/README.md +28 -0
- package/dist/cjs/bundle/apbundle.js +98 -0
- package/dist/cjs/bundle/apbundle.js.map +1 -0
- package/dist/cjs/bundle/hpke.js +120 -0
- package/dist/cjs/bundle/hpke.js.map +1 -0
- package/dist/cjs/checks/index.js +450 -0
- package/dist/cjs/checks/index.js.map +1 -0
- package/dist/cjs/control/client.js +85 -0
- package/dist/cjs/control/client.js.map +1 -0
- package/dist/cjs/golden/index.js +138 -0
- package/dist/cjs/golden/index.js.map +1 -0
- package/dist/cjs/index.js +125 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/judge/index.js +134 -0
- package/dist/cjs/judge/index.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/ports/node.js +30 -0
- package/dist/cjs/ports/node.js.map +1 -0
- package/dist/cjs/protocol/assignment.js +158 -0
- package/dist/cjs/protocol/assignment.js.map +1 -0
- package/dist/cjs/protocol/canonicalJson.js +72 -0
- package/dist/cjs/protocol/canonicalJson.js.map +1 -0
- package/dist/cjs/protocol/errors.js +22 -0
- package/dist/cjs/protocol/errors.js.map +1 -0
- package/dist/cjs/protocol/ports.js +19 -0
- package/dist/cjs/protocol/ports.js.map +1 -0
- package/dist/cjs/protocol/trust.js +225 -0
- package/dist/cjs/protocol/trust.js.map +1 -0
- package/dist/cjs/protocol/types.js +15 -0
- package/dist/cjs/protocol/types.js.map +1 -0
- package/dist/cjs/protocol/version.js +8 -0
- package/dist/cjs/protocol/version.js.map +1 -0
- package/dist/cjs/release/bundleRelease.js +61 -0
- package/dist/cjs/release/bundleRelease.js.map +1 -0
- package/dist/cjs/release/reader.js +12 -0
- package/dist/cjs/release/reader.js.map +1 -0
- package/dist/cjs/render/runRef.js +33 -0
- package/dist/cjs/render/runRef.js.map +1 -0
- package/dist/cjs/render/template.js +71 -0
- package/dist/cjs/render/template.js.map +1 -0
- package/dist/cjs/telemetry/feedback.js +81 -0
- package/dist/cjs/telemetry/feedback.js.map +1 -0
- package/dist/cjs/telemetry/rows.js +28 -0
- package/dist/cjs/telemetry/rows.js.map +1 -0
- package/dist/cjs/telemetry/uploadSink.js +13 -0
- package/dist/cjs/telemetry/uploadSink.js.map +1 -0
- package/dist/cjs/testing/controlPlane.js +381 -0
- package/dist/cjs/testing/controlPlane.js.map +1 -0
- package/dist/cjs/testing/index.js +45 -0
- package/dist/cjs/testing/index.js.map +1 -0
- package/dist/cjs/testing/memoryFs.js +236 -0
- package/dist/cjs/testing/memoryFs.js.map +1 -0
- package/dist/esm/.tsbuildinfo +1 -0
- package/dist/esm/bundle/apbundle.d.ts +33 -0
- package/dist/esm/bundle/apbundle.js +89 -0
- package/dist/esm/bundle/apbundle.js.map +1 -0
- package/dist/esm/bundle/hpke.d.ts +48 -0
- package/dist/esm/bundle/hpke.js +107 -0
- package/dist/esm/bundle/hpke.js.map +1 -0
- package/dist/esm/checks/index.d.ts +85 -0
- package/dist/esm/checks/index.js +436 -0
- package/dist/esm/checks/index.js.map +1 -0
- package/dist/esm/control/client.d.ts +78 -0
- package/dist/esm/control/client.js +81 -0
- package/dist/esm/control/client.js.map +1 -0
- package/dist/esm/golden/index.d.ts +97 -0
- package/dist/esm/golden/index.js +130 -0
- package/dist/esm/golden/index.js.map +1 -0
- package/dist/esm/index.d.ts +41 -0
- package/dist/esm/index.js +31 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/judge/index.d.ts +51 -0
- package/dist/esm/judge/index.js +127 -0
- package/dist/esm/judge/index.js.map +1 -0
- package/dist/esm/ports/node.d.ts +8 -0
- package/dist/esm/ports/node.js +27 -0
- package/dist/esm/ports/node.js.map +1 -0
- package/dist/esm/protocol/assignment.d.ts +49 -0
- package/dist/esm/protocol/assignment.js +143 -0
- package/dist/esm/protocol/assignment.js.map +1 -0
- package/dist/esm/protocol/canonicalJson.d.ts +10 -0
- package/dist/esm/protocol/canonicalJson.js +65 -0
- package/dist/esm/protocol/canonicalJson.js.map +1 -0
- package/dist/esm/protocol/errors.d.ts +15 -0
- package/dist/esm/protocol/errors.js +19 -0
- package/dist/esm/protocol/errors.js.map +1 -0
- package/dist/esm/protocol/ports.d.ts +65 -0
- package/dist/esm/protocol/ports.js +16 -0
- package/dist/esm/protocol/ports.js.map +1 -0
- package/dist/esm/protocol/trust.d.ts +60 -0
- package/dist/esm/protocol/trust.js +211 -0
- package/dist/esm/protocol/trust.js.map +1 -0
- package/dist/esm/protocol/types.d.ts +235 -0
- package/dist/esm/protocol/types.js +12 -0
- package/dist/esm/protocol/types.js.map +1 -0
- package/dist/esm/protocol/version.d.ts +4 -0
- package/dist/esm/protocol/version.js +5 -0
- package/dist/esm/protocol/version.js.map +1 -0
- package/dist/esm/release/bundleRelease.d.ts +45 -0
- package/dist/esm/release/bundleRelease.js +57 -0
- package/dist/esm/release/bundleRelease.js.map +1 -0
- package/dist/esm/release/reader.d.ts +30 -0
- package/dist/esm/release/reader.js +11 -0
- package/dist/esm/release/reader.js.map +1 -0
- package/dist/esm/render/runRef.d.ts +17 -0
- package/dist/esm/render/runRef.js +29 -0
- package/dist/esm/render/runRef.js.map +1 -0
- package/dist/esm/render/template.d.ts +38 -0
- package/dist/esm/render/template.js +65 -0
- package/dist/esm/render/template.js.map +1 -0
- package/dist/esm/telemetry/feedback.d.ts +21 -0
- package/dist/esm/telemetry/feedback.js +77 -0
- package/dist/esm/telemetry/feedback.js.map +1 -0
- package/dist/esm/telemetry/rows.d.ts +85 -0
- package/dist/esm/telemetry/rows.js +22 -0
- package/dist/esm/telemetry/rows.js.map +1 -0
- package/dist/esm/telemetry/uploadSink.d.ts +52 -0
- package/dist/esm/telemetry/uploadSink.js +12 -0
- package/dist/esm/telemetry/uploadSink.js.map +1 -0
- package/dist/esm/testing/controlPlane.d.ts +116 -0
- package/dist/esm/testing/controlPlane.js +341 -0
- package/dist/esm/testing/controlPlane.js.map +1 -0
- package/dist/esm/testing/index.d.ts +25 -0
- package/dist/esm/testing/index.js +35 -0
- package/dist/esm/testing/index.js.map +1 -0
- package/dist/esm/testing/memoryFs.d.ts +64 -0
- package/dist/esm/testing/memoryFs.js +232 -0
- package/dist/esm/testing/memoryFs.js.map +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundleRelease.js","sourceRoot":"","sources":["../../../src/release/bundleRelease.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAwB,MAAM,uBAAuB,CAAC;AAC7F,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAkB1E,MAAM,OAAO,aAAa;IAGL;IAER;IACA;IALF,IAAI,GAAG,QAAiB,CAAC;IAClC,YACmB,OAAsB;IACvC,uGAAuG;IAC9F,MAAoB,EACpB,QAAgB;QAHR,YAAO,GAAP,OAAO,CAAe;QAE9B,WAAM,GAAN,MAAM,CAAc;QACpB,aAAQ,GAAR,QAAQ,CAAQ;IACxB,CAAC;IAEJ,8FAA8F;IAC9F,MAAM,CAAC,IAAI,CAAC,KAAyB;QACnC,IAAI,QAAwB,CAAC;QAC7B,IAAI,CAAC;YACH,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;QAC3H,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,GAAI,KAA0C,CAAC,IAAI,KAAK,aAAa,CAAC,CAAC,CAAE,KAA0B,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC;YACjI,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,IAAI,EAA0B,EAAE,CAAC;QACzE,CAAC;QACD,IAAI,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;QACtB,MAAM,WAAW,GAAG,kBAAkB,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;QACtG,IAAI,WAAW,CAAC,EAAE;YAAE,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC;QAC3C,MAAM,QAAQ,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC;QAC9C,MAAM,OAAO,GAAG,cAAc,CAAC;YAC7B,QAAQ,EAAE,QAAQ,CAAC,QAAQ;YAC3B,IAAI;YACJ,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,gBAAgB,EAAE,CAAC;YACnB,QAAQ;YACR,eAAe,EAAE,KAAK,CAAC,eAAe,IAAI,IAAI;YAC9C,GAAG,CAAC,KAAK,CAAC,kBAAkB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,kBAAkB,EAAE,KAAK,CAAC,kBAAkB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACpG,CAAC,CAAC;QACH,IAAI,CAAC,OAAO,CAAC,EAAE;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;QAC9D,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,aAAa,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;IACtJ,CAAC;IAED,OAAO;QACL,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;CACF"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The seam between the packages (S10): a verified release as a runtime
|
|
3
|
+
* consumes it, and where one comes from. `@airprompter/agent-sync` produces
|
|
4
|
+
* a `LoadedRelease` from the encrypted slot store or a daemon;
|
|
5
|
+
* `@airprompter/agent-core` produces one from a bundle the customer loads
|
|
6
|
+
* (`loadBundleRelease`); `@airprompter/agent-runtime` renders and assigns
|
|
7
|
+
* over either without knowing which. Structural, never a class: two copies
|
|
8
|
+
* of a package in one lockfile must agree on it (S1, the discriminant rule).
|
|
9
|
+
*/
|
|
10
|
+
import type { Manifest } from "../protocol/types.js";
|
|
11
|
+
/** A verified release: the manifest, its generation, and the verified payload bytes by content hash. */
|
|
12
|
+
export interface LoadedRelease {
|
|
13
|
+
readonly manifest: Manifest;
|
|
14
|
+
readonly generation: number;
|
|
15
|
+
/** contentHash → plaintext bytes, every one verified against the manifest before it is here. */
|
|
16
|
+
readonly payloads: ReadonlyMap<string, Uint8Array>;
|
|
17
|
+
}
|
|
18
|
+
/** Where a runtime reads releases from: the slot store, a daemon, or a bundle the customer loaded. */
|
|
19
|
+
export interface ReleaseReader {
|
|
20
|
+
/** What the reader is, as data — never branch on a class. */
|
|
21
|
+
readonly kind: "store" | "daemon" | "bundle" | (string & {});
|
|
22
|
+
/** The release in force, or null when nothing verified is held. */
|
|
23
|
+
current(): LoadedRelease | null;
|
|
24
|
+
}
|
|
25
|
+
/** One slot of a release as the runtime resolves it: the manifest slot, and the arm and bucket the subject fell in. */
|
|
26
|
+
export interface ReleaseSlot {
|
|
27
|
+
slot: Manifest["payload"]["slots"][number];
|
|
28
|
+
arm: string;
|
|
29
|
+
bucket: number | null;
|
|
30
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The seam between the packages (S10): a verified release as a runtime
|
|
3
|
+
* consumes it, and where one comes from. `@airprompter/agent-sync` produces
|
|
4
|
+
* a `LoadedRelease` from the encrypted slot store or a daemon;
|
|
5
|
+
* `@airprompter/agent-core` produces one from a bundle the customer loads
|
|
6
|
+
* (`loadBundleRelease`); `@airprompter/agent-runtime` renders and assigns
|
|
7
|
+
* over either without knowing which. Structural, never a class: two copies
|
|
8
|
+
* of a package in one lockfile must agree on it (S1, the discriminant rule).
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=reader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reader.js","sourceRoot":"","sources":["../../../src/release/reader.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `runRef`: a compact, content-free token a customer keeps beside their own
|
|
3
|
+
* trace to attach feedback later. `agent·target·slot·versionId·arm·generation·bucket`,
|
|
4
|
+
* HMAC-signed with a per-store key so a forged ref cannot file signals
|
|
5
|
+
* against a version that was never run here. Carries no subject and no text.
|
|
6
|
+
*/
|
|
7
|
+
export interface RunRefFacts {
|
|
8
|
+
agentId: string;
|
|
9
|
+
target: string;
|
|
10
|
+
tag: string;
|
|
11
|
+
versionId: string;
|
|
12
|
+
arm: string;
|
|
13
|
+
generation: number;
|
|
14
|
+
bucket: number | null;
|
|
15
|
+
}
|
|
16
|
+
export declare function mintRunRef(facts: RunRefFacts, key: Uint8Array): string;
|
|
17
|
+
export declare function parseRunRef(token: string, key: Uint8Array): RunRefFacts | null;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `runRef`: a compact, content-free token a customer keeps beside their own
|
|
3
|
+
* trace to attach feedback later. `agent·target·slot·versionId·arm·generation·bucket`,
|
|
4
|
+
* HMAC-signed with a per-store key so a forged ref cannot file signals
|
|
5
|
+
* against a version that was never run here. Carries no subject and no text.
|
|
6
|
+
*/
|
|
7
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
8
|
+
const SEP = "·";
|
|
9
|
+
export function mintRunRef(facts, key) {
|
|
10
|
+
const body = [facts.agentId, facts.target, facts.tag, facts.versionId, facts.arm, String(facts.generation), facts.bucket === null ? "-" : String(facts.bucket)].join(SEP);
|
|
11
|
+
const mac = createHmac("sha256", key).update(body, "utf8").digest("base64url").slice(0, 22);
|
|
12
|
+
return `${Buffer.from(body, "utf8").toString("base64url")}.${mac}`;
|
|
13
|
+
}
|
|
14
|
+
export function parseRunRef(token, key) {
|
|
15
|
+
const dot = token.lastIndexOf(".");
|
|
16
|
+
if (dot <= 0)
|
|
17
|
+
return null;
|
|
18
|
+
const body = Buffer.from(token.slice(0, dot), "base64url").toString("utf8");
|
|
19
|
+
const mac = token.slice(dot + 1);
|
|
20
|
+
const expected = createHmac("sha256", key).update(body, "utf8").digest("base64url").slice(0, 22);
|
|
21
|
+
if (mac.length !== expected.length || !timingSafeEqual(Buffer.from(mac), Buffer.from(expected)))
|
|
22
|
+
return null;
|
|
23
|
+
const parts = body.split(SEP);
|
|
24
|
+
if (parts.length !== 7)
|
|
25
|
+
return null;
|
|
26
|
+
const [agentId, target, tag, versionId, arm, generation, bucket] = parts;
|
|
27
|
+
return { agentId, target, tag, versionId, arm, generation: Number(generation), bucket: bucket === "-" ? null : Number(bucket) };
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=runRef.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runRef.js","sourceRoot":"","sources":["../../../src/render/runRef.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAY1D,MAAM,GAAG,GAAG,GAAG,CAAC;AAEhB,MAAM,UAAU,UAAU,CAAC,KAAkB,EAAE,GAAe;IAC5D,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,KAAK,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1K,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC5F,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,GAAG,EAAE,CAAC;AACrE,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAa,EAAE,GAAe;IACxD,MAAM,GAAG,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,GAAG,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC5E,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IACjC,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACjG,IAAI,GAAG,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7G,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,CAAC,GAAG,KAAiE,CAAC;IACrI,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;AAClI,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rendering a slot's text with its declared variables.
|
|
3
|
+
*
|
|
4
|
+
* `{{name}}` placeholders are the only substitution. A variable declared
|
|
5
|
+
* `end_user` (D55) is never dropped into the text raw: it is wrapped in the
|
|
6
|
+
* delimiters declared for it — by default an XML-style element named after
|
|
7
|
+
* the variable — so the model sees where untrusted input starts and stops,
|
|
8
|
+
* and the assurance lens can find it. A missing required variable throws:
|
|
9
|
+
* that is the customer's bug and silence would ship a broken prompt.
|
|
10
|
+
* Sync and telemetry failures degrade; render contract failures throw.
|
|
11
|
+
*/
|
|
12
|
+
import type { SlotVariable } from "../protocol/types.js";
|
|
13
|
+
export declare class MissingVariableError extends Error {
|
|
14
|
+
readonly tag: string;
|
|
15
|
+
readonly missing: string[];
|
|
16
|
+
constructor(tag: string, missing: string[]);
|
|
17
|
+
}
|
|
18
|
+
export declare class UnknownVariableError extends Error {
|
|
19
|
+
readonly tag: string;
|
|
20
|
+
readonly unknown: string[];
|
|
21
|
+
constructor(tag: string, unknown: string[]);
|
|
22
|
+
}
|
|
23
|
+
export interface Delimiters {
|
|
24
|
+
open(name: string): string;
|
|
25
|
+
close(name: string): string;
|
|
26
|
+
}
|
|
27
|
+
/** `<name>…</name>`: visible to the model, easy to find in the text, and never confusable with `{{name}}`. */
|
|
28
|
+
export declare const xmlDelimiters: Delimiters;
|
|
29
|
+
export interface RenderInput {
|
|
30
|
+
tag: string;
|
|
31
|
+
text: string;
|
|
32
|
+
variables: readonly SlotVariable[];
|
|
33
|
+
values: Readonly<Record<string, string | number | boolean | null | undefined>>;
|
|
34
|
+
delimiters?: Delimiters;
|
|
35
|
+
/** Refuse values for variables the slot did not declare (default: true — a typo is a bug, not an optional). */
|
|
36
|
+
strictVariables?: boolean;
|
|
37
|
+
}
|
|
38
|
+
export declare function renderTemplate(input: RenderInput): string;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rendering a slot's text with its declared variables.
|
|
3
|
+
*
|
|
4
|
+
* `{{name}}` placeholders are the only substitution. A variable declared
|
|
5
|
+
* `end_user` (D55) is never dropped into the text raw: it is wrapped in the
|
|
6
|
+
* delimiters declared for it — by default an XML-style element named after
|
|
7
|
+
* the variable — so the model sees where untrusted input starts and stops,
|
|
8
|
+
* and the assurance lens can find it. A missing required variable throws:
|
|
9
|
+
* that is the customer's bug and silence would ship a broken prompt.
|
|
10
|
+
* Sync and telemetry failures degrade; render contract failures throw.
|
|
11
|
+
*/
|
|
12
|
+
export class MissingVariableError extends Error {
|
|
13
|
+
tag;
|
|
14
|
+
missing;
|
|
15
|
+
constructor(tag, missing) {
|
|
16
|
+
super(`render ${tag}: missing required variable${missing.length > 1 ? "s" : ""} ${missing.join(", ")}`);
|
|
17
|
+
this.tag = tag;
|
|
18
|
+
this.missing = missing;
|
|
19
|
+
this.name = "MissingVariableError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export class UnknownVariableError extends Error {
|
|
23
|
+
tag;
|
|
24
|
+
unknown;
|
|
25
|
+
constructor(tag, unknown) {
|
|
26
|
+
super(`render ${tag}: variable${unknown.length > 1 ? "s" : ""} ${unknown.join(", ")} not declared on this slot`);
|
|
27
|
+
this.tag = tag;
|
|
28
|
+
this.unknown = unknown;
|
|
29
|
+
this.name = "UnknownVariableError";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** `<name>…</name>`: visible to the model, easy to find in the text, and never confusable with `{{name}}`. */
|
|
33
|
+
export const xmlDelimiters = {
|
|
34
|
+
open: (name) => `<${name}>`,
|
|
35
|
+
close: (name) => `</${name}>`,
|
|
36
|
+
};
|
|
37
|
+
const PLACEHOLDER = /\{\{\s*([a-zA-Z0-9_.-]{1,64})\s*\}\}/g;
|
|
38
|
+
export function renderTemplate(input) {
|
|
39
|
+
const declared = new Map(input.variables.map((variable) => [variable.name, variable]));
|
|
40
|
+
const delimiters = input.delimiters ?? xmlDelimiters;
|
|
41
|
+
const present = new Set(Object.entries(input.values).filter(([, value]) => value !== undefined && value !== null).map(([name]) => name));
|
|
42
|
+
const missing = input.variables.filter((variable) => variable.required && !present.has(variable.name)).map((variable) => variable.name);
|
|
43
|
+
if (missing.length)
|
|
44
|
+
throw new MissingVariableError(input.tag, missing);
|
|
45
|
+
if (input.strictVariables !== false) {
|
|
46
|
+
const unknown = [...present].filter((name) => !declared.has(name));
|
|
47
|
+
if (unknown.length)
|
|
48
|
+
throw new UnknownVariableError(input.tag, unknown);
|
|
49
|
+
}
|
|
50
|
+
return input.text.replace(PLACEHOLDER, (whole, name) => {
|
|
51
|
+
const variable = declared.get(name);
|
|
52
|
+
if (!variable)
|
|
53
|
+
return whole; // an undeclared placeholder in the text is left for the author to see
|
|
54
|
+
const value = input.values[name];
|
|
55
|
+
if (value === undefined || value === null)
|
|
56
|
+
return "";
|
|
57
|
+
const rendered = String(value);
|
|
58
|
+
// End-user text is fenced; a value that carries the closing fence cannot break out of it.
|
|
59
|
+
return variable.trust === "end_user" ? `${delimiters.open(name)}${escapeFence(rendered, delimiters.close(name))}${delimiters.close(name)}` : rendered;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function escapeFence(value, close) {
|
|
63
|
+
return close ? value.split(close).join(close.replace("<", "<")) : value;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=template.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template.js","sourceRoot":"","sources":["../../../src/render/template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAElC;IACA;IAFX,YACW,GAAW,EACX,OAAiB;QAE1B,KAAK,CAAC,UAAU,GAAG,8BAA8B,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAH/F,QAAG,GAAH,GAAG,CAAQ;QACX,YAAO,GAAP,OAAO,CAAU;QAG1B,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;AAED,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAElC;IACA;IAFX,YACW,GAAW,EACX,OAAiB;QAE1B,KAAK,CAAC,UAAU,GAAG,aAAa,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;QAHxG,QAAG,GAAH,GAAG,CAAQ;QACX,YAAO,GAAP,OAAO,CAAU;QAG1B,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;AAOD,8GAA8G;AAC9G,MAAM,CAAC,MAAM,aAAa,GAAe;IACvC,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,GAAG;IAC3B,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,GAAG;CAC9B,CAAC;AAEF,MAAM,WAAW,GAAG,uCAAuC,CAAC;AAY5D,MAAM,UAAU,cAAc,CAAC,KAAkB;IAC/C,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IACvF,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,IAAI,aAAa,CAAC;IACrD,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IACzI,MAAM,OAAO,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACxI,IAAI,OAAO,CAAC,MAAM;QAAE,MAAM,IAAI,oBAAoB,CAAC,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IACvE,IAAI,KAAK,CAAC,eAAe,KAAK,KAAK,EAAE,CAAC;QACpC,MAAM,OAAO,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;QACnE,IAAI,OAAO,CAAC,MAAM;YAAE,MAAM,IAAI,oBAAoB,CAAC,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,KAAK,EAAE,IAAY,EAAE,EAAE;QAC7D,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,QAAQ;YAAE,OAAO,KAAK,CAAC,CAAC,sEAAsE;QACnG,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,EAAE,CAAC;QACrD,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC/B,0FAA0F;QAC1F,OAAO,QAAQ,CAAC,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,QAAQ,EAAE,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;IACxJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,WAAW,CAAC,KAAa,EAAE,KAAa;IAC/C,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AAC7E,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The feedback catalogue (`protocol/schemas/feedback-signals.schema.json`)
|
|
3
|
+
* normalised into window outcomes. Numbers, booleans and the declared enums
|
|
4
|
+
* only; anything else — free text above all — is named in `rejected` and
|
|
5
|
+
* never reaches the spool. `protocol/vectors/feedback.json` pins every rule.
|
|
6
|
+
*/
|
|
7
|
+
export declare const BOOLEAN_SIGNALS: readonly ["flagged", "accepted", "edited", "regenerated", "copied", "followUp", "escalated", "abandoned", "corrected", "resolved", "reopened", "converted", "refunded", "slaMet"];
|
|
8
|
+
export declare const UNIT_SIGNALS: readonly ["editDistanceRatio", "judgeScore"];
|
|
9
|
+
export declare const COUNT_SIGNALS: readonly ["regenerations", "timeToAcceptMs"];
|
|
10
|
+
/** T34: written by the runtime on a window (a golden-set run), never accepted from `feedback()`; reserved so `custom` cannot shadow it. */
|
|
11
|
+
export declare const RUNTIME_SIGNALS: readonly ["goldenPass"];
|
|
12
|
+
export declare const CATALOGUE: ReadonlySet<string>;
|
|
13
|
+
export declare const OUTCOME_NAME: RegExp;
|
|
14
|
+
export type FeedbackRejection = "invalid_value" | "invalid_name" | "reserved_name" | "unknown_signal" | "needs_slot_enum";
|
|
15
|
+
export interface NormalizedFeedback {
|
|
16
|
+
/** True when at least one signal became an outcome — the value `feedback()` returns. */
|
|
17
|
+
accepted: boolean;
|
|
18
|
+
outcomes: Record<string, number | boolean>;
|
|
19
|
+
rejected: Record<string, FeedbackRejection>;
|
|
20
|
+
}
|
|
21
|
+
export declare function normalizeFeedback(signals: Record<string, unknown>): NormalizedFeedback;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The feedback catalogue (`protocol/schemas/feedback-signals.schema.json`)
|
|
3
|
+
* normalised into window outcomes. Numbers, booleans and the declared enums
|
|
4
|
+
* only; anything else — free text above all — is named in `rejected` and
|
|
5
|
+
* never reaches the spool. `protocol/vectors/feedback.json` pins every rule.
|
|
6
|
+
*/
|
|
7
|
+
export const BOOLEAN_SIGNALS = ["flagged", "accepted", "edited", "regenerated", "copied", "followUp", "escalated", "abandoned", "corrected", "resolved", "reopened", "converted", "refunded", "slaMet"];
|
|
8
|
+
export const UNIT_SIGNALS = ["editDistanceRatio", "judgeScore"];
|
|
9
|
+
export const COUNT_SIGNALS = ["regenerations", "timeToAcceptMs"];
|
|
10
|
+
/** T34: written by the runtime on a window (a golden-set run), never accepted from `feedback()`; reserved so `custom` cannot shadow it. */
|
|
11
|
+
export const RUNTIME_SIGNALS = ["goldenPass"];
|
|
12
|
+
export const CATALOGUE = new Set(["thumbs", "rating", "correctedValue", "custom", ...BOOLEAN_SIGNALS, ...UNIT_SIGNALS, ...COUNT_SIGNALS, ...RUNTIME_SIGNALS]);
|
|
13
|
+
export const OUTCOME_NAME = /^[a-z][a-zA-Z0-9]{0,31}$/;
|
|
14
|
+
const finite = (value) => typeof value === "number" && Number.isFinite(value);
|
|
15
|
+
export function normalizeFeedback(signals) {
|
|
16
|
+
const outcomes = {};
|
|
17
|
+
const rejected = {};
|
|
18
|
+
for (const [name, value] of Object.entries(signals)) {
|
|
19
|
+
if (name === "thumbs") {
|
|
20
|
+
if (value === "up" || value === "down")
|
|
21
|
+
outcomes.thumbs = value === "up";
|
|
22
|
+
else
|
|
23
|
+
rejected[name] = "invalid_value";
|
|
24
|
+
}
|
|
25
|
+
else if (name === "rating") {
|
|
26
|
+
if (Number.isInteger(value) && value >= 1 && value <= 5)
|
|
27
|
+
outcomes.rating = value;
|
|
28
|
+
else
|
|
29
|
+
rejected[name] = "invalid_value";
|
|
30
|
+
}
|
|
31
|
+
else if (BOOLEAN_SIGNALS.includes(name)) {
|
|
32
|
+
if (typeof value === "boolean")
|
|
33
|
+
outcomes[name] = value;
|
|
34
|
+
else
|
|
35
|
+
rejected[name] = "invalid_value";
|
|
36
|
+
}
|
|
37
|
+
else if (UNIT_SIGNALS.includes(name)) {
|
|
38
|
+
if (finite(value) && value >= 0 && value <= 1)
|
|
39
|
+
outcomes[name] = value;
|
|
40
|
+
else
|
|
41
|
+
rejected[name] = "invalid_value";
|
|
42
|
+
}
|
|
43
|
+
else if (COUNT_SIGNALS.includes(name)) {
|
|
44
|
+
if (Number.isInteger(value) && value >= 0)
|
|
45
|
+
outcomes[name] = value;
|
|
46
|
+
else
|
|
47
|
+
rejected[name] = "invalid_value";
|
|
48
|
+
}
|
|
49
|
+
else if (name === "correctedValue") {
|
|
50
|
+
// Aggregating per enum value needs the slot's declared output check, which this writer does not hold yet.
|
|
51
|
+
rejected[name] = typeof value === "string" && value.length <= 64 ? "needs_slot_enum" : "invalid_value";
|
|
52
|
+
}
|
|
53
|
+
else if (RUNTIME_SIGNALS.includes(name)) {
|
|
54
|
+
rejected[name] = "reserved_name";
|
|
55
|
+
}
|
|
56
|
+
else if (name === "custom") {
|
|
57
|
+
if (typeof value !== "object" || value === null || Array.isArray(value) || Object.keys(value).length > 8) {
|
|
58
|
+
rejected[name] = "invalid_value";
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
for (const [customName, customValue] of Object.entries(value)) {
|
|
62
|
+
if (!OUTCOME_NAME.test(customName))
|
|
63
|
+
rejected[`custom.${customName}`] = "invalid_name";
|
|
64
|
+
else if (CATALOGUE.has(customName))
|
|
65
|
+
rejected[`custom.${customName}`] = "reserved_name";
|
|
66
|
+
else if (typeof customValue === "boolean" || finite(customValue))
|
|
67
|
+
outcomes[customName] = customValue;
|
|
68
|
+
else
|
|
69
|
+
rejected[`custom.${customName}`] = "invalid_value";
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else
|
|
73
|
+
rejected[name] = "unknown_signal";
|
|
74
|
+
}
|
|
75
|
+
return { accepted: Object.keys(outcomes).length > 0, outcomes, rejected };
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=feedback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feedback.js","sourceRoot":"","sources":["../../../src/telemetry/feedback.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,QAAQ,CAAU,CAAC;AACjN,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,mBAAmB,EAAE,YAAY,CAAU,CAAC;AACzE,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,eAAe,EAAE,gBAAgB,CAAU,CAAC;AAC1E,2IAA2I;AAC3I,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,YAAY,CAAU,CAAC;AACvD,MAAM,CAAC,MAAM,SAAS,GAAwB,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,EAAE,GAAG,eAAe,EAAE,GAAG,YAAY,EAAE,GAAG,aAAa,EAAE,GAAG,eAAe,CAAC,CAAC,CAAC;AACnL,MAAM,CAAC,MAAM,YAAY,GAAG,0BAA0B,CAAC;AAWvD,MAAM,MAAM,GAAG,CAAC,KAAc,EAAmB,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAExG,MAAM,UAAU,iBAAiB,CAAC,OAAgC;IAChE,MAAM,QAAQ,GAAqC,EAAE,CAAC;IACtD,MAAM,QAAQ,GAAsC,EAAE,CAAC;IACvD,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACpD,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;YACtB,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,MAAM;gBAAE,QAAQ,CAAC,MAAM,GAAG,KAAK,KAAK,IAAI,CAAC;;gBACpE,QAAQ,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC;QACxC,CAAC;aAAM,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC7B,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAK,KAAgB,IAAI,CAAC,IAAK,KAAgB,IAAI,CAAC;gBAAE,QAAQ,CAAC,MAAM,GAAG,KAAe,CAAC;;gBAC9G,QAAQ,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC;QACxC,CAAC;aAAM,IAAK,eAAqC,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACjE,IAAI,OAAO,KAAK,KAAK,SAAS;gBAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;;gBAClD,QAAQ,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC;QACxC,CAAC;aAAM,IAAK,YAAkC,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9D,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC;gBAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;;gBACjE,QAAQ,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC;QACxC,CAAC;aAAM,IAAK,aAAmC,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/D,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAK,KAAgB,IAAI,CAAC;gBAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAe,CAAC;;gBACnF,QAAQ,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC;QACxC,CAAC;aAAM,IAAI,IAAI,KAAK,gBAAgB,EAAE,CAAC;YACrC,0GAA0G;YAC1G,QAAQ,CAAC,IAAI,CAAC,GAAG,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,eAAe,CAAC;QACzG,CAAC;aAAM,IAAK,eAAqC,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACjE,QAAQ,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC;QACnC,CAAC;aAAM,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC7B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACzG,QAAQ,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC;gBACjC,SAAS;YACX,CAAC;YACD,KAAK,MAAM,CAAC,UAAU,EAAE,WAAW,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,EAAE,CAAC;gBACzF,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC;oBAAE,QAAQ,CAAC,UAAU,UAAU,EAAE,CAAC,GAAG,cAAc,CAAC;qBACjF,IAAI,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC;oBAAE,QAAQ,CAAC,UAAU,UAAU,EAAE,CAAC,GAAG,eAAe,CAAC;qBAClF,IAAI,OAAO,WAAW,KAAK,SAAS,IAAI,MAAM,CAAC,WAAW,CAAC;oBAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,WAAW,CAAC;;oBAChG,QAAQ,CAAC,UAAU,UAAU,EAAE,CAAC,GAAG,eAAe,CAAC;YAC1D,CAAC;QACH,CAAC;;YAAM,QAAQ,CAAC,IAAI,CAAC,GAAG,gBAAgB,CAAC;IAC3C,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;AAC5E,CAAC"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The telemetry row schemas (`protocol/spool-format.md`, D52/D66): what a
|
|
3
|
+
* spool segment carries, and nothing else. There is no field for prompt
|
|
4
|
+
* text, output, or an end-user identifier — the shape is the privacy rule.
|
|
5
|
+
* Pure: the spool writer (`@airprompter/agent-telemetry`) and the wrap
|
|
6
|
+
* adapters (`@airprompter/agent-runtime`) both build on these without
|
|
7
|
+
* importing each other.
|
|
8
|
+
*/
|
|
9
|
+
export declare const LATENCY_BUCKET_EDGES_MS: readonly [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 65536];
|
|
10
|
+
export type ErrorClass = "render_missing_variable" | "context_length_exceeded" | "output_schema_invalid" | "truncated" | "content_filter" | "provider_error" | "provider_timeout" | "provider_rate_limited";
|
|
11
|
+
export interface Observation {
|
|
12
|
+
tag: string;
|
|
13
|
+
versionId: string;
|
|
14
|
+
arm: string;
|
|
15
|
+
model: string;
|
|
16
|
+
status: "ok" | "error" | "refused";
|
|
17
|
+
errorClass?: ErrorClass | null;
|
|
18
|
+
latencyMs: number;
|
|
19
|
+
tokens?: {
|
|
20
|
+
input?: number;
|
|
21
|
+
cachedInput?: number;
|
|
22
|
+
output?: number;
|
|
23
|
+
};
|
|
24
|
+
usageSource?: "reported" | "measured" | "estimated" | "unavailable";
|
|
25
|
+
checks?: {
|
|
26
|
+
passed?: number;
|
|
27
|
+
failed?: number;
|
|
28
|
+
};
|
|
29
|
+
outcomes?: Record<string, number | boolean>;
|
|
30
|
+
}
|
|
31
|
+
export interface WindowRow {
|
|
32
|
+
type: "window";
|
|
33
|
+
v: 1;
|
|
34
|
+
minute: string;
|
|
35
|
+
instanceId: string;
|
|
36
|
+
instanceClass: "resident" | "ephemeral";
|
|
37
|
+
tag: string;
|
|
38
|
+
versionId: string;
|
|
39
|
+
arm: string;
|
|
40
|
+
model: string;
|
|
41
|
+
status: "ok" | "error" | "refused";
|
|
42
|
+
errorClass: ErrorClass | null;
|
|
43
|
+
usageSource: "reported" | "measured" | "estimated" | "unavailable";
|
|
44
|
+
count: number;
|
|
45
|
+
latencyMs: {
|
|
46
|
+
buckets: number[];
|
|
47
|
+
sum: number;
|
|
48
|
+
};
|
|
49
|
+
tokens: {
|
|
50
|
+
input: number;
|
|
51
|
+
cachedInput?: number;
|
|
52
|
+
output: number;
|
|
53
|
+
};
|
|
54
|
+
checks?: {
|
|
55
|
+
passed: number;
|
|
56
|
+
failed: number;
|
|
57
|
+
};
|
|
58
|
+
outcomes?: Record<string, {
|
|
59
|
+
n: number;
|
|
60
|
+
sum: number;
|
|
61
|
+
}>;
|
|
62
|
+
sdk: string;
|
|
63
|
+
}
|
|
64
|
+
export interface RefusalRow {
|
|
65
|
+
type: "refusal";
|
|
66
|
+
v: 1;
|
|
67
|
+
at: string;
|
|
68
|
+
instanceId: string;
|
|
69
|
+
reason: "disabled" | "lease_expired" | "payload_verification_failed" | "forced_downgrade" | "model_unavailable" | "unlock_refused";
|
|
70
|
+
generation: number;
|
|
71
|
+
tag: string | null;
|
|
72
|
+
}
|
|
73
|
+
/** Segments (or buffered rows) evicted by a budget: the loss is reported, never silent. */
|
|
74
|
+
export interface DroppedRow {
|
|
75
|
+
type: "dropped";
|
|
76
|
+
v: 1;
|
|
77
|
+
at: string;
|
|
78
|
+
instanceId: string;
|
|
79
|
+
segments: number;
|
|
80
|
+
bytes: number;
|
|
81
|
+
}
|
|
82
|
+
export type SpoolRow = WindowRow | RefusalRow | DroppedRow;
|
|
83
|
+
export declare function latencyBucketIndex(latencyMs: number): number;
|
|
84
|
+
export declare function minuteOf(epochMs: number): string;
|
|
85
|
+
export declare function epochMinute(epochMs: number): number;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The telemetry row schemas (`protocol/spool-format.md`, D52/D66): what a
|
|
3
|
+
* spool segment carries, and nothing else. There is no field for prompt
|
|
4
|
+
* text, output, or an end-user identifier — the shape is the privacy rule.
|
|
5
|
+
* Pure: the spool writer (`@airprompter/agent-telemetry`) and the wrap
|
|
6
|
+
* adapters (`@airprompter/agent-runtime`) both build on these without
|
|
7
|
+
* importing each other.
|
|
8
|
+
*/
|
|
9
|
+
export const LATENCY_BUCKET_EDGES_MS = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 65536];
|
|
10
|
+
export function latencyBucketIndex(latencyMs) {
|
|
11
|
+
const index = LATENCY_BUCKET_EDGES_MS.findIndex((edge) => latencyMs <= edge);
|
|
12
|
+
return index === -1 ? LATENCY_BUCKET_EDGES_MS.length - 1 : index;
|
|
13
|
+
}
|
|
14
|
+
export function minuteOf(epochMs) {
|
|
15
|
+
const date = new Date(epochMs);
|
|
16
|
+
date.setUTCSeconds(0, 0);
|
|
17
|
+
return date.toISOString().replace(".000Z", "Z");
|
|
18
|
+
}
|
|
19
|
+
export function epochMinute(epochMs) {
|
|
20
|
+
return Math.floor(epochMs / 60000);
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=rows.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rows.js","sourceRoot":"","sources":["../../../src/telemetry/rows.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,CAAU,CAAC;AAqE9H,MAAM,UAAU,kBAAkB,CAAC,SAAiB;IAClD,MAAM,KAAK,GAAG,uBAAuB,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,IAAI,IAAI,CAAC,CAAC;IAC7E,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,uBAAuB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,OAAe;IACtC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC;IAC/B,IAAI,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACzB,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,CAAC;AACrC,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where validated spool segments go (S13): the `UploadSink` port. The
|
|
3
|
+
* uploader in `@airprompter/agent-telemetry` owns the spool — the sweep, the
|
|
4
|
+
* budget, the quarantine, the delete-on-ack — and hands each validated
|
|
5
|
+
* segment to a sink. Two sinks ship: AirPrompter's (a grant per writer from
|
|
6
|
+
* the heartbeat, a PUT to the customer's own prefix) and the OpenTelemetry
|
|
7
|
+
* bridge (`@airprompter/otel-bridge`: the windows as OTLP metrics to the
|
|
8
|
+
* collector the customer already runs, no AirPrompter grant at all). A sink
|
|
9
|
+
* is data, never a class: `kind` says what it is.
|
|
10
|
+
*/
|
|
11
|
+
import type { SpoolRow } from "./rows.js";
|
|
12
|
+
export interface UploadSegment {
|
|
13
|
+
/** The writer whose segment this is (the instance id in the file name). */
|
|
14
|
+
instanceId: string;
|
|
15
|
+
/** The segment's file name: the object key on AirPrompter's side, a label elsewhere. */
|
|
16
|
+
segment: string;
|
|
17
|
+
/** The validated rows, in order. */
|
|
18
|
+
rows: SpoolRow[];
|
|
19
|
+
/** Exactly the whole lines, as bytes — what AirPrompter's sink posts. */
|
|
20
|
+
bytes: Uint8Array;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* What a sink says about one segment:
|
|
24
|
+
* - `ok`: shipped; the uploader deletes the segment.
|
|
25
|
+
* - `hold`: not now (a grant is throttled, a collector asked for backoff); the uploader keeps the segment and waits.
|
|
26
|
+
* - `failed`: try again later under backoff; the segment stays under the budget.
|
|
27
|
+
* - `dropped`: the sink gave up on this segment for good (the bridge's drop-and-count); the uploader deletes it and counts the loss.
|
|
28
|
+
* - `too_large`: the receiver refused the size; the segment is quarantined.
|
|
29
|
+
*/
|
|
30
|
+
export type UploadOutcome = {
|
|
31
|
+
status: "ok";
|
|
32
|
+
} | {
|
|
33
|
+
status: "hold";
|
|
34
|
+
retryAfterMs: number;
|
|
35
|
+
reason?: string;
|
|
36
|
+
} | {
|
|
37
|
+
status: "failed";
|
|
38
|
+
reason: string;
|
|
39
|
+
} | {
|
|
40
|
+
status: "dropped";
|
|
41
|
+
reason: string;
|
|
42
|
+
} | {
|
|
43
|
+
status: "too_large";
|
|
44
|
+
bytes: number;
|
|
45
|
+
};
|
|
46
|
+
export interface UploadSink {
|
|
47
|
+
/** What the sink is, as data: `"airprompter"`, `"otlp"`, or a customer's own. Never branch on a class. */
|
|
48
|
+
readonly kind: "airprompter" | "otlp" | (string & {});
|
|
49
|
+
ship(segment: UploadSegment): Promise<UploadOutcome>;
|
|
50
|
+
/** Live state for `status` (grants held, the collector's last answer); optional. */
|
|
51
|
+
status?(): Record<string, unknown>;
|
|
52
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where validated spool segments go (S13): the `UploadSink` port. The
|
|
3
|
+
* uploader in `@airprompter/agent-telemetry` owns the spool — the sweep, the
|
|
4
|
+
* budget, the quarantine, the delete-on-ack — and hands each validated
|
|
5
|
+
* segment to a sink. Two sinks ship: AirPrompter's (a grant per writer from
|
|
6
|
+
* the heartbeat, a PUT to the customer's own prefix) and the OpenTelemetry
|
|
7
|
+
* bridge (`@airprompter/otel-bridge`: the windows as OTLP metrics to the
|
|
8
|
+
* collector the customer already runs, no AirPrompter grant at all). A sink
|
|
9
|
+
* is data, never a class: `kind` says what it is.
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=uploadSink.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uploadSink.js","sourceRoot":"","sources":["../../../src/telemetry/uploadSink.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A fake control plane for SDK tests: an offline root ceremony, a signing
|
|
3
|
+
* key, sealed manifests per generation, payloads by hash, an edge pointer —
|
|
4
|
+
* served through a `fetch` the SDK takes as an option. Signs exactly as the
|
|
5
|
+
* hosted service does (canonical payload bytes, ES256, P1363).
|
|
6
|
+
*/
|
|
7
|
+
import type { Manifest, ManifestPayload, ManifestSlot, P256PrivateJwk, RootMetadata, Target } from "../protocol/types.js";
|
|
8
|
+
import type { FetchLike } from "../control/client.js";
|
|
9
|
+
/** The protocol version this checkout of the repository declares; manifests the fake signs carry it. */
|
|
10
|
+
export declare const PROTOCOL = "0.2.5";
|
|
11
|
+
export declare function newKey(): P256PrivateJwk;
|
|
12
|
+
export declare function rootDocument(input: {
|
|
13
|
+
rootKey: P256PrivateJwk;
|
|
14
|
+
signingKeys: P256PrivateJwk[];
|
|
15
|
+
environment: Target;
|
|
16
|
+
version?: number;
|
|
17
|
+
expires?: string;
|
|
18
|
+
purpose?: "platform" | "countersign";
|
|
19
|
+
}): RootMetadata;
|
|
20
|
+
export interface SlotSpec {
|
|
21
|
+
tag: string;
|
|
22
|
+
text: string;
|
|
23
|
+
model?: string;
|
|
24
|
+
variables?: ManifestSlot["variables"];
|
|
25
|
+
versionId?: string;
|
|
26
|
+
steps?: Array<{
|
|
27
|
+
text: string;
|
|
28
|
+
versionId?: string;
|
|
29
|
+
}>;
|
|
30
|
+
}
|
|
31
|
+
export declare class FakeControlPlane {
|
|
32
|
+
readonly scope: {
|
|
33
|
+
organizationId: string;
|
|
34
|
+
agentId: string;
|
|
35
|
+
target: Target;
|
|
36
|
+
};
|
|
37
|
+
readonly apiKey: string;
|
|
38
|
+
readonly rootKey: P256PrivateJwk;
|
|
39
|
+
readonly signingKey: P256PrivateJwk;
|
|
40
|
+
readonly root: RootMetadata;
|
|
41
|
+
readonly payloads: Map<string, Buffer<ArrayBufferLike>>;
|
|
42
|
+
readonly requests: string[];
|
|
43
|
+
private current;
|
|
44
|
+
generation: number;
|
|
45
|
+
edgeEtag: number;
|
|
46
|
+
requireCountersign: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Fresh keys by default (a test). `airprompter dev` (S12) hands in the keys it persisted, so the root a customer
|
|
49
|
+
* pinned yesterday still verifies today's generations.
|
|
50
|
+
*/
|
|
51
|
+
constructor(scope: {
|
|
52
|
+
organizationId: string;
|
|
53
|
+
agentId: string;
|
|
54
|
+
target: Target;
|
|
55
|
+
}, apiKey?: string, keys?: {
|
|
56
|
+
rootKey?: P256PrivateJwk;
|
|
57
|
+
signingKey?: P256PrivateJwk;
|
|
58
|
+
});
|
|
59
|
+
slot(spec: SlotSpec): ManifestSlot;
|
|
60
|
+
/** Seal and promote: generation + 1, signed with the signing key. */
|
|
61
|
+
/** T9: every heartbeat body the fake accepted, and what it answers (a test may change the cadence). */
|
|
62
|
+
readonly heartbeats: Array<Record<string, unknown>>;
|
|
63
|
+
heartbeatIntervalSeconds: number;
|
|
64
|
+
heartbeatRefusal: {
|
|
65
|
+
status: number;
|
|
66
|
+
code: string;
|
|
67
|
+
} | null;
|
|
68
|
+
/**
|
|
69
|
+
* T26: the grant issuer and a fake S3 behind it. With `grantBaseUrl` set, every accepted heartbeat answers an
|
|
70
|
+
* `uploadGrant` for the body's instance prefix (or `retryAfterSeconds` while `grantHold` is set); the POST endpoint at
|
|
71
|
+
* `<grantBaseUrl>/s3/agent-telemetry` checks the policy the way the bucket would and keeps the objects by key.
|
|
72
|
+
*/
|
|
73
|
+
grantBaseUrl: string | null;
|
|
74
|
+
/** S3: a party between the fleet and the edge pins the pointer at this generation; the origin moves on regardless. */
|
|
75
|
+
pinnedPointer: {
|
|
76
|
+
generation: number;
|
|
77
|
+
} | null;
|
|
78
|
+
/** S3: whether the heartbeat answer names the origin's generation (an older service does not). */
|
|
79
|
+
heartbeatLatestGeneration: boolean;
|
|
80
|
+
grantTtlMs: number;
|
|
81
|
+
grantHold: {
|
|
82
|
+
retryAfterSeconds: number;
|
|
83
|
+
} | null;
|
|
84
|
+
uploadIntervalSeconds: number;
|
|
85
|
+
readonly grants: Array<{
|
|
86
|
+
grantId: string;
|
|
87
|
+
instanceId: string;
|
|
88
|
+
keyPrefix: string;
|
|
89
|
+
expiresAt: string;
|
|
90
|
+
}>;
|
|
91
|
+
readonly objects: Map<string, Buffer<ArrayBufferLike>>;
|
|
92
|
+
readonly uploads: string[];
|
|
93
|
+
/** A test may fail the next N POSTs (a 500) to exercise backoff. */
|
|
94
|
+
failNextUploads: number;
|
|
95
|
+
/** The issuer's and the bucket's clock (grant expiry, policy expiry); a test drives it beside the uploader's. */
|
|
96
|
+
now: () => number;
|
|
97
|
+
private grantSeq;
|
|
98
|
+
private issueGrant;
|
|
99
|
+
/** The bucket's side of a presigned POST: the policy's conditions, the expiry, the size cap; nothing else is looked at. */
|
|
100
|
+
private acceptUpload;
|
|
101
|
+
promote(slots: ManifestSlot[], options?: Partial<Pick<ManifestPayload, "applyPolicy" | "leaseSeconds" | "experiment" | "directives" | "onLeaseExpiry" | "unlockWindow">> & {
|
|
102
|
+
signWith?: P256PrivateJwk;
|
|
103
|
+
generation?: number;
|
|
104
|
+
}): Manifest;
|
|
105
|
+
get manifest(): Manifest | null;
|
|
106
|
+
/** The `fetch` the SDK sees. */
|
|
107
|
+
fetch(): FetchLike;
|
|
108
|
+
}
|
|
109
|
+
/** The fake behind a real HTTP listener, for daemons and CLIs that run as their own process — and `airprompter dev` (S12). */
|
|
110
|
+
export declare function serveOverHttp(plane: FakeControlPlane, listen?: {
|
|
111
|
+
host?: string;
|
|
112
|
+
port?: number;
|
|
113
|
+
}): Promise<{
|
|
114
|
+
baseUrl: string;
|
|
115
|
+
close: () => Promise<void>;
|
|
116
|
+
}>;
|