@panaversity/ksor 0.0.16 → 0.0.17
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/CHANGELOG.md +30 -0
- package/dist/cli.mjs +123 -10
- package/package.json +1 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @panaversity/ksor
|
|
2
2
|
|
|
3
|
+
## 0.0.17
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 44feada: Installing ksor no longer pulls 32 MB of vendor SDK
|
|
8
|
+
|
|
9
|
+
`npx @panaversity/ksor init` installed 54 MB across 52 packages. 32 MB of that
|
|
10
|
+
was `@google/genai` and its dependencies — carried by every adopter, including
|
|
11
|
+
the ones who only ever run `init` and `dev` and never reach a served rung.
|
|
12
|
+
|
|
13
|
+
It existed to make two HTTP calls, both already wrapped behind one
|
|
14
|
+
structurally-typed client boundary. Those calls are now spoken directly:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
before 54 MB 52 packages
|
|
18
|
+
after 22 MB 22 packages
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Nothing about the embedding changed, and that was checked first rather than
|
|
22
|
+
assumed: the SDK and the REST endpoint return **byte-identical vectors** for the
|
|
23
|
+
same text, model, dimensionality and task type — a maximum per-component
|
|
24
|
+
difference of 0.000e+0 at 1536 dimensions. So stored embeddings stay valid and a
|
|
25
|
+
calibrated `vector_floor` keeps its meaning. Had they differed by a rounding
|
|
26
|
+
step, this would have quietly invalidated abstention on every existing record.
|
|
27
|
+
|
|
28
|
+
The provider seam is unchanged: a deployment that prefers an SDK can still
|
|
29
|
+
supply one through `clientFactory`. The single live call to the real vendor
|
|
30
|
+
stays where it was, as the tripwire for API drift, and now meets Gemini with
|
|
31
|
+
nothing in between.
|
|
32
|
+
|
|
3
33
|
## 0.0.16
|
|
4
34
|
|
|
5
35
|
### Patch Changes
|
package/dist/cli.mjs
CHANGED
|
@@ -7,7 +7,6 @@ import { z } from "zod";
|
|
|
7
7
|
import pg from "pg";
|
|
8
8
|
import path, { basename, dirname, join, resolve, sep } from "node:path";
|
|
9
9
|
import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
10
|
-
import { GoogleGenAI } from "@google/genai";
|
|
11
10
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
12
11
|
import { createRemoteJWKSet, errors, jwtVerify } from "jose";
|
|
13
12
|
import { serve } from "@hono/node-server";
|
|
@@ -16,7 +15,7 @@ import { bodyLimit } from "hono/body-limit";
|
|
|
16
15
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
17
16
|
import { parseArgs } from "node:util";
|
|
18
17
|
import { readFile, readdir, stat } from "node:fs/promises";
|
|
19
|
-
//#region ../content-gateway/dist/main-
|
|
18
|
+
//#region ../content-gateway/dist/main-BtKmcm72.mjs
|
|
20
19
|
/**
|
|
21
20
|
* A connection could not be ESTABLISHED in time — retryable.
|
|
22
21
|
*
|
|
@@ -1526,8 +1525,58 @@ var FakeEmbeddingProvider$1 = class {
|
|
|
1526
1525
|
}
|
|
1527
1526
|
reset() {}
|
|
1528
1527
|
};
|
|
1528
|
+
const DEFAULT_BASE$1 = "https://generativelanguage.googleapis.com/v1beta";
|
|
1529
1529
|
/**
|
|
1530
|
-
*
|
|
1530
|
+
* An HTTP-shaped failure carrying the status the retry classifier reads.
|
|
1531
|
+
*
|
|
1532
|
+
* `isRetryable` in `gemini.ts` asks for a numeric `status` and nothing else, by
|
|
1533
|
+
* design — it was written to survive SDK refactors. This keeps that contract
|
|
1534
|
+
* when the SDK is gone.
|
|
1535
|
+
*/
|
|
1536
|
+
var GeminiHttpError$1 = class extends Error {
|
|
1537
|
+
status;
|
|
1538
|
+
constructor(status, detail) {
|
|
1539
|
+
super(`Gemini API error ${status}: ${detail}`);
|
|
1540
|
+
this.name = "GeminiHttpError";
|
|
1541
|
+
this.status = status;
|
|
1542
|
+
}
|
|
1543
|
+
};
|
|
1544
|
+
/** One POST, with the key in a HEADER — never the query string, which is logged. */
|
|
1545
|
+
async function post$1(opts, apiKey, path, body, timeoutMs) {
|
|
1546
|
+
const res = await (opts.fetchImpl ?? fetch)(`${opts.baseUrl ?? DEFAULT_BASE$1}${path}`, {
|
|
1547
|
+
method: "POST",
|
|
1548
|
+
headers: {
|
|
1549
|
+
"x-goog-api-key": apiKey,
|
|
1550
|
+
"content-type": "application/json"
|
|
1551
|
+
},
|
|
1552
|
+
body: JSON.stringify(body),
|
|
1553
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
1554
|
+
});
|
|
1555
|
+
const text = await res.text();
|
|
1556
|
+
if (!res.ok) {
|
|
1557
|
+
let detail = text.slice(0, 300);
|
|
1558
|
+
try {
|
|
1559
|
+
const message = JSON.parse(text).error?.message;
|
|
1560
|
+
if (typeof message === "string") detail = message;
|
|
1561
|
+
} catch {}
|
|
1562
|
+
throw new GeminiHttpError$1(res.status, detail);
|
|
1563
|
+
}
|
|
1564
|
+
return JSON.parse(text);
|
|
1565
|
+
}
|
|
1566
|
+
/** The embedding half of the slice, spoken over `batchEmbedContents`. */
|
|
1567
|
+
function geminiRestEmbedClient$1(apiKey, opts = {}) {
|
|
1568
|
+
return { models: { async embedContent(params) {
|
|
1569
|
+
const payload = { requests: params.contents.map((text) => ({
|
|
1570
|
+
model: `models/${params.model}`,
|
|
1571
|
+
content: { parts: [{ text }] },
|
|
1572
|
+
taskType: params.config.taskType,
|
|
1573
|
+
outputDimensionality: params.config.outputDimensionality
|
|
1574
|
+
})) };
|
|
1575
|
+
return { embeddings: (await post$1(opts, apiKey, `/models/${params.model}:batchEmbedContents`, payload, params.config.httpOptions.timeout)).embeddings ?? [] };
|
|
1576
|
+
} } };
|
|
1577
|
+
}
|
|
1578
|
+
/**
|
|
1579
|
+
* The Gemini transport — the ONE place the vendor is spoken to (converted
|
|
1531
1580
|
* from the oracle's sor_content/lib/providers/gemini.py; decision 6).
|
|
1532
1581
|
* Identity (model, dim, task labels) is CONSTRUCTOR-INJECTED — this module
|
|
1533
1582
|
* never imports config, so the same adapter serves any Gemini embedding
|
|
@@ -1547,7 +1596,7 @@ var FakeEmbeddingProvider$1 = class {
|
|
|
1547
1596
|
* clock. (The oracle's one divergence — a sync query-intent embed keeping
|
|
1548
1597
|
* the batch clock, an eval-harness case — has no TS call site.)
|
|
1549
1598
|
* - The oracle's "has been closed" stale-client RuntimeError predicate is a
|
|
1550
|
-
* Python-SDK failure mode with no
|
|
1599
|
+
* Python-SDK failure mode with no JS equivalent; `reset()`
|
|
1551
1600
|
* keeps its drop-never-close contract regardless.
|
|
1552
1601
|
*/
|
|
1553
1602
|
function httpStatusOf$1(exc) {
|
|
@@ -1601,7 +1650,7 @@ var GeminiEmbeddingProvider$1 = class {
|
|
|
1601
1650
|
this.queryTaskLabel = opts.queryTaskLabel;
|
|
1602
1651
|
this.documentTimeoutMs = Math.trunc(opts.documentTimeoutS * 1e3);
|
|
1603
1652
|
this.queryTimeoutMs = Math.trunc(opts.queryTimeoutS * 1e3);
|
|
1604
|
-
this.clientFactory = opts.clientFactory ?? (() =>
|
|
1653
|
+
this.clientFactory = opts.clientFactory ?? (() => geminiRestEmbedClient$1(opts.apiKey));
|
|
1605
1654
|
}
|
|
1606
1655
|
get recipe() {
|
|
1607
1656
|
return `${this.modelId}/d${this.dim}/${this.documentTaskLabel}`;
|
|
@@ -4452,7 +4501,7 @@ async function withPgRetry(op, options = {}) {
|
|
|
4452
4501
|
throw lastError;
|
|
4453
4502
|
}
|
|
4454
4503
|
//#endregion
|
|
4455
|
-
//#region ../content/dist/commands-
|
|
4504
|
+
//#region ../content/dist/commands-CXqK2c2f.mjs
|
|
4456
4505
|
/**
|
|
4457
4506
|
* EVAL-LOCKED constants, quarried verbatim from the oracle
|
|
4458
4507
|
* (sor-agentfactory @ b554f91, config.py) — changing any of these is a
|
|
@@ -5423,8 +5472,72 @@ var FakeEmbeddingProvider = class {
|
|
|
5423
5472
|
}
|
|
5424
5473
|
reset() {}
|
|
5425
5474
|
};
|
|
5475
|
+
const DEFAULT_BASE = "https://generativelanguage.googleapis.com/v1beta";
|
|
5476
|
+
/**
|
|
5477
|
+
* An HTTP-shaped failure carrying the status the retry classifier reads.
|
|
5478
|
+
*
|
|
5479
|
+
* `isRetryable` in `gemini.ts` asks for a numeric `status` and nothing else, by
|
|
5480
|
+
* design — it was written to survive SDK refactors. This keeps that contract
|
|
5481
|
+
* when the SDK is gone.
|
|
5482
|
+
*/
|
|
5483
|
+
var GeminiHttpError = class extends Error {
|
|
5484
|
+
status;
|
|
5485
|
+
constructor(status, detail) {
|
|
5486
|
+
super(`Gemini API error ${status}: ${detail}`);
|
|
5487
|
+
this.name = "GeminiHttpError";
|
|
5488
|
+
this.status = status;
|
|
5489
|
+
}
|
|
5490
|
+
};
|
|
5491
|
+
/** One POST, with the key in a HEADER — never the query string, which is logged. */
|
|
5492
|
+
async function post(opts, apiKey, path, body, timeoutMs) {
|
|
5493
|
+
const res = await (opts.fetchImpl ?? fetch)(`${opts.baseUrl ?? DEFAULT_BASE}${path}`, {
|
|
5494
|
+
method: "POST",
|
|
5495
|
+
headers: {
|
|
5496
|
+
"x-goog-api-key": apiKey,
|
|
5497
|
+
"content-type": "application/json"
|
|
5498
|
+
},
|
|
5499
|
+
body: JSON.stringify(body),
|
|
5500
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
5501
|
+
});
|
|
5502
|
+
const text = await res.text();
|
|
5503
|
+
if (!res.ok) {
|
|
5504
|
+
let detail = text.slice(0, 300);
|
|
5505
|
+
try {
|
|
5506
|
+
const message = JSON.parse(text).error?.message;
|
|
5507
|
+
if (typeof message === "string") detail = message;
|
|
5508
|
+
} catch {}
|
|
5509
|
+
throw new GeminiHttpError(res.status, detail);
|
|
5510
|
+
}
|
|
5511
|
+
return JSON.parse(text);
|
|
5512
|
+
}
|
|
5513
|
+
/** The embedding half of the slice, spoken over `batchEmbedContents`. */
|
|
5514
|
+
function geminiRestEmbedClient(apiKey, opts = {}) {
|
|
5515
|
+
return { models: { async embedContent(params) {
|
|
5516
|
+
const payload = { requests: params.contents.map((text) => ({
|
|
5517
|
+
model: `models/${params.model}`,
|
|
5518
|
+
content: { parts: [{ text }] },
|
|
5519
|
+
taskType: params.config.taskType,
|
|
5520
|
+
outputDimensionality: params.config.outputDimensionality
|
|
5521
|
+
})) };
|
|
5522
|
+
return { embeddings: (await post(opts, apiKey, `/models/${params.model}:batchEmbedContents`, payload, params.config.httpOptions.timeout)).embeddings ?? [] };
|
|
5523
|
+
} } };
|
|
5524
|
+
}
|
|
5525
|
+
/** The text half of the slice, spoken over `generateContent`. */
|
|
5526
|
+
function geminiRestTextClient(apiKey, opts = {}) {
|
|
5527
|
+
return { models: { async generateContent(params) {
|
|
5528
|
+
const payload = {
|
|
5529
|
+
contents: [{ parts: [{ text: params.contents }] }],
|
|
5530
|
+
generationConfig: {
|
|
5531
|
+
temperature: params.config.temperature,
|
|
5532
|
+
maxOutputTokens: params.config.maxOutputTokens,
|
|
5533
|
+
thinkingConfig: params.config.thinkingConfig
|
|
5534
|
+
}
|
|
5535
|
+
};
|
|
5536
|
+
return { text: ((await post(opts, apiKey, `/models/${params.model}:generateContent`, payload, 12e4)).candidates?.[0]?.content?.parts ?? []).map((p) => p.text ?? "").join("") };
|
|
5537
|
+
} } };
|
|
5538
|
+
}
|
|
5426
5539
|
/**
|
|
5427
|
-
* The Gemini transport — the ONE place
|
|
5540
|
+
* The Gemini transport — the ONE place the vendor is spoken to (converted
|
|
5428
5541
|
* from the oracle's sor_content/lib/providers/gemini.py; decision 6).
|
|
5429
5542
|
* Identity (model, dim, task labels) is CONSTRUCTOR-INJECTED — this module
|
|
5430
5543
|
* never imports config, so the same adapter serves any Gemini embedding
|
|
@@ -5444,7 +5557,7 @@ var FakeEmbeddingProvider = class {
|
|
|
5444
5557
|
* clock. (The oracle's one divergence — a sync query-intent embed keeping
|
|
5445
5558
|
* the batch clock, an eval-harness case — has no TS call site.)
|
|
5446
5559
|
* - The oracle's "has been closed" stale-client RuntimeError predicate is a
|
|
5447
|
-
* Python-SDK failure mode with no
|
|
5560
|
+
* Python-SDK failure mode with no JS equivalent; `reset()`
|
|
5448
5561
|
* keeps its drop-never-close contract regardless.
|
|
5449
5562
|
*/
|
|
5450
5563
|
function httpStatusOf(exc) {
|
|
@@ -5498,7 +5611,7 @@ var GeminiEmbeddingProvider = class {
|
|
|
5498
5611
|
this.queryTaskLabel = opts.queryTaskLabel;
|
|
5499
5612
|
this.documentTimeoutMs = Math.trunc(opts.documentTimeoutS * 1e3);
|
|
5500
5613
|
this.queryTimeoutMs = Math.trunc(opts.queryTimeoutS * 1e3);
|
|
5501
|
-
this.clientFactory = opts.clientFactory ?? (() =>
|
|
5614
|
+
this.clientFactory = opts.clientFactory ?? (() => geminiRestEmbedClient(opts.apiKey));
|
|
5502
5615
|
}
|
|
5503
5616
|
get recipe() {
|
|
5504
5617
|
return `${this.modelId}/d${this.dim}/${this.documentTaskLabel}`;
|
|
@@ -5541,7 +5654,7 @@ var GeminiTextGenerator = class {
|
|
|
5541
5654
|
client = null;
|
|
5542
5655
|
constructor(opts) {
|
|
5543
5656
|
this.model = opts.model ?? "gemini-2.5-flash";
|
|
5544
|
-
this.clientFactory = opts.clientFactory ?? (() =>
|
|
5657
|
+
this.clientFactory = opts.clientFactory ?? (() => geminiRestTextClient(opts.apiKey));
|
|
5545
5658
|
}
|
|
5546
5659
|
getClient() {
|
|
5547
5660
|
this.client ??= this.clientFactory();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@panaversity/ksor",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.17",
|
|
4
4
|
"description": "Knowledge System of Record — compile governed markdown into a static site for people and an MCP server for AI agents, with citations and measured abstention.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"abstention",
|
|
@@ -51,7 +51,6 @@
|
|
|
51
51
|
"provenance": true
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@google/genai": "^2.17.1",
|
|
55
54
|
"@hono/node-server": "2.1.1",
|
|
56
55
|
"@modelcontextprotocol/server": "^2.0.0",
|
|
57
56
|
"@types/pg": "^8.21.0",
|