@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
package/README.md
CHANGED
|
@@ -113,7 +113,7 @@ Graft builds that understanding **once** and writes it into your repo as a folde
|
|
|
113
113
|
- **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
114
|
- **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
115
|
- **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
|
|
116
|
+
- **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
117
|
|
|
118
118
|
---
|
|
119
119
|
|
|
@@ -171,7 +171,7 @@ When the wiring graph exists, `graft check` validates it too — structural drif
|
|
|
171
171
|
## What runs where
|
|
172
172
|
|
|
173
173
|
- **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
|
|
174
|
+
- **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
175
|
- **No telemetry** and no analytics — the only network calls are the LLM requests you configured.
|
|
176
176
|
|
|
177
177
|
See [`.env.example`](.env.example) for the full list of settings (model, base URL, graph directory).
|
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
|
|
25
|
-
export declare class
|
|
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(
|
|
44
|
+
constructor(model: ChatModel);
|
|
29
45
|
describeFile(input: FileCruxInput): Promise<NodeCrux[]>;
|
|
30
46
|
}
|
|
31
47
|
//# sourceMappingURL=crux.d.ts.map
|
package/dist/ai/crux.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crux.d.ts","sourceRoot":"","sources":["../../src/ai/crux.ts"],"names":[],"mappings":"
|
|
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,
|
|
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
|
-
|
|
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
|
-
/**
|
|
48
|
-
function parseResults(
|
|
49
|
-
|
|
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
|
|
65
|
-
export class
|
|
66
|
-
client;
|
|
61
|
+
/** Crux summarizer backed by any {@link ChatModel} via forced tool calling. */
|
|
62
|
+
export class ChatCruxSummarizer {
|
|
67
63
|
model;
|
|
68
|
-
constructor(
|
|
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
|
|
80
|
-
model: this.model,
|
|
70
|
+
const res = await this.model.create({
|
|
81
71
|
temperature: 0,
|
|
82
|
-
|
|
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(
|
|
86
|
+
return parseResults(res.toolCalls[0]?.args);
|
|
89
87
|
}
|
|
90
88
|
}
|
|
91
89
|
//# sourceMappingURL=crux.js.map
|
package/dist/ai/crux.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crux.js","sourceRoot":"","sources":["../../src/ai/crux.ts"],"names":[],"mappings":"
|
|
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"}
|
|
@@ -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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"}
|