github-issue-tower-defence-management 1.59.0 → 1.60.1
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/.github/workflows/create-pr.yml +1 -1
- package/.github/workflows/test.yml +4 -0
- package/.github/workflows/umino-project.yml +3 -3
- package/CHANGELOG.md +14 -0
- package/bin/adapter/proxy/ClaudeMessageResponseParser.js +123 -0
- package/bin/adapter/proxy/ClaudeMessageResponseParser.js.map +1 -0
- package/bin/adapter/proxy/proxyEntry.js +16 -3
- package/bin/adapter/proxy/proxyEntry.js.map +1 -1
- package/bin/adapter/repositories/SqliteClaudeMessageResponseRepository.js +186 -0
- package/bin/adapter/repositories/SqliteClaudeMessageResponseRepository.js.map +1 -0
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +14 -0
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
- package/bin/domain/entities/ClaudeMessageResponse.js +3 -0
- package/bin/domain/entities/ClaudeMessageResponse.js.map +1 -0
- package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +18 -11
- package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
- package/bin/domain/usecases/adapter-interfaces/ClaudeMessageResponseRepository.js +3 -0
- package/bin/domain/usecases/adapter-interfaces/ClaudeMessageResponseRepository.js.map +1 -0
- package/package.json +3 -1
- package/src/adapter/proxy/ClaudeMessageResponseParser.test.ts +211 -0
- package/src/adapter/proxy/ClaudeMessageResponseParser.ts +180 -0
- package/src/adapter/proxy/proxyEntry.ts +28 -3
- package/src/adapter/repositories/SqliteClaudeMessageResponseRepository.test.ts +313 -0
- package/src/adapter/repositories/SqliteClaudeMessageResponseRepository.ts +164 -0
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +27 -0
- package/src/domain/entities/ClaudeMessageResponse.ts +31 -0
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +187 -39
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +37 -20
- package/src/domain/usecases/adapter-interfaces/ClaudeMessageResponseRepository.ts +5 -0
- package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +5 -0
- package/types/adapter/proxy/ClaudeMessageResponseParser.d.ts +4 -0
- package/types/adapter/proxy/ClaudeMessageResponseParser.d.ts.map +1 -0
- package/types/adapter/proxy/proxyEntry.d.ts +2 -1
- package/types/adapter/proxy/proxyEntry.d.ts.map +1 -1
- package/types/adapter/repositories/SqliteClaudeMessageResponseRepository.d.ts +10 -0
- package/types/adapter/repositories/SqliteClaudeMessageResponseRepository.d.ts.map +1 -0
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +1 -0
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
- package/types/domain/entities/ClaudeMessageResponse.d.ts +32 -0
- package/types/domain/entities/ClaudeMessageResponse.d.ts.map +1 -0
- package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts +3 -2
- package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/ClaudeMessageResponseRepository.d.ts +5 -0
- package/types/domain/usecases/adapter-interfaces/ClaudeMessageResponseRepository.d.ts.map +1 -0
- package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +1 -0
- package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -1
|
@@ -77,7 +77,7 @@ class NotifyFinishedIssuePreparationUseCase {
|
|
|
77
77
|
return;
|
|
78
78
|
}
|
|
79
79
|
const comments = await this.issueCommentRepository.getCommentsFromIssue(issue);
|
|
80
|
-
const { rejections
|
|
80
|
+
const { rejections } = await this.collectRejections(issue, comments);
|
|
81
81
|
const rejectionStatusMessage = rejections.length > 0
|
|
82
82
|
? `Auto Status Check: REJECTED\n${rejections.map((r) => `- ${r.detail}`).join('\n')}`
|
|
83
83
|
: 'Auto Status Check: APPROVED';
|
|
@@ -92,9 +92,7 @@ class NotifyFinishedIssuePreparationUseCase {
|
|
|
92
92
|
const escalationStatusLine = rejections.length > 0
|
|
93
93
|
? rejectionStatusMessage
|
|
94
94
|
: 'Auto Status Check: APPROVED (escalated due to prior failures)';
|
|
95
|
-
|
|
96
|
-
await this.setPrNextActionDate(approvedPrUrl, project);
|
|
97
|
-
}
|
|
95
|
+
await this.setDependedIssueUrlForAllOpenPRs(issue, params.issueUrl, project);
|
|
98
96
|
await this.issueCommentRepository.createComment(issue, `${escalationStatusLine}\n\nFailed to pass the check automatically for ${params.thresholdForAutoReject} times`);
|
|
99
97
|
await this.sendWorkflowBlockerNotification(params.issueUrl, params.workflowBlockerResolvedWebhookUrl, project);
|
|
100
98
|
return;
|
|
@@ -103,15 +101,14 @@ class NotifyFinishedIssuePreparationUseCase {
|
|
|
103
101
|
issue.status = WorkflowStatus_1.AWAITING_QUALITY_CHECK_STATUS_NAME;
|
|
104
102
|
await this.issueRepository.update(issue, project);
|
|
105
103
|
await this.issueRepository.updateStatus(project, issue, awaitingQualityCheckStatusOption.id);
|
|
106
|
-
|
|
107
|
-
await this.setPrNextActionDate(approvedPrUrl, project);
|
|
108
|
-
}
|
|
104
|
+
await this.setDependedIssueUrlForAllOpenPRs(issue, params.issueUrl, project);
|
|
109
105
|
await this.sendWorkflowBlockerNotification(params.issueUrl, params.workflowBlockerResolvedWebhookUrl, project);
|
|
110
106
|
return;
|
|
111
107
|
}
|
|
112
108
|
issue.status = WorkflowStatus_1.AWAITING_WORKSPACE_STATUS_NAME;
|
|
113
109
|
await this.issueRepository.update(issue, project);
|
|
114
110
|
await this.issueRepository.updateStatus(project, issue, awaitingWorkspaceStatusOption.id);
|
|
111
|
+
await this.setDependedIssueUrlForAllOpenPRs(issue, params.issueUrl, project);
|
|
115
112
|
await this.issueCommentRepository.createComment(issue, rejectionStatusMessage);
|
|
116
113
|
};
|
|
117
114
|
this.collectRejections = async (issue, comments) => {
|
|
@@ -154,10 +151,20 @@ class NotifyFinishedIssuePreparationUseCase {
|
|
|
154
151
|
const nextStepValue = Reflect.get(reportJson, 'nextStep');
|
|
155
152
|
return nextStepValue !== null && nextStepValue !== undefined;
|
|
156
153
|
};
|
|
157
|
-
this.
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
154
|
+
this.setDependedIssueUrlForAllOpenPRs = async (issue, issueUrl, project) => {
|
|
155
|
+
const openPRs = issue.isPr
|
|
156
|
+
? await this.resolveOpenPrsForPrItem(issue.url)
|
|
157
|
+
: await this.issueRepository.findRelatedOpenPRs(issue.url);
|
|
158
|
+
for (const pr of openPRs) {
|
|
159
|
+
await this.issueRepository.setDependedIssueUrl(pr.url, project, issueUrl);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
this.resolveOpenPrsForPrItem = async (prUrl) => {
|
|
163
|
+
const pr = await this.issueRepository.getOpenPullRequest(prUrl);
|
|
164
|
+
if (pr === null) {
|
|
165
|
+
return [];
|
|
166
|
+
}
|
|
167
|
+
return [pr];
|
|
161
168
|
};
|
|
162
169
|
this.sendWorkflowBlockerNotification = async (issueUrl, webhookUrlTemplate, project) => {
|
|
163
170
|
if (webhookUrlTemplate === null) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NotifyFinishedIssuePreparationUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"NotifyFinishedIssuePreparationUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClaudeMessageResponseRepository.js","sourceRoot":"","sources":["../../../../src/domain/usecases/adapter-interfaces/ClaudeMessageResponseRepository.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "github-issue-tower-defence-management",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.60.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "bin/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@semantic-release/changelog": "^6.0.3",
|
|
52
52
|
"@semantic-release/commit-analyzer": "^13.0.0",
|
|
53
|
+
"@types/better-sqlite3": "7.6.13",
|
|
53
54
|
"@types/cookie": "^1.0.0",
|
|
54
55
|
"@types/jest": "^29.5.12",
|
|
55
56
|
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
|
@@ -72,6 +73,7 @@
|
|
|
72
73
|
"typescript": "^6.0.0"
|
|
73
74
|
},
|
|
74
75
|
"dependencies": {
|
|
76
|
+
"better-sqlite3": "12.10.0",
|
|
75
77
|
"cheerio": "^1.0.0",
|
|
76
78
|
"commander": "^14.0.0",
|
|
77
79
|
"cookie": "^1.0.1",
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { parseClaudeMessageResponse } from './ClaudeMessageResponseParser';
|
|
2
|
+
|
|
3
|
+
const SUCCESS_BODY = JSON.stringify({
|
|
4
|
+
id: 'msg_01XFDUDYJgAACTvykiHMn8xy',
|
|
5
|
+
type: 'message',
|
|
6
|
+
role: 'assistant',
|
|
7
|
+
model: 'claude-sonnet-4-5',
|
|
8
|
+
stop_reason: 'end_turn',
|
|
9
|
+
stop_sequence: null,
|
|
10
|
+
usage: {
|
|
11
|
+
input_tokens: 100,
|
|
12
|
+
output_tokens: 50,
|
|
13
|
+
cache_creation_input_tokens: null,
|
|
14
|
+
cache_read_input_tokens: null,
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const ERROR_BODY = JSON.stringify({
|
|
19
|
+
type: 'error',
|
|
20
|
+
error: {
|
|
21
|
+
type: 'rate_limit_error',
|
|
22
|
+
message: 'Too many requests',
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const SUCCESS_HEADERS = {
|
|
27
|
+
'x-request-id': 'req_01234',
|
|
28
|
+
'anthropic-ratelimit-unified-status': 'active',
|
|
29
|
+
'anthropic-ratelimit-unified-5h-status': 'active',
|
|
30
|
+
'anthropic-ratelimit-unified-5h-utilization': '42.5',
|
|
31
|
+
'anthropic-ratelimit-unified-5h-reset': '1705316200',
|
|
32
|
+
'anthropic-ratelimit-unified-7d-status': 'active',
|
|
33
|
+
'anthropic-ratelimit-unified-7d-utilization': '10.0',
|
|
34
|
+
'anthropic-ratelimit-unified-7d-reset': '1705920000',
|
|
35
|
+
'anthropic-organization-id': 'org-abc123',
|
|
36
|
+
'anthropic-service-tier': 'standard',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
describe('parseClaudeMessageResponse', () => {
|
|
40
|
+
it('parses a successful 200 response with all fields', () => {
|
|
41
|
+
const result = parseClaudeMessageResponse(
|
|
42
|
+
'hashed-token',
|
|
43
|
+
200,
|
|
44
|
+
SUCCESS_HEADERS,
|
|
45
|
+
SUCCESS_BODY,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
expect(result.tokenName).toBe('hashed-token');
|
|
49
|
+
expect(result.httpStatus).toBe(200);
|
|
50
|
+
expect(result.externalClaudeMessageId).toBe('msg_01XFDUDYJgAACTvykiHMn8xy');
|
|
51
|
+
expect(result.externalClaudeRequestId).toBe('req_01234');
|
|
52
|
+
expect(result.model).toBe('claude-sonnet-4-5');
|
|
53
|
+
expect(result.role).toBe('assistant');
|
|
54
|
+
expect(result.stopReason).toBe('end_turn');
|
|
55
|
+
expect(result.stopSequence).toBeNull();
|
|
56
|
+
expect(result.inputTokens).toBe(100);
|
|
57
|
+
expect(result.outputTokens).toBe(50);
|
|
58
|
+
expect(result.cacheCreationInputTokens).toBeNull();
|
|
59
|
+
expect(result.cacheReadInputTokens).toBeNull();
|
|
60
|
+
expect(result.serviceTier).toBe('standard');
|
|
61
|
+
expect(result.errorType).toBeNull();
|
|
62
|
+
expect(result.errorMessage).toBeNull();
|
|
63
|
+
expect(result.anthropicRatelimitUnifiedStatus).toBe('active');
|
|
64
|
+
expect(result.anthropicRatelimitUnified5hStatus).toBe('active');
|
|
65
|
+
expect(result.anthropicRatelimitUnified5hUtilization).toBe(42.5);
|
|
66
|
+
expect(result.anthropicRatelimitUnified5hReset).toBe(1705316200);
|
|
67
|
+
expect(result.anthropicRatelimitUnified7dStatus).toBe('active');
|
|
68
|
+
expect(result.anthropicRatelimitUnified7dUtilization).toBe(10.0);
|
|
69
|
+
expect(result.anthropicRatelimitUnified7dReset).toBe(1705920000);
|
|
70
|
+
expect(result.retryAfter).toBeNull();
|
|
71
|
+
expect(result.anthropicOrganizationId).toBe('org-abc123');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('assigns a non-empty ULID as id', () => {
|
|
75
|
+
const result = parseClaudeMessageResponse('tok', 200, {}, '{}');
|
|
76
|
+
expect(result.id).toHaveLength(26);
|
|
77
|
+
expect(/^[0-9A-HJKMNP-TV-Z]{26}$/.test(result.id)).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('sets observedAt to a recent Date', () => {
|
|
81
|
+
const before = Date.now();
|
|
82
|
+
const result = parseClaudeMessageResponse('tok', 200, {}, '{}');
|
|
83
|
+
const after = Date.now();
|
|
84
|
+
expect(result.observedAt.getTime()).toBeGreaterThanOrEqual(before);
|
|
85
|
+
expect(result.observedAt.getTime()).toBeLessThanOrEqual(after);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('parses an error response body', () => {
|
|
89
|
+
const result = parseClaudeMessageResponse(
|
|
90
|
+
'hashed-token',
|
|
91
|
+
429,
|
|
92
|
+
{ 'retry-after': '30' },
|
|
93
|
+
ERROR_BODY,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
expect(result.httpStatus).toBe(429);
|
|
97
|
+
expect(result.errorType).toBe('rate_limit_error');
|
|
98
|
+
expect(result.errorMessage).toBe('Too many requests');
|
|
99
|
+
expect(result.retryAfter).toBe(30);
|
|
100
|
+
expect(result.model).toBeNull();
|
|
101
|
+
expect(result.role).toBeNull();
|
|
102
|
+
expect(result.stopReason).toBeNull();
|
|
103
|
+
expect(result.inputTokens).toBeNull();
|
|
104
|
+
expect(result.outputTokens).toBeNull();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('returns null for missing body fields when body is empty object', () => {
|
|
108
|
+
const result = parseClaudeMessageResponse('tok', 200, {}, '{}');
|
|
109
|
+
|
|
110
|
+
expect(result.externalClaudeMessageId).toBeNull();
|
|
111
|
+
expect(result.model).toBeNull();
|
|
112
|
+
expect(result.role).toBeNull();
|
|
113
|
+
expect(result.stopReason).toBeNull();
|
|
114
|
+
expect(result.stopSequence).toBeNull();
|
|
115
|
+
expect(result.inputTokens).toBeNull();
|
|
116
|
+
expect(result.outputTokens).toBeNull();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('returns null for rate limit headers when they are absent', () => {
|
|
120
|
+
const result = parseClaudeMessageResponse('tok', 200, {}, '{}');
|
|
121
|
+
|
|
122
|
+
expect(result.anthropicRatelimitUnifiedStatus).toBeNull();
|
|
123
|
+
expect(result.anthropicRatelimitUnified5hStatus).toBeNull();
|
|
124
|
+
expect(result.anthropicRatelimitUnified5hUtilization).toBeNull();
|
|
125
|
+
expect(result.anthropicRatelimitUnified5hReset).toBeNull();
|
|
126
|
+
expect(result.anthropicOrganizationId).toBeNull();
|
|
127
|
+
expect(result.retryAfter).toBeNull();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('handles non-JSON body gracefully by leaving body fields null', () => {
|
|
131
|
+
const result = parseClaudeMessageResponse(
|
|
132
|
+
'tok',
|
|
133
|
+
200,
|
|
134
|
+
{},
|
|
135
|
+
'this is not json',
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
expect(result.externalClaudeMessageId).toBeNull();
|
|
139
|
+
expect(result.model).toBeNull();
|
|
140
|
+
expect(result.inputTokens).toBeNull();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('handles empty body string gracefully', () => {
|
|
144
|
+
const result = parseClaudeMessageResponse('tok', 200, {}, '');
|
|
145
|
+
|
|
146
|
+
expect(result.externalClaudeMessageId).toBeNull();
|
|
147
|
+
expect(result.model).toBeNull();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('picks the first value when a header is an array', () => {
|
|
151
|
+
const result = parseClaudeMessageResponse(
|
|
152
|
+
'tok',
|
|
153
|
+
200,
|
|
154
|
+
{ 'x-request-id': ['req-first', 'req-second'] },
|
|
155
|
+
'{}',
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
expect(result.externalClaudeRequestId).toBe('req-first');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('parses cache token fields from usage', () => {
|
|
162
|
+
const body = JSON.stringify({
|
|
163
|
+
id: 'msg_cache',
|
|
164
|
+
role: 'assistant',
|
|
165
|
+
model: 'claude-sonnet-4-5',
|
|
166
|
+
stop_reason: 'end_turn',
|
|
167
|
+
usage: {
|
|
168
|
+
input_tokens: 200,
|
|
169
|
+
output_tokens: 80,
|
|
170
|
+
cache_creation_input_tokens: 150,
|
|
171
|
+
cache_read_input_tokens: 50,
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const result = parseClaudeMessageResponse('tok', 200, {}, body);
|
|
176
|
+
|
|
177
|
+
expect(result.cacheCreationInputTokens).toBe(150);
|
|
178
|
+
expect(result.cacheReadInputTokens).toBe(50);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('parses ephemeral token fields from usage', () => {
|
|
182
|
+
const body = JSON.stringify({
|
|
183
|
+
id: 'msg_ephemeral',
|
|
184
|
+
role: 'assistant',
|
|
185
|
+
model: 'claude-sonnet-4-5',
|
|
186
|
+
stop_reason: 'end_turn',
|
|
187
|
+
usage: {
|
|
188
|
+
input_tokens: 300,
|
|
189
|
+
output_tokens: 120,
|
|
190
|
+
ephemeral_5m_input_tokens: 100,
|
|
191
|
+
ephemeral_1h_input_tokens: 200,
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const result = parseClaudeMessageResponse('tok', 200, {}, body);
|
|
196
|
+
|
|
197
|
+
expect(result.ephemeral5mInputTokens).toBe(100);
|
|
198
|
+
expect(result.ephemeral1hInputTokens).toBe(200);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('parses a non-finite utilization header as null', () => {
|
|
202
|
+
const result = parseClaudeMessageResponse(
|
|
203
|
+
'tok',
|
|
204
|
+
200,
|
|
205
|
+
{ 'anthropic-ratelimit-unified-5h-utilization': 'not-a-number' },
|
|
206
|
+
'{}',
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
expect(result.anthropicRatelimitUnified5hUtilization).toBeNull();
|
|
210
|
+
});
|
|
211
|
+
});
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import * as http from 'http';
|
|
2
|
+
import { ClaudeMessageResponse } from '../../domain/entities/ClaudeMessageResponse';
|
|
3
|
+
import { generateUlid } from '../repositories/SqliteClaudeMessageResponseRepository';
|
|
4
|
+
|
|
5
|
+
const pickHeader = (
|
|
6
|
+
headers: http.IncomingHttpHeaders,
|
|
7
|
+
key: string,
|
|
8
|
+
): string | null => {
|
|
9
|
+
const value = headers[key];
|
|
10
|
+
if (Array.isArray(value)) return value[0] ?? null;
|
|
11
|
+
return value ?? null;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const parseNullableFloat = (value: string | null): number | null => {
|
|
15
|
+
if (value === null) return null;
|
|
16
|
+
const parsed = parseFloat(value);
|
|
17
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const parseNullableInt = (value: unknown): number | null => {
|
|
21
|
+
if (value === null || value === undefined) return null;
|
|
22
|
+
const parsed =
|
|
23
|
+
typeof value === 'number' ? value : parseInt(String(value), 10);
|
|
24
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
28
|
+
value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
29
|
+
|
|
30
|
+
const extractUsage = (
|
|
31
|
+
body: Record<string, unknown>,
|
|
32
|
+
): {
|
|
33
|
+
inputTokens: number | null;
|
|
34
|
+
outputTokens: number | null;
|
|
35
|
+
cacheCreationInputTokens: number | null;
|
|
36
|
+
cacheReadInputTokens: number | null;
|
|
37
|
+
ephemeral5mInputTokens: number | null;
|
|
38
|
+
ephemeral1hInputTokens: number | null;
|
|
39
|
+
} => {
|
|
40
|
+
const usage = body['usage'];
|
|
41
|
+
if (!isRecord(usage)) {
|
|
42
|
+
return {
|
|
43
|
+
inputTokens: null,
|
|
44
|
+
outputTokens: null,
|
|
45
|
+
cacheCreationInputTokens: null,
|
|
46
|
+
cacheReadInputTokens: null,
|
|
47
|
+
ephemeral5mInputTokens: null,
|
|
48
|
+
ephemeral1hInputTokens: null,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
inputTokens: parseNullableInt(usage['input_tokens']),
|
|
53
|
+
outputTokens: parseNullableInt(usage['output_tokens']),
|
|
54
|
+
cacheCreationInputTokens: parseNullableInt(
|
|
55
|
+
usage['cache_creation_input_tokens'],
|
|
56
|
+
),
|
|
57
|
+
cacheReadInputTokens: parseNullableInt(usage['cache_read_input_tokens']),
|
|
58
|
+
ephemeral5mInputTokens: parseNullableInt(
|
|
59
|
+
usage['ephemeral_5m_input_tokens'],
|
|
60
|
+
),
|
|
61
|
+
ephemeral1hInputTokens: parseNullableInt(
|
|
62
|
+
usage['ephemeral_1h_input_tokens'],
|
|
63
|
+
),
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const extractRole = (body: Record<string, unknown>): string | null => {
|
|
68
|
+
const role = body['role'];
|
|
69
|
+
return typeof role === 'string' ? role : null;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const extractFirstContentRole = (
|
|
73
|
+
body: Record<string, unknown>,
|
|
74
|
+
): string | null => {
|
|
75
|
+
const content = body['content'];
|
|
76
|
+
if (!Array.isArray(content)) return null;
|
|
77
|
+
const first: unknown = content[0];
|
|
78
|
+
if (!isRecord(first)) return null;
|
|
79
|
+
const role = first['role'];
|
|
80
|
+
return typeof role === 'string' ? role : null;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const parseClaudeMessageResponse = (
|
|
84
|
+
tokenName: string,
|
|
85
|
+
httpStatus: number,
|
|
86
|
+
headers: http.IncomingHttpHeaders,
|
|
87
|
+
body: string,
|
|
88
|
+
): ClaudeMessageResponse => {
|
|
89
|
+
let parsedBody: Record<string, unknown> = {};
|
|
90
|
+
try {
|
|
91
|
+
const parsed: unknown = JSON.parse(body);
|
|
92
|
+
if (isRecord(parsed)) {
|
|
93
|
+
parsedBody = parsed;
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
parsedBody = {};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const errorObj = parsedBody['error'];
|
|
100
|
+
const errorRecord = isRecord(errorObj) ? errorObj : null;
|
|
101
|
+
const errorType =
|
|
102
|
+
errorRecord !== null && typeof errorRecord['type'] === 'string'
|
|
103
|
+
? errorRecord['type']
|
|
104
|
+
: null;
|
|
105
|
+
const errorMessage =
|
|
106
|
+
errorRecord !== null && typeof errorRecord['message'] === 'string'
|
|
107
|
+
? errorRecord['message']
|
|
108
|
+
: null;
|
|
109
|
+
|
|
110
|
+
const externalClaudeMessageId =
|
|
111
|
+
typeof parsedBody['id'] === 'string' ? parsedBody['id'] : null;
|
|
112
|
+
|
|
113
|
+
const model =
|
|
114
|
+
typeof parsedBody['model'] === 'string' ? parsedBody['model'] : null;
|
|
115
|
+
|
|
116
|
+
const role = extractRole(parsedBody) ?? extractFirstContentRole(parsedBody);
|
|
117
|
+
|
|
118
|
+
const stopReason =
|
|
119
|
+
typeof parsedBody['stop_reason'] === 'string'
|
|
120
|
+
? parsedBody['stop_reason']
|
|
121
|
+
: null;
|
|
122
|
+
const stopSequence =
|
|
123
|
+
typeof parsedBody['stop_sequence'] === 'string'
|
|
124
|
+
? parsedBody['stop_sequence']
|
|
125
|
+
: null;
|
|
126
|
+
|
|
127
|
+
const usage = extractUsage(parsedBody);
|
|
128
|
+
|
|
129
|
+
const retryAfterRaw = pickHeader(headers, 'retry-after');
|
|
130
|
+
const retryAfter = parseNullableFloat(retryAfterRaw);
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
id: generateUlid(),
|
|
134
|
+
observedAt: new Date(),
|
|
135
|
+
tokenName,
|
|
136
|
+
externalClaudeMessageId,
|
|
137
|
+
externalClaudeRequestId: pickHeader(headers, 'x-request-id'),
|
|
138
|
+
httpStatus,
|
|
139
|
+
model,
|
|
140
|
+
role,
|
|
141
|
+
stopReason,
|
|
142
|
+
stopSequence,
|
|
143
|
+
inputTokens: usage.inputTokens,
|
|
144
|
+
outputTokens: usage.outputTokens,
|
|
145
|
+
cacheCreationInputTokens: usage.cacheCreationInputTokens,
|
|
146
|
+
cacheReadInputTokens: usage.cacheReadInputTokens,
|
|
147
|
+
ephemeral5mInputTokens: usage.ephemeral5mInputTokens,
|
|
148
|
+
ephemeral1hInputTokens: usage.ephemeral1hInputTokens,
|
|
149
|
+
serviceTier: pickHeader(headers, 'anthropic-service-tier'),
|
|
150
|
+
inferenceGeo: pickHeader(headers, 'anthropic-inference-geo'),
|
|
151
|
+
errorType,
|
|
152
|
+
errorMessage,
|
|
153
|
+
anthropicRatelimitUnifiedStatus: pickHeader(
|
|
154
|
+
headers,
|
|
155
|
+
'anthropic-ratelimit-unified-status',
|
|
156
|
+
),
|
|
157
|
+
anthropicRatelimitUnified5hStatus: pickHeader(
|
|
158
|
+
headers,
|
|
159
|
+
'anthropic-ratelimit-unified-5h-status',
|
|
160
|
+
),
|
|
161
|
+
anthropicRatelimitUnified5hUtilization: parseNullableFloat(
|
|
162
|
+
pickHeader(headers, 'anthropic-ratelimit-unified-5h-utilization'),
|
|
163
|
+
),
|
|
164
|
+
anthropicRatelimitUnified5hReset: parseNullableFloat(
|
|
165
|
+
pickHeader(headers, 'anthropic-ratelimit-unified-5h-reset'),
|
|
166
|
+
),
|
|
167
|
+
anthropicRatelimitUnified7dStatus: pickHeader(
|
|
168
|
+
headers,
|
|
169
|
+
'anthropic-ratelimit-unified-7d-status',
|
|
170
|
+
),
|
|
171
|
+
anthropicRatelimitUnified7dUtilization: parseNullableFloat(
|
|
172
|
+
pickHeader(headers, 'anthropic-ratelimit-unified-7d-utilization'),
|
|
173
|
+
),
|
|
174
|
+
anthropicRatelimitUnified7dReset: parseNullableFloat(
|
|
175
|
+
pickHeader(headers, 'anthropic-ratelimit-unified-7d-reset'),
|
|
176
|
+
),
|
|
177
|
+
retryAfter,
|
|
178
|
+
anthropicOrganizationId: pickHeader(headers, 'anthropic-organization-id'),
|
|
179
|
+
};
|
|
180
|
+
};
|
|
@@ -2,10 +2,14 @@ import * as http from 'http';
|
|
|
2
2
|
import * as https from 'https';
|
|
3
3
|
import {
|
|
4
4
|
PROXY_PORT,
|
|
5
|
+
hashToken,
|
|
5
6
|
parseModelRateLimitsFromBody,
|
|
6
7
|
writeModelRateLimit,
|
|
7
8
|
writeRateLimit,
|
|
8
9
|
} from './RateLimitCache';
|
|
10
|
+
import { ClaudeMessageResponseRepository } from '../../domain/usecases/adapter-interfaces/ClaudeMessageResponseRepository';
|
|
11
|
+
import { parseClaudeMessageResponse } from './ClaudeMessageResponseParser';
|
|
12
|
+
import { SqliteClaudeMessageResponseRepository } from '../repositories/SqliteClaudeMessageResponseRepository';
|
|
9
13
|
|
|
10
14
|
const UPSTREAM_HOST = 'api.anthropic.com';
|
|
11
15
|
|
|
@@ -25,7 +29,10 @@ const extractToken = (
|
|
|
25
29
|
return token.length > 0 ? token : null;
|
|
26
30
|
};
|
|
27
31
|
|
|
28
|
-
const startProxy = (
|
|
32
|
+
const startProxy = (
|
|
33
|
+
port: number,
|
|
34
|
+
claudeMessageResponseRepository: ClaudeMessageResponseRepository | null = null,
|
|
35
|
+
): void => {
|
|
29
36
|
const server = http.createServer((clientRequest, clientResponse) => {
|
|
30
37
|
const token = extractToken(clientRequest.headers['authorization']);
|
|
31
38
|
const upstreamHeaders: Record<string, string | string[] | undefined> = {
|
|
@@ -55,13 +62,29 @@ const startProxy = (port: number): void => {
|
|
|
55
62
|
inspectedBytes += chunk.length;
|
|
56
63
|
});
|
|
57
64
|
upstreamResponse.on('end', () => {
|
|
65
|
+
const body = Buffer.concat(inspectedChunks).toString('utf8');
|
|
58
66
|
try {
|
|
59
|
-
const body = Buffer.concat(inspectedChunks).toString('utf8');
|
|
60
67
|
const limits = parseModelRateLimitsFromBody(body);
|
|
61
68
|
writeModelRateLimit(token, limits);
|
|
62
69
|
} catch (error) {
|
|
63
70
|
console.error('Failed to write model rate limit cache:', error);
|
|
64
71
|
}
|
|
72
|
+
if (claudeMessageResponseRepository !== null) {
|
|
73
|
+
try {
|
|
74
|
+
const response = parseClaudeMessageResponse(
|
|
75
|
+
hashToken(token),
|
|
76
|
+
upstreamResponse.statusCode ?? 0,
|
|
77
|
+
upstreamResponse.headers,
|
|
78
|
+
body,
|
|
79
|
+
);
|
|
80
|
+
claudeMessageResponseRepository.append(response);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error(
|
|
83
|
+
'Failed to record Claude message response:',
|
|
84
|
+
error,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
65
88
|
});
|
|
66
89
|
}
|
|
67
90
|
clientResponse.writeHead(
|
|
@@ -86,7 +109,9 @@ const startProxy = (port: number): void => {
|
|
|
86
109
|
};
|
|
87
110
|
|
|
88
111
|
if (require.main === module) {
|
|
89
|
-
|
|
112
|
+
const dbPath = './db/claude_message_response.db';
|
|
113
|
+
const repository = new SqliteClaudeMessageResponseRepository(dbPath);
|
|
114
|
+
startProxy(PROXY_PORT, repository);
|
|
90
115
|
}
|
|
91
116
|
|
|
92
117
|
export { startProxy, extractToken };
|