openpond-code 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/README.md +121 -0
- package/RELEASE.md +24 -0
- package/dist/api.d.ts +247 -0
- package/dist/cache.d.ts +22 -0
- package/dist/cli.js +1767 -0
- package/dist/config.d.ts +16 -0
- package/dist/index.d.ts +154 -0
- package/dist/index.js +1346 -0
- package/dist/indicators.d.ts +38 -0
- package/dist/stream.d.ts +20 -0
- package/dist/types.d.ts +58 -0
- package/package.json +59 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1346 @@
|
|
|
1
|
+
// src/api.ts
|
|
2
|
+
async function apiFetch(baseUrl, token, path, init) {
|
|
3
|
+
const headers = new Headers(init?.headers || {});
|
|
4
|
+
headers.set("Content-Type", "application/json");
|
|
5
|
+
const apiKey = process.env.OPENPOND_API_KEY;
|
|
6
|
+
const trimmedToken = token?.trim() || "";
|
|
7
|
+
const tokenIsApiKey = trimmedToken.startsWith("opk_");
|
|
8
|
+
const effectiveApiKey = apiKey || (tokenIsApiKey ? trimmedToken : null);
|
|
9
|
+
if (effectiveApiKey && !headers.has("openpond-api-key")) {
|
|
10
|
+
headers.set("openpond-api-key", effectiveApiKey);
|
|
11
|
+
}
|
|
12
|
+
if (token) {
|
|
13
|
+
headers.set("Authorization", tokenIsApiKey ? `ApiKey ${trimmedToken}` : `Bearer ${token}`);
|
|
14
|
+
} else if (apiKey && !headers.has("Authorization")) {
|
|
15
|
+
headers.set("Authorization", `ApiKey ${apiKey}`);
|
|
16
|
+
}
|
|
17
|
+
return fetch(`${baseUrl}${path}`, {
|
|
18
|
+
...init,
|
|
19
|
+
headers
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
async function startDeviceLogin(baseUrl) {
|
|
23
|
+
const response = await apiFetch(baseUrl, null, "/api/auth/device/start", {
|
|
24
|
+
method: "POST",
|
|
25
|
+
body: JSON.stringify({})
|
|
26
|
+
});
|
|
27
|
+
if (!response.ok) {
|
|
28
|
+
const text = await response.text().catch(() => "");
|
|
29
|
+
throw new Error(`Device login start failed: ${response.status} ${text}`);
|
|
30
|
+
}
|
|
31
|
+
return await response.json();
|
|
32
|
+
}
|
|
33
|
+
async function pollDeviceLogin(baseUrl, deviceCode, userCode) {
|
|
34
|
+
const payload = userCode && typeof userCode === "string" ? { userCode } : { deviceCode };
|
|
35
|
+
const response = await apiFetch(baseUrl, null, "/api/auth/device/poll", {
|
|
36
|
+
method: "POST",
|
|
37
|
+
body: JSON.stringify(payload)
|
|
38
|
+
});
|
|
39
|
+
if (response.status === 202) {
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
const text = await response.text().catch(() => "");
|
|
44
|
+
throw new Error(`Device login poll failed: ${response.status} ${text}`);
|
|
45
|
+
}
|
|
46
|
+
return await response.json();
|
|
47
|
+
}
|
|
48
|
+
async function listApps(apiBase, token, options) {
|
|
49
|
+
const params = new URLSearchParams;
|
|
50
|
+
if (options?.handle) {
|
|
51
|
+
params.set("handle", options.handle);
|
|
52
|
+
}
|
|
53
|
+
const query = params.toString();
|
|
54
|
+
const response = await apiFetch(apiBase, token, `/apps/list${query ? `?${query}` : ""}`, { method: "GET" });
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
const text = await response.text().catch(() => "");
|
|
57
|
+
throw new Error(`Apps list failed: ${response.status} ${text}`);
|
|
58
|
+
}
|
|
59
|
+
const payload = await response.json().catch(() => ({}));
|
|
60
|
+
return Array.isArray(payload.apps) ? payload.apps : [];
|
|
61
|
+
}
|
|
62
|
+
async function createLocalProject(baseUrl, token, input) {
|
|
63
|
+
const response = await apiFetch(baseUrl, token, "/api/projects/local", {
|
|
64
|
+
method: "POST",
|
|
65
|
+
body: JSON.stringify(input)
|
|
66
|
+
});
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
const text = await response.text().catch(() => "");
|
|
69
|
+
throw new Error(`Create project failed: ${response.status} ${text}`);
|
|
70
|
+
}
|
|
71
|
+
return await response.json();
|
|
72
|
+
}
|
|
73
|
+
async function createRepo(apiBase, apiKey, input) {
|
|
74
|
+
const response = await apiFetch(apiBase, apiKey, "/apps/repo/create", {
|
|
75
|
+
method: "POST",
|
|
76
|
+
body: JSON.stringify(input)
|
|
77
|
+
});
|
|
78
|
+
if (!response.ok) {
|
|
79
|
+
const text = await response.text().catch(() => "");
|
|
80
|
+
throw new Error(`Repo create failed: ${response.status} ${text}`);
|
|
81
|
+
}
|
|
82
|
+
return await response.json();
|
|
83
|
+
}
|
|
84
|
+
async function createHeadlessApps(baseUrl, token, items) {
|
|
85
|
+
const response = await apiFetch(baseUrl, token, "/v4/apps/headless", {
|
|
86
|
+
method: "POST",
|
|
87
|
+
body: JSON.stringify({ items })
|
|
88
|
+
});
|
|
89
|
+
if (!response.ok) {
|
|
90
|
+
const text = await response.text().catch(() => "");
|
|
91
|
+
throw new Error(`Headless create failed: ${response.status} ${text}`);
|
|
92
|
+
}
|
|
93
|
+
return await response.json();
|
|
94
|
+
}
|
|
95
|
+
async function getTemplateStatus(apiBase, token, appId) {
|
|
96
|
+
const response = await apiFetch(apiBase, token, `/v4/apps/${appId}/template/status`, { method: "GET" });
|
|
97
|
+
if (!response.ok) {
|
|
98
|
+
const text = await response.text().catch(() => "");
|
|
99
|
+
throw new Error(`Template status failed: ${response.status} ${text}`);
|
|
100
|
+
}
|
|
101
|
+
return await response.json();
|
|
102
|
+
}
|
|
103
|
+
async function listTemplateBranches(apiBase, token, appId) {
|
|
104
|
+
const response = await apiFetch(apiBase, token, `/v4/apps/${appId}/template/branches`, { method: "GET" });
|
|
105
|
+
if (!response.ok) {
|
|
106
|
+
const text = await response.text().catch(() => "");
|
|
107
|
+
throw new Error(`Template branches failed: ${response.status} ${text}`);
|
|
108
|
+
}
|
|
109
|
+
return await response.json();
|
|
110
|
+
}
|
|
111
|
+
async function deployLatestTemplate(apiBase, token, appId, input) {
|
|
112
|
+
const response = await apiFetch(apiBase, token, `/v4/apps/${appId}/template/deploy-latest`, {
|
|
113
|
+
method: "POST",
|
|
114
|
+
body: JSON.stringify(input)
|
|
115
|
+
});
|
|
116
|
+
if (!response.ok) {
|
|
117
|
+
const text = await response.text().catch(() => "");
|
|
118
|
+
throw new Error(`Template deploy failed: ${response.status} ${text}`);
|
|
119
|
+
}
|
|
120
|
+
return await response.json();
|
|
121
|
+
}
|
|
122
|
+
async function updateAppEnvironment(apiBase, token, appId, input) {
|
|
123
|
+
const response = await apiFetch(apiBase, token, `/v4/apps/${appId}/environment`, {
|
|
124
|
+
method: "POST",
|
|
125
|
+
body: JSON.stringify(input)
|
|
126
|
+
});
|
|
127
|
+
if (!response.ok) {
|
|
128
|
+
const text = await response.text().catch(() => "");
|
|
129
|
+
throw new Error(`Environment update failed: ${response.status} ${text}`);
|
|
130
|
+
}
|
|
131
|
+
return await response.json();
|
|
132
|
+
}
|
|
133
|
+
async function getAppEnvironment(apiBase, token, appId) {
|
|
134
|
+
const response = await apiFetch(apiBase, token, `/v4/apps/${appId}/environment`, {
|
|
135
|
+
method: "GET"
|
|
136
|
+
});
|
|
137
|
+
if (!response.ok) {
|
|
138
|
+
const text = await response.text().catch(() => "");
|
|
139
|
+
throw new Error(`Environment get failed: ${response.status} ${text}`);
|
|
140
|
+
}
|
|
141
|
+
return await response.json();
|
|
142
|
+
}
|
|
143
|
+
async function fetchToolManifest(baseUrl, token) {
|
|
144
|
+
const response = await apiFetch(baseUrl, token, "/api/tools/manifest", {
|
|
145
|
+
method: "GET"
|
|
146
|
+
});
|
|
147
|
+
if (!response.ok) {
|
|
148
|
+
const text = await response.text().catch(() => "");
|
|
149
|
+
throw new Error(`Manifest fetch failed: ${response.status} ${text}`);
|
|
150
|
+
}
|
|
151
|
+
return await response.json();
|
|
152
|
+
}
|
|
153
|
+
async function listUserTools(baseUrl, token) {
|
|
154
|
+
const response = await apiFetch(baseUrl, token, "/apps/tools", {
|
|
155
|
+
method: "GET"
|
|
156
|
+
});
|
|
157
|
+
if (!response.ok) {
|
|
158
|
+
const text = await response.text().catch(() => "");
|
|
159
|
+
throw new Error(`Tools lookup failed: ${response.status} ${text}`);
|
|
160
|
+
}
|
|
161
|
+
return await response.json();
|
|
162
|
+
}
|
|
163
|
+
async function createAgentFromPrompt(baseUrl, token, payload) {
|
|
164
|
+
return apiFetch(baseUrl, token, "/apps/agent/create", {
|
|
165
|
+
method: "POST",
|
|
166
|
+
body: JSON.stringify(payload)
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
async function getUserPerformance(baseUrl, token, options) {
|
|
170
|
+
const params = new URLSearchParams;
|
|
171
|
+
if (options?.appId) {
|
|
172
|
+
params.set("appId", options.appId);
|
|
173
|
+
}
|
|
174
|
+
const qs = params.toString();
|
|
175
|
+
const response = await apiFetch(baseUrl, token, `/apps/performance${qs ? `?${qs}` : ""}`, {
|
|
176
|
+
method: "GET"
|
|
177
|
+
});
|
|
178
|
+
if (!response.ok) {
|
|
179
|
+
const text = await response.text().catch(() => "");
|
|
180
|
+
throw new Error(`Performance lookup failed: ${response.status} ${text}`);
|
|
181
|
+
}
|
|
182
|
+
return await response.json();
|
|
183
|
+
}
|
|
184
|
+
async function postAgentDigest(baseUrl, token, body) {
|
|
185
|
+
const response = await apiFetch(baseUrl, token, "/apps/agent/digest", {
|
|
186
|
+
method: "POST",
|
|
187
|
+
body: JSON.stringify(body)
|
|
188
|
+
});
|
|
189
|
+
if (!response.ok) {
|
|
190
|
+
const text = await response.text().catch(() => "");
|
|
191
|
+
throw new Error(`Agent digest failed: ${response.status} ${text}`);
|
|
192
|
+
}
|
|
193
|
+
return await response.json();
|
|
194
|
+
}
|
|
195
|
+
async function executeUserTool(baseUrl, token, body) {
|
|
196
|
+
const response = await apiFetch(baseUrl, token, "/apps/tools/execute", {
|
|
197
|
+
method: "POST",
|
|
198
|
+
body: JSON.stringify(body)
|
|
199
|
+
});
|
|
200
|
+
const text = await response.text().catch(() => "");
|
|
201
|
+
let payload = null;
|
|
202
|
+
try {
|
|
203
|
+
payload = text ? JSON.parse(text) : null;
|
|
204
|
+
} catch {
|
|
205
|
+
payload = text;
|
|
206
|
+
}
|
|
207
|
+
if (!response.ok) {
|
|
208
|
+
return {
|
|
209
|
+
ok: false,
|
|
210
|
+
status: response.status,
|
|
211
|
+
error: payload && typeof payload === "object" && "error" in payload ? String(payload.error) : text || response.statusText
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
ok: true,
|
|
216
|
+
status: response.status,
|
|
217
|
+
data: payload
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
async function submitPositionsTx(baseUrl, token, params) {
|
|
221
|
+
const qs = params.query && Object.keys(params.query).length > 0 ? `?${new URLSearchParams(params.query).toString()}` : "";
|
|
222
|
+
const response = await apiFetch(baseUrl, token, `/apps/positions/tx${qs}`, {
|
|
223
|
+
method: params.method,
|
|
224
|
+
body: params.method === "POST" && params.body !== undefined ? JSON.stringify(params.body) : undefined
|
|
225
|
+
});
|
|
226
|
+
if (!response.ok) {
|
|
227
|
+
const text = await response.text().catch(() => "");
|
|
228
|
+
throw new Error(`Positions request failed: ${response.status} ${text}`);
|
|
229
|
+
}
|
|
230
|
+
return await response.json();
|
|
231
|
+
}
|
|
232
|
+
async function commitFiles(baseUrl, token, appId, files, commitMessage) {
|
|
233
|
+
const response = await apiFetch(baseUrl, token, `/v4/apps/${appId}/commits`, {
|
|
234
|
+
method: "POST",
|
|
235
|
+
body: JSON.stringify({ files, message: commitMessage })
|
|
236
|
+
});
|
|
237
|
+
if (!response.ok) {
|
|
238
|
+
const text = await response.text().catch(() => "");
|
|
239
|
+
throw new Error(`Commit failed: ${response.status} ${text}`);
|
|
240
|
+
}
|
|
241
|
+
return await response.json();
|
|
242
|
+
}
|
|
243
|
+
async function deployApp(baseUrl, token, appId, input) {
|
|
244
|
+
const environment = input?.environment ?? "production";
|
|
245
|
+
const response = await apiFetch(baseUrl, token, `/v4/apps/${appId}/deployments`, {
|
|
246
|
+
method: "POST",
|
|
247
|
+
body: JSON.stringify({
|
|
248
|
+
environment,
|
|
249
|
+
...input?.commitSha ? { commitSha: input.commitSha } : {},
|
|
250
|
+
...input?.branch ? { branch: input.branch } : {}
|
|
251
|
+
})
|
|
252
|
+
});
|
|
253
|
+
if (!response.ok) {
|
|
254
|
+
const text = await response.text().catch(() => "");
|
|
255
|
+
throw new Error(`Deploy failed: ${response.status} ${text}`);
|
|
256
|
+
}
|
|
257
|
+
return await response.json();
|
|
258
|
+
}
|
|
259
|
+
async function getDeploymentLogs(apiBase, token, deploymentId) {
|
|
260
|
+
const response = await apiFetch(apiBase, token, `/apps/deployments/${deploymentId}/logs`, { method: "GET" });
|
|
261
|
+
if (!response.ok) {
|
|
262
|
+
const text = await response.text().catch(() => "");
|
|
263
|
+
throw new Error(`Deployment logs failed: ${response.status} ${text}`);
|
|
264
|
+
}
|
|
265
|
+
const payload = await response.json().catch(() => ({}));
|
|
266
|
+
const logs = Array.isArray(payload.logs) ? payload.logs : [];
|
|
267
|
+
return logs.map((log) => {
|
|
268
|
+
const createdAt = typeof log.createdAt === "string" ? log.createdAt : log.createdAt instanceof Date ? log.createdAt.toISOString() : new Date().toISOString();
|
|
269
|
+
return {
|
|
270
|
+
id: typeof log.id === "string" ? log.id : `${Math.random()}`,
|
|
271
|
+
type: typeof log.type === "string" ? log.type : undefined,
|
|
272
|
+
message: typeof log.message === "string" ? log.message : "",
|
|
273
|
+
createdAt
|
|
274
|
+
};
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
async function getDeploymentStatus(apiBase, token, deploymentId) {
|
|
278
|
+
const response = await apiFetch(apiBase, token, `/apps/deployments/${deploymentId}/status`, { method: "GET" });
|
|
279
|
+
if (!response.ok) {
|
|
280
|
+
const text = await response.text().catch(() => "");
|
|
281
|
+
throw new Error(`Deployment status failed: ${response.status} ${text}`);
|
|
282
|
+
}
|
|
283
|
+
const payload = await response.json().catch(() => ({}));
|
|
284
|
+
return { status: payload.deployment?.status };
|
|
285
|
+
}
|
|
286
|
+
async function getLatestDeploymentForApp(apiBase, token, appId, options) {
|
|
287
|
+
const params = new URLSearchParams;
|
|
288
|
+
if (options?.status && options.status.length > 0) {
|
|
289
|
+
params.set("status", options.status.join(","));
|
|
290
|
+
}
|
|
291
|
+
if (options?.createdAfter) {
|
|
292
|
+
params.set("createdAfter", options.createdAfter);
|
|
293
|
+
}
|
|
294
|
+
if (options?.branch) {
|
|
295
|
+
params.set("branch", options.branch);
|
|
296
|
+
}
|
|
297
|
+
const query = params.toString();
|
|
298
|
+
const response = await apiFetch(apiBase, token, `/apps/${appId}/deployments/latest${query ? `?${query}` : ""}`, { method: "GET" });
|
|
299
|
+
if (!response.ok) {
|
|
300
|
+
const text = await response.text().catch(() => "");
|
|
301
|
+
throw new Error(`Latest deployment lookup failed: ${response.status} ${text}`);
|
|
302
|
+
}
|
|
303
|
+
const payload = await response.json().catch(() => ({}));
|
|
304
|
+
if (!payload.deployment)
|
|
305
|
+
return null;
|
|
306
|
+
return {
|
|
307
|
+
id: payload.deployment.id,
|
|
308
|
+
status: payload.deployment.status
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
async function getDeploymentDetail(apiBase, token, deploymentId) {
|
|
312
|
+
const response = await apiFetch(apiBase, token, `/apps/deployments/${deploymentId}`, { method: "GET" });
|
|
313
|
+
if (!response.ok) {
|
|
314
|
+
const text = await response.text().catch(() => "");
|
|
315
|
+
throw new Error(`Deployment fetch failed: ${response.status} ${text}`);
|
|
316
|
+
}
|
|
317
|
+
const payload = await response.json().catch(() => ({}));
|
|
318
|
+
return payload.deployment ?? null;
|
|
319
|
+
}
|
|
320
|
+
var normalizeToolPathSegment = function(toolName) {
|
|
321
|
+
const trimmed = toolName.trim().replace(/^\/+/, "");
|
|
322
|
+
return encodeURIComponent(trimmed || "tool");
|
|
323
|
+
};
|
|
324
|
+
function resolveWorkerBaseUrl(baseUrl) {
|
|
325
|
+
const trimmed = baseUrl.replace(/\/$/, "");
|
|
326
|
+
const workerEnv = process.env.OPENPOND_TOOL_URL;
|
|
327
|
+
if (workerEnv) {
|
|
328
|
+
return workerEnv.replace(/\/$/, "");
|
|
329
|
+
}
|
|
330
|
+
try {
|
|
331
|
+
const url = new URL(trimmed);
|
|
332
|
+
const host = url.hostname.toLowerCase();
|
|
333
|
+
const mappedHost = (() => {
|
|
334
|
+
if (host === "apps.openpond.live") {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
if (host === "api.openpond.ai" || host === "openpond.live" || host === "www.openpond.live") {
|
|
338
|
+
return "https://apps.openpond.live";
|
|
339
|
+
}
|
|
340
|
+
return null;
|
|
341
|
+
})();
|
|
342
|
+
if (mappedHost) {
|
|
343
|
+
return mappedHost;
|
|
344
|
+
}
|
|
345
|
+
const isLocal = host === "localhost" || host === "127.0.0.1";
|
|
346
|
+
const port = url.port || (url.protocol === "https:" ? "443" : "80");
|
|
347
|
+
if (isLocal && port === "3000") {
|
|
348
|
+
return trimmed;
|
|
349
|
+
}
|
|
350
|
+
} catch {
|
|
351
|
+
}
|
|
352
|
+
return trimmed;
|
|
353
|
+
}
|
|
354
|
+
async function executeHostedTool(baseUrl, token, payload) {
|
|
355
|
+
const workerBase = resolveWorkerBaseUrl(baseUrl);
|
|
356
|
+
const toolPath = normalizeToolPathSegment(payload.toolName);
|
|
357
|
+
const deploymentPrefix = payload.deploymentId ? `/${payload.appId}/deployments/${payload.deploymentId}` : `/${payload.appId}`;
|
|
358
|
+
const requestPath = `${deploymentPrefix}/${toolPath}`;
|
|
359
|
+
const headers = new Headers(payload.headers || {});
|
|
360
|
+
const method = payload.method ?? "POST";
|
|
361
|
+
const body = payload.body === undefined || method === "GET" ? undefined : JSON.stringify(payload.body);
|
|
362
|
+
const response = await apiFetch(workerBase, token, requestPath, {
|
|
363
|
+
method,
|
|
364
|
+
body,
|
|
365
|
+
headers
|
|
366
|
+
});
|
|
367
|
+
const text = await response.text().catch(() => "");
|
|
368
|
+
let data = null;
|
|
369
|
+
try {
|
|
370
|
+
data = text ? JSON.parse(text) : null;
|
|
371
|
+
} catch {
|
|
372
|
+
data = text;
|
|
373
|
+
}
|
|
374
|
+
const dataOk = data && typeof data === "object" && "ok" in data ? Boolean(data.ok) : true;
|
|
375
|
+
const ok = response.ok && dataOk;
|
|
376
|
+
const status = data && typeof data === "object" && "status" in data ? Number(data.status) || response.status : response.status;
|
|
377
|
+
const error = data && typeof data === "object" && "error" in data ? String(data.error) : response.ok ? undefined : text || response.statusText;
|
|
378
|
+
const payloadData = data && typeof data === "object" && "data" in data ? data.data : data;
|
|
379
|
+
return { ok, status, data: payloadData, error };
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// src/cache.ts
|
|
383
|
+
import {promises as fs} from "node:fs";
|
|
384
|
+
import os from "node:os";
|
|
385
|
+
import path from "node:path";
|
|
386
|
+
var getCachePath = function() {
|
|
387
|
+
return path.join(os.homedir(), CACHE_DIR, CACHE_FILENAME);
|
|
388
|
+
};
|
|
389
|
+
var buildCacheKey = function(apiBase, apiKey) {
|
|
390
|
+
const trimmed = apiKey.trim();
|
|
391
|
+
const hint = trimmed.length > 12 ? `${trimmed.slice(0, 8)}_${trimmed.slice(-4)}` : trimmed;
|
|
392
|
+
try {
|
|
393
|
+
const host = new URL(apiBase).host;
|
|
394
|
+
return `${host}:${hint}`;
|
|
395
|
+
} catch {
|
|
396
|
+
return `${apiBase}:${hint}`;
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
var isFresh = function(updatedAt, ttlMs) {
|
|
400
|
+
const timestamp = Date.parse(updatedAt);
|
|
401
|
+
if (Number.isNaN(timestamp)) {
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
return Date.now() - timestamp < ttlMs;
|
|
405
|
+
};
|
|
406
|
+
async function loadCache() {
|
|
407
|
+
try {
|
|
408
|
+
const raw = await fs.readFile(getCachePath(), "utf-8");
|
|
409
|
+
const parsed = JSON.parse(raw);
|
|
410
|
+
if (!parsed || typeof parsed !== "object" || !parsed.byKey) {
|
|
411
|
+
return DEFAULT_STORE;
|
|
412
|
+
}
|
|
413
|
+
return parsed;
|
|
414
|
+
} catch {
|
|
415
|
+
return DEFAULT_STORE;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
async function saveCache(store) {
|
|
419
|
+
const filePath = getCachePath();
|
|
420
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
421
|
+
await fs.writeFile(filePath, JSON.stringify(store, null, 2), "utf-8");
|
|
422
|
+
}
|
|
423
|
+
async function getCachedApps(params) {
|
|
424
|
+
const ttlMs = params.ttlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
425
|
+
const store = await loadCache();
|
|
426
|
+
const cacheKey = buildCacheKey(params.apiBase, params.apiKey);
|
|
427
|
+
const entry = store.byKey[cacheKey]?.apps;
|
|
428
|
+
if (!entry || !isFresh(entry.updatedAt, ttlMs)) {
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
return Array.isArray(entry.items) ? entry.items : null;
|
|
432
|
+
}
|
|
433
|
+
async function setCachedApps(params) {
|
|
434
|
+
const store = await loadCache();
|
|
435
|
+
const cacheKey = buildCacheKey(params.apiBase, params.apiKey);
|
|
436
|
+
const bucket = store.byKey[cacheKey] || {};
|
|
437
|
+
bucket.apps = {
|
|
438
|
+
items: params.apps,
|
|
439
|
+
updatedAt: new Date().toISOString()
|
|
440
|
+
};
|
|
441
|
+
store.byKey[cacheKey] = bucket;
|
|
442
|
+
await saveCache(store);
|
|
443
|
+
}
|
|
444
|
+
async function getCachedTools(params) {
|
|
445
|
+
const ttlMs = params.ttlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
446
|
+
const store = await loadCache();
|
|
447
|
+
const cacheKey = buildCacheKey(params.apiBase, params.apiKey);
|
|
448
|
+
const entry = store.byKey[cacheKey]?.tools;
|
|
449
|
+
if (!entry || !isFresh(entry.updatedAt, ttlMs)) {
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
return Array.isArray(entry.items) ? entry.items : null;
|
|
453
|
+
}
|
|
454
|
+
async function setCachedTools(params) {
|
|
455
|
+
const store = await loadCache();
|
|
456
|
+
const cacheKey = buildCacheKey(params.apiBase, params.apiKey);
|
|
457
|
+
const bucket = store.byKey[cacheKey] || {};
|
|
458
|
+
bucket.tools = {
|
|
459
|
+
items: params.tools,
|
|
460
|
+
updatedAt: new Date().toISOString()
|
|
461
|
+
};
|
|
462
|
+
store.byKey[cacheKey] = bucket;
|
|
463
|
+
await saveCache(store);
|
|
464
|
+
}
|
|
465
|
+
var CACHE_DIR = ".openpond";
|
|
466
|
+
var CACHE_FILENAME = "cache.json";
|
|
467
|
+
var DEFAULT_STORE = { version: 1, byKey: {} };
|
|
468
|
+
var DEFAULT_CACHE_TTL_MS = 60 * 60 * 1000;
|
|
469
|
+
|
|
470
|
+
// src/stream.ts
|
|
471
|
+
function normalizeDataFrames(raw) {
|
|
472
|
+
const frames = Array.isArray(raw) ? raw : raw && typeof raw === "object" && Array.isArray(raw.data) ? raw.data : [raw];
|
|
473
|
+
const items = [];
|
|
474
|
+
let conversationId;
|
|
475
|
+
for (const frame of frames) {
|
|
476
|
+
if (!frame || typeof frame !== "object")
|
|
477
|
+
continue;
|
|
478
|
+
const typed = frame;
|
|
479
|
+
const type = typeof typed.type === "string" ? typed.type : undefined;
|
|
480
|
+
if (type === "conversation-created") {
|
|
481
|
+
const cid = typed.conversationId;
|
|
482
|
+
if (typeof cid === "string") {
|
|
483
|
+
conversationId = cid;
|
|
484
|
+
}
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
if (type === "response_items" && Array.isArray(typed.items)) {
|
|
488
|
+
items.push(...typed.items);
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
if (type === "response_item" && typed.item) {
|
|
492
|
+
items.push(typed.item);
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
if (type === "assistant-message" && typeof typed.content === "string") {
|
|
496
|
+
items.push({
|
|
497
|
+
type: "message",
|
|
498
|
+
role: "assistant",
|
|
499
|
+
id: `assistant-${Date.now()}`,
|
|
500
|
+
content: [{ type: "markdown", text: typed.content }],
|
|
501
|
+
createdAt: typeof typed.createdAt === "string" ? typed.createdAt : new Date().toISOString()
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return { conversationId, items };
|
|
506
|
+
}
|
|
507
|
+
var extractUsage = function(raw) {
|
|
508
|
+
const frames = Array.isArray(raw) ? raw : raw && typeof raw === "object" && Array.isArray(raw.data) ? raw.data : [raw];
|
|
509
|
+
for (const frame of frames) {
|
|
510
|
+
if (!frame || typeof frame !== "object")
|
|
511
|
+
continue;
|
|
512
|
+
const typed = frame;
|
|
513
|
+
if (typed.type !== "usage")
|
|
514
|
+
continue;
|
|
515
|
+
const usage = typed.usage;
|
|
516
|
+
if (!usage)
|
|
517
|
+
continue;
|
|
518
|
+
const promptTokens = typeof usage.promptTokens === "number" ? usage.promptTokens : typeof usage.prompt_tokens === "number" ? usage.prompt_tokens : undefined;
|
|
519
|
+
const completionTokens = typeof usage.completionTokens === "number" ? usage.completionTokens : typeof usage.completion_tokens === "number" ? usage.completion_tokens : undefined;
|
|
520
|
+
const totalTokens = typeof usage.totalTokens === "number" ? usage.totalTokens : typeof usage.total_tokens === "number" ? usage.total_tokens : undefined;
|
|
521
|
+
return { promptTokens, completionTokens, totalTokens };
|
|
522
|
+
}
|
|
523
|
+
return null;
|
|
524
|
+
};
|
|
525
|
+
async function consumeStream(response, callbacks) {
|
|
526
|
+
if (!response.body) {
|
|
527
|
+
throw new Error("Missing response body");
|
|
528
|
+
}
|
|
529
|
+
const reader = response.body.getReader();
|
|
530
|
+
const decoder = new TextDecoder;
|
|
531
|
+
let remainder = "";
|
|
532
|
+
const shouldStop = callbacks.shouldStop;
|
|
533
|
+
const stopIfNeeded = async () => {
|
|
534
|
+
if (!shouldStop || !shouldStop()) {
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
try {
|
|
538
|
+
await reader.cancel();
|
|
539
|
+
} catch {
|
|
540
|
+
}
|
|
541
|
+
callbacks.onStop?.();
|
|
542
|
+
return true;
|
|
543
|
+
};
|
|
544
|
+
while (true) {
|
|
545
|
+
const { done, value } = await reader.read();
|
|
546
|
+
if (done)
|
|
547
|
+
break;
|
|
548
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
549
|
+
const textChunk = remainder + chunk;
|
|
550
|
+
const lines = textChunk.split("\n");
|
|
551
|
+
remainder = lines.pop() || "";
|
|
552
|
+
for (const line of lines) {
|
|
553
|
+
if (!line)
|
|
554
|
+
continue;
|
|
555
|
+
if (line.startsWith("0:")) {
|
|
556
|
+
const payload = line.slice(2).trim();
|
|
557
|
+
if (!payload)
|
|
558
|
+
continue;
|
|
559
|
+
const delta = JSON.parse(payload);
|
|
560
|
+
if (typeof delta === "string") {
|
|
561
|
+
callbacks.onTextDelta?.(delta);
|
|
562
|
+
}
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
if (line.startsWith("g:")) {
|
|
566
|
+
const payload = line.slice(2).trim();
|
|
567
|
+
if (!payload)
|
|
568
|
+
continue;
|
|
569
|
+
const delta = JSON.parse(payload);
|
|
570
|
+
if (typeof delta === "string") {
|
|
571
|
+
callbacks.onReasoningDelta?.(delta);
|
|
572
|
+
}
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
if (line.startsWith("d:")) {
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
if (line.startsWith("3:")) {
|
|
579
|
+
const payload = line.slice(2).trim();
|
|
580
|
+
if (!payload)
|
|
581
|
+
continue;
|
|
582
|
+
try {
|
|
583
|
+
const message = JSON.parse(payload);
|
|
584
|
+
if (typeof message === "string") {
|
|
585
|
+
throw new Error(message);
|
|
586
|
+
}
|
|
587
|
+
} catch {
|
|
588
|
+
}
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
if (line.startsWith("2:")) {
|
|
592
|
+
const payload = line.slice(2).trim();
|
|
593
|
+
if (!payload)
|
|
594
|
+
continue;
|
|
595
|
+
try {
|
|
596
|
+
const dataItems = JSON.parse(payload);
|
|
597
|
+
const usage = extractUsage(dataItems);
|
|
598
|
+
if (usage) {
|
|
599
|
+
callbacks.onUsage?.(usage);
|
|
600
|
+
}
|
|
601
|
+
const { conversationId, items } = normalizeDataFrames(dataItems);
|
|
602
|
+
if (conversationId) {
|
|
603
|
+
callbacks.onConversationId?.(conversationId);
|
|
604
|
+
}
|
|
605
|
+
if (items.length > 0) {
|
|
606
|
+
await Promise.resolve(callbacks.onItems?.(items));
|
|
607
|
+
}
|
|
608
|
+
if (await stopIfNeeded()) {
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
} catch {
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
if (await stopIfNeeded()) {
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
function formatStreamItem(item) {
|
|
621
|
+
if (!item || typeof item !== "object")
|
|
622
|
+
return null;
|
|
623
|
+
const type = item.type;
|
|
624
|
+
if (typeof type !== "string")
|
|
625
|
+
return null;
|
|
626
|
+
if (type === "app_creation_started") {
|
|
627
|
+
const name = item.appName || item.name || item.appId || "app";
|
|
628
|
+
const templateId = typeof item.templateId === "string" ? item.templateId : null;
|
|
629
|
+
const suffix = templateId ? ` template=${templateId}` : "";
|
|
630
|
+
return `app_creation_started: ${name}${suffix}`;
|
|
631
|
+
}
|
|
632
|
+
if (type === "app_created") {
|
|
633
|
+
const appId = typeof item.appId === "string" ? item.appId : "unknown";
|
|
634
|
+
const name = typeof item.appName === "string" ? item.appName : null;
|
|
635
|
+
return name ? `app_created: ${name} (${appId})` : `app_created: ${appId}`;
|
|
636
|
+
}
|
|
637
|
+
if (type.startsWith("tool_creation_")) {
|
|
638
|
+
const message = item.message || item.content || "";
|
|
639
|
+
return message ? `${type}: ${message}` : type;
|
|
640
|
+
}
|
|
641
|
+
return null;
|
|
642
|
+
}
|
|
643
|
+
// src/indicators.ts
|
|
644
|
+
function computeRsi(values, period = 14) {
|
|
645
|
+
if (values.length < period + 1)
|
|
646
|
+
return null;
|
|
647
|
+
let gains = 0;
|
|
648
|
+
let losses = 0;
|
|
649
|
+
for (let i = 1;i <= period; i += 1) {
|
|
650
|
+
const delta = values[i] - values[i - 1];
|
|
651
|
+
if (delta >= 0) {
|
|
652
|
+
gains += delta;
|
|
653
|
+
} else {
|
|
654
|
+
losses -= delta;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
let avgGain = gains / period;
|
|
658
|
+
let avgLoss = losses / period;
|
|
659
|
+
for (let i = period + 1;i < values.length; i += 1) {
|
|
660
|
+
const delta = values[i] - values[i - 1];
|
|
661
|
+
const gain = delta > 0 ? delta : 0;
|
|
662
|
+
const loss = delta < 0 ? -delta : 0;
|
|
663
|
+
avgGain = (avgGain * (period - 1) + gain) / period;
|
|
664
|
+
avgLoss = (avgLoss * (period - 1) + loss) / period;
|
|
665
|
+
}
|
|
666
|
+
if (avgLoss === 0)
|
|
667
|
+
return 100;
|
|
668
|
+
const rs = avgGain / avgLoss;
|
|
669
|
+
return 100 - 100 / (1 + rs);
|
|
670
|
+
}
|
|
671
|
+
function computeSmaSeries(values, period) {
|
|
672
|
+
if (values.length < period)
|
|
673
|
+
return [];
|
|
674
|
+
const series = [];
|
|
675
|
+
let sum = 0;
|
|
676
|
+
for (let i = 0;i < values.length; i += 1) {
|
|
677
|
+
sum += values[i];
|
|
678
|
+
if (i >= period) {
|
|
679
|
+
sum -= values[i - period];
|
|
680
|
+
}
|
|
681
|
+
if (i >= period - 1) {
|
|
682
|
+
series.push(sum / period);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return series;
|
|
686
|
+
}
|
|
687
|
+
function computeSma(values, period) {
|
|
688
|
+
const series = computeSmaSeries(values, period);
|
|
689
|
+
return series.length > 0 ? series[series.length - 1] : null;
|
|
690
|
+
}
|
|
691
|
+
function computeEmaSeries(values, period) {
|
|
692
|
+
if (values.length < period)
|
|
693
|
+
return [];
|
|
694
|
+
const k = 2 / (period + 1);
|
|
695
|
+
const start = values.slice(0, period);
|
|
696
|
+
let ema = start.reduce((sum, value) => sum + value, 0) / period;
|
|
697
|
+
const series = [ema];
|
|
698
|
+
for (let i = period;i < values.length; i += 1) {
|
|
699
|
+
ema = values[i] * k + ema * (1 - k);
|
|
700
|
+
series.push(ema);
|
|
701
|
+
}
|
|
702
|
+
return series;
|
|
703
|
+
}
|
|
704
|
+
function computeEma(values, period) {
|
|
705
|
+
const series = computeEmaSeries(values, period);
|
|
706
|
+
return series.length > 0 ? series[series.length - 1] : null;
|
|
707
|
+
}
|
|
708
|
+
function computeMacd(values) {
|
|
709
|
+
const emaFast = computeEmaSeries(values, 12);
|
|
710
|
+
const emaSlow = computeEmaSeries(values, 26);
|
|
711
|
+
if (emaFast.length === 0 || emaSlow.length === 0)
|
|
712
|
+
return null;
|
|
713
|
+
const offset = emaFast.length - emaSlow.length;
|
|
714
|
+
const macdSeries = emaSlow.map((value, idx) => emaFast[idx + offset] - value);
|
|
715
|
+
const signalSeries = computeEmaSeries(macdSeries, 9);
|
|
716
|
+
if (signalSeries.length === 0)
|
|
717
|
+
return null;
|
|
718
|
+
const macd = macdSeries[macdSeries.length - 1];
|
|
719
|
+
const signalLine = signalSeries[signalSeries.length - 1];
|
|
720
|
+
return {
|
|
721
|
+
macd,
|
|
722
|
+
signalLine,
|
|
723
|
+
histogram: macd - signalLine
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
function computeMaCross(values, type, fastPeriod, slowPeriod) {
|
|
727
|
+
if (values.length < slowPeriod)
|
|
728
|
+
return null;
|
|
729
|
+
const fastSeries = type === "ema" ? computeEmaSeries(values, fastPeriod) : computeSmaSeries(values, fastPeriod);
|
|
730
|
+
const slowSeries = type === "ema" ? computeEmaSeries(values, slowPeriod) : computeSmaSeries(values, slowPeriod);
|
|
731
|
+
if (fastSeries.length === 0 || slowSeries.length === 0)
|
|
732
|
+
return null;
|
|
733
|
+
const offset = fastSeries.length - slowSeries.length;
|
|
734
|
+
if (offset < 0)
|
|
735
|
+
return null;
|
|
736
|
+
const lastIndex = slowSeries.length - 1;
|
|
737
|
+
const fastNow = fastSeries[lastIndex + offset];
|
|
738
|
+
const slowNow = slowSeries[lastIndex];
|
|
739
|
+
const prevIndex = lastIndex - 1;
|
|
740
|
+
const fastPrev = prevIndex >= 0 ? fastSeries[prevIndex + offset] : null;
|
|
741
|
+
const slowPrev = prevIndex >= 0 ? slowSeries[prevIndex] : null;
|
|
742
|
+
let signal = "neutral";
|
|
743
|
+
if (fastPrev != null && slowPrev != null) {
|
|
744
|
+
if (fastPrev <= slowPrev && fastNow > slowNow) {
|
|
745
|
+
signal = "bullish-cross";
|
|
746
|
+
} else if (fastPrev >= slowPrev && fastNow < slowNow) {
|
|
747
|
+
signal = "bearish-cross";
|
|
748
|
+
} else if (fastNow > slowNow) {
|
|
749
|
+
signal = "bullish";
|
|
750
|
+
} else if (fastNow < slowNow) {
|
|
751
|
+
signal = "bearish";
|
|
752
|
+
}
|
|
753
|
+
} else if (fastNow > slowNow) {
|
|
754
|
+
signal = "bullish";
|
|
755
|
+
} else if (fastNow < slowNow) {
|
|
756
|
+
signal = "bearish";
|
|
757
|
+
}
|
|
758
|
+
return { fast: fastNow, slow: slowNow, signal };
|
|
759
|
+
}
|
|
760
|
+
function computeBollinger(values, period = 20, multiplier = 2) {
|
|
761
|
+
if (values.length < period)
|
|
762
|
+
return null;
|
|
763
|
+
const slice = values.slice(-period);
|
|
764
|
+
const mean = slice.reduce((sum, value) => sum + value, 0) / period;
|
|
765
|
+
const variance = slice.reduce((sum, value) => sum + Math.pow(value - mean, 2), 0) / period;
|
|
766
|
+
const stdDev = Math.sqrt(variance);
|
|
767
|
+
return {
|
|
768
|
+
middle: mean,
|
|
769
|
+
upper: mean + multiplier * stdDev,
|
|
770
|
+
lower: mean - multiplier * stdDev
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
function computePriceChange(values, lookback = 24) {
|
|
774
|
+
if (values.length < 2)
|
|
775
|
+
return null;
|
|
776
|
+
const current = values[values.length - 1];
|
|
777
|
+
const index = Math.max(0, values.length - 1 - lookback);
|
|
778
|
+
const previous = values[index];
|
|
779
|
+
if (previous === 0)
|
|
780
|
+
return null;
|
|
781
|
+
const percent = (current - previous) / previous * 100;
|
|
782
|
+
return {
|
|
783
|
+
previous,
|
|
784
|
+
percent
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
function computeAtr(bars, period = 14) {
|
|
788
|
+
if (bars.length < period + 1)
|
|
789
|
+
return null;
|
|
790
|
+
const ranges = [];
|
|
791
|
+
for (let i = 1;i < bars.length; i += 1) {
|
|
792
|
+
const current = bars[i];
|
|
793
|
+
const prev = bars[i - 1];
|
|
794
|
+
const rangeHighLow = current.high - current.low;
|
|
795
|
+
const rangeHighClose = Math.abs(current.high - prev.close);
|
|
796
|
+
const rangeLowClose = Math.abs(current.low - prev.close);
|
|
797
|
+
ranges.push(Math.max(rangeHighLow, rangeHighClose, rangeLowClose));
|
|
798
|
+
}
|
|
799
|
+
if (ranges.length < period)
|
|
800
|
+
return null;
|
|
801
|
+
const slice = ranges.slice(-period);
|
|
802
|
+
const sum = slice.reduce((total, value) => total + value, 0);
|
|
803
|
+
return sum / period;
|
|
804
|
+
}
|
|
805
|
+
// src/config.ts
|
|
806
|
+
import {promises as fs2} from "node:fs";
|
|
807
|
+
import os2 from "node:os";
|
|
808
|
+
import path2 from "node:path";
|
|
809
|
+
function getConfigPath() {
|
|
810
|
+
return getGlobalConfigPath();
|
|
811
|
+
}
|
|
812
|
+
var getGlobalConfigPath = function() {
|
|
813
|
+
return path2.join(os2.homedir(), GLOBAL_DIRNAME, GLOBAL_CONFIG_FILENAME);
|
|
814
|
+
};
|
|
815
|
+
async function loadConfigFile(filePath) {
|
|
816
|
+
try {
|
|
817
|
+
const raw = await fs2.readFile(filePath, "utf-8");
|
|
818
|
+
return JSON.parse(raw);
|
|
819
|
+
} catch {
|
|
820
|
+
return {};
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
async function loadGlobalConfig() {
|
|
824
|
+
return loadConfigFile(getGlobalConfigPath());
|
|
825
|
+
}
|
|
826
|
+
async function loadConfig() {
|
|
827
|
+
return loadGlobalConfig();
|
|
828
|
+
}
|
|
829
|
+
async function saveConfig(next) {
|
|
830
|
+
const filePath = getGlobalConfigPath();
|
|
831
|
+
await fs2.mkdir(path2.dirname(filePath), { recursive: true });
|
|
832
|
+
const payload = JSON.stringify(Object.fromEntries(Object.entries(next).filter(([, value]) => value !== undefined && value !== null)), null, 2);
|
|
833
|
+
await fs2.writeFile(filePath, payload, "utf-8");
|
|
834
|
+
}
|
|
835
|
+
async function saveGlobalConfig(next) {
|
|
836
|
+
const filePath = getGlobalConfigPath();
|
|
837
|
+
await fs2.mkdir(path2.dirname(filePath), { recursive: true });
|
|
838
|
+
const current = await loadGlobalConfig();
|
|
839
|
+
const merged = { ...current };
|
|
840
|
+
for (const [key, value] of Object.entries(next)) {
|
|
841
|
+
if (value === undefined)
|
|
842
|
+
continue;
|
|
843
|
+
if (value === null) {
|
|
844
|
+
delete merged[key];
|
|
845
|
+
} else {
|
|
846
|
+
merged[key] = value;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
const payload = JSON.stringify(merged, null, 2);
|
|
850
|
+
await fs2.writeFile(filePath, payload, "utf-8");
|
|
851
|
+
}
|
|
852
|
+
var GLOBAL_DIRNAME = ".openpond";
|
|
853
|
+
var GLOBAL_CONFIG_FILENAME = "config.json";
|
|
854
|
+
|
|
855
|
+
// src/index.ts
|
|
856
|
+
var resolveUrl = function(value) {
|
|
857
|
+
return value.replace(/\/$/, "");
|
|
858
|
+
};
|
|
859
|
+
var resolveBaseUrl = function(options) {
|
|
860
|
+
const envBase = process.env.OPENPOND_BASE_URL;
|
|
861
|
+
const base = options.baseUrl || envBase || DEFAULT_BASE_URL;
|
|
862
|
+
return resolveUrl(base.trim());
|
|
863
|
+
};
|
|
864
|
+
var resolveApiUrl = function(options) {
|
|
865
|
+
const envBase = process.env.OPENPOND_API_URL;
|
|
866
|
+
const base = options.apiUrl || envBase || DEFAULT_API_URL;
|
|
867
|
+
return resolveUrl(base.trim());
|
|
868
|
+
};
|
|
869
|
+
var resolveToolUrl = function(options, baseUrl) {
|
|
870
|
+
const envBase = process.env.OPENPOND_TOOL_URL;
|
|
871
|
+
const base = options.toolUrl || envBase || baseUrl;
|
|
872
|
+
return resolveUrl(base.trim());
|
|
873
|
+
};
|
|
874
|
+
var resolveApiKey = function(options) {
|
|
875
|
+
const explicit = options.apiKey?.trim();
|
|
876
|
+
if (explicit)
|
|
877
|
+
return explicit;
|
|
878
|
+
const envKey = process.env.OPENPOND_API_KEY?.trim();
|
|
879
|
+
if (envKey)
|
|
880
|
+
return envKey;
|
|
881
|
+
throw new Error("OPENPOND_API_KEY is required");
|
|
882
|
+
};
|
|
883
|
+
var parseHandleRepo = function(value) {
|
|
884
|
+
const parts = value.split("/").filter(Boolean);
|
|
885
|
+
if (parts.length !== 2) {
|
|
886
|
+
throw new Error("expected <handle>/<repo>");
|
|
887
|
+
}
|
|
888
|
+
return { handle: parts[0], repo: parts[1] };
|
|
889
|
+
};
|
|
890
|
+
var normalizeRepoName = function(value) {
|
|
891
|
+
return (value || "").trim().toLowerCase();
|
|
892
|
+
};
|
|
893
|
+
var normalizeToolSummary = function(tool) {
|
|
894
|
+
if (!tool || typeof tool !== "object") {
|
|
895
|
+
return { name: "unknown", raw: tool };
|
|
896
|
+
}
|
|
897
|
+
const record = tool;
|
|
898
|
+
const profile = record.profile || record.function;
|
|
899
|
+
const name = typeof record.name === "string" ? record.name : typeof profile?.name === "string" ? profile.name : "unknown";
|
|
900
|
+
const description = typeof record.description === "string" ? record.description : typeof profile?.description === "string" ? profile.description : undefined;
|
|
901
|
+
return { name, description, raw: tool };
|
|
902
|
+
};
|
|
903
|
+
var extractDeploymentTools = function(detail) {
|
|
904
|
+
if (!detail)
|
|
905
|
+
return [];
|
|
906
|
+
if (Array.isArray(detail.toolsJson)) {
|
|
907
|
+
return detail.toolsJson;
|
|
908
|
+
}
|
|
909
|
+
if (detail.metadataJson && typeof detail.metadataJson === "object") {
|
|
910
|
+
const metadataTools = detail.metadataJson.tools;
|
|
911
|
+
if (Array.isArray(metadataTools)) {
|
|
912
|
+
return metadataTools;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
return [];
|
|
916
|
+
};
|
|
917
|
+
async function resolveAppTarget(params) {
|
|
918
|
+
const { handle, repo } = parseHandleRepo(params.target);
|
|
919
|
+
const apps = await fetchAppsWithCache({
|
|
920
|
+
apiBase: params.apiBase,
|
|
921
|
+
apiKey: params.apiKey,
|
|
922
|
+
useCache: params.useCache,
|
|
923
|
+
cacheTtlMs: params.cacheTtlMs,
|
|
924
|
+
forceRefresh: params.forceRefresh
|
|
925
|
+
});
|
|
926
|
+
const normalizedRepo = normalizeRepoName(repo);
|
|
927
|
+
const match = apps.find((app) => {
|
|
928
|
+
if (app.handle && app.handle !== handle) {
|
|
929
|
+
return false;
|
|
930
|
+
}
|
|
931
|
+
const candidates = [
|
|
932
|
+
app.repo,
|
|
933
|
+
app.gitRepo,
|
|
934
|
+
app.internalToolName,
|
|
935
|
+
app.name,
|
|
936
|
+
app.id
|
|
937
|
+
].map(normalizeRepoName);
|
|
938
|
+
return candidates.includes(normalizedRepo);
|
|
939
|
+
});
|
|
940
|
+
if (!match) {
|
|
941
|
+
throw new Error(`app not found for ${handle}/${repo}`);
|
|
942
|
+
}
|
|
943
|
+
return { app: match, handle, repo };
|
|
944
|
+
}
|
|
945
|
+
async function fetchAppsWithCache(params) {
|
|
946
|
+
if (params.useCache && !params.forceRefresh) {
|
|
947
|
+
const cached = await getCachedApps({
|
|
948
|
+
apiBase: params.apiBase,
|
|
949
|
+
apiKey: params.apiKey,
|
|
950
|
+
ttlMs: params.cacheTtlMs
|
|
951
|
+
});
|
|
952
|
+
if (cached) {
|
|
953
|
+
return cached;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
const apps = await listApps(params.apiBase, params.apiKey);
|
|
957
|
+
if (params.useCache) {
|
|
958
|
+
await setCachedApps({
|
|
959
|
+
apiBase: params.apiBase,
|
|
960
|
+
apiKey: params.apiKey,
|
|
961
|
+
apps
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
return apps;
|
|
965
|
+
}
|
|
966
|
+
async function fetchToolsWithCache(params) {
|
|
967
|
+
if (params.useCache && !params.forceRefresh) {
|
|
968
|
+
const cached = await getCachedTools({
|
|
969
|
+
apiBase: params.apiBase,
|
|
970
|
+
apiKey: params.apiKey,
|
|
971
|
+
ttlMs: params.cacheTtlMs
|
|
972
|
+
});
|
|
973
|
+
if (cached) {
|
|
974
|
+
return cached;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
const result = await listUserTools(params.apiBase, params.apiKey);
|
|
978
|
+
const tools = Array.isArray(result.tools) ? result.tools : [];
|
|
979
|
+
if (params.useCache) {
|
|
980
|
+
await setCachedTools({
|
|
981
|
+
apiBase: params.apiBase,
|
|
982
|
+
apiKey: params.apiKey,
|
|
983
|
+
tools
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
return tools;
|
|
987
|
+
}
|
|
988
|
+
var normalizeMethod = function(method) {
|
|
989
|
+
if (!method)
|
|
990
|
+
return "POST";
|
|
991
|
+
const upper = method.toUpperCase();
|
|
992
|
+
switch (upper) {
|
|
993
|
+
case "GET":
|
|
994
|
+
case "POST":
|
|
995
|
+
case "PUT":
|
|
996
|
+
case "DELETE":
|
|
997
|
+
return upper;
|
|
998
|
+
default:
|
|
999
|
+
throw new Error("method must be GET, POST, PUT, or DELETE");
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
1002
|
+
async function consumeAgentCreateStream(response, callbacks) {
|
|
1003
|
+
let conversationId;
|
|
1004
|
+
let appId;
|
|
1005
|
+
let deploymentId;
|
|
1006
|
+
await consumeStream(response, {
|
|
1007
|
+
...callbacks,
|
|
1008
|
+
onConversationId: (id) => {
|
|
1009
|
+
conversationId = id;
|
|
1010
|
+
callbacks?.onConversationId?.(id);
|
|
1011
|
+
},
|
|
1012
|
+
onItems: (items) => {
|
|
1013
|
+
for (const item of items) {
|
|
1014
|
+
const typed = item;
|
|
1015
|
+
if (!appId && typeof typed.appId === "string") {
|
|
1016
|
+
appId = typed.appId;
|
|
1017
|
+
callbacks?.onAppId?.(typed.appId);
|
|
1018
|
+
}
|
|
1019
|
+
if (!deploymentId && typeof typed.deploymentId === "string") {
|
|
1020
|
+
deploymentId = typed.deploymentId;
|
|
1021
|
+
callbacks?.onDeploymentId?.(typed.deploymentId);
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
callbacks?.onItems?.(items);
|
|
1025
|
+
}
|
|
1026
|
+
});
|
|
1027
|
+
return { conversationId, appId, deploymentId };
|
|
1028
|
+
}
|
|
1029
|
+
function createClient(options) {
|
|
1030
|
+
const apiKey = resolveApiKey(options);
|
|
1031
|
+
const baseUrl = resolveBaseUrl(options);
|
|
1032
|
+
const apiUrl = resolveApiUrl(options);
|
|
1033
|
+
const toolUrl = resolveToolUrl(options, baseUrl);
|
|
1034
|
+
const cacheTtlMs = options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
1035
|
+
const useCache = options.useCache !== false;
|
|
1036
|
+
const refreshCache = async () => {
|
|
1037
|
+
if (!useCache)
|
|
1038
|
+
return;
|
|
1039
|
+
await Promise.all([
|
|
1040
|
+
fetchAppsWithCache({
|
|
1041
|
+
apiBase: apiUrl,
|
|
1042
|
+
apiKey,
|
|
1043
|
+
useCache,
|
|
1044
|
+
cacheTtlMs,
|
|
1045
|
+
forceRefresh: true
|
|
1046
|
+
}),
|
|
1047
|
+
fetchToolsWithCache({
|
|
1048
|
+
apiBase: apiUrl,
|
|
1049
|
+
apiKey,
|
|
1050
|
+
useCache,
|
|
1051
|
+
cacheTtlMs,
|
|
1052
|
+
forceRefresh: true
|
|
1053
|
+
})
|
|
1054
|
+
]);
|
|
1055
|
+
};
|
|
1056
|
+
const resolveLatestDeploymentId = async (appId, branch) => {
|
|
1057
|
+
const latest = await getLatestDeploymentForApp(apiUrl, apiKey, appId, {
|
|
1058
|
+
branch
|
|
1059
|
+
});
|
|
1060
|
+
return latest?.id ?? null;
|
|
1061
|
+
};
|
|
1062
|
+
return {
|
|
1063
|
+
baseUrl,
|
|
1064
|
+
apiUrl,
|
|
1065
|
+
toolUrl,
|
|
1066
|
+
apiKey,
|
|
1067
|
+
tool: {
|
|
1068
|
+
list: async (target, options2) => {
|
|
1069
|
+
const { app } = await resolveAppTarget({
|
|
1070
|
+
apiBase: apiUrl,
|
|
1071
|
+
apiKey,
|
|
1072
|
+
target,
|
|
1073
|
+
useCache,
|
|
1074
|
+
cacheTtlMs,
|
|
1075
|
+
forceRefresh: options2?.forceRefresh
|
|
1076
|
+
});
|
|
1077
|
+
const deploymentId = options2?.deploymentId || await resolveLatestDeploymentId(app.id, options2?.branch);
|
|
1078
|
+
if (!deploymentId) {
|
|
1079
|
+
return { app, deploymentId: null, tools: [] };
|
|
1080
|
+
}
|
|
1081
|
+
const detail = await getDeploymentDetail(apiUrl, apiKey, deploymentId);
|
|
1082
|
+
const rawTools = extractDeploymentTools(detail);
|
|
1083
|
+
const tools = rawTools.map(normalizeToolSummary);
|
|
1084
|
+
return { app, deploymentId, tools };
|
|
1085
|
+
},
|
|
1086
|
+
run: async (target, toolName, options2) => {
|
|
1087
|
+
const { app } = await resolveAppTarget({
|
|
1088
|
+
apiBase: apiUrl,
|
|
1089
|
+
apiKey,
|
|
1090
|
+
target,
|
|
1091
|
+
useCache,
|
|
1092
|
+
cacheTtlMs,
|
|
1093
|
+
forceRefresh: options2?.forceRefresh
|
|
1094
|
+
});
|
|
1095
|
+
const deploymentId = options2?.deploymentId || await resolveLatestDeploymentId(app.id, options2?.branch);
|
|
1096
|
+
if (!deploymentId) {
|
|
1097
|
+
throw new Error("no deployments found");
|
|
1098
|
+
}
|
|
1099
|
+
const method = normalizeMethod(options2?.method);
|
|
1100
|
+
return executeHostedTool(toolUrl, apiKey, {
|
|
1101
|
+
appId: app.id,
|
|
1102
|
+
deploymentId,
|
|
1103
|
+
toolName,
|
|
1104
|
+
method,
|
|
1105
|
+
body: options2?.body,
|
|
1106
|
+
headers: options2?.headers
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
},
|
|
1110
|
+
deploy: {
|
|
1111
|
+
watch: async (target, options2) => {
|
|
1112
|
+
const { app } = await resolveAppTarget({
|
|
1113
|
+
apiBase: apiUrl,
|
|
1114
|
+
apiKey,
|
|
1115
|
+
target,
|
|
1116
|
+
useCache,
|
|
1117
|
+
cacheTtlMs,
|
|
1118
|
+
forceRefresh: options2?.forceRefresh
|
|
1119
|
+
});
|
|
1120
|
+
const deploymentId = options2?.deploymentId || await resolveLatestDeploymentId(app.id, options2?.branch);
|
|
1121
|
+
if (!deploymentId) {
|
|
1122
|
+
throw new Error("no deployments found");
|
|
1123
|
+
}
|
|
1124
|
+
const intervalMs = options2?.intervalMs ?? 5000;
|
|
1125
|
+
const timeoutMs = options2?.timeoutMs ?? 4 * 60 * 1000;
|
|
1126
|
+
const logs = [];
|
|
1127
|
+
const seen = new Set;
|
|
1128
|
+
const startedAt = Date.now();
|
|
1129
|
+
let status = null;
|
|
1130
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
1131
|
+
const batch = await getDeploymentLogs(apiUrl, apiKey, deploymentId);
|
|
1132
|
+
for (const log of batch) {
|
|
1133
|
+
if (seen.has(log.id))
|
|
1134
|
+
continue;
|
|
1135
|
+
seen.add(log.id);
|
|
1136
|
+
logs.push(log);
|
|
1137
|
+
options2?.onLog?.(log);
|
|
1138
|
+
}
|
|
1139
|
+
const statusResponse = await getDeploymentStatus(apiUrl, apiKey, deploymentId);
|
|
1140
|
+
status = statusResponse.status ?? null;
|
|
1141
|
+
options2?.onStatus?.(status);
|
|
1142
|
+
if (status === "failed") {
|
|
1143
|
+
return { deploymentId, status, logs };
|
|
1144
|
+
}
|
|
1145
|
+
if (status === "running" || status === "deployed") {
|
|
1146
|
+
return { deploymentId, status, logs };
|
|
1147
|
+
}
|
|
1148
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
1149
|
+
}
|
|
1150
|
+
return { deploymentId, status: "timeout", logs };
|
|
1151
|
+
}
|
|
1152
|
+
},
|
|
1153
|
+
template: {
|
|
1154
|
+
status: async (target, options2) => {
|
|
1155
|
+
const { app } = await resolveAppTarget({
|
|
1156
|
+
apiBase: apiUrl,
|
|
1157
|
+
apiKey,
|
|
1158
|
+
target,
|
|
1159
|
+
useCache,
|
|
1160
|
+
cacheTtlMs,
|
|
1161
|
+
forceRefresh: options2?.forceRefresh
|
|
1162
|
+
});
|
|
1163
|
+
return getTemplateStatus(apiUrl, apiKey, app.id);
|
|
1164
|
+
},
|
|
1165
|
+
branches: async (target, options2) => {
|
|
1166
|
+
const { app } = await resolveAppTarget({
|
|
1167
|
+
apiBase: apiUrl,
|
|
1168
|
+
apiKey,
|
|
1169
|
+
target,
|
|
1170
|
+
useCache,
|
|
1171
|
+
cacheTtlMs,
|
|
1172
|
+
forceRefresh: options2?.forceRefresh
|
|
1173
|
+
});
|
|
1174
|
+
return listTemplateBranches(apiUrl, apiKey, app.id);
|
|
1175
|
+
},
|
|
1176
|
+
update: async (target, options2) => {
|
|
1177
|
+
const { app } = await resolveAppTarget({
|
|
1178
|
+
apiBase: apiUrl,
|
|
1179
|
+
apiKey,
|
|
1180
|
+
target,
|
|
1181
|
+
useCache,
|
|
1182
|
+
cacheTtlMs,
|
|
1183
|
+
forceRefresh: options2?.forceRefresh
|
|
1184
|
+
});
|
|
1185
|
+
const environment = options2?.environment === "preview" ? "preview" : "production";
|
|
1186
|
+
return deployLatestTemplate(apiUrl, apiKey, app.id, { environment });
|
|
1187
|
+
}
|
|
1188
|
+
},
|
|
1189
|
+
apps: {
|
|
1190
|
+
list: async (options2) => {
|
|
1191
|
+
const apps = await fetchAppsWithCache({
|
|
1192
|
+
apiBase: apiUrl,
|
|
1193
|
+
apiKey,
|
|
1194
|
+
useCache,
|
|
1195
|
+
cacheTtlMs,
|
|
1196
|
+
forceRefresh: options2?.forceRefresh
|
|
1197
|
+
});
|
|
1198
|
+
if (!options2?.handle)
|
|
1199
|
+
return apps;
|
|
1200
|
+
return apps.filter((app) => app.handle === options2.handle);
|
|
1201
|
+
},
|
|
1202
|
+
tools: async (options2) => {
|
|
1203
|
+
return fetchToolsWithCache({
|
|
1204
|
+
apiBase: apiUrl,
|
|
1205
|
+
apiKey,
|
|
1206
|
+
useCache,
|
|
1207
|
+
cacheTtlMs,
|
|
1208
|
+
forceRefresh: options2?.forceRefresh
|
|
1209
|
+
});
|
|
1210
|
+
},
|
|
1211
|
+
performance: async (options2) => {
|
|
1212
|
+
return getUserPerformance(apiUrl, apiKey, { appId: options2?.appId });
|
|
1213
|
+
},
|
|
1214
|
+
agentCreate: async (input, callbacks) => {
|
|
1215
|
+
const { refreshCache: refreshCacheFlag, ...rest } = input;
|
|
1216
|
+
const payload = {
|
|
1217
|
+
...rest,
|
|
1218
|
+
streamDeployLogs: rest.streamDeployLogs ?? true
|
|
1219
|
+
};
|
|
1220
|
+
const response = await createAgentFromPrompt(apiUrl, apiKey, payload);
|
|
1221
|
+
if (!response.ok) {
|
|
1222
|
+
const text = await response.text().catch(() => "");
|
|
1223
|
+
throw new Error(`agent create failed: ${response.status} ${text}`);
|
|
1224
|
+
}
|
|
1225
|
+
const result = await consumeAgentCreateStream(response, callbacks);
|
|
1226
|
+
if (useCache && refreshCacheFlag !== false) {
|
|
1227
|
+
try {
|
|
1228
|
+
await refreshCache();
|
|
1229
|
+
} catch {
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
return result;
|
|
1233
|
+
},
|
|
1234
|
+
toolsExecute: async (input) => {
|
|
1235
|
+
return executeUserTool(apiUrl, apiKey, input);
|
|
1236
|
+
},
|
|
1237
|
+
deploy: async (input) => {
|
|
1238
|
+
return deployApp(apiUrl, apiKey, input.appId, {
|
|
1239
|
+
environment: input.environment,
|
|
1240
|
+
commitSha: input.commitSha,
|
|
1241
|
+
branch: input.branch
|
|
1242
|
+
});
|
|
1243
|
+
},
|
|
1244
|
+
envGet: async (input) => {
|
|
1245
|
+
return getAppEnvironment(apiUrl, apiKey, input.appId);
|
|
1246
|
+
},
|
|
1247
|
+
envSet: async (input) => {
|
|
1248
|
+
return updateAppEnvironment(apiUrl, apiKey, input.appId, {
|
|
1249
|
+
envVars: input.envVars
|
|
1250
|
+
});
|
|
1251
|
+
},
|
|
1252
|
+
positionsTx: async (input) => {
|
|
1253
|
+
const method = input?.method ?? "POST";
|
|
1254
|
+
if (method !== "GET" && method !== "POST") {
|
|
1255
|
+
throw new Error("method must be GET or POST");
|
|
1256
|
+
}
|
|
1257
|
+
let query = input?.query;
|
|
1258
|
+
if (!query && input?.params) {
|
|
1259
|
+
query = {};
|
|
1260
|
+
for (const [key, value] of Object.entries(input.params)) {
|
|
1261
|
+
if (value === undefined)
|
|
1262
|
+
continue;
|
|
1263
|
+
query[key] = typeof value === "string" ? value : JSON.stringify(value);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
return submitPositionsTx(apiUrl, apiKey, {
|
|
1267
|
+
method,
|
|
1268
|
+
body: method === "POST" ? input?.body : undefined,
|
|
1269
|
+
query
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
},
|
|
1273
|
+
repo: {
|
|
1274
|
+
create: async (input) => {
|
|
1275
|
+
const { refreshCache: refreshCacheFlag, ...rest } = input;
|
|
1276
|
+
const result = await createRepo(apiUrl, apiKey, rest);
|
|
1277
|
+
if (useCache && refreshCacheFlag !== false) {
|
|
1278
|
+
try {
|
|
1279
|
+
await refreshCache();
|
|
1280
|
+
} catch {
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
return result;
|
|
1284
|
+
}
|
|
1285
|
+
},
|
|
1286
|
+
cache: {
|
|
1287
|
+
refresh: refreshCache
|
|
1288
|
+
}
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
var DEFAULT_BASE_URL = "https://openpond.ai";
|
|
1292
|
+
var DEFAULT_API_URL = "https://api.openpond.ai";
|
|
1293
|
+
export {
|
|
1294
|
+
updateAppEnvironment,
|
|
1295
|
+
submitPositionsTx,
|
|
1296
|
+
startDeviceLogin,
|
|
1297
|
+
setCachedTools,
|
|
1298
|
+
setCachedApps,
|
|
1299
|
+
saveGlobalConfig,
|
|
1300
|
+
saveConfig,
|
|
1301
|
+
resolveWorkerBaseUrl,
|
|
1302
|
+
postAgentDigest,
|
|
1303
|
+
pollDeviceLogin,
|
|
1304
|
+
normalizeDataFrames,
|
|
1305
|
+
loadGlobalConfig,
|
|
1306
|
+
loadConfig,
|
|
1307
|
+
listUserTools,
|
|
1308
|
+
listTemplateBranches,
|
|
1309
|
+
listApps,
|
|
1310
|
+
getUserPerformance,
|
|
1311
|
+
getTemplateStatus,
|
|
1312
|
+
getLatestDeploymentForApp,
|
|
1313
|
+
getDeploymentStatus,
|
|
1314
|
+
getDeploymentLogs,
|
|
1315
|
+
getDeploymentDetail,
|
|
1316
|
+
getConfigPath,
|
|
1317
|
+
getCachedTools,
|
|
1318
|
+
getCachedApps,
|
|
1319
|
+
getAppEnvironment,
|
|
1320
|
+
formatStreamItem,
|
|
1321
|
+
fetchToolManifest,
|
|
1322
|
+
executeUserTool,
|
|
1323
|
+
executeHostedTool,
|
|
1324
|
+
deployLatestTemplate,
|
|
1325
|
+
deployApp,
|
|
1326
|
+
createRepo,
|
|
1327
|
+
createLocalProject,
|
|
1328
|
+
createHeadlessApps,
|
|
1329
|
+
createClient,
|
|
1330
|
+
createAgentFromPrompt,
|
|
1331
|
+
consumeStream,
|
|
1332
|
+
consumeAgentCreateStream,
|
|
1333
|
+
computeSmaSeries,
|
|
1334
|
+
computeSma,
|
|
1335
|
+
computeRsi,
|
|
1336
|
+
computePriceChange,
|
|
1337
|
+
computeMacd,
|
|
1338
|
+
computeMaCross,
|
|
1339
|
+
computeEmaSeries,
|
|
1340
|
+
computeEma,
|
|
1341
|
+
computeBollinger,
|
|
1342
|
+
computeAtr,
|
|
1343
|
+
commitFiles,
|
|
1344
|
+
apiFetch,
|
|
1345
|
+
DEFAULT_CACHE_TTL_MS
|
|
1346
|
+
};
|