cortad 0.3.0-rc.3 → 0.3.0-rc.4
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/lib/pyhook/sitecustomize.py +33 -4
- package/lib/replay.mjs +3 -2
- package/lib/trace.cjs +5 -2
- package/lib/verbs.mjs +7 -1
- package/package.json +1 -1
|
@@ -33,6 +33,16 @@ def _install():
|
|
|
33
33
|
except OSError:
|
|
34
34
|
pass
|
|
35
35
|
|
|
36
|
+
# Every other outbound call: the host and what it answered, never a byte of it. A search
|
|
37
|
+
# provider over its limit for a whole run was invisible until this row existed.
|
|
38
|
+
def dep(url, status, code=None):
|
|
39
|
+
try:
|
|
40
|
+
host = (urlsplit(str(url)).hostname or "").lower()
|
|
41
|
+
if not host or host in ("localhost", "127.0.0.1", "::1"):
|
|
42
|
+
return
|
|
43
|
+
write({"dep": {"at": int(time.time() * 1000), "host": host[:253], "status": int(status or 0), **({"code": str(code)[:40]} if code else {})}})
|
|
44
|
+
except Exception:
|
|
45
|
+
pass
|
|
36
46
|
def is_model_call(url):
|
|
37
47
|
try:
|
|
38
48
|
parts = urlsplit(str(url))
|
|
@@ -330,6 +340,7 @@ def _install():
|
|
|
330
340
|
|
|
331
341
|
def watch(request, response, sent):
|
|
332
342
|
if not is_model_call(request.url):
|
|
343
|
+
dep(request.url, response.status_code)
|
|
333
344
|
return response
|
|
334
345
|
kind = response.headers.get("content-type", "")
|
|
335
346
|
if getattr(response, "_content", None) is not None:
|
|
@@ -356,9 +367,11 @@ def _install():
|
|
|
356
367
|
pass
|
|
357
368
|
try:
|
|
358
369
|
response = _send(self, request, *a, **k)
|
|
359
|
-
except Exception:
|
|
370
|
+
except Exception as err:
|
|
360
371
|
if is_model_call(request.url):
|
|
361
372
|
meter(request.url, sent, 0, "", "")
|
|
373
|
+
else:
|
|
374
|
+
dep(request.url, 0, type(err).__name__)
|
|
362
375
|
raise
|
|
363
376
|
try:
|
|
364
377
|
return watch(request, response, sent)
|
|
@@ -374,9 +387,11 @@ def _install():
|
|
|
374
387
|
pass
|
|
375
388
|
try:
|
|
376
389
|
response = await _send(self, request, *a, **k)
|
|
377
|
-
except Exception:
|
|
390
|
+
except Exception as err:
|
|
378
391
|
if is_model_call(request.url):
|
|
379
392
|
meter(request.url, sent, 0, "", "")
|
|
393
|
+
else:
|
|
394
|
+
dep(request.url, 0, type(err).__name__)
|
|
380
395
|
raise
|
|
381
396
|
try:
|
|
382
397
|
return watch(request, response, sent)
|
|
@@ -392,8 +407,15 @@ def _install():
|
|
|
392
407
|
note(request.url, request.body)
|
|
393
408
|
except Exception:
|
|
394
409
|
pass
|
|
395
|
-
response = send(self, request, *a, **k)
|
|
396
410
|
try:
|
|
411
|
+
response = send(self, request, *a, **k)
|
|
412
|
+
except Exception as err:
|
|
413
|
+
if not is_model_call(request.url):
|
|
414
|
+
dep(request.url, 0, type(err).__name__)
|
|
415
|
+
raise
|
|
416
|
+
try:
|
|
417
|
+
if not is_model_call(request.url):
|
|
418
|
+
dep(request.url, response.status_code)
|
|
397
419
|
if is_model_call(request.url):
|
|
398
420
|
# A streamed reply is counted as a call whose counts were not read.
|
|
399
421
|
raw = response.content.decode("utf-8", "replace") if getattr(response, "_content_consumed", False) else ""
|
|
@@ -413,9 +435,16 @@ def _install():
|
|
|
413
435
|
note(str_or_url, body)
|
|
414
436
|
except Exception:
|
|
415
437
|
pass
|
|
416
|
-
|
|
438
|
+
try:
|
|
439
|
+
response = await request(self, method, str_or_url, *a, **k)
|
|
440
|
+
except Exception as err:
|
|
441
|
+
if not is_model_call(str_or_url):
|
|
442
|
+
dep(str_or_url, 0, type(err).__name__)
|
|
443
|
+
raise
|
|
417
444
|
if is_model_call(str_or_url):
|
|
418
445
|
response._cortad = (str_or_url, text(body))
|
|
446
|
+
else:
|
|
447
|
+
dep(str_or_url, response.status)
|
|
419
448
|
return response
|
|
420
449
|
|
|
421
450
|
# Metered when your app reads the reply, or, for one it streams, when the reply is let go.
|
package/lib/replay.mjs
CHANGED
|
@@ -88,8 +88,9 @@ const meter = (() => {
|
|
|
88
88
|
},
|
|
89
89
|
// A service their settings name: only its setting, host and status travel, never a byte of it.
|
|
90
90
|
dep(d) {
|
|
91
|
-
if (!d || typeof d.
|
|
92
|
-
|
|
91
|
+
if (!d || typeof d.host !== "string" || !d.host) return;
|
|
92
|
+
const env = typeof d.env === "string" && /^[A-Z_][A-Z0-9_]*$/.test(d.env) ? d.env : undefined;
|
|
93
|
+
deps.push({ at: count(d.at), ...(env ? { env } : {}), host: d.host.slice(0, 253), status: count(d.status), ...(typeof d.code === "string" ? { code: d.code.slice(0, 40) } : {}) });
|
|
93
94
|
if (deps.length > ROWS) deps.splice(0, deps.length - ROWS);
|
|
94
95
|
},
|
|
95
96
|
report() {
|
package/lib/trace.cjs
CHANGED
|
@@ -196,7 +196,7 @@ if (FILE) {
|
|
|
196
196
|
} catch { return null; }
|
|
197
197
|
};
|
|
198
198
|
const depRow = (dep, status, code) => {
|
|
199
|
-
try { fs.appendFileSync(FILE, JSON.stringify({ dep: { at: Date.now(), env: dep.env, host: dep.host, status, code: code ? String(code).slice(0, 40) : undefined } }) + "\n", { mode: 0o600 }); } catch { /* the command is gone */ }
|
|
199
|
+
try { fs.appendFileSync(FILE, JSON.stringify({ dep: { at: Date.now(), ...(dep.env ? { env: dep.env } : {}), host: dep.host, status, code: code ? String(code).slice(0, 40) : undefined } }) + "\n", { mode: 0o600 }); } catch { /* the command is gone */ }
|
|
200
200
|
};
|
|
201
201
|
|
|
202
202
|
// Outbound: the model call your app makes while it handles that request.
|
|
@@ -210,7 +210,10 @@ if (FILE) {
|
|
|
210
210
|
if (isModelCall(u.host, u.pathname)) url = u;
|
|
211
211
|
} catch { /* not a URL we can read */ }
|
|
212
212
|
if (!url) {
|
|
213
|
-
|
|
213
|
+
// Every outbound host, named by the setting that points at it when one does, and by the
|
|
214
|
+
// host alone otherwise: the search provider behind TAVILY_API_KEY has no URL setting.
|
|
215
|
+
let dep = depOf(input);
|
|
216
|
+
if (!dep) { try { const u = new URL(typeof input === "string" ? input : input && input.url ? input.url : String(input)); if (/^https?:$/.test(u.protocol) && !/^(localhost|127\.0\.0\.1|\[::1\])$/.test(u.hostname)) dep = { host: u.hostname }; } catch { /* not a URL */ } }
|
|
214
217
|
if (!dep) return realFetch.apply(this, arguments);
|
|
215
218
|
return realFetch.apply(this, arguments).then(
|
|
216
219
|
(res) => { depRow(dep, res.status); return res; },
|
package/lib/verbs.mjs
CHANGED
|
@@ -127,7 +127,13 @@ export function makeVerbs({ api, token, fetchImpl = fetch, startApp, pending, ro
|
|
|
127
127
|
const more = await call("GET", path(page, d.runId ?? jobId));
|
|
128
128
|
if (!more.ok) return more;
|
|
129
129
|
d.findings = [...(d.findings ?? []), ...(more.data.findings ?? [])];
|
|
130
|
-
|
|
130
|
+
// Every page carries the whole byLine; merged by line with the ids joined, or the first
|
|
131
|
+
// page's groups print twice.
|
|
132
|
+
for (const g of more.data.byLine ?? []) {
|
|
133
|
+
const held = (d.byLine ?? []).find((h) => h.path === g.path && h.line === g.line);
|
|
134
|
+
if (held) held.findingIds = [...new Set([...held.findingIds, ...g.findingIds])];
|
|
135
|
+
else d.byLine = [...(d.byLine ?? []), g];
|
|
136
|
+
}
|
|
131
137
|
}
|
|
132
138
|
return first;
|
|
133
139
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cortad",
|
|
3
|
-
"version": "0.3.0-rc.
|
|
3
|
+
"version": "0.3.0-rc.4",
|
|
4
4
|
"description": "Connects the AI app on your machine to Cortad for test conversations, and gives your coding agent the MCP and skill to run them. No dependencies.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"cortad": "local.mjs"
|