mirror of
https://github.com/kennethnym/aris.git
synced 2026-03-20 09:01:19 +00:00
Compare commits
2 Commits
2b1a50349c
...
13de230f05
| Author | SHA1 | Date | |
|---|---|---|---|
|
13de230f05
|
|||
|
64a03b253e
|
@@ -1,6 +1,10 @@
|
|||||||
import { describe, expect, test } from "bun:test"
|
import { describe, expect, test } from "bun:test"
|
||||||
|
|
||||||
import { emptyEnhancementResult, parseEnhancementResult } from "./schema.ts"
|
import {
|
||||||
|
emptyEnhancementResult,
|
||||||
|
enhancementResultJsonSchema,
|
||||||
|
parseEnhancementResult,
|
||||||
|
} from "./schema.ts"
|
||||||
|
|
||||||
describe("parseEnhancementResult", () => {
|
describe("parseEnhancementResult", () => {
|
||||||
test("parses valid result", () => {
|
test("parses valid result", () => {
|
||||||
@@ -87,3 +91,86 @@ describe("emptyEnhancementResult", () => {
|
|||||||
expect(result.syntheticItems).toEqual([])
|
expect(result.syntheticItems).toEqual([])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("schema sync", () => {
|
||||||
|
const referencePayloads = [
|
||||||
|
{
|
||||||
|
name: "full payload with null slot fill",
|
||||||
|
payload: {
|
||||||
|
slotFills: {
|
||||||
|
"weather-1": { insight: "Rain after 3pm", crossSource: null },
|
||||||
|
"cal-2": { summary: "Busy morning" },
|
||||||
|
},
|
||||||
|
syntheticItems: [
|
||||||
|
{ id: "briefing-morning", type: "briefing", text: "Light day ahead." },
|
||||||
|
{ id: "nudge-umbrella", type: "nudge", text: "Bring an umbrella." },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty collections",
|
||||||
|
payload: { slotFills: {}, syntheticItems: [] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "slot fills only",
|
||||||
|
payload: {
|
||||||
|
slotFills: { "item-1": { slot: "filled" } },
|
||||||
|
syntheticItems: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "synthetic items only",
|
||||||
|
payload: {
|
||||||
|
slotFills: {},
|
||||||
|
syntheticItems: [{ id: "insight-1", type: "insight", text: "Something." }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const { name, payload } of referencePayloads) {
|
||||||
|
test(`arktype and JSON Schema agree on: ${name}`, () => {
|
||||||
|
// arktype accepts it
|
||||||
|
const parsed = parseEnhancementResult(JSON.stringify(payload))
|
||||||
|
expect(parsed).not.toBeNull()
|
||||||
|
|
||||||
|
// JSON Schema structure matches
|
||||||
|
const jsonSchema = enhancementResultJsonSchema
|
||||||
|
expect(Object.keys(jsonSchema.properties).sort()).toEqual(
|
||||||
|
Object.keys(payload).sort(),
|
||||||
|
)
|
||||||
|
expect([...jsonSchema.required].sort()).toEqual(Object.keys(payload).sort())
|
||||||
|
|
||||||
|
// syntheticItems item schema has the right required fields
|
||||||
|
const itemSchema = jsonSchema.properties.syntheticItems.items
|
||||||
|
expect([...itemSchema.required].sort()).toEqual(["id", "text", "type"])
|
||||||
|
|
||||||
|
// Verify each synthetic item has exactly the fields the JSON Schema expects
|
||||||
|
for (const item of payload.syntheticItems) {
|
||||||
|
expect(Object.keys(item).sort()).toEqual([...itemSchema.required].sort())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
test("JSON Schema rejects what arktype rejects: missing required field", () => {
|
||||||
|
// Missing syntheticItems
|
||||||
|
expect(parseEnhancementResult(JSON.stringify({ slotFills: {} }))).toBeNull()
|
||||||
|
|
||||||
|
// JSON Schema also requires it
|
||||||
|
expect(enhancementResultJsonSchema.required).toContain("syntheticItems")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("JSON Schema rejects what arktype rejects: wrong slot fill value type", () => {
|
||||||
|
const bad = { slotFills: { "item-1": { slot: 42 } }, syntheticItems: [] }
|
||||||
|
|
||||||
|
// arktype rejects it
|
||||||
|
expect(parseEnhancementResult(JSON.stringify(bad))).toBeNull()
|
||||||
|
|
||||||
|
// JSON Schema only allows string or null for slot values
|
||||||
|
const slotValueTypes =
|
||||||
|
enhancementResultJsonSchema.properties.slotFills.additionalProperties
|
||||||
|
.additionalProperties.type
|
||||||
|
expect(slotValueTypes).toContain("string")
|
||||||
|
expect(slotValueTypes).toContain("null")
|
||||||
|
expect(slotValueTypes).not.toContain("number")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
import { type } from "arktype"
|
import { type } from "arktype"
|
||||||
|
|
||||||
const syntheticItemSchema = type({
|
const SyntheticItem = type({
|
||||||
id: "string",
|
id: "string",
|
||||||
type: "string",
|
type: "string",
|
||||||
text: "string",
|
text: "string",
|
||||||
})
|
})
|
||||||
|
|
||||||
const enhancementResultSchema = type({
|
const EnhancementResult = type({
|
||||||
slotFills: "Record<string, Record<string, string | null>>",
|
slotFills: "Record<string, Record<string, string | null>>",
|
||||||
syntheticItems: syntheticItemSchema.array(),
|
syntheticItems: SyntheticItem.array(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export type SyntheticItem = typeof syntheticItemSchema.infer
|
export type SyntheticItem = typeof SyntheticItem.infer
|
||||||
export type EnhancementResult = typeof enhancementResultSchema.infer
|
export type EnhancementResult = typeof EnhancementResult.infer
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JSON Schema passed to OpenRouter's structured output.
|
* JSON Schema passed to OpenRouter's structured output.
|
||||||
* OpenRouter doesn't support arktype, so this is maintained separately.
|
* OpenRouter doesn't support arktype, so this is maintained separately.
|
||||||
*
|
*
|
||||||
* ⚠️ Must stay in sync with enhancementResultSchema above.
|
* ⚠️ Must stay in sync with EnhancementResult above.
|
||||||
* If you add/remove fields, update both schemas.
|
* If you add/remove fields, update both schemas.
|
||||||
*/
|
*/
|
||||||
export const enhancementResultJsonSchema = {
|
export const enhancementResultJsonSchema = {
|
||||||
@@ -76,7 +76,7 @@ export function parseEnhancementResult(json: string): EnhancementResult | null {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = enhancementResultSchema(parsed)
|
const result = EnhancementResult(parsed)
|
||||||
if (result instanceof type.errors) {
|
if (result instanceof type.errors) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user