@graphorin/provider 0.5.0 → 0.6.1

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 (82) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +14 -9
  3. package/dist/adapters/llamacpp-server.d.ts +1 -1
  4. package/dist/adapters/llamacpp-server.js +1 -1
  5. package/dist/adapters/llamacpp-server.js.map +1 -1
  6. package/dist/adapters/ollama.d.ts.map +1 -1
  7. package/dist/adapters/ollama.js +3 -2
  8. package/dist/adapters/ollama.js.map +1 -1
  9. package/dist/adapters/openai-compatible.d.ts +14 -1
  10. package/dist/adapters/openai-compatible.d.ts.map +1 -1
  11. package/dist/adapters/openai-compatible.js +2 -0
  12. package/dist/adapters/openai-compatible.js.map +1 -1
  13. package/dist/adapters/vercel-messages.js +245 -0
  14. package/dist/adapters/vercel-messages.js.map +1 -0
  15. package/dist/adapters/vercel.d.ts +10 -8
  16. package/dist/adapters/vercel.d.ts.map +1 -1
  17. package/dist/adapters/vercel.js +54 -14
  18. package/dist/adapters/vercel.js.map +1 -1
  19. package/dist/counters/anthropic-wire.js +149 -0
  20. package/dist/counters/anthropic-wire.js.map +1 -0
  21. package/dist/counters/anthropic.d.ts +6 -1
  22. package/dist/counters/anthropic.d.ts.map +1 -1
  23. package/dist/counters/anthropic.js +25 -4
  24. package/dist/counters/anthropic.js.map +1 -1
  25. package/dist/counters/bedrock.js.map +1 -1
  26. package/dist/counters/dispatcher.d.ts.map +1 -1
  27. package/dist/counters/dispatcher.js +6 -4
  28. package/dist/counters/dispatcher.js.map +1 -1
  29. package/dist/counters/google.js.map +1 -1
  30. package/dist/counters/js-tiktoken.js.map +1 -1
  31. package/dist/errors/errors.d.ts +39 -11
  32. package/dist/errors/errors.d.ts.map +1 -1
  33. package/dist/errors/errors.js +54 -11
  34. package/dist/errors/errors.js.map +1 -1
  35. package/dist/errors/index.d.ts +2 -2
  36. package/dist/errors/index.js +2 -2
  37. package/dist/index.d.ts +5 -5
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +7 -6
  40. package/dist/index.js.map +1 -1
  41. package/dist/internal/abort.js +2 -2
  42. package/dist/internal/abort.js.map +1 -1
  43. package/dist/internal/http.js +19 -4
  44. package/dist/internal/http.js.map +1 -1
  45. package/dist/internal/openai-shaped.js +8 -3
  46. package/dist/internal/openai-shaped.js.map +1 -1
  47. package/dist/middleware/compose.d.ts +6 -6
  48. package/dist/middleware/compose.js +7 -7
  49. package/dist/middleware/compose.js.map +1 -1
  50. package/dist/middleware/production-hook.js.map +1 -1
  51. package/dist/middleware/with-cost-limit.d.ts +1 -1
  52. package/dist/middleware/with-cost-limit.js.map +1 -1
  53. package/dist/middleware/with-cost-tracking.d.ts +17 -4
  54. package/dist/middleware/with-cost-tracking.d.ts.map +1 -1
  55. package/dist/middleware/with-cost-tracking.js +12 -2
  56. package/dist/middleware/with-cost-tracking.js.map +1 -1
  57. package/dist/middleware/with-fallback.js +4 -2
  58. package/dist/middleware/with-fallback.js.map +1 -1
  59. package/dist/middleware/with-rate-limit.d.ts +2 -2
  60. package/dist/middleware/with-rate-limit.js.map +1 -1
  61. package/dist/middleware/with-redaction.d.ts +1 -1
  62. package/dist/middleware/with-redaction.js.map +1 -1
  63. package/dist/middleware/with-retry.js +3 -1
  64. package/dist/middleware/with-retry.js.map +1 -1
  65. package/dist/middleware/with-tracing.js +27 -6
  66. package/dist/middleware/with-tracing.js.map +1 -1
  67. package/dist/model-tier/classify.d.ts +1 -1
  68. package/dist/model-tier/classify.js +13 -5
  69. package/dist/model-tier/classify.js.map +1 -1
  70. package/dist/package.js +6 -0
  71. package/dist/package.js.map +1 -0
  72. package/dist/provider.js.map +1 -1
  73. package/dist/reasoning/apply-policy.js.map +1 -1
  74. package/dist/reasoning/classify-contract.js +14 -5
  75. package/dist/reasoning/classify-contract.js.map +1 -1
  76. package/dist/reasoning/retention.js.map +1 -1
  77. package/dist/tool-examples.d.ts +1 -1
  78. package/dist/tool-examples.js +1 -1
  79. package/dist/tool-examples.js.map +1 -1
  80. package/dist/trust/classify-local-provider.d.ts +1 -1
  81. package/dist/trust/classify-local-provider.js.map +1 -1
  82. package/package.json +6 -4
@@ -1 +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"}
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"}
@@ -1 +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"}
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"}
@@ -41,17 +41,26 @@ const REASONING_CONTRACT_RULES = Object.freeze([
41
41
  function inferReasoningContract(input) {
42
42
  if (typeof input.modelId !== "string" || input.modelId.length === 0) return "optional";
43
43
  const provider = input.provider?.toLowerCase();
44
- if (provider === "anthropic" || provider === "bedrock") return "round-trip-required";
44
+ if (provider !== void 0 && (provider.startsWith("anthropic") || provider.includes("bedrock"))) return "round-trip-required";
45
45
  const normalised = stripPrefix(input.modelId.toLowerCase());
46
46
  for (const rule of REASONING_CONTRACT_RULES) if (rule.pattern.test(normalised)) return rule.contract;
47
47
  return "optional";
48
48
  }
49
+ /**
50
+ * Bedrock cross-region inference-profile prefix (`us.anthropic.claude-…`,
51
+ * the standard way to invoke Claude on Bedrock since 2025). Stripped
52
+ * before pattern matching so the `^anthropic\.claude` rules still fire
53
+ * (core-provider-11).
54
+ */
55
+ const BEDROCK_REGION_PREFIX = /^(?:us|eu|apac|jp|au|us-gov)\./;
49
56
  function stripPrefix(model) {
50
57
  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;
58
+ if (slash !== -1) return model.slice(slash + 1).replace(BEDROCK_REGION_PREFIX, "");
59
+ const deRegioned = model.replace(BEDROCK_REGION_PREFIX, "");
60
+ if (deRegioned.startsWith("anthropic.")) return deRegioned;
61
+ const colon = deRegioned.indexOf(":");
62
+ if (colon !== -1 && !deRegioned.startsWith("http")) return deRegioned.slice(colon + 1);
63
+ return deRegioned;
55
64
  }
56
65
 
57
66
  //#endregion
@@ -1 +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"}
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`). Prefix /\n // substring matching because the AI SDK reports dotted provider ids\n // (`'anthropic.messages'`, `'amazon-bedrock.messages'`), not the\n // bare vendor name (core-provider-11).\n if (\n provider !== undefined &&\n (provider.startsWith('anthropic') || provider.includes('bedrock'))\n ) {\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\n/**\n * Bedrock cross-region inference-profile prefix (`us.anthropic.claude-…`,\n * the standard way to invoke Claude on Bedrock since 2025). Stripped\n * before pattern matching so the `^anthropic\\.claude` rules still fire\n * (core-provider-11).\n */\nconst BEDROCK_REGION_PREFIX = /^(?:us|eu|apac|jp|au|us-gov)\\./;\n\nfunction stripPrefix(model: string): string {\n const slash = model.indexOf('/');\n if (slash !== -1) return model.slice(slash + 1).replace(BEDROCK_REGION_PREFIX, '');\n const deRegioned = model.replace(BEDROCK_REGION_PREFIX, '');\n // Bedrock ids end in ':<version>' (`anthropic.claude-...-v1:0`) - the\n // colon there is a version separator, not a provider/model split, and\n // the rule patterns are prefix-anchored so the suffix is harmless.\n if (deRegioned.startsWith('anthropic.')) return deRegioned;\n const colon = deRegioned.indexOf(':');\n if (colon !== -1 && !deRegioned.startsWith('http')) {\n return deRegioned.slice(colon + 1);\n }\n return deRegioned;\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;AAO9C,KACE,aAAa,WACZ,SAAS,WAAW,YAAY,IAAI,SAAS,SAAS,UAAU,EAEjE,QAAO;CAET,MAAM,aAAa,YAAY,MAAM,QAAQ,aAAa,CAAC;AAC3D,MAAK,MAAM,QAAQ,yBACjB,KAAI,KAAK,QAAQ,KAAK,WAAW,CAAE,QAAO,KAAK;AAEjD,QAAO;;;;;;;;AAST,MAAM,wBAAwB;AAE9B,SAAS,YAAY,OAAuB;CAC1C,MAAM,QAAQ,MAAM,QAAQ,IAAI;AAChC,KAAI,UAAU,GAAI,QAAO,MAAM,MAAM,QAAQ,EAAE,CAAC,QAAQ,uBAAuB,GAAG;CAClF,MAAM,aAAa,MAAM,QAAQ,uBAAuB,GAAG;AAI3D,KAAI,WAAW,WAAW,aAAa,CAAE,QAAO;CAChD,MAAM,QAAQ,WAAW,QAAQ,IAAI;AACrC,KAAI,UAAU,MAAM,CAAC,WAAW,WAAW,OAAO,CAChD,QAAO,WAAW,MAAM,QAAQ,EAAE;AAEpC,QAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"retention.js","names":["REASONING_RETENTION_DEFAULTS: Readonly<Record<ReasoningContract, ReasoningRetention>>"],"sources":["../../src/reasoning/retention.ts"],"sourcesContent":["/**\n * Reasoning-retention resolver picks the effective\n * {@link ReasoningRetention} value for a request given:\n *\n * 1. an explicit per-request `reasoningRetention` field (highest\n * precedence user override always wins),\n * 2. an instance-level override supplied to `createProvider({...})`,\n * and\n * 3. the auto-detected default derived from the adapter's declared\n * `capabilities.reasoningContract` (lowest precedence).\n *\n * @packageDocumentation\n */\n\nimport type { ReasoningContract, ReasoningRetention } from '@graphorin/core';\n\n/**\n * Map from `ReasoningContract` to the conservative default\n * `ReasoningRetention` value used when no override is supplied.\n *\n * @stable\n */\nexport const REASONING_RETENTION_DEFAULTS: Readonly<Record<ReasoningContract, ReasoningRetention>> =\n Object.freeze({\n hidden: 'strip',\n 'round-trip-required': 'pass-through-claude',\n optional: 'strip',\n });\n\n/**\n * Inputs to {@link resolveReasoningRetention}.\n *\n * @stable\n */\nexport interface ResolveReasoningRetentionInput {\n /** Explicit per-request override (highest precedence). */\n readonly requested?: ReasoningRetention;\n /** Instance-level override supplied to `createProvider({...})`. */\n readonly overridden?: ReasoningRetention;\n /** Adapter-declared capability (lowest precedence). */\n readonly contract?: ReasoningContract;\n}\n\n/**\n * Resolve the effective {@link ReasoningRetention} value for a single\n * request. The resolution is precedence-driven:\n *\n * 1. `requested` wins if defined.\n * 2. `overridden` wins next.\n * 3. The default for `contract` is used if the contract is known.\n * 4. `'strip'` is the conservative fallback when no input is supplied.\n *\n * @stable\n */\nexport function resolveReasoningRetention(\n input: ResolveReasoningRetentionInput,\n): ReasoningRetention {\n if (input.requested !== undefined) return input.requested;\n if (input.overridden !== undefined) return input.overridden;\n if (input.contract !== undefined) {\n return REASONING_RETENTION_DEFAULTS[input.contract];\n }\n return 'strip';\n}\n"],"mappings":";;;;;;;AAsBA,MAAaA,+BACX,OAAO,OAAO;CACZ,QAAQ;CACR,uBAAuB;CACvB,UAAU;CACX,CAAC;;;;;;;;;;;;AA2BJ,SAAgB,0BACd,OACoB;AACpB,KAAI,MAAM,cAAc,OAAW,QAAO,MAAM;AAChD,KAAI,MAAM,eAAe,OAAW,QAAO,MAAM;AACjD,KAAI,MAAM,aAAa,OACrB,QAAO,6BAA6B,MAAM;AAE5C,QAAO"}
1
+ {"version":3,"file":"retention.js","names":["REASONING_RETENTION_DEFAULTS: Readonly<Record<ReasoningContract, ReasoningRetention>>"],"sources":["../../src/reasoning/retention.ts"],"sourcesContent":["/**\n * Reasoning-retention resolver - picks the effective\n * {@link ReasoningRetention} value for a request given:\n *\n * 1. an explicit per-request `reasoningRetention` field (highest\n * precedence - user override always wins),\n * 2. an instance-level override supplied to `createProvider({...})`,\n * and\n * 3. the auto-detected default derived from the adapter's declared\n * `capabilities.reasoningContract` (lowest precedence).\n *\n * @packageDocumentation\n */\n\nimport type { ReasoningContract, ReasoningRetention } from '@graphorin/core';\n\n/**\n * Map from `ReasoningContract` to the conservative default\n * `ReasoningRetention` value used when no override is supplied.\n *\n * @stable\n */\nexport const REASONING_RETENTION_DEFAULTS: Readonly<Record<ReasoningContract, ReasoningRetention>> =\n Object.freeze({\n hidden: 'strip',\n 'round-trip-required': 'pass-through-claude',\n optional: 'strip',\n });\n\n/**\n * Inputs to {@link resolveReasoningRetention}.\n *\n * @stable\n */\nexport interface ResolveReasoningRetentionInput {\n /** Explicit per-request override (highest precedence). */\n readonly requested?: ReasoningRetention;\n /** Instance-level override supplied to `createProvider({...})`. */\n readonly overridden?: ReasoningRetention;\n /** Adapter-declared capability (lowest precedence). */\n readonly contract?: ReasoningContract;\n}\n\n/**\n * Resolve the effective {@link ReasoningRetention} value for a single\n * request. The resolution is precedence-driven:\n *\n * 1. `requested` wins if defined.\n * 2. `overridden` wins next.\n * 3. The default for `contract` is used if the contract is known.\n * 4. `'strip'` is the conservative fallback when no input is supplied.\n *\n * @stable\n */\nexport function resolveReasoningRetention(\n input: ResolveReasoningRetentionInput,\n): ReasoningRetention {\n if (input.requested !== undefined) return input.requested;\n if (input.overridden !== undefined) return input.overridden;\n if (input.contract !== undefined) {\n return REASONING_RETENTION_DEFAULTS[input.contract];\n }\n return 'strip';\n}\n"],"mappings":";;;;;;;AAsBA,MAAaA,+BACX,OAAO,OAAO;CACZ,QAAQ;CACR,uBAAuB;CACvB,UAAU;CACX,CAAC;;;;;;;;;;;;AA2BJ,SAAgB,0BACd,OACoB;AACpB,KAAI,MAAM,cAAc,OAAW,QAAO,MAAM;AAChD,KAAI,MAAM,eAAe,OAAW,QAAO,MAAM;AACjD,KAAI,MAAM,aAAa,OACrB,QAAO,6BAA6B,MAAM;AAE5C,QAAO"}
@@ -5,7 +5,7 @@ import { ToolDefinition } from "@graphorin/core";
5
5
  /**
6
6
  * Fold each tool's `examples` into its `description` and drop the structured
7
7
  * field. Non-destructive: a tool with no examples is returned by reference, and
8
- * the whole array is returned unchanged (same reference) when nothing folds so
8
+ * the whole array is returned unchanged (same reference) when nothing folds - so
9
9
  * callers can cheaply detect a no-op.
10
10
  */
11
11
  declare function foldToolExamples(tools: ReadonlyArray<ToolDefinition>): ReadonlyArray<ToolDefinition>;
@@ -9,7 +9,7 @@ function renderExamplesSection(examples) {
9
9
  /**
10
10
  * Fold each tool's `examples` into its `description` and drop the structured
11
11
  * field. Non-destructive: a tool with no examples is returned by reference, and
12
- * the whole array is returned unchanged (same reference) when nothing folds so
12
+ * the whole array is returned unchanged (same reference) when nothing folds - so
13
13
  * callers can cheaply detect a no-op.
14
14
  */
15
15
  function foldToolExamples(tools) {
@@ -1 +1 @@
1
- {"version":3,"file":"tool-examples.js","names":[],"sources":["../src/tool-examples.ts"],"sourcesContent":["/**\n * A1 (SOTA): fold a tool's worked `examples` into its model-facing description.\n *\n * `ToolDefinition` already carries `examples` on the wire (the agent projects\n * them from `Tool.examples`), and the contract notes implementations MAY fold\n * them into the description but no adapter did, so the model never saw them.\n * Anthropic reports complex-parameter accuracy jumps 72% → 90% with worked\n * examples in the tool text. This renders them deterministically (so the tool\n * spec stays prompt-cache-stable) and drops the now-redundant structured field.\n *\n * @packageDocumentation\n */\n\nimport type { ToolDefinition, ToolDefinitionExample } from '@graphorin/core';\n\n/** Render worked examples as a compact, deterministic text block. */\nfunction renderExamplesSection(examples: ReadonlyArray<ToolDefinitionExample>): string {\n const lines = examples.map((ex, i) => {\n const comment = ex.comment !== undefined ? ` // ${ex.comment}` : '';\n return `${i + 1}. input: ${JSON.stringify(ex.input)} -> output: ${JSON.stringify(ex.output)}${comment}`;\n });\n return `Examples:\\n${lines.join('\\n')}`;\n}\n\n/**\n * Fold each tool's `examples` into its `description` and drop the structured\n * field. Non-destructive: a tool with no examples is returned by reference, and\n * the whole array is returned unchanged (same reference) when nothing folds so\n * callers can cheaply detect a no-op.\n */\nexport function foldToolExamples(\n tools: ReadonlyArray<ToolDefinition>,\n): ReadonlyArray<ToolDefinition> {\n let changed = false;\n const out = tools.map((tool): ToolDefinition => {\n const examples = tool.examples;\n if (examples === undefined || examples.length === 0) return tool;\n changed = true;\n const section = renderExamplesSection(examples);\n const description =\n tool.description !== undefined && tool.description.length > 0\n ? `${tool.description}\\n\\n${section}`\n : section;\n // Rebuild without `examples` (now in the description text), preserving every\n // other field notably `outputSchema` (A5).\n return {\n name: tool.name,\n inputSchema: tool.inputSchema,\n description,\n ...(tool.outputSchema !== undefined ? { outputSchema: tool.outputSchema } : {}),\n };\n });\n return changed ? out : tools;\n}\n"],"mappings":";;AAgBA,SAAS,sBAAsB,UAAwD;AAKrF,QAAO,cAJO,SAAS,KAAK,IAAI,MAAM;EACpC,MAAM,UAAU,GAAG,YAAY,SAAY,QAAQ,GAAG,YAAY;AAClE,SAAO,GAAG,IAAI,EAAE,WAAW,KAAK,UAAU,GAAG,MAAM,CAAC,cAAc,KAAK,UAAU,GAAG,OAAO,GAAG;GAC9F,CACyB,KAAK,KAAK;;;;;;;;AASvC,SAAgB,iBACd,OAC+B;CAC/B,IAAI,UAAU;CACd,MAAM,MAAM,MAAM,KAAK,SAAyB;EAC9C,MAAM,WAAW,KAAK;AACtB,MAAI,aAAa,UAAa,SAAS,WAAW,EAAG,QAAO;AAC5D,YAAU;EACV,MAAM,UAAU,sBAAsB,SAAS;EAC/C,MAAM,cACJ,KAAK,gBAAgB,UAAa,KAAK,YAAY,SAAS,IACxD,GAAG,KAAK,YAAY,MAAM,YAC1B;AAGN,SAAO;GACL,MAAM,KAAK;GACX,aAAa,KAAK;GAClB;GACA,GAAI,KAAK,iBAAiB,SAAY,EAAE,cAAc,KAAK,cAAc,GAAG,EAAE;GAC/E;GACD;AACF,QAAO,UAAU,MAAM"}
1
+ {"version":3,"file":"tool-examples.js","names":[],"sources":["../src/tool-examples.ts"],"sourcesContent":["/**\n * A1 (SOTA): fold a tool's worked `examples` into its model-facing description.\n *\n * `ToolDefinition` already carries `examples` on the wire (the agent projects\n * them from `Tool.examples`), and the contract notes implementations MAY fold\n * them into the description - but no adapter did, so the model never saw them.\n * Anthropic reports complex-parameter accuracy jumps 72% → 90% with worked\n * examples in the tool text. This renders them deterministically (so the tool\n * spec stays prompt-cache-stable) and drops the now-redundant structured field.\n *\n * @packageDocumentation\n */\n\nimport type { ToolDefinition, ToolDefinitionExample } from '@graphorin/core';\n\n/** Render worked examples as a compact, deterministic text block. */\nfunction renderExamplesSection(examples: ReadonlyArray<ToolDefinitionExample>): string {\n const lines = examples.map((ex, i) => {\n const comment = ex.comment !== undefined ? ` // ${ex.comment}` : '';\n return `${i + 1}. input: ${JSON.stringify(ex.input)} -> output: ${JSON.stringify(ex.output)}${comment}`;\n });\n return `Examples:\\n${lines.join('\\n')}`;\n}\n\n/**\n * Fold each tool's `examples` into its `description` and drop the structured\n * field. Non-destructive: a tool with no examples is returned by reference, and\n * the whole array is returned unchanged (same reference) when nothing folds - so\n * callers can cheaply detect a no-op.\n */\nexport function foldToolExamples(\n tools: ReadonlyArray<ToolDefinition>,\n): ReadonlyArray<ToolDefinition> {\n let changed = false;\n const out = tools.map((tool): ToolDefinition => {\n const examples = tool.examples;\n if (examples === undefined || examples.length === 0) return tool;\n changed = true;\n const section = renderExamplesSection(examples);\n const description =\n tool.description !== undefined && tool.description.length > 0\n ? `${tool.description}\\n\\n${section}`\n : section;\n // Rebuild without `examples` (now in the description text), preserving every\n // other field - notably `outputSchema` (A5).\n return {\n name: tool.name,\n inputSchema: tool.inputSchema,\n description,\n ...(tool.outputSchema !== undefined ? { outputSchema: tool.outputSchema } : {}),\n };\n });\n return changed ? out : tools;\n}\n"],"mappings":";;AAgBA,SAAS,sBAAsB,UAAwD;AAKrF,QAAO,cAJO,SAAS,KAAK,IAAI,MAAM;EACpC,MAAM,UAAU,GAAG,YAAY,SAAY,QAAQ,GAAG,YAAY;AAClE,SAAO,GAAG,IAAI,EAAE,WAAW,KAAK,UAAU,GAAG,MAAM,CAAC,cAAc,KAAK,UAAU,GAAG,OAAO,GAAG;GAC9F,CACyB,KAAK,KAAK;;;;;;;;AASvC,SAAgB,iBACd,OAC+B;CAC/B,IAAI,UAAU;CACd,MAAM,MAAM,MAAM,KAAK,SAAyB;EAC9C,MAAM,WAAW,KAAK;AACtB,MAAI,aAAa,UAAa,SAAS,WAAW,EAAG,QAAO;AAC5D,YAAU;EACV,MAAM,UAAU,sBAAsB,SAAS;EAC/C,MAAM,cACJ,KAAK,gBAAgB,UAAa,KAAK,YAAY,SAAS,IACxD,GAAG,KAAK,YAAY,MAAM,YAC1B;AAGN,SAAO;GACL,MAAM,KAAK;GACX,aAAa,KAAK;GAClB;GACA,GAAI,KAAK,iBAAiB,SAAY,EAAE,cAAc,KAAK,cAAc,GAAG,EAAE;GAC/E;GACD;AACF,QAAO,UAAU,MAAM"}
@@ -11,7 +11,7 @@ import { LocalProviderTrust, Sensitivity } from "@graphorin/core";
11
11
  */
12
12
  interface LocalProviderClassification {
13
13
  readonly trust: LocalProviderTrust;
14
- /** One-line reason `'loopback IP 127.0.0.1'`, `'public IP 5.6.7.8 over HTTPS'`, ... */
14
+ /** One-line reason - `'loopback IP 127.0.0.1'`, `'public IP 5.6.7.8 over HTTPS'`, ... */
15
15
  readonly reason: string;
16
16
  /** Default `acceptsSensitivity` for this trust class. */
17
17
  readonly acceptsSensitivity: ReadonlyArray<Sensitivity>;
@@ -1 +1 @@
1
- {"version":3,"file":"classify-local-provider.js","names":["SENSITIVITY_DEFAULTS_PER_TRUST: Readonly<\n Record<LocalProviderTrust, ReadonlyArray<Sensitivity>>\n>","url: URL","PERMANENT_LOOPBACK_CLASSIFICATION: LocalProviderClassification"],"sources":["../../src/trust/classify-local-provider.ts"],"sourcesContent":["/**\n * Shared trust classifier for every `baseUrl`-driven local-LLM\n * adapter. Emits one of four discriminant values per\n * {@link LocalProviderTrust}:\n *\n * - `'loopback'` — the URL points at the same host as the\n * running process (`localhost`, `127.0.0.0/8`, `::1`, or a\n * `unix:///path` socket).\n * - `'private'` — RFC 1918 / RFC 6598 / link-local /\n * multicast-DNS-style hostname.\n * - `'public-tls'` — public host AND `https://`.\n * - `'public-cleartext'` — public host AND `http://`.\n *\n * The dispatcher is a pure function: it sees the URL string and\n * nothing else. DNS resolution is deferred to the adapter layer\n * (cross-cut DEC-149 § DNS-rebinding mitigation), where the resolved\n * IP is re-classified on TTL expiry and the lowest-trust value wins.\n *\n * @packageDocumentation\n */\n\nimport type { LocalProviderTrust, Sensitivity } from '@graphorin/core';\n\n/**\n * Result of {@link classifyLocalProvider}. Carries both the trust\n * class and a short human-readable reason for the WARN log produced\n * by the consuming adapter.\n *\n * @stable\n */\nexport interface LocalProviderClassification {\n readonly trust: LocalProviderTrust;\n /** One-line reason — `'loopback IP 127.0.0.1'`, `'public IP 5.6.7.8 over HTTPS'`, ... */\n readonly reason: string;\n /** Default `acceptsSensitivity` for this trust class. */\n readonly acceptsSensitivity: ReadonlyArray<Sensitivity>;\n}\n\n/**\n * Per-tier default sensitivity envelope. Lifted to a constant so\n * downstream code (and tests) can import it without re-deriving the\n * matrix.\n *\n * @stable\n */\nexport const SENSITIVITY_DEFAULTS_PER_TRUST: Readonly<\n Record<LocalProviderTrust, ReadonlyArray<Sensitivity>>\n> = Object.freeze({\n loopback: Object.freeze(['public', 'internal', 'secret'] as const),\n private: Object.freeze(['public', 'internal'] as const),\n 'public-tls': Object.freeze(['public'] as const),\n 'public-cleartext': Object.freeze([] as const),\n});\n\n/**\n * Classify a URL string into one of the four {@link LocalProviderTrust}\n * tiers. Throws `TypeError` if the URL is unparseable so adapters\n * fail fast at construction time (programming error; not a runtime\n * fault).\n *\n * @stable\n */\nexport function classifyLocalProvider(baseUrl: string): LocalProviderClassification {\n if (typeof baseUrl !== 'string' || baseUrl.length === 0) {\n throw new TypeError('classifyLocalProvider: baseUrl must be a non-empty string.');\n }\n\n if (baseUrl.startsWith('unix://') || baseUrl.startsWith('unix:/')) {\n return {\n trust: 'loopback',\n reason: `unix domain socket (${baseUrl})`,\n acceptsSensitivity: SENSITIVITY_DEFAULTS_PER_TRUST.loopback,\n };\n }\n\n let url: URL;\n try {\n url = new URL(baseUrl);\n } catch (cause) {\n throw new TypeError(`classifyLocalProvider: baseUrl '${baseUrl}' is not a valid URL.`, {\n cause,\n });\n }\n\n const protocol = url.protocol;\n const hostname = url.hostname;\n if (protocol !== 'http:' && protocol !== 'https:') {\n // Custom protocols (e.g. ws://) are out of scope for the four\n // bundled adapters; treat as private so a downstream WARN fires.\n return {\n trust: 'private',\n reason: `non-HTTP protocol '${protocol}'`,\n acceptsSensitivity: SENSITIVITY_DEFAULTS_PER_TRUST.private,\n };\n }\n\n const ipv4 = parseIpv4(hostname);\n if (ipv4 !== null) {\n if (isLoopbackIpv4(ipv4)) {\n return {\n trust: 'loopback',\n reason: `loopback IPv4 ${hostname}`,\n acceptsSensitivity: SENSITIVITY_DEFAULTS_PER_TRUST.loopback,\n };\n }\n if (isPrivateIpv4(ipv4)) {\n return {\n trust: 'private',\n reason: `private IPv4 ${hostname}`,\n acceptsSensitivity: SENSITIVITY_DEFAULTS_PER_TRUST.private,\n };\n }\n return classifyPublic(protocol, `public IPv4 ${hostname}`);\n }\n\n if (isIpv6(hostname)) {\n if (isLoopbackIpv6(hostname)) {\n return {\n trust: 'loopback',\n reason: `loopback IPv6 ${hostname}`,\n acceptsSensitivity: SENSITIVITY_DEFAULTS_PER_TRUST.loopback,\n };\n }\n if (isPrivateIpv6(hostname)) {\n return {\n trust: 'private',\n reason: `private IPv6 ${hostname}`,\n acceptsSensitivity: SENSITIVITY_DEFAULTS_PER_TRUST.private,\n };\n }\n return classifyPublic(protocol, `public IPv6 ${hostname}`);\n }\n\n if (isLoopbackHostname(hostname)) {\n return {\n trust: 'loopback',\n reason: `loopback hostname '${hostname}'`,\n acceptsSensitivity: SENSITIVITY_DEFAULTS_PER_TRUST.loopback,\n };\n }\n if (isPrivateHostnameSuffix(hostname)) {\n return {\n trust: 'private',\n reason: `private hostname suffix '${hostname}'`,\n acceptsSensitivity: SENSITIVITY_DEFAULTS_PER_TRUST.private,\n };\n }\n\n return classifyPublic(protocol, `public hostname '${hostname}'`);\n}\n\nfunction classifyPublic(protocol: string, reason: string): LocalProviderClassification {\n if (protocol === 'https:') {\n return {\n trust: 'public-tls',\n reason: `${reason} over HTTPS`,\n acceptsSensitivity: SENSITIVITY_DEFAULTS_PER_TRUST['public-tls'],\n };\n }\n return {\n trust: 'public-cleartext',\n reason: `${reason} over HTTP`,\n acceptsSensitivity: SENSITIVITY_DEFAULTS_PER_TRUST['public-cleartext'],\n };\n}\n\nfunction parseIpv4(hostname: string): [number, number, number, number] | null {\n const match = /^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/.exec(hostname);\n if (match === null) return null;\n const a = Number(match[1]);\n const b = Number(match[2]);\n const c = Number(match[3]);\n const d = Number(match[4]);\n if (a > 255 || b > 255 || c > 255 || d > 255) return null;\n return [a, b, c, d];\n}\n\nfunction isLoopbackIpv4(ip: readonly [number, number, number, number]): boolean {\n return ip[0] === 127;\n}\n\nfunction isPrivateIpv4(ip: readonly [number, number, number, number]): boolean {\n // RFC 1918\n if (ip[0] === 10) return true;\n if (ip[0] === 192 && ip[1] === 168) return true;\n if (ip[0] === 172 && ip[1] >= 16 && ip[1] <= 31) return true;\n // RFC 6598 (CGNAT — Tailscale-friendly)\n if (ip[0] === 100 && ip[1] >= 64 && ip[1] <= 127) return true;\n // Link-local\n if (ip[0] === 169 && ip[1] === 254) return true;\n return false;\n}\n\nfunction isIpv6(hostname: string): boolean {\n const stripped =\n hostname.startsWith('[') && hostname.endsWith(']') ? hostname.slice(1, -1) : hostname;\n return stripped.includes(':');\n}\n\nfunction normaliseIpv6(hostname: string): string {\n return hostname.startsWith('[') && hostname.endsWith(']') ? hostname.slice(1, -1) : hostname;\n}\n\nfunction isLoopbackIpv6(hostname: string): boolean {\n const norm = normaliseIpv6(hostname).toLowerCase();\n return norm === '::1' || norm === '0:0:0:0:0:0:0:1';\n}\n\nfunction isPrivateIpv6(hostname: string): boolean {\n const norm = normaliseIpv6(hostname).toLowerCase();\n // Link-local fe80::/10\n if (\n norm.startsWith('fe80:') ||\n norm.startsWith('fe8') ||\n norm.startsWith('fe9') ||\n norm.startsWith('fea') ||\n norm.startsWith('feb')\n ) {\n return true;\n }\n // Unique local fc00::/7 (covers fc:: and fd::)\n if (norm.startsWith('fc') || norm.startsWith('fd')) return true;\n return false;\n}\n\nfunction isLoopbackHostname(hostname: string): boolean {\n const lower = hostname.toLowerCase();\n return lower === 'localhost' || lower === 'localhost.';\n}\n\nfunction isPrivateHostnameSuffix(hostname: string): boolean {\n const lower = hostname.toLowerCase();\n return (\n lower.endsWith('.local') ||\n lower.endsWith('.lan') ||\n lower.endsWith('.internal') ||\n lower.endsWith('.home.arpa') ||\n lower.endsWith('.intranet')\n );\n}\n\n/**\n * Permanent loopback classification used by in-process adapters\n * (e.g. the `llamaCppNodeAdapter` companion package). Adapters that\n * have no `baseUrl` declare this directly to make the source-of-truth\n * symmetry obvious.\n *\n * @stable\n */\nexport const PERMANENT_LOOPBACK_CLASSIFICATION: LocalProviderClassification = Object.freeze({\n trust: 'loopback',\n reason: 'in-process adapter (no baseUrl)',\n acceptsSensitivity: SENSITIVITY_DEFAULTS_PER_TRUST.loopback,\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"classify-local-provider.js","names":["SENSITIVITY_DEFAULTS_PER_TRUST: Readonly<\n Record<LocalProviderTrust, ReadonlyArray<Sensitivity>>\n>","url: URL","PERMANENT_LOOPBACK_CLASSIFICATION: LocalProviderClassification"],"sources":["../../src/trust/classify-local-provider.ts"],"sourcesContent":["/**\n * Shared trust classifier for every `baseUrl`-driven local-LLM\n * adapter. Emits one of four discriminant values per\n * {@link LocalProviderTrust}:\n *\n * - `'loopback'` - the URL points at the same host as the\n * running process (`localhost`, `127.0.0.0/8`, `::1`, or a\n * `unix:///path` socket).\n * - `'private'` - RFC 1918 / RFC 6598 / link-local /\n * multicast-DNS-style hostname.\n * - `'public-tls'` - public host AND `https://`.\n * - `'public-cleartext'` - public host AND `http://`.\n *\n * The dispatcher is a pure function: it sees the URL string and\n * nothing else. DNS resolution is deferred to the adapter layer\n * (cross-cut DEC-149 § DNS-rebinding mitigation), where the resolved\n * IP is re-classified on TTL expiry and the lowest-trust value wins.\n *\n * @packageDocumentation\n */\n\nimport type { LocalProviderTrust, Sensitivity } from '@graphorin/core';\n\n/**\n * Result of {@link classifyLocalProvider}. Carries both the trust\n * class and a short human-readable reason for the WARN log produced\n * by the consuming adapter.\n *\n * @stable\n */\nexport interface LocalProviderClassification {\n readonly trust: LocalProviderTrust;\n /** One-line reason - `'loopback IP 127.0.0.1'`, `'public IP 5.6.7.8 over HTTPS'`, ... */\n readonly reason: string;\n /** Default `acceptsSensitivity` for this trust class. */\n readonly acceptsSensitivity: ReadonlyArray<Sensitivity>;\n}\n\n/**\n * Per-tier default sensitivity envelope. Lifted to a constant so\n * downstream code (and tests) can import it without re-deriving the\n * matrix.\n *\n * @stable\n */\nexport const SENSITIVITY_DEFAULTS_PER_TRUST: Readonly<\n Record<LocalProviderTrust, ReadonlyArray<Sensitivity>>\n> = Object.freeze({\n loopback: Object.freeze(['public', 'internal', 'secret'] as const),\n private: Object.freeze(['public', 'internal'] as const),\n 'public-tls': Object.freeze(['public'] as const),\n 'public-cleartext': Object.freeze([] as const),\n});\n\n/**\n * Classify a URL string into one of the four {@link LocalProviderTrust}\n * tiers. Throws `TypeError` if the URL is unparseable so adapters\n * fail fast at construction time (programming error; not a runtime\n * fault).\n *\n * @stable\n */\nexport function classifyLocalProvider(baseUrl: string): LocalProviderClassification {\n if (typeof baseUrl !== 'string' || baseUrl.length === 0) {\n throw new TypeError('classifyLocalProvider: baseUrl must be a non-empty string.');\n }\n\n if (baseUrl.startsWith('unix://') || baseUrl.startsWith('unix:/')) {\n return {\n trust: 'loopback',\n reason: `unix domain socket (${baseUrl})`,\n acceptsSensitivity: SENSITIVITY_DEFAULTS_PER_TRUST.loopback,\n };\n }\n\n let url: URL;\n try {\n url = new URL(baseUrl);\n } catch (cause) {\n throw new TypeError(`classifyLocalProvider: baseUrl '${baseUrl}' is not a valid URL.`, {\n cause,\n });\n }\n\n const protocol = url.protocol;\n const hostname = url.hostname;\n if (protocol !== 'http:' && protocol !== 'https:') {\n // Custom protocols (e.g. ws://) are out of scope for the four\n // bundled adapters; treat as private so a downstream WARN fires.\n return {\n trust: 'private',\n reason: `non-HTTP protocol '${protocol}'`,\n acceptsSensitivity: SENSITIVITY_DEFAULTS_PER_TRUST.private,\n };\n }\n\n const ipv4 = parseIpv4(hostname);\n if (ipv4 !== null) {\n if (isLoopbackIpv4(ipv4)) {\n return {\n trust: 'loopback',\n reason: `loopback IPv4 ${hostname}`,\n acceptsSensitivity: SENSITIVITY_DEFAULTS_PER_TRUST.loopback,\n };\n }\n if (isPrivateIpv4(ipv4)) {\n return {\n trust: 'private',\n reason: `private IPv4 ${hostname}`,\n acceptsSensitivity: SENSITIVITY_DEFAULTS_PER_TRUST.private,\n };\n }\n return classifyPublic(protocol, `public IPv4 ${hostname}`);\n }\n\n if (isIpv6(hostname)) {\n if (isLoopbackIpv6(hostname)) {\n return {\n trust: 'loopback',\n reason: `loopback IPv6 ${hostname}`,\n acceptsSensitivity: SENSITIVITY_DEFAULTS_PER_TRUST.loopback,\n };\n }\n if (isPrivateIpv6(hostname)) {\n return {\n trust: 'private',\n reason: `private IPv6 ${hostname}`,\n acceptsSensitivity: SENSITIVITY_DEFAULTS_PER_TRUST.private,\n };\n }\n return classifyPublic(protocol, `public IPv6 ${hostname}`);\n }\n\n if (isLoopbackHostname(hostname)) {\n return {\n trust: 'loopback',\n reason: `loopback hostname '${hostname}'`,\n acceptsSensitivity: SENSITIVITY_DEFAULTS_PER_TRUST.loopback,\n };\n }\n if (isPrivateHostnameSuffix(hostname)) {\n return {\n trust: 'private',\n reason: `private hostname suffix '${hostname}'`,\n acceptsSensitivity: SENSITIVITY_DEFAULTS_PER_TRUST.private,\n };\n }\n\n return classifyPublic(protocol, `public hostname '${hostname}'`);\n}\n\nfunction classifyPublic(protocol: string, reason: string): LocalProviderClassification {\n if (protocol === 'https:') {\n return {\n trust: 'public-tls',\n reason: `${reason} over HTTPS`,\n acceptsSensitivity: SENSITIVITY_DEFAULTS_PER_TRUST['public-tls'],\n };\n }\n return {\n trust: 'public-cleartext',\n reason: `${reason} over HTTP`,\n acceptsSensitivity: SENSITIVITY_DEFAULTS_PER_TRUST['public-cleartext'],\n };\n}\n\nfunction parseIpv4(hostname: string): [number, number, number, number] | null {\n const match = /^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/.exec(hostname);\n if (match === null) return null;\n const a = Number(match[1]);\n const b = Number(match[2]);\n const c = Number(match[3]);\n const d = Number(match[4]);\n if (a > 255 || b > 255 || c > 255 || d > 255) return null;\n return [a, b, c, d];\n}\n\nfunction isLoopbackIpv4(ip: readonly [number, number, number, number]): boolean {\n return ip[0] === 127;\n}\n\nfunction isPrivateIpv4(ip: readonly [number, number, number, number]): boolean {\n // RFC 1918\n if (ip[0] === 10) return true;\n if (ip[0] === 192 && ip[1] === 168) return true;\n if (ip[0] === 172 && ip[1] >= 16 && ip[1] <= 31) return true;\n // RFC 6598 (CGNAT - Tailscale-friendly)\n if (ip[0] === 100 && ip[1] >= 64 && ip[1] <= 127) return true;\n // Link-local\n if (ip[0] === 169 && ip[1] === 254) return true;\n return false;\n}\n\nfunction isIpv6(hostname: string): boolean {\n const stripped =\n hostname.startsWith('[') && hostname.endsWith(']') ? hostname.slice(1, -1) : hostname;\n return stripped.includes(':');\n}\n\nfunction normaliseIpv6(hostname: string): string {\n return hostname.startsWith('[') && hostname.endsWith(']') ? hostname.slice(1, -1) : hostname;\n}\n\nfunction isLoopbackIpv6(hostname: string): boolean {\n const norm = normaliseIpv6(hostname).toLowerCase();\n return norm === '::1' || norm === '0:0:0:0:0:0:0:1';\n}\n\nfunction isPrivateIpv6(hostname: string): boolean {\n const norm = normaliseIpv6(hostname).toLowerCase();\n // Link-local fe80::/10\n if (\n norm.startsWith('fe80:') ||\n norm.startsWith('fe8') ||\n norm.startsWith('fe9') ||\n norm.startsWith('fea') ||\n norm.startsWith('feb')\n ) {\n return true;\n }\n // Unique local fc00::/7 (covers fc:: and fd::)\n if (norm.startsWith('fc') || norm.startsWith('fd')) return true;\n return false;\n}\n\nfunction isLoopbackHostname(hostname: string): boolean {\n const lower = hostname.toLowerCase();\n return lower === 'localhost' || lower === 'localhost.';\n}\n\nfunction isPrivateHostnameSuffix(hostname: string): boolean {\n const lower = hostname.toLowerCase();\n return (\n lower.endsWith('.local') ||\n lower.endsWith('.lan') ||\n lower.endsWith('.internal') ||\n lower.endsWith('.home.arpa') ||\n lower.endsWith('.intranet')\n );\n}\n\n/**\n * Permanent loopback classification used by in-process adapters\n * (e.g. the `llamaCppNodeAdapter` companion package). Adapters that\n * have no `baseUrl` declare this directly to make the source-of-truth\n * symmetry obvious.\n *\n * @stable\n */\nexport const PERMANENT_LOOPBACK_CLASSIFICATION: LocalProviderClassification = Object.freeze({\n trust: 'loopback',\n reason: 'in-process adapter (no baseUrl)',\n acceptsSensitivity: SENSITIVITY_DEFAULTS_PER_TRUST.loopback,\n});\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphorin/provider",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "description": "Vendor-neutral LLM provider layer for the Graphorin framework. Ships the canonical Provider interface, the Vercel AI SDK adapter (default cloud path), three local-LLM adapters (direct Ollama HTTP, llama-server HTTP, and a generic OpenAI-compatible adapter for LMStudio / LocalAI / vLLM / Together-style endpoints), the shared LocalProviderTrust classifier (loopback / private / public-tls / public-cleartext), the canonical-order middleware composer (withTracing / withRetry / withRateLimit / withCostLimit / withCostTracking / withFallback / withRedaction), the pluggable TokenCounter dispatcher (js-tiktoken default plus per-vendor native counters and a heuristic fallback), the per-provider model-tier auto-classifier (fast / balanced / smart), and the production startup hook that fails fast when withRedaction is missing.",
5
5
  "license": "MIT",
6
6
  "author": "Oleksiy Stepurenko",
@@ -95,8 +95,8 @@
95
95
  "LICENSE"
96
96
  ],
97
97
  "dependencies": {
98
- "@graphorin/core": "0.5.0",
99
- "@graphorin/observability": "0.5.0"
98
+ "@graphorin/core": "0.6.1",
99
+ "@graphorin/observability": "0.6.1"
100
100
  },
101
101
  "peerDependencies": {
102
102
  "ai": "^7.0.0-beta.76",
@@ -115,8 +115,10 @@
115
115
  "provenance": true
116
116
  },
117
117
  "devDependencies": {
118
+ "ai": "^7.0.15",
118
119
  "fast-check": "^3.23.0",
119
- "js-tiktoken": "^1.0.21"
120
+ "js-tiktoken": "^1.0.21",
121
+ "@graphorin/pricing": "0.6.1"
120
122
  },
121
123
  "scripts": {
122
124
  "build": "tsdown",