@exellix/ai-skills 5.8.0 → 5.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,600 +1,600 @@
1
- # @exellix/ai-skills
2
-
3
- Foundational skill execution layer for the exellix ecosystem: templates live in the **Catalox** native catalog **`ai-skills`**, execution uses **`@x12i/ai-gateway`**, and responses support **FlexMD 2.0** structured text.
4
-
5
- 🚀 **Env-Ready**: Provider keys can be loaded from `.env` automatically. **`catalox` is required** at construction time (see Quick Start).
6
-
7
- ## Documentation
8
-
9
- | Topic | Doc |
10
- |--------|-----|
11
- | Flex-MD vs gateway parsing, integration tests | **[docs/FLEX_MD_AND_TESTING.md](docs/FLEX_MD_AND_TESTING.md)** |
12
- | Catalox integration & env notes | **[docs/CATALOX_PEER_GUIDE.md](docs/CATALOX_PEER_GUIDE.md)** · [Environment (Firebase & Catalox v4)](#environment-firebase--catalox-v4) |
13
- | Activix integration best practices | **[.docs/activix-integration-best-practices-checklist.md](.docs/activix-integration-best-practices-checklist.md)** |
14
- | Per-package log level (`AI_SKILLS_LOGS_LEVEL`) | **[Logging](#logging-ai-skills--x12ilogxer)** below and [@x12i/logxer on npm](https://www.npmjs.com/package/@x12i/logxer) |
15
- | Gateway templates (v4), `invoke()` vs `invokeChat()`, `templateRendering` | **[docs/GATEWAY_TEMPLATE_PROTOCOL_V4.md](docs/GATEWAY_TEMPLATE_PROTOCOL_V4.md)** |
16
- | This package: `workingMemory`, `templateRenderOptions`, client `templateRendering` | **[docs/AI_SKILLS_GATEWAY_TEMPLATES.md](docs/AI_SKILLS_GATEWAY_TEMPLATES.md)** |
17
- | Invoke execution metadata (`provider`, `modelUsed`, `effectiveModelConfig`, …) | **[docs/AI_GATEWAY_INVOKE_EXECUTION_METADATA.md](docs/AI_GATEWAY_INVOKE_EXECUTION_METADATA.md)** |
18
- | Graph execution context (`graphId`, `nodeId`, identity mapping) | **[docs/GRAPH_EXECUTION_SUPPORT.md](docs/GRAPH_EXECUTION_SUPPORT.md)** |
19
- | Gateway invoke preflight (`analyzeSkillRequest`, FuncX ≥ 4.0.1) | **[docs/SKILL_REQUEST_ANALYSIS.md](docs/SKILL_REQUEST_ANALYSIS.md)** |
20
- | External follow-ups (Activix persistence vs gateway envelope) | **[docs/AI_GATEWAY_FEATURE_REQUESTS.md](docs/AI_GATEWAY_FEATURE_REQUESTS.md)** |
21
-
22
- ## Features
23
-
24
- - **Unified Skill Runner**: Execute skills via metadata, not hardcoded handlers
25
- - **FlexMD 2.0 Output**: Always returns structured-text format with parsed payloads
26
- - **Catalox-native templates**: Instruction and prompt bodies are read from the Firestore native catalog **`ai-skills`** via **`@x12i/catalox`**; the gateway receives **inline template text** (not nx-content registry keys for skills)
27
- - **Provision from disk**: `npm run catalox:provision-ai-skills` merges `.metadata/skills/*.instructions.md` / `*.prompt.md` into that catalog and sets `planned` | `draft` | `published` status
28
- - **Presentation API**: Load pretty markdown for editors, save with lossless storage normalization (`getSkillTemplatesForPresentation`, `updateSkillTemplatesFromPresentation`, `getSkillTemplateInputs`); see [Skill templates (Catalox)](#skill-templates-catalox)
29
- - **Catalog audit fields (storage only)**: Catalox rows may store optional audit template text for editors; this package **only** executes **`runSkill`** (no built-in audit pass).
30
- - **Gateway integration**: `@x12i/ai-gateway` for LLM calls only; the built-in gateway has **`enableContentRegistry: false`** — skill templates are **never** loaded from nx-content / GitHub in this package
31
- - **Activity tracking**: With `enableActivityTracking`, the gateway persists activities via **`@x12i/activix`**; this SDK enriches rows with **`cost` / `costStatus`** and contract-shaped **`outer.output.parsed`** when applicable (see [Cost and output contract](#cost-and-output-contract-run-analysis))
32
- - **Model Configuration**: Per-request model selection and parameter control (temperature, maxTokens, etc.)
33
- - **Full template pipeline (default)**: Uses `gateway.invoke()` with inline bodies; the gateway message builder renders tokens (downstream stack; e.g. Rendrix). `runSkill` populates `workingMemory.input` and can pass `templateRenderOptions`, `templateTokens`, or client-level `templateRendering` (see **[docs/AI_SKILLS_GATEWAY_TEMPLATES.md](docs/AI_SKILLS_GATEWAY_TEMPLATES.md)**).
34
- - **Execution engine catalog**: Optional **`aiEngineId`** echoes the Catalox **`ai-engines`** row (**`ai-gateway`** only; provision with **`npm run catalox:provision-ai-engines`**) for discovery and **`identity`** — see [Execution engines (`aiEngineId`)](#2b-execution-engines-aiengineid).
35
- - **Request analysis (implementation-phase)**: **`analyzeSkillRequest`** simulates the gateway packet and reviews it via FuncX — **not** chained into **`runSkill`**; see **[docs/SKILL_REQUEST_ANALYSIS.md](docs/SKILL_REQUEST_ANALYSIS.md)**.
36
-
37
- ## Installation
38
-
39
- ```bash
40
- npm install @exellix/ai-skills
41
- ```
42
-
43
- This package depends on **`@x12i/ai-gateway`**, **`@x12i/catalox`**, **`@x12i/logxer`**, **`@x12i/env`**, **`@x12i/rendrix`**, and **`firebase-admin`** (for Firestore-backed Catalox). **`@x12i/catalox` ≥ 4.0** expects **Node 20+**. Align **`firebase-admin`** / **`@x12i/catalox`** versions with your app if you share a Firebase app instance.
44
-
45
- **`@x12i/ai-gateway` ≥ 9.3.0** is recommended: invoke **`metadata`** includes routing fields (≥9.1.1), rejection **`metadata`** on failures (≥9.1.2), and normalized billing (**`costUsd`**, **`cost`**, **`costStatus: priced | unpriced`**) on success (≥9.3). See **`docs/AI_GATEWAY_INVOKE_EXECUTION_METADATA.md`**. Every **`runSkill`** call must include **`agentId`**, **`jobTypeId`**, and **`taskTypeId`** (Activix linkage — no package defaults).
46
-
47
- **CI:** `npm run test:ci` runs **`npm run build`** and **`npm run test:unit`** (no live Firestore or real LLM). Full **`npm test`** still runs **`test:integration`**, which may append live catalog tests when env enables them.
48
-
49
- Custom admin flows that call Catalox with **`createCatalog`**, **`bindCatalogToApp`**, or similar before an app binding exists should set **`superAdmin: true`** on **`CataloxContext`** (see **`defaultAiSkillsCataloxContext`** overrides and **[docs/CATALOX_PEER_GUIDE.md](docs/CATALOX_PEER_GUIDE.md)**).
50
-
51
- **Publishing:** the npm tarball is limited to **`dist/`**, **`README.md`**, and **`erc-manifest.json`** via the **`files`** field in **`package.json`** (avoids shipping tests, logs, or local scratch files).
52
-
53
- ## Environment variables (Firebase & Catalox v4)
54
-
55
- **`createCataloxFromEnv()`** in this package delegates credential resolution to **`@x12i/catalox/firebase`** (same precedence as upstream Catalox **v4**).
56
-
57
- | Variable | Notes |
58
- |----------|--------|
59
- | **`FIREBASE_PROJECT_ID`** | **Required** for this package’s **`createCataloxFromEnv()`** wiring: set it in `.env` or the process environment. Project id is **not** inferred from **`GCLOUD_PROJECT`**, **`GOOGLE_CLOUD_PROJECT`**, or service-account JSON. |
60
- | **`GOOGLE_SERVICE_ACCOUNT_BASE64`** | Recommended for CI and scripts: standard Google **service account JSON**, base64-encoded, passed to Admin **`cert(...)`**. |
61
- | **`FIRESTORE_DATABASE_ID`** | Optional named Firestore database id; omit for the default database. |
62
-
63
- **Not supported in Catalox v4:** an environment variable (or secret-token **`_path`** field) that points at a **service-account JSON file on disk**. Use **`GOOGLE_SERVICE_ACCOUNT_BASE64`**, Application Default Credentials (workload identity, **`GOOGLE_APPLICATION_CREDENTIALS`**, etc.), or load a key file **in your application** and pass **`cert(...)`** / Catalox’s **`serviceAccountPath`** bootstrap **option** (caller-supplied string only).
64
-
65
- Provider keys for the default gateway (**`OPENAI_API_KEY`**, **`GROK_API_KEY`**, **`OPEN_ROUTER_KEY`** / **`OPENROUTER_API_KEY`**) are unchanged — see **Troubleshooting** below.
66
-
67
- ## Quick Start
68
-
69
- ### 0) Prerequisites
70
-
71
- 1. **Node.js 20+** — matches **`@x12i/catalox`** engine requirements.
72
- 2. **Firebase / Firestore** — set **`FIREBASE_PROJECT_ID`** (see [above](#environment-firebase--catalox-v4)) and **`GOOGLE_SERVICE_ACCOUNT_BASE64`**. **`createCataloxFromEnv()`** uses **`@x12i/catalox/firebase`** under the hood. You can instead construct **`Catalox`** with **`createCatalox`** from **`@x12i/catalox`** / **`@x12i/catalox/embedder`** and your own **`Firestore`** instance.
73
- 3. **Provision the catalog** (merges in-repo `.metadata/skills` into Catalox):
74
-
75
- ```bash
76
- npm run catalox:provision-ai-skills
77
- npm run catalox:provision-ai-engines
78
- ```
79
-
80
- 4. At least one **LLM provider** key (`OPENAI_API_KEY`, `GROK_API_KEY`, or `OPEN_ROUTER_KEY` / `OPENROUTER_API_KEY`) for **`@x12i/ai-gateway`**.
81
-
82
- ### 1) Create the client
83
-
84
- ```typescript
85
- import { ExellixSkillsClient, RunSkillRequest, RunSkillResponse, createCataloxFromEnv } from "@exellix/ai-skills";
86
-
87
- const catalox = createCataloxFromEnv();
88
-
89
- const skills = new ExellixSkillsClient({
90
- catalox,
91
- enableActivityTracking: true,
92
- });
93
- ```
94
-
95
- You can also construct **`Catalox`** yourself (`createCatalox` from **`@x12i/catalox`** or **`@x12i/catalox/embedder`** with your **`firestore`** / **`firebaseApp`**) and pass **`options.catalox`**. An external **`gateway`** is optional; **`catalox` is always required**.
96
-
97
- ### 2) Run a skill
98
-
99
- ```typescript
100
- // Using the RunSkillRequest interface
101
- const runSkillRequest: RunSkillRequest = {
102
- skillKey: "skills/professional-answer",
103
- input: "Question: What is the best way to migrate from X to Y?",
104
- variables: { orgName: "ACME", audience: "security team" },
105
- jobId: "job-123",
106
- taskId: "task-456",
107
- agentId: "agent-abc",
108
- jobTypeId: "your-job-type-id",
109
- taskTypeId: "your-task-type-id",
110
- };
111
-
112
- const res: RunSkillResponse = await skills.runSkill(runSkillRequest);
113
-
114
- // Access FlexMD payloads
115
- console.log(res.flexMd.payloads.shortAnswer);
116
- console.log(res.flexMd.payloads.fullAnswer);
117
- ```
118
-
119
- ### 2b) Execution engines (`aiEngineId`)
120
-
121
- **`runSkill`** always invokes **`@x12i/ai-gateway`** with inline Catalox template text; **Rendrix runs inside the gateway**. Optional **`aiEngineId`** must be omitted, blank, or **`ai-gateway`** — it is merged into **`gateway.invoke`** **`identity`** and matches the single row in the Catalox **`ai-engines`** catalog (provisioned for listing / UIs). Unknown values throw at resolve time.
122
-
123
- ### 2a) Run a skill with model configuration
124
-
125
- ```typescript
126
- // Override model and generation parameters per-request
127
- const res = await skills.runSkill({
128
- skillKey: "skills/professional-answer",
129
- input: "Question: What is the best way to migrate from X to Y?",
130
- variables: { orgName: "ACME" },
131
- jobId: "job-123",
132
- taskId: "task-456",
133
- agentId: "agent-abc",
134
- jobTypeId: "your-job-type-id",
135
- taskTypeId: "your-task-type-id",
136
- modelConfig: {
137
- model: "gpt-4-turbo",
138
- temperature: 0.7,
139
- maxTokens: 2000,
140
- topP: 0.9
141
- }
142
- });
143
- ```
144
-
145
- ### 2b) Optional: parser / template overrides
146
-
147
- Per skill run you can pass **`templateRenderOptions`** (merged on gateway defaults) and **`templateTokens`** (highest-priority overlay). On the client, **`templateRendering`** sets defaults for every invoke. Details and examples: **[docs/AI_SKILLS_GATEWAY_TEMPLATES.md](docs/AI_SKILLS_GATEWAY_TEMPLATES.md)**.
148
-
149
- ### 2c) Optional: identity propagation
150
-
151
- **`identity` is per-request runtime context** from your application (trace ids, tenant, graph/node ids, etc.). It is **not** the SDK client’s constructor options, **not** env/config for this package, and **not** “package identity” from `package.json`—unless you explicitly copy such values into this object when you build the request.
152
-
153
- **`identity` vs `runContext`:** The gateway **`invoke()`** API uses the property name **`identity`** (see `@x12i/ai-gateway`). Activix stores that same envelope on activity documents as BSON **`runContext`**. This SDK passes **`identity`** through to the gateway; you do **not** set `runContext` on `runSkill` inputs here. Responses expose the envelope as **`identity`** when the gateway returns it (and resolution may also consider metadata shapes that use `runContext`).
154
-
155
- You can pass an **`identity`** object to `runSkill()` to propagate caller identity context downstream.
156
-
157
- - If you provide `identity`, the client forwards it **as-is** to the gateway.
158
- - The client will add **`identity.skillId`** from the executing request **only if** it is missing.
159
- - The `RunSkillResponse` includes `identity` **only if downstream returns one**. If downstream does not provide `identity`, it will be omitted (no fallback).
160
-
161
- ```typescript
162
- const res = await skills.runSkill({
163
- skillKey: "skills/professional-answer",
164
- input: "Question: ...",
165
- jobId: "job-123",
166
- taskId: "task-456",
167
- agentId: "agent-abc",
168
- jobTypeId: "your-job-type-id",
169
- taskTypeId: "your-task-type-id",
170
- skillId: "node-q0",
171
- identity: {
172
- traceId: "trace-123",
173
- userId: "user-456"
174
- // skillId is optional here; if omitted, it will be set from `skillId` above
175
- }
176
- });
177
-
178
- // Only present when downstream returned it:
179
- console.log(res.identity);
180
-
181
- // Token usage + cost (from gateway metadata); always on metadata.usage for every run.
182
- // costUsd when priced; costStatus when usage exists but no price ("unpriced") or async pricing ("deferred").
183
- console.log(res.metadata.usage, res.metadata.costUsd, res.metadata.costStatus, res.metadata.modelUsed);
184
-
185
- // When `identity` is returned, the same snapshot is nested under `identity.aiSkillsLlm` for Activix-style
186
- // chaining—forward the whole `identity` to the next `runSkill` so pipelines retain billing context.
187
- console.log(res.identity?.aiSkillsLlm);
188
- ```
189
-
190
- ### 3) Max output tokens (`maxTokens`), `modelConfig`, and trace visibility
191
-
192
- **This SDK does not compute or default `maxTokens`.** If you set `runSkill({ modelConfig: { maxTokens: N } })`, that object is forwarded **verbatim** on `gateway.invoke({ modelConfig })` (same for `temperature`, `model`, provider-specific fields, etc.). If you omit `modelConfig` or omit `maxTokens`, the effective cap comes from **`@x12i/ai-gateway`** / the provider (see **[docs/AI_GATEWAY_FEATURE_REQUESTS.md](docs/AI_GATEWAY_FEATURE_REQUESTS.md)** for documenting defaults upstream).
193
-
194
- **Dynamic per call:** compute limits in your orchestrator and pass a fresh `modelConfig` every invoke; nothing in this package caches values between calls.
195
-
196
- | What you need | Where it appears |
197
- |---------------|------------------|
198
- | What you **sent** (`modelConfig`, `timeoutMs`) | Trace-only: **`debugTrace.invokeRequest`** when `executionMode: "trace"` or `diagnostics.includeDebugTrace` |
199
- | Requested / echoed cap | **`debugTrace.usage.maxTokensRequested`** (from your `modelConfig.maxTokens` when set; may align with gateway metadata when present) |
200
- | What **ran** (model id, token counts) | **`debugTrace.modelUsed`**, **`debugTrace.usage`**, routing ids |
201
-
202
- ```typescript
203
- const budget = estimateMaxOutputTokens(someContext); // your policy
204
-
205
- const res = await skills.runSkill({
206
- skillKey: "skills/professional-answer",
207
- input: "Question: ...",
208
- jobId: "job-1",
209
- taskId: "task-1",
210
- modelConfig: {
211
- maxTokens: budget,
212
- temperature: 0.5,
213
- model: "gpt-4-turbo",
214
- },
215
- executionMode: "trace",
216
- });
217
-
218
- console.log(res.debugTrace?.invokeRequest?.modelConfig?.maxTokens);
219
- console.log(res.debugTrace?.usage.maxTokensRequested);
220
- ```
221
-
222
- On gateway failures with trace mode enabled, **`SkillExecutionTraceError`** carries **`diagnostics.trace`** with the same shapes where available; metadata attached on the thrown error is merged when present (see **`metadataFromInvokeError`** export).
223
-
224
- ### Cost and output contract (Run Analysis)
225
-
226
- Studio / graph Run Analysis expects activities and responses to explain billing and structured output. This package normalizes both on every successful **`runSkill`** and best-effort patches the persisted Activix row when **`metadata.activityId`** is present.
227
-
228
- #### Cost reporting (`metadata.costUsd` / `metadata.costStatus`)
229
-
230
- Gateway **`invoke()`** metadata (≥ **9.3**) uses **`costUsd`** / **`cost`** when priced and **`costStatus: "priced" | "unpriced"`**. This SDK never echoes gateway **`"priced"`** — priced runs expose **`costUsd` only**. When usage exists but no USD total is known, **`costStatus: "unpriced"`** is set (gateway explicit flag or SDK fallback).
231
-
232
- | Gateway / router situation | Gateway `metadata` | On `RunSkillResponse.metadata` | Activix record (gateway `completeRecord` + SDK patch) |
233
- |----------------------------|-------------------|-------------------------------|------------------------------------------------------|
234
- | Priced (router, catalog, or ai-tools) | `costUsd`, `cost`, `costStatus: "priced"` | `costUsd` (no `costStatus`) | Top-level `cost`, `costStatus: "priced"`, `outer.metadata` billing slice, `outer.cost.usd` |
235
- | Usage, no price | `costStatus: "unpriced"` | `costStatus: "unpriced"` | `costStatus: "unpriced"` + token usage on `outer.metadata` / `response.metadata` |
236
- | Legacy async pricing | `costStatus: "deferred"` (older gateways) | `costStatus: "deferred"` | same |
237
-
238
- Gateway **`logSuccess`** writes billing on the activity row during **`invoke()`** when **`enableActivityTracking`** is on (default). **`patchActivixActivityAfterSkill`** then merges the same billing slice plus **`outputContract`** **`parsed`** fields so Run Analysis and Activix queries stay aligned with **`RunSkillResponse.metadata`**.
239
-
240
- `metadata.usage` is always populated (zeros when the gateway omits token fields). The same billing slice is mirrored on **`identity.aiSkillsLlm`** when the gateway returns **`identity`**, and on **`debugTrace`** in trace mode.
241
-
242
- #### Output contract (`outputContract`)
243
-
244
- **`outputContract` is per invoke**, not a global list for all skills. Pass the field names your graph node or skill expects on **`parsed`** / **`outer.output.parsed`** — typically from graph **`inputs.outputContract`** (via **`@exellix/ai-tasks`** / graph-engine).
245
-
246
- Supported shapes: `string[]`, `{ fields: string[] }`, `{ keys: string[] }`, or `{ required: string[] }`.
247
-
248
- After FlexMD / gateway parsing (and local **`parseFlexMd`** fallback), the SDK fills **missing** contract keys from:
249
-
250
- 1. **`flexMd.payloads`**
251
- 2. Markdown section headings (`### Short Answer` → `shortAnswer`)
252
-
253
- Examples (illustrative — use the contract for **that** skill/node):
254
-
255
- ```typescript
256
- // professional-answer–style skill
257
- await skills.runSkill({
258
- skillKey: "skills/professional-answer",
259
- outputContract: ["shortAnswer", "fullAnswer", "assumptions", "unknowns", "evidence"],
260
- // ...jobId, taskId, agentId, jobTypeId, taskTypeId, input, ...
261
- });
262
-
263
- // professional-decision–style skill
264
- await skills.runSkill({
265
- skillKey: "skills/professional-decision",
266
- outputContract: ["decision", "score", "rationale", "risks"],
267
- // ...
268
- });
269
- ```
270
-
271
- This package does **not** auto-map **`skillKey`** to a catalog contract; upstream must pass **`outputContract`** when structured fields are required. Catalog payload names are documented in **[docs/metadata.md](docs/metadata.md)** (`requiredPayloads` per skill).
272
-
273
- **Exports** (for orchestrators and tests): `normalizeSkillBillingFromGatewayMetadata`, `enrichParsedForOutputContract`, `patchActivixActivityAfterSkill`, types **`SkillCostStatus`**, **`OutputContract`**.
274
-
275
- ## Built-in skills (catalog)
276
-
277
- Authoritative rows are defined in code as **`AI_SKILLS_CATALOG_ITEMS`** and written to Catalox by **`npm run catalox:provision-ai-skills`**. Typical runnable keys:
278
-
279
- - **`skills/professional-answer`** — structured professional answer (FlexMD payloads)
280
- - **`skills/professional-decision`** — structured decision output
281
- - Additional catalog rows exist for other packaged skills; use **`listCatalogSkills()`** (all statuses), **`listPublishedSkills()`** (published only), or Catalox **`listCatalogItems`** for the live list.
282
-
283
- `runSkill` takes **`skillKey`** like `skills/professional-answer`. Bodies are **not** resolved as nx-content registry keys for execution; they are **loaded from Catalox** and sent to the gateway as **inline template strings**.
284
-
285
- ## Skill templates (Catalox)
286
-
287
- ### Provisioning (disk → Firestore)
288
-
289
- Keep canonical markdown under **`.metadata/skills/<id>.instructions.md`** and **`.metadata/skills/<id>.prompt.md`**, then run:
290
-
291
- ```bash
292
- npm run catalox:provision-ai-skills
293
- ```
294
-
295
- That merges file contents into the native catalog **`ai-skills`**, sets **`instructionsText` / `promptText`**, and computes **`status`**: `planned` | `draft` | `published` (runnable when both bodies exist and status is `draft` or `published`).
296
-
297
- ### Presentation layer (read / edit)
298
-
299
- Low-level helpers live under **`@exellix/ai-skills`** exports from **`./catalox`** (e.g. **`normalizeForStorage`**, **`toPresentationMarkdown`**, **`extractTemplateTokensFromTexts`**).
300
-
301
- On **`ExellixSkillsClient`**:
302
-
303
- | Method | Purpose |
304
- |--------|---------|
305
- | **`getSkillTemplatesForPresentation(skillKey, { includeAudit? })`** | Catalox read → markdown formatted for editors |
306
- | **`updateSkillTemplatesFromPresentation(skillKey, patch, options?)`** | Editor markdown → lossless storage normalization → Catalox upsert (requires **write** Catalox context) |
307
- | **`getSkillTemplateInputs(skillKey)`** | Union of **`{{token}}`** placeholders from raw stored bodies; treats **`input`** as the primary payload token |
308
- | **`listCatalogSkills(options?)`** | Lists every catalog row (all **`status`** values); forwards Catalox query options (e.g. **`limit`**) |
309
- | **`upsertSkillCatalogItem(input, options?)`** | Create/update full row (metadata + optional markdown); optional **`ifNotExists`** for strict create |
310
- | **`softDeleteSkillCatalogItem(skillKey)`** | Clears bodies and audit fields and sets **`planned`** (no hard delete) |
311
-
312
- **Write access:** updates use **`batchUpsertNativeCatalogItems`**; use a Catalox context with permission to write the catalog (e.g. provision-style god mode or an app binding with write). This package does not create credentials.
313
-
314
- ### Skill key usage
315
-
316
- ```typescript
317
- await skills.runSkill({
318
- skillKey: "skills/professional-answer",
319
- input: "Your question here...",
320
- });
321
- ```
322
-
323
- Catalox item ids are the short key (e.g. `professional-answer`); the client accepts either `skills/professional-answer` or the short id where helpers normalize.
324
-
325
- ### Troubleshooting
326
-
327
- ##### ❌ `options.catalox is required`
328
-
329
- Pass **`catalox`** into **`new ExellixSkillsClient({ catalox, ... })`**. Use **`createCataloxFromEnv()`** from this package, **`createCataloxFromEnv()`** from **`@x12i/catalox/firebase`**, or **`createCatalox({ firestore, firebaseApp })`** from **`@x12i/catalox`** / **`@x12i/catalox/embedder`**.
330
-
331
- ##### ❌ Skill is `planned` or “incomplete templates”
332
-
333
- Run **`npm run catalox:provision-ai-skills`** (or write bodies via **`updateSkillTemplatesFromPresentation`**) so **`instructionsText`** and **`promptText`** are non-empty and **`status`** is **`draft`** or **`published`**.
334
-
335
- ##### ❌ Provider / env validation
336
-
337
- At least one of **`OPENAI_API_KEY`**, **`GROK_API_KEY`**, **`OPEN_ROUTER_KEY`**, **`OPENROUTER_API_KEY`** is required when the client builds the default gateway. Firebase / Catalox variables are summarized [above](#environment-firebase--catalox-v4).
338
-
339
- ### Logging (`ai-skills` / `@x12i/logxer`)
340
-
341
- This package uses **[`@x12i/logxer`](https://www.npmjs.com/package/@x12i/logxer)** with a stable env prefix **`AI_SKILLS`**.
342
-
343
- | Item | Detail |
344
- |------|--------|
345
- | **Canonical env var** | **`AI_SKILLS_LOGS_LEVEL`** |
346
- | **Fallback** | **`AI_SKILLS_LOG_LEVEL`** is used only if **`AI_SKILLS_LOGS_LEVEL`** is unset |
347
- | **Default** (both unset) | **`warn`** — not silent; you will see warnings and errors |
348
- | **Silence this package** | Set **`AI_SKILLS_LOGS_LEVEL=off`** (or `none` / `silent`) |
349
- | **More detail** | `info`, `debug`, or `verbose` (case-insensitive; see **@x12i/logxer** docs) |
350
-
351
- Cross-cutting sinks (console, file, JSON, unified app config) are configured by the host app, not per this prefix — see the **@x12i/logxer** README.
352
-
353
- **`.env`:** with default **`autoLoadDotenv`**, the client loads **`.env`** from the current working directory using **`loadDotenv`** from **`@x12i/env`**.
354
-
355
- ```bash
356
- # Examples
357
- export AI_SKILLS_LOGS_LEVEL=info
358
- export AI_SKILLS_LOGS_LEVEL=debug
359
- export AI_SKILLS_LOGS_LEVEL=off
360
- ```
361
-
362
- `ExellixSkillsClient` passes **`packageName`** into the logger (default `"AI-SKILLS"`); that label appears in log metadata alongside the `[AI-SKILLS]` message prefix.
363
-
364
- ## Testing (integration + templates)
365
-
366
- This repo is **ESM** (`"type": "module"`). Use the npm scripts below (they run via **`tsx`** for `.ts` entrypoints).
367
-
368
- **Default test gate (`npm test`):** **`npm run build`** then **`npm run test:integration`** (may append live catalog tests when env enables them). **`npm run test:ci`** runs **`npm run build`** and **`npm run test:unit`** (mocked gateway / no Firestore / no LLM).
369
-
370
- ```bash
371
- npm test # build + integration harness (see run.ts)
372
- npm run test:ci # build + deterministic unit slice (CI-safe)
373
- npm run test:unit # same unit slice as test:ci (without build)
374
- npm run test:integration # full integration harness (run.ts)
375
- npm run catalox:provision-ai-skills # merge .metadata/skills → Firestore (before live tests)
376
- npm run catalox:provision-ai-engines # merge in-repo engine rows → native `ai-engines` (before engine catalog live test)
377
- npm run live:professional-answer # smoke: professional-answer via Catalox + default gateway LLM
378
- npm run test:real # extra real LLM scenarios; needs .env + valid Firebase credentials
379
- ```
380
-
381
- #### Live tests (opt-in, `test/run.ts`)
382
-
383
- Some cases use **real Catalox** (Firestore-backed catalog) and the same integration slice can invoke **real LLM** calls when provider keys are set. They are **off by default** so CI does not require a provisioned Firebase project.
384
-
385
- Set **`AI_SKILLS_LIVE_TESTS=1`** (or `true` / `yes` / `on`) before **`npm run test:integration`** to append the live cases registered in **`test/run.ts`**:
386
-
387
- - **Catalox `ai-skills` list** — read-only Firestore list sanity check.
388
- - **Catalox `ai-engines` list** — verifies **`npm run catalox:provision-ai-engines`** wrote the **`ai-gateway`** row (read-only).
389
- - **Skill template presentation** — read-only Catalox + presentation helpers.
390
-
391
- **Why not in `@x12i/catalox`:** only **this package’s** test harness reads **`AI_SKILLS_LIVE_TESTS`**; Catalox does not gate downstream CI.
392
-
393
- Optionally run **`npm run test:real`** before a major release (broader LLM + client scenarios; needs Firebase + provider keys in **`.env`**).
394
-
395
- **Flex-MD log noise (`extractJsonFromFlexMd` / `require(...) is not a function`):** that path lives in **`@x12i/ai-gateway`**, not in this package’s `parseFlexMd`. See **[docs/FLEX_MD_AND_TESTING.md](docs/FLEX_MD_AND_TESTING.md)** for the split of responsibilities and acceptance notes.
396
-
397
- ## Model Configuration
398
-
399
- You can control which model is used and how it generates responses on a per-request basis using the `modelConfig` field.
400
-
401
- ### Model Configuration Options
402
-
403
- ```typescript
404
- interface ModelConfig {
405
- /** Model identifier (e.g., "gpt-4-turbo", "claude-3-opus", "gpt-3.5-turbo") */
406
- model?: string;
407
-
408
- /** Model ID (alternative to model name, for provider-specific model IDs) */
409
- modelId?: string;
410
-
411
- /** Provider name (e.g., "openai", "anthropic") */
412
- provider?: string;
413
-
414
- /** Temperature for generation (0.0 to 2.0) - controls randomness */
415
- temperature?: number;
416
-
417
- /** Maximum tokens to generate */
418
- maxTokens?: number;
419
-
420
- /** Top-p (nucleus) sampling parameter (0.0 to 1.0) */
421
- topP?: number;
422
-
423
- /** Frequency penalty (-2.0 to 2.0) */
424
- frequencyPenalty?: number;
425
-
426
- /** Presence penalty (-2.0 to 2.0) */
427
- presencePenalty?: number;
428
-
429
- /** Stop sequences (array of strings) */
430
- stop?: string[];
431
-
432
- /** Additional provider-specific parameters */
433
- [key: string]: any;
434
- }
435
- ```
436
-
437
- ### Usage Examples
438
-
439
- #### Basic Model Selection
440
-
441
- ```typescript
442
- const res = await skills.runSkill({
443
- skillKey: "skills/professional-answer",
444
- input: "Your question here",
445
- modelConfig: {
446
- model: "gpt-4-turbo"
447
- }
448
- });
449
- ```
450
-
451
- #### Full Model Configuration
452
-
453
- ```typescript
454
- const res = await skills.runSkill({
455
- skillKey: "skills/professional-answer",
456
- input: "Your question here",
457
- modelConfig: {
458
- model: "gpt-4-turbo",
459
- temperature: 0.7,
460
- maxTokens: 2000,
461
- topP: 0.9,
462
- frequencyPenalty: 0.5
463
- }
464
- });
465
- ```
466
-
467
- #### Provider Override
468
-
469
- ```typescript
470
- const res = await skills.runSkill({
471
- skillKey: "skills/professional-answer",
472
- input: "Your question here",
473
- modelConfig: {
474
- provider: "anthropic",
475
- model: "claude-3-opus",
476
- temperature: 0.3
477
- }
478
- });
479
- ```
480
-
481
- #### Dynamic `maxTokens` per invoke
482
-
483
- ```typescript
484
- function maxTokensForStep(step: "summary" | "detail") {
485
- return step === "summary" ? 800 : 4000;
486
- }
487
-
488
- const res = await skills.runSkill({
489
- skillKey: "skills/professional-answer",
490
- input: "Your question here",
491
- jobId: "job-1",
492
- taskId: "task-1",
493
- modelConfig: {
494
- maxTokens: maxTokensForStep("detail"),
495
- temperature: 0.5,
496
- },
497
- });
498
- ```
499
-
500
- ### Notes
501
-
502
- - `modelConfig` is **optional**. If omitted, gateway/router defaults apply for each field.
503
- - This package does **not** merge a global default `modelConfig` at construction time; only **`templateRendering`** has client-wide defaults for template rendering.
504
- - The gateway and provider enforce valid ranges (e.g. temperature).
505
-
506
- ## API Reference
507
-
508
- ### `ExellixSkillsClient`
509
-
510
- #### Constructor options (essential)
511
-
512
- ```typescript
513
- import type { Catalox } from "@x12i/catalox";
514
- import type { TemplateRenderOptions } from "@x12i/ai-gateway";
515
-
516
- interface ExellixSkillsClientOptions {
517
- /** Required. Skill bodies are read from the native `ai-skills` catalog. */
518
- catalox: Catalox;
519
- /** Optional; defaults to catalog app id `ai-skills`. */
520
- cataloxAppId?: string;
521
-
522
- packageName?: string;
523
- enableActivityTracking?: boolean;
524
- /** Optional external gateway; skill templates still load from `catalox`. */
525
- gateway?: AIGateway;
526
-
527
- forceLocal?: boolean;
528
- forceCreateRootDirectory?: boolean;
529
- templateRendering?: TemplateRenderOptions;
530
- autoLoadDotenv?: boolean;
531
- testMode?: boolean;
532
- disableActivityTrackingInTests?: boolean;
533
- }
534
- ```
535
-
536
- The built-in **`AIGateway`** uses **`enableContentRegistry: false`**; **`GITHUB_*`** is **not** required for **`ExellixSkillsClient`** initialization.
537
-
538
- #### Methods
539
-
540
- - **`runSkill<T>(input: RunSkillRequest): Promise<RunSkillResponse<T>>`** — loads templates from Catalox, invokes gateway with inline bodies. Optional **`outputContract`** enriches **`parsed`** and the Activix activity row (see [Cost and output contract](#cost-and-output-contract-run-analysis)).
541
- - **`getSkillTemplatesForPresentation(skillKey, opts?)`** — editor-oriented markdown + metadata.
542
- - **`updateSkillTemplatesFromPresentation(skillKey, patch, opts?)`** — save edited markdown (lossless storage normalization); requires Catalox **write** context.
543
- - **`getSkillTemplateInputs(skillKey)`** — `{{token}}` discovery from stored bodies.
544
- - **`listPublishedSkills(options?)`** — Catalox rows with **`status === "published"`**.
545
- - **`listCatalogSkills(options?)`** — all catalog rows (any status); optional Catalox list query options.
546
- - **`upsertSkillCatalogItem(input, options?)`** — create/update catalog metadata and template bodies; **`options.ifNotExists`** enforces create-only.
547
- - **`softDeleteSkillCatalogItem(skillKey)`** — clears template and audit text and sets **`planned`**.
548
-
549
- Template bodies are loaded **only** through **`runSkill`** (and the same Catalox fetch used there): pass **`skillKey`**; there is no separate “peek at one section string” API.
550
-
551
- #### Diagnostic methods (gateway)
552
-
553
- - `testContentRegistryConnection()`, `discoverContentStructure()`, `diagnoseSkillContent`, `listAvailableContent`, `runContentRegistryDiagnostics` — forwarded to **AIGateway**. With the default gateway, **nx-content is disabled**; these calls are mainly useful if you inject a **custom** `gateway` with its own content registry.
554
-
555
- For **`{{token}}`** discovery on stored bodies without invoking the LLM, use **`getSkillTemplateInputs(skillKey)`** (Catalox read + parse).
556
-
557
- ## FlexMD 2.0 Format
558
-
559
- Primary skills are designed around **structured markdown** (headings and/or Flex-MD style markers). A common explicit shape is:
560
-
561
- ```
562
- [[professional-answer]]
563
-
564
- @payload:shortAnswer
565
- Brief answer here...
566
-
567
- @payload:fullAnswer
568
- Detailed answer here...
569
-
570
- @payload:assumptions
571
- List of assumptions...
572
- ```
573
-
574
- The gateway normalizes responses with its own Flex-MD extraction. When it does not return parsed fields, this SDK uses **`parseFlexMd`** (`src/utils/flex-md-parser.ts`), which prefers the **`flex-md`** package via ESM `import()` and falls back to parsing `[[frame:...]]` + `@payload:` blocks.
575
-
576
- When you pass **`outputContract`**, the SDK also maps markdown `### Section` headings to camelCase keys (e.g. `### Full Answer` → `fullAnswer`) so professional-answer templates that use headings still populate **`parsed`** even without `@payload:` markers. See **[Cost and output contract](#cost-and-output-contract-run-analysis)**.
577
-
578
- Details and caveats: **[docs/FLEX_MD_AND_TESTING.md](docs/FLEX_MD_AND_TESTING.md)**.
579
-
580
- ## Requirements
581
-
582
- - **Node.js 20+** (see `package.json` **`engines`**; aligns with **`@x12i/catalox`**)
583
- - **`@x12i/catalox`** **≥ 4** and **Firebase** access for the **`ai-skills`** native catalog (see **`createCataloxFromEnv`** and [Environment (Firebase & Catalox v4)](#environment-firebase--catalox-v4))
584
- - **`@x12i/ai-gateway`** (see `package.json` for the supported range)
585
- - **`@x12i/activix`** (version aligned with **`@x12i/ai-gateway`** in `package.json`) when you rely on activity tracking
586
- - At least one LLM provider key when using the default constructed gateway
587
- - Optional: **`.metadata/skills`** on disk to feed **`npm run catalox:provision-ai-skills`**
588
- ## Related runtime packages
589
-
590
- Task and graph orchestration live in sibling packages, not in **`@exellix/ai-skills`**:
591
-
592
- - **`@exellix/ai-tasks`** — task orchestration and task-level runtime wiring
593
- - **`@exellix/graph-engine`** — graph execution and graph-level runtime wiring; forward each node’s **`inputs.outputContract`** on **`runSkill`** when Run Analysis or downstream validation require structured **`parsed`** fields
594
- - **`@exellix/exellix-runtime`** — root runtime composition (for example loading composed `runtimeObjects` for debug tooling)
595
-
596
- Older scopes such as **`@woroces/*`**, **`worox`**, **`worex`**, and graph packages named **`worox-graph`** / **`worex-graphs`** are not used here; there are **no** intentional aliases or compatibility shims for those names in this repository.
597
-
598
- ## License
599
-
600
- ISC
1
+ # @exellix/ai-skills
2
+
3
+ Foundational skill execution layer for the exellix ecosystem: templates live in the **Catalox** native catalog **`ai-skills`**, execution uses **`@x12i/ai-gateway`**, and responses support **FlexMD 2.0** structured text.
4
+
5
+ 🚀 **Env-Ready**: Provider keys can be loaded from `.env` automatically. **`catalox` is required** at construction time (see Quick Start).
6
+
7
+ ## Documentation
8
+
9
+ | Topic | Doc |
10
+ |--------|-----|
11
+ | Flex-MD vs gateway parsing, integration tests | **[docs/FLEX_MD_AND_TESTING.md](docs/FLEX_MD_AND_TESTING.md)** |
12
+ | Catalox integration & env notes | **[docs/CATALOX_PEER_GUIDE.md](docs/CATALOX_PEER_GUIDE.md)** · [Environment (Firebase & Catalox v4)](#environment-firebase--catalox-v4) |
13
+ | Activix integration best practices | **[.docs/activix-integration-best-practices-checklist.md](.docs/activix-integration-best-practices-checklist.md)** |
14
+ | Per-package log level (`AI_SKILLS_LOGS_LEVEL`) | **[Logging](#logging-ai-skills--x12ilogxer)** below and [@x12i/logxer on npm](https://www.npmjs.com/package/@x12i/logxer) |
15
+ | Gateway templates (v4), `invoke()` vs `invokeChat()`, `templateRendering` | **[docs/GATEWAY_TEMPLATE_PROTOCOL_V4.md](docs/GATEWAY_TEMPLATE_PROTOCOL_V4.md)** |
16
+ | This package: `workingMemory`, `templateRenderOptions`, client `templateRendering` | **[docs/AI_SKILLS_GATEWAY_TEMPLATES.md](docs/AI_SKILLS_GATEWAY_TEMPLATES.md)** |
17
+ | Invoke execution metadata (`provider`, `modelUsed`, `effectiveModelConfig`, …) | **[docs/AI_GATEWAY_INVOKE_EXECUTION_METADATA.md](docs/AI_GATEWAY_INVOKE_EXECUTION_METADATA.md)** |
18
+ | Graph execution context (`graphId`, `nodeId`, identity mapping) | **[docs/GRAPH_EXECUTION_SUPPORT.md](docs/GRAPH_EXECUTION_SUPPORT.md)** |
19
+ | Gateway invoke preflight (`analyzeSkillRequest`, FuncX ≥ 4.0.1) | **[docs/SKILL_REQUEST_ANALYSIS.md](docs/SKILL_REQUEST_ANALYSIS.md)** |
20
+ | External follow-ups (Activix persistence vs gateway envelope) | **[docs/AI_GATEWAY_FEATURE_REQUESTS.md](docs/AI_GATEWAY_FEATURE_REQUESTS.md)** |
21
+
22
+ ## Features
23
+
24
+ - **Unified Skill Runner**: Execute skills via metadata, not hardcoded handlers
25
+ - **FlexMD 2.0 Output**: Always returns structured-text format with parsed payloads
26
+ - **Catalox-native templates**: Instruction and prompt bodies are read from the Firestore native catalog **`ai-skills`** via **`@x12i/catalox`**; the gateway receives **inline template text** (not nx-content registry keys for skills)
27
+ - **Provision from disk**: `npm run catalox:provision-ai-skills` merges `.metadata/skills/*.instructions.md` / `*.prompt.md` into that catalog and sets `planned` | `draft` | `published` status
28
+ - **Presentation API**: Load pretty markdown for editors, save with lossless storage normalization (`getSkillTemplatesForPresentation`, `updateSkillTemplatesFromPresentation`, `getSkillTemplateInputs`); see [Skill templates (Catalox)](#skill-templates-catalox)
29
+ - **Catalog audit fields (storage only)**: Catalox rows may store optional audit template text for editors; this package **only** executes **`runSkill`** (no built-in audit pass).
30
+ - **Gateway integration**: `@x12i/ai-gateway` for LLM calls only; the built-in gateway has **`enableContentRegistry: false`** — skill templates are **never** loaded from nx-content / GitHub in this package
31
+ - **Activity tracking**: With `enableActivityTracking`, the gateway persists activities via **`@x12i/activix`**; this SDK enriches rows with **`cost` / `costStatus`** and contract-shaped **`outer.output.parsed`** when applicable (see [Cost and output contract](#cost-and-output-contract-run-analysis))
32
+ - **Model Configuration**: Per-request model selection and parameter control (temperature, maxTokens, etc.)
33
+ - **Full template pipeline (default)**: Uses `gateway.invoke()` with inline bodies; the gateway message builder renders tokens (downstream stack; e.g. Rendrix). `runSkill` populates `workingMemory.input` and can pass `templateRenderOptions`, `templateTokens`, or client-level `templateRendering` (see **[docs/AI_SKILLS_GATEWAY_TEMPLATES.md](docs/AI_SKILLS_GATEWAY_TEMPLATES.md)**).
34
+ - **Execution engine catalog**: Optional **`aiEngineId`** echoes the Catalox **`ai-engines`** row (**`ai-gateway`** only; provision with **`npm run catalox:provision-ai-engines`**) for discovery and **`identity`** — see [Execution engines (`aiEngineId`)](#2b-execution-engines-aiengineid).
35
+ - **Request analysis (implementation-phase)**: **`analyzeSkillRequest`** simulates the gateway packet and reviews it via FuncX — **not** chained into **`runSkill`**; see **[docs/SKILL_REQUEST_ANALYSIS.md](docs/SKILL_REQUEST_ANALYSIS.md)**.
36
+
37
+ ## Installation
38
+
39
+ ```bash
40
+ npm install @exellix/ai-skills
41
+ ```
42
+
43
+ This package depends on **`@x12i/ai-gateway`**, **`@x12i/catalox`**, **`@x12i/logxer`**, **`@x12i/env`**, **`@x12i/rendrix`**, and **`firebase-admin`** (for Firestore-backed Catalox). **`@x12i/catalox` ≥ 4.0** expects **Node 20+**. Align **`firebase-admin`** / **`@x12i/catalox`** versions with your app if you share a Firebase app instance.
44
+
45
+ **`@x12i/ai-gateway` ≥ 9.5.2** (see `package.json`) with **`@x12i/activix` ^8** is recommended: invoke **`metadata`** includes routing fields (≥9.1.1), rejection **`metadata`** on failures (≥9.1.2), and normalized billing (**`costUsd`**, **`cost`**, **`costStatus: priced | unpriced`**) on success (≥9.3). See **`docs/AI_GATEWAY_INVOKE_EXECUTION_METADATA.md`**. Every **`runSkill`** call must include **`agentId`**, **`jobTypeId`**, and **`taskTypeId`** (Activix linkage — no package defaults).
46
+
47
+ **CI:** `npm run test:ci` runs **`npm run build`** and **`npm run test:unit`** (no live Firestore or real LLM). Full **`npm test`** still runs **`test:integration`**, which may append live catalog tests when env enables them.
48
+
49
+ Custom admin flows that call Catalox with **`createCatalog`**, **`bindCatalogToApp`**, or similar before an app binding exists should set **`superAdmin: true`** on **`CataloxContext`** (see **`defaultAiSkillsCataloxContext`** overrides and **[docs/CATALOX_PEER_GUIDE.md](docs/CATALOX_PEER_GUIDE.md)**).
50
+
51
+ **Publishing:** the npm tarball is limited to **`dist/`**, **`README.md`**, and **`erc-manifest.json`** via the **`files`** field in **`package.json`** (avoids shipping tests, logs, or local scratch files).
52
+
53
+ ## Environment variables (Firebase & Catalox v4)
54
+
55
+ **`createCataloxFromEnv()`** in this package delegates credential resolution to **`@x12i/catalox/firebase`** (same precedence as upstream Catalox **v4**).
56
+
57
+ | Variable | Notes |
58
+ |----------|--------|
59
+ | **`FIREBASE_PROJECT_ID`** | **Required** for this package’s **`createCataloxFromEnv()`** wiring: set it in `.env` or the process environment. Project id is **not** inferred from **`GCLOUD_PROJECT`**, **`GOOGLE_CLOUD_PROJECT`**, or service-account JSON. |
60
+ | **`GOOGLE_SERVICE_ACCOUNT_BASE64`** | Recommended for CI and scripts: standard Google **service account JSON**, base64-encoded, passed to Admin **`cert(...)`**. |
61
+ | **`FIRESTORE_DATABASE_ID`** | Optional named Firestore database id; omit for the default database. |
62
+
63
+ **Not supported in Catalox v4:** an environment variable (or secret-token **`_path`** field) that points at a **service-account JSON file on disk**. Use **`GOOGLE_SERVICE_ACCOUNT_BASE64`**, Application Default Credentials (workload identity, **`GOOGLE_APPLICATION_CREDENTIALS`**, etc.), or load a key file **in your application** and pass **`cert(...)`** / Catalox’s **`serviceAccountPath`** bootstrap **option** (caller-supplied string only).
64
+
65
+ Provider keys for the default gateway (**`OPENAI_API_KEY`**, **`GROK_API_KEY`**, **`OPEN_ROUTER_KEY`** / **`OPENROUTER_API_KEY`**) are unchanged — see **Troubleshooting** below.
66
+
67
+ ## Quick Start
68
+
69
+ ### 0) Prerequisites
70
+
71
+ 1. **Node.js 20+** — matches **`@x12i/catalox`** engine requirements.
72
+ 2. **Firebase / Firestore** — set **`FIREBASE_PROJECT_ID`** (see [above](#environment-firebase--catalox-v4)) and **`GOOGLE_SERVICE_ACCOUNT_BASE64`**. **`createCataloxFromEnv()`** uses **`@x12i/catalox/firebase`** under the hood. You can instead construct **`Catalox`** with **`createCatalox`** from **`@x12i/catalox`** / **`@x12i/catalox/embedder`** and your own **`Firestore`** instance.
73
+ 3. **Provision the catalog** (merges in-repo `.metadata/skills` into Catalox):
74
+
75
+ ```bash
76
+ npm run catalox:provision-ai-skills
77
+ npm run catalox:provision-ai-engines
78
+ ```
79
+
80
+ 4. At least one **LLM provider** key (`OPENAI_API_KEY`, `GROK_API_KEY`, or `OPEN_ROUTER_KEY` / `OPENROUTER_API_KEY`) for **`@x12i/ai-gateway`**.
81
+
82
+ ### 1) Create the client
83
+
84
+ ```typescript
85
+ import { ExellixSkillsClient, RunSkillRequest, RunSkillResponse, createCataloxFromEnv } from "@exellix/ai-skills";
86
+
87
+ const catalox = createCataloxFromEnv();
88
+
89
+ const skills = new ExellixSkillsClient({
90
+ catalox,
91
+ enableActivityTracking: true,
92
+ });
93
+ ```
94
+
95
+ You can also construct **`Catalox`** yourself (`createCatalox` from **`@x12i/catalox`** or **`@x12i/catalox/embedder`** with your **`firestore`** / **`firebaseApp`**) and pass **`options.catalox`**. An external **`gateway`** is optional; **`catalox` is always required**.
96
+
97
+ ### 2) Run a skill
98
+
99
+ ```typescript
100
+ // Using the RunSkillRequest interface
101
+ const runSkillRequest: RunSkillRequest = {
102
+ skillKey: "skills/professional-answer",
103
+ input: "Question: What is the best way to migrate from X to Y?",
104
+ variables: { orgName: "ACME", audience: "security team" },
105
+ jobId: "job-123",
106
+ taskId: "task-456",
107
+ agentId: "agent-abc",
108
+ jobTypeId: "your-job-type-id",
109
+ taskTypeId: "your-task-type-id",
110
+ };
111
+
112
+ const res: RunSkillResponse = await skills.runSkill(runSkillRequest);
113
+
114
+ // Access FlexMD payloads
115
+ console.log(res.flexMd.payloads.shortAnswer);
116
+ console.log(res.flexMd.payloads.fullAnswer);
117
+ ```
118
+
119
+ ### 2b) Execution engines (`aiEngineId`)
120
+
121
+ **`runSkill`** always invokes **`@x12i/ai-gateway`** with inline Catalox template text; **Rendrix runs inside the gateway**. Optional **`aiEngineId`** must be omitted, blank, or **`ai-gateway`** — it is merged into **`gateway.invoke`** **`identity`** and matches the single row in the Catalox **`ai-engines`** catalog (provisioned for listing / UIs). Unknown values throw at resolve time.
122
+
123
+ ### 2a) Run a skill with model configuration
124
+
125
+ ```typescript
126
+ // Override model and generation parameters per-request
127
+ const res = await skills.runSkill({
128
+ skillKey: "skills/professional-answer",
129
+ input: "Question: What is the best way to migrate from X to Y?",
130
+ variables: { orgName: "ACME" },
131
+ jobId: "job-123",
132
+ taskId: "task-456",
133
+ agentId: "agent-abc",
134
+ jobTypeId: "your-job-type-id",
135
+ taskTypeId: "your-task-type-id",
136
+ modelConfig: {
137
+ model: "gpt-4-turbo",
138
+ temperature: 0.7,
139
+ maxTokens: 2000,
140
+ topP: 0.9
141
+ }
142
+ });
143
+ ```
144
+
145
+ ### 2b) Optional: parser / template overrides
146
+
147
+ Per skill run you can pass **`templateRenderOptions`** (merged on gateway defaults) and **`templateTokens`** (highest-priority overlay). On the client, **`templateRendering`** sets defaults for every invoke. Details and examples: **[docs/AI_SKILLS_GATEWAY_TEMPLATES.md](docs/AI_SKILLS_GATEWAY_TEMPLATES.md)**.
148
+
149
+ ### 2c) Optional: identity propagation
150
+
151
+ **`identity` is per-request runtime context** from your application (trace ids, tenant, graph/node ids, etc.). It is **not** the SDK client’s constructor options, **not** env/config for this package, and **not** “package identity” from `package.json`—unless you explicitly copy such values into this object when you build the request.
152
+
153
+ **`identity` vs `runContext`:** The gateway **`invoke()`** API uses the property name **`identity`** (see `@x12i/ai-gateway`). Activix stores that same envelope on activity documents as BSON **`runContext`**. This SDK passes **`identity`** through to the gateway; you do **not** set `runContext` on `runSkill` inputs here. Responses expose the envelope as **`identity`** when the gateway returns it (and resolution may also consider metadata shapes that use `runContext`).
154
+
155
+ You can pass an **`identity`** object to `runSkill()` to propagate caller identity context downstream.
156
+
157
+ - If you provide `identity`, the client forwards it **as-is** to the gateway.
158
+ - The client will add **`identity.skillId`** from the executing request **only if** it is missing.
159
+ - The `RunSkillResponse` includes `identity` **only if downstream returns one**. If downstream does not provide `identity`, it will be omitted (no fallback).
160
+
161
+ ```typescript
162
+ const res = await skills.runSkill({
163
+ skillKey: "skills/professional-answer",
164
+ input: "Question: ...",
165
+ jobId: "job-123",
166
+ taskId: "task-456",
167
+ agentId: "agent-abc",
168
+ jobTypeId: "your-job-type-id",
169
+ taskTypeId: "your-task-type-id",
170
+ skillId: "node-q0",
171
+ identity: {
172
+ traceId: "trace-123",
173
+ userId: "user-456"
174
+ // skillId is optional here; if omitted, it will be set from `skillId` above
175
+ }
176
+ });
177
+
178
+ // Only present when downstream returned it:
179
+ console.log(res.identity);
180
+
181
+ // Token usage + cost (from gateway metadata); always on metadata.usage for every run.
182
+ // costUsd when priced; costStatus when usage exists but no price ("unpriced") or async pricing ("deferred").
183
+ console.log(res.metadata.usage, res.metadata.costUsd, res.metadata.costStatus, res.metadata.modelUsed);
184
+
185
+ // When `identity` is returned, the same snapshot is nested under `identity.aiSkillsLlm` for Activix-style
186
+ // chaining—forward the whole `identity` to the next `runSkill` so pipelines retain billing context.
187
+ console.log(res.identity?.aiSkillsLlm);
188
+ ```
189
+
190
+ ### 3) Max output tokens (`maxTokens`), `modelConfig`, and trace visibility
191
+
192
+ **This SDK does not compute or default `maxTokens`.** If you set `runSkill({ modelConfig: { maxTokens: N } })`, that object is forwarded **verbatim** on `gateway.invoke({ modelConfig })` (same for `temperature`, `model`, provider-specific fields, etc.). If you omit `modelConfig` or omit `maxTokens`, the effective cap comes from **`@x12i/ai-gateway`** / the provider (see **[docs/AI_GATEWAY_FEATURE_REQUESTS.md](docs/AI_GATEWAY_FEATURE_REQUESTS.md)** for documenting defaults upstream).
193
+
194
+ **Dynamic per call:** compute limits in your orchestrator and pass a fresh `modelConfig` every invoke; nothing in this package caches values between calls.
195
+
196
+ | What you need | Where it appears |
197
+ |---------------|------------------|
198
+ | What you **sent** (`modelConfig`, `timeoutMs`) | Trace-only: **`debugTrace.invokeRequest`** when `executionMode: "trace"` or `diagnostics.includeDebugTrace` |
199
+ | Requested / echoed cap | **`debugTrace.usage.maxTokensRequested`** (from your `modelConfig.maxTokens` when set; may align with gateway metadata when present) |
200
+ | What **ran** (model id, token counts) | **`debugTrace.modelUsed`**, **`debugTrace.usage`**, routing ids |
201
+
202
+ ```typescript
203
+ const budget = estimateMaxOutputTokens(someContext); // your policy
204
+
205
+ const res = await skills.runSkill({
206
+ skillKey: "skills/professional-answer",
207
+ input: "Question: ...",
208
+ jobId: "job-1",
209
+ taskId: "task-1",
210
+ modelConfig: {
211
+ maxTokens: budget,
212
+ temperature: 0.5,
213
+ model: "gpt-4-turbo",
214
+ },
215
+ executionMode: "trace",
216
+ });
217
+
218
+ console.log(res.debugTrace?.invokeRequest?.modelConfig?.maxTokens);
219
+ console.log(res.debugTrace?.usage.maxTokensRequested);
220
+ ```
221
+
222
+ On gateway failures with trace mode enabled, **`SkillExecutionTraceError`** carries **`diagnostics.trace`** with the same shapes where available; metadata attached on the thrown error is merged when present (see **`metadataFromInvokeError`** export).
223
+
224
+ ### Cost and output contract (Run Analysis)
225
+
226
+ Studio / graph Run Analysis expects activities and responses to explain billing and structured output. This package normalizes both on every successful **`runSkill`** and best-effort patches the persisted Activix row when **`metadata.activityId`** is present.
227
+
228
+ #### Cost reporting (`metadata.costUsd` / `metadata.costStatus`)
229
+
230
+ Gateway **`invoke()`** metadata (≥ **9.3**) uses **`costUsd`** / **`cost`** when priced and **`costStatus: "priced" | "unpriced"`**. This SDK never echoes gateway **`"priced"`** — priced runs expose **`costUsd` only**. When usage exists but no USD total is known, **`costStatus: "unpriced"`** is set (gateway explicit flag or SDK fallback).
231
+
232
+ | Gateway / router situation | Gateway `metadata` | On `RunSkillResponse.metadata` | Activix record (gateway `completeRecord` + SDK patch) |
233
+ |----------------------------|-------------------|-------------------------------|------------------------------------------------------|
234
+ | Priced (router, catalog, or ai-tools) | `costUsd`, `cost`, `costStatus: "priced"` | `costUsd` (no `costStatus`) | Top-level `cost`, `costStatus: "priced"`, `outer.metadata` billing slice, `outer.cost.usd` |
235
+ | Usage, no price | `costStatus: "unpriced"` | `costStatus: "unpriced"` | `costStatus: "unpriced"` + token usage on `outer.metadata` / `response.metadata` |
236
+ | Legacy async pricing | `costStatus: "deferred"` (older gateways) | `costStatus: "deferred"` | same |
237
+
238
+ Gateway **`logSuccess`** writes billing on the activity row during **`invoke()`** when **`enableActivityTracking`** is on (default). **`patchActivixActivityAfterSkill`** then merges the same billing slice plus **`outputContract`** **`parsed`** fields so Run Analysis and Activix queries stay aligned with **`RunSkillResponse.metadata`**.
239
+
240
+ `metadata.usage` is always populated (zeros when the gateway omits token fields). The same billing slice is mirrored on **`identity.aiSkillsLlm`** when the gateway returns **`identity`**, and on **`debugTrace`** in trace mode.
241
+
242
+ #### Output contract (`outputContract`)
243
+
244
+ **`outputContract` is per invoke**, not a global list for all skills. Pass the field names your graph node or skill expects on **`parsed`** / **`outer.output.parsed`** — typically from graph **`inputs.outputContract`** (via **`@exellix/ai-tasks`** / graph-engine).
245
+
246
+ Supported shapes: `string[]`, `{ fields: string[] }`, `{ keys: string[] }`, or `{ required: string[] }`.
247
+
248
+ After FlexMD / gateway parsing (and local **`parseFlexMd`** fallback), the SDK fills **missing** contract keys from:
249
+
250
+ 1. **`flexMd.payloads`**
251
+ 2. Markdown section headings (`### Short Answer` → `shortAnswer`)
252
+
253
+ Examples (illustrative — use the contract for **that** skill/node):
254
+
255
+ ```typescript
256
+ // professional-answer–style skill
257
+ await skills.runSkill({
258
+ skillKey: "skills/professional-answer",
259
+ outputContract: ["shortAnswer", "fullAnswer", "assumptions", "unknowns", "evidence"],
260
+ // ...jobId, taskId, agentId, jobTypeId, taskTypeId, input, ...
261
+ });
262
+
263
+ // professional-decision–style skill
264
+ await skills.runSkill({
265
+ skillKey: "skills/professional-decision",
266
+ outputContract: ["decision", "score", "rationale", "risks"],
267
+ // ...
268
+ });
269
+ ```
270
+
271
+ This package does **not** auto-map **`skillKey`** to a catalog contract; upstream must pass **`outputContract`** when structured fields are required. Catalog payload names are documented in **[docs/metadata.md](docs/metadata.md)** (`requiredPayloads` per skill).
272
+
273
+ **Exports** (for orchestrators and tests): `normalizeSkillBillingFromGatewayMetadata`, `enrichParsedForOutputContract`, `patchActivixActivityAfterSkill`, types **`SkillCostStatus`**, **`OutputContract`**.
274
+
275
+ ## Built-in skills (catalog)
276
+
277
+ Authoritative rows are defined in code as **`AI_SKILLS_CATALOG_ITEMS`** and written to Catalox by **`npm run catalox:provision-ai-skills`**. Typical runnable keys:
278
+
279
+ - **`skills/professional-answer`** — structured professional answer (FlexMD payloads)
280
+ - **`skills/professional-decision`** — structured decision output
281
+ - Additional catalog rows exist for other packaged skills; use **`listCatalogSkills()`** (all statuses), **`listPublishedSkills()`** (published only), or Catalox **`listCatalogItems`** for the live list.
282
+
283
+ `runSkill` takes **`skillKey`** like `skills/professional-answer`. Bodies are **not** resolved as nx-content registry keys for execution; they are **loaded from Catalox** and sent to the gateway as **inline template strings**.
284
+
285
+ ## Skill templates (Catalox)
286
+
287
+ ### Provisioning (disk → Firestore)
288
+
289
+ Keep canonical markdown under **`.metadata/skills/<id>.instructions.md`** and **`.metadata/skills/<id>.prompt.md`**, then run:
290
+
291
+ ```bash
292
+ npm run catalox:provision-ai-skills
293
+ ```
294
+
295
+ That merges file contents into the native catalog **`ai-skills`**, sets **`instructionsText` / `promptText`**, and computes **`status`**: `planned` | `draft` | `published` (runnable when both bodies exist and status is `draft` or `published`).
296
+
297
+ ### Presentation layer (read / edit)
298
+
299
+ Low-level helpers live under **`@exellix/ai-skills`** exports from **`./catalox`** (e.g. **`normalizeForStorage`**, **`toPresentationMarkdown`**, **`extractTemplateTokensFromTexts`**).
300
+
301
+ On **`ExellixSkillsClient`**:
302
+
303
+ | Method | Purpose |
304
+ |--------|---------|
305
+ | **`getSkillTemplatesForPresentation(skillKey, { includeAudit? })`** | Catalox read → markdown formatted for editors |
306
+ | **`updateSkillTemplatesFromPresentation(skillKey, patch, options?)`** | Editor markdown → lossless storage normalization → Catalox upsert (requires **write** Catalox context) |
307
+ | **`getSkillTemplateInputs(skillKey)`** | Union of **`{{token}}`** placeholders from raw stored bodies; treats **`input`** as the primary payload token |
308
+ | **`listCatalogSkills(options?)`** | Lists every catalog row (all **`status`** values); forwards Catalox query options (e.g. **`limit`**) |
309
+ | **`upsertSkillCatalogItem(input, options?)`** | Create/update full row (metadata + optional markdown); optional **`ifNotExists`** for strict create |
310
+ | **`softDeleteSkillCatalogItem(skillKey)`** | Clears bodies and audit fields and sets **`planned`** (no hard delete) |
311
+
312
+ **Write access:** updates use **`batchUpsertNativeCatalogItems`**; use a Catalox context with permission to write the catalog (e.g. provision-style god mode or an app binding with write). This package does not create credentials.
313
+
314
+ ### Skill key usage
315
+
316
+ ```typescript
317
+ await skills.runSkill({
318
+ skillKey: "skills/professional-answer",
319
+ input: "Your question here...",
320
+ });
321
+ ```
322
+
323
+ Catalox item ids are the short key (e.g. `professional-answer`); the client accepts either `skills/professional-answer` or the short id where helpers normalize.
324
+
325
+ ### Troubleshooting
326
+
327
+ ##### ❌ `options.catalox is required`
328
+
329
+ Pass **`catalox`** into **`new ExellixSkillsClient({ catalox, ... })`**. Use **`createCataloxFromEnv()`** from this package, **`createCataloxFromEnv()`** from **`@x12i/catalox/firebase`**, or **`createCatalox({ firestore, firebaseApp })`** from **`@x12i/catalox`** / **`@x12i/catalox/embedder`**.
330
+
331
+ ##### ❌ Skill is `planned` or “incomplete templates”
332
+
333
+ Run **`npm run catalox:provision-ai-skills`** (or write bodies via **`updateSkillTemplatesFromPresentation`**) so **`instructionsText`** and **`promptText`** are non-empty and **`status`** is **`draft`** or **`published`**.
334
+
335
+ ##### ❌ Provider / env validation
336
+
337
+ At least one of **`OPENAI_API_KEY`**, **`GROK_API_KEY`**, **`OPEN_ROUTER_KEY`**, **`OPENROUTER_API_KEY`** is required when the client builds the default gateway. Firebase / Catalox variables are summarized [above](#environment-firebase--catalox-v4).
338
+
339
+ ### Logging (`ai-skills` / `@x12i/logxer`)
340
+
341
+ This package uses **[`@x12i/logxer`](https://www.npmjs.com/package/@x12i/logxer)** with a stable env prefix **`AI_SKILLS`**.
342
+
343
+ | Item | Detail |
344
+ |------|--------|
345
+ | **Canonical env var** | **`AI_SKILLS_LOGS_LEVEL`** |
346
+ | **Fallback** | **`AI_SKILLS_LOG_LEVEL`** is used only if **`AI_SKILLS_LOGS_LEVEL`** is unset |
347
+ | **Default** (both unset) | **`warn`** — not silent; you will see warnings and errors |
348
+ | **Silence this package** | Set **`AI_SKILLS_LOGS_LEVEL=off`** (or `none` / `silent`) |
349
+ | **More detail** | `info`, `debug`, or `verbose` (case-insensitive; see **@x12i/logxer** docs) |
350
+
351
+ Cross-cutting sinks (console, file, JSON, unified app config) are configured by the host app, not per this prefix — see the **@x12i/logxer** README.
352
+
353
+ **`.env`:** with default **`autoLoadDotenv`**, the client loads **`.env`** from the current working directory using **`loadDotenv`** from **`@x12i/env`**.
354
+
355
+ ```bash
356
+ # Examples
357
+ export AI_SKILLS_LOGS_LEVEL=info
358
+ export AI_SKILLS_LOGS_LEVEL=debug
359
+ export AI_SKILLS_LOGS_LEVEL=off
360
+ ```
361
+
362
+ `ExellixSkillsClient` passes **`packageName`** into the logger (default `"AI-SKILLS"`); that label appears in log metadata alongside the `[AI-SKILLS]` message prefix.
363
+
364
+ ## Testing (integration + templates)
365
+
366
+ This repo is **ESM** (`"type": "module"`). Use the npm scripts below (they run via **`tsx`** for `.ts` entrypoints).
367
+
368
+ **Default test gate (`npm test`):** **`npm run build`** then **`npm run test:integration`** (may append live catalog tests when env enables them). **`npm run test:ci`** runs **`npm run build`** and **`npm run test:unit`** (mocked gateway / no Firestore / no LLM).
369
+
370
+ ```bash
371
+ npm test # build + integration harness (see run.ts)
372
+ npm run test:ci # build + deterministic unit slice (CI-safe)
373
+ npm run test:unit # same unit slice as test:ci (without build)
374
+ npm run test:integration # full integration harness (run.ts)
375
+ npm run catalox:provision-ai-skills # merge .metadata/skills → Firestore (before live tests)
376
+ npm run catalox:provision-ai-engines # merge in-repo engine rows → native `ai-engines` (before engine catalog live test)
377
+ npm run live:professional-answer # smoke: professional-answer via Catalox + default gateway LLM
378
+ npm run test:real # extra real LLM scenarios; needs .env + valid Firebase credentials
379
+ ```
380
+
381
+ #### Live tests (opt-in, `test/run.ts`)
382
+
383
+ Some cases use **real Catalox** (Firestore-backed catalog) and the same integration slice can invoke **real LLM** calls when provider keys are set. They are **off by default** so CI does not require a provisioned Firebase project.
384
+
385
+ Set **`AI_SKILLS_LIVE_TESTS=1`** (or `true` / `yes` / `on`) before **`npm run test:integration`** to append the live cases registered in **`test/run.ts`**:
386
+
387
+ - **Catalox `ai-skills` list** — read-only Firestore list sanity check.
388
+ - **Catalox `ai-engines` list** — verifies **`npm run catalox:provision-ai-engines`** wrote the **`ai-gateway`** row (read-only).
389
+ - **Skill template presentation** — read-only Catalox + presentation helpers.
390
+
391
+ **Why not in `@x12i/catalox`:** only **this package’s** test harness reads **`AI_SKILLS_LIVE_TESTS`**; Catalox does not gate downstream CI.
392
+
393
+ Optionally run **`npm run test:real`** before a major release (broader LLM + client scenarios; needs Firebase + provider keys in **`.env`**).
394
+
395
+ **Flex-MD log noise (`extractJsonFromFlexMd` / `require(...) is not a function`):** that path lives in **`@x12i/ai-gateway`**, not in this package’s `parseFlexMd`. See **[docs/FLEX_MD_AND_TESTING.md](docs/FLEX_MD_AND_TESTING.md)** for the split of responsibilities and acceptance notes.
396
+
397
+ ## Model Configuration
398
+
399
+ You can control which model is used and how it generates responses on a per-request basis using the `modelConfig` field.
400
+
401
+ ### Model Configuration Options
402
+
403
+ ```typescript
404
+ interface ModelConfig {
405
+ /** Model identifier (e.g., "gpt-4-turbo", "claude-3-opus", "gpt-3.5-turbo") */
406
+ model?: string;
407
+
408
+ /** Model ID (alternative to model name, for provider-specific model IDs) */
409
+ modelId?: string;
410
+
411
+ /** Provider name (e.g., "openai", "anthropic") */
412
+ provider?: string;
413
+
414
+ /** Temperature for generation (0.0 to 2.0) - controls randomness */
415
+ temperature?: number;
416
+
417
+ /** Maximum tokens to generate */
418
+ maxTokens?: number;
419
+
420
+ /** Top-p (nucleus) sampling parameter (0.0 to 1.0) */
421
+ topP?: number;
422
+
423
+ /** Frequency penalty (-2.0 to 2.0) */
424
+ frequencyPenalty?: number;
425
+
426
+ /** Presence penalty (-2.0 to 2.0) */
427
+ presencePenalty?: number;
428
+
429
+ /** Stop sequences (array of strings) */
430
+ stop?: string[];
431
+
432
+ /** Additional provider-specific parameters */
433
+ [key: string]: any;
434
+ }
435
+ ```
436
+
437
+ ### Usage Examples
438
+
439
+ #### Basic Model Selection
440
+
441
+ ```typescript
442
+ const res = await skills.runSkill({
443
+ skillKey: "skills/professional-answer",
444
+ input: "Your question here",
445
+ modelConfig: {
446
+ model: "gpt-4-turbo"
447
+ }
448
+ });
449
+ ```
450
+
451
+ #### Full Model Configuration
452
+
453
+ ```typescript
454
+ const res = await skills.runSkill({
455
+ skillKey: "skills/professional-answer",
456
+ input: "Your question here",
457
+ modelConfig: {
458
+ model: "gpt-4-turbo",
459
+ temperature: 0.7,
460
+ maxTokens: 2000,
461
+ topP: 0.9,
462
+ frequencyPenalty: 0.5
463
+ }
464
+ });
465
+ ```
466
+
467
+ #### Provider Override
468
+
469
+ ```typescript
470
+ const res = await skills.runSkill({
471
+ skillKey: "skills/professional-answer",
472
+ input: "Your question here",
473
+ modelConfig: {
474
+ provider: "anthropic",
475
+ model: "claude-3-opus",
476
+ temperature: 0.3
477
+ }
478
+ });
479
+ ```
480
+
481
+ #### Dynamic `maxTokens` per invoke
482
+
483
+ ```typescript
484
+ function maxTokensForStep(step: "summary" | "detail") {
485
+ return step === "summary" ? 800 : 4000;
486
+ }
487
+
488
+ const res = await skills.runSkill({
489
+ skillKey: "skills/professional-answer",
490
+ input: "Your question here",
491
+ jobId: "job-1",
492
+ taskId: "task-1",
493
+ modelConfig: {
494
+ maxTokens: maxTokensForStep("detail"),
495
+ temperature: 0.5,
496
+ },
497
+ });
498
+ ```
499
+
500
+ ### Notes
501
+
502
+ - `modelConfig` is **optional**. If omitted, gateway/router defaults apply for each field.
503
+ - This package does **not** merge a global default `modelConfig` at construction time; only **`templateRendering`** has client-wide defaults for template rendering.
504
+ - The gateway and provider enforce valid ranges (e.g. temperature).
505
+
506
+ ## API Reference
507
+
508
+ ### `ExellixSkillsClient`
509
+
510
+ #### Constructor options (essential)
511
+
512
+ ```typescript
513
+ import type { Catalox } from "@x12i/catalox";
514
+ import type { TemplateRenderOptions } from "@x12i/ai-gateway";
515
+
516
+ interface ExellixSkillsClientOptions {
517
+ /** Required. Skill bodies are read from the native `ai-skills` catalog. */
518
+ catalox: Catalox;
519
+ /** Optional; defaults to catalog app id `ai-skills`. */
520
+ cataloxAppId?: string;
521
+
522
+ packageName?: string;
523
+ enableActivityTracking?: boolean;
524
+ /** Optional external gateway; skill templates still load from `catalox`. */
525
+ gateway?: AIGateway;
526
+
527
+ forceLocal?: boolean;
528
+ forceCreateRootDirectory?: boolean;
529
+ templateRendering?: TemplateRenderOptions;
530
+ autoLoadDotenv?: boolean;
531
+ testMode?: boolean;
532
+ disableActivityTrackingInTests?: boolean;
533
+ }
534
+ ```
535
+
536
+ The built-in **`AIGateway`** uses **`enableContentRegistry: false`**; **`GITHUB_*`** is **not** required for **`ExellixSkillsClient`** initialization.
537
+
538
+ #### Methods
539
+
540
+ - **`runSkill<T>(input: RunSkillRequest): Promise<RunSkillResponse<T>>`** — loads templates from Catalox, invokes gateway with inline bodies. Optional **`outputContract`** enriches **`parsed`** and the Activix activity row (see [Cost and output contract](#cost-and-output-contract-run-analysis)).
541
+ - **`getSkillTemplatesForPresentation(skillKey, opts?)`** — editor-oriented markdown + metadata.
542
+ - **`updateSkillTemplatesFromPresentation(skillKey, patch, opts?)`** — save edited markdown (lossless storage normalization); requires Catalox **write** context.
543
+ - **`getSkillTemplateInputs(skillKey)`** — `{{token}}` discovery from stored bodies.
544
+ - **`listPublishedSkills(options?)`** — Catalox rows with **`status === "published"`**.
545
+ - **`listCatalogSkills(options?)`** — all catalog rows (any status); optional Catalox list query options.
546
+ - **`upsertSkillCatalogItem(input, options?)`** — create/update catalog metadata and template bodies; **`options.ifNotExists`** enforces create-only.
547
+ - **`softDeleteSkillCatalogItem(skillKey)`** — clears template and audit text and sets **`planned`**.
548
+
549
+ Template bodies are loaded **only** through **`runSkill`** (and the same Catalox fetch used there): pass **`skillKey`**; there is no separate “peek at one section string” API.
550
+
551
+ #### Diagnostic methods (gateway)
552
+
553
+ - `testContentRegistryConnection()`, `discoverContentStructure()`, `diagnoseSkillContent`, `listAvailableContent`, `runContentRegistryDiagnostics` — forwarded to **AIGateway**. With the default gateway, **nx-content is disabled**; these calls are mainly useful if you inject a **custom** `gateway` with its own content registry.
554
+
555
+ For **`{{token}}`** discovery on stored bodies without invoking the LLM, use **`getSkillTemplateInputs(skillKey)`** (Catalox read + parse).
556
+
557
+ ## FlexMD 2.0 Format
558
+
559
+ Primary skills are designed around **structured markdown** (headings and/or Flex-MD style markers). A common explicit shape is:
560
+
561
+ ```
562
+ [[professional-answer]]
563
+
564
+ @payload:shortAnswer
565
+ Brief answer here...
566
+
567
+ @payload:fullAnswer
568
+ Detailed answer here...
569
+
570
+ @payload:assumptions
571
+ List of assumptions...
572
+ ```
573
+
574
+ The gateway normalizes responses with its own Flex-MD extraction. When it does not return parsed fields, this SDK uses **`parseFlexMd`** (`src/utils/flex-md-parser.ts`), which prefers the **`flex-md`** package via ESM `import()` and falls back to parsing `[[frame:...]]` + `@payload:` blocks.
575
+
576
+ When you pass **`outputContract`**, the SDK also maps markdown `### Section` headings to camelCase keys (e.g. `### Full Answer` → `fullAnswer`) so professional-answer templates that use headings still populate **`parsed`** even without `@payload:` markers. See **[Cost and output contract](#cost-and-output-contract-run-analysis)**.
577
+
578
+ Details and caveats: **[docs/FLEX_MD_AND_TESTING.md](docs/FLEX_MD_AND_TESTING.md)**.
579
+
580
+ ## Requirements
581
+
582
+ - **Node.js 20+** (see `package.json` **`engines`**; aligns with **`@x12i/catalox`**)
583
+ - **`@x12i/catalox`** **≥ 4** and **Firebase** access for the **`ai-skills`** native catalog (see **`createCataloxFromEnv`** and [Environment (Firebase & Catalox v4)](#environment-firebase--catalox-v4))
584
+ - **`@x12i/ai-gateway`** **^9.5.2** (see `package.json`; shares **`@x12i/activix` ^8** with this package)
585
+ - **`@x12i/activix`** **^8.0.0** (direct dependency for activity-row enrichment types/patches)
586
+ - At least one LLM provider key when using the default constructed gateway
587
+ - Optional: **`.metadata/skills`** on disk to feed **`npm run catalox:provision-ai-skills`**
588
+ ## Related runtime packages
589
+
590
+ Task and graph orchestration live in sibling packages, not in **`@exellix/ai-skills`**:
591
+
592
+ - **`@exellix/ai-tasks`** — task orchestration and task-level runtime wiring
593
+ - **`@exellix/graph-engine`** — graph execution and graph-level runtime wiring; forward each node’s **`inputs.outputContract`** on **`runSkill`** when Run Analysis or downstream validation require structured **`parsed`** fields
594
+ - **`@exellix/exellix-runtime`** — root runtime composition (for example loading composed `runtimeObjects` for debug tooling)
595
+
596
+ Older scopes such as **`@woroces/*`**, **`worox`**, **`worex`**, and graph packages named **`worox-graph`** / **`worex-graphs`** are not used here; there are **no** intentional aliases or compatibility shims for those names in this repository.
597
+
598
+ ## License
599
+
600
+ ISC