@dev-anywhere/proxy 0.1.9 → 0.2.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/dist/{chunk-BMVYMCKF.js → chunk-3ZUZ22V6.js} +2 -2
- package/dist/{chunk-7XMJMVIL.js → chunk-4YQ2JUM7.js} +41 -6
- package/dist/chunk-4YQ2JUM7.js.map +1 -0
- package/dist/chunk-7UOPAMX7.js +220 -0
- package/dist/chunk-7UOPAMX7.js.map +1 -0
- package/dist/chunk-NBRBO5GS.js +1032 -0
- package/dist/chunk-NBRBO5GS.js.map +1 -0
- package/dist/chunk-NQDJ6QAM.js +18 -0
- package/dist/chunk-NQDJ6QAM.js.map +1 -0
- package/dist/chunk-OBYEKZWC.js +104 -0
- package/dist/chunk-OBYEKZWC.js.map +1 -0
- package/dist/chunk-PWG6K5QB.js +204 -0
- package/dist/chunk-PWG6K5QB.js.map +1 -0
- package/dist/{chunk-DCDXAM76.js → chunk-RIQ6OL7X.js} +9 -6
- package/dist/chunk-RIQ6OL7X.js.map +1 -0
- package/dist/{chunk-6O6JTF24.js → chunk-WUBRUO3G.js} +1 -1
- package/dist/index.js +5 -5
- package/dist/{relay-token-Z4JZFPQ5.js → relay-token-RKAVVQHE.js} +5 -4
- package/dist/{relay-token-Z4JZFPQ5.js.map → relay-token-RKAVVQHE.js.map} +1 -1
- package/dist/serve.js +538 -431
- package/dist/serve.js.map +1 -1
- package/dist/session-worker.js +99 -32
- package/dist/session-worker.js.map +1 -1
- package/dist/{terminal-FJAIRC73.js → terminal-YO2D2OJU.js} +194 -151
- package/dist/terminal-YO2D2OJU.js.map +1 -0
- package/package.json +3 -3
- package/dist/chunk-2JUB4LDU.js +0 -84
- package/dist/chunk-2JUB4LDU.js.map +0 -1
- package/dist/chunk-7XMJMVIL.js.map +0 -1
- package/dist/chunk-DCDXAM76.js.map +0 -1
- package/dist/chunk-ORZTFYXR.js +0 -123
- package/dist/chunk-ORZTFYXR.js.map +0 -1
- package/dist/chunk-QFYI6AMN.js +0 -870
- package/dist/chunk-QFYI6AMN.js.map +0 -1
- package/dist/chunk-U5T7ZYXT.js +0 -346
- package/dist/chunk-U5T7ZYXT.js.map +0 -1
- package/dist/terminal-FJAIRC73.js.map +0 -1
- /package/dist/{chunk-BMVYMCKF.js.map → chunk-3ZUZ22V6.js.map} +0 -0
- /package/dist/{chunk-6O6JTF24.js.map → chunk-WUBRUO3G.js.map} +0 -0
package/dist/chunk-2JUB4LDU.js
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
CONFIG_PATH,
|
|
4
|
-
LOG_DIR,
|
|
5
|
-
createLogger
|
|
6
|
-
} from "./chunk-QFYI6AMN.js";
|
|
7
|
-
|
|
8
|
-
// src/common/logger.ts
|
|
9
|
-
import { existsSync, readFileSync } from "fs";
|
|
10
|
-
|
|
11
|
-
// src/common/runtime-env.ts
|
|
12
|
-
var VALID_LOG_LEVELS = [
|
|
13
|
-
"trace",
|
|
14
|
-
"debug",
|
|
15
|
-
"info",
|
|
16
|
-
"warn",
|
|
17
|
-
"error",
|
|
18
|
-
"fatal",
|
|
19
|
-
"silent"
|
|
20
|
-
];
|
|
21
|
-
function parseLogLevel(value) {
|
|
22
|
-
if (!value) return void 0;
|
|
23
|
-
if (VALID_LOG_LEVELS.includes(value)) return value;
|
|
24
|
-
throw new Error(
|
|
25
|
-
`Invalid LOG_LEVEL=${JSON.stringify(value)}; expected one of ${VALID_LOG_LEVELS.join(", ")}`
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
function parsePort(value, source) {
|
|
29
|
-
if (!value) return void 0;
|
|
30
|
-
const port = Number(value);
|
|
31
|
-
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
32
|
-
throw new Error(`Invalid ${source}=${JSON.stringify(value)}: expected TCP port 1-65535`);
|
|
33
|
-
}
|
|
34
|
-
return port;
|
|
35
|
-
}
|
|
36
|
-
function nonEmpty(value) {
|
|
37
|
-
return value && value.length > 0 ? value : void 0;
|
|
38
|
-
}
|
|
39
|
-
function loadProxyRuntimeEnv(env2 = process.env) {
|
|
40
|
-
return {
|
|
41
|
-
relayUrl: nonEmpty(env2.RELAY_URL),
|
|
42
|
-
relayProxyToken: nonEmpty(env2.RELAY_PROXY_TOKEN),
|
|
43
|
-
hookPort: parsePort(env2.DEV_ANYWHERE_HOOK_PORT, "DEV_ANYWHERE_HOOK_PORT"),
|
|
44
|
-
claudeBin: nonEmpty(env2.CLAUDE_BIN),
|
|
45
|
-
codexBin: nonEmpty(env2.CODEX_BIN),
|
|
46
|
-
logLevel: parseLogLevel(env2.LOG_LEVEL),
|
|
47
|
-
isVitest: !!env2.VITEST
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// src/common/logger.ts
|
|
52
|
-
var env = loadProxyRuntimeEnv();
|
|
53
|
-
function readConfigLogLevel() {
|
|
54
|
-
if (env.logLevel) return void 0;
|
|
55
|
-
if (!existsSync(CONFIG_PATH)) return void 0;
|
|
56
|
-
try {
|
|
57
|
-
const raw = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
|
|
58
|
-
if (typeof raw.logLevel !== "string") return void 0;
|
|
59
|
-
return VALID_LOG_LEVELS.includes(raw.logLevel) ? raw.logLevel : void 0;
|
|
60
|
-
} catch {
|
|
61
|
-
return void 0;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
var overrideLevel = env.logLevel ?? readConfigLogLevel();
|
|
65
|
-
var serviceLogger = createLogger({
|
|
66
|
-
name: "service",
|
|
67
|
-
level: overrideLevel ?? "info",
|
|
68
|
-
logDir: LOG_DIR,
|
|
69
|
-
silent: env.isVitest
|
|
70
|
-
});
|
|
71
|
-
var terminalLogger = createLogger({
|
|
72
|
-
name: "terminal",
|
|
73
|
-
level: overrideLevel ?? "debug",
|
|
74
|
-
logDir: LOG_DIR,
|
|
75
|
-
silent: env.isVitest
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
export {
|
|
79
|
-
VALID_LOG_LEVELS,
|
|
80
|
-
loadProxyRuntimeEnv,
|
|
81
|
-
serviceLogger,
|
|
82
|
-
terminalLogger
|
|
83
|
-
};
|
|
84
|
-
//# sourceMappingURL=chunk-2JUB4LDU.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/common/logger.ts","../src/common/runtime-env.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { createLogger } from \"@dev-anywhere/shared\";\nimport { CONFIG_PATH, LOG_DIR } from \"./paths.js\";\nimport { loadProxyRuntimeEnv, VALID_LOG_LEVELS } from \"./runtime-env.js\";\n\nconst env = loadProxyRuntimeEnv();\n\n// 直接读 config.json 而不走 config.ts loadConfig:loadConfig 依赖 logger.ts,\n// 反向引用会形成循环。这里只取 logLevel 字段做 best-effort 降级,全量 schema 校验仍由\n// config.ts 在第一次 loadConfig 时执行。\nfunction readConfigLogLevel(): string | undefined {\n if (env.logLevel) return undefined;\n if (!existsSync(CONFIG_PATH)) return undefined;\n try {\n const raw = JSON.parse(readFileSync(CONFIG_PATH, \"utf-8\")) as { logLevel?: unknown };\n if (typeof raw.logLevel !== \"string\") return undefined;\n return (VALID_LOG_LEVELS as readonly string[]).includes(raw.logLevel)\n ? raw.logLevel\n : undefined;\n } catch {\n return undefined;\n }\n}\n\n// 三级 precedence:LOG_LEVEL env > config.logLevel > 各 logger 自己的默认。\nconst overrideLevel = env.logLevel ?? readConfigLogLevel();\n\nexport const serviceLogger = createLogger({\n name: \"service\",\n level: overrideLevel ?? \"info\",\n logDir: LOG_DIR,\n silent: env.isVitest,\n});\n\nexport const terminalLogger = createLogger({\n name: \"terminal\",\n level: overrideLevel ?? \"debug\",\n logDir: LOG_DIR,\n silent: env.isVitest,\n});\n","// 单一入口集中读取 proxy 运行时关心的环境变量,类型化输出,避免 process.env.X\n// 散落在多个文件里各自做 parseInt / 空串判断 / undefined 兜底。\n//\n// 范围:用户面对的运行时旋钮 + 构建/测试模式标志。\n// 不包含:proxy → provider/hook 子进程之间的 plumbing(DEV_ANYWHERE_HOOK_TOKEN /\n// HOOK_URL / HOOK_MARKER / HOOK_EVENT / SESSION_ID 等),这些是内部传参,不是用户旋钮,\n// 留在各自消费点。\n\nexport const VALID_LOG_LEVELS = [\n \"trace\",\n \"debug\",\n \"info\",\n \"warn\",\n \"error\",\n \"fatal\",\n \"silent\",\n] as const;\nexport type LogLevel = (typeof VALID_LOG_LEVELS)[number];\n\nexport interface ProxyRuntimeEnv {\n // RELAY_URL —— 覆盖 config.relays[name].url;用于一次性指向另一个 relay。\n relayUrl: string | undefined;\n // RELAY_PROXY_TOKEN —— 覆盖 config.relays[name].proxyToken。\n relayProxyToken: string | undefined;\n // DEV_ANYWHERE_HOOK_PORT —— 覆盖按 profile 推导的 hook server 端口。\n hookPort: number | undefined;\n // CLAUDE_BIN / CODEX_BIN —— 覆盖 config.agentCli 里的 CLI 可执行文件路径。\n claudeBin: string | undefined;\n codexBin: string | undefined;\n // LOG_LEVEL —— 用户最高优先级;config.logLevel 是次优先;都缺则各 logger 自己 default。\n logLevel: LogLevel | undefined;\n // VITEST —— 测试运行器存在则把 logger 静默,避免污染 vitest 输出。\n isVitest: boolean;\n // NODE_ENV 故意不在这里:env.ts 把它读成 top-level const 让 tsup 静态折叠 + dead-code\n // elimination dev 分支。走函数调用会破坏这个 build-time 优化。\n}\n\nfunction parseLogLevel(value: string | undefined): LogLevel | undefined {\n if (!value) return undefined;\n if ((VALID_LOG_LEVELS as readonly string[]).includes(value)) return value as LogLevel;\n throw new Error(\n `Invalid LOG_LEVEL=${JSON.stringify(value)}; expected one of ${VALID_LOG_LEVELS.join(\", \")}`,\n );\n}\n\nfunction parsePort(value: string | undefined, source: string): number | undefined {\n if (!value) return undefined;\n const port = Number(value);\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n throw new Error(`Invalid ${source}=${JSON.stringify(value)}: expected TCP port 1-65535`);\n }\n return port;\n}\n\nfunction nonEmpty(value: string | undefined): string | undefined {\n return value && value.length > 0 ? value : undefined;\n}\n\nexport function loadProxyRuntimeEnv(env: NodeJS.ProcessEnv = process.env): ProxyRuntimeEnv {\n return {\n relayUrl: nonEmpty(env.RELAY_URL),\n relayProxyToken: nonEmpty(env.RELAY_PROXY_TOKEN),\n hookPort: parsePort(env.DEV_ANYWHERE_HOOK_PORT, \"DEV_ANYWHERE_HOOK_PORT\"),\n claudeBin: nonEmpty(env.CLAUDE_BIN),\n codexBin: nonEmpty(env.CODEX_BIN),\n logLevel: parseLogLevel(env.LOG_LEVEL),\n isVitest: !!env.VITEST,\n };\n}\n"],"mappings":";;;;;;;;AAAA,SAAS,YAAY,oBAAoB;;;ACQlC,IAAM,mBAAmB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAqBA,SAAS,cAAc,OAAiD;AACtE,MAAI,CAAC,MAAO,QAAO;AACnB,MAAK,iBAAuC,SAAS,KAAK,EAAG,QAAO;AACpE,QAAM,IAAI;AAAA,IACR,qBAAqB,KAAK,UAAU,KAAK,CAAC,qBAAqB,iBAAiB,KAAK,IAAI,CAAC;AAAA,EAC5F;AACF;AAEA,SAAS,UAAU,OAA2B,QAAoC;AAChF,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,OAAO,OAAO,KAAK;AACzB,MAAI,CAAC,OAAO,UAAU,IAAI,KAAK,OAAO,KAAK,OAAO,OAAO;AACvD,UAAM,IAAI,MAAM,WAAW,MAAM,IAAI,KAAK,UAAU,KAAK,CAAC,6BAA6B;AAAA,EACzF;AACA,SAAO;AACT;AAEA,SAAS,SAAS,OAA+C;AAC/D,SAAO,SAAS,MAAM,SAAS,IAAI,QAAQ;AAC7C;AAEO,SAAS,oBAAoBA,OAAyB,QAAQ,KAAsB;AACzF,SAAO;AAAA,IACL,UAAU,SAASA,KAAI,SAAS;AAAA,IAChC,iBAAiB,SAASA,KAAI,iBAAiB;AAAA,IAC/C,UAAU,UAAUA,KAAI,wBAAwB,wBAAwB;AAAA,IACxE,WAAW,SAASA,KAAI,UAAU;AAAA,IAClC,UAAU,SAASA,KAAI,SAAS;AAAA,IAChC,UAAU,cAAcA,KAAI,SAAS;AAAA,IACrC,UAAU,CAAC,CAACA,KAAI;AAAA,EAClB;AACF;;;AD/DA,IAAM,MAAM,oBAAoB;AAKhC,SAAS,qBAAyC;AAChD,MAAI,IAAI,SAAU,QAAO;AACzB,MAAI,CAAC,WAAW,WAAW,EAAG,QAAO;AACrC,MAAI;AACF,UAAM,MAAM,KAAK,MAAM,aAAa,aAAa,OAAO,CAAC;AACzD,QAAI,OAAO,IAAI,aAAa,SAAU,QAAO;AAC7C,WAAQ,iBAAuC,SAAS,IAAI,QAAQ,IAChE,IAAI,WACJ;AAAA,EACN,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,IAAM,gBAAgB,IAAI,YAAY,mBAAmB;AAElD,IAAM,gBAAgB,aAAa;AAAA,EACxC,MAAM;AAAA,EACN,OAAO,iBAAiB;AAAA,EACxB,QAAQ;AAAA,EACR,QAAQ,IAAI;AACd,CAAC;AAEM,IAAM,iBAAiB,aAAa;AAAA,EACzC,MAAM;AAAA,EACN,OAAO,iBAAiB;AAAA,EACxB,QAAQ;AAAA,EACR,QAAQ,IAAI;AACd,CAAC;","names":["env"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/common/seq-counter.ts","../src/common/stream-json-schema.ts"],"sourcesContent":["import { mkdirSync, readFileSync, writeFileSync, existsSync } from \"node:fs\";\nimport { sessionPaths } from \"./paths.js\";\n\n/**\n * 轻量级 per-session seq 计数器,持久化到文件\n *\n * 仅存储一个递增整数,proxy 重启后能接续。\n */\nexport class SeqCounter {\n private seq: number = 0;\n private readonly filePath: string;\n\n constructor(sessionId: string, baseDir?: string) {\n const dir = baseDir ?? sessionPaths(sessionId).dir;\n mkdirSync(dir, { recursive: true });\n this.filePath = `${dir}/seq`;\n this.load();\n }\n\n next(): number {\n this.seq++;\n this.save();\n return this.seq;\n }\n\n current(): number {\n return this.seq;\n }\n\n private load(): void {\n if (!existsSync(this.filePath)) return;\n const content = readFileSync(this.filePath, \"utf-8\").trim();\n const parsed = parseInt(content, 10);\n if (isNaN(parsed) || parsed < 0) {\n throw new Error(`Corrupt seq file: ${this.filePath} contains \"${content}\"`);\n }\n this.seq = parsed;\n }\n\n private save(): void {\n writeFileSync(this.filePath, String(this.seq));\n }\n}\n","import { z } from \"zod\";\n\n// Claude CLI stream-json 输出的事件结构定义。\n// 设计原则:\n// - 已知字段严格校验,未知字段 passthrough 保证前向兼容\n// - content block 单独 parse,一个未知 block type 不会导致整条 assistant 事件被丢弃\n// - 新 event/block 类型加到这里后,forwardEvent 添加对应分支即可\n// - 基于 ~/.claude-2.1.116 采样 fixture 验证\n\nconst TextBlockSchema = z\n .object({\n type: z.literal(\"text\"),\n text: z.string(),\n })\n .passthrough();\n\n// Opus extended thinking 的 thinking 字段可能是空字符串(明文被 Anthropic 服务端 redact),\n// 只在 signature 里保留加密内容。signature 始终存在于 thinking block。\nconst ThinkingBlockSchema = z\n .object({\n type: z.literal(\"thinking\"),\n thinking: z.string(),\n signature: z.string().optional(),\n })\n .passthrough();\n\nconst ToolUseBlockSchema = z\n .object({\n type: z.literal(\"tool_use\"),\n id: z.string(),\n name: z.string(),\n input: z.record(z.string(), z.unknown()),\n })\n .passthrough();\n\n// tool_result 的 content 形状多变(string / array of nested blocks),proxy 不解析内容本身\nconst ToolResultBlockSchema = z\n .object({\n type: z.literal(\"tool_result\"),\n tool_use_id: z.string(),\n content: z.unknown(),\n is_error: z.boolean().optional(),\n })\n .passthrough();\n\n// 已知 block 类型的 discriminated union;未知 type 会 safeParse 失败,调用方应跳过而非丢整个事件\nexport const KnownContentBlockSchema = z.discriminatedUnion(\"type\", [\n TextBlockSchema,\n ThinkingBlockSchema,\n ToolUseBlockSchema,\n ToolResultBlockSchema,\n]);\n\n// event 级别 schema。message.content 用 z.array(z.unknown()),不在此层 narrow content block 类型,\n// 让调用方逐 block 跑 KnownContentBlockSchema.safeParse 以宽容未知 block\nconst AssistantEventSchema = z\n .object({\n type: z.literal(\"assistant\"),\n message: z\n .object({\n content: z.array(z.unknown()),\n })\n .passthrough(),\n })\n .passthrough();\n\nconst UserEventSchema = z\n .object({\n type: z.literal(\"user\"),\n message: z\n .object({\n content: z.array(z.unknown()),\n })\n .passthrough(),\n })\n .passthrough();\n\nconst ResultEventSchema = z\n .object({\n type: z.literal(\"result\"),\n subtype: z.string(),\n is_error: z.boolean().optional(),\n })\n .passthrough();\n\n// --include-partial-messages 开启时出现的增量事件。我们消费 content_block_delta 中的\n// text_delta / thinking_delta,其余内层 event(message_start/message_delta/content_block_start 等)忽略。\n// signature_delta 存在但 proxy 不转发 —— signature 是 Anthropic replay 用的加密态,对 UI 无意义。\nconst TextDeltaSchema = z\n .object({\n type: z.literal(\"text_delta\"),\n text: z.string(),\n })\n .passthrough();\n\nconst ThinkingDeltaSchema = z\n .object({\n type: z.literal(\"thinking_delta\"),\n thinking: z.string(),\n })\n .passthrough();\n\nconst SignatureDeltaSchema = z\n .object({\n type: z.literal(\"signature_delta\"),\n signature: z.string(),\n })\n .passthrough();\n\nexport const ContentBlockDeltaSchema = z\n .object({\n type: z.literal(\"content_block_delta\"),\n index: z.number(),\n delta: z.discriminatedUnion(\"type\", [\n TextDeltaSchema,\n ThinkingDeltaSchema,\n SignatureDeltaSchema,\n ]),\n })\n .passthrough();\n\nconst StreamEventSchema = z\n .object({\n type: z.literal(\"stream_event\"),\n event: z.object({ type: z.string() }).passthrough(),\n })\n .passthrough();\n\n// permission-prompt-tool=stdio 模式下,claude 发 control_request 要求审批,\n// proxy handleControlRequest 需写回 control_response 解除工具阻塞。\n// subtype 目前只见过 \"can_use_tool\";request.display_name / permission_suggestions /\n// decision_reason / decision_reason_type / tool_use_id 是 CLI 辅助字段,审批决策只看 tool_name + input。\nconst CanUseToolRequestSchema = z\n .object({\n subtype: z.literal(\"can_use_tool\"),\n tool_name: z.string(),\n input: z.record(z.string(), z.unknown()),\n })\n .passthrough();\n\nexport const ControlRequestEventSchema = z\n .object({\n type: z.literal(\"control_request\"),\n request_id: z.string(),\n request: CanUseToolRequestSchema,\n })\n .passthrough();\n\n// 审批结果回写 shape。proxy 自己写入 stdin,主要用于单元测试断言 wire shape 合法。\nexport const ControlResponseEventSchema = z\n .object({\n type: z.literal(\"control_response\"),\n response: z\n .object({\n subtype: z.literal(\"success\"),\n request_id: z.string(),\n response: z.discriminatedUnion(\"behavior\", [\n z\n .object({\n behavior: z.literal(\"allow\"),\n updatedInput: z.record(z.string(), z.unknown()),\n })\n .passthrough(),\n z\n .object({\n behavior: z.literal(\"deny\"),\n message: z.string(),\n })\n .passthrough(),\n ]),\n })\n .passthrough(),\n })\n .passthrough();\n\n// control_response 是 proxy 写回 claude stdin 的方向,不会出现在 stdout fixture 里;\n// 但 control-request scenario 的采样脚本也把写回 shape 留在 fixture 里用于 round-trip 验证。\n// 并入 union 让通用 canary \"every event is known\" 测试能覆盖它。\nexport const StreamJsonEventSchema = z.discriminatedUnion(\"type\", [\n AssistantEventSchema,\n UserEventSchema,\n ResultEventSchema,\n StreamEventSchema,\n ControlRequestEventSchema,\n ControlResponseEventSchema,\n]);\n\n// schema discriminatedUnion 未覆盖但 proxy 明确静默忽略的 event type。\n// forwardEvent 遇到这些 type 不发 warn,测试也按这份名单判断\"safeParse 失败是否在意\"。\n// 新增一种忽略 type 时只改这里一处。\nexport const IGNORED_EVENT_TYPES: ReadonlySet<string> = new Set([\"system\", \"rate_limit_event\"]);\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/common/config.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, isAbsolute } from \"node:path\";\nimport { z } from \"zod\";\nimport { CONFIG_PATH, PROFILE_NAME, defaultHookPortForProfile } from \"./paths.js\";\nimport { serviceLogger } from \"./logger.js\";\nimport { VALID_LOG_LEVELS, loadProxyRuntimeEnv } from \"./runtime-env.js\";\nimport type { ProviderId } from \"../providers/types.js\";\n\nexport type { LogLevel } from \"./runtime-env.js\";\n\nexport interface ProxyConfig {\n profileName: string;\n relayName: string;\n relayUrl?: string;\n // /proxy 端点的预共享 token, 和 relay 侧 RELAY_PROXY_TOKEN 对应. 公网 relay 必须设置\n relayToken?: string;\n hookPort?: number;\n claudeBin?: string;\n codexBin?: string;\n previewRoots: string[];\n agentCliSuggestions: Record<ProviderId, string[]>;\n sources: {\n relayName: \"cli\" | \"profile\";\n relayUrl: \"env\" | \"file\" | \"none\";\n relayToken: \"env\" | \"file\" | \"none\";\n hookPort: \"env\" | \"default\";\n claudeBin: \"env\" | \"file\" | \"none\";\n codexBin: \"env\" | \"file\" | \"none\";\n };\n}\n\nconst LogLevelSchema = z.enum(VALID_LOG_LEVELS);\n// LogLevel 由 runtime-env.ts 定义并 re-export,schema 用同一组字面量保证两边对齐。\n\nconst RelayTargetSchema = z\n .object({\n url: z.string().optional(),\n proxyToken: z.string().optional(),\n })\n .strict();\n\nconst ProxyProfileSchema = z\n .object({\n relay: z.string().optional(),\n })\n .strict();\n\nconst AgentCliSchema = z\n .object({\n claudeBin: z.string().optional(),\n codexBin: z.string().optional(),\n claudeBinHistory: z.array(z.string()).optional(),\n codexBinHistory: z.array(z.string()).optional(),\n })\n .strict();\n\n// .strict() 在顶层捕获拼错的字段(\"relayss\" / \"profile\"),但 profiles/relays 内部\n// 是 record(用户定义键),不限制键名。\nconst ProxyConfigFileSchema = z\n .object({\n defaultProfile: z.string().optional(),\n profiles: z.record(z.string(), ProxyProfileSchema),\n relays: z.record(z.string(), RelayTargetSchema),\n agentCli: AgentCliSchema.optional(),\n previewRoots: z.array(z.string()).optional(),\n logLevel: LogLevelSchema.optional(),\n })\n .strict();\n\ntype ProxyConfigFile = z.infer<typeof ProxyConfigFileSchema>;\ntype RelayTargetConfig = z.infer<typeof RelayTargetSchema>;\ntype AgentCliConfig = z.infer<typeof AgentCliSchema>;\n\nfunction readConfigFile(): ProxyConfigFile {\n if (!existsSync(CONFIG_PATH)) {\n throw new Error(`Dev Anywhere config not found at ${CONFIG_PATH}. Run \"dev-anywhere init\".`);\n }\n let raw: unknown;\n try {\n raw = JSON.parse(readFileSync(CONFIG_PATH, \"utf-8\"));\n } catch (err) {\n throw new Error(\n `${CONFIG_PATH} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,\n { cause: err },\n );\n }\n const parsed = ProxyConfigFileSchema.safeParse(raw);\n if (!parsed.success) {\n const issues = parsed.error.issues\n .map(\n (issue) => ` ${issue.path.length > 0 ? issue.path.join(\".\") : \"(root)\"}: ${issue.message}`,\n )\n .join(\"\\n\");\n throw new Error(`Invalid config at ${CONFIG_PATH}:\\n${issues}`);\n }\n return parsed.data;\n}\n\nfunction agentCliField(provider: ProviderId): \"claudeBin\" | \"codexBin\" {\n return provider === \"claude\" ? \"claudeBin\" : \"codexBin\";\n}\n\nfunction agentCliHistoryField(provider: ProviderId): \"claudeBinHistory\" | \"codexBinHistory\" {\n return provider === \"claude\" ? \"claudeBinHistory\" : \"codexBinHistory\";\n}\n\nfunction validateAgentCliPath(path: string): string {\n const normalized = path.trim();\n if (!normalized) throw new Error(\"请输入 CLI 路径\");\n if (!isAbsolute(normalized)) throw new Error(\"CLI 路径必须是绝对路径\");\n return normalized;\n}\n\nfunction uniqueAbsolutePaths(paths: Array<string | undefined>): string[] {\n const seen = new Set<string>();\n const result: string[] = [];\n for (const path of paths) {\n const normalized = path?.trim();\n if (!normalized || !isAbsolute(normalized) || seen.has(normalized)) continue;\n seen.add(normalized);\n result.push(normalized);\n }\n return result;\n}\n\nfunction resolveRelayConfig(\n fromFile: ProxyConfigFile,\n requestedRelayName?: string,\n): {\n relayName: string;\n relayNameSource: ProxyConfig[\"sources\"][\"relayName\"];\n relay: RelayTargetConfig;\n} {\n const profile = fromFile.profiles[PROFILE_NAME];\n if (!profile) {\n const available = Object.keys(fromFile.profiles).sort();\n throw new Error(\n `Unknown profile \"${PROFILE_NAME}\". Available profiles: ${available.length > 0 ? available.join(\", \") : \"(none)\"}`,\n );\n }\n\n const relayName = requestedRelayName?.trim() || profile.relay?.trim();\n if (!relayName) {\n throw new Error(`Profile \"${PROFILE_NAME}\" must specify a relay.`);\n }\n\n const relay = fromFile.relays[relayName];\n if (!relay) {\n const available = Object.keys(fromFile.relays).sort();\n throw new Error(\n `Unknown relay \"${relayName}\". Available relays: ${available.length > 0 ? available.join(\", \") : \"(none)\"}`,\n );\n }\n\n return {\n relayName,\n relayNameSource: requestedRelayName?.trim() ? \"cli\" : \"profile\",\n relay,\n };\n}\n\nexport function loadConfig(options?: { relayName?: string }): ProxyConfig {\n const env = loadProxyRuntimeEnv();\n const fromFile = readConfigFile();\n const agentCli = fromFile.agentCli ?? {};\n const resolved = resolveRelayConfig(fromFile, options?.relayName);\n const claudeBin = env.claudeBin ?? agentCli.claudeBin;\n const codexBin = env.codexBin ?? agentCli.codexBin;\n const config: ProxyConfig = {\n profileName: PROFILE_NAME,\n relayName: resolved.relayName,\n relayUrl: env.relayUrl ?? resolved.relay.url,\n relayToken: env.relayProxyToken ?? resolved.relay.proxyToken,\n hookPort: env.hookPort ?? defaultHookPortForProfile(PROFILE_NAME),\n claudeBin,\n codexBin,\n previewRoots: uniqueAbsolutePaths(fromFile.previewRoots ?? []),\n agentCliSuggestions: {\n claude: uniqueAbsolutePaths([\n env.claudeBin,\n agentCli.claudeBin,\n ...(agentCli.claudeBinHistory ?? []),\n ]),\n codex: uniqueAbsolutePaths([\n env.codexBin,\n agentCli.codexBin,\n ...(agentCli.codexBinHistory ?? []),\n ]),\n },\n sources: {\n relayName: resolved.relayNameSource,\n relayUrl: env.relayUrl ? \"env\" : resolved.relay.url ? \"file\" : \"none\",\n relayToken: env.relayProxyToken ? \"env\" : resolved.relay.proxyToken ? \"file\" : \"none\",\n hookPort: env.hookPort !== undefined ? \"env\" : \"default\",\n claudeBin: env.claudeBin ? \"env\" : agentCli.claudeBin ? \"file\" : \"none\",\n codexBin: env.codexBin ? \"env\" : agentCli.codexBin ? \"file\" : \"none\",\n },\n };\n\n serviceLogger.info(\n {\n profile: config.profileName,\n relayName: config.relayName,\n relayNameSource: config.sources.relayName,\n relayUrl: config.relayUrl ?? \"(unset)\",\n relayUrlSource: config.sources.relayUrl,\n relayTokenSource: config.sources.relayToken,\n hookPort: config.hookPort,\n hookPortSource: config.sources.hookPort,\n claudeBinSource: config.sources.claudeBin,\n codexBinSource: config.sources.codexBin,\n },\n \"Config loaded\",\n );\n\n return config;\n}\n\nexport function buildProviderEnv(\n config: ProxyConfig,\n baseEnv: NodeJS.ProcessEnv = process.env,\n): NodeJS.ProcessEnv {\n return {\n ...baseEnv,\n ...(config.claudeBin ? { CLAUDE_BIN: config.claudeBin } : {}),\n ...(config.codexBin ? { CODEX_BIN: config.codexBin } : {}),\n };\n}\n\nfunction updateAgentCliConfig(\n config: AgentCliConfig,\n provider: ProviderId,\n path: string,\n): AgentCliConfig {\n const field = agentCliField(provider);\n const historyField = agentCliHistoryField(provider);\n const history = uniqueAbsolutePaths([path, ...(config[historyField] ?? [])]).slice(0, 8);\n return {\n ...config,\n [field]: path,\n [historyField]: history,\n };\n}\n\nexport function saveAgentCliPath(provider: ProviderId, path: string): void {\n const normalized = validateAgentCliPath(path);\n const fromFile = readConfigFile();\n fromFile.agentCli = updateAgentCliConfig(fromFile.agentCli ?? {}, provider, normalized);\n mkdirSync(dirname(CONFIG_PATH), { recursive: true });\n writeFileSync(CONFIG_PATH, `${JSON.stringify(fromFile, null, 2)}\\n`, \"utf-8\");\n}\n"],"mappings":"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","names":[]}
|
package/dist/chunk-ORZTFYXR.js
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/common/osc-extractor.ts
|
|
4
|
-
var OSC_PATTERN = /\x1b\](\d+);([^\x07\x1b]*?)(?:\x07|\x1b\\)/g;
|
|
5
|
-
function extractOscSequences(rawData) {
|
|
6
|
-
const regex = new RegExp(OSC_PATTERN.source, OSC_PATTERN.flags);
|
|
7
|
-
const matches = [];
|
|
8
|
-
let match;
|
|
9
|
-
while ((match = regex.exec(rawData)) !== null) {
|
|
10
|
-
matches.push({ code: parseInt(match[1], 10), text: match[2] });
|
|
11
|
-
}
|
|
12
|
-
return matches;
|
|
13
|
-
}
|
|
14
|
-
function lastSequence(matches, code) {
|
|
15
|
-
for (let i = matches.length - 1; i >= 0; i -= 1) {
|
|
16
|
-
if (matches[i].code === code) return matches[i];
|
|
17
|
-
}
|
|
18
|
-
return void 0;
|
|
19
|
-
}
|
|
20
|
-
function isCodexActionRequiredTitle(title) {
|
|
21
|
-
return /\bAction Required\b/i.test(title);
|
|
22
|
-
}
|
|
23
|
-
function extractOscSignals(rawData, provider) {
|
|
24
|
-
const matches = extractOscSequences(rawData);
|
|
25
|
-
if (matches.length === 0) return null;
|
|
26
|
-
const osc0 = lastSequence(matches, 0);
|
|
27
|
-
const osc9 = lastSequence(matches, 9);
|
|
28
|
-
if (osc9) {
|
|
29
|
-
if (osc9.text.includes("waiting for your input") || osc9.text.trim() === "4;0;") {
|
|
30
|
-
return { state: "turn_complete", ...osc0 ? { title: osc0.text } : {} };
|
|
31
|
-
}
|
|
32
|
-
if (osc9.text.includes("needs your permission")) {
|
|
33
|
-
const toolMatch = osc9.text.match(/permission.*?:\s*(\S+)/);
|
|
34
|
-
return {
|
|
35
|
-
state: "approval_wait",
|
|
36
|
-
...osc0 ? { title: osc0.text } : {},
|
|
37
|
-
...toolMatch?.[1] ? { tool: toolMatch[1] } : {}
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
if (provider === "codex" && osc0 && isCodexActionRequiredTitle(osc0.text)) {
|
|
42
|
-
return { state: "approval_wait", title: osc0.text };
|
|
43
|
-
}
|
|
44
|
-
if (osc0 && !osc9) {
|
|
45
|
-
return { state: "mid_pause", title: osc0.text };
|
|
46
|
-
}
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// src/common/pty-approval-state.ts
|
|
51
|
-
function shouldReleaseApprovalWait(options) {
|
|
52
|
-
if (options.currentState !== "approval_wait") return false;
|
|
53
|
-
return options.signalState !== void 0 && options.signalState !== "approval_wait";
|
|
54
|
-
}
|
|
55
|
-
function stateAfterApprovalRelease(signalState) {
|
|
56
|
-
return signalState && signalState !== "approval_wait" ? signalState : "working";
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// src/common/state-machine.ts
|
|
60
|
-
function computeAbsorbingSet(transitions) {
|
|
61
|
-
const absorbing = /* @__PURE__ */ new Set();
|
|
62
|
-
const entries = Object.entries(transitions);
|
|
63
|
-
for (const [s, outs] of entries) {
|
|
64
|
-
if (outs.length === 0) absorbing.add(s);
|
|
65
|
-
}
|
|
66
|
-
let changed = true;
|
|
67
|
-
while (changed) {
|
|
68
|
-
changed = false;
|
|
69
|
-
for (const [s, outs] of entries) {
|
|
70
|
-
if (absorbing.has(s)) continue;
|
|
71
|
-
if (outs.length > 0 && outs.every((t) => absorbing.has(t))) {
|
|
72
|
-
absorbing.add(s);
|
|
73
|
-
changed = true;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return absorbing;
|
|
78
|
-
}
|
|
79
|
-
function createFSM(def) {
|
|
80
|
-
let state = def.initial;
|
|
81
|
-
const absorbing = computeAbsorbingSet(def.transitions);
|
|
82
|
-
const tryTransitionTo = (to) => {
|
|
83
|
-
const allowed = def.transitions[state];
|
|
84
|
-
if (!allowed?.includes(to)) {
|
|
85
|
-
def.onRejected?.(state, to, absorbing.has(state));
|
|
86
|
-
return false;
|
|
87
|
-
}
|
|
88
|
-
const from = state;
|
|
89
|
-
state = to;
|
|
90
|
-
def.onTransition?.(from, to);
|
|
91
|
-
return true;
|
|
92
|
-
};
|
|
93
|
-
return {
|
|
94
|
-
current: () => state,
|
|
95
|
-
is: (s) => state === s,
|
|
96
|
-
isIn: (ss) => ss.includes(state),
|
|
97
|
-
canTransitionTo: (to) => def.transitions[state]?.includes(to) ?? false,
|
|
98
|
-
transitionTo: (to) => {
|
|
99
|
-
if (!tryTransitionTo(to)) {
|
|
100
|
-
throw new Error(`Invalid FSM transition: ${state} -> ${to}`);
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
tryTransitionTo,
|
|
104
|
-
isInAbsorbingState: () => absorbing.has(state)
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
function defineFSM(transitions) {
|
|
108
|
-
const absorbing = computeAbsorbingSet(transitions);
|
|
109
|
-
return {
|
|
110
|
-
canTransition: (from, to) => transitions[from]?.includes(to) ?? false,
|
|
111
|
-
isAbsorbing: (state) => absorbing.has(state)
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export {
|
|
116
|
-
extractOscSequences,
|
|
117
|
-
extractOscSignals,
|
|
118
|
-
shouldReleaseApprovalWait,
|
|
119
|
-
stateAfterApprovalRelease,
|
|
120
|
-
createFSM,
|
|
121
|
-
defineFSM
|
|
122
|
-
};
|
|
123
|
-
//# sourceMappingURL=chunk-ORZTFYXR.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/common/osc-extractor.ts","../src/common/pty-approval-state.ts","../src/common/state-machine.ts"],"sourcesContent":["// OSC 0: 窗口标题 -- ESC ] 0 ; <title> BEL/ST\n// OSC 9: 通知 -- ESC ] 9 ; <text> BEL/ST\n// 每次调用创建新的 regex 实例避免 g flag 导致的 lastIndex 状态泄漏\n// eslint-disable-next-line no-control-regex\nconst OSC_PATTERN = /\\x1b\\](\\d+);([^\\x07\\x1b]*?)(?:\\x07|\\x1b\\\\)/g;\n\nexport type PtySemanticState = \"working\" | \"turn_complete\" | \"approval_wait\" | \"mid_pause\";\ntype PtySignalProvider = \"claude\" | \"codex\";\n\ninterface PtyStateEvent {\n state: PtySemanticState;\n title?: string;\n tool?: string;\n}\n\ninterface OscSequence {\n code: number;\n text: string;\n}\n\nexport function extractOscSequences(rawData: string): OscSequence[] {\n const regex = new RegExp(OSC_PATTERN.source, OSC_PATTERN.flags);\n const matches: OscSequence[] = [];\n\n let match: RegExpExecArray | null;\n while ((match = regex.exec(rawData)) !== null) {\n matches.push({ code: parseInt(match[1], 10), text: match[2] });\n }\n\n return matches;\n}\n\nfunction lastSequence(matches: OscSequence[], code: number): OscSequence | undefined {\n for (let i = matches.length - 1; i >= 0; i -= 1) {\n if (matches[i].code === code) return matches[i];\n }\n return undefined;\n}\n\nfunction isCodexActionRequiredTitle(title: string): boolean {\n return /\\bAction Required\\b/i.test(title);\n}\n\n// 从 PTY 原始数据中提取 OSC 语义信号。\n// OSC 9 优先级高于 OSC 0,无匹配时返回 null。\nexport function extractOscSignals(\n rawData: string,\n provider?: PtySignalProvider,\n): PtyStateEvent | null {\n const matches = extractOscSequences(rawData);\n\n if (matches.length === 0) return null;\n\n const osc0 = lastSequence(matches, 0);\n\n // OSC 9 优先级更高,包含具体的语义信号;同帧 OSC 0 仍保留 title 给 UI。\n const osc9 = lastSequence(matches, 9);\n if (osc9) {\n if (osc9.text.includes(\"waiting for your input\") || osc9.text.trim() === \"4;0;\") {\n return { state: \"turn_complete\", ...(osc0 ? { title: osc0.text } : {}) };\n }\n if (osc9.text.includes(\"needs your permission\")) {\n const toolMatch = osc9.text.match(/permission.*?:\\s*(\\S+)/);\n return {\n state: \"approval_wait\",\n ...(osc0 ? { title: osc0.text } : {}),\n ...(toolMatch?.[1] ? { tool: toolMatch[1] } : {}),\n };\n }\n }\n\n if (provider === \"codex\" && osc0 && isCodexActionRequiredTitle(osc0.text)) {\n return { state: \"approval_wait\", title: osc0.text };\n }\n\n // 仅有 OSC 0(标题/spinner 变化)时视为 MID_PAUSE\n if (osc0 && !osc9) {\n return { state: \"mid_pause\", title: osc0.text };\n }\n\n return null;\n}\n","import type { PtySemanticState } from \"./osc-extractor.js\";\n\nexport function shouldReleaseApprovalWait(options: {\n currentState: PtySemanticState;\n signalState?: PtySemanticState;\n}): boolean {\n if (options.currentState !== \"approval_wait\") return false;\n return options.signalState !== undefined && options.signalState !== \"approval_wait\";\n}\n\nexport function stateAfterApprovalRelease(signalState?: PtySemanticState): PtySemanticState {\n return signalState && signalState !== \"approval_wait\" ? signalState : \"working\";\n}\n","// 有限状态机 helper\n//\n// 提供显式转换表的小型 FSM,区分两类调用模式:\n// - transitionTo(throw):同步确定性流程里调用,非法转移代表 bug,立即暴露\n// - tryTransitionTo(bool):异步事件回调里调用,非法转移可能是吸收态残余事件,调用方按\n// isInAbsorbingState() 分级日志\n//\n// 吸收态采用传递闭包定义:终态(transitions=[])或所有出边都指向吸收态的状态都算吸收。\n// 这样 SessionState.ERROR (→[TERMINATED]) 和 RelayConnectionState.CLOSED ([]) 都被\n// 自动识别为吸收态,不用在 caller 里硬编码状态名。\n//\n// onTransition/onRejected 仅做日志/观测,不应 throw。\n\ninterface FSMDef<S extends string> {\n initial: S;\n // from-state → 允许转入的 to-state 列表;终态对应空数组\n transitions: Record<S, readonly S[]>;\n // 合法转换发生后触发,典型用法是结构化日志\n onTransition?: (from: S, to: S) => void;\n // tryTransitionTo 非法转移时触发;isAbsorbing 指示 from 是否吸收态(晚到残余 vs. 真非法)\n onRejected?: (from: S, to: S, isAbsorbing: boolean) => void;\n}\n\ninterface FSM<S extends string> {\n current(): S;\n is(state: S): boolean;\n isIn(states: readonly S[]): boolean;\n canTransitionTo(to: S): boolean;\n // 非法转换抛 Error;同步流程里当 assert 用\n transitionTo(to: S): void;\n // 非法转换返回 false,不抛;异步回调里用,配合 isInAbsorbingState 分级日志\n tryTransitionTo(to: S): boolean;\n // 当前是否在吸收态(传递闭包)\n isInAbsorbingState(): boolean;\n}\n\n// 计算吸收态集合:终态 + 所有出边都指向吸收态的状态,迭代至不动点\nfunction computeAbsorbingSet<S extends string>(transitions: Record<S, readonly S[]>): Set<S> {\n const absorbing = new Set<S>();\n const entries = Object.entries(transitions) as Array<[S, readonly S[]]>;\n for (const [s, outs] of entries) {\n if (outs.length === 0) absorbing.add(s);\n }\n let changed = true;\n while (changed) {\n changed = false;\n for (const [s, outs] of entries) {\n if (absorbing.has(s)) continue;\n if (outs.length > 0 && outs.every((t) => absorbing.has(t))) {\n absorbing.add(s);\n changed = true;\n }\n }\n }\n return absorbing;\n}\n\nexport function createFSM<S extends string>(def: FSMDef<S>): FSM<S> {\n let state = def.initial;\n const absorbing = computeAbsorbingSet(def.transitions);\n const tryTransitionTo = (to: S): boolean => {\n const allowed = def.transitions[state];\n if (!allowed?.includes(to)) {\n def.onRejected?.(state, to, absorbing.has(state));\n return false;\n }\n const from = state;\n state = to;\n def.onTransition?.(from, to);\n return true;\n };\n return {\n current: () => state,\n is: (s) => state === s,\n isIn: (ss) => ss.includes(state),\n canTransitionTo: (to) => def.transitions[state]?.includes(to) ?? false,\n transitionTo: (to) => {\n if (!tryTransitionTo(to)) {\n throw new Error(`Invalid FSM transition: ${state} -> ${to}`);\n }\n },\n tryTransitionTo,\n isInAbsorbingState: () => absorbing.has(state),\n };\n}\n\n// 无内部 state 的 FSM 视图,供 state 存在外部(如 SessionInfo、DB 行)的 per-instance 场景使用。\n// 调用方自行传入 from,canTransition 校验;吸收态判定通过 isAbsorbing(state) 提供。\ninterface StatelessFSM<S extends string> {\n canTransition(from: S, to: S): boolean;\n isAbsorbing(state: S): boolean;\n}\n\nexport function defineFSM<S extends string>(transitions: Record<S, readonly S[]>): StatelessFSM<S> {\n const absorbing = computeAbsorbingSet(transitions);\n return {\n canTransition: (from, to) => transitions[from]?.includes(to) ?? false,\n isAbsorbing: (state) => absorbing.has(state),\n };\n}\n"],"mappings":"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","names":[]}
|