@krutai/ai-provider 0.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.
@@ -0,0 +1,248 @@
1
+ import * as _openrouter_sdk_lib_event_streams_js from '@openrouter/sdk/lib/event-streams.js';
2
+ import * as _openrouter_sdk_models from '@openrouter/sdk/models';
3
+ import { OpenRouter } from '@openrouter/sdk';
4
+
5
+ /**
6
+ * Types for @krutai/ai-provider
7
+ */
8
+ /**
9
+ * Default model used when no model is specified
10
+ */
11
+ declare const DEFAULT_MODEL: "qwen/qwen3-235b-a22b-thinking-2507";
12
+ /**
13
+ * Configuration options for KrutAIProvider
14
+ */
15
+ interface KrutAIProviderConfig {
16
+ /**
17
+ * KrutAI API key for service validation.
18
+ * @required
19
+ */
20
+ apiKey: string;
21
+ /**
22
+ * OpenRouter API key.
23
+ * Falls back to process.env.OPENROUTER_API_KEY if not provided.
24
+ */
25
+ openRouterApiKey?: string;
26
+ /**
27
+ * The AI model to use.
28
+ * @default "qwen/qwen3-235b-a22b-thinking-2507"
29
+ * @see https://openrouter.ai/models
30
+ */
31
+ model?: string;
32
+ /**
33
+ * Whether to validate the OpenRouter API key on initialization.
34
+ * @default true
35
+ */
36
+ validateOnInit?: boolean;
37
+ /**
38
+ * Custom POST endpoint for OpenRouter API key validation.
39
+ * Will be wired in once you deploy the route.
40
+ */
41
+ validationEndpoint?: string;
42
+ }
43
+ /**
44
+ * A single chat message (OpenRouter format)
45
+ */
46
+ interface ChatMessage {
47
+ role: 'user' | 'assistant' | 'system';
48
+ content: string;
49
+ }
50
+ /**
51
+ * Options for a single generate / stream call
52
+ */
53
+ interface GenerateOptions {
54
+ /**
55
+ * Override the model for this specific call.
56
+ */
57
+ model?: string;
58
+ /**
59
+ * System prompt (prepended as a system message).
60
+ */
61
+ system?: string;
62
+ /**
63
+ * Maximum tokens to generate.
64
+ */
65
+ maxTokens?: number;
66
+ /**
67
+ * Temperature (0–2).
68
+ */
69
+ temperature?: number;
70
+ }
71
+
72
+ /**
73
+ * OpenRouter API Key Validator
74
+ *
75
+ * Validates the OpenRouter API key format and (optionally) its validity
76
+ * via a configurable POST endpoint.
77
+ *
78
+ * The user will later provide a live POST route; until then the
79
+ * service-level check is a placeholder that accepts any well-formed key.
80
+ */
81
+ declare class OpenRouterKeyValidationError extends Error {
82
+ constructor(message: string);
83
+ }
84
+ /**
85
+ * Validates the format of an OpenRouter API key.
86
+ * @throws {OpenRouterKeyValidationError}
87
+ */
88
+ declare function validateOpenRouterKeyFormat(apiKey: string): void;
89
+ /**
90
+ * Validates the OpenRouter API key with the KrutAI validation service.
91
+ *
92
+ * The user will provide a live POST route later. Until then this is a
93
+ * placeholder that accepts any key that passes format validation.
94
+ *
95
+ * @param apiKey - OpenRouter API key to validate
96
+ * @param validationEndpoint - Optional POST URL to validate against
97
+ */
98
+ declare function validateOpenRouterKeyWithService(apiKey: string, validationEndpoint?: string): Promise<boolean>;
99
+
100
+ /**
101
+ * KrutAIProvider — AI provider for KrutAI
102
+ *
103
+ * Wraps `@openrouter/sdk` and adds:
104
+ * - OpenRouter API key format validation
105
+ * - Configurable default model (defaults to qwen/qwen3-235b-a22b-thinking-2507)
106
+ * - Optional pluggable validation endpoint
107
+ *
108
+ * @example
109
+ * ```typescript
110
+ * import { KrutAIProvider } from '@krutai/ai-provider';
111
+ *
112
+ * const ai = new KrutAIProvider({
113
+ * apiKey: process.env.KRUTAI_API_KEY!,
114
+ * openRouterApiKey: process.env.OPENROUTER_API_KEY!, // or set in env
115
+ * });
116
+ *
117
+ * await ai.initialize();
118
+ *
119
+ * const text = await ai.generate('Tell me a joke');
120
+ * console.log(text);
121
+ * ```
122
+ */
123
+ declare class KrutAIProvider {
124
+ private readonly resolvedOpenRouterKey;
125
+ private readonly resolvedModel;
126
+ private readonly config;
127
+ private openRouterClient;
128
+ private initialized;
129
+ constructor(config: KrutAIProviderConfig);
130
+ /**
131
+ * Initialize the provider.
132
+ * Validates the OpenRouter API key (optionally against a service endpoint)
133
+ * and sets up the underlying OpenRouter client.
134
+ *
135
+ * @throws {OpenRouterKeyValidationError}
136
+ */
137
+ initialize(): Promise<void>;
138
+ /** @private */
139
+ private setupClient;
140
+ /**
141
+ * Get the raw OpenRouter SDK client instance.
142
+ * @throws {Error} If not initialized
143
+ */
144
+ getClient(): OpenRouter;
145
+ /**
146
+ * Get the currently configured default model.
147
+ */
148
+ getModel(): string;
149
+ /**
150
+ * Check whether the provider has been initialized.
151
+ */
152
+ isInitialized(): boolean;
153
+ /**
154
+ * Generate a response for a prompt (non-streaming).
155
+ *
156
+ * @param prompt - The user prompt string
157
+ * @param options - Optional overrides (model, system, maxTokens, temperature)
158
+ * @returns The assistant's response text
159
+ */
160
+ generate(prompt: string, options?: GenerateOptions): Promise<string>;
161
+ /**
162
+ * Generate a streaming response for a prompt.
163
+ *
164
+ * @param prompt - The user prompt string
165
+ * @param options - Optional overrides (model, system, maxTokens, temperature)
166
+ * @returns An async iterable of server-sent event chunks
167
+ *
168
+ * @example
169
+ * ```typescript
170
+ * const stream = await ai.stream('Tell me a story');
171
+ * for await (const chunk of stream) {
172
+ * process.stdout.write(chunk.choices?.[0]?.delta?.content ?? '');
173
+ * }
174
+ * ```
175
+ */
176
+ stream(prompt: string, options?: GenerateOptions): Promise<_openrouter_sdk_lib_event_streams_js.EventStream<_openrouter_sdk_models.ChatStreamingResponseChunkData>>;
177
+ /**
178
+ * Multi-turn conversation: pass a full message history.
179
+ *
180
+ * @param messages - Full conversation history
181
+ * @param options - Optional overrides (model, maxTokens, temperature)
182
+ */
183
+ chat(messages: ChatMessage[], options?: GenerateOptions): Promise<string>;
184
+ }
185
+
186
+ /**
187
+ * @krutai/ai-provider — AI Provider package for KrutAI
188
+ *
189
+ * A thin wrapper around `@openrouter/sdk`, mirroring the patterns from `@krutai/auth`.
190
+ *
191
+ * Default model: `qwen/qwen3-235b-a22b-thinking-2507`
192
+ *
193
+ * @example Basic usage
194
+ * ```typescript
195
+ * import { krutAI } from '@krutai/ai-provider';
196
+ *
197
+ * const ai = krutAI(); // uses OPENROUTER_API_KEY env var
198
+ * await ai.initialize();
199
+ *
200
+ * const text = await ai.generate('Write a poem about TypeScript');
201
+ * console.log(text);
202
+ * ```
203
+ *
204
+ * @example With custom model
205
+ * ```typescript
206
+ * const ai = krutAI({ model: 'openai/gpt-4o' });
207
+ * await ai.initialize();
208
+ * const text = await ai.generate('Hello!');
209
+ * ```
210
+ *
211
+ * @example Streaming
212
+ * ```typescript
213
+ * const ai = krutAI();
214
+ * await ai.initialize();
215
+ *
216
+ * const stream = await ai.stream('Tell me a story');
217
+ * for await (const chunk of stream) {
218
+ * process.stdout.write(chunk.choices[0]?.delta?.content ?? '');
219
+ * }
220
+ * ```
221
+ *
222
+ * @packageDocumentation
223
+ */
224
+
225
+ /**
226
+ * krutAI — convenience factory (mirrors `krutAuth` in @krutai/auth).
227
+ *
228
+ * Creates a `KrutAIProvider` instance. OpenRouter API key is read from
229
+ * `config.openRouterApiKey` or falls back to `process.env.OPENROUTER_API_KEY`.
230
+ *
231
+ * @param config - Provider configuration (all fields optional except apiKey)
232
+ * @returns A `KrutAIProvider` instance (call `.initialize()` before use)
233
+ *
234
+ * @example
235
+ * ```typescript
236
+ * import { krutAI } from '@krutai/ai-provider';
237
+ *
238
+ * const ai = krutAI(); // env OPENROUTER_API_KEY + default model
239
+ * await ai.initialize();
240
+ * const text = await ai.generate('Hello!');
241
+ * ```
242
+ */
243
+ declare function krutAI(config?: Partial<KrutAIProviderConfig> & {
244
+ apiKey?: string;
245
+ }): KrutAIProvider;
246
+ declare const VERSION = "0.1.0";
247
+
248
+ export { type ChatMessage, DEFAULT_MODEL, type GenerateOptions, KrutAIProvider, type KrutAIProviderConfig, OpenRouterKeyValidationError, VERSION, krutAI, validateOpenRouterKeyFormat, validateOpenRouterKeyWithService };
package/dist/index.js ADDED
@@ -0,0 +1,231 @@
1
+ 'use strict';
2
+
3
+ var sdk = require('@openrouter/sdk');
4
+
5
+ // src/types.ts
6
+ var DEFAULT_MODEL = "qwen/qwen3-235b-a22b-thinking-2507";
7
+
8
+ // src/validator.ts
9
+ var OpenRouterKeyValidationError = class extends Error {
10
+ constructor(message) {
11
+ super(message);
12
+ this.name = "OpenRouterKeyValidationError";
13
+ }
14
+ };
15
+ var OPENROUTER_KEY_PREFIX = "sk-or-v1-";
16
+ var OPENROUTER_KEY_MIN_LENGTH = 20;
17
+ function validateOpenRouterKeyFormat(apiKey) {
18
+ if (!apiKey || typeof apiKey !== "string") {
19
+ throw new OpenRouterKeyValidationError("OpenRouter API key must be a non-empty string");
20
+ }
21
+ if (apiKey.trim().length === 0) {
22
+ throw new OpenRouterKeyValidationError("OpenRouter API key cannot be empty or whitespace");
23
+ }
24
+ if (!apiKey.startsWith(OPENROUTER_KEY_PREFIX)) {
25
+ throw new OpenRouterKeyValidationError(
26
+ `OpenRouter API key must start with "${OPENROUTER_KEY_PREFIX}"`
27
+ );
28
+ }
29
+ if (apiKey.length < OPENROUTER_KEY_MIN_LENGTH) {
30
+ throw new OpenRouterKeyValidationError(
31
+ `OpenRouter API key must be at least ${OPENROUTER_KEY_MIN_LENGTH} characters long`
32
+ );
33
+ }
34
+ }
35
+ async function validateOpenRouterKeyWithService(apiKey, validationEndpoint) {
36
+ validateOpenRouterKeyFormat(apiKey);
37
+ if (validationEndpoint) {
38
+ try {
39
+ const response = await fetch(validationEndpoint, {
40
+ method: "POST",
41
+ headers: { "Content-Type": "application/json" },
42
+ body: JSON.stringify({ apiKey })
43
+ });
44
+ if (!response.ok) {
45
+ throw new OpenRouterKeyValidationError(
46
+ `OpenRouter API key validation failed: HTTP ${response.status}`
47
+ );
48
+ }
49
+ const data = await response.json();
50
+ if (data.valid === false) {
51
+ throw new OpenRouterKeyValidationError(
52
+ "OpenRouter API key rejected by validation service"
53
+ );
54
+ }
55
+ return true;
56
+ } catch (error) {
57
+ if (error instanceof OpenRouterKeyValidationError) throw error;
58
+ throw new OpenRouterKeyValidationError(
59
+ `Failed to reach validation endpoint: ${error instanceof Error ? error.message : "Unknown error"}`
60
+ );
61
+ }
62
+ }
63
+ return true;
64
+ }
65
+
66
+ // src/client.ts
67
+ var KrutAIProvider = class {
68
+ resolvedOpenRouterKey;
69
+ resolvedModel;
70
+ config;
71
+ openRouterClient = null;
72
+ initialized = false;
73
+ constructor(config) {
74
+ this.config = config;
75
+ this.resolvedOpenRouterKey = config.openRouterApiKey ?? process.env.OPENROUTER_API_KEY ?? "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";
76
+ this.resolvedModel = config.model ?? DEFAULT_MODEL;
77
+ validateOpenRouterKeyFormat(this.resolvedOpenRouterKey);
78
+ if (config.validateOnInit === false) {
79
+ this.setupClient();
80
+ }
81
+ }
82
+ /**
83
+ * Initialize the provider.
84
+ * Validates the OpenRouter API key (optionally against a service endpoint)
85
+ * and sets up the underlying OpenRouter client.
86
+ *
87
+ * @throws {OpenRouterKeyValidationError}
88
+ */
89
+ async initialize() {
90
+ if (this.initialized) return;
91
+ if (this.config.validateOnInit !== false) {
92
+ await validateOpenRouterKeyWithService(
93
+ this.resolvedOpenRouterKey,
94
+ this.config.validationEndpoint
95
+ );
96
+ }
97
+ this.setupClient();
98
+ this.initialized = true;
99
+ }
100
+ /** @private */
101
+ setupClient() {
102
+ this.openRouterClient = new sdk.OpenRouter({
103
+ apiKey: this.resolvedOpenRouterKey
104
+ });
105
+ }
106
+ /**
107
+ * Get the raw OpenRouter SDK client instance.
108
+ * @throws {Error} If not initialized
109
+ */
110
+ getClient() {
111
+ if (!this.openRouterClient) {
112
+ throw new Error(
113
+ "KrutAIProvider not initialized. Call initialize() first or set validateOnInit to false."
114
+ );
115
+ }
116
+ return this.openRouterClient;
117
+ }
118
+ /**
119
+ * Get the currently configured default model.
120
+ */
121
+ getModel() {
122
+ return this.resolvedModel;
123
+ }
124
+ /**
125
+ * Check whether the provider has been initialized.
126
+ */
127
+ isInitialized() {
128
+ return this.initialized;
129
+ }
130
+ /**
131
+ * Generate a response for a prompt (non-streaming).
132
+ *
133
+ * @param prompt - The user prompt string
134
+ * @param options - Optional overrides (model, system, maxTokens, temperature)
135
+ * @returns The assistant's response text
136
+ */
137
+ async generate(prompt, options = {}) {
138
+ const client = this.getClient();
139
+ const model = options.model ?? this.resolvedModel;
140
+ const messages = [];
141
+ if (options.system) {
142
+ messages.push({ role: "system", content: options.system });
143
+ }
144
+ messages.push({ role: "user", content: prompt });
145
+ const result = await client.chat.send({
146
+ chatGenerationParams: {
147
+ model,
148
+ messages,
149
+ stream: false,
150
+ ...options.maxTokens !== void 0 ? { maxTokens: options.maxTokens } : {},
151
+ ...options.temperature !== void 0 ? { temperature: options.temperature } : {}
152
+ }
153
+ });
154
+ const response = result;
155
+ return response.choices?.[0]?.message?.content ?? "";
156
+ }
157
+ /**
158
+ * Generate a streaming response for a prompt.
159
+ *
160
+ * @param prompt - The user prompt string
161
+ * @param options - Optional overrides (model, system, maxTokens, temperature)
162
+ * @returns An async iterable of server-sent event chunks
163
+ *
164
+ * @example
165
+ * ```typescript
166
+ * const stream = await ai.stream('Tell me a story');
167
+ * for await (const chunk of stream) {
168
+ * process.stdout.write(chunk.choices?.[0]?.delta?.content ?? '');
169
+ * }
170
+ * ```
171
+ */
172
+ async stream(prompt, options = {}) {
173
+ const client = this.getClient();
174
+ const model = options.model ?? this.resolvedModel;
175
+ const messages = [];
176
+ if (options.system) {
177
+ messages.push({ role: "system", content: options.system });
178
+ }
179
+ messages.push({ role: "user", content: prompt });
180
+ return client.chat.send({
181
+ chatGenerationParams: {
182
+ model,
183
+ messages,
184
+ stream: true,
185
+ ...options.maxTokens !== void 0 ? { maxTokens: options.maxTokens } : {},
186
+ ...options.temperature !== void 0 ? { temperature: options.temperature } : {}
187
+ }
188
+ });
189
+ }
190
+ /**
191
+ * Multi-turn conversation: pass a full message history.
192
+ *
193
+ * @param messages - Full conversation history
194
+ * @param options - Optional overrides (model, maxTokens, temperature)
195
+ */
196
+ async chat(messages, options = {}) {
197
+ const client = this.getClient();
198
+ const model = options.model ?? this.resolvedModel;
199
+ const result = await client.chat.send({
200
+ chatGenerationParams: {
201
+ model,
202
+ messages,
203
+ stream: false,
204
+ ...options.maxTokens !== void 0 ? { maxTokens: options.maxTokens } : {},
205
+ ...options.temperature !== void 0 ? { temperature: options.temperature } : {}
206
+ }
207
+ });
208
+ const response = result;
209
+ return response.choices?.[0]?.message?.content ?? "";
210
+ }
211
+ };
212
+
213
+ // src/index.ts
214
+ function krutAI(config = {}) {
215
+ return new KrutAIProvider({
216
+ apiKey: config.apiKey ?? "krutai-internal",
217
+ model: config.model ?? DEFAULT_MODEL,
218
+ ...config
219
+ });
220
+ }
221
+ var VERSION = "0.1.0";
222
+
223
+ exports.DEFAULT_MODEL = DEFAULT_MODEL;
224
+ exports.KrutAIProvider = KrutAIProvider;
225
+ exports.OpenRouterKeyValidationError = OpenRouterKeyValidationError;
226
+ exports.VERSION = VERSION;
227
+ exports.krutAI = krutAI;
228
+ exports.validateOpenRouterKeyFormat = validateOpenRouterKeyFormat;
229
+ exports.validateOpenRouterKeyWithService = validateOpenRouterKeyWithService;
230
+ //# sourceMappingURL=index.js.map
231
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/validator.ts","../src/client.ts","../src/index.ts"],"names":["OpenRouter"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Types for @krutai/ai-provider\n */\n\n/**\n * Default model used when no model is specified\n */\nexport const DEFAULT_MODEL = 'qwen/qwen3-235b-a22b-thinking-2507' as const;\n\n/**\n * Configuration options for KrutAIProvider\n */\nexport interface KrutAIProviderConfig {\n /**\n * KrutAI API key for service validation.\n * @required\n */\n apiKey: string;\n\n /**\n * OpenRouter API key.\n * Falls back to process.env.OPENROUTER_API_KEY if not provided.\n */\n openRouterApiKey?: string;\n\n /**\n * The AI model to use.\n * @default \"qwen/qwen3-235b-a22b-thinking-2507\"\n * @see https://openrouter.ai/models\n */\n model?: string;\n\n /**\n * Whether to validate the OpenRouter API key on initialization.\n * @default true\n */\n validateOnInit?: boolean;\n\n /**\n * Custom POST endpoint for OpenRouter API key validation.\n * Will be wired in once you deploy the route.\n */\n validationEndpoint?: string;\n}\n\n/**\n * A single chat message (OpenRouter format)\n */\nexport interface ChatMessage {\n role: 'user' | 'assistant' | 'system';\n content: string;\n}\n\n/**\n * Options for a single generate / stream call\n */\nexport interface GenerateOptions {\n /**\n * Override the model for this specific call.\n */\n model?: string;\n\n /**\n * System prompt (prepended as a system message).\n */\n system?: string;\n\n /**\n * Maximum tokens to generate.\n */\n maxTokens?: number;\n\n /**\n * Temperature (0–2).\n */\n temperature?: number;\n}\n","/**\n * OpenRouter API Key Validator\n *\n * Validates the OpenRouter API key format and (optionally) its validity\n * via a configurable POST endpoint.\n *\n * The user will later provide a live POST route; until then the\n * service-level check is a placeholder that accepts any well-formed key.\n */\n\nexport class OpenRouterKeyValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'OpenRouterKeyValidationError';\n }\n}\n\n/**\n * OpenRouter API keys start with \"sk-or-v1-\"\n */\nconst OPENROUTER_KEY_PREFIX = 'sk-or-v1-';\nconst OPENROUTER_KEY_MIN_LENGTH = 20;\n\n/**\n * Validates the format of an OpenRouter API key.\n * @throws {OpenRouterKeyValidationError}\n */\nexport function validateOpenRouterKeyFormat(apiKey: string): void {\n if (!apiKey || typeof apiKey !== 'string') {\n throw new OpenRouterKeyValidationError('OpenRouter API key must be a non-empty string');\n }\n\n if (apiKey.trim().length === 0) {\n throw new OpenRouterKeyValidationError('OpenRouter API key cannot be empty or whitespace');\n }\n\n if (!apiKey.startsWith(OPENROUTER_KEY_PREFIX)) {\n throw new OpenRouterKeyValidationError(\n `OpenRouter API key must start with \"${OPENROUTER_KEY_PREFIX}\"`\n );\n }\n\n if (apiKey.length < OPENROUTER_KEY_MIN_LENGTH) {\n throw new OpenRouterKeyValidationError(\n `OpenRouter API key must be at least ${OPENROUTER_KEY_MIN_LENGTH} characters long`\n );\n }\n}\n\n/**\n * Validates the OpenRouter API key with the KrutAI validation service.\n *\n * The user will provide a live POST route later. Until then this is a\n * placeholder that accepts any key that passes format validation.\n *\n * @param apiKey - OpenRouter API key to validate\n * @param validationEndpoint - Optional POST URL to validate against\n */\nexport async function validateOpenRouterKeyWithService(\n apiKey: string,\n validationEndpoint?: string\n): Promise<boolean> {\n // Always validate format first\n validateOpenRouterKeyFormat(apiKey);\n\n if (validationEndpoint) {\n try {\n const response = await fetch(validationEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ apiKey }),\n });\n\n if (!response.ok) {\n throw new OpenRouterKeyValidationError(\n `OpenRouter API key validation failed: HTTP ${response.status}`\n );\n }\n\n const data = (await response.json()) as { valid?: boolean };\n if (data.valid === false) {\n throw new OpenRouterKeyValidationError(\n 'OpenRouter API key rejected by validation service'\n );\n }\n\n return true;\n } catch (error) {\n if (error instanceof OpenRouterKeyValidationError) throw error;\n throw new OpenRouterKeyValidationError(\n `Failed to reach validation endpoint: ${error instanceof Error ? error.message : 'Unknown error'}`\n );\n }\n }\n\n // TODO: Replace with live endpoint once provided\n // Placeholder — accepts any correctly-formatted key\n return true;\n}\n","import { OpenRouter } from '@openrouter/sdk';\nimport type { KrutAIProviderConfig, GenerateOptions, ChatMessage } from './types';\nimport { DEFAULT_MODEL } from './types';\nimport {\n validateOpenRouterKeyFormat,\n validateOpenRouterKeyWithService,\n OpenRouterKeyValidationError,\n} from './validator';\n\nexport { OpenRouterKeyValidationError };\n\n/**\n * KrutAIProvider — AI provider for KrutAI\n *\n * Wraps `@openrouter/sdk` and adds:\n * - OpenRouter API key format validation\n * - Configurable default model (defaults to qwen/qwen3-235b-a22b-thinking-2507)\n * - Optional pluggable validation endpoint\n *\n * @example\n * ```typescript\n * import { KrutAIProvider } from '@krutai/ai-provider';\n *\n * const ai = new KrutAIProvider({\n * apiKey: process.env.KRUTAI_API_KEY!,\n * openRouterApiKey: process.env.OPENROUTER_API_KEY!, // or set in env\n * });\n *\n * await ai.initialize();\n *\n * const text = await ai.generate('Tell me a joke');\n * console.log(text);\n * ```\n */\nexport class KrutAIProvider {\n private readonly resolvedOpenRouterKey: string;\n private readonly resolvedModel: string;\n private readonly config: KrutAIProviderConfig;\n\n private openRouterClient: OpenRouter | null = null;\n private initialized = false;\n\n constructor(config: KrutAIProviderConfig) {\n this.config = config;\n\n // Resolve OpenRouter key: explicit config → env var → hardcoded default (temporary)\n // TODO: remove hardcoded key once validation endpoint is live\n this.resolvedOpenRouterKey =\n config.openRouterApiKey ??\n process.env.OPENROUTER_API_KEY ??\n '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';\n\n this.resolvedModel = config.model ?? DEFAULT_MODEL;\n\n // Validate OpenRouter key format immediately on construction\n validateOpenRouterKeyFormat(this.resolvedOpenRouterKey);\n\n // Skip async validation if the user opts out\n if (config.validateOnInit === false) {\n this.setupClient();\n }\n }\n\n /**\n * Initialize the provider.\n * Validates the OpenRouter API key (optionally against a service endpoint)\n * and sets up the underlying OpenRouter client.\n *\n * @throws {OpenRouterKeyValidationError}\n */\n async initialize(): Promise<void> {\n if (this.initialized) return;\n\n if (this.config.validateOnInit !== false) {\n await validateOpenRouterKeyWithService(\n this.resolvedOpenRouterKey,\n this.config.validationEndpoint\n );\n }\n\n this.setupClient();\n this.initialized = true;\n }\n\n /** @private */\n private setupClient(): void {\n this.openRouterClient = new OpenRouter({\n apiKey: this.resolvedOpenRouterKey,\n });\n }\n\n /**\n * Get the raw OpenRouter SDK client instance.\n * @throws {Error} If not initialized\n */\n getClient(): OpenRouter {\n if (!this.openRouterClient) {\n throw new Error(\n 'KrutAIProvider not initialized. Call initialize() first or set validateOnInit to false.'\n );\n }\n return this.openRouterClient;\n }\n\n /**\n * Get the currently configured default model.\n */\n getModel(): string {\n return this.resolvedModel;\n }\n\n /**\n * Check whether the provider has been initialized.\n */\n isInitialized(): boolean {\n return this.initialized;\n }\n\n /**\n * Generate a response for a prompt (non-streaming).\n *\n * @param prompt - The user prompt string\n * @param options - Optional overrides (model, system, maxTokens, temperature)\n * @returns The assistant's response text\n */\n async generate(prompt: string, options: GenerateOptions = {}): Promise<string> {\n const client = this.getClient();\n const model = options.model ?? this.resolvedModel;\n\n const messages: ChatMessage[] = [];\n if (options.system) {\n messages.push({ role: 'system', content: options.system });\n }\n messages.push({ role: 'user', content: prompt });\n\n const result = await client.chat.send({\n chatGenerationParams: {\n model,\n messages,\n stream: false,\n ...(options.maxTokens !== undefined ? { maxTokens: options.maxTokens } : {}),\n ...(options.temperature !== undefined ? { temperature: options.temperature } : {}),\n },\n });\n\n // Non-streaming result: ChatResponse\n const response = result as { choices: Array<{ message: { content: string } }> };\n return response.choices?.[0]?.message?.content ?? '';\n }\n\n /**\n * Generate a streaming response for a prompt.\n *\n * @param prompt - The user prompt string\n * @param options - Optional overrides (model, system, maxTokens, temperature)\n * @returns An async iterable of server-sent event chunks\n *\n * @example\n * ```typescript\n * const stream = await ai.stream('Tell me a story');\n * for await (const chunk of stream) {\n * process.stdout.write(chunk.choices?.[0]?.delta?.content ?? '');\n * }\n * ```\n */\n async stream(prompt: string, options: GenerateOptions = {}) {\n const client = this.getClient();\n const model = options.model ?? this.resolvedModel;\n\n const messages: ChatMessage[] = [];\n if (options.system) {\n messages.push({ role: 'system', content: options.system });\n }\n messages.push({ role: 'user', content: prompt });\n\n return client.chat.send({\n chatGenerationParams: {\n model,\n messages,\n stream: true,\n ...(options.maxTokens !== undefined ? { maxTokens: options.maxTokens } : {}),\n ...(options.temperature !== undefined ? { temperature: options.temperature } : {}),\n },\n });\n }\n\n /**\n * Multi-turn conversation: pass a full message history.\n *\n * @param messages - Full conversation history\n * @param options - Optional overrides (model, maxTokens, temperature)\n */\n async chat(messages: ChatMessage[], options: GenerateOptions = {}): Promise<string> {\n const client = this.getClient();\n const model = options.model ?? this.resolvedModel;\n\n const result = await client.chat.send({\n chatGenerationParams: {\n model,\n messages,\n stream: false,\n ...(options.maxTokens !== undefined ? { maxTokens: options.maxTokens } : {}),\n ...(options.temperature !== undefined ? { temperature: options.temperature } : {}),\n },\n });\n\n const response = result as { choices: Array<{ message: { content: string } }> };\n return response.choices?.[0]?.message?.content ?? '';\n }\n}\n","/**\n * @krutai/ai-provider — AI Provider package for KrutAI\n *\n * A thin wrapper around `@openrouter/sdk`, mirroring the patterns from `@krutai/auth`.\n *\n * Default model: `qwen/qwen3-235b-a22b-thinking-2507`\n *\n * @example Basic usage\n * ```typescript\n * import { krutAI } from '@krutai/ai-provider';\n *\n * const ai = krutAI(); // uses OPENROUTER_API_KEY env var\n * await ai.initialize();\n *\n * const text = await ai.generate('Write a poem about TypeScript');\n * console.log(text);\n * ```\n *\n * @example With custom model\n * ```typescript\n * const ai = krutAI({ model: 'openai/gpt-4o' });\n * await ai.initialize();\n * const text = await ai.generate('Hello!');\n * ```\n *\n * @example Streaming\n * ```typescript\n * const ai = krutAI();\n * await ai.initialize();\n *\n * const stream = await ai.stream('Tell me a story');\n * for await (const chunk of stream) {\n * process.stdout.write(chunk.choices[0]?.delta?.content ?? '');\n * }\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { KrutAIProviderConfig } from './types';\nimport { DEFAULT_MODEL } from './types';\nimport { KrutAIProvider } from './client';\n\nexport { KrutAIProvider } from './client';\nexport { OpenRouterKeyValidationError } from './client';\nexport {\n validateOpenRouterKeyFormat,\n validateOpenRouterKeyWithService,\n} from './validator';\nexport type { KrutAIProviderConfig, GenerateOptions, ChatMessage } from './types';\nexport { DEFAULT_MODEL } from './types';\n\n/**\n * krutAI — convenience factory (mirrors `krutAuth` in @krutai/auth).\n *\n * Creates a `KrutAIProvider` instance. OpenRouter API key is read from\n * `config.openRouterApiKey` or falls back to `process.env.OPENROUTER_API_KEY`.\n *\n * @param config - Provider configuration (all fields optional except apiKey)\n * @returns A `KrutAIProvider` instance (call `.initialize()` before use)\n *\n * @example\n * ```typescript\n * import { krutAI } from '@krutai/ai-provider';\n *\n * const ai = krutAI(); // env OPENROUTER_API_KEY + default model\n * await ai.initialize();\n * const text = await ai.generate('Hello!');\n * ```\n */\nexport function krutAI(\n config: Partial<KrutAIProviderConfig> & { apiKey?: string } = {}\n): KrutAIProvider {\n return new KrutAIProvider({\n apiKey: config.apiKey ?? 'krutai-internal',\n model: config.model ?? DEFAULT_MODEL,\n ...config,\n });\n}\n\n// Package metadata\nexport const VERSION = '0.1.0';\n"]}