@checkstack/backend-api 0.21.3 → 0.21.4

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,62 @@
1
1
  # @checkstack/backend-api
2
2
 
3
+ ## 0.21.4
4
+
5
+ ### Patch Changes
6
+
7
+ - b50916d: Fix "Date cannot be represented in JSON Schema" crashing the AI chat. Zod v4's
8
+ `toJSONSchema()` throws on `z.date()` (and even `z.coerce.date()`) by default,
9
+ and the chat hit this in TWO places:
10
+
11
+ - **`@checkstack/backend-api`** `toJsonSchema()` (the OpenAPI generator and AI
12
+ tool-introspection / MCP substrate) called it with no options.
13
+ - **`@checkstack/ai-backend`** the agent loop hands the Vercel AI SDK the raw
14
+ Zod tool input, and the SDK runs its OWN `toJSONSchema()` (throwing) to build
15
+ the model-facing tool schema - so a single date field in any tool input
16
+ crashed every chat turn (the whole tool list is projected before the model is
17
+ called).
18
+
19
+ Both now render dates as `{ type: "string", format: "date-time" }` (their wire
20
+ shape) and degrade other unrepresentable types to `{}` instead of throwing.
21
+
22
+ For the model boundary, a single `dateSafeModelSchema()` helper hands the SDK a
23
+ ready-made date-safe schema plus a validator that COERCES the ISO strings the
24
+ model emits back into real `Date`s before parsing with the original schema
25
+ (refinements and the downstream RPC client, which expects `Date`s, keep
26
+ working). A single `toModelSchema()` entry point applies this at EVERY point a
27
+ schema is handed to the model - chat tool inputs, the headless agent runner's
28
+ tool inputs (the automation "AI Action"), and `generateObject` structured
29
+ output - gated so non-date schemas are untouched, so individual tool / agent
30
+ definitions never special-case dates. Regression tests cover the converter, the
31
+ AI tool serializer, and the model-schema generation + coercion helper, including
32
+ the full inbound round-trip with the exact ISO shape a live model emits
33
+ (`...T22:00:00Z`, no milliseconds).
34
+
35
+ **Timezone correctness.** Because the model produces dates as text, the chat now
36
+ enforces an unambiguous wire contract: a date-time tool argument MUST be RFC 3339
37
+ with an explicit timezone offset. Zone-less (`2026-07-01T22:00:00`) and date-only
38
+ (`2026-07-01`) values are rejected with a model-readable error (the model
39
+ self-repairs), instead of being silently interpreted in the pod's local zone -
40
+ which would resolve the same string to different instants across pods. To resolve
41
+ an operator's bare "22:00", the browser's IANA timezone is sent with every chat
42
+ turn and folded into the system prompt, so each operator's times are interpreted
43
+ in their own zone by default. When no browser zone is available (a headless
44
+ automation AI Action), the reference zone falls back to the host/container
45
+ timezone (`TZ`), not UTC. A format-matrix test covers every common shape a model
46
+ might emit. The chat UI shows the operator which timezone is in use, and the
47
+ `TZ` override is documented for operators.
48
+
49
+ **Current time in context.** The model has no clock, so the system prompt now
50
+ includes the current instant (UTC plus the reference-zone wall clock), letting it
51
+ resolve relative dates like "today at 10:00" without asking. Applied to both the
52
+ chat and the headless agent runner, computed per turn/run so it is never stale.
53
+
54
+ **Less-strict topic classifier.** The chat's off-topic pre-classifier was
55
+ refusing legitimate requests like "create a maintenance" because maintenances
56
+ (and several other domains) were not listed. The classifier now enumerates the
57
+ full domain set and treats any create/list/update/delete action on a platform
58
+ resource as on-topic by default.
59
+
3
60
  ## 0.21.3
4
61
 
5
62
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/backend-api",
3
- "version": "0.21.3",
3
+ "version": "0.21.4",
4
4
  "license": "Elastic-2.0",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -42,3 +42,36 @@ describe("toJsonSchema x-* metadata", () => {
42
42
  expect(json.properties.secretEnv?.["x-secret-env"]).toBe(true);
43
43
  });
44
44
  });
45
+
46
+ describe("toJsonSchema unrepresentable types", () => {
47
+ // Regression: Zod v4 throws "Date cannot be represented in JSON Schema" for
48
+ // `z.date()` by default. This converter feeds the OpenAPI generator AND the
49
+ // AI tool projection, so a single date field (timestamps are everywhere)
50
+ // would crash the whole AI chat. Dates must serialize as date-time strings.
51
+ test("represents z.date() as a date-time string instead of throwing", () => {
52
+ const schema = z.object({
53
+ createdAt: z.date(),
54
+ note: z.string(),
55
+ });
56
+
57
+ const json = toJsonSchema(schema) as {
58
+ properties: Record<string, Record<string, unknown>>;
59
+ };
60
+
61
+ expect(json.properties.createdAt?.type).toBe("string");
62
+ expect(json.properties.createdAt?.format).toBe("date-time");
63
+ expect(json.properties.note?.type).toBe("string");
64
+ });
65
+
66
+ test("handles a top-level / optional / array date without throwing", () => {
67
+ expect(() => toJsonSchema(z.date())).not.toThrow();
68
+ const json = toJsonSchema(
69
+ z.object({ seen: z.array(z.date()), at: z.date().optional() }),
70
+ ) as { properties: Record<string, Record<string, unknown>> };
71
+ expect(
72
+ (json.properties.seen?.items as Record<string, unknown> | undefined)
73
+ ?.format,
74
+ ).toBe("date-time");
75
+ expect(json.properties.at?.format).toBe("date-time");
76
+ });
77
+ });
@@ -101,8 +101,25 @@ function addSchemaMetadata(
101
101
  * dropdowns for optionsResolver fields, hidden for auto-populated fields).
102
102
  */
103
103
  export function toJsonSchema(zodSchema: z.ZodTypeAny): Record<string, unknown> {
104
- // Use Zod's native JSON Schema conversion
105
- const jsonSchema = zodSchema.toJSONSchema() as Record<string, unknown>;
104
+ // Use Zod's native JSON Schema conversion.
105
+ //
106
+ // Zod v4 throws "Date cannot be represented in JSON Schema" for `z.date()`
107
+ // by default (`unrepresentable: "throw"`). Many platform contracts carry
108
+ // date fields (timestamps), and this converter is the substrate for the
109
+ // OpenAPI generator AND the AI tool projection - so a single date field
110
+ // would crash the whole AI chat (every turn projects the full tool list)
111
+ // and break OpenAPI. Dates serialize as ISO strings over the wire, so we
112
+ // represent them as `{ type: "string", format: "date-time" }` and let any
113
+ // other unrepresentable type degrade to `{}` (any) rather than throw.
114
+ const jsonSchema = z.toJSONSchema(zodSchema, {
115
+ unrepresentable: "any",
116
+ override: (ctx) => {
117
+ if (ctx.zodSchema instanceof z.ZodDate) {
118
+ ctx.jsonSchema.type = "string";
119
+ ctx.jsonSchema.format = "date-time";
120
+ }
121
+ },
122
+ }) as Record<string, unknown>;
106
123
  addSchemaMetadata(zodSchema, jsonSchema);
107
124
  return jsonSchema;
108
125
  }