History

Register standard JSON Schema formats

Rhys Sullivan committed 7 months ago

Commit 2b1e1401 parent f7749984 files · +78 −1Browse files

Unified · Split

4 files
  1. bun.lock +2 −1
  2. packages/kernel/core/package.json +1 −0
  3. packages/kernel/core/src/index.test.ts +72 −0
  4. packages/kernel/core/src/json-schema.ts +3 −0
bun.lock+2−1
@@ -19,7 +19,7 @@ · 18 unmodified lines
19 },19 },
20 "apps/executor": {20 "apps/executor": {
21 "name": "executor",21 "name": "executor",
22 "version": "1.3.0-beta.0",22 "version": "1.3.0-beta.3",
23 "bin": {23 "bin": {
24 "executor": "bin/executor",24 "executor": "bin/executor",
25 },25 },
@@ -239,6 +239,7 @@ · 213 unmodified lines
239 "dependencies": {239 "dependencies": {
240 "@standard-schema/spec": "^1.0.0",240 "@standard-schema/spec": "^1.0.0",
241 "ajv": "^8.17.1",241 "ajv": "^8.17.1",
242 "ajv-formats": "^3.0.1",
242 "effect": "catalog:",243 "effect": "catalog:",
243 },244 },
244 "devDependencies": {245 "devDependencies": {
packages/kernel/core/package.json+1−0
@@ -13,6 +13,7 @@ · 12 unmodified lines
13 "dependencies": {13 "dependencies": {
14 "@standard-schema/spec": "^1.0.0",14 "@standard-schema/spec": "^1.0.0",
15 "ajv": "^8.17.1",15 "ajv": "^8.17.1",
16 "ajv-formats": "^3.0.1",
16 "effect": "catalog:"17 "effect": "catalog:"
17 },18 },
18 "devDependencies": {19 "devDependencies": {
packages/kernel/core/src/index.test.ts+72−0
@@ -282,6 +282,67 @@ describe("codemode-core", () => { · 281 unmodified lines
282 }),282 }),
283 );283 );
284 284
285 it.effect("enforces standard JSON Schema string formats", () =>
286 Effect.gen(function* () {
287 const tools = {
288 "events.create": {
289 inputSchema: standardSchemaFromJsonSchema({
290 type: "object",
291 required: ["dateFrom", "issueId", "email", "documentationUrl"],
292 properties: {
293 dateFrom: { type: "string", format: "date-time" },
294 issueId: { type: "string", format: "uuid" },
295 email: { type: "string", format: "email" },
296 documentationUrl: { type: "string", format: "uri" },
297 },
298 additionalProperties: false,
299 }),
300 execute: (input: unknown) => input,
301 },
302 } satisfies ToolMap;
303
304 const invoker = makeToolInvokerFromTools({ tools });
305
306 const success = yield* invoker.invoke({
307 path: "events.create",
308 args: {
309 dateFrom: "2026-03-30T12:00:00Z",
310 issueId: "2c981f4a-7318-47a3-a3d3-8c145ee8ca1f",
311 email: "rhys@example.com",
312 documentationUrl: "https://example.com/docs",
313 },
314 });
315
316 expect(success).toEqual({
317 dateFrom: "2026-03-30T12:00:00Z",
318 issueId: "2c981f4a-7318-47a3-a3d3-8c145ee8ca1f",
319 email: "rhys@example.com",
320 documentationUrl: "https://example.com/docs",
321 });
322
323 const failure = yield* Effect.either(
324 invoker.invoke({
325 path: "events.create",
326 args: {
327 dateFrom: "not-a-date",
328 issueId: "not-a-uuid",
329 email: "not-an-email",
330 documentationUrl: "not-a-uri",
331 },
332 }),
333 );
334
335 assertTrue(Either.isLeft(failure));
336 assertInstanceOf(failure.left, Error);
337 expect(failure.left.message).toContain('dateFrom: format: must match format "date-time"');
338 expect(failure.left.message).toContain('issueId: format: must match format "uuid"');
339 expect(failure.left.message).toContain('email: format: must match format "email"');
340 expect(failure.left.message).toContain(
341 'documentationUrl: format: must match format "uri"',
342 );
343 }),
344 );
345
285 it.effect("hydrates dynamic discover results via tool catalog", () =>346 it.effect("hydrates dynamic discover results via tool catalog", () =>
286 Effect.gen(function* () {347 Effect.gen(function* () {
287 const descriptors: Record<string, ToolDescriptor> = {348 const descriptors: Record<string, ToolDescriptor> = {
@@ -373,6 +434,17 @@ describe("codemode-core", () => { · 85 unmodified lines
373 expect(discovered.bestPath).toBe("source.issues.create");434 expect(discovered.bestPath).toBe("source.issues.create");
374 expect(discovered.results[0]?.path).toBe("source.issues.create");435 expect(discovered.results[0]?.path).toBe("source.issues.create");
375 expect(discovered.results[0]?.interaction).toBe("required");436 expect(discovered.results[0]?.interaction).toBe("required");
437
438 const executeDescription = yield* dynamic.executeDescription;
439 expect(executeDescription).toContain(
440 'const { results, bestPath } = await tools.discover({ query: "<intent>", limit: 12 });',
441 );
442 expect(executeDescription).toContain(
443 'const path = bestPath ?? results[0]?.path; if (!path) return "No matching tools found.";',
444 );
445 expect(executeDescription).toContain(
446 "Object.keys(tools) is not a useful way to discover capabilities.",
447 );
376 }),448 }),
377 );449 );
378 450
packages/kernel/core/src/json-schema.ts+3−0
@@ -1,5 +1,6 @@
1import type { ErrorObject } from "ajv";1import type { ErrorObject } from "ajv";
2import Ajv2020 from "ajv/dist/2020";2import Ajv2020 from "ajv/dist/2020";
3import addFormats from "ajv-formats";
3 4
4import type { StandardSchema } from "./types";5import type { StandardSchema } from "./types";
5import { unknownInputSchema } from "./types";6import { unknownInputSchema } from "./types";
@@ -11,6 +12,8 @@ const ajv = new Ajv2020({ · 5 unmodified lines
11 allowUnionTypes: true,12 allowUnionTypes: true,
12});13});
13 14
15addFormats(ajv);
16
14const decodePointerSegment = (segment: string): PropertyKey => {17const decodePointerSegment = (segment: string): PropertyKey => {
15 const decoded = segment.replaceAll("~1", "/").replaceAll("~0", "~");18 const decoded = segment.replaceAll("~1", "/").replaceAll("~0", "~");
16 return /^\d+$/.test(decoded) ? Number(decoded) : decoded;19 return /^\d+$/.test(decoded) ? Number(decoded) : decoded;

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t