opentool 0.5.0 → 0.6.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.
- package/README.md +84 -22
- package/dist/ai/index.d.ts +237 -0
- package/dist/ai/index.js +759 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/cli/index.d.ts +38 -5
- package/dist/cli/index.js +2218 -67
- package/dist/cli/index.js.map +1 -1
- package/dist/index-D3DaM5Rs.d.ts +1693 -0
- package/dist/index.d.ts +33 -5
- package/dist/index.js +3258 -25
- package/dist/index.js.map +1 -1
- package/dist/payment/index.d.ts +2 -0
- package/dist/payment/index.js +969 -0
- package/dist/payment/index.js.map +1 -0
- package/dist/{types/metadata.d.ts → validate-DiIOFUU5.d.ts} +262 -415
- package/dist/wallets/index.d.ts +117 -0
- package/dist/wallets/index.js +337 -0
- package/dist/wallets/index.js.map +1 -0
- package/package.json +35 -4
- package/dist/cli/build.d.ts +0 -23
- package/dist/cli/build.d.ts.map +0 -1
- package/dist/cli/build.js +0 -223
- package/dist/cli/build.js.map +0 -1
- package/dist/cli/dev.d.ts +0 -6
- package/dist/cli/dev.d.ts.map +0 -1
- package/dist/cli/dev.js +0 -123
- package/dist/cli/dev.js.map +0 -1
- package/dist/cli/generate-metadata.d.ts +0 -15
- package/dist/cli/generate-metadata.d.ts.map +0 -1
- package/dist/cli/generate-metadata.js +0 -90
- package/dist/cli/generate-metadata.js.map +0 -1
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/shared/metadata.d.ts +0 -19
- package/dist/cli/shared/metadata.d.ts.map +0 -1
- package/dist/cli/shared/metadata.js +0 -283
- package/dist/cli/shared/metadata.js.map +0 -1
- package/dist/cli/validate.d.ts +0 -12
- package/dist/cli/validate.d.ts.map +0 -1
- package/dist/cli/validate.js +0 -237
- package/dist/cli/validate.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/runtime/index.d.ts +0 -12
- package/dist/runtime/index.d.ts.map +0 -1
- package/dist/runtime/index.js +0 -241
- package/dist/runtime/index.js.map +0 -1
- package/dist/types/index.d.ts +0 -33
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -3
- package/dist/types/index.js.map +0 -1
- package/dist/types/metadata.d.ts.map +0 -1
- package/dist/types/metadata.js +0 -108
- package/dist/types/metadata.js.map +0 -1
- package/dist/utils/esbuild.d.ts +0 -13
- package/dist/utils/esbuild.d.ts.map +0 -1
- package/dist/utils/esbuild.js +0 -95
- package/dist/utils/esbuild.js.map +0 -1
- package/dist/utils/module-loader.d.ts +0 -3
- package/dist/utils/module-loader.d.ts.map +0 -1
- package/dist/utils/module-loader.js +0 -49
- package/dist/utils/module-loader.js.map +0 -1
package/dist/ai/index.js
ADDED
|
@@ -0,0 +1,759 @@
|
|
|
1
|
+
// src/ai/errors.ts
|
|
2
|
+
var AIError = class extends Error {
|
|
3
|
+
constructor(message, options) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "AIError";
|
|
6
|
+
if (options && "cause" in options) {
|
|
7
|
+
this.cause = options.cause;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
var AIFetchError = class extends AIError {
|
|
12
|
+
constructor(message, options) {
|
|
13
|
+
super(message, options);
|
|
14
|
+
this.name = "AIFetchError";
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
var AIResponseError = class extends AIError {
|
|
18
|
+
constructor(details, message) {
|
|
19
|
+
super(message ?? `AI response error: ${details.status} ${details.statusText}`);
|
|
20
|
+
this.name = "AIResponseError";
|
|
21
|
+
this.status = details.status;
|
|
22
|
+
this.statusText = details.statusText;
|
|
23
|
+
this.body = details.body;
|
|
24
|
+
this.headers = details.headers ?? {};
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
var AIAbortError = class extends AIError {
|
|
28
|
+
constructor(message = "AI request aborted") {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = "AIAbortError";
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// src/ai/config.ts
|
|
35
|
+
var DEFAULT_BASE_URL = "https://gateway.openpond.dev";
|
|
36
|
+
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
37
|
+
var DEFAULT_MODEL = "openai/gpt-5-mini";
|
|
38
|
+
function assertFetchAvailable(fetchImplementation) {
|
|
39
|
+
if (!fetchImplementation) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
"No fetch implementation available. Provide one via AIClientConfig.fetchImplementation."
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function resolveConfig(config = {}) {
|
|
46
|
+
const fetchImplementation = config.fetchImplementation ?? globalThis.fetch;
|
|
47
|
+
assertFetchAvailable(fetchImplementation);
|
|
48
|
+
const resolved = {
|
|
49
|
+
baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,
|
|
50
|
+
defaultModel: config.defaultModel ?? DEFAULT_MODEL,
|
|
51
|
+
defaultHeaders: {
|
|
52
|
+
"Content-Type": "application/json",
|
|
53
|
+
...config.defaultHeaders
|
|
54
|
+
},
|
|
55
|
+
fetchImplementation,
|
|
56
|
+
timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS
|
|
57
|
+
};
|
|
58
|
+
if (config.apiKey !== void 0) {
|
|
59
|
+
resolved.apiKey = config.apiKey;
|
|
60
|
+
}
|
|
61
|
+
return resolved;
|
|
62
|
+
}
|
|
63
|
+
function mergeHeaders(base, overrides) {
|
|
64
|
+
if (!overrides) {
|
|
65
|
+
return { ...base };
|
|
66
|
+
}
|
|
67
|
+
const merged = { ...base };
|
|
68
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
69
|
+
if (value === void 0) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
merged[key] = value;
|
|
73
|
+
}
|
|
74
|
+
return merged;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/ai/models.ts
|
|
78
|
+
var MODEL_REGISTRY = [
|
|
79
|
+
{
|
|
80
|
+
name: "openai/gpt-5-mini",
|
|
81
|
+
label: "OpenAI GPT-5 Mini",
|
|
82
|
+
provider: "openai",
|
|
83
|
+
supportsStreaming: true,
|
|
84
|
+
supportsTools: true,
|
|
85
|
+
reasoning: true,
|
|
86
|
+
aliases: ["gpt-5-mini", "gpt5-mini", "gpt-5.0-mini"],
|
|
87
|
+
default: true
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: "anthropic/claude-4-sonnet-20250514",
|
|
91
|
+
label: "Claude 4 Sonnet (20250514)",
|
|
92
|
+
provider: "anthropic",
|
|
93
|
+
supportsStreaming: true,
|
|
94
|
+
supportsTools: true,
|
|
95
|
+
aliases: ["claude-4-sonnet", "claude-sonnet"]
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: "google/gemini-2.0-flash-001",
|
|
99
|
+
label: "Gemini 2.0 Flash",
|
|
100
|
+
provider: "google",
|
|
101
|
+
supportsStreaming: true,
|
|
102
|
+
supportsTools: true,
|
|
103
|
+
aliases: ["gemini-2.0-flash", "gemini-flash"]
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: "deepseek/deepseek-chat",
|
|
107
|
+
label: "DeepSeek Chat",
|
|
108
|
+
provider: "deepseek",
|
|
109
|
+
supportsStreaming: true,
|
|
110
|
+
supportsTools: true,
|
|
111
|
+
aliases: ["deepseek-chat", "deepseek"]
|
|
112
|
+
}
|
|
113
|
+
];
|
|
114
|
+
var ALIAS_LOOKUP = MODEL_REGISTRY.reduce(
|
|
115
|
+
(accumulator, model) => {
|
|
116
|
+
accumulator[model.name.toLowerCase()] = model.name;
|
|
117
|
+
if (model.aliases) {
|
|
118
|
+
for (const alias of model.aliases) {
|
|
119
|
+
accumulator[alias.toLowerCase()] = model.name;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return accumulator;
|
|
123
|
+
},
|
|
124
|
+
{}
|
|
125
|
+
);
|
|
126
|
+
var DEFAULT_MODEL_NAME = MODEL_REGISTRY.find((model) => model.default)?.name ?? MODEL_REGISTRY[0].name;
|
|
127
|
+
function listModels() {
|
|
128
|
+
return [...MODEL_REGISTRY];
|
|
129
|
+
}
|
|
130
|
+
function getModelConfig(modelName) {
|
|
131
|
+
if (!modelName) {
|
|
132
|
+
return MODEL_REGISTRY.find((model) => model.default) ?? MODEL_REGISTRY[0];
|
|
133
|
+
}
|
|
134
|
+
const normalized = normalizeModelName(modelName);
|
|
135
|
+
return MODEL_REGISTRY.find((model) => model.name === normalized);
|
|
136
|
+
}
|
|
137
|
+
function normalizeModelName(modelName) {
|
|
138
|
+
if (!modelName) {
|
|
139
|
+
return DEFAULT_MODEL_NAME;
|
|
140
|
+
}
|
|
141
|
+
const trimmed = modelName.trim();
|
|
142
|
+
if (!trimmed) {
|
|
143
|
+
return DEFAULT_MODEL_NAME;
|
|
144
|
+
}
|
|
145
|
+
const directMatch = ALIAS_LOOKUP[trimmed.toLowerCase()];
|
|
146
|
+
if (directMatch) {
|
|
147
|
+
return directMatch;
|
|
148
|
+
}
|
|
149
|
+
if (trimmed.includes("/")) {
|
|
150
|
+
return trimmed;
|
|
151
|
+
}
|
|
152
|
+
return `openai/${trimmed}`;
|
|
153
|
+
}
|
|
154
|
+
function isStreamingSupported(modelName) {
|
|
155
|
+
const config = getModelConfig(modelName);
|
|
156
|
+
return config ? config.supportsStreaming : true;
|
|
157
|
+
}
|
|
158
|
+
function isToolCallingSupported(modelName) {
|
|
159
|
+
const config = getModelConfig(modelName);
|
|
160
|
+
return config ? config.supportsTools : true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/ai/tools.ts
|
|
164
|
+
var WEBSEARCH_TOOL_NAME = "websearch";
|
|
165
|
+
var WEBSEARCH_TOOL_DEFINITION = {
|
|
166
|
+
type: "function",
|
|
167
|
+
function: {
|
|
168
|
+
name: WEBSEARCH_TOOL_NAME,
|
|
169
|
+
description: "Search the web using the OpenPond search engine. Returns relevant results with titles, URLs, and text content.",
|
|
170
|
+
parameters: {
|
|
171
|
+
type: "object",
|
|
172
|
+
properties: {
|
|
173
|
+
query: {
|
|
174
|
+
type: "string",
|
|
175
|
+
description: "The search query"
|
|
176
|
+
},
|
|
177
|
+
limit: {
|
|
178
|
+
type: "number",
|
|
179
|
+
description: "Maximum number of results to return (default: 5)"
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
required: ["query"]
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
function resolveToolset(tools, policy) {
|
|
187
|
+
if (!policy) {
|
|
188
|
+
return tools;
|
|
189
|
+
}
|
|
190
|
+
const resolved = tools ? [...tools] : [];
|
|
191
|
+
if (policy.webSearch) {
|
|
192
|
+
const alreadyIncluded = resolved.some(
|
|
193
|
+
(tool) => tool.type === "function" && tool.function?.name === WEBSEARCH_TOOL_NAME
|
|
194
|
+
);
|
|
195
|
+
if (!alreadyIncluded) {
|
|
196
|
+
resolved.push(materializeWebSearchTool(policy.webSearch));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return resolved.length > 0 ? resolved : void 0;
|
|
200
|
+
}
|
|
201
|
+
function materializeWebSearchTool(options) {
|
|
202
|
+
if (!options || Object.keys(options).length === 0) {
|
|
203
|
+
return WEBSEARCH_TOOL_DEFINITION;
|
|
204
|
+
}
|
|
205
|
+
const baseParameters = WEBSEARCH_TOOL_DEFINITION.function.parameters ?? {};
|
|
206
|
+
const baseProperties = baseParameters.properties ?? {};
|
|
207
|
+
const properties = { ...baseProperties };
|
|
208
|
+
if (options.limit !== void 0) {
|
|
209
|
+
const existingLimit = baseProperties["limit"];
|
|
210
|
+
const limitSchema = typeof existingLimit === "object" && existingLimit !== null ? { ...existingLimit } : {
|
|
211
|
+
type: "number",
|
|
212
|
+
description: "Maximum number of results to return (default: 5)"
|
|
213
|
+
};
|
|
214
|
+
limitSchema.default = options.limit;
|
|
215
|
+
properties.limit = limitSchema;
|
|
216
|
+
}
|
|
217
|
+
if (options.includeImages) {
|
|
218
|
+
properties.includeImages = {
|
|
219
|
+
type: "boolean",
|
|
220
|
+
description: "Whether to include representative images in results.",
|
|
221
|
+
default: true
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
...WEBSEARCH_TOOL_DEFINITION,
|
|
226
|
+
function: {
|
|
227
|
+
...WEBSEARCH_TOOL_DEFINITION.function,
|
|
228
|
+
parameters: {
|
|
229
|
+
...WEBSEARCH_TOOL_DEFINITION.function.parameters,
|
|
230
|
+
properties
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// src/ai/messages.ts
|
|
237
|
+
function flattenMessageContent(content, options = {}) {
|
|
238
|
+
if (typeof content === "string") {
|
|
239
|
+
return content;
|
|
240
|
+
}
|
|
241
|
+
if (!Array.isArray(content)) {
|
|
242
|
+
return void 0;
|
|
243
|
+
}
|
|
244
|
+
const separator = options.separator ?? "";
|
|
245
|
+
const collected = [];
|
|
246
|
+
for (const part of content) {
|
|
247
|
+
const text = extractTextPart(part, options);
|
|
248
|
+
if (text) {
|
|
249
|
+
collected.push(text);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (collected.length === 0) {
|
|
253
|
+
return void 0;
|
|
254
|
+
}
|
|
255
|
+
return collected.join(separator);
|
|
256
|
+
}
|
|
257
|
+
function ensureTextContent(message, options) {
|
|
258
|
+
const flattened = flattenMessageContent(message.content, options);
|
|
259
|
+
if (flattened !== void 0) {
|
|
260
|
+
return flattened;
|
|
261
|
+
}
|
|
262
|
+
throw new AIError(
|
|
263
|
+
options?.errorMessage ?? "Assistant response did not contain textual content."
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
function extractTextPart(part, options) {
|
|
267
|
+
if (!part || typeof part !== "object") {
|
|
268
|
+
return void 0;
|
|
269
|
+
}
|
|
270
|
+
if ("text" in part && typeof part.text === "string") {
|
|
271
|
+
return part.text;
|
|
272
|
+
}
|
|
273
|
+
if (options.includeUnknown) {
|
|
274
|
+
try {
|
|
275
|
+
return JSON.stringify(part);
|
|
276
|
+
} catch (error) {
|
|
277
|
+
return `[unserializable_part: ${String(error)}]`;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return void 0;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// src/ai/client.ts
|
|
284
|
+
var CHAT_COMPLETIONS_PATH = "/v1/chat/completions";
|
|
285
|
+
function createAIClient(config = {}) {
|
|
286
|
+
const resolved = resolveConfig(config);
|
|
287
|
+
return {
|
|
288
|
+
get config() {
|
|
289
|
+
return resolved;
|
|
290
|
+
},
|
|
291
|
+
async generateText(options) {
|
|
292
|
+
return generateText(options, config);
|
|
293
|
+
},
|
|
294
|
+
async streamText(options) {
|
|
295
|
+
return streamText(options, config);
|
|
296
|
+
},
|
|
297
|
+
listModels
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
async function generateText(options, clientConfig = {}) {
|
|
301
|
+
const resolved = resolveConfig(clientConfig);
|
|
302
|
+
const model = normalizeModelName(options.model ?? resolved.defaultModel);
|
|
303
|
+
const payload = buildRequestPayload(options, model, {
|
|
304
|
+
allowTools: isToolCallingSupported(model)
|
|
305
|
+
});
|
|
306
|
+
const headers = mergeHeaders(resolved.defaultHeaders, options.headers);
|
|
307
|
+
if (resolved.apiKey) {
|
|
308
|
+
headers.Authorization = `Bearer ${resolved.apiKey}`;
|
|
309
|
+
}
|
|
310
|
+
const endpoint = buildUrl(resolved.baseUrl, CHAT_COMPLETIONS_PATH);
|
|
311
|
+
const abortBundle = createAbortBundle(
|
|
312
|
+
options.abortSignal,
|
|
313
|
+
options.timeoutMs ?? resolved.timeoutMs
|
|
314
|
+
);
|
|
315
|
+
let response;
|
|
316
|
+
try {
|
|
317
|
+
response = await resolved.fetchImplementation(endpoint, {
|
|
318
|
+
method: "POST",
|
|
319
|
+
headers,
|
|
320
|
+
body: JSON.stringify(payload),
|
|
321
|
+
signal: abortBundle.signal
|
|
322
|
+
});
|
|
323
|
+
} catch (error) {
|
|
324
|
+
if (abortBundle.signal.aborted) {
|
|
325
|
+
throw toAbortError(abortBundle.signal.reason ?? error);
|
|
326
|
+
}
|
|
327
|
+
throw new AIFetchError("Failed to reach AI gateway", { cause: error });
|
|
328
|
+
} finally {
|
|
329
|
+
abortBundle.cleanup();
|
|
330
|
+
}
|
|
331
|
+
if (!response.ok) {
|
|
332
|
+
const errorBody = await safeParseJson(response);
|
|
333
|
+
throw new AIResponseError({
|
|
334
|
+
status: response.status,
|
|
335
|
+
statusText: response.statusText,
|
|
336
|
+
body: errorBody,
|
|
337
|
+
headers: collectHeaders(response.headers)
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
const data = await response.json();
|
|
341
|
+
const primaryChoice = data.choices.find(isPrimaryChoice);
|
|
342
|
+
if (!primaryChoice) {
|
|
343
|
+
throw new AIResponseError(
|
|
344
|
+
{
|
|
345
|
+
status: response.status,
|
|
346
|
+
statusText: response.statusText,
|
|
347
|
+
body: data
|
|
348
|
+
},
|
|
349
|
+
"Gateway response did not contain a valid choice"
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
const result = {
|
|
353
|
+
id: data.id,
|
|
354
|
+
model: data.model,
|
|
355
|
+
message: primaryChoice.message,
|
|
356
|
+
raw: data
|
|
357
|
+
};
|
|
358
|
+
if (primaryChoice.finish_reason !== void 0) {
|
|
359
|
+
result.finishReason = primaryChoice.finish_reason;
|
|
360
|
+
}
|
|
361
|
+
if (data.usage) {
|
|
362
|
+
result.usage = data.usage;
|
|
363
|
+
}
|
|
364
|
+
return result;
|
|
365
|
+
}
|
|
366
|
+
async function streamText(options, clientConfig = {}) {
|
|
367
|
+
const resolved = resolveConfig(clientConfig);
|
|
368
|
+
const model = normalizeModelName(options.model ?? resolved.defaultModel);
|
|
369
|
+
const streamExtras = buildStreamMetadataExtras(options);
|
|
370
|
+
const payload = buildRequestPayload(
|
|
371
|
+
options,
|
|
372
|
+
model,
|
|
373
|
+
{
|
|
374
|
+
allowTools: isToolCallingSupported(model)
|
|
375
|
+
},
|
|
376
|
+
streamExtras
|
|
377
|
+
);
|
|
378
|
+
payload.stream = true;
|
|
379
|
+
if (options.includeUsage) {
|
|
380
|
+
payload.stream_options = { include_usage: true };
|
|
381
|
+
}
|
|
382
|
+
const headers = mergeHeaders(resolved.defaultHeaders, options.headers);
|
|
383
|
+
if (resolved.apiKey) {
|
|
384
|
+
headers.Authorization = `Bearer ${resolved.apiKey}`;
|
|
385
|
+
}
|
|
386
|
+
const endpoint = buildUrl(resolved.baseUrl, CHAT_COMPLETIONS_PATH);
|
|
387
|
+
const abortBundle = createAbortBundle(
|
|
388
|
+
options.abortSignal,
|
|
389
|
+
options.timeoutMs ?? resolved.timeoutMs
|
|
390
|
+
);
|
|
391
|
+
let response;
|
|
392
|
+
try {
|
|
393
|
+
response = await resolved.fetchImplementation(endpoint, {
|
|
394
|
+
method: "POST",
|
|
395
|
+
headers,
|
|
396
|
+
body: JSON.stringify(payload),
|
|
397
|
+
signal: abortBundle.signal
|
|
398
|
+
});
|
|
399
|
+
} catch (error) {
|
|
400
|
+
if (abortBundle.signal.aborted) {
|
|
401
|
+
throw toAbortError(abortBundle.signal.reason ?? error);
|
|
402
|
+
}
|
|
403
|
+
throw new AIFetchError("Failed to reach AI gateway", { cause: error });
|
|
404
|
+
}
|
|
405
|
+
if (!response.ok) {
|
|
406
|
+
const errorBody = await safeParseJson(response);
|
|
407
|
+
abortBundle.cleanup();
|
|
408
|
+
throw new AIResponseError({
|
|
409
|
+
status: response.status,
|
|
410
|
+
statusText: response.statusText,
|
|
411
|
+
body: errorBody,
|
|
412
|
+
headers: collectHeaders(response.headers)
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
if (!response.body) {
|
|
416
|
+
abortBundle.cleanup();
|
|
417
|
+
throw new AIFetchError("Streaming response did not include a readable body");
|
|
418
|
+
}
|
|
419
|
+
const reader = response.body.getReader();
|
|
420
|
+
const decoder = new TextDecoder();
|
|
421
|
+
const handlers = options.handlers ?? {};
|
|
422
|
+
let finishedResolve;
|
|
423
|
+
let finishedReject;
|
|
424
|
+
const finished = new Promise((resolve, reject) => {
|
|
425
|
+
finishedResolve = resolve;
|
|
426
|
+
finishedReject = reject;
|
|
427
|
+
});
|
|
428
|
+
let settled = false;
|
|
429
|
+
const resolveStream = () => {
|
|
430
|
+
if (settled) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
settled = true;
|
|
434
|
+
try {
|
|
435
|
+
handlers.onDone?.();
|
|
436
|
+
finishedResolve();
|
|
437
|
+
} catch (error) {
|
|
438
|
+
settled = false;
|
|
439
|
+
rejectStream(error);
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
const rejectStream = (reason) => {
|
|
443
|
+
if (settled) {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
settled = true;
|
|
447
|
+
try {
|
|
448
|
+
handlers.onError?.(reason);
|
|
449
|
+
} catch (handlerError) {
|
|
450
|
+
reason = handlerError;
|
|
451
|
+
}
|
|
452
|
+
finishedReject(reason);
|
|
453
|
+
};
|
|
454
|
+
const abort = () => abortBundle.abort();
|
|
455
|
+
(async () => {
|
|
456
|
+
let buffer = "";
|
|
457
|
+
try {
|
|
458
|
+
while (true) {
|
|
459
|
+
const { done, value } = await reader.read();
|
|
460
|
+
if (done) {
|
|
461
|
+
buffer += decoder.decode();
|
|
462
|
+
buffer = buffer.replace(/\r\n/g, "\n");
|
|
463
|
+
if (buffer.trim().length > 0) {
|
|
464
|
+
if (processStreamEventChunk(buffer, handlers)) {
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
resolveStream();
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
buffer += decoder.decode(value, { stream: true });
|
|
472
|
+
buffer = buffer.replace(/\r\n/g, "\n");
|
|
473
|
+
let boundaryIndex;
|
|
474
|
+
while ((boundaryIndex = buffer.indexOf("\n\n")) !== -1) {
|
|
475
|
+
const chunk = buffer.slice(0, boundaryIndex);
|
|
476
|
+
buffer = buffer.slice(boundaryIndex + 2);
|
|
477
|
+
if (!chunk) {
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
if (processStreamEventChunk(chunk, handlers)) {
|
|
481
|
+
await reader.cancel().catch(() => void 0);
|
|
482
|
+
resolveStream();
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
} catch (error) {
|
|
488
|
+
if (abortBundle.signal.aborted) {
|
|
489
|
+
rejectStream(toAbortError(abortBundle.signal.reason ?? error));
|
|
490
|
+
} else {
|
|
491
|
+
rejectStream(error);
|
|
492
|
+
}
|
|
493
|
+
} finally {
|
|
494
|
+
try {
|
|
495
|
+
reader.releaseLock();
|
|
496
|
+
} catch (error) {
|
|
497
|
+
}
|
|
498
|
+
abortBundle.cleanup();
|
|
499
|
+
}
|
|
500
|
+
})().catch((error) => {
|
|
501
|
+
rejectStream(error);
|
|
502
|
+
});
|
|
503
|
+
return {
|
|
504
|
+
abort,
|
|
505
|
+
finished
|
|
506
|
+
};
|
|
507
|
+
function processStreamEventChunk(chunk, eventHandlers) {
|
|
508
|
+
const dataString = extractSseData(chunk);
|
|
509
|
+
if (dataString == null) {
|
|
510
|
+
return false;
|
|
511
|
+
}
|
|
512
|
+
const trimmed = dataString.trim();
|
|
513
|
+
if (trimmed === "[DONE]") {
|
|
514
|
+
return true;
|
|
515
|
+
}
|
|
516
|
+
let payload2;
|
|
517
|
+
try {
|
|
518
|
+
payload2 = JSON.parse(dataString);
|
|
519
|
+
} catch (error) {
|
|
520
|
+
rejectStream(new AIError("Failed to parse streaming payload", { cause: error }));
|
|
521
|
+
return true;
|
|
522
|
+
}
|
|
523
|
+
try {
|
|
524
|
+
handleStreamPayload(payload2, eventHandlers);
|
|
525
|
+
} catch (error) {
|
|
526
|
+
rejectStream(error);
|
|
527
|
+
return true;
|
|
528
|
+
}
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
function handleStreamPayload(payload2, eventHandlers) {
|
|
532
|
+
if (!payload2 || typeof payload2 !== "object") {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if ("error" in payload2 && payload2.error) {
|
|
536
|
+
const message = typeof payload2.error === "string" ? payload2.error : payload2.error.message;
|
|
537
|
+
throw new AIError(message ?? "AI stream returned an error payload");
|
|
538
|
+
}
|
|
539
|
+
const structured = payload2;
|
|
540
|
+
if (Array.isArray(structured.choices)) {
|
|
541
|
+
for (const choice of structured.choices) {
|
|
542
|
+
if (!choice || typeof choice !== "object") {
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
const delta = choice.delta;
|
|
546
|
+
if (!delta || typeof delta !== "object") {
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
const deltaObject = delta;
|
|
550
|
+
const textDelta = extractDeltaText(deltaObject.content);
|
|
551
|
+
if (textDelta) {
|
|
552
|
+
eventHandlers.onTextDelta?.(textDelta);
|
|
553
|
+
}
|
|
554
|
+
const reasoningDelta = extractDeltaText(deltaObject.reasoning);
|
|
555
|
+
if (reasoningDelta) {
|
|
556
|
+
eventHandlers.onReasoningDelta?.(reasoningDelta);
|
|
557
|
+
}
|
|
558
|
+
if (deltaObject.tool_calls !== void 0) {
|
|
559
|
+
eventHandlers.onToolCallDelta?.(deltaObject.tool_calls);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
if (structured.usage) {
|
|
564
|
+
eventHandlers.onUsage?.(structured.usage);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
function extractDeltaText(value) {
|
|
568
|
+
if (!value) {
|
|
569
|
+
return void 0;
|
|
570
|
+
}
|
|
571
|
+
if (typeof value === "string") {
|
|
572
|
+
return value;
|
|
573
|
+
}
|
|
574
|
+
if (Array.isArray(value)) {
|
|
575
|
+
return flattenMessageContent(value);
|
|
576
|
+
}
|
|
577
|
+
if (typeof value === "object" && value !== null && "content" in value && Array.isArray(value.content)) {
|
|
578
|
+
return flattenMessageContent(
|
|
579
|
+
value.content ?? []
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
return void 0;
|
|
583
|
+
}
|
|
584
|
+
function extractSseData(chunk) {
|
|
585
|
+
const lines = chunk.split("\n");
|
|
586
|
+
const dataLines = [];
|
|
587
|
+
for (const rawLine of lines) {
|
|
588
|
+
if (!rawLine) {
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
const match = /^data:(.*)$/.exec(rawLine);
|
|
592
|
+
if (!match) {
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
const value = match[1];
|
|
596
|
+
dataLines.push(value.startsWith(" ") ? value.slice(1) : value);
|
|
597
|
+
}
|
|
598
|
+
if (dataLines.length === 0) {
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
601
|
+
return dataLines.join("\n");
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
function buildStreamMetadataExtras(options) {
|
|
605
|
+
const streamConfig = {};
|
|
606
|
+
if (options.sendReasoning !== void 0) {
|
|
607
|
+
streamConfig.sendReasoning = options.sendReasoning;
|
|
608
|
+
}
|
|
609
|
+
if (options.includeUsage !== void 0) {
|
|
610
|
+
streamConfig.includeUsage = options.includeUsage;
|
|
611
|
+
}
|
|
612
|
+
if (Object.keys(streamConfig).length === 0) {
|
|
613
|
+
return void 0;
|
|
614
|
+
}
|
|
615
|
+
return {
|
|
616
|
+
openpond: {
|
|
617
|
+
stream: streamConfig
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
function buildRequestPayload(options, model, capabilities, metadataExtras) {
|
|
622
|
+
const payload = {
|
|
623
|
+
model,
|
|
624
|
+
messages: options.messages
|
|
625
|
+
};
|
|
626
|
+
const generation = options.generation ?? {};
|
|
627
|
+
assignIfDefined(payload, "temperature", generation.temperature);
|
|
628
|
+
assignIfDefined(payload, "top_p", generation.topP);
|
|
629
|
+
assignIfDefined(payload, "max_tokens", generation.maxTokens);
|
|
630
|
+
assignIfDefined(payload, "stop", generation.stop);
|
|
631
|
+
assignIfDefined(
|
|
632
|
+
payload,
|
|
633
|
+
"frequency_penalty",
|
|
634
|
+
generation.frequencyPenalty
|
|
635
|
+
);
|
|
636
|
+
assignIfDefined(payload, "presence_penalty", generation.presencePenalty);
|
|
637
|
+
assignIfDefined(payload, "response_format", generation.responseFormat);
|
|
638
|
+
const toolExecution = options.toolExecution;
|
|
639
|
+
const enableTools = toolExecution?.enableTools ?? true;
|
|
640
|
+
if (enableTools && capabilities.allowTools) {
|
|
641
|
+
const resolvedTools = resolveToolset(options.tools, toolExecution);
|
|
642
|
+
assignIfDefined(payload, "tools", resolvedTools);
|
|
643
|
+
assignIfDefined(payload, "tool_choice", options.toolChoice);
|
|
644
|
+
} else if (options.toolChoice && options.toolChoice !== "none") {
|
|
645
|
+
payload.tool_choice = "none";
|
|
646
|
+
}
|
|
647
|
+
const metadataPayload = buildMetadataPayload(
|
|
648
|
+
options.metadata,
|
|
649
|
+
toolExecution,
|
|
650
|
+
metadataExtras
|
|
651
|
+
);
|
|
652
|
+
if (metadataPayload) {
|
|
653
|
+
payload.metadata = metadataPayload;
|
|
654
|
+
}
|
|
655
|
+
return payload;
|
|
656
|
+
}
|
|
657
|
+
function assignIfDefined(target, key, value) {
|
|
658
|
+
if (value !== void 0) {
|
|
659
|
+
target[key] = value;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
function buildUrl(baseUrl, path) {
|
|
663
|
+
const sanitizedBase = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
664
|
+
return `${sanitizedBase}${path}`;
|
|
665
|
+
}
|
|
666
|
+
function createAbortBundle(upstreamSignal, timeoutMs) {
|
|
667
|
+
const controller = new AbortController();
|
|
668
|
+
const cleanupCallbacks = [];
|
|
669
|
+
if (upstreamSignal) {
|
|
670
|
+
if (upstreamSignal.aborted) {
|
|
671
|
+
controller.abort(upstreamSignal.reason);
|
|
672
|
+
} else {
|
|
673
|
+
const onAbort = () => controller.abort(upstreamSignal.reason);
|
|
674
|
+
upstreamSignal.addEventListener("abort", onAbort, { once: true });
|
|
675
|
+
cleanupCallbacks.push(
|
|
676
|
+
() => upstreamSignal.removeEventListener("abort", onAbort)
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
if (timeoutMs && timeoutMs > 0) {
|
|
681
|
+
const timeoutId = setTimeout(() => {
|
|
682
|
+
controller.abort(new Error("AI request timed out"));
|
|
683
|
+
}, timeoutMs);
|
|
684
|
+
cleanupCallbacks.push(() => clearTimeout(timeoutId));
|
|
685
|
+
}
|
|
686
|
+
return {
|
|
687
|
+
signal: controller.signal,
|
|
688
|
+
abort: () => controller.abort(),
|
|
689
|
+
cleanup: () => {
|
|
690
|
+
cleanupCallbacks.forEach((fn) => fn());
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
function collectHeaders(headers) {
|
|
695
|
+
const result = {};
|
|
696
|
+
headers.forEach((value, key) => {
|
|
697
|
+
result[key] = value;
|
|
698
|
+
});
|
|
699
|
+
return result;
|
|
700
|
+
}
|
|
701
|
+
function buildMetadataPayload(base, toolExecution, extras) {
|
|
702
|
+
const metadata = base ? { ...base } : {};
|
|
703
|
+
if (extras) {
|
|
704
|
+
for (const [key, value] of Object.entries(extras)) {
|
|
705
|
+
if (value === void 0) {
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
708
|
+
if (key === "openpond" && typeof value === "object" && value !== null) {
|
|
709
|
+
const existing = {
|
|
710
|
+
...metadata.openpond ?? {}
|
|
711
|
+
};
|
|
712
|
+
metadata.openpond = {
|
|
713
|
+
...existing,
|
|
714
|
+
...value
|
|
715
|
+
};
|
|
716
|
+
} else {
|
|
717
|
+
metadata[key] = value;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
if (toolExecution) {
|
|
722
|
+
const openpond = {
|
|
723
|
+
...metadata.openpond ?? {},
|
|
724
|
+
toolExecution
|
|
725
|
+
};
|
|
726
|
+
metadata.openpond = openpond;
|
|
727
|
+
}
|
|
728
|
+
return Object.keys(metadata).length > 0 ? metadata : void 0;
|
|
729
|
+
}
|
|
730
|
+
async function safeParseJson(response) {
|
|
731
|
+
const contentType = response.headers.get("content-type");
|
|
732
|
+
if (!contentType || !contentType.includes("application/json")) {
|
|
733
|
+
return void 0;
|
|
734
|
+
}
|
|
735
|
+
try {
|
|
736
|
+
return await response.json();
|
|
737
|
+
} catch (error) {
|
|
738
|
+
return { error: "Failed to parse error body", cause: String(error) };
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
function isPrimaryChoice(choice) {
|
|
742
|
+
return choice.index === 0 || choice.message !== void 0;
|
|
743
|
+
}
|
|
744
|
+
function toAbortError(reason) {
|
|
745
|
+
if (reason instanceof AIAbortError) {
|
|
746
|
+
return reason;
|
|
747
|
+
}
|
|
748
|
+
if (reason instanceof Error) {
|
|
749
|
+
if (reason.name === "AbortError") {
|
|
750
|
+
return new AIAbortError(reason.message || "AI request aborted");
|
|
751
|
+
}
|
|
752
|
+
return new AIAbortError(reason.message);
|
|
753
|
+
}
|
|
754
|
+
return new AIAbortError(String(reason ?? "AI request aborted"));
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
export { AIAbortError, AIError, AIFetchError, AIResponseError, DEFAULT_BASE_URL, DEFAULT_MODEL, DEFAULT_TIMEOUT_MS, WEBSEARCH_TOOL_DEFINITION, WEBSEARCH_TOOL_NAME, createAIClient, ensureTextContent, flattenMessageContent, generateText, getModelConfig, isStreamingSupported, isToolCallingSupported, listModels, normalizeModelName, resolveConfig, resolveToolset, streamText };
|
|
758
|
+
//# sourceMappingURL=index.js.map
|
|
759
|
+
//# sourceMappingURL=index.js.map
|