{"version":3,"file":"token_encoding_requires_context_window.cjs","names":[],"sources":["../../../src/eslint/rules/token_encoding_requires_context_window.ts"],"sourcesContent":["/**\n * @module @nhtio/adk/eslint/rules/token_encoding_requires_context_window\n *\n * Flags a Chat Completions adapter options literal that sets a non-null `tokenEncoding` but omits\n * `contextWindow`.\n *\n * Why: the OpenAI / WebLLM Chat Completions batteries only perform local token counting + overflow\n * protection when BOTH `tokenEncoding` (how to count) and `contextWindow` (the budget to count\n * against) are provided. Setting `tokenEncoding` alone is a footgun — the encoding is configured but\n * there is no ceiling to enforce, so the overflow guard silently never runs. The adapters throw on\n * this cross-field mismatch at iteration time; this rule surfaces it at the construction site.\n *\n * Detects `new OpenAIChatCompletionsAdapter({ … })` / `new WebLLMChatCompletionsAdapter({ … })`\n * (and any `*ChatCompletionsAdapter`) where `tokenEncoding` is present and not `null`/`undefined`\n * while `contextWindow` is absent.\n *\n * Opt-out:\n *   // eslint-disable-next-line adk/token-encoding-requires-context-window -- <reason>\n */\n\nimport { createRule } from './common'\n\nimport type { TSESTree } from '@typescript-eslint/utils'\n\nconst literalKey = (prop: TSESTree.Property): string | undefined => {\n  if (prop.computed) return undefined\n  if (prop.key.type === 'Identifier') return prop.key.name\n  if (prop.key.type === 'Literal' && typeof prop.key.value === 'string') return prop.key.value\n  return undefined\n}\n\nconst isNullOrUndefined = (node: TSESTree.Node): boolean =>\n  (node.type === 'Literal' && node.value === null) ||\n  (node.type === 'Identifier' && node.name === 'undefined')\n\n/** ESLint rule: flags setting a tokenEncoding without also setting a contextWindow. */\nconst tokenEncodingRequiresContextWindowRule = createRule({\n  name: 'token-encoding-requires-context-window',\n  meta: {\n    type: 'problem',\n    docs: {\n      description:\n        'A Chat Completions adapter configured with a non-null `tokenEncoding` must also set `contextWindow`; otherwise token counting is configured but the overflow guard has no budget and never runs. The adapters enforce this at runtime; this surfaces it at the construction site.',\n    },\n    schema: [],\n    messages: {\n      requireContextWindow:\n        '`tokenEncoding` is set but `contextWindow` is missing — the adapter counts tokens with no budget to enforce, so the context-overflow guard never runs. Add `contextWindow`, or drop `tokenEncoding`. Opt out with an eslint-disable-next-line adk/token-encoding-requires-context-window comment + reason.',\n    },\n  },\n  defaultOptions: [],\n  create(context) {\n    return {\n      NewExpression(node: TSESTree.NewExpression) {\n        if (node.callee.type !== 'Identifier') return\n        if (!/ChatCompletionsAdapter$/.test(node.callee.name)) return\n        const arg = node.arguments[0]\n        if (!arg || arg.type !== 'ObjectExpression') return\n\n        let tokenEncodingProp: TSESTree.Property | undefined\n        let hasContextWindow = false\n        let hasSpread = false\n        for (const p of arg.properties) {\n          if (p.type === 'SpreadElement') {\n            hasSpread = true\n            continue\n          }\n          const key = literalKey(p)\n          if (key === 'tokenEncoding' && !isNullOrUndefined(p.value)) tokenEncodingProp = p\n          else if (key === 'contextWindow' && !isNullOrUndefined(p.value)) hasContextWindow = true\n        }\n\n        // A spread could supply contextWindow we can't see — stay conservative, don't flag.\n        if (tokenEncodingProp && !hasContextWindow && !hasSpread) {\n          context.report({ node: tokenEncodingProp, messageId: 'requireContextWindow' })\n        }\n      },\n    }\n  },\n})\n\nexport default tokenEncodingRequiresContextWindowRule\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAwBA,IAAM,cAAc,SAAgD;CAClE,IAAI,KAAK,UAAU,OAAO,KAAA;CAC1B,IAAI,KAAK,IAAI,SAAS,cAAc,OAAO,KAAK,IAAI;CACpD,IAAI,KAAK,IAAI,SAAS,aAAa,OAAO,KAAK,IAAI,UAAU,UAAU,OAAO,KAAK,IAAI;AAEzF;AAEA,IAAM,qBAAqB,SACxB,KAAK,SAAS,aAAa,KAAK,UAAU,QAC1C,KAAK,SAAS,gBAAgB,KAAK,SAAS;;AAG/C,IAAM,yCAAyC,eAAA,WAAW;CACxD,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,oRACJ;EACA,QAAQ,CAAC;EACT,UAAU,EACR,sBACE,6SACJ;CACF;CACA,gBAAgB,CAAC;CACjB,OAAO,SAAS;EACd,OAAO,EACL,cAAc,MAA8B;GAC1C,IAAI,KAAK,OAAO,SAAS,cAAc;GACvC,IAAI,CAAC,0BAA0B,KAAK,KAAK,OAAO,IAAI,GAAG;GACvD,MAAM,MAAM,KAAK,UAAU;GAC3B,IAAI,CAAC,OAAO,IAAI,SAAS,oBAAoB;GAE7C,IAAI;GACJ,IAAI,mBAAmB;GACvB,IAAI,YAAY;GAChB,KAAK,MAAM,KAAK,IAAI,YAAY;IAC9B,IAAI,EAAE,SAAS,iBAAiB;KAC9B,YAAY;KACZ;IACF;IACA,MAAM,MAAM,WAAW,CAAC;IACxB,IAAI,QAAQ,mBAAmB,CAAC,kBAAkB,EAAE,KAAK,GAAG,oBAAoB;SAC3E,IAAI,QAAQ,mBAAmB,CAAC,kBAAkB,EAAE,KAAK,GAAG,mBAAmB;GACtF;GAGA,IAAI,qBAAqB,CAAC,oBAAoB,CAAC,WAC7C,QAAQ,OAAO;IAAE,MAAM;IAAmB,WAAW;GAAuB,CAAC;EAEjF,EACF;CACF;AACF,CAAC"}