@agentpolicyspecification/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/LICENSE +187 -0
  2. package/README.md +13 -0
  3. package/coverage/clover.xml +458 -0
  4. package/coverage/coverage-final.json +7 -0
  5. package/coverage/lcov-report/base.css +224 -0
  6. package/coverage/lcov-report/block-navigation.js +87 -0
  7. package/coverage/lcov-report/favicon.png +0 -0
  8. package/coverage/lcov-report/index.html +146 -0
  9. package/coverage/lcov-report/prettify.css +1 -0
  10. package/coverage/lcov-report/prettify.js +2 -0
  11. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  12. package/coverage/lcov-report/sorter.js +210 -0
  13. package/coverage/lcov-report/src/core/errors.ts.html +217 -0
  14. package/coverage/lcov-report/src/core/index.html +146 -0
  15. package/coverage/lcov-report/src/core/policy.ts.html +142 -0
  16. package/coverage/lcov-report/src/core/types.ts.html +364 -0
  17. package/coverage/lcov-report/src/engine/aps-engine.ts.html +703 -0
  18. package/coverage/lcov-report/src/engine/index.html +131 -0
  19. package/coverage/lcov-report/src/engine/policy-set.ts.html +115 -0
  20. package/coverage/lcov-report/src/index.html +116 -0
  21. package/coverage/lcov-report/src/index.ts.html +244 -0
  22. package/coverage/lcov.info +558 -0
  23. package/dist/core/errors.d.ts +29 -0
  24. package/dist/core/errors.d.ts.map +1 -0
  25. package/dist/core/errors.js +21 -0
  26. package/dist/core/errors.js.map +1 -0
  27. package/dist/core/policy.d.ts +17 -0
  28. package/dist/core/policy.d.ts.map +1 -0
  29. package/dist/core/policy.js +2 -0
  30. package/dist/core/policy.js.map +1 -0
  31. package/dist/core/types.d.ts +67 -0
  32. package/dist/core/types.d.ts.map +1 -0
  33. package/dist/core/types.js +3 -0
  34. package/dist/core/types.js.map +1 -0
  35. package/dist/engine/aps-engine.d.ts +22 -0
  36. package/dist/engine/aps-engine.d.ts.map +1 -0
  37. package/dist/engine/aps-engine.js +167 -0
  38. package/dist/engine/aps-engine.js.map +1 -0
  39. package/dist/engine/policy-set.d.ts +9 -0
  40. package/dist/engine/policy-set.d.ts.map +1 -0
  41. package/dist/engine/policy-set.js +2 -0
  42. package/dist/engine/policy-set.js.map +1 -0
  43. package/dist/generated/base.d.ts +7 -0
  44. package/dist/generated/base.d.ts.map +1 -0
  45. package/dist/generated/base.js +4 -0
  46. package/dist/generated/base.js.map +1 -0
  47. package/dist/generated/dsl-policy.d.ts +130 -0
  48. package/dist/generated/dsl-policy.d.ts.map +1 -0
  49. package/dist/generated/dsl-policy.js +4 -0
  50. package/dist/generated/dsl-policy.js.map +1 -0
  51. package/dist/generated/input-context.d.ts +48 -0
  52. package/dist/generated/input-context.d.ts.map +1 -0
  53. package/dist/generated/input-context.js +4 -0
  54. package/dist/generated/input-context.js.map +1 -0
  55. package/dist/generated/output-context.d.ts +42 -0
  56. package/dist/generated/output-context.d.ts.map +1 -0
  57. package/dist/generated/output-context.js +4 -0
  58. package/dist/generated/output-context.js.map +1 -0
  59. package/dist/generated/policy-decision.d.ts +95 -0
  60. package/dist/generated/policy-decision.d.ts.map +1 -0
  61. package/dist/generated/policy-decision.js +4 -0
  62. package/dist/generated/policy-decision.js.map +1 -0
  63. package/dist/generated/policy-set.d.ts +139 -0
  64. package/dist/generated/policy-set.d.ts.map +1 -0
  65. package/dist/generated/policy-set.js +4 -0
  66. package/dist/generated/policy-set.js.map +1 -0
  67. package/dist/generated/tool-call-context.d.ts +52 -0
  68. package/dist/generated/tool-call-context.d.ts.map +1 -0
  69. package/dist/generated/tool-call-context.js +4 -0
  70. package/dist/generated/tool-call-context.js.map +1 -0
  71. package/dist/index.d.ts +13 -0
  72. package/dist/index.d.ts.map +1 -0
  73. package/dist/index.js +3 -0
  74. package/dist/index.js.map +1 -0
  75. package/examples/basic-usage.ts +89 -0
  76. package/jest.config.js +20 -0
  77. package/package.json +46 -0
  78. package/scripts/generate-types.mjs +24 -0
  79. package/src/core/errors.ts +44 -0
  80. package/src/core/policy.ts +19 -0
  81. package/src/core/types.ts +93 -0
  82. package/src/engine/aps-engine.ts +206 -0
  83. package/src/engine/policy-set.ts +10 -0
  84. package/src/generated/base.ts +9 -0
  85. package/src/generated/dsl-policy.ts +133 -0
  86. package/src/generated/input-context.ts +51 -0
  87. package/src/generated/output-context.ts +45 -0
  88. package/src/generated/policy-decision.ts +98 -0
  89. package/src/generated/policy-set.ts +142 -0
  90. package/src/generated/tool-call-context.ts +55 -0
  91. package/src/index.ts +53 -0
  92. package/test/aps-engine.test.ts +264 -0
  93. package/tsconfig.json +22 -0
  94. package/tsconfig.test.json +10 -0
@@ -0,0 +1,139 @@
1
+ /**
2
+ * A condition evaluated against the context
3
+ */
4
+ export type Condition = EqualsCondition | ContainsCondition | NotInCondition | GreaterThanCondition | AlwaysCondition;
5
+ /**
6
+ * A collection of DSL policies with interception point and tool scope bindings
7
+ */
8
+ export interface PolicySet {
9
+ /**
10
+ * The APS specification version this policy set targets (e.g. '0.1.0').
11
+ */
12
+ aps_version: string;
13
+ /**
14
+ * The list of policies in this set
15
+ */
16
+ policies: PolicyEntry[];
17
+ }
18
+ /**
19
+ * A DSL policy rule with optional scope restrictions
20
+ */
21
+ export interface PolicyEntry {
22
+ condition: Condition;
23
+ /**
24
+ * The action to take when the condition matches
25
+ */
26
+ action: "allow" | "deny" | "redact" | "transform" | "audit";
27
+ /**
28
+ * Optional human-readable reason, typically used with deny
29
+ */
30
+ reason?: string;
31
+ /**
32
+ * Redaction instructions to apply when action is 'redact'.
33
+ *
34
+ * @minItems 1
35
+ */
36
+ redactions?: [
37
+ {
38
+ /**
39
+ * Dot-notation path to the field being redacted (e.g. 'response.content').
40
+ */
41
+ field: string;
42
+ /**
43
+ * 'mask' replaces with a fixed string, 'remove' deletes the field, 'replace' substitutes matched patterns.
44
+ */
45
+ strategy: "mask" | "remove" | "replace";
46
+ /**
47
+ * Replacement string. Required when strategy is 'mask' or 'replace'.
48
+ */
49
+ replacement?: string;
50
+ /**
51
+ * Regex pattern identifying the content to redact. Required when strategy is 'replace'.
52
+ */
53
+ pattern?: string;
54
+ },
55
+ ...{
56
+ /**
57
+ * Dot-notation path to the field being redacted (e.g. 'response.content').
58
+ */
59
+ field: string;
60
+ /**
61
+ * 'mask' replaces with a fixed string, 'remove' deletes the field, 'replace' substitutes matched patterns.
62
+ */
63
+ strategy: "mask" | "remove" | "replace";
64
+ /**
65
+ * Replacement string. Required when strategy is 'mask' or 'replace'.
66
+ */
67
+ replacement?: string;
68
+ /**
69
+ * Regex pattern identifying the content to redact. Required when strategy is 'replace'.
70
+ */
71
+ pattern?: string;
72
+ }[]
73
+ ];
74
+ /**
75
+ * Field transformations when action is 'transform'. Keys are dot-notation field paths, values are template strings supporting {{field.path}} interpolation.
76
+ */
77
+ transformation?: {
78
+ [k: string]: string;
79
+ };
80
+ /**
81
+ * Interception points this policy applies to. Omit to apply to all points.
82
+ *
83
+ * @minItems 1
84
+ */
85
+ applies_to?: ["input" | "output" | "tool_call", ...("input" | "output" | "tool_call")[]];
86
+ /**
87
+ * Tool names this policy applies to. Only evaluated when applies_to includes 'tool_call'. Omit to apply to all tools.
88
+ */
89
+ tools?: string[];
90
+ }
91
+ /**
92
+ * Matches when the resolved field value strictly equals the operand
93
+ */
94
+ export interface EqualsCondition {
95
+ /**
96
+ * Dot-notation path to the field in the context (e.g. 'tool_name', 'messages.0.content')
97
+ */
98
+ field: string;
99
+ /**
100
+ * The value to compare against using strict equality
101
+ */
102
+ equals: {
103
+ [k: string]: unknown;
104
+ };
105
+ }
106
+ /**
107
+ * Matches when the resolved field value contains any of the given substrings (case-insensitive)
108
+ */
109
+ export interface ContainsCondition {
110
+ field: string;
111
+ /**
112
+ * @minItems 1
113
+ */
114
+ contains: [string, ...string[]];
115
+ }
116
+ /**
117
+ * Matches when the resolved field value is not present in the given list
118
+ */
119
+ export interface NotInCondition {
120
+ field: string;
121
+ /**
122
+ * List of values the field must not be equal to
123
+ */
124
+ not_in: unknown[];
125
+ }
126
+ /**
127
+ * Matches when the resolved field value is numerically greater than the operand
128
+ */
129
+ export interface GreaterThanCondition {
130
+ field: string;
131
+ greater_than: number;
132
+ }
133
+ /**
134
+ * Always matches, regardless of context
135
+ */
136
+ export interface AlwaysCondition {
137
+ always: true;
138
+ }
139
+ //# sourceMappingURL=policy-set.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy-set.d.ts","sourceRoot":"","sources":["../../src/generated/policy-set.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,eAAe,GAAG,iBAAiB,GAAG,cAAc,GAAG,oBAAoB,GAAG,eAAe,CAAC;AAEtH;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,QAAQ,EAAE,WAAW,EAAE,CAAC;CACzB;AACD;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,SAAS,CAAC;IACrB;;OAEG;IACH,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,WAAW,GAAG,OAAO,CAAC;IAC5D;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,UAAU,CAAC,EAAE;QACX;YACE;;eAEG;YACH,KAAK,EAAE,MAAM,CAAC;YACd;;eAEG;YACH,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;YACxC;;eAEG;YACH,WAAW,CAAC,EAAE,MAAM,CAAC;YACrB;;eAEG;YACH,OAAO,CAAC,EAAE,MAAM,CAAC;SAClB;QACD,GAAG;YACD;;eAEG;YACH,KAAK,EAAE,MAAM,CAAC;YACd;;eAEG;YACH,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;YACxC;;eAEG;YACH,WAAW,CAAC,EAAE,MAAM,CAAC;YACrB;;eAEG;YACH,OAAO,CAAC,EAAE,MAAM,CAAC;SAClB,EAAE;KACJ,CAAC;IACF;;OAEG;IACH,cAAc,CAAC,EAAE;QACf,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;KACrB,CAAC;IACF;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,OAAO,GAAG,QAAQ,GAAG,WAAW,EAAE,GAAG,CAAC,OAAO,GAAG,QAAQ,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IACzF;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AACD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,MAAM,EAAE;QACN,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;KACtB,CAAC;CACH;AACD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,QAAQ,EAAE,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;CACjC;AACD;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,MAAM,EAAE,OAAO,EAAE,CAAC;CACnB;AACD;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;CACtB;AACD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,IAAI,CAAC;CACd"}
@@ -0,0 +1,4 @@
1
+ /* eslint-disable */
2
+ // This file is auto-generated from policy-set.schema.json. Do not edit manually.
3
+ export {};
4
+ //# sourceMappingURL=policy-set.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy-set.js","sourceRoot":"","sources":["../../src/generated/policy-set.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB,iFAAiF"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * The evaluation input provided to policies at the Tool Call Interception point.
3
+ */
4
+ export type ToolCallContext = ApsBase & {
5
+ /**
6
+ * The name of the tool the LLM has requested to invoke.
7
+ */
8
+ tool_name: string;
9
+ /**
10
+ * The arguments provided by the LLM for the tool invocation.
11
+ */
12
+ arguments: {
13
+ [k: string]: unknown;
14
+ };
15
+ calling_message: AssistantMessage;
16
+ metadata: Metadata;
17
+ };
18
+ /**
19
+ * Base schema for the Agent Policy Specification v0.1.0. All other APS schemas extend this schema. Defines shared types used across the specification.
20
+ */
21
+ export interface ApsBase {
22
+ [k: string]: unknown;
23
+ }
24
+ /**
25
+ * A message produced by the LLM (role must be 'assistant').
26
+ */
27
+ export interface AssistantMessage {
28
+ role: "assistant";
29
+ /**
30
+ * The text content of the assistant message.
31
+ */
32
+ content: string;
33
+ }
34
+ /**
35
+ * Common metadata attached to every APS context object.
36
+ */
37
+ export interface Metadata {
38
+ /**
39
+ * Unique identifier for the agent that owns this session.
40
+ */
41
+ agent_id: string;
42
+ /**
43
+ * Unique identifier for the current session.
44
+ */
45
+ session_id: string;
46
+ /**
47
+ * ISO 8601 timestamp of when the interception occurred.
48
+ */
49
+ timestamp: string;
50
+ [k: string]: unknown;
51
+ }
52
+ //# sourceMappingURL=tool-call-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-call-context.d.ts","sourceRoot":"","sources":["../../src/generated/tool-call-context.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG;IACtC;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,SAAS,EAAE;QACT,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;KACtB,CAAC;IACF,eAAe,EAAE,gBAAgB,CAAC;IAClC,QAAQ,EAAE,QAAQ,CAAC;CACpB,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,OAAO;IACtB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACtB;AACD;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,WAAW,CAAC;IAClB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB;AACD;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACtB"}
@@ -0,0 +1,4 @@
1
+ /* eslint-disable */
2
+ // This file is auto-generated from tool-call-context.schema.json. Do not edit manually.
3
+ export {};
4
+ //# sourceMappingURL=tool-call-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-call-context.js","sourceRoot":"","sources":["../../src/generated/tool-call-context.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB,wFAAwF"}
@@ -0,0 +1,13 @@
1
+ export type { Message, InputContext, } from "./generated/input-context.js";
2
+ export type { OutputContext, Metadata, } from './generated/output-context.js';
3
+ export type { ToolCallContext, AssistantMessage, } from './generated/tool-call-context.js';
4
+ export type { PolicyDecision, AllowDecision, DenyDecision, RedactDecision, TransformDecision, AuditDecision, Redaction, Transformation, } from './generated/policy-decision.js';
5
+ export type { DSLPolicy, Condition, EqualsCondition, ContainsCondition, NotInCondition, GreaterThanCondition, AlwaysCondition, } from './generated/dsl-policy.js';
6
+ export type { PolicySet as JsonPolicySet, PolicyEntry, } from './generated/policy-set.js';
7
+ export type { InputPolicy, ToolCallPolicy, OutputPolicy } from "./core/policy.js";
8
+ export { PolicyDenialError, PolicyEvaluationError, } from "./core/errors.js";
9
+ export type { AuditRecord, InterceptionPoint } from "./core/errors.js";
10
+ export type { PolicySet, OnErrorBehavior } from "./engine/policy-set.js";
11
+ export { ApsEngine } from "./engine/aps-engine.js";
12
+ export type { ApsEngineOptions, AuditHandler } from "./engine/aps-engine.js";
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,OAAO,EACP,YAAY,GACb,MAAM,8BAA8B,CAAC;AAEtC,YAAY,EACV,aAAa,EACb,QAAQ,GACT,MAAM,+BAA+B,CAAC;AAEvC,YAAY,EACV,eAAe,EACf,gBAAgB,GACjB,MAAM,kCAAkC,CAAC;AAE1C,YAAY,EACV,cAAc,EACd,aAAa,EACb,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,aAAa,EACb,SAAS,EACT,cAAc,GACf,MAAM,gCAAgC,CAAC;AAExC,YAAY,EACV,SAAS,EACT,SAAS,EACT,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,oBAAoB,EACpB,eAAe,GAChB,MAAM,2BAA2B,CAAC;AAEnC,YAAY,EACV,SAAS,IAAI,aAAa,EAC1B,WAAW,GACZ,MAAM,2BAA2B,CAAC;AAEnC,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAElF,OAAO,EACL,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAGvE,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { PolicyDenialError, PolicyEvaluationError, } from "./core/errors.js";
2
+ export { ApsEngine } from "./engine/aps-engine.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA2CA,OAAO,EACL,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,kBAAkB,CAAC;AAK1B,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,89 @@
1
+ import {
2
+ ApsEngine,
3
+ type InputPolicy,
4
+ type InputContext,
5
+ type PolicyDecision,
6
+ type ToolCallPolicy,
7
+ type ToolCallContext,
8
+ PolicyDenialError,
9
+ } from "../src/index.js";
10
+
11
+ // ─── Runtime policies ────────────────────────────────────────────────────
12
+
13
+ class NoSSNInputPolicy implements InputPolicy {
14
+ readonly id = "no-ssn-input";
15
+
16
+ evaluate(context: InputContext): PolicyDecision {
17
+ const ssnPattern = /\b\d{3}-\d{2}-\d{4}\b/;
18
+ const found = context.messages.some((m) => ssnPattern.test(m.content));
19
+ return found
20
+ ? { decision: "deny", reason: "Message contains a potential SSN." }
21
+ : { decision: "allow" };
22
+ }
23
+ }
24
+
25
+ class ApprovedToolsPolicy implements ToolCallPolicy {
26
+ readonly id = "approved-tools";
27
+ private readonly approved = new Set(["web_search", "read_file", "summarize"]);
28
+
29
+ evaluate(context: ToolCallContext): PolicyDecision {
30
+ return this.approved.has(context.tool_name)
31
+ ? { decision: "allow" }
32
+ : { decision: "deny", reason: `Tool '${context.tool_name}' is not approved.` };
33
+ }
34
+ }
35
+
36
+ // ─── Engine setup ─────────────────────────────────────────────────────────────
37
+
38
+ const engine = new ApsEngine({
39
+ policySet: {
40
+ on_error: "deny",
41
+ input: [new NoSSNInputPolicy()],
42
+ tool_call: [new ApprovedToolsPolicy()],
43
+ },
44
+ onAudit: (record) => {
45
+ console.log("[audit]", record.interception_point, record.decision, record.reason ?? "");
46
+ },
47
+ });
48
+
49
+ // ─── Example: allowed input ───────────────────────────────────────────────────
50
+
51
+ const allowedInput: InputContext = {
52
+ messages: [{ role: "user", content: "What is the weather in Amsterdam?" }],
53
+ metadata: { agent_id: "agent-1", session_id: "session-1", timestamp: new Date().toISOString() },
54
+ };
55
+
56
+ await engine.evaluateInput(allowedInput);
57
+ console.log("Input allowed.");
58
+
59
+ // ─── Example: denied input ────────────────────────────────────────────────────
60
+
61
+ const deniedInput: InputContext = {
62
+ messages: [{ role: "user", content: "My SSN is 123-45-6789, can you store it?" }],
63
+ metadata: { agent_id: "agent-1", session_id: "session-1", timestamp: new Date().toISOString() },
64
+ };
65
+
66
+ try {
67
+ await engine.evaluateInput(deniedInput);
68
+ } catch (err) {
69
+ if (err instanceof PolicyDenialError) {
70
+ console.log("Input denied:", err.message);
71
+ }
72
+ }
73
+
74
+ // ─── Example: denied tool call ────────────────────────────────────────────────
75
+
76
+ const deniedToolCall: ToolCallContext = {
77
+ tool_name: "delete_file",
78
+ arguments: { path: "/workspace/important.txt" },
79
+ calling_message: { role: "assistant", content: "I will delete the file." },
80
+ metadata: { agent_id: "agent-1", session_id: "session-1", timestamp: new Date().toISOString() },
81
+ };
82
+
83
+ try {
84
+ await engine.evaluateToolCall(deniedToolCall);
85
+ } catch (err) {
86
+ if (err instanceof PolicyDenialError) {
87
+ console.log("Tool call denied:", err.message);
88
+ }
89
+ }
package/jest.config.js ADDED
@@ -0,0 +1,20 @@
1
+ export default {
2
+ preset: "ts-jest/presets/default-esm",
3
+ testEnvironment: "node",
4
+ testMatch: ["**/test/**/*.test.ts"],
5
+ collectCoverage: true,
6
+ coverageDirectory: "coverage",
7
+ coverageProvider: "v8",
8
+ collectCoverageFrom: ["src/**/*.ts", "!src/generated/**"],
9
+ transform: {
10
+ "^.+\\.tsx?$": [
11
+ "ts-jest",
12
+ {
13
+ useESM: true,
14
+ },
15
+ ],
16
+ },
17
+ moduleNameMapper: {
18
+ "^(\\.{1,2}/.*)\\.js$": "$1",
19
+ },
20
+ };
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@agentpolicyspecification/core",
3
+ "version": "0.1.0",
4
+ "description": "Core TypeScript implementation of the Agent Policy Specification (APS)",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "type": "module",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "dependencies": {},
15
+ "devDependencies": {
16
+ "@jest/globals": "^29.7.0",
17
+ "@types/jest": "^29.5.0",
18
+ "@types/node": "^20.0.0",
19
+ "jest": "^29.5.0",
20
+ "json-schema-to-typescript": "^15.0.0",
21
+ "ts-jest": "^29.1.0",
22
+ "tsx": "^4.7.0",
23
+ "typescript": "^5.4.0"
24
+ },
25
+ "license": "Apache-2.0",
26
+ "author": "Pascal Wilbrink",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/agentpolicyspecification/aps-typescript.git",
30
+ "directory": "packages/core"
31
+ },
32
+ "bugs": {
33
+ "url": "https://github.com/agentpolicyspecification/aps-typescript/issues"
34
+ },
35
+ "homepage": "https://github.com/agentpolicyspecification/aps-typescript/tree/main/packages/core#readme",
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "scripts": {
40
+ "build": "pnpm run generate-types && tsc",
41
+ "test": "NODE_OPTIONS=--experimental-vm-modules jest --passWithNoTests",
42
+ "lint": "eslint src --ext .ts",
43
+ "example": "tsx examples/basic-usage.ts",
44
+ "generate-types": "node scripts/generate-types.mjs"
45
+ }
46
+ }
@@ -0,0 +1,24 @@
1
+ import { readFile, writeFile, mkdir, readdir } from 'node:fs/promises';
2
+ import { join, dirname, basename } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { compile } from 'json-schema-to-typescript';
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const schemasDir = join(__dirname, '../../../schemas');
8
+ const outDir = join(__dirname, '../src/generated');
9
+
10
+ await mkdir(outDir, { recursive: true });
11
+
12
+ const files = (await readdir(schemasDir)).filter(f => f.endsWith('.schema.json'));
13
+
14
+ for (const file of files) {
15
+ const schema = JSON.parse(await readFile(join(schemasDir, file), 'utf-8'));
16
+ const ts = await compile(schema, schema.title ?? basename(file, '.schema.json'), {
17
+ bannerComment: `/* eslint-disable */\n// This file is auto-generated from ${file}. Do not edit manually.`,
18
+ cwd: schemasDir,
19
+ });
20
+
21
+ const outFile = join(outDir, basename(file, '.schema.json') + '.ts');
22
+ await writeFile(outFile, ts, 'utf-8');
23
+ console.log(`Generated ${outFile}`);
24
+ }
@@ -0,0 +1,44 @@
1
+ import type { InputContext, OutputContext, ToolCallContext } from "./types.js";
2
+
3
+ export type InterceptionPoint = "input" | "tool_call" | "output";
4
+
5
+ export class PolicyDenialError extends Error {
6
+ readonly policy_id: string | undefined;
7
+ readonly interception_point: InterceptionPoint;
8
+
9
+ constructor(opts: {
10
+ policy_id?: string;
11
+ interception_point: InterceptionPoint;
12
+ reason?: string;
13
+ }) {
14
+ super(opts.reason ?? "Request denied by policy.");
15
+ this.name = "PolicyDenialError";
16
+ this.policy_id = opts.policy_id;
17
+ this.interception_point = opts.interception_point;
18
+ }
19
+ }
20
+
21
+ export class PolicyEvaluationError extends Error {
22
+ readonly policy_id: string;
23
+ readonly interception_point: InterceptionPoint;
24
+
25
+ constructor(opts: {
26
+ policy_id: string;
27
+ interception_point: InterceptionPoint;
28
+ cause: unknown;
29
+ }) {
30
+ super(`Policy evaluation failed for '${opts.policy_id}' at '${opts.interception_point}'.`, { cause: opts.cause });
31
+ this.name = "PolicyEvaluationError";
32
+ this.policy_id = opts.policy_id;
33
+ this.interception_point = opts.interception_point;
34
+ }
35
+ }
36
+
37
+ export type AuditRecord = {
38
+ policy_id: string | undefined;
39
+ decision: string;
40
+ interception_point: InterceptionPoint;
41
+ reason: string | undefined;
42
+ context: InputContext | ToolCallContext | OutputContext;
43
+ timestamp: string;
44
+ };
@@ -0,0 +1,19 @@
1
+ import type { PolicyDecision } from "../generated/policy-decision.js";
2
+ import type { InputContext } from "../generated/input-context.js";
3
+ import type { OutputContext } from "../generated/output-context.js";
4
+ import type { ToolCallContext } from "../generated/tool-call-context.js";
5
+
6
+ export interface InputPolicy {
7
+ readonly id: string;
8
+ evaluate(context: InputContext): Promise<PolicyDecision> | PolicyDecision;
9
+ }
10
+
11
+ export interface ToolCallPolicy {
12
+ readonly id: string;
13
+ evaluate(context: ToolCallContext): Promise<PolicyDecision> | PolicyDecision;
14
+ }
15
+
16
+ export interface OutputPolicy {
17
+ readonly id: string;
18
+ evaluate(context: OutputContext): Promise<PolicyDecision> | PolicyDecision;
19
+ }
@@ -0,0 +1,93 @@
1
+ // ─── Shared ──────────────────────────────────────────────────────────────────
2
+
3
+ export type MessageRole = "system" | "user" | "assistant";
4
+
5
+ export interface Message {
6
+ role: MessageRole;
7
+ content: string;
8
+ }
9
+
10
+ export interface Metadata {
11
+ agent_id: string;
12
+ session_id: string;
13
+ timestamp: string;
14
+ [key: string]: unknown;
15
+ }
16
+
17
+ // ─── Interception Contexts ────────────────────────────────────────────────────
18
+
19
+ export interface InputContext {
20
+ messages: Message[];
21
+ metadata: Metadata;
22
+ }
23
+
24
+ export interface ToolCallContext {
25
+ tool_name: string;
26
+ arguments: Record<string, unknown>;
27
+ calling_message: Message & { role: "assistant" };
28
+ metadata: Metadata;
29
+ }
30
+
31
+ export interface OutputContext {
32
+ response: Message & { role: "assistant" };
33
+ metadata: Metadata;
34
+ }
35
+
36
+ // ─── Policy Decisions ─────────────────────────────────────────────────────────
37
+
38
+ export interface AllowDecision {
39
+ decision: "allow";
40
+ }
41
+
42
+ export interface DenyDecision {
43
+ decision: "deny";
44
+ reason?: string;
45
+ policy_id?: string;
46
+ }
47
+
48
+ export interface RedactDecision {
49
+ decision: "redact";
50
+ redactions: Redaction[];
51
+ }
52
+
53
+ export interface TransformDecision {
54
+ decision: "transform";
55
+ transformation: Transformation;
56
+ }
57
+
58
+ export interface AuditDecision {
59
+ decision: "audit";
60
+ reason?: string;
61
+ }
62
+
63
+ export type PolicyDecision =
64
+ | AllowDecision
65
+ | DenyDecision
66
+ | RedactDecision
67
+ | TransformDecision
68
+ | AuditDecision;
69
+
70
+ // ─── Redaction ────────────────────────────────────────────────────────────────
71
+
72
+ export type RedactionStrategy = "mask" | "remove" | "replace";
73
+
74
+ export interface Redaction {
75
+ field: string;
76
+ strategy: RedactionStrategy;
77
+ replacement?: string;
78
+ pattern?: string;
79
+ }
80
+
81
+ // ─── Transformation ───────────────────────────────────────────────────────────
82
+
83
+ export type TransformOp = "set" | "prepend" | "append";
84
+
85
+ export interface TransformOperation {
86
+ op: TransformOp;
87
+ field: string;
88
+ value: unknown;
89
+ }
90
+
91
+ export interface Transformation {
92
+ operations: TransformOperation[];
93
+ }