@graphorin/provider 0.5.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 +6 -0
- package/LICENSE +21 -0
- package/README.md +105 -0
- package/dist/adapters/index.d.ts +4 -0
- package/dist/adapters/index.js +6 -0
- package/dist/adapters/llamacpp-server.d.ts +69 -0
- package/dist/adapters/llamacpp-server.d.ts.map +1 -0
- package/dist/adapters/llamacpp-server.js +51 -0
- package/dist/adapters/llamacpp-server.js.map +1 -0
- package/dist/adapters/ollama.d.ts +43 -0
- package/dist/adapters/ollama.d.ts.map +1 -0
- package/dist/adapters/ollama.js +246 -0
- package/dist/adapters/ollama.js.map +1 -0
- package/dist/adapters/openai-compatible.d.ts +48 -0
- package/dist/adapters/openai-compatible.d.ts.map +1 -0
- package/dist/adapters/openai-compatible.js +28 -0
- package/dist/adapters/openai-compatible.js.map +1 -0
- package/dist/adapters/vercel.d.ts +135 -0
- package/dist/adapters/vercel.d.ts.map +1 -0
- package/dist/adapters/vercel.js +306 -0
- package/dist/adapters/vercel.js.map +1 -0
- package/dist/counters/anthropic.d.ts +36 -0
- package/dist/counters/anthropic.d.ts.map +1 -0
- package/dist/counters/anthropic.js +63 -0
- package/dist/counters/anthropic.js.map +1 -0
- package/dist/counters/bedrock.d.ts +27 -0
- package/dist/counters/bedrock.d.ts.map +1 -0
- package/dist/counters/bedrock.js +29 -0
- package/dist/counters/bedrock.js.map +1 -0
- package/dist/counters/dispatcher.d.ts +34 -0
- package/dist/counters/dispatcher.d.ts.map +1 -0
- package/dist/counters/dispatcher.js +78 -0
- package/dist/counters/dispatcher.js.map +1 -0
- package/dist/counters/global.d.ts +26 -0
- package/dist/counters/global.d.ts.map +1 -0
- package/dist/counters/global.js +31 -0
- package/dist/counters/global.js.map +1 -0
- package/dist/counters/google.d.ts +27 -0
- package/dist/counters/google.d.ts.map +1 -0
- package/dist/counters/google.js +29 -0
- package/dist/counters/google.js.map +1 -0
- package/dist/counters/heuristic.d.ts +37 -0
- package/dist/counters/heuristic.d.ts.map +1 -0
- package/dist/counters/heuristic.js +64 -0
- package/dist/counters/heuristic.js.map +1 -0
- package/dist/counters/index.d.ts +9 -0
- package/dist/counters/index.js +10 -0
- package/dist/counters/js-tiktoken.d.ts +63 -0
- package/dist/counters/js-tiktoken.d.ts.map +1 -0
- package/dist/counters/js-tiktoken.js +73 -0
- package/dist/counters/js-tiktoken.js.map +1 -0
- package/dist/counters/serialize.d.ts +37 -0
- package/dist/counters/serialize.d.ts.map +1 -0
- package/dist/counters/serialize.js +57 -0
- package/dist/counters/serialize.js.map +1 -0
- package/dist/errors/errors.d.ts +167 -0
- package/dist/errors/errors.d.ts.map +1 -0
- package/dist/errors/errors.js +182 -0
- package/dist/errors/errors.js.map +1 -0
- package/dist/errors/index.d.ts +2 -0
- package/dist/errors/index.js +3 -0
- package/dist/index.d.ts +69 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +70 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/abort.js +34 -0
- package/dist/internal/abort.js.map +1 -0
- package/dist/internal/http.js +163 -0
- package/dist/internal/http.js.map +1 -0
- package/dist/internal/openai-shaped.js +308 -0
- package/dist/internal/openai-shaped.js.map +1 -0
- package/dist/internal/sse.js +104 -0
- package/dist/internal/sse.js.map +1 -0
- package/dist/internal/url-utils.js +24 -0
- package/dist/internal/url-utils.js.map +1 -0
- package/dist/middleware/compose.d.ts +85 -0
- package/dist/middleware/compose.d.ts.map +1 -0
- package/dist/middleware/compose.js +174 -0
- package/dist/middleware/compose.js.map +1 -0
- package/dist/middleware/index.d.ts +10 -0
- package/dist/middleware/index.js +11 -0
- package/dist/middleware/production-hook.d.ts +27 -0
- package/dist/middleware/production-hook.d.ts.map +1 -0
- package/dist/middleware/production-hook.js +21 -0
- package/dist/middleware/production-hook.js.map +1 -0
- package/dist/middleware/with-cost-limit.d.ts +46 -0
- package/dist/middleware/with-cost-limit.d.ts.map +1 -0
- package/dist/middleware/with-cost-limit.js +66 -0
- package/dist/middleware/with-cost-limit.js.map +1 -0
- package/dist/middleware/with-cost-tracking.d.ts +91 -0
- package/dist/middleware/with-cost-tracking.d.ts.map +1 -0
- package/dist/middleware/with-cost-tracking.js +95 -0
- package/dist/middleware/with-cost-tracking.js.map +1 -0
- package/dist/middleware/with-fallback.d.ts +25 -0
- package/dist/middleware/with-fallback.d.ts.map +1 -0
- package/dist/middleware/with-fallback.js +84 -0
- package/dist/middleware/with-fallback.js.map +1 -0
- package/dist/middleware/with-rate-limit.d.ts +36 -0
- package/dist/middleware/with-rate-limit.d.ts.map +1 -0
- package/dist/middleware/with-rate-limit.js +141 -0
- package/dist/middleware/with-rate-limit.js.map +1 -0
- package/dist/middleware/with-redaction.d.ts +60 -0
- package/dist/middleware/with-redaction.d.ts.map +1 -0
- package/dist/middleware/with-redaction.js +305 -0
- package/dist/middleware/with-redaction.js.map +1 -0
- package/dist/middleware/with-retry.d.ts +26 -0
- package/dist/middleware/with-retry.d.ts.map +1 -0
- package/dist/middleware/with-retry.js +157 -0
- package/dist/middleware/with-retry.js.map +1 -0
- package/dist/middleware/with-tracing.d.ts +21 -0
- package/dist/middleware/with-tracing.d.ts.map +1 -0
- package/dist/middleware/with-tracing.js +55 -0
- package/dist/middleware/with-tracing.js.map +1 -0
- package/dist/model-tier/classify.d.ts +34 -0
- package/dist/model-tier/classify.d.ts.map +1 -0
- package/dist/model-tier/classify.js +117 -0
- package/dist/model-tier/classify.js.map +1 -0
- package/dist/model-tier/index.d.ts +2 -0
- package/dist/model-tier/index.js +3 -0
- package/dist/provider.d.ts +51 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +64 -0
- package/dist/provider.js.map +1 -0
- package/dist/reasoning/apply-policy.d.ts +22 -0
- package/dist/reasoning/apply-policy.d.ts.map +1 -0
- package/dist/reasoning/apply-policy.js +47 -0
- package/dist/reasoning/apply-policy.js.map +1 -0
- package/dist/reasoning/classify-contract.d.ts +41 -0
- package/dist/reasoning/classify-contract.d.ts.map +1 -0
- package/dist/reasoning/classify-contract.js +59 -0
- package/dist/reasoning/classify-contract.js.map +1 -0
- package/dist/reasoning/index.d.ts +4 -0
- package/dist/reasoning/index.js +5 -0
- package/dist/reasoning/retention.d.ts +39 -0
- package/dist/reasoning/retention.d.ts.map +1 -0
- package/dist/reasoning/retention.js +33 -0
- package/dist/reasoning/retention.js.map +1 -0
- package/dist/tool-examples.d.ts +14 -0
- package/dist/tool-examples.d.ts.map +1 -0
- package/dist/tool-examples.js +35 -0
- package/dist/tool-examples.js.map +1 -0
- package/dist/trust/classify-local-provider.d.ts +47 -0
- package/dist/trust/classify-local-provider.d.ts.map +1 -0
- package/dist/trust/classify-local-provider.js +164 -0
- package/dist/trust/classify-local-provider.js.map +1 -0
- package/dist/trust/index.d.ts +3 -0
- package/dist/trust/index.js +3 -0
- package/package.json +128 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
//#region src/internal/sse.ts
|
|
2
|
+
/**
|
|
3
|
+
* Minimal `text/event-stream` chunk parser. Lives here because three
|
|
4
|
+
* adapters consume the same shape (`llamaCppServerAdapter`,
|
|
5
|
+
* `openAICompatibleAdapter`, plus the OpenAI-compatible legacy path
|
|
6
|
+
* inside `ollamaAdapter`). Keeping it local avoids pulling in a
|
|
7
|
+
* dedicated SSE package.
|
|
8
|
+
*
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Parse a Web `Response`'s body as Server-Sent Events. Yields one
|
|
13
|
+
* payload per `data: <payload>` line; ignores `event:` / `id:` / `:`
|
|
14
|
+
* comments lines per the SSE spec. Terminates on `data: [DONE]` per
|
|
15
|
+
* the OpenAI-compatible convention.
|
|
16
|
+
*
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
async function* parseEventStream(body, options = {}) {
|
|
20
|
+
if (body === null) return;
|
|
21
|
+
const reader = body.getReader();
|
|
22
|
+
const decoder = new TextDecoder("utf-8");
|
|
23
|
+
let buffer = "";
|
|
24
|
+
try {
|
|
25
|
+
while (true) {
|
|
26
|
+
if (options.signal?.aborted) return;
|
|
27
|
+
const { value, done } = await reader.read();
|
|
28
|
+
if (done) break;
|
|
29
|
+
buffer += decoder.decode(value, { stream: true });
|
|
30
|
+
let separatorIdx = findSeparator(buffer);
|
|
31
|
+
while (separatorIdx !== -1) {
|
|
32
|
+
const eventBlock = buffer.slice(0, separatorIdx);
|
|
33
|
+
buffer = buffer.slice(separatorIdx + matchSeparatorLength(buffer, separatorIdx));
|
|
34
|
+
const dataPayload = extractDataLines(eventBlock);
|
|
35
|
+
if (dataPayload === "[DONE]") return;
|
|
36
|
+
if (dataPayload !== null) yield dataPayload;
|
|
37
|
+
separatorIdx = findSeparator(buffer);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const tail = buffer.trim();
|
|
41
|
+
if (tail.length > 0) {
|
|
42
|
+
const dataPayload = extractDataLines(tail);
|
|
43
|
+
if (dataPayload !== null && dataPayload !== "[DONE]") yield dataPayload;
|
|
44
|
+
}
|
|
45
|
+
} finally {
|
|
46
|
+
reader.releaseLock();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function findSeparator(buffer) {
|
|
50
|
+
const lf = buffer.indexOf("\n\n");
|
|
51
|
+
const crlf = buffer.indexOf("\r\n\r\n");
|
|
52
|
+
if (lf === -1) return crlf;
|
|
53
|
+
if (crlf === -1) return lf;
|
|
54
|
+
return lf < crlf ? lf : crlf;
|
|
55
|
+
}
|
|
56
|
+
function matchSeparatorLength(buffer, idx) {
|
|
57
|
+
return buffer.startsWith("\r\n\r\n", idx) ? 4 : 2;
|
|
58
|
+
}
|
|
59
|
+
function extractDataLines(block) {
|
|
60
|
+
const dataLines = [];
|
|
61
|
+
for (const rawLine of block.split(/\r?\n/)) {
|
|
62
|
+
const line = rawLine;
|
|
63
|
+
if (line.startsWith(":")) continue;
|
|
64
|
+
if (line.startsWith("data:")) dataLines.push(line.slice(5).replace(/^ /, ""));
|
|
65
|
+
}
|
|
66
|
+
if (dataLines.length === 0) return null;
|
|
67
|
+
return dataLines.join("\n");
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Parse a newline-delimited JSON stream. Used by the native Ollama
|
|
71
|
+
* `/api/chat` path which emits one JSON object per `\n`-terminated
|
|
72
|
+
* line (NOT SSE).
|
|
73
|
+
*
|
|
74
|
+
* @internal
|
|
75
|
+
*/
|
|
76
|
+
async function* parseNdJsonStream(body, options = {}) {
|
|
77
|
+
if (body === null) return;
|
|
78
|
+
const reader = body.getReader();
|
|
79
|
+
const decoder = new TextDecoder("utf-8");
|
|
80
|
+
let buffer = "";
|
|
81
|
+
try {
|
|
82
|
+
while (true) {
|
|
83
|
+
if (options.signal?.aborted) return;
|
|
84
|
+
const { value, done } = await reader.read();
|
|
85
|
+
if (done) break;
|
|
86
|
+
buffer += decoder.decode(value, { stream: true });
|
|
87
|
+
let nlIdx = buffer.indexOf("\n");
|
|
88
|
+
while (nlIdx !== -1) {
|
|
89
|
+
const line = buffer.slice(0, nlIdx).trim();
|
|
90
|
+
buffer = buffer.slice(nlIdx + 1);
|
|
91
|
+
if (line.length > 0) yield line;
|
|
92
|
+
nlIdx = buffer.indexOf("\n");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const tail = buffer.trim();
|
|
96
|
+
if (tail.length > 0) yield tail;
|
|
97
|
+
} finally {
|
|
98
|
+
reader.releaseLock();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
//#endregion
|
|
103
|
+
export { parseEventStream, parseNdJsonStream };
|
|
104
|
+
//# sourceMappingURL=sse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse.js","names":["dataLines: string[]"],"sources":["../../src/internal/sse.ts"],"sourcesContent":["/**\n * Minimal `text/event-stream` chunk parser. Lives here because three\n * adapters consume the same shape (`llamaCppServerAdapter`,\n * `openAICompatibleAdapter`, plus the OpenAI-compatible legacy path\n * inside `ollamaAdapter`). Keeping it local avoids pulling in a\n * dedicated SSE package.\n *\n * @internal\n */\n\n/**\n * Parse a Web `Response`'s body as Server-Sent Events. Yields one\n * payload per `data: <payload>` line; ignores `event:` / `id:` / `:`\n * comments lines per the SSE spec. Terminates on `data: [DONE]` per\n * the OpenAI-compatible convention.\n *\n * @internal\n */\nexport async function* parseEventStream(\n body: ReadableStream<Uint8Array> | null,\n options: { readonly signal?: AbortSignal } = {},\n): AsyncIterable<string> {\n if (body === null) return;\n const reader = body.getReader();\n const decoder = new TextDecoder('utf-8');\n let buffer = '';\n try {\n while (true) {\n if (options.signal?.aborted) return;\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n let separatorIdx = findSeparator(buffer);\n while (separatorIdx !== -1) {\n const eventBlock = buffer.slice(0, separatorIdx);\n buffer = buffer.slice(separatorIdx + matchSeparatorLength(buffer, separatorIdx));\n const dataPayload = extractDataLines(eventBlock);\n if (dataPayload === '[DONE]') return;\n if (dataPayload !== null) yield dataPayload;\n separatorIdx = findSeparator(buffer);\n }\n }\n const tail = buffer.trim();\n if (tail.length > 0) {\n const dataPayload = extractDataLines(tail);\n if (dataPayload !== null && dataPayload !== '[DONE]') yield dataPayload;\n }\n } finally {\n reader.releaseLock();\n }\n}\n\nfunction findSeparator(buffer: string): number {\n const lf = buffer.indexOf('\\n\\n');\n const crlf = buffer.indexOf('\\r\\n\\r\\n');\n if (lf === -1) return crlf;\n if (crlf === -1) return lf;\n return lf < crlf ? lf : crlf;\n}\n\nfunction matchSeparatorLength(buffer: string, idx: number): number {\n return buffer.startsWith('\\r\\n\\r\\n', idx) ? 4 : 2;\n}\n\nfunction extractDataLines(block: string): string | null {\n const dataLines: string[] = [];\n for (const rawLine of block.split(/\\r?\\n/)) {\n const line = rawLine;\n if (line.startsWith(':')) continue;\n if (line.startsWith('data:')) {\n dataLines.push(line.slice(5).replace(/^ /, ''));\n }\n }\n if (dataLines.length === 0) return null;\n return dataLines.join('\\n');\n}\n\n/**\n * Parse a newline-delimited JSON stream. Used by the native Ollama\n * `/api/chat` path which emits one JSON object per `\\n`-terminated\n * line (NOT SSE).\n *\n * @internal\n */\nexport async function* parseNdJsonStream(\n body: ReadableStream<Uint8Array> | null,\n options: { readonly signal?: AbortSignal } = {},\n): AsyncIterable<string> {\n if (body === null) return;\n const reader = body.getReader();\n const decoder = new TextDecoder('utf-8');\n let buffer = '';\n try {\n while (true) {\n if (options.signal?.aborted) return;\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n let nlIdx = buffer.indexOf('\\n');\n while (nlIdx !== -1) {\n const line = buffer.slice(0, nlIdx).trim();\n buffer = buffer.slice(nlIdx + 1);\n if (line.length > 0) yield line;\n nlIdx = buffer.indexOf('\\n');\n }\n }\n const tail = buffer.trim();\n if (tail.length > 0) yield tail;\n } finally {\n reader.releaseLock();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,gBAAuB,iBACrB,MACA,UAA6C,EAAE,EACxB;AACvB,KAAI,SAAS,KAAM;CACnB,MAAM,SAAS,KAAK,WAAW;CAC/B,MAAM,UAAU,IAAI,YAAY,QAAQ;CACxC,IAAI,SAAS;AACb,KAAI;AACF,SAAO,MAAM;AACX,OAAI,QAAQ,QAAQ,QAAS;GAC7B,MAAM,EAAE,OAAO,SAAS,MAAM,OAAO,MAAM;AAC3C,OAAI,KAAM;AACV,aAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,MAAM,CAAC;GACjD,IAAI,eAAe,cAAc,OAAO;AACxC,UAAO,iBAAiB,IAAI;IAC1B,MAAM,aAAa,OAAO,MAAM,GAAG,aAAa;AAChD,aAAS,OAAO,MAAM,eAAe,qBAAqB,QAAQ,aAAa,CAAC;IAChF,MAAM,cAAc,iBAAiB,WAAW;AAChD,QAAI,gBAAgB,SAAU;AAC9B,QAAI,gBAAgB,KAAM,OAAM;AAChC,mBAAe,cAAc,OAAO;;;EAGxC,MAAM,OAAO,OAAO,MAAM;AAC1B,MAAI,KAAK,SAAS,GAAG;GACnB,MAAM,cAAc,iBAAiB,KAAK;AAC1C,OAAI,gBAAgB,QAAQ,gBAAgB,SAAU,OAAM;;WAEtD;AACR,SAAO,aAAa;;;AAIxB,SAAS,cAAc,QAAwB;CAC7C,MAAM,KAAK,OAAO,QAAQ,OAAO;CACjC,MAAM,OAAO,OAAO,QAAQ,WAAW;AACvC,KAAI,OAAO,GAAI,QAAO;AACtB,KAAI,SAAS,GAAI,QAAO;AACxB,QAAO,KAAK,OAAO,KAAK;;AAG1B,SAAS,qBAAqB,QAAgB,KAAqB;AACjE,QAAO,OAAO,WAAW,YAAY,IAAI,GAAG,IAAI;;AAGlD,SAAS,iBAAiB,OAA8B;CACtD,MAAMA,YAAsB,EAAE;AAC9B,MAAK,MAAM,WAAW,MAAM,MAAM,QAAQ,EAAE;EAC1C,MAAM,OAAO;AACb,MAAI,KAAK,WAAW,IAAI,CAAE;AAC1B,MAAI,KAAK,WAAW,QAAQ,CAC1B,WAAU,KAAK,KAAK,MAAM,EAAE,CAAC,QAAQ,MAAM,GAAG,CAAC;;AAGnD,KAAI,UAAU,WAAW,EAAG,QAAO;AACnC,QAAO,UAAU,KAAK,KAAK;;;;;;;;;AAU7B,gBAAuB,kBACrB,MACA,UAA6C,EAAE,EACxB;AACvB,KAAI,SAAS,KAAM;CACnB,MAAM,SAAS,KAAK,WAAW;CAC/B,MAAM,UAAU,IAAI,YAAY,QAAQ;CACxC,IAAI,SAAS;AACb,KAAI;AACF,SAAO,MAAM;AACX,OAAI,QAAQ,QAAQ,QAAS;GAC7B,MAAM,EAAE,OAAO,SAAS,MAAM,OAAO,MAAM;AAC3C,OAAI,KAAM;AACV,aAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,MAAM,CAAC;GACjD,IAAI,QAAQ,OAAO,QAAQ,KAAK;AAChC,UAAO,UAAU,IAAI;IACnB,MAAM,OAAO,OAAO,MAAM,GAAG,MAAM,CAAC,MAAM;AAC1C,aAAS,OAAO,MAAM,QAAQ,EAAE;AAChC,QAAI,KAAK,SAAS,EAAG,OAAM;AAC3B,YAAQ,OAAO,QAAQ,KAAK;;;EAGhC,MAAM,OAAO,OAAO,MAAM;AAC1B,MAAI,KAAK,SAAS,EAAG,OAAM;WACnB;AACR,SAAO,aAAa"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region src/internal/url-utils.ts
|
|
2
|
+
/**
|
|
3
|
+
* Tiny URL helpers shared between the provider adapters. Kept regex-free
|
|
4
|
+
* so CodeQL does not flag the (otherwise harmless) `/+` quantifier on
|
|
5
|
+
* adapter base URLs.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Strip every trailing `/` from a URL string. Operates in `O(n)` time
|
|
11
|
+
* with a single linear scan from the end, so it is safe to call on
|
|
12
|
+
* adversarial inputs without bounding the length first.
|
|
13
|
+
*
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
function stripTrailingSlashes(url) {
|
|
17
|
+
let end = url.length;
|
|
18
|
+
while (end > 0 && url.charCodeAt(end - 1) === 47) end -= 1;
|
|
19
|
+
return end === url.length ? url : url.slice(0, end);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
//#endregion
|
|
23
|
+
export { stripTrailingSlashes };
|
|
24
|
+
//# sourceMappingURL=url-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url-utils.js","names":[],"sources":["../../src/internal/url-utils.ts"],"sourcesContent":["/**\n * Tiny URL helpers shared between the provider adapters. Kept regex-free\n * so CodeQL does not flag the (otherwise harmless) `/+` quantifier on\n * adapter base URLs.\n *\n * @internal\n */\n\n/**\n * Strip every trailing `/` from a URL string. Operates in `O(n)` time\n * with a single linear scan from the end, so it is safe to call on\n * adversarial inputs without bounding the length first.\n *\n * @internal\n */\nexport function stripTrailingSlashes(url: string): string {\n let end = url.length;\n while (end > 0 && url.charCodeAt(end - 1) === 0x2f /* '/' */) end -= 1;\n return end === url.length ? url : url.slice(0, end);\n}\n"],"mappings":";;;;;;;;;;;;;;;AAeA,SAAgB,qBAAqB,KAAqB;CACxD,IAAI,MAAM,IAAI;AACd,QAAO,MAAM,KAAK,IAAI,WAAW,MAAM,EAAE,KAAK,GAAgB,QAAO;AACrE,QAAO,QAAQ,IAAI,SAAS,MAAM,IAAI,MAAM,GAAG,IAAI"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Provider, ProviderMiddleware } from "@graphorin/core";
|
|
2
|
+
|
|
3
|
+
//#region src/middleware/compose.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Symbol attached to every middleware-produced provider so the
|
|
7
|
+
* composer can detect and validate the chain. The symbol is opaque
|
|
8
|
+
* and cross-realm safe via `Symbol.for`.
|
|
9
|
+
*
|
|
10
|
+
* @stable
|
|
11
|
+
*/
|
|
12
|
+
declare const MIDDLEWARE_KIND: unique symbol;
|
|
13
|
+
/**
|
|
14
|
+
* Canonical middleware ordering — outermost → innermost. The table
|
|
15
|
+
* is enforced by {@link composeProviderMiddleware} and is part of the
|
|
16
|
+
* provider layer's public contract (DEC-145 / ADR-039).
|
|
17
|
+
*
|
|
18
|
+
* ## Why this order
|
|
19
|
+
*
|
|
20
|
+
* Composition is outermost-first, so a request flows top→bottom and a
|
|
21
|
+
* response flows bottom→top:
|
|
22
|
+
*
|
|
23
|
+
* - `withTracing` is outermost so the span wraps everything below —
|
|
24
|
+
* including retries — and records true end-to-end latency.
|
|
25
|
+
* - `withRetry` sits above the rate/cost limiters so each retry
|
|
26
|
+
* attempt is independently counted and throttled.
|
|
27
|
+
* - `withRateLimit` → `withCostLimit` → `withCostTracking` form the
|
|
28
|
+
* budget stack: throttle before admitting, reject over-budget calls
|
|
29
|
+
* before spending, then meter what actually went through.
|
|
30
|
+
* - `withFallback` is just above redaction so a fallback to a
|
|
31
|
+
* secondary provider still passes through the redactor.
|
|
32
|
+
* - `withRedaction` is **innermost** (closest to the provider) so it
|
|
33
|
+
* is the last thing to touch the outbound payload and the first to
|
|
34
|
+
* touch the inbound stream — guaranteeing every retry, fallback, and
|
|
35
|
+
* cost-tracked request sees an already-redacted payload and no
|
|
36
|
+
* secret can bypass it.
|
|
37
|
+
*
|
|
38
|
+
* @stable
|
|
39
|
+
*/
|
|
40
|
+
declare const CANONICAL_MIDDLEWARE_ORDER: readonly string[];
|
|
41
|
+
/**
|
|
42
|
+
* Read the discriminant kind attached to a middleware-produced
|
|
43
|
+
* provider. Returns `undefined` if the provider is the bare adapter
|
|
44
|
+
* or a custom wrapper that does not declare a kind.
|
|
45
|
+
*
|
|
46
|
+
* @stable
|
|
47
|
+
*/
|
|
48
|
+
declare function getMiddlewareKind(provider: Provider): string | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Return `true` iff the chain rooted at `provider` contains a
|
|
51
|
+
* middleware whose kind matches `name`.
|
|
52
|
+
*
|
|
53
|
+
* @stable
|
|
54
|
+
*/
|
|
55
|
+
declare function providerHasMiddleware(provider: Provider, name: string): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Wrap an adapter in a middleware chain whose order is validated
|
|
58
|
+
* against {@link CANONICAL_MIDDLEWARE_ORDER}. The argument array MUST
|
|
59
|
+
* be ordered outermost → innermost — the same way the layers appear
|
|
60
|
+
* in the documented composition example. The composer validates that
|
|
61
|
+
* every kind known to the canonical order is monotonically non-
|
|
62
|
+
* decreasing in index, throws otherwise.
|
|
63
|
+
*
|
|
64
|
+
* Custom middleware whose kind is NOT in the canonical order is
|
|
65
|
+
* silently allowed at any position — operators registering bespoke
|
|
66
|
+
* layers via {@link defineProviderMiddleware} carry the
|
|
67
|
+
* responsibility of placing them sensibly.
|
|
68
|
+
*
|
|
69
|
+
* @stable
|
|
70
|
+
*/
|
|
71
|
+
declare function composeProviderMiddleware(middlewares: ReadonlyArray<ProviderMiddleware>): ProviderMiddleware;
|
|
72
|
+
/**
|
|
73
|
+
* Decorator factory used internally by every built-in middleware. The
|
|
74
|
+
* returned function attaches the canonical kind discriminator and the
|
|
75
|
+
* inner-provider symbol so the composer can introspect chains.
|
|
76
|
+
*
|
|
77
|
+
* @stable
|
|
78
|
+
*/
|
|
79
|
+
declare function defineProviderMiddleware<T>(args: {
|
|
80
|
+
readonly kind: string;
|
|
81
|
+
readonly factory: (opts: T) => ProviderMiddleware;
|
|
82
|
+
}): (opts: T) => ProviderMiddleware;
|
|
83
|
+
//#endregion
|
|
84
|
+
export { CANONICAL_MIDDLEWARE_ORDER, MIDDLEWARE_KIND, composeProviderMiddleware, defineProviderMiddleware, getMiddlewareKind, providerHasMiddleware };
|
|
85
|
+
//# sourceMappingURL=compose.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose.d.ts","names":[],"sources":["../../src/middleware/compose.ts"],"sourcesContent":[],"mappings":";;;;AAgMA;;;;;;;cAzKa;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA6BA;;;;;;;;iBAqCG,iBAAA,WAA4B;;;;;;;iBA2B5B,qBAAA,WAAgC;;;;;;;;;;;;;;;;iBAmBhC,yBAAA,cACD,cAAc,sBAC1B;;;;;;;;iBAuDa;;2BAEW,MAAM;WACtB,MAAM"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { MiddlewareOrderingError } from "../errors/errors.js";
|
|
2
|
+
|
|
3
|
+
//#region src/middleware/compose.ts
|
|
4
|
+
/**
|
|
5
|
+
* Symbol attached to every middleware-produced provider so the
|
|
6
|
+
* composer can detect and validate the chain. The symbol is opaque
|
|
7
|
+
* and cross-realm safe via `Symbol.for`.
|
|
8
|
+
*
|
|
9
|
+
* @stable
|
|
10
|
+
*/
|
|
11
|
+
const MIDDLEWARE_KIND = Symbol.for("graphorin.provider.middleware.kind");
|
|
12
|
+
/**
|
|
13
|
+
* Canonical middleware ordering — outermost → innermost. The table
|
|
14
|
+
* is enforced by {@link composeProviderMiddleware} and is part of the
|
|
15
|
+
* provider layer's public contract (DEC-145 / ADR-039).
|
|
16
|
+
*
|
|
17
|
+
* ## Why this order
|
|
18
|
+
*
|
|
19
|
+
* Composition is outermost-first, so a request flows top→bottom and a
|
|
20
|
+
* response flows bottom→top:
|
|
21
|
+
*
|
|
22
|
+
* - `withTracing` is outermost so the span wraps everything below —
|
|
23
|
+
* including retries — and records true end-to-end latency.
|
|
24
|
+
* - `withRetry` sits above the rate/cost limiters so each retry
|
|
25
|
+
* attempt is independently counted and throttled.
|
|
26
|
+
* - `withRateLimit` → `withCostLimit` → `withCostTracking` form the
|
|
27
|
+
* budget stack: throttle before admitting, reject over-budget calls
|
|
28
|
+
* before spending, then meter what actually went through.
|
|
29
|
+
* - `withFallback` is just above redaction so a fallback to a
|
|
30
|
+
* secondary provider still passes through the redactor.
|
|
31
|
+
* - `withRedaction` is **innermost** (closest to the provider) so it
|
|
32
|
+
* is the last thing to touch the outbound payload and the first to
|
|
33
|
+
* touch the inbound stream — guaranteeing every retry, fallback, and
|
|
34
|
+
* cost-tracked request sees an already-redacted payload and no
|
|
35
|
+
* secret can bypass it.
|
|
36
|
+
*
|
|
37
|
+
* @stable
|
|
38
|
+
*/
|
|
39
|
+
const CANONICAL_MIDDLEWARE_ORDER = [
|
|
40
|
+
"withTracing",
|
|
41
|
+
"withRetry",
|
|
42
|
+
"withRateLimit",
|
|
43
|
+
"withCostLimit",
|
|
44
|
+
"withCostTracking",
|
|
45
|
+
"withFallback",
|
|
46
|
+
"withRedaction"
|
|
47
|
+
];
|
|
48
|
+
const CANONICAL_INDEX = Object.freeze(CANONICAL_MIDDLEWARE_ORDER.reduce((acc, name, idx) => {
|
|
49
|
+
acc[name] = idx;
|
|
50
|
+
return acc;
|
|
51
|
+
}, {}));
|
|
52
|
+
/**
|
|
53
|
+
* Symbol used to walk the chain — every wrapper exposes the inner
|
|
54
|
+
* provider so the composer can introspect the full stack at startup.
|
|
55
|
+
*
|
|
56
|
+
* @stable
|
|
57
|
+
*/
|
|
58
|
+
const INNER_PROVIDER = Symbol.for("graphorin.provider.middleware.inner");
|
|
59
|
+
/**
|
|
60
|
+
* Read the discriminant kind attached to a middleware-produced
|
|
61
|
+
* provider. Returns `undefined` if the provider is the bare adapter
|
|
62
|
+
* or a custom wrapper that does not declare a kind.
|
|
63
|
+
*
|
|
64
|
+
* @stable
|
|
65
|
+
*/
|
|
66
|
+
function getMiddlewareKind(provider) {
|
|
67
|
+
return provider[MIDDLEWARE_KIND];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Walk the middleware chain inside `provider` and return the array of
|
|
71
|
+
* declared kinds (outer → inner).
|
|
72
|
+
*
|
|
73
|
+
* @stable
|
|
74
|
+
*/
|
|
75
|
+
function listMiddlewareKinds(provider) {
|
|
76
|
+
const kinds = [];
|
|
77
|
+
let cur = provider;
|
|
78
|
+
while (cur !== void 0) {
|
|
79
|
+
const kind = cur[MIDDLEWARE_KIND];
|
|
80
|
+
if (kind !== void 0) kinds.push(kind);
|
|
81
|
+
cur = cur[INNER_PROVIDER];
|
|
82
|
+
}
|
|
83
|
+
return kinds;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Return `true` iff the chain rooted at `provider` contains a
|
|
87
|
+
* middleware whose kind matches `name`.
|
|
88
|
+
*
|
|
89
|
+
* @stable
|
|
90
|
+
*/
|
|
91
|
+
function providerHasMiddleware(provider, name) {
|
|
92
|
+
return listMiddlewareKinds(provider).includes(name);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Wrap an adapter in a middleware chain whose order is validated
|
|
96
|
+
* against {@link CANONICAL_MIDDLEWARE_ORDER}. The argument array MUST
|
|
97
|
+
* be ordered outermost → innermost — the same way the layers appear
|
|
98
|
+
* in the documented composition example. The composer validates that
|
|
99
|
+
* every kind known to the canonical order is monotonically non-
|
|
100
|
+
* decreasing in index, throws otherwise.
|
|
101
|
+
*
|
|
102
|
+
* Custom middleware whose kind is NOT in the canonical order is
|
|
103
|
+
* silently allowed at any position — operators registering bespoke
|
|
104
|
+
* layers via {@link defineProviderMiddleware} carry the
|
|
105
|
+
* responsibility of placing them sensibly.
|
|
106
|
+
*
|
|
107
|
+
* @stable
|
|
108
|
+
*/
|
|
109
|
+
function composeProviderMiddleware(middlewares) {
|
|
110
|
+
return (next) => {
|
|
111
|
+
let chain = next;
|
|
112
|
+
for (let i = middlewares.length - 1; i >= 0; i--) {
|
|
113
|
+
const factory = middlewares[i];
|
|
114
|
+
if (typeof factory !== "function") throw new TypeError(`composeProviderMiddleware: entry at index ${i} is not a ProviderMiddleware function.`);
|
|
115
|
+
chain = factory(chain);
|
|
116
|
+
}
|
|
117
|
+
validateMiddlewareOrder(chain);
|
|
118
|
+
return chain;
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Validate the kinds present in `provider` against the canonical
|
|
123
|
+
* order. Exposed for tests; the composer calls it on every chain.
|
|
124
|
+
*
|
|
125
|
+
* @stable
|
|
126
|
+
*/
|
|
127
|
+
function validateMiddlewareOrder(provider) {
|
|
128
|
+
const kinds = listMiddlewareKinds(provider);
|
|
129
|
+
const recognised = [];
|
|
130
|
+
for (const kind of kinds) {
|
|
131
|
+
const idx = CANONICAL_INDEX[kind];
|
|
132
|
+
if (idx !== void 0) recognised.push({
|
|
133
|
+
kind,
|
|
134
|
+
index: idx
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
for (let i = 1; i < recognised.length; i++) {
|
|
138
|
+
const prev = recognised[i - 1];
|
|
139
|
+
const cur = recognised[i];
|
|
140
|
+
if (prev !== void 0 && cur !== void 0 && cur.index < prev.index) throw new MiddlewareOrderingError({
|
|
141
|
+
offendingPair: [prev.kind, cur.kind],
|
|
142
|
+
canonicalOrder: CANONICAL_MIDDLEWARE_ORDER
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Decorator factory used internally by every built-in middleware. The
|
|
148
|
+
* returned function attaches the canonical kind discriminator and the
|
|
149
|
+
* inner-provider symbol so the composer can introspect chains.
|
|
150
|
+
*
|
|
151
|
+
* @stable
|
|
152
|
+
*/
|
|
153
|
+
function defineProviderMiddleware(args) {
|
|
154
|
+
return (opts) => {
|
|
155
|
+
const built = args.factory(opts);
|
|
156
|
+
return (next) => {
|
|
157
|
+
const wrapped = built(next);
|
|
158
|
+
const branded = Object.create(wrapped);
|
|
159
|
+
Object.defineProperty(branded, MIDDLEWARE_KIND, {
|
|
160
|
+
value: args.kind,
|
|
161
|
+
enumerable: false
|
|
162
|
+
});
|
|
163
|
+
Object.defineProperty(branded, INNER_PROVIDER, {
|
|
164
|
+
value: next,
|
|
165
|
+
enumerable: false
|
|
166
|
+
});
|
|
167
|
+
return branded;
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
//#endregion
|
|
173
|
+
export { CANONICAL_MIDDLEWARE_ORDER, MIDDLEWARE_KIND, composeProviderMiddleware, defineProviderMiddleware, getMiddlewareKind, providerHasMiddleware };
|
|
174
|
+
//# sourceMappingURL=compose.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose.js","names":["MIDDLEWARE_KIND: unique symbol","CANONICAL_MIDDLEWARE_ORDER: readonly string[]","CANONICAL_INDEX: Readonly<Record<string, number>>","INNER_PROVIDER: unique symbol","kinds: string[]","cur: KindedProvider | undefined","chain: Provider","recognised: { kind: string; index: number }[]","branded: Provider"],"sources":["../../src/middleware/compose.ts"],"sourcesContent":["/**\n * Canonical-order middleware composer. The composer enforces the\n * documented ordering at startup and throws\n * {@link MiddlewareOrderingError} on violation. Manual\n * `withRetry(withRedaction(p))` style still works but emits a one-time\n * WARN unless the consumer flips `strict: true`, in which case the\n * composer also throws on manual nesting that violates the canonical\n * order.\n *\n * @packageDocumentation\n */\n\nimport type { Provider, ProviderMiddleware } from '@graphorin/core';\n\nimport { MiddlewareOrderingError } from '../errors/errors.js';\n\n/**\n * Symbol attached to every middleware-produced provider so the\n * composer can detect and validate the chain. The symbol is opaque\n * and cross-realm safe via `Symbol.for`.\n *\n * @stable\n */\nexport const MIDDLEWARE_KIND: unique symbol = Symbol.for('graphorin.provider.middleware.kind');\n\n/**\n * Canonical middleware ordering — outermost → innermost. The table\n * is enforced by {@link composeProviderMiddleware} and is part of the\n * provider layer's public contract (DEC-145 / ADR-039).\n *\n * ## Why this order\n *\n * Composition is outermost-first, so a request flows top→bottom and a\n * response flows bottom→top:\n *\n * - `withTracing` is outermost so the span wraps everything below —\n * including retries — and records true end-to-end latency.\n * - `withRetry` sits above the rate/cost limiters so each retry\n * attempt is independently counted and throttled.\n * - `withRateLimit` → `withCostLimit` → `withCostTracking` form the\n * budget stack: throttle before admitting, reject over-budget calls\n * before spending, then meter what actually went through.\n * - `withFallback` is just above redaction so a fallback to a\n * secondary provider still passes through the redactor.\n * - `withRedaction` is **innermost** (closest to the provider) so it\n * is the last thing to touch the outbound payload and the first to\n * touch the inbound stream — guaranteeing every retry, fallback, and\n * cost-tracked request sees an already-redacted payload and no\n * secret can bypass it.\n *\n * @stable\n */\nexport const CANONICAL_MIDDLEWARE_ORDER: readonly string[] = [\n 'withTracing',\n 'withRetry',\n 'withRateLimit',\n 'withCostLimit',\n 'withCostTracking',\n 'withFallback',\n 'withRedaction',\n];\n\nconst CANONICAL_INDEX: Readonly<Record<string, number>> = Object.freeze(\n CANONICAL_MIDDLEWARE_ORDER.reduce<Record<string, number>>((acc, name, idx) => {\n acc[name] = idx;\n return acc;\n }, {}),\n);\n\ninterface KindedProvider extends Provider {\n readonly [MIDDLEWARE_KIND]?: string;\n readonly [INNER_PROVIDER]?: Provider;\n}\n\n/**\n * Symbol used to walk the chain — every wrapper exposes the inner\n * provider so the composer can introspect the full stack at startup.\n *\n * @stable\n */\nexport const INNER_PROVIDER: unique symbol = Symbol.for('graphorin.provider.middleware.inner');\n\n/**\n * Read the discriminant kind attached to a middleware-produced\n * provider. Returns `undefined` if the provider is the bare adapter\n * or a custom wrapper that does not declare a kind.\n *\n * @stable\n */\nexport function getMiddlewareKind(provider: Provider): string | undefined {\n return (provider as KindedProvider)[MIDDLEWARE_KIND];\n}\n\n/**\n * Walk the middleware chain inside `provider` and return the array of\n * declared kinds (outer → inner).\n *\n * @stable\n */\nexport function listMiddlewareKinds(provider: Provider): readonly string[] {\n const kinds: string[] = [];\n let cur: KindedProvider | undefined = provider as KindedProvider;\n while (cur !== undefined) {\n const kind = cur[MIDDLEWARE_KIND];\n if (kind !== undefined) kinds.push(kind);\n cur = cur[INNER_PROVIDER] as KindedProvider | undefined;\n }\n return kinds;\n}\n\n/**\n * Return `true` iff the chain rooted at `provider` contains a\n * middleware whose kind matches `name`.\n *\n * @stable\n */\nexport function providerHasMiddleware(provider: Provider, name: string): boolean {\n return listMiddlewareKinds(provider).includes(name);\n}\n\n/**\n * Wrap an adapter in a middleware chain whose order is validated\n * against {@link CANONICAL_MIDDLEWARE_ORDER}. The argument array MUST\n * be ordered outermost → innermost — the same way the layers appear\n * in the documented composition example. The composer validates that\n * every kind known to the canonical order is monotonically non-\n * decreasing in index, throws otherwise.\n *\n * Custom middleware whose kind is NOT in the canonical order is\n * silently allowed at any position — operators registering bespoke\n * layers via {@link defineProviderMiddleware} carry the\n * responsibility of placing them sensibly.\n *\n * @stable\n */\nexport function composeProviderMiddleware(\n middlewares: ReadonlyArray<ProviderMiddleware>,\n): ProviderMiddleware {\n return (next: Provider): Provider => {\n let chain: Provider = next;\n // Apply innermost → outermost so the composed call order matches\n // the array's outer → inner declaration.\n for (let i = middlewares.length - 1; i >= 0; i--) {\n const factory = middlewares[i];\n if (typeof factory !== 'function') {\n throw new TypeError(\n `composeProviderMiddleware: entry at index ${i} is not a ProviderMiddleware function.`,\n );\n }\n chain = factory(chain);\n }\n validateMiddlewareOrder(chain);\n return chain;\n };\n}\n\n/**\n * Validate the kinds present in `provider` against the canonical\n * order. Exposed for tests; the composer calls it on every chain.\n *\n * @stable\n */\nexport function validateMiddlewareOrder(provider: Provider): void {\n const kinds = listMiddlewareKinds(provider);\n // Filter to the kinds we recognise.\n const recognised: { kind: string; index: number }[] = [];\n for (const kind of kinds) {\n const idx = CANONICAL_INDEX[kind];\n if (idx !== undefined) recognised.push({ kind, index: idx });\n }\n // The recognised list runs outer → inner; canonical indices must\n // increase monotonically. The first decreasing pair is the\n // violation.\n for (let i = 1; i < recognised.length; i++) {\n const prev = recognised[i - 1];\n const cur = recognised[i];\n if (prev !== undefined && cur !== undefined && cur.index < prev.index) {\n throw new MiddlewareOrderingError({\n offendingPair: [prev.kind, cur.kind],\n canonicalOrder: CANONICAL_MIDDLEWARE_ORDER,\n });\n }\n }\n}\n\n/**\n * Decorator factory used internally by every built-in middleware. The\n * returned function attaches the canonical kind discriminator and the\n * inner-provider symbol so the composer can introspect chains.\n *\n * @stable\n */\nexport function defineProviderMiddleware<T>(args: {\n readonly kind: string;\n readonly factory: (opts: T) => ProviderMiddleware;\n}): (opts: T) => ProviderMiddleware {\n return (opts: T): ProviderMiddleware => {\n const built = args.factory(opts);\n return (next: Provider): Provider => {\n const wrapped = built(next);\n const branded: Provider = Object.create(wrapped) as Provider;\n Object.defineProperty(branded, MIDDLEWARE_KIND, {\n value: args.kind,\n enumerable: false,\n });\n Object.defineProperty(branded, INNER_PROVIDER, {\n value: next,\n enumerable: false,\n });\n return branded;\n };\n };\n}\n"],"mappings":";;;;;;;;;;AAuBA,MAAaA,kBAAiC,OAAO,IAAI,qCAAqC;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6B9F,MAAaC,6BAAgD;CAC3D;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,MAAMC,kBAAoD,OAAO,OAC/D,2BAA2B,QAAgC,KAAK,MAAM,QAAQ;AAC5E,KAAI,QAAQ;AACZ,QAAO;GACN,EAAE,CAAC,CACP;;;;;;;AAaD,MAAaC,iBAAgC,OAAO,IAAI,sCAAsC;;;;;;;;AAS9F,SAAgB,kBAAkB,UAAwC;AACxE,QAAQ,SAA4B;;;;;;;;AAStC,SAAgB,oBAAoB,UAAuC;CACzE,MAAMC,QAAkB,EAAE;CAC1B,IAAIC,MAAkC;AACtC,QAAO,QAAQ,QAAW;EACxB,MAAM,OAAO,IAAI;AACjB,MAAI,SAAS,OAAW,OAAM,KAAK,KAAK;AACxC,QAAM,IAAI;;AAEZ,QAAO;;;;;;;;AAST,SAAgB,sBAAsB,UAAoB,MAAuB;AAC/E,QAAO,oBAAoB,SAAS,CAAC,SAAS,KAAK;;;;;;;;;;;;;;;;;AAkBrD,SAAgB,0BACd,aACoB;AACpB,SAAQ,SAA6B;EACnC,IAAIC,QAAkB;AAGtB,OAAK,IAAI,IAAI,YAAY,SAAS,GAAG,KAAK,GAAG,KAAK;GAChD,MAAM,UAAU,YAAY;AAC5B,OAAI,OAAO,YAAY,WACrB,OAAM,IAAI,UACR,6CAA6C,EAAE,wCAChD;AAEH,WAAQ,QAAQ,MAAM;;AAExB,0BAAwB,MAAM;AAC9B,SAAO;;;;;;;;;AAUX,SAAgB,wBAAwB,UAA0B;CAChE,MAAM,QAAQ,oBAAoB,SAAS;CAE3C,MAAMC,aAAgD,EAAE;AACxD,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,MAAM,gBAAgB;AAC5B,MAAI,QAAQ,OAAW,YAAW,KAAK;GAAE;GAAM,OAAO;GAAK,CAAC;;AAK9D,MAAK,IAAI,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;EAC1C,MAAM,OAAO,WAAW,IAAI;EAC5B,MAAM,MAAM,WAAW;AACvB,MAAI,SAAS,UAAa,QAAQ,UAAa,IAAI,QAAQ,KAAK,MAC9D,OAAM,IAAI,wBAAwB;GAChC,eAAe,CAAC,KAAK,MAAM,IAAI,KAAK;GACpC,gBAAgB;GACjB,CAAC;;;;;;;;;;AAYR,SAAgB,yBAA4B,MAGR;AAClC,SAAQ,SAAgC;EACtC,MAAM,QAAQ,KAAK,QAAQ,KAAK;AAChC,UAAQ,SAA6B;GACnC,MAAM,UAAU,MAAM,KAAK;GAC3B,MAAMC,UAAoB,OAAO,OAAO,QAAQ;AAChD,UAAO,eAAe,SAAS,iBAAiB;IAC9C,OAAO,KAAK;IACZ,YAAY;IACb,CAAC;AACF,UAAO,eAAe,SAAS,gBAAgB;IAC7C,OAAO;IACP,YAAY;IACb,CAAC;AACF,UAAO"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { CANONICAL_MIDDLEWARE_ORDER, MIDDLEWARE_KIND, composeProviderMiddleware, defineProviderMiddleware, getMiddlewareKind, providerHasMiddleware } from "./compose.js";
|
|
2
|
+
import { ProductionStartupHookOptions, assertProductionMiddleware } from "./production-hook.js";
|
|
3
|
+
import { WithCostLimitOptions, withCostLimit } from "./with-cost-limit.js";
|
|
4
|
+
import { CostAccumulator, CostTrackingTotals, WithCostTrackingOptions, createCostAccumulator, withCostTracking } from "./with-cost-tracking.js";
|
|
5
|
+
import { WithFallbackOptions, withFallback } from "./with-fallback.js";
|
|
6
|
+
import { WithRateLimitOptions, withRateLimit } from "./with-rate-limit.js";
|
|
7
|
+
import { PromptRedactionPolicy, PromptRedactionScanScope, PromptRedactionViolation, withRedaction } from "./with-redaction.js";
|
|
8
|
+
import { WithRetryOptions, withRetry } from "./with-retry.js";
|
|
9
|
+
import { WithTracingOptions, withTracing } from "./with-tracing.js";
|
|
10
|
+
export { CANONICAL_MIDDLEWARE_ORDER, type CostAccumulator, type CostTrackingTotals, MIDDLEWARE_KIND, type ProductionStartupHookOptions, type PromptRedactionPolicy, type PromptRedactionScanScope, type PromptRedactionViolation, type WithCostLimitOptions, type WithCostTrackingOptions, type WithFallbackOptions, type WithRateLimitOptions, type WithRetryOptions, type WithTracingOptions, assertProductionMiddleware, composeProviderMiddleware, createCostAccumulator, defineProviderMiddleware, getMiddlewareKind, providerHasMiddleware, withCostLimit, withCostTracking, withFallback, withRateLimit, withRedaction, withRetry, withTracing };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { CANONICAL_MIDDLEWARE_ORDER, MIDDLEWARE_KIND, composeProviderMiddleware, defineProviderMiddleware, getMiddlewareKind, providerHasMiddleware } from "./compose.js";
|
|
2
|
+
import { assertProductionMiddleware } from "./production-hook.js";
|
|
3
|
+
import { withCostLimit } from "./with-cost-limit.js";
|
|
4
|
+
import { createCostAccumulator, withCostTracking } from "./with-cost-tracking.js";
|
|
5
|
+
import { withFallback } from "./with-fallback.js";
|
|
6
|
+
import { withRateLimit } from "./with-rate-limit.js";
|
|
7
|
+
import { withRedaction } from "./with-redaction.js";
|
|
8
|
+
import { withRetry } from "./with-retry.js";
|
|
9
|
+
import { withTracing } from "./with-tracing.js";
|
|
10
|
+
|
|
11
|
+
export { CANONICAL_MIDDLEWARE_ORDER, MIDDLEWARE_KIND, assertProductionMiddleware, composeProviderMiddleware, createCostAccumulator, defineProviderMiddleware, getMiddlewareKind, providerHasMiddleware, withCostLimit, withCostTracking, withFallback, withRateLimit, withRedaction, withRetry, withTracing };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Provider } from "@graphorin/core";
|
|
2
|
+
|
|
3
|
+
//#region src/middleware/production-hook.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Options for {@link assertProductionMiddleware}.
|
|
7
|
+
*
|
|
8
|
+
* @stable
|
|
9
|
+
*/
|
|
10
|
+
interface ProductionStartupHookOptions {
|
|
11
|
+
/** Middleware kinds that must be present. Defaults to `['withRedaction']`. */
|
|
12
|
+
readonly requiredKinds?: ReadonlyArray<string>;
|
|
13
|
+
/** Force the check regardless of `NODE_ENV`. */
|
|
14
|
+
readonly force?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Throw {@link MissingProductionMiddlewareError} if a required
|
|
18
|
+
* middleware is missing from the chain rooted at `provider`. The
|
|
19
|
+
* check runs only when `NODE_ENV === 'production'` unless `force` is
|
|
20
|
+
* `true`.
|
|
21
|
+
*
|
|
22
|
+
* @stable
|
|
23
|
+
*/
|
|
24
|
+
declare function assertProductionMiddleware(provider: Provider, options?: ProductionStartupHookOptions): void;
|
|
25
|
+
//#endregion
|
|
26
|
+
export { ProductionStartupHookOptions, assertProductionMiddleware };
|
|
27
|
+
//# sourceMappingURL=production-hook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"production-hook.d.ts","names":[],"sources":["../../src/middleware/production-hook.ts"],"sourcesContent":[],"mappings":";;;;;;;;;UAmBiB,4BAAA;;2BAEU;;;;;;;;;;;;iBAaX,0BAAA,WACJ,oBACD"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { MissingProductionMiddlewareError } from "../errors/errors.js";
|
|
2
|
+
import { providerHasMiddleware } from "./compose.js";
|
|
3
|
+
|
|
4
|
+
//#region src/middleware/production-hook.ts
|
|
5
|
+
/**
|
|
6
|
+
* Throw {@link MissingProductionMiddlewareError} if a required
|
|
7
|
+
* middleware is missing from the chain rooted at `provider`. The
|
|
8
|
+
* check runs only when `NODE_ENV === 'production'` unless `force` is
|
|
9
|
+
* `true`.
|
|
10
|
+
*
|
|
11
|
+
* @stable
|
|
12
|
+
*/
|
|
13
|
+
function assertProductionMiddleware(provider, options = {}) {
|
|
14
|
+
if (!(process.env.NODE_ENV === "production") && options.force !== true) return;
|
|
15
|
+
const required = options.requiredKinds ?? ["withRedaction"];
|
|
16
|
+
for (const kind of required) if (!providerHasMiddleware(provider, kind)) throw new MissingProductionMiddlewareError(kind);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
//#endregion
|
|
20
|
+
export { assertProductionMiddleware };
|
|
21
|
+
//# sourceMappingURL=production-hook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"production-hook.js","names":[],"sources":["../../src/middleware/production-hook.ts"],"sourcesContent":["/**\n * Production startup hook — fails fast when a security-critical\n * middleware is missing from the composed chain. The default\n * configuration enforces `withRedaction`; consumers can extend the\n * `requiredKinds` list to lock additional middlewares.\n *\n * @packageDocumentation\n */\n\nimport type { Provider } from '@graphorin/core';\n\nimport { MissingProductionMiddlewareError } from '../errors/errors.js';\nimport { providerHasMiddleware } from './compose.js';\n\n/**\n * Options for {@link assertProductionMiddleware}.\n *\n * @stable\n */\nexport interface ProductionStartupHookOptions {\n /** Middleware kinds that must be present. Defaults to `['withRedaction']`. */\n readonly requiredKinds?: ReadonlyArray<string>;\n /** Force the check regardless of `NODE_ENV`. */\n readonly force?: boolean;\n}\n\n/**\n * Throw {@link MissingProductionMiddlewareError} if a required\n * middleware is missing from the chain rooted at `provider`. The\n * check runs only when `NODE_ENV === 'production'` unless `force` is\n * `true`.\n *\n * @stable\n */\nexport function assertProductionMiddleware(\n provider: Provider,\n options: ProductionStartupHookOptions = {},\n): void {\n const isProd = process.env.NODE_ENV === 'production';\n if (!isProd && options.force !== true) return;\n const required = options.requiredKinds ?? ['withRedaction'];\n for (const kind of required) {\n if (!providerHasMiddleware(provider, kind)) {\n throw new MissingProductionMiddlewareError(kind);\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;AAkCA,SAAgB,2BACd,UACA,UAAwC,EAAE,EACpC;AAEN,KAAI,EADW,QAAQ,IAAI,aAAa,iBACzB,QAAQ,UAAU,KAAM;CACvC,MAAM,WAAW,QAAQ,iBAAiB,CAAC,gBAAgB;AAC3D,MAAK,MAAM,QAAQ,SACjB,KAAI,CAAC,sBAAsB,UAAU,KAAK,CACxC,OAAM,IAAI,iCAAiC,KAAK"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as _graphorin_core1 from "@graphorin/core";
|
|
2
|
+
|
|
3
|
+
//#region src/middleware/with-cost-limit.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `withCostLimit` — enforce per-session / per-run / per-agent /
|
|
7
|
+
* per-hour cost ceilings. Couples with {@link withCostTracking} for
|
|
8
|
+
* the underlying accumulator. The middleware is positioned between
|
|
9
|
+
* `withRateLimit` and `withCostTracking` per the canonical order so a
|
|
10
|
+
* budget breach trips before the request hits the underlying
|
|
11
|
+
* provider.
|
|
12
|
+
*
|
|
13
|
+
* @packageDocumentation
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Options for {@link withCostLimit}.
|
|
17
|
+
*
|
|
18
|
+
* @stable
|
|
19
|
+
*/
|
|
20
|
+
interface WithCostLimitOptions {
|
|
21
|
+
/** Maximum cumulative USD cost per session. */
|
|
22
|
+
readonly maxPerSession?: number;
|
|
23
|
+
/** Maximum cumulative USD cost per run. */
|
|
24
|
+
readonly maxPerRun?: number;
|
|
25
|
+
/** Maximum cumulative USD cost per hour. */
|
|
26
|
+
readonly maxPerHour?: number;
|
|
27
|
+
/** What to do on breach. Default `'throw'`. */
|
|
28
|
+
readonly onExceed?: 'throw' | 'warn';
|
|
29
|
+
/**
|
|
30
|
+
* Resolver returning the current observed cost for the relevant
|
|
31
|
+
* scope. The resolver lets consumers wire any accumulator (the
|
|
32
|
+
* shipped `@graphorin/observability/cost.CostTracker` works out of
|
|
33
|
+
* the box). When unset, the middleware is a no-op (a placeholder
|
|
34
|
+
* for tooling that wires the accumulator later).
|
|
35
|
+
*/
|
|
36
|
+
readonly resolveObservedCost?: (scope: 'session' | 'run' | 'hour', metadata: Readonly<Record<string, unknown>> | undefined) => number;
|
|
37
|
+
/** Optional sink for `'warn'` mode. Defaults to `console.warn`. */
|
|
38
|
+
readonly logger?: (message: string, meta?: object) => void;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* @stable
|
|
42
|
+
*/
|
|
43
|
+
declare const withCostLimit: (opts: WithCostLimitOptions) => _graphorin_core1.ProviderMiddleware;
|
|
44
|
+
//#endregion
|
|
45
|
+
export { WithCostLimitOptions, withCostLimit };
|
|
46
|
+
//# sourceMappingURL=with-cost-limit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"with-cost-limit.d.ts","names":[],"sources":["../../src/middleware/with-cost-limit.ts"],"sourcesContent":[],"mappings":";;;;;;;AAqBA;AA2BA;;;;;;;;;;;UA3BiB,oBAAA;;;;;;;;;;;;;;;;+EAkBH,SAAS;;;;;;;cASV,sBAAa,yBAAA,gBAAA,CAAA"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { CostBudgetExceededError } from "../errors/errors.js";
|
|
2
|
+
import { defineProviderMiddleware } from "./compose.js";
|
|
3
|
+
|
|
4
|
+
//#region src/middleware/with-cost-limit.ts
|
|
5
|
+
/**
|
|
6
|
+
* @stable
|
|
7
|
+
*/
|
|
8
|
+
const withCostLimit = defineProviderMiddleware({
|
|
9
|
+
kind: "withCostLimit",
|
|
10
|
+
factory: (opts) => {
|
|
11
|
+
const onExceed = opts.onExceed ?? "throw";
|
|
12
|
+
const resolver = opts.resolveObservedCost;
|
|
13
|
+
const logger = opts.logger ?? defaultLogger;
|
|
14
|
+
if ((opts.maxPerSession !== void 0 || opts.maxPerRun !== void 0 || opts.maxPerHour !== void 0) && resolver === void 0) logger("[graphorin/provider] withCostLimit: a cost ceiling is configured but no `resolveObservedCost` was supplied, so the limit is UNENFORCED (no-op). Wire a resolver (e.g. from createCostAccumulator / @graphorin/observability).");
|
|
15
|
+
return (next) => ({
|
|
16
|
+
name: next.name,
|
|
17
|
+
modelId: next.modelId,
|
|
18
|
+
capabilities: next.capabilities,
|
|
19
|
+
...next.acceptsSensitivity !== void 0 ? { acceptsSensitivity: next.acceptsSensitivity } : {},
|
|
20
|
+
stream(req) {
|
|
21
|
+
if (resolver !== void 0) check(asRecord(req.metadata), resolver, opts, onExceed, logger);
|
|
22
|
+
return next.stream(req);
|
|
23
|
+
},
|
|
24
|
+
async generate(req) {
|
|
25
|
+
if (resolver !== void 0) check(asRecord(req.metadata), resolver, opts, onExceed, logger);
|
|
26
|
+
return next.generate(req);
|
|
27
|
+
},
|
|
28
|
+
...next.countTokens ? { countTokens: next.countTokens.bind(next) } : {}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
function check(metadata, resolver, opts, onExceed, logger) {
|
|
33
|
+
const checks = [
|
|
34
|
+
["session", opts.maxPerSession],
|
|
35
|
+
["run", opts.maxPerRun],
|
|
36
|
+
["hour", opts.maxPerHour]
|
|
37
|
+
];
|
|
38
|
+
for (const [scope, limit] of checks) {
|
|
39
|
+
if (limit === void 0) continue;
|
|
40
|
+
const observed = resolver(scope, metadata);
|
|
41
|
+
if (observed > limit) {
|
|
42
|
+
if (onExceed === "throw") throw new CostBudgetExceededError({
|
|
43
|
+
scope,
|
|
44
|
+
limit,
|
|
45
|
+
observed
|
|
46
|
+
});
|
|
47
|
+
logger(`[graphorin/provider] withCostLimit: ${scope} budget breach (${observed.toFixed(4)} > ${limit.toFixed(4)} USD).`, {
|
|
48
|
+
scope,
|
|
49
|
+
limit,
|
|
50
|
+
observed
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function defaultLogger(message, meta) {
|
|
56
|
+
if (meta !== void 0) console.warn(message, meta);
|
|
57
|
+
else console.warn(message);
|
|
58
|
+
}
|
|
59
|
+
function asRecord(value) {
|
|
60
|
+
if (value === void 0 || value === null) return void 0;
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
//#endregion
|
|
65
|
+
export { withCostLimit };
|
|
66
|
+
//# sourceMappingURL=with-cost-limit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"with-cost-limit.js","names":["checks: Array<['session' | 'run' | 'hour', number | undefined]>"],"sources":["../../src/middleware/with-cost-limit.ts"],"sourcesContent":["/**\n * `withCostLimit` — enforce per-session / per-run / per-agent /\n * per-hour cost ceilings. Couples with {@link withCostTracking} for\n * the underlying accumulator. The middleware is positioned between\n * `withRateLimit` and `withCostTracking` per the canonical order so a\n * budget breach trips before the request hits the underlying\n * provider.\n *\n * @packageDocumentation\n */\n\nimport type { Provider } from '@graphorin/core';\n\nimport { CostBudgetExceededError } from '../errors/errors.js';\nimport { defineProviderMiddleware } from './compose.js';\n\n/**\n * Options for {@link withCostLimit}.\n *\n * @stable\n */\nexport interface WithCostLimitOptions {\n /** Maximum cumulative USD cost per session. */\n readonly maxPerSession?: number;\n /** Maximum cumulative USD cost per run. */\n readonly maxPerRun?: number;\n /** Maximum cumulative USD cost per hour. */\n readonly maxPerHour?: number;\n /** What to do on breach. Default `'throw'`. */\n readonly onExceed?: 'throw' | 'warn';\n /**\n * Resolver returning the current observed cost for the relevant\n * scope. The resolver lets consumers wire any accumulator (the\n * shipped `@graphorin/observability/cost.CostTracker` works out of\n * the box). When unset, the middleware is a no-op (a placeholder\n * for tooling that wires the accumulator later).\n */\n readonly resolveObservedCost?: (\n scope: 'session' | 'run' | 'hour',\n metadata: Readonly<Record<string, unknown>> | undefined,\n ) => number;\n /** Optional sink for `'warn'` mode. Defaults to `console.warn`. */\n readonly logger?: (message: string, meta?: object) => void;\n}\n\n/**\n * @stable\n */\nexport const withCostLimit = defineProviderMiddleware<WithCostLimitOptions>({\n kind: 'withCostLimit',\n factory: (opts: WithCostLimitOptions) => {\n const onExceed = opts.onExceed ?? 'throw';\n const resolver = opts.resolveObservedCost;\n const logger = opts.logger ?? defaultLogger;\n // PS-8: a ceiling without a resolver is a silent no-op — the limits look\n // enforced but never trip. Warn loudly so the gap is visible. (No ceiling\n // + no resolver is the documented inert placeholder, so it stays quiet.)\n const hasCeiling =\n opts.maxPerSession !== undefined ||\n opts.maxPerRun !== undefined ||\n opts.maxPerHour !== undefined;\n if (hasCeiling && resolver === undefined) {\n logger(\n '[graphorin/provider] withCostLimit: a cost ceiling is configured but no ' +\n '`resolveObservedCost` was supplied, so the limit is UNENFORCED (no-op). ' +\n 'Wire a resolver (e.g. from createCostAccumulator / @graphorin/observability).',\n );\n }\n return (next: Provider): Provider => ({\n name: next.name,\n modelId: next.modelId,\n capabilities: next.capabilities,\n ...(next.acceptsSensitivity !== undefined\n ? { acceptsSensitivity: next.acceptsSensitivity }\n : {}),\n stream(req) {\n if (resolver !== undefined) {\n check(asRecord(req.metadata), resolver, opts, onExceed, logger);\n }\n return next.stream(req);\n },\n async generate(req) {\n if (resolver !== undefined) {\n check(asRecord(req.metadata), resolver, opts, onExceed, logger);\n }\n return next.generate(req);\n },\n ...(next.countTokens ? { countTokens: next.countTokens.bind(next) } : {}),\n });\n },\n});\n\nfunction check(\n metadata: Readonly<Record<string, unknown>> | undefined,\n resolver: (\n scope: 'session' | 'run' | 'hour',\n metadata?: Readonly<Record<string, unknown>>,\n ) => number,\n opts: WithCostLimitOptions,\n onExceed: 'throw' | 'warn',\n logger: (message: string, meta?: object) => void,\n): void {\n const checks: Array<['session' | 'run' | 'hour', number | undefined]> = [\n ['session', opts.maxPerSession],\n ['run', opts.maxPerRun],\n ['hour', opts.maxPerHour],\n ];\n for (const [scope, limit] of checks) {\n if (limit === undefined) continue;\n const observed = resolver(scope, metadata);\n if (observed > limit) {\n if (onExceed === 'throw') {\n throw new CostBudgetExceededError({ scope, limit, observed });\n }\n logger(\n `[graphorin/provider] withCostLimit: ${scope} budget breach (${observed.toFixed(4)} > ${limit.toFixed(4)} USD).`,\n { scope, limit, observed },\n );\n }\n }\n}\n\nfunction defaultLogger(message: string, meta?: object): void {\n if (meta !== undefined) console.warn(message, meta);\n else console.warn(message);\n}\n\nfunction asRecord(value: unknown): Readonly<Record<string, unknown>> | undefined {\n if (value === undefined || value === null) return undefined;\n return value as Readonly<Record<string, unknown>>;\n}\n"],"mappings":";;;;;;;AAgDA,MAAa,gBAAgB,yBAA+C;CAC1E,MAAM;CACN,UAAU,SAA+B;EACvC,MAAM,WAAW,KAAK,YAAY;EAClC,MAAM,WAAW,KAAK;EACtB,MAAM,SAAS,KAAK,UAAU;AAQ9B,OAHE,KAAK,kBAAkB,UACvB,KAAK,cAAc,UACnB,KAAK,eAAe,WACJ,aAAa,OAC7B,QACE,gOAGD;AAEH,UAAQ,UAA8B;GACpC,MAAM,KAAK;GACX,SAAS,KAAK;GACd,cAAc,KAAK;GACnB,GAAI,KAAK,uBAAuB,SAC5B,EAAE,oBAAoB,KAAK,oBAAoB,GAC/C,EAAE;GACN,OAAO,KAAK;AACV,QAAI,aAAa,OACf,OAAM,SAAS,IAAI,SAAS,EAAE,UAAU,MAAM,UAAU,OAAO;AAEjE,WAAO,KAAK,OAAO,IAAI;;GAEzB,MAAM,SAAS,KAAK;AAClB,QAAI,aAAa,OACf,OAAM,SAAS,IAAI,SAAS,EAAE,UAAU,MAAM,UAAU,OAAO;AAEjE,WAAO,KAAK,SAAS,IAAI;;GAE3B,GAAI,KAAK,cAAc,EAAE,aAAa,KAAK,YAAY,KAAK,KAAK,EAAE,GAAG,EAAE;GACzE;;CAEJ,CAAC;AAEF,SAAS,MACP,UACA,UAIA,MACA,UACA,QACM;CACN,MAAMA,SAAkE;EACtE,CAAC,WAAW,KAAK,cAAc;EAC/B,CAAC,OAAO,KAAK,UAAU;EACvB,CAAC,QAAQ,KAAK,WAAW;EAC1B;AACD,MAAK,MAAM,CAAC,OAAO,UAAU,QAAQ;AACnC,MAAI,UAAU,OAAW;EACzB,MAAM,WAAW,SAAS,OAAO,SAAS;AAC1C,MAAI,WAAW,OAAO;AACpB,OAAI,aAAa,QACf,OAAM,IAAI,wBAAwB;IAAE;IAAO;IAAO;IAAU,CAAC;AAE/D,UACE,uCAAuC,MAAM,kBAAkB,SAAS,QAAQ,EAAE,CAAC,KAAK,MAAM,QAAQ,EAAE,CAAC,SACzG;IAAE;IAAO;IAAO;IAAU,CAC3B;;;;AAKP,SAAS,cAAc,SAAiB,MAAqB;AAC3D,KAAI,SAAS,OAAW,SAAQ,KAAK,SAAS,KAAK;KAC9C,SAAQ,KAAK,QAAQ;;AAG5B,SAAS,SAAS,OAA+D;AAC/E,KAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,QAAO"}
|