@nanonets/graft 0.4.5 → 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 +60 -14
- 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 +19 -34
- package/dist/ai/synthesize.js.map +1 -1
- package/dist/ask/ask.d.ts +33 -0
- package/dist/ask/ask.d.ts.map +1 -1
- package/dist/ask/ask.js +119 -9
- 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/ask/graphrank.d.ts.map +1 -1
- package/dist/ask/graphrank.js +1 -13
- package/dist/ask/graphrank.js.map +1 -1
- package/dist/claude/format.d.ts +17 -0
- package/dist/claude/format.d.ts.map +1 -1
- package/dist/claude/format.js +49 -2
- package/dist/claude/format.js.map +1 -1
- package/dist/claude/hooks.d.ts.map +1 -1
- package/dist/claude/hooks.js +16 -15
- package/dist/claude/hooks.js.map +1 -1
- package/dist/claude/skill-template.d.ts.map +1 -1
- package/dist/claude/skill-template.js +28 -13
- package/dist/claude/skill-template.js.map +1 -1
- package/dist/claude/state.d.ts +4 -0
- package/dist/claude/state.d.ts.map +1 -1
- package/dist/claude/state.js +1 -1
- package/dist/claude/state.js.map +1 -1
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +100 -9
- package/dist/cli.js.map +1 -1
- package/dist/engine.d.ts +5 -3
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +31 -20
- package/dist/engine.js.map +1 -1
- package/dist/graph/bindings.d.ts +42 -0
- package/dist/graph/bindings.d.ts.map +1 -0
- package/dist/graph/bindings.js +304 -0
- package/dist/graph/bindings.js.map +1 -0
- package/dist/graph/extract.d.ts +17 -1
- package/dist/graph/extract.d.ts.map +1 -1
- package/dist/graph/extract.js +47 -5
- package/dist/graph/extract.js.map +1 -1
- package/dist/graph/map.d.ts +71 -0
- package/dist/graph/map.d.ts.map +1 -0
- package/dist/graph/map.js +150 -0
- package/dist/graph/map.js.map +1 -0
- package/dist/graph/relations.d.ts +14 -0
- package/dist/graph/relations.d.ts.map +1 -0
- package/dist/graph/relations.js +8 -0
- package/dist/graph/relations.js.map +1 -0
- package/dist/graph/resolve.d.ts.map +1 -1
- package/dist/graph/resolve.js +0 -0
- package/dist/graph/resolve.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/graph/traverse.d.ts.map +1 -1
- package/dist/graph/traverse.js +1 -12
- package/dist/graph/traverse.js.map +1 -1
- package/dist/hosts/instructions.d.ts.map +1 -1
- package/dist/hosts/instructions.js +13 -6
- package/dist/hosts/instructions.js.map +1 -1
- 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/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +2 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools.d.ts +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +80 -7
- package/dist/mcp/tools.js.map +1 -1
- package/dist/search/grep-cli.d.ts +32 -0
- package/dist/search/grep-cli.d.ts.map +1 -0
- package/dist/search/grep-cli.js +101 -0
- package/dist/search/grep-cli.js.map +1 -0
- package/dist/search/grep.d.ts +55 -0
- package/dist/search/grep.d.ts.map +1 -0
- package/dist/search/grep.js +126 -0
- package/dist/search/grep.js.map +1 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
<img src="https://img.shields.io/badge/telemetry-none-546FFF?style=for-the-badge" />
|
|
18
18
|
</p>
|
|
19
19
|
|
|
20
|
-
<!--
|
|
21
|
-
**
|
|
20
|
+
<!-- numbers from the committed run in bench/results/ (2026-07-22, 162 agent runs, 2 repos) — see Benchmark below -->
|
|
21
|
+
**32% less cost · 46% fewer tool calls · 60% lower latency** with the graph bundle pushed up front, at equal correctness — or **16% less cost with correctness up 5 points** when the agent pulls from the graph on demand. Measured against the same agent going in cold.
|
|
22
22
|
|
|
23
23
|
</div>
|
|
24
24
|
|
|
@@ -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).
|
|
@@ -192,6 +192,13 @@ graft callers <symbol> [dir] # who calls/references/imports/implements/e
|
|
|
192
192
|
graft callees <symbol> [dir] # what a symbol calls/references/imports/implements/extends (no LLM, no key)
|
|
193
193
|
graft impact <symbol> [dir] -d N # BFS over incoming edges — who breaks if this symbol changes (no LLM, no key)
|
|
194
194
|
|
|
195
|
+
graft grep "<regex>" [dir] # exhaustive regex search over indexed files, grouped by enclosing symbol (no LLM, no key)
|
|
196
|
+
graft grep "<regex>" --in <path> # narrow to files whose path contains this substring
|
|
197
|
+
graft grep "<regex>" -i --fixed # case-insensitive; treat the pattern as a literal string, not a regex
|
|
198
|
+
|
|
199
|
+
graft map [dir] # token-budgeted repo orientation — dir clusters, hubs, hotspots (no LLM, no key)
|
|
200
|
+
graft map --max-dirs N # raise/lower the number of directories shown
|
|
201
|
+
|
|
195
202
|
graft check [dir] # fail (exit 1) if graft/ has drifted from the code
|
|
196
203
|
graft check --json # print the drift report as JSON
|
|
197
204
|
|
|
@@ -212,6 +219,44 @@ graft --dir <path> # use a context dir other than <repo>/graft
|
|
|
212
219
|
graft --version, -v # print the installed version and exit
|
|
213
220
|
```
|
|
214
221
|
|
|
222
|
+
Method calls resolve through the receiver's type — constructor assignments
|
|
223
|
+
(`self.router = APIRouter()`) and type annotations, not just the call-site
|
|
224
|
+
name — so `callers`/`impact`/`grep --in` return calls bound to the right
|
|
225
|
+
type on method-heavy code, not every method anywhere with that name.
|
|
226
|
+
|
|
227
|
+
## Search & orient (`graft grep` / `graft map`)
|
|
228
|
+
|
|
229
|
+
`graft grep "<regex>"` is exhaustive over every indexed file and groups hits
|
|
230
|
+
by enclosing symbol, ranked by the same in-edge coupling `graft map` uses —
|
|
231
|
+
built for "every occurrence of this pattern" tasks where `graft ask`'s
|
|
232
|
+
ranked top-N isn't enough:
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
"NEEDLE" — 2 hits in 2 symbols across 1 files (searched 1 indexed files)
|
|
236
|
+
|
|
237
|
+
heavilyCalled · function · src/a.ts:L1-L3 · 3 in-edges
|
|
238
|
+
L2: console.log("NEEDLE hit in heavilyCalled");
|
|
239
|
+
|
|
240
|
+
rarelyCalled · function · src/a.ts:L4-L6 · 0 in-edges
|
|
241
|
+
L5: console.log("NEEDLE hit in rarelyCalled");
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
`graft map` is a token-budgeted first look at a repo — directory clusters
|
|
245
|
+
with file/symbol counts, each dir's local hubs, and the global hotspots —
|
|
246
|
+
all ranked by in-degree, no LLM, no key:
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
repo map — 105 files · 582 symbols · 1815 edges · typescript
|
|
250
|
+
|
|
251
|
+
src/ 54 files · 414 symbols hubs: set (bindings.ts, 38←), contextDirFor (node-file.ts, 11←), wiringPath (write.ts, 10←)
|
|
252
|
+
test/ 36 files · 68 symbols hubs: edge (graph-traverse.test.ts, 4←), graphOf (graph-traverse.test.ts, 4←), nodeStub (graph-traverse.test.ts, 3←)
|
|
253
|
+
viewer/ 5 files · 58 symbols hubs: $ (main.ts, 9←), activeGraph (main.ts, 5←), cvar (data.ts, 5←)
|
|
254
|
+
bench/ 8 files · 42 symbols hubs: makeClient (llm.ts, 3←), buildMarkdown (report.ts, 2←), judge (judge.ts, 2←)
|
|
255
|
+
scripts/ 2 files · 0 symbols
|
|
256
|
+
|
|
257
|
+
hotspots: set · method · src/graph/bindings.ts:L21-L23 · 38← contextDirFor · function · src/context/node-file.ts:L100-L103 · 11← wiringPath · function · src/graph/write.ts:L20-L22 · 10← ...
|
|
258
|
+
```
|
|
259
|
+
|
|
215
260
|
## Visualize it (`graft viz`)
|
|
216
261
|
|
|
217
262
|
`graft viz` opens a local, interactive view of both graphs — no install, no dev
|
|
@@ -245,22 +290,23 @@ normalized on load — no regeneration needed.
|
|
|
245
290
|
|
|
246
291
|
## Benchmark
|
|
247
292
|
|
|
248
|
-
|
|
293
|
+
The claim Graft has to earn is simple: an agent that gets its context from the graph is cheaper and faster without getting more answers wrong. The harness runs every task through three arms of the same Claude Sonnet 5 agent with the same file tools: **cold** (explores from zero), **graph (push)** (a `graft ask --source` bundle injected up front), and **graft (pull)** (graft_ask/graft_skeleton tools, nothing injected — context paid for only when asked). An Opus 4.8 judge scores correctness with a required-keyword floor so a fast-but-wrong answer cannot win. Cost is cache-aware (reads ≈0.1×, writes 1.25×) — the billing model agents actually run under.
|
|
249
294
|
|
|
250
|
-
|
|
295
|
+
Committed run: `bench/results/2026-07-22T13-19-14-246Z` — 162 runs across two repos (graft itself and a real Node/Express auth service), 3 trials each, tasks split between localized (single-file) and multi-file questions.
|
|
251
296
|
|
|
252
|
-
| Metric | Cold | Graph |
|
|
297
|
+
| Metric (mean/task) | Cold | Graph (push) | Graft (pull) |
|
|
253
298
|
|---|---|---|---|
|
|
254
|
-
|
|
|
255
|
-
|
|
|
256
|
-
|
|
|
257
|
-
|
|
|
299
|
+
| Cost ($) | 0.0429 | **0.0292 (−32%)** | 0.0362 (−16%) |
|
|
300
|
+
| Uncached input tokens | 8,070 | **4,650 (−42%)** | 5,965 (−26%) |
|
|
301
|
+
| Tool calls | 4.2 | **2.3 (−46%)** | 4.2 (−1%) |
|
|
302
|
+
| Latency (s) | 39.8 | **15.8 (−60%)** | 39.5 (−1%) |
|
|
303
|
+
| Correctness | 93% | 93% | **98% (+5 pts)** |
|
|
258
304
|
|
|
259
|
-
|
|
305
|
+
Push is the cost/latency win; pull is the correctness win (and never lost to cold anywhere, including single-file tasks). On one corpus push traded 3 correctness points for its savings — the per-corpus and per-locality splits are in the committed results, not averaged away. Reproduce it yourself:
|
|
260
306
|
|
|
261
307
|
```bash
|
|
262
|
-
npm run bench -- --smoke # 1 corpus, 1 task, a quick plumbing check
|
|
263
|
-
npm run bench # full: all corpora, all tasks
|
|
308
|
+
npm run bench -- --smoke # 1 corpus, 1 task, all arms — a quick plumbing check
|
|
309
|
+
npm run bench # full: all corpora, all tasks, 3 arms × 3 trials
|
|
264
310
|
```
|
|
265
311
|
|
|
266
312
|
See [`bench/README.md`](bench/README.md) for the method and how to add your own repo.
|
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"}
|