@adversarylabs/sdk 0.1.10 → 0.1.12
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 +68 -5
- package/dist/index.d.ts +34 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +80 -5
- package/dist/index.js.map +1 -1
- package/dist/manifest.d.ts +1 -0
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js.map +1 -1
- package/dist/model.d.ts +47 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +300 -0
- package/dist/model.js.map +1 -0
- package/package.json +1 -1
- package/schemas/adversary.manifest.v1.schema.json +4 -0
package/dist/manifest.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export interface EnvironmentPermissionsManifest {
|
|
|
24
24
|
export interface PermissionsManifest {
|
|
25
25
|
filesystem?: FilesystemPermissionsManifest;
|
|
26
26
|
network?: boolean;
|
|
27
|
+
model?: boolean;
|
|
27
28
|
environment?: EnvironmentPermissionsManifest;
|
|
28
29
|
enforcement?: "advisory" | "required";
|
|
29
30
|
}
|
package/dist/manifest.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,4BAA4B,mBAAmB,CAAC;AAC7D,eAAO,MAAM,4BAA4B,QAAU,CAAC;AAEpD,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,6BAA6B;IAC5C,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,8BAA8B;IAC7C,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,CAAC,EAAE,6BAA6B,CAAC;IAC3C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,8BAA8B,CAAC;IAC7C,WAAW,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC;CACvC;AAMD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,qBAAqB,CAAC;CAChC;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,OAAO,EAAE,eAAe,CAAC;IACzB,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,QAAQ,CAAC,EAAE,gBAAgB,CAAC;CAC7B;AAMD,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;gBAEd,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,EAAO;CAKnD;AAID,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,iBAAiB,CAiCrF;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,iBAAiB,CAO3E"}
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,4BAA4B,mBAAmB,CAAC;AAC7D,eAAO,MAAM,4BAA4B,QAAU,CAAC;AAEpD,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,6BAA6B;IAC5C,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,8BAA8B;IAC7C,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,CAAC,EAAE,6BAA6B,CAAC;IAC3C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,8BAA8B,CAAC;IAC7C,WAAW,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC;CACvC;AAMD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,qBAAqB,CAAC;CAChC;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,OAAO,EAAE,eAAe,CAAC;IACzB,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,QAAQ,CAAC,EAAE,gBAAgB,CAAC;CAC7B;AAMD,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;gBAEd,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,EAAO;CAKnD;AAID,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,iBAAiB,CAiCrF;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,iBAAiB,CAO3E"}
|
package/dist/manifest.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAA2C,MAAM,kBAAkB,CAAC;AACpF,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAEzC,MAAM,CAAC,MAAM,4BAA4B,GAAG,gBAAgB,CAAC;AAC7D,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,IAAI,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { Ajv2020, type ErrorObject, type ValidateFunction } from \"ajv/dist/2020.js\";\nimport { parseAllDocuments } from \"yaml\";\n\nexport const ADVERSARY_MANIFEST_FILE_NAME = \"adversary.yaml\";\nexport const ADVERSARY_MANIFEST_MAX_BYTES = 1 << 20;\n\nexport interface DetectionManifest {\n files?: string[];\n entrypoint?: string;\n}\n\nexport interface TriggerManifest {\n manual?: boolean;\n files_changed?: string[];\n}\n\nexport interface RuntimeManifest {\n name?: \"node\" | \"process\";\n image?: string;\n version?: string;\n command: string[];\n}\n\nexport interface FilesystemPermissionsManifest {\n read?: string[];\n write?: string[];\n}\n\nexport interface EnvironmentPermissionsManifest {\n allow?: string[];\n}\n\nexport interface PermissionsManifest {\n filesystem?: FilesystemPermissionsManifest;\n network?: boolean;\n model?: boolean;\n environment?: EnvironmentPermissionsManifest;\n enforcement?: \"advisory\" | \"required\";\n}\n\ninterface LegacyPermissionsManifest extends PermissionsManifest {\n env?: string[];\n}\n\nexport interface FindingsManifest {\n format?: \"adversary.review.v1\";\n}\n\nexport interface AdversaryManifest {\n name: string;\n version?: string;\n description?: string;\n triggers?: TriggerManifest;\n detection?: DetectionManifest;\n runtime: RuntimeManifest;\n permissions?: PermissionsManifest;\n findings?: FindingsManifest;\n}\n\ninterface ParsedAdversaryManifest extends Omit<AdversaryManifest, \"permissions\"> {\n permissions?: LegacyPermissionsManifest;\n}\n\nexport class ManifestValidationError extends Error {\n readonly issues: string[];\n\n constructor(message: string, issues: string[] = []) {\n super(message);\n this.name = \"ManifestValidationError\";\n this.issues = issues;\n }\n}\n\nlet manifestValidator: ValidateFunction<ParsedAdversaryManifest> | undefined;\n\nexport function parseAdversaryManifest(source: string | Uint8Array): AdversaryManifest {\n const text = typeof source === \"string\" ? source : new TextDecoder().decode(source);\n const size = Buffer.byteLength(text, \"utf8\");\n if (size > ADVERSARY_MANIFEST_MAX_BYTES) {\n throw new ManifestValidationError(\n `adversary.yaml is too large: ${size} bytes exceeds ${ADVERSARY_MANIFEST_MAX_BYTES} bytes`,\n );\n }\n\n const documents = parseAllDocuments(text, {\n prettyErrors: true,\n uniqueKeys: true,\n });\n if (documents.length !== 1) {\n throw new ManifestValidationError(\"manifest must contain exactly one YAML document\");\n }\n const document = documents[0];\n if (document === undefined || document.contents === null) {\n throw new ManifestValidationError(\"manifest is empty\");\n }\n if (document.errors.length > 0) {\n const issues = document.errors.map((error) => error.message);\n throw new ManifestValidationError(`decode manifest YAML: ${issues.join(\"; \")}`, issues);\n }\n\n let value: unknown;\n try {\n value = document.toJS({ maxAliasCount: 0 });\n } catch (error) {\n const issue = error instanceof Error ? error.message : String(error);\n throw new ManifestValidationError(`decode manifest YAML: ${issue}`, [issue]);\n }\n return validateAdversaryManifest(value);\n}\n\nexport function validateAdversaryManifest(value: unknown): AdversaryManifest {\n const validator = getManifestValidator();\n if (!validator(value)) {\n const issues = (validator.errors ?? []).map(formatSchemaError);\n throw new ManifestValidationError(`invalid adversary manifest: ${issues.join(\"; \")}`, issues);\n }\n return normalizeLegacyPermissions(value);\n}\n\nfunction getManifestValidator(): ValidateFunction<ParsedAdversaryManifest> {\n if (manifestValidator !== undefined) return manifestValidator;\n const schema = JSON.parse(\n readFileSync(new URL(\"../schemas/adversary.manifest.v1.schema.json\", import.meta.url), \"utf8\"),\n );\n manifestValidator = new Ajv2020({\n allErrors: true,\n strict: true,\n }).compile<ParsedAdversaryManifest>(schema);\n return manifestValidator;\n}\n\nfunction formatSchemaError(error: ErrorObject): string {\n const path = `manifest${error.instancePath.replaceAll(\"/\", \".\")}`;\n if (error.keyword === \"pattern\" && error.schemaPath === \"#/$defs/portableProjectPath/pattern\") {\n return `${path} must be a portable project-relative path`;\n }\n if (error.keyword === \"additionalProperties\") {\n return `${path} contains unknown field ${JSON.stringify(error.params.additionalProperty)}`;\n }\n if (error.keyword === \"required\") {\n return `${path} is missing required field ${JSON.stringify(error.params.missingProperty)}`;\n }\n return `${path} ${error.message ?? `failed ${error.keyword} validation`}`;\n}\n\nfunction normalizeLegacyPermissions(manifest: ParsedAdversaryManifest): AdversaryManifest {\n if (manifest.permissions?.env === undefined) return manifest;\n const { env, ...permissions } = manifest.permissions;\n return {\n ...manifest,\n permissions: {\n ...permissions,\n environment: { allow: env },\n },\n };\n}\n"]}
|
package/dist/model.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export declare const ADVERSARY_MODEL_PROTOCOL_VERSION = 1;
|
|
2
|
+
export declare const ADVERSARY_MODEL_ENDPOINT_ENV = "ADVERSARY_MODEL_ENDPOINT";
|
|
3
|
+
export declare const ADVERSARY_MODEL_TOKEN_ENV = "ADVERSARY_MODEL_TOKEN";
|
|
4
|
+
export interface ModelReviewBudget {
|
|
5
|
+
maximumOutputTokens?: number;
|
|
6
|
+
timeoutMs?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface ModelReviewRequest {
|
|
9
|
+
prompt: string;
|
|
10
|
+
input: unknown;
|
|
11
|
+
schema: Record<string, unknown>;
|
|
12
|
+
budget?: ModelReviewBudget;
|
|
13
|
+
}
|
|
14
|
+
export interface ModelReviewUsage {
|
|
15
|
+
inputTokens?: number;
|
|
16
|
+
outputTokens?: number;
|
|
17
|
+
}
|
|
18
|
+
export interface ModelReviewResult<T = unknown> {
|
|
19
|
+
output: T;
|
|
20
|
+
provider: string;
|
|
21
|
+
model: string;
|
|
22
|
+
usage?: ModelReviewUsage;
|
|
23
|
+
}
|
|
24
|
+
export interface ReviewModel {
|
|
25
|
+
review<T = unknown>(request: ModelReviewRequest): Promise<ModelReviewResult<T>>;
|
|
26
|
+
}
|
|
27
|
+
export type ModelEnvironment = Readonly<Record<string, string | undefined>>;
|
|
28
|
+
export declare class ModelUnavailableError extends Error {
|
|
29
|
+
constructor(message?: string);
|
|
30
|
+
}
|
|
31
|
+
export declare class ModelReviewError extends Error {
|
|
32
|
+
readonly code?: string;
|
|
33
|
+
readonly retryable: boolean;
|
|
34
|
+
constructor(message: string, options?: {
|
|
35
|
+
code?: string;
|
|
36
|
+
retryable?: boolean;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
export declare function createModelFromEnvironment(environment?: ModelEnvironment): ReviewModel;
|
|
40
|
+
export declare function unavailableModel(): ReviewModel;
|
|
41
|
+
export declare class BrokerReviewModel implements ReviewModel {
|
|
42
|
+
#private;
|
|
43
|
+
readonly endpoint: string;
|
|
44
|
+
constructor(endpoint: string, token: string);
|
|
45
|
+
review<T = unknown>(request: ModelReviewRequest): Promise<ModelReviewResult<T>>;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,gCAAgC,IAAI,CAAC;AAClD,eAAO,MAAM,4BAA4B,6BAA6B,CAAC;AACvE,eAAO,MAAM,yBAAyB,0BAA0B,CAAC;AAWjE,MAAM,WAAW,iBAAiB;IAChC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB,CAAC,CAAC,GAAG,OAAO;IAC5C,MAAM,EAAE,CAAC,CAAC;IACV,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,gBAAgB,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,CAAC,GAAG,OAAO,EAAE,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC;CACjF;AAED,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;AA0B5E,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,SAA8D;CAIlF;AAED,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;gBAEhB,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,CAAA;KAAO;CAMlF;AAED,wBAAgB,0BAA0B,CACxC,WAAW,GAAE,gBAA8B,GAC1C,WAAW,CAOb;AAED,wBAAgB,gBAAgB,IAAI,WAAW,CAM9C;AAED,qBAAa,iBAAkB,YAAW,WAAW;;IACnD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBAGd,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAuBrC,MAAM,CAAC,CAAC,GAAG,OAAO,EAAE,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC;CA0EtF"}
|
package/dist/model.js
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
2
|
+
export const ADVERSARY_MODEL_PROTOCOL_VERSION = 1;
|
|
3
|
+
export const ADVERSARY_MODEL_ENDPOINT_ENV = "ADVERSARY_MODEL_ENDPOINT";
|
|
4
|
+
export const ADVERSARY_MODEL_TOKEN_ENV = "ADVERSARY_MODEL_TOKEN";
|
|
5
|
+
const DEFAULT_MODEL_TIMEOUT_MS = 120_000;
|
|
6
|
+
const MAX_MODEL_TIMEOUT_MS = 600_000;
|
|
7
|
+
const DEFAULT_MAXIMUM_OUTPUT_TOKENS = 8_192;
|
|
8
|
+
const MAX_MAXIMUM_OUTPUT_TOKENS = 65_536;
|
|
9
|
+
const MAX_PROMPT_BYTES = 256 << 10;
|
|
10
|
+
const MAX_INPUT_BYTES = 4 << 20;
|
|
11
|
+
const MAX_SCHEMA_BYTES = 512 << 10;
|
|
12
|
+
const MAX_RESPONSE_BYTES = 4 << 20;
|
|
13
|
+
export class ModelUnavailableError extends Error {
|
|
14
|
+
constructor(message = "Model review is unavailable for this adversary execution.") {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "ModelUnavailableError";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export class ModelReviewError extends Error {
|
|
20
|
+
code;
|
|
21
|
+
retryable;
|
|
22
|
+
constructor(message, options = {}) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = "ModelReviewError";
|
|
25
|
+
this.code = options.code;
|
|
26
|
+
this.retryable = options.retryable ?? false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function createModelFromEnvironment(environment = process.env) {
|
|
30
|
+
const endpoint = environment[ADVERSARY_MODEL_ENDPOINT_ENV]?.trim();
|
|
31
|
+
const token = environment[ADVERSARY_MODEL_TOKEN_ENV]?.trim();
|
|
32
|
+
if (endpoint === undefined || endpoint === "" || token === undefined || token === "") {
|
|
33
|
+
return unavailableModel();
|
|
34
|
+
}
|
|
35
|
+
return new BrokerReviewModel(endpoint, token);
|
|
36
|
+
}
|
|
37
|
+
export function unavailableModel() {
|
|
38
|
+
return Object.freeze({
|
|
39
|
+
async review() {
|
|
40
|
+
throw new ModelUnavailableError();
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
export class BrokerReviewModel {
|
|
45
|
+
endpoint;
|
|
46
|
+
#token;
|
|
47
|
+
constructor(endpoint, token) {
|
|
48
|
+
const parsed = new URL(endpoint);
|
|
49
|
+
if (parsed.protocol !== "http:" ||
|
|
50
|
+
(parsed.hostname !== "127.0.0.1" &&
|
|
51
|
+
parsed.hostname !== "::1" &&
|
|
52
|
+
parsed.hostname !== "[::1]" &&
|
|
53
|
+
parsed.hostname !== "localhost")) {
|
|
54
|
+
throw new ModelReviewError("The model broker endpoint must use HTTP on the local loopback interface.", { code: "invalid_broker_endpoint" });
|
|
55
|
+
}
|
|
56
|
+
if (token.trim() === "") {
|
|
57
|
+
throw new ModelReviewError("The model broker token must not be empty.", {
|
|
58
|
+
code: "invalid_broker_token",
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
this.endpoint = parsed.toString();
|
|
62
|
+
this.#token = token;
|
|
63
|
+
}
|
|
64
|
+
async review(request) {
|
|
65
|
+
const normalized = normalizeRequest(request);
|
|
66
|
+
const controller = new AbortController();
|
|
67
|
+
const timeout = setTimeout(() => controller.abort(), normalized.budget.timeoutMs);
|
|
68
|
+
try {
|
|
69
|
+
let response;
|
|
70
|
+
try {
|
|
71
|
+
response = await fetch(this.endpoint, {
|
|
72
|
+
method: "POST",
|
|
73
|
+
headers: {
|
|
74
|
+
accept: "application/json",
|
|
75
|
+
authorization: `Bearer ${this.#token}`,
|
|
76
|
+
"content-type": "application/json",
|
|
77
|
+
"x-adversary-model-protocol": String(ADVERSARY_MODEL_PROTOCOL_VERSION),
|
|
78
|
+
},
|
|
79
|
+
body: JSON.stringify({
|
|
80
|
+
protocolVersion: ADVERSARY_MODEL_PROTOCOL_VERSION,
|
|
81
|
+
prompt: normalized.prompt,
|
|
82
|
+
input: normalized.input,
|
|
83
|
+
schema: normalized.schema,
|
|
84
|
+
budget: normalized.budget,
|
|
85
|
+
}),
|
|
86
|
+
signal: controller.signal,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
if (controller.signal.aborted) {
|
|
91
|
+
throw modelTimeoutError(normalized.budget.timeoutMs);
|
|
92
|
+
}
|
|
93
|
+
throw new ModelReviewError(`Model broker request failed: ${error instanceof Error ? error.message : String(error)}`, { code: "broker_unavailable", retryable: true });
|
|
94
|
+
}
|
|
95
|
+
let body;
|
|
96
|
+
try {
|
|
97
|
+
body = await readBoundedResponse(response);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
if (controller.signal.aborted) {
|
|
101
|
+
throw modelTimeoutError(normalized.budget.timeoutMs);
|
|
102
|
+
}
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
let decoded;
|
|
106
|
+
try {
|
|
107
|
+
decoded = JSON.parse(body);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
throw new ModelReviewError("Model broker returned malformed JSON.", {
|
|
111
|
+
code: "invalid_broker_response",
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
if (!response.ok) {
|
|
115
|
+
const failure = decoded;
|
|
116
|
+
throw new ModelReviewError(failure.error?.message ?? `Model broker returned HTTP ${response.status}.`, {
|
|
117
|
+
code: failure.error?.code ?? "model_review_failed",
|
|
118
|
+
retryable: failure.error?.retryable ?? response.status >= 500,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
const envelope = requireBrokerResponse(decoded);
|
|
122
|
+
validateModelOutput(normalized.schema, envelope.output);
|
|
123
|
+
return {
|
|
124
|
+
output: envelope.output,
|
|
125
|
+
provider: envelope.provider,
|
|
126
|
+
model: envelope.model,
|
|
127
|
+
...(envelope.usage === undefined ? {} : { usage: envelope.usage }),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
clearTimeout(timeout);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function modelTimeoutError(timeoutMs) {
|
|
136
|
+
return new ModelReviewError(`Model review exceeded its ${timeoutMs}ms timeout.`, {
|
|
137
|
+
code: "model_timeout",
|
|
138
|
+
retryable: true,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
function normalizeRequest(request) {
|
|
142
|
+
if (typeof request !== "object" || request === null) {
|
|
143
|
+
throw new ModelReviewError("Model review request must be an object.", {
|
|
144
|
+
code: "invalid_model_request",
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
if (typeof request.prompt !== "string" || request.prompt.trim() === "") {
|
|
148
|
+
throw new ModelReviewError("Model review prompt must be a non-empty string.", {
|
|
149
|
+
code: "invalid_model_request",
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
const promptBytes = Buffer.byteLength(request.prompt, "utf8");
|
|
153
|
+
if (promptBytes > MAX_PROMPT_BYTES) {
|
|
154
|
+
throw new ModelReviewError(`Model review prompt exceeds ${MAX_PROMPT_BYTES} bytes.`, {
|
|
155
|
+
code: "model_request_too_large",
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
requireJsonSize(request.input, "input", MAX_INPUT_BYTES);
|
|
159
|
+
if (typeof request.schema !== "object" ||
|
|
160
|
+
request.schema === null ||
|
|
161
|
+
Array.isArray(request.schema)) {
|
|
162
|
+
throw new ModelReviewError("Model review schema must be a JSON Schema object.", {
|
|
163
|
+
code: "invalid_model_schema",
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
requireJsonSize(request.schema, "schema", MAX_SCHEMA_BYTES);
|
|
167
|
+
const maximumOutputTokens = request.budget?.maximumOutputTokens ?? DEFAULT_MAXIMUM_OUTPUT_TOKENS;
|
|
168
|
+
const timeoutMs = request.budget?.timeoutMs ?? DEFAULT_MODEL_TIMEOUT_MS;
|
|
169
|
+
requireIntegerRange(maximumOutputTokens, "budget.maximumOutputTokens", 1, MAX_MAXIMUM_OUTPUT_TOKENS);
|
|
170
|
+
requireIntegerRange(timeoutMs, "budget.timeoutMs", 1, MAX_MODEL_TIMEOUT_MS);
|
|
171
|
+
return {
|
|
172
|
+
prompt: request.prompt,
|
|
173
|
+
input: request.input,
|
|
174
|
+
schema: request.schema,
|
|
175
|
+
budget: { maximumOutputTokens, timeoutMs },
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function requireJsonSize(value, name, maximum) {
|
|
179
|
+
let encoded;
|
|
180
|
+
try {
|
|
181
|
+
encoded = JSON.stringify(value);
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
throw new ModelReviewError(`Model review ${name} must be JSON-serializable: ${error instanceof Error ? error.message : String(error)}`, { code: "invalid_model_request" });
|
|
185
|
+
}
|
|
186
|
+
if (encoded === undefined) {
|
|
187
|
+
throw new ModelReviewError(`Model review ${name} must be JSON-serializable.`, {
|
|
188
|
+
code: "invalid_model_request",
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
if (Buffer.byteLength(encoded, "utf8") > maximum) {
|
|
192
|
+
throw new ModelReviewError(`Model review ${name} exceeds ${maximum} bytes.`, {
|
|
193
|
+
code: "model_request_too_large",
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function requireIntegerRange(value, name, minimum, maximum) {
|
|
198
|
+
if (!Number.isInteger(value) || value < minimum || value > maximum) {
|
|
199
|
+
throw new ModelReviewError(`${name} must be an integer from ${minimum} through ${maximum}.`, {
|
|
200
|
+
code: "invalid_model_budget",
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async function readBoundedResponse(response) {
|
|
205
|
+
const declared = response.headers.get("content-length");
|
|
206
|
+
if (declared !== null && Number(declared) > MAX_RESPONSE_BYTES) {
|
|
207
|
+
throw new ModelReviewError(`Model broker response exceeds ${MAX_RESPONSE_BYTES} bytes.`, {
|
|
208
|
+
code: "model_response_too_large",
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
if (response.body === null) {
|
|
212
|
+
return "";
|
|
213
|
+
}
|
|
214
|
+
const reader = response.body.getReader();
|
|
215
|
+
const chunks = [];
|
|
216
|
+
let size = 0;
|
|
217
|
+
for (;;) {
|
|
218
|
+
const { done, value } = await reader.read();
|
|
219
|
+
if (done) {
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
size += value.byteLength;
|
|
223
|
+
if (size > MAX_RESPONSE_BYTES) {
|
|
224
|
+
await reader.cancel();
|
|
225
|
+
throw new ModelReviewError(`Model broker response exceeds ${MAX_RESPONSE_BYTES} bytes.`, {
|
|
226
|
+
code: "model_response_too_large",
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
chunks.push(value);
|
|
230
|
+
}
|
|
231
|
+
const joined = new Uint8Array(size);
|
|
232
|
+
let offset = 0;
|
|
233
|
+
for (const chunk of chunks) {
|
|
234
|
+
joined.set(chunk, offset);
|
|
235
|
+
offset += chunk.byteLength;
|
|
236
|
+
}
|
|
237
|
+
return new TextDecoder().decode(joined);
|
|
238
|
+
}
|
|
239
|
+
function requireBrokerResponse(value) {
|
|
240
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
241
|
+
throw new ModelReviewError("Model broker response must be an object.", {
|
|
242
|
+
code: "invalid_broker_response",
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
const response = value;
|
|
246
|
+
if (response.protocolVersion !== ADVERSARY_MODEL_PROTOCOL_VERSION) {
|
|
247
|
+
throw new ModelReviewError(`Model broker protocol version must be ${ADVERSARY_MODEL_PROTOCOL_VERSION}.`, { code: "unsupported_model_protocol" });
|
|
248
|
+
}
|
|
249
|
+
if (typeof response.provider !== "string" || response.provider.trim() === "") {
|
|
250
|
+
throw new ModelReviewError("Model broker response provider must be a non-empty string.", {
|
|
251
|
+
code: "invalid_broker_response",
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
if (typeof response.model !== "string" || response.model.trim() === "") {
|
|
255
|
+
throw new ModelReviewError("Model broker response model must be a non-empty string.", {
|
|
256
|
+
code: "invalid_broker_response",
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
if (!Object.hasOwn(response, "output")) {
|
|
260
|
+
throw new ModelReviewError("Model broker response is missing output.", {
|
|
261
|
+
code: "invalid_broker_response",
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
if (response.usage !== undefined) {
|
|
265
|
+
validateUsage(response.usage);
|
|
266
|
+
}
|
|
267
|
+
return response;
|
|
268
|
+
}
|
|
269
|
+
function validateUsage(value) {
|
|
270
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
271
|
+
throw new ModelReviewError("Model broker response usage must be an object.", {
|
|
272
|
+
code: "invalid_broker_response",
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
for (const field of ["inputTokens", "outputTokens"]) {
|
|
276
|
+
const count = value[field];
|
|
277
|
+
if (count !== undefined && (!Number.isInteger(count) || count < 0)) {
|
|
278
|
+
throw new ModelReviewError(`Model broker response usage.${field} must be a non-negative integer.`, {
|
|
279
|
+
code: "invalid_broker_response",
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
function validateModelOutput(schema, output) {
|
|
285
|
+
const ajv = new Ajv2020({ allErrors: true, strict: true });
|
|
286
|
+
let validate;
|
|
287
|
+
try {
|
|
288
|
+
validate = ajv.compile(schema);
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
throw new ModelReviewError(`Model review schema is invalid: ${error instanceof Error ? error.message : String(error)}`, { code: "invalid_model_schema" });
|
|
292
|
+
}
|
|
293
|
+
if (!validate(output)) {
|
|
294
|
+
const detail = ajv.errorsText(validate.errors, { separator: "; " });
|
|
295
|
+
throw new ModelReviewError(`Model output does not match the requested schema: ${detail}`, {
|
|
296
|
+
code: "invalid_model_output",
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"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","sourcesContent":["import { Ajv2020 } from \"ajv/dist/2020.js\";\n\nexport const ADVERSARY_MODEL_PROTOCOL_VERSION = 1;\nexport const ADVERSARY_MODEL_ENDPOINT_ENV = \"ADVERSARY_MODEL_ENDPOINT\";\nexport const ADVERSARY_MODEL_TOKEN_ENV = \"ADVERSARY_MODEL_TOKEN\";\n\nconst DEFAULT_MODEL_TIMEOUT_MS = 120_000;\nconst MAX_MODEL_TIMEOUT_MS = 600_000;\nconst DEFAULT_MAXIMUM_OUTPUT_TOKENS = 8_192;\nconst MAX_MAXIMUM_OUTPUT_TOKENS = 65_536;\nconst MAX_PROMPT_BYTES = 256 << 10;\nconst MAX_INPUT_BYTES = 4 << 20;\nconst MAX_SCHEMA_BYTES = 512 << 10;\nconst MAX_RESPONSE_BYTES = 4 << 20;\n\nexport interface ModelReviewBudget {\n maximumOutputTokens?: number;\n timeoutMs?: number;\n}\n\nexport interface ModelReviewRequest {\n prompt: string;\n input: unknown;\n schema: Record<string, unknown>;\n budget?: ModelReviewBudget;\n}\n\nexport interface ModelReviewUsage {\n inputTokens?: number;\n outputTokens?: number;\n}\n\nexport interface ModelReviewResult<T = unknown> {\n output: T;\n provider: string;\n model: string;\n usage?: ModelReviewUsage;\n}\n\nexport interface ReviewModel {\n review<T = unknown>(request: ModelReviewRequest): Promise<ModelReviewResult<T>>;\n}\n\nexport type ModelEnvironment = Readonly<Record<string, string | undefined>>;\n\ninterface ModelBrokerRequest extends ModelReviewRequest {\n protocolVersion: typeof ADVERSARY_MODEL_PROTOCOL_VERSION;\n}\n\ninterface ModelBrokerResponse {\n protocolVersion: typeof ADVERSARY_MODEL_PROTOCOL_VERSION;\n output: unknown;\n provider: string;\n model: string;\n usage?: ModelReviewUsage;\n}\n\ninterface ModelBrokerErrorResponse {\n error?: {\n code?: string;\n message?: string;\n retryable?: boolean;\n };\n}\n\ntype NormalizedModelReviewRequest = Omit<ModelReviewRequest, \"budget\"> & {\n budget: Required<ModelReviewBudget>;\n};\n\nexport class ModelUnavailableError extends Error {\n constructor(message = \"Model review is unavailable for this adversary execution.\") {\n super(message);\n this.name = \"ModelUnavailableError\";\n }\n}\n\nexport class ModelReviewError extends Error {\n readonly code?: string;\n readonly retryable: boolean;\n\n constructor(message: string, options: { code?: string; retryable?: boolean } = {}) {\n super(message);\n this.name = \"ModelReviewError\";\n this.code = options.code;\n this.retryable = options.retryable ?? false;\n }\n}\n\nexport function createModelFromEnvironment(\n environment: ModelEnvironment = process.env,\n): ReviewModel {\n const endpoint = environment[ADVERSARY_MODEL_ENDPOINT_ENV]?.trim();\n const token = environment[ADVERSARY_MODEL_TOKEN_ENV]?.trim();\n if (endpoint === undefined || endpoint === \"\" || token === undefined || token === \"\") {\n return unavailableModel();\n }\n return new BrokerReviewModel(endpoint, token);\n}\n\nexport function unavailableModel(): ReviewModel {\n return Object.freeze({\n async review(): Promise<never> {\n throw new ModelUnavailableError();\n },\n });\n}\n\nexport class BrokerReviewModel implements ReviewModel {\n readonly endpoint: string;\n readonly #token: string;\n\n constructor(endpoint: string, token: string) {\n const parsed = new URL(endpoint);\n if (\n parsed.protocol !== \"http:\" ||\n (parsed.hostname !== \"127.0.0.1\" &&\n parsed.hostname !== \"::1\" &&\n parsed.hostname !== \"[::1]\" &&\n parsed.hostname !== \"localhost\")\n ) {\n throw new ModelReviewError(\n \"The model broker endpoint must use HTTP on the local loopback interface.\",\n { code: \"invalid_broker_endpoint\" },\n );\n }\n if (token.trim() === \"\") {\n throw new ModelReviewError(\"The model broker token must not be empty.\", {\n code: \"invalid_broker_token\",\n });\n }\n this.endpoint = parsed.toString();\n this.#token = token;\n }\n\n async review<T = unknown>(request: ModelReviewRequest): Promise<ModelReviewResult<T>> {\n const normalized = normalizeRequest(request);\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), normalized.budget.timeoutMs);\n try {\n let response: Response;\n try {\n response = await fetch(this.endpoint, {\n method: \"POST\",\n headers: {\n accept: \"application/json\",\n authorization: `Bearer ${this.#token}`,\n \"content-type\": \"application/json\",\n \"x-adversary-model-protocol\": String(ADVERSARY_MODEL_PROTOCOL_VERSION),\n },\n body: JSON.stringify({\n protocolVersion: ADVERSARY_MODEL_PROTOCOL_VERSION,\n prompt: normalized.prompt,\n input: normalized.input,\n schema: normalized.schema,\n budget: normalized.budget,\n } satisfies ModelBrokerRequest),\n signal: controller.signal,\n });\n } catch (error) {\n if (controller.signal.aborted) {\n throw modelTimeoutError(normalized.budget.timeoutMs);\n }\n throw new ModelReviewError(\n `Model broker request failed: ${error instanceof Error ? error.message : String(error)}`,\n { code: \"broker_unavailable\", retryable: true },\n );\n }\n\n let body: string;\n try {\n body = await readBoundedResponse(response);\n } catch (error) {\n if (controller.signal.aborted) {\n throw modelTimeoutError(normalized.budget.timeoutMs);\n }\n throw error;\n }\n let decoded: unknown;\n try {\n decoded = JSON.parse(body);\n } catch {\n throw new ModelReviewError(\"Model broker returned malformed JSON.\", {\n code: \"invalid_broker_response\",\n });\n }\n if (!response.ok) {\n const failure = decoded as ModelBrokerErrorResponse;\n throw new ModelReviewError(\n failure.error?.message ?? `Model broker returned HTTP ${response.status}.`,\n {\n code: failure.error?.code ?? \"model_review_failed\",\n retryable: failure.error?.retryable ?? response.status >= 500,\n },\n );\n }\n\n const envelope = requireBrokerResponse(decoded);\n validateModelOutput(normalized.schema, envelope.output);\n return {\n output: envelope.output as T,\n provider: envelope.provider,\n model: envelope.model,\n ...(envelope.usage === undefined ? {} : { usage: envelope.usage }),\n };\n } finally {\n clearTimeout(timeout);\n }\n }\n}\n\nfunction modelTimeoutError(timeoutMs: number): ModelReviewError {\n return new ModelReviewError(`Model review exceeded its ${timeoutMs}ms timeout.`, {\n code: \"model_timeout\",\n retryable: true,\n });\n}\n\nfunction normalizeRequest(request: ModelReviewRequest): NormalizedModelReviewRequest {\n if (typeof request !== \"object\" || request === null) {\n throw new ModelReviewError(\"Model review request must be an object.\", {\n code: \"invalid_model_request\",\n });\n }\n if (typeof request.prompt !== \"string\" || request.prompt.trim() === \"\") {\n throw new ModelReviewError(\"Model review prompt must be a non-empty string.\", {\n code: \"invalid_model_request\",\n });\n }\n const promptBytes = Buffer.byteLength(request.prompt, \"utf8\");\n if (promptBytes > MAX_PROMPT_BYTES) {\n throw new ModelReviewError(`Model review prompt exceeds ${MAX_PROMPT_BYTES} bytes.`, {\n code: \"model_request_too_large\",\n });\n }\n requireJsonSize(request.input, \"input\", MAX_INPUT_BYTES);\n if (\n typeof request.schema !== \"object\" ||\n request.schema === null ||\n Array.isArray(request.schema)\n ) {\n throw new ModelReviewError(\"Model review schema must be a JSON Schema object.\", {\n code: \"invalid_model_schema\",\n });\n }\n requireJsonSize(request.schema, \"schema\", MAX_SCHEMA_BYTES);\n\n const maximumOutputTokens = request.budget?.maximumOutputTokens ?? DEFAULT_MAXIMUM_OUTPUT_TOKENS;\n const timeoutMs = request.budget?.timeoutMs ?? DEFAULT_MODEL_TIMEOUT_MS;\n requireIntegerRange(\n maximumOutputTokens,\n \"budget.maximumOutputTokens\",\n 1,\n MAX_MAXIMUM_OUTPUT_TOKENS,\n );\n requireIntegerRange(timeoutMs, \"budget.timeoutMs\", 1, MAX_MODEL_TIMEOUT_MS);\n return {\n prompt: request.prompt,\n input: request.input,\n schema: request.schema,\n budget: { maximumOutputTokens, timeoutMs },\n };\n}\n\nfunction requireJsonSize(value: unknown, name: string, maximum: number): void {\n let encoded: string | undefined;\n try {\n encoded = JSON.stringify(value);\n } catch (error) {\n throw new ModelReviewError(\n `Model review ${name} must be JSON-serializable: ${error instanceof Error ? error.message : String(error)}`,\n { code: \"invalid_model_request\" },\n );\n }\n if (encoded === undefined) {\n throw new ModelReviewError(`Model review ${name} must be JSON-serializable.`, {\n code: \"invalid_model_request\",\n });\n }\n if (Buffer.byteLength(encoded, \"utf8\") > maximum) {\n throw new ModelReviewError(`Model review ${name} exceeds ${maximum} bytes.`, {\n code: \"model_request_too_large\",\n });\n }\n}\n\nfunction requireIntegerRange(value: number, name: string, minimum: number, maximum: number): void {\n if (!Number.isInteger(value) || value < minimum || value > maximum) {\n throw new ModelReviewError(`${name} must be an integer from ${minimum} through ${maximum}.`, {\n code: \"invalid_model_budget\",\n });\n }\n}\n\nasync function readBoundedResponse(response: Response): Promise<string> {\n const declared = response.headers.get(\"content-length\");\n if (declared !== null && Number(declared) > MAX_RESPONSE_BYTES) {\n throw new ModelReviewError(`Model broker response exceeds ${MAX_RESPONSE_BYTES} bytes.`, {\n code: \"model_response_too_large\",\n });\n }\n if (response.body === null) {\n return \"\";\n }\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let size = 0;\n for (;;) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n size += value.byteLength;\n if (size > MAX_RESPONSE_BYTES) {\n await reader.cancel();\n throw new ModelReviewError(`Model broker response exceeds ${MAX_RESPONSE_BYTES} bytes.`, {\n code: \"model_response_too_large\",\n });\n }\n chunks.push(value);\n }\n const joined = new Uint8Array(size);\n let offset = 0;\n for (const chunk of chunks) {\n joined.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return new TextDecoder().decode(joined);\n}\n\nfunction requireBrokerResponse(value: unknown): ModelBrokerResponse {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new ModelReviewError(\"Model broker response must be an object.\", {\n code: \"invalid_broker_response\",\n });\n }\n const response = value as Partial<ModelBrokerResponse>;\n if (response.protocolVersion !== ADVERSARY_MODEL_PROTOCOL_VERSION) {\n throw new ModelReviewError(\n `Model broker protocol version must be ${ADVERSARY_MODEL_PROTOCOL_VERSION}.`,\n { code: \"unsupported_model_protocol\" },\n );\n }\n if (typeof response.provider !== \"string\" || response.provider.trim() === \"\") {\n throw new ModelReviewError(\"Model broker response provider must be a non-empty string.\", {\n code: \"invalid_broker_response\",\n });\n }\n if (typeof response.model !== \"string\" || response.model.trim() === \"\") {\n throw new ModelReviewError(\"Model broker response model must be a non-empty string.\", {\n code: \"invalid_broker_response\",\n });\n }\n if (!Object.hasOwn(response, \"output\")) {\n throw new ModelReviewError(\"Model broker response is missing output.\", {\n code: \"invalid_broker_response\",\n });\n }\n if (response.usage !== undefined) {\n validateUsage(response.usage);\n }\n return response as ModelBrokerResponse;\n}\n\nfunction validateUsage(value: unknown): asserts value is ModelReviewUsage {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new ModelReviewError(\"Model broker response usage must be an object.\", {\n code: \"invalid_broker_response\",\n });\n }\n for (const field of [\"inputTokens\", \"outputTokens\"] as const) {\n const count = (value as ModelReviewUsage)[field];\n if (count !== undefined && (!Number.isInteger(count) || count < 0)) {\n throw new ModelReviewError(\n `Model broker response usage.${field} must be a non-negative integer.`,\n {\n code: \"invalid_broker_response\",\n },\n );\n }\n }\n}\n\nfunction validateModelOutput(schema: Record<string, unknown>, output: unknown): void {\n const ajv = new Ajv2020({ allErrors: true, strict: true });\n let validate: ReturnType<Ajv2020[\"compile\"]>;\n try {\n validate = ajv.compile(schema);\n } catch (error) {\n throw new ModelReviewError(\n `Model review schema is invalid: ${error instanceof Error ? error.message : String(error)}`,\n { code: \"invalid_model_schema\" },\n );\n }\n if (!validate(output)) {\n const detail = ajv.errorsText(validate.errors, { separator: \"; \" });\n throw new ModelReviewError(`Model output does not match the requested schema: ${detail}`, {\n code: \"invalid_model_output\",\n });\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -100,6 +100,10 @@
|
|
|
100
100
|
}
|
|
101
101
|
},
|
|
102
102
|
"network": { "type": "boolean" },
|
|
103
|
+
"model": {
|
|
104
|
+
"type": "boolean",
|
|
105
|
+
"description": "Request access to the CLI-owned model review broker."
|
|
106
|
+
},
|
|
103
107
|
"environment": {
|
|
104
108
|
"type": "object",
|
|
105
109
|
"additionalProperties": false,
|