@volter/twin-ai-gateway 0.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/README.md +176 -0
- package/dist/src/ai-gateway-capabilities.d.ts +4 -0
- package/dist/src/ai-gateway-capabilities.js +772 -0
- package/dist/src/ai-gateway-conformance.d.ts +11 -0
- package/dist/src/ai-gateway-conformance.js +72 -0
- package/dist/src/ai-gateway-connector.d.ts +50 -0
- package/dist/src/ai-gateway-connector.js +97 -0
- package/dist/src/ai-gateway-models.d.ts +27 -0
- package/dist/src/ai-gateway-models.js +65 -0
- package/dist/src/ai-gateway-perform-harness.d.ts +5 -0
- package/dist/src/ai-gateway-perform-harness.js +17 -0
- package/dist/src/ai-gateway-scenario.d.ts +36 -0
- package/dist/src/ai-gateway-scenario.js +125 -0
- package/dist/src/ai-gateway-server.d.ts +16 -0
- package/dist/src/ai-gateway-server.js +107 -0
- package/dist/src/ai-gateway-stub.d.ts +20 -0
- package/dist/src/ai-gateway-stub.js +124 -0
- package/dist/src/ai-gateway-twin.d.ts +2 -0
- package/dist/src/ai-gateway-twin.js +994 -0
- package/dist/src/ai-gateway-types.d.ts +94 -0
- package/dist/src/ai-gateway-types.js +1 -0
- package/dist/src/ai-gateway-v3.d.ts +5 -0
- package/dist/src/ai-gateway-v3.js +367 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +24 -0
- package/dist/src/index.d.ts +12 -0
- package/dist/src/index.js +54 -0
- package/package.json +66 -0
- package/src/ai-gateway-capabilities.ts +894 -0
- package/src/ai-gateway-conformance.ts +77 -0
- package/src/ai-gateway-connector.ts +100 -0
- package/src/ai-gateway-models.ts +105 -0
- package/src/ai-gateway-perform-harness.ts +17 -0
- package/src/ai-gateway-scenario.ts +137 -0
- package/src/ai-gateway-server.ts +122 -0
- package/src/ai-gateway-stub.ts +115 -0
- package/src/ai-gateway-twin.ts +1068 -0
- package/src/ai-gateway-types.ts +96 -0
- package/src/ai-gateway-v3.ts +372 -0
- package/src/cli.ts +23 -0
- package/src/index.ts +67 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { ChatMessage } from './ai-gateway-types.ts';
|
|
2
|
+
|
|
3
|
+
export function contentToText(content: unknown): string {
|
|
4
|
+
if (typeof content === 'string') return content;
|
|
5
|
+
if (Array.isArray(content)) {
|
|
6
|
+
return content.map((part) => {
|
|
7
|
+
if (typeof part === 'string') return part;
|
|
8
|
+
if (part && typeof part === 'object' && 'text' in part) return String((part as { text?: unknown }).text ?? '');
|
|
9
|
+
return '';
|
|
10
|
+
}).filter(Boolean).join(' ');
|
|
11
|
+
}
|
|
12
|
+
return '';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function lastUserText(messages: ChatMessage[]): string {
|
|
16
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
17
|
+
if (messages[i]?.role === 'user') return contentToText(messages[i]?.content);
|
|
18
|
+
}
|
|
19
|
+
return '';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function estimateTokens(input: string): number {
|
|
23
|
+
const trimmed = input.trim();
|
|
24
|
+
if (!trimmed) return 0;
|
|
25
|
+
return Math.max(1, Math.ceil(trimmed.length / 4));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function countPromptTokens(messages: ChatMessage[]): number {
|
|
29
|
+
return messages.reduce((sum, message) => sum + estimateTokens(`${message.role}: ${contentToText(message.content)}`), 0);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function stubAssistantText(messages: ChatMessage[], model: string): string {
|
|
33
|
+
const text = lastUserText(messages) || 'empty prompt';
|
|
34
|
+
return `[twin-stub:ai-gateway:${model}] Echoing the last user message for deterministic simulation: ${text}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function stableHash(value: unknown): string {
|
|
38
|
+
const s = JSON.stringify(value);
|
|
39
|
+
let h = 2166136261;
|
|
40
|
+
for (let i = 0; i < s.length; i++) {
|
|
41
|
+
h ^= s.charCodeAt(i);
|
|
42
|
+
h = Math.imul(h, 16777619);
|
|
43
|
+
}
|
|
44
|
+
return (h >>> 0).toString(16).padStart(8, '0');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function schemaType(schema: Record<string, unknown>): string | undefined {
|
|
48
|
+
const type = schema.type;
|
|
49
|
+
if (typeof type === 'string') return type;
|
|
50
|
+
if (Array.isArray(type)) return type.find((item) => item !== 'null' && typeof item === 'string') as string | undefined;
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function schemaString(key: string): string {
|
|
55
|
+
const k = key.toLowerCase();
|
|
56
|
+
if (k.includes('timestamp') || k.includes('date')) return '2026-06-16T00:00:00.000Z';
|
|
57
|
+
if (k.includes('url')) return 'http://127.0.0.1/twin';
|
|
58
|
+
if (k.includes('summary')) return 'Twin structured-output stub summary.';
|
|
59
|
+
if (k.includes('description')) return 'Deterministic twin structured-output stub description.';
|
|
60
|
+
return 'twin stub';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Synthesize a deterministic value conforming to a JSON schema — used for json_schema
|
|
64
|
+
* structured outputs AND for tool-call arguments, so an unmodified SDK's own schema
|
|
65
|
+
* validation of the stubbed output passes. */
|
|
66
|
+
export function synthesizeJsonSchemaValue(schema: unknown, key = 'value'): unknown {
|
|
67
|
+
if (!schema || typeof schema !== 'object' || Array.isArray(schema)) return {};
|
|
68
|
+
const s = schema as Record<string, unknown>;
|
|
69
|
+
if (Array.isArray(s.enum) && s.enum.length > 0) return s.enum[0];
|
|
70
|
+
if (Array.isArray(s.const)) return s.const[0];
|
|
71
|
+
if (s.const !== undefined) return s.const;
|
|
72
|
+
if (Array.isArray(s.anyOf) && s.anyOf.length > 0) return synthesizeJsonSchemaValue(s.anyOf[0], key);
|
|
73
|
+
if (Array.isArray(s.oneOf) && s.oneOf.length > 0) return synthesizeJsonSchemaValue(s.oneOf[0], key);
|
|
74
|
+
|
|
75
|
+
const type = schemaType(s);
|
|
76
|
+
if (type === 'object' || (!type && s.properties && typeof s.properties === 'object')) {
|
|
77
|
+
const out: Record<string, unknown> = {};
|
|
78
|
+
const props = (s.properties && typeof s.properties === 'object' && !Array.isArray(s.properties))
|
|
79
|
+
? s.properties as Record<string, unknown>
|
|
80
|
+
: {};
|
|
81
|
+
for (const [propKey, propSchema] of Object.entries(props)) {
|
|
82
|
+
out[propKey] = synthesizeJsonSchemaValue(propSchema, propKey);
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
if (type === 'array') return [];
|
|
87
|
+
if (type === 'string') return schemaString(key);
|
|
88
|
+
if (type === 'number' || type === 'integer') return 0;
|
|
89
|
+
if (type === 'boolean') return true;
|
|
90
|
+
if (type === 'null') return null;
|
|
91
|
+
return {};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
type NormalizedTool = { name: string; parameters: unknown };
|
|
95
|
+
|
|
96
|
+
export function normalizeTool(tool: unknown): NormalizedTool | null {
|
|
97
|
+
if (!tool || typeof tool !== 'object') return null;
|
|
98
|
+
const t = tool as Record<string, unknown>;
|
|
99
|
+
const fn = t.function && typeof t.function === 'object' ? t.function as Record<string, unknown> : t;
|
|
100
|
+
if (typeof fn.name !== 'string' || !fn.name) return null;
|
|
101
|
+
return { name: fn.name, parameters: fn.parameters };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Build the deterministic tool_call the stub emits for a chosen tool: id from the tool name,
|
|
105
|
+
* arguments synthesized to CONFORM to the tool's own parameters schema. */
|
|
106
|
+
export function buildToolCall(tool: NormalizedTool): Record<string, unknown> {
|
|
107
|
+
return {
|
|
108
|
+
id: `call_twin_${stableHash(tool.name).slice(0, 8)}`,
|
|
109
|
+
type: 'function',
|
|
110
|
+
function: {
|
|
111
|
+
name: tool.name,
|
|
112
|
+
arguments: JSON.stringify(synthesizeJsonSchemaValue(tool.parameters)),
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|