@nanonets/graft 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/README.md +11 -9
  2. package/dist/ai/crux.d.ts +20 -4
  3. package/dist/ai/crux.d.ts.map +1 -1
  4. package/dist/ai/crux.js +39 -41
  5. package/dist/ai/crux.js.map +1 -1
  6. package/dist/ai/llm/anthropic.d.ts +37 -0
  7. package/dist/ai/llm/anthropic.d.ts.map +1 -0
  8. package/dist/ai/llm/anthropic.js +141 -0
  9. package/dist/ai/llm/anthropic.js.map +1 -0
  10. package/dist/ai/llm/factory.d.ts +19 -0
  11. package/dist/ai/llm/factory.d.ts.map +1 -0
  12. package/dist/ai/llm/factory.js +20 -0
  13. package/dist/ai/llm/factory.js.map +1 -0
  14. package/dist/ai/llm/openai.d.ts +33 -0
  15. package/dist/ai/llm/openai.d.ts.map +1 -0
  16. package/dist/ai/llm/openai.js +146 -0
  17. package/dist/ai/llm/openai.js.map +1 -0
  18. package/dist/ai/llm/types.d.ts +113 -0
  19. package/dist/ai/llm/types.d.ts.map +1 -0
  20. package/dist/ai/llm/types.js +22 -0
  21. package/dist/ai/llm/types.js.map +1 -0
  22. package/dist/ai/providers.d.ts +37 -15
  23. package/dist/ai/providers.d.ts.map +1 -1
  24. package/dist/ai/providers.js +30 -5
  25. package/dist/ai/providers.js.map +1 -1
  26. package/dist/ai/summarize.d.ts +4 -4
  27. package/dist/ai/summarize.d.ts.map +1 -1
  28. package/dist/ai/summarize.js +6 -13
  29. package/dist/ai/summarize.js.map +1 -1
  30. package/dist/ai/synthesize.d.ts +12 -4
  31. package/dist/ai/synthesize.d.ts.map +1 -1
  32. package/dist/ai/synthesize.js +18 -34
  33. package/dist/ai/synthesize.js.map +1 -1
  34. package/dist/ask/ask.d.ts +3 -0
  35. package/dist/ask/ask.d.ts.map +1 -1
  36. package/dist/ask/ask.js +20 -18
  37. package/dist/ask/ask.js.map +1 -1
  38. package/dist/ask/fuse.d.ts +96 -0
  39. package/dist/ask/fuse.d.ts.map +1 -0
  40. package/dist/ask/fuse.js +184 -0
  41. package/dist/ask/fuse.js.map +1 -0
  42. package/dist/claude/format.d.ts.map +1 -1
  43. package/dist/claude/format.js +10 -2
  44. package/dist/claude/format.js.map +1 -1
  45. package/dist/claude/init.d.ts +3 -0
  46. package/dist/claude/init.d.ts.map +1 -1
  47. package/dist/claude/init.js +6 -1
  48. package/dist/claude/init.js.map +1 -1
  49. package/dist/claude/skill-template.d.ts.map +1 -1
  50. package/dist/claude/skill-template.js +34 -22
  51. package/dist/claude/skill-template.js.map +1 -1
  52. package/dist/cli.d.ts +3 -2
  53. package/dist/cli.d.ts.map +1 -1
  54. package/dist/cli.js +50 -37
  55. package/dist/cli.js.map +1 -1
  56. package/dist/context/savings.d.ts +34 -0
  57. package/dist/context/savings.d.ts.map +1 -0
  58. package/dist/context/savings.js +49 -0
  59. package/dist/context/savings.js.map +1 -0
  60. package/dist/engine.d.ts +4 -3
  61. package/dist/engine.d.ts.map +1 -1
  62. package/dist/engine.js +30 -19
  63. package/dist/engine.js.map +1 -1
  64. package/dist/graph/map.d.ts +4 -0
  65. package/dist/graph/map.d.ts.map +1 -1
  66. package/dist/graph/map.js +4 -1
  67. package/dist/graph/map.js.map +1 -1
  68. package/dist/graph/scopes.d.ts +30 -0
  69. package/dist/graph/scopes.d.ts.map +1 -0
  70. package/dist/graph/scopes.js +369 -0
  71. package/dist/graph/scopes.js.map +1 -0
  72. package/dist/graph/traverse-cli.d.ts +28 -18
  73. package/dist/graph/traverse-cli.d.ts.map +1 -1
  74. package/dist/graph/traverse-cli.js +68 -43
  75. package/dist/graph/traverse-cli.js.map +1 -1
  76. package/dist/graph/traverse.d.ts +35 -14
  77. package/dist/graph/traverse.d.ts.map +1 -1
  78. package/dist/graph/traverse.js +46 -20
  79. package/dist/graph/traverse.js.map +1 -1
  80. package/dist/graph/workspace.d.ts +136 -0
  81. package/dist/graph/workspace.d.ts.map +1 -0
  82. package/dist/graph/workspace.js +339 -0
  83. package/dist/graph/workspace.js.map +1 -0
  84. package/dist/hosts/instructions.d.ts.map +1 -1
  85. package/dist/hosts/instructions.js +4 -3
  86. package/dist/hosts/instructions.js.map +1 -1
  87. package/dist/hosts/mcp-config.d.ts +5 -0
  88. package/dist/hosts/mcp-config.d.ts.map +1 -1
  89. package/dist/hosts/mcp-config.js +2 -2
  90. package/dist/hosts/mcp-config.js.map +1 -1
  91. package/dist/index.d.ts +9 -3
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +8 -4
  94. package/dist/index.js.map +1 -1
  95. package/dist/mcp/tools.d.ts.map +1 -1
  96. package/dist/mcp/tools.js +33 -65
  97. package/dist/mcp/tools.js.map +1 -1
  98. package/dist/search/grep-cli.d.ts.map +1 -1
  99. package/dist/search/grep-cli.js +4 -0
  100. package/dist/search/grep-cli.js.map +1 -1
  101. package/dist/search/grep.d.ts +4 -0
  102. package/dist/search/grep.d.ts.map +1 -1
  103. package/dist/search/grep.js +2 -0
  104. package/dist/search/grep.js.map +1 -1
  105. package/package.json +2 -1
package/README.md CHANGED
@@ -71,9 +71,11 @@ npx @nanonets/graft init
71
71
  ### MCP server
72
72
 
73
73
  `graft init` also registers Graft's MCP server with agents that support it, so
74
- `graft_ask`, `graft_check`, and `graft_blast_radius` appear as native tools —
75
- no shell required. Skip with `--no-mcp`. Run it manually with `graft mcp [dir]`,
76
- or register it by hand:
74
+ `graft_ask`, `graft_callers`, `graft_grep`, `graft_skeleton`, `graft_map`, and
75
+ `graft_check` appear as native tools — no shell required. Claude Code now gets
76
+ this too: `graft init` writes the server into the project's `.mcp.json` (restart
77
+ Claude Code to load it). Skip with `--no-mcp`. Run it manually with
78
+ `graft mcp [dir]`, or register it by hand:
77
79
 
78
80
  ```json
79
81
  { "mcpServers": { "graft": { "command": "npx", "args": ["-y", "@nanonets/graft", "mcp"] } } }
@@ -113,7 +115,7 @@ Graft builds that understanding **once** and writes it into your repo as a folde
113
115
  - **A real graph you can read.** No embeddings, no similarity search, no index to keep warm. The graph is a set of linked files your agent opens, greps, and follows, exactly the way it reads any other file in the repo.
114
116
  - **Grafted into git.** The graph is just files in `graft/`. Commit it, and anyone who clones the repo has it. No database, no server, no setup. Git does the syncing, and a stale graph shows up as a diff in review instead of rotting in some external store.
115
117
  - **The diff lives with the code.** When a change moves things around, you see it in the graph diff in the same pull request, right next to the code that caused it.
116
- - **Your key, your model.** Summaries are written by a model you pick on [OpenRouter](https://openrouter.ai), under your own key. The structural code graph (`graft build`, `graft check`) is deterministic tree-sitter and never calls a model at all.
118
+ - **Your provider, your key, your model.** Summaries are written by any provider you choose — OpenAI, Anthropic (native), OpenRouter, Fireworks, Groq, a LiteLLM proxy, or a local model — under your own key. The structural code graph (`graft build`, `graft check`) is deterministic tree-sitter and never calls a model at all.
117
119
 
118
120
  ---
119
121
 
@@ -171,7 +173,7 @@ When the wiring graph exists, `graft check` validates it too — structural drif
171
173
  ## What runs where
172
174
 
173
175
  - **On your machine, no key, no network:** the structural code graph. `graft build` (wiring graph + per-file cards), `graft check`, and `graft ask` are deterministic tree-sitter — they never call a model.
174
- - **Through your OpenRouter key:** the LLM-written parts — `graft build --deep` adds the concept nodes (file summaries + node synthesis) and the per-symbol summaries and cruxes. Set `OPENROUTER_API_KEY` and optionally pick the model with `GRAFT_OPENROUTER_MODEL` (default: `openai/gpt-4o-mini`).
176
+ - **Through your provider key:** the LLM-written parts — `graft build --deep` adds the concept nodes (file summaries + node synthesis) and the per-symbol summaries and cruxes. graft is vendor-neutral: set `GRAFT_PROVIDER` (`openai` for any OpenAI-compatible endpoint, or `anthropic` for the native API), your `GRAFT_API_KEY`, `GRAFT_MODEL`, and — for the `openai` wire format — `GRAFT_BASE_URL` to point at OpenRouter, Fireworks, Groq, a LiteLLM proxy, a local server, or OpenAI itself. Or pass `--provider/--model/--api-key/--base-url` on the command line. (`OPENROUTER_API_KEY` still works as a deprecated fallback.)
175
177
  - **No telemetry** and no analytics — the only network calls are the LLM requests you configured.
176
178
 
177
179
  See [`.env.example`](.env.example) for the full list of settings (model, base URL, graph directory).
@@ -189,8 +191,8 @@ graft ask "<task>" [dir] # query the graph — ranked nodes + exact
189
191
  graft ask "<task>" --json # machine-readable result
190
192
 
191
193
  graft callers <symbol> [dir] # who calls/references/imports/implements/extends a symbol (no LLM, no key)
192
- graft callees <symbol> [dir] # what a symbol calls/references/imports/implements/extends (no LLM, no key)
193
- graft impact <symbol> [dir] -d N # BFS over incoming edges — who breaks if this symbol changes (no LLM, no key)
194
+ graft callers <symbol> --direction out # the reverse: what the symbol itself calls/references (was `graft callees`)
195
+ graft callers <symbol> -d N # walk transitively out to depth N — full blast radius (was `graft impact`)
194
196
 
195
197
  graft grep "<regex>" [dir] # exhaustive regex search over indexed files, grouped by enclosing symbol (no LLM, no key)
196
198
  graft grep "<regex>" --in <path> # narrow to files whose path contains this substring
@@ -205,7 +207,7 @@ graft check --json # print the drift report as JSON
205
207
  graft viz [dir] # see the graph: serves an interactive viewer on localhost
206
208
  graft viz --port 5000 --no-open # pick a port; don't auto-open the browser
207
209
 
208
- graft init [dir] # wire Graft into the coding agents detected in this repo (Claude Code always gets full hooks + statusline)
210
+ graft init [dir] # wire Graft into the coding agents detected in this repo (Claude Code always gets full hooks + statusline + MCP)
209
211
  graft init --no-build # wire the files only; don't build the graph
210
212
  graft init --agents cursor kiro # wire only these agents (ids: agents, cursor, gemini, copilot, kiro, windsurf, claude)
211
213
  graft init --all-agents # wire every known agent, detected or not
@@ -221,7 +223,7 @@ graft --version, -v # print the installed version and exit
221
223
 
222
224
  Method calls resolve through the receiver's type — constructor assignments
223
225
  (`self.router = APIRouter()`) and type annotations, not just the call-site
224
- name — so `callers`/`impact`/`grep --in` return calls bound to the right
226
+ name — so `callers`/`grep --in` return calls bound to the right
225
227
  type on method-heavy code, not every method anywhere with that name.
226
228
 
227
229
  ## Search & orient (`graft grep` / `graft map`)
package/dist/ai/crux.d.ts CHANGED
@@ -1,3 +1,20 @@
1
+ /**
2
+ * Tier-2 "meaning" call for the code graph — batched one request per file.
3
+ *
4
+ * Given a source file (with 1-based line numbers) and the list of definitions in
5
+ * it, one call returns, for each definition:
6
+ * 1. `summary` — one plain-English sentence: what the symbol is *for*, at the
7
+ * business-logic level, not a restatement of its signature.
8
+ * 2. `crux_start`/`crux_end` — the smallest contiguous range of FILE line
9
+ * numbers (inside that symbol's own span) that a reviewer must read to see
10
+ * the decision or rule the code encodes. `0/0` means there is no single
11
+ * crux (a trivial getter, a plain data holder).
12
+ *
13
+ * Batching per file means N definitions cost one request, not N — and the model
14
+ * sees each symbol's neighbours, which sharpens the summaries. Line numbers are
15
+ * consumed once, at write time, to slice the crux text verbatim from source.
16
+ */
17
+ import type { ChatModel } from "./llm/types.js";
1
18
  import type { Kind } from "../graph/types.js";
2
19
  /** One definition we want described, located by its line span within the file. */
3
20
  export interface NodeRef {
@@ -21,11 +38,10 @@ export interface NodeCrux {
21
38
  export interface CruxSummarizer {
22
39
  describeFile(input: FileCruxInput): Promise<NodeCrux[]>;
23
40
  }
24
- /** Crux summarizer backed by OpenRouter's OpenAI-compatible chat API. */
25
- export declare class OpenRouterCruxSummarizer implements CruxSummarizer {
26
- private client;
41
+ /** Crux summarizer backed by any {@link ChatModel} via forced tool calling. */
42
+ export declare class ChatCruxSummarizer implements CruxSummarizer {
27
43
  private model;
28
- constructor(apiKey: string, model: string, baseUrl?: string);
44
+ constructor(model: ChatModel);
29
45
  describeFile(input: FileCruxInput): Promise<NodeCrux[]>;
30
46
  }
31
47
  //# sourceMappingURL=crux.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"crux.d.ts","sourceRoot":"","sources":["../../src/ai/crux.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAE9C,kFAAkF;AAClF,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,IAAI,CAAC;IACX,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;CACzD;AAwDD,yEAAyE;AACzE,qBAAa,wBAAyB,YAAW,cAAc;IAC7D,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;gBAEV,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,SAAiC;IAS7E,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;CAa9D"}
1
+ {"version":3,"file":"crux.d.ts","sourceRoot":"","sources":["../../src/ai/crux.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAE9C,kFAAkF;AAClF,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,IAAI,CAAC;IACX,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;CACzD;AAwED,+EAA+E;AAC/E,qBAAa,kBAAmB,YAAW,cAAc;IAC3C,OAAO,CAAC,KAAK;gBAAL,KAAK,EAAE,SAAS;IAE9B,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;CAoB9D"}
package/dist/ai/crux.js CHANGED
@@ -1,33 +1,32 @@
1
- /**
2
- * Tier-2 "meaning" call for the code graph — batched one request per file.
3
- *
4
- * Given a source file (with 1-based line numbers) and the list of definitions in
5
- * it, one call returns, for each definition:
6
- * 1. `summary` — one plain-English sentence: what the symbol is *for*, at the
7
- * business-logic level, not a restatement of its signature.
8
- * 2. `crux_start`/`crux_end` — the smallest contiguous range of FILE line
9
- * numbers (inside that symbol's own span) that a reviewer must read to see
10
- * the decision or rule the code encodes. `0/0` means there is no single
11
- * crux (a trivial getter, a plain data holder).
12
- *
13
- * Batching per file means N definitions cost one request, not N — and the model
14
- * sees each symbol's neighbours, which sharpens the summaries. Line numbers are
15
- * consumed once, at write time, to slice the crux text verbatim from source.
16
- */
17
- import OpenAI from "openai";
18
1
  const SYSTEM_PROMPT = `You explain code definitions for a code graph that helps engineers navigate a codebase.
19
2
 
20
- You are given ONE source file with 1-based line numbers, and a list of TARGET definitions in it. For every target, return its purpose and the line range of its core logic.
21
-
22
- Return STRICT JSON, exactly:
23
- { "symbols": [ { "id": string, "summary": string, "crux_start": number, "crux_end": number }, ... ] }
3
+ You are given ONE source file with 1-based line numbers, and a list of TARGET definitions in it. For every target, record its purpose and the line range of its core logic via the record_symbols tool.
24
4
 
25
5
  Rules:
26
6
  - Emit exactly one entry per target id given, using that id verbatim.
27
7
  - summary: ONE sentence — what the symbol is FOR at the business-logic level. Say what problem it solves or rule it enforces, not what its signature already says.
28
8
  - crux_start / crux_end: FILE line numbers (as shown), inside that symbol's own line range. Pick the SINGLE most important contiguous span — the core branch, formula, guard, or state change. Keep it TIGHT: at most ~8 lines, and NEVER the whole function. If you can't narrow it below that, the symbol has no distinct crux — use 0/0.
29
- - Skip boilerplate, logging, and plumbing. If a symbol has no meaningful crux (trivial getter, data holder, one-line delegation, or logic spread evenly with no focal point), use "crux_start": 0 and "crux_end": 0.
30
- - Output ONLY the JSON object. No prose, no code fences.`;
9
+ - Skip boilerplate, logging, and plumbing. If a symbol has no meaningful crux (trivial getter, data holder, one-line delegation, or logic spread evenly with no focal point), use "crux_start": 0 and "crux_end": 0.`;
10
+ const RECORD_TOOL = "record_symbols";
11
+ const SYMBOLS_SCHEMA = {
12
+ type: "object",
13
+ properties: {
14
+ symbols: {
15
+ type: "array",
16
+ items: {
17
+ type: "object",
18
+ properties: {
19
+ id: { type: "string" },
20
+ summary: { type: "string" },
21
+ crux_start: { type: "number" },
22
+ crux_end: { type: "number" },
23
+ },
24
+ required: ["id", "summary", "crux_start", "crux_end"],
25
+ },
26
+ },
27
+ },
28
+ required: ["symbols"],
29
+ };
31
30
  /** Cap the file text sent per request so one huge file can't blow the context. */
32
31
  const MAX_CODE_CHARS = 18_000;
33
32
  function numberLines(source) {
@@ -44,11 +43,9 @@ function userContent(input) {
44
43
  .join("\n");
45
44
  return `FILE: ${input.path}\n\n${numberLines(input.source)}\n\nTARGETS:\n${targets}`;
46
45
  }
47
- /** Best-effort parse of a model's JSON reply into a {@link NodeCrux} list. */
48
- function parseResults(raw) {
49
- const text = raw.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, "");
50
- const obj = JSON.parse(text);
51
- if (!Array.isArray(obj.symbols))
46
+ /** Normalize the tool's parsed argument object into a {@link NodeCrux} list. */
47
+ function parseResults(obj) {
48
+ if (!obj || !Array.isArray(obj.symbols))
52
49
  return [];
53
50
  const num = (v) => (typeof v === "number" && Number.isFinite(v) ? Math.trunc(v) : 0);
54
51
  return obj.symbols
@@ -61,31 +58,32 @@ function parseResults(raw) {
61
58
  crux_end: num(s.crux_end),
62
59
  }));
63
60
  }
64
- /** Crux summarizer backed by OpenRouter's OpenAI-compatible chat API. */
65
- export class OpenRouterCruxSummarizer {
66
- client;
61
+ /** Crux summarizer backed by any {@link ChatModel} via forced tool calling. */
62
+ export class ChatCruxSummarizer {
67
63
  model;
68
- constructor(apiKey, model, baseUrl = "https://openrouter.ai/api/v1") {
69
- this.client = new OpenAI({
70
- apiKey,
71
- baseURL: baseUrl,
72
- defaultHeaders: { "X-Title": "Context Graph Engine" },
73
- });
64
+ constructor(model) {
74
65
  this.model = model;
75
66
  }
76
67
  async describeFile(input) {
77
68
  if (input.nodes.length === 0)
78
69
  return [];
79
- const response = await this.client.chat.completions.create({
80
- model: this.model,
70
+ const res = await this.model.create({
81
71
  temperature: 0,
82
- response_format: { type: "json_object" },
72
+ maxTokens: 8192,
73
+ tools: [
74
+ {
75
+ name: RECORD_TOOL,
76
+ description: "Record each target definition's purpose and crux line range.",
77
+ parameters: SYMBOLS_SCHEMA,
78
+ },
79
+ ],
80
+ responseFormat: { kind: "tool", name: RECORD_TOOL },
83
81
  messages: [
84
82
  { role: "system", content: SYSTEM_PROMPT },
85
83
  { role: "user", content: userContent(input) },
86
84
  ],
87
85
  });
88
- return parseResults(response.choices[0]?.message?.content ?? "");
86
+ return parseResults(res.toolCalls[0]?.args);
89
87
  }
90
88
  }
91
89
  //# sourceMappingURL=crux.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"crux.js","sourceRoot":"","sources":["../../src/ai/crux.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"crux.js","sourceRoot":"","sources":["../../src/ai/crux.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Native Anthropic transport. Wraps `@anthropic-ai/sdk` (the Messages API), so
3
+ * Claude is a first-class provider rather than something tunneled through an
4
+ * OpenAI-compatible endpoint.
5
+ *
6
+ * The Messages API differs from Chat Completions in ways this adapter absorbs so
7
+ * the rest of graft never sees them:
8
+ * - `system` is a top-level parameter, not a message.
9
+ * - tool results ride inside a USER turn, and all results answering one
10
+ * assistant turn must be coalesced into a single user message.
11
+ * - `max_tokens` is required (callers get a default).
12
+ * - `temperature` is rejected by current models, so it is never forwarded.
13
+ * - structured output is a forced tool (there is no `response_format`).
14
+ * - assistant turns are replayed verbatim (via `providerRaw`) so thinking-block
15
+ * signatures survive a multi-turn loop.
16
+ */
17
+ import Anthropic from "@anthropic-ai/sdk";
18
+ import type { ChatModel, ChatRequest, ChatResponse } from "./types.js";
19
+ export interface AnthropicChatModelOptions {
20
+ apiKey: string;
21
+ model: string;
22
+ baseUrl?: string;
23
+ label?: string;
24
+ /** Inject a pre-built client (tests pass a stub; production omits it). */
25
+ client?: Anthropic;
26
+ }
27
+ export declare class AnthropicChatModel implements ChatModel {
28
+ readonly label: string;
29
+ private client;
30
+ private model;
31
+ constructor(opts: AnthropicChatModelOptions);
32
+ create(req: ChatRequest): Promise<ChatResponse>;
33
+ /** Reconstruct an assistant turn, replaying the original blocks when we made them. */
34
+ private assistantParam;
35
+ private fromResponse;
36
+ }
37
+ //# sourceMappingURL=anthropic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../../src/ai/llm/anthropic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAC1C,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAsC,MAAM,YAAY,CAAC;AAM3G,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB;AAKD,qBAAa,kBAAmB,YAAW,SAAS;IAClD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,KAAK,CAAS;gBAEV,IAAI,EAAE,yBAAyB;IAMrC,MAAM,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IAuDrD,sFAAsF;IACtF,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,YAAY;CA4BrB"}
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Native Anthropic transport. Wraps `@anthropic-ai/sdk` (the Messages API), so
3
+ * Claude is a first-class provider rather than something tunneled through an
4
+ * OpenAI-compatible endpoint.
5
+ *
6
+ * The Messages API differs from Chat Completions in ways this adapter absorbs so
7
+ * the rest of graft never sees them:
8
+ * - `system` is a top-level parameter, not a message.
9
+ * - tool results ride inside a USER turn, and all results answering one
10
+ * assistant turn must be coalesced into a single user message.
11
+ * - `max_tokens` is required (callers get a default).
12
+ * - `temperature` is rejected by current models, so it is never forwarded.
13
+ * - structured output is a forced tool (there is no `response_format`).
14
+ * - assistant turns are replayed verbatim (via `providerRaw`) so thinking-block
15
+ * signatures survive a multi-turn loop.
16
+ */
17
+ import Anthropic from "@anthropic-ai/sdk";
18
+ const PROVIDER = "anthropic";
19
+ const JSON_TOOL = "emit_json";
20
+ const DEFAULT_MAX_TOKENS = 4096;
21
+ const cc = (on) => (on ? { cache_control: { type: "ephemeral" } } : {});
22
+ export class AnthropicChatModel {
23
+ label;
24
+ client;
25
+ model;
26
+ constructor(opts) {
27
+ this.model = opts.model;
28
+ this.label = opts.label ?? `${PROVIDER}:${opts.model}`;
29
+ this.client = opts.client ?? new Anthropic({ apiKey: opts.apiKey, baseURL: opts.baseUrl });
30
+ }
31
+ async create(req) {
32
+ const system = [];
33
+ const messages = [];
34
+ for (const m of req.messages) {
35
+ if (m.role === "system") {
36
+ system.push({ type: "text", text: m.content, ...cc(m.cacheBreakpoint) });
37
+ }
38
+ else if (m.role === "user") {
39
+ messages.push({ role: "user", content: [{ type: "text", text: m.content, ...cc(m.cacheBreakpoint) }] });
40
+ }
41
+ else if (m.role === "tool") {
42
+ // Tool results live in a user turn; coalesce consecutive results together.
43
+ const block = {
44
+ type: "tool_result",
45
+ tool_use_id: m.toolCallId ?? "",
46
+ content: [{ type: "text", text: m.content }],
47
+ ...cc(m.cacheBreakpoint),
48
+ };
49
+ const last = messages[messages.length - 1];
50
+ if (last?.role === "user" && Array.isArray(last.content)) {
51
+ last.content.push(block);
52
+ }
53
+ else {
54
+ messages.push({ role: "user", content: [block] });
55
+ }
56
+ }
57
+ else {
58
+ messages.push(this.assistantParam(m));
59
+ }
60
+ }
61
+ const tools = req.tools?.map(toAnthropicTool);
62
+ const params = {
63
+ model: this.model,
64
+ max_tokens: req.maxTokens ?? DEFAULT_MAX_TOKENS,
65
+ messages,
66
+ ...(system.length ? { system } : {}),
67
+ };
68
+ // temperature is intentionally NOT forwarded — current models reject it.
69
+ const fmt = req.responseFormat ?? { kind: "text" };
70
+ if (fmt.kind === "json") {
71
+ params.tools = [
72
+ ...(tools ?? []),
73
+ { name: JSON_TOOL, description: "Return the answer as a JSON object.", input_schema: { type: "object" } },
74
+ ];
75
+ params.tool_choice = { type: "tool", name: JSON_TOOL };
76
+ }
77
+ else if (fmt.kind === "tool") {
78
+ params.tools = tools;
79
+ params.tool_choice = { type: "tool", name: fmt.name };
80
+ }
81
+ else if (tools) {
82
+ params.tools = tools;
83
+ }
84
+ const resp = await this.client.messages.create(params);
85
+ return this.fromResponse(resp, fmt.kind);
86
+ }
87
+ /** Reconstruct an assistant turn, replaying the original blocks when we made them. */
88
+ assistantParam(m) {
89
+ if (m.providerRaw?.provider === PROVIDER)
90
+ return m.providerRaw.raw;
91
+ const content = [];
92
+ if (m.content)
93
+ content.push({ type: "text", text: m.content });
94
+ for (const tc of m.toolCalls ?? []) {
95
+ content.push({ type: "tool_use", id: tc.id, name: tc.name, input: tc.args });
96
+ }
97
+ return { role: "assistant", content };
98
+ }
99
+ fromResponse(resp, format) {
100
+ let text = "";
101
+ let toolCalls = [];
102
+ for (const block of resp.content) {
103
+ if (block.type === "text")
104
+ text += block.text;
105
+ else if (block.type === "tool_use")
106
+ toolCalls.push({ id: block.id, name: block.name, args: block.input });
107
+ }
108
+ if (format === "json") {
109
+ const jsonCall = toolCalls.find((c) => c.name === JSON_TOOL);
110
+ if (jsonCall)
111
+ text = JSON.stringify(jsonCall.args);
112
+ toolCalls = toolCalls.filter((c) => c.name !== JSON_TOOL);
113
+ }
114
+ return {
115
+ text,
116
+ toolCalls,
117
+ usage: normalizeUsage(resp.usage),
118
+ stopReason: resp.stop_reason ?? null,
119
+ // Replay the raw content verbatim so thinking-block signatures survive.
120
+ assistant: {
121
+ role: "assistant",
122
+ content: text,
123
+ toolCalls: toolCalls.length ? toolCalls : undefined,
124
+ providerRaw: { provider: PROVIDER, raw: { role: "assistant", content: resp.content } },
125
+ },
126
+ };
127
+ }
128
+ }
129
+ function toAnthropicTool(t) {
130
+ return { name: t.name, description: t.description, input_schema: t.parameters };
131
+ }
132
+ /** Anthropic reports uncached input in `input_tokens` and cache tokens separately. */
133
+ function normalizeUsage(u) {
134
+ return {
135
+ input: u.input_tokens ?? 0,
136
+ output: u.output_tokens ?? 0,
137
+ cacheRead: u.cache_read_input_tokens ?? 0,
138
+ cacheCreate: u.cache_creation_input_tokens ?? 0,
139
+ };
140
+ }
141
+ //# sourceMappingURL=anthropic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../../src/ai/llm/anthropic.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * One place that turns resolved config into a {@link ChatModel}. `provider` names
3
+ * the WIRE FORMAT, not a vendor: `openai` speaks the OpenAI-compatible API (point
4
+ * `baseUrl` at OpenRouter, Fireworks, a LiteLLM proxy, Groq, a local server, …),
5
+ * `anthropic` speaks the native Messages API. Adding a vendor is a base URL, not
6
+ * a code change; adding a wire format is one new adapter here.
7
+ */
8
+ import type { ChatModel } from "./types.js";
9
+ export type ProviderKind = "openai" | "anthropic";
10
+ export interface ChatModelConfig {
11
+ provider: ProviderKind;
12
+ apiKey: string;
13
+ model: string;
14
+ baseUrl?: string;
15
+ /** Extra default headers for OpenAI-compatible endpoints (e.g. OpenRouter `X-Title`). */
16
+ headers?: Record<string, string>;
17
+ }
18
+ export declare function createChatModel(cfg: ChatModelConfig): ChatModel;
19
+ //# sourceMappingURL=factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../src/ai/llm/factory.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAI5C,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,WAAW,CAAC;AAElD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,YAAY,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yFAAyF;IACzF,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,eAAe,GAAG,SAAS,CAgB/D"}
@@ -0,0 +1,20 @@
1
+ import { OpenAIChatModel } from "./openai.js";
2
+ import { AnthropicChatModel } from "./anthropic.js";
3
+ export function createChatModel(cfg) {
4
+ switch (cfg.provider) {
5
+ case "anthropic":
6
+ return new AnthropicChatModel({ apiKey: cfg.apiKey, model: cfg.model, baseUrl: cfg.baseUrl });
7
+ case "openai":
8
+ return new OpenAIChatModel({
9
+ apiKey: cfg.apiKey,
10
+ model: cfg.model,
11
+ baseUrl: cfg.baseUrl,
12
+ headers: cfg.headers,
13
+ });
14
+ default: {
15
+ const _exhaustive = cfg.provider;
16
+ throw new Error(`unknown provider: ${String(_exhaustive)}`);
17
+ }
18
+ }
19
+ }
20
+ //# sourceMappingURL=factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../../src/ai/llm/factory.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAapD,MAAM,UAAU,eAAe,CAAC,GAAoB;IAClD,QAAQ,GAAG,CAAC,QAAQ,EAAE,CAAC;QACrB,KAAK,WAAW;YACd,OAAO,IAAI,kBAAkB,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QAChG,KAAK,QAAQ;YACX,OAAO,IAAI,eAAe,CAAC;gBACzB,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,KAAK,EAAE,GAAG,CAAC,KAAK;gBAChB,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,OAAO,EAAE,GAAG,CAAC,OAAO;aACrB,CAAC,CAAC;QACL,OAAO,CAAC,CAAC,CAAC;YACR,MAAM,WAAW,GAAU,GAAG,CAAC,QAAQ,CAAC;YACxC,MAAM,IAAI,KAAK,CAAC,qBAAqB,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * OpenAI-compatible transport. Wraps the `openai` SDK pointed at any
3
+ * OpenAI-compatible endpoint (OpenAI, OpenRouter, Fireworks, a LiteLLM proxy,
4
+ * Groq, Together, DeepSeek, a local server, …) — the user picks the endpoint
5
+ * with `baseUrl` and authenticates with their own key.
6
+ *
7
+ * This adapter reproduces graft's historical wire behavior exactly: temperature
8
+ * is forwarded, cache breakpoints become `cache_control` content parts (which
9
+ * OpenRouter forwards to Anthropic), and cached tokens are subtracted out of the
10
+ * input count so {@link Usage.input} is uncached-only.
11
+ */
12
+ import OpenAI from "openai";
13
+ import type { ChatModel, ChatRequest, ChatResponse } from "./types.js";
14
+ export interface OpenAIChatModelOptions {
15
+ apiKey: string;
16
+ model: string;
17
+ baseUrl?: string;
18
+ /** Stable manifest label; defaults to `openai:<model>`. */
19
+ label?: string;
20
+ /** Extra default headers (e.g. OpenRouter's `X-Title`). */
21
+ headers?: Record<string, string>;
22
+ /** Inject a pre-built client (tests pass a stub; production omits it). */
23
+ client?: OpenAI;
24
+ }
25
+ export declare class OpenAIChatModel implements ChatModel {
26
+ readonly label: string;
27
+ private client;
28
+ private model;
29
+ constructor(opts: OpenAIChatModelOptions);
30
+ create(req: ChatRequest): Promise<ChatResponse>;
31
+ private fromResponse;
32
+ }
33
+ //# sourceMappingURL=openai.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../../src/ai/llm/openai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAsC,MAAM,YAAY,CAAC;AAM3G,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AA4CD,qBAAa,eAAgB,YAAW,SAAS;IAC/C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;gBAEV,IAAI,EAAE,sBAAsB;IAYlC,MAAM,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IA2BrD,OAAO,CAAC,YAAY;CA6CrB"}