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.
Files changed (40) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/LICENSE +21 -0
  3. package/README.md +191 -0
  4. package/dist/src/clients/anthropic-client.d.ts +16 -0
  5. package/dist/src/clients/anthropic-client.d.ts.map +1 -0
  6. package/dist/src/clients/anthropic-client.js +67 -0
  7. package/dist/src/clients/anthropic-client.js.map +1 -0
  8. package/dist/src/clients/google-client.d.ts +16 -0
  9. package/dist/src/clients/google-client.d.ts.map +1 -0
  10. package/dist/src/clients/google-client.js +59 -0
  11. package/dist/src/clients/google-client.js.map +1 -0
  12. package/dist/src/clients/index.d.ts +10 -0
  13. package/dist/src/clients/index.d.ts.map +1 -0
  14. package/dist/src/clients/index.js +9 -0
  15. package/dist/src/clients/index.js.map +1 -0
  16. package/dist/src/clients/model-interface.d.ts +60 -0
  17. package/dist/src/clients/model-interface.d.ts.map +1 -0
  18. package/dist/src/clients/model-interface.js +109 -0
  19. package/dist/src/clients/model-interface.js.map +1 -0
  20. package/dist/src/clients/openai-client.d.ts +16 -0
  21. package/dist/src/clients/openai-client.d.ts.map +1 -0
  22. package/dist/src/clients/openai-client.js +82 -0
  23. package/dist/src/clients/openai-client.js.map +1 -0
  24. package/dist/src/clients/xai-client.d.ts +16 -0
  25. package/dist/src/clients/xai-client.d.ts.map +1 -0
  26. package/dist/src/clients/xai-client.js +71 -0
  27. package/dist/src/clients/xai-client.js.map +1 -0
  28. package/dist/src/index.d.ts +20 -0
  29. package/dist/src/index.d.ts.map +1 -0
  30. package/dist/src/index.js +20 -0
  31. package/dist/src/index.js.map +1 -0
  32. package/dist/src/multi-model-query.d.ts +97 -0
  33. package/dist/src/multi-model-query.d.ts.map +1 -0
  34. package/dist/src/multi-model-query.js +121 -0
  35. package/dist/src/multi-model-query.js.map +1 -0
  36. package/dist/src/types.d.ts +184 -0
  37. package/dist/src/types.d.ts.map +1 -0
  38. package/dist/src/types.js +5 -0
  39. package/dist/src/types.js.map +1 -0
  40. 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