@stacksjs/ai 0.70.87 → 0.70.90

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,276 @@
1
+ import { fetchWithRetry } from "../../utils/retry";
2
+ import { recordUsage } from "../../utils/usage";
3
+ import { normalizeMessagesForProvider } from "../../utils/vision";
4
+ const DEFAULT_MODEL = "claude-sonnet-4-20250514", DEFAULT_MAX_TOKENS = 4096, DEFAULT_VERSION = "2023-06-01", BASE_URL = "https://api.anthropic.com/v1";
5
+ let globalConfig = null;
6
+ export function configure(config) {
7
+ globalConfig = config;
8
+ }
9
+ function getConfig(config) {
10
+ const merged = { ...globalConfig, ...config };
11
+ if (!merged.apiKey)
12
+ merged.apiKey = process.env.ANTHROPIC_API_KEY || "";
13
+ return merged;
14
+ }
15
+ export function createAnthropicDriver(config) {
16
+ const {
17
+ apiKey,
18
+ model = DEFAULT_MODEL,
19
+ maxTokens = DEFAULT_MAX_TOKENS,
20
+ anthropicVersion = DEFAULT_VERSION
21
+ } = config;
22
+ return {
23
+ name: "Claude API",
24
+ async process(command, systemPrompt, history) {
25
+ if (!apiKey)
26
+ throw Error("Anthropic API key not set. Configure your API key in settings.");
27
+ const response = await fetchWithRetry(`${BASE_URL}/messages`, {
28
+ method: "POST",
29
+ headers: {
30
+ "Content-Type": "application/json",
31
+ "x-api-key": apiKey,
32
+ "anthropic-version": anthropicVersion
33
+ },
34
+ body: JSON.stringify({
35
+ model,
36
+ max_tokens: maxTokens,
37
+ system: systemPrompt,
38
+ messages: [...history, { role: "user", content: command }]
39
+ })
40
+ });
41
+ if (!response.ok) {
42
+ const error = await response.text();
43
+ throw Error(`Claude API error: ${error}`);
44
+ }
45
+ const data = await response.json();
46
+ if (!data.content || data.content.length === 0)
47
+ throw Error("Claude API returned empty content");
48
+ return data.content[0].text;
49
+ },
50
+ async* stream(command, systemPrompt, history) {
51
+ if (!apiKey)
52
+ throw Error("Anthropic API key not set. Configure your API key in settings.");
53
+ const response = await fetch(`${BASE_URL}/messages`, {
54
+ method: "POST",
55
+ headers: {
56
+ "Content-Type": "application/json",
57
+ "x-api-key": apiKey,
58
+ "anthropic-version": anthropicVersion
59
+ },
60
+ body: JSON.stringify({
61
+ model,
62
+ max_tokens: maxTokens,
63
+ system: systemPrompt,
64
+ stream: !0,
65
+ messages: [...history, { role: "user", content: command }]
66
+ })
67
+ });
68
+ if (!response.ok) {
69
+ const error = await response.text();
70
+ throw Error(`Claude API error: ${error}`);
71
+ }
72
+ const reader = response.body?.getReader();
73
+ if (!reader)
74
+ throw Error("No response body");
75
+ const decoder = new TextDecoder;
76
+ let buffer = "";
77
+ const handlePayload = function* (data) {
78
+ if (data === "[DONE]")
79
+ return;
80
+ let event;
81
+ try {
82
+ event = JSON.parse(data);
83
+ } catch {
84
+ return;
85
+ }
86
+ if (event.type === "error") {
87
+ const msg = event.error?.message ?? JSON.stringify(event.error ?? event);
88
+ throw Error(`[anthropic/stream] mid-stream error: ${msg}`);
89
+ }
90
+ if (event.type === "content_block_delta" && event.delta?.text)
91
+ yield event.delta.text;
92
+ };
93
+ while (!0) {
94
+ const { done, value } = await reader.read();
95
+ if (done)
96
+ break;
97
+ buffer += decoder.decode(value, { stream: !0 });
98
+ const lines = buffer.split(`
99
+ `);
100
+ buffer = lines.pop() || "";
101
+ for (const line of lines)
102
+ if (line.startsWith("data: "))
103
+ yield* handlePayload(line.slice(6));
104
+ }
105
+ if (buffer.startsWith("data: "))
106
+ yield* handlePayload(buffer.slice(6));
107
+ }
108
+ };
109
+ }
110
+ export async function chat(messages, options = {}) {
111
+ const config = getConfig(), {
112
+ model = DEFAULT_MODEL,
113
+ maxTokens = DEFAULT_MAX_TOKENS,
114
+ temperature,
115
+ topP,
116
+ stop,
117
+ system,
118
+ tools,
119
+ toolChoice,
120
+ responseFormat
121
+ } = options, normalizedMessages = normalizeMessagesForProvider(messages, "anthropic"), startedAt = Date.now(), body = {
122
+ model,
123
+ max_tokens: maxTokens,
124
+ temperature,
125
+ top_p: topP,
126
+ stop_sequences: stop ? Array.isArray(stop) ? stop : [stop] : void 0,
127
+ system,
128
+ messages: normalizedMessages
129
+ };
130
+ if (tools && tools.length > 0) {
131
+ body.tools = tools.map((t) => ({
132
+ name: t.name,
133
+ description: t.description,
134
+ input_schema: t.parameters ?? { type: "object", properties: {} }
135
+ }));
136
+ if (toolChoice !== void 0)
137
+ body.tool_choice = mapAnthropicToolChoice(toolChoice);
138
+ }
139
+ if (responseFormat && responseFormat.type !== "text") {
140
+ const outputTool = buildAnthropicJsonTool(responseFormat), existing = Array.isArray(body.tools) ? body.tools : [];
141
+ body.tools = [...existing, outputTool];
142
+ if (toolChoice === void 0)
143
+ body.tool_choice = { type: "tool", name: outputTool.name };
144
+ }
145
+ const response = await fetch(`${BASE_URL}/messages`, {
146
+ method: "POST",
147
+ headers: {
148
+ "Content-Type": "application/json",
149
+ "x-api-key": config.apiKey,
150
+ "anthropic-version": config.anthropicVersion || DEFAULT_VERSION
151
+ },
152
+ body: JSON.stringify(body)
153
+ });
154
+ if (!response.ok) {
155
+ const error = await response.text();
156
+ throw Error(`Claude API error: ${error}`);
157
+ }
158
+ const data = await response.json();
159
+ if (!data.content || data.content.length === 0)
160
+ throw Error("Claude API returned empty content");
161
+ const block = data.content.find((b) => b.type === "tool_use") ?? data.content.find((b) => b.type === "text") ?? data.content[0], result = {
162
+ content: block?.type === "tool_use" ? JSON.stringify(block.input) : block?.text ?? "",
163
+ model: data.model,
164
+ usage: {
165
+ promptTokens: data.usage?.input_tokens || 0,
166
+ completionTokens: data.usage?.output_tokens || 0,
167
+ totalTokens: (data.usage?.input_tokens || 0) + (data.usage?.output_tokens || 0)
168
+ },
169
+ finishReason: data.stop_reason
170
+ };
171
+ recordUsage({
172
+ provider: "anthropic",
173
+ model: data.model,
174
+ promptTokens: result.usage.promptTokens,
175
+ completionTokens: result.usage.completionTokens,
176
+ totalTokens: result.usage.totalTokens,
177
+ durationMs: Date.now() - startedAt,
178
+ timestamp: Date.now()
179
+ });
180
+ return result;
181
+ }
182
+ function mapAnthropicToolChoice(choice) {
183
+ if (choice === "auto")
184
+ return { type: "auto" };
185
+ if (choice === "required")
186
+ return { type: "any" };
187
+ if (choice === "none")
188
+ return { type: "auto", disable_parallel_tool_use: !0 };
189
+ return { type: "tool", name: choice.name };
190
+ }
191
+ function buildAnthropicJsonTool(format) {
192
+ if (format.type === "json_schema")
193
+ return {
194
+ name: format.json_schema.name,
195
+ description: `Returns the result as JSON matching the '${format.json_schema.name}' schema.`,
196
+ input_schema: format.json_schema.schema
197
+ };
198
+ return {
199
+ name: "structured_output",
200
+ description: "Returns the result as a JSON object.",
201
+ input_schema: { type: "object", additionalProperties: !0 }
202
+ };
203
+ }
204
+ export async function* streamChat(messages, options = {}) {
205
+ const config = getConfig(), {
206
+ model = DEFAULT_MODEL,
207
+ maxTokens = DEFAULT_MAX_TOKENS,
208
+ temperature,
209
+ topP,
210
+ stop,
211
+ system
212
+ } = options, response = await fetch(`${BASE_URL}/messages`, {
213
+ method: "POST",
214
+ headers: {
215
+ "Content-Type": "application/json",
216
+ "x-api-key": config.apiKey,
217
+ "anthropic-version": config.anthropicVersion || DEFAULT_VERSION
218
+ },
219
+ body: JSON.stringify({
220
+ model,
221
+ max_tokens: maxTokens,
222
+ temperature,
223
+ top_p: topP,
224
+ stop_sequences: stop ? Array.isArray(stop) ? stop : [stop] : void 0,
225
+ system,
226
+ stream: !0,
227
+ messages: normalizeMessagesForProvider(messages, "anthropic")
228
+ })
229
+ });
230
+ if (!response.ok) {
231
+ const error = await response.text();
232
+ throw Error(`Claude API error: ${error}`);
233
+ }
234
+ const reader = response.body?.getReader();
235
+ if (!reader)
236
+ throw Error("No response body");
237
+ const decoder = new TextDecoder;
238
+ let buffer = "";
239
+ while (!0) {
240
+ const { done, value } = await reader.read();
241
+ if (done)
242
+ break;
243
+ buffer += decoder.decode(value, { stream: !0 });
244
+ const lines = buffer.split(`
245
+ `);
246
+ buffer = lines.pop() || "";
247
+ for (const line of lines)
248
+ if (line.startsWith("data: ")) {
249
+ const data = line.slice(6);
250
+ if (data === "[DONE]")
251
+ continue;
252
+ try {
253
+ const event = JSON.parse(data);
254
+ if (event.type === "content_block_delta" && event.delta?.text)
255
+ yield event.delta.text;
256
+ } catch {}
257
+ }
258
+ }
259
+ }
260
+ export async function prompt(text, options = {}) {
261
+ return (await chat([{ role: "user", content: text }], options)).content;
262
+ }
263
+ export function estimateTokens(text) {
264
+ return Math.ceil(text.length / 4);
265
+ }
266
+ export const anthropicDriver = {
267
+ create: createAnthropicDriver
268
+ }, anthropic = {
269
+ configure,
270
+ chat,
271
+ streamChat,
272
+ prompt,
273
+ estimateTokens,
274
+ createDriver: createAnthropicDriver
275
+ };
276
+ export default anthropic;
@@ -0,0 +1,198 @@
1
+ let sdkModule = null;
2
+ async function getSDK() {
3
+ if (!sdkModule)
4
+ try {
5
+ sdkModule = await import("@anthropic-ai/claude-agent-sdk");
6
+ } catch {
7
+ throw Error("Claude Agent SDK not installed. Run: bun add @anthropic-ai/claude-agent-sdk");
8
+ }
9
+ return sdkModule;
10
+ }
11
+ const sdkState = {
12
+ lastSessionId: void 0
13
+ }, DEFAULT_CONFIG = {
14
+ maxTurns: 25,
15
+ allowedTools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep"],
16
+ permissionMode: "bypassPermissions"
17
+ };
18
+ export function createClaudeAgentSDKDriver(config = {}) {
19
+ const {
20
+ maxTurns = DEFAULT_CONFIG.maxTurns,
21
+ cwd,
22
+ allowedTools = DEFAULT_CONFIG.allowedTools,
23
+ disallowedTools,
24
+ permissionMode = DEFAULT_CONFIG.permissionMode,
25
+ customSystemPrompt,
26
+ appendSystemPrompt,
27
+ resumeSessionId
28
+ } = config;
29
+ return {
30
+ name: "Claude Agent SDK",
31
+ async process(command, systemPrompt, _history) {
32
+ const sdk = await getSDK(), { query } = sdk, fullPrompt = systemPrompt ? `${systemPrompt}
33
+
34
+ User request: ${command}` : command, options = {
35
+ allowedTools,
36
+ permissionMode,
37
+ maxTurns
38
+ };
39
+ if (disallowedTools)
40
+ options.disallowedTools = disallowedTools;
41
+ if (customSystemPrompt)
42
+ options.customSystemPrompt = customSystemPrompt;
43
+ if (appendSystemPrompt)
44
+ options.appendSystemPrompt = appendSystemPrompt;
45
+ if (cwd)
46
+ options.cwd = cwd;
47
+ if (resumeSessionId || sdkState.lastSessionId)
48
+ options.resume = resumeSessionId || sdkState.lastSessionId;
49
+ let result = "";
50
+ try {
51
+ for await (const message of query({ prompt: fullPrompt, options })) {
52
+ if (message.type === "system" && message.subtype === "init")
53
+ sdkState.lastSessionId = message.session_id;
54
+ if ("result" in message && typeof message.result === "string")
55
+ result = message.result;
56
+ }
57
+ return result || "No response from Claude Agent SDK";
58
+ } catch (error) {
59
+ const err = error;
60
+ if (err.message.includes("ANTHROPIC_API_KEY"))
61
+ throw Error("Claude Agent SDK requires ANTHROPIC_API_KEY environment variable or Claude Code authentication.");
62
+ throw Error(`Claude Agent SDK error: ${err.message}`);
63
+ }
64
+ },
65
+ async* stream(command, systemPrompt, _history) {
66
+ const sdk = await getSDK(), { query } = sdk, fullPrompt = systemPrompt ? `${systemPrompt}
67
+
68
+ User request: ${command}` : command, options = {
69
+ allowedTools,
70
+ permissionMode,
71
+ maxTurns
72
+ };
73
+ if (disallowedTools)
74
+ options.disallowedTools = disallowedTools;
75
+ if (customSystemPrompt)
76
+ options.customSystemPrompt = customSystemPrompt;
77
+ if (appendSystemPrompt)
78
+ options.appendSystemPrompt = appendSystemPrompt;
79
+ if (cwd)
80
+ options.cwd = cwd;
81
+ if (resumeSessionId || sdkState.lastSessionId)
82
+ options.resume = resumeSessionId || sdkState.lastSessionId;
83
+ try {
84
+ for await (const message of query({ prompt: fullPrompt, options })) {
85
+ if (message.type === "system" && message.subtype === "init")
86
+ sdkState.lastSessionId = message.session_id;
87
+ if (message.type === "assistant") {
88
+ const assistantMsg = message;
89
+ if (assistantMsg.message?.content) {
90
+ for (const block of assistantMsg.message.content)
91
+ if (block.type === "text" && block.text)
92
+ yield block.text;
93
+ }
94
+ }
95
+ if ("result" in message && typeof message.result === "string")
96
+ yield message.result;
97
+ }
98
+ } catch (error) {
99
+ const err = error;
100
+ if (err.message.includes("ANTHROPIC_API_KEY"))
101
+ throw Error("Claude Agent SDK requires ANTHROPIC_API_KEY environment variable or Claude Code authentication.");
102
+ throw Error(`Claude Agent SDK error: ${err.message}`);
103
+ }
104
+ }
105
+ };
106
+ }
107
+ export async function processStreaming(command, cwd, config = {}) {
108
+ const sdk = await getSDK(), { query } = sdk, {
109
+ maxTurns = DEFAULT_CONFIG.maxTurns,
110
+ allowedTools = DEFAULT_CONFIG.allowedTools,
111
+ disallowedTools,
112
+ permissionMode = DEFAULT_CONFIG.permissionMode,
113
+ customSystemPrompt,
114
+ appendSystemPrompt,
115
+ resumeSessionId
116
+ } = config, options = {
117
+ allowedTools,
118
+ permissionMode,
119
+ maxTurns
120
+ };
121
+ if (disallowedTools)
122
+ options.disallowedTools = disallowedTools;
123
+ if (customSystemPrompt)
124
+ options.customSystemPrompt = customSystemPrompt;
125
+ if (appendSystemPrompt)
126
+ options.appendSystemPrompt = appendSystemPrompt;
127
+ if (cwd)
128
+ options.cwd = cwd;
129
+ if (resumeSessionId || sdkState.lastSessionId)
130
+ options.resume = resumeSessionId || sdkState.lastSessionId;
131
+ const encoder = new TextEncoder;
132
+ let fullResponse = "", resolveFullResponse;
133
+ const fullResponsePromise = new Promise((resolve) => {
134
+ resolveFullResponse = resolve;
135
+ });
136
+ return {
137
+ stream: new ReadableStream({
138
+ async start(controller) {
139
+ try {
140
+ for await (const message of query({ prompt: command, options })) {
141
+ if (message.type === "system" && message.subtype === "init")
142
+ sdkState.lastSessionId = message.session_id;
143
+ if (message.type === "assistant") {
144
+ const assistantMsg = message;
145
+ if (assistantMsg.message?.content) {
146
+ for (const block of assistantMsg.message.content)
147
+ if (block.type === "text" && block.text) {
148
+ fullResponse += block.text;
149
+ controller.enqueue(encoder.encode(block.text));
150
+ }
151
+ }
152
+ }
153
+ if ("result" in message && typeof message.result === "string") {
154
+ if (!fullResponse) {
155
+ fullResponse = message.result;
156
+ controller.enqueue(encoder.encode(message.result));
157
+ }
158
+ }
159
+ }
160
+ resolveFullResponse(fullResponse);
161
+ controller.close();
162
+ } catch (error) {
163
+ resolveFullResponse(fullResponse);
164
+ controller.error(error);
165
+ }
166
+ }
167
+ }),
168
+ fullResponse: fullResponsePromise
169
+ };
170
+ }
171
+ export async function resumeSession(sessionId, prompt) {
172
+ const sdk = await getSDK(), { query } = sdk;
173
+ let result = "";
174
+ for await (const message of query({
175
+ prompt,
176
+ options: {
177
+ resume: sessionId,
178
+ permissionMode: "bypassPermissions"
179
+ }
180
+ }))
181
+ if ("result" in message && typeof message.result === "string")
182
+ result = message.result;
183
+ return result || "No response from resumed session";
184
+ }
185
+ export function getLastSessionId() {
186
+ return sdkState.lastSessionId;
187
+ }
188
+ export function clearSession() {
189
+ sdkState.lastSessionId = void 0;
190
+ }
191
+ export const claudeAgentSDK = {
192
+ createDriver: createClaudeAgentSDKDriver,
193
+ processStreaming,
194
+ resumeSession,
195
+ getLastSessionId,
196
+ clearSession
197
+ };
198
+ export default claudeAgentSDK;
@@ -0,0 +1,4 @@
1
+ export { createAnthropicDriver, anthropicDriver, anthropic, estimateTokens } from "./anthropic";
2
+ export { createOpenAIDriver, openaiDriver, openai } from "./openai";
3
+ export { createOllamaDriver, ollamaDriver, ollama } from "./ollama";
4
+ export { createClaudeAgentSDKDriver, claudeAgentSDK, getLastSessionId, clearSession } from "./claude-agent-sdk";