@graphorin/provider 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/LICENSE +21 -0
- package/README.md +105 -0
- package/dist/adapters/index.d.ts +4 -0
- package/dist/adapters/index.js +6 -0
- package/dist/adapters/llamacpp-server.d.ts +69 -0
- package/dist/adapters/llamacpp-server.d.ts.map +1 -0
- package/dist/adapters/llamacpp-server.js +51 -0
- package/dist/adapters/llamacpp-server.js.map +1 -0
- package/dist/adapters/ollama.d.ts +43 -0
- package/dist/adapters/ollama.d.ts.map +1 -0
- package/dist/adapters/ollama.js +246 -0
- package/dist/adapters/ollama.js.map +1 -0
- package/dist/adapters/openai-compatible.d.ts +48 -0
- package/dist/adapters/openai-compatible.d.ts.map +1 -0
- package/dist/adapters/openai-compatible.js +28 -0
- package/dist/adapters/openai-compatible.js.map +1 -0
- package/dist/adapters/vercel.d.ts +135 -0
- package/dist/adapters/vercel.d.ts.map +1 -0
- package/dist/adapters/vercel.js +306 -0
- package/dist/adapters/vercel.js.map +1 -0
- package/dist/counters/anthropic.d.ts +36 -0
- package/dist/counters/anthropic.d.ts.map +1 -0
- package/dist/counters/anthropic.js +63 -0
- package/dist/counters/anthropic.js.map +1 -0
- package/dist/counters/bedrock.d.ts +27 -0
- package/dist/counters/bedrock.d.ts.map +1 -0
- package/dist/counters/bedrock.js +29 -0
- package/dist/counters/bedrock.js.map +1 -0
- package/dist/counters/dispatcher.d.ts +34 -0
- package/dist/counters/dispatcher.d.ts.map +1 -0
- package/dist/counters/dispatcher.js +78 -0
- package/dist/counters/dispatcher.js.map +1 -0
- package/dist/counters/global.d.ts +26 -0
- package/dist/counters/global.d.ts.map +1 -0
- package/dist/counters/global.js +31 -0
- package/dist/counters/global.js.map +1 -0
- package/dist/counters/google.d.ts +27 -0
- package/dist/counters/google.d.ts.map +1 -0
- package/dist/counters/google.js +29 -0
- package/dist/counters/google.js.map +1 -0
- package/dist/counters/heuristic.d.ts +37 -0
- package/dist/counters/heuristic.d.ts.map +1 -0
- package/dist/counters/heuristic.js +64 -0
- package/dist/counters/heuristic.js.map +1 -0
- package/dist/counters/index.d.ts +9 -0
- package/dist/counters/index.js +10 -0
- package/dist/counters/js-tiktoken.d.ts +63 -0
- package/dist/counters/js-tiktoken.d.ts.map +1 -0
- package/dist/counters/js-tiktoken.js +73 -0
- package/dist/counters/js-tiktoken.js.map +1 -0
- package/dist/counters/serialize.d.ts +37 -0
- package/dist/counters/serialize.d.ts.map +1 -0
- package/dist/counters/serialize.js +57 -0
- package/dist/counters/serialize.js.map +1 -0
- package/dist/errors/errors.d.ts +167 -0
- package/dist/errors/errors.d.ts.map +1 -0
- package/dist/errors/errors.js +182 -0
- package/dist/errors/errors.js.map +1 -0
- package/dist/errors/index.d.ts +2 -0
- package/dist/errors/index.js +3 -0
- package/dist/index.d.ts +69 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +70 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/abort.js +34 -0
- package/dist/internal/abort.js.map +1 -0
- package/dist/internal/http.js +163 -0
- package/dist/internal/http.js.map +1 -0
- package/dist/internal/openai-shaped.js +308 -0
- package/dist/internal/openai-shaped.js.map +1 -0
- package/dist/internal/sse.js +104 -0
- package/dist/internal/sse.js.map +1 -0
- package/dist/internal/url-utils.js +24 -0
- package/dist/internal/url-utils.js.map +1 -0
- package/dist/middleware/compose.d.ts +85 -0
- package/dist/middleware/compose.d.ts.map +1 -0
- package/dist/middleware/compose.js +174 -0
- package/dist/middleware/compose.js.map +1 -0
- package/dist/middleware/index.d.ts +10 -0
- package/dist/middleware/index.js +11 -0
- package/dist/middleware/production-hook.d.ts +27 -0
- package/dist/middleware/production-hook.d.ts.map +1 -0
- package/dist/middleware/production-hook.js +21 -0
- package/dist/middleware/production-hook.js.map +1 -0
- package/dist/middleware/with-cost-limit.d.ts +46 -0
- package/dist/middleware/with-cost-limit.d.ts.map +1 -0
- package/dist/middleware/with-cost-limit.js +66 -0
- package/dist/middleware/with-cost-limit.js.map +1 -0
- package/dist/middleware/with-cost-tracking.d.ts +91 -0
- package/dist/middleware/with-cost-tracking.d.ts.map +1 -0
- package/dist/middleware/with-cost-tracking.js +95 -0
- package/dist/middleware/with-cost-tracking.js.map +1 -0
- package/dist/middleware/with-fallback.d.ts +25 -0
- package/dist/middleware/with-fallback.d.ts.map +1 -0
- package/dist/middleware/with-fallback.js +84 -0
- package/dist/middleware/with-fallback.js.map +1 -0
- package/dist/middleware/with-rate-limit.d.ts +36 -0
- package/dist/middleware/with-rate-limit.d.ts.map +1 -0
- package/dist/middleware/with-rate-limit.js +141 -0
- package/dist/middleware/with-rate-limit.js.map +1 -0
- package/dist/middleware/with-redaction.d.ts +60 -0
- package/dist/middleware/with-redaction.d.ts.map +1 -0
- package/dist/middleware/with-redaction.js +305 -0
- package/dist/middleware/with-redaction.js.map +1 -0
- package/dist/middleware/with-retry.d.ts +26 -0
- package/dist/middleware/with-retry.d.ts.map +1 -0
- package/dist/middleware/with-retry.js +157 -0
- package/dist/middleware/with-retry.js.map +1 -0
- package/dist/middleware/with-tracing.d.ts +21 -0
- package/dist/middleware/with-tracing.d.ts.map +1 -0
- package/dist/middleware/with-tracing.js +55 -0
- package/dist/middleware/with-tracing.js.map +1 -0
- package/dist/model-tier/classify.d.ts +34 -0
- package/dist/model-tier/classify.d.ts.map +1 -0
- package/dist/model-tier/classify.js +117 -0
- package/dist/model-tier/classify.js.map +1 -0
- package/dist/model-tier/index.d.ts +2 -0
- package/dist/model-tier/index.js +3 -0
- package/dist/provider.d.ts +51 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +64 -0
- package/dist/provider.js.map +1 -0
- package/dist/reasoning/apply-policy.d.ts +22 -0
- package/dist/reasoning/apply-policy.d.ts.map +1 -0
- package/dist/reasoning/apply-policy.js +47 -0
- package/dist/reasoning/apply-policy.js.map +1 -0
- package/dist/reasoning/classify-contract.d.ts +41 -0
- package/dist/reasoning/classify-contract.d.ts.map +1 -0
- package/dist/reasoning/classify-contract.js +59 -0
- package/dist/reasoning/classify-contract.js.map +1 -0
- package/dist/reasoning/index.d.ts +4 -0
- package/dist/reasoning/index.js +5 -0
- package/dist/reasoning/retention.d.ts +39 -0
- package/dist/reasoning/retention.d.ts.map +1 -0
- package/dist/reasoning/retention.js +33 -0
- package/dist/reasoning/retention.js.map +1 -0
- package/dist/tool-examples.d.ts +14 -0
- package/dist/tool-examples.d.ts.map +1 -0
- package/dist/tool-examples.js +35 -0
- package/dist/tool-examples.js.map +1 -0
- package/dist/trust/classify-local-provider.d.ts +47 -0
- package/dist/trust/classify-local-provider.d.ts.map +1 -0
- package/dist/trust/classify-local-provider.js +164 -0
- package/dist/trust/classify-local-provider.js.map +1 -0
- package/dist/trust/index.d.ts +3 -0
- package/dist/trust/index.js +3 -0
- package/package.json +128 -0
|
@@ -0,0 +1,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,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"}
|
package/dist/provider.js
ADDED
|
@@ -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"}
|