@linxiraos/pi-utils 1.0.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.
Files changed (244) hide show
  1. package/CHANGELOG.md +449 -0
  2. package/README.md +36 -0
  3. package/dist/types/abortable.d.ts +32 -0
  4. package/dist/types/acp/connection.d.ts +118 -0
  5. package/dist/types/acp/protocol.d.ts +526 -0
  6. package/dist/types/acp/schema.d.ts +41 -0
  7. package/dist/types/acp/stream.d.ts +8 -0
  8. package/dist/types/acp/transport.d.ts +81 -0
  9. package/dist/types/acp.d.ts +6 -0
  10. package/dist/types/async.d.ts +20 -0
  11. package/dist/types/binary.d.ts +17 -0
  12. package/dist/types/browsers.d.ts +68 -0
  13. package/dist/types/chalk.d.ts +125 -0
  14. package/dist/types/cli.d.ts +134 -0
  15. package/dist/types/color.d.ts +102 -0
  16. package/dist/types/dates.d.ts +7 -0
  17. package/dist/types/dirs.d.ts +282 -0
  18. package/dist/types/docx/converter.d.ts +46 -0
  19. package/dist/types/docx/xml.d.ts +26 -0
  20. package/dist/types/docx/zip.d.ts +6 -0
  21. package/dist/types/docx.d.ts +11 -0
  22. package/dist/types/dom/core.d.ts +431 -0
  23. package/dist/types/dom/parser.d.ts +7 -0
  24. package/dist/types/dom/selector.d.ts +5 -0
  25. package/dist/types/dom.d.ts +5 -0
  26. package/dist/types/env.d.ts +124 -0
  27. package/dist/types/fetch-retry.d.ts +96 -0
  28. package/dist/types/file-lock.d.ts +23 -0
  29. package/dist/types/format.d.ts +37 -0
  30. package/dist/types/frontmatter.d.ts +46 -0
  31. package/dist/types/fs-error.d.ts +31 -0
  32. package/dist/types/glob.d.ts +28 -0
  33. package/dist/types/headers.d.ts +34 -0
  34. package/dist/types/index.d.ts +39 -0
  35. package/dist/types/json-parse.d.ts +74 -0
  36. package/dist/types/json.d.ts +14 -0
  37. package/dist/types/logger/rotating-file.d.ts +18 -0
  38. package/dist/types/logger.d.ts +96 -0
  39. package/dist/types/loop-phase.d.ts +10 -0
  40. package/dist/types/lru.d.ts +46 -0
  41. package/dist/types/marked/core.d.ts +445 -0
  42. package/dist/types/marked.d.ts +2 -0
  43. package/dist/types/mermaid-ascii.d.ts +11 -0
  44. package/dist/types/mime.d.ts +29 -0
  45. package/dist/types/module-timer.d.ts +1 -0
  46. package/dist/types/path-tree.d.ts +76 -0
  47. package/dist/types/path.d.ts +2 -0
  48. package/dist/types/peek-file.d.ts +29 -0
  49. package/dist/types/postmortem.d.ts +104 -0
  50. package/dist/types/process-name.d.ts +7 -0
  51. package/dist/types/procmgr.d.ts +74 -0
  52. package/dist/types/prompt.d.ts +18 -0
  53. package/dist/types/ptree.d.ts +110 -0
  54. package/dist/types/readability/readability.d.ts +9 -0
  55. package/dist/types/readability/readerable.d.ts +10 -0
  56. package/dist/types/readability/types.d.ts +70 -0
  57. package/dist/types/readability.d.ts +4 -0
  58. package/dist/types/ring.d.ts +93 -0
  59. package/dist/types/runtime-install.d.ts +78 -0
  60. package/dist/types/sanitize-text.d.ts +29 -0
  61. package/dist/types/snowflake.d.ts +25 -0
  62. package/dist/types/stderr-guard.d.ts +22 -0
  63. package/dist/types/stream.d.ts +67 -0
  64. package/dist/types/tab-spacing.d.ts +24 -0
  65. package/dist/types/temp.d.ts +17 -0
  66. package/dist/types/template.d.ts +62 -0
  67. package/dist/types/timing-buffer.d.ts +22 -0
  68. package/dist/types/tls-fetch.d.ts +37 -0
  69. package/dist/types/turndown/gfm.d.ts +11 -0
  70. package/dist/types/turndown/html.d.ts +5 -0
  71. package/dist/types/turndown/service.d.ts +21 -0
  72. package/dist/types/turndown/types.d.ts +70 -0
  73. package/dist/types/turndown.d.ts +4 -0
  74. package/dist/types/type-guards.d.ts +5 -0
  75. package/dist/types/vendor/mermaid-ascii/ascii/ansi.d.ts +41 -0
  76. package/dist/types/vendor/mermaid-ascii/ascii/canvas.d.ts +89 -0
  77. package/dist/types/vendor/mermaid-ascii/ascii/class-diagram.d.ts +7 -0
  78. package/dist/types/vendor/mermaid-ascii/ascii/converter.d.ts +12 -0
  79. package/dist/types/vendor/mermaid-ascii/ascii/draw.d.ts +66 -0
  80. package/dist/types/vendor/mermaid-ascii/ascii/edge-bundling.d.ts +48 -0
  81. package/dist/types/vendor/mermaid-ascii/ascii/edge-routing.d.ts +43 -0
  82. package/dist/types/vendor/mermaid-ascii/ascii/er-diagram.d.ts +7 -0
  83. package/dist/types/vendor/mermaid-ascii/ascii/grid.d.ts +56 -0
  84. package/dist/types/vendor/mermaid-ascii/ascii/index.d.ts +65 -0
  85. package/dist/types/vendor/mermaid-ascii/ascii/multiline-utils.d.ts +27 -0
  86. package/dist/types/vendor/mermaid-ascii/ascii/pathfinder.d.ts +17 -0
  87. package/dist/types/vendor/mermaid-ascii/ascii/sequence.d.ts +7 -0
  88. package/dist/types/vendor/mermaid-ascii/ascii/shapes/circle.d.ts +11 -0
  89. package/dist/types/vendor/mermaid-ascii/ascii/shapes/corners.d.ts +34 -0
  90. package/dist/types/vendor/mermaid-ascii/ascii/shapes/diamond.d.ts +11 -0
  91. package/dist/types/vendor/mermaid-ascii/ascii/shapes/hexagon.d.ts +11 -0
  92. package/dist/types/vendor/mermaid-ascii/ascii/shapes/index.d.ts +26 -0
  93. package/dist/types/vendor/mermaid-ascii/ascii/shapes/rectangle.d.ts +31 -0
  94. package/dist/types/vendor/mermaid-ascii/ascii/shapes/rounded.d.ts +11 -0
  95. package/dist/types/vendor/mermaid-ascii/ascii/shapes/special.d.ts +59 -0
  96. package/dist/types/vendor/mermaid-ascii/ascii/shapes/stadium.d.ts +17 -0
  97. package/dist/types/vendor/mermaid-ascii/ascii/shapes/state.d.ts +30 -0
  98. package/dist/types/vendor/mermaid-ascii/ascii/shapes/types.d.ts +55 -0
  99. package/dist/types/vendor/mermaid-ascii/ascii/types.d.ts +206 -0
  100. package/dist/types/vendor/mermaid-ascii/ascii/validate.d.ts +51 -0
  101. package/dist/types/vendor/mermaid-ascii/ascii/xychart.d.ts +2 -0
  102. package/dist/types/vendor/mermaid-ascii/class/parser.d.ts +6 -0
  103. package/dist/types/vendor/mermaid-ascii/class/types.d.ts +102 -0
  104. package/dist/types/vendor/mermaid-ascii/er/parser.d.ts +6 -0
  105. package/dist/types/vendor/mermaid-ascii/er/types.d.ts +76 -0
  106. package/dist/types/vendor/mermaid-ascii/index.d.ts +1 -0
  107. package/dist/types/vendor/mermaid-ascii/multiline-utils.d.ts +9 -0
  108. package/dist/types/vendor/mermaid-ascii/parser.d.ts +7 -0
  109. package/dist/types/vendor/mermaid-ascii/sequence/parser.d.ts +6 -0
  110. package/dist/types/vendor/mermaid-ascii/sequence/types.d.ts +130 -0
  111. package/dist/types/vendor/mermaid-ascii/text-metrics.d.ts +21 -0
  112. package/dist/types/vendor/mermaid-ascii/types.d.ts +114 -0
  113. package/dist/types/vendor/mermaid-ascii/xychart/colors.d.ts +25 -0
  114. package/dist/types/vendor/mermaid-ascii/xychart/parser.d.ts +6 -0
  115. package/dist/types/vendor/mermaid-ascii/xychart/types.d.ts +145 -0
  116. package/dist/types/version.d.ts +18 -0
  117. package/dist/types/vterm/buffer.d.ts +99 -0
  118. package/dist/types/vterm/terminal.d.ts +44 -0
  119. package/dist/types/vterm.d.ts +8 -0
  120. package/dist/types/which.d.ts +37 -0
  121. package/dist/types/worker-host.d.ts +51 -0
  122. package/dist/types/xml.d.ts +31 -0
  123. package/package.json +59 -0
  124. package/src/abortable.ts +109 -0
  125. package/src/acp/connection.ts +344 -0
  126. package/src/acp/protocol.ts +466 -0
  127. package/src/acp/schema.ts +160 -0
  128. package/src/acp/stream.ts +82 -0
  129. package/src/acp/transport.ts +213 -0
  130. package/src/acp.ts +6 -0
  131. package/src/async.ts +91 -0
  132. package/src/binary.ts +50 -0
  133. package/src/browsers.ts +501 -0
  134. package/src/chalk.ts +312 -0
  135. package/src/cli.ts +529 -0
  136. package/src/color.ts +302 -0
  137. package/src/dates.ts +194 -0
  138. package/src/dirs.ts +1026 -0
  139. package/src/docx/converter.ts +681 -0
  140. package/src/docx/xml.ts +166 -0
  141. package/src/docx/zip.ts +87 -0
  142. package/src/docx.ts +20 -0
  143. package/src/dom/core.ts +1254 -0
  144. package/src/dom/parser.ts +370 -0
  145. package/src/dom/selector.ts +290 -0
  146. package/src/dom.ts +33 -0
  147. package/src/env.ts +400 -0
  148. package/src/fetch-retry.ts +379 -0
  149. package/src/file-lock.ts +69 -0
  150. package/src/format.ts +113 -0
  151. package/src/frontmatter.ts +205 -0
  152. package/src/fs-error.ts +56 -0
  153. package/src/glob.ts +189 -0
  154. package/src/headers.ts +167 -0
  155. package/src/index.ts +59 -0
  156. package/src/json-parse.ts +821 -0
  157. package/src/json.ts +23 -0
  158. package/src/logger/rotating-file.ts +149 -0
  159. package/src/logger.ts +798 -0
  160. package/src/loop-phase.ts +49 -0
  161. package/src/lru.ts +185 -0
  162. package/src/marked/core.ts +1576 -0
  163. package/src/marked.ts +2 -0
  164. package/src/mermaid-ascii.ts +31 -0
  165. package/src/mime.ts +159 -0
  166. package/src/module-timer.ts +148 -0
  167. package/src/path-tree.ts +147 -0
  168. package/src/path.ts +28 -0
  169. package/src/peek-file.ts +188 -0
  170. package/src/postmortem.ts +433 -0
  171. package/src/process-name.ts +57 -0
  172. package/src/procmgr.ts +247 -0
  173. package/src/prompt.ts +545 -0
  174. package/src/ptree.ts +412 -0
  175. package/src/readability/readability.ts +533 -0
  176. package/src/readability/readerable.ts +51 -0
  177. package/src/readability/types.ts +72 -0
  178. package/src/readability.ts +11 -0
  179. package/src/ring.ts +169 -0
  180. package/src/runtime-install.ts +393 -0
  181. package/src/sanitize-text.ts +96 -0
  182. package/src/snowflake.ts +121 -0
  183. package/src/stderr-guard.ts +155 -0
  184. package/src/stream.ts +454 -0
  185. package/src/tab-spacing.ts +336 -0
  186. package/src/temp.ts +134 -0
  187. package/src/template.ts +586 -0
  188. package/src/timing-buffer.ts +47 -0
  189. package/src/tls-fetch.ts +178 -0
  190. package/src/turndown/gfm.ts +106 -0
  191. package/src/turndown/html.ts +257 -0
  192. package/src/turndown/service.ts +334 -0
  193. package/src/turndown/types.ts +81 -0
  194. package/src/turndown.ts +5 -0
  195. package/src/type-guards.ts +17 -0
  196. package/src/vendor/mermaid-ascii/NOTICE +33 -0
  197. package/src/vendor/mermaid-ascii/ascii/ansi.ts +409 -0
  198. package/src/vendor/mermaid-ascii/ascii/canvas.ts +476 -0
  199. package/src/vendor/mermaid-ascii/ascii/class-diagram.ts +699 -0
  200. package/src/vendor/mermaid-ascii/ascii/converter.ts +271 -0
  201. package/src/vendor/mermaid-ascii/ascii/draw.ts +1382 -0
  202. package/src/vendor/mermaid-ascii/ascii/edge-bundling.ts +328 -0
  203. package/src/vendor/mermaid-ascii/ascii/edge-routing.ts +297 -0
  204. package/src/vendor/mermaid-ascii/ascii/er-diagram.ts +441 -0
  205. package/src/vendor/mermaid-ascii/ascii/grid.ts +578 -0
  206. package/src/vendor/mermaid-ascii/ascii/index.ts +187 -0
  207. package/src/vendor/mermaid-ascii/ascii/multiline-utils.ts +78 -0
  208. package/src/vendor/mermaid-ascii/ascii/pathfinder.ts +277 -0
  209. package/src/vendor/mermaid-ascii/ascii/sequence.ts +460 -0
  210. package/src/vendor/mermaid-ascii/ascii/shapes/circle.ts +27 -0
  211. package/src/vendor/mermaid-ascii/ascii/shapes/corners.ts +127 -0
  212. package/src/vendor/mermaid-ascii/ascii/shapes/diamond.ts +27 -0
  213. package/src/vendor/mermaid-ascii/ascii/shapes/hexagon.ts +27 -0
  214. package/src/vendor/mermaid-ascii/ascii/shapes/index.ts +101 -0
  215. package/src/vendor/mermaid-ascii/ascii/shapes/rectangle.ts +175 -0
  216. package/src/vendor/mermaid-ascii/ascii/shapes/rounded.ts +27 -0
  217. package/src/vendor/mermaid-ascii/ascii/shapes/special.ts +296 -0
  218. package/src/vendor/mermaid-ascii/ascii/shapes/stadium.ts +114 -0
  219. package/src/vendor/mermaid-ascii/ascii/shapes/state.ts +192 -0
  220. package/src/vendor/mermaid-ascii/ascii/shapes/types.ts +73 -0
  221. package/src/vendor/mermaid-ascii/ascii/types.ts +273 -0
  222. package/src/vendor/mermaid-ascii/ascii/validate.ts +120 -0
  223. package/src/vendor/mermaid-ascii/ascii/xychart.ts +875 -0
  224. package/src/vendor/mermaid-ascii/class/parser.ts +290 -0
  225. package/src/vendor/mermaid-ascii/class/types.ts +121 -0
  226. package/src/vendor/mermaid-ascii/er/parser.ts +181 -0
  227. package/src/vendor/mermaid-ascii/er/types.ts +91 -0
  228. package/src/vendor/mermaid-ascii/index.ts +14 -0
  229. package/src/vendor/mermaid-ascii/multiline-utils.ts +30 -0
  230. package/src/vendor/mermaid-ascii/parser.ts +645 -0
  231. package/src/vendor/mermaid-ascii/sequence/parser.ts +207 -0
  232. package/src/vendor/mermaid-ascii/sequence/types.ts +146 -0
  233. package/src/vendor/mermaid-ascii/text-metrics.ts +71 -0
  234. package/src/vendor/mermaid-ascii/types.ts +164 -0
  235. package/src/vendor/mermaid-ascii/xychart/colors.ts +140 -0
  236. package/src/vendor/mermaid-ascii/xychart/parser.ts +115 -0
  237. package/src/vendor/mermaid-ascii/xychart/types.ts +150 -0
  238. package/src/version.ts +99 -0
  239. package/src/vterm/buffer.ts +218 -0
  240. package/src/vterm/terminal.ts +773 -0
  241. package/src/vterm.ts +8 -0
  242. package/src/which.ts +234 -0
  243. package/src/worker-host.ts +100 -0
  244. package/src/xml.ts +313 -0
@@ -0,0 +1,178 @@
1
+ /**
2
+ * `NODE_EXTRA_CA_CERTS` shim for Bun's `fetch`.
3
+ *
4
+ * Node's TLS layer honours `NODE_EXTRA_CA_CERTS` natively, but Bun's
5
+ * `fetch` does not, and both the provider streams (`openai-responses`,
6
+ * `openai-completions`, `openai-codex-responses`, `ollama-chat`, ...) and
7
+ * catalog model discovery (`/models` probes) route every request through
8
+ * Bun's runtime. Without this wrapper, corporate relays and private
9
+ * gateways behind a custom CA bundle fail with
10
+ * `unknown certificate verification error` even when the env var is set.
11
+ *
12
+ * The wrapper merges the resolved CA bundle into Bun's `RequestInit.tls.ca`.
13
+ * Bun's `tls.ca` REPLACES the default trust store when set, so the wrapper
14
+ * always seeds {@link tls.rootCertificates} when the caller has not already
15
+ * curated their own CA list.
16
+ */
17
+ import * as fs from "node:fs";
18
+ import * as tls from "node:tls";
19
+ import { $env } from "./env";
20
+ import { isEnoent } from "./fs-error";
21
+
22
+ /**
23
+ * `fetch`-compatible function. Accepts any callable matching the standard
24
+ * fetch signature; `preconnect` is optional because non-Bun runtimes
25
+ * (browsers, test mocks) won't expose it.
26
+ */
27
+ export type FetchImpl = ((input: string | URL | Request, init?: RequestInit) => Promise<Response>) & {
28
+ preconnect?: typeof globalThis.fetch.preconnect;
29
+ };
30
+
31
+ /**
32
+ * `NODE_EXTRA_CA_CERTS` was set but unusable (path does not exist). This is
33
+ * a config/contract error, not a transient transport fault — it is never
34
+ * retried.
35
+ */
36
+ export class ExtraCaError extends Error {
37
+ constructor(message: string, options?: { cause?: unknown }) {
38
+ super(message, options?.cause === undefined ? undefined : { cause: options.cause });
39
+ this.name = "ExtraCaError";
40
+ }
41
+ }
42
+
43
+ /** Bun extension to `RequestInit` for the TLS options we touch. */
44
+ type BunTlsOptions = {
45
+ ca?: string | string[];
46
+ cert?: string;
47
+ key?: string;
48
+ rejectUnauthorized?: boolean;
49
+ serverName?: string;
50
+ ciphers?: string;
51
+ };
52
+
53
+ type BunTlsRequestInit = RequestInit & { tls?: BunTlsOptions };
54
+
55
+ const EXTRA_CA_FETCH_MARKER = Symbol("omp.extraCaFetch");
56
+ type ExtraCaFetch = FetchImpl & { [EXTRA_CA_FETCH_MARKER]?: true };
57
+
58
+ /**
59
+ * Cached resolution of `NODE_EXTRA_CA_CERTS`. Keyed on the env value plus
60
+ * the file mtime for path values so on-disk cert rotation (short-lived
61
+ * corporate bundles) invalidates the cache instead of pinning the first
62
+ * read forever.
63
+ */
64
+ let cacheKey: string | undefined;
65
+ let cacheValue: string | undefined;
66
+
67
+ /**
68
+ * Returns the PEM bytes referenced by `NODE_EXTRA_CA_CERTS`, or `undefined`
69
+ * when the env var is unset/empty.
70
+ *
71
+ * Accepts the same shapes Node accepts plus an inline-PEM escape hatch:
72
+ * - Inline PEM (`-----BEGIN CERTIFICATE-----...`). Literal `\n` escapes in
73
+ * the env value are expanded so callers can ship single-line PEMs through
74
+ * shell exports.
75
+ * - File path. Anything that does not contain a PEM header is treated as a
76
+ * path, matching Node's "extensionless filename is still a path" contract.
77
+ * `ENOENT` becomes {@link ExtraCaError}; other I/O errors bubble.
78
+ */
79
+ function resolveExtraCa(): string | undefined {
80
+ const raw = $env.NODE_EXTRA_CA_CERTS?.trim();
81
+ if (!raw) return undefined;
82
+
83
+ let key: string;
84
+ if (raw.includes("-----BEGIN")) {
85
+ key = raw;
86
+ } else {
87
+ try {
88
+ key = `${raw}@${fs.statSync(raw).mtimeMs}`;
89
+ } catch {
90
+ key = raw;
91
+ }
92
+ }
93
+ if (key === cacheKey) return cacheValue;
94
+
95
+ if (raw.includes("-----BEGIN")) {
96
+ cacheValue = raw.replace(/\\n/g, "\n");
97
+ } else {
98
+ try {
99
+ cacheValue = fs.readFileSync(raw, "utf8");
100
+ } catch (error) {
101
+ if (isEnoent(error)) {
102
+ throw new ExtraCaError(`NODE_EXTRA_CA_CERTS path does not exist: ${raw}`);
103
+ }
104
+ throw error;
105
+ }
106
+ }
107
+ cacheKey = key;
108
+ return cacheValue;
109
+ }
110
+
111
+ /** Test seam: drop the cached PEM so a follow-up call re-reads the env. */
112
+ export function __resetExtraCaCache(): void {
113
+ cacheKey = undefined;
114
+ cacheValue = undefined;
115
+ }
116
+
117
+ /**
118
+ * Merge `extraCa` into `init.tls.ca`. When the caller has not supplied a CA
119
+ * list, the system root store is included alongside the extra bundle —
120
+ * Bun's `tls.ca` replaces the default trust store, so omitting roots would
121
+ * break every public host. When the caller already curated a list (e.g.
122
+ * Anthropic Foundry's mTLS options, which already seed
123
+ * `tls.rootCertificates`), only the extra CA is appended.
124
+ */
125
+ function withExtraCaInit(init: RequestInit | undefined, extraCa: string): RequestInit {
126
+ const existingTls = (init as BunTlsRequestInit | undefined)?.tls;
127
+ const existingCa = existingTls?.ca;
128
+ let mergedCa: string[];
129
+ if (existingCa === undefined) {
130
+ mergedCa = [...tls.rootCertificates, extraCa];
131
+ } else if (Array.isArray(existingCa)) {
132
+ mergedCa = [...existingCa, extraCa];
133
+ } else {
134
+ mergedCa = [existingCa, extraCa];
135
+ }
136
+ return { ...init, tls: { ...existingTls, ca: mergedCa } } as RequestInit;
137
+ }
138
+
139
+ /**
140
+ * Wrap `fetchImpl` so every call honours `NODE_EXTRA_CA_CERTS`. Idempotent:
141
+ * a fetch already wrapped is returned unchanged so repeated composition
142
+ * (Anthropic auth-retry replays, request-debug fan-out) never stacks
143
+ * wrappers. When the env var is unset the original fetch is returned, so
144
+ * default deployments pay nothing.
145
+ */
146
+ export function wrapFetchForExtraCa(fetchImpl: FetchImpl): FetchImpl {
147
+ const maybeWrapped = fetchImpl as ExtraCaFetch;
148
+ if (maybeWrapped[EXTRA_CA_FETCH_MARKER]) return fetchImpl;
149
+ // Peek once at construction — if the env var is unset, skip the wrapper
150
+ // entirely so the hot fetch path stays a single function call. The env
151
+ // is evaluated again per request below to catch in-process updates from
152
+ // tests (`__resetExtraCaCache` + env mutation).
153
+ if (!$env.NODE_EXTRA_CA_CERTS?.trim()) return fetchImpl;
154
+
155
+ const wrapped = Object.assign(
156
+ async (input: string | URL | Request, init?: RequestInit): Promise<Response> => {
157
+ const extraCa = resolveExtraCa();
158
+ return extraCa ? fetchImpl(input, withExtraCaInit(init, extraCa)) : fetchImpl(input, init);
159
+ },
160
+ fetchImpl.preconnect ? { preconnect: fetchImpl.preconnect } : {},
161
+ { [EXTRA_CA_FETCH_MARKER]: true as const },
162
+ );
163
+ return wrapped;
164
+ }
165
+
166
+ /**
167
+ * Convenience for options-bag composition (e.g. the stream-entry path in
168
+ * `@linxiraos/pi-ai`'s `stream.ts`, which mirrors `withRequestDebugFetch` so
169
+ * the proxy/debug/extra-CA wrappers compose uniformly). No-op when the env
170
+ * var is unset.
171
+ */
172
+ export function withExtraCaFetch<T extends { fetch?: FetchImpl } | undefined>(options: T): T {
173
+ if (!$env.NODE_EXTRA_CA_CERTS?.trim()) return options;
174
+ const fetchImpl = options?.fetch ?? (globalThis.fetch as FetchImpl);
175
+ const wrapped = wrapFetchForExtraCa(fetchImpl);
176
+ if (wrapped === fetchImpl && options?.fetch !== undefined) return options;
177
+ return { ...(options ?? {}), fetch: wrapped } as T;
178
+ }
@@ -0,0 +1,106 @@
1
+ import type { TurndownNode, TurndownPlugin, TurndownServiceLike } from "./types";
2
+
3
+ function descendantElements(node: TurndownNode, name: string): TurndownNode[] {
4
+ const matches: TurndownNode[] = [];
5
+ for (const child of Array.from(node.children)) {
6
+ if (child.nodeName === name) matches.push(child);
7
+ if (child.nodeName !== "TABLE") matches.push(...descendantElements(child, name));
8
+ }
9
+ return matches;
10
+ }
11
+
12
+ function isHeadingTable(node: TurndownNode): boolean {
13
+ if (node.nodeName !== "TABLE") return false;
14
+ const firstRow = descendantElements(node, "TR")[0];
15
+ if (!firstRow) return false;
16
+ const cells = Array.from(firstRow.children);
17
+ return cells.length > 0 && cells.every(cell => cell.nodeName === "TH");
18
+ }
19
+
20
+ function cellAlignment(cell: TurndownNode): "left" | "center" | "right" | undefined {
21
+ const explicit = cell.getAttribute("align")?.toLowerCase();
22
+ if (explicit === "left" || explicit === "center" || explicit === "right") return explicit;
23
+ const style = cell.getAttribute("style") ?? "";
24
+ const styled = /(?:^|;)\s*text-align\s*:\s*(left|center|right)\s*(?:;|$)/i.exec(style)?.[1]?.toLowerCase();
25
+ return styled === "left" || styled === "center" || styled === "right" ? styled : undefined;
26
+ }
27
+
28
+ function alignmentMarker(cell: TurndownNode): string {
29
+ const alignment = cellAlignment(cell);
30
+ if (alignment === "left") return ":--";
31
+ if (alignment === "center") return ":-:";
32
+ if (alignment === "right") return "--:";
33
+ return "---";
34
+ }
35
+
36
+ function renderTable(service: TurndownServiceLike, table: TurndownNode): string {
37
+ const rows = descendantElements(table, "TR");
38
+ const rendered: string[] = [];
39
+ for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
40
+ const row = rows[rowIndex];
41
+ if (!row) continue;
42
+ const cells = Array.from(row.children).filter(cell => cell.nodeName === "TH" || cell.nodeName === "TD");
43
+ const values = cells.map(cell => service.convertChildren(cell).trim());
44
+ rendered.push(`| ${values.join(" | ")} |`);
45
+ if (rowIndex === 0) rendered.push(`| ${cells.map(alignmentMarker).join(" | ")} |`);
46
+ }
47
+ return `\n\n${rendered.join("\n")}\n\n`;
48
+ }
49
+
50
+ /** Install GitHub fenced code blocks selected by `highlight-source-*` wrappers. */
51
+ export const highlightedCodeBlock: TurndownPlugin = service => {
52
+ service.addRule("highlightedCodeBlock", {
53
+ filter(node) {
54
+ return node.nodeName === "DIV" && /(?:^|\s)highlight-source-([^\s]+)/.test(node.getAttribute("class") ?? "");
55
+ },
56
+ replacement(_content, node, options) {
57
+ const language = /(?:^|\s)highlight-source-([^\s]+)/.exec(node.getAttribute("class") ?? "")?.[1] ?? "";
58
+ const text = descendantElements(node, "PRE")[0]?.textContent ?? "";
59
+ return `\n\n${options.fence}${language}\n${text}\n${options.fence}\n\n`;
60
+ },
61
+ });
62
+ };
63
+
64
+ /** Install GFM strikethrough conversion. */
65
+ export const strikethrough: TurndownPlugin = service => {
66
+ service.addRule("strikethrough", {
67
+ filter: ["del", "s", "strike"],
68
+ replacement(content) {
69
+ return `~${content}~`;
70
+ },
71
+ });
72
+ };
73
+
74
+ /** Install GFM task-list checkbox conversion. */
75
+ export const taskListItems: TurndownPlugin = service => {
76
+ service.addRule("taskListItems", {
77
+ filter(node) {
78
+ return (
79
+ node.nodeName === "INPUT" &&
80
+ node.parentNode?.nodeName === "LI" &&
81
+ (node.getAttribute("type") ?? "").toLowerCase() === "checkbox"
82
+ );
83
+ },
84
+ replacement(_content, node) {
85
+ return node.hasAttribute("checked") ? "[x] " : "[ ] ";
86
+ },
87
+ });
88
+ };
89
+
90
+ /** Install GFM table conversion for tables with a heading row. */
91
+ export const tables: TurndownPlugin = service => {
92
+ service.addRule("table", {
93
+ filter: isHeadingTable,
94
+ replacement(_content, node) {
95
+ return renderTable(service, node);
96
+ },
97
+ });
98
+ };
99
+
100
+ /** Install every GFM rule used by the coding agent. */
101
+ export const gfm: TurndownPlugin = service => {
102
+ highlightedCodeBlock(service);
103
+ strikethrough(service);
104
+ tables(service);
105
+ taskListItems(service);
106
+ };
@@ -0,0 +1,257 @@
1
+ import type { TurndownNode } from "./types";
2
+
3
+ const VOID_ELEMENTS: Readonly<Record<string, true>> = {
4
+ AREA: true,
5
+ BASE: true,
6
+ BR: true,
7
+ COL: true,
8
+ EMBED: true,
9
+ HR: true,
10
+ IMG: true,
11
+ INPUT: true,
12
+ LINK: true,
13
+ META: true,
14
+ PARAM: true,
15
+ SOURCE: true,
16
+ TRACK: true,
17
+ WBR: true,
18
+ };
19
+
20
+ const NAMED_ENTITIES: Readonly<Record<string, string>> = {
21
+ amp: "&",
22
+ apos: "'",
23
+ copy: "©",
24
+ gt: ">",
25
+ hellip: "…",
26
+ laquo: "«",
27
+ lt: "<",
28
+ mdash: "—",
29
+ nbsp: " ",
30
+ ndash: "–",
31
+ quot: '"',
32
+ raquo: "»",
33
+ reg: "®",
34
+ };
35
+
36
+ function decodeEntities(value: string): string {
37
+ return value.replace(/&(#(?:x[\da-f]+|\d+)|[a-z][\da-z]+);?/gi, (entity, name: string) => {
38
+ if (name.charAt(0) === "#") {
39
+ const hexadecimal = name.charAt(1).toLowerCase() === "x";
40
+ const number = Number.parseInt(name.slice(hexadecimal ? 2 : 1), hexadecimal ? 16 : 10);
41
+ if (!Number.isFinite(number) || number <= 0 || number > 0x10ffff) return entity;
42
+ try {
43
+ return String.fromCodePoint(number);
44
+ } catch {
45
+ return "�";
46
+ }
47
+ }
48
+ return NAMED_ENTITIES[name.toLowerCase()] ?? entity;
49
+ });
50
+ }
51
+
52
+ function escapeAttribute(value: string): string {
53
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
54
+ }
55
+
56
+ abstract class HtmlNode implements TurndownNode {
57
+ parentNode: HtmlNode | null = null;
58
+ readonly childNodes: HtmlNode[] = [];
59
+ abstract readonly nodeType: number;
60
+ abstract readonly nodeName: string;
61
+
62
+ get children(): HtmlNode[] {
63
+ return this.childNodes.filter(child => child.nodeType === 1);
64
+ }
65
+
66
+ get firstChild(): HtmlNode | null {
67
+ return this.childNodes[0] ?? null;
68
+ }
69
+
70
+ get lastChild(): HtmlNode | null {
71
+ return this.childNodes[this.childNodes.length - 1] ?? null;
72
+ }
73
+
74
+ get previousSibling(): HtmlNode | null {
75
+ if (!this.parentNode) return null;
76
+ const index = this.parentNode.childNodes.indexOf(this);
77
+ return index > 0 ? (this.parentNode.childNodes[index - 1] ?? null) : null;
78
+ }
79
+
80
+ get nextSibling(): HtmlNode | null {
81
+ if (!this.parentNode) return null;
82
+ const index = this.parentNode.childNodes.indexOf(this);
83
+ return index >= 0 ? (this.parentNode.childNodes[index + 1] ?? null) : null;
84
+ }
85
+
86
+ get textContent(): string {
87
+ return this.childNodes.map(child => child.textContent).join("");
88
+ }
89
+
90
+ abstract get outerHTML(): string;
91
+
92
+ getAttribute(_name: string): string | null {
93
+ return null;
94
+ }
95
+
96
+ hasAttribute(_name: string): boolean {
97
+ return false;
98
+ }
99
+
100
+ append(child: HtmlNode): void {
101
+ child.parentNode = this;
102
+ this.childNodes.push(child);
103
+ }
104
+ }
105
+
106
+ class HtmlText extends HtmlNode {
107
+ readonly nodeType = 3;
108
+ readonly nodeName = "#text";
109
+ readonly value: string;
110
+
111
+ constructor(value: string) {
112
+ super();
113
+ this.value = value;
114
+ }
115
+
116
+ override get textContent(): string {
117
+ return this.value;
118
+ }
119
+
120
+ get outerHTML(): string {
121
+ return this.value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
122
+ }
123
+ }
124
+
125
+ class HtmlElement extends HtmlNode {
126
+ readonly nodeType = 1;
127
+ readonly nodeName: string;
128
+ readonly #attributes: Map<string, string>;
129
+
130
+ constructor(name: string, attributes: Map<string, string>) {
131
+ super();
132
+ this.nodeName = name.toUpperCase();
133
+ this.#attributes = attributes;
134
+ }
135
+
136
+ override getAttribute(name: string): string | null {
137
+ return this.#attributes.get(name.toLowerCase()) ?? null;
138
+ }
139
+
140
+ override hasAttribute(name: string): boolean {
141
+ return this.#attributes.has(name.toLowerCase());
142
+ }
143
+
144
+ get outerHTML(): string {
145
+ const tag = this.nodeName.toLowerCase();
146
+ const attributes = [...this.#attributes]
147
+ .map(([name, value]) => (value === "" ? name : `${name}="${escapeAttribute(value)}"`))
148
+ .join(" ");
149
+ const opening = `<${tag}${attributes ? ` ${attributes}` : ""}>`;
150
+ if (VOID_ELEMENTS[this.nodeName]) return opening;
151
+ return `${opening}${this.childNodes.map(child => child.outerHTML).join("")}</${tag}>`;
152
+ }
153
+ }
154
+
155
+ class HtmlFragment extends HtmlNode {
156
+ readonly nodeType = 11;
157
+ readonly nodeName = "#document-fragment";
158
+
159
+ get outerHTML(): string {
160
+ return this.childNodes.map(child => child.outerHTML).join("");
161
+ }
162
+ }
163
+
164
+ function parseAttributes(source: string): Map<string, string> {
165
+ const attributes = new Map<string, string>();
166
+ const pattern = /([^\s=/>]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
167
+ for (const match of source.matchAll(pattern)) {
168
+ const name = match[1]?.toLowerCase();
169
+ if (!name) continue;
170
+ attributes.set(name, decodeEntities(match[2] ?? match[3] ?? match[4] ?? ""));
171
+ }
172
+ return attributes;
173
+ }
174
+
175
+ function* htmlTokens(html: string): Generator<string> {
176
+ let cursor = 0;
177
+ while (cursor < html.length) {
178
+ if (html.charAt(cursor) !== "<") {
179
+ const nextTag = html.indexOf("<", cursor);
180
+ const end = nextTag < 0 ? html.length : nextTag;
181
+ yield html.slice(cursor, end);
182
+ cursor = end;
183
+ continue;
184
+ }
185
+ if (!/[/!a-z]/i.test(html.charAt(cursor + 1))) {
186
+ yield "<";
187
+ cursor++;
188
+ continue;
189
+ }
190
+ if (html.startsWith("<!--", cursor)) {
191
+ const commentEnd = html.indexOf("-->", cursor + 4);
192
+ const end = commentEnd < 0 ? html.length : commentEnd + 3;
193
+ yield html.slice(cursor, end);
194
+ cursor = end;
195
+ continue;
196
+ }
197
+ let quote: string | undefined;
198
+ let tagEnd = cursor + 1;
199
+ for (; tagEnd < html.length; tagEnd++) {
200
+ const character = html.charAt(tagEnd);
201
+ if (quote) {
202
+ if (character === quote) quote = undefined;
203
+ } else if (character === '"' || character === "'") {
204
+ quote = character;
205
+ } else if (character === ">") {
206
+ break;
207
+ }
208
+ }
209
+ if (tagEnd >= html.length) {
210
+ yield "<";
211
+ cursor++;
212
+ continue;
213
+ }
214
+ yield html.slice(cursor, tagEnd + 1);
215
+ cursor = tagEnd + 1;
216
+ }
217
+ }
218
+
219
+ /** Parse an HTML fragment into the small DOM subset needed by Turndown. */
220
+ export function parseHtmlFragment(html: string): TurndownNode {
221
+ const root = new HtmlFragment();
222
+ const stack: HtmlNode[] = [root];
223
+ for (const token of htmlTokens(html)) {
224
+ const parent = stack[stack.length - 1] ?? root;
225
+ if (token.startsWith("<!--") || /^<!/i.test(token)) continue;
226
+ if (!token.startsWith("<")) {
227
+ parent.append(new HtmlText(decodeEntities(token)));
228
+ continue;
229
+ }
230
+ const closing = /^<\/\s*([a-z][\w:-]*)[^>]*>$/i.exec(token);
231
+ if (closing) {
232
+ const name = closing[1]?.toUpperCase();
233
+ for (let index = stack.length - 1; index > 0; index--) {
234
+ if (stack[index]?.nodeName !== name) continue;
235
+ stack.length = index;
236
+ break;
237
+ }
238
+ continue;
239
+ }
240
+ const opening = /^<\s*([a-z][\w:-]*)([\s\S]*?)\/?\s*>$/i.exec(token);
241
+ if (!opening?.[1]) {
242
+ parent.append(new HtmlText("<"));
243
+ continue;
244
+ }
245
+ const element = new HtmlElement(opening[1], parseAttributes(opening[2] ?? ""));
246
+ parent.append(element);
247
+ if (!VOID_ELEMENTS[element.nodeName] && !/\/\s*>$/.test(token)) stack.push(element);
248
+ }
249
+ return root;
250
+ }
251
+
252
+ /** Serialize a standards-shaped or internal HTML node. */
253
+ export function serializeNode(node: TurndownNode): string {
254
+ if (typeof node.outerHTML === "string") return node.outerHTML;
255
+ if (node.nodeType === 3) return node.textContent ?? "";
256
+ return Array.from(node.childNodes).map(serializeNode).join("");
257
+ }