# Tempo Full Documentation Context (v4.2.0)
> This file contains the complete concatenated markdown documentation set for @magmacomputing/tempo (v4.2.0). It is intended for automated LLM context ingestion and RAG indexing.
---
# Document: 1-getting-started/ai-integration.md
# π€ AI & IDE Integration (`llms.txt`)
To ensure modern AI coding assistantsβsuch as **Cursor**, **VS Code (GitHub Copilot)**, **Antigravity**, **ChatGPT**, and **Claude**βgenerate accurate, idiomatically aligned Tempo code and minimize hallucinations, Tempo publishes an official, standardized [`llms.txt`](https://tempo.magmacomputing.com.au/llms.txt) rulebook.
By providing these rules to your AI assistant, your IDE will respect Tempo's strict immutability, zero-cost getters, native `Temporal` runtime expectations, and layout token syntax out-of-the-box.
---
## π Quick Setup by IDE / Tool
### 1. Cursor IDE
Add Tempo to Cursor's native documentation index:
1. Open **Cursor Settings** (`Cmd + ,` or `Ctrl + ,`).
2. Navigate to **Features** β **Docs**.
3. Click **+ Add new doc** and enter:
- **Name**: `Tempo`
- **URL**: `https://tempo.magmacomputing.com.au/llms.txt`
> [!TIP]
> Once added, type `@Tempo` in any Cursor chat or prompt window to inject exact API syntax rules into your conversation.
---
### 2. VS Code & GitHub Copilot
In VS Code, configure GitHub Copilot Chat by adding a `.github/copilot-instructions.md` file to the root of your workspace:
```markdown
# Tempo AI Rules
- Always use `Tempo` from `@magmacomputing/tempo`.
- Never instantiate legacy JavaScript `Date`. Tempo expects native `Temporal` or polyfill.
- All mutating methods (`.add()`, `.subtract()`, `.set()`) return a brand-new, frozen `Tempo` instance.
- Refer to https://tempo.magmacomputing.com.au/llms.txt for full layout token grammar.
```
When prompting Copilot Chat in VS Code:
```text
"Using https://tempo.magmacomputing.com.au/llms.txt, write a custom layout parser..."
```
---
### 3. Antigravity AI Assistant
In Antigravity, you can reference the live endpoint directly in your chat prompt or store it as a localized Knowledge Item (KI):
- Reference `@https://tempo.magmacomputing.com.au/llms.txt` in your prompt for instant context ingestion.
---
### 4. ChatGPT & Claude Projects
For web-based LLM interfaces, reference or copy-paste the full, un-truncated documentation context file:
π **[Full RAG Documentation Bundle (`llms-full.txt`)](https://tempo.magmacomputing.com.au/llms-full.txt)**
---
## π οΈ Prompting AI for Custom Layout Extensions
When asking AI assistants to generate custom layout patterns for non-standard date-time formats, instruct the model to use Tempo's configuration syntax (`Tempo.init({ registry: { layouts: { ... } } })`) and layout tokens (`{yy}`, `{mm}`, `{dd}`, `{hh}`, `{mi}`, `{ss}`).
### Sample Prompt:
> *"Using the rules from https://tempo.magmacomputing.com.au/llms.txt, register a custom Tempo layout for star-delimited dates (e.g., '08*04*2026') using `Tempo.init({ registry: { layouts: { ... } } })` and parse the date string using `new Tempo(...)`."*
### Generated Code (Actual Tempo Syntax):
```typescript
import { Tempo } from '@magmacomputing/tempo';
// 1. Register custom layout pattern using layout tokens
Tempo.init({
registry: {
layouts: {
star_date: '{mm}\\*{dd}\\*{yy}'
}
}
});
// 2. Parse date string matching the custom layout
const date = new Tempo('08*04*2026');
```
---
# Document: 1-getting-started/installation.md
# Installation Guide
`Tempo` is designed to be environment-agnostic. Whether you are building a server-side application, a modern browser project with ESM, or a performance-critical "Lite" bundle, `Tempo` provides a specific path for you.
## Temporal Polyfill Note
`Tempo` expects the host environment to provide `Temporal`, either through native runtime support or a user-supplied polyfill.
`Temporal` has reached Stage 4 of the [TC39 standards process](https://tc39.es/proposal-temporal/) (the committee that evolves JavaScript) and is shipping natively in modern environments (Deno 2.7+, Node.js 26+, Chrome 144+, Firefox 139+). Note that Safari/iOS currently do not support Temporal natively and require a polyfill. You can verify current browser support at [caniuse.com/temporal](https://caniuse.com/temporal). To avoid needlessly inflating package sizes for modern apps, `Tempo` does not bundle a `Temporal` polyfill by default.
::: warning
Node.js environments that ship `Temporal` behind a feature flag (`--harmony-temporal`) may have incomplete implementations. For stability, we strongly recommend using `@js-temporal/polyfill` instead of the native flag until you upgrade to an official unflagged release.
:::
You can check at runtime with a simple guard:
```js
if (typeof globalThis.Temporal === 'undefined') {
// Load your Temporal polyfill for this environment
}
```
Note: The examples below include a polyfill for demonstration purposes only, so the snippets work consistently across environments.
---
## π» Server & Bundlers (Node.js, Bun, Vite)
For most modern projects using a package manager, install Tempo via the npm registry.
```bash
npm install @magmacomputing/tempo # npm
yarn add @magmacomputing/tempo # yarn
pnpm add @magmacomputing/tempo # pnpm
bun add @magmacomputing/tempo # bun
```
### Usage
```javascript
import { Tempo } from '@magmacomputing/tempo';
const t = new Tempo('next Friday');
```
### Node.js (with Native Temporal)
Native unflagged `Temporal` support is available in Node.js 26+ and is enabled by default.
```bash
node my-app.js
```
### Node.js (with Polyfill)
The polyfill import shown here is conditional guidance, not required for all environments.
```bash
npm install @js-temporal/polyfill
```
```javascript
import '@js-temporal/polyfill';
import { Tempo } from '@magmacomputing/tempo';
const t = new Tempo('next Friday');
```
---
## π¦ Deno
Tempo is a native ESM package and works perfectly with Deno. You can add it via the `deno add` command which will resolve it from the npm registry.
As of Deno 2.7, the Temporal API is fully stabilized and enabled by default. You no longer need to pass the --unstable-temporal flag to use it.
```bash
deno add npm:@magmacomputing/tempo
```
### Usage
```javascript
import { Tempo } from "@magmacomputing/tempo";
const t = new Tempo();
```
---
## π Browser & Native Environments
Tempo provides multiple native browser distribution formats. Here is the quick breakdown of which approach to use:
- **Standard Usage** (No plugins): Use the Native ESM Bundle.
- **Plugins without a bundler**: Use **Smart CDNs** (Easiest setup) OR **Static CDNs** (Best production performance).
- **Plugins with a bundler** (Vite/Webpack): Do nothing. Your bundler handles the resolution automatically.
- **Non-ESM Environments**: Use the UMD Global Variable approach.
### 1. The Global Bundle (Standard Usage)
The easiest way to use Tempo natively in the browser is via the pre-optimized ESM bundle. It includes the entire core engine in a single file, eliminating network waterfall effects.
```html
```
```html
```
### 2. Smart CDNs (The "Best-of-Both-Worlds")
If you want the absolute easiest setup for **Tempo Plugins** natively in the browser, use an on-the-fly bundling CDN like [esm.sh](https://esm.sh). Smart CDNs act like a Node environmentβthey read the package resolution rules and resolve nested dependencies automatically, meaning you don't have to map any internal subpaths.
While you *could* import directly from the URL everywhere, the best practice is to use a tiny import map for your top-level packages to keep your application code clean:
```html
```
β οΈ Trade-offs of using Smart CDNs in Production
While `esm.sh` is fantastic for prototyping and reducing import map complexity, there are architectural trade-offs to consider before using it in a mission-critical production environment:
1. **Network Waterfalls:** The browser must fetch the module, parse it, and then fetch its nested dependencies sequentially. This can slow down page load times compared to a fully bundled application.
2. **Uptime Dependency:** You are introducing a critical third-party dependency into your runtime. If the CDN experiences routing issues, your application could break for end-users.
3. **Sub-dependency Version Floating:** `esm.sh` automatically resolves sub-dependencies based on semver constraints. If a sub-dependency introduces an accidental breaking change, it could affect your app.
4. **Suboptimal Tree Shaking:** The browser will download the entire module graph for that package; you cannot easily tree-shake unused exports as you can with a dedicated bundler like Vite or Webpack.
5. **Environment Parity:** Handling development versus production environments (like `process.env.NODE_ENV`) requires query parameters (e.g., `?dev`), which complicates deployment.
### 3. Static CDNs (Production-Ready)
For production environments where uptime and load speeds are critical, you should use a static file CDN (like jsdelivr). Because static CDNs serve raw files without compiling them on the fly, they are significantly faster and more reliable than Smart CDNs.
To use **Tempo Plugins** via a static CDN, you simply need to explicitly map the unified `plugin/sdk` subpath so the browser knows how to resolve the internal connections:
```html
```
> [!WARNING] Cache Busting
> The jsdelivr CDN aggressively caches major version tags (like `@4`). When relying on precise module resolution for plugins, it is highly recommended to use explicit patch versions (like `@4.0.0`) to avoid fetching mismatched or outdated sub-modules.
---
## π¦ Browser (Global Variable / Plugins)
If you aren't using ESM or just want a simple `
```
---
## π§ͺ Granular "Lite" Builds (Advanced)
If you are extremely concerned about bundle size, you can bypass the "Batteries Included" entry point and import only the core engine. You then manually opt-in to the modules you need.
```javascript
import { Tempo } from '@magmacomputing/tempo/core';
import { MutateModule } from '@magmacomputing/tempo/mutate';
// Opt-in to specific functionality
Tempo.use(MutateModule);
const t = new Tempo().add({ days: 1 });
```
::: warning
When using the Lite build, the `Tempo` class will have almost no methods (like `.add()`, `.set()`, or `.format()`) until you explicitly call `Tempo.use()` with the appropriate module.
:::
---
## π‘οΈ Versioning Policy
We recommend pinning your versions in production environments to ensure stability.
* **JSDelivr**: `https://cdn.jsdelivr.net/npm/@magmacomputing/tempo@4/...` (Locks to major version 4)
* **Latest**: `https://cdn.jsdelivr.net/npm/@magmacomputing/tempo/...` (Omit the version string to always receive the latest release. Note that JSDelivr will resolve a missing version tag to the latest published release).
---
## π€ AI & IDE Integration (`llms.txt`)
> [!TIP]
> Using **Cursor**, **VS Code (GitHub Copilot)**, **Antigravity**, **ChatGPT**, or **Claude**?
> Tempo publishes an official [`llms.txt`](https://tempo.magmacomputing.com.au/llms.txt) index to give AI assistants documented project context and reduce hallucinations regarding Tempo's syntax, token grammar, and immutability rules.
>
> π **[Read the dedicated AI & IDE Integration Guide](./ai-integration.md)** for step-by-step setup instructions for your IDE or tool.
---
# Document: 1-getting-started/tempo.cookbook.md
# Tempo Cookbook
A collection of recipes for solving common date and time challenges using Tempo.
## Table of Contents
1. [The Basics](#the-basics)
2. [Parsing Challenges](#parsing-challenges)
3. [Manipulation and Calculations](#manipulation-and-calculations)
4. [Timezones and Locales](#timezones-and-locales)
5. [Business Logic and Terms](#business-logic-and-terms)
6. [Formatting and Localization](#formatting-and-localization)
7. [Interoperability](#interoperability)
---
## The Basics
### How do I get the current date and time?
When invoked without arguments, the constructor initializes to the current date and time.
```typescript
const now = new Tempo();
console.log(now.toString()); // e.g. "2026-07-31T14:42:11+10:00[Australia/Sydney]"
```
### Get "Now" in UTC
```typescript
const utcNow = new Tempo({ timeZone: 'UTC' });
```
### How do I format a date for my UI?
Use the placeholder syntax in the `.format()` method.
```typescript
const t = new Tempo('2024-12-25');
t.format('{dd} {mon} {yyyy}'); // "25 December 2024"
t.format('{h12}:{mi}'); // "12:00am"
```
### How do I check if a date is valid?
```typescript
const t = new Tempo('invalid-date');
if (t.isValid) {
// ...
}
```
### Global Configuration
You can initialize global defaults that apply to all future `Tempo` instances.
```typescript
Tempo.init({
timeZone: 'UTC',
locale: 'en-GB',
silent: true
});
```
π **Learn More:** [Configuration Guide](../2-core-concepts/tempo.config.md)
---
## Parsing Challenges
### Parsing "Ambiguous" Digits (US vs UK)
Tempo intelligently resolves ambiguous dates like `04012026` based on your timezone.
```typescript
const us = new Tempo('04012026', { timeZone: 'America/New_York' });
console.log(us.format('{mon} {dd}')); // "April 01"
```
π **Learn More:** [Ambiguity Resolution Guide](../2-core-concepts/tempo.parse.md)
### Handling Relative Strings
Tempo natively understands human-readable offsets.
```typescript
new Tempo('yesterday');
new Tempo('next Friday');
new Tempo('2 weeks ago');
new Tempo('tomorrow afternoon');
```
π **Learn More:** You can seamlessly localize relative phrases (e.g. `next` to `prochain`) by reading the [Internationalized Parsing Guide](../2-core-concepts/tempo.parse.md#internationalized-parsing-locales).
### Parsing Unix Timestamps
Tempo handles both milliseconds (Number) and nanoseconds (BigInt).
```typescript
new Tempo(1716163200000); // Milliseconds
new Tempo(1716163200000000000n); // Nanoseconds
```
---
## Manipulation and Calculations
### Add or Subtract Time
Tempo instances are immutable; `add()` returns a new instance.
```typescript
const deadline = new Tempo().add({ days: 7, hours: 2 });
const past = new Tempo().add({ months: -1 });
// You can also step by semantic Terms using the `#` prefix!
const t1 = new Tempo('2024-05-15'); // Middle of Q2
const t2 = t1.add({ '#quarter': 1 }); // Middle of Q3: "2024-08-14" (approx)
```
### Jumping to Boundaries (`start`, `mid`, `end`)
The `.set()` method allows you to jump to the boundaries of native units (like months or years) or semantic Terms (using the `#` prefix). You can specify whether to land on the inclusive start, inclusive end, or the exact center.
```typescript
// Native Units
const monthStart = new Tempo().set({ month: 'start' });
// Semantic Terms (Lands on 30-Sep 23:59:59.999... Inclusive End)
const qtrEnd = new Tempo('2024-07-15').set({ '#quarter': 'end' });
// Lands on the arithmetic nanosecond midpoint of the period
const qtrMid = new Tempo().set({ '#quarter': 'mid' });
```
### Slick Object Mutations
You can navigate relative to your current date by using Slick Shorthand operators directly inside `.set()`. Use the snippet shorthand keys (`yy`, `mm`, `ww`, `dd`, `wkd`, etc.) and provide a string payload containing a directional modifier:
```typescript
const t = new Tempo('2024-05-20'); // Monday
t.set({ mm: '>2' }); // July 20th
t.set({ wkd: '>Fri' }); // May 24th
```
π **Learn More:** To read about advanced chaining, order-of-operations, and architectural limitations, see the [Slick Object Mutations Deep Dive](../2-core-concepts/tempo.mutate.md#slick-object-mutations).
### How long until a deadline? (`until`)
```typescript
const t = new Tempo();
const daysLeft = t.until('2025-01-01', 'days');
console.log(`${daysLeft} days remaining`);
```
### Relative Time (`since`)
Generate human-readable relative time strings instantly.
```typescript
const t = new Tempo('yesterday');
console.log(t.since()); // "1d ago"
```
---
## Timezones and Locales
### Convert Time to Another Zone
```typescript
const nyc = new Tempo('2024-05-20 10:00', { timeZone: 'America/New_York' });
const london = nyc.set({ timeZone: 'Europe/London' });
console.log(nyc.format('{hh}:{mi}')); // "10:00"
console.log(london.format('{hh}:{mi}')); // "15:00"
```
### Dynamic / Multi-Tenant Context (Functional Options)
Context options (`timeZone`, `locale`, `calendar`, `sphere`) accept supplier functions (`() => string`). Tempo resolves suppliers at instantiation time to construct an immutable, frozen instance:
```typescript
import { AsyncLocalStorage } from 'node:async_hooks';
const requestContext = new AsyncLocalStorage<{ timeZone: string; locale: string }>();
// Configure dynamic suppliers that evaluate against current request context
const t = new Tempo('now', {
timeZone: () => requestContext.getStore()?.timeZone || 'UTC',
locale: () => requestContext.getStore()?.locale || 'en-US'
});
```
π **Learn More:** See the [Configuration Guide](../2-core-concepts/tempo.config.md#dynamic--functional-context-evaluation) for details on functional options and immutability guarantees.
---
## Business Logic and Terms
### Is it the weekend?
```typescript
const t = new Tempo();
const isWeekend = t.dow >= 6; // Saturday = 6, Sunday = 7
```
### What Fiscal Quarter are we in?
Using the `qtr` Term plugin (`term.qtr` is a convenient alias for the full `term.quarter` property).
```typescript
const t = new Tempo();
console.log(`Current Quarter: ${t.term.qtr}`); // "Q1", "Q2", etc.
```
### Hemispheric Seasons
Tempo Terms are hemisphere-aware.
```typescript
const sydney = new Tempo('2024-07-01', { sphere: 'south' });
console.log(sydney.term.szn); // "Winter"
const london = new Tempo('2024-07-01', { sphere: 'north' });
console.log(london.term.szn); // "Summer"
// or even via the timeZone setting
console.log(new Tempo({ timeZone: 'America/New_York' }).term.szn); // "Summer"
console.log(new Tempo({ timeZone: 'Australia/Sydney' }).term.szn); // "Winter"
```
---
## Formatting and Localization
### Semantic Formatting
Use specific Term tokens like `{#quarter}` or `{#season}` to automatically embed a Term's label (or key) into a format string.
```typescript
const t = new Tempo();
console.log(t.format('We are currently in the {#quarter}')); // "We are currently in the First Quarter"
```
### Format Modifiers & Localization
Format strings support chained colon-modifiers (e.g., `:upper`, `:locale`, `:ord`) to dynamically change the presentation casing or delegate to the native `Intl` API. You can stack them to get the exact presentation required!
```typescript
const t = new Tempo('2024-05-15 15:30', { locale: 'fr-FR' });
t.format('{mon:upper}'); // "MAY" (English Default -> UpperCase)
t.format('{mon:long}'); // "mai" (Native French Intl output via styling bridge)
t.format('{mon:long:upper} {dd}'); // "MAI 15" (Native French Intl output)
```
π **Learn More:** See the [Smart Formatting Guide](../2-core-concepts/tempo.format.md) for the complete list of available modifiers.
::: tip
**Tired of typing styling modifiers?**
If you find yourself repeatedly writing `:long` or `:short` for the same localized date structure, save it to the global **FORMATS** registry! This creates a clean, reusable shortcut:
```typescript
Tempo.init({
locale: 'fr-FR',
registry: {
formats: {
'ui-date': '{wkd:long}, {dd:raw} {mon:long} {yyyy}'
}
}
});
t.format('ui-date'); // Resolved with all modifiers intact!
```
*Note: Format keys are resolved case-sensitively from the global `registry.formats` object. If the requested key is not found, Tempo will simply treat the provided string as a literal layout string rather than throwing an error.*
:::
π **Learn More:** To build custom zero-overhead logic evaluators (like Fiscal Years or native Intl bridges), read the [Custom Format Tokens Deep Dive](../2-core-concepts/tempo.format.md#custom-format-tokens).
π **Learn More:**
- [Smart Formatting Guide](../2-core-concepts/tempo.format.md)
- [The Role of Locale](../4-advanced-reference/tempo.locale.md)
- [Smart Parsing Guide](../2-core-concepts/tempo.parse.md)
---
### Ticker Plugin
The Ticker engine is a premium plugin for precisely driving business logic (like recurring billing or reporting cycles) on specific date boundaries.
```typescript
// Drive internal reporting precisely when a new quarter begins
await using quarterly = Tempo.ticker({ '#quarter': 1 });
for await (const t of quarterly) {
generateReport(t.term.qtr);
}
```
π **Learn More:** See the [Ticker Plugin Documentation](../../../plugins/ticker/doc/index.md) for detailed configuration, term-driven intervals, and `await using` syntax requirements.
---
## Interoperability
### Converting to / from Native `Date`
```typescript
const date = new Tempo().toDate();
const tempo = new Tempo(new Date());
```
### Converting to `Temporal` Objects
```typescript
const zdt = new Tempo().toDateTime(); // Temporal.ZonedDateTime
const instant = new Tempo().toInstant(); // Temporal.Instant
const pdt = new Tempo().toPlainDate(); // Temporal.PlainDate
```
### Sorting an array of Tempos
```typescript
const dates = [new Tempo('tomorrow'), new Tempo('yesterday'), new Tempo('today')];
dates.sort(Tempo.compare); // Sorts chronologically
```
---
# Document: 2-core-concepts/tempo.cache.md
# Cache Management Guide
**Tempo** includes a centralized, high-performance **`BoundedCache`** singleton accessible via `Tempo.cache`. It provides dual-layer resolution for dynamic relative dates (with LRU eviction and TTL expiration) and static business glossaries (immortal keys).
---
## ποΈ Centralized Cache Architecture
All date resolution cachingβwhether triggered by core `Tempo` parsing or the Tempo AI plugin (`parseAI`, `formatAI`, `contextAI`)βis managed centrally by `Tempo.cache`.
::: info Cache Behavior: Core Tempo vs. Tempo AI Plugin
* **Core Tempo**: Caching is **opt-in**. Core date parsing executes at sub-microsecond speeds using standard regex matching. `Tempo.cache` is consulted when you seed a static glossary or enable caching.
* **Tempo AI Plugin**: Caching is **automatic**. To eliminate network latency (~500ms+) and avoid redundant LLM API billing, AI functions (like `parseAI`) automatically check `Tempo.cache` before sending network requests and cache every successful LLM resolution.
:::
### Cache Topology & Configuration
You can configure global cache parameters using `Tempo.init()`:
```typescript
import { Tempo } from '@magmacomputing/tempo';
Tempo.init({
cache: {
maxSize: 1000, // Maximum number of entries before LRU eviction (default: 1000)
ttl: 24 * 60 * 60 * 1000 // Time-to-live in milliseconds (default: 24 hours)
}
});
```
* **Capacity Management (LRU):** When the cache reaches `maxSize`, the Least Recently Used dynamic entry is automatically evicted.
* **TTL Expiration:** Dynamic entries older than `ttl` are automatically purged upon lookup.
* **Static Glossary Isolation:** Static entries added to the glossary are **exempt** from both LRU eviction and TTL expiration.
---
## π Seeding & Appending Glossaries
You can seed static business terms into `Tempo.cache` using a native JavaScript `Map` or via `Tempo.init({ cache: map })`:
```typescript
const businessGlossary = new Map([
['fiscal year start 2026', '2026-07-01T00:00:00Z'],
['q3 board review', '2026-09-15T09:00:00Z']
]);
// Appends entries to Tempo.cache as static, immortal terms
Tempo.init({ cache: businessGlossary });
```
::: tip Non-Destructive Appending
Passing a `Map` or custom key-value pairs to `Tempo.init({ cache })` or `initAI({ cache })` **appends** to the existing cache without clearing previously cached terms or resetting cache capacity settings.
:::
---
## π‘ When to Use What: Glossary vs. Alias vs. Snippet/Layout
Tempo provides multiple mechanisms for augmenting parsing intelligence. Choosing the right pattern depends on whether your logic is static, dynamic, structural, or string replacement:
| Mechanism | Tier / Location | Evaluation Model | Best Used For... |
| :--- | :--- | :--- | :--- |
| **Glossary** (`Tempo.cache`) | Core Engine | Zero-cost `O(1)` Map lookup | Pre-calculated static ISO date/time strings or exact business dates. |
| **Aliases / Events / Periods** (`registry.events` / `periods`) | Registry Engine | Dynamic function or target string | Computing dynamic business dates (e.g. `'deadline' => () => this.add({ days: 30 })`). |
| **Snippet / Layouts** (`registry.snippets` / `layouts`) | Parser Planner | Regex pattern matcher | Structural natural language formats (e.g. `yyyy/mm/dd` or custom date tokens). |
### Decision Tree
1. **Use a Glossary (`Tempo.cache`)** when you have fixed, pre-resolved ISO dates for specific terms (e.g., `'eoy 2026'` -> `'2026-12-31T23:59:59Z'`). It offers instant `O(1)` resolution without invoking the regex parser.
2. **Use an Alias (`registry.events` / `periods`)** when you need dynamic rules calculated relative to the current date/time (e.g., `'market-close'` -> `'16:00'` or `'deadline'` -> `30 days from now`).
3. **Use a Snippet or Layout (`registry.snippets` / `layouts`)** when parsing custom input structures with variable numbers or tokens (e.g. `"2026-W05"` or `"Quarter 3, 2026"`).
---
## π€ AI Plugin Cache Integration (`@magmacomputing/tempo-plugin-ai`)
The `@magmacomputing/tempo-plugin-ai` plugin works hand-in-hand with `Tempo.cache` to reduce LLM API calls and costs across AI functions:
```typescript
import { Tempo } from '@magmacomputing/tempo';
import { parseAI, initAI } from '@magmacomputing/tempo-plugin-ai';
initAI({ providers: [...] });
// First lookup: Triggers LLM call -> Stores ISO result in Tempo.cache
const t1 = await parseAI("The penultimate Tuesday before Thanksgiving in 2026");
// Second lookup: Instantly resolves from Tempo.cache (O(1) local hit, $0 cost)
const t2 = new Tempo("The penultimate Tuesday before Thanksgiving in 2026");
```
### Two-Tier Resolution Architecture
1. **Date-Salted Relative Cache**: Relative queries (e.g. `"next Tuesday"`) are salted with the anchor date so cached entries remain valid for the given day.
2. **Static Glossary Fallback**: Business glossary terms seeded via `initAI({ cache })` or `Tempo.init({ cache })` are checked first, providing zero-latency resolution without ever contacting the LLM.
---
# Document: 2-core-concepts/tempo.config.md
# Configuration Guide
**Tempo** provides a flexible, multi-tiered configuration system. Settings are applied in a specific order of precedence, allowing you to set broad defaults that can be refined at the application or instance level.
## Precedence Hierarchy
Settings are loaded in the following order (where later stages override earlier ones):
1. **Library Defaults**: Sensible out-of-the-box baseline.
2. **Persistent Storage**: Sticky user preferences (which merge into Defaults).
3. **Global Discovery**: Enterprise-level setup discovered via `Symbol.for('$Tempo')`.
4. **Library Extension**: Dynamic feature registration via `Tempo.use()`.
5. **Explicit Initialization**: Baseline configuration via `Tempo.init()`.
6. **Instance Constructor**: Specific overrides for a single `new Tempo()` call.
---
## π Best Practice: The `tempo.config.ts` Pattern
Rather than scattering `Tempo.init()` or `Tempo.use()` calls throughout your application, the recommended best practice is to centralize your environment setup into a single `tempo.config.ts` (or `.js`) file.
This mirrors modern ecosystem standards (like `vite.config.ts` or `tailwind.config.js`) and ensures that plugins, timezones, and custom aliases are consistently applied before any domain logic executes.
::: info
**Target Environment**: This automatic configuration discovery pattern relies on Node.js file system capabilities and is designed for Server, Fullstack, or Bundled environments (like Vite or Webpack). If you are using Tempo via a `
```
---
## 5. Authoring Custom Plugins
Interested in creating your own plugin? Check out the [Creating Custom Plugins Guide](../../../tempo/doc/3-extending-tempo/tempo.extension.md) and [Plugin Ecosystem Catalog](../../../tempo/doc/3-extending-tempo/ecosystem.md) to learn how to register custom getters, term definitions, and dynamic proxies.
---
# Document: 9-plugins/ai.architecture.md
# Provider Architecture & Security
The `@magmacomputing/tempo-plugin-ai` plugin is designed to be highly flexible, supporting both direct Bring Your Own Key (BYOK) integrations for backend systems, and Proxied integrations for frontend clients.
## Bring Your Own Key (BYOK) & Zero-Config Discovery
For Node.js, Deno, and Bun backends, `@magmacomputing/tempo-plugin-ai` supports **Zero-Config Auto-Discovery**. If standard environment variables (`GROQ_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, `MISTRAL_API_KEY`) are present, calling `initAI()` is optionalβthe plugin will automatically discover credentials and wire up provider defaults lazily on first function call.
Alternatively, you can supply your API keys and execution options explicitly via `initAI`:
```typescript
import { initAI } from '@magmacomputing/tempo-plugin-ai';
initAI({
providers: [
...(process.env.GROQ_API_KEY ? [{ id: 'groq', key: process.env.GROQ_API_KEY }] : []),
...(process.env.GEMINI_API_KEY ? [{ id: 'gemini', key: process.env.GEMINI_API_KEY }] : []),
...(process.env.OPENAI_API_KEY ? [{ id: 'openai', key: process.env.OPENAI_API_KEY }] : [])
]
});
```
### Advanced Configuration (Custom Models & LLM Options)
By default, standard providers automatically map to their optimal APIs and default models.
However, you can explicitly override URLs, models, and inject arbitrary LLM parameters (like `temperature`) for power-user control!
```typescript
initAI({
providers: [
// 1. Enterprise Azure OpenAI (via Entra ID Bearer token or backend proxy wrapper)
// Note: BYOK requests send 'Authorization: Bearer '. When connecting to Azure OpenAI,
// supply an Entra ID bearer token as provider.key or route through an Azure API gateway.
...(process.env.AZURE_ENTRA_BEARER_TOKEN ? [{
id: 'openai',
key: process.env.AZURE_ENTRA_BEARER_TOKEN,
url: 'https://my-enterprise.openai.azure.com/v1/chat/completions',
model: 'your-enterprise-model',
options: { temperature: 0.2, seed: 42 }
}] : []),
// 2. Local Open-Source Models (e.g. Ollama)
{
id: 'local',
key: 'no-key-needed',
url: 'http://localhost:11434/v1/chat/completions',
model: 'your-local-model',
options: { timeout: 5000 } // Custom provider-level timeout (5s)
}
]
});
```
### Per-Request Lazy Resolution & Fallback Defaults
When dispatching requests via `transport.ts`, all provider fields (`key`, `url`, `model`) and execution context (`timeZone`, `locale`, `calendar`, `sphere`) are resolved lazily just-in-time using functional evaluation (`evaluate` / `evaluateAsync`):
1. **Explicit Dynamic Suppliers**: If a supplier function was provided (e.g. `key: async () => await getRotatedKey()`), it is called per-dispatch.
2. **Built-in Fallbacks**: If a property is omitted or resolves to `undefined`, the transport layer seamlessly cascades to the compiled `DEFAULT_PROVIDERS` templates, remote manifest endpoints, and auto-discovered environment variables.
3. **No Configuration Mutation**: The dynamic resolution runs ephemerally per HTTP dispatch without mutating or locking shared global provider state.
### Dynamic Provider Manifests & Remote Endpoint Trust
By default, `@magmacomputing/tempo-plugin-ai` lazily fetches provider defaults (model IDs, endpoints, token parameter keys) from `https://tempo.magmacomputing.com.au/providers.v1.json` once per application lifecycle.
- **Remote Manifest Trust & Endpoint Enforcement**:
- `remoteConfigUrl` is restricted to fixed trusted hosts (`tempo.magmacomputing.com.au` or trusted internal HTTPS endpoints).
- Any dynamic `provider.url` values received from the manifest or dynamically returned via `fetchDefaults` are strictly validated against an enforced provider host allowlist (or must use verified HTTPS/localhost origins) before `getResolvedProviderDefaults()` merges them into runtime provider configurations. Untrusted or unauthenticated endpoints are rejected and stripped before merging.
- **Validation on `fetchDefaults` Hook**: The exact same host allowlist and HTTPS origin verification is enforced when resolving custom provider options via the `fetchDefaults` callback. Any dynamic hook attempting to return unauthenticated or disallowed host URLs will have the `url` property safely discarded.
- **Async Resolution & Promise Lifecycle**: `initAI()` returns a `Promise`.
- **Synchronous Fire-and-Forget**: Calling `initAI(...)` synchronously without `await` immediately initializes system state with compiled local provider defaults (`DEFAULT_PROVIDERS`). You can execute `parseAI()` immediately on the next line without blocking. The remote manifest is fetched in the background and transparently updates provider defaults once received.
- **Guaranteed Remote Resolution**: If your application strictly requires remote provider defaults to be resolved before executing your first AI request, you can `await initAI(...)`:
```typescript
// Await guaranteed remote manifest completion before proceeding
await initAI({
providers: [{ id: 'groq', key: process.env.GROQ_API_KEY! }]
});
```
- **Fail-Open & Air-Gapped Fallback**: If the network request fails, times out (1500ms limit), or the application is running offline or in an air-gapped environment, `initAI()` automatically and silently falls back to compiled local defaults (`DEFAULT_PROVIDERS`).
- **Disabling Remote Manifest**: Pass `remoteConfigUrl: false` to disable remote manifest fetching entirely:
```typescript
initAI({
providers: [{ id: 'groq', key: process.env.GROQ_API_KEY! }],
remoteConfigUrl: false // Disable remote manifest fetching
});
```
### Frontend Security Warning
> [!CAUTION]
> **Never** expose a raw LLM API key in a client-side browser bundle (like React, Vue, or Svelte) or store it in browser storage (`localStorage`, `sessionStorage`, `IndexedDB`, or browser cache). Any Cross-Site Scripting (XSS) vulnerability, compromised NPM dependency, or malicious browser extension can inspect client-side memory/storage and extract secret keys, leading to quota drainage, unexpected billing spikes, or account bans. BYOK provider keys are *only* safe on backend servers and edge workers.
## Browser & Client-Side Proxy Architecture
To execute AI functions within client-side browser applications safely, route requests through a secure self-hosted backend proxy or unified AI Gateway (such as a Cloudflare Worker, Next.js API route, Express server, OpenRouter, Portkey, or LiteLLM):
```mermaid
flowchart LR
subgraph Browser ["Client-Side Browser (SPA)"]
Client["Tempo AI Plugin (initAI / parseAI / diffAI)"]
end
subgraph Backend ["Self-Hosted Proxy / AI Gateway"]
Proxy["Your Backend API / AI Gateway β’ User Authentication & Rate Limits β’ Secure Secret Management"]
end
subgraph Providers ["Upstream LLM Providers"]
LLM["Groq β’ OpenAI β’ Gemini β’ Anthropic"]
end
Client -- "1. HTTPS (TLS 1.2+) Bearer Token / Auth Header" --> Proxy
Proxy -- "2. HTTPS (TLS 1.2+) Private Provider API Key" --> LLM
LLM -- "3. HTTPS (TLS 1.2+) Raw JSON Completion" --> Proxy
Proxy -- "4. HTTPS (TLS 1.2+) Validated Payload" --> Client
```
### 1. Browser Configuration Example
Configure `initAI` in your browser code to target your backend proxy or AI Gateway URL:
```typescript
import { initAI, parseAI } from '@magmacomputing/tempo-plugin-ai';
// Safe for browser deployment: No private LLM API keys are bundled
await initAI({
providers: [
{
id: 'my-gateway',
url: 'https://api.mycompany.com/v1/ai/chat/completions', // Your secure proxy endpoint
key: userSessionToken, // Short-lived user Bearer JWT token
model: 'llama-3.3-70b-instruct'
}
]
});
// All Tempo AI functions now execute securely through your proxy
const date = await parseAI("Team standup next Wednesday at 9:30am");
```
### 2. Backend Proxy Handler Example (Next.js / Cloudflare Worker / Express)
Your backend endpoint receives the request, validates the user's session, enforces ingress quotas, attaches your private LLM API key, and forwards the validated payload to the upstream provider:
```typescript
// Example: Next.js API Route / Cloudflare Worker / Express Proxy Handler
export async function POST(req: Request, env?: { GROQ_API_KEY?: string }) {
// 1. Authenticate user session
const authHeader = req.headers.get('Authorization');
const session = await validateUserSession(authHeader);
if (!session) {
return new Response('Unauthorized', { status: 401 });
}
// 2. Ingress validation & per-user quota enforcement
const body = await req.json();
if (typeof body?.prompt !== 'string' || body.prompt.length > 4096) {
return new Response('Invalid prompt or payload exceeds size limit', { status: 400 });
}
if (!checkUserRateLimit(session.userId)) {
return new Response('Too Many Requests', { status: 429 });
}
// 3. Resolve API key (Cloudflare Worker env binding or Node/Next.js process.env)
const apiKey = env?.GROQ_API_KEY || (typeof process !== 'undefined' ? process.env?.GROQ_API_KEY : undefined);
if (!apiKey) {
return new Response('Provider key configuration missing', { status: 500 });
}
// 4. Construct upstream fetch with bounded timeout and cleanup
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10s upstream limit
try {
const upstreamResponse = await fetch('https://api.groq.com/openai/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify({
model: 'openai/gpt-oss-120b',
messages: body.messages,
temperature: 0.1,
}),
signal: controller.signal
});
// 5. Return provider payload to client
const data = await upstreamResponse.json();
return new Response(JSON.stringify(data), {
status: upstreamResponse.status,
headers: { 'Content-Type': 'application/json' }
});
} catch (err: any) {
if (err.name === 'AbortError' || controller.signal.aborted) {
return new Response(JSON.stringify({ error: 'Upstream provider gateway timeout' }), {
status: 504,
headers: { 'Content-Type': 'application/json' }
});
}
return new Response(JSON.stringify({ error: 'Upstream connection failure' }), {
status: 502,
headers: { 'Content-Type': 'application/json' }
});
} finally {
clearTimeout(timeoutId);
}
}
```
---
## π Security & Privacy Guarantees
> [!TIP]
> For an in-depth breakdown of our automated PII redaction, Smart Debug infrastructure, and tamper-resistant Proxy introspection, see the dedicated **[Security & Privacy Architecture Guide (`security.md`)](./ai.security.md)**.
Whether running directly on backend servers (Node.js, Deno, Bun, Edge Workers) or through a client-side browser proxy, `@magmacomputing/tempo-plugin-ai` enforces strict security and privacy standards:
### 1. Transport Security (HTTPS / TLS)
All network communicationβboth from client to proxy and from proxy/server to upstream LLM endpointsβis required over HTTPS. Negotiated TLS versions (such as TLS 1.2 or TLS 1.3) depend on deployment environment and server configuration unless strictly enforced by your reverse proxy. Plaintext HTTP endpoints are disallowed in production environments (permitted only on `localhost` during local development).
### 2. Ephemeral Processing & Cache Retention Controls
Temporal processing payloads (dates, times, context snippets, prompts) are processed ephemerally. The plugin does not send telemetry or store user prompt data on external analytics servers. However, functions supporting caching (e.g. `parseAI`, `formatAI`, `diffAI`) may retain prompt-derived cache keys and final results in local memory or configured custom cache adapters according to the resolved TTL. Requests requiring zero cache retention must explicitly pass `cache: false`.
### 3. In-Memory Credential Redaction & Immutability
* **Automated Key Redaction**: Calling `getAiConfig()` returns a sanitized, deeply read-only snapshot of active configurations with all provider `key` values replaced with `[REDACTED]`, preventing accidental exposure in log files, APM traces, or crash dumps.
* **Frozen Metadata**: All diagnostic metadata attached to `Tempo` instances via `.ai` and all structured AI result objects (`TempoAiFormatResult`, `TempoAiExtractResult`, `TempoAiDiffResult`, `TempoScheduleResult`, `TempoRecurrenceResult`, `TempoContext`) are deeply frozen with `Object.freeze()` and guarded via runtime `Proxy` wrappers, protecting against direct runtime mutation.
### 4. Deterministic Schema Guardrails & Confidence Range Verification
All LLM prompts are paired with rigid, machine-verifiable JSON schemas. Responses undergo strict boundary validation, regex parsing, confidence range verification (`0.0` to `1.0`), and schema verification before any native `Tempo` date object or structured result payload is instantiated. If an LLM returns malformed, out-of-range, or unparseable data, the plugin throws a typed `TempoAiError` or triggers automatic provider fallback rather than silently propagating corrupt data.
### 5. Partitioned Caching & Fail-Open Storage Resilience
* **Strict Cache Key Partitioning**: Caches are namespaced and hashed (`ai:::...`) with timezone, locale, calendar, and anchor date isolation to prevent cross-tenant or cross-regional cache poisoning.
* **Fail-Open Protection**: If a custom distributed cache adapter (e.g. Redis or Cloudflare KV) encounters network disruption or errors, the plugin automatically fails open to direct LLM resolution, preserving application uptime.
## Multi-Provider Execution Strategies (`AiMode`)
Because third-party APIs can experience downtime, latency spikes, or quota exhaustion, `@magmacomputing/tempo-plugin-ai` provides six dedicated dispatch strategies configured via `AiMode` (or string literals):
| Strategy | Enum (`AiMode`) | Primary Advantage | Typical Use Case |
| :--- | :--- | :--- | :--- |
| **Fallback** *(Default)* | `AiMode.Fallback` | Minimum token cost (sequential cascade) | Default production baseline & background tasks |
| **Hedged** | `AiMode.Hedged` | Ultra-fast latency with low token overhead (~1.15x) | Latency-sensitive interactive search & chatbots |
| **RoundRobin** | `AiMode.RoundRobin` | Cyclic rotation across multi-key pools | High-throughput batch ingestion across API keys |
| **Adaptive** | `AiMode.Adaptive` | Telemetry-driven rate-limit avoidance | Multi-tier provider pools with mixed quotas |
| **Race** | `AiMode.Race` | Absolute minimum response latency | Real-time typeahead & autocomplete |
| **Consensus** | `AiMode.Consensus` | Cross-LLM verification & hallucination trapping | High-stakes legal, financial, and contract dates |
π For detailed architecture breakdowns, Mermaid decision trees, and configuration guides for each mode, see the **[Multi-Provider Execution Modes Guide (`modes.md`)](./ai.modes.md)**.
### Provider ID Canonicalization
Provider IDs are normalized case-insensitively during `initAI` lookup (e.g. `'Gemini'`, `'gemini'`, `'OpenAI'`), automatically applying default endpoints and models while preserving the caller's registered identifier for logging and metadata.
---
# Document: 9-plugins/ai.context.md
# `contextAI` β Context & Regional Inference
`contextAI()` is designed to analyze unstructured or ambiguous text (e.g. user biographies, locations, or email bodies) and infer their regional configuration settings. It resolves these properties to a standard configuration object containing timezone, locale, calendar system, and hemisphere.
> [!TIP]
> **Smart Debug Telemetry**: Enabling `debug: true` activates operational logs. In production environments (`NODE_ENV === 'production'`), PII (emails, phone numbers, auth tokens) is automatically sanitized and masked in console output and terminal inspections. See the [Security & Privacy Architecture Guide](./ai.security.md).
This is highly useful for user onboarding settings, automatic context mapping for calendar syncs, and geolocating inputs dynamically without maintaining static geographic mapping tables.
---
## Basic Usage
> [!NOTE]
> `@magmacomputing/tempo-plugin-ai` features zero-config auto-discovery. If provider keys exist in your environment (`GROQ_API_KEY`, `OPENAI_API_KEY`, etc.) or `tempo.config.json`, calling `initAI()` is **completely optional**.
```typescript
import { contextAI } from '@magmacomputing/tempo-plugin-ai';
// 1. Infer contextual settings directly from unstructured text (auto-discovers provider keys)
const context = await contextAI("I'm a photographer based in Sydney, Australia.");
console.log(context.timeZone); // "Australia/Sydney"
console.log(context.locale); // "en-AU"
console.log(context.calendar); // "gregory"
console.log(context.sphere); // "south"
console.log(context.confidence);// 0.98
```
---
## Configuration Options (`AiContextOptions`)
| Option | Type | Description |
| :--- | :--- | :--- |
| **`force`** | `boolean` | If true, bypasses the cache to initiate a fresh LLM query. |
| **`cache`** | `boolean` | If false, disables writing to and reading from cache adapters. |
| **`cacheAdapter`** | `AiCacheAdapter` | Custom cache engine (e.g., Redis) for caching results on this request. |
| **`ttl`** | `number` | Time-to-live override in milliseconds for cached results. |
| **`minConfidence`**| `number` | Minimum confidence score threshold (0.0 to 1.0) required to return a valid slot. Throws `TempoAiError(422)` if lower. |
| **`mode`** | `AiMode` | Concurrency routing strategy (`fallback`, `race`, `consensus`, `hedged`, `roundrobin`, `adaptive`). Refer to the [Multi-Provider Execution Modes Guide](./ai.modes.md). |
| **`providers`** | `AiProvider[]` | Per-request provider configuration overrides. |
| **`timeout`** | `number` | Per-request timeout in milliseconds (overrides provider and global timeouts). |
| **`hedgeDelay`** | `number` | Delay in milliseconds before initiating speculative hedging in `AiMode.Hedged` (default: `800ms`). |
| **`debug`** | `boolean` | If true, logs prompt context and cache operations to console (automatically PII-sanitized in production). |
| **`softErrors`** | `boolean` | If true, returns `TempoAiError` into array index position instead of rejecting the entire batch query. |
---
## Result Schema (`TempoContext`)
```typescript
export interface TempoContext {
/** Inferred IANA time zone identifier (e.g. 'America/New_York') */
timeZone: string;
/** Inferred BCP 47 language/region tag (e.g. 'en-US') */
locale: string;
/** Inferred Unicode calendar system type (e.g. 'gregory') */
calendar: string;
/** Inferred hemisphere, or undefined if ambiguous */
sphere?: 'north' | 'south' | undefined;
/** Confidence rating from 0.0 (unparseable) to 1.0 (certain) */
confidence: number;
/** Resolution source ('cache' or provider ID like 'groq', 'gemini', 'openai') */
provider: string;
/** Step-by-step reasoning or justification provided by the engine/LLM */
reasoning?: string | undefined;
}
```
---
## Key Architectural Behaviors
### 1. Workspace Baseline Context
`contextAI` inspects the host runtime or current `Tempo` configuration (`Tempo.options.timeZone`, `Tempo.options.locale`, etc.) as a fallback baseline. If an input like `"at home"` is provided, the LLM will ground its inference in the workstation's baseline defaults.
### 2. Strict Confidence Thresholds
Using `minConfidence`, developers can guarantee that low-certainty or completely ambiguous inputs (e.g., `"in the park"`) throw a `TempoAiError(422)` rather than silently returning guessed context parameters:
```typescript
const context = await contextAI("meeting somewhere online", { minConfidence: 0.9 });
// Throws TempoAiError(422): Inferred context confidence (0.4) is below the required threshold of 0.9.
```
### 3. Timezone Validation
Before returning, the returned IANA timezone string is dynamically validated against the runtime's native JavaScript `Intl` API. If the LLM returns an unsupported or fake timezone identifier, `contextAI` throws a `TempoAiError(422)` to prevent application runtime failures.
### 4. Parallel Batch Processing
You can pass an array of strings to process multiple contexts concurrently:
```typescript
const [context1, context2] = await contextAI([
"Working from Kyoto",
"Living in Melbourne"
]);
```
### Combining `contextAI` with `parseAI` (The Pivot Flow)
Often, a user will mention their location in one sentence and a relative time in another. You can chain these APIs together to form a seamless date-resolution pipeline:
```typescript
import { contextAI, parseAI } from '@magmacomputing/tempo-plugin-ai';
// 1. Deduces the context
const ticketContext = await contextAI("customer issue from our London office");
// ticketContext = { timeZone: 'Europe/London', locale: 'en-GB', sphere: 'north' }
// 2. Feed the output context directly as options into parseAI
const resolutionTime = await parseAI("issue occurred on 04/05/2026 at 3 PM", ticketContext);
// 1. Correctly parses 04/05 to May 4th (UK format) rather than April 5th.
// 2. Adjusts to BST/GMT (Europe/London).
```
---
# Document: 9-plugins/ai.diff.md
# `diffAI` β Contextual & Business Date Deltas
`diffAI()` expresses the temporal delta between two `Tempo` instances, timestamps, or date strings in human, business, or operational terms.
While core `Tempo` provides precise numeric calculations (`start.until(end, 'day')`), `diffAI` bridges the gap to domain-specific narrative explanations (e.g. accounting working days, delivery SLAs, relative countdowns, and sprint planning summaries) backed by arithmetic grounding.
---
## Basic Usage
```typescript
import { Tempo } from '@magmacomputing/tempo';
import { initAI, diffAI } from '@magmacomputing/tempo-plugin-ai';
// 1. Initialize AI providers
await initAI({
providers: [
{ id: 'groq', key: process.env.GROQ_API_KEY }
]
});
const start = new Tempo('2026-08-01T09:00:00Z'); // Saturday
const end = new Tempo('2026-08-10T17:00:00Z'); // Next Monday
const diff = await diffAI(start, end, 'explain in terms of business working days');
console.log(diff.formatted); // "5 business days (approx. 224 calendar hours)"
console.log(diff.businessDays); // 5
console.log(diff.days); // 9.33
console.log(diff.hours); // 224
console.log(diff.confidence); // 0.96
```
---
## Configuration Options (`AiDiffOptions`)
| Option | Type | Description |
| :--- | :--- | :--- |
| **`holidays`** | `string[]` | Explicit array of holiday dates (`'YYYY-MM-DD'`) to exclude from business days. |
| **`region`** | `string` | Regional context (e.g., `'AU-NSW'`, `'US'`) passed to LLM grounding. |
| **`timeZone`** | `string` | Target IANA timezone for date boundaries and calculation. |
| **`locale`** | `string \| string[]` | Locale for narrative language formatting. |
| **`force`** | `boolean` | If true, bypasses the cache to initiate a fresh LLM query. |
| **`cache`** | `boolean` | If false, disables writing to and reading from cache adapters. |
| **`cacheAdapter`** | `AiCacheAdapter` | Custom cache engine (e.g., Redis) for caching results on this request. |
| **`ttl`** | `number` | Time-to-live override in milliseconds for cached results. |
| **`minConfidence`**| `number` | Minimum confidence score threshold (0.0 to 1.0) required. Throws `TempoAiError(422)` if lower. |
| **`mode`** | `AiMode` | Concurrency routing strategy (`fallback`, `race`, `consensus`, `hedged`, `roundrobin`, `adaptive`). Refer to the [Multi-Provider Execution Modes Guide](./ai.modes.md). |
| **`softErrors`** | `boolean` | If true, returns `TempoAiError` into array indices instead of rejecting batch queries. |
---
## Result Schema (`TempoAiDiffResult`)
```typescript
export interface TempoAiDiffResult {
/** Human-friendly, contextual narrative text summarizing the difference */
formatted: string;
/** Total calendar days between start and end */
days?: number | undefined;
/** Total elapsed calendar hours between start and end */
hours?: number | undefined;
/** Total business working days (excluding weekends and matching holidays) */
businessDays?: number | undefined;
/** List of holiday dates (YYYY-MM-DD) encountered within the interval */
holidays?: string[] | undefined;
/** Confidence rating from 0.0 (unparseable) to 1.0 (certain) */
confidence: number;
/** Resolution source ('cache' or provider ID like 'groq', 'gemini', 'openai') */
provider: string;
/** Step-by-step reasoning or justification provided by the engine/LLM */
reasoning?: string | undefined;
}
```
---
## Key Architectural Behaviors
### 1. Native Grounding Context
To guarantee arithmetic precision and prevent LLM hallucinations, `diffAI` natively computes exact calendar days, elapsed hours, and business working days (excluding Saturdays, Sundays, and provided holidays) using `Tempo` before dispatching to the LLM. These metrics are supplied as grounding constraints in the system prompt.
### 2. Public Holiday Exclusions
You can supply regional public holidays (e.g., from `@magmacomputing/tempo-fns` via `getPublicHolidays`) to automatically adjust business day counters:
```typescript
import { getPublicHolidays } from '@magmacomputing/tempo-fns';
const holidays = (await getPublicHolidays(2026, 'AU')).map(h => h.date);
const result = await diffAI('2026-12-24', '2027-01-04', 'calculate net business days', {
holidays,
region: 'AU',
});
console.log(result.businessDays); // 5 (Christmas, Boxing Day, New Year's Day excluded)
console.log(result.holidays); // ['2026-12-25', '2026-12-26', '2027-01-01']
```
### 3. Directional Awareness & Reverse Intervals
When the end date is earlier than the start date, `diffAI` indicates past/backward direction and provides negative business day counts (e.g. `-5` business days), allowing the LLM to format relative past descriptions (e.g. `"5 business days ago"`).
### 4. Parallel Batch Processing
You can pass an array of diff pairs to resolve multiple deltas concurrently:
```typescript
const [diff1, diff2] = await diffAI([
{ start: '2026-08-01', end: '2026-08-05', prompt: 'summarize for billing' },
{ start: '2026-08-05', end: '2026-08-15', prompt: 'explain project sprint' },
]);
```
---
# Document: 9-plugins/ai.extract.md
# `extractAI` β Unstructured Text & Calendar Event Extraction
`extractAI()` scans unstructured, multi-paragraph text (emails, meeting transcripts, chat logs, task notes, calendar invitations) to automatically identify, parse, and extract all embedded temporal entities and time-bound events into structured `TempoAiExtractResult` records containing native `Tempo` instances.
Relative expressions (such as *"tomorrow at 10am"*, *"next Tuesday from 1 to 3pm"*, *"final deliverables due Friday EOD"*) are resolved and mathematically grounded against an explicit or current reference `anchor` timestamp, timezone, and calendar system.
---
## Basic Usage
```typescript
import { Tempo } from '@magmacomputing/tempo';
import { initAI, extractAI } from '@magmacomputing/tempo-plugin-ai';
// 1. Initialize AI providers
await initAI({
providers: [
{ id: 'groq', key: process.env.GROQ_API_KEY }
]
});
const emailText = `
Hi team,
Let's schedule our Sprint Review tomorrow from 10:00 AM to 11:30 AM in Room 4A.
Also, reminder that all pull requests and documentation are due next Friday by 5:00 PM.
`;
const anchor = new Tempo('2026-08-10T09:00:00Z'); // Monday morning
const result = await extractAI(emailText, { anchor, timeZone: 'America/New_York' });
for (const event of result.events) {
console.log(`[${event.type}] ${event.label}`);
console.log(` Start: ${event.start.format('{yyyy}-{mm}-{dd} {hh}:{mi}')}`);
if (event.end) {
console.log(` End: ${event.end.format('{yyyy}-{mm}-{dd} {hh}:{mi}')}`);
}
console.log(` Source: "${event.rawText}" (Confidence: ${event.confidence})`);
}
```
---
## Configuration Options (`AiExtractOptions`)
| Option | Type | Description |
| :--- | :--- | :--- |
| **`anchor`** | `TempoDateInput` | Reference anchor date for relative expressions (defaults to current time). |
| **`timeZone`** | `string` | Target IANA timezone for grounding and output Tempo instances. |
| **`locale`** | `string \| string[]` | Target BCP 47 locale or language tag (e.g. `'en-US'`, `'fr-FR'`). |
| **`calendar`** | `string` | Calendar system (e.g. `'gregory'`, `'hebrew'`, `'islamic'`). |
| **`categories`** | `string[]` | Optional list of categories to filter entities (e.g. `['meeting', 'deadline']`). |
| **`region`** | `string` | Regional context (e.g. `'AU-NSW'`, `'US-NY'`) passed to LLM grounding. |
| **`force`** | `boolean` | If true, bypasses cache to force a fresh LLM query. |
| **`cache`** | `boolean` | If false, disables writing to and reading from cache adapters. |
| **`cacheAdapter`** | `AiCacheAdapter` | Custom cache engine (e.g., Redis, Cloudflare KV) for caching results. |
| **`ttl`** | `number` | Time-to-live override in milliseconds for cached results (defaults to 24h). |
| **`minConfidence`**| `number` | Minimum confidence score threshold (0.0 to 1.0) required. Throws `TempoAiError(422)` if lower. |
| **`mode`** | `AiMode` | Concurrency routing strategy (`fallback`, `race`, `consensus`, `hedged`, `roundrobin`, `adaptive`). Refer to the [Multi-Provider Execution Modes Guide](./ai.modes.md). |
| **`softErrors`** | `boolean` | If true, returns `TempoAiError` into array indices instead of rejecting batch queries. |
---
## Result Schema (`TempoAiExtractResult`)
```typescript
export interface TempoAiExtractResult {
/** Array of extracted events with instantiated Tempo objects. */
events: TempoExtractedEvent[];
/** Overall extraction confidence score between 0.0 and 1.0. */
confidence: number;
/** ID of the provider that fulfilled the request (or 'cache'). */
provider: string;
/** Optional summary or reasoning from the LLM. */
reasoning?: string | undefined;
}
export interface TempoExtractedEvent {
/** Short descriptive label or title of the extracted event/activity. */
label: string;
/** Start date-time point as an instantiated Tempo instance. */
start: Tempo;
/** Optional end date-time point (if an interval or duration was mentioned). */
end?: Tempo | undefined;
/** Classification category ('point' | 'interval' | 'deadline' | 'recurrence' | 'tentative'). */
type: TempoEventType;
/** Raw text snippet extracted from the source document. */
rawText?: string | undefined;
/** Confidence score for this specific entity extraction (0.0 to 1.0). */
confidence: number;
}
```
---
## Key Architectural Behaviors
### 1. Mathematical Grounding & Hallucination Suppression
To prevent hallucinated dates, `extractAI` calculates grounding anchor coordinates before dispatching to the LLM:
- Localized ISO reference timestamp and timezone
- Day of the week name and ordinal index
- Target calendar system and regional context
- Constrained JSON schema ensuring valid ISO dates
### 2. Native `Tempo` Instances
Extracted start and end points are immediately instantiated as live `Tempo` objects, ready for subsequent date math, interval arithmetic, or timezone shifting:
```typescript
const result = await extractAI(transcript);
const meeting = result.events[0];
// Instant date operations with Tempo
const reminderTime = meeting.start.subtract('15 minutes');
console.log(`Set alarm for: ${reminderTime.format('{h12}:{mi} {mer}')}`);
```
### 3. Multi-Tier Distributed Caching
`extractAI` integrates multi-tier caching (in-memory and optional asynchronous `AiCacheAdapter` such as Redis or Cloudflare KV). Cached ISO timestamps are rehydrated into live `Tempo` objects upon cache hits:
```typescript
const result = await extractAI(documentText, {
cacheAdapter: redisCacheAdapter,
ttl: 86_400_000, // 24 hours
});
```
### 4. Parallel Batch Extraction
Process arrays of documents concurrently with optional `softErrors` fault-tolerance:
```typescript
const documents = [
"Team offsite next Thursday from 9am to 5pm.",
"Project proposal submission deadline is August 20 at midnight."
];
const results = await extractAI(documents, { softErrors: true });
```
---
# Document: 9-plugins/ai.format.md
# `formatAI` β Contextual & Narrative Date Formatting
`formatAI()` formats a `Tempo` instance, TC39 `Temporal` object, Date, or timestamp into human-friendly, contextual narrative text tailored to specific UI tones, relative time frames, or business domains.
While core `Tempo` provides token-based template formatting (`t.format('{yyyy}-{mm}-{dd}')`), `formatAI` bridges the gap to contextual, localized human descriptions that token patterns alone cannot capture (e.g. countdowns, calendar invites, conversational reminders, and domain summaries), backed by mathematical grounding.
---
## Basic Usage
```typescript
import { Tempo } from '@magmacomputing/tempo';
import { initAI, formatAI } from '@magmacomputing/tempo-plugin-ai';
// 1. Initialize AI providers
await initAI({
providers: [
{ id: 'groq', key: process.env.GROQ_API_KEY }
]
});
const target = new Tempo('2026-08-07T17:00:00[America/New_York]');
const anchor = new Tempo('2026-08-02T17:00:00[America/New_York]');
// "this Friday at 5:00 PM EDT (in 5 days)"
const result = await formatAI(target, 'friendly reminder tone with relative countdown', { anchor });
console.log(result.formatted); // "this Friday at 5:00 PM EDT (in 5 days)"
console.log(result.confidence); // 0.98
console.log(result.provider); // 'groq'
```
---
## Configuration Options (`AiFormatOptions`)
| Option | Type | Description |
| :--- | :--- | :--- |
| **`anchor`** | `TempoDateInput` | Reference anchor date for relative delta calculations (defaults to current time). |
| **`style`** | `string` | Narrative style or tone hint (e.g. `'casual'`, `'formal'`, `'compact'`, `'countdown'`). |
| **`region`** | `string` | Regional context (e.g., `'AU-NSW'`, `'US-CA'`) passed to LLM grounding. |
| **`timeZone`** | `string` | Target IANA timezone for output formatting. |
| **`locale`** | `string \| string[]` | Target BCP 47 locale or language tag (e.g. `'fr-FR'`, `'en-US'`). |
| **`force`** | `boolean` | If true, bypasses the cache to initiate a fresh LLM query. |
| **`cache`** | `boolean` | If false, disables writing to and reading from cache adapters. |
| **`cacheAdapter`** | `AiCacheAdapter` | Custom cache engine (e.g., Redis, Cloudflare KV) for caching results. |
| **`ttl`** | `number` | Time-to-live override in milliseconds for cached results (defaults to 24h). |
| **`minConfidence`**| `number` | Minimum confidence score threshold (0.0 to 1.0) required. Throws `TempoAiError(422)` if lower. |
| **`mode`** | `AiMode` | Concurrency routing strategy (`fallback`, `race`, `consensus`, `hedged`, `roundrobin`, `adaptive`). Refer to the [Multi-Provider Execution Modes Guide](./ai.modes.md). |
| **`softErrors`** | `boolean` | If true, returns `TempoAiError` into array indices instead of rejecting batch queries. |
---
## Result Schema (`TempoAiFormatResult`)
```typescript
export interface TempoAiFormatResult {
/** Formatted narrative string. */
formatted: string;
/** Confidence score between 0.0 and 1.0. */
confidence: number;
/** ID of the provider that fulfilled the request (or 'cache'). */
provider: string;
/** Optional step-by-step rationale from the LLM. */
reasoning?: string | undefined;
}
```
---
## Key Architectural Behaviors
### 1. Native Grounding Context
To eliminate LLM date and day-of-week hallucinations, `formatAI` computes deterministic grounding metrics before constructing the prompt:
- Exact ISO timestamp and timezone
- Day of the week name and ordinal (e.g. `Friday`, Day 5)
- Relative delta in calendar days and elapsed hours compared to anchor
- Directionality (`'past'`, `'present'`, `'future'`)
These metrics are injected into the system prompt as immutable constraints.
### 2. TC39 Temporal & Universal Interoperability
`formatAI` seamlessly accepts `Tempo` instances, native JavaScript `Date` objects, ISO strings, timestamps, and TC39 `Temporal` objects (`Temporal.ZonedDateTime`, `Temporal.Instant`, `Temporal.PlainDateTime`, `Temporal.PlainDate`):
```typescript
import { Temporal } from '@magmacomputing/tempo/library';
const zdt = Temporal.ZonedDateTime.from('2026-08-05T15:00:00+10:00[Australia/Sydney]');
const result = await formatAI(zdt, 'compact relative format');
```
### 3. Multi-Tier Distributed Caching
`formatAI` integrates multi-tier caching (in-memory + optional asynchronous `AiCacheAdapter` such as Redis or Cloudflare KV). Cache keys incorporate input timestamp, anchor timestamp, normalized prompt, timezone, locale, region, and style to ensure complete cache correctness:
```typescript
const result = await formatAI(target, 'casual invitation', {
cacheAdapter: redisCacheAdapter,
ttl: 3_600_000, // 1 hour
});
```
### 4. Parallel Batch Formatting
Format multiple dates and prompts concurrently with optional `softErrors` resilience:
```typescript
const results = await formatAI([
{ date: '2026-08-03T09:00:00Z', prompt: 'calendar invite' },
{ date: '2026-08-05T18:00:00Z', prompt: 'flight departure notification' },
], { softErrors: true });
```
---
# Document: 9-plugins/ai.grounding.md
# Grounding & Natural Language Parsing
Because natural language dates are entirely relative (e.g., *"next Tuesday"*) and culturally ambiguous (e.g., *"11/12"*), an LLM cannot reliably parse them in a vacuum.
The Tempo AI plugin solves this by automatically injecting **deterministic temporal and regional grounding coordinates** before dispatching queries to the LLM.
## Temporal & Regional Grounding
The plugin automatically resolves the active `Tempo.config` to establish the exact reference time and regional coordinates:
- **Anchor Reference Clock**: The exact ISO timestamp at the moment of invocation.
- **Regional Coordinates**: TimeZone (e.g., `America/New_York`), Calendar system (`iso8601`), Locale (`en-US`), and Hemisphere (`northern`).
Along with your text query, the plugin passes these grounding coordinates directly to the model's system prompt:
> *`Grounding Anchor: [Anchor], Timezone: [TZ], Calendar: [Cal], Locale: [Loc], Hemisphere: [Sphere]`*
### Custom Grounding Anchors & Options
You can explicitly override any grounding coordinate on a per-request basis by passing an options object as the second argument, identical to how you pass configuration options to a standard `new Tempo()` constructor:
```typescript
// Explicitly evaluate this complex relative query from the perspective of September 1st
const dt = await parseAI("The penultimate Tuesday before Thanksgiving", {
anchor: '2026-09-01T00:00:00Z'
});
// Explicitly parse assuming a Japanese locale and timezone
const tokyoDt = await parseAI("The second Sunday of May", {
locale: 'ja-JP',
timeZone: 'Asia/Tokyo'
});
```
### Why Cultural & Regional Grounding is Critical
Passing the `Locale` and `TimeZone` is critical for the LLM to know whether `"11/12"` represents November 12th (US format) or 11th of December (UK/EU format). The plugin grounds these ambiguous tokens transparently based on your standard Tempo configuration!
> [!WARNING]
> **Calendar Math Hallucinations**: LLMs are language predictors, not calculators. While they excel at parsing conversational times (like `"tomorrow at 5pm"`), smaller models are notoriously prone to hallucinations on complex, cross-year calendar math. For example, asking a lightweight model for `"Thanksgiving in 2026"` may result in a hallucinated day of the week because the model doesn't natively compute "the fourth Thursday of November 2026." If your application relies on heavy holiday logic or complex multi-year math, you *must* use a capable frontier model or rely on deterministic plugins instead of AI.
## The Decoupled Output Bridge
To ensure deterministic, type-safe behavior, the plugin enforces a strict decoupled bridge between AI text generation and JavaScript object hydration:
* **For Point-in-Time Parsing (`parseAI`)**: The LLM is instructed to return a strict local ISO 8601 string without a timezone offset or 'Z' suffix (e.g. `"2026-11-26T00:00:00"`). The plugin immediately constructs a native `new Tempo()` instance with caller-defined timezone and calendar context.
* **For Structured Functions (`formatAI`, `extractAI`, `diffAI`, `contextAI`)**: The LLM completes rigid JSON schemas validated against strict boundary rules, instantiating typed result objects (`TempoAiFormatResult`, `TempoAiExtractResult`, `TempoAiDiffResult`, `TempoContext`).
* **For Intervals & Generators (`scheduleAI`, `recurrenceAI`)**: The plugin hydrates interval boundaries into a proxied `Interval` or exposes an iterable generator yielding sequential `Tempo` instances.
This eliminates AST-construction ambiguity and provides clean runtime contracts for every operation.
### Relative Date Ambiguity Tie-Breakers
To eliminate model variance on idioms like "Next Friday" or "Last Tuesday", the plugin enforces static system prompt ambiguity rules:
* `"next [weekday/unit]"`: Evaluated as the immediate next chronological occurrence after the grounding anchor.
* `"last [weekday/unit]"` / `"previous [weekday/unit]"`: Evaluated as the most recent past occurrence prior to the grounding anchor.
* `"this [weekday]"`: Evaluated as the occurrence within the current calendar week containing the grounding anchor.
### Confidence Thresholds & Metadata Handling
When `minConfidence` is supplied in options (e.g. `{ minConfidence: 0.85 }`):
* **`parseAI`**: Any LLM response returning a confidence score below the threshold produces a `Tempo` instance with `isValid === false` (when using `softErrors: true`) or throws a `TempoAiError(422)`.
* **Structured Functions (`formatAI`, `extractAI`, `diffAI`, `contextAI`, `scheduleAI`, `recurrenceAI`)**: Low-confidence completions immediately throw a `TempoAiError(422)` (or return a `TempoAiError` in batch arrays when `softErrors: true` is enabled).
Every resolved `Tempo` instance returned by `parseAI` has a non-writable, frozen `.ai` metadata descriptor containing execution audit data:
```typescript
const dt = await parseAI("Christmas 2026", { debug: true });
console.log(dt.ai);
// {
// provider: 'openai',
// cached: false,
// confidence: 0.95,
// ambiguous: false,
// granularity: 'day',
// rawIso: '2026-12-25T00:00:00',
// rawPrompt: 'Christmas 2026', // Present when debug is enabled
// normalizedPrompt: 'christmas 2026' // Present when debug is enabled
// }
```
*(For other AI functions like `extractAI` or `diffAI`, diagnostic metadata including `confidence`, `reasoning`, and `provider` is attached directly to the returned result object.)*
---
# Document: 9-plugins/ai.index.md

# @magmacomputing/tempo-plugin-ai
Tempo community plugin for LLM-powered natural language date parsing, schedule compilation, and temporal processing.
This plugin bridges the gap between deterministic date-math and unstructured NLP inputs, utilizing large language models (like Gemini, Groq, or OpenAI) to safely and asynchronously parse, format, and process complex natural language temporal expressions into `Tempo` instances.
::: warning π Security Notice
Raw LLM API keys must **never** be exposed in client-side browser bundles or stored in browser storage (`localStorage`, `sessionStorage`, `IndexedDB`, or browser cache). BYOK (Bring Your Own Key) is only secure on backend servers (Node, edge workers). For public frontend applications, route requests through a secure backend proxy service.
:::
## Installation & Quickstart
```bash
npm install @magmacomputing/tempo-plugin-ai
```
### 1. Zero-Config Mode (Instant Execution)
If you have standard provider keys in your environment (`GROQ_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, `MISTRAL_API_KEY`), simply call any AI function directly with zero boilerplate:
```typescript
import { parseAI } from '@magmacomputing/tempo-plugin-ai';
// Automatically discovers GROQ_API_KEY / OPENAI_API_KEY from the environment
const dt = await parseAI("The penultimate Tuesday before Thanksgiving in 2026");
console.log(dt.format('{yyyy}-{mm}-{dd}')); // 2026-11-17
```
### 2. Explicit Provider Farm Configuration
For custom models, custom SLAs, or multi-provider execution strategies:
```typescript
import { parseAI, initAI, AiMode } from '@magmacomputing/tempo-plugin-ai';
// Explicitly configure provider farm & fallback strategy
await initAI({
mode: AiMode.Fallback,
providers: [
{ id: 'groq', key: process.env.GROQ_API_KEY },
{ id: 'openai', key: process.env.OPENAI_API_KEY, model: 'gpt-4o' }
],
timeout: 5000
});
const dt = await parseAI("The penultimate Tuesday before Thanksgiving in 2026");
console.log(dt.format('{yyyy}-{mm}-{dd}')); // 2026-11-17
```
## AI Function Catalog
All AI functions return a standard ES Promise wrapped object.
| Function | Input | Returns (`Promise<...>`) | Description | Doc |
| :--- | :--- | :--- | :--- | :---: |
| **`parseAI`** | Natural language text string(s) | `Tempo` \| `Tempo[]` \| `(Tempo \| TempoAiError)[]` | Single point-in-time `Tempo` instance (or batch array) | |
| **`formatAI`** | Date-time + prompt / style | `TempoAiFormatResult` \| `TempoAiFormatResult[]` \| `(TempoAiFormatResult \| TempoAiError)[]` | **Contextual narrative date formatting** (`formatted`, `confidence`, `provider`, `reasoning`) | |
| **`extractAI`** | Unstructured text string(s) | `TempoAiExtractResult` \| `TempoAiExtractResult[]` \| `(TempoAiExtractResult \| TempoAiError)[]` | **Extracted temporal entities & calendar events** (`events: TempoExtractedEvent[]`, `confidence`, `reasoning`) | |
| **`recurrenceAI`** | Natural language pattern or RRULE string | `TempoRecurrenceResult` | **Iterable series of `Tempo` dates** (with `.take(n)`, `[Symbol.iterator]`, & RRULE string) | |
| **`scheduleAI`** | Booking prompt + busy constraints | `TempoScheduleResult` | **Resolved appointment slot** (`start`, `end`, `slot`, `alternatives`, `ai.conflictBumped`) | |
| **`diffAI`** | Start & End dates + prompt | `TempoAiDiffResult` \| `TempoAiDiffResult[]` \| `(TempoAiDiffResult \| TempoAiError)[]` | **Narrative time delta & business days** (`formatted`, `businessDays`, `days`, `hours`, `holidays`) | |
| **`contextAI`** | Context text string(s) | `TempoContext` \| `TempoContext[]` \| `(TempoContext \| TempoAiError)[]` | **Inferred regional context** (`timeZone`, `locale`, `calendar`, `sphere`) | |
| **`initAI`** | Provider config & API keys | `void` | Configured AI provider farm | |
### Summary of Distinct Return Contracts
To streamline error handling and data consumption, return shapes across the AI plugin follow three distinct contracts:
| Category | Functions | Return Type | Single Query Low-Confidence / Failure | Batch Array `softErrors: true` Contract |
| :--- | :--- | :--- | :--- | :--- |
| **Point-in-Time Date** | `parseAI` | `Tempo` (with `.ai`) | Throws `TempoAiError` (or returns invalid `Tempo` if `minConfidence` threshold unmet) | Returns invalid `Tempo` (`isValid === false`) in array position |
| **Structured AI Objects** | `formatAI` `extractAI` `diffAI` `contextAI` | `TempoAiFormatResult` `TempoAiExtractResult` `TempoAiDiffResult` `TempoContext` | Throws `TempoAiError` (422 for low confidence, 429 for quota, 500 for network) | Returns typed `TempoAiError` object directly in array position |
| **Intervals & Generators** | `scheduleAI` `recurrenceAI` | `TempoScheduleResult` (Proxied `Interval`) `TempoRecurrenceResult` (`.take(n)`) | Throws `TempoAiError` (Single item query only) | N/A (Single query operations) |
## Architecture & Infrastructure Guides
> [!IMPORTANT]
> **Production Recommendation**: Due to the complexities of LLM APIs, including caching gotchas, context injection, rate limits, and calendar math hallucinations, we politely but strongly recommend reading the dedicated guides below before deploying this plugin in a production environment.
- [Security & Privacy Architecture](./ai.security.md) (Smart Debug Telemetry, PII Masking, HTTPS & Proxy Introspection)
- [Multi-Provider Execution Modes](./ai.modes.md) (Hedged, RoundRobin, Adaptive, Race, Consensus, Fallback)
- [Provider Architecture & Security](./ai.architecture.md) (BYOK vs Proxy patterns, Browser Security, TLS 1.3 & Privacy Guarantees)
- [Grounding & Natural Language Parsing](./ai.grounding.md) (How Timezone and Locale are injected)
- [Rate Limits & Cache Management](./ai.rate-limits.md) (Tracking API quotas, handling 429 errors, and custom Redis caches)
## Community Feedback & Production Notice
> [!NOTE]
> **Community Feedback & Prompt Engineering**
> While `@magmacomputing/tempo-plugin-ai` utilizes deterministic grounding, schema enforcement, and confidence validation, LLM outputs can vary across models and prompt styles. We actively welcome community feedback and prompt optimizationsβplease report any edge cases or suggestions on the [Magma GitHub Issue Tracker](https://github.com/magmacomputing/magma/issues/new?template=bug_report_ai.yml).
>
> **Production Notice & "As-Is" Disclaimer**: Magma Computing Solutions and the Tempo core maintainers provide `@magmacomputing/tempo-plugin-ai` "as-is" without warranty of any kind. Large Language Models operate probabilistically; developers and system architects are responsible for validating AI-generated temporal outputs before committing them to financial, legal, medical, or life-critical applications.
## Licensing
This is a **Community** plugin. It is completely free and open-source for personal and commercial use under the MIT license.
---
# Document: 9-plugins/ai.init.md
# `initAI` β Provider Initialization & Farm Configuration
`initAI()` sets up the global configuration for `@magmacomputing/tempo-plugin-ai`, managing provider authentication, multi-provider execution modes, global SLAs/timeouts, and caching strategies.
## Zero-Config Auto-Discovery
`@magmacomputing/tempo-plugin-ai` features a zero-boilerplate auto-discovery architecture. In server environments (Node.js, Deno, Bun), calling `initAI()` is **completely optional** when standard environment variables (`GROQ_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, `MISTRAL_API_KEY`) or a `tempo.config.json` file are present.
```typescript
import { parseAI } from '@magmacomputing/tempo-plugin-ai';
// When GROQ_API_KEY is present in the environment:
// Zero setup required β providers and SLAs are auto-discovered lazily on first call!
const dt = await parseAI("next Friday at 4pm");
```
### Configuration Resolution Order
Configuration is automatically discovered and resolved in the following priority:
1. **Call-site explicit overrides** (`options.providers`, `options.mode`).
2. **Explicit `initAI(config)` parameters**.
3. **Active `Tempo.config.plugins.ai`** (in-memory or loaded via `Tempo.bootstrap()`).
4. **Filesystem `tempo.config.*` files** (JSON, JSONC, JS, TS).
5. **Runtime Environment Variables** (`GROQ_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, `MISTRAL_API_KEY`).
### `tempo.config.json` Example
You can declare AI provider configurations directly within your project's `tempo.config.json` using template variable interpolation:
```json
{
"timeZone": "Australia/Sydney",
"locale": "en-AU",
"plugins": {
"ai": {
"mode": "fallback",
"timeout": 5000,
"minConfidence": 0.85,
"providers": [
{ "id": "groq", "key": "${GROQ_API_KEY}" },
{ "id": "openai", "key": "$env:OPENAI_API_KEY", "model": "gpt-4o-mini" }
]
}
}
}
```
## Basic Usage (Explicit Configuration)
```typescript
import { initAI, parseAI } from '@magmacomputing/tempo-plugin-ai';
// Initialize with custom provider credentials and execution options
await initAI({
providers: [
{ id: 'groq', key: process.env.GROQ_API_KEY },
{ id: 'openai', key: process.env.OPENAI_API_KEY, model: 'gpt-4o' }
],
timeout: 5000, // 5-second global SLA default
debug: true // Enable operational trace logging (automatically PII-sanitized in production)
});
```
> **Tip**: `initAI` returns a `Promise` and is fully re-callable! Calling it synchronously without `await` instantly initializes local configurations so you can call `parseAI` immediately, while `await initAI()` guarantees that remote provider manifest defaults are fetched and applied before proceeding (with explicit provider configuration values always taking precedence over remote manifest defaults).
### Dynamic Provider Credentials & Context Suppliers
The provider `key` configuration supports asynchronous or synchronous supplier functions (`AsyncEvaluable`), allowing automated secret vault retrieval and dynamic token refreshing. Provider attributes (`url`, `model`) as well as global context settings (`timeZone`, `locale`, `calendar`, `sphere`) accept synchronous supplier functions (`Evaluable`).
This enables automated secret vault rotation, dynamic AI gateways, and multi-tenant context resolution evaluated just-in-time on every HTTP dispatch:
```typescript
import { initAI, parseAI } from '@magmacomputing/tempo-plugin-ai';
// Initialize with dynamic async key resolver and per-request context
initAI({
providers: [
{
id: 'openai',
// Resolved dynamically per-request: enables secret vault rotation without restarting
key: async () => await secretVault.getApiKey('openai'),
// Dynamic proxy endpoint
url: () => getActiveGatewayUrl()
}
],
// Dynamic timezone / locale resolution
timeZone: () => currentRequestContext.timeZone,
locale: () => currentRequestContext.locale
});
```
## Execution Modes & Multi-Provider Options
The AI plugin supports six multi-provider execution strategies (`fallback`, `race`, `consensus`, `adaptive`, `hedged`, `roundrobin`):
* **`fallback`** (default): Queries providers sequentially in array order until one succeeds.
* **`race`**: Sends concurrent requests to all providers, returning the fastest valid response.
* **`consensus`**: Queries providers concurrently and boosts confidence when outputs agree.
* **`hedged`**: Initiates staggered latency hedging, querying subsequent providers if the primary is slow.
* **`roundrobin`**: Cyclic load balancing across the provider pool.
* **`adaptive`**: Proactive telemetry-aware rate-limit avoidance.
For a comprehensive guide, decision flowcharts, and configuration details for all execution modes, refer to the [Multi-Provider Execution Modes Guide](./ai.modes.md).
```typescript
// 1. Fallback mode (default): query providers sequentially in array order until one succeeds
initAI({
mode: 'fallback',
providers: [
{ id: 'groq', key: process.env.GROQ_API_KEY }, // Primary provider
{ id: 'openai', key: process.env.OPENAI_API_KEY } // Fallback provider
]
});
// 2. Race mode: send concurrent requests to all providers, returning the fastest valid response
const fastest = await parseAI("Third Friday of October", { mode: 'race' });
// 3. Consensus mode: query providers concurrently and boost confidence when outputs agree
const agreed = await parseAI("The penultimate Tuesday before Thanksgiving", {
mode: 'consensus',
minConfidence: 0.85
});
// 4. Hedged mode: speculative concurrency with staggered delay (e.g. 800ms)
const hedged = await parseAI("First Monday of December", {
mode: 'hedged',
hedgeDelay: 800
});
```
## Timeout Controls & SLAs
Prevent hanging requests using the 3-tier timeout hierarchy (`call-site` > `provider` > `global` > `default 15s`):
```typescript
initAI({
providers: [
{ id: 'groq', key: process.env.GROQ_API_KEY, options: { timeout: 2000 } } // 2s timeout for fast provider
],
timeout: 5000 // 5s global default timeout
});
// Hard 3-second SLA override for a specific call-site
const dt = await parseAI("Next Tuesday at 4pm", { timeout: 3000 });
```
## Operational Trace Logging & Debugging
**Operational Trace Logging**
Passing `debug: true` into `initAI` (or setting `{ debug: true }` on a per-request options object) outputs concise operational trace logs (prefixed with `[tempo-plugin-ai]`) to the developer console for monitoring provider fallback decisions and execution timing.
Detailed diagnostic contextβincluding provider resolution, execution lineage, confidence scores, and when `debug: true` is active, `rawPrompt`, `normalizedPrompt`, and rate-limit snapshotsβis attached directly to the returned `Tempo` instance via the `.ai` property for `parseAI`. Structured functions (`formatAI`, `diffAI`, `extractAI`, `contextAI`, `scheduleAI`) surface their respective typed properties directly on the result object (such as `res.confidence`, `res.provider`, and optional `res.reasoning`).
> [!TIP]
> **Smart Debug & Proxy Introspection**: In production environments (`NODE_ENV === 'production'`), terminal logging via `console.log(date.ai)` or `console.log(result)` automatically sanitizes and masks PII (emails, phones, bearer tokens) while preserving 100% in-memory data integrity for application code. Refer to the [Security & Privacy Architecture Guide](./ai.security.md).
## Configuration Options Reference
```typescript
export interface AiConfig {
/** List of configured AI providers */
providers?: AiProvider[];
/** Default execution mode across providers ('fallback' | 'race' | 'consensus' | 'hedged' | 'roundrobin' | 'adaptive') */
mode?: 'fallback' | 'race' | 'consensus' | 'hedged' | 'roundrobin' | 'adaptive';
/** Speculative hedge delay in milliseconds (hedged mode only, default: 800ms) */
hedgeDelay?: number;
/** Global SLA timeout in milliseconds */
timeout?: number;
/** Global debug flag for operational trace logging */
debug?: boolean;
/** Synchronous Map or BoundedCache for static glossary terms */
cache?: Map;
/** Custom cache adapter for distributed storage (e.g. Redis, KV) */
cacheAdapter?: AiCacheAdapter;
/** Global default time-to-live in milliseconds for cache adapters */
ttl?: number;
/** Minimum confidence threshold for AI parsing results (0.0 to 1.0) */
minConfidence?: number;
/** Dynamic or static default timezone context (string | (() => string)) */
timeZone?: Evaluable;
/** Dynamic or static default locale context (string | string[] | (() => string | string[])) */
locale?: Evaluable;
/** Dynamic or static default calendar context (string | (() => string)) */
calendar?: Evaluable;
/** Dynamic or static default celestial sphere context (string | (() => string)) */
sphere?: Evaluable;
/** Optional hook to intercept and resolve dynamic provider defaults */
fetchDefaults?: (providerId: string) => Promise | null> | Partial | null;
/** URL for dynamic remote provider manifest updates, or `false` to disable */
remoteConfigUrl?: string | false;
}
```
---
# Document: 9-plugins/ai.modes.md
# Multi-Provider Execution Modes (`AiMode`)
`@magmacomputing/tempo-plugin-ai` provides a robust, multi-provider dispatch orchestrator that handles latency hedging, quota load-balancing, consensus verification, and fault-tolerant failovers across diverse LLM providers (e.g. OpenAI, Anthropic, Groq, Mistral, Google Gemini, Ollama, local vLLM).
Execution modes can be configured globally during plugin initialization or overridden per request:
```typescript
import { initAI, parseAI, AiMode } from '@magmacomputing/tempo-plugin-ai';
// Global configuration
await initAI({
providers: [
{ id: 'groq', key: process.env.GROQ_API_KEY },
{ id: 'openai', key: process.env.OPENAI_API_KEY }
],
mode: AiMode.Hedged
});
// Per-request override
const dt = await parseAI('next friday at 3pm', {
mode: AiMode.Adaptive
});
```
---
## Strategy Comparison
| Mode | Dispatch | Token Cost | Latency | Rate-Limit Resilience |
| :--- | :--- | :---: | :---: | :---: |
| **`Fallback`** *(Default)* | Sequential | π’ 1 request | π‘ Moderate | π’ Proactive Cooldown Filter |
| **`Hedged`** | Staggered (primary + timer) | π’ ~1.15 avg | π’ Ultra-Fast | π’ Proactive Cooldown Filter |
| **`RoundRobin`** | Cyclic rotation | π’ 1 request | π‘ Moderate | π’ High (Cyclic + Filter) |
| **`Adaptive`** | Quota-sorted rotation | π’ 1 request | π‘ Moderate | π’ Maximum (Telemetry-Ranked) |
| **`Race`** | Full parallel | π΄ N requests | π’ Ultra-Fast | π’ Proactive Cooldown Filter |
| **`Consensus`** | Full parallel + voting | π΄ N requests | π‘ Moderate | π’ Proactive Cooldown Filter |
---
## Global Telemetry & Cooldown Filtering
Regardless of the execution mode chosen, the AI dispatch engine actively monitors per-provider rate-limiting metadata across all network responses (`x-ratelimit-remaining-requests`, `x-ratelimit-remaining-tokens`, `x-ratelimit-reset-requests`, `retry-after`).
```mermaid
flowchart LR
A["Incoming Request\n(Any AiMode)"] --> B{"Check Provider Farm\nCooldown State"}
B -- "Exhausted (remaining === 0\n& resetAt > now)" --> C["π« Proactively Filter Out\n(Skip 429 endpoints)"]
B -- "Ready / High Quota" --> D["β Active Provider Pool"]
C -. "If ALL in cooldown" .-> D
D --> E["Dispatch via Selected Mode\n(Fallback, Race, Hedged, etc.)"]
```
### Proactive Cooldown Avoidance
Before dispatching any request:
1. **Cooldown Detection**: The orchestrator checks if any configured provider has exhausted its request or token quota (`remainingRequests === 0` or `remainingTokens === 0`) and is within an active reset window (`resetAt > now`, derived from response reset timestamps or `retry-after` metadata).
2. **Pre-Dispatch Filtering**: In `Fallback`, `Race`, `Hedged`, and `RoundRobin` modes, exhausted providers are automatically removed from the active candidate pool for that request.
- **`Fallback` & `Hedged`**: Avoids stalling on primary providers that are guaranteed to reject with HTTP 429.
- **`Race`**: Saves network bandwidth and avoids firing wasted requests to rate-limited models.
- **`RoundRobin`**: Skips over cooling-down keys without breaking the cyclic load-balancing progression.
3. **Fail-Open Resilience**: If *all* providers in the farm are currently in a cooldown window, the orchestrator keeps all providers available rather than failing prematurely, allowing the request to cascade or surface accurate rate-limit errors.
---
## Dispatch Decision Guide
```mermaid
flowchart TD
Start(["Where does this run?"]) --> UI["User-facing\n(search bar, chatbot, form)"]
Start --> Batch["Background / batch\n(ingestion, ETL, reports)"]
Start --> Audit["High-stakes audit\n(legal, financial, scheduling)"]
UI --> MultiUI{"Multiple providers\nor API keys?"}
MultiUI -- No --> Fallback["β¬οΈ AiMode.Fallback\nDefault β’ Most cost-efficient"]
MultiUI -- Yes --> CostSpeed{"Latency sensitive\nor cost concern?"}
CostSpeed -- "Fastest response (high cost)" --> Race["π AiMode.Race\nAbsolute fastest response"]
CostSpeed -- "Staggered concurrency (lower cost)" --> Hedged["β³ AiMode.Hedged\nStaggered latency hedging"]
Batch --> Quota{"Quota pressure\nor multiple keys?"}
Quota -- "Spread evenly" --> RR["π AiMode.RoundRobin\nCyclic key rotation"]
Quota -- "Avoid 429 proactively" --> Adaptive["π‘ AiMode.Adaptive\nTelemetry-driven ordering"]
Quota -- "Single key, simple" --> Fallback["β¬οΈ AiMode.Fallback\nDefault β’ Most cost-efficient"]
Audit --> Consensus["π³οΈ AiMode.Consensus\nCross-LLM voting β’ Highest accuracy"]
```
---
## Mode Deep-Dives & Code Examples
### 1. `AiMode.Fallback` β Sequential Cascade *(Default)*
Dispatches requests to providers sequentially in configured order until a provider succeeds and satisfies `minConfidence`. The most cost-efficient mode.
**Best for:** Standard production baseline β general date parsing and background tasks.
```typescript
const dt = await parseAI('first monday in october 2026', {
mode: AiMode.Fallback,
minConfidence: 0.85 // Automatically cascades to next provider if score is too low
});
```
---
### 2. `AiMode.Hedged` β Speculative Latency Hedging
Sends a request to the primary provider immediately. If no valid response arrives within `hedgeDelay` (default: `800ms`), launches a speculative concurrent request to the secondary provider. The first valid response wins; all in-flight requests are aborted.
**Best for:** Latency-sensitive user-facing APIs β search bars, chatbots, web forms.
```typescript
const dt = await parseAI('schedule team sync for next wednesday at 2pm', {
mode: AiMode.Hedged,
hedgeDelay: 600 // Launch hedge after 600ms if primary is still pending
});
```
> [!TIP]
> `hedgeDelay` can also be set globally in `initAI({ hedgeDelay: 600 })` so it applies to all functions (`parseAI`, `recurrenceAI`, `scheduleAI`, `extractAI`).
---
### 3. `AiMode.RoundRobin` β Multi-Key Cyclic Load Balancing
Cycles through the configured provider pool on each invocation (`0 β 1 β 2 β 0 ...`). If the selected starting provider fails, automatically falls over to the remaining providers in cyclic order.
**Best for:** High-throughput batch processing β ingesting large volumes of dates across multiple API keys to avoid single-account RPM throttling.
```typescript
await initAI({
providers: [
{ id: 'groq-key-1', key: process.env.GROQ_KEY_1 },
{ id: 'groq-key-2', key: process.env.GROQ_KEY_2 },
{ id: 'groq-key-3', key: process.env.GROQ_KEY_3 }
],
mode: AiMode.RoundRobin
});
```
---
### 4. `AiMode.Adaptive` β Rate-Limit Telemetry Prioritization
Reads `x-ratelimit-*` HTTP headers after every provider response and stores per-provider quota snapshots. On subsequent requests, providers are ranked dynamically by highest remaining quota descending, guaranteeing that providers with ample headroom are prioritized ahead of constrained models.
**Best for:** Multi-tier production gateways β mixed free/paid provider pools where proactively avoiding `429 Too Many Requests` is essential.
```typescript
const dt = await parseAI('quarterly review deadline next quarter', {
mode: AiMode.Adaptive
});
```
> [!NOTE]
> Telemetry accumulates across calls. The first request in a session uses original provider order; sorting kicks in from the second call onward once header data is available.
---
### 5. `AiMode.Race` β Speculative Parallel Execution
Dispatches requests concurrently across all configured providers. The fastest successful response is returned, and all remaining requests are immediately cancelled via `AbortSignal`.
**Best for:** Real-time interactive typeahead β live search inputs or autocomplete where the fastest possible response is required regardless of token cost.
```typescript
const dt = await parseAI('tomorrow at noon', {
mode: AiMode.Race
});
```
---
### 6. `AiMode.Consensus` β Multi-LLM Cross-Validation
Dispatches requests concurrently across all providers and compares the normalized outputs (e.g. ISO timestamps for `parseAI`, RRULE strings for `recurrenceAI`, formatted strings for `diffAI`/`formatAI`, or structured entity counts for `extractAI`). If all responding providers agree, confidence is elevated to `1.0` (unanimous). If providers disagree, the highest-confidence candidate is returned and flagged with `ai.ambiguous = true` (attached to `Tempo.ai` on `parseAI` or returned on structured result objects).
**Best for:** High-stakes legal, financial, and scheduling β contract dates, event conflict resolution, or auditing where hallucination prevention requires unanimous LLM agreement.
```typescript
// 1. Point-in-time cross validation
const dt = await parseAI('contract renewal date', {
mode: AiMode.Consensus
});
if (dt.ai?.ambiguous) {
console.warn('Providers disagreed β treat this date with caution.');
}
// 2. High-precision duration calculation across multiple providers
const diff = await diffAI(startDate, endDate, 'in business days excluding UK bank holidays', {
mode: AiMode.Consensus
});
```
---
# Document: 9-plugins/ai.parse.md
# `parseAI` β Natural Language Point-in-Time Parsing
`parseAI()` is the primary entry point for converting complex, unstructured natural language date/time expressions into deterministic `Tempo` instances.
## Basic Usage
```typescript
import { parseAI, initAI } from '@magmacomputing/tempo-plugin-ai';
// Initialize AI providers
await initAI({
providers: [
{ id: 'groq', key: process.env.GROQ_API_KEY }
]
});
// Parse natural language
const dt = await parseAI("The penultimate Tuesday before Thanksgiving in 2026");
console.log(dt.format('{yyyy}-{mm}-{dd}')); // 2026-11-17
console.log(dt.ai?.confidence); // 0.98
```
## Options & Overrides
`parseAI(input, options)` accepts per-request options:
```typescript
const dt = await parseAI("Third Friday of October", {
anchor: '2026-05-10T12:00:00Z', // Anchor date for relative calculations
timeZone: 'Australia/Sydney', // Context timezone
locale: 'en-AU', // Context locale
minConfidence: 0.85, // Require at least 0.85 confidence score
timeout: 3000, // 3-second request timeout (throws TempoAiError(504) if exceeded)
force: true, // Skip native pre-parsing & cache lookup
debug: true // Enable operational trace logging & .ai metadata
});
```
## Multi-Provider Execution Modes
`parseAI` supports all six multi-provider execution strategies (`fallback`, `race`, `consensus`, `hedged`, `roundrobin`, `adaptive`) configured globally or overridden per-request.
For a comprehensive guide, decision flowcharts, and configuration details for all execution modes, refer to the [Multi-Provider Execution Modes Guide](./ai.modes.md).
## Batch Array Parsing
Pass an array of prompts to process multiple queries in parallel while preserving index ordering:
```typescript
const [dt1, dt2] = await parseAI([
"New Years Day 2026",
"Groundhog Day 2026"
]);
console.log(dt1.format('{yyyy}-{mm}-{dd}')); // 2026-01-01
console.log(dt2.format('{yyyy}-{mm}-{dd}')); // 2026-02-02
```
## Diagnostic Metadata (`.ai`)
When a date is parsed, a frozen diagnostic metadata object is attached to the returned `Tempo` instance:
```typescript
console.log(dt.ai);
/*
{
provider: 'groq',
cached: false,
confidence: 0.98,
ambiguous: false,
granularity: 'day',
rawIso: '2026-11-17T00:00:00'
}
*/
```
---
# Document: 9-plugins/ai.rate-limits.md
# Rate Limits & Cache Management
When using third-party AI APIs, your application is subject to strict rate limits.
The plugin automatically tracks these limits by reading the standard `x-ratelimit-*` HTTP headers returned by providers like OpenAI and Groq.
## Tracking Quota Real-time
Quota and rate-limit metadata can be inspected in two convenient ways:
### 1. Request-Locked Instance Metadata (`dt.ai.limits`)
For `parseAI`, every resolved `Tempo` instance includes a frozen `.ai.limits` snapshot representing the rate limit state returned by provider HTTP headers for *that specific request*. Note that `.ai.limits` is guaranteed only for provider-backed network requests where rate-limit headers are returned by the selected provider; results resolved via native parsing (`provider: 'native'`) or cache hits (`provider: 'cache'`) may omit `.ai.limits` (`undefined`).
```typescript
const dt = await parseAI("The third Friday of next month");
if (dt.ai?.limits) {
console.log(`Remaining Tokens: ${dt.ai.limits.remainingTokens}`);
console.log(`Remaining Requests: ${dt.ai.limits.remainingRequests}`);
console.log(`Resets At: ${dt.ai.limits.resetAt?.format('{hh}:{mi}:{ss}')}`);
}
```
### 2. Global State Utility (`getAiRateLimits()`)
For quick status checks or global monitoring across the application lifecycle, `getAiRateLimits()` exposes the stats from the most recent LLM request:
```typescript
import { getAiRateLimits } from '@magmacomputing/tempo-plugin-ai';
// Returns global stats from the most recent LLM proxy request
const stats = getAiRateLimits();
if (stats) {
console.log(`Remaining Tokens: ${stats.remainingTokens}`);
console.log(`Remaining Requests: ${stats.remainingRequests}`);
console.log(`Limits Reset At: ${stats.resetAt?.format('{hh}:{mi}:{ss}')}`);
}
```
## Handling Quota Exhaustion (429s)
If you actually exhaust your quota and the provider rejects the request (e.g., HTTP 429 Too Many Requests), the plugin will instantly attempt to failover to the next provider in your configuration array.
If all providers fail, the plugin will throw a `TempoAiError`. This custom error class includes a highly valuable `retryAt` property:
```typescript
import { parseAI, TempoAiError } from '@magmacomputing/tempo-plugin-ai';
try {
const dt = await parseAI("The third Friday of next month");
} catch (error) {
if (error instanceof TempoAiError && error.code === 429) {
// Safely queue the remaining batch of dates until your minute-limit resets!
console.warn(`All API quotas exhausted. Retry after: ${error.retryAt}`);
}
}
```
## Cache Management
By default, Tempo AI functions integrate directly with `Tempo.cache` (`BoundedCache`) to store pre-resolved ISO 8601 results, drastically reducing LLM API calls and network latency on repetitive queries.
### Array Processing & Token Economics
When you pass an array of inputs to AI functions (such as `parseAI`), the plugin intentionally does **not** batch them into a single massive LLM request. Instead, it iterates through the array and processes each item individually.
This is by design for three critical reasons:
1. **Cache Efficiency**: Individual processing allows AI functions to instantly resolve duplicate strings against `Tempo.cache`, saving massive amounts of API tokens. If you pass an array of 10,000 dates, but only 1,000 are unique, only 1,000 network requests are made.
2. **Token Economics**: A single request consumes ~100 tokens (System Prompt + User String + Output ISO). Given that frontier models cost cents per million tokens, the risk of array-misalignment bugs (see below) far outweighs the negligible savings of batching system prompts.
3. **Deterministic Safety**: LLMs are language models, not arrays. If you pass 50 strings in a single prompt, smaller models often hallucinate and return 49 strings, completely breaking your array indexing. By dispatching items individually, we guarantee a strict 1:1 index alignment, prevent hallucinations from corrupting sibling entries, and allow granular per-item error isolation when `softErrors: true` is enabled.
> [!WARNING]
> **Granular Time Gotcha**: The cache key is automatically salted with the **calendar date** (`yyyy-mm-dd`) of the execution anchor. By default this uses the system execution date, but when `options.anchor` is explicitly set, it uses the caller-provided anchor date. Note that keeping a fixed anchor date retains the same cache key across midnight boundaries, so an automatic midnight cache miss is not guaranteed.
### Soft Errors in Array Batches
When processing arrays of inputs, an unparseable input or provider failure on one item will throw an error and reject the entire batch operation by default. Passing `softErrors: true` allows batch operations to continue processing all items and return per-item failure representations:
* **For `parseAI`**: Failed array items return an invalid `Tempo` instance (`isValid === false`).
* **For Structured Functions (`formatAI`, `extractAI`, `diffAI`, `contextAI`)**: Failed array items return the typed `TempoAiError` object directly in that array position.
```typescript
// 1. parseAI with softErrors returns invalid Tempo instances
const dates = await parseAI(["Thanksgiving 2026", "INVALID_PROMPT_STRING"], { softErrors: true });
console.log(dates[0].isValid); // true
console.log(dates[1].isValid); // false
// 2. Structured functions return TempoAiError objects into the array
import { formatAI, TempoAiError } from '@magmacomputing/tempo-plugin-ai';
const formatted = await formatAI([validDate, invalidDate], 'casual tone', { softErrors: true });
if (formatted[1] instanceof TempoAiError) {
console.warn(`Format failed with code: ${formatted[1].code}`);
}
```
### Static Glossary Seeding
In addition to dynamic cache lookups, `initAI` can be initialized with a pre-seeded `BoundedCache` or synchronous `Map` containing immortal static business terms (e.g. company glossaries). Static entries bypass TTL expiration and LLM network requests:
```typescript
const glossary = new Map([
['fiscal_q3_start', '2026-07-01T00:00:00Z'],
['annual_shutdown', '2026-12-24T00:00:00Z']
]);
initAI({
providers: [{ id: 'openai', key: process.env.OPENAI_API_KEY }],
cache: glossary
});
const start = await parseAI('fiscal_q3_start'); // Resolves instantly from static cache without hitting network!
```
### Bypassing Cache & Forcing Network Requests
Passing `cache: false` disables reading and writing to the cache, but native pre-parsing may still resolve standard phrases. To guarantee an LLM provider request while disabling caching of the response, combine `force: true` with `cache: false`:
```typescript
// Forces an LLM network request and prevents reading or writing to cache
const dt = await parseAI("The last Friday before Christmas", { force: true, cache: false });
```
### Evicting Bad Parses
If the LLM hallucinates or returns an incorrect absolute date, you can explicitly purge the string from the cache:
```typescript
import { aiCache } from '@magmacomputing/tempo-plugin-ai';
// Evict a single string
await aiCache.clear("2nd tuesday in nov");
// Or purge all AI cached entries
await aiCache.clear();
```
### Forcing a Refresh
If you want to explicitly query the LLM again and *overwrite* the existing cache entry with the new result, use the `force: true` flag:
```typescript
const dt = await parseAI("Q3_START", { force: true });
```
### Extensible Caching & Async Storage Adapters (`AiCacheAdapter`)
By default, parsed AI responses are cached in memory using `Tempo.cache` (`BoundedCache`). For distributed serverless environments (e.g. Next.js, Cloudflare Workers, Express) or cluster nodes, you can pass a custom synchronous or asynchronous storage adapter (`AiCacheAdapter`):
```typescript
import { initAI, parseAI, type AiCacheAdapter } from '@magmacomputing/tempo-plugin-ai';
import { Redis } from '@upstash/redis';
const redis = new Redis({ url: process.env.UPSTASH_URL!, token: process.env.UPSTASH_TOKEN! });
// Implement custom async Redis storage adapter with namespacing & prefix deletion support
const redisAdapter: AiCacheAdapter = {
get: async (key) => (await redis.get(`tempo:ai:${key}`)) ?? undefined,
set: async (key, value, ttlMs) => {
if (ttlMs !== undefined) await redis.set(`tempo:ai:${key}`, value, { px: ttlMs });
else await redis.set(`tempo:ai:${key}`, value);
},
delete: async (key) => {
await redis.del(`tempo:ai:${key}`);
},
clear: async (prefix) => {
const pattern = prefix ? `tempo:ai:${prefix}*` : `tempo:ai:*`;
let cursor = '0';
do {
const [nextCursor, keys] = await redis.scan(cursor, { match: pattern, count: 100 });
cursor = nextCursor;
if (keys.length > 0) await redis.del(...keys);
} while (cursor !== '0');
}
};
initAI({
providers: [{ id: 'groq', key: process.env.GROQ_API_KEY!, ttl: 7200000 }], // Provider-specific TTL (2 hours)
cacheAdapter: redisAdapter,
ttl: 3600000 // Global default TTL (1 hour)
});
// Call-site TTL override (15 minutes)
const dt = await parseAI("next Monday at 9am", { ttl: 900000 });
```
### Cascading TTL Resolution Policies
The plugin calculates cache TTL per entry using a strict resolution hierarchy:
1. **Call-site `options.ttl`**: `parseAI(prompt, { ttl: 900000 })`
2. **Provider-level `provider.ttl`**: `providers: [{ id: 'groq', ttl: 7200000 }]`
3. **Global `initAI({ ttl: 3600000 })`**
4. **Default TTL**: `3,600,000` ms (1 hour)
### Fail-Open Cache Resilience
Custom storage adapter calls (`adapter.get` and `adapter.set`) are wrapped in safe error handlers. If an external Redis instance crashes or encounters a network partition, the plugin logs a debug warning (if `debug: true`) and gracefully fails open to direct LLM resolution without crashing the application request.
---
# Document: 9-plugins/ai.recurrence.md
# `recurrenceAI` β Recurrence Rules & Schedule Translation
`recurrenceAI()` provides multi-directional translation between natural language repeating schedule descriptions (*"Every 2nd Tuesday of the month at 3pm"*) and RFC 5545 **RRULE strings**, generating paged `Tempo` instance batches on demand.
## Basic Usage
```typescript
import { recurrenceAI, initAI } from '@magmacomputing/tempo-plugin-ai';
// 1. Initialize provider configuration
await initAI({
providers: [{ id: 'groq', key: process.env.GROQ_API_KEY }]
});
// 2. Natural Language Input (human-in -> RRule & Tempo batches out)
const result = await recurrenceAI("Every 2 weeks on Friday at 9am", {
locale: 'fr-FR', // Output localized human summary
count: 5 // Default batch size
});
console.log(result.rrule); // "FREQ=WEEKLY;INTERVAL=2;BYDAY=FR;BYHOUR=9"
console.log(result.summary); // "Chaque 2 semaines le vendredi Γ 09:00"
console.log(result.isFinite); // false (recurs indefinitely)
console.log(result.size); // Infinity
```
## Stateful Paged Batching (`.take(n)`)
`recurrenceAI` maintains an internal date cursor. Calling `.take(n)` repeatedly returns consecutive batches of `Tempo` instances:
```typescript
// Fetch initial batch of 5 items
const batch1 = result.take(5);
console.log(batch1.length); // 5
// Fetch NEXT batch of 5 items starting right where batch 1 left off
const batch2 = result.take(5);
console.log(batch2.length); // 5
```
When a finite schedule (e.g. `COUNT=10`) completes, `.take(n)` returns an empty array `[]` to signal exhaustion:
```typescript
const finiteResult = await recurrenceAI("FREQ=MONTHLY;BYDAY=1MO;COUNT=2");
const b1 = finiteResult.take(2); // [ Tempo(Month 1), Tempo(Month 2) ]
const b2 = finiteResult.take(2); // [] (Exhausted)
```
## Native RRULE Parsing (Zero Network Overhead)
Passing a raw RFC 5545 RRULE string directly to `recurrenceAI` bypasses network LLM calls entirely (`provider: 'rrule-parser'`), functioning as an instant native parser:
```typescript
const native = await recurrenceAI("FREQ=MONTHLY;BYDAY=1MO;COUNT=12");
console.log(native.provider); // "rrule-parser" (Instant native resolution)
console.log(native.isFinite); // true
console.log(native.size); // 12
```
## Lazy Iteration (`for...of`)
`TempoRecurrenceResult` implements `[Symbol.iterator]`, allowing lazy iteration over occurrences up to the batch limit (`count: 5` by default).
When iterating over open-ended schedules (`isFinite === false`), build a `break` termination clause into the loop:
```typescript
const schedule = await recurrenceAI("Every Friday");
for (const occurrence of schedule) {
// Always include a termination condition for open-ended schedules
if (occurrence.yy > 2028) break;
console.log(occurrence.format('{yyyy}-{mm}-{dd}'));
}
```
## Result Interface
```typescript
export interface TempoRecurrenceResult {
/** Standard RFC 5545 RRULE string (e.g. 'FREQ=WEEKLY;BYDAY=TU') */
rrule: string;
/** Localized human-friendly schedule summary */
summary: string;
/** Reasoning / explanation of how the recurrence pattern was parsed */
reasoning?: string;
/** True if schedule has an explicit end date or count limit; false if infinite */
isFinite: boolean;
/** Total count of occurrences if finite, or Infinity (Number.POSITIVE_INFINITY) */
size: number;
/** Advances cursor and returns the next batch of N Tempo instances */
take(count?: number): Tempo[];
/** Lazy generator yielding Tempo instances */
[Symbol.iterator](): Generator;
/** Confidence score (0.0 to 1.0) */
confidence: number;
/** Provider ID responsible for processing or 'rrule-parser' */
provider: string;
}
```
---
# Document: 9-plugins/ai.schedule.md
# `scheduleAI` β Intelligent Appointment Booking & Conflict Resolution
`scheduleAI()` provides automated slot booking, calendar conflict detection, working hour verification, and iterative slot bumping. It evaluates a natural language booking request (e.g. *"45 min sync next Wednesday afternoon"*) alongside a list of existing busy intervals and working hours to find the best available appointment slots.
---
## Basic Usage
```typescript
import { scheduleAI, initAI } from '@magmacomputing/tempo-plugin-ai';
// 1. Initialize provider
await initAI({
providers: [{ id: 'groq', key: process.env.GROQ_API_KEY }]
});
// 2. Schedule a meeting avoiding team standups
const booking = await scheduleAI("45 min sync next Wednesday afternoon", {
anchor: "2026-08-10 09:00",
timeZone: "America/New_York",
events: [
{ start: "2026-08-12 14:00", end: "2026-08-12 15:00", title: "Team standup" }
],
workingHours: { start: "09:00", end: "17:00" } // only match New York business hours
});
console.log(booking.start.format('{yyyy}-{mm}-{dd} {hh}:{mi}')); // 2026-08-12 15:00
console.log(booking.end.format('{yyyy}-{mm}-{dd} {hh}:{mi}')); // 2026-08-12 15:45
console.log(booking.durationMinutes); // 45
console.log(booking.ai?.conflictBumped); // true (pushed past team standup)
```
---
## Configuration Options (`TempoScheduleOptions`)
| Option | Type | Description |
| :--- | :--- | :--- |
| **`anchor`** | `TempoDateInput` | Anchor reference time to evaluate relative dates from. Defaults to workstation/browser current time. |
| **`events`** | `Array` | A list of existing busy calendar intervals or booked events that the meeting must not overlap with. |
| **`workingHours`** | `TempoWorkingHours` | Daily time window constraint (HH:MM formats) inside which slots must fit. |
| **`timeZone`** | `string` | Target IANA timezone to calculate the booking slot and boundaries within. |
| **`minConfidence`**| `number` | Minimum confidence score threshold (0.0 to 1.0) required to return a valid slot. |
| **`mode`** | `AiMode` | Concurrency routing strategy (`fallback`, `race`, `consensus`, `hedged`, `roundrobin`, `adaptive`). Refer to the [Multi-Provider Execution Modes Guide](./ai.modes.md). |
### `TempoInterval` Interface
```typescript
export interface TempoInterval {
start: Tempo;
end: Tempo;
}
```
### Event Input Shape (`ScheduleEventInput`)
The `events` (or `intervals`) option accepts raw event objects, continuous `TempoInterval` pairs, native `Interval` instances, or `[start, end]` tuples:
```typescript
export type ScheduleEventInput =
| { start: TempoDateInput; end: TempoDateInput; title?: string; label?: string }
| TempoInterval
| Interval
| [TempoDateInput, TempoDateInput]
| TempoDateInput;
```
---
## Result Schema (`TempoScheduleResult`)
```typescript
export interface TempoScheduleResult {
/** The calculated start timestamp of the selected slot */
start: Tempo;
/** The calculated end timestamp of the selected slot */
end: Tempo;
/** The resolved slot represented as a Tempo Interval object */
slot: Interval;
/** The duration of the resolved meeting in minutes */
durationMinutes: number;
/** Alternative available slot intervals that also satisfy all constraints */
alternatives: Interval[];
/** Metadata on the AI resolution process */
ai?: {
provider: string;
confidence: number;
conflictBumped: boolean; // True if the slot was pushed/bumped to avoid a busyEvent conflict
reasoning?: string;
};
}
```
---
## Internal Conflict Bumping Engine
When an LLM proposes a slot (like Wednesday at 2:00 PM), `scheduleAI` doesn't just trust it blindly. It runs a deterministic check using Tempo's native `Interval.overlaps()` method against your `events` list:
1. If the proposed slot overlaps with any busy event, the engine shifts (bumps) the slot forward iteratively.
2. It re-verifies the new slot against all other busy events and working hours constraints.
3. If it successfully lands on a free spot, it marks `ai.conflictBumped: true`.
4. If no free spot can be found within the proposed date frame, it returns a failed interval with low confidence.
This decoupled architecture ensures that the LLM is used for parsing intent (finding the duration and preferred time slot) while Tempo's math engine handles the strict conflict prevention.
---
# Document: 9-plugins/ai.security.md
# Security & Privacy Architecture
The `@magmacomputing/tempo-plugin-ai` plugin is engineered with a **"Privacy and Security by Default"** philosophy. Because date parsing and calendar scheduling frequently interact with Personally Identifiable Information (PII)βsuch as meeting attendees, emails, phone numbers, and sensitive notesβthe plugin incorporates multi-layered security controls to protect user data across transit, runtime memory, log output, and caching tiers.
```mermaid
flowchart TD
subgraph Input ["1. Ingress & Transport"]
User["User Prompt / Event Data"] -->|"HTTPS / TLS 1.3 Enforcement"| Transport["Secure Transport Layer"]
end
subgraph Memory ["2. In-Memory Processing & Storage"]
Transport --> Schema["Rigid Schema Validation & Grounding"]
Schema --> Runtime["In-Memory Execution (Full Fidelity Access)"]
Runtime --> Cache["Partitioned Multi-Tier Cache (Namespaced by Tenant/TZ/Locale)"]
end
subgraph Egress ["3. Egress & Smart Debugging"]
Runtime --> ProxyMeta["Proxy-Wrapped Result Objects (attachCustomInspect)"]
ProxyMeta --> Logic["Application Business Logic (100% Raw Data Access)"]
ProxyMeta -->|"console.log() / util.inspect"| Logger["Smart Logger (logDebug) β’ NODE_ENV=production: Auto-Masked PII β’ NODE_ENV=development: Full Diagnostic Logs"]
end
```
---
## 1. Smart Debug Telemetry & PII Hardening
Debugging LLM integrations traditionally presents a major security dilemma: enabling debug logs often inadvertently dumps raw prompts containing sensitive user emails, phone numbers, and auth tokens into centralized log aggregators (e.g. Datadog, CloudWatch, Sentry).
`@magmacomputing/tempo-plugin-ai` significantly mitigates this risk through **Smart Debug Infrastructure**:
### Universal Environment Detection & Zero-Config Safety
* **Unified Flag**: Telemetry is enabled directly using `{ debug: true }` on individual requests or globally via `initAI({ debug: true })`.
* **Environment-Aware Sanitization**: The runtime automatically inspects `NODE_ENV`. In production environments (`NODE_ENV === 'production'`), all debug logs and terminal outputs automatically sanitize sensitive data before printing to `console.log` or `console.warn`.
* **Development Fidelity**: In non-production environments (local development, testing), full diagnostic strings are preserved for seamless prompt debugging.
### Automatic PII Redaction
In production mode, all debug telemetry is scrubbed through automated regex sanitizers:
* **Email Addresses**: Masked to initial and domain (e.g., `john.doe@enterprise.com` β `j***@enterprise.com`).
* **Phone Numbers**: Masked to last four digits (e.g., `+1-555-867-5309` β `***-***-5309`).
* **Bearer & API Tokens**: Redacted with prefix/suffix preservation (e.g., `Bearer sk-proj-1234...` β `Bearer sk-p...1234`).
* **Length Bounds**: Exceptionally long strings (> 256 characters) are safely truncated with character count annotations to prevent log bloat and denial-of-service attacks.
```typescript
import { parseAI, initAI } from '@magmacomputing/tempo-plugin-ai';
await initAI({
providers: [{ id: 'groq', key: process.env.GROQ_API_KEY }],
debug: true // Safe in all environments
});
// Input containing sensitive attendee data
const date = await parseAI("Meeting with john.smith@company.org (call 555-123-4567) next Friday");
// In Production, console.log(date.ai) outputs:
// {
// provider: 'groq',
// confidence: 0.98,
// rawPrompt: 'Meeting with j***@company.org (call ***-***-4567) next Friday',
// reasoning: 'Parsed meeting for next Friday with j***@company.org'
// }
```
---
## 2. Tamper-Resistant Proxy Introspection
All AI return objects (`Tempo.ai`, `TempoAiFormatResult`, `TempoAiExtractResult`, `TempoAiDiffResult`, `TempoScheduleResult`, `TempoRecurrenceResult`) utilize JavaScript `Proxy` wrappers and Node.js custom inspection hooks (`Symbol.for('nodejs.util.inspect.custom')` and `.toJSON()`):
1. **Terminal & Log Safety**: When an AI result object is logged via `console.log()`, `util.inspect()`, or serialized for telemetry, the custom inspection hook intercepts the call and outputs the PII-masked view.
2. **100% In-Memory Code Integrity**: In-memory property access within your application code (`date.ai?.rawPrompt`, `result.events[0].rawText`, `res.reasoning`) retains full, unmodified data fidelity.
3. **Deep Immutability**: Metadata properties attached to `Tempo` instances are frozen using `Object.freeze()`, preventing runtime tampering or prototype pollution by downstream code or dependencies.
```typescript
const result = await formatAI(targetDate, 'Notify alice.cooper@domain.com');
// 1. Terminal / Log Aggregators see sanitized PII in production:
console.log(result);
// => { formatted: '...', reasoning: '... client a***@domain.com ...' }
// 2. Your application code receives full raw fidelity:
const rawReasoning = result.reasoning;
// => "Formatted for client alice.cooper@domain.com"
```
---
## 3. Transport Security & Network Hardening
### Enforced HTTPS / TLS
* **Strict HTTPS Requirement**: All network communication with upstream LLM APIs and remote configuration servers must use HTTPS with modern TLS (TLS 1.2 or TLS 1.3).
* **Plaintext HTTP Disallowed**: Unencrypted HTTP endpoints are rejected at runtime, with an exception allowed exclusively for `localhost` origins during local development or unit testing with mock servers.
### Dynamic Manifest Host Verification
* **Trusted Remote Endpoints**: When `loadRemoteManifest` resolves provider manifests, it enforces trusted origin allowlists.
* **Provider URL Sanitization**: Any dynamic endpoint received via remote manifests or the `fetchDefaults` hook is verified before runtime merging. Disallowed hosts are rejected and stripped to prevent server-side request forgery (SSRF).
---
## 4. Credential Isolation & BYOK Architecture
### Automated In-Memory Key Redaction
* Calling `getAiConfig()` returns a sanitized, read-only configuration snapshot.
* All provider `key` values, authorization tokens, and shared secrets are permanently replaced with `[REDACTED]`, ensuring secrets cannot be leaked via diagnostic endpoints or error monitors.
### Dynamic Secret Vaults & Automated Key Rotation
* Provider `key` parameters support synchronous and asynchronous supplier functions (`AsyncEvaluable` / `() => Promise | string`), while `url`, `model`, and temporal context fields accept synchronous suppliers (`Evaluable`).
* **Enterprise Secret Vaults**: Instead of pinning long-lived static API keys in memory, applications can integrate cloud key vaults (e.g. AWS Secrets Manager, HashiCorp Vault, Azure Key Vault, Doppler):
```typescript
initAI({
providers: [
{
id: 'openai',
// Evaluated just-in-time on every provider HTTP dispatch
key: async () => await secretVault.getSecret('OPENAI_API_KEY')
}
]
});
```
* **Multi-Tenant / Per-Request Key Isolation**: In SaaS applications where each tenant supplies their own BYOK credentials, resolve keys dynamically from the active request context without re-initializing global AI state:
```typescript
initAI({
providers: [
{
id: 'openai',
// Pulls tenant-specific key from AsyncLocalStorage or request session
key: () => {
const tenant = tenantStore.getStore();
if (!tenant) throw new Error('No tenant context found');
return tenant.openaiApiKey;
}
}
]
});
```
* **Short-Lived & OAuth Token Refreshers**: Dynamic suppliers allow automatic token refresh for short-lived credentials (e.g. Google Cloud Vertex AI / Azure Entra ID OAuth tokens) without service disruption:
```typescript
initAI({
providers: [
{
id: 'gemini',
key: async () => (await authClient.getAccessToken()).token
}
]
});
```
* Keys are fetched just-in-time prior to the HTTP request and never stored in plain text in persistent global state, enabling zero-downtime key rotation.
### Frontend Zero-Storage Principle
* **No Client-Side Secrets**: LLM API keys must **never** be bundled into client-side single-page applications (React, Vue, Svelte) or stored in browser storage (`localStorage`, `sessionStorage`, `IndexedDB`).
* **Proxy Architecture**: Public frontend web applications must route requests through a self-hosted backend proxy or secure AI Gateway (Cloudflare Worker, Next.js API Route) where private API keys are kept server-side.
---
## 5. Ephemeral Processing & Partitioned Caching
### Zero External Telemetry Policy
* The plugin does not transmit telemetry, analytics, or prompt logs to external tracking servers.
* Prompt processing and temporal computations occur ephemerally during request execution.
### Partitioned Multi-Tier Caching
* **Namespaced Cache Keys**: Cache keys are generated with multi-factor domain partitioning (e.g., `diff::`, `format::`, `extract::`) incorporating the prompt text, anchor epoch, target timezone, locale, calendar system, and regional parameters to prevent contextual collision.
* **Storage Lifecycle**: Cached entries persist in the local `Tempo.cache` (`BoundedCache`) or caller-provided `AiCacheAdapter` (e.g. Redis, KV) strictly until TTL expiration or LRU capacity eviction.
* **Granular Bypass Controls**: Operations requiring zero cache persistence can supply `cache: false` or `force: true` on any individual request, or programmatically flush entries using `await aiCache.clear()`.
---
## 6. Schema Enforcement & Hallucination Defense
Large Language Models can occasionally hallucinate dates or output non-deterministic formats. `@magmacomputing/tempo-plugin-ai` prevents invalid data propagation through strict input/output boundaries:
1. **Rigid Schema Validation**: All provider completions are validated against deterministic schemas and regex patterns prior to object construction.
2. **Confidence Threshold Gating**: The plugin enforces configurable `minConfidence` thresholds (e.g. `minConfidence: 0.85`). Results falling below the threshold throw a typed `TempoAiError` or trigger automatic fallback.
3. **Deterministic Grounding Fallbacks**: Grounding metrics (such as business days, calendar day offsets, and duration calculations) are verified using deterministic `Tempo` calculations rather than unverified LLM assumptions.
---
## 7. Residual Risks & Threat Model Matrix
> [!IMPORTANT]
> **Primary Production Strategy**: The primary recommendation for production environments is to keep **`debug: false`** (the default). Smart Debug is designed as an automated safety net to prevent catastrophic PII leaks when developers troubleshoot live issues, but no automated sanitization layer can eliminate 100% of risk when raw diagnostic telemetry is captured.
The following matrix documents residual threat vectors and recommended mitigations:
| Threat Vector | Source | Risk Level | Architectural Behavior | Recommended Mitigation |
| :--- | :--- | :---: | :--- | :--- |
| **Direct Primitive Logging** | Developer `console.log(res.reasoning)` | Medium | Evaluates to the raw in-memory string and bypasses object inspection hooks. | Log entire result objects (`console.log(res)`) or leave `debug: false`. |
| **Object Spread Logging** | `console.log({ ...res })` | Low | Spreading copies raw enumerable keys into a plain object without non-enumerable inspect symbols. | Log the object directly (`console.log(res)`) rather than shallow spreading. |
| **Network-Layer APM Tracing** | Datadog, OpenTelemetry, Sentry HTTP capture | High | APM agents monkey-patching `fetch` capture raw outbound HTTP payloads in transit. | Disable full HTTP body capture on LLM routes in your APM configuration. |
| **Semantic PII** | Unstructured names, physical addresses, health info | Medium | Regexes catch structured PII (emails, phones, tokens) but not unstructured names/addresses. | Rely on payload truncation limits (< 256 chars) and avoid `debug: true` on sensitive workflows. |
| **Environment Variable Drift** | `NODE_ENV` not set or misconfigured | Low | Logger checks `NODE_ENV` (`production`, `prod`, `live`) and `PROD=true`. If unset, defaults to dev mode. | Verify deployment manifests explicitly export `NODE_ENV=production`. |
| **External Cache Driver Logs** | Third-party Redis/DB client debug logs | Low | Distributed cache adapters store raw JSON required to rehydrate `Tempo` instances. | Ensure production Redis/database clients have debug logging disabled. |
---
# Document: 9-plugins/astro.index.md

# @magmacomputing/tempo-plugin-astro
This is a Community plugin for the [Tempo](https://github.com/magmacomputing/magma) library that calculates exact astronomical seasons (Equinoxes and Solstices via `t.term.astro`, `t.term.astronomy`, `t.term.equinox`, `t.term.solstice`) using the **Jean Meeus polynomial algorithm**.
> [!NOTE]
> **Mean-Polynomial Approximation (Ch. 27)**
> This plugin specifically implements the mean-polynomial calculation from Chapter 27 of Meeus' *Astronomical Algorithms*. To keep the library extremely lightweight, it omits the massive periodic correction tables required for exact apparent calculations. It is strictly enforced to support the mathematical range of **-1000 to +3000 AD**.
Because these are true astronomical calculations, the plugin precisely determines solar solstice and equinox boundaries. It is also **hemisphere-aware**: by configuring your Tempo instance with a `sphere` (e.g., `sphere: 'south'`), the plugin accurately flips the Vernal Equinox from Spring to Autumn.
::: info Meteorological vs Astronomical
Unlike Tempo's built-in **Meteorological** `season` Term β which rigidly snaps to the 1st day of calendar months β this **Astronomical** plugin calculates the dynamic, true solar boundaries.
:::
## Installation
```bash
npm install @magmacomputing/tempo-plugin-astro
```
## Usage
```typescript
import { Tempo } from '@magmacomputing/tempo';
import '@magmacomputing/tempo-plugin-astro'; // Auto-registers Astro terms!
const t = new Tempo('2026-03-20', { sphere: 'north' });
// Get the Astronomical Event key ('Vernal', 'Summer', 'Autumnal', 'Winter')
console.log(t.term.astro);
// Output: 'Vernal'
// Query via aliases
console.log(t.term.equinox); // 'Vernal' or 'Autumnal'
console.log(t.term.solstice); // 'Summer' or 'Winter'
// Get full Astronomical metadata
console.log(t.term.astronomy);
// Output: { key: 'Vernal', season: 'Spring', event: 'Equinox', sphere: 'north', ... }
```
### Response Payload
#### Astronomical Seasons (`t.term.astronomy`)
```javascript
{
key: 'Vernal', // Flips to 'Autumnal' if sphere is set to 'south'
season: 'Spring', // Flips to 'Autumn' if sphere is set to 'south'
sphere: 'north', // Flips to 'south' if sphere is set to 'south'
event: 'Equinox',
group: 'astronomy',
year: 2026,
month: 3,
day: 20,
hour: 14,
minute: 45,
second: 0,
start: ,
end:
}
```
::: tip Did you know?
**Seasons:** `t.term.astronomy.season` returns the *Astronomical* season calculated by the precise timing of solstices and equinoxes. This will often differ from `t.term.season.key` in the core library, which uses standard Meteorological/Civil calendar boundaries (e.g., 1st of the month).
For real-time solar daylight/twilight, lunar phase, and tidal tracking, install `@magmacomputing/tempo-plugin-celestial`.
:::
## Licensing
This is a **Community** plugin. It is completely free and open-source for personal and commercial use under the MIT license.
---
# Document: 9-plugins/batch.index.md

# @magmacomputing/tempo-plugin-batch
This is a Community plugin for the [Tempo](https://github.com/magmacomputing/magma) library that parallelizes massive epoch mutation tasks across worker threads utilizing lock-free `SharedArrayBuffer` architecture for extreme throughput.
::: tip Perfect For
Heavy data ETL pipelines, massive IoT telemetry ingestion, financial ledger chronometrics, and any parallel bulk date-processing workloads.
:::
## Installation
```bash
npm install @magmacomputing/tempo-plugin-batch
```
## Usage
```typescript
import { Tempo } from '@magmacomputing/tempo';
import { BatchPlugin } from '@magmacomputing/tempo-plugin-batch';
Tempo.init({
extends: [BatchPlugin]
});
// Assume `epochs` is a massive array of integers representing timestamps
const epochs = [1700000000000, 1700000001000, /* ... millions more ... */];
// Mutate millions of dates concurrently using the worker pool
// The engine automatically splits the payload and offloads to workers!
const batchResult = await Tempo.batch(epochs, { weeks: 1 });
console.log(batchResult); // Returns an array of mutated timestamp integers
```
### Rehydration
By default, `Tempo.batch` returns an array of primitive `number` timestamps to maximize throughput over the thread boundary. If you need fully-fledged `Tempo` objects back, pass `{ rehydrate: true }`:
```typescript
// Returns an array of Tempo instances instead of integers
const tempoInstances = await Tempo.batch(epochs, { weeks: 1 }, { rehydrate: true });
```
### Graceful Degradation
If the host environment does not support `SharedArrayBuffer` (or if it is blocked by CORS/COOP headers in the browser), the orchestrator intelligently and transparently falls back to using traditional `postMessage` structural cloning chunks to ensure execution never halts.
## Licensing
This is a **Community** plugin. It is completely free and open-source for personal and commercial use under the MIT license.
---
# Document: 9-plugins/celestial.index.md

# @magmacomputing/tempo-plugin-celestial
This is a Community plugin for [Tempo](https://github.com/magmacomputing/magma) providing location-aware solar twilight events (`t.term.sun`, `t.term.solar`) and real-time lunar cycle phases (`t.term.moon`, `t.term.lunar`).
## Installation
```bash
npm install @magmacomputing/tempo-plugin-celestial
```
## Features
- **Solar Day Cycles**: Calculates `daylight`, `night`, `civil-twilight`, `nautical-twilight`, and `astronomical-twilight`.
- **Ephemeris Data**: Returns `sunrise`, `sunset`, `noon`, total `daylightDurationMs`, and explicit `latitude`/`longitude` for given coordinates.
- **Lunar Phase & Ephemeris**: Calculates 8 discrete lunar phase states (`new-moon`, `waxing-crescent`, etc.), illumination 0.0β1.0 fraction, age in days, hemisphere-aware emoji indicators, and location-aware `moonrise` and `moonset` events.
- **Astronomical Tidal Mechanics (`TidalTerm`)**: Provides pure astronomical solar/lunar alignment calculations (`t.term.tide`, `t.term.tides`) for `spring`, `neap`, and `normal` tides, alongside `isKingTide` perigee indicators.
> [!NOTE]
> **Pure Astronomical Calculations**:
> Tidal state resolution relies exclusively on deterministic celestial mechanics (solar-lunar ecliptic longitude alignment (ΞΞ») and anomalistic lunar perigee proximity) for reproducible, offset-independent math across all time zones and locations.
## Geographic Coordinates & Null Contract
> [!IMPORTANT]
> **Location-Dependent Null Contract**:
> - **Global Astronomical Properties** (`t.term.moon`, `t.term.lunar.phase`, `t.term.tides.isSpringTide`, `t.term.tides.alignmentDeg`) resolve location-independently and are always computed.
> - **Geo-Dependent Properties** (`t.term.sun`, `solar.sunrise`, `solar.sunset`, `solar.noon`, `lunar.moonrise`, `lunar.moonset`, `tides.lunarTideMinute`) evaluate to `null` when geographic coordinates (`geo: { lat, lng }`) are omitted.
> - **Distinction**: Property access on `t.term` evaluates to `undefined` if `CelestialPlugin` is not loaded, and to `null` if the plugin is active but location coordinates were not supplied. When `debug >= 1` is enabled in `Tempo` configuration, a developer warning is logged when evaluating geo-dependent keys without coordinates.
### Obtaining Coordinates
Use `geoLookup()` from `@magmacomputing/tempo-plugin-geo` to automatically resolve location coordinates across both browser and server environments:
```bash
npm install @magmacomputing/tempo-plugin-geo
```
> [!WARNING]
> **Geolocation Behavior**:
> - **Browser**: On first invocation, `geoLookup()` will prompt the user for permission to access hardware location services.
> - **Server**: In Node.js or server environments without GPS hardware, coordinates are resolved via IP geolocation representing the physical server/datacenter network location.
```typescript
import { Tempo } from '@magmacomputing/tempo';
import { geoLookup } from '@magmacomputing/tempo-plugin-geo';
import '@magmacomputing/tempo-plugin-celestial';
// Automatically resolves location coordinates via browser hardware or server IP
const geo = await geoLookup();
const t = new Tempo({ geo });
console.log(t.term.sun); // 'daylight' or 'night'
console.log(t.term.lunar.moonrise); // Tempo instance or null when no rise occurs on the local date
console.log(t.term.tide); // 'spring', 'neap', or 'normal'
```
## Usage
```typescript
import { Tempo } from '@magmacomputing/tempo';
import '@magmacomputing/tempo-plugin-celestial';
const t = new Tempo('2026-06-21T12:00:00Z', { geo: { lat: 40.7128, lng: -74.006 } });
// --- Solar Day State & Phase Querying ---
console.log(t.term.sun); // 'daylight'
console.log(t.term.solar.key); // 'daylight'
console.log(t.term.solar.phase); // 'Daylight'
console.log(t.term.solar.phases); // ['night', 'astronomical-twilight', 'nautical-twilight', 'civil-twilight', 'daylight']
console.log(t.term.solar.sunrise); // Tempo instance for local sunrise
console.log(t.term.solar.geo); // { latitude: 40.7128, longitude: -74.006 }
// --- Lunar Phase & Ephemeris ---
console.log(t.term.moon); // 'waxing-crescent'
console.log(t.term.lunar.phase); // 'Waxing Crescent'
console.log(t.term.lunar.phases); // ['new-moon', 'waxing-crescent', 'first-quarter', 'waxing-gibbous', 'full-moon', 'waning-gibbous', 'third-quarter', 'waning-crescent']
console.log(t.term.lunar.illumination); // 0.45
console.log(t.term.lunar.moonrise); // Tempo instance for local moonrise (or null)
// --- Astronomical Tidal Mechanics ---
console.log(t.term.tide); // 'spring', 'neap', or 'normal'
console.log(t.term.tides.alignmentDeg); // Solar-lunar alignment angle (0..360Β°)
console.log(t.term.tides.isSpringTide); // true during Syzygy (New or Full Moon)
console.log(t.term.tides.isNeapTide); // true during Quadrature (1st or 3rd Quarter)
console.log(t.term.tides.isKingTide); // true when Spring Tide aligns with Lunar Perigee
// --- Programmatic Navigation ---
// Use .phases to dynamically navigate to the next lunar phase
const nextPhaseKey = t.term.lunar.phases[t.term.lunar.index % 8];
const nextMoonTempo = t.set(`#lunar.${nextPhaseKey}`);
```
## Phase & State Discovery Metadata
`LunarTerm`, `SolarTerm`, and `TidalTerm` expose immutable, frozen array references (`Object.freeze`) containing all valid identifiers for terms resolution:
- **Static Term References**: `LunarTerm.phases`, `SolarTerm.phases`, and `TidalTerm.phases` are available on the plugin definitions without instantiating a `Tempo` object.
- **Instance Scope References**: `t.term.lunar.phases`, `t.term.solar.phases`, and `t.term.tides.states` share the exact same frozen array references (`t.term.lunar.phases === LunarTerm.phases`), adding zero memory or GC overhead.
> [!TIP]
> **Indexing Tip**: Following ISO calendar standards that drive Temporal and Tempo, `.index` is 1-based (`1..8`), while `.phases` is a standard 0-indexed JavaScript array (`0..7`).
> - **Current Phase**: Use `lunar.key` or `lunar.phases[lunar.index - 1]`.
> - **Next Phase**: Use `lunar.phases[lunar.index % 8]` (1-based index modulo 8 seamlessly targets the next phase index with automatic wrap-around).
## Licensing
This is a **Community** plugin. It is completely free and open-source for personal and commercial use. No license token is required.
## License
MIT
---
# Document: 9-plugins/finance.index.md

# @magmacomputing/tempo-plugin-finance
A specialized namespace plugin for Tempo that provides fiscal year and financial date utilities.
## Installation
```bash
npm install @magmacomputing/tempo-plugin-finance
```
## Usage
```typescript
import { Tempo } from '@magmacomputing/tempo';
import { FinanceNamespace } from '@magmacomputing/tempo-plugin-finance';
// Register the namespace
Tempo.use(FinanceNamespace);
const t = new Tempo('2024-07-01');
// Evaluate static properties
console.log(t.finance.fiscalQuarter); // 3
console.log(t.finance.taxYear); // 2024
// Evaluate functional closures
console.log(t.finance.isFiscalYearStart()); // false
```
## Licensing
This is a **Community** plugin. It is completely free and open-source for personal and commercial use under the MIT license.
---
# Document: 9-plugins/geo.index.md

# @magmacomputing/tempo-plugin-geo
A Community plugin for the [Tempo](https://github.com/magmacomputing/magma) ecosystem providing IP geolocation lookup, browser hardware location services, coordinate normalization, and 24-hour cached coordinate stashing.
By decoupling network-based geolocation lookup into a dedicated plugin, `@magmacomputing/tempo` remains zero-network and purely deterministic.
---
## Installation
```bash
npm install @magmacomputing/tempo-plugin-geo
```
---
## Architecture & Namespacing
Installing `GeoPlugin` mounts an immutable, locked-down **`Tempo.geo`** namespace onto the `Tempo` class. This avoids polluting the root class while providing a single landing pad for all coordinate and lookup operations.
```typescript
import { Tempo } from '@magmacomputing/tempo';
import { GeoPlugin } from '@magmacomputing/tempo-plugin-geo';
Tempo.use(GeoPlugin);
```
### The `Tempo.geo` API Surface
| Method / Property | Description |
| :--- | :--- |
| `Tempo.geo.lookup(opts?)` | Universal geolocation lookup (browser hardware GPS or server IP lookup) cached for 24h. Supports `{ refresh: true }`. |
| `Tempo.geo.resolve(input, opts?)` | Asynchronously resolves coordinates from an instance, configuration, or ambient storage cache. |
| `Tempo.geo.coerce(input)` | Pure function normalizing various coordinate formats (`lat/lng`, `latitude/longitude`, etc.) into a canonical `GeoConfig`. |
| `Tempo.geo.stash(coords, ttl?, keyOrOpts?)` | Stashes coordinates in storage with an optional custom TTL (default: 24h) and multi-tenant partitioning. |
| `Tempo.geo.clear(keyOrOpts?)` | Purges stashed coordinates from storage. |
| `Tempo.geo.get(keyOrOpts?)` | Reads stashed coordinates for the specified tenant/IP or ambient default. |
| `Tempo.geo.current` | **Read-only getter** returning the active global/ambient coordinates snapshot (`getStashedGeo() ?? Tempo.config.geo`). |
| `Tempo.geo.server(opts?)` | Low-level server-side IP geolocation handler. |
| `Tempo.geo.browser(opts?)` | Low-level browser Geolocation API handler. |
---
## Usage Examples
### 1. Fluent OOP with `Tempo.geo`
```typescript
import { Tempo } from '@magmacomputing/tempo';
import { GeoPlugin } from '@magmacomputing/tempo-plugin-geo';
Tempo.use(GeoPlugin);
// Universal Geolocation Lookup (cached for 24h)
const lookup = await Tempo.geo.lookup();
console.log(lookup.lat, lookup.lng, lookup.city);
// Inspect live ambient coordinate snapshot
console.log(Tempo.geo.current);
// Force fresh network lookup (bypassing 24h cache)
const fresh = await Tempo.geo.lookup({ refresh: true });
// Enrich a Tempo instance asynchronously
const t = new Tempo();
const localTime = await t.geoLocate();
console.log(localTime.geo?.latitude, localTime.geo?.longitude);
```
### 2. Functional Tree-Shakeable APIs
All underlying utilities can be imported as standalone tree-shakeable functions:
```typescript
import { Tempo } from '@magmacomputing/tempo';
import {
geoLookup,
resolveGeoCoordinates,
stashGeo,
clearStashedGeo,
getStashedGeo,
} from '@magmacomputing/tempo-plugin-geo';
const coords = await geoLookup();
const t = new Tempo('2026-06-21', { geo: coords });
```
---
## β οΈ Critical Operational Warnings
### 1. Server Context vs. Client Context
::: warning Server IP vs. User Location
**Ambient IP lookup on a server resolves the SERVER's location, NOT the user's location.**
- In a server environment (Node.js, Deno, Bun, Edge runtimes), calling `Tempo.geo.lookup()` without options queries the **server datacenter's public outbound IP address**.
- If your server runs in AWS `us-east-1` (Virginia) and an Australian user hits your API, calling ambient `Tempo.geo.lookup()` will resolve to Virginia!
- **Best Practice for Backends**:
- Always extract the client IP from trusted reverse proxy headers (e.g., `X-Forwarded-For`, `CF-Connecting-IP`) and pass it explicitly:
```typescript
const userCoords = await Tempo.geo.lookup({ ip: clientIp });
const userTime = new Tempo(date, { geo: userCoords });
```
- Or receive explicit GPS/browser coordinates from the frontend client request payload.
:::
---
### 2. Multi-Tenant Key Isolation
::: danger Shared Ambient Storage
**Unpartitioned ambient storage is shared. In multi-tenant environments, always use unique keys or instance-level options.**
- Ambient storage stores coordinates under `_magma_geo_` by default.
- In a shared process handling requests for multiple tenants or distinct users, calling `stash()` or ambient `lookup()` without a key will cause tenants to **overwrite each other's cached coordinates**!
- **Solution A: Multi-Tenant Key Scoping**:
Pass a tenant identifier or user ID as the key:
```typescript
// Stash coordinates partitioned for tenant A:
Tempo.geo.stash(tenantACoords, undefined, 'tenant-alpha');
// Lookup / retrieve for a specific tenant:
const coords = Tempo.geo.get('tenant-alpha');
Tempo.geo.clear('tenant-alpha');
```
The cache automatically partitions keys under `_magma_geo_:`, guaranteeing strict isolation.
- **Solution B: Instance-Level Configuration (Recommended)**:
Avoid ambient storage altogether by binding coordinates directly to `Tempo` instances:
```typescript
const tenantTime = new Tempo(date, { geo: tenantCoords });
```
Instance-level coordinates are completely local, immutable, and never touch shared memory or ambient caches.
:::
---
## Security & Immutability
In keeping with Tempo's strict immutability principles, the `Tempo.geo` namespace is fully locked down:
- **Deeply Frozen**: The entire `Tempo.geo` namespace and its attached utilities are recursively frozen.
- **Tamper-Proof**: Protected against modification, deletion, or monkey-patching. Any attempt to reassign `Tempo.geo` or mutate its methods (e.g. `Tempo.geo.lookup = ...`) will throw a `TypeError` in strict mode.
- **Pure Instance Operations**: Instance methods like `t.geoLocate()` always return a new, enriched `Tempo` instance, preserving the immutability of the original instance.
---
## Licensing
This is a **Community** plugin. It is completely free and open-source for personal and commercial use under the MIT license.
---
# Document: 9-plugins/snap.index.md

# @magmacomputing/tempo-plugin-snap
A Community plugin for the [Tempo](https://github.com/magmacomputing/magma) library that provides robust time rounding and snapping functionality (e.g., snapping to the nearest 15 minutes or 1 hour block) for calendar and scheduling applications.
By default, the plugin effortlessly snaps dates to a configurable minute-interval. This is particularly useful when building UI components like time-pickers, ensuring data boundaries align perfectly with application logic.
### π‘ User Notes: Why Sub-Second Snapping?
While `hours` and `minutes` cover most UI use cases, sub-second precision (`ms`, `us`, `ns`) is invaluable for:
1. **Telemetry & Log Aggregation**: Snapping high-frequency jittery timestamps to the nearest `100ms` or `500ms` bucket for cleaner charts and analysis.
2. **Video & Audio Synchronization**: Multimedia frame rates require precise timing. Snap to the nearest `16ms` (approx 60fps) or `40ms` (25fps) to align data points with visual boundaries.
3. **Database & API Normalization**: Truncating or snapping Tempo's native nanosecond precision to the nearest `ms` before sending payloads ensures your local application state perfectly matches remote databases that don't support microseconds.
4. **Performance Benchmarking**: Grouping execution times into buckets (e.g., nearest `10ms`) for histograms.
## Installation
```bash
npm install @magmacomputing/tempo-plugin-snap
```
## Usage
```typescript
import { Tempo } from '@magmacomputing/tempo';
import { SnapPlugin } from '@magmacomputing/tempo-plugin-snap';
// Pass the plugin to `Tempo.init` to register it into the runtime.
Tempo.init({
extends: [SnapPlugin]
});
const t = new Tempo('2026-06-01T14:08:00Z');
// Snaps to the nearest 15 minutes by default
const snapped = t.snap();
console.log(snapped.format('{hh}:{mi}')); // "14:15"
// Or explicitly provide units and intervals
const snapHour = t.snap({ hh: 1 });
const snapSecond = t.snap({ ss: 30 });
const snapMs = t.snap({ ms: 100 });
// Force snapping direction instead of standard rounding
const snapUp = t.snap({ mi: 15, direction: 'up' });
const snapDown = t.snap({ mi: 15, direction: 'down' });
```
## Licensing
This is a **Community** plugin. It is completely free and open-source for personal and commercial use under the MIT license.
---
# Document: 9-plugins/sync.index.md

# @magmacomputing/tempo-plugin-sync
This is a Community plugin for the [Tempo](https://github.com/magmacomputing/magma) library that provides lock-free, nanosecond-accurate cross-thread time synchronization using `SharedArrayBuffer` and `Atomics`.
::: tip Perfect For
High-frequency trading platforms, real-time multiplayer game servers, distributed microservice tracing, and extreme-precision scientific telemetry.
:::
## Installation
```bash
npm install @magmacomputing/tempo-plugin-sync
```
## Usage
```typescript
import { Tempo } from '@magmacomputing/tempo';
import { SyncPlugin } from '@magmacomputing/tempo-plugin-sync';
Tempo.init({
extends: [SyncPlugin]
});
// Master Thread: Start the clock
const clock = Tempo.sync.startClock({ updateIntervalMs: 1 });
const buffer = clock.buffer; // Pass this SharedArrayBuffer to your workers
```
### Reading from Worker Threads
To read the synchronized time from inside a worker thread, pass the `SharedArrayBuffer` via `workerData` and instantiate an `AtomicReader`.
```typescript
// worker.ts
import { workerData } from 'node:worker_threads';
import { AtomicReader } from '@magmacomputing/tempo-plugin-sync';
// Hydrate the reader using the master buffer
const reader = new AtomicReader(workerData.buffer);
// 1. Get raw milliseconds (O(1) Atomic Read)
const ms = reader.now();
// 2. Get high-precision BigInt nanoseconds
const ns = reader.nowNano();
// 3. Hydrate a brand new Tempo instance with exact precision
const t = reader.getTempo();
```
## Licensing
This is a **Community** plugin. It is completely free and open-source for personal and commercial use under the MIT license.
---
# Document: 9-plugins/ticker.index.md

# @magmacomputing/tempo-plugin-ticker
This is a Community plugin for the [Tempo](https://github.com/magmacomputing/magma) library that provides a high-performance continuous execution loop (Ticker) based on temporal mathematics.
::: info High Performance Loop
Unlike raw `setInterval`, the Ticker plugin leverages Tempo's temporal core to provide best-effort scheduling with millisecond resolution, making it ideal for standard UI updates, periodic tasks, and accurate state synchronization.
:::
## Installation
```bash
npm install @magmacomputing/tempo-plugin-ticker
```
## Usage
To use the Ticker, pass the plugin to `Tempo.init` or `Tempo.use`:
```typescript
import { Tempo } from '@magmacomputing/tempo';
import { TickerPlugin } from '@magmacomputing/tempo-plugin-ticker';
Tempo.init({
plugins: [TickerPlugin]
});
// You can access Ticker-based execution loops through the Tempo API:
const ticker = Tempo.ticker({ seconds: 1 });
```
### Direct Access
If you need to access the [Reporting & Registry](#reporting-registry) API (like `Ticker.active`), you should import the `Ticker` namespace:
```typescript
import { Ticker } from '@magmacomputing/tempo-plugin-ticker';
console.log(Ticker.active);
```
## π Key Features
The Ticker supports a unified **Options** object, enabling professional resource management and semantic duration-based intervals.
### 1. Semantic Intervals (Duration Objects)
Instead of raw numeric seconds, you can use `DurationLike` objects or shorthand keys for clarity. This is especially powerful for variable-length intervals like **months**.
```typescript
// Pulse exactly once a month
await using monthly = Tempo.ticker({ months: 1 });
// You can also use highly compact shorthand keys
await using concise = Tempo.ticker({ hh: 1, mi: 30 }); // every 1h 30m
// Pulse every time a new #quarter begins
await using quarterly = Tempo.ticker({ '#quarter': 1 });
```
### 2. Term-Based Intervals
Ticker intervals can be driven by any registered **Term**. This is powerful for syncing with business cycles or daily shifts.
> **Snapping vs Shifting:** Use directional shorthands (like `>`) to snap pulses exactly to the **boundaries** of the term (e.g., the very start of the morning). Using numeric values (like `1`) performs a relative shift, which preserves your current time-offset into the next period (e.g. two hours into a time-period will always be two hours into the next time-period).
```typescript
// Snap and pulse exactly at the start of every 'morning', 'afternoon', etc.
using shiftTicker = Tempo.ticker({ '#timeOfDay': '>' }, (t) => {
console.log(`New period started: ${t.term.tod}`);
});
```
### 3. Stop Conditions (Resource Management)
Prevent memory leaks and runaway processes by setting a built-in termination condition.
```typescript
// Pattern A: Stop after exactly 5 ticks (defaults to 1-second interval)
using tickerA = Tempo.ticker({ limit: 5 }, (t) => console.log(t));
// Pattern B: Stop when a specific virtual time is reached (Inclusive)
using tickerB = Tempo.ticker({
seconds: 10, // Plural DurationLike property
seed: '2024-12-25T10:00:00',
until: '2024-12-25T12:00:00'
}, (t) => console.log(t));
// Pattern C: Stop immediately without pulsing (Limit: 0 is strictly honored)
using tickerC = Tempo.ticker({ limit: 0 }, (t) => console.log(t));
```
### 4. Virtual Clock (Seeding)
To create a **Virtual Clock** that increments from a specific point rather than using the system time, use the `seed` option:
```typescript
// Starts at '2024-01-01', then increments by 1 day per pulse
await using daily = Tempo.ticker({
days: 1,
seed: '2024-01-01'
}, (t) => console.log(t));
```
### 5. Backwards Tickers (Countdowns)
By providing a **negative** interval, you can create a Ticker that moves backwards in time.
```typescript
// Count down from 10 seconds, moving backwards 1s at a time
using countdown = Tempo.ticker({ seconds: -1, seed: "00:00:10" }, (t, stop) => {
console.log(t.format('{ss}'));
if (t.ss === 0) stop();
});
```
### 6. Recurrence Rules (RRULE)
Ticker natively supports standard RFC 5545 RRULE strings or options objects with an `rrule` property.
```typescript
// Pulse on deterministic calendar recurrences (e.g. daily)
await using dailySync = Tempo.ticker('FREQ=DAILY;INTERVAL=1');
// Or via options object with additional properties
await using weeklyMeeting = Tempo.ticker({
rrule: 'FREQ=WEEKLY;BYDAY=MO',
label: 'Weekly Monday Sync'
});
```
### 7. Cron Expressions (Standard 5-Field Syntax)
Ticker natively accepts 5-part cron expressions (`min hr dom mon dow`) powered by `@magmacomputing/tempo-fns`. You can pass cron strings directly or via the `cron` configuration option.
```typescript
// Pattern A: Positional 5-field cron string (e.g., 9am MondayβFriday)
await using weekdaySync = Tempo.ticker('0 9 * * 1-5', (t) => {
console.log(`Workday morning pulse: ${t.format('isoTime')}`);
});
// Pattern B: Options object with cron schedule and boundary limits
await using healthCheck = Tempo.ticker({
cron: '*/15 * * * *', // Every 15 minutes
label: '15-Minute Healthcheck',
limit: 10
}, (t) => {
console.log(`Healthcheck pulse: ${t.format('isoTime')}`);
});
```
## Usage Patterns
### 1. Resource Management (Recommended)
Using the `using` and `await using` keywords ensures that Tickers are automatically stopped when they go out of scope.
```typescript
// Pattern A: Automatic cleanup for callback-based ticker
{
using ticker = Tempo.ticker((t) => render(t)); // Defaults to a 1-second pulse
} // interval stops automatically here
// Pattern B: Automatic cleanup for async generator
{
await using ticker = Tempo.ticker(1);
for await (const t of ticker) {
if (done) break;
}
} // generator is closed and interval stops here
```
### 2. Manual Control (Programmatic Stop)
If you are not using the `using` or `await using` keywords, or if you need to stop the Ticker from outside its own loop (e.g., in a separate event handler), you can manually call the `stop()` method on the Ticker object.
```typescript
// Pattern A: Stop a callback-based ticker
const tickerA = Tempo.ticker(1, (t) => console.log(t));
// ... later
tickerA.stop();
// Pattern B: Stop an async generator externally
const tickerB = Tempo.ticker(1);
(async () => {
for await (const t of tickerB) {
console.log(t.toString());
}
console.log('Ticker has been gracefully stopped.');
})();
// Close the generator from somewhere else
setTimeout(() => {
tickerB.stop();
}, 5000);
```
### 3. Event Listeners (.on)
Instead of (or in addition to) the constructor callback, you can register listeners for the `'pulse'`, `'stop'`, and `'catch'` events.
All listeners use the same callback signature: `(t, stop) => {}`.
```typescript
const ticker = Tempo.ticker(1);
ticker.on('pulse', (t) => console.log('Listener A:', t.fmt.weekTime));
ticker.on('pulse', (t) => console.log('Listener B:', t.fmt.weekTime));
ticker.on('stop', (t) => console.log('Ticker stopped at:', t.fmt.weekTime));
```
For `'stop'` listeners, the `stop` callback argument is included for signature consistency; however, invoking it after stop has already occurred is a no-op.
### 4. Manual Pulsing (.pulse)
In some scenarios, you may want to drive a Ticker manually (e.g., from a UI event or a WebSocket message) while still benefiting from the Ticker's internal state management and listeners.
```typescript
const ticker = Tempo.ticker({ seconds: 1 }); // Still has a 1s duration logic
// ...
ticker.pulse(); // Manually advance and notify listeners
```
## π§ Zombie Tickers (Warning) {#zombie-tickers-warning}
In a Node.js environment, `Tempo.ticker()` uses background timers (`setTimeout`) to drive its pulses. If you do not explicitly stop a Ticker, it becomes a **"Zombie Ticker"** that continues to run indefinitely, even if the variable that created it has gone out of scope.
### The Risks:
- **Process Hangs**: Node.js will not exit a process if there are active timers. Undisposed Tickers are a common cause of "mysterious hangs" at the end of test runs.
- **Test Inconsistency**: Leaked Tickers can continue to fire while subsequent tests are running, leading to flaky assertions and "impossible" state changes.
- **Memory Leaks**: Each active Ticker maintains closures that prevent garbage collection of the `Tempo` instance and its listeners.
### The Solution:
Always use the **Disposer Pattern** (`using` or `await using`) or a `try...finally` block to guarantee cleanup:
```typescript
// β β BEST: Automatic cleanup via 'using'
{
using ticker = Tempo.ticker(1);
// ... logic ...
} // Stays clean: ticker stopped automatically here
// β GOOD: Manual cleanup in finally block (Required for captured variables)
let ticker;
try {
ticker = Tempo.ticker(1, (t) => { ... });
// ... assertions ...
} finally {
ticker?.stop(); // Prevents "Zombie Tickers" even if assertions fail
}
```
::: warning
If you are using `const` or `let` without a `finally` block, an assertion failure will skip the `stop()` call, leaving a live timer in the event loop. Always prefer the `using` keyword or `try...finally` for industrial-grade resource management.
:::
### `Ticker` Object
The object returned by `Tempo.ticker()` (or an instance of the `Ticker` class) implements the following interface:
| Method / Property | Description |
| :--- | :--- |
| `on(event, cb)` | Registers a listener for the `'pulse'`, `'stop'`, or `'catch'` events. |
| `pulse()` | Manually triggers a pulse, advances state, and notifies listeners. Returns the emitted pulse Tempo. |
| `info` | Read-only getter returning `{ next, ticks, limit, interval, stopped }`. |
| `stop()` | Stops the Ticker, clears active timers, and immediately resolves any pending async iteration Promises. |
| `[Symbol.dispose]` | Standard cleanup for `using` blocks. |
| `[Symbol.asyncDispose]` | Standard async cleanup for `await using` blocks. |
| `[Symbol.asyncIterator]` | Standard async iteration support (for `for await` loops). |
## Reporting & Registry {#reporting-registry}
The `Ticker` class maintains a static registry of all currently active Tickers. This is useful for debugging, monitoring, or cleanup checks.
### `Ticker.active`
A static getter that returns an array of [`Ticker.Snapshot`](#tickersnapshot) objects for all active (non-stopped) Tickers.
```typescript
import { Ticker } from '@magmacomputing/tempo-plugin-ticker';
// Get a report of all running tickers
const reports = Ticker.active;
reports.forEach(({ ticker, next, ticks }) => {
console.log(`Ticker ${ticker} next pulse: ${next}, ticks so far: ${ticks}`);
});
```
#### `Ticker.Snapshot`
```typescript
type Snapshot = {
ticker: Instance; // The Ticker instance (Proxy) itself
next: Tempo; // The next Tempo value to be emitted
ticks: number; // Number of pulses emitted so far
limit?: number; // The configured limit (if any)
interval: object; // The duration-based interval
stopped: boolean; // Whether the ticker is stopped
}
```
## π― One-Shot Ticker (Meeting Alerts)
You can use the Ticker as a "one-shot" timer for specific events by simply specifying a **seed** value. This is perfect for setting up a single alert (e.g., for a meeting) that cleans itself up immediately after firing.
::: tip
**Seed-Only Logic**: Providing a `seed` (as a string or in an options object) without any other duration-based keys (`seconds`, `minutes`, etc.) or a `limit` implies a `limit: 1`.
Effectively, `Tempo.ticker('Fri 10am')` and `Tempo.ticker({ seed: 'Fri 10am' })` and `Tempo.ticker({ seed: 'Fri 10am', limit: 1 })` are all treated as one-shot Tickers.
**Inclusive Boundaries**: Termination conditions (`limit` and `until`) are **inclusive**. A Ticker with `limit: 1` will pulse exactly once before stopping.
:::
```typescript
// Pattern A: Implicit one-shot via string seed
Tempo.ticker('Friday 10am', (t) => {
console.log(`Meeting alert: ${t.format('{hh}:{mi}')}`);
});
// Pattern B: Explicit one-shot via options
const event = { meeting: 'Friday 10am' };
Tempo.ticker({
seed: { value: 'meeting', event }
}, (t) => {
console.log(`Meeting alert: ${t.format('{hh}:{mi}')}`);
});
```
::: warning
**Future Seeds**: If the `seed` is in the future, the Ticker will remain dormant (waiting) until that time is reached. **Most Tickers emit an initial pulse immediately** (at the `seed` time or "now"), but a future seed will delay that first pulse until the specified time.
:::
::: danger
**Persistence**: Ticker timers exist only **in-memory**. If the driving process (e.g., Node.js) terminates, any scheduled future pulses (including those from future seeds) are lost. For critical long-term scheduling, consider an external persistent job runner.
:::
::: warning
While `limit: 1` handles the stop condition automatically, always remember that if you are using long-running Tickers without a limit, you **must** use the [Disposer Pattern](#zombie-tickers-warning) or manual `stop()` to avoid memory leaks and zombie processes.
:::
## π§ Advanced: Syncing Multiple Clocks
If you need to show multiple timezones on a dashboard, avoid creating multiple Tickers. Instead, use a single **Master Ticker** to drive all views. This prevents "drift" between the clocks and is much more efficient.
### Using Signals (Recommended)
Signals (from Preact, Solid, or Vue) are perfect for this "one source, many views" pattern.
```typescript
// 1. Master source of truth
const now = signal(new Tempo());
// 2. Drive the master from a single ticker
using _ = Tempo.ticker(1, (t) => now.value = t);
// 3. Derived timezones update automatically and stay 100% in sync
const sydney = computed(() => now.value.set({ timeZone: 'Australia/Sydney' }));
const london = computed(() => now.value.set({ timeZone: 'Europe/London' }));
```
### Using Async Generators (Framework-Agnostic)
If you are not using a reactive framework, you can use the same pattern with an `AsyncGenerator` to derive all clocks from a single pulse.
```typescript
// One generator, one interval, zero drift.
await using master = Tempo.ticker(1);
for await (const t of master) {
const clocks = {
sydney: t.set({ timeZone: 'Australia/Sydney' }),
ny: t.set({ timeZone: 'America/New_York' }),
london: t.set({ timeZone: 'Europe/London' })
};
renderDashboard(clocks);
}
```
## Licensing
This is a **Community** plugin. It is completely free and open-source for personal and commercial use under the MIT license.
---