@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
@@ -0,0 +1,146 @@
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
+ const PROVIDER = "openai";
14
+ /** Synthetic tool used to coerce a plain JSON object out of `{ kind: "json" }`. */
15
+ const JSON_TOOL = "emit_json";
16
+ /** A text content part, optionally carrying a cache breakpoint (OpenRouter passthrough). */
17
+ function textPart(text, cache) {
18
+ return cache
19
+ ? [{ type: "text", text, cache_control: { type: "ephemeral" } }]
20
+ : text;
21
+ }
22
+ function toChatMessage(m) {
23
+ switch (m.role) {
24
+ case "system":
25
+ return { role: "system", content: textPart(m.content, m.cacheBreakpoint) };
26
+ case "user":
27
+ return { role: "user", content: textPart(m.content, m.cacheBreakpoint) };
28
+ case "tool":
29
+ return {
30
+ role: "tool",
31
+ tool_call_id: m.toolCallId ?? "",
32
+ content: textPart(m.content, m.cacheBreakpoint),
33
+ };
34
+ case "assistant": {
35
+ if (m.providerRaw?.provider === PROVIDER)
36
+ return m.providerRaw.raw;
37
+ const tool_calls = m.toolCalls?.map((tc) => ({
38
+ id: tc.id,
39
+ type: "function",
40
+ function: { name: tc.name, arguments: JSON.stringify(tc.args ?? {}) },
41
+ }));
42
+ return {
43
+ role: "assistant",
44
+ content: m.content || null,
45
+ ...(tool_calls?.length ? { tool_calls } : {}),
46
+ };
47
+ }
48
+ }
49
+ }
50
+ function toChatTool(t) {
51
+ return { type: "function", function: { name: t.name, description: t.description, parameters: t.parameters } };
52
+ }
53
+ export class OpenAIChatModel {
54
+ label;
55
+ client;
56
+ model;
57
+ constructor(opts) {
58
+ this.model = opts.model;
59
+ this.label = opts.label ?? `${PROVIDER}:${opts.model}`;
60
+ this.client =
61
+ opts.client ??
62
+ new OpenAI({
63
+ apiKey: opts.apiKey,
64
+ baseURL: opts.baseUrl,
65
+ defaultHeaders: opts.headers,
66
+ });
67
+ }
68
+ async create(req) {
69
+ const messages = req.messages.map(toChatMessage);
70
+ const tools = req.tools ? req.tools.map(toChatTool) : undefined;
71
+ const params = { model: this.model, messages };
72
+ if (req.temperature !== undefined)
73
+ params.temperature = req.temperature;
74
+ if (req.maxTokens !== undefined)
75
+ params.max_tokens = req.maxTokens;
76
+ const fmt = req.responseFormat ?? { kind: "text" };
77
+ if (fmt.kind === "json") {
78
+ // Coerce JSON via a forced synthetic tool — the one structured-output
79
+ // mechanism shared with Anthropic (no reliance on `response_format`).
80
+ params.tools = [
81
+ ...(tools ?? []),
82
+ { type: "function", function: { name: JSON_TOOL, description: "Return the answer as a JSON object.", parameters: { type: "object", additionalProperties: true } } },
83
+ ];
84
+ params.tool_choice = { type: "function", function: { name: JSON_TOOL } };
85
+ }
86
+ else if (fmt.kind === "tool") {
87
+ params.tools = tools;
88
+ params.tool_choice = { type: "function", function: { name: fmt.name } };
89
+ }
90
+ else if (tools) {
91
+ params.tools = tools;
92
+ }
93
+ const resp = await this.client.chat.completions.create(params);
94
+ return this.fromResponse(resp, fmt.kind);
95
+ }
96
+ fromResponse(resp, format) {
97
+ const choice = resp.choices[0];
98
+ const msg = choice?.message;
99
+ const rawCalls = (msg?.tool_calls ?? []).filter((c) => c.type === "function");
100
+ const parse = (s) => {
101
+ try {
102
+ return JSON.parse(s || "{}");
103
+ }
104
+ catch {
105
+ return {};
106
+ }
107
+ };
108
+ let text = msg?.content ?? "";
109
+ let toolCalls = rawCalls.map((c) => ({
110
+ id: c.id,
111
+ name: c.function.name,
112
+ args: parse(c.function.arguments),
113
+ }));
114
+ if (format === "json") {
115
+ // Surface the synthetic tool's object as JSON text; hide it from `toolCalls`.
116
+ const jsonCall = toolCalls.find((c) => c.name === JSON_TOOL);
117
+ if (jsonCall)
118
+ text = JSON.stringify(jsonCall.args);
119
+ toolCalls = toolCalls.filter((c) => c.name !== JSON_TOOL);
120
+ }
121
+ return {
122
+ text,
123
+ toolCalls,
124
+ usage: normalizeUsage(resp.usage),
125
+ stopReason: choice?.finish_reason ?? null,
126
+ assistant: {
127
+ role: "assistant",
128
+ content: msg?.content ?? "",
129
+ toolCalls: toolCalls.length ? toolCalls : undefined,
130
+ providerRaw: { provider: PROVIDER, raw: msg },
131
+ },
132
+ };
133
+ }
134
+ }
135
+ /** Cached tokens are inside `prompt_tokens`; subtract so `input` is uncached-only. */
136
+ function normalizeUsage(u) {
137
+ const prompt = u?.prompt_tokens ?? 0;
138
+ const cacheRead = u?.prompt_tokens_details?.cached_tokens ?? 0;
139
+ return {
140
+ input: Math.max(0, prompt - cacheRead),
141
+ output: u?.completion_tokens ?? 0,
142
+ cacheRead,
143
+ cacheCreate: 0,
144
+ };
145
+ }
146
+ //# sourceMappingURL=openai.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai.js","sourceRoot":"","sources":["../../../src/ai/llm/openai.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Provider-neutral chat transport.
3
+ *
4
+ * Every LLM call in graft — the engine's single-shot summarize/synthesize/crux
5
+ * ops AND the benchmark's multi-turn ReAct loop — goes through one interface,
6
+ * {@link ChatModel}. Adapters translate this neutral shape to a concrete SDK
7
+ * (OpenAI-compatible or native Anthropic). Nothing above this layer knows which
8
+ * provider is in play, so adding a provider is a new adapter, never a change to
9
+ * a call site.
10
+ *
11
+ * The design carries three affordances that let a single interface serve the
12
+ * multi-turn loop as cleanly as a one-shot call:
13
+ * - {@link ChatResponse.assistant} — a ready-to-append assistant message, so a
14
+ * caller continues a conversation without touching provider shapes.
15
+ * - {@link Message.providerRaw} — opaque verbatim replay of a message the same
16
+ * adapter produced (preserves Anthropic thinking-block signatures, which must
17
+ * be echoed back unchanged).
18
+ * - {@link Usage} normalized to uncached-input, so token accounting is identical
19
+ * across providers.
20
+ */
21
+ /** A single tool invocation requested by the model. `args` is ALREADY parsed. */
22
+ export interface ToolCall {
23
+ id: string;
24
+ name: string;
25
+ /** Parsed argument object. Adapters (de)serialize to their wire format. */
26
+ args: unknown;
27
+ }
28
+ /** One turn in a conversation, in provider-neutral form. */
29
+ export interface Message {
30
+ role: "system" | "user" | "assistant" | "tool";
31
+ /** Plain-text content. Empty string when a turn is purely tool calls/results. */
32
+ content: string;
33
+ /** Assistant turns only: tool calls the model asked for. */
34
+ toolCalls?: ToolCall[];
35
+ /** Tool turns only: the id of the {@link ToolCall} this result answers. */
36
+ toolCallId?: string;
37
+ /**
38
+ * Request a provider cache marker (`cache_control: ephemeral`) on THIS message.
39
+ * The transport only understands this per-message flag; any sliding/rotation
40
+ * policy lives in the caller (see the bench loop).
41
+ */
42
+ cacheBreakpoint?: boolean;
43
+ /**
44
+ * Opaque, provider-tagged payload from the adapter that produced this message.
45
+ * When the SAME adapter replays it, the original wire form is sent verbatim
46
+ * (preserving e.g. Anthropic thinking blocks + signatures). A different adapter
47
+ * ignores it and reconstructs from {@link content}/{@link toolCalls}.
48
+ */
49
+ providerRaw?: {
50
+ provider: string;
51
+ raw: unknown;
52
+ };
53
+ }
54
+ /** A tool the model may call. `parameters` is a JSON Schema object. */
55
+ export interface ToolSpec {
56
+ name: string;
57
+ description: string;
58
+ parameters: Record<string, unknown>;
59
+ }
60
+ /**
61
+ * How the model must shape its reply:
62
+ * - `text` — free-form assistant text.
63
+ * - `json` — a JSON object (mapped to a forced synthetic tool on both providers,
64
+ * since that is the one structured-output mechanism they share).
65
+ * - `tool` — force a call to the named tool (structured output with a schema).
66
+ */
67
+ export type ResponseFormat = {
68
+ kind: "text";
69
+ } | {
70
+ kind: "json";
71
+ } | {
72
+ kind: "tool";
73
+ name: string;
74
+ };
75
+ /**
76
+ * Token accounting, normalized across providers. `input` is UNCACHED input only
77
+ * (OpenAI reports cached tokens inside `prompt_tokens`; Anthropic reports them
78
+ * separately — adapters reconcile so callers can sum these directly).
79
+ */
80
+ export interface Usage {
81
+ input: number;
82
+ output: number;
83
+ cacheRead: number;
84
+ cacheCreate: number;
85
+ }
86
+ export interface ChatRequest {
87
+ messages: Message[];
88
+ tools?: ToolSpec[];
89
+ /** Default: `{ kind: "text" }`. */
90
+ responseFormat?: ResponseFormat;
91
+ /** Providers that reject it (current Anthropic models) drop it in the adapter. */
92
+ temperature?: number;
93
+ /** Max output tokens. Required by Anthropic; adapters supply a default. */
94
+ maxTokens?: number;
95
+ }
96
+ export interface ChatResponse {
97
+ /** Concatenated assistant text (empty when the turn only made tool calls). */
98
+ text: string;
99
+ toolCalls: ToolCall[];
100
+ usage: Usage;
101
+ stopReason: string | null;
102
+ /** The assistant turn as a neutral message — push straight back into `messages`. */
103
+ assistant: Message;
104
+ }
105
+ /**
106
+ * A configured connection to one model. `label` is a stable `<provider>:<model>`
107
+ * string recorded in the graph manifest.
108
+ */
109
+ export interface ChatModel {
110
+ readonly label: string;
111
+ create(req: ChatRequest): Promise<ChatResponse>;
112
+ }
113
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/ai/llm/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,iFAAiF;AACjF,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,IAAI,EAAE,OAAO,CAAC;CACf;AAED,4DAA4D;AAC5D,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC;IAC/C,iFAAiF;IACjF,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;IACvB,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;OAKG;IACH,WAAW,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,OAAO,CAAA;KAAE,CAAC;CAClD;AAED,uEAAuE;AACvE,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AAED;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnC;;;;GAIG;AACH,MAAM,WAAW,KAAK;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;IACnB,mCAAmC;IACnC,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,kFAAkF;IAClF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,KAAK,EAAE,KAAK,CAAC;IACb,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,oFAAoF;IACpF,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;CACjD"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Provider-neutral chat transport.
3
+ *
4
+ * Every LLM call in graft — the engine's single-shot summarize/synthesize/crux
5
+ * ops AND the benchmark's multi-turn ReAct loop — goes through one interface,
6
+ * {@link ChatModel}. Adapters translate this neutral shape to a concrete SDK
7
+ * (OpenAI-compatible or native Anthropic). Nothing above this layer knows which
8
+ * provider is in play, so adding a provider is a new adapter, never a change to
9
+ * a call site.
10
+ *
11
+ * The design carries three affordances that let a single interface serve the
12
+ * multi-turn loop as cleanly as a one-shot call:
13
+ * - {@link ChatResponse.assistant} — a ready-to-append assistant message, so a
14
+ * caller continues a conversation without touching provider shapes.
15
+ * - {@link Message.providerRaw} — opaque verbatim replay of a message the same
16
+ * adapter produced (preserves Anthropic thinking-block signatures, which must
17
+ * be echoed back unchanged).
18
+ * - {@link Usage} normalized to uncached-input, so token accounting is identical
19
+ * across providers.
20
+ */
21
+ export {};
22
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/ai/llm/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG"}
@@ -1,36 +1,58 @@
1
1
  import type { Summarizer } from "./summarize.js";
2
2
  import type { Synthesizer } from "./synthesize.js";
3
+ import type { CruxSummarizer } from "./crux.js";
4
+ import type { ChatModel } from "./llm/types.js";
5
+ import type { ProviderKind } from "./llm/factory.js";
3
6
  /**
4
7
  * User-facing configuration. Anything omitted falls back to environment
5
- * variables and then to sensible defaults. The engine calls OpenRouter, so an
6
- * API key is required for any LLM-backed operation.
8
+ * variables and then to sensible defaults.
9
+ *
10
+ * graft is vendor-neutral: `provider` names only the WIRE FORMAT, not a company.
11
+ * `openai` speaks the OpenAI-compatible API — point `baseUrl` at OpenRouter,
12
+ * Fireworks, a LiteLLM proxy, Groq, a local server, or OpenAI itself, and pass
13
+ * your own key. `anthropic` speaks the native Messages API. Any LLM-backed
14
+ * operation needs an API key.
7
15
  */
8
16
  export interface EngineConfig {
9
17
  /** Where the graph lives. Env: GRAFT_DIR. Default: `<repo>/.context`. */
10
18
  contextDir?: string;
11
- /** OpenRouter key for extraction + summarization. Env: OPENROUTER_API_KEY. */
12
- openrouterApiKey?: string;
13
- /** OpenRouter model id. Env: GRAFT_OPENROUTER_MODEL. Default: openai/gpt-4o-mini */
14
- openrouterModel?: string;
15
- /** OpenRouter API base URL. Env: OPENROUTER_BASE_URL. Default: https://openrouter.ai/api/v1 */
16
- openrouterBaseUrl?: string;
17
- /** Override the synthesizer (defaults to OpenRouter). */
19
+ /** Wire format / SDK. Env: GRAFT_PROVIDER. Default: `openai`. */
20
+ provider?: ProviderKind;
21
+ /** API key for the chosen provider. Env: GRAFT_API_KEY (legacy: OPENROUTER_API_KEY). */
22
+ apiKey?: string;
23
+ /** Model id. Env: GRAFT_MODEL. Provider-specific default. */
24
+ model?: string;
25
+ /** Base URL for OpenAI-compatible endpoints. Env: GRAFT_BASE_URL. */
26
+ baseUrl?: string;
27
+ /** Override the whole transport (skips provider/apiKey/baseUrl). */
28
+ chatModel?: ChatModel;
29
+ /** Override the synthesizer. */
18
30
  synthesizer?: Synthesizer;
19
- /** Override the code summarizer (defaults to OpenRouter). */
31
+ /** Override the code summarizer. */
20
32
  summarizer?: Summarizer;
33
+ /** Override the per-symbol crux summarizer. */
34
+ cruxSummarizer?: CruxSummarizer;
21
35
  }
22
36
  /** Fully-resolved configuration with all defaults applied. */
23
37
  export interface ResolvedConfig {
24
38
  contextDir?: string;
25
- openrouterApiKey?: string;
26
- openrouterModel: string;
27
- openrouterBaseUrl: string;
39
+ provider: ProviderKind;
40
+ apiKey?: string;
41
+ model: string;
42
+ baseUrl?: string;
43
+ headers?: Record<string, string>;
44
+ /** True when the key came from the deprecated OPENROUTER_* fallback. */
45
+ usedLegacyEnv: boolean;
46
+ chatModel?: ChatModel;
28
47
  synthesizer?: Synthesizer;
29
48
  summarizer?: Summarizer;
49
+ cruxSummarizer?: CruxSummarizer;
30
50
  }
51
+ /** Per-provider default model. */
52
+ export declare const DEFAULT_MODELS: Record<ProviderKind, string>;
31
53
  export declare const DEFAULTS: {
32
- readonly openrouterModel: "openai/gpt-4o-mini";
33
- readonly openrouterBaseUrl: "https://openrouter.ai/api/v1";
54
+ readonly provider: ProviderKind;
55
+ readonly model: string;
34
56
  };
35
57
  /** Merge user config with environment variables and defaults. */
36
58
  export declare function resolveConfig(config?: EngineConfig): ResolvedConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../../src/ai/providers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnD;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oFAAoF;IACpF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,+FAA+F;IAC/F,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAG3B,yDAAyD;IACzD,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,6DAA6D;IAC7D,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED,8DAA8D;AAC9D,MAAM,WAAW,cAAc;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED,eAAO,MAAM,QAAQ;;;CAGX,CAAC;AAEX,iEAAiE;AACjE,wBAAgB,aAAa,CAAC,MAAM,GAAE,YAAiB,GAAG,cAAc,CAYvE"}
1
+ {"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../../src/ai/providers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD;;;;;;;;;GASG;AACH,MAAM,WAAW,YAAY;IAC3B,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,wFAAwF;IACxF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;IAGjB,oEAAoE;IACpE,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,gCAAgC;IAChC,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,oCAAoC;IACpC,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,+CAA+C;IAC/C,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED,8DAA8D;AAC9D,MAAM,WAAW,cAAc;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,YAAY,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,wEAAwE;IACxE,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAID,kCAAkC;AAClC,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAGvD,CAAC;AAEF,eAAO,MAAM,QAAQ;uBACG,YAAY;;CAE1B,CAAC;AAEX,iEAAiE;AACjE,wBAAgB,aAAa,CAAC,MAAM,GAAE,YAAiB,GAAG,cAAc,CAmCvE"}
@@ -1,17 +1,42 @@
1
+ const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
2
+ /** Per-provider default model. */
3
+ export const DEFAULT_MODELS = {
4
+ openai: "openai/gpt-4o-mini",
5
+ anthropic: "claude-sonnet-5",
6
+ };
1
7
  export const DEFAULTS = {
2
- openrouterModel: "openai/gpt-4o-mini",
3
- openrouterBaseUrl: "https://openrouter.ai/api/v1",
8
+ provider: "openai",
9
+ model: DEFAULT_MODELS.openai,
4
10
  };
5
11
  /** Merge user config with environment variables and defaults. */
6
12
  export function resolveConfig(config = {}) {
7
13
  const env = process.env;
14
+ const provider = config.provider ?? env.GRAFT_PROVIDER ?? DEFAULTS.provider;
15
+ const explicitKey = config.apiKey ?? env.GRAFT_API_KEY;
16
+ const legacyKey = env.OPENROUTER_API_KEY;
17
+ const apiKey = explicitKey ?? legacyKey;
18
+ const usedLegacyEnv = !explicitKey && !!legacyKey;
19
+ const model = config.model ?? env.GRAFT_MODEL ?? env.GRAFT_OPENROUTER_MODEL ?? DEFAULT_MODELS[provider];
20
+ let baseUrl = config.baseUrl ?? env.GRAFT_BASE_URL ?? env.OPENROUTER_BASE_URL;
21
+ // Back-compat: an existing setup with only OPENROUTER_API_KEY keeps hitting
22
+ // OpenRouter without any config change.
23
+ if (!baseUrl && provider === "openai" && usedLegacyEnv)
24
+ baseUrl = OPENROUTER_BASE_URL;
25
+ const headers = provider === "openai" && baseUrl?.includes("openrouter.ai")
26
+ ? { "X-Title": "graft" }
27
+ : undefined;
8
28
  return {
9
29
  contextDir: config.contextDir ?? env.GRAFT_DIR,
10
- openrouterApiKey: config.openrouterApiKey ?? env.OPENROUTER_API_KEY,
11
- openrouterModel: config.openrouterModel ?? env.GRAFT_OPENROUTER_MODEL ?? DEFAULTS.openrouterModel,
12
- openrouterBaseUrl: config.openrouterBaseUrl ?? env.OPENROUTER_BASE_URL ?? DEFAULTS.openrouterBaseUrl,
30
+ provider,
31
+ apiKey,
32
+ model,
33
+ baseUrl,
34
+ headers,
35
+ usedLegacyEnv,
36
+ chatModel: config.chatModel,
13
37
  synthesizer: config.synthesizer,
14
38
  summarizer: config.summarizer,
39
+ cruxSummarizer: config.cruxSummarizer,
15
40
  };
16
41
  }
17
42
  //# sourceMappingURL=providers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"providers.js","sourceRoot":"","sources":["../../src/ai/providers.ts"],"names":[],"mappings":"AAoCA,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,eAAe,EAAE,oBAAoB;IACrC,iBAAiB,EAAE,8BAA8B;CACzC,CAAC;AAEX,iEAAiE;AACjE,MAAM,UAAU,aAAa,CAAC,SAAuB,EAAE;IACrD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IACxB,OAAO;QACL,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,GAAG,CAAC,SAAS;QAC9C,gBAAgB,EAAE,MAAM,CAAC,gBAAgB,IAAI,GAAG,CAAC,kBAAkB;QACnE,eAAe,EACb,MAAM,CAAC,eAAe,IAAI,GAAG,CAAC,sBAAsB,IAAI,QAAQ,CAAC,eAAe;QAClF,iBAAiB,EACf,MAAM,CAAC,iBAAiB,IAAI,GAAG,CAAC,mBAAmB,IAAI,QAAQ,CAAC,iBAAiB;QACnF,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,UAAU,EAAE,MAAM,CAAC,UAAU;KAC9B,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"providers.js","sourceRoot":"","sources":["../../src/ai/providers.ts"],"names":[],"mappings":"AAwDA,MAAM,mBAAmB,GAAG,8BAA8B,CAAC;AAE3D,kCAAkC;AAClC,MAAM,CAAC,MAAM,cAAc,GAAiC;IAC1D,MAAM,EAAE,oBAAoB;IAC5B,SAAS,EAAE,iBAAiB;CAC7B,CAAC;AAEF,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,QAAQ,EAAE,QAAwB;IAClC,KAAK,EAAE,cAAc,CAAC,MAAM;CACpB,CAAC;AAEX,iEAAiE;AACjE,MAAM,UAAU,aAAa,CAAC,SAAuB,EAAE;IACrD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IACxB,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAK,GAAG,CAAC,cAA2C,IAAI,QAAQ,CAAC,QAAQ,CAAC;IAE1G,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,aAAa,CAAC;IACvD,MAAM,SAAS,GAAG,GAAG,CAAC,kBAAkB,CAAC;IACzC,MAAM,MAAM,GAAG,WAAW,IAAI,SAAS,CAAC;IACxC,MAAM,aAAa,GAAG,CAAC,WAAW,IAAI,CAAC,CAAC,SAAS,CAAC;IAElD,MAAM,KAAK,GACT,MAAM,CAAC,KAAK,IAAI,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,sBAAsB,IAAI,cAAc,CAAC,QAAQ,CAAC,CAAC;IAE5F,IAAI,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,GAAG,CAAC,cAAc,IAAI,GAAG,CAAC,mBAAmB,CAAC;IAC9E,4EAA4E;IAC5E,wCAAwC;IACxC,IAAI,CAAC,OAAO,IAAI,QAAQ,KAAK,QAAQ,IAAI,aAAa;QAAE,OAAO,GAAG,mBAAmB,CAAC;IAEtF,MAAM,OAAO,GACX,QAAQ,KAAK,QAAQ,IAAI,OAAO,EAAE,QAAQ,CAAC,eAAe,CAAC;QACzD,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE;QACxB,CAAC,CAAC,SAAS,CAAC;IAEhB,OAAO;QACL,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,GAAG,CAAC,SAAS;QAC9C,QAAQ;QACR,MAAM;QACN,KAAK;QACL,OAAO;QACP,OAAO;QACP,aAAa;QACb,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,cAAc,EAAE,MAAM,CAAC,cAAc;KACtC,CAAC;AACJ,CAAC"}
@@ -1,3 +1,4 @@
1
+ import type { ChatModel } from "./llm/types.js";
1
2
  /**
2
3
  * Turns one source-code file into a short prose summary for the knowledge graph.
3
4
  *
@@ -13,11 +14,10 @@ export interface Summarizer {
13
14
  path: string;
14
15
  }): Promise<string>;
15
16
  }
16
- /** Summarizer backed by OpenRouter's OpenAI-compatible chat API. */
17
- export declare class OpenRouterSummarizer implements Summarizer {
18
- private client;
17
+ /** Summarizer backed by any {@link ChatModel} (a plain-text completion). */
18
+ export declare class ChatSummarizer implements Summarizer {
19
19
  private model;
20
- constructor(apiKey: string, model: string, baseUrl?: string);
20
+ constructor(model: ChatModel);
21
21
  summarize(code: string, opts: {
22
22
  path: string;
23
23
  }): Promise<string>;
@@ -1 +1 @@
1
- {"version":3,"file":"summarize.d.ts","sourceRoot":"","sources":["../../src/ai/summarize.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,WAAW,UAAU;IACzB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAClE;AAqBD,oEAAoE;AACpE,qBAAa,oBAAqB,YAAW,UAAU;IACrD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;gBAEV,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,SAAiC;IAS7E,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;CAWvE"}
1
+ {"version":3,"file":"summarize.d.ts","sourceRoot":"","sources":["../../src/ai/summarize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD;;;;;;;;;GASG;AACH,MAAM,WAAW,UAAU;IACzB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAClE;AAqBD,4EAA4E;AAC5E,qBAAa,cAAe,YAAW,UAAU;IACnC,OAAO,CAAC,KAAK;gBAAL,KAAK,EAAE,SAAS;IAE9B,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;CAWvE"}
@@ -1,4 +1,3 @@
1
- import OpenAI from "openai";
2
1
  const SYSTEM_PROMPT = `You document source code for a team knowledge base. Given one source file, write a compact plain-English summary covering:
3
2
  1. The purpose of the file — what it exists to do.
4
3
  2. The key exported functions/classes/types and what each is for.
@@ -14,28 +13,22 @@ function userContent(code, path) {
14
13
  : code;
15
14
  return `File: ${path}\n\n${clipped}`;
16
15
  }
17
- /** Summarizer backed by OpenRouter's OpenAI-compatible chat API. */
18
- export class OpenRouterSummarizer {
19
- client;
16
+ /** Summarizer backed by any {@link ChatModel} (a plain-text completion). */
17
+ export class ChatSummarizer {
20
18
  model;
21
- constructor(apiKey, model, baseUrl = "https://openrouter.ai/api/v1") {
22
- this.client = new OpenAI({
23
- apiKey,
24
- baseURL: baseUrl,
25
- defaultHeaders: { "X-Title": "Context Graph Engine" },
26
- });
19
+ constructor(model) {
27
20
  this.model = model;
28
21
  }
29
22
  async summarize(code, opts) {
30
- const response = await this.client.chat.completions.create({
31
- model: this.model,
23
+ const res = await this.model.create({
32
24
  temperature: 0,
25
+ maxTokens: 2048,
33
26
  messages: [
34
27
  { role: "system", content: SYSTEM_PROMPT },
35
28
  { role: "user", content: userContent(code, opts.path) },
36
29
  ],
37
30
  });
38
- return response.choices[0]?.message?.content?.trim() ?? "";
31
+ return res.text.trim();
39
32
  }
40
33
  }
41
34
  //# sourceMappingURL=summarize.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"summarize.js","sourceRoot":"","sources":["../../src/ai/summarize.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAgB5B,MAAM,aAAa,GAAG;;;;;;sLAMgK,CAAC;AAEvL,gFAAgF;AAChF,MAAM,cAAc,GAAG,MAAM,CAAC;AAE9B,SAAS,WAAW,CAAC,IAAY,EAAE,IAAY;IAC7C,MAAM,OAAO,GACX,IAAI,CAAC,MAAM,GAAG,cAAc;QAC1B,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,qBAAqB,cAAc,cAAc;QACnF,CAAC,CAAC,IAAI,CAAC;IACX,OAAO,SAAS,IAAI,OAAO,OAAO,EAAE,CAAC;AACvC,CAAC;AAED,oEAAoE;AACpE,MAAM,OAAO,oBAAoB;IACvB,MAAM,CAAS;IACf,KAAK,CAAS;IAEtB,YAAY,MAAc,EAAE,KAAa,EAAE,OAAO,GAAG,8BAA8B;QACjF,IAAI,CAAC,MAAM,GAAG,IAAI,MAAM,CAAC;YACvB,MAAM;YACN,OAAO,EAAE,OAAO;YAChB,cAAc,EAAE,EAAE,SAAS,EAAE,sBAAsB,EAAE;SACtD,CAAC,CAAC;QACH,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,IAAY,EAAE,IAAsB;QAClD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;YACzD,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,WAAW,EAAE,CAAC;YACd,QAAQ,EAAE;gBACR,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,aAAa,EAAE;gBAC1C,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE;aACxD;SACF,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC7D,CAAC;CACF"}
1
+ {"version":3,"file":"summarize.js","sourceRoot":"","sources":["../../src/ai/summarize.ts"],"names":[],"mappings":"AAgBA,MAAM,aAAa,GAAG;;;;;;sLAMgK,CAAC;AAEvL,gFAAgF;AAChF,MAAM,cAAc,GAAG,MAAM,CAAC;AAE9B,SAAS,WAAW,CAAC,IAAY,EAAE,IAAY;IAC7C,MAAM,OAAO,GACX,IAAI,CAAC,MAAM,GAAG,cAAc;QAC1B,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,qBAAqB,cAAc,cAAc;QACnF,CAAC,CAAC,IAAI,CAAC;IACX,OAAO,SAAS,IAAI,OAAO,OAAO,EAAE,CAAC;AACvC,CAAC;AAED,4EAA4E;AAC5E,MAAM,OAAO,cAAc;IACL;IAApB,YAAoB,KAAgB;QAAhB,UAAK,GAAL,KAAK,CAAW;IAAG,CAAC;IAExC,KAAK,CAAC,SAAS,CAAC,IAAY,EAAE,IAAsB;QAClD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;YAClC,WAAW,EAAE,CAAC;YACd,SAAS,EAAE,IAAI;YACf,QAAQ,EAAE;gBACR,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,aAAa,EAAE;gBAC1C,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE;aACxD;SACF,CAAC,CAAC;QACH,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IACzB,CAAC;CACF"}
@@ -1,3 +1,12 @@
1
+ /**
2
+ * The synthesis step: turn per-file prose summaries into a CURATED set of graph
3
+ * nodes. This is where granularity is decided — the model groups related files
4
+ * into subsystem nodes, keeps a notable file as its own node when it deserves
5
+ * one, and adds cross-cutting concept nodes, instead of emitting one entity per
6
+ * incidental identifier. Each node is attributed to the source files it is
7
+ * grounded in, so provenance (and staleness) stays exact.
8
+ */
9
+ import type { ChatModel } from "./llm/types.js";
1
10
  /** A directed edge to another node, by node name (resolved to a slug later). */
2
11
  export interface SynthLink {
3
12
  to: string;
@@ -22,11 +31,10 @@ export interface FileSummary {
22
31
  export interface Synthesizer {
23
32
  synthesize(files: FileSummary[]): Promise<SynthNode[]>;
24
33
  }
25
- /** Synthesizer backed by OpenRouter's OpenAI-compatible chat API (tool calling). */
26
- export declare class OpenRouterSynthesizer implements Synthesizer {
27
- private client;
34
+ /** Synthesizer backed by any {@link ChatModel} via forced tool calling. */
35
+ export declare class ChatSynthesizer implements Synthesizer {
28
36
  private model;
29
- constructor(apiKey: string, model: string, baseUrl?: string);
37
+ constructor(model: ChatModel);
30
38
  synthesize(files: FileSummary[]): Promise<SynthNode[]>;
31
39
  }
32
40
  //# sourceMappingURL=synthesize.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"synthesize.d.ts","sourceRoot":"","sources":["../../src/ai/synthesize.ts"],"names":[],"mappings":"AAUA,gFAAgF;AAChF,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,2EAA2E;AAC3E,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,EAAE,SAAS,EAAE,CAAC;CACpB;AAED,mDAAmD;AACnD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;CACxD;AAiGD,oFAAoF;AACpF,qBAAa,qBAAsB,YAAW,WAAW;IACvD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;gBAEV,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,SAAiC;IAK7E,UAAU,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;CAoB7D"}
1
+ {"version":3,"file":"synthesize.d.ts","sourceRoot":"","sources":["../../src/ai/synthesize.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,gFAAgF;AAChF,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,2EAA2E;AAC3E,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,EAAE,SAAS,EAAE,CAAC;CACpB;AAED,mDAAmD;AACnD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;CACxD;AA0FD,2EAA2E;AAC3E,qBAAa,eAAgB,YAAW,WAAW;IACrC,OAAO,CAAC,KAAK;gBAAL,KAAK,EAAE,SAAS;IAE9B,UAAU,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;CAuB7D"}
@@ -1,12 +1,3 @@
1
- /**
2
- * The synthesis step: turn per-file prose summaries into a CURATED set of graph
3
- * nodes. This is where granularity is decided — the model groups related files
4
- * into subsystem nodes, keeps a notable file as its own node when it deserves
5
- * one, and adds cross-cutting concept nodes, instead of emitting one entity per
6
- * incidental identifier. Each node is attributed to the source files it is
7
- * grounded in, so provenance (and staleness) stays exact.
8
- */
9
- import OpenAI from "openai";
10
1
  const SYSTEM_PROMPT = `You build an ARCHITECTURE graph of a codebase from per-file summaries. The reader is an AI agent that will read this graph before working on the code, so it must describe the system at the level a senior engineer would explain it — not file by file.
11
2
 
12
3
  Produce a CURATED set of nodes of mixed granularity:
@@ -90,44 +81,37 @@ function clean(nodes) {
90
81
  }
91
82
  return out;
92
83
  }
93
- const RECORD_TOOL = {
94
- type: "function",
95
- function: {
96
- name: "record_graph",
97
- description: "Record the curated architecture-graph nodes and their links.",
98
- parameters: NODES_SCHEMA,
99
- },
100
- };
101
- /** Synthesizer backed by OpenRouter's OpenAI-compatible chat API (tool calling). */
102
- export class OpenRouterSynthesizer {
103
- client;
84
+ const RECORD_TOOL = "record_graph";
85
+ /** Synthesizer backed by any {@link ChatModel} via forced tool calling. */
86
+ export class ChatSynthesizer {
104
87
  model;
105
- constructor(apiKey, model, baseUrl = "https://openrouter.ai/api/v1") {
106
- this.client = new OpenAI({ apiKey, baseURL: baseUrl, defaultHeaders: { "X-Title": "Context Graph Engine" } });
88
+ constructor(model) {
107
89
  this.model = model;
108
90
  }
109
91
  async synthesize(files) {
110
92
  if (files.length === 0)
111
93
  return [];
112
- const response = await this.client.chat.completions.create({
113
- model: this.model,
94
+ const res = await this.model.create({
114
95
  temperature: 0,
115
- tools: [RECORD_TOOL],
116
- tool_choice: { type: "function", function: { name: "record_graph" } },
96
+ maxTokens: 8192,
97
+ tools: [
98
+ {
99
+ name: RECORD_TOOL,
100
+ description: "Record the curated architecture-graph nodes and their links.",
101
+ parameters: NODES_SCHEMA,
102
+ },
103
+ ],
104
+ responseFormat: { kind: "tool", name: RECORD_TOOL },
117
105
  messages: [
118
106
  { role: "system", content: SYSTEM_PROMPT },
119
107
  { role: "user", content: userContent(files) },
120
108
  ],
121
109
  });
122
- const call = response.choices[0]?.message?.tool_calls?.[0];
123
- if (!call || call.type !== "function")
124
- return [];
125
- try {
126
- return clean(JSON.parse(call.function.arguments).nodes);
127
- }
128
- catch {
110
+ const call = res.toolCalls[0];
111
+ if (!call)
129
112
  return [];
130
- }
113
+ // `args` is already a parsed object — no JSON.parse.
114
+ return clean(call.args?.nodes);
131
115
  }
132
116
  }
133
117
  //# sourceMappingURL=synthesize.js.map