@emkodev/emkore 1.0.3 → 1.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/CHANGELOG.md +17 -0
- package/docs/permission.md +325 -0
- package/package.json +1 -1
- package/src/common/llm/api-definition.type.ts +76 -0
- package/test/unit/authorization-constraints.test.ts +1001 -0
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,23 @@ and this project adheres to
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [1.1.0] - 2026-04-08
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **`LlmStringParameter` extensions**: Added optional `enum?: readonly string[]`, `format?: string`, `default?: string` fields. These let LLM tool descriptions carry enum constraints (allowed values), format hints (e.g. `"date-time"`, `"uuid"`), and default values — closing the largest gap in MCP tool-call quality for typical CRUD usecases.
|
|
16
|
+
- **`LlmNumberParameter` extensions**: Added optional `minimum?: number`, `maximum?: number`, `default?: number` fields for numeric parameter constraints.
|
|
17
|
+
- **`LlmBooleanParameter` extensions**: Added optional `default?: boolean`.
|
|
18
|
+
- **`LlmArrayParameter.items` extensions**: The `items` descriptor now carries optional `format?`, `enum?`, and `properties?` fields, so arrays of dates, enums, or nested objects can be described accurately to LLMs.
|
|
19
|
+
- **`LlmStringReturnValue` / `LlmNumberReturnValue`**: Added the same optional `enum`/`format` (string) and `minimum`/`maximum` (number) fields on the return-value variants for symmetry with parameters.
|
|
20
|
+
- **`apiDefinitionToJsonSchema()` forwarding**: The converter now copies all the new optional fields through to the JSON Schema output, so consumers that read the projected JSON Schema get the full information.
|
|
21
|
+
|
|
22
|
+
### Notes
|
|
23
|
+
|
|
24
|
+
All additions are purely additive optional fields. Existing `ApiDefinition` literals continue to type-check and behave identically. The discriminated-union narrowing on `LlmParameter.type` is preserved — adding optional fields to individual member interfaces does not affect narrowing.
|
|
25
|
+
|
|
26
|
+
This release pairs with the LLM-side `apiDefinition` generation work in `@emkode/cli` 0.7.0 and the field-forwarding logic in `@emkodev/emkoord`.
|
|
27
|
+
|
|
11
28
|
## [1.0.3] - 2026-01-20
|
|
12
29
|
|
|
13
30
|
### Added
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# Permissions
|
|
2
|
+
|
|
3
|
+
## Design philosophy
|
|
4
|
+
|
|
5
|
+
Every permission is explicit. If an actor has access, it is because someone
|
|
6
|
+
created a `Permission` object that says so. There are no wildcards, no deny
|
|
7
|
+
rules, no implicit inheritance, no role abstractions. This is deliberate:
|
|
8
|
+
|
|
9
|
+
- **No `*` wildcards.** A grant covers exactly what it names. You cannot grant
|
|
10
|
+
`{ resource: "*" }` to cover all resources. If an actor needs access to five
|
|
11
|
+
resources, they get five grants.
|
|
12
|
+
- **No deny rules.** The model is allow-only. Access is the union of what is
|
|
13
|
+
explicitly granted — never subtracted.
|
|
14
|
+
- **No implicit inheritance.** A grant on one resource does not imply access to
|
|
15
|
+
related resources. A grant with a broad scope does not bypass non-scope
|
|
16
|
+
constraints.
|
|
17
|
+
- **No roles.** Consumers map roles to permissions externally. Emkore only sees
|
|
18
|
+
flat permission arrays.
|
|
19
|
+
- **No authentication.** The system does not distinguish "authenticated" from
|
|
20
|
+
"guest." It only sees actors and their grants. A guest is an actor with
|
|
21
|
+
grants — possibly none, possibly explicit grants for public resources.
|
|
22
|
+
Authentication is an external concern.
|
|
23
|
+
|
|
24
|
+
The cost is verbosity. The benefit is that every access decision is traceable to
|
|
25
|
+
a specific grant with no ambiguity.
|
|
26
|
+
|
|
27
|
+
## Permission structure
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
interface Permission {
|
|
31
|
+
readonly resource: string; // e.g. "invoice", "document"
|
|
32
|
+
readonly action: string; // e.g. "create", "retrieve", "update"
|
|
33
|
+
readonly constraints?: ResourceConstraints;
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
A permission is a `resource + action` pair with optional `ResourceConstraints`.
|
|
38
|
+
|
|
39
|
+
## ResourceConstraints
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
interface ResourceConstraints {
|
|
43
|
+
scope?: ResourceScope;
|
|
44
|
+
statuses?: string[];
|
|
45
|
+
timeRestriction?: { from: Date; to: Date };
|
|
46
|
+
businessId?: string;
|
|
47
|
+
teamId?: string;
|
|
48
|
+
projectId?: string;
|
|
49
|
+
resourceId?: string;
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Every field is optional. Constraints narrow when and where a permission applies.
|
|
54
|
+
|
|
55
|
+
## ResourceScope
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
BUSINESS ────── tenant-wide (the ceiling)
|
|
59
|
+
├─ PROJECT ──> OWNED
|
|
60
|
+
└─ TEAM ──> OWNED
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`BUSINESS` is the largest organizational scope — it covers the entire tenant.
|
|
64
|
+
`PROJECT` and `TEAM` are parallel siblings under it — neither satisfies the
|
|
65
|
+
other. `OWNED` is the smallest meaningful scope — the baseline that comes with
|
|
66
|
+
any grant. Whatever scope you have, you can access your own resources.
|
|
67
|
+
|
|
68
|
+
The scope hierarchy determines **how much beyond your own resources** you can
|
|
69
|
+
see:
|
|
70
|
+
|
|
71
|
+
- `OWNED` — only your resources
|
|
72
|
+
- `TEAM` — your team's resources (and your own)
|
|
73
|
+
- `PROJECT` — your project's resources (and your own)
|
|
74
|
+
- `BUSINESS` — everything in the tenant (and your own, trivially)
|
|
75
|
+
|
|
76
|
+
### ALL scope
|
|
77
|
+
|
|
78
|
+
`ALL` sits above `BUSINESS` as a god-mode scope. On the grant side, it
|
|
79
|
+
satisfies every scope requirement. On the requirement side, only `ALL` grants
|
|
80
|
+
satisfy it — making it the most restrictive requirement.
|
|
81
|
+
|
|
82
|
+
`ALL` exists for consumers who need a scope tier above the tenant. If a consumer
|
|
83
|
+
does not use it, it is invisible — no grants are issued, no usecases require
|
|
84
|
+
it, and it has no effect.
|
|
85
|
+
|
|
86
|
+
`BUSINESS` is the broadest organizational scope for normal use. `ALL` is
|
|
87
|
+
opt-in for exceptional cases.
|
|
88
|
+
|
|
89
|
+
### Scope satisfaction
|
|
90
|
+
|
|
91
|
+
A grant's scope satisfies a requirement's scope if it is equal to or above it
|
|
92
|
+
in the hierarchy:
|
|
93
|
+
|
|
94
|
+
| Grant \ Requirement | ALL | BUSINESS | PROJECT | TEAM | OWNED |
|
|
95
|
+
| ------------------- | --- | -------- | ------- | ---- | ----- |
|
|
96
|
+
| ALL | Y | Y | Y | Y | Y |
|
|
97
|
+
| BUSINESS | | Y | Y | Y | Y |
|
|
98
|
+
| PROJECT | | | Y | | Y |
|
|
99
|
+
| TEAM | | | | Y | Y |
|
|
100
|
+
| OWNED | | | | | Y |
|
|
101
|
+
|
|
102
|
+
## How constraint matching works
|
|
103
|
+
|
|
104
|
+
All constraints follow the same pattern: **the grant defines what the actor can
|
|
105
|
+
do, the requirement defines what the usecase needs, the interceptor checks if
|
|
106
|
+
the grant covers the requirement.**
|
|
107
|
+
|
|
108
|
+
- Scope: grant must satisfy requirement (hierarchy).
|
|
109
|
+
- Statuses: grant must be a superset of requirement.
|
|
110
|
+
- Time: grant window must cover requirement window.
|
|
111
|
+
- IDs: grant must match requirement (exact).
|
|
112
|
+
|
|
113
|
+
When an actor has multiple grants for the same `resource + action`, each grant
|
|
114
|
+
is checked independently. If any single grant satisfies all required
|
|
115
|
+
constraints, the actor passes.
|
|
116
|
+
|
|
117
|
+
### Rules
|
|
118
|
+
|
|
119
|
+
1. If the usecase requires **no constraints**, any grant with a matching
|
|
120
|
+
`resource + action` passes — regardless of what constraints the grant
|
|
121
|
+
carries.
|
|
122
|
+
|
|
123
|
+
2. If the usecase requires constraints but the grant has **none**,
|
|
124
|
+
authorization fails.
|
|
125
|
+
|
|
126
|
+
3. Each required constraint is checked independently. All must pass (AND logic).
|
|
127
|
+
|
|
128
|
+
4. **Grant-side constraints that the usecase does not require are ignored.**
|
|
129
|
+
This applies to all constraints except `timeRestriction` — see below.
|
|
130
|
+
|
|
131
|
+
### Rule 4 in practice
|
|
132
|
+
|
|
133
|
+
If an actor's grant has `{ scope: ResourceScope.BUSINESS, teamId: "sales" }`,
|
|
134
|
+
the `teamId: "sales"` is only enforced when a usecase explicitly requires a
|
|
135
|
+
`teamId`. A usecase that only requires `{ scope: ResourceScope.TEAM }` will not
|
|
136
|
+
check the `teamId` — the actor passes.
|
|
137
|
+
|
|
138
|
+
Grant-side constraints are **not hard limits on the actor**. They are data the
|
|
139
|
+
actor carries. They are enforced only when a usecase asks for them. If you need
|
|
140
|
+
a constraint to always be enforced, every relevant usecase must include it in
|
|
141
|
+
its `requiredPermissions`.
|
|
142
|
+
|
|
143
|
+
### timeRestriction
|
|
144
|
+
|
|
145
|
+
`timeRestriction` is the exception to rule 4. A grant's time window is always
|
|
146
|
+
enforced against the current time, regardless of whether the usecase requires a
|
|
147
|
+
time restriction. If the actor's grant has a `timeRestriction` and `now` falls
|
|
148
|
+
outside that window, the permission is invalid.
|
|
149
|
+
|
|
150
|
+
When both the grant and the requirement have a `timeRestriction`, both windows
|
|
151
|
+
are checked against `now`. The effective window is their intersection. This
|
|
152
|
+
covers:
|
|
153
|
+
|
|
154
|
+
- **Temporary access** — grant the actor a time window. No usecase changes
|
|
155
|
+
needed. The contractor's grant expires on its own.
|
|
156
|
+
- **Subscription expiration** — the grant's time window represents the
|
|
157
|
+
subscription period.
|
|
158
|
+
- **Narrowed operational windows** — if the usecase restricts its availability
|
|
159
|
+
to certain hours and the grant restricts the actor to different hours, both
|
|
160
|
+
are enforced.
|
|
161
|
+
|
|
162
|
+
> Note: this is the intended behavior. The current implementation does not yet
|
|
163
|
+
> enforce grant-side `timeRestriction` independently — it only checks when the
|
|
164
|
+
> requirement also has one. This is a known issue to be fixed.
|
|
165
|
+
|
|
166
|
+
### Constraint-by-constraint behavior
|
|
167
|
+
|
|
168
|
+
| Constraint | Comparison |
|
|
169
|
+
| ----------------- | -------------------------------------------------------------------------------- |
|
|
170
|
+
| `scope` | Hierarchical: granted scope must satisfy the required scope per the scope table. |
|
|
171
|
+
| `statuses` | Set containment: granted statuses must be a superset of required statuses. |
|
|
172
|
+
| `timeRestriction` | Grant window and requirement window both checked against `now`. |
|
|
173
|
+
| `businessId` | Exact match. |
|
|
174
|
+
| `teamId` | Exact match. |
|
|
175
|
+
| `projectId` | Exact match. |
|
|
176
|
+
| `resourceId` | Exact match. |
|
|
177
|
+
|
|
178
|
+
## Public access
|
|
179
|
+
|
|
180
|
+
"Public" is not a system concept. It is the result of granting a guest actor
|
|
181
|
+
explicit permissions for specific resources and actions.
|
|
182
|
+
|
|
183
|
+
A guest actor with `{ resource: "article", action: "retrieve" }` can retrieve
|
|
184
|
+
articles. The same usecase, same interceptor, same authorization path as any
|
|
185
|
+
other actor. What makes it "public" is that the guest was given the grant.
|
|
186
|
+
|
|
187
|
+
Usecases with empty `requiredPermissions` skip the interceptor entirely. This
|
|
188
|
+
means no authorization runs at all — use this for operations that genuinely need
|
|
189
|
+
no access control (health checks, internal tools), not for public access.
|
|
190
|
+
|
|
191
|
+
## Recipes
|
|
192
|
+
|
|
193
|
+
### 1. "Can this user edit this specific resource?"
|
|
194
|
+
|
|
195
|
+
The usecase requires `resourceId` so the interceptor verifies the actor has
|
|
196
|
+
access to that exact entity. `resourceId` is an exact match — no pattern or
|
|
197
|
+
prefix matching.
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
// Usecase requires:
|
|
201
|
+
{ resource: "document", action: "update",
|
|
202
|
+
constraints: { scope: ResourceScope.TEAM, resourceId: "doc-42" } }
|
|
203
|
+
|
|
204
|
+
// Actor grant — access to one specific document
|
|
205
|
+
{ resource: "document", action: "update",
|
|
206
|
+
constraints: { scope: ResourceScope.TEAM, resourceId: "doc-42" } }
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
The actor can only update `doc-42`. Any other `resourceId` requirement fails.
|
|
210
|
+
|
|
211
|
+
### 2. "Can this user see everything in their team?"
|
|
212
|
+
|
|
213
|
+
The usecase requires `TEAM` scope and a `teamId`. The actor's grant must match
|
|
214
|
+
both.
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
// Usecase requires:
|
|
218
|
+
{ resource: "ticket", action: "list",
|
|
219
|
+
constraints: { scope: ResourceScope.TEAM, teamId: "engineering" } }
|
|
220
|
+
|
|
221
|
+
// Actor grant
|
|
222
|
+
{ resource: "ticket", action: "list",
|
|
223
|
+
constraints: { scope: ResourceScope.TEAM, teamId: "engineering" } }
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
A `BUSINESS`-scoped grant with `teamId: "engineering"` also passes (BUSINESS
|
|
227
|
+
satisfies TEAM). A grant with `teamId: "sales"` fails.
|
|
228
|
+
|
|
229
|
+
### 3. "Can this user only modify draft resources?"
|
|
230
|
+
|
|
231
|
+
The usecase requires certain statuses; the actor's grant must cover all of them.
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
// Usecase requires:
|
|
235
|
+
{ resource: "invoice", action: "update",
|
|
236
|
+
constraints: { statuses: ["draft"] } }
|
|
237
|
+
|
|
238
|
+
// Actor grant — can update drafts and pending invoices
|
|
239
|
+
{ resource: "invoice", action: "update",
|
|
240
|
+
constraints: { statuses: ["draft", "pending"] } }
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Passes because the granted statuses are a superset of the required statuses. An
|
|
244
|
+
actor with only `statuses: ["pending"]` would fail — `"draft"` is not covered.
|
|
245
|
+
|
|
246
|
+
### 4. "How do I make something publicly accessible?"
|
|
247
|
+
|
|
248
|
+
Give the guest actor explicit grants for the resources and actions that should
|
|
249
|
+
be public. The guest goes through the same authorization path as everyone else.
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
// Guest actor grants
|
|
253
|
+
{ resource: "article", action: "retrieve" }
|
|
254
|
+
{ resource: "product", action: "list" }
|
|
255
|
+
|
|
256
|
+
// Usecase — same as for any other actor
|
|
257
|
+
override get requiredPermissions(): Permission[] {
|
|
258
|
+
return [{
|
|
259
|
+
resource: "article",
|
|
260
|
+
action: "retrieve",
|
|
261
|
+
constraints: { scope: ResourceScope.OWNED },
|
|
262
|
+
}];
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
The guest's grant matches the resource and action. The scope on the requirement
|
|
267
|
+
is `OWNED` — the smallest scope, satisfied by any grant. The interceptor passes.
|
|
268
|
+
|
|
269
|
+
What is "public" is determined entirely by which grants the guest actor
|
|
270
|
+
carries — not by the usecase, not by a special scope, not by empty
|
|
271
|
+
requirements.
|
|
272
|
+
|
|
273
|
+
### 5. "Can this user only act during a specific time window?"
|
|
274
|
+
|
|
275
|
+
Put a `timeRestriction` on the grant. No usecase changes needed.
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
// Actor grant — temporary contractor access, March 16 only, 10am to 2pm
|
|
279
|
+
{ resource: "report", action: "retrieve",
|
|
280
|
+
constraints: {
|
|
281
|
+
scope: ResourceScope.TEAM,
|
|
282
|
+
teamId: "engineering",
|
|
283
|
+
timeRestriction: { from: new Date("2026-03-16T10:00"), to: new Date("2026-03-16T14:00") },
|
|
284
|
+
} }
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
The interceptor checks `now` against the grant's time window. Outside the
|
|
288
|
+
window, the grant is invalid. The usecase does not need to know about the time
|
|
289
|
+
restriction.
|
|
290
|
+
|
|
291
|
+
## Grant-side constraints as metadata
|
|
292
|
+
|
|
293
|
+
Grant-side constraints serve a secondary purpose beyond authorization: they
|
|
294
|
+
carry data that usecases and repositories can use at runtime.
|
|
295
|
+
|
|
296
|
+
`Actor.getTeamIds()` extracts all unique `teamId` values from the actor's
|
|
297
|
+
permission constraints. This is useful for filtering queries — e.g. a
|
|
298
|
+
repository can use the actor's team IDs to scope a database query without the
|
|
299
|
+
interceptor needing to understand the query.
|
|
300
|
+
|
|
301
|
+
The `OWNED` scope implies a runtime filter: the usecase or repository should
|
|
302
|
+
return only records owned by the actor. This avoids the need for individual
|
|
303
|
+
`resourceId` grants for every record the actor owns.
|
|
304
|
+
|
|
305
|
+
## What the permissions model does not do
|
|
306
|
+
|
|
307
|
+
- **No wildcard matching.** You cannot grant `{ resource: "*" }` to cover all
|
|
308
|
+
resources. Every grant names exactly what it covers.
|
|
309
|
+
- **No deny rules.** The model is allow-only. You cannot deny a permission to
|
|
310
|
+
override a broader grant.
|
|
311
|
+
- **No roles.** There is no built-in role abstraction. Consumers manage
|
|
312
|
+
role-to-permission mapping externally.
|
|
313
|
+
- **No authentication.** The system does not know about authentication. It sees
|
|
314
|
+
actors and grants. A "guest" is an actor with explicit grants for public
|
|
315
|
+
resources.
|
|
316
|
+
- **No dynamic/contextual checks.** Constraints are static on the permission
|
|
317
|
+
object. There is no hook to evaluate permissions based on the resource's
|
|
318
|
+
runtime state. The exception is `OWNED` scope, which implies the usecase or
|
|
319
|
+
repository should filter by the actor's identity.
|
|
320
|
+
- **No field-level access control.** Permissions operate at the resource level.
|
|
321
|
+
- **No permission delegation.** An actor cannot grant a subset of its
|
|
322
|
+
permissions to another actor.
|
|
323
|
+
- **No custom scope levels.** The scope hierarchy is fixed.
|
|
324
|
+
- **No built-in persistence.** Emkore provides the model and enforcement.
|
|
325
|
+
Consumers decide how to store and load permissions.
|
package/package.json
CHANGED
|
@@ -13,14 +13,21 @@ interface LlmBaseParameter {
|
|
|
13
13
|
// Primitive type parameters
|
|
14
14
|
interface LlmStringParameter extends LlmBaseParameter {
|
|
15
15
|
readonly type: "string";
|
|
16
|
+
readonly enum?: readonly string[];
|
|
17
|
+
readonly format?: string;
|
|
18
|
+
readonly default?: string;
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
interface LlmNumberParameter extends LlmBaseParameter {
|
|
19
22
|
readonly type: "number" | "integer";
|
|
23
|
+
readonly minimum?: number;
|
|
24
|
+
readonly maximum?: number;
|
|
25
|
+
readonly default?: number;
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
interface LlmBooleanParameter extends LlmBaseParameter {
|
|
23
29
|
readonly type: "boolean";
|
|
30
|
+
readonly default?: boolean;
|
|
24
31
|
}
|
|
25
32
|
|
|
26
33
|
interface LlmNullParameter extends LlmBaseParameter {
|
|
@@ -33,6 +40,8 @@ interface LlmArrayParameter extends LlmBaseParameter {
|
|
|
33
40
|
readonly items: {
|
|
34
41
|
readonly type: string;
|
|
35
42
|
readonly description?: string;
|
|
43
|
+
readonly format?: string;
|
|
44
|
+
readonly enum?: readonly string[];
|
|
36
45
|
readonly properties?: JsonSchema;
|
|
37
46
|
};
|
|
38
47
|
}
|
|
@@ -60,10 +69,14 @@ interface LlmBaseReturnValue {
|
|
|
60
69
|
// Primitive type returns
|
|
61
70
|
interface LlmStringReturnValue extends LlmBaseReturnValue {
|
|
62
71
|
readonly type: "string";
|
|
72
|
+
readonly enum?: readonly string[];
|
|
73
|
+
readonly format?: string;
|
|
63
74
|
}
|
|
64
75
|
|
|
65
76
|
interface LlmNumberReturnValue extends LlmBaseReturnValue {
|
|
66
77
|
readonly type: "number" | "integer";
|
|
78
|
+
readonly minimum?: number;
|
|
79
|
+
readonly maximum?: number;
|
|
67
80
|
}
|
|
68
81
|
|
|
69
82
|
interface LlmBooleanReturnValue extends LlmBaseReturnValue {
|
|
@@ -80,7 +93,10 @@ interface LlmArrayReturnValue extends LlmBaseReturnValue {
|
|
|
80
93
|
readonly items: {
|
|
81
94
|
readonly type: string;
|
|
82
95
|
readonly description?: string;
|
|
96
|
+
readonly format?: string;
|
|
97
|
+
readonly enum?: readonly string[];
|
|
83
98
|
readonly properties?: JsonSchema;
|
|
99
|
+
readonly required?: readonly string[];
|
|
84
100
|
};
|
|
85
101
|
}
|
|
86
102
|
|
|
@@ -118,6 +134,42 @@ export function apiDefinitionToJsonSchema(
|
|
|
118
134
|
description: param.description,
|
|
119
135
|
};
|
|
120
136
|
|
|
137
|
+
// String-typed parameter extras
|
|
138
|
+
if (param.type === "string") {
|
|
139
|
+
const stringParam = param as LlmStringParameter;
|
|
140
|
+
if (stringParam.enum) {
|
|
141
|
+
paramSchema.enum = [...stringParam.enum];
|
|
142
|
+
}
|
|
143
|
+
if (stringParam.format !== undefined) {
|
|
144
|
+
paramSchema.format = stringParam.format;
|
|
145
|
+
}
|
|
146
|
+
if (stringParam.default !== undefined) {
|
|
147
|
+
paramSchema.default = stringParam.default;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Number/integer-typed parameter extras
|
|
152
|
+
if (param.type === "number" || param.type === "integer") {
|
|
153
|
+
const numberParam = param as LlmNumberParameter;
|
|
154
|
+
if (numberParam.minimum !== undefined) {
|
|
155
|
+
paramSchema.minimum = numberParam.minimum;
|
|
156
|
+
}
|
|
157
|
+
if (numberParam.maximum !== undefined) {
|
|
158
|
+
paramSchema.maximum = numberParam.maximum;
|
|
159
|
+
}
|
|
160
|
+
if (numberParam.default !== undefined) {
|
|
161
|
+
paramSchema.default = numberParam.default;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Boolean-typed parameter extras
|
|
166
|
+
if (param.type === "boolean") {
|
|
167
|
+
const booleanParam = param as LlmBooleanParameter;
|
|
168
|
+
if (booleanParam.default !== undefined) {
|
|
169
|
+
paramSchema.default = booleanParam.default;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
121
173
|
// Add items for array types (JSON Schema 2020-12 compliance)
|
|
122
174
|
if (param.type === "array" && "items" in param) {
|
|
123
175
|
const arrayParam = param as LlmArrayParameter;
|
|
@@ -150,6 +202,30 @@ export function apiDefinitionToJsonSchema(
|
|
|
150
202
|
description: definition.returns.description,
|
|
151
203
|
};
|
|
152
204
|
|
|
205
|
+
// String-typed return extras
|
|
206
|
+
if (definition.returns.type === "string") {
|
|
207
|
+
const stringReturn = definition.returns as LlmStringReturnValue;
|
|
208
|
+
if (stringReturn.enum) {
|
|
209
|
+
returnsSchema.enum = [...stringReturn.enum];
|
|
210
|
+
}
|
|
211
|
+
if (stringReturn.format !== undefined) {
|
|
212
|
+
returnsSchema.format = stringReturn.format;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Number/integer-typed return extras
|
|
217
|
+
if (
|
|
218
|
+
definition.returns.type === "number" || definition.returns.type === "integer"
|
|
219
|
+
) {
|
|
220
|
+
const numberReturn = definition.returns as LlmNumberReturnValue;
|
|
221
|
+
if (numberReturn.minimum !== undefined) {
|
|
222
|
+
returnsSchema.minimum = numberReturn.minimum;
|
|
223
|
+
}
|
|
224
|
+
if (numberReturn.maximum !== undefined) {
|
|
225
|
+
returnsSchema.maximum = numberReturn.maximum;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
153
229
|
// Add items for array return types (JSON Schema 2020-12 compliance)
|
|
154
230
|
if (definition.returns.type === "array" && "items" in definition.returns) {
|
|
155
231
|
const arrayReturn = definition.returns as LlmArrayReturnValue;
|