decisionmemos 1.1.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/CHANGELOG.md +62 -0
- package/LICENSE +21 -0
- package/README.md +191 -0
- package/dist/src/clients/anthropic-client.d.ts +16 -0
- package/dist/src/clients/anthropic-client.d.ts.map +1 -0
- package/dist/src/clients/anthropic-client.js +67 -0
- package/dist/src/clients/anthropic-client.js.map +1 -0
- package/dist/src/clients/google-client.d.ts +16 -0
- package/dist/src/clients/google-client.d.ts.map +1 -0
- package/dist/src/clients/google-client.js +59 -0
- package/dist/src/clients/google-client.js.map +1 -0
- package/dist/src/clients/index.d.ts +10 -0
- package/dist/src/clients/index.d.ts.map +1 -0
- package/dist/src/clients/index.js +9 -0
- package/dist/src/clients/index.js.map +1 -0
- package/dist/src/clients/model-interface.d.ts +60 -0
- package/dist/src/clients/model-interface.d.ts.map +1 -0
- package/dist/src/clients/model-interface.js +109 -0
- package/dist/src/clients/model-interface.js.map +1 -0
- package/dist/src/clients/openai-client.d.ts +16 -0
- package/dist/src/clients/openai-client.d.ts.map +1 -0
- package/dist/src/clients/openai-client.js +82 -0
- package/dist/src/clients/openai-client.js.map +1 -0
- package/dist/src/clients/xai-client.d.ts +16 -0
- package/dist/src/clients/xai-client.d.ts.map +1 -0
- package/dist/src/clients/xai-client.js +71 -0
- package/dist/src/clients/xai-client.js.map +1 -0
- package/dist/src/index.d.ts +20 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +20 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/multi-model-query.d.ts +97 -0
- package/dist/src/multi-model-query.d.ts.map +1 -0
- package/dist/src/multi-model-query.js +121 -0
- package/dist/src/multi-model-query.js.map +1 -0
- package/dist/src/types.d.ts +184 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +5 -0
- package/dist/src/types.js.map +1 -0
- package/package.json +69 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to Decision Memos will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [1.1.0] — 2026-02-13
|
|
6
|
+
|
|
7
|
+
### SDK Monetisation Split
|
|
8
|
+
- **Free SDK**: New `MultiModelQuery` class and `createMultiModelQuery()` factory — query 4 models in parallel with typed responses, zero boilerplate
|
|
9
|
+
- **Paid API**: Synthesis, personas, briefing, consensus scoring, and Decision Memos are now exclusive to the hosted API
|
|
10
|
+
- Restructured public exports: free SDK ships only `MultiModelQuery`, model clients, and basic types
|
|
11
|
+
- Engine internals (`DecisionEngine`, `Synthesizer`, `BriefingGenerator`, personas) excluded from npm package
|
|
12
|
+
|
|
13
|
+
### Pricing Changes
|
|
14
|
+
- **Pro**: 40 memos/month (doubled from 20)
|
|
15
|
+
- **Team**: 200 memos/month (doubled from 100)
|
|
16
|
+
- **BYOK discount**: 20% off per provider key supplied (up to 80% with all 4 keys)
|
|
17
|
+
|
|
18
|
+
### Documentation
|
|
19
|
+
- Docs sidebar reorganised into Free SDK / Paid API / Guides sections
|
|
20
|
+
- New SDK reference documents `MultiModelQuery` API
|
|
21
|
+
- BYOK guide updated with discount tiers and cost comparison
|
|
22
|
+
- Quickstart rewritten for free SDK entry point with paid API upgrade path
|
|
23
|
+
- For Developers section now shows both free and paid code snippets
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## [1.0.0] — 2026-02-13
|
|
28
|
+
|
|
29
|
+
### Core SDK
|
|
30
|
+
- Multi-model parallel query engine (OpenAI, Anthropic, xAI, Google)
|
|
31
|
+
- Persona layer — advisor archetypes (Strategist, Analyst, Challenger, Architect)
|
|
32
|
+
- Transparent mode toggle — show real model names when needed
|
|
33
|
+
- Decision Memo schema — structured JSON output with typed interfaces
|
|
34
|
+
- Dynamic briefing generator — AI-powered contextual follow-up questions
|
|
35
|
+
- Consensus scoring — strong, moderate, weak agreement levels
|
|
36
|
+
- Custom persona support — override default archetypes
|
|
37
|
+
|
|
38
|
+
### Hosted API
|
|
39
|
+
- `POST /v1/deliberate` — Run panel deliberations via REST
|
|
40
|
+
- `POST /v1/briefing` — Generate dynamic follow-up questions
|
|
41
|
+
- `GET /v1/status` — Provider availability and usage stats
|
|
42
|
+
- `GET /health` — Public health check
|
|
43
|
+
- SSE streaming — real-time deliberation events (advisor.thinking, advisor.snippet, advisor.complete, synthesis.started, deliberation.complete)
|
|
44
|
+
- Bearer token authentication (dm_live_ / dm_test_ keys)
|
|
45
|
+
- Rate limiting — sliding window, per-plan limits
|
|
46
|
+
- Quota enforcement — monthly memo caps
|
|
47
|
+
- BYOK support — bring your own provider API keys per-request
|
|
48
|
+
|
|
49
|
+
### Web
|
|
50
|
+
- Landing page — hero, artifact showcase, how-it-works, advisor panel, developer section, pricing
|
|
51
|
+
- Documentation site — 14 pages covering SDK, API, guides
|
|
52
|
+
- Example gallery — real memo showcases
|
|
53
|
+
- SaaS application:
|
|
54
|
+
- Guided memo flow (question → briefing → live deliberation → memo)
|
|
55
|
+
- Decision library with search
|
|
56
|
+
- Memo viewer with tabbed interface (verdict, perspectives, consensus, risks, next steps)
|
|
57
|
+
- Settings — plan management, API keys, BYOK configuration, transparent mode
|
|
58
|
+
- Share and PDF export actions
|
|
59
|
+
|
|
60
|
+
### CLI
|
|
61
|
+
- `npm run consultation` — Run memos from markdown files
|
|
62
|
+
- JSON output with full comparison matrix
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Decision Memos
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# Decision Memos
|
|
2
|
+
|
|
3
|
+
**Four advisors. One structured verdict.**
|
|
4
|
+
|
|
5
|
+
Decision Memos has two tiers:
|
|
6
|
+
|
|
7
|
+
1. **Free SDK** — Query GPT, Claude, Grok, and Gemini in parallel with one function call. Typed responses, zero boilerplate.
|
|
8
|
+
2. **Paid API** — Adds persona-tuned prompts, synthesis with consensus scoring, dynamic briefing, and structured Decision Memos.
|
|
9
|
+
|
|
10
|
+
## Free SDK — Quick Start
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install decisionmemos
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { createMultiModelQuery } from 'decisionmemos';
|
|
18
|
+
|
|
19
|
+
const query = createMultiModelQuery();
|
|
20
|
+
|
|
21
|
+
const result = await query.ask(
|
|
22
|
+
'Should we migrate to microservices or adopt a modular monolith?'
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
for (const r of result.responses) {
|
|
26
|
+
console.log(`[${r.modelName}] ${r.response.slice(0, 200)}`);
|
|
27
|
+
}
|
|
28
|
+
// → 4 typed responses from 4 models, in parallel
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### API Keys
|
|
32
|
+
|
|
33
|
+
You need keys for the AI providers you want to use. The SDK works with any subset — missing providers are gracefully excluded.
|
|
34
|
+
|
|
35
|
+
| Provider | Env Variable |
|
|
36
|
+
|----------|-------------|
|
|
37
|
+
| OpenAI | `OPENAI_API_KEY` |
|
|
38
|
+
| Anthropic | `ANTHROPIC_API_KEY` |
|
|
39
|
+
| xAI | `XAI_API_KEY` |
|
|
40
|
+
| Google AI | `GOOGLE_API_KEY` |
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# .env
|
|
44
|
+
OPENAI_API_KEY=sk-...
|
|
45
|
+
ANTHROPIC_API_KEY=sk-ant-...
|
|
46
|
+
XAI_API_KEY=xai-...
|
|
47
|
+
GOOGLE_API_KEY=AIza...
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Paid API — Structured Verdicts
|
|
51
|
+
|
|
52
|
+
The hosted API adds the orchestration layer that turns four raw responses into one structured verdict:
|
|
53
|
+
|
|
54
|
+
| Feature | Free SDK | Paid API |
|
|
55
|
+
|---------|----------|----------|
|
|
56
|
+
| Parallel multi-model queries | Yes | Yes |
|
|
57
|
+
| Typed model responses | Yes | Yes |
|
|
58
|
+
| Advisor personas | - | Yes |
|
|
59
|
+
| Persona-tuned system prompts | - | Yes |
|
|
60
|
+
| Dynamic briefing (context intake) | - | Yes |
|
|
61
|
+
| Synthesis with consensus scoring | - | Yes |
|
|
62
|
+
| Structured Decision Memo | - | Yes |
|
|
63
|
+
| SSE streaming deliberation | - | Yes |
|
|
64
|
+
| BYOK discount (20% per key) | - | Yes |
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
curl -X POST https://api.decisionmemos.com/v1/deliberate \
|
|
68
|
+
-H "Authorization: Bearer dm_live_..." \
|
|
69
|
+
-H "Content-Type: application/json" \
|
|
70
|
+
-d '{
|
|
71
|
+
"question": "Should we migrate to microservices?",
|
|
72
|
+
"criteria": [
|
|
73
|
+
{ "name": "Team readiness" },
|
|
74
|
+
{ "name": "Timeline" }
|
|
75
|
+
],
|
|
76
|
+
"byok": {
|
|
77
|
+
"openai": "sk-..."
|
|
78
|
+
}
|
|
79
|
+
}'
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### BYOK Discount
|
|
83
|
+
|
|
84
|
+
Supply your own provider API keys and save:
|
|
85
|
+
- 1 key → 20% off
|
|
86
|
+
- 2 keys → 40% off
|
|
87
|
+
- 3 keys → 60% off
|
|
88
|
+
- 4 keys → 80% off
|
|
89
|
+
|
|
90
|
+
### Plans
|
|
91
|
+
|
|
92
|
+
| Plan | Memos |
|
|
93
|
+
|------|-------|
|
|
94
|
+
| Starter ($14/mo) | 5/month |
|
|
95
|
+
| Pro ($29/mo) | 40/month |
|
|
96
|
+
| Team ($99/mo) | 200/month |
|
|
97
|
+
| Enterprise | Unlimited |
|
|
98
|
+
|
|
99
|
+
## The Decision Memo
|
|
100
|
+
|
|
101
|
+
Every paid memo produces a structured artifact:
|
|
102
|
+
|
|
103
|
+
- **Verdict** — Synthesised recommendation with consensus scoring (strong / moderate / weak)
|
|
104
|
+
- **Perspectives** — Each advisor's individual analysis
|
|
105
|
+
- **Consensus map** — Where the panel agreed and diverged
|
|
106
|
+
- **Risks** — Identified risks with mitigations
|
|
107
|
+
- **Trade-offs** — Key tensions the decision involves
|
|
108
|
+
- **Next steps** — Prioritised implementation actions
|
|
109
|
+
|
|
110
|
+
### Advisors
|
|
111
|
+
|
|
112
|
+
| Advisor | Role | Strength |
|
|
113
|
+
|---------|------|----------|
|
|
114
|
+
| **The Strategist** | Big-picture thinking | Balanced, diplomatic, sees all angles |
|
|
115
|
+
| **The Analyst** | Risk & nuance | Identifies second-order consequences |
|
|
116
|
+
| **The Challenger** | Breaking groupthink | Challenges premises, calls out weak logic |
|
|
117
|
+
| **The Architect** | Structure & implementation | Turns ideas into frameworks with evidence |
|
|
118
|
+
|
|
119
|
+
## Brand & design system (source of truth)
|
|
120
|
+
|
|
121
|
+
If you’re making **any** design, UI, or copy decision, start here:
|
|
122
|
+
|
|
123
|
+
- `docs/BRAND.md`
|
|
124
|
+
|
|
125
|
+
## Project Structure
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
decisionmemos/
|
|
129
|
+
src/ # SDK source
|
|
130
|
+
index.ts # Public API (free tier exports)
|
|
131
|
+
multi-model-query.ts # MultiModelQuery class
|
|
132
|
+
types.ts # TypeScript interfaces
|
|
133
|
+
clients/ # AI provider clients
|
|
134
|
+
engine/ # Deliberation engine (paid API only)
|
|
135
|
+
personas.ts # Advisor archetypes (paid API only)
|
|
136
|
+
api/ # Hosted API server
|
|
137
|
+
src/
|
|
138
|
+
server.ts # Express server
|
|
139
|
+
auth.ts # API key authentication
|
|
140
|
+
metering.ts # Rate limits, quotas, BYOK discount
|
|
141
|
+
sse.ts # Streaming helpers
|
|
142
|
+
routes/ # API endpoints
|
|
143
|
+
web/ # Next.js landing + SaaS app
|
|
144
|
+
bin/ # CLI tools (internal)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Production (Vercel + Railway)
|
|
148
|
+
|
|
149
|
+
Before go-live, set spending limits and follow the **rate limiting & caching** checklist so bots and traffic spikes don’t cause surprise bills: **[docs/RATE-LIMITING-AND-CACHING.md](docs/RATE-LIMITING-AND-CACHING.md)**.
|
|
150
|
+
|
|
151
|
+
## Documentation
|
|
152
|
+
|
|
153
|
+
Full documentation at [decisionmemos.com/docs](https://decisionmemos.com/docs):
|
|
154
|
+
|
|
155
|
+
- [Quickstart](https://decisionmemos.com/docs/quickstart)
|
|
156
|
+
- [SDK Reference](https://decisionmemos.com/docs/sdk)
|
|
157
|
+
- [API Reference](https://decisionmemos.com/docs/api/deliberate)
|
|
158
|
+
- [BYOK & Pricing](https://decisionmemos.com/docs/guides/byok)
|
|
159
|
+
- [Streaming Guide](https://decisionmemos.com/docs/guides/streaming)
|
|
160
|
+
- [Concepts](https://decisionmemos.com/docs/concepts)
|
|
161
|
+
|
|
162
|
+
## Testing as a live user
|
|
163
|
+
|
|
164
|
+
You can run the full memo flow end-to-end locally:
|
|
165
|
+
|
|
166
|
+
1. **Start the API server** (needs provider API keys in `.env`):
|
|
167
|
+
```bash
|
|
168
|
+
cd api
|
|
169
|
+
npm install
|
|
170
|
+
npm run dev # listens on http://localhost:4000
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
2. **Set environment variables** for the web app:
|
|
174
|
+
```bash
|
|
175
|
+
# web/.env.local
|
|
176
|
+
NEXT_PUBLIC_API_URL=http://localhost:4000
|
|
177
|
+
NEXT_PUBLIC_DM_API_KEY=dm_test_dev_key_001
|
|
178
|
+
```
|
|
179
|
+
The dev test key (`dm_test_dev_key_001`) is seeded automatically in `api/src/auth.ts`.
|
|
180
|
+
|
|
181
|
+
3. **Start the web app**:
|
|
182
|
+
```bash
|
|
183
|
+
cd web
|
|
184
|
+
npm run dev # listens on http://localhost:3000
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
4. **Use it**: Go to [http://localhost:3000/app/new](http://localhost:3000/app/new), enter a question, answer the briefing, and watch a real deliberation. When `NEXT_PUBLIC_API_URL` is not set, the app falls back to mock data so the UI is always testable.
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
|
|
191
|
+
MIT — see [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic API Client for Claude
|
|
3
|
+
*/
|
|
4
|
+
import { BaseModelClient } from './model-interface.js';
|
|
5
|
+
import { ModelResponse } from '../types.js';
|
|
6
|
+
export declare class AnthropicClient extends BaseModelClient {
|
|
7
|
+
readonly name: string;
|
|
8
|
+
readonly provider = "anthropic";
|
|
9
|
+
private client;
|
|
10
|
+
private model;
|
|
11
|
+
private maxOutputTokens;
|
|
12
|
+
constructor(apiKey: string, model?: string, maxOutputTokens?: number);
|
|
13
|
+
query(prompt: string, systemPrompt?: string): Promise<ModelResponse>;
|
|
14
|
+
testConnection(): Promise<boolean>;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=anthropic-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic-client.d.ts","sourceRoot":"","sources":["../../../src/clients/anthropic-client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG5C,qBAAa,eAAgB,SAAQ,eAAe;IAClD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,eAAe;IAChC,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,eAAe,CAAS;gBAG9B,MAAM,EAAE,MAAM,EACd,KAAK,GAAE,MAA4B,EACnC,eAAe,GAAE,MAAkC;IAS/C,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IA4CpE,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC;CAQzC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic API Client for Claude
|
|
3
|
+
*/
|
|
4
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
5
|
+
import { BaseModelClient } from './model-interface.js';
|
|
6
|
+
import { DEFAULT_MAX_OUTPUT_TOKENS } from '../constants.js';
|
|
7
|
+
export class AnthropicClient extends BaseModelClient {
|
|
8
|
+
name;
|
|
9
|
+
provider = 'anthropic';
|
|
10
|
+
client;
|
|
11
|
+
model;
|
|
12
|
+
maxOutputTokens;
|
|
13
|
+
constructor(apiKey, model = 'claude-sonnet-4-6', maxOutputTokens = DEFAULT_MAX_OUTPUT_TOKENS) {
|
|
14
|
+
super(apiKey);
|
|
15
|
+
this.client = new Anthropic({ apiKey });
|
|
16
|
+
this.model = model;
|
|
17
|
+
this.name = model;
|
|
18
|
+
this.maxOutputTokens = maxOutputTokens;
|
|
19
|
+
}
|
|
20
|
+
async query(prompt, systemPrompt) {
|
|
21
|
+
if (!this.isConfigured()) {
|
|
22
|
+
return this.createErrorResponse(new Error('Anthropic API key not configured'));
|
|
23
|
+
}
|
|
24
|
+
const startTime = Date.now();
|
|
25
|
+
try {
|
|
26
|
+
const message = await this.withRetry(() => this.client.messages.create({
|
|
27
|
+
model: this.model,
|
|
28
|
+
max_tokens: this.maxOutputTokens,
|
|
29
|
+
temperature: 0.7,
|
|
30
|
+
system: systemPrompt || undefined,
|
|
31
|
+
messages: [
|
|
32
|
+
{
|
|
33
|
+
role: 'user',
|
|
34
|
+
content: prompt,
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
}));
|
|
38
|
+
const latency = Date.now() - startTime;
|
|
39
|
+
const responseText = message.content
|
|
40
|
+
.filter((block) => block.type === 'text')
|
|
41
|
+
.map((block) => block.text)
|
|
42
|
+
.join('\n');
|
|
43
|
+
return {
|
|
44
|
+
modelName: this.name,
|
|
45
|
+
provider: this.provider,
|
|
46
|
+
response: responseText,
|
|
47
|
+
timestamp: new Date(),
|
|
48
|
+
tokensUsed: message.usage.input_tokens + message.usage.output_tokens,
|
|
49
|
+
latency
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
console.error(`Anthropic query error:`, error.message);
|
|
54
|
+
return this.createErrorResponse(error);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async testConnection() {
|
|
58
|
+
try {
|
|
59
|
+
const response = await this.query('Test connection. Respond with: OK');
|
|
60
|
+
return !response.error && response.response.includes('OK');
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=anthropic-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic-client.js","sourceRoot":"","sources":["../../../src/clients/anthropic-client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAE5D,MAAM,OAAO,eAAgB,SAAQ,eAAe;IACzC,IAAI,CAAS;IACb,QAAQ,GAAG,WAAW,CAAC;IACxB,MAAM,CAAY;IAClB,KAAK,CAAS;IACd,eAAe,CAAS;IAEhC,YACE,MAAc,EACd,QAAgB,mBAAmB,EACnC,kBAA0B,yBAAyB;QAEnD,KAAK,CAAC,MAAM,CAAC,CAAC;QACd,IAAI,CAAC,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;QACxC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC;QAClB,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,MAAc,EAAE,YAAqB;QAC/C,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC;YACzB,OAAO,IAAI,CAAC,mBAAmB,CAAC,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC,CAAC;QACjF,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAE7B,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,CACxC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAC1B,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,UAAU,EAAE,IAAI,CAAC,eAAe;gBAChC,WAAW,EAAE,GAAG;gBAChB,MAAM,EAAE,YAAY,IAAI,SAAS;gBACjC,QAAQ,EAAE;oBACR;wBACE,IAAI,EAAE,MAAM;wBACZ,OAAO,EAAE,MAAM;qBAChB;iBACF;aACF,CAAC,CACH,CAAC;YAEF,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;YAEvC,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO;iBACjC,MAAM,CAAC,CAAC,KAAU,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC;iBAC7C,GAAG,CAAC,CAAC,KAAU,EAAE,EAAE,CAAE,KAA0B,CAAC,IAAI,CAAC;iBACrD,IAAI,CAAC,IAAI,CAAC,CAAC;YAEd,OAAO;gBACL,SAAS,EAAE,IAAI,CAAC,IAAI;gBACpB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,QAAQ,EAAE,YAAY;gBACtB,SAAS,EAAE,IAAI,IAAI,EAAE;gBACrB,UAAU,EAAE,OAAO,CAAC,KAAK,CAAC,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC,aAAa;gBACpE,OAAO;aACR,CAAC;QACJ,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACpB,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;YACvD,OAAO,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IAED,KAAK,CAAC,cAAc;QAClB,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;YACvE,OAAO,CAAC,QAAQ,CAAC,KAAK,IAAI,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC7D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google AI API Client for Gemini
|
|
3
|
+
*/
|
|
4
|
+
import { BaseModelClient } from './model-interface.js';
|
|
5
|
+
import { ModelResponse } from '../types.js';
|
|
6
|
+
export declare class GoogleClient extends BaseModelClient {
|
|
7
|
+
readonly name: string;
|
|
8
|
+
readonly provider = "google";
|
|
9
|
+
private client;
|
|
10
|
+
private model;
|
|
11
|
+
private maxOutputTokens;
|
|
12
|
+
constructor(apiKey: string, model?: string, maxOutputTokens?: number);
|
|
13
|
+
query(prompt: string, systemPrompt?: string): Promise<ModelResponse>;
|
|
14
|
+
testConnection(): Promise<boolean>;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=google-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google-client.d.ts","sourceRoot":"","sources":["../../../src/clients/google-client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG5C,qBAAa,YAAa,SAAQ,eAAe;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,YAAY;IAC7B,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,eAAe,CAAS;gBAG9B,MAAM,EAAE,MAAM,EACd,KAAK,GAAE,MAA+B,EACtC,eAAe,GAAE,MAAkC;IAS/C,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAkCpE,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC;CAQzC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google AI API Client for Gemini
|
|
3
|
+
*/
|
|
4
|
+
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
5
|
+
import { BaseModelClient } from './model-interface.js';
|
|
6
|
+
import { DEFAULT_MAX_OUTPUT_TOKENS } from '../constants.js';
|
|
7
|
+
export class GoogleClient extends BaseModelClient {
|
|
8
|
+
name;
|
|
9
|
+
provider = 'google';
|
|
10
|
+
client;
|
|
11
|
+
model;
|
|
12
|
+
maxOutputTokens;
|
|
13
|
+
constructor(apiKey, model = 'gemini-3-pro-preview', maxOutputTokens = DEFAULT_MAX_OUTPUT_TOKENS) {
|
|
14
|
+
super(apiKey);
|
|
15
|
+
this.client = new GoogleGenerativeAI(apiKey);
|
|
16
|
+
this.model = model;
|
|
17
|
+
this.name = model;
|
|
18
|
+
this.maxOutputTokens = maxOutputTokens;
|
|
19
|
+
}
|
|
20
|
+
async query(prompt, systemPrompt) {
|
|
21
|
+
if (!this.isConfigured()) {
|
|
22
|
+
return this.createErrorResponse(new Error('Google API key not configured'));
|
|
23
|
+
}
|
|
24
|
+
const startTime = Date.now();
|
|
25
|
+
try {
|
|
26
|
+
const genModel = this.client.getGenerativeModel({
|
|
27
|
+
model: this.model,
|
|
28
|
+
systemInstruction: systemPrompt,
|
|
29
|
+
generationConfig: { maxOutputTokens: this.maxOutputTokens }
|
|
30
|
+
});
|
|
31
|
+
const result = await this.withRetry(() => genModel.generateContent(prompt));
|
|
32
|
+
const response = result.response;
|
|
33
|
+
const text = response.text();
|
|
34
|
+
const latency = Date.now() - startTime;
|
|
35
|
+
return {
|
|
36
|
+
modelName: this.name,
|
|
37
|
+
provider: this.provider,
|
|
38
|
+
response: text,
|
|
39
|
+
timestamp: new Date(),
|
|
40
|
+
tokensUsed: response.usageMetadata?.totalTokenCount,
|
|
41
|
+
latency
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
console.error(`Google AI query error:`, error.message);
|
|
46
|
+
return this.createErrorResponse(error);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async testConnection() {
|
|
50
|
+
try {
|
|
51
|
+
const response = await this.query('Test connection. Respond with: OK');
|
|
52
|
+
return !response.error && response.response.includes('OK');
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=google-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google-client.js","sourceRoot":"","sources":["../../../src/clients/google-client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAE5D,MAAM,OAAO,YAAa,SAAQ,eAAe;IACtC,IAAI,CAAS;IACb,QAAQ,GAAG,QAAQ,CAAC;IACrB,MAAM,CAAqB;IAC3B,KAAK,CAAS;IACd,eAAe,CAAS;IAEhC,YACE,MAAc,EACd,QAAgB,sBAAsB,EACtC,kBAA0B,yBAAyB;QAEnD,KAAK,CAAC,MAAM,CAAC,CAAC;QACd,IAAI,CAAC,MAAM,GAAG,IAAI,kBAAkB,CAAC,MAAM,CAAC,CAAC;QAC7C,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC;QAClB,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,MAAc,EAAE,YAAqB;QAC/C,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC;YACzB,OAAO,IAAI,CAAC,mBAAmB,CAAC,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC,CAAC;QAC9E,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAE7B,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,kBAAkB,CAAC;gBAC9C,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,iBAAiB,EAAE,YAAY;gBAC/B,gBAAgB,EAAE,EAAE,eAAe,EAAE,IAAI,CAAC,eAAe,EAAE;aAC5D,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC;YAC5E,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAS,CAAC;YAClC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;YAE7B,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;YAEvC,OAAO;gBACL,SAAS,EAAE,IAAI,CAAC,IAAI;gBACpB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,QAAQ,EAAE,IAAI;gBACd,SAAS,EAAE,IAAI,IAAI,EAAE;gBACrB,UAAU,EAAE,QAAQ,CAAC,aAAa,EAAE,eAAe;gBACnD,OAAO;aACR,CAAC;QACJ,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACpB,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;YACvD,OAAO,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IAED,KAAK,CAAC,cAAc;QAClB,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;YACvE,OAAO,CAAC,QAAQ,CAAC,KAAK,IAAI,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC7D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Clients index - exports all model clients
|
|
3
|
+
*/
|
|
4
|
+
export type { AIModelClient } from './model-interface.js';
|
|
5
|
+
export { BaseModelClient } from './model-interface.js';
|
|
6
|
+
export { OpenAIClient } from './openai-client.js';
|
|
7
|
+
export { XAIClient } from './xai-client.js';
|
|
8
|
+
export { AnthropicClient } from './anthropic-client.js';
|
|
9
|
+
export { GoogleClient } from './google-client.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/clients/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,YAAY,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Clients index - exports all model clients
|
|
3
|
+
*/
|
|
4
|
+
export { BaseModelClient } from './model-interface.js';
|
|
5
|
+
export { OpenAIClient } from './openai-client.js';
|
|
6
|
+
export { XAIClient } from './xai-client.js';
|
|
7
|
+
export { AnthropicClient } from './anthropic-client.js';
|
|
8
|
+
export { GoogleClient } from './google-client.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/clients/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified interface for all AI model clients
|
|
3
|
+
*/
|
|
4
|
+
import { ModelResponse } from '../types.js';
|
|
5
|
+
export interface AIModelClient {
|
|
6
|
+
/**
|
|
7
|
+
* Name of the model (e.g., "GPT-5.2", "Claude Opus 4.6", "Grok 4.1", "Gemini 3 Pro")
|
|
8
|
+
*/
|
|
9
|
+
readonly name: string;
|
|
10
|
+
/**
|
|
11
|
+
* Provider identifier
|
|
12
|
+
*/
|
|
13
|
+
readonly provider: string;
|
|
14
|
+
/**
|
|
15
|
+
* Whether the client is properly configured and ready to use
|
|
16
|
+
*/
|
|
17
|
+
isConfigured(): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Query the model with a prompt and system context
|
|
20
|
+
* @param prompt The user prompt/question
|
|
21
|
+
* @param systemPrompt Optional system-level instructions
|
|
22
|
+
* @returns ModelResponse with the completion and metadata
|
|
23
|
+
*/
|
|
24
|
+
query(prompt: string, systemPrompt?: string): Promise<ModelResponse>;
|
|
25
|
+
/**
|
|
26
|
+
* Test the connection and configuration
|
|
27
|
+
*/
|
|
28
|
+
testConnection(): Promise<boolean>;
|
|
29
|
+
}
|
|
30
|
+
export declare abstract class BaseModelClient implements AIModelClient {
|
|
31
|
+
abstract readonly name: string;
|
|
32
|
+
abstract readonly provider: string;
|
|
33
|
+
protected apiKey: string;
|
|
34
|
+
/** Maximum retries for transient API errors (0 = no retry) */
|
|
35
|
+
protected maxRetries: number;
|
|
36
|
+
constructor(apiKey: string);
|
|
37
|
+
isConfigured(): boolean;
|
|
38
|
+
abstract query(prompt: string, systemPrompt?: string): Promise<ModelResponse>;
|
|
39
|
+
abstract testConnection(): Promise<boolean>;
|
|
40
|
+
/**
|
|
41
|
+
* Retry wrapper for transient API errors.
|
|
42
|
+
* Retries up to `maxRetries` times with exponential backoff (1s, 2s, 4s…)
|
|
43
|
+
* only when the thrown error is classified as transient (network issues,
|
|
44
|
+
* rate limits, server errors).
|
|
45
|
+
*
|
|
46
|
+
* The `() => T` signature (rather than `() => Promise<T>`) ensures that
|
|
47
|
+
* when SDK module types are unavailable and the callback returns `any`,
|
|
48
|
+
* the result correctly resolves to `any` via `Awaited<any>` instead of
|
|
49
|
+
* falling back to `unknown`.
|
|
50
|
+
*/
|
|
51
|
+
protected withRetry<T>(fn: () => T): Promise<Awaited<T>>;
|
|
52
|
+
protected createErrorResponse(error: unknown): ModelResponse;
|
|
53
|
+
}
|
|
54
|
+
/** Determine whether an error is transient and worth retrying. */
|
|
55
|
+
export declare function isRetryableError(error: unknown): boolean;
|
|
56
|
+
/** Calculate the backoff delay for a given retry attempt. */
|
|
57
|
+
export declare function retryDelay(attempt: number, error?: unknown): number;
|
|
58
|
+
/** Promisified sleep. */
|
|
59
|
+
export declare function sleep(ms: number): Promise<void>;
|
|
60
|
+
//# sourceMappingURL=model-interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-interface.d.ts","sourceRoot":"","sources":["../../../src/clients/model-interface.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAwB5C,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B;;OAEG;IACH,YAAY,IAAI,OAAO,CAAC;IAExB;;;;;OAKG;IACH,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAErE;;OAEG;IACH,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CACpC;AAMD,8BAAsB,eAAgB,YAAW,aAAa;IAC5D,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IACnC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC;IAEzB,8DAA8D;IAC9D,SAAS,CAAC,UAAU,EAAE,MAAM,CAAuB;gBAEvC,MAAM,EAAE,MAAM;IAI1B,YAAY,IAAI,OAAO;IAIvB,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAC7E,QAAQ,CAAC,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC;IAE3C;;;;;;;;;;OAUG;cACa,SAAS,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAsB9D,SAAS,CAAC,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa;CAgB7D;AAMD,kEAAkE;AAClE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CASxD;AAED,6DAA6D;AAC7D,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CASnE;AAED,yBAAyB;AACzB,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified interface for all AI model clients
|
|
3
|
+
*/
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Retry configuration
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
/** HTTP status codes that indicate a transient, retryable failure */
|
|
8
|
+
const RETRYABLE_STATUS_CODES = new Set([429, 500, 502, 503, 529]);
|
|
9
|
+
/** Error message substrings that indicate a transient failure */
|
|
10
|
+
const RETRYABLE_PATTERNS = [
|
|
11
|
+
'econnrefused', 'econnreset', 'etimedout', 'enotfound',
|
|
12
|
+
'timeout', 'rate limit', 'rate_limit', 'overloaded',
|
|
13
|
+
'capacity', 'network', 'socket hang up', 'fetch failed',
|
|
14
|
+
'service unavailable', 'internal server error',
|
|
15
|
+
];
|
|
16
|
+
/** Default number of retries for transient API errors */
|
|
17
|
+
const DEFAULT_MAX_RETRIES = 2;
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Base class with built-in retry
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
export class BaseModelClient {
|
|
22
|
+
apiKey;
|
|
23
|
+
/** Maximum retries for transient API errors (0 = no retry) */
|
|
24
|
+
maxRetries = DEFAULT_MAX_RETRIES;
|
|
25
|
+
constructor(apiKey) {
|
|
26
|
+
this.apiKey = apiKey;
|
|
27
|
+
}
|
|
28
|
+
isConfigured() {
|
|
29
|
+
return !!this.apiKey && this.apiKey !== 'your_api_key_here';
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Retry wrapper for transient API errors.
|
|
33
|
+
* Retries up to `maxRetries` times with exponential backoff (1s, 2s, 4s…)
|
|
34
|
+
* only when the thrown error is classified as transient (network issues,
|
|
35
|
+
* rate limits, server errors).
|
|
36
|
+
*
|
|
37
|
+
* The `() => T` signature (rather than `() => Promise<T>`) ensures that
|
|
38
|
+
* when SDK module types are unavailable and the callback returns `any`,
|
|
39
|
+
* the result correctly resolves to `any` via `Awaited<any>` instead of
|
|
40
|
+
* falling back to `unknown`.
|
|
41
|
+
*/
|
|
42
|
+
async withRetry(fn) {
|
|
43
|
+
let lastError;
|
|
44
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
45
|
+
try {
|
|
46
|
+
return await fn();
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
lastError = err;
|
|
50
|
+
if (attempt < this.maxRetries && isRetryableError(err)) {
|
|
51
|
+
const delay = retryDelay(attempt, err);
|
|
52
|
+
console.warn(`[${this.name}] Attempt ${attempt + 1}/${this.maxRetries + 1} failed` +
|
|
53
|
+
` (${err instanceof Error ? err.message : err}), retrying in ${delay}ms…`);
|
|
54
|
+
await sleep(delay);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
throw lastError;
|
|
62
|
+
}
|
|
63
|
+
createErrorResponse(error) {
|
|
64
|
+
const message = error instanceof Error
|
|
65
|
+
? error.message
|
|
66
|
+
: typeof error === 'string'
|
|
67
|
+
? error
|
|
68
|
+
: 'Unknown error occurred';
|
|
69
|
+
return {
|
|
70
|
+
modelName: this.name,
|
|
71
|
+
provider: this.provider,
|
|
72
|
+
response: '',
|
|
73
|
+
timestamp: new Date(),
|
|
74
|
+
latency: 0,
|
|
75
|
+
error: message
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// Helpers (exported for testing)
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
/** Determine whether an error is transient and worth retrying. */
|
|
83
|
+
export function isRetryableError(error) {
|
|
84
|
+
if (!error)
|
|
85
|
+
return false;
|
|
86
|
+
// HTTP status from SDK errors (OpenAI, Anthropic, etc.)
|
|
87
|
+
const status = error?.status ?? error?.statusCode;
|
|
88
|
+
if (typeof status === 'number' && RETRYABLE_STATUS_CODES.has(status))
|
|
89
|
+
return true;
|
|
90
|
+
const msg = (error instanceof Error ? error.message : String(error)).toLowerCase();
|
|
91
|
+
return RETRYABLE_PATTERNS.some((p) => msg.includes(p));
|
|
92
|
+
}
|
|
93
|
+
/** Calculate the backoff delay for a given retry attempt. */
|
|
94
|
+
export function retryDelay(attempt, error) {
|
|
95
|
+
// Honour Retry-After from rate-limit responses (in seconds)
|
|
96
|
+
const retryAfter = error?.headers?.['retry-after'];
|
|
97
|
+
if (retryAfter) {
|
|
98
|
+
const secs = parseInt(retryAfter, 10);
|
|
99
|
+
if (!isNaN(secs) && secs > 0 && secs <= 60)
|
|
100
|
+
return secs * 1000;
|
|
101
|
+
}
|
|
102
|
+
// Exponential backoff: 1s, 2s, 4s… capped at 10s
|
|
103
|
+
return Math.min(1000 * Math.pow(2, attempt), 10_000);
|
|
104
|
+
}
|
|
105
|
+
/** Promisified sleep. */
|
|
106
|
+
export function sleep(ms) {
|
|
107
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=model-interface.js.map
|