@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.
Files changed (148) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/LICENSE +21 -0
  3. package/README.md +105 -0
  4. package/dist/adapters/index.d.ts +4 -0
  5. package/dist/adapters/index.js +6 -0
  6. package/dist/adapters/llamacpp-server.d.ts +69 -0
  7. package/dist/adapters/llamacpp-server.d.ts.map +1 -0
  8. package/dist/adapters/llamacpp-server.js +51 -0
  9. package/dist/adapters/llamacpp-server.js.map +1 -0
  10. package/dist/adapters/ollama.d.ts +43 -0
  11. package/dist/adapters/ollama.d.ts.map +1 -0
  12. package/dist/adapters/ollama.js +246 -0
  13. package/dist/adapters/ollama.js.map +1 -0
  14. package/dist/adapters/openai-compatible.d.ts +48 -0
  15. package/dist/adapters/openai-compatible.d.ts.map +1 -0
  16. package/dist/adapters/openai-compatible.js +28 -0
  17. package/dist/adapters/openai-compatible.js.map +1 -0
  18. package/dist/adapters/vercel.d.ts +135 -0
  19. package/dist/adapters/vercel.d.ts.map +1 -0
  20. package/dist/adapters/vercel.js +306 -0
  21. package/dist/adapters/vercel.js.map +1 -0
  22. package/dist/counters/anthropic.d.ts +36 -0
  23. package/dist/counters/anthropic.d.ts.map +1 -0
  24. package/dist/counters/anthropic.js +63 -0
  25. package/dist/counters/anthropic.js.map +1 -0
  26. package/dist/counters/bedrock.d.ts +27 -0
  27. package/dist/counters/bedrock.d.ts.map +1 -0
  28. package/dist/counters/bedrock.js +29 -0
  29. package/dist/counters/bedrock.js.map +1 -0
  30. package/dist/counters/dispatcher.d.ts +34 -0
  31. package/dist/counters/dispatcher.d.ts.map +1 -0
  32. package/dist/counters/dispatcher.js +78 -0
  33. package/dist/counters/dispatcher.js.map +1 -0
  34. package/dist/counters/global.d.ts +26 -0
  35. package/dist/counters/global.d.ts.map +1 -0
  36. package/dist/counters/global.js +31 -0
  37. package/dist/counters/global.js.map +1 -0
  38. package/dist/counters/google.d.ts +27 -0
  39. package/dist/counters/google.d.ts.map +1 -0
  40. package/dist/counters/google.js +29 -0
  41. package/dist/counters/google.js.map +1 -0
  42. package/dist/counters/heuristic.d.ts +37 -0
  43. package/dist/counters/heuristic.d.ts.map +1 -0
  44. package/dist/counters/heuristic.js +64 -0
  45. package/dist/counters/heuristic.js.map +1 -0
  46. package/dist/counters/index.d.ts +9 -0
  47. package/dist/counters/index.js +10 -0
  48. package/dist/counters/js-tiktoken.d.ts +63 -0
  49. package/dist/counters/js-tiktoken.d.ts.map +1 -0
  50. package/dist/counters/js-tiktoken.js +73 -0
  51. package/dist/counters/js-tiktoken.js.map +1 -0
  52. package/dist/counters/serialize.d.ts +37 -0
  53. package/dist/counters/serialize.d.ts.map +1 -0
  54. package/dist/counters/serialize.js +57 -0
  55. package/dist/counters/serialize.js.map +1 -0
  56. package/dist/errors/errors.d.ts +167 -0
  57. package/dist/errors/errors.d.ts.map +1 -0
  58. package/dist/errors/errors.js +182 -0
  59. package/dist/errors/errors.js.map +1 -0
  60. package/dist/errors/index.d.ts +2 -0
  61. package/dist/errors/index.js +3 -0
  62. package/dist/index.d.ts +69 -0
  63. package/dist/index.d.ts.map +1 -0
  64. package/dist/index.js +70 -0
  65. package/dist/index.js.map +1 -0
  66. package/dist/internal/abort.js +34 -0
  67. package/dist/internal/abort.js.map +1 -0
  68. package/dist/internal/http.js +163 -0
  69. package/dist/internal/http.js.map +1 -0
  70. package/dist/internal/openai-shaped.js +308 -0
  71. package/dist/internal/openai-shaped.js.map +1 -0
  72. package/dist/internal/sse.js +104 -0
  73. package/dist/internal/sse.js.map +1 -0
  74. package/dist/internal/url-utils.js +24 -0
  75. package/dist/internal/url-utils.js.map +1 -0
  76. package/dist/middleware/compose.d.ts +85 -0
  77. package/dist/middleware/compose.d.ts.map +1 -0
  78. package/dist/middleware/compose.js +174 -0
  79. package/dist/middleware/compose.js.map +1 -0
  80. package/dist/middleware/index.d.ts +10 -0
  81. package/dist/middleware/index.js +11 -0
  82. package/dist/middleware/production-hook.d.ts +27 -0
  83. package/dist/middleware/production-hook.d.ts.map +1 -0
  84. package/dist/middleware/production-hook.js +21 -0
  85. package/dist/middleware/production-hook.js.map +1 -0
  86. package/dist/middleware/with-cost-limit.d.ts +46 -0
  87. package/dist/middleware/with-cost-limit.d.ts.map +1 -0
  88. package/dist/middleware/with-cost-limit.js +66 -0
  89. package/dist/middleware/with-cost-limit.js.map +1 -0
  90. package/dist/middleware/with-cost-tracking.d.ts +91 -0
  91. package/dist/middleware/with-cost-tracking.d.ts.map +1 -0
  92. package/dist/middleware/with-cost-tracking.js +95 -0
  93. package/dist/middleware/with-cost-tracking.js.map +1 -0
  94. package/dist/middleware/with-fallback.d.ts +25 -0
  95. package/dist/middleware/with-fallback.d.ts.map +1 -0
  96. package/dist/middleware/with-fallback.js +84 -0
  97. package/dist/middleware/with-fallback.js.map +1 -0
  98. package/dist/middleware/with-rate-limit.d.ts +36 -0
  99. package/dist/middleware/with-rate-limit.d.ts.map +1 -0
  100. package/dist/middleware/with-rate-limit.js +141 -0
  101. package/dist/middleware/with-rate-limit.js.map +1 -0
  102. package/dist/middleware/with-redaction.d.ts +60 -0
  103. package/dist/middleware/with-redaction.d.ts.map +1 -0
  104. package/dist/middleware/with-redaction.js +305 -0
  105. package/dist/middleware/with-redaction.js.map +1 -0
  106. package/dist/middleware/with-retry.d.ts +26 -0
  107. package/dist/middleware/with-retry.d.ts.map +1 -0
  108. package/dist/middleware/with-retry.js +157 -0
  109. package/dist/middleware/with-retry.js.map +1 -0
  110. package/dist/middleware/with-tracing.d.ts +21 -0
  111. package/dist/middleware/with-tracing.d.ts.map +1 -0
  112. package/dist/middleware/with-tracing.js +55 -0
  113. package/dist/middleware/with-tracing.js.map +1 -0
  114. package/dist/model-tier/classify.d.ts +34 -0
  115. package/dist/model-tier/classify.d.ts.map +1 -0
  116. package/dist/model-tier/classify.js +117 -0
  117. package/dist/model-tier/classify.js.map +1 -0
  118. package/dist/model-tier/index.d.ts +2 -0
  119. package/dist/model-tier/index.js +3 -0
  120. package/dist/provider.d.ts +51 -0
  121. package/dist/provider.d.ts.map +1 -0
  122. package/dist/provider.js +64 -0
  123. package/dist/provider.js.map +1 -0
  124. package/dist/reasoning/apply-policy.d.ts +22 -0
  125. package/dist/reasoning/apply-policy.d.ts.map +1 -0
  126. package/dist/reasoning/apply-policy.js +47 -0
  127. package/dist/reasoning/apply-policy.js.map +1 -0
  128. package/dist/reasoning/classify-contract.d.ts +41 -0
  129. package/dist/reasoning/classify-contract.d.ts.map +1 -0
  130. package/dist/reasoning/classify-contract.js +59 -0
  131. package/dist/reasoning/classify-contract.js.map +1 -0
  132. package/dist/reasoning/index.d.ts +4 -0
  133. package/dist/reasoning/index.js +5 -0
  134. package/dist/reasoning/retention.d.ts +39 -0
  135. package/dist/reasoning/retention.d.ts.map +1 -0
  136. package/dist/reasoning/retention.js +33 -0
  137. package/dist/reasoning/retention.js.map +1 -0
  138. package/dist/tool-examples.d.ts +14 -0
  139. package/dist/tool-examples.d.ts.map +1 -0
  140. package/dist/tool-examples.js +35 -0
  141. package/dist/tool-examples.js.map +1 -0
  142. package/dist/trust/classify-local-provider.d.ts +47 -0
  143. package/dist/trust/classify-local-provider.d.ts.map +1 -0
  144. package/dist/trust/classify-local-provider.js +164 -0
  145. package/dist/trust/classify-local-provider.js.map +1 -0
  146. package/dist/trust/index.d.ts +3 -0
  147. package/dist/trust/index.js +3 -0
  148. 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"}