lynkr 7.2.5 → 8.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/config/model-tiers.json +89 -0
- package/install.sh +6 -1
- package/package.json +4 -2
- package/scripts/setup.js +0 -1
- package/src/agents/executor.js +14 -6
- package/src/api/middleware/session.js +15 -2
- package/src/api/openai-router.js +162 -37
- package/src/api/providers-handler.js +15 -1
- package/src/api/router.js +107 -2
- package/src/budget/index.js +4 -3
- package/src/clients/databricks.js +431 -234
- package/src/clients/gpt-utils.js +181 -0
- package/src/clients/ollama-utils.js +66 -140
- package/src/clients/routing.js +0 -1
- package/src/clients/standard-tools.js +99 -3
- package/src/config/index.js +133 -35
- package/src/context/toon.js +173 -0
- package/src/logger/index.js +23 -0
- package/src/orchestrator/index.js +688 -213
- package/src/routing/agentic-detector.js +320 -0
- package/src/routing/complexity-analyzer.js +202 -2
- package/src/routing/cost-optimizer.js +305 -0
- package/src/routing/index.js +168 -159
- package/src/routing/model-tiers.js +365 -0
- package/src/server.js +4 -14
- package/src/sessions/cleanup.js +3 -3
- package/src/sessions/record.js +10 -1
- package/src/sessions/store.js +7 -2
- package/src/tools/agent-task.js +48 -1
- package/src/tools/index.js +19 -2
- package/src/tools/lazy-loader.js +7 -0
- package/src/tools/tinyfish.js +358 -0
- package/src/tools/truncate.js +1 -0
- package/.github/FUNDING.yml +0 -15
- package/.github/workflows/README.md +0 -215
- package/.github/workflows/ci.yml +0 -69
- package/.github/workflows/index.yml +0 -62
- package/.github/workflows/web-tools-tests.yml +0 -56
- package/CITATIONS.bib +0 -6
- package/CLAWROUTER_ROUTING_PLAN.md +0 -910
- package/DEPLOYMENT.md +0 -1001
- package/LYNKR-TUI-PLAN.md +0 -984
- package/PERFORMANCE-REPORT.md +0 -866
- package/PLAN-per-client-model-routing.md +0 -252
- package/ROUTER_COMPARISON.md +0 -173
- package/TIER_ROUTING_PLAN.md +0 -771
- package/docs/42642f749da6234f41b6b425c3bb07c9.txt +0 -1
- package/docs/BingSiteAuth.xml +0 -4
- package/docs/docs-style.css +0 -478
- package/docs/docs.html +0 -197
- package/docs/google5be250e608e6da39.html +0 -1
- package/docs/index.html +0 -577
- package/docs/index.md +0 -577
- package/docs/robots.txt +0 -4
- package/docs/sitemap.xml +0 -44
- package/docs/style.css +0 -1223
- package/documentation/README.md +0 -100
- package/documentation/api.md +0 -806
- package/documentation/claude-code-cli.md +0 -672
- package/documentation/codex-cli.md +0 -397
- package/documentation/contributing.md +0 -571
- package/documentation/cursor-integration.md +0 -731
- package/documentation/docker.md +0 -867
- package/documentation/embeddings.md +0 -760
- package/documentation/faq.md +0 -659
- package/documentation/features.md +0 -396
- package/documentation/headroom.md +0 -519
- package/documentation/installation.md +0 -706
- package/documentation/memory-system.md +0 -476
- package/documentation/production.md +0 -601
- package/documentation/providers.md +0 -906
- package/documentation/testing.md +0 -629
- package/documentation/token-optimization.md +0 -323
- package/documentation/tools.md +0 -697
- package/documentation/troubleshooting.md +0 -893
- package/final-test.js +0 -33
- package/headroom-sidecar/config.py +0 -93
- package/headroom-sidecar/requirements.txt +0 -14
- package/headroom-sidecar/server.py +0 -451
- package/monitor-agents.sh +0 -31
- package/scripts/audit-log-reader.js +0 -399
- package/scripts/compact-dictionary.js +0 -204
- package/scripts/test-deduplication.js +0 -448
- package/src/db/database.sqlite +0 -0
- package/test/README.md +0 -212
- package/test/azure-openai-config.test.js +0 -204
- package/test/azure-openai-error-resilience.test.js +0 -238
- package/test/azure-openai-format-conversion.test.js +0 -354
- package/test/azure-openai-integration.test.js +0 -281
- package/test/azure-openai-routing.test.js +0 -177
- package/test/azure-openai-streaming.test.js +0 -171
- package/test/bedrock-integration.test.js +0 -471
- package/test/comprehensive-test-suite.js +0 -928
- package/test/config-validation.test.js +0 -207
- package/test/cursor-integration.test.js +0 -484
- package/test/format-conversion.test.js +0 -578
- package/test/hybrid-routing-integration.test.js +0 -254
- package/test/hybrid-routing-performance.test.js +0 -418
- package/test/llamacpp-integration.test.js +0 -863
- package/test/lmstudio-integration.test.js +0 -335
- package/test/memory/extractor.test.js +0 -398
- package/test/memory/retriever.test.js +0 -613
- package/test/memory/retriever.test.js.bak +0 -585
- package/test/memory/search.test.js +0 -537
- package/test/memory/search.test.js.bak +0 -389
- package/test/memory/store.test.js +0 -344
- package/test/memory/store.test.js.bak +0 -312
- package/test/memory/surprise.test.js +0 -300
- package/test/memory-performance.test.js +0 -472
- package/test/openai-integration.test.js +0 -686
- package/test/openrouter-error-resilience.test.js +0 -418
- package/test/passthrough-mode.test.js +0 -385
- package/test/performance-benchmark.js +0 -351
- package/test/performance-tests.js +0 -528
- package/test/routing.test.js +0 -219
- package/test/web-tools.test.js +0 -329
- package/test-agents-simple.js +0 -43
- package/test-cli-connection.sh +0 -33
- package/test-learning-unit.js +0 -126
- package/test-learning.js +0 -112
- package/test-parallel-agents.sh +0 -124
- package/test-parallel-direct.js +0 -155
- package/test-subagents.sh +0 -117
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
const { URL } = require("url");
|
|
2
|
+
const { Agent } = require("undici");
|
|
3
|
+
const config = require("../config");
|
|
4
|
+
const logger = require("../logger");
|
|
5
|
+
const { registerTool } = require(".");
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Dedicated HTTP agent for TinyFish SSE streams.
|
|
9
|
+
* The default webAgent in web-client.js has a 30s bodyTimeout which is too
|
|
10
|
+
* short for browser-automation tasks that can take up to 120s.
|
|
11
|
+
*/
|
|
12
|
+
const sseAgent = new Agent({
|
|
13
|
+
connections: 10,
|
|
14
|
+
pipelining: 1,
|
|
15
|
+
keepAliveTimeout: 60000,
|
|
16
|
+
connectTimeout: 15000,
|
|
17
|
+
bodyTimeout: 0, // no body timeout — we manage timeout via AbortController
|
|
18
|
+
headersTimeout: 15000,
|
|
19
|
+
maxRedirections: 3,
|
|
20
|
+
strictContentLength: false,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Argument normalisers
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
function normalizeUrl(args) {
|
|
28
|
+
const raw = args.url ?? args.uri ?? args.href ?? args.target_url;
|
|
29
|
+
if (typeof raw !== "string" || raw.trim().length === 0) {
|
|
30
|
+
throw new Error("web_agent requires a non-empty url string.");
|
|
31
|
+
}
|
|
32
|
+
// Validate URL
|
|
33
|
+
try {
|
|
34
|
+
new URL(raw.trim());
|
|
35
|
+
} catch {
|
|
36
|
+
throw new Error(`web_agent received an invalid URL: ${raw}`);
|
|
37
|
+
}
|
|
38
|
+
return raw.trim();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function normalizeGoal(args) {
|
|
42
|
+
const goal = args.goal ?? args.task ?? args.prompt ?? args.instruction;
|
|
43
|
+
if (typeof goal !== "string" || goal.trim().length === 0) {
|
|
44
|
+
throw new Error("web_agent requires a non-empty goal string.");
|
|
45
|
+
}
|
|
46
|
+
return goal.trim();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function resolveBrowserProfile(args) {
|
|
50
|
+
const profile = args.browser_profile ?? args.browserProfile ?? config.tinyfish.browserProfile;
|
|
51
|
+
if (profile === "stealth") return "stealth";
|
|
52
|
+
return "lite";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// SSE stream consumer
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
async function consumeSSEStream(response, timeoutMs) {
|
|
60
|
+
const reader = response.body.getReader();
|
|
61
|
+
const decoder = new TextDecoder();
|
|
62
|
+
let buffer = "";
|
|
63
|
+
const startTime = Date.now();
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
while (true) {
|
|
67
|
+
if (Date.now() - startTime > timeoutMs) {
|
|
68
|
+
const err = new Error(`TinyFish SSE stream timed out after ${timeoutMs}ms`);
|
|
69
|
+
err.code = "ETIMEDOUT";
|
|
70
|
+
err.status = 504;
|
|
71
|
+
throw err;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const { done, value } = await reader.read();
|
|
75
|
+
if (done) break;
|
|
76
|
+
|
|
77
|
+
buffer += decoder.decode(value, { stream: true });
|
|
78
|
+
|
|
79
|
+
// SSE events are separated by double newlines
|
|
80
|
+
const parts = buffer.split("\n\n");
|
|
81
|
+
// Keep the last (possibly incomplete) chunk in the buffer
|
|
82
|
+
buffer = parts.pop() || "";
|
|
83
|
+
|
|
84
|
+
for (const part of parts) {
|
|
85
|
+
// Extract the data: line(s)
|
|
86
|
+
const lines = part.split("\n");
|
|
87
|
+
let dataStr = "";
|
|
88
|
+
for (const line of lines) {
|
|
89
|
+
if (line.startsWith("data: ")) {
|
|
90
|
+
dataStr += line.slice(6);
|
|
91
|
+
} else if (line.startsWith("data:")) {
|
|
92
|
+
dataStr += line.slice(5);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!dataStr) continue;
|
|
97
|
+
|
|
98
|
+
let event;
|
|
99
|
+
try {
|
|
100
|
+
event = JSON.parse(dataStr);
|
|
101
|
+
} catch {
|
|
102
|
+
// Not valid JSON — skip this SSE frame
|
|
103
|
+
logger.debug({ raw: dataStr.slice(0, 200) }, "TinyFish: non-JSON SSE frame, skipping");
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
logger.debug(
|
|
108
|
+
{ type: event.type, status: event.status },
|
|
109
|
+
"TinyFish SSE event"
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
if (event.type === "COMPLETE" || event.type === "complete") {
|
|
113
|
+
const status = (event.status ?? "").toUpperCase();
|
|
114
|
+
if (status === "COMPLETED" || status === "SUCCESS") {
|
|
115
|
+
return event.resultJson ?? event.result ?? event.data ?? event;
|
|
116
|
+
}
|
|
117
|
+
// Task failed
|
|
118
|
+
const errMsg = event.error ?? event.message ?? "TinyFish task failed";
|
|
119
|
+
const err = new Error(typeof errMsg === "string" ? errMsg : JSON.stringify(errMsg));
|
|
120
|
+
err.code = "TINYFISH_TASK_FAILED";
|
|
121
|
+
err.status = 502;
|
|
122
|
+
throw err;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Stream ended without a COMPLETE event
|
|
128
|
+
const err = new Error("TinyFish SSE stream ended without a COMPLETE event");
|
|
129
|
+
err.code = "TINYFISH_INCOMPLETE";
|
|
130
|
+
err.status = 502;
|
|
131
|
+
throw err;
|
|
132
|
+
} finally {
|
|
133
|
+
reader.releaseLock();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
// Core API call
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
async function callTinyFishAPI({ url, goal, browserProfile, proxyConfig, timeoutMs }) {
|
|
142
|
+
const endpoint = config.tinyfish.endpoint;
|
|
143
|
+
const apiKey = config.tinyfish.apiKey;
|
|
144
|
+
|
|
145
|
+
if (!apiKey) {
|
|
146
|
+
return {
|
|
147
|
+
ok: false,
|
|
148
|
+
status: 503,
|
|
149
|
+
content: JSON.stringify({
|
|
150
|
+
error: "tinyfish_not_configured",
|
|
151
|
+
message:
|
|
152
|
+
"TinyFish API key is not configured. Set TINYFISH_API_KEY in your .env file. Get a key from https://tinyfish.ai",
|
|
153
|
+
}, null, 2),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const body = {
|
|
158
|
+
url,
|
|
159
|
+
goal,
|
|
160
|
+
browserProfile,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
if (proxyConfig) {
|
|
164
|
+
body.proxy = proxyConfig;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const controller = new AbortController();
|
|
168
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
const response = await fetch(endpoint, {
|
|
172
|
+
method: "POST",
|
|
173
|
+
headers: {
|
|
174
|
+
"Content-Type": "application/json",
|
|
175
|
+
"X-API-Key": apiKey,
|
|
176
|
+
Accept: "text/event-stream",
|
|
177
|
+
},
|
|
178
|
+
body: JSON.stringify(body),
|
|
179
|
+
signal: controller.signal,
|
|
180
|
+
dispatcher: sseAgent,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Handle non-2xx responses before attempting SSE parse
|
|
184
|
+
if (!response.ok) {
|
|
185
|
+
const text = await response.text().catch(() => "");
|
|
186
|
+
const shouldRetry = response.status === 429 || response.status >= 500;
|
|
187
|
+
|
|
188
|
+
if (shouldRetry) {
|
|
189
|
+
// Retry once with 2s backoff
|
|
190
|
+
logger.warn(
|
|
191
|
+
{ status: response.status, body: text.slice(0, 200) },
|
|
192
|
+
"TinyFish API error, retrying once"
|
|
193
|
+
);
|
|
194
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
195
|
+
|
|
196
|
+
const retryController = new AbortController();
|
|
197
|
+
const retryTimeout = setTimeout(() => retryController.abort(), timeoutMs);
|
|
198
|
+
try {
|
|
199
|
+
const retryResponse = await fetch(endpoint, {
|
|
200
|
+
method: "POST",
|
|
201
|
+
headers: {
|
|
202
|
+
"Content-Type": "application/json",
|
|
203
|
+
"X-API-Key": apiKey,
|
|
204
|
+
Accept: "text/event-stream",
|
|
205
|
+
},
|
|
206
|
+
body: JSON.stringify(body),
|
|
207
|
+
signal: retryController.signal,
|
|
208
|
+
dispatcher: sseAgent,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
if (!retryResponse.ok) {
|
|
212
|
+
const retryText = await retryResponse.text().catch(() => "");
|
|
213
|
+
const err = new Error(
|
|
214
|
+
`TinyFish API error (${retryResponse.status}): ${retryResponse.statusText}`
|
|
215
|
+
);
|
|
216
|
+
err.status = retryResponse.status;
|
|
217
|
+
err.body = retryText;
|
|
218
|
+
throw err;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const result = await consumeSSEStream(retryResponse, timeoutMs);
|
|
222
|
+
return {
|
|
223
|
+
ok: true,
|
|
224
|
+
status: 200,
|
|
225
|
+
result,
|
|
226
|
+
};
|
|
227
|
+
} finally {
|
|
228
|
+
clearTimeout(retryTimeout);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const err = new Error(
|
|
233
|
+
`TinyFish API error (${response.status}): ${response.statusText}`
|
|
234
|
+
);
|
|
235
|
+
err.status = response.status;
|
|
236
|
+
err.body = text;
|
|
237
|
+
throw err;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const result = await consumeSSEStream(response, timeoutMs);
|
|
241
|
+
return {
|
|
242
|
+
ok: true,
|
|
243
|
+
status: 200,
|
|
244
|
+
result,
|
|
245
|
+
};
|
|
246
|
+
} catch (error) {
|
|
247
|
+
if (error.name === "AbortError") {
|
|
248
|
+
const err = new Error(`TinyFish request timed out after ${timeoutMs}ms`);
|
|
249
|
+
err.code = "ETIMEDOUT";
|
|
250
|
+
err.status = 504;
|
|
251
|
+
throw err;
|
|
252
|
+
}
|
|
253
|
+
throw error;
|
|
254
|
+
} finally {
|
|
255
|
+
clearTimeout(timeout);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ---------------------------------------------------------------------------
|
|
260
|
+
// Tool registration
|
|
261
|
+
// ---------------------------------------------------------------------------
|
|
262
|
+
|
|
263
|
+
function registerTinyFishTool() {
|
|
264
|
+
registerTool(
|
|
265
|
+
"web_agent",
|
|
266
|
+
async ({ args = {} }) => {
|
|
267
|
+
const url = normalizeUrl(args);
|
|
268
|
+
const goal = normalizeGoal(args);
|
|
269
|
+
const browserProfile = resolveBrowserProfile(args);
|
|
270
|
+
const timeoutMs = config.tinyfish.timeoutMs;
|
|
271
|
+
|
|
272
|
+
// Build proxy config if enabled
|
|
273
|
+
let proxyConfig = null;
|
|
274
|
+
if (config.tinyfish.proxyEnabled) {
|
|
275
|
+
proxyConfig = {
|
|
276
|
+
enabled: true,
|
|
277
|
+
country: config.tinyfish.proxyCountry,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
try {
|
|
282
|
+
const response = await callTinyFishAPI({
|
|
283
|
+
url,
|
|
284
|
+
goal,
|
|
285
|
+
browserProfile,
|
|
286
|
+
proxyConfig,
|
|
287
|
+
timeoutMs,
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
// Guard clause: not configured
|
|
291
|
+
if (!response.ok && response.status === 503) {
|
|
292
|
+
return response;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const resultStr =
|
|
296
|
+
typeof response.result === "string"
|
|
297
|
+
? response.result
|
|
298
|
+
: JSON.stringify(response.result, null, 2);
|
|
299
|
+
|
|
300
|
+
logger.debug(
|
|
301
|
+
{
|
|
302
|
+
url,
|
|
303
|
+
goal: goal.slice(0, 100),
|
|
304
|
+
browserProfile,
|
|
305
|
+
resultLength: resultStr.length,
|
|
306
|
+
},
|
|
307
|
+
"TinyFish web_agent completed"
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
ok: true,
|
|
312
|
+
status: 200,
|
|
313
|
+
content: resultStr,
|
|
314
|
+
metadata: {
|
|
315
|
+
url,
|
|
316
|
+
goal,
|
|
317
|
+
browserProfile,
|
|
318
|
+
resultLength: resultStr.length,
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
} catch (err) {
|
|
322
|
+
logger.error(
|
|
323
|
+
{ err, url, goal: goal.slice(0, 100) },
|
|
324
|
+
"web_agent request failed"
|
|
325
|
+
);
|
|
326
|
+
return {
|
|
327
|
+
ok: false,
|
|
328
|
+
status: err.status ?? 500,
|
|
329
|
+
content: JSON.stringify(
|
|
330
|
+
{
|
|
331
|
+
error: err.code ?? "web_agent_failed",
|
|
332
|
+
message: err.message,
|
|
333
|
+
url,
|
|
334
|
+
...(err.status ? { http_status: err.status } : {}),
|
|
335
|
+
},
|
|
336
|
+
null,
|
|
337
|
+
2
|
|
338
|
+
),
|
|
339
|
+
metadata: {
|
|
340
|
+
url,
|
|
341
|
+
goal,
|
|
342
|
+
error_code: err.code,
|
|
343
|
+
...(err.status ? { http_status: err.status } : {}),
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
{ category: "tinyfish" }
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function registerTinyFishTools() {
|
|
353
|
+
registerTinyFishTool();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
module.exports = {
|
|
357
|
+
registerTinyFishTools,
|
|
358
|
+
};
|
package/src/tools/truncate.js
CHANGED
|
@@ -8,6 +8,7 @@ const TRUNCATION_LIMITS = {
|
|
|
8
8
|
Glob: { maxChars: 8000, strategy: 'head' },
|
|
9
9
|
WebFetch: { maxChars: 16000, strategy: 'head' },
|
|
10
10
|
WebSearch: { maxChars: 12000, strategy: 'head' },
|
|
11
|
+
WebAgent: { maxChars: 16000, strategy: 'head' },
|
|
11
12
|
LSP: { maxChars: 8000, strategy: 'head' },
|
|
12
13
|
Edit: { maxChars: 8000, strategy: 'middle' },
|
|
13
14
|
Write: { maxChars: 8000, strategy: 'middle' },
|
package/.github/FUNDING.yml
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# These are supported funding model platforms
|
|
2
|
-
|
|
3
|
-
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
|
4
|
-
patreon: # Replace with a single Patreon username
|
|
5
|
-
open_collective: # Replace with a single Open Collective username
|
|
6
|
-
ko_fi: # Replace with a single Ko-fi username
|
|
7
|
-
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
|
8
|
-
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
|
9
|
-
liberapay: # Replace with a single Liberapay username
|
|
10
|
-
issuehunt: # Replace with a single IssueHunt username
|
|
11
|
-
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
|
12
|
-
polar: # Replace with a single Polar username
|
|
13
|
-
buy_me_a_coffee: srinivasveera
|
|
14
|
-
thanks_dev: # Replace with a single thanks.dev username
|
|
15
|
-
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
# GitHub Actions Workflows
|
|
2
|
-
|
|
3
|
-
This directory contains GitHub Actions workflows for automated testing and CI/CD.
|
|
4
|
-
|
|
5
|
-
## Available Workflows
|
|
6
|
-
|
|
7
|
-
### 1. CI Tests (`ci.yml`)
|
|
8
|
-
|
|
9
|
-
**Purpose:** Run comprehensive test suite on every push and pull request
|
|
10
|
-
|
|
11
|
-
**Triggers:**
|
|
12
|
-
- Push to `main` or `develop` branches
|
|
13
|
-
- Pull requests to `main` or `develop` branches
|
|
14
|
-
|
|
15
|
-
**What it does:**
|
|
16
|
-
- Tests on Node.js 20.x and 22.x
|
|
17
|
-
- Runs linter (`npm run lint`)
|
|
18
|
-
- Runs unit tests (`npm run test:unit`)
|
|
19
|
-
- Runs performance tests (`npm run test:performance`)
|
|
20
|
-
- Uses npm cache for faster builds
|
|
21
|
-
|
|
22
|
-
**Environment Variables:**
|
|
23
|
-
- `DATABRICKS_API_KEY=test-key` (mock value for tests)
|
|
24
|
-
- `DATABRICKS_API_BASE=http://test.com` (mock value for tests)
|
|
25
|
-
|
|
26
|
-
**Status:** Runs on every push/PR, fails if unit tests fail
|
|
27
|
-
|
|
28
|
-
---
|
|
29
|
-
|
|
30
|
-
### 2. Web Tools Tests (`web-tools-tests.yml`)
|
|
31
|
-
|
|
32
|
-
**Purpose:** Run web search tool tests when related files change
|
|
33
|
-
|
|
34
|
-
**Triggers:**
|
|
35
|
-
- Changes to web tools source files:
|
|
36
|
-
- `src/tools/web.js`
|
|
37
|
-
- `src/tools/web-client.js`
|
|
38
|
-
- `src/clients/retry.js`
|
|
39
|
-
- `src/config/index.js`
|
|
40
|
-
- `test/web-tools.test.js`
|
|
41
|
-
|
|
42
|
-
**What it does:**
|
|
43
|
-
- Runs only the web tools test suite
|
|
44
|
-
- Generates test summary in GitHub Actions UI
|
|
45
|
-
- Faster feedback for web tools changes
|
|
46
|
-
|
|
47
|
-
**Test Coverage:**
|
|
48
|
-
- HTML extraction (9 tests)
|
|
49
|
-
- HTTP keep-alive agent (2 tests)
|
|
50
|
-
- Retry logic with exponential backoff (2 tests)
|
|
51
|
-
- Configuration management (3 tests)
|
|
52
|
-
- Error handling (1 test)
|
|
53
|
-
- Performance validation (1 test)
|
|
54
|
-
- Body preview configuration (1 test)
|
|
55
|
-
|
|
56
|
-
**Total:** 19 tests
|
|
57
|
-
|
|
58
|
-
---
|
|
59
|
-
|
|
60
|
-
### 3. NPM Publish (`npm-publish.yml`)
|
|
61
|
-
|
|
62
|
-
**Purpose:** Automatically publish package to npm registry
|
|
63
|
-
|
|
64
|
-
**Triggers:**
|
|
65
|
-
- Git tags starting with `v` (e.g., `v0.1.5`)
|
|
66
|
-
- GitHub Releases created
|
|
67
|
-
|
|
68
|
-
**What it does:**
|
|
69
|
-
- Runs full test suite before publishing
|
|
70
|
-
- Checks if version already exists on npm
|
|
71
|
-
- Publishes package to npm registry (if tests pass)
|
|
72
|
-
- Prevents duplicate publishes
|
|
73
|
-
- Creates publish summary
|
|
74
|
-
|
|
75
|
-
**Requirements:**
|
|
76
|
-
- `NPM_TOKEN` secret must be configured
|
|
77
|
-
- Tests must pass
|
|
78
|
-
- Version must be new
|
|
79
|
-
|
|
80
|
-
**Status:** Only publishes on successful builds
|
|
81
|
-
|
|
82
|
-
---
|
|
83
|
-
|
|
84
|
-
### 4. Version Bump (`version-bump.yml`)
|
|
85
|
-
|
|
86
|
-
**Purpose:** Manual workflow to bump version and create releases
|
|
87
|
-
|
|
88
|
-
**Triggers:**
|
|
89
|
-
- Manual workflow dispatch (button in Actions tab)
|
|
90
|
-
|
|
91
|
-
**What it does:**
|
|
92
|
-
- Prompts for version type (patch/minor/major)
|
|
93
|
-
- Runs tests before version bump
|
|
94
|
-
- Updates package.json version
|
|
95
|
-
- Creates git commit and tag
|
|
96
|
-
- Pushes changes to repository
|
|
97
|
-
- Creates GitHub Release with changelog
|
|
98
|
-
- Triggers npm-publish workflow automatically
|
|
99
|
-
|
|
100
|
-
**Options:**
|
|
101
|
-
- `patch` - Bug fixes (0.1.4 → 0.1.5)
|
|
102
|
-
- `minor` - New features (0.1.4 → 0.2.0)
|
|
103
|
-
- `major` - Breaking changes (0.1.4 → 1.0.0)
|
|
104
|
-
|
|
105
|
-
---
|
|
106
|
-
|
|
107
|
-
### 5. IndexNow Notification (`index.yml`)
|
|
108
|
-
|
|
109
|
-
**Purpose:** Notify search engines when documentation is updated
|
|
110
|
-
|
|
111
|
-
**Triggers:**
|
|
112
|
-
- Push to `main` branch
|
|
113
|
-
- Changes in `docs/**` directory
|
|
114
|
-
|
|
115
|
-
**What it does:**
|
|
116
|
-
- Notifies Bing IndexNow about updated documentation
|
|
117
|
-
- Helps with SEO and documentation discoverability
|
|
118
|
-
|
|
119
|
-
---
|
|
120
|
-
|
|
121
|
-
## Adding Status Badges
|
|
122
|
-
|
|
123
|
-
Add these badges to your README.md:
|
|
124
|
-
|
|
125
|
-
```markdown
|
|
126
|
-

|
|
127
|
-

|
|
128
|
-

|
|
129
|
-

|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
## Running Tests Locally
|
|
133
|
-
|
|
134
|
-
Before pushing, run tests locally:
|
|
135
|
-
|
|
136
|
-
```bash
|
|
137
|
-
# Run all unit tests
|
|
138
|
-
npm run test:unit
|
|
139
|
-
|
|
140
|
-
# Run only web tools tests
|
|
141
|
-
DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com \
|
|
142
|
-
node --test test/web-tools.test.js
|
|
143
|
-
|
|
144
|
-
# Run quick tests (routing only)
|
|
145
|
-
npm run test:quick
|
|
146
|
-
|
|
147
|
-
# Run all tests including performance
|
|
148
|
-
npm test
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
## Workflow Configuration
|
|
152
|
-
|
|
153
|
-
### Required Secrets
|
|
154
|
-
|
|
155
|
-
**For npm publishing workflows:**
|
|
156
|
-
- `NPM_TOKEN` - Your npm automation token (required to publish)
|
|
157
|
-
- Get from: https://www.npmjs.com/settings/YOUR_USERNAME/tokens
|
|
158
|
-
- Type: "Automation" token
|
|
159
|
-
- Add to: Settings → Secrets → Actions → New repository secret
|
|
160
|
-
|
|
161
|
-
**For test workflows:**
|
|
162
|
-
- No secrets required (uses mock credentials)
|
|
163
|
-
|
|
164
|
-
**For IndexNow workflow:**
|
|
165
|
-
- `INDEX_NOW` - Your IndexNow API key (optional, only for docs)
|
|
166
|
-
|
|
167
|
-
### Matrix Strategy
|
|
168
|
-
|
|
169
|
-
The CI workflow uses a matrix strategy to test on multiple Node.js versions:
|
|
170
|
-
- Node.js 20.x (LTS)
|
|
171
|
-
- Node.js 22.x (Current)
|
|
172
|
-
|
|
173
|
-
This ensures compatibility across different Node versions.
|
|
174
|
-
|
|
175
|
-
## Troubleshooting
|
|
176
|
-
|
|
177
|
-
### Tests fail locally but pass in CI
|
|
178
|
-
- Check Node.js version (`node --version`)
|
|
179
|
-
- Ensure `npm ci` is used (not `npm install`)
|
|
180
|
-
- Check for platform-specific issues (macOS vs Linux)
|
|
181
|
-
|
|
182
|
-
### Tests pass locally but fail in CI
|
|
183
|
-
- Environment variables might be missing
|
|
184
|
-
- Dependencies might need updating
|
|
185
|
-
- Check GitHub Actions logs for details
|
|
186
|
-
|
|
187
|
-
### Workflow doesn't trigger
|
|
188
|
-
- Verify file paths in `on.push.paths`
|
|
189
|
-
- Check branch names match
|
|
190
|
-
- Ensure workflow file is in `.github/workflows/`
|
|
191
|
-
|
|
192
|
-
## Modifying Workflows
|
|
193
|
-
|
|
194
|
-
When making changes:
|
|
195
|
-
|
|
196
|
-
1. Test YAML syntax (use a YAML validator)
|
|
197
|
-
2. Test locally first with same commands
|
|
198
|
-
3. Create a PR to test in CI before merging
|
|
199
|
-
4. Check GitHub Actions tab for results
|
|
200
|
-
|
|
201
|
-
## Performance Considerations
|
|
202
|
-
|
|
203
|
-
- **npm cache:** Workflows cache `node_modules` for faster builds
|
|
204
|
-
- **Parallel jobs:** Tests run on multiple Node versions in parallel
|
|
205
|
-
- **Path filtering:** Web tools workflow only runs when relevant files change
|
|
206
|
-
- **continue-on-error:** Performance tests won't fail the build
|
|
207
|
-
|
|
208
|
-
## Future Improvements
|
|
209
|
-
|
|
210
|
-
Potential additions:
|
|
211
|
-
- Code coverage reporting
|
|
212
|
-
- Docker container testing
|
|
213
|
-
- E2E integration tests
|
|
214
|
-
- Deploy previews for PRs
|
|
215
|
-
- Automated dependency updates (Dependabot)
|
package/.github/workflows/ci.yml
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
name: CI Tests
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches:
|
|
6
|
-
- main
|
|
7
|
-
- develop
|
|
8
|
-
pull_request:
|
|
9
|
-
branches:
|
|
10
|
-
- main
|
|
11
|
-
- develop
|
|
12
|
-
|
|
13
|
-
jobs:
|
|
14
|
-
test:
|
|
15
|
-
name: Run Tests
|
|
16
|
-
runs-on: ubuntu-latest
|
|
17
|
-
|
|
18
|
-
strategy:
|
|
19
|
-
matrix:
|
|
20
|
-
node-version: [20.x, 22.x, 23.x, 24.x]
|
|
21
|
-
|
|
22
|
-
steps:
|
|
23
|
-
- name: Checkout code
|
|
24
|
-
uses: actions/checkout@v4
|
|
25
|
-
|
|
26
|
-
- name: Setup Node.js ${{ matrix.node-version }}
|
|
27
|
-
uses: actions/setup-node@v4
|
|
28
|
-
with:
|
|
29
|
-
node-version: ${{ matrix.node-version }}
|
|
30
|
-
cache: 'npm'
|
|
31
|
-
|
|
32
|
-
- name: Install dependencies (C++20 for native addons)
|
|
33
|
-
env:
|
|
34
|
-
CXXFLAGS: "-std=gnu++20"
|
|
35
|
-
npm_config_build_from_source: true
|
|
36
|
-
run: npm ci
|
|
37
|
-
|
|
38
|
-
- name: Run linter
|
|
39
|
-
run: npm run lint
|
|
40
|
-
continue-on-error: true
|
|
41
|
-
|
|
42
|
-
- name: Run unit tests
|
|
43
|
-
run: npm run test:unit
|
|
44
|
-
env:
|
|
45
|
-
DATABRICKS_API_KEY: test-key
|
|
46
|
-
DATABRICKS_API_BASE: http://test.com
|
|
47
|
-
|
|
48
|
-
- name: Run performance tests
|
|
49
|
-
run: npm run test:performance
|
|
50
|
-
env:
|
|
51
|
-
DATABRICKS_API_KEY: test-key
|
|
52
|
-
DATABRICKS_API_BASE: http://test.com
|
|
53
|
-
continue-on-error: true
|
|
54
|
-
|
|
55
|
-
test-summary:
|
|
56
|
-
name: Test Summary
|
|
57
|
-
runs-on: ubuntu-latest
|
|
58
|
-
needs: test
|
|
59
|
-
if: always()
|
|
60
|
-
|
|
61
|
-
steps:
|
|
62
|
-
- name: Check test results
|
|
63
|
-
run: |
|
|
64
|
-
echo "Tests completed"
|
|
65
|
-
if [ "${{ needs.test.result }}" == "failure" ]; then
|
|
66
|
-
echo "Tests failed!"
|
|
67
|
-
exit 1
|
|
68
|
-
fi
|
|
69
|
-
echo "All tests passed!"
|