@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,55 @@
1
+ import { defineProviderMiddleware } from "./compose.js";
2
+
3
+ //#region src/middleware/with-tracing.ts
4
+ /**
5
+ * @stable
6
+ */
7
+ const withTracing = defineProviderMiddleware({
8
+ kind: "withTracing",
9
+ factory: (opts) => {
10
+ return (next) => ({
11
+ name: next.name,
12
+ modelId: next.modelId,
13
+ capabilities: next.capabilities,
14
+ ...next.acceptsSensitivity !== void 0 ? { acceptsSensitivity: next.acceptsSensitivity } : {},
15
+ stream(req) {
16
+ if (opts.tracer === void 0) return next.stream(req);
17
+ return tracedStream(opts.tracer, next, req);
18
+ },
19
+ async generate(req) {
20
+ if (opts.tracer === void 0) return next.generate(req);
21
+ return opts.tracer.span({
22
+ type: "provider.generate",
23
+ attrs: {
24
+ "graphorin.provider.id": next.name,
25
+ "graphorin.provider.model": next.modelId
26
+ }
27
+ }, async () => next.generate(req));
28
+ },
29
+ ...next.countTokens ? { countTokens: next.countTokens.bind(next) } : {}
30
+ });
31
+ }
32
+ });
33
+ async function* tracedStream(tracer, next, req) {
34
+ const span = tracer.startSpan({
35
+ type: "provider.stream",
36
+ attrs: {
37
+ "graphorin.provider.id": next.name,
38
+ "graphorin.provider.model": next.modelId
39
+ }
40
+ });
41
+ try {
42
+ for await (const event of next.stream(req)) yield event;
43
+ span.setStatus("ok");
44
+ } catch (err) {
45
+ span.recordException(err);
46
+ span.setStatus("error", err.message);
47
+ throw err;
48
+ } finally {
49
+ span.end();
50
+ }
51
+ }
52
+
53
+ //#endregion
54
+ export { withTracing };
55
+ //# sourceMappingURL=with-tracing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"with-tracing.js","names":[],"sources":["../../src/middleware/with-tracing.ts"],"sourcesContent":["/**\n * `withTracing` — outermost middleware. Wraps every `provider.stream`\n * / `provider.generate` call in a Graphorin OTel-shaped span so the\n * span captures retry decisions, rate-limit waits, and the underlying\n * provider call as one logical unit.\n *\n * The middleware accepts any object that exposes the\n * {@link Tracer} contract from `@graphorin/core/contracts`. Tests pass\n * a `NOOP_TRACER`-shaped stub; production wires the real Graphorin\n * tracer from `@graphorin/observability`.\n *\n * @packageDocumentation\n */\n\nimport type { Provider, Tracer } from '@graphorin/core';\n\nimport { defineProviderMiddleware } from './compose.js';\n\n/**\n * Options for {@link withTracing}.\n *\n * @stable\n */\nexport interface WithTracingOptions {\n /** Tracer instance. Defaults to a no-op tracer if unset. */\n readonly tracer?: Tracer;\n}\n\n/**\n * @stable\n */\nexport const withTracing = defineProviderMiddleware<WithTracingOptions>({\n kind: 'withTracing',\n factory: (opts: WithTracingOptions) => {\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 (opts.tracer === undefined) return next.stream(req);\n return tracedStream(opts.tracer, next, req);\n },\n async generate(req) {\n if (opts.tracer === undefined) return next.generate(req);\n return opts.tracer.span(\n {\n type: 'provider.generate' as const,\n attrs: {\n 'graphorin.provider.id': next.name,\n 'graphorin.provider.model': next.modelId,\n },\n },\n async () => next.generate(req),\n );\n },\n ...(next.countTokens ? { countTokens: next.countTokens.bind(next) } : {}),\n });\n },\n});\n\nasync function* tracedStream(\n tracer: Tracer,\n next: Provider,\n req: import('@graphorin/core').ProviderRequest,\n): AsyncIterable<import('@graphorin/core').ProviderEvent> {\n const span = tracer.startSpan({\n type: 'provider.stream' as const,\n attrs: {\n 'graphorin.provider.id': next.name,\n 'graphorin.provider.model': next.modelId,\n },\n });\n try {\n for await (const event of next.stream(req)) {\n yield event;\n }\n span.setStatus('ok');\n } catch (err) {\n span.recordException(err);\n span.setStatus('error', (err as Error).message);\n throw err;\n } finally {\n // PS-7: a consumer `break`/early-return injects a generator `return` at the\n // `yield`, skipping both the success and catch paths. Ending the span in\n // `finally` guarantees it is closed exactly once on every exit — normal\n // completion, error, or early abort (which leaves the status unset).\n span.end();\n }\n}\n"],"mappings":";;;;;;AA+BA,MAAa,cAAc,yBAA6C;CACtE,MAAM;CACN,UAAU,SAA6B;AACrC,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,KAAK,WAAW,OAAW,QAAO,KAAK,OAAO,IAAI;AACtD,WAAO,aAAa,KAAK,QAAQ,MAAM,IAAI;;GAE7C,MAAM,SAAS,KAAK;AAClB,QAAI,KAAK,WAAW,OAAW,QAAO,KAAK,SAAS,IAAI;AACxD,WAAO,KAAK,OAAO,KACjB;KACE,MAAM;KACN,OAAO;MACL,yBAAyB,KAAK;MAC9B,4BAA4B,KAAK;MAClC;KACF,EACD,YAAY,KAAK,SAAS,IAAI,CAC/B;;GAEH,GAAI,KAAK,cAAc,EAAE,aAAa,KAAK,YAAY,KAAK,KAAK,EAAE,GAAG,EAAE;GACzE;;CAEJ,CAAC;AAEF,gBAAgB,aACd,QACA,MACA,KACwD;CACxD,MAAM,OAAO,OAAO,UAAU;EAC5B,MAAM;EACN,OAAO;GACL,yBAAyB,KAAK;GAC9B,4BAA4B,KAAK;GAClC;EACF,CAAC;AACF,KAAI;AACF,aAAW,MAAM,SAAS,KAAK,OAAO,IAAI,CACxC,OAAM;AAER,OAAK,UAAU,KAAK;UACb,KAAK;AACZ,OAAK,gBAAgB,IAAI;AACzB,OAAK,UAAU,SAAU,IAAc,QAAQ;AAC/C,QAAM;WACE;AAKR,OAAK,KAAK"}
@@ -0,0 +1,34 @@
1
+ import { ModelHint, ProviderLike } from "@graphorin/core";
2
+
3
+ //#region src/model-tier/classify.d.ts
4
+
5
+ /**
6
+ * Single entry in the classifier rule table.
7
+ *
8
+ * @stable
9
+ */
10
+ interface ClassifierRule {
11
+ readonly tier: ModelHint;
12
+ readonly pattern: RegExp;
13
+ /** Human-readable family label (`'anthropic-claude-haiku'`, …). */
14
+ readonly family: string;
15
+ }
16
+ /**
17
+ * The static rule table. Order matters — higher-specificity entries
18
+ * come first (e.g. `claude-haiku` before `claude-`). Tests assert
19
+ * that the table covers the canonical 2026 model families.
20
+ *
21
+ * @stable
22
+ */
23
+ declare const CLASSIFIER_RULES: readonly ClassifierRule[];
24
+ /**
25
+ * Classify a `Provider`'s `modelId` into one of `'fast' | 'balanced' |
26
+ * 'smart'`. Returns `undefined` when the model id matches none of the
27
+ * canonical 2026 mappings (Ollama / OpenAI-compatible / unknown).
28
+ *
29
+ * @stable
30
+ */
31
+ declare function classifyModelTier(provider: ProviderLike): ModelHint | undefined;
32
+ //#endregion
33
+ export { CLASSIFIER_RULES, ClassifierRule, classifyModelTier };
34
+ //# sourceMappingURL=classify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classify.d.ts","names":[],"sources":["../../src/model-tier/classify.ts"],"sourcesContent":[],"mappings":";;;;;;;;;UAwBiB,cAAA;iBACA;oBACG;;;;;;;;;;;cAYP,2BAA2B;;;;;;;;iBA4CxB,iBAAA,WAA4B,eAAe"}
@@ -0,0 +1,117 @@
1
+ import { InvalidProviderError } from "../errors/errors.js";
2
+
3
+ //#region src/model-tier/classify.ts
4
+ /**
5
+ * The static rule table. Order matters — higher-specificity entries
6
+ * come first (e.g. `claude-haiku` before `claude-`). Tests assert
7
+ * that the table covers the canonical 2026 model families.
8
+ *
9
+ * @stable
10
+ */
11
+ const CLASSIFIER_RULES = Object.freeze([
12
+ {
13
+ tier: "fast",
14
+ family: "anthropic-haiku",
15
+ pattern: /^claude(?:-[\d.]+)*-?haiku/
16
+ },
17
+ {
18
+ tier: "balanced",
19
+ family: "anthropic-sonnet",
20
+ pattern: /^claude(?:-[\d.]+)*-?sonnet/
21
+ },
22
+ {
23
+ tier: "smart",
24
+ family: "anthropic-opus",
25
+ pattern: /^claude(?:-[\d.]+)*-?opus/
26
+ },
27
+ {
28
+ tier: "smart",
29
+ family: "anthropic-fable",
30
+ pattern: /^claude.*fable/
31
+ },
32
+ {
33
+ tier: "smart",
34
+ family: "anthropic-mythos",
35
+ pattern: /^claude.*mythos/
36
+ },
37
+ {
38
+ tier: "fast",
39
+ family: "bedrock-claude-haiku",
40
+ pattern: /^anthropic\.claude(?:-[\d.]+)*-?haiku/
41
+ },
42
+ {
43
+ tier: "balanced",
44
+ family: "bedrock-claude-sonnet",
45
+ pattern: /^anthropic\.claude(?:-[\d.]+)*-?sonnet/
46
+ },
47
+ {
48
+ tier: "smart",
49
+ family: "bedrock-claude-opus",
50
+ pattern: /^anthropic\.claude(?:-[\d.]+)*-?opus/
51
+ },
52
+ {
53
+ tier: "fast",
54
+ family: "openai-mini",
55
+ pattern: /^gpt-(\d|\d+\.\d+)-?mini/
56
+ },
57
+ {
58
+ tier: "fast",
59
+ family: "openai-nano",
60
+ pattern: /^gpt-(\d|\d+\.\d+)-?nano/
61
+ },
62
+ {
63
+ tier: "balanced",
64
+ family: "openai-gpt",
65
+ pattern: /^gpt-(?!.*(?:mini|nano))/
66
+ },
67
+ {
68
+ tier: "smart",
69
+ family: "openai-reasoning",
70
+ pattern: /^o[1-9]\b/
71
+ },
72
+ {
73
+ tier: "smart",
74
+ family: "openai-reasoning-extended",
75
+ pattern: /^o[1-9][a-z-]/
76
+ },
77
+ {
78
+ tier: "fast",
79
+ family: "gemini-flash",
80
+ pattern: /^gemini.*flash/
81
+ },
82
+ {
83
+ tier: "balanced",
84
+ family: "gemini-pro",
85
+ pattern: /^gemini.*pro\b/
86
+ },
87
+ {
88
+ tier: "smart",
89
+ family: "gemini-ultra",
90
+ pattern: /^gemini.*ultra/
91
+ }
92
+ ]);
93
+ /**
94
+ * Classify a `Provider`'s `modelId` into one of `'fast' | 'balanced' |
95
+ * 'smart'`. Returns `undefined` when the model id matches none of the
96
+ * canonical 2026 mappings (Ollama / OpenAI-compatible / unknown).
97
+ *
98
+ * @stable
99
+ */
100
+ function classifyModelTier(provider) {
101
+ if (provider === null || typeof provider !== "object") throw new InvalidProviderError("classifyModelTier: argument must be a Provider-shaped object.");
102
+ const modelId = provider.modelId;
103
+ if (typeof modelId !== "string" || modelId.length === 0) throw new InvalidProviderError("classifyModelTier: provider.modelId must be a non-empty string.");
104
+ const normalised = stripFamilyPrefix(modelId.toLowerCase());
105
+ for (const rule of CLASSIFIER_RULES) if (rule.pattern.test(normalised)) return rule.tier;
106
+ }
107
+ function stripFamilyPrefix(model) {
108
+ const slash = model.indexOf("/");
109
+ if (slash !== -1) return model.slice(slash + 1);
110
+ const colon = model.indexOf(":");
111
+ if (colon !== -1 && !model.startsWith("http")) return model.slice(colon + 1);
112
+ return model;
113
+ }
114
+
115
+ //#endregion
116
+ export { CLASSIFIER_RULES, classifyModelTier };
117
+ //# sourceMappingURL=classify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classify.js","names":["CLASSIFIER_RULES: readonly ClassifierRule[]"],"sources":["../../src/model-tier/classify.ts"],"sourcesContent":["/**\n * Per-provider model-tier auto-classifier — returns\n * `'fast' | 'balanced' | 'smart' | undefined` for any model id. The\n * classifier is consumed by the agent runtime (Phase 12) to validate\n * operator-supplied tier mappings and to surface tier-not-mapped\n * recommendations.\n *\n * The dispatcher is a pure function; it reads from a small static\n * rule table keyed on regex patterns matched against the lowercased\n * model id. The table can be inspected via {@link CLASSIFIER_RULES}\n * for debugging and downstream tooling (CLI, dashboard, lint rules).\n *\n * @packageDocumentation\n */\n\nimport type { ModelHint, ProviderLike } from '@graphorin/core';\n\nimport { InvalidProviderError } from '../errors/errors.js';\n\n/**\n * Single entry in the classifier rule table.\n *\n * @stable\n */\nexport interface ClassifierRule {\n readonly tier: ModelHint;\n readonly pattern: RegExp;\n /** Human-readable family label (`'anthropic-claude-haiku'`, …). */\n readonly family: string;\n}\n\n/**\n * The static rule table. Order matters — higher-specificity entries\n * come first (e.g. `claude-haiku` before `claude-`). Tests assert\n * that the table covers the canonical 2026 model families.\n *\n * @stable\n */\nexport const CLASSIFIER_RULES: readonly ClassifierRule[] = Object.freeze([\n // Anthropic — direct. PS-20: the version segment between `claude` and the\n // family word can be multi-part (`claude-3-5-haiku`, `claude-3-7-sonnet`) or\n // absent (`claude-haiku-4-5`), so allow zero-or-more `-<digits/dots>` groups.\n { tier: 'fast', family: 'anthropic-haiku', pattern: /^claude(?:-[\\d.]+)*-?haiku/ },\n { tier: 'balanced', family: 'anthropic-sonnet', pattern: /^claude(?:-[\\d.]+)*-?sonnet/ },\n { tier: 'smart', family: 'anthropic-opus', pattern: /^claude(?:-[\\d.]+)*-?opus/ },\n { tier: 'smart', family: 'anthropic-fable', pattern: /^claude.*fable/ },\n { tier: 'smart', family: 'anthropic-mythos', pattern: /^claude.*mythos/ },\n // Anthropic — via Bedrock.\n {\n tier: 'fast',\n family: 'bedrock-claude-haiku',\n pattern: /^anthropic\\.claude(?:-[\\d.]+)*-?haiku/,\n },\n {\n tier: 'balanced',\n family: 'bedrock-claude-sonnet',\n pattern: /^anthropic\\.claude(?:-[\\d.]+)*-?sonnet/,\n },\n {\n tier: 'smart',\n family: 'bedrock-claude-opus',\n pattern: /^anthropic\\.claude(?:-[\\d.]+)*-?opus/,\n },\n // OpenAI.\n { tier: 'fast', family: 'openai-mini', pattern: /^gpt-(\\d|\\d+\\.\\d+)-?mini/ },\n { tier: 'fast', family: 'openai-nano', pattern: /^gpt-(\\d|\\d+\\.\\d+)-?nano/ },\n { tier: 'balanced', family: 'openai-gpt', pattern: /^gpt-(?!.*(?:mini|nano))/ },\n { tier: 'smart', family: 'openai-reasoning', pattern: /^o[1-9]\\b/ },\n { tier: 'smart', family: 'openai-reasoning-extended', pattern: /^o[1-9][a-z-]/ },\n // Google Gemini.\n { tier: 'fast', family: 'gemini-flash', pattern: /^gemini.*flash/ },\n { tier: 'balanced', family: 'gemini-pro', pattern: /^gemini.*pro\\b/ },\n { tier: 'smart', family: 'gemini-ultra', pattern: /^gemini.*ultra/ },\n]);\n\n/**\n * Classify a `Provider`'s `modelId` into one of `'fast' | 'balanced' |\n * 'smart'`. Returns `undefined` when the model id matches none of the\n * canonical 2026 mappings (Ollama / OpenAI-compatible / unknown).\n *\n * @stable\n */\nexport function classifyModelTier(provider: ProviderLike): ModelHint | undefined {\n if (provider === null || typeof provider !== 'object') {\n throw new InvalidProviderError('classifyModelTier: argument must be a Provider-shaped object.');\n }\n const modelId = provider.modelId;\n if (typeof modelId !== 'string' || modelId.length === 0) {\n throw new InvalidProviderError(\n 'classifyModelTier: provider.modelId must be a non-empty string.',\n );\n }\n const normalised = stripFamilyPrefix(modelId.toLowerCase());\n for (const rule of CLASSIFIER_RULES) {\n if (rule.pattern.test(normalised)) return rule.tier;\n }\n return undefined;\n}\n\nfunction stripFamilyPrefix(model: string): string {\n // Common prefixes used by adapters: `anthropic/...`, `openai/...`,\n // `google/...`, `provider:model`, etc. Strip them so the rule\n // patterns can stay anchored at `^`.\n const slash = model.indexOf('/');\n if (slash !== -1) return model.slice(slash + 1);\n const colon = model.indexOf(':');\n if (colon !== -1 && !model.startsWith('http')) {\n // Skip URL-like values (`http://localhost:8080`) — `:` there is a\n // port separator, not a provider/model split.\n return model.slice(colon + 1);\n }\n return model;\n}\n"],"mappings":";;;;;;;;;;AAsCA,MAAaA,mBAA8C,OAAO,OAAO;CAIvE;EAAE,MAAM;EAAQ,QAAQ;EAAmB,SAAS;EAA8B;CAClF;EAAE,MAAM;EAAY,QAAQ;EAAoB,SAAS;EAA+B;CACxF;EAAE,MAAM;EAAS,QAAQ;EAAkB,SAAS;EAA6B;CACjF;EAAE,MAAM;EAAS,QAAQ;EAAmB,SAAS;EAAkB;CACvE;EAAE,MAAM;EAAS,QAAQ;EAAoB,SAAS;EAAmB;CAEzE;EACE,MAAM;EACN,QAAQ;EACR,SAAS;EACV;CACD;EACE,MAAM;EACN,QAAQ;EACR,SAAS;EACV;CACD;EACE,MAAM;EACN,QAAQ;EACR,SAAS;EACV;CAED;EAAE,MAAM;EAAQ,QAAQ;EAAe,SAAS;EAA4B;CAC5E;EAAE,MAAM;EAAQ,QAAQ;EAAe,SAAS;EAA4B;CAC5E;EAAE,MAAM;EAAY,QAAQ;EAAc,SAAS;EAA4B;CAC/E;EAAE,MAAM;EAAS,QAAQ;EAAoB,SAAS;EAAa;CACnE;EAAE,MAAM;EAAS,QAAQ;EAA6B,SAAS;EAAiB;CAEhF;EAAE,MAAM;EAAQ,QAAQ;EAAgB,SAAS;EAAkB;CACnE;EAAE,MAAM;EAAY,QAAQ;EAAc,SAAS;EAAkB;CACrE;EAAE,MAAM;EAAS,QAAQ;EAAgB,SAAS;EAAkB;CACrE,CAAC;;;;;;;;AASF,SAAgB,kBAAkB,UAA+C;AAC/E,KAAI,aAAa,QAAQ,OAAO,aAAa,SAC3C,OAAM,IAAI,qBAAqB,gEAAgE;CAEjG,MAAM,UAAU,SAAS;AACzB,KAAI,OAAO,YAAY,YAAY,QAAQ,WAAW,EACpD,OAAM,IAAI,qBACR,kEACD;CAEH,MAAM,aAAa,kBAAkB,QAAQ,aAAa,CAAC;AAC3D,MAAK,MAAM,QAAQ,iBACjB,KAAI,KAAK,QAAQ,KAAK,WAAW,CAAE,QAAO,KAAK;;AAKnD,SAAS,kBAAkB,OAAuB;CAIhD,MAAM,QAAQ,MAAM,QAAQ,IAAI;AAChC,KAAI,UAAU,GAAI,QAAO,MAAM,MAAM,QAAQ,EAAE;CAC/C,MAAM,QAAQ,MAAM,QAAQ,IAAI;AAChC,KAAI,UAAU,MAAM,CAAC,MAAM,WAAW,OAAO,CAG3C,QAAO,MAAM,MAAM,QAAQ,EAAE;AAE/B,QAAO"}
@@ -0,0 +1,2 @@
1
+ import { CLASSIFIER_RULES, ClassifierRule, classifyModelTier } from "./classify.js";
2
+ export { CLASSIFIER_RULES, type ClassifierRule, classifyModelTier };
@@ -0,0 +1,3 @@
1
+ import { CLASSIFIER_RULES, classifyModelTier } from "./classify.js";
2
+
3
+ export { CLASSIFIER_RULES, classifyModelTier };
@@ -0,0 +1,51 @@
1
+ import { Provider, ProviderCapabilities, ReasoningRetention, Sensitivity } from "@graphorin/core";
2
+
3
+ //#region src/provider.d.ts
4
+
5
+ /**
6
+ * Options accepted by {@link createProvider}.
7
+ *
8
+ * @stable
9
+ */
10
+ interface CreateProviderOptions {
11
+ /**
12
+ * Sensitivity tiers this provider is allowed to receive. When
13
+ * unset, the value is forwarded from the wrapped adapter (which is
14
+ * itself populated from the trust class for `baseUrl`-driven
15
+ * adapters).
16
+ */
17
+ readonly acceptsSensitivity?: ReadonlyArray<Sensitivity>;
18
+ /**
19
+ * Per-request override of the reasoning-retention default. The
20
+ * adapter's `capabilities.reasoningContract` decides the auto-
21
+ * detected default; this option pins a different value for every
22
+ * request the wrapper sees.
23
+ */
24
+ readonly reasoningRetention?: ReasoningRetention;
25
+ /**
26
+ * Optional capability override. Useful for narrowing what a
27
+ * downstream tool advertises (e.g. setting `multimodal: false`
28
+ * when the consumer's prompt cache is text-only).
29
+ */
30
+ readonly capabilities?: Partial<ProviderCapabilities>;
31
+ }
32
+ /**
33
+ * Wrap an adapter in the canonical `Provider` shape. Adapters returned
34
+ * by the bundled factories already implement `Provider`; passing them
35
+ * through `createProvider(...)` is the recommended entry point because
36
+ * it keeps the construction site documented and gives downstream
37
+ * middleware a single attachment surface.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * const provider = createProvider(vercelAdapter(model), {
42
+ * acceptsSensitivity: ['public', 'internal'],
43
+ * });
44
+ * ```
45
+ *
46
+ * @stable
47
+ */
48
+ declare function createProvider(adapter: Provider, options?: CreateProviderOptions): Provider;
49
+ //#endregion
50
+ export { CreateProviderOptions, createProvider };
51
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","names":[],"sources":["../src/provider.ts"],"sourcesContent":[],"mappings":";;;;;;;;;UAkCiB,qBAAA;;;;;;;gCAOe,cAAc;;;;;;;gCAOd;;;;;;0BAMN,QAAQ;;;;;;;;;;;;;;;;;;iBAmBlB,cAAA,UAAwB,oBAAmB,wBAA6B"}
@@ -0,0 +1,64 @@
1
+ import { resolveReasoningRetention } from "./reasoning/retention.js";
2
+ import { foldToolExamples } from "./tool-examples.js";
3
+
4
+ //#region src/provider.ts
5
+ /**
6
+ * Wrap an adapter in the canonical `Provider` shape. Adapters returned
7
+ * by the bundled factories already implement `Provider`; passing them
8
+ * through `createProvider(...)` is the recommended entry point because
9
+ * it keeps the construction site documented and gives downstream
10
+ * middleware a single attachment surface.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const provider = createProvider(vercelAdapter(model), {
15
+ * acceptsSensitivity: ['public', 'internal'],
16
+ * });
17
+ * ```
18
+ *
19
+ * @stable
20
+ */
21
+ function createProvider(adapter, options = {}) {
22
+ const capabilities = {
23
+ ...adapter.capabilities,
24
+ ...options.capabilities
25
+ };
26
+ const acceptsSensitivity = options.acceptsSensitivity ?? adapter.acceptsSensitivity ?? void 0;
27
+ return {
28
+ name: adapter.name,
29
+ modelId: adapter.modelId,
30
+ capabilities,
31
+ ...acceptsSensitivity ? { acceptsSensitivity } : {},
32
+ stream(req) {
33
+ return adapter.stream(applyDefaults(req, options, capabilities));
34
+ },
35
+ generate(req) {
36
+ return adapter.generate(applyDefaults(req, options, capabilities));
37
+ },
38
+ ...adapter.countTokens ? { countTokens: adapter.countTokens.bind(adapter) } : {}
39
+ };
40
+ }
41
+ function applyDefaults(req, options, capabilities) {
42
+ let next = req;
43
+ if (next.tools !== void 0) {
44
+ const foldedTools = foldToolExamples(next.tools);
45
+ if (foldedTools !== next.tools) next = {
46
+ ...next,
47
+ tools: foldedTools
48
+ };
49
+ }
50
+ const effectiveRetention = resolveReasoningRetention({
51
+ ...next.reasoningRetention !== void 0 ? { requested: next.reasoningRetention } : {},
52
+ ...options.reasoningRetention !== void 0 ? { overridden: options.reasoningRetention } : {},
53
+ ...capabilities.reasoningContract !== void 0 ? { contract: capabilities.reasoningContract } : {}
54
+ });
55
+ if (effectiveRetention !== next.reasoningRetention) next = {
56
+ ...next,
57
+ reasoningRetention: effectiveRetention
58
+ };
59
+ return next;
60
+ }
61
+
62
+ //#endregion
63
+ export { createProvider };
64
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","names":["capabilities: ProviderCapabilities"],"sources":["../src/provider.ts"],"sourcesContent":["/**\n * `createProvider(...)` — wrap an adapter return value in the canonical\n * Graphorin `Provider` shape and apply optional sensitivity / capability\n * overrides.\n *\n * Adapters return a \"raw\" provider object; `createProvider(...)` gives\n * downstream code a single place to centralise:\n *\n * - per-instance `acceptsSensitivity` declarations,\n * - capability overrides (e.g. forcing `multimodal: false` for a tool\n * that does not need it),\n * - default `reasoningRetention` resolution from the adapter's declared\n * `reasoningContract`,\n * - and the agreed-upon `name` / `modelId` lock-in.\n *\n * @stable\n */\n\nimport type {\n Provider,\n ProviderCapabilities,\n ProviderRequest,\n ReasoningRetention,\n Sensitivity,\n} from '@graphorin/core';\n\nimport { resolveReasoningRetention } from './reasoning/retention.js';\nimport { foldToolExamples } from './tool-examples.js';\n\n/**\n * Options accepted by {@link createProvider}.\n *\n * @stable\n */\nexport interface CreateProviderOptions {\n /**\n * Sensitivity tiers this provider is allowed to receive. When\n * unset, the value is forwarded from the wrapped adapter (which is\n * itself populated from the trust class for `baseUrl`-driven\n * adapters).\n */\n readonly acceptsSensitivity?: ReadonlyArray<Sensitivity>;\n /**\n * Per-request override of the reasoning-retention default. The\n * adapter's `capabilities.reasoningContract` decides the auto-\n * detected default; this option pins a different value for every\n * request the wrapper sees.\n */\n readonly reasoningRetention?: ReasoningRetention;\n /**\n * Optional capability override. Useful for narrowing what a\n * downstream tool advertises (e.g. setting `multimodal: false`\n * when the consumer's prompt cache is text-only).\n */\n readonly capabilities?: Partial<ProviderCapabilities>;\n}\n\n/**\n * Wrap an adapter in the canonical `Provider` shape. Adapters returned\n * by the bundled factories already implement `Provider`; passing them\n * through `createProvider(...)` is the recommended entry point because\n * it keeps the construction site documented and gives downstream\n * middleware a single attachment surface.\n *\n * @example\n * ```ts\n * const provider = createProvider(vercelAdapter(model), {\n * acceptsSensitivity: ['public', 'internal'],\n * });\n * ```\n *\n * @stable\n */\nexport function createProvider(adapter: Provider, options: CreateProviderOptions = {}): Provider {\n const capabilities: ProviderCapabilities = {\n ...adapter.capabilities,\n ...options.capabilities,\n };\n const acceptsSensitivity = options.acceptsSensitivity ?? adapter.acceptsSensitivity ?? undefined;\n\n const wrapped: Provider = {\n name: adapter.name,\n modelId: adapter.modelId,\n capabilities,\n ...(acceptsSensitivity ? { acceptsSensitivity } : {}),\n stream(req) {\n return adapter.stream(applyDefaults(req, options, capabilities));\n },\n generate(req) {\n return adapter.generate(applyDefaults(req, options, capabilities));\n },\n ...(adapter.countTokens ? { countTokens: adapter.countTokens.bind(adapter) } : {}),\n };\n return wrapped;\n}\n\nfunction applyDefaults(\n req: ProviderRequest,\n options: CreateProviderOptions,\n capabilities: ProviderCapabilities,\n): ProviderRequest {\n let next = req;\n // A1: fold worked tool examples into the model-facing description so the model\n // actually sees them (the wire contract carries them; adapters never rendered\n // them). Deterministic ⇒ the tool spec stays prompt-cache-stable.\n if (next.tools !== undefined) {\n const foldedTools = foldToolExamples(next.tools);\n if (foldedTools !== next.tools) next = { ...next, tools: foldedTools };\n }\n const effectiveRetention = resolveReasoningRetention({\n ...(next.reasoningRetention !== undefined ? { requested: next.reasoningRetention } : {}),\n ...(options.reasoningRetention !== undefined ? { overridden: options.reasoningRetention } : {}),\n ...(capabilities.reasoningContract !== undefined\n ? { contract: capabilities.reasoningContract }\n : {}),\n });\n if (effectiveRetention !== next.reasoningRetention) {\n next = { ...next, reasoningRetention: effectiveRetention };\n }\n return next;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAyEA,SAAgB,eAAe,SAAmB,UAAiC,EAAE,EAAY;CAC/F,MAAMA,eAAqC;EACzC,GAAG,QAAQ;EACX,GAAG,QAAQ;EACZ;CACD,MAAM,qBAAqB,QAAQ,sBAAsB,QAAQ,sBAAsB;AAevF,QAb0B;EACxB,MAAM,QAAQ;EACd,SAAS,QAAQ;EACjB;EACA,GAAI,qBAAqB,EAAE,oBAAoB,GAAG,EAAE;EACpD,OAAO,KAAK;AACV,UAAO,QAAQ,OAAO,cAAc,KAAK,SAAS,aAAa,CAAC;;EAElE,SAAS,KAAK;AACZ,UAAO,QAAQ,SAAS,cAAc,KAAK,SAAS,aAAa,CAAC;;EAEpE,GAAI,QAAQ,cAAc,EAAE,aAAa,QAAQ,YAAY,KAAK,QAAQ,EAAE,GAAG,EAAE;EAClF;;AAIH,SAAS,cACP,KACA,SACA,cACiB;CACjB,IAAI,OAAO;AAIX,KAAI,KAAK,UAAU,QAAW;EAC5B,MAAM,cAAc,iBAAiB,KAAK,MAAM;AAChD,MAAI,gBAAgB,KAAK,MAAO,QAAO;GAAE,GAAG;GAAM,OAAO;GAAa;;CAExE,MAAM,qBAAqB,0BAA0B;EACnD,GAAI,KAAK,uBAAuB,SAAY,EAAE,WAAW,KAAK,oBAAoB,GAAG,EAAE;EACvF,GAAI,QAAQ,uBAAuB,SAAY,EAAE,YAAY,QAAQ,oBAAoB,GAAG,EAAE;EAC9F,GAAI,aAAa,sBAAsB,SACnC,EAAE,UAAU,aAAa,mBAAmB,GAC5C,EAAE;EACP,CAAC;AACF,KAAI,uBAAuB,KAAK,mBAC9B,QAAO;EAAE,GAAG;EAAM,oBAAoB;EAAoB;AAE5D,QAAO"}
@@ -0,0 +1,22 @@
1
+ import { Message, ReasoningRetention } from "@graphorin/core";
2
+
3
+ //#region src/reasoning/apply-policy.d.ts
4
+
5
+ /**
6
+ * Inputs to {@link applyReasoningPolicy}.
7
+ *
8
+ * @stable
9
+ */
10
+ interface ApplyReasoningPolicyInput {
11
+ readonly messages: ReadonlyArray<Message>;
12
+ readonly retention: ReasoningRetention;
13
+ }
14
+ /**
15
+ * Apply the resolved retention policy to the provided messages.
16
+ *
17
+ * @stable
18
+ */
19
+ declare function applyReasoningPolicy(input: ApplyReasoningPolicyInput): ReadonlyArray<Message>;
20
+ //#endregion
21
+ export { ApplyReasoningPolicyInput, applyReasoningPolicy };
22
+ //# sourceMappingURL=apply-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply-policy.d.ts","names":[],"sources":["../../src/reasoning/apply-policy.ts"],"sourcesContent":[],"mappings":";;;;;;;;;UAgCiB,yBAAA;qBACI,cAAc;sBACb;;;;;;;iBAQN,oBAAA,QAA4B,4BAA4B,cAAc"}
@@ -0,0 +1,47 @@
1
+ //#region src/reasoning/apply-policy.ts
2
+ /**
3
+ * Apply the resolved retention policy to the provided messages.
4
+ *
5
+ * @stable
6
+ */
7
+ function applyReasoningPolicy(input) {
8
+ if (input.retention === "pass-through-all") return input.messages;
9
+ return input.messages.map((msg) => {
10
+ if (msg.role !== "assistant") return msg;
11
+ return rewriteAssistantMessage(msg, input.retention);
12
+ });
13
+ }
14
+ function rewriteAssistantMessage(msg, retention) {
15
+ const content = msg.content;
16
+ if (typeof content === "string") return msg;
17
+ const filtered = [];
18
+ let mutated = false;
19
+ for (const part of content) {
20
+ if (part.type !== "reasoning") {
21
+ filtered.push(part);
22
+ continue;
23
+ }
24
+ if (retention === "strip") {
25
+ mutated = true;
26
+ continue;
27
+ }
28
+ if (retention === "pass-through-claude") if (isAnthropicShapedReasoning(part)) filtered.push(part);
29
+ else mutated = true;
30
+ }
31
+ if (!mutated) return msg;
32
+ return {
33
+ ...msg,
34
+ content: filtered
35
+ };
36
+ }
37
+ function isAnthropicShapedReasoning(part) {
38
+ const meta = part.meta;
39
+ if (meta === void 0) return false;
40
+ if (meta.provider === "anthropic") return true;
41
+ if (typeof meta.signature === "string" && meta.signature.length > 0) return true;
42
+ return false;
43
+ }
44
+
45
+ //#endregion
46
+ export { applyReasoningPolicy };
47
+ //# sourceMappingURL=apply-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply-policy.js","names":["filtered: MessageContent[]"],"sources":["../../src/reasoning/apply-policy.ts"],"sourcesContent":["/**\n * `applyReasoningPolicy` — given a list of `Message`s and the resolved\n * {@link ReasoningRetention}, return a transformed list with\n * `reasoning` content parts treated according to the policy:\n *\n * - `'strip'` — drop every `reasoning` content part.\n * - `'pass-through-claude'` — keep parts that carry Anthropic-shaped\n * metadata (`provider === 'anthropic'` OR a `signature` field);\n * drop the rest.\n * - `'pass-through-all'` — keep every `reasoning` content part\n * unchanged.\n *\n * The transformation is shallow: only the `assistant` role's content\n * arrays are inspected (the only role that legitimately carries\n * reasoning blocks).\n *\n * @packageDocumentation\n */\n\nimport type {\n AssistantMessage,\n Message,\n MessageContent,\n ReasoningContent,\n ReasoningRetention,\n} from '@graphorin/core';\n\n/**\n * Inputs to {@link applyReasoningPolicy}.\n *\n * @stable\n */\nexport interface ApplyReasoningPolicyInput {\n readonly messages: ReadonlyArray<Message>;\n readonly retention: ReasoningRetention;\n}\n\n/**\n * Apply the resolved retention policy to the provided messages.\n *\n * @stable\n */\nexport function applyReasoningPolicy(input: ApplyReasoningPolicyInput): ReadonlyArray<Message> {\n if (input.retention === 'pass-through-all') return input.messages;\n return input.messages.map((msg) => {\n if (msg.role !== 'assistant') return msg;\n return rewriteAssistantMessage(msg, input.retention);\n });\n}\n\nfunction rewriteAssistantMessage(\n msg: AssistantMessage,\n retention: ReasoningRetention,\n): AssistantMessage {\n const content = msg.content;\n if (typeof content === 'string') return msg;\n const filtered: MessageContent[] = [];\n let mutated = false;\n for (const part of content) {\n if (part.type !== 'reasoning') {\n filtered.push(part);\n continue;\n }\n if (retention === 'strip') {\n mutated = true;\n continue;\n }\n if (retention === 'pass-through-claude') {\n if (isAnthropicShapedReasoning(part)) {\n filtered.push(part);\n } else {\n mutated = true;\n }\n }\n }\n if (!mutated) return msg;\n return {\n ...msg,\n content: filtered,\n };\n}\n\nfunction isAnthropicShapedReasoning(part: ReasoningContent): boolean {\n const meta = part.meta;\n if (meta === undefined) return false;\n if (meta.provider === 'anthropic') return true;\n if (typeof meta.signature === 'string' && meta.signature.length > 0) return true;\n return false;\n}\n"],"mappings":";;;;;;AA0CA,SAAgB,qBAAqB,OAA0D;AAC7F,KAAI,MAAM,cAAc,mBAAoB,QAAO,MAAM;AACzD,QAAO,MAAM,SAAS,KAAK,QAAQ;AACjC,MAAI,IAAI,SAAS,YAAa,QAAO;AACrC,SAAO,wBAAwB,KAAK,MAAM,UAAU;GACpD;;AAGJ,SAAS,wBACP,KACA,WACkB;CAClB,MAAM,UAAU,IAAI;AACpB,KAAI,OAAO,YAAY,SAAU,QAAO;CACxC,MAAMA,WAA6B,EAAE;CACrC,IAAI,UAAU;AACd,MAAK,MAAM,QAAQ,SAAS;AAC1B,MAAI,KAAK,SAAS,aAAa;AAC7B,YAAS,KAAK,KAAK;AACnB;;AAEF,MAAI,cAAc,SAAS;AACzB,aAAU;AACV;;AAEF,MAAI,cAAc,sBAChB,KAAI,2BAA2B,KAAK,CAClC,UAAS,KAAK,KAAK;MAEnB,WAAU;;AAIhB,KAAI,CAAC,QAAS,QAAO;AACrB,QAAO;EACL,GAAG;EACH,SAAS;EACV;;AAGH,SAAS,2BAA2B,MAAiC;CACnE,MAAM,OAAO,KAAK;AAClB,KAAI,SAAS,OAAW,QAAO;AAC/B,KAAI,KAAK,aAAa,YAAa,QAAO;AAC1C,KAAI,OAAO,KAAK,cAAc,YAAY,KAAK,UAAU,SAAS,EAAG,QAAO;AAC5E,QAAO"}
@@ -0,0 +1,41 @@
1
+ import { ReasoningContract } from "@graphorin/core";
2
+
3
+ //#region src/reasoning/classify-contract.d.ts
4
+
5
+ /**
6
+ * Single entry in the contract-classifier rule table.
7
+ *
8
+ * @stable
9
+ */
10
+ interface ReasoningContractRule {
11
+ readonly contract: ReasoningContract;
12
+ readonly pattern: RegExp;
13
+ /** Human-readable family label. */
14
+ readonly family: string;
15
+ }
16
+ /**
17
+ * Static rule table. Higher-specificity entries come first. Mirrors
18
+ * the per-family matrix documented for the provider layer.
19
+ *
20
+ * @stable
21
+ */
22
+ declare const REASONING_CONTRACT_RULES: readonly ReasoningContractRule[];
23
+ /**
24
+ * Inputs to {@link inferReasoningContract}.
25
+ *
26
+ * @stable
27
+ */
28
+ interface InferReasoningContractInput {
29
+ readonly modelId: string;
30
+ readonly provider?: string;
31
+ }
32
+ /**
33
+ * Return the canonical {@link ReasoningContract} for a model id, or
34
+ * `'optional'` for unknown / Ollama / OpenAI-compatible families.
35
+ *
36
+ * @stable
37
+ */
38
+ declare function inferReasoningContract(input: InferReasoningContractInput): ReasoningContract;
39
+ //#endregion
40
+ export { InferReasoningContractInput, REASONING_CONTRACT_RULES, ReasoningContractRule, inferReasoningContract };
41
+ //# sourceMappingURL=classify-contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classify-contract.d.ts","names":[],"sources":["../../src/reasoning/classify-contract.ts"],"sourcesContent":[],"mappings":";;;;;;;;;UAqBiB,qBAAA;qBACI;oBACD;;;;;;;;;;cAWP,mCAAmC;;;;;;UA6B/B,2BAAA;;;;;;;;;;iBAWD,sBAAA,QAA8B,8BAA8B"}
@@ -0,0 +1,59 @@
1
+ //#region src/reasoning/classify-contract.ts
2
+ /**
3
+ * Static rule table. Higher-specificity entries come first. Mirrors
4
+ * the per-family matrix documented for the provider layer.
5
+ *
6
+ * @stable
7
+ */
8
+ const REASONING_CONTRACT_RULES = Object.freeze([
9
+ {
10
+ contract: "round-trip-required",
11
+ family: "anthropic-claude",
12
+ pattern: /^claude(?:-\d|-)/
13
+ },
14
+ {
15
+ contract: "round-trip-required",
16
+ family: "bedrock-claude",
17
+ pattern: /^anthropic\.claude/
18
+ },
19
+ {
20
+ contract: "hidden",
21
+ family: "openai-reasoning",
22
+ pattern: /^o[1-9]\b/
23
+ },
24
+ {
25
+ contract: "hidden",
26
+ family: "openai-reasoning-extended",
27
+ pattern: /^o[1-9][a-z-]/
28
+ },
29
+ {
30
+ contract: "hidden",
31
+ family: "gemini-reasoning",
32
+ pattern: /^gemini.*(?:thinking|reasoning)/
33
+ }
34
+ ]);
35
+ /**
36
+ * Return the canonical {@link ReasoningContract} for a model id, or
37
+ * `'optional'` for unknown / Ollama / OpenAI-compatible families.
38
+ *
39
+ * @stable
40
+ */
41
+ function inferReasoningContract(input) {
42
+ if (typeof input.modelId !== "string" || input.modelId.length === 0) return "optional";
43
+ const provider = input.provider?.toLowerCase();
44
+ if (provider === "anthropic" || provider === "bedrock") return "round-trip-required";
45
+ const normalised = stripPrefix(input.modelId.toLowerCase());
46
+ for (const rule of REASONING_CONTRACT_RULES) if (rule.pattern.test(normalised)) return rule.contract;
47
+ return "optional";
48
+ }
49
+ function stripPrefix(model) {
50
+ const slash = model.indexOf("/");
51
+ if (slash !== -1) return model.slice(slash + 1);
52
+ const colon = model.indexOf(":");
53
+ if (colon !== -1 && !model.startsWith("http")) return model.slice(colon + 1);
54
+ return model;
55
+ }
56
+
57
+ //#endregion
58
+ export { REASONING_CONTRACT_RULES, inferReasoningContract };
59
+ //# sourceMappingURL=classify-contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classify-contract.js","names":["REASONING_CONTRACT_RULES: readonly ReasoningContractRule[]"],"sources":["../../src/reasoning/classify-contract.ts"],"sourcesContent":["/**\n * `inferReasoningContract` — derive the canonical\n * {@link ReasoningContract} value for a model id. Adapters that wrap\n * cloud-LLM language-model values call this at construction time so\n * the runtime can pick the correct\n * {@link import('@graphorin/core').ReasoningRetention} default.\n *\n * The classifier is a pure function with the same shape as the\n * `classifyModelTier` family: a small static rule table matched\n * against the lowercased + prefix-stripped model id.\n *\n * @packageDocumentation\n */\n\nimport type { ReasoningContract } from '@graphorin/core';\n\n/**\n * Single entry in the contract-classifier rule table.\n *\n * @stable\n */\nexport interface ReasoningContractRule {\n readonly contract: ReasoningContract;\n readonly pattern: RegExp;\n /** Human-readable family label. */\n readonly family: string;\n}\n\n/**\n * Static rule table. Higher-specificity entries come first. Mirrors\n * the per-family matrix documented for the provider layer.\n *\n * @stable\n */\nexport const REASONING_CONTRACT_RULES: readonly ReasoningContractRule[] = Object.freeze([\n // Anthropic Claude families (and Bedrock-distributed equivalents).\n {\n contract: 'round-trip-required',\n family: 'anthropic-claude',\n pattern: /^claude(?:-\\d|-)/,\n },\n {\n contract: 'round-trip-required',\n family: 'bedrock-claude',\n pattern: /^anthropic\\.claude/,\n },\n // OpenAI hidden chain-of-thought reasoning models.\n { contract: 'hidden', family: 'openai-reasoning', pattern: /^o[1-9]\\b/ },\n { contract: 'hidden', family: 'openai-reasoning-extended', pattern: /^o[1-9][a-z-]/ },\n // Google Gemini reasoning families (the dedicated thinking variants;\n // the regular pro / flash families do not require round-tripping).\n {\n contract: 'hidden',\n family: 'gemini-reasoning',\n pattern: /^gemini.*(?:thinking|reasoning)/,\n },\n]);\n\n/**\n * Inputs to {@link inferReasoningContract}.\n *\n * @stable\n */\nexport interface InferReasoningContractInput {\n readonly modelId: string;\n readonly provider?: string;\n}\n\n/**\n * Return the canonical {@link ReasoningContract} for a model id, or\n * `'optional'` for unknown / Ollama / OpenAI-compatible families.\n *\n * @stable\n */\nexport function inferReasoningContract(input: InferReasoningContractInput): ReasoningContract {\n if (typeof input.modelId !== 'string' || input.modelId.length === 0) {\n return 'optional';\n }\n const provider = input.provider?.toLowerCase();\n // Provider-explicit short-circuits — let cloud-LLM consumers tag\n // their model with `provider: 'anthropic'` even when the modelId is\n // a non-canonical alias (e.g. `legacy-thinking-router`).\n if (provider === 'anthropic' || provider === 'bedrock') {\n return 'round-trip-required';\n }\n const normalised = stripPrefix(input.modelId.toLowerCase());\n for (const rule of REASONING_CONTRACT_RULES) {\n if (rule.pattern.test(normalised)) return rule.contract;\n }\n return 'optional';\n}\n\nfunction stripPrefix(model: string): string {\n const slash = model.indexOf('/');\n if (slash !== -1) return model.slice(slash + 1);\n const colon = model.indexOf(':');\n if (colon !== -1 && !model.startsWith('http')) {\n return model.slice(colon + 1);\n }\n return model;\n}\n"],"mappings":";;;;;;;AAkCA,MAAaA,2BAA6D,OAAO,OAAO;CAEtF;EACE,UAAU;EACV,QAAQ;EACR,SAAS;EACV;CACD;EACE,UAAU;EACV,QAAQ;EACR,SAAS;EACV;CAED;EAAE,UAAU;EAAU,QAAQ;EAAoB,SAAS;EAAa;CACxE;EAAE,UAAU;EAAU,QAAQ;EAA6B,SAAS;EAAiB;CAGrF;EACE,UAAU;EACV,QAAQ;EACR,SAAS;EACV;CACF,CAAC;;;;;;;AAkBF,SAAgB,uBAAuB,OAAuD;AAC5F,KAAI,OAAO,MAAM,YAAY,YAAY,MAAM,QAAQ,WAAW,EAChE,QAAO;CAET,MAAM,WAAW,MAAM,UAAU,aAAa;AAI9C,KAAI,aAAa,eAAe,aAAa,UAC3C,QAAO;CAET,MAAM,aAAa,YAAY,MAAM,QAAQ,aAAa,CAAC;AAC3D,MAAK,MAAM,QAAQ,yBACjB,KAAI,KAAK,QAAQ,KAAK,WAAW,CAAE,QAAO,KAAK;AAEjD,QAAO;;AAGT,SAAS,YAAY,OAAuB;CAC1C,MAAM,QAAQ,MAAM,QAAQ,IAAI;AAChC,KAAI,UAAU,GAAI,QAAO,MAAM,MAAM,QAAQ,EAAE;CAC/C,MAAM,QAAQ,MAAM,QAAQ,IAAI;AAChC,KAAI,UAAU,MAAM,CAAC,MAAM,WAAW,OAAO,CAC3C,QAAO,MAAM,MAAM,QAAQ,EAAE;AAE/B,QAAO"}
@@ -0,0 +1,4 @@
1
+ import { ApplyReasoningPolicyInput, applyReasoningPolicy } from "./apply-policy.js";
2
+ import { InferReasoningContractInput, REASONING_CONTRACT_RULES, ReasoningContractRule, inferReasoningContract } from "./classify-contract.js";
3
+ import { REASONING_RETENTION_DEFAULTS, ResolveReasoningRetentionInput, resolveReasoningRetention } from "./retention.js";
4
+ export { type ApplyReasoningPolicyInput, type InferReasoningContractInput, REASONING_CONTRACT_RULES, REASONING_RETENTION_DEFAULTS, type ReasoningContractRule, type ResolveReasoningRetentionInput, applyReasoningPolicy, inferReasoningContract, resolveReasoningRetention };
@@ -0,0 +1,5 @@
1
+ import { applyReasoningPolicy } from "./apply-policy.js";
2
+ import { REASONING_RETENTION_DEFAULTS, resolveReasoningRetention } from "./retention.js";
3
+ import { REASONING_CONTRACT_RULES, inferReasoningContract } from "./classify-contract.js";
4
+
5
+ export { REASONING_CONTRACT_RULES, REASONING_RETENTION_DEFAULTS, applyReasoningPolicy, inferReasoningContract, resolveReasoningRetention };
@@ -0,0 +1,39 @@
1
+ import { ReasoningContract, ReasoningRetention } from "@graphorin/core";
2
+
3
+ //#region src/reasoning/retention.d.ts
4
+
5
+ /**
6
+ * Map from `ReasoningContract` to the conservative default
7
+ * `ReasoningRetention` value used when no override is supplied.
8
+ *
9
+ * @stable
10
+ */
11
+ declare const REASONING_RETENTION_DEFAULTS: Readonly<Record<ReasoningContract, ReasoningRetention>>;
12
+ /**
13
+ * Inputs to {@link resolveReasoningRetention}.
14
+ *
15
+ * @stable
16
+ */
17
+ interface ResolveReasoningRetentionInput {
18
+ /** Explicit per-request override (highest precedence). */
19
+ readonly requested?: ReasoningRetention;
20
+ /** Instance-level override supplied to `createProvider({...})`. */
21
+ readonly overridden?: ReasoningRetention;
22
+ /** Adapter-declared capability (lowest precedence). */
23
+ readonly contract?: ReasoningContract;
24
+ }
25
+ /**
26
+ * Resolve the effective {@link ReasoningRetention} value for a single
27
+ * request. The resolution is precedence-driven:
28
+ *
29
+ * 1. `requested` wins if defined.
30
+ * 2. `overridden` wins next.
31
+ * 3. The default for `contract` is used if the contract is known.
32
+ * 4. `'strip'` is the conservative fallback when no input is supplied.
33
+ *
34
+ * @stable
35
+ */
36
+ declare function resolveReasoningRetention(input: ResolveReasoningRetentionInput): ReasoningRetention;
37
+ //#endregion
38
+ export { REASONING_RETENTION_DEFAULTS, ResolveReasoningRetentionInput, resolveReasoningRetention };
39
+ //# sourceMappingURL=retention.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retention.d.ts","names":[],"sources":["../../src/reasoning/retention.ts"],"sourcesContent":[],"mappings":";;;;AAsDA;;;;;;cAhCa,8BAA8B,SAAS,OAAO,mBAAmB;;;;;;UAY7D,8BAAA;;uBAEM;;wBAEC;;sBAEF;;;;;;;;;;;;;iBAcN,yBAAA,QACP,iCACN"}