@checkstack/ai-frontend 0.1.4 → 0.1.6

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,74 @@
1
1
  # @checkstack/ai-frontend
2
2
 
3
+ ## 0.1.6
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [56e7c75]
8
+ - Updated dependencies [56e7c75]
9
+ - @checkstack/frontend-api@0.9.0
10
+ - @checkstack/ui@1.15.1
11
+ - @checkstack/common@0.15.0
12
+ - @checkstack/ai-common@0.1.3
13
+ - @checkstack/integration-common@0.7.3
14
+
15
+ ## 0.1.5
16
+
17
+ ### Patch Changes
18
+
19
+ - b50916d: Fix "Date cannot be represented in JSON Schema" crashing the AI chat. Zod v4's
20
+ `toJSONSchema()` throws on `z.date()` (and even `z.coerce.date()`) by default,
21
+ and the chat hit this in TWO places:
22
+
23
+ - **`@checkstack/backend-api`** `toJsonSchema()` (the OpenAPI generator and AI
24
+ tool-introspection / MCP substrate) called it with no options.
25
+ - **`@checkstack/ai-backend`** the agent loop hands the Vercel AI SDK the raw
26
+ Zod tool input, and the SDK runs its OWN `toJSONSchema()` (throwing) to build
27
+ the model-facing tool schema - so a single date field in any tool input
28
+ crashed every chat turn (the whole tool list is projected before the model is
29
+ called).
30
+
31
+ Both now render dates as `{ type: "string", format: "date-time" }` (their wire
32
+ shape) and degrade other unrepresentable types to `{}` instead of throwing.
33
+
34
+ For the model boundary, a single `dateSafeModelSchema()` helper hands the SDK a
35
+ ready-made date-safe schema plus a validator that COERCES the ISO strings the
36
+ model emits back into real `Date`s before parsing with the original schema
37
+ (refinements and the downstream RPC client, which expects `Date`s, keep
38
+ working). A single `toModelSchema()` entry point applies this at EVERY point a
39
+ schema is handed to the model - chat tool inputs, the headless agent runner's
40
+ tool inputs (the automation "AI Action"), and `generateObject` structured
41
+ output - gated so non-date schemas are untouched, so individual tool / agent
42
+ definitions never special-case dates. Regression tests cover the converter, the
43
+ AI tool serializer, and the model-schema generation + coercion helper, including
44
+ the full inbound round-trip with the exact ISO shape a live model emits
45
+ (`...T22:00:00Z`, no milliseconds).
46
+
47
+ **Timezone correctness.** Because the model produces dates as text, the chat now
48
+ enforces an unambiguous wire contract: a date-time tool argument MUST be RFC 3339
49
+ with an explicit timezone offset. Zone-less (`2026-07-01T22:00:00`) and date-only
50
+ (`2026-07-01`) values are rejected with a model-readable error (the model
51
+ self-repairs), instead of being silently interpreted in the pod's local zone -
52
+ which would resolve the same string to different instants across pods. To resolve
53
+ an operator's bare "22:00", the browser's IANA timezone is sent with every chat
54
+ turn and folded into the system prompt, so each operator's times are interpreted
55
+ in their own zone by default. When no browser zone is available (a headless
56
+ automation AI Action), the reference zone falls back to the host/container
57
+ timezone (`TZ`), not UTC. A format-matrix test covers every common shape a model
58
+ might emit. The chat UI shows the operator which timezone is in use, and the
59
+ `TZ` override is documented for operators.
60
+
61
+ **Current time in context.** The model has no clock, so the system prompt now
62
+ includes the current instant (UTC plus the reference-zone wall clock), letting it
63
+ resolve relative dates like "today at 10:00" without asking. Applied to both the
64
+ chat and the headless agent runner, computed per turn/run so it is never stale.
65
+
66
+ **Less-strict topic classifier.** The chat's off-topic pre-classifier was
67
+ refusing legitimate requests like "create a maintenance" because maintenances
68
+ (and several other domains) were not listed. The classifier now enumerates the
69
+ full domain set and treats any create/list/update/delete action on a platform
70
+ resource as on-topic by default.
71
+
3
72
  ## 0.1.4
4
73
 
5
74
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/ai-frontend",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "license": "Elastic-2.0",
5
5
  "type": "module",
6
6
  "main": "src/index.tsx",
@@ -14,11 +14,11 @@
14
14
  "test": "bun test"
15
15
  },
16
16
  "dependencies": {
17
- "@checkstack/ai-common": "0.1.2",
18
- "@checkstack/common": "0.14.1",
19
- "@checkstack/frontend-api": "0.8.0",
20
- "@checkstack/integration-common": "0.7.2",
21
- "@checkstack/ui": "1.15.0",
17
+ "@checkstack/ai-common": "0.1.3",
18
+ "@checkstack/common": "0.15.0",
19
+ "@checkstack/frontend-api": "0.9.0",
20
+ "@checkstack/integration-common": "0.7.3",
21
+ "@checkstack/ui": "1.15.1",
22
22
  "lucide-react": "^1.17.0",
23
23
  "react": "19.2.7",
24
24
  "react-router-dom": "^7.16.0"
@@ -27,6 +27,6 @@
27
27
  "typescript": "^5.0.0",
28
28
  "@types/react": "^19.0.0",
29
29
  "@checkstack/tsconfig": "0.0.7",
30
- "@checkstack/scripts": "0.6.0"
30
+ "@checkstack/scripts": "0.6.1"
31
31
  }
32
32
  }
@@ -18,6 +18,20 @@ function newId(): string {
18
18
  : `${Date.now()}-${Math.random().toString(36).slice(2)}`;
19
19
  }
20
20
 
21
+ /**
22
+ * The operator's IANA timezone (e.g. "Europe/Berlin"), sent with every turn so
23
+ * the backend prompt can resolve bare times like "22:00" into the right offset.
24
+ * The browser is the authoritative source of the user's wall clock. Returns
25
+ * undefined where `Intl` is unavailable (the backend then falls back to UTC).
26
+ */
27
+ function browserTimeZone(): string | undefined {
28
+ try {
29
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || undefined;
30
+ } catch {
31
+ return undefined;
32
+ }
33
+ }
34
+
21
35
  /**
22
36
  * React hook that drives ONE streaming chat turn against /api/ai/chat. The
23
37
  * heavy lifting (SSE parsing, state folding) lives in DOM-free, unit-tested
@@ -65,7 +79,9 @@ export function useChatTurn({
65
79
  .fetch("/chat", {
66
80
  method: "POST",
67
81
  headers: { "content-type": "application/json" },
68
- body: JSON.stringify(body),
82
+ // Stamp the operator's timezone on every turn (user message AND
83
+ // confirm-card decision) so the model resolves bare times correctly.
84
+ body: JSON.stringify({ timeZone: browserTimeZone(), ...body }),
69
85
  signal: controller.signal,
70
86
  });
71
87
  if (!response.ok || !response.body) {
@@ -206,6 +206,16 @@ export function ChatPage() {
206
206
  const [copiedError, setCopiedError] = useState(false);
207
207
  const bottomRef = useRef<HTMLDivElement>(null);
208
208
 
209
+ // The operator's browser timezone, surfaced as a hint and sent with each turn
210
+ // so the assistant resolves bare times ("22:00") in this zone by default.
211
+ const browserTimeZone = useMemo<string | undefined>(() => {
212
+ try {
213
+ return Intl.DateTimeFormat().resolvedOptions().timeZone;
214
+ } catch {
215
+ return;
216
+ }
217
+ }, []);
218
+
209
219
  const {
210
220
  messages,
211
221
  setMessages,
@@ -643,6 +653,12 @@ export function ChatPage() {
643
653
  <Send className="w-4 h-4" />
644
654
  </Button>
645
655
  </div>
656
+ {browserTimeZone ? (
657
+ <p className="px-2 pb-2 -mt-1 text-xs text-muted-foreground">
658
+ Times you mention are interpreted in your timezone (
659
+ {browserTimeZone}).
660
+ </p>
661
+ ) : null}
646
662
  </Card>
647
663
  </div>
648
664