@trazum/core 1.8.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/LICENSE +21 -0
- package/README.md +163 -0
- package/dist/advisories.d.ts +23 -0
- package/dist/advisories.d.ts.map +1 -0
- package/dist/advisories.js +376 -0
- package/dist/advisories.js.map +1 -0
- package/dist/aws-sigv4.d.ts +88 -0
- package/dist/aws-sigv4.d.ts.map +1 -0
- package/dist/aws-sigv4.js +117 -0
- package/dist/aws-sigv4.js.map +1 -0
- package/dist/baseline.d.ts +171 -0
- package/dist/baseline.d.ts.map +1 -0
- package/dist/baseline.js +273 -0
- package/dist/baseline.js.map +1 -0
- package/dist/cache.d.ts +26 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +28 -0
- package/dist/cache.js.map +1 -0
- package/dist/changes.d.ts +29 -0
- package/dist/changes.d.ts.map +1 -0
- package/dist/changes.js +142 -0
- package/dist/changes.js.map +1 -0
- package/dist/compare.d.ts +65 -0
- package/dist/compare.d.ts.map +1 -0
- package/dist/compare.js +58 -0
- package/dist/compare.js.map +1 -0
- package/dist/config-schema.d.ts +118 -0
- package/dist/config-schema.d.ts.map +1 -0
- package/dist/config-schema.js +315 -0
- package/dist/config-schema.js.map +1 -0
- package/dist/config.d.ts +47 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +112 -0
- package/dist/config.js.map +1 -0
- package/dist/detect.d.ts +70 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/detect.js +228 -0
- package/dist/detect.js.map +1 -0
- package/dist/evaluate.d.ts +98 -0
- package/dist/evaluate.d.ts.map +1 -0
- package/dist/evaluate.js +110 -0
- package/dist/evaluate.js.map +1 -0
- package/dist/extract.d.ts +81 -0
- package/dist/extract.d.ts.map +1 -0
- package/dist/extract.js +280 -0
- package/dist/extract.js.map +1 -0
- package/dist/gcp-auth.d.ts +58 -0
- package/dist/gcp-auth.d.ts.map +1 -0
- package/dist/gcp-auth.js +113 -0
- package/dist/gcp-auth.js.map +1 -0
- package/dist/glob.d.ts +49 -0
- package/dist/glob.d.ts.map +1 -0
- package/dist/glob.js +154 -0
- package/dist/glob.js.map +1 -0
- package/dist/host.d.ts +30 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +69 -0
- package/dist/host.js.map +1 -0
- package/dist/i18n/en.d.ts +4 -0
- package/dist/i18n/en.d.ts.map +1 -0
- package/dist/i18n/en.js +168 -0
- package/dist/i18n/en.js.map +1 -0
- package/dist/i18n/es.d.ts +4 -0
- package/dist/i18n/es.d.ts.map +1 -0
- package/dist/i18n/es.js +168 -0
- package/dist/i18n/es.js.map +1 -0
- package/dist/i18n/index.d.ts +36 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +50 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n/types.d.ts +180 -0
- package/dist/i18n/types.d.ts.map +1 -0
- package/dist/i18n/types.js +11 -0
- package/dist/i18n/types.js.map +1 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +69 -0
- package/dist/index.js.map +1 -0
- package/dist/llm.d.ts +226 -0
- package/dist/llm.d.ts.map +1 -0
- package/dist/llm.js +485 -0
- package/dist/llm.js.map +1 -0
- package/dist/nearest.d.ts +20 -0
- package/dist/nearest.d.ts.map +1 -0
- package/dist/nearest.js +54 -0
- package/dist/nearest.js.map +1 -0
- package/dist/net.d.ts +90 -0
- package/dist/net.d.ts.map +1 -0
- package/dist/net.js +203 -0
- package/dist/net.js.map +1 -0
- package/dist/node.d.ts +32 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +33 -0
- package/dist/node.js.map +1 -0
- package/dist/openrouter.d.ts +25 -0
- package/dist/openrouter.d.ts.map +1 -0
- package/dist/openrouter.js +72 -0
- package/dist/openrouter.js.map +1 -0
- package/dist/optimize.d.ts +38 -0
- package/dist/optimize.d.ts.map +1 -0
- package/dist/optimize.js +183 -0
- package/dist/optimize.js.map +1 -0
- package/dist/otlp.d.ts +91 -0
- package/dist/otlp.d.ts.map +1 -0
- package/dist/otlp.js +102 -0
- package/dist/otlp.js.map +1 -0
- package/dist/phrases.d.ts +169 -0
- package/dist/phrases.d.ts.map +1 -0
- package/dist/phrases.js +939 -0
- package/dist/phrases.js.map +1 -0
- package/dist/pricing-overlay.d.ts +55 -0
- package/dist/pricing-overlay.d.ts.map +1 -0
- package/dist/pricing-overlay.js +241 -0
- package/dist/pricing-overlay.js.map +1 -0
- package/dist/pricing.d.ts +115 -0
- package/dist/pricing.d.ts.map +1 -0
- package/dist/pricing.js +400 -0
- package/dist/pricing.js.map +1 -0
- package/dist/profile.d.ts +71 -0
- package/dist/profile.d.ts.map +1 -0
- package/dist/profile.js +55 -0
- package/dist/profile.js.map +1 -0
- package/dist/promptfoo.d.ts +58 -0
- package/dist/promptfoo.d.ts.map +1 -0
- package/dist/promptfoo.js +149 -0
- package/dist/promptfoo.js.map +1 -0
- package/dist/prune.d.ts +91 -0
- package/dist/prune.d.ts.map +1 -0
- package/dist/prune.js +110 -0
- package/dist/prune.js.map +1 -0
- package/dist/reorder.d.ts +82 -0
- package/dist/reorder.d.ts.map +1 -0
- package/dist/reorder.js +215 -0
- package/dist/reorder.js.map +1 -0
- package/dist/review.d.ts +54 -0
- package/dist/review.d.ts.map +1 -0
- package/dist/review.js +131 -0
- package/dist/review.js.map +1 -0
- package/dist/rules.d.ts +5 -0
- package/dist/rules.d.ts.map +1 -0
- package/dist/rules.js +279 -0
- package/dist/rules.js.map +1 -0
- package/dist/savings.d.ts +36 -0
- package/dist/savings.d.ts.map +1 -0
- package/dist/savings.js +83 -0
- package/dist/savings.js.map +1 -0
- package/dist/segment.d.ts +8 -0
- package/dist/segment.d.ts.map +1 -0
- package/dist/segment.js +74 -0
- package/dist/segment.js.map +1 -0
- package/dist/shared-prefix.d.ts +63 -0
- package/dist/shared-prefix.d.ts.map +1 -0
- package/dist/shared-prefix.js +151 -0
- package/dist/shared-prefix.js.map +1 -0
- package/dist/similarity.d.ts +13 -0
- package/dist/similarity.d.ts.map +1 -0
- package/dist/similarity.js +30 -0
- package/dist/similarity.js.map +1 -0
- package/dist/structure.d.ts +144 -0
- package/dist/structure.d.ts.map +1 -0
- package/dist/structure.js +455 -0
- package/dist/structure.js.map +1 -0
- package/dist/suggest.d.ts +100 -0
- package/dist/suggest.d.ts.map +1 -0
- package/dist/suggest.js +151 -0
- package/dist/suggest.js.map +1 -0
- package/dist/tokenizer.d.ts +57 -0
- package/dist/tokenizer.d.ts.map +1 -0
- package/dist/tokenizer.js +157 -0
- package/dist/tokenizer.js.map +1 -0
- package/dist/types.d.ts +296 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/walk.d.ts +40 -0
- package/dist/walk.d.ts.map +1 -0
- package/dist/walk.js +95 -0
- package/dist/walk.js.map +1 -0
- package/package.json +56 -0
- package/src/advisories.ts +431 -0
- package/src/aws-sigv4.ts +174 -0
- package/src/baseline.ts +390 -0
- package/src/cache.ts +54 -0
- package/src/changes.ts +158 -0
- package/src/compare.ts +131 -0
- package/src/config-schema.ts +451 -0
- package/src/config.ts +161 -0
- package/src/detect.ts +312 -0
- package/src/evaluate.ts +188 -0
- package/src/extract.ts +336 -0
- package/src/gcp-auth.ts +166 -0
- package/src/glob.ts +160 -0
- package/src/host.ts +90 -0
- package/src/i18n/en.ts +236 -0
- package/src/i18n/es.ts +236 -0
- package/src/i18n/index.ts +68 -0
- package/src/i18n/types.ts +230 -0
- package/src/index.ts +228 -0
- package/src/llm.ts +708 -0
- package/src/nearest.ts +61 -0
- package/src/net.ts +233 -0
- package/src/node.ts +63 -0
- package/src/openrouter.ts +125 -0
- package/src/optimize.ts +228 -0
- package/src/otlp.ts +179 -0
- package/src/phrases.ts +1047 -0
- package/src/pricing-overlay.ts +319 -0
- package/src/pricing.ts +468 -0
- package/src/profile.ts +124 -0
- package/src/promptfoo.ts +213 -0
- package/src/prune.ts +211 -0
- package/src/reorder.ts +307 -0
- package/src/review.ts +180 -0
- package/src/rules.ts +324 -0
- package/src/savings.ts +121 -0
- package/src/segment.ts +106 -0
- package/src/shared-prefix.ts +198 -0
- package/src/similarity.ts +28 -0
- package/src/structure.ts +652 -0
- package/src/suggest.ts +254 -0
- package/src/tokenizer.ts +190 -0
- package/src/types.ts +323 -0
- package/src/walk.ts +117 -0
package/dist/llm.js
ADDED
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
import { buildAdvisories } from './advisories.js';
|
|
2
|
+
import { getMessages } from './i18n/index.js';
|
|
3
|
+
import { signRequest } from './aws-sigv4.js';
|
|
4
|
+
import { accessToken } from './gcp-auth.js';
|
|
5
|
+
import { SAFE_FETCH_INIT, checkedEndpoint } from './net.js';
|
|
6
|
+
import { computeSavings } from './savings.js';
|
|
7
|
+
import { segment } from './segment.js';
|
|
8
|
+
import { estimateTokens } from './tokenizer.js';
|
|
9
|
+
/**
|
|
10
|
+
* Optional LLM layer.
|
|
11
|
+
*
|
|
12
|
+
* The deterministic core already does the work at zero cost. This pass adds
|
|
13
|
+
* the semantic compression rules cannot do — rewriting a whole sentence,
|
|
14
|
+
* merging two instructions that say the same thing in different words — and
|
|
15
|
+
* that is why it costs one call.
|
|
16
|
+
*
|
|
17
|
+
* The provider is pluggable on purpose: your own hosted model, an
|
|
18
|
+
* OpenAI-compatible endpoint, the Claude API or anything else behind
|
|
19
|
+
* `customProvider`.
|
|
20
|
+
*/
|
|
21
|
+
export const REFINER_SYSTEM_PROMPT = `You rewrite prompts so they cost fewer tokens without changing what they ask for.
|
|
22
|
+
|
|
23
|
+
Rules:
|
|
24
|
+
- Preserve exactly the same task, constraints, output format and success criteria.
|
|
25
|
+
- Copy verbatim, without changing a single character: code blocks, URLs, template placeholders ({{x}}, \${x}, {x}) and XML/HTML tags.
|
|
26
|
+
- Do not summarise and do not drop requirements. When in doubt about whether something is a requirement, keep it.
|
|
27
|
+
- Remove redundancy, padding and repetition. Merge duplicated instructions.
|
|
28
|
+
- Keep the original language of the prompt.
|
|
29
|
+
- Return ONLY the rewritten prompt. No explanations, no commentary, no code fences wrapping the answer.`;
|
|
30
|
+
/** Strips code fences if the model wrapped its answer despite being asked not to. */
|
|
31
|
+
function stripCodeFence(text) {
|
|
32
|
+
const trimmed = text.trim();
|
|
33
|
+
const match = /^(?:```|~~~)[a-zA-Z]*\n([\s\S]*?)\n?(?:```|~~~)$/.exec(trimmed);
|
|
34
|
+
return match?.[1] ?? trimmed;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Runs the already-optimised prompt through the LLM and accepts the result
|
|
38
|
+
* only if it passes the safety checks.
|
|
39
|
+
*
|
|
40
|
+
* It never returns a prompt worse than the deterministic one: if the candidate
|
|
41
|
+
* loses protected content, grows in tokens or shrinks suspiciously, it is
|
|
42
|
+
* discarded and the previous result stands.
|
|
43
|
+
*/
|
|
44
|
+
export async function refineWithLlm(result, provider, options = {}) {
|
|
45
|
+
const count = options.tokenCounter ?? estimateTokens;
|
|
46
|
+
const minRetainRatio = options.minRetainRatio ?? 0.25;
|
|
47
|
+
const locale = options.locale ?? result.locale;
|
|
48
|
+
const t = getMessages(locale);
|
|
49
|
+
const raw = await provider.complete({
|
|
50
|
+
system: REFINER_SYSTEM_PROMPT,
|
|
51
|
+
user: result.optimized,
|
|
52
|
+
});
|
|
53
|
+
const candidate = stripCodeFence(raw);
|
|
54
|
+
const tokensBefore = result.tokensAfter;
|
|
55
|
+
const tokensAfter = count(candidate);
|
|
56
|
+
const base = {
|
|
57
|
+
provider: provider.name,
|
|
58
|
+
model: provider.model,
|
|
59
|
+
candidate,
|
|
60
|
+
tokensBefore,
|
|
61
|
+
tokensAfter,
|
|
62
|
+
};
|
|
63
|
+
const reject = (rejectedReason) => ({
|
|
64
|
+
...result,
|
|
65
|
+
llm: { ...base, applied: false, rejectedReason },
|
|
66
|
+
});
|
|
67
|
+
if (!candidate.trim()) {
|
|
68
|
+
return reject(t.llm.emptyResponse());
|
|
69
|
+
}
|
|
70
|
+
// Protected content has to still be there, character for character.
|
|
71
|
+
const mustSurvive = [
|
|
72
|
+
...new Set(segment(result.optimized)
|
|
73
|
+
.filter((s) => s.kind === 'protected')
|
|
74
|
+
.map((s) => s.text)),
|
|
75
|
+
];
|
|
76
|
+
const lost = mustSurvive.filter((text) => !candidate.includes(text));
|
|
77
|
+
if (lost.length > 0) {
|
|
78
|
+
return reject(t.llm.protectedContentAltered(lost.length));
|
|
79
|
+
}
|
|
80
|
+
if (tokensAfter >= tokensBefore) {
|
|
81
|
+
return reject(t.llm.notShorter(tokensAfter, tokensBefore));
|
|
82
|
+
}
|
|
83
|
+
if (tokensAfter < tokensBefore * minRetainRatio) {
|
|
84
|
+
return reject(t.llm.suspiciousShrink(Math.round((tokensAfter / tokensBefore) * 100)));
|
|
85
|
+
}
|
|
86
|
+
const finalTokensBefore = result.tokensBefore;
|
|
87
|
+
const savings = computeSavings(finalTokensBefore, tokensAfter, result.usage);
|
|
88
|
+
const advisories = buildAdvisories(candidate, tokensAfter, result.usage, { count, locale });
|
|
89
|
+
return {
|
|
90
|
+
...result,
|
|
91
|
+
optimized: candidate,
|
|
92
|
+
tokensAfter,
|
|
93
|
+
tokensSaved: finalTokensBefore - tokensAfter,
|
|
94
|
+
reductionPct: finalTokensBefore > 0 ? ((finalTokensBefore - tokensAfter) / finalTokensBefore) * 100 : 0,
|
|
95
|
+
savings,
|
|
96
|
+
advisories,
|
|
97
|
+
llm: { ...base, applied: true },
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The endpoint check lives in `net.ts`, beside the validator and beside the
|
|
102
|
+
* `fetch` options every server-side call here carries.
|
|
103
|
+
*
|
|
104
|
+
* At construction rather than at call time, so a provider that can never work
|
|
105
|
+
* does not exist to be handed around.
|
|
106
|
+
*
|
|
107
|
+
* The web route already validates a body-supplied URL before it gets here, and
|
|
108
|
+
* that stays — it turns the reason code into a sentence in the reader's
|
|
109
|
+
* language. This is the second lock, at the boundary. `openAiCompatible` is an
|
|
110
|
+
* exported library function, so "the caller checks" is a promise about every
|
|
111
|
+
* future caller, including ones outside this repository.
|
|
112
|
+
*/
|
|
113
|
+
/**
|
|
114
|
+
* Any endpoint speaking OpenAI's `/chat/completions` format. Covers vLLM,
|
|
115
|
+
* Ollama, OpenRouter, LM Studio, Together and most internal gateways.
|
|
116
|
+
*/
|
|
117
|
+
export function openAiCompatible(options) {
|
|
118
|
+
const { baseUrl, apiKey, model, headers = {}, name = 'openai-compatible', maxTokens = 8192, fetchImpl = fetch, allowInsecure = false, } = options;
|
|
119
|
+
const endpoint = checkedEndpoint(baseUrl, { allowInsecure, name });
|
|
120
|
+
return {
|
|
121
|
+
name,
|
|
122
|
+
model,
|
|
123
|
+
async complete({ system, user }) {
|
|
124
|
+
const res = await fetchImpl(`${endpoint}/chat/completions`, {
|
|
125
|
+
...SAFE_FETCH_INIT,
|
|
126
|
+
method: 'POST',
|
|
127
|
+
headers: {
|
|
128
|
+
'content-type': 'application/json',
|
|
129
|
+
...(apiKey ? { authorization: `Bearer ${apiKey}` } : {}),
|
|
130
|
+
...headers,
|
|
131
|
+
},
|
|
132
|
+
body: JSON.stringify({
|
|
133
|
+
model,
|
|
134
|
+
max_tokens: maxTokens,
|
|
135
|
+
messages: [
|
|
136
|
+
{ role: 'system', content: system },
|
|
137
|
+
{ role: 'user', content: user },
|
|
138
|
+
],
|
|
139
|
+
}),
|
|
140
|
+
});
|
|
141
|
+
if (!res.ok) {
|
|
142
|
+
throw new Error(`Provider "${name}" responded ${res.status}: ${await res.text()}`);
|
|
143
|
+
}
|
|
144
|
+
const data = (await res.json());
|
|
145
|
+
const content = data.choices?.[0]?.message?.content;
|
|
146
|
+
if (typeof content !== 'string') {
|
|
147
|
+
throw new Error(`Unexpected response from "${name}": choices[0].message.content not found`);
|
|
148
|
+
}
|
|
149
|
+
return content;
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/** The Claude API directly, via `/v1/messages`. */
|
|
154
|
+
export function anthropicProvider(options) {
|
|
155
|
+
const { apiKey, model = 'claude-opus-5', baseUrl = 'https://api.anthropic.com', maxTokens = 8192, fetchImpl = fetch, allowInsecure = false, } = options;
|
|
156
|
+
// The same door, one along. This one has a safe default, which is exactly why
|
|
157
|
+
// it is easy to forget that the option overriding it is a network target.
|
|
158
|
+
const endpoint = checkedEndpoint(baseUrl, { allowInsecure, name: 'anthropic' });
|
|
159
|
+
return {
|
|
160
|
+
name: 'anthropic',
|
|
161
|
+
model,
|
|
162
|
+
async complete({ system, user }) {
|
|
163
|
+
const res = await fetchImpl(`${endpoint}/v1/messages`, {
|
|
164
|
+
...SAFE_FETCH_INIT,
|
|
165
|
+
method: 'POST',
|
|
166
|
+
headers: {
|
|
167
|
+
'content-type': 'application/json',
|
|
168
|
+
'x-api-key': apiKey,
|
|
169
|
+
'anthropic-version': '2023-06-01',
|
|
170
|
+
},
|
|
171
|
+
body: JSON.stringify({
|
|
172
|
+
model,
|
|
173
|
+
max_tokens: maxTokens,
|
|
174
|
+
system,
|
|
175
|
+
messages: [{ role: 'user', content: user }],
|
|
176
|
+
}),
|
|
177
|
+
});
|
|
178
|
+
if (!res.ok) {
|
|
179
|
+
throw new Error(`The Claude API responded ${res.status}: ${await res.text()}`);
|
|
180
|
+
}
|
|
181
|
+
const data = (await res.json());
|
|
182
|
+
if (data.stop_reason === 'refusal') {
|
|
183
|
+
throw new Error('The Claude API declined the request (stop_reason: refusal).');
|
|
184
|
+
}
|
|
185
|
+
const text = data.content?.find((b) => b.type === 'text')?.text;
|
|
186
|
+
if (typeof text !== 'string') {
|
|
187
|
+
throw new Error('Unexpected response from the Claude API: no text block.');
|
|
188
|
+
}
|
|
189
|
+
return text;
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Reads an answer out of Google's `generateContent` response, or refuses.
|
|
195
|
+
*
|
|
196
|
+
* Shared by `geminiProvider` and `vertexProvider`, which speak to the same API
|
|
197
|
+
* behind different credentials. Two copies of "is this answer complete" is one
|
|
198
|
+
* copy too many: the whole point of these checks is that three of Google's
|
|
199
|
+
* failure modes arrive as HTTP 200, and a second copy is a second place for one
|
|
200
|
+
* of them to be forgotten.
|
|
201
|
+
*
|
|
202
|
+
* `label` names the surface in the error, because "Gemini refused" and "Vertex
|
|
203
|
+
* refused" send somebody to different consoles.
|
|
204
|
+
*/
|
|
205
|
+
function readGeminiAnswer(payload, label) {
|
|
206
|
+
const data = payload;
|
|
207
|
+
const blocked = data.promptFeedback?.blockReason;
|
|
208
|
+
if (blocked) {
|
|
209
|
+
throw new Error(`${label} blocked the prompt (${blocked}).`);
|
|
210
|
+
}
|
|
211
|
+
const candidate = data.candidates?.[0];
|
|
212
|
+
if (!candidate) {
|
|
213
|
+
throw new Error(`Unexpected response from ${label}: no candidates.`);
|
|
214
|
+
}
|
|
215
|
+
if (candidate.finishReason === 'MAX_TOKENS') {
|
|
216
|
+
// Refused rather than returned. A truncated rewrite is the failure this
|
|
217
|
+
// whole package is built to avoid: it reads as an answer.
|
|
218
|
+
throw new Error(`${label} stopped at the token limit — the answer is incomplete.`);
|
|
219
|
+
}
|
|
220
|
+
if (candidate.finishReason === 'SAFETY' || candidate.finishReason === 'PROHIBITED_CONTENT') {
|
|
221
|
+
throw new Error(`${label} declined the request (${candidate.finishReason}).`);
|
|
222
|
+
}
|
|
223
|
+
const text = candidate.content?.parts?.map((part) => part.text ?? '').join('');
|
|
224
|
+
if (!text) {
|
|
225
|
+
throw new Error(`Unexpected response from ${label}: no text in the candidate.`);
|
|
226
|
+
}
|
|
227
|
+
return text;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Gemini directly, via `generateContent`.
|
|
231
|
+
*
|
|
232
|
+
* The one provider on the list that needs its own function rather than the
|
|
233
|
+
* OpenAI-compatible path. Everything else — Groq, Together, Fireworks,
|
|
234
|
+
* DeepInfra, Cerebras, SiliconFlow, OpenRouter, LiteLLM — speaks the OpenAI
|
|
235
|
+
* shape, so `openAiCompatibleProvider` with a base URL is the whole
|
|
236
|
+
* integration. Google's is a different document: the system prompt is
|
|
237
|
+
* `systemInstruction` rather than a message, turns are `contents` with `parts`,
|
|
238
|
+
* and the answer is the first candidate's parts joined.
|
|
239
|
+
*
|
|
240
|
+
* Three failure modes that are not HTTP errors, and each has bitten somebody:
|
|
241
|
+
*
|
|
242
|
+
* - **A safety block returns 200.** `promptFeedback.blockReason` arrives with no
|
|
243
|
+
* candidates at all, so reading `candidates[0]` gives `undefined` and the
|
|
244
|
+
* caller sees "no text" for what is actually a refusal.
|
|
245
|
+
* - **`finishReason: MAX_TOKENS` also returns 200**, with a truncated answer.
|
|
246
|
+
* For a rewrite pass that is worse than an error: the text looks like a
|
|
247
|
+
* result and is half a result.
|
|
248
|
+
* - **Parts can be empty.** A candidate with no text part is a valid document
|
|
249
|
+
* and not a valid answer.
|
|
250
|
+
*
|
|
251
|
+
* The key goes in a header, not the query string. Google's own examples put it
|
|
252
|
+
* in `?key=`, which puts a credential in every proxy log and referrer between
|
|
253
|
+
* here and there.
|
|
254
|
+
*/
|
|
255
|
+
export function geminiProvider(options) {
|
|
256
|
+
const { apiKey, model = 'gemini-2.5-pro', baseUrl = 'https://generativelanguage.googleapis.com', maxTokens = 8192, fetchImpl = fetch, allowInsecure = false, } = options;
|
|
257
|
+
const endpoint = checkedEndpoint(baseUrl, { allowInsecure, name: 'gemini' });
|
|
258
|
+
return {
|
|
259
|
+
name: 'gemini',
|
|
260
|
+
model,
|
|
261
|
+
async complete({ system, user }) {
|
|
262
|
+
const res = await fetchImpl(`${endpoint}/v1beta/models/${encodeURIComponent(model)}:generateContent`, {
|
|
263
|
+
...SAFE_FETCH_INIT,
|
|
264
|
+
method: 'POST',
|
|
265
|
+
headers: { 'content-type': 'application/json', 'x-goog-api-key': apiKey },
|
|
266
|
+
body: JSON.stringify({
|
|
267
|
+
systemInstruction: { parts: [{ text: system }] },
|
|
268
|
+
contents: [{ role: 'user', parts: [{ text: user }] }],
|
|
269
|
+
generationConfig: { maxOutputTokens: maxTokens, temperature: 0 },
|
|
270
|
+
}),
|
|
271
|
+
});
|
|
272
|
+
if (!res.ok) {
|
|
273
|
+
throw new Error(`The Gemini API responded ${res.status}: ${await res.text()}`);
|
|
274
|
+
}
|
|
275
|
+
return readGeminiAnswer(await res.json(), 'The Gemini API');
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Amazon Bedrock, through **Converse** rather than `InvokeModel`.
|
|
281
|
+
*
|
|
282
|
+
* That choice is the whole reason this is one provider instead of six.
|
|
283
|
+
* `InvokeModel` takes a body in each model family's own shape — Anthropic's
|
|
284
|
+
* `messages` with `anthropic_version`, Meta's `prompt`, Amazon's
|
|
285
|
+
* `inputText` — so supporting "Bedrock" through it means supporting each vendor
|
|
286
|
+
* separately and getting a 400 for every model nobody thought about. `Converse`
|
|
287
|
+
* is Bedrock's unified surface: one request shape, one response shape, every
|
|
288
|
+
* model that supports it.
|
|
289
|
+
*
|
|
290
|
+
* Signed with SigV4 by hand — see `aws-sigv4.ts` for why there is no SDK here
|
|
291
|
+
* and what the tests do and do not prove.
|
|
292
|
+
*
|
|
293
|
+
* `stopReason: 'max_tokens'` throws, for the same reason it does on Gemini: a
|
|
294
|
+
* truncated rewrite reads exactly like a finished one, and that is the failure
|
|
295
|
+
* this package exists to refuse.
|
|
296
|
+
*/
|
|
297
|
+
export function bedrockProvider(options) {
|
|
298
|
+
const { model, region, accessKeyId, secretAccessKey, sessionToken, maxTokens = 8192, baseUrl = `https://bedrock-runtime.${region}.amazonaws.com`, fetchImpl = fetch, allowInsecure = false, now = () => new Date(), } = options;
|
|
299
|
+
const endpoint = checkedEndpoint(baseUrl, { allowInsecure, name: 'bedrock' });
|
|
300
|
+
const host = new URL(endpoint).host;
|
|
301
|
+
return {
|
|
302
|
+
name: 'bedrock',
|
|
303
|
+
model,
|
|
304
|
+
async complete({ system, user }) {
|
|
305
|
+
/**
|
|
306
|
+
* Bedrock model ids contain a colon — `anthropic.claude-v2:1` — and AWS's
|
|
307
|
+
* own URLs carry it unencoded, which RFC 3986 permits in a path segment.
|
|
308
|
+
*
|
|
309
|
+
* The comment that used to sit here claimed `encodeURIComponent` leaves it
|
|
310
|
+
* alone. It does not: it produces `%3A`. The signature would still have
|
|
311
|
+
* matched, because the same string is signed and sent — but the request
|
|
312
|
+
* would have gone to a path AWS does not document, and whether its router
|
|
313
|
+
* normalises `%3A` back to `:` is not something this repository can find
|
|
314
|
+
* out from here. A test asserting the path exactly is what surfaced it.
|
|
315
|
+
*
|
|
316
|
+
* So: encode, then put the colon back. `/` stays encoded, which is the one
|
|
317
|
+
* character that would change the shape of the path rather than a
|
|
318
|
+
* character in it.
|
|
319
|
+
*/
|
|
320
|
+
const path = `/model/${encodeURIComponent(model).replace(/%3A/g, ':')}/converse`;
|
|
321
|
+
const body = JSON.stringify({
|
|
322
|
+
system: [{ text: system }],
|
|
323
|
+
messages: [{ role: 'user', content: [{ text: user }] }],
|
|
324
|
+
inferenceConfig: { maxTokens, temperature: 0 },
|
|
325
|
+
});
|
|
326
|
+
const signed = await signRequest({
|
|
327
|
+
method: 'POST',
|
|
328
|
+
path,
|
|
329
|
+
host,
|
|
330
|
+
region,
|
|
331
|
+
service: 'bedrock',
|
|
332
|
+
body,
|
|
333
|
+
accessKeyId,
|
|
334
|
+
secretAccessKey,
|
|
335
|
+
...(sessionToken ? { sessionToken } : {}),
|
|
336
|
+
now: now(),
|
|
337
|
+
});
|
|
338
|
+
const res = await fetchImpl(`${endpoint}${path}`, {
|
|
339
|
+
...SAFE_FETCH_INIT,
|
|
340
|
+
method: 'POST',
|
|
341
|
+
headers: { ...signed, 'content-type': 'application/json' },
|
|
342
|
+
body,
|
|
343
|
+
});
|
|
344
|
+
if (!res.ok) {
|
|
345
|
+
throw new Error(`Bedrock responded ${res.status}: ${await res.text()}`);
|
|
346
|
+
}
|
|
347
|
+
const data = (await res.json());
|
|
348
|
+
if (data.stopReason === 'max_tokens') {
|
|
349
|
+
throw new Error('Bedrock stopped at the token limit — the answer is incomplete.');
|
|
350
|
+
}
|
|
351
|
+
if (data.stopReason === 'content_filtered') {
|
|
352
|
+
throw new Error('Bedrock filtered the response (stopReason: content_filtered).');
|
|
353
|
+
}
|
|
354
|
+
const text = data.output?.message?.content?.map((part) => part.text ?? '').join('');
|
|
355
|
+
if (!text) {
|
|
356
|
+
throw new Error('Unexpected response from Bedrock: no text in the message.');
|
|
357
|
+
}
|
|
358
|
+
return text;
|
|
359
|
+
},
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Gemini through Vertex AI, with a service account instead of an API key.
|
|
364
|
+
*
|
|
365
|
+
* Vertex will not take an API key, which is the whole difference from
|
|
366
|
+
* `geminiProvider`: the credential is a signed assertion traded for an access
|
|
367
|
+
* token that lasts an hour. `gcp-auth.ts` does that, caches the token, and
|
|
368
|
+
* explains why there is no SDK.
|
|
369
|
+
*
|
|
370
|
+
* The response shape is Gemini's, so the same three HTTP-200 failures apply and
|
|
371
|
+
* are refused the same way — a blocked prompt, a truncated answer, an empty
|
|
372
|
+
* candidate. The parsing is shared with `geminiProvider` rather than copied,
|
|
373
|
+
* because two copies of "is this answer complete" is one copy too many.
|
|
374
|
+
*/
|
|
375
|
+
export function vertexProvider(options) {
|
|
376
|
+
const { serviceAccount, project, location, model = 'gemini-2.5-pro', publisher = 'google', maxTokens = 8192, baseUrl = location === 'global'
|
|
377
|
+
? 'https://aiplatform.googleapis.com'
|
|
378
|
+
: `https://${location}-aiplatform.googleapis.com`, fetchImpl = fetch, allowInsecure = false, now = () => new Date(), } = options;
|
|
379
|
+
const endpoint = checkedEndpoint(baseUrl, { allowInsecure, name: 'vertex' });
|
|
380
|
+
// One cache per provider instance, so two providers in one process do not
|
|
381
|
+
// share a token — and neither leaks into the other's requests.
|
|
382
|
+
const cache = { current: null };
|
|
383
|
+
return {
|
|
384
|
+
name: 'vertex',
|
|
385
|
+
model,
|
|
386
|
+
async complete({ system, user }) {
|
|
387
|
+
const token = await accessToken(serviceAccount, { fetchImpl, now, cache });
|
|
388
|
+
const path = `/v1/projects/${encodeURIComponent(project)}` +
|
|
389
|
+
`/locations/${encodeURIComponent(location)}` +
|
|
390
|
+
`/publishers/${encodeURIComponent(publisher)}` +
|
|
391
|
+
`/models/${encodeURIComponent(model)}:generateContent`;
|
|
392
|
+
const res = await fetchImpl(`${endpoint}${path}`, {
|
|
393
|
+
...SAFE_FETCH_INIT,
|
|
394
|
+
method: 'POST',
|
|
395
|
+
headers: { 'content-type': 'application/json', authorization: `Bearer ${token}` },
|
|
396
|
+
body: JSON.stringify({
|
|
397
|
+
systemInstruction: { parts: [{ text: system }] },
|
|
398
|
+
contents: [{ role: 'user', parts: [{ text: user }] }],
|
|
399
|
+
generationConfig: { maxOutputTokens: maxTokens, temperature: 0 },
|
|
400
|
+
}),
|
|
401
|
+
});
|
|
402
|
+
if (!res.ok) {
|
|
403
|
+
throw new Error(`Vertex responded ${res.status}: ${await res.text()}`);
|
|
404
|
+
}
|
|
405
|
+
return readGeminiAnswer(await res.json(), 'Vertex');
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Escape hatch: if your endpoint speaks none of the formats above, you define
|
|
411
|
+
* how the request is built and how the response is read, and everything else
|
|
412
|
+
* keeps working the same.
|
|
413
|
+
*/
|
|
414
|
+
export function customProvider(options) {
|
|
415
|
+
const { name, model, request, extract, fetchImpl = fetch } = options;
|
|
416
|
+
return {
|
|
417
|
+
name,
|
|
418
|
+
model,
|
|
419
|
+
async complete(input) {
|
|
420
|
+
const { url, init } = request(input);
|
|
421
|
+
// The caller built this request themselves, so the URL is theirs to choose
|
|
422
|
+
// — but the redirect default is not something they opted into, and it is
|
|
423
|
+
// the one that turns any endpoint into a hop. Overridable, since a custom
|
|
424
|
+
// provider may genuinely need to follow one.
|
|
425
|
+
const res = await fetchImpl(url, { ...SAFE_FETCH_INIT, ...init });
|
|
426
|
+
if (!res.ok) {
|
|
427
|
+
throw new Error(`Provider "${name}" responded ${res.status}: ${await res.text()}`);
|
|
428
|
+
}
|
|
429
|
+
return extract(await res.json());
|
|
430
|
+
},
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Builds a provider from environment variables.
|
|
435
|
+
*
|
|
436
|
+
* TRAZUM_LLM_PROVIDER openai | anthropic (default: openai)
|
|
437
|
+
* TRAZUM_LLM_BASE_URL base URL of the endpoint
|
|
438
|
+
* TRAZUM_LLM_API_KEY key, when one is needed
|
|
439
|
+
* TRAZUM_LLM_MODEL model identifier
|
|
440
|
+
*
|
|
441
|
+
* Returns `null` when the configuration is incomplete, so the tool keeps
|
|
442
|
+
* working in deterministic mode instead of failing.
|
|
443
|
+
*/
|
|
444
|
+
export function providerFromEnv(env = process.env) {
|
|
445
|
+
const kind = (env.TRAZUM_LLM_PROVIDER ?? 'openai').toLowerCase();
|
|
446
|
+
const apiKey = env.TRAZUM_LLM_API_KEY;
|
|
447
|
+
const model = env.TRAZUM_LLM_MODEL;
|
|
448
|
+
const baseUrl = env.TRAZUM_LLM_BASE_URL;
|
|
449
|
+
// Trusted because it came from the environment: the operator configuring
|
|
450
|
+
// their own machine, not a stranger naming a host for this server to fetch.
|
|
451
|
+
// `http://localhost:11434` for Ollama is the normal case here, and the
|
|
452
|
+
// documentation promises it works.
|
|
453
|
+
if (kind === 'anthropic') {
|
|
454
|
+
if (!apiKey)
|
|
455
|
+
return null;
|
|
456
|
+
return anthropicProvider({
|
|
457
|
+
apiKey,
|
|
458
|
+
allowInsecure: true,
|
|
459
|
+
...(model ? { model } : {}),
|
|
460
|
+
...(baseUrl ? { baseUrl } : {}),
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
if (kind === 'gemini' || kind === 'google') {
|
|
464
|
+
// Same shape as the Anthropic branch: a key is enough, because the endpoint
|
|
465
|
+
// has a working default and the model does too.
|
|
466
|
+
if (!apiKey)
|
|
467
|
+
return null;
|
|
468
|
+
return geminiProvider({
|
|
469
|
+
apiKey,
|
|
470
|
+
allowInsecure: true,
|
|
471
|
+
...(model ? { model } : {}),
|
|
472
|
+
...(baseUrl ? { baseUrl } : {}),
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
if (!baseUrl || !model)
|
|
476
|
+
return null;
|
|
477
|
+
return openAiCompatible({
|
|
478
|
+
baseUrl,
|
|
479
|
+
allowInsecure: true,
|
|
480
|
+
model,
|
|
481
|
+
...(apiKey ? { apiKey } : {}),
|
|
482
|
+
name: env.TRAZUM_LLM_NAME ?? 'llm',
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
//# sourceMappingURL=llm.js.map
|
package/dist/llm.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm.js","sourceRoot":"","sources":["../src/llm.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Suggesting what somebody probably meant.
|
|
3
|
+
*
|
|
4
|
+
* Used for an unrecognised CLI flag and an unrecognised config key. Both are
|
|
5
|
+
* the same failure: a name that looks right, is silently not the name the tool
|
|
6
|
+
* reads, and leaves a limit unenforced while the author believes it is set.
|
|
7
|
+
*/
|
|
8
|
+
export declare function editDistance(a: string, b: string): number;
|
|
9
|
+
/**
|
|
10
|
+
* The closest candidate, or null when nothing is close enough to name.
|
|
11
|
+
*
|
|
12
|
+
* **The tolerance scales with the length of what was typed**, and that is the
|
|
13
|
+
* whole reason this function exists rather than a fixed threshold. Three edits
|
|
14
|
+
* is a plausible typo on `max-tokens` and a completely different word on
|
|
15
|
+
* `llm` — which is how a fixed budget of three came to answer "did you mean
|
|
16
|
+
* --help?" for `--llm`. A wrong guess is worse than no guess: it sends the
|
|
17
|
+
* reader off to check something that was never the answer.
|
|
18
|
+
*/
|
|
19
|
+
export declare function nearestName(typed: string, candidates: readonly string[]): string | null;
|
|
20
|
+
//# sourceMappingURL=nearest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nearest.d.ts","sourceRoot":"","sources":["../src/nearest.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAaH,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAezD;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CAcvF"}
|
package/dist/nearest.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Suggesting what somebody probably meant.
|
|
3
|
+
*
|
|
4
|
+
* Used for an unrecognised CLI flag and an unrecognised config key. Both are
|
|
5
|
+
* the same failure: a name that looks right, is silently not the name the tool
|
|
6
|
+
* reads, and leaves a limit unenforced while the author believes it is set.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Edit distance, with an early exit.
|
|
10
|
+
*
|
|
11
|
+
* Bounded by construction: the table is two rows of `b.length + 1`, and pairs
|
|
12
|
+
* whose lengths differ by more than `MAX_DISTANCE` return immediately without
|
|
13
|
+
* building anything. Only ever used to rank candidates, so a large distance
|
|
14
|
+
* needs no precision — `SENTINEL` is simply "further than we care about".
|
|
15
|
+
*/
|
|
16
|
+
const MAX_DISTANCE = 3;
|
|
17
|
+
const SENTINEL = 99;
|
|
18
|
+
export function editDistance(a, b) {
|
|
19
|
+
if (Math.abs(a.length - b.length) > MAX_DISTANCE)
|
|
20
|
+
return SENTINEL;
|
|
21
|
+
let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
22
|
+
for (let i = 1; i <= a.length; i++) {
|
|
23
|
+
const row = [i];
|
|
24
|
+
for (let j = 1; j <= b.length; j++) {
|
|
25
|
+
row[j] = Math.min(prev[j] + 1, row[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
26
|
+
}
|
|
27
|
+
prev = row;
|
|
28
|
+
}
|
|
29
|
+
return prev[b.length];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The closest candidate, or null when nothing is close enough to name.
|
|
33
|
+
*
|
|
34
|
+
* **The tolerance scales with the length of what was typed**, and that is the
|
|
35
|
+
* whole reason this function exists rather than a fixed threshold. Three edits
|
|
36
|
+
* is a plausible typo on `max-tokens` and a completely different word on
|
|
37
|
+
* `llm` — which is how a fixed budget of three came to answer "did you mean
|
|
38
|
+
* --help?" for `--llm`. A wrong guess is worse than no guess: it sends the
|
|
39
|
+
* reader off to check something that was never the answer.
|
|
40
|
+
*/
|
|
41
|
+
export function nearestName(typed, candidates) {
|
|
42
|
+
let best = null;
|
|
43
|
+
let bestDistance = Infinity;
|
|
44
|
+
for (const candidate of candidates) {
|
|
45
|
+
const distance = editDistance(typed, candidate);
|
|
46
|
+
if (distance < bestDistance) {
|
|
47
|
+
best = candidate;
|
|
48
|
+
bestDistance = distance;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const tolerance = Math.min(MAX_DISTANCE, Math.max(1, Math.floor(typed.length / 3)));
|
|
52
|
+
return best !== null && bestDistance <= tolerance ? best : null;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=nearest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nearest.js","sourceRoot":"","sources":["../src/nearest.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;GAOG;AACH,MAAM,YAAY,GAAG,CAAC,CAAC;AACvB,MAAM,QAAQ,GAAG,EAAE,CAAC;AAEpB,MAAM,UAAU,YAAY,CAAC,CAAS,EAAE,CAAS;IAC/C,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,GAAG,YAAY;QAAE,OAAO,QAAQ,CAAC;IAClE,IAAI,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;QAChB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACnC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CACf,IAAI,CAAC,CAAC,CAAE,GAAG,CAAC,EACZ,GAAG,CAAC,CAAC,GAAG,CAAC,CAAE,GAAG,CAAC,EACf,IAAI,CAAC,CAAC,GAAG,CAAC,CAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAC/C,CAAC;QACJ,CAAC;QACD,IAAI,GAAG,GAAG,CAAC;IACb,CAAC;IACD,OAAO,IAAI,CAAC,CAAC,CAAC,MAAM,CAAE,CAAC;AACzB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,WAAW,CAAC,KAAa,EAAE,UAA6B;IACtE,IAAI,IAAI,GAAkB,IAAI,CAAC;IAC/B,IAAI,YAAY,GAAG,QAAQ,CAAC;IAE5B,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;QAChD,IAAI,QAAQ,GAAG,YAAY,EAAE,CAAC;YAC5B,IAAI,GAAG,SAAS,CAAC;YACjB,YAAY,GAAG,QAAQ,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACpF,OAAO,IAAI,KAAK,IAAI,IAAI,YAAY,IAAI,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;AAClE,CAAC"}
|