@xynogen/pix-9router 0.3.19 → 0.3.21
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 +2 -0
- package/package.json +1 -1
- package/src/http.ts +80 -30
package/README.md
CHANGED
|
@@ -17,6 +17,8 @@ Pi coding agent extension — **9Router provider** + **fetch**, **search**, and
|
|
|
17
17
|
pi install npm:@xynogen/pix-9router
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
+
> Standalone/opt-in — **not** bundled by [`@xynogen/pix-core`](https://www.npmjs.com/package/@xynogen/pix-core). Needs a 9Router API key, so only useful if you route through 9Router.
|
|
21
|
+
|
|
20
22
|
## Environment
|
|
21
23
|
|
|
22
24
|
| Variable | Required | Default | Description |
|
package/package.json
CHANGED
package/src/http.ts
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* so both are parameters with the old per-file defaults.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { readFileSync } from "node:fs";
|
|
11
|
+
import { basename } from "node:path";
|
|
11
12
|
import { ioTimeoutMs, ioTimeoutSignal } from "@xynogen/pix-runtime/io";
|
|
12
13
|
import { routerBaseUrl } from "./data.js";
|
|
13
14
|
|
|
@@ -44,41 +45,90 @@ export async function apiPost(
|
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
export interface CurlOptions {
|
|
47
|
-
/**
|
|
48
|
-
maxTime?: number;
|
|
49
|
-
/** child-process kill timeout in ms (default 30_000). */
|
|
48
|
+
/** Request timeout in ms (default ioTimeoutMs()). */
|
|
50
49
|
timeoutMs?: number;
|
|
51
|
-
/** Optional
|
|
50
|
+
/** Optional request body (was piped to curl stdin). */
|
|
52
51
|
stdin?: string;
|
|
53
52
|
}
|
|
54
53
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
54
|
+
// ponytail: this is NOT a general curl shim — it parses only the flag forms the
|
|
55
|
+
// three fallback call sites actually pass (-L, -X, -H, -d, -F key=value /
|
|
56
|
+
// file=@path, plus the -sS/--connect-timeout/--max-time defaults added below).
|
|
57
|
+
// New curl flags won't work here; extend the parser or call fetch directly.
|
|
58
|
+
function curlArgsToRequest(args: string[]): { url: string; init: RequestInit } {
|
|
59
|
+
const headers = new Headers();
|
|
60
|
+
let method = "GET";
|
|
61
|
+
let body: string | undefined;
|
|
62
|
+
let form: FormData | undefined;
|
|
63
|
+
let url = "";
|
|
64
|
+
for (let i = 0; i < args.length; i++) {
|
|
65
|
+
const a = args[i] ?? "";
|
|
66
|
+
switch (a) {
|
|
67
|
+
case "-sS":
|
|
68
|
+
case "-L":
|
|
69
|
+
break; // silent / follow-redirects are fetch defaults
|
|
70
|
+
case "--connect-timeout":
|
|
71
|
+
case "--max-time":
|
|
72
|
+
i++; // value handled by AbortSignal below
|
|
73
|
+
break;
|
|
74
|
+
case "-X":
|
|
75
|
+
method = args[++i] ?? method;
|
|
76
|
+
break;
|
|
77
|
+
case "-H": {
|
|
78
|
+
const h = args[++i] ?? "";
|
|
79
|
+
const idx = h.indexOf(":");
|
|
80
|
+
if (idx >= 0) headers.set(h.slice(0, idx).trim(), h.slice(idx + 1).trim());
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
case "-d":
|
|
84
|
+
body = args[++i] ?? "";
|
|
85
|
+
break;
|
|
86
|
+
case "-F": {
|
|
87
|
+
form ??= new FormData();
|
|
88
|
+
const field = args[++i] ?? "";
|
|
89
|
+
const eq = field.indexOf("=");
|
|
90
|
+
const key = field.slice(0, eq);
|
|
91
|
+
const val = field.slice(eq + 1);
|
|
92
|
+
if (val.startsWith("@")) {
|
|
93
|
+
const path = val.slice(1);
|
|
94
|
+
form.set(key, new Blob([readFileSync(path)]), basename(path));
|
|
95
|
+
} else {
|
|
96
|
+
form.set(key, val);
|
|
73
97
|
}
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
child.stdin.write(stdin);
|
|
79
|
-
child.stdin.end();
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
default:
|
|
101
|
+
if (!a.startsWith("-")) url = a; // positional = URL
|
|
80
102
|
}
|
|
81
|
-
}
|
|
103
|
+
}
|
|
104
|
+
const init: RequestInit = { method };
|
|
105
|
+
if (form)
|
|
106
|
+
init.body = form; // let fetch set the multipart boundary
|
|
107
|
+
else if (body !== undefined) init.body = body;
|
|
108
|
+
if ([...headers.keys()].length > 0) init.headers = headers;
|
|
109
|
+
return { url, init };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Raw fetch fallback (curl-signature compatible), rejecting on non-2xx or timeout. */
|
|
113
|
+
export async function curl(args: string[], opts: CurlOptions = {}): Promise<string> {
|
|
114
|
+
const timeoutMs = opts.timeoutMs ?? ioTimeoutMs();
|
|
115
|
+
const { url, init } = curlArgsToRequest(args);
|
|
116
|
+
if (opts.stdin !== undefined) init.body = opts.stdin;
|
|
117
|
+
init.signal = AbortSignal.timeout(timeoutMs);
|
|
118
|
+
let res: Response;
|
|
119
|
+
try {
|
|
120
|
+
res = await fetch(url, init);
|
|
121
|
+
} catch (err) {
|
|
122
|
+
if (err instanceof DOMException && err.name === "TimeoutError") {
|
|
123
|
+
throw new Error("curl timed out");
|
|
124
|
+
}
|
|
125
|
+
throw new Error(`curl failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
126
|
+
}
|
|
127
|
+
if (!res.ok) {
|
|
128
|
+
const errText = await res.text().catch(() => "");
|
|
129
|
+
throw new Error(`curl exit ${res.status}: ${errText.slice(0, 300)}`);
|
|
130
|
+
}
|
|
131
|
+
return res.text();
|
|
82
132
|
}
|
|
83
133
|
|
|
84
134
|
export function isCancelled(error: unknown, signal: AbortSignal | undefined): boolean {
|