llmpt 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.
Files changed (83) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/LICENSE +21 -0
  3. package/README.md +130 -0
  4. package/dist/index.d.ts +3 -0
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.js +71 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/server/api/routes.d.ts +6 -0
  9. package/dist/server/api/routes.d.ts.map +1 -0
  10. package/dist/server/api/routes.js +128 -0
  11. package/dist/server/api/routes.js.map +1 -0
  12. package/dist/server/app.d.ts +14 -0
  13. package/dist/server/app.d.ts.map +1 -0
  14. package/dist/server/app.js +75 -0
  15. package/dist/server/app.js.map +1 -0
  16. package/dist/server/config/crypto.d.ts +14 -0
  17. package/dist/server/config/crypto.d.ts.map +1 -0
  18. package/dist/server/config/crypto.js +63 -0
  19. package/dist/server/config/crypto.js.map +1 -0
  20. package/dist/server/config/loader.d.ts +12 -0
  21. package/dist/server/config/loader.d.ts.map +1 -0
  22. package/dist/server/config/loader.js +142 -0
  23. package/dist/server/config/loader.js.map +1 -0
  24. package/dist/server/providers/anthropic.d.ts +9 -0
  25. package/dist/server/providers/anthropic.d.ts.map +1 -0
  26. package/dist/server/providers/anthropic.js +29 -0
  27. package/dist/server/providers/anthropic.js.map +1 -0
  28. package/dist/server/providers/base.d.ts +14 -0
  29. package/dist/server/providers/base.d.ts.map +1 -0
  30. package/dist/server/providers/base.js +15 -0
  31. package/dist/server/providers/base.js.map +1 -0
  32. package/dist/server/providers/ollama.d.ts +9 -0
  33. package/dist/server/providers/ollama.d.ts.map +1 -0
  34. package/dist/server/providers/ollama.js +24 -0
  35. package/dist/server/providers/ollama.js.map +1 -0
  36. package/dist/server/providers/openai.d.ts +9 -0
  37. package/dist/server/providers/openai.d.ts.map +1 -0
  38. package/dist/server/providers/openai.js +32 -0
  39. package/dist/server/providers/openai.js.map +1 -0
  40. package/dist/server/providers/registry.d.ts +23 -0
  41. package/dist/server/providers/registry.d.ts.map +1 -0
  42. package/dist/server/providers/registry.js +50 -0
  43. package/dist/server/providers/registry.js.map +1 -0
  44. package/dist/server/proxy/handler.d.ts +9 -0
  45. package/dist/server/proxy/handler.d.ts.map +1 -0
  46. package/dist/server/proxy/handler.js +101 -0
  47. package/dist/server/proxy/handler.js.map +1 -0
  48. package/dist/server/proxy/stream.d.ts +9 -0
  49. package/dist/server/proxy/stream.d.ts.map +1 -0
  50. package/dist/server/proxy/stream.js +200 -0
  51. package/dist/server/proxy/stream.js.map +1 -0
  52. package/dist/server/static.d.ts +6 -0
  53. package/dist/server/static.d.ts.map +1 -0
  54. package/dist/server/static.js +58 -0
  55. package/dist/server/static.js.map +1 -0
  56. package/dist/server/trace/collector.d.ts +59 -0
  57. package/dist/server/trace/collector.d.ts.map +1 -0
  58. package/dist/server/trace/collector.js +278 -0
  59. package/dist/server/trace/collector.js.map +1 -0
  60. package/dist/server/trace/sanitizer.d.ts +5 -0
  61. package/dist/server/trace/sanitizer.d.ts.map +1 -0
  62. package/dist/server/trace/sanitizer.js +45 -0
  63. package/dist/server/trace/sanitizer.js.map +1 -0
  64. package/dist/server/trace/writer.d.ts +26 -0
  65. package/dist/server/trace/writer.d.ts.map +1 -0
  66. package/dist/server/trace/writer.js +125 -0
  67. package/dist/server/trace/writer.js.map +1 -0
  68. package/dist/server/types.d.ts +167 -0
  69. package/dist/server/types.d.ts.map +1 -0
  70. package/dist/server/types.js +5 -0
  71. package/dist/server/types.js.map +1 -0
  72. package/dist/server/ws/server.d.ts +20 -0
  73. package/dist/server/ws/server.d.ts.map +1 -0
  74. package/dist/server/ws/server.js +78 -0
  75. package/dist/server/ws/server.js.map +1 -0
  76. package/dist/web/assets/index-BleB8LKa.js +21 -0
  77. package/dist/web/assets/index-DAsXJzMt.css +1 -0
  78. package/dist/web/assets/markdown-DsSlw-Z4.js +20 -0
  79. package/dist/web/assets/react-vendor-BnH7jZBV.js +21 -0
  80. package/dist/web/assets/rolldown-runtime-QTnfLwEv.js +1 -0
  81. package/dist/web/index.html +18 -0
  82. package/lpt.config.yaml +11 -0
  83. package/package.json +70 -0
@@ -0,0 +1,142 @@
1
+ // ============================================================
2
+ // Config Schema & Loader
3
+ // ============================================================
4
+ import yaml from 'js-yaml';
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { decrypt, encrypt } from './crypto.js';
8
+ /**
9
+ * 极简 .env 解析:KEY=VALUE,忽略注释和空行,不处理引号转义
10
+ */
11
+ function loadDotEnv(envPath) {
12
+ if (!fs.existsSync(envPath))
13
+ return;
14
+ const lines = fs.readFileSync(envPath, 'utf-8').split('\n');
15
+ for (const raw of lines) {
16
+ const line = raw.trim();
17
+ if (!line || line.startsWith('#'))
18
+ continue;
19
+ const eq = line.indexOf('=');
20
+ if (eq < 1)
21
+ continue;
22
+ const key = line.slice(0, eq).trim();
23
+ const val = line.slice(eq + 1).trim().replace(/^["']|["']$/g, '');
24
+ if (key && !(key in process.env))
25
+ process.env[key] = val;
26
+ }
27
+ }
28
+ /**
29
+ * 用环境变量覆盖 providers 中的 baseUrl / apiKey
30
+ * 规则:OPENAI_BASE_URL / OPENAI_API_KEY / ANTHROPIC_* / OLLAMA_*
31
+ */
32
+ function applyEnvToProviders(providers) {
33
+ const map = {
34
+ openai: { baseUrl: 'OPENAI_BASE_URL', apiKey: 'OPENAI_API_KEY' },
35
+ anthropic: { baseUrl: 'ANTHROPIC_BASE_URL', apiKey: 'ANTHROPIC_API_KEY' },
36
+ ollama: { baseUrl: 'OLLAMA_BASE_URL', apiKey: 'OLLAMA_API_KEY' },
37
+ };
38
+ for (const [type, vars] of Object.entries(map)) {
39
+ const baseUrl = process.env[vars.baseUrl];
40
+ const apiKey = process.env[vars.apiKey];
41
+ if (!baseUrl && !apiKey)
42
+ continue;
43
+ // 若 yaml 里该 provider 完全没有配置,自动创建
44
+ if (!providers[type]) {
45
+ providers[type] = { baseUrl: '' };
46
+ }
47
+ const p = providers[type];
48
+ if (baseUrl)
49
+ p.baseUrl = baseUrl;
50
+ if (apiKey)
51
+ p.apiKey = apiKey;
52
+ }
53
+ }
54
+ const DEFAULT_CONFIG = {
55
+ proxy: {
56
+ port: 19900,
57
+ logLevel: 'info',
58
+ },
59
+ providers: {},
60
+ trace: {
61
+ dir: './traces',
62
+ maxFileSize: 50 * 1024 * 1024,
63
+ maxAge: 7 * 24 * 3600,
64
+ maxInMemory: 1000,
65
+ },
66
+ dashboard: {
67
+ autoOpen: true,
68
+ },
69
+ };
70
+ /**
71
+ * Load config from YAML file, falling back to defaults
72
+ */
73
+ export function loadConfig(configPath) {
74
+ const resolvedPath = configPath || path.resolve(process.cwd(), 'lpt.config.yaml');
75
+ // 从项目根目录加载 .env(仅设置尚未存在的环境变量)
76
+ const envPath = path.resolve(process.cwd(), '.env');
77
+ loadDotEnv(envPath);
78
+ if (!fs.existsSync(resolvedPath)) {
79
+ const config = { ...DEFAULT_CONFIG, providers: {} };
80
+ applyEnvToProviders(config.providers);
81
+ return config;
82
+ }
83
+ const raw = fs.readFileSync(resolvedPath, 'utf-8');
84
+ const parsed = yaml.load(raw);
85
+ const config = {
86
+ proxy: { ...DEFAULT_CONFIG.proxy, ...parsed.proxy },
87
+ providers: normalizeProviders(parsed.providers),
88
+ trace: { ...DEFAULT_CONFIG.trace, ...parsed.trace },
89
+ dashboard: { ...DEFAULT_CONFIG.dashboard, ...parsed.dashboard },
90
+ };
91
+ // Decrypt API keys
92
+ for (const p of Object.values(config.providers)) {
93
+ if (p?.apiKey?.startsWith('enc:')) {
94
+ try {
95
+ p.apiKey = decrypt(p.apiKey.slice(4));
96
+ }
97
+ catch { /* keep as-is */ }
98
+ }
99
+ }
100
+ // 环境变量优先级最高,覆盖 yaml 中的值
101
+ applyEnvToProviders(config.providers);
102
+ return config;
103
+ }
104
+ /**
105
+ * Save config back to YAML file (encrypts API keys)
106
+ */
107
+ export function saveConfig(config, configPath) {
108
+ const resolvedPath = configPath || path.resolve(process.cwd(), 'lpt.config.yaml');
109
+ // Encrypt API keys before saving
110
+ const encryptedProviders = {};
111
+ for (const [type, p] of Object.entries(config.providers)) {
112
+ if (!p)
113
+ continue;
114
+ encryptedProviders[type] = p.apiKey
115
+ ? { ...p, apiKey: 'enc:' + encrypt(p.apiKey) }
116
+ : p;
117
+ }
118
+ const saveable = { ...config, providers: encryptedProviders };
119
+ const content = yaml.dump(saveable, { indent: 2, lineWidth: 120 });
120
+ fs.writeFileSync(resolvedPath, content, 'utf-8');
121
+ }
122
+ function normalizeProvider(p) {
123
+ return {
124
+ baseUrl: p.baseUrl || '',
125
+ apiKey: p.apiKey,
126
+ headers: p.headers,
127
+ };
128
+ }
129
+ function normalizeProviders(raw) {
130
+ if (!raw)
131
+ return {};
132
+ const result = {};
133
+ if (raw.openai && typeof raw.openai === 'object')
134
+ result.openai = normalizeProvider(raw.openai);
135
+ if (raw.anthropic && typeof raw.anthropic === 'object')
136
+ result.anthropic = normalizeProvider(raw.anthropic);
137
+ if (raw.ollama && typeof raw.ollama === 'object')
138
+ result.ollama = normalizeProvider(raw.ollama);
139
+ return result;
140
+ }
141
+ export { DEFAULT_CONFIG };
142
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../../../src/server/config/loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import { Provider } from './base.js';
2
+ import type { ProviderConfig } from '../types.js';
3
+ export declare class AnthropicProvider extends Provider {
4
+ constructor(config: ProviderConfig);
5
+ getUpstreamUrl(requestPath: string): string;
6
+ getUpstreamHeaders(): Record<string, string>;
7
+ matchesPath(path: string): boolean;
8
+ }
9
+ //# sourceMappingURL=anthropic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../../src/server/providers/anthropic.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,qBAAa,iBAAkB,SAAQ,QAAQ;gBACjC,MAAM,EAAE,cAAc;IAIlC,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAM3C,kBAAkB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAU5C,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;CAGnC"}
@@ -0,0 +1,29 @@
1
+ // ============================================================
2
+ // Anthropic Provider Adapter
3
+ // ============================================================
4
+ import { Provider } from './base.js';
5
+ export class AnthropicProvider extends Provider {
6
+ constructor(config) {
7
+ super('anthropic', config);
8
+ }
9
+ getUpstreamUrl(requestPath) {
10
+ const cleanPath = requestPath.startsWith('/') ? requestPath.slice(1) : requestPath;
11
+ const base = this.config.baseUrl.replace(/\/+$/, '');
12
+ return `${base}/${cleanPath}`;
13
+ }
14
+ getUpstreamHeaders() {
15
+ const headers = {
16
+ 'Content-Type': 'application/json',
17
+ 'anthropic-version': '2023-06-01',
18
+ };
19
+ if (this.config.apiKey)
20
+ headers['x-api-key'] = this.config.apiKey;
21
+ if (this.config.headers)
22
+ Object.assign(headers, this.config.headers);
23
+ return headers;
24
+ }
25
+ matchesPath(path) {
26
+ return path.includes('/messages');
27
+ }
28
+ }
29
+ //# sourceMappingURL=anthropic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../../src/server/providers/anthropic.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,6BAA6B;AAC7B,+DAA+D;AAE/D,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAGrC,MAAM,OAAO,iBAAkB,SAAQ,QAAQ;IAC7C,YAAY,MAAsB;QAChC,KAAK,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAC7B,CAAC;IAED,cAAc,CAAC,WAAmB;QAChC,MAAM,SAAS,GAAG,WAAW,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;QACnF,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACrD,OAAO,GAAG,IAAI,IAAI,SAAS,EAAE,CAAC;IAChC,CAAC;IAED,kBAAkB;QAChB,MAAM,OAAO,GAA2B;YACtC,cAAc,EAAE,kBAAkB;YAClC,mBAAmB,EAAE,YAAY;SAClC,CAAC;QACF,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM;YAAE,OAAO,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;QAClE,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACrE,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,WAAW,CAAC,IAAY;QACtB,OAAO,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IACpC,CAAC;CACF"}
@@ -0,0 +1,14 @@
1
+ import type { ProviderConfig } from '../types.js';
2
+ export declare abstract class Provider {
3
+ readonly type: string;
4
+ readonly config: ProviderConfig;
5
+ constructor(type: string, config: ProviderConfig);
6
+ /** Build the upstream URL for a given request path */
7
+ abstract getUpstreamUrl(requestPath: string): string;
8
+ /** Build headers to send to the upstream provider */
9
+ abstract getUpstreamHeaders(): Record<string, string>;
10
+ /** Check if this provider handles the given request path */
11
+ abstract matchesPath(path: string): boolean;
12
+ get baseUrl(): string;
13
+ }
14
+ //# sourceMappingURL=base.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../src/server/providers/base.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,8BAAsB,QAAQ;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;gBAEpB,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc;IAKhD,sDAAsD;IACtD,QAAQ,CAAC,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAEpD,qDAAqD;IACrD,QAAQ,CAAC,kBAAkB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAErD,4DAA4D;IAC5D,QAAQ,CAAC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAE3C,IAAI,OAAO,IAAI,MAAM,CAEpB;CACF"}
@@ -0,0 +1,15 @@
1
+ // ============================================================
2
+ // Provider Base - 提供商适配器基类
3
+ // ============================================================
4
+ export class Provider {
5
+ type;
6
+ config;
7
+ constructor(type, config) {
8
+ this.type = type;
9
+ this.config = config;
10
+ }
11
+ get baseUrl() {
12
+ return this.config.baseUrl;
13
+ }
14
+ }
15
+ //# sourceMappingURL=base.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base.js","sourceRoot":"","sources":["../../../src/server/providers/base.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,2BAA2B;AAC3B,+DAA+D;AAI/D,MAAM,OAAgB,QAAQ;IACnB,IAAI,CAAS;IACb,MAAM,CAAiB;IAEhC,YAAY,IAAY,EAAE,MAAsB;QAC9C,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAWD,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;IAC7B,CAAC;CACF"}
@@ -0,0 +1,9 @@
1
+ import { Provider } from './base.js';
2
+ import type { ProviderConfig } from '../types.js';
3
+ export declare class OllamaProvider extends Provider {
4
+ constructor(config: ProviderConfig);
5
+ getUpstreamUrl(requestPath: string): string;
6
+ getUpstreamHeaders(): Record<string, string>;
7
+ matchesPath(path: string): boolean;
8
+ }
9
+ //# sourceMappingURL=ollama.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ollama.d.ts","sourceRoot":"","sources":["../../../src/server/providers/ollama.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,qBAAa,cAAe,SAAQ,QAAQ;gBAC9B,MAAM,EAAE,cAAc;IAIlC,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAM3C,kBAAkB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAM5C,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;CAGnC"}
@@ -0,0 +1,24 @@
1
+ // ============================================================
2
+ // Ollama Provider Adapter
3
+ // ============================================================
4
+ import { Provider } from './base.js';
5
+ export class OllamaProvider extends Provider {
6
+ constructor(config) {
7
+ super('ollama', config);
8
+ }
9
+ getUpstreamUrl(requestPath) {
10
+ const cleanPath = requestPath.startsWith('/') ? requestPath.slice(1) : requestPath;
11
+ const base = this.config.baseUrl.replace(/\/+$/, '');
12
+ return `${base}/${cleanPath}`;
13
+ }
14
+ getUpstreamHeaders() {
15
+ const headers = { 'Content-Type': 'application/json' };
16
+ if (this.config.headers)
17
+ Object.assign(headers, this.config.headers);
18
+ return headers;
19
+ }
20
+ matchesPath(path) {
21
+ return path.startsWith('/api/') || path.startsWith('api/');
22
+ }
23
+ }
24
+ //# sourceMappingURL=ollama.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ollama.js","sourceRoot":"","sources":["../../../src/server/providers/ollama.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,0BAA0B;AAC1B,+DAA+D;AAE/D,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAGrC,MAAM,OAAO,cAAe,SAAQ,QAAQ;IAC1C,YAAY,MAAsB;QAChC,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC1B,CAAC;IAED,cAAc,CAAC,WAAmB;QAChC,MAAM,SAAS,GAAG,WAAW,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;QACnF,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACrD,OAAO,GAAG,IAAI,IAAI,SAAS,EAAE,CAAC;IAChC,CAAC;IAED,kBAAkB;QAChB,MAAM,OAAO,GAA2B,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC;QAC/E,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACrE,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,WAAW,CAAC,IAAY;QACtB,OAAO,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IAC7D,CAAC;CACF"}
@@ -0,0 +1,9 @@
1
+ import { Provider } from './base.js';
2
+ import type { ProviderConfig } from '../types.js';
3
+ export declare class OpenAIProvider extends Provider {
4
+ constructor(config: ProviderConfig);
5
+ getUpstreamUrl(requestPath: string): string;
6
+ getUpstreamHeaders(): Record<string, string>;
7
+ matchesPath(path: string): boolean;
8
+ }
9
+ //# sourceMappingURL=openai.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../../src/server/providers/openai.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,qBAAa,cAAe,SAAQ,QAAQ;gBAC9B,MAAM,EAAE,cAAc;IAIlC,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAU3C,kBAAkB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAO5C,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;CAKnC"}
@@ -0,0 +1,32 @@
1
+ // ============================================================
2
+ // OpenAI Provider Adapter
3
+ // ============================================================
4
+ import { Provider } from './base.js';
5
+ export class OpenAIProvider extends Provider {
6
+ constructor(config) {
7
+ super('openai', config);
8
+ }
9
+ getUpstreamUrl(requestPath) {
10
+ const base = this.config.baseUrl.replace(/\/+$/, '');
11
+ const cleanPath = requestPath.startsWith('/') ? requestPath.slice(1) : requestPath;
12
+ // If baseUrl already ends with /v1, don't add it again
13
+ if (base.endsWith('/v1') || cleanPath.startsWith('v1/')) {
14
+ return `${base}/${cleanPath}`;
15
+ }
16
+ return `${base}/v1/${cleanPath}`;
17
+ }
18
+ getUpstreamHeaders() {
19
+ const headers = { 'Content-Type': 'application/json' };
20
+ if (this.config.apiKey)
21
+ headers['Authorization'] = `Bearer ${this.config.apiKey}`;
22
+ if (this.config.headers)
23
+ Object.assign(headers, this.config.headers);
24
+ return headers;
25
+ }
26
+ matchesPath(path) {
27
+ return path.startsWith('/v1/') || path.startsWith('v1/') ||
28
+ path.startsWith('/chat/') || path.startsWith('/completions') ||
29
+ path === '/models';
30
+ }
31
+ }
32
+ //# sourceMappingURL=openai.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai.js","sourceRoot":"","sources":["../../../src/server/providers/openai.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,0BAA0B;AAC1B,+DAA+D;AAE/D,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAGrC,MAAM,OAAO,cAAe,SAAQ,QAAQ;IAC1C,YAAY,MAAsB;QAChC,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC1B,CAAC;IAED,cAAc,CAAC,WAAmB;QAChC,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACrD,MAAM,SAAS,GAAG,WAAW,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;QACnF,uDAAuD;QACvD,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,SAAS,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;YACxD,OAAO,GAAG,IAAI,IAAI,SAAS,EAAE,CAAC;QAChC,CAAC;QACD,OAAO,GAAG,IAAI,OAAO,SAAS,EAAE,CAAC;IACnC,CAAC;IAED,kBAAkB;QAChB,MAAM,OAAO,GAA2B,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC;QAC/E,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM;YAAE,OAAO,CAAC,eAAe,CAAC,GAAG,UAAU,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QAClF,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACrE,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,WAAW,CAAC,IAAY;QACtB,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;YACjD,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC;YAC5D,IAAI,KAAK,SAAS,CAAC;IAC5B,CAAC;CACF"}
@@ -0,0 +1,23 @@
1
+ import type { ProvidersConfig, ProviderType } from '../types.js';
2
+ import type { Provider } from './base.js';
3
+ export declare class ProviderRegistry {
4
+ private providers;
5
+ /** Register providers from config object */
6
+ registerAll(config: ProvidersConfig): void;
7
+ /** Get a provider by type */
8
+ get(type: ProviderType): Provider | undefined;
9
+ /** Get all registered providers */
10
+ getAll(): Provider[];
11
+ /** Get all registered type names (for logging) */
12
+ getTypes(): ProviderType[];
13
+ /**
14
+ * Resolve provider for a request path.
15
+ * 1. Anthropic path match (/messages)
16
+ * 2. Ollama path match (/api/*)
17
+ * 3. Fallback to openai
18
+ */
19
+ resolveProvider(requestPath: string): Provider | undefined;
20
+ startRefreshTimers(): void;
21
+ stopRefreshTimers(): void;
22
+ }
23
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/server/providers/registry.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAK1C,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,SAAS,CAA0C;IAE3D,4CAA4C;IAC5C,WAAW,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI;IAM1C,6BAA6B;IAC7B,GAAG,CAAC,IAAI,EAAE,YAAY,GAAG,QAAQ,GAAG,SAAS;IAI7C,mCAAmC;IACnC,MAAM,IAAI,QAAQ,EAAE;IAIpB,kDAAkD;IAClD,QAAQ,IAAI,YAAY,EAAE;IAI1B;;;;;OAKG;IACH,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS;IAY1D,kBAAkB,IAAI,IAAI;IAC1B,iBAAiB,IAAI,IAAI;CAC1B"}
@@ -0,0 +1,50 @@
1
+ // ============================================================
2
+ // Provider Registry - 以 type 为 key 管理提供商实例
3
+ // ============================================================
4
+ import { OpenAIProvider } from './openai.js';
5
+ import { AnthropicProvider } from './anthropic.js';
6
+ import { OllamaProvider } from './ollama.js';
7
+ export class ProviderRegistry {
8
+ providers = new Map();
9
+ /** Register providers from config object */
10
+ registerAll(config) {
11
+ if (config.openai)
12
+ this.providers.set('openai', new OpenAIProvider(config.openai));
13
+ if (config.anthropic)
14
+ this.providers.set('anthropic', new AnthropicProvider(config.anthropic));
15
+ if (config.ollama)
16
+ this.providers.set('ollama', new OllamaProvider(config.ollama));
17
+ }
18
+ /** Get a provider by type */
19
+ get(type) {
20
+ return this.providers.get(type);
21
+ }
22
+ /** Get all registered providers */
23
+ getAll() {
24
+ return Array.from(this.providers.values());
25
+ }
26
+ /** Get all registered type names (for logging) */
27
+ getTypes() {
28
+ return Array.from(this.providers.keys());
29
+ }
30
+ /**
31
+ * Resolve provider for a request path.
32
+ * 1. Anthropic path match (/messages)
33
+ * 2. Ollama path match (/api/*)
34
+ * 3. Fallback to openai
35
+ */
36
+ resolveProvider(requestPath) {
37
+ // Path-specific matching first (Anthropic, Ollama have distinct paths)
38
+ for (const provider of this.providers.values()) {
39
+ if (provider.type !== 'openai' && provider.matchesPath(requestPath)) {
40
+ return provider;
41
+ }
42
+ }
43
+ // Fallback: openai (handles /v1/* and anything else)
44
+ return this.providers.get('openai') ?? this.providers.values().next().value;
45
+ }
46
+ // 保留空实现,兼容 index.ts 调用
47
+ startRefreshTimers() { }
48
+ stopRefreshTimers() { }
49
+ }
50
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/server/providers/registry.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,2CAA2C;AAC3C,+DAA+D;AAI/D,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,OAAO,gBAAgB;IACnB,SAAS,GAAgC,IAAI,GAAG,EAAE,CAAC;IAE3D,4CAA4C;IAC5C,WAAW,CAAC,MAAuB;QACjC,IAAI,MAAM,CAAC,MAAM;YAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QACnF,IAAI,MAAM,CAAC,SAAS;YAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,iBAAiB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;QAC/F,IAAI,MAAM,CAAC,MAAM;YAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IACrF,CAAC;IAED,6BAA6B;IAC7B,GAAG,CAAC,IAAkB;QACpB,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,mCAAmC;IACnC,MAAM;QACJ,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED,kDAAkD;IAClD,QAAQ;QACN,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC;IAC3C,CAAC;IAED;;;;;OAKG;IACH,eAAe,CAAC,WAAmB;QACjC,uEAAuE;QACvE,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC;YAC/C,IAAI,QAAQ,CAAC,IAAI,KAAK,QAAQ,IAAI,QAAQ,CAAC,WAAW,CAAC,WAAW,CAAC,EAAE,CAAC;gBACpE,OAAO,QAAQ,CAAC;YAClB,CAAC;QACH,CAAC;QACD,qDAAqD;QACrD,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;IAC9E,CAAC;IAED,uBAAuB;IACvB,kBAAkB,KAAU,CAAC;IAC7B,iBAAiB,KAAU,CAAC;CAC7B"}
@@ -0,0 +1,9 @@
1
+ import type { Context } from 'hono';
2
+ import type { ProviderRegistry } from '../providers/registry.js';
3
+ import type { TraceCollector } from '../trace/collector.js';
4
+ import type { WSServer } from '../ws/server.js';
5
+ /**
6
+ * Handle proxy request: intercept → route → forward → trace
7
+ */
8
+ export declare function handleProxy(c: Context, registry: ProviderRegistry, collector: TraceCollector, ws: WSServer): Promise<Response>;
9
+ //# sourceMappingURL=handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/server/proxy/handler.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAIhD;;GAEG;AACH,wBAAsB,WAAW,CAC/B,CAAC,EAAE,OAAO,EACV,QAAQ,EAAE,gBAAgB,EAC1B,SAAS,EAAE,cAAc,EACzB,EAAE,EAAE,QAAQ,GACX,OAAO,CAAC,QAAQ,CAAC,CAkFnB"}
@@ -0,0 +1,101 @@
1
+ // ============================================================
2
+ // Proxy Handler - 代理请求处理核心
3
+ // ============================================================
4
+ import { proxySSEStream } from './stream.js';
5
+ /**
6
+ * Handle proxy request: intercept → route → forward → trace
7
+ */
8
+ export async function handleProxy(c, registry, collector, ws) {
9
+ const method = c.req.method;
10
+ const path = c.req.path;
11
+ const startTime = Date.now();
12
+ // Parse request body - use raw text + JSON.parse for robustness
13
+ let body = {};
14
+ try {
15
+ const rawText = await c.req.text();
16
+ if (rawText) {
17
+ body = JSON.parse(rawText);
18
+ }
19
+ }
20
+ catch {
21
+ // Non-JSON body (e.g., GET /v1/models)
22
+ }
23
+ // Create trace record
24
+ const reqHeaders = {};
25
+ for (const [key, value] of Object.entries(c.req.header())) {
26
+ if (typeof value === 'string') {
27
+ reqHeaders[key] = value;
28
+ }
29
+ }
30
+ const trace = collector.create(method, path, reqHeaders, body);
31
+ // Resolve provider
32
+ const provider = registry.resolveProvider(path);
33
+ if (!provider) {
34
+ collector.markError(trace.id, 'NO_PROVIDER', 'No provider available for this request');
35
+ // Return 400 to prevent SDK auto-retry (502 triggers retries)
36
+ return c.json({ error: { message: 'No provider configured', type: 'proxy_error' } }, 400);
37
+ }
38
+ collector.setProvider(trace.id, provider.type);
39
+ // Build upstream request
40
+ const upstreamUrl = provider.getUpstreamUrl(path);
41
+ const upstreamHeaders = provider.getUpstreamHeaders();
42
+ const isStream = body.stream === true;
43
+ try {
44
+ // Forward request to upstream
45
+ const upstreamResp = await fetch(upstreamUrl, {
46
+ method,
47
+ headers: upstreamHeaders,
48
+ body: method !== 'GET' ? JSON.stringify(body) : undefined,
49
+ });
50
+ if (!upstreamResp.ok && !isStream) {
51
+ const errorBody = await upstreamResp.text();
52
+ collector.recordResponse(trace.id, upstreamResp.status, Object.fromEntries(upstreamResp.headers.entries()), errorBody);
53
+ collector.markError(trace.id, 'UPSTREAM_ERROR', `Upstream returned ${upstreamResp.status}: ${errorBody.slice(0, 200)}`);
54
+ // B07: preserve original upstream status, pass through 4xx; translate 5xx to 502
55
+ const clientStatus = upstreamResp.status >= 500 ? 502 : upstreamResp.status;
56
+ return c.json({ error: { message: 'Upstream error', type: 'upstream_error', upstream_status: upstreamResp.status } }, clientStatus);
57
+ }
58
+ // Handle streaming response
59
+ if (isStream && upstreamResp.body) {
60
+ return proxySSEStream(upstreamResp, trace, collector, ws, startTime);
61
+ }
62
+ // Handle non-streaming response
63
+ const respBody = await upstreamResp.json();
64
+ const ttfb = Date.now() - startTime;
65
+ collector.recordResponse(trace.id, upstreamResp.status, Object.fromEntries(upstreamResp.headers.entries()), respBody);
66
+ // Extract usage
67
+ const usage = extractUsageFromResponse(respBody);
68
+ collector.complete(trace.id, ttfb, usage);
69
+ return c.json(respBody);
70
+ }
71
+ catch (err) {
72
+ const errorMsg = err instanceof Error ? err.message : String(err);
73
+ collector.markError(trace.id, 'FETCH_ERROR', errorMsg);
74
+ // Return 400 to prevent SDK auto-retry
75
+ return c.json({ error: { message: 'Proxy fetch failed', type: 'proxy_error', detail: errorMsg } }, 400);
76
+ }
77
+ }
78
+ function extractUsageFromResponse(body) {
79
+ const usage = body.usage;
80
+ if (!usage)
81
+ return undefined;
82
+ const result = {
83
+ promptTokens: usage.prompt_tokens || 0,
84
+ completionTokens: usage.completion_tokens || 0,
85
+ totalTokens: usage.total_tokens || 0,
86
+ };
87
+ const compDetails = usage.completion_tokens_details;
88
+ if (compDetails?.reasoning_tokens)
89
+ result.reasoningTokens = compDetails.reasoning_tokens;
90
+ const promptDetails = usage.prompt_tokens_details;
91
+ if (promptDetails?.cached_tokens)
92
+ result.cachedTokens = promptDetails.cached_tokens;
93
+ if (usage.cache_read_input_tokens)
94
+ result.cacheReadTokens = usage.cache_read_input_tokens;
95
+ if (usage.cache_creation_input_tokens)
96
+ result.cacheWriteTokens = usage.cache_creation_input_tokens;
97
+ if (!result.reasoningTokens && usage.reasoning_tokens)
98
+ result.reasoningTokens = usage.reasoning_tokens;
99
+ return result;
100
+ }
101
+ //# sourceMappingURL=handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.js","sourceRoot":"","sources":["../../../src/server/proxy/handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { TraceCollector } from '../trace/collector.js';
2
+ import type { WSServer } from '../ws/server.js';
3
+ import type { TraceRecord } from '../types.js';
4
+ /**
5
+ * Proxy an SSE stream from upstream to client, recording chunks for trace.
6
+ * 使用 start() 异步循环驱动,避免 pull() 回调的竞态问题。
7
+ */
8
+ export declare function proxySSEStream(upstreamResponse: Response, trace: TraceRecord, collector: TraceCollector, ws: WSServer, requestStartTime: number): Promise<Response>;
9
+ //# sourceMappingURL=stream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../../src/server/proxy/stream.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAwB,MAAM,aAAa,CAAC;AAErE;;;GAGG;AACH,wBAAsB,cAAc,CAClC,gBAAgB,EAAE,QAAQ,EAC1B,KAAK,EAAE,WAAW,EAClB,SAAS,EAAE,cAAc,EACzB,EAAE,EAAE,QAAQ,EACZ,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,QAAQ,CAAC,CA4InB"}