@ai-agent-forge/plugin-sdk 0.85.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 +27 -0
- package/dist/artifact.d.ts +20 -0
- package/dist/artifact.d.ts.map +1 -0
- package/dist/artifact.js +63 -0
- package/dist/artifact.js.map +1 -0
- package/dist/capability-manifest.d.ts +11 -0
- package/dist/capability-manifest.d.ts.map +1 -0
- package/dist/capability-manifest.js +188 -0
- package/dist/capability-manifest.js.map +1 -0
- package/dist/common.d.ts +24 -0
- package/dist/common.d.ts.map +1 -0
- package/dist/common.js +115 -0
- package/dist/common.js.map +1 -0
- package/dist/context-budget.d.ts +33 -0
- package/dist/context-budget.d.ts.map +1 -0
- package/dist/context-budget.js +124 -0
- package/dist/context-budget.js.map +1 -0
- package/dist/diff.d.ts +38 -0
- package/dist/diff.d.ts.map +1 -0
- package/dist/diff.js +164 -0
- package/dist/diff.js.map +1 -0
- package/dist/durable.d.ts +108 -0
- package/dist/durable.d.ts.map +1 -0
- package/dist/durable.js +15 -0
- package/dist/durable.js.map +1 -0
- package/dist/ecosystem-manifest.d.ts +56 -0
- package/dist/ecosystem-manifest.d.ts.map +1 -0
- package/dist/ecosystem-manifest.js +11 -0
- package/dist/ecosystem-manifest.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/memory.d.ts +370 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/memory.js +26 -0
- package/dist/memory.js.map +1 -0
- package/dist/observability.d.ts +354 -0
- package/dist/observability.d.ts.map +1 -0
- package/dist/observability.js +33 -0
- package/dist/observability.js.map +1 -0
- package/dist/operation.d.ts +60 -0
- package/dist/operation.d.ts.map +1 -0
- package/dist/operation.js +202 -0
- package/dist/operation.js.map +1 -0
- package/dist/public-api.d.ts +1977 -0
- package/dist/public-api.d.ts.map +1 -0
- package/dist/public-api.js +237 -0
- package/dist/public-api.js.map +1 -0
- package/dist/render.d.ts +38 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +112 -0
- package/dist/render.js.map +1 -0
- package/dist/session-lineage.d.ts +36 -0
- package/dist/session-lineage.d.ts.map +1 -0
- package/dist/session-lineage.js +134 -0
- package/dist/session-lineage.js.map +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-budget.d.ts","sourceRoot":"","sources":["../src/context-budget.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD,yFAAyF;AACzF,MAAM,WAAW,qBAAqB;IACrC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,8BAA8B;IAC9C,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,uFAAuF;IACvF,QAAQ,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,CAAC;IACrC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,yBAAyB;IACzC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,QAAQ,CAAC;IACxC,QAAQ,CAAC,WAAW,EAAE,SAAS,SAAS,EAAE,CAAC;IAC3C,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,QAAA,MAAM,gCAAgC,EAAE,qBAItC,CAAC;AA+EH;;;GAGG;AACH,wBAAgB,sBAAsB,CACrC,KAAK,EAAE,8BAA8B,EACrC,MAAM,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC,GACrC,yBAAyB,CAuC3B;AAED,OAAO,EAAE,gCAAgC,EAAE,CAAC","sourcesContent":["import type { JsonValue } from \"./common.ts\";\nimport type { PluginErrorV1 } from \"./operation.ts\";\n\n/** Shared egress budget for every plugin result that is projected into model context. */\nexport interface PluginContextBudgetV1 {\n\treadonly maxOutputBytes: number;\n\treadonly maxInlineItems: number;\n\treadonly maxCellChars: number;\n}\n\nexport interface PluginContextProjectionInputV1 {\n\treadonly version: 1;\n\treadonly operationId: string;\n\t/** Already summarized values only. Raw rows/payloads are rejected at this boundary. */\n\treadonly items: readonly JsonValue[];\n\treadonly outputArtifactRef?: string;\n\treadonly truncated?: boolean;\n}\n\nexport interface PluginContextProjectionV1 {\n\treadonly version: 1;\n\treadonly operationId: string;\n\treadonly status: \"succeeded\" | \"failed\";\n\treadonly inlineItems: readonly JsonValue[];\n\treadonly truncated: boolean;\n\treadonly outputArtifactRef?: string;\n\treadonly error?: PluginErrorV1;\n}\n\nconst DEFAULT_PLUGIN_CONTEXT_BUDGET_V1: PluginContextBudgetV1 = Object.freeze({\n\tmaxOutputBytes: 64 * 1024,\n\tmaxInlineItems: 20,\n\tmaxCellChars: 256,\n});\n\nfunction error(code: string, message: string): PluginErrorV1 {\n\treturn { version: 1, code, message };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction assertPositiveInteger(value: unknown, label: string): asserts value is number {\n\tif (!Number.isSafeInteger(value) || (value as number) <= 0)\n\t\tthrow new Error(`${label} must be a positive safe integer`);\n}\n\nfunction assertNonNegativeInteger(value: unknown, label: string): asserts value is number {\n\tif (!Number.isSafeInteger(value) || (value as number) < 0)\n\t\tthrow new Error(`${label} must be a non-negative safe integer`);\n}\n\nfunction isJsonValue(value: unknown): value is JsonValue {\n\tif (value === null || typeof value === \"string\" || typeof value === \"boolean\") return true;\n\tif (typeof value === \"number\") return Number.isFinite(value);\n\tif (Array.isArray(value)) return value.every(isJsonValue);\n\tif (isRecord(value)) return Object.values(value).every(isJsonValue);\n\treturn false;\n}\n\nfunction containsRawPayload(value: unknown, seen = new Set<object>()): boolean {\n\tif (!isRecord(value) && !Array.isArray(value)) return false;\n\tif (seen.has(value)) return false;\n\tseen.add(value);\n\tif (isRecord(value)) {\n\t\tfor (const [key, child] of Object.entries(value)) {\n\t\t\tif ((key === \"rows\" || key === \"rawRows\" || key === \"rawPayload\" || key === \"payload\") && child !== undefined)\n\t\t\t\treturn true;\n\t\t\tif (containsRawPayload(child, seen)) return true;\n\t\t}\n\t\treturn false;\n\t}\n\treturn value.some((child) => containsRawPayload(child, seen));\n}\n\nfunction validateBudget(input: Partial<PluginContextBudgetV1> | undefined): PluginContextBudgetV1 {\n\tconst budget = { ...DEFAULT_PLUGIN_CONTEXT_BUDGET_V1, ...(input ?? {}) };\n\tassertPositiveInteger(budget.maxOutputBytes, \"maxOutputBytes\");\n\tassertNonNegativeInteger(budget.maxInlineItems, \"maxInlineItems\");\n\tassertPositiveInteger(budget.maxCellChars, \"maxCellChars\");\n\treturn Object.freeze(budget);\n}\n\nfunction boundStrings(value: JsonValue, maxCellChars: number): JsonValue {\n\tif (typeof value === \"string\") {\n\t\tif ([...value].length > maxCellChars) throw new Error(\"context string exceeds maxCellChars\");\n\t\treturn value;\n\t}\n\tif (Array.isArray(value))\n\t\treturn Object.freeze(value.map((child) => boundStrings(child, maxCellChars))) as unknown as JsonValue;\n\tif (isRecord(value)) {\n\t\treturn Object.freeze(\n\t\t\tObject.fromEntries(\n\t\t\t\tObject.entries(value).map(([key, child]) => [key, boundStrings(child as JsonValue, maxCellChars)]),\n\t\t\t),\n\t\t);\n\t}\n\treturn value;\n}\n\nfunction invalid(operationId: string, code: string, message: string): PluginContextProjectionV1 {\n\treturn Object.freeze({\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"failed\",\n\t\tinlineItems: Object.freeze([]),\n\t\ttruncated: false,\n\t\terror: error(code, message),\n\t});\n}\n\n/**\n * Projects plugin summaries into the model-visible context envelope. This is an\n * egress boundary: it never accepts raw rows or silently exceeds a byte budget.\n */\nexport function projectPluginContextV1(\n\tinput: PluginContextProjectionInputV1,\n\tlimits?: Partial<PluginContextBudgetV1>,\n): PluginContextProjectionV1 {\n\tconst operationId = isRecord(input) && typeof input.operationId === \"string\" ? input.operationId : \"invalid\";\n\ttry {\n\t\tif (!isRecord(input) || input.version !== 1) throw new Error(\"input must have version 1\");\n\t\tif (typeof input.operationId !== \"string\" || input.operationId.trim() === \"\")\n\t\t\tthrow new Error(\"operationId must be a non-empty string\");\n\t\tif (!Array.isArray(input.items)) throw new Error(\"items must be an array\");\n\t\tconst budget = validateBudget(limits);\n\t\tif (containsRawPayload(input.items)) throw new Error(\"raw payloads are not allowed in plugin context\");\n\t\tif (input.outputArtifactRef !== undefined) {\n\t\t\tif (typeof input.outputArtifactRef !== \"string\" || input.outputArtifactRef.trim() === \"\")\n\t\t\t\tthrow new Error(\"outputArtifactRef must be a non-empty string\");\n\t\t\tif (/^(?:[a-zA-Z]:[\\\\/]|[\\\\/]{1,2}|\\\\\\\\)/.test(input.outputArtifactRef))\n\t\t\t\tthrow new Error(\"outputArtifactRef must be opaque\");\n\t\t}\n\t\tconst items = input.items.map((item, index) => {\n\t\t\tif (!isJsonValue(item)) throw new Error(`items[${index}] must be JSON-safe`);\n\t\t\treturn boundStrings(item, budget.maxCellChars);\n\t\t});\n\t\tconst truncated = input.truncated === true || items.length > budget.maxInlineItems;\n\t\tconst inlineItems = Object.freeze(items.slice(0, budget.maxInlineItems));\n\t\tconst result = Object.freeze({\n\t\t\tversion: 1 as const,\n\t\t\toperationId: input.operationId,\n\t\t\tstatus: \"succeeded\" as const,\n\t\t\tinlineItems,\n\t\t\ttruncated,\n\t\t\t...(input.outputArtifactRef === undefined ? {} : { outputArtifactRef: input.outputArtifactRef }),\n\t\t});\n\t\tif (new TextEncoder().encode(JSON.stringify(result)).byteLength > budget.maxOutputBytes)\n\t\t\tthrow new Error(\"projected context exceeds maxOutputBytes\");\n\t\treturn result;\n\t} catch (cause) {\n\t\treturn invalid(\n\t\t\toperationId,\n\t\t\t\"context_projection_rejected\",\n\t\t\tcause instanceof Error ? cause.message : \"Context projection rejected\",\n\t\t);\n\t}\n}\n\nexport { DEFAULT_PLUGIN_CONTEXT_BUDGET_V1 };\n"]}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
const DEFAULT_PLUGIN_CONTEXT_BUDGET_V1 = Object.freeze({
|
|
2
|
+
maxOutputBytes: 64 * 1024,
|
|
3
|
+
maxInlineItems: 20,
|
|
4
|
+
maxCellChars: 256,
|
|
5
|
+
});
|
|
6
|
+
function error(code, message) {
|
|
7
|
+
return { version: 1, code, message };
|
|
8
|
+
}
|
|
9
|
+
function isRecord(value) {
|
|
10
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11
|
+
}
|
|
12
|
+
function assertPositiveInteger(value, label) {
|
|
13
|
+
if (!Number.isSafeInteger(value) || value <= 0)
|
|
14
|
+
throw new Error(`${label} must be a positive safe integer`);
|
|
15
|
+
}
|
|
16
|
+
function assertNonNegativeInteger(value, label) {
|
|
17
|
+
if (!Number.isSafeInteger(value) || value < 0)
|
|
18
|
+
throw new Error(`${label} must be a non-negative safe integer`);
|
|
19
|
+
}
|
|
20
|
+
function isJsonValue(value) {
|
|
21
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
22
|
+
return true;
|
|
23
|
+
if (typeof value === "number")
|
|
24
|
+
return Number.isFinite(value);
|
|
25
|
+
if (Array.isArray(value))
|
|
26
|
+
return value.every(isJsonValue);
|
|
27
|
+
if (isRecord(value))
|
|
28
|
+
return Object.values(value).every(isJsonValue);
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
function containsRawPayload(value, seen = new Set()) {
|
|
32
|
+
if (!isRecord(value) && !Array.isArray(value))
|
|
33
|
+
return false;
|
|
34
|
+
if (seen.has(value))
|
|
35
|
+
return false;
|
|
36
|
+
seen.add(value);
|
|
37
|
+
if (isRecord(value)) {
|
|
38
|
+
for (const [key, child] of Object.entries(value)) {
|
|
39
|
+
if ((key === "rows" || key === "rawRows" || key === "rawPayload" || key === "payload") && child !== undefined)
|
|
40
|
+
return true;
|
|
41
|
+
if (containsRawPayload(child, seen))
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
return value.some((child) => containsRawPayload(child, seen));
|
|
47
|
+
}
|
|
48
|
+
function validateBudget(input) {
|
|
49
|
+
const budget = { ...DEFAULT_PLUGIN_CONTEXT_BUDGET_V1, ...(input ?? {}) };
|
|
50
|
+
assertPositiveInteger(budget.maxOutputBytes, "maxOutputBytes");
|
|
51
|
+
assertNonNegativeInteger(budget.maxInlineItems, "maxInlineItems");
|
|
52
|
+
assertPositiveInteger(budget.maxCellChars, "maxCellChars");
|
|
53
|
+
return Object.freeze(budget);
|
|
54
|
+
}
|
|
55
|
+
function boundStrings(value, maxCellChars) {
|
|
56
|
+
if (typeof value === "string") {
|
|
57
|
+
if ([...value].length > maxCellChars)
|
|
58
|
+
throw new Error("context string exceeds maxCellChars");
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
if (Array.isArray(value))
|
|
62
|
+
return Object.freeze(value.map((child) => boundStrings(child, maxCellChars)));
|
|
63
|
+
if (isRecord(value)) {
|
|
64
|
+
return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, child]) => [key, boundStrings(child, maxCellChars)])));
|
|
65
|
+
}
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
function invalid(operationId, code, message) {
|
|
69
|
+
return Object.freeze({
|
|
70
|
+
version: 1,
|
|
71
|
+
operationId,
|
|
72
|
+
status: "failed",
|
|
73
|
+
inlineItems: Object.freeze([]),
|
|
74
|
+
truncated: false,
|
|
75
|
+
error: error(code, message),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Projects plugin summaries into the model-visible context envelope. This is an
|
|
80
|
+
* egress boundary: it never accepts raw rows or silently exceeds a byte budget.
|
|
81
|
+
*/
|
|
82
|
+
export function projectPluginContextV1(input, limits) {
|
|
83
|
+
const operationId = isRecord(input) && typeof input.operationId === "string" ? input.operationId : "invalid";
|
|
84
|
+
try {
|
|
85
|
+
if (!isRecord(input) || input.version !== 1)
|
|
86
|
+
throw new Error("input must have version 1");
|
|
87
|
+
if (typeof input.operationId !== "string" || input.operationId.trim() === "")
|
|
88
|
+
throw new Error("operationId must be a non-empty string");
|
|
89
|
+
if (!Array.isArray(input.items))
|
|
90
|
+
throw new Error("items must be an array");
|
|
91
|
+
const budget = validateBudget(limits);
|
|
92
|
+
if (containsRawPayload(input.items))
|
|
93
|
+
throw new Error("raw payloads are not allowed in plugin context");
|
|
94
|
+
if (input.outputArtifactRef !== undefined) {
|
|
95
|
+
if (typeof input.outputArtifactRef !== "string" || input.outputArtifactRef.trim() === "")
|
|
96
|
+
throw new Error("outputArtifactRef must be a non-empty string");
|
|
97
|
+
if (/^(?:[a-zA-Z]:[\\/]|[\\/]{1,2}|\\\\)/.test(input.outputArtifactRef))
|
|
98
|
+
throw new Error("outputArtifactRef must be opaque");
|
|
99
|
+
}
|
|
100
|
+
const items = input.items.map((item, index) => {
|
|
101
|
+
if (!isJsonValue(item))
|
|
102
|
+
throw new Error(`items[${index}] must be JSON-safe`);
|
|
103
|
+
return boundStrings(item, budget.maxCellChars);
|
|
104
|
+
});
|
|
105
|
+
const truncated = input.truncated === true || items.length > budget.maxInlineItems;
|
|
106
|
+
const inlineItems = Object.freeze(items.slice(0, budget.maxInlineItems));
|
|
107
|
+
const result = Object.freeze({
|
|
108
|
+
version: 1,
|
|
109
|
+
operationId: input.operationId,
|
|
110
|
+
status: "succeeded",
|
|
111
|
+
inlineItems,
|
|
112
|
+
truncated,
|
|
113
|
+
...(input.outputArtifactRef === undefined ? {} : { outputArtifactRef: input.outputArtifactRef }),
|
|
114
|
+
});
|
|
115
|
+
if (new TextEncoder().encode(JSON.stringify(result)).byteLength > budget.maxOutputBytes)
|
|
116
|
+
throw new Error("projected context exceeds maxOutputBytes");
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
catch (cause) {
|
|
120
|
+
return invalid(operationId, "context_projection_rejected", cause instanceof Error ? cause.message : "Context projection rejected");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
export { DEFAULT_PLUGIN_CONTEXT_BUDGET_V1 };
|
|
124
|
+
//# sourceMappingURL=context-budget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-budget.js","sourceRoot":"","sources":["../src/context-budget.ts"],"names":[],"mappings":"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","sourcesContent":["import type { JsonValue } from \"./common.ts\";\nimport type { PluginErrorV1 } from \"./operation.ts\";\n\n/** Shared egress budget for every plugin result that is projected into model context. */\nexport interface PluginContextBudgetV1 {\n\treadonly maxOutputBytes: number;\n\treadonly maxInlineItems: number;\n\treadonly maxCellChars: number;\n}\n\nexport interface PluginContextProjectionInputV1 {\n\treadonly version: 1;\n\treadonly operationId: string;\n\t/** Already summarized values only. Raw rows/payloads are rejected at this boundary. */\n\treadonly items: readonly JsonValue[];\n\treadonly outputArtifactRef?: string;\n\treadonly truncated?: boolean;\n}\n\nexport interface PluginContextProjectionV1 {\n\treadonly version: 1;\n\treadonly operationId: string;\n\treadonly status: \"succeeded\" | \"failed\";\n\treadonly inlineItems: readonly JsonValue[];\n\treadonly truncated: boolean;\n\treadonly outputArtifactRef?: string;\n\treadonly error?: PluginErrorV1;\n}\n\nconst DEFAULT_PLUGIN_CONTEXT_BUDGET_V1: PluginContextBudgetV1 = Object.freeze({\n\tmaxOutputBytes: 64 * 1024,\n\tmaxInlineItems: 20,\n\tmaxCellChars: 256,\n});\n\nfunction error(code: string, message: string): PluginErrorV1 {\n\treturn { version: 1, code, message };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction assertPositiveInteger(value: unknown, label: string): asserts value is number {\n\tif (!Number.isSafeInteger(value) || (value as number) <= 0)\n\t\tthrow new Error(`${label} must be a positive safe integer`);\n}\n\nfunction assertNonNegativeInteger(value: unknown, label: string): asserts value is number {\n\tif (!Number.isSafeInteger(value) || (value as number) < 0)\n\t\tthrow new Error(`${label} must be a non-negative safe integer`);\n}\n\nfunction isJsonValue(value: unknown): value is JsonValue {\n\tif (value === null || typeof value === \"string\" || typeof value === \"boolean\") return true;\n\tif (typeof value === \"number\") return Number.isFinite(value);\n\tif (Array.isArray(value)) return value.every(isJsonValue);\n\tif (isRecord(value)) return Object.values(value).every(isJsonValue);\n\treturn false;\n}\n\nfunction containsRawPayload(value: unknown, seen = new Set<object>()): boolean {\n\tif (!isRecord(value) && !Array.isArray(value)) return false;\n\tif (seen.has(value)) return false;\n\tseen.add(value);\n\tif (isRecord(value)) {\n\t\tfor (const [key, child] of Object.entries(value)) {\n\t\t\tif ((key === \"rows\" || key === \"rawRows\" || key === \"rawPayload\" || key === \"payload\") && child !== undefined)\n\t\t\t\treturn true;\n\t\t\tif (containsRawPayload(child, seen)) return true;\n\t\t}\n\t\treturn false;\n\t}\n\treturn value.some((child) => containsRawPayload(child, seen));\n}\n\nfunction validateBudget(input: Partial<PluginContextBudgetV1> | undefined): PluginContextBudgetV1 {\n\tconst budget = { ...DEFAULT_PLUGIN_CONTEXT_BUDGET_V1, ...(input ?? {}) };\n\tassertPositiveInteger(budget.maxOutputBytes, \"maxOutputBytes\");\n\tassertNonNegativeInteger(budget.maxInlineItems, \"maxInlineItems\");\n\tassertPositiveInteger(budget.maxCellChars, \"maxCellChars\");\n\treturn Object.freeze(budget);\n}\n\nfunction boundStrings(value: JsonValue, maxCellChars: number): JsonValue {\n\tif (typeof value === \"string\") {\n\t\tif ([...value].length > maxCellChars) throw new Error(\"context string exceeds maxCellChars\");\n\t\treturn value;\n\t}\n\tif (Array.isArray(value))\n\t\treturn Object.freeze(value.map((child) => boundStrings(child, maxCellChars))) as unknown as JsonValue;\n\tif (isRecord(value)) {\n\t\treturn Object.freeze(\n\t\t\tObject.fromEntries(\n\t\t\t\tObject.entries(value).map(([key, child]) => [key, boundStrings(child as JsonValue, maxCellChars)]),\n\t\t\t),\n\t\t);\n\t}\n\treturn value;\n}\n\nfunction invalid(operationId: string, code: string, message: string): PluginContextProjectionV1 {\n\treturn Object.freeze({\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"failed\",\n\t\tinlineItems: Object.freeze([]),\n\t\ttruncated: false,\n\t\terror: error(code, message),\n\t});\n}\n\n/**\n * Projects plugin summaries into the model-visible context envelope. This is an\n * egress boundary: it never accepts raw rows or silently exceeds a byte budget.\n */\nexport function projectPluginContextV1(\n\tinput: PluginContextProjectionInputV1,\n\tlimits?: Partial<PluginContextBudgetV1>,\n): PluginContextProjectionV1 {\n\tconst operationId = isRecord(input) && typeof input.operationId === \"string\" ? input.operationId : \"invalid\";\n\ttry {\n\t\tif (!isRecord(input) || input.version !== 1) throw new Error(\"input must have version 1\");\n\t\tif (typeof input.operationId !== \"string\" || input.operationId.trim() === \"\")\n\t\t\tthrow new Error(\"operationId must be a non-empty string\");\n\t\tif (!Array.isArray(input.items)) throw new Error(\"items must be an array\");\n\t\tconst budget = validateBudget(limits);\n\t\tif (containsRawPayload(input.items)) throw new Error(\"raw payloads are not allowed in plugin context\");\n\t\tif (input.outputArtifactRef !== undefined) {\n\t\t\tif (typeof input.outputArtifactRef !== \"string\" || input.outputArtifactRef.trim() === \"\")\n\t\t\t\tthrow new Error(\"outputArtifactRef must be a non-empty string\");\n\t\t\tif (/^(?:[a-zA-Z]:[\\\\/]|[\\\\/]{1,2}|\\\\\\\\)/.test(input.outputArtifactRef))\n\t\t\t\tthrow new Error(\"outputArtifactRef must be opaque\");\n\t\t}\n\t\tconst items = input.items.map((item, index) => {\n\t\t\tif (!isJsonValue(item)) throw new Error(`items[${index}] must be JSON-safe`);\n\t\t\treturn boundStrings(item, budget.maxCellChars);\n\t\t});\n\t\tconst truncated = input.truncated === true || items.length > budget.maxInlineItems;\n\t\tconst inlineItems = Object.freeze(items.slice(0, budget.maxInlineItems));\n\t\tconst result = Object.freeze({\n\t\t\tversion: 1 as const,\n\t\t\toperationId: input.operationId,\n\t\t\tstatus: \"succeeded\" as const,\n\t\t\tinlineItems,\n\t\t\ttruncated,\n\t\t\t...(input.outputArtifactRef === undefined ? {} : { outputArtifactRef: input.outputArtifactRef }),\n\t\t});\n\t\tif (new TextEncoder().encode(JSON.stringify(result)).byteLength > budget.maxOutputBytes)\n\t\t\tthrow new Error(\"projected context exceeds maxOutputBytes\");\n\t\treturn result;\n\t} catch (cause) {\n\t\treturn invalid(\n\t\t\toperationId,\n\t\t\t\"context_projection_rejected\",\n\t\t\tcause instanceof Error ? cause.message : \"Context projection rejected\",\n\t\t);\n\t}\n}\n\nexport { DEFAULT_PLUGIN_CONTEXT_BUDGET_V1 };\n"]}
|
package/dist/diff.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type ArtifactRefV1 } from "./artifact.ts";
|
|
2
|
+
import { type EvidenceRefV1, type PluginErrorV1, type PluginResultStatusV1, type VerificationV1 } from "./operation.ts";
|
|
3
|
+
export type DiffKindV1 = "text" | "dom" | "structure" | "pixel" | "style";
|
|
4
|
+
export interface DiffThresholdV1 {
|
|
5
|
+
readonly maxChangedPixels?: number;
|
|
6
|
+
readonly maxChangedRatio?: number;
|
|
7
|
+
readonly allowedSelectors?: readonly string[];
|
|
8
|
+
}
|
|
9
|
+
export interface DiffRequestV1 {
|
|
10
|
+
readonly version: 1;
|
|
11
|
+
readonly operationId: string;
|
|
12
|
+
readonly kind: DiffKindV1;
|
|
13
|
+
readonly before: ArtifactRefV1;
|
|
14
|
+
readonly after: ArtifactRefV1;
|
|
15
|
+
readonly threshold?: DiffThresholdV1;
|
|
16
|
+
}
|
|
17
|
+
export interface DiffResultV1 {
|
|
18
|
+
readonly version: 1;
|
|
19
|
+
readonly operationId: string;
|
|
20
|
+
readonly status: PluginResultStatusV1;
|
|
21
|
+
readonly kind: DiffKindV1;
|
|
22
|
+
readonly summary: string;
|
|
23
|
+
readonly metrics?: DiffMetricsV1;
|
|
24
|
+
readonly diffArtifact?: ArtifactRefV1;
|
|
25
|
+
readonly evidence: readonly EvidenceRefV1[];
|
|
26
|
+
readonly verification?: VerificationV1;
|
|
27
|
+
readonly error?: PluginErrorV1;
|
|
28
|
+
}
|
|
29
|
+
export interface DiffMetricsV1 {
|
|
30
|
+
readonly changedPixels?: number;
|
|
31
|
+
readonly changedRatio?: number;
|
|
32
|
+
readonly selector?: string;
|
|
33
|
+
}
|
|
34
|
+
export declare function validateDiffRequestV1(value: unknown, label?: string): DiffRequestV1;
|
|
35
|
+
export declare function buildDiffRequestV1(input: DiffRequestV1): DiffRequestV1;
|
|
36
|
+
export declare function validateDiffResultV1(value: unknown, label?: string): DiffResultV1;
|
|
37
|
+
export declare function buildDiffResultV1(input: DiffResultV1): DiffResultV1;
|
|
38
|
+
//# sourceMappingURL=diff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAyB,MAAM,eAAe,CAAC;AAQ1E,OAAO,EACN,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,cAAc,EAInB,MAAM,gBAAgB,CAAC;AAExB,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,KAAK,GAAG,WAAW,GAAG,OAAO,GAAG,OAAO,CAAC;AAE1E,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9C;AAED,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,SAAS,CAAC,EAAE,eAAe,CAAC;CACrC;AAED,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAC;IAC5C,QAAQ,CAAC,YAAY,CAAC,EAAE,cAAc,CAAC;IACvC,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC3B;AA4FD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,SAAiB,GAAG,aAAa,CAmB3F;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,GAAG,aAAa,CAEtE;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,SAAgB,GAAG,YAAY,CAuDxF;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,YAAY,GAAG,YAAY,CAEnE","sourcesContent":["import { type ArtifactRefV1, validateArtifactRefV1 } from \"./artifact.ts\";\nimport {\n\tassertExactKeys,\n\tassertNonEmptyString,\n\tassertNonNegativeNumber,\n\tassertPlainRecord,\n\tdetachedFreeze,\n} from \"./common.ts\";\nimport {\n\ttype EvidenceRefV1,\n\ttype PluginErrorV1,\n\ttype PluginResultStatusV1,\n\ttype VerificationV1,\n\tvalidateEvidenceRefV1,\n\tvalidatePluginErrorV1,\n\tvalidateVerificationV1,\n} from \"./operation.ts\";\n\nexport type DiffKindV1 = \"text\" | \"dom\" | \"structure\" | \"pixel\" | \"style\";\n\nexport interface DiffThresholdV1 {\n\treadonly maxChangedPixels?: number;\n\treadonly maxChangedRatio?: number;\n\treadonly allowedSelectors?: readonly string[];\n}\n\nexport interface DiffRequestV1 {\n\treadonly version: 1;\n\treadonly operationId: string;\n\treadonly kind: DiffKindV1;\n\treadonly before: ArtifactRefV1;\n\treadonly after: ArtifactRefV1;\n\treadonly threshold?: DiffThresholdV1;\n}\n\nexport interface DiffResultV1 {\n\treadonly version: 1;\n\treadonly operationId: string;\n\treadonly status: PluginResultStatusV1;\n\treadonly kind: DiffKindV1;\n\treadonly summary: string;\n\treadonly metrics?: DiffMetricsV1;\n\treadonly diffArtifact?: ArtifactRefV1;\n\treadonly evidence: readonly EvidenceRefV1[];\n\treadonly verification?: VerificationV1;\n\treadonly error?: PluginErrorV1;\n}\n\nexport interface DiffMetricsV1 {\n\treadonly changedPixels?: number;\n\treadonly changedRatio?: number;\n\treadonly selector?: string;\n}\n\nconst DIFF_MAX_ITEMS_V1 = 64;\nconst DIFF_MAX_STRING_CHARS_V1 = 2048;\nconst DIFF_MAX_RESULT_BYTES_V1 = 64 * 1024;\n\nfunction validateBoundedString(value: unknown, label: string): asserts value is string {\n\tassertNonEmptyString(value, label);\n\tif (value.length > DIFF_MAX_STRING_CHARS_V1) throw new Error(`${label} exceeds the context budget`);\n}\n\nconst DIFF_KINDS_V1: readonly DiffKindV1[] = [\"text\", \"dom\", \"structure\", \"pixel\", \"style\"];\nconst RESULT_STATUSES_V1: readonly PluginResultStatusV1[] = [\n\t\"succeeded\",\n\t\"failed\",\n\t\"cancelled\",\n\t\"needs_user\",\n\t\"unsupported\",\n];\n\nfunction validateKind(value: unknown, label: string): DiffKindV1 {\n\tif (typeof value !== \"string\" || !DIFF_KINDS_V1.includes(value as DiffKindV1))\n\t\tthrow new Error(`${label} is invalid`);\n\treturn value as DiffKindV1;\n}\n\nfunction validateStatus(value: unknown, label: string): PluginResultStatusV1 {\n\tif (typeof value !== \"string\" || !RESULT_STATUSES_V1.includes(value as PluginResultStatusV1))\n\t\tthrow new Error(`${label} is invalid`);\n\treturn value as PluginResultStatusV1;\n}\n\nfunction validateMetrics(value: unknown, label: string): DiffMetricsV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(value, [], [\"changedPixels\", \"changedRatio\", \"selector\"], label);\n\tif (value.changedPixels !== undefined) {\n\t\tif (\n\t\t\ttypeof value.changedPixels !== \"number\" ||\n\t\t\t!Number.isSafeInteger(value.changedPixels) ||\n\t\t\tvalue.changedPixels < 0\n\t\t)\n\t\t\tthrow new Error(`${label}.changedPixels must be a non-negative safe integer`);\n\t}\n\tif (value.changedRatio !== undefined) {\n\t\tassertNonNegativeNumber(value.changedRatio, `${label}.changedRatio`);\n\t\tif (value.changedRatio > 1) throw new Error(`${label}.changedRatio must be at most 1`);\n\t}\n\tif (value.selector !== undefined) validateBoundedString(value.selector, `${label}.selector`);\n\treturn detachedFreeze({\n\t\t...(value.changedPixels === undefined ? {} : { changedPixels: value.changedPixels }),\n\t\t...(value.changedRatio === undefined ? {} : { changedRatio: value.changedRatio }),\n\t\t...(value.selector === undefined ? {} : { selector: value.selector }),\n\t});\n}\n\nfunction validateThreshold(value: unknown, label: string): DiffThresholdV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(value, [], [\"maxChangedPixels\", \"maxChangedRatio\", \"allowedSelectors\"], label);\n\tif (value.maxChangedPixels !== undefined) {\n\t\tif (\n\t\t\ttypeof value.maxChangedPixels !== \"number\" ||\n\t\t\t!Number.isSafeInteger(value.maxChangedPixels) ||\n\t\t\tvalue.maxChangedPixels < 0\n\t\t) {\n\t\t\tthrow new Error(`${label}.maxChangedPixels must be a non-negative safe integer`);\n\t\t}\n\t}\n\tif (value.maxChangedRatio !== undefined) {\n\t\tassertNonNegativeNumber(value.maxChangedRatio, `${label}.maxChangedRatio`);\n\t\tif (value.maxChangedRatio > 1) throw new Error(`${label}.maxChangedRatio must be at most 1`);\n\t}\n\tif (value.allowedSelectors !== undefined) {\n\t\tif (\n\t\t\t!Array.isArray(value.allowedSelectors) ||\n\t\t\tvalue.allowedSelectors.some((item) => typeof item !== \"string\" || item.trim() === \"\")\n\t\t) {\n\t\t\tthrow new Error(`${label}.allowedSelectors must contain non-empty strings`);\n\t\t}\n\t\tif (new Set(value.allowedSelectors).size !== value.allowedSelectors.length)\n\t\t\tthrow new Error(`${label}.allowedSelectors must not contain duplicates`);\n\t\tif (value.allowedSelectors.length > DIFF_MAX_ITEMS_V1)\n\t\t\tthrow new Error(`${label}.allowedSelectors must contain at most ${DIFF_MAX_ITEMS_V1} items`);\n\t\tfor (const [index, selector] of value.allowedSelectors.entries())\n\t\t\tvalidateBoundedString(selector, `${label}.allowedSelectors[${index}]`);\n\t}\n\treturn detachedFreeze({\n\t\t...(value.maxChangedPixels === undefined ? {} : { maxChangedPixels: value.maxChangedPixels }),\n\t\t...(value.maxChangedRatio === undefined ? {} : { maxChangedRatio: value.maxChangedRatio }),\n\t\t...(value.allowedSelectors === undefined ? {} : { allowedSelectors: [...value.allowedSelectors].sort() }),\n\t});\n}\n\nexport function validateDiffRequestV1(value: unknown, label = \"Diff request\"): DiffRequestV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(value, [\"version\", \"operationId\", \"kind\", \"before\", \"after\"], [\"threshold\"], label);\n\tif (value.version !== 1) throw new Error(`${label}.version must be 1`);\n\tassertNonEmptyString(value.operationId, `${label}.operationId`);\n\tconst kind = validateKind(value.kind, `${label}.kind`);\n\tconst before = validateArtifactRefV1(value.before, `${label}.before`);\n\tconst after = validateArtifactRefV1(value.after, `${label}.after`);\n\tif (before.artifactRef === after.artifactRef) throw new Error(`${label}.before and after must differ`);\n\tconst threshold =\n\t\tvalue.threshold === undefined ? undefined : validateThreshold(value.threshold, `${label}.threshold`);\n\treturn detachedFreeze({\n\t\tversion: 1 as const,\n\t\toperationId: value.operationId,\n\t\tkind,\n\t\tbefore,\n\t\tafter,\n\t\t...(threshold === undefined ? {} : { threshold }),\n\t});\n}\n\nexport function buildDiffRequestV1(input: DiffRequestV1): DiffRequestV1 {\n\treturn validateDiffRequestV1(input);\n}\n\nexport function validateDiffResultV1(value: unknown, label = \"Diff result\"): DiffResultV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(\n\t\tvalue,\n\t\t[\"version\", \"operationId\", \"status\", \"kind\", \"summary\", \"evidence\"],\n\t\t[\"diffArtifact\", \"metrics\", \"verification\", \"error\"],\n\t\tlabel,\n\t);\n\tif (value.version !== 1) throw new Error(`${label}.version must be 1`);\n\tassertNonEmptyString(value.operationId, `${label}.operationId`);\n\tconst resultStatus = validateStatus(value.status, `${label}.status`);\n\tconst kind = validateKind(value.kind, `${label}.kind`);\n\tvalidateBoundedString(value.summary, `${label}.summary`);\n\tconst metrics = value.metrics === undefined ? undefined : validateMetrics(value.metrics, `${label}.metrics`);\n\tconst diffArtifact =\n\t\tvalue.diffArtifact === undefined ? undefined : validateArtifactRefV1(value.diffArtifact, `${label}.diffArtifact`);\n\tif (!Array.isArray(value.evidence)) throw new Error(`${label}.evidence must be an array`);\n\tif (value.evidence.length > DIFF_MAX_ITEMS_V1)\n\t\tthrow new Error(`${label}.evidence must contain at most ${DIFF_MAX_ITEMS_V1} items`);\n\tconst evidence = value.evidence.map((item, index) => validateEvidenceRefV1(item, `${label}.evidence[${index}]`));\n\tif (new Set(evidence.map((item) => item.evidenceId)).size !== evidence.length)\n\t\tthrow new Error(`${label}.evidence must not contain duplicate evidenceId values`);\n\tfor (const [index, item] of evidence.entries()) {\n\t\tvalidateBoundedString(item.evidenceId, `${label}.evidence[${index}].evidenceId`);\n\t\tif (item.summary !== undefined) validateBoundedString(item.summary, `${label}.evidence[${index}].summary`);\n\t\tif (item.observedAt !== undefined)\n\t\t\tvalidateBoundedString(item.observedAt, `${label}.evidence[${index}].observedAt`);\n\t}\n\tconst orderedEvidence = Object.freeze([...evidence].sort((a, b) => a.evidenceId.localeCompare(b.evidenceId)));\n\tconst verification =\n\t\tvalue.verification === undefined\n\t\t\t? undefined\n\t\t\t: validateVerificationV1(value.verification, `${label}.verification`);\n\tconst error = value.error === undefined ? undefined : validatePluginErrorV1(value.error, `${label}.error`);\n\tif (resultStatus !== \"succeeded\" && error === undefined)\n\t\tthrow new Error(`${label}.error is required for status ${resultStatus}`);\n\tif (resultStatus === \"succeeded\" && error !== undefined)\n\t\tthrow new Error(`${label}.succeeded must not include error`);\n\tif (resultStatus === \"succeeded\" && verification?.status === \"failed\")\n\t\tthrow new Error(`${label}.succeeded cannot have failed verification`);\n\tconst result = {\n\t\tversion: 1 as const,\n\t\toperationId: value.operationId,\n\t\tstatus: resultStatus,\n\t\tkind,\n\t\tsummary: value.summary,\n\t\t...(metrics === undefined ? {} : { metrics }),\n\t\t...(diffArtifact === undefined ? {} : { diffArtifact }),\n\t\tevidence: orderedEvidence,\n\t\t...(verification === undefined ? {} : { verification }),\n\t\t...(error === undefined ? {} : { error }),\n\t};\n\tif (new TextEncoder().encode(JSON.stringify(result)).byteLength > DIFF_MAX_RESULT_BYTES_V1)\n\t\tthrow new Error(`${label} exceeds the context budget`);\n\treturn detachedFreeze(result);\n}\n\nexport function buildDiffResultV1(input: DiffResultV1): DiffResultV1 {\n\treturn validateDiffResultV1(input);\n}\n"]}
|
package/dist/diff.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { validateArtifactRefV1 } from "./artifact.js";
|
|
2
|
+
import { assertExactKeys, assertNonEmptyString, assertNonNegativeNumber, assertPlainRecord, detachedFreeze, } from "./common.js";
|
|
3
|
+
import { validateEvidenceRefV1, validatePluginErrorV1, validateVerificationV1, } from "./operation.js";
|
|
4
|
+
const DIFF_MAX_ITEMS_V1 = 64;
|
|
5
|
+
const DIFF_MAX_STRING_CHARS_V1 = 2048;
|
|
6
|
+
const DIFF_MAX_RESULT_BYTES_V1 = 64 * 1024;
|
|
7
|
+
function validateBoundedString(value, label) {
|
|
8
|
+
assertNonEmptyString(value, label);
|
|
9
|
+
if (value.length > DIFF_MAX_STRING_CHARS_V1)
|
|
10
|
+
throw new Error(`${label} exceeds the context budget`);
|
|
11
|
+
}
|
|
12
|
+
const DIFF_KINDS_V1 = ["text", "dom", "structure", "pixel", "style"];
|
|
13
|
+
const RESULT_STATUSES_V1 = [
|
|
14
|
+
"succeeded",
|
|
15
|
+
"failed",
|
|
16
|
+
"cancelled",
|
|
17
|
+
"needs_user",
|
|
18
|
+
"unsupported",
|
|
19
|
+
];
|
|
20
|
+
function validateKind(value, label) {
|
|
21
|
+
if (typeof value !== "string" || !DIFF_KINDS_V1.includes(value))
|
|
22
|
+
throw new Error(`${label} is invalid`);
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
function validateStatus(value, label) {
|
|
26
|
+
if (typeof value !== "string" || !RESULT_STATUSES_V1.includes(value))
|
|
27
|
+
throw new Error(`${label} is invalid`);
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
function validateMetrics(value, label) {
|
|
31
|
+
assertPlainRecord(value, label);
|
|
32
|
+
assertExactKeys(value, [], ["changedPixels", "changedRatio", "selector"], label);
|
|
33
|
+
if (value.changedPixels !== undefined) {
|
|
34
|
+
if (typeof value.changedPixels !== "number" ||
|
|
35
|
+
!Number.isSafeInteger(value.changedPixels) ||
|
|
36
|
+
value.changedPixels < 0)
|
|
37
|
+
throw new Error(`${label}.changedPixels must be a non-negative safe integer`);
|
|
38
|
+
}
|
|
39
|
+
if (value.changedRatio !== undefined) {
|
|
40
|
+
assertNonNegativeNumber(value.changedRatio, `${label}.changedRatio`);
|
|
41
|
+
if (value.changedRatio > 1)
|
|
42
|
+
throw new Error(`${label}.changedRatio must be at most 1`);
|
|
43
|
+
}
|
|
44
|
+
if (value.selector !== undefined)
|
|
45
|
+
validateBoundedString(value.selector, `${label}.selector`);
|
|
46
|
+
return detachedFreeze({
|
|
47
|
+
...(value.changedPixels === undefined ? {} : { changedPixels: value.changedPixels }),
|
|
48
|
+
...(value.changedRatio === undefined ? {} : { changedRatio: value.changedRatio }),
|
|
49
|
+
...(value.selector === undefined ? {} : { selector: value.selector }),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function validateThreshold(value, label) {
|
|
53
|
+
assertPlainRecord(value, label);
|
|
54
|
+
assertExactKeys(value, [], ["maxChangedPixels", "maxChangedRatio", "allowedSelectors"], label);
|
|
55
|
+
if (value.maxChangedPixels !== undefined) {
|
|
56
|
+
if (typeof value.maxChangedPixels !== "number" ||
|
|
57
|
+
!Number.isSafeInteger(value.maxChangedPixels) ||
|
|
58
|
+
value.maxChangedPixels < 0) {
|
|
59
|
+
throw new Error(`${label}.maxChangedPixels must be a non-negative safe integer`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (value.maxChangedRatio !== undefined) {
|
|
63
|
+
assertNonNegativeNumber(value.maxChangedRatio, `${label}.maxChangedRatio`);
|
|
64
|
+
if (value.maxChangedRatio > 1)
|
|
65
|
+
throw new Error(`${label}.maxChangedRatio must be at most 1`);
|
|
66
|
+
}
|
|
67
|
+
if (value.allowedSelectors !== undefined) {
|
|
68
|
+
if (!Array.isArray(value.allowedSelectors) ||
|
|
69
|
+
value.allowedSelectors.some((item) => typeof item !== "string" || item.trim() === "")) {
|
|
70
|
+
throw new Error(`${label}.allowedSelectors must contain non-empty strings`);
|
|
71
|
+
}
|
|
72
|
+
if (new Set(value.allowedSelectors).size !== value.allowedSelectors.length)
|
|
73
|
+
throw new Error(`${label}.allowedSelectors must not contain duplicates`);
|
|
74
|
+
if (value.allowedSelectors.length > DIFF_MAX_ITEMS_V1)
|
|
75
|
+
throw new Error(`${label}.allowedSelectors must contain at most ${DIFF_MAX_ITEMS_V1} items`);
|
|
76
|
+
for (const [index, selector] of value.allowedSelectors.entries())
|
|
77
|
+
validateBoundedString(selector, `${label}.allowedSelectors[${index}]`);
|
|
78
|
+
}
|
|
79
|
+
return detachedFreeze({
|
|
80
|
+
...(value.maxChangedPixels === undefined ? {} : { maxChangedPixels: value.maxChangedPixels }),
|
|
81
|
+
...(value.maxChangedRatio === undefined ? {} : { maxChangedRatio: value.maxChangedRatio }),
|
|
82
|
+
...(value.allowedSelectors === undefined ? {} : { allowedSelectors: [...value.allowedSelectors].sort() }),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
export function validateDiffRequestV1(value, label = "Diff request") {
|
|
86
|
+
assertPlainRecord(value, label);
|
|
87
|
+
assertExactKeys(value, ["version", "operationId", "kind", "before", "after"], ["threshold"], label);
|
|
88
|
+
if (value.version !== 1)
|
|
89
|
+
throw new Error(`${label}.version must be 1`);
|
|
90
|
+
assertNonEmptyString(value.operationId, `${label}.operationId`);
|
|
91
|
+
const kind = validateKind(value.kind, `${label}.kind`);
|
|
92
|
+
const before = validateArtifactRefV1(value.before, `${label}.before`);
|
|
93
|
+
const after = validateArtifactRefV1(value.after, `${label}.after`);
|
|
94
|
+
if (before.artifactRef === after.artifactRef)
|
|
95
|
+
throw new Error(`${label}.before and after must differ`);
|
|
96
|
+
const threshold = value.threshold === undefined ? undefined : validateThreshold(value.threshold, `${label}.threshold`);
|
|
97
|
+
return detachedFreeze({
|
|
98
|
+
version: 1,
|
|
99
|
+
operationId: value.operationId,
|
|
100
|
+
kind,
|
|
101
|
+
before,
|
|
102
|
+
after,
|
|
103
|
+
...(threshold === undefined ? {} : { threshold }),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
export function buildDiffRequestV1(input) {
|
|
107
|
+
return validateDiffRequestV1(input);
|
|
108
|
+
}
|
|
109
|
+
export function validateDiffResultV1(value, label = "Diff result") {
|
|
110
|
+
assertPlainRecord(value, label);
|
|
111
|
+
assertExactKeys(value, ["version", "operationId", "status", "kind", "summary", "evidence"], ["diffArtifact", "metrics", "verification", "error"], label);
|
|
112
|
+
if (value.version !== 1)
|
|
113
|
+
throw new Error(`${label}.version must be 1`);
|
|
114
|
+
assertNonEmptyString(value.operationId, `${label}.operationId`);
|
|
115
|
+
const resultStatus = validateStatus(value.status, `${label}.status`);
|
|
116
|
+
const kind = validateKind(value.kind, `${label}.kind`);
|
|
117
|
+
validateBoundedString(value.summary, `${label}.summary`);
|
|
118
|
+
const metrics = value.metrics === undefined ? undefined : validateMetrics(value.metrics, `${label}.metrics`);
|
|
119
|
+
const diffArtifact = value.diffArtifact === undefined ? undefined : validateArtifactRefV1(value.diffArtifact, `${label}.diffArtifact`);
|
|
120
|
+
if (!Array.isArray(value.evidence))
|
|
121
|
+
throw new Error(`${label}.evidence must be an array`);
|
|
122
|
+
if (value.evidence.length > DIFF_MAX_ITEMS_V1)
|
|
123
|
+
throw new Error(`${label}.evidence must contain at most ${DIFF_MAX_ITEMS_V1} items`);
|
|
124
|
+
const evidence = value.evidence.map((item, index) => validateEvidenceRefV1(item, `${label}.evidence[${index}]`));
|
|
125
|
+
if (new Set(evidence.map((item) => item.evidenceId)).size !== evidence.length)
|
|
126
|
+
throw new Error(`${label}.evidence must not contain duplicate evidenceId values`);
|
|
127
|
+
for (const [index, item] of evidence.entries()) {
|
|
128
|
+
validateBoundedString(item.evidenceId, `${label}.evidence[${index}].evidenceId`);
|
|
129
|
+
if (item.summary !== undefined)
|
|
130
|
+
validateBoundedString(item.summary, `${label}.evidence[${index}].summary`);
|
|
131
|
+
if (item.observedAt !== undefined)
|
|
132
|
+
validateBoundedString(item.observedAt, `${label}.evidence[${index}].observedAt`);
|
|
133
|
+
}
|
|
134
|
+
const orderedEvidence = Object.freeze([...evidence].sort((a, b) => a.evidenceId.localeCompare(b.evidenceId)));
|
|
135
|
+
const verification = value.verification === undefined
|
|
136
|
+
? undefined
|
|
137
|
+
: validateVerificationV1(value.verification, `${label}.verification`);
|
|
138
|
+
const error = value.error === undefined ? undefined : validatePluginErrorV1(value.error, `${label}.error`);
|
|
139
|
+
if (resultStatus !== "succeeded" && error === undefined)
|
|
140
|
+
throw new Error(`${label}.error is required for status ${resultStatus}`);
|
|
141
|
+
if (resultStatus === "succeeded" && error !== undefined)
|
|
142
|
+
throw new Error(`${label}.succeeded must not include error`);
|
|
143
|
+
if (resultStatus === "succeeded" && verification?.status === "failed")
|
|
144
|
+
throw new Error(`${label}.succeeded cannot have failed verification`);
|
|
145
|
+
const result = {
|
|
146
|
+
version: 1,
|
|
147
|
+
operationId: value.operationId,
|
|
148
|
+
status: resultStatus,
|
|
149
|
+
kind,
|
|
150
|
+
summary: value.summary,
|
|
151
|
+
...(metrics === undefined ? {} : { metrics }),
|
|
152
|
+
...(diffArtifact === undefined ? {} : { diffArtifact }),
|
|
153
|
+
evidence: orderedEvidence,
|
|
154
|
+
...(verification === undefined ? {} : { verification }),
|
|
155
|
+
...(error === undefined ? {} : { error }),
|
|
156
|
+
};
|
|
157
|
+
if (new TextEncoder().encode(JSON.stringify(result)).byteLength > DIFF_MAX_RESULT_BYTES_V1)
|
|
158
|
+
throw new Error(`${label} exceeds the context budget`);
|
|
159
|
+
return detachedFreeze(result);
|
|
160
|
+
}
|
|
161
|
+
export function buildDiffResultV1(input) {
|
|
162
|
+
return validateDiffResultV1(input);
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=diff.js.map
|
package/dist/diff.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC1E,OAAO,EACN,eAAe,EACf,oBAAoB,EACpB,uBAAuB,EACvB,iBAAiB,EACjB,cAAc,GACd,MAAM,aAAa,CAAC;AACrB,OAAO,EAKN,qBAAqB,EACrB,qBAAqB,EACrB,sBAAsB,GACtB,MAAM,gBAAgB,CAAC;AAsCxB,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAC7B,MAAM,wBAAwB,GAAG,IAAI,CAAC;AACtC,MAAM,wBAAwB,GAAG,EAAE,GAAG,IAAI,CAAC;AAE3C,SAAS,qBAAqB,CAAC,KAAc,EAAE,KAAa,EAA2B;IACtF,oBAAoB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACnC,IAAI,KAAK,CAAC,MAAM,GAAG,wBAAwB;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,6BAA6B,CAAC,CAAC;AAAA,CACpG;AAED,MAAM,aAAa,GAA0B,CAAC,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAC5F,MAAM,kBAAkB,GAAoC;IAC3D,WAAW;IACX,QAAQ;IACR,WAAW;IACX,YAAY;IACZ,aAAa;CACb,CAAC;AAEF,SAAS,YAAY,CAAC,KAAc,EAAE,KAAa,EAAc;IAChE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,KAAmB,CAAC;QAC5E,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,aAAa,CAAC,CAAC;IACxC,OAAO,KAAmB,CAAC;AAAA,CAC3B;AAED,SAAS,cAAc,CAAC,KAAc,EAAE,KAAa,EAAwB;IAC5E,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,KAA6B,CAAC;QAC3F,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,aAAa,CAAC,CAAC;IACxC,OAAO,KAA6B,CAAC;AAAA,CACrC;AAED,SAAS,eAAe,CAAC,KAAc,EAAE,KAAa,EAAiB;IACtE,iBAAiB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAChC,eAAe,CAAC,KAAK,EAAE,EAAE,EAAE,CAAC,eAAe,EAAE,cAAc,EAAE,UAAU,CAAC,EAAE,KAAK,CAAC,CAAC;IACjF,IAAI,KAAK,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;QACvC,IACC,OAAO,KAAK,CAAC,aAAa,KAAK,QAAQ;YACvC,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,aAAa,CAAC;YAC1C,KAAK,CAAC,aAAa,GAAG,CAAC;YAEvB,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,oDAAoD,CAAC,CAAC;IAChF,CAAC;IACD,IAAI,KAAK,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;QACtC,uBAAuB,CAAC,KAAK,CAAC,YAAY,EAAE,GAAG,KAAK,eAAe,CAAC,CAAC;QACrE,IAAI,KAAK,CAAC,YAAY,GAAG,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,iCAAiC,CAAC,CAAC;IACxF,CAAC;IACD,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS;QAAE,qBAAqB,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,KAAK,WAAW,CAAC,CAAC;IAC7F,OAAO,cAAc,CAAC;QACrB,GAAG,CAAC,KAAK,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,KAAK,CAAC,aAAa,EAAE,CAAC;QACpF,GAAG,CAAC,KAAK,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC;QACjF,GAAG,CAAC,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC;KACrE,CAAC,CAAC;AAAA,CACH;AAED,SAAS,iBAAiB,CAAC,KAAc,EAAE,KAAa,EAAmB;IAC1E,iBAAiB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAChC,eAAe,CAAC,KAAK,EAAE,EAAE,EAAE,CAAC,kBAAkB,EAAE,iBAAiB,EAAE,kBAAkB,CAAC,EAAE,KAAK,CAAC,CAAC;IAC/F,IAAI,KAAK,CAAC,gBAAgB,KAAK,SAAS,EAAE,CAAC;QAC1C,IACC,OAAO,KAAK,CAAC,gBAAgB,KAAK,QAAQ;YAC1C,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,gBAAgB,CAAC;YAC7C,KAAK,CAAC,gBAAgB,GAAG,CAAC,EACzB,CAAC;YACF,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,uDAAuD,CAAC,CAAC;QAClF,CAAC;IACF,CAAC;IACD,IAAI,KAAK,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;QACzC,uBAAuB,CAAC,KAAK,CAAC,eAAe,EAAE,GAAG,KAAK,kBAAkB,CAAC,CAAC;QAC3E,IAAI,KAAK,CAAC,eAAe,GAAG,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,oCAAoC,CAAC,CAAC;IAC9F,CAAC;IACD,IAAI,KAAK,CAAC,gBAAgB,KAAK,SAAS,EAAE,CAAC;QAC1C,IACC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,gBAAgB,CAAC;YACtC,KAAK,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,EACpF,CAAC;YACF,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,kDAAkD,CAAC,CAAC;QAC7E,CAAC;QACD,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,gBAAgB,CAAC,MAAM;YACzE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,+CAA+C,CAAC,CAAC;QAC1E,IAAI,KAAK,CAAC,gBAAgB,CAAC,MAAM,GAAG,iBAAiB;YACpD,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,0CAA0C,iBAAiB,QAAQ,CAAC,CAAC;QAC9F,KAAK,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,IAAI,KAAK,CAAC,gBAAgB,CAAC,OAAO,EAAE;YAC/D,qBAAqB,CAAC,QAAQ,EAAE,GAAG,KAAK,qBAAqB,KAAK,GAAG,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,cAAc,CAAC;QACrB,GAAG,CAAC,KAAK,CAAC,gBAAgB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,KAAK,CAAC,gBAAgB,EAAE,CAAC;QAC7F,GAAG,CAAC,KAAK,CAAC,eAAe,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,KAAK,CAAC,eAAe,EAAE,CAAC;QAC1F,GAAG,CAAC,KAAK,CAAC,gBAAgB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,CAAC,GAAG,KAAK,CAAC,gBAAgB,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;KACzG,CAAC,CAAC;AAAA,CACH;AAED,MAAM,UAAU,qBAAqB,CAAC,KAAc,EAAE,KAAK,GAAG,cAAc,EAAiB;IAC5F,iBAAiB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAChC,eAAe,CAAC,KAAK,EAAE,CAAC,SAAS,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,CAAC,WAAW,CAAC,EAAE,KAAK,CAAC,CAAC;IACpG,IAAI,KAAK,CAAC,OAAO,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,oBAAoB,CAAC,CAAC;IACvE,oBAAoB,CAAC,KAAK,CAAC,WAAW,EAAE,GAAG,KAAK,cAAc,CAAC,CAAC;IAChE,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC;IACvD,MAAM,MAAM,GAAG,qBAAqB,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,KAAK,SAAS,CAAC,CAAC;IACtE,MAAM,KAAK,GAAG,qBAAqB,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,KAAK,QAAQ,CAAC,CAAC;IACnE,IAAI,MAAM,CAAC,WAAW,KAAK,KAAK,CAAC,WAAW;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,+BAA+B,CAAC,CAAC;IACvG,MAAM,SAAS,GACd,KAAK,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,iBAAiB,CAAC,KAAK,CAAC,SAAS,EAAE,GAAG,KAAK,YAAY,CAAC,CAAC;IACtG,OAAO,cAAc,CAAC;QACrB,OAAO,EAAE,CAAU;QACnB,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,IAAI;QACJ,MAAM;QACN,KAAK;QACL,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC;KACjD,CAAC,CAAC;AAAA,CACH;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAoB,EAAiB;IACvE,OAAO,qBAAqB,CAAC,KAAK,CAAC,CAAC;AAAA,CACpC;AAED,MAAM,UAAU,oBAAoB,CAAC,KAAc,EAAE,KAAK,GAAG,aAAa,EAAgB;IACzF,iBAAiB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAChC,eAAe,CACd,KAAK,EACL,CAAC,SAAS,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,CAAC,EACnE,CAAC,cAAc,EAAE,SAAS,EAAE,cAAc,EAAE,OAAO,CAAC,EACpD,KAAK,CACL,CAAC;IACF,IAAI,KAAK,CAAC,OAAO,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,oBAAoB,CAAC,CAAC;IACvE,oBAAoB,CAAC,KAAK,CAAC,WAAW,EAAE,GAAG,KAAK,cAAc,CAAC,CAAC;IAChE,MAAM,YAAY,GAAG,cAAc,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,KAAK,SAAS,CAAC,CAAC;IACrE,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC;IACvD,qBAAqB,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,KAAK,UAAU,CAAC,CAAC;IACzD,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,KAAK,UAAU,CAAC,CAAC;IAC7G,MAAM,YAAY,GACjB,KAAK,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,qBAAqB,CAAC,KAAK,CAAC,YAAY,EAAE,GAAG,KAAK,eAAe,CAAC,CAAC;IACnH,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,4BAA4B,CAAC,CAAC;IAC1F,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,iBAAiB;QAC5C,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,kCAAkC,iBAAiB,QAAQ,CAAC,CAAC;IACtF,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,qBAAqB,CAAC,IAAI,EAAE,GAAG,KAAK,aAAa,KAAK,GAAG,CAAC,CAAC,CAAC;IACjH,IAAI,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,MAAM;QAC5E,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,wDAAwD,CAAC,CAAC;IACnF,KAAK,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC;QAChD,qBAAqB,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,KAAK,aAAa,KAAK,cAAc,CAAC,CAAC;QACjF,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS;YAAE,qBAAqB,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,KAAK,aAAa,KAAK,WAAW,CAAC,CAAC;QAC3G,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS;YAChC,qBAAqB,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,KAAK,aAAa,KAAK,cAAc,CAAC,CAAC;IACnF,CAAC;IACD,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAC9G,MAAM,YAAY,GACjB,KAAK,CAAC,YAAY,KAAK,SAAS;QAC/B,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,sBAAsB,CAAC,KAAK,CAAC,YAAY,EAAE,GAAG,KAAK,eAAe,CAAC,CAAC;IACxE,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,qBAAqB,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,KAAK,QAAQ,CAAC,CAAC;IAC3G,IAAI,YAAY,KAAK,WAAW,IAAI,KAAK,KAAK,SAAS;QACtD,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,iCAAiC,YAAY,EAAE,CAAC,CAAC;IAC1E,IAAI,YAAY,KAAK,WAAW,IAAI,KAAK,KAAK,SAAS;QACtD,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,mCAAmC,CAAC,CAAC;IAC9D,IAAI,YAAY,KAAK,WAAW,IAAI,YAAY,EAAE,MAAM,KAAK,QAAQ;QACpE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,4CAA4C,CAAC,CAAC;IACvE,MAAM,MAAM,GAAG;QACd,OAAO,EAAE,CAAU;QACnB,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,MAAM,EAAE,YAAY;QACpB,IAAI;QACJ,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;QAC7C,GAAG,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC;QACvD,QAAQ,EAAE,eAAe;QACzB,GAAG,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC;QACvD,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;KACzC,CAAC;IACF,IAAI,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,GAAG,wBAAwB;QACzF,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,6BAA6B,CAAC,CAAC;IACxD,OAAO,cAAc,CAAC,MAAM,CAAC,CAAC;AAAA,CAC9B;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAmB,EAAgB;IACpE,OAAO,oBAAoB,CAAC,KAAK,CAAC,CAAC;AAAA,CACnC","sourcesContent":["import { type ArtifactRefV1, validateArtifactRefV1 } from \"./artifact.ts\";\nimport {\n\tassertExactKeys,\n\tassertNonEmptyString,\n\tassertNonNegativeNumber,\n\tassertPlainRecord,\n\tdetachedFreeze,\n} from \"./common.ts\";\nimport {\n\ttype EvidenceRefV1,\n\ttype PluginErrorV1,\n\ttype PluginResultStatusV1,\n\ttype VerificationV1,\n\tvalidateEvidenceRefV1,\n\tvalidatePluginErrorV1,\n\tvalidateVerificationV1,\n} from \"./operation.ts\";\n\nexport type DiffKindV1 = \"text\" | \"dom\" | \"structure\" | \"pixel\" | \"style\";\n\nexport interface DiffThresholdV1 {\n\treadonly maxChangedPixels?: number;\n\treadonly maxChangedRatio?: number;\n\treadonly allowedSelectors?: readonly string[];\n}\n\nexport interface DiffRequestV1 {\n\treadonly version: 1;\n\treadonly operationId: string;\n\treadonly kind: DiffKindV1;\n\treadonly before: ArtifactRefV1;\n\treadonly after: ArtifactRefV1;\n\treadonly threshold?: DiffThresholdV1;\n}\n\nexport interface DiffResultV1 {\n\treadonly version: 1;\n\treadonly operationId: string;\n\treadonly status: PluginResultStatusV1;\n\treadonly kind: DiffKindV1;\n\treadonly summary: string;\n\treadonly metrics?: DiffMetricsV1;\n\treadonly diffArtifact?: ArtifactRefV1;\n\treadonly evidence: readonly EvidenceRefV1[];\n\treadonly verification?: VerificationV1;\n\treadonly error?: PluginErrorV1;\n}\n\nexport interface DiffMetricsV1 {\n\treadonly changedPixels?: number;\n\treadonly changedRatio?: number;\n\treadonly selector?: string;\n}\n\nconst DIFF_MAX_ITEMS_V1 = 64;\nconst DIFF_MAX_STRING_CHARS_V1 = 2048;\nconst DIFF_MAX_RESULT_BYTES_V1 = 64 * 1024;\n\nfunction validateBoundedString(value: unknown, label: string): asserts value is string {\n\tassertNonEmptyString(value, label);\n\tif (value.length > DIFF_MAX_STRING_CHARS_V1) throw new Error(`${label} exceeds the context budget`);\n}\n\nconst DIFF_KINDS_V1: readonly DiffKindV1[] = [\"text\", \"dom\", \"structure\", \"pixel\", \"style\"];\nconst RESULT_STATUSES_V1: readonly PluginResultStatusV1[] = [\n\t\"succeeded\",\n\t\"failed\",\n\t\"cancelled\",\n\t\"needs_user\",\n\t\"unsupported\",\n];\n\nfunction validateKind(value: unknown, label: string): DiffKindV1 {\n\tif (typeof value !== \"string\" || !DIFF_KINDS_V1.includes(value as DiffKindV1))\n\t\tthrow new Error(`${label} is invalid`);\n\treturn value as DiffKindV1;\n}\n\nfunction validateStatus(value: unknown, label: string): PluginResultStatusV1 {\n\tif (typeof value !== \"string\" || !RESULT_STATUSES_V1.includes(value as PluginResultStatusV1))\n\t\tthrow new Error(`${label} is invalid`);\n\treturn value as PluginResultStatusV1;\n}\n\nfunction validateMetrics(value: unknown, label: string): DiffMetricsV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(value, [], [\"changedPixels\", \"changedRatio\", \"selector\"], label);\n\tif (value.changedPixels !== undefined) {\n\t\tif (\n\t\t\ttypeof value.changedPixels !== \"number\" ||\n\t\t\t!Number.isSafeInteger(value.changedPixels) ||\n\t\t\tvalue.changedPixels < 0\n\t\t)\n\t\t\tthrow new Error(`${label}.changedPixels must be a non-negative safe integer`);\n\t}\n\tif (value.changedRatio !== undefined) {\n\t\tassertNonNegativeNumber(value.changedRatio, `${label}.changedRatio`);\n\t\tif (value.changedRatio > 1) throw new Error(`${label}.changedRatio must be at most 1`);\n\t}\n\tif (value.selector !== undefined) validateBoundedString(value.selector, `${label}.selector`);\n\treturn detachedFreeze({\n\t\t...(value.changedPixels === undefined ? {} : { changedPixels: value.changedPixels }),\n\t\t...(value.changedRatio === undefined ? {} : { changedRatio: value.changedRatio }),\n\t\t...(value.selector === undefined ? {} : { selector: value.selector }),\n\t});\n}\n\nfunction validateThreshold(value: unknown, label: string): DiffThresholdV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(value, [], [\"maxChangedPixels\", \"maxChangedRatio\", \"allowedSelectors\"], label);\n\tif (value.maxChangedPixels !== undefined) {\n\t\tif (\n\t\t\ttypeof value.maxChangedPixels !== \"number\" ||\n\t\t\t!Number.isSafeInteger(value.maxChangedPixels) ||\n\t\t\tvalue.maxChangedPixels < 0\n\t\t) {\n\t\t\tthrow new Error(`${label}.maxChangedPixels must be a non-negative safe integer`);\n\t\t}\n\t}\n\tif (value.maxChangedRatio !== undefined) {\n\t\tassertNonNegativeNumber(value.maxChangedRatio, `${label}.maxChangedRatio`);\n\t\tif (value.maxChangedRatio > 1) throw new Error(`${label}.maxChangedRatio must be at most 1`);\n\t}\n\tif (value.allowedSelectors !== undefined) {\n\t\tif (\n\t\t\t!Array.isArray(value.allowedSelectors) ||\n\t\t\tvalue.allowedSelectors.some((item) => typeof item !== \"string\" || item.trim() === \"\")\n\t\t) {\n\t\t\tthrow new Error(`${label}.allowedSelectors must contain non-empty strings`);\n\t\t}\n\t\tif (new Set(value.allowedSelectors).size !== value.allowedSelectors.length)\n\t\t\tthrow new Error(`${label}.allowedSelectors must not contain duplicates`);\n\t\tif (value.allowedSelectors.length > DIFF_MAX_ITEMS_V1)\n\t\t\tthrow new Error(`${label}.allowedSelectors must contain at most ${DIFF_MAX_ITEMS_V1} items`);\n\t\tfor (const [index, selector] of value.allowedSelectors.entries())\n\t\t\tvalidateBoundedString(selector, `${label}.allowedSelectors[${index}]`);\n\t}\n\treturn detachedFreeze({\n\t\t...(value.maxChangedPixels === undefined ? {} : { maxChangedPixels: value.maxChangedPixels }),\n\t\t...(value.maxChangedRatio === undefined ? {} : { maxChangedRatio: value.maxChangedRatio }),\n\t\t...(value.allowedSelectors === undefined ? {} : { allowedSelectors: [...value.allowedSelectors].sort() }),\n\t});\n}\n\nexport function validateDiffRequestV1(value: unknown, label = \"Diff request\"): DiffRequestV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(value, [\"version\", \"operationId\", \"kind\", \"before\", \"after\"], [\"threshold\"], label);\n\tif (value.version !== 1) throw new Error(`${label}.version must be 1`);\n\tassertNonEmptyString(value.operationId, `${label}.operationId`);\n\tconst kind = validateKind(value.kind, `${label}.kind`);\n\tconst before = validateArtifactRefV1(value.before, `${label}.before`);\n\tconst after = validateArtifactRefV1(value.after, `${label}.after`);\n\tif (before.artifactRef === after.artifactRef) throw new Error(`${label}.before and after must differ`);\n\tconst threshold =\n\t\tvalue.threshold === undefined ? undefined : validateThreshold(value.threshold, `${label}.threshold`);\n\treturn detachedFreeze({\n\t\tversion: 1 as const,\n\t\toperationId: value.operationId,\n\t\tkind,\n\t\tbefore,\n\t\tafter,\n\t\t...(threshold === undefined ? {} : { threshold }),\n\t});\n}\n\nexport function buildDiffRequestV1(input: DiffRequestV1): DiffRequestV1 {\n\treturn validateDiffRequestV1(input);\n}\n\nexport function validateDiffResultV1(value: unknown, label = \"Diff result\"): DiffResultV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(\n\t\tvalue,\n\t\t[\"version\", \"operationId\", \"status\", \"kind\", \"summary\", \"evidence\"],\n\t\t[\"diffArtifact\", \"metrics\", \"verification\", \"error\"],\n\t\tlabel,\n\t);\n\tif (value.version !== 1) throw new Error(`${label}.version must be 1`);\n\tassertNonEmptyString(value.operationId, `${label}.operationId`);\n\tconst resultStatus = validateStatus(value.status, `${label}.status`);\n\tconst kind = validateKind(value.kind, `${label}.kind`);\n\tvalidateBoundedString(value.summary, `${label}.summary`);\n\tconst metrics = value.metrics === undefined ? undefined : validateMetrics(value.metrics, `${label}.metrics`);\n\tconst diffArtifact =\n\t\tvalue.diffArtifact === undefined ? undefined : validateArtifactRefV1(value.diffArtifact, `${label}.diffArtifact`);\n\tif (!Array.isArray(value.evidence)) throw new Error(`${label}.evidence must be an array`);\n\tif (value.evidence.length > DIFF_MAX_ITEMS_V1)\n\t\tthrow new Error(`${label}.evidence must contain at most ${DIFF_MAX_ITEMS_V1} items`);\n\tconst evidence = value.evidence.map((item, index) => validateEvidenceRefV1(item, `${label}.evidence[${index}]`));\n\tif (new Set(evidence.map((item) => item.evidenceId)).size !== evidence.length)\n\t\tthrow new Error(`${label}.evidence must not contain duplicate evidenceId values`);\n\tfor (const [index, item] of evidence.entries()) {\n\t\tvalidateBoundedString(item.evidenceId, `${label}.evidence[${index}].evidenceId`);\n\t\tif (item.summary !== undefined) validateBoundedString(item.summary, `${label}.evidence[${index}].summary`);\n\t\tif (item.observedAt !== undefined)\n\t\t\tvalidateBoundedString(item.observedAt, `${label}.evidence[${index}].observedAt`);\n\t}\n\tconst orderedEvidence = Object.freeze([...evidence].sort((a, b) => a.evidenceId.localeCompare(b.evidenceId)));\n\tconst verification =\n\t\tvalue.verification === undefined\n\t\t\t? undefined\n\t\t\t: validateVerificationV1(value.verification, `${label}.verification`);\n\tconst error = value.error === undefined ? undefined : validatePluginErrorV1(value.error, `${label}.error`);\n\tif (resultStatus !== \"succeeded\" && error === undefined)\n\t\tthrow new Error(`${label}.error is required for status ${resultStatus}`);\n\tif (resultStatus === \"succeeded\" && error !== undefined)\n\t\tthrow new Error(`${label}.succeeded must not include error`);\n\tif (resultStatus === \"succeeded\" && verification?.status === \"failed\")\n\t\tthrow new Error(`${label}.succeeded cannot have failed verification`);\n\tconst result = {\n\t\tversion: 1 as const,\n\t\toperationId: value.operationId,\n\t\tstatus: resultStatus,\n\t\tkind,\n\t\tsummary: value.summary,\n\t\t...(metrics === undefined ? {} : { metrics }),\n\t\t...(diffArtifact === undefined ? {} : { diffArtifact }),\n\t\tevidence: orderedEvidence,\n\t\t...(verification === undefined ? {} : { verification }),\n\t\t...(error === undefined ? {} : { error }),\n\t};\n\tif (new TextEncoder().encode(JSON.stringify(result)).byteLength > DIFF_MAX_RESULT_BYTES_V1)\n\t\tthrow new Error(`${label} exceeds the context budget`);\n\treturn detachedFreeze(result);\n}\n\nexport function buildDiffResultV1(input: DiffResultV1): DiffResultV1 {\n\treturn validateDiffResultV1(input);\n}\n"]}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable service contract types (7.4b) — the plugin-facing persistence
|
|
3
|
+
* surface of the host's durable store.
|
|
4
|
+
*
|
|
5
|
+
* Authority: `@agent-forge/plugin-sdk` (D-075 S4 third batch, type-only
|
|
6
|
+
* migration from the host's `src/durable/durable-service.ts`). The host keeps
|
|
7
|
+
* the implementation (`createDurableService`, writer lease, backends) and its
|
|
8
|
+
* historical module exports; these pure type definitions are the contract a
|
|
9
|
+
* capability plugin programs against (e.g. the first-party subagent-delegate
|
|
10
|
+
* plugin's scheduler persists terminal records through `results.store`).
|
|
11
|
+
* No runtime behavior lives here — importing this module never pulls in
|
|
12
|
+
* `node:fs`.
|
|
13
|
+
*/
|
|
14
|
+
export interface DurableSessionRecordV1 {
|
|
15
|
+
readonly sessionId: string;
|
|
16
|
+
readonly data: unknown;
|
|
17
|
+
readonly persistedAt: number;
|
|
18
|
+
}
|
|
19
|
+
export interface DurableOperationRecordV1 {
|
|
20
|
+
readonly operationId: string;
|
|
21
|
+
readonly sessionId: string;
|
|
22
|
+
readonly data: unknown;
|
|
23
|
+
readonly persistedAt: number;
|
|
24
|
+
}
|
|
25
|
+
export interface DurableArtifactRecordV1 {
|
|
26
|
+
readonly artifactId: string;
|
|
27
|
+
readonly sessionId: string;
|
|
28
|
+
readonly data: unknown;
|
|
29
|
+
readonly persistedAt: number;
|
|
30
|
+
}
|
|
31
|
+
export interface DurableSnapshotV1 {
|
|
32
|
+
readonly version: 1;
|
|
33
|
+
readonly exportedAt: number;
|
|
34
|
+
readonly sessions: readonly DurableSessionRecordV1[];
|
|
35
|
+
readonly operations: readonly DurableOperationRecordV1[];
|
|
36
|
+
readonly artifacts: readonly DurableArtifactRecordV1[];
|
|
37
|
+
readonly writer: {
|
|
38
|
+
readonly holder: string | null;
|
|
39
|
+
readonly crashPending: string | null;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export type DurableWriterClaimV1 = {
|
|
43
|
+
readonly status: "claimed";
|
|
44
|
+
} | {
|
|
45
|
+
readonly status: "held";
|
|
46
|
+
readonly by: string;
|
|
47
|
+
};
|
|
48
|
+
export type DurableWriterRecoveryV1 = {
|
|
49
|
+
readonly status: "recovered";
|
|
50
|
+
readonly previousWriter?: string;
|
|
51
|
+
} | {
|
|
52
|
+
readonly status: "held";
|
|
53
|
+
};
|
|
54
|
+
export interface DurableReadinessV1 {
|
|
55
|
+
readonly ready: boolean;
|
|
56
|
+
readonly reason?: string;
|
|
57
|
+
}
|
|
58
|
+
/** Diagnostics from the startup log replay of a backend-backed service. */
|
|
59
|
+
export interface DurableReplayDiagnosticsV1 {
|
|
60
|
+
/** Malformed log lines skipped while replaying. */
|
|
61
|
+
readonly corruptedLines: number;
|
|
62
|
+
/** True when the log existed but could not be read. */
|
|
63
|
+
readonly unreadable: boolean;
|
|
64
|
+
}
|
|
65
|
+
export interface DurableServiceV1 {
|
|
66
|
+
persistSession(input: {
|
|
67
|
+
readonly writerId: string;
|
|
68
|
+
readonly sessionId: string;
|
|
69
|
+
readonly data: unknown;
|
|
70
|
+
}): DurableSessionRecordV1;
|
|
71
|
+
loadSession(input: {
|
|
72
|
+
readonly sessionId: string;
|
|
73
|
+
}): DurableSessionRecordV1 | undefined;
|
|
74
|
+
persistOperation(input: {
|
|
75
|
+
readonly writerId: string;
|
|
76
|
+
readonly operationId: string;
|
|
77
|
+
readonly sessionId: string;
|
|
78
|
+
readonly data: unknown;
|
|
79
|
+
}): DurableOperationRecordV1;
|
|
80
|
+
loadOperation(input: {
|
|
81
|
+
readonly operationId: string;
|
|
82
|
+
}): DurableOperationRecordV1 | undefined;
|
|
83
|
+
persistArtifact(input: {
|
|
84
|
+
readonly writerId: string;
|
|
85
|
+
readonly artifactId: string;
|
|
86
|
+
readonly sessionId: string;
|
|
87
|
+
readonly data: unknown;
|
|
88
|
+
}): DurableArtifactRecordV1;
|
|
89
|
+
loadArtifact(input: {
|
|
90
|
+
readonly artifactId: string;
|
|
91
|
+
}): DurableArtifactRecordV1 | undefined;
|
|
92
|
+
/** All session records, ordered by sessionId for stable output. */
|
|
93
|
+
listSessions(): readonly DurableSessionRecordV1[];
|
|
94
|
+
claimWriter(writerId: string): DurableWriterClaimV1;
|
|
95
|
+
releaseWriter(writerId: string): {
|
|
96
|
+
readonly status: "released";
|
|
97
|
+
};
|
|
98
|
+
recoverWriter(writerId: string): DurableWriterRecoveryV1;
|
|
99
|
+
readiness(): DurableReadinessV1;
|
|
100
|
+
exportState(): DurableSnapshotV1;
|
|
101
|
+
importState(snapshot: unknown): void;
|
|
102
|
+
/**
|
|
103
|
+
* Diagnostics from the startup log replay of the backend (zeros/false
|
|
104
|
+
* without a backend). Corrupted lines were skipped, not fatal.
|
|
105
|
+
*/
|
|
106
|
+
replayDiagnostics(): DurableReplayDiagnosticsV1;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=durable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"durable.d.ts","sourceRoot":"","sources":["../src/durable.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Durable service contract types (7.4b) — the plugin-facing persistence\n * surface of the host's durable store.\n *\n * Authority: `@agent-forge/plugin-sdk` (D-075 S4 third batch, type-only\n * migration from the host's `src/durable/durable-service.ts`). The host keeps\n * the implementation (`createDurableService`, writer lease, backends) and its\n * historical module exports; these pure type definitions are the contract a\n * capability plugin programs against (e.g. the first-party subagent-delegate\n * plugin's scheduler persists terminal records through `results.store`).\n * No runtime behavior lives here — importing this module never pulls in\n * `node:fs`.\n */\n\nexport interface DurableSessionRecordV1 {\n\treadonly sessionId: string;\n\treadonly data: unknown;\n\treadonly persistedAt: number;\n}\n\nexport interface DurableOperationRecordV1 {\n\treadonly operationId: string;\n\treadonly sessionId: string;\n\treadonly data: unknown;\n\treadonly persistedAt: number;\n}\n\nexport interface DurableArtifactRecordV1 {\n\treadonly artifactId: string;\n\treadonly sessionId: string;\n\treadonly data: unknown;\n\treadonly persistedAt: number;\n}\n\nexport interface DurableSnapshotV1 {\n\treadonly version: 1;\n\treadonly exportedAt: number;\n\treadonly sessions: readonly DurableSessionRecordV1[];\n\treadonly operations: readonly DurableOperationRecordV1[];\n\treadonly artifacts: readonly DurableArtifactRecordV1[];\n\treadonly writer: { readonly holder: string | null; readonly crashPending: string | null };\n}\n\nexport type DurableWriterClaimV1 = { readonly status: \"claimed\" } | { readonly status: \"held\"; readonly by: string };\n\nexport type DurableWriterRecoveryV1 =\n\t| { readonly status: \"recovered\"; readonly previousWriter?: string }\n\t| { readonly status: \"held\" };\n\nexport interface DurableReadinessV1 {\n\treadonly ready: boolean;\n\treadonly reason?: string;\n}\n\n/** Diagnostics from the startup log replay of a backend-backed service. */\nexport interface DurableReplayDiagnosticsV1 {\n\t/** Malformed log lines skipped while replaying. */\n\treadonly corruptedLines: number;\n\t/** True when the log existed but could not be read. */\n\treadonly unreadable: boolean;\n}\n\nexport interface DurableServiceV1 {\n\tpersistSession(input: {\n\t\treadonly writerId: string;\n\t\treadonly sessionId: string;\n\t\treadonly data: unknown;\n\t}): DurableSessionRecordV1;\n\tloadSession(input: { readonly sessionId: string }): DurableSessionRecordV1 | undefined;\n\tpersistOperation(input: {\n\t\treadonly writerId: string;\n\t\treadonly operationId: string;\n\t\treadonly sessionId: string;\n\t\treadonly data: unknown;\n\t}): DurableOperationRecordV1;\n\tloadOperation(input: { readonly operationId: string }): DurableOperationRecordV1 | undefined;\n\tpersistArtifact(input: {\n\t\treadonly writerId: string;\n\t\treadonly artifactId: string;\n\t\treadonly sessionId: string;\n\t\treadonly data: unknown;\n\t}): DurableArtifactRecordV1;\n\tloadArtifact(input: { readonly artifactId: string }): DurableArtifactRecordV1 | undefined;\n\t/** All session records, ordered by sessionId for stable output. */\n\tlistSessions(): readonly DurableSessionRecordV1[];\n\tclaimWriter(writerId: string): DurableWriterClaimV1;\n\treleaseWriter(writerId: string): { readonly status: \"released\" };\n\trecoverWriter(writerId: string): DurableWriterRecoveryV1;\n\treadiness(): DurableReadinessV1;\n\texportState(): DurableSnapshotV1;\n\timportState(snapshot: unknown): void;\n\t/**\n\t * Diagnostics from the startup log replay of the backend (zeros/false\n\t * without a backend). Corrupted lines were skipped, not fatal.\n\t */\n\treplayDiagnostics(): DurableReplayDiagnosticsV1;\n}\n"]}
|
package/dist/durable.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable service contract types (7.4b) — the plugin-facing persistence
|
|
3
|
+
* surface of the host's durable store.
|
|
4
|
+
*
|
|
5
|
+
* Authority: `@agent-forge/plugin-sdk` (D-075 S4 third batch, type-only
|
|
6
|
+
* migration from the host's `src/durable/durable-service.ts`). The host keeps
|
|
7
|
+
* the implementation (`createDurableService`, writer lease, backends) and its
|
|
8
|
+
* historical module exports; these pure type definitions are the contract a
|
|
9
|
+
* capability plugin programs against (e.g. the first-party subagent-delegate
|
|
10
|
+
* plugin's scheduler persists terminal records through `results.store`).
|
|
11
|
+
* No runtime behavior lives here — importing this module never pulls in
|
|
12
|
+
* `node:fs`.
|
|
13
|
+
*/
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=durable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"durable.js","sourceRoot":"","sources":["../src/durable.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG","sourcesContent":["/**\n * Durable service contract types (7.4b) — the plugin-facing persistence\n * surface of the host's durable store.\n *\n * Authority: `@agent-forge/plugin-sdk` (D-075 S4 third batch, type-only\n * migration from the host's `src/durable/durable-service.ts`). The host keeps\n * the implementation (`createDurableService`, writer lease, backends) and its\n * historical module exports; these pure type definitions are the contract a\n * capability plugin programs against (e.g. the first-party subagent-delegate\n * plugin's scheduler persists terminal records through `results.store`).\n * No runtime behavior lives here — importing this module never pulls in\n * `node:fs`.\n */\n\nexport interface DurableSessionRecordV1 {\n\treadonly sessionId: string;\n\treadonly data: unknown;\n\treadonly persistedAt: number;\n}\n\nexport interface DurableOperationRecordV1 {\n\treadonly operationId: string;\n\treadonly sessionId: string;\n\treadonly data: unknown;\n\treadonly persistedAt: number;\n}\n\nexport interface DurableArtifactRecordV1 {\n\treadonly artifactId: string;\n\treadonly sessionId: string;\n\treadonly data: unknown;\n\treadonly persistedAt: number;\n}\n\nexport interface DurableSnapshotV1 {\n\treadonly version: 1;\n\treadonly exportedAt: number;\n\treadonly sessions: readonly DurableSessionRecordV1[];\n\treadonly operations: readonly DurableOperationRecordV1[];\n\treadonly artifacts: readonly DurableArtifactRecordV1[];\n\treadonly writer: { readonly holder: string | null; readonly crashPending: string | null };\n}\n\nexport type DurableWriterClaimV1 = { readonly status: \"claimed\" } | { readonly status: \"held\"; readonly by: string };\n\nexport type DurableWriterRecoveryV1 =\n\t| { readonly status: \"recovered\"; readonly previousWriter?: string }\n\t| { readonly status: \"held\" };\n\nexport interface DurableReadinessV1 {\n\treadonly ready: boolean;\n\treadonly reason?: string;\n}\n\n/** Diagnostics from the startup log replay of a backend-backed service. */\nexport interface DurableReplayDiagnosticsV1 {\n\t/** Malformed log lines skipped while replaying. */\n\treadonly corruptedLines: number;\n\t/** True when the log existed but could not be read. */\n\treadonly unreadable: boolean;\n}\n\nexport interface DurableServiceV1 {\n\tpersistSession(input: {\n\t\treadonly writerId: string;\n\t\treadonly sessionId: string;\n\t\treadonly data: unknown;\n\t}): DurableSessionRecordV1;\n\tloadSession(input: { readonly sessionId: string }): DurableSessionRecordV1 | undefined;\n\tpersistOperation(input: {\n\t\treadonly writerId: string;\n\t\treadonly operationId: string;\n\t\treadonly sessionId: string;\n\t\treadonly data: unknown;\n\t}): DurableOperationRecordV1;\n\tloadOperation(input: { readonly operationId: string }): DurableOperationRecordV1 | undefined;\n\tpersistArtifact(input: {\n\t\treadonly writerId: string;\n\t\treadonly artifactId: string;\n\t\treadonly sessionId: string;\n\t\treadonly data: unknown;\n\t}): DurableArtifactRecordV1;\n\tloadArtifact(input: { readonly artifactId: string }): DurableArtifactRecordV1 | undefined;\n\t/** All session records, ordered by sessionId for stable output. */\n\tlistSessions(): readonly DurableSessionRecordV1[];\n\tclaimWriter(writerId: string): DurableWriterClaimV1;\n\treleaseWriter(writerId: string): { readonly status: \"released\" };\n\trecoverWriter(writerId: string): DurableWriterRecoveryV1;\n\treadiness(): DurableReadinessV1;\n\texportState(): DurableSnapshotV1;\n\timportState(snapshot: unknown): void;\n\t/**\n\t * Diagnostics from the startup log replay of the backend (zeros/false\n\t * without a backend). Corrupted lines were skipped, not fatal.\n\t */\n\treplayDiagnostics(): DurableReplayDiagnosticsV1;\n}\n"]}
|