@amboras-dev/capability-kit 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +148 -2
- package/dist/index.cjs +1065 -0
- package/dist/index.d.cts +313 -0
- package/dist/index.d.ts +313 -0
- package/dist/index.js +1036 -0
- package/package.json +56 -5
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
//#region src/canonical-json.d.ts
|
|
3
|
+
/** Sorted keys, no whitespace. Throws on BigInt and circular values; `undefined` at the top serializes as `null`. */
|
|
4
|
+
export declare function canonicalJson(v: unknown): string;
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region src/ids.d.ts
|
|
7
|
+
export declare const AREAS: readonly ["promotions", "catalog", "inventory", "setup", "content", "orders", "payments", "domains", "publish"];
|
|
8
|
+
type Area = (typeof AREAS)[number];
|
|
9
|
+
/** `<area>.<verb>` or `<area>.<entity>.<verb>`: lowercase segments of `a-z` and `_`. */
|
|
10
|
+
type CapabilityId = `${Area}.${string}`;
|
|
11
|
+
type Role = 'owner' | 'manager' | 'staff';
|
|
12
|
+
/** Higher rank includes every lower one: an owner can do what a manager can. */
|
|
13
|
+
export declare const ROLE_RANK: Readonly<Record<Role, number>>;
|
|
14
|
+
/** True for a well-formed id in a known area. */
|
|
15
|
+
export declare function isCapabilityId(value: unknown): value is CapabilityId;
|
|
16
|
+
/** The area an id belongs to (`promotions.create` → `promotions`). */
|
|
17
|
+
export declare function areaOf(id: CapabilityId): Area;
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/context.d.ts
|
|
20
|
+
type Actor = {
|
|
21
|
+
kind: 'member';
|
|
22
|
+
profileId: string;
|
|
23
|
+
role: Role | null;
|
|
24
|
+
} | {
|
|
25
|
+
kind: 'internal';
|
|
26
|
+
service: string;
|
|
27
|
+
};
|
|
28
|
+
/** A label for logs. Never trusted for a policy decision. */
|
|
29
|
+
type Surface = 'ai' | 'admin' | 'api';
|
|
30
|
+
interface CapabilityCallContext {
|
|
31
|
+
tenantId: string;
|
|
32
|
+
actor: Actor;
|
|
33
|
+
surface: Surface;
|
|
34
|
+
runId?: string;
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/json-schema.d.ts
|
|
38
|
+
type JsonSchema = {
|
|
39
|
+
[key: string]: unknown;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* A copy of `schema` without the hidden fields (`agent.hideFields`), also
|
|
43
|
+
* dropped from `required`. Adapters build the model-facing input with this.
|
|
44
|
+
*/
|
|
45
|
+
export declare function omitFields(schema: JsonSchema, fields: readonly string[]): JsonSchema;
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/policy-types.d.ts
|
|
48
|
+
interface CapabilityPolicy {
|
|
49
|
+
minRole: Role;
|
|
50
|
+
planFeature?: string;
|
|
51
|
+
effect: 'read' | 'write' | 'destructive';
|
|
52
|
+
/** True = safe to retry without a key (reads, set-to-value). */
|
|
53
|
+
idempotent: boolean;
|
|
54
|
+
/** Grants or moves value: fail closed everywhere. */
|
|
55
|
+
money?: boolean;
|
|
56
|
+
confirm: 'never' | 'always' | {
|
|
57
|
+
when: {
|
|
58
|
+
recordsAtLeast: number;
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
rateLimit?: {
|
|
62
|
+
perMinute: number;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region src/define.d.ts
|
|
67
|
+
interface CapabilityAgentSpec {
|
|
68
|
+
/** What it does and when to use it, written for the model. */
|
|
69
|
+
description: string;
|
|
70
|
+
/** Only what the schema shape cannot carry. */
|
|
71
|
+
rules?: string[];
|
|
72
|
+
examples?: Array<{
|
|
73
|
+
input: unknown;
|
|
74
|
+
note: string;
|
|
75
|
+
}>;
|
|
76
|
+
/** Dotted input paths the model never sees; each must exist and be optional. */
|
|
77
|
+
hideFields?: string[];
|
|
78
|
+
exposure: 'task-tool' | 'long-tail' | 'hidden';
|
|
79
|
+
/** Phase 1: keeps the legacy tool name (e.g. 'create_promotion') so prompts, evals and TOOL_CAPABILITIES keys stay stable. */
|
|
80
|
+
toolName?: string;
|
|
81
|
+
}
|
|
82
|
+
interface CapabilityDefinition<I = unknown, O = unknown, Ctx = unknown> {
|
|
83
|
+
id: CapabilityId;
|
|
84
|
+
/** Integer ≥ 1; bump on a breaking input or output change. */
|
|
85
|
+
version: number;
|
|
86
|
+
owner: 'medusa' | 'orchestrator';
|
|
87
|
+
http: {
|
|
88
|
+
method: 'GET' | 'POST' | 'DELETE';
|
|
89
|
+
path: string;
|
|
90
|
+
runsOn: 'custom' | 'core';
|
|
91
|
+
};
|
|
92
|
+
input: z.ZodType<I>;
|
|
93
|
+
output: z.ZodType<O>;
|
|
94
|
+
run?(ctx: Ctx, input: I): Promise<O>;
|
|
95
|
+
policy: CapabilityPolicy;
|
|
96
|
+
agent: CapabilityAgentSpec;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Checks a definition and returns a frozen copy. Throws CapabilityDefinitionError
|
|
100
|
+
* on: a bad id or unknown area; a version that is not a positive integer; an
|
|
101
|
+
* input that is not an object, accepts undeclared keys or any value, or names a
|
|
102
|
+
* store / tenant / profile / environment id at any depth; runsOn 'custom'
|
|
103
|
+
* without run; hideFields naming a missing (or required) field; a schema JSON
|
|
104
|
+
* Schema cannot express; a money() field without its unit sentence or with a
|
|
105
|
+
* currencyField that is not a string field at the input root; an invalid
|
|
106
|
+
* minRole, confirm threshold, rate limit or toolName.
|
|
107
|
+
*/
|
|
108
|
+
export declare function defineCapability<I, O, Ctx = unknown>(def: CapabilityDefinition<I, O, Ctx>): Readonly<CapabilityDefinition<I, O, Ctx>>;
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region src/errors.d.ts
|
|
111
|
+
type CapabilityErrorCode = 'invalid_input' | 'forbidden' | 'not_found' | 'conflict' | 'confirmation_required' | 'rate_limited' | 'idempotency_conflict' | 'unavailable' | 'internal';
|
|
112
|
+
interface CapabilityErrorBody {
|
|
113
|
+
code: CapabilityErrorCode;
|
|
114
|
+
message: string;
|
|
115
|
+
/** Dotted input path the error is about (`discount.amount_off`). */
|
|
116
|
+
field?: string;
|
|
117
|
+
/** Stable id of the business rule that refused (`idempotency_key_required`). */
|
|
118
|
+
rule?: string;
|
|
119
|
+
retryable: boolean;
|
|
120
|
+
issues?: Array<{
|
|
121
|
+
field: string;
|
|
122
|
+
message: string;
|
|
123
|
+
}>;
|
|
124
|
+
/** Small structured facts the caller can act on (duplicate_code → { existing_id, existing_status }). Never secrets or PII. */
|
|
125
|
+
details?: Record<string, string | number | boolean | null>;
|
|
126
|
+
}
|
|
127
|
+
export declare function httpStatusFor(code: CapabilityErrorCode): number;
|
|
128
|
+
type CapabilityErrorInit = Omit<CapabilityErrorBody, 'retryable'> & {
|
|
129
|
+
retryable?: boolean;
|
|
130
|
+
};
|
|
131
|
+
export declare class CapabilityError extends Error {
|
|
132
|
+
readonly body: CapabilityErrorBody;
|
|
133
|
+
readonly status: number;
|
|
134
|
+
/** `retryable` defaults to true for `rate_limited` and `unavailable`, else false. */
|
|
135
|
+
constructor(init: CapabilityErrorInit);
|
|
136
|
+
}
|
|
137
|
+
/** A definition that breaks a kit rule. Thrown at boot, never per request. */
|
|
138
|
+
export declare class CapabilityDefinitionError extends Error {
|
|
139
|
+
constructor(message: string);
|
|
140
|
+
}
|
|
141
|
+
/** A manifest that fails its shape or hash check. */
|
|
142
|
+
export declare class CapabilityManifestError extends Error {
|
|
143
|
+
constructor(message: string);
|
|
144
|
+
}
|
|
145
|
+
interface FromZodErrorOptions {
|
|
146
|
+
/**
|
|
147
|
+
* Fields that no longer exist, keyed by the old name, valued with what to use
|
|
148
|
+
* instead. An unrecognized key listed here (strict objects only) answers
|
|
149
|
+
* `"value" was removed: use percent_off or amount_off (major units).` with
|
|
150
|
+
* rule `field_renamed`, instead of zod's generic message.
|
|
151
|
+
*/
|
|
152
|
+
renamed?: Readonly<Record<string, string>>;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* `invalid_input`: `field` is the dotted path of the first issue, `issues` lists
|
|
156
|
+
* all of them. Renamed-field issues (`options.renamed`) come first and make the headline. A custom issue's `params.rule` becomes `body.rule`; see
|
|
157
|
+
* `options.renamed` for steering removed field names.
|
|
158
|
+
*/
|
|
159
|
+
export declare function fromZodError(err: z.ZodError, options?: FromZodErrorOptions): CapabilityError;
|
|
160
|
+
//#endregion
|
|
161
|
+
//#region src/idempotency.d.ts
|
|
162
|
+
/**
|
|
163
|
+
* 8–100 of `A-Z a-z 0-9 . _ : -`. The underscore is there because the
|
|
164
|
+
* orchestrator's key is `cap.{runId}.{toolCallId}` and model tool call ids
|
|
165
|
+
* contain one (`toolu_…`, `call_…`).
|
|
166
|
+
*/
|
|
167
|
+
export declare const IDEMPOTENCY_KEY_PATTERN: RegExp;
|
|
168
|
+
/** A non-read capability that is not naturally idempotent needs an `Idempotency-Key`. */
|
|
169
|
+
export declare function requiresIdempotencyKey(policy: Pick<CapabilityPolicy, 'effect' | 'idempotent'>): boolean;
|
|
170
|
+
/** sha256 hex of canonicalJson({ id, input }): same key + different fingerprint = a different request. */
|
|
171
|
+
export declare function requestFingerprint(id: CapabilityId, input: unknown): string;
|
|
172
|
+
//#endregion
|
|
173
|
+
//#region src/manifest.d.ts
|
|
174
|
+
interface ManifestEntry {
|
|
175
|
+
id: CapabilityId;
|
|
176
|
+
version: number;
|
|
177
|
+
owner: 'medusa' | 'orchestrator';
|
|
178
|
+
http: CapabilityDefinition['http'];
|
|
179
|
+
input: JsonSchema;
|
|
180
|
+
output: JsonSchema;
|
|
181
|
+
policy: CapabilityPolicy;
|
|
182
|
+
agent: CapabilityAgentSpec;
|
|
183
|
+
}
|
|
184
|
+
interface CapabilityManifest {
|
|
185
|
+
schemaVersion: 1;
|
|
186
|
+
source: 'medusa' | 'orchestrator';
|
|
187
|
+
hash: string;
|
|
188
|
+
capabilities: ManifestEntry[];
|
|
189
|
+
}
|
|
190
|
+
/** Any definition, whatever its input, output and context types. */
|
|
191
|
+
type AnyCapabilityDefinition = Readonly<CapabilityDefinition<unknown, unknown, never>>;
|
|
192
|
+
/**
|
|
193
|
+
* Deterministic: entries sorted by id, schemas converted with
|
|
194
|
+
* `{ target: 'draft-2020-12', io }`, no timestamps. Every definition is checked
|
|
195
|
+
* again (the defineCapability rules). Throws on a duplicate id.
|
|
196
|
+
*
|
|
197
|
+
* The JSON Schema text depends on the zod version that emits it (zod 3.25 writes
|
|
198
|
+
* a discriminated union as anyOf, 4.3 as oneOf), so the hash is only comparable
|
|
199
|
+
* between emits on the SAME zod version. Consumers verify a received manifest
|
|
200
|
+
* with parseManifest; they never re-emit it to compare.
|
|
201
|
+
*/
|
|
202
|
+
export declare function emitManifest(defs: readonly AnyCapabilityDefinition[], source: 'medusa' | 'orchestrator'): CapabilityManifest;
|
|
203
|
+
/**
|
|
204
|
+
* Validates the shape, re-runs the definition rules on every entry (tenant ids,
|
|
205
|
+
* open inputs, money sentence …) and recomputes the hash from the JSON as
|
|
206
|
+
* received, never from schemas. Throws CapabilityManifestError on any failure.
|
|
207
|
+
* The hash proves the file is intact, not who wrote it: trust comes from where
|
|
208
|
+
* the file is read (the committed copy), not from the hash.
|
|
209
|
+
*/
|
|
210
|
+
export declare function parseManifest(json: unknown): CapabilityManifest;
|
|
211
|
+
//#endregion
|
|
212
|
+
//#region src/money.d.ts
|
|
213
|
+
/** The one unit sentence every money field carries, for the model and for people. */
|
|
214
|
+
export declare const MONEY_UNIT_SENTENCE = "Amount in normal currency units as the merchant says it (99 means 99.00; 1.5 means 1.50). Never cents or minor units.";
|
|
215
|
+
/** JSON Schema keyword marking a money field. */
|
|
216
|
+
export declare const MONEY_META_KEY = "x-amboras-money";
|
|
217
|
+
/**
|
|
218
|
+
* A bad amount or currency code. It is an `invalid_input` CapabilityError, so an
|
|
219
|
+
* adapter that maps CapabilityError answers 400 with the rule (and the field,
|
|
220
|
+
* when the caller passed one) instead of a 500.
|
|
221
|
+
*/
|
|
222
|
+
export declare class MoneyError extends CapabilityError {
|
|
223
|
+
constructor(message: string, rule: 'money_precision' | 'money_invalid' | 'currency_invalid', field?: string);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Where the amount's currency comes from. `currencyField` names a field resolved
|
|
227
|
+
* from the INPUT ROOT, at any depth of the money field (`discount.amount_off`
|
|
228
|
+
* with `{ currencyField: 'currency_code' }` reads the top-level `currency_code`).
|
|
229
|
+
*/
|
|
230
|
+
type MoneyCurrency = {
|
|
231
|
+
currencyField: string;
|
|
232
|
+
} | {
|
|
233
|
+
currency: 'store';
|
|
234
|
+
};
|
|
235
|
+
type MoneyOptions = MoneyCurrency & {
|
|
236
|
+
/** What the amount is; prepended to the unit sentence. */
|
|
237
|
+
describe?: string;
|
|
238
|
+
/** Strict lower bound (`0` = must be more than zero). Default: amounts are ≥ 0. */
|
|
239
|
+
exclusiveMinimum?: number;
|
|
240
|
+
/** Inclusive upper bound. */
|
|
241
|
+
max?: number;
|
|
242
|
+
};
|
|
243
|
+
/**
|
|
244
|
+
* A major-unit amount: finite, ≥ 0 (or > `exclusiveMinimum`), at most `max`,
|
|
245
|
+
* described with MONEY_UNIT_SENTENCE and marked `x-amboras-money` in JSON Schema.
|
|
246
|
+
*
|
|
247
|
+
* Chained checks (`.positive()`, `.max()`, `.int()`) and `.optional()`,
|
|
248
|
+
* `.nullable()`, `.nullish()` are safe: the kit's JSON Schema keeps the sentence
|
|
249
|
+
* and the marker. `.describe()` replaces the sentence, which `defineCapability`
|
|
250
|
+
* rejects: pass `describe` here instead.
|
|
251
|
+
*/
|
|
252
|
+
export declare function money(opts: MoneyOptions): z.ZodNumber;
|
|
253
|
+
/** ISO 4217 minor-unit digits: 0 for JPY/KRW/VND…, 3 for KWD/BHD/OMR/TND/JOD/IQD/LYD, else 2 (also for unknown codes). */
|
|
254
|
+
export declare function fractionDigits(currency: string): 0 | 2 | 3;
|
|
255
|
+
/**
|
|
256
|
+
* Amboras storage rule: ×1000 for the seven three-decimal codes, ×100 for every
|
|
257
|
+
* other code INCLUDING zero-decimal ones (¥5 is stored as 500). By design; see
|
|
258
|
+
* Medusa `divisorFor`.
|
|
259
|
+
*/
|
|
260
|
+
export declare function storageScale(currency: string): 100 | 1000;
|
|
261
|
+
/**
|
|
262
|
+
* Major → stored minor integer. Throws MoneyError (`invalid_input`) when the
|
|
263
|
+
* amount has more fraction digits than the currency allows (19.999 USD, 5.5 JPY),
|
|
264
|
+
* is not finite, or does not fit a safe integer. Pass `field` to name the input
|
|
265
|
+
* field in the error.
|
|
266
|
+
*
|
|
267
|
+
* An amount is accepted when its decimal text has at most the allowed digits
|
|
268
|
+
* (exact at any magnitude), or when it is within float noise of such an amount
|
|
269
|
+
* (0.1 + 0.2 → 0.30). Noise is bounded absolutely, so a real extra digit is never
|
|
270
|
+
* rounded away, however large the amount.
|
|
271
|
+
*/
|
|
272
|
+
export declare function toStorageAmount(major: number, currency: string, field?: string): number;
|
|
273
|
+
/** Stored minor amount → major units. */
|
|
274
|
+
export declare function fromStorageAmount(minor: number, currency: string): number;
|
|
275
|
+
interface MoneyView {
|
|
276
|
+
amount: number;
|
|
277
|
+
currency: string;
|
|
278
|
+
display: string;
|
|
279
|
+
}
|
|
280
|
+
/** Output schema for a MoneyView (what `moneyView` returns), for capability outputs. */
|
|
281
|
+
export declare const moneyViewSchema: z.ZodType<MoneyView>;
|
|
282
|
+
/**
|
|
283
|
+
* A stored amount as the merchant reads it. Fraction digits are pinned (not left
|
|
284
|
+
* to the runtime's ICU data): ISO digits, widened only to show a stored value
|
|
285
|
+
* that is not a whole minor unit (¥5.5 stays visible).
|
|
286
|
+
*/
|
|
287
|
+
export declare function moneyView(minor: number, currency: string): MoneyView;
|
|
288
|
+
//#endregion
|
|
289
|
+
//#region src/policy.d.ts
|
|
290
|
+
/** Phase 1 adapters pass observe/observe. */
|
|
291
|
+
interface PolicyEnforcement {
|
|
292
|
+
role: 'enforce' | 'observe';
|
|
293
|
+
plan: 'enforce' | 'observe';
|
|
294
|
+
}
|
|
295
|
+
/** `observed` holds the rule ids an 'observe' check would have denied, for logging. */
|
|
296
|
+
type PolicyDecision = {
|
|
297
|
+
allow: true;
|
|
298
|
+
observed: string[];
|
|
299
|
+
} | {
|
|
300
|
+
allow: false;
|
|
301
|
+
error: CapabilityError;
|
|
302
|
+
};
|
|
303
|
+
export declare function evaluatePolicy(policy: CapabilityPolicy, ctx: CapabilityCallContext, facts: {
|
|
304
|
+
planFeatures?: string[];
|
|
305
|
+
}, enforcement: PolicyEnforcement): PolicyDecision;
|
|
306
|
+
/**
|
|
307
|
+
* True when the call needs an explicit confirmation before it runs. `records` is
|
|
308
|
+
* how many records it touches. A threshold policy with no count, or a count that
|
|
309
|
+
* is not a non-negative integer, requires confirmation (the adapter could not count).
|
|
310
|
+
*/
|
|
311
|
+
export declare function confirmationRequired(policy: Pick<CapabilityPolicy, 'confirm'>, records?: number): boolean;
|
|
312
|
+
//#endregion
|
|
313
|
+
export type { Actor, AnyCapabilityDefinition, Area, CapabilityAgentSpec, CapabilityCallContext, CapabilityDefinition, CapabilityErrorBody, CapabilityErrorCode, CapabilityErrorInit, CapabilityId, CapabilityManifest, CapabilityPolicy, FromZodErrorOptions, JsonSchema, ManifestEntry, MoneyCurrency, MoneyOptions, MoneyView, PolicyDecision, PolicyEnforcement, Role, Surface };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
//#region src/canonical-json.d.ts
|
|
3
|
+
/** Sorted keys, no whitespace. Throws on BigInt and circular values; `undefined` at the top serializes as `null`. */
|
|
4
|
+
export declare function canonicalJson(v: unknown): string;
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region src/ids.d.ts
|
|
7
|
+
export declare const AREAS: readonly ["promotions", "catalog", "inventory", "setup", "content", "orders", "payments", "domains", "publish"];
|
|
8
|
+
type Area = (typeof AREAS)[number];
|
|
9
|
+
/** `<area>.<verb>` or `<area>.<entity>.<verb>`: lowercase segments of `a-z` and `_`. */
|
|
10
|
+
type CapabilityId = `${Area}.${string}`;
|
|
11
|
+
type Role = 'owner' | 'manager' | 'staff';
|
|
12
|
+
/** Higher rank includes every lower one: an owner can do what a manager can. */
|
|
13
|
+
export declare const ROLE_RANK: Readonly<Record<Role, number>>;
|
|
14
|
+
/** True for a well-formed id in a known area. */
|
|
15
|
+
export declare function isCapabilityId(value: unknown): value is CapabilityId;
|
|
16
|
+
/** The area an id belongs to (`promotions.create` → `promotions`). */
|
|
17
|
+
export declare function areaOf(id: CapabilityId): Area;
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/context.d.ts
|
|
20
|
+
type Actor = {
|
|
21
|
+
kind: 'member';
|
|
22
|
+
profileId: string;
|
|
23
|
+
role: Role | null;
|
|
24
|
+
} | {
|
|
25
|
+
kind: 'internal';
|
|
26
|
+
service: string;
|
|
27
|
+
};
|
|
28
|
+
/** A label for logs. Never trusted for a policy decision. */
|
|
29
|
+
type Surface = 'ai' | 'admin' | 'api';
|
|
30
|
+
interface CapabilityCallContext {
|
|
31
|
+
tenantId: string;
|
|
32
|
+
actor: Actor;
|
|
33
|
+
surface: Surface;
|
|
34
|
+
runId?: string;
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/json-schema.d.ts
|
|
38
|
+
type JsonSchema = {
|
|
39
|
+
[key: string]: unknown;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* A copy of `schema` without the hidden fields (`agent.hideFields`), also
|
|
43
|
+
* dropped from `required`. Adapters build the model-facing input with this.
|
|
44
|
+
*/
|
|
45
|
+
export declare function omitFields(schema: JsonSchema, fields: readonly string[]): JsonSchema;
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/policy-types.d.ts
|
|
48
|
+
interface CapabilityPolicy {
|
|
49
|
+
minRole: Role;
|
|
50
|
+
planFeature?: string;
|
|
51
|
+
effect: 'read' | 'write' | 'destructive';
|
|
52
|
+
/** True = safe to retry without a key (reads, set-to-value). */
|
|
53
|
+
idempotent: boolean;
|
|
54
|
+
/** Grants or moves value: fail closed everywhere. */
|
|
55
|
+
money?: boolean;
|
|
56
|
+
confirm: 'never' | 'always' | {
|
|
57
|
+
when: {
|
|
58
|
+
recordsAtLeast: number;
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
rateLimit?: {
|
|
62
|
+
perMinute: number;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region src/define.d.ts
|
|
67
|
+
interface CapabilityAgentSpec {
|
|
68
|
+
/** What it does and when to use it, written for the model. */
|
|
69
|
+
description: string;
|
|
70
|
+
/** Only what the schema shape cannot carry. */
|
|
71
|
+
rules?: string[];
|
|
72
|
+
examples?: Array<{
|
|
73
|
+
input: unknown;
|
|
74
|
+
note: string;
|
|
75
|
+
}>;
|
|
76
|
+
/** Dotted input paths the model never sees; each must exist and be optional. */
|
|
77
|
+
hideFields?: string[];
|
|
78
|
+
exposure: 'task-tool' | 'long-tail' | 'hidden';
|
|
79
|
+
/** Phase 1: keeps the legacy tool name (e.g. 'create_promotion') so prompts, evals and TOOL_CAPABILITIES keys stay stable. */
|
|
80
|
+
toolName?: string;
|
|
81
|
+
}
|
|
82
|
+
interface CapabilityDefinition<I = unknown, O = unknown, Ctx = unknown> {
|
|
83
|
+
id: CapabilityId;
|
|
84
|
+
/** Integer ≥ 1; bump on a breaking input or output change. */
|
|
85
|
+
version: number;
|
|
86
|
+
owner: 'medusa' | 'orchestrator';
|
|
87
|
+
http: {
|
|
88
|
+
method: 'GET' | 'POST' | 'DELETE';
|
|
89
|
+
path: string;
|
|
90
|
+
runsOn: 'custom' | 'core';
|
|
91
|
+
};
|
|
92
|
+
input: z.ZodType<I>;
|
|
93
|
+
output: z.ZodType<O>;
|
|
94
|
+
run?(ctx: Ctx, input: I): Promise<O>;
|
|
95
|
+
policy: CapabilityPolicy;
|
|
96
|
+
agent: CapabilityAgentSpec;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Checks a definition and returns a frozen copy. Throws CapabilityDefinitionError
|
|
100
|
+
* on: a bad id or unknown area; a version that is not a positive integer; an
|
|
101
|
+
* input that is not an object, accepts undeclared keys or any value, or names a
|
|
102
|
+
* store / tenant / profile / environment id at any depth; runsOn 'custom'
|
|
103
|
+
* without run; hideFields naming a missing (or required) field; a schema JSON
|
|
104
|
+
* Schema cannot express; a money() field without its unit sentence or with a
|
|
105
|
+
* currencyField that is not a string field at the input root; an invalid
|
|
106
|
+
* minRole, confirm threshold, rate limit or toolName.
|
|
107
|
+
*/
|
|
108
|
+
export declare function defineCapability<I, O, Ctx = unknown>(def: CapabilityDefinition<I, O, Ctx>): Readonly<CapabilityDefinition<I, O, Ctx>>;
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region src/errors.d.ts
|
|
111
|
+
type CapabilityErrorCode = 'invalid_input' | 'forbidden' | 'not_found' | 'conflict' | 'confirmation_required' | 'rate_limited' | 'idempotency_conflict' | 'unavailable' | 'internal';
|
|
112
|
+
interface CapabilityErrorBody {
|
|
113
|
+
code: CapabilityErrorCode;
|
|
114
|
+
message: string;
|
|
115
|
+
/** Dotted input path the error is about (`discount.amount_off`). */
|
|
116
|
+
field?: string;
|
|
117
|
+
/** Stable id of the business rule that refused (`idempotency_key_required`). */
|
|
118
|
+
rule?: string;
|
|
119
|
+
retryable: boolean;
|
|
120
|
+
issues?: Array<{
|
|
121
|
+
field: string;
|
|
122
|
+
message: string;
|
|
123
|
+
}>;
|
|
124
|
+
/** Small structured facts the caller can act on (duplicate_code → { existing_id, existing_status }). Never secrets or PII. */
|
|
125
|
+
details?: Record<string, string | number | boolean | null>;
|
|
126
|
+
}
|
|
127
|
+
export declare function httpStatusFor(code: CapabilityErrorCode): number;
|
|
128
|
+
type CapabilityErrorInit = Omit<CapabilityErrorBody, 'retryable'> & {
|
|
129
|
+
retryable?: boolean;
|
|
130
|
+
};
|
|
131
|
+
export declare class CapabilityError extends Error {
|
|
132
|
+
readonly body: CapabilityErrorBody;
|
|
133
|
+
readonly status: number;
|
|
134
|
+
/** `retryable` defaults to true for `rate_limited` and `unavailable`, else false. */
|
|
135
|
+
constructor(init: CapabilityErrorInit);
|
|
136
|
+
}
|
|
137
|
+
/** A definition that breaks a kit rule. Thrown at boot, never per request. */
|
|
138
|
+
export declare class CapabilityDefinitionError extends Error {
|
|
139
|
+
constructor(message: string);
|
|
140
|
+
}
|
|
141
|
+
/** A manifest that fails its shape or hash check. */
|
|
142
|
+
export declare class CapabilityManifestError extends Error {
|
|
143
|
+
constructor(message: string);
|
|
144
|
+
}
|
|
145
|
+
interface FromZodErrorOptions {
|
|
146
|
+
/**
|
|
147
|
+
* Fields that no longer exist, keyed by the old name, valued with what to use
|
|
148
|
+
* instead. An unrecognized key listed here (strict objects only) answers
|
|
149
|
+
* `"value" was removed: use percent_off or amount_off (major units).` with
|
|
150
|
+
* rule `field_renamed`, instead of zod's generic message.
|
|
151
|
+
*/
|
|
152
|
+
renamed?: Readonly<Record<string, string>>;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* `invalid_input`: `field` is the dotted path of the first issue, `issues` lists
|
|
156
|
+
* all of them. Renamed-field issues (`options.renamed`) come first and make the headline. A custom issue's `params.rule` becomes `body.rule`; see
|
|
157
|
+
* `options.renamed` for steering removed field names.
|
|
158
|
+
*/
|
|
159
|
+
export declare function fromZodError(err: z.ZodError, options?: FromZodErrorOptions): CapabilityError;
|
|
160
|
+
//#endregion
|
|
161
|
+
//#region src/idempotency.d.ts
|
|
162
|
+
/**
|
|
163
|
+
* 8–100 of `A-Z a-z 0-9 . _ : -`. The underscore is there because the
|
|
164
|
+
* orchestrator's key is `cap.{runId}.{toolCallId}` and model tool call ids
|
|
165
|
+
* contain one (`toolu_…`, `call_…`).
|
|
166
|
+
*/
|
|
167
|
+
export declare const IDEMPOTENCY_KEY_PATTERN: RegExp;
|
|
168
|
+
/** A non-read capability that is not naturally idempotent needs an `Idempotency-Key`. */
|
|
169
|
+
export declare function requiresIdempotencyKey(policy: Pick<CapabilityPolicy, 'effect' | 'idempotent'>): boolean;
|
|
170
|
+
/** sha256 hex of canonicalJson({ id, input }): same key + different fingerprint = a different request. */
|
|
171
|
+
export declare function requestFingerprint(id: CapabilityId, input: unknown): string;
|
|
172
|
+
//#endregion
|
|
173
|
+
//#region src/manifest.d.ts
|
|
174
|
+
interface ManifestEntry {
|
|
175
|
+
id: CapabilityId;
|
|
176
|
+
version: number;
|
|
177
|
+
owner: 'medusa' | 'orchestrator';
|
|
178
|
+
http: CapabilityDefinition['http'];
|
|
179
|
+
input: JsonSchema;
|
|
180
|
+
output: JsonSchema;
|
|
181
|
+
policy: CapabilityPolicy;
|
|
182
|
+
agent: CapabilityAgentSpec;
|
|
183
|
+
}
|
|
184
|
+
interface CapabilityManifest {
|
|
185
|
+
schemaVersion: 1;
|
|
186
|
+
source: 'medusa' | 'orchestrator';
|
|
187
|
+
hash: string;
|
|
188
|
+
capabilities: ManifestEntry[];
|
|
189
|
+
}
|
|
190
|
+
/** Any definition, whatever its input, output and context types. */
|
|
191
|
+
type AnyCapabilityDefinition = Readonly<CapabilityDefinition<unknown, unknown, never>>;
|
|
192
|
+
/**
|
|
193
|
+
* Deterministic: entries sorted by id, schemas converted with
|
|
194
|
+
* `{ target: 'draft-2020-12', io }`, no timestamps. Every definition is checked
|
|
195
|
+
* again (the defineCapability rules). Throws on a duplicate id.
|
|
196
|
+
*
|
|
197
|
+
* The JSON Schema text depends on the zod version that emits it (zod 3.25 writes
|
|
198
|
+
* a discriminated union as anyOf, 4.3 as oneOf), so the hash is only comparable
|
|
199
|
+
* between emits on the SAME zod version. Consumers verify a received manifest
|
|
200
|
+
* with parseManifest; they never re-emit it to compare.
|
|
201
|
+
*/
|
|
202
|
+
export declare function emitManifest(defs: readonly AnyCapabilityDefinition[], source: 'medusa' | 'orchestrator'): CapabilityManifest;
|
|
203
|
+
/**
|
|
204
|
+
* Validates the shape, re-runs the definition rules on every entry (tenant ids,
|
|
205
|
+
* open inputs, money sentence …) and recomputes the hash from the JSON as
|
|
206
|
+
* received, never from schemas. Throws CapabilityManifestError on any failure.
|
|
207
|
+
* The hash proves the file is intact, not who wrote it: trust comes from where
|
|
208
|
+
* the file is read (the committed copy), not from the hash.
|
|
209
|
+
*/
|
|
210
|
+
export declare function parseManifest(json: unknown): CapabilityManifest;
|
|
211
|
+
//#endregion
|
|
212
|
+
//#region src/money.d.ts
|
|
213
|
+
/** The one unit sentence every money field carries, for the model and for people. */
|
|
214
|
+
export declare const MONEY_UNIT_SENTENCE = "Amount in normal currency units as the merchant says it (99 means 99.00; 1.5 means 1.50). Never cents or minor units.";
|
|
215
|
+
/** JSON Schema keyword marking a money field. */
|
|
216
|
+
export declare const MONEY_META_KEY = "x-amboras-money";
|
|
217
|
+
/**
|
|
218
|
+
* A bad amount or currency code. It is an `invalid_input` CapabilityError, so an
|
|
219
|
+
* adapter that maps CapabilityError answers 400 with the rule (and the field,
|
|
220
|
+
* when the caller passed one) instead of a 500.
|
|
221
|
+
*/
|
|
222
|
+
export declare class MoneyError extends CapabilityError {
|
|
223
|
+
constructor(message: string, rule: 'money_precision' | 'money_invalid' | 'currency_invalid', field?: string);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Where the amount's currency comes from. `currencyField` names a field resolved
|
|
227
|
+
* from the INPUT ROOT, at any depth of the money field (`discount.amount_off`
|
|
228
|
+
* with `{ currencyField: 'currency_code' }` reads the top-level `currency_code`).
|
|
229
|
+
*/
|
|
230
|
+
type MoneyCurrency = {
|
|
231
|
+
currencyField: string;
|
|
232
|
+
} | {
|
|
233
|
+
currency: 'store';
|
|
234
|
+
};
|
|
235
|
+
type MoneyOptions = MoneyCurrency & {
|
|
236
|
+
/** What the amount is; prepended to the unit sentence. */
|
|
237
|
+
describe?: string;
|
|
238
|
+
/** Strict lower bound (`0` = must be more than zero). Default: amounts are ≥ 0. */
|
|
239
|
+
exclusiveMinimum?: number;
|
|
240
|
+
/** Inclusive upper bound. */
|
|
241
|
+
max?: number;
|
|
242
|
+
};
|
|
243
|
+
/**
|
|
244
|
+
* A major-unit amount: finite, ≥ 0 (or > `exclusiveMinimum`), at most `max`,
|
|
245
|
+
* described with MONEY_UNIT_SENTENCE and marked `x-amboras-money` in JSON Schema.
|
|
246
|
+
*
|
|
247
|
+
* Chained checks (`.positive()`, `.max()`, `.int()`) and `.optional()`,
|
|
248
|
+
* `.nullable()`, `.nullish()` are safe: the kit's JSON Schema keeps the sentence
|
|
249
|
+
* and the marker. `.describe()` replaces the sentence, which `defineCapability`
|
|
250
|
+
* rejects: pass `describe` here instead.
|
|
251
|
+
*/
|
|
252
|
+
export declare function money(opts: MoneyOptions): z.ZodNumber;
|
|
253
|
+
/** ISO 4217 minor-unit digits: 0 for JPY/KRW/VND…, 3 for KWD/BHD/OMR/TND/JOD/IQD/LYD, else 2 (also for unknown codes). */
|
|
254
|
+
export declare function fractionDigits(currency: string): 0 | 2 | 3;
|
|
255
|
+
/**
|
|
256
|
+
* Amboras storage rule: ×1000 for the seven three-decimal codes, ×100 for every
|
|
257
|
+
* other code INCLUDING zero-decimal ones (¥5 is stored as 500). By design; see
|
|
258
|
+
* Medusa `divisorFor`.
|
|
259
|
+
*/
|
|
260
|
+
export declare function storageScale(currency: string): 100 | 1000;
|
|
261
|
+
/**
|
|
262
|
+
* Major → stored minor integer. Throws MoneyError (`invalid_input`) when the
|
|
263
|
+
* amount has more fraction digits than the currency allows (19.999 USD, 5.5 JPY),
|
|
264
|
+
* is not finite, or does not fit a safe integer. Pass `field` to name the input
|
|
265
|
+
* field in the error.
|
|
266
|
+
*
|
|
267
|
+
* An amount is accepted when its decimal text has at most the allowed digits
|
|
268
|
+
* (exact at any magnitude), or when it is within float noise of such an amount
|
|
269
|
+
* (0.1 + 0.2 → 0.30). Noise is bounded absolutely, so a real extra digit is never
|
|
270
|
+
* rounded away, however large the amount.
|
|
271
|
+
*/
|
|
272
|
+
export declare function toStorageAmount(major: number, currency: string, field?: string): number;
|
|
273
|
+
/** Stored minor amount → major units. */
|
|
274
|
+
export declare function fromStorageAmount(minor: number, currency: string): number;
|
|
275
|
+
interface MoneyView {
|
|
276
|
+
amount: number;
|
|
277
|
+
currency: string;
|
|
278
|
+
display: string;
|
|
279
|
+
}
|
|
280
|
+
/** Output schema for a MoneyView (what `moneyView` returns), for capability outputs. */
|
|
281
|
+
export declare const moneyViewSchema: z.ZodType<MoneyView>;
|
|
282
|
+
/**
|
|
283
|
+
* A stored amount as the merchant reads it. Fraction digits are pinned (not left
|
|
284
|
+
* to the runtime's ICU data): ISO digits, widened only to show a stored value
|
|
285
|
+
* that is not a whole minor unit (¥5.5 stays visible).
|
|
286
|
+
*/
|
|
287
|
+
export declare function moneyView(minor: number, currency: string): MoneyView;
|
|
288
|
+
//#endregion
|
|
289
|
+
//#region src/policy.d.ts
|
|
290
|
+
/** Phase 1 adapters pass observe/observe. */
|
|
291
|
+
interface PolicyEnforcement {
|
|
292
|
+
role: 'enforce' | 'observe';
|
|
293
|
+
plan: 'enforce' | 'observe';
|
|
294
|
+
}
|
|
295
|
+
/** `observed` holds the rule ids an 'observe' check would have denied, for logging. */
|
|
296
|
+
type PolicyDecision = {
|
|
297
|
+
allow: true;
|
|
298
|
+
observed: string[];
|
|
299
|
+
} | {
|
|
300
|
+
allow: false;
|
|
301
|
+
error: CapabilityError;
|
|
302
|
+
};
|
|
303
|
+
export declare function evaluatePolicy(policy: CapabilityPolicy, ctx: CapabilityCallContext, facts: {
|
|
304
|
+
planFeatures?: string[];
|
|
305
|
+
}, enforcement: PolicyEnforcement): PolicyDecision;
|
|
306
|
+
/**
|
|
307
|
+
* True when the call needs an explicit confirmation before it runs. `records` is
|
|
308
|
+
* how many records it touches. A threshold policy with no count, or a count that
|
|
309
|
+
* is not a non-negative integer, requires confirmation (the adapter could not count).
|
|
310
|
+
*/
|
|
311
|
+
export declare function confirmationRequired(policy: Pick<CapabilityPolicy, 'confirm'>, records?: number): boolean;
|
|
312
|
+
//#endregion
|
|
313
|
+
export type { Actor, AnyCapabilityDefinition, Area, CapabilityAgentSpec, CapabilityCallContext, CapabilityDefinition, CapabilityErrorBody, CapabilityErrorCode, CapabilityErrorInit, CapabilityId, CapabilityManifest, CapabilityPolicy, FromZodErrorOptions, JsonSchema, ManifestEntry, MoneyCurrency, MoneyOptions, MoneyView, PolicyDecision, PolicyEnforcement, Role, Surface };
|