a2acalling 0.6.1 → 0.6.2

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.
@@ -22,6 +22,7 @@ const CONFIG_DIR = process.env.A2A_CONFIG_DIR ||
22
22
  const EXTERNAL_IP_CACHE_FILE = path.join(CONFIG_DIR, 'a2a-external-ip.json');
23
23
 
24
24
  const DEFAULT_SERVICES = [
25
+ 'https://ifconfig.me/ip',
25
26
  'https://api.ipify.org',
26
27
  'https://checkip.amazonaws.com/',
27
28
  'https://icanhazip.com/'
@@ -113,6 +114,7 @@ async function fetchExternalIp(options = {}) {
113
114
  ? options.services
114
115
  : DEFAULT_SERVICES;
115
116
 
117
+ const attempts = [];
116
118
  let lastError = null;
117
119
  for (const serviceUrl of services) {
118
120
  try {
@@ -124,14 +126,18 @@ async function fetchExternalIp(options = {}) {
124
126
  if (!ip) {
125
127
  throw new Error('invalid_ip');
126
128
  }
127
- return { ip, source: serviceUrl };
129
+ attempts.push({ service: serviceUrl, ok: true, statusCode: res.statusCode, ip });
130
+ return { ip, source: serviceUrl, attempts };
128
131
  } catch (err) {
129
132
  lastError = err;
133
+ attempts.push({ service: serviceUrl, ok: false, error: err && err.message ? err.message : 'request_failed' });
130
134
  }
131
135
  }
132
136
 
133
137
  const msg = lastError ? lastError.message : 'unavailable';
134
- throw new Error(`external_ip_unavailable:${msg}`);
138
+ const failure = new Error(`external_ip_unavailable:${msg}`);
139
+ failure.attempts = attempts;
140
+ throw failure;
135
141
  }
136
142
 
137
143
  /**
@@ -168,13 +174,13 @@ async function getExternalIp(options = {}) {
168
174
  }
169
175
 
170
176
  try {
171
- const { ip, source } = await fetchExternalIp({
177
+ const { ip, source, attempts } = await fetchExternalIp({
172
178
  timeoutMs: options.timeoutMs,
173
179
  services: options.services
174
180
  });
175
181
  const checkedAt = new Date(nowMs).toISOString();
176
182
  atomicWriteJson(cacheFile, { ip, checked_at: checkedAt, source });
177
- return { ip, checkedAt, source, fromCache: false, stale: false };
183
+ return { ip, checkedAt, source, fromCache: false, stale: false, attempts: Array.isArray(attempts) ? attempts : null };
178
184
  } catch (err) {
179
185
  if (cached && cached.ip) {
180
186
  const cachedIp = parseIp(cached.ip);
@@ -184,11 +190,17 @@ async function getExternalIp(options = {}) {
184
190
  checkedAt: cached.checked_at || null,
185
191
  source: cached.source || 'cache',
186
192
  fromCache: true,
187
- stale: true
193
+ stale: true,
194
+ error: err && err.message ? err.message : 'external_ip_unavailable',
195
+ attempts: err && Array.isArray(err.attempts) ? err.attempts : null
188
196
  };
189
197
  }
190
198
  }
191
- return { ip: null, error: err.message };
199
+ return {
200
+ ip: null,
201
+ error: err && err.message ? err.message : 'external_ip_unavailable',
202
+ attempts: err && Array.isArray(err.attempts) ? err.attempts : null
203
+ };
192
204
  }
193
205
  }
194
206
 
@@ -197,4 +209,3 @@ module.exports = {
197
209
  fetchExternalIp,
198
210
  getExternalIp
199
211
  };
200
-
@@ -166,24 +166,23 @@ async function resolveInviteHost(options = {}) {
166
166
  options.refreshExternalIp && isPublicIpHostname(parsed.hostname)
167
167
  );
168
168
 
169
- if (!shouldReplaceWithExternalIp) {
170
- return {
171
- host: candidateHostWithPort,
172
- source: candidateSource,
173
- originalHost: candidateHostWithPort,
174
- warnings
175
- };
176
- }
177
-
178
- const external = await getExternalIp({
179
- ttlMs,
180
- timeoutMs: options.externalIpTimeoutMs,
181
- services: options.externalIpServices,
182
- cacheFile: options.externalIpCacheFile,
183
- forceRefresh: Boolean(options.forceRefreshExternalIp)
184
- });
185
-
186
- if (external && external.ip) {
169
+ const alwaysLookupExternalIp = Boolean(options.alwaysLookupExternalIp);
170
+ const wantsExternalIp = shouldReplaceWithExternalIp || alwaysLookupExternalIp;
171
+ const warnOnExternalIpFailure = options.warnOnExternalIpFailure !== undefined
172
+ ? Boolean(options.warnOnExternalIpFailure)
173
+ : shouldReplaceWithExternalIp;
174
+
175
+ const external = wantsExternalIp
176
+ ? await getExternalIp({
177
+ ttlMs,
178
+ timeoutMs: options.externalIpTimeoutMs,
179
+ services: options.externalIpServices,
180
+ cacheFile: options.externalIpCacheFile,
181
+ forceRefresh: Boolean(options.forceRefreshExternalIp)
182
+ })
183
+ : null;
184
+
185
+ if (shouldReplaceWithExternalIp && external && external.ip) {
187
186
  const finalHost = formatHostPort(external.ip, desiredPort);
188
187
  if (finalHost !== candidateHostWithPort) {
189
188
  warnings.push(
@@ -195,17 +194,21 @@ async function resolveInviteHost(options = {}) {
195
194
  source: 'external_ip',
196
195
  originalHost: candidateHostWithPort,
197
196
  externalIp: external.ip,
197
+ externalIpInfo: external,
198
198
  warnings
199
199
  };
200
200
  }
201
201
 
202
- warnings.push(
203
- `Invite host "${candidateHostWithPort}" may not be reachable from other machines, and external IP lookup failed. Set A2A_HOSTNAME="your-public-host:port".`
204
- );
202
+ if (wantsExternalIp && (!external || !external.ip) && warnOnExternalIpFailure) {
203
+ warnings.push(
204
+ `Invite host "${candidateHostWithPort}" may not be reachable from other machines, and external IP lookup failed. Set A2A_HOSTNAME="your-public-host:port".`
205
+ );
206
+ }
205
207
  return {
206
208
  host: candidateHostWithPort,
207
209
  source: candidateSource,
208
210
  originalHost: candidateHostWithPort,
211
+ externalIpInfo: external,
209
212
  warnings
210
213
  };
211
214
  }
package/src/lib/logger.js CHANGED
@@ -129,7 +129,28 @@ class LogStore {
129
129
  } catch (err) {
130
130
  // best effort
131
131
  }
132
- this._migrate();
132
+ const ok = this._ensureSchema();
133
+ if (!ok) {
134
+ // Prototyping mode: do not attempt DB migrations; keep the old file and start fresh.
135
+ const backupPath = `${this.dbPath}.legacy.${Date.now()}`;
136
+ try {
137
+ this.db.close();
138
+ } catch (err) {
139
+ // ignore
140
+ }
141
+ fs.renameSync(this.dbPath, backupPath);
142
+ this.db = new Database(this.dbPath);
143
+ try {
144
+ fs.chmodSync(this.dbPath, 0o600);
145
+ } catch (err) {
146
+ // best effort
147
+ }
148
+ const ok2 = this._ensureSchema();
149
+ if (!ok2) {
150
+ this._dbError = 'failed_to_initialize_log_db_schema';
151
+ return null;
152
+ }
153
+ }
133
154
  this._prepareStatements();
134
155
  return this.db;
135
156
  } catch (err) {
@@ -138,7 +159,7 @@ class LogStore {
138
159
  }
139
160
  }
140
161
 
141
- _migrate() {
162
+ _ensureSchema() {
142
163
  this.db.exec(`
143
164
  CREATE TABLE IF NOT EXISTS logs (
144
165
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -167,18 +188,9 @@ class LogStore {
167
188
  `);
168
189
 
169
190
  const columns = this.db.prepare(`PRAGMA table_info(logs)`).all();
170
- const hasErrorCode = columns.some(c => c.name === 'error_code');
171
- const hasStatusCode = columns.some(c => c.name === 'status_code');
172
- const hasHint = columns.some(c => c.name === 'hint');
173
- if (!hasErrorCode) {
174
- this.db.exec(`ALTER TABLE logs ADD COLUMN error_code TEXT`);
175
- }
176
- if (!hasStatusCode) {
177
- this.db.exec(`ALTER TABLE logs ADD COLUMN status_code INTEGER`);
178
- }
179
- if (!hasHint) {
180
- this.db.exec(`ALTER TABLE logs ADD COLUMN hint TEXT`);
181
- }
191
+ const names = new Set(columns.map(c => c && c.name).filter(Boolean));
192
+ const required = ['timestamp', 'level', 'component', 'message', 'error_code', 'status_code', 'hint', 'data'];
193
+ return required.every((name) => names.has(name));
182
194
  }
183
195
 
184
196
  _prepareStatements() {