@jterrazz/intelligence 4.2.0 → 6.0.0
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 +233 -217
- package/dist/formatting.cjs +2845 -0
- package/dist/formatting.cjs.map +1 -0
- package/dist/formatting.d.cts +105 -0
- package/dist/formatting.d.ts +105 -0
- package/dist/formatting.js +2819 -0
- package/dist/formatting.js.map +1 -0
- package/dist/index.cjs +683 -476
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +183 -1396
- package/dist/index.d.ts +183 -1396
- package/dist/index.js +680 -459
- package/dist/index.js.map +1 -1
- package/dist/oxlint.cjs +629 -0
- package/dist/oxlint.cjs.map +1 -0
- package/dist/oxlint.d.cts +132 -0
- package/dist/oxlint.d.ts +132 -0
- package/dist/oxlint.js +623 -0
- package/dist/oxlint.js.map +1 -0
- package/package.json +28 -22
- package/dist/parse-text.cjs +0 -57
- package/dist/parse-text.cjs.map +0 -1
- package/dist/parse-text.d.cts +0 -18
- package/dist/parse-text.d.ts +0 -18
- package/dist/parse-text.js +0 -52
- package/dist/parse-text.js.map +0 -1
- package/dist/text.cjs +0 -3
- package/dist/text.d.cts +0 -2
- package/dist/text.d.ts +0 -2
- package/dist/text.js +0 -2
package/README.md
CHANGED
|
@@ -1,115 +1,150 @@
|
|
|
1
1
|
# @jterrazz/intelligence
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A thin composition layer over [AI SDK v7](https://ai-sdk.dev) — provider factories, cost/fallback/logging middleware, and a config-driven factory that wires them together. Observability goes through OpenTelemetry: the host app registers an OTel Node SDK (e.g. via [`@jterrazz/telemetry`](https://www.npmjs.com/package/@jterrazz/telemetry)), and this package emits AI SDK spans and a `gen_ai.usage.cost` attribute into it.
|
|
4
|
+
|
|
5
|
+
The public surface is AI SDK's own `LanguageModel` type — every factory here returns a model you can pass straight into `generateText`, `streamText`, `generateObject`, etc.
|
|
4
6
|
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
|
-
npm install @jterrazz/intelligence ai
|
|
10
|
+
npm install @jterrazz/intelligence ai
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
### `generateStructured` - Type-safe structured generation with error handling
|
|
13
|
+
`ai` (^7.0.0) is a peer dependency — bring your own version.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
## Quick start
|
|
16
16
|
|
|
17
17
|
```typescript
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
const schema = z.object({
|
|
22
|
-
sentiment: z.string(),
|
|
23
|
-
score: z.number(),
|
|
24
|
-
});
|
|
18
|
+
import { createIntelligence } from '@jterrazz/intelligence';
|
|
19
|
+
import { generateText } from 'ai';
|
|
25
20
|
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
const intelligence = createIntelligence({
|
|
22
|
+
providers: {
|
|
23
|
+
openrouter: {
|
|
24
|
+
type: 'openrouter',
|
|
25
|
+
apiKey: process.env.OPENROUTER_API_KEY,
|
|
26
|
+
metadata: { application: 'my-app', website: 'https://example.com' },
|
|
27
|
+
},
|
|
28
|
+
localProxy: {
|
|
29
|
+
type: 'gateway',
|
|
30
|
+
baseURL: 'https://my-gateway.example.com/v1',
|
|
31
|
+
apiKey: process.env.PROXY_API_KEY,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
agents: {
|
|
35
|
+
summarizer: {
|
|
36
|
+
provider: 'openrouter',
|
|
37
|
+
model: 'google/gemini-2.5-flash-lite',
|
|
38
|
+
fallback: { provider: 'openrouter', model: 'openai/gpt-4o-mini' },
|
|
39
|
+
},
|
|
40
|
+
localAgent: {
|
|
41
|
+
provider: 'localProxy',
|
|
42
|
+
model: 'some-local-model',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
pricing: {
|
|
46
|
+
// USD per million tokens — used only when the provider doesn't report actual cost
|
|
47
|
+
'openrouter/openai/gpt-4o-mini': { input: 0.15, output: 0.6 },
|
|
48
|
+
},
|
|
49
|
+
logger, // LoggerPort from @jterrazz/telemetry, optional
|
|
31
50
|
});
|
|
32
51
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
} else {
|
|
36
|
-
// Typed error with code: TIMEOUT | RATE_LIMITED | PARSING_FAILED | etc.
|
|
37
|
-
console.error(result.error.code, result.error.message);
|
|
38
|
-
}
|
|
52
|
+
const model = intelligence.model('summarizer');
|
|
53
|
+
const { text } = await generateText({ model, prompt: 'Summarize this article...' });
|
|
39
54
|
```
|
|
40
55
|
|
|
41
|
-
|
|
56
|
+
Each agent has a `provider` (a key into `providers`) and a `model` (the technical model id, passed through to the provider as-is — e.g. `'anthropic/claude-sonnet-4'` for a model id that itself contains a `/`). `pricing` is keyed by `"<provider>/<model>"`, joining those same two fields.
|
|
42
57
|
|
|
43
|
-
|
|
58
|
+
`intelligence.model(agentName)` builds each model lazily and caches it — calling it twice for the same agent returns the same instance.
|
|
59
|
+
|
|
60
|
+
## What `createIntelligence` wires up
|
|
61
|
+
|
|
62
|
+
For each resolved model reference:
|
|
63
|
+
|
|
64
|
+
1. The provider's base model (via `createOpenRouterProvider` or `createGatewayProvider`).
|
|
65
|
+
2. `createCostMiddleware` — records the generation's USD cost on the active OpenTelemetry span.
|
|
66
|
+
3. If the agent has a `fallback`, `createFallbackModel` wraps primary + fallback with automatic retry-on-failure.
|
|
67
|
+
4. If a `logger` is configured, `createLoggingMiddleware` wraps the whole thing.
|
|
68
|
+
|
|
69
|
+
On first use, `createIntelligence` registers the AI SDK's OpenTelemetry integration (`@ai-sdk/otel`) globally. This is idempotent and best-effort — if the host app hasn't set up an OpenTelemetry SDK, this is a no-op rather than an error.
|
|
70
|
+
|
|
71
|
+
## Building blocks
|
|
72
|
+
|
|
73
|
+
Each piece is also exported individually if you want to compose things yourself instead of using `createIntelligence`.
|
|
74
|
+
|
|
75
|
+
### Providers
|
|
44
76
|
|
|
45
77
|
```typescript
|
|
46
|
-
import {
|
|
47
|
-
generationSuccess,
|
|
48
|
-
generationFailure,
|
|
49
|
-
isSuccess,
|
|
50
|
-
isFailure,
|
|
51
|
-
unwrap,
|
|
52
|
-
unwrapOr,
|
|
53
|
-
classifyError,
|
|
54
|
-
type GenerationResult,
|
|
55
|
-
} from '@jterrazz/intelligence';
|
|
56
|
-
|
|
57
|
-
// Create results
|
|
58
|
-
const success = generationSuccess({ data: 'value' });
|
|
59
|
-
const failure = generationFailure('TIMEOUT', 'Request timed out');
|
|
60
|
-
|
|
61
|
-
// Type guards
|
|
62
|
-
if (isSuccess(result)) {
|
|
63
|
-
console.log(result.data);
|
|
64
|
-
}
|
|
78
|
+
import { createOpenRouterProvider, createGatewayProvider } from '@jterrazz/intelligence';
|
|
65
79
|
|
|
66
|
-
|
|
67
|
-
|
|
80
|
+
const openrouter = createOpenRouterProvider({
|
|
81
|
+
apiKey: process.env.OPENROUTER_API_KEY,
|
|
82
|
+
metadata: { application: 'my-app', website: 'https://example.com' },
|
|
83
|
+
});
|
|
84
|
+
const model = openrouter.model('anthropic/claude-sonnet-4-20250514');
|
|
68
85
|
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
86
|
+
// Per-call options (reasoning effort, max tokens, ...) now go through providerOptions
|
|
87
|
+
// at the call site instead of the provider factory:
|
|
88
|
+
await generateText({
|
|
89
|
+
model,
|
|
90
|
+
prompt: 'Hello!',
|
|
91
|
+
providerOptions: { openrouter: { reasoning: { effort: 'high' } } },
|
|
92
|
+
});
|
|
75
93
|
```
|
|
76
94
|
|
|
77
|
-
|
|
95
|
+
```typescript
|
|
96
|
+
const proxy = createGatewayProvider({
|
|
97
|
+
baseURL: 'https://my-gateway.example.com/v1',
|
|
98
|
+
apiKey: process.env.PROXY_API_KEY,
|
|
99
|
+
});
|
|
100
|
+
const model = proxy.model('some-model-id');
|
|
101
|
+
```
|
|
78
102
|
|
|
79
|
-
|
|
103
|
+
`createGatewayProvider` targets the chat completions endpoint (`.chat()`, not the Responses API) for maximum compatibility with gateways exposing any API implementing the OpenAI chat completions spec, and wraps every model with AI SDK's `extractJsonMiddleware` — a safety net that strips markdown code fences from responses. This matters for gateways that sometimes wrap JSON output in ` ```json ` fences even when structured output was requested.
|
|
80
104
|
|
|
81
|
-
###
|
|
105
|
+
### Cost middleware
|
|
82
106
|
|
|
83
107
|
```typescript
|
|
84
108
|
import { wrapLanguageModel } from 'ai';
|
|
85
|
-
import {
|
|
86
|
-
createLoggingMiddleware,
|
|
87
|
-
createObservabilityMiddleware,
|
|
88
|
-
LangfuseAdapter,
|
|
89
|
-
OpenRouterMetadataAdapter,
|
|
90
|
-
} from '@jterrazz/intelligence';
|
|
109
|
+
import { createCostMiddleware } from '@jterrazz/intelligence';
|
|
91
110
|
|
|
92
111
|
const model = wrapLanguageModel({
|
|
93
|
-
model: provider.model('
|
|
112
|
+
model: provider.model('google/gemini-2.5-flash-lite'),
|
|
94
113
|
middleware: [
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
publicKey: process.env.LANGFUSE_PUBLIC_KEY,
|
|
100
|
-
}),
|
|
101
|
-
providerMetadata: new OpenRouterMetadataAdapter(),
|
|
114
|
+
createCostMiddleware({
|
|
115
|
+
modelRef: 'openrouter/google/gemini-2.5-flash-lite',
|
|
116
|
+
// Only used as a fallback when the provider doesn't report actual cost
|
|
117
|
+
pricing: { input: 0.1, output: 0.4 }, // USD per million tokens
|
|
102
118
|
}),
|
|
103
119
|
],
|
|
104
120
|
});
|
|
105
121
|
```
|
|
106
122
|
|
|
107
|
-
|
|
123
|
+
Cost resolution order:
|
|
124
|
+
|
|
125
|
+
1. Actual cost reported by the provider (currently: OpenRouter's `providerMetadata.openrouter.usage.cost`), when present and greater than zero.
|
|
126
|
+
2. Otherwise, an estimate from `pricing` and the reported token usage.
|
|
127
|
+
|
|
128
|
+
When a cost is determined, it's set as the `gen_ai.usage.cost` attribute on `trace.getActiveSpan()`. This is the attribute Langfuse's OpenTelemetry ingestion prioritizes over its own cost inference — `langfuse.observation.cost_details` is buggy on ingestion, so this package deliberately avoids it. All enrichment is best-effort: it never throws, even with no active span or a broken telemetry backend.
|
|
129
|
+
|
|
130
|
+
### Fallback model
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import { createFallbackModel } from '@jterrazz/intelligence';
|
|
134
|
+
|
|
135
|
+
const model = createFallbackModel({
|
|
136
|
+
primary: provider.model('anthropic/claude-sonnet-4'),
|
|
137
|
+
fallback: provider.model('openai/gpt-4o-mini'),
|
|
138
|
+
logger, // optional — logs 'ai.fallback.triggered' when the switch happens
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`createFallbackModel` returns a model (implementing `LanguageModelV4`), not a middleware — a middleware can't swap the underlying model. It retries on the fallback only for retryable errors: HTTP 429, 5xx, and network errors (connection refused/reset, timeouts). Non-retryable errors (400s, validation errors, aborts) propagate unchanged.
|
|
108
143
|
|
|
109
|
-
|
|
144
|
+
### Logging middleware
|
|
110
145
|
|
|
111
146
|
```typescript
|
|
112
|
-
import { wrapLanguageModel
|
|
147
|
+
import { wrapLanguageModel } from 'ai';
|
|
113
148
|
import { createLoggingMiddleware } from '@jterrazz/intelligence';
|
|
114
149
|
|
|
115
150
|
const model = wrapLanguageModel({
|
|
@@ -123,194 +158,175 @@ const model = wrapLanguageModel({
|
|
|
123
158
|
},
|
|
124
159
|
}),
|
|
125
160
|
});
|
|
126
|
-
|
|
127
|
-
await generateText({ model, prompt: 'Hello!' });
|
|
128
|
-
// Logs: ai.generate.start, ai.generate.complete (with durationMs, usage, etc.)
|
|
129
161
|
```
|
|
130
162
|
|
|
131
|
-
|
|
163
|
+
Logs `ai.generate.start` / `ai.generate.complete` / `ai.generate.error` (and the `ai.stream.*` equivalents) with timing and usage.
|
|
132
164
|
|
|
133
|
-
|
|
165
|
+
### `cleanAiText` / `toSentenceCase` — text formatting utilities
|
|
166
|
+
|
|
167
|
+
Dependency-free — import them from `@jterrazz/intelligence/formatting` to avoid installing `ai`.
|
|
134
168
|
|
|
135
169
|
```typescript
|
|
136
|
-
import {
|
|
137
|
-
import {
|
|
138
|
-
createObservabilityMiddleware,
|
|
139
|
-
withObservability,
|
|
140
|
-
LangfuseAdapter,
|
|
141
|
-
} from '@jterrazz/intelligence';
|
|
142
|
-
|
|
143
|
-
const observability = new LangfuseAdapter({
|
|
144
|
-
secretKey: process.env.LANGFUSE_SECRET_KEY,
|
|
145
|
-
publicKey: process.env.LANGFUSE_PUBLIC_KEY,
|
|
146
|
-
});
|
|
170
|
+
import { cleanAiText, toSentenceCase } from '@jterrazz/intelligence/formatting';
|
|
147
171
|
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
});
|
|
172
|
+
const clean = cleanAiText(messyAiOutput);
|
|
173
|
+
// Removes: BOM, zero-width chars, citation markers
|
|
174
|
+
// Normalizes: smart quotes, em dashes, ellipsis
|
|
152
175
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
model,
|
|
156
|
-
prompt: 'Analyze this...',
|
|
157
|
-
providerOptions: withObservability({
|
|
158
|
-
traceId: 'trace-123',
|
|
159
|
-
name: 'analyzer',
|
|
160
|
-
metadata: { userId: 'user-1' },
|
|
161
|
-
}),
|
|
162
|
-
});
|
|
176
|
+
const headline = toSentenceCase('Your Next AI Skill Is Worldbuilding');
|
|
177
|
+
// -> "Your next AI skill is worldbuilding"
|
|
163
178
|
```
|
|
164
179
|
|
|
165
|
-
|
|
180
|
+
## Agent & prompt conventions
|
|
166
181
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
```typescript
|
|
170
|
-
import type { ObservabilityPort, ProviderMetadataPort } from "@jterrazz/intelligence";
|
|
171
|
-
|
|
172
|
-
// Observability adapter (Datadog, etc.)
|
|
173
|
-
class DatadogAdapter implements ObservabilityPort {
|
|
174
|
-
trace(params) { /* ... */ }
|
|
175
|
-
generation(params) { /* ... */ }
|
|
176
|
-
async flush() { /* ... */ }
|
|
177
|
-
async shutdown() { /* ... */ }
|
|
178
|
-
}
|
|
182
|
+
`@jterrazz/intelligence` gives you the `LanguageModel`; how you organize the agents that call it is up to the host repo, but here's the folder-per-agent convention we've converged on across `@jterrazz` projects:
|
|
179
183
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
184
|
+
```
|
|
185
|
+
agents/
|
|
186
|
+
article-composer/
|
|
187
|
+
article-composer.ts # the agent class — canonical, framework-visible
|
|
188
|
+
article-composer.prompt.ts # the prompt, and only the prompt
|
|
189
|
+
event-assigner/
|
|
190
|
+
event-assigner.ts
|
|
191
|
+
event-assigner.prompt.ts
|
|
192
|
+
_shared/
|
|
193
|
+
category-taxonomy.prompt.ts # sections shared across agents
|
|
186
194
|
```
|
|
187
195
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
### `parseObject` - Extract structured data from AI responses
|
|
191
|
-
|
|
192
|
-
Extracts and validates JSON from messy AI outputs (markdown blocks, malformed syntax).
|
|
193
|
-
|
|
194
|
-
````typescript
|
|
195
|
-
import { parseObject } from '@jterrazz/intelligence';
|
|
196
|
-
import { z } from 'zod';
|
|
197
|
-
|
|
198
|
-
const schema = z.object({
|
|
199
|
-
title: z.string(),
|
|
200
|
-
tags: z.array(z.string()),
|
|
201
|
-
});
|
|
196
|
+
Large agents can split their prompt across several `<name>-<section>.prompt.ts` files instead of one.
|
|
202
197
|
|
|
203
|
-
|
|
204
|
-
const result = parseObject(text, schema);
|
|
205
|
-
// { title: "Hello", tags: ["ai"] }
|
|
206
|
-
````
|
|
198
|
+
The hard rule: **no multi-line natural-language literal outside `*.prompt.ts`.** The class only shapes data — filter, sort, map to flat records, `JSON.stringify` — and hands the builder plain values, never assembled prose. `generateText`/`Output.object` stay visible in `run()`, unabstracted:
|
|
207
199
|
|
|
208
|
-
|
|
200
|
+
```typescript
|
|
201
|
+
export class ArticleComposer implements ArticleComposerPort {
|
|
202
|
+
static readonly SCHEMA = z.object({/* ... */});
|
|
203
|
+
|
|
204
|
+
constructor(
|
|
205
|
+
private readonly model: LanguageModel,
|
|
206
|
+
private readonly logger: LoggerPort,
|
|
207
|
+
) {}
|
|
208
|
+
|
|
209
|
+
async run(input: ArticleCompositionInput): Promise<ArticleCompositionResult> {
|
|
210
|
+
const { output } = await generateText({
|
|
211
|
+
model: this.model,
|
|
212
|
+
output: Output.object({ schema: ArticleComposer.SCHEMA }),
|
|
213
|
+
prompt: buildPrompt({
|
|
214
|
+
angle: input.angle,
|
|
215
|
+
factsJson: ArticleComposer.buildFactsJson(input.facts),
|
|
216
|
+
history: ArticleComposer.buildHistoryRecords(input.previousArticles),
|
|
217
|
+
}),
|
|
218
|
+
});
|
|
219
|
+
return output;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Data prep only — map/sort/JSON.stringify/flatten to records. No prose.
|
|
223
|
+
private static buildFactsJson(facts: Fact[]): string {
|
|
224
|
+
/* ... */
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private static buildHistoryRecords(articles?: PreviousArticle[]): HistoryArticle[] {
|
|
228
|
+
/* sort chronologically, map to { angle, body, date, headline }[] */
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
209
232
|
|
|
210
|
-
|
|
233
|
+
The sibling `*.prompt.ts` holds all the prose. It's not limited to one `buildPrompt` — it can export several builders, a main one plus section builders it calls internally, each owning its own empty-case handling:
|
|
211
234
|
|
|
212
235
|
```typescript
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
236
|
+
// article-composer.prompt.ts
|
|
237
|
+
interface HistoryArticle {
|
|
238
|
+
angle: string;
|
|
239
|
+
body: string;
|
|
240
|
+
date: string;
|
|
241
|
+
headline: string;
|
|
242
|
+
}
|
|
216
243
|
|
|
217
|
-
|
|
244
|
+
interface Variables {
|
|
245
|
+
angle: string;
|
|
246
|
+
factsJson: string;
|
|
247
|
+
history: HistoryArticle[];
|
|
248
|
+
}
|
|
218
249
|
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
});
|
|
250
|
+
export const buildHistorySection = (articles: HistoryArticle[]): string => {
|
|
251
|
+
if (articles.length === 0) {
|
|
252
|
+
return '';
|
|
253
|
+
}
|
|
224
254
|
|
|
225
|
-
|
|
226
|
-
|
|
255
|
+
return `\n\n## Previously Published Articles\n\n${articles
|
|
256
|
+
.map((a) => `### ${a.headline}\n${a.body}`)
|
|
257
|
+
.join('\n\n')}`;
|
|
258
|
+
};
|
|
227
259
|
|
|
228
|
-
|
|
260
|
+
export const buildPrompt = (v: Variables): string => {
|
|
261
|
+
return `# Article Composition
|
|
229
262
|
|
|
230
|
-
|
|
263
|
+
Your angle for this article: **${v.angle}**.
|
|
231
264
|
|
|
232
|
-
|
|
233
|
-
import { parseText } from '@jterrazz/intelligence';
|
|
265
|
+
## Facts
|
|
234
266
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
// Normalizes: smart quotes, em dashes, ellipsis
|
|
267
|
+
${v.factsJson}${buildHistorySection(v.history)}`;
|
|
268
|
+
};
|
|
238
269
|
```
|
|
239
270
|
|
|
240
|
-
|
|
271
|
+
Sections reused across agents (a category taxonomy needed by both an assigner and an ingester, say) live in `_shared/<name>.prompt.ts` as `export const <name> = (): string => { ... }` and get imported wherever they're needed — never copy-pasted between prompt files.
|
|
241
272
|
|
|
242
|
-
|
|
273
|
+
Why: this is the shape most reference TypeScript AI codebases converge on, and it gives the prompt↔variables contract native TypeScript typing instead of loose string interpolation.
|
|
243
274
|
|
|
244
|
-
|
|
245
|
-
import { generateText } from 'ai';
|
|
246
|
-
import { createOpenRouterProvider } from '@jterrazz/intelligence';
|
|
275
|
+
### Enforcing it — `@jterrazz/intelligence/oxlint`
|
|
247
276
|
|
|
248
|
-
|
|
249
|
-
apiKey: process.env.OPENROUTER_API_KEY,
|
|
250
|
-
});
|
|
277
|
+
These conventions are mechanized as an oxlint plugin, shaped like `@jterrazz/test`'s own `oxlint` export: a `LintPlugin` of `intelligence/*` rules, a manifest of `RULE_DOCS`, and a composable `intelligence` fragment.
|
|
251
278
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
});
|
|
279
|
+
```typescript
|
|
280
|
+
import { compose, node } from '@jterrazz/typescript/oxlint';
|
|
281
|
+
import { intelligence } from '@jterrazz/intelligence/oxlint';
|
|
256
282
|
|
|
257
|
-
|
|
258
|
-
const reasoningModel = provider.model('anthropic/claude-sonnet-4-20250514', {
|
|
259
|
-
maxTokens: 16000,
|
|
260
|
-
reasoning: { effort: 'high' },
|
|
261
|
-
});
|
|
283
|
+
export default compose(node, intelligence);
|
|
262
284
|
```
|
|
263
285
|
|
|
264
|
-
|
|
286
|
+
`compose()` (from `@jterrazz/typescript/oxlint`) merges any number of fragments — add `@jterrazz/test`'s `testing` fragment, `hexagonal`, or your own overrides the same way: `compose(node, hexagonal, testing, intelligence, { rules: {...} })`.
|
|
265
287
|
|
|
266
|
-
|
|
288
|
+
| Rule | Severity | Enforces |
|
|
289
|
+
| ----------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
290
|
+
| `intelligence/p1-prose-in-prompt-files` | error | No multi-line natural-language template literal outside `*.prompt.ts` — the flagship rule for this whole convention. |
|
|
291
|
+
| `intelligence/p2-prompt-file-exports` | error | A `*.prompt.ts` file exports only const string-builder functions and types/interfaces. |
|
|
292
|
+
| `intelligence/p3-agent-prompt-sibling` | error | An agent file imports `./<name>.prompt.js`; a non-`_shared/` prompt file has its `<name>.ts` sibling on disk. |
|
|
293
|
+
| `intelligence/g1-agent-class-shape` | error | An agent file exports exactly one class with `static readonly SCHEMA`, a `run` method, and `constructor(model, ...)`. |
|
|
294
|
+
| `intelligence/m1-model-resolution-in-container` | error | `createIntelligence`/`createGatewayProvider`/`createOpenRouterProvider` and `.model('…')` resolution are DI/container-only. |
|
|
295
|
+
| `intelligence/m2w-no-hardcoded-model-id` | warning | A string literal shaped like a model id outside config/test/fixture files — model ids belong in configuration. |
|
|
267
296
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
297
|
+
Each rule is deliberately best-effort where full static verification isn't possible (documented per rule via `meta.docs` / the manifest) — the goal is catching the common slip, not a type checker.
|
|
298
|
+
|
|
299
|
+
## API Reference
|
|
271
300
|
|
|
272
|
-
###
|
|
301
|
+
### Factory
|
|
273
302
|
|
|
274
|
-
| Export
|
|
275
|
-
|
|
|
276
|
-
| `
|
|
277
|
-
| `generationSuccess(data)` | Create success result |
|
|
278
|
-
| `generationFailure(code, message, cause?)` | Create failure result |
|
|
279
|
-
| `isSuccess(result)` | Type guard for success |
|
|
280
|
-
| `isFailure(result)` | Type guard for failure |
|
|
281
|
-
| `unwrap(result)` | Extract data or throw |
|
|
282
|
-
| `unwrapOr(result, default)` | Extract data or return default |
|
|
283
|
-
| `classifyError(error)` | Classify error into error code |
|
|
284
|
-
| `GenerationErrorCode` | Error codes: TIMEOUT, RATE_LIMITED, PARSING_FAILED, etc. |
|
|
303
|
+
| Export | Description |
|
|
304
|
+
| ---------------------------- | ------------------------------------------------------------ |
|
|
305
|
+
| `createIntelligence(config)` | Config-driven factory: agents → fully wired `LanguageModel`s |
|
|
285
306
|
|
|
286
307
|
### Middleware
|
|
287
308
|
|
|
288
|
-
| Export
|
|
289
|
-
|
|
|
290
|
-
| `
|
|
291
|
-
| `
|
|
292
|
-
| `withObservability(meta)` | Helper for type-safe observability metadata |
|
|
293
|
-
| `LangfuseAdapter` | Langfuse implementation of ObservabilityPort |
|
|
294
|
-
| `NoopObservabilityAdapter` | No-op adapter for testing/development |
|
|
295
|
-
| `OpenRouterMetadataAdapter` | Extract usage/cost from OpenRouter |
|
|
309
|
+
| Export | Description |
|
|
310
|
+
| ---------------------------------- | --------------------------------------------- |
|
|
311
|
+
| `createCostMiddleware(options)` | Records USD cost on the active OTel span |
|
|
312
|
+
| `createLoggingMiddleware(options)` | Logs requests/responses with timing and usage |
|
|
296
313
|
|
|
297
|
-
###
|
|
314
|
+
### Model
|
|
298
315
|
|
|
299
|
-
| Export
|
|
300
|
-
|
|
|
301
|
-
| `
|
|
302
|
-
| `ProviderMetadataPort` | Interface for provider metadata extraction |
|
|
316
|
+
| Export | Description |
|
|
317
|
+
| ------------------------------ | -------------------------------------------------- |
|
|
318
|
+
| `createFallbackModel(options)` | A `LanguageModel` that retries on a fallback model |
|
|
303
319
|
|
|
304
|
-
###
|
|
320
|
+
### Providers
|
|
305
321
|
|
|
306
|
-
| Export
|
|
307
|
-
|
|
|
308
|
-
| `
|
|
309
|
-
| `
|
|
310
|
-
| `parseText(text, options?)` | Sanitize AI-generated text |
|
|
322
|
+
| Export | Description |
|
|
323
|
+
| ---------------------------------- | -------------------------------------------------------- |
|
|
324
|
+
| `createOpenRouterProvider(config)` | OpenRouter provider for AI SDK |
|
|
325
|
+
| `createGatewayProvider(config)` | Provider for any gateway exposing a chat-completions API |
|
|
311
326
|
|
|
312
|
-
###
|
|
327
|
+
### Formatting
|
|
313
328
|
|
|
314
|
-
| Export
|
|
315
|
-
|
|
|
316
|
-
| `
|
|
329
|
+
| Export | Description |
|
|
330
|
+
| -------------------------------- | -------------------------------------------------------------------------- |
|
|
331
|
+
| `cleanAiText(text, options?)` | Sanitize AI-generated text (also at `/formatting`) |
|
|
332
|
+
| `toSentenceCase(text, options?)` | Normalize Title Case overuse back to sentence case (also at `/formatting`) |
|