llm-session-proxy 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 +201 -0
- package/README.en.md +466 -0
- package/README.md +471 -0
- package/bin/llm-session-proxy.js +7 -0
- package/examples/generic-openai.json +76 -0
- package/examples/opencode-go-models.json +116 -0
- package/examples/opencode-go.json +69 -0
- package/package.json +60 -0
- package/src/cli.js +425 -0
- package/src/config.js +310 -0
- package/src/index.js +107 -0
- package/src/inject.js +141 -0
- package/src/logger.js +128 -0
- package/src/proxy.js +386 -0
- package/src/session.js +269 -0
- package/src/template.js +91 -0
package/src/template.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
const TEMPLATE_RE = /\{\{\s*([^{}]+?)\s*\}\}/g;
|
|
4
|
+
|
|
5
|
+
function randomHex(length = 26) {
|
|
6
|
+
let out = '';
|
|
7
|
+
while (out.length < length) out += crypto.randomBytes(32).toString('hex');
|
|
8
|
+
return out.slice(0, length);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function randomBase36(length = 26) {
|
|
12
|
+
const alphabet = '0123456789abcdefghijklmnopqrstuvwxyz';
|
|
13
|
+
const bytes = crypto.randomBytes(length);
|
|
14
|
+
let out = '';
|
|
15
|
+
for (let i = 0; i < length; i += 1) out += alphabet[bytes[i] % 36];
|
|
16
|
+
return out;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** 内置模板函数。`{{name}}` 或带参 `{{name:arg}}`。 */
|
|
20
|
+
export function builtinFunctions() {
|
|
21
|
+
return {
|
|
22
|
+
uuid: () => crypto.randomUUID(),
|
|
23
|
+
random: (arg) => randomHex(Number(arg) > 0 ? Number(arg) : 26),
|
|
24
|
+
randomHex: (arg) => randomHex(Number(arg) > 0 ? Number(arg) : 16),
|
|
25
|
+
randomBase36: (arg) => randomBase36(Number(arg) > 0 ? Number(arg) : 16),
|
|
26
|
+
timestamp: () => Math.floor(Date.now() / 1000),
|
|
27
|
+
timestampMs: () => Date.now(),
|
|
28
|
+
now: () => new Date().toISOString(),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 创建模板上下文。所有字段都是纯对象,取值走点路径,行为可预测。
|
|
34
|
+
*
|
|
35
|
+
* 可用变量:
|
|
36
|
+
* {{session.id}} {{session.count}} {{session.requestId}} {{session.key}} {{session.source}}
|
|
37
|
+
* {{model}} {{path}} {{method}} {{uuid}} {{random}} {{randomHex:16}} {{timestamp}}
|
|
38
|
+
* {{env.HOME}} {{header.authorization}} {{query.foo}}
|
|
39
|
+
*/
|
|
40
|
+
export function createContext(extra = {}) {
|
|
41
|
+
return {
|
|
42
|
+
...extra,
|
|
43
|
+
env: extra.env || process.env,
|
|
44
|
+
functions: { ...builtinFunctions(), ...(extra.functions || {}) },
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function lookup(expr, ctx) {
|
|
49
|
+
let cursor = ctx;
|
|
50
|
+
for (const segment of expr.split('.')) {
|
|
51
|
+
if (cursor === null || cursor === undefined || typeof cursor !== 'object') return '';
|
|
52
|
+
cursor = cursor[segment];
|
|
53
|
+
}
|
|
54
|
+
if (cursor === undefined || cursor === null) return '';
|
|
55
|
+
return typeof cursor === 'object' ? JSON.stringify(cursor) : String(cursor);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** 渲染 `{{...}}` 模板。非字符串或没有占位符时原样返回。 */
|
|
59
|
+
export function renderTemplate(input, ctx = {}) {
|
|
60
|
+
if (typeof input !== 'string' || !input.includes('{{')) return input;
|
|
61
|
+
return input.replace(TEMPLATE_RE, (_match, rawExpr) => {
|
|
62
|
+
const expr = rawExpr.trim();
|
|
63
|
+
const colon = expr.indexOf(':');
|
|
64
|
+
const name = colon === -1 ? expr : expr.slice(0, colon);
|
|
65
|
+
const arg = colon === -1 ? undefined : expr.slice(colon + 1).trim();
|
|
66
|
+
const fn = ctx.functions ? ctx.functions[name] : undefined;
|
|
67
|
+
if (typeof fn === 'function') {
|
|
68
|
+
try {
|
|
69
|
+
const value = fn(arg);
|
|
70
|
+
return value === undefined || value === null ? '' : String(value);
|
|
71
|
+
} catch {
|
|
72
|
+
return '';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return lookup(expr, ctx);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** 对对象/数组里的所有字符串值递归渲染模板。 */
|
|
80
|
+
export function renderDeep(value, ctx) {
|
|
81
|
+
if (typeof value === 'string') return renderTemplate(value, ctx);
|
|
82
|
+
if (Array.isArray(value)) return value.map((item) => renderDeep(item, ctx));
|
|
83
|
+
if (value && typeof value === 'object') {
|
|
84
|
+
const out = {};
|
|
85
|
+
for (const [k, v] of Object.entries(value)) out[k] = renderDeep(v, ctx);
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export { randomHex, randomBase36 };
|