@stage-labs/metro 0.1.0-beta.313 → 0.1.0-beta.314
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/package.json
CHANGED
|
@@ -43,7 +43,7 @@ import { notReady, resolveRoute, routeLabel, type Connection, type ModelSetup, t
|
|
|
43
43
|
import { codexMessages, type CodexDeps } from './codex.js';
|
|
44
44
|
import { countTokens } from './subscription.js';
|
|
45
45
|
import { OPENROUTER_BASE } from './openrouter.js';
|
|
46
|
-
import { refusedWithPage, ROUTEWAY_BASE, routewayHeaders, routewayTools } from './routeway.js';
|
|
46
|
+
import { noteRoutewayRefusal, refusedWithPage, ROUTEWAY_BASE, routewayHeaders, routewayTools } from './routeway.js';
|
|
47
47
|
import { isRecord } from '@metro-labs/core/is-record';
|
|
48
48
|
import { forgetServed, noteServed } from './served.js';
|
|
49
49
|
import { forgetUsage, noteUsageHeaders, UsageScanner } from './usage.js';
|
|
@@ -193,6 +193,7 @@ async function toRouteway(req: IncomingMessage, res: ServerResponse, body: Shape
|
|
|
193
193
|
const fitted: Shaped = { raw: sent === body.sent ? body.raw : Buffer.from(JSON.stringify(sent)), sent, shaped: routewayTools(body.shaped), bind: false };
|
|
194
194
|
const upstream = await shapedAttempt(send, routewayHeaders(req, conn.apiKey), fitted, route);
|
|
195
195
|
noteRefusal(conn.label, route.model, upstream);
|
|
196
|
+
await noteRoutewayRefusal(upstream);
|
|
196
197
|
await refusedWithPage(upstream, watch);
|
|
197
198
|
await pipeResponse(upstream, res, watch, { keepalive: true, ownCredential: true, scanner: new UsageScanner(conn.id) });
|
|
198
199
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { IncomingMessage } from 'node:http';
|
|
2
2
|
import { isRecord } from '@metro-labs/core/is-record';
|
|
3
|
+
import { log } from '@metro-labs/core/log';
|
|
3
4
|
import { anthropicHeaders, GatewayError, type Watch } from './forward.js';
|
|
4
5
|
|
|
5
6
|
export const ROUTEWAY_BASE = 'https://api.routeway.ai';
|
|
@@ -19,9 +20,49 @@ export async function refusedWithPage(upstream: Response, watch: Watch): Promise
|
|
|
19
20
|
throw new GatewayError(502, 'api_error', `metro gateway: Routeway answered with a web page instead of its API (${String(upstream.status)}); try again in a moment`);
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
const SCHEMA_MAPS = ['properties', 'patternProperties', '$defs', 'definitions'];
|
|
24
|
+
const SCHEMA_LISTS = ['anyOf', 'oneOf', 'allOf', 'prefixItems'];
|
|
25
|
+
const SCHEMA_ONES = ['items', 'additionalProperties', 'not', 'contains'];
|
|
26
|
+
|
|
27
|
+
function fitMap(raw: Record<string, unknown>): Record<string, unknown> {
|
|
28
|
+
const entries = Object.entries(raw).map(([key, value]) => [key, fitSchema(value)] as const);
|
|
29
|
+
return entries.some(([key, value]) => value !== raw[key]) ? Object.fromEntries(entries) : raw;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function fitList(raw: unknown[]): unknown[] {
|
|
33
|
+
const fitted = raw.map(fitSchema);
|
|
34
|
+
return fitted.some((value, at) => value !== raw[at]) ? fitted : raw;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function fitChild(key: string, value: unknown): unknown {
|
|
38
|
+
if (SCHEMA_MAPS.includes(key)) return isRecord(value) ? fitMap(value) : value;
|
|
39
|
+
if (SCHEMA_LISTS.includes(key)) return Array.isArray(value) ? fitList(value) : value;
|
|
40
|
+
return SCHEMA_ONES.includes(key) ? fitSchema(value) : value;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function singleTypes(schema: Record<string, unknown>): Record<string, unknown> {
|
|
44
|
+
const types: unknown = schema.type;
|
|
45
|
+
if (!Array.isArray(types) || !types.every((type) => typeof type === 'string')) return schema;
|
|
46
|
+
const rest = Object.fromEntries(Object.entries(schema).filter(([key]) => key !== 'type'));
|
|
47
|
+
if (types.length === 1) return { ...rest, type: types[0] };
|
|
48
|
+
const choices = types.map((type) => ({ type }));
|
|
49
|
+
if (!Object.hasOwn(rest, 'anyOf')) return { ...rest, anyOf: choices };
|
|
50
|
+
const { anyOf, ...others } = rest;
|
|
51
|
+
return { ...others, allOf: [{ anyOf: choices }, { anyOf }] };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function fitSchema(raw: unknown): unknown {
|
|
55
|
+
if (!isRecord(raw)) return raw;
|
|
56
|
+
const entries = Object.entries(raw).map(([key, value]) => [key, fitChild(key, value)] as const);
|
|
57
|
+
const children = entries.some(([key, value]) => value !== raw[key]) ? Object.fromEntries(entries) : raw;
|
|
58
|
+
return singleTypes(children);
|
|
59
|
+
}
|
|
60
|
+
|
|
22
61
|
function withProperties(tool: unknown): unknown {
|
|
23
|
-
if (!isRecord(tool) || !isRecord(tool.input_schema)
|
|
24
|
-
|
|
62
|
+
if (!isRecord(tool) || !isRecord(tool.input_schema)) return tool;
|
|
63
|
+
const schema = fitSchema(tool.input_schema);
|
|
64
|
+
const full = isRecord(schema) && !Object.hasOwn(schema, 'properties') ? { ...schema, properties: {} } : schema;
|
|
65
|
+
return full === tool.input_schema ? tool : { ...tool, input_schema: full };
|
|
25
66
|
}
|
|
26
67
|
|
|
27
68
|
export function routewayTools(body: Record<string, unknown>): Record<string, unknown> {
|
|
@@ -30,3 +71,12 @@ export function routewayTools(body: Record<string, unknown>): Record<string, unk
|
|
|
30
71
|
const tools: unknown[] = listed.map(withProperties);
|
|
31
72
|
return tools.some((tool, at) => tool !== listed[at]) ? { ...body, tools } : body;
|
|
32
73
|
}
|
|
74
|
+
|
|
75
|
+
export async function noteRoutewayRefusal(upstream: Response): Promise<void> {
|
|
76
|
+
if (upstream.ok || !(upstream.headers.get('content-type') ?? '').includes('application/json')) return;
|
|
77
|
+
const body: unknown = await upstream.clone().json().catch(() => null);
|
|
78
|
+
const error = isRecord(body) && isRecord(body.error) ? body.error : null;
|
|
79
|
+
if (error === null) return;
|
|
80
|
+
const text = (value: unknown): string | null => (typeof value === 'string' ? value.slice(0, 300) : null);
|
|
81
|
+
log.warn({ status: upstream.status, reason: text(error.message), param: text(error.param) }, 'gateway: Routeway refused the request');
|
|
82
|
+
}
|
package/runtime/runtime.json
CHANGED