babelgate 0.0.0 → 1.1.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 +202 -0
- package/NOTICE +2 -0
- package/README.md +467 -1
- package/dist/app.js +40 -0
- package/dist/cli.js +203 -0
- package/dist/config/env-config.js +99 -0
- package/dist/index.js +60 -0
- package/dist/middleware/error-handler.js +158 -0
- package/dist/middleware/request-logger.js +66 -0
- package/dist/middleware/require-api-key.js +80 -0
- package/dist/middleware/resolve-source.js +28 -0
- package/dist/routes/proxy.js +74 -0
- package/dist/services/http-client.js +220 -0
- package/dist/services/proxy.service.js +329 -0
- package/dist/services/stream-pipeline.js +119 -0
- package/dist/services/upstream-errors.js +34 -0
- package/dist/transformers/adapters/anthropic.adapter.js +292 -0
- package/dist/transformers/adapters/anthropic.messages.js +261 -0
- package/dist/transformers/adapters/anthropic.stream.js +378 -0
- package/dist/transformers/adapters/gemini.adapter.js +418 -0
- package/dist/transformers/adapters/gemini.schema.js +61 -0
- package/dist/transformers/adapters/gemini.stream.js +341 -0
- package/dist/transformers/adapters/gemini.tools.js +246 -0
- package/dist/transformers/adapters/index.js +141 -0
- package/dist/transformers/adapters/ollama.adapter.js +288 -0
- package/dist/transformers/adapters/ollama.stream.js +294 -0
- package/dist/transformers/adapters/ollama.tools.js +107 -0
- package/dist/transformers/adapters/openai.adapter.js +460 -0
- package/dist/transformers/adapters/openai.stream.js +229 -0
- package/dist/transformers/index.js +110 -0
- package/dist/types/anthropic.types.js +2 -0
- package/dist/types/canonical.schema.js +160 -0
- package/dist/types/canonical.types.js +8 -0
- package/dist/types/gemini.types.js +2 -0
- package/dist/types/ollama.types.js +4 -0
- package/dist/types/openai.types.js +2 -0
- package/dist/utils/canonical-error.js +77 -0
- package/dist/utils/errors.js +95 -0
- package/dist/utils/ids.js +17 -0
- package/dist/utils/logger.js +21 -0
- package/dist/utils/model-map.js +39 -0
- package/dist/utils/sse.js +147 -0
- package/dist/utils/stream-reassemble.js +104 -0
- package/dist/utils/token-estimate.js +36 -0
- package/dist/utils/tool-args.js +26 -0
- package/package.json +54 -7
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.PROVIDER_ROUTES = exports.PROVIDER_PATHS = exports.adapters = void 0;
|
|
18
|
+
exports.transformRequest = transformRequest;
|
|
19
|
+
exports.transformResponse = transformResponse;
|
|
20
|
+
exports.getTransformers = getTransformers;
|
|
21
|
+
exports.toCanonical = toCanonical;
|
|
22
|
+
exports.fromCanonical = fromCanonical;
|
|
23
|
+
const errors_1 = require("../utils/errors");
|
|
24
|
+
const adapters_1 = require("./adapters");
|
|
25
|
+
// Re-export adapters
|
|
26
|
+
var adapters_2 = require("./adapters");
|
|
27
|
+
Object.defineProperty(exports, "adapters", { enumerable: true, get: function () { return adapters_2.adapters; } });
|
|
28
|
+
Object.defineProperty(exports, "PROVIDER_PATHS", { enumerable: true, get: function () { return adapters_2.PROVIDER_PATHS; } });
|
|
29
|
+
Object.defineProperty(exports, "PROVIDER_ROUTES", { enumerable: true, get: function () { return adapters_2.PROVIDER_ROUTES; } });
|
|
30
|
+
__exportStar(require("../types/canonical.types"), exports);
|
|
31
|
+
/**
|
|
32
|
+
* Transform a request from source provider format to target provider format.
|
|
33
|
+
* Uses canonical format as intermediate representation.
|
|
34
|
+
*
|
|
35
|
+
* Flow: Source Request → Canonical → Target Request
|
|
36
|
+
*/
|
|
37
|
+
function transformRequest(sourceProvider, targetProvider, request) {
|
|
38
|
+
const sourceAdapter = adapters_1.adapters[sourceProvider];
|
|
39
|
+
const targetAdapter = adapters_1.adapters[targetProvider];
|
|
40
|
+
if (!sourceAdapter) {
|
|
41
|
+
throw new errors_1.ConfigurationError(`Unknown source provider: ${sourceProvider}`, {
|
|
42
|
+
availableProviders: Object.keys(adapters_1.adapters),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
if (!targetAdapter) {
|
|
46
|
+
throw new errors_1.ConfigurationError(`Unknown target provider: ${targetProvider}`, {
|
|
47
|
+
availableProviders: Object.keys(adapters_1.adapters),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
// Source → Canonical → Target
|
|
51
|
+
const canonical = sourceAdapter.toCanonicalRequest(request);
|
|
52
|
+
return targetAdapter.fromCanonicalRequest(canonical);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Transform a response from target provider format back to source provider format.
|
|
56
|
+
* Uses canonical format as intermediate representation.
|
|
57
|
+
*
|
|
58
|
+
* Flow: Target Response → Canonical → Source Response
|
|
59
|
+
*/
|
|
60
|
+
function transformResponse(sourceProvider, targetProvider, response, model) {
|
|
61
|
+
const sourceAdapter = adapters_1.adapters[sourceProvider];
|
|
62
|
+
const targetAdapter = adapters_1.adapters[targetProvider];
|
|
63
|
+
if (!sourceAdapter) {
|
|
64
|
+
throw new errors_1.ConfigurationError(`Unknown source provider: ${sourceProvider}`, {
|
|
65
|
+
availableProviders: Object.keys(adapters_1.adapters),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
if (!targetAdapter) {
|
|
69
|
+
throw new errors_1.ConfigurationError(`Unknown target provider: ${targetProvider}`, {
|
|
70
|
+
availableProviders: Object.keys(adapters_1.adapters),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
// Target → Canonical → Source
|
|
74
|
+
const canonical = targetAdapter.toCanonicalResponse(response, model);
|
|
75
|
+
return sourceAdapter.fromCanonicalResponse(canonical);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Get transformer functions for a source-target pair.
|
|
79
|
+
* Returns functions that handle the full transformation pipeline.
|
|
80
|
+
*/
|
|
81
|
+
function getTransformers(sourceProvider, targetProvider) {
|
|
82
|
+
return {
|
|
83
|
+
transformRequest: (request) => transformRequest(sourceProvider, targetProvider, request),
|
|
84
|
+
transformResponse: (response, model) => transformResponse(sourceProvider, targetProvider, response, model),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Convert any provider request to canonical format.
|
|
89
|
+
*/
|
|
90
|
+
function toCanonical(provider, request) {
|
|
91
|
+
const adapter = adapters_1.adapters[provider];
|
|
92
|
+
if (!adapter) {
|
|
93
|
+
throw new errors_1.ConfigurationError(`Unknown provider: ${provider}`, {
|
|
94
|
+
availableProviders: Object.keys(adapters_1.adapters),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return adapter.toCanonicalRequest(request);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Convert canonical request to any provider format.
|
|
101
|
+
*/
|
|
102
|
+
function fromCanonical(provider, request) {
|
|
103
|
+
const adapter = adapters_1.adapters[provider];
|
|
104
|
+
if (!adapter) {
|
|
105
|
+
throw new errors_1.ConfigurationError(`Unknown provider: ${provider}`, {
|
|
106
|
+
availableProviders: Object.keys(adapters_1.adapters),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return adapter.fromCanonicalRequest(request);
|
|
110
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.canonicalStreamEventSchema = exports.canonicalModelListSchema = exports.canonicalModelSchema = exports.canonicalErrorSchema = exports.canonicalErrorKindSchema = exports.canonicalResponseSchema = exports.canonicalUsageSchema = exports.finishReasonSchema = exports.canonicalRequestSchema = exports.canonicalToolChoiceSchema = exports.canonicalToolSchema = exports.canonicalConfigSchema = exports.canonicalMessageSchema = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
/**
|
|
6
|
+
* Zod schemas mirroring canonical.types.ts.
|
|
7
|
+
* Used for runtime contract validation of adapter outputs.
|
|
8
|
+
*/
|
|
9
|
+
const roleSchema = zod_1.z.enum(["system", "user", "assistant", "tool"]);
|
|
10
|
+
const mimeTypeSchema = zod_1.z.enum([
|
|
11
|
+
"image/jpeg",
|
|
12
|
+
"image/png",
|
|
13
|
+
"image/gif",
|
|
14
|
+
"image/webp",
|
|
15
|
+
]);
|
|
16
|
+
const imageSchema = zod_1.z.object({
|
|
17
|
+
data: zod_1.z.string(),
|
|
18
|
+
mimeType: mimeTypeSchema,
|
|
19
|
+
});
|
|
20
|
+
const contentPartSchema = zod_1.z
|
|
21
|
+
.object({
|
|
22
|
+
type: zod_1.z.enum(["text", "image"]),
|
|
23
|
+
text: zod_1.z.string().optional(),
|
|
24
|
+
image: imageSchema.optional(),
|
|
25
|
+
})
|
|
26
|
+
.refine((p) => (p.type === "text" ? p.text !== undefined : true), {
|
|
27
|
+
message: "content part of type 'text' must have text",
|
|
28
|
+
});
|
|
29
|
+
const toolCallSchema = zod_1.z.object({
|
|
30
|
+
id: zod_1.z.string(),
|
|
31
|
+
type: zod_1.z.literal("function"),
|
|
32
|
+
function: zod_1.z.object({
|
|
33
|
+
name: zod_1.z.string(),
|
|
34
|
+
arguments: zod_1.z.string(),
|
|
35
|
+
}),
|
|
36
|
+
});
|
|
37
|
+
exports.canonicalMessageSchema = zod_1.z.object({
|
|
38
|
+
role: roleSchema,
|
|
39
|
+
content: zod_1.z.union([zod_1.z.string(), zod_1.z.array(contentPartSchema)]),
|
|
40
|
+
toolCalls: zod_1.z.array(toolCallSchema).optional(),
|
|
41
|
+
toolCallId: zod_1.z.string().optional(),
|
|
42
|
+
isError: zod_1.z.boolean().optional(),
|
|
43
|
+
name: zod_1.z.string().optional(),
|
|
44
|
+
});
|
|
45
|
+
exports.canonicalConfigSchema = zod_1.z.object({
|
|
46
|
+
temperature: zod_1.z.number().optional(),
|
|
47
|
+
maxTokens: zod_1.z.number().int().positive().optional(),
|
|
48
|
+
topP: zod_1.z.number().optional(),
|
|
49
|
+
topK: zod_1.z.number().int().positive().optional(),
|
|
50
|
+
stopSequences: zod_1.z.array(zod_1.z.string()).optional(),
|
|
51
|
+
candidateCount: zod_1.z.number().int().positive().optional(),
|
|
52
|
+
seed: zod_1.z.number().optional(),
|
|
53
|
+
frequencyPenalty: zod_1.z.number().optional(),
|
|
54
|
+
presencePenalty: zod_1.z.number().optional(),
|
|
55
|
+
contextWindow: zod_1.z.number().int().positive().optional(),
|
|
56
|
+
});
|
|
57
|
+
const functionSchema = zod_1.z.object({
|
|
58
|
+
name: zod_1.z.string(),
|
|
59
|
+
description: zod_1.z.string().optional(),
|
|
60
|
+
parameters: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
|
|
61
|
+
});
|
|
62
|
+
exports.canonicalToolSchema = zod_1.z.object({
|
|
63
|
+
type: zod_1.z.literal("function"),
|
|
64
|
+
function: functionSchema,
|
|
65
|
+
});
|
|
66
|
+
exports.canonicalToolChoiceSchema = zod_1.z.union([
|
|
67
|
+
zod_1.z.enum(["auto", "none", "required"]),
|
|
68
|
+
zod_1.z.object({
|
|
69
|
+
type: zod_1.z.literal("function"),
|
|
70
|
+
function: zod_1.z.object({ name: zod_1.z.string() }),
|
|
71
|
+
}),
|
|
72
|
+
]);
|
|
73
|
+
exports.canonicalRequestSchema = zod_1.z.object({
|
|
74
|
+
model: zod_1.z.string().min(1),
|
|
75
|
+
messages: zod_1.z.array(exports.canonicalMessageSchema).min(1),
|
|
76
|
+
systemPrompt: zod_1.z.string().optional(),
|
|
77
|
+
config: exports.canonicalConfigSchema.optional(),
|
|
78
|
+
tools: zod_1.z.array(exports.canonicalToolSchema).optional(),
|
|
79
|
+
toolChoice: exports.canonicalToolChoiceSchema.optional(),
|
|
80
|
+
stream: zod_1.z.boolean().optional(),
|
|
81
|
+
});
|
|
82
|
+
exports.finishReasonSchema = zod_1.z.enum([
|
|
83
|
+
"stop",
|
|
84
|
+
"length",
|
|
85
|
+
"tool_call",
|
|
86
|
+
"content_filter",
|
|
87
|
+
"error",
|
|
88
|
+
"unknown",
|
|
89
|
+
]);
|
|
90
|
+
exports.canonicalUsageSchema = zod_1.z.object({
|
|
91
|
+
inputTokens: zod_1.z.number().int().nonnegative(),
|
|
92
|
+
outputTokens: zod_1.z.number().int().nonnegative(),
|
|
93
|
+
totalTokens: zod_1.z.number().int().nonnegative(),
|
|
94
|
+
});
|
|
95
|
+
exports.canonicalResponseSchema = zod_1.z.object({
|
|
96
|
+
id: zod_1.z.string().min(1),
|
|
97
|
+
model: zod_1.z.string().min(1),
|
|
98
|
+
message: exports.canonicalMessageSchema,
|
|
99
|
+
finishReason: exports.finishReasonSchema,
|
|
100
|
+
usage: exports.canonicalUsageSchema.optional(),
|
|
101
|
+
created: zod_1.z.number().optional(),
|
|
102
|
+
});
|
|
103
|
+
// ============ ERRORS ============
|
|
104
|
+
exports.canonicalErrorKindSchema = zod_1.z.enum([
|
|
105
|
+
"invalid_request",
|
|
106
|
+
"authentication",
|
|
107
|
+
"permission",
|
|
108
|
+
"not_found",
|
|
109
|
+
"request_too_large",
|
|
110
|
+
"rate_limit",
|
|
111
|
+
"unsupported",
|
|
112
|
+
"upstream",
|
|
113
|
+
"unavailable",
|
|
114
|
+
"timeout",
|
|
115
|
+
"internal",
|
|
116
|
+
]);
|
|
117
|
+
exports.canonicalErrorSchema = zod_1.z.object({
|
|
118
|
+
kind: exports.canonicalErrorKindSchema,
|
|
119
|
+
status: zod_1.z.number().int(),
|
|
120
|
+
message: zod_1.z.string(),
|
|
121
|
+
retryAfterSeconds: zod_1.z.number().optional(),
|
|
122
|
+
});
|
|
123
|
+
// ============ MODEL LIST ============
|
|
124
|
+
exports.canonicalModelSchema = zod_1.z.object({
|
|
125
|
+
id: zod_1.z.string().min(1),
|
|
126
|
+
displayName: zod_1.z.string().optional(),
|
|
127
|
+
created: zod_1.z.number().optional(),
|
|
128
|
+
ownedBy: zod_1.z.string().optional(),
|
|
129
|
+
});
|
|
130
|
+
exports.canonicalModelListSchema = zod_1.z.object({
|
|
131
|
+
models: zod_1.z.array(exports.canonicalModelSchema),
|
|
132
|
+
});
|
|
133
|
+
// ============ STREAMING ============
|
|
134
|
+
const streamIndexSchema = zod_1.z.number().int().nonnegative();
|
|
135
|
+
exports.canonicalStreamEventSchema = zod_1.z.discriminatedUnion("type", [
|
|
136
|
+
zod_1.z.object({
|
|
137
|
+
type: zod_1.z.literal("message_start"),
|
|
138
|
+
id: zod_1.z.string(),
|
|
139
|
+
model: zod_1.z.string(),
|
|
140
|
+
}),
|
|
141
|
+
zod_1.z.object({ type: zod_1.z.literal("text_delta"), text: zod_1.z.string() }),
|
|
142
|
+
zod_1.z.object({
|
|
143
|
+
type: zod_1.z.literal("tool_call_start"),
|
|
144
|
+
index: streamIndexSchema,
|
|
145
|
+
id: zod_1.z.string(),
|
|
146
|
+
name: zod_1.z.string(),
|
|
147
|
+
}),
|
|
148
|
+
zod_1.z.object({
|
|
149
|
+
type: zod_1.z.literal("tool_call_delta"),
|
|
150
|
+
index: streamIndexSchema,
|
|
151
|
+
argumentsDelta: zod_1.z.string(),
|
|
152
|
+
}),
|
|
153
|
+
zod_1.z.object({ type: zod_1.z.literal("tool_call_end"), index: streamIndexSchema }),
|
|
154
|
+
zod_1.z.object({ type: zod_1.z.literal("usage"), usage: exports.canonicalUsageSchema }),
|
|
155
|
+
zod_1.z.object({
|
|
156
|
+
type: zod_1.z.literal("message_end"),
|
|
157
|
+
finishReason: exports.finishReasonSchema,
|
|
158
|
+
}),
|
|
159
|
+
zod_1.z.object({ type: zod_1.z.literal("error"), error: exports.canonicalErrorSchema }),
|
|
160
|
+
]);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CANONICAL_ERROR_STATUS = void 0;
|
|
4
|
+
exports.kindFromStatus = kindFromStatus;
|
|
5
|
+
exports.makeCanonicalError = makeCanonicalError;
|
|
6
|
+
exports.parseRetryAfter = parseRetryAfter;
|
|
7
|
+
/** HTTP status the client sees for each canonical kind (spec §3.3). */
|
|
8
|
+
exports.CANONICAL_ERROR_STATUS = {
|
|
9
|
+
invalid_request: 400,
|
|
10
|
+
authentication: 401,
|
|
11
|
+
permission: 403,
|
|
12
|
+
not_found: 404,
|
|
13
|
+
request_too_large: 413,
|
|
14
|
+
rate_limit: 429,
|
|
15
|
+
internal: 500,
|
|
16
|
+
unsupported: 501,
|
|
17
|
+
upstream: 502,
|
|
18
|
+
unavailable: 503,
|
|
19
|
+
timeout: 504,
|
|
20
|
+
};
|
|
21
|
+
/** Map an HTTP status to the canonical kind (spec §3.3). */
|
|
22
|
+
function kindFromStatus(status) {
|
|
23
|
+
switch (status) {
|
|
24
|
+
case 400:
|
|
25
|
+
case 422:
|
|
26
|
+
return "invalid_request";
|
|
27
|
+
case 401:
|
|
28
|
+
return "authentication";
|
|
29
|
+
case 403:
|
|
30
|
+
return "permission";
|
|
31
|
+
case 404:
|
|
32
|
+
return "not_found";
|
|
33
|
+
case 413:
|
|
34
|
+
return "request_too_large";
|
|
35
|
+
case 429:
|
|
36
|
+
return "rate_limit";
|
|
37
|
+
case 500:
|
|
38
|
+
return "internal";
|
|
39
|
+
case 501:
|
|
40
|
+
return "unsupported";
|
|
41
|
+
case 503:
|
|
42
|
+
case 529:
|
|
43
|
+
return "unavailable";
|
|
44
|
+
case 504:
|
|
45
|
+
return "timeout";
|
|
46
|
+
default:
|
|
47
|
+
if (status >= 500)
|
|
48
|
+
return "upstream";
|
|
49
|
+
if (status >= 400)
|
|
50
|
+
return "invalid_request";
|
|
51
|
+
return "internal";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function makeCanonicalError(kind, message, retryAfterSeconds) {
|
|
55
|
+
return {
|
|
56
|
+
kind,
|
|
57
|
+
status: exports.CANONICAL_ERROR_STATUS[kind],
|
|
58
|
+
message,
|
|
59
|
+
...(retryAfterSeconds !== undefined && { retryAfterSeconds }),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Parse an upstream `Retry-After` header into whole seconds.
|
|
64
|
+
* delta-seconds pass through; an HTTP-date becomes seconds from `now`
|
|
65
|
+
* (minimum 1); anything unrecognised is dropped.
|
|
66
|
+
*/
|
|
67
|
+
function parseRetryAfter(header, now = new Date()) {
|
|
68
|
+
if (!header)
|
|
69
|
+
return undefined;
|
|
70
|
+
const trimmed = header.trim();
|
|
71
|
+
if (/^\d+$/.test(trimmed))
|
|
72
|
+
return parseInt(trimmed, 10);
|
|
73
|
+
const date = Date.parse(trimmed);
|
|
74
|
+
if (Number.isNaN(date))
|
|
75
|
+
return undefined;
|
|
76
|
+
return Math.max(1, Math.ceil((date - now.getTime()) / 1000));
|
|
77
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConfigurationError = exports.AuthError = exports.NotSupportedError = exports.UpstreamError = exports.TransformError = exports.ValidationError = exports.ProxyError = void 0;
|
|
4
|
+
const canonical_error_1 = require("./canonical-error");
|
|
5
|
+
class ProxyError extends Error {
|
|
6
|
+
statusCode;
|
|
7
|
+
code;
|
|
8
|
+
context;
|
|
9
|
+
constructor(message, statusCode = 500, code = "PROXY_ERROR", context) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.statusCode = statusCode;
|
|
12
|
+
this.code = code;
|
|
13
|
+
this.context = context;
|
|
14
|
+
this.name = this.constructor.name;
|
|
15
|
+
Error.captureStackTrace(this, this.constructor);
|
|
16
|
+
}
|
|
17
|
+
/** Canonical form used to render this error in the client's dialect. */
|
|
18
|
+
toCanonical() {
|
|
19
|
+
return (0, canonical_error_1.makeCanonicalError)((0, canonical_error_1.kindFromStatus)(this.statusCode), this.message);
|
|
20
|
+
}
|
|
21
|
+
toJSON() {
|
|
22
|
+
return {
|
|
23
|
+
error: {
|
|
24
|
+
message: this.message,
|
|
25
|
+
code: this.code,
|
|
26
|
+
status: this.statusCode,
|
|
27
|
+
...(process.env.NODE_ENV !== "production" && this.context
|
|
28
|
+
? { details: this.context }
|
|
29
|
+
: {}),
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.ProxyError = ProxyError;
|
|
35
|
+
class ValidationError extends ProxyError {
|
|
36
|
+
constructor(message, context) {
|
|
37
|
+
super(message, 400, "VALIDATION_ERROR", context);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.ValidationError = ValidationError;
|
|
41
|
+
class TransformError extends ProxyError {
|
|
42
|
+
constructor(message, context) {
|
|
43
|
+
super(message, 500, "TRANSFORM_ERROR", context);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
exports.TransformError = TransformError;
|
|
47
|
+
class UpstreamError extends ProxyError {
|
|
48
|
+
upstreamStatus;
|
|
49
|
+
upstreamBody;
|
|
50
|
+
/**
|
|
51
|
+
* Client-facing rendering computed by proxy.service's upstream mapping
|
|
52
|
+
* (spec §3.3). When set it wins over the default status-derived kind.
|
|
53
|
+
*/
|
|
54
|
+
clientError;
|
|
55
|
+
/** Seconds from the upstream Retry-After header (set on 429/503). */
|
|
56
|
+
retryAfterSeconds;
|
|
57
|
+
constructor(message, upstreamStatus, upstreamBody, context, statusCode = 502) {
|
|
58
|
+
// upstreamBody stays a field (for the §3.3 log summary) but is kept
|
|
59
|
+
// out of `context`: context is serialized into non-production
|
|
60
|
+
// `details` and an upstream body may echo keys or completion text.
|
|
61
|
+
super(message, statusCode, "UPSTREAM_ERROR", {
|
|
62
|
+
...context,
|
|
63
|
+
upstreamStatus,
|
|
64
|
+
});
|
|
65
|
+
this.upstreamStatus = upstreamStatus;
|
|
66
|
+
this.upstreamBody = upstreamBody;
|
|
67
|
+
}
|
|
68
|
+
toCanonical() {
|
|
69
|
+
return this.clientError ?? super.toCanonical();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
exports.UpstreamError = UpstreamError;
|
|
73
|
+
/**
|
|
74
|
+
* 501 — the client asked for something the proxy refuses by design
|
|
75
|
+
* (streaming, unsupported provider methods). Canonical kind
|
|
76
|
+
* `unsupported` via the default toCanonical().
|
|
77
|
+
*/
|
|
78
|
+
class NotSupportedError extends ProxyError {
|
|
79
|
+
constructor(message, context) {
|
|
80
|
+
super(message, 501, "NOT_SUPPORTED", context);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
exports.NotSupportedError = NotSupportedError;
|
|
84
|
+
class AuthError extends ProxyError {
|
|
85
|
+
constructor(message, context) {
|
|
86
|
+
super(message, 401, "UNAUTHORIZED", context);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
exports.AuthError = AuthError;
|
|
90
|
+
class ConfigurationError extends ProxyError {
|
|
91
|
+
constructor(message, context) {
|
|
92
|
+
super(message, 500, "CONFIGURATION_ERROR", context);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
exports.ConfigurationError = ConfigurationError;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createIdGenerator = createIdGenerator;
|
|
4
|
+
exports.sequentialIds = sequentialIds;
|
|
5
|
+
const crypto_1 = require("crypto");
|
|
6
|
+
/**
|
|
7
|
+
* Random-suffix ID generator (spec §3.4: `call_<n>`-style generated
|
|
8
|
+
* tool-call IDs) — the default injected into decoders/adapters.
|
|
9
|
+
*/
|
|
10
|
+
function createIdGenerator(prefix = "call_") {
|
|
11
|
+
return () => `${prefix}${(0, crypto_1.randomBytes)(9).toString("base64url")}`;
|
|
12
|
+
}
|
|
13
|
+
/** Deterministic `call_0`, `call_1`, … — used in tests and fixtures. */
|
|
14
|
+
function sequentialIds(prefix = "call_") {
|
|
15
|
+
let next = 0;
|
|
16
|
+
return () => `${prefix}${next++}`;
|
|
17
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.logger = void 0;
|
|
7
|
+
const pino_1 = __importDefault(require("pino"));
|
|
8
|
+
const isProduction = process.env.NODE_ENV === "production";
|
|
9
|
+
exports.logger = (0, pino_1.default)({
|
|
10
|
+
level: process.env.LOG_LEVEL || (isProduction ? "info" : "debug"),
|
|
11
|
+
transport: isProduction
|
|
12
|
+
? undefined
|
|
13
|
+
: {
|
|
14
|
+
target: "pino-pretty",
|
|
15
|
+
options: {
|
|
16
|
+
colorize: true,
|
|
17
|
+
translateTime: "SYS:standard",
|
|
18
|
+
ignore: "pid,hostname",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseModelMap = parseModelMap;
|
|
4
|
+
exports.applyModelMap = applyModelMap;
|
|
5
|
+
const errors_1 = require("./errors");
|
|
6
|
+
/** `*` matches any run of characters; every other character is literal. */
|
|
7
|
+
function globToRegExp(pattern) {
|
|
8
|
+
const escaped = pattern
|
|
9
|
+
.replace(/[.+?^${}()|[\]\\]/g, "\\$&")
|
|
10
|
+
.replace(/\*/g, ".*");
|
|
11
|
+
return new RegExp(`^${escaped}$`);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Comma-separated `glob=model` entries → ordered rules. An entry that
|
|
15
|
+
* isn't `glob=model` (empty pattern or model, no `=`) is a configuration
|
|
16
|
+
* error — it fails at startup, not at request time.
|
|
17
|
+
*/
|
|
18
|
+
function parseModelMap(value) {
|
|
19
|
+
if (value === undefined || value.trim() === "")
|
|
20
|
+
return [];
|
|
21
|
+
return value.split(",").map((raw) => {
|
|
22
|
+
const entry = raw.trim();
|
|
23
|
+
const eq = entry.indexOf("=");
|
|
24
|
+
const pattern = eq === -1 ? "" : entry.slice(0, eq).trim();
|
|
25
|
+
const model = eq === -1 ? "" : entry.slice(eq + 1).trim();
|
|
26
|
+
if (pattern === "" || model === "") {
|
|
27
|
+
throw new errors_1.ConfigurationError(`MODEL_MAP entry "${entry}" must be glob=model`);
|
|
28
|
+
}
|
|
29
|
+
return { pattern, model };
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/** First matching rule wins; no match leaves the model unchanged. */
|
|
33
|
+
function applyModelMap(model, rules) {
|
|
34
|
+
for (const rule of rules) {
|
|
35
|
+
if (globToRegExp(rule.pattern).test(model))
|
|
36
|
+
return rule.model;
|
|
37
|
+
}
|
|
38
|
+
return model;
|
|
39
|
+
}
|