call-ai 0.8.3 → 0.8.5-dev-preview

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,40 @@
1
+ /**
2
+ * Core API implementation for call-ai
3
+ */
4
+ import { CallAIOptions, Message, StreamResponse, ThenableStreamResponse } from "./types";
5
+ declare const PACKAGE_VERSION: any;
6
+ /**
7
+ * Main API interface function for making AI API calls
8
+ *
9
+ * @param prompt The prompt to send to the AI, either a string or a Message array
10
+ * @param options Configuration options for the API call
11
+ * @returns Promise<string> for non-streaming or AsyncGenerator for streaming
12
+ */
13
+ declare function callAI(prompt: string | Message[], options?: CallAIOptions): ThenableStreamResponse | Promise<string>;
14
+ /**
15
+ * Buffers the results of a streaming generator into a single string
16
+ *
17
+ * @param generator The streaming generator returned by callAI
18
+ * @returns Promise<string> with the complete response
19
+ */
20
+ declare function bufferStreamingResults(generator: AsyncGenerator<string, string, unknown>): Promise<string>;
21
+ /**
22
+ * Create a proxy that acts both as a Promise and an AsyncGenerator for backward compatibility
23
+ * @internal This is for internal use only, not part of public API
24
+ */
25
+ declare function createBackwardCompatStreamingProxy(promise: Promise<StreamResponse>): ThenableStreamResponse;
26
+ /**
27
+ * Validates and prepares request parameters for API calls
28
+ *
29
+ * @param prompt User prompt (string or Message array)
30
+ * @param options Call options
31
+ * @returns Validated and processed parameters including apiKey
32
+ */
33
+ declare function prepareRequestParams(prompt: string | Message[], options?: CallAIOptions): {
34
+ messages: Message[] | {
35
+ role: string;
36
+ content: string;
37
+ }[];
38
+ apiKey: any;
39
+ };
40
+ export { callAI, bufferStreamingResults, createBackwardCompatStreamingProxy, prepareRequestParams, PACKAGE_VERSION, };
@@ -0,0 +1,313 @@
1
+ "use strict";
2
+ /**
3
+ * Core API implementation for call-ai
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.PACKAGE_VERSION = void 0;
7
+ exports.callAI = callAI;
8
+ exports.bufferStreamingResults = bufferStreamingResults;
9
+ exports.createBackwardCompatStreamingProxy = createBackwardCompatStreamingProxy;
10
+ exports.prepareRequestParams = prepareRequestParams;
11
+ const key_management_1 = require("./key-management");
12
+ const non_streaming_1 = require("./non-streaming");
13
+ const streaming_1 = require("./streaming");
14
+ // Import package version for debugging
15
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
16
+ const PACKAGE_VERSION = require("../package.json").version;
17
+ exports.PACKAGE_VERSION = PACKAGE_VERSION;
18
+ /**
19
+ * Main API interface function for making AI API calls
20
+ *
21
+ * @param prompt The prompt to send to the AI, either a string or a Message array
22
+ * @param options Configuration options for the API call
23
+ * @returns Promise<string> for non-streaming or AsyncGenerator for streaming
24
+ */
25
+ // Main API interface function - must match original signature exactly
26
+ function callAI(prompt, options = {}) {
27
+ // Use the global debug flag if not specified in options
28
+ const debug = options.debug === undefined ? key_management_1.globalDebug : options.debug;
29
+ // Validate and prepare parameters (including API key validation)
30
+ prepareRequestParams(prompt, options);
31
+ // Handle schema strategy based on model or explicitly provided strategy
32
+ let schemaStrategy = {
33
+ strategy: "none",
34
+ model: options.model || "openai/gpt-3.5-turbo",
35
+ prepareRequest: () => ({}),
36
+ processResponse: (response) => {
37
+ // If response is an object, stringify it to match expected test output
38
+ if (response && typeof response === "object") {
39
+ return JSON.stringify(response);
40
+ }
41
+ return response;
42
+ },
43
+ shouldForceStream: false,
44
+ };
45
+ // If a schema is provided, determine the appropriate strategy
46
+ if (options.schema) {
47
+ const model = options.model || "openai/gpt-3.5-turbo";
48
+ // Choose function calling strategy based on model
49
+ if (/claude/i.test(model) || /anthropic/i.test(model)) {
50
+ schemaStrategy = {
51
+ strategy: "tool_mode",
52
+ model,
53
+ shouldForceStream: false,
54
+ prepareRequest: (schema) => {
55
+ // Parse the schema to extract the function definition
56
+ let toolDef = {};
57
+ if (typeof schema === "string") {
58
+ try {
59
+ toolDef = JSON.parse(schema);
60
+ }
61
+ catch (e) {
62
+ // If it's not valid JSON, we'll use it as a plain description
63
+ toolDef = { description: schema };
64
+ }
65
+ }
66
+ else if (schema) {
67
+ toolDef = schema;
68
+ }
69
+ // Build a tools array compatible with Claude's format
70
+ const tools = [
71
+ {
72
+ type: "function",
73
+ function: {
74
+ name: toolDef.name || "execute_function",
75
+ description: toolDef.description || "Execute a function",
76
+ parameters: toolDef.parameters || {
77
+ type: "object",
78
+ properties: {},
79
+ },
80
+ },
81
+ },
82
+ ];
83
+ return {
84
+ tools,
85
+ tool_choice: {
86
+ type: "function",
87
+ function: { name: tools[0].function.name },
88
+ },
89
+ };
90
+ },
91
+ processResponse: (response) => {
92
+ // Handle different response formats
93
+ if (typeof response === "string") {
94
+ return response;
95
+ }
96
+ // Handle direct tool_use format
97
+ if (response && response.type === "tool_use") {
98
+ return response.input || "{}";
99
+ }
100
+ // Handle object with tool_use property
101
+ if (response && response.tool_use) {
102
+ return response.tool_use.input || "{}";
103
+ }
104
+ // Handle array of tool calls (OpenAI format)
105
+ if (Array.isArray(response)) {
106
+ if (response.length > 0 &&
107
+ response[0].function &&
108
+ response[0].function.arguments) {
109
+ return response[0].function.arguments;
110
+ }
111
+ }
112
+ // For all other cases, return string representation
113
+ return typeof response === "string"
114
+ ? response
115
+ : JSON.stringify(response);
116
+ },
117
+ };
118
+ }
119
+ else {
120
+ // For OpenAI compatible models, use json_schema format
121
+ schemaStrategy = {
122
+ strategy: "json_schema",
123
+ model,
124
+ shouldForceStream: false,
125
+ prepareRequest: (schema) => {
126
+ // Create a properly formatted JSON schema request
127
+ const schemaObj = schema || {};
128
+ return {
129
+ response_format: {
130
+ type: "json_schema",
131
+ json_schema: {
132
+ name: schemaObj.name || "result",
133
+ schema: {
134
+ type: "object",
135
+ properties: schemaObj.properties || {},
136
+ required: schemaObj.required ||
137
+ Object.keys(schemaObj.properties || {}),
138
+ additionalProperties: schemaObj.additionalProperties !== undefined
139
+ ? schemaObj.additionalProperties
140
+ : false,
141
+ },
142
+ },
143
+ },
144
+ };
145
+ },
146
+ processResponse: (response) => {
147
+ // Handle different response formats
148
+ if (typeof response === "string") {
149
+ // Keep string responses as is
150
+ return response;
151
+ }
152
+ // If it's an object, convert to string to match test expectations
153
+ return JSON.stringify(response);
154
+ },
155
+ };
156
+ }
157
+ }
158
+ // Check if this should be a streaming or non-streaming call
159
+ if (options.stream) {
160
+ if (debug) {
161
+ console.log(`[callAI:${PACKAGE_VERSION}] Making streaming request`);
162
+ }
163
+ // Handle streaming mode - return a Promise that resolves to an AsyncGenerator
164
+ // but also supports legacy non-awaited usage for backward compatibility
165
+ const streamPromise = (async () => {
166
+ // This exact pattern matches the original implementation in api.ts
167
+ return (0, streaming_1.callAIStreaming)(prompt, {
168
+ ...options,
169
+ schemaStrategy,
170
+ });
171
+ })();
172
+ // Create a proxy object that acts both as a Promise and an AsyncGenerator for backward compatibility
173
+ // @ts-ignore - We're deliberately implementing a proxy with dual behavior
174
+ return createBackwardCompatStreamingProxy(streamPromise);
175
+ }
176
+ else {
177
+ if (debug) {
178
+ console.log(`[callAI:${PACKAGE_VERSION}] Making non-streaming request`);
179
+ }
180
+ // Pass schemaStrategy through options to avoid type error
181
+ const optionsWithSchema = {
182
+ ...options,
183
+ schemaStrategy,
184
+ };
185
+ // Make a non-streaming API call
186
+ return (0, non_streaming_1.callAINonStreaming)(prompt, optionsWithSchema);
187
+ }
188
+ }
189
+ /**
190
+ * Buffers the results of a streaming generator into a single string
191
+ *
192
+ * @param generator The streaming generator returned by callAI
193
+ * @returns Promise<string> with the complete response
194
+ */
195
+ async function bufferStreamingResults(generator) {
196
+ let result = "";
197
+ try {
198
+ // Iterate through the generator and collect results
199
+ for await (const chunk of generator) {
200
+ result += chunk;
201
+ }
202
+ return result;
203
+ }
204
+ catch (error) {
205
+ // If we already collected some content, attach it to the error
206
+ if (error instanceof Error) {
207
+ const enhancedError = new Error(`${error.message} (Partial content: ${result.slice(0, 100)}...)`);
208
+ enhancedError.partialContent = result;
209
+ enhancedError.originalError = error;
210
+ throw enhancedError;
211
+ }
212
+ else {
213
+ // For non-Error objects, create an Error with info
214
+ const newError = new Error(`Streaming error: ${String(error)}`);
215
+ newError.partialContent = result;
216
+ newError.originalError = error;
217
+ throw newError;
218
+ }
219
+ }
220
+ }
221
+ /**
222
+ * Create a proxy that acts both as a Promise and an AsyncGenerator for backward compatibility
223
+ * @internal This is for internal use only, not part of public API
224
+ */
225
+ function createBackwardCompatStreamingProxy(promise) {
226
+ // Create a proxy that forwards methods to the Promise or AsyncGenerator as appropriate
227
+ return new Proxy({}, {
228
+ get(_target, prop) {
229
+ // First check if it's an AsyncGenerator method (needed for for-await)
230
+ if (prop === "next" ||
231
+ prop === "throw" ||
232
+ prop === "return" ||
233
+ prop === Symbol.asyncIterator) {
234
+ // Create wrapper functions that await the Promise first
235
+ if (prop === Symbol.asyncIterator) {
236
+ return function () {
237
+ return {
238
+ // Implement async iterator that gets the generator first
239
+ async next(value) {
240
+ try {
241
+ const generator = await promise;
242
+ return generator.next(value);
243
+ }
244
+ catch (error) {
245
+ // Turn Promise rejection into iterator result with error thrown
246
+ return Promise.reject(error);
247
+ }
248
+ },
249
+ };
250
+ };
251
+ }
252
+ // Methods like next, throw, return
253
+ return async function (value) {
254
+ const generator = await promise;
255
+ return generator[prop](value);
256
+ };
257
+ }
258
+ // Then check if it's a Promise method
259
+ if (prop === "then" || prop === "catch" || prop === "finally") {
260
+ return promise[prop].bind(promise);
261
+ }
262
+ return undefined;
263
+ },
264
+ });
265
+ }
266
+ /**
267
+ * Validates and prepares request parameters for API calls
268
+ *
269
+ * @param prompt User prompt (string or Message array)
270
+ * @param options Call options
271
+ * @returns Validated and processed parameters including apiKey
272
+ */
273
+ function prepareRequestParams(prompt, options = {}) {
274
+ // Get API key from options or window.CALLAI_API_KEY (exactly matching original)
275
+ const apiKey = options.apiKey ||
276
+ (typeof window !== "undefined" ? window.CALLAI_API_KEY : null);
277
+ // Validate API key with original error message
278
+ if (!apiKey) {
279
+ throw new Error("API key is required. Provide it via options.apiKey or set window.CALLAI_API_KEY");
280
+ }
281
+ // Validate and process input parameters
282
+ if (!prompt || (typeof prompt !== "string" && !Array.isArray(prompt))) {
283
+ throw new Error(`Invalid prompt: ${prompt}. Must be a string or an array of message objects.`);
284
+ }
285
+ // Convert simple string prompts to message array format
286
+ const messages = Array.isArray(prompt)
287
+ ? prompt
288
+ : [{ role: "user", content: prompt }];
289
+ // Validate message structure if array provided
290
+ if (Array.isArray(prompt)) {
291
+ for (const message of prompt) {
292
+ if (!message.role || !message.content) {
293
+ throw new Error(`Invalid message format. Each message must have 'role' and 'content' properties. Received: ${JSON.stringify(message)}`);
294
+ }
295
+ if (typeof message.role !== "string" ||
296
+ (typeof message.content !== "string" && !Array.isArray(message.content))) {
297
+ throw new Error(`Invalid message format. 'role' must be a string and 'content' must be a string or array. Received role: ${typeof message.role}, content: ${typeof message.content}`);
298
+ }
299
+ }
300
+ }
301
+ // If provider-specific options are given, check for conflicts
302
+ if (options.provider &&
303
+ options.provider !== "auto" &&
304
+ options.model &&
305
+ !options.model.startsWith(options.provider + "/")) {
306
+ console.warn(`[callAI:${PACKAGE_VERSION}] WARNING: Specified provider '${options.provider}' doesn't match model '${options.model}'. Using model as specified.`);
307
+ }
308
+ // Return the validated parameters including API key
309
+ return {
310
+ messages,
311
+ apiKey,
312
+ };
313
+ }
package/dist/api.d.ts CHANGED
@@ -2,6 +2,8 @@
2
2
  * Core API implementation for call-ai
3
3
  */
4
4
  import { CallAIOptions, Message, StreamResponse } from "./types";
5
+ import { getMeta } from "./response-metadata";
6
+ export { getMeta };
5
7
  /**
6
8
  * Make an AI API call with the given options
7
9
  * @param prompt User prompt as string or an array of message objects