@kindgi/api 0.1.2 → 0.1.4-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) hide show
  1. package/dist/agent-binding.d.ts +18 -4
  2. package/dist/agent-binding.d.ts.map +1 -1
  3. package/dist/agent-pins.d.ts +48 -0
  4. package/dist/agent-pins.d.ts.map +1 -0
  5. package/dist/agent-pins.js +102 -0
  6. package/dist/agent-pins.js.map +1 -0
  7. package/dist/app.d.ts +22 -0
  8. package/dist/app.d.ts.map +1 -1
  9. package/dist/app.js +22 -3
  10. package/dist/app.js.map +1 -1
  11. package/dist/block-binding.d.ts +132 -0
  12. package/dist/block-binding.d.ts.map +1 -0
  13. package/dist/block-binding.js +4 -0
  14. package/dist/block-binding.js.map +1 -0
  15. package/dist/block-pins.d.ts +22 -0
  16. package/dist/block-pins.d.ts.map +1 -0
  17. package/dist/block-pins.js +112 -0
  18. package/dist/block-pins.js.map +1 -0
  19. package/dist/cost-binding.d.ts +100 -5
  20. package/dist/cost-binding.d.ts.map +1 -1
  21. package/dist/cost-binding.js +10 -0
  22. package/dist/cost-binding.js.map +1 -1
  23. package/dist/deploy-versions.d.ts +58 -0
  24. package/dist/deploy-versions.d.ts.map +1 -0
  25. package/dist/deploy-versions.js +91 -0
  26. package/dist/deploy-versions.js.map +1 -0
  27. package/dist/deployment-binding.d.ts +24 -3
  28. package/dist/deployment-binding.d.ts.map +1 -1
  29. package/dist/derive-agent-version.d.ts +69 -0
  30. package/dist/derive-agent-version.d.ts.map +1 -0
  31. package/dist/derive-agent-version.js +139 -0
  32. package/dist/derive-agent-version.js.map +1 -0
  33. package/dist/errors.d.ts.map +1 -1
  34. package/dist/errors.js +17 -0
  35. package/dist/errors.js.map +1 -1
  36. package/dist/eval-case-binding.d.ts +65 -0
  37. package/dist/eval-case-binding.d.ts.map +1 -0
  38. package/dist/eval-case-binding.js +4 -0
  39. package/dist/eval-case-binding.js.map +1 -0
  40. package/dist/eval-run-binding.d.ts +30 -0
  41. package/dist/eval-run-binding.d.ts.map +1 -1
  42. package/dist/eval-run-dispatcher.d.ts +38 -3
  43. package/dist/eval-run-dispatcher.d.ts.map +1 -1
  44. package/dist/eval-run-dispatcher.js +21 -15
  45. package/dist/eval-run-dispatcher.js.map +1 -1
  46. package/dist/eval-suite-binding.d.ts +1 -1
  47. package/dist/eval-suite-binding.d.ts.map +1 -1
  48. package/dist/eval-suite-binding.js +2 -0
  49. package/dist/eval-suite-binding.js.map +1 -1
  50. package/dist/flow-binding.d.ts +10 -4
  51. package/dist/flow-binding.d.ts.map +1 -1
  52. package/dist/flow-pins.d.ts +36 -0
  53. package/dist/flow-pins.d.ts.map +1 -0
  54. package/dist/flow-pins.js +81 -0
  55. package/dist/flow-pins.js.map +1 -0
  56. package/dist/hitl-binding.d.ts +20 -5
  57. package/dist/hitl-binding.d.ts.map +1 -1
  58. package/dist/index.d.ts +19 -8
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +8 -2
  61. package/dist/index.js.map +1 -1
  62. package/dist/judged-dispatcher.d.ts +134 -0
  63. package/dist/judged-dispatcher.d.ts.map +1 -0
  64. package/dist/judged-dispatcher.js +297 -0
  65. package/dist/judged-dispatcher.js.map +1 -0
  66. package/dist/judged-items.d.ts +86 -0
  67. package/dist/judged-items.d.ts.map +1 -0
  68. package/dist/judged-items.js +184 -0
  69. package/dist/judged-items.js.map +1 -0
  70. package/dist/judgment-binding.d.ts +316 -0
  71. package/dist/judgment-binding.d.ts.map +1 -0
  72. package/dist/judgment-binding.js +19 -0
  73. package/dist/judgment-binding.js.map +1 -0
  74. package/dist/middleware/auth.d.ts +3 -2
  75. package/dist/middleware/auth.d.ts.map +1 -1
  76. package/dist/middleware/auth.js.map +1 -1
  77. package/dist/middleware/idempotency.d.ts +5 -1
  78. package/dist/middleware/idempotency.d.ts.map +1 -1
  79. package/dist/middleware/idempotency.js +8 -1
  80. package/dist/middleware/idempotency.js.map +1 -1
  81. package/dist/openapi/generate.d.ts.map +1 -1
  82. package/dist/openapi/generate.js +4 -1
  83. package/dist/openapi/generate.js.map +1 -1
  84. package/dist/openapi/operations.d.ts.map +1 -1
  85. package/dist/openapi/operations.js +543 -24
  86. package/dist/openapi/operations.js.map +1 -1
  87. package/dist/openapi/schemas.d.ts +58 -0
  88. package/dist/openapi/schemas.d.ts.map +1 -1
  89. package/dist/openapi/schemas.js +1058 -27
  90. package/dist/openapi/schemas.js.map +1 -1
  91. package/dist/provenance-binding.d.ts +27 -1
  92. package/dist/provenance-binding.d.ts.map +1 -1
  93. package/dist/provenance-binding.js.map +1 -1
  94. package/dist/provider-binding.d.ts +12 -7
  95. package/dist/provider-binding.d.ts.map +1 -1
  96. package/dist/reviewer-binding.d.ts +10 -2
  97. package/dist/reviewer-binding.d.ts.map +1 -1
  98. package/dist/reviewer-role.d.ts +13 -0
  99. package/dist/reviewer-role.d.ts.map +1 -0
  100. package/dist/reviewer-role.js +27 -0
  101. package/dist/reviewer-role.js.map +1 -0
  102. package/dist/routes/agents.d.ts +9 -1
  103. package/dist/routes/agents.d.ts.map +1 -1
  104. package/dist/routes/agents.js +175 -11
  105. package/dist/routes/agents.js.map +1 -1
  106. package/dist/routes/approvals.d.ts +5 -4
  107. package/dist/routes/approvals.d.ts.map +1 -1
  108. package/dist/routes/approvals.js +52 -16
  109. package/dist/routes/approvals.js.map +1 -1
  110. package/dist/routes/blocks.d.ts +19 -0
  111. package/dist/routes/blocks.d.ts.map +1 -0
  112. package/dist/routes/blocks.js +281 -0
  113. package/dist/routes/blocks.js.map +1 -0
  114. package/dist/routes/conversations.d.ts +7 -1
  115. package/dist/routes/conversations.d.ts.map +1 -1
  116. package/dist/routes/conversations.js +41 -1
  117. package/dist/routes/conversations.js.map +1 -1
  118. package/dist/routes/cost.d.ts.map +1 -1
  119. package/dist/routes/cost.js +142 -11
  120. package/dist/routes/cost.js.map +1 -1
  121. package/dist/routes/deployments.d.ts +3 -0
  122. package/dist/routes/deployments.d.ts.map +1 -1
  123. package/dist/routes/deployments.js +208 -55
  124. package/dist/routes/deployments.js.map +1 -1
  125. package/dist/routes/eval-comparison.d.ts +14 -0
  126. package/dist/routes/eval-comparison.d.ts.map +1 -0
  127. package/dist/routes/eval-comparison.js +87 -0
  128. package/dist/routes/eval-comparison.js.map +1 -0
  129. package/dist/routes/eval-runs.d.ts.map +1 -1
  130. package/dist/routes/eval-runs.js +8 -0
  131. package/dist/routes/eval-runs.js.map +1 -1
  132. package/dist/routes/flows.d.ts +13 -1
  133. package/dist/routes/flows.d.ts.map +1 -1
  134. package/dist/routes/flows.js +44 -3
  135. package/dist/routes/flows.js.map +1 -1
  136. package/dist/routes/hierarchy-errors.d.ts +35 -0
  137. package/dist/routes/hierarchy-errors.d.ts.map +1 -0
  138. package/dist/routes/hierarchy-errors.js +39 -0
  139. package/dist/routes/hierarchy-errors.js.map +1 -0
  140. package/dist/routes/identity.d.ts +7 -0
  141. package/dist/routes/identity.d.ts.map +1 -1
  142. package/dist/routes/identity.js +3 -2
  143. package/dist/routes/identity.js.map +1 -1
  144. package/dist/routes/judged-suites.d.ts +20 -0
  145. package/dist/routes/judged-suites.d.ts.map +1 -0
  146. package/dist/routes/judged-suites.js +272 -0
  147. package/dist/routes/judged-suites.js.map +1 -0
  148. package/dist/routes/judgment-context.d.ts +22 -0
  149. package/dist/routes/judgment-context.d.ts.map +1 -0
  150. package/dist/routes/judgment-context.js +88 -0
  151. package/dist/routes/judgment-context.js.map +1 -0
  152. package/dist/routes/judgment-flow-context.d.ts +32 -0
  153. package/dist/routes/judgment-flow-context.d.ts.map +1 -0
  154. package/dist/routes/judgment-flow-context.js +195 -0
  155. package/dist/routes/judgment-flow-context.js.map +1 -0
  156. package/dist/routes/judgments.d.ts +41 -0
  157. package/dist/routes/judgments.d.ts.map +1 -0
  158. package/dist/routes/judgments.js +566 -0
  159. package/dist/routes/judgments.js.map +1 -0
  160. package/dist/routes/orgs.d.ts +5 -2
  161. package/dist/routes/orgs.d.ts.map +1 -1
  162. package/dist/routes/orgs.js +38 -22
  163. package/dist/routes/orgs.js.map +1 -1
  164. package/dist/routes/policies.d.ts.map +1 -1
  165. package/dist/routes/policies.js +12 -1
  166. package/dist/routes/policies.js.map +1 -1
  167. package/dist/routes/projects.d.ts +10 -2
  168. package/dist/routes/projects.d.ts.map +1 -1
  169. package/dist/routes/projects.js +87 -79
  170. package/dist/routes/projects.js.map +1 -1
  171. package/dist/routes/provenance.d.ts.map +1 -1
  172. package/dist/routes/provenance.js +32 -1
  173. package/dist/routes/provenance.js.map +1 -1
  174. package/dist/routes/providers.d.ts.map +1 -1
  175. package/dist/routes/providers.js +6 -1
  176. package/dist/routes/providers.js.map +1 -1
  177. package/dist/routes/runs.d.ts +0 -7
  178. package/dist/routes/runs.d.ts.map +1 -1
  179. package/dist/routes/runs.js +65 -20
  180. package/dist/routes/runs.js.map +1 -1
  181. package/dist/routes/scope-params.d.ts +16 -1
  182. package/dist/routes/scope-params.d.ts.map +1 -1
  183. package/dist/routes/scope-params.js +28 -0
  184. package/dist/routes/scope-params.js.map +1 -1
  185. package/dist/routes/teams.d.ts +6 -2
  186. package/dist/routes/teams.d.ts.map +1 -1
  187. package/dist/routes/teams.js +77 -73
  188. package/dist/routes/teams.js.map +1 -1
  189. package/dist/types.d.ts +4 -3
  190. package/dist/types.d.ts.map +1 -1
  191. package/dist/webhook-endpoint-binding.d.ts +11 -0
  192. package/dist/webhook-endpoint-binding.d.ts.map +1 -1
  193. package/dist/webhook-endpoint-binding.js.map +1 -1
  194. package/openapi.json +13316 -9299
  195. package/package.json +21 -21
  196. package/src/agent-binding.ts +19 -4
  197. package/src/agent-pins.ts +147 -0
  198. package/src/app.ts +76 -3
  199. package/src/block-binding.ts +137 -0
  200. package/src/block-pins.ts +148 -0
  201. package/src/cost-binding.ts +116 -5
  202. package/src/deploy-versions.ts +157 -0
  203. package/src/deployment-binding.ts +27 -3
  204. package/src/derive-agent-version.ts +206 -0
  205. package/src/errors.ts +17 -0
  206. package/src/eval-case-binding.ts +71 -0
  207. package/src/eval-run-binding.ts +33 -0
  208. package/src/eval-run-dispatcher.ts +57 -16
  209. package/src/eval-suite-binding.ts +2 -0
  210. package/src/flow-binding.ts +11 -4
  211. package/src/flow-pins.ts +113 -0
  212. package/src/hitl-binding.ts +20 -4
  213. package/src/index.ts +88 -2
  214. package/src/judged-dispatcher.ts +507 -0
  215. package/src/judged-items.ts +263 -0
  216. package/src/judgment-binding.ts +349 -0
  217. package/src/middleware/auth.ts +3 -2
  218. package/src/middleware/idempotency.ts +7 -1
  219. package/src/openapi/generate.ts +7 -1
  220. package/src/openapi/operations.ts +615 -24
  221. package/src/openapi/schemas.ts +1157 -22
  222. package/src/provenance-binding.ts +42 -1
  223. package/src/provider-binding.ts +12 -7
  224. package/src/reviewer-binding.ts +10 -2
  225. package/src/reviewer-role.ts +35 -0
  226. package/src/routes/agents.ts +243 -19
  227. package/src/routes/approvals.ts +70 -19
  228. package/src/routes/blocks.ts +362 -0
  229. package/src/routes/conversations.ts +57 -1
  230. package/src/routes/cost.ts +159 -21
  231. package/src/routes/deployments.ts +266 -56
  232. package/src/routes/eval-comparison.ts +101 -0
  233. package/src/routes/eval-runs.ts +11 -0
  234. package/src/routes/flows.ts +63 -5
  235. package/src/routes/hierarchy-errors.ts +51 -0
  236. package/src/routes/identity.ts +10 -2
  237. package/src/routes/judged-suites.ts +363 -0
  238. package/src/routes/judgment-context.ts +128 -0
  239. package/src/routes/judgment-flow-context.ts +245 -0
  240. package/src/routes/judgments.ts +743 -0
  241. package/src/routes/orgs.ts +44 -27
  242. package/src/routes/policies.ts +19 -0
  243. package/src/routes/projects.ts +106 -95
  244. package/src/routes/provenance.ts +48 -1
  245. package/src/routes/providers.ts +5 -0
  246. package/src/routes/runs.ts +79 -22
  247. package/src/routes/scope-params.ts +35 -1
  248. package/src/routes/teams.ts +96 -90
  249. package/src/types.ts +4 -3
  250. package/src/webhook-endpoint-binding.ts +11 -0
@@ -0,0 +1,148 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+
4
+ import { type Agent, MODEL_SETTINGS_SCHEMA, settingsSchemaIssues } from '@kindgi/agents';
5
+ import { pickVersion } from '@kindgi/tools';
6
+ import type { Cursor, TenantId } from '@kindgi/types';
7
+
8
+ import type { UnpinnableRef } from './agent-pins.js';
9
+ import type { BlockRegistryBinding } from './block-binding.js';
10
+
11
+ /** The data-block versions an agent version pins, and the references that matched none. */
12
+ export interface BlockPins {
13
+ readonly prompts: Readonly<Record<string, string>>;
14
+ readonly settings: Readonly<Record<string, string>>;
15
+ readonly issues: readonly UnpinnableRef[];
16
+ }
17
+
18
+ interface Ref {
19
+ readonly path: string;
20
+ readonly id: string;
21
+ readonly range: string;
22
+ readonly role: 'prompt' | 'settings' | 'model-settings';
23
+ }
24
+
25
+ /** Page size for reading a block's versions. */
26
+ const VERSIONS_PAGE = 200;
27
+
28
+ /**
29
+ * Pin the data blocks an agent references (its prompt block, settings
30
+ * blocks and model-settings block) when a version of it is published:
31
+ * each range resolves by `pickVersion` over the block's active versions,
32
+ * as a turn of an unpinned agent would. A reference that matches no
33
+ * published version, names a block of the other kind, or names model
34
+ * settings that aren't (`MODEL_SETTINGS_SCHEMA`) is an issue: the
35
+ * caller refuses the publish. So is any reference on a runtime with no
36
+ * block registry.
37
+ */
38
+ export async function resolveBlockPins(
39
+ blocks: BlockRegistryBinding | undefined,
40
+ tenantId: TenantId,
41
+ agent: Agent,
42
+ ): Promise<BlockPins> {
43
+ const prompts: Record<string, string> = {};
44
+ const settings: Record<string, string> = {};
45
+ const issues: UnpinnableRef[] = [];
46
+ for (const ref of blockRefs(agent)) {
47
+ if (blocks === undefined) {
48
+ issues.push({
49
+ path: ref.path,
50
+ message: `${describe(ref)}: this runtime serves no data blocks`,
51
+ });
52
+ continue;
53
+ }
54
+ const available = await activeBlockVersions(blocks, tenantId, ref.id);
55
+ const pick = pickVersion(available, ref.range);
56
+ if (pick.kind !== 'ok') {
57
+ issues.push({
58
+ path: ref.path,
59
+ message:
60
+ available.length === 0
61
+ ? `${describe(ref)} has no published version; publish the block first`
62
+ : `${describe(ref)} has no published version in "${ref.range}" (published: ${available.join(', ')})`,
63
+ });
64
+ continue;
65
+ }
66
+ const block = await blocks.getVersion({ tenantId, blockId: ref.id, version: pick.version });
67
+ const problem =
68
+ block === null
69
+ ? `${describe(ref)} version ${pick.version} can't be read`
70
+ : misfit(ref, block);
71
+ if (problem !== undefined) {
72
+ issues.push({ path: ref.path, message: problem });
73
+ continue;
74
+ }
75
+ (ref.role === 'prompt' ? prompts : settings)[ref.id] = pick.version;
76
+ }
77
+ return { prompts, settings, issues };
78
+ }
79
+
80
+ function blockRefs(agent: Agent): Ref[] {
81
+ const refs: Ref[] = [];
82
+ if (typeof agent.instructions === 'object') {
83
+ refs.push({
84
+ path: '/instructions/version',
85
+ id: agent.instructions.prompt,
86
+ range: agent.instructions.version,
87
+ role: 'prompt',
88
+ });
89
+ }
90
+ for (const [i, s] of (agent.settings ?? []).entries()) {
91
+ refs.push({ path: `/settings/${i}/version`, id: s.id, range: s.version, role: 'settings' });
92
+ }
93
+ if (agent.modelSettings !== undefined) {
94
+ refs.push({
95
+ path: '/modelSettings/version',
96
+ id: agent.modelSettings.id,
97
+ range: agent.modelSettings.version,
98
+ role: 'model-settings',
99
+ });
100
+ }
101
+ return refs;
102
+ }
103
+
104
+ /** Why a block version can't serve its reference; undefined when it can. */
105
+ function misfit(
106
+ ref: Ref,
107
+ block: { readonly kind: string; readonly version: string; readonly content: unknown },
108
+ ): string | undefined {
109
+ const wanted = ref.role === 'prompt' ? 'prompt' : 'settings';
110
+ if (block.kind !== wanted) return `${describe(ref)} is a ${block.kind} block`;
111
+ if (ref.role !== 'model-settings') return undefined;
112
+ const values = (block.content as { readonly values?: unknown }).values;
113
+ const issues = settingsSchemaIssues(values, MODEL_SETTINGS_SCHEMA);
114
+ return issues.length === 0
115
+ ? undefined
116
+ : `${describe(ref)} version ${block.version} isn't model settings: ${issues.map((i) => `${i.path} ${i.message}`).join('; ')}`;
117
+ }
118
+
119
+ function describe(ref: Ref): string {
120
+ const what =
121
+ ref.role === 'prompt'
122
+ ? 'prompt'
123
+ : ref.role === 'model-settings'
124
+ ? 'model-settings'
125
+ : 'settings';
126
+ return `${what} block "${ref.id}"`;
127
+ }
128
+
129
+ /** Every active version of a block, newest published first. */
130
+ async function activeBlockVersions(
131
+ blocks: BlockRegistryBinding,
132
+ tenantId: TenantId,
133
+ blockId: string,
134
+ ): Promise<readonly string[]> {
135
+ const versions: string[] = [];
136
+ let cursor: Cursor | undefined;
137
+ do {
138
+ const page = await blocks.listVersions({
139
+ tenantId,
140
+ blockId,
141
+ limit: VERSIONS_PAGE,
142
+ ...(cursor !== undefined && { cursor }),
143
+ });
144
+ versions.push(...page.data.map((b) => b.version));
145
+ cursor = page.nextCursor;
146
+ } while (cursor !== undefined);
147
+ return versions;
148
+ }
@@ -42,13 +42,19 @@ 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. With `limit`, a binding may return only the
48
+ * `limit` most expensive groups (`totalUsd` descending, ties by key)
49
+ * and the count before the cap in `totalGroups`; one that returns
50
+ * every group is capped by the route.
48
51
  */
49
52
  aggregate(input: CostAggregateInput): Promise<CostAggregateResult>;
50
53
  }
51
54
 
55
+ // A binding throws when its store fails: the route answers 500. It never
56
+ // answers a failed read with an empty page or zero sums.
57
+
52
58
  // -------- listRecords --------
53
59
 
54
60
  export interface CostListRecordsInput {
@@ -80,6 +86,8 @@ export interface CostListRecordsInput {
80
86
  * uniformity: scope-aware bindings share one filter shape.
81
87
  */
82
88
  readonly inherit?: boolean;
89
+ /** Add each record's `rawUsage`: the vendor's own usage object. */
90
+ readonly includeRawUsage?: boolean;
83
91
  }
84
92
 
85
93
  export interface CostRecordFilter {
@@ -94,7 +102,20 @@ export interface CostRecordFilter {
94
102
  */
95
103
  readonly category?: string;
96
104
  readonly providerId?: string;
105
+ /** The model actually called. */
106
+ readonly model?: string;
107
+ /** The exact model version the vendor reported. */
108
+ readonly servedModel?: string;
109
+ /** Every record of the run tree whose root is this run. */
110
+ readonly rootRunId?: string;
111
+ /**
112
+ * With `runId`: that run's records and those of every run it started,
113
+ * at any depth.
114
+ */
115
+ readonly includeDescendants?: boolean;
116
+ /** Inclusive. */
97
117
  readonly from?: Date;
118
+ /** Exclusive. */
98
119
  readonly to?: Date;
99
120
  }
100
121
 
@@ -108,6 +129,8 @@ export interface CostRecordPage {
108
129
  export interface CostGetRecordInput {
109
130
  readonly tenantId: TenantId;
110
131
  readonly recordId: string;
132
+ /** Add the record's `rawUsage`: the vendor's own usage object. */
133
+ readonly includeRawUsage?: boolean;
111
134
  }
112
135
 
113
136
  // -------- aggregate --------
@@ -116,7 +139,9 @@ export interface CostGetRecordInput {
116
139
  * Group dimensions the binding may aggregate over. `day` / `month` are
117
140
  * time bucketing on `occurredAt` (UTC calendar day / month). Every call
118
141
  * is tenant-scoped, so `tenant` always resolves to the caller's tenant
119
- * id.
142
+ * id. `model` is the model actually called; `servedModel` the exact
143
+ * version the vendor reported. `orgId` is the org of the record's
144
+ * project (`null` for a project in no org).
120
145
  */
121
146
  export type CostGroupDimension =
122
147
  | 'agentId'
@@ -126,7 +151,13 @@ export type CostGroupDimension =
126
151
  | 'day'
127
152
  | 'month'
128
153
  | 'tenant'
129
- | 'conversationId';
154
+ | 'conversationId'
155
+ | 'model'
156
+ | 'servedModel'
157
+ | 'projectId'
158
+ | 'orgId'
159
+ | 'rootRunId'
160
+ | 'flowId';
130
161
 
131
162
  export const COST_GROUP_DIMENSIONS: readonly CostGroupDimension[] = [
132
163
  'agentId',
@@ -137,6 +168,12 @@ export const COST_GROUP_DIMENSIONS: readonly CostGroupDimension[] = [
137
168
  'month',
138
169
  'tenant',
139
170
  'conversationId',
171
+ 'model',
172
+ 'servedModel',
173
+ 'projectId',
174
+ 'orgId',
175
+ 'rootRunId',
176
+ 'flowId',
140
177
  ];
141
178
 
142
179
  export interface CostAggregateInput {
@@ -161,8 +198,20 @@ export interface CostAggregateInput {
161
198
  * shape uniformity.
162
199
  */
163
200
  readonly inherit?: boolean;
201
+ /**
202
+ * The most groups the caller wants (`?limit=`, 1..`COST_AGGREGATE_MAX_LIMIT`,
203
+ * default `COST_AGGREGATE_DEFAULT_LIMIT`): the most expensive ones. A
204
+ * binding may cap in its query; the window's totals stay over every
205
+ * record either way.
206
+ */
207
+ readonly limit?: number;
164
208
  }
165
209
 
210
+ /** `GET /v1/cost/aggregate`'s `?limit=` when absent. */
211
+ export const COST_AGGREGATE_DEFAULT_LIMIT = 1000;
212
+ /** The largest `?limit=` the aggregate takes. */
213
+ export const COST_AGGREGATE_MAX_LIMIT = 10_000;
214
+
166
215
  /**
167
216
  * One row of the aggregate result. `key` maps every requested `groupBy`
168
217
  * dimension to its value for this group — `null` when the underlying
@@ -174,12 +223,32 @@ export interface CostAggregateGroup {
174
223
  readonly key: Readonly<Record<string, string | null>>;
175
224
  readonly count: number;
176
225
  readonly totalUsd: number;
226
+ readonly tokens: CostTokenTotals;
227
+ }
228
+
229
+ /**
230
+ * Token sums. `prompt` and `completion` are the totals; `cacheRead` /
231
+ * `cacheWrite` are parts of `prompt`, `reasoning` of `completion`
232
+ * (`0` where providers didn't report them).
233
+ */
234
+ export interface CostTokenTotals {
235
+ readonly prompt: number;
236
+ readonly completion: number;
237
+ readonly cacheRead: number;
238
+ readonly cacheWrite: number;
239
+ readonly reasoning: number;
177
240
  }
178
241
 
179
242
  export interface CostAggregateResult {
180
243
  readonly groups: readonly CostAggregateGroup[];
244
+ /**
245
+ * How many groups there were before a cap: set by a binding that caps
246
+ * (`CostAggregateInput.limit`). Absent: `groups` is every group.
247
+ */
248
+ readonly totalGroups?: number;
181
249
  readonly totalUsd: number;
182
250
  readonly totalRecords: number;
251
+ readonly tokens: CostTokenTotals;
183
252
  readonly timeRange: {
184
253
  readonly from: Timestamp;
185
254
  readonly to: Timestamp;
@@ -210,4 +279,46 @@ export interface CostRecord {
210
279
  readonly occurredAt: Timestamp;
211
280
  readonly metrics?: Readonly<Record<string, unknown>>;
212
281
  readonly attributes?: Readonly<Record<string, unknown>>;
282
+ // A model call (`category` `llm.inference`):
283
+ /** The call's id; its provenance node carries it too. */
284
+ readonly callId?: string;
285
+ readonly projectId?: string;
286
+ /** The root of the record's run tree. */
287
+ readonly rootRunId?: string;
288
+ readonly parentRunId?: string;
289
+ readonly agentVersion?: string;
290
+ /** The flow of the run tree's root. */
291
+ readonly flowId?: string;
292
+ /** The step that made the call, and the turn's step number. */
293
+ readonly nodeId?: string;
294
+ readonly step?: number;
295
+ /** What the call was for, beyond the turn's own model step (`guardrail-judge:<id>`). */
296
+ readonly purpose?: string;
297
+ /** The model actually called. */
298
+ readonly model?: string;
299
+ /** The exact model version the vendor reported. */
300
+ readonly servedModel?: string;
301
+ /** The router picked a fallback provider. */
302
+ readonly fallback?: boolean;
303
+ /** `ok`: the provider answered. `failed`: the call threw. */
304
+ readonly status?: 'ok' | 'failed';
305
+ readonly usage?: {
306
+ readonly promptTokens: number;
307
+ readonly completionTokens: number;
308
+ readonly cacheReadTokens?: number;
309
+ readonly cacheWriteTokens?: number;
310
+ readonly reasoningTokens?: number;
311
+ };
312
+ readonly durationMs?: number;
313
+ readonly finishReason?: string;
314
+ readonly providerRequestId?: string;
315
+ /** HTTP attempts, the client's retries included. */
316
+ readonly attempts?: number;
317
+ readonly error?: { readonly message: string };
318
+ /** The vendor's own usage object (only when asked for: `include=rawUsage`). */
319
+ readonly rawUsage?: {
320
+ readonly provider: string;
321
+ readonly model: string;
322
+ readonly usage: Readonly<Record<string, unknown>>;
323
+ };
213
324
  }
@@ -0,0 +1,157 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+
4
+ import { type PinChange, type PinSet, pinChanges } from '@kindgi/agents';
5
+ import { canonicalize } from '@kindgi/schema';
6
+ import { nextVersion } from '@kindgi/tools';
7
+ import type { VersionDerivation, VersionDerivationReason } from '@kindgi/types';
8
+
9
+ /** A version a deploy registers with pins: an agent's or a flow's. */
10
+ export interface PinnedDefinition {
11
+ readonly version: string;
12
+ readonly pins?: PinSet;
13
+ readonly pinsDigest?: string;
14
+ readonly derivedFrom?: VersionDerivation;
15
+ }
16
+
17
+ /**
18
+ * How a deploy registered one of its agents or flows, given its pins.
19
+ * A deploy never keeps a version's old pins and never refuses a
20
+ * routine deploy:
21
+ *
22
+ * - `registered`: the definition's version was free, and is now
23
+ * registered with these pins;
24
+ * - `unchanged`: it's registered with the same definition and pins (or
25
+ * its project is gone, which a deploy has always left as it is);
26
+ * - `reused`: an earlier deploy already registered this definition and
27
+ * these pins under another number (`version`), so a redeploy changes
28
+ * nothing;
29
+ * - `renumbered`: the definition's version is registered with other
30
+ * pins or other content, and versions never change, so this deploy
31
+ * registered the next free version in its line (`nextVersion`).
32
+ */
33
+ export type DeployedVersionOutcome =
34
+ | { readonly kind: 'registered' | 'unchanged'; readonly version: string }
35
+ | {
36
+ readonly kind: 'reused' | 'renumbered';
37
+ readonly version: string;
38
+ readonly reason: VersionDerivationReason;
39
+ readonly pinChanges?: readonly PinChange[];
40
+ };
41
+
42
+ /** What registering one version did: stored it, found the number taken, or left it (no project). */
43
+ export type PublishOutcome = 'ok' | 'taken' | 'skipped';
44
+
45
+ export interface DeployVersionInput<T extends PinnedDefinition> {
46
+ /** Names the definition in an error, e.g. `agent "acme.matcher"`. */
47
+ readonly label: string;
48
+ /** The definition as the pack has it, with no pins. */
49
+ readonly definition: T;
50
+ readonly pins: PinSet;
51
+ readonly pinsDigest: string;
52
+ /** Every active version registered under the definition's id. */
53
+ readonly existing: readonly T[];
54
+ /** Register one version. */
55
+ readonly publish: (version: T) => Promise<PublishOutcome>;
56
+ }
57
+
58
+ /** How many versions after the definition's a deploy tries before it gives up. */
59
+ const MAX_RENUMBER = 100;
60
+
61
+ /**
62
+ * Register a deployed agent or flow with its pins (see
63
+ * `DeployedVersionOutcome`): one rule for both, so they can't drift
64
+ * apart. Each definition is registered at most once per deploy, and a
65
+ * new version derives from the definition's version only, never from a
66
+ * version the same deploy created, so a change cascading from a tool
67
+ * through an agent into a flow ends within the one deploy.
68
+ */
69
+ export async function deployVersion<T extends PinnedDefinition>(
70
+ input: DeployVersionInput<T>,
71
+ ): Promise<DeployedVersionOutcome> {
72
+ const { definition, pins, pinsDigest, existing } = input;
73
+ const authored = definition.version;
74
+ const key = definitionKey(definition);
75
+ const same = (v: T) => definitionKey(v) === key && v.pinsDigest === pinsDigest;
76
+
77
+ const atAuthored = existing.find((v) => v.version === authored);
78
+ if (atAuthored !== undefined && same(atAuthored)) return { kind: 'unchanged', version: authored };
79
+ if (atAuthored === undefined) {
80
+ const outcome = await input.publish({ ...definition, pins, pinsDigest });
81
+ if (outcome !== 'taken') {
82
+ return { kind: outcome === 'ok' ? 'registered' : 'unchanged', version: authored };
83
+ }
84
+ // Taken with no active version: an unregistered version holds the
85
+ // number. Register the next one.
86
+ }
87
+
88
+ const reason = derivationReason(atAuthored, key);
89
+ const changes =
90
+ reason === 'pins-changed' ? { pinChanges: pinChanges(atAuthored?.pins, pins) } : {};
91
+ const earlier = existing.find(same);
92
+ if (earlier !== undefined) {
93
+ return {
94
+ kind: 'reused',
95
+ version: earlier.version,
96
+ // An expert's edit reused by a deploy reports the deploy's own reason.
97
+ reason:
98
+ earlier.derivedFrom !== undefined && earlier.derivedFrom.reason !== 'edited'
99
+ ? earlier.derivedFrom.reason
100
+ : reason,
101
+ ...changes,
102
+ };
103
+ }
104
+ const taken = new Set(existing.map((v) => v.version));
105
+ const version = await registerNextFree(input, reason, taken);
106
+ return version === undefined
107
+ ? { kind: 'unchanged', version: authored }
108
+ : { kind: 'renumbered', version, reason, ...changes };
109
+ }
110
+
111
+ /** Why the definition's version can't be registered as it is. */
112
+ function derivationReason(
113
+ atAuthored: PinnedDefinition | undefined,
114
+ key: string,
115
+ ): VersionDerivationReason {
116
+ if (atAuthored === undefined || definitionKey(atAuthored) !== key) return 'version-taken';
117
+ return atAuthored.pins === undefined ? 'unpinned' : 'pins-changed';
118
+ }
119
+
120
+ /**
121
+ * Register the definition with its pins under the first free version
122
+ * after its own (`nextVersion`), recording where it came from.
123
+ * `undefined` when it was left as it is (its project is gone).
124
+ */
125
+ async function registerNextFree<T extends PinnedDefinition>(
126
+ input: DeployVersionInput<T>,
127
+ reason: VersionDerivationReason,
128
+ taken: ReadonlySet<string>,
129
+ ): Promise<string | undefined> {
130
+ const { definition, pins, pinsDigest } = input;
131
+ const authored = definition.version;
132
+ let candidate = nextVersion(authored);
133
+ for (let tries = 0; candidate !== undefined && tries < MAX_RENUMBER; tries++) {
134
+ if (!taken.has(candidate)) {
135
+ const outcome = await input.publish({
136
+ ...definition,
137
+ version: candidate,
138
+ pins,
139
+ pinsDigest,
140
+ derivedFrom: { version: authored, reason },
141
+ });
142
+ if (outcome === 'ok') return candidate;
143
+ if (outcome === 'skipped') return undefined;
144
+ // Taken: an unregistered version holds this one too.
145
+ }
146
+ candidate = nextVersion(candidate);
147
+ }
148
+ throw new Error(
149
+ `${input.label}: no free version after ${authored} to register its new pins under`,
150
+ );
151
+ }
152
+
153
+ /** A definition: everything but its version and what the runtime sets. */
154
+ function definitionKey(definition: PinnedDefinition): string {
155
+ const { version: _v, pins: _p, pinsDigest: _d, derivedFrom: _f, ...rest } = definition;
156
+ return canonicalize(rest);
157
+ }
@@ -1,7 +1,8 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // Copyright (C) 2026 Kindgi Inc.
3
3
 
4
- import type { Cursor, SigningKeyId, TenantId } from '@kindgi/types';
4
+ import type { PinChange } from '@kindgi/agents';
5
+ import type { Cursor, SigningKeyId, TenantId, VersionDerivationReason } from '@kindgi/types';
5
6
 
6
7
  /**
7
8
  * Caller-plugged surface for the deployment ledger — the audit anchor
@@ -127,11 +128,34 @@ export interface DeployedPrimitive {
127
128
  readonly version?: string;
128
129
  }
129
130
 
131
+ /**
132
+ * An agent or flow a deployment shipped, under the version it's
133
+ * registered as. A deploy registers one under another version than its
134
+ * definition's when that version is registered already with other pins
135
+ * or content (versions never change); then `authoredVersion` is the
136
+ * definition's and `reason` says why.
137
+ */
138
+ export interface DeployedVersion extends DeployedPrimitive {
139
+ /** The version the agent's definition names, when it differs from `version`. */
140
+ readonly authoredVersion?: string;
141
+ readonly reason?: VersionDerivationReason;
142
+ /** `true`: this deploy registered `version`; `false`: an earlier deploy did. */
143
+ readonly newVersion?: boolean;
144
+ /** For `pins-changed`: the pins that differ from `authoredVersion`'s. */
145
+ readonly pinChanges?: readonly PinChange[];
146
+ }
147
+
148
+ /** An agent a deployment shipped (`DeployedVersion`). */
149
+ export type DeployedAgent = DeployedVersion;
150
+
151
+ /** A flow a deployment shipped (`DeployedVersion`). */
152
+ export type DeployedFlow = DeployedVersion;
153
+
130
154
  export interface DeploymentContents {
131
155
  readonly tools: readonly DeployedPrimitive[];
132
156
  readonly guardrails: readonly DeployedPrimitive[];
133
- readonly agents: readonly DeployedPrimitive[];
134
- readonly flows: readonly DeployedPrimitive[];
157
+ readonly agents: readonly DeployedAgent[];
158
+ readonly flows: readonly DeployedFlow[];
135
159
  }
136
160
 
137
161
  export interface DeploymentPrimitiveCounts {