chatccc 0.2.181 → 0.2.183
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 +69 -69
- package/package.json +1 -1
- package/src/__tests__/card-plain-text.test.ts +5 -5
- package/src/__tests__/cards.test.ts +77 -77
- package/src/__tests__/codex-reset-actions.test.ts +40 -7
- package/src/__tests__/feishu-avatar.test.ts +115 -115
- package/src/__tests__/feishu-platform.test.ts +22 -22
- package/src/__tests__/format-message.test.ts +316 -273
- package/src/__tests__/orchestrator.test.ts +117 -117
- package/src/__tests__/shared-prefix.test.ts +36 -36
- package/src/cards.ts +89 -89
- package/src/codex-reset-actions.ts +19 -6
- package/src/feishu-api.ts +184 -184
- package/src/feishu-platform.ts +20 -20
- package/src/format-message.ts +293 -214
- package/src/index.ts +2 -0
- package/src/orchestrator.ts +120 -120
- package/src/shared-prefix.ts +29 -29
- package/src/sim-platform.ts +20 -20
|
@@ -39,23 +39,23 @@ async function loadFeishuApiWithHome(homeDir: string, userDataDir: string) {
|
|
|
39
39
|
async function writeCodexAuth(homeDir: string): Promise<void> {
|
|
40
40
|
const codexDir = join(homeDir, ".codex");
|
|
41
41
|
await mkdir(codexDir, { recursive: true });
|
|
42
|
-
await writeFile(
|
|
43
|
-
join(codexDir, "auth.json"),
|
|
44
|
-
JSON.stringify({ tokens: { access_token: "codex-access-token", account_id: "codex-account-id" } }),
|
|
45
|
-
"utf-8",
|
|
46
|
-
);
|
|
47
|
-
}
|
|
42
|
+
await writeFile(
|
|
43
|
+
join(codexDir, "auth.json"),
|
|
44
|
+
JSON.stringify({ tokens: { access_token: "codex-access-token", account_id: "codex-account-id" } }),
|
|
45
|
+
"utf-8",
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
48
|
|
|
49
49
|
function mockAvatarFetch(uploadedNames: string[], usageResponse: Response): void {
|
|
50
50
|
vi.stubGlobal("fetch", vi.fn(async (url: string | URL | Request, init?: RequestInit) => {
|
|
51
51
|
const urlText = String(url);
|
|
52
|
-
if (urlText === "https://chatgpt.com/backend-api/wham/usage") {
|
|
53
|
-
return usageResponse.clone();
|
|
54
|
-
}
|
|
55
|
-
if (urlText === "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits") {
|
|
56
|
-
return new Response(JSON.stringify({ credits: [] }), { status: 200 });
|
|
57
|
-
}
|
|
58
|
-
if (urlText === "https://open.feishu.test/im/v1/images") {
|
|
52
|
+
if (urlText === "https://chatgpt.com/backend-api/wham/usage") {
|
|
53
|
+
return usageResponse.clone();
|
|
54
|
+
}
|
|
55
|
+
if (urlText === "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits") {
|
|
56
|
+
return new Response(JSON.stringify({ credits: [] }), { status: 200 });
|
|
57
|
+
}
|
|
58
|
+
if (urlText === "https://open.feishu.test/im/v1/images") {
|
|
59
59
|
const form = init?.body as FormData;
|
|
60
60
|
const image = form.get("image") as File;
|
|
61
61
|
uploadedNames.push(image.name);
|
|
@@ -103,109 +103,109 @@ describe("Codex avatar usage battery", () => {
|
|
|
103
103
|
}
|
|
104
104
|
});
|
|
105
105
|
|
|
106
|
-
it("returns both usage windows and available reset credit expiries", async () => {
|
|
107
|
-
const homeDir = await mkdtemp(join(tmpdir(), "chatccc-avatar-home-"));
|
|
108
|
-
const userDataDir = await mkdtemp(join(tmpdir(), "chatccc-avatar-data-"));
|
|
109
|
-
await writeCodexAuth(homeDir);
|
|
110
|
-
const fetchMock = vi.fn(async (url: string | URL | Request) => {
|
|
111
|
-
const urlText = String(url);
|
|
112
|
-
if (urlText === "https://chatgpt.com/backend-api/wham/usage") {
|
|
113
|
-
return new Response(JSON.stringify({
|
|
114
|
-
rate_limit: {
|
|
115
|
-
primary_window: { used_percent: 37, reset_after_seconds: 10349, reset_at: 1781528212 },
|
|
116
|
-
secondary_window: { used_percent: 12, reset_after_seconds: 325063, reset_at: 1781842926 },
|
|
117
|
-
},
|
|
118
|
-
rate_limit_reset_credits: { available_count: 1 },
|
|
119
|
-
}), { status: 200 });
|
|
120
|
-
}
|
|
121
|
-
if (urlText === "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits") {
|
|
122
|
-
return new Response(JSON.stringify({
|
|
123
|
-
available_count: 2,
|
|
124
|
-
credits: [
|
|
125
|
-
{
|
|
126
|
-
status: "available",
|
|
127
|
-
granted_at: "2026-06-12T04:01:47.770016Z",
|
|
128
|
-
expires_at: "2026-07-12T04:01:47.770016Z",
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
status: "redeemed",
|
|
132
|
-
granted_at: "2026-06-13T04:01:47.770016Z",
|
|
133
|
-
expires_at: "2026-07-13T04:01:47.770016Z",
|
|
134
|
-
},
|
|
135
|
-
{
|
|
136
|
-
status: "available",
|
|
137
|
-
granted_at: "2026-06-18T00:44:23.904386Z",
|
|
138
|
-
expires_at: "2026-07-18T00:44:23.904386Z",
|
|
139
|
-
},
|
|
140
|
-
],
|
|
141
|
-
}), { status: 200 });
|
|
142
|
-
}
|
|
143
|
-
throw new Error(`unexpected fetch: ${urlText}`);
|
|
144
|
-
});
|
|
145
|
-
vi.stubGlobal("fetch", fetchMock);
|
|
146
|
-
|
|
147
|
-
try {
|
|
148
|
-
const { getCodexUsageSummary } = await loadFeishuApiWithHome(homeDir, userDataDir);
|
|
149
|
-
await expect(getCodexUsageSummary()).resolves.toEqual({
|
|
150
|
-
fiveHour: { usedPercent: 37, remainingPercent: 63, resetAfterSeconds: 10349, resetAtEpochSeconds: 1781528212 },
|
|
151
|
-
weekly: { usedPercent: 12, remainingPercent: 88, resetAfterSeconds: 325063, resetAtEpochSeconds: 1781842926 },
|
|
152
|
-
rateLimitResetCreditsAvailable: 2,
|
|
153
|
-
rateLimitResetCredits: [
|
|
154
|
-
{ grantedAt: "2026-06-12T04:01:47.770016Z", expiresAt: "2026-07-12T04:01:47.770016Z" },
|
|
155
|
-
{ grantedAt: "2026-06-18T00:44:23.904386Z", expiresAt: "2026-07-18T00:44:23.904386Z" },
|
|
156
|
-
],
|
|
157
|
-
});
|
|
158
|
-
expect(fetchMock).toHaveBeenCalledWith(
|
|
159
|
-
"https://chatgpt.com/backend-api/wham/rate-limit-reset-credits",
|
|
160
|
-
expect.objectContaining({
|
|
161
|
-
headers: expect.objectContaining({
|
|
162
|
-
Authorization: "Bearer codex-access-token",
|
|
163
|
-
"ChatGPT-Account-ID": "codex-account-id",
|
|
164
|
-
"OpenAI-Beta": "codex-1",
|
|
165
|
-
originator: "Codex Desktop",
|
|
166
|
-
}),
|
|
167
|
-
}),
|
|
168
|
-
);
|
|
169
|
-
} finally {
|
|
170
|
-
await rm(homeDir, { recursive: true, force: true });
|
|
171
|
-
await rm(userDataDir, { recursive: true, force: true });
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
it("consumes a Codex rate-limit reset credit through the WHAM endpoint", async () => {
|
|
176
|
-
const homeDir = await mkdtemp(join(tmpdir(), "chatccc-avatar-home-"));
|
|
177
|
-
const userDataDir = await mkdtemp(join(tmpdir(), "chatccc-avatar-data-"));
|
|
178
|
-
await writeCodexAuth(homeDir);
|
|
179
|
-
const fetchMock = vi.fn(async () => new Response(JSON.stringify({
|
|
180
|
-
code: "reset",
|
|
181
|
-
windows_reset: 2,
|
|
182
|
-
}), { status: 200 }));
|
|
183
|
-
vi.stubGlobal("fetch", fetchMock);
|
|
184
|
-
|
|
185
|
-
try {
|
|
186
|
-
const { consumeCodexRateLimitResetCredit } = await loadFeishuApiWithHome(homeDir, userDataDir);
|
|
187
|
-
await expect(consumeCodexRateLimitResetCredit("request-1")).resolves.toEqual({
|
|
188
|
-
code: "reset",
|
|
189
|
-
windowsReset: 2,
|
|
190
|
-
});
|
|
191
|
-
expect(fetchMock).toHaveBeenCalledWith(
|
|
192
|
-
"https://chatgpt.com/backend-api/wham/rate-limit-reset-credits/consume",
|
|
193
|
-
expect.objectContaining({
|
|
194
|
-
method: "POST",
|
|
195
|
-
headers: expect.objectContaining({
|
|
196
|
-
Authorization: "Bearer codex-access-token",
|
|
197
|
-
"Content-Type": "application/json",
|
|
198
|
-
}),
|
|
199
|
-
body: JSON.stringify({ redeem_request_id: "request-1" }),
|
|
200
|
-
}),
|
|
201
|
-
);
|
|
202
|
-
} finally {
|
|
203
|
-
await rm(homeDir, { recursive: true, force: true });
|
|
204
|
-
await rm(userDataDir, { recursive: true, force: true });
|
|
106
|
+
it("returns both usage windows and available reset credit expiries", async () => {
|
|
107
|
+
const homeDir = await mkdtemp(join(tmpdir(), "chatccc-avatar-home-"));
|
|
108
|
+
const userDataDir = await mkdtemp(join(tmpdir(), "chatccc-avatar-data-"));
|
|
109
|
+
await writeCodexAuth(homeDir);
|
|
110
|
+
const fetchMock = vi.fn(async (url: string | URL | Request) => {
|
|
111
|
+
const urlText = String(url);
|
|
112
|
+
if (urlText === "https://chatgpt.com/backend-api/wham/usage") {
|
|
113
|
+
return new Response(JSON.stringify({
|
|
114
|
+
rate_limit: {
|
|
115
|
+
primary_window: { used_percent: 37, reset_after_seconds: 10349, reset_at: 1781528212 },
|
|
116
|
+
secondary_window: { used_percent: 12, reset_after_seconds: 325063, reset_at: 1781842926 },
|
|
117
|
+
},
|
|
118
|
+
rate_limit_reset_credits: { available_count: 1 },
|
|
119
|
+
}), { status: 200 });
|
|
120
|
+
}
|
|
121
|
+
if (urlText === "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits") {
|
|
122
|
+
return new Response(JSON.stringify({
|
|
123
|
+
available_count: 2,
|
|
124
|
+
credits: [
|
|
125
|
+
{
|
|
126
|
+
status: "available",
|
|
127
|
+
granted_at: "2026-06-12T04:01:47.770016Z",
|
|
128
|
+
expires_at: "2026-07-12T04:01:47.770016Z",
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
status: "redeemed",
|
|
132
|
+
granted_at: "2026-06-13T04:01:47.770016Z",
|
|
133
|
+
expires_at: "2026-07-13T04:01:47.770016Z",
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
status: "available",
|
|
137
|
+
granted_at: "2026-06-18T00:44:23.904386Z",
|
|
138
|
+
expires_at: "2026-07-18T00:44:23.904386Z",
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
}), { status: 200 });
|
|
142
|
+
}
|
|
143
|
+
throw new Error(`unexpected fetch: ${urlText}`);
|
|
144
|
+
});
|
|
145
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const { getCodexUsageSummary } = await loadFeishuApiWithHome(homeDir, userDataDir);
|
|
149
|
+
await expect(getCodexUsageSummary()).resolves.toEqual({
|
|
150
|
+
fiveHour: { usedPercent: 37, remainingPercent: 63, resetAfterSeconds: 10349, resetAtEpochSeconds: 1781528212 },
|
|
151
|
+
weekly: { usedPercent: 12, remainingPercent: 88, resetAfterSeconds: 325063, resetAtEpochSeconds: 1781842926 },
|
|
152
|
+
rateLimitResetCreditsAvailable: 2,
|
|
153
|
+
rateLimitResetCredits: [
|
|
154
|
+
{ grantedAt: "2026-06-12T04:01:47.770016Z", expiresAt: "2026-07-12T04:01:47.770016Z" },
|
|
155
|
+
{ grantedAt: "2026-06-18T00:44:23.904386Z", expiresAt: "2026-07-18T00:44:23.904386Z" },
|
|
156
|
+
],
|
|
157
|
+
});
|
|
158
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
159
|
+
"https://chatgpt.com/backend-api/wham/rate-limit-reset-credits",
|
|
160
|
+
expect.objectContaining({
|
|
161
|
+
headers: expect.objectContaining({
|
|
162
|
+
Authorization: "Bearer codex-access-token",
|
|
163
|
+
"ChatGPT-Account-ID": "codex-account-id",
|
|
164
|
+
"OpenAI-Beta": "codex-1",
|
|
165
|
+
originator: "Codex Desktop",
|
|
166
|
+
}),
|
|
167
|
+
}),
|
|
168
|
+
);
|
|
169
|
+
} finally {
|
|
170
|
+
await rm(homeDir, { recursive: true, force: true });
|
|
171
|
+
await rm(userDataDir, { recursive: true, force: true });
|
|
205
172
|
}
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
it("
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("consumes a Codex rate-limit reset credit through the WHAM endpoint", async () => {
|
|
176
|
+
const homeDir = await mkdtemp(join(tmpdir(), "chatccc-avatar-home-"));
|
|
177
|
+
const userDataDir = await mkdtemp(join(tmpdir(), "chatccc-avatar-data-"));
|
|
178
|
+
await writeCodexAuth(homeDir);
|
|
179
|
+
const fetchMock = vi.fn(async () => new Response(JSON.stringify({
|
|
180
|
+
code: "reset",
|
|
181
|
+
windows_reset: 2,
|
|
182
|
+
}), { status: 200 }));
|
|
183
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
const { consumeCodexRateLimitResetCredit } = await loadFeishuApiWithHome(homeDir, userDataDir);
|
|
187
|
+
await expect(consumeCodexRateLimitResetCredit("request-1")).resolves.toEqual({
|
|
188
|
+
code: "reset",
|
|
189
|
+
windowsReset: 2,
|
|
190
|
+
});
|
|
191
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
192
|
+
"https://chatgpt.com/backend-api/wham/rate-limit-reset-credits/consume",
|
|
193
|
+
expect.objectContaining({
|
|
194
|
+
method: "POST",
|
|
195
|
+
headers: expect.objectContaining({
|
|
196
|
+
Authorization: "Bearer codex-access-token",
|
|
197
|
+
"Content-Type": "application/json",
|
|
198
|
+
}),
|
|
199
|
+
body: JSON.stringify({ redeem_request_id: "request-1" }),
|
|
200
|
+
}),
|
|
201
|
+
);
|
|
202
|
+
} finally {
|
|
203
|
+
await rm(homeDir, { recursive: true, force: true });
|
|
204
|
+
await rm(userDataDir, { recursive: true, force: true });
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("falls back to the pre-combined Codex avatar when usage lookup fails", async () => {
|
|
209
209
|
const homeDir = await mkdtemp(join(tmpdir(), "chatccc-avatar-home-"));
|
|
210
210
|
const userDataDir = await mkdtemp(join(tmpdir(), "chatccc-avatar-data-"));
|
|
211
211
|
const uploadedNames: string[] = [];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
2
|
-
import { getPlatform, setPlatform, getTenantAccessToken, getChatInfo, createGroupChat, updateChatInfo, sendTextReply, sendCardReply, sendRawCard, sendPostMessage, extractSessionInfo, formatDelayNotice, reportPermissionResults, verifyAllPermissions, addReaction, recallMessage, updateCardMessage, setChatAvatar, getCodexUsageSummary, consumeCodexRateLimitResetCredit, disbandChat, sendRestartCard, getMergeForwardMessages } from "../feishu-platform.ts";
|
|
2
|
+
import { getPlatform, setPlatform, getTenantAccessToken, getChatInfo, createGroupChat, updateChatInfo, sendTextReply, sendCardReply, sendRawCard, sendPostMessage, extractSessionInfo, formatDelayNotice, reportPermissionResults, verifyAllPermissions, addReaction, recallMessage, updateCardMessage, setChatAvatar, getCodexUsageSummary, consumeCodexRateLimitResetCredit, disbandChat, sendRestartCard, getMergeForwardMessages } from "../feishu-platform.ts";
|
|
3
3
|
import type { FeishuPlatform } from "../feishu-platform.ts";
|
|
4
4
|
|
|
5
5
|
const realPlatform = getPlatform();
|
|
@@ -26,16 +26,16 @@ describe("feishu-platform", () => {
|
|
|
26
26
|
createGroupChat: async () => "mock_chat",
|
|
27
27
|
updateChatInfo: async () => {},
|
|
28
28
|
getChatInfo: async () => ({ name: "x", description: "y" }),
|
|
29
|
-
disbandChat: async () => {},
|
|
30
|
-
setChatAvatar: async () => {},
|
|
31
|
-
getCodexUsageSummary: async () => ({
|
|
32
|
-
fiveHour: { usedPercent: 10, remainingPercent: 90, resetAtEpochSeconds: 1781528212, resetAfterSeconds: 10349 },
|
|
33
|
-
weekly: { usedPercent: 20, remainingPercent: 80, resetAtEpochSeconds: 1781842926, resetAfterSeconds: 325063 },
|
|
34
|
-
rateLimitResetCreditsAvailable: 1,
|
|
35
|
-
rateLimitResetCredits: [{ grantedAt: null, expiresAt: "2026-07-12T04:01:47.770016Z" }],
|
|
36
|
-
}),
|
|
37
|
-
consumeCodexRateLimitResetCredit: async () => ({ code: "reset", windowsReset: 2 }),
|
|
38
|
-
getOrDownloadImage: async () => "/tmp/img.png",
|
|
29
|
+
disbandChat: async () => {},
|
|
30
|
+
setChatAvatar: async () => {},
|
|
31
|
+
getCodexUsageSummary: async () => ({
|
|
32
|
+
fiveHour: { usedPercent: 10, remainingPercent: 90, resetAtEpochSeconds: 1781528212, resetAfterSeconds: 10349 },
|
|
33
|
+
weekly: { usedPercent: 20, remainingPercent: 80, resetAtEpochSeconds: 1781842926, resetAfterSeconds: 325063 },
|
|
34
|
+
rateLimitResetCreditsAvailable: 1,
|
|
35
|
+
rateLimitResetCredits: [{ grantedAt: null, expiresAt: "2026-07-12T04:01:47.770016Z" }],
|
|
36
|
+
}),
|
|
37
|
+
consumeCodexRateLimitResetCredit: async () => ({ code: "reset", windowsReset: 2 }),
|
|
38
|
+
getOrDownloadImage: async () => "/tmp/img.png",
|
|
39
39
|
verifyAllPermissions: async () => [],
|
|
40
40
|
reportPermissionResults: realPlatform.reportPermissionResults,
|
|
41
41
|
extractSessionInfo: realPlatform.extractSessionInfo,
|
|
@@ -53,16 +53,16 @@ describe("feishu-platform", () => {
|
|
|
53
53
|
expect(await getChatInfo("t", "mock_chat")).toEqual({ name: "x", description: "y" });
|
|
54
54
|
const perms = await verifyAllPermissions("t");
|
|
55
55
|
expect(perms).toEqual([]);
|
|
56
|
-
const mergeMsgs = await getMergeForwardMessages("t", "om_test");
|
|
57
|
-
expect(mergeMsgs).toEqual([]);
|
|
58
|
-
expect(await getCodexUsageSummary()).toEqual({
|
|
59
|
-
fiveHour: { usedPercent: 10, remainingPercent: 90, resetAtEpochSeconds: 1781528212, resetAfterSeconds: 10349 },
|
|
60
|
-
weekly: { usedPercent: 20, remainingPercent: 80, resetAtEpochSeconds: 1781842926, resetAfterSeconds: 325063 },
|
|
61
|
-
rateLimitResetCreditsAvailable: 1,
|
|
62
|
-
rateLimitResetCredits: [{ grantedAt: null, expiresAt: "2026-07-12T04:01:47.770016Z" }],
|
|
63
|
-
});
|
|
64
|
-
expect(await consumeCodexRateLimitResetCredit("request-1")).toEqual({ code: "reset", windowsReset: 2 });
|
|
65
|
-
} finally {
|
|
56
|
+
const mergeMsgs = await getMergeForwardMessages("t", "om_test");
|
|
57
|
+
expect(mergeMsgs).toEqual([]);
|
|
58
|
+
expect(await getCodexUsageSummary()).toEqual({
|
|
59
|
+
fiveHour: { usedPercent: 10, remainingPercent: 90, resetAtEpochSeconds: 1781528212, resetAfterSeconds: 10349 },
|
|
60
|
+
weekly: { usedPercent: 20, remainingPercent: 80, resetAtEpochSeconds: 1781842926, resetAfterSeconds: 325063 },
|
|
61
|
+
rateLimitResetCreditsAvailable: 1,
|
|
62
|
+
rateLimitResetCredits: [{ grantedAt: null, expiresAt: "2026-07-12T04:01:47.770016Z" }],
|
|
63
|
+
});
|
|
64
|
+
expect(await consumeCodexRateLimitResetCredit("request-1")).toEqual({ code: "reset", windowsReset: 2 });
|
|
65
|
+
} finally {
|
|
66
66
|
// 恢复到真实实现,避免影响后续测试
|
|
67
67
|
setPlatform(realPlatform);
|
|
68
68
|
}
|
|
@@ -72,4 +72,4 @@ describe("feishu-platform", () => {
|
|
|
72
72
|
setPlatform(realPlatform);
|
|
73
73
|
expect(getPlatform()).toBe(realPlatform);
|
|
74
74
|
});
|
|
75
|
-
});
|
|
75
|
+
});
|