agentdog 1.2.0 → 1.3.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/README.md +5 -4
- package/dist/ai-gateway/auth.d.ts.map +1 -1
- package/dist/ai-gateway/auth.js +7 -1
- package/dist/ai-gateway/auth.js.map +1 -1
- package/dist/ai-gateway/auth.test.js +11 -4
- package/dist/ai-gateway/auth.test.js.map +1 -1
- package/dist/ai-gateway/call-log.d.ts +18 -0
- package/dist/ai-gateway/call-log.d.ts.map +1 -0
- package/dist/ai-gateway/call-log.js +136 -0
- package/dist/ai-gateway/call-log.js.map +1 -0
- package/dist/ai-gateway/call-log.test.d.ts +2 -0
- package/dist/ai-gateway/call-log.test.d.ts.map +1 -0
- package/dist/ai-gateway/call-log.test.js +136 -0
- package/dist/ai-gateway/call-log.test.js.map +1 -0
- package/dist/ai-gateway/count-tokens.d.ts.map +1 -1
- package/dist/ai-gateway/count-tokens.js +11 -1
- package/dist/ai-gateway/count-tokens.js.map +1 -1
- package/dist/ai-gateway/dialects.d.ts +1 -0
- package/dist/ai-gateway/dialects.d.ts.map +1 -1
- package/dist/ai-gateway/dialects.js +7 -0
- package/dist/ai-gateway/dialects.js.map +1 -1
- package/dist/ai-gateway/gateway-server.d.ts +2 -3
- package/dist/ai-gateway/gateway-server.d.ts.map +1 -1
- package/dist/ai-gateway/gateway-server.js +183 -67
- package/dist/ai-gateway/gateway-server.js.map +1 -1
- package/dist/ai-gateway/integration.test.js +237 -3
- package/dist/ai-gateway/integration.test.js.map +1 -1
- package/dist/ai-gateway/ir/anthropic-out.d.ts +15 -0
- package/dist/ai-gateway/ir/anthropic-out.d.ts.map +1 -0
- package/dist/ai-gateway/ir/anthropic-out.js +173 -0
- package/dist/ai-gateway/ir/anthropic-out.js.map +1 -0
- package/dist/ai-gateway/ir/anthropic-out.test.d.ts +2 -0
- package/dist/ai-gateway/ir/anthropic-out.test.d.ts.map +1 -0
- package/dist/ai-gateway/ir/anthropic-out.test.js +173 -0
- package/dist/ai-gateway/ir/anthropic-out.test.js.map +1 -0
- package/dist/ai-gateway/ir/openai-in.d.ts +3 -0
- package/dist/ai-gateway/ir/openai-in.d.ts.map +1 -0
- package/dist/ai-gateway/ir/openai-in.js +133 -0
- package/dist/ai-gateway/ir/openai-in.js.map +1 -0
- package/dist/ai-gateway/ir/openai-in.test.d.ts +2 -0
- package/dist/ai-gateway/ir/openai-in.test.d.ts.map +1 -0
- package/dist/ai-gateway/ir/openai-in.test.js +108 -0
- package/dist/ai-gateway/ir/openai-in.test.js.map +1 -0
- package/dist/ai-gateway/ir/openai-sse.d.ts +22 -0
- package/dist/ai-gateway/ir/openai-sse.d.ts.map +1 -0
- package/dist/ai-gateway/ir/openai-sse.js +166 -0
- package/dist/ai-gateway/ir/openai-sse.js.map +1 -0
- package/dist/ai-gateway/ir/openai-sse.test.d.ts +2 -0
- package/dist/ai-gateway/ir/openai-sse.test.d.ts.map +1 -0
- package/dist/ai-gateway/ir/openai-sse.test.js +119 -0
- package/dist/ai-gateway/ir/openai-sse.test.js.map +1 -0
- package/dist/ai-gateway/ir-channel.d.ts +14 -1
- package/dist/ai-gateway/ir-channel.d.ts.map +1 -1
- package/dist/ai-gateway/ir-channel.js +52 -11
- package/dist/ai-gateway/ir-channel.js.map +1 -1
- package/dist/ai-gateway/model-router.d.ts.map +1 -1
- package/dist/ai-gateway/model-router.js +3 -0
- package/dist/ai-gateway/model-router.js.map +1 -1
- package/dist/ai-gateway/model-router.test.js +10 -0
- package/dist/ai-gateway/model-router.test.js.map +1 -1
- package/dist/ai-gateway/models-list.d.ts +9 -0
- package/dist/ai-gateway/models-list.d.ts.map +1 -0
- package/dist/ai-gateway/models-list.js +42 -0
- package/dist/ai-gateway/models-list.js.map +1 -0
- package/dist/ai-gateway/models-list.test.d.ts +2 -0
- package/dist/ai-gateway/models-list.test.d.ts.map +1 -0
- package/dist/ai-gateway/models-list.test.js +47 -0
- package/dist/ai-gateway/models-list.test.js.map +1 -0
- package/dist/ai-gateway/openai-errors.d.ts +7 -0
- package/dist/ai-gateway/openai-errors.d.ts.map +1 -0
- package/dist/ai-gateway/openai-errors.js +23 -0
- package/dist/ai-gateway/openai-errors.js.map +1 -0
- package/dist/ai-gateway/openai-errors.test.d.ts +2 -0
- package/dist/ai-gateway/openai-errors.test.d.ts.map +1 -0
- package/dist/ai-gateway/openai-errors.test.js +38 -0
- package/dist/ai-gateway/openai-errors.test.js.map +1 -0
- package/dist/ai-gateway/passthrough.d.ts +1 -0
- package/dist/ai-gateway/passthrough.d.ts.map +1 -1
- package/dist/ai-gateway/passthrough.js +104 -0
- package/dist/ai-gateway/passthrough.js.map +1 -1
- package/dist/ai-gateway/passthrough.test.js +66 -1
- package/dist/ai-gateway/passthrough.test.js.map +1 -1
- package/dist/daemon/agentdog-daemon.d.ts +1 -1
- package/dist/daemon/agentdog-daemon.d.ts.map +1 -1
- package/dist/daemon/agentdog-daemon.js +27 -1
- package/dist/daemon/agentdog-daemon.js.map +1 -1
- package/dist/daemon/daemon-web-server.ai.test.js +30 -3
- package/dist/daemon/daemon-web-server.ai.test.js.map +1 -1
- package/dist/daemon/daemon-web-server.d.ts +2 -0
- package/dist/daemon/daemon-web-server.d.ts.map +1 -1
- package/dist/daemon/daemon-web-server.js +83 -11
- package/dist/daemon/daemon-web-server.js.map +1 -1
- package/dist/logging/jsonl-store.d.ts +23 -0
- package/dist/logging/jsonl-store.d.ts.map +1 -0
- package/dist/logging/jsonl-store.js +107 -0
- package/dist/logging/jsonl-store.js.map +1 -0
- package/dist/logging/jsonl-store.test.d.ts +2 -0
- package/dist/logging/jsonl-store.test.d.ts.map +1 -0
- package/dist/logging/jsonl-store.test.js +89 -0
- package/dist/logging/jsonl-store.test.js.map +1 -0
- package/dist/logging/server-log-manager.d.ts +3 -0
- package/dist/logging/server-log-manager.d.ts.map +1 -1
- package/dist/logging/server-log-manager.js +20 -0
- package/dist/logging/server-log-manager.js.map +1 -1
- package/dist/types/index.d.ts +20 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +3 -2
- package/web/dist/assets/index-CHSrrPtl.css +1 -0
- package/web/dist/assets/index-gdp_c7vR.js +106 -0
- package/web/dist/assets/index-gdp_c7vR.js.map +1 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-BdEVyQ8g.js +0 -118
- package/web/dist/assets/index-BdEVyQ8g.js.map +0 -1
- package/web/dist/assets/index-Bsl7BWhF.css +0 -1
package/README.md
CHANGED
|
@@ -161,16 +161,16 @@ agentdog diagnose --fix # 自动修复常见问题
|
|
|
161
161
|
|
|
162
162
|
## 🤖 AI API 网关
|
|
163
163
|
|
|
164
|
-
daemon 内置 AI API 网关:对外提供 Anthropic Messages 兼容端点,Claude Code 统一接入任意供应商(openai 兼容 / anthropic / gemini
|
|
164
|
+
daemon 内置 AI API 网关:对外提供 Anthropic Messages 兼容端点,Claude Code 统一接入任意供应商(openai 兼容 / anthropic / gemini);同时提供 OpenAI Chat Completions 兼容入口 `/openai/v1/chat/completions`(base URL 指向 `<host>:<port>/openai/v1`),OpenAI 协议客户端同样按 `slug:modelId` 寻址。
|
|
165
165
|
|
|
166
166
|
```bash
|
|
167
|
-
agentdog daemon start --
|
|
167
|
+
agentdog daemon start --web-port 61125 # 网关与 Web 管理界面同端口(方言前缀路由)
|
|
168
168
|
```
|
|
169
169
|
|
|
170
170
|
在 Web 界面"AI 供应商"视图配置供应商(slug、方言、Base URL、API Key),然后:
|
|
171
171
|
|
|
172
172
|
```bash
|
|
173
|
-
export ANTHROPIC_BASE_URL=http://127.0.0.1:
|
|
173
|
+
export ANTHROPIC_BASE_URL=http://127.0.0.1:61125/anthropic
|
|
174
174
|
export ANTHROPIC_API_KEY=<网关 apiKey,界面可复制>
|
|
175
175
|
export ANTHROPIC_MODEL=deepseek:deepseek-chat # 格式:供应商slug:模型Id
|
|
176
176
|
export ANTHROPIC_SMALL_FAST_MODEL=deepseek:deepseek-chat
|
|
@@ -179,8 +179,9 @@ export ANTHROPIC_SMALL_FAST_MODEL=deepseek:deepseek-chat
|
|
|
179
179
|
- **寻址**:模型名按第一个 `:` 分割,前段命中供应商 slug,后段为上游真实模型名原样透传;未命中返回 400 并列出可用 slug。
|
|
180
180
|
- **anthropic 上游直通**:请求字节级转发,`cache_control`、`thinking`、prompt cache 计费字段零损耗;`count_tokens` 直通真值(openai/gemini 上游本地估算)。
|
|
181
181
|
- **openai / gemini 转换**:工具调用、图片、system、tool_choice、采样参数经统一 IR 双向映射;流式 SSE 双向转换(延迟提交、ping 保活、断连 abort 链),上游错误状态码透传(message 保留原文,便于诊断中转站差异)。
|
|
182
|
+
- **模型列表端点**:`GET /anthropic/v1/models`(含裸 `/v1/models` 别名)与 `GET /openai/v1/models`,返回 `slug:modelId` 形式的可用模型,供客户端模型自动发现。
|
|
182
183
|
- **Provider 管理面**:`/api/ai-gateway/*` 与 `/api/ai-providers` REST 接口(CRUD / 连通测试 / 模型拉取),apiKey 脱敏返回;对外 key 以 `ad-sk-` 前缀在首次启用时自动生成。配置文件新增可选段 `aiGateway` / `providers`,老配置无此段 = 网关关闭。
|
|
183
|
-
- **已知限制**:gemini 同名函数多调用的 `tool_result` 配对存在歧义;openai 兼容端点(DeepSeek / OpenRouter /
|
|
184
|
+
- **已知限制**:gemini 同名函数多调用的 `tool_result` 配对存在歧义;openai 兼容端点(DeepSeek / OpenRouter / 中转站)各有出入,不做静默兼容;OpenAI 入口不支持 `n>1`(返回 400),openai 入口到 anthropic/gemini 上游时 IR 无法承载的参数(`response_format` / `logprobs` / `reasoning_effort` 等)直接丢弃(openai 上游走直通不受影响);anthropic 上游 thinking 期间 OpenAI 出口静默(SSE 无 ping 约定);`/v1/models` 不支持分页参数,`created` 为占位值。穿插对话中间的 `system` 消息统一并入开头系统提示;`count_tokens` 无 OpenAI 入口端点(协议本身没有)。
|
|
184
185
|
|
|
185
186
|
## 🧩 子服务器配置示例
|
|
186
187
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/ai-gateway/auth.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/ai-gateway/auth.ts"],"names":[],"mappings":"AAEA,OAAO,EAAmC,cAAc,EAAE,MAAM,SAAS,CAAC;AAI1E,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,CAgBhE"}
|
package/dist/ai-gateway/auth.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { anthropicError } from './anthropic-errors.js';
|
|
2
|
+
import { openaiError } from './openai-errors.js';
|
|
2
3
|
export function createGatewayAuth(apiKey) {
|
|
3
4
|
return (req, res, next) => {
|
|
4
5
|
const headerKey = req.headers['x-api-key'] || '';
|
|
@@ -8,7 +9,12 @@ export function createGatewayAuth(apiKey) {
|
|
|
8
9
|
next();
|
|
9
10
|
return;
|
|
10
11
|
}
|
|
11
|
-
|
|
12
|
+
if ((req.path || '').startsWith('/openai')) {
|
|
13
|
+
openaiError(res, 401, 'invalid_request_error', 'invalid x-api-key or bearer token', 'invalid_api_key');
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
anthropicError(res, 401, 'authentication_error', 'invalid x-api-key');
|
|
17
|
+
}
|
|
12
18
|
};
|
|
13
19
|
}
|
|
14
20
|
//# sourceMappingURL=auth.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/ai-gateway/auth.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/ai-gateway/auth.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC9C,OAAO,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAQ,EAAE;QAC/D,MAAM,SAAS,GAAI,GAAG,CAAC,OAAO,CAAC,WAAW,CAAwB,IAAI,EAAE,CAAC;QACzE,MAAM,UAAU,GAAI,GAAG,CAAC,OAAO,CAAC,aAAoC,IAAI,EAAE,CAAC;QAC3E,MAAM,SAAS,GAAG,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAE9E,IAAI,SAAS,KAAK,MAAM,IAAI,CAAC,SAAS,IAAI,SAAS,KAAK,MAAM,CAAC,EAAE,CAAC;YAChE,IAAI,EAAE,CAAC;YACP,OAAO;QACT,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC3C,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,uBAAuB,EAAE,mCAAmC,EAAE,iBAAiB,CAAC,CAAC;QACzG,CAAC;aAAM,CAAC;YACN,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,sBAAsB,EAAE,mBAAmB,CAAC,CAAC;QACxE,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -13,20 +13,20 @@ describe('createGatewayAuth', () => {
|
|
|
13
13
|
it('x-api-key 通过', () => {
|
|
14
14
|
const auth = createGatewayAuth('sk-test');
|
|
15
15
|
let next = 0;
|
|
16
|
-
auth({ headers: { 'x-api-key': 'sk-test' } }, mockRes(), () => next++);
|
|
16
|
+
auth({ headers: { 'x-api-key': 'sk-test' }, path: '/v1/messages' }, mockRes(), () => next++);
|
|
17
17
|
expect(next).toBe(1);
|
|
18
18
|
});
|
|
19
19
|
it('Authorization: Bearer 通过', () => {
|
|
20
20
|
const auth = createGatewayAuth('sk-test');
|
|
21
21
|
let next = 0;
|
|
22
|
-
auth({ headers: { authorization: 'Bearer sk-test' } }, mockRes(), () => next++);
|
|
22
|
+
auth({ headers: { authorization: 'Bearer sk-test' }, path: '/v1/messages' }, mockRes(), () => next++);
|
|
23
23
|
expect(next).toBe(1);
|
|
24
24
|
});
|
|
25
25
|
it('错误 key 返回 401 且 Anthropic 格式错误体', () => {
|
|
26
26
|
const auth = createGatewayAuth('sk-test');
|
|
27
27
|
let next = 0;
|
|
28
28
|
const res = mockRes();
|
|
29
|
-
auth({ headers: { 'x-api-key': 'bad' } }, res, () => next++);
|
|
29
|
+
auth({ headers: { 'x-api-key': 'bad' }, path: '/v1/messages' }, res, () => next++);
|
|
30
30
|
expect(next).toBe(0);
|
|
31
31
|
expect(res.statusCode).toBe(401);
|
|
32
32
|
expect(res.body).toEqual({
|
|
@@ -34,10 +34,17 @@ describe('createGatewayAuth', () => {
|
|
|
34
34
|
error: { type: 'authentication_error', message: 'invalid x-api-key' },
|
|
35
35
|
});
|
|
36
36
|
});
|
|
37
|
+
it('错误 key 且 /openai 路径返回 401 OpenAI 格式错误体', () => {
|
|
38
|
+
const auth = createGatewayAuth('sk-test');
|
|
39
|
+
const res = mockRes();
|
|
40
|
+
auth({ headers: { 'x-api-key': 'bad' }, path: '/openai/v1/models' }, res, () => { });
|
|
41
|
+
expect(res.statusCode).toBe(401);
|
|
42
|
+
expect(res.body.error).toEqual({ message: 'invalid x-api-key or bearer token', type: 'invalid_request_error', code: 'invalid_api_key' });
|
|
43
|
+
});
|
|
37
44
|
it('无认证头拒绝', () => {
|
|
38
45
|
const auth = createGatewayAuth('sk-test');
|
|
39
46
|
const res = mockRes();
|
|
40
|
-
auth({ headers: {} }, res, () => { throw new Error('should not call next'); });
|
|
47
|
+
auth({ headers: {}, path: '/v1/messages' }, res, () => { throw new Error('should not call next'); });
|
|
41
48
|
expect(res.statusCode).toBe(401);
|
|
42
49
|
});
|
|
43
50
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.test.js","sourceRoot":"","sources":["../../src/ai-gateway/auth.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE1D,SAAS,OAAO;IACd,OAAO;QACL,UAAU,EAAE,CAAC;QACb,IAAI,EAAE,IAAW;QACjB,MAAM,CAAC,CAAS,IAAI,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC;QACvD,IAAI,CAAC,CAAM,IAAI,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC;KACtC,CAAC;AACX,CAAC;AAED,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,EAAE,CAAC,cAAc,EAAE,GAAG,EAAE;QACtB,MAAM,IAAI,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;QAC1C,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,IAAI,CAAC,EAAE,OAAO,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE,EAAS,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.test.js","sourceRoot":"","sources":["../../src/ai-gateway/auth.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Request, Response } from 'express';
|
|
2
|
+
import { AiCallLog } from '../types/index.js';
|
|
3
|
+
import { JsonlStore } from '../logging/jsonl-store.js';
|
|
4
|
+
export declare function getAiCallStore(): JsonlStore;
|
|
5
|
+
export declare function resetAiCallStoreForTests(): void;
|
|
6
|
+
export declare function beginCallLog(res: Response, init: {
|
|
7
|
+
ingress: 'anthropic' | 'openai';
|
|
8
|
+
path: string;
|
|
9
|
+
model: string;
|
|
10
|
+
}): void;
|
|
11
|
+
export declare function patchCallLog(res: Response, patch: Partial<AiCallLog>): void;
|
|
12
|
+
export declare function finalizeCallLog(res: Response, patch: Partial<AiCallLog>): void;
|
|
13
|
+
export declare function getRecentAiCalls(limit?: number): AiCallLog[];
|
|
14
|
+
export declare function requestExcerptOf(req: Request): string;
|
|
15
|
+
export declare function trackAnthropicSSEUsage(res: Response, line: string): void;
|
|
16
|
+
export declare function trackOpenAISSEUsage(res: Response, line: string): void;
|
|
17
|
+
export declare function responseExcerptOf(text: string): string;
|
|
18
|
+
//# sourceMappingURL=call-log.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"call-log.d.ts","sourceRoot":"","sources":["../../src/ai-gateway/call-log.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAiB,MAAM,2BAA2B,CAAC;AAStE,wBAAgB,cAAc,IAAI,UAAU,CAQ3C;AAGD,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C;AAaD,wBAAgB,YAAY,CAC1B,GAAG,EAAE,QAAQ,EACb,IAAI,EAAE;IAAE,OAAO,EAAE,WAAW,GAAG,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACrE,IAAI,CA0BN;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,CAAC,SAAS,CAAC,GAAG,IAAI,CAS3E;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,CAAC,SAAS,CAAC,GAAG,IAAI,CAS9E;AAED,wBAAgB,gBAAgB,CAAC,KAAK,SAAa,GAAG,SAAS,EAAE,CAEhE;AAID,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAcrD;AAGD,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAYxE;AAGD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAYrE;AAGD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtD"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// AI 调用流水:网关请求生命周期埋点 → 环形缓冲 + JSONL 落盘 + append 事件
|
|
2
|
+
// (daemon-web-server 订阅事件转 socket.io 'ai-log' 推送)。
|
|
3
|
+
// 埋点协议:入口 handler 调 beginCallLog 挂 res.locals.callLog 并注册 finish/close 统一 finalize;
|
|
4
|
+
// 转发通道(passthrough / ir-channel / count-tokens)通过 patchCallLog 补 usage 与失败摘要。
|
|
5
|
+
import { randomUUID } from 'crypto';
|
|
6
|
+
import { JsonlStore, resolveLogDir } from '../logging/jsonl-store.js';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
const EXCERPT_CHARS = 500;
|
|
9
|
+
const MAX_RECENT = 200;
|
|
10
|
+
// 惰性单例:首次使用时才解析 AGENTDOG_LOG_DIR(测试隔离在用例前设 env 即可生效)
|
|
11
|
+
let store;
|
|
12
|
+
export function getAiCallStore() {
|
|
13
|
+
if (!store) {
|
|
14
|
+
store = new JsonlStore({
|
|
15
|
+
filePath: join(resolveLogDir(), 'ai-calls.jsonl'),
|
|
16
|
+
maxEntries: MAX_RECENT,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
return store;
|
|
20
|
+
}
|
|
21
|
+
// 仅供测试:重置惰性单例,让下一个用例用新的 AGENTDOG_LOG_DIR 重新初始化
|
|
22
|
+
export function resetAiCallStoreForTests() {
|
|
23
|
+
store = undefined;
|
|
24
|
+
}
|
|
25
|
+
function ctxOf(res) {
|
|
26
|
+
if (!res.locals)
|
|
27
|
+
return undefined; // 非标准 Response(测试 mock)无埋点上下文,静默跳过
|
|
28
|
+
return res.locals.callLog;
|
|
29
|
+
}
|
|
30
|
+
export function beginCallLog(res, init) {
|
|
31
|
+
const call = {
|
|
32
|
+
id: randomUUID(),
|
|
33
|
+
timestamp: new Date().toISOString(),
|
|
34
|
+
ingress: init.ingress,
|
|
35
|
+
path: init.path,
|
|
36
|
+
model: init.model,
|
|
37
|
+
providerName: '',
|
|
38
|
+
upstreamModel: '',
|
|
39
|
+
status: 0,
|
|
40
|
+
durationMs: 0,
|
|
41
|
+
};
|
|
42
|
+
if (!res.locals) {
|
|
43
|
+
res.locals = {};
|
|
44
|
+
}
|
|
45
|
+
res.locals.callLog = { call, startedAt: Date.now(), finalized: false };
|
|
46
|
+
// 响应结束(正常完成或断连)统一在此落一条流水;finalized 防两事件重复触发
|
|
47
|
+
res.on('finish', () => finalizeCallLog(res, {}));
|
|
48
|
+
res.on('close', () => {
|
|
49
|
+
if (!res.headersSent) {
|
|
50
|
+
finalizeCallLog(res, { status: 499 }); // 客户端断连(请求未完成)
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
finalizeCallLog(res, {});
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
export function patchCallLog(res, patch) {
|
|
58
|
+
const ctx = ctxOf(res);
|
|
59
|
+
if (!ctx)
|
|
60
|
+
return;
|
|
61
|
+
const { usage, ...rest } = patch;
|
|
62
|
+
if (usage) {
|
|
63
|
+
// 流式场景 usage 分多帧到达(input 先于 output),合并而非覆盖
|
|
64
|
+
ctx.call.usage = { ...ctx.call.usage, ...usage };
|
|
65
|
+
}
|
|
66
|
+
Object.assign(ctx.call, rest);
|
|
67
|
+
}
|
|
68
|
+
export function finalizeCallLog(res, patch) {
|
|
69
|
+
const ctx = ctxOf(res);
|
|
70
|
+
if (!ctx || ctx.finalized)
|
|
71
|
+
return;
|
|
72
|
+
ctx.finalized = true;
|
|
73
|
+
Object.assign(ctx.call, patch, { durationMs: Date.now() - ctx.startedAt });
|
|
74
|
+
if (!ctx.call.status) {
|
|
75
|
+
ctx.call.status = res.statusCode || 0;
|
|
76
|
+
}
|
|
77
|
+
getAiCallStore().append(ctx.call);
|
|
78
|
+
}
|
|
79
|
+
export function getRecentAiCalls(limit = MAX_RECENT) {
|
|
80
|
+
return getAiCallStore().recent(limit);
|
|
81
|
+
}
|
|
82
|
+
// 失败请求的结构摘要:只记关键字段与规模,不序列化正文
|
|
83
|
+
//(请求体可达 32MB,全量 stringify 会拖垮失败路径且可能携带截图 base64)
|
|
84
|
+
export function requestExcerptOf(req) {
|
|
85
|
+
const body = req.body;
|
|
86
|
+
if (!body || typeof body !== 'object') {
|
|
87
|
+
return '';
|
|
88
|
+
}
|
|
89
|
+
const b = body;
|
|
90
|
+
const messages = Array.isArray(b.messages) ? b.messages : undefined;
|
|
91
|
+
return JSON.stringify({
|
|
92
|
+
model: b.model,
|
|
93
|
+
stream: b.stream,
|
|
94
|
+
messages: messages ? `${messages.length} messages` : undefined,
|
|
95
|
+
system: typeof b.system === 'string' ? `${b.system.length} chars` : undefined,
|
|
96
|
+
maxTokens: b.max_tokens ?? b.maxTokens,
|
|
97
|
+
}).slice(0, EXCERPT_CHARS);
|
|
98
|
+
}
|
|
99
|
+
// anthropic SSE 流式 usage:message_start 携带 input_tokens,message_delta 携带 output_tokens
|
|
100
|
+
export function trackAnthropicSSEUsage(res, line) {
|
|
101
|
+
if (!line.startsWith('data:'))
|
|
102
|
+
return;
|
|
103
|
+
try {
|
|
104
|
+
const data = JSON.parse(line.slice(5).trim());
|
|
105
|
+
if (data.type === 'message_start' && data.message?.usage) {
|
|
106
|
+
patchCallLog(res, { usage: { inputTokens: data.message.usage.input_tokens } });
|
|
107
|
+
}
|
|
108
|
+
else if (data.type === 'message_delta' && data.usage) {
|
|
109
|
+
patchCallLog(res, { usage: { outputTokens: data.usage.output_tokens } });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// 非 JSON 行(注释/心跳)忽略
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// openai SSE 流式 usage:stream_options.include_usage 时末 chunk 携带 usage
|
|
117
|
+
export function trackOpenAISSEUsage(res, line) {
|
|
118
|
+
if (!line.startsWith('data:'))
|
|
119
|
+
return;
|
|
120
|
+
try {
|
|
121
|
+
const data = JSON.parse(line.slice(5).trim());
|
|
122
|
+
if (data.usage) {
|
|
123
|
+
patchCallLog(res, {
|
|
124
|
+
usage: { inputTokens: data.usage.prompt_tokens, outputTokens: data.usage.completion_tokens },
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// 非 JSON 行忽略
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// 上游响应正文摘要(已是字符串,直接截断)
|
|
133
|
+
export function responseExcerptOf(text) {
|
|
134
|
+
return text.slice(0, EXCERPT_CHARS);
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=call-log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"call-log.js","sourceRoot":"","sources":["../../src/ai-gateway/call-log.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"call-log.test.d.ts","sourceRoot":"","sources":["../../src/ai-gateway/call-log.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// getAiCallStore 惰性初始化,beforeEach 设 AGENTDOG_LOG_DIR + reset 后用例内首次调用即生效
|
|
2
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
3
|
+
import { mkdtemp, rm } from 'fs/promises';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { beginCallLog, patchCallLog, getRecentAiCalls, getAiCallStore, resetAiCallStoreForTests, requestExcerptOf, trackAnthropicSSEUsage, trackOpenAISSEUsage, responseExcerptOf, } from './call-log.js';
|
|
7
|
+
let dir;
|
|
8
|
+
beforeEach(async () => {
|
|
9
|
+
dir = await mkdtemp(join(tmpdir(), 'agentdog-calllog-'));
|
|
10
|
+
process.env.AGENTDOG_LOG_DIR = dir;
|
|
11
|
+
resetAiCallStoreForTests(); // 单例跟随新目录重新初始化,缓冲不跨用例累积
|
|
12
|
+
});
|
|
13
|
+
afterEach(async () => {
|
|
14
|
+
// 等 fire-and-forget 落盘完成再删临时目录,避免用例尾部的 write 撞上目录清理
|
|
15
|
+
await getAiCallStore().ready().catch(() => { });
|
|
16
|
+
await new Promise((r) => setTimeout(r, 30));
|
|
17
|
+
delete process.env.AGENTDOG_LOG_DIR;
|
|
18
|
+
await rm(dir, { recursive: true, force: true });
|
|
19
|
+
});
|
|
20
|
+
// 最小 Response mock:express 的 res.locals / 事件注册 / statusCode
|
|
21
|
+
function mockRes(statusCode = 200, headersSent = true) {
|
|
22
|
+
const listeners = new Map();
|
|
23
|
+
const res = {
|
|
24
|
+
locals: {},
|
|
25
|
+
headersSent,
|
|
26
|
+
statusCode,
|
|
27
|
+
on(event, cb) {
|
|
28
|
+
listeners.set(event, [...(listeners.get(event) || []), cb]);
|
|
29
|
+
return res;
|
|
30
|
+
},
|
|
31
|
+
emit(event) {
|
|
32
|
+
(listeners.get(event) || []).forEach((cb) => cb());
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
return res;
|
|
36
|
+
}
|
|
37
|
+
function mockReq(body) {
|
|
38
|
+
return { body };
|
|
39
|
+
}
|
|
40
|
+
describe('call-log 埋点协议', () => {
|
|
41
|
+
it('begin + finish → finalize 落一条流水,status/durationMs 就位', async () => {
|
|
42
|
+
const res = mockRes(200);
|
|
43
|
+
beginCallLog(res, { ingress: 'anthropic', path: '/anthropic/v1/messages', model: 'zhipu:glm-4' });
|
|
44
|
+
patchCallLog(res, { providerName: 'zhipu', upstreamModel: 'glm-4' });
|
|
45
|
+
res.emit('finish');
|
|
46
|
+
const calls = getRecentAiCalls();
|
|
47
|
+
expect(calls).toHaveLength(1);
|
|
48
|
+
expect(calls[0]).toMatchObject({
|
|
49
|
+
ingress: 'anthropic',
|
|
50
|
+
path: '/anthropic/v1/messages',
|
|
51
|
+
model: 'zhipu:glm-4',
|
|
52
|
+
providerName: 'zhipu',
|
|
53
|
+
upstreamModel: 'glm-4',
|
|
54
|
+
status: 200,
|
|
55
|
+
});
|
|
56
|
+
expect(calls[0].durationMs).toBeGreaterThanOrEqual(0);
|
|
57
|
+
expect(calls[0].id).toBeTruthy();
|
|
58
|
+
});
|
|
59
|
+
it('finish 与 close 连续触发只落一条(finalized 防重)', () => {
|
|
60
|
+
const res = mockRes(200);
|
|
61
|
+
beginCallLog(res, { ingress: 'openai', path: '/openai/v1/chat/completions', model: '' });
|
|
62
|
+
res.emit('finish');
|
|
63
|
+
res.emit('close');
|
|
64
|
+
expect(getRecentAiCalls()).toHaveLength(1);
|
|
65
|
+
});
|
|
66
|
+
it('断连(close 且未发响应头)记 status 499', () => {
|
|
67
|
+
const res = mockRes(200, false);
|
|
68
|
+
beginCallLog(res, { ingress: 'anthropic', path: '/v1/messages', model: 'x' });
|
|
69
|
+
res.emit('close');
|
|
70
|
+
expect(getRecentAiCalls()[0].status).toBe(499);
|
|
71
|
+
});
|
|
72
|
+
it('流式 usage 分帧 patch 被合并而非覆盖', () => {
|
|
73
|
+
const res = mockRes(200);
|
|
74
|
+
beginCallLog(res, { ingress: 'anthropic', path: '/v1/messages', model: 'x' });
|
|
75
|
+
patchCallLog(res, { usage: { inputTokens: 12 } });
|
|
76
|
+
patchCallLog(res, { usage: { outputTokens: 34 } });
|
|
77
|
+
res.emit('finish');
|
|
78
|
+
expect(getRecentAiCalls()[0].usage).toEqual({ inputTokens: 12, outputTokens: 34 });
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
describe('SSE usage 解析', () => {
|
|
82
|
+
it('anthropic:message_start 取 input、message_delta 取 output', () => {
|
|
83
|
+
const res = mockRes();
|
|
84
|
+
beginCallLog(res, { ingress: 'anthropic', path: '/v1/messages', model: 'x' });
|
|
85
|
+
trackAnthropicSSEUsage(res, 'event: message_start');
|
|
86
|
+
trackAnthropicSSEUsage(res, `data: ${JSON.stringify({ type: 'message_start', message: { usage: { input_tokens: 7 } } })}`);
|
|
87
|
+
trackAnthropicSSEUsage(res, `data: ${JSON.stringify({ type: 'message_delta', usage: { output_tokens: 9 } })}`);
|
|
88
|
+
trackAnthropicSSEUsage(res, 'data: not-json');
|
|
89
|
+
res.emit('finish');
|
|
90
|
+
expect(getRecentAiCalls()[0].usage).toEqual({ inputTokens: 7, outputTokens: 9 });
|
|
91
|
+
});
|
|
92
|
+
it('openai:末 chunk usage 直接取 prompt/completion', () => {
|
|
93
|
+
const res = mockRes();
|
|
94
|
+
beginCallLog(res, { ingress: 'openai', path: '/openai/v1/chat/completions', model: 'x' });
|
|
95
|
+
trackOpenAISSEUsage(res, `data: ${JSON.stringify({ choices: [] })}`);
|
|
96
|
+
trackOpenAISSEUsage(res, `data: ${JSON.stringify({ usage: { prompt_tokens: 5, completion_tokens: 6 } })}`);
|
|
97
|
+
res.emit('finish');
|
|
98
|
+
expect(getRecentAiCalls()[0].usage).toEqual({ inputTokens: 5, outputTokens: 6 });
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
describe('摘要边界', () => {
|
|
102
|
+
it('requestExcerpt 只记结构摘要,不含 messages 正文', () => {
|
|
103
|
+
const req = mockReq({
|
|
104
|
+
model: 'zhipu:glm-4',
|
|
105
|
+
stream: true,
|
|
106
|
+
messages: [{ role: 'user', content: 'x'.repeat(100000) }],
|
|
107
|
+
max_tokens: 1024,
|
|
108
|
+
});
|
|
109
|
+
const excerpt = requestExcerptOf(req);
|
|
110
|
+
expect(excerpt).toContain('"model":"zhipu:glm-4"');
|
|
111
|
+
expect(excerpt).toContain('1 messages');
|
|
112
|
+
expect(excerpt.length).toBeLessThan(300);
|
|
113
|
+
expect(excerpt).not.toContain('xxxx');
|
|
114
|
+
});
|
|
115
|
+
it('requestExcerpt 空请求体返回空串', () => {
|
|
116
|
+
expect(requestExcerptOf(mockReq(undefined))).toBe('');
|
|
117
|
+
});
|
|
118
|
+
it('responseExcerpt 截断到 500 字符', () => {
|
|
119
|
+
expect(responseExcerptOf('y'.repeat(2000)).length).toBe(500);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
describe('store 落盘', () => {
|
|
123
|
+
it('finalize 后 ai-calls.jsonl 出现对应 JSON 行', async () => {
|
|
124
|
+
const res = mockRes(200);
|
|
125
|
+
beginCallLog(res, { ingress: 'anthropic', path: '/v1/messages', model: 'm' });
|
|
126
|
+
res.emit('finish');
|
|
127
|
+
await getAiCallStore().ready();
|
|
128
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
129
|
+
const { readFile } = await import('fs/promises');
|
|
130
|
+
const text = await readFile(join(dir, 'ai-calls.jsonl'), 'utf8');
|
|
131
|
+
const line = JSON.parse(text.trim().split('\n')[0]);
|
|
132
|
+
expect(line.model).toBe('m');
|
|
133
|
+
expect(line.path).toBe('/v1/messages');
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
//# sourceMappingURL=call-log.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"call-log.test.js","sourceRoot":"","sources":["../../src/ai-gateway/call-log.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"count-tokens.d.ts","sourceRoot":"","sources":["../../src/ai-gateway/count-tokens.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"count-tokens.d.ts","sourceRoot":"","sources":["../../src/ai-gateway/count-tokens.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAMrD,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAmCf;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAInD"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { sendUpstream } from './upstream/provider-client.js';
|
|
2
2
|
import { anthropicError } from './anthropic-errors.js';
|
|
3
3
|
import { trimTrailingSlash, UPSTREAM_HEADER_TIMEOUT_MS } from './passthrough.js';
|
|
4
|
+
import { patchCallLog, requestExcerptOf, responseExcerptOf } from './call-log.js';
|
|
4
5
|
export async function handleCountTokens(req, res, provider, model) {
|
|
5
6
|
if (provider.dialect === 'anthropic') {
|
|
6
7
|
const headers = {
|
|
@@ -17,10 +18,19 @@ export async function handleCountTokens(req, res, provider, model) {
|
|
|
17
18
|
body: JSON.stringify({ ...req.body, model }),
|
|
18
19
|
timeoutMs: UPSTREAM_HEADER_TIMEOUT_MS,
|
|
19
20
|
});
|
|
21
|
+
// body 只能读一次(fetch 语义),失败摘要与回传共用同一份
|
|
22
|
+
const text = await upstream.text().catch(() => '');
|
|
23
|
+
if (upstream.status !== 200) {
|
|
24
|
+
patchCallLog(res, { error: `upstream ${upstream.status}`, responseExcerpt: responseExcerptOf(text) });
|
|
25
|
+
}
|
|
20
26
|
res.status(upstream.status).set('content-type', upstream.headers['content-type'] || 'application/json')
|
|
21
|
-
.send(
|
|
27
|
+
.send(text);
|
|
22
28
|
}
|
|
23
29
|
catch (error) {
|
|
30
|
+
patchCallLog(res, {
|
|
31
|
+
error: `upstream count_tokens failed: ${error.message}`,
|
|
32
|
+
requestExcerpt: requestExcerptOf(req),
|
|
33
|
+
});
|
|
24
34
|
anthropicError(res, 502, 'api_error', `upstream count_tokens failed: ${error.message}`);
|
|
25
35
|
}
|
|
26
36
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"count-tokens.js","sourceRoot":"","sources":["../../src/ai-gateway/count-tokens.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAwB,MAAM,+BAA+B,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"count-tokens.js","sourceRoot":"","sources":["../../src/ai-gateway/count-tokens.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAwB,MAAM,+BAA+B,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAElF,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,GAAY,EACZ,GAAa,EACb,QAA0B,EAC1B,KAAa;IAEb,IAAI,QAAQ,CAAC,OAAO,KAAK,WAAW,EAAE,CAAC;QACrC,MAAM,OAAO,GAA2B;YACtC,cAAc,EAAE,kBAAkB;YAClC,WAAW,EAAE,QAAQ,CAAC,MAAM;YAC5B,mBAAmB,EAAG,GAAG,CAAC,OAAO,CAAC,mBAAmB,CAAY,IAAI,YAAY;YACjF,GAAG,CAAC,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC;SAC5B,CAAC;QACF,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC;gBAClC,GAAG,EAAE,iBAAiB,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,2BAA2B;gBACtE,MAAM,EAAE,MAAM;gBACd,OAAO;gBACP,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,GAAI,GAAG,CAAC,IAAe,EAAE,KAAK,EAAE,CAAC;gBACxD,SAAS,EAAE,0BAA0B;aACtC,CAAC,CAAC;YACH,oCAAoC;YACpC,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YACnD,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBAC5B,YAAY,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,YAAY,QAAQ,CAAC,MAAM,EAAE,EAAE,eAAe,EAAE,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACxG,CAAC;YACD,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,cAAc,EAAE,QAAQ,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,kBAAkB,CAAC;iBACpG,IAAI,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,YAAY,CAAC,GAAG,EAAE;gBAChB,KAAK,EAAE,iCAAkC,KAAe,CAAC,OAAO,EAAE;gBAClE,cAAc,EAAE,gBAAgB,CAAC,GAAG,CAAC;aACtC,CAAC,CAAC;YACH,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,WAAW,EAAE,iCAAkC,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;QACrG,CAAC;QACD,OAAO;IACT,CAAC;IACD,2CAA2C;IAC3C,MAAM,WAAW,GAAG,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC;IACnE,GAAG,CAAC,IAAI,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,4CAA4C,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;IACpF,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC;IAC/B,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,GAAG,IAAI,GAAG,GAAG,CAAC,CAAC;AACrC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialects.d.ts","sourceRoot":"","sources":["../../src/ai-gateway/dialects.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"dialects.d.ts","sourceRoot":"","sources":["../../src/ai-gateway/dialects.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAK/C,eAAO,MAAM,WAAW,EAAE,YAKzB,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,YAKzB,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,YAK5B,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { irToOpenAI, OpenAIStreamState, openaiSSELineToEvents, openaiFinalToIREvents } from './ir/openai-out.js';
|
|
2
2
|
import { irToGemini, GeminiStreamState, geminiSSELineToEvents, geminiFinalToIREvents } from './ir/gemini-out.js';
|
|
3
|
+
import { irToAnthropic, AnthropicStreamState, anthropicSSELineToEvents, anthropicFinalToIREvents } from './ir/anthropic-out.js';
|
|
3
4
|
export const openaiCodec = {
|
|
4
5
|
buildRequest: irToOpenAI,
|
|
5
6
|
parseSSELine: openaiSSELineToEvents,
|
|
@@ -12,4 +13,10 @@ export const geminiCodec = {
|
|
|
12
13
|
createState: () => new GeminiStreamState(),
|
|
13
14
|
parseFinal: geminiFinalToIREvents,
|
|
14
15
|
};
|
|
16
|
+
export const anthropicCodec = {
|
|
17
|
+
buildRequest: irToAnthropic,
|
|
18
|
+
parseSSELine: anthropicSSELineToEvents,
|
|
19
|
+
createState: () => new AnthropicStreamState(),
|
|
20
|
+
parseFinal: anthropicFinalToIREvents,
|
|
21
|
+
};
|
|
15
22
|
//# sourceMappingURL=dialects.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialects.js","sourceRoot":"","sources":["../../src/ai-gateway/dialects.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AACjH,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"dialects.js","sourceRoot":"","sources":["../../src/ai-gateway/dialects.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AACjH,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AACjH,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAEhI,MAAM,CAAC,MAAM,WAAW,GAAiB;IACvC,YAAY,EAAE,UAAU;IACxB,YAAY,EAAE,qBAAqB;IACnC,WAAW,EAAE,GAAG,EAAE,CAAC,IAAI,iBAAiB,EAAE;IAC1C,UAAU,EAAE,qBAAqB;CAClC,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAiB;IACvC,YAAY,EAAE,UAAU;IACxB,YAAY,EAAE,qBAAqB;IACnC,WAAW,EAAE,GAAG,EAAE,CAAC,IAAI,iBAAiB,EAAE;IAC1C,UAAU,EAAE,qBAAqB;CAClC,CAAC;AAEF,MAAM,CAAC,MAAM,cAAc,GAAiB;IAC1C,YAAY,EAAE,aAAa;IAC3B,YAAY,EAAE,wBAAwB;IACtC,WAAW,EAAE,GAAG,EAAE,CAAC,IAAI,oBAAoB,EAAE;IAC7C,UAAU,EAAE,wBAAwB;CACrC,CAAC"}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
+
import express from 'express';
|
|
1
2
|
import { ConfigManager } from '../config/config-manager.js';
|
|
3
|
+
export declare function createGatewayRouter(configManager: ConfigManager): express.Router;
|
|
2
4
|
export declare class AiGatewayServer {
|
|
3
5
|
private app;
|
|
4
6
|
private server?;
|
|
5
7
|
private configManager;
|
|
6
8
|
constructor(configManager: ConfigManager);
|
|
7
|
-
private setupRoutes;
|
|
8
|
-
private handleMessages;
|
|
9
|
-
private handleCountTokensRoute;
|
|
10
9
|
start(): Promise<void>;
|
|
11
10
|
stop(): Promise<void>;
|
|
12
11
|
isRunning(): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gateway-server.d.ts","sourceRoot":"","sources":["../../src/ai-gateway/gateway-server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"gateway-server.d.ts","sourceRoot":"","sources":["../../src/ai-gateway/gateway-server.ts"],"names":[],"mappings":"AAIA,OAAO,OAAO,MAAM,SAAS,CAAC;AAG9B,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAuH5D,wBAAgB,mBAAmB,CAAC,aAAa,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CA0EhF;AAGD,qBAAa,eAAe;IAC1B,OAAO,CAAC,GAAG,CAAsB;IACjC,OAAO,CAAC,MAAM,CAAC,CAAa;IAC5B,OAAO,CAAC,aAAa,CAAgB;gBAEzB,aAAa,EAAE,aAAa;IAMlC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAoBtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAS3B,SAAS,IAAI,OAAO;IAIpB,OAAO,IAAI,MAAM,GAAG,SAAS;CAG9B"}
|