@nanonets/graft 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/ai/crux.d.ts +20 -4
- package/dist/ai/crux.d.ts.map +1 -1
- package/dist/ai/crux.js +39 -41
- package/dist/ai/crux.js.map +1 -1
- package/dist/ai/llm/anthropic.d.ts +37 -0
- package/dist/ai/llm/anthropic.d.ts.map +1 -0
- package/dist/ai/llm/anthropic.js +141 -0
- package/dist/ai/llm/anthropic.js.map +1 -0
- package/dist/ai/llm/factory.d.ts +19 -0
- package/dist/ai/llm/factory.d.ts.map +1 -0
- package/dist/ai/llm/factory.js +20 -0
- package/dist/ai/llm/factory.js.map +1 -0
- package/dist/ai/llm/openai.d.ts +33 -0
- package/dist/ai/llm/openai.d.ts.map +1 -0
- package/dist/ai/llm/openai.js +146 -0
- package/dist/ai/llm/openai.js.map +1 -0
- package/dist/ai/llm/types.d.ts +113 -0
- package/dist/ai/llm/types.d.ts.map +1 -0
- package/dist/ai/llm/types.js +22 -0
- package/dist/ai/llm/types.js.map +1 -0
- package/dist/ai/providers.d.ts +37 -15
- package/dist/ai/providers.d.ts.map +1 -1
- package/dist/ai/providers.js +30 -5
- package/dist/ai/providers.js.map +1 -1
- package/dist/ai/summarize.d.ts +4 -4
- package/dist/ai/summarize.d.ts.map +1 -1
- package/dist/ai/summarize.js +6 -13
- package/dist/ai/summarize.js.map +1 -1
- package/dist/ai/synthesize.d.ts +12 -4
- package/dist/ai/synthesize.d.ts.map +1 -1
- package/dist/ai/synthesize.js +18 -34
- package/dist/ai/synthesize.js.map +1 -1
- package/dist/ask/ask.js +14 -2
- package/dist/ask/ask.js.map +1 -1
- package/dist/ask/fuse.d.ts +96 -0
- package/dist/ask/fuse.d.ts.map +1 -0
- package/dist/ask/fuse.js +184 -0
- package/dist/ask/fuse.js.map +1 -0
- package/dist/claude/format.d.ts.map +1 -1
- package/dist/claude/format.js +10 -2
- package/dist/claude/format.js.map +1 -1
- package/dist/claude/skill-template.d.ts.map +1 -1
- package/dist/claude/skill-template.js +21 -16
- package/dist/claude/skill-template.js.map +1 -1
- package/dist/cli.js +25 -7
- package/dist/cli.js.map +1 -1
- package/dist/engine.d.ts +4 -3
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +30 -19
- package/dist/engine.js.map +1 -1
- package/dist/graph/scopes.d.ts +15 -0
- package/dist/graph/scopes.d.ts.map +1 -0
- package/dist/graph/scopes.js +346 -0
- package/dist/graph/scopes.js.map +1 -0
- package/dist/index.d.ts +9 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
|
@@ -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"}
|
package/dist/ai/providers.d.ts
CHANGED
|
@@ -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.
|
|
6
|
-
*
|
|
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
|
-
/**
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
/**
|
|
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
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
33
|
-
readonly
|
|
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;
|
|
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"}
|
package/dist/ai/providers.js
CHANGED
|
@@ -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
|
-
|
|
3
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
package/dist/ai/providers.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"providers.js","sourceRoot":"","sources":["../../src/ai/providers.ts"],"names":[],"mappings":"
|
|
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"}
|
package/dist/ai/summarize.d.ts
CHANGED
|
@@ -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
|
|
17
|
-
export declare class
|
|
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(
|
|
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":"
|
|
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"}
|
package/dist/ai/summarize.js
CHANGED
|
@@ -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
|
|
18
|
-
export class
|
|
19
|
-
client;
|
|
16
|
+
/** Summarizer backed by any {@link ChatModel} (a plain-text completion). */
|
|
17
|
+
export class ChatSummarizer {
|
|
20
18
|
model;
|
|
21
|
-
constructor(
|
|
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
|
|
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
|
|
31
|
+
return res.text.trim();
|
|
39
32
|
}
|
|
40
33
|
}
|
|
41
34
|
//# sourceMappingURL=summarize.js.map
|
package/dist/ai/summarize.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"summarize.js","sourceRoot":"","sources":["../../src/ai/summarize.ts"],"names":[],"mappings":"
|
|
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"}
|
package/dist/ai/synthesize.d.ts
CHANGED
|
@@ -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
|
|
26
|
-
export declare class
|
|
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(
|
|
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":"
|
|
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"}
|
package/dist/ai/synthesize.js
CHANGED
|
@@ -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
|
-
|
|
95
|
-
|
|
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(
|
|
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
|
|
113
|
-
model: this.model,
|
|
94
|
+
const res = await this.model.create({
|
|
114
95
|
temperature: 0,
|
|
115
|
-
|
|
116
|
-
|
|
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 =
|
|
123
|
-
if (!call
|
|
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
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"synthesize.js","sourceRoot":"","sources":["../../src/ai/synthesize.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"synthesize.js","sourceRoot":"","sources":["../../src/ai/synthesize.ts"],"names":[],"mappings":"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"}
|
package/dist/ask/ask.js
CHANGED
|
@@ -589,7 +589,7 @@ export function formatAsk(r) {
|
|
|
589
589
|
.join("\n")
|
|
590
590
|
: "";
|
|
591
591
|
if (r.hits.length === 0) {
|
|
592
|
-
return `${head}\n\n${noteBlock || "no matches."}`;
|
|
592
|
+
return `${head}\n\n${noteBlock || "no matches."}${escalationNudge(r)}\n`;
|
|
593
593
|
}
|
|
594
594
|
const lines = noteBlock ? [head, "", noteBlock, ""] : [head, ""];
|
|
595
595
|
if (r.mode === "structural") {
|
|
@@ -614,7 +614,19 @@ export function formatAsk(r) {
|
|
|
614
614
|
});
|
|
615
615
|
}
|
|
616
616
|
const body = lines.join("\n").trimEnd();
|
|
617
|
-
return body + savingsFooter(r, body) + "\n";
|
|
617
|
+
return body + savingsFooter(r, body) + escalationNudge(r) + "\n";
|
|
618
|
+
}
|
|
619
|
+
/** When a lexical `ask` returns thin/no results, the productive next move is a
|
|
620
|
+
* different TOOL, not a re-phrased ask — re-asking is the "over-ask" trap that
|
|
621
|
+
* inflates cost. Surface the escalation in the output so the agent switches to
|
|
622
|
+
* grep/skeleton/callers instead. Silent when the result is already rich (>3
|
|
623
|
+
* hits) or in structural mode (a resolved who-calls IS the answer). */
|
|
624
|
+
function escalationNudge(r) {
|
|
625
|
+
if ((r.mode !== "lexical" && r.mode !== "empty") || r.hits.length > 3)
|
|
626
|
+
return "";
|
|
627
|
+
const n = r.hits.length;
|
|
628
|
+
return (`\n\n[graft] ${n === 0 ? "no hits" : `only ${n} hit${n === 1 ? "" : "s"}`} — don't re-ask with new wording; switch tool: ` +
|
|
629
|
+
"`graft grep \"<literal>\"` for every occurrence · `graft skeleton <file>` for a file's full API · `graft callers <symbol>` for who-uses.");
|
|
618
630
|
}
|
|
619
631
|
/** The one-line token-saving estimate `ask` appends in retriever mode, so the
|
|
620
632
|
* agent gets the number for free in the tool output — no extra work on its end.
|