@node-llm/core 1.17.0 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/agent/Agent.d.ts +24 -11
- package/dist/agent/Agent.d.ts.map +1 -1
- package/dist/agent/Agent.js +1 -3
- package/dist/aliases.d.ts +826 -467
- package/dist/aliases.d.ts.map +1 -1
- package/dist/aliases.js +881 -522
- package/dist/chat/Chat.d.ts +14 -10
- package/dist/chat/Chat.d.ts.map +1 -1
- package/dist/chat/Chat.js +20 -5
- package/dist/chat/ChatOptions.d.ts +12 -11
- package/dist/chat/ChatOptions.d.ts.map +1 -1
- package/dist/chat/Tool.d.ts +26 -5
- package/dist/chat/Tool.d.ts.map +1 -1
- package/dist/chat/Tool.js +25 -3
- package/dist/chat/ToolCallOutcome.d.ts +6 -6
- package/dist/chat/ToolCallOutcome.d.ts.map +1 -1
- package/dist/chat/ToolHandler.d.ts +2 -2
- package/dist/chat/ToolHandler.d.ts.map +1 -1
- package/dist/chat/ToolHandler.js +15 -1
- package/dist/config.d.ts +12 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +18 -1
- package/dist/constants.d.ts +8 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +6 -1
- package/dist/errors/index.d.ts +3 -1
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +4 -1
- package/dist/executor/Executor.d.ts +6 -0
- package/dist/executor/Executor.d.ts.map +1 -1
- package/dist/executor/Executor.js +19 -2
- package/dist/index.d.ts +9 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/judge/Judge.d.ts +107 -0
- package/dist/judge/Judge.d.ts.map +1 -0
- package/dist/judge/Judge.js +104 -0
- package/dist/judge/Judgment.d.ts +116 -0
- package/dist/judge/Judgment.d.ts.map +1 -0
- package/dist/judge/Judgment.js +159 -0
- package/dist/judge/Question.d.ts +91 -0
- package/dist/judge/Question.d.ts.map +1 -0
- package/dist/judge/Question.js +180 -0
- package/dist/llm.d.ts +46 -0
- package/dist/llm.d.ts.map +1 -1
- package/dist/llm.js +105 -5
- package/dist/models/ModelRegistry.d.ts +11 -18
- package/dist/models/ModelRegistry.d.ts.map +1 -1
- package/dist/models/ModelRegistry.js +6 -0
- package/dist/models/models.json +16534 -10534
- package/dist/providers/BaseProvider.d.ts +2 -1
- package/dist/providers/BaseProvider.d.ts.map +1 -1
- package/dist/providers/BaseProvider.js +3 -0
- package/dist/providers/Provider.d.ts +37 -0
- package/dist/providers/Provider.d.ts.map +1 -1
- package/dist/providers/anthropic/Models.d.ts.map +1 -1
- package/dist/providers/anthropic/Models.js +4 -2
- package/dist/providers/deepseek/Models.d.ts.map +1 -1
- package/dist/providers/deepseek/Models.js +4 -2
- package/dist/providers/gemini/Embeddings.d.ts.map +1 -1
- package/dist/providers/gemini/Embeddings.js +4 -2
- package/dist/providers/gemini/Image.d.ts.map +1 -1
- package/dist/providers/gemini/Image.js +4 -2
- package/dist/providers/gemini/Models.d.ts.map +1 -1
- package/dist/providers/gemini/Models.js +4 -2
- package/dist/providers/gemini/Transcription.d.ts.map +1 -1
- package/dist/providers/gemini/Transcription.js +4 -2
- package/dist/providers/mistral/Embedding.d.ts.map +1 -1
- package/dist/providers/mistral/Embedding.js +4 -2
- package/dist/providers/mistral/Models.d.ts.map +1 -1
- package/dist/providers/mistral/Models.js +4 -2
- package/dist/providers/openai/Models.d.ts.map +1 -1
- package/dist/providers/openai/Models.js +4 -2
- package/dist/providers/openrouter/Models.d.ts.map +1 -1
- package/dist/providers/openrouter/Models.js +4 -2
- package/dist/providers/registry.d.ts +2 -1
- package/dist/providers/registry.d.ts.map +1 -1
- package/dist/providers/registry.js +2 -1
- package/dist/providers/typesafe/SystemOne.d.ts +11 -0
- package/dist/providers/typesafe/SystemOne.d.ts.map +1 -0
- package/dist/providers/typesafe/SystemOne.js +171 -0
- package/dist/providers/typesafe/TypeSafeProvider.d.ts +27 -0
- package/dist/providers/typesafe/TypeSafeProvider.d.ts.map +1 -0
- package/dist/providers/typesafe/TypeSafeProvider.js +76 -0
- package/dist/providers/typesafe/index.d.ts +4 -0
- package/dist/providers/typesafe/index.d.ts.map +1 -0
- package/dist/providers/typesafe/index.js +19 -0
- package/dist/types/Middleware.d.ts +9 -0
- package/dist/types/Middleware.d.ts.map +1 -1
- package/dist/utils/Binary.d.ts.map +1 -1
- package/dist/utils/Binary.js +11 -0
- package/dist/utils/FileLoader.d.ts.map +1 -1
- package/dist/utils/FileLoader.js +21 -2
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +8 -1
- package/package.json +1 -1
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { APIError, AuthenticationError, BadRequestError, InvalidModelError, NotFoundError, RateLimitError, ServerError, ServiceUnavailableError } from "../../errors/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* The System One protocol: one request carries the input as `state` and a map
|
|
4
|
+
* of typed questions; the response carries one typed answer per question.
|
|
5
|
+
*
|
|
6
|
+
* Wire names differ from ours in two places. A probability question is a
|
|
7
|
+
* `noul` ("not unlikely"), and its yes/no descriptions are keyed "true" and
|
|
8
|
+
* "false".
|
|
9
|
+
*/
|
|
10
|
+
const WIRE_TYPES = { probability: "noul", choice: "choice", score: "score" };
|
|
11
|
+
const OUTCOME_KEYS = {
|
|
12
|
+
yes: "true",
|
|
13
|
+
true: "true",
|
|
14
|
+
no: "false",
|
|
15
|
+
false: "false"
|
|
16
|
+
};
|
|
17
|
+
const RESERVED = ["model", "state", "questions"];
|
|
18
|
+
function renderQuestion(question) {
|
|
19
|
+
let criteria;
|
|
20
|
+
switch (question.type) {
|
|
21
|
+
case "probability":
|
|
22
|
+
criteria = question.criteria
|
|
23
|
+
? Object.fromEntries(Object.entries(question.criteria).map(([key, value]) => [OUTCOME_KEYS[key], value]))
|
|
24
|
+
: undefined;
|
|
25
|
+
break;
|
|
26
|
+
case "choice":
|
|
27
|
+
criteria = question.options;
|
|
28
|
+
break;
|
|
29
|
+
case "score":
|
|
30
|
+
criteria = question.levels;
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
const rendered = { type: WIRE_TYPES[question.type] };
|
|
34
|
+
if (question.instructions !== undefined && question.instructions !== null) {
|
|
35
|
+
rendered.instructions = question.instructions;
|
|
36
|
+
}
|
|
37
|
+
if (criteria !== undefined)
|
|
38
|
+
rendered.criteria = criteria;
|
|
39
|
+
return rendered;
|
|
40
|
+
}
|
|
41
|
+
export function renderJudgmentPayload(request) {
|
|
42
|
+
const options = request.providerOptions ?? {};
|
|
43
|
+
const reserved = Object.keys(options).filter((key) => RESERVED.includes(key));
|
|
44
|
+
if (reserved.length > 0) {
|
|
45
|
+
throw new Error(`Use the judgment arguments instead of providerOptions for ${reserved.join(", ")}`);
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
model: request.model,
|
|
49
|
+
state: request.input,
|
|
50
|
+
questions: Object.fromEntries(request.questions.map((q) => [q.name, renderQuestion(q)])),
|
|
51
|
+
...options
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function malformed(detail) {
|
|
55
|
+
throw new Error(`System One returned an invalid judgment: ${detail}`);
|
|
56
|
+
}
|
|
57
|
+
function requireNumber(value, field) {
|
|
58
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
59
|
+
malformed(`${field} is not a number`);
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
function requireObject(value, field) {
|
|
63
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
64
|
+
malformed(`${field} is missing`);
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
function parseAnswer(raw, question) {
|
|
68
|
+
const answer = requireObject(raw, `the answer to '${question.name}'`);
|
|
69
|
+
switch (question.type) {
|
|
70
|
+
case "probability":
|
|
71
|
+
return {
|
|
72
|
+
type: "probability",
|
|
73
|
+
probability: requireNumber(answer.noul, `${question.name}.noul`)
|
|
74
|
+
};
|
|
75
|
+
case "choice": {
|
|
76
|
+
const options = Object.keys(question.options);
|
|
77
|
+
const chosen = answer.choice;
|
|
78
|
+
if (typeof chosen !== "string" || !options.includes(chosen)) {
|
|
79
|
+
malformed(`'${question.name}' chose '${String(chosen)}', which is not one of its options`);
|
|
80
|
+
}
|
|
81
|
+
const wire = requireObject(answer.probabilities, `${question.name}.probabilities`);
|
|
82
|
+
const probabilities = Object.fromEntries(options.map((option) => [
|
|
83
|
+
option,
|
|
84
|
+
requireNumber(wire[option], `${question.name}.probabilities.${option}`)
|
|
85
|
+
]));
|
|
86
|
+
return {
|
|
87
|
+
type: "choice",
|
|
88
|
+
choice: chosen,
|
|
89
|
+
probabilities,
|
|
90
|
+
confidence: requireNumber(answer.confidence, `${question.name}.confidence`)
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
case "score": {
|
|
94
|
+
// Levels go out as an array and come back keyed "0", "1", ... - map them
|
|
95
|
+
// back to numeric indexes so they line up with the declared levels.
|
|
96
|
+
const wire = requireObject(answer.probabilities, `${question.name}.probabilities`);
|
|
97
|
+
const probabilities = {};
|
|
98
|
+
question.levels.forEach((_, index) => {
|
|
99
|
+
probabilities[index] = requireNumber(wire[String(index)], `${question.name}.probabilities.${index}`);
|
|
100
|
+
});
|
|
101
|
+
return {
|
|
102
|
+
type: "score",
|
|
103
|
+
score: requireNumber(answer.score, `${question.name}.score`),
|
|
104
|
+
probabilities,
|
|
105
|
+
confidence: requireNumber(answer.confidence, `${question.name}.confidence`)
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
export function parseJudgmentResponse(json, questions) {
|
|
111
|
+
const body = requireObject(json, "the response body");
|
|
112
|
+
const answers = requireObject(body.answers, "answers");
|
|
113
|
+
const usage = (body.usage ?? {});
|
|
114
|
+
const input = typeof usage.input_tokens === "number" ? usage.input_tokens : 0;
|
|
115
|
+
const output = typeof usage.output_tokens === "number" ? usage.output_tokens : 0;
|
|
116
|
+
return {
|
|
117
|
+
model: typeof body.model === "string" ? body.model : "",
|
|
118
|
+
answers: Object.fromEntries(questions.map((question) => [question.name, parseAnswer(answers[question.name], question)])),
|
|
119
|
+
usage: { input_tokens: input, output_tokens: output, total_tokens: input + output },
|
|
120
|
+
raw: json
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Turns an error response into the matching NodeLLM error. System One reports
|
|
125
|
+
* errors FastAPI-style: `detail` is a string, an object with a `message`, or a
|
|
126
|
+
* list of validation errors each with a location and message.
|
|
127
|
+
*/
|
|
128
|
+
export async function handleTypeSafeError(response, model) {
|
|
129
|
+
const status = response.status;
|
|
130
|
+
let body;
|
|
131
|
+
let message = `TypeSafe error (${status})`;
|
|
132
|
+
try {
|
|
133
|
+
body = await response.json();
|
|
134
|
+
const detail = body?.detail;
|
|
135
|
+
if (typeof detail === "string") {
|
|
136
|
+
message = detail;
|
|
137
|
+
}
|
|
138
|
+
else if (Array.isArray(detail)) {
|
|
139
|
+
message = detail
|
|
140
|
+
.map((item) => [Array.isArray(item.loc) ? item.loc.join(".") : "", item.msg ?? ""]
|
|
141
|
+
.filter(Boolean)
|
|
142
|
+
.join(": "))
|
|
143
|
+
.join("; ");
|
|
144
|
+
}
|
|
145
|
+
else if (detail && typeof detail === "object" && "message" in detail) {
|
|
146
|
+
message = String(detail.message);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
body = await response.text().catch(() => "");
|
|
151
|
+
}
|
|
152
|
+
const provider = "typesafe";
|
|
153
|
+
if (status === 400 || status === 422)
|
|
154
|
+
throw new BadRequestError(message, body, provider, model);
|
|
155
|
+
if (status === 401 || status === 403)
|
|
156
|
+
throw new AuthenticationError(message, status, body, provider);
|
|
157
|
+
if (status === 404) {
|
|
158
|
+
if (message.toLowerCase().includes("model"))
|
|
159
|
+
throw new InvalidModelError(message, body, provider, model);
|
|
160
|
+
throw new NotFoundError(message, status, body, provider, model);
|
|
161
|
+
}
|
|
162
|
+
if (status === 429) {
|
|
163
|
+
const retryAfter = Number(response.headers?.get?.("retry-after"));
|
|
164
|
+
throw new RateLimitError(message, body, provider, model, Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter : undefined);
|
|
165
|
+
}
|
|
166
|
+
if (status === 503)
|
|
167
|
+
throw new ServiceUnavailableError(message, status, body, provider);
|
|
168
|
+
if (status >= 500)
|
|
169
|
+
throw new ServerError(message, status, body, provider);
|
|
170
|
+
throw new APIError(message, status, body, provider, model);
|
|
171
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { BaseProvider } from "../BaseProvider.js";
|
|
2
|
+
import { ChatRequest, ChatResponse, JudgmentRequest, JudgmentResponse, ModelInfo } from "../Provider.js";
|
|
3
|
+
export interface TypeSafeProviderOptions {
|
|
4
|
+
apiKey: string;
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* TypeSafe's System One models (Jev) answer typed questions with calibrated
|
|
9
|
+
* probabilities. They do not generate text, so this provider offers judgments
|
|
10
|
+
* and a model listing only; chat and every other operation are unsupported.
|
|
11
|
+
*
|
|
12
|
+
* Any server implementing the same System One API works by pointing
|
|
13
|
+
* `typesafeApiBase` at it.
|
|
14
|
+
*/
|
|
15
|
+
export declare class TypeSafeProvider extends BaseProvider {
|
|
16
|
+
private readonly options;
|
|
17
|
+
private readonly baseUrl;
|
|
18
|
+
constructor(options: TypeSafeProviderOptions);
|
|
19
|
+
apiBase(): string;
|
|
20
|
+
headers(): Record<string, string>;
|
|
21
|
+
protected providerName(): string;
|
|
22
|
+
defaultModel(feature?: string): string;
|
|
23
|
+
chat(_request: ChatRequest): Promise<ChatResponse>;
|
|
24
|
+
judge(request: JudgmentRequest): Promise<JudgmentResponse>;
|
|
25
|
+
listModels(): Promise<ModelInfo[]>;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=TypeSafeProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TypeSafeProvider.d.ts","sourceRoot":"","sources":["../../../src/providers/typesafe/TypeSafeProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EACL,WAAW,EACX,YAAY,EACZ,eAAe,EACf,gBAAgB,EAChB,SAAS,EACV,MAAM,gBAAgB,CAAC;AAMxB,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,qBAAa,gBAAiB,SAAQ,YAAY;IAGpC,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAEJ,OAAO,EAAE,uBAAuB;IAM7D,OAAO,IAAI,MAAM;IAIjB,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAOjC,SAAS,CAAC,YAAY,IAAI,MAAM;IAIhC,YAAY,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM;IAIhC,IAAI,CAAC,QAAQ,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IAIlD,KAAK,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAkB1D,UAAU,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;CA2BzC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { BaseProvider } from "../BaseProvider.js";
|
|
2
|
+
import { fetchWithTimeout } from "../../utils/fetch.js";
|
|
3
|
+
import { logger } from "../../utils/logger.js";
|
|
4
|
+
import { DEFAULT_MODELS, DEFAULT_TYPESAFE_BASE_URL } from "../../constants.js";
|
|
5
|
+
import { handleTypeSafeError, parseJudgmentResponse, renderJudgmentPayload } from "./SystemOne.js";
|
|
6
|
+
/**
|
|
7
|
+
* TypeSafe's System One models (Jev) answer typed questions with calibrated
|
|
8
|
+
* probabilities. They do not generate text, so this provider offers judgments
|
|
9
|
+
* and a model listing only; chat and every other operation are unsupported.
|
|
10
|
+
*
|
|
11
|
+
* Any server implementing the same System One API works by pointing
|
|
12
|
+
* `typesafeApiBase` at it.
|
|
13
|
+
*/
|
|
14
|
+
export class TypeSafeProvider extends BaseProvider {
|
|
15
|
+
options;
|
|
16
|
+
baseUrl;
|
|
17
|
+
constructor(options) {
|
|
18
|
+
super();
|
|
19
|
+
this.options = options;
|
|
20
|
+
this.baseUrl = (options.baseUrl || DEFAULT_TYPESAFE_BASE_URL).replace(/\/+$/, "");
|
|
21
|
+
this.capabilities = this.defaultCapabilities();
|
|
22
|
+
}
|
|
23
|
+
apiBase() {
|
|
24
|
+
return this.baseUrl;
|
|
25
|
+
}
|
|
26
|
+
headers() {
|
|
27
|
+
return {
|
|
28
|
+
Authorization: `Bearer ${this.options.apiKey}`,
|
|
29
|
+
"Content-Type": "application/json"
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
providerName() {
|
|
33
|
+
return "typesafe";
|
|
34
|
+
}
|
|
35
|
+
defaultModel(feature) {
|
|
36
|
+
return feature === undefined || feature === "judgment" ? DEFAULT_MODELS.JUDGMENT : "";
|
|
37
|
+
}
|
|
38
|
+
async chat(_request) {
|
|
39
|
+
this.throwUnsupportedError("chat (TypeSafe models answer judgments, not chat)");
|
|
40
|
+
}
|
|
41
|
+
async judge(request) {
|
|
42
|
+
const url = `${this.baseUrl}/v1/systemone`;
|
|
43
|
+
const body = renderJudgmentPayload(request);
|
|
44
|
+
logger.logRequest("TypeSafe", "POST", url, body);
|
|
45
|
+
const response = await fetchWithTimeout(url, { method: "POST", headers: this.headers(), body: JSON.stringify(body) }, request.requestTimeout);
|
|
46
|
+
if (!response.ok)
|
|
47
|
+
await handleTypeSafeError(response, request.model);
|
|
48
|
+
const json = await response.json();
|
|
49
|
+
logger.logResponse("TypeSafe", response.status, response.statusText, json);
|
|
50
|
+
return parseJudgmentResponse(json, request.questions);
|
|
51
|
+
}
|
|
52
|
+
async listModels() {
|
|
53
|
+
const response = await fetchWithTimeout(`${this.baseUrl}/v1/models`, {
|
|
54
|
+
method: "GET",
|
|
55
|
+
headers: this.headers()
|
|
56
|
+
});
|
|
57
|
+
if (!response.ok)
|
|
58
|
+
await handleTypeSafeError(response);
|
|
59
|
+
const json = (await response.json());
|
|
60
|
+
return (json.models ?? []).map((entry) => ({
|
|
61
|
+
id: entry.name,
|
|
62
|
+
name: entry.name,
|
|
63
|
+
provider: "typesafe",
|
|
64
|
+
family: "jev",
|
|
65
|
+
context_window: null,
|
|
66
|
+
max_output_tokens: null,
|
|
67
|
+
modalities: { input: ["text"], output: ["judgment"] },
|
|
68
|
+
capabilities: ["judgment"],
|
|
69
|
+
pricing: {},
|
|
70
|
+
metadata: {
|
|
71
|
+
...(entry.description ? { description: entry.description } : {}),
|
|
72
|
+
...(entry.release_date ? { created_at: entry.release_date } : {})
|
|
73
|
+
}
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/typesafe/index.ts"],"names":[],"mappings":"AAIA,cAAc,uBAAuB,CAAC;AAItC,wBAAgB,wBAAwB,SAiBvC;AAED,eAAO,MAAM,wBAAwB,iCAA2B,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { config as globalConfig } from "../../config.js";
|
|
2
|
+
import { providerRegistry } from "../registry.js";
|
|
3
|
+
import { TypeSafeProvider } from "./TypeSafeProvider.js";
|
|
4
|
+
export * from "./TypeSafeProvider.js";
|
|
5
|
+
let registered = false;
|
|
6
|
+
export function registerTypeSafeProvider() {
|
|
7
|
+
if (registered)
|
|
8
|
+
return;
|
|
9
|
+
providerRegistry.register("typesafe", (config) => {
|
|
10
|
+
const cfg = config || globalConfig;
|
|
11
|
+
const apiKey = cfg.typesafeApiKey;
|
|
12
|
+
if (!apiKey) {
|
|
13
|
+
throw new Error("typesafeApiKey is not set in config or TYPESAFE_API_KEY environment variable");
|
|
14
|
+
}
|
|
15
|
+
return new TypeSafeProvider({ apiKey, baseUrl: cfg.typesafeApiBase });
|
|
16
|
+
});
|
|
17
|
+
registered = true;
|
|
18
|
+
}
|
|
19
|
+
export const ensureTypeSafeRegistered = registerTypeSafeProvider;
|
|
@@ -53,6 +53,15 @@ export interface MiddlewareContext {
|
|
|
53
53
|
* Options passed to the moderation request (Moderation only).
|
|
54
54
|
*/
|
|
55
55
|
moderationOptions?: Record<string, unknown>;
|
|
56
|
+
/**
|
|
57
|
+
* Options for the judgment request (Judgment only).
|
|
58
|
+
*/
|
|
59
|
+
judgmentOptions?: Record<string, unknown>;
|
|
60
|
+
/**
|
|
61
|
+
* The input a judgment is asked about (Judgment only). Text or JSON-compatible
|
|
62
|
+
* structured data. Mutating it in `onRequest` affects the actual call.
|
|
63
|
+
*/
|
|
64
|
+
judgmentInput?: unknown;
|
|
56
65
|
/**
|
|
57
66
|
* Options passed to the image generation request (Paint only).
|
|
58
67
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Middleware.d.ts","sourceRoot":"","sources":["../../src/types/Middleware.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Middleware.d.ts","sourceRoot":"","sources":["../../src/types/Middleware.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Binary.d.ts","sourceRoot":"","sources":["../../src/utils/Binary.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Binary.d.ts","sourceRoot":"","sources":["../../src/utils/Binary.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,WAAW;IACtB;;OAEG;WACU,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAmD9D;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAI7C;;OAEG;IACH,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM;IAIjD,OAAO,CAAC,MAAM,CAAC,aAAa;CA0B7B"}
|
package/dist/utils/Binary.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { logger } from "./logger.js";
|
|
4
|
+
/** Max size (bytes) for a remotely-fetched binary, buffered fully into memory. */
|
|
5
|
+
const MAX_REMOTE_BYTES = 50 * 1024 * 1024;
|
|
4
6
|
export class BinaryUtils {
|
|
5
7
|
/**
|
|
6
8
|
* Converts a URL (data:, http:, or local path) to a base64 string and mime type.
|
|
@@ -20,7 +22,16 @@ export class BinaryUtils {
|
|
|
20
22
|
const response = await fetch(url);
|
|
21
23
|
if (!response.ok)
|
|
22
24
|
return null;
|
|
25
|
+
const declared = Number(response.headers.get("content-length"));
|
|
26
|
+
if (Number.isFinite(declared) && declared > MAX_REMOTE_BYTES) {
|
|
27
|
+
logger.error(`Remote file exceeds ${MAX_REMOTE_BYTES}-byte limit: ${url}`);
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
23
30
|
const buffer = await response.arrayBuffer();
|
|
31
|
+
if (buffer.byteLength > MAX_REMOTE_BYTES) {
|
|
32
|
+
logger.error(`Remote file exceeds ${MAX_REMOTE_BYTES}-byte limit: ${url}`);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
24
35
|
const base64 = Buffer.from(buffer).toString("base64");
|
|
25
36
|
const mimeType = response.headers.get("content-type") || this.guessMimeType(url);
|
|
26
37
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileLoader.d.ts","sourceRoot":"","sources":["../../src/utils/FileLoader.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"FileLoader.d.ts","sourceRoot":"","sources":["../../src/utils/FileLoader.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAyFjD,qBAAa,UAAU;WACR,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;CAmF1D"}
|
package/dist/utils/FileLoader.js
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
import { promises as fs } from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
|
+
/**
|
|
4
|
+
* Maximum size (bytes) for a remotely-fetched attachment. Caps memory use and
|
|
5
|
+
* limits the blast radius if an untrusted URL is ever passed here (the fetched
|
|
6
|
+
* body is buffered fully into memory as base64). 50 MB comfortably covers
|
|
7
|
+
* images, audio, and PDFs sent to providers.
|
|
8
|
+
*/
|
|
9
|
+
const MAX_REMOTE_BYTES = 50 * 1024 * 1024;
|
|
10
|
+
async function readCapped(response, source) {
|
|
11
|
+
const declared = Number(response.headers.get("content-length"));
|
|
12
|
+
if (Number.isFinite(declared) && declared > MAX_REMOTE_BYTES) {
|
|
13
|
+
throw new Error(`Remote file ${source} is ${declared} bytes, exceeding the ${MAX_REMOTE_BYTES}-byte limit.`);
|
|
14
|
+
}
|
|
15
|
+
const buffer = await response.arrayBuffer();
|
|
16
|
+
if (buffer.byteLength > MAX_REMOTE_BYTES) {
|
|
17
|
+
throw new Error(`Remote file ${source} is ${buffer.byteLength} bytes, exceeding the ${MAX_REMOTE_BYTES}-byte limit.`);
|
|
18
|
+
}
|
|
19
|
+
return buffer;
|
|
20
|
+
}
|
|
3
21
|
const MIME_TYPES = {
|
|
4
22
|
".jpg": "image/jpeg",
|
|
5
23
|
".jpeg": "image/jpeg",
|
|
@@ -50,7 +68,8 @@ const TEXT_EXTENSIONS = new Set([
|
|
|
50
68
|
".xml",
|
|
51
69
|
".yml",
|
|
52
70
|
".yaml",
|
|
53
|
-
".env"
|
|
71
|
+
// ".env" intentionally excluded: silently inlining a dotenv file would leak
|
|
72
|
+
// secrets into the prompt sent to the provider.
|
|
54
73
|
".csv",
|
|
55
74
|
".go",
|
|
56
75
|
".java",
|
|
@@ -67,7 +86,7 @@ export class FileLoader {
|
|
|
67
86
|
const response = await fetch(filePath);
|
|
68
87
|
if (!response.ok)
|
|
69
88
|
throw new Error(`Failed to fetch file: ${response.statusText}`);
|
|
70
|
-
const buffer = await response
|
|
89
|
+
const buffer = await readCapped(response, filePath);
|
|
71
90
|
const contentTypeFull = response.headers.get("content-type") || "image/jpeg";
|
|
72
91
|
const contentType = (contentTypeFull.split(";")[0] ?? "image/jpeg").trim();
|
|
73
92
|
const base64 = Buffer.from(buffer).toString("base64");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAUA,cAAM,MAAM;IACV,OAAO,CAAC,cAAc;IAItB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI;IAO5C;;OAEG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI;IAS/E;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI;IASvF,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI3B,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI;IAI3C,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;CAG5B;AAED,eAAO,MAAM,MAAM,QAAe,CAAC"}
|
package/dist/utils/logger.js
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { config } from "../config.js";
|
|
2
|
+
/**
|
|
3
|
+
* Masks credentials passed as URL query parameters (e.g. Gemini's `?key=...`)
|
|
4
|
+
* so they never reach stdout or a log aggregator when debug logging is on.
|
|
5
|
+
*/
|
|
6
|
+
function redactUrl(url) {
|
|
7
|
+
return url.replace(/([?&](?:key|api_key|apikey|access_token|token)=)[^&#]+/gi, "$1[REDACTED]");
|
|
8
|
+
}
|
|
2
9
|
class Logger {
|
|
3
10
|
isDebugEnabled() {
|
|
4
11
|
return config.debug === true;
|
|
@@ -14,7 +21,7 @@ class Logger {
|
|
|
14
21
|
*/
|
|
15
22
|
logRequest(provider, method, url, body) {
|
|
16
23
|
if (this.isDebugEnabled()) {
|
|
17
|
-
console.log(`[NodeLLM] [${provider}] Request: ${method} ${url}`);
|
|
24
|
+
console.log(`[NodeLLM] [${provider}] Request: ${method} ${redactUrl(url)}`);
|
|
18
25
|
if (body) {
|
|
19
26
|
console.log(JSON.stringify(body, null, 2));
|
|
20
27
|
}
|