Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,520 changes: 72 additions & 1,448 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions packages/functions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@
"build:esm": "node ../../node_modules/typescript-7/bin/tsc -b",
"build:global": "rollup -c",
"clean": "magma-cli rm dist && (node ../../node_modules/typescript-7/bin/tsc -b --clean || true)",
"prepublishOnly": "if [ $(git rev-parse --abbrev-ref HEAD) != main ]; then echo 'ERROR: Must be on main branch to publish.'; exit 1; fi && npm run build",
"prepublishOnly": "tempo-cli prepublish",
"docs:dev": "node ./bin/sync-docs.mjs && vitepress dev doc",
"docs:build": "node ./bin/sync-docs.mjs && vitepress build doc"
},
"peerDependencies": {
"@js-temporal/polyfill": "^0.5.1",
"@magmacomputing/tempo": "^3.7.0"
"@magmacomputing/tempo": "^3.7.0 || ^4.0.0"
},
"peerDependenciesMeta": {
"@js-temporal/polyfill": {
Expand Down
2 changes: 1 addition & 1 deletion packages/library/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@
"test": "vitest run",
"build": "tsc -b",
"clean": "tsc -b --clean",
"prepublishOnly": "if [ $(git rev-parse --abbrev-ref HEAD) != main ]; then echo 'ERROR: Must be on main branch to publish.'; exit 1; fi && npm run build"
"prepublishOnly": "tempo-cli prepublish"
},
"dependencies": {
"tslib": "^2.8.1"
Expand Down
12 changes: 10 additions & 2 deletions packages/plugins/.setup/catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
{
"id": "ai",
"name": "AI Plugin",
"description": "Tempo community plugin for LLM-powered natural language processing and parsing.",
"description": "Tempo community plugin for LLM-powered natural language parsing.",
"packageName": "@magmacomputing/tempo-plugin-ai",
"plan": "community",
"status": "active"
Expand All @@ -52,7 +52,15 @@
"name": "Ticker Plugin",
"description": "Tempo plugin that provides a high-performance continuous execution loop (Ticker) based on temporal mathematics.",
"packageName": "@magmacomputing/tempo-plugin-ticker",
"plan": "pro",
"plan": "community",
"status": "active"
},
{
"id": "_std",
"name": "_std Plugin",
"description": "Standard built-in Terms for @magmacomputing/tempo (showcase implementation — not published separately)",
"packageName": "@magmacomputing/tempo-std",
"plan": "community",
"status": "active"
}
]
21 changes: 20 additions & 1 deletion packages/plugins/.setup/community-plugin-template.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ Ensure the plugin's `package.json` contains the correct community configuration:
"access": "public"
}
```
- **Repository**: Required for npm provenance and source linking. Must include the exact sub-directory path:
```json
"repository": {
"type": "git",
"url": "git+https://github.com/magmacomputing/magma.git",
"directory": "packages/plugins/[name]"
}
```
- **Exports**: Define exports with types and import entrypoints:
```json
"exports": {
Expand All @@ -37,7 +45,7 @@ Ensure the plugin's `package.json` contains the correct community configuration:
```
- **Scripts**:
- Ensure `"build": "tsup && tsc"` is present.
- Include the prepublish safeguard: `"prepublishOnly": "if [ $(git rev-parse --abbrev-ref HEAD) != main ]; then echo 'ERROR: Must be on main branch to publish.'; exit 1; fi && npm run build"`.
- Include the prepublish safeguard: `"prepublishOnly": "tempo-cli prepublish"`.
- Include the correct test script: `"test": "vitest run -c ../vitest.shared.ts"`.
- **Keywords**: Ensure relevant keywords are present (`tempo`, `tempo-plugin`, `magmacomputing`, `temporal`, `plugin`, etc.).
- **tempo**: Set `"plan": "community"`.
Expand Down Expand Up @@ -134,3 +142,14 @@ All exported components (functions, interfaces, classes, and types) must be prop
*/
export function myExportedFunction(input: string): string { ... }
```

## 7. Release & CI Configuration (`.github/workflows/publish.yml`)

When adding a new plugin to the monorepo, update `.github/workflows/publish.yml` to enable manual `workflow_dispatch` provenance releases:

1. **Add to Package Selector**: Add `@magmacomputing/tempo-plugin-[name]` to the `options` array under `inputs.package`.
2. **Add to Bulk Publish**: Add the workspace to the `all` branch in the publishing step:
```bash
npm publish --workspace=@magmacomputing/tempo-plugin-[name] $PROVENANCE_FLAG
```

2 changes: 1 addition & 1 deletion packages/plugins/.std/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@
"test": "vitest run -c ../vitest.shared.ts"
},
"peerDependencies": {
"@magmacomputing/tempo": "^3.9.x"
"@magmacomputing/tempo": "^4.0.0"
}
}
3 changes: 1 addition & 2 deletions packages/plugins/.std/src/term.quarter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { defineTerm, getTermRange, defineRange, resolveCycleWindow, COMPASS } from '@magmacomputing/tempo/plugin-api';
import { logWarn } from '@magmacomputing/tempo/plugin-api';
import { defineTerm, getTermRange, defineRange, resolveCycleWindow, COMPASS, logWarn } from '@magmacomputing/tempo/plugin/sdk';
import { isNumber, asArray } from '@magmacomputing/library';
import type { Tempo } from '@magmacomputing/tempo';

Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/.std/src/term.season.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getTermRange, defineTerm, defineRange, resolveCycleWindow, logWarn, COMPASS } from '@magmacomputing/tempo/plugin-api';
import { getTermRange, defineTerm, defineRange, resolveCycleWindow, logWarn, COMPASS } from '@magmacomputing/tempo/plugin/sdk';
import type { Tempo } from '@magmacomputing/tempo';

/** definition of meteorological season ranges */
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/.std/src/term.timeline.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { defineTerm, getTermRange, defineRange, resolveCycleWindow } from '@magmacomputing/tempo/plugin-api';
import { defineTerm, getTermRange, defineRange, resolveCycleWindow } from '@magmacomputing/tempo/plugin/sdk';
import type { Tempo } from '@magmacomputing/tempo';

/** definition of daily time periods */
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/.std/src/term.zodiac.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { defineTerm, getTermRange, defineRange, resolveCycleWindow } from '@magmacomputing/tempo/plugin-api';
import { defineTerm, getTermRange, defineRange, resolveCycleWindow } from '@magmacomputing/tempo/plugin/sdk';
import { isNumber } from '@magmacomputing/library';
import type { Tempo } from '@magmacomputing/tempo';

Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/.std/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"composite": true,
"noEmit": false,
"paths": {
"@magmacomputing/tempo/plugin-api": ["../../tempo/src/plugin-api.index.ts"],
"@magmacomputing/tempo/plugin/sdk": ["../../tempo/src/plugin/plugin.sdk.ts"],
"@magmacomputing/tempo": ["../../tempo/src/tempo.index.ts"],
"@magmacomputing/library": ["../../library/src/common.index.ts"],
"#library/*": ["../../library/src/common/*"]
Expand Down
7 changes: 7 additions & 0 deletions packages/plugins/ai/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ All notable changes to the `@magmacomputing/tempo-plugin-ai` project will be doc
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.1.0] - 2026-08-19

### Added
- **Dynamic Context & Lazy Provider Resolution**: Upgraded `AiConfig` and all AI handlers (`parseAI`, `formatAI`, `diffAI`, `extractAI`, `recurrenceAI`, `scheduleAI`, `contextAI`) to support `Evaluable<T>` and `AsyncEvaluable<T>` configuration suppliers (`T | (() => T | Promise<T>)`).
- **Dynamic Secret Rotation & Custom Endpoints**: AI provider configurations (`AiProvider`) now accept dynamic functions for `key`, `url`, and `model`, enabling automated secret vault rotation and dynamic proxy routing evaluated just-in-time on each request dispatch.
- **Provider Fallback & Default Hierarchy**: Hardened `fetchFromProvider` in `transport.ts` with automated fallback defaults via `evaluate`/`evaluateAsync`, cleanly resolving `DEFAULT_PROVIDERS` templates and environment keys when explicit provider fields are omitted.

## [1.0.0] - 2026-08-15

### Added
Expand Down
10 changes: 10 additions & 0 deletions packages/plugins/ai/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,16 @@ For complete API references, architecture guides, and advanced examples:

---

## 🔒 Security, Privacy & Transparency

* 🌐 **Direct Provider Communication**: By default, requests are dispatched directly from your application runtime to official provider endpoints (OpenAI, Google Gemini, Anthropic, Groq, or local Ollama). When custom endpoint URLs or AI Gateways are configured, requests route directly to your specified destination. There are **no hidden intermediary services**, **no third-party telemetry**, and **zero tracking**.
* 🛡️ **Zero Data Retention**: Prompts, input expressions, and temporal context are processed ephemerally and are never stored, logged, or retained outside of your own runtime memory or explicitly configured cache adapters.
* 🔑 **Scoped Environment Lookups**: Auto-discovery only reads standard, documented provider variables (`OPENAI_API_KEY`, `GROQ_API_KEY`, `GEMINI_API_KEY`, `ANTHROPIC_API_KEY`, `TEMPO_AI_KEY`). No other system environment variables are inspected.
* 📦 **Client-Side Safety**: BYOK API keys are designed exclusively for server, edge runtime, or secure container environments and should never be exposed in client-side browser bundles.

---

## ⚖️ Licensing

This is a **Community** plugin. It is completely free and open-source for personal and commercial use under the MIT license.

8 changes: 8 additions & 0 deletions packages/plugins/ai/doc/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ initAI({
});
```

### 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.
Expand Down
13 changes: 3 additions & 10 deletions packages/plugins/ai/doc/context.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,12 @@ This is highly useful for user onboarding settings, automatic context mapping fo
## Basic Usage

> [!NOTE]
> Like all Tempo AI functions, `contextAI` requires prior initialization with at least one active provider via `initAI()`.
> `@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 { initAI, contextAI } from '@magmacomputing/tempo-plugin-ai';
import { contextAI } from '@magmacomputing/tempo-plugin-ai';

// 1. Configure the AI provider farm
await initAI({
providers: [
{ id: 'groq', key: process.env.GROQ_API_KEY }
]
});

// 2. Infer contextual settings from unstructured text
// 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"
Expand Down
34 changes: 34 additions & 0 deletions packages/plugins/ai/doc/init.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,32 @@ await initAI({

> **Tip**: `initAI` returns a `Promise<void>` 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<string>`), 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<T>`).

This enables automated secret vault rotation, dynamic AI gateways, and multi-tenant context resolution evaluated just-in-time on every HTTP dispatch:
Comment thread
magmacomputing marked this conversation as resolved.

```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`):
Expand Down Expand Up @@ -150,6 +176,14 @@ export interface AiConfig {
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<string>;
/** Dynamic or static default locale context (string | string[] | (() => string | string[])) */
locale?: Evaluable<string | string[]>;
/** Dynamic or static default calendar context (string | (() => string)) */
calendar?: Evaluable<string>;
/** Dynamic or static default celestial sphere context (string | (() => string)) */
sphere?: Evaluable<string>;
/** Optional hook to intercept and resolve dynamic provider defaults */
fetchDefaults?: (providerId: string) => Promise<Partial<AiProvider> | null> | Partial<AiProvider> | null;
/** URL for dynamic remote provider manifest updates, or `false` to disable */
Expand Down
43 changes: 43 additions & 0 deletions packages/plugins/ai/doc/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,49 @@ const rawReasoning = result.reasoning;
* 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<string>` / `() => Promise<string> | string`), while `url`, `model`, and temporal context fields accept synchronous suppliers (`Evaluable<T>`).
* **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;
}
}
Comment thread
magmacomputing marked this conversation as resolved.
]
});
```
* **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.
Expand Down
13 changes: 9 additions & 4 deletions packages/plugins/ai/package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
{
"name": "@magmacomputing/tempo-plugin-ai",
"version": "1.0.0",
"version": "1.1.1",
"description": "Tempo community plugin for LLM-powered natural language parsing.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/magmacomputing/magma.git",
"directory": "packages/plugins/ai"
},
"files": [
"dist",
"README.md",
Expand All @@ -17,15 +22,15 @@
},
"scripts": {
"build": "tsup",
"test": "cross-env TEMPO_LICENSE_KEY=\"\" vitest run -c ../vitest.shared.ts",
"prepublishOnly": "if [ $(git rev-parse --abbrev-ref HEAD) != main ]; then echo 'ERROR: Must be on main branch to publish.'; exit 1; fi && npm run build"
"test": "vitest run -c ../vitest.shared.ts",
"prepublishOnly": "tempo-cli prepublish"
},
"tempo": {
"vendorVariantId": "tempo-plugin-ai",
"plan": "community"
},
"peerDependencies": {
"@magmacomputing/tempo": "^3.11.1"
"@magmacomputing/tempo": "^4.0.0"
},
"devDependencies": {
"@js-temporal/polyfill": "^0.5.1"
Expand Down
16 changes: 16 additions & 0 deletions packages/plugins/ai/src/core/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,31 @@ const _entryExpiries = new Map<string, number>();

/**
* Normalizes input string for deterministic cache lookups by trimming excess whitespace and lowercasing.
*
* @param input - Raw input string to normalize
* @returns Normalized cache key string
*/
export function normalizeCacheInput(input: string): string {
return input.trim().toLowerCase().replace(/\s+/g, ' ');
}

/**
* Generates a namespaced cache key for domain-specific AI functions.
*
* @param namespace - The functional namespace (e.g., 'parse', 'format', 'diff')
* @param key - The specific cache key within the namespace
* @returns Fully namespaced cache key string
*/
export function getNamespacedCacheKey(namespace: string, key: string): string {
return `${AI_CACHE_NAMESPACE_PREFIX}${namespace}::${key}`;
}

/**
* Reads from multi-tier cache (Tier 2 external async adapter first, Tier 1 local in-memory Tempo.cache fallback).
*
* @param cacheKey - The cache key to retrieve
* @param options - Cache read options including force, cache toggle, adapter, debug, and tag
* @returns The cached string value, or undefined if not found or cache is disabled
*/
export async function readMultiTierCache(
cacheKey: string,
Expand Down Expand Up @@ -69,6 +80,11 @@ export async function readMultiTierCache(

/**
* Writes to multi-tier cache (Tier 1 local in-memory Tempo.cache and Tier 2 external async adapter).
*
* @param cacheKey - The cache key to store
* @param value - The string value to cache
* @param ttl - Time-to-live in milliseconds
* @param options - Cache write options including cache toggle, adapter, debug, and tag
*/
export async function writeMultiTierCache(
cacheKey: string,
Expand Down
Loading
Loading