codeksei 0.1.0 → 0.1.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.
Files changed (68) hide show
  1. package/LICENSE +661 -661
  2. package/README.en.md +109 -47
  3. package/README.md +79 -58
  4. package/bin/cyberboss.js +1 -1
  5. package/package.json +86 -86
  6. package/scripts/open_shared_wechat_thread.sh +77 -77
  7. package/scripts/open_wechat_thread.sh +108 -108
  8. package/scripts/shared-common.js +144 -144
  9. package/scripts/shared-open.js +14 -14
  10. package/scripts/shared-start.js +5 -5
  11. package/scripts/shared-status.js +27 -27
  12. package/scripts/show_shared_status.sh +45 -45
  13. package/scripts/start_shared_app_server.sh +52 -52
  14. package/scripts/start_shared_wechat.sh +94 -94
  15. package/scripts/timeline-screenshot.sh +14 -14
  16. package/src/adapters/channel/weixin/account-store.js +99 -99
  17. package/src/adapters/channel/weixin/api-v2.js +50 -50
  18. package/src/adapters/channel/weixin/api.js +169 -169
  19. package/src/adapters/channel/weixin/context-token-store.js +84 -84
  20. package/src/adapters/channel/weixin/index.js +618 -604
  21. package/src/adapters/channel/weixin/legacy.js +579 -566
  22. package/src/adapters/channel/weixin/media-mime.js +22 -22
  23. package/src/adapters/channel/weixin/media-receive.js +370 -370
  24. package/src/adapters/channel/weixin/media-send.js +102 -102
  25. package/src/adapters/channel/weixin/message-utils-v2.js +282 -282
  26. package/src/adapters/channel/weixin/message-utils.js +199 -199
  27. package/src/adapters/channel/weixin/redact.js +41 -41
  28. package/src/adapters/channel/weixin/reminder-queue-store.js +101 -101
  29. package/src/adapters/channel/weixin/sync-buffer-store.js +35 -35
  30. package/src/adapters/runtime/codex/events.js +215 -215
  31. package/src/adapters/runtime/codex/index.js +109 -104
  32. package/src/adapters/runtime/codex/message-utils.js +95 -95
  33. package/src/adapters/runtime/codex/model-catalog.js +106 -106
  34. package/src/adapters/runtime/codex/protocol-leak-monitor.js +75 -75
  35. package/src/adapters/runtime/codex/rpc-client.js +339 -339
  36. package/src/adapters/runtime/codex/session-store.js +286 -286
  37. package/src/app/channel-send-file-cli.js +57 -57
  38. package/src/app/diary-write-cli.js +236 -88
  39. package/src/app/note-sync-cli.js +2 -2
  40. package/src/app/reminder-write-cli.js +215 -210
  41. package/src/app/review-cli.js +7 -5
  42. package/src/app/system-checkin-poller.js +64 -64
  43. package/src/app/system-send-cli.js +129 -129
  44. package/src/app/timeline-event-cli.js +28 -25
  45. package/src/app/timeline-screenshot-cli.js +103 -100
  46. package/src/core/app.js +1763 -1763
  47. package/src/core/branding.js +2 -1
  48. package/src/core/command-registry.js +381 -369
  49. package/src/core/config.js +30 -14
  50. package/src/core/default-targets.js +163 -163
  51. package/src/core/durable-note-schema.js +9 -8
  52. package/src/core/instructions-template.js +17 -16
  53. package/src/core/note-sync.js +8 -7
  54. package/src/core/path-utils.js +54 -0
  55. package/src/core/project-radar.js +11 -10
  56. package/src/core/review.js +48 -50
  57. package/src/core/stream-delivery.js +1162 -983
  58. package/src/core/system-message-dispatcher.js +68 -68
  59. package/src/core/system-message-queue-store.js +128 -128
  60. package/src/core/thread-state-store.js +96 -96
  61. package/src/core/timeline-screenshot-queue-store.js +134 -134
  62. package/src/core/timezone.js +436 -0
  63. package/src/core/workspace-bootstrap.js +9 -1
  64. package/src/index.js +148 -146
  65. package/src/integrations/timeline/index.js +130 -74
  66. package/src/integrations/timeline/state-sync.js +240 -0
  67. package/templates/weixin-instructions.md +12 -38
  68. package/templates/weixin-operations.md +29 -31
@@ -1,7 +1,7 @@
1
- const fs = require("fs");
2
- const { renderInstructionTemplate } = require("../../../core/instructions-template");
3
- const { CodexRpcClient } = require("./rpc-client");
4
- const { mapCodexMessageToRuntimeEvent } = require("./events");
1
+ const fs = require("fs");
2
+ const { renderInstructionTemplate } = require("../../../core/instructions-template");
3
+ const { CodexRpcClient } = require("./rpc-client");
4
+ const { mapCodexMessageToRuntimeEvent } = require("./events");
5
5
  const {
6
6
  extractAssistantText,
7
7
  extractFailureText,
@@ -13,21 +13,21 @@ const {
13
13
  const { SessionStore } = require("./session-store");
14
14
  const { resolveCodexWorkspaceRoot } = require("../../../core/workspace-alias");
15
15
  const { buildWorkspaceContinuityInstructions } = require("../../../core/workspace-bootstrap");
16
-
16
+
17
17
  function createCodexRuntimeAdapter(config) {
18
18
  const sessionStore = new SessionStore({ filePath: config.sessionsFile });
19
19
  let client = null;
20
20
  let readyState = null;
21
-
21
+
22
22
  function ensureClient() {
23
- if (!client) {
24
- client = new CodexRpcClient({
25
- endpoint: config.codexEndpoint,
26
- codexCommand: config.codexCommand,
27
- env: process.env,
28
- extraWritableRoots: [config.stateDir],
29
- });
30
- }
23
+ if (!client) {
24
+ client = new CodexRpcClient({
25
+ endpoint: config.codexEndpoint,
26
+ codexCommand: config.codexCommand,
27
+ env: process.env,
28
+ extraWritableRoots: [config.stateDir],
29
+ });
30
+ }
31
31
  return client;
32
32
  }
33
33
 
@@ -84,42 +84,42 @@ function createCodexRuntimeAdapter(config) {
84
84
  }
85
85
 
86
86
  return {
87
- describe() {
88
- return {
89
- id: "codex",
90
- kind: "runtime",
91
- endpoint: config.codexEndpoint || "(spawn)",
92
- sessionsFile: config.sessionsFile,
93
- };
94
- },
95
- createClient() {
96
- return ensureClient();
97
- },
98
- onEvent(listener) {
99
- if (typeof listener !== "function") {
100
- return () => {};
101
- }
102
- const runtimeClient = ensureClient();
103
- return runtimeClient.onMessage((message) => {
104
- const event = mapCodexMessageToRuntimeEvent(message);
105
- if (event) {
106
- listener(event, message);
107
- }
108
- });
109
- },
110
- getSessionStore() {
111
- return sessionStore;
112
- },
87
+ describe() {
88
+ return {
89
+ id: "codex",
90
+ kind: "runtime",
91
+ endpoint: config.codexEndpoint || "(spawn)",
92
+ sessionsFile: config.sessionsFile,
93
+ };
94
+ },
95
+ createClient() {
96
+ return ensureClient();
97
+ },
98
+ onEvent(listener) {
99
+ if (typeof listener !== "function") {
100
+ return () => {};
101
+ }
102
+ const runtimeClient = ensureClient();
103
+ return runtimeClient.onMessage((message) => {
104
+ const event = mapCodexMessageToRuntimeEvent(message);
105
+ if (event) {
106
+ listener(event, message);
107
+ }
108
+ });
109
+ },
110
+ getSessionStore() {
111
+ return sessionStore;
112
+ },
113
113
  async initialize() {
114
114
  return ensureInitialized();
115
115
  },
116
- async close() {
117
- if (client) {
118
- await client.close();
119
- }
120
- readyState = null;
121
- client = null;
122
- },
116
+ async close() {
117
+ if (client) {
118
+ await client.close();
119
+ }
120
+ readyState = null;
121
+ client = null;
122
+ },
123
123
  async respondApproval({ requestId, decision }) {
124
124
  return withRuntimeReconnect(async (runtimeClient) => {
125
125
  const normalizedDecision = decision === "accept" ? "accept" : "decline";
@@ -320,23 +320,28 @@ function buildInstructionBlocks(config = {}, workspaceRoot = "") {
320
320
  function loadWechatInstructions(config = {}) {
321
321
  const persona = loadInstructionFile(config.weixinInstructionsFile, config);
322
322
  const operations = loadInstructionFile(config.weixinOperationsFile, config);
323
- return [persona, operations].filter(Boolean).join("\n\n").trim();
323
+ const personaOverlay = loadInstructionFile(config.weixinInstructionsOverlayFile, config);
324
+ const operationsOverlay = loadInstructionFile(config.weixinOperationsOverlayFile, config);
325
+ return [persona, operations, personaOverlay, operationsOverlay].filter(Boolean).join("\n\n").trim();
324
326
  }
325
-
327
+
326
328
  function loadInstructionFile(filePath, config = {}) {
327
- const normalizedPath = typeof filePath === "string" ? filePath.trim() : "";
328
- if (!normalizedPath) {
329
- return "";
330
- }
331
- try {
332
- const raw = fs.readFileSync(normalizedPath, "utf8");
333
- return renderInstructionTemplate(raw, config).trim();
334
- } catch {
335
- return "";
336
- }
329
+ const normalizedPath = typeof filePath === "string" ? filePath.trim() : "";
330
+ if (!normalizedPath) {
331
+ return "";
332
+ }
333
+ try {
334
+ const raw = fs.readFileSync(normalizedPath, "utf8");
335
+ return renderInstructionTemplate(raw, config).trim();
336
+ } catch {
337
+ return "";
338
+ }
337
339
  }
338
340
 
339
- module.exports = { createCodexRuntimeAdapter };
341
+ module.exports = {
342
+ createCodexRuntimeAdapter,
343
+ loadWechatInstructions,
344
+ };
340
345
 
341
346
  async function startThreadWithWorkspaceDiagnostics({
342
347
  runtimeClient,
@@ -436,47 +441,47 @@ function formatErrorMessage(error) {
436
441
  }
437
442
 
438
443
  function waitForTurnCompletion(client, threadId) {
439
- return new Promise((resolve, reject) => {
440
- let activeTurnId = "";
441
- const itemOrder = [];
442
- const completedTextByItemId = new Map();
443
-
444
- const cleanup = () => {
445
- unsubscribe();
446
- clearTimeout(timer);
447
- };
448
-
449
- const timer = setTimeout(() => {
450
- cleanup();
451
- reject(new Error("codex turn timed out"));
452
- }, 10 * 60_000);
453
-
454
- const unsubscribe = client.onMessage((message) => {
455
- const params = message?.params || {};
456
- if (extractThreadIdFromParams(params) !== threadId) {
457
- return;
458
- }
459
-
460
- if ((message?.method === "turn/started" || message?.method === "turn/start") && !activeTurnId) {
461
- activeTurnId = extractTurnIdFromParams(params);
462
- return;
463
- }
464
-
465
- if (isAssistantItemCompleted(message)) {
466
- const itemId = typeof params?.item?.id === "string" ? params.item.id.trim() : `item-${itemOrder.length + 1}`;
467
- if (!completedTextByItemId.has(itemId)) {
468
- itemOrder.push(itemId);
469
- }
470
- completedTextByItemId.set(itemId, extractAssistantText(params));
471
- return;
472
- }
473
-
474
- if (message?.method === "turn/failed") {
475
- cleanup();
476
- reject(new Error(extractFailureText(params)));
477
- return;
478
- }
479
-
444
+ return new Promise((resolve, reject) => {
445
+ let activeTurnId = "";
446
+ const itemOrder = [];
447
+ const completedTextByItemId = new Map();
448
+
449
+ const cleanup = () => {
450
+ unsubscribe();
451
+ clearTimeout(timer);
452
+ };
453
+
454
+ const timer = setTimeout(() => {
455
+ cleanup();
456
+ reject(new Error("codex turn timed out"));
457
+ }, 10 * 60_000);
458
+
459
+ const unsubscribe = client.onMessage((message) => {
460
+ const params = message?.params || {};
461
+ if (extractThreadIdFromParams(params) !== threadId) {
462
+ return;
463
+ }
464
+
465
+ if ((message?.method === "turn/started" || message?.method === "turn/start") && !activeTurnId) {
466
+ activeTurnId = extractTurnIdFromParams(params);
467
+ return;
468
+ }
469
+
470
+ if (isAssistantItemCompleted(message)) {
471
+ const itemId = typeof params?.item?.id === "string" ? params.item.id.trim() : `item-${itemOrder.length + 1}`;
472
+ if (!completedTextByItemId.has(itemId)) {
473
+ itemOrder.push(itemId);
474
+ }
475
+ completedTextByItemId.set(itemId, extractAssistantText(params));
476
+ return;
477
+ }
478
+
479
+ if (message?.method === "turn/failed") {
480
+ cleanup();
481
+ reject(new Error(extractFailureText(params)));
482
+ return;
483
+ }
484
+
480
485
  if (message?.method === "turn/completed") {
481
486
  const completedTurnId = extractTurnIdFromParams(params);
482
487
  if (activeTurnId && completedTurnId && completedTurnId !== activeTurnId) {
@@ -497,6 +502,6 @@ function waitForTurnCompletion(client, threadId) {
497
502
  text: String(text || "").trim() || "已完成。",
498
503
  });
499
504
  }
500
- });
501
- });
502
- }
505
+ });
506
+ });
507
+ }
@@ -1,42 +1,42 @@
1
- function extractThreadId(response) {
2
- return response?.result?.thread?.id || null;
3
- }
4
-
5
- function extractThreadIdFromParams(params) {
6
- return normalizeIdentifier(params?.threadId);
7
- }
8
-
9
- function extractTurnIdFromParams(params) {
10
- return normalizeIdentifier(params?.turnId || params?.turn?.id);
11
- }
12
-
13
- function isAssistantItemCompleted(message) {
14
- return message?.method === "item/completed"
15
- && normalizeIdentifier(message?.params?.item?.type).toLowerCase() === "agentmessage";
16
- }
17
-
1
+ function extractThreadId(response) {
2
+ return response?.result?.thread?.id || null;
3
+ }
4
+
5
+ function extractThreadIdFromParams(params) {
6
+ return normalizeIdentifier(params?.threadId);
7
+ }
8
+
9
+ function extractTurnIdFromParams(params) {
10
+ return normalizeIdentifier(params?.turnId || params?.turn?.id);
11
+ }
12
+
13
+ function isAssistantItemCompleted(message) {
14
+ return message?.method === "item/completed"
15
+ && normalizeIdentifier(message?.params?.item?.type).toLowerCase() === "agentmessage";
16
+ }
17
+
18
18
  function extractAssistantText(params) {
19
- const directText = [
20
- params?.delta,
21
- params?.item?.text,
22
- ];
23
- for (const value of directText) {
24
- if (typeof value === "string" && value.length > 0) {
25
- return normalizeLineEndings(value);
26
- }
27
- }
28
-
29
- const contentObjects = [
30
- params?.item?.content,
31
- params?.content,
32
- ];
33
- for (const content of contentObjects) {
34
- const extracted = extractRawTextFromContent(content);
35
- if (extracted) {
36
- return extracted;
37
- }
38
- }
39
-
19
+ const directText = [
20
+ params?.delta,
21
+ params?.item?.text,
22
+ ];
23
+ for (const value of directText) {
24
+ if (typeof value === "string" && value.length > 0) {
25
+ return normalizeLineEndings(value);
26
+ }
27
+ }
28
+
29
+ const contentObjects = [
30
+ params?.item?.content,
31
+ params?.content,
32
+ ];
33
+ for (const content of contentObjects) {
34
+ const extracted = extractRawTextFromContent(content);
35
+ if (extracted) {
36
+ return extracted;
37
+ }
38
+ }
39
+
40
40
  return "";
41
41
  }
42
42
 
@@ -55,16 +55,16 @@ function extractAssistantPhase(params) {
55
55
  }
56
56
  return "";
57
57
  }
58
-
59
- function extractFailureText(params) {
60
- const rawMessage = normalizeIdentifier(params?.turn?.error?.message || params?.error?.message);
61
- return rawMessage ? `执行失败:${rawMessage}` : "执行失败";
62
- }
63
-
64
- function normalizeIdentifier(value) {
65
- return typeof value === "string" ? value.trim() : "";
66
- }
67
-
58
+
59
+ function extractFailureText(params) {
60
+ const rawMessage = normalizeIdentifier(params?.turn?.error?.message || params?.error?.message);
61
+ return rawMessage ? `执行失败:${rawMessage}` : "执行失败";
62
+ }
63
+
64
+ function normalizeIdentifier(value) {
65
+ return typeof value === "string" ? value.trim() : "";
66
+ }
67
+
68
68
  function normalizeLineEndings(value) {
69
69
  return String(value || "").replace(/\r\n/g, "\n");
70
70
  }
@@ -82,53 +82,53 @@ function normalizeAssistantPhase(value) {
82
82
  }
83
83
  return "";
84
84
  }
85
-
86
- function extractRawTextFromContent(content) {
87
- if (typeof content === "string" && content.length > 0) {
88
- return normalizeLineEndings(content);
89
- }
90
-
91
- if (!content) {
92
- return "";
93
- }
94
-
95
- if (Array.isArray(content)) {
96
- const parts = [];
97
- for (const entry of content) {
98
- if (typeof entry === "string" && entry.length > 0) {
99
- parts.push(normalizeLineEndings(entry));
100
- continue;
101
- }
102
- if (!entry || typeof entry !== "object") {
103
- continue;
104
- }
105
- const entryType = String(entry.type || "").toLowerCase();
106
- if (entryType === "text" && typeof entry.text === "string" && entry.text.length > 0) {
107
- parts.push(normalizeLineEndings(entry.text));
108
- continue;
109
- }
110
- if (typeof entry.text === "string" && entry.text.length > 0) {
111
- parts.push(normalizeLineEndings(entry.text));
112
- continue;
113
- }
114
- if (typeof entry.value === "string" && entry.value.length > 0) {
115
- parts.push(normalizeLineEndings(entry.value));
116
- }
117
- }
118
- return parts.join("");
119
- }
120
-
121
- if (typeof content !== "object") {
122
- return "";
123
- }
124
-
125
- if (typeof content.text === "string" && content.text.length > 0) {
126
- return normalizeLineEndings(content.text);
127
- }
128
-
129
- return "";
130
- }
131
-
85
+
86
+ function extractRawTextFromContent(content) {
87
+ if (typeof content === "string" && content.length > 0) {
88
+ return normalizeLineEndings(content);
89
+ }
90
+
91
+ if (!content) {
92
+ return "";
93
+ }
94
+
95
+ if (Array.isArray(content)) {
96
+ const parts = [];
97
+ for (const entry of content) {
98
+ if (typeof entry === "string" && entry.length > 0) {
99
+ parts.push(normalizeLineEndings(entry));
100
+ continue;
101
+ }
102
+ if (!entry || typeof entry !== "object") {
103
+ continue;
104
+ }
105
+ const entryType = String(entry.type || "").toLowerCase();
106
+ if (entryType === "text" && typeof entry.text === "string" && entry.text.length > 0) {
107
+ parts.push(normalizeLineEndings(entry.text));
108
+ continue;
109
+ }
110
+ if (typeof entry.text === "string" && entry.text.length > 0) {
111
+ parts.push(normalizeLineEndings(entry.text));
112
+ continue;
113
+ }
114
+ if (typeof entry.value === "string" && entry.value.length > 0) {
115
+ parts.push(normalizeLineEndings(entry.value));
116
+ }
117
+ }
118
+ return parts.join("");
119
+ }
120
+
121
+ if (typeof content !== "object") {
122
+ return "";
123
+ }
124
+
125
+ if (typeof content.text === "string" && content.text.length > 0) {
126
+ return normalizeLineEndings(content.text);
127
+ }
128
+
129
+ return "";
130
+ }
131
+
132
132
  module.exports = {
133
133
  extractAssistantPhase,
134
134
  extractAssistantText,
@@ -1,106 +1,106 @@
1
- function extractModelCatalogFromListResponse(response) {
2
- const candidates = Array.isArray(response?.result?.data)
3
- ? response.result.data
4
- : Array.isArray(response?.data)
5
- ? response.data
6
- : [];
7
- return normalizeModelCatalog(candidates);
8
- }
9
-
10
- function resolveEffectiveModelForEffort(models, currentModel) {
11
- if (!Array.isArray(models) || !models.length) {
12
- return null;
13
- }
14
- const normalizedCurrent = normalizeText(currentModel).toLowerCase();
15
- if (normalizedCurrent) {
16
- const matched = findModelByQuery(models, normalizedCurrent);
17
- if (matched) {
18
- return matched;
19
- }
20
- }
21
- return models.find((item) => item.isDefault) || models[0];
22
- }
23
-
24
- function findModelByQuery(models, query) {
25
- const normalizedQuery = normalizeText(query).toLowerCase();
26
- if (!normalizedQuery || !Array.isArray(models)) {
27
- return null;
28
- }
29
- return models.find((item) => (
30
- normalizeText(item?.model).toLowerCase() === normalizedQuery
31
- || normalizeText(item?.id).toLowerCase() === normalizedQuery
32
- )) || null;
33
- }
34
-
35
- function normalizeModelCatalog(models) {
36
- if (!Array.isArray(models)) {
37
- return [];
38
- }
39
- const normalized = [];
40
- const seen = new Set();
41
- for (const model of models) {
42
- if (!model || typeof model !== "object") {
43
- continue;
44
- }
45
- const modelId = normalizeText(model.model);
46
- const id = normalizeText(model.id);
47
- const normalizedModel = modelId || id;
48
- if (!normalizedModel) {
49
- continue;
50
- }
51
- const dedupeKey = normalizedModel.toLowerCase();
52
- if (seen.has(dedupeKey)) {
53
- continue;
54
- }
55
- seen.add(dedupeKey);
56
- normalized.push({
57
- id,
58
- model: normalizedModel,
59
- displayName: normalizeText(model.displayName || model.display_name),
60
- supportedReasoningEfforts: normalizeReasoningEfforts(
61
- model.supportedReasoningEfforts || model.supported_reasoning_efforts
62
- ),
63
- defaultReasoningEffort: normalizeText(model.defaultReasoningEffort || model.default_reasoning_effort),
64
- isDefault: !!(model.isDefault || model.is_default),
65
- });
66
- }
67
- return normalized;
68
- }
69
-
70
- function normalizeReasoningEfforts(efforts) {
71
- if (!Array.isArray(efforts)) {
72
- return [];
73
- }
74
- const result = [];
75
- const seen = new Set();
76
- for (const effort of efforts) {
77
- const normalized = normalizeText(
78
- typeof effort === "string"
79
- ? effort
80
- : effort?.reasoningEffort || effort?.reasoning_effort
81
- );
82
- if (!normalized) {
83
- continue;
84
- }
85
- const key = normalized.toLowerCase();
86
- if (seen.has(key)) {
87
- continue;
88
- }
89
- seen.add(key);
90
- result.push(normalized);
91
- }
92
- return result;
93
- }
94
-
95
- function normalizeText(value) {
96
- return typeof value === "string" ? value.trim() : "";
97
- }
98
-
99
- module.exports = {
100
- extractModelCatalogFromListResponse,
101
- findModelByQuery,
102
- normalizeModelCatalog,
103
- normalizeText,
104
- resolveEffectiveModelForEffort,
105
- };
106
-
1
+ function extractModelCatalogFromListResponse(response) {
2
+ const candidates = Array.isArray(response?.result?.data)
3
+ ? response.result.data
4
+ : Array.isArray(response?.data)
5
+ ? response.data
6
+ : [];
7
+ return normalizeModelCatalog(candidates);
8
+ }
9
+
10
+ function resolveEffectiveModelForEffort(models, currentModel) {
11
+ if (!Array.isArray(models) || !models.length) {
12
+ return null;
13
+ }
14
+ const normalizedCurrent = normalizeText(currentModel).toLowerCase();
15
+ if (normalizedCurrent) {
16
+ const matched = findModelByQuery(models, normalizedCurrent);
17
+ if (matched) {
18
+ return matched;
19
+ }
20
+ }
21
+ return models.find((item) => item.isDefault) || models[0];
22
+ }
23
+
24
+ function findModelByQuery(models, query) {
25
+ const normalizedQuery = normalizeText(query).toLowerCase();
26
+ if (!normalizedQuery || !Array.isArray(models)) {
27
+ return null;
28
+ }
29
+ return models.find((item) => (
30
+ normalizeText(item?.model).toLowerCase() === normalizedQuery
31
+ || normalizeText(item?.id).toLowerCase() === normalizedQuery
32
+ )) || null;
33
+ }
34
+
35
+ function normalizeModelCatalog(models) {
36
+ if (!Array.isArray(models)) {
37
+ return [];
38
+ }
39
+ const normalized = [];
40
+ const seen = new Set();
41
+ for (const model of models) {
42
+ if (!model || typeof model !== "object") {
43
+ continue;
44
+ }
45
+ const modelId = normalizeText(model.model);
46
+ const id = normalizeText(model.id);
47
+ const normalizedModel = modelId || id;
48
+ if (!normalizedModel) {
49
+ continue;
50
+ }
51
+ const dedupeKey = normalizedModel.toLowerCase();
52
+ if (seen.has(dedupeKey)) {
53
+ continue;
54
+ }
55
+ seen.add(dedupeKey);
56
+ normalized.push({
57
+ id,
58
+ model: normalizedModel,
59
+ displayName: normalizeText(model.displayName || model.display_name),
60
+ supportedReasoningEfforts: normalizeReasoningEfforts(
61
+ model.supportedReasoningEfforts || model.supported_reasoning_efforts
62
+ ),
63
+ defaultReasoningEffort: normalizeText(model.defaultReasoningEffort || model.default_reasoning_effort),
64
+ isDefault: !!(model.isDefault || model.is_default),
65
+ });
66
+ }
67
+ return normalized;
68
+ }
69
+
70
+ function normalizeReasoningEfforts(efforts) {
71
+ if (!Array.isArray(efforts)) {
72
+ return [];
73
+ }
74
+ const result = [];
75
+ const seen = new Set();
76
+ for (const effort of efforts) {
77
+ const normalized = normalizeText(
78
+ typeof effort === "string"
79
+ ? effort
80
+ : effort?.reasoningEffort || effort?.reasoning_effort
81
+ );
82
+ if (!normalized) {
83
+ continue;
84
+ }
85
+ const key = normalized.toLowerCase();
86
+ if (seen.has(key)) {
87
+ continue;
88
+ }
89
+ seen.add(key);
90
+ result.push(normalized);
91
+ }
92
+ return result;
93
+ }
94
+
95
+ function normalizeText(value) {
96
+ return typeof value === "string" ? value.trim() : "";
97
+ }
98
+
99
+ module.exports = {
100
+ extractModelCatalogFromListResponse,
101
+ findModelByQuery,
102
+ normalizeModelCatalog,
103
+ normalizeText,
104
+ resolveEffectiveModelForEffort,
105
+ };
106
+