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