@coduckai/sdk 0.1.0 → 0.3.1
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/dist/ai/index.cjs +179 -10
- package/dist/ai/index.cjs.map +1 -1
- package/dist/ai/index.d.cts +52 -7
- package/dist/ai/index.d.ts +52 -7
- package/dist/ai/index.js +179 -10
- package/dist/ai/index.js.map +1 -1
- package/dist/auth/index.cjs.map +1 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/client/index.cjs +1 -1
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/db/index.cjs.map +1 -1
- package/dist/db/index.js.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js.map +1 -1
- package/dist/payments/index.cjs +159 -11
- package/dist/payments/index.cjs.map +1 -1
- package/dist/payments/index.d.cts +70 -7
- package/dist/payments/index.d.ts +70 -7
- package/dist/payments/index.js +155 -11
- package/dist/payments/index.js.map +1 -1
- package/package.json +29 -7
package/dist/ai/index.cjs
CHANGED
|
@@ -12,20 +12,189 @@ function assertServer(moduleName) {
|
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
// src/internal/config.ts
|
|
16
|
+
var globalConfig = {};
|
|
17
|
+
function getConfig() {
|
|
18
|
+
return globalConfig;
|
|
19
|
+
}
|
|
20
|
+
|
|
15
21
|
// src/ai/index.ts
|
|
16
22
|
assertServer("ai");
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
function
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
function detectProvider() {
|
|
24
|
+
const config = getConfig();
|
|
25
|
+
if (config.ai?.provider) return config.ai.provider;
|
|
26
|
+
if (process.env.OPENAI_API_KEY) return "openai";
|
|
27
|
+
if (process.env.ANTHROPIC_API_KEY) return "anthropic";
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
function requireProvider() {
|
|
31
|
+
const provider = detectProvider();
|
|
32
|
+
if (!provider) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
"@coduckai/sdk/ai: No AI provider configured. Set OPENAI_API_KEY or ANTHROPIC_API_KEY."
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return provider;
|
|
38
|
+
}
|
|
39
|
+
async function openaiChat(options) {
|
|
40
|
+
const { default: OpenAI } = await import('openai');
|
|
41
|
+
const config = getConfig();
|
|
42
|
+
const client = new OpenAI({ apiKey: config.ai?.apiKey || process.env.OPENAI_API_KEY });
|
|
43
|
+
const messages = [];
|
|
44
|
+
if (options.system) {
|
|
45
|
+
messages.push({ role: "system", content: options.system });
|
|
46
|
+
}
|
|
47
|
+
messages.push(...options.messages);
|
|
48
|
+
const response = await client.chat.completions.create({
|
|
49
|
+
model: options.model || "gpt-4o",
|
|
50
|
+
messages,
|
|
51
|
+
max_tokens: options.maxTokens || 4096,
|
|
52
|
+
temperature: options.temperature ?? 0.7
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
text: response.choices[0]?.message?.content || "",
|
|
56
|
+
usage: {
|
|
57
|
+
inputTokens: response.usage?.prompt_tokens || 0,
|
|
58
|
+
outputTokens: response.usage?.completion_tokens || 0
|
|
59
|
+
}
|
|
27
60
|
};
|
|
28
61
|
}
|
|
62
|
+
async function* openaiStreamChat(options) {
|
|
63
|
+
const { default: OpenAI } = await import('openai');
|
|
64
|
+
const config = getConfig();
|
|
65
|
+
const client = new OpenAI({ apiKey: config.ai?.apiKey || process.env.OPENAI_API_KEY });
|
|
66
|
+
const messages = [];
|
|
67
|
+
if (options.system) {
|
|
68
|
+
messages.push({ role: "system", content: options.system });
|
|
69
|
+
}
|
|
70
|
+
messages.push(...options.messages);
|
|
71
|
+
const stream = await client.chat.completions.create({
|
|
72
|
+
model: options.model || "gpt-4o",
|
|
73
|
+
messages,
|
|
74
|
+
max_tokens: options.maxTokens || 4096,
|
|
75
|
+
temperature: options.temperature ?? 0.7,
|
|
76
|
+
stream: true
|
|
77
|
+
});
|
|
78
|
+
for await (const chunk of stream) {
|
|
79
|
+
const text = chunk.choices[0]?.delta?.content || "";
|
|
80
|
+
const done = chunk.choices[0]?.finish_reason !== null && chunk.choices[0]?.finish_reason !== void 0;
|
|
81
|
+
if (text) yield { text, done: false };
|
|
82
|
+
if (done) yield { text: "", done: true };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async function anthropicChat(options) {
|
|
86
|
+
const { default: Anthropic } = await import('@anthropic-ai/sdk');
|
|
87
|
+
const config = getConfig();
|
|
88
|
+
const client = new Anthropic({ apiKey: config.ai?.apiKey || process.env.ANTHROPIC_API_KEY });
|
|
89
|
+
const messages = options.messages.filter((m) => m.role !== "system").map((m) => ({ role: m.role, content: m.content }));
|
|
90
|
+
const response = await client.messages.create({
|
|
91
|
+
model: options.model || "claude-sonnet-4-6",
|
|
92
|
+
max_tokens: options.maxTokens || 4096,
|
|
93
|
+
...options.system && { system: options.system },
|
|
94
|
+
messages,
|
|
95
|
+
...options.temperature !== void 0 && { temperature: options.temperature }
|
|
96
|
+
});
|
|
97
|
+
const textBlock = response.content.find((b) => b.type === "text");
|
|
98
|
+
return {
|
|
99
|
+
text: textBlock?.text || "",
|
|
100
|
+
usage: {
|
|
101
|
+
inputTokens: response.usage?.input_tokens || 0,
|
|
102
|
+
outputTokens: response.usage?.output_tokens || 0
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
async function* anthropicStreamChat(options) {
|
|
107
|
+
const { default: Anthropic } = await import('@anthropic-ai/sdk');
|
|
108
|
+
const config = getConfig();
|
|
109
|
+
const client = new Anthropic({ apiKey: config.ai?.apiKey || process.env.ANTHROPIC_API_KEY });
|
|
110
|
+
const messages = options.messages.filter((m) => m.role !== "system").map((m) => ({ role: m.role, content: m.content }));
|
|
111
|
+
const stream = client.messages.stream({
|
|
112
|
+
model: options.model || "claude-sonnet-4-6",
|
|
113
|
+
max_tokens: options.maxTokens || 4096,
|
|
114
|
+
...options.system && { system: options.system },
|
|
115
|
+
messages,
|
|
116
|
+
...options.temperature !== void 0 && { temperature: options.temperature }
|
|
117
|
+
});
|
|
118
|
+
for await (const event of stream) {
|
|
119
|
+
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
|
|
120
|
+
yield { text: event.delta.text, done: false };
|
|
121
|
+
} else if (event.type === "message_stop") {
|
|
122
|
+
yield { text: "", done: true };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
var ai = {
|
|
127
|
+
/**
|
|
128
|
+
* Send a chat completion request. Auto-detects OpenAI or Anthropic from env vars.
|
|
129
|
+
*/
|
|
130
|
+
async chat(options) {
|
|
131
|
+
const provider = requireProvider();
|
|
132
|
+
return provider === "openai" ? openaiChat(options) : anthropicChat(options);
|
|
133
|
+
},
|
|
134
|
+
/**
|
|
135
|
+
* Stream a chat completion. Returns an async iterable of text chunks.
|
|
136
|
+
*/
|
|
137
|
+
streamChat(options) {
|
|
138
|
+
const provider = requireProvider();
|
|
139
|
+
return provider === "openai" ? openaiStreamChat(options) : anthropicStreamChat(options);
|
|
140
|
+
},
|
|
141
|
+
/**
|
|
142
|
+
* Stream AI response directly to an Express response as SSE.
|
|
143
|
+
* Handles headers, streaming, error handling, and cleanup.
|
|
144
|
+
*/
|
|
145
|
+
async streamToSSE(res, options) {
|
|
146
|
+
res.writeHead(200, {
|
|
147
|
+
"Content-Type": "text/event-stream",
|
|
148
|
+
"Cache-Control": "no-cache",
|
|
149
|
+
Connection: "keep-alive"
|
|
150
|
+
});
|
|
151
|
+
try {
|
|
152
|
+
for await (const chunk of ai.streamChat(options)) {
|
|
153
|
+
if (chunk.text) {
|
|
154
|
+
res.write(`data: ${JSON.stringify({ text: chunk.text })}
|
|
155
|
+
|
|
156
|
+
`);
|
|
157
|
+
}
|
|
158
|
+
if (chunk.done) {
|
|
159
|
+
res.write(`data: ${JSON.stringify({ done: true })}
|
|
160
|
+
|
|
161
|
+
`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
} catch (error) {
|
|
165
|
+
res.write(`data: ${JSON.stringify({ error: error.message })}
|
|
166
|
+
|
|
167
|
+
`);
|
|
168
|
+
} finally {
|
|
169
|
+
res.end();
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
/**
|
|
173
|
+
* Generate an image (OpenAI DALL-E only).
|
|
174
|
+
*/
|
|
175
|
+
async generateImage(options) {
|
|
176
|
+
const provider = detectProvider();
|
|
177
|
+
if (provider !== "openai") {
|
|
178
|
+
throw new Error("@coduckai/sdk/ai: generateImage() requires OpenAI. Set OPENAI_API_KEY.");
|
|
179
|
+
}
|
|
180
|
+
const { default: OpenAI } = await import('openai');
|
|
181
|
+
const config = getConfig();
|
|
182
|
+
const client = new OpenAI({ apiKey: config.ai?.apiKey || process.env.OPENAI_API_KEY });
|
|
183
|
+
const response = await client.images.generate({
|
|
184
|
+
model: "dall-e-3",
|
|
185
|
+
prompt: options.prompt,
|
|
186
|
+
size: options.size || "1024x1024",
|
|
187
|
+
n: 1
|
|
188
|
+
});
|
|
189
|
+
return { url: response.data?.[0]?.url || "" };
|
|
190
|
+
},
|
|
191
|
+
/**
|
|
192
|
+
* Returns the detected AI provider ('openai' | 'anthropic' | null).
|
|
193
|
+
*/
|
|
194
|
+
getProvider() {
|
|
195
|
+
return detectProvider();
|
|
196
|
+
}
|
|
197
|
+
};
|
|
29
198
|
|
|
30
199
|
exports.ai = ai;
|
|
31
200
|
//# sourceMappingURL=index.cjs.map
|
package/dist/ai/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/internal/env.ts","../../src/ai/index.ts"],"names":[],"mappings":";;;AAgBO,SAAS,QAAA,GAAoB;AAClC,EAAA,OAAO,OAAO,MAAA,KAAW,WAAA;AAC3B;AAEO,SAAS,aAAa,UAAA,EAA0B;AACrD,EAAA,IAAI,CAAC,UAAS,EAAG;AACf,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,iBAAiB,UAAU,CAAA,2DAAA;AAAA,KAC7B;AAAA,EACF;AACF;;;ACnBA,YAAA,CAAa,IAAI,CAAA;AAEV,IAAM,EAAA,GAAK;AAAA,EAChB,IAAA,EAAM,eAAe,SAAS,CAAA;AAAA,EAC9B,UAAA,EAAY,eAAe,eAAe,CAAA;AAAA,EAC1C,WAAA,EAAa,eAAe,gBAAgB,CAAA;AAAA,EAC5C,aAAA,EAAe,eAAe,kBAAkB,CAAA;AAAA,EAChD,WAAA,EAAa,eAAe,gBAAgB;AAC9C;AAEA,SAAS,eAAe,IAAA,EAAc;AACpC,EAAA,OAAO,MAAM;AACX,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,eAAA,EAAkB,IAAI,CAAA,4CAAA,CAA8C,CAAA;AAAA,EACtF,CAAA;AACF","file":"index.cjs","sourcesContent":["/**\n * Environment variable helpers\n */\n\nexport function getEnv(key: string, fallback?: string): string | undefined {\n return process.env[key] || fallback;\n}\n\nexport function requireEnv(key: string): string {\n const val = process.env[key];\n if (!val) {\n throw new Error(`@coduckai/sdk: Missing required environment variable: ${key}`);\n }\n return val;\n}\n\nexport function isServer(): boolean {\n return typeof window === 'undefined';\n}\n\nexport function assertServer(moduleName: string): void {\n if (!isServer()) {\n throw new Error(\n `@coduckai/sdk/${moduleName} is server-only. Use @coduckai/sdk/client for browser code.`\n );\n }\n}\n","/**\n * @coduckai/sdk/ai — AI Providers (server-only)\n *\n * Coming in v0.2.0. Unified interface for OpenAI + Anthropic.\n */\n\nimport { assertServer } from '../internal/env';\nassertServer('ai');\n\nexport const ai = {\n chat: notImplemented('ai.chat'),\n streamChat: notImplemented('ai.streamChat'),\n streamToSSE: notImplemented('ai.streamToSSE'),\n generateImage: notImplemented('ai.generateImage'),\n getProvider: notImplemented('ai.getProvider'),\n};\n\nfunction notImplemented(name: string) {\n return () => {\n throw new Error(`@coduckai/sdk: ${name}() is not yet implemented. Coming in v0.2.0.`);\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/internal/env.ts","../../src/internal/config.ts","../../src/ai/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Environment variable helpers\n */\n\nexport function getEnv(key: string, fallback?: string): string | undefined {\n return process.env[key] || fallback;\n}\n\nexport function requireEnv(key: string): string {\n const val = process.env[key];\n if (!val) {\n throw new Error(`@coduckai/sdk: Missing required environment variable: ${key}`);\n }\n return val;\n}\n\nexport function isServer(): boolean {\n return typeof window === 'undefined';\n}\n\nexport function assertServer(moduleName: string): void {\n if (!isServer()) {\n throw new Error(\n `@coduckai/sdk/${moduleName} is server-only. Use @coduckai/sdk/client for browser code.`\n );\n }\n}\n","/**\n * Global SDK configuration\n *\n * CoDuck apps use env vars automatically — no configure() needed.\n * Standalone usage can call configure() to override.\n */\n\nexport interface SDKConfig {\n auth?: {\n jwtSecret?: string;\n jwtExpiresIn?: string;\n bcryptRounds?: number;\n };\n db?: {\n connectionString?: string;\n };\n client?: {\n baseUrl?: string;\n tokenKey?: string;\n };\n payments?: {\n stripeSecretKey?: string;\n connectedAccountId?: string;\n webhookSecret?: string;\n };\n ai?: {\n provider?: 'openai' | 'anthropic';\n apiKey?: string;\n };\n}\n\nlet globalConfig: SDKConfig = {};\n\nexport function setConfig(config: SDKConfig): void {\n globalConfig = { ...globalConfig, ...config };\n}\n\nexport function getConfig(): SDKConfig {\n return globalConfig;\n}\n","/**\n * @coduckai/sdk/ai — AI Providers (server-only)\n *\n * Unified interface for OpenAI and Anthropic. Auto-detects provider from env vars.\n * Set OPENAI_API_KEY for OpenAI or ANTHROPIC_API_KEY for Anthropic.\n */\n\nimport { assertServer } from '../internal/env';\nimport { getConfig } from '../internal/config';\n\nassertServer('ai');\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface ChatMessage {\n role: 'user' | 'assistant' | 'system';\n content: string;\n}\n\nexport interface ChatOptions {\n system?: string;\n messages: ChatMessage[];\n model?: string;\n maxTokens?: number;\n temperature?: number;\n}\n\nexport interface ChatResponse {\n text: string;\n usage: { inputTokens: number; outputTokens: number };\n}\n\nexport interface StreamChunk {\n text: string;\n done: boolean;\n}\n\nexport type AIProvider = 'openai' | 'anthropic' | null;\n\n// ---------------------------------------------------------------------------\n// Provider detection\n// ---------------------------------------------------------------------------\n\nfunction detectProvider(): AIProvider {\n const config = getConfig();\n if (config.ai?.provider) return config.ai.provider;\n if (process.env.OPENAI_API_KEY) return 'openai';\n if (process.env.ANTHROPIC_API_KEY) return 'anthropic';\n return null;\n}\n\nfunction requireProvider(): AIProvider & string {\n const provider = detectProvider();\n if (!provider) {\n throw new Error(\n '@coduckai/sdk/ai: No AI provider configured. Set OPENAI_API_KEY or ANTHROPIC_API_KEY.'\n );\n }\n return provider;\n}\n\n// ---------------------------------------------------------------------------\n// OpenAI\n// ---------------------------------------------------------------------------\n\nasync function openaiChat(options: ChatOptions): Promise<ChatResponse> {\n const { default: OpenAI } = await import('openai');\n const config = getConfig();\n const client = new OpenAI({ apiKey: config.ai?.apiKey || process.env.OPENAI_API_KEY });\n\n const messages: Array<{ role: string; content: string }> = [];\n if (options.system) {\n messages.push({ role: 'system', content: options.system });\n }\n messages.push(...options.messages);\n\n const response = await client.chat.completions.create({\n model: options.model || 'gpt-4o',\n messages: messages as any,\n max_tokens: options.maxTokens || 4096,\n temperature: options.temperature ?? 0.7,\n });\n\n return {\n text: response.choices[0]?.message?.content || '',\n usage: {\n inputTokens: response.usage?.prompt_tokens || 0,\n outputTokens: response.usage?.completion_tokens || 0,\n },\n };\n}\n\nasync function* openaiStreamChat(options: ChatOptions): AsyncGenerator<StreamChunk> {\n const { default: OpenAI } = await import('openai');\n const config = getConfig();\n const client = new OpenAI({ apiKey: config.ai?.apiKey || process.env.OPENAI_API_KEY });\n\n const messages: Array<{ role: string; content: string }> = [];\n if (options.system) {\n messages.push({ role: 'system', content: options.system });\n }\n messages.push(...options.messages);\n\n const stream = await client.chat.completions.create({\n model: options.model || 'gpt-4o',\n messages: messages as any,\n max_tokens: options.maxTokens || 4096,\n temperature: options.temperature ?? 0.7,\n stream: true,\n });\n\n for await (const chunk of stream) {\n const text = chunk.choices[0]?.delta?.content || '';\n const done = chunk.choices[0]?.finish_reason !== null && chunk.choices[0]?.finish_reason !== undefined;\n if (text) yield { text, done: false };\n if (done) yield { text: '', done: true };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Anthropic\n// ---------------------------------------------------------------------------\n\nasync function anthropicChat(options: ChatOptions): Promise<ChatResponse> {\n const { default: Anthropic } = await import('@anthropic-ai/sdk');\n const config = getConfig();\n const client = new Anthropic({ apiKey: config.ai?.apiKey || process.env.ANTHROPIC_API_KEY });\n\n const messages = options.messages\n .filter(m => m.role !== 'system')\n .map(m => ({ role: m.role as 'user' | 'assistant', content: m.content }));\n\n const response = await client.messages.create({\n model: options.model || 'claude-sonnet-4-6',\n max_tokens: options.maxTokens || 4096,\n ...(options.system && { system: options.system }),\n messages,\n ...(options.temperature !== undefined && { temperature: options.temperature }),\n });\n\n const textBlock = response.content.find((b: any) => b.type === 'text');\n\n return {\n text: (textBlock as any)?.text || '',\n usage: {\n inputTokens: response.usage?.input_tokens || 0,\n outputTokens: response.usage?.output_tokens || 0,\n },\n };\n}\n\nasync function* anthropicStreamChat(options: ChatOptions): AsyncGenerator<StreamChunk> {\n const { default: Anthropic } = await import('@anthropic-ai/sdk');\n const config = getConfig();\n const client = new Anthropic({ apiKey: config.ai?.apiKey || process.env.ANTHROPIC_API_KEY });\n\n const messages = options.messages\n .filter(m => m.role !== 'system')\n .map(m => ({ role: m.role as 'user' | 'assistant', content: m.content }));\n\n const stream = client.messages.stream({\n model: options.model || 'claude-sonnet-4-6',\n max_tokens: options.maxTokens || 4096,\n ...(options.system && { system: options.system }),\n messages,\n ...(options.temperature !== undefined && { temperature: options.temperature }),\n });\n\n for await (const event of stream) {\n if (event.type === 'content_block_delta' && (event.delta as any).type === 'text_delta') {\n yield { text: (event.delta as any).text, done: false };\n } else if (event.type === 'message_stop') {\n yield { text: '', done: true };\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// AI API\n// ---------------------------------------------------------------------------\n\nexport const ai = {\n /**\n * Send a chat completion request. Auto-detects OpenAI or Anthropic from env vars.\n */\n async chat(options: ChatOptions): Promise<ChatResponse> {\n const provider = requireProvider();\n return provider === 'openai' ? openaiChat(options) : anthropicChat(options);\n },\n\n /**\n * Stream a chat completion. Returns an async iterable of text chunks.\n */\n streamChat(options: ChatOptions): AsyncGenerator<StreamChunk> {\n const provider = requireProvider();\n return provider === 'openai' ? openaiStreamChat(options) : anthropicStreamChat(options);\n },\n\n /**\n * Stream AI response directly to an Express response as SSE.\n * Handles headers, streaming, error handling, and cleanup.\n */\n async streamToSSE(res: any, options: ChatOptions): Promise<void> {\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n });\n\n try {\n for await (const chunk of ai.streamChat(options)) {\n if (chunk.text) {\n res.write(`data: ${JSON.stringify({ text: chunk.text })}\\n\\n`);\n }\n if (chunk.done) {\n res.write(`data: ${JSON.stringify({ done: true })}\\n\\n`);\n }\n }\n } catch (error: any) {\n res.write(`data: ${JSON.stringify({ error: error.message })}\\n\\n`);\n } finally {\n res.end();\n }\n },\n\n /**\n * Generate an image (OpenAI DALL-E only).\n */\n async generateImage(options: { prompt: string; size?: string }): Promise<{ url: string }> {\n const provider = detectProvider();\n if (provider !== 'openai') {\n throw new Error('@coduckai/sdk/ai: generateImage() requires OpenAI. Set OPENAI_API_KEY.');\n }\n\n const { default: OpenAI } = await import('openai');\n const config = getConfig();\n const client = new OpenAI({ apiKey: config.ai?.apiKey || process.env.OPENAI_API_KEY });\n\n const response = await client.images.generate({\n model: 'dall-e-3',\n prompt: options.prompt,\n size: (options.size as any) || '1024x1024',\n n: 1,\n });\n\n return { url: response.data?.[0]?.url || '' };\n },\n\n /**\n * Returns the detected AI provider ('openai' | 'anthropic' | null).\n */\n getProvider(): AIProvider {\n return detectProvider();\n },\n};\n"]}
|
package/dist/ai/index.d.cts
CHANGED
|
@@ -1,14 +1,59 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @coduckai/sdk/ai — AI Providers (server-only)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Unified interface for OpenAI and Anthropic. Auto-detects provider from env vars.
|
|
5
|
+
* Set OPENAI_API_KEY for OpenAI or ANTHROPIC_API_KEY for Anthropic.
|
|
5
6
|
*/
|
|
7
|
+
interface ChatMessage {
|
|
8
|
+
role: 'user' | 'assistant' | 'system';
|
|
9
|
+
content: string;
|
|
10
|
+
}
|
|
11
|
+
interface ChatOptions {
|
|
12
|
+
system?: string;
|
|
13
|
+
messages: ChatMessage[];
|
|
14
|
+
model?: string;
|
|
15
|
+
maxTokens?: number;
|
|
16
|
+
temperature?: number;
|
|
17
|
+
}
|
|
18
|
+
interface ChatResponse {
|
|
19
|
+
text: string;
|
|
20
|
+
usage: {
|
|
21
|
+
inputTokens: number;
|
|
22
|
+
outputTokens: number;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
interface StreamChunk {
|
|
26
|
+
text: string;
|
|
27
|
+
done: boolean;
|
|
28
|
+
}
|
|
29
|
+
type AIProvider = 'openai' | 'anthropic' | null;
|
|
6
30
|
declare const ai: {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Send a chat completion request. Auto-detects OpenAI or Anthropic from env vars.
|
|
33
|
+
*/
|
|
34
|
+
chat(options: ChatOptions): Promise<ChatResponse>;
|
|
35
|
+
/**
|
|
36
|
+
* Stream a chat completion. Returns an async iterable of text chunks.
|
|
37
|
+
*/
|
|
38
|
+
streamChat(options: ChatOptions): AsyncGenerator<StreamChunk>;
|
|
39
|
+
/**
|
|
40
|
+
* Stream AI response directly to an Express response as SSE.
|
|
41
|
+
* Handles headers, streaming, error handling, and cleanup.
|
|
42
|
+
*/
|
|
43
|
+
streamToSSE(res: any, options: ChatOptions): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Generate an image (OpenAI DALL-E only).
|
|
46
|
+
*/
|
|
47
|
+
generateImage(options: {
|
|
48
|
+
prompt: string;
|
|
49
|
+
size?: string;
|
|
50
|
+
}): Promise<{
|
|
51
|
+
url: string;
|
|
52
|
+
}>;
|
|
53
|
+
/**
|
|
54
|
+
* Returns the detected AI provider ('openai' | 'anthropic' | null).
|
|
55
|
+
*/
|
|
56
|
+
getProvider(): AIProvider;
|
|
12
57
|
};
|
|
13
58
|
|
|
14
|
-
export { ai };
|
|
59
|
+
export { type AIProvider, type ChatMessage, type ChatOptions, type ChatResponse, type StreamChunk, ai };
|
package/dist/ai/index.d.ts
CHANGED
|
@@ -1,14 +1,59 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @coduckai/sdk/ai — AI Providers (server-only)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Unified interface for OpenAI and Anthropic. Auto-detects provider from env vars.
|
|
5
|
+
* Set OPENAI_API_KEY for OpenAI or ANTHROPIC_API_KEY for Anthropic.
|
|
5
6
|
*/
|
|
7
|
+
interface ChatMessage {
|
|
8
|
+
role: 'user' | 'assistant' | 'system';
|
|
9
|
+
content: string;
|
|
10
|
+
}
|
|
11
|
+
interface ChatOptions {
|
|
12
|
+
system?: string;
|
|
13
|
+
messages: ChatMessage[];
|
|
14
|
+
model?: string;
|
|
15
|
+
maxTokens?: number;
|
|
16
|
+
temperature?: number;
|
|
17
|
+
}
|
|
18
|
+
interface ChatResponse {
|
|
19
|
+
text: string;
|
|
20
|
+
usage: {
|
|
21
|
+
inputTokens: number;
|
|
22
|
+
outputTokens: number;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
interface StreamChunk {
|
|
26
|
+
text: string;
|
|
27
|
+
done: boolean;
|
|
28
|
+
}
|
|
29
|
+
type AIProvider = 'openai' | 'anthropic' | null;
|
|
6
30
|
declare const ai: {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Send a chat completion request. Auto-detects OpenAI or Anthropic from env vars.
|
|
33
|
+
*/
|
|
34
|
+
chat(options: ChatOptions): Promise<ChatResponse>;
|
|
35
|
+
/**
|
|
36
|
+
* Stream a chat completion. Returns an async iterable of text chunks.
|
|
37
|
+
*/
|
|
38
|
+
streamChat(options: ChatOptions): AsyncGenerator<StreamChunk>;
|
|
39
|
+
/**
|
|
40
|
+
* Stream AI response directly to an Express response as SSE.
|
|
41
|
+
* Handles headers, streaming, error handling, and cleanup.
|
|
42
|
+
*/
|
|
43
|
+
streamToSSE(res: any, options: ChatOptions): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Generate an image (OpenAI DALL-E only).
|
|
46
|
+
*/
|
|
47
|
+
generateImage(options: {
|
|
48
|
+
prompt: string;
|
|
49
|
+
size?: string;
|
|
50
|
+
}): Promise<{
|
|
51
|
+
url: string;
|
|
52
|
+
}>;
|
|
53
|
+
/**
|
|
54
|
+
* Returns the detected AI provider ('openai' | 'anthropic' | null).
|
|
55
|
+
*/
|
|
56
|
+
getProvider(): AIProvider;
|
|
12
57
|
};
|
|
13
58
|
|
|
14
|
-
export { ai };
|
|
59
|
+
export { type AIProvider, type ChatMessage, type ChatOptions, type ChatResponse, type StreamChunk, ai };
|
package/dist/ai/index.js
CHANGED
|
@@ -10,20 +10,189 @@ function assertServer(moduleName) {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
// src/internal/config.ts
|
|
14
|
+
var globalConfig = {};
|
|
15
|
+
function getConfig() {
|
|
16
|
+
return globalConfig;
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
// src/ai/index.ts
|
|
14
20
|
assertServer("ai");
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
function
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
function detectProvider() {
|
|
22
|
+
const config = getConfig();
|
|
23
|
+
if (config.ai?.provider) return config.ai.provider;
|
|
24
|
+
if (process.env.OPENAI_API_KEY) return "openai";
|
|
25
|
+
if (process.env.ANTHROPIC_API_KEY) return "anthropic";
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
function requireProvider() {
|
|
29
|
+
const provider = detectProvider();
|
|
30
|
+
if (!provider) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
"@coduckai/sdk/ai: No AI provider configured. Set OPENAI_API_KEY or ANTHROPIC_API_KEY."
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
return provider;
|
|
36
|
+
}
|
|
37
|
+
async function openaiChat(options) {
|
|
38
|
+
const { default: OpenAI } = await import('openai');
|
|
39
|
+
const config = getConfig();
|
|
40
|
+
const client = new OpenAI({ apiKey: config.ai?.apiKey || process.env.OPENAI_API_KEY });
|
|
41
|
+
const messages = [];
|
|
42
|
+
if (options.system) {
|
|
43
|
+
messages.push({ role: "system", content: options.system });
|
|
44
|
+
}
|
|
45
|
+
messages.push(...options.messages);
|
|
46
|
+
const response = await client.chat.completions.create({
|
|
47
|
+
model: options.model || "gpt-4o",
|
|
48
|
+
messages,
|
|
49
|
+
max_tokens: options.maxTokens || 4096,
|
|
50
|
+
temperature: options.temperature ?? 0.7
|
|
51
|
+
});
|
|
52
|
+
return {
|
|
53
|
+
text: response.choices[0]?.message?.content || "",
|
|
54
|
+
usage: {
|
|
55
|
+
inputTokens: response.usage?.prompt_tokens || 0,
|
|
56
|
+
outputTokens: response.usage?.completion_tokens || 0
|
|
57
|
+
}
|
|
25
58
|
};
|
|
26
59
|
}
|
|
60
|
+
async function* openaiStreamChat(options) {
|
|
61
|
+
const { default: OpenAI } = await import('openai');
|
|
62
|
+
const config = getConfig();
|
|
63
|
+
const client = new OpenAI({ apiKey: config.ai?.apiKey || process.env.OPENAI_API_KEY });
|
|
64
|
+
const messages = [];
|
|
65
|
+
if (options.system) {
|
|
66
|
+
messages.push({ role: "system", content: options.system });
|
|
67
|
+
}
|
|
68
|
+
messages.push(...options.messages);
|
|
69
|
+
const stream = await client.chat.completions.create({
|
|
70
|
+
model: options.model || "gpt-4o",
|
|
71
|
+
messages,
|
|
72
|
+
max_tokens: options.maxTokens || 4096,
|
|
73
|
+
temperature: options.temperature ?? 0.7,
|
|
74
|
+
stream: true
|
|
75
|
+
});
|
|
76
|
+
for await (const chunk of stream) {
|
|
77
|
+
const text = chunk.choices[0]?.delta?.content || "";
|
|
78
|
+
const done = chunk.choices[0]?.finish_reason !== null && chunk.choices[0]?.finish_reason !== void 0;
|
|
79
|
+
if (text) yield { text, done: false };
|
|
80
|
+
if (done) yield { text: "", done: true };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async function anthropicChat(options) {
|
|
84
|
+
const { default: Anthropic } = await import('@anthropic-ai/sdk');
|
|
85
|
+
const config = getConfig();
|
|
86
|
+
const client = new Anthropic({ apiKey: config.ai?.apiKey || process.env.ANTHROPIC_API_KEY });
|
|
87
|
+
const messages = options.messages.filter((m) => m.role !== "system").map((m) => ({ role: m.role, content: m.content }));
|
|
88
|
+
const response = await client.messages.create({
|
|
89
|
+
model: options.model || "claude-sonnet-4-6",
|
|
90
|
+
max_tokens: options.maxTokens || 4096,
|
|
91
|
+
...options.system && { system: options.system },
|
|
92
|
+
messages,
|
|
93
|
+
...options.temperature !== void 0 && { temperature: options.temperature }
|
|
94
|
+
});
|
|
95
|
+
const textBlock = response.content.find((b) => b.type === "text");
|
|
96
|
+
return {
|
|
97
|
+
text: textBlock?.text || "",
|
|
98
|
+
usage: {
|
|
99
|
+
inputTokens: response.usage?.input_tokens || 0,
|
|
100
|
+
outputTokens: response.usage?.output_tokens || 0
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
async function* anthropicStreamChat(options) {
|
|
105
|
+
const { default: Anthropic } = await import('@anthropic-ai/sdk');
|
|
106
|
+
const config = getConfig();
|
|
107
|
+
const client = new Anthropic({ apiKey: config.ai?.apiKey || process.env.ANTHROPIC_API_KEY });
|
|
108
|
+
const messages = options.messages.filter((m) => m.role !== "system").map((m) => ({ role: m.role, content: m.content }));
|
|
109
|
+
const stream = client.messages.stream({
|
|
110
|
+
model: options.model || "claude-sonnet-4-6",
|
|
111
|
+
max_tokens: options.maxTokens || 4096,
|
|
112
|
+
...options.system && { system: options.system },
|
|
113
|
+
messages,
|
|
114
|
+
...options.temperature !== void 0 && { temperature: options.temperature }
|
|
115
|
+
});
|
|
116
|
+
for await (const event of stream) {
|
|
117
|
+
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
|
|
118
|
+
yield { text: event.delta.text, done: false };
|
|
119
|
+
} else if (event.type === "message_stop") {
|
|
120
|
+
yield { text: "", done: true };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
var ai = {
|
|
125
|
+
/**
|
|
126
|
+
* Send a chat completion request. Auto-detects OpenAI or Anthropic from env vars.
|
|
127
|
+
*/
|
|
128
|
+
async chat(options) {
|
|
129
|
+
const provider = requireProvider();
|
|
130
|
+
return provider === "openai" ? openaiChat(options) : anthropicChat(options);
|
|
131
|
+
},
|
|
132
|
+
/**
|
|
133
|
+
* Stream a chat completion. Returns an async iterable of text chunks.
|
|
134
|
+
*/
|
|
135
|
+
streamChat(options) {
|
|
136
|
+
const provider = requireProvider();
|
|
137
|
+
return provider === "openai" ? openaiStreamChat(options) : anthropicStreamChat(options);
|
|
138
|
+
},
|
|
139
|
+
/**
|
|
140
|
+
* Stream AI response directly to an Express response as SSE.
|
|
141
|
+
* Handles headers, streaming, error handling, and cleanup.
|
|
142
|
+
*/
|
|
143
|
+
async streamToSSE(res, options) {
|
|
144
|
+
res.writeHead(200, {
|
|
145
|
+
"Content-Type": "text/event-stream",
|
|
146
|
+
"Cache-Control": "no-cache",
|
|
147
|
+
Connection: "keep-alive"
|
|
148
|
+
});
|
|
149
|
+
try {
|
|
150
|
+
for await (const chunk of ai.streamChat(options)) {
|
|
151
|
+
if (chunk.text) {
|
|
152
|
+
res.write(`data: ${JSON.stringify({ text: chunk.text })}
|
|
153
|
+
|
|
154
|
+
`);
|
|
155
|
+
}
|
|
156
|
+
if (chunk.done) {
|
|
157
|
+
res.write(`data: ${JSON.stringify({ done: true })}
|
|
158
|
+
|
|
159
|
+
`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
} catch (error) {
|
|
163
|
+
res.write(`data: ${JSON.stringify({ error: error.message })}
|
|
164
|
+
|
|
165
|
+
`);
|
|
166
|
+
} finally {
|
|
167
|
+
res.end();
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
/**
|
|
171
|
+
* Generate an image (OpenAI DALL-E only).
|
|
172
|
+
*/
|
|
173
|
+
async generateImage(options) {
|
|
174
|
+
const provider = detectProvider();
|
|
175
|
+
if (provider !== "openai") {
|
|
176
|
+
throw new Error("@coduckai/sdk/ai: generateImage() requires OpenAI. Set OPENAI_API_KEY.");
|
|
177
|
+
}
|
|
178
|
+
const { default: OpenAI } = await import('openai');
|
|
179
|
+
const config = getConfig();
|
|
180
|
+
const client = new OpenAI({ apiKey: config.ai?.apiKey || process.env.OPENAI_API_KEY });
|
|
181
|
+
const response = await client.images.generate({
|
|
182
|
+
model: "dall-e-3",
|
|
183
|
+
prompt: options.prompt,
|
|
184
|
+
size: options.size || "1024x1024",
|
|
185
|
+
n: 1
|
|
186
|
+
});
|
|
187
|
+
return { url: response.data?.[0]?.url || "" };
|
|
188
|
+
},
|
|
189
|
+
/**
|
|
190
|
+
* Returns the detected AI provider ('openai' | 'anthropic' | null).
|
|
191
|
+
*/
|
|
192
|
+
getProvider() {
|
|
193
|
+
return detectProvider();
|
|
194
|
+
}
|
|
195
|
+
};
|
|
27
196
|
|
|
28
197
|
export { ai };
|
|
29
198
|
//# sourceMappingURL=index.js.map
|
package/dist/ai/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/internal/env.ts","../../src/ai/index.ts"],"names":[],"mappings":";AAgBO,SAAS,QAAA,GAAoB;AAClC,EAAA,OAAO,OAAO,MAAA,KAAW,WAAA;AAC3B;AAEO,SAAS,aAAa,UAAA,EAA0B;AACrD,EAAA,IAAI,CAAC,UAAS,EAAG;AACf,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,iBAAiB,UAAU,CAAA,2DAAA;AAAA,KAC7B;AAAA,EACF;AACF;;;ACnBA,YAAA,CAAa,IAAI,CAAA;AAEV,IAAM,EAAA,GAAK;AAAA,EAChB,IAAA,EAAM,eAAe,SAAS,CAAA;AAAA,EAC9B,UAAA,EAAY,eAAe,eAAe,CAAA;AAAA,EAC1C,WAAA,EAAa,eAAe,gBAAgB,CAAA;AAAA,EAC5C,aAAA,EAAe,eAAe,kBAAkB,CAAA;AAAA,EAChD,WAAA,EAAa,eAAe,gBAAgB;AAC9C;AAEA,SAAS,eAAe,IAAA,EAAc;AACpC,EAAA,OAAO,MAAM;AACX,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,eAAA,EAAkB,IAAI,CAAA,4CAAA,CAA8C,CAAA;AAAA,EACtF,CAAA;AACF","file":"index.js","sourcesContent":["/**\n * Environment variable helpers\n */\n\nexport function getEnv(key: string, fallback?: string): string | undefined {\n return process.env[key] || fallback;\n}\n\nexport function requireEnv(key: string): string {\n const val = process.env[key];\n if (!val) {\n throw new Error(`@coduckai/sdk: Missing required environment variable: ${key}`);\n }\n return val;\n}\n\nexport function isServer(): boolean {\n return typeof window === 'undefined';\n}\n\nexport function assertServer(moduleName: string): void {\n if (!isServer()) {\n throw new Error(\n `@coduckai/sdk/${moduleName} is server-only. Use @coduckai/sdk/client for browser code.`\n );\n }\n}\n","/**\n * @coduckai/sdk/ai — AI Providers (server-only)\n *\n * Coming in v0.2.0. Unified interface for OpenAI + Anthropic.\n */\n\nimport { assertServer } from '../internal/env';\nassertServer('ai');\n\nexport const ai = {\n chat: notImplemented('ai.chat'),\n streamChat: notImplemented('ai.streamChat'),\n streamToSSE: notImplemented('ai.streamToSSE'),\n generateImage: notImplemented('ai.generateImage'),\n getProvider: notImplemented('ai.getProvider'),\n};\n\nfunction notImplemented(name: string) {\n return () => {\n throw new Error(`@coduckai/sdk: ${name}() is not yet implemented. Coming in v0.2.0.`);\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/internal/env.ts","../../src/internal/config.ts","../../src/ai/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Environment variable helpers\n */\n\nexport function getEnv(key: string, fallback?: string): string | undefined {\n return process.env[key] || fallback;\n}\n\nexport function requireEnv(key: string): string {\n const val = process.env[key];\n if (!val) {\n throw new Error(`@coduckai/sdk: Missing required environment variable: ${key}`);\n }\n return val;\n}\n\nexport function isServer(): boolean {\n return typeof window === 'undefined';\n}\n\nexport function assertServer(moduleName: string): void {\n if (!isServer()) {\n throw new Error(\n `@coduckai/sdk/${moduleName} is server-only. Use @coduckai/sdk/client for browser code.`\n );\n }\n}\n","/**\n * Global SDK configuration\n *\n * CoDuck apps use env vars automatically — no configure() needed.\n * Standalone usage can call configure() to override.\n */\n\nexport interface SDKConfig {\n auth?: {\n jwtSecret?: string;\n jwtExpiresIn?: string;\n bcryptRounds?: number;\n };\n db?: {\n connectionString?: string;\n };\n client?: {\n baseUrl?: string;\n tokenKey?: string;\n };\n payments?: {\n stripeSecretKey?: string;\n connectedAccountId?: string;\n webhookSecret?: string;\n };\n ai?: {\n provider?: 'openai' | 'anthropic';\n apiKey?: string;\n };\n}\n\nlet globalConfig: SDKConfig = {};\n\nexport function setConfig(config: SDKConfig): void {\n globalConfig = { ...globalConfig, ...config };\n}\n\nexport function getConfig(): SDKConfig {\n return globalConfig;\n}\n","/**\n * @coduckai/sdk/ai — AI Providers (server-only)\n *\n * Unified interface for OpenAI and Anthropic. Auto-detects provider from env vars.\n * Set OPENAI_API_KEY for OpenAI or ANTHROPIC_API_KEY for Anthropic.\n */\n\nimport { assertServer } from '../internal/env';\nimport { getConfig } from '../internal/config';\n\nassertServer('ai');\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface ChatMessage {\n role: 'user' | 'assistant' | 'system';\n content: string;\n}\n\nexport interface ChatOptions {\n system?: string;\n messages: ChatMessage[];\n model?: string;\n maxTokens?: number;\n temperature?: number;\n}\n\nexport interface ChatResponse {\n text: string;\n usage: { inputTokens: number; outputTokens: number };\n}\n\nexport interface StreamChunk {\n text: string;\n done: boolean;\n}\n\nexport type AIProvider = 'openai' | 'anthropic' | null;\n\n// ---------------------------------------------------------------------------\n// Provider detection\n// ---------------------------------------------------------------------------\n\nfunction detectProvider(): AIProvider {\n const config = getConfig();\n if (config.ai?.provider) return config.ai.provider;\n if (process.env.OPENAI_API_KEY) return 'openai';\n if (process.env.ANTHROPIC_API_KEY) return 'anthropic';\n return null;\n}\n\nfunction requireProvider(): AIProvider & string {\n const provider = detectProvider();\n if (!provider) {\n throw new Error(\n '@coduckai/sdk/ai: No AI provider configured. Set OPENAI_API_KEY or ANTHROPIC_API_KEY.'\n );\n }\n return provider;\n}\n\n// ---------------------------------------------------------------------------\n// OpenAI\n// ---------------------------------------------------------------------------\n\nasync function openaiChat(options: ChatOptions): Promise<ChatResponse> {\n const { default: OpenAI } = await import('openai');\n const config = getConfig();\n const client = new OpenAI({ apiKey: config.ai?.apiKey || process.env.OPENAI_API_KEY });\n\n const messages: Array<{ role: string; content: string }> = [];\n if (options.system) {\n messages.push({ role: 'system', content: options.system });\n }\n messages.push(...options.messages);\n\n const response = await client.chat.completions.create({\n model: options.model || 'gpt-4o',\n messages: messages as any,\n max_tokens: options.maxTokens || 4096,\n temperature: options.temperature ?? 0.7,\n });\n\n return {\n text: response.choices[0]?.message?.content || '',\n usage: {\n inputTokens: response.usage?.prompt_tokens || 0,\n outputTokens: response.usage?.completion_tokens || 0,\n },\n };\n}\n\nasync function* openaiStreamChat(options: ChatOptions): AsyncGenerator<StreamChunk> {\n const { default: OpenAI } = await import('openai');\n const config = getConfig();\n const client = new OpenAI({ apiKey: config.ai?.apiKey || process.env.OPENAI_API_KEY });\n\n const messages: Array<{ role: string; content: string }> = [];\n if (options.system) {\n messages.push({ role: 'system', content: options.system });\n }\n messages.push(...options.messages);\n\n const stream = await client.chat.completions.create({\n model: options.model || 'gpt-4o',\n messages: messages as any,\n max_tokens: options.maxTokens || 4096,\n temperature: options.temperature ?? 0.7,\n stream: true,\n });\n\n for await (const chunk of stream) {\n const text = chunk.choices[0]?.delta?.content || '';\n const done = chunk.choices[0]?.finish_reason !== null && chunk.choices[0]?.finish_reason !== undefined;\n if (text) yield { text, done: false };\n if (done) yield { text: '', done: true };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Anthropic\n// ---------------------------------------------------------------------------\n\nasync function anthropicChat(options: ChatOptions): Promise<ChatResponse> {\n const { default: Anthropic } = await import('@anthropic-ai/sdk');\n const config = getConfig();\n const client = new Anthropic({ apiKey: config.ai?.apiKey || process.env.ANTHROPIC_API_KEY });\n\n const messages = options.messages\n .filter(m => m.role !== 'system')\n .map(m => ({ role: m.role as 'user' | 'assistant', content: m.content }));\n\n const response = await client.messages.create({\n model: options.model || 'claude-sonnet-4-6',\n max_tokens: options.maxTokens || 4096,\n ...(options.system && { system: options.system }),\n messages,\n ...(options.temperature !== undefined && { temperature: options.temperature }),\n });\n\n const textBlock = response.content.find((b: any) => b.type === 'text');\n\n return {\n text: (textBlock as any)?.text || '',\n usage: {\n inputTokens: response.usage?.input_tokens || 0,\n outputTokens: response.usage?.output_tokens || 0,\n },\n };\n}\n\nasync function* anthropicStreamChat(options: ChatOptions): AsyncGenerator<StreamChunk> {\n const { default: Anthropic } = await import('@anthropic-ai/sdk');\n const config = getConfig();\n const client = new Anthropic({ apiKey: config.ai?.apiKey || process.env.ANTHROPIC_API_KEY });\n\n const messages = options.messages\n .filter(m => m.role !== 'system')\n .map(m => ({ role: m.role as 'user' | 'assistant', content: m.content }));\n\n const stream = client.messages.stream({\n model: options.model || 'claude-sonnet-4-6',\n max_tokens: options.maxTokens || 4096,\n ...(options.system && { system: options.system }),\n messages,\n ...(options.temperature !== undefined && { temperature: options.temperature }),\n });\n\n for await (const event of stream) {\n if (event.type === 'content_block_delta' && (event.delta as any).type === 'text_delta') {\n yield { text: (event.delta as any).text, done: false };\n } else if (event.type === 'message_stop') {\n yield { text: '', done: true };\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// AI API\n// ---------------------------------------------------------------------------\n\nexport const ai = {\n /**\n * Send a chat completion request. Auto-detects OpenAI or Anthropic from env vars.\n */\n async chat(options: ChatOptions): Promise<ChatResponse> {\n const provider = requireProvider();\n return provider === 'openai' ? openaiChat(options) : anthropicChat(options);\n },\n\n /**\n * Stream a chat completion. Returns an async iterable of text chunks.\n */\n streamChat(options: ChatOptions): AsyncGenerator<StreamChunk> {\n const provider = requireProvider();\n return provider === 'openai' ? openaiStreamChat(options) : anthropicStreamChat(options);\n },\n\n /**\n * Stream AI response directly to an Express response as SSE.\n * Handles headers, streaming, error handling, and cleanup.\n */\n async streamToSSE(res: any, options: ChatOptions): Promise<void> {\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n });\n\n try {\n for await (const chunk of ai.streamChat(options)) {\n if (chunk.text) {\n res.write(`data: ${JSON.stringify({ text: chunk.text })}\\n\\n`);\n }\n if (chunk.done) {\n res.write(`data: ${JSON.stringify({ done: true })}\\n\\n`);\n }\n }\n } catch (error: any) {\n res.write(`data: ${JSON.stringify({ error: error.message })}\\n\\n`);\n } finally {\n res.end();\n }\n },\n\n /**\n * Generate an image (OpenAI DALL-E only).\n */\n async generateImage(options: { prompt: string; size?: string }): Promise<{ url: string }> {\n const provider = detectProvider();\n if (provider !== 'openai') {\n throw new Error('@coduckai/sdk/ai: generateImage() requires OpenAI. Set OPENAI_API_KEY.');\n }\n\n const { default: OpenAI } = await import('openai');\n const config = getConfig();\n const client = new OpenAI({ apiKey: config.ai?.apiKey || process.env.OPENAI_API_KEY });\n\n const response = await client.images.generate({\n model: 'dall-e-3',\n prompt: options.prompt,\n size: (options.size as any) || '1024x1024',\n n: 1,\n });\n\n return { url: response.data?.[0]?.url || '' };\n },\n\n /**\n * Returns the detected AI provider ('openai' | 'anthropic' | null).\n */\n getProvider(): AIProvider {\n return detectProvider();\n },\n};\n"]}
|