@axlsdk/axl 0.27.0 → 0.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +166 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +57 -6
- package/dist/index.d.ts +57 -6
- package/dist/index.js +166 -20
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -602,7 +602,7 @@ var sharedGuard = typeof installedGuard === "object" && installedGuard !== null
|
|
|
602
602
|
var buildPath = typeof __AXL_BUILD_PATH__ === "string" ? __AXL_BUILD_PATH__ : "<source>";
|
|
603
603
|
var thisCopy = {
|
|
604
604
|
path: buildPath.startsWith("file:") ? (0, import_node_url.fileURLToPath)(buildPath) : buildPath,
|
|
605
|
-
version: true ? "0.27.
|
|
605
|
+
version: true ? "0.27.1" : "unknown"
|
|
606
606
|
};
|
|
607
607
|
function warnCrossCopy(other, joined) {
|
|
608
608
|
if (other === thisCopy) return;
|
|
@@ -17280,7 +17280,7 @@ ${appendedSchemaText}`;
|
|
|
17280
17280
|
`ctx.checkpoint name "${name}" is reserved \u2014 names starting with "__auto/" (case-insensitive) are used by the runtime for ask/spawn/race/parallel/map auto-checkpointing.`
|
|
17281
17281
|
);
|
|
17282
17282
|
}
|
|
17283
|
-
return this._checkpoint(name, fn);
|
|
17283
|
+
return this._checkpoint(name, fn, { saveRequired: true });
|
|
17284
17284
|
}
|
|
17285
17285
|
/**
|
|
17286
17286
|
* Internal checkpoint implementation shared by both the public
|
|
@@ -17310,7 +17310,17 @@ ${appendedSchemaText}`;
|
|
|
17310
17310
|
return saved;
|
|
17311
17311
|
}
|
|
17312
17312
|
const result = await fn();
|
|
17313
|
-
|
|
17313
|
+
if (!options?.saveRequired && this.autoCheckpointCounters.saveFailed) return result;
|
|
17314
|
+
try {
|
|
17315
|
+
await this.stateStore.saveCheckpoint(this.executionId, name, result);
|
|
17316
|
+
} catch (err) {
|
|
17317
|
+
if (options?.saveRequired) throw err;
|
|
17318
|
+
this.autoCheckpointCounters.saveFailed = true;
|
|
17319
|
+
console.error(
|
|
17320
|
+
`[axl] Failed to save checkpoint "${name}" for executionId=${this.executionId}; a replay would run this step and later ones again: ${err instanceof Error ? err.message : String(err)}`
|
|
17321
|
+
);
|
|
17322
|
+
return result;
|
|
17323
|
+
}
|
|
17314
17324
|
this.emitEvent({
|
|
17315
17325
|
type: "checkpoint_save",
|
|
17316
17326
|
...agentStamp,
|
|
@@ -20231,11 +20241,18 @@ var Session = class _Session {
|
|
|
20231
20241
|
try {
|
|
20232
20242
|
const completion = axlStream.promise.then(
|
|
20233
20243
|
(result) => this.commitHistory(history, result).catch((err) => {
|
|
20234
|
-
|
|
20235
|
-
|
|
20236
|
-
|
|
20237
|
-
|
|
20238
|
-
|
|
20244
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
20245
|
+
try {
|
|
20246
|
+
this.runtime.emit("error", {
|
|
20247
|
+
type: "session_history_save_failed",
|
|
20248
|
+
sessionId: this.sessionId,
|
|
20249
|
+
error
|
|
20250
|
+
});
|
|
20251
|
+
} catch {
|
|
20252
|
+
console.error(
|
|
20253
|
+
`[axl] Failed to save session history for sessionId=${this.sessionId}: ${error}`
|
|
20254
|
+
);
|
|
20255
|
+
}
|
|
20239
20256
|
}),
|
|
20240
20257
|
() => void 0
|
|
20241
20258
|
);
|
|
@@ -22302,7 +22319,11 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
22302
22319
|
observation
|
|
22303
22320
|
});
|
|
22304
22321
|
if (this.stateStore.deleteCheckpoints) {
|
|
22305
|
-
await this.stateStore.deleteCheckpoints(execInfo.executionId)
|
|
22322
|
+
await this.stateStore.deleteCheckpoints(execInfo.executionId).catch((err) => {
|
|
22323
|
+
console.error(
|
|
22324
|
+
`[axl] Failed to delete checkpoints for executionId=${execInfo.executionId}: ` + (err instanceof Error ? err.message : String(err))
|
|
22325
|
+
);
|
|
22326
|
+
});
|
|
22306
22327
|
}
|
|
22307
22328
|
span?.setAttribute("axl.workflow.cost", execInfo.totalCost);
|
|
22308
22329
|
span?.setAttribute("axl.workflow.duration", execInfo.duration);
|
|
@@ -22374,7 +22395,10 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
22374
22395
|
const chain = this.stateStore.saveExecution(snapshot).then(() => {
|
|
22375
22396
|
return this.streamingFlusher?.finalize(id).catch(() => {
|
|
22376
22397
|
});
|
|
22377
|
-
}).catch(() => {
|
|
22398
|
+
}).catch((err) => {
|
|
22399
|
+
console.error(
|
|
22400
|
+
`[axl] Failed to save execution history for executionId=${id}: ` + (err instanceof Error ? err.message : String(err))
|
|
22401
|
+
);
|
|
22378
22402
|
}).then(() => void 0);
|
|
22379
22403
|
this.persistInflight.add(chain);
|
|
22380
22404
|
const pending = { snapshot, settled: chain };
|
|
@@ -24797,12 +24821,108 @@ function warnIfRedisExecutionRetentionUnset(opts) {
|
|
|
24797
24821
|
warnExecutionRetentionUnset("RedisStore");
|
|
24798
24822
|
}
|
|
24799
24823
|
}
|
|
24824
|
+
var CLOSE_TIMEOUT_MS = 2e3;
|
|
24825
|
+
function reconnectDelayMs(retries) {
|
|
24826
|
+
return Math.min(2 ** retries * 50, 2e3) + Math.floor(Math.random() * 200);
|
|
24827
|
+
}
|
|
24828
|
+
function abandonConnection(client) {
|
|
24829
|
+
try {
|
|
24830
|
+
client.destroy?.();
|
|
24831
|
+
} catch {
|
|
24832
|
+
}
|
|
24833
|
+
client.unref?.();
|
|
24834
|
+
}
|
|
24835
|
+
function errorMessage(error) {
|
|
24836
|
+
return error instanceof Error ? error.message : String(error);
|
|
24837
|
+
}
|
|
24838
|
+
function connectionEventReporter(client, onConnectionEvent) {
|
|
24839
|
+
if (onConnectionEvent) {
|
|
24840
|
+
const reportFailure = (error) => console.error(`[axl] RedisStore: onConnectionEvent failed: ${errorMessage(error)}`);
|
|
24841
|
+
return (event) => {
|
|
24842
|
+
try {
|
|
24843
|
+
Promise.resolve(onConnectionEvent(event)).catch(reportFailure);
|
|
24844
|
+
} catch (error) {
|
|
24845
|
+
reportFailure(error);
|
|
24846
|
+
}
|
|
24847
|
+
};
|
|
24848
|
+
}
|
|
24849
|
+
let outageLogged = false;
|
|
24850
|
+
return (event) => {
|
|
24851
|
+
if (event.type === "reconnected") {
|
|
24852
|
+
outageLogged = false;
|
|
24853
|
+
console.warn("[axl] RedisStore: reconnected to Redis.");
|
|
24854
|
+
} else if (!outageLogged) {
|
|
24855
|
+
outageLogged = client.isReady === false;
|
|
24856
|
+
console.warn(
|
|
24857
|
+
`[axl] RedisStore: Redis connection lost (${event.error.message}). Store calls fail until it reconnects, which happens automatically.`
|
|
24858
|
+
);
|
|
24859
|
+
}
|
|
24860
|
+
};
|
|
24861
|
+
}
|
|
24862
|
+
async function connectClient(createClient, opts) {
|
|
24863
|
+
let established = false;
|
|
24864
|
+
const client = createClient({
|
|
24865
|
+
...opts.url ? { url: opts.url } : {},
|
|
24866
|
+
// A command accepted just before a drop rejects instead of waiting for
|
|
24867
|
+
// the reconnect.
|
|
24868
|
+
disableOfflineQueue: true,
|
|
24869
|
+
socket: { reconnectStrategy: (retries) => established ? reconnectDelayMs(retries) : false }
|
|
24870
|
+
});
|
|
24871
|
+
const reportConnectionEvent = connectionEventReporter(client, opts.onConnectionEvent);
|
|
24872
|
+
client.on("error", (error) => {
|
|
24873
|
+
if (!established || client.isOpen === false) return;
|
|
24874
|
+
reportConnectionEvent({
|
|
24875
|
+
type: "error",
|
|
24876
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
24877
|
+
});
|
|
24878
|
+
});
|
|
24879
|
+
client.on("connect", () => {
|
|
24880
|
+
if (client.isOpen === false) client.unref?.();
|
|
24881
|
+
});
|
|
24882
|
+
client.on("ready", () => {
|
|
24883
|
+
if (!established) {
|
|
24884
|
+
established = true;
|
|
24885
|
+
} else if (client.isOpen !== false) {
|
|
24886
|
+
reportConnectionEvent({ type: "reconnected" });
|
|
24887
|
+
}
|
|
24888
|
+
});
|
|
24889
|
+
await client.connect();
|
|
24890
|
+
return client;
|
|
24891
|
+
}
|
|
24800
24892
|
var RedisStore = class _RedisStore {
|
|
24801
|
-
constructor(
|
|
24802
|
-
this.
|
|
24893
|
+
constructor(connection, keyPrefix, ttls) {
|
|
24894
|
+
this.connection = connection;
|
|
24803
24895
|
this.keyPrefix = keyPrefix;
|
|
24804
24896
|
this.ttls = ttls;
|
|
24805
24897
|
}
|
|
24898
|
+
/**
|
|
24899
|
+
* Whether the store can send commands right now, for a health check or for
|
|
24900
|
+
* telling an outage from other failures when a call rejects. `false` while
|
|
24901
|
+
* reconnecting and after `close()`.
|
|
24902
|
+
*/
|
|
24903
|
+
get isConnected() {
|
|
24904
|
+
return this.connection.isOpen !== false && this.connection.isReady !== false;
|
|
24905
|
+
}
|
|
24906
|
+
/**
|
|
24907
|
+
* The client for sending a command, failing immediately while disconnected.
|
|
24908
|
+
*
|
|
24909
|
+
* Every command goes through here because node-redis's `disableOfflineQueue`
|
|
24910
|
+
* rejects single commands but not `multi().exec()`, which would wait for a
|
|
24911
|
+
* reconnect attempt to fail or succeed. Nothing may `await` between
|
|
24912
|
+
* `client.multi()` and `.exec()`, or a transaction could get past this check.
|
|
24913
|
+
*
|
|
24914
|
+
* A closed store isn't "unavailable": it falls through to node-redis's own
|
|
24915
|
+
* "The client is closed" rejection.
|
|
24916
|
+
*/
|
|
24917
|
+
get client() {
|
|
24918
|
+
if (this.connection.isOpen !== false && this.connection.isReady === false) {
|
|
24919
|
+
throw new AxlError(
|
|
24920
|
+
"REDIS_UNAVAILABLE",
|
|
24921
|
+
"RedisStore: not connected to Redis, so the command was not sent. The store reconnects automatically; retry once Redis is back."
|
|
24922
|
+
);
|
|
24923
|
+
}
|
|
24924
|
+
return this.connection;
|
|
24925
|
+
}
|
|
24806
24926
|
/**
|
|
24807
24927
|
* History kinds with a prune running in this process. A save skips its
|
|
24808
24928
|
* prune while another one of the same kind is in flight: the running one
|
|
@@ -24872,8 +24992,7 @@ var RedisStore = class _RedisStore {
|
|
|
24872
24992
|
if (err instanceof Error && err.message.includes("createClient")) throw err;
|
|
24873
24993
|
throw new Error("redis is required for RedisStore. Install it with: npm install redis");
|
|
24874
24994
|
}
|
|
24875
|
-
const client =
|
|
24876
|
-
await client.connect();
|
|
24995
|
+
const client = await connectClient(createClient, opts);
|
|
24877
24996
|
const resolveCategory = (override) => {
|
|
24878
24997
|
if (override === null) return null;
|
|
24879
24998
|
if (typeof override === "number") return override;
|
|
@@ -24897,8 +25016,13 @@ var RedisStore = class _RedisStore {
|
|
|
24897
25016
|
const store = new _RedisStore(client, keyPrefix, effectiveTtls);
|
|
24898
25017
|
warnIfRedisExecutionRetentionUnset(opts);
|
|
24899
25018
|
if (!opts.skipMigration) {
|
|
24900
|
-
|
|
24901
|
-
|
|
25019
|
+
try {
|
|
25020
|
+
await store.backfillExecutionIndex();
|
|
25021
|
+
await store.backfillEvalIndex();
|
|
25022
|
+
} catch (error) {
|
|
25023
|
+
abandonConnection(client);
|
|
25024
|
+
throw error;
|
|
25025
|
+
}
|
|
24902
25026
|
}
|
|
24903
25027
|
return store;
|
|
24904
25028
|
}
|
|
@@ -25167,7 +25291,7 @@ var RedisStore = class _RedisStore {
|
|
|
25167
25291
|
}
|
|
25168
25292
|
}
|
|
25169
25293
|
if (dead.length > 0) {
|
|
25170
|
-
this.
|
|
25294
|
+
this.connection.sRem(this.pendingExecSetKey(), dead).catch((err) => {
|
|
25171
25295
|
console.error(
|
|
25172
25296
|
`[axl] RedisStore: failed to prune ${dead.length} stale pending-exec ids: ` + (err instanceof Error ? err.message : String(err))
|
|
25173
25297
|
);
|
|
@@ -25213,7 +25337,7 @@ var RedisStore = class _RedisStore {
|
|
|
25213
25337
|
}
|
|
25214
25338
|
const ttl = this.ttlFor("memory");
|
|
25215
25339
|
if (ttl !== void 0) {
|
|
25216
|
-
this.
|
|
25340
|
+
this.connection.expire(this.memoryKey(scope), ttl).catch((err) => {
|
|
25217
25341
|
console.error(
|
|
25218
25342
|
`[axl] RedisStore: failed to set TTL on migrated memory key ${this.memoryKey(scope)}: ` + (err instanceof Error ? err.message : String(err))
|
|
25219
25343
|
);
|
|
@@ -25505,6 +25629,7 @@ var RedisStore = class _RedisStore {
|
|
|
25505
25629
|
try {
|
|
25506
25630
|
await this.pruneHistory(index);
|
|
25507
25631
|
} catch (err) {
|
|
25632
|
+
if (this.connection.isReady === false) return;
|
|
25508
25633
|
warnDiagnosticOnce(
|
|
25509
25634
|
`redis_history_prune_failed\0${index.kind}`,
|
|
25510
25635
|
`RedisStore: removing expired ${index.label} index entries failed (${err instanceof Error ? err.message : String(err)}). The record was saved; the next save retries the cleanup. Listings stay correct meanwhile. The cleanup runs a Lua script, so the Redis user needs permission to run EVAL.`,
|
|
@@ -25709,10 +25834,31 @@ var RedisStore = class _RedisStore {
|
|
|
25709
25834
|
* must not get a rejected promise for it. `MemoryStore.close()` and
|
|
25710
25835
|
* better-sqlite3's `close()` are both no-ops the second time; node-redis
|
|
25711
25836
|
* instead rejects with "The client is closed", so absorb that here.
|
|
25837
|
+
*
|
|
25838
|
+
* Bounded, so a shutdown can't hang on Redis. `QUIT` lets queued commands
|
|
25839
|
+
* finish on a healthy connection, but node-redis never settles it if the
|
|
25840
|
+
* socket closes first, and a stalled server may never answer it. While
|
|
25841
|
+
* disconnected it couldn't be sent at all.
|
|
25712
25842
|
*/
|
|
25713
25843
|
async close() {
|
|
25714
|
-
if (this.
|
|
25715
|
-
|
|
25844
|
+
if (this.connection.isOpen === false) return;
|
|
25845
|
+
if (this.connection.isReady === false) {
|
|
25846
|
+
abandonConnection(this.connection);
|
|
25847
|
+
return;
|
|
25848
|
+
}
|
|
25849
|
+
let timer;
|
|
25850
|
+
try {
|
|
25851
|
+
await Promise.race([
|
|
25852
|
+
this.connection.quit(),
|
|
25853
|
+
new Promise((_resolve, reject) => {
|
|
25854
|
+
timer = setTimeout(() => reject(new Error("QUIT timed out")), CLOSE_TIMEOUT_MS);
|
|
25855
|
+
})
|
|
25856
|
+
]);
|
|
25857
|
+
} catch {
|
|
25858
|
+
abandonConnection(this.connection);
|
|
25859
|
+
} finally {
|
|
25860
|
+
clearTimeout(timer);
|
|
25861
|
+
}
|
|
25716
25862
|
}
|
|
25717
25863
|
async deleteCheckpoints(executionId) {
|
|
25718
25864
|
await this.client.del(this.checkpointKey(executionId));
|