@sixb/core 0.0.1 → 0.1.1

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 (214) hide show
  1. package/README.md +24 -4
  2. package/dist/actions/index.js +17 -17
  3. package/dist/actions/worker.js +7 -7
  4. package/dist/actions/worker.js.map +1 -1
  5. package/dist/agents/adapters.d.ts.map +1 -1
  6. package/dist/agents/builders.d.ts +8 -1
  7. package/dist/agents/builders.d.ts.map +1 -1
  8. package/dist/agents/context.js +2 -2
  9. package/dist/agents/errors.d.ts +15 -0
  10. package/dist/agents/errors.d.ts.map +1 -1
  11. package/dist/agents/index.d.ts +4 -4
  12. package/dist/agents/index.d.ts.map +1 -1
  13. package/dist/agents/index.js +30 -11
  14. package/dist/agents/index.js.map +1 -1
  15. package/dist/agents/tool-definition.d.ts +9 -0
  16. package/dist/agents/tool-definition.d.ts.map +1 -0
  17. package/dist/agents/types.d.ts +64 -0
  18. package/dist/agents/types.d.ts.map +1 -1
  19. package/dist/agents/validation.d.ts +14 -1
  20. package/dist/agents/validation.d.ts.map +1 -1
  21. package/dist/auth/index.js +4 -4
  22. package/dist/authorization/index.js +3 -3
  23. package/dist/blob-storage/browser.js +3 -2
  24. package/dist/blob-storage/browser.js.map +1 -1
  25. package/dist/blob-storage/index.js +4 -3
  26. package/dist/blob-storage/index.js.map +1 -1
  27. package/dist/blob-storage/validation.d.ts.map +1 -1
  28. package/dist/bootstrap/index.js +25 -25
  29. package/dist/broker/index.js +2 -2
  30. package/dist/{chunk-590dejbp.js → chunk-4hwxq5e6.js} +1 -1
  31. package/dist/{chunk-wj01s5hr.js → chunk-4tvj3ate.js} +1 -1
  32. package/dist/{chunk-tsk2bxs3.js → chunk-53s4asfm.js} +13 -13
  33. package/dist/{chunk-tsk2bxs3.js.map → chunk-53s4asfm.js.map} +3 -3
  34. package/dist/{chunk-8r8cw2xw.js → chunk-63te770v.js} +40 -5
  35. package/dist/{chunk-8r8cw2xw.js.map → chunk-63te770v.js.map} +3 -3
  36. package/dist/{chunk-vj723r3p.js → chunk-6rzdtvn5.js} +1 -1
  37. package/dist/{chunk-5yny9z0e.js → chunk-6zrpewy4.js} +1 -1
  38. package/dist/{chunk-nvamz5dv.js → chunk-86459jn0.js} +294 -22
  39. package/dist/chunk-86459jn0.js.map +22 -0
  40. package/dist/{chunk-p6r5g77m.js → chunk-8kv2sez0.js} +1 -1
  41. package/dist/{chunk-k41ajtz7.js → chunk-9k3hgv8h.js} +1 -1
  42. package/dist/{chunk-jdwx7vzs.js → chunk-avppcgt6.js} +7 -7
  43. package/dist/{chunk-jdwx7vzs.js.map → chunk-avppcgt6.js.map} +1 -1
  44. package/dist/{chunk-2gpr6jsz.js → chunk-b1bqvhpe.js} +2 -2
  45. package/dist/{chunk-pv1yad91.js → chunk-b5dp7gaf.js} +17 -90
  46. package/dist/{chunk-pv1yad91.js.map → chunk-b5dp7gaf.js.map} +5 -6
  47. package/dist/{chunk-1vffvydj.js → chunk-be9vm29v.js} +6 -6
  48. package/dist/chunk-be9vm29v.js.map +10 -0
  49. package/dist/{chunk-wxhdnwh4.js → chunk-bh8bwma3.js} +1 -1
  50. package/dist/{chunk-0psb6kdw.js → chunk-btcsywk4.js} +12 -16
  51. package/dist/{chunk-0psb6kdw.js.map → chunk-btcsywk4.js.map} +3 -3
  52. package/dist/{chunk-0fygrz37.js → chunk-exrfbt2d.js} +1 -1
  53. package/dist/{chunk-c3zd880g.js → chunk-f6kyyeef.js} +2 -2
  54. package/dist/{chunk-q4qzq0ea.js → chunk-hb9xcx9c.js} +3 -3
  55. package/dist/{chunk-4pycxcj3.js → chunk-hz8m927w.js} +6 -9
  56. package/dist/{chunk-4pycxcj3.js.map → chunk-hz8m927w.js.map} +3 -3
  57. package/dist/{chunk-btpe64e4.js → chunk-m9w0artv.js} +1 -1
  58. package/dist/{chunk-45tw90k7.js → chunk-md9jbrm9.js} +1 -1
  59. package/dist/{chunk-dhmj53mx.js → chunk-nbywkytr.js} +3 -3
  60. package/dist/{chunk-t358gywy.js → chunk-p7vj8xgb.js} +8 -8
  61. package/dist/{chunk-2s6sydsh.js → chunk-qzyemcj7.js} +294 -4
  62. package/dist/chunk-qzyemcj7.js.map +13 -0
  63. package/dist/{chunk-r65yscvk.js → chunk-r82k4z7z.js} +131 -13
  64. package/dist/chunk-r82k4z7z.js.map +13 -0
  65. package/dist/{chunk-aebzcsxr.js → chunk-raq0x6bd.js} +11 -9
  66. package/dist/{chunk-aebzcsxr.js.map → chunk-raq0x6bd.js.map} +4 -4
  67. package/dist/{chunk-hsv43v58.js → chunk-rgzed50y.js} +77 -22
  68. package/dist/chunk-rgzed50y.js.map +22 -0
  69. package/dist/{chunk-yvef9xwc.js → chunk-rjqm6ksg.js} +7 -7
  70. package/dist/{chunk-f2b5e2gf.js → chunk-sb0shdvv.js} +18 -3
  71. package/dist/{chunk-f2b5e2gf.js.map → chunk-sb0shdvv.js.map} +3 -3
  72. package/dist/{chunk-dcgrzj1t.js → chunk-xt6qx9sh.js} +1 -1
  73. package/dist/{chunk-mx84y05q.js → chunk-y2ygpd0m.js} +2 -2
  74. package/dist/{chunk-yxss87qa.js → chunk-yn947ck3.js} +1 -1
  75. package/dist/datasets/builders.d.ts +18 -5
  76. package/dist/datasets/builders.d.ts.map +1 -1
  77. package/dist/datasets/changes.d.ts +18 -0
  78. package/dist/datasets/changes.d.ts.map +1 -0
  79. package/dist/datasets/index.d.ts +3 -1
  80. package/dist/datasets/index.d.ts.map +1 -1
  81. package/dist/datasets/types.d.ts +2 -0
  82. package/dist/datasets/types.d.ts.map +1 -1
  83. package/dist/datasets/validation.d.ts.map +1 -1
  84. package/dist/events/index.js +2 -2
  85. package/dist/http/api-routes.d.ts.map +1 -1
  86. package/dist/http/index.js +1 -1
  87. package/dist/index.d.ts +6 -6
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +64 -58
  90. package/dist/index.js.map +1 -1
  91. package/dist/json.d.ts +8 -1
  92. package/dist/json.d.ts.map +1 -1
  93. package/dist/lake-storage/definition-updates.d.ts.map +1 -1
  94. package/dist/lake-storage/in-memory.d.ts +15 -1
  95. package/dist/lake-storage/in-memory.d.ts.map +1 -1
  96. package/dist/lake-storage/index.d.ts +3 -1
  97. package/dist/lake-storage/index.d.ts.map +1 -1
  98. package/dist/lake-storage/index.js +15 -7
  99. package/dist/lake-storage/index.js.map +1 -1
  100. package/dist/lake-storage/merge-validation.d.ts +10 -0
  101. package/dist/lake-storage/merge-validation.d.ts.map +1 -0
  102. package/dist/lake-storage/merge.d.ts +32 -0
  103. package/dist/lake-storage/merge.d.ts.map +1 -0
  104. package/dist/lake-storage/types.d.ts +3 -1
  105. package/dist/lake-storage/types.d.ts.map +1 -1
  106. package/dist/logging/index.js +3 -3
  107. package/dist/logging/types.js +2 -2
  108. package/dist/materialization/index.js +2 -2
  109. package/dist/materializer/index.js +15 -15
  110. package/dist/materializer/index.js.map +1 -1
  111. package/dist/objects/index.js +17 -17
  112. package/dist/objects/query/index.js +8 -8
  113. package/dist/ontology/index.js +5 -5
  114. package/dist/ontology/internal.js +7 -6
  115. package/dist/ontology/internal.js.map +3 -3
  116. package/dist/ontology/validation/normalize.d.ts.map +1 -1
  117. package/dist/projections/internal.js +4 -4
  118. package/dist/runtime/sixb.d.ts.map +1 -1
  119. package/dist/storage/index.d.ts +1 -1
  120. package/dist/storage/index.d.ts.map +1 -1
  121. package/dist/storage/index.js +16 -16
  122. package/dist/storage/index.js.map +1 -1
  123. package/dist/storage/ontology/provider.js +4 -4
  124. package/dist/storage/sync-runs/index.d.ts +1 -1
  125. package/dist/storage/sync-runs/index.d.ts.map +1 -1
  126. package/dist/storage/sync-runs/types.d.ts +7 -3
  127. package/dist/storage/sync-runs/types.d.ts.map +1 -1
  128. package/dist/storage/types.d.ts +1 -1
  129. package/dist/storage/types.d.ts.map +1 -1
  130. package/dist/storage/workflow-runs/in-memory.d.ts.map +1 -1
  131. package/dist/storage/workflow-runs/types.d.ts +2 -0
  132. package/dist/storage/workflow-runs/types.d.ts.map +1 -1
  133. package/dist/syncs/builders.d.ts +11 -3
  134. package/dist/syncs/builders.d.ts.map +1 -1
  135. package/dist/syncs/index.d.ts +1 -1
  136. package/dist/syncs/index.d.ts.map +1 -1
  137. package/dist/syncs/types.d.ts +24 -19
  138. package/dist/syncs/types.d.ts.map +1 -1
  139. package/dist/syncs/validation.d.ts +14 -0
  140. package/dist/syncs/validation.d.ts.map +1 -0
  141. package/dist/testing/index.d.ts +1 -0
  142. package/dist/testing/index.d.ts.map +1 -1
  143. package/dist/testing/index.js +837 -583
  144. package/dist/testing/index.js.map +5 -4
  145. package/dist/testing/lake-merge-storage-contract.d.ts +7 -0
  146. package/dist/testing/lake-merge-storage-contract.d.ts.map +1 -0
  147. package/dist/testing/lake-storage-contract.d.ts.map +1 -1
  148. package/dist/workflows/index.js +23 -23
  149. package/package.json +1 -1
  150. package/src/agents/adapters.ts +1 -9
  151. package/src/agents/builders.ts +53 -3
  152. package/src/agents/errors.ts +22 -0
  153. package/src/agents/index.ts +22 -2
  154. package/src/agents/prompt.ts +2 -2
  155. package/src/agents/tool-definition.ts +93 -0
  156. package/src/agents/types.ts +101 -0
  157. package/src/agents/validation.ts +287 -3
  158. package/src/blob-storage/validation.ts +2 -10
  159. package/src/datasets/builders.ts +49 -5
  160. package/src/datasets/changes.ts +13 -0
  161. package/src/datasets/index.ts +3 -0
  162. package/src/datasets/types.ts +5 -0
  163. package/src/datasets/validation.ts +53 -11
  164. package/src/http/api-routes.ts +39 -4
  165. package/src/index.ts +18 -1
  166. package/src/json.ts +11 -4
  167. package/src/lake-storage/definition-updates.ts +21 -0
  168. package/src/lake-storage/in-memory.ts +284 -2
  169. package/src/lake-storage/index.ts +14 -0
  170. package/src/lake-storage/merge-validation.ts +117 -0
  171. package/src/lake-storage/merge.ts +35 -0
  172. package/src/lake-storage/types.ts +3 -1
  173. package/src/ontology/json-schema.ts +2 -1
  174. package/src/ontology/validation/normalize.ts +9 -15
  175. package/src/runtime/sixb.ts +16 -1
  176. package/src/storage/index.ts +1 -0
  177. package/src/storage/sync-runs/in-memory.ts +2 -2
  178. package/src/storage/sync-runs/index.ts +1 -0
  179. package/src/storage/sync-runs/types.ts +9 -4
  180. package/src/storage/types.ts +1 -0
  181. package/src/storage/workflow-runs/in-memory.ts +2 -0
  182. package/src/storage/workflow-runs/types.ts +2 -0
  183. package/src/syncs/builders.ts +31 -13
  184. package/src/syncs/index.ts +1 -0
  185. package/src/syncs/types.ts +44 -18
  186. package/src/syncs/validation.ts +77 -0
  187. package/src/testing/index.ts +4 -0
  188. package/src/testing/lake-merge-storage-contract.ts +302 -0
  189. package/src/testing/lake-storage-contract.ts +23 -0
  190. package/src/workflows/snapshot.ts +1 -10
  191. package/dist/chunk-1vffvydj.js.map +0 -10
  192. package/dist/chunk-2s6sydsh.js.map +0 -12
  193. package/dist/chunk-hsv43v58.js.map +0 -21
  194. package/dist/chunk-nvamz5dv.js.map +0 -21
  195. package/dist/chunk-r65yscvk.js.map +0 -11
  196. /package/dist/{chunk-590dejbp.js.map → chunk-4hwxq5e6.js.map} +0 -0
  197. /package/dist/{chunk-wj01s5hr.js.map → chunk-4tvj3ate.js.map} +0 -0
  198. /package/dist/{chunk-vj723r3p.js.map → chunk-6rzdtvn5.js.map} +0 -0
  199. /package/dist/{chunk-5yny9z0e.js.map → chunk-6zrpewy4.js.map} +0 -0
  200. /package/dist/{chunk-p6r5g77m.js.map → chunk-8kv2sez0.js.map} +0 -0
  201. /package/dist/{chunk-k41ajtz7.js.map → chunk-9k3hgv8h.js.map} +0 -0
  202. /package/dist/{chunk-2gpr6jsz.js.map → chunk-b1bqvhpe.js.map} +0 -0
  203. /package/dist/{chunk-wxhdnwh4.js.map → chunk-bh8bwma3.js.map} +0 -0
  204. /package/dist/{chunk-0fygrz37.js.map → chunk-exrfbt2d.js.map} +0 -0
  205. /package/dist/{chunk-c3zd880g.js.map → chunk-f6kyyeef.js.map} +0 -0
  206. /package/dist/{chunk-q4qzq0ea.js.map → chunk-hb9xcx9c.js.map} +0 -0
  207. /package/dist/{chunk-btpe64e4.js.map → chunk-m9w0artv.js.map} +0 -0
  208. /package/dist/{chunk-45tw90k7.js.map → chunk-md9jbrm9.js.map} +0 -0
  209. /package/dist/{chunk-dhmj53mx.js.map → chunk-nbywkytr.js.map} +0 -0
  210. /package/dist/{chunk-t358gywy.js.map → chunk-p7vj8xgb.js.map} +0 -0
  211. /package/dist/{chunk-yvef9xwc.js.map → chunk-rjqm6ksg.js.map} +0 -0
  212. /package/dist/{chunk-dcgrzj1t.js.map → chunk-xt6qx9sh.js.map} +0 -0
  213. /package/dist/{chunk-mx84y05q.js.map → chunk-y2ygpd0m.js.map} +0 -0
  214. /package/dist/{chunk-yxss87qa.js.map → chunk-yn947ck3.js.map} +0 -0
@@ -1,4 +1,11 @@
1
- import { getInvalidJsonValueReason } from "../json"
1
+ import { getInvalidJsonValueReason, isPlainRecord, type JsonValue } from "../json"
2
+ import {
3
+ normalizeSchemaValue,
4
+ type ObjectSchema,
5
+ OntologyValidationError,
6
+ type ValueType,
7
+ validateSchemaValue,
8
+ } from "../ontology"
2
9
  import type { GroupDefinition, SecurityRegistry } from "../security"
3
10
  import { AgentDefinitionError } from "./errors"
4
11
  import {
@@ -6,9 +13,26 @@ import {
6
13
  type AgentDefinition,
7
14
  type AgentLoopConfig,
8
15
  type AgentReasoningLevel,
16
+ type AgentToolDefinition,
17
+ type AgentToolInputSchema,
9
18
  type DefineAgentConfig,
10
19
  } from "./types"
11
20
 
21
+ const AGENT_TOOL_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_-]{0,63}$/
22
+ const AGENT_TOOL_PRIMITIVE_SCHEMAS = new Set([
23
+ "string",
24
+ "integer",
25
+ "double",
26
+ "decimal",
27
+ "boolean",
28
+ "date",
29
+ "timestamp",
30
+ "uuid",
31
+ "fileRef",
32
+ ])
33
+
34
+ export const AGENT_RESERVED_TOOL_NAMES = ["bash"] as const
35
+
12
36
  export function assertNonEmpty(value: string, field: string): void {
13
37
  if (!value.trim()) {
14
38
  throw new AgentDefinitionError(`[Sixb] Agent ${field} must not be empty.`)
@@ -24,10 +48,117 @@ export function isAgentDefinition(value: unknown): value is AgentDefinition {
24
48
  typeof value.instructions === "string" &&
25
49
  (value.reasoning === undefined || isAgentReasoningLevel(value.reasoning)) &&
26
50
  (value.providerOptions === undefined || isProviderOptions(value.providerOptions)) &&
27
- Array.isArray(value.groupIds)
51
+ Array.isArray(value.groupIds) &&
52
+ isAgentToolDefinitionArray(value.tools)
53
+ )
54
+ }
55
+
56
+ export function isAgentToolDefinition(value: unknown): value is AgentToolDefinition {
57
+ return (
58
+ isRecord(value) &&
59
+ value.kind === "agentTool" &&
60
+ typeof value.name === "string" &&
61
+ typeof value.description === "string" &&
62
+ isPlainRecord(value.input) &&
63
+ typeof value.handler === "function"
64
+ )
65
+ }
66
+
67
+ export function assertValidAgentToolName(name: unknown): asserts name is string {
68
+ if (typeof name !== "string" || !AGENT_TOOL_NAME_PATTERN.test(name)) {
69
+ throw new AgentDefinitionError(
70
+ "[Sixb] Agent tool name must be 1-64 characters, start with a letter or underscore, and contain only letters, numbers, underscores, or hyphens."
71
+ )
72
+ }
73
+ if ((AGENT_RESERVED_TOOL_NAMES as readonly string[]).includes(name)) {
74
+ throw new AgentDefinitionError(`[Sixb] Agent tool name '${name}' is reserved by the framework.`)
75
+ }
76
+ }
77
+
78
+ export function assertValidAgentToolDescription(name: string, description: unknown): void {
79
+ if (typeof description !== "string" || !description.trim()) {
80
+ throw new AgentDefinitionError(`[Sixb] Agent tool '${name}' description must not be empty.`)
81
+ }
82
+ }
83
+
84
+ export function assertValidAgentToolInput(name: string, input: unknown): void {
85
+ if (!isPlainRecord(input)) {
86
+ throw new AgentDefinitionError(`[Sixb] Agent tool '${name}' input must be a schema object.`)
87
+ }
88
+
89
+ for (const [field, schema] of Object.entries(input)) {
90
+ if (!field.trim()) {
91
+ throw new AgentDefinitionError(
92
+ `[Sixb] Agent tool '${name}' input field names must not be empty.`
93
+ )
94
+ }
95
+ assertValidAgentToolSchema(name, schema, `input.${field}`, new Set())
96
+ }
97
+ }
98
+
99
+ export function validateAndSnapshotAgentToolInput<TInput extends AgentToolInputSchema>(
100
+ toolName: string,
101
+ input: TInput
102
+ ): TInput {
103
+ assertValidAgentToolInput(toolName, input)
104
+ try {
105
+ return deepFreeze(structuredClone(input))
106
+ } catch {
107
+ throw new AgentDefinitionError(
108
+ `[Sixb] Agent tool '${toolName}' input must be safely snapshot-compatible.`
109
+ )
110
+ }
111
+ }
112
+
113
+ /** Validate model input against a tool schema, normalize it to JSON, and lock the snapshot. */
114
+ export function validateAndNormalizeAgentToolInput(
115
+ toolName: string,
116
+ shape: AgentToolInputSchema,
117
+ value: unknown,
118
+ valueTypesById: ReadonlyMap<string, ValueType>
119
+ ): Record<string, JsonValue> {
120
+ const label = `Agent tool '${toolName}' input`
121
+ if (!isPlainRecord(value)) {
122
+ throw new OntologyValidationError(`[Sixb] ${label} must be a JSON object.`)
123
+ }
124
+
125
+ const reason = getInvalidJsonValueReason(value, label)
126
+ if (reason) {
127
+ throw new OntologyValidationError(`[Sixb] ${label} must be JSON-compatible; ${reason}.`)
128
+ }
129
+
130
+ const schema: ObjectSchema = {
131
+ type: "object",
132
+ properties: Object.fromEntries(
133
+ Object.entries(shape).map(([field, fieldSchema]) => [
134
+ field,
135
+ { schema: fieldSchema, required: true },
136
+ ])
137
+ ),
138
+ }
139
+ validateSchemaValue(schema, value, label, valueTypesById)
140
+ return deepFreeze(
141
+ normalizeSchemaValue(schema, value, label, valueTypesById) as Record<string, JsonValue>
28
142
  )
29
143
  }
30
144
 
145
+ /** Revalidate and lock tool definitions supplied directly to the runtime. */
146
+ export function validateAgentToolsAtStartup(agents: readonly AgentDefinition[]): void {
147
+ for (const agent of agents) {
148
+ if (!isAgentDefinition(agent)) {
149
+ throw new AgentDefinitionError("[Sixb] Agents must contain only valid agent definitions.")
150
+ }
151
+
152
+ assertValidAgentToolDefinitions(agent.id, agent.tools)
153
+ for (const tool of agent.tools) {
154
+ deepFreeze(tool.input)
155
+ Object.freeze(tool)
156
+ }
157
+ Object.freeze(agent.tools)
158
+ Object.freeze(agent)
159
+ }
160
+ }
161
+
31
162
  export function assertValidLoopConfig(loop: AgentLoopConfig | undefined): void {
32
163
  const maxSteps = loop?.stopWhen?.maxSteps
33
164
  if (maxSteps === undefined) {
@@ -96,7 +227,7 @@ export function groupIdsFromDefinitions(
96
227
  })
97
228
 
98
229
  assertNoDuplicateGroupIds(agentId, groupIds)
99
- return groupIds
230
+ return Object.freeze(groupIds)
100
231
  }
101
232
 
102
233
  export function validateAgentGroupReferences(
@@ -146,6 +277,159 @@ function isProviderOptions(
146
277
  )
147
278
  }
148
279
 
280
+ function assertValidAgentToolSchema(
281
+ toolName: string,
282
+ schema: unknown,
283
+ path: string,
284
+ visiting: Set<object>
285
+ ): void {
286
+ if (typeof schema === "string") {
287
+ if (AGENT_TOOL_PRIMITIVE_SCHEMAS.has(schema)) {
288
+ return
289
+ }
290
+ throw invalidAgentToolSchema(toolName, path)
291
+ }
292
+ if (!isPlainRecord(schema) || typeof schema.type !== "string" || visiting.has(schema)) {
293
+ throw invalidAgentToolSchema(toolName, path)
294
+ }
295
+
296
+ visiting.add(schema)
297
+ switch (schema.type) {
298
+ case "enum": {
299
+ const values = schema.values
300
+ const validValues = isValidAgentToolEnumValues(values, schema.valueType)
301
+ if (!validValues || new Set(values).size !== values.length) {
302
+ throw invalidAgentToolSchema(toolName, path)
303
+ }
304
+ break
305
+ }
306
+ case "array":
307
+ assertValidAgentToolSchema(toolName, schema.items, `${path}.items`, visiting)
308
+ break
309
+ case "map":
310
+ if (schema.keySchema !== "string") {
311
+ throw invalidAgentToolSchema(toolName, path)
312
+ }
313
+ assertValidAgentToolSchema(toolName, schema.valueSchema, `${path}.valueSchema`, visiting)
314
+ break
315
+ case "object": {
316
+ if (!isPlainRecord(schema.properties)) {
317
+ throw invalidAgentToolSchema(toolName, path)
318
+ }
319
+ for (const [fieldId, field] of Object.entries(schema.properties)) {
320
+ if (!fieldId.trim() || !isPlainRecord(field) || !("schema" in field)) {
321
+ throw invalidAgentToolSchema(toolName, `${path}.properties.${fieldId}`)
322
+ }
323
+ if (
324
+ (field.required !== undefined && typeof field.required !== "boolean") ||
325
+ (field.nullable !== undefined && typeof field.nullable !== "boolean") ||
326
+ (field.description !== undefined &&
327
+ (typeof field.description !== "string" || !field.description.trim()))
328
+ ) {
329
+ throw invalidAgentToolSchema(toolName, `${path}.properties.${fieldId}`)
330
+ }
331
+ assertValidAgentToolSchema(
332
+ toolName,
333
+ field.schema,
334
+ `${path}.properties.${fieldId}.schema`,
335
+ visiting
336
+ )
337
+ }
338
+ break
339
+ }
340
+ case "valueTypeRef":
341
+ if (typeof schema.valueTypeId !== "string" || !schema.valueTypeId.trim()) {
342
+ throw invalidAgentToolSchema(toolName, path)
343
+ }
344
+ if (schema._resolved !== undefined) {
345
+ assertValidAgentToolSchema(toolName, schema._resolved, `${path}._resolved`, visiting)
346
+ }
347
+ break
348
+ default:
349
+ throw invalidAgentToolSchema(toolName, path)
350
+ }
351
+ visiting.delete(schema)
352
+ }
353
+
354
+ function invalidAgentToolSchema(toolName: string, path: string): AgentDefinitionError {
355
+ return new AgentDefinitionError(
356
+ `[Sixb] Agent tool '${toolName}' ${path} must be a valid Sixb schema.`
357
+ )
358
+ }
359
+
360
+ function isAgentToolDefinitionArray(value: unknown): value is readonly AgentToolDefinition[] {
361
+ if (!Array.isArray(value)) {
362
+ return false
363
+ }
364
+ for (let index = 0; index < value.length; index += 1) {
365
+ if (!Object.hasOwn(value, index) || !isAgentToolDefinition(value[index])) {
366
+ return false
367
+ }
368
+ }
369
+ return true
370
+ }
371
+
372
+ export function assertValidAgentToolDefinitions(
373
+ agentId: string,
374
+ tools: readonly AgentToolDefinition[]
375
+ ): void {
376
+ const seen = new Set<string>()
377
+ for (let index = 0; index < tools.length; index += 1) {
378
+ const tool = tools[index]
379
+ if (!Object.hasOwn(tools, index) || !isAgentToolDefinition(tool)) {
380
+ throw new AgentDefinitionError(
381
+ `[Sixb] Agent '${agentId}' tools must contain only agent tool definitions.`
382
+ )
383
+ }
384
+
385
+ assertValidAgentToolName(tool.name)
386
+ assertValidAgentToolDescription(tool.name, tool.description)
387
+ assertValidAgentToolInput(tool.name, tool.input)
388
+ if (seen.has(tool.name)) {
389
+ throw new AgentDefinitionError(
390
+ `[Sixb] Agent '${agentId}' tools contains duplicate tool name '${tool.name}'.`
391
+ )
392
+ }
393
+ seen.add(tool.name)
394
+ }
395
+ }
396
+
397
+ function isValidAgentToolEnumValues(values: unknown, valueType: unknown): values is unknown[] {
398
+ if (!Array.isArray(values) || values.length === 0) {
399
+ return false
400
+ }
401
+
402
+ for (let index = 0; index < values.length; index += 1) {
403
+ if (!Object.hasOwn(values, index)) {
404
+ return false
405
+ }
406
+ const value = values[index]
407
+ if (
408
+ valueType === "string"
409
+ ? typeof value !== "string"
410
+ : valueType !== "integer" || typeof value !== "number" || !Number.isInteger(value)
411
+ ) {
412
+ return false
413
+ }
414
+ }
415
+ return true
416
+ }
417
+
418
+ function deepFreeze<T>(value: T, seen: Set<object> = new Set()): T {
419
+ if (value === null || typeof value !== "object" || seen.has(value)) {
420
+ return value
421
+ }
422
+
423
+ seen.add(value)
424
+ for (const key of Reflect.ownKeys(value)) {
425
+ const descriptor = Object.getOwnPropertyDescriptor(value, key)
426
+ if (descriptor && "value" in descriptor) {
427
+ deepFreeze(descriptor.value, seen)
428
+ }
429
+ }
430
+ return Object.freeze(value)
431
+ }
432
+
149
433
  function isRecord(value: unknown): value is Record<string, unknown> {
150
434
  return typeof value === "object" && value !== null && !Array.isArray(value)
151
435
  }
@@ -1,3 +1,4 @@
1
+ import { isPlainRecord } from "../json"
1
2
  import { blobIdFromDigest } from "./derive"
2
3
  import type {
3
4
  BlobDigest,
@@ -7,22 +8,13 @@ import type {
7
8
  RangeReadableBlobStorage,
8
9
  } from "./types"
9
10
 
10
- function isPlainObject(value: unknown): value is Record<string, unknown> {
11
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
12
- return false
13
- }
14
-
15
- const prototype = Object.getPrototypeOf(value)
16
- return prototype === Object.prototype || prototype === null
17
- }
18
-
19
11
  export function isBlobDigest(value: unknown): value is BlobDigest {
20
12
  return typeof value === "string" && value.startsWith("sha256:") && value.length > "sha256:".length
21
13
  }
22
14
 
23
15
  export function isFileRef(value: unknown): value is FileRef {
24
16
  return (
25
- isPlainObject(value) &&
17
+ isPlainRecord(value) &&
26
18
  typeof value.blobId === "string" &&
27
19
  value.blobId.trim().length > 0 &&
28
20
  isBlobDigest(value.digest) &&
@@ -4,6 +4,7 @@ import type {
4
4
  DatasetColumnNameOf,
5
5
  DatasetColumnType,
6
6
  DatasetDefinition,
7
+ DatasetPrimaryKey,
7
8
  } from "./types"
8
9
  import { assertDatasetColumnDefinition, assertDatasetDefinition } from "./validation"
9
10
 
@@ -13,6 +14,7 @@ type DatasetColumnOptions = {
13
14
 
14
15
  type DefineDatasetOptions = {
15
16
  readonly schema: readonly DatasetColumnDefinition[]
17
+ readonly primaryKey?: DatasetPrimaryKey
16
18
  readonly partitionBy?: readonly string[]
17
19
  readonly description?: string
18
20
  }
@@ -20,6 +22,7 @@ type DefineDatasetOptions = {
20
22
  type DeriveDatasetOptions<TParent extends DatasetDefinition> = {
21
23
  readonly pick?: readonly DatasetColumnNameOf<TParent>[]
22
24
  readonly add?: readonly DatasetColumnDefinition[]
25
+ readonly primaryKey?: DatasetPrimaryKey
23
26
  readonly partitionBy?: readonly string[]
24
27
  readonly description?: string
25
28
  }
@@ -39,6 +42,24 @@ type ColumnDefinitionOf<
39
42
  TName extends ColumnNameOf<TColumns>,
40
43
  > = Extract<TColumns[number], { readonly name: TName }>
41
44
 
45
+ type PrimaryKeyColumnNameOf<TColumns extends readonly DatasetColumnDefinition[]> =
46
+ string extends ColumnNameOf<TColumns>
47
+ ? string
48
+ : {
49
+ [TName in ColumnNameOf<TColumns>]: ColumnDefinitionOf<
50
+ TColumns,
51
+ TName
52
+ >["type"] extends "string"
53
+ ? ColumnDefinitionOf<TColumns, TName> extends { readonly nullable: true }
54
+ ? never
55
+ : TName
56
+ : never
57
+ }[ColumnNameOf<TColumns>]
58
+
59
+ type CheckedDefineDatasetOptions<TOptions extends DefineDatasetOptions> = TOptions & {
60
+ readonly primaryKey?: DatasetPrimaryKey<PrimaryKeyColumnNameOf<TOptions["schema"]>>
61
+ }
62
+
42
63
  type PickColumns<
43
64
  TColumns extends readonly DatasetColumnDefinition[],
44
65
  TNames extends readonly ColumnNameOf<TColumns>[],
@@ -86,8 +107,9 @@ type DatasetColumnResult<
86
107
 
87
108
  type DatasetDefinitionResult<TId extends string, TOptions extends DefineDatasetOptions> = Omit<
88
109
  DatasetDefinition<TId, TOptions["schema"]>,
89
- "partitionBy" | "description"
110
+ "primaryKey" | "partitionBy" | "description"
90
111
  > &
112
+ FieldFromOptions<TOptions, "primaryKey", DatasetPrimaryKey> &
91
113
  FieldFromOptions<TOptions, "partitionBy", readonly string[]> &
92
114
  FieldFromOptions<TOptions, "description", string>
93
115
 
@@ -95,10 +117,21 @@ type DerivedDatasetDefinitionResult<
95
117
  TId extends string,
96
118
  TParent extends DatasetDefinition,
97
119
  TOptions extends DeriveDatasetOptions<TParent> | undefined,
98
- > = Omit<DatasetDefinition<TId, DerivedColumns<TParent, TOptions>>, "partitionBy" | "description"> &
120
+ > = Omit<
121
+ DatasetDefinition<TId, DerivedColumns<TParent, TOptions>>,
122
+ "primaryKey" | "partitionBy" | "description"
123
+ > &
124
+ FieldFromOptions<TOptions, "primaryKey", DatasetPrimaryKey> &
99
125
  FieldFromOptions<TOptions, "partitionBy", readonly string[]> &
100
126
  FieldFromOptions<TOptions, "description", string>
101
127
 
128
+ type CheckedDeriveDatasetOptions<
129
+ TParent extends DatasetDefinition,
130
+ TOptions extends DeriveDatasetOptions<TParent>,
131
+ > = TOptions & {
132
+ readonly primaryKey?: DatasetPrimaryKey<PrimaryKeyColumnNameOf<DerivedColumns<TParent, TOptions>>>
133
+ }
134
+
102
135
  export function col<const TName extends string, const TType extends DatasetColumnType>(
103
136
  name: TName,
104
137
  type: TType
@@ -130,12 +163,19 @@ function assertDatasetId(id: string): void {
130
163
  }
131
164
 
132
165
  function createDatasetDefinition(id: string, options: DefineDatasetOptions): DatasetDefinition {
133
- const definition: DatasetDefinition = {
166
+ const definition: unknown = {
134
167
  kind: "dataset",
135
168
  id,
136
169
  schema: {
137
170
  columns: [...options.schema],
138
171
  },
172
+ ...(options.primaryKey !== undefined
173
+ ? {
174
+ primaryKey: Array.isArray(options.primaryKey)
175
+ ? [...options.primaryKey]
176
+ : options.primaryKey,
177
+ }
178
+ : {}),
139
179
  ...(options.partitionBy !== undefined ? { partitionBy: [...options.partitionBy] } : {}),
140
180
  ...(options.description !== undefined ? { description: options.description } : {}),
141
181
  }
@@ -189,6 +229,7 @@ function deriveDatasetDefinition(
189
229
 
190
230
  return createDatasetDefinition(id, {
191
231
  schema: [...pickedColumns, ...(options.add ?? [])],
232
+ ...(options.primaryKey !== undefined ? { primaryKey: options.primaryKey } : {}),
192
233
  ...(options.partitionBy !== undefined ? { partitionBy: options.partitionBy } : {}),
193
234
  ...(options.description !== undefined ? { description: options.description } : {}),
194
235
  })
@@ -203,7 +244,10 @@ function createDatasetDeriveBuilder<TId extends string>(id: TId) {
203
244
  function derive<
204
245
  const TParent extends DatasetDefinition,
205
246
  const TOptions extends DeriveDatasetOptions<TParent>,
206
- >(parent: TParent, options: TOptions): DerivedDatasetDefinitionResult<TId, TParent, TOptions>
247
+ >(
248
+ parent: TParent,
249
+ options: CheckedDeriveDatasetOptions<TParent, TOptions>
250
+ ): DerivedDatasetDefinitionResult<TId, TParent, TOptions>
207
251
  function derive(
208
252
  parent: DatasetDefinition,
209
253
  options?: DeriveDatasetOptions<DatasetDefinition>
@@ -220,7 +264,7 @@ export function defineDataset<const TId extends string>(
220
264
  export function defineDataset<
221
265
  const TId extends string,
222
266
  const TOptions extends DefineDatasetOptions,
223
- >(id: TId, options: TOptions): DatasetDefinitionResult<TId, TOptions>
267
+ >(id: TId, options: CheckedDefineDatasetOptions<TOptions>): DatasetDefinitionResult<TId, TOptions>
224
268
  export function defineDataset(
225
269
  id: string,
226
270
  options?: DefineDatasetOptions
@@ -0,0 +1,13 @@
1
+ export type MergeChange<Row, Key> =
2
+ | { readonly kind: "upsert"; readonly row: Row }
3
+ | { readonly kind: "delete"; readonly key: Key }
4
+
5
+ export const change = {
6
+ upsert<const Row extends object>(row: Row): { readonly kind: "upsert"; readonly row: Row } {
7
+ return { kind: "upsert", row }
8
+ },
9
+
10
+ delete<const Key extends object>(key: Key): { readonly kind: "delete"; readonly key: Key } {
11
+ return { kind: "delete", key }
12
+ },
13
+ }
@@ -1,4 +1,6 @@
1
1
  export { col, defineDataset } from "./builders"
2
+ export type { MergeChange } from "./changes"
3
+ export { change } from "./changes"
2
4
  export { DatasetValidationError } from "./errors"
3
5
  export type {
4
6
  DatasetColumnDefinition,
@@ -8,6 +10,7 @@ export type {
8
10
  DatasetColumnTypeOf,
9
11
  DatasetColumnUnionOf,
10
12
  DatasetDefinition,
13
+ DatasetPrimaryKey,
11
14
  DatasetSchema,
12
15
  } from "./types"
13
16
  export { getDatasetRowValidationError, isDatasetDefinition } from "./validation"
@@ -24,6 +24,10 @@ export interface DatasetSchema<
24
24
  readonly columns: TColumns
25
25
  }
26
26
 
27
+ export type DatasetPrimaryKey<TColumnName extends string = string> =
28
+ | TColumnName
29
+ | readonly [TColumnName, TColumnName, ...TColumnName[]]
30
+
27
31
  export interface DatasetDefinition<
28
32
  TId extends string = string,
29
33
  TColumns extends readonly DatasetColumnDefinition[] = readonly DatasetColumnDefinition[],
@@ -31,6 +35,7 @@ export interface DatasetDefinition<
31
35
  readonly kind: "dataset"
32
36
  readonly id: TId
33
37
  readonly schema: DatasetSchema<TColumns>
38
+ readonly primaryKey?: DatasetPrimaryKey
34
39
  readonly partitionBy?: readonly string[]
35
40
  readonly description?: string
36
41
  }
@@ -1,5 +1,5 @@
1
1
  import { isFileRef } from "../blob-storage"
2
- import { isJsonValue } from "../json"
2
+ import { isJsonValue, isPlainRecord } from "../json"
3
3
  import { isDecimalString } from "../ontology/decimal"
4
4
  import { DatasetValidationError } from "./errors"
5
5
  import type {
@@ -25,15 +25,6 @@ function isRecord(value: unknown): value is Record<string, unknown> {
25
25
  return typeof value === "object" && value !== null && !Array.isArray(value)
26
26
  }
27
27
 
28
- function isPlainObject(value: unknown): value is Record<string, unknown> {
29
- if (!isRecord(value)) {
30
- return false
31
- }
32
-
33
- const prototype = Object.getPrototypeOf(value)
34
- return prototype === Object.prototype || prototype === null
35
- }
36
-
37
28
  function isValidDateValue(value: unknown): boolean {
38
29
  return value instanceof Date && !Number.isNaN(value.getTime())
39
30
  }
@@ -128,6 +119,53 @@ function assertDatasetSchema(
128
119
  }
129
120
  }
130
121
 
122
+ function assertDatasetPrimaryKey(
123
+ primaryKey: unknown,
124
+ schema: DatasetSchema,
125
+ createError: (message: string) => Error
126
+ ): void {
127
+ let keyColumns: readonly unknown[]
128
+ if (typeof primaryKey === "string") {
129
+ keyColumns = [primaryKey]
130
+ } else if (Array.isArray(primaryKey)) {
131
+ if (primaryKey.length < 2) {
132
+ throw createError(
133
+ "Dataset primaryKey arrays must contain at least two column names. Use a string for a single-column key."
134
+ )
135
+ }
136
+ keyColumns = primaryKey
137
+ } else {
138
+ throw createError(
139
+ "Dataset primaryKey must be a column name or an array of at least two column names."
140
+ )
141
+ }
142
+
143
+ const columnsByName = new Map(schema.columns.map((column) => [column.name, column] as const))
144
+ const seenColumnNames = new Set<string>()
145
+
146
+ for (const columnName of keyColumns) {
147
+ if (typeof columnName !== "string" || columnName.trim().length === 0) {
148
+ throw createError("Dataset primaryKey columns must be non-empty strings.")
149
+ }
150
+
151
+ if (seenColumnNames.has(columnName)) {
152
+ throw createError(`Dataset primaryKey contains duplicate column '${columnName}'.`)
153
+ }
154
+ seenColumnNames.add(columnName)
155
+
156
+ const column = columnsByName.get(columnName)
157
+ if (column === undefined) {
158
+ throw createError(`Dataset primaryKey column '${columnName}' is not declared in the schema.`)
159
+ }
160
+ if (column.type !== "string") {
161
+ throw createError(`Dataset primaryKey column '${columnName}' must have type 'string'.`)
162
+ }
163
+ if (column.nullable === true) {
164
+ throw createError(`Dataset primaryKey column '${columnName}' must not be nullable.`)
165
+ }
166
+ }
167
+ }
168
+
131
169
  export function assertDatasetDefinition(
132
170
  definition: unknown,
133
171
  createError: (message: string) => Error = (message) => new DatasetValidationError(message)
@@ -143,6 +181,10 @@ export function assertDatasetDefinition(
143
181
  assertNonEmptyString(definition.id, "Dataset id", createError)
144
182
  assertDatasetSchema(definition.schema, createError)
145
183
 
184
+ if (definition.primaryKey !== undefined) {
185
+ assertDatasetPrimaryKey(definition.primaryKey, definition.schema, createError)
186
+ }
187
+
146
188
  if (definition.partitionBy !== undefined) {
147
189
  if (!Array.isArray(definition.partitionBy)) {
148
190
  throw createError("Dataset partitionBy must be an array of column names.")
@@ -176,7 +218,7 @@ export function getDatasetRowValidationError(
176
218
  dataset: DatasetDefinition,
177
219
  options?: { readonly columns?: Iterable<string> }
178
220
  ): string | null {
179
- if (!isPlainObject(row)) {
221
+ if (!isPlainRecord(row)) {
180
222
  return `Dataset '${dataset.id}' rows must be plain objects.`
181
223
  }
182
224