@mcpmake/core 0.2.5 → 0.3.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/dist/analyzer/dom-parser.d.ts +28 -0
- package/dist/analyzer/dom-parser.d.ts.map +1 -1
- package/dist/analyzer/dom-parser.js +77 -2
- package/dist/analyzer/dom-parser.js.map +1 -1
- package/dist/analyzer/goal-crawler.d.ts +3 -2
- package/dist/analyzer/goal-crawler.d.ts.map +1 -1
- package/dist/analyzer/goal-crawler.js +67 -18
- package/dist/analyzer/goal-crawler.js.map +1 -1
- package/dist/analyzer/semantic-analyzer.d.ts +2 -2
- package/dist/analyzer/semantic-analyzer.d.ts.map +1 -1
- package/dist/analyzer/semantic-analyzer.js +16 -17
- package/dist/analyzer/semantic-analyzer.js.map +1 -1
- package/dist/analyzer/site-crawler.d.ts.map +1 -1
- package/dist/analyzer/site-crawler.js +86 -12
- package/dist/analyzer/site-crawler.js.map +1 -1
- package/dist/config/mcpmake-config.d.ts.map +1 -1
- package/dist/config/mcpmake-config.js +5 -0
- package/dist/config/mcpmake-config.js.map +1 -1
- package/dist/emitter/code-writer.d.ts +1 -0
- package/dist/emitter/code-writer.d.ts.map +1 -1
- package/dist/emitter/code-writer.js +79 -12
- package/dist/emitter/code-writer.js.map +1 -1
- package/dist/emitter/index.d.ts +8 -0
- package/dist/emitter/index.d.ts.map +1 -1
- package/dist/emitter/index.js.map +1 -1
- package/dist/emitter/python-templates/dockerfile.hbs +4 -1
- package/dist/emitter/python-templates/server.py.hbs +4 -0
- package/dist/emitter/site-scaffolder.d.ts.map +1 -1
- package/dist/emitter/site-scaffolder.js +28 -1
- package/dist/emitter/site-scaffolder.js.map +1 -1
- package/dist/emitter/site-templates/browser-manager.ts.hbs +24 -3
- package/dist/emitter/site-templates/dockerfile.hbs +13 -4
- package/dist/emitter/site-templates/env.example.hbs +8 -0
- package/dist/emitter/site-templates/server-main-http.ts.hbs +53 -0
- package/dist/emitter/site-templates/telemetry.ts.hbs +117 -0
- package/dist/emitter/templates/auth-provider.ts.hbs +9 -5
- package/dist/emitter/templates/config.ts.hbs +22 -0
- package/dist/emitter/templates/dockerfile.hbs +12 -4
- package/dist/emitter/templates/oauth.ts.hbs +19 -65
- package/dist/emitter/templates/server-main-http.ts.hbs +33 -8
- package/dist/emitter/templates/task-handlers.ts.hbs +40 -1
- package/dist/emitter/worker-templates/worker.ts.hbs +88 -25
- package/dist/generator/spec-generator.d.ts.map +1 -1
- package/dist/generator/spec-generator.js +11 -56
- package/dist/generator/spec-generator.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/llm/anthropic-provider.d.ts +14 -0
- package/dist/llm/anthropic-provider.d.ts.map +1 -0
- package/dist/llm/anthropic-provider.js +50 -0
- package/dist/llm/anthropic-provider.js.map +1 -0
- package/dist/llm/index.d.ts +19 -0
- package/dist/llm/index.d.ts.map +1 -0
- package/dist/llm/index.js +116 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/llm/openai-provider.d.ts +46 -0
- package/dist/llm/openai-provider.d.ts.map +1 -0
- package/dist/llm/openai-provider.js +245 -0
- package/dist/llm/openai-provider.js.map +1 -0
- package/dist/llm/types.d.ts +61 -0
- package/dist/llm/types.d.ts.map +1 -0
- package/dist/llm/types.js +16 -0
- package/dist/llm/types.js.map +1 -0
- package/dist/parser/har-filter.d.ts +7 -0
- package/dist/parser/har-filter.d.ts.map +1 -1
- package/dist/parser/har-filter.js +107 -1
- package/dist/parser/har-filter.js.map +1 -1
- package/dist/parser/index.d.ts +1 -1
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/parser/index.js +1 -1
- package/dist/parser/index.js.map +1 -1
- package/dist/parser/openapi-loader.d.ts.map +1 -1
- package/dist/parser/openapi-loader.js +36 -2
- package/dist/parser/openapi-loader.js.map +1 -1
- package/dist/parser/postman-loader.d.ts.map +1 -1
- package/dist/parser/postman-loader.js +6 -1
- package/dist/parser/postman-loader.js.map +1 -1
- package/dist/parser/schema-converter.d.ts.map +1 -1
- package/dist/parser/schema-converter.js +34 -23
- package/dist/parser/schema-converter.js.map +1 -1
- package/dist/plugins/loader.d.ts +5 -1
- package/dist/plugins/loader.d.ts.map +1 -1
- package/dist/plugins/loader.js +39 -10
- package/dist/plugins/loader.js.map +1 -1
- package/dist/pricing.d.ts +2 -1
- package/dist/pricing.d.ts.map +1 -1
- package/dist/pricing.js +11 -5
- package/dist/pricing.js.map +1 -1
- package/dist/recorder/browser-recorder.d.ts.map +1 -1
- package/dist/recorder/browser-recorder.js +50 -5
- package/dist/recorder/browser-recorder.js.map +1 -1
- package/dist/rescan/rescan-scheduler.d.ts.map +1 -1
- package/dist/rescan/rescan-scheduler.js +22 -7
- package/dist/rescan/rescan-scheduler.js.map +1 -1
- package/dist/site-transformer/selector-healer.d.ts +1 -1
- package/dist/site-transformer/selector-healer.d.ts.map +1 -1
- package/dist/site-transformer/selector-healer.js +17 -18
- package/dist/site-transformer/selector-healer.js.map +1 -1
- package/dist/transformer/auth-detector.d.ts.map +1 -1
- package/dist/transformer/auth-detector.js +13 -1
- package/dist/transformer/auth-detector.js.map +1 -1
- package/dist/transformer/har-schema-inferrer.d.ts.map +1 -1
- package/dist/transformer/har-schema-inferrer.js +2 -0
- package/dist/transformer/har-schema-inferrer.js.map +1 -1
- package/dist/transformer/har-to-operations.d.ts +1 -0
- package/dist/transformer/har-to-operations.d.ts.map +1 -1
- package/dist/transformer/har-to-operations.js +42 -10
- package/dist/transformer/har-to-operations.js.map +1 -1
- package/dist/transformer/llm-namer.d.ts +1 -1
- package/dist/transformer/llm-namer.d.ts.map +1 -1
- package/dist/transformer/llm-namer.js +12 -15
- package/dist/transformer/llm-namer.js.map +1 -1
- package/dist/transformer/tool-builder.d.ts.map +1 -1
- package/dist/transformer/tool-builder.js +30 -1
- package/dist/transformer/tool-builder.js.map +1 -1
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/fail.d.ts.map +1 -1
- package/dist/utils/fail.js +22 -4
- package/dist/utils/fail.js.map +1 -1
- package/dist/utils/json-extract.d.ts +21 -0
- package/dist/utils/json-extract.d.ts.map +1 -0
- package/dist/utils/json-extract.js +67 -0
- package/dist/utils/json-extract.js.map +1 -0
- package/dist/utils/model-resolver.d.ts.map +1 -1
- package/dist/utils/model-resolver.js +19 -4
- package/dist/utils/model-resolver.js.map +1 -1
- package/dist/utils/sanitize.d.ts +1 -1
- package/dist/utils/sanitize.d.ts.map +1 -1
- package/dist/utils/sanitize.js +6 -2
- package/dist/utils/sanitize.js.map +1 -1
- package/dist/utils/ssrf-guard.d.ts +10 -0
- package/dist/utils/ssrf-guard.d.ts.map +1 -0
- package/dist/utils/ssrf-guard.js +130 -0
- package/dist/utils/ssrf-guard.js.map +1 -0
- package/dist/utils/watcher.d.ts +17 -1
- package/dist/utils/watcher.d.ts.map +1 -1
- package/dist/utils/watcher.js +53 -21
- package/dist/utils/watcher.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai-provider.d.ts","sourceRoot":"","sources":["../../src/llm/openai-provider.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EACV,mBAAmB,EACnB,eAAe,EACf,WAAW,EAGZ,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,QAAQ,GAAG,mBAAmB,CAAC;CACtC;AAgCD,qBAAa,cAAe,YAAW,WAAW;IAChD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,cAAc,QAAQ;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAiC;IAGtD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAgC;IAG3D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IAGjC,OAAO,CAAC,mBAAmB,CAAS;gBAExB,IAAI,EAAE,qBAAqB;IAOvC;;;;;;;;;;;;;;;OAeG;YACW,YAAY;IA2D1B;;;;OAIG;IACH,OAAO,CAAC,UAAU;IAQlB,kFAAkF;IAClF,OAAO,CAAC,aAAa;IAOf,YAAY,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC;IAa1F,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE,EAC9B,MAAM,EACN,MAAM,EACN,MAAM,EACN,IAAI,EACJ,KAAK,EACL,SAAS,GACV,EAAE,mBAAmB,GAAG,OAAO,CAAC,CAAC,CAAC;IA2EnC,8EAA8E;IAC9E,OAAO,CAAC,iBAAiB;CAO1B"}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI-family provider. One class serves BOTH:
|
|
3
|
+
* - `openai` — the hosted api.openai.com endpoint, and
|
|
4
|
+
* - `openai-compatible` — any self-hosted server speaking the OpenAI wire
|
|
5
|
+
* format (Ollama, vLLM, LM Studio, Groq, Together, …).
|
|
6
|
+
*
|
|
7
|
+
* The two differ in three places, all handled here:
|
|
8
|
+
* 1. Model resolution against `/v1/models` — hosted OpenAI always lists models,
|
|
9
|
+
* compatible servers may not implement the endpoint at all.
|
|
10
|
+
* 2. The output-token parameter name (`max_completion_tokens` vs `max_tokens`).
|
|
11
|
+
* 3. Structured-output support (`response_format: json_schema`), which many
|
|
12
|
+
* compatible servers don't honor — so `completeJson` degrades gracefully.
|
|
13
|
+
*/
|
|
14
|
+
import OpenAI from 'openai';
|
|
15
|
+
import { extractJsonObject } from '../utils/json-extract.js';
|
|
16
|
+
import { logger } from '../utils/logger.js';
|
|
17
|
+
/**
|
|
18
|
+
* Preferred (undated) model id per tier. Model ids rot — a whole family can be
|
|
19
|
+
* retired and a hardcoded snapshot then silently 404s — so the preferred id is
|
|
20
|
+
* only trusted when the live `/v1/models` listing confirms it (mirrors the
|
|
21
|
+
* Anthropic resolver in {@link ../utils/model-resolver}).
|
|
22
|
+
*/
|
|
23
|
+
const PREFERRED = {
|
|
24
|
+
balanced: 'gpt-4o',
|
|
25
|
+
fast: 'gpt-4o-mini',
|
|
26
|
+
};
|
|
27
|
+
const TIER_PATTERN = {
|
|
28
|
+
balanced: /gpt-(5|4\.1|4o|4)/i,
|
|
29
|
+
fast: /mini|nano|small|flash|haiku/i,
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* True only for an HTTP 400 from the OpenAI SDK — the signal that a server or
|
|
33
|
+
* model rejects a *request format* (e.g. an unsupported `response_format`),
|
|
34
|
+
* which the `completeJson` fallback chain may legitimately downgrade past.
|
|
35
|
+
*
|
|
36
|
+
* Every other error (401 auth, 429 rate limit, 5xx, network/connection) is a
|
|
37
|
+
* hard failure that retrying with a weaker request mode will not fix, so those
|
|
38
|
+
* must propagate untouched instead of being masked as "unparseable JSON". The
|
|
39
|
+
* SDK exposes the HTTP status as `err.status` on its error objects.
|
|
40
|
+
*/
|
|
41
|
+
function isFormatError(err) {
|
|
42
|
+
return err?.status === 400;
|
|
43
|
+
}
|
|
44
|
+
export class OpenAiProvider {
|
|
45
|
+
name;
|
|
46
|
+
supportsVision = true;
|
|
47
|
+
client;
|
|
48
|
+
kind;
|
|
49
|
+
// Resolved once per tier per process: the served-model list rarely changes
|
|
50
|
+
// mid-run, and we don't want a Models API round-trip before every call.
|
|
51
|
+
modelCache = new Map();
|
|
52
|
+
// The endpoint we talk to, used in error messages so a misconfigured
|
|
53
|
+
// openai-compatible server is obvious from the thrown error alone.
|
|
54
|
+
baseURL;
|
|
55
|
+
// Whether we've already warned about a structured-output downgrade. Warning
|
|
56
|
+
// once per process keeps the log readable when many JSON calls degrade.
|
|
57
|
+
jsonDowngradeWarned = false;
|
|
58
|
+
constructor(opts) {
|
|
59
|
+
this.kind = opts.kind;
|
|
60
|
+
this.name = opts.kind;
|
|
61
|
+
this.baseURL = opts.baseURL ?? 'https://api.openai.com/v1';
|
|
62
|
+
this.client = new OpenAI({ apiKey: opts.apiKey, baseURL: opts.baseURL });
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Resolve a usable model id for a tier against the live `/v1/models` listing,
|
|
66
|
+
* so a retired id can never silently 404 the way a hardcoded snapshot does.
|
|
67
|
+
*
|
|
68
|
+
* Resolution order:
|
|
69
|
+
* 1. An explicit `override` (e.g. a `--model` flag) wins, unchecked — the
|
|
70
|
+
* caller knows what they want, and a bad id surfaces as a clear API error.
|
|
71
|
+
* 2. The tier's preferred id, **if** the listing contains it.
|
|
72
|
+
* 3. Otherwise the newest served model whose id matches the tier pattern.
|
|
73
|
+
* 4. No match: hosted OpenAI falls back to the preferred id; an
|
|
74
|
+
* openai-compatible server falls back to its first listed model (or throws
|
|
75
|
+
* if it lists nothing).
|
|
76
|
+
* 5. Listing throws (server has no `/v1/models`): hosted OpenAI returns the
|
|
77
|
+
* preferred id WITHOUT caching (so a later call retries); an
|
|
78
|
+
* openai-compatible server throws — there's nothing safe to guess.
|
|
79
|
+
*/
|
|
80
|
+
async resolveModel(tier, override) {
|
|
81
|
+
if (override)
|
|
82
|
+
return override;
|
|
83
|
+
const cached = this.modelCache.get(tier);
|
|
84
|
+
if (cached)
|
|
85
|
+
return cached;
|
|
86
|
+
const preferred = PREFERRED[tier];
|
|
87
|
+
let choice;
|
|
88
|
+
let models;
|
|
89
|
+
try {
|
|
90
|
+
const page = await this.client.models.list();
|
|
91
|
+
models = page.data;
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
// Transient or unimplemented Models API.
|
|
95
|
+
if (this.kind === 'openai') {
|
|
96
|
+
// Return the fallback for this call WITHOUT caching it, so a later call
|
|
97
|
+
// retries once the API recovers (a poisoned cache would pin the whole
|
|
98
|
+
// process to the fallback id).
|
|
99
|
+
logger.warn(`Could not list models (${err instanceof Error ? err.message : err}); using "${preferred}"`);
|
|
100
|
+
return preferred;
|
|
101
|
+
}
|
|
102
|
+
throw new Error('Could not list models; pass --model for openai-compatible providers');
|
|
103
|
+
}
|
|
104
|
+
if (models.some((m) => m.id === preferred)) {
|
|
105
|
+
choice = preferred;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
const pattern = TIER_PATTERN[tier];
|
|
109
|
+
const newest = models
|
|
110
|
+
.filter((m) => pattern.test(m.id))
|
|
111
|
+
// `created` is an epoch number (seconds); missing values sort oldest.
|
|
112
|
+
.sort((a, b) => (b.created ?? 0) - (a.created ?? 0))[0];
|
|
113
|
+
if (newest) {
|
|
114
|
+
logger.warn(`Preferred model "${preferred}" unavailable; using "${newest.id}"`);
|
|
115
|
+
choice = newest.id;
|
|
116
|
+
}
|
|
117
|
+
else if (this.kind === 'openai') {
|
|
118
|
+
logger.warn(`Preferred model "${preferred}" unavailable and no ${tier} model found; trying it anyway`);
|
|
119
|
+
choice = preferred;
|
|
120
|
+
}
|
|
121
|
+
else if (models[0]) {
|
|
122
|
+
// openai-compatible: no tiered match, but the server lists *something* —
|
|
123
|
+
// use the first model rather than guessing an OpenAI id it won't have.
|
|
124
|
+
logger.warn(`No ${tier} model matched on ${this.baseURL}; using "${models[0].id}"`);
|
|
125
|
+
choice = models[0].id;
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
throw new Error(`No model available from ${this.baseURL}; pass --model`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Only successful resolutions are cached.
|
|
132
|
+
this.modelCache.set(tier, choice);
|
|
133
|
+
return choice;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Output-token parameter, keyed by provider kind: newer hosted OpenAI models
|
|
137
|
+
* reject `max_tokens` and require `max_completion_tokens`, while many
|
|
138
|
+
* openai-compatible servers only understand the older `max_tokens`.
|
|
139
|
+
*/
|
|
140
|
+
tokenParam(maxTokens) {
|
|
141
|
+
return this.kind === 'openai'
|
|
142
|
+
? { max_completion_tokens: maxTokens }
|
|
143
|
+
: { max_tokens: maxTokens };
|
|
144
|
+
}
|
|
145
|
+
/** Build the message list: a leading system message only when `system` is set. */
|
|
146
|
+
buildMessages(prompt, system) {
|
|
147
|
+
const messages = [];
|
|
148
|
+
if (system)
|
|
149
|
+
messages.push({ role: 'system', content: system });
|
|
150
|
+
messages.push({ role: 'user', content: prompt });
|
|
151
|
+
return messages;
|
|
152
|
+
}
|
|
153
|
+
async completeText({ system, prompt, tier, model, maxTokens }) {
|
|
154
|
+
const resolved = await this.resolveModel(tier, model);
|
|
155
|
+
const messages = this.buildMessages(prompt, system);
|
|
156
|
+
const res = await this.client.chat.completions.create({
|
|
157
|
+
model: resolved,
|
|
158
|
+
messages,
|
|
159
|
+
...this.tokenParam(maxTokens),
|
|
160
|
+
});
|
|
161
|
+
return res.choices[0]?.message?.content ?? '';
|
|
162
|
+
}
|
|
163
|
+
async completeJson({ system, prompt, schema, tier, model, maxTokens, }) {
|
|
164
|
+
const resolved = await this.resolveModel(tier, model);
|
|
165
|
+
const tokenParam = this.tokenParam(maxTokens);
|
|
166
|
+
// Nudge appended to the prompt for the non-native fallbacks, so the model
|
|
167
|
+
// still aims at the schema even without enforced structured output.
|
|
168
|
+
const schemaNudge = `Respond with JSON only that conforms to this JSON Schema:\n${JSON.stringify(schema)}`;
|
|
169
|
+
// 1) Native structured output (`json_schema`). Best fidelity when supported.
|
|
170
|
+
let content;
|
|
171
|
+
try {
|
|
172
|
+
const res = await this.client.chat.completions.create({
|
|
173
|
+
model: resolved,
|
|
174
|
+
messages: this.buildMessages(prompt, system),
|
|
175
|
+
response_format: {
|
|
176
|
+
type: 'json_schema',
|
|
177
|
+
json_schema: { name: 'response', strict: true, schema },
|
|
178
|
+
},
|
|
179
|
+
...tokenParam,
|
|
180
|
+
});
|
|
181
|
+
content = res.choices[0]?.message?.content ?? '';
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
// Only an HTTP 400 means "this server/model rejects the structured-output
|
|
185
|
+
// *format*" — that is the one error we downgrade past. A hard error (401
|
|
186
|
+
// auth, 429 rate limit, 5xx, network) is not fixed by retrying with a
|
|
187
|
+
// weaker request mode, so rethrow it rather than masking it behind a later
|
|
188
|
+
// "unparseable JSON". (See how the SDK surfaces `status` on its errors.)
|
|
189
|
+
if (!isFormatError(err))
|
|
190
|
+
throw err;
|
|
191
|
+
this.warnJsonDowngrade();
|
|
192
|
+
try {
|
|
193
|
+
// 2) `json_object` mode + schema-in-prompt: forces valid JSON without
|
|
194
|
+
// enforcing the schema, which most compatible servers support.
|
|
195
|
+
const messages = this.buildMessages(prompt, system);
|
|
196
|
+
messages.push({ role: 'user', content: schemaNudge });
|
|
197
|
+
const res = await this.client.chat.completions.create({
|
|
198
|
+
model: resolved,
|
|
199
|
+
messages,
|
|
200
|
+
response_format: { type: 'json_object' },
|
|
201
|
+
...tokenParam,
|
|
202
|
+
});
|
|
203
|
+
content = res.choices[0]?.message?.content ?? '';
|
|
204
|
+
}
|
|
205
|
+
catch (err2) {
|
|
206
|
+
// Same rule one tier down: only a 400 (server rejects `response_format`)
|
|
207
|
+
// justifies the final plain-completion attempt; anything else rethrows.
|
|
208
|
+
if (!isFormatError(err2))
|
|
209
|
+
throw err2;
|
|
210
|
+
// 3) Plain completion + schema-in-prompt: last resort for servers that
|
|
211
|
+
// reject `response_format` entirely. Rely on the JSON extractor.
|
|
212
|
+
const messages = this.buildMessages(prompt, system);
|
|
213
|
+
messages.push({ role: 'user', content: schemaNudge });
|
|
214
|
+
const res = await this.client.chat.completions.create({
|
|
215
|
+
model: resolved,
|
|
216
|
+
messages,
|
|
217
|
+
...tokenParam,
|
|
218
|
+
});
|
|
219
|
+
content = res.choices[0]?.message?.content ?? '';
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const json = extractJsonObject(content);
|
|
223
|
+
if (json === null)
|
|
224
|
+
throw new Error('OpenAI provider returned unparseable JSON');
|
|
225
|
+
let parsed;
|
|
226
|
+
try {
|
|
227
|
+
parsed = JSON.parse(json);
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
throw new Error('OpenAI provider returned unparseable JSON');
|
|
231
|
+
}
|
|
232
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
233
|
+
throw new Error('OpenAI provider returned unparseable JSON');
|
|
234
|
+
}
|
|
235
|
+
return parsed;
|
|
236
|
+
}
|
|
237
|
+
/** Warn once when structured output is downgraded, so the loss is visible. */
|
|
238
|
+
warnJsonDowngrade() {
|
|
239
|
+
if (this.jsonDowngradeWarned)
|
|
240
|
+
return;
|
|
241
|
+
this.jsonDowngradeWarned = true;
|
|
242
|
+
logger.warn(`${this.name}: native JSON-schema output not supported on ${this.baseURL}; falling back to best-effort JSON parsing`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=openai-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai-provider.js","sourceRoot":"","sources":["../../src/llm/openai-provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-agnostic LLM abstraction.
|
|
3
|
+
*
|
|
4
|
+
* The CLI's AI features (spec generation, tool naming, semantic naming,
|
|
5
|
+
* goal-directed crawl, selector healing) used to call `@anthropic-ai/sdk`
|
|
6
|
+
* directly. They now go through an {@link LlmProvider} so the same features can
|
|
7
|
+
* run against Claude, OpenAI/ChatGPT, or any OpenAI-compatible endpoint
|
|
8
|
+
* (Ollama, vLLM, LM Studio, Groq, Together, OpenRouter, …).
|
|
9
|
+
*
|
|
10
|
+
* The active provider is selected by environment (see `getLlmProvider`):
|
|
11
|
+
* `MCPMAKE_LLM_PROVIDER` + the matching key/base-url vars. Anthropic stays the
|
|
12
|
+
* default so existing setups are unaffected.
|
|
13
|
+
*/
|
|
14
|
+
/** Model tiers used across the generator. */
|
|
15
|
+
export type ModelTier = 'balanced' | 'fast';
|
|
16
|
+
/** Which backend the active provider talks to. */
|
|
17
|
+
export type ProviderKind = 'anthropic' | 'openai' | 'openai-compatible';
|
|
18
|
+
/** All supported provider kinds, for validation + help text. */
|
|
19
|
+
export declare const PROVIDER_KINDS: readonly ProviderKind[];
|
|
20
|
+
/** A single completion request. */
|
|
21
|
+
export interface CompleteOptions {
|
|
22
|
+
/** Optional system instruction. */
|
|
23
|
+
system?: string;
|
|
24
|
+
/** User prompt. */
|
|
25
|
+
prompt: string;
|
|
26
|
+
/** Tier resolved to a concrete model when `model` is not given. */
|
|
27
|
+
tier: ModelTier;
|
|
28
|
+
/** Explicit model id — wins over tier resolution, passed through unchecked. */
|
|
29
|
+
model?: string;
|
|
30
|
+
/** Hard cap on output tokens. */
|
|
31
|
+
maxTokens: number;
|
|
32
|
+
}
|
|
33
|
+
/** A completion request constrained to a JSON Schema. */
|
|
34
|
+
export interface CompleteJsonOptions extends CompleteOptions {
|
|
35
|
+
/** JSON Schema the response must satisfy (flat/non-recursive for native modes). */
|
|
36
|
+
schema: Record<string, unknown>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* One LLM backend. Implementations hide SDK + wire-format differences:
|
|
40
|
+
* structured output (Anthropic `messages.parse` vs OpenAI `response_format`),
|
|
41
|
+
* model resolution (live model listing per provider), and text completion.
|
|
42
|
+
*/
|
|
43
|
+
export interface LlmProvider {
|
|
44
|
+
/** Human-readable provider id, e.g. `anthropic` / `openai` / `openai-compatible`. */
|
|
45
|
+
readonly name: string;
|
|
46
|
+
/** Whether this provider/model class can accept image inputs (future use). */
|
|
47
|
+
readonly supportsVision: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Plain text completion. Returns the model's raw text (callers that want JSON
|
|
50
|
+
* run it through `utils/json-extract`). Throws on transport/API failure.
|
|
51
|
+
*/
|
|
52
|
+
completeText(options: CompleteOptions): Promise<string>;
|
|
53
|
+
/**
|
|
54
|
+
* Schema-constrained completion returning the parsed object. Uses the
|
|
55
|
+
* provider's native structured-output mode when available, otherwise falls
|
|
56
|
+
* back to a text completion parsed with the shared JSON extractor. Throws if
|
|
57
|
+
* the response cannot be parsed into an object.
|
|
58
|
+
*/
|
|
59
|
+
completeJson<T = unknown>(options: CompleteJsonOptions): Promise<T>;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/llm/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,6CAA6C;AAC7C,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG,MAAM,CAAC;AAE5C,kDAAkD;AAClD,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,QAAQ,GAAG,mBAAmB,CAAC;AAExE,gEAAgE;AAChE,eAAO,MAAM,cAAc,EAAE,SAAS,YAAY,EAAiD,CAAC;AAEpG,mCAAmC;AACnC,MAAM,WAAW,eAAe;IAC9B,mCAAmC;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mBAAmB;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,IAAI,EAAE,SAAS,CAAC;IAChB,+EAA+E;IAC/E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,yDAAyD;AACzD,MAAM,WAAW,mBAAoB,SAAQ,eAAe;IAC1D,mFAAmF;IACnF,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,qFAAqF;IACrF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IAEjC;;;OAGG;IACH,YAAY,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAExD;;;;;OAKG;IACH,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACrE"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-agnostic LLM abstraction.
|
|
3
|
+
*
|
|
4
|
+
* The CLI's AI features (spec generation, tool naming, semantic naming,
|
|
5
|
+
* goal-directed crawl, selector healing) used to call `@anthropic-ai/sdk`
|
|
6
|
+
* directly. They now go through an {@link LlmProvider} so the same features can
|
|
7
|
+
* run against Claude, OpenAI/ChatGPT, or any OpenAI-compatible endpoint
|
|
8
|
+
* (Ollama, vLLM, LM Studio, Groq, Together, OpenRouter, …).
|
|
9
|
+
*
|
|
10
|
+
* The active provider is selected by environment (see `getLlmProvider`):
|
|
11
|
+
* `MCPMAKE_LLM_PROVIDER` + the matching key/base-url vars. Anthropic stays the
|
|
12
|
+
* default so existing setups are unaffected.
|
|
13
|
+
*/
|
|
14
|
+
/** All supported provider kinds, for validation + help text. */
|
|
15
|
+
export const PROVIDER_KINDS = ['anthropic', 'openai', 'openai-compatible'];
|
|
16
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/llm/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAQH,gEAAgE;AAChE,MAAM,CAAC,MAAM,cAAc,GAA4B,CAAC,WAAW,EAAE,QAAQ,EAAE,mBAAmB,CAAC,CAAC"}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import type { Entry } from 'har-format';
|
|
2
|
+
/**
|
|
3
|
+
* Scrub credentials out of a HAR entry in place: Authorization/Cookie/API-key
|
|
4
|
+
* request & response headers, and the parsed request/response cookie arrays.
|
|
5
|
+
* Recorded HAR is persisted to disk, so this prevents live secrets leaking in
|
|
6
|
+
* cleartext. Safe to call more than once (redaction is idempotent).
|
|
7
|
+
*/
|
|
8
|
+
export declare function redactEntrySecrets(entry: Entry): Entry;
|
|
2
9
|
export interface FilterOptions {
|
|
3
10
|
/** Only include entries from these domains. If empty, include all. */
|
|
4
11
|
allowedDomains?: string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"har-filter.d.ts","sourceRoot":"","sources":["../../src/parser/har-filter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"har-filter.d.ts","sourceRoot":"","sources":["../../src/parser/har-filter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAU,KAAK,EAAU,MAAM,YAAY,CAAC;AA6FxD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,CAMtD;AAiCD,MAAM,WAAW,aAAa;IAC5B,sEAAsE;IACtE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,sEAAsE;IACtE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,OAAO,GAAE,aAAkB,GAAG,KAAK,EAAE,CAgDvF"}
|
|
@@ -1,3 +1,105 @@
|
|
|
1
|
+
const REDACTED = '<redacted>';
|
|
2
|
+
/**
|
|
3
|
+
* Headers whose value is a credential and must be scrubbed from recorded HAR.
|
|
4
|
+
* We keep the header NAME (and, for Authorization, the scheme word) so auth
|
|
5
|
+
* detection downstream still works, but strip the secret material.
|
|
6
|
+
*/
|
|
7
|
+
const SENSITIVE_HEADERS = new Set([
|
|
8
|
+
'authorization',
|
|
9
|
+
'proxy-authorization',
|
|
10
|
+
'cookie',
|
|
11
|
+
'set-cookie',
|
|
12
|
+
'x-api-key',
|
|
13
|
+
'api-key',
|
|
14
|
+
'apikey',
|
|
15
|
+
'authorization-token',
|
|
16
|
+
'x-auth-token',
|
|
17
|
+
'x-access-token',
|
|
18
|
+
'x-session-token',
|
|
19
|
+
'x-csrf-token',
|
|
20
|
+
'x-amz-security-token',
|
|
21
|
+
'x-goog-api-key',
|
|
22
|
+
]);
|
|
23
|
+
// Authorization values are `<scheme> <credential>`; keep the scheme so
|
|
24
|
+
// downstream auth detection can still classify bearer/basic/token.
|
|
25
|
+
const AUTH_SCHEME_PATTERN = /^(\s*[A-Za-z][\w-]*\s+)\S[\s\S]*$/;
|
|
26
|
+
/**
|
|
27
|
+
* Redact the secret value of a single header in place, preserving the header
|
|
28
|
+
* name (and the auth scheme word for Authorization-style headers) so auth
|
|
29
|
+
* detection still sees a scheme exists.
|
|
30
|
+
*/
|
|
31
|
+
function redactHeaderValue(name, value) {
|
|
32
|
+
const lower = name.toLowerCase();
|
|
33
|
+
if (lower === 'cookie')
|
|
34
|
+
return redactCookiePairs(value);
|
|
35
|
+
if (lower === 'set-cookie')
|
|
36
|
+
return redactSetCookie(value);
|
|
37
|
+
if (lower === 'authorization' || lower === 'proxy-authorization') {
|
|
38
|
+
const m = AUTH_SCHEME_PATTERN.exec(value);
|
|
39
|
+
if (m)
|
|
40
|
+
return `${m[1].trimStart()}${REDACTED}`;
|
|
41
|
+
}
|
|
42
|
+
return REDACTED;
|
|
43
|
+
}
|
|
44
|
+
/** Replace a single `name[=value]` segment's value with the redaction marker. */
|
|
45
|
+
function redactPairValue(pair) {
|
|
46
|
+
const eq = pair.indexOf('=');
|
|
47
|
+
if (eq === -1)
|
|
48
|
+
return pair; // attribute flag (e.g. `Secure`) — no value.
|
|
49
|
+
return `${pair.slice(0, eq)}=${REDACTED}`;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Cookie request header: every `;`-separated segment is a `name=value` cookie.
|
|
53
|
+
* `s=Y; foo=bar` becomes `s=<redacted>; foo=<redacted>` (names kept).
|
|
54
|
+
*/
|
|
55
|
+
function redactCookiePairs(value) {
|
|
56
|
+
return value
|
|
57
|
+
.split(';')
|
|
58
|
+
.map((part) => part.replace(/^(\s*)(.*)$/, (_, ws, body) => ws + redactPairValue(body)))
|
|
59
|
+
.join(';');
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Set-Cookie response header: only the FIRST segment is the cookie `name=value`;
|
|
63
|
+
* the rest (Path, Domain, Expires, Secure, ...) are attributes carrying no
|
|
64
|
+
* secret, so they are preserved verbatim to keep the HAR shape intact.
|
|
65
|
+
*/
|
|
66
|
+
function redactSetCookie(value) {
|
|
67
|
+
const semi = value.indexOf(';');
|
|
68
|
+
if (semi === -1)
|
|
69
|
+
return redactCookiePairs(value);
|
|
70
|
+
const head = value.slice(0, semi);
|
|
71
|
+
const rest = value.slice(semi); // includes leading ';'
|
|
72
|
+
return redactCookiePairs(head) + rest;
|
|
73
|
+
}
|
|
74
|
+
function redactHeaders(headers) {
|
|
75
|
+
if (!headers)
|
|
76
|
+
return;
|
|
77
|
+
for (const header of headers) {
|
|
78
|
+
if (SENSITIVE_HEADERS.has(header.name.toLowerCase())) {
|
|
79
|
+
header.value = redactHeaderValue(header.name, header.value);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function redactCookies(cookies) {
|
|
84
|
+
if (!cookies)
|
|
85
|
+
return;
|
|
86
|
+
for (const cookie of cookies) {
|
|
87
|
+
cookie.value = REDACTED;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Scrub credentials out of a HAR entry in place: Authorization/Cookie/API-key
|
|
92
|
+
* request & response headers, and the parsed request/response cookie arrays.
|
|
93
|
+
* Recorded HAR is persisted to disk, so this prevents live secrets leaking in
|
|
94
|
+
* cleartext. Safe to call more than once (redaction is idempotent).
|
|
95
|
+
*/
|
|
96
|
+
export function redactEntrySecrets(entry) {
|
|
97
|
+
redactHeaders(entry.request?.headers);
|
|
98
|
+
redactHeaders(entry.response?.headers);
|
|
99
|
+
redactCookies(entry.request?.cookies);
|
|
100
|
+
redactCookies(entry.response?.cookies);
|
|
101
|
+
return entry;
|
|
102
|
+
}
|
|
1
103
|
const NOISE_EXTENSIONS = /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map|webp|avif)(\?|$)/i;
|
|
2
104
|
const ANALYTICS_DOMAINS = [
|
|
3
105
|
'google-analytics.com',
|
|
@@ -26,7 +128,7 @@ const SKIP_MIME_TYPES = [
|
|
|
26
128
|
'video/',
|
|
27
129
|
];
|
|
28
130
|
export function filterHarEntries(entries, options = {}) {
|
|
29
|
-
|
|
131
|
+
const kept = entries.filter((entry) => {
|
|
30
132
|
const url = entry.request.url;
|
|
31
133
|
let hostname;
|
|
32
134
|
try {
|
|
@@ -68,5 +170,9 @@ export function filterHarEntries(entries, options = {}) {
|
|
|
68
170
|
}
|
|
69
171
|
return true;
|
|
70
172
|
});
|
|
173
|
+
// Scrub credentials before the HAR (or anything derived from it) is persisted.
|
|
174
|
+
for (const entry of kept)
|
|
175
|
+
redactEntrySecrets(entry);
|
|
176
|
+
return kept;
|
|
71
177
|
}
|
|
72
178
|
//# sourceMappingURL=har-filter.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"har-filter.js","sourceRoot":"","sources":["../../src/parser/har-filter.ts"],"names":[],"mappings":"AAEA,MAAM,gBAAgB,GACpB,6EAA6E,CAAC;AAEhF,MAAM,iBAAiB,GAAG;IACxB,sBAAsB;IACtB,sBAAsB;IACtB,YAAY;IACZ,cAAc;IACd,YAAY;IACZ,aAAa;IACb,YAAY;IACZ,iBAAiB;IACjB,cAAc;IACd,WAAW;IACX,WAAW;IACX,cAAc;IACd,eAAe;IACf,YAAY;CACb,CAAC;AAEF,MAAM,eAAe,GAAG;IACtB,WAAW;IACX,UAAU;IACV,wBAAwB;IACxB,iBAAiB;IACjB,QAAQ;IACR,OAAO;IACP,QAAQ;IACR,QAAQ;CACT,CAAC;AASF,MAAM,UAAU,gBAAgB,CAAC,OAAgB,EAAE,UAAyB,EAAE;IAC5E,
|
|
1
|
+
{"version":3,"file":"har-filter.js","sourceRoot":"","sources":["../../src/parser/har-filter.ts"],"names":[],"mappings":"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"}
|
package/dist/parser/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { extractOperations } from './operation-extractor.js';
|
|
|
4
4
|
export type { ExtractionResult } from './operation-extractor.js';
|
|
5
5
|
export { jsonSchemaToZodCode, buildOperationInputSchema } from './schema-converter.js';
|
|
6
6
|
export { loadHarFile } from './har-loader.js';
|
|
7
|
-
export { filterHarEntries } from './har-filter.js';
|
|
7
|
+
export { filterHarEntries, redactEntrySecrets } from './har-filter.js';
|
|
8
8
|
export type { FilterOptions } from './har-filter.js';
|
|
9
9
|
export { normalizeEntry } from './har-normalizer.js';
|
|
10
10
|
export type { NormalizedEntry, PathParam, QueryParam } from './har-normalizer.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/parser/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,YAAY,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,YAAY,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AACvF,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/parser/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,YAAY,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,YAAY,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AACvF,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACvE,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/parser/index.js
CHANGED
|
@@ -2,6 +2,6 @@ export { loadOpenApiSpec } from './openapi-loader.js';
|
|
|
2
2
|
export { extractOperations } from './operation-extractor.js';
|
|
3
3
|
export { jsonSchemaToZodCode, buildOperationInputSchema } from './schema-converter.js';
|
|
4
4
|
export { loadHarFile } from './har-loader.js';
|
|
5
|
-
export { filterHarEntries } from './har-filter.js';
|
|
5
|
+
export { filterHarEntries, redactEntrySecrets } from './har-filter.js';
|
|
6
6
|
export { normalizeEntry } from './har-normalizer.js';
|
|
7
7
|
//# sourceMappingURL=index.js.map
|
package/dist/parser/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/parser/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,OAAO,EAAE,mBAAmB,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AACvF,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/parser/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,OAAO,EAAE,mBAAmB,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AACvF,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAEvE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openapi-loader.d.ts","sourceRoot":"","sources":["../../src/parser/openapi-loader.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"openapi-loader.d.ts","sourceRoot":"","sources":["../../src/parser/openapi-loader.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAI7C,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,OAAO,CAAC,QAAQ,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAyXD,wBAAsB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAqBxE"}
|
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
import SwaggerParser from '@apidevtools/swagger-parser';
|
|
2
2
|
import { logger } from '../utils/logger.js';
|
|
3
|
+
import { assertPublicUrl } from '../utils/ssrf-guard.js';
|
|
4
|
+
function isHttpUrl(input) {
|
|
5
|
+
return /^https?:\/\//i.test(input.trim());
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Parser options that route every remote `$ref` fetch through the SSRF guard.
|
|
9
|
+
*
|
|
10
|
+
* The custom `read` runs before any download, so each remote ref URL is rejected
|
|
11
|
+
* if it resolves to a private/reserved host. Redirects are not followed
|
|
12
|
+
* (`redirect: 'error'`): a 3xx to an unvetted host (e.g. the metadata endpoint)
|
|
13
|
+
* surfaces as an error instead of an unguarded fetch.
|
|
14
|
+
*/
|
|
15
|
+
const guardedHttpResolver = {
|
|
16
|
+
order: 200,
|
|
17
|
+
canRead(file) {
|
|
18
|
+
return /^https?:\/\//i.test(file.url);
|
|
19
|
+
},
|
|
20
|
+
async read(file) {
|
|
21
|
+
await assertPublicUrl(file.url);
|
|
22
|
+
const res = await fetch(file.url, { redirect: 'error' });
|
|
23
|
+
if (!res.ok) {
|
|
24
|
+
throw new Error(`Error downloading ${file.url}: HTTP ${res.status}`);
|
|
25
|
+
}
|
|
26
|
+
return Buffer.from(await res.arrayBuffer());
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
const guardedParserOptions = {
|
|
30
|
+
resolve: { http: guardedHttpResolver },
|
|
31
|
+
};
|
|
3
32
|
/**
|
|
4
33
|
* Detects if a parsed document is Swagger 2.0 and converts it to OpenAPI 3.0.
|
|
5
34
|
*/
|
|
@@ -297,13 +326,18 @@ function convertSchemaRef(schema) {
|
|
|
297
326
|
return result;
|
|
298
327
|
}
|
|
299
328
|
export async function loadOpenApiSpec(input) {
|
|
329
|
+
// SSRF guard: vet a remote top-level spec URL before any fetch. Remote `$ref`s
|
|
330
|
+
// are vetted per-URL by the guarded http resolver in guardedParserOptions.
|
|
331
|
+
if (isHttpUrl(input)) {
|
|
332
|
+
await assertPublicUrl(input);
|
|
333
|
+
}
|
|
300
334
|
// First, parse without dereferencing to check for Swagger 2.0
|
|
301
|
-
const raw = (await SwaggerParser.parse(input));
|
|
335
|
+
const raw = (await SwaggerParser.parse(input, guardedParserOptions));
|
|
302
336
|
let specToParse = input;
|
|
303
337
|
if (isSwagger2(raw)) {
|
|
304
338
|
specToParse = convertSwagger2ToOpenApi3(raw);
|
|
305
339
|
}
|
|
306
|
-
const api = (await SwaggerParser.dereference(specToParse));
|
|
340
|
+
const api = (await SwaggerParser.dereference(specToParse, guardedParserOptions));
|
|
307
341
|
return { api, specPath: input };
|
|
308
342
|
}
|
|
309
343
|
//# sourceMappingURL=openapi-loader.js.map
|