@covro/covro 0.0.0-stage → 0.1.0-dev.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 (133) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +173 -2
  3. package/README.zh-CN.md +174 -0
  4. package/bin/covro.mjs +8 -0
  5. package/dist/cli/control.js +66 -0
  6. package/dist/cli/control.js.map +7 -0
  7. package/dist/cli/daemon.js +118 -0
  8. package/dist/cli/daemon.js.map +7 -0
  9. package/dist/cli/main.js +152 -0
  10. package/dist/cli/main.js.map +7 -0
  11. package/dist/cli/paths.js +60 -0
  12. package/dist/cli/paths.js.map +7 -0
  13. package/dist/cli/service.js +87 -0
  14. package/dist/cli/service.js.map +7 -0
  15. package/dist/codex/adapter.js +1195 -0
  16. package/dist/codex/adapter.js.map +7 -0
  17. package/dist/codex/auto-review.js +102 -0
  18. package/dist/codex/auto-review.js.map +7 -0
  19. package/dist/codex/compatibility.js +7 -0
  20. package/dist/codex/compatibility.js.map +7 -0
  21. package/dist/codex/mapping.js +210 -0
  22. package/dist/codex/mapping.js.map +7 -0
  23. package/dist/codex/permissions.js +51 -0
  24. package/dist/codex/permissions.js.map +7 -0
  25. package/dist/codex/thread-title-policy.js +52 -0
  26. package/dist/codex/thread-title-policy.js.map +7 -0
  27. package/dist/codex/thread-titles.js +259 -0
  28. package/dist/codex/thread-titles.js.map +7 -0
  29. package/dist/codex/transport.js +236 -0
  30. package/dist/codex/transport.js.map +7 -0
  31. package/dist/codex/turn-token-usage.js +46 -0
  32. package/dist/codex/turn-token-usage.js.map +7 -0
  33. package/dist/codex/types.js +1 -0
  34. package/dist/codex/types.js.map +7 -0
  35. package/dist/drizzle/0000_past_wildside.sql +45 -0
  36. package/dist/drizzle/0001_shocking_ender_wiggin.sql +2 -0
  37. package/dist/drizzle/0002_allow_parallel_threads.sql +1 -0
  38. package/dist/drizzle/0003_project_registrations.sql +11 -0
  39. package/dist/drizzle/0004_turn_usage.sql +6 -0
  40. package/dist/drizzle/meta/0000_snapshot.json +309 -0
  41. package/dist/drizzle/meta/0001_snapshot.json +323 -0
  42. package/dist/drizzle/meta/0002_snapshot.json +315 -0
  43. package/dist/drizzle/meta/0003_snapshot.json +381 -0
  44. package/dist/drizzle/meta/0004_snapshot.json +420 -0
  45. package/dist/drizzle/meta/_journal.json +41 -0
  46. package/dist/server/auth.js +47 -0
  47. package/dist/server/auth.js.map +7 -0
  48. package/dist/server/compaction.js +40 -0
  49. package/dist/server/compaction.js.map +7 -0
  50. package/dist/server/config-store.js +147 -0
  51. package/dist/server/config-store.js.map +7 -0
  52. package/dist/server/config.js +124 -0
  53. package/dist/server/config.js.map +7 -0
  54. package/dist/server/db/index.js +21 -0
  55. package/dist/server/db/index.js.map +7 -0
  56. package/dist/server/db/migrate.js +26 -0
  57. package/dist/server/db/migrate.js.map +7 -0
  58. package/dist/server/db/operations.js +201 -0
  59. package/dist/server/db/operations.js.map +7 -0
  60. package/dist/server/db/schema.js +99 -0
  61. package/dist/server/db/schema.js.map +7 -0
  62. package/dist/server/db/turn-usage.js +31 -0
  63. package/dist/server/db/turn-usage.js.map +7 -0
  64. package/dist/server/events.js +123 -0
  65. package/dist/server/events.js.map +7 -0
  66. package/dist/server/http.js +305 -0
  67. package/dist/server/http.js.map +7 -0
  68. package/dist/server/images.js +49 -0
  69. package/dist/server/images.js.map +7 -0
  70. package/dist/server/instance-lock.js +25 -0
  71. package/dist/server/instance-lock.js.map +7 -0
  72. package/dist/server/local-files.js +100 -0
  73. package/dist/server/local-files.js.map +7 -0
  74. package/dist/server/main.js +125 -0
  75. package/dist/server/main.js.map +7 -0
  76. package/dist/server/markdown.js +28 -0
  77. package/dist/server/markdown.js.map +7 -0
  78. package/dist/server/projection.js +348 -0
  79. package/dist/server/projection.js.map +7 -0
  80. package/dist/server/projects.js +230 -0
  81. package/dist/server/projects.js.map +7 -0
  82. package/dist/server/service.js +1221 -0
  83. package/dist/server/service.js.map +7 -0
  84. package/dist/server/snapshot.js +98 -0
  85. package/dist/server/snapshot.js.map +7 -0
  86. package/dist/server/thread-export.js +97 -0
  87. package/dist/server/thread-export.js.map +7 -0
  88. package/dist/server/websocket.js +126 -0
  89. package/dist/server/websocket.js.map +7 -0
  90. package/dist/shared/async-questions.js +39 -0
  91. package/dist/shared/async-questions.js.map +7 -0
  92. package/dist/shared/codex-compatibility.js +56 -0
  93. package/dist/shared/codex-compatibility.js.map +7 -0
  94. package/dist/shared/elicitation.js +314 -0
  95. package/dist/shared/elicitation.js.map +7 -0
  96. package/dist/shared/errors.js +24 -0
  97. package/dist/shared/errors.js.map +7 -0
  98. package/dist/shared/gateway-config.js +1 -0
  99. package/dist/shared/gateway-config.js.map +7 -0
  100. package/dist/shared/images.js +21 -0
  101. package/dist/shared/images.js.map +7 -0
  102. package/dist/shared/package.js +5 -0
  103. package/dist/shared/package.js.map +7 -0
  104. package/dist/shared/permissions.js +63 -0
  105. package/dist/shared/permissions.js.map +7 -0
  106. package/dist/shared/thread-name.js +14 -0
  107. package/dist/shared/thread-name.js.map +7 -0
  108. package/dist/shared/thread-summary.js +24 -0
  109. package/dist/shared/thread-summary.js.map +7 -0
  110. package/dist/shared/types.js +1 -0
  111. package/dist/shared/types.js.map +7 -0
  112. package/dist/shared/updates.js +7 -0
  113. package/dist/shared/updates.js.map +7 -0
  114. package/dist/shared/validation.js +116 -0
  115. package/dist/shared/validation.js.map +7 -0
  116. package/dist/shared/versions.js +36 -0
  117. package/dist/shared/versions.js.map +7 -0
  118. package/dist/update-worker.cjs +21968 -0
  119. package/dist/updates/files.js +54 -0
  120. package/dist/updates/files.js.map +7 -0
  121. package/dist/updates/manager.js +315 -0
  122. package/dist/updates/manager.js.map +7 -0
  123. package/dist/updates/registry.js +71 -0
  124. package/dist/updates/registry.js.map +7 -0
  125. package/dist/updates/transaction.js +96 -0
  126. package/dist/updates/transaction.js.map +7 -0
  127. package/dist/updates/worker.js +345 -0
  128. package/dist/updates/worker.js.map +7 -0
  129. package/dist/web/assets/index-BW9afH_Y.js +83 -0
  130. package/dist/web/assets/index-L3S1ZiO0.css +1 -0
  131. package/dist/web/covro.svg +6 -0
  132. package/dist/web/index.html +15 -0
  133. package/package.json +88 -4
@@ -0,0 +1,126 @@
1
+ import { socketMessageSchema } from "../shared/validation.js";
2
+ import { AppError } from "../shared/errors.js";
3
+ import { compactThreadForTransport } from "./snapshot.js";
4
+ function sendBounded(socket, message, limit) {
5
+ if (socket.readyState !== 1) return;
6
+ let outgoing = message;
7
+ const budget = Math.max(512, Math.floor(limit / 2));
8
+ try {
9
+ if (message.type === "snapshot")
10
+ outgoing = { ...message, data: compactThreadForTransport(message.data, budget) };
11
+ else if (message.type === "event" && message.kind === "thread")
12
+ outgoing = {
13
+ ...message,
14
+ payload: compactThreadForTransport(message.payload, budget)
15
+ };
16
+ } catch (error) {
17
+ outgoing = {
18
+ type: "error",
19
+ error: error instanceof AppError ? error.toJSON() : {
20
+ code: "SNAPSHOT_TOO_LARGE",
21
+ message: "Current control state exceeds the configured limit"
22
+ }
23
+ };
24
+ }
25
+ const encoded = JSON.stringify(outgoing);
26
+ if (socket.bufferedAmount + Buffer.byteLength(encoded) > limit) {
27
+ socket.close(4008, "Slow connection; reconnect for snapshot");
28
+ socket.terminate?.();
29
+ return;
30
+ }
31
+ socket.send(encoded);
32
+ }
33
+ function socketHandlers(service, config, identity, overview = false) {
34
+ let unsubscribe;
35
+ let stopRuntime;
36
+ let stopOverview;
37
+ let generation = 0;
38
+ let expiry;
39
+ const send = (ws, message) => {
40
+ const raw = ws.raw;
41
+ sendBounded(
42
+ raw ?? {
43
+ readyState: ws.readyState,
44
+ bufferedAmount: 0,
45
+ send: (data) => ws.send(data),
46
+ close: (code, reason) => ws.close(code, reason)
47
+ },
48
+ message,
49
+ config.limits.wsBytes
50
+ );
51
+ };
52
+ return {
53
+ onOpen(_event, ws) {
54
+ stopRuntime = service.subscribeRuntime((message) => send(ws, message));
55
+ if (overview) stopOverview = service.subscribeOverview((message) => send(ws, message));
56
+ if (identity.expiresAt)
57
+ expiry = setTimeout(
58
+ () => ws.close(4001, "Authentication expired"),
59
+ Math.min(2147483647, Math.max(1, identity.expiresAt - Date.now()))
60
+ );
61
+ },
62
+ async onMessage(event, ws) {
63
+ const current = ++generation;
64
+ unsubscribe?.();
65
+ unsubscribe = void 0;
66
+ const deliver = (message) => {
67
+ if (current === generation && !(message.type === "event" && message.kind === "runtime"))
68
+ send(ws, message);
69
+ };
70
+ try {
71
+ if (typeof event.data !== "string" || Buffer.byteLength(event.data) > 8192)
72
+ throw new AppError("VALIDATION_FAILED", "Invalid subscription");
73
+ const input = socketMessageSchema.parse(JSON.parse(event.data));
74
+ if (input.type === "unsubscribe") return;
75
+ const stop = await service.subscribe(input.threadId, input.after, deliver);
76
+ if (current !== generation || ws.readyState !== 1) stop();
77
+ else unsubscribe = stop;
78
+ } catch (error) {
79
+ deliver({
80
+ type: "error",
81
+ error: error instanceof AppError ? error.toJSON() : { code: "VALIDATION_FAILED", message: "Invalid subscription" }
82
+ });
83
+ }
84
+ },
85
+ onClose() {
86
+ generation++;
87
+ unsubscribe?.();
88
+ stopRuntime?.();
89
+ stopOverview?.();
90
+ clearTimeout(expiry);
91
+ },
92
+ onError() {
93
+ generation++;
94
+ unsubscribe?.();
95
+ stopRuntime?.();
96
+ stopOverview?.();
97
+ clearTimeout(expiry);
98
+ }
99
+ };
100
+ }
101
+ function startHeartbeat(server) {
102
+ const alive = /* @__PURE__ */ new WeakSet();
103
+ server.on("connection", (ws) => {
104
+ alive.add(ws);
105
+ ws.on("pong", () => alive.add(ws));
106
+ ws.on("error", () => ws.terminate());
107
+ });
108
+ const timer = setInterval(() => {
109
+ for (const ws of server.clients) {
110
+ if (!alive.has(ws)) {
111
+ ws.terminate();
112
+ continue;
113
+ }
114
+ alive.delete(ws);
115
+ ws.ping();
116
+ }
117
+ }, 25e3);
118
+ timer.unref();
119
+ return () => clearInterval(timer);
120
+ }
121
+ export {
122
+ sendBounded,
123
+ socketHandlers,
124
+ startHeartbeat
125
+ };
126
+ //# sourceMappingURL=websocket.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/server/websocket.ts"],
4
+ "sourcesContent": ["import type { WSEvents, WSContext } from 'hono/ws';\nimport type { WebSocketLike } from '@hono/node-server';\nimport type { WebSocketServer, WebSocket } from 'ws';\nimport { socketMessageSchema } from '../shared/validation.js';\nimport type { ServerMessage, ThreadView } from '../shared/types.js';\nimport { AppError } from '../shared/errors.js';\nimport type { GatewayService } from './service.js';\nimport type { AppConfig } from './config.js';\nimport type { Identity } from './auth.js';\nimport { compactThreadForTransport } from './snapshot.js';\n\nexport interface SocketSink {\n readyState: number;\n bufferedAmount: number;\n send(data: string): void;\n close(code: number, reason: string): void;\n terminate?(): void;\n}\n/** Bounds both individual snapshots and queued bytes, including runtime broadcasts. */\nexport function sendBounded(socket: SocketSink, message: ServerMessage, limit: number): void {\n if (socket.readyState !== 1) return;\n let outgoing = message;\n const budget = Math.max(512, Math.floor(limit / 2));\n try {\n if (message.type === 'snapshot')\n outgoing = { ...message, data: compactThreadForTransport(message.data, budget) };\n else if (message.type === 'event' && message.kind === 'thread')\n outgoing = {\n ...message,\n payload: compactThreadForTransport(message.payload as ThreadView, budget),\n };\n } catch (error) {\n outgoing = {\n type: 'error',\n error:\n error instanceof AppError\n ? error.toJSON()\n : {\n code: 'SNAPSHOT_TOO_LARGE',\n message: 'Current control state exceeds the configured limit',\n },\n };\n }\n const encoded = JSON.stringify(outgoing);\n if (socket.bufferedAmount + Buffer.byteLength(encoded) > limit) {\n socket.close(4008, 'Slow connection; reconnect for snapshot');\n socket.terminate?.();\n return;\n }\n socket.send(encoded);\n}\nexport function socketHandlers(\n service: GatewayService,\n config: AppConfig,\n identity: Identity,\n overview = false,\n): WSEvents<WebSocketLike> {\n let unsubscribe: (() => void) | undefined;\n let stopRuntime: (() => void) | undefined;\n let stopOverview: (() => void) | undefined;\n let generation = 0;\n let expiry: ReturnType<typeof setTimeout> | undefined;\n const send = (ws: WSContext<WebSocketLike>, message: ServerMessage) => {\n const raw = ws.raw as WebSocket | undefined;\n sendBounded(\n raw ?? {\n readyState: ws.readyState,\n bufferedAmount: 0,\n send: (data) => ws.send(data),\n close: (code, reason) => ws.close(code, reason),\n },\n message,\n config.limits.wsBytes,\n );\n };\n return {\n onOpen(_event, ws) {\n stopRuntime = service.subscribeRuntime((message) => send(ws, message));\n if (overview) stopOverview = service.subscribeOverview((message) => send(ws, message));\n if (identity.expiresAt)\n expiry = setTimeout(\n () => ws.close(4001, 'Authentication expired'),\n Math.min(2_147_483_647, Math.max(1, identity.expiresAt - Date.now())),\n );\n },\n async onMessage(event, ws) {\n const current = ++generation;\n unsubscribe?.();\n unsubscribe = undefined;\n const deliver = (message: ServerMessage) => {\n if (current === generation && !(message.type === 'event' && message.kind === 'runtime'))\n send(ws, message);\n };\n try {\n if (typeof event.data !== 'string' || Buffer.byteLength(event.data) > 8192)\n throw new AppError('VALIDATION_FAILED', 'Invalid subscription');\n const input = socketMessageSchema.parse(JSON.parse(event.data));\n if (input.type === 'unsubscribe') return;\n const stop = await service.subscribe(input.threadId, input.after, deliver);\n if (current !== generation || ws.readyState !== 1) stop();\n else unsubscribe = stop;\n } catch (error) {\n deliver({\n type: 'error',\n error:\n error instanceof AppError\n ? error.toJSON()\n : { code: 'VALIDATION_FAILED', message: 'Invalid subscription' },\n });\n }\n },\n onClose() {\n generation++;\n unsubscribe?.();\n stopRuntime?.();\n stopOverview?.();\n clearTimeout(expiry);\n },\n onError() {\n generation++;\n unsubscribe?.();\n stopRuntime?.();\n stopOverview?.();\n clearTimeout(expiry);\n },\n };\n}\nexport function startHeartbeat(server: WebSocketServer): () => void {\n const alive = new WeakSet<WebSocket>();\n server.on('connection', (ws) => {\n alive.add(ws);\n ws.on('pong', () => alive.add(ws));\n ws.on('error', () => ws.terminate());\n });\n const timer = setInterval(() => {\n for (const ws of server.clients) {\n if (!alive.has(ws)) {\n ws.terminate();\n continue;\n }\n alive.delete(ws);\n ws.ping();\n }\n }, 25_000);\n timer.unref();\n return () => clearInterval(timer);\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,39 @@
1
+ const opening = "<send_user_message_question_reply>";
2
+ const closing = "</send_user_message_question_reply>";
3
+ function asyncQuestionId(itemId, index) {
4
+ return JSON.stringify(["request_user_input_async", itemId, index]);
5
+ }
6
+ function encodeQuestionReplies(replies) {
7
+ return `${opening}${JSON.stringify(replies)}${closing}`;
8
+ }
9
+ function parseQuestionReplies(text) {
10
+ let content = text.trim();
11
+ if (content.startsWith("# Context from my IDE setup:\n")) {
12
+ const delimiter = "\n## My request for Codex:\n";
13
+ const index = content.lastIndexOf(delimiter);
14
+ if (index < 0) return;
15
+ content = content.slice(index + delimiter.length).trim();
16
+ }
17
+ if (!content.startsWith(opening) || !content.endsWith(closing)) return;
18
+ try {
19
+ const parsed = JSON.parse(content.slice(opening.length, -closing.length));
20
+ const replies = Array.isArray(parsed) ? parsed : [parsed];
21
+ if (!replies.length || !replies.every(
22
+ (reply) => typeof reply === "object" && reply !== null && "questionItemId" in reply && typeof reply.questionItemId === "string" && Boolean(reply.questionItemId.trim()) && "question" in reply && typeof reply.question === "string" && "answer" in reply && typeof reply.answer === "string"
23
+ ))
24
+ return;
25
+ return replies.map(({ questionItemId, question, answer }) => ({
26
+ questionItemId,
27
+ question,
28
+ answer
29
+ }));
30
+ } catch {
31
+ return;
32
+ }
33
+ }
34
+ export {
35
+ asyncQuestionId,
36
+ encodeQuestionReplies,
37
+ parseQuestionReplies
38
+ };
39
+ //# sourceMappingURL=async-questions.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/shared/async-questions.ts"],
4
+ "sourcesContent": ["import type { AsyncQuestionReply } from './types.js';\n\n// The native desktop/CLI envelope correlates ordinary user messages with async questions.\nconst opening = '<send_user_message_question_reply>';\nconst closing = '</send_user_message_question_reply>';\n\nexport function asyncQuestionId(itemId: string, index: number): string {\n return JSON.stringify(['request_user_input_async', itemId, index]);\n}\n\nexport function encodeQuestionReplies(replies: AsyncQuestionReply[]): string {\n return `${opening}${JSON.stringify(replies)}${closing}`;\n}\n\nexport function parseQuestionReplies(text: string): AsyncQuestionReply[] | undefined {\n let content = text.trim();\n if (content.startsWith('# Context from my IDE setup:\\n')) {\n const delimiter = '\\n## My request for Codex:\\n';\n const index = content.lastIndexOf(delimiter);\n if (index < 0) return;\n content = content.slice(index + delimiter.length).trim();\n }\n if (!content.startsWith(opening) || !content.endsWith(closing)) return;\n try {\n const parsed: unknown = JSON.parse(content.slice(opening.length, -closing.length));\n const replies: unknown[] = Array.isArray(parsed) ? parsed : [parsed];\n if (\n !replies.length ||\n !replies.every(\n (reply): reply is AsyncQuestionReply =>\n typeof reply === 'object' &&\n reply !== null &&\n 'questionItemId' in reply &&\n typeof reply.questionItemId === 'string' &&\n Boolean(reply.questionItemId.trim()) &&\n 'question' in reply &&\n typeof reply.question === 'string' &&\n 'answer' in reply &&\n typeof reply.answer === 'string',\n )\n )\n return;\n return replies.map(({ questionItemId, question, answer }) => ({\n questionItemId,\n question,\n answer,\n }));\n } catch {\n return;\n }\n}\n"],
5
+ "mappings": "AAGA,MAAM,UAAU;AAChB,MAAM,UAAU;AAET,SAAS,gBAAgB,QAAgB,OAAuB;AACrE,SAAO,KAAK,UAAU,CAAC,4BAA4B,QAAQ,KAAK,CAAC;AACnE;AAEO,SAAS,sBAAsB,SAAuC;AAC3E,SAAO,GAAG,OAAO,GAAG,KAAK,UAAU,OAAO,CAAC,GAAG,OAAO;AACvD;AAEO,SAAS,qBAAqB,MAAgD;AACnF,MAAI,UAAU,KAAK,KAAK;AACxB,MAAI,QAAQ,WAAW,gCAAgC,GAAG;AACxD,UAAM,YAAY;AAClB,UAAM,QAAQ,QAAQ,YAAY,SAAS;AAC3C,QAAI,QAAQ,EAAG;AACf,cAAU,QAAQ,MAAM,QAAQ,UAAU,MAAM,EAAE,KAAK;AAAA,EACzD;AACA,MAAI,CAAC,QAAQ,WAAW,OAAO,KAAK,CAAC,QAAQ,SAAS,OAAO,EAAG;AAChE,MAAI;AACF,UAAM,SAAkB,KAAK,MAAM,QAAQ,MAAM,QAAQ,QAAQ,CAAC,QAAQ,MAAM,CAAC;AACjF,UAAM,UAAqB,MAAM,QAAQ,MAAM,IAAI,SAAS,CAAC,MAAM;AACnE,QACE,CAAC,QAAQ,UACT,CAAC,QAAQ;AAAA,MACP,CAAC,UACC,OAAO,UAAU,YACjB,UAAU,QACV,oBAAoB,SACpB,OAAO,MAAM,mBAAmB,YAChC,QAAQ,MAAM,eAAe,KAAK,CAAC,KACnC,cAAc,SACd,OAAO,MAAM,aAAa,YAC1B,YAAY,SACZ,OAAO,MAAM,WAAW;AAAA,IAC5B;AAEA;AACF,WAAO,QAAQ,IAAI,CAAC,EAAE,gBAAgB,UAAU,OAAO,OAAO;AAAA,MAC5D;AAAA,MACA;AAAA,MACA;AAAA,IACF,EAAE;AAAA,EACJ,QAAQ;AACN;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,56 @@
1
+ import { z } from "zod";
2
+ import { validVersion } from "./versions.js";
3
+ const version = z.string().max(128).refine(validVersion);
4
+ const codexCompatibilitySchema = z.object({
5
+ recommended: version,
6
+ verified: z.array(version).min(1).max(100),
7
+ unsupported: z.array(z.object({ version, reason: z.string().min(1).max(500) }).strict()).max(100)
8
+ }).strict().refine((value) => value.verified.includes(value.recommended), "Recommended must be verified").refine(
9
+ (value) => !value.unsupported.some((entry) => value.verified.includes(entry.version)),
10
+ "Verified and unsupported versions must not overlap"
11
+ );
12
+ function assessCodexCompatibility(metadata, running) {
13
+ const parsed = codexCompatibilitySchema.safeParse(metadata);
14
+ if (!parsed.success)
15
+ return {
16
+ version: running,
17
+ status: "unknown",
18
+ reason: "\u76EE\u6807\u5305\u7F3A\u5C11\u6709\u6548\u7684 Codex \u517C\u5BB9\u58F0\u660E\uFF0C\u65E0\u6CD5\u5347\u7EA7\u3002"
19
+ };
20
+ const manifest = parsed.data;
21
+ if (!running || !validVersion(running))
22
+ return {
23
+ version: running,
24
+ manifest,
25
+ status: "unknown",
26
+ reason: "\u65E0\u6CD5\u786E\u8BA4\u5F53\u524D Codex CLI \u7248\u672C\uFF0C\u8BF7\u5148\u6062\u590D Codex \u8FDE\u63A5\u540E\u91CD\u65B0\u68C0\u67E5\u3002"
27
+ };
28
+ const unsupported = manifest.unsupported.find((entry) => entry.version === running);
29
+ if (unsupported)
30
+ return {
31
+ version: running,
32
+ manifest,
33
+ status: "unsupported",
34
+ reason: `\u76EE\u6807\u7248\u672C\u4E0D\u652F\u6301 Codex ${running}\uFF1A${unsupported.reason}`
35
+ };
36
+ if (manifest.verified.includes(running))
37
+ return { version: running, manifest, status: "verified" };
38
+ return {
39
+ version: running,
40
+ manifest,
41
+ status: "unverified",
42
+ reason: `\u5F53\u524D Codex ${running} \u4E0E\u76EE\u6807 Covro \u7684\u7EC4\u5408\u5C1A\u672A\u9A8C\u8BC1\uFF1B\u81EA\u52A8\u5347\u7EA7\u5DF2\u6682\u505C\u3002`
43
+ };
44
+ }
45
+ function requireCodexCompatibility(metadata, running, confirmed = false) {
46
+ const result = assessCodexCompatibility(metadata, running);
47
+ if (result.status !== "verified" && !(confirmed && result.status === "unverified"))
48
+ throw new Error(result.reason);
49
+ return result;
50
+ }
51
+ export {
52
+ assessCodexCompatibility,
53
+ codexCompatibilitySchema,
54
+ requireCodexCompatibility
55
+ };
56
+ //# sourceMappingURL=codex-compatibility.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/shared/codex-compatibility.ts"],
4
+ "sourcesContent": ["import { z } from 'zod';\nimport { validVersion } from './versions.js';\n\nconst version = z.string().max(128).refine(validVersion);\nexport const codexCompatibilitySchema = z\n .object({\n recommended: version,\n verified: z.array(version).min(1).max(100),\n unsupported: z\n .array(z.object({ version, reason: z.string().min(1).max(500) }).strict())\n .max(100),\n })\n .strict()\n .refine((value) => value.verified.includes(value.recommended), 'Recommended must be verified')\n .refine(\n (value) => !value.unsupported.some((entry) => value.verified.includes(entry.version)),\n 'Verified and unsupported versions must not overlap',\n );\nexport type CodexCompatibilityManifest = z.infer<typeof codexCompatibilitySchema>;\nexport interface CodexCompatibility {\n version: string | null;\n status: 'verified' | 'unverified' | 'unsupported' | 'unknown';\n manifest?: CodexCompatibilityManifest;\n reason?: string;\n}\nexport interface CodexUpdateConfirmation {\n version: string;\n target: string;\n}\nexport function assessCodexCompatibility(\n metadata: unknown,\n running: string | null,\n): CodexCompatibility {\n const parsed = codexCompatibilitySchema.safeParse(metadata);\n if (!parsed.success)\n return {\n version: running,\n status: 'unknown',\n reason: '\u76EE\u6807\u5305\u7F3A\u5C11\u6709\u6548\u7684 Codex \u517C\u5BB9\u58F0\u660E\uFF0C\u65E0\u6CD5\u5347\u7EA7\u3002',\n };\n const manifest = parsed.data;\n if (!running || !validVersion(running))\n return {\n version: running,\n manifest,\n status: 'unknown',\n reason: '\u65E0\u6CD5\u786E\u8BA4\u5F53\u524D Codex CLI \u7248\u672C\uFF0C\u8BF7\u5148\u6062\u590D Codex \u8FDE\u63A5\u540E\u91CD\u65B0\u68C0\u67E5\u3002',\n };\n const unsupported = manifest.unsupported.find((entry) => entry.version === running);\n if (unsupported)\n return {\n version: running,\n manifest,\n status: 'unsupported',\n reason: `\u76EE\u6807\u7248\u672C\u4E0D\u652F\u6301 Codex ${running}\uFF1A${unsupported.reason}`,\n };\n if (manifest.verified.includes(running))\n return { version: running, manifest, status: 'verified' };\n return {\n version: running,\n manifest,\n status: 'unverified',\n reason: `\u5F53\u524D Codex ${running} \u4E0E\u76EE\u6807 Covro \u7684\u7EC4\u5408\u5C1A\u672A\u9A8C\u8BC1\uFF1B\u81EA\u52A8\u5347\u7EA7\u5DF2\u6682\u505C\u3002`,\n };\n}\nexport function requireCodexCompatibility(\n metadata: unknown,\n running: string | null,\n confirmed = false,\n) {\n const result = assessCodexCompatibility(metadata, running);\n if (result.status !== 'verified' && !(confirmed && result.status === 'unverified'))\n throw new Error(result.reason);\n return result;\n}\n"],
5
+ "mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,oBAAoB;AAE7B,MAAM,UAAU,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,OAAO,YAAY;AAChD,MAAM,2BAA2B,EACrC,OAAO;AAAA,EACN,aAAa;AAAA,EACb,UAAU,EAAE,MAAM,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAAA,EACzC,aAAa,EACV,MAAM,EAAE,OAAO,EAAE,SAAS,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,CAAC,EAAE,OAAO,CAAC,EACxE,IAAI,GAAG;AACZ,CAAC,EACA,OAAO,EACP,OAAO,CAAC,UAAU,MAAM,SAAS,SAAS,MAAM,WAAW,GAAG,8BAA8B,EAC5F;AAAA,EACC,CAAC,UAAU,CAAC,MAAM,YAAY,KAAK,CAAC,UAAU,MAAM,SAAS,SAAS,MAAM,OAAO,CAAC;AAAA,EACpF;AACF;AAYK,SAAS,yBACd,UACA,SACoB;AACpB,QAAM,SAAS,yBAAyB,UAAU,QAAQ;AAC1D,MAAI,CAAC,OAAO;AACV,WAAO;AAAA,MACL,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV;AACF,QAAM,WAAW,OAAO;AACxB,MAAI,CAAC,WAAW,CAAC,aAAa,OAAO;AACnC,WAAO;AAAA,MACL,SAAS;AAAA,MACT;AAAA,MACA,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV;AACF,QAAM,cAAc,SAAS,YAAY,KAAK,CAAC,UAAU,MAAM,YAAY,OAAO;AAClF,MAAI;AACF,WAAO;AAAA,MACL,SAAS;AAAA,MACT;AAAA,MACA,QAAQ;AAAA,MACR,QAAQ,oDAAiB,OAAO,SAAI,YAAY,MAAM;AAAA,IACxD;AACF,MAAI,SAAS,SAAS,SAAS,OAAO;AACpC,WAAO,EAAE,SAAS,SAAS,UAAU,QAAQ,WAAW;AAC1D,SAAO;AAAA,IACL,SAAS;AAAA,IACT;AAAA,IACA,QAAQ;AAAA,IACR,QAAQ,sBAAY,OAAO;AAAA,EAC7B;AACF;AACO,SAAS,0BACd,UACA,SACA,YAAY,OACZ;AACA,QAAM,SAAS,yBAAyB,UAAU,OAAO;AACzD,MAAI,OAAO,WAAW,cAAc,EAAE,aAAa,OAAO,WAAW;AACnE,UAAM,IAAI,MAAM,OAAO,MAAM;AAC/B,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -0,0 +1,314 @@
1
+ import { AppError } from "./errors.js";
2
+ const record = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3
+ const has = (value, key) => Object.hasOwn(value, key);
4
+ const fail = (message) => {
5
+ throw new AppError("VALIDATION_ERROR", message);
6
+ };
7
+ function onlyKeys(value, keys, context) {
8
+ const unknown = Object.keys(value).find((key) => !keys.includes(key));
9
+ if (unknown !== void 0) fail(`${context}\u5305\u542B\u5C1A\u4E0D\u652F\u6301\u7684\u7EA6\u675F\uFF1A${unknown}`);
10
+ }
11
+ function optionalText(value, key) {
12
+ if (!has(value, key)) return void 0;
13
+ if (typeof value[key] !== "string") fail(`${key} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32`);
14
+ return value[key];
15
+ }
16
+ function limit(value, key, integer) {
17
+ if (!has(value, key)) return void 0;
18
+ const number = value[key];
19
+ if (typeof number !== "number" || !Number.isFinite(number) || integer && (!Number.isSafeInteger(number) || number < 0))
20
+ fail(`${key} \u4E0D\u662F\u6709\u6548\u7684${integer ? "\u975E\u8D1F\u6574\u6570" : "\u6570\u503C"}`);
21
+ return number;
22
+ }
23
+ function enumOptions(schema, variant = "oneOf") {
24
+ let options;
25
+ if (has(schema, "enum")) {
26
+ const values = schema.enum;
27
+ if (!Array.isArray(values) || values.some((value) => typeof value !== "string"))
28
+ fail("\u679A\u4E3E\u9009\u9879\u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u5217\u8868");
29
+ const names = schema.enumNames;
30
+ if (names !== void 0 && (!Array.isArray(names) || names.length !== values.length || names.some((name) => typeof name !== "string")))
31
+ fail("\u679A\u4E3E\u6807\u9898\u5FC5\u987B\u4E0E\u9009\u9879\u4E00\u4E00\u5BF9\u5E94");
32
+ if (has(schema, variant)) fail("\u4E0D\u80FD\u540C\u65F6\u63D0\u4F9B\u591A\u79CD\u679A\u4E3E\u7EA6\u675F");
33
+ options = values.map((value, index) => ({
34
+ value,
35
+ label: names?.[index] ?? value
36
+ }));
37
+ } else {
38
+ if (has(schema, "enumNames")) fail("\u679A\u4E3E\u6807\u9898\u7F3A\u5C11 enum \u9009\u9879");
39
+ const values = schema[variant];
40
+ if (!Array.isArray(values)) fail("\u7F3A\u5C11\u53EF\u7528\u7684\u679A\u4E3E\u9009\u9879");
41
+ options = values.map((value) => {
42
+ if (!record(value)) fail("\u679A\u4E3E\u9009\u9879\u683C\u5F0F\u4E0D\u53D7\u652F\u6301");
43
+ const option = value;
44
+ onlyKeys(option, ["const", "title", "description", "type"], "\u679A\u4E3E\u9009\u9879");
45
+ if (typeof option.const !== "string" || has(option, "type") && option.type !== "string")
46
+ fail("\u4EC5\u652F\u6301\u5B57\u7B26\u4E32\u679A\u4E3E\u9009\u9879");
47
+ optionalText(option, "description");
48
+ return {
49
+ value: option.const,
50
+ label: optionalText(option, "title") ?? String(option.const)
51
+ };
52
+ });
53
+ }
54
+ if (!options.length || new Set(options.map((option) => option.value)).size !== options.length)
55
+ fail("\u679A\u4E3E\u9009\u9879\u4E0D\u80FD\u4E3A\u7A7A\u6216\u91CD\u590D");
56
+ if (options.some((option) => option.value.length > 1e4)) fail("\u679A\u4E3E\u503C\u8D85\u8FC7\u53EF\u63D0\u4EA4\u7684\u957F\u5EA6\u4E0A\u9650");
57
+ return options;
58
+ }
59
+ function dateValid(value) {
60
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
61
+ const date = /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
62
+ return Number.isFinite(date.getTime()) && date.toISOString().slice(0, 10) === value;
63
+ }
64
+ function formatValid(format, value) {
65
+ switch (format) {
66
+ case "email":
67
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
68
+ case "uri":
69
+ try {
70
+ return !/\s/.test(value) && Boolean(new URL(value).protocol);
71
+ } catch {
72
+ return false;
73
+ }
74
+ case "date":
75
+ return dateValid(value);
76
+ case "date-time": {
77
+ const match = /^(\d{4}-\d{2}-\d{2})[Tt](\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:[Zz]|[+-](\d{2}):(\d{2}))$/.exec(
78
+ value
79
+ );
80
+ return Boolean(
81
+ match && dateValid(match[1]) && Number(match[2]) <= 23 && Number(match[3]) <= 59 && Number(match[4]) <= 59 && (match[5] === void 0 || Number(match[5]) <= 23) && (match[6] === void 0 || Number(match[6]) <= 59) && Number.isFinite(Date.parse(value))
82
+ );
83
+ }
84
+ default:
85
+ return true;
86
+ }
87
+ }
88
+ function validateElicitationFieldValue(field, value) {
89
+ const invalid = () => fail(`\u201C${field.title}\u201D\u7684\u56DE\u7B54\u4E0D\u7B26\u5408\u5B57\u6BB5\u8981\u6C42`);
90
+ switch (field.type) {
91
+ case "string":
92
+ if (typeof value !== "string") invalid();
93
+ else {
94
+ const length = [...value].length;
95
+ if (field.minLength !== void 0 && length < field.minLength || field.maxLength !== void 0 && length > field.maxLength || value.length > 1e4 || !formatValid(field.format, value))
96
+ invalid();
97
+ }
98
+ break;
99
+ case "number":
100
+ case "integer":
101
+ if (typeof value !== "number" || !Number.isFinite(value) || field.type === "integer" && !Number.isSafeInteger(value) || field.minimum !== void 0 && value < field.minimum || field.maximum !== void 0 && value > field.maximum)
102
+ invalid();
103
+ break;
104
+ case "boolean":
105
+ if (typeof value !== "boolean") invalid();
106
+ break;
107
+ case "enum":
108
+ if (typeof value !== "string" || value.length > 1e4 || !field.options?.some((option) => option.value === value))
109
+ invalid();
110
+ break;
111
+ case "multiEnum":
112
+ if (!Array.isArray(value) || value.some(
113
+ (item) => typeof item !== "string" || item.length > 1e4 || !field.options?.some((option) => option.value === item)
114
+ ) || value.length > 500 || new Set(value).size !== value.length || field.minItems !== void 0 && value.length < field.minItems || field.maxItems !== void 0 && value.length > field.maxItems)
115
+ invalid();
116
+ break;
117
+ }
118
+ }
119
+ function normalizeField(name, value, required) {
120
+ if (name === "__proto__") fail("\u5B57\u6BB5\u540D\u79F0 __proto__ \u4E0D\u53D7\u5F53\u524D\u8868\u5355\u4F20\u8F93\u652F\u6301");
121
+ if (name.length > 1e3) fail("\u5B57\u6BB5\u540D\u79F0\u8D85\u8FC7\u53EF\u63D0\u4EA4\u7684\u957F\u5EA6\u4E0A\u9650");
122
+ if (!record(value)) fail(`\u5B57\u6BB5 ${name} \u7684\u5B9A\u4E49\u683C\u5F0F\u4E0D\u53D7\u652F\u6301`);
123
+ const schema = value;
124
+ const commonKeys = ["type", "title", "description", "default"];
125
+ const field = {
126
+ name,
127
+ title: optionalText(schema, "title") ?? name,
128
+ description: optionalText(schema, "description"),
129
+ required,
130
+ type: "string"
131
+ };
132
+ switch (schema.type) {
133
+ case "string":
134
+ if (has(schema, "enum") || has(schema, "oneOf") || has(schema, "enumNames")) {
135
+ onlyKeys(schema, [...commonKeys, "enum", "enumNames", "oneOf"], `\u5B57\u6BB5 ${name}`);
136
+ field.type = "enum";
137
+ field.options = enumOptions(schema);
138
+ } else {
139
+ onlyKeys(schema, [...commonKeys, "minLength", "maxLength", "format"], `\u5B57\u6BB5 ${name}`);
140
+ field.minLength = limit(schema, "minLength", true);
141
+ field.maxLength = limit(schema, "maxLength", true);
142
+ if (field.minLength !== void 0 && field.minLength > 1e4)
143
+ fail(`\u5B57\u6BB5 ${name} \u7684\u6700\u5C0F\u957F\u5EA6\u8D85\u8FC7\u53EF\u63D0\u4EA4\u7684\u957F\u5EA6\u4E0A\u9650`);
144
+ if (field.minLength !== void 0 && field.maxLength !== void 0 && field.minLength > field.maxLength)
145
+ fail(`\u5B57\u6BB5 ${name} \u7684\u957F\u5EA6\u8303\u56F4\u65E0\u6548`);
146
+ if (has(schema, "format")) {
147
+ if (!["email", "uri", "date", "date-time"].includes(String(schema.format)))
148
+ fail(`\u5B57\u6BB5 ${name} \u7684 format \u4E0D\u53D7\u652F\u6301`);
149
+ field.format = schema.format;
150
+ }
151
+ }
152
+ break;
153
+ case "number":
154
+ case "integer":
155
+ onlyKeys(schema, [...commonKeys, "minimum", "maximum"], `\u5B57\u6BB5 ${name}`);
156
+ field.type = schema.type;
157
+ field.minimum = limit(schema, "minimum", false);
158
+ field.maximum = limit(schema, "maximum", false);
159
+ if (field.minimum !== void 0 && field.maximum !== void 0 && field.minimum > field.maximum)
160
+ fail(`\u5B57\u6BB5 ${name} \u7684\u6570\u503C\u8303\u56F4\u65E0\u6548`);
161
+ break;
162
+ case "boolean":
163
+ onlyKeys(schema, commonKeys, `\u5B57\u6BB5 ${name}`);
164
+ field.type = "boolean";
165
+ break;
166
+ case "array": {
167
+ onlyKeys(
168
+ schema,
169
+ [...commonKeys, "items", "minItems", "maxItems", "uniqueItems"],
170
+ `\u5B57\u6BB5 ${name}`
171
+ );
172
+ if (has(schema, "uniqueItems") && schema.uniqueItems !== true)
173
+ fail(`\u5B57\u6BB5 ${name} \u4EC5\u652F\u6301\u4E0D\u91CD\u590D\u7684\u591A\u9009\u9879`);
174
+ if (!record(schema.items)) fail(`\u5B57\u6BB5 ${name} \u7F3A\u5C11\u6709\u6548\u7684\u591A\u9009\u9879`);
175
+ const items = schema.items;
176
+ onlyKeys(items, ["type", "enum", "enumNames", "anyOf"], `\u5B57\u6BB5 ${name} \u7684\u591A\u9009\u9879`);
177
+ if (has(items, "type") && items.type !== "string") fail(`\u5B57\u6BB5 ${name} \u4EC5\u652F\u6301\u5B57\u7B26\u4E32\u591A\u9009\u9879`);
178
+ field.type = "multiEnum";
179
+ field.options = enumOptions(items, "anyOf");
180
+ field.minItems = limit(schema, "minItems", true);
181
+ field.maxItems = limit(schema, "maxItems", true);
182
+ if (field.minItems !== void 0 && field.minItems > 500)
183
+ fail(`\u5B57\u6BB5 ${name} \u7684\u6700\u5C11\u9009\u9879\u6570\u8D85\u8FC7\u53EF\u63D0\u4EA4\u7684\u6570\u91CF\u4E0A\u9650`);
184
+ if (field.minItems !== void 0 && field.maxItems !== void 0 && field.minItems > field.maxItems)
185
+ fail(`\u5B57\u6BB5 ${name} \u7684\u9009\u9879\u6570\u91CF\u8303\u56F4\u65E0\u6548`);
186
+ break;
187
+ }
188
+ default:
189
+ fail(`\u5B57\u6BB5 ${name} \u7684\u7C7B\u578B\u4E0D\u53D7\u652F\u6301\uFF1A${String(schema.type)}`);
190
+ }
191
+ if (has(schema, "default")) {
192
+ validateElicitationFieldValue(field, schema.default);
193
+ field.default = schema.default;
194
+ }
195
+ return field;
196
+ }
197
+ function normalizeElicitationSchema(schema) {
198
+ try {
199
+ if (!record(schema)) fail("\u8868\u5355\u7ED3\u6784\u5FC5\u987B\u662F JSON \u5BF9\u8C61");
200
+ const root = schema;
201
+ onlyKeys(
202
+ root,
203
+ ["$schema", "type", "title", "description", "properties", "required", "additionalProperties"],
204
+ "\u8868\u5355"
205
+ );
206
+ if (root.type !== "object" || !record(root.properties))
207
+ fail("\u4EC5\u652F\u6301\u5305\u542B properties \u7684\u6241\u5E73\u5BF9\u8C61\u8868\u5355");
208
+ optionalText(root, "$schema");
209
+ optionalText(root, "title");
210
+ optionalText(root, "description");
211
+ if (has(root, "additionalProperties") && typeof root.additionalProperties !== "boolean")
212
+ fail("additionalProperties \u7EA6\u675F\u4E0D\u53D7\u652F\u6301");
213
+ const properties = root.properties;
214
+ const required = has(root, "required") ? root.required : [];
215
+ if (!Array.isArray(required) || required.some((name) => typeof name !== "string" || !has(properties, name)) || new Set(required).size !== required.length)
216
+ fail("\u5FC5\u586B\u5B57\u6BB5\u5FC5\u987B\u5B58\u5728\u4E8E\u8868\u5355\u4E2D");
217
+ return {
218
+ fields: Object.entries(properties).map(
219
+ ([name, value]) => normalizeField(name, value, required.includes(name))
220
+ )
221
+ };
222
+ } catch (error) {
223
+ return { fields: [], schemaError: error instanceof Error ? error.message : "\u8868\u5355\u7ED3\u6784\u4E0D\u53D7\u652F\u6301" };
224
+ }
225
+ }
226
+ function normalizeElicitationRequest(params) {
227
+ const base = { serverName: typeof params.serverName === "string" ? params.serverName : "MCP" };
228
+ if (params.mode === "url") {
229
+ const url = typeof params.url === "string" ? params.url : "";
230
+ try {
231
+ const parsed = new URL(url);
232
+ if (!["http:", "https:"].includes(parsed.protocol) || parsed.username || parsed.password)
233
+ throw new Error("unsupported URL");
234
+ return { ...base, mode: "url", url };
235
+ } catch {
236
+ return {
237
+ ...base,
238
+ mode: "url",
239
+ url,
240
+ schemaError: "\u6B64\u8BF7\u6C42\u7684\u94FE\u63A5\u65E0\u6548\uFF1B\u4EC5\u652F\u6301 HTTP \u6216 HTTPS \u94FE\u63A5"
241
+ };
242
+ }
243
+ }
244
+ if (["form", "openai/form", "openaiForm"].includes(String(params.mode))) {
245
+ const schema = params.requestedSchema;
246
+ const form = schema === null ? { fields: [] } : normalizeElicitationSchema(schema);
247
+ const meta = record(params._meta) ? params._meta : void 0;
248
+ const allowSession = meta?.persist === "session" || Array.isArray(meta?.persist) && meta.persist.includes("session");
249
+ const approval = !form.schemaError && form.fields?.length === 0 && (meta?.codex_approval_kind === "mcp_tool_call" || allowSession) ? { allowSession } : void 0;
250
+ return { ...base, mode: "form", ...form, ...approval ? { approval } : {} };
251
+ }
252
+ return {
253
+ ...base,
254
+ mode: "form",
255
+ fields: [],
256
+ schemaError: `\u5C1A\u4E0D\u652F\u6301\u7684\u4EA4\u4E92\u6A21\u5F0F\uFF1A${String(params.mode)}`
257
+ };
258
+ }
259
+ function validateElicitationResolution(request, resolution) {
260
+ if (!record(resolution)) fail("\u8BF7\u63D0\u4EA4\u6709\u6548\u7684\u4EA4\u4E92\u56DE\u7B54");
261
+ onlyKeys(resolution, ["decision", "content", "persist"], "\u4EA4\u4E92\u56DE\u7B54");
262
+ if (!("decision" in resolution) || !["accept", "decline", "cancel"].includes(resolution.decision))
263
+ fail("\u6B64\u8BF7\u6C42\u4E0D\u652F\u6301\u6240\u63D0\u4EA4\u7684\u51B3\u5B9A");
264
+ const answer = resolution;
265
+ if (has(answer, "persist") && (answer.persist !== "session" || answer.decision !== "accept" || request?.mode !== "form" || !request.approval?.allowSession))
266
+ fail("\u6B64\u8BF7\u6C42\u4E0D\u652F\u6301\u6240\u9009\u6388\u6743\u8303\u56F4");
267
+ if (answer.decision !== "accept") {
268
+ if (has(answer, "content")) fail("\u62D2\u7EDD\u6216\u53D6\u6D88\u65F6\u4E0D\u80FD\u63D0\u4EA4\u8868\u5355\u5185\u5BB9");
269
+ return null;
270
+ }
271
+ if (!request || request.schemaError) fail(request?.schemaError ?? "\u4EA4\u4E92\u8BF7\u6C42\u7F3A\u5C11\u8868\u5355\u4FE1\u606F");
272
+ const elicitation = request;
273
+ if (elicitation.approval) {
274
+ if (has(answer, "content")) fail("\u5DE5\u5177\u5BA1\u6279\u4E0D\u80FD\u63D0\u4EA4\u8868\u5355\u5185\u5BB9");
275
+ return null;
276
+ }
277
+ if (elicitation.mode === "url") {
278
+ const checked = normalizeElicitationRequest({
279
+ mode: "url",
280
+ serverName: elicitation.serverName,
281
+ url: elicitation.url
282
+ });
283
+ if (checked.schemaError) fail(checked.schemaError);
284
+ if (has(answer, "content")) fail("\u94FE\u63A5\u786E\u8BA4\u4E0D\u80FD\u63D0\u4EA4\u8868\u5355\u5185\u5BB9");
285
+ return null;
286
+ }
287
+ if (!record(answer.content)) fail("\u8BF7\u63D0\u4EA4\u7ED3\u6784\u5316\u7684\u8868\u5355\u56DE\u7B54");
288
+ const content = answer.content;
289
+ if (!Array.isArray(elicitation.fields)) fail("\u4EA4\u4E92\u8BF7\u6C42\u7F3A\u5C11\u8868\u5355\u5B57\u6BB5");
290
+ const fields = elicitation.fields;
291
+ if (Object.keys(content).some((name) => !fields.some((field) => field.name === name)))
292
+ fail("\u56DE\u7B54\u5305\u542B\u672A\u77E5\u5B57\u6BB5");
293
+ for (const field of fields) {
294
+ if (!has(content, field.name)) {
295
+ if (field.required) fail(`\u8BF7\u586B\u5199\u201C${field.title}\u201D`);
296
+ } else validateElicitationFieldValue(field, content[field.name]);
297
+ }
298
+ return structuredClone(content);
299
+ }
300
+ function validatePermissionResolution(resolution) {
301
+ if (!record(resolution)) fail("\u8BF7\u63D0\u4EA4\u6709\u6548\u7684\u6743\u9650\u51B3\u5B9A");
302
+ onlyKeys(resolution, ["decision"], "\u6743\u9650\u56DE\u7B54");
303
+ if (!("decision" in resolution) || !["accept", "acceptForSession", "decline"].includes(resolution.decision))
304
+ fail("\u8BF7\u9009\u62E9\u5F53\u524D\u4EFB\u52A1\u5141\u8BB8\u3001\u4F1A\u8BDD\u5141\u8BB8\u6216\u62D2\u7EDD");
305
+ return resolution.decision;
306
+ }
307
+ export {
308
+ normalizeElicitationRequest,
309
+ normalizeElicitationSchema,
310
+ validateElicitationFieldValue,
311
+ validateElicitationResolution,
312
+ validatePermissionResolution
313
+ };
314
+ //# sourceMappingURL=elicitation.js.map