Files
craft-libs/crates/ai-core
Dylan M. Taylor f180fc0d35 Use chrono instead of hand-rolled UTC conversion (#219)
* Use chrono for UTC/calendar conversion instead of hand-rolled copies

Replaces the hand-rolled civil_from_days and Day::to_days in
crates/ai-core/src/history/report.rs with chrono, added as a workspace
dependency (std-only, so ai-core still compiles for wasm). The day/month
grouping, ISO and CSV timestamps and the Day/Month display are byte-identical
and pinned by the existing tests. AGENTS.md now says time goes through chrono.

Follow-up to storytold/cadcraft#1785

* history: preserve out-of-range conversion failure in civil_from_days

civil_from_days used `i32::try_from(..).unwrap_or(0)` before calling
NaiveDate::from_num_days_from_ce_opt, so a CE day count past i32 (e.g.
from Day::from_days(i64::MAX)) passed a valid day 0 to chrono and read
as 0000-12-31 instead of the 0000-01-01 fallback the None arm gives for
every other out-of-range date. Convert with `.ok().and_then(..)` so both
the integer-conversion and NaiveDate-construction failures share the one
fallback. Regression test covers i64::MAX and i64::MIN.

Doc comments on the chrono-touched to_days/from_days/civil_from_days now
state the out-of-range behaviour (0 / 0000-01-01) for the docstring gate.

* Convert every i32 year, not only chrono's range

Day holds any i32 year, but NaiveDate stops near ±262143, so years like
262144 read as 0 / 0000-01-01. The Gregorian calendar repeats every 400
years: chrono now converts the day within its cycle and whole cycles are
added back, in both directions. Values match the pre-chrono formula
(tested for 262144, -262145, i32::MAX and i32::MIN). Nonexistent days
still read as 0, and day counts whose year doesn't fit i32 still read as 0000-01-01.

* Name the 400-year range extension as the one exception to the time rule

The rule said never to hand-roll calendar math, but the range fix
(shifting by whole 400-year Gregorian cycles so dates past chrono's year
range keep their old output) is cycle arithmetic, so the rule and the code
disagreed. The rule now names that exception: the library still computes
month, day and time, and only whole cycles are carried in the year.

* Say the library computes month and day; time-of-day is plain arithmetic

Narrows the range-extension wording: the date library computes the
calendar fields, and splitting seconds into hours, minutes and seconds
is ordinary arithmetic, which is what the code does.
2026-10-23 11:17:51 -04:00
..

Craft AI model

craft-ai-core is the data model and contract for AI in the Craft Apps: connections, models, capabilities, roles (tools), requests, jobs, consent, request history, the shared store's file format, and the declarative route spec. It has no I/O, no async runtime and compiles for wasm32-unknown-unknown. It depends only on serde and serde_json.

One connection is set up once (in the launcher or any app) and every app that has the tool uses it. Each app keeps its own interface for each tool.

Boundaries

  • Never crashes: the crate denies unwrap, expect, panic and friends, and the workspace forbids unsafe.
  • No secrets: a connection holds a reference to a secret (SecretRef), never the value. SecretString does not print.
  • No app code: an app is only an AppId string. The contract is described in docs/ai-shared-layer.md and the declarative route spec in docs/ai-declarative-adapter.md.

Later pull requests add the desktop half (secret stores and the shared store file), the client, the provider adapters and egui widgets as separate crates.

Platforms

All platforms, including wasm32-unknown-unknown (checked in CI). No I/O.

Usage

Depend on it for the types. Worked examples: tests/custom_tools.rs, tests/mock_conformance.rs, tests/spec_calls.rs; the contract is in docs/ai-shared-layer.md.