@everworker/oneringai 0.1.3 → 0.2.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.
package/dist/index.js CHANGED
@@ -1,14 +1,14 @@
1
1
  import * as crypto2 from 'crypto';
2
2
  import { randomUUID } from 'crypto';
3
3
  import { importPKCS8, SignJWT } from 'jose';
4
- import * as fs15 from 'fs';
4
+ import * as fs16 from 'fs';
5
5
  import { promises, existsSync } from 'fs';
6
6
  import { EventEmitter } from 'eventemitter3';
7
- import * as path3 from 'path';
7
+ import * as path2 from 'path';
8
8
  import { join, resolve, dirname, relative, isAbsolute, normalize, extname } from 'path';
9
- import * as os from 'os';
9
+ import * as os2 from 'os';
10
10
  import { homedir } from 'os';
11
- import OpenAI2 from 'openai';
11
+ import OpenAI3 from 'openai';
12
12
  import Anthropic from '@anthropic-ai/sdk';
13
13
  import { GoogleGenAI } from '@google/genai';
14
14
  import 'zod/v3';
@@ -16,7 +16,7 @@ import * as z4mini from 'zod/v4-mini';
16
16
  import * as z from 'zod/v4';
17
17
  import process2 from 'process';
18
18
  import { PassThrough } from 'stream';
19
- import * as fs14 from 'fs/promises';
19
+ import * as fs15 from 'fs/promises';
20
20
  import { stat, readFile, mkdir, writeFile, readdir } from 'fs/promises';
21
21
  import * as simpleIcons from 'simple-icons';
22
22
  import { exec, spawn } from 'child_process';
@@ -600,7 +600,7 @@ var init_JWTBearer = __esm({
600
600
  this.privateKey = config.privateKey;
601
601
  } else if (config.privateKeyPath) {
602
602
  try {
603
- this.privateKey = fs15.readFileSync(config.privateKeyPath, "utf8");
603
+ this.privateKey = fs16.readFileSync(config.privateKeyPath, "utf8");
604
604
  } catch (error) {
605
605
  throw new Error(`Failed to read private key from ${config.privateKeyPath}: ${error.message}`);
606
606
  }
@@ -1250,11 +1250,11 @@ var init_Logger = __esm({
1250
1250
  */
1251
1251
  initFileStream(filePath) {
1252
1252
  try {
1253
- const dir = path3.dirname(filePath);
1254
- if (!fs15.existsSync(dir)) {
1255
- fs15.mkdirSync(dir, { recursive: true });
1253
+ const dir = path2.dirname(filePath);
1254
+ if (!fs16.existsSync(dir)) {
1255
+ fs16.mkdirSync(dir, { recursive: true });
1256
1256
  }
1257
- this.fileStream = fs15.createWriteStream(filePath, {
1257
+ this.fileStream = fs16.createWriteStream(filePath, {
1258
1258
  flags: "a",
1259
1259
  // append mode
1260
1260
  encoding: "utf8"
@@ -2003,7 +2003,14 @@ var init_Connector = __esm({
2003
2003
  }
2004
2004
  const startTime = Date.now();
2005
2005
  this.requestCount++;
2006
- const url2 = endpoint.startsWith("http") ? endpoint : `${this.baseURL}${endpoint}`;
2006
+ let url2;
2007
+ if (endpoint.startsWith("http")) {
2008
+ url2 = endpoint;
2009
+ } else {
2010
+ const base = (this.baseURL ?? "").replace(/\/+$/, "");
2011
+ const path6 = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
2012
+ url2 = `${base}${path6}`;
2013
+ }
2007
2014
  const timeout = options?.timeout ?? this.config.timeout ?? DEFAULT_CONNECTOR_TIMEOUT;
2008
2015
  if (this.config.logging?.enabled) {
2009
2016
  this.logRequest(url2, options);
@@ -14524,12 +14531,12 @@ var require_dist = __commonJS({
14524
14531
  throw new Error(`Unknown format "${name}"`);
14525
14532
  return f;
14526
14533
  };
14527
- function addFormats(ajv, list, fs18, exportName) {
14534
+ function addFormats(ajv, list, fs17, exportName) {
14528
14535
  var _a;
14529
14536
  var _b;
14530
14537
  (_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
14531
14538
  for (const f of list)
14532
- ajv.addFormat(f, fs18[f]);
14539
+ ajv.addFormat(f, fs17[f]);
14533
14540
  }
14534
14541
  module.exports = exports$1 = formatsPlugin;
14535
14542
  Object.defineProperty(exports$1, "__esModule", { value: true });
@@ -14542,7 +14549,7 @@ var require_windows = __commonJS({
14542
14549
  "node_modules/isexe/windows.js"(exports$1, module) {
14543
14550
  module.exports = isexe;
14544
14551
  isexe.sync = sync;
14545
- var fs18 = __require("fs");
14552
+ var fs17 = __require("fs");
14546
14553
  function checkPathExt(path6, options) {
14547
14554
  var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
14548
14555
  if (!pathext) {
@@ -14560,19 +14567,19 @@ var require_windows = __commonJS({
14560
14567
  }
14561
14568
  return false;
14562
14569
  }
14563
- function checkStat(stat5, path6, options) {
14564
- if (!stat5.isSymbolicLink() && !stat5.isFile()) {
14570
+ function checkStat(stat6, path6, options) {
14571
+ if (!stat6.isSymbolicLink() && !stat6.isFile()) {
14565
14572
  return false;
14566
14573
  }
14567
14574
  return checkPathExt(path6, options);
14568
14575
  }
14569
14576
  function isexe(path6, options, cb) {
14570
- fs18.stat(path6, function(er, stat5) {
14571
- cb(er, er ? false : checkStat(stat5, path6, options));
14577
+ fs17.stat(path6, function(er, stat6) {
14578
+ cb(er, er ? false : checkStat(stat6, path6, options));
14572
14579
  });
14573
14580
  }
14574
14581
  function sync(path6, options) {
14575
- return checkStat(fs18.statSync(path6), path6, options);
14582
+ return checkStat(fs17.statSync(path6), path6, options);
14576
14583
  }
14577
14584
  }
14578
14585
  });
@@ -14582,22 +14589,22 @@ var require_mode = __commonJS({
14582
14589
  "node_modules/isexe/mode.js"(exports$1, module) {
14583
14590
  module.exports = isexe;
14584
14591
  isexe.sync = sync;
14585
- var fs18 = __require("fs");
14592
+ var fs17 = __require("fs");
14586
14593
  function isexe(path6, options, cb) {
14587
- fs18.stat(path6, function(er, stat5) {
14588
- cb(er, er ? false : checkStat(stat5, options));
14594
+ fs17.stat(path6, function(er, stat6) {
14595
+ cb(er, er ? false : checkStat(stat6, options));
14589
14596
  });
14590
14597
  }
14591
14598
  function sync(path6, options) {
14592
- return checkStat(fs18.statSync(path6), options);
14599
+ return checkStat(fs17.statSync(path6), options);
14593
14600
  }
14594
- function checkStat(stat5, options) {
14595
- return stat5.isFile() && checkMode(stat5, options);
14601
+ function checkStat(stat6, options) {
14602
+ return stat6.isFile() && checkMode(stat6, options);
14596
14603
  }
14597
- function checkMode(stat5, options) {
14598
- var mod = stat5.mode;
14599
- var uid = stat5.uid;
14600
- var gid = stat5.gid;
14604
+ function checkMode(stat6, options) {
14605
+ var mod = stat6.mode;
14606
+ var uid = stat6.uid;
14607
+ var gid = stat6.gid;
14601
14608
  var myUid = options.uid !== void 0 ? options.uid : process.getuid && process.getuid();
14602
14609
  var myGid = options.gid !== void 0 ? options.gid : process.getgid && process.getgid();
14603
14610
  var u = parseInt("100", 8);
@@ -14871,16 +14878,16 @@ var require_shebang_command = __commonJS({
14871
14878
  // node_modules/cross-spawn/lib/util/readShebang.js
14872
14879
  var require_readShebang = __commonJS({
14873
14880
  "node_modules/cross-spawn/lib/util/readShebang.js"(exports$1, module) {
14874
- var fs18 = __require("fs");
14881
+ var fs17 = __require("fs");
14875
14882
  var shebangCommand = require_shebang_command();
14876
14883
  function readShebang(command) {
14877
14884
  const size = 150;
14878
14885
  const buffer = Buffer.alloc(size);
14879
14886
  let fd;
14880
14887
  try {
14881
- fd = fs18.openSync(command, "r");
14882
- fs18.readSync(fd, buffer, 0, size, 0);
14883
- fs18.closeSync(fd);
14888
+ fd = fs17.openSync(command, "r");
14889
+ fs17.readSync(fd, buffer, 0, size, 0);
14890
+ fs17.closeSync(fd);
14884
14891
  } catch (e) {
14885
14892
  }
14886
14893
  return shebangCommand(buffer.toString());
@@ -15989,13 +15996,26 @@ var ToolManager = class extends EventEmitter {
15989
15996
  pipeline;
15990
15997
  /** Optional tool context for execution (set by agent before runs) */
15991
15998
  _toolContext;
15992
- constructor() {
15999
+ /** Hard timeout for tool execution (0 = disabled) */
16000
+ _toolExecutionTimeout;
16001
+ constructor(config) {
15993
16002
  super();
16003
+ this._toolExecutionTimeout = config?.toolExecutionTimeout ?? 0;
15994
16004
  this.namespaceIndex.set("default", /* @__PURE__ */ new Set());
15995
16005
  this.toolLogger = logger.child({ component: "ToolManager" });
15996
16006
  this.pipeline = new ToolExecutionPipeline();
15997
16007
  this.pipeline.use(new ResultNormalizerPlugin());
15998
16008
  }
16009
+ /**
16010
+ * Get or set the hard tool execution timeout in milliseconds.
16011
+ * 0 = disabled (relies on tool's own timeout).
16012
+ */
16013
+ get toolExecutionTimeout() {
16014
+ return this._toolExecutionTimeout;
16015
+ }
16016
+ set toolExecutionTimeout(value) {
16017
+ this._toolExecutionTimeout = Math.max(0, value);
16018
+ }
15999
16019
  /**
16000
16020
  * Access the execution pipeline for plugin management.
16001
16021
  *
@@ -16493,7 +16513,7 @@ var ToolManager = class extends EventEmitter {
16493
16513
  const startTime = Date.now();
16494
16514
  metrics.increment("tool.executed", 1, { tool: toolName });
16495
16515
  try {
16496
- const result = await breaker.execute(async () => {
16516
+ const executionPromise = breaker.execute(async () => {
16497
16517
  const toolWithContext = {
16498
16518
  ...registration.tool,
16499
16519
  execute: async (pipelineArgs) => {
@@ -16502,6 +16522,7 @@ var ToolManager = class extends EventEmitter {
16502
16522
  };
16503
16523
  return await this.pipeline.execute(toolWithContext, args);
16504
16524
  });
16525
+ const result = this._toolExecutionTimeout > 0 ? await this.withHardTimeout(executionPromise, toolName, this._toolExecutionTimeout) : await executionPromise;
16505
16526
  const duration = Date.now() - startTime;
16506
16527
  this.recordExecution(toolName, duration, true);
16507
16528
  const resultSummary = this.summarizeResult(result);
@@ -16564,6 +16585,29 @@ var ToolManager = class extends EventEmitter {
16564
16585
  return this.list();
16565
16586
  }
16566
16587
  // ==========================================================================
16588
+ // Hard Timeout
16589
+ // ==========================================================================
16590
+ /**
16591
+ * Wrap a promise with a hard timeout safety net.
16592
+ * If the promise doesn't resolve within the timeout, throws ToolExecutionError.
16593
+ */
16594
+ async withHardTimeout(promise, toolName, timeoutMs) {
16595
+ let timeoutId;
16596
+ const timeoutPromise = new Promise((_, reject) => {
16597
+ timeoutId = setTimeout(() => {
16598
+ reject(new ToolExecutionError(
16599
+ toolName,
16600
+ `Tool execution hard timeout after ${timeoutMs}ms (safety net - tool's own timeout may have failed)`
16601
+ ));
16602
+ }, timeoutMs);
16603
+ });
16604
+ try {
16605
+ return await Promise.race([promise, timeoutPromise]);
16606
+ } finally {
16607
+ clearTimeout(timeoutId);
16608
+ }
16609
+ }
16610
+ // ==========================================================================
16567
16611
  // Circuit Breaker Management
16568
16612
  // ==========================================================================
16569
16613
  /**
@@ -18369,6 +18413,9 @@ var BasePluginNextGen = class {
18369
18413
  }
18370
18414
  };
18371
18415
 
18416
+ // src/core/context-nextgen/AgentContextNextGen.ts
18417
+ init_Connector();
18418
+
18372
18419
  // src/domain/entities/Memory.ts
18373
18420
  function isTaskAwareScope(scope) {
18374
18421
  return typeof scope === "object" && scope !== null && "type" in scope;
@@ -19307,13 +19354,18 @@ Values are immediately visible - no retrieval needed.
19307
19354
  - \`high\`: Keep longer. Important state.
19308
19355
  - \`critical\`: Never auto-evicted.
19309
19356
 
19357
+ **UI Display:** Set \`showInUI: true\` in context_set to display the entry in the user's side panel.
19358
+ Values shown in the UI support the same rich markdown formatting as the chat window
19359
+ (see formatting instructions above). Use this for dashboards, progress displays, and results the user should see.
19360
+
19310
19361
  **Tools:** context_set, context_delete, context_list`;
19311
19362
  var contextSetDefinition = {
19312
19363
  type: "function",
19313
19364
  function: {
19314
19365
  name: "context_set",
19315
19366
  description: `Store or update a key-value pair in live context.
19316
- Value appears directly in context - no retrieval needed.`,
19367
+ Value appears directly in context - no retrieval needed.
19368
+ Set showInUI to true to also display the entry in the user's side panel.`,
19317
19369
  parameters: {
19318
19370
  type: "object",
19319
19371
  properties: {
@@ -19324,6 +19376,10 @@ Value appears directly in context - no retrieval needed.`,
19324
19376
  type: "string",
19325
19377
  enum: ["low", "normal", "high", "critical"],
19326
19378
  description: 'Eviction priority. Default: "normal"'
19379
+ },
19380
+ showInUI: {
19381
+ type: "boolean",
19382
+ description: "If true, display this entry in the user's side panel with full rich markdown rendering \u2014 same capabilities as the chat window (code blocks, tables, LaTeX, Mermaid diagrams, Vega-Lite charts, mindmaps, etc. \u2014 see formatting instructions in system prompt). Use this for dashboards, status displays, and structured results the user should see. Default: false"
19327
19383
  }
19328
19384
  },
19329
19385
  required: ["key", "description", "value"]
@@ -19364,6 +19420,7 @@ var InContextMemoryPluginNextGen = class {
19364
19420
  _destroyed = false;
19365
19421
  _tokenCache = null;
19366
19422
  _instructionsTokenCache = null;
19423
+ _notifyTimer = null;
19367
19424
  constructor(config = {}) {
19368
19425
  this.config = { ...DEFAULT_CONFIG, ...config };
19369
19426
  }
@@ -19404,15 +19461,18 @@ var InContextMemoryPluginNextGen = class {
19404
19461
  return a.updatedAt - b.updatedAt;
19405
19462
  });
19406
19463
  let freed = 0;
19464
+ let evicted = false;
19407
19465
  for (const entry of evictable) {
19408
19466
  if (freed >= targetTokensToFree) break;
19409
19467
  const entryTokens = this.estimator.estimateTokens(this.formatEntry(entry));
19410
19468
  this.entries.delete(entry.key);
19411
19469
  freed += entryTokens;
19470
+ evicted = true;
19412
19471
  }
19413
19472
  this._tokenCache = null;
19414
19473
  const content = await this.getContent();
19415
19474
  const after = content ? this.estimator.estimateTokens(content) : 0;
19475
+ if (evicted) this.notifyEntriesChanged();
19416
19476
  return Math.max(0, before - after);
19417
19477
  }
19418
19478
  getTools() {
@@ -19424,6 +19484,7 @@ var InContextMemoryPluginNextGen = class {
19424
19484
  }
19425
19485
  destroy() {
19426
19486
  if (this._destroyed) return;
19487
+ if (this._notifyTimer) clearTimeout(this._notifyTimer);
19427
19488
  this.entries.clear();
19428
19489
  this._destroyed = true;
19429
19490
  this._tokenCache = null;
@@ -19441,6 +19502,7 @@ var InContextMemoryPluginNextGen = class {
19441
19502
  this.entries.set(entry.key, entry);
19442
19503
  }
19443
19504
  this._tokenCache = null;
19505
+ this.notifyEntriesChanged();
19444
19506
  }
19445
19507
  // ============================================================================
19446
19508
  // Entry Management
@@ -19448,19 +19510,21 @@ var InContextMemoryPluginNextGen = class {
19448
19510
  /**
19449
19511
  * Store or update a key-value pair
19450
19512
  */
19451
- set(key, description, value, priority) {
19513
+ set(key, description, value, priority, showInUI) {
19452
19514
  this.assertNotDestroyed();
19453
19515
  const entry = {
19454
19516
  key,
19455
19517
  description,
19456
19518
  value,
19457
19519
  updatedAt: Date.now(),
19458
- priority: priority ?? this.config.defaultPriority
19520
+ priority: priority ?? this.config.defaultPriority,
19521
+ showInUI: showInUI ?? false
19459
19522
  };
19460
19523
  this.entries.set(key, entry);
19461
19524
  this.enforceMaxEntries();
19462
19525
  this.enforceTokenLimit();
19463
19526
  this._tokenCache = null;
19527
+ this.notifyEntriesChanged();
19464
19528
  }
19465
19529
  /**
19466
19530
  * Get a value by key
@@ -19482,7 +19546,10 @@ var InContextMemoryPluginNextGen = class {
19482
19546
  delete(key) {
19483
19547
  this.assertNotDestroyed();
19484
19548
  const deleted = this.entries.delete(key);
19485
- if (deleted) this._tokenCache = null;
19549
+ if (deleted) {
19550
+ this._tokenCache = null;
19551
+ this.notifyEntriesChanged();
19552
+ }
19486
19553
  return deleted;
19487
19554
  }
19488
19555
  /**
@@ -19494,7 +19561,8 @@ var InContextMemoryPluginNextGen = class {
19494
19561
  key: e.key,
19495
19562
  description: e.description,
19496
19563
  priority: e.priority,
19497
- updatedAt: e.updatedAt
19564
+ updatedAt: e.updatedAt,
19565
+ showInUI: e.showInUI ?? false
19498
19566
  }));
19499
19567
  }
19500
19568
  /**
@@ -19504,6 +19572,7 @@ var InContextMemoryPluginNextGen = class {
19504
19572
  this.assertNotDestroyed();
19505
19573
  this.entries.clear();
19506
19574
  this._tokenCache = null;
19575
+ this.notifyEntriesChanged();
19507
19576
  }
19508
19577
  // ============================================================================
19509
19578
  // Private Helpers
@@ -19567,6 +19636,20 @@ ${valueStr}
19567
19636
  return a.updatedAt - b.updatedAt;
19568
19637
  });
19569
19638
  }
19639
+ /**
19640
+ * Debounced notification when entries change.
19641
+ * Calls config.onEntriesChanged with all current entries.
19642
+ */
19643
+ notifyEntriesChanged() {
19644
+ if (!this.config.onEntriesChanged) return;
19645
+ if (this._notifyTimer) clearTimeout(this._notifyTimer);
19646
+ this._notifyTimer = setTimeout(() => {
19647
+ this._notifyTimer = null;
19648
+ if (!this._destroyed && this.config.onEntriesChanged) {
19649
+ this.config.onEntriesChanged(Array.from(this.entries.values()));
19650
+ }
19651
+ }, 100);
19652
+ }
19570
19653
  assertNotDestroyed() {
19571
19654
  if (this._destroyed) {
19572
19655
  throw new Error("InContextMemoryPluginNextGen is destroyed");
@@ -19583,16 +19666,18 @@ ${valueStr}
19583
19666
  args.key,
19584
19667
  args.description,
19585
19668
  args.value,
19586
- args.priority
19669
+ args.priority,
19670
+ args.showInUI
19587
19671
  );
19588
19672
  return {
19589
19673
  success: true,
19590
19674
  key: args.key,
19591
- message: `Stored "${args.key}" in live context`
19675
+ showInUI: args.showInUI ?? false,
19676
+ message: `Stored "${args.key}" in live context${args.showInUI ? " (visible in UI)" : ""}`
19592
19677
  };
19593
19678
  },
19594
19679
  permission: { scope: "always", riskLevel: "low" },
19595
- describeCall: (args) => `set ${args.key}`
19680
+ describeCall: (args) => `set ${args.key}${args.showInUI ? " [UI]" : ""}`
19596
19681
  };
19597
19682
  }
19598
19683
  createContextDeleteTool() {
@@ -20904,6 +20989,10 @@ var AgentContextNextGen = class _AgentContextNextGen extends EventEmitter {
20904
20989
  _sessionId = null;
20905
20990
  /** Agent ID */
20906
20991
  _agentId;
20992
+ /** User ID for multi-user scenarios */
20993
+ _userId;
20994
+ /** Allowed connector names (when agent is restricted to a subset) */
20995
+ _allowedConnectors;
20907
20996
  /** Storage backend */
20908
20997
  _storage;
20909
20998
  /** Destroyed flag */
@@ -20940,15 +21029,20 @@ var AgentContextNextGen = class _AgentContextNextGen extends EventEmitter {
20940
21029
  };
20941
21030
  this._systemPrompt = config.systemPrompt;
20942
21031
  this._agentId = this._config.agentId;
21032
+ this._userId = config.userId;
21033
+ this._allowedConnectors = config.connectors;
20943
21034
  this._storage = config.storage;
20944
21035
  this._compactionStrategy = config.compactionStrategy ?? StrategyRegistry.create(this._config.strategy);
20945
- this._tools = new ToolManager();
21036
+ this._tools = new ToolManager(
21037
+ config.toolExecutionTimeout ? { toolExecutionTimeout: config.toolExecutionTimeout } : void 0
21038
+ );
20946
21039
  if (config.tools) {
20947
21040
  for (const tool of config.tools) {
20948
21041
  this._tools.register(tool);
20949
21042
  }
20950
21043
  }
20951
21044
  this.initializePlugins(config.plugins);
21045
+ this.syncToolContext();
20952
21046
  }
20953
21047
  /**
20954
21048
  * Initialize plugins based on feature flags.
@@ -20993,6 +21087,62 @@ var AgentContextNextGen = class _AgentContextNextGen extends EventEmitter {
20993
21087
  );
20994
21088
  }
20995
21089
  }
21090
+ /**
21091
+ * Sync identity fields and connector registry to ToolContext.
21092
+ * Merges with existing ToolContext to preserve other fields (memory, signal, taskId).
21093
+ *
21094
+ * Connector registry resolution order:
21095
+ * 1. If `connectors` (allowed names) is set → filtered view of global registry
21096
+ * 2. If access policy + userId → scoped view via Connector.scoped()
21097
+ * 3. Otherwise → full global registry
21098
+ */
21099
+ syncToolContext() {
21100
+ const existing = this._tools.getToolContext();
21101
+ this._tools.setToolContext({
21102
+ ...existing,
21103
+ agentId: this._agentId,
21104
+ userId: this._userId,
21105
+ connectorRegistry: this.buildConnectorRegistry()
21106
+ });
21107
+ }
21108
+ /**
21109
+ * Build the connector registry appropriate for this agent's config.
21110
+ */
21111
+ buildConnectorRegistry() {
21112
+ if (this._allowedConnectors?.length) {
21113
+ const allowedSet = new Set(this._allowedConnectors);
21114
+ const base = this._userId && Connector.getAccessPolicy() ? Connector.scoped({ userId: this._userId }) : Connector.asRegistry();
21115
+ return {
21116
+ get: (name) => {
21117
+ if (!allowedSet.has(name)) {
21118
+ const available = this._allowedConnectors.filter((n) => base.has(n)).join(", ") || "none";
21119
+ throw new Error(`Connector '${name}' not found. Available: ${available}`);
21120
+ }
21121
+ return base.get(name);
21122
+ },
21123
+ has: (name) => allowedSet.has(name) && base.has(name),
21124
+ list: () => base.list().filter((n) => allowedSet.has(n)),
21125
+ listAll: () => base.listAll().filter((c) => allowedSet.has(c.name)),
21126
+ size: () => base.listAll().filter((c) => allowedSet.has(c.name)).length,
21127
+ getDescriptionsForTools: () => {
21128
+ const connectors = base.listAll().filter((c) => allowedSet.has(c.name));
21129
+ if (connectors.length === 0) return "No connectors registered yet.";
21130
+ return connectors.map((c) => ` - "${c.name}": ${c.displayName} - ${c.config.description || "No description"}`).join("\n");
21131
+ },
21132
+ getInfo: () => {
21133
+ const info = {};
21134
+ for (const c of base.listAll().filter((c2) => allowedSet.has(c2.name))) {
21135
+ info[c.name] = { displayName: c.displayName, description: c.config.description || "", baseURL: c.baseURL };
21136
+ }
21137
+ return info;
21138
+ }
21139
+ };
21140
+ }
21141
+ if (this._userId && Connector.getAccessPolicy()) {
21142
+ return Connector.scoped({ userId: this._userId });
21143
+ }
21144
+ return Connector.asRegistry();
21145
+ }
20996
21146
  // ============================================================================
20997
21147
  // Public Properties
20998
21148
  // ============================================================================
@@ -21008,6 +21158,24 @@ var AgentContextNextGen = class _AgentContextNextGen extends EventEmitter {
21008
21158
  get agentId() {
21009
21159
  return this._agentId;
21010
21160
  }
21161
+ /** Get the current user ID */
21162
+ get userId() {
21163
+ return this._userId;
21164
+ }
21165
+ /** Set user ID. Automatically updates ToolContext for all tool executions. */
21166
+ set userId(value) {
21167
+ this._userId = value;
21168
+ this.syncToolContext();
21169
+ }
21170
+ /** Get the allowed connector names (undefined = all visible connectors) */
21171
+ get connectors() {
21172
+ return this._allowedConnectors;
21173
+ }
21174
+ /** Set allowed connector names. Updates ToolContext.connectorRegistry. */
21175
+ set connectors(value) {
21176
+ this._allowedConnectors = value;
21177
+ this.syncToolContext();
21178
+ }
21011
21179
  /** Get/set system prompt */
21012
21180
  get systemPrompt() {
21013
21181
  return this._systemPrompt;
@@ -21921,6 +22089,7 @@ ${content}`);
21921
22089
  metadata: {
21922
22090
  savedAt: Date.now(),
21923
22091
  agentId: this._agentId,
22092
+ userId: this._userId,
21924
22093
  model: this._config.model
21925
22094
  }
21926
22095
  };
@@ -22678,7 +22847,7 @@ var OpenAITextProvider = class extends BaseTextProvider {
22678
22847
  streamConverter;
22679
22848
  constructor(config) {
22680
22849
  super(config);
22681
- this.client = new OpenAI2({
22850
+ this.client = new OpenAI3({
22682
22851
  apiKey: this.getApiKey(),
22683
22852
  baseURL: this.getBaseURL(),
22684
22853
  organization: config.organization,
@@ -24408,7 +24577,9 @@ var GoogleTextProvider = class extends BaseTextProvider {
24408
24577
  constructor(config) {
24409
24578
  super(config);
24410
24579
  this.client = new GoogleGenAI({
24411
- apiKey: this.getApiKey()
24580
+ apiKey: this.getApiKey(),
24581
+ // Pass custom baseURL for proxy support (e.g. when routing through EW proxy)
24582
+ ...config.baseURL ? { httpOptions: { baseUrl: config.baseURL } } : {}
24412
24583
  });
24413
24584
  this.converter = new GoogleConverter();
24414
24585
  this.streamConverter = new GoogleStreamConverter();
@@ -24705,6 +24876,30 @@ var GenericOpenAIProvider = class extends OpenAITextProvider {
24705
24876
  };
24706
24877
 
24707
24878
  // src/core/createProvider.ts
24879
+ var VENDOR_DEFAULT_URLS = (() => {
24880
+ const map = /* @__PURE__ */ new Map();
24881
+ try {
24882
+ map.set(Vendor.OpenAI, new OpenAI3({ apiKey: "_" }).baseURL);
24883
+ } catch {
24884
+ }
24885
+ try {
24886
+ map.set(Vendor.Anthropic, new Anthropic({ apiKey: "_" }).baseURL);
24887
+ } catch {
24888
+ }
24889
+ map.set(Vendor.Google, "https://generativelanguage.googleapis.com");
24890
+ map.set(Vendor.GoogleVertex, "https://us-central1-aiplatform.googleapis.com");
24891
+ map.set(Vendor.Groq, "https://api.groq.com/openai/v1");
24892
+ map.set(Vendor.Together, "https://api.together.xyz/v1");
24893
+ map.set(Vendor.Perplexity, "https://api.perplexity.ai");
24894
+ map.set(Vendor.Grok, "https://api.x.ai/v1");
24895
+ map.set(Vendor.DeepSeek, "https://api.deepseek.com/v1");
24896
+ map.set(Vendor.Mistral, "https://api.mistral.ai/v1");
24897
+ map.set(Vendor.Ollama, "http://localhost:11434/v1");
24898
+ return map;
24899
+ })();
24900
+ function getVendorDefaultBaseURL(vendor) {
24901
+ return VENDOR_DEFAULT_URLS.get(vendor);
24902
+ }
24708
24903
  function createProvider(connector) {
24709
24904
  const injectedProvider = connector.getOptions().provider;
24710
24905
  if (injectedProvider && typeof injectedProvider.generate === "function") {
@@ -24739,39 +24934,15 @@ function createProvider(connector) {
24739
24934
  });
24740
24935
  // OpenAI-compatible providers (use connector.name for unique identification)
24741
24936
  case Vendor.Groq:
24742
- return new GenericOpenAIProvider(connector.name, {
24743
- ...config,
24744
- baseURL: config.baseURL || "https://api.groq.com/openai/v1"
24745
- });
24746
24937
  case Vendor.Together:
24747
- return new GenericOpenAIProvider(connector.name, {
24748
- ...config,
24749
- baseURL: config.baseURL || "https://api.together.xyz/v1"
24750
- });
24751
24938
  case Vendor.Perplexity:
24752
- return new GenericOpenAIProvider(connector.name, {
24753
- ...config,
24754
- baseURL: config.baseURL || "https://api.perplexity.ai"
24755
- });
24756
24939
  case Vendor.Grok:
24757
- return new GenericOpenAIProvider(connector.name, {
24758
- ...config,
24759
- baseURL: config.baseURL || "https://api.x.ai/v1"
24760
- });
24761
24940
  case Vendor.DeepSeek:
24762
- return new GenericOpenAIProvider(connector.name, {
24763
- ...config,
24764
- baseURL: config.baseURL || "https://api.deepseek.com/v1"
24765
- });
24766
24941
  case Vendor.Mistral:
24767
- return new GenericOpenAIProvider(connector.name, {
24768
- ...config,
24769
- baseURL: config.baseURL || "https://api.mistral.ai/v1"
24770
- });
24771
24942
  case Vendor.Ollama:
24772
24943
  return new GenericOpenAIProvider(connector.name, {
24773
24944
  ...config,
24774
- baseURL: config.baseURL || "http://localhost:11434/v1"
24945
+ baseURL: config.baseURL || getVendorDefaultBaseURL(vendor)
24775
24946
  });
24776
24947
  case Vendor.Custom:
24777
24948
  if (!config.baseURL) {
@@ -24881,9 +25052,14 @@ var BaseAgent = class extends EventEmitter {
24881
25052
  const contextConfig = {
24882
25053
  model: config.model,
24883
25054
  agentId: config.name,
25055
+ userId: config.userId,
25056
+ connectors: config.connectors,
24884
25057
  // Include storage and sessionId if session config is provided
24885
25058
  storage: config.session?.storage,
25059
+ // Thread tool execution timeout to ToolManager
25060
+ toolExecutionTimeout: config.toolExecutionTimeout,
24886
25061
  // Subclasses can add systemPrompt via their config
25062
+ // Note: context-level toolExecutionTimeout overrides agent-level if both set
24887
25063
  ...typeof config.context === "object" && config.context !== null ? config.context : {}
24888
25064
  };
24889
25065
  return AgentContextNextGen.create(contextConfig);
@@ -25034,6 +25210,30 @@ var BaseAgent = class extends EventEmitter {
25034
25210
  get context() {
25035
25211
  return this._agentContext;
25036
25212
  }
25213
+ /**
25214
+ * Get the current user ID. Delegates to AgentContextNextGen.
25215
+ */
25216
+ get userId() {
25217
+ return this._agentContext.userId;
25218
+ }
25219
+ /**
25220
+ * Set user ID at runtime. Automatically updates ToolContext for all tool executions.
25221
+ */
25222
+ set userId(value) {
25223
+ this._agentContext.userId = value;
25224
+ }
25225
+ /**
25226
+ * Get the allowed connector names (undefined = all visible connectors).
25227
+ */
25228
+ get connectors() {
25229
+ return this._agentContext.connectors;
25230
+ }
25231
+ /**
25232
+ * Restrict this agent to a subset of connectors. Updates ToolContext.connectorRegistry.
25233
+ */
25234
+ set connectors(value) {
25235
+ this._agentContext.connectors = value;
25236
+ }
25037
25237
  /**
25038
25238
  * Permission management. Returns ToolPermissionManager for approval control.
25039
25239
  */
@@ -25085,9 +25285,10 @@ var BaseAgent = class extends EventEmitter {
25085
25285
  * always sees up-to-date tool descriptions.
25086
25286
  */
25087
25287
  getEnabledToolDefinitions() {
25288
+ const toolContext = this._agentContext.tools.getToolContext();
25088
25289
  return this._agentContext.tools.getEnabled().map((tool) => {
25089
25290
  if (tool.descriptionFactory) {
25090
- const dynamicDescription = tool.descriptionFactory();
25291
+ const dynamicDescription = tool.descriptionFactory(toolContext);
25091
25292
  return {
25092
25293
  ...tool.definition,
25093
25294
  function: {
@@ -26133,6 +26334,7 @@ var Agent = class _Agent extends BaseAgent {
26133
26334
  * const agent = Agent.create({
26134
26335
  * connector: 'openai', // or Connector instance
26135
26336
  * model: 'gpt-4',
26337
+ * userId: 'user-123', // flows to all tool executions automatically
26136
26338
  * instructions: 'You are a helpful assistant',
26137
26339
  * tools: [myTool]
26138
26340
  * });
@@ -27355,8 +27557,8 @@ var Agent = class _Agent extends BaseAgent {
27355
27557
  throw new Error("Configuration file not found. Searched: " + this.DEFAULT_PATHS.join(", "));
27356
27558
  }
27357
27559
  try {
27358
- const fs18 = __require("fs");
27359
- const content = fs18.readFileSync(configPath, "utf-8");
27560
+ const fs17 = __require("fs");
27561
+ const content = fs17.readFileSync(configPath, "utf-8");
27360
27562
  let config = JSON.parse(content);
27361
27563
  config = this.interpolateEnvVars(config);
27362
27564
  this.validate(config);
@@ -27385,10 +27587,10 @@ var Agent = class _Agent extends BaseAgent {
27385
27587
  * Find configuration file synchronously
27386
27588
  */
27387
27589
  static findConfigSync() {
27388
- const fs18 = __require("fs");
27590
+ const fs17 = __require("fs");
27389
27591
  for (const path6 of this.DEFAULT_PATHS) {
27390
27592
  try {
27391
- fs18.accessSync(resolve(path6));
27593
+ fs17.accessSync(resolve(path6));
27392
27594
  return resolve(path6);
27393
27595
  } catch {
27394
27596
  }
@@ -33335,7 +33537,7 @@ var OpenAITTSProvider = class extends BaseMediaProvider {
33335
33537
  client;
33336
33538
  constructor(config) {
33337
33539
  super({ apiKey: config.auth.apiKey, ...config });
33338
- this.client = new OpenAI2({
33540
+ this.client = new OpenAI3({
33339
33541
  apiKey: config.auth.apiKey,
33340
33542
  baseURL: config.baseURL,
33341
33543
  organization: config.organization,
@@ -33418,7 +33620,7 @@ var OpenAITTSProvider = class extends BaseMediaProvider {
33418
33620
  * Handle OpenAI API errors
33419
33621
  */
33420
33622
  handleError(error) {
33421
- if (error instanceof OpenAI2.APIError) {
33623
+ if (error instanceof OpenAI3.APIError) {
33422
33624
  const status = error.status;
33423
33625
  const message = error.message || "Unknown OpenAI API error";
33424
33626
  if (status === 401) {
@@ -33450,7 +33652,7 @@ var OpenAISTTProvider = class extends BaseMediaProvider {
33450
33652
  client;
33451
33653
  constructor(config) {
33452
33654
  super({ apiKey: config.auth.apiKey, ...config });
33453
- this.client = new OpenAI2({
33655
+ this.client = new OpenAI3({
33454
33656
  apiKey: config.auth.apiKey,
33455
33657
  baseURL: config.baseURL,
33456
33658
  organization: config.organization,
@@ -33552,7 +33754,7 @@ var OpenAISTTProvider = class extends BaseMediaProvider {
33552
33754
  if (Buffer.isBuffer(audio)) {
33553
33755
  return new File([new Uint8Array(audio)], "audio.wav", { type: "audio/wav" });
33554
33756
  } else if (typeof audio === "string") {
33555
- return fs15.createReadStream(audio);
33757
+ return fs16.createReadStream(audio);
33556
33758
  } else {
33557
33759
  throw new Error("Invalid audio input: must be Buffer or file path");
33558
33760
  }
@@ -33611,7 +33813,7 @@ var OpenAISTTProvider = class extends BaseMediaProvider {
33611
33813
  * Handle OpenAI API errors
33612
33814
  */
33613
33815
  handleError(error) {
33614
- if (error instanceof OpenAI2.APIError) {
33816
+ if (error instanceof OpenAI3.APIError) {
33615
33817
  const status = error.status;
33616
33818
  const message = error.message || "Unknown OpenAI API error";
33617
33819
  if (status === 401) {
@@ -34105,7 +34307,7 @@ var TextToSpeech = class _TextToSpeech {
34105
34307
  */
34106
34308
  async toFile(text, filePath, options) {
34107
34309
  const response = await this.synthesize(text, options);
34108
- await fs14.writeFile(filePath, response.audio);
34310
+ await fs15.writeFile(filePath, response.audio);
34109
34311
  }
34110
34312
  // ======================== Introspection Methods ========================
34111
34313
  /**
@@ -34453,7 +34655,7 @@ var SpeechToText = class _SpeechToText {
34453
34655
  * @param options - Optional transcription parameters
34454
34656
  */
34455
34657
  async transcribeFile(filePath, options) {
34456
- const audio = await fs14.readFile(filePath);
34658
+ const audio = await fs15.readFile(filePath);
34457
34659
  return this.transcribe(audio, options);
34458
34660
  }
34459
34661
  /**
@@ -34617,7 +34819,7 @@ var OpenAIImageProvider = class extends BaseMediaProvider {
34617
34819
  client;
34618
34820
  constructor(config) {
34619
34821
  super({ apiKey: config.auth.apiKey, ...config });
34620
- this.client = new OpenAI2({
34822
+ this.client = new OpenAI3({
34621
34823
  apiKey: config.auth.apiKey,
34622
34824
  baseURL: config.baseURL,
34623
34825
  organization: config.organization,
@@ -34779,7 +34981,7 @@ var OpenAIImageProvider = class extends BaseMediaProvider {
34779
34981
  if (Buffer.isBuffer(image)) {
34780
34982
  return new File([new Uint8Array(image)], "image.png", { type: "image/png" });
34781
34983
  }
34782
- return fs15.createReadStream(image);
34984
+ return fs16.createReadStream(image);
34783
34985
  }
34784
34986
  /**
34785
34987
  * Handle OpenAI API errors
@@ -34926,8 +35128,8 @@ var GoogleImageProvider = class extends BaseMediaProvider {
34926
35128
  if (Buffer.isBuffer(image)) {
34927
35129
  imageBytes = image.toString("base64");
34928
35130
  } else {
34929
- const fs18 = await import('fs');
34930
- const buffer = fs18.readFileSync(image);
35131
+ const fs17 = await import('fs');
35132
+ const buffer = fs17.readFileSync(image);
34931
35133
  imageBytes = buffer.toString("base64");
34932
35134
  }
34933
35135
  return {
@@ -34977,7 +35179,7 @@ var GrokImageProvider = class extends BaseMediaProvider {
34977
35179
  client;
34978
35180
  constructor(config) {
34979
35181
  super({ apiKey: config.auth.apiKey, ...config });
34980
- this.client = new OpenAI2({
35182
+ this.client = new OpenAI3({
34981
35183
  apiKey: config.auth.apiKey,
34982
35184
  baseURL: config.baseURL || GROK_API_BASE_URL,
34983
35185
  timeout: config.timeout,
@@ -35088,7 +35290,7 @@ var GrokImageProvider = class extends BaseMediaProvider {
35088
35290
  if (Buffer.isBuffer(image)) {
35089
35291
  return new File([new Uint8Array(image)], "image.png", { type: "image/png" });
35090
35292
  }
35091
- return fs15.createReadStream(image);
35293
+ return fs16.createReadStream(image);
35092
35294
  }
35093
35295
  /**
35094
35296
  * Handle API errors
@@ -36290,7 +36492,7 @@ var OpenAISoraProvider = class extends BaseMediaProvider {
36290
36492
  client;
36291
36493
  constructor(config) {
36292
36494
  super({ apiKey: config.auth.apiKey, ...config });
36293
- this.client = new OpenAI2({
36495
+ this.client = new OpenAI3({
36294
36496
  apiKey: config.auth.apiKey,
36295
36497
  baseURL: config.baseURL,
36296
36498
  organization: config.organization,
@@ -36538,8 +36740,8 @@ var OpenAISoraProvider = class extends BaseMediaProvider {
36538
36740
  return new File([new Uint8Array(image)], "input.png", { type: "image/png" });
36539
36741
  }
36540
36742
  if (!image.startsWith("http")) {
36541
- const fs18 = await import('fs');
36542
- const data = fs18.readFileSync(image);
36743
+ const fs17 = await import('fs');
36744
+ const data = fs17.readFileSync(image);
36543
36745
  return new File([new Uint8Array(data)], "input.png", { type: "image/png" });
36544
36746
  }
36545
36747
  const response = await fetch(image);
@@ -36717,7 +36919,7 @@ var GoogleVeoProvider = class extends BaseMediaProvider {
36717
36919
  if (video.videoBytes) {
36718
36920
  buffer = Buffer.from(video.videoBytes, "base64");
36719
36921
  } else if (video.uri) {
36720
- const fs18 = await import('fs/promises');
36922
+ const fs17 = await import('fs/promises');
36721
36923
  const os3 = await import('os');
36722
36924
  const path6 = await import('path');
36723
36925
  const tempDir = os3.tmpdir();
@@ -36728,11 +36930,11 @@ var GoogleVeoProvider = class extends BaseMediaProvider {
36728
36930
  // Pass as GeneratedVideo
36729
36931
  downloadPath: tempFile
36730
36932
  });
36731
- buffer = await fs18.readFile(tempFile);
36732
- await fs18.unlink(tempFile).catch(() => {
36933
+ buffer = await fs17.readFile(tempFile);
36934
+ await fs17.unlink(tempFile).catch(() => {
36733
36935
  });
36734
36936
  } catch (downloadError) {
36735
- await fs18.unlink(tempFile).catch(() => {
36937
+ await fs17.unlink(tempFile).catch(() => {
36736
36938
  });
36737
36939
  throw new ProviderError(
36738
36940
  "google",
@@ -36854,8 +37056,8 @@ var GoogleVeoProvider = class extends BaseMediaProvider {
36854
37056
  if (image.startsWith("http://") || image.startsWith("https://")) {
36855
37057
  return { imageUri: image };
36856
37058
  }
36857
- const fs18 = await import('fs/promises');
36858
- const data = await fs18.readFile(image);
37059
+ const fs17 = await import('fs/promises');
37060
+ const data = await fs17.readFile(image);
36859
37061
  return {
36860
37062
  imageBytes: data.toString("base64")
36861
37063
  };
@@ -37162,8 +37364,8 @@ var GrokImagineProvider = class extends BaseMediaProvider {
37162
37364
  if (image.startsWith("http") || image.startsWith("data:")) {
37163
37365
  return image;
37164
37366
  }
37165
- const fs18 = await import('fs');
37166
- const data = fs18.readFileSync(image);
37367
+ const fs17 = await import('fs');
37368
+ const data = fs17.readFileSync(image);
37167
37369
  const base64 = data.toString("base64");
37168
37370
  const ext = image.split(".").pop()?.toLowerCase() || "png";
37169
37371
  const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext}`;
@@ -40900,6 +41102,126 @@ var FileAgentDefinitionStorage = class {
40900
41102
  function createFileAgentDefinitionStorage(config) {
40901
41103
  return new FileAgentDefinitionStorage(config);
40902
41104
  }
41105
+ var MIME_TYPES = {
41106
+ png: "image/png",
41107
+ jpeg: "image/jpeg",
41108
+ jpg: "image/jpeg",
41109
+ webp: "image/webp",
41110
+ gif: "image/gif",
41111
+ mp4: "video/mp4",
41112
+ webm: "video/webm",
41113
+ mp3: "audio/mpeg",
41114
+ wav: "audio/wav",
41115
+ opus: "audio/opus",
41116
+ ogg: "audio/ogg",
41117
+ aac: "audio/aac",
41118
+ flac: "audio/flac",
41119
+ pcm: "audio/pcm"
41120
+ };
41121
+ var MEDIA_TYPE_PREFIXES = ["image", "video", "audio"];
41122
+ var FileMediaStorage = class {
41123
+ outputDir;
41124
+ initialized = false;
41125
+ constructor(config) {
41126
+ this.outputDir = config?.outputDir ?? path2.join(os2.tmpdir(), "oneringai-media");
41127
+ }
41128
+ async save(data, metadata) {
41129
+ const dir = metadata.userId ? path2.join(this.outputDir, metadata.userId) : this.outputDir;
41130
+ await fs15.mkdir(dir, { recursive: true });
41131
+ const filename = metadata.suggestedFilename ?? this.generateFilename(metadata);
41132
+ const filePath = path2.join(dir, filename);
41133
+ await fs15.writeFile(filePath, data);
41134
+ const format = metadata.format.toLowerCase();
41135
+ const mimeType = MIME_TYPES[format] ?? "application/octet-stream";
41136
+ return {
41137
+ location: filePath,
41138
+ mimeType,
41139
+ size: data.length
41140
+ };
41141
+ }
41142
+ async read(location) {
41143
+ try {
41144
+ return await fs15.readFile(location);
41145
+ } catch (err) {
41146
+ if (err.code === "ENOENT") {
41147
+ return null;
41148
+ }
41149
+ throw err;
41150
+ }
41151
+ }
41152
+ async delete(location) {
41153
+ try {
41154
+ await fs15.unlink(location);
41155
+ } catch (err) {
41156
+ if (err.code === "ENOENT") {
41157
+ return;
41158
+ }
41159
+ throw err;
41160
+ }
41161
+ }
41162
+ async exists(location) {
41163
+ try {
41164
+ await fs15.access(location);
41165
+ return true;
41166
+ } catch {
41167
+ return false;
41168
+ }
41169
+ }
41170
+ async list(options) {
41171
+ await this.ensureDir();
41172
+ let entries = [];
41173
+ const files = await fs15.readdir(this.outputDir);
41174
+ for (const file of files) {
41175
+ const filePath = path2.join(this.outputDir, file);
41176
+ try {
41177
+ const stat6 = await fs15.stat(filePath);
41178
+ if (!stat6.isFile()) continue;
41179
+ const ext = path2.extname(file).slice(1).toLowerCase();
41180
+ const mimeType = MIME_TYPES[ext] ?? "application/octet-stream";
41181
+ let type;
41182
+ for (const prefix of MEDIA_TYPE_PREFIXES) {
41183
+ if (file.startsWith(`${prefix}_`)) {
41184
+ type = prefix;
41185
+ break;
41186
+ }
41187
+ }
41188
+ entries.push({
41189
+ location: filePath,
41190
+ mimeType,
41191
+ size: stat6.size,
41192
+ type,
41193
+ createdAt: stat6.birthtime
41194
+ });
41195
+ } catch {
41196
+ }
41197
+ }
41198
+ if (options?.type) {
41199
+ entries = entries.filter((e) => e.type === options.type);
41200
+ }
41201
+ entries.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
41202
+ const offset = options?.offset ?? 0;
41203
+ const limit = options?.limit ?? entries.length;
41204
+ return entries.slice(offset, offset + limit);
41205
+ }
41206
+ getPath() {
41207
+ return this.outputDir;
41208
+ }
41209
+ generateFilename(metadata) {
41210
+ const timestamp = Date.now();
41211
+ const random2 = crypto2.randomBytes(4).toString("hex");
41212
+ const indexSuffix = metadata.index != null ? `_${metadata.index}` : "";
41213
+ return `${metadata.type}_${timestamp}_${random2}${indexSuffix}.${metadata.format}`;
41214
+ }
41215
+ async ensureDir() {
41216
+ if (!this.initialized) {
41217
+ await fs15.mkdir(this.outputDir, { recursive: true });
41218
+ this.initialized = true;
41219
+ }
41220
+ }
41221
+ };
41222
+ function createFileMediaStorage(config) {
41223
+ return new FileMediaStorage(config);
41224
+ }
40903
41225
 
40904
41226
  // src/capabilities/agents/StreamHelpers.ts
40905
41227
  var StreamHelpers = class {
@@ -41590,6 +41912,32 @@ function filterProtectedHeaders(headers) {
41590
41912
  }
41591
41913
  return filtered;
41592
41914
  }
41915
+ function normalizeBody(body) {
41916
+ if (typeof body === "string") {
41917
+ try {
41918
+ return JSON.parse(body);
41919
+ } catch {
41920
+ return body;
41921
+ }
41922
+ }
41923
+ return body;
41924
+ }
41925
+ function detectAPIError(data) {
41926
+ if (!data || typeof data !== "object") return null;
41927
+ const obj = data;
41928
+ if (obj.ok === false && typeof obj.error === "string") {
41929
+ return obj.error;
41930
+ }
41931
+ if (obj.success === false) {
41932
+ if (typeof obj.error === "string") return obj.error;
41933
+ if (typeof obj.message === "string") return obj.message;
41934
+ }
41935
+ if (obj.error && typeof obj.error === "object") {
41936
+ const err = obj.error;
41937
+ if (typeof err.message === "string") return err.message;
41938
+ }
41939
+ return null;
41940
+ }
41593
41941
  var ConnectorTools = class {
41594
41942
  /** Registry of service-specific tool factories */
41595
41943
  static factories = /* @__PURE__ */ new Map();
@@ -41822,7 +42170,7 @@ var ConnectorTools = class {
41822
42170
  static createGenericAPITool(connector, options) {
41823
42171
  const toolName = options?.toolName ?? `${connector.name}_api`;
41824
42172
  const userId = options?.userId;
41825
- const description = options?.description ?? `Make an authenticated API call to ${connector.displayName}.` + (connector.baseURL ? ` Base URL: ${connector.baseURL}` : " Provide full URL in endpoint.");
42173
+ const description = options?.description ?? `Make an authenticated API call to ${connector.displayName}.` + (connector.baseURL ? ` Base URL: ${connector.baseURL}.` : " Provide full URL in endpoint.") + ' IMPORTANT: For POST/PUT/PATCH requests, pass data in the "body" parameter as a JSON object, NOT as query string parameters in the endpoint URL. The body is sent as application/json.';
41826
42174
  return {
41827
42175
  definition: {
41828
42176
  type: "function",
@@ -41839,15 +42187,15 @@ var ConnectorTools = class {
41839
42187
  },
41840
42188
  endpoint: {
41841
42189
  type: "string",
41842
- description: "API endpoint (relative to base URL) or full URL"
42190
+ description: 'API endpoint path (relative to base URL) or full URL. Do NOT put request data as query parameters here for POST/PUT/PATCH \u2014 use the "body" parameter instead.'
41843
42191
  },
41844
42192
  body: {
41845
42193
  type: "object",
41846
- description: "Request body (for POST/PUT/PATCH)"
42194
+ description: 'JSON request body for POST/PUT/PATCH requests. MUST be a JSON object (NOT a string). Example: {"channel": "C123", "text": "hello"}. Do NOT stringify this \u2014 pass it as a raw JSON object. Do NOT use query string parameters for POST data.'
41847
42195
  },
41848
42196
  queryParams: {
41849
42197
  type: "object",
41850
- description: "URL query parameters"
42198
+ description: 'URL query parameters (for filtering/pagination on GET requests). Do NOT use for POST/PUT/PATCH data \u2014 use "body" instead.'
41851
42199
  },
41852
42200
  headers: {
41853
42201
  type: "object",
@@ -41858,7 +42206,8 @@ var ConnectorTools = class {
41858
42206
  }
41859
42207
  }
41860
42208
  },
41861
- execute: async (args) => {
42209
+ execute: async (args, context) => {
42210
+ const effectiveUserId = context?.userId ?? userId;
41862
42211
  let url2 = args.endpoint;
41863
42212
  if (args.queryParams && Object.keys(args.queryParams).length > 0) {
41864
42213
  const params = new URLSearchParams();
@@ -41871,7 +42220,8 @@ var ConnectorTools = class {
41871
42220
  let bodyStr;
41872
42221
  if (args.body) {
41873
42222
  try {
41874
- bodyStr = safeStringify2(args.body);
42223
+ const normalized = normalizeBody(args.body);
42224
+ bodyStr = safeStringify2(normalized);
41875
42225
  } catch (e) {
41876
42226
  return {
41877
42227
  success: false,
@@ -41890,7 +42240,7 @@ var ConnectorTools = class {
41890
42240
  },
41891
42241
  body: bodyStr
41892
42242
  },
41893
- userId
42243
+ effectiveUserId
41894
42244
  );
41895
42245
  const text = await response.text();
41896
42246
  let data;
@@ -41899,11 +42249,12 @@ var ConnectorTools = class {
41899
42249
  } catch {
41900
42250
  data = text;
41901
42251
  }
42252
+ const apiError = detectAPIError(data);
41902
42253
  return {
41903
- success: response.ok,
42254
+ success: response.ok && !apiError,
41904
42255
  status: response.status,
41905
- data: response.ok ? data : void 0,
41906
- error: response.ok ? void 0 : typeof data === "string" ? data : safeStringify2(data)
42256
+ data: response.ok && !apiError ? data : void 0,
42257
+ error: apiError ? apiError : response.ok ? void 0 : typeof data === "string" ? data : safeStringify2(data)
41907
42258
  };
41908
42259
  } catch (error) {
41909
42260
  return {
@@ -41912,7 +42263,10 @@ var ConnectorTools = class {
41912
42263
  };
41913
42264
  }
41914
42265
  },
41915
- describeCall: (args) => `${args.method} ${args.endpoint}`,
42266
+ describeCall: (args) => {
42267
+ const bodyInfo = args.body ? ` body=${JSON.stringify(args.body).slice(0, 100)}` : "";
42268
+ return `${args.method} ${args.endpoint}${bodyInfo}`;
42269
+ },
41916
42270
  permission: options?.permission ?? {
41917
42271
  scope: "session",
41918
42272
  riskLevel: "medium",
@@ -41947,8 +42301,8 @@ var FileStorage = class {
41947
42301
  }
41948
42302
  async ensureDirectory() {
41949
42303
  try {
41950
- await fs14.mkdir(this.directory, { recursive: true });
41951
- await fs14.chmod(this.directory, 448);
42304
+ await fs15.mkdir(this.directory, { recursive: true });
42305
+ await fs15.chmod(this.directory, 448);
41952
42306
  } catch (error) {
41953
42307
  }
41954
42308
  }
@@ -41957,20 +42311,20 @@ var FileStorage = class {
41957
42311
  */
41958
42312
  getFilePath(key) {
41959
42313
  const hash = crypto2.createHash("sha256").update(key).digest("hex");
41960
- return path3.join(this.directory, `${hash}.token`);
42314
+ return path2.join(this.directory, `${hash}.token`);
41961
42315
  }
41962
42316
  async storeToken(key, token) {
41963
42317
  await this.ensureDirectory();
41964
42318
  const filePath = this.getFilePath(key);
41965
42319
  const plaintext = JSON.stringify(token);
41966
42320
  const encrypted = encrypt(plaintext, this.encryptionKey);
41967
- await fs14.writeFile(filePath, encrypted, "utf8");
41968
- await fs14.chmod(filePath, 384);
42321
+ await fs15.writeFile(filePath, encrypted, "utf8");
42322
+ await fs15.chmod(filePath, 384);
41969
42323
  }
41970
42324
  async getToken(key) {
41971
42325
  const filePath = this.getFilePath(key);
41972
42326
  try {
41973
- const encrypted = await fs14.readFile(filePath, "utf8");
42327
+ const encrypted = await fs15.readFile(filePath, "utf8");
41974
42328
  const decrypted = decrypt(encrypted, this.encryptionKey);
41975
42329
  return JSON.parse(decrypted);
41976
42330
  } catch (error) {
@@ -41979,7 +42333,7 @@ var FileStorage = class {
41979
42333
  }
41980
42334
  console.error("Failed to read/decrypt token file:", error);
41981
42335
  try {
41982
- await fs14.unlink(filePath);
42336
+ await fs15.unlink(filePath);
41983
42337
  } catch {
41984
42338
  }
41985
42339
  return null;
@@ -41988,7 +42342,7 @@ var FileStorage = class {
41988
42342
  async deleteToken(key) {
41989
42343
  const filePath = this.getFilePath(key);
41990
42344
  try {
41991
- await fs14.unlink(filePath);
42345
+ await fs15.unlink(filePath);
41992
42346
  } catch (error) {
41993
42347
  if (error.code !== "ENOENT") {
41994
42348
  throw error;
@@ -41998,7 +42352,7 @@ var FileStorage = class {
41998
42352
  async hasToken(key) {
41999
42353
  const filePath = this.getFilePath(key);
42000
42354
  try {
42001
- await fs14.access(filePath);
42355
+ await fs15.access(filePath);
42002
42356
  return true;
42003
42357
  } catch {
42004
42358
  return false;
@@ -42009,7 +42363,7 @@ var FileStorage = class {
42009
42363
  */
42010
42364
  async listTokens() {
42011
42365
  try {
42012
- const files = await fs14.readdir(this.directory);
42366
+ const files = await fs15.readdir(this.directory);
42013
42367
  return files.filter((f) => f.endsWith(".token")).map((f) => f.replace(".token", ""));
42014
42368
  } catch {
42015
42369
  return [];
@@ -42020,10 +42374,10 @@ var FileStorage = class {
42020
42374
  */
42021
42375
  async clearAll() {
42022
42376
  try {
42023
- const files = await fs14.readdir(this.directory);
42377
+ const files = await fs15.readdir(this.directory);
42024
42378
  const tokenFiles = files.filter((f) => f.endsWith(".token"));
42025
42379
  await Promise.all(
42026
- tokenFiles.map((f) => fs14.unlink(path3.join(this.directory, f)).catch(() => {
42380
+ tokenFiles.map((f) => fs15.unlink(path2.join(this.directory, f)).catch(() => {
42027
42381
  }))
42028
42382
  );
42029
42383
  } catch {
@@ -42291,22 +42645,26 @@ var ConnectorConfigStore = class {
42291
42645
  * Encrypt secrets in ConnectorAuth based on auth type
42292
42646
  */
42293
42647
  encryptAuthSecrets(auth2) {
42648
+ const encryptedExtra = this.encryptExtra(auth2.extra);
42294
42649
  switch (auth2.type) {
42295
42650
  case "api_key":
42296
42651
  return {
42297
42652
  ...auth2,
42298
- apiKey: this.encryptValue(auth2.apiKey)
42653
+ apiKey: this.encryptValue(auth2.apiKey),
42654
+ ...encryptedExtra ? { extra: encryptedExtra } : {}
42299
42655
  };
42300
42656
  case "oauth":
42301
42657
  return {
42302
42658
  ...auth2,
42303
42659
  clientSecret: auth2.clientSecret ? this.encryptValue(auth2.clientSecret) : void 0,
42304
- privateKey: auth2.privateKey ? this.encryptValue(auth2.privateKey) : void 0
42660
+ privateKey: auth2.privateKey ? this.encryptValue(auth2.privateKey) : void 0,
42661
+ ...encryptedExtra ? { extra: encryptedExtra } : {}
42305
42662
  };
42306
42663
  case "jwt":
42307
42664
  return {
42308
42665
  ...auth2,
42309
- privateKey: this.encryptValue(auth2.privateKey)
42666
+ privateKey: this.encryptValue(auth2.privateKey),
42667
+ ...encryptedExtra ? { extra: encryptedExtra } : {}
42310
42668
  };
42311
42669
  default:
42312
42670
  return auth2;
@@ -42316,27 +42674,53 @@ var ConnectorConfigStore = class {
42316
42674
  * Decrypt secrets in ConnectorAuth based on auth type
42317
42675
  */
42318
42676
  decryptAuthSecrets(auth2) {
42677
+ const decryptedExtra = this.decryptExtra(auth2.extra);
42319
42678
  switch (auth2.type) {
42320
42679
  case "api_key":
42321
42680
  return {
42322
42681
  ...auth2,
42323
- apiKey: this.decryptValue(auth2.apiKey)
42682
+ apiKey: this.decryptValue(auth2.apiKey),
42683
+ ...decryptedExtra ? { extra: decryptedExtra } : {}
42324
42684
  };
42325
42685
  case "oauth":
42326
42686
  return {
42327
42687
  ...auth2,
42328
42688
  clientSecret: auth2.clientSecret ? this.decryptValue(auth2.clientSecret) : void 0,
42329
- privateKey: auth2.privateKey ? this.decryptValue(auth2.privateKey) : void 0
42689
+ privateKey: auth2.privateKey ? this.decryptValue(auth2.privateKey) : void 0,
42690
+ ...decryptedExtra ? { extra: decryptedExtra } : {}
42330
42691
  };
42331
42692
  case "jwt":
42332
42693
  return {
42333
42694
  ...auth2,
42334
- privateKey: this.decryptValue(auth2.privateKey)
42695
+ privateKey: this.decryptValue(auth2.privateKey),
42696
+ ...decryptedExtra ? { extra: decryptedExtra } : {}
42335
42697
  };
42336
42698
  default:
42337
42699
  return auth2;
42338
42700
  }
42339
42701
  }
42702
+ /**
42703
+ * Encrypt all values in an extra Record (vendor-specific credentials)
42704
+ */
42705
+ encryptExtra(extra) {
42706
+ if (!extra || Object.keys(extra).length === 0) return void 0;
42707
+ const result = {};
42708
+ for (const [key, value] of Object.entries(extra)) {
42709
+ result[key] = this.encryptValue(value);
42710
+ }
42711
+ return result;
42712
+ }
42713
+ /**
42714
+ * Decrypt all values in an extra Record (vendor-specific credentials)
42715
+ */
42716
+ decryptExtra(extra) {
42717
+ if (!extra || Object.keys(extra).length === 0) return void 0;
42718
+ const result = {};
42719
+ for (const [key, value] of Object.entries(extra)) {
42720
+ result[key] = this.decryptValue(value);
42721
+ }
42722
+ return result;
42723
+ }
42340
42724
  /**
42341
42725
  * Encrypt a single value if not already encrypted
42342
42726
  */
@@ -42414,20 +42798,20 @@ var FileConnectorStorage = class {
42414
42798
  throw new Error("FileConnectorStorage requires a directory path");
42415
42799
  }
42416
42800
  this.directory = config.directory;
42417
- this.indexPath = path3.join(this.directory, "_index.json");
42801
+ this.indexPath = path2.join(this.directory, "_index.json");
42418
42802
  }
42419
42803
  async save(name, stored) {
42420
42804
  await this.ensureDirectory();
42421
42805
  const filePath = this.getFilePath(name);
42422
42806
  const json = JSON.stringify(stored, null, 2);
42423
- await fs14.writeFile(filePath, json, "utf8");
42424
- await fs14.chmod(filePath, 384);
42807
+ await fs15.writeFile(filePath, json, "utf8");
42808
+ await fs15.chmod(filePath, 384);
42425
42809
  await this.updateIndex(name, "add");
42426
42810
  }
42427
42811
  async get(name) {
42428
42812
  const filePath = this.getFilePath(name);
42429
42813
  try {
42430
- const json = await fs14.readFile(filePath, "utf8");
42814
+ const json = await fs15.readFile(filePath, "utf8");
42431
42815
  return JSON.parse(json);
42432
42816
  } catch (error) {
42433
42817
  const err = error;
@@ -42440,7 +42824,7 @@ var FileConnectorStorage = class {
42440
42824
  async delete(name) {
42441
42825
  const filePath = this.getFilePath(name);
42442
42826
  try {
42443
- await fs14.unlink(filePath);
42827
+ await fs15.unlink(filePath);
42444
42828
  await this.updateIndex(name, "remove");
42445
42829
  return true;
42446
42830
  } catch (error) {
@@ -42454,7 +42838,7 @@ var FileConnectorStorage = class {
42454
42838
  async has(name) {
42455
42839
  const filePath = this.getFilePath(name);
42456
42840
  try {
42457
- await fs14.access(filePath);
42841
+ await fs15.access(filePath);
42458
42842
  return true;
42459
42843
  } catch {
42460
42844
  return false;
@@ -42480,13 +42864,13 @@ var FileConnectorStorage = class {
42480
42864
  */
42481
42865
  async clear() {
42482
42866
  try {
42483
- const files = await fs14.readdir(this.directory);
42867
+ const files = await fs15.readdir(this.directory);
42484
42868
  const connectorFiles = files.filter(
42485
42869
  (f) => f.endsWith(".connector.json") || f === "_index.json"
42486
42870
  );
42487
42871
  await Promise.all(
42488
42872
  connectorFiles.map(
42489
- (f) => fs14.unlink(path3.join(this.directory, f)).catch(() => {
42873
+ (f) => fs15.unlink(path2.join(this.directory, f)).catch(() => {
42490
42874
  })
42491
42875
  )
42492
42876
  );
@@ -42499,7 +42883,7 @@ var FileConnectorStorage = class {
42499
42883
  */
42500
42884
  getFilePath(name) {
42501
42885
  const hash = this.hashName(name);
42502
- return path3.join(this.directory, `${hash}.connector.json`);
42886
+ return path2.join(this.directory, `${hash}.connector.json`);
42503
42887
  }
42504
42888
  /**
42505
42889
  * Hash connector name to prevent enumeration
@@ -42513,8 +42897,8 @@ var FileConnectorStorage = class {
42513
42897
  async ensureDirectory() {
42514
42898
  if (this.initialized) return;
42515
42899
  try {
42516
- await fs14.mkdir(this.directory, { recursive: true });
42517
- await fs14.chmod(this.directory, 448);
42900
+ await fs15.mkdir(this.directory, { recursive: true });
42901
+ await fs15.chmod(this.directory, 448);
42518
42902
  this.initialized = true;
42519
42903
  } catch {
42520
42904
  this.initialized = true;
@@ -42525,7 +42909,7 @@ var FileConnectorStorage = class {
42525
42909
  */
42526
42910
  async loadIndex() {
42527
42911
  try {
42528
- const json = await fs14.readFile(this.indexPath, "utf8");
42912
+ const json = await fs15.readFile(this.indexPath, "utf8");
42529
42913
  return JSON.parse(json);
42530
42914
  } catch {
42531
42915
  return { connectors: {} };
@@ -42543,8 +42927,8 @@ var FileConnectorStorage = class {
42543
42927
  delete index.connectors[hash];
42544
42928
  }
42545
42929
  const json = JSON.stringify(index, null, 2);
42546
- await fs14.writeFile(this.indexPath, json, "utf8");
42547
- await fs14.chmod(this.indexPath, 384);
42930
+ await fs15.writeFile(this.indexPath, json, "utf8");
42931
+ await fs15.chmod(this.indexPath, 384);
42548
42932
  }
42549
42933
  };
42550
42934
 
@@ -42589,11 +42973,19 @@ function buildAuthConfig(authTemplate, credentials) {
42589
42973
  if (!credentials.apiKey) {
42590
42974
  throw new Error("API key is required for api_key auth");
42591
42975
  }
42976
+ const standardApiKeyFields = /* @__PURE__ */ new Set(["apiKey", "headerName", "headerPrefix"]);
42977
+ const extra = {};
42978
+ for (const field of authTemplate.optionalFields ?? []) {
42979
+ if (!standardApiKeyFields.has(field) && credentials[field]) {
42980
+ extra[field] = credentials[field];
42981
+ }
42982
+ }
42592
42983
  return {
42593
42984
  type: "api_key",
42594
42985
  apiKey: credentials.apiKey,
42595
42986
  headerName: defaults.headerName ?? "Authorization",
42596
- headerPrefix: defaults.headerPrefix ?? "Bearer"
42987
+ headerPrefix: defaults.headerPrefix ?? "Bearer",
42988
+ ...Object.keys(extra).length > 0 ? { extra } : {}
42597
42989
  };
42598
42990
  }
42599
42991
  if (!authTemplate.flow) {
@@ -42863,8 +43255,9 @@ var slackTemplate = {
42863
43255
  id: "bot-token",
42864
43256
  name: "Bot Token",
42865
43257
  type: "api_key",
42866
- description: "Internal workspace bot - get from OAuth & Permissions page of your Slack app",
43258
+ description: "Internal workspace bot - get from OAuth & Permissions page of your Slack app. For Socket Mode bots, also provide appToken and signingSecret in extra fields.",
42867
43259
  requiredFields: ["apiKey"],
43260
+ optionalFields: ["appToken", "signingSecret"],
42868
43261
  defaults: {
42869
43262
  type: "api_key",
42870
43263
  headerName: "Authorization",
@@ -44702,14 +45095,14 @@ function createMessageWithImages(text, imageUrls, role = "user" /* USER */) {
44702
45095
  var execAsync = promisify(exec);
44703
45096
  function cleanupTempFile(filePath) {
44704
45097
  try {
44705
- if (fs15.existsSync(filePath)) {
44706
- fs15.unlinkSync(filePath);
45098
+ if (fs16.existsSync(filePath)) {
45099
+ fs16.unlinkSync(filePath);
44707
45100
  }
44708
45101
  } catch {
44709
45102
  }
44710
45103
  }
44711
45104
  async function readClipboardImage() {
44712
- const platform2 = os.platform();
45105
+ const platform2 = os2.platform();
44713
45106
  try {
44714
45107
  switch (platform2) {
44715
45108
  case "darwin":
@@ -44732,7 +45125,7 @@ async function readClipboardImage() {
44732
45125
  }
44733
45126
  }
44734
45127
  async function readClipboardImageMac() {
44735
- const tempFile = path3.join(os.tmpdir(), `clipboard-${Date.now()}.png`);
45128
+ const tempFile = path2.join(os2.tmpdir(), `clipboard-${Date.now()}.png`);
44736
45129
  try {
44737
45130
  try {
44738
45131
  await execAsync(`pngpaste "${tempFile}"`);
@@ -44754,7 +45147,7 @@ async function readClipboardImageMac() {
44754
45147
  end try
44755
45148
  `;
44756
45149
  const { stdout } = await execAsync(`osascript -e '${script}'`);
44757
- if (stdout.includes("success") || fs15.existsSync(tempFile)) {
45150
+ if (stdout.includes("success") || fs16.existsSync(tempFile)) {
44758
45151
  return await convertFileToDataUri(tempFile);
44759
45152
  }
44760
45153
  return {
@@ -44767,18 +45160,18 @@ async function readClipboardImageMac() {
44767
45160
  }
44768
45161
  }
44769
45162
  async function readClipboardImageLinux() {
44770
- const tempFile = path3.join(os.tmpdir(), `clipboard-${Date.now()}.png`);
45163
+ const tempFile = path2.join(os2.tmpdir(), `clipboard-${Date.now()}.png`);
44771
45164
  try {
44772
45165
  try {
44773
45166
  await execAsync(`xclip -selection clipboard -t image/png -o > "${tempFile}"`);
44774
- if (fs15.existsSync(tempFile) && fs15.statSync(tempFile).size > 0) {
45167
+ if (fs16.existsSync(tempFile) && fs16.statSync(tempFile).size > 0) {
44775
45168
  return await convertFileToDataUri(tempFile);
44776
45169
  }
44777
45170
  } catch {
44778
45171
  }
44779
45172
  try {
44780
45173
  await execAsync(`wl-paste -t image/png > "${tempFile}"`);
44781
- if (fs15.existsSync(tempFile) && fs15.statSync(tempFile).size > 0) {
45174
+ if (fs16.existsSync(tempFile) && fs16.statSync(tempFile).size > 0) {
44782
45175
  return await convertFileToDataUri(tempFile);
44783
45176
  }
44784
45177
  } catch {
@@ -44792,7 +45185,7 @@ async function readClipboardImageLinux() {
44792
45185
  }
44793
45186
  }
44794
45187
  async function readClipboardImageWindows() {
44795
- const tempFile = path3.join(os.tmpdir(), `clipboard-${Date.now()}.png`);
45188
+ const tempFile = path2.join(os2.tmpdir(), `clipboard-${Date.now()}.png`);
44796
45189
  try {
44797
45190
  const psScript = `
44798
45191
  Add-Type -AssemblyName System.Windows.Forms;
@@ -44805,7 +45198,7 @@ async function readClipboardImageWindows() {
44805
45198
  }
44806
45199
  `;
44807
45200
  await execAsync(`powershell -Command "${psScript}"`);
44808
- if (fs15.existsSync(tempFile) && fs15.statSync(tempFile).size > 0) {
45201
+ if (fs16.existsSync(tempFile) && fs16.statSync(tempFile).size > 0) {
44809
45202
  return await convertFileToDataUri(tempFile);
44810
45203
  }
44811
45204
  return {
@@ -44818,7 +45211,7 @@ async function readClipboardImageWindows() {
44818
45211
  }
44819
45212
  async function convertFileToDataUri(filePath) {
44820
45213
  try {
44821
- const imageBuffer = fs15.readFileSync(filePath);
45214
+ const imageBuffer = fs16.readFileSync(filePath);
44822
45215
  const base64Image = imageBuffer.toString("base64");
44823
45216
  const magic = imageBuffer.slice(0, 4).toString("hex");
44824
45217
  let mimeType = "image/png";
@@ -44845,7 +45238,7 @@ async function convertFileToDataUri(filePath) {
44845
45238
  }
44846
45239
  }
44847
45240
  async function hasClipboardImage() {
44848
- const platform2 = os.platform();
45241
+ const platform2 = os2.platform();
44849
45242
  try {
44850
45243
  switch (platform2) {
44851
45244
  case "darwin":
@@ -45063,20 +45456,29 @@ __export(tools_exports, {
45063
45456
  ConnectorTools: () => ConnectorTools,
45064
45457
  DEFAULT_FILESYSTEM_CONFIG: () => DEFAULT_FILESYSTEM_CONFIG,
45065
45458
  DEFAULT_SHELL_CONFIG: () => DEFAULT_SHELL_CONFIG,
45066
- FileMediaOutputHandler: () => FileMediaOutputHandler,
45459
+ FileMediaOutputHandler: () => FileMediaStorage,
45067
45460
  ToolRegistry: () => ToolRegistry,
45068
45461
  bash: () => bash,
45069
45462
  createBashTool: () => createBashTool,
45463
+ createCreatePRTool: () => createCreatePRTool,
45070
45464
  createEditFileTool: () => createEditFileTool,
45071
45465
  createExecuteJavaScriptTool: () => createExecuteJavaScriptTool,
45466
+ createGetPRTool: () => createGetPRTool,
45467
+ createGitHubReadFileTool: () => createGitHubReadFileTool,
45072
45468
  createGlobTool: () => createGlobTool,
45073
45469
  createGrepTool: () => createGrepTool,
45074
45470
  createImageGenerationTool: () => createImageGenerationTool,
45075
45471
  createListDirectoryTool: () => createListDirectoryTool,
45472
+ createPRCommentsTool: () => createPRCommentsTool,
45473
+ createPRFilesTool: () => createPRFilesTool,
45076
45474
  createReadFileTool: () => createReadFileTool,
45475
+ createSearchCodeTool: () => createSearchCodeTool,
45476
+ createSearchFilesTool: () => createSearchFilesTool,
45077
45477
  createSpeechToTextTool: () => createSpeechToTextTool,
45078
45478
  createTextToSpeechTool: () => createTextToSpeechTool,
45079
45479
  createVideoTools: () => createVideoTools,
45480
+ createWebScrapeTool: () => createWebScrapeTool,
45481
+ createWebSearchTool: () => createWebSearchTool,
45080
45482
  createWriteFileTool: () => createWriteFileTool,
45081
45483
  developerTools: () => developerTools,
45082
45484
  editFile: () => editFile,
@@ -45085,6 +45487,7 @@ __export(tools_exports, {
45085
45487
  getAllBuiltInTools: () => getAllBuiltInTools,
45086
45488
  getBackgroundOutput: () => getBackgroundOutput,
45087
45489
  getMediaOutputHandler: () => getMediaOutputHandler,
45490
+ getMediaStorage: () => getMediaStorage,
45088
45491
  getToolByName: () => getToolByName,
45089
45492
  getToolCategories: () => getToolCategories,
45090
45493
  getToolRegistry: () => getToolRegistry,
@@ -45097,15 +45500,15 @@ __export(tools_exports, {
45097
45500
  jsonManipulator: () => jsonManipulator,
45098
45501
  killBackgroundProcess: () => killBackgroundProcess,
45099
45502
  listDirectory: () => listDirectory,
45100
- readFile: () => readFile4,
45503
+ parseRepository: () => parseRepository,
45504
+ readFile: () => readFile5,
45505
+ resolveRepository: () => resolveRepository,
45101
45506
  setMediaOutputHandler: () => setMediaOutputHandler,
45507
+ setMediaStorage: () => setMediaStorage,
45102
45508
  toolRegistry: () => toolRegistry,
45103
45509
  validatePath: () => validatePath,
45104
45510
  webFetch: () => webFetch,
45105
- webFetchJS: () => webFetchJS,
45106
- webScrape: () => webScrape,
45107
- webSearch: () => webSearch,
45108
- writeFile: () => writeFile4
45511
+ writeFile: () => writeFile5
45109
45512
  });
45110
45513
  var DEFAULT_FILESYSTEM_CONFIG = {
45111
45514
  workingDirectory: process.cwd(),
@@ -45358,7 +45761,7 @@ EXAMPLES:
45358
45761
  }
45359
45762
  };
45360
45763
  }
45361
- var readFile4 = createReadFileTool();
45764
+ var readFile5 = createReadFileTool();
45362
45765
  function createWriteFileTool(config = {}) {
45363
45766
  const mergedConfig = { ...DEFAULT_FILESYSTEM_CONFIG, ...config };
45364
45767
  return {
@@ -45445,7 +45848,7 @@ EXAMPLES:
45445
45848
  }
45446
45849
  };
45447
45850
  }
45448
- var writeFile4 = createWriteFileTool();
45851
+ var writeFile5 = createWriteFileTool();
45449
45852
  function createEditFileTool(config = {}) {
45450
45853
  const mergedConfig = { ...DEFAULT_FILESYSTEM_CONFIG, ...config };
45451
45854
  return {
@@ -46372,7 +46775,8 @@ EXAMPLES:
46372
46775
  shell: mergedConfig.shell,
46373
46776
  cwd: mergedConfig.workingDirectory,
46374
46777
  env,
46375
- stdio: ["pipe", "pipe", "pipe"]
46778
+ stdio: ["pipe", "pipe", "pipe"],
46779
+ detached: true
46376
46780
  });
46377
46781
  if (run_in_background && mergedConfig.allowBackground) {
46378
46782
  const bgId = generateBackgroundId();
@@ -46399,14 +46803,27 @@ EXAMPLES:
46399
46803
  let stdout = "";
46400
46804
  let stderr = "";
46401
46805
  let killed = false;
46806
+ const killProcessGroup = (signal) => {
46807
+ try {
46808
+ if (childProcess.pid) {
46809
+ process.kill(-childProcess.pid, signal);
46810
+ }
46811
+ } catch {
46812
+ try {
46813
+ childProcess.kill(signal);
46814
+ } catch {
46815
+ }
46816
+ }
46817
+ };
46818
+ const GRACEFUL_KILL_WAIT_MS = 3e3;
46402
46819
  const timeoutId = setTimeout(() => {
46403
46820
  killed = true;
46404
- childProcess.kill("SIGTERM");
46821
+ killProcessGroup("SIGTERM");
46405
46822
  setTimeout(() => {
46406
46823
  if (!childProcess.killed) {
46407
- childProcess.kill("SIGKILL");
46824
+ killProcessGroup("SIGKILL");
46408
46825
  }
46409
- }, 5e3);
46826
+ }, GRACEFUL_KILL_WAIT_MS);
46410
46827
  }, effectiveTimeout);
46411
46828
  childProcess.stdout.on("data", (data) => {
46412
46829
  stdout += data.toString();
@@ -46420,8 +46837,28 @@ EXAMPLES:
46420
46837
  stderr = stderr.slice(-mergedConfig.maxOutputSize);
46421
46838
  }
46422
46839
  });
46423
- childProcess.on("close", (code, signal) => {
46840
+ let resolved = false;
46841
+ const safeResolve = (result) => {
46842
+ if (resolved) return;
46843
+ resolved = true;
46424
46844
  clearTimeout(timeoutId);
46845
+ clearTimeout(hardTimeoutId);
46846
+ resolve4(result);
46847
+ };
46848
+ const HARD_TIMEOUT_GRACE_MS = 5e3;
46849
+ const hardTimeoutId = setTimeout(() => {
46850
+ if (!resolved) {
46851
+ killProcessGroup("SIGKILL");
46852
+ safeResolve({
46853
+ success: false,
46854
+ stdout,
46855
+ stderr,
46856
+ duration: Date.now() - startTime,
46857
+ error: `Command timed out after ${effectiveTimeout}ms (hard timeout: process group did not exit)`
46858
+ });
46859
+ }
46860
+ }, effectiveTimeout + GRACEFUL_KILL_WAIT_MS + HARD_TIMEOUT_GRACE_MS);
46861
+ childProcess.on("close", (code, signal) => {
46425
46862
  const duration = Date.now() - startTime;
46426
46863
  let truncated = false;
46427
46864
  if (stdout.length > mergedConfig.maxOutputSize) {
@@ -46433,7 +46870,7 @@ EXAMPLES:
46433
46870
  truncated = true;
46434
46871
  }
46435
46872
  if (killed) {
46436
- resolve4({
46873
+ safeResolve({
46437
46874
  success: false,
46438
46875
  stdout,
46439
46876
  stderr,
@@ -46444,7 +46881,7 @@ EXAMPLES:
46444
46881
  error: `Command timed out after ${effectiveTimeout}ms`
46445
46882
  });
46446
46883
  } else {
46447
- resolve4({
46884
+ safeResolve({
46448
46885
  success: code === 0,
46449
46886
  stdout,
46450
46887
  stderr,
@@ -46457,8 +46894,7 @@ EXAMPLES:
46457
46894
  }
46458
46895
  });
46459
46896
  childProcess.on("error", (error) => {
46460
- clearTimeout(timeoutId);
46461
- resolve4({
46897
+ safeResolve({
46462
46898
  success: false,
46463
46899
  error: `Failed to execute command: ${error.message}`,
46464
46900
  duration: Date.now() - startTime
@@ -46794,6 +47230,111 @@ The tool returns a result object with:
46794
47230
  }
46795
47231
  };
46796
47232
 
47233
+ // src/tools/web/createWebSearchTool.ts
47234
+ init_Logger();
47235
+ var searchLogger = logger.child({ component: "webSearch" });
47236
+ function createWebSearchTool(connector) {
47237
+ return {
47238
+ definition: {
47239
+ type: "function",
47240
+ function: {
47241
+ name: "web_search",
47242
+ description: `Search the web and get relevant results with snippets.
47243
+
47244
+ RETURNS:
47245
+ An array of search results, each containing:
47246
+ - title: Page title
47247
+ - url: Direct URL to the page
47248
+ - snippet: Short description/excerpt from the page
47249
+ - position: Search ranking position (1, 2, 3...)
47250
+
47251
+ USE CASES:
47252
+ - Find current information on any topic
47253
+ - Research multiple sources
47254
+ - Discover relevant websites
47255
+ - Find URLs to fetch with web_fetch tool
47256
+
47257
+ WORKFLOW PATTERN:
47258
+ 1. Use web_search to find relevant URLs
47259
+ 2. Use web_fetch to get full content from top results
47260
+ 3. Process and summarize the information
47261
+
47262
+ EXAMPLE:
47263
+ {
47264
+ "query": "latest AI developments 2026",
47265
+ "numResults": 5
47266
+ }`,
47267
+ parameters: {
47268
+ type: "object",
47269
+ properties: {
47270
+ query: {
47271
+ type: "string",
47272
+ description: "The search query string. Be specific for better results."
47273
+ },
47274
+ numResults: {
47275
+ type: "number",
47276
+ description: "Number of results to return (default: 10, max: 100)."
47277
+ },
47278
+ country: {
47279
+ type: "string",
47280
+ description: 'Country/region code for localized results (e.g., "us", "gb", "de")'
47281
+ },
47282
+ language: {
47283
+ type: "string",
47284
+ description: 'Language code for results (e.g., "en", "fr", "de")'
47285
+ }
47286
+ },
47287
+ required: ["query"]
47288
+ }
47289
+ },
47290
+ blocking: true,
47291
+ timeout: 15e3
47292
+ },
47293
+ execute: async (args) => {
47294
+ const numResults = args.numResults || 10;
47295
+ searchLogger.debug({ connectorName: connector.name }, "Executing search with connector");
47296
+ try {
47297
+ const searchProvider = SearchProvider.create({ connector: connector.name });
47298
+ const response = await searchProvider.search(args.query, {
47299
+ numResults,
47300
+ country: args.country,
47301
+ language: args.language
47302
+ });
47303
+ if (response.success) {
47304
+ searchLogger.debug({
47305
+ provider: response.provider,
47306
+ count: response.count
47307
+ }, "Search completed successfully");
47308
+ } else {
47309
+ searchLogger.warn({
47310
+ provider: response.provider,
47311
+ error: response.error
47312
+ }, "Search failed");
47313
+ }
47314
+ return {
47315
+ success: response.success,
47316
+ query: response.query,
47317
+ provider: response.provider,
47318
+ results: response.results,
47319
+ count: response.count,
47320
+ error: response.error
47321
+ };
47322
+ } catch (error) {
47323
+ searchLogger.error({ error: error.message, connectorName: connector.name }, "Search threw exception");
47324
+ return {
47325
+ success: false,
47326
+ query: args.query,
47327
+ provider: connector.name,
47328
+ results: [],
47329
+ count: 0,
47330
+ error: error.message || "Unknown error"
47331
+ };
47332
+ }
47333
+ },
47334
+ describeCall: (args) => `"${args.query}"${args.numResults ? ` (${args.numResults} results)` : ""}`
47335
+ };
47336
+ }
47337
+
46797
47338
  // src/tools/web/contentDetector.ts
46798
47339
  function detectContentQuality(html, text, $) {
46799
47340
  const issues = [];
@@ -46868,7 +47409,7 @@ function detectContentQuality(html, text, $) {
46868
47409
  }
46869
47410
  let suggestion;
46870
47411
  if (requiresJS && score < 50) {
46871
- suggestion = "Content quality is low. This appears to be a JavaScript-rendered site. Use the web_fetch_js tool for better results.";
47412
+ suggestion = "Content quality is low. This appears to be a JavaScript-rendered site. Use a scraping service connector for better results.";
46872
47413
  } else if (score < 30) {
46873
47414
  suggestion = "Content extraction failed or page has errors. Check the URL and try again.";
46874
47415
  }
@@ -46977,7 +47518,7 @@ The tool analyzes the fetched content and returns a quality score (0-100):
46977
47518
  - 50-79: Moderate quality, some content extracted
46978
47519
  - 0-49: Low quality, likely needs JavaScript or has errors
46979
47520
 
46980
- If the quality score is low or requiresJS is true, the tool will suggest using 'web_fetch_js' instead.
47521
+ If the quality score is low or requiresJS is true, consider using a scraping service connector for better results.
46981
47522
 
46982
47523
  RETURNS:
46983
47524
  {
@@ -47141,460 +47682,109 @@ With custom user agent:
47141
47682
  }
47142
47683
  };
47143
47684
 
47144
- // src/tools/web/webFetchJS.ts
47145
- var puppeteerModule = null;
47146
- var browserInstance = null;
47147
- async function loadPuppeteer() {
47148
- if (!puppeteerModule) {
47149
- try {
47150
- puppeteerModule = await import('puppeteer');
47151
- } catch (error) {
47152
- throw new Error("Puppeteer not installed");
47153
- }
47154
- }
47155
- return puppeteerModule;
47156
- }
47157
- async function getBrowser() {
47158
- if (!browserInstance) {
47159
- const puppeteer = await loadPuppeteer();
47160
- browserInstance = await puppeteer.launch({
47161
- headless: true,
47162
- args: ["--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage"]
47163
- });
47164
- process.on("exit", async () => {
47165
- if (browserInstance) {
47166
- await browserInstance.close();
47167
- }
47168
- });
47169
- }
47170
- return browserInstance;
47171
- }
47172
- var webFetchJS = {
47173
- definition: {
47174
- type: "function",
47175
- function: {
47176
- name: "web_fetch_js",
47177
- description: `Fetch and extract content from JavaScript-rendered websites using a headless browser (Puppeteer).
47178
-
47179
- USE THIS TOOL WHEN:
47180
- - The web_fetch tool returned a low quality score (<50)
47181
- - The web_fetch tool suggested using JavaScript rendering
47182
- - You know the website is built with React/Vue/Angular/Next.js
47183
- - Content loads dynamically via JavaScript
47184
- - The page requires interaction (though this tool doesn't support interaction yet)
47185
-
47186
- HOW IT WORKS:
47187
- - Launches a headless Chrome browser
47188
- - Navigates to the URL
47189
- - Waits for JavaScript to execute and content to load
47190
- - Extracts the rendered HTML and text content
47191
- - Optionally captures a screenshot
47192
-
47193
- CAPABILITIES:
47194
- - Executes all JavaScript on the page
47195
- - Waits for network to be idle (all resources loaded)
47196
- - Can wait for specific CSS selectors to appear
47197
- - Handles React, Vue, Angular, Next.js, and other SPAs
47198
- - Returns content after full JavaScript execution
47199
-
47200
- LIMITATIONS:
47201
- - Slower than web_fetch (typically 3-10 seconds vs <1 second)
47202
- - Uses more system resources (runs a full browser)
47203
- - May still fail on sites with aggressive bot detection
47204
- - Requires puppeteer to be installed (npm install puppeteer)
47205
-
47206
- PERFORMANCE:
47207
- - First call: Slower (launches browser ~1-2s)
47208
- - Subsequent calls: Faster (reuses browser instance)
47209
-
47210
- RETURNS:
47211
- {
47212
- success: boolean,
47213
- url: string,
47214
- title: string,
47215
- content: string, // Clean markdown (converted via Readability + Turndown)
47216
- screenshot: string, // Base64 PNG screenshot (if requested)
47217
- loadTime: number, // Time taken in milliseconds
47218
- excerpt: string, // Short summary excerpt (if extracted)
47219
- byline: string, // Author info (if extracted)
47220
- wasTruncated: boolean, // True if content was truncated
47221
- error: string // Error message if failed
47222
- }
47223
-
47224
- EXAMPLES:
47225
- Basic usage:
47226
- {
47227
- url: "https://react-app.com/page"
47228
- }
47229
-
47230
- Wait for specific content:
47231
- {
47232
- url: "https://app.com/dashboard",
47233
- waitForSelector: "#main-content", // Wait for this element
47234
- timeout: 20000
47685
+ // src/tools/web/createWebScrapeTool.ts
47686
+ init_Logger();
47687
+ var scrapeLogger = logger.child({ component: "webScrape" });
47688
+ var DEFAULT_MIN_QUALITY = 50;
47689
+ function stripBase64DataUris(content) {
47690
+ if (!content) return content;
47691
+ let cleaned = content.replace(/!\[[^\]]*\]\(data:[^)]+\)/g, "[image removed]");
47692
+ cleaned = cleaned.replace(/url\(['"]?data:[^)]+['"]?\)/gi, "url([data-uri-removed])");
47693
+ cleaned = cleaned.replace(/data:(?:image|font|application)\/[^;]+;base64,[A-Za-z0-9+/=]{100,}/g, "[base64-data-removed]");
47694
+ return cleaned;
47235
47695
  }
47236
-
47237
- With screenshot:
47238
- {
47239
- url: "https://site.com",
47240
- takeScreenshot: true
47241
- }`,
47242
- parameters: {
47243
- type: "object",
47244
- properties: {
47245
- url: {
47246
- type: "string",
47247
- description: "The URL to fetch. Must start with http:// or https://"
47248
- },
47249
- waitForSelector: {
47250
- type: "string",
47251
- description: 'Optional CSS selector to wait for before extracting content. Example: "#main-content" or ".article-body"'
47252
- },
47253
- timeout: {
47254
- type: "number",
47255
- description: "Max wait time in milliseconds (default: 15000)"
47256
- },
47257
- takeScreenshot: {
47258
- type: "boolean",
47259
- description: "Whether to capture a screenshot of the page (default: false). Screenshot returned as base64 PNG."
47260
- }
47261
- },
47262
- required: ["url"]
47263
- }
47264
- },
47265
- blocking: true,
47266
- timeout: 3e4
47267
- // Allow extra time for browser operations
47268
- },
47269
- execute: async (args) => {
47270
- let page = null;
47696
+ function createWebScrapeTool(connector) {
47697
+ async function tryNative(args, startTime, attemptedMethods) {
47698
+ attemptedMethods.push("native");
47699
+ scrapeLogger.debug({ url: args.url }, "Trying native fetch");
47271
47700
  try {
47272
- const browser = await getBrowser();
47273
- page = await browser.newPage();
47274
- await page.setViewport({ width: 1280, height: 800 });
47275
- await page.setUserAgent(
47276
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
47277
- );
47278
- const startTime = Date.now();
47279
- await page.goto(args.url, {
47280
- waitUntil: "networkidle2",
47281
- // Wait until network is mostly idle
47282
- timeout: args.timeout || 15e3
47701
+ const result = await webFetch.execute({
47702
+ url: args.url,
47703
+ timeout: args.timeout || 1e4
47283
47704
  });
47284
- if (args.waitForSelector) {
47285
- await page.waitForSelector(args.waitForSelector, {
47286
- timeout: args.timeout || 15e3
47287
- });
47288
- }
47289
- const html = await page.content();
47290
- const browserTitle = await page.title();
47291
- const loadTime = Date.now() - startTime;
47292
- let screenshot;
47293
- if (args.takeScreenshot) {
47294
- const buffer = await page.screenshot({
47295
- type: "png",
47296
- fullPage: false
47297
- // Just viewport
47298
- });
47299
- screenshot = buffer.toString("base64");
47300
- }
47301
- await page.close();
47302
- const mdResult = await htmlToMarkdown(html, args.url);
47303
- const title = browserTitle || mdResult.title || "Untitled";
47705
+ const cleanContent = stripBase64DataUris(result.content);
47304
47706
  return {
47305
- success: true,
47707
+ success: result.success,
47306
47708
  url: args.url,
47307
- title,
47308
- content: mdResult.markdown,
47309
- screenshot,
47310
- loadTime,
47311
- excerpt: mdResult.excerpt,
47312
- byline: mdResult.byline,
47313
- wasReadabilityUsed: mdResult.wasReadabilityUsed,
47314
- wasTruncated: mdResult.wasTruncated
47709
+ finalUrl: args.url,
47710
+ method: "native",
47711
+ title: result.title,
47712
+ content: cleanContent,
47713
+ qualityScore: result.qualityScore,
47714
+ durationMs: Date.now() - startTime,
47715
+ attemptedMethods,
47716
+ error: result.error
47315
47717
  };
47316
47718
  } catch (error) {
47317
- if (page) {
47318
- try {
47319
- await page.close();
47320
- } catch {
47321
- }
47322
- }
47323
- if (error.message === "Puppeteer not installed") {
47324
- return {
47325
- success: false,
47326
- url: args.url,
47327
- title: "",
47328
- content: "",
47329
- loadTime: 0,
47330
- error: "Puppeteer is not installed",
47331
- suggestion: "Install Puppeteer with: npm install puppeteer (note: downloads ~50MB Chrome binary)"
47332
- };
47333
- }
47334
47719
  return {
47335
47720
  success: false,
47336
47721
  url: args.url,
47722
+ method: "native",
47337
47723
  title: "",
47338
47724
  content: "",
47339
- loadTime: 0,
47725
+ durationMs: Date.now() - startTime,
47726
+ attemptedMethods,
47340
47727
  error: error.message
47341
47728
  };
47342
47729
  }
47343
47730
  }
47344
- };
47345
-
47346
- // src/tools/web/webSearch.ts
47347
- init_Logger();
47348
-
47349
- // src/tools/web/searchProviders/serper.ts
47350
- async function searchWithSerper(query, numResults, apiKey) {
47351
- const response = await fetch("https://google.serper.dev/search", {
47352
- method: "POST",
47353
- headers: {
47354
- "X-API-KEY": apiKey,
47355
- "Content-Type": "application/json"
47356
- },
47357
- body: JSON.stringify({
47358
- q: query,
47359
- num: numResults
47360
- })
47361
- });
47362
- if (!response.ok) {
47363
- throw new Error(`Serper API error: ${response.status} ${response.statusText}`);
47364
- }
47365
- const data = await response.json();
47366
- if (!data.organic || !Array.isArray(data.organic)) {
47367
- throw new Error("Invalid response from Serper API");
47368
- }
47369
- return data.organic.slice(0, numResults).map((result, index) => ({
47370
- title: result.title || "Untitled",
47371
- url: result.link || "",
47372
- snippet: result.snippet || "",
47373
- position: index + 1
47374
- }));
47375
- }
47376
-
47377
- // src/tools/web/searchProviders/brave.ts
47378
- async function searchWithBrave(query, numResults, apiKey) {
47379
- const url2 = `https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=${numResults}`;
47380
- const response = await fetch(url2, {
47381
- headers: {
47382
- "X-Subscription-Token": apiKey,
47383
- Accept: "application/json"
47384
- }
47385
- });
47386
- if (!response.ok) {
47387
- throw new Error(`Brave API error: ${response.status} ${response.statusText}`);
47388
- }
47389
- const data = await response.json();
47390
- if (!data.web?.results || !Array.isArray(data.web.results)) {
47391
- throw new Error("Invalid response from Brave API");
47392
- }
47393
- return data.web.results.slice(0, numResults).map((result, index) => ({
47394
- title: result.title || "Untitled",
47395
- url: result.url || "",
47396
- snippet: result.description || "",
47397
- position: index + 1
47398
- }));
47399
- }
47400
-
47401
- // src/tools/web/searchProviders/tavily.ts
47402
- async function searchWithTavily(query, numResults, apiKey) {
47403
- const response = await fetch("https://api.tavily.com/search", {
47404
- method: "POST",
47405
- headers: {
47406
- "Content-Type": "application/json"
47407
- },
47408
- body: JSON.stringify({
47409
- api_key: apiKey,
47410
- query,
47411
- max_results: numResults,
47412
- search_depth: "basic",
47413
- // 'basic' or 'advanced'
47414
- include_answer: false,
47415
- include_raw_content: false
47416
- })
47417
- });
47418
- if (!response.ok) {
47419
- throw new Error(`Tavily API error: ${response.status} ${response.statusText}`);
47420
- }
47421
- const data = await response.json();
47422
- if (!data.results || !Array.isArray(data.results)) {
47423
- throw new Error("Invalid response from Tavily API");
47424
- }
47425
- return data.results.slice(0, numResults).map((result, index) => ({
47426
- title: result.title || "Untitled",
47427
- url: result.url || "",
47428
- snippet: result.content || "",
47429
- position: index + 1
47430
- }));
47431
- }
47432
-
47433
- // src/tools/web/webSearch.ts
47434
- var searchLogger = logger.child({ component: "webSearch" });
47435
- var SEARCH_SERVICE_TYPES = ["serper", "brave-search", "tavily", "rapidapi-search"];
47436
- var webSearch = {
47437
- definition: {
47438
- type: "function",
47439
- function: {
47440
- name: "web_search",
47441
- description: `Search the web and get relevant results with snippets.
47442
-
47443
- RETURNS:
47444
- An array of search results, each containing:
47445
- - title: Page title
47446
- - url: Direct URL to the page
47447
- - snippet: Short description/excerpt from the page
47448
- - position: Search ranking position (1, 2, 3...)
47449
-
47450
- USE CASES:
47451
- - Find current information on any topic
47452
- - Research multiple sources
47453
- - Discover relevant websites
47454
- - Find URLs to fetch with web_fetch tool
47455
-
47456
- WORKFLOW PATTERN:
47457
- 1. Use web_search to find relevant URLs
47458
- 2. Use web_fetch to get full content from top results
47459
- 3. Process and summarize the information
47460
-
47461
- EXAMPLE:
47462
- {
47463
- "query": "latest AI developments 2026",
47464
- "numResults": 5
47465
- }`,
47466
- parameters: {
47467
- type: "object",
47468
- properties: {
47469
- query: {
47470
- type: "string",
47471
- description: "The search query string. Be specific for better results."
47472
- },
47473
- numResults: {
47474
- type: "number",
47475
- description: "Number of results to return (default: 10, max: 100)."
47476
- },
47477
- country: {
47478
- type: "string",
47479
- description: 'Country/region code for localized results (e.g., "us", "gb", "de")'
47480
- },
47481
- language: {
47482
- type: "string",
47483
- description: 'Language code for results (e.g., "en", "fr", "de")'
47484
- }
47485
- },
47486
- required: ["query"]
47487
- }
47488
- },
47489
- blocking: true,
47490
- timeout: 15e3
47491
- },
47492
- execute: async (args) => {
47493
- const numResults = args.numResults || 10;
47494
- const connector = findConnectorByServiceTypes(SEARCH_SERVICE_TYPES);
47495
- if (connector) {
47496
- return await executeWithConnector(connector.name, args, numResults);
47497
- }
47498
- return await executeWithEnvVar(args, numResults);
47499
- },
47500
- describeCall: (args) => `"${args.query}"${args.numResults ? ` (${args.numResults} results)` : ""}`
47501
- };
47502
- async function executeWithConnector(connectorName, args, numResults) {
47503
- searchLogger.debug({ connectorName }, "Executing search with connector");
47504
- try {
47505
- const searchProvider = SearchProvider.create({ connector: connectorName });
47506
- const response = await searchProvider.search(args.query, {
47507
- numResults,
47508
- country: args.country,
47509
- language: args.language
47510
- });
47511
- if (response.success) {
47512
- searchLogger.debug({
47513
- provider: response.provider,
47514
- count: response.count
47515
- }, "Search completed successfully");
47516
- } else {
47517
- searchLogger.warn({
47518
- provider: response.provider,
47519
- error: response.error
47520
- }, "Search failed");
47521
- }
47522
- return {
47523
- success: response.success,
47524
- query: response.query,
47525
- provider: response.provider,
47526
- results: response.results,
47527
- count: response.count,
47528
- error: response.error
47529
- };
47530
- } catch (error) {
47531
- searchLogger.error({ error: error.message, connectorName }, "Search threw exception");
47532
- return {
47533
- success: false,
47534
- query: args.query,
47535
- provider: connectorName,
47536
- results: [],
47537
- count: 0,
47538
- error: error.message || "Unknown error"
47539
- };
47540
- }
47541
- }
47542
- async function executeWithEnvVar(args, numResults) {
47543
- const providers = [
47544
- { name: "serper", key: process.env.SERPER_API_KEY, fn: searchWithSerper },
47545
- { name: "brave", key: process.env.BRAVE_API_KEY, fn: searchWithBrave },
47546
- { name: "tavily", key: process.env.TAVILY_API_KEY, fn: searchWithTavily }
47547
- ];
47548
- for (const provider of providers) {
47549
- if (provider.key) {
47550
- searchLogger.debug({ provider: provider.name }, "Using environment variable fallback");
47551
- try {
47552
- const results = await provider.fn(args.query, numResults, provider.key);
47553
- return {
47554
- success: true,
47555
- query: args.query,
47556
- provider: provider.name,
47557
- results,
47558
- count: results.length
47559
- };
47560
- } catch (error) {
47561
- searchLogger.warn({ provider: provider.name, error: error.message }, "Provider failed, trying next");
47562
- }
47731
+ async function tryAPI(args, startTime, attemptedMethods) {
47732
+ attemptedMethods.push(`api:${connector.name}`);
47733
+ scrapeLogger.debug({ url: args.url, connectorName: connector.name }, "Trying external API");
47734
+ try {
47735
+ const provider = ScrapeProvider.create({ connector: connector.name });
47736
+ const options = {
47737
+ timeout: args.timeout,
47738
+ waitForSelector: args.waitForSelector,
47739
+ includeHtml: args.includeHtml,
47740
+ includeMarkdown: args.includeMarkdown,
47741
+ includeLinks: args.includeLinks
47742
+ };
47743
+ const result = await provider.scrape(args.url, options);
47744
+ const rawContent = result.result?.content || "";
47745
+ const rawMarkdown = result.result?.markdown;
47746
+ const cleanContent = stripBase64DataUris(rawContent);
47747
+ const cleanMarkdown = rawMarkdown ? stripBase64DataUris(rawMarkdown) : void 0;
47748
+ const isDuplicate = !!cleanMarkdown && cleanContent === cleanMarkdown;
47749
+ return {
47750
+ success: result.success,
47751
+ url: args.url,
47752
+ finalUrl: result.finalUrl,
47753
+ method: result.provider,
47754
+ title: result.result?.title || "",
47755
+ content: cleanContent,
47756
+ html: result.result?.html,
47757
+ markdown: isDuplicate ? void 0 : cleanMarkdown,
47758
+ metadata: result.result?.metadata,
47759
+ links: result.result?.links,
47760
+ qualityScore: result.success ? 90 : 0,
47761
+ durationMs: Date.now() - startTime,
47762
+ attemptedMethods,
47763
+ error: result.error
47764
+ };
47765
+ } catch (error) {
47766
+ return {
47767
+ success: false,
47768
+ url: args.url,
47769
+ method: "api",
47770
+ title: "",
47771
+ content: "",
47772
+ durationMs: Date.now() - startTime,
47773
+ attemptedMethods,
47774
+ error: error.message
47775
+ };
47563
47776
  }
47564
47777
  }
47565
47778
  return {
47566
- success: false,
47567
- query: args.query,
47568
- provider: "none",
47569
- results: [],
47570
- count: 0,
47571
- error: "No search provider configured. Set up a search connector (serper, brave-search, tavily) or set SERPER_API_KEY, BRAVE_API_KEY, or TAVILY_API_KEY environment variable."
47572
- };
47573
- }
47574
-
47575
- // src/tools/web/webScrape.ts
47576
- init_Logger();
47577
- var scrapeLogger = logger.child({ component: "webScrape" });
47578
- function stripBase64DataUris(content) {
47579
- if (!content) return content;
47580
- let cleaned = content.replace(/!\[[^\]]*\]\(data:[^)]+\)/g, "[image removed]");
47581
- cleaned = cleaned.replace(/url\(['"]?data:[^)]+['"]?\)/gi, "url([data-uri-removed])");
47582
- cleaned = cleaned.replace(/data:(?:image|font|application)\/[^;]+;base64,[A-Za-z0-9+/=]{100,}/g, "[base64-data-removed]");
47583
- return cleaned;
47584
- }
47585
- var SCRAPE_SERVICE_TYPES = ["zenrows", "jina-reader", "firecrawl", "scrapingbee"];
47586
- var DEFAULT_MIN_QUALITY = 50;
47587
- var webScrape = {
47588
- definition: {
47589
- type: "function",
47590
- function: {
47591
- name: "web_scrape",
47592
- description: `Scrape any URL with automatic fallback - guaranteed to work on most sites.
47779
+ definition: {
47780
+ type: "function",
47781
+ function: {
47782
+ name: "web_scrape",
47783
+ description: `Scrape any URL with automatic fallback - guaranteed to work on most sites.
47593
47784
 
47594
47785
  Automatically tries multiple methods in sequence:
47595
47786
  1. Native fetch - Fast (~1s), works for blogs/docs/articles
47596
- 2. JS rendering - Handles React/Vue/Angular SPAs
47597
- 3. External API - Handles bot protection, CAPTCHAs (if configured)
47787
+ 2. External API - Handles bot protection, CAPTCHAs, SPAs (if configured)
47598
47788
 
47599
47789
  RETURNS:
47600
47790
  {
@@ -47629,46 +47819,64 @@ For JS-heavy sites:
47629
47819
  "url": "https://spa-app.com",
47630
47820
  "waitForSelector": ".main-content"
47631
47821
  }`,
47632
- parameters: {
47633
- type: "object",
47634
- properties: {
47635
- url: {
47636
- type: "string",
47637
- description: "URL to scrape. Must start with http:// or https://"
47638
- },
47639
- timeout: {
47640
- type: "number",
47641
- description: "Timeout in milliseconds (default: 30000)"
47642
- },
47643
- includeHtml: {
47644
- type: "boolean",
47645
- description: "Include raw HTML in response (default: false)"
47646
- },
47647
- includeMarkdown: {
47648
- type: "boolean",
47649
- description: "Include markdown conversion (default: false)"
47650
- },
47651
- includeLinks: {
47652
- type: "boolean",
47653
- description: "Extract and include links (default: false)"
47822
+ parameters: {
47823
+ type: "object",
47824
+ properties: {
47825
+ url: {
47826
+ type: "string",
47827
+ description: "URL to scrape. Must start with http:// or https://"
47828
+ },
47829
+ timeout: {
47830
+ type: "number",
47831
+ description: "Timeout in milliseconds (default: 30000)"
47832
+ },
47833
+ includeHtml: {
47834
+ type: "boolean",
47835
+ description: "Include raw HTML in response (default: false)"
47836
+ },
47837
+ includeMarkdown: {
47838
+ type: "boolean",
47839
+ description: "Include markdown conversion (default: false)"
47840
+ },
47841
+ includeLinks: {
47842
+ type: "boolean",
47843
+ description: "Extract and include links (default: false)"
47844
+ },
47845
+ waitForSelector: {
47846
+ type: "string",
47847
+ description: "CSS selector to wait for before scraping (for JS-heavy sites)"
47848
+ }
47654
47849
  },
47655
- waitForSelector: {
47656
- type: "string",
47657
- description: "CSS selector to wait for before scraping (for JS-heavy sites)"
47658
- }
47659
- },
47660
- required: ["url"]
47661
- }
47850
+ required: ["url"]
47851
+ }
47852
+ },
47853
+ blocking: true,
47854
+ timeout: 6e4
47662
47855
  },
47663
- blocking: true,
47664
- timeout: 6e4
47665
- },
47666
- execute: async (args) => {
47667
- const startTime = Date.now();
47668
- const attemptedMethods = [];
47669
- try {
47670
- new URL(args.url);
47671
- } catch {
47856
+ execute: async (args) => {
47857
+ const startTime = Date.now();
47858
+ const attemptedMethods = [];
47859
+ try {
47860
+ new URL(args.url);
47861
+ } catch {
47862
+ return {
47863
+ success: false,
47864
+ url: args.url,
47865
+ method: "none",
47866
+ title: "",
47867
+ content: "",
47868
+ durationMs: Date.now() - startTime,
47869
+ attemptedMethods: [],
47870
+ error: "Invalid URL format"
47871
+ };
47872
+ }
47873
+ const native = await tryNative(args, startTime, attemptedMethods);
47874
+ if (native.success && (native.qualityScore ?? 0) >= DEFAULT_MIN_QUALITY) {
47875
+ return native;
47876
+ }
47877
+ const api = await tryAPI(args, startTime, attemptedMethods);
47878
+ if (api.success) return api;
47879
+ if (native.success) return native;
47672
47880
  return {
47673
47881
  success: false,
47674
47882
  url: args.url,
@@ -47676,230 +47884,119 @@ For JS-heavy sites:
47676
47884
  title: "",
47677
47885
  content: "",
47678
47886
  durationMs: Date.now() - startTime,
47679
- attemptedMethods: [],
47680
- error: "Invalid URL format"
47887
+ attemptedMethods,
47888
+ error: "All scraping methods failed. Site may have bot protection."
47681
47889
  };
47682
- }
47683
- const native = await tryNative(args, startTime, attemptedMethods);
47684
- if (native.success && (native.qualityScore ?? 0) >= DEFAULT_MIN_QUALITY) {
47685
- return native;
47686
- }
47687
- const js = await tryJS(args, startTime, attemptedMethods);
47688
- if (js.success && (js.qualityScore ?? 0) >= DEFAULT_MIN_QUALITY) {
47689
- return js;
47690
- }
47691
- const connector = findConnectorByServiceTypes(SCRAPE_SERVICE_TYPES);
47692
- if (connector) {
47693
- const api = await tryAPI(connector.name, args, startTime, attemptedMethods);
47694
- if (api.success) return api;
47695
- }
47696
- if (js.success) return js;
47697
- if (native.success) return native;
47698
- return {
47699
- success: false,
47700
- url: args.url,
47701
- method: "none",
47702
- title: "",
47703
- content: "",
47704
- durationMs: Date.now() - startTime,
47705
- attemptedMethods,
47706
- error: "All scraping methods failed. Site may have bot protection."
47707
- };
47708
- },
47709
- describeCall: (args) => args.url
47710
- };
47711
- async function tryNative(args, startTime, attemptedMethods) {
47712
- attemptedMethods.push("native");
47713
- scrapeLogger.debug({ url: args.url }, "Trying native fetch");
47714
- try {
47715
- const result = await webFetch.execute({
47716
- url: args.url,
47717
- timeout: args.timeout || 1e4
47718
- });
47719
- const cleanContent = stripBase64DataUris(result.content);
47720
- return {
47721
- success: result.success,
47722
- url: args.url,
47723
- finalUrl: args.url,
47724
- method: "native",
47725
- title: result.title,
47726
- content: cleanContent,
47727
- // Note: raw HTML not available with native method (returns markdown instead)
47728
- markdown: args.includeMarkdown ? cleanContent : void 0,
47729
- qualityScore: result.qualityScore,
47730
- durationMs: Date.now() - startTime,
47731
- attemptedMethods,
47732
- error: result.error
47733
- };
47734
- } catch (error) {
47735
- return {
47736
- success: false,
47737
- url: args.url,
47738
- method: "native",
47739
- title: "",
47740
- content: "",
47741
- durationMs: Date.now() - startTime,
47742
- attemptedMethods,
47743
- error: error.message
47744
- };
47745
- }
47890
+ },
47891
+ describeCall: (args) => args.url
47892
+ };
47746
47893
  }
47747
- async function tryJS(args, startTime, attemptedMethods) {
47748
- attemptedMethods.push("js");
47749
- scrapeLogger.debug({ url: args.url }, "Trying JS rendering");
47750
- try {
47751
- const result = await webFetchJS.execute({
47752
- url: args.url,
47753
- timeout: args.timeout || 15e3,
47754
- waitForSelector: args.waitForSelector
47755
- });
47756
- const cleanContent = stripBase64DataUris(result.content);
47757
- return {
47758
- success: result.success,
47759
- url: args.url,
47760
- finalUrl: args.url,
47761
- method: "js",
47762
- title: result.title,
47763
- content: cleanContent,
47764
- // Note: raw HTML not available with JS method (returns markdown instead)
47765
- markdown: args.includeMarkdown ? cleanContent : void 0,
47766
- qualityScore: result.success ? 80 : 0,
47767
- durationMs: Date.now() - startTime,
47768
- attemptedMethods,
47769
- error: result.error
47770
- };
47771
- } catch (error) {
47772
- return {
47773
- success: false,
47774
- url: args.url,
47775
- method: "js",
47776
- title: "",
47777
- content: "",
47778
- durationMs: Date.now() - startTime,
47779
- attemptedMethods,
47780
- error: error.message
47781
- };
47894
+
47895
+ // src/tools/web/register.ts
47896
+ var SEARCH_SERVICE_TYPES = ["serper", "brave-search", "tavily", "rapidapi-search"];
47897
+ var SCRAPE_SERVICE_TYPES = ["zenrows", "jina-reader", "firecrawl", "scrapingbee"];
47898
+ function registerWebTools() {
47899
+ for (const st of SEARCH_SERVICE_TYPES) {
47900
+ ConnectorTools.registerService(st, (connector) => [
47901
+ createWebSearchTool(connector)
47902
+ ]);
47782
47903
  }
47783
- }
47784
- async function tryAPI(connectorName, args, startTime, attemptedMethods) {
47785
- attemptedMethods.push(`api:${connectorName}`);
47786
- scrapeLogger.debug({ url: args.url, connectorName }, "Trying external API");
47787
- try {
47788
- const provider = ScrapeProvider.create({ connector: connectorName });
47789
- const options = {
47790
- timeout: args.timeout,
47791
- waitForSelector: args.waitForSelector,
47792
- includeHtml: args.includeHtml,
47793
- includeMarkdown: args.includeMarkdown,
47794
- includeLinks: args.includeLinks
47795
- };
47796
- const result = await provider.scrape(args.url, options);
47797
- const cleanContent = stripBase64DataUris(result.result?.content || "");
47798
- const cleanMarkdown = result.result?.markdown ? stripBase64DataUris(result.result.markdown) : void 0;
47799
- return {
47800
- success: result.success,
47801
- url: args.url,
47802
- finalUrl: result.finalUrl,
47803
- method: result.provider,
47804
- title: result.result?.title || "",
47805
- content: cleanContent,
47806
- html: result.result?.html,
47807
- // Keep raw HTML as-is (only used if explicitly requested)
47808
- markdown: cleanMarkdown,
47809
- metadata: result.result?.metadata,
47810
- links: result.result?.links,
47811
- qualityScore: result.success ? 90 : 0,
47812
- durationMs: Date.now() - startTime,
47813
- attemptedMethods,
47814
- error: result.error
47815
- };
47816
- } catch (error) {
47817
- return {
47818
- success: false,
47819
- url: args.url,
47820
- method: "api",
47821
- title: "",
47822
- content: "",
47823
- durationMs: Date.now() - startTime,
47824
- attemptedMethods,
47825
- error: error.message
47826
- };
47904
+ for (const st of SCRAPE_SERVICE_TYPES) {
47905
+ ConnectorTools.registerService(st, (connector) => [
47906
+ createWebScrapeTool(connector)
47907
+ ]);
47827
47908
  }
47828
47909
  }
47829
47910
 
47911
+ // src/tools/web/index.ts
47912
+ registerWebTools();
47913
+
47830
47914
  // src/tools/code/executeJavaScript.ts
47831
47915
  init_Connector();
47832
- function generateDescription() {
47833
- const connectors = Connector.listAll();
47834
- const connectorList = connectors.length > 0 ? connectors.map((c) => {
47835
- const authType = c.config.auth?.type || "none";
47836
- return ` \u2022 "${c.name}": ${c.displayName}
47837
- ${c.config.description || "No description"}
47838
- Base URL: ${c.baseURL}
47839
- Auth: ${authType}`;
47840
- }).join("\n\n") : " No connectors registered.";
47841
- return `Execute JavaScript code in a secure sandbox with authenticated API access.
47842
-
47843
- AVAILABLE APIS:
47844
-
47845
- 1. authenticatedFetch(url, options, connectorName, userId?)
47846
- Makes authenticated API calls using the connector's configured auth scheme.
47847
- Auth headers are added automatically - DO NOT set Authorization header.
47916
+ var DEFAULT_TIMEOUT = 1e4;
47917
+ var DEFAULT_MAX_TIMEOUT = 3e4;
47918
+ function formatConnectorEntry(c) {
47919
+ const parts = [];
47920
+ const serviceOrVendor = c.serviceType ?? c.vendor ?? void 0;
47921
+ if (serviceOrVendor) parts.push(`Service: ${serviceOrVendor}`);
47922
+ if (c.config.description) parts.push(c.config.description);
47923
+ if (c.baseURL) parts.push(`URL: ${c.baseURL}`);
47924
+ const details = parts.map((p) => ` ${p}`).join("\n");
47925
+ return ` \u2022 "${c.name}" (${c.displayName})
47926
+ ${details}`;
47927
+ }
47928
+ function generateDescription(context, maxTimeout) {
47929
+ const registry = context?.connectorRegistry ?? Connector.asRegistry();
47930
+ const connectors = registry.listAll();
47931
+ const connectorList = connectors.length > 0 ? connectors.map(formatConnectorEntry).join("\n\n") : " No connectors registered.";
47932
+ const timeoutSec = Math.round(maxTimeout / 1e3);
47933
+ return `Execute JavaScript code in a secure sandbox with authenticated API access to external services.
47934
+
47935
+ Use this tool when you need to:
47936
+ - Call external APIs (GitHub, Slack, Stripe, etc.) using registered connectors
47937
+ - Process, transform, or compute data that requires programmatic logic
47938
+ - Chain multiple API calls or perform complex data manipulation
47939
+ - Do anything that plain text generation cannot accomplish
47940
+
47941
+ SANDBOX API:
47942
+
47943
+ 1. authenticatedFetch(url, options, connectorName)
47944
+ Makes authenticated HTTP requests using the connector's credentials.
47945
+ The current user's identity (userId) is automatically included \u2014 no need to pass it.
47946
+ Auth headers are added automatically \u2014 DO NOT set Authorization header manually.
47848
47947
 
47849
47948
  Parameters:
47850
- \u2022 url: Full URL or relative path (uses connector's baseURL)
47949
+ \u2022 url: Full URL or path relative to the connector's base URL
47851
47950
  - Full: "https://api.github.com/user/repos"
47852
- - Relative: "/user/repos" (appended to connector's baseURL)
47853
- \u2022 options: Standard fetch options { method, body, headers }
47854
- \u2022 connectorName: One of the registered connectors below
47855
- \u2022 userId: (optional) For multi-tenant apps with per-user tokens
47951
+ - Relative: "/user/repos" (resolved against connector's base URL)
47952
+ \u2022 options: Standard fetch options { method, headers, body }
47953
+ - For POST/PUT: set body to JSON.stringify(data) and headers to { 'Content-Type': 'application/json' }
47954
+ \u2022 connectorName: Name of a registered connector (see list below)
47856
47955
 
47857
47956
  Returns: Promise<Response>
47858
- \u2022 response.ok - true if status 200-299
47859
- \u2022 response.status - HTTP status code
47860
- \u2022 response.json() - parse JSON body
47861
- \u2022 response.text() - get text body
47957
+ \u2022 response.ok \u2014 true if status 200-299
47958
+ \u2022 response.status \u2014 HTTP status code
47959
+ \u2022 await response.json() \u2014 parse JSON body
47960
+ \u2022 await response.text() \u2014 get text body
47862
47961
 
47863
- Auth Schemes (handled automatically per connector):
47864
- \u2022 Bearer tokens (GitHub, Slack, Stripe)
47865
- \u2022 Bot tokens (Discord)
47866
- \u2022 Basic auth (Twilio, Zendesk)
47867
- \u2022 Custom headers (Shopify uses X-Shopify-Access-Token)
47962
+ 2. fetch(url, options) \u2014 Standard fetch without authentication
47868
47963
 
47869
- 2. connectors.list() - List available connector names
47870
- 3. connectors.get(name) - Get connector info { displayName, description, baseURL }
47871
- 4. fetch(url, options) - Standard fetch (no auth)
47964
+ 3. connectors.list() \u2014 Array of available connector names
47965
+ 4. connectors.get(name) \u2014 Connector info: { displayName, description, baseURL, serviceType }
47872
47966
 
47873
- INPUT/OUTPUT:
47874
- \u2022 input - data passed to your code via the "input" parameter
47875
- \u2022 output - SET THIS variable to return your result
47967
+ VARIABLES:
47968
+ \u2022 input \u2014 data passed via the "input" parameter (default: {})
47969
+ \u2022 output \u2014 SET THIS to return your result to the caller
47876
47970
 
47877
- UTILITIES: console.log/error/warn, Buffer, JSON, Math, Date, Promise
47971
+ GLOBALS: console.log/error/warn, JSON, Math, Date, Buffer, Promise, Array, Object, String, Number, Boolean, setTimeout, setInterval, URL, URLSearchParams, RegExp, Map, Set, Error, TextEncoder, TextDecoder
47878
47972
 
47879
47973
  REGISTERED CONNECTORS:
47880
47974
  ${connectorList}
47881
47975
 
47882
- EXAMPLE:
47883
- (async () => {
47884
- const response = await authenticatedFetch(
47885
- '/user/repos',
47886
- { method: 'GET' },
47887
- 'github'
47888
- );
47976
+ EXAMPLES:
47889
47977
 
47890
- if (!response.ok) {
47891
- throw new Error(\`API error: \${response.status}\`);
47892
- }
47978
+ // GET request
47979
+ const resp = await authenticatedFetch('/user/repos', { method: 'GET' }, 'github');
47980
+ const repos = await resp.json();
47981
+ output = repos.map(r => r.full_name);
47893
47982
 
47894
- const repos = await response.json();
47895
- console.log(\`Found \${repos.length} repositories\`);
47983
+ // POST request with JSON body
47984
+ const resp = await authenticatedFetch('/chat.postMessage', {
47985
+ method: 'POST',
47986
+ headers: { 'Content-Type': 'application/json' },
47987
+ body: JSON.stringify({ channel: '#general', text: 'Hello!' })
47988
+ }, 'slack');
47989
+ output = await resp.json();
47896
47990
 
47897
- output = repos;
47898
- })();
47991
+ // Data processing (no API needed)
47992
+ const items = input.data;
47993
+ output = items.filter(i => i.score > 0.8).sort((a, b) => b.score - a.score);
47899
47994
 
47900
- SECURITY: 10s timeout, no file system, no require/import.`;
47995
+ LIMITS: ${timeoutSec}s max timeout, no file system access, no require/import.`;
47901
47996
  }
47902
- function createExecuteJavaScriptTool() {
47997
+ function createExecuteJavaScriptTool(options) {
47998
+ const maxTimeout = options?.maxTimeout ?? DEFAULT_MAX_TIMEOUT;
47999
+ const defaultTimeout = options?.defaultTimeout ?? DEFAULT_TIMEOUT;
47903
48000
  return {
47904
48001
  definition: {
47905
48002
  type: "function",
@@ -47912,32 +48009,40 @@ function createExecuteJavaScriptTool() {
47912
48009
  properties: {
47913
48010
  code: {
47914
48011
  type: "string",
47915
- description: 'JavaScript code to execute. MUST set the "output" variable. Wrap in async IIFE for async operations.'
48012
+ description: 'JavaScript code to execute. Set the "output" variable with your result. Code is auto-wrapped in async IIFE \u2014 you can use await directly. For explicit async control, wrap in (async () => { ... })().'
47916
48013
  },
47917
48014
  input: {
47918
- description: 'Optional input data available as "input" variable in your code'
48015
+ description: 'Optional data available as the "input" variable in your code. Can be any JSON value.'
47919
48016
  },
47920
48017
  timeout: {
47921
48018
  type: "number",
47922
- description: "Execution timeout in milliseconds (default: 10000, max: 30000)"
48019
+ description: `Execution timeout in milliseconds. Default: ${defaultTimeout}ms, max: ${maxTimeout}ms. Increase for slow API calls or multiple sequential requests.`
47923
48020
  }
47924
48021
  },
47925
48022
  required: ["code"]
47926
48023
  }
47927
48024
  },
47928
48025
  blocking: true,
47929
- timeout: 35e3
47930
- // Tool timeout (slightly more than max code timeout)
48026
+ timeout: maxTimeout + 5e3
48027
+ // Tool-level timeout slightly above max code timeout
47931
48028
  },
47932
- // Dynamic description - evaluated each time tool definitions are sent to LLM
47933
- // This ensures the connector list is always current
47934
- descriptionFactory: generateDescription,
47935
- execute: async (args) => {
48029
+ // Dynamic description regenerated each time tool definitions are sent to LLM.
48030
+ // Receives ToolContext so connector list is scoped to current userId.
48031
+ descriptionFactory: (context) => generateDescription(context, maxTimeout),
48032
+ execute: async (args, context) => {
47936
48033
  const logs = [];
47937
48034
  const startTime = Date.now();
47938
48035
  try {
47939
- const timeout = Math.min(args.timeout || 1e4, 3e4);
47940
- const result = await executeInVM(args.code, args.input, timeout, logs);
48036
+ const timeout = Math.min(Math.max(args.timeout || defaultTimeout, 0), maxTimeout);
48037
+ const registry = context?.connectorRegistry ?? Connector.asRegistry();
48038
+ const result = await executeInVM(
48039
+ args.code,
48040
+ args.input,
48041
+ timeout,
48042
+ logs,
48043
+ context?.userId,
48044
+ registry
48045
+ );
47941
48046
  return {
47942
48047
  success: true,
47943
48048
  result,
@@ -47957,31 +48062,36 @@ function createExecuteJavaScriptTool() {
47957
48062
  };
47958
48063
  }
47959
48064
  var executeJavaScript = createExecuteJavaScriptTool();
47960
- async function executeInVM(code, input, timeout, logs) {
48065
+ async function executeInVM(code, input, timeout, logs, userId, registry) {
47961
48066
  const sandbox = {
47962
48067
  // Input/output
47963
- input: input || {},
48068
+ input: input ?? {},
47964
48069
  output: null,
47965
- // Console (captured)
48070
+ // Console (captured) — stringify objects for readable logs
47966
48071
  console: {
47967
- log: (...args) => logs.push(args.map((a) => String(a)).join(" ")),
47968
- error: (...args) => logs.push("ERROR: " + args.map((a) => String(a)).join(" ")),
47969
- warn: (...args) => logs.push("WARN: " + args.map((a) => String(a)).join(" "))
48072
+ log: (...args) => logs.push(args.map((a) => typeof a === "object" ? JSON.stringify(a) : String(a)).join(" ")),
48073
+ error: (...args) => logs.push("ERROR: " + args.map((a) => typeof a === "object" ? JSON.stringify(a) : String(a)).join(" ")),
48074
+ warn: (...args) => logs.push("WARN: " + args.map((a) => typeof a === "object" ? JSON.stringify(a) : String(a)).join(" "))
48075
+ },
48076
+ // Authenticated fetch — userId auto-injected from ToolContext.
48077
+ // Only connectors visible in the scoped registry are accessible.
48078
+ authenticatedFetch: (url2, options, connectorName) => {
48079
+ registry.get(connectorName);
48080
+ return authenticatedFetch(url2, options, connectorName, userId);
47970
48081
  },
47971
- // Authenticated fetch
47972
- authenticatedFetch,
47973
- // Standard fetch
48082
+ // Standard fetch (no auth)
47974
48083
  fetch: globalThis.fetch,
47975
- // Connector info
48084
+ // Connector info (userId-scoped)
47976
48085
  connectors: {
47977
- list: () => Connector.list(),
48086
+ list: () => registry.list(),
47978
48087
  get: (name) => {
47979
48088
  try {
47980
- const connector = Connector.get(name);
48089
+ const connector = registry.get(name);
47981
48090
  return {
47982
48091
  displayName: connector.displayName,
47983
48092
  description: connector.config.description || "",
47984
- baseURL: connector.baseURL
48093
+ baseURL: connector.baseURL,
48094
+ serviceType: connector.serviceType
47985
48095
  };
47986
48096
  } catch {
47987
48097
  return null;
@@ -47998,14 +48108,22 @@ async function executeInVM(code, input, timeout, logs) {
47998
48108
  clearTimeout,
47999
48109
  clearInterval,
48000
48110
  Promise,
48001
- // Array/Object
48111
+ // Built-in types
48002
48112
  Array,
48003
48113
  Object,
48004
48114
  String,
48005
48115
  Number,
48006
- Boolean
48116
+ Boolean,
48117
+ RegExp,
48118
+ Map,
48119
+ Set,
48120
+ Error,
48121
+ URL,
48122
+ URLSearchParams,
48123
+ TextEncoder,
48124
+ TextDecoder
48007
48125
  };
48008
- const context = vm.createContext(sandbox);
48126
+ const vmContext = vm.createContext(sandbox);
48009
48127
  const wrappedCode = code.trim().startsWith("(async") ? code : `
48010
48128
  (async () => {
48011
48129
  ${code}
@@ -48013,75 +48131,32 @@ async function executeInVM(code, input, timeout, logs) {
48013
48131
  })()
48014
48132
  `;
48015
48133
  const script = new vm.Script(wrappedCode);
48016
- const resultPromise = script.runInContext(context, {
48134
+ const resultPromise = script.runInContext(vmContext, {
48017
48135
  timeout,
48018
48136
  displayErrors: true
48019
48137
  });
48020
48138
  const result = await resultPromise;
48021
48139
  return result !== void 0 ? result : sandbox.output;
48022
48140
  }
48023
- var MIME_TYPES = {
48024
- png: "image/png",
48025
- jpeg: "image/jpeg",
48026
- jpg: "image/jpeg",
48027
- webp: "image/webp",
48028
- gif: "image/gif",
48029
- mp4: "video/mp4",
48030
- webm: "video/webm",
48031
- mp3: "audio/mpeg",
48032
- wav: "audio/wav",
48033
- opus: "audio/opus",
48034
- ogg: "audio/ogg",
48035
- aac: "audio/aac",
48036
- flac: "audio/flac",
48037
- pcm: "audio/pcm"
48038
- };
48039
- var FileMediaOutputHandler = class {
48040
- outputDir;
48041
- initialized = false;
48042
- constructor(outputDir) {
48043
- this.outputDir = outputDir ?? path3.join(os.tmpdir(), "oneringai-media");
48044
- }
48045
- async save(data, metadata) {
48046
- if (!this.initialized) {
48047
- await fs14.mkdir(this.outputDir, { recursive: true });
48048
- this.initialized = true;
48049
- }
48050
- const filename = metadata.suggestedFilename ?? this.generateFilename(metadata);
48051
- const filePath = path3.join(this.outputDir, filename);
48052
- await fs14.writeFile(filePath, data);
48053
- const format = metadata.format.toLowerCase();
48054
- const mimeType = MIME_TYPES[format] ?? `application/octet-stream`;
48055
- return {
48056
- location: filePath,
48057
- mimeType,
48058
- size: data.length
48059
- };
48060
- }
48061
- generateFilename(metadata) {
48062
- const timestamp = Date.now();
48063
- const random2 = crypto2.randomBytes(4).toString("hex");
48064
- const indexSuffix = metadata.index != null ? `_${metadata.index}` : "";
48065
- return `${metadata.type}_${timestamp}_${random2}${indexSuffix}.${metadata.format}`;
48066
- }
48067
- };
48068
48141
 
48069
48142
  // src/tools/multimedia/config.ts
48070
- var _outputHandler = null;
48071
- function getMediaOutputHandler() {
48072
- if (!_outputHandler) {
48073
- _outputHandler = new FileMediaOutputHandler();
48143
+ var _storage = null;
48144
+ function getMediaStorage() {
48145
+ if (!_storage) {
48146
+ _storage = new FileMediaStorage();
48074
48147
  }
48075
- return _outputHandler;
48148
+ return _storage;
48076
48149
  }
48077
- function setMediaOutputHandler(handler) {
48078
- _outputHandler = handler;
48150
+ function setMediaStorage(storage) {
48151
+ _storage = storage;
48079
48152
  }
48153
+ var getMediaOutputHandler = getMediaStorage;
48154
+ var setMediaOutputHandler = setMediaStorage;
48080
48155
 
48081
48156
  // src/tools/multimedia/imageGeneration.ts
48082
- function createImageGenerationTool(connector, outputHandler) {
48157
+ function createImageGenerationTool(connector, storage, userId) {
48083
48158
  const vendor = connector.vendor;
48084
- const handler = outputHandler ?? getMediaOutputHandler();
48159
+ const handler = storage ?? getMediaStorage();
48085
48160
  const vendorModels = vendor ? getImageModelsByVendor(vendor) : [];
48086
48161
  const modelNames = vendorModels.map((m) => m.name);
48087
48162
  const properties = {
@@ -48154,8 +48229,9 @@ function createImageGenerationTool(connector, outputHandler) {
48154
48229
  }
48155
48230
  }
48156
48231
  },
48157
- execute: async (args) => {
48232
+ execute: async (args, context) => {
48158
48233
  try {
48234
+ const effectiveUserId = userId ?? context?.userId;
48159
48235
  const imageGen = ImageGeneration.create({ connector });
48160
48236
  const response = await imageGen.generate({
48161
48237
  prompt: args.prompt,
@@ -48186,7 +48262,8 @@ function createImageGenerationTool(connector, outputHandler) {
48186
48262
  format,
48187
48263
  model: modelName,
48188
48264
  vendor: vendor || "unknown",
48189
- index: response.data.length > 1 ? i : void 0
48265
+ index: response.data.length > 1 ? i : void 0,
48266
+ userId: effectiveUserId
48190
48267
  });
48191
48268
  images.push({
48192
48269
  location: result.location,
@@ -48213,9 +48290,9 @@ function createImageGenerationTool(connector, outputHandler) {
48213
48290
 
48214
48291
  // src/tools/multimedia/videoGeneration.ts
48215
48292
  var videoGenInstances = /* @__PURE__ */ new Map();
48216
- function createVideoTools(connector, outputHandler) {
48293
+ function createVideoTools(connector, storage, userId) {
48217
48294
  const vendor = connector.vendor;
48218
- const handler = outputHandler ?? getMediaOutputHandler();
48295
+ const handler = storage ?? getMediaStorage();
48219
48296
  const vendorModels = vendor ? getVideoModelsByVendor(vendor) : [];
48220
48297
  const modelNames = vendorModels.map((m) => m.name);
48221
48298
  const generateProperties = {
@@ -48279,7 +48356,7 @@ function createVideoTools(connector, outputHandler) {
48279
48356
  }
48280
48357
  }
48281
48358
  },
48282
- execute: async (args) => {
48359
+ execute: async (args, _context) => {
48283
48360
  try {
48284
48361
  const videoGen = VideoGeneration.create({ connector });
48285
48362
  const response = await videoGen.generate({
@@ -48328,8 +48405,9 @@ function createVideoTools(connector, outputHandler) {
48328
48405
  }
48329
48406
  }
48330
48407
  },
48331
- execute: async (args) => {
48408
+ execute: async (args, context) => {
48332
48409
  try {
48410
+ const effectiveUserId = userId ?? context?.userId;
48333
48411
  let videoGen = videoGenInstances.get(args.jobId);
48334
48412
  if (!videoGen) {
48335
48413
  videoGen = VideoGeneration.create({ connector });
@@ -48355,7 +48433,8 @@ function createVideoTools(connector, outputHandler) {
48355
48433
  type: "video",
48356
48434
  format,
48357
48435
  model: modelName,
48358
- vendor: vendor || "unknown"
48436
+ vendor: vendor || "unknown",
48437
+ userId: effectiveUserId
48359
48438
  });
48360
48439
  videoGenInstances.delete(args.jobId);
48361
48440
  return {
@@ -48403,9 +48482,9 @@ function createVideoTools(connector, outputHandler) {
48403
48482
  }
48404
48483
 
48405
48484
  // src/tools/multimedia/textToSpeech.ts
48406
- function createTextToSpeechTool(connector, outputHandler) {
48485
+ function createTextToSpeechTool(connector, storage, userId) {
48407
48486
  const vendor = connector.vendor;
48408
- const handler = outputHandler ?? getMediaOutputHandler();
48487
+ const handler = storage ?? getMediaStorage();
48409
48488
  const vendorModels = vendor ? getTTSModelsByVendor(vendor) : [];
48410
48489
  const modelNames = vendorModels.map((m) => m.name);
48411
48490
  const properties = {
@@ -48466,8 +48545,9 @@ function createTextToSpeechTool(connector, outputHandler) {
48466
48545
  }
48467
48546
  }
48468
48547
  },
48469
- execute: async (args) => {
48548
+ execute: async (args, context) => {
48470
48549
  try {
48550
+ const effectiveUserId = userId ?? context?.userId;
48471
48551
  const tts = TextToSpeech.create({
48472
48552
  connector,
48473
48553
  model: args.model,
@@ -48481,7 +48561,8 @@ function createTextToSpeechTool(connector, outputHandler) {
48481
48561
  type: "audio",
48482
48562
  format,
48483
48563
  model: args.model || modelNames[0] || "unknown",
48484
- vendor: vendor || "unknown"
48564
+ vendor: vendor || "unknown",
48565
+ userId: effectiveUserId
48485
48566
  });
48486
48567
  return {
48487
48568
  success: true,
@@ -48504,14 +48585,17 @@ function createTextToSpeechTool(connector, outputHandler) {
48504
48585
  }
48505
48586
  };
48506
48587
  }
48507
- function createSpeechToTextTool(connector) {
48588
+
48589
+ // src/tools/multimedia/speechToText.ts
48590
+ function createSpeechToTextTool(connector, storage) {
48508
48591
  const vendor = connector.vendor;
48592
+ const handler = storage ?? getMediaStorage();
48509
48593
  const vendorModels = vendor ? getSTTModelsByVendor(vendor) : [];
48510
48594
  const modelNames = vendorModels.map((m) => m.name);
48511
48595
  const properties = {
48512
- audioFilePath: {
48596
+ audioSource: {
48513
48597
  type: "string",
48514
- description: "Path to the audio file to transcribe"
48598
+ description: "Path or location of the audio file to transcribe (file path, storage location, etc.)"
48515
48599
  }
48516
48600
  };
48517
48601
  if (modelNames.length > 0) {
@@ -48539,13 +48623,19 @@ function createSpeechToTextTool(connector) {
48539
48623
  parameters: {
48540
48624
  type: "object",
48541
48625
  properties,
48542
- required: ["audioFilePath"]
48626
+ required: ["audioSource"]
48543
48627
  }
48544
48628
  }
48545
48629
  },
48546
- execute: async (args) => {
48630
+ execute: async (args, _context) => {
48547
48631
  try {
48548
- const audioBuffer = await fs14.readFile(args.audioFilePath);
48632
+ const audioBuffer = await handler.read(args.audioSource);
48633
+ if (!audioBuffer) {
48634
+ return {
48635
+ success: false,
48636
+ error: `Audio not found at: ${args.audioSource}`
48637
+ };
48638
+ }
48549
48639
  const stt = SpeechToText.create({
48550
48640
  connector,
48551
48641
  model: args.model,
@@ -48567,7 +48657,7 @@ function createSpeechToTextTool(connector) {
48567
48657
  };
48568
48658
  }
48569
48659
  },
48570
- describeCall: (args) => args.audioFilePath,
48660
+ describeCall: (args) => args.audioSource,
48571
48661
  permission: {
48572
48662
  scope: "session",
48573
48663
  riskLevel: "low",
@@ -48582,21 +48672,22 @@ var VENDOR_CAPABILITIES = {
48582
48672
  [Vendor.Google]: ["image", "video", "tts"],
48583
48673
  [Vendor.Grok]: ["image", "video"]
48584
48674
  };
48585
- function registerMultimediaTools() {
48675
+ function registerMultimediaTools(storage) {
48586
48676
  for (const [vendor, capabilities] of Object.entries(VENDOR_CAPABILITIES)) {
48587
- ConnectorTools.registerService(vendor, (connector, _userId) => {
48677
+ ConnectorTools.registerService(vendor, (connector, userId) => {
48678
+ const handler = getMediaStorage();
48588
48679
  const tools = [];
48589
48680
  if (capabilities.includes("image")) {
48590
- tools.push(createImageGenerationTool(connector));
48681
+ tools.push(createImageGenerationTool(connector, handler, userId));
48591
48682
  }
48592
48683
  if (capabilities.includes("video")) {
48593
- tools.push(...createVideoTools(connector));
48684
+ tools.push(...createVideoTools(connector, handler, userId));
48594
48685
  }
48595
48686
  if (capabilities.includes("tts")) {
48596
- tools.push(createTextToSpeechTool(connector));
48687
+ tools.push(createTextToSpeechTool(connector, handler, userId));
48597
48688
  }
48598
48689
  if (capabilities.includes("stt")) {
48599
- tools.push(createSpeechToTextTool(connector));
48690
+ tools.push(createSpeechToTextTool(connector, handler));
48600
48691
  }
48601
48692
  return tools;
48602
48693
  });
@@ -48606,6 +48697,858 @@ function registerMultimediaTools() {
48606
48697
  // src/tools/multimedia/index.ts
48607
48698
  registerMultimediaTools();
48608
48699
 
48700
+ // src/tools/github/types.ts
48701
+ function parseRepository(input) {
48702
+ if (!input || input.trim().length === 0) {
48703
+ throw new Error("Repository cannot be empty");
48704
+ }
48705
+ const trimmed = input.trim();
48706
+ try {
48707
+ const url2 = new URL(trimmed);
48708
+ if (url2.hostname === "github.com" || url2.hostname === "www.github.com") {
48709
+ const segments = url2.pathname.split("/").filter(Boolean);
48710
+ if (segments.length >= 2) {
48711
+ return { owner: segments[0], repo: segments[1].replace(/\.git$/, "") };
48712
+ }
48713
+ }
48714
+ } catch {
48715
+ }
48716
+ const parts = trimmed.split("/");
48717
+ if (parts.length === 2 && parts[0].length > 0 && parts[1].length > 0) {
48718
+ return { owner: parts[0], repo: parts[1] };
48719
+ }
48720
+ throw new Error(
48721
+ `Invalid repository format: "${input}". Expected "owner/repo" or "https://github.com/owner/repo"`
48722
+ );
48723
+ }
48724
+ function resolveRepository(repository, connector) {
48725
+ const repoStr = repository ?? connector.getOptions().defaultRepository;
48726
+ if (!repoStr) {
48727
+ return {
48728
+ success: false,
48729
+ error: 'No repository specified. Provide a "repository" parameter (e.g., "owner/repo") or configure defaultRepository on the connector.'
48730
+ };
48731
+ }
48732
+ try {
48733
+ return { success: true, repo: parseRepository(repoStr) };
48734
+ } catch (err) {
48735
+ return { success: false, error: err instanceof Error ? err.message : String(err) };
48736
+ }
48737
+ }
48738
+ var GitHubAPIError = class extends Error {
48739
+ constructor(status, statusText, body) {
48740
+ const msg = typeof body === "object" && body !== null && "message" in body ? body.message : statusText;
48741
+ super(`GitHub API error ${status}: ${msg}`);
48742
+ this.status = status;
48743
+ this.statusText = statusText;
48744
+ this.body = body;
48745
+ this.name = "GitHubAPIError";
48746
+ }
48747
+ };
48748
+ async function githubFetch(connector, endpoint, options) {
48749
+ let url2 = endpoint;
48750
+ if (options?.queryParams && Object.keys(options.queryParams).length > 0) {
48751
+ const params = new URLSearchParams();
48752
+ for (const [key, value] of Object.entries(options.queryParams)) {
48753
+ params.append(key, String(value));
48754
+ }
48755
+ url2 += (url2.includes("?") ? "&" : "?") + params.toString();
48756
+ }
48757
+ const headers = {
48758
+ "Accept": options?.accept ?? "application/vnd.github+json",
48759
+ "X-GitHub-Api-Version": "2022-11-28"
48760
+ };
48761
+ if (options?.body) {
48762
+ headers["Content-Type"] = "application/json";
48763
+ }
48764
+ const response = await connector.fetch(
48765
+ url2,
48766
+ {
48767
+ method: options?.method ?? "GET",
48768
+ headers,
48769
+ body: options?.body ? JSON.stringify(options.body) : void 0
48770
+ },
48771
+ options?.userId
48772
+ );
48773
+ const text = await response.text();
48774
+ let data;
48775
+ try {
48776
+ data = JSON.parse(text);
48777
+ } catch {
48778
+ data = text;
48779
+ }
48780
+ if (!response.ok) {
48781
+ throw new GitHubAPIError(response.status, response.statusText, data);
48782
+ }
48783
+ return data;
48784
+ }
48785
+
48786
+ // src/tools/github/searchFiles.ts
48787
+ function matchGlobPattern2(pattern, filePath) {
48788
+ let regexPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, ".").replace(/\{\{GLOBSTAR\}\}/g, ".*");
48789
+ regexPattern = "^" + regexPattern + "$";
48790
+ try {
48791
+ const regex = new RegExp(regexPattern);
48792
+ return regex.test(filePath);
48793
+ } catch {
48794
+ return false;
48795
+ }
48796
+ }
48797
+ function createSearchFilesTool(connector, userId) {
48798
+ return {
48799
+ definition: {
48800
+ type: "function",
48801
+ function: {
48802
+ name: "search_files",
48803
+ description: `Search for files by name/path pattern in a GitHub repository.
48804
+
48805
+ USAGE:
48806
+ - Supports glob patterns like "**/*.ts", "src/**/*.tsx"
48807
+ - Returns matching file paths sorted alphabetically
48808
+ - Uses the repository's file tree for fast matching
48809
+
48810
+ PATTERN SYNTAX:
48811
+ - * matches any characters except /
48812
+ - ** matches any characters including /
48813
+ - ? matches a single character
48814
+
48815
+ EXAMPLES:
48816
+ - Find all TypeScript files: { "pattern": "**/*.ts" }
48817
+ - Find files in src: { "pattern": "src/**/*.{ts,tsx}" }
48818
+ - Find package.json: { "pattern": "**/package.json" }
48819
+ - Search specific branch: { "pattern": "**/*.ts", "ref": "develop" }`,
48820
+ parameters: {
48821
+ type: "object",
48822
+ properties: {
48823
+ repository: {
48824
+ type: "string",
48825
+ description: 'Repository in "owner/repo" format or full GitHub URL. Optional if connector has a default repository.'
48826
+ },
48827
+ pattern: {
48828
+ type: "string",
48829
+ description: 'Glob pattern to match files (e.g., "**/*.ts", "src/**/*.tsx")'
48830
+ },
48831
+ ref: {
48832
+ type: "string",
48833
+ description: "Branch, tag, or commit SHA. Defaults to the repository's default branch."
48834
+ }
48835
+ },
48836
+ required: ["pattern"]
48837
+ }
48838
+ }
48839
+ },
48840
+ describeCall: (args) => {
48841
+ const parts = [args.pattern];
48842
+ if (args.repository) parts.push(`in ${args.repository}`);
48843
+ if (args.ref) parts.push(`@${args.ref}`);
48844
+ return parts.join(" ");
48845
+ },
48846
+ permission: {
48847
+ scope: "session",
48848
+ riskLevel: "low",
48849
+ approvalMessage: `Search files in a GitHub repository via ${connector.displayName}`
48850
+ },
48851
+ execute: async (args, context) => {
48852
+ const effectiveUserId = context?.userId ?? userId;
48853
+ const resolved = resolveRepository(args.repository, connector);
48854
+ if (!resolved.success) {
48855
+ return { success: false, error: resolved.error };
48856
+ }
48857
+ const { owner, repo } = resolved.repo;
48858
+ try {
48859
+ let ref = args.ref;
48860
+ if (!ref) {
48861
+ const repoInfo = await githubFetch(
48862
+ connector,
48863
+ `/repos/${owner}/${repo}`,
48864
+ { userId: effectiveUserId }
48865
+ );
48866
+ ref = repoInfo.default_branch;
48867
+ }
48868
+ const tree = await githubFetch(
48869
+ connector,
48870
+ `/repos/${owner}/${repo}/git/trees/${ref}?recursive=1`,
48871
+ { userId: effectiveUserId }
48872
+ );
48873
+ const matching = tree.tree.filter(
48874
+ (entry) => entry.type === "blob" && matchGlobPattern2(args.pattern, entry.path)
48875
+ ).map((entry) => ({
48876
+ path: entry.path,
48877
+ size: entry.size ?? 0,
48878
+ type: entry.type
48879
+ })).sort((a, b) => a.path.localeCompare(b.path));
48880
+ return {
48881
+ success: true,
48882
+ files: matching,
48883
+ count: matching.length,
48884
+ truncated: tree.truncated
48885
+ };
48886
+ } catch (error) {
48887
+ return {
48888
+ success: false,
48889
+ error: `Failed to search files: ${error instanceof Error ? error.message : String(error)}`
48890
+ };
48891
+ }
48892
+ }
48893
+ };
48894
+ }
48895
+
48896
+ // src/tools/github/searchCode.ts
48897
+ function createSearchCodeTool(connector, userId) {
48898
+ return {
48899
+ definition: {
48900
+ type: "function",
48901
+ function: {
48902
+ name: "search_code",
48903
+ description: `Search for code content across a GitHub repository.
48904
+
48905
+ USAGE:
48906
+ - Search by keyword, function name, class name, or any text
48907
+ - Filter by language, path, or file extension
48908
+ - Returns matching files with text fragments showing context
48909
+
48910
+ RATE LIMITS:
48911
+ - GitHub's code search API is limited to 30 requests per minute
48912
+ - Results may be incomplete for very large repositories
48913
+
48914
+ EXAMPLES:
48915
+ - Find function: { "query": "function handleAuth", "language": "typescript" }
48916
+ - Find imports: { "query": "import React", "extension": "tsx" }
48917
+ - Search in path: { "query": "TODO", "path": "src/utils" }
48918
+ - Limit results: { "query": "console.log", "limit": 10 }`,
48919
+ parameters: {
48920
+ type: "object",
48921
+ properties: {
48922
+ repository: {
48923
+ type: "string",
48924
+ description: 'Repository in "owner/repo" format or full GitHub URL. Optional if connector has a default repository.'
48925
+ },
48926
+ query: {
48927
+ type: "string",
48928
+ description: "Search query \u2014 keyword, function name, or any text to find in code"
48929
+ },
48930
+ language: {
48931
+ type: "string",
48932
+ description: 'Filter by programming language (e.g., "typescript", "python", "go")'
48933
+ },
48934
+ path: {
48935
+ type: "string",
48936
+ description: 'Filter by file path prefix (e.g., "src/", "lib/utils")'
48937
+ },
48938
+ extension: {
48939
+ type: "string",
48940
+ description: 'Filter by file extension without dot (e.g., "ts", "py", "go")'
48941
+ },
48942
+ limit: {
48943
+ type: "number",
48944
+ description: "Maximum number of results (default: 30, max: 100)"
48945
+ }
48946
+ },
48947
+ required: ["query"]
48948
+ }
48949
+ }
48950
+ },
48951
+ describeCall: (args) => {
48952
+ const parts = [`"${args.query}"`];
48953
+ if (args.language) parts.push(`lang:${args.language}`);
48954
+ if (args.repository) parts.push(`in ${args.repository}`);
48955
+ return parts.join(" ");
48956
+ },
48957
+ permission: {
48958
+ scope: "session",
48959
+ riskLevel: "low",
48960
+ approvalMessage: `Search code in a GitHub repository via ${connector.displayName}`
48961
+ },
48962
+ execute: async (args, context) => {
48963
+ const effectiveUserId = context?.userId ?? userId;
48964
+ const resolved = resolveRepository(args.repository, connector);
48965
+ if (!resolved.success) {
48966
+ return { success: false, error: resolved.error };
48967
+ }
48968
+ const { owner, repo } = resolved.repo;
48969
+ try {
48970
+ const qualifiers = [`repo:${owner}/${repo}`];
48971
+ if (args.language) qualifiers.push(`language:${args.language}`);
48972
+ if (args.path) qualifiers.push(`path:${args.path}`);
48973
+ if (args.extension) qualifiers.push(`extension:${args.extension}`);
48974
+ const q = `${args.query} ${qualifiers.join(" ")}`;
48975
+ const perPage = Math.min(args.limit ?? 30, 100);
48976
+ const result = await githubFetch(
48977
+ connector,
48978
+ `/search/code`,
48979
+ {
48980
+ userId: effectiveUserId,
48981
+ // Request text-match fragments
48982
+ accept: "application/vnd.github.text-match+json",
48983
+ queryParams: { q, per_page: perPage }
48984
+ }
48985
+ );
48986
+ const matches = result.items.map((item) => ({
48987
+ file: item.path,
48988
+ fragment: item.text_matches?.[0]?.fragment
48989
+ }));
48990
+ return {
48991
+ success: true,
48992
+ matches,
48993
+ count: result.total_count,
48994
+ truncated: result.incomplete_results || result.total_count > perPage
48995
+ };
48996
+ } catch (error) {
48997
+ return {
48998
+ success: false,
48999
+ error: `Failed to search code: ${error instanceof Error ? error.message : String(error)}`
49000
+ };
49001
+ }
49002
+ }
49003
+ };
49004
+ }
49005
+
49006
+ // src/tools/github/readFile.ts
49007
+ function createGitHubReadFileTool(connector, userId) {
49008
+ return {
49009
+ definition: {
49010
+ type: "function",
49011
+ function: {
49012
+ name: "read_file",
49013
+ description: `Read file content from a GitHub repository.
49014
+
49015
+ USAGE:
49016
+ - Reads a file and returns content with line numbers
49017
+ - Supports line range selection with offset/limit for large files
49018
+ - By default reads up to 2000 lines from the beginning
49019
+
49020
+ EXAMPLES:
49021
+ - Read entire file: { "path": "src/index.ts" }
49022
+ - Read specific branch: { "path": "README.md", "ref": "develop" }
49023
+ - Read lines 100-200: { "path": "src/app.ts", "offset": 100, "limit": 100 }
49024
+ - Specific repo: { "repository": "owner/repo", "path": "package.json" }
49025
+
49026
+ NOTE: Files larger than 1MB are fetched via the Git Blob API. Very large files (>5MB) may be truncated.`,
49027
+ parameters: {
49028
+ type: "object",
49029
+ properties: {
49030
+ repository: {
49031
+ type: "string",
49032
+ description: 'Repository in "owner/repo" format or full GitHub URL. Optional if connector has a default repository.'
49033
+ },
49034
+ path: {
49035
+ type: "string",
49036
+ description: 'File path within the repository (e.g., "src/index.ts")'
49037
+ },
49038
+ ref: {
49039
+ type: "string",
49040
+ description: "Branch, tag, or commit SHA. Defaults to the repository's default branch."
49041
+ },
49042
+ offset: {
49043
+ type: "number",
49044
+ description: "Line number to start reading from (1-indexed). Only provide if the file is too large."
49045
+ },
49046
+ limit: {
49047
+ type: "number",
49048
+ description: "Number of lines to read (default: 2000). Only provide if the file is too large."
49049
+ }
49050
+ },
49051
+ required: ["path"]
49052
+ }
49053
+ }
49054
+ },
49055
+ describeCall: (args) => {
49056
+ const parts = [args.path];
49057
+ if (args.repository) parts.push(`in ${args.repository}`);
49058
+ if (args.ref) parts.push(`@${args.ref}`);
49059
+ if (args.offset && args.limit) parts.push(`[lines ${args.offset}-${args.offset + args.limit}]`);
49060
+ return parts.join(" ");
49061
+ },
49062
+ permission: {
49063
+ scope: "session",
49064
+ riskLevel: "low",
49065
+ approvalMessage: `Read a file from a GitHub repository via ${connector.displayName}`
49066
+ },
49067
+ execute: async (args, context) => {
49068
+ const effectiveUserId = context?.userId ?? userId;
49069
+ const resolved = resolveRepository(args.repository, connector);
49070
+ if (!resolved.success) {
49071
+ return { success: false, error: resolved.error };
49072
+ }
49073
+ const { owner, repo } = resolved.repo;
49074
+ try {
49075
+ let fileContent;
49076
+ let fileSha;
49077
+ let fileSize;
49078
+ const refParam = args.ref ? `?ref=${encodeURIComponent(args.ref)}` : "";
49079
+ const contentResp = await githubFetch(
49080
+ connector,
49081
+ `/repos/${owner}/${repo}/contents/${args.path}${refParam}`,
49082
+ { userId: effectiveUserId }
49083
+ );
49084
+ if (contentResp.type !== "file") {
49085
+ return {
49086
+ success: false,
49087
+ error: `Path is not a file: ${args.path} (type: ${contentResp.type}). Use search_files to explore the repository.`,
49088
+ path: args.path
49089
+ };
49090
+ }
49091
+ fileSha = contentResp.sha;
49092
+ fileSize = contentResp.size;
49093
+ if (contentResp.content && contentResp.encoding === "base64") {
49094
+ fileContent = Buffer.from(contentResp.content, "base64").toString("utf-8");
49095
+ } else if (contentResp.git_url) {
49096
+ const blob = await githubFetch(
49097
+ connector,
49098
+ contentResp.git_url,
49099
+ { userId: effectiveUserId }
49100
+ );
49101
+ fileContent = Buffer.from(blob.content, "base64").toString("utf-8");
49102
+ fileSize = blob.size;
49103
+ } else {
49104
+ return {
49105
+ success: false,
49106
+ error: `Cannot read file content: ${args.path} (no content or git_url in response)`,
49107
+ path: args.path
49108
+ };
49109
+ }
49110
+ const offset = args.offset ?? 1;
49111
+ const limit = args.limit ?? 2e3;
49112
+ const allLines = fileContent.split("\n");
49113
+ const totalLines = allLines.length;
49114
+ const startIndex = Math.max(0, offset - 1);
49115
+ const endIndex = Math.min(totalLines, startIndex + limit);
49116
+ const selectedLines = allLines.slice(startIndex, endIndex);
49117
+ const lineNumberWidth = String(endIndex).length;
49118
+ const formattedLines = selectedLines.map((line, i) => {
49119
+ const lineNum = startIndex + i + 1;
49120
+ const paddedNum = String(lineNum).padStart(lineNumberWidth, " ");
49121
+ const truncatedLine = line.length > 2e3 ? line.substring(0, 2e3) + "..." : line;
49122
+ return `${paddedNum} ${truncatedLine}`;
49123
+ });
49124
+ const truncated = endIndex < totalLines;
49125
+ const result = formattedLines.join("\n");
49126
+ return {
49127
+ success: true,
49128
+ content: result,
49129
+ path: args.path,
49130
+ size: fileSize,
49131
+ lines: totalLines,
49132
+ truncated,
49133
+ sha: fileSha
49134
+ };
49135
+ } catch (error) {
49136
+ return {
49137
+ success: false,
49138
+ error: `Failed to read file: ${error instanceof Error ? error.message : String(error)}`,
49139
+ path: args.path
49140
+ };
49141
+ }
49142
+ }
49143
+ };
49144
+ }
49145
+
49146
+ // src/tools/github/getPR.ts
49147
+ function createGetPRTool(connector, userId) {
49148
+ return {
49149
+ definition: {
49150
+ type: "function",
49151
+ function: {
49152
+ name: "get_pr",
49153
+ description: `Get full details of a pull request from a GitHub repository.
49154
+
49155
+ Returns: title, description, state, author, labels, reviewers, merge status, branches, file stats, and more.
49156
+
49157
+ EXAMPLES:
49158
+ - Get PR: { "pull_number": 123 }
49159
+ - Specific repo: { "repository": "owner/repo", "pull_number": 456 }`,
49160
+ parameters: {
49161
+ type: "object",
49162
+ properties: {
49163
+ repository: {
49164
+ type: "string",
49165
+ description: 'Repository in "owner/repo" format or full GitHub URL. Optional if connector has a default repository.'
49166
+ },
49167
+ pull_number: {
49168
+ type: "number",
49169
+ description: "Pull request number"
49170
+ }
49171
+ },
49172
+ required: ["pull_number"]
49173
+ }
49174
+ }
49175
+ },
49176
+ describeCall: (args) => {
49177
+ const parts = [`#${args.pull_number}`];
49178
+ if (args.repository) parts.push(`in ${args.repository}`);
49179
+ return parts.join(" ");
49180
+ },
49181
+ permission: {
49182
+ scope: "session",
49183
+ riskLevel: "low",
49184
+ approvalMessage: `Get pull request details from GitHub via ${connector.displayName}`
49185
+ },
49186
+ execute: async (args, context) => {
49187
+ const effectiveUserId = context?.userId ?? userId;
49188
+ const resolved = resolveRepository(args.repository, connector);
49189
+ if (!resolved.success) {
49190
+ return { success: false, error: resolved.error };
49191
+ }
49192
+ const { owner, repo } = resolved.repo;
49193
+ try {
49194
+ const pr = await githubFetch(
49195
+ connector,
49196
+ `/repos/${owner}/${repo}/pulls/${args.pull_number}`,
49197
+ { userId: effectiveUserId }
49198
+ );
49199
+ return {
49200
+ success: true,
49201
+ data: {
49202
+ number: pr.number,
49203
+ title: pr.title,
49204
+ body: pr.body,
49205
+ state: pr.state,
49206
+ draft: pr.draft,
49207
+ author: pr.user.login,
49208
+ labels: pr.labels.map((l) => l.name),
49209
+ reviewers: pr.requested_reviewers.map((r) => r.login),
49210
+ mergeable: pr.mergeable,
49211
+ head: pr.head.ref,
49212
+ base: pr.base.ref,
49213
+ url: pr.html_url,
49214
+ created_at: pr.created_at,
49215
+ updated_at: pr.updated_at,
49216
+ additions: pr.additions,
49217
+ deletions: pr.deletions,
49218
+ changed_files: pr.changed_files
49219
+ }
49220
+ };
49221
+ } catch (error) {
49222
+ return {
49223
+ success: false,
49224
+ error: `Failed to get PR: ${error instanceof Error ? error.message : String(error)}`
49225
+ };
49226
+ }
49227
+ }
49228
+ };
49229
+ }
49230
+
49231
+ // src/tools/github/prFiles.ts
49232
+ function createPRFilesTool(connector, userId) {
49233
+ return {
49234
+ definition: {
49235
+ type: "function",
49236
+ function: {
49237
+ name: "pr_files",
49238
+ description: `Get the files changed in a pull request with diffs.
49239
+
49240
+ Returns: filename, status (added/modified/removed/renamed), additions, deletions, and patch (diff) content for each file.
49241
+
49242
+ EXAMPLES:
49243
+ - Get files: { "pull_number": 123 }
49244
+ - Specific repo: { "repository": "owner/repo", "pull_number": 456 }
49245
+
49246
+ NOTE: Very large diffs may be truncated by GitHub. Patch content may be absent for binary files.`,
49247
+ parameters: {
49248
+ type: "object",
49249
+ properties: {
49250
+ repository: {
49251
+ type: "string",
49252
+ description: 'Repository in "owner/repo" format or full GitHub URL. Optional if connector has a default repository.'
49253
+ },
49254
+ pull_number: {
49255
+ type: "number",
49256
+ description: "Pull request number"
49257
+ }
49258
+ },
49259
+ required: ["pull_number"]
49260
+ }
49261
+ }
49262
+ },
49263
+ describeCall: (args) => {
49264
+ const parts = [`files for #${args.pull_number}`];
49265
+ if (args.repository) parts.push(`in ${args.repository}`);
49266
+ return parts.join(" ");
49267
+ },
49268
+ permission: {
49269
+ scope: "session",
49270
+ riskLevel: "low",
49271
+ approvalMessage: `Get PR changed files from GitHub via ${connector.displayName}`
49272
+ },
49273
+ execute: async (args, context) => {
49274
+ const effectiveUserId = context?.userId ?? userId;
49275
+ const resolved = resolveRepository(args.repository, connector);
49276
+ if (!resolved.success) {
49277
+ return { success: false, error: resolved.error };
49278
+ }
49279
+ const { owner, repo } = resolved.repo;
49280
+ try {
49281
+ const files = await githubFetch(
49282
+ connector,
49283
+ `/repos/${owner}/${repo}/pulls/${args.pull_number}/files`,
49284
+ {
49285
+ userId: effectiveUserId,
49286
+ queryParams: { per_page: 100 }
49287
+ }
49288
+ );
49289
+ return {
49290
+ success: true,
49291
+ files: files.map((f) => ({
49292
+ filename: f.filename,
49293
+ status: f.status,
49294
+ additions: f.additions,
49295
+ deletions: f.deletions,
49296
+ changes: f.changes,
49297
+ patch: f.patch
49298
+ })),
49299
+ count: files.length
49300
+ };
49301
+ } catch (error) {
49302
+ return {
49303
+ success: false,
49304
+ error: `Failed to get PR files: ${error instanceof Error ? error.message : String(error)}`
49305
+ };
49306
+ }
49307
+ }
49308
+ };
49309
+ }
49310
+
49311
+ // src/tools/github/prComments.ts
49312
+ function createPRCommentsTool(connector, userId) {
49313
+ return {
49314
+ definition: {
49315
+ type: "function",
49316
+ function: {
49317
+ name: "pr_comments",
49318
+ description: `Get all comments and reviews on a pull request.
49319
+
49320
+ Returns a unified list of:
49321
+ - **review_comment**: Line-level comments on specific code (includes file path and line number)
49322
+ - **review**: Full reviews (approve/request changes/comment)
49323
+ - **comment**: General comments on the PR (issue-level)
49324
+
49325
+ All entries are sorted by creation date (oldest first).
49326
+
49327
+ EXAMPLES:
49328
+ - Get comments: { "pull_number": 123 }
49329
+ - Specific repo: { "repository": "owner/repo", "pull_number": 456 }`,
49330
+ parameters: {
49331
+ type: "object",
49332
+ properties: {
49333
+ repository: {
49334
+ type: "string",
49335
+ description: 'Repository in "owner/repo" format or full GitHub URL. Optional if connector has a default repository.'
49336
+ },
49337
+ pull_number: {
49338
+ type: "number",
49339
+ description: "Pull request number"
49340
+ }
49341
+ },
49342
+ required: ["pull_number"]
49343
+ }
49344
+ }
49345
+ },
49346
+ describeCall: (args) => {
49347
+ const parts = [`comments for #${args.pull_number}`];
49348
+ if (args.repository) parts.push(`in ${args.repository}`);
49349
+ return parts.join(" ");
49350
+ },
49351
+ permission: {
49352
+ scope: "session",
49353
+ riskLevel: "low",
49354
+ approvalMessage: `Get PR comments and reviews from GitHub via ${connector.displayName}`
49355
+ },
49356
+ execute: async (args, context) => {
49357
+ const effectiveUserId = context?.userId ?? userId;
49358
+ const resolved = resolveRepository(args.repository, connector);
49359
+ if (!resolved.success) {
49360
+ return { success: false, error: resolved.error };
49361
+ }
49362
+ const { owner, repo } = resolved.repo;
49363
+ try {
49364
+ const basePath = `/repos/${owner}/${repo}`;
49365
+ const queryOpts = { userId: effectiveUserId, queryParams: { per_page: 100 } };
49366
+ const [reviewComments, reviews, issueComments] = await Promise.all([
49367
+ githubFetch(
49368
+ connector,
49369
+ `${basePath}/pulls/${args.pull_number}/comments`,
49370
+ queryOpts
49371
+ ),
49372
+ githubFetch(
49373
+ connector,
49374
+ `${basePath}/pulls/${args.pull_number}/reviews`,
49375
+ queryOpts
49376
+ ),
49377
+ githubFetch(
49378
+ connector,
49379
+ `${basePath}/issues/${args.pull_number}/comments`,
49380
+ queryOpts
49381
+ )
49382
+ ]);
49383
+ const allComments = [];
49384
+ for (const rc of reviewComments) {
49385
+ allComments.push({
49386
+ id: rc.id,
49387
+ type: "review_comment",
49388
+ author: rc.user.login,
49389
+ body: rc.body,
49390
+ created_at: rc.created_at,
49391
+ path: rc.path,
49392
+ line: rc.line ?? rc.original_line ?? void 0
49393
+ });
49394
+ }
49395
+ for (const r of reviews) {
49396
+ if (!r.body && r.state === "APPROVED") continue;
49397
+ allComments.push({
49398
+ id: r.id,
49399
+ type: "review",
49400
+ author: r.user.login,
49401
+ body: r.body || `[${r.state}]`,
49402
+ created_at: r.submitted_at,
49403
+ state: r.state
49404
+ });
49405
+ }
49406
+ for (const ic of issueComments) {
49407
+ allComments.push({
49408
+ id: ic.id,
49409
+ type: "comment",
49410
+ author: ic.user.login,
49411
+ body: ic.body,
49412
+ created_at: ic.created_at
49413
+ });
49414
+ }
49415
+ allComments.sort(
49416
+ (a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
49417
+ );
49418
+ return {
49419
+ success: true,
49420
+ comments: allComments,
49421
+ count: allComments.length
49422
+ };
49423
+ } catch (error) {
49424
+ return {
49425
+ success: false,
49426
+ error: `Failed to get PR comments: ${error instanceof Error ? error.message : String(error)}`
49427
+ };
49428
+ }
49429
+ }
49430
+ };
49431
+ }
49432
+
49433
+ // src/tools/github/createPR.ts
49434
+ function createCreatePRTool(connector, userId) {
49435
+ return {
49436
+ definition: {
49437
+ type: "function",
49438
+ function: {
49439
+ name: "create_pr",
49440
+ description: `Create a pull request on a GitHub repository.
49441
+
49442
+ USAGE:
49443
+ - Specify source branch (head) and target branch (base)
49444
+ - Optionally create as draft
49445
+
49446
+ EXAMPLES:
49447
+ - Create PR: { "title": "Add feature", "head": "feature-branch", "base": "main" }
49448
+ - Draft PR: { "title": "WIP: Refactor", "head": "refactor", "base": "develop", "draft": true }
49449
+ - With body: { "title": "Fix bug #42", "body": "Fixes the login issue\\n\\n## Changes\\n- Fixed auth flow", "head": "fix/42", "base": "main" }`,
49450
+ parameters: {
49451
+ type: "object",
49452
+ properties: {
49453
+ repository: {
49454
+ type: "string",
49455
+ description: 'Repository in "owner/repo" format or full GitHub URL. Optional if connector has a default repository.'
49456
+ },
49457
+ title: {
49458
+ type: "string",
49459
+ description: "Pull request title"
49460
+ },
49461
+ body: {
49462
+ type: "string",
49463
+ description: "Pull request description/body (Markdown supported)"
49464
+ },
49465
+ head: {
49466
+ type: "string",
49467
+ description: "Source branch name (the branch with your changes)"
49468
+ },
49469
+ base: {
49470
+ type: "string",
49471
+ description: 'Target branch name (the branch you want to merge into, e.g., "main")'
49472
+ },
49473
+ draft: {
49474
+ type: "boolean",
49475
+ description: "Create as a draft pull request (default: false)"
49476
+ }
49477
+ },
49478
+ required: ["title", "head", "base"]
49479
+ }
49480
+ }
49481
+ },
49482
+ describeCall: (args) => {
49483
+ const parts = [args.title];
49484
+ if (args.repository) parts.push(`in ${args.repository}`);
49485
+ return parts.join(" ");
49486
+ },
49487
+ permission: {
49488
+ scope: "session",
49489
+ riskLevel: "medium",
49490
+ approvalMessage: `Create a pull request on GitHub via ${connector.displayName}`
49491
+ },
49492
+ execute: async (args, context) => {
49493
+ const effectiveUserId = context?.userId ?? userId;
49494
+ const resolved = resolveRepository(args.repository, connector);
49495
+ if (!resolved.success) {
49496
+ return { success: false, error: resolved.error };
49497
+ }
49498
+ const { owner, repo } = resolved.repo;
49499
+ try {
49500
+ const pr = await githubFetch(
49501
+ connector,
49502
+ `/repos/${owner}/${repo}/pulls`,
49503
+ {
49504
+ method: "POST",
49505
+ userId: effectiveUserId,
49506
+ body: {
49507
+ title: args.title,
49508
+ body: args.body,
49509
+ head: args.head,
49510
+ base: args.base,
49511
+ draft: args.draft ?? false
49512
+ }
49513
+ }
49514
+ );
49515
+ return {
49516
+ success: true,
49517
+ data: {
49518
+ number: pr.number,
49519
+ url: pr.html_url,
49520
+ state: pr.state,
49521
+ title: pr.title
49522
+ }
49523
+ };
49524
+ } catch (error) {
49525
+ return {
49526
+ success: false,
49527
+ error: `Failed to create PR: ${error instanceof Error ? error.message : String(error)}`
49528
+ };
49529
+ }
49530
+ }
49531
+ };
49532
+ }
49533
+
49534
+ // src/tools/github/register.ts
49535
+ function registerGitHubTools() {
49536
+ ConnectorTools.registerService("github", (connector, userId) => {
49537
+ return [
49538
+ createSearchFilesTool(connector, userId),
49539
+ createSearchCodeTool(connector, userId),
49540
+ createGitHubReadFileTool(connector, userId),
49541
+ createGetPRTool(connector, userId),
49542
+ createPRFilesTool(connector, userId),
49543
+ createPRCommentsTool(connector, userId),
49544
+ createCreatePRTool(connector, userId)
49545
+ ];
49546
+ });
49547
+ }
49548
+
49549
+ // src/tools/github/index.ts
49550
+ registerGitHubTools();
49551
+
48609
49552
  // src/tools/registry.generated.ts
48610
49553
  var toolRegistry = [
48611
49554
  {
@@ -48659,7 +49602,7 @@ var toolRegistry = [
48659
49602
  displayName: "Read File",
48660
49603
  category: "filesystem",
48661
49604
  description: "Read content from a file on the local filesystem.",
48662
- tool: readFile4,
49605
+ tool: readFile5,
48663
49606
  safeByDefault: true
48664
49607
  },
48665
49608
  {
@@ -48668,7 +49611,7 @@ var toolRegistry = [
48668
49611
  displayName: "Write File",
48669
49612
  category: "filesystem",
48670
49613
  description: "Write content to a file on the local filesystem.",
48671
- tool: writeFile4,
49614
+ tool: writeFile5,
48672
49615
  safeByDefault: false
48673
49616
  },
48674
49617
  {
@@ -48697,37 +49640,6 @@ var toolRegistry = [
48697
49640
  description: "Fetch and extract text content from a web page URL.",
48698
49641
  tool: webFetch,
48699
49642
  safeByDefault: true
48700
- },
48701
- {
48702
- name: "web_fetch_js",
48703
- exportName: "webFetchJS",
48704
- displayName: "Web Fetch Js",
48705
- category: "web",
48706
- description: "Fetch and extract content from JavaScript-rendered websites using a headless browser (Puppeteer).",
48707
- tool: webFetchJS,
48708
- safeByDefault: true
48709
- },
48710
- {
48711
- name: "web_scrape",
48712
- exportName: "webScrape",
48713
- displayName: "Web Scrape",
48714
- category: "web",
48715
- description: "Scrape any URL with automatic fallback - guaranteed to work on most sites.",
48716
- tool: webScrape,
48717
- safeByDefault: true,
48718
- requiresConnector: true,
48719
- connectorServiceTypes: ["zenrows"]
48720
- },
48721
- {
48722
- name: "web_search",
48723
- exportName: "webSearch",
48724
- displayName: "Web Search",
48725
- category: "web",
48726
- description: "Search the web and get relevant results with snippets.",
48727
- tool: webSearch,
48728
- safeByDefault: true,
48729
- requiresConnector: true,
48730
- connectorServiceTypes: ["serper", "brave-search", "tavily", "rapidapi-websearch"]
48731
49643
  }
48732
49644
  ];
48733
49645
  function getAllBuiltInTools() {
@@ -48897,8 +49809,8 @@ var ToolRegistry = class {
48897
49809
 
48898
49810
  // src/tools/index.ts
48899
49811
  var developerTools = [
48900
- readFile4,
48901
- writeFile4,
49812
+ readFile5,
49813
+ writeFile5,
48902
49814
  editFile,
48903
49815
  glob,
48904
49816
  grep,
@@ -49085,6 +49997,6 @@ REMEMBER: Keep it conversational, ask one question at a time, and only output th
49085
49997
  }
49086
49998
  };
49087
49999
 
49088
- export { AGENT_DEFINITION_FORMAT_VERSION, AIError, APPROVAL_STATE_VERSION, Agent, AgentContextNextGen, ApproximateTokenEstimator, BaseMediaProvider, BasePluginNextGen, BaseProvider, BaseTextProvider, BraveProvider, CONNECTOR_CONFIG_VERSION, CONTEXT_SESSION_FORMAT_VERSION, CheckpointManager, CircuitBreaker, CircuitOpenError, Connector, ConnectorConfigStore, ConnectorTools, ConsoleMetrics, ContentType, ContextOverflowError, DEFAULT_ALLOWLIST, DEFAULT_BACKOFF_CONFIG, DEFAULT_BASE_DELAY_MS, DEFAULT_CHECKPOINT_STRATEGY, DEFAULT_CIRCUIT_BREAKER_CONFIG, DEFAULT_CONFIG2 as DEFAULT_CONFIG, DEFAULT_CONNECTOR_TIMEOUT, DEFAULT_CONTEXT_CONFIG, DEFAULT_FEATURES, DEFAULT_FILESYSTEM_CONFIG, DEFAULT_HISTORY_MANAGER_CONFIG, DEFAULT_MAX_DELAY_MS, DEFAULT_MAX_RETRIES, DEFAULT_MEMORY_CONFIG, DEFAULT_PERMISSION_CONFIG, DEFAULT_RATE_LIMITER_CONFIG, DEFAULT_RETRYABLE_STATUSES, DEFAULT_SHELL_CONFIG, DefaultCompactionStrategy, DependencyCycleError, ErrorHandler, ExecutionContext, ExternalDependencyHandler, FileAgentDefinitionStorage, FileConnectorStorage, FileContextStorage, FileMediaOutputHandler, FilePersistentInstructionsStorage, FileStorage, FrameworkLogger, HookManager, IMAGE_MODELS, IMAGE_MODEL_REGISTRY, ImageGeneration, InContextMemoryPluginNextGen, InMemoryAgentStateStorage, InMemoryHistoryStorage, InMemoryMetrics, InMemoryPlanStorage, InMemoryStorage, InvalidConfigError, InvalidToolArgumentsError, LLM_MODELS, LoggingPlugin, MCPClient, MCPConnectionError, MCPError, MCPProtocolError, MCPRegistry, MCPResourceError, MCPTimeoutError, MCPToolError, MEMORY_PRIORITY_VALUES, MODEL_REGISTRY, MemoryConnectorStorage, MemoryEvictionCompactor, MemoryStorage, MessageBuilder, MessageRole, ModelNotSupportedError, NoOpMetrics, OAuthManager, ParallelTasksError, PersistentInstructionsPluginNextGen, PlanningAgent, ProviderAuthError, ProviderConfigAgent, ProviderContextLengthError, ProviderError, ProviderErrorMapper, ProviderNotFoundError, ProviderRateLimitError, RapidAPIProvider, RateLimitError, SERVICE_DEFINITIONS, SERVICE_INFO, SERVICE_URL_PATTERNS, SIMPLE_ICONS_CDN, STT_MODELS, STT_MODEL_REGISTRY, ScopedConnectorRegistry, ScrapeProvider, SearchProvider, SerperProvider, Services, SpeechToText, StrategyRegistry, StreamEventType, StreamHelpers, StreamState, SummarizeCompactor, TERMINAL_TASK_STATUSES, TTS_MODELS, TTS_MODEL_REGISTRY, TaskTimeoutError, TaskValidationError, TavilyProvider, TextToSpeech, TokenBucketRateLimiter, ToolCallState, ToolExecutionError, ToolExecutionPipeline, ToolManager, ToolNotFoundError, ToolPermissionManager, ToolRegistry, ToolTimeoutError, TruncateCompactor, VENDORS, VENDOR_ICON_MAP, VIDEO_MODELS, VIDEO_MODEL_REGISTRY, Vendor, VideoGeneration, WorkingMemory, WorkingMemoryPluginNextGen, addJitter, allVendorTemplates, assertNotDestroyed, authenticatedFetch, backoffSequence, backoffWait, bash, buildAuthConfig, buildEndpointWithQuery, buildQueryString, calculateBackoff, calculateCost, calculateEntrySize, calculateImageCost, calculateSTTCost, calculateTTSCost, calculateVideoCost, canTaskExecute, createAgentStorage, createAuthenticatedFetch, createBashTool, createConnectorFromTemplate, createEditFileTool, createEstimator, createExecuteJavaScriptTool, createFileAgentDefinitionStorage, createFileContextStorage, createGlobTool, createGrepTool, createImageGenerationTool, createImageProvider, createListDirectoryTool, createMessageWithImages, createMetricsCollector, createPlan, createProvider, createReadFileTool, createSpeechToTextTool, createTask, createTextMessage, createTextToSpeechTool, createVideoProvider, createVideoTools, createWriteFileTool, defaultDescribeCall, detectDependencyCycle, detectServiceFromURL, developerTools, editFile, evaluateCondition, extractJSON, extractJSONField, extractNumber, findConnectorByServiceTypes, forPlan, forTasks, generateEncryptionKey, generateSimplePlan, generateWebAPITool, getActiveImageModels, getActiveModels, getActiveSTTModels, getActiveTTSModels, getActiveVideoModels, getAllBuiltInTools, getAllServiceIds, getAllVendorLogos, getAllVendorTemplates, getBackgroundOutput, getConnectorTools, getCredentialsSetupURL, getDocsURL, getImageModelInfo, getImageModelsByVendor, getImageModelsWithFeature, getMediaOutputHandler, getModelInfo, getModelsByVendor, getNextExecutableTasks, getRegisteredScrapeProviders, getSTTModelInfo, getSTTModelsByVendor, getSTTModelsWithFeature, getServiceDefinition, getServiceInfo, getServicesByCategory, getTTSModelInfo, getTTSModelsByVendor, getTTSModelsWithFeature, getTaskDependencies, getToolByName, getToolCallDescription, getToolCategories, getToolRegistry, getToolsByCategory, getToolsRequiringConnector, getVendorAuthTemplate, getVendorColor, getVendorInfo, getVendorLogo, getVendorLogoCdnUrl, getVendorLogoSvg, getVendorTemplate, getVideoModelInfo, getVideoModelsByVendor, getVideoModelsWithAudio, getVideoModelsWithFeature, glob, globalErrorHandler, grep, hasClipboardImage, hasVendorLogo, isBlockedCommand, isErrorEvent, isExcludedExtension, isKnownService, isOutputTextDelta, isResponseComplete, isSimpleScope, isStreamEvent, isTaskAwareScope, isTaskBlocked, isTerminalMemoryStatus, isTerminalStatus, isToolCallArgumentsDelta, isToolCallArgumentsDone, isToolCallStart, isVendor, killBackgroundProcess, listConnectorsByServiceTypes, listDirectory, listVendorIds, listVendors, listVendorsByAuthType, listVendorsByCategory, listVendorsWithLogos, logger, metrics, readClipboardImage, readFile4 as readFile, registerScrapeProvider, resolveConnector, resolveDependencies, retryWithBackoff, scopeEquals, scopeMatches, setMediaOutputHandler, setMetricsCollector, simpleTokenEstimator, toConnectorOptions, toolRegistry, tools_exports as tools, updateTaskStatus, validatePath, writeFile4 as writeFile };
50000
+ export { AGENT_DEFINITION_FORMAT_VERSION, AIError, APPROVAL_STATE_VERSION, Agent, AgentContextNextGen, ApproximateTokenEstimator, BaseMediaProvider, BasePluginNextGen, BaseProvider, BaseTextProvider, BraveProvider, CONNECTOR_CONFIG_VERSION, CONTEXT_SESSION_FORMAT_VERSION, CheckpointManager, CircuitBreaker, CircuitOpenError, Connector, ConnectorConfigStore, ConnectorTools, ConsoleMetrics, ContentType, ContextOverflowError, DEFAULT_ALLOWLIST, DEFAULT_BACKOFF_CONFIG, DEFAULT_BASE_DELAY_MS, DEFAULT_CHECKPOINT_STRATEGY, DEFAULT_CIRCUIT_BREAKER_CONFIG, DEFAULT_CONFIG2 as DEFAULT_CONFIG, DEFAULT_CONNECTOR_TIMEOUT, DEFAULT_CONTEXT_CONFIG, DEFAULT_FEATURES, DEFAULT_FILESYSTEM_CONFIG, DEFAULT_HISTORY_MANAGER_CONFIG, DEFAULT_MAX_DELAY_MS, DEFAULT_MAX_RETRIES, DEFAULT_MEMORY_CONFIG, DEFAULT_PERMISSION_CONFIG, DEFAULT_RATE_LIMITER_CONFIG, DEFAULT_RETRYABLE_STATUSES, DEFAULT_SHELL_CONFIG, DefaultCompactionStrategy, DependencyCycleError, ErrorHandler, ExecutionContext, ExternalDependencyHandler, FileAgentDefinitionStorage, FileConnectorStorage, FileContextStorage, FileMediaStorage as FileMediaOutputHandler, FileMediaStorage, FilePersistentInstructionsStorage, FileStorage, FrameworkLogger, HookManager, IMAGE_MODELS, IMAGE_MODEL_REGISTRY, ImageGeneration, InContextMemoryPluginNextGen, InMemoryAgentStateStorage, InMemoryHistoryStorage, InMemoryMetrics, InMemoryPlanStorage, InMemoryStorage, InvalidConfigError, InvalidToolArgumentsError, LLM_MODELS, LoggingPlugin, MCPClient, MCPConnectionError, MCPError, MCPProtocolError, MCPRegistry, MCPResourceError, MCPTimeoutError, MCPToolError, MEMORY_PRIORITY_VALUES, MODEL_REGISTRY, MemoryConnectorStorage, MemoryEvictionCompactor, MemoryStorage, MessageBuilder, MessageRole, ModelNotSupportedError, NoOpMetrics, OAuthManager, ParallelTasksError, PersistentInstructionsPluginNextGen, PlanningAgent, ProviderAuthError, ProviderConfigAgent, ProviderContextLengthError, ProviderError, ProviderErrorMapper, ProviderNotFoundError, ProviderRateLimitError, RapidAPIProvider, RateLimitError, SERVICE_DEFINITIONS, SERVICE_INFO, SERVICE_URL_PATTERNS, SIMPLE_ICONS_CDN, STT_MODELS, STT_MODEL_REGISTRY, ScopedConnectorRegistry, ScrapeProvider, SearchProvider, SerperProvider, Services, SpeechToText, StrategyRegistry, StreamEventType, StreamHelpers, StreamState, SummarizeCompactor, TERMINAL_TASK_STATUSES, TTS_MODELS, TTS_MODEL_REGISTRY, TaskTimeoutError, TaskValidationError, TavilyProvider, TextToSpeech, TokenBucketRateLimiter, ToolCallState, ToolExecutionError, ToolExecutionPipeline, ToolManager, ToolNotFoundError, ToolPermissionManager, ToolRegistry, ToolTimeoutError, TruncateCompactor, VENDORS, VENDOR_ICON_MAP, VIDEO_MODELS, VIDEO_MODEL_REGISTRY, Vendor, VideoGeneration, WorkingMemory, WorkingMemoryPluginNextGen, addJitter, allVendorTemplates, assertNotDestroyed, authenticatedFetch, backoffSequence, backoffWait, bash, buildAuthConfig, buildEndpointWithQuery, buildQueryString, calculateBackoff, calculateCost, calculateEntrySize, calculateImageCost, calculateSTTCost, calculateTTSCost, calculateVideoCost, canTaskExecute, createAgentStorage, createAuthenticatedFetch, createBashTool, createConnectorFromTemplate, createCreatePRTool, createEditFileTool, createEstimator, createExecuteJavaScriptTool, createFileAgentDefinitionStorage, createFileContextStorage, createFileMediaStorage, createGetPRTool, createGitHubReadFileTool, createGlobTool, createGrepTool, createImageGenerationTool, createImageProvider, createListDirectoryTool, createMessageWithImages, createMetricsCollector, createPRCommentsTool, createPRFilesTool, createPlan, createProvider, createReadFileTool, createSearchCodeTool, createSearchFilesTool, createSpeechToTextTool, createTask, createTextMessage, createTextToSpeechTool, createVideoProvider, createVideoTools, createWriteFileTool, defaultDescribeCall, detectDependencyCycle, detectServiceFromURL, developerTools, editFile, evaluateCondition, extractJSON, extractJSONField, extractNumber, findConnectorByServiceTypes, forPlan, forTasks, generateEncryptionKey, generateSimplePlan, generateWebAPITool, getActiveImageModels, getActiveModels, getActiveSTTModels, getActiveTTSModels, getActiveVideoModels, getAllBuiltInTools, getAllServiceIds, getAllVendorLogos, getAllVendorTemplates, getBackgroundOutput, getConnectorTools, getCredentialsSetupURL, getDocsURL, getImageModelInfo, getImageModelsByVendor, getImageModelsWithFeature, getMediaOutputHandler, getMediaStorage, getModelInfo, getModelsByVendor, getNextExecutableTasks, getRegisteredScrapeProviders, getSTTModelInfo, getSTTModelsByVendor, getSTTModelsWithFeature, getServiceDefinition, getServiceInfo, getServicesByCategory, getTTSModelInfo, getTTSModelsByVendor, getTTSModelsWithFeature, getTaskDependencies, getToolByName, getToolCallDescription, getToolCategories, getToolRegistry, getToolsByCategory, getToolsRequiringConnector, getVendorAuthTemplate, getVendorColor, getVendorDefaultBaseURL, getVendorInfo, getVendorLogo, getVendorLogoCdnUrl, getVendorLogoSvg, getVendorTemplate, getVideoModelInfo, getVideoModelsByVendor, getVideoModelsWithAudio, getVideoModelsWithFeature, glob, globalErrorHandler, grep, hasClipboardImage, hasVendorLogo, isBlockedCommand, isErrorEvent, isExcludedExtension, isKnownService, isOutputTextDelta, isResponseComplete, isSimpleScope, isStreamEvent, isTaskAwareScope, isTaskBlocked, isTerminalMemoryStatus, isTerminalStatus, isToolCallArgumentsDelta, isToolCallArgumentsDone, isToolCallStart, isVendor, killBackgroundProcess, listConnectorsByServiceTypes, listDirectory, listVendorIds, listVendors, listVendorsByAuthType, listVendorsByCategory, listVendorsWithLogos, logger, metrics, parseRepository, readClipboardImage, readFile5 as readFile, registerScrapeProvider, resolveConnector, resolveDependencies, resolveRepository, retryWithBackoff, scopeEquals, scopeMatches, setMediaOutputHandler, setMediaStorage, setMetricsCollector, simpleTokenEstimator, toConnectorOptions, toolRegistry, tools_exports as tools, updateTaskStatus, validatePath, writeFile5 as writeFile };
49089
50001
  //# sourceMappingURL=index.js.map
49090
50002
  //# sourceMappingURL=index.js.map