@nhtio/adk 1.20260607.0 → 1.20260607.2

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 (44) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/common-BUIjZ6LV.mjs +119 -0
  3. package/common-BUIjZ6LV.mjs.map +1 -0
  4. package/common-DeNipTQI.js +148 -0
  5. package/common-DeNipTQI.js.map +1 -0
  6. package/eslint/index.d.ts +106 -0
  7. package/eslint/rules/artifact_tool_forbids_artifact_constructor.cjs +57 -0
  8. package/eslint/rules/artifact_tool_forbids_artifact_constructor.cjs.map +1 -0
  9. package/eslint/rules/artifact_tool_forbids_artifact_constructor.d.ts +21 -0
  10. package/eslint/rules/artifact_tool_forbids_artifact_constructor.mjs +52 -0
  11. package/eslint/rules/artifact_tool_forbids_artifact_constructor.mjs.map +1 -0
  12. package/eslint/rules/common.d.ts +41 -0
  13. package/eslint/rules/index.d.ts +12 -0
  14. package/eslint/rules/no_model_in_tool_handler.cjs +64 -0
  15. package/eslint/rules/no_model_in_tool_handler.cjs.map +1 -0
  16. package/eslint/rules/no_model_in_tool_handler.d.ts +26 -0
  17. package/eslint/rules/no_model_in_tool_handler.mjs +59 -0
  18. package/eslint/rules/no_model_in_tool_handler.mjs.map +1 -0
  19. package/eslint/rules/require_validator_any_required.cjs +100 -0
  20. package/eslint/rules/require_validator_any_required.cjs.map +1 -0
  21. package/eslint/rules/require_validator_any_required.d.ts +31 -0
  22. package/eslint/rules/require_validator_any_required.mjs +95 -0
  23. package/eslint/rules/require_validator_any_required.mjs.map +1 -0
  24. package/eslint/rules/thought_payload_requires_replay_tag.cjs +71 -0
  25. package/eslint/rules/thought_payload_requires_replay_tag.cjs.map +1 -0
  26. package/eslint/rules/thought_payload_requires_replay_tag.d.ts +26 -0
  27. package/eslint/rules/thought_payload_requires_replay_tag.mjs +66 -0
  28. package/eslint/rules/thought_payload_requires_replay_tag.mjs.map +1 -0
  29. package/eslint/rules/token_encoding_requires_context_window.cjs +70 -0
  30. package/eslint/rules/token_encoding_requires_context_window.cjs.map +1 -0
  31. package/eslint/rules/token_encoding_requires_context_window.d.ts +24 -0
  32. package/eslint/rules/token_encoding_requires_context_window.mjs +65 -0
  33. package/eslint/rules/token_encoding_requires_context_window.mjs.map +1 -0
  34. package/eslint/rules.cjs +12 -0
  35. package/eslint/rules.mjs +6 -0
  36. package/eslint.cjs +82 -0
  37. package/eslint.cjs.map +1 -0
  38. package/eslint.mjs +74 -0
  39. package/eslint.mjs.map +1 -0
  40. package/index.cjs +1 -1
  41. package/index.mjs +1 -1
  42. package/mcp/adk-docs-corpus.json +1 -1
  43. package/package.json +230 -187
  44. package/skills/adk-assembly/SKILL.md +2 -2
package/CHANGELOG.md CHANGED
@@ -9,6 +9,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ### Added
11
11
 
12
+ - **SoDK — a mental-model doc that teaches the loop in human terms.** A new page,
13
+ `docs/sodk.md` ("Society Development Kit"), retells [How agents work](https://adk.nht.io/how-agents-work)
14
+ and [What ADK is](https://adk.nht.io/what-adk-is) with exactly one noun swapped: where ADK says
15
+ *model*, SoDK says *person*. It is a teaching device for the reader who can't yet see why an agent
16
+ is the loop, not the LLM — role↔agent, task↔turn, briefing↔context, request↔tool, process↔middleware,
17
+ "say where things get filed"↔the required storage callbacks. The human-facing prose plays it
18
+ straight; the `<llm-only>` block names the metaphor outright and carries the full 1:1 map, so an
19
+ agent answering a question can explain a concept *through* the framing or translate either way. Wired
20
+ into the sidebar and home listing, and cross-linked from both source docs. Docs only — no code, types,
21
+ or package surface change.
22
+
23
+ - **Importable ESLint plugin (`@nhtio/adk/eslint`).** The harness's documented contracts are now
24
+ machine-checkable: a flat-config plugin that flags footguns the TypeScript compiler cannot see
25
+ because they live in runtime validators or conventions, not types. Five rules ship —
26
+ `require-validator-any-required` (a `validator.any()` chain with no explicit
27
+ `.required()`/`.optional()`/`.default()`/`.forbidden()` silently admits null/undefined),
28
+ `thought-payload-requires-replay-tag` (a `Thought` with a vendor `payload` but no
29
+ `replayCompatibility` can never be safely replayed), `token-encoding-requires-context-window` (a
30
+ Chat Completions adapter that counts tokens with no budget never runs its overflow guard),
31
+ `artifact-tool-forbids-artifact-constructor` (an `ArtifactTool` that wraps another artifact
32
+ recurses forever), and `no-model-in-tool-handler` (a model call inside a tool handler hides an
33
+ unmanaged dispatch — unless the handler runs its own scoped sub-agent via `new TurnRunner(...)` or
34
+ `DispatchRunner.dispatch(...)`). Import the assembled plugin
35
+ from `@nhtio/adk/eslint` (or `adk.configs.recommended` for all five), or individual rules from
36
+ `@nhtio/adk/eslint/rules/<name>`. `eslint` and `@typescript-eslint/utils` are **optional peers** —
37
+ installed only by consumers who lint with the plugin. Rules are report-only with inline
38
+ `eslint-disable` carve-outs. See the new **Developer Tools** docs section, which also now houses
39
+ the ADK Assembly MCP guide.
40
+
12
41
  - **Vector conformance harness is now public (`@nhtio/adk/batteries/vector/conformance`).** The
13
42
  `runVectorStoreConformance` suite (plus `stubEncoder` / `paddedStubEncoder`) that the 29 shipped
14
43
  adapters test against is now an exported, deep-import-only subpath, so anyone writing their own
@@ -0,0 +1,119 @@
1
+ import { ESLintUtils } from "@typescript-eslint/utils";
2
+ //#region src/eslint/rules/common.ts
3
+ /**
4
+ * Shared `createRule` factory for the published `@nhtio/adk` ESLint rules.
5
+ *
6
+ * @remarks
7
+ * Wraps `ESLintUtils.RuleCreator` so every rule gets a consistent `meta.docs.url` pointing at the
8
+ * published rule reference. Each rule module imports this and default-exports `createRule({...})`.
9
+ *
10
+ * `@typescript-eslint/utils` is an OPTIONAL peer dependency of `@nhtio/adk` — it is only needed by
11
+ * consumers who import `@nhtio/adk/eslint`. The main library never imports this module.
12
+ */
13
+ /**
14
+ * Rule factory for `@nhtio/adk` ESLint rules. The `name` passed to each rule becomes the slug in the
15
+ * generated documentation URL.
16
+ */
17
+ var createRule = ESLintUtils.RuleCreator((name) => `https://adk.nht.io/eslint/rules/${name}`);
18
+ var isFunctionNode = (node) => node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
19
+ var LLM_CTOR_NAMES = new Set([
20
+ "OpenAI",
21
+ "AzureOpenAI",
22
+ "Anthropic",
23
+ "AnthropicBedrock",
24
+ "AnthropicVertex",
25
+ "GoogleGenerativeAI",
26
+ "GoogleGenAI",
27
+ "Mistral",
28
+ "CohereClient",
29
+ "CohereClientV2",
30
+ "Groq"
31
+ ]);
32
+ var LLM_METHOD_NAMES = new Set([
33
+ "generateContent",
34
+ "generateContentStream",
35
+ "generateMessage"
36
+ ]);
37
+ var memberPropertyName = (node) => node.property.type === "Identifier" ? node.property.name : void 0;
38
+ /**
39
+ * Heuristic: does this call/new expression look like a primary LLM invocation? Detects known
40
+ * provider SDK constructors and `…create()` calls whose receiver chain passes through
41
+ * `chat`/`completions`/`messages`/`responses`, plus a small set of generate* methods. Deliberately
42
+ * conservative — the goal is to catch the obvious footgun, not to be exhaustive.
43
+ */
44
+ var isLlmCall = (node) => {
45
+ if (node.type === "NewExpression") return node.callee.type === "Identifier" && LLM_CTOR_NAMES.has(node.callee.name);
46
+ if (node.type === "CallExpression" && node.callee.type === "MemberExpression") {
47
+ const method = memberPropertyName(node.callee);
48
+ if (method && LLM_METHOD_NAMES.has(method)) return true;
49
+ if (method === "create") {
50
+ let cur = node.callee.object;
51
+ while (cur.type === "MemberExpression") {
52
+ const seg = memberPropertyName(cur);
53
+ if (seg === "completions" || seg === "messages" || seg === "responses") return true;
54
+ cur = cur.object;
55
+ }
56
+ }
57
+ }
58
+ return false;
59
+ };
60
+ /**
61
+ * Walks the descendants of a function body (NOT crossing into nested function scopes) and invokes
62
+ * `visit` for every node, so a rule can scan a handler/middleware body for a pattern while ignoring
63
+ * inner closures (e.g. a `new TurnRunner` sub-agent's own callbacks).
64
+ */
65
+ var walkBodySkippingNestedFunctions = (body, visit) => {
66
+ const recurse = (node) => {
67
+ visit(node);
68
+ for (const key of Object.keys(node)) {
69
+ if (key === "parent") continue;
70
+ const value = node[key];
71
+ const children = Array.isArray(value) ? value : [value];
72
+ for (const child of children) {
73
+ if (!child || typeof child !== "object") continue;
74
+ const childNode = child;
75
+ if (typeof childNode.type !== "string") continue;
76
+ if (isFunctionNode(childNode)) continue;
77
+ recurse(childNode);
78
+ }
79
+ }
80
+ };
81
+ recurse(body);
82
+ };
83
+ var isSubAgentEntry = (node) => {
84
+ if (node.type === "NewExpression" && node.callee.type === "Identifier" && node.callee.name === "TurnRunner") return true;
85
+ if (node.type === "CallExpression" && node.callee.type === "MemberExpression" && node.callee.property.type === "Identifier" && node.callee.property.name === "dispatch" && node.callee.object.type === "Identifier" && node.callee.object.name === "DispatchRunner") return true;
86
+ return false;
87
+ };
88
+ /**
89
+ * True when the function body runs a scoped sub-agent — either `new TurnRunner(...)` or
90
+ * `DispatchRunner.dispatch(...)` — the documented escape hatch that exempts a tool handler from
91
+ * {@link isLlmCall} flagging.
92
+ */
93
+ var runsSubAgent = (body) => {
94
+ let found = false;
95
+ const recurse = (node) => {
96
+ if (found) return;
97
+ if (isSubAgentEntry(node)) {
98
+ found = true;
99
+ return;
100
+ }
101
+ for (const key of Object.keys(node)) {
102
+ if (key === "parent") continue;
103
+ const value = node[key];
104
+ const children = Array.isArray(value) ? value : [value];
105
+ for (const child of children) {
106
+ if (!child || typeof child !== "object") continue;
107
+ const childNode = child;
108
+ if (typeof childNode.type !== "string") continue;
109
+ recurse(childNode);
110
+ }
111
+ }
112
+ };
113
+ recurse(body);
114
+ return found;
115
+ };
116
+ //#endregion
117
+ export { walkBodySkippingNestedFunctions as a, runsSubAgent as i, isFunctionNode as n, isLlmCall as r, createRule as t };
118
+
119
+ //# sourceMappingURL=common-BUIjZ6LV.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"common-BUIjZ6LV.mjs","names":[],"sources":["../src/eslint/rules/common.ts"],"sourcesContent":["/**\n * Shared `createRule` factory for the published `@nhtio/adk` ESLint rules.\n *\n * @remarks\n * Wraps `ESLintUtils.RuleCreator` so every rule gets a consistent `meta.docs.url` pointing at the\n * published rule reference. Each rule module imports this and default-exports `createRule({...})`.\n *\n * `@typescript-eslint/utils` is an OPTIONAL peer dependency of `@nhtio/adk` — it is only needed by\n * consumers who import `@nhtio/adk/eslint`. The main library never imports this module.\n */\n\nimport { ESLintUtils } from '@typescript-eslint/utils'\n\nimport type { TSESTree } from '@typescript-eslint/utils'\n\n/**\n * Rule factory for `@nhtio/adk` ESLint rules. The `name` passed to each rule becomes the slug in the\n * generated documentation URL.\n */\nexport const createRule = ESLintUtils.RuleCreator(\n (name) => `https://adk.nht.io/eslint/rules/${name}`\n)\n\n/** Any function-like AST node (arrow, function expression, or declaration). */\nexport type FunctionNode =\n | TSESTree.FunctionDeclaration\n | TSESTree.FunctionExpression\n | TSESTree.ArrowFunctionExpression\n\nexport const isFunctionNode = (node: TSESTree.Node): node is FunctionNode =>\n node.type === 'FunctionDeclaration' ||\n node.type === 'FunctionExpression' ||\n node.type === 'ArrowFunctionExpression'\n\n// Provider SDK constructor names whose presence inside a handler/middleware indicates a primary\n// model call. Conservative, well-known set — keeps false positives low.\nconst LLM_CTOR_NAMES = new Set([\n 'OpenAI',\n 'AzureOpenAI',\n 'Anthropic',\n 'AnthropicBedrock',\n 'AnthropicVertex',\n 'GoogleGenerativeAI',\n 'GoogleGenAI',\n 'Mistral',\n 'CohereClient',\n 'CohereClientV2',\n 'Groq',\n])\n\n// Method-name tails that indicate a chat/completion/generation call on a provider client, e.g.\n// `client.chat.completions.create(...)`, `client.messages.create(...)`, `model.generateContent(...)`.\nconst LLM_METHOD_NAMES = new Set(['generateContent', 'generateContentStream', 'generateMessage'])\n\nconst memberPropertyName = (node: TSESTree.MemberExpression): string | undefined =>\n node.property.type === 'Identifier' ? node.property.name : undefined\n\n/**\n * Heuristic: does this call/new expression look like a primary LLM invocation? Detects known\n * provider SDK constructors and `…create()` calls whose receiver chain passes through\n * `chat`/`completions`/`messages`/`responses`, plus a small set of generate* methods. Deliberately\n * conservative — the goal is to catch the obvious footgun, not to be exhaustive.\n */\nexport const isLlmCall = (node: TSESTree.Node): boolean => {\n if (node.type === 'NewExpression') {\n return node.callee.type === 'Identifier' && LLM_CTOR_NAMES.has(node.callee.name)\n }\n if (node.type === 'CallExpression' && node.callee.type === 'MemberExpression') {\n const method = memberPropertyName(node.callee)\n if (method && LLM_METHOD_NAMES.has(method)) return true\n // `<chain>.create()` where the chain passes through a provider sub-resource.\n if (method === 'create') {\n let cur: TSESTree.Node = node.callee.object\n while (cur.type === 'MemberExpression') {\n const seg = memberPropertyName(cur)\n if (seg === 'completions' || seg === 'messages' || seg === 'responses') return true\n cur = cur.object\n }\n }\n }\n return false\n}\n\n/**\n * Walks the descendants of a function body (NOT crossing into nested function scopes) and invokes\n * `visit` for every node, so a rule can scan a handler/middleware body for a pattern while ignoring\n * inner closures (e.g. a `new TurnRunner` sub-agent's own callbacks).\n */\nexport const walkBodySkippingNestedFunctions = (\n body: TSESTree.Node,\n visit: (node: TSESTree.Node) => void\n): void => {\n const recurse = (node: TSESTree.Node): void => {\n visit(node)\n for (const key of Object.keys(node)) {\n if (key === 'parent') continue\n const value = (node as unknown as Record<string, unknown>)[key]\n const children = Array.isArray(value) ? value : [value]\n for (const child of children) {\n if (!child || typeof child !== 'object') continue\n const childNode = child as TSESTree.Node\n if (typeof childNode.type !== 'string') continue\n // Do not descend into nested function scopes — their calls belong to a different context.\n if (isFunctionNode(childNode)) continue\n recurse(childNode)\n }\n }\n }\n recurse(body)\n}\n\n// A sub-agent is run through one of two blessed entry points: constructing a scoped\n// `new TurnRunner(...)`, or the lower-level static `DispatchRunner.dispatch(...)` (its constructor\n// is token-gated private, so `dispatch()` is the real entry point). Either one inside a tool handler\n// means \"this tool is deliberately a sub-agent,\" which is the documented exception to\n// no-model-in-tool-handler.\nconst isSubAgentEntry = (node: TSESTree.Node): boolean => {\n // new TurnRunner(...)\n if (\n node.type === 'NewExpression' &&\n node.callee.type === 'Identifier' &&\n node.callee.name === 'TurnRunner'\n ) {\n return true\n }\n // DispatchRunner.dispatch(...)\n if (\n node.type === 'CallExpression' &&\n node.callee.type === 'MemberExpression' &&\n node.callee.property.type === 'Identifier' &&\n node.callee.property.name === 'dispatch' &&\n node.callee.object.type === 'Identifier' &&\n node.callee.object.name === 'DispatchRunner'\n ) {\n return true\n }\n return false\n}\n\n/**\n * True when the function body runs a scoped sub-agent — either `new TurnRunner(...)` or\n * `DispatchRunner.dispatch(...)` — the documented escape hatch that exempts a tool handler from\n * {@link isLlmCall} flagging.\n */\nexport const runsSubAgent = (body: TSESTree.Node): boolean => {\n let found = false\n const recurse = (node: TSESTree.Node): void => {\n if (found) return\n if (isSubAgentEntry(node)) {\n found = true\n return\n }\n for (const key of Object.keys(node)) {\n if (key === 'parent') continue\n const value = (node as unknown as Record<string, unknown>)[key]\n const children = Array.isArray(value) ? value : [value]\n for (const child of children) {\n if (!child || typeof child !== 'object') continue\n const childNode = child as TSESTree.Node\n if (typeof childNode.type !== 'string') continue\n recurse(childNode)\n }\n }\n }\n recurse(body)\n return found\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAmBA,IAAa,aAAa,YAAY,aACnC,SAAS,mCAAmC,MAC/C;AAQA,IAAa,kBAAkB,SAC7B,KAAK,SAAS,yBACd,KAAK,SAAS,wBACd,KAAK,SAAS;AAIhB,IAAM,iBAAiB,IAAI,IAAI;CAC7B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAID,IAAM,mBAAmB,IAAI,IAAI;CAAC;CAAmB;CAAyB;AAAiB,CAAC;AAEhG,IAAM,sBAAsB,SAC1B,KAAK,SAAS,SAAS,eAAe,KAAK,SAAS,OAAO,KAAA;;;;;;;AAQ7D,IAAa,aAAa,SAAiC;CACzD,IAAI,KAAK,SAAS,iBAChB,OAAO,KAAK,OAAO,SAAS,gBAAgB,eAAe,IAAI,KAAK,OAAO,IAAI;CAEjF,IAAI,KAAK,SAAS,oBAAoB,KAAK,OAAO,SAAS,oBAAoB;EAC7E,MAAM,SAAS,mBAAmB,KAAK,MAAM;EAC7C,IAAI,UAAU,iBAAiB,IAAI,MAAM,GAAG,OAAO;EAEnD,IAAI,WAAW,UAAU;GACvB,IAAI,MAAqB,KAAK,OAAO;GACrC,OAAO,IAAI,SAAS,oBAAoB;IACtC,MAAM,MAAM,mBAAmB,GAAG;IAClC,IAAI,QAAQ,iBAAiB,QAAQ,cAAc,QAAQ,aAAa,OAAO;IAC/E,MAAM,IAAI;GACZ;EACF;CACF;CACA,OAAO;AACT;;;;;;AAOA,IAAa,mCACX,MACA,UACS;CACT,MAAM,WAAW,SAA8B;EAC7C,MAAM,IAAI;EACV,KAAK,MAAM,OAAO,OAAO,KAAK,IAAI,GAAG;GACnC,IAAI,QAAQ,UAAU;GACtB,MAAM,QAAS,KAA4C;GAC3D,MAAM,WAAW,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;GACtD,KAAK,MAAM,SAAS,UAAU;IAC5B,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU;IACzC,MAAM,YAAY;IAClB,IAAI,OAAO,UAAU,SAAS,UAAU;IAExC,IAAI,eAAe,SAAS,GAAG;IAC/B,QAAQ,SAAS;GACnB;EACF;CACF;CACA,QAAQ,IAAI;AACd;AAOA,IAAM,mBAAmB,SAAiC;CAExD,IACE,KAAK,SAAS,mBACd,KAAK,OAAO,SAAS,gBACrB,KAAK,OAAO,SAAS,cAErB,OAAO;CAGT,IACE,KAAK,SAAS,oBACd,KAAK,OAAO,SAAS,sBACrB,KAAK,OAAO,SAAS,SAAS,gBAC9B,KAAK,OAAO,SAAS,SAAS,cAC9B,KAAK,OAAO,OAAO,SAAS,gBAC5B,KAAK,OAAO,OAAO,SAAS,kBAE5B,OAAO;CAET,OAAO;AACT;;;;;;AAOA,IAAa,gBAAgB,SAAiC;CAC5D,IAAI,QAAQ;CACZ,MAAM,WAAW,SAA8B;EAC7C,IAAI,OAAO;EACX,IAAI,gBAAgB,IAAI,GAAG;GACzB,QAAQ;GACR;EACF;EACA,KAAK,MAAM,OAAO,OAAO,KAAK,IAAI,GAAG;GACnC,IAAI,QAAQ,UAAU;GACtB,MAAM,QAAS,KAA4C;GAC3D,MAAM,WAAW,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;GACtD,KAAK,MAAM,SAAS,UAAU;IAC5B,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU;IACzC,MAAM,YAAY;IAClB,IAAI,OAAO,UAAU,SAAS,UAAU;IACxC,QAAQ,SAAS;GACnB;EACF;CACF;CACA,QAAQ,IAAI;CACZ,OAAO;AACT"}
@@ -0,0 +1,148 @@
1
+ require("./chunk-Ble4zEEl.js");
2
+ //#region src/eslint/rules/common.ts
3
+ /**
4
+ * Shared `createRule` factory for the published `@nhtio/adk` ESLint rules.
5
+ *
6
+ * @remarks
7
+ * Wraps `ESLintUtils.RuleCreator` so every rule gets a consistent `meta.docs.url` pointing at the
8
+ * published rule reference. Each rule module imports this and default-exports `createRule({...})`.
9
+ *
10
+ * `@typescript-eslint/utils` is an OPTIONAL peer dependency of `@nhtio/adk` — it is only needed by
11
+ * consumers who import `@nhtio/adk/eslint`. The main library never imports this module.
12
+ */
13
+ /**
14
+ * Rule factory for `@nhtio/adk` ESLint rules. The `name` passed to each rule becomes the slug in the
15
+ * generated documentation URL.
16
+ */
17
+ var createRule = require("@typescript-eslint/utils").ESLintUtils.RuleCreator((name) => `https://adk.nht.io/eslint/rules/${name}`);
18
+ var isFunctionNode = (node) => node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
19
+ var LLM_CTOR_NAMES = new Set([
20
+ "OpenAI",
21
+ "AzureOpenAI",
22
+ "Anthropic",
23
+ "AnthropicBedrock",
24
+ "AnthropicVertex",
25
+ "GoogleGenerativeAI",
26
+ "GoogleGenAI",
27
+ "Mistral",
28
+ "CohereClient",
29
+ "CohereClientV2",
30
+ "Groq"
31
+ ]);
32
+ var LLM_METHOD_NAMES = new Set([
33
+ "generateContent",
34
+ "generateContentStream",
35
+ "generateMessage"
36
+ ]);
37
+ var memberPropertyName = (node) => node.property.type === "Identifier" ? node.property.name : void 0;
38
+ /**
39
+ * Heuristic: does this call/new expression look like a primary LLM invocation? Detects known
40
+ * provider SDK constructors and `…create()` calls whose receiver chain passes through
41
+ * `chat`/`completions`/`messages`/`responses`, plus a small set of generate* methods. Deliberately
42
+ * conservative — the goal is to catch the obvious footgun, not to be exhaustive.
43
+ */
44
+ var isLlmCall = (node) => {
45
+ if (node.type === "NewExpression") return node.callee.type === "Identifier" && LLM_CTOR_NAMES.has(node.callee.name);
46
+ if (node.type === "CallExpression" && node.callee.type === "MemberExpression") {
47
+ const method = memberPropertyName(node.callee);
48
+ if (method && LLM_METHOD_NAMES.has(method)) return true;
49
+ if (method === "create") {
50
+ let cur = node.callee.object;
51
+ while (cur.type === "MemberExpression") {
52
+ const seg = memberPropertyName(cur);
53
+ if (seg === "completions" || seg === "messages" || seg === "responses") return true;
54
+ cur = cur.object;
55
+ }
56
+ }
57
+ }
58
+ return false;
59
+ };
60
+ /**
61
+ * Walks the descendants of a function body (NOT crossing into nested function scopes) and invokes
62
+ * `visit` for every node, so a rule can scan a handler/middleware body for a pattern while ignoring
63
+ * inner closures (e.g. a `new TurnRunner` sub-agent's own callbacks).
64
+ */
65
+ var walkBodySkippingNestedFunctions = (body, visit) => {
66
+ const recurse = (node) => {
67
+ visit(node);
68
+ for (const key of Object.keys(node)) {
69
+ if (key === "parent") continue;
70
+ const value = node[key];
71
+ const children = Array.isArray(value) ? value : [value];
72
+ for (const child of children) {
73
+ if (!child || typeof child !== "object") continue;
74
+ const childNode = child;
75
+ if (typeof childNode.type !== "string") continue;
76
+ if (isFunctionNode(childNode)) continue;
77
+ recurse(childNode);
78
+ }
79
+ }
80
+ };
81
+ recurse(body);
82
+ };
83
+ var isSubAgentEntry = (node) => {
84
+ if (node.type === "NewExpression" && node.callee.type === "Identifier" && node.callee.name === "TurnRunner") return true;
85
+ if (node.type === "CallExpression" && node.callee.type === "MemberExpression" && node.callee.property.type === "Identifier" && node.callee.property.name === "dispatch" && node.callee.object.type === "Identifier" && node.callee.object.name === "DispatchRunner") return true;
86
+ return false;
87
+ };
88
+ /**
89
+ * True when the function body runs a scoped sub-agent — either `new TurnRunner(...)` or
90
+ * `DispatchRunner.dispatch(...)` — the documented escape hatch that exempts a tool handler from
91
+ * {@link isLlmCall} flagging.
92
+ */
93
+ var runsSubAgent = (body) => {
94
+ let found = false;
95
+ const recurse = (node) => {
96
+ if (found) return;
97
+ if (isSubAgentEntry(node)) {
98
+ found = true;
99
+ return;
100
+ }
101
+ for (const key of Object.keys(node)) {
102
+ if (key === "parent") continue;
103
+ const value = node[key];
104
+ const children = Array.isArray(value) ? value : [value];
105
+ for (const child of children) {
106
+ if (!child || typeof child !== "object") continue;
107
+ const childNode = child;
108
+ if (typeof childNode.type !== "string") continue;
109
+ recurse(childNode);
110
+ }
111
+ }
112
+ };
113
+ recurse(body);
114
+ return found;
115
+ };
116
+ //#endregion
117
+ Object.defineProperty(exports, "createRule", {
118
+ enumerable: true,
119
+ get: function() {
120
+ return createRule;
121
+ }
122
+ });
123
+ Object.defineProperty(exports, "isFunctionNode", {
124
+ enumerable: true,
125
+ get: function() {
126
+ return isFunctionNode;
127
+ }
128
+ });
129
+ Object.defineProperty(exports, "isLlmCall", {
130
+ enumerable: true,
131
+ get: function() {
132
+ return isLlmCall;
133
+ }
134
+ });
135
+ Object.defineProperty(exports, "runsSubAgent", {
136
+ enumerable: true,
137
+ get: function() {
138
+ return runsSubAgent;
139
+ }
140
+ });
141
+ Object.defineProperty(exports, "walkBodySkippingNestedFunctions", {
142
+ enumerable: true,
143
+ get: function() {
144
+ return walkBodySkippingNestedFunctions;
145
+ }
146
+ });
147
+
148
+ //# sourceMappingURL=common-DeNipTQI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"common-DeNipTQI.js","names":[],"sources":["../src/eslint/rules/common.ts"],"sourcesContent":["/**\n * Shared `createRule` factory for the published `@nhtio/adk` ESLint rules.\n *\n * @remarks\n * Wraps `ESLintUtils.RuleCreator` so every rule gets a consistent `meta.docs.url` pointing at the\n * published rule reference. Each rule module imports this and default-exports `createRule({...})`.\n *\n * `@typescript-eslint/utils` is an OPTIONAL peer dependency of `@nhtio/adk` — it is only needed by\n * consumers who import `@nhtio/adk/eslint`. The main library never imports this module.\n */\n\nimport { ESLintUtils } from '@typescript-eslint/utils'\n\nimport type { TSESTree } from '@typescript-eslint/utils'\n\n/**\n * Rule factory for `@nhtio/adk` ESLint rules. The `name` passed to each rule becomes the slug in the\n * generated documentation URL.\n */\nexport const createRule = ESLintUtils.RuleCreator(\n (name) => `https://adk.nht.io/eslint/rules/${name}`\n)\n\n/** Any function-like AST node (arrow, function expression, or declaration). */\nexport type FunctionNode =\n | TSESTree.FunctionDeclaration\n | TSESTree.FunctionExpression\n | TSESTree.ArrowFunctionExpression\n\nexport const isFunctionNode = (node: TSESTree.Node): node is FunctionNode =>\n node.type === 'FunctionDeclaration' ||\n node.type === 'FunctionExpression' ||\n node.type === 'ArrowFunctionExpression'\n\n// Provider SDK constructor names whose presence inside a handler/middleware indicates a primary\n// model call. Conservative, well-known set — keeps false positives low.\nconst LLM_CTOR_NAMES = new Set([\n 'OpenAI',\n 'AzureOpenAI',\n 'Anthropic',\n 'AnthropicBedrock',\n 'AnthropicVertex',\n 'GoogleGenerativeAI',\n 'GoogleGenAI',\n 'Mistral',\n 'CohereClient',\n 'CohereClientV2',\n 'Groq',\n])\n\n// Method-name tails that indicate a chat/completion/generation call on a provider client, e.g.\n// `client.chat.completions.create(...)`, `client.messages.create(...)`, `model.generateContent(...)`.\nconst LLM_METHOD_NAMES = new Set(['generateContent', 'generateContentStream', 'generateMessage'])\n\nconst memberPropertyName = (node: TSESTree.MemberExpression): string | undefined =>\n node.property.type === 'Identifier' ? node.property.name : undefined\n\n/**\n * Heuristic: does this call/new expression look like a primary LLM invocation? Detects known\n * provider SDK constructors and `…create()` calls whose receiver chain passes through\n * `chat`/`completions`/`messages`/`responses`, plus a small set of generate* methods. Deliberately\n * conservative — the goal is to catch the obvious footgun, not to be exhaustive.\n */\nexport const isLlmCall = (node: TSESTree.Node): boolean => {\n if (node.type === 'NewExpression') {\n return node.callee.type === 'Identifier' && LLM_CTOR_NAMES.has(node.callee.name)\n }\n if (node.type === 'CallExpression' && node.callee.type === 'MemberExpression') {\n const method = memberPropertyName(node.callee)\n if (method && LLM_METHOD_NAMES.has(method)) return true\n // `<chain>.create()` where the chain passes through a provider sub-resource.\n if (method === 'create') {\n let cur: TSESTree.Node = node.callee.object\n while (cur.type === 'MemberExpression') {\n const seg = memberPropertyName(cur)\n if (seg === 'completions' || seg === 'messages' || seg === 'responses') return true\n cur = cur.object\n }\n }\n }\n return false\n}\n\n/**\n * Walks the descendants of a function body (NOT crossing into nested function scopes) and invokes\n * `visit` for every node, so a rule can scan a handler/middleware body for a pattern while ignoring\n * inner closures (e.g. a `new TurnRunner` sub-agent's own callbacks).\n */\nexport const walkBodySkippingNestedFunctions = (\n body: TSESTree.Node,\n visit: (node: TSESTree.Node) => void\n): void => {\n const recurse = (node: TSESTree.Node): void => {\n visit(node)\n for (const key of Object.keys(node)) {\n if (key === 'parent') continue\n const value = (node as unknown as Record<string, unknown>)[key]\n const children = Array.isArray(value) ? value : [value]\n for (const child of children) {\n if (!child || typeof child !== 'object') continue\n const childNode = child as TSESTree.Node\n if (typeof childNode.type !== 'string') continue\n // Do not descend into nested function scopes — their calls belong to a different context.\n if (isFunctionNode(childNode)) continue\n recurse(childNode)\n }\n }\n }\n recurse(body)\n}\n\n// A sub-agent is run through one of two blessed entry points: constructing a scoped\n// `new TurnRunner(...)`, or the lower-level static `DispatchRunner.dispatch(...)` (its constructor\n// is token-gated private, so `dispatch()` is the real entry point). Either one inside a tool handler\n// means \"this tool is deliberately a sub-agent,\" which is the documented exception to\n// no-model-in-tool-handler.\nconst isSubAgentEntry = (node: TSESTree.Node): boolean => {\n // new TurnRunner(...)\n if (\n node.type === 'NewExpression' &&\n node.callee.type === 'Identifier' &&\n node.callee.name === 'TurnRunner'\n ) {\n return true\n }\n // DispatchRunner.dispatch(...)\n if (\n node.type === 'CallExpression' &&\n node.callee.type === 'MemberExpression' &&\n node.callee.property.type === 'Identifier' &&\n node.callee.property.name === 'dispatch' &&\n node.callee.object.type === 'Identifier' &&\n node.callee.object.name === 'DispatchRunner'\n ) {\n return true\n }\n return false\n}\n\n/**\n * True when the function body runs a scoped sub-agent — either `new TurnRunner(...)` or\n * `DispatchRunner.dispatch(...)` — the documented escape hatch that exempts a tool handler from\n * {@link isLlmCall} flagging.\n */\nexport const runsSubAgent = (body: TSESTree.Node): boolean => {\n let found = false\n const recurse = (node: TSESTree.Node): void => {\n if (found) return\n if (isSubAgentEntry(node)) {\n found = true\n return\n }\n for (const key of Object.keys(node)) {\n if (key === 'parent') continue\n const value = (node as unknown as Record<string, unknown>)[key]\n const children = Array.isArray(value) ? value : [value]\n for (const child of children) {\n if (!child || typeof child !== 'object') continue\n const childNode = child as TSESTree.Node\n if (typeof childNode.type !== 'string') continue\n recurse(childNode)\n }\n }\n }\n recurse(body)\n return found\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAmBA,IAAa,+CAAa,EAAA,YAAY,aACnC,SAAS,mCAAmC,MAC/C;AAQA,IAAa,kBAAkB,SAC7B,KAAK,SAAS,yBACd,KAAK,SAAS,wBACd,KAAK,SAAS;AAIhB,IAAM,iBAAiB,IAAI,IAAI;CAC7B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAID,IAAM,mBAAmB,IAAI,IAAI;CAAC;CAAmB;CAAyB;AAAiB,CAAC;AAEhG,IAAM,sBAAsB,SAC1B,KAAK,SAAS,SAAS,eAAe,KAAK,SAAS,OAAO,KAAA;;;;;;;AAQ7D,IAAa,aAAa,SAAiC;CACzD,IAAI,KAAK,SAAS,iBAChB,OAAO,KAAK,OAAO,SAAS,gBAAgB,eAAe,IAAI,KAAK,OAAO,IAAI;CAEjF,IAAI,KAAK,SAAS,oBAAoB,KAAK,OAAO,SAAS,oBAAoB;EAC7E,MAAM,SAAS,mBAAmB,KAAK,MAAM;EAC7C,IAAI,UAAU,iBAAiB,IAAI,MAAM,GAAG,OAAO;EAEnD,IAAI,WAAW,UAAU;GACvB,IAAI,MAAqB,KAAK,OAAO;GACrC,OAAO,IAAI,SAAS,oBAAoB;IACtC,MAAM,MAAM,mBAAmB,GAAG;IAClC,IAAI,QAAQ,iBAAiB,QAAQ,cAAc,QAAQ,aAAa,OAAO;IAC/E,MAAM,IAAI;GACZ;EACF;CACF;CACA,OAAO;AACT;;;;;;AAOA,IAAa,mCACX,MACA,UACS;CACT,MAAM,WAAW,SAA8B;EAC7C,MAAM,IAAI;EACV,KAAK,MAAM,OAAO,OAAO,KAAK,IAAI,GAAG;GACnC,IAAI,QAAQ,UAAU;GACtB,MAAM,QAAS,KAA4C;GAC3D,MAAM,WAAW,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;GACtD,KAAK,MAAM,SAAS,UAAU;IAC5B,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU;IACzC,MAAM,YAAY;IAClB,IAAI,OAAO,UAAU,SAAS,UAAU;IAExC,IAAI,eAAe,SAAS,GAAG;IAC/B,QAAQ,SAAS;GACnB;EACF;CACF;CACA,QAAQ,IAAI;AACd;AAOA,IAAM,mBAAmB,SAAiC;CAExD,IACE,KAAK,SAAS,mBACd,KAAK,OAAO,SAAS,gBACrB,KAAK,OAAO,SAAS,cAErB,OAAO;CAGT,IACE,KAAK,SAAS,oBACd,KAAK,OAAO,SAAS,sBACrB,KAAK,OAAO,SAAS,SAAS,gBAC9B,KAAK,OAAO,SAAS,SAAS,cAC9B,KAAK,OAAO,OAAO,SAAS,gBAC5B,KAAK,OAAO,OAAO,SAAS,kBAE5B,OAAO;CAET,OAAO;AACT;;;;;;AAOA,IAAa,gBAAgB,SAAiC;CAC5D,IAAI,QAAQ;CACZ,MAAM,WAAW,SAA8B;EAC7C,IAAI,OAAO;EACX,IAAI,gBAAgB,IAAI,GAAG;GACzB,QAAQ;GACR;EACF;EACA,KAAK,MAAM,OAAO,OAAO,KAAK,IAAI,GAAG;GACnC,IAAI,QAAQ,UAAU;GACtB,MAAM,QAAS,KAA4C;GAC3D,MAAM,WAAW,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;GACtD,KAAK,MAAM,SAAS,UAAU;IAC5B,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU;IACzC,MAAM,YAAY;IAClB,IAAI,OAAO,UAAU,SAAS,UAAU;IACxC,QAAQ,SAAS;GACnB;EACF;CACF;CACA,QAAQ,IAAI;CACZ,OAAO;AACT"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * @module @nhtio/adk/eslint
3
+ *
4
+ * The `@nhtio/adk` ESLint plugin: machine-checkable enforcement of the harness contracts that
5
+ * implementors of bring-your-own batteries, tools, and primitives are most likely to get wrong.
6
+ *
7
+ * @remarks
8
+ * These rules turn documented footguns into lint errors. They are report-only (no autofix) — each
9
+ * names the contract it enforces and the fix; carve out a deliberate exception with an inline
10
+ * `// eslint-disable-next-line adk/<rule> -- <reason>` comment.
11
+ *
12
+ * `@typescript-eslint/utils` and `eslint` are OPTIONAL peer dependencies of `@nhtio/adk` — installed
13
+ * only by consumers who lint with this plugin. The main library never imports this module.
14
+ *
15
+ * @example Flat config
16
+ * ```ts
17
+ * import adk from '@nhtio/adk/eslint'
18
+ *
19
+ * export default [
20
+ * { plugins: { adk: adk.plugin }, rules: adk.configs.recommended.rules },
21
+ * ]
22
+ * ```
23
+ */
24
+ import type { FlatConfig } from '@typescript-eslint/utils/ts-eslint';
25
+ /**
26
+ * Map of rule id (without the `adk/` plugin prefix) to its rule object. Registered on the plugin as
27
+ * `rules`, so configs reference them as `adk/<id>`.
28
+ */
29
+ export declare const rules: {
30
+ 'require-validator-any-required': import("@typescript-eslint/utils/ts-eslint").RuleModule<"declareIntent", [
31
+ ], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
32
+ name: string;
33
+ };
34
+ 'no-model-in-tool-handler': import("@typescript-eslint/utils/ts-eslint").RuleModule<"noModelInHandler", [
35
+ ], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
36
+ name: string;
37
+ };
38
+ 'thought-payload-requires-replay-tag': import("@typescript-eslint/utils/ts-eslint").RuleModule<"requireReplayTag", [
39
+ ], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
40
+ name: string;
41
+ };
42
+ 'token-encoding-requires-context-window': import("@typescript-eslint/utils/ts-eslint").RuleModule<"requireContextWindow", [
43
+ ], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
44
+ name: string;
45
+ };
46
+ 'artifact-tool-forbids-artifact-constructor': import("@typescript-eslint/utils/ts-eslint").RuleModule<"forbidArtifactConstructor", [
47
+ ], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
48
+ name: string;
49
+ };
50
+ };
51
+ /**
52
+ * The ESLint plugin object. Register under the `adk` namespace: `plugins: { adk: plugin }`.
53
+ */
54
+ export declare const plugin: FlatConfig.Plugin;
55
+ /**
56
+ * Named config presets. `recommended` enables every rule at `error` and registers the plugin under
57
+ * the `adk` namespace — spread it into a flat config to adopt the full set.
58
+ */
59
+ export declare const configs: {
60
+ recommended: {
61
+ name: string;
62
+ plugins: {
63
+ adk: FlatConfig.Plugin;
64
+ };
65
+ rules: Partial<Record<string, import("@typescript-eslint/utils/ts-eslint").SharedConfig.RuleEntry>>;
66
+ };
67
+ };
68
+ /**
69
+ * Default export bundles the plugin and its config presets, mirroring the shape ESLint flat configs
70
+ * expect from a plugin module.
71
+ */
72
+ declare const _default: {
73
+ plugin: FlatConfig.Plugin;
74
+ rules: {
75
+ 'require-validator-any-required': import("@typescript-eslint/utils/ts-eslint").RuleModule<"declareIntent", [
76
+ ], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
77
+ name: string;
78
+ };
79
+ 'no-model-in-tool-handler': import("@typescript-eslint/utils/ts-eslint").RuleModule<"noModelInHandler", [
80
+ ], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
81
+ name: string;
82
+ };
83
+ 'thought-payload-requires-replay-tag': import("@typescript-eslint/utils/ts-eslint").RuleModule<"requireReplayTag", [
84
+ ], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
85
+ name: string;
86
+ };
87
+ 'token-encoding-requires-context-window': import("@typescript-eslint/utils/ts-eslint").RuleModule<"requireContextWindow", [
88
+ ], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
89
+ name: string;
90
+ };
91
+ 'artifact-tool-forbids-artifact-constructor': import("@typescript-eslint/utils/ts-eslint").RuleModule<"forbidArtifactConstructor", [
92
+ ], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
93
+ name: string;
94
+ };
95
+ };
96
+ configs: {
97
+ recommended: {
98
+ name: string;
99
+ plugins: {
100
+ adk: FlatConfig.Plugin;
101
+ };
102
+ rules: Partial<Record<string, import("@typescript-eslint/utils/ts-eslint").SharedConfig.RuleEntry>>;
103
+ };
104
+ };
105
+ };
106
+ export default _default;
@@ -0,0 +1,57 @@
1
+ Object.defineProperties(exports, {
2
+ __esModule: { value: true },
3
+ [Symbol.toStringTag]: { value: "Module" }
4
+ });
5
+ require("../../chunk-Ble4zEEl.js");
6
+ const require_common = require("../../common-DeNipTQI.js");
7
+ //#region src/eslint/rules/artifact_tool_forbids_artifact_constructor.ts
8
+ /**
9
+ * @module @nhtio/adk/eslint/rules/artifact_tool_forbids_artifact_constructor
10
+ *
11
+ * Flags `new ArtifactTool({ … })` whose options literal sets an `artifactConstructor`.
12
+ *
13
+ * Why: an `ArtifactTool` is the tool that answers queries AGAINST a spooled artifact — it must not
14
+ * itself return a `SpooledArtifact`, or the result would be re-wrapped into another artifact whose
15
+ * query tools are themselves `ArtifactTool`s, and so on without end. The base `RawTool` accepts an
16
+ * `artifactConstructor` (the subclass used to wrap a tool's bytes), but `ArtifactTool` explicitly
17
+ * FORBIDS it (`artifactConstructor: validator.any().forbidden()` in its schema). Supplying one is a
18
+ * recursion footgun the constructor rejects at runtime; this rule surfaces it at the construction
19
+ * site so it fails lint.
20
+ *
21
+ * Opt-out:
22
+ * // eslint-disable-next-line adk/artifact-tool-forbids-artifact-constructor -- <reason>
23
+ */
24
+ var literalKey = (prop) => {
25
+ if (prop.computed) return void 0;
26
+ if (prop.key.type === "Identifier") return prop.key.name;
27
+ if (prop.key.type === "Literal" && typeof prop.key.value === "string") return prop.key.value;
28
+ };
29
+ var isUndefinedValue = (node) => node.type === "Identifier" && node.name === "undefined";
30
+ var artifactToolForbidsArtifactConstructorRule = require_common.createRule({
31
+ name: "artifact-tool-forbids-artifact-constructor",
32
+ meta: {
33
+ type: "problem",
34
+ docs: { description: "An `ArtifactTool` must not declare `artifactConstructor` — it answers queries against an artifact and cannot itself return one (infinite re-wrapping). The ArtifactTool schema forbids the field at runtime; this surfaces it at the construction site." },
35
+ schema: [],
36
+ messages: { forbidArtifactConstructor: "`new ArtifactTool` must not set `artifactConstructor` — an artifact-query tool cannot itself return a SpooledArtifact (it would re-wrap forever). The ArtifactTool schema forbids this field. Remove it. Opt out with an eslint-disable-next-line adk/artifact-tool-forbids-artifact-constructor comment + reason." }
37
+ },
38
+ defaultOptions: [],
39
+ create(context) {
40
+ return { NewExpression(node) {
41
+ if (node.callee.type !== "Identifier" || node.callee.name !== "ArtifactTool") return;
42
+ const arg = node.arguments[0];
43
+ if (!arg || arg.type !== "ObjectExpression") return;
44
+ for (const p of arg.properties) {
45
+ if (p.type === "SpreadElement") continue;
46
+ if (literalKey(p) === "artifactConstructor" && !isUndefinedValue(p.value)) context.report({
47
+ node: p,
48
+ messageId: "forbidArtifactConstructor"
49
+ });
50
+ }
51
+ } };
52
+ }
53
+ });
54
+ //#endregion
55
+ exports.default = artifactToolForbidsArtifactConstructorRule;
56
+
57
+ //# sourceMappingURL=artifact_tool_forbids_artifact_constructor.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifact_tool_forbids_artifact_constructor.cjs","names":[],"sources":["../../../src/eslint/rules/artifact_tool_forbids_artifact_constructor.ts"],"sourcesContent":["/**\n * @module @nhtio/adk/eslint/rules/artifact_tool_forbids_artifact_constructor\n *\n * Flags `new ArtifactTool({ … })` whose options literal sets an `artifactConstructor`.\n *\n * Why: an `ArtifactTool` is the tool that answers queries AGAINST a spooled artifact — it must not\n * itself return a `SpooledArtifact`, or the result would be re-wrapped into another artifact whose\n * query tools are themselves `ArtifactTool`s, and so on without end. The base `RawTool` accepts an\n * `artifactConstructor` (the subclass used to wrap a tool's bytes), but `ArtifactTool` explicitly\n * FORBIDS it (`artifactConstructor: validator.any().forbidden()` in its schema). Supplying one is a\n * recursion footgun the constructor rejects at runtime; this rule surfaces it at the construction\n * site so it fails lint.\n *\n * Opt-out:\n * // eslint-disable-next-line adk/artifact-tool-forbids-artifact-constructor -- <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 isUndefinedValue = (node: TSESTree.Node): boolean =>\n node.type === 'Identifier' && node.name === 'undefined'\n\nconst artifactToolForbidsArtifactConstructorRule = createRule({\n name: 'artifact-tool-forbids-artifact-constructor',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'An `ArtifactTool` must not declare `artifactConstructor` — it answers queries against an artifact and cannot itself return one (infinite re-wrapping). The ArtifactTool schema forbids the field at runtime; this surfaces it at the construction site.',\n },\n schema: [],\n messages: {\n forbidArtifactConstructor:\n '`new ArtifactTool` must not set `artifactConstructor` — an artifact-query tool cannot itself return a SpooledArtifact (it would re-wrap forever). The ArtifactTool schema forbids this field. Remove it. Opt out with an eslint-disable-next-line adk/artifact-tool-forbids-artifact-constructor comment + reason.',\n },\n },\n defaultOptions: [],\n create(context) {\n return {\n NewExpression(node: TSESTree.NewExpression) {\n if (node.callee.type !== 'Identifier' || node.callee.name !== 'ArtifactTool') return\n const arg = node.arguments[0]\n if (!arg || arg.type !== 'ObjectExpression') return\n\n for (const p of arg.properties) {\n if (p.type === 'SpreadElement') continue\n if (literalKey(p) === 'artifactConstructor' && !isUndefinedValue(p.value)) {\n context.report({ node: p, messageId: 'forbidArtifactConstructor' })\n }\n }\n },\n }\n },\n})\n\nexport default artifactToolForbidsArtifactConstructorRule\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAqBA,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,oBAAoB,SACxB,KAAK,SAAS,gBAAgB,KAAK,SAAS;AAE9C,IAAM,6CAA6C,eAAA,WAAW;CAC5D,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,0PACJ;EACA,QAAQ,CAAC;EACT,UAAU,EACR,2BACE,qTACJ;CACF;CACA,gBAAgB,CAAC;CACjB,OAAO,SAAS;EACd,OAAO,EACL,cAAc,MAA8B;GAC1C,IAAI,KAAK,OAAO,SAAS,gBAAgB,KAAK,OAAO,SAAS,gBAAgB;GAC9E,MAAM,MAAM,KAAK,UAAU;GAC3B,IAAI,CAAC,OAAO,IAAI,SAAS,oBAAoB;GAE7C,KAAK,MAAM,KAAK,IAAI,YAAY;IAC9B,IAAI,EAAE,SAAS,iBAAiB;IAChC,IAAI,WAAW,CAAC,MAAM,yBAAyB,CAAC,iBAAiB,EAAE,KAAK,GACtE,QAAQ,OAAO;KAAE,MAAM;KAAG,WAAW;IAA4B,CAAC;GAEtE;EACF,EACF;CACF;AACF,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @module @nhtio/adk/eslint/rules/artifact_tool_forbids_artifact_constructor
3
+ *
4
+ * Flags `new ArtifactTool({ … })` whose options literal sets an `artifactConstructor`.
5
+ *
6
+ * Why: an `ArtifactTool` is the tool that answers queries AGAINST a spooled artifact — it must not
7
+ * itself return a `SpooledArtifact`, or the result would be re-wrapped into another artifact whose
8
+ * query tools are themselves `ArtifactTool`s, and so on without end. The base `RawTool` accepts an
9
+ * `artifactConstructor` (the subclass used to wrap a tool's bytes), but `ArtifactTool` explicitly
10
+ * FORBIDS it (`artifactConstructor: validator.any().forbidden()` in its schema). Supplying one is a
11
+ * recursion footgun the constructor rejects at runtime; this rule surfaces it at the construction
12
+ * site so it fails lint.
13
+ *
14
+ * Opt-out:
15
+ * // eslint-disable-next-line adk/artifact-tool-forbids-artifact-constructor -- <reason>
16
+ */
17
+ declare const artifactToolForbidsArtifactConstructorRule: import("@typescript-eslint/utils/ts-eslint").RuleModule<"forbidArtifactConstructor", [
18
+ ], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
19
+ name: string;
20
+ };
21
+ export default artifactToolForbidsArtifactConstructorRule;
@@ -0,0 +1,52 @@
1
+ import { t as createRule } from "../../common-BUIjZ6LV.mjs";
2
+ //#region src/eslint/rules/artifact_tool_forbids_artifact_constructor.ts
3
+ /**
4
+ * @module @nhtio/adk/eslint/rules/artifact_tool_forbids_artifact_constructor
5
+ *
6
+ * Flags `new ArtifactTool({ … })` whose options literal sets an `artifactConstructor`.
7
+ *
8
+ * Why: an `ArtifactTool` is the tool that answers queries AGAINST a spooled artifact — it must not
9
+ * itself return a `SpooledArtifact`, or the result would be re-wrapped into another artifact whose
10
+ * query tools are themselves `ArtifactTool`s, and so on without end. The base `RawTool` accepts an
11
+ * `artifactConstructor` (the subclass used to wrap a tool's bytes), but `ArtifactTool` explicitly
12
+ * FORBIDS it (`artifactConstructor: validator.any().forbidden()` in its schema). Supplying one is a
13
+ * recursion footgun the constructor rejects at runtime; this rule surfaces it at the construction
14
+ * site so it fails lint.
15
+ *
16
+ * Opt-out:
17
+ * // eslint-disable-next-line adk/artifact-tool-forbids-artifact-constructor -- <reason>
18
+ */
19
+ var literalKey = (prop) => {
20
+ if (prop.computed) return void 0;
21
+ if (prop.key.type === "Identifier") return prop.key.name;
22
+ if (prop.key.type === "Literal" && typeof prop.key.value === "string") return prop.key.value;
23
+ };
24
+ var isUndefinedValue = (node) => node.type === "Identifier" && node.name === "undefined";
25
+ var artifactToolForbidsArtifactConstructorRule = createRule({
26
+ name: "artifact-tool-forbids-artifact-constructor",
27
+ meta: {
28
+ type: "problem",
29
+ docs: { description: "An `ArtifactTool` must not declare `artifactConstructor` — it answers queries against an artifact and cannot itself return one (infinite re-wrapping). The ArtifactTool schema forbids the field at runtime; this surfaces it at the construction site." },
30
+ schema: [],
31
+ messages: { forbidArtifactConstructor: "`new ArtifactTool` must not set `artifactConstructor` — an artifact-query tool cannot itself return a SpooledArtifact (it would re-wrap forever). The ArtifactTool schema forbids this field. Remove it. Opt out with an eslint-disable-next-line adk/artifact-tool-forbids-artifact-constructor comment + reason." }
32
+ },
33
+ defaultOptions: [],
34
+ create(context) {
35
+ return { NewExpression(node) {
36
+ if (node.callee.type !== "Identifier" || node.callee.name !== "ArtifactTool") return;
37
+ const arg = node.arguments[0];
38
+ if (!arg || arg.type !== "ObjectExpression") return;
39
+ for (const p of arg.properties) {
40
+ if (p.type === "SpreadElement") continue;
41
+ if (literalKey(p) === "artifactConstructor" && !isUndefinedValue(p.value)) context.report({
42
+ node: p,
43
+ messageId: "forbidArtifactConstructor"
44
+ });
45
+ }
46
+ } };
47
+ }
48
+ });
49
+ //#endregion
50
+ export { artifactToolForbidsArtifactConstructorRule as default };
51
+
52
+ //# sourceMappingURL=artifact_tool_forbids_artifact_constructor.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifact_tool_forbids_artifact_constructor.mjs","names":[],"sources":["../../../src/eslint/rules/artifact_tool_forbids_artifact_constructor.ts"],"sourcesContent":["/**\n * @module @nhtio/adk/eslint/rules/artifact_tool_forbids_artifact_constructor\n *\n * Flags `new ArtifactTool({ … })` whose options literal sets an `artifactConstructor`.\n *\n * Why: an `ArtifactTool` is the tool that answers queries AGAINST a spooled artifact — it must not\n * itself return a `SpooledArtifact`, or the result would be re-wrapped into another artifact whose\n * query tools are themselves `ArtifactTool`s, and so on without end. The base `RawTool` accepts an\n * `artifactConstructor` (the subclass used to wrap a tool's bytes), but `ArtifactTool` explicitly\n * FORBIDS it (`artifactConstructor: validator.any().forbidden()` in its schema). Supplying one is a\n * recursion footgun the constructor rejects at runtime; this rule surfaces it at the construction\n * site so it fails lint.\n *\n * Opt-out:\n * // eslint-disable-next-line adk/artifact-tool-forbids-artifact-constructor -- <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 isUndefinedValue = (node: TSESTree.Node): boolean =>\n node.type === 'Identifier' && node.name === 'undefined'\n\nconst artifactToolForbidsArtifactConstructorRule = createRule({\n name: 'artifact-tool-forbids-artifact-constructor',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'An `ArtifactTool` must not declare `artifactConstructor` — it answers queries against an artifact and cannot itself return one (infinite re-wrapping). The ArtifactTool schema forbids the field at runtime; this surfaces it at the construction site.',\n },\n schema: [],\n messages: {\n forbidArtifactConstructor:\n '`new ArtifactTool` must not set `artifactConstructor` — an artifact-query tool cannot itself return a SpooledArtifact (it would re-wrap forever). The ArtifactTool schema forbids this field. Remove it. Opt out with an eslint-disable-next-line adk/artifact-tool-forbids-artifact-constructor comment + reason.',\n },\n },\n defaultOptions: [],\n create(context) {\n return {\n NewExpression(node: TSESTree.NewExpression) {\n if (node.callee.type !== 'Identifier' || node.callee.name !== 'ArtifactTool') return\n const arg = node.arguments[0]\n if (!arg || arg.type !== 'ObjectExpression') return\n\n for (const p of arg.properties) {\n if (p.type === 'SpreadElement') continue\n if (literalKey(p) === 'artifactConstructor' && !isUndefinedValue(p.value)) {\n context.report({ node: p, messageId: 'forbidArtifactConstructor' })\n }\n }\n },\n }\n },\n})\n\nexport default artifactToolForbidsArtifactConstructorRule\n"],"mappings":";;;;;;;;;;;;;;;;;;AAqBA,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,oBAAoB,SACxB,KAAK,SAAS,gBAAgB,KAAK,SAAS;AAE9C,IAAM,6CAA6C,WAAW;CAC5D,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,0PACJ;EACA,QAAQ,CAAC;EACT,UAAU,EACR,2BACE,qTACJ;CACF;CACA,gBAAgB,CAAC;CACjB,OAAO,SAAS;EACd,OAAO,EACL,cAAc,MAA8B;GAC1C,IAAI,KAAK,OAAO,SAAS,gBAAgB,KAAK,OAAO,SAAS,gBAAgB;GAC9E,MAAM,MAAM,KAAK,UAAU;GAC3B,IAAI,CAAC,OAAO,IAAI,SAAS,oBAAoB;GAE7C,KAAK,MAAM,KAAK,IAAI,YAAY;IAC9B,IAAI,EAAE,SAAS,iBAAiB;IAChC,IAAI,WAAW,CAAC,MAAM,yBAAyB,CAAC,iBAAiB,EAAE,KAAK,GACtE,QAAQ,OAAO;KAAE,MAAM;KAAG,WAAW;IAA4B,CAAC;GAEtE;EACF,EACF;CACF;AACF,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Shared `createRule` factory for the published `@nhtio/adk` ESLint rules.
3
+ *
4
+ * @remarks
5
+ * Wraps `ESLintUtils.RuleCreator` so every rule gets a consistent `meta.docs.url` pointing at the
6
+ * published rule reference. Each rule module imports this and default-exports `createRule({...})`.
7
+ *
8
+ * `@typescript-eslint/utils` is an OPTIONAL peer dependency of `@nhtio/adk` — it is only needed by
9
+ * consumers who import `@nhtio/adk/eslint`. The main library never imports this module.
10
+ */
11
+ import { ESLintUtils } from '@typescript-eslint/utils';
12
+ import type { TSESTree } from '@typescript-eslint/utils';
13
+ /**
14
+ * Rule factory for `@nhtio/adk` ESLint rules. The `name` passed to each rule becomes the slug in the
15
+ * generated documentation URL.
16
+ */
17
+ export declare const createRule: <Options extends readonly unknown[], MessageIds extends string>({ meta, name, ...rule }: Readonly<ESLintUtils.RuleWithMetaAndName<Options, MessageIds, unknown>>) => ESLintUtils.RuleModule<MessageIds, Options, unknown, ESLintUtils.RuleListener> & {
18
+ name: string;
19
+ };
20
+ /** Any function-like AST node (arrow, function expression, or declaration). */
21
+ export type FunctionNode = TSESTree.FunctionDeclaration | TSESTree.FunctionExpression | TSESTree.ArrowFunctionExpression;
22
+ export declare const isFunctionNode: (node: TSESTree.Node) => node is FunctionNode;
23
+ /**
24
+ * Heuristic: does this call/new expression look like a primary LLM invocation? Detects known
25
+ * provider SDK constructors and `…create()` calls whose receiver chain passes through
26
+ * `chat`/`completions`/`messages`/`responses`, plus a small set of generate* methods. Deliberately
27
+ * conservative — the goal is to catch the obvious footgun, not to be exhaustive.
28
+ */
29
+ export declare const isLlmCall: (node: TSESTree.Node) => boolean;
30
+ /**
31
+ * Walks the descendants of a function body (NOT crossing into nested function scopes) and invokes
32
+ * `visit` for every node, so a rule can scan a handler/middleware body for a pattern while ignoring
33
+ * inner closures (e.g. a `new TurnRunner` sub-agent's own callbacks).
34
+ */
35
+ export declare const walkBodySkippingNestedFunctions: (body: TSESTree.Node, visit: (node: TSESTree.Node) => void) => void;
36
+ /**
37
+ * True when the function body runs a scoped sub-agent — either `new TurnRunner(...)` or
38
+ * `DispatchRunner.dispatch(...)` — the documented escape hatch that exempts a tool handler from
39
+ * {@link isLlmCall} flagging.
40
+ */
41
+ export declare const runsSubAgent: (body: TSESTree.Node) => boolean;