@emqo/claudebridge 0.7.0 → 0.9.0

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.
@@ -1,73 +1,5 @@
1
1
  import "dotenv/config";
2
- import { Endpoint } from "./keys.js";
3
- export interface MemoryConfig {
4
- enabled: boolean;
5
- auto_summary: boolean;
6
- max_memories: number;
7
- }
8
- export interface SkillConfig {
9
- enabled: boolean;
10
- }
11
- export interface AgentConfig {
12
- allowed_tools: string[];
13
- permission_mode: string;
14
- max_turns: number;
15
- max_budget_usd: number;
16
- system_prompt: string;
17
- cwd: string;
18
- timeout_seconds: number;
19
- max_parallel: number;
20
- memory: MemoryConfig;
21
- skill: SkillConfig;
22
- }
23
- export interface WorkspaceConfig {
24
- base_dir: string;
25
- isolation: boolean;
26
- }
27
- export interface AccessConfig {
28
- allowed_users: string[];
29
- allowed_groups: string[];
30
- }
31
- export interface TelegramConfig {
32
- enabled: boolean;
33
- token: string;
34
- chunk_size: number;
35
- }
36
- export interface DiscordConfig {
37
- enabled: boolean;
38
- token: string;
39
- chunk_size: number;
40
- }
41
- export interface RedisConfig {
42
- enabled: boolean;
43
- url: string;
44
- }
45
- export interface WebhookConfig {
46
- enabled: boolean;
47
- port: number;
48
- token: string;
49
- github_secret: string;
50
- }
51
- export interface CronEntry {
52
- schedule_minutes: number;
53
- user_id: string;
54
- platform: string;
55
- chat_id: string;
56
- description: string;
57
- }
58
- export interface Config {
59
- endpoints: Endpoint[];
60
- agent: AgentConfig;
61
- workspace: WorkspaceConfig;
62
- access: AccessConfig;
63
- redis: RedisConfig;
64
- locale: string;
65
- platforms: {
66
- telegram: TelegramConfig;
67
- discord: DiscordConfig;
68
- };
69
- webhook: WebhookConfig;
70
- cron: CronEntry[];
71
- }
2
+ export type { Config, Endpoint, AgentConfig, MemoryConfig, SkillConfig, SessionConfig, WorkspaceConfig, AccessConfig, TelegramConfig, DiscordConfig, RedisConfig, WebhookConfig, CronEntry, } from "./schema.js";
3
+ import type { Config } from "./schema.js";
72
4
  export declare function loadConfig(path?: string): Config;
73
5
  export declare function reloadConfig(): Config;
@@ -1,50 +1,21 @@
1
1
  import { readFileSync } from "fs";
2
2
  import { parse } from "yaml";
3
3
  import "dotenv/config";
4
+ import { ConfigSchema } from "./schema.js";
4
5
  let _configPath = "config.yaml";
5
6
  export function loadConfig(path) {
6
7
  if (path)
7
8
  _configPath = path;
8
- const raw = parse(readFileSync(_configPath, "utf-8"));
9
- const c = {
10
- endpoints: raw.endpoints || [],
11
- agent: {
12
- ...raw.agent,
13
- timeout_seconds: raw.agent?.timeout_seconds ?? 300,
14
- max_parallel: raw.agent?.max_parallel ?? 1,
15
- memory: { enabled: true, auto_summary: true, max_memories: 50, ...raw.agent?.memory },
16
- skill: { enabled: true, ...raw.agent?.skill },
17
- },
18
- workspace: raw.workspace,
19
- access: raw.access || { allowed_users: [], allowed_groups: [] },
20
- redis: raw.redis || { enabled: false, url: "" },
21
- locale: raw.locale || "en",
22
- platforms: raw.platforms,
23
- webhook: { enabled: false, port: 3100, token: "", github_secret: "", ...raw.webhook },
24
- cron: raw.cron || [],
25
- };
26
- // defaults for each endpoint
27
- for (const ep of c.endpoints) {
28
- ep.name = ep.name || "default";
29
- ep.base_url = ep.base_url || "";
30
- ep.api_key = ep.api_key || "";
31
- ep.model = ep.model || "";
32
- }
33
- // env fallback: single endpoint from env vars
34
- if (!c.endpoints.length && process.env.ANTHROPIC_API_KEY) {
35
- c.endpoints.push({
36
- name: "env-default",
37
- base_url: process.env.ANTHROPIC_BASE_URL || "",
38
- api_key: process.env.ANTHROPIC_API_KEY,
39
- model: process.env.ANTHROPIC_MODEL || "",
40
- });
9
+ const raw = parse(readFileSync(_configPath, "utf-8")) || {};
10
+ const result = ConfigSchema.safeParse(raw);
11
+ if (!result.success) {
12
+ const issues = result.error.issues.map(i => ` ${i.path.join(".")}: ${i.message}`).join("\n");
13
+ throw new Error(`Config validation failed:\n${issues}`);
41
14
  }
15
+ const c = result.data;
42
16
  c.redis.url = c.redis.url || process.env.REDIS_URL || "";
43
- c.platforms.telegram.token =
44
- c.platforms.telegram.token || process.env.TELEGRAM_BOT_TOKEN || "";
45
- c.platforms.discord = c.platforms.discord || { enabled: false, token: "", chunk_size: 1900 };
46
- c.platforms.discord.token =
47
- c.platforms.discord.token || process.env.DISCORD_BOT_TOKEN || "";
17
+ c.platforms.telegram.token = c.platforms.telegram.token || process.env.TELEGRAM_BOT_TOKEN || "";
18
+ c.platforms.discord.token = c.platforms.discord.token || process.env.DISCORD_BOT_TOKEN || "";
48
19
  return c;
49
20
  }
50
21
  export function reloadConfig() {
package/dist/core/i18n.js CHANGED
@@ -1,6 +1,6 @@
1
1
  const messages = {
2
2
  en: {
3
- help: "ClaudeBridge ready.\n\nManagement commands:\n/new - clear session\n/usage - your stats\n/allusage - all stats\n/history - recent chats\n/model - endpoints info\n/status - auto task status\n/reload - reload config\n/help - show this help\n\nJust chat naturally to manage memories, tasks, reminders, and more — Claude handles it all.",
3
+ help: "ClaudeBridge ready.\n\nManagement commands:\n/new - clear session\n/usage - your stats\n/allusage - all stats\n/history - recent chats\n/model - endpoints info\n/status - auto task status\n/sessions - active sessions\n/reload - reload config\n/help - show this help\n\nJust chat naturally to manage memories, tasks, reminders, and more — Claude handles it all.",
4
4
  session_cleared: "Session cleared.",
5
5
  no_usage: "No usage data.",
6
6
  no_history: "No history.",
@@ -23,9 +23,13 @@ const messages = {
23
23
  no_auto_tasks: "No auto tasks found.",
24
24
  status_report: "Auto Task Status:",
25
25
  chain_progress: "Chain #{id} progress: {done}/{total} done{cost}",
26
+ sessions_list: "Active sessions:",
27
+ no_sessions: "No active sessions.",
28
+ session_created: "New session created: {label}",
29
+ session_limit: "Session limit reached. Oldest idle session closed.",
26
30
  },
27
31
  zh: {
28
- help: "ClaudeBridge 就绪。\n\n管理命令:\n/new - 清除会话\n/usage - 你的用量\n/allusage - 所有用量\n/history - 最近对话\n/model - 端点信息\n/status - 自动任务状态\n/reload - 重载配置\n/help - 显示帮助\n\n直接对话即可管理记忆、任务、提醒等 — Claude 会自动处理。",
32
+ help: "ClaudeBridge 就绪。\n\n管理命令:\n/new - 清除会话\n/usage - 你的用量\n/allusage - 所有用量\n/history - 最近对话\n/model - 端点信息\n/status - 自动任务状态\n/sessions - 活跃会话\n/reload - 重载配置\n/help - 显示帮助\n\n直接对话即可管理记忆、任务、提醒等 — Claude 会自动处理。",
29
33
  session_cleared: "会话已清除。",
30
34
  no_usage: "暂无用量数据。",
31
35
  no_history: "暂无历史记录。",
@@ -48,18 +52,22 @@ const messages = {
48
52
  no_auto_tasks: "暂无自动任务。",
49
53
  status_report: "自动任务状态:",
50
54
  chain_progress: "任务链 #{id} 进度:{done}/{total} 完成{cost}",
55
+ sessions_list: "活跃会话:",
56
+ no_sessions: "暂无活跃会话。",
57
+ session_created: "新会话已创建:{label}",
58
+ session_limit: "会话数已达上限,已关闭最旧的空闲会话。",
51
59
  },
52
60
  };
53
61
  const commandDescriptions = {
54
62
  en: {
55
63
  new: "Clear session", usage: "Your usage stats", allusage: "All users usage",
56
64
  history: "Recent conversations", model: "Current model/endpoints", status: "Auto task status",
57
- reload: "Reload config", help: "Show all commands",
65
+ sessions: "Active sessions", reload: "Reload config", help: "Show all commands",
58
66
  },
59
67
  zh: {
60
68
  new: "清除会话", usage: "你的用量", allusage: "所有用量",
61
69
  history: "最近对话", model: "端点信息", status: "自动任务状态",
62
- reload: "重载配置", help: "显示帮助",
70
+ sessions: "活跃会话", reload: "重载配置", help: "显示帮助",
63
71
  },
64
72
  };
65
73
  export function t(locale, key, vars) {
@@ -1,18 +1,10 @@
1
- export interface Endpoint {
2
- name: string;
3
- base_url: string;
4
- api_key: string;
5
- model: string;
6
- }
7
- /** Round-robin endpoint rotation with cooldown on failure */
1
+ import type { Endpoint } from "./config.js";
2
+ /** Simple round-robin endpoint selector — CLI handles its own auth */
8
3
  export declare class EndpointRotator {
9
4
  private endpoints;
10
5
  private index;
11
- private cooldowns;
12
- private cooldownMs;
13
6
  constructor(endpoints: Endpoint[]);
14
7
  next(): Endpoint;
15
- markFailed(ep: Endpoint): void;
16
8
  get count(): number;
17
9
  list(): {
18
10
  name: string;
package/dist/core/keys.js CHANGED
@@ -1,31 +1,17 @@
1
- /** Round-robin endpoint rotation with cooldown on failure */
1
+ /** Simple round-robin endpoint selector CLI handles its own auth */
2
2
  export class EndpointRotator {
3
3
  endpoints;
4
4
  index = 0;
5
- cooldowns = new Map();
6
- cooldownMs = 60_000;
7
5
  constructor(endpoints) {
8
- this.endpoints = endpoints.filter(e => e.api_key);
6
+ this.endpoints = endpoints;
9
7
  }
10
8
  next() {
11
- const now = Date.now();
12
- const len = this.endpoints.length;
13
- for (let i = 0; i < len; i++) {
14
- const idx = (this.index + i) % len;
15
- if ((this.cooldowns.get(idx) || 0) <= now) {
16
- this.index = (idx + 1) % len;
17
- return this.endpoints[idx];
18
- }
19
- }
20
- const idx = this.index;
21
- this.index = (idx + 1) % len;
9
+ if (!this.endpoints.length)
10
+ throw new Error("No endpoints configured");
11
+ const idx = this.index % this.endpoints.length;
12
+ this.index = (idx + 1) % this.endpoints.length;
22
13
  return this.endpoints[idx];
23
14
  }
24
- markFailed(ep) {
25
- const idx = this.endpoints.indexOf(ep);
26
- if (idx >= 0)
27
- this.cooldowns.set(idx, Date.now() + this.cooldownMs);
28
- }
29
15
  get count() {
30
16
  return this.endpoints.length;
31
17
  }
@@ -33,8 +19,7 @@ export class EndpointRotator {
33
19
  return this.endpoints.map(e => ({ name: e.name, model: e.model }));
34
20
  }
35
21
  reload(endpoints) {
36
- this.endpoints = endpoints.filter(e => e.api_key);
22
+ this.endpoints = endpoints;
37
23
  this.index = 0;
38
- this.cooldowns.clear();
39
24
  }
40
25
  }
@@ -1,12 +1,16 @@
1
- /** Per-user lock with Redis backend, memory fallback */
2
- export declare class UserLock {
1
+ /** Per-session lock with Redis backend, memory fallback.
2
+ * Renamed from UserLock to SessionLock — key is now subSessionId, not userId.
3
+ * Multiple sub-sessions for the same user can run concurrently. */
4
+ export declare class SessionLock {
3
5
  private memLocks;
4
6
  private redis;
5
7
  private prefix;
6
8
  private ttl;
7
9
  constructor(redisUrl?: string);
8
- acquire(userId: string): Promise<() => void>;
9
- isLocked(userId: string): boolean;
10
+ acquire(sessionId: string): Promise<() => void>;
11
+ isLocked(sessionId: string): boolean;
12
+ /** Return which of the given keys are currently locked (memory backend only) */
13
+ isAnyLocked(keys: string[]): string[];
10
14
  private _acquireMem;
11
15
  private _acquireRedis;
12
16
  }
package/dist/core/lock.js CHANGED
@@ -1,16 +1,20 @@
1
1
  import Redis from "ioredis";
2
- /** Per-user lock with Redis backend, memory fallback */
3
- export class UserLock {
2
+ import { log as rootLog } from "./logger.js";
3
+ const log = rootLog.child("lock");
4
+ /** Per-session lock with Redis backend, memory fallback.
5
+ * Renamed from UserLock to SessionLock — key is now subSessionId, not userId.
6
+ * Multiple sub-sessions for the same user can run concurrently. */
7
+ export class SessionLock {
4
8
  memLocks = new Map();
5
9
  redis = null;
6
- prefix = "claudebridge:lock:";
10
+ prefix = "claudebridge:lock:session:";
7
11
  ttl = 300; // 5 min max lock
8
12
  constructor(redisUrl) {
9
13
  if (redisUrl) {
10
14
  try {
11
15
  this.redis = new Redis(redisUrl, { maxRetriesPerRequest: 1, lazyConnect: true });
12
16
  this.redis.connect().catch(() => {
13
- console.warn("[lock] Redis unavailable, falling back to memory");
17
+ log.warn("Redis unavailable, falling back to memory");
14
18
  this.redis = null;
15
19
  });
16
20
  }
@@ -19,35 +23,42 @@ export class UserLock {
19
23
  }
20
24
  }
21
25
  }
22
- async acquire(userId) {
26
+ async acquire(sessionId) {
23
27
  if (this.redis)
24
- return this._acquireRedis(userId);
25
- return this._acquireMem(userId);
28
+ return this._acquireRedis(sessionId);
29
+ return this._acquireMem(sessionId);
26
30
  }
27
- isLocked(userId) {
31
+ isLocked(sessionId) {
28
32
  if (this.redis)
29
33
  return false; // can't sync-check redis, rely on acquire
30
- return this.memLocks.has(userId);
34
+ return this.memLocks.has(sessionId);
31
35
  }
32
- async _acquireMem(userId) {
33
- while (this.memLocks.has(userId)) {
34
- await this.memLocks.get(userId);
36
+ /** Return which of the given keys are currently locked (memory backend only) */
37
+ isAnyLocked(keys) {
38
+ return keys.filter(k => this.memLocks.has(k));
39
+ }
40
+ async _acquireMem(sessionId) {
41
+ while (this.memLocks.has(sessionId)) {
42
+ await this.memLocks.get(sessionId);
35
43
  }
36
44
  let release;
37
45
  const p = new Promise((r) => (release = r));
38
- this.memLocks.set(userId, p);
46
+ this.memLocks.set(sessionId, p);
39
47
  return () => {
40
- this.memLocks.delete(userId);
48
+ this.memLocks.delete(sessionId);
41
49
  release();
42
50
  };
43
51
  }
44
- async _acquireRedis(userId) {
45
- const key = this.prefix + userId;
46
- // spin until acquired
52
+ async _acquireRedis(sessionId) {
53
+ const key = this.prefix + sessionId;
54
+ const maxWait = this.ttl * 1000 + 5000; // TTL + 5s grace
55
+ const start = Date.now();
47
56
  while (true) {
48
57
  const ok = await this.redis.set(key, "1", "EX", this.ttl, "NX");
49
58
  if (ok)
50
59
  break;
60
+ if (Date.now() - start > maxWait)
61
+ throw new Error(`Lock timeout for session ${sessionId}`);
51
62
  await new Promise((r) => setTimeout(r, 500));
52
63
  }
53
64
  return async () => {
@@ -0,0 +1,11 @@
1
+ export type LogLevel = "debug" | "info" | "warn" | "error";
2
+ export declare function setLogLevel(level: LogLevel): void;
3
+ export declare function getLogLevel(): LogLevel;
4
+ export interface Logger {
5
+ debug(msg: string, extra?: Record<string, unknown>): void;
6
+ info(msg: string, extra?: Record<string, unknown>): void;
7
+ warn(msg: string, extra?: Record<string, unknown>): void;
8
+ error(msg: string, extra?: Record<string, unknown>): void;
9
+ child(module: string): Logger;
10
+ }
11
+ export declare const log: Logger;
@@ -0,0 +1,24 @@
1
+ const LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
2
+ let globalLevel = "info";
3
+ export function setLogLevel(level) { globalLevel = level; }
4
+ export function getLogLevel() { return globalLevel; }
5
+ function emit(level, module, msg, extra) {
6
+ if (LEVELS[level] < LEVELS[globalLevel])
7
+ return;
8
+ const entry = { ts: new Date().toISOString(), level, module, msg, pid: process.pid, ...extra };
9
+ const line = JSON.stringify(entry);
10
+ if (level === "warn" || level === "error")
11
+ process.stderr.write(line + "\n");
12
+ else
13
+ process.stdout.write(line + "\n");
14
+ }
15
+ function createLogger(module) {
16
+ return {
17
+ debug: (msg, extra) => emit("debug", module, msg, extra),
18
+ info: (msg, extra) => emit("info", module, msg, extra),
19
+ warn: (msg, extra) => emit("warn", module, msg, extra),
20
+ error: (msg, extra) => emit("error", module, msg, extra),
21
+ child: (sub) => createLogger(`${module}:${sub}`),
22
+ };
23
+ }
24
+ export const log = createLogger("bridge");
@@ -0,0 +1,25 @@
1
+ import { SessionManager } from "./session.js";
2
+ import { EndpointRotator } from "./keys.js";
3
+ import { SessionConfig } from "./config.js";
4
+ export interface RouterDecision {
5
+ action: "route" | "create";
6
+ subSessionId?: string;
7
+ label?: string;
8
+ }
9
+ export declare class SessionRouter {
10
+ private sessionMgr;
11
+ private rotator;
12
+ private config;
13
+ constructor(sessionMgr: SessionManager, rotator: EndpointRotator, config: SessionConfig);
14
+ /**
15
+ * 3-tier routing:
16
+ * Tier 1: reply-to → direct route ($0)
17
+ * Tier 2: 0-1 active sessions → bypass ($0)
18
+ * Tier 3: 2+ active sessions → Claude classifier (~$0.002)
19
+ */
20
+ route(userId: string, platform: string, chatId: string, messageText: string, replyToMsgId?: string): Promise<RouterDecision>;
21
+ /** Single-turn Claude call to classify which session a message belongs to */
22
+ private _classify;
23
+ /** Spawn claude CLI for single-turn classification (no tools, no session) */
24
+ private _callClassifier;
25
+ }
@@ -0,0 +1,125 @@
1
+ import { spawn } from "child_process";
2
+ import { log as rootLog } from "./logger.js";
3
+ const log = rootLog.child("router");
4
+ export class SessionRouter {
5
+ sessionMgr;
6
+ rotator;
7
+ config;
8
+ constructor(sessionMgr, rotator, config) {
9
+ this.sessionMgr = sessionMgr;
10
+ this.rotator = rotator;
11
+ this.config = config;
12
+ }
13
+ /**
14
+ * 3-tier routing:
15
+ * Tier 1: reply-to → direct route ($0)
16
+ * Tier 2: 0-1 active sessions → bypass ($0)
17
+ * Tier 3: 2+ active sessions → Claude classifier (~$0.002)
18
+ */
19
+ async route(userId, platform, chatId, messageText, replyToMsgId) {
20
+ // Tier 1: reply-to routing
21
+ if (replyToMsgId) {
22
+ const sessId = this.sessionMgr.getSessionByMessage(replyToMsgId, chatId);
23
+ if (sessId) {
24
+ const sess = this.sessionMgr.get(sessId);
25
+ if (sess && this.sessionMgr.isUsable(sess)) {
26
+ return { action: "route", subSessionId: sessId };
27
+ }
28
+ }
29
+ // reply-to pointed to closed/expired session — fall through to Tier 2/3
30
+ }
31
+ // Tier 2: 0-1 active sessions → direct
32
+ const active = this.sessionMgr.getActive(userId, platform);
33
+ if (active.length === 0) {
34
+ return { action: "create", label: messageText.slice(0, 50) };
35
+ }
36
+ if (active.length === 1) {
37
+ return { action: "route", subSessionId: active[0].id };
38
+ }
39
+ // Tier 3: 2+ sessions → Claude classifier
40
+ return await this._classify(userId, platform, messageText, active);
41
+ }
42
+ /** Single-turn Claude call to classify which session a message belongs to */
43
+ async _classify(userId, platform, text, sessions) {
44
+ try {
45
+ const sessionList = sessions
46
+ .map(s => {
47
+ const ago = Math.round((Date.now() - s.lastActiveAt) / 60000);
48
+ return `[${s.id.slice(0, 8)}] "${s.label || "(no topic)"}" (${ago}min ago)`;
49
+ })
50
+ .join("\n");
51
+ const prompt = `You are a message router. Active conversations:\n${sessionList}\n\nUser message: "${text.slice(0, 200)}"\n\nReply with ONLY the 8-char session ID to route to, or "new" for a new conversation. No explanation.`;
52
+ const result = await this._callClassifier(prompt);
53
+ const cleaned = result.trim().toLowerCase();
54
+ if (cleaned === "new") {
55
+ return { action: "create", label: text.slice(0, 50) };
56
+ }
57
+ // Match against active sessions (first 8 chars of ID)
58
+ const match = sessions.find(s => s.id.slice(0, 8) === cleaned);
59
+ if (match) {
60
+ return { action: "route", subSessionId: match.id };
61
+ }
62
+ // Fallback: if classifier returned something unexpected, route to most recently active
63
+ log.warn("classifier returned unexpected, falling back", { result: cleaned });
64
+ return { action: "route", subSessionId: sessions[0].id };
65
+ }
66
+ catch (err) {
67
+ // Classifier failed — fallback: create new session
68
+ log.warn("classifier error, creating new session", { error: err.message });
69
+ return { action: "create", label: text.slice(0, 50) };
70
+ }
71
+ }
72
+ /** Spawn claude CLI for single-turn classification (no tools, no session) */
73
+ _callClassifier(prompt) {
74
+ return new Promise((resolve, reject) => {
75
+ const args = ["-p", prompt, "--output-format", "stream-json", "--max-turns", "1"];
76
+ if (this.config.classifier_budget)
77
+ args.push("--max-budget-usd", String(this.config.classifier_budget));
78
+ if (this.config.classifier_model)
79
+ args.push("--model", this.config.classifier_model);
80
+ const env = { ...process.env };
81
+ // Use the first available endpoint for the classifier model
82
+ if (this.rotator.count) {
83
+ const ep = this.rotator.next();
84
+ if (!this.config.classifier_model && ep.model)
85
+ args.push("--model", ep.model);
86
+ }
87
+ const child = spawn("claude", args, { env, stdio: ["pipe", "pipe", "pipe"] });
88
+ child.stdin.end();
89
+ const timer = setTimeout(() => { try {
90
+ child.kill("SIGTERM");
91
+ }
92
+ catch { } }, 15000);
93
+ let result = "";
94
+ let buffer = "";
95
+ child.stdout.on("data", (data) => {
96
+ buffer += data.toString();
97
+ const lines = buffer.split("\n");
98
+ buffer = lines.pop() || "";
99
+ for (const line of lines) {
100
+ if (!line.trim())
101
+ continue;
102
+ try {
103
+ const msg = JSON.parse(line);
104
+ if (msg.type === "result" && msg.result)
105
+ result = msg.result;
106
+ }
107
+ catch { }
108
+ }
109
+ });
110
+ let stderr = "";
111
+ child.stderr.on("data", (data) => { stderr += data.toString(); });
112
+ child.on("close", (code) => {
113
+ clearTimeout(timer);
114
+ if (result)
115
+ resolve(result);
116
+ else
117
+ reject(new Error(`classifier exited ${code}: ${stderr.slice(0, 200)}`));
118
+ });
119
+ child.on("error", (err) => {
120
+ clearTimeout(timer);
121
+ reject(err);
122
+ });
123
+ });
124
+ }
125
+ }