@kindgi/api 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/dist/app.d.ts.map +1 -1
  2. package/dist/app.js +2 -1
  3. package/dist/app.js.map +1 -1
  4. package/dist/cost-binding.d.ts +81 -5
  5. package/dist/cost-binding.d.ts.map +1 -1
  6. package/dist/cost-binding.js +6 -0
  7. package/dist/cost-binding.js.map +1 -1
  8. package/dist/hitl-binding.d.ts +20 -5
  9. package/dist/hitl-binding.d.ts.map +1 -1
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js.map +1 -1
  13. package/dist/middleware/auth.d.ts +3 -2
  14. package/dist/middleware/auth.d.ts.map +1 -1
  15. package/dist/middleware/auth.js.map +1 -1
  16. package/dist/middleware/idempotency.d.ts +5 -1
  17. package/dist/middleware/idempotency.d.ts.map +1 -1
  18. package/dist/middleware/idempotency.js +8 -1
  19. package/dist/middleware/idempotency.js.map +1 -1
  20. package/dist/openapi/operations.d.ts.map +1 -1
  21. package/dist/openapi/operations.js +83 -18
  22. package/dist/openapi/operations.js.map +1 -1
  23. package/dist/openapi/schemas.d.ts +18 -0
  24. package/dist/openapi/schemas.d.ts.map +1 -1
  25. package/dist/openapi/schemas.js +202 -7
  26. package/dist/openapi/schemas.js.map +1 -1
  27. package/dist/provenance-binding.d.ts +27 -1
  28. package/dist/provenance-binding.d.ts.map +1 -1
  29. package/dist/provenance-binding.js.map +1 -1
  30. package/dist/reviewer-binding.d.ts +10 -2
  31. package/dist/reviewer-binding.d.ts.map +1 -1
  32. package/dist/reviewer-role.d.ts +13 -0
  33. package/dist/reviewer-role.d.ts.map +1 -0
  34. package/dist/reviewer-role.js +27 -0
  35. package/dist/reviewer-role.js.map +1 -0
  36. package/dist/routes/approvals.d.ts +5 -4
  37. package/dist/routes/approvals.d.ts.map +1 -1
  38. package/dist/routes/approvals.js +52 -16
  39. package/dist/routes/approvals.js.map +1 -1
  40. package/dist/routes/conversations.d.ts +7 -1
  41. package/dist/routes/conversations.d.ts.map +1 -1
  42. package/dist/routes/conversations.js +41 -1
  43. package/dist/routes/conversations.js.map +1 -1
  44. package/dist/routes/cost.d.ts.map +1 -1
  45. package/dist/routes/cost.js +95 -9
  46. package/dist/routes/cost.js.map +1 -1
  47. package/dist/routes/identity.d.ts +7 -0
  48. package/dist/routes/identity.d.ts.map +1 -1
  49. package/dist/routes/identity.js +3 -2
  50. package/dist/routes/identity.js.map +1 -1
  51. package/dist/routes/provenance.d.ts.map +1 -1
  52. package/dist/routes/provenance.js +32 -1
  53. package/dist/routes/provenance.js.map +1 -1
  54. package/dist/routes/runs.d.ts +0 -7
  55. package/dist/routes/runs.d.ts.map +1 -1
  56. package/dist/routes/runs.js +44 -20
  57. package/dist/routes/runs.js.map +1 -1
  58. package/dist/routes/scope-params.d.ts +16 -1
  59. package/dist/routes/scope-params.d.ts.map +1 -1
  60. package/dist/routes/scope-params.js +28 -0
  61. package/dist/routes/scope-params.js.map +1 -1
  62. package/dist/routes/sse.d.ts +2 -2
  63. package/dist/routes/sse.js +1 -1
  64. package/dist/types.d.ts +4 -3
  65. package/dist/types.d.ts.map +1 -1
  66. package/dist/webhook-endpoint-binding.d.ts +11 -0
  67. package/dist/webhook-endpoint-binding.d.ts.map +1 -1
  68. package/dist/webhook-endpoint-binding.js.map +1 -1
  69. package/openapi.json +599 -24
  70. package/package.json +24 -24
  71. package/src/app.ts +5 -1
  72. package/src/cost-binding.ts +96 -5
  73. package/src/hitl-binding.ts +20 -4
  74. package/src/index.ts +3 -0
  75. package/src/middleware/auth.ts +3 -2
  76. package/src/middleware/idempotency.ts +7 -1
  77. package/src/openapi/operations.ts +93 -18
  78. package/src/openapi/schemas.ts +221 -7
  79. package/src/provenance-binding.ts +42 -1
  80. package/src/reviewer-binding.ts +10 -2
  81. package/src/reviewer-role.ts +35 -0
  82. package/src/routes/approvals.ts +70 -19
  83. package/src/routes/conversations.ts +57 -1
  84. package/src/routes/cost.ts +104 -20
  85. package/src/routes/identity.ts +10 -2
  86. package/src/routes/provenance.ts +48 -1
  87. package/src/routes/runs.ts +54 -22
  88. package/src/routes/scope-params.ts +35 -1
  89. package/src/routes/sse.ts +2 -2
  90. package/src/types.ts +4 -3
  91. package/src/webhook-endpoint-binding.ts +11 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kindgi/api",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "REST + SSE HTTP surface for Kindgi™. createApp assembles a Hono app serving the /v1/* REST API (bearer or session-token auth; OpenAPI 3.1 document at /v1/openapi.json) and an optional S3-compatible /s3/* surface (SigV4), over caller-plugged bindings for the runtime (runs, agents, flows, HITL, supervisor) and for storage. Route conventions: docs/API-ROUTE-CONVENTIONS.md.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -18,7 +18,8 @@
18
18
  "exports": {
19
19
  ".": {
20
20
  "types": "./dist/index.d.ts",
21
- "import": "./dist/index.js"
21
+ "import": "./dist/index.js",
22
+ "default": "./dist/index.js"
22
23
  },
23
24
  "./openapi.json": "./openapi.json",
24
25
  "./package.json": "./package.json"
@@ -31,31 +32,30 @@
31
32
  "openapi.json"
32
33
  ],
33
34
  "dependencies": {
34
- "@kindgi/agents": "0.1.1",
35
- "@kindgi/audit-events": "0.1.1",
36
- "@kindgi/compliance": "0.1.1",
37
- "@kindgi/authz": "0.1.1",
38
- "@kindgi/blob-binding": "0.1.1",
39
- "@kindgi/capabilities": "0.1.1",
40
- "@kindgi/crypto": "0.1.1",
41
- "@kindgi/flow": "0.1.1",
42
- "@kindgi/guardrails": "0.1.1",
43
- "@kindgi/memory": "0.1.1",
44
- "@kindgi/platform": "0.1.1",
45
- "@kindgi/provenance": "0.1.1",
46
- "@kindgi/runtime": "0.1.1",
47
- "@kindgi/policy-contract": "0.1.1",
48
- "@kindgi/schema": "0.1.1",
49
- "@kindgi/tools": "0.1.1",
50
- "@kindgi/types": "0.1.1",
35
+ "@kindgi/agents": "0.1.3",
36
+ "@kindgi/audit-events": "0.1.3",
37
+ "@kindgi/compliance": "0.1.3",
38
+ "@kindgi/authz": "0.1.3",
39
+ "@kindgi/blob-binding": "0.1.3",
40
+ "@kindgi/capabilities": "0.1.3",
41
+ "@kindgi/crypto": "0.1.3",
42
+ "@kindgi/flow": "0.1.3",
43
+ "@kindgi/guardrails": "0.1.3",
44
+ "@kindgi/memory": "0.1.3",
45
+ "@kindgi/platform": "0.1.3",
46
+ "@kindgi/provenance": "0.1.3",
47
+ "@kindgi/runtime": "0.1.3",
48
+ "@kindgi/policy-contract": "0.1.3",
49
+ "@kindgi/schema": "0.1.3",
50
+ "@kindgi/tools": "0.1.3",
51
+ "@kindgi/types": "0.1.3",
51
52
  "@scalar/hono-api-reference": "^0.12.2",
52
- "drizzle-orm": "^0.36.4",
53
53
  "hono": "^4.6.14"
54
54
  },
55
55
  "devDependencies": {
56
- "@kindgi/audit-events-inmemory": "0.1.1",
57
- "@kindgi/specs": "0.1.1",
58
- "@kindgi/testing": "0.1.1",
56
+ "@kindgi/audit-events-inmemory": "0.1.3",
57
+ "@kindgi/specs": "0.1.3",
58
+ "@kindgi/testing": "0.1.3",
59
59
  "@types/aws4": "^1.11.6",
60
60
  "@types/node": "^22.10.5",
61
61
  "aws4": "^1.13.2",
@@ -63,7 +63,7 @@
63
63
  "vitest": "^2.1.8"
64
64
  },
65
65
  "engines": {
66
- "node": ">=22.0.0"
66
+ "node": ">=22.12.0"
67
67
  },
68
68
  "publishConfig": {
69
69
  "access": "public",
package/src/app.ts CHANGED
@@ -955,7 +955,10 @@ export function createApp(input: CreateAppInput): Hono<AppEnv> {
955
955
  authorizer,
956
956
  ),
957
957
  );
958
- v1.route('/conversations', conversationsRouter(input.conversationBinding, runBinding));
958
+ v1.route(
959
+ '/conversations',
960
+ conversationsRouter(input.conversationBinding, runBinding, input.projectBinding),
961
+ );
959
962
  if (publicRunTokenLimits !== undefined) {
960
963
  v1.route(
961
964
  '/tokens/public',
@@ -1070,6 +1073,7 @@ export function createApp(input: CreateAppInput): Hono<AppEnv> {
1070
1073
  identityRouter({
1071
1074
  ...(input.identityDirectory !== undefined && { directory: input.identityDirectory }),
1072
1075
  ...(input.sessionStore !== undefined && { sessionStore: input.sessionStore }),
1076
+ ...(input.reviewerBinding !== undefined && { reviewerBinding: input.reviewerBinding }),
1073
1077
  }),
1074
1078
  );
1075
1079
  if (input.cost !== undefined) {
@@ -42,13 +42,16 @@ export interface CostBinding {
42
42
  getRecord(input: CostGetRecordInput): Promise<CostRecord | null>;
43
43
  /**
44
44
  * Multi-dimensional aggregate rollup. `groupBy` may combine any
45
- * subset of `agentId | runId | category | providerId | day | month
46
- * | tenant | conversationId`; the binding returns one group per
47
- * distinct key tuple within the required `from`..`to` window.
45
+ * subset of `COST_GROUP_DIMENSIONS`; the binding returns one group per
46
+ * distinct key tuple within the required `from`..`to` window, with its
47
+ * cost and token sums.
48
48
  */
49
49
  aggregate(input: CostAggregateInput): Promise<CostAggregateResult>;
50
50
  }
51
51
 
52
+ // A binding throws when its store fails: the route answers 500. It never
53
+ // answers a failed read with an empty page or zero sums.
54
+
52
55
  // -------- listRecords --------
53
56
 
54
57
  export interface CostListRecordsInput {
@@ -80,6 +83,8 @@ export interface CostListRecordsInput {
80
83
  * uniformity: scope-aware bindings share one filter shape.
81
84
  */
82
85
  readonly inherit?: boolean;
86
+ /** Add each record's `rawUsage`: the vendor's own usage object. */
87
+ readonly includeRawUsage?: boolean;
83
88
  }
84
89
 
85
90
  export interface CostRecordFilter {
@@ -94,7 +99,20 @@ export interface CostRecordFilter {
94
99
  */
95
100
  readonly category?: string;
96
101
  readonly providerId?: string;
102
+ /** The model actually called. */
103
+ readonly model?: string;
104
+ /** The exact model version the vendor reported. */
105
+ readonly servedModel?: string;
106
+ /** Every record of the run tree whose root is this run. */
107
+ readonly rootRunId?: string;
108
+ /**
109
+ * With `runId`: that run's records and those of every run it started,
110
+ * at any depth.
111
+ */
112
+ readonly includeDescendants?: boolean;
113
+ /** Inclusive. */
97
114
  readonly from?: Date;
115
+ /** Exclusive. */
98
116
  readonly to?: Date;
99
117
  }
100
118
 
@@ -108,6 +126,8 @@ export interface CostRecordPage {
108
126
  export interface CostGetRecordInput {
109
127
  readonly tenantId: TenantId;
110
128
  readonly recordId: string;
129
+ /** Add the record's `rawUsage`: the vendor's own usage object. */
130
+ readonly includeRawUsage?: boolean;
111
131
  }
112
132
 
113
133
  // -------- aggregate --------
@@ -116,7 +136,9 @@ export interface CostGetRecordInput {
116
136
  * Group dimensions the binding may aggregate over. `day` / `month` are
117
137
  * time bucketing on `occurredAt` (UTC calendar day / month). Every call
118
138
  * is tenant-scoped, so `tenant` always resolves to the caller's tenant
119
- * id.
139
+ * id. `model` is the model actually called; `servedModel` the exact
140
+ * version the vendor reported. `orgId` is the org of the record's
141
+ * project (`null` for a project in no org).
120
142
  */
121
143
  export type CostGroupDimension =
122
144
  | 'agentId'
@@ -126,7 +148,13 @@ export type CostGroupDimension =
126
148
  | 'day'
127
149
  | 'month'
128
150
  | 'tenant'
129
- | 'conversationId';
151
+ | 'conversationId'
152
+ | 'model'
153
+ | 'servedModel'
154
+ | 'projectId'
155
+ | 'orgId'
156
+ | 'rootRunId'
157
+ | 'flowId';
130
158
 
131
159
  export const COST_GROUP_DIMENSIONS: readonly CostGroupDimension[] = [
132
160
  'agentId',
@@ -137,6 +165,12 @@ export const COST_GROUP_DIMENSIONS: readonly CostGroupDimension[] = [
137
165
  'month',
138
166
  'tenant',
139
167
  'conversationId',
168
+ 'model',
169
+ 'servedModel',
170
+ 'projectId',
171
+ 'orgId',
172
+ 'rootRunId',
173
+ 'flowId',
140
174
  ];
141
175
 
142
176
  export interface CostAggregateInput {
@@ -174,12 +208,27 @@ export interface CostAggregateGroup {
174
208
  readonly key: Readonly<Record<string, string | null>>;
175
209
  readonly count: number;
176
210
  readonly totalUsd: number;
211
+ readonly tokens: CostTokenTotals;
212
+ }
213
+
214
+ /**
215
+ * Token sums. `prompt` and `completion` are the totals; `cacheRead` /
216
+ * `cacheWrite` are parts of `prompt`, `reasoning` of `completion`
217
+ * (`0` where providers didn't report them).
218
+ */
219
+ export interface CostTokenTotals {
220
+ readonly prompt: number;
221
+ readonly completion: number;
222
+ readonly cacheRead: number;
223
+ readonly cacheWrite: number;
224
+ readonly reasoning: number;
177
225
  }
178
226
 
179
227
  export interface CostAggregateResult {
180
228
  readonly groups: readonly CostAggregateGroup[];
181
229
  readonly totalUsd: number;
182
230
  readonly totalRecords: number;
231
+ readonly tokens: CostTokenTotals;
183
232
  readonly timeRange: {
184
233
  readonly from: Timestamp;
185
234
  readonly to: Timestamp;
@@ -210,4 +259,46 @@ export interface CostRecord {
210
259
  readonly occurredAt: Timestamp;
211
260
  readonly metrics?: Readonly<Record<string, unknown>>;
212
261
  readonly attributes?: Readonly<Record<string, unknown>>;
262
+ // A model call (`category` `llm.inference`):
263
+ /** The call's id; its provenance node carries it too. */
264
+ readonly callId?: string;
265
+ readonly projectId?: string;
266
+ /** The root of the record's run tree. */
267
+ readonly rootRunId?: string;
268
+ readonly parentRunId?: string;
269
+ readonly agentVersion?: string;
270
+ /** The flow of the run tree's root. */
271
+ readonly flowId?: string;
272
+ /** The step that made the call, and the turn's step number. */
273
+ readonly nodeId?: string;
274
+ readonly step?: number;
275
+ /** What the call was for, beyond the turn's own model step (`guardrail-judge:<id>`). */
276
+ readonly purpose?: string;
277
+ /** The model actually called. */
278
+ readonly model?: string;
279
+ /** The exact model version the vendor reported. */
280
+ readonly servedModel?: string;
281
+ /** The router picked a fallback provider. */
282
+ readonly fallback?: boolean;
283
+ /** `ok`: the provider answered. `failed`: the call threw. */
284
+ readonly status?: 'ok' | 'failed';
285
+ readonly usage?: {
286
+ readonly promptTokens: number;
287
+ readonly completionTokens: number;
288
+ readonly cacheReadTokens?: number;
289
+ readonly cacheWriteTokens?: number;
290
+ readonly reasoningTokens?: number;
291
+ };
292
+ readonly durationMs?: number;
293
+ readonly finishReason?: string;
294
+ readonly providerRequestId?: string;
295
+ /** HTTP attempts, the client's retries included. */
296
+ readonly attempts?: number;
297
+ readonly error?: { readonly message: string };
298
+ /** The vendor's own usage object (only when asked for: `include=rawUsage`). */
299
+ readonly rawUsage?: {
300
+ readonly provider: string;
301
+ readonly model: string;
302
+ readonly usage: Readonly<Record<string, unknown>>;
303
+ };
213
304
  }
@@ -12,6 +12,7 @@ import type { ReviewerRole } from '@kindgi/authz';
12
12
  import type {
13
13
  ApprovalId,
14
14
  Cursor,
15
+ ListScope,
15
16
  ProjectId,
16
17
  ProvenanceId,
17
18
  Result,
@@ -57,6 +58,12 @@ export interface Approval {
57
58
  readonly updatedAt: Timestamp;
58
59
  readonly decidedAt?: Timestamp;
59
60
  readonly expiresAt?: Timestamp;
61
+ /**
62
+ * The reviewer's decision, once one is recorded. Absent while the
63
+ * approval is open, and when it ended without one (it expired, or a
64
+ * timeout escalated it).
65
+ */
66
+ readonly decision?: ReviewDecisionRecord;
60
67
  }
61
68
 
62
69
  export interface ReviewDecision {
@@ -76,6 +83,8 @@ export interface ReviewDecision {
76
83
  export interface ListApprovalsBindingInput {
77
84
  readonly tenantId: TenantId;
78
85
  readonly limit: number;
86
+ /** Only one project's approvals, or every project's in an org. Absent: the tenant's. */
87
+ readonly scope?: ListScope;
79
88
  readonly status?: ApprovalStatus;
80
89
  readonly requiredRole?: ReviewerRole;
81
90
  readonly since?: Timestamp;
@@ -110,12 +119,18 @@ export type SubmitReviewBindingResult =
110
119
  };
111
120
 
112
121
  /**
113
- * A single review-decision row hydrated for the audit-bundle route.
114
- * `null` = the approval terminated without a recorded decision (e.g.
115
- * status `expired`).
122
+ * An approval's recorded decision: on the approval it decided
123
+ * (`Approval.decision`), and for the audit-bundle route. `null` from
124
+ * `loadReviewDecision` = the approval terminated without a recorded
125
+ * decision (e.g. status `expired`).
116
126
  */
117
127
  export interface ReviewDecisionRecord {
118
128
  readonly decision: ReviewDecisionKind;
129
+ /**
130
+ * Who decided, as an actor: `user:<userId>`, the reviewer's user.
131
+ * Absent from a binding that doesn't record it (the Kindgi runtime does).
132
+ */
133
+ readonly decidedBy?: string;
119
134
  readonly reviewerId: ReviewerId;
120
135
  readonly reviewerRoleAtDecision: ReviewerRole;
121
136
  readonly decidedAt: Timestamp;
@@ -163,7 +178,8 @@ export interface HitlBinding {
163
178
  /**
164
179
  * Hydrate the recorded decision row for an approval — used by the
165
180
  * audit-bundle route. Returns `null` when the approval terminated
166
- * without a recorded decision (status `expired`).
181
+ * without a recorded decision (status `expired`). `getApproval` and
182
+ * `listApprovals` return the same decision on each approval.
167
183
  */
168
184
  loadReviewDecision(
169
185
  tenantId: TenantId,
package/src/index.ts CHANGED
@@ -391,6 +391,7 @@ export type {
391
391
  CostRecord,
392
392
  CostRecordFilter,
393
393
  CostRecordPage,
394
+ CostTokenTotals,
394
395
  } from './cost-binding.js';
395
396
  export type {
396
397
  ToolGetInput,
@@ -550,6 +551,8 @@ export type {
550
551
  ListProvenanceRecordsInput,
551
552
  ListProvenanceRecordsResult,
552
553
  ProvenanceBinding,
554
+ CallUsage,
555
+ CallUsageByCallId,
553
556
  ProvenanceBindingError,
554
557
  ProvenanceListCursor,
555
558
  ProvenanceRecordSummary,
@@ -37,8 +37,9 @@ export interface TokenResolution {
37
37
  * middleware surfaces it as `c.get('reviewerRole')` and approvals
38
38
  * routes gate visibility / decisions on the `standard < senior < admin`
39
39
  * hierarchy. Absent for tokens that were never provisioned with a
40
- * reviewer role — such tokens can still hit `/v1/runs` etc., but any
41
- * approvals route returns `403 permission-denied`.
40
+ * reviewer role: the approvals routes then take the role the reviewer
41
+ * roster gives the token's user (`ReviewerBinding.resolveReviewerRole`),
42
+ * and return `403 permission-denied` when it gives none.
42
43
  */
43
44
  readonly reviewerRole?: ReviewerRole;
44
45
  /**
@@ -63,7 +63,11 @@ const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
63
63
  * byte-identical (status, content-type, body).
64
64
  * - Cached hit with different body hash → 409
65
65
  * `idempotency-key-body-mismatch`.
66
- * - Cache miss → run the handler, then store the response.
66
+ * - Cache miss → run the handler, then store the response when the
67
+ * request took effect (a status below 400). A refusal (4xx) changed
68
+ * nothing, and a failure (5xx) may be transient: neither is stored, so
69
+ * a retry with the same key after fixing the cause runs again instead
70
+ * of replaying the old error.
67
71
  *
68
72
  * The middleware sits AFTER auth so `tenantId` is available; the
69
73
  * cache key is namespaced by tenant so callers can't collide across
@@ -122,6 +126,8 @@ export function idempotencyMiddleware(
122
126
  await next();
123
127
 
124
128
  const res = c.res;
129
+ // Only what took effect is replayed; see the doc above.
130
+ if (res.status >= 400) return;
125
131
  const savedBody = await res.clone().text();
126
132
  const contentType = res.headers.get('Content-Type') ?? 'application/octet-stream';
127
133
  await store.set(cacheKey, {